commit 5df6f7f680489fbefb214ce701a5d2bac7f5b6da
Author: MrJensK <jens.se@icloud.com>
AuthorDate: Wed Aug 12 20:47:38 2026 +0200
Commit: MrJensK <jens.se@icloud.com>
CommitDate: Wed Aug 12 20:47:38 2026 +0200
scrips install updates
---
README.md | 38 ++++++++++++++++++++++--
src/parser.c | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 132 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 806d331..5dac418 100644
--- a/README.md
+++ b/README.md
@@ -195,8 +195,9 @@ prefix_cmd : battery : "~/.config/sxbar/scripts/battery_icon.sh"
This and a matching `volume_icon.sh` live under `scripts/` in this repo and
are installed by `make install` to `$(PREFIX)/share/sxbar/scripts/` as
reference copies — `sxbarc` still points at your own copy under
-`~/.config/sxbar/scripts/`, so copy and edit from there rather than the
-installed ones.
+`~/.config/sxbar/scripts/`, which `sxbar` seeds from those reference
+copies on first run (see "Auto-seeded `~/.config/sxbar/scripts/`" below),
+so just edit the copy that ends up there rather than the installed ones.
### Result
@@ -1108,6 +1109,39 @@ for every module.
it silently resolves to the no-op fallback, same as any other name
without a matching script.
+## Auto-seeded `~/.config/sxbar/scripts/`
+
+`make install` only ever populated `$(PREFIX)/share/sxbar/scripts/` (the
+system-wide reference copies) — nothing created or filled in
+`~/.config/sxbar/scripts/`, the copy `resolve_script()` actually prefers
+and the one users are told to edit. On a genuinely fresh install that
+directory didn't exist at all, so every built-in module silently fell
+back to the reference copy (or, if `PREFIX` wasn't `/usr/local`, to a
+no-op) until the user found the docs and copied the scripts over by hand.
+
+### Changes
+- Added `seed_user_scripts()` (`src/parser.c`), called once at the top of
+ `parse_config()`. It walks `/usr/local/share/sxbar/scripts/*.sh` (the
+ same hardcoded reference path `resolve_script()` already falls back to)
+ and, for each script not already present under
+ `~/.config/sxbar/scripts/`, creates that directory if needed and copies
+ the file in with mode `0755`.
+- Never overwrites a file that's already there, so hand-edited scripts
+ are untouched — it only fills in gaps, which also means a script added
+ for a new built-in module in a later `sxbar` version appears
+ automatically on next launch without clobbering everything else.
+- Runs as whatever user launches `sxbar` (never the installer, which is
+ typically `sudo make install`), so the seeded files end up correctly
+ user-owned instead of root-owned.
+
+### Result
+- A fresh install works out of the box: `sudo make install` for the
+ binary and reference scripts, then just running `sxbar` populates
+ `~/.config/sxbar/scripts/` with editable copies on first launch.
+- `resolve_script()`'s existing preference order (user copy → installed
+ reference → no-op) is unchanged; this only affects how the user copy
+ gets there in the first place.
+
## Disk footprint
- `sxbar` is lightweight: the compiled binary is about 36 KB without
diff --git a/src/parser.c b/src/parser.c
index d1ab803..08b4f55 100644
--- a/src/parser.c
+++ b/src/parser.c
@@ -1,9 +1,12 @@
#define _POSIX_C_SOURCE 200809L
#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/stat.h>
#include <unistd.h>
#include "defs.h"
@@ -121,6 +124,97 @@ static int grow_modules(Config *cfg)
return 0;
}
+static int mkdir_p(const char *dir)
+{
+ char tmp[PATH_MAX];
+ snprintf(tmp, sizeof tmp, "%s", dir);
+ for (char *p = tmp + 1; *p; p++) {
+ if (*p != '/')
+ continue;
+ *p = '\0';
+ if (mkdir(tmp, 0755) != 0 && errno != EEXIST)
+ return -1;
+ *p = '/';
+ }
+ if (mkdir(tmp, 0755) != 0 && errno != EEXIST)
+ return -1;
+ return 0;
+}
+
+static void copy_script(const char *src_path, const char *dst_path)
+{
+ FILE *src = fopen(src_path, "rb");
+ if (!src)
+ return;
+ FILE *dst = fopen(dst_path, "wb");
+ if (!dst) {
+ fclose(src);
+ return;
+ }
+ char buf[4096];
+ size_t n;
+ while ((n = fread(buf, 1, sizeof buf, src)) > 0)
+ fwrite(buf, 1, n, dst);
+ fclose(src);
+ fclose(dst);
+ chmod(dst_path, 0755);
+}
+
+/* on a fresh install, $PREFIX/share/sxbar/scripts/ (installed by `make
+ * install`) has every built-in module's reference script, but
+ * ~/.config/sxbar/scripts/ -- the copy resolve_script() actually prefers,
+ * and the one users are told to edit -- starts out empty; nothing ever
+ * created it. Seed it once at startup: copy in any reference script whose
+ * name isn't already present under ~/.config/sxbar/scripts/, so built-in
+ * modules work (and are editable in place) without a manual copy step
+ * first. Never overwrites a file that's already there, so existing user
+ * edits are untouched; only fills in gaps (e.g. a script for a built-in
+ * module added in a later sxbar version). */
+static void seed_user_scripts(void)
+{
+ const char *home = getenv("HOME");
+ if (!home)
+ return;
+
+ char user_dir[PATH_MAX];
+ snprintf(user_dir, sizeof user_dir, "%s/.config/sxbar/scripts", home);
+
+ const char *sys_dir = "/usr/local/share/sxbar/scripts";
+ DIR *d = opendir(sys_dir);
+ if (!d)
+ return; /* not installed system-wide yet -- nothing to seed from */
+
+ int seeded = 0;
+ struct dirent *ent;
+ while ((ent = readdir(d))) {
+ size_t len = strlen(ent->d_name);
+ if (len < 4 || strcmp(ent->d_name + len - 3, ".sh"))
+ continue;
+
+ char dst_path[PATH_MAX];
+ if ((size_t)snprintf(dst_path, sizeof dst_path, "%s/%s", user_dir,
+ ent->d_name) >= sizeof dst_path)
+ continue; /* path too long to ever exist -- skip */
+ if (access(dst_path, F_OK) == 0)
+ continue; /* user already has (or edited) this one */
+
+ if (!seeded && mkdir_p(user_dir) != 0) {
+ closedir(d);
+ return;
+ }
+
+ char src_path[PATH_MAX];
+ snprintf(src_path, sizeof src_path, "%s/%s", sys_dir, ent->d_name);
+ copy_script(src_path, dst_path);
+ seeded++;
+ }
+ closedir(d);
+
+ if (seeded)
+ printf("sxbarc: seeded %d script%s into %s\n", seeded,
+ seeded == 1 ? "" : "s", user_dir);
+}
+
/* resolve a module name to its script: the user's own edited copy at
* ~/.config/sxbar/scripts/<name>.sh wins over the installed reference copy
* at $PREFIX/share/sxbar/scripts/<name>.sh (currently hardcoded to
@@ -661,6 +755,8 @@ void load_popup_from_script(Module *m, const char *script_path)
int parse_config(Config *cfg)
{
+ seed_user_scripts();
+
char path[PATH_MAX];
FILE *f = open_config(path, sizeof(path));
if (!f)