Warning, file /frameworks/kglobalaccel/src/runtime/kserviceactioncomponent.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     SPDX-FileCopyrightText: 2008 Michael Jansen <kde@michael-jansen.biz>
0003     SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
0004     SPDX-FileCopyrightText: 2021 David Redondo <kde@david-redondo.de>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "kserviceactioncomponent.h"
0010 #include "logging_p.h"
0011 
0012 #include <QDBusConnectionInterface>
0013 #include <QFileInfo>
0014 #include <QProcess>
0015 
0016 #include <KShell>
0017 #include <KWindowSystem>
0018 
0019 #include "config-kglobalaccel.h"
0020 #if HAVE_X11
0021 #include <KStartupInfo>
0022 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0023 #include <private/qtx11extras_p.h>
0024 #else
0025 #include <QX11Info>
0026 #endif
0027 #endif
0028 
0029 KServiceActionComponent::KServiceActionComponent(const QString &serviceStorageId, const QString &friendlyName)
0030     : Component(serviceStorageId, friendlyName)
0031     , m_serviceStorageId(serviceStorageId)
0032 {
0033     QString filePath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kglobalaccel/") + serviceStorageId);
0034     if (filePath.isEmpty()) {
0035         // Fallback to applications data dir for custom shortcut for instance
0036         filePath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("applications/") + serviceStorageId);
0037         m_isInApplicationsDir = true;
0038     } else {
0039         QFileInfo info(filePath);
0040         if (info.isSymLink()) {
0041             const QString filePath2 = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("applications/") + serviceStorageId);
0042             if (info.symLinkTarget() == filePath2) {
0043                 filePath = filePath2;
0044                 m_isInApplicationsDir = true;
0045             }
0046         }
0047     }
0048 
0049     if (filePath.isEmpty()) {
0050         qCWarning(KGLOBALACCELD) << "No desktop file found for service " << serviceStorageId;
0051     }
0052     m_desktopFile.reset(new KDesktopFile(filePath));
0053 }
0054 
0055 KServiceActionComponent::~KServiceActionComponent() = default;
0056 
0057 void KServiceActionComponent::runProcess(const KConfigGroup &group, const QString &token)
0058 {
0059     QStringList args = KShell::splitArgs(group.readEntry(QStringLiteral("Exec"), QString()));
0060     if (args.isEmpty()) {
0061         return;
0062     }
0063     // sometimes entries have an %u for command line parameters
0064     if (args.last().contains(QLatin1Char('%'))) {
0065         args.pop_back();
0066     }
0067 
0068     const QString command = args.takeFirst();
0069 
0070     auto startDetachedWithToken = [token](const QString &program, const QStringList &args) {
0071         QProcess p;
0072         p.setProgram(program);
0073         p.setArguments(args);
0074         QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
0075         if (!token.isEmpty()) {
0076             if (KWindowSystem::isPlatformWayland()) {
0077                 env.insert(QStringLiteral("XDG_ACTIVATION_TOKEN"), token);
0078             } else {
0079                 env.insert(QStringLiteral("DESKTOP_STARTUP_ID"), token);
0080             }
0081         }
0082         p.setProcessEnvironment(env);
0083         if (!p.startDetached()) {
0084             qCWarning(KGLOBALACCELD) << "Failed to start" << program;
0085         }
0086     };
0087 
0088     const auto kstart = QStandardPaths::findExecutable(QStringLiteral("kstart5"));
0089     if (!kstart.isEmpty()) {
0090         if (group.name() == QLatin1String("Desktop Entry") && m_isInApplicationsDir) {
0091             startDetachedWithToken(kstart, {QStringLiteral("--application"), QFileInfo(m_desktopFile->fileName()).completeBaseName()});
0092         } else {
0093             args.prepend(command);
0094             args.prepend(QStringLiteral("--"));
0095             startDetachedWithToken(kstart, args);
0096         }
0097         return;
0098     }
0099 
0100     QDBusConnectionInterface *dbusDaemon = QDBusConnection::sessionBus().interface();
0101     const bool klauncherAvailable = dbusDaemon->isServiceRegistered(QStringLiteral("org.kde.klauncher5"));
0102     if (klauncherAvailable) {
0103         QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.klauncher5"),
0104                                                           QStringLiteral("/KLauncher"),
0105                                                           QStringLiteral("org.kde.KLauncher"),
0106                                                           QStringLiteral("exec_blind"));
0107         msg << command << args;
0108 
0109         QDBusConnection::sessionBus().asyncCall(msg);
0110         return;
0111     }
0112 
0113     const QString cmdExec = QStandardPaths::findExecutable(command);
0114     if (cmdExec.isEmpty()) {
0115         qCWarning(KGLOBALACCELD) << "Could not find executable in PATH" << command;
0116         return;
0117     }
0118     startDetachedWithToken(cmdExec, args);
0119 }
0120 
0121 void KServiceActionComponent::emitGlobalShortcutPressed(const GlobalShortcut &shortcut)
0122 {
0123     // TODO KF6 use ApplicationLauncherJob to start processes when it's available in a framework that we depend on
0124 
0125     auto launchWithToken = [this, shortcut](const QString &token) {
0126         // DBusActivatatable spec as per https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#dbus
0127         if (m_desktopFile->desktopGroup().readEntry("DBusActivatable", false)) {
0128             QString method;
0129             const QString serviceName = m_serviceStorageId.chopped(strlen(".desktop"));
0130             const QString objectPath = QStringLiteral("/%1").arg(serviceName).replace(QLatin1Char('.'), QLatin1Char('/'));
0131             const QString interface = QStringLiteral("org.freedesktop.Application");
0132             QDBusMessage message;
0133             if (shortcut.uniqueName() == QLatin1String("_launch")) {
0134                 message = QDBusMessage::createMethodCall(serviceName, objectPath, interface, QStringLiteral("Activate"));
0135             } else {
0136                 message = QDBusMessage::createMethodCall(serviceName, objectPath, interface, QStringLiteral("ActivateAction"));
0137                 message << shortcut.uniqueName() << QVariantList();
0138             }
0139             if (!token.isEmpty()) {
0140                 if (KWindowSystem::isPlatformWayland()) {
0141                     message << QVariantMap{{QStringLiteral("activation-token"), token}};
0142                 } else {
0143                     message << QVariantMap{{QStringLiteral("desktop-startup-id"), token}};
0144                 }
0145             } else {
0146                 message << QVariantMap();
0147             }
0148 
0149             QDBusConnection::sessionBus().asyncCall(message);
0150             return;
0151         }
0152 
0153         // we can't use KRun there as it depends from KIO and would create a circular dep
0154         if (shortcut.uniqueName() == QLatin1String("_launch")) {
0155             runProcess(m_desktopFile->desktopGroup(), token);
0156             return;
0157         }
0158         const auto lstActions = m_desktopFile->readActions();
0159         for (const QString &action : lstActions) {
0160             if (action == shortcut.uniqueName()) {
0161                 runProcess(m_desktopFile->actionGroup(action), token);
0162                 return;
0163             }
0164         }
0165     };
0166     if (KWindowSystem::isPlatformWayland()) {
0167         const QString serviceName = m_serviceStorageId.chopped(strlen(".desktop"));
0168         KWindowSystem::requestXdgActivationToken(nullptr, 0, serviceName);
0169         connect(KWindowSystem::self(), &KWindowSystem::xdgActivationTokenArrived, this, [this, launchWithToken](int tokenSerial, const QString &token) {
0170             if (tokenSerial == 0) {
0171                 launchWithToken(token);
0172                 bool b = disconnect(KWindowSystem::self(), &KWindowSystem::xdgActivationTokenArrived, this, nullptr);
0173                 Q_ASSERT(b);
0174             }
0175         });
0176     } else {
0177 #if HAVE_X11
0178         launchWithToken(QString::fromUtf8(KStartupInfo::createNewStartupIdForTimestamp(QX11Info::appTime())));
0179 #endif
0180     }
0181 }
0182 
0183 void KServiceActionComponent::loadFromService()
0184 {
0185     auto registerGroupShortcut = [this](const QString &name, const KConfigGroup &group) {
0186         const QString shortcutString = group.readEntry(QStringLiteral("X-KDE-Shortcuts"), QString()).replace(QLatin1Char(','), QLatin1Char('\t'));
0187         GlobalShortcut *shortcut = registerShortcut(name, group.readEntry(QStringLiteral("Name"), QString()), shortcutString, shortcutString);
0188         shortcut->setIsPresent(true);
0189     };
0190 
0191     registerGroupShortcut(QStringLiteral("_launch"), m_desktopFile->desktopGroup());
0192     const auto lstActions = m_desktopFile->readActions();
0193     for (const QString &action : lstActions) {
0194         registerGroupShortcut(action, m_desktopFile->actionGroup(action));
0195     }
0196 }
0197 
0198 bool KServiceActionComponent::cleanUp()
0199 {
0200     qCDebug(KGLOBALACCELD) << "Disabling desktop file";
0201 
0202     const auto shortcuts = allShortcuts();
0203     for (GlobalShortcut *shortcut : shortcuts) {
0204         shortcut->setIsPresent(false);
0205     }
0206 
0207     return Component::cleanUp();
0208 }
0209 
0210 #include "moc_kserviceactioncomponent.cpp"