foxygit / sxbar Log in
commit 51513ae2948b0bd2c1fb39c81b960cb6b13589c2
Author:     MrJensK <jens.se@icloud.com>
AuthorDate: Tue May 12 20:21:21 2026 +0200
Commit:     MrJensK <jens.se@icloud.com>
CommitDate: Tue May 12 20:21:21 2026 +0200

    update
---
 default_sxbarc |  35 ++++++++
 src/parser.c   | 262 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/parser.h   |   4 +
 src/sxbar.c    |   2 +
 4 files changed, 303 insertions(+)

diff --git a/default_sxbarc b/default_sxbarc
index e69de29..b98d34c 100644
--- a/default_sxbarc
+++ b/default_sxbarc
@@ -0,0 +1,35 @@
+# sxbar configuration
+# Format: key : value
+# Colours accept hex (#rrggbb) or X colour names
+
+# Appearance
+height              : 20
+bottom_bar          : false
+vertical_padding    : 0
+horizontal_padding  : 0
+text_padding        : 5
+border              : false
+border_width        : 1
+background_colour   : #000000
+foreground_colour   : #7abccd
+border_colour       : #005577
+font                : fixed
+
+# Built-in modules
+# module : name : enabled : refresh_interval_seconds
+# Available: clock, date, battery, volume, cpu
+module : clock   : true  : 1
+module : date    : true  : 60
+module : battery : false : 30
+module : volume  : true  : 5
+module : cpu     : false : 3
+
+# Custom script modules (like polybar exec)
+# Command must be quoted. Output is displayed in the bar.
+# custom : label : "command or script path" : refresh_interval_seconds
+#
+# Examples:
+# custom : temp     : "sensors | grep 'Package' | awk '{print $4}'" : 5
+# custom : mem      : "free -h | awk '/^Mem:/{print $3\"/\"$2}'" : 10
+# custom : network  : "~/.config/sxbar/scripts/network.sh" : 5
+# custom : updates  : "checkupdates | wc -l | tr -d ' '" : 300
diff --git a/src/parser.c b/src/parser.c
new file mode 100644
index 0000000..b1c464f
--- /dev/null
+++ b/src/parser.c
@@ -0,0 +1,262 @@
+#define _POSIX_C_SOURCE 200809L
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "defs.h"
+#include "parser.h"
+
+extern unsigned long parse_col(const char *hex);
+
+static char *strip(char *s)
+{
+	while (*s && isspace((unsigned char)*s))
+		s++;
+	if (!*s)
+		return s;
+	char *e = s + strlen(s) - 1;
+	while (e > s && isspace((unsigned char)*e))
+		*e-- = '\0';
+	return s;
+}
+
+static char *strip_comment(char *s)
+{
+	char *c = strchr(s, '#');
+	if (c)
+		*c = '\0';
+	return strip(s);
+}
+
+/* true/false with optional trailing whitespace/comment */
+static int parse_bool(const char *s)
+{
+	return strncmp(s, "true", 4) == 0 &&
+	       (s[4] == '\0' || isspace((unsigned char)s[4]) || s[4] == '#');
+}
+
+/* expand leading ~/ to $HOME/ */
+static char *expand_home(const char *s)
+{
+	if (s[0] != '~' || s[1] != '/')
+		return strdup(s);
+	const char *home = getenv("HOME");
+	if (!home)
+		return strdup(s);
+	size_t len = strlen(home) + strlen(s + 1) + 1;
+	char *out = malloc(len);
+	if (!out)
+		return strdup(s);
+	snprintf(out, len, "%s%s", home, s + 1);
+	return out;
+}
+
+static FILE *open_config(char *path, size_t pathsz)
+{
+	const char *home = getenv("HOME");
+	if (!home) {
+		fputs("sxbarc: HOME not set\n", stderr);
+		return NULL;
+	}
+
+	const char *xdg = getenv("XDG_CONFIG_HOME");
+	if (xdg) {
+		snprintf(path, pathsz, "%s/sxbarc", xdg);
+		if (access(path, R_OK) == 0)
+			goto found;
+		snprintf(path, pathsz, "%s/sxbar/sxbarc", xdg);
+		if (access(path, R_OK) == 0)
+			goto found;
+	}
+
+	snprintf(path, pathsz, "%s/.config/sxbarc", home);
+	if (access(path, R_OK) == 0)
+		goto found;
+
+	snprintf(path, pathsz, "%s/.config/sxbar/sxbarc", home);
+	if (access(path, R_OK) == 0)
+		goto found;
+
+	snprintf(path, pathsz, "/usr/local/share/sxbarc");
+	if (access(path, R_OK) == 0)
+		goto found;
+
+	fprintf(stderr, "sxbarc: no configuration file found\n");
+	return NULL;
+
+found:
+	printf("sxbarc: using %s\n", path);
+	FILE *f = fopen(path, "r");
+	if (!f)
+		fprintf(stderr, "sxbarc: cannot open %s\n", path);
+	return f;
+}
+
+static Module *find_module(Config *cfg, const char *name)
+{
+	for (int i = 0; i < cfg->module_count; i++) {
+		if (cfg->modules[i].name && !strcmp(cfg->modules[i].name, name))
+			return &cfg->modules[i];
+	}
+	return NULL;
+}
+
+static int grow_modules(Config *cfg)
+{
+	if (cfg->module_count < cfg->max_modules)
+		return 0;
+	int newmax = cfg->max_modules * 2;
+	Module *tmp = realloc(cfg->modules, newmax * sizeof(Module));
+	if (!tmp)
+		return -1;
+	cfg->modules   = tmp;
+	cfg->max_modules = newmax;
+	return 0;
+}
+
+int parse_config(Config *cfg)
+{
+	char path[PATH_MAX];
+	FILE *f = open_config(path, sizeof(path));
+	if (!f)
+		return -1;
+
+	char line[1024];
+	int lineno = 0;
+
+	while (fgets(line, sizeof line, f)) {
+		lineno++;
+		char *s = strip(line);
+		if (!*s || *s == '#')
+			continue;
+
+		char *sep = strchr(s, ':');
+		if (!sep) {
+			fprintf(stderr, "sxbarc:%d: missing ':'\n", lineno);
+			continue;
+		}
+		*sep = '\0';
+		char *key  = strip(s);
+		char *rest = strip(sep + 1);
+
+		if (!strcmp(key, "height")) {
+			cfg->height = atoi(rest);
+		} else if (!strcmp(key, "bottom_bar")) {
+			cfg->bottom_bar = parse_bool(rest);
+		} else if (!strcmp(key, "vertical_padding")) {
+			cfg->vertical_padding = atoi(rest);
+		} else if (!strcmp(key, "horizontal_padding")) {
+			cfg->horizontal_padding = atoi(rest);
+		} else if (!strcmp(key, "text_padding")) {
+			cfg->text_padding = atoi(rest);
+		} else if (!strcmp(key, "border")) {
+			cfg->border = parse_bool(rest);
+		} else if (!strcmp(key, "border_width")) {
+			cfg->border_width = atoi(rest);
+		} else if (!strcmp(key, "background_colour") ||
+		           !strcmp(key, "background_color")) {
+			cfg->background_colour = parse_col(rest);
+		} else if (!strcmp(key, "foreground_colour") ||
+		           !strcmp(key, "foreground_color")) {
+			cfg->foreground_colour = parse_col(rest);
+		} else if (!strcmp(key, "border_colour") ||
+		           !strcmp(key, "border_color")) {
+			cfg->border_colour = parse_col(rest);
+		} else if (!strcmp(key, "font")) {
+			strip_comment(rest);
+			free(cfg->font);
+			cfg->font = strdup(rest);
+		} else if (!strcmp(key, "module")) {
+			/* module : name : enabled : interval */
+			char *p1 = strchr(rest, ':');
+			if (!p1) {
+				fprintf(stderr, "sxbarc:%d: module missing fields\n", lineno);
+				continue;
+			}
+			*p1 = '\0';
+			char *name     = strip(rest);
+			char *rest2    = strip(p1 + 1);
+			char *p2       = strchr(rest2, ':');
+			char *enabled_s, *interval_s = NULL;
+			if (p2) {
+				*p2 = '\0';
+				enabled_s  = strip(rest2);
+				interval_s = strip(p2 + 1);
+			} else {
+				enabled_s = strip(rest2);
+			}
+			Module *m = find_module(cfg, name);
+			if (!m) {
+				fprintf(stderr, "sxbarc:%d: unknown builtin module '%s'\n", lineno, name);
+				continue;
+			}
+			m->enabled = parse_bool(enabled_s);
+			if (interval_s && *interval_s)
+				m->refresh_interval = atoi(interval_s);
+		} else if (!strcmp(key, "custom")) {
+			/*
+			 * custom : name : "command" : interval
+			 * command must be quoted; may contain ':' or '#'
+			 * interval is optional (defaults to 5s)
+			 */
+			char *p1 = strchr(rest, ':');
+			if (!p1) {
+				fprintf(stderr, "sxbarc:%d: custom missing name and command\n", lineno);
+				continue;
+			}
+			*p1 = '\0';
+			char *name  = strip(rest);
+			char *after = strip(p1 + 1);
+
+			if (*after != '"' && *after != '\'') {
+				fprintf(stderr, "sxbarc:%d: custom command must be quoted\n", lineno);
+				continue;
+			}
+
+			char q = *after;
+			char *cmd_start = after + 1;
+			char *closing   = strchr(cmd_start, q);
+			if (!closing) {
+				fprintf(stderr, "sxbarc:%d: custom command missing closing quote\n", lineno);
+				continue;
+			}
+			*closing = '\0';
+
+			int interval = 5;
+			char *tail   = strip(closing + 1);
+			if (*tail == ':') {
+				tail = strip(tail + 1);
+				strip_comment(tail);
+				if (*tail)
+					interval = atoi(tail);
+			}
+
+			if (!*cmd_start) {
+				fprintf(stderr, "sxbarc:%d: custom '%s' has empty command\n", lineno, name);
+				continue;
+			}
+
+			if (grow_modules(cfg) < 0) {
+				fprintf(stderr, "sxbarc: out of memory\n");
+				fclose(f);
+				return -1;
+			}
+
+			Module *m         = &cfg->modules[cfg->module_count++];
+			m->name           = strdup(name);
+			m->command        = expand_home(cmd_start);
+			m->enabled        = 1;
+			m->refresh_interval = interval;
+			m->last_update    = 0;
+			m->cached_output  = NULL;
+		} else {
+			fprintf(stderr, "sxbarc:%d: unknown option '%s'\n", lineno, key);
+		}
+	}
+
+	fclose(f);
+	return 0;
+}
diff --git a/src/parser.h b/src/parser.h
new file mode 100644
index 0000000..d5f8237
--- /dev/null
+++ b/src/parser.h
@@ -0,0 +1,4 @@
+#pragma once
+#include "defs.h"
+
+int parse_config(Config *cfg);
diff --git a/src/sxbar.c b/src/sxbar.c
index ad75c4e..5063ca1 100644
--- a/src/sxbar.c
+++ b/src/sxbar.c
@@ -12,6 +12,7 @@
 #include <X11/extensions/Xinerama.h>

 #include "defs.h"
+#include "parser.h"


 void cleanup_modules(void);
@@ -553,6 +554,7 @@ void setup(void)
 	XSelectInput(dpy, root, PropertyChangeMask);

 	init_defaults();
+	parse_config(&config);
 	create_bars();
 	update_modules();
 }