1/*
2 SPDX-FileCopyrightText: 2008 Rob Scheepmaker <r.scheepmaker@student.utwente.nl>
3 SPDX-FileCopyrightText: 2010 Shaun Reich <shaun.reich@kdemail.net>
4 SPDX-FileCopyrightText: 2021 Kai Uwe Broulik <kde@broulik.de>
5
6 SPDX-License-Identifier: LGPL-2.1-or-later
7*/
8
9#include "kdynamicjobtracker_p.h"
10#include "kio_widgets_debug.h"
11#include "kuiserver_interface.h"
12
13#include <KInhibitionJobTracker>
14#include <KJobTrackerInterface>
15#include <KUiServerV2JobTracker>
16#include <KWidgetJobTracker>
17#include <kio/jobtracker.h>
18
19#include <QApplication>
20#include <QDBusConnection>
21#include <QDBusConnectionInterface>
22#include <QDBusServiceWatcher>
23#include <QMap>
24#include <QXmlStreamReader>
25
26struct AllTrackers {
27 KUiServerV2JobTracker *kuiserverV2Tracker;
28 KWidgetJobTracker *widgetTracker;
29 KInhibitionJobTracker *inhibitionTracker;
30};
31
32class KDynamicJobTrackerPrivate
33{
34public:
35 KDynamicJobTrackerPrivate()
36 {
37 }
38
39 ~KDynamicJobTrackerPrivate()
40 {
41 delete kuiserverV2Tracker;
42 delete widgetTracker;
43 delete inhibitionTracker;
44 }
45
46 static bool hasDBusInterface(const QString &introspectionData, const QString &interface)
47 {
48 QXmlStreamReader xml(introspectionData);
49 while (!xml.atEnd() && !xml.hasError()) {
50 xml.readNext();
51
52 if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == QLatin1String("interface")) {
53 if (xml.attributes().value(qualifiedName: QLatin1String("name")) == interface) {
54 return true;
55 }
56 }
57 }
58 return false;
59 }
60
61 KUiServerV2JobTracker *kuiserverV2Tracker = nullptr;
62 KWidgetJobTracker *widgetTracker = nullptr;
63 KInhibitionJobTracker *inhibitionTracker = nullptr;
64 QMap<KJob *, AllTrackers> trackers;
65
66 enum JobViewServerSupport {
67 NeedsChecking,
68 Error,
69 V2Supported,
70 V2NotSupported,
71 };
72 JobViewServerSupport jobViewServerSupport = NeedsChecking;
73 QDBusServiceWatcher *jobViewServerWatcher = nullptr;
74};
75
76KDynamicJobTracker::KDynamicJobTracker(QObject *parent)
77 : KJobTrackerInterface(parent)
78 , d(new KDynamicJobTrackerPrivate)
79{
80}
81
82KDynamicJobTracker::~KDynamicJobTracker() = default;
83
84void KDynamicJobTracker::registerJob(KJob *job)
85{
86 if (d->trackers.contains(key: job)) {
87 return;
88 }
89
90 // only interested in finished() signal,
91 // so catching ourselves instead of using KJobTrackerInterface::registerJob()
92 connect(sender: job, signal: &KJob::finished, context: this, slot: &KDynamicJobTracker::unregisterJob);
93
94 const bool canHaveWidgets = (qobject_cast<QApplication *>(qApp) != nullptr);
95
96 // always add an entry, even with no trackers used at all,
97 // so unregisterJob() will work as normal
98 AllTrackers &trackers = d->trackers[job];
99 trackers.kuiserverV2Tracker = nullptr;
100 trackers.widgetTracker = nullptr;
101 trackers.inhibitionTracker = nullptr;
102
103 auto useWidgetsFallback = [this, canHaveWidgets, &trackers, job] {
104 if (canHaveWidgets) {
105 // fallback to widget tracker only!
106 if (!d->widgetTracker) {
107 d->widgetTracker = new KWidgetJobTracker();
108 }
109
110 trackers.widgetTracker = d->widgetTracker;
111 trackers.widgetTracker->registerJob(job);
112 }
113 };
114
115 // do not try to use kuiserver on Windows/macOS
116#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
117 useWidgetsFallback();
118 return;
119#endif
120
121 // do not try to query kuiserver if dbus is not available
122 if (!QDBusConnection::sessionBus().interface()) {
123 useWidgetsFallback();
124 return;
125 }
126
127 if (!job->property(name: "kio_no_inhibit_suspend").toBool()) {
128 if (!d->inhibitionTracker) {
129 d->inhibitionTracker = new KInhibitionJobTracker();
130 }
131
132 trackers.inhibitionTracker = d->inhibitionTracker;
133 trackers.inhibitionTracker->registerJob(job);
134 }
135
136 const QString kuiserverService = QStringLiteral("org.kde.kuiserver");
137
138 if (!d->jobViewServerWatcher) {
139 d->jobViewServerWatcher = new QDBusServiceWatcher(kuiserverService,
140 QDBusConnection::sessionBus(),
141 QDBusServiceWatcher::WatchForOwnerChange | QDBusServiceWatcher::WatchForUnregistration,
142 this);
143 connect(sender: d->jobViewServerWatcher, signal: &QDBusServiceWatcher::serviceOwnerChanged, context: this, slot: [this] {
144 d->jobViewServerSupport = KDynamicJobTrackerPrivate::NeedsChecking;
145 });
146 }
147
148 if (d->jobViewServerSupport == KDynamicJobTrackerPrivate::NeedsChecking) {
149 // Unfortunately no DBus ObjectManager support in Qt DBus.
150 QDBusMessage msg = QDBusMessage::createMethodCall(destination: kuiserverService,
151 QStringLiteral("/JobViewServer"),
152 QStringLiteral("org.freedesktop.DBus.Introspectable"),
153 QStringLiteral("Introspect"));
154 auto reply = QDBusConnection::sessionBus().call(message: msg);
155 if (reply.type() == QDBusMessage::ErrorMessage || reply.arguments().count() != 1) {
156 qCWarning(KIO_WIDGETS) << "Failed to check which JobView API is supported" << reply.errorMessage();
157 d->jobViewServerSupport = KDynamicJobTrackerPrivate::Error;
158 } else {
159 const QString introspectionData = reply.arguments().first().toString();
160
161 if (KDynamicJobTrackerPrivate::hasDBusInterface(introspectionData, QStringLiteral("org.kde.JobViewServerV2"))) {
162 d->jobViewServerSupport = KDynamicJobTrackerPrivate::V2Supported;
163 } else {
164 d->jobViewServerSupport = KDynamicJobTrackerPrivate::V2NotSupported;
165 }
166 }
167
168 org::kde::kuiserver interface(kuiserverService, QStringLiteral("/JobViewServer"), QDBusConnection::sessionBus(), this);
169
170 QDBusReply<bool> requiresTrackerReply = interface.requiresJobTracker();
171 if (!requiresTrackerReply.isValid() || requiresTrackerReply.value()) {
172 d->jobViewServerSupport = KDynamicJobTrackerPrivate::Error;
173 }
174
175 QDBusConnection::sessionBus().connect(service: kuiserverService,
176 QStringLiteral("/JobViewServer"),
177 QStringLiteral("org.kde.kuiserver"),
178 QStringLiteral("requiresJobTrackerChanged"),
179 receiver: this,
180 SLOT(handleRequiresJobTrackerChanged(bool)));
181 }
182
183 if (d->jobViewServerSupport == KDynamicJobTrackerPrivate::V2Supported) {
184 if (!d->kuiserverV2Tracker) {
185 d->kuiserverV2Tracker = new KUiServerV2JobTracker();
186 }
187
188 trackers.kuiserverV2Tracker = d->kuiserverV2Tracker;
189 trackers.kuiserverV2Tracker->registerJob(job);
190 return;
191 }
192
193 // If kuiserver isn't available or it tells us a job tracker is required
194 // create a widget tracker.
195 if (d->jobViewServerSupport == KDynamicJobTrackerPrivate::Error || d->jobViewServerSupport == KDynamicJobTrackerPrivate::V2NotSupported) {
196 useWidgetsFallback();
197 }
198}
199
200void KDynamicJobTracker::unregisterJob(KJob *job)
201{
202 job->disconnect(receiver: this);
203
204 QMap<KJob *, AllTrackers>::Iterator it = d->trackers.find(key: job);
205
206 if (it == d->trackers.end()) {
207 qCWarning(KIO_WIDGETS) << "Tried to unregister a kio job that hasn't been registered.";
208 return;
209 }
210
211 const AllTrackers &trackers = it.value();
212 KUiServerV2JobTracker *kuiserverV2Tracker = trackers.kuiserverV2Tracker;
213 KWidgetJobTracker *widgetTracker = trackers.widgetTracker;
214 KInhibitionJobTracker *inhibitionTracker = trackers.inhibitionTracker;
215
216 if (kuiserverV2Tracker) {
217 kuiserverV2Tracker->unregisterJob(job);
218 }
219
220 if (widgetTracker) {
221 widgetTracker->unregisterJob(job);
222 }
223
224 if (inhibitionTracker) {
225 inhibitionTracker->unregisterJob(job);
226 }
227
228 d->trackers.erase(it);
229}
230
231void KDynamicJobTracker::handleRequiresJobTrackerChanged(bool req)
232{
233 if (req) {
234 d->jobViewServerSupport = KDynamicJobTrackerPrivate::Error;
235 } else {
236 d->jobViewServerSupport = KDynamicJobTrackerPrivate::V2Supported;
237 }
238}
239
240Q_GLOBAL_STATIC(KDynamicJobTracker, globalJobTracker)
241
242// Simply linking to this library, creates a GUI job tracker for all KIO jobs
243static int registerDynamicJobTracker()
244{
245 KIO::setJobTracker(globalJobTracker());
246
247 return 0; // something
248}
249
250Q_CONSTRUCTOR_FUNCTION(registerDynamicJobTracker)
251
252#include "moc_kdynamicjobtracker_p.cpp"
253

source code of kio/src/widgets/kdynamicjobtracker.cpp