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"