Skip to content

Commit 4e16e37

Browse files
committed
powerprofiles: Add support for power-profiles actions
The power profiles setting is made available by freedesktop's power-profiles-daemon. We just provide basic functionality in case DBus interface is available in runtime.
1 parent b7cb200 commit 4e16e37

8 files changed

+438
-0
lines changed

src/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ set(SOURCES
1717
iconproducer.cpp
1818
powerbutton.cpp
1919
../config/powermanagementsettings.cpp
20+
PowerProfiles.cpp
21+
DBusPropAsyncGetter.cpp
2022
)
2123

2224
set(UI_FILES

src/DBusPropAsyncGetter.cpp

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/* BEGIN_COMMON_COPYRIGHT_HEADER
2+
* (c)LGPL2+
3+
*
4+
* LXQt - a lightweight, Qt based, desktop toolset
5+
* https://lxqt.org
6+
*
7+
* Copyright: 2025~ LXQt team
8+
* Authors:
9+
* Palo Kisa <[email protected]>
10+
*
11+
* This program or library is free software; you can redistribute it
12+
* and/or modify it under the terms of the GNU Lesser General Public
13+
* License as published by the Free Software Foundation; either
14+
* version 2.1 of the License, or (at your option) any later version.
15+
*
16+
* This library is distributed in the hope that it will be useful,
17+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
18+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19+
* Lesser General Public License for more details.
20+
21+
* You should have received a copy of the GNU Lesser General
22+
* Public License along with this library; if not, write to the
23+
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24+
* Boston, MA 02110-1301 USA
25+
*
26+
* END_COMMON_COPYRIGHT_HEADER */
27+
28+
#include "DBusPropAsyncGetter.h"
29+
#include <QDBusMessage>
30+
#include <QDBusPendingCallWatcher>
31+
#include <QDBusPendingReply>
32+
#include <QDBusServiceWatcher>
33+
34+
using namespace Qt::Literals::StringLiterals;
35+
36+
namespace LXQt
37+
{
38+
DBusPropAsyncGetter::DBusPropAsyncGetter(const QString & service, const QString & path, const QString & interface, const QDBusConnection & conn)
39+
: mService{service}
40+
, mPath{path}
41+
, mInterface{interface}
42+
, mConn{conn}
43+
{
44+
if (!mConn.connect(mService
45+
, mPath
46+
, "org.freedesktop.DBus.Properties"_L1
47+
, "PropertiesChanged"_L1
48+
, this
49+
, SLOT(onPropertiesChanged(QString, QVariantMap, QStringList))
50+
)
51+
)
52+
qDebug().noquote().nospace() << "Could not connect to org.freedesktop.DBus.Properties.PropertiesChanged()(" << mService << ',' << mPath << ')';
53+
54+
connect(new QDBusServiceWatcher{mService, mConn, QDBusServiceWatcher::WatchForUnregistration, this}
55+
, &QDBusServiceWatcher::serviceUnregistered
56+
, this
57+
, [this] { Q_EMIT serviceDisappeared(); }
58+
);
59+
}
60+
61+
void DBusPropAsyncGetter::fetch(const QString name)
62+
{
63+
QDBusMessage msg = QDBusMessage::createMethodCall(mService, mPath, "org.freedesktop.DBus.Properties"_L1, "Get"_L1);
64+
msg << mInterface << name;
65+
connect(new QDBusPendingCallWatcher{mConn.asyncCall(msg), this}
66+
, &QDBusPendingCallWatcher::finished
67+
, this
68+
, [this, name] (QDBusPendingCallWatcher * call) {
69+
QDBusPendingReply<QVariant> reply = *call;
70+
if (reply.isError())
71+
qDebug().noquote().nospace() << "Error on DBus request(" << mService << ',' << mPath << ',' << name << "): " << reply.error();
72+
Q_EMIT fetched(name, reply.value());
73+
call->deleteLater();
74+
}
75+
);
76+
}
77+
78+
void DBusPropAsyncGetter::push(const QString name, const QVariant value)
79+
{
80+
QDBusMessage msg = QDBusMessage::createMethodCall(mService, mPath, "org.freedesktop.DBus.Properties"_L1, "Set"_L1);
81+
msg << mInterface << name << value;
82+
connect(new QDBusPendingCallWatcher{mConn.asyncCall(msg), this}
83+
, &QDBusPendingCallWatcher::finished
84+
, this
85+
, [this, name] (QDBusPendingCallWatcher * call) {
86+
QDBusPendingReply<> reply = *call;
87+
if (reply.isError())
88+
qDebug().noquote().nospace() << "Error on DBus request(" << mService << ',' << mPath << ',' << mInterface << ',' << name << "): " << reply.error();
89+
else
90+
Q_EMIT pushed(name);
91+
call->deleteLater();
92+
}
93+
);
94+
}
95+
96+
void DBusPropAsyncGetter::onPropertiesChanged(const QString & /*interfaceName*/, const QVariantMap & changedProperties, const QStringList & invalidatedProperties)
97+
{
98+
for (const auto & [key, value] : changedProperties.asKeyValueRange())
99+
Q_EMIT fetched(key, value);
100+
for (const auto & key : invalidatedProperties)
101+
Q_EMIT fetched(key, {});
102+
}
103+
}

src/DBusPropAsyncGetter.h

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/* BEGIN_COMMON_COPYRIGHT_HEADER
2+
* (c)LGPL2+
3+
*
4+
* LXQt - a lightweight, Qt based, desktop toolset
5+
* https://lxqt.org
6+
*
7+
* Copyright: 2025~ LXQt team
8+
* Authors:
9+
* Palo Kisa <[email protected]>
10+
*
11+
* This program or library is free software; you can redistribute it
12+
* and/or modify it under the terms of the GNU Lesser General Public
13+
* License as published by the Free Software Foundation; either
14+
* version 2.1 of the License, or (at your option) any later version.
15+
*
16+
* This library is distributed in the hope that it will be useful,
17+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
18+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19+
* Lesser General Public License for more details.
20+
21+
* You should have received a copy of the GNU Lesser General
22+
* Public License along with this library; if not, write to the
23+
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24+
* Boston, MA 02110-1301 USA
25+
*
26+
* END_COMMON_COPYRIGHT_HEADER */
27+
28+
#pragma once
29+
30+
#include <QObject>
31+
#include <QDBusConnection>
32+
33+
namespace LXQt
34+
{
35+
class DBusPropAsyncGetter : public QObject
36+
{
37+
Q_OBJECT
38+
public:
39+
DBusPropAsyncGetter(const QString & service, const QString & path, const QString & interface, const QDBusConnection & conn);
40+
41+
public Q_SLOTS:
42+
// Note: interthread signal/slot communication - parameters by values
43+
void fetch(const QString name);
44+
void push(const QString name, const QVariant value);
45+
46+
public Q_SLOTS:
47+
void onPropertiesChanged(const QString & interfaceName, const QVariantMap & changedProperties, const QStringList & invalidatedProperties);
48+
49+
Q_SIGNALS:
50+
// Note: interthread signal/slot communication - parameters by values
51+
void fetched(const QString name, const QVariant value);
52+
void pushed(const QString name);
53+
void serviceDisappeared();
54+
55+
private:
56+
const QString mService;
57+
const QString mPath;
58+
const QString mInterface;
59+
QDBusConnection mConn;
60+
};
61+
}

src/PowerProfiles.cpp

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
/* BEGIN_COMMON_COPYRIGHT_HEADER
2+
* (c)LGPL2+
3+
*
4+
* LXQt - a lightweight, Qt based, desktop toolset
5+
* https://lxqt.org
6+
*
7+
* Copyright: 2025~ LXQt team
8+
* Authors:
9+
* Palo Kisa <[email protected]>
10+
*
11+
* This program or library is free software; you can redistribute it
12+
* and/or modify it under the terms of the GNU Lesser General Public
13+
* License as published by the Free Software Foundation; either
14+
* version 2.1 of the License, or (at your option) any later version.
15+
*
16+
* This library is distributed in the hope that it will be useful,
17+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
18+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19+
* Lesser General Public License for more details.
20+
21+
* You should have received a copy of the GNU Lesser General
22+
* Public License along with this library; if not, write to the
23+
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24+
* Boston, MA 02110-1301 USA
25+
*
26+
* END_COMMON_COPYRIGHT_HEADER */
27+
28+
#include <QMenu>
29+
#include <QActionGroup>
30+
#include <QAction>
31+
#include <QThread>
32+
#include <QDBusMetaType>
33+
#include "PowerProfiles.h"
34+
#include "DBusPropAsyncGetter.h"
35+
36+
Q_GLOBAL_STATIC(LXQt::PowerProfiles, g_power_profiles)
37+
38+
using namespace Qt::Literals::StringLiterals;
39+
40+
namespace LXQt
41+
{
42+
PowerProfiles & PowerProfiles::instance()
43+
{
44+
return *g_power_profiles;
45+
}
46+
47+
PowerProfiles::PowerProfiles(QObject * parent/* = nullptr*/)
48+
: QObject{parent}
49+
, mThread{new QThread}
50+
, mPropGetter{new DBusPropAsyncGetter{"org.freedesktop.UPower.PowerProfiles"_L1, "/org/freedesktop/UPower/PowerProfiles"_L1
51+
, "org.freedesktop.UPower.PowerProfiles"_L1, QDBusConnection::systemBus()}}
52+
, mMenu{new QMenu}
53+
54+
{
55+
mMenu->menuAction()->setIcon(QIcon::fromTheme(QStringLiteral("preferences-system-performance")));
56+
mMenu->setTitle(tr("Power profile"));
57+
58+
// register the DBus types
59+
qRegisterMetaType<QListOfQVariantMap>("QListOfQVariantMap");
60+
qDBusRegisterMetaType<QListOfQVariantMap>();
61+
62+
// we want to dispatch DBus calls on dedicated thread
63+
mPropGetter->moveToThread(mThread.get());
64+
65+
// signals forwarding
66+
connect(mPropGetter.get(), &DBusPropAsyncGetter::fetched, this, &PowerProfiles::propertyFetched);
67+
connect(mPropGetter.get(), &DBusPropAsyncGetter::pushed, this, &PowerProfiles::propertyPushed);
68+
69+
// interested signals for our processing
70+
connect(mPropGetter.get(), &DBusPropAsyncGetter::fetched, this, &PowerProfiles::onFetched);
71+
connect(mPropGetter.get(), &DBusPropAsyncGetter::serviceDisappeared, this, [this] { reassembleMenu({}); });
72+
73+
// our generated signals for dispatching into dedicated thread
74+
connect(this, &PowerProfiles::needPropertyFetch, mPropGetter.get(), &DBusPropAsyncGetter::fetch);
75+
connect(this, &PowerProfiles::needPropertyPush, mPropGetter.get(), &DBusPropAsyncGetter::push);
76+
77+
mThread->start();
78+
79+
reassembleMenu({});
80+
81+
Q_EMIT needPropertyFetch("ActiveProfile"_L1);
82+
Q_EMIT needPropertyFetch("Profiles"_L1);
83+
}
84+
85+
PowerProfiles::~PowerProfiles()
86+
{
87+
mThread->quit();
88+
mThread->wait();
89+
}
90+
91+
QMenu * PowerProfiles::menu()
92+
{
93+
return mMenu.get();
94+
}
95+
96+
void PowerProfiles::onFetched(const QString name, const QVariant value)
97+
{
98+
if (name == "ActiveProfile"_L1) {
99+
const QString profile = qdbus_cast<QString>(value);
100+
if (mActiveProfile == profile)
101+
return;
102+
mActiveProfile = profile;
103+
if (mActions)
104+
for (auto const & action : mActions->actions())
105+
action->setChecked(get<QString>(action->data()) == mActiveProfile);
106+
} else if (name == "Profiles"_L1) {
107+
reassembleMenu(qdbus_cast<QListOfQVariantMap>(value));
108+
}
109+
}
110+
111+
void PowerProfiles::reassembleMenu(const QListOfQVariantMap & profiles)
112+
{
113+
mMenu->clear();
114+
if (profiles.empty())
115+
{
116+
mActions.reset(nullptr);
117+
mMenu->addAction(tr("power-profiles-daemon not available"))->setDisabled(true);
118+
return;
119+
}
120+
121+
mActions.reset(new QActionGroup(nullptr));
122+
mActions->setExclusionPolicy(QActionGroup::ExclusionPolicy::ExclusiveOptional);
123+
124+
connect(mActions.get(), &QActionGroup::triggered, this, [this] (const QAction * a) {
125+
setActiveProfile(get<QString>(a->data()));
126+
});
127+
128+
for (auto const & profile : profiles)
129+
{
130+
const QString p = qdbus_cast<QString>(profile[QStringLiteral("Profile")]);
131+
auto a = new QAction(tr(p.toStdString().c_str(), "power-profile"), mActions.get());
132+
a->setCheckable(true);
133+
a->setData(p);
134+
a->setChecked(mActiveProfile == p);
135+
}
136+
mMenu->addActions(mActions->actions());
137+
}
138+
139+
const QString & PowerProfiles::activeProfile() const
140+
{
141+
return mActiveProfile;
142+
}
143+
144+
void PowerProfiles::setActiveProfile(const QString &value)
145+
{
146+
// Note: Just emit the signal to make change. Don't modify any internal state.
147+
// We will be notified by the fetched() signal upon sucessfull PropertyChanged.
148+
Q_EMIT needPropertyPush("ActiveProfile"_L1, QVariant::fromValue(QDBusVariant{value}));
149+
}
150+
}

0 commit comments

Comments
 (0)