diff --git a/PowerDevilSettings.kcfg b/PowerDevilSettings.kcfg
index 0b82211d..76324f06 100644
--- a/PowerDevilSettings.kcfg
+++ b/PowerDevilSettings.kcfg
@@ -1,41 +1,48 @@
true
true
250
100
10
5
2
10
+
+
+ 0
+
+
+ 100
+
diff --git a/daemon/backends/CMakeLists.txt b/daemon/backends/CMakeLists.txt
index 05c4263e..802c3ad0 100644
--- a/daemon/backends/CMakeLists.txt
+++ b/daemon/backends/CMakeLists.txt
@@ -1,69 +1,76 @@
########################## UPower Backend #####################################
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/upower
${X11_INCLUDE_DIR}
${X11_Xrandr_INCLUDE_PATH})
set(powerdevilupowerbackend_SRCS
${CMAKE_CURRENT_BINARY_DIR}/../powerdevil_debug.cpp
upower/upowersuspendjob.cpp
upower/login1suspendjob.cpp
upower/powerdevilupowerbackend.cpp
upower/xrandrbrightness.cpp
upower/xrandrxcbhelper.cpp
upower/udevqtclient.cpp
upower/udevqtdevice.cpp
upower/ddcutilbrightness.cpp
)
set_source_files_properties(
${CMAKE_CURRENT_SOURCE_DIR}/upower/dbus/org.freedesktop.UPower.xml
${CMAKE_CURRENT_SOURCE_DIR}/upower/dbus/org.freedesktop.UPower.Device.xml
PROPERTIES NO_NAMESPACE TRUE)
qt5_add_dbus_interface(powerdevilupowerbackend_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/upower/dbus/org.freedesktop.UPower.xml
upower_interface)
qt5_add_dbus_interface(powerdevilupowerbackend_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/upower/dbus/org.freedesktop.UPower.Device.xml
upower_device_interface)
qt5_add_dbus_interface(powerdevilupowerbackend_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/upower/dbus/org.freedesktop.UPower.KbdBacklight.xml
upower_kbdbacklight_interface)
## backlight helper executable
add_executable(backlighthelper upower/backlighthelper.cpp ${CMAKE_CURRENT_BINARY_DIR}/../powerdevil_debug.cpp ${backlighthelper_mocs})
target_link_libraries(backlighthelper Qt5::Core KF5::AuthCore KF5::I18n)
install(TARGETS backlighthelper DESTINATION ${KAUTH_HELPER_INSTALL_DIR})
kauth_install_helper_files(backlighthelper org.kde.powerdevil.backlighthelper root)
kauth_install_actions(org.kde.powerdevil.backlighthelper ${CMAKE_CURRENT_SOURCE_DIR}/upower/backlight_helper_actions.actions)
## discrete gpu helper executable
add_executable(discretegpuhelper upower/discretegpuhelper.cpp ${CMAKE_CURRENT_BINARY_DIR}/../powerdevil_debug.cpp ${discretegpuhelper_mocs})
target_link_libraries(discretegpuhelper Qt5::Core KF5::AuthCore)
install(TARGETS discretegpuhelper DESTINATION ${KAUTH_HELPER_INSTALL_DIR})
kauth_install_helper_files(discretegpuhelper org.kde.powerdevil.discretegpuhelper root)
kauth_install_actions(org.kde.powerdevil.discretegpuhelper ${CMAKE_CURRENT_SOURCE_DIR}/upower/discretegpu_helper_actions.actions)
+## charge threshold helper executable
+add_executable(chargethresholdhelper upower/chargethresholdhelper.cpp ${CMAKE_CURRENT_BINARY_DIR}/../powerdevil_debug.cpp ${chargethresholdhelper_mocs})
+target_link_libraries(chargethresholdhelper Qt5::Core KF5::AuthCore)
+install(TARGETS chargethresholdhelper DESTINATION ${KAUTH_HELPER_INSTALL_DIR})
+kauth_install_helper_files(chargethresholdhelper org.kde.powerdevil.chargethresholdhelper root)
+kauth_install_actions(org.kde.powerdevil.chargethresholdhelper ${CMAKE_CURRENT_SOURCE_DIR}/upower/chargethreshold_helper_actions.actions)
+
add_library(powerdevilupowerbackend ${powerdevilupowerbackend_SRCS})
set_target_properties(powerdevilupowerbackend PROPERTIES PREFIX "")
target_link_libraries(powerdevilupowerbackend
Qt5::Widgets
KF5::AuthCore
KF5::ConfigCore
KF5::CoreAddons
KF5::DBusAddons
KF5::I18n
${UDEV_LIBS}
${X11_LIBRARIES}
${X11_Xrandr_LIB}
${XCB_XCB_LIBRARY}
${XCB_RANDR_LIBRARY}
powerdevilcore
)
if(DDCUTIL_FOUND)
target_link_libraries(powerdevilupowerbackend ${LIBDDCUTIL_LIBRARY})
endif()
install(TARGETS powerdevilupowerbackend DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/powerdevil)
diff --git a/daemon/backends/upower/chargethreshold_helper_actions.actions b/daemon/backends/upower/chargethreshold_helper_actions.actions
new file mode 100644
index 00000000..c9aa1257
--- /dev/null
+++ b/daemon/backends/upower/chargethreshold_helper_actions.actions
@@ -0,0 +1,16 @@
+[Domain]
+Name=KDE
+Icon=kde
+
+[org.kde.powerdevil.chargethresholdhelper.getthreshold]
+Name=Get current battery charge limit
+Description=System policies prevent you from getting the current battery charge limit.
+Policy=yes
+# FIXME whatever this means?
+PolicyInactive=yes
+
+[org.kde.powerdevil.chargethresholdhelper.setthreshold]
+Name=Set battery charge limit
+Description=System policies prevent you from setting a battery charge limit.
+Policy=yes
+PolicyInactive=yes
diff --git a/daemon/backends/upower/chargethresholdhelper.cpp b/daemon/backends/upower/chargethresholdhelper.cpp
new file mode 100644
index 00000000..374635bb
--- /dev/null
+++ b/daemon/backends/upower/chargethresholdhelper.cpp
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2020 Kai Uwe Broulik
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "chargethresholdhelper.h"
+
+#include
+
+#include
+
+#include
+#include
+
+static const QString s_powerSupplySysFsPath = QStringLiteral("/sys/class/power_supply");
+
+static const QString s_chargeStartThreshold = QStringLiteral("charge_start_threshold");
+static const QString s_chargeStopThreshold = QStringLiteral("charge_stop_threshold");
+
+ChargeThresholdHelper::ChargeThresholdHelper(QObject *parent)
+ : QObject(parent)
+{
+}
+
+static QStringList getBatteries()
+{
+ return QDir(s_powerSupplySysFsPath).entryList({QStringLiteral("BAT*")}, QDir::Dirs);
+}
+
+static QVector getThresholds(const QString &which)
+{
+ QVector thresholds;
+
+ const QStringList batteries = getBatteries();
+ for (const QString &battery : batteries) {
+ QFile file(s_powerSupplySysFsPath + QLatin1Char('/') + battery + QLatin1Char('/') + which);
+ if (!file.open(QIODevice::ReadOnly)) {
+ qWarning() << "Failed to open" << file.fileName() << "for reading";
+ continue;
+ }
+
+ int threshold = -1;
+ QTextStream stream(&file);
+ stream >> threshold;
+
+ if (threshold < 0 || threshold > 100) {
+ qWarning() << file.fileName() << "contains invalid threshold" << threshold;
+ continue;
+ }
+
+ thresholds.append(threshold);
+ }
+
+ return thresholds;
+}
+
+static bool setThresholds(const QString &which, int threshold)
+{
+ const QStringList batteries = getBatteries();
+ for (const QString &battery : batteries) {
+ QFile file(s_powerSupplySysFsPath + QLatin1Char('/') + battery + QLatin1Char('/') + which);
+ // TODO should we check the current value before writing the new one or is it clever
+ // enough not to shred some chip by writing the same thing again?
+ if (!file.open(QIODevice::WriteOnly)) {
+ qWarning() << "Failed to open" << file.fileName() << "for writing";
+ return false;
+ }
+
+ if (file.write(QByteArray::number(threshold)) == -1) {
+ qWarning() << "Failed to write threshold into" << file.fileName();
+ return false;
+ }
+ }
+
+ return true;
+}
+
+ActionReply ChargeThresholdHelper::getthreshold(const QVariantMap &args)
+{
+ Q_UNUSED(args);
+
+ const auto startThresholds = getThresholds(s_chargeStartThreshold);
+ const auto stopThresholds = getThresholds(s_chargeStopThreshold);
+
+ if (startThresholds.isEmpty() || stopThresholds.isEmpty() || startThresholds.count() != stopThresholds.count()) {
+ auto reply = ActionReply::HelperErrorReply();
+ reply.setErrorDescription(QStringLiteral("Charge thresholds not supported"));
+ return reply;
+ }
+
+ // In the rare case there are multiple batteries with varying charge thresholds, try to use something sensible
+ const int startThreshold = *std::max_element(startThresholds.begin(), startThresholds.end());
+ const int stopThreshold = *std::min_element(stopThresholds.begin(), stopThresholds.end());
+
+ ActionReply reply;
+ reply.setData({
+ {QStringLiteral("chargeStartThreshold"), startThreshold},
+ {QStringLiteral("chargeStopThreshold"), stopThreshold}
+ });
+ return reply;
+}
+
+ActionReply ChargeThresholdHelper::setthreshold(const QVariantMap &args)
+{
+ const int startThreshold = args.value(QStringLiteral("chargeStartThreshold")).toInt();
+ const int stopThreshold = args.value(QStringLiteral("chargeStopThreshold")).toInt();
+
+ if (startThreshold < 0
+ || startThreshold > 100
+ || stopThreshold < 0
+ || stopThreshold > 100
+ || startThreshold > stopThreshold) {
+ auto reply = ActionReply::HelperErrorReply(); // is there an "invalid arguments" error?
+ reply.setErrorDescription(QStringLiteral("Invalid thresholds provided"));
+ return reply;
+ }
+
+ if (!setThresholds(s_chargeStartThreshold, startThreshold)) {
+ auto reply = ActionReply::HelperErrorReply();
+ reply.setErrorDescription(QStringLiteral("Failed to write start charge threshold"));
+ return reply;
+ }
+
+ if (!setThresholds(s_chargeStopThreshold, stopThreshold)) {
+ auto reply = ActionReply::HelperErrorReply();
+ reply.setErrorDescription(QStringLiteral("Failed to write stop charge threshold"));
+ return reply;
+ }
+
+ return ActionReply();
+}
+
+KAUTH_HELPER_MAIN("org.kde.powerdevil.chargethresholdhelper", ChargeThresholdHelper)
diff --git a/daemon/backends/upower/chargethresholdhelper.h b/daemon/backends/upower/chargethresholdhelper.h
new file mode 100644
index 00000000..f505f008
--- /dev/null
+++ b/daemon/backends/upower/chargethresholdhelper.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2020 Kai Uwe Broulik
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#pragma once
+
+#include
+
+#include
+
+using namespace KAuth;
+
+class ChargeThresholdHelper : public QObject
+{
+ Q_OBJECT
+public:
+ explicit ChargeThresholdHelper(QObject *parent = nullptr);
+
+public Q_SLOTS:
+ ActionReply getthreshold(const QVariantMap &args);
+ ActionReply setthreshold(const QVariantMap &args);
+
+};
diff --git a/daemon/org.kde.Solid.PowerManagement.xml b/daemon/org.kde.Solid.PowerManagement.xml
index af584080..dc514823 100644
--- a/daemon/org.kde.Solid.PowerManagement.xml
+++ b/daemon/org.kde.Solid.PowerManagement.xml
@@ -1,52 +1,58 @@
+
+
+
+
+
+
diff --git a/daemon/powerdevilcore.cpp b/daemon/powerdevilcore.cpp
index 26a16aa6..fa80853c 100644
--- a/daemon/powerdevilcore.cpp
+++ b/daemon/powerdevilcore.cpp
@@ -1,938 +1,1004 @@
/***************************************************************************
* Copyright (C) 2010 by Dario Freddi *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
***************************************************************************/
#include "powerdevilcore.h"
#include "PowerDevilSettings.h"
#include "powerdevilaction.h"
#include "powerdevilactionpool.h"
#include "powerdevilbackendinterface.h"
#include "powerdevilpolicyagent.h"
#include "powerdevilprofilegenerator.h"
#include "powerdevil_debug.h"
#include "actions/bundled/suspendsession.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace PowerDevil
{
Core::Core(QObject* parent)
: QObject(parent)
, m_hasDualGpu(false)
, m_backend(nullptr)
, m_notificationsWatcher(nullptr)
, m_criticalBatteryTimer(new QTimer(this))
, m_activityConsumer(new KActivities::Consumer(this))
, m_pendingWakeupEvent(true)
{
KAuth::Action discreteGpuAction(QStringLiteral("org.kde.powerdevil.discretegpuhelper.hasdualgpu"));
discreteGpuAction.setHelperId(QStringLiteral("org.kde.powerdevil.discretegpuhelper"));
KAuth::ExecuteJob *discreteGpuJob = discreteGpuAction.execute();
connect(discreteGpuJob, &KJob::result, this, [this, discreteGpuJob] {
if (discreteGpuJob->error()) {
qCWarning(POWERDEVIL) << "org.kde.powerdevil.discretegpuhelper.hasdualgpu failed";
qCDebug(POWERDEVIL) << discreteGpuJob->errorText();
return;
}
m_hasDualGpu = discreteGpuJob->data()[QStringLiteral("hasdualgpu")].toBool();
});
discreteGpuJob->start();
+
+ readChargeThreshold();
}
Core::~Core()
{
qCDebug(POWERDEVIL) << "Core unloading";
// Unload all actions before exiting, and clear the cache
ActionPool::instance()->unloadAllActiveActions();
ActionPool::instance()->clearCache();
}
void Core::loadCore(BackendInterface* backend)
{
if (!backend) {
return;
}
m_backend = backend;
// Async backend init - so that KDED gets a bit of a speed up
qCDebug(POWERDEVIL) << "Core loaded, initializing backend";
connect(m_backend, SIGNAL(backendReady()), this, SLOT(onBackendReady()));
m_backend->init();
}
void Core::onBackendReady()
{
qCDebug(POWERDEVIL) << "Backend ready, KDE Power Management system initialized";
m_profilesConfig = KSharedConfig::openConfig(QStringLiteral("powermanagementprofilesrc"), KConfig::CascadeConfig);
QStringList groups = m_profilesConfig->groupList();
// the "migration" key is for shortcuts migration in added by migratePre512KeyboardShortcuts
// and as such our configuration would never be considered empty, ignore it!
groups.removeOne(QStringLiteral("migration"));
// Is it brand new?
if (groups.isEmpty()) {
// Generate defaults
qCDebug(POWERDEVIL) << "Generating a default configuration";
bool toRam = m_backend->supportedSuspendMethods() & PowerDevil::BackendInterface::ToRam;
bool toDisk = m_backend->supportedSuspendMethods() & PowerDevil::BackendInterface::ToDisk;
ProfileGenerator::generateProfiles(toRam, toDisk);
m_profilesConfig->reparseConfiguration();
}
// Get the battery devices ready
{
using namespace Solid;
connect(DeviceNotifier::instance(), SIGNAL(deviceAdded(QString)),
this, SLOT(onDeviceAdded(QString)));
connect(DeviceNotifier::instance(), SIGNAL(deviceRemoved(QString)),
this, SLOT(onDeviceRemoved(QString)));
// Force the addition of already existent batteries
const auto devices = Device::listFromType(DeviceInterface::Battery, QString());
for (const Device &device : devices) {
onDeviceAdded(device.udi());
}
}
connect(m_backend, SIGNAL(acAdapterStateChanged(PowerDevil::BackendInterface::AcAdapterState)),
this, SLOT(onAcAdapterStateChanged(PowerDevil::BackendInterface::AcAdapterState)));
connect(m_backend, SIGNAL(batteryRemainingTimeChanged(qulonglong)),
this, SLOT(onBatteryRemainingTimeChanged(qulonglong)));
connect(m_backend, SIGNAL(lidClosedChanged(bool)),
this, SLOT(onLidClosedChanged(bool)));
connect(m_backend, &BackendInterface::aboutToSuspend,
this, &Core::onAboutToSuspend);
connect(KIdleTime::instance(), SIGNAL(timeoutReached(int,int)),
this, SLOT(onKIdleTimeoutReached(int,int)));
connect(KIdleTime::instance(), SIGNAL(resumingFromIdle()),
this, SLOT(onResumingFromIdle()));
connect(m_activityConsumer, &KActivities::Consumer::currentActivityChanged, this, [this]() {
loadProfile();
});
// Set up the policy agent
PowerDevil::PolicyAgent::instance()->init();
// When inhibitions change, simulate user activity, see Bug 315438
connect(PowerDevil::PolicyAgent::instance(), &PowerDevil::PolicyAgent::unavailablePoliciesChanged, this,
[](PowerDevil::PolicyAgent::RequiredPolicies newPolicies) {
Q_UNUSED(newPolicies);
KIdleTime::instance()->simulateUserActivity();
});
// Bug 354250: Simulate user activity when session becomes inactive,
// this keeps us from sending the computer to sleep when switching to an idle session.
// (this is just being lazy as it will result in us clearing everything
connect(PowerDevil::PolicyAgent::instance(), &PowerDevil::PolicyAgent::sessionActiveChanged, this, [this](bool active) {
if (active) {
// force reload profile so all actions re-register their idle timeouts
loadProfile(true /*force*/);
} else {
// Bug 354250: Keep us from sending the computer to sleep when switching
// to an idle session by removing all idle timeouts
KIdleTime::instance()->removeAllIdleTimeouts();
m_registeredActionTimeouts.clear();
}
});
// Initialize the action pool, which will also load the needed startup actions.
PowerDevil::ActionPool::instance()->init(this);
// Set up the critical battery timer
m_criticalBatteryTimer->setSingleShot(true);
m_criticalBatteryTimer->setInterval(60000);
connect(m_criticalBatteryTimer, SIGNAL(timeout()), this, SLOT(onCriticalBatteryTimerExpired()));
// wait until the notification system is set up before firing notifications
// to avoid them showing ontop of ksplash...
if (QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.freedesktop.Notifications"))) {
onServiceRegistered(QString());
} else {
m_notificationsWatcher = new QDBusServiceWatcher(QStringLiteral("org.freedesktop.Notifications"),
QDBusConnection::sessionBus(),
QDBusServiceWatcher::WatchForRegistration,
this);
connect(m_notificationsWatcher, SIGNAL(serviceRegistered(QString)), this, SLOT(onServiceRegistered(QString)));
// ...but fire them after 30s nonetheless to ensure they've been shown
QTimer::singleShot(30000, this, SLOT(onNotificationTimeout()));
}
// All systems up Houston, let's go!
Q_EMIT coreReady();
refreshStatus();
}
bool Core::isActionSupported(const QString& actionName)
{
Action *action = ActionPool::instance()->loadAction(actionName, KConfigGroup(), this);
if (!action) {
return false;
} else {
return action->isSupported();
}
}
void Core::refreshStatus()
{
/* The configuration could have changed if this function was called, so
* let's resync it.
*/
reparseConfiguration();
loadProfile(true);
}
void Core::reparseConfiguration()
{
PowerDevilSettings::self()->load();
m_profilesConfig->reparseConfiguration();
// Config reloaded
Q_EMIT configurationReloaded();
// Check if critical threshold might have changed and cancel the timer if necessary.
if (m_criticalBatteryTimer->isActive() && currentChargePercent() > PowerDevilSettings::batteryCriticalLevel()) {
m_criticalBatteryTimer->stop();
if (m_criticalBatteryNotification) {
m_criticalBatteryNotification->close();
}
}
if (m_lowBatteryNotification && currentChargePercent() > PowerDevilSettings::batteryLowLevel()) {
m_lowBatteryNotification->close();
}
+
+ if (m_chargeStartThreshold != PowerDevilSettings::chargeStartThreshold()
+ || m_chargeStopThreshold != PowerDevilSettings::chargeStopThreshold()) {
+ writeChargeThreshold();
+ }
}
QString Core::currentProfile() const
{
return m_currentProfile;
}
void Core::loadProfile(bool force)
{
QString profileId;
// Policy check
if (PolicyAgent::instance()->requirePolicyCheck(PolicyAgent::ChangeProfile) != PolicyAgent::None) {
qCDebug(POWERDEVIL) << "Policy Agent prevention: on";
return;
}
KConfigGroup config;
// Check the activity in which we are in
QString activity = m_activityConsumer->currentActivity();
qCDebug(POWERDEVIL) << "Currently using activity " << activity;
KConfigGroup activitiesConfig(m_profilesConfig, "Activities");
qCDebug(POWERDEVIL) << activitiesConfig.groupList() << activitiesConfig.keyList();
// Are we mirroring an activity?
if (activitiesConfig.group(activity).readEntry("mode", "None") == QStringLiteral("ActLike") &&
activitiesConfig.group(activity).readEntry("actLike", QString()) != QStringLiteral("AC") &&
activitiesConfig.group(activity).readEntry("actLike", QString()) != QStringLiteral("Battery") &&
activitiesConfig.group(activity).readEntry("actLike", QString()) != QStringLiteral("LowBattery")) {
// Yes, let's use that then
activity = activitiesConfig.group(activity).readEntry("actLike", QString());
qCDebug(POWERDEVIL) << "Activity is a mirror";
}
KConfigGroup activityConfig = activitiesConfig.group(activity);
qCDebug(POWERDEVIL) << activityConfig.groupList() << activityConfig.keyList();
// See if this activity has priority
if (activityConfig.readEntry("mode", "None") == QStringLiteral("SeparateSettings")) {
// Prioritize this profile over anything
config = activityConfig.group("SeparateSettings");
qCDebug(POWERDEVIL) << "Activity is enforcing a different profile";
profileId = activity;
} else {
// It doesn't, let's load the current state's profile
if (m_batteriesPercent.isEmpty()) {
qCDebug(POWERDEVIL) << "No batteries found, loading AC";
profileId = QStringLiteral("AC");
} else if (activityConfig.readEntry("mode", "None") == QStringLiteral("ActLike")) {
if (activityConfig.readEntry("actLike", QString()) == QStringLiteral("AC") ||
activityConfig.readEntry("actLike", QString()) == QStringLiteral("Battery") ||
activityConfig.readEntry("actLike", QString()) == QStringLiteral("LowBattery")) {
// Same as above, but with an existing profile
config = m_profilesConfig.data()->group(activityConfig.readEntry("actLike", QString()));
profileId = activityConfig.readEntry("actLike", QString());
qCDebug(POWERDEVIL) << "Activity is mirroring a different profile";
}
} else {
// Compute the previous and current global percentage
const int percent = currentChargePercent();
if (backend()->acAdapterState() == BackendInterface::Plugged) {
profileId = QStringLiteral("AC");
qCDebug(POWERDEVIL) << "Loading profile for plugged AC";
} else if (percent <= PowerDevilSettings::batteryLowLevel()) {
profileId = QStringLiteral("LowBattery");
qCDebug(POWERDEVIL) << "Loading profile for low battery";
} else {
profileId = QStringLiteral("Battery");
qCDebug(POWERDEVIL) << "Loading profile for unplugged AC";
}
}
config = m_profilesConfig.data()->group(profileId);
qCDebug(POWERDEVIL) << "Activity is not forcing a profile";
}
// Release any special inhibitions
{
QHash::iterator i = m_sessionActivityInhibit.begin();
while (i != m_sessionActivityInhibit.end()) {
PolicyAgent::instance()->ReleaseInhibition(i.value());
i = m_sessionActivityInhibit.erase(i);
}
i = m_screenActivityInhibit.begin();
while (i != m_screenActivityInhibit.end()) {
PolicyAgent::instance()->ReleaseInhibition(i.value());
i = m_screenActivityInhibit.erase(i);
}
}
if (!config.isValid()) {
qCWarning(POWERDEVIL) << "Profile " << profileId << "has been selected but does not exist.";
return;
}
// Check: do we need to change profile at all?
if (m_currentProfile == profileId && !force) {
// No, let's leave things as they are
qCDebug(POWERDEVIL) << "Skipping action reload routine as profile has not changed";
// Do we need to force a wakeup?
if (m_pendingWakeupEvent) {
// Fake activity at this stage, when no timeouts are registered
onResumingFromIdle();
m_pendingWakeupEvent = false;
}
} else {
// First of all, let's clean the old actions. This will also call the onProfileUnload callback
ActionPool::instance()->unloadAllActiveActions();
// Do we need to force a wakeup?
if (m_pendingWakeupEvent) {
// Fake activity at this stage, when no timeouts are registered
onResumingFromIdle();
m_pendingWakeupEvent = false;
}
// Cool, now let's load the needed actions
const auto groupList = config.groupList();
for (const QString &actionName : groupList) {
Action *action = ActionPool::instance()->loadAction(actionName, config.group(actionName), this);
if (action) {
action->onProfileLoad();
} else {
// Ouch, error. But let's just warn and move on anyway
//TODO Maybe Remove from the configuration if unsupported
qCWarning(POWERDEVIL) << "The profile " << profileId << "tried to activate"
<< actionName << "a non-existent action. This is usually due to an installation problem,"
" a configuration problem, or because the action is not supported";
}
}
// We are now on a different profile
m_currentProfile = profileId;
Q_EMIT profileChanged(m_currentProfile);
}
// Now... any special behaviors we'd like to consider?
if (activityConfig.readEntry("mode", "None") == QStringLiteral("SpecialBehavior")) {
qCDebug(POWERDEVIL) << "Activity has special behaviors";
KConfigGroup behaviorGroup = activityConfig.group("SpecialBehavior");
if (behaviorGroup.readEntry("performAction", false)) {
// Let's override the configuration for this action at all times
ActionPool::instance()->loadAction(QStringLiteral("SuspendSession"), behaviorGroup.group("ActionConfig"), this);
qCDebug(POWERDEVIL) << "Activity overrides suspend session action"; // debug hence not sleep
}
if (behaviorGroup.readEntry("noSuspend", false)) {
qCDebug(POWERDEVIL) << "Activity triggers a suspend inhibition"; // debug hence not sleep
// Trigger a special inhibition - if we don't have one yet
if (!m_sessionActivityInhibit.contains(activity)) {
int cookie =
PolicyAgent::instance()->AddInhibition(PolicyAgent::InterruptSession, i18n("Activity Manager"),
i18n("This activity's policies prevent the system from going to sleep"));
m_sessionActivityInhibit.insert(activity, cookie);
}
}
if (behaviorGroup.readEntry("noScreenManagement", false)) {
qCDebug(POWERDEVIL) << "Activity triggers a screen management inhibition";
// Trigger a special inhibition - if we don't have one yet
if (!m_screenActivityInhibit.contains(activity)) {
int cookie =
PolicyAgent::instance()->AddInhibition(PolicyAgent::ChangeScreenSettings, i18n("Activity Manager"),
i18n("This activity's policies prevent screen power management"));
m_screenActivityInhibit.insert(activity, cookie);
}
}
}
// If the lid is closed, retrigger the lid close signal
// so that "switching profile then closing the lid" has the same result as
// "closing lid then switching profile".
if (m_backend->isLidClosed()) {
Q_EMIT m_backend->buttonPressed(PowerDevil::BackendInterface::LidClose);
}
}
void Core::onDeviceAdded(const QString &udi)
{
if (m_batteriesPercent.contains(udi) || m_peripheralBatteriesPercent.contains(udi)) {
// We already know about this device
return;
}
using namespace Solid;
Device device(udi);
Battery *b = qobject_cast(device.asDeviceInterface(DeviceInterface::Battery));
if (!b) {
return;
}
connect(b, &Battery::chargePercentChanged, this, &Core::onBatteryChargePercentChanged);
connect(b, &Battery::chargeStateChanged, this, &Core::onBatteryChargeStateChanged);
qCDebug(POWERDEVIL) << "Battery with UDI" << udi << "was detected";
if (b->isPowerSupply()) {
m_batteriesPercent[udi] = b->chargePercent();
m_batteriesCharged[udi] = (b->chargeState() == Solid::Battery::FullyCharged);
+
+ // Apply charge limit to the new battery, too
+ writeChargeThreshold();
} else { // non-power supply batteries are treated separately
m_peripheralBatteriesPercent[udi] = b->chargePercent();
// notify the user about the empty mouse/keyboard when plugging it in; don't when
// notifications aren't ready yet so we avoid showing them ontop of ksplash;
// also we'll notify about all devices when notifications are ready anyway
if (m_notificationsReady) {
emitBatteryChargePercentNotification(b->chargePercent(), 1000 /* so current is always lower than previous */, udi);
}
}
// If a new battery has been added, let's clear some pending suspend actions if the new global batteries percentage is
// higher than the battery critical level. (See bug 329537)
if (m_lowBatteryNotification && currentChargePercent() > PowerDevilSettings::batteryLowLevel()) {
m_lowBatteryNotification->close();
}
if (m_criticalBatteryTimer->isActive() && currentChargePercent() > PowerDevilSettings::batteryCriticalLevel()) {
m_criticalBatteryTimer->stop();
if (m_criticalBatteryNotification) {
m_criticalBatteryNotification->close();
}
emitRichNotification(QStringLiteral("pluggedin"),
i18n("Extra Battery Added"),
i18n("The computer will no longer go to sleep."));
}
}
void Core::onDeviceRemoved(const QString &udi)
{
if (!m_batteriesPercent.contains(udi) && !m_peripheralBatteriesPercent.contains(udi)) {
// We don't know about this device
return;
}
using namespace Solid;
Device device(udi);
Battery *b = qobject_cast(device.asDeviceInterface(DeviceInterface::Battery));
disconnect(b, &Battery::chargePercentChanged, this, &Core::onBatteryChargePercentChanged);
disconnect(b, &Battery::chargeStateChanged, this, &Core::onBatteryChargeStateChanged);
qCDebug(POWERDEVIL) << "Battery with UDI" << udi << "has been removed";
m_batteriesPercent.remove(udi);
m_peripheralBatteriesPercent.remove(udi);
m_batteriesCharged.remove(udi);
}
void Core::emitNotification(const QString &eventId, const QString &title, const QString &message, const QString &iconName)
{
KNotification::event(eventId, title, message, iconName, nullptr, KNotification::CloseOnTimeout, QStringLiteral("powerdevil"));
}
void Core::emitRichNotification(const QString &evid, const QString &title, const QString &message)
{
KNotification::event(evid, title, message, QPixmap(),
nullptr, KNotification::CloseOnTimeout, QStringLiteral("powerdevil"));
}
bool Core::emitBatteryChargePercentNotification(int currentPercent, int previousPercent, const QString &udi)
{
using namespace Solid;
Device device(udi);
Battery *b = qobject_cast(device.asDeviceInterface(DeviceInterface::Battery));
if (b && !b->isPowerSupply()) {
// if you leave the device out of reach or it has not been initialized yet
// it won't be "there" and report 0%, don't show anything in this case
if (!b->isPresent() || b->chargePercent() == 0) {
return false;
}
// Bluetooth devices don't report charge state, so it's "NoCharge" in all cases for them
if (b->chargeState() != Battery::Discharging && b->chargeState() != Battery::NoCharge) {
return false;
}
if (currentPercent <= PowerDevilSettings::peripheralBatteryLowLevel() &&
previousPercent > PowerDevilSettings::peripheralBatteryLowLevel()) {
QString name = device.product();
if (!device.vendor().isEmpty()) {
name = i18nc("%1 is vendor name, %2 is product name", "%1 %2", device.vendor(), device.product());
}
QString title;
QString msg;
QString icon;
switch(b->type()) {
case Battery::MouseBattery:
title = i18n("Mouse Battery Low (%1% Remaining)", currentPercent);
msg = i18nc("Placeholder is device name",
"The battery in (\"%1\") is running low, and the device may turn off at any time. "
"Please recharge or replace the battery.", name);
icon = QStringLiteral("input-mouse");
break;
case Battery::KeyboardBattery:
title = i18n("Keyboard Battery Low (%1% Remaining)", currentPercent);
msg = i18nc("Placeholder is device name",
"The battery in (\"%1\") is running low, and the device may turn off at any time. "
"Please recharge or replace the battery.", name);
icon = QStringLiteral("input-keyboard");
break;
case Battery::BluetoothBattery:
title = i18n("Bluetooth Device Battery Low (%1% Remaining)", currentPercent);
msg = i18nc("Placeholder is device name",
"The battery in Bluetooth device \"%1\" is running low, and the device may turn off at any time. "
"Please recharge or replace the battery.", name);
icon = QStringLiteral("preferences-system-bluetooth");
break;
default:
title = i18nc("The battery in an external device", "Device Battery Low (%1% Remaining)", currentPercent);
msg = i18nc("Placeholder is device name",
"The battery in (\"%1\") is running low, and the device may turn off at any time. "
"Please recharge or replace the battery.", name);
icon = QStringLiteral("battery-caution");
break;
}
emitNotification(QStringLiteral("lowperipheralbattery"), title, msg, icon);
return true;
}
return false;
}
if (m_backend->acAdapterState() == BackendInterface::Plugged) {
return false;
}
if (currentPercent <= PowerDevilSettings::batteryCriticalLevel() &&
previousPercent > PowerDevilSettings::batteryCriticalLevel()) {
handleCriticalBattery(currentPercent);
return true;
} else if (currentPercent <= PowerDevilSettings::batteryLowLevel() &&
previousPercent > PowerDevilSettings::batteryLowLevel()) {
handleLowBattery(currentPercent);
return true;
}
return false;
}
void Core::handleLowBattery(int percent)
{
if (m_lowBatteryNotification) {
return;
}
m_lowBatteryNotification = new KNotification(QStringLiteral("lowbattery"), KNotification::Persistent, nullptr);
m_lowBatteryNotification->setComponentName(QStringLiteral("powerdevil"));
m_lowBatteryNotification->setTitle(i18n("Battery Low (%1% Remaining)", percent));
m_lowBatteryNotification->setText(i18n("Battery running low - to continue using your computer, plug it in or shut it down and change the battery."));
m_lowBatteryNotification->setUrgency(KNotification::CriticalUrgency);
m_lowBatteryNotification->sendEvent();
}
void Core::handleCriticalBattery(int percent)
{
if (m_lowBatteryNotification) {
m_lowBatteryNotification->close();
}
// no parent, but it won't leak, since it will be closed both in case of timeout or direct action
m_criticalBatteryNotification = new KNotification(QStringLiteral("criticalbattery"), KNotification::Persistent, nullptr);
m_criticalBatteryNotification->setComponentName(QStringLiteral("powerdevil"));
m_criticalBatteryNotification->setTitle(i18n("Battery Critical (%1% Remaining)", percent));
const QStringList actions = {i18nc("Cancel timeout that will automatically put system to sleep because of low battery", "Cancel")};
connect(m_criticalBatteryNotification.data(), &KNotification::action1Activated, this, [this] {
m_criticalBatteryTimer->stop();
m_criticalBatteryNotification->close();
});
switch (PowerDevilSettings::batteryCriticalAction()) {
case PowerDevil::BundledActions::SuspendSession::ShutdownMode:
m_criticalBatteryNotification->setText(i18n("Battery level critical. Your computer will shut down in 60 seconds."));
m_criticalBatteryNotification->setActions(actions);
m_criticalBatteryTimer->start();
break;
case PowerDevil::BundledActions::SuspendSession::ToDiskMode:
m_criticalBatteryNotification->setText(i18n("Battery level critical. Your computer will enter hibernation mode in 60 seconds."));
m_criticalBatteryNotification->setActions(actions);
m_criticalBatteryTimer->start();
break;
case PowerDevil::BundledActions::SuspendSession::ToRamMode:
m_criticalBatteryNotification->setText(i18n("Battery level critical. Your computer will go to sleep in 60 seconds."));
m_criticalBatteryNotification->setActions(actions);
m_criticalBatteryTimer->start();
break;
default:
m_criticalBatteryNotification->setText(i18n("Battery level critical. Please save your work."));
// no timer, no actions
break;
}
m_criticalBatteryNotification->sendEvent();
}
void Core::onAcAdapterStateChanged(PowerDevil::BackendInterface::AcAdapterState state)
{
qCDebug(POWERDEVIL);
// Post request for faking an activity event - usually adapters don't plug themselves out :)
m_pendingWakeupEvent = true;
loadProfile();
if (state == BackendInterface::Plugged) {
// If the AC Adaptor has been plugged in, let's clear some pending suspend actions
if (m_lowBatteryNotification) {
m_lowBatteryNotification->close();
}
if (m_criticalBatteryTimer->isActive()) {
m_criticalBatteryTimer->stop();
if (m_criticalBatteryNotification) {
m_criticalBatteryNotification->close();
}
emitRichNotification(QStringLiteral("pluggedin"),
i18n("AC Adapter Plugged In"),
i18n("The computer will no longer go to sleep."));
} else {
emitRichNotification(QStringLiteral("pluggedin"), i18n("Running on AC power"), i18n("The power adapter has been plugged in."));
}
} else if (state == BackendInterface::Unplugged) {
emitRichNotification(QStringLiteral("unplugged"), i18n("Running on Battery Power"), i18n("The power adapter has been unplugged."));
}
}
void Core::onBatteryChargePercentChanged(int percent, const QString &udi)
{
if (m_peripheralBatteriesPercent.contains(udi)) {
const int previousPercent = m_peripheralBatteriesPercent.value(udi);
m_peripheralBatteriesPercent[udi] = percent;
if (percent < previousPercent) {
emitBatteryChargePercentNotification(percent, previousPercent, udi);
}
return;
}
// Compute the previous and current global percentage
const int previousPercent = currentChargePercent();
const int currentPercent = previousPercent - (m_batteriesPercent[udi] - percent);
// Update the battery percentage
m_batteriesPercent[udi] = percent;
if (currentPercent < previousPercent) {
if (emitBatteryChargePercentNotification(currentPercent, previousPercent, udi)) {
// Only refresh status if a notification has actually been emitted
loadProfile();
}
}
}
void Core::onBatteryChargeStateChanged(int state, const QString &udi)
{
if (!m_batteriesCharged.contains(udi)) {
return;
}
bool previousCharged = true;
for (auto i = m_batteriesCharged.constBegin(); i != m_batteriesCharged.constEnd(); ++i) {
if (!i.value()) {
previousCharged = false;
break;
}
}
m_batteriesCharged[udi] = (state == Solid::Battery::FullyCharged);
if (m_backend->acAdapterState() != BackendInterface::Plugged) {
return;
}
bool currentCharged = true;
for (auto i = m_batteriesCharged.constBegin(); i != m_batteriesCharged.constEnd(); ++i) {
if (!i.value()) {
currentCharged = false;
break;
}
}
if (!previousCharged && currentCharged) {
emitRichNotification(QStringLiteral("fullbattery"), i18n("Charging Complete"), i18n("Battery now fully charged."));
loadProfile();
}
}
void Core::onCriticalBatteryTimerExpired()
{
if (m_criticalBatteryNotification) {
m_criticalBatteryNotification->close();
}
// Do that only if we're not on AC
if (m_backend->acAdapterState() == BackendInterface::Unplugged) {
// We consider this as a very special button
PowerDevil::Action *helperAction = ActionPool::instance()->loadAction(QStringLiteral("HandleButtonEvents"), KConfigGroup(), this);
if (helperAction) {
QVariantMap args;
args[QStringLiteral("Button")] = 32;
args[QStringLiteral("Type")] = QVariant::fromValue(PowerDevilSettings::batteryCriticalAction());
args[QStringLiteral("Explicit")] = true;
helperAction->trigger(args);
}
}
}
void Core::onBatteryRemainingTimeChanged(qulonglong time)
{
Q_EMIT batteryRemainingTimeChanged(time);
}
void Core::onKIdleTimeoutReached(int identifier, int msec)
{
// Find which action(s) requested this idle timeout
for (auto i = m_registeredActionTimeouts.constBegin(), end = m_registeredActionTimeouts.constEnd(); i != end; ++i) {
if (i.value().contains(identifier)) {
i.key()->onIdleTimeout(msec);
// And it will need to be awaken
m_pendingResumeFromIdleActions.insert(i.key());
break;
}
}
// Catch the next resume event if some actions require it
if (!m_pendingResumeFromIdleActions.isEmpty()) {
KIdleTime::instance()->catchNextResumeEvent();
}
}
void Core::onLidClosedChanged(bool closed)
{
Q_EMIT lidClosedChanged(closed);
}
void Core::onAboutToSuspend()
{
if (PowerDevilSettings::pausePlayersOnSuspend()) {
qCDebug(POWERDEVIL) << "Pausing all media players before sleep";
QDBusPendingCall listNamesCall = QDBusConnection::sessionBus().interface()->asyncCall(QStringLiteral("ListNames"));
QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(listNamesCall, this);
connect(callWatcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) {
QDBusPendingReply reply = *watcher;
watcher->deleteLater();
if (reply.isError()) {
qCWarning(POWERDEVIL) << "Failed to fetch list of DBus service names for pausing players on entering sleep" << reply.error().message();
return;
}
const QStringList &services = reply.value();
for (const QString &serviceName : services) {
if (!serviceName.startsWith(QLatin1String("org.mpris.MediaPlayer2."))) {
continue;
}
qCDebug(POWERDEVIL) << "Pausing media player with service name" << serviceName;
QDBusMessage pauseMsg = QDBusMessage::createMethodCall(serviceName,
QStringLiteral("/org/mpris/MediaPlayer2"),
QStringLiteral("org.mpris.MediaPlayer2.Player"),
QStringLiteral("Pause"));
QDBusConnection::sessionBus().asyncCall(pauseMsg);
}
});
}
}
void Core::registerActionTimeout(Action* action, int timeout)
{
// Register the timeout with KIdleTime
int identifier = KIdleTime::instance()->addIdleTimeout(timeout);
// Add the identifier to the action hash
QList< int > timeouts = m_registeredActionTimeouts[action];
timeouts.append(identifier);
m_registeredActionTimeouts[action] = timeouts;
}
void Core::unregisterActionTimeouts(Action* action)
{
// Clear all timeouts from the action
const QList< int > timeoutsToClean = m_registeredActionTimeouts[action];
for (int id : timeoutsToClean) {
KIdleTime::instance()->removeIdleTimeout(id);
}
m_registeredActionTimeouts.remove(action);
}
int Core::currentChargePercent() const
{
int chargePercent = 0;
for (auto it = m_batteriesPercent.constBegin(); it != m_batteriesPercent.constEnd(); ++it) {
chargePercent += it.value();
}
return chargePercent;
}
void Core::onResumingFromIdle()
{
KIdleTime::instance()->simulateUserActivity();
// Wake up the actions in which an idle action was triggered
std::for_each(m_pendingResumeFromIdleActions.cbegin(), m_pendingResumeFromIdleActions.cend(),
std::mem_fn(&PowerDevil::Action::onWakeupFromIdle));
m_pendingResumeFromIdleActions.clear();
}
void Core::onNotificationTimeout()
{
// cannot connect QTimer::singleShot directly to the other method
onServiceRegistered(QString());
}
void Core::onServiceRegistered(const QString &service)
{
Q_UNUSED(service);
if (m_notificationsReady) {
return;
}
bool needsRefresh = false;
// show warning about low batteries right on session startup, force it to show
// by making sure the "old" percentage (that magic number) is always higher than the current one
if (emitBatteryChargePercentNotification(currentChargePercent(), 1000)) {
needsRefresh = true;
}
// now also emit notifications for all peripheral batteries
for (auto it = m_peripheralBatteriesPercent.constBegin(), end = m_peripheralBatteriesPercent.constEnd(); it != end; ++it) {
if (emitBatteryChargePercentNotification(it.value() /*currentPercent*/, 1000, it.key() /*udi*/)) {
needsRefresh = true;
}
}
// need to refresh status to prevent the notification from showing again when charge percentage changes
if (needsRefresh) {
refreshStatus();
}
m_notificationsReady = true;
if (m_notificationsWatcher) {
delete m_notificationsWatcher;
m_notificationsWatcher = nullptr;
}
}
+void Core::onChargeThresholdChanged(int start, int stop)
+{
+ m_chargeStartThreshold = start;
+
+ if (m_chargeStopThreshold != stop) {
+ m_chargeStopThreshold = stop;
+ Q_EMIT chargeStopThresholdChanged(stop);
+ }
+}
+
+void Core::readChargeThreshold()
+{
+ KAuth::Action action(QStringLiteral("org.kde.powerdevil.chargethresholdhelper.getthreshold"));
+ action.setHelperId(QStringLiteral("org.kde.powerdevil.chargethresholdhelper"));
+ KAuth::ExecuteJob *job = action.execute();
+ connect(job, &KJob::result, this, [this, job] {
+ if (job->error()) {
+ qCWarning(POWERDEVIL) << "org.kde.powerdevil.chargethresholdhelper.getthreshold failed" << job->errorText();
+ return;
+ }
+
+ const auto data = job->data();
+ onChargeThresholdChanged(data.value(QStringLiteral("chargeStartThreshold")).toInt(),
+ data.value(QStringLiteral("chargeStopThreshold")).toInt());
+ });
+ job->start();
+}
+
+void Core::writeChargeThreshold()
+{
+ const int newStartThreshold = PowerDevilSettings::chargeStartThreshold();
+ const int newStopThreshold = PowerDevilSettings::chargeStopThreshold();
+
+ KAuth::Action action(QStringLiteral("org.kde.powerdevil.chargethresholdhelper.setthreshold"));
+ action.setHelperId(QStringLiteral("org.kde.powerdevil.chargethresholdhelper"));
+ action.setArguments({
+ {QStringLiteral("chargeStartThreshold"), newStartThreshold},
+ {QStringLiteral("chargeStopThreshold"), newStopThreshold}
+ });
+ KAuth::ExecuteJob *job = action.execute();
+ connect(job, &KJob::result, this, [this, job, newStartThreshold, newStopThreshold] {
+ if (job->error()) {
+ qCWarning(POWERDEVIL) << "org.kde.powerdevil.chargethresholdhelper.setthreshold failed" << job->errorText();
+ return;
+ }
+
+ onChargeThresholdChanged(newStartThreshold, newStopThreshold);
+ });
+ job->start();
+}
+
BackendInterface* Core::backend()
{
return m_backend;
}
bool Core::isLidClosed() const
{
return m_backend->isLidClosed();
}
bool Core::isLidPresent() const
{
return m_backend->isLidPresent();
}
bool Core::hasDualGpu() const
{
return m_hasDualGpu;
}
+int Core::chargeStopThreshold() const
+{
+ return m_chargeStopThreshold;
+}
+
qulonglong Core::batteryRemainingTime() const
{
return m_backend->batteryRemainingTime();
}
uint Core::backendCapabilities()
{
return m_backend->capabilities();
}
}
diff --git a/daemon/powerdevilcore.h b/daemon/powerdevilcore.h
index 8f36bca3..80d31464 100644
--- a/daemon/powerdevilcore.h
+++ b/daemon/powerdevilcore.h
@@ -1,160 +1,168 @@
/***************************************************************************
* Copyright (C) 2010 by Dario Freddi *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
***************************************************************************/
#ifndef POWERDEVILCORE_H
#define POWERDEVILCORE_H
#include "powerdevilbackendinterface.h"
#include
#include
#include
#include
namespace KActivities
{
class Consumer;
} // namespace KActivities
class QDBusServiceWatcher;
class QTimer;
class KNotification;
namespace Solid {
class Battery;
}
namespace PowerDevil
{
class BackendInterface;
class Action;
class Q_DECL_EXPORT Core : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(Core)
Q_CLASSINFO("D-Bus Interface", "org.kde.Solid.PowerManagement")
public:
explicit Core(QObject* parent);
~Core() override;
void reloadProfile(int state);
void emitRichNotification(const QString &evid, const QString &title, const QString &message = QString());
void emitNotification(const QString &eventId, const QString &title, const QString &message, const QString &iconName);
bool emitBatteryChargePercentNotification(int currentPercent, int previousPercent, const QString &udi = QString());
BackendInterface *backend();
// More...
public Q_SLOTS:
void loadCore(PowerDevil::BackendInterface *backend);
// Set of common action - useful for the DBus interface
uint backendCapabilities();
void refreshStatus();
void reparseConfiguration();
QString currentProfile() const;
void loadProfile(bool force = false);
qulonglong batteryRemainingTime() const;
bool isLidClosed() const;
bool isLidPresent() const;
bool isActionSupported(const QString &actionName);
bool hasDualGpu() const;
+ int chargeStopThreshold() const;
Q_SIGNALS:
void coreReady();
void profileChanged(const QString &newProfile);
void configurationReloaded();
void batteryRemainingTimeChanged(qulonglong time);
void lidClosedChanged(bool closed);
+ void chargeStopThresholdChanged(int threshold);
private:
void registerActionTimeout(Action *action, int timeout);
void unregisterActionTimeouts(Action *action);
void handleLowBattery(int percent);
void handleCriticalBattery(int percent);
+ void onChargeThresholdChanged(int start, int stop);
+ void readChargeThreshold();
+ void writeChargeThreshold();
+
/**
* Computes the current global charge percentage.
* Sum of all battery charges.
*/
int currentChargePercent() const;
friend class Action;
bool m_hasDualGpu;
+ int m_chargeStartThreshold = 0;
+ int m_chargeStopThreshold = 100;
BackendInterface *m_backend;
QDBusServiceWatcher *m_notificationsWatcher;
bool m_notificationsReady = false;
KSharedConfigPtr m_profilesConfig;
QString m_currentProfile;
QHash m_batteriesPercent;
QHash m_peripheralBatteriesPercent;
QHash m_batteriesCharged;
QPointer m_lowBatteryNotification;
QTimer *m_criticalBatteryTimer;
QPointer m_criticalBatteryNotification;
KActivities::Consumer *m_activityConsumer;
// Idle time management
QHash< Action*, QList< int > > m_registeredActionTimeouts;
QSet m_pendingResumeFromIdleActions;
bool m_pendingWakeupEvent;
// Activity inhibition management
QHash< QString, int > m_sessionActivityInhibit;
QHash< QString, int > m_screenActivityInhibit;
private Q_SLOTS:
void onBackendReady();
void onAcAdapterStateChanged(PowerDevil::BackendInterface::AcAdapterState);
void onBatteryChargePercentChanged(int,const QString&);
void onBatteryChargeStateChanged(int,const QString&);
void onBatteryRemainingTimeChanged(qulonglong);
void onKIdleTimeoutReached(int,int);
void onResumingFromIdle();
void onDeviceAdded(const QString &udi);
void onDeviceRemoved(const QString &udi);
void onCriticalBatteryTimerExpired();
void onNotificationTimeout();
void onServiceRegistered(const QString &service);
void onLidClosedChanged(bool closed);
void onAboutToSuspend();
};
}
#endif // POWERDEVILCORE_H
diff --git a/kcmodule/global/CMakeLists.txt b/kcmodule/global/CMakeLists.txt
index a125fccb..c9b27850 100644
--- a/kcmodule/global/CMakeLists.txt
+++ b/kcmodule/global/CMakeLists.txt
@@ -1,22 +1,23 @@
add_definitions(-DTRANSLATION_DOMAIN=\"powerdevilglobalconfig\")
set( kcm_powerdevil_global_SRCS
GeneralPage.cpp
)
ki18n_wrap_ui(kcm_powerdevil_global_SRCS
generalPage.ui)
kconfig_add_kcfg_files(kcm_powerdevil_global_SRCS ../../PowerDevilSettings.kcfgc)
add_library(kcm_powerdevilglobalconfig MODULE ${kcm_powerdevil_global_SRCS})
target_link_libraries(kcm_powerdevilglobalconfig
+ KF5::AuthCore
KF5::ConfigWidgets
KF5::KIOWidgets
KF5::NotifyConfig
powerdevilconfigcommonprivate
)
install(TARGETS kcm_powerdevilglobalconfig DESTINATION ${KDE_INSTALL_PLUGINDIR} )
install( FILES powerdevilglobalconfig.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} )
diff --git a/kcmodule/global/GeneralPage.cpp b/kcmodule/global/GeneralPage.cpp
index e3746ce2..a650b696 100644
--- a/kcmodule/global/GeneralPage.cpp
+++ b/kcmodule/global/GeneralPage.cpp
@@ -1,215 +1,254 @@
/***************************************************************************
* Copyright (C) 2008 by Dario Freddi *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
***************************************************************************/
#include "GeneralPage.h"
#include "ErrorOverlay.h"
#include "PowerDevilSettings.h"
#include "actions/bundled/suspendsession.h"
#include "powerdevilpowermanagement.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
+#include
+#include
+
K_PLUGIN_FACTORY(PowerDevilGeneralKCMFactory,
registerPlugin();
)
GeneralPage::GeneralPage(QWidget *parent, const QVariantList &args)
: KCModule(nullptr, parent, args)
{
setButtons(Apply | Help);
// KAboutData *about =
// new KAboutData("powerdevilglobalconfig", "powerdevilglobalconfig", ki18n("Global Power Management Configuration"),
// "", ki18n("A global power management configurator for KDE Power Management System"),
// KAboutData::License_GPL, ki18n("(c), 2010 Dario Freddi"),
// ki18n("From this module, you can configure the main Power Management daemon, assign profiles to "
// "states, and do some advanced fine tuning on battery handling"));
//
// about->addAuthor(ki18n("Dario Freddi"), ki18n("Maintainer") , "drf@kde.org",
// "http://drfav.wordpress.com");
//
// setAboutData(about);
setupUi(this);
fillUi();
QDBusServiceWatcher *watcher = new QDBusServiceWatcher("org.kde.Solid.PowerManagement",
QDBusConnection::sessionBus(),
QDBusServiceWatcher::WatchForRegistration |
QDBusServiceWatcher::WatchForUnregistration,
this);
connect(watcher, SIGNAL(serviceRegistered(QString)), this, SLOT(onServiceRegistered(QString)));
connect(watcher, SIGNAL(serviceUnregistered(QString)), this, SLOT(onServiceUnregistered(QString)));
if (QDBusConnection::sessionBus().interface()->isServiceRegistered("org.kde.Solid.PowerManagement")) {
onServiceRegistered("org.kde.Solid.PowerManagement");
} else {
onServiceUnregistered("org.kde.Solid.PowerManagement");
}
}
GeneralPage::~GeneralPage()
{
}
void GeneralPage::fillUi()
{
bool hasPowerSupplyBattery = false;
bool hasPeripheralBattery = false;
const auto devices = Solid::Device::listFromType(Solid::DeviceInterface::Battery, QString());
for (const Solid::Device &device : devices) {
const Solid::Battery *b = qobject_cast (device.asDeviceInterface(Solid::DeviceInterface::Battery));
if (b->isPowerSupply()) {
hasPowerSupplyBattery = true;
} else {
hasPeripheralBattery = true;
}
}
BatteryCriticalCombo->addItem(QIcon::fromTheme("dialog-cancel"), i18n("Do nothing"), PowerDevil::BundledActions::SuspendSession::None);
if (PowerDevil::PowerManagement::instance()->canSuspend()) {
BatteryCriticalCombo->addItem(QIcon::fromTheme("system-suspend"), i18nc("Suspend to RAM", "Sleep"), PowerDevil::BundledActions::SuspendSession::ToRamMode);
}
if (PowerDevil::PowerManagement::instance()->canHibernate()) {
BatteryCriticalCombo->addItem(QIcon::fromTheme("system-suspend-hibernate"), i18n("Hibernate"), PowerDevil::BundledActions::SuspendSession::ToDiskMode);
}
BatteryCriticalCombo->addItem(QIcon::fromTheme("system-shutdown"), i18n("Shut down"), PowerDevil::BundledActions::SuspendSession::ShutdownMode);
notificationsButton->setIcon(QIcon::fromTheme("preferences-desktop-notification"));
// modified fields...
connect(notificationsButton, SIGNAL(clicked()), SLOT(configureNotifications()));
connect(lowSpin, SIGNAL(valueChanged(int)), SLOT(changed()));
connect(criticalSpin, SIGNAL(valueChanged(int)), SLOT(changed()));
connect(lowPeripheralSpin, SIGNAL(valueChanged(int)), SLOT(changed()));
connect(BatteryCriticalCombo, SIGNAL(currentIndexChanged(int)), SLOT(changed()));
+ connect(chargeStartThresholdSpin, QOverload::of(&QSpinBox::valueChanged), this, &GeneralPage::markAsChanged);
+ connect(chargeStopThresholdSpin, QOverload::of(&QSpinBox::valueChanged), this, &GeneralPage::markAsChanged);
+
connect(pausePlayersCheckBox, SIGNAL(stateChanged(int)), SLOT(changed()));
if (!hasPowerSupplyBattery) {
BatteryCriticalLabel->hide();
BatteryCriticalCombo->hide();
lowLabel->hide();
lowSpin->hide();
criticalLabel->hide();
criticalSpin->hide();
}
if (!hasPeripheralBattery) {
lowPeripheralLabel->hide();
lowPeripheralSpin->hide();
}
if (!hasPowerSupplyBattery && !hasPeripheralBattery) {
batteryLevelsLabel->hide();
}
+
+ // We're basically just interested whehter we can read the values
+ // Displayed SpinBox is from config which PowerDevil will apply rather than the actual current threshold
+ KAuth::Action action(QStringLiteral("org.kde.powerdevil.chargethresholdhelper.getthreshold"));
+ action.setHelperId(QStringLiteral("org.kde.powerdevil.chargethresholdhelper"));
+ KAuth::ExecuteJob *job = action.execute();
+ connect(job, &KJob::result, this, [this, job] {
+ if (job->error()) {
+ qDebug() << "org.kde.powerdevil.chargethresholdhelper.getthreshold failed" << job->errorText();
+ setChargeThresholdSupported(false);
+ return;
+ }
+
+ setChargeThresholdSupported(true);
+ });
+ job->start();
}
void GeneralPage::load()
{
lowSpin->setValue(PowerDevilSettings::batteryLowLevel());
criticalSpin->setValue(PowerDevilSettings::batteryCriticalLevel());
lowPeripheralSpin->setValue(PowerDevilSettings::peripheralBatteryLowLevel());
+ chargeStartThresholdSpin->setValue(PowerDevilSettings::chargeStartThreshold());
+ chargeStopThresholdSpin->setValue(PowerDevilSettings::chargeStopThreshold());
+
BatteryCriticalCombo->setCurrentIndex(BatteryCriticalCombo->findData(PowerDevilSettings::batteryCriticalAction()));
pausePlayersCheckBox->setChecked(PowerDevilSettings::pausePlayersOnSuspend());
}
void GeneralPage::configureNotifications()
{
KNotifyConfigWidget::configure(this, "powerdevil");
}
void GeneralPage::save()
{
PowerDevilSettings::setBatteryLowLevel(lowSpin->value());
PowerDevilSettings::setBatteryCriticalLevel(criticalSpin->value());
PowerDevilSettings::setPeripheralBatteryLowLevel(lowPeripheralSpin->value());
PowerDevilSettings::setBatteryCriticalAction(BatteryCriticalCombo->itemData(BatteryCriticalCombo->currentIndex()).toInt());
+ PowerDevilSettings::setChargeStartThreshold(chargeStartThresholdSpin->value());
+ PowerDevilSettings::setChargeStopThreshold(chargeStopThresholdSpin->value());
+
PowerDevilSettings::setPausePlayersOnSuspend(pausePlayersCheckBox->checkState() == Qt::Checked);
PowerDevilSettings::self()->save();
// Notify Daemon
QDBusMessage call = QDBusMessage::createMethodCall("org.kde.Solid.PowerManagement", "/org/kde/Solid/PowerManagement",
"org.kde.Solid.PowerManagement", "refreshStatus");
// Perform call
QDBusConnection::sessionBus().asyncCall(call);
// And now we are set with no change
Q_EMIT changed(false);
}
void GeneralPage::defaults()
{
KCModule::defaults();
}
+void GeneralPage::setChargeThresholdSupported(bool supported)
+{
+ batteryThresholdLabel->setVisible(supported);
+ batteryThresholdExplanation->setVisible(supported);
+
+ chargeStartThresholdLabel->setVisible(supported);
+ chargeStartThresholdSpin->setVisible(supported);
+ chargeStopThresholdLabel->setVisible(supported);
+ chargeStopThresholdSpin->setVisible(supported);
+}
+
void GeneralPage::onServiceRegistered(const QString& service)
{
Q_UNUSED(service);
if (m_errorOverlay) {
m_errorOverlay->deleteLater();
m_errorOverlay = nullptr;
}
}
void GeneralPage::onServiceUnregistered(const QString& service)
{
Q_UNUSED(service);
if (m_errorOverlay) {
m_errorOverlay->deleteLater();
}
m_errorOverlay = new ErrorOverlay(this, i18n("The Power Management Service appears not to be running.\n"
"This can be solved by starting or scheduling it inside \"Startup and Shutdown\""),
this);
}
#include "GeneralPage.moc"
diff --git a/kcmodule/global/GeneralPage.h b/kcmodule/global/GeneralPage.h
index 6b690e92..00da3514 100644
--- a/kcmodule/global/GeneralPage.h
+++ b/kcmodule/global/GeneralPage.h
@@ -1,50 +1,52 @@
/***************************************************************************
* Copyright (C) 2008 by Dario Freddi *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
***************************************************************************/
#ifndef GENERALPAGE_H
#define GENERALPAGE_H
#include
#include "ui_generalPage.h"
class ErrorOverlay;
class GeneralPage : public KCModule, private Ui_generalPage
{
Q_OBJECT
public:
GeneralPage(QWidget *parent, const QVariantList &args);
~GeneralPage() override;
void fillUi();
void load() override;
void save() override;
void defaults() override;
private Q_SLOTS:
void configureNotifications();
void onServiceRegistered(const QString &service);
void onServiceUnregistered(const QString &service);
private:
+ void setChargeThresholdSupported(bool supported);
+
ErrorOverlay *m_errorOverlay = nullptr;
};
#endif /* GENERALPAGE_H */
diff --git a/kcmodule/global/generalPage.ui b/kcmodule/global/generalPage.ui
index 8d6b466a..97aa4951 100644
--- a/kcmodule/global/generalPage.ui
+++ b/kcmodule/global/generalPage.ui
@@ -1,208 +1,274 @@
generalPage
0
0
510
- 276
+ 406
-
QFormLayout::ExpandingFieldsGrow
-
<b>Battery Levels </b>
-
&Low level:
lowSpin
-
100
16777215
Low battery level
Battery will be considered low when it reaches this level
%
100
-
&Critical level:
criticalSpin
-
100
16777215
Critical battery level
Battery will be considered critical when it reaches this level
%
100
-
A&t critical level:
Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
false
BatteryCriticalCombo
-
300
16777215
+ -
+
+
+ Low level for peripheral devices:
+
+
+
+ -
+
+
+ %
+
+
+ 100
+
+
+
-
-
-
- Qt::Vertical
+
+
+
+ 75
+ true
+
-
-
- 20
- 16
-
+
+ Charge Limit
-
+
- -
+
-
+
+
+ <html><head/><body><p>Keeping the battery charged 100% over a prolonged period of time may accelerate deterioration of battery health. By limiting the maximum battery charge you can help extend the battery lifespan.</p></body></html>
+
+
+ true
+
+
+
+ -
+
+
+ Start charging only once below:
+
+
+
+ -
+
+
+ Always charge when plugged in
+
+
+ %
+
+
+ 99
+
+
+
+ -
+
+
+ Pause media players when suspending:
+
+
+
+ -
+
+
+ Enabled
+
+
+
+ -
0
0
16777215
16777215
Configure Notifications...
- -
-
+
-
+
+
+
+ 75
+ true
+
+
- Low level for peripheral devices:
+ Other Settings
- -
-
+
-
+
+
+ Stop charging at:
+
+
+
+ -
+
%
+
+ 50
+
100
-
-
- -
-
-
- Enabled
-
-
-
- -
-
-
- Pause media players when suspending:
+
+ 100
-
Qt::Horizontal
QSizePolicy::Fixed
8
20
KComboBox
QComboBox