menubar: add power-profiles D-Bus widget

parent 7dcd9056
...@@ -627,6 +627,42 @@ ...@@ -627,6 +627,42 @@
"$ref": "#/widgets/buttons-grid/properties/actions" "$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; ...@@ -3,7 +3,8 @@ using GLib;
namespace SwayNotificationCenter.Widgets { namespace SwayNotificationCenter.Widgets {
public enum MenuType { public enum MenuType {
BUTTONS, BUTTONS,
MENU MENU,
POWER_PROFILES
} }
public enum Position { public enum Position {
...@@ -20,6 +21,7 @@ namespace SwayNotificationCenter.Widgets { ...@@ -20,6 +21,7 @@ namespace SwayNotificationCenter.Widgets {
Gtk.Revealer ?revealer; Gtk.Revealer ?revealer;
int animation_duration; int animation_duration;
Gtk.RevealerTransitionType animation_type; Gtk.RevealerTransitionType animation_type;
HashTable<string, string> ?label_overrides;
} }
public struct Action { public struct Action {
...@@ -42,12 +44,15 @@ namespace SwayNotificationCenter.Widgets { ...@@ -42,12 +44,15 @@ namespace SwayNotificationCenter.Widgets {
List<ConfigObject ?> menu_objects; List<ConfigObject ?> menu_objects;
List<ToggleButton> toggle_buttons; List<ToggleButton> toggle_buttons;
List<PowerProfiles> power_profiles_widgets;
public Menubar (string suffix) { public Menubar (string suffix) {
base (suffix); base (suffix);
set_orientation (Gtk.Orientation.VERTICAL); set_orientation (Gtk.Orientation.VERTICAL);
set_hexpand (true); set_hexpand (true);
power_profiles_widgets = new List<PowerProfiles> ();
Json.Object ?config = get_config (this); Json.Object ?config = get_config (this);
if (config != null) { if (config != null) {
parse_config_objects (config); parse_config_objects (config);
...@@ -140,6 +145,9 @@ namespace SwayNotificationCenter.Widgets { ...@@ -140,6 +145,9 @@ namespace SwayNotificationCenter.Widgets {
foreach (var o in menu_objects) { foreach (var o in menu_objects) {
o.revealer ?.set_reveal_child (false); o.revealer ?.set_reveal_child (false);
} }
foreach (var pp in power_profiles_widgets) {
pp.close ();
}
r.set_reveal_child (visible); r.set_reveal_child (visible);
}); });
...@@ -172,6 +180,38 @@ namespace SwayNotificationCenter.Widgets { ...@@ -172,6 +180,38 @@ namespace SwayNotificationCenter.Widgets {
append (r); append (r);
break; 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 { ...@@ -194,9 +234,11 @@ namespace SwayNotificationCenter.Widgets {
type = MenuType.BUTTONS; type = MenuType.BUTTONS;
} else if (t == "menu") { } else if (t == "menu") {
type = MenuType.MENU; type = MenuType.MENU;
} else if (t == "power-profiles") {
type = MenuType.POWER_PROFILES;
} else { } else {
info ("Invalid type for menu-object - valid options: " + info ("Invalid type for menu-object - valid options: " +
"'menu' || 'buttons' using default"); "'menu' || 'buttons' || 'power-profiles' using default");
} }
string name = key[1]; string name = key[1];
...@@ -213,7 +255,7 @@ namespace SwayNotificationCenter.Widgets { ...@@ -213,7 +255,7 @@ namespace SwayNotificationCenter.Widgets {
} }
Json.Array ?actions = get_prop_array (obj, "actions"); 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"); info ("Error parsing actions for menu-object");
} }
...@@ -249,7 +291,20 @@ namespace SwayNotificationCenter.Widgets { ...@@ -249,7 +291,20 @@ namespace SwayNotificationCenter.Widgets {
break; 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 () { menu_objects.append (ConfigObject () {
name = name, name = name,
type = type, type = type,
...@@ -259,6 +314,7 @@ namespace SwayNotificationCenter.Widgets { ...@@ -259,6 +314,7 @@ namespace SwayNotificationCenter.Widgets {
revealer = null, revealer = null,
animation_duration = duration, animation_duration = duration,
animation_type = revealer_type, animation_type = revealer_type,
label_overrides = icons,
}); });
} }
} }
...@@ -268,6 +324,9 @@ namespace SwayNotificationCenter.Widgets { ...@@ -268,6 +324,9 @@ namespace SwayNotificationCenter.Widgets {
foreach (var obj in menu_objects) { foreach (var obj in menu_objects) {
obj.revealer ?.set_reveal_child (false); obj.revealer ?.set_reveal_child (false);
} }
foreach (var pp in power_profiles_widgets) {
pp.close ();
}
} else { } else {
foreach (var tb in toggle_buttons) { foreach (var tb in toggle_buttons) {
tb.on_update.begin (); 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 = [ ...@@ -50,6 +50,8 @@ widget_sources = [
'controlCenter/widgets/backlight/backlightUtil.vala', 'controlCenter/widgets/backlight/backlightUtil.vala',
# Widget: Inhibitors # Widget: Inhibitors
'controlCenter/widgets/inhibitors/inhibitors.vala', 'controlCenter/widgets/inhibitors/inhibitors.vala',
# Widget: Power Profiles (D-Bus PPD integration for menubar)
'controlCenter/widgets/powerProfiles/powerProfiles.vala',
] ]
app_sources = [ 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