diff --git a/daemon/CMakeLists.txt b/daemon/CMakeLists.txt index f99a2201e42328b4167becc918d1fa2f896d147a..789f99605b689a4c4b335545224afbebdec84e2d 100644 --- a/daemon/CMakeLists.txt +++ b/daemon/CMakeLists.txt @@ -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) diff --git a/daemon/actions/bundled/CMakeLists.txt b/daemon/actions/bundled/CMakeLists.txt index b7c16a962ff1632ddc1d7d5370a39e9f0848c5f1..6b26927bbe9b9f5f89012ca5e43b238524f63b72 100644 --- a/daemon/actions/bundled/CMakeLists.txt +++ b/daemon/actions/bundled/CMakeLists.txt @@ -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() diff --git a/daemon/actions/bundled/net.hadess.PowerProfiles.xml b/daemon/actions/bundled/net.hadess.PowerProfiles.xml new file mode 100644 index 0000000000000000000000000000000000000000..20383379d07ea81e364294d1b6446121e7bacc67 --- /dev/null +++ b/daemon/actions/bundled/net.hadess.PowerProfiles.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/daemon/actions/bundled/org.freedesktop.DBus.Properties.xml b/daemon/actions/bundled/org.freedesktop.DBus.Properties.xml new file mode 100644 index 0000000000000000000000000000000000000000..3e729ca643d6dfe592ba37fac26573af38abe02a --- /dev/null +++ b/daemon/actions/bundled/org.freedesktop.DBus.Properties.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/daemon/actions/bundled/org.kde.Solid.PowerManagement.Actions.PowerProfile.xml b/daemon/actions/bundled/org.kde.Solid.PowerManagement.Actions.PowerProfile.xml new file mode 100644 index 0000000000000000000000000000000000000000..d8db191a90aaaa7322496ed2fc7d87f3a3630577 --- /dev/null +++ b/daemon/actions/bundled/org.kde.Solid.PowerManagement.Actions.PowerProfile.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/daemon/actions/bundled/powerdevilpowerprofileaction.desktop b/daemon/actions/bundled/powerdevilpowerprofileaction.desktop new file mode 100644 index 0000000000000000000000000000000000000000..f3ac591b3203e441aa92d9753f2e86c8e4eb5070 --- /dev/null +++ b/daemon/actions/bundled/powerdevilpowerprofileaction.desktop @@ -0,0 +1,13 @@ +[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 diff --git a/daemon/actions/bundled/powerprofile.cpp b/daemon/actions/bundled/powerprofile.cpp new file mode 100644 index 0000000000000000000000000000000000000000..72608a622a9d78a0495d453542faed3564c2a3d8 --- /dev/null +++ b/daemon/actions/bundled/powerprofile.cpp @@ -0,0 +1,167 @@ +/* + * Copyright 2020 Kai Uwe Broulik + * Copyright 2021 David Redondo + * + * 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 "powerprofile.h" + +#include "power_profiles_interface.h" +#include "powerprofileadaptor.h" +#include "properties_interface.h" + +#include + +#include + +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 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 profiles; + properties[profilesProperty].value() >> 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); +} diff --git a/daemon/actions/bundled/powerprofile.h b/daemon/actions/bundled/powerprofile.h new file mode 100644 index 0000000000000000000000000000000000000000..6c64b276679480807e610ea277574527f3e8e162 --- /dev/null +++ b/daemon/actions/bundled/powerprofile.h @@ -0,0 +1,81 @@ +/* + * Copyright 2020 Kai Uwe Broulik + * Copyright 2021 David Redondo + * + * 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 + +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 diff --git a/daemon/actions/bundled/powerprofileconfig.cpp b/daemon/actions/bundled/powerprofileconfig.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3c5e7944bfa4915ffe604992190fe9e7a526db39 --- /dev/null +++ b/daemon/actions/bundled/powerprofileconfig.cpp @@ -0,0 +1,104 @@ +/* + * 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 "powerprofileconfig.h" + +#include "powerdevilpowermanagement.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +K_PLUGIN_FACTORY(PowerDevilPowerProfileConfigFactory, registerPlugin();) + +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 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 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> 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::of(&QComboBox::currentIndexChanged), this, &PowerProfileConfig::setChanged); + + return {qMakePair(i18nc("Switch to power management profile", "Switch to:"), m_profileCombo)}; +} + +#include "powerprofileconfig.moc" diff --git a/daemon/actions/bundled/powerprofileconfig.h b/daemon/actions/bundled/powerprofileconfig.h new file mode 100644 index 0000000000000000000000000000000000000000..ce215c1f3fdc90b42853f6c568fbc903fed72a0f --- /dev/null +++ b/daemon/actions/bundled/powerprofileconfig.h @@ -0,0 +1,48 @@ +/* + * 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 + +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> buildUi() override; + +private: + QComboBox *m_profileCombo; +}; + +} // namespace BundledActions +} // namespace PowerDevil diff --git a/daemon/powerdevilactionpool.cpp b/daemon/powerdevilactionpool.cpp index ec84b0edfaebe585229f214047635ae3a56a0bb4..7ced41e4aef7b145f1260b5677d8502dda6a3e59 100644 --- a/daemon/powerdevilactionpool.cpp +++ b/daemon/powerdevilactionpool.cpp @@ -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::iterator i = m_actionPool.begin();