Skip to content

Commit

Permalink
Introduce power-profiles-daemon module
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
picnoir committed Feb 26, 2024
1 parent 6703adc commit c38d05b
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- Local time
- Battery
- UPower
- Power profiles daemon
- Network
- Bluetooth
- Pulseaudio
Expand Down
38 changes: 38 additions & 0 deletions include/modules/power_profiles_daemon.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# pragma once

#include <fmt/format.h>

#include "ALabel.hpp"
#include "giomm/dbusproxy.h"

namespace waybar::modules {

typedef struct {
std::string name;
std::string driver;
} Profile;

class PowerProfilesDaemon : public ALabel {
public:
PowerProfilesDaemon(const std::string&, const Json::Value&);
~PowerProfilesDaemon();
auto update() -> void override;
void profileChanged_cb( const Gio::DBus::Proxy::MapChangedProperties&, const std::vector<Glib::ustring>&);
void populateInitState();
virtual bool handleToggle(GdkEventButton* const& e);
private:
// Look for a profile name in the list of available profiles and
// switch activeProfile_ to it.
void switchToProfile_(std::string);
// Used to toggle/display the profiles
std::vector<Profile> availableProfiles_;
// Points to the active profile in the profiles list
std::vector<Profile>::iterator activeProfile_;
// Current CSS class applied to the label
std::string currentStyle_;
// DBus Proxy used to track the current active profile
Glib::RefPtr<Gio::DBus::Proxy> power_profiles_proxy_;
sigc::connection powerProfileChangeSignal_;
};

}
1 change: 1 addition & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ if is_linux
'src/modules/cpu_usage/linux.cpp',
'src/modules/memory/common.cpp',
'src/modules/memory/linux.cpp',
'src/modules/power_profiles_daemon.cpp',
'src/modules/systemd_failed_units.cpp',
)
man_files += files(
Expand Down
2 changes: 1 addition & 1 deletion resources/config.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"idle_inhibitor",
"pulseaudio",
"network",
"power_profiles_daemon",
"cpu",
"memory",
"temperature",
Expand Down Expand Up @@ -188,4 +189,3 @@
// "exec": "$HOME/.config/waybar/mediaplayer.py --player spotify 2> /dev/null" // Filter player based on name
}
}

16 changes: 16 additions & 0 deletions resources/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ button:hover {
#mode,
#idle_inhibitor,
#scratchpad,
#power-profiles-daemon,
#mpd {
padding: 0 10px;
color: #ffffff;
Expand Down Expand Up @@ -139,6 +140,21 @@ button:hover {
animation-direction: alternate;
}

#power-profiles-daemon.performance {
background-color: #f53c3c;
color: #ffffff;
}

#power-profiles-daemon.balanced {
background-color: #2980b9;
color: #ffffff;
}

#power-profiles-daemon.power-saver {
background-color: #2ecc71;
color: #000000;
}

