menubar: add power-profiles D-Bus widget

parent 7dcd9056
......@@ -627,6 +627,42 @@
"$ref": "#/widgets/buttons-grid/properties/actions"
}
}
},
"^power-profiles(#[a-zA-Z0-9_-]{1,}){0,1}?$": {
"type": "object",
"description": "A button that opens a dropdown with power profile options from D-Bus PowerProfiles daemon. Profiles are detected automatically.",
"additionalProperties": false,
"properties": {
"label": {
"type": "string",
"description": "Text to be displayed in the button",
"default": "Power"
},
"position": {
"type": "string",
"description": "Horizontal position of the button in the bar",
"default": "right",
"enum": ["right", "left"]
},
"animation-type": {
"type": "string",
"default": "slide_down",
"description": "Animation type for the dropdown",
"enum": ["slide_down", "slide_up", "none"]
},
"animation-duration": {
"type": "integer",
"default": 250,
"description": "Duration of animation in milliseconds"
},
"button-labels": {
"type": "object",
"description": "Override button text for specific profiles. Keys are profile names (performance, balanced, power-saver), values are the full button text.",
"additionalProperties": {
"type": "string"
}
}
}
}
}
},
......
......@@ -3,7 +3,8 @@ using GLib;
namespace SwayNotificationCenter.Widgets {
public enum MenuType {
BUTTONS,
MENU
MENU,
POWER_PROFILES
}
public enum Position {
......@@ -20,6 +21,7 @@ namespace SwayNotificationCenter.Widgets {
Gtk.Revealer ?revealer;
int animation_duration;
Gtk.RevealerTransitionType animation_type;
HashTable<string, string> ?label_overrides;
}
public struct Action {
......@@ -42,12 +44,15 @@ namespace SwayNotificationCenter.Widgets {
List<ConfigObject ?> menu_objects;
List<ToggleButton> toggle_buttons;
List<PowerProfiles> power_profiles_widgets;
public Menubar (string suffix) {
base (suffix);
set_orientation (Gtk.Orientation.VERTICAL);
set_hexpand (true);
power_profiles_widgets = new List<PowerProfiles> ();
Json.Object ?config = get_config (this);
if (config != null) {
parse_config_objects (config);
......@@ -140,6 +145,9 @@ namespace SwayNotificationCenter.Widgets {
foreach (var o in menu_objects) {
o.revealer ?.set_reveal_child (false);
}
foreach (var pp in power_profiles_widgets) {
pp.close ();
}
r.set_reveal_child (visible);
});
......@@ -172,6 +180,38 @@ namespace SwayNotificationCenter.Widgets {
append (r);
break;
case MenuType.POWER_PROFILES :
var pp = new PowerProfiles (
obj.label,
obj.animation_duration,
obj.animation_type,
obj.label_overrides);
pp.request_close_other_revealers.connect (() => {
foreach (var o in menu_objects) {
o.revealer ?.set_reveal_child (false);
}
foreach (var other_pp in power_profiles_widgets) {
if (other_pp != pp) {
other_pp.close ();
}
}
});
obj.revealer = pp.get_revealer ();
power_profiles_widgets.append (pp);
switch (obj.position) {
case Position.RIGHT:
right_container.append (pp.get_button ());
break;
case Position.LEFT:
left_container.append (pp.get_button ());
break;
}
append (pp.get_revealer ());
break;
}
}
......@@ -194,9 +234,11 @@ namespace SwayNotificationCenter.Widgets {
type = MenuType.BUTTONS;
} else if (t == "menu") {
type = MenuType.MENU;
} else if (t == "power-profiles") {
type = MenuType.POWER_PROFILES;
} else {
info ("Invalid type for menu-object - valid options: " +
"'menu' || 'buttons' using default");
"'menu' || 'buttons' || 'power-profiles' using default");
}
string name = key[1];
......@@ -213,7 +255,7 @@ namespace SwayNotificationCenter.Widgets {
}
Json.Array ?actions = get_prop_array (obj, "actions");
if (actions == null) {
if (actions == null && type != MenuType.POWER_PROFILES) {
info ("Error parsing actions for menu-object");
}
......@@ -249,7 +291,20 @@ namespace SwayNotificationCenter.Widgets {
break;
}
Action[] actions_list = parse_actions (actions);
Action[] actions_list = actions != null ? parse_actions (actions) : new Action[0];
// Parse label overrides for power-profiles
HashTable<string, string>? icons = null;
if (type == MenuType.POWER_PROFILES && obj.has_member ("button-labels")) {
icons = new HashTable<string, string> (str_hash, str_equal);
var icons_obj = obj.get_object_member ("button-labels");
if (icons_obj != null) {
foreach (string profile_key in icons_obj.get_members ()) {
icons.insert (profile_key, icons_obj.get_string_member (profile_key));
}
}
}
menu_objects.append (ConfigObject () {
name = name,
type = type,
......@@ -259,6 +314,7 @@ namespace SwayNotificationCenter.Widgets {
revealer = null,
animation_duration = duration,
animation_type = revealer_type,
label_overrides = icons,
});
}
}
......@@ -268,6 +324,9 @@ namespace SwayNotificationCenter.Widgets {
foreach (var obj in menu_objects) {
obj.revealer ?.set_reveal_child (false);
}
foreach (var pp in power_profiles_widgets) {
pp.close ();
}
} else {
foreach (var tb in toggle_buttons) {
tb.on_update.begin ();
......
namespace SwayNotificationCenter.Widgets {
[DBus (name = "net.hadess.PowerProfiles")]
interface PowerProfilesProxy : Object {
public abstract string active_profile { owned get; set; }
public abstract HashTable<string, Variant>[] profiles { owned get; }
}
public class PowerProfiles : Object {
PowerProfilesProxy? proxy = null;
DBusProxy raw_proxy = null;
Gtk.ToggleButton show_button;
Gtk.Revealer revealer;
Gtk.Box menu;
string[] profile_names = {};
Gtk.ToggleButton[] profile_buttons = {};
bool updating_buttons = false;
// Custom label overrides from config (profile_name -> full button text)
HashTable<string, string> label_overrides;
public signal void request_close_other_revealers ();
public PowerProfiles (string label,
int animation_duration,
Gtk.RevealerTransitionType animation_type,
HashTable<string, string>? label_overrides) {
this.label_overrides = label_overrides ?? new HashTable<string, string> (str_hash, str_equal);
show_button = new Gtk.ToggleButton.with_label (label);
menu = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
menu.add_css_class ("power-profiles");
revealer = new Gtk.Revealer () {
child = menu,
transition_duration = animation_duration,
transition_type = animation_type,
};
revealer.bind_property ("child-revealed",
show_button, "active",
BindingFlags.SYNC_CREATE, null, null);
show_button.clicked.connect (() => {
bool visible = !revealer.get_reveal_child ();
request_close_other_revealers ();
revealer.set_reveal_child (visible);
});
connect_dbus.begin ();
}
private async void connect_dbus () {
try {
proxy = yield Bus.get_proxy (BusType.SYSTEM,
"net.hadess.PowerProfiles",
"/net/hadess/PowerProfiles");
raw_proxy = proxy as DBusProxy;
populate_profiles ();
raw_proxy.g_properties_changed.connect (on_properties_changed);
} catch (Error e) {
warning ("PowerProfiles D-Bus not available: %s", e.message);
show_button.set_visible (false);
revealer.set_visible (false);
}
}
private void on_properties_changed (Variant changed, string[] invalidated) {
var iter = changed.iterator ();
string key;
Variant val;
while (iter.next ("{sv}", out key, out val)) {
if (key == "ActiveProfile") {
update_active_button (val.get_string ());
} else if (key == "Profiles") {
populate_profiles ();
}
}
}
private async void set_profile (string profile_name) {
if (raw_proxy == null) return;
try {
yield raw_proxy.call (
"org.freedesktop.DBus.Properties.Set",
new Variant ("(ssv)",
"net.hadess.PowerProfiles",
"ActiveProfile",
new Variant.string (profile_name)),
DBusCallFlags.NONE, -1, null);
} catch (Error e) {
warning ("Failed to set power profile: %s", e.message);
}
}
private void populate_profiles () {
if (proxy == null) return;
Gtk.Widget? child = menu.get_first_child ();
while (child != null) {
Gtk.Widget? next = child.get_next_sibling ();
menu.remove (child);
child = next;
}
profile_names = {};
profile_buttons = {};
string active = proxy.active_profile;
foreach (var profile in proxy.profiles) {
Variant? v = profile.lookup ("Profile");
if (v == null) continue;
string name = v.get_string ();
// Use override if set, otherwise default
string? custom = label_overrides.lookup (name);
string display = custom ?? get_default_label (name);
var btn = new Gtk.ToggleButton ();
btn.set_hexpand (true);
btn.set_label (display);
btn.set_active (name == active);
string profile_name = name;
btn.toggled.connect (() => {
if (!updating_buttons && btn.active) {
set_profile.begin (profile_name);
}
});
menu.append (btn);
profile_names += name;
profile_buttons += btn;
}
}
private void update_active_button (string active_profile) {
updating_buttons = true;
for (int i = 0; i < profile_names.length; i++) {
profile_buttons[i].set_active (profile_names[i] == active_profile);
}
updating_buttons = false;
}
private string get_default_label (string profile) {
switch (profile) {
case "performance": return "\xf0\x9f\x9a\x80 Performance";
case "balanced": return "\xe2\x9a\x96 Balanced";
case "power-saver": return "\xf0\x9f\x94\x8b Power Saver";
default: return profile;
}
}
public Gtk.ToggleButton get_button () { return show_button; }
public Gtk.Revealer get_revealer () { return revealer; }
public void close () { revealer.set_reveal_child (false); }
}
}
......@@ -50,6 +50,8 @@ widget_sources = [
'controlCenter/widgets/backlight/backlightUtil.vala',
# Widget: Inhibitors
'controlCenter/widgets/inhibitors/inhibitors.vala',
# Widget: Power Profiles (D-Bus PPD integration for menubar)
'controlCenter/widgets/powerProfiles/powerProfiles.vala',
]
app_sources = [
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment