1 | /* This file is part of the KDE libraries |
2 | * SPDX-FileCopyrightText: 2009 Dario Freddi <drf at kde.org> |
3 | * |
4 | * SPDX-License-Identifier: LGPL-2.1-or-later |
5 | */ |
6 | |
7 | #include "kidletime.h" |
8 | |
9 | #include <config-kidletime.h> |
10 | |
11 | #include "kabstractidletimepoller_p.h" |
12 | #include "logging.h" |
13 | |
14 | #include <QDir> |
15 | #include <QGuiApplication> |
16 | #include <QJsonArray> |
17 | #include <QPluginLoader> |
18 | #include <QPointer> |
19 | #include <QSet> |
20 | |
21 | class KIdleTimeHelper |
22 | { |
23 | public: |
24 | KIdleTimeHelper() |
25 | : q(nullptr) |
26 | { |
27 | } |
28 | ~KIdleTimeHelper() |
29 | { |
30 | delete q; |
31 | } |
32 | KIdleTimeHelper(const KIdleTimeHelper &) = delete; |
33 | KIdleTimeHelper &operator=(const KIdleTimeHelper &) = delete; |
34 | KIdleTime *q; |
35 | }; |
36 | |
37 | Q_GLOBAL_STATIC(KIdleTimeHelper, s_globalKIdleTime) |
38 | |
39 | KIdleTime *KIdleTime::instance() |
40 | { |
41 | if (!s_globalKIdleTime()->q) { |
42 | new KIdleTime; |
43 | } |
44 | |
45 | return s_globalKIdleTime()->q; |
46 | } |
47 | |
48 | class KIdleTimePrivate |
49 | { |
50 | Q_DECLARE_PUBLIC(KIdleTime) |
51 | KIdleTime *q_ptr; |
52 | |
53 | public: |
54 | KIdleTimePrivate() |
55 | : catchResume(false) |
56 | , currentId(0) |
57 | { |
58 | } |
59 | |
60 | void loadSystem(); |
61 | void unloadCurrentSystem(); |
62 | void resumingFromIdle(); |
63 | void timeoutReached(int msec); |
64 | |
65 | QPointer<KAbstractIdleTimePoller> poller; |
66 | bool catchResume; |
67 | |
68 | int currentId; |
69 | QHash<int, int> associations; |
70 | }; |
71 | |
72 | KIdleTime::KIdleTime() |
73 | : QObject(nullptr) |
74 | , d_ptr(new KIdleTimePrivate()) |
75 | { |
76 | Q_ASSERT(!s_globalKIdleTime()->q); |
77 | s_globalKIdleTime()->q = this; |
78 | |
79 | d_ptr->q_ptr = this; |
80 | |
81 | Q_D(KIdleTime); |
82 | d->loadSystem(); |
83 | |
84 | connect(sender: d->poller.data(), signal: &KAbstractIdleTimePoller::resumingFromIdle, context: this, slot: [d]() { |
85 | d->resumingFromIdle(); |
86 | }); |
87 | connect(sender: d->poller.data(), signal: &KAbstractIdleTimePoller::timeoutReached, context: this, slot: [d](int msec) { |
88 | d->timeoutReached(msec); |
89 | }); |
90 | } |
91 | |
92 | KIdleTime::~KIdleTime() |
93 | { |
94 | Q_D(KIdleTime); |
95 | d->unloadCurrentSystem(); |
96 | } |
97 | |
98 | void KIdleTime::catchNextResumeEvent() |
99 | { |
100 | Q_D(KIdleTime); |
101 | |
102 | if (!d->catchResume && d->poller) { |
103 | d->catchResume = true; |
104 | d->poller.data()->catchIdleEvent(); |
105 | } |
106 | } |
107 | |
108 | void KIdleTime::stopCatchingResumeEvent() |
109 | { |
110 | Q_D(KIdleTime); |
111 | |
112 | if (d->catchResume && d->poller) { |
113 | d->catchResume = false; |
114 | d->poller.data()->stopCatchingIdleEvents(); |
115 | } |
116 | } |
117 | |
118 | int KIdleTime::addIdleTimeout(int msec) |
119 | { |
120 | Q_D(KIdleTime); |
121 | if (Q_UNLIKELY(msec < 0)) { |
122 | qCWarning(KIDLETIME, "KIdleTime::addIdleTimeout: invalid timeout: %d" , msec); |
123 | return 0; |
124 | } |
125 | if (Q_UNLIKELY(!d->poller)) { |
126 | return 0; |
127 | } |
128 | |
129 | d->poller.data()->addTimeout(nextTimeout: msec); |
130 | |
131 | ++d->currentId; |
132 | d->associations[d->currentId] = msec; |
133 | |
134 | return d->currentId; |
135 | } |
136 | |
137 | void KIdleTime::removeIdleTimeout(int identifier) |
138 | { |
139 | Q_D(KIdleTime); |
140 | |
141 | const auto it = d->associations.constFind(key: identifier); |
142 | if (it == d->associations.cend() || !d->poller) { |
143 | return; |
144 | } |
145 | |
146 | const int msec = it.value(); |
147 | |
148 | d->associations.erase(it); |
149 | |
150 | const bool isFound = std::any_of(first: d->associations.cbegin(), last: d->associations.cend(), pred: [msec](int i) { |
151 | return i == msec; |
152 | }); |
153 | |
154 | if (!isFound) { |
155 | d->poller.data()->removeTimeout(nextTimeout: msec); |
156 | } |
157 | } |
158 | |
159 | void KIdleTime::removeAllIdleTimeouts() |
160 | { |
161 | Q_D(KIdleTime); |
162 | |
163 | std::vector<int> removed; |
164 | |
165 | for (auto it = d->associations.cbegin(); it != d->associations.cend(); ++it) { |
166 | const int msec = it.value(); |
167 | const bool alreadyIns = std::find(first: removed.cbegin(), last: removed.cend(), val: msec) != removed.cend(); |
168 | if (!alreadyIns && d->poller) { |
169 | removed.push_back(x: msec); |
170 | d->poller.data()->removeTimeout(nextTimeout: msec); |
171 | } |
172 | } |
173 | |
174 | d->associations.clear(); |
175 | } |
176 | |
177 | static QStringList pluginCandidates() |
178 | { |
179 | QStringList ret; |
180 | |
181 | const QStringList libPath = QCoreApplication::libraryPaths(); |
182 | for (const QString &path : libPath) { |
183 | #ifdef Q_OS_MACOS |
184 | const QDir pluginDir(path + QStringLiteral("/kf6/kidletime" )); |
185 | #else |
186 | const QDir pluginDir(path + QStringLiteral("/kf6/org.kde.kidletime.platforms" )); |
187 | #endif |
188 | if (!pluginDir.exists()) { |
189 | continue; |
190 | } |
191 | |
192 | const auto entries = pluginDir.entryList(filters: QDir::Files | QDir::NoDotAndDotDot); |
193 | |
194 | ret.reserve(asize: ret.size() + entries.size()); |
195 | for (const QString &entry : entries) { |
196 | ret << pluginDir.absoluteFilePath(fileName: entry); |
197 | } |
198 | } |
199 | |
200 | return ret; |
201 | } |
202 | |
203 | static bool checkPlatform(const QJsonObject &metadata, const QString &platformName) |
204 | { |
205 | const QJsonArray platforms = metadata.value(QStringLiteral("MetaData" )).toObject().value(QStringLiteral("platforms" )).toArray(); |
206 | return std::any_of(first: platforms.begin(), last: platforms.end(), pred: [&platformName](const QJsonValue &value) { |
207 | return QString::compare(s1: platformName, s2: value.toString(), cs: Qt::CaseInsensitive) == 0; |
208 | }); |
209 | } |
210 | |
211 | static KAbstractIdleTimePoller *loadPoller() |
212 | { |
213 | const QString platformName = QGuiApplication::platformName(); |
214 | |
215 | const QList<QStaticPlugin> staticPlugins = QPluginLoader::staticPlugins(); |
216 | for (const QStaticPlugin &staticPlugin : staticPlugins) { |
217 | const QJsonObject metadata = staticPlugin.metaData(); |
218 | if (metadata.value(key: QLatin1String("IID" )) != QLatin1String(KAbstractIdleTimePoller_iid)) { |
219 | continue; |
220 | } |
221 | if (checkPlatform(metadata, platformName)) { |
222 | auto *poller = qobject_cast<KAbstractIdleTimePoller *>(object: staticPlugin.instance()); |
223 | if (poller) { |
224 | if (poller->isAvailable()) { |
225 | qCDebug(KIDLETIME) << "Loaded system poller from a static plugin" ; |
226 | return poller; |
227 | } |
228 | delete poller; |
229 | } |
230 | } |
231 | } |
232 | |
233 | const QStringList lstPlugins = pluginCandidates(); |
234 | for (const QString &candidate : lstPlugins) { |
235 | if (!QLibrary::isLibrary(fileName: candidate)) { |
236 | continue; |
237 | } |
238 | QPluginLoader loader(candidate); |
239 | if (checkPlatform(metadata: loader.metaData(), platformName)) { |
240 | auto *poller = qobject_cast<KAbstractIdleTimePoller *>(object: loader.instance()); |
241 | if (poller) { |
242 | qCDebug(KIDLETIME) << "Trying plugin" << candidate; |
243 | if (poller->isAvailable()) { |
244 | qCDebug(KIDLETIME) << "Using" << candidate << "for platform" << platformName; |
245 | return poller; |
246 | } |
247 | delete poller; |
248 | } |
249 | } |
250 | } |
251 | |
252 | qCWarning(KIDLETIME) << "Could not find any system poller plugin" ; |
253 | return nullptr; |
254 | } |
255 | |
256 | void KIdleTimePrivate::loadSystem() |
257 | { |
258 | if (!poller.isNull()) { |
259 | unloadCurrentSystem(); |
260 | } |
261 | |
262 | // load plugin |
263 | poller = loadPoller(); |
264 | |
265 | if (poller && !poller->isAvailable()) { |
266 | poller = nullptr; |
267 | } |
268 | if (!poller.isNull()) { |
269 | poller.data()->setUpPoller(); |
270 | } |
271 | } |
272 | |
273 | void KIdleTimePrivate::unloadCurrentSystem() |
274 | { |
275 | if (!poller.isNull()) { |
276 | poller.data()->unloadPoller(); |
277 | poller.data()->deleteLater(); |
278 | } |
279 | } |
280 | |
281 | void KIdleTimePrivate::resumingFromIdle() |
282 | { |
283 | Q_Q(KIdleTime); |
284 | |
285 | if (catchResume) { |
286 | Q_EMIT q->resumingFromIdle(); |
287 | q->stopCatchingResumeEvent(); |
288 | } |
289 | } |
290 | |
291 | void KIdleTimePrivate::timeoutReached(int msec) |
292 | { |
293 | Q_Q(KIdleTime); |
294 | |
295 | const auto listKeys = associations.keys(value: msec); |
296 | |
297 | for (const auto key : listKeys) { |
298 | Q_EMIT q->timeoutReached(identifier: key, msec); |
299 | } |
300 | } |
301 | |
302 | void KIdleTime::simulateUserActivity() |
303 | { |
304 | Q_D(KIdleTime); |
305 | |
306 | if (Q_LIKELY(d->poller)) { |
307 | d->poller.data()->simulateUserActivity(); |
308 | } |
309 | } |
310 | |
311 | int KIdleTime::idleTime() const |
312 | { |
313 | Q_D(const KIdleTime); |
314 | if (Q_LIKELY(d->poller)) { |
315 | return d->poller.data()->forcePollRequest(); |
316 | } |
317 | return 0; |
318 | } |
319 | |
320 | QHash<int, int> KIdleTime::idleTimeouts() const |
321 | { |
322 | Q_D(const KIdleTime); |
323 | |
324 | return d->associations; |
325 | } |
326 | |
327 | #include "moc_kidletime.cpp" |
328 | |