Skip to content

Commit 67cd0cd

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 76a0315 commit 67cd0cd

8 files changed

+453
-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: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
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+
namespace LXQt
37+
{
38+
Q_GLOBAL_STATIC(LXQt::PowerProfiles, g_power_profiles)
39+
40+
const QString PowerProfiles::msDBusPPService = QStringLiteral("org.freedesktop.UPower.PowerProfiles");
41+
const QString PowerProfiles::msDBusPPPath = QStringLiteral("/org/freedesktop/UPower/PowerProfiles");
42+
const QString PowerProfiles::msDBusPPInterface = msDBusPPService;
43+
const QString PowerProfiles::msDBusPPProperties[] = { QStringLiteral("ActiveProfile"), QStringLiteral("Profiles") };
44+
45+
PowerProfiles & PowerProfiles::instance()
46+
{
47+
return *g_power_profiles;
48+
}
49+
50+
PowerProfiles::PowerProfiles(QObject * parent/* = nullptr*/)
51+
: QObject{parent}
52+
, mThread{new QThread}
53+
, mPropGetter{new DBusPropAsyncGetter{msDBusPPService, msDBusPPPath, msDBusPPInterface, QDBusConnection::systemBus()}}
54+
, mMenu{new QMenu}
55+
56+
{
57+
mMenu->menuAction()->setIcon(QIcon::fromTheme(QStringLiteral("preferences-system-performance")));
58+
mMenu->setTitle(tr("Power profile"));
59+
60+
// register the DBus types
61+
qRegisterMetaType<QListOfQVariantMap>("QListOfQVariantMap");
62+
qDBusRegisterMetaType<QListOfQVariantMap>();
63+
64+
// we want to dispatch DBus calls on dedicated thread
65+
mPropGetter->moveToThread(mThread.get());
66+
67+
// signals forwarding
68+
connect(mPropGetter.get(), &DBusPropAsyncGetter::fetched, this, &PowerProfiles::propertyFetched);
69+
connect(mPropGetter.get(), &DBusPropAsyncGetter::pushed, this, &PowerProfiles::propertyPushed);
70+
71+
// interested signals for our processing
72+
connect(mPropGetter.get(), &DBusPropAsyncGetter::fetched, this, &PowerProfiles::onFetched);
73+
connect(mPropGetter.get(), &DBusPropAsyncGetter::serviceDisappeared, this, [this] { reassembleMenu({}); });
74+
75+
// our generated signals for dispatching into dedicated thread
76+
connect(this, &PowerProfiles::needPropertyFetch, mPropGetter.get(), &DBusPropAsyncGetter::fetch);
77+
connect(this, &PowerProfiles::needPropertyPush, mPropGetter.get(), &DBusPropAsyncGetter::push);
78+
79+
mThread->start();
80+
81+
reassembleMenu({});
82+
83+
Q_EMIT needPropertyFetch(msDBusPPProperties[PPP_ActiveProfile]);
84+
Q_EMIT needPropertyFetch(msDBusPPProperties[PPP_Profiles]);
85+
}
86+
87+
PowerProfiles::~PowerProfiles()
88+
{
89+
mThread->quit();
90+
mThread->wait();
91+
}
92+
93+
QMenu * PowerProfiles::menu()
94+
{
95+
return mMenu.get();
96+
}
97+
98+
void PowerProfiles::onFetched(const QString name, const QVariant value)
99+
{
100+
if (name == msDBusPPProperties[PPP_ActiveProfile]) {
101+
const QString profile = qdbus_cast<QString>(value);
102+
if (mActiveProfile == profile)
103+
return;
104+
mActiveProfile = profile;
105+
if (mActions)
106+
for (auto const & action : mActions->actions())
107+
action->setChecked(get<QString>(action->data()) == mActiveProfile);
108+
} else if (name == msDBusPPProperties[PPP_Profiles]) {
109+
reassembleMenu(qdbus_cast<QListOfQVariantMap>(value));
110+
}
111+
}
112+
113+
void PowerProfiles::reassembleMenu(const QListOfQVariantMap & profiles)
114+
{
115+
mMenu->clear();
116+
if (profiles.empty())
117+
{
118+
mActions.reset(nullptr);
119+
mMenu->addAction(tr("power-profiles-daemon not available"))->setDisabled(true);
120+
return;
121+
}
122+
123+
mActions.reset(new QActionGroup(nullptr));
124+
mActions->setExclusionPolicy(QActionGroup::ExclusionPolicy::ExclusiveOptional);
125+
126+
connect(mActions.get(), &QActionGroup::triggered, this, [this] (const QAction * a) {
127+
setActiveProfile(get<QString>(a->data()));
128+
});
129+
130+
for (auto const & profile : profiles)
131+
{
132+
const QString p = qdbus_cast<QString>(profile[QStringLiteral("Profile")]);
133+
auto a = new QAction(p, mActions.get());
134+
a->setCheckable(true);
135+
a->setData(p);
136+
a->setChecked(mActiveProfile == p);
137+
}
138+
mMenu->addActions(mActions->actions());
139+
}
140+
141+
const QString & PowerProfiles::activeProfile() const
142+
{
143+
return mActiveProfile;
144+
}
145+
146+
void PowerProfiles::setActiveProfile(const QString &value)
147+
{
148+
// Note: Just emit the signal to make change. Don't modify any internal state.
149+
// We will be notified by the fetched() signal upon sucessfull PropertyChanged.
150+
Q_EMIT needPropertyPush(msDBusPPProperties[PPP_ActiveProfile], QVariant::fromValue(QDBusVariant{value}));
151+
}
152+
}

0 commit comments

Comments
 (0)