From 09bb6a055dec244d0d8a26fccc79752015bd03ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Baylac=20Jacqu=C3=A9?= Date: Fri, 1 Mar 2024 12:33:36 +0100 Subject: [PATCH] modules/power_profiles_daemon: safely call dbus asynchronously 2 changes to address the review feedback: 1. Aleksei pointed out in this comment (https://github.com/Alexays/Waybar/pull/2971#issuecomment-1972364896) that there's no way to tell if a proxy is alive other than trying to call a method on it. We perform a little dance to check whether or not power-profiles-daemon is available on the system by calling properties.GetAll. If something responds, we assume power-profiles-daemon is installed, it's then safe to draw the widget and attach the callback to the active profile. 2. We replaced all the synchronous DBus operations by their async counterparts. --- include/modules/power_profiles_daemon.hpp | 16 +- src/modules/power_profiles_daemon.cpp | 184 ++++++++++++++-------- 2 files changed, 129 insertions(+), 71 deletions(-) diff --git a/include/modules/power_profiles_daemon.hpp b/include/modules/power_profiles_daemon.hpp index 72ffb314..bfb5c99d 100644 --- a/include/modules/power_profiles_daemon.hpp +++ b/include/modules/power_profiles_daemon.hpp @@ -14,18 +14,24 @@ struct Profile { class PowerProfilesDaemon : public ALabel { public: - PowerProfilesDaemon(const std::string&, const Json::Value&); + PowerProfilesDaemon(const std::string &, const Json::Value &); ~PowerProfilesDaemon() override; auto update() -> void override; - void profileChangedCb(const Gio::DBus::Proxy::MapChangedProperties&, - const std::vector&); + void profileChangedCb(const Gio::DBus::Proxy::MapChangedProperties &, + const std::vector &); + void busConnectedCb(Glib::RefPtr &r); + void getAllPropsCb(Glib::RefPtr &r); + void setPropCb(Glib::RefPtr &r); void populateInitState(); - bool handleToggle(GdkEventButton* const& e) override; + 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&); + void switchToProfile(std::string const &); // Used to toggle/display the profiles std::vector availableProfiles_; // Points to the active profile in the profiles list diff --git a/src/modules/power_profiles_daemon.cpp b/src/modules/power_profiles_daemon.cpp index 6b684662..bd6a52a7 100644 --- a/src/modules/power_profiles_daemon.cpp +++ b/src/modules/power_profiles_daemon.cpp @@ -16,7 +16,7 @@ namespace waybar::modules { PowerProfilesDaemon::PowerProfilesDaemon(const std::string& id, const Json::Value& config) - : ALabel(config, "power-profiles-daemon", id, "{profile}", 0, false, true) { + : ALabel(config, "power-profiles-daemon", id, "{profile}", 0, false, true), connected_(false) { if (config_["format"].isString()) { format_ = config_["format"].asString(); } else { @@ -28,6 +28,20 @@ PowerProfilesDaemon::PowerProfilesDaemon(const std::string& id, const Json::Valu } 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. @@ -39,29 +53,52 @@ PowerProfilesDaemon::PowerProfilesDaemon(const std::string& id, const Json::Valu // adresses for compatibility sake. // // Revisit this in 2026, systems should be updated by then. - powerProfilesProxy_ = Gio::DBus::Proxy::create_for_bus_sync( - Gio::DBus::BusType::BUS_TYPE_SYSTEM, "net.hadess.PowerProfiles", "/net/hadess/PowerProfiles", - "net.hadess.PowerProfiles"); - if (!powerProfilesProxy_) { - spdlog::error("PowerProfilesDaemon: DBus error, cannot connect to net.hasdess.PowerProfile"); - } else { + 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(); + } + if (powerProfilesProxy_) { + powerProfilesProxy_.reset(); + } +} + +void PowerProfilesDaemon::busConnectedCb(Glib::RefPtr& r) { + try { + powerProfilesProxy_ = Gio::DBus::Proxy::create_for_bus_finish(r); + using GetAllProfilesVar = Glib::Variant>; + auto callArgs = GetAllProfilesVar::create(std::make_tuple("net.hadess.PowerProfiles")); + + auto container = Glib::VariantBase::cast_dynamic(callArgs); + powerProfilesProxy_->call("org.freedesktop.DBus.Properties.GetAll", + sigc::mem_fun(*this, &PowerProfilesDaemon::getAllPropsCb), container); // Connect active profile callback + } catch (const std::exception& e) { + 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& 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(); - } -} - -// 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"); + } catch (const std::exception& err) { + spdlog::error("Failed to query power-profiles-daemon via dbus: {}", err.what()); } } @@ -95,68 +132,83 @@ void PowerProfilesDaemon::populateInitState() { update(); } -PowerProfilesDaemon::~PowerProfilesDaemon() { - if (powerProfileChangeSignal_.connected()) { - powerProfileChangeSignal_.disconnect(); - } - if (powerProfilesProxy_) { - powerProfilesProxy_.reset(); - } -} - void PowerProfilesDaemon::profileChangedCb( const Gio::DBus::Proxy::MapChangedProperties& changedProperties, const std::vector& invalidatedProperties) { - if (auto activeProfileVariant = changedProperties.find("ActiveProfile"); - activeProfileVariant != changedProperties.end()) { - std::string activeProfile = - Glib::VariantBase::cast_dynamic>(activeProfileVariant->second) - .get(); - switchToProfile(activeProfile); - update(); + // 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>(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"); } } auto PowerProfilesDaemon::update() -> void { - auto profile = (*activeProfile_); - // Set label - fmt::dynamic_format_arg_store 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)); - } + if (connected_) { + auto profile = (*activeProfile_); + // Set label + fmt::dynamic_format_arg_store 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; + // 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(); + ALabel::update(); + } } bool PowerProfilesDaemon::handleToggle(GdkEventButton* const& e) { - if (e->type == GdkEventType::GDK_BUTTON_PRESS && powerProfilesProxy_) { - activeProfile_++; - if (activeProfile_ == availableProfiles_.end()) { - activeProfile_ = availableProfiles_.begin(); + if (connected_) { + if (e->type == GdkEventType::GDK_BUTTON_PRESS && powerProfilesProxy_) { + activeProfile_++; + if (activeProfile_ == availableProfiles_.end()) { + activeProfile_ = availableProfiles_.begin(); + } + + using VarStr = Glib::Variant; + using SetPowerProfileVar = Glib::Variant>; + VarStr activeProfileVariant = VarStr::create(activeProfile_->name); + auto callArgs = SetPowerProfileVar::create( + std::make_tuple("net.hadess.PowerProfiles", "ActiveProfile", activeProfileVariant)); + auto container = Glib::VariantBase::cast_dynamic(callArgs); + powerProfilesProxy_->call("org.freedesktop.DBus.Properties.Set", + sigc::mem_fun(*this, &PowerProfilesDaemon::setPropCb), container); } - - using VarStr = Glib::Variant; - using SetPowerProfileVar = Glib::Variant>; - VarStr activeProfileVariant = VarStr::create(activeProfile_->name); - auto callArgs = SetPowerProfileVar::create( - std::make_tuple("net.hadess.PowerProfiles", "ActiveProfile", activeProfileVariant)); - auto container = Glib::VariantBase::cast_dynamic(callArgs); - powerProfilesProxy_->call_sync("org.freedesktop.DBus.Properties.Set", container); - - update(); } return true; } +void PowerProfilesDaemon::setPropCb(Glib::RefPtr& r) { + auto _ = powerProfilesProxy_->call_finish(r); + update(); +} + } // namespace waybar::modules