File indexing completed on 2024-04-28 05:36:15

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