Nimbin[12]?Sway / SwBr / commits / 7497ed1

SwBr git · main

SwBr - status bar for sway

sway wayland c bar · first commit 2026-08-27 · last commit 2026-10-05 (4 days ago) · synced 3 days ago · upstream: github.com/nimbin2/SwBr

C 95.9% Markdown 3.7%
git clone https://git.christianimmanuel.de/sway/SwBr.gitwget https://git.christianimmanuel.de/sway/SwBr/archive/SwBr.tar.gz

Unify config And stuff

Christian Immanuel · 2026-08-28 15:34 · 7497ed11dbe60f01dd705077f9b773052c6e890b

 Makefile                              |  2 +-
 README.md                             | 29 +++++++++--
 swbr_config.example => config.example | 15 ++++--
 sw_theme.h                            | 10 ++--
 swbr.c                                | 96 +++++++++++++++++++++++------------
 5 files changed, 105 insertions(+), 47 deletions(-)

diff --git a/Makefile b/Makefile
index 2c59b77..339c434 100644
--- a/Makefile
+++ b/Makefile
@@ -31,7 +31,7 @@ install: swbr
 
 config:
 	install -d $(CONFDIR)
-	[ -f $(CONFDIR)/config ] || install -m644 swbr_config.example $(CONFDIR)/config
+	[ -f $(CONFDIR)/config ] || install -m644 config.example $(CONFDIR)/config
 
 uninstall:
 	rm -f $(BINDIR)/swbr
diff --git a/README.md b/README.md
index 01b498e..94de1f3 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@ what is going on.
 sudo apt install build-essential libwayland-dev
 make
 make install          # ~/.local/bin, or PREFIX=/usr/local
-make config           # optional: swbr_config.example -> ~/.config/swbr/config
+make config           # optional: config.example -> ~/.config/swbr/config
 ```
 
 `libwayland-client` is the only library it links. The wlr-layer-shell protocol
@@ -76,7 +76,7 @@ something.
 | `bar` | a gauge, `slim_min`–`slim_max` |
 | `clock` | twelve bars, lit up to the hour — two at 02:00, still two at 02:55, softer before noon |
 | `presence` | a block in the `running` colour, there only while the program is |
-| `media` | two full-height bars while playing, the same two short and dim when paused, nothing when stopped |
+| `media` | a full block while playing, a short dim one when paused, nothing when stopped |
 | `auto` | pick from what the cell prints |
 | `off` | nothing |
 
@@ -180,6 +180,27 @@ so the button's own colour still reads through the gaps — six of them on a
 30 px bar, three in the folded strip, where the same dots run up the workspace
 slot.
 
+The scale is in **cores**: `1.0` is one core kept busy, and `ws_cpu_full=4`
+means four cores fill the column. A share of the whole machine was the wrong
+unit — on sixteen threads a browser pinning two cores is twelve percent, which
+rounded to nothing, and nobody thinks about their machine that way.
+
+Anything alive but below the scale gets a single faint dot rather than
+nothing, so a workspace that is *doing* something never looks asleep — a
+player, a polling script, a shell running something small. A terminal sitting
+at a prompt still gets nothing, which is the distinction that matters.
+
+```
+0.000 cores  ......   a terminal at a prompt
+0.02         +.....   cmus playing
+0.09         +.....   a script polling every second
+0.4          #.....   mpv decoding
+1.9          ###...   a browser working
+4.0          ######   make -j4
+```
+
+`ws_cpu_idle` is where "nothing" ends and that faint dot begins.
+
 They keep one colour — `accent`, held back towards `dim` — and only tip
 towards `urgent` when a workspace is really pinned, so it is the count and not
 the hue that carries the load. `hl` is what paints the focused workspace, so
@@ -197,7 +218,7 @@ no workspace and is left out. The colour runs from `running` through
 `slim_warm` into `urgent`.
 
 ```
