From fd8382a82ca9bb51265e0338a8b8cc977bfe6f45 Mon Sep 17 00:00:00 2001 From: Jon H <> Date: Wed, 18 Mar 2026 06:17:07 -0700 Subject: [PATCH 1/5] Adds stack bounded stack navigation functions --- src/config/parse_config.h | 10 ++++++-- src/dispatch/bind_declare.h | 2 ++ src/dispatch/bind_define.h | 48 +++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index fda401d9..d2d7cf83 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -938,7 +938,10 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, if (strcmp(func_name, "focusstack") == 0) { func = focusstack; (*arg).i = parse_circle_direction(arg_value); - } else if (strcmp(func_name, "focusdir") == 0) { + } else if (strcmp(func_name, "focusstack_bounded") == 0) { + func = focusstack_bounded; + (*arg).i = parse_circle_direction(arg_value); + } else if (strcmp(func_name, "focusdir") == 0) { func = focusdir; (*arg).i = parse_direction(arg_value); } else if (strcmp(func_name, "incnmaster") == 0) { @@ -955,7 +958,10 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, } else if (strcmp(func_name, "exchange_stack_client") == 0) { func = exchange_stack_client; (*arg).i = parse_circle_direction(arg_value); - } else if (strcmp(func_name, "toggleglobal") == 0) { + } else if (strcmp(func_name, "exchange_stack_client_bounded") == 0) { + func = exchange_stack_client_bounded; + (*arg).i = parse_circle_direction(arg_value); + } else if (strcmp(func_name, "toggleglobal") == 0) { func = toggleglobal; } else if (strcmp(func_name, "toggleoverview") == 0) { func = toggleoverview; diff --git a/src/dispatch/bind_declare.h b/src/dispatch/bind_declare.h index dbeebd33..fcfb9520 100644 --- a/src/dispatch/bind_declare.h +++ b/src/dispatch/bind_declare.h @@ -32,11 +32,13 @@ int32_t quit(const Arg *arg); int32_t moveresize(const Arg *arg); int32_t exchange_client(const Arg *arg); int32_t exchange_stack_client(const Arg *arg); +int32_t exchange_stack_client_bounded(const Arg *arg); int32_t killclient(const Arg *arg); int32_t toggleglobal(const Arg *arg); int32_t incnmaster(const Arg *arg); int32_t focusmon(const Arg *arg); int32_t focusstack(const Arg *arg); +int32_t focusstack_bounded(const Arg *arg); int32_t chvt(const Arg *arg); int32_t reload_config(const Arg *arg); int32_t smartmovewin(const Arg *arg); diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 68309356..6c9059dc 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -134,6 +134,27 @@ int32_t exchange_stack_client(const Arg *arg) { return 0; } +int32_t exchange_stack_client_bounded(const Arg *arg) { + if (!selmon) + return 0; + + Client *c = selmon->sel; + Client *tc = NULL; + if (!c || c->isfloating || c->isfullscreen || c->ismaximizescreen) + return 0; + /* again, lazy we just search once more if we found master */ + if (arg->i == NEXT) { + tc = get_next_stack_client(c, false); + if (tc && tc->ismaster) tc = get_next_stack_client(tc, false); + } else { + tc = get_next_stack_client(c, true); + if (tc && tc->ismaster) tc = get_next_stack_client(tc, true); + } + if (tc) + exchange_two_client(c, tc); + return 0; +} + int32_t focusdir(const Arg *arg) { Client *c = NULL; c = direction_select(arg); @@ -263,6 +284,33 @@ int32_t focusstack(const Arg *arg) { return 0; } +int32_t focusstack_bounded(const Arg *arg) { + /* Focus the next or previous client (in tiling order) on selmon */ + /* but skipping past master */ + Client *sel = focustop(selmon); + Client *tc = NULL; + + if (!sel) + return 0; + /* lazy fix we just search twice, don't make new deeper functions */ + if (arg->i == NEXT) { + tc = get_next_stack_client(sel, false); + if (tc && tc->ismaster) tc = get_next_stack_client(tc, false); + } else { + tc = get_next_stack_client(sel, true); + if (tc && tc->ismaster) tc = get_next_stack_client(tc, true); + } + /* If only one client is visible on selmon, then c == sel */ + + if (!tc) + return 0; + + focusclient(tc, 1); + if (config.warpcursor) + warp_cursor(tc); + return 0; +} + int32_t incnmaster(const Arg *arg) { if (!arg || !selmon) return 0; From e9ad5943574c986ad145cb605d886cf4cb9e9a40 Mon Sep 17 00:00:00 2001 From: Jon H <> Date: Thu, 19 Mar 2026 11:16:32 -0700 Subject: [PATCH 2/5] Add "ismaster" ipc protocol extension --- mmsg/mmsg.c | 24 ++++++++++++++++++++++-- protocols/dwl-ipc-unstable-v2.xml | 9 ++++++++- src/ext-protocol/dwl-ipc.h | 5 +++++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/mmsg/mmsg.c b/mmsg/mmsg.c index 4e0e1d8c..2ad9d074 100644 --- a/mmsg/mmsg.c +++ b/mmsg/mmsg.c @@ -35,6 +35,7 @@ static int32_t cflag; static int32_t vflag; static int32_t mflag; static int32_t fflag; +static int32_t Mflag; static int32_t qflag; static int32_t dflag; static int32_t xflag; @@ -310,6 +311,17 @@ static void dwl_ipc_output_floating(void *data, printf("floating %u\n", is_floating); } +static void dwl_ipc_output_master(void *data, + struct zdwl_ipc_output_v2 *dwl_ipc_output, + uint32_t is_master) { + if (!Mflag) + return; + char *output_name = data; + if (output_name) + printf("%s ", output_name); + printf("master: %u\n", is_master); +} + static void dwl_ipc_output_frame(void *data, struct zdwl_ipc_output_v2 *dwl_ipc_output) { if (mode & SET) { @@ -419,6 +431,7 @@ static const struct zdwl_ipc_output_v2_listener dwl_ipc_output_listener = { .layout_symbol = dwl_ipc_output_layout_symbol, .fullscreen = dwl_ipc_output_fullscreen, .floating = dwl_ipc_output_floating, + .master = dwl_ipc_output_master, .x = dwl_ipc_output_x, .y = dwl_ipc_output_y, .width = dwl_ipc_output_width, @@ -529,6 +542,7 @@ static void usage(void) { "\t-v Get visibility of statusbar\n" "\t-m Get fullscreen status\n" "\t-f Get floating status\n" + "\t-M Get master status\n" "\t-x Get focused client geometry\n" "\t-e Get name of last focused layer\n" "\t-k Get current keyboard layout\n" @@ -758,6 +772,12 @@ int32_t main(int32_t argc, char *argv[]) { usage(); mode |= GET; break; + case 'M': + Mflag = 1; + if (mode == SET) + usage(); + mode |= GET; + break; case 'x': xflag = 1; if (mode == SET) @@ -797,9 +817,9 @@ int32_t main(int32_t argc, char *argv[]) { usage(); if (mode & GET && !output_name && !(oflag || tflag || lflag || Oflag || Tflag || Lflag || cflag || - vflag || mflag || fflag || xflag || eflag || kflag || bflag || + vflag || mflag || fflag || Mflag || xflag || eflag || kflag || bflag || Aflag || dflag)) - oflag = tflag = lflag = cflag = vflag = mflag = fflag = xflag = eflag = + oflag = tflag = lflag = cflag = vflag = mflag = fflag = Mflag = xflag = eflag = kflag = bflag = Aflag = 1; display = wl_display_connect(NULL); diff --git a/protocols/dwl-ipc-unstable-v2.xml b/protocols/dwl-ipc-unstable-v2.xml index 5852107e..608981f7 100644 --- a/protocols/dwl-ipc-unstable-v2.xml +++ b/protocols/dwl-ipc-unstable-v2.xml @@ -190,7 +190,14 @@ I would probably just submit raphi's patchset but I don't think that would be po Indicates if the selected client on this output is floating. - + + + + + Indicates if the selected client on this output is master. + + + diff --git a/src/ext-protocol/dwl-ipc.h b/src/ext-protocol/dwl-ipc.h index ab0bdb8d..22cd1fda 100644 --- a/src/ext-protocol/dwl-ipc.h +++ b/src/ext-protocol/dwl-ipc.h @@ -169,6 +169,11 @@ void dwl_ipc_output_printstatus_to(DwlIpcOutput *ipc_output) { zdwl_ipc_output_v2_send_floating(ipc_output->resource, focused ? focused->isfloating : 0); } + if (wl_resource_get_version(ipc_output->resource) >= + ZDWL_IPC_OUTPUT_V2_MASTER_SINCE_VERSION) { + zdwl_ipc_output_v2_send_master(ipc_output->resource, + focused ? focused->ismaster : 0); + } if (wl_resource_get_version(ipc_output->resource) >= ZDWL_IPC_OUTPUT_V2_X_SINCE_VERSION) { zdwl_ipc_output_v2_send_x(ipc_output->resource, From a66b248ea998b466ff8246529127be4655320ed7 Mon Sep 17 00:00:00 2001 From: Jon H <> Date: Thu, 19 Mar 2026 11:45:11 -0700 Subject: [PATCH 3/5] Loop past master in bounded --- src/dispatch/bind_define.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dispatch/bind_define.h b/src/dispatch/bind_define.h index 6c9059dc..e051da0c 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -145,10 +145,10 @@ int32_t exchange_stack_client_bounded(const Arg *arg) { /* again, lazy we just search once more if we found master */ if (arg->i == NEXT) { tc = get_next_stack_client(c, false); - if (tc && tc->ismaster) tc = get_next_stack_client(tc, false); + while (tc && tc != c && tc->ismaster) tc = get_next_stack_client(tc, false); } else { tc = get_next_stack_client(c, true); - if (tc && tc->ismaster) tc = get_next_stack_client(tc, true); + while (tc && tc != c && tc->ismaster) tc = get_next_stack_client(tc, true); } if (tc) exchange_two_client(c, tc); @@ -295,10 +295,10 @@ int32_t focusstack_bounded(const Arg *arg) { /* lazy fix we just search twice, don't make new deeper functions */ if (arg->i == NEXT) { tc = get_next_stack_client(sel, false); - if (tc && tc->ismaster) tc = get_next_stack_client(tc, false); + while (tc && tc != sel && tc->ismaster) tc = get_next_stack_client(tc, false); } else { tc = get_next_stack_client(sel, true); - if (tc && tc->ismaster) tc = get_next_stack_client(tc, true); + while (tc && tc != sel && tc->ismaster) tc = get_next_stack_client(tc, true); } /* If only one client is visible on selmon, then c == sel */ From f3336eae6833eb2cbd82222e115908a700283c1b Mon Sep 17 00:00:00 2001 From: Jon H <> Date: Thu, 19 Mar 2026 15:23:19 -0700 Subject: [PATCH 4/5] Implements ipc protocol for retrieving current count of masters --- mmsg/mmsg.c | 25 ++++++++++++++++++++++--- protocols/dwl-ipc-unstable-v2.xml | 7 +++++++ src/ext-protocol/dwl-ipc.h | 5 +++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/mmsg/mmsg.c b/mmsg/mmsg.c index 2ad9d074..b20e45c4 100644 --- a/mmsg/mmsg.c +++ b/mmsg/mmsg.c @@ -36,6 +36,7 @@ static int32_t vflag; static int32_t mflag; static int32_t fflag; static int32_t Mflag; +static int32_t nflag; static int32_t qflag; static int32_t dflag; static int32_t xflag; @@ -178,6 +179,16 @@ static void dwl_ipc_output_layout_symbol( printf("layout %s\n", layout); } +static void dwl_ipc_output_nmasters( + void *data, struct zdwl_ipc_output_v2 *dwl_ipc_output, uint32_t count) { + if (!(nflag && mode & GET)) + return; + char *output_name = data; + if (output_name) + printf("%s ", output_name); + printf("nmasters %d\n", count); +} + static void dwl_ipc_output_title(void *data, struct zdwl_ipc_output_v2 *dwl_ipc_output, const char *title) { @@ -432,6 +443,7 @@ static const struct zdwl_ipc_output_v2_listener dwl_ipc_output_listener = { .fullscreen = dwl_ipc_output_fullscreen, .floating = dwl_ipc_output_floating, .master = dwl_ipc_output_master, + .nmasters = dwl_ipc_output_nmasters, .x = dwl_ipc_output_x, .y = dwl_ipc_output_y, .width = dwl_ipc_output_width, @@ -543,6 +555,7 @@ static void usage(void) { "\t-m Get fullscreen status\n" "\t-f Get floating status\n" "\t-M Get master status\n" + "\t-n Get current masters count\n" "\t-x Get focused client geometry\n" "\t-e Get name of last focused layer\n" "\t-k Get current keyboard layout\n" @@ -778,6 +791,12 @@ int32_t main(int32_t argc, char *argv[]) { usage(); mode |= GET; break; + case 'n': + nflag = 1; + if (mode == SET) + usage(); + mode |= GET; + break; case 'x': xflag = 1; if (mode == SET) @@ -816,11 +835,11 @@ int32_t main(int32_t argc, char *argv[]) { if (mode == NONE) usage(); if (mode & GET && !output_name && - !(oflag || tflag || lflag || Oflag || Tflag || Lflag || cflag || + !(oflag || tflag || lflag || Oflag || nflag || Tflag || Lflag || cflag || vflag || mflag || fflag || Mflag || xflag || eflag || kflag || bflag || Aflag || dflag)) - oflag = tflag = lflag = cflag = vflag = mflag = fflag = Mflag = xflag = eflag = - kflag = bflag = Aflag = 1; + oflag = tflag = lflag = cflag = vflag = mflag = + nflag = fflag = Mflag = xflag = eflag = kflag = bflag = Aflag = 1; display = wl_display_connect(NULL); if (!display) diff --git a/protocols/dwl-ipc-unstable-v2.xml b/protocols/dwl-ipc-unstable-v2.xml index 608981f7..41b88153 100644 --- a/protocols/dwl-ipc-unstable-v2.xml +++ b/protocols/dwl-ipc-unstable-v2.xml @@ -136,6 +136,13 @@ I would probably just submit raphi's patchset but I don't think that would be po + + + + Indicates the count of masters has changed. + + + diff --git a/src/ext-protocol/dwl-ipc.h b/src/ext-protocol/dwl-ipc.h index 22cd1fda..4f7bfa26 100644 --- a/src/ext-protocol/dwl-ipc.h +++ b/src/ext-protocol/dwl-ipc.h @@ -114,6 +114,7 @@ void dwl_ipc_output_printstatus_to(DwlIpcOutput *ipc_output) { xkb_layout_index_t current; int32_t tagmask, state, numclients, focused_client, tag; const char *title, *appid, *symbol; + uint32_t nmasters; char kb_layout[32]; focused = focustop(monitor); zdwl_ipc_output_v2_send_active(ipc_output->resource, monitor == selmon); @@ -143,8 +144,10 @@ void dwl_ipc_output_printstatus_to(DwlIpcOutput *ipc_output) { if (monitor->isoverview) { symbol = overviewlayout.symbol; + nmasters = 0; } else { symbol = monitor->pertag->ltidxs[monitor->pertag->curtag]->symbol; + nmasters = monitor->pertag->nmasters[monitor->pertag->curtag]; } keyboard = &kb_group->wlr_group->keyboard; @@ -159,6 +162,8 @@ void dwl_ipc_output_printstatus_to(DwlIpcOutput *ipc_output) { zdwl_ipc_output_v2_send_title(ipc_output->resource, title ? title : broken); zdwl_ipc_output_v2_send_appid(ipc_output->resource, appid ? appid : broken); zdwl_ipc_output_v2_send_layout_symbol(ipc_output->resource, symbol); + zdwl_ipc_output_v2_send_nmasters(ipc_output->resource, nmasters); + if (wl_resource_get_version(ipc_output->resource) >= ZDWL_IPC_OUTPUT_V2_FULLSCREEN_SINCE_VERSION) { zdwl_ipc_output_v2_send_fullscreen(ipc_output->resource, From df1d62264973d48c0094703760f8df7ec4402f0f Mon Sep 17 00:00:00 2001 From: Jon H <> Date: Fri, 20 Mar 2026 13:29:22 -0700 Subject: [PATCH 5/5] Adds killclient_byappid to internal commands --- src/config/parse_config.h | 5 ++++- src/dispatch/bind_declare.h | 1 + src/dispatch/bind_define.h | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/config/parse_config.h b/src/config/parse_config.h index d2d7cf83..4c9ebc27 100644 --- a/src/config/parse_config.h +++ b/src/config/parse_config.h @@ -989,7 +989,10 @@ FuncType parse_func_name(char *func_name, Arg *arg, char *arg_value, (*arg).i = atoi(arg_value); } else if (strcmp(func_name, "killclient") == 0) { func = killclient; - } else if (strcmp(func_name, "centerwin") == 0) { + } else if (strcmp(func_name, "killclient_byappid") == 0) { + func = killclient_byappid; + (*arg).v = strdup(arg_value); + } else if (strcmp(func_name, "centerwin") == 0) { func = centerwin; } else if (strcmp(func_name, "focuslast") == 0) { func = focuslast; diff --git a/src/dispatch/bind_declare.h b/src/dispatch/bind_declare.h index fcfb9520..d0b3e538 100644 --- a/src/dispatch/bind_declare.h +++ b/src/dispatch/bind_declare.h @@ -34,6 +34,7 @@ int32_t exchange_client(const Arg *arg); int32_t exchange_stack_client(const Arg *arg); int32_t exchange_stack_client_bounded(const Arg *arg); int32_t killclient(const Arg *arg); +int32_t killclient_byappid(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 e051da0c..e1f12fc7 100644 --- a/src/dispatch/bind_define.h +++ b/src/dispatch/bind_define.h @@ -409,6 +409,21 @@ int32_t killclient(const Arg *arg) { return 0; } +int32_t killclient_byappid(const Arg *arg) { + Client *c = NULL; + /* No Arg? No Kill. */ + if (!arg || !arg->v) + return 0; + /* for some reason its warning me about this indent? */ + + wl_list_for_each(c, &clients, link) { + if (strcmp((*arg).v, client_get_appid(c)) == 0) { + pending_kill_client(c); + } + } + return 0; +} + int32_t moveresize(const Arg *arg) { const char *cursors[] = {"nw-resize", "ne-resize", "sw-resize", "se-resize"};