File indexing completed on 2024-04-21 03:55:27

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<bool>(QStringLiteral("DBusActivatable"))) {
0028         if (service->desktopEntryName().count(QLatin1Char('.')) < 2) {
0029             qCWarning(KIO_GUI) << "Cannot activate" << service->desktopEntryName() << "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 #if HAVE_X11
0072         message << QVariantMap{{QStringLiteral("desktop-startup-id"), m_startupId.id()}};
0073 #endif
0074     } else if (KWindowSystem::isPlatformWayland()) {
0075         message << QVariantMap{{QStringLiteral("activation-token"), m_process->processEnvironment().value(QStringLiteral("XDG_ACTIVATION_TOKEN"))}};
0076     }
0077     auto call = QDBusConnection::sessionBus().asyncCall(message);
0078     auto activationWatcher = new QDBusPendingCallWatcher(call, this);
0079     connect(activationWatcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) {
0080         watcher->deleteLater();
0081         if (watcher->isError()) {
0082             Q_EMIT error(watcher->error().message());
0083             terminateStartupNotification();
0084             m_finished = true;
0085             deleteLater();
0086             return;
0087         }
0088         auto call = QDBusConnection::sessionBus().interface()->asyncCall(QStringLiteral("GetConnectionUnixProcessID"), m_desktopName);
0089         auto pidWatcher = new QDBusPendingCallWatcher(call, this);
0090         connect(pidWatcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) {
0091             m_finished = true;
0092             QDBusPendingReply<uint> reply = *watcher;
0093             if (reply.isError()) {
0094                 Q_EMIT error(watcher->error().message());
0095                 terminateStartupNotification();
0096             } else {
0097                 Q_EMIT processStarted(reply.value());
0098             }
0099             deleteLater();
0100         });
0101     });
0102 #endif
0103 }
0104 
0105 bool DBusActivationRunner::waitForStarted(int timeout)
0106 {
0107 #ifndef Q_OS_ANDROID
0108     if (m_finished) {
0109         return m_pid != 0;
0110     }
0111 
0112     QEventLoop loop;
0113     bool success = false;
0114     connect(this, &KProcessRunner::processStarted, [&loop, &success]() {
0115         loop.quit();
0116         success = true;
0117     });
0118     connect(this, &KProcessRunner::error, &loop, &QEventLoop::quit);
0119     QTimer::singleShot(timeout, &loop, &QEventLoop::quit);
0120     loop.exec();
0121     return success;
0122 #else
0123     return false;
0124 #endif
0125 }
0126 
0127 #include "moc_dbusactivationrunner_p.cpp"