label:focus {
background-color: #000000;
}
Expand Down
4 changes: 4 additions & 0 deletions src/factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
#endif
#if defined(__linux__)
#include "modules/bluetooth.hpp"
#include "modules/power_profiles_daemon.hpp"
#endif
#ifdef HAVE_LOGIND_INHIBITOR
#include "modules/inhibitor.hpp"
Expand Down Expand Up @@ -282,6 +283,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name,
if (ref == "bluetooth") {
return new waybar::modules::Bluetooth(id, config_[name]);
}
if (ref == "power_profiles_daemon") {
return new waybar::modules::PowerProfilesDaemon(id, config_[name]);
}
#endif
#ifdef HAVE_LOGIND_INHIBITOR
if (ref == "inhibitor") {
Expand Down
146 changes: 146 additions & 0 deletions src/modules/power_profiles_daemon.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
#include "modules/power_profiles_daemon.hpp"

// In the 80000 version of fmt library authors decided to optimize imports
// and moved declarations required for fmt::dynamic_format_arg_store in new
// header fmt/args.h
#if (FMT_VERSION >= 80000)
#include <fmt/args.h>
#else
#include <fmt/core.h>
#endif

#include <spdlog/spdlog.h>
#include <glibmm.h>
#include <glibmm/variant.h>



namespace waybar::modules {

PowerProfilesDaemon::PowerProfilesDaemon(const std::string& id, const Json::Value& config)
: ALabel(config, "power-profiles-daemon", id, "{profile}", 0, false, true)
{
// NOTE: the DBus adresses are under migration. They should be
// changed to org.freedesktop.UPower.PowerProfiles at some point.
//
// See
// https://gitlab.freedesktop.org/upower/power-profiles-daemon/-/releases/0.20
//
// The old name is still announced for now. Let's rather use the old
// adresses for compatibility sake.
//
// Revisit this in 2026, systems should be updated by then.
power_profiles_proxy_ = Gio::DBus::Proxy::create_for_bus_sync(Gio::DBus::BusType::BUS_TYPE_SYSTEM,
"net.hadess.PowerProfiles", "/net/hadess/PowerProfiles",
"net.hadess.PowerProfiles");
if (!power_profiles_proxy_) {
spdlog::error("PowerProfilesDaemon: DBus error, cannot connect to net.hasdess.PowerProfile");
} else {
// Connect active profile callback
powerProfileChangeSignal_ = power_profiles_proxy_->signal_properties_changed()
.connect(sigc::mem_fun(*this, &PowerProfilesDaemon::profileChanged_cb));
populateInitState();
dp.emit();
}
}

// Look for the profile str in our internal profiles list. Using a
// vector to store the profiles ain't the smartest move
// complexity-wise, but it makes toggling between the mode easy. This
// vector is 3 elements max, we'll be fine :P
void PowerProfilesDaemon::switchToProfile_(std::string str) {
auto pred = [str](Profile p) { return p.name == str; };
activeProfile_ = std::find_if(availableProfiles_.begin(), availableProfiles_.end(), pred);
if (activeProfile_ == availableProfiles_.end()) {
throw std::runtime_error("FATAL, can't find the active profile in the available profiles list");
}
}

void PowerProfilesDaemon::populateInitState() {
// Retrieve current active profile
Glib::Variant<std::string> profileStr;
power_profiles_proxy_->get_cached_property(profileStr, "ActiveProfile");

// Retrieve profiles list, it's aa{sv}.
using ProfilesType = std::vector<std::map<Glib::ustring, Glib::Variant<std::string>>>;
Glib::Variant<ProfilesType> profilesVariant;
power_profiles_proxy_->get_cached_property(profilesVariant, "Profiles");
Glib::ustring name, driver;
Profile profile;
for (auto & variantDict: profilesVariant.get()) {
if (auto p = variantDict.find("Profile"); p != variantDict.end()) {
name = p->second.get();
}
if (auto d = variantDict.find("Driver"); d != variantDict.end()) {
driver = d->second.get();
}
profile = { name, driver };
availableProfiles_.push_back(profile);
}

// Find the index of the current activated mode (to toggle)
std::string str = profileStr.get();
switchToProfile_(str);

update();
}

PowerProfilesDaemon::~PowerProfilesDaemon() {
if (powerProfileChangeSignal_.connected()) {
powerProfileChangeSignal_.disconnect();
}
if (power_profiles_proxy_) {
power_profiles_proxy_.reset();
}
}

void PowerProfilesDaemon::profileChanged_cb(const Gio::DBus::Proxy::MapChangedProperties& changedProperties,
const std::vector<Glib::ustring>& invalidatedProperties) {
if (auto activeProfileVariant = changedProperties.find("ActiveProfile"); activeProfileVariant != changedProperties.end()) {
std::string activeProfile = Glib::VariantBase::cast_dynamic<Glib::Variant<std::string>>(activeProfileVariant->second).get();
switchToProfile_(activeProfile);
update();
}
}

auto PowerProfilesDaemon::update () -> void {
auto profile = (*activeProfile_);
// Set label
fmt::dynamic_format_arg_store<fmt::format_context> store;
store.push_back(fmt::arg("profile", profile.name));
label_.set_markup(fmt::vformat("⚡ {profile}", store));
if (tooltipEnabled()) {
label_.set_tooltip_text(fmt::format("Driver: {}", profile.driver));
}

// Set CSS class
if (!currentStyle_.empty()) {
label_.get_style_context()->remove_class(currentStyle_);
}
label_.get_style_context()->add_class(profile.name);
currentStyle_ = profile.name;

ALabel::update();
}


bool PowerProfilesDaemon::handleToggle(GdkEventButton* const& e) {
if (e->type == GdkEventType::GDK_BUTTON_PRESS && power_profiles_proxy_) {
activeProfile_++;
if (activeProfile_ == availableProfiles_.end()) {
activeProfile_ = availableProfiles_.begin();
}

using VarStr = Glib::Variant<Glib::ustring>;
using SetPowerProfileVar = Glib::Variant<std::tuple<Glib::ustring,Glib::ustring,VarStr>>;
VarStr activeProfileVariant = VarStr::create(activeProfile_->name);
auto call_args = SetPowerProfileVar::create(std::make_tuple("net.hadess.PowerProfiles", "ActiveProfile", activeProfileVariant));
auto container = Glib::VariantBase::cast_dynamic<Glib::VariantContainerBase>(call_args);
power_profiles_proxy_->call_sync("org.freedesktop.DBus.Properties.Set", container);

update();
}
return true;
}

}

0 comments on commit c38d05b

Please sign in to comment.