File indexing completed on 2024-06-16 05:22:24

0001 /*
0002  * Copyright 2020 Han Young <hanyoung@protonmail.com>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "utilities.h"
0008 
0009 #include <QApplication>
0010 #include <QDBusReply>
0011 #include <QDBusServiceWatcher>
0012 #include <QDebug>
0013 #include <QThread>
0014 #include <QTimer>
0015 
0016 #include "generated/systeminterfaces/mprisplayer.h"
0017 
0018 // manually disable powerdevil, even if found
0019 static bool noPowerDevil = false;
0020 
0021 QStringList Utilities::m_pausedSources;
0022 
0023 void Utilities::disablePowerDevil(bool disable)
0024 {
0025     noPowerDevil = disable;
0026 }
0027 
0028 Utilities &Utilities::instance()
0029 {
0030     static Utilities singleton;
0031     return singleton;
0032 }
0033 
0034 Utilities::Utilities(QObject *parent)
0035     : QObject(parent)
0036     , m_interface(new QDBusInterface(POWERDEVIL_SERVICE_NAME,
0037                                      QStringLiteral("/org/kde/Solid/PowerManagement"),
0038                                      QStringLiteral("org.kde.Solid.PowerManagement"),
0039                                      QDBusConnection::sessionBus(),
0040                                      this))
0041     , m_hasPowerDevil(false)
0042     , m_timer(new QTimer(this))
0043 {
0044     // TODO: It'd be nice to be able to have the daemon off if no alarms/timers are running.
0045     // However, with the current implementation, the client continuously thinks it is off.
0046     //     connect(m_timer, &QTimer::timeout, this, [this] {
0047     //         if (m_activeTimerCount <= 0) {
0048     //             QApplication::exit();
0049     //         }
0050     //     });
0051 
0052     // if PowerDevil is present, we can rely on PowerDevil to track time, otherwise we do it ourself
0053     if (m_interface->isValid() && !noPowerDevil) {
0054         m_hasPowerDevil = hasWakeup();
0055     }
0056 
0057     bool success = QDBusConnection::sessionBus().registerObject(QStringLiteral("/Utility"),
0058                                                                 QStringLiteral("org.kde.PowerManagement"),
0059                                                                 this,
0060                                                                 QDBusConnection::ExportScriptableSlots);
0061     qDebug() << "Registered on DBus:" << success;
0062 
0063     if (hasPowerDevil()) {
0064         qDebug() << "PowerDevil found, using it for time tracking.";
0065     } else {
0066         initWorker();
0067         qDebug() << "PowerDevil not found, using wait worker thread for time tracking.";
0068     }
0069 
0070     if (!noPowerDevil) { // do not watch for powerdevil if we explicitly turned it off
0071         auto m_watcher = new QDBusServiceWatcher{POWERDEVIL_SERVICE_NAME,
0072                                                  QDBusConnection::sessionBus(),
0073                                                  QDBusServiceWatcher::WatchForRegistration | QDBusServiceWatcher::WatchForUnregistration};
0074         connect(m_watcher, &QDBusServiceWatcher::serviceRegistered, this, [this]() {
0075             qDebug() << "PowerDevil found on DBus";
0076             m_hasPowerDevil = hasWakeup();
0077             if (m_hasPowerDevil && m_timerThread)
0078                 m_timerThread->quit();
0079 
0080             Q_EMIT needsReschedule();
0081         });
0082         connect(m_watcher, &QDBusServiceWatcher::serviceUnregistered, this, [this]() {
0083             m_hasPowerDevil = false;
0084             initWorker();
0085 
0086             Q_EMIT needsReschedule();
0087         });
0088     }
0089 
0090     // exit after 1 min if nothing happens
0091     m_timer->start(60 * 1000);
0092 }
0093 
0094 bool Utilities::hasPowerDevil()
0095 {
0096     return m_hasPowerDevil;
0097 }
0098 
0099 int Utilities::scheduleWakeup(quint64 timestamp)
0100 {
0101     if (hasPowerDevil()) {
0102         QDBusReply<uint> reply = m_interface->call(QStringLiteral("scheduleWakeup"), QStringLiteral("org.kde.kclockd"), QDBusObjectPath("/Utility"), timestamp);
0103         if (reply.isValid()) {
0104             m_powerDevilCookies.append(reply.value());
0105         } else {
0106             qDebug() << "Invalid reply, error: " << reply.error();
0107         }
0108         return reply.value();
0109     } else {
0110         m_waitWorkerCookies.append({++m_cookie, timestamp});
0111         schedule();
0112         return m_cookie;
0113     }
0114 }
0115 
0116 void Utilities::clearWakeup(int cookie)
0117 {
0118     if (hasPowerDevil()) {
0119         auto index = m_powerDevilCookies.indexOf(cookie);
0120         if (index != -1) {
0121             m_interface->call(QStringLiteral("clearWakeup"), cookie);
0122             m_powerDevilCookies.removeAt(index);
0123         }
0124     } else {
0125         for (auto index = m_waitWorkerCookies.begin(); index < m_waitWorkerCookies.end(); ++index) {
0126             int pairCookie = (*index).first;
0127             if (cookie == pairCookie) {
0128                 m_waitWorkerCookies.erase(index);
0129             }
0130         }
0131 
0132         // ensure that we schedule the next queued wakeup
0133         schedule();
0134     }
0135 }
0136 
0137 void Utilities::wakeupCallback(int cookie)
0138 {
0139     qDebug() << "Received wakeup callback.";
0140     auto index = m_powerDevilCookies.indexOf(cookie);
0141 
0142     if (index == -1) {
0143         // something must be wrong here, return and do nothing
0144         qDebug() << "Callback ignored (wrong cookie).";
0145     } else {
0146         // remove token
0147         m_powerDevilCookies.removeAt(index);
0148         Q_EMIT wakeup(cookie);
0149     }
0150 }
0151 
0152 void Utilities::schedule()
0153 {
0154     auto eternity = std::numeric_limits<unsigned long long>::max();
0155     auto minTime = eternity;
0156 
0157     for (auto tuple : m_waitWorkerCookies) {
0158         if (minTime > tuple.second) {
0159             minTime = tuple.second;
0160             m_currentCookie = tuple.first;
0161         }
0162     }
0163 
0164     if (minTime != eternity) { // only schedule worker if we have something to wait on
0165         m_worker->setNewTime(minTime); // Unix uses int64 internally for time
0166     }
0167 }
0168 
0169 void Utilities::initWorker()
0170 {
0171     if (!m_timerThread) {
0172         m_timerThread = new QThread(this);
0173         m_worker = new AlarmWaitWorker();
0174         m_worker->moveToThread(m_timerThread);
0175         connect(m_worker, &AlarmWaitWorker::finished, this, [this] {
0176             // notify time is up
0177             Q_EMIT wakeup(m_currentCookie);
0178             clearWakeup(m_currentCookie);
0179         });
0180     }
0181     m_timerThread->start();
0182 }
0183 
0184 bool Utilities::hasWakeup()
0185 {
0186     QDBusMessage m = QDBusMessage::createMethodCall(QStringLiteral("org.kde.Solid.PowerManagement"),
0187                                                     QStringLiteral("/org/kde/Solid/PowerManagement"),
0188                                                     QStringLiteral("org.freedesktop.DBus.Introspectable"),
0189                                                     QStringLiteral("Introspect"));
0190     QDBusReply<QString> result = QDBusConnection::sessionBus().call(m);
0191 
0192     return result.isValid() && result.value().indexOf(QStringLiteral("scheduleWakeup")) >= 0;
0193 }
0194 
0195 void Utilities::exitAfterTimeout()
0196 {
0197     m_timer->start(30000);
0198 }
0199 
0200 void Utilities::incfActiveCount()
0201 {
0202     m_activeTimerCount++;
0203 }
0204 
0205 void Utilities::decfActiveCount()
0206 {
0207     m_activeTimerCount--;
0208     exitAfterTimeout();
0209 }
0210 
0211 void Utilities::wakeupNow()
0212 {
0213     QDBusMessage wakeupCall = QDBusMessage::createMethodCall(QStringLiteral("org.kde.Solid.PowerManagement"),
0214                                                              QStringLiteral("/org/kde/Solid/PowerManagement"),
0215                                                              QStringLiteral("org.kde.Solid.PowerManagement"),
0216                                                              QStringLiteral("wakeup"));
0217     QDBusConnection::sessionBus().call(wakeupCall);
0218 }
0219 
0220 // hack, use timer count to keep alive
0221 void Utilities::keepAlive()
0222 {
0223     incfActiveCount();
0224 }
0225 
0226 void Utilities::canExit()
0227 {
0228     decfActiveCount();
0229 }
0230 
0231 void Utilities::pauseMprisSources()
0232 {
0233     const QStringList interfaces = QDBusConnection::sessionBus().interface()->registeredServiceNames().value();
0234     for (const QString &interface : interfaces) {
0235         if (interface.startsWith(QLatin1String("org.mpris.MediaPlayer2"))) {
0236             OrgMprisMediaPlayer2PlayerInterface mprisInterface(interface, QStringLiteral("/org/mpris/MediaPlayer2"), QDBusConnection::sessionBus());
0237             QString status = mprisInterface.playbackStatus();
0238             if (status == QLatin1String("Playing")) {
0239                 if (!m_pausedSources.contains(interface)) {
0240                     m_pausedSources.append(interface);
0241                     if (mprisInterface.canPause()) {
0242                         mprisInterface.Pause();
0243                     } else {
0244                         mprisInterface.Stop();
0245                     }
0246                 }
0247             }
0248         }
0249     }
0250 }
0251 
0252 void Utilities::resumeMprisSources()
0253 {
0254     for (const QString &interface : std::as_const(m_pausedSources)) {
0255         OrgMprisMediaPlayer2PlayerInterface mprisInterface(interface, QStringLiteral("/org/mpris/MediaPlayer2"), QDBusConnection::sessionBus());
0256         mprisInterface.Play();
0257     }
0258 
0259     m_pausedSources.clear();
0260 }