Commit 374c6482 authored by David Redondo's avatar David Redondo 🏎
Browse files

Integrate power-profiles daemon



This allows to switch between the "battery-saver", "balanced"
and "performance" profiles.

It adds an action that can be configured to switch automatically
on AC (un)plugging or low power, as well as exposes that setting
on DBus for a future Battery Monitor UI.
Co-authored-by: Kai Uwe Broulik's avatarKai Uwe Broulik <kde@privat.broulik.de>
parent 93e441d9
......@@ -19,8 +19,10 @@ set(powerdevil_bundled_actions_SRCS
actions/bundled/xcbdpmshelper.cpp
actions/bundled/waylanddpmshelper.cpp
${PowerDevil_SOURCE_DIR}/daemon/kwinkscreenhelpereffect.cpp
actions/bundled/powerprofile.cpp
)
if(HAVE_WIRELESS_SUPPORT)
set(powerdevil_bundled_actions_SRCS ${powerdevil_bundled_actions_SRCS}
actions/bundled/wirelesspowersaving.cpp
......@@ -66,9 +68,15 @@ if(HAVE_WIRELESS_SUPPORT)
qt_add_dbus_adaptor(powerdevilcore_SRCS actions/bundled/org.kde.Solid.PowerManagement.Actions.WirelessPowerSaving.xml
actions/bundled/wirelesspowersaving.h PowerDevil::BundledActions::WirelessPowerSaving)
endif()
qt5_add_dbus_adaptor(powerdevilcore_SRCS actions/bundled/org.kde.Solid.PowerManagement.Actions.PowerProfile.xml
actions/bundled/powerprofile.h PowerDevil::BundledActions::PowerProfile)
qt_add_dbus_interface(powerdevilcore_SRCS org.freedesktop.ScreenSaver.xml screenlocker_interface)
qt5_add_dbus_interface(powerdevilcore_SRCS actions/bundled/org.freedesktop.DBus.Properties.xml properties_interface)
qt5_add_dbus_interface(powerdevilcore_SRCS actions/bundled/net.hadess.PowerProfiles.xml power_profiles_interface)
add_library(powerdevilcore SHARED ${powerdevilcore_SRCS} ${powerdevil_bundled_actions_SRCS})
set_target_properties(powerdevilcore PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION 2)
......
......@@ -21,6 +21,7 @@ add_powerdevil_bundled_action(dimdisplay)
add_powerdevil_bundled_action(runscript KF5::KIOCore KF5::KIOWidgets)
add_powerdevil_bundled_action(suspendsession KF5::KIOCore KF5::KIOWidgets)
add_powerdevil_bundled_action(dpms)
add_powerdevil_bundled_action(powerprofile)
if(HAVE_WIRELESS_SUPPORT)
add_powerdevil_bundled_action(wirelesspowersaving KF5::NetworkManagerQt KF5::BluezQt)
endif()
......
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<!--
net.hadess.PowerProfiles:
@short_description: Power Profiles daemon
The power-profiles-daemon API is meant to be used by parts of the OS or
desktop environment to switch system power profiles based on user choice,
or user intent.
OS components would typically use the "Profiles" property to construct
their UI (2 or 3 profiles available), and monitor the "ActiveProfile"
and the "PerformanceInhibited" properties to update that UI. The UI
would try to set the "ActiveProfile" property if the user selected
a different one.
Note that the reason why the project exists and how it is different from
existing projects is explained <ulink href=" https://gitlab.freedesktop.org/hadess/power-profiles-daemon/-/blob/master/README.md">
in the project's README file</ulink>.
The object path will be "/net/hadess/PowerProfiles".
-->
<interface name="net.hadess.PowerProfiles">
<!--
ActiveProfile:
The type of the currently active profile. It might change automatically
if the "performance" profile was selected but it got inhibited, in which
case the "PerformanceInhibited" property will reflect the reason.
-->
<property name="ActiveProfile" type="s" access="readwrite"/>
<!--
PerformanceInhibited:
This will be set if the performance power profile is unavailable, with
the value being used to identify the reason for unavailability. As new
reasons can be added, it is recommended that front-ends show a generic
reason if they do not recognise the value. Possible values are:
- "lap-detected" (the computer is sitting on the user's lap)
- "high-operating-temperature" (the computer is close to overheating)
- "" (the empty string, if not inhibited)
-->
<property name="PerformanceInhibited" type="s" access="read"/>
<!--
Profiles:
An array of key-pair values representing each profile. The key named
"Driver" (s) identifies the power-profiles-daemon backend code used to
implement the profile.
The key named "Profile" (s) will be one of:
- "power-saver" (battery saving profile)
- "balanced" (the default profile)
- "performance" (a profile that does not care about noise or battery consumption)
Only one of each type of profile will be listed, with the daemon choosing the
more appropriate "driver" for each profile type.
-->
<property name="Profiles" type="aa{sv}" access="read">
<annotation name="org.qtproject.QtDBus.QtTypeName" value="QList&lt;QVariantMap&gt;"/>
</property>
<!--
Actions:
An array of strings listing each one of the "actions" implemented in
the running daemon. This is used by API users to figure out whether
particular functionality is available in a version of the daemon.
-->
<property name="Actions" type="as" access="read"/>
</interface>
</node>
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.freedesktop.DBus.Properties">
<method name="Get">
<arg name="interface_name" type="s" direction="in"/>
<arg name="property_name" type="s" direction="in"/>
<arg name="value" type="v" direction="out"/>
</method>
<method name="Set">
<arg name="interface_name" type="s" direction="in"/>
<arg name="property_name" type="s" direction="in"/>
<arg name="value" type="v" direction="in"/>
</method>
<method name="GetAll">
<arg name="interface_name" type="s" direction="in"/>
<arg name="values" type="a{sv}" direction="out"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QVariantMap"/>
</method>
<signal name="PropertiesChanged">
<arg name="interface_name" type="s" direction="out"/>
<arg name="changed_properties" type="a{sv}" direction="out"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="QVariantMap"/>
<arg name="invalidated_properties" type="as" direction="out"/>
</signal>
</interface>
</node>
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.kde.Solid.PowerManagement.Actions.PowerProfile">
<method name="currentProfile">
<arg type="s" direction="out" />
</method>
<method name="profileChoices">
<arg type="as" direction="out" />
</method>
<method name="setProfile">
<arg type="s" direction="in"/>
</method>
<method name="performanceInhibitedReason">
<arg type="s" direction="out" />
</method>
<signal name="currentProfileChanged">
<arg type="s" direction="out" />
</signal>
<signal name="profileChoicesChanged">
<arg type="as" direction="out" />
</signal>
<signal name="performanceInhibitedReasonChanged">
<arg type="s" direction="out" />
</signal>
</interface>
</node>
[Desktop Entry]
Type=Service
X-KDE-ServiceTypes=PowerDevil/Action
Icon=preferences-system-power-management-profile
Name=Power management profile
Comment=Apply a power management profile
X-KDE-PowerDevil-Action-ID=PowerProfile
X-KDE-PowerDevil-Action-IsBundled=true
X-KDE-PowerDevil-Action-UIComponentLibrary=powerdevilpowerprofileaction_config
X-KDE-PowerDevil-Action-ConfigPriority=70
X-KDE-PowerDevil-Action-HasRuntimeRequirement=true
X-KDE-PowerDevil-Action-RegistersDBusInterface=true
/*
* Copyright 2020 Kai Uwe Broulik <kde@broulik.de>
* Copyright 2021 David Redondo <kde@david-redondo.de>
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include "powerprofile.h"
#include "power_profiles_interface.h"
#include "powerprofileadaptor.h"
#include "properties_interface.h"
#include <powerdevil_debug.h>
#include <KConfigGroup>
using namespace PowerDevil::BundledActions;
static const QString activeProfileProperty = QStringLiteral("ActiveProfile");
static const QString profilesProperty = QStringLiteral("Profiles");
static const QString performanceInhibitedProperty = QStringLiteral("PerformanceInhibited");
static const QString ppdName = QStringLiteral("net.hadess.PowerProfiles");
static const QString ppdPath = QStringLiteral("/net/hadess/PowerProfiles");
PowerProfile::PowerProfile(QObject *parent)
: Action(parent)
, m_powerProfilesInterface(new NetHadessPowerProfilesInterface(ppdName, ppdPath, QDBusConnection::systemBus(), this))
, m_powerProfilesPropertiesInterface(new OrgFreedesktopDBusPropertiesInterface(ppdName, ppdPath, QDBusConnection::systemBus(), this))
{
new PowerProfileAdaptor(this);
connect(m_powerProfilesPropertiesInterface, &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged, this, &PowerProfile::propertiesChanged);
auto watcher = new QDBusPendingCallWatcher(m_powerProfilesPropertiesInterface->GetAll(m_powerProfilesInterface->interface()));
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, watcher] {
watcher->deleteLater();
QDBusPendingReply<QVariantMap> reply = *watcher;
if (watcher->isError()) {
return;
}
readProperties(reply.value());
});
}
PowerProfile::~PowerProfile() = default;
void PowerProfile::onProfileLoad()
{
if (!m_configuredProfile.isEmpty()) {
setProfile(m_configuredProfile);
}
}
void PowerProfile::onWakeupFromIdle()
{
}
void PowerProfile::onIdleTimeout(int msec)
{
Q_UNUSED(msec);
}
void PowerProfile::onProfileUnload()
{
}
void PowerProfile::triggerImpl(const QVariantMap &args)
{
Q_UNUSED(args);
}
bool PowerProfile::loadAction(const KConfigGroup &config)
{
if (config.hasKey("profile")) {
m_configuredProfile = config.readEntry("profile", QString());
}
return true;
}
bool PowerProfile::isSupported()
{
return QDBusConnection::systemBus().interface()->activatableServiceNames().value().contains(ppdName);
}
QStringList PowerProfile::profileChoices() const
{
return m_profileChoices;
}
QString PowerProfile::currentProfile() const
{
return m_currentProfile;
}
QString PowerProfile::performanceInhibitedReason() const
{
return m_performanceInhibitedReason;
}
void PowerProfile::setProfile(const QString &profile)
{
auto call = m_powerProfilesPropertiesInterface->Set(m_powerProfilesInterface->interface(), activeProfileProperty, QDBusVariant(profile));
if (calledFromDBus()) {
setDelayedReply(true);
const auto msg = message();
auto watcher = new QDBusPendingCallWatcher(call);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [msg, watcher] {
watcher->deleteLater();
if (watcher->isError()) {
QDBusConnection::sessionBus().send(msg.createErrorReply(watcher->error()));
} else {
QDBusConnection::sessionBus().send(msg.createReply());
}
});
}
}
void PowerProfile::readProperties(const QVariantMap &properties)
{
if (properties.contains(activeProfileProperty)) {
m_currentProfile = properties[activeProfileProperty].toString();
Q_EMIT currentProfileChanged(m_currentProfile);
}
if (properties.contains(profilesProperty)) {
QList<QVariantMap> profiles;
properties[profilesProperty].value<QDBusArgument>() >> profiles;
m_profileChoices.clear();
if (profiles.first()[QStringLiteral("Driver")] != QLatin1String("placeholder")) {
std::transform(profiles.cbegin(), profiles.cend(), std::back_inserter(m_profileChoices), [](const QVariantMap &dict) {
return dict[QStringLiteral("Profile")].toString();
});
}
Q_EMIT profileChoicesChanged(m_profileChoices);
}
if (properties.contains(performanceInhibitedProperty)) {
m_performanceInhibitedReason = properties[performanceInhibitedProperty].toString();
Q_EMIT performanceInhibitedReasonChanged(m_performanceInhibitedReason);
}
}
void PowerProfile::propertiesChanged(const QString &interface, const QVariantMap &changed, const QStringList &invalidated)
{
Q_UNUSED(invalidated)
if (interface != m_powerProfilesInterface->interface()) {
return;
}
readProperties(changed);
}
/*
* Copyright 2020 Kai Uwe Broulik <kde@broulik.de>
* Copyright 2021 David Redondo <kde@david-redondo.de>
*
* 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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <powerdevilaction.h>
class OrgFreedesktopDBusPropertiesInterface;
class NetHadessPowerProfilesInterface;
namespace PowerDevil
{
namespace BundledActions
{
class PowerProfile : public PowerDevil::Action, protected QDBusContext
{
Q_OBJECT
Q_DISABLE_COPY(PowerProfile)
Q_CLASSINFO("D-Bus Interface", "org.kde.Solid.PowerManagement.Actions.PowerProfile")
Q_PROPERTY(QStringList profileChoices READ profileChoices NOTIFY profileChoicesChanged)
Q_PROPERTY(QString currentProfile READ currentProfile NOTIFY currentProfileChanged)
public:
explicit PowerProfile(QObject *parent);
~PowerProfile() override;
bool loadAction(const KConfigGroup &config) override;
QStringList profileChoices() const;
QString currentProfile() const;
void setProfile(const QString &profile);
QString performanceInhibitedReason() const;
Q_SIGNALS:
void currentProfileChanged(const QString &profile);
void profileChoicesChanged(const QStringList &profiles);
void performanceInhibitedReasonChanged(const QString &reason);
protected:
void onProfileLoad() override;
void onWakeupFromIdle() override;
void onIdleTimeout(int msec) override;
void onProfileUnload() override;
void triggerImpl(const QVariantMap &args) override;
bool isSupported() override;
private Q_SLOTS:
void propertiesChanged(const QString &interface, const QVariantMap &changed, const QStringList &invalidated);
private:
void readProperties(const QVariantMap &properties);
NetHadessPowerProfilesInterface *m_powerProfilesInterface;
OrgFreedesktopDBusPropertiesInterface *m_powerProfilesPropertiesInterface;
QStringList m_profileChoices;
QString m_currentProfile;
QString m_performanceInhibitedReason;
QString m_configuredProfile;
};
} // namespace BundledActions
} // namespace PowerDevil
/*
* Copyright 2020 Kai Uwe Broulik <kde@broulik.de>
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include "powerprofileconfig.h"
#include "powerdevilpowermanagement.h"
#include <QComboBox>
#include <QDBusConnection>
#include <QDBusPendingCallWatcher>
#include <QDBusPendingReply>
#include <KConfig>
#include <KLocalizedString>
#include <KPluginFactory>
#include <KSharedConfig>
K_PLUGIN_FACTORY(PowerDevilPowerProfileConfigFactory, registerPlugin<PowerDevil::BundledActions::PowerProfileConfig>();)
using namespace PowerDevil::BundledActions;
PowerProfileConfig::PowerProfileConfig(QObject *parent, const QVariantList &args)
: ActionConfig(parent)
{
Q_UNUSED(args)
}
PowerProfileConfig::~PowerProfileConfig() = default;
void PowerProfileConfig::save()
{
const QString profile = m_profileCombo->currentData().toString();
configGroup().writeEntry("profile", profile);
configGroup().sync();
}
void PowerProfileConfig::load()
{
configGroup().config()->reparseConfiguration();
const QString profile = configGroup().readEntry("profile", QString());
QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.Solid.PowerManagement"),
QStringLiteral("/org/kde/Solid/PowerManagement/Actions/PowerProfile"),
QStringLiteral("org.kde.Solid.PowerManagement.Actions.PowerProfile"),
QStringLiteral("profileChoices"));
auto *watcher = new QDBusPendingCallWatcher(QDBusConnection::sessionBus().asyncCall(msg), this);
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, profile](QDBusPendingCallWatcher *watcher) {
QDBusPendingReply<QStringList> reply = *watcher;
watcher->deleteLater();
m_profileCombo->clear();
m_profileCombo->addItem(i18n("Leave unchanged"));
if (reply.isError()) {
qWarning() << "Failed to query platform profile choices" << reply.error().message();
return;
}
const QHash<QString, QString> profileNames = {
{QStringLiteral("power-saver"), i18n("Power Save")},
{QStringLiteral("balanced"), i18n("Balanced")},
{QStringLiteral("performance"), i18n("Performance")},
};
const QStringList choices = reply.value();
for (const QString &choice : choices) {
m_profileCombo->addItem(profileNames.value(choice, choice), choice);
}
m_profileCombo->setCurrentIndex(qMax(0, m_profileCombo->findData(profile)));
});
}
QList<QPair<QString, QWidget *>> PowerProfileConfig::buildUi()
{
m_profileCombo = new QComboBox;
// Uniform ComboBox width throughout all action config modules
m_profileCombo->setMinimumWidth(300);
m_profileCombo->setMaximumWidth(300);
connect(m_profileCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &PowerProfileConfig::setChanged);
return {qMakePair<QString, QWidget *>(i18nc("Switch to power management profile", "Switch to:"), m_profileCombo)};
}
#include "powerprofileconfig.moc"
/*
* Copyright 2020 Kai Uwe Broulik <kde@broulik.de>
*
* 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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <powerdevilactionconfig.h>
class QComboBox;
namespace PowerDevil
{
namespace BundledActions
{
class PowerProfileConfig : public PowerDevil::ActionConfig
{
Q_OBJECT
Q_DISABLE_COPY(PowerProfileConfig)
public:
PowerProfileConfig(QObject *parent, const QVariantList &args);
~PowerProfileConfig() override;
void save() override;
void load() override;
QList<QPair<QString, QWidget *>> buildUi() override;
private:
QComboBox *m_profileCombo;
};
} // namespace BundledActions
} // namespace PowerDevil
......@@ -43,6 +43,7 @@
#ifdef HAVE_WIRELESS_SUPPORT
#include "actions/bundled/wirelesspowersaving.h"
#endif
#include "actions/bundled/powerprofile.h"
namespace PowerDevil
{
......@@ -135,6 +136,7 @@ void ActionPool::init(PowerDevil::Core *parent)
#ifdef HAVE_WIRELESS_SUPPORT
m_actionPool.insert(QStringLiteral("WirelessPowerSaving"), new BundledActions::WirelessPowerSaving(parent));
#endif
m_actionPool.insert(QStringLiteral("PowerProfile"), new BundledActions::PowerProfile(parent));
// Verify support
QHash<QString,Action*>::iterator i = m_actionPool.begin();
......