| 1 | /* |
| 2 | This file is part of the KDE libraries |
| 3 | SPDX-FileCopyrightText: 2020-2021 David Redondo <kde@david-redondo.de> |
| 4 | |
| 5 | SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL |
| 6 | */ |
| 7 | |
| 8 | #include "dbusactivationrunner_p.h" |
| 9 | |
| 10 | #include "kiogui_debug.h" |
| 11 | #include <KWindowSystem> |
| 12 | |
| 13 | #include <QDBusConnection> |
| 14 | #include <QDBusConnectionInterface> |
| 15 | #include <QDBusMessage> |
| 16 | #include <QDBusPendingCallWatcher> |
| 17 | #include <QTimer> |
| 18 | |
| 19 | bool DBusActivationRunner::activationPossible(const KService::Ptr service, KIO::ApplicationLauncherJob::RunFlags flags, const QString &suggestedFileName) |
| 20 | { |
| 21 | if (!service->isApplication()) { |
| 22 | return false; |
| 23 | } |
| 24 | if (service->property<bool>(QStringLiteral("DBusActivatable" ))) { |
| 25 | if (service->desktopEntryName().count(c: QLatin1Char('.')) < 2) { |
| 26 | qCWarning(KIO_GUI) << "Cannot activate" << service->desktopEntryName() << "doesn't have enough '.' for a well-formed service name" ; |
| 27 | return false; |
| 28 | } |
| 29 | if (!suggestedFileName.isEmpty()) { |
| 30 | qCDebug(KIO_GUI) << "Cannot activate" << service->desktopEntryName() << "because suggestedFileName is set" ; |
| 31 | return false; |
| 32 | } |
| 33 | if (flags & KIO::ApplicationLauncherJob::DeleteTemporaryFiles) { |
| 34 | qCDebug(KIO_GUI) << "Cannot activate" << service->desktopEntryName() << "because DeleteTemporaryFiles is set" ; |
| 35 | return false; |
| 36 | } |
| 37 | return true; |
| 38 | } |
| 39 | return false; |
| 40 | } |
| 41 | |
| 42 | DBusActivationRunner::DBusActivationRunner(const QString &action) |
| 43 | : KProcessRunner() |
| 44 | , m_actionName(action) |
| 45 | { |
| 46 | } |
| 47 | |
| 48 | void DBusActivationRunner::startProcess() |
| 49 | { |
| 50 | // DBusActivatable as per https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#dbus |
| 51 | const QString objectPath = QStringLiteral("/%1" ).arg(a: m_desktopName).replace(before: QLatin1Char('.'), after: QLatin1Char('/')).replace(before: QLatin1Char('-'), after: QLatin1Char('_')); |
| 52 | const QString interface = QStringLiteral("org.freedesktop.Application" ); |
| 53 | QDBusMessage message; |
| 54 | if (m_urls.isEmpty()) { |
| 55 | if (m_actionName.isEmpty()) { |
| 56 | message = QDBusMessage::createMethodCall(destination: m_desktopName, path: objectPath, interface, QStringLiteral("Activate" )); |
| 57 | } else { |
| 58 | message = QDBusMessage::createMethodCall(destination: m_desktopName, path: objectPath, interface, QStringLiteral("ActivateAction" )); |
| 59 | message << m_actionName << QVariantList(); |
| 60 | } |
| 61 | } else { |
| 62 | message = QDBusMessage::createMethodCall(destination: m_desktopName, path: objectPath, interface, QStringLiteral("Open" )); |
| 63 | message << QUrl::toStringList(uris: m_urls); |
| 64 | } |
| 65 | if (KWindowSystem::isPlatformX11()) { |
| 66 | #if HAVE_X11 |
| 67 | message << QVariantMap{{QStringLiteral("desktop-startup-id" ), m_startupId.id()}}; |
| 68 | #endif |
| 69 | } else if (KWindowSystem::isPlatformWayland()) { |
| 70 | message << QVariantMap{{QStringLiteral("activation-token" ), m_process->processEnvironment().value(QStringLiteral("XDG_ACTIVATION_TOKEN" ))}}; |
| 71 | } |
| 72 | auto call = QDBusConnection::sessionBus().asyncCall(message); |
| 73 | auto activationWatcher = new QDBusPendingCallWatcher(call, this); |
| 74 | connect(sender: activationWatcher, signal: &QDBusPendingCallWatcher::finished, context: this, slot: [this](QDBusPendingCallWatcher *watcher) { |
| 75 | watcher->deleteLater(); |
| 76 | if (watcher->isError()) { |
| 77 | Q_EMIT error(errorString: watcher->error().message()); |
| 78 | terminateStartupNotification(); |
| 79 | m_finished = true; |
| 80 | deleteLater(); |
| 81 | return; |
| 82 | } |
| 83 | auto call = QDBusConnection::sessionBus().interface()->asyncCall(QStringLiteral("GetConnectionUnixProcessID" ), args&: m_desktopName); |
| 84 | auto pidWatcher = new QDBusPendingCallWatcher(call, this); |
| 85 | connect(sender: pidWatcher, signal: &QDBusPendingCallWatcher::finished, context: this, slot: [this](QDBusPendingCallWatcher *watcher) { |
| 86 | m_finished = true; |
| 87 | QDBusPendingReply<uint> reply = *watcher; |
| 88 | if (reply.isError()) { |
| 89 | Q_EMIT error(errorString: watcher->error().message()); |
| 90 | terminateStartupNotification(); |
| 91 | } else { |
| 92 | Q_EMIT processStarted(pid: reply.value()); |
| 93 | } |
| 94 | deleteLater(); |
| 95 | }); |
| 96 | }); |
| 97 | } |
| 98 | |
| 99 | bool DBusActivationRunner::waitForStarted(int timeout) |
| 100 | { |
| 101 | if (m_finished) { |
| 102 | return m_pid != 0; |
| 103 | } |
| 104 | |
| 105 | QEventLoop loop; |
| 106 | bool success = false; |
| 107 | connect(sender: this, signal: &KProcessRunner::processStarted, slot: [&loop, &success]() { |
| 108 | loop.quit(); |
| 109 | success = true; |
| 110 | }); |
| 111 | connect(sender: this, signal: &KProcessRunner::error, context: &loop, slot: &QEventLoop::quit); |
| 112 | QTimer::singleShot(interval: timeout, receiver: &loop, slot: &QEventLoop::quit); |
| 113 | loop.exec(); |
| 114 | return success; |
| 115 | } |
| 116 | |
| 117 | #include "moc_dbusactivationrunner_p.cpp" |
| 118 | |