-
-
Notifications
You must be signed in to change notification settings - Fork 770
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
Introduce a power-profiles-daemon module #2971
Changes from 5 commits
c38d05b
968f469
162b41c
61fed6a
09bb6a0
bddc870
cc759a8
5ba7c9e
5578c12
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ | |
- Local time | ||
- Battery | ||
- UPower | ||
- Power profiles daemon | ||
- Network | ||
- Bluetooth | ||
- Pulseaudio | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
#pragma once | ||
|
||
#include <fmt/format.h> | ||
|
||
#include "ALabel.hpp" | ||
#include "giomm/dbusproxy.h" | ||
|
||
namespace waybar::modules { | ||
|
||
struct Profile { | ||
std::string name; | ||
std::string driver; | ||
}; | ||
|
||
class PowerProfilesDaemon : public ALabel { | ||
public: | ||
PowerProfilesDaemon(const std::string &, const Json::Value &); | ||
~PowerProfilesDaemon() override; | ||
auto update() -> void override; | ||
void profileChangedCb(const Gio::DBus::Proxy::MapChangedProperties &, | ||
const std::vector<Glib::ustring> &); | ||
void busConnectedCb(Glib::RefPtr<Gio::AsyncResult> &r); | ||
void getAllPropsCb(Glib::RefPtr<Gio::AsyncResult> &r); | ||
void setPropCb(Glib::RefPtr<Gio::AsyncResult> &r); | ||
void populateInitState(); | ||
bool handleToggle(GdkEventButton *const &e) override; | ||
|
||
private: | ||
// True if we're connected to the dbug interface. False if we're | ||
// not. | ||
bool connected_; | ||
// Look for a profile name in the list of available profiles and | ||
// switch activeProfile_ to it. | ||
void switchToProfile(std::string const &); | ||
// 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_; | ||
// Format strings | ||
std::string labelFormat_; | ||
std::string tooltipFormat_; | ||
// DBus Proxy used to track the current active profile | ||
Glib::RefPtr<Gio::DBus::Proxy> powerProfilesProxy_; | ||
sigc::connection powerProfileChangeSignal_; | ||
picnoir marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}; | ||
|
||
} // namespace waybar::modules |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,214 @@ | ||
#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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Waybar requires fmt >= 8.1.1, this check is not necessary in a new code. |
||
#include <fmt/args.h> | ||
#else | ||
#include <fmt/core.h> | ||
#endif | ||
|
||
#include <glibmm.h> | ||
#include <glibmm/variant.h> | ||
#include <spdlog/spdlog.h> | ||
|
||
namespace waybar::modules { | ||
|
||
PowerProfilesDaemon::PowerProfilesDaemon(const std::string& id, const Json::Value& config) | ||
: ALabel(config, "power-profiles-daemon", id, "{profile}", 0, false, true), connected_(false) { | ||
if (config_["format"].isString()) { | ||
format_ = config_["format"].asString(); | ||
} else { | ||
format_ = "{icon}"; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
if (config_["tooltip-format"].isString()) { | ||
tooltipFormat_ = config_["tooltip-format"].asString(); | ||
} else { | ||
tooltipFormat_ = "Power profile: {profile}\nDriver: {driver}"; | ||
} | ||
// Fasten your seatbelt, we're up for quite a ride. The rest of the | ||
// init is performed asynchronously. There's 2 callbacks involved. | ||
// Here's the overall idea: | ||
// 1. Async connect to the system bus. | ||
// 2. In the system bus connect callback, try to call | ||
// org.freedesktop.DBus.Properties.GetAll to see if | ||
// power-profiles-daemon is able to respond. | ||
// 3. In the GetAll callback, connect the activeProfile monitoring | ||
// callback, consider the init to be successful. Meaning start | ||
// drawing the module. | ||
// | ||
// There's sadly no other way around that, we have to try to call a | ||
// method on the proxy to see whether or not something's responding | ||
// on the other side. | ||
|
||
// 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. | ||
Gio::DBus::Proxy::create_for_bus(Gio::DBus::BusType::BUS_TYPE_SYSTEM, "net.hadess.PowerProfiles", | ||
"/net/hadess/PowerProfiles", "net.hadess.PowerProfiles", | ||
sigc::mem_fun(*this, &PowerProfilesDaemon::busConnectedCb)); | ||
} | ||
|
||
PowerProfilesDaemon::~PowerProfilesDaemon() { | ||
if (powerProfileChangeSignal_.connected()) { | ||
powerProfileChangeSignal_.disconnect(); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For any connections made with |
||
if (powerProfilesProxy_) { | ||
powerProfilesProxy_.reset(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
} | ||
|
||
void PowerProfilesDaemon::busConnectedCb(Glib::RefPtr<Gio::AsyncResult>& r) { | ||
try { | ||
powerProfilesProxy_ = Gio::DBus::Proxy::create_for_bus_finish(r); | ||
using GetAllProfilesVar = Glib::Variant<std::tuple<Glib::ustring>>; | ||
auto callArgs = GetAllProfilesVar::create(std::make_tuple("net.hadess.PowerProfiles")); | ||
|
||
auto container = Glib::VariantBase::cast_dynamic<Glib::VariantContainerBase>(callArgs); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the manual conversion here necessary? You should be able to pass callArgs to call directly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, yes, indeed, my bad. I was sure I tried and it error-ed out. It definitely does not 😕 |
||
powerProfilesProxy_->call("org.freedesktop.DBus.Properties.GetAll", | ||
sigc::mem_fun(*this, &PowerProfilesDaemon::getAllPropsCb), container); | ||
// Connect active profile callback | ||
} catch (const std::exception& e) { | ||
picnoir marked this conversation as resolved.
Show resolved
Hide resolved
|
||
spdlog::error("Failed to create the power profiles daemon DBus proxy"); | ||
} | ||
} | ||
|
||
// Callback for the GetAll call. | ||
// | ||
// We're abusing this call to make sure power-profiles-daemon is | ||
// available on the host. We're not really using | ||
void PowerProfilesDaemon::getAllPropsCb(Glib::RefPtr<Gio::AsyncResult>& r) { | ||
try { | ||
auto _ = powerProfilesProxy_->call_finish(r); | ||
// Power-profiles-daemon responded something, we can assume it's | ||
// available, we can safely attach the activeProfile monitoring | ||
// now. | ||
connected_ = true; | ||
powerProfileChangeSignal_ = powerProfilesProxy_->signal_properties_changed().connect( | ||
sigc::mem_fun(*this, &PowerProfilesDaemon::profileChangedCb)); | ||
populateInitState(); | ||
dp.emit(); | ||
} catch (const std::exception& err) { | ||
spdlog::error("Failed to query power-profiles-daemon via dbus: {}", err.what()); | ||
} | ||
} | ||
|
||
void PowerProfilesDaemon::populateInitState() { | ||
// Retrieve current active profile | ||
Glib::Variant<std::string> profileStr; | ||
powerProfilesProxy_->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; | ||
powerProfilesProxy_->get_cached_property(profilesVariant, "Profiles"); | ||
Glib::ustring name; | ||
Glib::ustring 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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
And if you move name/driver declaration into the loop (which you might want to do anyways to avoid accidentally reusing values from previous iterations), it'll be safe to write |
||
} | ||
|
||
// Find the index of the current activated mode (to toggle) | ||
std::string str = profileStr.get(); | ||
switchToProfile(str); | ||
|
||
update(); | ||
} | ||
|
||
void PowerProfilesDaemon::profileChangedCb( | ||
const Gio::DBus::Proxy::MapChangedProperties& changedProperties, | ||
const std::vector<Glib::ustring>& invalidatedProperties) { | ||
// We're likely connected if this callback gets triggered. | ||
// But better be safe than sorry. | ||
if (connected_) { | ||
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(); | ||
} | ||
} | ||
} | ||
|
||
// 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 const& str) { | ||
auto pred = [str](Profile const& p) { return p.name == str; }; | ||
this->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"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure throwing exception is necessary here. It will be caught by Glibmm exception handler anyways, with no consequences. |
||
} | ||
} | ||
|
||
auto PowerProfilesDaemon::update() -> void { | ||
if (connected_) { | ||
auto profile = (*activeProfile_); | ||
picnoir marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Set label | ||
fmt::dynamic_format_arg_store<fmt::format_context> store; | ||
store.push_back(fmt::arg("profile", profile.name)); | ||
store.push_back(fmt::arg("driver", profile.driver)); | ||
store.push_back(fmt::arg("icon", getIcon(0, profile.name))); | ||
label_.set_markup(fmt::vformat(format_, store)); | ||
if (tooltipEnabled()) { | ||
label_.set_tooltip_text(fmt::vformat(tooltipFormat_, store)); | ||
} | ||
|
||
// 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 (connected_) { | ||
if (e->type == GdkEventType::GDK_BUTTON_PRESS && powerProfilesProxy_) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The check for |
||
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 callArgs = SetPowerProfileVar::create( | ||
std::make_tuple("net.hadess.PowerProfiles", "ActiveProfile", activeProfileVariant)); | ||
auto container = Glib::VariantBase::cast_dynamic<Glib::VariantContainerBase>(callArgs); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The manual cast might be unnecessary |
||
powerProfilesProxy_->call("org.freedesktop.DBus.Properties.Set", | ||
sigc::mem_fun(*this, &PowerProfilesDaemon::setPropCb), container); | ||
} | ||
} | ||
return true; | ||
} | ||
|
||
void PowerProfilesDaemon::setPropCb(Glib::RefPtr<Gio::AsyncResult>& r) { | ||
auto _ = powerProfilesProxy_->call_finish(r); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please, check for exceptions here. |
||
update(); | ||
} | ||
|
||
} // namespace waybar::modules |
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.
labelFormat_ is unused