Nimbin[12]?Sway / SWOv / commits / 8ce5892

SWOv git · main

SWOv - window overview for sway

sway wayland c overview · first commit 2026-08-21 · last commit 2026-09-20 (19 days ago) · synced 3 days ago · upstream: github.com/nimbin2/SWOv

C 91.5% Markdown 7.6%
git clone https://git.christianimmanuel.de/sway/SWOv.gitwget https://git.christianimmanuel.de/sway/SWOv/archive/SWOv.tar.gz

Fix make windows below floating windows moveable

Christian Immanuel · 2026-08-21 10:10 · 8ce5892eea09d876b380c3535779d39fd4a5e519

 README.md |   5 +++
 swov.c    | 103 ++++++++++++++++++++++++++++++++++++++++----------------------
 2 files changed, 71 insertions(+), 37 deletions(-)

diff --git a/README.md b/README.md
index bb67780..0278ae3 100644
--- a/README.md
+++ b/README.md
@@ -51,6 +51,9 @@ swov opens with the current workspace selected as a whole, no window picked.
 
 Press, move, release. `Esc` cancels.
 
+A floating or fullscreen window is grabbed by its name plate — the rest of it is
+click-through, so the windows underneath stay selectable.
+
 **A window** onto a tile moves it there. Onto the left or right edge of another
 window it lands beside it, splitting horizontally; top or bottom splits
 vertically. A bar shows the edge before you let go.
@@ -100,3 +103,5 @@ swov --shot /tmp/o.png     # one frame to a PNG, for tuning colours
 - Reordering workspaces renames them, which is all sway offers. Swaps go through
   a temporary name.
 - Scratchpad windows are not shown.
+- Only the name plate of a floating or fullscreen window takes the mouse; clicks
+  on its body go to whatever is beneath it.
diff --git a/swov.c b/swov.c
index 41ecb3f..b245d0f 100644
--- a/swov.c
+++ b/swov.c
@@ -1,8 +1,7 @@
 /* swov — a fast window/workspace overview for Sway (SDL3)
  *
  * Build:
- *   cc -std=c11 -O2 -Wall -Wextra -o swov swov.c \
- *      $(pkg-config --cflags --libs sdl3 sdl3-image sdl3-ttf)
+cc -std=c11 -O2 -Wall -Wextra -o swov swov.c $(pkg-config --cflags --libs sdl3 sdl3-image sdl3-ttf)
  *
  * Runtime deps: a running Sway session ($SWAYSOCK). No jq, no shell-outs:
  * the program talks to the Sway IPC socket directly. fontconfig (fc-match)
@@ -1528,6 +1527,8 @@ typedef struct {
 
     int   ws;                  /* index into WSS */
     SDL_FRect card;            /* where it is drawn (render pixels) */
+    SDL_FRect hit;             /* what the mouse acts on: the whole card, or
+                                * just the name plate of a floating window   */
     int   group;               /* head index of a tabbed/stacked group, or -1 */
 
     int   lay_mode;            /* how the card contents are arranged */
@@ -2017,6 +2018,34 @@ static void compute_card_layout(Win *w)
     w->lay_icon = SDL_min(availw, availh);                       /* icon only */
 }
 
