aboutsummaryrefslogtreecommitdiffstats
path: root/include
diff options
context:
space:
mode:
authoruser <user@clank>2026-09-22 12:42:15 +0200
committeruser <user@clank>2026-09-22 12:42:15 +0200
commitfaa3fb6ee575a900abc574b0f9ed5ff291761837 (patch)
treece644998c144687e205d1f4e97e6f999de98723b /include
parenttabs: idle tabs you can actually read across the room (diff)
downloadslosh-faa3fb6ee575a900abc574b0f9ed5ff291761837.tar.gz
slosh-faa3fb6ee575a900abc574b0f9ed5ff291761837.tar.xz
slosh-faa3fb6ee575a900abc574b0f9ed5ff291761837.zip
theme: a name and a directory, not sixty colours in your config
Wearing a theme meant pasting its `theme { }` block into config.kdl, or including a path to a file you had copied there yourself. Both make the palette a literal in your config, and a literal is the one thing you cannot change cheaply: not from a script, not from a running session, not by a list of what is available, and never again from upstream -- the colours you pasted are the colours that theme had the day you pasted them. So a theme is a file in a directory now, and a config names it: theme_name "phosphor" theme { frame_focus "#00ff88" } // ...but that one colour is mine theme_name is applied after the includes and before everything the file says itself, which is the rule includes already follow and for the same reason: the loader reads a document by asking it for names rather than walking it in order, so "here" is not a position it could honour, and "what you asked for is the base, what you wrote is yours" is the rule worth having. Two directories, yours first: `themes/` beside the root config -- beside the file that was loaded, so a config somewhere else (SLOSH_CONFIG, or one handed to --check) takes its themes with it -- and then <prefix>/share/slosh/themes, compiled in from PREFIX. install(1) has been putting themes there all along and nothing could read them; now a shipped theme is nameable without being copied, and a file of yours with the same name shadows it, which is how a root-owned theme gets edited. A name, not a path. The directory is what makes a theme switchable, listable and shadowable, and a path silently does none of those, so `theme_name "../x.kdl"` is refused with a line pointing at `include`, which still works and is still the answer for a file somewhere a directory of names cannot reach. A theme that is not there is one complaint and no more (D9): the rest of the config applies, because losing your keybindings over a mistyped colour scheme would be the wrong trade. A theme file is read by the same loader, so it is watched like any other: saving one reloads the session wearing it, and the derivations (tab_status_fg, tab_idle_bg) run over the theme's own colours exactly as before. Switching without editing anything, over the socket a session already has: slosh -s main cmd theme # amber default* ... slosh -s main cmd '{"cmd":"theme","name":"amber"}' # now slosh -s main cmd '{"cmd":"theme","name":"amber","save":true}' The session holds the name over later reloads, so saving your config for an unrelated reason does not put the old colours back; `save` writes the theme_name line into the file, replacing the one already there and leaving every other line and comment alone -- dumping the whole config back over itself would reformat a hand-written file and drop its comments, which is not a fair price for changing a colour scheme. A name is tried on a throwaway config first, so a typo costs nothing. An empty name gives the answer back to the file. --dump-config stops writing colours: it writes the theme's name and the colours a config overrode by hand, so a dumped config keeps following its theme instead of freezing it. --dump-theme is the other half and is new: the resolved palette as a theme file, which is how you start one from the session in front of you rather than from an empty file. It is also what keeps contrib/themes/default.kdl honest -- that file is now the annotated reference (the commented palette moved there out of config.kdl, where it was 119 lines of colour in the middle of the prose), and a test compares it against the compiled-in one. While in there: tests/test_themes.py's "every theme sets every colour" guard had been grepping for `&c->`, the spelling before the colour table moved to offsetof, so it had been finding zero colours and passing for months. Fixed, with the derived and opt-in colours exempted for the reasons they are derived and opt-in, and with a check that the guard found a table at all.
Diffstat (limited to 'include')
-rw-r--r--include/app.h17
-rw-r--r--include/config.h63
2 files changed, 80 insertions, 0 deletions
diff --git a/include/app.h b/include/app.h
index 106d54a..74b841d 100644
--- a/include/app.h
+++ b/include/app.h
@@ -59,6 +59,23 @@ char *app_take_clipboard(app_t *a);
/* Re-read the config file. Keeps the working one if the new file is broken. */
bool app_reload_config(char *err, size_t errcap);
+
+/* Switch the session to a named theme, now.
+ *
+ * The name is held by the session and re-applied over every later reload, so
+ * editing your config does not quietly put the old colours back. `save` also
+ * writes the `theme_name` line into the config file, which is the difference
+ * between "for now" and "from now on" -- a live switch that edited your config
+ * behind you would be rude, and one that vanished on the next reload would be
+ * useless, so it is a choice the caller makes. An empty name drops the
+ * session's own answer and goes back to whatever the file says. */
+bool app_set_theme(const char *name, bool save, char *err, size_t errcap);
+/* The theme in force, or "" when nobody has named one. */
+const char *app_theme(void);
+/* What there is to pick from, and where it was looked for. Through the app
+ * rather than the config, because the front ends do not own a config. */
+size_t app_themes(char (*out)[64], size_t max);
+size_t app_theme_dirs(char (*out)[512], size_t max);
/* The files the config in force was read from: the one that was loaded and
* everything it included, existing or not. What the watcher watches. */
size_t app_config_files(const char **out, size_t max);
diff --git a/include/config.h b/include/config.h
index e24817a..8d1917b 100644
--- a/include/config.h
+++ b/include/config.h
@@ -142,6 +142,16 @@ enum {
* problems in it has one problem, and the list is long enough to say so. */
#define CONFIG_MSGS_MAX 32
+/* Room for the colour table in config.c, checked against it there: the flags
+ * beside it live in the config, so the header needs a ceiling rather than the
+ * table. Generous, because overshooting costs one byte per unused slot. */
+#define THEME_COLORS_MAX 128
+
+/* How many named themes a picker will list, and how long a name may be. A
+ * theme is a file in a directory; both numbers are "more than anyone has". */
+#define THEMES_MAX 64
+#define THEME_NAME_MAX 64
+
typedef struct {
/* geometry */
uint16_t gap, gap_aspect;
@@ -571,6 +581,31 @@ typedef struct {
size_t nproject_roots;
int project_depth;
+ /* Named themes. `theme_name "phosphor"` reads `phosphor.kdl` out of the
+ * theme directory and applies it *before* the rest of the file, exactly as
+ * an include does: the theme is the base, anything you write beside it
+ * wins. NULL is "nobody asked", and the compiled-in palette stands.
+ *
+ * `theme_dir` is where to look, resolved against the file that said it when
+ * it is relative; NULL means the pair of directories config_theme_dirs
+ * derives -- `themes/` beside your config, then the ones `make install`
+ * put in `<prefix>/share/slosh/themes`. Two rather than one so a theme that
+ * ships with slosh is reachable by name without being copied first, and a
+ * theme of yours with the same name shadows it. */
+ char *theme_dir;
+ char *theme_name;
+
+ /* Which colours were named by hand rather than inherited from the theme the
+ * config asked for, indexed like the table in config.c. Only the dump reads
+ * it, and only so that `--dump-config` can write `theme_name` plus your two
+ * overrides instead of freezing sixty resolved colours into a file -- which
+ * would pin the theme's palette at the moment you dumped it and quietly
+ * make every later change to that theme invisible. */
+ bool theme_set[THEME_COLORS_MAX];
+ /* Loader scratch: true while a named theme file is being read, which is how
+ * the flags above tell "the theme said it" from "you said it". */
+ bool in_theme;
+
/* The layout a project with no file of its own opens as. Yours rather than a
* guess about your stack: relative paths in it bind to whichever project is
* being opened, so one file is the shape you start every project in. NULL is
@@ -603,6 +638,29 @@ typedef struct {
* it keeps the defaults and writes the reason to `err`. */
void config_defaults(config_t *c);
bool config_load(config_t *c, const char *path, char *err, size_t errcap);
+
+/* Apply a named theme over a config that is already loaded, as if the config
+ * had asked for it: the same lookup, the same file, the same derivations. How
+ * a live theme switch is made, and why switching is not a special path through
+ * the loader -- a theme applied at runtime and one named in a config have to
+ * end up at the same palette, or the session and its file disagree. */
+bool config_apply_theme(config_t *c, const char *name, char *err,
+ size_t errcap);
+
+/* Where named themes are looked for, in order. Absolute and expanded. */
+size_t config_theme_dirs(const config_t *c, char (*out)[512], size_t max);
+
+/* Every theme that can be named, sorted, each one listed once -- a name in an
+ * earlier directory shadows the same name in a later one, which is what makes
+ * a theme of yours able to replace a shipped one. */
+size_t config_themes(const config_t *c, char (*out)[THEME_NAME_MAX],
+ size_t max);
+
+/* Write `theme_name "x"` into a config file, replacing the line already there
+ * or adding one. For a switch somebody asked to keep: the session can hold a
+ * theme for itself, but only the file can hold it past a restart. */
+bool config_write_theme_name(const char *path, const char *name, char *err,
+ size_t errcap);
/* $SLOSH_CONFIG, else $XDG_CONFIG_HOME/slosh/config.kdl, else ~/.config/… */
const char *config_default_path(void);
/* Every complaint from the last load, oldest first: "config.kdl:12: ...".
@@ -676,6 +734,11 @@ char *config_render(const config_t *c);
/* The same, for a config nobody has edited: what a fresh install would do.
* Caller frees. */
char *config_dump_defaults(void);
+/* The palette in force as a theme file, which is how a theme is started from
+ * the one you are looking at. The config dump writes `theme_name` rather than
+ * sixty colours, so this is the only place they are all spelled out. */
+char *config_render_theme(const config_t *c);
+char *config_dump_theme(void);
const char *config_action_group(action_t a);
/* "ctrl+a", "alt+left", "\\", "f" -> key + mods. False if unparseable. */
bool config_parse_chord(const char *text, int *out_key, uint16_t *out_mods);