From e4c74de7dffe6f27933a93d9b1a37fd9d19da2f8 Mon Sep 17 00:00:00 2001 From: ShadowProgr <14125835+ShadowProgr@users.noreply.github.com> Date: Sun, 31 May 2026 15:48:36 +0700 Subject: [PATCH 1/2] feat: add smartkillclient dispatcher Add a smartkillclient bind function that removes the focused client from the currently viewed tag(s) when it lives on more than one tag, and only falls back to closing it (killclient behaviour) when it is on a single tag, or when removing the viewed tag(s) would leave it on none. --- docs/bindings/keys.md | 1 + src/config/parse_config.h | 2 ++ src/dispatch/bind_declare.h | 1 + src/dispatch/bind_define.h | 20 ++++++++++++++++++++ 4 files changed, 24 insertions(+) diff --git a/docs/bindings/keys.md b/docs/bindings/keys.md index 6c8a8c58..d1eaa67e 100644 --- a/docs/bindings/keys.md +++ b/docs/bindings/keys.md @@ -89,6 +89,7 @@ bindr=Super,Super_L,spawn,rofi -show run | Command | Param | Description | | :--- | :--- | :--- | | `killclient` | `force` | Close the focused window. If `force` is specified, sends `SIGKILL`. | +| `smartkillclient` | - | Remove the focused window from the current tag(s) if it is on multiple tags; otherwise close it. | | `togglefloating` | - | Toggle floating state. | | `toggle_all_floating` | - | Toggle all visible clients floating state. | | `togglefullscreen` | - | Toggle fullscreen. | diff --git a/src/config/parse_config.h b/src/config/parse_config.h index 61af421b..e31a040d 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -1061,6 +1061,8 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, } else if (strcmp(func_name, "killclient") == 0) { func = killclient; (*arg).i = parse_force(arg_value); + } else if (strcmp(func_name, "smartkillclient") == 0) { + func = smartkillclient; } else if (strcmp(func_name, "centerwin") == 0) { func = centerwin; } else if (strcmp(func_name, "focuslast") == 0) { diff --git a/src/dispatch/bind_declare.h b/src/dispatch/bind_declare.h index 6960b001..1de2d78b 100644 --- a/src/dispatch/bind_declare.h +++ b/src/dispatch/bind_declare.h @@ -34,6 +34,7 @@ int32_t moveresize(const Arg *arg); int32_t exchange_client(const Arg *arg); int32_t exchange_stack_client(const Arg *arg); int32_t killclient(const Arg *arg); +int32_t smartkillclient(const Arg *arg); int32_t toggleglobal(const Arg *arg); int32_t incnmaster(const Arg *arg); int32_t focusmon(const Arg *arg); diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index e7cfd020..3fd2d46a 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -360,6 +360,26 @@ int32_t killclient(const Arg *arg) { return 0; } +int32_t smartkillclient(const Arg *arg) { + Client *c = arg->tc ? arg->tc : (selmon ? selmon->sel : NULL); + if (!c) + return 0; + + uint32_t newtags = c->tags & ~(c->mon->tagset[c->mon->seltags] & TAGMASK); + if (__builtin_popcount(c->tags & TAGMASK) <= 1 || !newtags) { + pending_kill_client(c); + return 0; + } + + // Remove the client from the currently viewed tag(s) on its own monitor + // (the one that changed) while keeping focus on the user's active monitor. + c->tags = newtags; + focusclient(focustop(selmon), 1); + arrange(c->mon, false, false); + printstatus(IPC_WATCH_ARRANGGE); + return 0; +} + int32_t moveresize(const Arg *arg) { const char *cursors[] = {"nw-resize", "ne-resize", "sw-resize", "se-resize"}; From e903cd99f4966a3b7039a400826b198828328a97 Mon Sep 17 00:00:00 2001 From: ShadowProgr <14125835+ShadowProgr@users.noreply.github.com> Date: Mon, 1 Jun 2026 08:52:34 +0700 Subject: [PATCH 2/2] docs: clarify smartkillclient kill fallback Add an inline comment on the fallback branch noting that the client is closed when it is single-tag, or when removing the viewed tag(s) would leave it on none. --- src/dispatch/bind_define.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 3fd2d46a..34731b9f 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -366,6 +366,8 @@ int32_t smartkillclient(const Arg *arg) { return 0; uint32_t newtags = c->tags & ~(c->mon->tagset[c->mon->seltags] & TAGMASK); + // Kill the client if it's single-tag, or if removing the tag(s) would leave + // it on none if (__builtin_popcount(c->tags & TAGMASK) <= 1 || !newtags) { pending_kill_client(c); return 0;