+/* Floating and fullscreen windows are drawn as see-through frames with a name
+ * plate at the top. The plate is the only part that takes the mouse, so the
+ * windows underneath stay clickable. */
+static bool card_is_overlay(const Win *w) { return w->floating || w->fullscreen; }
+
+static bool overlay_plate(const Win *w, SDL_FRect r, SDL_FRect *plate, float *icon_w)
+{
+    if (!w->label.t) return false;
+
+    float ip = 6.0f * SC;
+    float ih = SDL_min(22.0f * SC, (float)C.icon_px * SC * 0.6f);
+    bool  has_icon = C.icons && w->icon && w->lay_icon >= 10.0f * SC && ih >= 10.0f * SC;
+    float iw = has_icon ? ih + 5.0f * SC : 0.0f;
+    float pw = iw + (float)w->label.w + 2.0f * ip;
+    float ph = SDL_max((float)w->label.h, ih) + ip;
+
+    if (pw > r.w - 4.0f * SC) {                 /* tight: drop the icon first */
+        iw = 0.0f;
+        pw = (float)w->label.w + 2.0f * ip;
+        ph = (float)w->label.h + ip;
+    }
+    if (pw > r.w - 4.0f * SC || ph > r.h * 0.6f) return false;
+
+    *plate = (SDL_FRect){ r.x + (r.w - pw) * 0.5f, r.y + 7.0f * SC, pw, ph };
+    if (icon_w) *icon_w = iw;
+    return true;
+}
+
 static float card_text_width(const Win *w)
 {
     float pad = 7.0f * SC;
@@ -2091,6 +2120,8 @@ static void build_texts(void)
         Win *w = &WINS[i];
         tex_free(&w->label);
         tex_free(&w->subtitle);
+        w->hit = w->card;                    /* the whole card, unless a plate
+                                              * takes over below */
 
         compute_card_layout(w);
         int maxw = (int)card_text_width(w);
@@ -2100,6 +2131,10 @@ static void build_texts(void)
         w->label = text_make_fit(F_LABEL, name, maxw);
         if (w->lay_sub && strcmp(name, w->title) != 0)
             w->subtitle = text_make_fit(F_TITLE, w->title, maxw);
+
+        SDL_FRect plate;
+        if (card_is_overlay(w) && overlay_plate(w, w->card, &plate, NULL))
+            w->hit = plate;
     }
 }
 
@@ -2425,7 +2460,7 @@ static int hit_test(float mx, float my, int *out_ws)
         *out_ws = i;
         for (int j = ws->count - 1; j >= 0; --j) {      /* floats are last = on top */
             Win *w = &WINS[ws->first + j];
-            SDL_FRect r = w->card;
+            SDL_FRect r = (w->hit.w > 0.0f) ? w->hit : w->card;
             if (mx >= r.x && mx < r.x + r.w && my >= r.y && my < r.y + r.h)
                 return ws->first + j;
         }
