From 49b0da41f1f28a60cf216f5bffecc0ce1ea1ec9c Mon Sep 17 00:00:00 2001 From: nate zhou Date: Mon, 23 Mar 2026 00:50:54 +0800 Subject: [PATCH] shifttag with filtering occupied/unoccupied tags support --- config.def.h | 8 +++++ shifttag.c | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 shifttag.c diff --git a/config.def.h b/config.def.h index 8a6eda0..2377fa5 100644 --- a/config.def.h +++ b/config.def.h @@ -118,6 +118,8 @@ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TA static const char *termcmd[] = { "foot", NULL }; static const char *menucmd[] = { "wmenu-run", NULL }; +#include "shifttag.c" + static const Key keys[] = { /* Note that Shift changes certain key codes: 2 -> at, etc. */ /* modifier key function argument */ @@ -131,6 +133,12 @@ static const Key keys[] = { { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, { MODKEY, XKB_KEY_Return, zoom, {0} }, { MODKEY, XKB_KEY_Tab, view, {0} }, + { MODKEY, XKB_KEY_apostrophe, shifttag_occupied, { .i = 1 } }, + { MODKEY, XKB_KEY_semicolon, shifttag_occupied, { .i = -1 } }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_quotedbl, shifttag, { .i = 1 } }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_colon, shifttag, { .i = -1 } }, + { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_apostrophe, shifttag_unoccupied, { .i = 1 } }, + { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_semicolon, shifttag_unoccupied, { .i = -1 } }, { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_c, killclient, {0} }, { MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} }, { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, diff --git a/shifttag.c b/shifttag.c new file mode 100644 index 0000000..5aeee00 --- /dev/null +++ b/shifttag.c @@ -0,0 +1,99 @@ +// "arg->i" stores the number of tags to shift right (positive value) +// or left (negative value) + +static uint32_t +get_occupied_tags(Monitor *m) +{ + uint32_t occupied = 0; + Client *c; + wl_list_for_each(c, &clients, link) { + if (c->mon == m) + occupied |= c->tags; + } + return occupied & TAGMASK; +} + +static uint32_t +find_next_tag(uint32_t current_tag, int direction, bool skip_unoccupied, bool skip_occupied) +{ + uint32_t occupied = get_occupied_tags(selmon); + uint32_t start = current_tag; + uint32_t test_tag = current_tag; + uint32_t new_tag = current_tag; + int count = 0; + + if (!skip_unoccupied && !skip_occupied) { + if (direction > 0) { + new_tag = (current_tag << 1) | (current_tag >> (TAGCOUNT - 1)); + } else { + new_tag = (current_tag >> 1) | (current_tag << (TAGCOUNT - 1)); + } + return new_tag & TAGMASK; + } + + do { + if (direction > 0) { + if (test_tag << 1 && (test_tag << 1) <= TAGMASK) + test_tag = test_tag << 1; + else + test_tag = 1; + } else { + if (test_tag >> 1) + test_tag = test_tag >> 1; + else + test_tag = 1 << (TAGCOUNT - 1); + } + + int is_occupied = (occupied & test_tag) != 0; + int should_select = (skip_unoccupied && is_occupied) || + (skip_occupied && !is_occupied); + + if (should_select) { + new_tag = test_tag; + break; + } + count++; + } while (test_tag != start && count < TAGCOUNT); + + return new_tag; +} + +static void +shifttag_with_filter(const Arg *arg, bool skip_unoccupied, bool skip_occupied) +{ + Arg a; + if (!selmon) + return; + + uint32_t occupied = get_occupied_tags(selmon); + if (skip_unoccupied && !skip_occupied && occupied == 0) + return; + if (skip_occupied && !skip_unoccupied && (occupied == TAGMASK)) + return; + + uint32_t curseltags = selmon->tagset[selmon->seltags]; + uint32_t nextseltags = find_next_tag(curseltags, arg->i, skip_unoccupied, skip_occupied); + + if (nextseltags != curseltags) { + a.i = nextseltags; + view(&a); + } +} + +void +shifttag(const Arg *arg) +{ + shifttag_with_filter(arg, false, false); +} + +void +shifttag_occupied(const Arg *arg) +{ + shifttag_with_filter(arg, true, false); +} + +void +shifttag_unoccupied(const Arg *arg) +{ + shifttag_with_filter(arg, false, true); +} -- 2.53.0