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();
}