Skip to content

Commit 7405011

Browse files
committed
Introduce power-profiles-daemon module
We introduce a module in charge to display and toggle on click the power profiles via power-profiles-daemon. https://gitlab.freedesktop.org/upower/power-profiles-daemon This daemon is pretty widespread. It's the component used by Gnome and KDE to manage the power profiles. The power management daemon is a pretty important software component for laptops and other battery-powered devices. We're using the daemon DBus interface to: - Fetch the available power profiles. - Track the active power profile. - Change the active power profile. The original author recently gave up maintenance on the project. The Upower group took over the maintenance burden… …and created a new DBus name for the project. The old name is still advertised for now. We use the old name for compatibility sake: most distributions did not release 0.20, which introduces this new DBus name. We'll likely revisit this in the future and point to the new bus name. See the inline comment for more details. Given how widespread this daemon is, I activated the module in the default configuration.
1 parent 6703adc commit 7405011

File tree

7 files changed

+207
-1
lines changed

7 files changed

+207
-1
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- Local time
1414
- Battery
1515
- UPower
16+
- Power profiles daemon
1617
- Network
1718
- Bluetooth
1819
- Pulseaudio
+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# pragma once
2+
3+
#include <fmt/format.h>
4+
5+
#include "ALabel.hpp"
6+
#include "giomm/dbusproxy.h"
7+
8+
namespace waybar::modules {
9+
10+
typedef struct {
11+
std::string name;
12+
std::string driver;
13+
} Profile;
14+
15+
class PowerProfilesDaemon : public ALabel {
16+
public:
17+
PowerProfilesDaemon(const std::string&, const Json::Value&);
18+
~PowerProfilesDaemon();
19+
auto update() -> void override;
20+
void profileChanged_cb( const Gio::DBus::Proxy::MapChangedProperties&, const std::vector<Glib::ustring>&);
21+
void populateInitState();
22+
virtual bool handleToggle(GdkEventButton* const& e);
23+
private:
24+
// Look for a profile name in the list of available profiles and
25+
// switch activeProfile_ to it.
26+
void switchToProfile_(std::string);
27+
// Used to toggle/display the profiles
28+
std::vector<Profile> availableProfiles_;
29+
// Points to the active profile in the profiles list
30+
std::vector<Profile>::iterator activeProfile_;
31+
// Current CSS class applied to the label
32+
std::string currentStyle_;
33+
// DBus Proxy used to track the current active profile
34+
Glib::RefPtr<Gio::DBus::Proxy> power_profiles_proxy_;
35+
sigc::connection powerProfileChangeSignal_;
36+
};
37+
38+
}

meson.build

+1
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ if is_linux
212212
'src/modules/cpu_usage/linux.cpp',
213213
'src/modules/memory/common.cpp',
214214
'src/modules/memory/linux.cpp',
215+
'src/modules/power_profiles_daemon.cpp',
215216
'src/modules/systemd_failed_units.cpp',
216217
)
217218
man_files += files(

resources/config.jsonc

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"idle_inhibitor",
2121
"pulseaudio",
2222
"network",
23+
"power_profiles_daemon",
2324
"cpu",
2425
"memory",
2526
"temperature",
@@ -188,4 +189,3 @@
188189
// "exec": "$HOME/.config/waybar/mediaplayer.py --player spotify 2> /dev/null" // Filter player based on name
189190
}
190191
}
191-

resources/style.css

+16
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ button:hover {
8787
#mode,
8888
#idle_inhibitor,
8989
#scratchpad,
90+
#power-profiles-daemon,
9091
#mpd {
9192
padding: 0 10px;
9293
color: #ffffff;
@@ -139,6 +140,21 @@ button:hover {
139140
animation-direction: alternate;
140141
}
141142

143+
#power-profiles-daemon.performance {
144+
background-color: #f53c3c;
145+
color: #ffffff;
146+
}
147+
148+
#power-profiles-daemon.balanced {
149+
background-color: #2980b9;
150+
color: #ffffff;
151+
}
152+
153+
#power-profiles-daemon.power-saver {
154+
background-color: #2ecc71;
155+
color: #000000;
156+
}
157+
142158
label:focus {
143159
background-color: #000000;
144160
}

