File indexing completed on 2024-04-28 16:55:14

0001 /***************************************************************************
0002  *   Copyright (C) 2010 by Dario Freddi <drf@kde.org>                      *
0003  *                                                                         *
0004  *   This program is free software; you can redistribute it and/or modify  *
0005  *   it under the terms of the GNU General Public License as published by  *
0006  *   the Free Software Foundation; either version 2 of the License, or     *
0007  *   (at your option) any later version.                                   *
0008  *                                                                         *
0009  *   This program is distributed in the hope that it will be useful,       *
0010  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0011  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0012  *   GNU General Public License for more details.                          *
0013  *                                                                         *
0014  *   You should have received a copy of the GNU General Public License     *
0015  *   along with this program; if not, write to the                         *
0016  *   Free Software Foundation, Inc.,                                       *
0017  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA .        *
0018  ***************************************************************************/
0019 
0020 #include "powerdevilcore.h"
0021 
0022 #include "PowerDevilSettings.h"
0023 
0024 #include "powerdevilaction.h"
0025 #include "powerdevilactionpool.h"
0026 #include "powerdevilpolicyagent.h"
0027 #include "powerdevilpowermanagement.h"
0028 #include "powerdevilprofilegenerator.h"
0029 #include "powerdevil_debug.h"
0030 
0031 #include "actions/bundled/suspendsession.h"
0032 
0033 #include <Solid/Battery>
0034 #include <Solid/Device>
0035 #include <Solid/DeviceNotifier>
0036 
0037 
0038 #include <kauth_version.h>
0039 #include <KAuth/Action>
0040 #include <KAuth/ExecuteJob>
0041 
0042 #include <KIdleTime>
0043 #include <KLocalizedString>
0044 #include <KNotification>
0045 
0046 #include <KActivities/Consumer>
0047 
0048 #include <QTimer>
0049 #include <QDBusConnection>
0050 #include <QDBusConnectionInterface>
0051 #include <QDBusServiceWatcher>
0052 
0053 #include <QDebug>
0054 
0055 #include <algorithm>
0056 #include <Kirigami/TabletModeWatcher>
0057 
0058 #ifdef Q_OS_LINUX
0059 #include <sys/timerfd.h>
0060 #endif
0061 
0062 namespace PowerDevil
0063 {
0064 
0065 Core::Core(QObject* parent)
0066     : QObject(parent)
0067     , m_hasDualGpu(false)
0068     , m_criticalBatteryTimer(new QTimer(this))
0069     , m_activityConsumer(new KActivities::Consumer(this))
0070     , m_pendingWakeupEvent(true)
0071 {
0072     KAuth::Action discreteGpuAction(QStringLiteral("org.kde.powerdevil.discretegpuhelper.hasdualgpu"));
0073     discreteGpuAction.setHelperId(QStringLiteral("org.kde.powerdevil.discretegpuhelper"));
0074     KAuth::ExecuteJob *discreteGpuJob = discreteGpuAction.execute();
0075     connect(discreteGpuJob, &KJob::result, this, [this, discreteGpuJob]  {
0076         if (discreteGpuJob->error()) {
0077             qCWarning(POWERDEVIL) << "org.kde.powerdevil.discretegpuhelper.hasdualgpu failed";
0078             qCDebug(POWERDEVIL) << discreteGpuJob->errorText();
0079             return;
0080         }
0081         m_hasDualGpu = discreteGpuJob->data()[QStringLiteral("hasdualgpu")].toBool();
0082     });
0083     discreteGpuJob->start();
0084 
0085     readChargeThreshold();
0086 }
0087 
0088 Core::~Core()
0089 {
0090     qCDebug(POWERDEVIL) << "Core unloading";
0091     // Unload all actions before exiting, and clear the cache
0092     ActionPool::instance()->unloadAllActiveActions();
0093     ActionPool::instance()->clearCache();
0094 }
0095 
0096 void Core::loadCore(BackendInterface* backend)
0097 {
0098     if (!backend) {
0099         return;
0100     }
0101 
0102     m_backend = backend;
0103 
0104     // Async backend init - so that KDED gets a bit of a speed up
0105     qCDebug(POWERDEVIL) << "Core loaded, initializing backend";
0106     connect(m_backend, &BackendInterface::backendReady, this, &Core::onBackendReady);
0107     m_backend->init();
0108 }
0109 
0110 void Core::onBackendReady()
0111 {
0112     qCDebug(POWERDEVIL) << "Backend ready, KDE Power Management system initialized";
0113 
0114     m_profilesConfig = KSharedConfig::openConfig(QStringLiteral("powermanagementprofilesrc"), KConfig::CascadeConfig);
0115 
0116     QStringList groups = m_profilesConfig->groupList();
0117     // the "migration" key is for shortcuts migration in added by migratePre512KeyboardShortcuts
0118     // and as such our configuration would never be considered empty, ignore it!
0119     groups.removeOne(QStringLiteral("migration"));
0120 
0121     // Is it brand new?
0122     if (groups.isEmpty()) {
0123         // Generate defaults
0124         qCDebug(POWERDEVIL) << "Generating a default configuration";
0125         bool toRam = m_backend->supportedSuspendMethods() & PowerDevil::BackendInterface::ToRam;
0126         bool toDisk = m_backend->supportedSuspendMethods() & PowerDevil::BackendInterface::ToDisk;
0127 
0128         // These are generated profiles,
0129         const bool mobile = Kirigami::TabletModeWatcher::self()->isTabletMode();
0130         const bool vm = PowerDevil::PowerManagement::instance()->isVirtualMachine();
0131 
0132         ProfileGenerator::generateProfiles(mobile, vm, toRam, toDisk);
0133         m_profilesConfig->reparseConfiguration();
0134     }
0135 
0136     // Get the battery devices ready
0137     {
0138         using namespace Solid;
0139         connect(DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceAdded,
0140                 this, &Core::onDeviceAdded);
0141         connect(DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceRemoved,
0142                 this, &Core::onDeviceRemoved);
0143 
0144         // Force the addition of already existent batteries
0145         const auto devices = Device::listFromType(DeviceInterface::Battery, QString());
0146         for (const Device &device : devices) {
0147             onDeviceAdded(device.udi());
0148         }
0149     }
0150 
0151     connect(m_backend, &BackendInterface::acAdapterStateChanged,
0152             this, &Core::onAcAdapterStateChanged);
0153     connect(m_backend, &BackendInterface::batteryRemainingTimeChanged,
0154             this, &Core::onBatteryRemainingTimeChanged);
0155     connect(m_backend, &BackendInterface::smoothedBatteryRemainingTimeChanged, this, &Core::onSmoothedBatteryRemainingTimeChanged);
0156     connect(m_backend, &BackendInterface::lidClosedChanged,
0157             this, &Core::onLidClosedChanged);
0158     connect(m_backend, &BackendInterface::aboutToSuspend,
0159             this, &Core::onAboutToSuspend);
0160     connect(KIdleTime::instance(), SIGNAL(timeoutReached(int,int)),
0161             this, SLOT(onKIdleTimeoutReached(int,int)));
0162     connect(KIdleTime::instance(), &KIdleTime::resumingFromIdle,
0163             this, &Core::onResumingFromIdle);
0164     connect(m_activityConsumer, &KActivities::Consumer::currentActivityChanged, this, [this]() {
0165         loadProfile();
0166     });
0167 
0168     // Set up the policy agent
0169     PowerDevil::PolicyAgent::instance()->init();
0170     // When inhibitions change, simulate user activity, see Bug 315438
0171     connect(PowerDevil::PolicyAgent::instance(), &PowerDevil::PolicyAgent::unavailablePoliciesChanged, this,
0172             [](PowerDevil::PolicyAgent::RequiredPolicies newPolicies) {
0173         Q_UNUSED(newPolicies);
0174         KIdleTime::instance()->simulateUserActivity();
0175     });
0176 
0177     // Bug 354250: Simulate user activity when session becomes inactive,
0178     // this keeps us from sending the computer to sleep when switching to an idle session.
0179     // (this is just being lazy as it will result in us clearing everything
0180     connect(PowerDevil::PolicyAgent::instance(), &PowerDevil::PolicyAgent::sessionActiveChanged, this, [this](bool active) {
0181         if (active) {
0182             // force reload profile so all actions re-register their idle timeouts
0183             loadProfile(true /*force*/);
0184         } else {
0185             // Bug 354250: Keep us from sending the computer to sleep when switching
0186             // to an idle session by removing all idle timeouts
0187             KIdleTime::instance()->removeAllIdleTimeouts();
0188             m_registeredActionTimeouts.clear();
0189         }
0190     });
0191 
0192     // Initialize the action pool, which will also load the needed startup actions.
0193     PowerDevil::ActionPool::instance()->init(this);
0194 
0195     // Set up the critical battery timer
0196     m_criticalBatteryTimer->setSingleShot(true);
0197     m_criticalBatteryTimer->setInterval(60000);
0198     connect(m_criticalBatteryTimer, &QTimer::timeout, this, &Core::onCriticalBatteryTimerExpired);
0199 
0200     // wait until the notification system is set up before firing notifications
0201     // to avoid them showing ontop of ksplash...
0202     if (QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.freedesktop.Notifications"))) {
0203         onServiceRegistered(QString());
0204     } else {
0205         m_notificationsWatcher = new QDBusServiceWatcher(QStringLiteral("org.freedesktop.Notifications"),
0206                                                          QDBusConnection::sessionBus(),
0207                                                          QDBusServiceWatcher::WatchForRegistration,
0208                                                          this);
0209         connect(m_notificationsWatcher, &QDBusServiceWatcher::serviceRegistered, this, &Core::onServiceRegistered);
0210 
0211         // ...but fire them after 30s nonetheless to ensure they've been shown
0212         QTimer::singleShot(30000, this, &Core::onNotificationTimeout);
0213     }
0214 
0215 #ifdef Q_OS_LINUX
0216 
0217     // try creating a timerfd which can wake system from suspend
0218     m_timerFd = timerfd_create(CLOCK_REALTIME_ALARM, TFD_CLOEXEC);
0219 
0220     // if that fails due to privilges maybe, try normal timerfd
0221     if (m_timerFd == -1) {
0222         qCDebug(POWERDEVIL) << "Unable to create a CLOCK_REALTIME_ALARM timer, trying CLOCK_REALTIME\n This would mean that wakeup requests won't wake device from suspend";
0223         m_timerFd = timerfd_create(CLOCK_REALTIME, TFD_CLOEXEC);
0224     }
0225 
0226     if (m_timerFd != -1) {
0227         m_timerFdSocketNotifier = new QSocketNotifier(m_timerFd, QSocketNotifier::Read);
0228         connect(m_timerFdSocketNotifier, &QSocketNotifier::activated, this, &Core::timerfdEventHandler);
0229         // we disable events reading for now
0230         m_timerFdSocketNotifier->setEnabled(false);
0231     } else {
0232         qCDebug(POWERDEVIL) << "Unable to create a CLOCK_REALTIME timer, scheduled wakeups won't be available";
0233     }
0234 
0235 #endif
0236 
0237     // All systems up Houston, let's go!
0238     Q_EMIT coreReady();
0239     refreshStatus();
0240 }
0241 
0242 bool Core::isActionSupported(const QString& actionName)
0243 {
0244     Action *action = ActionPool::instance()->loadAction(actionName, KConfigGroup(), this);
0245     if (!action) {
0246         return false;
0247     } else {
0248         return action->isSupported();
0249     }
0250 }
0251 
0252 void Core::refreshStatus()
0253 {
0254     /* The configuration could have changed if this function was called, so
0255      * let's resync it.
0256      */
0257     reparseConfiguration();
0258 
0259     loadProfile(true);
0260 }
0261 
0262 void Core::reparseConfiguration()
0263 {
0264     PowerDevilSettings::self()->load();
0265     m_profilesConfig->reparseConfiguration();
0266 
0267     // Config reloaded
0268     Q_EMIT configurationReloaded();
0269 
0270     // Check if critical threshold might have changed and cancel the timer if necessary.
0271     if (currentChargePercent() > PowerDevilSettings::batteryCriticalLevel()) {
0272         m_criticalBatteryTimer->stop();
0273         if (m_criticalBatteryNotification) {
0274             m_criticalBatteryNotification->close();
0275         }
0276     }
0277 
0278     if (m_lowBatteryNotification && currentChargePercent() > PowerDevilSettings::batteryLowLevel()) {
0279         m_lowBatteryNotification->close();
0280     }
0281 
0282     readChargeThreshold();
0283 }
0284 
0285 QString Core::currentProfile() const
0286 {
0287     return m_currentProfile;
0288 }
0289 
0290 void Core::loadProfile(bool force)
0291 {
0292     QString profileId;
0293 
0294     // Policy check
0295     if (PolicyAgent::instance()->requirePolicyCheck(PolicyAgent::ChangeProfile) != PolicyAgent::None) {
0296         qCDebug(POWERDEVIL) << "Policy Agent prevention: on";
0297         return;
0298     }
0299 
0300     KConfigGroup config;
0301 
0302     // Check the activity in which we are in
0303     QString activity = m_activityConsumer->currentActivity();
0304     qCDebug(POWERDEVIL) << "Currently using activity " << activity;
0305     KConfigGroup activitiesConfig(m_profilesConfig, "Activities");
0306     qCDebug(POWERDEVIL) << "Activities with settings:" << activitiesConfig.groupList() << activitiesConfig.keyList();
0307 
0308     // Are we mirroring an activity?
0309     if (activitiesConfig.group(activity).readEntry("mode", "None") == QStringLiteral("ActLike") &&
0310         activitiesConfig.group(activity).readEntry("actLike", QString()) != QStringLiteral("AC") &&
0311         activitiesConfig.group(activity).readEntry("actLike", QString()) != QStringLiteral("Battery") &&
0312         activitiesConfig.group(activity).readEntry("actLike", QString()) != QStringLiteral("LowBattery")) {
0313         // Yes, let's use that then
0314         activity = activitiesConfig.group(activity).readEntry("actLike", QString());
0315         qCDebug(POWERDEVIL) << "Activity is a mirror";
0316     }
0317 
0318     KConfigGroup activityConfig = activitiesConfig.group(activity);
0319     qCDebug(POWERDEVIL) << "Settings for loaded activity:" << activity << activityConfig.groupList() << activityConfig.keyList();
0320 
0321     // See if this activity has priority
0322     if (activityConfig.readEntry("mode", "None") == QStringLiteral("SeparateSettings")) {
0323         // Prioritize this profile over anything
0324         config = activityConfig.group("SeparateSettings");
0325         qCDebug(POWERDEVIL) << "Activity is enforcing a different profile";
0326         profileId = activity;
0327     } else {
0328         // It doesn't, let's load the current state's profile
0329         if (m_batteriesPercent.isEmpty()) {
0330             qCDebug(POWERDEVIL) << "No batteries found, loading AC";
0331             profileId = QStringLiteral("AC");
0332         } else if (activityConfig.readEntry("mode", "None") == QStringLiteral("ActLike")) {
0333             if (activityConfig.readEntry("actLike", QString()) == QStringLiteral("AC") ||
0334                 activityConfig.readEntry("actLike", QString()) == QStringLiteral("Battery") ||
0335                 activityConfig.readEntry("actLike", QString()) == QStringLiteral("LowBattery")) {
0336                 // Same as above, but with an existing profile
0337                 config = m_profilesConfig.data()->group(activityConfig.readEntry("actLike", QString()));
0338                 profileId = activityConfig.readEntry("actLike", QString());
0339                 qCDebug(POWERDEVIL) << "Activity is mirroring a different profile";
0340             }
0341         } else {
0342             // Compute the previous and current global percentage
0343             const int percent = currentChargePercent();
0344 
0345             if (backend()->acAdapterState() == BackendInterface::Plugged) {
0346                 profileId = QStringLiteral("AC");
0347                 qCDebug(POWERDEVIL) << "Loading profile for plugged AC";
0348             } else if (percent <= PowerDevilSettings::batteryLowLevel()) {
0349                 profileId = QStringLiteral("LowBattery");
0350                 qCDebug(POWERDEVIL) << "Loading profile for low battery";
0351             } else {
0352                 profileId = QStringLiteral("Battery");
0353                 qCDebug(POWERDEVIL) << "Loading profile for unplugged AC";
0354             }
0355         }
0356 
0357         config = m_profilesConfig.data()->group(profileId);
0358         qCDebug(POWERDEVIL) << "Activity is not forcing a profile";
0359     }
0360 
0361     // Release any special inhibitions
0362     {
0363         QHash<QString,int>::iterator i = m_sessionActivityInhibit.begin();
0364         while (i != m_sessionActivityInhibit.end()) {
0365             PolicyAgent::instance()->ReleaseInhibition(i.value());
0366             i = m_sessionActivityInhibit.erase(i);
0367         }
0368 
0369         i = m_screenActivityInhibit.begin();
0370         while (i != m_screenActivityInhibit.end()) {
0371             PolicyAgent::instance()->ReleaseInhibition(i.value());
0372             i = m_screenActivityInhibit.erase(i);
0373         }
0374     }
0375 
0376     if (!config.isValid()) {
0377         qCWarning(POWERDEVIL) << "Profile " << profileId << "has been selected but does not exist.";
0378         return;
0379     }
0380 
0381     // Check: do we need to change profile at all?
0382     if (m_currentProfile == profileId && !force) {
0383         // No, let's leave things as they are
0384         qCDebug(POWERDEVIL) << "Skipping action reload routine as profile has not changed";
0385 
0386         // Do we need to force a wakeup?
0387         if (m_pendingWakeupEvent) {
0388             // Fake activity at this stage, when no timeouts are registered
0389             onResumingFromIdle();
0390             m_pendingWakeupEvent = false;
0391         }
0392     } else {
0393         // First of all, let's clean the old actions. This will also call the onProfileUnload callback
0394         ActionPool::instance()->unloadAllActiveActions();
0395 
0396         // Do we need to force a wakeup?
0397         if (m_pendingWakeupEvent) {
0398             // Fake activity at this stage, when no timeouts are registered
0399             onResumingFromIdle();
0400             m_pendingWakeupEvent = false;
0401         }
0402 
0403         // Cool, now let's load the needed actions
0404         const auto groupList = config.groupList();
0405         for (const QString &actionName : groupList) {
0406             Action *action = ActionPool::instance()->loadAction(actionName, config.group(actionName), this);
0407             if (action) {
0408                 action->onProfileLoad();
0409             } else {
0410                 // Ouch, error. But let's just warn and move on anyway
0411                 //TODO Maybe Remove from the configuration if unsupported
0412                 qCWarning(POWERDEVIL) << "The profile " << profileId <<  "tried to activate"
0413                                 << actionName << "a non-existent action. This is usually due to an installation problem,"
0414                                 " a configuration problem, or because the action is not supported";
0415             }
0416         }
0417 
0418         // We are now on a different profile
0419         m_currentProfile = profileId;
0420         Q_EMIT profileChanged(m_currentProfile);
0421     }
0422 
0423     // Now... any special behaviors we'd like to consider?
0424     if (activityConfig.readEntry("mode", "None") == QStringLiteral("SpecialBehavior")) {
0425         qCDebug(POWERDEVIL) << "Activity has special behaviors";
0426         KConfigGroup behaviorGroup = activityConfig.group("SpecialBehavior");
0427         if (behaviorGroup.readEntry("performAction", false)) {
0428             // Let's override the configuration for this action at all times
0429             ActionPool::instance()->loadAction(QStringLiteral("SuspendSession"), behaviorGroup.group("ActionConfig"), this);
0430             qCDebug(POWERDEVIL) << "Activity overrides suspend session action"; // debug hence not sleep
0431         }
0432 
0433         if (behaviorGroup.readEntry("noSuspend", false)) {
0434             qCDebug(POWERDEVIL) << "Activity triggers a suspend inhibition"; // debug hence not sleep
0435             // Trigger a special inhibition - if we don't have one yet
0436             if (!m_sessionActivityInhibit.contains(activity)) {
0437                 int cookie =
0438                 PolicyAgent::instance()->AddInhibition(PolicyAgent::InterruptSession, i18n("Activity Manager"),
0439                                                        i18n("This activity's policies prevent the system from going to sleep"));
0440 
0441                 m_sessionActivityInhibit.insert(activity, cookie);
0442             }
0443         }
0444 
0445         if (behaviorGroup.readEntry("noScreenManagement", false)) {
0446             qCDebug(POWERDEVIL) << "Activity triggers a screen management inhibition";
0447             // Trigger a special inhibition - if we don't have one yet
0448             if (!m_screenActivityInhibit.contains(activity)) {
0449                 int cookie =
0450                 PolicyAgent::instance()->AddInhibition(PolicyAgent::ChangeScreenSettings, i18n("Activity Manager"),
0451                                                        i18n("This activity's policies prevent screen power management"));
0452 
0453                 m_screenActivityInhibit.insert(activity, cookie);
0454             }
0455         }
0456     }
0457 
0458     // If the lid is closed, retrigger the lid close signal
0459     // so that "switching profile then closing the lid" has the same result as
0460     // "closing lid then switching profile".
0461     if (m_backend->isLidClosed()) {
0462         Q_EMIT m_backend->buttonPressed(PowerDevil::BackendInterface::LidClose);
0463     }
0464 }
0465 
0466 void Core::onDeviceAdded(const QString &udi)
0467 {
0468     if (m_batteriesPercent.contains(udi) || m_peripheralBatteriesPercent.contains(udi)) {
0469         // We already know about this device
0470         return;
0471     }
0472 
0473     using namespace Solid;
0474     Device device(udi);
0475     Battery *b = qobject_cast<Battery *>(device.asDeviceInterface(DeviceInterface::Battery));
0476 
0477     if (!b) {
0478         return;
0479     }
0480 
0481     connect(b, &Battery::chargePercentChanged, this, &Core::onBatteryChargePercentChanged);
0482     connect(b, &Battery::chargeStateChanged, this, &Core::onBatteryChargeStateChanged);
0483 
0484     qCDebug(POWERDEVIL) << "Battery with UDI" << udi << "was detected";
0485 
0486     if (b->isPowerSupply()) {
0487         m_batteriesPercent[udi] = b->chargePercent();
0488         m_batteriesCharged[udi] = (b->chargeState() == Solid::Battery::FullyCharged);
0489     } else { // non-power supply batteries are treated separately
0490         m_peripheralBatteriesPercent[udi] = b->chargePercent();
0491 
0492         // notify the user about the empty mouse/keyboard when plugging it in; don't when
0493         // notifications aren't ready yet so we avoid showing them ontop of ksplash;
0494         // also we'll notify about all devices when notifications are ready anyway
0495         if (m_notificationsReady) {
0496             emitBatteryChargePercentNotification(b->chargePercent(), 1000 /* so current is always lower than previous */, udi);
0497         }
0498     }
0499 
0500     // If a new battery has been added, let's clear some pending suspend actions if the new global batteries percentage is
0501     // higher than the battery critical level. (See bug 329537)
0502     if (m_lowBatteryNotification && currentChargePercent() > PowerDevilSettings::batteryLowLevel()) {
0503         m_lowBatteryNotification->close();
0504     }
0505 
0506     if (currentChargePercent() > PowerDevilSettings::batteryCriticalLevel()) {
0507         if (m_criticalBatteryNotification) {
0508             m_criticalBatteryNotification->close();
0509         }
0510 
0511         if (m_criticalBatteryTimer->isActive()) {
0512             m_criticalBatteryTimer->stop();
0513             emitRichNotification(QStringLiteral("pluggedin"),
0514                                  i18n("Extra Battery Added"),
0515                                  i18n("The computer will no longer go to sleep."));
0516         }
0517     }
0518 }
0519 
0520 void Core::onDeviceRemoved(const QString &udi)
0521 {
0522     if (!m_batteriesPercent.contains(udi) && !m_peripheralBatteriesPercent.contains(udi)) {
0523         // We don't know about this device
0524         return;
0525     }
0526 
0527     using namespace Solid;
0528     Device device(udi);
0529     Battery *b = qobject_cast<Battery *>(device.asDeviceInterface(DeviceInterface::Battery));
0530 
0531     disconnect(b, &Battery::chargePercentChanged, this, &Core::onBatteryChargePercentChanged);
0532     disconnect(b, &Battery::chargeStateChanged, this, &Core::onBatteryChargeStateChanged);
0533 
0534     qCDebug(POWERDEVIL) << "Battery with UDI" << udi << "has been removed";
0535 
0536     m_batteriesPercent.remove(udi);
0537     m_peripheralBatteriesPercent.remove(udi);
0538     m_batteriesCharged.remove(udi);
0539 }
0540 
0541 void Core::emitNotification(const QString &eventId, const QString &title, const QString &message, const QString &iconName)
0542 {
0543     KNotification::event(eventId, title, message, iconName, nullptr, KNotification::CloseOnTimeout, QStringLiteral("powerdevil"));
0544 }
0545 
0546 void Core::emitRichNotification(const QString &evid, const QString &title, const QString &message)
0547 {
0548     KNotification::event(evid, title, message, QPixmap(),
0549                          nullptr, KNotification::CloseOnTimeout, QStringLiteral("powerdevil"));
0550 }
0551 
0552 bool Core::emitBatteryChargePercentNotification(int currentPercent, int previousPercent, const QString &udi, Core::ChargeNotificationFlags flags)
0553 {
0554     if (m_peripheralBatteriesPercent.contains(udi)) {
0555         // Show the notification just once on each normal->low transition
0556         if (currentPercent > PowerDevilSettings::peripheralBatteryLowLevel() ||
0557             previousPercent <= PowerDevilSettings::peripheralBatteryLowLevel()) {
0558             return false;
0559         }
0560 
0561         using namespace Solid;
0562         Device device(udi);
0563         Battery *b = qobject_cast<Battery *>(device.asDeviceInterface(DeviceInterface::Battery));
0564         if (!b) {
0565             return false;
0566         }
0567 
0568         // if you leave the device out of reach or it has not been initialized yet
0569         // it won't be "there" and report 0%, don't show anything in this case
0570         if (!b->isPresent() || b->chargePercent() == 0) {
0571             return false;
0572         }
0573 
0574         // Bluetooth devices don't report charge state, so it's "NoCharge" in all cases for them
0575         if (b->chargeState() != Battery::Discharging && b->chargeState() != Battery::NoCharge) {
0576             return false;
0577         }
0578 
0579         {
0580             QString name = device.product();
0581             if (!device.vendor().isEmpty()) {
0582                 name = i18nc("%1 is vendor name, %2 is product name", "%1 %2", device.vendor(), device.product());
0583             }
0584 
0585             QString title = i18nc("The battery in an external device", "Device Battery Low (%1% Remaining)", currentPercent);
0586             QString msg = i18nc("Placeholder is device name",
0587                                       "The battery in \"%1\" is running low, and the device may turn off at any time. "
0588                                       "Please recharge or replace the battery.", name);
0589             QString icon = QStringLiteral("battery-caution");
0590 
0591             switch(b->type()) {
0592             case Battery::MouseBattery:
0593                 title = i18n("Mouse Battery Low (%1% Remaining)", currentPercent);
0594                 icon = QStringLiteral("input-mouse");
0595                 break;
0596             case Battery::KeyboardBattery:
0597                 title = i18n("Keyboard Battery Low (%1% Remaining)", currentPercent);
0598                 icon = QStringLiteral("input-keyboard");
0599                 break;
0600             case Battery::BluetoothBattery:
0601                 title = i18n("Bluetooth Device Battery Low (%1% Remaining)", currentPercent);
0602                 msg = i18nc("Placeholder is device name",
0603                             "The battery in Bluetooth device \"%1\" is running low, and the device may turn off at any time. "
0604                             "Please recharge or replace the battery.", name);
0605                 icon = QStringLiteral("preferences-system-bluetooth");
0606                 break;
0607             default:
0608                 break;
0609             }
0610 
0611             emitNotification(QStringLiteral("lowperipheralbattery"), title, msg, icon);
0612         }
0613 
0614         return true;
0615     }
0616 
0617     // Make sure a notificaton that's kept open updates its percentage live.
0618     updateBatteryNotifications(currentPercent);
0619 
0620     if (m_backend->acAdapterState() == BackendInterface::Plugged
0621             && !flags.testFlag(ChargeNotificationFlag::NotifyWhenAcPluggedIn)) {
0622         return false;
0623     }
0624 
0625     if (currentPercent <= PowerDevilSettings::batteryCriticalLevel() &&
0626         previousPercent > PowerDevilSettings::batteryCriticalLevel()) {
0627         handleCriticalBattery(currentPercent);
0628         return true;
0629     } else if (currentPercent <= PowerDevilSettings::batteryLowLevel() &&
0630                previousPercent > PowerDevilSettings::batteryLowLevel()) {
0631         handleLowBattery(currentPercent);
0632         return true;
0633     }
0634     return false;
0635 }
0636 
0637 void Core::handleLowBattery(int percent)
0638 {
0639     if (m_lowBatteryNotification) {
0640         return;
0641     }
0642 
0643     m_lowBatteryNotification = new KNotification(QStringLiteral("lowbattery"), KNotification::Persistent, nullptr);
0644     m_lowBatteryNotification->setComponentName(QStringLiteral("powerdevil"));
0645     updateBatteryNotifications(percent); // sets title
0646     if (m_backend->acAdapterState() == BackendInterface::Plugged) {
0647         m_lowBatteryNotification->setText(i18n("Battery running low - to continue using your computer, make sure that the power adapter is plugged in and that it provides enough power."));
0648     } else {
0649         m_lowBatteryNotification->setText(i18n("Battery running low - to continue using your computer, plug it in or shut it down and change the battery."));
0650     }
0651     m_lowBatteryNotification->setUrgency(KNotification::CriticalUrgency);
0652     m_lowBatteryNotification->sendEvent();
0653 }
0654 
0655 void Core::handleCriticalBattery(int percent)
0656 {
0657     if (m_lowBatteryNotification) {
0658         m_lowBatteryNotification->close();
0659     }
0660 
0661     // no parent, but it won't leak, since it will be closed both in case of timeout or direct action
0662     m_criticalBatteryNotification = new KNotification(QStringLiteral("criticalbattery"), KNotification::Persistent, nullptr);
0663     m_criticalBatteryNotification->setComponentName(QStringLiteral("powerdevil"));
0664     updateBatteryNotifications(percent); // sets title
0665 
0666     QStringList actions = {i18nc("Cancel timeout that will automatically put system to sleep because of low battery", "Cancel")};
0667 
0668     connect(m_criticalBatteryNotification.data(), &KNotification::action1Activated, this, [this] {
0669         triggerCriticalBatteryAction();
0670     });
0671     connect(m_criticalBatteryNotification.data(), &KNotification::action2Activated, this, [this] {
0672         m_criticalBatteryTimer->stop();
0673         m_criticalBatteryNotification->close();
0674     });
0675 
0676     switch (PowerDevilSettings::batteryCriticalAction()) {
0677     case PowerDevil::BundledActions::SuspendSession::ShutdownMode:
0678         m_criticalBatteryNotification->setText(i18n("Battery level critical. Your computer will shut down in 60 seconds."));
0679         actions.prepend(i18nc("@action:button Shut down without waiting for the battery critical timer", "Shut Down Now"));
0680         m_criticalBatteryNotification->setActions(actions);
0681         m_criticalBatteryTimer->start();
0682         break;
0683     case PowerDevil::BundledActions::SuspendSession::ToDiskMode:
0684         m_criticalBatteryNotification->setText(i18n("Battery level critical. Your computer will enter hibernation mode in 60 seconds."));
0685         actions.prepend(i18nc("@action:button Enter hibernation mode without waiting for the battery critical timer", "Hibernate Now"));
0686         m_criticalBatteryNotification->setActions(actions);
0687         m_criticalBatteryTimer->start();
0688         break;
0689     case PowerDevil::BundledActions::SuspendSession::ToRamMode:
0690         m_criticalBatteryNotification->setText(i18n("Battery level critical. Your computer will go to sleep in 60 seconds."));
0691         actions.prepend(i18nc("@action:button Suspend to ram without waiting for the battery critical timer", "Sleep Now"));
0692         m_criticalBatteryNotification->setActions(actions);
0693         m_criticalBatteryTimer->start();
0694         break;
0695     default:
0696         m_criticalBatteryNotification->setText(i18n("Battery level critical. Please save your work."));
0697         // no timer, no actions
0698         break;
0699     }
0700 
0701     m_criticalBatteryNotification->sendEvent();
0702 }
0703 
0704 void Core::updateBatteryNotifications(int percent)
0705 {
0706     if (m_lowBatteryNotification) {
0707         m_lowBatteryNotification->setTitle(i18n("Battery Low (%1% Remaining)", percent));
0708     }
0709 
0710     if (m_criticalBatteryNotification) {
0711         m_criticalBatteryNotification->setTitle(i18n("Battery Critical (%1% Remaining)", percent));
0712     }
0713 }
0714 
0715 void Core::onAcAdapterStateChanged(PowerDevil::BackendInterface::AcAdapterState state)
0716 {
0717     qCDebug(POWERDEVIL);
0718     // Post request for faking an activity event - usually adapters don't plug themselves out :)
0719     m_pendingWakeupEvent = true;
0720     loadProfile();
0721 
0722     if (state == BackendInterface::Plugged) {
0723         // If the AC Adaptor has been plugged in, let's clear some pending suspend actions
0724         if (m_lowBatteryNotification) {
0725             m_lowBatteryNotification->close();
0726         }
0727 
0728         if (m_criticalBatteryNotification) {
0729             m_criticalBatteryNotification->close();
0730         }
0731 
0732         if (m_criticalBatteryTimer->isActive()) {
0733             m_criticalBatteryTimer->stop();
0734             emitRichNotification(QStringLiteral("pluggedin"),
0735                              i18n("AC Adapter Plugged In"),
0736                              i18n("The computer will no longer go to sleep."));
0737         } else {
0738             emitRichNotification(QStringLiteral("pluggedin"), i18n("Running on AC power"), i18n("The power adapter has been plugged in."));
0739         }
0740     } else if (state == BackendInterface::Unplugged) {
0741         emitRichNotification(QStringLiteral("unplugged"), i18n("Running on Battery Power"), i18n("The power adapter has been unplugged."));
0742     }
0743 }
0744 
0745 void Core::onBatteryChargePercentChanged(int percent, const QString &udi)
0746 {
0747     if (m_peripheralBatteriesPercent.contains(udi)) {
0748         const int previousPercent = m_peripheralBatteriesPercent.value(udi);
0749         m_peripheralBatteriesPercent[udi] = percent;
0750 
0751         if (percent < previousPercent) {
0752             emitBatteryChargePercentNotification(percent, previousPercent, udi);
0753         }
0754         return;
0755     }
0756 
0757     // Compute the previous and current global percentage
0758     const int previousPercent = currentChargePercent();
0759     const int currentPercent = previousPercent - (m_batteriesPercent[udi] - percent);
0760 
0761     // Update the battery percentage
0762     m_batteriesPercent[udi] = percent;
0763 
0764     if (currentPercent < previousPercent) {
0765         // When battery drains while still plugged in, warn nevertheless.
0766         if (emitBatteryChargePercentNotification(currentPercent, previousPercent, udi, ChargeNotificationFlag::NotifyWhenAcPluggedIn)) {
0767             // Only refresh status if a notification has actually been emitted
0768             loadProfile();
0769         }
0770     }
0771 }
0772 
0773 void Core::onBatteryChargeStateChanged(int state, const QString &udi)
0774 {
0775     if (!m_batteriesCharged.contains(udi)) {
0776         return;
0777     }
0778 
0779     bool previousCharged = true;
0780     for (auto i = m_batteriesCharged.constBegin(); i != m_batteriesCharged.constEnd(); ++i) {
0781         if (!i.value()) {
0782             previousCharged = false;
0783             break;
0784         }
0785     }
0786 
0787     m_batteriesCharged[udi] = (state == Solid::Battery::FullyCharged);
0788 
0789     if (m_backend->acAdapterState() != BackendInterface::Plugged) {
0790         return;
0791     }
0792 
0793     bool currentCharged = true;
0794     for (auto i = m_batteriesCharged.constBegin(); i != m_batteriesCharged.constEnd(); ++i) {
0795         if (!i.value()) {
0796             currentCharged = false;
0797             break;
0798         }
0799     }
0800 
0801     if (!previousCharged && currentCharged) {
0802         emitRichNotification(QStringLiteral("fullbattery"), i18n("Charging Complete"), i18n("Battery now fully charged."));
0803         loadProfile();
0804     }
0805 }
0806 
0807 void Core::onCriticalBatteryTimerExpired()
0808 {
0809     if (m_criticalBatteryNotification) {
0810         m_criticalBatteryNotification->close();
0811     }
0812 
0813     // Do that only if we're not on AC
0814     if (m_backend->acAdapterState() == BackendInterface::Unplugged) {
0815        triggerCriticalBatteryAction();
0816     }
0817 }
0818 
0819 void Core::triggerCriticalBatteryAction()
0820 {
0821     // We consider this as a very special button
0822     PowerDevil::Action *helperAction = ActionPool::instance()->loadAction(QStringLiteral("HandleButtonEvents"), KConfigGroup(), this);
0823     if (helperAction) {
0824         QVariantMap args;
0825         args[QStringLiteral("Button")] = 32;
0826         args[QStringLiteral("Type")] = QVariant::fromValue<uint>(PowerDevilSettings::batteryCriticalAction());
0827         args[QStringLiteral("Explicit")] = true;
0828         helperAction->trigger(args);
0829     }
0830 }
0831 
0832 void Core::onBatteryRemainingTimeChanged(qulonglong time)
0833 {
0834     Q_EMIT batteryRemainingTimeChanged(time);
0835 }
0836 
0837 void Core::onSmoothedBatteryRemainingTimeChanged(qulonglong time)
0838 {
0839     Q_EMIT smoothedBatteryRemainingTimeChanged(time);
0840 }
0841 
0842 void Core::onKIdleTimeoutReached(int identifier, int msec)
0843 {
0844     // Find which action(s) requested this idle timeout
0845     for (auto i = m_registeredActionTimeouts.constBegin(), end = m_registeredActionTimeouts.constEnd(); i != end; ++i) {
0846         if (i.value().contains(identifier)) {
0847             i.key()->onIdleTimeout(msec);
0848 
0849             // And it will need to be awaken
0850             m_pendingResumeFromIdleActions.insert(i.key());
0851             break;
0852         }
0853     }
0854 
0855     // Catch the next resume event if some actions require it
0856     if (!m_pendingResumeFromIdleActions.isEmpty()) {
0857         KIdleTime::instance()->catchNextResumeEvent();
0858     }
0859 }
0860 
0861 void Core::onLidClosedChanged(bool closed)
0862 {
0863     Q_EMIT lidClosedChanged(closed);
0864 }
0865 
0866 void Core::onAboutToSuspend()
0867 {
0868     if (PowerDevilSettings::pausePlayersOnSuspend()) {
0869         qCDebug(POWERDEVIL) << "Pausing all media players before sleep";
0870 
0871         QDBusPendingCall listNamesCall = QDBusConnection::sessionBus().interface()->asyncCall(QStringLiteral("ListNames"));
0872         QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(listNamesCall, this);
0873         connect(callWatcher, &QDBusPendingCallWatcher::finished, this, [](QDBusPendingCallWatcher *watcher) {
0874             QDBusPendingReply<QStringList> reply = *watcher;
0875             watcher->deleteLater();
0876 
0877             if (reply.isError()) {
0878                 qCWarning(POWERDEVIL) << "Failed to fetch list of DBus service names for pausing players on entering sleep" << reply.error().message();
0879                 return;
0880             }
0881 
0882             const QStringList &services = reply.value();
0883             for (const QString &serviceName : services) {
0884                 if (!serviceName.startsWith(QLatin1String("org.mpris.MediaPlayer2."))) {
0885                     continue;
0886                 }
0887 
0888                 if (serviceName.startsWith(QLatin1String("org.mpris.MediaPlayer2.kdeconnect.mpris_"))) {
0889                     // This is actually a player on another device exposed by KDE Connect
0890                     // We don't want to pause it
0891                     // See https://bugs.kde.org/show_bug.cgi?id=427209
0892                     continue;
0893                 }
0894 
0895                 qCDebug(POWERDEVIL) << "Pausing media player with service name" << serviceName;
0896 
0897                 QDBusMessage pauseMsg = QDBusMessage::createMethodCall(serviceName,
0898                                                                        QStringLiteral("/org/mpris/MediaPlayer2"),
0899                                                                        QStringLiteral("org.mpris.MediaPlayer2.Player"),
0900                                                                        QStringLiteral("Pause"));
0901                 QDBusConnection::sessionBus().asyncCall(pauseMsg);
0902             }
0903        });
0904     }
0905 }
0906 
0907 void Core::registerActionTimeout(Action* action, int timeout)
0908 {
0909     // Register the timeout with KIdleTime
0910     int identifier = KIdleTime::instance()->addIdleTimeout(timeout);
0911 
0912     // Add the identifier to the action hash
0913     QList< int > timeouts = m_registeredActionTimeouts[action];
0914     timeouts.append(identifier);
0915     m_registeredActionTimeouts[action] = timeouts;
0916 }
0917 
0918 void Core::unregisterActionTimeouts(Action* action)
0919 {
0920     // Clear all timeouts from the action
0921     const QList< int > timeoutsToClean = m_registeredActionTimeouts[action];
0922 
0923     for (int id : timeoutsToClean) {
0924         KIdleTime::instance()->removeIdleTimeout(id);
0925     }
0926 
0927     m_registeredActionTimeouts.remove(action);
0928 }
0929 
0930 int Core::currentChargePercent() const
0931 {
0932     int chargePercent = 0;
0933     for (auto it = m_batteriesPercent.constBegin(); it != m_batteriesPercent.constEnd(); ++it) {
0934         chargePercent += it.value();
0935     }
0936     return chargePercent;
0937 }
0938 
0939 void Core::onResumingFromIdle()
0940 {
0941     KIdleTime::instance()->simulateUserActivity();
0942     // Wake up the actions in which an idle action was triggered
0943     std::for_each(m_pendingResumeFromIdleActions.cbegin(), m_pendingResumeFromIdleActions.cend(),
0944         std::mem_fn(&PowerDevil::Action::onWakeupFromIdle));
0945 
0946     m_pendingResumeFromIdleActions.clear();
0947 }
0948 
0949 void Core::onNotificationTimeout()
0950 {
0951     // cannot connect QTimer::singleShot directly to the other method
0952     onServiceRegistered(QString());
0953 }
0954 
0955 void Core::onServiceRegistered(const QString &service)
0956 {
0957     Q_UNUSED(service);
0958 
0959     if (m_notificationsReady) {
0960         return;
0961     }
0962 
0963     bool needsRefresh = false;
0964 
0965     // show warning about low batteries right on session startup, force it to show
0966     // by making sure the "old" percentage (that magic number) is always higher than the current one
0967     if (emitBatteryChargePercentNotification(currentChargePercent(), 1000)) {
0968         needsRefresh = true;
0969     }
0970 
0971     // now also emit notifications for all peripheral batteries
0972     for (auto it = m_peripheralBatteriesPercent.constBegin(), end = m_peripheralBatteriesPercent.constEnd(); it != end; ++it) {
0973         if (emitBatteryChargePercentNotification(it.value() /*currentPercent*/, 1000, it.key() /*udi*/)) {
0974             needsRefresh = true;
0975         }
0976     }
0977 
0978     // need to refresh status to prevent the notification from showing again when charge percentage changes
0979     if (needsRefresh) {
0980         refreshStatus();
0981     }
0982 
0983     m_notificationsReady = true;
0984 
0985     if (m_notificationsWatcher) {
0986         delete m_notificationsWatcher;
0987         m_notificationsWatcher = nullptr;
0988     }
0989 }
0990 
0991 void Core::readChargeThreshold()
0992 {
0993     KAuth::Action action(QStringLiteral("org.kde.powerdevil.chargethresholdhelper.getthreshold"));
0994     action.setHelperId(QStringLiteral("org.kde.powerdevil.chargethresholdhelper"));
0995     KAuth::ExecuteJob *job = action.execute();
0996     connect(job, &KJob::result, this, [this, job] {
0997         if (job->error()) {
0998             qCWarning(POWERDEVIL) << "org.kde.powerdevil.chargethresholdhelper.getthreshold failed" << job->errorText();
0999             return;
1000         }
1001 
1002         const auto data = job->data();
1003 
1004         const int chargeStartThreshold = data.value(QStringLiteral("chargeStartThreshold")).toInt();
1005         if (chargeStartThreshold != -1 && m_chargeStartThreshold != chargeStartThreshold) {
1006             m_chargeStartThreshold = chargeStartThreshold;
1007             Q_EMIT chargeStartThresholdChanged(chargeStartThreshold);
1008         }
1009 
1010         const int chargeStopThreshold = data.value(QStringLiteral("chargeStopThreshold")).toInt();
1011         if (chargeStopThreshold != -1 && m_chargeStopThreshold != chargeStopThreshold) {
1012             m_chargeStopThreshold = chargeStopThreshold;
1013             Q_EMIT chargeStopThresholdChanged(chargeStopThreshold);
1014         }
1015 
1016         qCDebug(POWERDEVIL) << "Charge thresholds: start at" << chargeStartThreshold << "- stop at" << chargeStopThreshold;
1017     });
1018     job->start();
1019 }
1020 
1021 BackendInterface* Core::backend()
1022 {
1023     return m_backend;
1024 }
1025 
1026 bool Core::isLidClosed() const
1027 {
1028     return m_backend->isLidClosed();
1029 }
1030 
1031 bool Core::isLidPresent() const
1032 {
1033     return m_backend->isLidPresent();
1034 }
1035 
1036 bool Core::hasDualGpu() const
1037 {
1038     return m_hasDualGpu;
1039 }
1040 
1041 int Core::chargeStartThreshold() const
1042 {
1043     return m_chargeStartThreshold;
1044 }
1045 
1046 int Core::chargeStopThreshold() const
1047 {
1048     return m_chargeStopThreshold;
1049 }
1050 
1051 uint Core::scheduleWakeup(const QString &service, const QDBusObjectPath &path, qint64 timeout)
1052 {
1053     ++m_lastWakeupCookie;
1054 
1055     int cookie = m_lastWakeupCookie;
1056     // if some one is trying to time travel, deny them
1057     if (timeout < QDateTime::currentSecsSinceEpoch()) {
1058         sendErrorReply(QDBusError::InvalidArgs, "You can not schedule wakeup in past");
1059     } else {
1060 #ifndef Q_OS_LINUX
1061         sendErrorReply(QDBusError::NotSupported, "Scheduled wakeups are available only on Linux platforms");
1062 #else
1063         WakeupInfo wakeup{ service, path, cookie, timeout };
1064         m_scheduledWakeups << wakeup;
1065         qCDebug(POWERDEVIL) << "Received request to wakeup at " << QDateTime::fromSecsSinceEpoch(timeout);
1066         resetAndScheduleNextWakeup();
1067 #endif
1068     }
1069     return cookie;
1070 }
1071 
1072 void Core::wakeup()
1073 {
1074     onResumingFromIdle();
1075     PowerDevil::Action *helperAction = ActionPool::instance()->loadAction(QStringLiteral("DPMSControl"), KConfigGroup(), this);
1076     if (helperAction) {
1077         QVariantMap args;
1078         // we pass empty string as type because when empty type is passed,
1079         // it turns screen on.
1080         args[QStringLiteral("Type")] = "";
1081         helperAction->trigger(args);
1082     }
1083 }
1084 
1085 void Core::clearWakeup(int cookie)
1086 {
1087     // if we do not have any timeouts return from here
1088     if (m_scheduledWakeups.isEmpty()) {
1089         return;
1090     }
1091 
1092     int oldListSize = m_scheduledWakeups.size();
1093 
1094     // depending on cookie, remove it from scheduled wakeups
1095     m_scheduledWakeups.erase(std::remove_if(m_scheduledWakeups.begin(), m_scheduledWakeups.end(), [cookie](WakeupInfo wakeup) {
1096         return wakeup.cookie == cookie;
1097     }), m_scheduledWakeups.end());
1098 
1099     if (oldListSize == m_scheduledWakeups.size()) {
1100         sendErrorReply(QDBusError::InvalidArgs, "Can not clear the invalid wakeup");
1101         return;
1102     }
1103 
1104     // reset timerfd
1105     resetAndScheduleNextWakeup();
1106 }
1107 
1108 qulonglong Core::batteryRemainingTime() const
1109 {
1110     return m_backend->batteryRemainingTime();
1111 }
1112 
1113 qulonglong Core::smoothedBatteryRemainingTime() const
1114 {
1115     return m_backend->smoothedBatteryRemainingTime();
1116 }
1117 
1118 uint Core::backendCapabilities()
1119 {
1120     return m_backend->capabilities();
1121 }
1122 
1123 void Core::resetAndScheduleNextWakeup()
1124 {
1125 
1126 #ifdef Q_OS_LINUX
1127     // first we sort the wakeup list
1128     std::sort(m_scheduledWakeups.begin(), m_scheduledWakeups.end(), [](const WakeupInfo& lhs, const WakeupInfo& rhs) 
1129     {
1130         return lhs.timeout < rhs.timeout;
1131     });
1132 
1133     // we don't want any of our wakeups to repeat'
1134     timespec interval = {0, 0};
1135     timespec nextWakeup;
1136     bool enableNotifier = false;
1137     // if we don't have any wakeups left, we call it a day and stop timer_fd
1138     if(m_scheduledWakeups.isEmpty()) {
1139         nextWakeup = {0, 0};
1140     } else {
1141         // now pick the first timeout from the list
1142         WakeupInfo wakeup = m_scheduledWakeups.first();
1143         nextWakeup = {wakeup.timeout, 0};
1144         enableNotifier = true;
1145     }
1146     if (m_timerFd != -1) {
1147         const itimerspec spec = {interval, nextWakeup};
1148         timerfd_settime(m_timerFd, TFD_TIMER_ABSTIME, &spec, nullptr);
1149     }
1150     m_timerFdSocketNotifier->setEnabled(enableNotifier);
1151 #endif
1152 }
1153 
1154 void Core::timerfdEventHandler()
1155 {
1156     // wakeup from the linux/rtc
1157 
1158     // Disable reading events from the timer_fd
1159     m_timerFdSocketNotifier->setEnabled(false);
1160 
1161     // At this point scheduled wakeup list should not be empty, but just in case
1162     if (m_scheduledWakeups.isEmpty()) {
1163         qWarning(POWERDEVIL) << "Wakeup was recieved but list is now empty! This should not happen!";
1164         return;
1165     }
1166 
1167     // first thing to do is, we pick the first wakeup from list
1168     WakeupInfo currentWakeup = m_scheduledWakeups.takeFirst();
1169 
1170     // Before doing anything further, lets set the next set of wakeup alarm
1171     resetAndScheduleNextWakeup();
1172 
1173     // Now current wakeup needs to be processed
1174     // prepare message for sending back to the consumer
1175     QDBusMessage msg = QDBusMessage::createMethodCall(currentWakeup.service, currentWakeup.path.path(),
1176                                                       QStringLiteral("org.kde.PowerManagement"), QStringLiteral("wakeupCallback"));
1177     msg << currentWakeup.cookie;
1178     // send it away
1179     QDBusConnection::sessionBus().call(msg, QDBus::NoBlock);
1180 }
1181 
1182 }