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 | |