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.
This commit is contained in:
Félix Baylac Jacqué 2024-03-01 12:33:36 +01:00
parent 61fed6a214
commit 09bb6a055d
2 changed files with 129 additions and 71 deletions

View File

@ -19,10 +19,16 @@ class PowerProfilesDaemon : public ALabel {
auto update() -> void override; auto update() -> void override;
void profileChangedCb(const Gio::DBus::Proxy::MapChangedProperties &, void profileChangedCb(const Gio::DBus::Proxy::MapChangedProperties &,
const std::vector<Glib::ustring> &); 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(); void populateInitState();
bool handleToggle(GdkEventButton *const &e) override; bool handleToggle(GdkEventButton *const &e) override;
private: 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 // Look for a profile name in the list of available profiles and
// switch activeProfile_ to it. // switch activeProfile_ to it.
void switchToProfile(std::string const &); void switchToProfile(std::string const &);

View File

@ -16,7 +16,7 @@
namespace waybar::modules { namespace waybar::modules {
PowerProfilesDaemon::PowerProfilesDaemon(const std::string& id, const Json::Value& config) 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()) { if (config_["format"].isString()) {
format_ = config_["format"].asString(); format_ = config_["format"].asString();
} else { } else {
@ -28,6 +28,20 @@ PowerProfilesDaemon::PowerProfilesDaemon(const std::string& id, const Json::Valu
} else { } else {
tooltipFormat_ = "Power profile: {profile}\nDriver: {driver}"; 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 // NOTE: the DBus adresses are under migration. They should be
// changed to org.freedesktop.UPower.PowerProfiles at some point. // 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. // adresses for compatibility sake.
// //
// Revisit this in 2026, systems should be updated by then. // Revisit this in 2026, systems should be updated by then.
powerProfilesProxy_ = Gio::DBus::Proxy::create_for_bus_sync( Gio::DBus::Proxy::create_for_bus(Gio::DBus::BusType::BUS_TYPE_SYSTEM, "net.hadess.PowerProfiles",
Gio::DBus::BusType::BUS_TYPE_SYSTEM, "net.hadess.PowerProfiles", "/net/hadess/PowerProfiles", "/net/hadess/PowerProfiles", "net.hadess.PowerProfiles",
"net.hadess.PowerProfiles"); sigc::mem_fun(*this, &PowerProfilesDaemon::busConnectedCb));
if (!powerProfilesProxy_) { }
spdlog::error("PowerProfilesDaemon: DBus error, cannot connect to net.hasdess.PowerProfile");
} else { PowerProfilesDaemon::~PowerProfilesDaemon() {
if (powerProfileChangeSignal_.connected()) {
powerProfileChangeSignal_.disconnect();
}
if (powerProfilesProxy_) {
powerProfilesProxy_.reset();
}
}
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);
powerProfilesProxy_->call("org.freedesktop.DBus.Properties.GetAll",
sigc::mem_fun(*this, &PowerProfilesDaemon::getAllPropsCb), container);
// Connect active profile callback // 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<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( powerProfileChangeSignal_ = powerProfilesProxy_->signal_properties_changed().connect(
sigc::mem_fun(*this, &PowerProfilesDaemon::profileChangedCb)); sigc::mem_fun(*this, &PowerProfilesDaemon::profileChangedCb));
populateInitState(); populateInitState();
dp.emit(); dp.emit();
} } catch (const std::exception& err) {
} spdlog::error("Failed to query power-profiles-daemon via dbus: {}", err.what());
// 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");
} }
} }
@ -95,18 +132,12 @@ void PowerProfilesDaemon::populateInitState() {
update(); update();
} }
PowerProfilesDaemon::~PowerProfilesDaemon() {
if (powerProfileChangeSignal_.connected()) {
powerProfileChangeSignal_.disconnect();
}
if (powerProfilesProxy_) {
powerProfilesProxy_.reset();
}
}
void PowerProfilesDaemon::profileChangedCb( void PowerProfilesDaemon::profileChangedCb(
const Gio::DBus::Proxy::MapChangedProperties& changedProperties, const Gio::DBus::Proxy::MapChangedProperties& changedProperties,
const std::vector<Glib::ustring>& invalidatedProperties) { 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"); if (auto activeProfileVariant = changedProperties.find("ActiveProfile");
activeProfileVariant != changedProperties.end()) { activeProfileVariant != changedProperties.end()) {
std::string activeProfile = std::string activeProfile =
@ -116,8 +147,22 @@ void PowerProfilesDaemon::profileChangedCb(
update(); 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 PowerProfilesDaemon::update() -> void {
if (connected_) {
auto profile = (*activeProfile_); auto profile = (*activeProfile_);
// Set label // Set label
fmt::dynamic_format_arg_store<fmt::format_context> store; fmt::dynamic_format_arg_store<fmt::format_context> store;
@ -138,8 +183,10 @@ auto PowerProfilesDaemon::update() -> void {
ALabel::update(); ALabel::update();
} }
}
bool PowerProfilesDaemon::handleToggle(GdkEventButton* const& e) { bool PowerProfilesDaemon::handleToggle(GdkEventButton* const& e) {
if (connected_) {
if (e->type == GdkEventType::GDK_BUTTON_PRESS && powerProfilesProxy_) { if (e->type == GdkEventType::GDK_BUTTON_PRESS && powerProfilesProxy_) {
activeProfile_++; activeProfile_++;
if (activeProfile_ == availableProfiles_.end()) { if (activeProfile_ == availableProfiles_.end()) {
@ -152,11 +199,16 @@ bool PowerProfilesDaemon::handleToggle(GdkEventButton* const& e) {
auto callArgs = SetPowerProfileVar::create( auto callArgs = SetPowerProfileVar::create(
std::make_tuple("net.hadess.PowerProfiles", "ActiveProfile", activeProfileVariant)); std::make_tuple("net.hadess.PowerProfiles", "ActiveProfile", activeProfileVariant));
auto container = Glib::VariantBase::cast_dynamic<Glib::VariantContainerBase>(callArgs); auto container = Glib::VariantBase::cast_dynamic<Glib::VariantContainerBase>(callArgs);
powerProfilesProxy_->call_sync("org.freedesktop.DBus.Properties.Set", container); powerProfilesProxy_->call("org.freedesktop.DBus.Properties.Set",
sigc::mem_fun(*this, &PowerProfilesDaemon::setPropCb), container);
update(); }
} }
return true; return true;
} }
void PowerProfilesDaemon::setPropCb(Glib::RefPtr<Gio::AsyncResult>& r) {
auto _ = powerProfilesProxy_->call_finish(r);
update();
}
} // namespace waybar::modules } // namespace waybar::modules