diff --git a/patches/shifttag/README.md b/patches/shifttag/README.md new file mode 100644 index 0000000..ed7e1f9 --- /dev/null +++ b/patches/shifttag/README.md @@ -0,0 +1,22 @@ +### Description + +Shift to next/previous tag, with skipping occupied/unoccupied variants. + +This patch is an alternative to +[shiftview](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/shiftview) with more controls: + +| function | behavior | +| :--- | :---| +| shifttag | shift to next/previous tags | +| shifttag_occupied | skipping unoccupied tags | +| shifttag_unoccupied | skipping occupied tags | + +`shifttag-bar` is a variant to be applied on top of the +[bar](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/bar/) patch. + +### Download +- [shifttag-v0.8](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/shifttag/shifttag.patch) +- [shifttag-bar-v0.8](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/shifttag/shifttag-bar.patch) + +### Authors +- [unixchad](https://codeberg.org/unixchad/) diff --git a/patches/shifttag/shifttag-bar.patch b/patches/shifttag/shifttag-bar.patch new file mode 100644 index 0000000..863b8e6 --- /dev/null +++ b/patches/shifttag/shifttag-bar.patch @@ -0,0 +1,146 @@ +From 6d3a0506f45243cf11f0f7ba06048f453576567c Mon Sep 17 00:00:00 2001 +From: nate zhou +Date: Mon, 23 Mar 2026 01:16:06 +0800 +Subject: [PATCH] shifttag with filtering occupied/unoccupied tags support for + bar patch + +--- + 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 7da50d2..9fbcf2a 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -124,6 +124,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 */ +@@ -138,6 +140,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..2615f4c +--- /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 >> (LENGTH(tags) - 1)); ++ } else { ++ new_tag = (current_tag >> 1) | (current_tag << (LENGTH(tags) - 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 << (LENGTH(tags) - 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 < LENGTH(tags)); ++ ++ 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 + diff --git a/patches/shifttag/shifttag.patch b/patches/shifttag/shifttag.patch new file mode 100644 index 0000000..82ccf15 --- /dev/null +++ b/patches/shifttag/shifttag.patch @@ -0,0 +1,145 @@ +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 +