foxygit / sxbar Log in
commit 1bcd796d6092c06317a620a97d96d865de22293e
Author:     MrJensK <jens.se@icloud.com>
AuthorDate: Thu May 14 12:19:13 2026 +0200
Commit:     MrJensK <jens.se@icloud.com>
CommitDate: Thu May 14 12:19:13 2026 +0200

    Add scroll command functionality to modules in configuration
---
 default_sxbarc |  8 ++++++++
 src/defs.h     |  2 ++
 src/parser.c   | 24 ++++++++++++++++--------
 src/sxbar.c    | 11 +++++++++--
 4 files changed, 35 insertions(+), 10 deletions(-)

diff --git a/default_sxbarc b/default_sxbarc
index c612c61..4036cef 100644
--- a/default_sxbarc
+++ b/default_sxbarc
@@ -47,6 +47,14 @@ module : cpu     : false : 3
 # click : clock   : "xclock"
 # click : battery : "xterm -e 'upower -i /org/freedesktop/UPower/devices/battery_BAT0; read'"
 # click : network : "xterm -e nmtui"
+#
+# Scroll wheel — scroll up/down over a module to run a command
+# scroll_up   : module_name : "command"
+# scroll_down : module_name : "command"
+#
+# Examples:
+# scroll_up   : volume : "wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+"
+# scroll_down : volume : "wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"

 # Media controller example (requires playerctl)
 # playerctl uses the MPRIS2 protocol and works with most players automatically:
diff --git a/src/defs.h b/src/defs.h
index 03533a2..01d4168 100644
--- a/src/defs.h
+++ b/src/defs.h
@@ -13,6 +13,8 @@ typedef struct Module {
 	char *name;
 	char *command;
 	char *click_command;
+	char *scroll_up_command;
+	char *scroll_down_command;
 	int enabled;
 	int refresh_interval;
 	time_t last_update;
diff --git a/src/parser.c b/src/parser.c
index 2c0ae2e..0c56eea 100644
--- a/src/parser.c
+++ b/src/parser.c
@@ -258,11 +258,11 @@ int parse_config(Config *cfg)
 			m->refresh_interval = interval;
 			m->last_update    = 0;
 			m->cached_output  = NULL;
-		} else if (!strcmp(key, "click")) {
-			/* click : module_name : "command" */
+		} else if (!strcmp(key, "click") || !strcmp(key, "scroll_up") || !strcmp(key, "scroll_down")) {
+			/* click/scroll_up/scroll_down : module_name : "command" */
 			char *p1 = strchr(rest, ':');
 			if (!p1) {
-				fprintf(stderr, "sxbarc:%d: click missing name and command\n", lineno);
+				fprintf(stderr, "sxbarc:%d: %s missing name and command\n", lineno, key);
 				continue;
 			}
 			*p1 = '\0';
@@ -270,25 +270,33 @@ int parse_config(Config *cfg)
 			char *after = strip(p1 + 1);

 			if (*after != '"' && *after != '\'') {
-				fprintf(stderr, "sxbarc:%d: click command must be quoted\n", lineno);
+				fprintf(stderr, "sxbarc:%d: %s command must be quoted\n", lineno, key);
 				continue;
 			}
 			char q = *after;
 			char *cmd_start = after + 1;
 			char *closing   = strchr(cmd_start, q);
 			if (!closing) {
-				fprintf(stderr, "sxbarc:%d: click command missing closing quote\n", lineno);
+				fprintf(stderr, "sxbarc:%d: %s command missing closing quote\n", lineno, key);
 				continue;
 			}
 			*closing = '\0';

 			Module *m = find_module(cfg, name);
 			if (!m) {
-				fprintf(stderr, "sxbarc:%d: click: unknown module '%s'\n", lineno, name);
+				fprintf(stderr, "sxbarc:%d: %s: unknown module '%s'\n", lineno, key, name);
 				continue;
 			}
-			free(m->click_command);
-			m->click_command = expand_home(cmd_start);
+			if (!strcmp(key, "click")) {
+				free(m->click_command);
+				m->click_command = expand_home(cmd_start);
+			} else if (!strcmp(key, "scroll_up")) {
+				free(m->scroll_up_command);
+				m->scroll_up_command = expand_home(cmd_start);
+			} else {
+				free(m->scroll_down_command);
+				m->scroll_down_command = expand_home(cmd_start);
+			}
 		} else {
 			fprintf(stderr, "sxbarc:%d: unknown option '%s'\n", lineno, key);
 		}
diff --git a/src/sxbar.c b/src/sxbar.c
index 0d67ab1..8831f00 100644
--- a/src/sxbar.c
+++ b/src/sxbar.c
@@ -89,6 +89,8 @@ void cleanup_modules(void)
 		free(config.modules[i].name);
 		free(config.modules[i].command);
 		free(config.modules[i].click_command);
+		free(config.modules[i].scroll_up_command);
+		free(config.modules[i].scroll_down_command);
 		free(config.modules[i].cached_output);
 	}
 	free(config.modules);
@@ -384,7 +386,8 @@ static void spawn(const char *cmd)

 void hdl_button(XEvent *xev)
 {
-	if (xev->xbutton.button != Button1)
+	unsigned int btn = xev->xbutton.button;
+	if (btn != Button1 && btn != Button4 && btn != Button5)
 		return;

 	int idx = find_window_monitor(xev->xbutton.window);
@@ -409,8 +412,12 @@ void hdl_button(XEvent *xev)
 		char *out = config.modules[i].cached_output;
 		int tw = XTextWidth(font, out, strlen(out));
 		if (x_click >= mx && x_click < mx + tw + mod_sp) {
-			if (config.modules[i].click_command)
+			if (btn == Button1 && config.modules[i].click_command)
 				spawn(config.modules[i].click_command);
+			else if (btn == Button4 && config.modules[i].scroll_up_command)
+				spawn(config.modules[i].scroll_up_command);
+			else if (btn == Button5 && config.modules[i].scroll_down_command)
+				spawn(config.modules[i].scroll_down_command);
 			return;
 		}
 		mx += tw + mod_sp;