src/factory.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
#endif
8787
#if defined(__linux__)
8888
#include "modules/bluetooth.hpp"
89+
#include "modules/power_profiles_daemon.hpp"
8990
#endif
9091
#ifdef HAVE_LOGIND_INHIBITOR
9192
#include "modules/inhibitor.hpp"
@@ -287,6 +288,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name,
287288
if (ref == "inhibitor") {
288289
return new waybar::modules::Inhibitor(id, bar_, config_[name]);
289290
}
291+
if (ref == "power_profiles_daemon") {
292+
return new waybar::modules::PowerProfilesDaemon(id, config_[name]);
293+
}
290294
#endif
291295
#ifdef HAVE_LIBJACK
292296
if (ref == "jack") {

src/modules/power_profiles_daemon.cpp

+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
#include "modules/power_profiles_daemon.hpp"
2+
3+
// In the 80000 version of fmt library authors decided to optimize imports
4+
// and moved declarations required for fmt::dynamic_format_arg_store in new
5+
// header fmt/args.h
6+
#if (FMT_VERSION >= 80000)
7+
#include <fmt/args.h>
8+
#else
9+
#include <fmt/core.h>
10+
#endif
11+
12+
#include <spdlog/spdlog.h>
13+
#include <glibmm.h>
14+
#include <glibmm/variant.h>
15+
16+
17+
18+
namespace waybar::modules {
19+
20+
PowerProfilesDaemon::PowerProfilesDaemon(const std::string& id, const Json::Value& config)
21+
: ALabel(config, "power-profiles-daemon", id, "{profile}", 0, false, true)
22+
{
23+
// NOTE: the DBus adresses are under migration. They should be
24+
// changed to org.freedesktop.UPower.PowerProfiles at some point.
25+
//
26+
// See
27+
// https://gitlab.freedesktop.org/upower/power-profiles-daemon/-/releases/0.20
28+
//
29+
// The old name is still announced for now. Let's rather use the old
30+
// adresses for compatibility sake.
31+
//
32+
// Revisit this in 2026, systems should be updated by then.
33+
power_profiles_proxy_ = Gio::DBus::Proxy::create_for_bus_sync(Gio::DBus::BusType::BUS_TYPE_SYSTEM,
34+
"net.hadess.PowerProfiles", "/net/hadess/PowerProfiles",
35+
"net.hadess.PowerProfiles");
36+
if (!power_profiles_proxy_) {
37+
spdlog::error("PowerProfilesDaemon: DBus error, cannot connect to net.hasdess.PowerProfile");
38+
} else {
39+
// Connect active profile callback
40+
powerProfileChangeSignal_ = power_profiles_proxy_->signal_properties_changed()
41+
.connect(sigc::mem_fun(*this, &PowerProfilesDaemon::profileChanged_cb));
42+
populateInitState();
43+
dp.emit();
44+
}
45+
}
46+
47+
// Look for the profile str in our internal profiles list. Using a
48+
// vector to store the profiles ain't the smartest move
49+
// complexity-wise, but it makes toggling between the mode easy. This
50+
// vector is 3 elements max, we'll be fine :P
51+
void PowerProfilesDaemon::switchToProfile_(std::string str) {
52+
auto pred = [str](Profile p) { return p.name == str; };
53+
activeProfile_ = std::find_if(availableProfiles_.begin(), availableProfiles_.end(), pred);
54+
if (activeProfile_ == availableProfiles_.end()) {
55+
throw std::runtime_error("FATAL, can't find the active profile in the available profiles list");
56+
}
57+
}
58+
59+
void PowerProfilesDaemon::populateInitState() {
60+
// Retrieve current active profile
61+
Glib::Variant<std::string> profileStr;
62+
power_profiles_proxy_->get_cached_property(profileStr, "ActiveProfile");
63+
64+
// Retrieve profiles list, it's aa{sv}.
65+
using ProfilesType = std::vector<std::map<Glib::ustring, Glib::Variant<std::string>>>;
66+
Glib::Variant<ProfilesType> profilesVariant;
67+
power_profiles_proxy_->get_cached_property(profilesVariant, "Profiles");
68+
Glib::ustring name, driver;
69+
Profile profile;
70+
for (auto & variantDict: profilesVariant.get()) {
71+
if (auto p = variantDict.find("Profile"); p != variantDict.end()) {
72+
name = p->second.get();
73+
}
74+
if (auto d = variantDict.find("Driver"); d != variantDict.end()) {
75+
driver = d->second.get();
76+
}
77+
profile = { name, driver };
78+
availableProfiles_.push_back(profile);
79+
}
80+
81+
// Find the index of the current activated mode (to toggle)
82+
std::string str = profileStr.get();
83+
switchToProfile_(str);
84+
85+
update();
86+
}
87+
88+
PowerProfilesDaemon::~PowerProfilesDaemon() {
89+
if (powerProfileChangeSignal_.connected()) {
90+
powerProfileChangeSignal_.disconnect();
91+
}
92+
if (power_profiles_proxy_) {
93+
power_profiles_proxy_.reset();
94+
}
95+
}
96+
97+
void PowerProfilesDaemon::profileChanged_cb(const Gio::DBus::Proxy::MapChangedProperties& changedProperties,
98+
const std::vector<Glib::ustring>& invalidatedProperties) {
99+
if (auto activeProfileVariant = changedProperties.find("ActiveProfile"); activeProfileVariant != changedProperties.end()) {
100+
std::string activeProfile = Glib::VariantBase::cast_dynamic<Glib::Variant<std::string>>(activeProfileVariant->second).get();
101+
switchToProfile_(activeProfile);
102+
update();
103+
}
104+
}
105+
106+
auto PowerProfilesDaemon::update () -> void {
107+
auto profile = (*activeProfile_);
108+
// Set label
109+
fmt::dynamic_format_arg_store<fmt::format_context> store;
110+
store.push_back(fmt::arg("profile", profile.name));
111+
label_.set_markup(fmt::vformat("⚡ {profile}", store));
112+
if (tooltipEnabled()) {
113+
label_.set_tooltip_text(fmt::format("Driver: {}", profile.driver));
114+
}
115+
116+
// Set CSS class
117+
if (!currentStyle_.empty()) {
118+
label_.get_style_context()->remove_class(currentStyle_);
119+
}
120+
label_.get_style_context()->add_class(profile.name);
121+
currentStyle_ = profile.name;
122+
123+
ALabel::update();
124+
}
125+
126+
127+
bool PowerProfilesDaemon::handleToggle(GdkEventButton* const& e) {
128+
if (e->type == GdkEventType::GDK_BUTTON_PRESS && power_profiles_proxy_) {
129+
activeProfile_++;
130+
if (activeProfile_ == availableProfiles_.end()) {
131+
activeProfile_ = availableProfiles_.begin();
132+
}
133+
134+
using VarStr = Glib::Variant<Glib::ustring>;
135+
using SetPowerProfileVar = Glib::Variant<std::tuple<Glib::ustring,Glib::ustring,VarStr>>;
136+
VarStr activeProfileVariant = VarStr::create(activeProfile_->name);
137+
auto call_args = SetPowerProfileVar::create(std::make_tuple("net.hadess.PowerProfiles", "ActiveProfile", activeProfileVariant));
138+
auto container = Glib::VariantBase::cast_dynamic<Glib::VariantContainerBase>(call_args);
139+
power_profiles_proxy_->call_sync("org.freedesktop.DBus.Properties.Set", container);
140+
141+
update();
142+
}
143+
return true;
144+
}
145+
146+
}

0 commit comments

Comments
 (0)