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