File indexing completed on 2024-11-17 05:15:20
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 }