From b7631c7de51259f455a7a9d6c07ccb7c3aa9a783 Mon Sep 17 00:00:00 2001 From: Jannis <78504175+JannisPetschenka@users.noreply.github.com> Date: Mon, 13 Nov 2023 00:52:26 +0100 Subject: [PATCH] Add toggle button (#304) * Add toggle button * Toggle button documentation * Fix issues * Add env variable for toggle button * Add state update command for toggle buttons * Fix Uncrustify * Update README --- README.md | 27 +++++++++++ man/swaync.5.scd | 46 +++++++++++++++++++ src/config.json.in | 11 +++++ src/configSchema.json | 16 +++++++ src/controlCenter/widgets/baseWidget.vala | 25 +++++++++- .../widgets/buttonsGrid/buttonsGrid.vala | 26 +++++++++-- .../widgets/menubar/menubar.vala | 24 ++++++++-- .../widgets/shared/toggleButton.vala | 44 ++++++++++++++++++ src/functions.vala | 30 +++++++++++- src/meson.build | 1 + src/style.css | 5 +- 11 files changed, 241 insertions(+), 14 deletions(-) create mode 100644 src/controlCenter/widgets/shared/toggleButton.vala diff --git a/README.md b/README.md index fe489046..14d5cb20 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ Table of Contents * [Run](#run) * [Control Center Shortcuts](#control-center-shortcuts) * [Configuring](#configuring) + * [Toggle Buttons](#toggle-buttons) * [Notification Inhibition](#notification-inhibition) * [Scripting](#scripting) * [Disable scripting](#disable-scripting) @@ -242,6 +243,32 @@ to your `~/.config/swaync/` folder to customize without needing root access. **Tip**: running swaync with `GTK_DEBUG=interactive swaync` will open a inspector window that'll allow you to see all of the CSS classes + other information. +## Toggle Buttons + +To add toggle buttons to your control center you can set the "type" in any acton to "toggle". +The toggle button supports different commands depending on the state of the button and +an "update_command" to update the state in case of changes from outside swaync. The update_command +is called every time the control center is opened/closed. +The active toggle button also gains the css-class ".toggle:checked" + +`config.json` example: + +```json +{ + "buttons-gird": { // also works with actions in menubar widget + "actions": [ + { + "label": "WiFi", + "type": "toggle", + "active": true, + "command": "sh -c '[[ $SWAYNC_TOGGLE_STATE == true ]] && nmcli radio wifi on || nmcli radio wifi off'", + "update_command": "sh -c '[[ $(nmcli radio wifi) == \"enabled\" ]] && echo true || echo false'" + } + ] + } +} +``` + ## Notification Inhibition Notifications can be inhibited through the provided `swaync-client` executable diff --git a/man/swaync.5.scd b/man/swaync.5.scd index 685259d6..ada6f9b7 100644 --- a/man/swaync.5.scd +++ b/man/swaync.5.scd @@ -377,6 +377,22 @@ config file to be able to detect config errors type: string ++ default: "" ++ description: "Command to be executed on click" ++ + type: ++ + type: string ++ + default: "normal" ++ + description: Type of the button. ++ + Toggle buttons receive the '.active' css class ++ + enum: ["normal", "toggle"] ++ + update-command: ++ + type: string ++ + default: "" ++ + description: "Command to be executed on visibility change of ++ + cc to update the active state of the toggle button (should ++ + echo true or false)" ++ + active: ++ + type: bool ++ + default: false ++ + description: Wether the toggle button is active as default or not ++ description: A list of actions containing a label and a command ++ description: A button to reveal a dropdown with action-buttons ++ buttons#: ++ @@ -402,6 +418,24 @@ config file to be able to detect config errors type: string ++ default: "" ++ description: "Command to be executed on click" ++ + type: ++ + type: string ++ + default: "normal" ++ + description: Type of the button ++ + Toggle buttons receive the '.active' css class and an env ++ + variable "SWAYNC_TOGGLE_STATE" is set. See example usage in the ++ + default config.json ++ + enum: ["normal", "toggle"] ++ + update-command: ++ + type: string ++ + default: "" ++ + description: "Command to be executed on visibility change of ++ + cc to update the active state of the toggle button (should ++ + echo true or false)" ++ + active: ++ + type: bool ++ + default: false ++ + description: Wether the toggle button is active as default or not ++ description: A list of actions containing a label and a command ++ description: A list of buttons to be displayed in the menu-button-bar ++ *buttons-grid*++ @@ -422,6 +456,18 @@ config file to be able to detect config errors type: string ++ default: "" ++ description: "Command to be executed on click" ++ + type: ++ + type: string ++ + default: "normal" ++ + description: Type of the button ++ + Toggle buttons receive the '.active' css class and an env ++ + variable "SWAYNC_TOGGLE_STATE" is set. See example usage in the ++ + default config.json ++ + enum: ["normal", "toggle"] ++ + active: ++ + type: bool ++ + default: false ++ + description: Wether the toggle button is active as default or not ++ description: A list of actions containing a label and a command ++ description: A grid of buttons that execute shell commands ++ #START pulse-audio diff --git a/src/config.json.in b/src/config.json.in index da2c46c2..14f4f1c2 100644 --- a/src/config.json.in +++ b/src/config.json.in @@ -74,6 +74,17 @@ "mpris": { "image-size": 96, "image-radius": 12 + }, + "buttons-grid": { + "actions": [ + { + "label": "яки", + "type": "toggle", + "active": true, + "command": "sh -c '[[ $SWAYNC_TOGGLE_STATE == true ]] && nmcli radio wifi on || nmcli radio wifi off'", + "update_command": "sh -c '[[ $(nmcli radio wifi) == \"enabled\" ]] && echo true || echo false'" + } + ] } } } diff --git a/src/configSchema.json b/src/configSchema.json index 1a8510aa..f5e962a9 100644 --- a/src/configSchema.json +++ b/src/configSchema.json @@ -427,6 +427,22 @@ "type": "string", "description": "Command to be executed on click", "default": "" + }, + "type": { + "type": "string", + "description": "Type of the button; toggle buttons receive the .active css class and an env variable 'SWAYNC_TOGGLE_STATE' is set. See example in the default config.json", + "default": "normal", + "enum": ["normal", "toggle"] + }, + "update-command": { + "type": "string", + "description": "Command to be executed on visibility change of cc to update the active state of the toggle button (should echo true or false)", + "default": "" + }, + "active": { + "type": "bool", + "description": "Wether the toggle button is active as default or not", + "default": false } } } diff --git a/src/controlCenter/widgets/baseWidget.vala b/src/controlCenter/widgets/baseWidget.vala index 3a6e3876..bc3455c7 100644 --- a/src/controlCenter/widgets/baseWidget.vala +++ b/src/controlCenter/widgets/baseWidget.vala @@ -14,6 +14,20 @@ namespace SwayNotificationCenter.Widgets { public unowned SwayncDaemon swaync_daemon; public unowned NotiDaemon noti_daemon; + public enum ButtonType { + TOGGLE, + NORMAL; + + public static ButtonType parse (string value) { + switch (value) { + case "toggle": + return ButtonType.TOGGLE; + default: + return ButtonType.NORMAL; + } + } + } + protected BaseWidget (string suffix, SwayncDaemon swaync_daemon, NotiDaemon noti_daemon) { this.suffix = suffix; this.key = widget_name + (suffix.length > 0 ? "#%s".printf (suffix) : ""); @@ -91,14 +105,23 @@ namespace SwayNotificationCenter.Widgets { for (int i = 0; i < actions.get_length (); i++) { string label = actions.get_object_element (i).get_string_member_with_default ("label", "label"); string command = actions.get_object_element (i).get_string_member_with_default ("command", ""); + string t = actions.get_object_element (i).get_string_member_with_default ("type", "normal"); + ButtonType type = ButtonType.parse (t); + string update_command = + actions.get_object_element (i).get_string_member_with_default ("update-command", ""); + bool active = actions.get_object_element (i).get_boolean_member_with_default ("active", false); res[i] = Action () { label = label, - command = command + command = command, + type = type, + update_command = update_command, + active = active }; } return res; } + protected async void execute_command (string cmd, string[] env_additions = {}) { string msg = ""; yield Functions.execute_command (cmd, env_additions, out msg); diff --git a/src/controlCenter/widgets/buttonsGrid/buttonsGrid.vala b/src/controlCenter/widgets/buttonsGrid/buttonsGrid.vala index 9953e371..6328343e 100644 --- a/src/controlCenter/widgets/buttonsGrid/buttonsGrid.vala +++ b/src/controlCenter/widgets/buttonsGrid/buttonsGrid.vala @@ -10,6 +10,7 @@ namespace SwayNotificationCenter.Widgets { } Action[] actions; + List toggle_buttons; public ButtonsGrid (string suffix, SwayncDaemon swaync_daemon, NotiDaemon noti_daemon) { base (suffix, swaync_daemon, noti_daemon); @@ -26,14 +27,29 @@ namespace SwayNotificationCenter.Widgets { // add action to container foreach (var act in actions) { - Gtk.Button b = new Gtk.Button.with_label (act.label); - - b.clicked.connect (() => execute_command.begin (act.command)); - - container.insert (b, -1); + switch (act.type) { + case ButtonType.TOGGLE: + ToggleButton tb = new ToggleButton (act.label, act.command, act.update_command, act.active); + container.insert (tb, -1); + toggle_buttons.append (tb); + break; + default: + Gtk.Button b = new Gtk.Button.with_label (act.label); + b.clicked.connect (() => execute_command.begin (act.command)); + container.insert (b, -1); + break; + } } show_all (); } + + public override void on_cc_visibility_change (bool value) { + if (!value) { + foreach (var tb in toggle_buttons) { + tb.on_update.begin (); + } + } + } } } diff --git a/src/controlCenter/widgets/menubar/menubar.vala b/src/controlCenter/widgets/menubar/menubar.vala index 6797d470..36a4ce93 100644 --- a/src/controlCenter/widgets/menubar/menubar.vala +++ b/src/controlCenter/widgets/menubar/menubar.vala @@ -25,6 +25,9 @@ namespace SwayNotificationCenter.Widgets { public struct Action { string ? label; string ? command; + BaseWidget.ButtonType ? type; + string ? update_command; + bool ? active; } public class Menubar : BaseWidget { @@ -38,6 +41,7 @@ namespace SwayNotificationCenter.Widgets { Gtk.Box topbar_container; List menu_objects; + List toggle_buttons; public Menubar (string suffix, SwayncDaemon swaync_daemon, NotiDaemon noti_daemon) { base (suffix, swaync_daemon, noti_daemon); @@ -74,11 +78,18 @@ namespace SwayNotificationCenter.Widgets { if (obj.name != null) container.get_style_context ().add_class (obj.name); foreach (Action a in obj.actions) { - Gtk.Button b = new Gtk.Button.with_label (a.label); - - b.clicked.connect (() => execute_command.begin (a.command)); - - container.add (b); + switch (a.type) { + case ButtonType.TOGGLE: + ToggleButton tb = new ToggleButton (a.label, a.command, a.update_command, a.active); + container.add (tb); + toggle_buttons.append (tb); + break; + default: + Gtk.Button b = new Gtk.Button.with_label (a.label); + b.clicked.connect (() => execute_command.begin (a.command)); + container.add (b); + break; + } } switch (obj.position) { case Position.LEFT: @@ -210,6 +221,9 @@ namespace SwayNotificationCenter.Widgets { foreach (var obj in menu_objects) { obj.revealer ?.set_reveal_child (false); } + foreach (var tb in toggle_buttons) { + tb.on_update.begin (); + } } } } diff --git a/src/controlCenter/widgets/shared/toggleButton.vala b/src/controlCenter/widgets/shared/toggleButton.vala new file mode 100644 index 00000000..1d55b991 --- /dev/null +++ b/src/controlCenter/widgets/shared/toggleButton.vala @@ -0,0 +1,44 @@ +namespace SwayNotificationCenter.Widgets { + class ToggleButton : Gtk.ToggleButton { + + private string command; + private string update_command; + + public ToggleButton (string label, string command, string update_command, bool active) { + this.command = command; + this.update_command = update_command; + this.label = label; + + if (active) { + this.active = true; + } + + this.toggled.connect (on_toggle); + } + + private async void on_toggle () { + string msg = ""; + string[] env_additions = { "SWAYNC_TOGGLE_STATE=" + this.active.to_string () }; + yield Functions.execute_command (this.command, env_additions, out msg); + } + + public async void on_update () { + if (update_command == "") return; + string msg = ""; + string[] env_additions = { "SWAYNC_TOGGLE_STATE=" + this.active.to_string () }; + yield Functions.execute_command (this.update_command, env_additions, out msg); + try { + // remove trailing whitespaces + Regex regex = new Regex ("\\s+$"); + string res = regex.replace (msg, msg.length, 0, ""); + if (res.up () == "TRUE") { + this.active = true; + } else { + this.active = false; + } + } catch (RegexError e) { + stderr.printf ("RegexError: %s\n", e.message); + } + } + } +} diff --git a/src/functions.vala b/src/functions.vala index d0d7ebdb..5918dc78 100644 --- a/src/functions.vala +++ b/src/functions.vala @@ -313,13 +313,38 @@ namespace SwayNotificationCenter { Shell.parse_argv (cmd, out argvp); Pid child_pid; - Process.spawn_async ( + int std_input; + int std_output; + int std_err; + Process.spawn_async_with_pipes ( "/", argvp, spawn_env, SpawnFlags.SEARCH_PATH | SpawnFlags.DO_NOT_REAP_CHILD, null, - out child_pid); + out child_pid, + out std_input, + out std_output, + out std_err); + + // stdout: + string res = ""; + IOChannel output = new IOChannel.unix_new (std_output); + output.add_watch (IOCondition.IN | IOCondition.HUP, (channel, condition) => { + if (condition == IOCondition.HUP) { + return false; + } + try { + channel.read_line (out res, null, null); + return true; + } catch (IOChannelError e) { + stderr.printf ("stdout: IOChannelError: %s\n", e.message); + return false; + } catch (ConvertError e) { + stderr.printf ("stdout: ConvertError: %s\n", e.message); + return false; + } + }); // Close the child when the spawned process is idling int end_status = 0; @@ -330,6 +355,7 @@ namespace SwayNotificationCenter { }); // Waits until `run_script.callback()` is called above yield; + msg = res; return end_status == 0; } catch (Error e) { stderr.printf ("Run_Script Error: %s\n", e.message); diff --git a/src/meson.build b/src/meson.build index b3d200bc..8a62739a 100644 --- a/src/meson.build +++ b/src/meson.build @@ -26,6 +26,7 @@ widget_sources = [ # Helpers 'controlCenter/widgets/baseWidget.vala', 'controlCenter/widgets/factory.vala', + 'controlCenter/widgets/shared/toggleButton.vala', # Widget: Title 'controlCenter/widgets/title/title.vala', # Widget: Dnd diff --git a/src/style.css b/src/style.css index 967a069e..4e3e69ff 100644 --- a/src/style.css +++ b/src/style.css @@ -282,8 +282,11 @@ border-radius: 12px; } +/* style given to the active toggle button */ +.widget-buttons-grid>flowbox>flowboxchild>button.toggle:checked { +} + .widget-buttons-grid>flowbox>flowboxchild>button:hover { - background: @noti-bg-hover; } /* Menubar widget */