-
Notifications
You must be signed in to change notification settings - Fork 27
Power profiles #426
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Power profiles #426
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
76a0315
trayicon: Use QIcon::fromTheme()
palinek 67cd0cd
powerprofiles: Add support for power-profiles actions
palinek 1222635
powerprofiles: Add profile names translation support
palinek 6e973ca
powerprofiles: Hide power profiles menu, when API not available
palinek File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
/* BEGIN_COMMON_COPYRIGHT_HEADER | ||
* (c)LGPL2+ | ||
* | ||
* LXQt - a lightweight, Qt based, desktop toolset | ||
* https://lxqt.org | ||
* | ||
* Copyright: 2025~ LXQt team | ||
* Authors: | ||
* Palo Kisa <[email protected]> | ||
* | ||
* This program or library is free software; you can redistribute it | ||
* and/or modify it under the terms of the GNU Lesser General Public | ||
* License as published by the Free Software Foundation; either | ||
* version 2.1 of the License, or (at your option) any later version. | ||
* | ||
* This library 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 | ||
* Lesser General Public License for more details. | ||
|
||
* You should have received a copy of the GNU Lesser General | ||
* Public License along with this library; if not, write to the | ||
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||
* Boston, MA 02110-1301 USA | ||
* | ||
* END_COMMON_COPYRIGHT_HEADER */ | ||
|
||
#include "DBusPropAsyncGetter.h" | ||
#include <QDBusMessage> | ||
#include <QDBusPendingCallWatcher> | ||
#include <QDBusPendingReply> | ||
#include <QDBusServiceWatcher> | ||
|
||
using namespace Qt::Literals::StringLiterals; | ||
|
||
namespace LXQt | ||
{ | ||
DBusPropAsyncGetter::DBusPropAsyncGetter(const QString & service, const QString & path, const QString & interface, const QDBusConnection & conn) | ||
: mService{service} | ||
, mPath{path} | ||
, mInterface{interface} | ||
, mConn{conn} | ||
{ | ||
if (!mConn.connect(mService | ||
, mPath | ||
, "org.freedesktop.DBus.Properties"_L1 | ||
, "PropertiesChanged"_L1 | ||
, this | ||
, SLOT(onPropertiesChanged(QString, QVariantMap, QStringList)) | ||
) | ||
) | ||
qDebug().noquote().nospace() << "Could not connect to org.freedesktop.DBus.Properties.PropertiesChanged()(" << mService << ',' << mPath << ')'; | ||
|
||
connect(new QDBusServiceWatcher{mService, mConn, QDBusServiceWatcher::WatchForUnregistration, this} | ||
, &QDBusServiceWatcher::serviceUnregistered | ||
, this | ||
, [this] { Q_EMIT serviceDisappeared(); } | ||
); | ||
} | ||
|
||
void DBusPropAsyncGetter::fetch(const QString name) | ||
{ | ||
QDBusMessage msg = QDBusMessage::createMethodCall(mService, mPath, "org.freedesktop.DBus.Properties"_L1, "Get"_L1); | ||
msg << mInterface << name; | ||
connect(new QDBusPendingCallWatcher{mConn.asyncCall(msg), this} | ||
, &QDBusPendingCallWatcher::finished | ||
, this | ||
, [this, name] (QDBusPendingCallWatcher * call) { | ||
QDBusPendingReply<QVariant> reply = *call; | ||
if (reply.isError()) | ||
qDebug().noquote().nospace() << "Error on DBus request(" << mService << ',' << mPath << ',' << name << "): " << reply.error(); | ||
Q_EMIT fetched(name, reply.value()); | ||
call->deleteLater(); | ||
} | ||
); | ||
} | ||
|
||
void DBusPropAsyncGetter::push(const QString name, const QVariant value) | ||
{ | ||
QDBusMessage msg = QDBusMessage::createMethodCall(mService, mPath, "org.freedesktop.DBus.Properties"_L1, "Set"_L1); | ||
msg << mInterface << name << value; | ||
connect(new QDBusPendingCallWatcher{mConn.asyncCall(msg), this} | ||
, &QDBusPendingCallWatcher::finished | ||
, this | ||
, [this, name] (QDBusPendingCallWatcher * call) { | ||
QDBusPendingReply<> reply = *call; | ||
if (reply.isError()) | ||
qDebug().noquote().nospace() << "Error on DBus request(" << mService << ',' << mPath << ',' << mInterface << ',' << name << "): " << reply.error(); | ||
else | ||
Q_EMIT pushed(name); | ||
call->deleteLater(); | ||
} | ||
); | ||
} | ||
|
||
void DBusPropAsyncGetter::onPropertiesChanged(const QString & /*interfaceName*/, const QVariantMap & changedProperties, const QStringList & invalidatedProperties) | ||
{ | ||
for (const auto & [key, value] : changedProperties.asKeyValueRange()) | ||
Q_EMIT fetched(key, value); | ||
for (const auto & key : invalidatedProperties) | ||
Q_EMIT fetched(key, {}); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
/* BEGIN_COMMON_COPYRIGHT_HEADER | ||
* (c)LGPL2+ | ||
* | ||
* LXQt - a lightweight, Qt based, desktop toolset | ||
* https://lxqt.org | ||
* | ||
* Copyright: 2025~ LXQt team | ||
* Authors: | ||
* Palo Kisa <[email protected]> | ||
* | ||
* This program or library is free software; you can redistribute it | ||
* and/or modify it under the terms of the GNU Lesser General Public | ||
* License as published by the Free Software Foundation; either | ||
* version 2.1 of the License, or (at your option) any later version. | ||
* | ||
* This library 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 | ||
* Lesser General Public License for more details. | ||
|
||
* You should have received a copy of the GNU Lesser General | ||
* Public License along with this library; if not, write to the | ||
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||
* Boston, MA 02110-1301 USA | ||
* | ||
* END_COMMON_COPYRIGHT_HEADER */ | ||
|
||
#pragma once | ||
|
||
#include <QObject> | ||
#include <QDBusConnection> | ||
|
||
namespace LXQt | ||
{ | ||
class DBusPropAsyncGetter : public QObject | ||
{ | ||
Q_OBJECT | ||
public: | ||
DBusPropAsyncGetter(const QString & service, const QString & path, const QString & interface, const QDBusConnection & conn); | ||
|
||
public Q_SLOTS: | ||
// Note: interthread signal/slot communication - parameters by values | ||
void fetch(const QString name); | ||
void push(const QString name, const QVariant value); | ||
|
||
public Q_SLOTS: | ||
void onPropertiesChanged(const QString & interfaceName, const QVariantMap & changedProperties, const QStringList & invalidatedProperties); | ||
|
||
Q_SIGNALS: | ||
// Note: interthread signal/slot communication - parameters by values | ||
void fetched(const QString name, const QVariant value); | ||
void pushed(const QString name); | ||
void serviceDisappeared(); | ||
|
||
private: | ||
const QString mService; | ||
const QString mPath; | ||
const QString mInterface; | ||
QDBusConnection mConn; | ||
}; | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
/* BEGIN_COMMON_COPYRIGHT_HEADER | ||
* (c)LGPL2+ | ||
* | ||
* LXQt - a lightweight, Qt based, desktop toolset | ||
* https://lxqt.org | ||
* | ||
* Copyright: 2025~ LXQt team | ||
* Authors: | ||
* Palo Kisa <[email protected]> | ||
* | ||
* This program or library is free software; you can redistribute it | ||
* and/or modify it under the terms of the GNU Lesser General Public | ||
* License as published by the Free Software Foundation; either | ||
* version 2.1 of the License, or (at your option) any later version. | ||
* | ||
* This library 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 | ||
* Lesser General Public License for more details. | ||
|
||
* You should have received a copy of the GNU Lesser General | ||
* Public License along with this library; if not, write to the | ||
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||
* Boston, MA 02110-1301 USA | ||
* | ||
* END_COMMON_COPYRIGHT_HEADER */ | ||
|
||
#include <QMenu> | ||
#include <QActionGroup> | ||
#include <QAction> | ||
#include <QThread> | ||
#include <QDBusMetaType> | ||
#include "PowerProfiles.h" | ||
#include "DBusPropAsyncGetter.h" | ||
|
||
namespace { | ||
using Trans = struct { char const * const s1; char const * const s2; }; | ||
[[maybe_unused]] void just_for_translations_dummy_function(const Trans = QT_TRANSLATE_NOOP3("LXQt::PowerProfiles", "power-saver", "power-profiles-daemon") | ||
, const Trans = QT_TRANSLATE_NOOP3("LXQt::PowerProfiles", "balanced", "power-profiles-daemon") | ||
, const Trans = QT_TRANSLATE_NOOP3("LXQt::PowerProfiles", "performance", "power-profiles-daemon") | ||
) | ||
{} | ||
} | ||
|
||
namespace LXQt | ||
{ | ||
Q_GLOBAL_STATIC(LXQt::PowerProfiles, g_power_profiles) | ||
|
||
const QString PowerProfiles::msDBusPPService = QStringLiteral("org.freedesktop.UPower.PowerProfiles"); | ||
const QString PowerProfiles::msDBusPPPath = QStringLiteral("/org/freedesktop/UPower/PowerProfiles"); | ||
const QString PowerProfiles::msDBusPPInterface = msDBusPPService; | ||
const QString PowerProfiles::msDBusPPProperties[] = { QStringLiteral("ActiveProfile"), QStringLiteral("Profiles") }; | ||
|
||
PowerProfiles & PowerProfiles::instance() | ||
{ | ||
return *g_power_profiles; | ||
} | ||
|
||
PowerProfiles::PowerProfiles(QObject * parent/* = nullptr*/) | ||
: QObject{parent} | ||
, mThread{new QThread} | ||
, mPropGetter{new DBusPropAsyncGetter{msDBusPPService, msDBusPPPath, msDBusPPInterface, QDBusConnection::systemBus()}} | ||
, mMenuAction{new QAction} | ||
, mMenu{new QMenu} | ||
|
||
{ | ||
mMenuAction->setMenu(mMenu.get()); | ||
mMenu->menuAction()->setIcon(QIcon::fromTheme(QStringLiteral("preferences-system-performance"))); | ||
mMenu->setTitle(tr("Power profile")); | ||
|
||
// register the DBus types | ||
qRegisterMetaType<QListOfQVariantMap>("QListOfQVariantMap"); | ||
qDBusRegisterMetaType<QListOfQVariantMap>(); | ||
|
||
// we want to dispatch DBus calls on dedicated thread | ||
mPropGetter->moveToThread(mThread.get()); | ||
|
||
// signals forwarding | ||
connect(mPropGetter.get(), &DBusPropAsyncGetter::fetched, this, &PowerProfiles::propertyFetched); | ||
connect(mPropGetter.get(), &DBusPropAsyncGetter::pushed, this, &PowerProfiles::propertyPushed); | ||
|
||
// interested signals for our processing | ||
connect(mPropGetter.get(), &DBusPropAsyncGetter::fetched, this, &PowerProfiles::onFetched); | ||
connect(mPropGetter.get(), &DBusPropAsyncGetter::serviceDisappeared, this, [this] { reassembleMenu({}); }); | ||
|
||
// our generated signals for dispatching into dedicated thread | ||
connect(this, &PowerProfiles::needPropertyFetch, mPropGetter.get(), &DBusPropAsyncGetter::fetch); | ||
connect(this, &PowerProfiles::needPropertyPush, mPropGetter.get(), &DBusPropAsyncGetter::push); | ||
|
||
mThread->start(); | ||
|
||
reassembleMenu({}); | ||
|
||
Q_EMIT needPropertyFetch(msDBusPPProperties[PPP_ActiveProfile]); | ||
Q_EMIT needPropertyFetch(msDBusPPProperties[PPP_Profiles]); | ||
} | ||
|
||
PowerProfiles::~PowerProfiles() | ||
{ | ||
mThread->quit(); | ||
mThread->wait(); | ||
} | ||
|
||
QAction * PowerProfiles::menuAction() | ||
{ | ||
return mMenuAction.get(); | ||
} | ||
|
||
void PowerProfiles::onFetched(const QString name, const QVariant value) | ||
{ | ||
if (name == msDBusPPProperties[PPP_ActiveProfile]) { | ||
const QString profile = qdbus_cast<QString>(value); | ||
if (mActiveProfile == profile) | ||
return; | ||
mActiveProfile = profile; | ||
if (mActions) | ||
for (auto const & action : mActions->actions()) | ||
action->setChecked(get<QString>(action->data()) == mActiveProfile); | ||
} else if (name == msDBusPPProperties[PPP_Profiles]) { | ||
reassembleMenu(qdbus_cast<QListOfQVariantMap>(value)); | ||
} | ||
} | ||
|
||
void PowerProfiles::reassembleMenu(const QListOfQVariantMap & profiles) | ||
{ | ||
mMenu->clear(); | ||
if (profiles.empty()) | ||
{ | ||
mActions.reset(nullptr); | ||
mMenuAction->setVisible(false); | ||
return; | ||
} | ||
|
||
mMenuAction->setVisible(true); | ||
mActions.reset(new QActionGroup(nullptr)); | ||
mActions->setExclusionPolicy(QActionGroup::ExclusionPolicy::Exclusive); | ||
|
||
connect(mActions.get(), &QActionGroup::triggered, this, [this] (const QAction * a) { | ||
setActiveProfile(get<QString>(a->data())); | ||
}); | ||
|
||
for (auto const & profile : profiles) | ||
{ | ||
const QString p = qdbus_cast<QString>(profile[QStringLiteral("Profile")]); | ||
auto a = new QAction(tr(qUtf8Printable(p), "power-profiles-daemon"), mActions.get()); | ||
a->setCheckable(true); | ||
a->setData(p); | ||
a->setChecked(mActiveProfile == p); | ||
} | ||
mMenu->addActions(mActions->actions()); | ||
} | ||
|
||
const QString & PowerProfiles::activeProfile() const | ||
{ | ||
return mActiveProfile; | ||
} | ||
|
||
void PowerProfiles::setActiveProfile(const QString &value) | ||
{ | ||
// Note: Just emit the signal to make change. Don't modify any internal state. | ||
// We will be notified by the fetched() signal upon sucessfull PropertyChanged. | ||
Q_EMIT needPropertyPush(msDBusPPProperties[PPP_ActiveProfile], QVariant::fromValue(QDBusVariant{value})); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a little uncomfortable about showing the new menu-item when
power-profiles-daemon
isn't available. This may pretend thatpower-profiles-daemon
is preferred by LXQt, while many users may installtlp
, which is in conflict with it.IMHO, this deserves a discussion before merging.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well we don't necessarily depend on power-profiles-daemon, but on provided DBus service. Should we rather use
org.freedesktop.UPower.PowerProfiles DBus service not available
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
power-profiles-daemon
is an actual package that the user can install to fix the problem, so it is much more user friendly imo.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Too technical.
Why not hiding the menu-item instead?
EDIT: I mean the Power Profile menu-item, not just its submenu.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's a good solution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK. Done