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:
parent
61fed6a214
commit
09bb6a055d
|
@ -14,18 +14,24 @@ struct Profile {
|
||||||
|
|
||||||
class PowerProfilesDaemon : public ALabel {
|
class PowerProfilesDaemon : public ALabel {
|
||||||
public:
|
public:
|
||||||
PowerProfilesDaemon(const std::string&, const Json::Value&);
|
PowerProfilesDaemon(const std::string &, const Json::Value &);
|
||||||
~PowerProfilesDaemon() override;
|
~PowerProfilesDaemon() override;
|
||||||
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 &);
|
||||||
// Used to toggle/display the profiles
|
// Used to toggle/display the profiles
|
||||||
std::vector<Profile> availableProfiles_;
|
std::vector<Profile> availableProfiles_;
|
||||||
// Points to the active profile in the profiles list
|
// Points to the active profile in the profiles list
|
||||||
|
|
|
@ -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,68 +132,83 @@ 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) {
|
||||||
if (auto activeProfileVariant = changedProperties.find("ActiveProfile");
|
// We're likely connected if this callback gets triggered.
|
||||||
activeProfileVariant != changedProperties.end()) {
|
// But better be safe than sorry.
|
||||||
std::string activeProfile =
|
if (connected_) {
|
||||||
Glib::VariantBase::cast_dynamic<Glib::Variant<std::string>>(activeProfileVariant->second)
|
if (auto activeProfileVariant = changedProperties.find("ActiveProfile");
|
||||||
.get();
|
activeProfileVariant != changedProperties.end()) {
|
||||||
switchToProfile(activeProfile);
|
std::string activeProfile =
|
||||||
update();
|
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 PowerProfilesDaemon::update() -> void {
|
||||||
auto profile = (*activeProfile_);
|
if (connected_) {
|
||||||
// Set label
|
auto profile = (*activeProfile_);
|
||||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
// Set label
|
||||||
store.push_back(fmt::arg("profile", profile.name));
|
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||||
store.push_back(fmt::arg("driver", profile.driver));
|
store.push_back(fmt::arg("profile", profile.name));
|
||||||
store.push_back(fmt::arg("icon", getIcon(0, profile.name)));
|
store.push_back(fmt::arg("driver", profile.driver));
|
||||||
label_.set_markup(fmt::vformat(format_, store));
|
store.push_back(fmt::arg("icon", getIcon(0, profile.name)));
|
||||||
if (tooltipEnabled()) {
|
label_.set_markup(fmt::vformat(format_, store));
|
||||||
label_.set_tooltip_text(fmt::vformat(tooltipFormat_, store));
|
if (tooltipEnabled()) {
|
||||||
}
|
label_.set_tooltip_text(fmt::vformat(tooltipFormat_, store));
|
||||||
|
}
|
||||||
|
|
||||||
// Set CSS class
|
// Set CSS class
|
||||||
if (!currentStyle_.empty()) {
|
if (!currentStyle_.empty()) {
|
||||||
label_.get_style_context()->remove_class(currentStyle_);
|
label_.get_style_context()->remove_class(currentStyle_);
|
||||||
}
|
}
|
||||||
label_.get_style_context()->add_class(profile.name);
|
label_.get_style_context()->add_class(profile.name);
|
||||||
currentStyle_ = profile.name;
|
currentStyle_ = profile.name;
|
||||||
|
|
||||||
ALabel::update();
|
ALabel::update();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PowerProfilesDaemon::handleToggle(GdkEventButton* const& e) {
|
bool PowerProfilesDaemon::handleToggle(GdkEventButton* const& e) {
|
||||||
if (e->type == GdkEventType::GDK_BUTTON_PRESS && powerProfilesProxy_) {
|
if (connected_) {
|
||||||
activeProfile_++;
|
if (e->type == GdkEventType::GDK_BUTTON_PRESS && powerProfilesProxy_) {
|
||||||
if (activeProfile_ == availableProfiles_.end()) {
|
activeProfile_++;
|
||||||
activeProfile_ = availableProfiles_.begin();
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PowerProfilesDaemon::setPropCb(Glib::RefPtr<Gio::AsyncResult>& r) {
|
||||||
|
auto _ = powerProfilesProxy_->call_finish(r);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace waybar::modules
|
} // namespace waybar::modules
|
||||||
|
|
Loading…
Reference in New Issue