File indexing completed on 2024-04-28 15:26:51

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2020-2021 David Redondo <kde@david-redondo.de>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0006 */
0007 
0008 #include "dbusactivationrunner_p.h"
0009 
0010 #include "kiogui_debug.h"
0011 #include <KWindowSystem>
0012 
0013 #ifndef Q_OS_ANDROID
0014 #include <QDBusConnection>
0015 #include <QDBusConnectionInterface>
0016 #include <QDBusMessage>
0017 #include <QDBusPendingCallWatcher>
0018 #endif
0019 #include <QTimer>
0020 
0021 bool DBusActivationRunner::activationPossible(const KService::Ptr service, KIO::ApplicationLauncherJob::RunFlags flags, const QString &suggestedFileName)
0022 {
0023 #if defined Q_OS_UNIX && !defined Q_OS_ANDROID
0024     if (!service->isApplication()) {
0025         return false;
0026     }
0027     if (service->property(QStringLiteral("DBusActivatable"), QMetaType::Bool).toBool()) {
0028         if (service->menuId().count(QLatin1Char('.')) < 3) {
0029             qCWarning(KIO_GUI) << "Cannot activate" << service->menuId() << "doesn't have enough '.' for a well-formed service name";
0030             return false;
0031         }
0032         if (!suggestedFileName.isEmpty()) {
0033             qCDebug(KIO_GUI) << "Cannot activate" << service->desktopEntryName() << "because suggestedFileName is set";
0034             return false;
0035         }
0036         if (flags & KIO::ApplicationLauncherJob::DeleteTemporaryFiles) {
0037             qCDebug(KIO_GUI) << "Cannot activate" << service->desktopEntryName() << "because DeleteTemporaryFiles is set";
0038             return false;
0039         }
0040         return true;
0041     }
0042 #endif
0043     return false;
0044 }
0045 
0046 DBusActivationRunner::DBusActivationRunner(const QString &action)
0047     : KProcessRunner()
0048     , m_actionName(action)
0049 {
0050 }
0051 
0052 void DBusActivationRunner::startProcess()
0053 {
0054 #ifndef Q_OS_ANDROID
0055     // DBusActivatable as per https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#dbus
0056     const QString objectPath = QStringLiteral("/%1").arg(m_desktopName).replace(QLatin1Char('.'), QLatin1Char('/')).replace(QLatin1Char('-'), QLatin1Char('_'));
0057     const QString interface = QStringLiteral("org.freedesktop.Application");
0058     QDBusMessage message;
0059     if (m_urls.isEmpty()) {
0060         if (m_actionName.isEmpty()) {
0061             message = QDBusMessage::createMethodCall(m_desktopName, objectPath, interface, QStringLiteral("Activate"));
0062         } else {
0063             message = QDBusMessage::createMethodCall(m_desktopName, objectPath, interface, QStringLiteral("ActivateAction"));
0064             message << m_actionName << QVariantList();
0065         }
0066     } else {
0067         message = QDBusMessage::createMethodCall(m_desktopName, objectPath, interface, QStringLiteral("Open"));
0068         message << QUrl::toStringList(m_urls);
0069     }
0070     if (KWindowSystem::isPlatformX11()) {
0071         message << QVariantMap{{QStringLiteral("desktop-startup-id"), m_startupId.id()}};
0072     } else if (KWindowSystem::isPlatformWayland()) {
0073         message << QVariantMap{{QStringLiteral("activation-token"), m_process->processEnvironment().value(QStringLiteral("XDG_ACTIVATION_TOKEN"))}};
0074     }
0075     auto call = QDBusConnection::sessionBus().asyncCall(message);
0076     auto activationWatcher = new QDBusPendingCallWatcher(call, this);
0077     connect(activationWatcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) {
0078         watcher->deleteLater();
0079         if (watcher->isError()) {
0080             Q_EMIT error(watcher->error().message());
0081             terminateStartupNotification();
0082             m_finished = true;
0083             deleteLater();
0084             return;
0085         }
0086         auto call = QDBusConnection::sessionBus().interface()->asyncCall(QStringLiteral("GetConnectionUnixProcessID"), m_desktopName);
0087         auto pidWatcher = new QDBusPendingCallWatcher(call, this);
0088         connect(pidWatcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) {
0089             m_finished = true;
0090             QDBusPendingReply<uint> reply = *watcher;
0091             if (reply.isError()) {
0092                 Q_EMIT error(watcher->error().message());
0093                 terminateStartupNotification();
0094             } else {
0095                 Q_EMIT processStarted(reply.value());
0096             }
0097             deleteLater();
0098         });
0099     });
0100 #endif
0101 }
0102 
0103 bool DBusActivationRunner::waitForStarted(int timeout)
0104 {
0105 #ifndef Q_OS_ANDROID
0106     if (m_finished) {
0107         return m_pid != 0;
0108     }
0109 
0110     QEventLoop loop;
0111     bool success = false;
0112     connect(this, &KProcessRunner::processStarted, [&loop, &success]() {
0113         loop.quit();
0114         success = true;
0115     });
0116     connect(this, &KProcessRunner::error, &loop, &QEventLoop::quit);
0117     QTimer::singleShot(timeout, &loop, &QEventLoop::quit);
0118     loop.exec();
0119     return success;
0120 #else
0121     return false;
0122 #endif
0123 }
0124 
0125 #include "moc_dbusactivationrunner_p.cpp"