-ws_cpu=1  ws_cpu_interval=3  ws_cpu_min=4  ws_cpu_full=60
+ws_cpu=1  ws_cpu_interval=3  ws_cpu_idle=0.01  ws_cpu_min=0.25  ws_cpu_full=4
 ```
 
 The numbers are written to `$XDG_RUNTIME_DIR/swbr-cpu` so swov can show the
@@ -229,7 +250,7 @@ swbr --dump-config > ~/.config/swbr/config
 swbr --probe        # outputs, sizes, font metrics and what each cell prints
 ```
 
-`swbr_config.example` lists everything with defaults. The ones worth knowing:
+`config.example` lists everything with defaults. The ones worth knowing:
 
 | key | |
 | --- | --- |
diff --git a/swbr_config.example b/config.example
similarity index 95%
rename from swbr_config.example
rename to config.example
index d9ba20d..2174833 100644
--- a/swbr_config.example
+++ b/config.example
@@ -149,8 +149,8 @@ cmusui.align=center
 cmusui.pad=4
 cmusui.bg=ffffff14
 cmusui.color=e8b81a
-cmusui.slim=media  # folded: TWO full bars while playing, the same two short
-                   # and dim when paused, nothing when cmus is stopped.
+cmusui.slim=media  # folded: a full block while playing, a short dim one when
+                   # paused, nothing at all when cmus is stopped.
                    # source=cmus knows which, so slim_on is not needed.
 cmusui.slim_w=26   # wider than the other ticks, so it is easy to find and hit
 cmusui.button1=pgrep -x cmus >/dev/null && cmus_control toggle || foot -e cmus
@@ -291,8 +291,15 @@ ws_cpu=1           # a dot on each workspace button showing how much
                    # processor time the windows on it are using. Folded, the
                    # workspace slot fills from the bottom instead
 ws_cpu_interval=3  # seconds between samples
-ws_cpu_min=4       # below this much load nothing is drawn
-ws_cpu_full=60     # this much load paints it fully hot
+ws_cpu_idle=0.01   # in cores. Under this a workspace counts as doing
+                   # nothing and stays blank. Above it, but under ws_cpu_min,
+                   # one faint dot: a player, a polling script, something
+                   # small but alive
+ws_cpu_min=0.25    # where the real scale starts, in cores: 1.0 is one core
+                   # kept busy
+ws_cpu_full=4      # four cores fills the column. A share of the whole
+                   # machine was the wrong unit: on sixteen threads two busy
+                   # cores are twelve percent, which rounds to nothing
 
 running=3ddc84ff   # folded strip: this program is up, and where a charging
                    # gauge's pips ramp to
diff --git a/sw_theme.h b/sw_theme.h
index 63d22c6..0a512fc 100644
--- a/sw_theme.h
+++ b/sw_theme.h
@@ -1,10 +1,10 @@
-/* sw_theme.h — one look for swov, appwheel and swbr.
+/* sw_theme.h — one look for swov, swas and swbr.
  *
  *     ${XDG_CONFIG_HOME:-~/.config}/sw/config
  *
  * key=value, '#' comments, same as every other config here. Keys written
  * before any [section] go to all three programs; keys inside [swov],
- * [appwheel] or [swbr] go to that one only.
+ * [swas] or [swbr] go to that one only.
  *
  * Outside a section only *role* names are understood (surface, accent, hl,
  * ...). Each program translates them to its own keys, and roles it has no use
@@ -34,11 +34,11 @@
  * separated by commas: swbr paints its focused workspace button with `hl`.
  */
 typedef struct {
-    const char *role, *swov, *appwheel, *swbr;
+    const char *role, *swov, *swas, *swbr;
 } SwRole;
 
 static const SwRole SW_ROLES[] = {
-    /* role          swov            appwheel   swbr                           */
+    /* role          swov            swas       swbr                           */
     { "bg",          "bg",           "bg",      NULL                           },
     { "surface",     "tile",         "ring",    "bg"                           },
     { "surface_alt", "tile_sel",     "ring2",   NULL                           },
@@ -97,7 +97,7 @@ static int sw_ci_eq(const char *a, const char *b)
 static const char *sw_role_for(const SwRole *r, const char *app)
 {
     if (sw_ci_eq(app, "swov"))     return r->swov;
-    if (sw_ci_eq(app, "appwheel")) return r->appwheel;
+    if (sw_ci_eq(app, "swas") || sw_ci_eq(app, "appwheel")) return r->swas;
     if (sw_ci_eq(app, "swbr"))     return r->swbr;
     return NULL;
 }
diff --git a/swbr.c b/swbr.c
index 47e61f7..8c83c1a 100644
--- a/swbr.c
+++ b/swbr.c
@@ -791,7 +791,7 @@ typedef struct { int pid; int ws; } PidWs;      /* window pid -> ws_list index *
 
 static PidWs  cpu_win[512];
 static int    cpu_nwin;
-static double cpu_ws_pct[MAX_WORKSPACES];       /* by ws_list index */
+static double cpu_ws_cores[MAX_WORKSPACES];    /* by ws_list index */
 static char   cpu_ws_name[MAX_WORKSPACES][128];
 static int    cpu_ws_n;
 static double cpu_last_at;
@@ -927,7 +927,7 @@ static void cpu_write_cache(void)
     FILE *f = fopen(tmp, "w");
     if (!f) return;
     for (int i = 0; i < cpu_ws_n; ++i)
-        fprintf(f, "%s\t%.1f\n", cpu_ws_name[i], cpu_ws_pct[i]);
+        fprintf(f, "%s\t%.2f\n", cpu_ws_name[i], cpu_ws_cores[i]);
     fclose(f);
     if (rename(tmp, path) != 0) unlink(tmp);
 }
