From a512176b4bdbf0f0471a9b9089f4a936c14e2732 Mon Sep 17 00:00:00 2001 From: Duong Do Minh Chau Date: Thu, 12 Nov 2020 16:38:44 +0700 Subject: Add options to hide some context menu entries This commit add options to hide the following context menu entries: - Add to Places - Copy Location - Duplicate Here - Open in New Tab and Open in New Tabs - Open in New Window - Sort By - View Mode The Services settings page is renamed to Context Menu ShowCopyMoveMenu option is moved from GeneralSettings to ContextMenuSettings BUG: 314594 --- src/CMakeLists.txt | 26 +- src/dolphincontextmenu.cpp | 87 ++-- .../contextmenu/contextmenusettingspage.cpp | 346 +++++++++++++++ src/settings/contextmenu/contextmenusettingspage.h | 69 +++ src/settings/contextmenu/servicemenu.knsrc | 9 + .../servicemenuinstaller/CMakeLists.txt | 15 + .../contextmenu/servicemenuinstaller/Messages.sh | 2 + .../servicemenuinstaller/servicemenuinstaller.cpp | 462 +++++++++++++++++++++ .../test/service_menu_deinstallation_test.rb | 112 +++++ .../test/service_menu_installation_test.rb | 106 +++++ src/settings/contextmenu/test/test_helper.rb | 18 + src/settings/contextmenu/test/test_run.rb | 11 + src/settings/dolphin_contextmenusettings.kcfg | 42 ++ src/settings/dolphin_contextmenusettings.kcfgc | 4 + src/settings/dolphin_generalsettings.kcfg | 4 - src/settings/dolphinsettingsdialog.cpp | 16 +- src/settings/kcm/kcmdolphincontextmenu.cpp | 47 +++ src/settings/kcm/kcmdolphincontextmenu.desktop | 62 +++ src/settings/kcm/kcmdolphincontextmenu.h | 32 ++ src/settings/kcm/kcmdolphinservices.cpp | 46 -- src/settings/kcm/kcmdolphinservices.desktop | 204 --------- src/settings/kcm/kcmdolphinservices.h | 32 -- src/settings/services/servicemenu.knsrc | 9 - .../services/servicemenuinstaller/CMakeLists.txt | 15 - .../services/servicemenuinstaller/Messages.sh | 2 - .../servicemenuinstaller/servicemenuinstaller.cpp | 462 --------------------- src/settings/services/servicessettingspage.cpp | 287 ------------- src/settings/services/servicessettingspage.h | 69 --- .../test/service_menu_deinstallation_test.rb | 112 ----- .../test/service_menu_installation_test.rb | 106 ----- src/settings/services/test/test_helper.rb | 18 - src/settings/services/test/test_run.rb | 11 - src/tests/CMakeLists.txt | 2 +- 33 files changed, 1412 insertions(+), 1433 deletions(-) create mode 100644 src/settings/contextmenu/contextmenusettingspage.cpp create mode 100644 src/settings/contextmenu/contextmenusettingspage.h create mode 100644 src/settings/contextmenu/servicemenu.knsrc create mode 100644 src/settings/contextmenu/servicemenuinstaller/CMakeLists.txt create mode 100755 src/settings/contextmenu/servicemenuinstaller/Messages.sh create mode 100644 src/settings/contextmenu/servicemenuinstaller/servicemenuinstaller.cpp create mode 100644 src/settings/contextmenu/test/service_menu_deinstallation_test.rb create mode 100644 src/settings/contextmenu/test/service_menu_installation_test.rb create mode 100644 src/settings/contextmenu/test/test_helper.rb create mode 100755 src/settings/contextmenu/test/test_run.rb create mode 100644 src/settings/dolphin_contextmenusettings.kcfg create mode 100644 src/settings/dolphin_contextmenusettings.kcfgc create mode 100644 src/settings/kcm/kcmdolphincontextmenu.cpp create mode 100644 src/settings/kcm/kcmdolphincontextmenu.desktop create mode 100644 src/settings/kcm/kcmdolphincontextmenu.h delete mode 100644 src/settings/kcm/kcmdolphinservices.cpp delete mode 100644 src/settings/kcm/kcmdolphinservices.desktop delete mode 100644 src/settings/kcm/kcmdolphinservices.h delete mode 100644 src/settings/services/servicemenu.knsrc delete mode 100644 src/settings/services/servicemenuinstaller/CMakeLists.txt delete mode 100755 src/settings/services/servicemenuinstaller/Messages.sh delete mode 100644 src/settings/services/servicemenuinstaller/servicemenuinstaller.cpp delete mode 100644 src/settings/services/servicessettingspage.cpp delete mode 100644 src/settings/services/servicessettingspage.h delete mode 100644 src/settings/services/test/service_menu_deinstallation_test.rb delete mode 100644 src/settings/services/test/service_menu_installation_test.rb delete mode 100644 src/settings/services/test/test_helper.rb delete mode 100755 src/settings/services/test/test_run.rb (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 87bc2d3c2..1d7084854 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -134,6 +134,7 @@ kconfig_add_kcfg_files(dolphinprivate_LIB_SRCS GENERATE_MOC settings/dolphin_detailsmodesettings.kcfgc settings/dolphin_iconsmodesettings.kcfgc settings/dolphin_generalsettings.kcfgc + settings/dolphin_contextmenusettings.kcfgc settings/dolphin_versioncontrolsettings.kcfgc ) @@ -238,7 +239,7 @@ set(dolphinstatic_SRCS settings/general/statusbarsettingspage.cpp settings/dolphinsettingsdialog.cpp settings/navigation/navigationsettingspage.cpp - settings/services/servicessettingspage.cpp + settings/contextmenu/contextmenusettingspage.cpp settings/settingspagebase.cpp settings/serviceitemdelegate.cpp settings/servicemodel.cpp @@ -285,6 +286,7 @@ kconfig_add_kcfg_files(dolphinstatic_SRCS GENERATE_MOC settings/dolphin_compactmodesettings.kcfgc settings/dolphin_detailsmodesettings.kcfgc settings/dolphin_generalsettings.kcfgc + settings/dolphin_contextmenusettings.kcfgc settings/dolphin_iconsmodesettings.kcfgc search/dolphin_searchsettings.kcfgc settings/dolphin_versioncontrolsettings.kcfgc @@ -369,9 +371,9 @@ set(kcm_dolphinnavigation_PART_SRCS settings/navigation/navigationsettingspage.cpp settings/settingspagebase.cpp) -set(kcm_dolphinservices_PART_SRCS - settings/kcm/kcmdolphinservices.cpp - settings/services/servicessettingspage.cpp +set(kcm_dolphincontextmenu_PART_SRCS + settings/kcm/kcmdolphincontextmenu.cpp + settings/contextmenu/contextmenusettingspage.cpp settings/settingspagebase.cpp settings/serviceitemdelegate.cpp settings/servicemodel.cpp) @@ -398,8 +400,9 @@ kconfig_add_kcfg_files(kcm_dolphinviewmodes_PART_SRCS kconfig_add_kcfg_files(kcm_dolphinnavigation_PART_SRCS settings/dolphin_generalsettings.kcfgc) -kconfig_add_kcfg_files(kcm_dolphinservices_PART_SRCS +kconfig_add_kcfg_files(kcm_dolphincontextmenu_PART_SRCS settings/dolphin_generalsettings.kcfgc + settings/dolphin_contextmenusettings.kcfgc settings/dolphin_versioncontrolsettings.kcfgc) kconfig_add_kcfg_files(kcm_dolphingeneral_PART_SRCS @@ -410,29 +413,29 @@ if(NOT WIN32) # The settings are still accessible from the hamburger menu add_library(kcm_dolphinviewmodes MODULE ${kcm_dolphinviewmodes_PART_SRCS}) add_library(kcm_dolphinnavigation MODULE ${kcm_dolphinnavigation_PART_SRCS}) - add_library(kcm_dolphinservices MODULE ${kcm_dolphinservices_PART_SRCS}) + add_library(kcm_dolphincontextmenu MODULE ${kcm_dolphincontextmenu_PART_SRCS}) add_library(kcm_dolphingeneral MODULE ${kcm_dolphingeneral_PART_SRCS}) target_link_libraries(kcm_dolphinviewmodes dolphinprivate) target_link_libraries(kcm_dolphinnavigation dolphinprivate) - target_link_libraries(kcm_dolphinservices dolphinprivate) + target_link_libraries(kcm_dolphincontextmenu dolphinprivate) target_link_libraries(kcm_dolphingeneral dolphinprivate) install( FILES org.kde.dolphin.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR} ) install( FILES settings/kcm/kcmdolphinviewmodes.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} ) install( FILES settings/kcm/kcmdolphinnavigation.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} ) - install( FILES settings/kcm/kcmdolphinservices.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} ) + install( FILES settings/kcm/kcmdolphincontextmenu.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} ) install( FILES settings/kcm/kcmdolphingeneral.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} ) install(TARGETS kcm_dolphinviewmodes DESTINATION ${KDE_INSTALL_PLUGINDIR} ) install(TARGETS kcm_dolphinnavigation DESTINATION ${KDE_INSTALL_PLUGINDIR} ) - install(TARGETS kcm_dolphinservices DESTINATION ${KDE_INSTALL_PLUGINDIR} ) + install(TARGETS kcm_dolphincontextmenu DESTINATION ${KDE_INSTALL_PLUGINDIR} ) install(TARGETS kcm_dolphingeneral DESTINATION ${KDE_INSTALL_PLUGINDIR} ) endif() if(NOT WIN32) - add_subdirectory(settings/services/servicemenuinstaller) - install( FILES settings/services/servicemenu.knsrc DESTINATION ${KDE_INSTALL_KNSRCDIR} ) + add_subdirectory(settings/contextmenu/servicemenuinstaller) + install( FILES settings/contextmenu/servicemenu.knsrc DESTINATION ${KDE_INSTALL_KNSRCDIR} ) endif() ########### install files ############### @@ -447,6 +450,7 @@ install( install( FILES settings/dolphin_directoryviewpropertysettings.kcfg settings/dolphin_generalsettings.kcfg + settings/dolphin_contextmenusettings.kcfg settings/dolphin_compactmodesettings.kcfg settings/dolphin_iconsmodesettings.kcfg settings/dolphin_detailsmodesettings.kcfg diff --git a/src/dolphincontextmenu.cpp b/src/dolphincontextmenu.cpp index eabd81e22..2d06449e7 100644 --- a/src/dolphincontextmenu.cpp +++ b/src/dolphincontextmenu.cpp @@ -7,6 +7,7 @@ #include "dolphincontextmenu.h" #include "dolphin_generalsettings.h" +#include "dolphin_contextmenusettings.h" #include "dolphinmainwindow.h" #include "dolphinnewfilemenu.h" #include "dolphinplacesmodelsingleton.h" @@ -185,32 +186,33 @@ void DolphinContextMenu::openTrashItemContextMenu() void DolphinContextMenu::addDirectoryItemContextMenu(KFileItemActions &fileItemActions) { // insert 'Open in new window' and 'Open in new tab' entries - const KFileItemListProperties& selectedItemsProps = selectedItemsProperties(); - - addAction(m_mainWindow->actionCollection()->action(QStringLiteral("open_in_new_tab"))); - addAction(m_mainWindow->actionCollection()->action(QStringLiteral("open_in_new_window"))); + if (ContextMenuSettings::showOpenInNewTab()) { + addAction(m_mainWindow->actionCollection()->action(QStringLiteral("open_in_new_tab"))); + } + if (ContextMenuSettings::showOpenInNewWindow()) { + addAction(m_mainWindow->actionCollection()->action(QStringLiteral("open_in_new_window"))); + } // Insert 'Open With' entries addOpenWithActions(fileItemActions); // set up 'Create New' menu - DolphinNewFileMenu* newFileMenu = new DolphinNewFileMenu(m_mainWindow->actionCollection(), m_mainWindow); - const DolphinView* view = m_mainWindow->activeViewContainer()->view(); - newFileMenu->setViewShowsHiddenFiles(view->hiddenFilesShown()); - newFileMenu->checkUpToDate(); - newFileMenu->setPopupFiles(QList() << m_fileInfo.url()); - newFileMenu->setEnabled(selectedItemsProps.supportsWriting()); - connect(newFileMenu, &DolphinNewFileMenu::fileCreated, newFileMenu, &DolphinNewFileMenu::deleteLater); - connect(newFileMenu, &DolphinNewFileMenu::directoryCreated, newFileMenu, &DolphinNewFileMenu::deleteLater); - - QMenu* menu = newFileMenu->menu(); - menu->setTitle(i18nc("@title:menu Create new folder, file, link, etc.", "Create New")); - menu->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); - menu->setParent(this, Qt::Popup); - addMenu(menu); - - addSeparator(); + DolphinNewFileMenu* newFileMenu = new DolphinNewFileMenu(m_mainWindow->actionCollection(), m_mainWindow); + const DolphinView* view = m_mainWindow->activeViewContainer()->view(); + newFileMenu->setViewShowsHiddenFiles(view->hiddenFilesShown()); + newFileMenu->checkUpToDate(); + newFileMenu->setPopupFiles(QList() << m_fileInfo.url()); + newFileMenu->setEnabled(selectedItemsProps.supportsWriting()); + connect(newFileMenu, &DolphinNewFileMenu::fileCreated, newFileMenu, &DolphinNewFileMenu::deleteLater); + connect(newFileMenu, &DolphinNewFileMenu::directoryCreated, newFileMenu, &DolphinNewFileMenu::deleteLater); + + QMenu* menu = newFileMenu->menu(); + menu->setTitle(i18nc("@title:menu Create new folder, file, link, etc.", "Create New")); + menu->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); + addMenu(menu); + + addSeparator(); } void DolphinContextMenu::openItemContextMenu() @@ -271,7 +273,7 @@ void DolphinContextMenu::openItemContextMenu() } } - if (selectionHasOnlyDirs) { + if (selectionHasOnlyDirs && ContextMenuSettings::showOpenInNewTab()) { // insert 'Open in new tab' entry addAction(m_mainWindow->actionCollection()->action(QStringLiteral("open_in_new_tabs"))); } @@ -284,7 +286,7 @@ void DolphinContextMenu::openItemContextMenu() addAdditionalActions(fileItemActions, selectedItemsProps); // insert 'Copy To' and 'Move To' sub menus - if (GeneralSettings::showCopyMoveMenu()) { + if (ContextMenuSettings::showCopyMoveMenu()) { m_copyToMenu.setUrls(m_selectedItems.urlList()); m_copyToMenu.setReadOnly(!selectedItemsProps.supportsWriting()); m_copyToMenu.setAutoErrorHandlingEnabled(true); @@ -334,14 +336,22 @@ void DolphinContextMenu::openViewportContextMenu() } // Insert 'Add to Places' entry if it's not already in the places panel - if (!placeExists(m_mainWindow->activeViewContainer()->url())) { + if (ContextMenuSettings::showAddToPlaces() && + !placeExists(m_mainWindow->activeViewContainer()->url())) { addAction(m_mainWindow->actionCollection()->action(QStringLiteral("add_to_places"))); } addSeparator(); // Insert 'Sort By' and 'View Mode' - addAction(m_mainWindow->actionCollection()->action(QStringLiteral("sort"))); - addAction(m_mainWindow->actionCollection()->action(QStringLiteral("view_mode"))); + if (ContextMenuSettings::showSortBy()) { + addAction(m_mainWindow->actionCollection()->action(QStringLiteral("sort"))); + } + if (ContextMenuSettings::showViewMode()) { + addAction(m_mainWindow->actionCollection()->action(QStringLiteral("view_mode"))); + } + if (ContextMenuSettings::showSortBy() || ContextMenuSettings::showViewMode()) { + addSeparator(); + } addAdditionalActions(fileItemActions, baseUrlProperties); addCustomActions(); @@ -363,25 +373,30 @@ void DolphinContextMenu::insertDefaultItemActions(const KFileItemListProperties& // Insert 'Cut', 'Copy', 'Copy Location' and 'Paste' addAction(collection->action(KStandardAction::name(KStandardAction::Cut))); addAction(collection->action(KStandardAction::name(KStandardAction::Copy))); - QAction* copyPathAction = collection->action(QString("copy_location")); - copyPathAction->setEnabled(m_selectedItems.size() == 1); - addAction(copyPathAction); + if (ContextMenuSettings::showCopyLocation()) { + QAction* copyPathAction = collection->action(QString("copy_location")); + copyPathAction->setEnabled(m_selectedItems.size() == 1); + addAction(copyPathAction); + } QAction* pasteAction = createPasteAction(); if (pasteAction) { addAction(pasteAction); } - addAction(m_mainWindow->actionCollection()->action(QStringLiteral("duplicate"))); + + // Insert 'Duplicate Here' + if (ContextMenuSettings::showDuplicateHere()) { + addAction(m_mainWindow->actionCollection()->action(QStringLiteral("duplicate"))); + } // Insert 'Rename' addAction(collection->action(KStandardAction::name(KStandardAction::RenameFile))); - // insert 'Add to Places' entry if appropriate - if (m_selectedItems.count() == 1) { - if (m_fileInfo.isDir()) { - if (!placeExists(m_fileInfo.url())) { - addAction(m_mainWindow->actionCollection()->action(QStringLiteral("add_to_places"))); - } - } + // Insert 'Add to Places' entry if appropriate + if (ContextMenuSettings::showAddToPlaces() && + m_selectedItems.count() == 1 && + m_fileInfo.isDir() && + !placeExists(m_fileInfo.url())) { + addAction(m_mainWindow->actionCollection()->action(QStringLiteral("add_to_places"))); } addSeparator(); diff --git a/src/settings/contextmenu/contextmenusettingspage.cpp b/src/settings/contextmenu/contextmenusettingspage.cpp new file mode 100644 index 000000000..4f126d3e2 --- /dev/null +++ b/src/settings/contextmenu/contextmenusettingspage.cpp @@ -0,0 +1,346 @@ +/* + * SPDX-FileCopyrightText: 2009-2010 Peter Penz + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "contextmenusettingspage.h" + +#include "dolphin_generalsettings.h" +#include "dolphin_versioncontrolsettings.h" +#include "dolphin_contextmenusettings.h" +#include "settings/serviceitemdelegate.h" +#include "settings/servicemodel.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + const bool ShowDeleteDefault = false; + const char VersionControlServicePrefix[] = "_version_control_"; + const char DeleteService[] = "_delete"; + const char CopyToMoveToService[] ="_copy_to_move_to"; + const char AddToPlacesService[] = "_add_to_places"; + const char SortByService[] = "_sort_by"; + const char ViewModeService[] = "_view_mode"; + const char OpenInNewTabService[] = "_open_in_new_tab"; + const char OpenInNewWindowService[] = "_open_in_new_window"; + const char CopyLocationService[] = "_copy_location"; + const char DuplicateHereService[] = "_duplicate_here"; +} + +ContextMenuSettingsPage::ContextMenuSettingsPage(QWidget* parent) : + SettingsPageBase(parent), + m_initialized(false), + m_serviceModel(nullptr), + m_sortModel(nullptr), + m_listView(nullptr), + m_enabledVcsPlugins() +{ + QVBoxLayout* topLayout = new QVBoxLayout(this); + + QLabel* label = new QLabel(i18nc("@label:textbox", + "Select which services should " + "be shown in the context menu:"), this); + label->setWordWrap(true); + m_searchLineEdit = new QLineEdit(this); + m_searchLineEdit->setPlaceholderText(i18nc("@label:textbox", "Search...")); + connect(m_searchLineEdit, &QLineEdit::textChanged, this, [this](const QString &filter){ + m_sortModel->setFilterFixedString(filter); + }); + + m_listView = new QListView(this); + QScroller::grabGesture(m_listView->viewport(), QScroller::TouchGesture); + + auto *delegate = new ServiceItemDelegate(m_listView, m_listView); + m_serviceModel = new ServiceModel(this); + m_sortModel = new QSortFilterProxyModel(this); + m_sortModel->setSourceModel(m_serviceModel); + m_sortModel->setSortRole(Qt::DisplayRole); + m_sortModel->setSortLocaleAware(true); + m_sortModel->setFilterRole(Qt::DisplayRole); + m_sortModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + m_listView->setModel(m_sortModel); + m_listView->setItemDelegate(delegate); + m_listView->setVerticalScrollMode(QListView::ScrollPerPixel); + connect(m_listView, &QListView::clicked, this, &ContextMenuSettingsPage::changed); + +#ifndef Q_OS_WIN + auto *downloadButton = new KNS3::Button(i18nc("@action:button", "Download New Services..."), + QStringLiteral("servicemenu.knsrc"), + this); + connect(downloadButton, &KNS3::Button::dialogFinished, this, [this](const KNS3::Entry::List &changedEntries) { + if (!changedEntries.isEmpty()) { + m_serviceModel->clear(); + loadServices(); + } + }); + +#endif + + topLayout->addWidget(label); + topLayout->addWidget(m_searchLineEdit); + topLayout->addWidget(m_listView); +#ifndef Q_OS_WIN + topLayout->addWidget(downloadButton); +#endif + + m_enabledVcsPlugins = VersionControlSettings::enabledPlugins(); + std::sort(m_enabledVcsPlugins.begin(), m_enabledVcsPlugins.end()); +} + +ContextMenuSettingsPage::~ContextMenuSettingsPage() { +} + +void ContextMenuSettingsPage::applySettings() +{ + if (!m_initialized) { + return; + } + + KConfig config(QStringLiteral("kservicemenurc"), KConfig::NoGlobals); + KConfigGroup showGroup = config.group("Show"); + + QStringList enabledPlugins; + + const QAbstractItemModel *model = m_listView->model(); + for (int i = 0; i < model->rowCount(); ++i) { + const QModelIndex index = model->index(i, 0); + const QString service = model->data(index, ServiceModel::DesktopEntryNameRole).toString(); + const bool checked = model->data(index, Qt::CheckStateRole).toBool(); + + if (service.startsWith(VersionControlServicePrefix)) { + if (checked) { + enabledPlugins.append(model->data(index, Qt::DisplayRole).toString()); + } + } else if (service == QLatin1String(DeleteService)) { + KSharedConfig::Ptr globalConfig = KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::NoGlobals); + KConfigGroup configGroup(globalConfig, "KDE"); + configGroup.writeEntry("ShowDeleteCommand", checked); + configGroup.sync(); + } else if (service == QLatin1String(CopyToMoveToService)) { + ContextMenuSettings::setShowCopyMoveMenu(checked); + ContextMenuSettings::self()->save(); + } else if (service == QLatin1String(AddToPlacesService)) { + ContextMenuSettings::setShowAddToPlaces(checked); + ContextMenuSettings::self()->save(); + } else if (service == QLatin1String(SortByService)) { + ContextMenuSettings::setShowSortBy(checked); + ContextMenuSettings::self()->save(); + } else if (service == QLatin1String(ViewModeService)) { + ContextMenuSettings::setShowViewMode(checked); + ContextMenuSettings::self()->save(); + } else if (service == QLatin1String(OpenInNewTabService)) { + ContextMenuSettings::setShowOpenInNewTab(checked); + ContextMenuSettings::self()->save(); + } else if (service == QLatin1String(OpenInNewWindowService)) { + ContextMenuSettings::setShowOpenInNewWindow(checked); + ContextMenuSettings::self()->save(); + } else if (service == QLatin1String(CopyLocationService)) { + ContextMenuSettings::setShowCopyLocation(checked); + ContextMenuSettings::self()->save(); + } else if (service == QLatin1String(DuplicateHereService)) { + ContextMenuSettings::setShowDuplicateHere(checked); + ContextMenuSettings::self()->save(); + } else { + showGroup.writeEntry(service, checked); + } + } + + showGroup.sync(); + + if (m_enabledVcsPlugins != enabledPlugins) { + VersionControlSettings::setEnabledPlugins(enabledPlugins); + VersionControlSettings::self()->save(); + + KMessageBox::information(window(), + i18nc("@info", "Dolphin must be restarted to apply the " + "updated version control systems settings."), + QString(), // default title + QStringLiteral("ShowVcsRestartInformation")); + } +} + +void ContextMenuSettingsPage::restoreDefaults() +{ + QAbstractItemModel* model = m_listView->model(); + for (int i = 0; i < model->rowCount(); ++i) { + const QModelIndex index = model->index(i, 0); + const QString service = model->data(index, ServiceModel::DesktopEntryNameRole).toString(); + + const bool checked = !service.startsWith(VersionControlServicePrefix) + && service != QLatin1String(DeleteService) + && service != QLatin1String(CopyToMoveToService); + model->setData(index, checked, Qt::CheckStateRole); + } +} + +void ContextMenuSettingsPage::showEvent(QShowEvent* event) +{ + if (!event->spontaneous() && !m_initialized) { + loadServices(); + + loadVersionControlSystems(); + + // Add "Show 'Delete' command" as service + KSharedConfig::Ptr globalConfig = KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::IncludeGlobals); + KConfigGroup configGroup(globalConfig, "KDE"); + addRow(QStringLiteral("edit-delete"), + i18nc("@option:check", "Delete"), + DeleteService, + configGroup.readEntry("ShowDeleteCommand", ShowDeleteDefault)); + + // Add "Show 'Copy To' and 'Move To' commands" as service + addRow(QStringLiteral("edit-copy"), + i18nc("@option:check", "'Copy To' and 'Move To' commands"), + CopyToMoveToService, + ContextMenuSettings::showCopyMoveMenu()); + + // Add other built-in actions + addRow(QStringLiteral("bookmark-new"), + i18nc("@option:check", "Add to Places"), + AddToPlacesService, + ContextMenuSettings::showAddToPlaces()); + addRow(QStringLiteral("view-sort"), + i18nc("@option:check", "Sort By"), + SortByService, + ContextMenuSettings::showSortBy()); + addRow(QStringLiteral("view-list-icons"), + i18nc("@option:check", "View Mode"), + ViewModeService, + ContextMenuSettings::showViewMode()); + addRow(QStringLiteral("folder-new"), + i18nc("@option:check", "'Open in New Tab' and 'Open in New Tabs'"), + OpenInNewTabService, + ContextMenuSettings::showOpenInNewTab()); + addRow(QStringLiteral("window-new"), + i18nc("@option:check", "Open in New Window"), + OpenInNewWindowService, + ContextMenuSettings::showOpenInNewWindow()); + addRow(QStringLiteral("edit-copy"), + i18nc("@option:check", "Copy Location"), + CopyLocationService, + ContextMenuSettings::showCopyLocation()); + addRow(QStringLiteral("edit-copy"), + i18nc("@option:check", "Duplicate Here"), + DuplicateHereService, + ContextMenuSettings::showDuplicateHere()); + + m_sortModel->sort(Qt::DisplayRole); + + m_initialized = true; + } + SettingsPageBase::showEvent(event); +} + +void ContextMenuSettingsPage::loadServices() +{ + const KConfig config(QStringLiteral("kservicemenurc"), KConfig::NoGlobals); + const KConfigGroup showGroup = config.group("Show"); + + // Load generic services + const KService::List entries = KServiceTypeTrader::self()->query(QStringLiteral("KonqPopupMenu/Plugin")); + for (const KService::Ptr &service : entries) { + const QString file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kservices5/" % service->entryPath()); + const QList serviceActions = KDesktopFileActions::userDefinedServices(file, true); + + const KDesktopFile desktopFile(file); + const QString subMenuName = desktopFile.desktopGroup().readEntry("X-KDE-Submenu"); + + for (const KServiceAction &action : serviceActions) { + const QString serviceName = action.name(); + const bool addService = !action.noDisplay() && !action.isSeparator() && !isInServicesList(serviceName); + + if (addService) { + const QString itemName = subMenuName.isEmpty() + ? action.text() + : i18nc("@item:inmenu", "%1: %2", subMenuName, action.text()); + const bool checked = showGroup.readEntry(serviceName, true); + addRow(action.icon(), itemName, serviceName, checked); + } + } + } + + // Load service plugins that implement the KFileItemActionPlugin interface + const KService::List pluginServices = KServiceTypeTrader::self()->query(QStringLiteral("KFileItemAction/Plugin")); + for (const KService::Ptr &service : pluginServices) { + const QString desktopEntryName = service->desktopEntryName(); + if (!isInServicesList(desktopEntryName)) { + const bool checked = showGroup.readEntry(desktopEntryName, true); + addRow(service->icon(), service->name(), desktopEntryName, checked); + } + } + + // Load JSON-based plugins that implement the KFileItemActionPlugin interface + const auto jsonPlugins = KPluginLoader::findPlugins(QStringLiteral("kf5/kfileitemaction"), [](const KPluginMetaData& metaData) { + return metaData.serviceTypes().contains(QLatin1String("KFileItemAction/Plugin")); + }); + + for (const auto &jsonMetadata : jsonPlugins) { + const QString desktopEntryName = jsonMetadata.pluginId(); + if (!isInServicesList(desktopEntryName)) { + const bool checked = showGroup.readEntry(desktopEntryName, true); + addRow(jsonMetadata.iconName(), jsonMetadata.name(), desktopEntryName, checked); + } + } + + m_sortModel->sort(Qt::DisplayRole); + m_searchLineEdit->setFocus(Qt::OtherFocusReason); +} + +void ContextMenuSettingsPage::loadVersionControlSystems() +{ + const QStringList enabledPlugins = VersionControlSettings::enabledPlugins(); + + // Create a checkbox for each available version control plugin + const KService::List pluginServices = KServiceTypeTrader::self()->query(QStringLiteral("FileViewVersionControlPlugin")); + for (const auto &plugin : pluginServices) { + const QString pluginName = plugin->name(); + addRow(QStringLiteral("code-class"), + pluginName, + VersionControlServicePrefix + pluginName, + enabledPlugins.contains(pluginName)); + } + + m_sortModel->sort(Qt::DisplayRole); +} + +bool ContextMenuSettingsPage::isInServicesList(const QString &service) const +{ + for (int i = 0; i < m_serviceModel->rowCount(); ++i) { + const QModelIndex index = m_serviceModel->index(i, 0); + if (m_serviceModel->data(index, ServiceModel::DesktopEntryNameRole).toString() == service) { + return true; + } + } + return false; +} + +void ContextMenuSettingsPage::addRow(const QString &icon, + const QString &text, + const QString &value, + bool checked) +{ + m_serviceModel->insertRow(0); + + const QModelIndex index = m_serviceModel->index(0, 0); + m_serviceModel->setData(index, icon, Qt::DecorationRole); + m_serviceModel->setData(index, text, Qt::DisplayRole); + m_serviceModel->setData(index, value, ServiceModel::DesktopEntryNameRole); + m_serviceModel->setData(index, checked, Qt::CheckStateRole); +} diff --git a/src/settings/contextmenu/contextmenusettingspage.h b/src/settings/contextmenu/contextmenusettingspage.h new file mode 100644 index 000000000..3825e6f86 --- /dev/null +++ b/src/settings/contextmenu/contextmenusettingspage.h @@ -0,0 +1,69 @@ +/* + * SPDX-FileCopyrightText: 2009-2010 Peter Penz + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#ifndef CONTEXTMENUSETTINGSPAGE_H +#define CONTEXTMENUSETTINGSPAGE_H + +#include "settings/settingspagebase.h" + +#include + +class QListView; +class QSortFilterProxyModel; +class ServiceModel; +class QLineEdit; + +/** + * @brief Configurations for services in the context menu. + */ +class ContextMenuSettingsPage : public SettingsPageBase +{ + Q_OBJECT + +public: + explicit ContextMenuSettingsPage(QWidget* parent); + ~ContextMenuSettingsPage() override; + + /** @see SettingsPageBase::applySettings() */ + void applySettings() override; + + /** @see SettingsPageBase::restoreDefaults() */ + void restoreDefaults() override; + +protected: + void showEvent(QShowEvent* event) override; + +private slots: + /** + * Loads locally installed services. + */ + void loadServices(); + +private: + /** + * Loads installed version control systems. + */ + void loadVersionControlSystems(); + + bool isInServicesList(const QString &service) const; + + /** + * Adds a row to the model of m_listView. + */ + void addRow(const QString &icon, + const QString &text, + const QString &value, + bool checked); + +private: + bool m_initialized; + ServiceModel *m_serviceModel; + QSortFilterProxyModel *m_sortModel; + QListView* m_listView; + QLineEdit *m_searchLineEdit; + QStringList m_enabledVcsPlugins; +}; + +#endif diff --git a/src/settings/contextmenu/servicemenu.knsrc b/src/settings/contextmenu/servicemenu.knsrc new file mode 100644 index 000000000..0d1c103f6 --- /dev/null +++ b/src/settings/contextmenu/servicemenu.knsrc @@ -0,0 +1,9 @@ +[KNewStuff2] +ProvidersUrl=https://download.kde.org/ocs/providers.xml +Categories=Dolphin Service Menus +ChecksumPolicy=ifpossible +SignaturePolicy=ifpossible +TargetDir=servicemenu-download +Uncompress=never +InstallationCommand=servicemenuinstaller install %f +UninstallCommand=servicemenuinstaller uninstall %f diff --git a/src/settings/contextmenu/servicemenuinstaller/CMakeLists.txt b/src/settings/contextmenu/servicemenuinstaller/CMakeLists.txt new file mode 100644 index 000000000..46b159079 --- /dev/null +++ b/src/settings/contextmenu/servicemenuinstaller/CMakeLists.txt @@ -0,0 +1,15 @@ +remove_definitions(-DTRANSLATION_DOMAIN=\"dolphin\") +add_definitions(-DTRANSLATION_DOMAIN=\"dolphin_servicemenuinstaller\") + +add_executable(servicemenuinstaller servicemenuinstaller.cpp) +target_link_libraries(servicemenuinstaller PRIVATE + Qt5::Core + Qt5::Gui + KF5::I18n + KF5::CoreAddons +) + +if(HAVE_PACKAGEKIT) + target_link_libraries(servicemenuinstaller PRIVATE PK::packagekitqt5) +endif() +install(TARGETS servicemenuinstaller ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/src/settings/contextmenu/servicemenuinstaller/Messages.sh b/src/settings/contextmenu/servicemenuinstaller/Messages.sh new file mode 100755 index 000000000..5012eead6 --- /dev/null +++ b/src/settings/contextmenu/servicemenuinstaller/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT `find . -name \*.cpp` -o $podir/dolphin_servicemenuinstaller.pot diff --git a/src/settings/contextmenu/servicemenuinstaller/servicemenuinstaller.cpp b/src/settings/contextmenu/servicemenuinstaller/servicemenuinstaller.cpp new file mode 100644 index 000000000..91da3d256 --- /dev/null +++ b/src/settings/contextmenu/servicemenuinstaller/servicemenuinstaller.cpp @@ -0,0 +1,462 @@ +/* + * SPDX-FileCopyrightText: 2019 Alexander Potashev + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../../config-packagekit.h" + +Q_GLOBAL_STATIC_WITH_ARGS(QStringList, binaryPackages, ({QLatin1String("application/vnd.debian.binary-package"), + QLatin1String("application/x-rpm"), + QLatin1String("application/x-xz"), + QLatin1String("application/zstd")})) + +enum PackageOperation { + Install, + Uninstall +}; + +#ifdef HAVE_PACKAGEKIT +#include +#include +#include +#else +#include +#endif + +// @param msg Error that gets logged to CLI +Q_NORETURN void fail(const QString &str) +{ + qCritical() << str; + const QStringList args = {"--detailederror" ,i18n("Dolphin service menu installation failed"), str}; + QProcess::startDetached("kdialog", args); + + exit(1); +} + +QString getServiceMenusDir() +{ + const QString dataLocation = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); + return QDir(dataLocation).absoluteFilePath("kservices5/ServiceMenus"); +} + +#ifdef HAVE_PACKAGEKIT +void packageKitInstall(const QString &fileName) +{ + PackageKit::Transaction *transaction = PackageKit::Daemon::installFile(fileName, PackageKit::Transaction::TransactionFlagNone); + + const auto exitWithError = [=](PackageKit::Transaction::Error, const QString &details) { + fail(details); + }; + + QObject::connect(transaction, &PackageKit::Transaction::finished, + [=](PackageKit::Transaction::Exit status, uint) { + if (status == PackageKit::Transaction::ExitSuccess) { + exit(0); + } + // Fallback error handling + QTimer::singleShot(500, [=](){ + fail(i18n("Failed to install \"%1\", exited with status \"%2\"", + fileName, QVariant::fromValue(status).toString())); + }); + }); + QObject::connect(transaction, &PackageKit::Transaction::errorCode, exitWithError); +} + +void packageKitUninstall(const QString &fileName) +{ + const auto exitWithError = [=](PackageKit::Transaction::Error, const QString &details) { + fail(details); + }; + const auto uninstallLambda = [=](PackageKit::Transaction::Exit status, uint) { + if (status == PackageKit::Transaction::ExitSuccess) { + exit(0); + } + }; + + PackageKit::Transaction *transaction = PackageKit::Daemon::getDetailsLocal(fileName); + QObject::connect(transaction, &PackageKit::Transaction::details, + [=](const PackageKit::Details &details) { + PackageKit::Transaction *transaction = PackageKit::Daemon::removePackage(details.packageId()); + QObject::connect(transaction, &PackageKit::Transaction::finished, uninstallLambda); + QObject::connect(transaction, &PackageKit::Transaction::errorCode, exitWithError); + }); + + QObject::connect(transaction, &PackageKit::Transaction::errorCode, exitWithError); + // Fallback error handling + QObject::connect(transaction, &PackageKit::Transaction::finished, + [=](PackageKit::Transaction::Exit status, uint) { + if (status != PackageKit::Transaction::ExitSuccess) { + QTimer::singleShot(500, [=]() { + fail(i18n("Failed to uninstall \"%1\", exited with status \"%2\"", + fileName, QVariant::fromValue(status).toString())); + }); + } + }); + } +#endif + +Q_NORETURN void packageKit(PackageOperation operation, const QString &fileName) +{ +#ifdef HAVE_PACKAGEKIT + QFileInfo fileInfo(fileName); + if (!fileInfo.exists()) { + fail(i18n("The file does not exist!")); + } + const QString absPath = fileInfo.absoluteFilePath(); + if (operation == PackageOperation::Install) { + packageKitInstall(absPath); + } else { + packageKitUninstall(absPath); + } + QGuiApplication::exec(); // For event handling, no return after signals finish + fail(i18n("Unknown error when installing package")); +#else + Q_UNUSED(operation) + QDesktopServices::openUrl(QUrl(fileName)); + exit(0); +#endif +} + +struct UncompressCommand +{ + QString command; + QStringList args1; + QStringList args2; +}; + +enum ScriptExecution{ + Process, + Konsole +}; + +void runUncompress(const QString &inputPath, const QString &outputPath) +{ + QVector> mimeTypeToCommand; + mimeTypeToCommand.append({{"application/x-tar", "application/tar", "application/x-gtar", "multipart/x-tar"}, + UncompressCommand({"tar", {"-xf"}, {"-C"}})}); + mimeTypeToCommand.append({{"application/x-gzip", "application/gzip", + "application/x-gzip-compressed-tar", "application/gzip-compressed-tar", + "application/x-gzip-compressed", "application/gzip-compressed", + "application/tgz", "application/x-compressed-tar", + "application/x-compressed-gtar", "file/tgz", + "multipart/x-tar-gz", "application/x-gunzip", "application/gzipped", + "gzip/document"}, + UncompressCommand({"tar", {"-zxf"}, {"-C"}})}); + mimeTypeToCommand.append({{"application/bzip", "application/bzip2", "application/x-bzip", + "application/x-bzip2", "application/bzip-compressed", + "application/bzip2-compressed", "application/x-bzip-compressed", + "application/x-bzip2-compressed", "application/bzip-compressed-tar", + "application/bzip2-compressed-tar", "application/x-bzip-compressed-tar", + "application/x-bzip2-compressed-tar", "application/x-bz2"}, + UncompressCommand({"tar", {"-jxf"}, {"-C"}})}); + mimeTypeToCommand.append({{"application/zip", "application/x-zip", "application/x-zip-compressed", + "multipart/x-zip"}, + UncompressCommand({"unzip", {}, {"-d"}})}); + + const auto mime = QMimeDatabase().mimeTypeForFile(inputPath).name(); + + UncompressCommand command{}; + for (const auto &pair : qAsConst(mimeTypeToCommand)) { + if (pair.first.contains(mime)) { + command = pair.second; + break; + } + } + + if (command.command.isEmpty()) { + fail(i18n("Unsupported archive type %1: %2", mime, inputPath)); + } + + QProcess process; + process.start( + command.command, + QStringList() << command.args1 << inputPath << command.args2 << outputPath, + QIODevice::NotOpen); + if (!process.waitForStarted()) { + fail(i18n("Failed to run uncompressor command for %1", inputPath)); + } + + if (!process.waitForFinished()) { + fail( + i18n("Process did not finish in reasonable time: %1 %2", process.program(), process.arguments().join(" "))); + } + + if (process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) { + fail(i18n("Failed to uncompress %1", inputPath)); + } +} + +QString findRecursive(const QString &dir, const QString &basename) +{ + QDirIterator it(dir, QStringList{basename}, QDir::Files, QDirIterator::Subdirectories); + while (it.hasNext()) { + return QFileInfo(it.next()).canonicalFilePath(); + } + + return QString(); +} + +bool runScriptOnce(const QString &path, const QStringList &args, ScriptExecution execution) +{ + QProcess process; + process.setWorkingDirectory(QFileInfo(path).absolutePath()); + + const static bool konsoleAvailable = !QStandardPaths::findExecutable("konsole").isEmpty(); + if (konsoleAvailable && execution == ScriptExecution::Konsole) { + QString bashCommand = KShell::quoteArg(path) + ' '; + if (!args.isEmpty()) { + bashCommand.append(args.join(' ')); + } + bashCommand.append("|| $SHELL"); + // If the install script fails a shell opens and the user can fix the problem + // without an error konsole closes + process.start("konsole", QStringList() << "-e" << "bash" << "-c" << bashCommand, QIODevice::NotOpen); + } else { + process.start(path, args, QIODevice::NotOpen); + } + if (!process.waitForStarted()) { + fail(i18n("Failed to run installer script %1", path)); + } + + // Wait until installer exits, without timeout + if (!process.waitForFinished(-1)) { + qWarning() << "Failed to wait on installer:" << process.program() << process.arguments().join(" "); + return false; + } + + if (process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) { + qWarning() << "Installer script exited with error:" << process.program() << process.arguments().join(" "); + return false; + } + + return true; +} + +// If hasArgVariants is true, run "path". +// If hasArgVariants is false, run "path argVariants[i]" until successful. +bool runScriptVariants(const QString &path, bool hasArgVariants, const QStringList &argVariants, QString &errorText) +{ + QFile file(path); + if (!file.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner)) { + errorText = i18n("Failed to set permissions on %1: %2", path, file.errorString()); + return false; + } + + qInfo() << "[servicemenuinstaller]: Trying to run installer/uninstaller" << path; + if (hasArgVariants) { + for (const auto &arg : argVariants) { + if (runScriptOnce(path, {arg}, ScriptExecution::Process)) { + return true; + } + } + } else if (runScriptOnce(path, {}, ScriptExecution::Konsole)) { + return true; + } + + errorText = i18nc( + "%2 = comma separated list of arguments", + "Installer script %1 failed, tried arguments \"%2\".", path, argVariants.join(i18nc("Separator between arguments", "\", \""))); + return false; +} + +QString generateDirPath(const QString &archive) +{ + return QStringLiteral("%1-dir").arg(archive); +} + +bool cmdInstall(const QString &archive, QString &errorText) +{ + const auto serviceDir = getServiceMenusDir(); + if (!QDir().mkpath(serviceDir)) { + // TODO Cannot get error string because of this bug: https://bugreports.qt.io/browse/QTBUG-1483 + errorText = i18n("Failed to create path %1", serviceDir); + return false; + } + + if (archive.endsWith(QLatin1String(".desktop"))) { + // Append basename to destination directory + const auto dest = QDir(serviceDir).absoluteFilePath(QFileInfo(archive).fileName()); + if (QFileInfo::exists(dest)) { + QFile::remove(dest); + } + qInfo() << "Single-File Service-Menu" << archive << dest; + + QFile source(archive); + if (!source.copy(dest)) { + errorText = i18n("Failed to copy .desktop file %1 to %2: %3", archive, dest, source.errorString()); + return false; + } + } else { + if (binaryPackages->contains(QMimeDatabase().mimeTypeForFile(archive).name())) { + packageKit(PackageOperation::Install, archive); + } + const QString dir = generateDirPath(archive); + if (QFile::exists(dir)) { + if (!QDir(dir).removeRecursively()) { + errorText = i18n("Failed to remove directory %1", dir); + return false; + } + } + + if (QDir().mkdir(dir)) { + errorText = i18n("Failed to create directory %1", dir); + } + + runUncompress(archive, dir); + + // Try "install-it" first + QString installItPath; + const QStringList basenames1 = {"install-it.sh", "install-it"}; + for (const auto &basename : basenames1) { + const auto path = findRecursive(dir, basename); + if (!path.isEmpty()) { + installItPath = path; + break; + } + } + + if (!installItPath.isEmpty()) { + return runScriptVariants(installItPath, false, QStringList{}, errorText); + } + + // If "install-it" is missing, try "install" + QString installerPath; + const QStringList basenames2 = {"installKDE4.sh", "installKDE4", "install.sh", "install"}; + for (const auto &basename : basenames2) { + const auto path = findRecursive(dir, basename); + if (!path.isEmpty()) { + installerPath = path; + break; + } + } + + if (!installerPath.isEmpty()) { + // Try to run script without variants first + if (!runScriptVariants(installerPath, false, {}, errorText)) { + return runScriptVariants(installerPath, true, {"--local", "--local-install", "--install"}, errorText); + } + return true; + } + + fail(i18n("Failed to find an installation script in %1", dir)); + } + + return true; +} + +bool cmdUninstall(const QString &archive, QString &errorText) +{ + const auto serviceDir = getServiceMenusDir(); + if (archive.endsWith(QLatin1String(".desktop"))) { + // Append basename to destination directory + const auto dest = QDir(serviceDir).absoluteFilePath(QFileInfo(archive).fileName()); + QFile file(dest); + if (!file.remove()) { + errorText = i18n("Failed to remove .desktop file %1: %2", dest, file.errorString()); + return false; + } + } else { + if (binaryPackages->contains(QMimeDatabase().mimeTypeForFile(archive).name())) { + packageKit(PackageOperation::Uninstall, archive); + } + const QString dir = generateDirPath(archive); + + // Try "deinstall" first + QString deinstallPath; + const QStringList basenames1 = {"uninstall.sh", "uninstal", "deinstall.sh", "deinstall"}; + for (const auto &basename : basenames1) { + const auto path = findRecursive(dir, basename); + if (!path.isEmpty()) { + deinstallPath = path; + break; + } + } + + if (!deinstallPath.isEmpty()) { + const bool ok = runScriptVariants(deinstallPath, false, {}, errorText); + if (!ok) { + return ok; + } + } else { + // If "deinstall" is missing, try "install --uninstall" + QString installerPath; + const QStringList basenames2 = {"install-it.sh", "install-it", "installKDE4.sh", + "installKDE4", "install.sh", "install"}; + for (const auto &basename : basenames2) { + const auto path = findRecursive(dir, basename); + if (!path.isEmpty()) { + installerPath = path; + break; + } + } + + if (!installerPath.isEmpty()) { + const bool ok = runScriptVariants(installerPath, true, + {"--remove", "--delete", "--uninstall", "--deinstall"}, errorText); + if (!ok) { + return ok; + } + } else { + fail(i18n("Failed to find an uninstallation script in %1", dir)); + } + } + + QDir dirObject(dir); + if (!dirObject.removeRecursively()) { + errorText = i18n("Failed to remove directory %1", dir); + return false; + } + } + + return true; +} + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + + QCommandLineParser parser; + parser.addPositionalArgument(QStringLiteral("command"), i18nc("@info:shell", "Command to execute: install or uninstall.")); + parser.addPositionalArgument(QStringLiteral("path"), i18nc("@info:shell", "Path to archive.")); + parser.process(app); + + const QStringList args = parser.positionalArguments(); + if (args.isEmpty()) { + fail(i18n("Command is required.")); + } + if (args.size() == 1) { + fail(i18n("Path to archive is required.")); + } + + const QString cmd = args[0]; + const QString archive = args[1]; + + QString errorText; + if (cmd == QLatin1String("install")) { + if (!cmdInstall(archive, errorText)) { + fail(errorText); + } + } else if (cmd == QLatin1String("uninstall")) { + if (!cmdUninstall(archive, errorText)) { + fail(errorText); + } + } else { + fail(i18n("Unsupported command %1", cmd)); + } + + return 0; +} diff --git a/src/settings/contextmenu/test/service_menu_deinstallation_test.rb b/src/settings/contextmenu/test/service_menu_deinstallation_test.rb new file mode 100644 index 000000000..bf44b7b7f --- /dev/null +++ b/src/settings/contextmenu/test/service_menu_deinstallation_test.rb @@ -0,0 +1,112 @@ +#!/usr/bin/env ruby + +# SPDX-FileCopyrightText: 2019 Harald Sitter +# +# SPDX-License-Identifier: GPL-2.0-or-later + +require_relative 'test_helper' + +require 'tmpdir' + +class ServiceMenuDeinstallationTest < Test::Unit::TestCase + def setup + @tmpdir = Dir.mktmpdir("dolphintest-#{self.class.to_s.tr(':', '_')}") + @pwdir = Dir.pwd + Dir.chdir(@tmpdir) + + ENV['XDG_DATA_HOME'] = File.join(@tmpdir, 'data') + end + + def teardown + Dir.chdir(@pwdir) + FileUtils.rm_rf(@tmpdir) + + ENV.delete('XDG_DATA_HOME') + end + + def test_run_deinstall + service_dir = File.join(Dir.pwd, 'share/servicemenu-download') + archive_base = "#{service_dir}/foo.zip" + archive_dir = "#{archive_base}-dir/foo-1.1/" + FileUtils.mkpath(archive_dir) + File.write("#{archive_dir}/deinstall.sh", <<-DEINSTALL_SH) +#!/bin/sh +set -e +cat deinstall.sh +touch #{@tmpdir}/deinstall.sh-run + DEINSTALL_SH + File.write("#{archive_dir}/install.sh", <<-INSTALL_SH) +#!/bin/sh +set -e +cat install.sh +touch #{@tmpdir}/install.sh-run + INSTALL_SH + + assert(system('servicemenuinstaller', 'uninstall', archive_base)) + + # deinstaller should be run + # installer should not be run + # archive_dir should have been correctly removed + + assert_path_exist('deinstall.sh-run') + assert_path_not_exist('install.sh-run') + assert_path_not_exist(archive_dir) + end + + def test_run_install_with_arg + service_dir = File.join(Dir.pwd, 'share/servicemenu-download') + archive_base = "#{service_dir}/foo.zip" + archive_dir = "#{archive_base}-dir/foo-1.1/" + FileUtils.mkpath(archive_dir) + + File.write("#{archive_dir}/install.sh", <<-INSTALL_SH) +#!/bin/sh +if [ "$@" = "--uninstall" ]; then + touch #{@tmpdir}/install.sh-run + exit 0 +fi +exit 1 + INSTALL_SH + + assert(system('servicemenuinstaller', 'uninstall', archive_base)) + + assert_path_not_exist('deinstall.sh-run') + assert_path_exist('install.sh-run') + assert_path_not_exist(archive_dir) + end + + # no scripts in sight + def test_run_fail + service_dir = File.join(Dir.pwd, 'share/servicemenu-download') + archive_base = "#{service_dir}/foo.zip" + archive_dir = "#{archive_base}-dir/foo-1.1/" + FileUtils.mkpath(archive_dir) + + refute(system('servicemenuinstaller', 'uninstall', archive_base)) + + # I am unsure if deinstallation really should keep the files around. But + # that's how it behaved originally so it's supposedly intentional + # - sitter, 2019 + assert_path_exist(archive_dir) + end + + # For desktop files things are a bit special. There is one in .local/share/servicemenu-download + # and another in the actual ServiceMenus dir. The latter gets removed by the + # script, the former by KNS. + def test_run_desktop + service_dir = File.join(Dir.pwd, 'share/servicemenu-download') + downloaded_file = "#{service_dir}/foo.desktop" + FileUtils.mkpath(service_dir) + FileUtils.touch(downloaded_file) + + menu_dir = "#{ENV['XDG_DATA_HOME']}/kservices5/ServiceMenus/" + installed_file = "#{menu_dir}/foo.desktop" + FileUtils.mkpath(menu_dir) + FileUtils.touch(installed_file) + + assert(system('servicemenuinstaller', 'uninstall', downloaded_file)) + + assert_path_exist(downloaded_file) + assert_path_not_exist(installed_file) + end +end diff --git a/src/settings/contextmenu/test/service_menu_installation_test.rb b/src/settings/contextmenu/test/service_menu_installation_test.rb new file mode 100644 index 000000000..7c05a40e3 --- /dev/null +++ b/src/settings/contextmenu/test/service_menu_installation_test.rb @@ -0,0 +1,106 @@ +#!/usr/bin/env ruby + +# SPDX-FileCopyrightText: 2019 Harald Sitter +# +# SPDX-License-Identifier: GPL-2.0-or-later + +require_relative 'test_helper' + +require 'tmpdir' + +class ServiceMenuInstallationTest < Test::Unit::TestCase + def setup + @tmpdir = Dir.mktmpdir("dolphintest-#{self.class.to_s.tr(':', '_')}") + @pwdir = Dir.pwd + Dir.chdir(@tmpdir) + + ENV['XDG_DATA_HOME'] = File.join(@tmpdir, 'data') + end + + def teardown + Dir.chdir(@pwdir) + FileUtils.rm_rf(@tmpdir) + + ENV.delete('XDG_DATA_HOME') + end + + def test_run_install + service_dir = File.join(Dir.pwd, 'share/servicemenu-download') + FileUtils.mkpath(service_dir) + archive = "#{service_dir}/foo.tar" + + archive_dir = 'foo' # relative so tar cf is relative without fuzz + FileUtils.mkpath(archive_dir) + File.write("#{archive_dir}/install-it.sh", <<-INSTALL_IT_SH) +#!/bin/sh +touch #{@tmpdir}/install-it.sh-run +INSTALL_IT_SH + File.write("#{archive_dir}/install.sh", <<-INSTALL_SH) +#!/bin/sh +touch #{@tmpdir}/install.sh-run + INSTALL_SH + assert(system('tar', '-cf', archive, archive_dir)) + + assert(system('servicemenuinstaller', 'install', archive)) + + tar_dir = "#{service_dir}/foo.tar-dir" + tar_extract_dir = "#{service_dir}/foo.tar-dir/foo" + assert_path_exist(tar_dir) + assert_path_exist(tar_extract_dir) + assert_path_exist("#{tar_extract_dir}/install-it.sh") + assert_path_exist("#{tar_extract_dir}/install.sh") + end + + def test_run_install_with_arg + service_dir = File.join(Dir.pwd, 'share/servicemenu-download') + FileUtils.mkpath(service_dir) + archive = "#{service_dir}/foo.tar" + + archive_dir = 'foo' # relative so tar cf is relative without fuzz + FileUtils.mkpath(archive_dir) + File.write("#{archive_dir}/install.sh", <<-INSTALL_SH) +#!/bin/sh +if [ "$@" = "--install" ]; then + touch #{@tmpdir}/install.sh-run + exit 0 +fi +exit 1 + INSTALL_SH + assert(system('tar', '-cf', archive, archive_dir)) + + assert(system('servicemenuinstaller', 'install', archive)) + + tar_dir = "#{service_dir}/foo.tar-dir" + tar_extract_dir = "#{service_dir}/foo.tar-dir/foo" + assert_path_exist(tar_dir) + assert_path_exist(tar_extract_dir) + assert_path_not_exist("#{tar_extract_dir}/install-it.sh") + assert_path_exist("#{tar_extract_dir}/install.sh") + end + + def test_run_fail + service_dir = File.join(Dir.pwd, 'share/servicemenu-download') + FileUtils.mkpath(service_dir) + archive = "#{service_dir}/foo.tar" + + archive_dir = 'foo' # relative so tar cf is relative without fuzz + FileUtils.mkpath(archive_dir) + assert(system('tar', '-cf', archive, archive_dir)) + + refute(system('servicemenuinstaller', 'install', archive)) + end + + def test_run_desktop + service_dir = File.join(Dir.pwd, 'share/servicemenu-download') + downloaded_file = "#{service_dir}/foo.desktop" + FileUtils.mkpath(service_dir) + FileUtils.touch(downloaded_file) + + installed_file = "#{ENV['XDG_DATA_HOME']}/kservices5/ServiceMenus/foo.desktop" + + assert(system('servicemenuinstaller', 'install', downloaded_file)) + + assert_path_exist(downloaded_file) + assert_path_exist(installed_file) + end +end diff --git a/src/settings/contextmenu/test/test_helper.rb b/src/settings/contextmenu/test/test_helper.rb new file mode 100644 index 000000000..b4e4dded2 --- /dev/null +++ b/src/settings/contextmenu/test/test_helper.rb @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: 2019 Harald Sitter +# +# SPDX-License-Identifier: GPL-2.0-or-later + +$LOAD_PATH.unshift(File.absolute_path('../', __dir__)) # ../ + +def __test_method_name__ + return @method_name if defined?(:@method_name) + index = 0 + caller = '' + until caller.start_with?('test_') + caller = caller_locations(index, 1)[0].label + index += 1 + end + caller +end + +require 'test/unit' diff --git a/src/settings/contextmenu/test/test_run.rb b/src/settings/contextmenu/test/test_run.rb new file mode 100755 index 000000000..ab298a0b0 --- /dev/null +++ b/src/settings/contextmenu/test/test_run.rb @@ -0,0 +1,11 @@ +#!/usr/bin/env ruby + +# SPDX-FileCopyrightText: 2019 Harald Sitter +# +# SPDX-License-Identifier: GPL-2.0-or-later +# This is a fancy wrapper around test_helper to prevent the collector from +# loading the helper twice as it would occur if we ran the helper directly. + +require_relative 'test_helper' + +Test::Unit::AutoRunner.run(true, File.absolute_path(__dir__)) diff --git a/src/settings/dolphin_contextmenusettings.kcfg b/src/settings/dolphin_contextmenusettings.kcfg new file mode 100644 index 000000000..9e7056551 --- /dev/null +++ b/src/settings/dolphin_contextmenusettings.kcfg @@ -0,0 +1,42 @@ + + + + + + + + false + + + + true + + + + true + + + + true + + + + true + + + + true + + + + true + + + + true + + + diff --git a/src/settings/dolphin_contextmenusettings.kcfgc b/src/settings/dolphin_contextmenusettings.kcfgc new file mode 100644 index 000000000..b50e98f01 --- /dev/null +++ b/src/settings/dolphin_contextmenusettings.kcfgc @@ -0,0 +1,4 @@ +File=dolphin_contextmenusettings.kcfg +ClassName=ContextMenuSettings +Singleton=yes +Mutators=true diff --git a/src/settings/dolphin_generalsettings.kcfg b/src/settings/dolphin_generalsettings.kcfg index c397b2945..0ec5f282b 100644 --- a/src/settings/dolphin_generalsettings.kcfg +++ b/src/settings/dolphin_generalsettings.kcfg @@ -90,10 +90,6 @@ false - - - false - diff --git a/src/settings/dolphinsettingsdialog.cpp b/src/settings/dolphinsettingsdialog.cpp index 9d8fb032a..01d0ad030 100644 --- a/src/settings/dolphinsettingsdialog.cpp +++ b/src/settings/dolphinsettingsdialog.cpp @@ -10,7 +10,7 @@ #include "dolphinmainwindow.h" #include "general/generalsettingspage.h" #include "navigation/navigationsettingspage.h" -#include "services/servicessettingspage.h" +#include "contextmenu/contextmenusettingspage.h" #include "startup/startupsettingspage.h" #include "trash/trashsettingspage.h" #include "viewmodes/viewsettingspage.h" @@ -77,12 +77,12 @@ DolphinSettingsDialog::DolphinSettingsDialog(const QUrl& url, QWidget* parent) : navigationSettingsFrame->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-navigation"))); connect(navigationSettingsPage, &NavigationSettingsPage::changed, this, &DolphinSettingsDialog::enableApply); - // Services - ServicesSettingsPage* servicesSettingsPage = new ServicesSettingsPage(this); - KPageWidgetItem* servicesSettingsFrame = addPage(servicesSettingsPage, - i18nc("@title:group", "Services")); - servicesSettingsFrame->setIcon(QIcon::fromTheme(QStringLiteral("preferences-system-services"))); - connect(servicesSettingsPage, &ServicesSettingsPage::changed, this, &DolphinSettingsDialog::enableApply); + // Context Menu + auto contextMenuSettingsPage = new ContextMenuSettingsPage(this); + KPageWidgetItem* contextMenuSettingsFrame = addPage(contextMenuSettingsPage, + i18nc("@title:group", "Context Menu")); + contextMenuSettingsFrame->setIcon(QIcon::fromTheme(QStringLiteral("application-menu"))); + connect(contextMenuSettingsPage, &ContextMenuSettingsPage::changed, this, &DolphinSettingsDialog::enableApply); // Trash SettingsPageBase* trashSettingsPage = nullptr; @@ -111,7 +111,7 @@ DolphinSettingsDialog::DolphinSettingsDialog(const QUrl& url, QWidget* parent) : m_pages.append(startupSettingsPage); m_pages.append(viewSettingsPage); m_pages.append(navigationSettingsPage); - m_pages.append(servicesSettingsPage); + m_pages.append(contextMenuSettingsPage); if (trashSettingsPage) { m_pages.append(trashSettingsPage); } diff --git a/src/settings/kcm/kcmdolphincontextmenu.cpp b/src/settings/kcm/kcmdolphincontextmenu.cpp new file mode 100644 index 000000000..a730dfcfe --- /dev/null +++ b/src/settings/kcm/kcmdolphincontextmenu.cpp @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2009 Peter Penz + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "kcmdolphincontextmenu.h" + +#include "settings/contextmenu/contextmenusettingspage.h" + +#include +#include +#include + +#include + +K_PLUGIN_FACTORY(KCMDolphinContextMenuConfigFactory, registerPlugin(QStringLiteral("dolphincontextmenu"));) + +DolphinContextMenuConfigModule::DolphinContextMenuConfigModule(QWidget* parent, const QVariantList& args) : + KCModule(parent, args), + m_contextMenu(nullptr) +{ + setButtons(KCModule::Default | KCModule::Help); + + QVBoxLayout* topLayout = new QVBoxLayout(this); + topLayout->setContentsMargins(0, 0, 0, 0); + + m_contextMenu = new ContextMenuSettingsPage(this); + connect(m_contextMenu, &ContextMenuSettingsPage::changed, this, &DolphinContextMenuConfigModule::markAsChanged); + topLayout->addWidget(m_contextMenu, 0, {}); +} + +DolphinContextMenuConfigModule::~DolphinContextMenuConfigModule() +{ +} + +void DolphinContextMenuConfigModule::save() +{ + m_contextMenu->applySettings(); +} + +void DolphinContextMenuConfigModule::defaults() +{ + m_contextMenu->restoreDefaults(); +} + +#include "kcmdolphincontextmenu.moc" diff --git a/src/settings/kcm/kcmdolphincontextmenu.desktop b/src/settings/kcm/kcmdolphincontextmenu.desktop new file mode 100644 index 000000000..17f16cd81 --- /dev/null +++ b/src/settings/kcm/kcmdolphincontextmenu.desktop @@ -0,0 +1,62 @@ +Name=Dolphin Context Menu + +[Desktop Entry] +Icon=application-menu +Type=Service +X-KDE-ServiceTypes=KCModule + +X-KDE-Library=kcm_dolphincontextmenu +X-KDE-PluginKeyword=dolphincontextmenu +X-DocPath=dolphin/index.html#preferences-dialog-services +Name=Dolphin Context Menu + + +X-KDE-Keywords=file manager +X-KDE-Keywords[ar]=مدير ملفّات ملفات الملفّات الملفات +X-KDE-Keywords[ast]=xestor de ficheros +X-KDE-Keywords[az]=fayl meneceri +X-KDE-Keywords[ca]=gestor de fitxers +X-KDE-Keywords[ca@valencia]=gestor de fitxers +X-KDE-Keywords[cs]=správce souborů +X-KDE-Keywords[da]=filhåndtering +X-KDE-Keywords[de]=Dateiverwaltung +X-KDE-Keywords[el]=διαχειριστής αρχείων +X-KDE-Keywords[en_GB]=file manager +X-KDE-Keywords[es]=gestor de archivos +X-KDE-Keywords[et]=failihaldur +X-KDE-Keywords[eu]=Fitxategi-kudeatzailea +X-KDE-Keywords[fi]=tiedostonhallinta +X-KDE-Keywords[fr]=gestionnaire de fichiers +X-KDE-Keywords[gl]=xestor de ficheiros +X-KDE-Keywords[he]=מנהל קבצים +X-KDE-Keywords[hu]=fájlkezelő +X-KDE-Keywords[ia]=gerente de file +X-KDE-Keywords[id]=pengelola file +X-KDE-Keywords[it]=gestore dei file +X-KDE-Keywords[ja]=ファイルマネージャ +X-KDE-Keywords[ko]=파일 관리자 +X-KDE-Keywords[lt]=failų tvarkytuvė +X-KDE-Keywords[lv]=datņu pārvaldnieks +X-KDE-Keywords[ml]=ഫയൽ മാനേജർ +X-KDE-Keywords[nb]=filbehandler +X-KDE-Keywords[nl]=bestandsbeheerder +X-KDE-Keywords[nn]=filhandsamar +X-KDE-Keywords[pa]=ਫਾਇਲ ਮੈਨੇਜਰ +X-KDE-Keywords[pl]=zarządzanie plikami +X-KDE-Keywords[pt]=gestor de ficheiros +X-KDE-Keywords[pt_BR]=gerenciador de arquivos +X-KDE-Keywords[ro]=gestionar de fișiere +X-KDE-Keywords[ru]=диспетчер файлов +X-KDE-Keywords[sk]=správca súborov +X-KDE-Keywords[sl]=upravljalnik datotek +X-KDE-Keywords[sr]=file manager,менаџер фајлова +X-KDE-Keywords[sr@ijekavian]=file manager,менаџер фајлова +X-KDE-Keywords[sr@ijekavianlatin]=file manager,menadžer fajlova +X-KDE-Keywords[sr@latin]=file manager,menadžer fajlova +X-KDE-Keywords[sv]=filhanterare +X-KDE-Keywords[tr]=dosya yöneticisi +X-KDE-Keywords[uk]=менеджер,керування,файл,файли +X-KDE-Keywords[vi]=file manager,trình quản lí tệp +X-KDE-Keywords[x-test]=xxfile managerxx +X-KDE-Keywords[zh_CN]=文件管理器 +X-KDE-Keywords[zh_TW]=檔案管理員 diff --git a/src/settings/kcm/kcmdolphincontextmenu.h b/src/settings/kcm/kcmdolphincontextmenu.h new file mode 100644 index 000000000..bfe55d254 --- /dev/null +++ b/src/settings/kcm/kcmdolphincontextmenu.h @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2009 Peter Penz + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef KCMDOLPHINCONTEXTMENU_H +#define KCMDOLPHINCONTEXTMENU_H + +#include + +class ContextMenuSettingsPage; + +/** + * @brief Allow to configure the Dolphin context menu. + */ +class DolphinContextMenuConfigModule : public KCModule +{ + Q_OBJECT + +public: + DolphinContextMenuConfigModule(QWidget* parent, const QVariantList& args); + ~DolphinContextMenuConfigModule() override; + + void save() override; + void defaults() override; + +private: + ContextMenuSettingsPage *m_contextMenu; +}; + +#endif diff --git a/src/settings/kcm/kcmdolphinservices.cpp b/src/settings/kcm/kcmdolphinservices.cpp deleted file mode 100644 index 583440d3e..000000000 --- a/src/settings/kcm/kcmdolphinservices.cpp +++ /dev/null @@ -1,46 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2009 Peter Penz - * - * SPDX-License-Identifier: GPL-2.0-or-later - */ - -#include "kcmdolphinservices.h" - -#include "settings/services/servicessettingspage.h" - -#include -#include - -#include - -K_PLUGIN_FACTORY(KCMDolphinServicesConfigFactory, registerPlugin(QStringLiteral("dolphinservices"));) - -DolphinServicesConfigModule::DolphinServicesConfigModule(QWidget* parent, const QVariantList& args) : - KCModule(parent, args), - m_services(nullptr) -{ - setButtons(KCModule::Default | KCModule::Help); - - QVBoxLayout* topLayout = new QVBoxLayout(this); - topLayout->setContentsMargins(0, 0, 0, 0); - - m_services = new ServicesSettingsPage(this); - connect(m_services, &ServicesSettingsPage::changed, this, &DolphinServicesConfigModule::markAsChanged); - topLayout->addWidget(m_services, 0, {}); -} - -DolphinServicesConfigModule::~DolphinServicesConfigModule() -{ -} - -void DolphinServicesConfigModule::save() -{ - m_services->applySettings(); -} - -void DolphinServicesConfigModule::defaults() -{ - m_services->restoreDefaults(); -} - -#include "kcmdolphinservices.moc" diff --git a/src/settings/kcm/kcmdolphinservices.desktop b/src/settings/kcm/kcmdolphinservices.desktop deleted file mode 100644 index 2e188f3fa..000000000 --- a/src/settings/kcm/kcmdolphinservices.desktop +++ /dev/null @@ -1,204 +0,0 @@ -Name=Dolphin Services -Name[ar]=خدمات دولفين -Name[ast]=Servicios de Dolphin -Name[az]=Dolphin xidmətləri -Name[ca]=Serveis del Dolphin -Name[ca@valencia]=Serveis del Dolphin -Name[cs]=Služby Dolphinu -Name[da]=Dolphin-tjenester -Name[de]=Dolphin-Dienste -Name[el]=Dolphin Υπηρεσίες -Name[en_GB]=Dolphin Services -Name[es]=Servicios de Dolphin -Name[et]=Dolphini teenused -Name[eu]=Dolphin zerbitzuak -Name[fi]=Dolphin – palvelut -Name[fr]=Services de Dolphin -Name[gl]=Servizos de Dolphin -Name[he]=שרותי Dolphin -Name[hu]=Dolphin szolgáltatások -Name[ia]=Servicios de Dolphin -Name[id]=Layanan Dolphin -Name[it]=Servizi di Dolphin -Name[ja]=Dolphin サービス -Name[ko]=Dolphin 서비스 -Name[lt]=Dolphin paslaugos -Name[lv]=Dolphin servisi -Name[ml]=ഡോള്‍ഫിന്‍ സേവനങ്ങള്‍ -Name[nb]=Dolphin-tjenester -Name[nl]=Dolphin-services -Name[nn]=Dolphin-tenester -Name[pa]=ਡਾਲਫਿਨ ਸੇਵਾਵਾਂ -Name[pl]=Usługi Dolphina -Name[pt]=Serviços do Dolphin -Name[pt_BR]=Serviços do Dolphin -Name[ro]=Dolphin – Servicii -Name[ru]=Действия Dolphin -Name[sk]=Služby Dolphinu -Name[sl]=Dolphin - storitve -Name[sr]=Делфинови сервиси -Name[sr@ijekavian]=Делфинови сервиси -Name[sr@ijekavianlatin]=Dolphinovi servisi -Name[sr@latin]=Dolphinovi servisi -Name[sv]=Dolphin tjänster -Name[tr]=Dolphin Servisleri -Name[uk]=Служби Dolphin -Name[vi]=Các dịch vụ Dolphin -Name[x-test]=xxDolphin Servicesxx -Name[zh_CN]=Dolphin 服务 -Name[zh_TW]=Dolphin 服務 - -[Desktop Entry] -Icon=preferences-system-services -Type=Service -X-KDE-ServiceTypes=KCModule - -X-KDE-Library=kcm_dolphinservices -X-KDE-PluginKeyword=dolphinservices -X-DocPath=dolphin/index.html#preferences-dialog-services -Name=Services -Name[ar]=الخدمات -Name[ast]=Servicios -Name[az]=Xidmətlər -Name[ca]=Serveis -Name[ca@valencia]=Serveis -Name[cs]=Služby -Name[da]=Tjenester -Name[de]=KDE-Dienste -Name[el]=Υπηρεσίες -Name[en_GB]=Services -Name[es]=Servicios -Name[et]=Teenused -Name[eu]=Zerbitzuak -Name[fi]=Palvelut -Name[fr]=Services -Name[gl]=Servizos -Name[he]=שירותים -Name[hu]=Szolgáltatások -Name[ia]=Servicios -Name[id]=Layanan -Name[it]=Servizi -Name[ja]=サービス -Name[ko]=서비스 -Name[lt]=Paslaugos -Name[lv]=Servisi -Name[ml]=സേവനങ്ങള്‍ -Name[nb]=Tjenester -Name[nl]=Services -Name[nn]=Tenester -Name[pa]=ਸੇਵਾਵਾਂ -Name[pl]=Usługi -Name[pt]=Serviços -Name[pt_BR]=Serviços -Name[ro]=Servicii -Name[ru]=Действия -Name[sk]=Služby -Name[sl]=Storitve -Name[sr]=Сервиси -Name[sr@ijekavian]=Сервиси -Name[sr@ijekavianlatin]=Servisi -Name[sr@latin]=Servisi -Name[sv]=Tjänster -Name[tr]=Servisler -Name[uk]=Служби -Name[vi]=Các dịch vụ -Name[x-test]=xxServicesxx -Name[zh_CN]=服务 -Name[zh_TW]=服務 -Comment=Configure file manager services -Comment[ar]=اضبط خدمات مدير الملفّات -Comment[ast]=Configura los servicios del xestor de ficheros -Comment[az]=Fayl meneceri xidmətlərini tənzimləmək -Comment[ca]=Configura els serveis del gestor de fitxers -Comment[ca@valencia]=Configura els serveis del gestor de fitxers -Comment[cs]=Nastavení služeb správce souborů -Comment[da]=Indstil filhåndteringstjenester -Comment[de]=Dateiverwaltungs-Dienste einrichten -Comment[el]=Διαμόρφωση υπηρεσιών του διαχειριστή αρχείων -Comment[en_GB]=Configure file manager services -Comment[es]=Configurar los servicios del gestor de archivos -Comment[et]=Failihalduri teenuste seadistamine -Comment[eu]=Konfiguratu fitxategi-kudeatzailearen zerbitzuak -Comment[fi]=Tiedostonhallinnan palveluasetukset -Comment[fr]=Configuration des services du gestionnaire de fichiers -Comment[gl]=Configurar servizos de xestores de ficheiros. -Comment[hu]=A fájlkezelő szolgáltatásainak beállítása -Comment[ia]=Configura servicios del gerente de file -Comment[id]=Konfigurasikan layanan pengelola file -Comment[it]=Configura i servizi del gestore dei file -Comment[ja]=ファイルマネージャのサービスを設定します -Comment[ko]=파일 관리자 서비스 구성 -Comment[lt]=Konfigūruoti failų tvarkytuvės paslaugas -Comment[lv]=Konfigurēt datņu pārvaldnieka servisus -Comment[ml]=ഫയല്‍ മാനേജർ സേവനങ്ങള്‍ ക്രമീകരിയ്ക്കുക -Comment[nb]=Sett opp tjenester i filbehandleren -Comment[nl]=Bestandsbeheerderservices configureren -Comment[nn]=Set opp tenester i filhandsamaren -Comment[pa]=ਫਾਇਲ ਮੈਨੇਜਰ ਦੀਆਂ ਸਰਵਿਸਾਂ ਦੀ ਸੰਰਚਨਾ -Comment[pl]=Ustawienia usług zarządzania plikami -Comment[pt]=Configurar os serviços do gestor de ficheiros -Comment[pt_BR]=Configura os serviços do gerenciador de arquivos -Comment[ro]=Configurează serviciile gestionarului de fișiere -Comment[ru]=Настройка действий в диспетчере файлов -Comment[sk]=Nastavenie služieb správcu súborov -Comment[sl]=Nastavitve storitev upravljalnika datotek -Comment[sr]=Подешавање сервиса менаџера фајлова -Comment[sr@ijekavian]=Подешавање сервиса менаџера фајлова -Comment[sr@ijekavianlatin]=Podešavanje servisa menadžera fajlova -Comment[sr@latin]=Podešavanje servisa menadžera fajlova -Comment[sv]=Anpassa filhanterarens tjänster -Comment[tr]=Dosya yöneticisi servislerini yapılandır -Comment[uk]=Налаштувати служби менеджера файлів -Comment[vi]=Cấu hình các dịch vụ trình quản lí tệp -Comment[x-test]=xxConfigure file manager servicesxx -Comment[zh_CN]=配置文件管理器服务 -Comment[zh_TW]=設定檔案管理員服務 -X-KDE-Keywords=file manager -X-KDE-Keywords[ar]=مدير ملفّات ملفات الملفّات الملفات -X-KDE-Keywords[ast]=xestor de ficheros -X-KDE-Keywords[az]=fayl meneceri -X-KDE-Keywords[ca]=gestor de fitxers -X-KDE-Keywords[ca@valencia]=gestor de fitxers -X-KDE-Keywords[cs]=správce souborů -X-KDE-Keywords[da]=filhåndtering -X-KDE-Keywords[de]=Dateiverwaltung -X-KDE-Keywords[el]=διαχειριστής αρχείων -X-KDE-Keywords[en_GB]=file manager -X-KDE-Keywords[es]=gestor de archivos -X-KDE-Keywords[et]=failihaldur -X-KDE-Keywords[eu]=Fitxategi-kudeatzailea -X-KDE-Keywords[fi]=tiedostonhallinta -X-KDE-Keywords[fr]=gestionnaire de fichiers -X-KDE-Keywords[gl]=xestor de ficheiros -X-KDE-Keywords[he]=מנהל קבצים -X-KDE-Keywords[hu]=fájlkezelő -X-KDE-Keywords[ia]=gerente de file -X-KDE-Keywords[id]=pengelola file -X-KDE-Keywords[it]=gestore dei file -X-KDE-Keywords[ja]=ファイルマネージャ -X-KDE-Keywords[ko]=파일 관리자 -X-KDE-Keywords[lt]=failų tvarkytuvė -X-KDE-Keywords[lv]=datņu pārvaldnieks -X-KDE-Keywords[ml]=ഫയൽ മാനേജർ -X-KDE-Keywords[nb]=filbehandler -X-KDE-Keywords[nl]=bestandsbeheerder -X-KDE-Keywords[nn]=filhandsamar -X-KDE-Keywords[pa]=ਫਾਇਲ ਮੈਨੇਜਰ -X-KDE-Keywords[pl]=zarządzanie plikami -X-KDE-Keywords[pt]=gestor de ficheiros -X-KDE-Keywords[pt_BR]=gerenciador de arquivos -X-KDE-Keywords[ro]=gestionar de fișiere -X-KDE-Keywords[ru]=диспетчер файлов -X-KDE-Keywords[sk]=správca súborov -X-KDE-Keywords[sl]=upravljalnik datotek -X-KDE-Keywords[sr]=file manager,менаџер фајлова -X-KDE-Keywords[sr@ijekavian]=file manager,менаџер фајлова -X-KDE-Keywords[sr@ijekavianlatin]=file manager,menadžer fajlova -X-KDE-Keywords[sr@latin]=file manager,menadžer fajlova -X-KDE-Keywords[sv]=filhanterare -X-KDE-Keywords[tr]=dosya yöneticisi -X-KDE-Keywords[uk]=менеджер,керування,файл,файли -X-KDE-Keywords[vi]=file manager,trình quản lí tệp -X-KDE-Keywords[x-test]=xxfile managerxx -X-KDE-Keywords[zh_CN]=文件管理器 -X-KDE-Keywords[zh_TW]=檔案管理員 diff --git a/src/settings/kcm/kcmdolphinservices.h b/src/settings/kcm/kcmdolphinservices.h deleted file mode 100644 index ea94a98d4..000000000 --- a/src/settings/kcm/kcmdolphinservices.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2009 Peter Penz - * - * SPDX-License-Identifier: GPL-2.0-or-later - */ - -#ifndef KCMDOLPHINSERVICES_H -#define KCMDOLPHINSERVICES_H - -#include - -class ServicesSettingsPage; - -/** - * @brief Allow to configure the Dolphin services. - */ -class DolphinServicesConfigModule : public KCModule -{ - Q_OBJECT - -public: - DolphinServicesConfigModule(QWidget* parent, const QVariantList& args); - ~DolphinServicesConfigModule() override; - - void save() override; - void defaults() override; - -private: - ServicesSettingsPage *m_services; -}; - -#endif diff --git a/src/settings/services/servicemenu.knsrc b/src/settings/services/servicemenu.knsrc deleted file mode 100644 index 0d1c103f6..000000000 --- a/src/settings/services/servicemenu.knsrc +++ /dev/null @@ -1,9 +0,0 @@ -[KNewStuff2] -ProvidersUrl=https://download.kde.org/ocs/providers.xml -Categories=Dolphin Service Menus -ChecksumPolicy=ifpossible -SignaturePolicy=ifpossible -TargetDir=servicemenu-download -Uncompress=never -InstallationCommand=servicemenuinstaller install %f -UninstallCommand=servicemenuinstaller uninstall %f diff --git a/src/settings/services/servicemenuinstaller/CMakeLists.txt b/src/settings/services/servicemenuinstaller/CMakeLists.txt deleted file mode 100644 index 46b159079..000000000 --- a/src/settings/services/servicemenuinstaller/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ -remove_definitions(-DTRANSLATION_DOMAIN=\"dolphin\") -add_definitions(-DTRANSLATION_DOMAIN=\"dolphin_servicemenuinstaller\") - -add_executable(servicemenuinstaller servicemenuinstaller.cpp) -target_link_libraries(servicemenuinstaller PRIVATE - Qt5::Core - Qt5::Gui - KF5::I18n - KF5::CoreAddons -) - -if(HAVE_PACKAGEKIT) - target_link_libraries(servicemenuinstaller PRIVATE PK::packagekitqt5) -endif() -install(TARGETS servicemenuinstaller ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/src/settings/services/servicemenuinstaller/Messages.sh b/src/settings/services/servicemenuinstaller/Messages.sh deleted file mode 100755 index 5012eead6..000000000 --- a/src/settings/services/servicemenuinstaller/Messages.sh +++ /dev/null @@ -1,2 +0,0 @@ -#! /usr/bin/env bash -$XGETTEXT `find . -name \*.cpp` -o $podir/dolphin_servicemenuinstaller.pot diff --git a/src/settings/services/servicemenuinstaller/servicemenuinstaller.cpp b/src/settings/services/servicemenuinstaller/servicemenuinstaller.cpp deleted file mode 100644 index 91da3d256..000000000 --- a/src/settings/services/servicemenuinstaller/servicemenuinstaller.cpp +++ /dev/null @@ -1,462 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2019 Alexander Potashev - * - * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../../config-packagekit.h" - -Q_GLOBAL_STATIC_WITH_ARGS(QStringList, binaryPackages, ({QLatin1String("application/vnd.debian.binary-package"), - QLatin1String("application/x-rpm"), - QLatin1String("application/x-xz"), - QLatin1String("application/zstd")})) - -enum PackageOperation { - Install, - Uninstall -}; - -#ifdef HAVE_PACKAGEKIT -#include -#include -#include -#else -#include -#endif - -// @param msg Error that gets logged to CLI -Q_NORETURN void fail(const QString &str) -{ - qCritical() << str; - const QStringList args = {"--detailederror" ,i18n("Dolphin service menu installation failed"), str}; - QProcess::startDetached("kdialog", args); - - exit(1); -} - -QString getServiceMenusDir() -{ - const QString dataLocation = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); - return QDir(dataLocation).absoluteFilePath("kservices5/ServiceMenus"); -} - -#ifdef HAVE_PACKAGEKIT -void packageKitInstall(const QString &fileName) -{ - PackageKit::Transaction *transaction = PackageKit::Daemon::installFile(fileName, PackageKit::Transaction::TransactionFlagNone); - - const auto exitWithError = [=](PackageKit::Transaction::Error, const QString &details) { - fail(details); - }; - - QObject::connect(transaction, &PackageKit::Transaction::finished, - [=](PackageKit::Transaction::Exit status, uint) { - if (status == PackageKit::Transaction::ExitSuccess) { - exit(0); - } - // Fallback error handling - QTimer::singleShot(500, [=](){ - fail(i18n("Failed to install \"%1\", exited with status \"%2\"", - fileName, QVariant::fromValue(status).toString())); - }); - }); - QObject::connect(transaction, &PackageKit::Transaction::errorCode, exitWithError); -} - -void packageKitUninstall(const QString &fileName) -{ - const auto exitWithError = [=](PackageKit::Transaction::Error, const QString &details) { - fail(details); - }; - const auto uninstallLambda = [=](PackageKit::Transaction::Exit status, uint) { - if (status == PackageKit::Transaction::ExitSuccess) { - exit(0); - } - }; - - PackageKit::Transaction *transaction = PackageKit::Daemon::getDetailsLocal(fileName); - QObject::connect(transaction, &PackageKit::Transaction::details, - [=](const PackageKit::Details &details) { - PackageKit::Transaction *transaction = PackageKit::Daemon::removePackage(details.packageId()); - QObject::connect(transaction, &PackageKit::Transaction::finished, uninstallLambda); - QObject::connect(transaction, &PackageKit::Transaction::errorCode, exitWithError); - }); - - QObject::connect(transaction, &PackageKit::Transaction::errorCode, exitWithError); - // Fallback error handling - QObject::connect(transaction, &PackageKit::Transaction::finished, - [=](PackageKit::Transaction::Exit status, uint) { - if (status != PackageKit::Transaction::ExitSuccess) { - QTimer::singleShot(500, [=]() { - fail(i18n("Failed to uninstall \"%1\", exited with status \"%2\"", - fileName, QVariant::fromValue(status).toString())); - }); - } - }); - } -#endif - -Q_NORETURN void packageKit(PackageOperation operation, const QString &fileName) -{ -#ifdef HAVE_PACKAGEKIT - QFileInfo fileInfo(fileName); - if (!fileInfo.exists()) { - fail(i18n("The file does not exist!")); - } - const QString absPath = fileInfo.absoluteFilePath(); - if (operation == PackageOperation::Install) { - packageKitInstall(absPath); - } else { - packageKitUninstall(absPath); - } - QGuiApplication::exec(); // For event handling, no return after signals finish - fail(i18n("Unknown error when installing package")); -#else - Q_UNUSED(operation) - QDesktopServices::openUrl(QUrl(fileName)); - exit(0); -#endif -} - -struct UncompressCommand -{ - QString command; - QStringList args1; - QStringList args2; -}; - -enum ScriptExecution{ - Process, - Konsole -}; - -void runUncompress(const QString &inputPath, const QString &outputPath) -{ - QVector> mimeTypeToCommand; - mimeTypeToCommand.append({{"application/x-tar", "application/tar", "application/x-gtar", "multipart/x-tar"}, - UncompressCommand({"tar", {"-xf"}, {"-C"}})}); - mimeTypeToCommand.append({{"application/x-gzip", "application/gzip", - "application/x-gzip-compressed-tar", "application/gzip-compressed-tar", - "application/x-gzip-compressed", "application/gzip-compressed", - "application/tgz", "application/x-compressed-tar", - "application/x-compressed-gtar", "file/tgz", - "multipart/x-tar-gz", "application/x-gunzip", "application/gzipped", - "gzip/document"}, - UncompressCommand({"tar", {"-zxf"}, {"-C"}})}); - mimeTypeToCommand.append({{"application/bzip", "application/bzip2", "application/x-bzip", - "application/x-bzip2", "application/bzip-compressed", - "application/bzip2-compressed", "application/x-bzip-compressed", - "application/x-bzip2-compressed", "application/bzip-compressed-tar", - "application/bzip2-compressed-tar", "application/x-bzip-compressed-tar", - "application/x-bzip2-compressed-tar", "application/x-bz2"}, - UncompressCommand({"tar", {"-jxf"}, {"-C"}})}); - mimeTypeToCommand.append({{"application/zip", "application/x-zip", "application/x-zip-compressed", - "multipart/x-zip"}, - UncompressCommand({"unzip", {}, {"-d"}})}); - - const auto mime = QMimeDatabase().mimeTypeForFile(inputPath).name(); - - UncompressCommand command{}; - for (const auto &pair : qAsConst(mimeTypeToCommand)) { - if (pair.first.contains(mime)) { - command = pair.second; - break; - } - } - - if (command.command.isEmpty()) { - fail(i18n("Unsupported archive type %1: %2", mime, inputPath)); - } - - QProcess process; - process.start( - command.command, - QStringList() << command.args1 << inputPath << command.args2 << outputPath, - QIODevice::NotOpen); - if (!process.waitForStarted()) { - fail(i18n("Failed to run uncompressor command for %1", inputPath)); - } - - if (!process.waitForFinished()) { - fail( - i18n("Process did not finish in reasonable time: %1 %2", process.program(), process.arguments().join(" "))); - } - - if (process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) { - fail(i18n("Failed to uncompress %1", inputPath)); - } -} - -QString findRecursive(const QString &dir, const QString &basename) -{ - QDirIterator it(dir, QStringList{basename}, QDir::Files, QDirIterator::Subdirectories); - while (it.hasNext()) { - return QFileInfo(it.next()).canonicalFilePath(); - } - - return QString(); -} - -bool runScriptOnce(const QString &path, const QStringList &args, ScriptExecution execution) -{ - QProcess process; - process.setWorkingDirectory(QFileInfo(path).absolutePath()); - - const static bool konsoleAvailable = !QStandardPaths::findExecutable("konsole").isEmpty(); - if (konsoleAvailable && execution == ScriptExecution::Konsole) { - QString bashCommand = KShell::quoteArg(path) + ' '; - if (!args.isEmpty()) { - bashCommand.append(args.join(' ')); - } - bashCommand.append("|| $SHELL"); - // If the install script fails a shell opens and the user can fix the problem - // without an error konsole closes - process.start("konsole", QStringList() << "-e" << "bash" << "-c" << bashCommand, QIODevice::NotOpen); - } else { - process.start(path, args, QIODevice::NotOpen); - } - if (!process.waitForStarted()) { - fail(i18n("Failed to run installer script %1", path)); - } - - // Wait until installer exits, without timeout - if (!process.waitForFinished(-1)) { - qWarning() << "Failed to wait on installer:" << process.program() << process.arguments().join(" "); - return false; - } - - if (process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) { - qWarning() << "Installer script exited with error:" << process.program() << process.arguments().join(" "); - return false; - } - - return true; -} - -// If hasArgVariants is true, run "path". -// If hasArgVariants is false, run "path argVariants[i]" until successful. -bool runScriptVariants(const QString &path, bool hasArgVariants, const QStringList &argVariants, QString &errorText) -{ - QFile file(path); - if (!file.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner)) { - errorText = i18n("Failed to set permissions on %1: %2", path, file.errorString()); - return false; - } - - qInfo() << "[servicemenuinstaller]: Trying to run installer/uninstaller" << path; - if (hasArgVariants) { - for (const auto &arg : argVariants) { - if (runScriptOnce(path, {arg}, ScriptExecution::Process)) { - return true; - } - } - } else if (runScriptOnce(path, {}, ScriptExecution::Konsole)) { - return true; - } - - errorText = i18nc( - "%2 = comma separated list of arguments", - "Installer script %1 failed, tried arguments \"%2\".", path, argVariants.join(i18nc("Separator between arguments", "\", \""))); - return false; -} - -QString generateDirPath(const QString &archive) -{ - return QStringLiteral("%1-dir").arg(archive); -} - -bool cmdInstall(const QString &archive, QString &errorText) -{ - const auto serviceDir = getServiceMenusDir(); - if (!QDir().mkpath(serviceDir)) { - // TODO Cannot get error string because of this bug: https://bugreports.qt.io/browse/QTBUG-1483 - errorText = i18n("Failed to create path %1", serviceDir); - return false; - } - - if (archive.endsWith(QLatin1String(".desktop"))) { - // Append basename to destination directory - const auto dest = QDir(serviceDir).absoluteFilePath(QFileInfo(archive).fileName()); - if (QFileInfo::exists(dest)) { - QFile::remove(dest); - } - qInfo() << "Single-File Service-Menu" << archive << dest; - - QFile source(archive); - if (!source.copy(dest)) { - errorText = i18n("Failed to copy .desktop file %1 to %2: %3", archive, dest, source.errorString()); - return false; - } - } else { - if (binaryPackages->contains(QMimeDatabase().mimeTypeForFile(archive).name())) { - packageKit(PackageOperation::Install, archive); - } - const QString dir = generateDirPath(archive); - if (QFile::exists(dir)) { - if (!QDir(dir).removeRecursively()) { - errorText = i18n("Failed to remove directory %1", dir); - return false; - } - } - - if (QDir().mkdir(dir)) { - errorText = i18n("Failed to create directory %1", dir); - } - - runUncompress(archive, dir); - - // Try "install-it" first - QString installItPath; - const QStringList basenames1 = {"install-it.sh", "install-it"}; - for (const auto &basename : basenames1) { - const auto path = findRecursive(dir, basename); - if (!path.isEmpty()) { - installItPath = path; - break; - } - } - - if (!installItPath.isEmpty()) { - return runScriptVariants(installItPath, false, QStringList{}, errorText); - } - - // If "install-it" is missing, try "install" - QString installerPath; - const QStringList basenames2 = {"installKDE4.sh", "installKDE4", "install.sh", "install"}; - for (const auto &basename : basenames2) { - const auto path = findRecursive(dir, basename); - if (!path.isEmpty()) { - installerPath = path; - break; - } - } - - if (!installerPath.isEmpty()) { - // Try to run script without variants first - if (!runScriptVariants(installerPath, false, {}, errorText)) { - return runScriptVariants(installerPath, true, {"--local", "--local-install", "--install"}, errorText); - } - return true; - } - - fail(i18n("Failed to find an installation script in %1", dir)); - } - - return true; -} - -bool cmdUninstall(const QString &archive, QString &errorText) -{ - const auto serviceDir = getServiceMenusDir(); - if (archive.endsWith(QLatin1String(".desktop"))) { - // Append basename to destination directory - const auto dest = QDir(serviceDir).absoluteFilePath(QFileInfo(archive).fileName()); - QFile file(dest); - if (!file.remove()) { - errorText = i18n("Failed to remove .desktop file %1: %2", dest, file.errorString()); - return false; - } - } else { - if (binaryPackages->contains(QMimeDatabase().mimeTypeForFile(archive).name())) { - packageKit(PackageOperation::Uninstall, archive); - } - const QString dir = generateDirPath(archive); - - // Try "deinstall" first - QString deinstallPath; - const QStringList basenames1 = {"uninstall.sh", "uninstal", "deinstall.sh", "deinstall"}; - for (const auto &basename : basenames1) { - const auto path = findRecursive(dir, basename); - if (!path.isEmpty()) { - deinstallPath = path; - break; - } - } - - if (!deinstallPath.isEmpty()) { - const bool ok = runScriptVariants(deinstallPath, false, {}, errorText); - if (!ok) { - return ok; - } - } else { - // If "deinstall" is missing, try "install --uninstall" - QString installerPath; - const QStringList basenames2 = {"install-it.sh", "install-it", "installKDE4.sh", - "installKDE4", "install.sh", "install"}; - for (const auto &basename : basenames2) { - const auto path = findRecursive(dir, basename); - if (!path.isEmpty()) { - installerPath = path; - break; - } - } - - if (!installerPath.isEmpty()) { - const bool ok = runScriptVariants(installerPath, true, - {"--remove", "--delete", "--uninstall", "--deinstall"}, errorText); - if (!ok) { - return ok; - } - } else { - fail(i18n("Failed to find an uninstallation script in %1", dir)); - } - } - - QDir dirObject(dir); - if (!dirObject.removeRecursively()) { - errorText = i18n("Failed to remove directory %1", dir); - return false; - } - } - - return true; -} - -int main(int argc, char *argv[]) -{ - QGuiApplication app(argc, argv); - - QCommandLineParser parser; - parser.addPositionalArgument(QStringLiteral("command"), i18nc("@info:shell", "Command to execute: install or uninstall.")); - parser.addPositionalArgument(QStringLiteral("path"), i18nc("@info:shell", "Path to archive.")); - parser.process(app); - - const QStringList args = parser.positionalArguments(); - if (args.isEmpty()) { - fail(i18n("Command is required.")); - } - if (args.size() == 1) { - fail(i18n("Path to archive is required.")); - } - - const QString cmd = args[0]; - const QString archive = args[1]; - - QString errorText; - if (cmd == QLatin1String("install")) { - if (!cmdInstall(archive, errorText)) { - fail(errorText); - } - } else if (cmd == QLatin1String("uninstall")) { - if (!cmdUninstall(archive, errorText)) { - fail(errorText); - } - } else { - fail(i18n("Unsupported command %1", cmd)); - } - - return 0; -} diff --git a/src/settings/services/servicessettingspage.cpp b/src/settings/services/servicessettingspage.cpp deleted file mode 100644 index fa064d8a1..000000000 --- a/src/settings/services/servicessettingspage.cpp +++ /dev/null @@ -1,287 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2009-2010 Peter Penz - * - * SPDX-License-Identifier: GPL-2.0-or-later - */ - -#include "servicessettingspage.h" - -#include "dolphin_generalsettings.h" -#include "dolphin_versioncontrolsettings.h" -#include "settings/serviceitemdelegate.h" -#include "settings/servicemodel.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace -{ - const bool ShowDeleteDefault = false; - const char VersionControlServicePrefix[] = "_version_control_"; - const char DeleteService[] = "_delete"; - const char CopyToMoveToService[] ="_copy_to_move_to"; -} - -ServicesSettingsPage::ServicesSettingsPage(QWidget* parent) : - SettingsPageBase(parent), - m_initialized(false), - m_serviceModel(nullptr), - m_sortModel(nullptr), - m_listView(nullptr), - m_enabledVcsPlugins() -{ - QVBoxLayout* topLayout = new QVBoxLayout(this); - - QLabel* label = new QLabel(i18nc("@label:textbox", - "Select which services should " - "be shown in the context menu:"), this); - label->setWordWrap(true); - m_searchLineEdit = new QLineEdit(this); - m_searchLineEdit->setPlaceholderText(i18nc("@label:textbox", "Search...")); - connect(m_searchLineEdit, &QLineEdit::textChanged, this, [this](const QString &filter){ - m_sortModel->setFilterFixedString(filter); - }); - - m_listView = new QListView(this); - QScroller::grabGesture(m_listView->viewport(), QScroller::TouchGesture); - - auto *delegate = new ServiceItemDelegate(m_listView, m_listView); - m_serviceModel = new ServiceModel(this); - m_sortModel = new QSortFilterProxyModel(this); - m_sortModel->setSourceModel(m_serviceModel); - m_sortModel->setSortRole(Qt::DisplayRole); - m_sortModel->setSortLocaleAware(true); - m_sortModel->setFilterRole(Qt::DisplayRole); - m_sortModel->setFilterCaseSensitivity(Qt::CaseInsensitive); - m_listView->setModel(m_sortModel); - m_listView->setItemDelegate(delegate); - m_listView->setVerticalScrollMode(QListView::ScrollPerPixel); - connect(m_listView, &QListView::clicked, this, &ServicesSettingsPage::changed); - -#ifndef Q_OS_WIN - auto *downloadButton = new KNS3::Button(i18nc("@action:button", "Download New Services..."), - QStringLiteral("servicemenu.knsrc"), - this); - connect(downloadButton, &KNS3::Button::dialogFinished, this, [this](const KNS3::Entry::List &changedEntries) { - if (!changedEntries.isEmpty()) { - m_serviceModel->clear(); - loadServices(); - } - }); - -#endif - - topLayout->addWidget(label); - topLayout->addWidget(m_searchLineEdit); - topLayout->addWidget(m_listView); -#ifndef Q_OS_WIN - topLayout->addWidget(downloadButton); -#endif - - m_enabledVcsPlugins = VersionControlSettings::enabledPlugins(); - std::sort(m_enabledVcsPlugins.begin(), m_enabledVcsPlugins.end()); -} - -ServicesSettingsPage::~ServicesSettingsPage() = default; - -void ServicesSettingsPage::applySettings() -{ - if (!m_initialized) { - return; - } - - KConfig config(QStringLiteral("kservicemenurc"), KConfig::NoGlobals); - KConfigGroup showGroup = config.group("Show"); - - QStringList enabledPlugins; - - const QAbstractItemModel *model = m_listView->model(); - for (int i = 0; i < model->rowCount(); ++i) { - const QModelIndex index = model->index(i, 0); - const QString service = model->data(index, ServiceModel::DesktopEntryNameRole).toString(); - const bool checked = model->data(index, Qt::CheckStateRole).toBool(); - - if (service.startsWith(VersionControlServicePrefix)) { - if (checked) { - enabledPlugins.append(model->data(index, Qt::DisplayRole).toString()); - } - } else if (service == QLatin1String(DeleteService)) { - KSharedConfig::Ptr globalConfig = KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::NoGlobals); - KConfigGroup configGroup(globalConfig, "KDE"); - configGroup.writeEntry("ShowDeleteCommand", checked); - configGroup.sync(); - } else if (service == QLatin1String(CopyToMoveToService)) { - GeneralSettings::setShowCopyMoveMenu(checked); - GeneralSettings::self()->save(); - } else { - showGroup.writeEntry(service, checked); - } - } - - showGroup.sync(); - - if (m_enabledVcsPlugins != enabledPlugins) { - VersionControlSettings::setEnabledPlugins(enabledPlugins); - VersionControlSettings::self()->save(); - - KMessageBox::information(window(), - i18nc("@info", "Dolphin must be restarted to apply the " - "updated version control systems settings."), - QString(), // default title - QStringLiteral("ShowVcsRestartInformation")); - } -} - -void ServicesSettingsPage::restoreDefaults() -{ - QAbstractItemModel* model = m_listView->model(); - for (int i = 0; i < model->rowCount(); ++i) { - const QModelIndex index = model->index(i, 0); - const QString service = model->data(index, ServiceModel::DesktopEntryNameRole).toString(); - - const bool checked = !service.startsWith(VersionControlServicePrefix) - && service != QLatin1String(DeleteService) - && service != QLatin1String(CopyToMoveToService); - model->setData(index, checked, Qt::CheckStateRole); - } -} - -void ServicesSettingsPage::showEvent(QShowEvent* event) -{ - if (!event->spontaneous() && !m_initialized) { - loadServices(); - - loadVersionControlSystems(); - - // Add "Show 'Delete' command" as service - KSharedConfig::Ptr globalConfig = KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::IncludeGlobals); - KConfigGroup configGroup(globalConfig, "KDE"); - addRow(QStringLiteral("edit-delete"), - i18nc("@option:check", "Delete"), - DeleteService, - configGroup.readEntry("ShowDeleteCommand", ShowDeleteDefault)); - - // Add "Show 'Copy To' and 'Move To' commands" as service - addRow(QStringLiteral("edit-copy"), - i18nc("@option:check", "'Copy To' and 'Move To' commands"), - CopyToMoveToService, - GeneralSettings::showCopyMoveMenu()); - - m_sortModel->sort(Qt::DisplayRole); - - m_initialized = true; - } - SettingsPageBase::showEvent(event); -} - -void ServicesSettingsPage::loadServices() -{ - const KConfig config(QStringLiteral("kservicemenurc"), KConfig::NoGlobals); - const KConfigGroup showGroup = config.group("Show"); - - // Load generic services - const KService::List entries = KServiceTypeTrader::self()->query(QStringLiteral("KonqPopupMenu/Plugin")); - for (const KService::Ptr &service : entries) { - const QString file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kservices5/" % service->entryPath()); - const QList serviceActions = KDesktopFileActions::userDefinedServices(file, true); - - const KDesktopFile desktopFile(file); - const QString subMenuName = desktopFile.desktopGroup().readEntry("X-KDE-Submenu"); - - for (const KServiceAction &action : serviceActions) { - const QString serviceName = action.name(); - const bool addService = !action.noDisplay() && !action.isSeparator() && !isInServicesList(serviceName); - - if (addService) { - const QString itemName = subMenuName.isEmpty() - ? action.text() - : i18nc("@item:inmenu", "%1: %2", subMenuName, action.text()); - const bool checked = showGroup.readEntry(serviceName, true); - addRow(action.icon(), itemName, serviceName, checked); - } - } - } - - // Load service plugins that implement the KFileItemActionPlugin interface - const KService::List pluginServices = KServiceTypeTrader::self()->query(QStringLiteral("KFileItemAction/Plugin")); - for (const KService::Ptr &service : pluginServices) { - const QString desktopEntryName = service->desktopEntryName(); - if (!isInServicesList(desktopEntryName)) { - const bool checked = showGroup.readEntry(desktopEntryName, true); - addRow(service->icon(), service->name(), desktopEntryName, checked); - } - } - - // Load JSON-based plugins that implement the KFileItemActionPlugin interface - const auto jsonPlugins = KPluginLoader::findPlugins(QStringLiteral("kf5/kfileitemaction"), [](const KPluginMetaData& metaData) { - return metaData.serviceTypes().contains(QLatin1String("KFileItemAction/Plugin")); - }); - - for (const auto &jsonMetadata : jsonPlugins) { - const QString desktopEntryName = jsonMetadata.pluginId(); - if (!isInServicesList(desktopEntryName)) { - const bool checked = showGroup.readEntry(desktopEntryName, true); - addRow(jsonMetadata.iconName(), jsonMetadata.name(), desktopEntryName, checked); - } - } - - m_sortModel->sort(Qt::DisplayRole); - m_searchLineEdit->setFocus(Qt::OtherFocusReason); -} - -void ServicesSettingsPage::loadVersionControlSystems() -{ - const QStringList enabledPlugins = VersionControlSettings::enabledPlugins(); - - // Create a checkbox for each available version control plugin - const KService::List pluginServices = KServiceTypeTrader::self()->query(QStringLiteral("FileViewVersionControlPlugin")); - for (const auto &plugin : pluginServices) { - const QString pluginName = plugin->name(); - addRow(QStringLiteral("code-class"), - pluginName, - VersionControlServicePrefix + pluginName, - enabledPlugins.contains(pluginName)); - } - - m_sortModel->sort(Qt::DisplayRole); -} - -bool ServicesSettingsPage::isInServicesList(const QString &service) const -{ - for (int i = 0; i < m_serviceModel->rowCount(); ++i) { - const QModelIndex index = m_serviceModel->index(i, 0); - if (m_serviceModel->data(index, ServiceModel::DesktopEntryNameRole).toString() == service) { - return true; - } - } - return false; -} - -void ServicesSettingsPage::addRow(const QString &icon, - const QString &text, - const QString &value, - bool checked) -{ - m_serviceModel->insertRow(0); - - const QModelIndex index = m_serviceModel->index(0, 0); - m_serviceModel->setData(index, icon, Qt::DecorationRole); - m_serviceModel->setData(index, text, Qt::DisplayRole); - m_serviceModel->setData(index, value, ServiceModel::DesktopEntryNameRole); - m_serviceModel->setData(index, checked, Qt::CheckStateRole); -} - diff --git a/src/settings/services/servicessettingspage.h b/src/settings/services/servicessettingspage.h deleted file mode 100644 index b569852ae..000000000 --- a/src/settings/services/servicessettingspage.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2009-2010 Peter Penz - * - * SPDX-License-Identifier: GPL-2.0-or-later - */ -#ifndef SERVICESSETTINGSPAGE_H -#define SERVICESSETTINGSPAGE_H - -#include "settings/settingspagebase.h" - -#include - -class QListView; -class QSortFilterProxyModel; -class ServiceModel; -class QLineEdit; - -/** - * @brief Page for the 'Services' settings of the Dolphin settings dialog. - */ -class ServicesSettingsPage : public SettingsPageBase -{ - Q_OBJECT - -public: - explicit ServicesSettingsPage(QWidget* parent); - ~ServicesSettingsPage() override; - - /** @see SettingsPageBase::applySettings() */ - void applySettings() override; - - /** @see SettingsPageBase::restoreDefaults() */ - void restoreDefaults() override; - -protected: - void showEvent(QShowEvent* event) override; - -private slots: - /** - * Loads locally installed services. - */ - void loadServices(); - -private: - /** - * Loads installed version control systems. - */ - void loadVersionControlSystems(); - - bool isInServicesList(const QString &service) const; - - /** - * Adds a row to the model of m_listView. - */ - void addRow(const QString &icon, - const QString &text, - const QString &value, - bool checked); - -private: - bool m_initialized; - ServiceModel *m_serviceModel; - QSortFilterProxyModel *m_sortModel; - QListView* m_listView; - QLineEdit *m_searchLineEdit; - QStringList m_enabledVcsPlugins; -}; - -#endif diff --git a/src/settings/services/test/service_menu_deinstallation_test.rb b/src/settings/services/test/service_menu_deinstallation_test.rb deleted file mode 100644 index bf44b7b7f..000000000 --- a/src/settings/services/test/service_menu_deinstallation_test.rb +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env ruby - -# SPDX-FileCopyrightText: 2019 Harald Sitter -# -# SPDX-License-Identifier: GPL-2.0-or-later - -require_relative 'test_helper' - -require 'tmpdir' - -class ServiceMenuDeinstallationTest < Test::Unit::TestCase - def setup - @tmpdir = Dir.mktmpdir("dolphintest-#{self.class.to_s.tr(':', '_')}") - @pwdir = Dir.pwd - Dir.chdir(@tmpdir) - - ENV['XDG_DATA_HOME'] = File.join(@tmpdir, 'data') - end - - def teardown - Dir.chdir(@pwdir) - FileUtils.rm_rf(@tmpdir) - - ENV.delete('XDG_DATA_HOME') - end - - def test_run_deinstall - service_dir = File.join(Dir.pwd, 'share/servicemenu-download') - archive_base = "#{service_dir}/foo.zip" - archive_dir = "#{archive_base}-dir/foo-1.1/" - FileUtils.mkpath(archive_dir) - File.write("#{archive_dir}/deinstall.sh", <<-DEINSTALL_SH) -#!/bin/sh -set -e -cat deinstall.sh -touch #{@tmpdir}/deinstall.sh-run - DEINSTALL_SH - File.write("#{archive_dir}/install.sh", <<-INSTALL_SH) -#!/bin/sh -set -e -cat install.sh -touch #{@tmpdir}/install.sh-run - INSTALL_SH - - assert(system('servicemenuinstaller', 'uninstall', archive_base)) - - # deinstaller should be run - # installer should not be run - # archive_dir should have been correctly removed - - assert_path_exist('deinstall.sh-run') - assert_path_not_exist('install.sh-run') - assert_path_not_exist(archive_dir) - end - - def test_run_install_with_arg - service_dir = File.join(Dir.pwd, 'share/servicemenu-download') - archive_base = "#{service_dir}/foo.zip" - archive_dir = "#{archive_base}-dir/foo-1.1/" - FileUtils.mkpath(archive_dir) - - File.write("#{archive_dir}/install.sh", <<-INSTALL_SH) -#!/bin/sh -if [ "$@" = "--uninstall" ]; then - touch #{@tmpdir}/install.sh-run - exit 0 -fi -exit 1 - INSTALL_SH - - assert(system('servicemenuinstaller', 'uninstall', archive_base)) - - assert_path_not_exist('deinstall.sh-run') - assert_path_exist('install.sh-run') - assert_path_not_exist(archive_dir) - end - - # no scripts in sight - def test_run_fail - service_dir = File.join(Dir.pwd, 'share/servicemenu-download') - archive_base = "#{service_dir}/foo.zip" - archive_dir = "#{archive_base}-dir/foo-1.1/" - FileUtils.mkpath(archive_dir) - - refute(system('servicemenuinstaller', 'uninstall', archive_base)) - - # I am unsure if deinstallation really should keep the files around. But - # that's how it behaved originally so it's supposedly intentional - # - sitter, 2019 - assert_path_exist(archive_dir) - end - - # For desktop files things are a bit special. There is one in .local/share/servicemenu-download - # and another in the actual ServiceMenus dir. The latter gets removed by the - # script, the former by KNS. - def test_run_desktop - service_dir = File.join(Dir.pwd, 'share/servicemenu-download') - downloaded_file = "#{service_dir}/foo.desktop" - FileUtils.mkpath(service_dir) - FileUtils.touch(downloaded_file) - - menu_dir = "#{ENV['XDG_DATA_HOME']}/kservices5/ServiceMenus/" - installed_file = "#{menu_dir}/foo.desktop" - FileUtils.mkpath(menu_dir) - FileUtils.touch(installed_file) - - assert(system('servicemenuinstaller', 'uninstall', downloaded_file)) - - assert_path_exist(downloaded_file) - assert_path_not_exist(installed_file) - end -end diff --git a/src/settings/services/test/service_menu_installation_test.rb b/src/settings/services/test/service_menu_installation_test.rb deleted file mode 100644 index 7c05a40e3..000000000 --- a/src/settings/services/test/service_menu_installation_test.rb +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env ruby - -# SPDX-FileCopyrightText: 2019 Harald Sitter -# -# SPDX-License-Identifier: GPL-2.0-or-later - -require_relative 'test_helper' - -require 'tmpdir' - -class ServiceMenuInstallationTest < Test::Unit::TestCase - def setup - @tmpdir = Dir.mktmpdir("dolphintest-#{self.class.to_s.tr(':', '_')}") - @pwdir = Dir.pwd - Dir.chdir(@tmpdir) - - ENV['XDG_DATA_HOME'] = File.join(@tmpdir, 'data') - end - - def teardown - Dir.chdir(@pwdir) - FileUtils.rm_rf(@tmpdir) - - ENV.delete('XDG_DATA_HOME') - end - - def test_run_install - service_dir = File.join(Dir.pwd, 'share/servicemenu-download') - FileUtils.mkpath(service_dir) - archive = "#{service_dir}/foo.tar" - - archive_dir = 'foo' # relative so tar cf is relative without fuzz - FileUtils.mkpath(archive_dir) - File.write("#{archive_dir}/install-it.sh", <<-INSTALL_IT_SH) -#!/bin/sh -touch #{@tmpdir}/install-it.sh-run -INSTALL_IT_SH - File.write("#{archive_dir}/install.sh", <<-INSTALL_SH) -#!/bin/sh -touch #{@tmpdir}/install.sh-run - INSTALL_SH - assert(system('tar', '-cf', archive, archive_dir)) - - assert(system('servicemenuinstaller', 'install', archive)) - - tar_dir = "#{service_dir}/foo.tar-dir" - tar_extract_dir = "#{service_dir}/foo.tar-dir/foo" - assert_path_exist(tar_dir) - assert_path_exist(tar_extract_dir) - assert_path_exist("#{tar_extract_dir}/install-it.sh") - assert_path_exist("#{tar_extract_dir}/install.sh") - end - - def test_run_install_with_arg - service_dir = File.join(Dir.pwd, 'share/servicemenu-download') - FileUtils.mkpath(service_dir) - archive = "#{service_dir}/foo.tar" - - archive_dir = 'foo' # relative so tar cf is relative without fuzz - FileUtils.mkpath(archive_dir) - File.write("#{archive_dir}/install.sh", <<-INSTALL_SH) -#!/bin/sh -if [ "$@" = "--install" ]; then - touch #{@tmpdir}/install.sh-run - exit 0 -fi -exit 1 - INSTALL_SH - assert(system('tar', '-cf', archive, archive_dir)) - - assert(system('servicemenuinstaller', 'install', archive)) - - tar_dir = "#{service_dir}/foo.tar-dir" - tar_extract_dir = "#{service_dir}/foo.tar-dir/foo" - assert_path_exist(tar_dir) - assert_path_exist(tar_extract_dir) - assert_path_not_exist("#{tar_extract_dir}/install-it.sh") - assert_path_exist("#{tar_extract_dir}/install.sh") - end - - def test_run_fail - service_dir = File.join(Dir.pwd, 'share/servicemenu-download') - FileUtils.mkpath(service_dir) - archive = "#{service_dir}/foo.tar" - - archive_dir = 'foo' # relative so tar cf is relative without fuzz - FileUtils.mkpath(archive_dir) - assert(system('tar', '-cf', archive, archive_dir)) - - refute(system('servicemenuinstaller', 'install', archive)) - end - - def test_run_desktop - service_dir = File.join(Dir.pwd, 'share/servicemenu-download') - downloaded_file = "#{service_dir}/foo.desktop" - FileUtils.mkpath(service_dir) - FileUtils.touch(downloaded_file) - - installed_file = "#{ENV['XDG_DATA_HOME']}/kservices5/ServiceMenus/foo.desktop" - - assert(system('servicemenuinstaller', 'install', downloaded_file)) - - assert_path_exist(downloaded_file) - assert_path_exist(installed_file) - end -end diff --git a/src/settings/services/test/test_helper.rb b/src/settings/services/test/test_helper.rb deleted file mode 100644 index b4e4dded2..000000000 --- a/src/settings/services/test/test_helper.rb +++ /dev/null @@ -1,18 +0,0 @@ -# SPDX-FileCopyrightText: 2019 Harald Sitter -# -# SPDX-License-Identifier: GPL-2.0-or-later - -$LOAD_PATH.unshift(File.absolute_path('../', __dir__)) # ../ - -def __test_method_name__ - return @method_name if defined?(:@method_name) - index = 0 - caller = '' - until caller.start_with?('test_') - caller = caller_locations(index, 1)[0].label - index += 1 - end - caller -end - -require 'test/unit' diff --git a/src/settings/services/test/test_run.rb b/src/settings/services/test/test_run.rb deleted file mode 100755 index ab298a0b0..000000000 --- a/src/settings/services/test/test_run.rb +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env ruby - -# SPDX-FileCopyrightText: 2019 Harald Sitter -# -# SPDX-License-Identifier: GPL-2.0-or-later -# This is a fancy wrapper around test_helper to prevent the collector from -# loading the helper twice as it would occur if we ran the helper directly. - -require_relative 'test_helper' - -Test::Unit::AutoRunner.run(true, File.absolute_path(__dir__)) diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index ee0a1f3fd..82db749b5 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -84,5 +84,5 @@ set_package_properties(Gem:test-unit PROPERTIES DESCRIPTION "Ruby gem 'test-unit' required for testing of servicemenu helpers.") if (Gem:test-unit_FOUND) add_test(NAME servicemenutest - COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/../settings/services/test/test_run.rb) + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/../settings/contextmenu/test/test_run.rb) endif() -- cgit v1.3