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

@ -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<Glib::ustring>&);
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;
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<Profile> availableProfiles_;
// Points to the active profile in the profiles list

View File

@ -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<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
} 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(
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<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();
// 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");
}
}
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));
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<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;
// 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<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);
powerProfilesProxy_->call("org.freedesktop.DBus.Properties.Set",
sigc::mem_fun(*this, &PowerProfilesDaemon::setPropCb), container);
}
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);
powerProfilesProxy_->call_sync("org.freedesktop.DBus.Properties.Set", container);
update();
}
return true;
}
void PowerProfilesDaemon::setPropCb(Glib::RefPtr<Gio::AsyncResult>& r) {
auto _ = powerProfilesProxy_->call_finish(r);
update();
}
} // namespace waybar::modules