@@ -1030,10 +1030,14 @@ static void cpu_sample(void)
         cpu_ws_n = 0;
         for (int i = 0; i < ws_count && i < MAX_WORKSPACES; ++i) {
             if (used[i] < 0) used[i] = 0;
-            double pct = used[i] / dt / (double)ncpu * 100.0;
-            if (pct < 0) pct = 0;
-            if (pct > 100) pct = 100;
-            cpu_ws_pct[i] = pct;
+            /* core-seconds per second: 1.0 is one core kept busy. A share of
+             * the whole machine was the wrong unit — on sixteen threads a
+             * compiler pinning two cores is twelve percent, which rounds to
+             * nothing, and nobody thinks about their machine that way. */
+            double cores = used[i] / dt;
+            if (cores < 0) cores = 0;
+            if (cores > (double)ncpu) cores = (double)ncpu;
+            cpu_ws_cores[i] = cores;
             str_set(cpu_ws_name[cpu_ws_n], sizeof(cpu_ws_name[0]), ws_list[i].name);
             cpu_ws_n++;
         }
@@ -1143,8 +1147,8 @@ typedef struct {
     int ws_other;                /* show other monitors' workspaces too  */
     int ws_cpu;                  /* the load dot on a workspace button   */
     int ws_cpu_interval;         /* seconds between samples              */
-    float ws_cpu_min, ws_cpu_full;  /* below min nothing shows; full = the
-                                       load that paints it urgent        */
+    float ws_cpu_idle;           /* under this, nothing is happening at all */
+    float ws_cpu_min, ws_cpu_full;  /* the scale, in cores                  */
     Col ws_bg, ws_fg, ws_focused_bg, ws_focused_fg;
     Col ws_visible_bg, ws_visible_fg, ws_urgent_bg, ws_urgent_fg;
     Col mode_bg, mode_fg;
@@ -1218,8 +1222,9 @@ static void config_defaults(Config *c)
     c->ws_other        = 0;
     c->ws_cpu          = 1;
     c->ws_cpu_interval = 3;
-    c->ws_cpu_min      = 4.0f;
-    c->ws_cpu_full     = 60.0f;
+    c->ws_cpu_idle     = 0.01f;   /* one percent of one core */
+    c->ws_cpu_min      = 0.25f;   /* a quarter of one core */
+    c->ws_cpu_full     = 4.0f;    /* four cores fills the column */
     c->ws_focused_bg = (Col){ 0xcb, 0x9b, 0x00, 0xff };
     c->ws_focused_fg = (Col){ 0x14, 0x14, 0x14, 0xff };
     c->ws_visible_bg = (Col){ 0x3c, 0x4a, 0x5c, 0xff };
@@ -1445,6 +1450,7 @@ static void config_set(Config *c, const char *k, const char *v)
     else if (!strcmp(k, "ws_other")) c->ws_other = atoi(v);
     else if (!strcmp(k, "ws_cpu")) c->ws_cpu = atoi(v);
     else if (!strcmp(k, "ws_cpu_interval")) c->ws_cpu_interval = atoi(v);
+    else if (!strcmp(k, "ws_cpu_idle")) c->ws_cpu_idle = (float)atof(v);
     else if (!strcmp(k, "ws_cpu_min")) c->ws_cpu_min = (float)atof(v);
     else if (!strcmp(k, "ws_cpu_full")) c->ws_cpu_full = (float)atof(v);
     else if (!strcmp(k, "ws_focused_bg")) parse_color(v, &c->ws_focused_bg);
@@ -3144,18 +3150,18 @@ static void slim_draw_one(Canvas *cv, Cell *e, int mode, float x, float y,
          * the cell's own colour. Not playing: one short stub at a third of
          * the height, well under half as bright. Same shape at two sizes read
          * as the same thing, so the two states differ in height as well. */
+        /* One block, the full width of the slot. Two of them were meant to
+         * echo a pause glyph, from when playing and paused were told apart by
+         * shape; now that paused is short and dim and stopped is nothing at
+         * all, the height says it, and one wide block is far easier to see
+         * than two thin ones. */
         int st = media_state(e);
-        if (st > 0) {                             /* playing: two full bars */
-            float bar = w * 0.38f, gapw = w - 2.0f * bar;
-            fill_rect(cv, x, y, bar, h, c);
-            fill_rect(cv, x + bar + gapw, y, bar, h, c);
-        } else if (st == 0) {                     /* paused: the same two,
-                                                     short and dim */
+        if (st > 0) {                             /* playing */
+            fill_rect(cv, x, y, w, h, c);
+        } else if (st == 0) {                     /* paused: short and dim */
             Col d = c;
             d.a = 110;
-            float bar = w * 0.38f, gapw = w - 2.0f * bar;
-            fill_rect(cv, x, y + h * 0.34f, bar, h * 0.32f, d);
-            fill_rect(cv, x + bar + gapw, y + h * 0.34f, bar, h * 0.32f, d);
+            fill_rect(cv, x, y + h * 0.34f, w, h * 0.32f, d);
         }
         return;                                   /* st < 0: nothing to show */
     }
@@ -3190,14 +3196,35 @@ static void dot_column(Canvas *cv, float x, float y, float w, float h,
 
 /* How busy, as a colour: quiet green through the accent into urgent. Nothing
  * at all below ws_cpu_min, so an idle bar stays clean. */
-static bool cpu_load_col(int ws_idx, Col *out, float *frac)
-{
+/* How busy, and whether it is only busy on one core.
+ *
+ * The scale is a share of the whole machine, so on sixteen threads a single
+ * pinned core is six percent — real work that looks like idling. When that is
+ * what is happening the bottom dot lights on its own, dimmer than a proper
+ * load, which says "something is running here" without pretending the
+ * workspace is hot. */
+static bool cpu_load_col(int ws_idx, Col *out, float *frac, bool *faint)
+{
+    if (faint) *faint = false;
     if (!cfg.ws_cpu || ws_idx < 0 || ws_idx >= cpu_ws_n) return false;
-    double pct = cpu_ws_pct[ws_idx];
-    if (pct < cfg.ws_cpu_min) return false;
+
+    double cores = cpu_ws_cores[ws_idx];
+    if (cores < cfg.ws_cpu_idle) return false;    /* genuinely nothing */
+
+    /* Something is happening, but not enough to be worth a place on the
+     * scale: a player decoding, a script waking up every second, a shell
+     * running something small. The bottom dot on its own, faint — a terminal
+     * sitting at a prompt gets nothing, this gets a hint. */
+    if (cores < cfg.ws_cpu_min) {
+        if (faint) *faint = true;
+        *frac = 0.0f;
+        *out = col_mix(cfg.dim, cfg.accent, 0.40f);
+        out->a = 120;
+        return true;
+    }
 
     float f = cfg.ws_cpu_full > cfg.ws_cpu_min
-            ? (float)(pct - cfg.ws_cpu_min) / (cfg.ws_cpu_full - cfg.ws_cpu_min)
+            ? (float)(cores - cfg.ws_cpu_min) / (cfg.ws_cpu_full - cfg.ws_cpu_min)
             : 1.0f;
     f = clampf(f, 0.0f, 1.0f);
     *frac = f;
@@ -3257,11 +3284,12 @@ static void draw_signals(Canvas *cv, Bar *b, float y, float h, float s, Runs *rs
         fill_rect(cv, x, y, tick, h, c);
         if (w) {
             int wi = (int)(w - ws_list);
-            Col lc; float lf;
-            if (cpu_load_col(wi, &lc, &lf)) {
+            Col lc; float lf; bool faint;
+            if (cpu_load_col(wi, &lc, &lf, &faint)) {
                 /* Three or four dots up the slot. Filling it solid hid which
                  * workspace it was, which is the thing the slot is for. */
-                dot_column(cv, x, y, tick, h, 1.0f * s, 1.0f * s, lf, lc);
+                dot_column(cv, x, y, tick, h, 1.0f * s, 1.0f * s,
+                           faint ? 0.001f : lf, lc);
             }
         }
         if (w && b->hits < MAX_WORKSPACES) {      /* still switchable when folded */
@@ -3413,14 +3441,15 @@ static void ws_block_draw(Canvas *cv, Bar *b, Font *f, float x, float y0,
                       col_scale_alpha(fg, bfade * 0.72f), w->label);
         }
 
-        Col lc; float lf;
-        if (cpu_load_col(i, &lc, &lf)) {
+        Col lc; float lf; bool faint;
+        if (cpu_load_col(i, &lc, &lf, &faint)) {
             /* Up the right edge of the button, the same dots as the folded
              * strip so the two read as one idea. */
             float dw  = clampf(2.0f * s, 1.0f, 3.0f);
             float pad = 3.0f * s;
             dot_column(cv, x + bwid - dw - pad, by + pad, dw, bh - 2.0f * pad,
-                       2.0f * s, 2.0f * s, lf, col_scale_alpha(lc, bfade));
+                       2.0f * s, 2.0f * s, faint ? 0.001f : lf,
+                       col_scale_alpha(lc, bfade));
         }
 
         if (b->hits < MAX_WORKSPACES) {
@@ -4556,8 +4585,9 @@ static void probe_report(void)
                (double)baseline_for(f, 0, (float)b->bh, (float)b->scale), b->bh);
     }
     if (cfg.ws_cpu) {
-        printf("cpu per workspace, sampled every %ds (min %g%%, full %g%%)\n",
-               cfg.ws_cpu_interval, (double)cfg.ws_cpu_min, (double)cfg.ws_cpu_full);
+        printf("cpu per workspace, sampled every %ds (idle under %g, scale %g..%g cores)\n",
+               cfg.ws_cpu_interval, (double)cfg.ws_cpu_idle,
+               (double)cfg.ws_cpu_min, (double)cfg.ws_cpu_full);
         printf("            %d window(s) in sway's tree to attribute work to\n",
                cpu_nwin);
         for (int i = 0; i < cpu_nwin && i < 8; ++i)
@@ -4612,7 +4642,7 @@ static void probe_report(void)
         }
         if (!cpu_ws_n) printf("            no sample yet\n");
         for (int i = 0; i < cpu_ws_n; ++i)
-            printf("            %-12s %5.1f%%\n", cpu_ws_name[i], cpu_ws_pct[i]);
+            printf("            %-12s %5.2f cores\n", cpu_ws_name[i], cpu_ws_cores[i]);
     }
     static const char *SLIM_NAME[] = { "auto", "tick", "bar", "clock",
                                        "presence", "media", "off" };