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
21class KIdleTimeHelper
22{
23public:
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
37Q_GLOBAL_STATIC(KIdleTimeHelper, s_globalKIdleTime)
38
39KIdleTime *KIdleTime::instance()
40{
41 if (!s_globalKIdleTime()->q) {
42 new KIdleTime;
43 }
44
45 return s_globalKIdleTime()->q;
46}
47
48class KIdleTimePrivate
49{
50 Q_DECLARE_PUBLIC(KIdleTime)
51 KIdleTime *q_ptr;
52
53public:
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
72KIdleTime::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
92KIdleTime::~KIdleTime()
93{
94 Q_D(KIdleTime);
95 d->unloadCurrentSystem();
96}
97
98void 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
108void 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
118int 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
137void 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
159void 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
177static 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
203static 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
211static 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
256void 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
273void KIdleTimePrivate::unloadCurrentSystem()
274{
275 if (!poller.isNull()) {
276 poller.data()->unloadPoller();
277 poller.data()->deleteLater();
278 }
279}
280
281void KIdleTimePrivate::resumingFromIdle()
282{
283 Q_Q(KIdleTime);
284
285 if (catchResume) {
286 Q_EMIT q->resumingFromIdle();
287 q->stopCatchingResumeEvent();
288 }
289}
290
291void 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
302void KIdleTime::simulateUserActivity()
303{
304 Q_D(KIdleTime);
305
306 if (Q_LIKELY(d->poller)) {
307 d->poller.data()->simulateUserActivity();
308 }
309}
310
311int 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
320QHash<int, int> KIdleTime::idleTimeouts() const
321{
322 Q_D(const KIdleTime);
323
324 return d->associations;
325}
326
327#include "moc_kidletime.cpp"
328

source code of kidletime/src/kidletime.cpp