From 63ccd5226235b59f17a0b1f584be8cf1bf203acc Mon Sep 17 00:00:00 2001 From: trigg Date: Sun, 31 May 2026 15:05:50 +0100 Subject: [PATCH 1/4] panel: add power profile switching to battery widget --- src/panel/widgets/battery.cpp | 141 +++++++++++++++++++++++++++++++--- src/panel/widgets/battery.hpp | 19 ++++- 2 files changed, 144 insertions(+), 16 deletions(-) diff --git a/src/panel/widgets/battery.cpp b/src/panel/widgets/battery.cpp index e1f89267..49471514 100644 --- a/src/panel/widgets/battery.cpp +++ b/src/panel/widgets/battery.cpp @@ -2,7 +2,8 @@ #include #include "battery.hpp" - +#define POWER_PROFILE_PATH "/org/freedesktop/UPower/PowerProfiles" +#define POWER_PROFILE_NAME "org.freedesktop.UPower.PowerProfiles" #define UPOWER_NAME "org.freedesktop.UPower" #define DISPLAY_DEVICE "/org/freedesktop/UPower/devices/DisplayDevice" @@ -14,6 +15,10 @@ #define TIMETOEMPTY "TimeToEmpty" #define SHOULD_DISPLAY "IsPresent" +#define DEGRADED "PerformanceDegraded" +#define PROFILES "Profiles" +#define ACTIVE_PROFILE "ActiveProfile" + static std::string get_device_type_description(uint32_t type) { if (type == 2) @@ -139,14 +144,14 @@ void WayfireBatteryInfo::update_details() description += ", " + uint_to_time(time_to_empty.get()) + " remaining"; } - box.set_tooltip_text( + box->set_tooltip_text( get_device_type_description(type.get()) + description); if (status_opt.value() == BATTERY_STATUS_PERCENT) { label.set_text(percentage_string); overlay.remove_overlay(label); - box.append(label); + box->append(label); } else if (status_opt.value() == BATTERY_STATUS_FULL) { label.set_text(description); @@ -156,14 +161,14 @@ void WayfireBatteryInfo::update_details() overlay.remove_overlay(label); } - box.append(label); + box->append(label); } else if (status_opt.value() == BATTERY_STATUS_OVERLAY) { label.set_text(percentage_string); - auto children = box.get_children(); + auto children = box->get_children(); if (std::count(children.begin(), children.end(), &label)) { - box.remove(label); + box->remove(label); } overlay.add_overlay(label); @@ -194,6 +199,31 @@ bool WayfireBatteryInfo::setup_dbus() return false; } + powerprofile_proxy = Gio::DBus::Proxy::create_sync(connection, POWER_PROFILE_NAME, + POWER_PROFILE_PATH, + POWER_PROFILE_NAME); + if (!powerprofile_proxy) + { + std::cout << "Unable to conect to Power Profiles. Continuing" << std::endl; + } else + { + powerprofile_proxy->signal_properties_changed().connect( + sigc::mem_fun(*this, &WayfireBatteryInfo::on_upower_properties_changed)); + Glib::Variant current_profile; + Glib::Variant>> profiles; + powerprofile_proxy->get_cached_property(current_profile, ACTIVE_PROFILE); + powerprofile_proxy->get_cached_property(profiles, PROFILES); + + if (profiles && current_profile) + { + setup_profiles(profiles.get()); + set_current_profile(current_profile.get()); + } else + { + std::cout << "Unable to conect to Power Profiles. Continuing" << std::endl; + } + } + upower_proxy = Gio::DBus::Proxy::create_sync(connection, UPOWER_NAME, "/org/freedesktop/UPower", "org.freedesktop.UPower"); @@ -231,10 +261,10 @@ void WayfireBatteryInfo::update_layout() if (panel_position.value() == PANEL_POSITION_LEFT or panel_position.value() == PANEL_POSITION_RIGHT) { - box.set_orientation(Gtk::Orientation::VERTICAL); + box->set_orientation(Gtk::Orientation::VERTICAL); } else { - box.set_orientation(Gtk::Orientation::HORIZONTAL); + box->set_orientation(Gtk::Orientation::HORIZONTAL); } } @@ -247,23 +277,55 @@ void WayfireBatteryInfo::handle_config_reload() void WayfireBatteryInfo::init(Gtk::Box *container) { + profiles_menu = Gio::Menu::create(); + state_action = Gio::SimpleAction::create_radio_string("set_profile", ""); + box = std::make_unique("panel", "battery"); + if (!setup_dbus()) { return; } - box.append(overlay); + box->append(overlay); overlay.set_child(icon); icon.add_css_class("widget-icon"); - box.add_css_class("battery"); status_opt.set_callback([=] () { update_details(); }); update_details(); update_icon(); + auto actions = Gio::SimpleActionGroup::create(); + + state_action->signal_activate().connect([=] (Glib::VariantBase vb) + { + // User has requested a change of state. Don't change the UI choice, let the dbus roundtrip happen to + // be sure. + if (vb.is_of_type(Glib::VariantType("s"))) + { + // Couldn't seem to make proxy send property back, so this will have to do + Glib::VariantContainerBase params = Glib::Variant>::create({POWER_PROFILE_NAME, ACTIVE_PROFILE, vb}); + + connection->call_sync( + POWER_PROFILE_PATH, + "org.freedesktop.DBus.Properties", + "Set", + params, + NULL, + POWER_PROFILE_NAME, + -1, + Gio::DBus::CallFlags::NONE, + {}); + } + }); - container->append(box); - box.set_spacing(5); + box->open_on(1); + + actions->add_action(state_action); + box->insert_action_group("actions", actions); + container->append(*box); + box->set_spacing(5); + box->set_menu_model(profiles_menu); icon.property_scale_factor().signal_changed() .connect(sigc::mem_fun(*this, &WayfireBatteryInfo::update_icon)); @@ -271,6 +333,61 @@ void WayfireBatteryInfo::init(Gtk::Box *container) update_layout(); } +void WayfireBatteryInfo::on_upower_properties_changed( + const Gio::DBus::Proxy::MapChangedProperties& properties, + const std::vector& invalidated) +{ + for (auto& prop : properties) + { + if (prop.first == ACTIVE_PROFILE) + { + if (prop.second.is_of_type(Glib::VariantType("s"))) + { + auto value_string = + Glib::VariantBase::cast_dynamic>(prop.second).get(); + set_current_profile(value_string); + } + } else if (prop.first == PROFILES) + { + // I've been unable to find a way to change possible profiles on the fly, so cannot confirm this + // works at all. + auto value = Glib::VariantBase::cast_dynamic>>>(prop.second); + setup_profiles(value.get()); + } + + // TODO Consider watching for "Performance Degraded" events too, but we currently have no way to + // output this additional information + } +} + +void WayfireBatteryInfo::set_current_profile(Glib::ustring profile) +{ + state_action->set_state(Glib::Variant::create(profile)); +} + +void WayfireBatteryInfo::setup_profiles(std::vector> profiles) +{ + profiles_menu->remove_all(); + for (auto profile : profiles) + { + if (profile.count("Profile") == 1) + { + Glib::VariantBase value = profile.at("Profile"); + if (value.is_of_type(Glib::VariantType("s"))) + { + auto value_string = + Glib::VariantBase::cast_dynamic>(value).get(); + auto item = Gio::MenuItem::create(value_string, "noactionyet"); + + item->set_action_and_target("actions.set_profile", + Glib::Variant::create(value_string)); + profiles_menu->append_item(item); + } + } + } +} + WayfireBatteryInfo::~WayfireBatteryInfo() { btn_sig.disconnect(); diff --git a/src/panel/widgets/battery.hpp b/src/panel/widgets/battery.hpp index 0e07a9f4..dea24f5b 100644 --- a/src/panel/widgets/battery.hpp +++ b/src/panel/widgets/battery.hpp @@ -8,10 +8,12 @@ #include #include +#include #include -#include "../widget.hpp" +#include "widget.hpp" +#include "wf-popover.hpp" using DBusConnection = Glib::RefPtr; using DBusProxy = Glib::RefPtr; @@ -29,13 +31,13 @@ class WayfireBatteryInfo : public WayfireWidget sigc::connection btn_sig, disp_dev_sig; Gtk::Label label; - Gtk::Box box; + std::unique_ptr box; Gtk::Overlay overlay; - + std::shared_ptr profiles_menu; Gtk::Image icon; DBusConnection connection; - DBusProxy upower_proxy, display_device; + DBusProxy upower_proxy, powerprofile_proxy, display_device; bool setup_dbus(); @@ -50,6 +52,15 @@ class WayfireBatteryInfo : public WayfireWidget const Gio::DBus::Proxy::MapChangedProperties& properties, const std::vector& invalidated); + void on_upower_properties_changed( + const Gio::DBus::Proxy::MapChangedProperties& properties, + const std::vector& invalidated); + + void set_current_profile(Glib::ustring profile); + void setup_profiles(std::vector> profiles); + + std::shared_ptr state_action; + public: virtual void init(Gtk::Box *container); virtual ~WayfireBatteryInfo(); From e767d380116e8b98097896278572ab3dcdf8f872 Mon Sep 17 00:00:00 2001 From: Hue Date: Fri, 5 Jun 2026 00:37:19 +0200 Subject: [PATCH 2/4] =?UTF-8?q?panel=C2=A0:=20battery=C2=A0:=20only=20show?= =?UTF-8?q?=20power=20mode=20popover=20if=20it=20is=20available?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit do not connect open_on if the power-profiles-daemon interface is not found, thus not letting the user open an empty popover, which wuold be jarring --- src/panel/widgets/battery.cpp | 81 ++++++++++++++++++++++------------- src/panel/widgets/battery.hpp | 3 +- 2 files changed, 53 insertions(+), 31 deletions(-) diff --git a/src/panel/widgets/battery.cpp b/src/panel/widgets/battery.cpp index 49471514..65739288 100644 --- a/src/panel/widgets/battery.cpp +++ b/src/panel/widgets/battery.cpp @@ -189,7 +189,7 @@ void WayfireBatteryInfo::update_state() "\n\tWayfireBatteryInfo::update_state()" << std::endl; } -bool WayfireBatteryInfo::setup_dbus() +bool WayfireBatteryInfo::setup_dbus_power_modes() { auto cancellable = Gio::Cancellable::create(); connection = Gio::DBus::Connection::get_sync(Gio::DBus::BusType::SYSTEM, cancellable); @@ -218,12 +218,27 @@ bool WayfireBatteryInfo::setup_dbus() { setup_profiles(profiles.get()); set_current_profile(current_profile.get()); + return true; } else { std::cout << "Unable to conect to Power Profiles. Continuing" << std::endl; + return false; } } + return false; +} + +bool WayfireBatteryInfo::setup_dbus_battery() +{ + auto cancellable = Gio::Cancellable::create(); + connection = Gio::DBus::Connection::get_sync(Gio::DBus::BusType::SYSTEM, cancellable); + if (!connection) + { + std::cerr << "Failed to connect to dbus" << std::endl; + return false; + } + upower_proxy = Gio::DBus::Proxy::create_sync(connection, UPOWER_NAME, "/org/freedesktop/UPower", "org.freedesktop.UPower"); @@ -277,11 +292,9 @@ void WayfireBatteryInfo::handle_config_reload() void WayfireBatteryInfo::init(Gtk::Box *container) { - profiles_menu = Gio::Menu::create(); - state_action = Gio::SimpleAction::create_radio_string("set_profile", ""); box = std::make_unique("panel", "battery"); - if (!setup_dbus()) + if (!setup_dbus_battery()) { return; } @@ -294,38 +307,46 @@ void WayfireBatteryInfo::init(Gtk::Box *container) update_details(); update_icon(); - auto actions = Gio::SimpleActionGroup::create(); - state_action->signal_activate().connect([=] (Glib::VariantBase vb) + if (setup_dbus_power_modes()) { - // User has requested a change of state. Don't change the UI choice, let the dbus roundtrip happen to - // be sure. - if (vb.is_of_type(Glib::VariantType("s"))) + profiles_menu = Gio::Menu::create(); + state_action = Gio::SimpleAction::create_radio_string("set_profile", ""); + + auto actions = Gio::SimpleActionGroup::create(); + + state_action->signal_activate().connect([=] (Glib::VariantBase vb) { - // Couldn't seem to make proxy send property back, so this will have to do - Glib::VariantContainerBase params = Glib::Variant>::create({POWER_PROFILE_NAME, ACTIVE_PROFILE, vb}); - - connection->call_sync( - POWER_PROFILE_PATH, - "org.freedesktop.DBus.Properties", - "Set", - params, - NULL, - POWER_PROFILE_NAME, - -1, - Gio::DBus::CallFlags::NONE, - {}); - } - }); + // User has requested a change of state. Don't change the UI choice, + // let the dbus roundtrip happen to be sure. + if (vb.is_of_type(Glib::VariantType("s"))) + { + // Couldn't seem to make proxy send property back, so this will have to do + Glib::VariantContainerBase params = Glib::Variant>::create({POWER_PROFILE_NAME, ACTIVE_PROFILE, vb}); + + connection->call_sync( + POWER_PROFILE_PATH, + "org.freedesktop.DBus.Properties", + "Set", + params, + NULL, + POWER_PROFILE_NAME, + -1, + Gio::DBus::CallFlags::NONE, + {}); + } + }); - box->open_on(1); + actions->add_action(state_action); + + box->open_on(1); + box->insert_action_group("actions", actions); + box->set_spacing(5); + box->set_menu_model(profiles_menu); + } - actions->add_action(state_action); - box->insert_action_group("actions", actions); container->append(*box); - box->set_spacing(5); - box->set_menu_model(profiles_menu); icon.property_scale_factor().signal_changed() .connect(sigc::mem_fun(*this, &WayfireBatteryInfo::update_icon)); diff --git a/src/panel/widgets/battery.hpp b/src/panel/widgets/battery.hpp index dea24f5b..69df35a6 100644 --- a/src/panel/widgets/battery.hpp +++ b/src/panel/widgets/battery.hpp @@ -39,7 +39,8 @@ class WayfireBatteryInfo : public WayfireWidget DBusConnection connection; DBusProxy upower_proxy, powerprofile_proxy, display_device; - bool setup_dbus(); + bool setup_dbus_power_modes(); + bool setup_dbus_battery(); void update_icon(); void update_details(); From 6d4f8684ffad4ac1527866b7c0c9934dd2fc8644 Mon Sep 17 00:00:00 2001 From: Hue Date: Fri, 5 Jun 2026 12:37:53 +0200 Subject: [PATCH 3/4] =?UTF-8?q?panel=C2=A0:=20battery=C2=A0:=20replace=20b?= =?UTF-8?q?at=20icon=20with=20profile=20icon=20if=20battery=20isn=E2=80=99?= =?UTF-8?q?t=20available?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit use the power icon corresponding to the current state if the device doesn’t have a battery but power profiles switching is available if neither are available, still ignore the widget --- src/panel/widgets/battery.cpp | 26 +++++++++++++++++++++----- src/panel/widgets/battery.hpp | 4 +++- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/panel/widgets/battery.cpp b/src/panel/widgets/battery.cpp index 65739288..02fc9844 100644 --- a/src/panel/widgets/battery.cpp +++ b/src/panel/widgets/battery.cpp @@ -77,6 +77,12 @@ void WayfireBatteryInfo::on_properties_changed( void WayfireBatteryInfo::update_icon() { + if (!feat_bat && feat_modes) + { + icon.set_from_icon_name("power-profile-" + power_mode); + return; + } + Glib::Variant icon_name; display_device->get_cached_property(icon_name, ICON); icon.set_from_icon_name(icon_name.get()); @@ -292,23 +298,31 @@ void WayfireBatteryInfo::handle_config_reload() void WayfireBatteryInfo::init(Gtk::Box *container) { - box = std::make_unique("panel", "battery"); + // the two features are battery displaying and power modes + feat_bat = setup_dbus_battery(); + feat_modes = setup_dbus_power_modes(); - if (!setup_dbus_battery()) + // ignore if we can’t do either + if (!feat_bat && !feat_modes) { return; } + box = std::make_unique("panel", "battery"); + box->append(overlay); overlay.set_child(icon); icon.add_css_class("widget-icon"); - status_opt.set_callback([=] () { update_details(); }); + if (feat_bat) + { + status_opt.set_callback([=] () { update_details(); }); + update_details(); + } - update_details(); update_icon(); - if (setup_dbus_power_modes()) + if (feat_modes) { profiles_menu = Gio::Menu::create(); state_action = Gio::SimpleAction::create_radio_string("set_profile", ""); @@ -366,7 +380,9 @@ void WayfireBatteryInfo::on_upower_properties_changed( { auto value_string = Glib::VariantBase::cast_dynamic>(prop.second).get(); + power_mode = value_string; set_current_profile(value_string); + update_icon(); } } else if (prop.first == PROFILES) { diff --git a/src/panel/widgets/battery.hpp b/src/panel/widgets/battery.hpp index 69df35a6..df85e4a8 100644 --- a/src/panel/widgets/battery.hpp +++ b/src/panel/widgets/battery.hpp @@ -38,9 +38,11 @@ class WayfireBatteryInfo : public WayfireWidget DBusConnection connection; DBusProxy upower_proxy, powerprofile_proxy, display_device; + std::string power_mode = ""; - bool setup_dbus_power_modes(); + bool feat_bat, feat_modes; // the available features when running bool setup_dbus_battery(); + bool setup_dbus_power_modes(); void update_icon(); void update_details(); From 6bfc9f51c568bb35dc7ab956481496c8e3f7e768 Mon Sep 17 00:00:00 2001 From: trigg Date: Fri, 5 Jun 2026 12:54:53 +0100 Subject: [PATCH 4/4] panel: fix segfault for machine without system battery panel: fix first icon being invaid for machines without system battery --- src/panel/widgets/battery.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/panel/widgets/battery.cpp b/src/panel/widgets/battery.cpp index 02fc9844..baaafcb6 100644 --- a/src/panel/widgets/battery.cpp +++ b/src/panel/widgets/battery.cpp @@ -298,6 +298,9 @@ void WayfireBatteryInfo::handle_config_reload() void WayfireBatteryInfo::init(Gtk::Box *container) { + profiles_menu = Gio::Menu::create(); + state_action = Gio::SimpleAction::create_radio_string("set_profile", ""); + // the two features are battery displaying and power modes feat_bat = setup_dbus_battery(); feat_modes = setup_dbus_power_modes(); @@ -324,9 +327,6 @@ void WayfireBatteryInfo::init(Gtk::Box *container) if (feat_modes) { - profiles_menu = Gio::Menu::create(); - state_action = Gio::SimpleAction::create_radio_string("set_profile", ""); - auto actions = Gio::SimpleActionGroup::create(); state_action->signal_activate().connect([=] (Glib::VariantBase vb) @@ -380,9 +380,7 @@ void WayfireBatteryInfo::on_upower_properties_changed( { auto value_string = Glib::VariantBase::cast_dynamic>(prop.second).get(); - power_mode = value_string; set_current_profile(value_string); - update_icon(); } } else if (prop.first == PROFILES) { @@ -400,7 +398,9 @@ void WayfireBatteryInfo::on_upower_properties_changed( void WayfireBatteryInfo::set_current_profile(Glib::ustring profile) { + power_mode = profile; state_action->set_state(Glib::Variant::create(profile)); + update_icon(); } void WayfireBatteryInfo::setup_profiles(std::vector> profiles)