File indexing completed on 2024-12-01 03:40:14

0001 /* This file is part of the KDE libraries
0002  * SPDX-FileCopyrightText: 2009 Dario Freddi <drf at kde.org>
0003  *
0004  * SPDX-License-Identifier: LGPL-2.1-or-later
0005  */
0006 
0007 #include "kidletime.h"
0008 
0009 #include <config-kidletime.h>
0010 
0011 #include "kabstractidletimepoller_p.h"
0012 #include "logging.h"
0013 
0014 #include <QDir>
0015 #include <QGuiApplication>
0016 #include <QJsonArray>
0017 #include <QPluginLoader>
0018 #include <QPointer>
0019 #include <QSet>
0020 
0021 class KIdleTimeHelper
0022 {
0023 public:
0024     KIdleTimeHelper()
0025         : q(nullptr)
0026     {
0027     }
0028     ~KIdleTimeHelper()
0029     {
0030         delete q;
0031     }
0032     KIdleTimeHelper(const KIdleTimeHelper &) = delete;
0033     KIdleTimeHelper &operator=(const KIdleTimeHelper &) = delete;
0034     KIdleTime *q;
0035 };
0036 
0037 Q_GLOBAL_STATIC(KIdleTimeHelper, s_globalKIdleTime)
0038 
0039 KIdleTime *KIdleTime::instance()
0040 {
0041     if (!s_globalKIdleTime()->q) {
0042         new KIdleTime;
0043     }
0044 
0045     return s_globalKIdleTime()->q;
0046 }
0047 
0048 class KIdleTimePrivate
0049 {
0050     Q_DECLARE_PUBLIC(KIdleTime)
0051     KIdleTime *q_ptr;
0052 
0053 public:
0054     KIdleTimePrivate()
0055         : catchResume(false)
0056         , currentId(0)
0057     {
0058     }
0059 
0060     void loadSystem();
0061     void unloadCurrentSystem();
0062     void resumingFromIdle();
0063     void timeoutReached(int msec);
0064 
0065     QPointer<KAbstractIdleTimePoller> poller;
0066     bool catchResume;
0067 
0068     int currentId;
0069     QHash<int, int> associations;
0070 };
0071 
0072 KIdleTime::KIdleTime()
0073     : QObject(nullptr)
0074     , d_ptr(new KIdleTimePrivate())
0075 {
0076     Q_ASSERT(!s_globalKIdleTime()->q);
0077     s_globalKIdleTime()->q = this;
0078 
0079     d_ptr->q_ptr = this;
0080 
0081     Q_D(KIdleTime);
0082     d->loadSystem();
0083 
0084     connect(d->poller.data(), &KAbstractIdleTimePoller::resumingFromIdle, this, [d]() {
0085         d->resumingFromIdle();
0086     });
0087     connect(d->poller.data(), &KAbstractIdleTimePoller::timeoutReached, this, [d](int msec) {
0088         d->timeoutReached(msec);
0089     });
0090 }
0091 
0092 KIdleTime::~KIdleTime()
0093 {
0094     Q_D(KIdleTime);
0095     d->unloadCurrentSystem();
0096 }
0097 
0098 void KIdleTime::catchNextResumeEvent()
0099 {
0100     Q_D(KIdleTime);
0101 
0102     if (!d->catchResume && d->poller) {
0103         d->catchResume = true;
0104         d->poller.data()->catchIdleEvent();
0105     }
0106 }
0107 
0108 void KIdleTime::stopCatchingResumeEvent()
0109 {
0110     Q_D(KIdleTime);
0111 
0112     if (d->catchResume && d->poller) {
0113         d->catchResume = false;
0114         d->poller.data()->stopCatchingIdleEvents();
0115     }
0116 }
0117 
0118 int KIdleTime::addIdleTimeout(int msec)
0119 {
0120     Q_D(KIdleTime);
0121     if (Q_UNLIKELY(!d->poller)) {
0122         return 0;
0123     }
0124 
0125     d->poller.data()->addTimeout(msec);
0126 
0127     ++d->currentId;
0128     d->associations[d->currentId] = msec;
0129 
0130     return d->currentId;
0131 }
0132 
0133 void KIdleTime::removeIdleTimeout(int identifier)
0134 {
0135     Q_D(KIdleTime);
0136 
0137     const auto it = d->associations.constFind(identifier);
0138     if (it == d->associations.cend() || !d->poller) {
0139         return;
0140     }
0141 
0142     const int msec = it.value();
0143 
0144     d->associations.erase(it);
0145 
0146     const bool isFound = std::any_of(d->associations.cbegin(), d->associations.cend(), [msec](int i) {
0147         return i == msec;
0148     });
0149 
0150     if (!isFound) {
0151         d->poller.data()->removeTimeout(msec);
0152     }
0153 }
0154 
0155 void KIdleTime::removeAllIdleTimeouts()
0156 {
0157     Q_D(KIdleTime);
0158 
0159     std::vector<int> removed;
0160 
0161     for (auto it = d->associations.cbegin(); it != d->associations.cend(); ++it) {
0162         const int msec = it.value();
0163         const bool alreadyIns = std::find(removed.cbegin(), removed.cend(), msec) != removed.cend();
0164         if (!alreadyIns && d->poller) {
0165             removed.push_back(msec);
0166             d->poller.data()->removeTimeout(msec);
0167         }
0168     }
0169 
0170     d->associations.clear();
0171 }
0172 
0173 static QStringList pluginCandidates()
0174 {
0175     QStringList ret;
0176 
0177     const QStringList libPath = QCoreApplication::libraryPaths();
0178     for (const QString &path : libPath) {
0179 #ifdef Q_OS_MACOS
0180         const QDir pluginDir(path + QStringLiteral("/kf6/kidletime"));
0181 #else
0182         const QDir pluginDir(path + QStringLiteral("/kf6/org.kde.kidletime.platforms"));
0183 #endif
0184         if (!pluginDir.exists()) {
0185             continue;
0186         }
0187 
0188         const auto entries = pluginDir.entryList(QDir::Files | QDir::NoDotAndDotDot);
0189 
0190         ret.reserve(ret.size() + entries.size());
0191         for (const QString &entry : entries) {
0192             ret << pluginDir.absoluteFilePath(entry);
0193         }
0194     }
0195 
0196     return ret;
0197 }
0198 
0199 static bool checkPlatform(const QJsonObject &metadata, const QString &platformName)
0200 {
0201     const QJsonArray platforms = metadata.value(QStringLiteral("MetaData")).toObject().value(QStringLiteral("platforms")).toArray();
0202     return std::any_of(platforms.begin(), platforms.end(), [&platformName](const QJsonValue &value) {
0203         return QString::compare(platformName, value.toString(), Qt::CaseInsensitive) == 0;
0204     });
0205 }
0206 
0207 static KAbstractIdleTimePoller *loadPoller()
0208 {
0209     const QString platformName = QGuiApplication::platformName();
0210 
0211     const QList<QStaticPlugin> staticPlugins = QPluginLoader::staticPlugins();
0212     for (const QStaticPlugin &staticPlugin : staticPlugins) {
0213         const QJsonObject metadata = staticPlugin.metaData();
0214         if (metadata.value(QLatin1String("IID")) != QLatin1String(KAbstractIdleTimePoller_iid)) {
0215             continue;
0216         }
0217         if (checkPlatform(metadata, platformName)) {
0218             auto *poller = qobject_cast<KAbstractIdleTimePoller *>(staticPlugin.instance());
0219             if (poller) {
0220                 if (poller->isAvailable()) {
0221                     qCDebug(KIDLETIME) << "Loaded system poller from a static plugin";
0222                     return poller;
0223                 }
0224                 delete poller;
0225             }
0226         }
0227     }
0228 
0229     const QStringList lstPlugins = pluginCandidates();
0230     for (const QString &candidate : lstPlugins) {
0231         if (!QLibrary::isLibrary(candidate)) {
0232             continue;
0233         }
0234         QPluginLoader loader(candidate);
0235         if (checkPlatform(loader.metaData(), platformName)) {
0236             auto *poller = qobject_cast<KAbstractIdleTimePoller *>(loader.instance());
0237             if (poller) {
0238                 qCDebug(KIDLETIME) << "Trying plugin" << candidate;
0239                 if (poller->isAvailable()) {
0240                     qCDebug(KIDLETIME) << "Using" << candidate << "for platform" << platformName;
0241                     return poller;
0242                 }
0243                 delete poller;
0244             }
0245         }
0246     }
0247 
0248     qCWarning(KIDLETIME) << "Could not find any system poller plugin";
0249     return nullptr;
0250 }
0251 
0252 void KIdleTimePrivate::loadSystem()
0253 {
0254     if (!poller.isNull()) {
0255         unloadCurrentSystem();
0256     }
0257 
0258     // load plugin
0259     poller = loadPoller();
0260 
0261     if (poller && !poller->isAvailable()) {
0262         poller = nullptr;
0263     }
0264     if (!poller.isNull()) {
0265         poller.data()->setUpPoller();
0266     }
0267 }
0268 
0269 void KIdleTimePrivate::unloadCurrentSystem()
0270 {
0271     if (!poller.isNull()) {
0272         poller.data()->unloadPoller();
0273         poller.data()->deleteLater();
0274     }
0275 }
0276 
0277 void KIdleTimePrivate::resumingFromIdle()
0278 {
0279     Q_Q(KIdleTime);
0280 
0281     if (catchResume) {
0282         Q_EMIT q->resumingFromIdle();
0283         q->stopCatchingResumeEvent();
0284     }
0285 }
0286 
0287 void KIdleTimePrivate::timeoutReached(int msec)
0288 {
0289     Q_Q(KIdleTime);
0290 
0291     const auto listKeys = associations.keys(msec);
0292 
0293     for (const auto key : listKeys) {
0294         Q_EMIT q->timeoutReached(key, msec);
0295     }
0296 }
0297 
0298 void KIdleTime::simulateUserActivity()
0299 {
0300     Q_D(KIdleTime);
0301 
0302     if (Q_LIKELY(d->poller)) {
0303         d->poller.data()->simulateUserActivity();
0304     }
0305 }
0306 
0307 int KIdleTime::idleTime() const
0308 {
0309     Q_D(const KIdleTime);
0310     if (Q_LIKELY(d->poller)) {
0311         return d->poller.data()->forcePollRequest();
0312     }
0313     return 0;
0314 }
0315 
0316 QHash<int, int> KIdleTime::idleTimeouts() const
0317 {
0318     Q_D(const KIdleTime);
0319 
0320     return d->associations;
0321 }
0322 
0323 #include "moc_kidletime.cpp"