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"