From f1148d9cb4a7028a91ba67799a76efa515b78c7d Mon Sep 17 00:00:00 2001 From: David Redondo Date: Thu, 13 Jan 2022 12:21:14 +0100 Subject: [PATCH] Load additional actions for app entries in application launchers This allows 3rd parties to provide additional actions in the context menu of applications in kicker, kickoff,... by supplying suitable desktopfiles in GENERIC_DATA_LOCATION/plasma/kickeractions similar to mechanism used for service menues. Entries for which the actions are displayed can be controlled by X-KDE-OnlyForAppIds in the [Desktop Entry], for example X-KDE-OnlyForAppIds=org.kde.kate,org.kde.dolphin This was requested as a patch for steam, but we've made a generic solution so that it can be used by everyone. --- applets/kicker/plugin/actionlist.cpp | 49 ++++++++++++++++++++ applets/kicker/plugin/actionlist.h | 3 ++ applets/kicker/plugin/appentry.cpp | 7 +++ applets/kicker/plugin/runnermatchesmodel.cpp | 7 +++ 4 files changed, 66 insertions(+) diff --git a/applets/kicker/plugin/actionlist.cpp b/applets/kicker/plugin/actionlist.cpp index 285c37adf..1fb31d69c 100644 --- a/applets/kicker/plugin/actionlist.cpp +++ b/applets/kicker/plugin/actionlist.cpp @@ -16,6 +16,8 @@ #include #include +#include +#include #include #include #include @@ -456,6 +458,53 @@ bool handleAppstreamActions(const QString &actionId, const QVariant &argument) return false; } +static QList additionalActions(const KService::Ptr &service) +{ + QList actions; + const static auto locations = + QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("plasma/kickeractions"), QStandardPaths::LocateDirectory); + const auto files = KFileUtils::findAllUniqueFiles(locations); + for (const auto &file : files) { + KService actionsService(file); + const auto filter = actionsService.property(QStringLiteral("X-KDE-OnlyForAppIds"), QVariant::StringList).toStringList(); + if (filter.empty() || filter.contains(storageIdFromService(service))) { + actions.append(KDesktopFileActions::userDefinedServices(actionsService, true)); + } + } + return actions; +} + +QVariantList additionalAppActions(const KService::Ptr &service) +{ + QVariantList list; + const auto actions = additionalActions(service); + list.reserve(actions.size()); + for (const auto &action : actions) { + list << createActionItem(action.text(), action.icon(), action.name(), action.service()->entryPath()); + } + return list; +} + +bool handleAdditionalAppActions(const QString &actionId, const KService::Ptr &service, const QVariant &argument) +{ + const KService actionProvider(argument.toString()); + if (!actionProvider.isValid()) { + return false; + } + const auto actions = actionProvider.actions(); + auto action = std::find_if(actions.begin(), actions.end(), [&actionId](const KServiceAction &action) { + return action.name() == actionId; + }); + if (action == actions.end()) { + return false; + } + auto *job = new KIO::ApplicationLauncherJob(*action); + job->setUrls({QUrl::fromLocalFile(resolvedServiceEntryPath(service))}); + job->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled)); + job->start(); + return true; +} + QString resolvedServiceEntryPath(const KService::Ptr &service) { QString path = service->entryPath(); diff --git a/applets/kicker/plugin/actionlist.h b/applets/kicker/plugin/actionlist.h index 2acbe0f5d..e8d59def0 100644 --- a/applets/kicker/plugin/actionlist.h +++ b/applets/kicker/plugin/actionlist.h @@ -56,6 +56,9 @@ bool handleEditApplicationAction(const QString &actionId, const KService::Ptr &s QVariantList appstreamActions(const KService::Ptr &service); bool handleAppstreamActions(const QString &actionId, const QVariant &argument); +QVariantList additionalAppActions(const KService::Ptr &service); +bool handleAdditionalAppActions(const QString &actionId, const KService::Ptr &service, const QVariant &argument); + QString resolvedServiceEntryPath(const KService::Ptr &service); } diff --git a/applets/kicker/plugin/appentry.cpp b/applets/kicker/plugin/appentry.cpp index 6f24d1dab..8ecdf63d7 100644 --- a/applets/kicker/plugin/appentry.cpp +++ b/applets/kicker/plugin/appentry.cpp @@ -168,6 +168,11 @@ QVariantList AppEntry::actions() const actionList << recentDocuments << Kicker::createSeparatorActionItem(); } + const QVariantList &additionalActions = Kicker::additionalAppActions(m_service); + if (!additionalActions.isEmpty()) { + actionList << additionalActions << Kicker::createSeparatorActionItem(); + } + // Don't allow adding launchers, editing, hiding, or uninstalling applications // when system is immutable. if (systemImmutable) { @@ -237,6 +242,8 @@ bool AppEntry::run(const QString &actionId, const QVariant &argument) job->setDesktopName(m_service->entryPath()); job->setIcon(m_service->icon()); return job->exec(); + } else if (Kicker::handleAdditionalAppActions(actionId, m_service, argument)) { + return true; } return Kicker::handleRecentDocumentAction(m_service, actionId, argument); diff --git a/applets/kicker/plugin/runnermatchesmodel.cpp b/applets/kicker/plugin/runnermatchesmodel.cpp index 154ab3324..8c70382ec 100644 --- a/applets/kicker/plugin/runnermatchesmodel.cpp +++ b/applets/kicker/plugin/runnermatchesmodel.cpp @@ -131,6 +131,11 @@ QVariant RunnerMatchesModel::data(const QModelIndex &index, int role) const actionList << recentDocuments << Kicker::createSeparatorActionItem(); } + const QVariantList &additionalActions = Kicker::additionalAppActions(service); + if (!additionalActions.isEmpty()) { + actionList << additionalActions << Kicker::createSeparatorActionItem(); + } + // Don't allow adding launchers, editing, hiding, or uninstalling applications // when system is immutable. if (systemImmutable) { @@ -209,6 +214,8 @@ bool RunnerMatchesModel::trigger(int row, const QString &actionId, const QVarian return job->exec(); } else if (actionId == QLatin1String("_kicker_recentDocument") || actionId == QLatin1String("_kicker_forgetRecentDocuments")) { return Kicker::handleRecentDocumentAction(service, actionId, argument); + } else if (Kicker::handleAdditionalAppActions(actionId, service, argument)) { + return true; } return false;