@@ -2764,8 +2799,8 @@ static void rebuild_chrome(void)
         if (same_band && T_HEADER.t) avail -= T_HEADER.w + (int)(C.margin * SC);
 
         T_HINTS = text_make_fit(F_HINT,
-            "\xe2\x86\xb5 focus    drag to move    tab workspace    0-9 move    "
-            "ctrl+0-9 go to    space mark    x close    / filter    esc quit", avail);
+            "\xe2\x86\xb5 focus    drag to move    tab workspace    0-9 go to    "
+            "ctrl+0-9 move    space mark    x close    / filter    esc quit", avail);
     }
 
     if (filtering) {
@@ -2888,37 +2923,27 @@ static void draw_card(Win *w, bool tile_selected, bool is_hovered)
     bool has_icon = C.icons && w->icon && w->lay_icon >= 10.0f * SC;
     if (has_icon) SDL_SetTextureAlphaModFloat(w->icon, a);
 
-    if (as_overlay && w->label.t) {
+    SDL_FRect plate;
+    float plate_iw = 0.0f;
+    if (as_overlay && overlay_plate(w, r, &plate, &plate_iw)) {
         /* Centre would land on top of the cards showing through, so the name
-         * rides in a small plate at the top edge instead. */
+         * rides in a small plate at the top edge instead. That plate is also
+         * the only part of this window the mouse can grab. */
         float ip = 6.0f * SC;
-        float ih = SDL_min(22.0f * SC, (float)C.icon_px * SC * 0.6f);
-        float iw = (has_icon && ih >= 10.0f * SC) ? ih + 5.0f * SC : 0.0f;
-        float pw = iw + (float)w->label.w + 2.0f * ip;
-        float phh = SDL_max((float)w->label.h, ih) + ip;
-
-        if (pw > r.w - 4.0f * SC) {            /* tight: drop the icon first */
-            iw = 0.0f;
-            pw = (float)w->label.w + 2.0f * ip;
-            phh = (float)w->label.h + ip;
-        }
-
-        if (pw <= r.w - 4.0f * SC && phh <= r.h * 0.6f) {
-            SDL_FRect plate = { r.x + (r.w - pw) * 0.5f, r.y + 7.0f * SC, pw, phh };
-            fill_round_rect(plate, phh * 0.32f, with_alpha(mix(C.card, C.bg, 0.25f), 0.94f));
-            stroke_round_rect(plate, phh * 0.32f, SDL_max(1.0f, 1.4f * SC),
-                              with_alpha(C.accent, 0.55f));
-
-            float tx = plate.x + ip;
-            if (iw > 0.0f && has_icon) {
-                draw_icon(w->icon, tx + ih * 0.5f, plate.y + (phh - ih) * 0.5f, ih);
-                tx += iw;
-            }
-            tex_draw(w->label, tx, plate.y + (phh - (float)w->label.h) * 0.5f,
-                     with_alpha(C.text, a));
-            if (has_icon) SDL_SetTextureAlphaModFloat(w->icon, 1.0f);
-            return;
+        fill_round_rect(plate, plate.h * 0.32f, with_alpha(mix(C.card, C.bg, 0.25f), 0.94f));
+        stroke_round_rect(plate, plate.h * 0.32f, SDL_max(1.0f, 1.4f * SC),
+                          is_hovered ? C.accent : with_alpha(C.accent, 0.55f));
+
+        float tx = plate.x + ip;
+        if (plate_iw > 0.0f && has_icon) {
+            float ih = plate_iw - 5.0f * SC;
+            draw_icon(w->icon, tx + ih * 0.5f, plate.y + (plate.h - ih) * 0.5f, ih);
+            tx += plate_iw;
         }
+        tex_draw(w->label, tx, plate.y + (plate.h - (float)w->label.h) * 0.5f,
+                 with_alpha(C.text, a));
+        if (has_icon) SDL_SetTextureAlphaModFloat(w->icon, 1.0f);
+        return;
     }
 
     float lab_h = w->label.t    ? (float)w->label.h    : 0.0f;
@@ -3471,10 +3496,11 @@ static void usage(void)
 "  space / right click toggle the mark on a window\n"
 "  a                   mark or unmark every window of the workspace\n"
 "  c                   clear all marks\n"
-"  0-9                 move marked or selected windows to that workspace;\n"
+"  0-9                 switch to that workspace and leave\n"
+"  ctrl+0-9            move marked or selected windows to that workspace;\n"
 "                      with a whole workspace selected it moves everything\n"
 "                      there, keeping the container layout intact\n"
-"  ctrl+0-9            switch to that workspace\n"
+
 "  x / delete          close marked (or selected) windows\n"
 "  /                   filter by app id, title or workspace\n"
 "  r                   reload the tree\n"
@@ -3569,8 +3595,11 @@ static void handle_key(const SDL_KeyboardEvent *k)
      * German keyboard shift+7 is "/" — and that has to reach text input. */
     int digit = digit_from_scancode(k->scancode);
     if (digit >= 0 && !shift && !(k->mod & SDL_KMOD_ALT)) {
-        if (k->mod & SDL_KMOD_CTRL) goto_workspace_number(digit);
-        else if (was_active)        act_move_to_workspace(digit);
+        if (k->mod & SDL_KMOD_CTRL) {
+            if (was_active) act_move_to_workspace(digit);   /* ctrl: take it there */
+        } else {
+            goto_workspace_number(digit);                   /* bare: go there */
+        }
         return;
     }
     if (digit >= 0) return;                    /* shifted digit: let text input have it */