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