forked from quickshell/quickshell
service/upower: add power-profiles support
This commit is contained in:
parent
66b9917e70
commit
47bcf8ee61
6 changed files with 462 additions and 2 deletions
|
@ -36,8 +36,8 @@ template <typename T>
|
|||
class DBusResult {
|
||||
public:
|
||||
explicit DBusResult() = default;
|
||||
explicit DBusResult(T value): value(std::move(value)) {}
|
||||
explicit DBusResult(QDBusError error): error(std::move(error)) {}
|
||||
DBusResult(T value): value(std::move(value)) {}
|
||||
DBusResult(QDBusError error): error(std::move(error)) {}
|
||||
explicit DBusResult(T value, QDBusError error)
|
||||
: value(std::move(value))
|
||||
, error(std::move(error)) {}
|
||||
|
|
|
@ -21,6 +21,7 @@ qt_add_dbus_interface(DBUS_INTERFACES
|
|||
qt_add_library(quickshell-service-upower STATIC
|
||||
core.cpp
|
||||
device.cpp
|
||||
powerprofiles.cpp
|
||||
${DBUS_INTERFACES}
|
||||
)
|
||||
|
||||
|
|
|
@ -54,6 +54,14 @@ private:
|
|||
DBusUPowerService* service = nullptr;
|
||||
};
|
||||
|
||||
///! Provides access to the UPower service.
|
||||
/// An interface to the [UPower daemon], which can be used to
|
||||
/// view battery and power statistics for your computer and
|
||||
/// connected devices.
|
||||
///
|
||||
/// > [!NOTE] The UPower daemon must be installed to use this service.
|
||||
///
|
||||
/// [UPower daemon]: https://upower.freedesktop.org
|
||||
class UPowerQml: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_NAMED_ELEMENT(UPower);
|
||||
|
|
|
@ -3,5 +3,6 @@ description = "UPower Service"
|
|||
headers = [
|
||||
"core.hpp",
|
||||
"device.hpp",
|
||||
"powerprofiles.hpp",
|
||||
]
|
||||
-----
|
||||
|
|
213
src/services/upower/powerprofiles.cpp
Normal file
213
src/services/upower/powerprofiles.cpp
Normal file
|
@ -0,0 +1,213 @@
|
|||
#include "powerprofiles.hpp"
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdbusconnection.h>
|
||||
#include <qdbuserror.h>
|
||||
#include <qdbusinterface.h>
|
||||
#include <qdbusmetatype.h>
|
||||
#include <qdebug.h>
|
||||
#include <qlist.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qobject.h>
|
||||
#include <qstringliteral.h>
|
||||
|
||||
#include "../../dbus/bus.hpp"
|
||||
#include "../../dbus/properties.hpp"
|
||||
|
||||
namespace qs::service::upower {
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(logPowerProfiles, "quickshell.service.powerprofiles", QtWarningMsg);
|
||||
}
|
||||
|
||||
QString PowerProfile::toString(PowerProfile::Enum profile) {
|
||||
switch (profile) {
|
||||
case PowerProfile::PowerSaver: return QStringLiteral("PowerSaver");
|
||||
case PowerProfile::Balanced: return QStringLiteral("Balanced");
|
||||
case PowerProfile::Performance: return QStringLiteral("Performance");
|
||||
default: return QStringLiteral("Invalid");
|
||||
}
|
||||
}
|
||||
|
||||
QString PerformanceDegradationReason::toString(PerformanceDegradationReason::Enum reason) {
|
||||
switch (reason) {
|
||||
case PerformanceDegradationReason::LapDetected: return QStringLiteral("LapDetected");
|
||||
case PerformanceDegradationReason::HighTemperature: return QStringLiteral("HighTemperature");
|
||||
default: return QStringLiteral("Invalid");
|
||||
}
|
||||
}
|
||||
|
||||
bool PowerProfileHold::operator==(const PowerProfileHold& other) const {
|
||||
return other.profile == this->profile && other.applicationId == this->applicationId
|
||||
&& other.reason == this->reason;
|
||||
}
|
||||
|
||||
QDebug& operator<<(QDebug& debug, const PowerProfileHold& hold) {
|
||||
auto saver = QDebugStateSaver(debug);
|
||||
|
||||
debug.nospace();
|
||||
debug << "PowerProfileHold(profile=" << hold.profile << ", applicationId=" << hold.applicationId
|
||||
<< ", reason=" << hold.reason << ')';
|
||||
|
||||
return debug;
|
||||
}
|
||||
|
||||
PowerProfiles::PowerProfiles() {
|
||||
qDBusRegisterMetaType<QList<QVariantMap>>();
|
||||
|
||||
this->bHasPerformanceProfile.setBinding([this]() {
|
||||
return this->bProfiles.value().contains(PowerProfile::Performance);
|
||||
});
|
||||
|
||||
qCDebug(logPowerProfiles) << "Starting PowerProfiles Service.";
|
||||
|
||||
auto bus = QDBusConnection::systemBus();
|
||||
|
||||
if (!bus.isConnected()) {
|
||||
qCWarning(logPowerProfiles
|
||||
) << "Could not connect to DBus. PowerProfiles services will not work.";
|
||||
}
|
||||
|
||||
this->service = new QDBusInterface(
|
||||
"org.freedesktop.UPower.PowerProfiles",
|
||||
"/org/freedesktop/UPower/PowerProfiles",
|
||||
"org.freedesktop.UPower.PowerProfiles",
|
||||
bus,
|
||||
this
|
||||
);
|
||||
|
||||
if (!this->service->isValid()) {
|
||||
qCDebug(logPowerProfiles
|
||||
) << "PowerProfilesDaemon is not currently running, attempting to start it.";
|
||||
|
||||
dbus::tryLaunchService(this, bus, "org.freedesktop.UPower.PowerProfiles", [this](bool success) {
|
||||
if (success) {
|
||||
qCDebug(logPowerProfiles) << "Successfully launched PowerProfiles service.";
|
||||
this->init();
|
||||
} else {
|
||||
qCWarning(logPowerProfiles)
|
||||
<< "Could not start PowerProfilesDaemon. The PowerProfiles service will not work.";
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this->init();
|
||||
}
|
||||
}
|
||||
|
||||
void PowerProfiles::init() {
|
||||
this->properties.setInterface(this->service);
|
||||
this->properties.updateAllViaGetAll();
|
||||
}
|
||||
|
||||
void PowerProfiles::setProfile(PowerProfile::Enum profile) {
|
||||
if (profile == PowerProfile::Performance && !this->bHasPerformanceProfile) {
|
||||
qCCritical(logPowerProfiles
|
||||
) << "Cannot request performance profile as it is not present for this device.";
|
||||
return;
|
||||
} else if (profile < PowerProfile::PowerSaver || profile > PowerProfile::Performance) {
|
||||
qCCritical(logPowerProfiles) << "Tried to request invalid power profile" << profile;
|
||||
return;
|
||||
}
|
||||
|
||||
this->bProfile = profile;
|
||||
this->pProfile.write();
|
||||
}
|
||||
|
||||
PowerProfiles* PowerProfiles::instance() {
|
||||
static auto* instance = new PowerProfiles(); // NOLINT
|
||||
return instance;
|
||||
}
|
||||
|
||||
PowerProfilesQml::PowerProfilesQml(QObject* parent): QObject(parent) {
|
||||
auto* instance = PowerProfiles::instance();
|
||||
|
||||
this->bProfile.setBinding([instance]() { return instance->bProfile.value(); });
|
||||
|
||||
this->bHasPerformanceProfile.setBinding([instance]() {
|
||||
return instance->bHasPerformanceProfile.value();
|
||||
});
|
||||
|
||||
this->bDegradationReason.setBinding([instance]() { return instance->bDegradationReason.value(); }
|
||||
);
|
||||
|
||||
this->bHolds.setBinding([instance]() { return instance->bHolds.value(); });
|
||||
}
|
||||
|
||||
} // namespace qs::service::upower
|
||||
|
||||
namespace qs::dbus {
|
||||
|
||||
using namespace qs::service::upower;
|
||||
|
||||
DBusResult<PowerProfile::Enum> DBusDataTransform<PowerProfile::Enum>::fromWire(const Wire& wire) {
|
||||
if (wire == QStringLiteral("power-saver")) {
|
||||
return PowerProfile::PowerSaver;
|
||||
} else if (wire == QStringLiteral("balanced")) {
|
||||
return PowerProfile::Balanced;
|
||||
} else if (wire == QStringLiteral("performance")) {
|
||||
return PowerProfile::Performance;
|
||||
} else {
|
||||
return QDBusError(QDBusError::InvalidArgs, QString("Invalid PowerProfile: %1").arg(wire));
|
||||
}
|
||||
}
|
||||
|
||||
QString DBusDataTransform<PowerProfile::Enum>::toWire(Data data) {
|
||||
switch (data) {
|
||||
case PowerProfile::PowerSaver: return QStringLiteral("power-saver");
|
||||
case PowerProfile::Balanced: return QStringLiteral("balanced");
|
||||
case PowerProfile::Performance: return QStringLiteral("performance");
|
||||
}
|
||||
}
|
||||
|
||||
DBusResult<QList<PowerProfile::Enum>>
|
||||
DBusDataTransform<QList<PowerProfile::Enum>>::fromWire(const Wire& wire) {
|
||||
QList<PowerProfile::Enum> profiles;
|
||||
|
||||
for (const auto& entry: wire) {
|
||||
auto profile =
|
||||
DBusDataTransform<PowerProfile::Enum>::fromWire(entry.value("Profile").value<QString>());
|
||||
|
||||
if (!profile.isValid()) return profile.error;
|
||||
profiles.append(profile.value);
|
||||
}
|
||||
|
||||
return profiles;
|
||||
}
|
||||
|
||||
DBusResult<PerformanceDegradationReason::Enum>
|
||||
DBusDataTransform<PerformanceDegradationReason::Enum>::fromWire(const Wire& wire) {
|
||||
if (wire.isEmpty()) {
|
||||
return PerformanceDegradationReason::None;
|
||||
} else if (wire == QStringLiteral("lap-detected")) {
|
||||
return PerformanceDegradationReason::LapDetected;
|
||||
} else if (wire == QStringLiteral("high-operating-temperature")) {
|
||||
return PerformanceDegradationReason::HighTemperature;
|
||||
} else {
|
||||
return QDBusError(
|
||||
QDBusError::InvalidArgs,
|
||||
QString("Invalid PerformanceDegradationReason: %1").arg(wire)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DBusResult<QList<PowerProfileHold>>
|
||||
DBusDataTransform<QList<PowerProfileHold>>::fromWire(const Wire& wire) {
|
||||
QList<PowerProfileHold> holds;
|
||||
|
||||
for (const auto& entry: wire) {
|
||||
auto profile =
|
||||
DBusDataTransform<PowerProfile::Enum>::fromWire(entry.value("Profile").value<QString>());
|
||||
|
||||
if (!profile.isValid()) return profile.error;
|
||||
|
||||
auto applicationId = entry.value("ApplicationId").value<QString>();
|
||||
auto reason = entry.value("Reason").value<QString>();
|
||||
|
||||
holds.append(PowerProfileHold(profile.value, applicationId, reason));
|
||||
}
|
||||
|
||||
return holds;
|
||||
}
|
||||
|
||||
} // namespace qs::dbus
|
237
src/services/upower/powerprofiles.hpp
Normal file
237
src/services/upower/powerprofiles.hpp
Normal file
|
@ -0,0 +1,237 @@
|
|||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdbusinterface.h>
|
||||
#include <qdebug.h>
|
||||
#include <qobject.h>
|
||||
#include <qproperty.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../dbus/properties.hpp"
|
||||
|
||||
namespace qs::service::upower {
|
||||
|
||||
///! Power profile exposed by the PowerProfiles service.
|
||||
/// See @@PowerProfiles.
|
||||
class PowerProfile: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_SINGLETON;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
/// This profile will limit system performance in order to save power.
|
||||
PowerSaver = 0,
|
||||
/// This profile is the default, and will attempt to strike a balance
|
||||
/// between performance and power consumption.
|
||||
Balanced = 1,
|
||||
/// This profile will maximize performance at the cost of power consumption.
|
||||
Performance = 2,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
|
||||
Q_INVOKABLE static QString toString(qs::service::upower::PowerProfile::Enum profile);
|
||||
};
|
||||
|
||||
///! Reason for performance degradation exposed by the PowerProfiles service.
|
||||
/// See @@PowerProfiles.degradationReason for more information.
|
||||
class PerformanceDegradationReason: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_SINGLETON;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
/// Performance has not been degraded in a way power-profiles-daemon can detect.
|
||||
None = 0,
|
||||
/// Performance has been reduced due to the computer's lap detection function,
|
||||
/// which attempts to keep the computer from getting too hot while on your lap.
|
||||
LapDetected = 1,
|
||||
/// Performance has been reduced due to high system temperatures.
|
||||
HighTemperature = 2,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
|
||||
// clang-format off
|
||||
Q_INVOKABLE static QString toString(qs::service::upower::PerformanceDegradationReason::Enum reason);
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
class PowerProfileHold;
|
||||
|
||||
} // namespace qs::service::upower
|
||||
|
||||
namespace qs::dbus {
|
||||
|
||||
template <>
|
||||
struct DBusDataTransform<qs::service::upower::PowerProfile::Enum> {
|
||||
using Wire = QString;
|
||||
using Data = qs::service::upower::PowerProfile::Enum;
|
||||
static DBusResult<Data> fromWire(const Wire& wire);
|
||||
static Wire toWire(Data data);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct DBusDataTransform<QList<qs::service::upower::PowerProfile::Enum>> {
|
||||
using Wire = QList<QVariantMap>;
|
||||
using Data = QList<qs::service::upower::PowerProfile::Enum>;
|
||||
static DBusResult<Data> fromWire(const Wire& wire);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct DBusDataTransform<qs::service::upower::PerformanceDegradationReason::Enum> {
|
||||
using Wire = QString;
|
||||
using Data = qs::service::upower::PerformanceDegradationReason::Enum;
|
||||
static DBusResult<Data> fromWire(const Wire& wire);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct DBusDataTransform<QList<qs::service::upower::PowerProfileHold>> {
|
||||
using Wire = QList<QVariantMap>;
|
||||
using Data = QList<qs::service::upower::PowerProfileHold>;
|
||||
static DBusResult<Data> fromWire(const Wire& wire);
|
||||
};
|
||||
|
||||
} // namespace qs::dbus
|
||||
|
||||
namespace qs::service::upower {
|
||||
|
||||
// docgen can't hit gadgets yet
|
||||
class PowerProfileHold {
|
||||
Q_GADGET;
|
||||
QML_VALUE_TYPE(powerProfileHold);
|
||||
Q_PROPERTY(qs::service::upower::PowerProfile::Enum profile MEMBER profile CONSTANT);
|
||||
Q_PROPERTY(QString applicationId MEMBER applicationId CONSTANT);
|
||||
Q_PROPERTY(QString reason MEMBER reason CONSTANT);
|
||||
|
||||
public:
|
||||
explicit PowerProfileHold() = default;
|
||||
explicit PowerProfileHold(PowerProfile::Enum profile, QString applicationId, QString reason)
|
||||
: profile(profile)
|
||||
, applicationId(std::move(applicationId))
|
||||
, reason(std::move(reason)) {}
|
||||
|
||||
PowerProfile::Enum profile = PowerProfile::Balanced;
|
||||
QString applicationId;
|
||||
QString reason;
|
||||
|
||||
[[nodiscard]] bool operator==(const PowerProfileHold& other) const;
|
||||
};
|
||||
|
||||
QDebug& operator<<(QDebug& debug, const PowerProfileHold& hold);
|
||||
|
||||
class PowerProfiles: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
void setProfile(PowerProfile::Enum profile);
|
||||
|
||||
// clang-format off
|
||||
Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(PowerProfiles, PowerProfile::Enum, bProfile, PowerProfile::Balanced);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(PowerProfiles, bool, bHasPerformanceProfile);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(PowerProfiles, PerformanceDegradationReason::Enum, bDegradationReason);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(PowerProfiles, QList<PowerProfileHold>, bHolds);
|
||||
// clang-format on
|
||||
|
||||
static PowerProfiles* instance();
|
||||
|
||||
private:
|
||||
explicit PowerProfiles();
|
||||
void init();
|
||||
|
||||
// clang-format off
|
||||
Q_OBJECT_BINDABLE_PROPERTY(PowerProfiles, QList<PowerProfile::Enum>, bProfiles);
|
||||
|
||||
QS_DBUS_BINDABLE_PROPERTY_GROUP(PowerProfiles, properties);
|
||||
QS_DBUS_PROPERTY_BINDING(PowerProfiles, pProfile, bProfile, properties, "ActiveProfile");
|
||||
QS_DBUS_PROPERTY_BINDING(PowerProfiles, pProfiles, bProfiles, properties, "Profiles");
|
||||
QS_DBUS_PROPERTY_BINDING(PowerProfiles, pPerformanceDegraded, bDegradationReason, properties, "PerformanceDegraded");
|
||||
QS_DBUS_PROPERTY_BINDING(PowerProfiles, pHolds, bHolds, properties, "ActiveProfileHolds");
|
||||
// clang-format on
|
||||
|
||||
QDBusInterface* service = nullptr;
|
||||
};
|
||||
|
||||
///! Provides access to the Power Profiles service.
|
||||
/// An interface to the UPower [power profiles daemon], which can be
|
||||
/// used to view and manage power profiles.
|
||||
///
|
||||
/// > [!NOTE] The power profiles daemon must be installed to use this service.
|
||||
/// > Installing UPower does not necessarily install the power profiles daemon.
|
||||
///
|
||||
/// [power profiles daemon]: https://gitlab.freedesktop.org/upower/power-profiles-daemon
|
||||
class PowerProfilesQml: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_NAMED_ELEMENT(PowerProfiles);
|
||||
QML_SINGLETON;
|
||||
// clang-format off
|
||||
/// The current power profile.
|
||||
///
|
||||
/// This property may be set to change the system's power profile, however
|
||||
/// it cannot be set to `Performance` unless @@hasPerformanceProfile is true.
|
||||
Q_PROPERTY(qs::service::upower::PowerProfile::Enum profile READ default WRITE setProfile NOTIFY profileChanged BINDABLE bindableProfile);
|
||||
/// If the system has a performance profile.
|
||||
///
|
||||
/// If this property is false, your system does not have a performance
|
||||
/// profile known to power-profiles-daemon.
|
||||
Q_PROPERTY(bool hasPerformanceProfile READ default NOTIFY hasPerformanceProfileChanged BINDABLE bindableHasPerformanceProfile);
|
||||
/// If power-profiles-daemon detects degraded system performance, the reason
|
||||
/// for the degradation will be present here.
|
||||
Q_PROPERTY(qs::service::upower::PerformanceDegradationReason::Enum degradationReason READ default NOTIFY degradationReasonChanged BINDABLE bindableDegradationReason);
|
||||
/// Power profile holds created by other applications.
|
||||
///
|
||||
/// This property returns a `powerProfileHold` object, which has the following properties.
|
||||
/// - `profile` - The @@PowerProfile held by the application.
|
||||
/// - `applicationId` - A string identifying the application
|
||||
/// - `reason` - The reason the application has given for holding the profile.
|
||||
///
|
||||
/// Applications may "hold" a power profile in place for their lifetime, such
|
||||
/// as a game holding Performance mode or a system daemon holding Power Saver mode
|
||||
/// when reaching a battery threshold. If the user selects a different profile explicitly
|
||||
/// (e.g. by setting @@profile$) all holds will be removed.
|
||||
///
|
||||
/// Multiple applications may hold a power profile, however if multiple applications request
|
||||
/// profiles than `PowerSaver` will win over `Performance`. Only `Performance` and `PowerSaver`
|
||||
/// profiles may be held.
|
||||
Q_PROPERTY(QList<qs::service::upower::PowerProfileHold> holds READ default NOTIFY holdsChanged BINDABLE bindableHolds);
|
||||
// clang-format on
|
||||
|
||||
signals:
|
||||
void profileChanged();
|
||||
void hasPerformanceProfileChanged();
|
||||
void degradationReasonChanged();
|
||||
void holdsChanged();
|
||||
|
||||
public:
|
||||
explicit PowerProfilesQml(QObject* parent = nullptr);
|
||||
|
||||
[[nodiscard]] QBindable<PowerProfile::Enum> bindableProfile() const { return &this->bProfile; }
|
||||
|
||||
static void setProfile(PowerProfile::Enum profile) {
|
||||
PowerProfiles::instance()->setProfile(profile);
|
||||
}
|
||||
|
||||
[[nodiscard]] QBindable<bool> bindableHasPerformanceProfile() const {
|
||||
return &this->bHasPerformanceProfile;
|
||||
}
|
||||
|
||||
[[nodiscard]] QBindable<PerformanceDegradationReason::Enum> bindableDegradationReason() const {
|
||||
return &this->bDegradationReason;
|
||||
}
|
||||
|
||||
[[nodiscard]] QBindable<QList<PowerProfileHold>> bindableHolds() const { return &this->bHolds; }
|
||||
|
||||
private:
|
||||
// clang-format off
|
||||
Q_OBJECT_BINDABLE_PROPERTY(PowerProfilesQml, PowerProfile::Enum, bProfile, &PowerProfilesQml::profileChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(PowerProfilesQml, bool, bHasPerformanceProfile, &PowerProfilesQml::hasPerformanceProfileChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(PowerProfilesQml, PerformanceDegradationReason::Enum, bDegradationReason, &PowerProfilesQml::degradationReasonChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(PowerProfilesQml, QList<PowerProfileHold>, bHolds, &PowerProfilesQml::holdsChanged);
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
} // namespace qs::service::upower
|
Loading…
Add table
Add a link
Reference in a new issue