From 6d4b7d3a57feeb09b603c6e8122695a108742849 Mon Sep 17 00:00:00 2001 From: LaKato Date: Wed, 18 Mar 2026 20:51:39 -0400 Subject: [PATCH 1/5] togglekblayoutandoptions: update for dwl v0.8 No changes, just a rebase to explicitly advertise support. --- patches/togglekblayoutandoptions/README.md | 5 +- ...tch => togglekblayoutandoptions-0.7.patch} | 0 .../togglekblayoutandoptions-0.8.patch | 160 ++++++++++++++++++ 3 files changed, 164 insertions(+), 1 deletion(-) rename patches/togglekblayoutandoptions/{togglekblayoutandoptions.patch => togglekblayoutandoptions-0.7.patch} (100%) create mode 100644 patches/togglekblayoutandoptions/togglekblayoutandoptions-0.8.patch diff --git a/patches/togglekblayoutandoptions/README.md b/patches/togglekblayoutandoptions/README.md index 883dd6d..05bb078 100644 --- a/patches/togglekblayoutandoptions/README.md +++ b/patches/togglekblayoutandoptions/README.md @@ -1,8 +1,11 @@ ### Description Switch between multiple keyboard layouts, variants, and options at runtime. +Supports both assigning a specific keyboard to a shortcut and cycling through +keyboards. ### Download -- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/togglekblayoutandoptions/togglekblayoutandoptions.patch) +- [0.8](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/togglekblayoutandoptions/togglekblayoutandoptions-0.8.patch) +- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/togglekblayoutandoptions/togglekblayoutandoptions-0.7.patch) ### Authors - [LaKato](https://codeberg.org/LaKato) diff --git a/patches/togglekblayoutandoptions/togglekblayoutandoptions.patch b/patches/togglekblayoutandoptions/togglekblayoutandoptions-0.7.patch similarity index 100% rename from patches/togglekblayoutandoptions/togglekblayoutandoptions.patch rename to patches/togglekblayoutandoptions/togglekblayoutandoptions-0.7.patch diff --git a/patches/togglekblayoutandoptions/togglekblayoutandoptions-0.8.patch b/patches/togglekblayoutandoptions/togglekblayoutandoptions-0.8.patch new file mode 100644 index 0000000..f68f49c --- /dev/null +++ b/patches/togglekblayoutandoptions/togglekblayoutandoptions-0.8.patch @@ -0,0 +1,160 @@ +From 6c028b4cacd21f9e6a195ce7b0e1987868fb3e62 Mon Sep 17 00:00:00 2001 +From: LaKato +Date: Wed, 18 Mar 2026 20:47:30 -0400 +Subject: [PATCH] Rebase togglekblayoutandoptions to v0.8 + +No actual changes, and the previous patch applied just fine if +whitespace was ignored. Just adding this to explicitly advertise +support for v0.8 and reduce noise about patches being applied at an +offset. +--- + config.def.h | 17 +++++++++++------ + dwl.c | 44 +++++++++++++++++++++++++++++++++----------- + 2 files changed, 44 insertions(+), 17 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 8a6eda0..850f322 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -48,12 +48,15 @@ static const MonitorRule monrules[] = { + }; + + /* keyboard */ +-static const struct xkb_rule_names xkb_rules = { +- /* can specify fields: rules, model, layout, variant, options */ +- /* example: +- .options = "ctrl:nocaps", +- */ +- .options = NULL, ++static const struct xkb_rule_names xkb_rules[] = { ++ { ++ .layout = "us", ++ }, ++ /*{ ++ .layout = "us", ++ .variant = "dvp", ++ .options = "compose:102,numpad:shift3,kpdl:semi,keypad:atm,caps:super" ++ }*/ + }; + + static const int repeat_rate = 25; +@@ -144,6 +147,8 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_A, incxkbrules, {.i = +1} }, ++ /*{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_E, setxkbrules, {.i = +1} },*/ + TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), + TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), + TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), +diff --git a/dwl.c b/dwl.c +index 44f3ad9..a86c692 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -246,6 +246,7 @@ static void arrange(Monitor *m); + static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); ++static void assignkeymap(struct wlr_keyboard *keyboard); + static void axisnotify(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); +@@ -290,6 +291,7 @@ static void fullscreennotify(struct wl_listener *listener, void *data); + static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); ++static void incxkbrules(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); + static int keybinding(uint32_t mods, xkb_keysym_t sym); + static void keypress(struct wl_listener *listener, void *data); +@@ -329,6 +331,7 @@ static void setmon(Client *c, Monitor *m, uint32_t newtags); + static void setpsel(struct wl_listener *listener, void *data); + static void setsel(struct wl_listener *listener, void *data); + static void setup(void); ++static void setxkbrules(const Arg *arg); + static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); +@@ -397,6 +400,7 @@ static struct wlr_session_lock_v1 *cur_lock; + + static struct wlr_seat *seat; + static KeyboardGroup *kb_group; ++static unsigned int kblayout = 0; + static unsigned int cursor_mode; + static Client *grabc; + static int grabcx, grabcy; /* client-relative */ +@@ -603,6 +607,20 @@ arrangelayers(Monitor *m) + } + } + ++void ++assignkeymap(struct wlr_keyboard *keyboard) { ++ struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); ++ struct xkb_keymap *keymap = xkb_keymap_new_from_names(context, &xkb_rules[kblayout], ++ XKB_KEYMAP_COMPILE_NO_FLAGS); ++ ++ if (!keymap) ++ die("failed to compile keymap"); ++ ++ wlr_keyboard_set_keymap(keyboard, keymap); ++ xkb_keymap_unref(keymap); ++ xkb_context_unref(context); ++} ++ + void + axisnotify(struct wl_listener *listener, void *data) + { +@@ -951,21 +969,11 @@ KeyboardGroup * + createkeyboardgroup(void) + { + KeyboardGroup *group = ecalloc(1, sizeof(*group)); +- struct xkb_context *context; +- struct xkb_keymap *keymap; + + group->wlr_group = wlr_keyboard_group_create(); + group->wlr_group->data = group; + +- /* Prepare an XKB keymap and assign it to the keyboard group. */ +- context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); +- if (!(keymap = xkb_keymap_new_from_names(context, &xkb_rules, +- XKB_KEYMAP_COMPILE_NO_FLAGS))) +- die("failed to compile keymap"); +- +- wlr_keyboard_set_keymap(&group->wlr_group->keyboard, keymap); +- xkb_keymap_unref(keymap); +- xkb_context_unref(context); ++ assignkeymap(&group->wlr_group->keyboard); + + wlr_keyboard_set_repeat_info(&group->wlr_group->keyboard, repeat_rate, repeat_delay); + +@@ -1575,6 +1583,13 @@ incnmaster(const Arg *arg) + arrange(selmon); + } + ++void ++incxkbrules(const Arg *arg) ++{ ++ kblayout = (kblayout + arg->i) % LENGTH(xkb_rules); ++ assignkeymap(&kb_group->wlr_group->keyboard); ++} ++ + void + inputdevice(struct wl_listener *listener, void *data) + { +@@ -2666,6 +2681,13 @@ setup(void) + #endif + } + ++void ++setxkbrules(const Arg *arg) ++{ ++ kblayout = arg->i; ++ assignkeymap(&kb_group->wlr_group->keyboard); ++} ++ + void + spawn(const Arg *arg) + { +-- +2.52.0 + From ee75e70f7963344267d92cff31be9ed0c0ea2585 Mon Sep 17 00:00:00 2001 From: julmajustus Date: Thu, 19 Mar 2026 23:20:11 +0200 Subject: [PATCH 2/5] btrtile update for dwl v0.8 and wlroots-next --- patches/btrtile/README.md | 6 +- ...0.7-gaps.patch => btrtile-v0.8-gaps.patch} | 129 ++- patches/btrtile/btrtile-v0.8.patch | 894 +++++++++++++++++ .../btrtile-wlroots-next-d41ecb7-gaps.patch | 913 ++++++++++++++++++ ...tch => btrtile-wlroots-next-d41ecb7.patch} | 121 ++- 5 files changed, 1927 insertions(+), 136 deletions(-) rename patches/btrtile/{btrtile-v0.7-gaps.patch => btrtile-v0.8-gaps.patch} (89%) create mode 100644 patches/btrtile/btrtile-v0.8.patch create mode 100644 patches/btrtile/btrtile-wlroots-next-d41ecb7-gaps.patch rename patches/btrtile/{btrtile-v0.7.patch => btrtile-wlroots-next-d41ecb7.patch} (89%) diff --git a/patches/btrtile/README.md b/patches/btrtile/README.md index d187f5f..7e679c4 100644 --- a/patches/btrtile/README.md +++ b/patches/btrtile/README.md @@ -96,8 +96,10 @@ If mouse resizing feels sluggish, you can try compiling dwl with more aggressive ### Download - [git branch](https://codeberg.org/julmajustus/dwl/src/branch/btrtile-dev) -- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/btrtile/btrtile-v0.7.patch) -- [0.7 WITH gaps](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/btrtile/btrtile-v0.7-gaps.patch) +- [0.8](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/btrtile/btrtile-v0.8.patch) +- [0.8 WITH gaps](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/btrtile/btrtile-v0.8-gaps.patch) +- [wlroots-next](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/btrtile/btrtile-wlroots-next-d41ecb7.patch) +- [wlroots-next WITH gaps](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/btrtile/btrtile-wlroots-next-d41ecb7-gaps.patch) ### Authors - [julmajustus](https://codeberg.org/julmajustus) diff --git a/patches/btrtile/btrtile-v0.7-gaps.patch b/patches/btrtile/btrtile-v0.8-gaps.patch similarity index 89% rename from patches/btrtile/btrtile-v0.7-gaps.patch rename to patches/btrtile/btrtile-v0.8-gaps.patch index dc4e9cf..17cc5ca 100644 --- a/patches/btrtile/btrtile-v0.7-gaps.patch +++ b/patches/btrtile/btrtile-v0.8-gaps.patch @@ -1,30 +1,32 @@ -From 858ef20d36c2d5e6a23a69b3b5909a80fab05f97 Mon Sep 17 00:00:00 2001 +From f5d7fe287bd4405879a92d649243d3f646652617 Mon Sep 17 00:00:00 2001 From: julmajustus -Date: Thu, 13 Feb 2025 23:25:20 +0200 -Subject: [PATCH] btrtile-gaps with multi-tag support +Date: Thu, 19 Mar 2026 00:08:10 +0200 +Subject: [PATCH] Refactor btrtile-gaps to v0.8 --- - btrtile.c | 582 +++++++++++++++++++++++++++++++++++++++++++++++++++ + btrtile.c | 584 +++++++++++++++++++++++++++++++++++++++++++++++++++ config.def.h | 12 ++ - dwl.c | 152 +++++++++++--- - 3 files changed, 717 insertions(+), 29 deletions(-) + dwl.c | 148 ++++++++++--- + 3 files changed, 717 insertions(+), 27 deletions(-) create mode 100644 btrtile.c diff --git a/btrtile.c b/btrtile.c new file mode 100644 -index 0000000..650cab5 +index 0000000..afe3c8f --- /dev/null +++ b/btrtile.c -@@ -0,0 +1,582 @@ +@@ -0,0 +1,584 @@ +/* ************************************************************************** */ -+/* */ -+/* ::: :::::::: */ -+/* btrtile.c :+: :+: :+: */ -+/* +:+ +:+ +:+ */ -+/* By: jmakkone +#+ +:+ +#+ */ -+/* +#+#+#+#+#+ +#+ */ -+/* Created: 2024/12/15 00:26:07 by jmakkone #+# #+# */ -+/* Updated: 2025/02/13 23:25:03 by jmakkone ### ########.fr */ ++/* @@@ @@@@@@@@ */ ++/* @@@ @@@@@@@@@@ */ ++/* @@! @@! @@@@ */ ++/* !@! !@! @!@!@ */ ++/* btrtile.c @!! @!@ @! !@! */ ++/* !!! !@!!! !!! */ ++/* By: julmajustus !!: !!:! !!! */ ++/* ::! :!: !:! */ ++/* Created: 2024/12/15 00:26:07 by julmajustus :: ::::::: :: */ ++/* Updated: 2026/03/19 00:07:19 by julmajustus : : : : : : */ +/* */ +/* ************************************************************************** */ + @@ -74,7 +76,7 @@ index 0000000..650cab5 + if (!node) + return; + -+ if (is_root && e) { ++ if (is_root && e) { + area.x += gappx; + area.y += gappx; + area.width -= 2 * gappx; @@ -127,7 +129,7 @@ index 0000000..650cab5 + right_area.width = area.width - mid; + right_area.height = area.height; + -+ if (e) { ++ if (e) { + left_area.width -= gappx / 2; + right_area.x += gappx / 2; + right_area.width -= gappx / 2; @@ -145,7 +147,7 @@ index 0000000..650cab5 + right_area.width = area.width; + right_area.height= area.height - mid; + -+ if (e) { ++ if (e) { + left_area.height -= gappx / 2; + right_area.y += gappx / 2; + right_area.height -= gappx / 2; @@ -599,21 +601,21 @@ index 0000000..650cab5 + return closest; +} diff --git a/config.def.h b/config.def.h -index 22d2171..92f3ad6 100644 +index 8a6eda0..bc04e3f 100644 --- a/config.def.h +++ b/config.def.h @@ -13,7 +13,10 @@ static const float focuscolor[] = COLOR(0x005577ff); static const float urgentcolor[] = COLOR(0xff0000ff); /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ - static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ -+static const float resize_factor = 0.0002f; /* Resize multiplier for mouse resizing, depends on mouse sensitivity. */ + static const float fullscreen_bg[] = {0.0f, 0.0f, 0.0f, 1.0f}; /* You can also use glsl colors */ ++static const float resize_factor = 0.0002f; /* Resize multiplier for mouse resizing, depends on mouse sensivity. */ +static const uint32_t resize_interval_ms = 16; /* Resize interval depends on framerate and screen refresh rate. */ +enum Direction { DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN }; /* tagging - TAGCOUNT must be no greater than 31 */ #define TAGCOUNT (9) -@@ -31,6 +34,7 @@ static const Rule rules[] = { +@@ -30,6 +33,7 @@ static const Rule rules[] = { /* layout(s) */ static const Layout layouts[] = { /* symbol arrange function */ @@ -621,23 +623,23 @@ index 22d2171..92f3ad6 100644 { "[]=", tile }, { "><>", NULL }, /* no layout function means floating behavior */ { "[M]", monocle }, -@@ -148,6 +152,14 @@ static const Key keys[] = { - { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, - { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, - { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, -+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, swapclients, {.i = DIR_UP} }, -+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, swapclients, {.i = DIR_DOWN} }, -+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, swapclients, {.i = DIR_RIGHT} }, -+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, swapclients, {.i = DIR_LEFT} }, -+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Right, setratio_h, {.f = +0.025f} }, -+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Left, setratio_h, {.f = -0.025f} }, -+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Up, setratio_v, {.f = -0.025f} }, -+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Down, setratio_v, {.f = +0.025f} }, - TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), - TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), - TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), +@@ -144,6 +148,14 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, swapclients, {.i = DIR_UP} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, swapclients, {.i = DIR_DOWN} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, swapclients, {.i = DIR_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, swapclients, {.i = DIR_LEFT} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Right, setratio_h, {.f = +0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Left, setratio_h, {.f = -0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Up, setratio_v, {.f = -0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Down, setratio_v, {.f = +0.025f} }, + TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), + TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), + TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), diff --git a/dwl.c b/dwl.c -index a2711f6..e49a061 100644 +index 44f3ad9..f74d9a1 100644 --- a/dwl.c +++ b/dwl.c @@ -1,6 +1,7 @@ @@ -648,15 +650,15 @@ index a2711f6..e49a061 100644 #include #include #include -@@ -103,6 +104,7 @@ typedef struct { +@@ -100,6 +101,7 @@ typedef struct { const Arg arg; } Button; +typedef struct LayoutNode LayoutNode; typedef struct Monitor Monitor; typedef struct { - /* Must keep these three elements in this order */ -@@ -139,8 +141,9 @@ typedef struct { + /* Must keep this field first */ +@@ -137,8 +139,9 @@ typedef struct { #endif unsigned int bw; uint32_t tags; @@ -667,7 +669,7 @@ index a2711f6..e49a061 100644 } Client; typedef struct { -@@ -208,6 +211,7 @@ struct Monitor { +@@ -205,6 +208,7 @@ struct Monitor { int nmaster; char ltsymbol[16]; int asleep; @@ -675,7 +677,7 @@ index a2711f6..e49a061 100644 }; typedef struct { -@@ -250,6 +254,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list, +@@ -247,6 +251,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list, struct wlr_box *usable_area, int exclusive); static void arrangelayers(Monitor *m); static void axisnotify(struct wl_listener *listener, void *data); @@ -683,7 +685,7 @@ index a2711f6..e49a061 100644 static void buttonpress(struct wl_listener *listener, void *data); static void chvt(const Arg *arg); static void checkidleinhibitor(struct wlr_surface *exclude); -@@ -333,6 +338,9 @@ static void setmon(Client *c, Monitor *m, uint32_t newtags); +@@ -329,6 +334,9 @@ static void setmon(Client *c, Monitor *m, uint32_t newtags); static void setpsel(struct wl_listener *listener, void *data); static void setsel(struct wl_listener *listener, void *data); static void setup(void); @@ -693,7 +695,7 @@ index a2711f6..e49a061 100644 static void spawn(const Arg *arg); static void startdrag(struct wl_listener *listener, void *data); static void tag(const Arg *arg); -@@ -431,6 +439,7 @@ static xcb_atom_t netatom[NetLast]; +@@ -454,6 +462,7 @@ static struct wlr_xwayland *xwayland; /* attempt to encapsulate suck into one file */ #include "client.h" @@ -701,7 +703,7 @@ index a2711f6..e49a061 100644 /* function implementations */ void -@@ -601,7 +610,7 @@ buttonpress(struct wl_listener *listener, void *data) +@@ -624,7 +633,7 @@ buttonpress(struct wl_listener *listener, void *data) struct wlr_pointer_button_event *event = data; struct wlr_keyboard *keyboard; uint32_t mods; @@ -710,7 +712,7 @@ index a2711f6..e49a061 100644 const Button *b; wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); -@@ -622,7 +631,7 @@ buttonpress(struct wl_listener *listener, void *data) +@@ -645,7 +654,7 @@ buttonpress(struct wl_listener *listener, void *data) mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; for (b = buttons; b < END(buttons); b++) { if (CLEANMASK(mods) == CLEANMASK(b->mod) && @@ -719,9 +721,9 @@ index a2711f6..e49a061 100644 b->func(&b->arg); return; } -@@ -632,15 +641,36 @@ buttonpress(struct wl_listener *listener, void *data) +@@ -655,6 +664,27 @@ buttonpress(struct wl_listener *listener, void *data) /* If you released any buttons, we exit interactive move/resize mode. */ - /* TODO should reset to the pointer focus's current setcursor */ + /* TODO: should reset to the pointer focus's current setcursor */ if (!locked && cursor_mode != CurNormal && cursor_mode != CurPressed) { + c = grabc; + if (c && c->was_tiled && !strcmp(selmon->ltsymbol, "|w|")) { @@ -747,18 +749,7 @@ index a2711f6..e49a061 100644 wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); cursor_mode = CurNormal; /* Drop the window off on its new monitor */ - selmon = xytomon(cursor->x, cursor->y); - setmon(grabc, selmon, 0); -+ grabc = NULL; - return; -- } else { -- cursor_mode = CurNormal; - } -+ cursor_mode = CurNormal; - break; - } - /* If the event wasn't handled by the compositor, notify the client with -@@ -720,6 +750,7 @@ cleanupmon(struct wl_listener *listener, void *data) +@@ -746,6 +776,7 @@ cleanupmon(struct wl_listener *listener, void *data) wlr_output_layout_remove(output_layout, m->wlr_output); wlr_scene_output_destroy(m->scene_output); @@ -766,7 +757,7 @@ index a2711f6..e49a061 100644 closemon(m); wlr_scene_node_destroy(&m->fullscreen_bg->node); free(m); -@@ -1024,6 +1055,7 @@ createmon(struct wl_listener *listener, void *data) +@@ -1090,6 +1121,7 @@ createmon(struct wl_listener *listener, void *data) wl_list_insert(&mons, &m->link); printstatus(); @@ -774,7 +765,7 @@ index a2711f6..e49a061 100644 /* The xdg-protocol specifies: * -@@ -1263,6 +1295,10 @@ destroynotify(struct wl_listener *listener, void *data) +@@ -1332,6 +1364,10 @@ destroynotify(struct wl_listener *listener, void *data) wl_list_remove(&c->destroy.link); wl_list_remove(&c->set_title.link); wl_list_remove(&c->fullscreen.link); @@ -785,7 +776,7 @@ index a2711f6..e49a061 100644 #ifdef XWAYLAND if (c->type != XDGShell) { wl_list_remove(&c->activate.link); -@@ -1809,7 +1845,8 @@ void +@@ -1862,7 +1898,8 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy, double dx_unaccel, double dy_unaccel) { @@ -795,7 +786,7 @@ index a2711f6..e49a061 100644 Client *c = NULL, *w = NULL; LayerSurface *l = NULL; struct wlr_surface *surface = NULL; -@@ -1863,18 +1900,56 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d +@@ -1916,18 +1953,56 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d /* Update drag icon's position */ wlr_scene_node_set_position(&drag_icon->node, (int)round(cursor->x), (int)round(cursor->y)); @@ -859,7 +850,7 @@ index a2711f6..e49a061 100644 /* If there's no client surface under the cursor, set the cursor image to a * default. This is what makes the cursor image appear when you move it * off of a client or over its border. */ -@@ -1908,22 +1983,41 @@ moveresize(const Arg *arg) +@@ -1961,22 +2036,41 @@ moveresize(const Arg *arg) if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen) return; @@ -869,7 +860,7 @@ index a2711f6..e49a061 100644 - case CurMove: - grabcx = (int)round(cursor->x) - grabc->geom.x; - grabcy = (int)round(cursor->y) - grabc->geom.y; -- wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "all-scroll"); - break; - case CurResize: - /* Doesn't work for X11 output - the next absolute motion event @@ -918,5 +909,5 @@ index a2711f6..e49a061 100644 } -- -2.45.3 +2.52.0 diff --git a/patches/btrtile/btrtile-v0.8.patch b/patches/btrtile/btrtile-v0.8.patch new file mode 100644 index 0000000..f43312b --- /dev/null +++ b/patches/btrtile/btrtile-v0.8.patch @@ -0,0 +1,894 @@ +From 1b75163aac16942a715c6fc0aaf5d508e778dd6a Mon Sep 17 00:00:00 2001 +From: julmajustus +Date: Wed, 18 Mar 2026 20:23:00 +0200 +Subject: [PATCH] Refactor btrtile for v0.8 + +--- + btrtile.c | 565 +++++++++++++++++++++++++++++++++++++++++++++++++++ + config.def.h | 12 ++ + dwl.c | 148 +++++++++++--- + 3 files changed, 698 insertions(+), 27 deletions(-) + create mode 100644 btrtile.c + +diff --git a/btrtile.c b/btrtile.c +new file mode 100644 +index 0000000..d706c3b +--- /dev/null ++++ b/btrtile.c +@@ -0,0 +1,565 @@ ++/* ************************************************************************** */ ++/* @@@ @@@@@@@@ */ ++/* @@@ @@@@@@@@@@ */ ++/* @@! @@! @@@@ */ ++/* !@! !@! @!@!@ */ ++/* btrtile.c @!! @!@ @! !@! */ ++/* !!! !@!!! !!! */ ++/* By: julmajustus !!: !!:! !!! */ ++/* ::! :!: !:! */ ++/* Created: 2024/12/15 00:26:07 by julmajustus :: ::::::: :: */ ++/* Updated: 2026/03/18 20:26:17 by julmajustus : : : : : : */ ++/* */ ++/* ************************************************************************** */ ++ ++typedef struct LayoutNode { ++ unsigned int is_client_node; ++ unsigned int is_split_vertically; ++ float split_ratio; ++ struct LayoutNode *left; ++ struct LayoutNode *right; ++ struct LayoutNode *split_node; ++ Client *client; ++} LayoutNode; ++ ++static void apply_layout(Monitor *m, LayoutNode *node, ++ struct wlr_box area, unsigned int is_root); ++static void btrtile(Monitor *m); ++static LayoutNode *create_client_node(Client *c); ++static LayoutNode *create_split_node(unsigned int is_split_vertically, ++ LayoutNode *left, LayoutNode *right); ++static void destroy_node(LayoutNode *node); ++static void destroy_tree(Monitor *m); ++static LayoutNode *find_client_node(LayoutNode *node, Client *c); ++static LayoutNode *find_suitable_split(LayoutNode *start, unsigned int need_vert); ++static void init_tree(Monitor *m); ++static void insert_client(Monitor *m, Client *focused_client, Client *new_client); ++static LayoutNode *remove_client_node(LayoutNode *node, Client *c); ++static void remove_client(Monitor *m, Client *c); ++static void setratio_h(const Arg *arg); ++static void setratio_v(const Arg *arg); ++static void swapclients(const Arg *arg); ++static unsigned int visible_count(LayoutNode *node, Monitor *m); ++static Client *xytoclient(double x, double y); ++ ++static int resizing_from_mouse = 0; ++static double resize_last_update_x, resize_last_update_y; ++static uint32_t last_resize_time = 0; ++ ++void ++apply_layout(Monitor *m, LayoutNode *node, ++ struct wlr_box area, unsigned int is_root) ++{ ++ Client *c; ++ float ratio; ++ unsigned int left_count, right_count, mid; ++ struct wlr_box left_area, right_area; ++ ++ if (!node) ++ return; ++ ++ /* If this node is a client node, check if it is visible. */ ++ if (node->is_client_node) { ++ c = node->client; ++ if (!c || !VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ return; ++ resize(c, area, 0); ++ c->old_geom = area; ++ return; ++ } ++ ++ /* For a split node, we see how many visible children are on each side: */ ++ left_count = visible_count(node->left, m); ++ right_count = visible_count(node->right, m); ++ ++ if (left_count == 0 && right_count == 0) { ++ return; ++ } else if (left_count > 0 && right_count == 0) { ++ apply_layout(m, node->left, area, 0); ++ return; ++ } else if (left_count == 0 && right_count > 0) { ++ apply_layout(m, node->right, area, 0); ++ return; ++ } ++ ++ /* If we’re here, we have visible clients in both subtrees. */ ++ ratio = node->split_ratio; ++ if (ratio < 0.05f) ++ ratio = 0.05f; ++ if (ratio > 0.95f) ++ ratio = 0.95f; ++ ++ memset(&left_area, 0, sizeof(left_area)); ++ memset(&right_area, 0, sizeof(right_area)); ++ ++ if (node->is_split_vertically) { ++ mid = (unsigned int)(area.width * ratio); ++ left_area.x = area.x; ++ left_area.y = area.y; ++ left_area.width = mid; ++ left_area.height = area.height; ++ ++ right_area.x = area.x + mid; ++ right_area.y = area.y; ++ right_area.width = area.width - mid; ++ right_area.height = area.height; ++ } else { ++ /* horizontal split */ ++ mid = (unsigned int)(area.height * ratio); ++ left_area.x = area.x; ++ left_area.y = area.y; ++ left_area.width = area.width; ++ left_area.height = mid; ++ ++ right_area.x = area.x; ++ right_area.y = area.y + mid; ++ right_area.width = area.width; ++ right_area.height= area.height - mid; ++ } ++ ++ apply_layout(m, node->left, left_area, 0); ++ apply_layout(m, node->right, right_area, 0); ++} ++ ++void ++btrtile(Monitor *m) ++{ ++ Client *c, *focused = NULL; ++ int n = 0; ++ LayoutNode *found; ++ struct wlr_box full_area; ++ ++ if (!m || !m->root) ++ return; ++ ++ /* Remove non tiled clients from tree. */ ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon == m && !c->isfloating && !c->isfullscreen) { ++ } else { ++ remove_client(m, c); ++ } ++ } ++ ++ /* If no client is found under cursor, fallback to focustop(m) */ ++ if (!(focused = xytoclient(cursor->x, cursor->y))) ++ focused = focustop(m); ++ ++ /* Insert visible clients that are not part of the tree. */ ++ wl_list_for_each(c, &clients, link) { ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen && c->mon == m) { ++ found = find_client_node(m->root, c); ++ if (!found) { ++ insert_client(m, focused, c); ++ } ++ n++; ++ } ++ } ++ ++ if (n == 0) ++ return; ++ ++ full_area = m->w; ++ apply_layout(m, m->root, full_area, 1); ++} ++ ++LayoutNode * ++create_client_node(Client *c) ++{ ++ LayoutNode *node = calloc(1, sizeof(LayoutNode)); ++ ++ if (!node) ++ return NULL; ++ node->is_client_node = 1; ++ node->split_ratio = 0.5f; ++ node->client = c; ++ return node; ++} ++ ++LayoutNode * ++create_split_node(unsigned int is_split_vertically, ++ LayoutNode *left, LayoutNode *right) ++{ ++ LayoutNode *node = calloc(1, sizeof(LayoutNode)); ++ ++ if (!node) ++ return NULL; ++ node->is_client_node = 0; ++ node->split_ratio = 0.5f; ++ node->is_split_vertically = is_split_vertically; ++ node->left = left; ++ node->right = right; ++ if (left) ++ left->split_node = node; ++ if (right) ++ right->split_node = node; ++ return node; ++} ++ ++void ++destroy_node(LayoutNode *node) ++{ ++ if (!node) ++ return; ++ if (!node->is_client_node) { ++ destroy_node(node->left); ++ destroy_node(node->right); ++ } ++ free(node); ++} ++ ++void ++destroy_tree(Monitor *m) ++{ ++ if (!m || !m->root) ++ return; ++ destroy_node(m->root); ++ m->root = NULL; ++} ++ ++LayoutNode * ++find_client_node(LayoutNode *node, Client *c) ++{ ++ LayoutNode *res; ++ ++ if (!node || !c) ++ return NULL; ++ if (node->is_client_node) { ++ return (node->client == c) ? node : NULL; ++ } ++ res = find_client_node(node->left, c); ++ return res ? res : find_client_node(node->right, c); ++} ++ ++LayoutNode * ++find_suitable_split(LayoutNode *start_node, unsigned int need_vertical) ++{ ++ LayoutNode *n = start_node; ++ /* if we started from a client node, jump to its parent: */ ++ if (n && n->is_client_node) ++ n = n->split_node; ++ ++ while (n) { ++ if (!n->is_client_node && n->is_split_vertically == need_vertical && ++ visible_count(n->left, selmon) > 0 && visible_count(n->right, selmon) > 0) ++ return n; ++ n = n->split_node; ++ } ++ return NULL; ++} ++ ++void ++init_tree(Monitor *m) ++{ ++ if (!m) ++ return; ++ m->root = calloc(1, sizeof(LayoutNode)); ++ if (!m->root) ++ m->root = NULL; ++} ++ ++void ++insert_client(Monitor *m, Client *focused_client, Client *new_client) ++{ ++ Client *old_client; ++ LayoutNode **root = &m->root, *old_root, ++ *focused_node, *new_client_node, *old_client_node; ++ unsigned int wider, mid_x, mid_y; ++ ++ /* If no root , new client becomes the root. */ ++ if (!*root) { ++ *root = create_client_node(new_client); ++ return; ++ } ++ ++ /* Find the focused_client node, ++ * if not found split the root. */ ++ focused_node = focused_client ? ++ find_client_node(*root, focused_client) : NULL; ++ if (!focused_node) { ++ old_root = *root; ++ new_client_node = create_client_node(new_client); ++ *root = create_split_node(1, old_root, new_client_node); ++ return; ++ } ++ ++ /* Turn focused node from a client node into a split node, ++ * and attach old_client + new_client. */ ++ old_client = focused_node->client; ++ old_client_node = create_client_node(old_client); ++ new_client_node = create_client_node(new_client); ++ ++ /* Decide split direction. */ ++ wider = (focused_client->geom.width >= focused_client->geom.height); ++ focused_node->is_client_node = 0; ++ focused_node->client = NULL; ++ focused_node->is_split_vertically = (wider ? 1 : 0); ++ ++ /* Pick new_client side depending on the cursor position. */ ++ mid_x = focused_client->geom.x + focused_client->geom.width / 2; ++ mid_y = focused_client->geom.y + focused_client->geom.height / 2; ++ ++ if (wider) { ++ /* vertical split => left vs right */ ++ if (cursor->x <= mid_x) { ++ focused_node->left = new_client_node; ++ focused_node->right = old_client_node; ++ } else { ++ focused_node->left = old_client_node; ++ focused_node->right = new_client_node; ++ } ++ } else { ++ /* horizontal split => top vs bottom */ ++ if (cursor->y <= mid_y) { ++ focused_node->left = new_client_node; ++ focused_node->right = old_client_node; ++ } else { ++ focused_node->left = old_client_node; ++ focused_node->right = new_client_node; ++ } ++ } ++ old_client_node->split_node = focused_node; ++ new_client_node->split_node = focused_node; ++ focused_node->split_ratio = 0.5f; ++} ++ ++LayoutNode * ++remove_client_node(LayoutNode *node, Client *c) ++{ ++ LayoutNode *tmp; ++ if (!node) ++ return NULL; ++ if (node->is_client_node) { ++ /* If this client_node is the client we're removing, ++ * return NULL to remove it */ ++ if (node->client == c) { ++ free(node); ++ return NULL; ++ } ++ return node; ++ } ++ ++ node->left = remove_client_node(node->left, c); ++ node->right = remove_client_node(node->right, c); ++ ++ /* If one of the client node is NULL after removal and the other is not, ++ * we "lift" the other client node up to replace this split node. */ ++ if (!node->left && node->right) { ++ tmp = node->right; ++ ++ /* Save pointer to split node */ ++ if (tmp) ++ tmp->split_node = node->split_node; ++ ++ free(node); ++ return tmp; ++ } ++ ++ if (!node->right && node->left) { ++ tmp = node->left; ++ ++ /* Save pointer to split node */ ++ if (tmp) ++ tmp->split_node = node->split_node; ++ ++ free(node); ++ return tmp; ++ } ++ ++ /* If both children exist or both are NULL (empty tree), ++ * return node as is. */ ++ return node; ++} ++ ++void ++remove_client(Monitor *m, Client *c) ++{ ++ if (!m->root || !c) ++ return; ++ m->root = remove_client_node(m->root, c); ++} ++ ++void ++setratio_h(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ LayoutNode *client_node, *split_node; ++ float new_ratio; ++ ++ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ ++ client_node = find_client_node(selmon->root, sel); ++ if (!client_node) ++ return; ++ ++ split_node = find_suitable_split(client_node, 1); ++ if (!split_node) ++ return; ++ ++ new_ratio = (arg->f != 0.0f) ? (split_node->split_ratio + arg->f) : 0.5f; ++ if (new_ratio < 0.05f) ++ new_ratio = 0.05f; ++ if (new_ratio > 0.95f) ++ new_ratio = 0.95f; ++ split_node->split_ratio = new_ratio; ++ ++ /* Skip the arrange if done resizing by mouse, ++ * we call arrange from motionotify */ ++ if (!resizing_from_mouse) { ++ arrange(selmon); ++ } ++} ++ ++void ++setratio_v(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ LayoutNode *client_node, *split_node; ++ float new_ratio; ++ ++ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ ++ client_node = find_client_node(selmon->root, sel); ++ if (!client_node) ++ return; ++ ++ split_node = find_suitable_split(client_node, 0); ++ if (!split_node) ++ return; ++ ++ new_ratio = (arg->f != 0.0f) ? (split_node->split_ratio + arg->f) : 0.5f; ++ if (new_ratio < 0.05f) ++ new_ratio = 0.05f; ++ if (new_ratio > 0.95f) ++ new_ratio = 0.95f; ++ split_node->split_ratio = new_ratio; ++ ++ /* Skip the arrange if done resizing by mouse, ++ * we call arrange from motionotify */ ++ if (!resizing_from_mouse) { ++ arrange(selmon); ++ } ++} ++ ++void swapclients(const Arg *arg) { ++ Client *c, *tmp, *target = NULL, *sel = focustop(selmon); ++ LayoutNode *sel_node, *target_node; ++ int closest_dist = INT_MAX, dist, sel_center_x, sel_center_y, ++ cand_center_x, cand_center_y; ++ ++ if (!sel || sel->isfullscreen || ++ !selmon->root || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ ++ ++ /* Get the center coordinates of the selected client */ ++ sel_center_x = sel->geom.x + sel->geom.width / 2; ++ sel_center_y = sel->geom.y + sel->geom.height / 2; ++ ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, selmon) || c->isfloating || c->isfullscreen || c == sel) ++ continue; ++ ++ /* Get the center of candidate client */ ++ cand_center_x = c->geom.x + c->geom.width / 2; ++ cand_center_y = c->geom.y + c->geom.height / 2; ++ ++ /* Check that the candidate lies in the requested direction. */ ++ switch (arg->ui) { ++ case 0: ++ if (cand_center_x >= sel_center_x) ++ continue; ++ break; ++ case 1: ++ if (cand_center_x <= sel_center_x) ++ continue; ++ break; ++ case 2: ++ if (cand_center_y >= sel_center_y) ++ continue; ++ break; ++ case 3: ++ if (cand_center_y <= sel_center_y) ++ continue; ++ break; ++ default: ++ continue; ++ } ++ ++ /* Get distance between the centers */ ++ dist = abs(sel_center_x - cand_center_x) + abs(sel_center_y - cand_center_y); ++ if (dist < closest_dist) { ++ closest_dist = dist; ++ target = c; ++ } ++ } ++ ++ /* If target is found, swap the two clients’ positions in the layout tree */ ++ if (target) { ++ sel_node = find_client_node(selmon->root, sel); ++ target_node = find_client_node(selmon->root, target); ++ if (sel_node && target_node) { ++ tmp = sel_node->client; ++ sel_node->client = target_node->client; ++ target_node->client = tmp; ++ arrange(selmon); ++ } ++ } ++} ++ ++unsigned int ++visible_count(LayoutNode *node, Monitor *m) ++{ ++ Client *c; ++ ++ if (!node) ++ return 0; ++ /* Check if this client is visible. */ ++ if (node->is_client_node) { ++ c = node->client; ++ if (c && VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) ++ return 1; ++ return 0; ++ } ++ /* Else it’s a split node. */ ++ return visible_count(node->left, m) + visible_count(node->right, m); ++} ++ ++Client * ++xytoclient(double x, double y) { ++ Client *c, *closest = NULL; ++ double dist, mindist = INT_MAX, dx, dy; ++ ++ wl_list_for_each_reverse(c, &clients, link) { ++ if (VISIBLEON(c, selmon) && !c->isfloating && !c->isfullscreen && ++ x >= c->geom.x && x <= (c->geom.x + c->geom.width) && ++ y >= c->geom.y && y <= (c->geom.y + c->geom.height)){ ++ return c; ++ } ++ } ++ ++ /* If no client was found at cursor position fallback to closest. */ ++ wl_list_for_each_reverse(c, &clients, link) { ++ if (VISIBLEON(c, selmon) && !c->isfloating && !c->isfullscreen) { ++ dx = 0, dy = 0; ++ ++ if (x < c->geom.x) ++ dx = c->geom.x - x; ++ else if (x > (c->geom.x + c->geom.width)) ++ dx = x - (c->geom.x + c->geom.width); ++ ++ if (y < c->geom.y) ++ dy = c->geom.y - y; ++ else if (y > (c->geom.y + c->geom.height)) ++ dy = y - (c->geom.y + c->geom.height); ++ ++ dist = sqrt(dx * dx + dy * dy); ++ if (dist < mindist) { ++ mindist = dist; ++ closest = c; ++ } ++ } ++ } ++ return closest; ++} +diff --git a/config.def.h b/config.def.h +index 8a6eda0..bc04e3f 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -13,7 +13,10 @@ static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.0f, 0.0f, 0.0f, 1.0f}; /* You can also use glsl colors */ ++static const float resize_factor = 0.0002f; /* Resize multiplier for mouse resizing, depends on mouse sensivity. */ ++static const uint32_t resize_interval_ms = 16; /* Resize interval depends on framerate and screen refresh rate. */ + ++enum Direction { DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN }; + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) + +@@ -30,6 +33,7 @@ static const Rule rules[] = { + /* layout(s) */ + static const Layout layouts[] = { + /* symbol arrange function */ ++ { "|w|", btrtile }, + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, +@@ -144,6 +148,14 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, swapclients, {.i = DIR_UP} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, swapclients, {.i = DIR_DOWN} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, swapclients, {.i = DIR_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, swapclients, {.i = DIR_LEFT} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Right, setratio_h, {.f = +0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Left, setratio_h, {.f = -0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Up, setratio_v, {.f = -0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Down, setratio_v, {.f = +0.025f} }, + TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), + TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), + TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), +diff --git a/dwl.c b/dwl.c +index 44f3ad9..f74d9a1 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1,6 +1,7 @@ + /* + * See LICENSE file for copyright and license details. + */ ++#include + #include + #include + #include +@@ -100,6 +101,7 @@ typedef struct { + const Arg arg; + } Button; + ++typedef struct LayoutNode LayoutNode; + typedef struct Monitor Monitor; + typedef struct { + /* Must keep this field first */ +@@ -137,8 +139,9 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, was_tiled; + uint32_t resize; /* configure serial of a pending resize */ ++ struct wlr_box old_geom; + } Client; + + typedef struct { +@@ -205,6 +208,7 @@ struct Monitor { + int nmaster; + char ltsymbol[16]; + int asleep; ++ LayoutNode *root; + }; + + typedef struct { +@@ -247,6 +251,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); + static void axisnotify(struct wl_listener *listener, void *data); ++static void btrtile(Monitor *m); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); + static void checkidleinhibitor(struct wlr_surface *exclude); +@@ -329,6 +334,9 @@ static void setmon(Client *c, Monitor *m, uint32_t newtags); + static void setpsel(struct wl_listener *listener, void *data); + static void setsel(struct wl_listener *listener, void *data); + static void setup(void); ++static void setratio_h(const Arg *arg); ++static void setratio_v(const Arg *arg); ++static void swapclients(const Arg *arg); + static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); +@@ -454,6 +462,7 @@ static struct wlr_xwayland *xwayland; + + /* attempt to encapsulate suck into one file */ + #include "client.h" ++#include "btrtile.c" + + /* function implementations */ + void +@@ -624,7 +633,7 @@ buttonpress(struct wl_listener *listener, void *data) + struct wlr_pointer_button_event *event = data; + struct wlr_keyboard *keyboard; + uint32_t mods; +- Client *c; ++ Client *c, *target = NULL; + const Button *b; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); +@@ -645,7 +654,7 @@ buttonpress(struct wl_listener *listener, void *data) + mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; + for (b = buttons; b < END(buttons); b++) { + if (CLEANMASK(mods) == CLEANMASK(b->mod) && +- event->button == b->button && b->func) { ++ event->button == b->button && b->func) { + b->func(&b->arg); + return; + } +@@ -655,6 +664,27 @@ buttonpress(struct wl_listener *listener, void *data) + /* If you released any buttons, we exit interactive move/resize mode. */ + /* TODO: should reset to the pointer focus's current setcursor */ + if (!locked && cursor_mode != CurNormal && cursor_mode != CurPressed) { ++ c = grabc; ++ if (c && c->was_tiled && !strcmp(selmon->ltsymbol, "|w|")) { ++ if (cursor_mode == CurMove && c->isfloating) { ++ target = xytoclient(cursor->x, cursor->y); ++ ++ if (target && !target->isfloating && !target->isfullscreen) ++ insert_client(selmon, target, c); ++ else ++ selmon->root = create_client_node(c); ++ ++ setfloating(c, 0); ++ arrange(selmon); ++ ++ } else if (cursor_mode == CurResize && !c->isfloating) { ++ resizing_from_mouse = 0; ++ } ++ } else { ++ if (cursor_mode == CurResize && resizing_from_mouse) ++ resizing_from_mouse = 0; ++ } ++ /* Default behaviour */ + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + cursor_mode = CurNormal; + /* Drop the window off on its new monitor */ +@@ -746,6 +776,7 @@ cleanupmon(struct wl_listener *listener, void *data) + wlr_output_layout_remove(output_layout, m->wlr_output); + wlr_scene_output_destroy(m->scene_output); + ++ destroy_tree(m); + closemon(m); + wlr_scene_node_destroy(&m->fullscreen_bg->node); + free(m); +@@ -1090,6 +1121,7 @@ createmon(struct wl_listener *listener, void *data) + + wl_list_insert(&mons, &m->link); + printstatus(); ++ init_tree(m); + + /* The xdg-protocol specifies: + * +@@ -1332,6 +1364,10 @@ destroynotify(struct wl_listener *listener, void *data) + wl_list_remove(&c->destroy.link); + wl_list_remove(&c->set_title.link); + wl_list_remove(&c->fullscreen.link); ++ /* We check if the destroyed client was part of any tiled_list, to catch ++ * client removals even if they would not be currently managed by btrtile */ ++ if (selmon && selmon->root) ++ remove_client(selmon, c); + #ifdef XWAYLAND + if (c->type != XDGShell) { + wl_list_remove(&c->activate.link); +@@ -1862,7 +1898,8 @@ void + motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy, + double dx_unaccel, double dy_unaccel) + { +- double sx = 0, sy = 0, sx_confined, sy_confined; ++ int tiled = 0; ++ double sx = 0, sy = 0, sx_confined, sy_confined, dx_total, dy_total; + Client *c = NULL, *w = NULL; + LayerSurface *l = NULL; + struct wlr_surface *surface = NULL; +@@ -1916,18 +1953,56 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d + /* Update drag icon's position */ + wlr_scene_node_set_position(&drag_icon->node, (int)round(cursor->x), (int)round(cursor->y)); + +- /* If we are currently grabbing the mouse, handle and return */ ++ /* Skip if internal call or already resizing */ ++ if (time == 0 && resizing_from_mouse) ++ goto focus; ++ ++ tiled = grabc && !grabc->isfloating && !grabc->isfullscreen; + if (cursor_mode == CurMove) { + /* Move the grabbed client to the new position. */ +- resize(grabc, (struct wlr_box){.x = (int)round(cursor->x) - grabcx, .y = (int)round(cursor->y) - grabcy, +- .width = grabc->geom.width, .height = grabc->geom.height}, 1); +- return; ++ if (grabc && grabc->isfloating) { ++ resize(grabc, (struct wlr_box){ ++ .x = (int)round(cursor->x) - grabcx, ++ .y = (int)round(cursor->y) - grabcy, ++ .width = grabc->geom.width, ++ .height = grabc->geom.height ++ }, 1); ++ return; ++ } + } else if (cursor_mode == CurResize) { +- resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, +- .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1); +- return; ++ if (tiled && resizing_from_mouse) { ++ dx_total = cursor->x - resize_last_update_x; ++ dy_total = cursor->y - resize_last_update_y; ++ ++ if (time - last_resize_time >= resize_interval_ms) { ++ Arg a = {0}; ++ if (fabs(dx_total) > fabs(dy_total)) { ++ a.f = (float)(dx_total * resize_factor); ++ setratio_h(&a); ++ } else { ++ a.f = (float)(dy_total * resize_factor); ++ setratio_v(&a); ++ } ++ arrange(selmon); ++ ++ last_resize_time = time; ++ resize_last_update_x = cursor->x; ++ resize_last_update_y = cursor->y; ++ } ++ ++ } else if (grabc && grabc->isfloating) { ++ /* Floating resize as original */ ++ resize(grabc, (struct wlr_box){ ++ .x = grabc->geom.x, ++ .y = grabc->geom.y, ++ .width = (int)round(cursor->x) - grabc->geom.x, ++ .height = (int)round(cursor->y) - grabc->geom.y ++ }, 1); ++ return; ++ } + } + ++focus: + /* If there's no client surface under the cursor, set the cursor image to a + * default. This is what makes the cursor image appear when you move it + * off of a client or over its border. */ +@@ -1961,22 +2036,41 @@ moveresize(const Arg *arg) + if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen) + return; + +- /* Float the window and tell motionnotify to grab it */ +- setfloating(grabc, 1); +- switch (cursor_mode = arg->ui) { +- case CurMove: +- grabcx = (int)round(cursor->x) - grabc->geom.x; +- grabcy = (int)round(cursor->y) - grabc->geom.y; +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "all-scroll"); +- break; +- case CurResize: +- /* Doesn't work for X11 output - the next absolute motion event +- * returns the cursor to where it started */ +- wlr_cursor_warp_closest(cursor, NULL, +- grabc->geom.x + grabc->geom.width, +- grabc->geom.y + grabc->geom.height); +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); +- break; ++ cursor_mode = arg->ui; ++ grabc->was_tiled = (!grabc->isfloating && !grabc->isfullscreen); ++ ++ if (grabc->was_tiled) { ++ switch (cursor_mode) { ++ case CurMove: ++ setfloating(grabc, 1); ++ grabcx = (int)round(cursor->x) - grabc->geom.x; ++ grabcy = (int)round(cursor->y) - grabc->geom.y; ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); ++ break; ++ case CurResize: ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); ++ resize_last_update_x = cursor->x; ++ resize_last_update_y = cursor->y; ++ resizing_from_mouse = 1; ++ break; ++ } ++ } else { ++ /* Default floating logic */ ++ /* Float the window and tell motionnotify to grab it */ ++ setfloating(grabc, 1); ++ switch (cursor_mode) { ++ case CurMove: ++ grabcx = (int)round(cursor->x) - grabc->geom.x; ++ grabcy = (int)round(cursor->y) - grabc->geom.y; ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); ++ break; ++ case CurResize: ++ wlr_cursor_warp_closest(cursor, NULL, ++ grabc->geom.x + grabc->geom.width, ++ grabc->geom.y + grabc->geom.height); ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); ++ break; ++ } + } + } + +-- +2.52.0 + diff --git a/patches/btrtile/btrtile-wlroots-next-d41ecb7-gaps.patch b/patches/btrtile/btrtile-wlroots-next-d41ecb7-gaps.patch new file mode 100644 index 0000000..3177c56 --- /dev/null +++ b/patches/btrtile/btrtile-wlroots-next-d41ecb7-gaps.patch @@ -0,0 +1,913 @@ +From a9329abe48083a4c5b64eee726ceefdb4bf0bb9f Mon Sep 17 00:00:00 2001 +From: julmajustus +Date: Thu, 19 Mar 2026 22:59:49 +0200 +Subject: [PATCH] Refactor btrtile-gaps to wlroots-next + +--- + btrtile.c | 584 +++++++++++++++++++++++++++++++++++++++++++++++++++ + config.def.h | 12 ++ + dwl.c | 148 ++++++++++--- + 3 files changed, 717 insertions(+), 27 deletions(-) + create mode 100644 btrtile.c + +diff --git a/btrtile.c b/btrtile.c +new file mode 100644 +index 0000000..8703617 +--- /dev/null ++++ b/btrtile.c +@@ -0,0 +1,584 @@ ++/* ************************************************************************** */ ++/* @@@ @@@@@@@@ */ ++/* @@@ @@@@@@@@@@ */ ++/* @@! @@! @@@@ */ ++/* !@! !@! @!@!@ */ ++/* btrtile.c @!! @!@ @! !@! */ ++/* !!! !@!!! !!! */ ++/* By: julmajustus !!: !!:! !!! */ ++/* ::! :!: !:! */ ++/* Created: 2024/12/15 00:26:07 by julmajustus :: ::::::: :: */ ++/* Updated: 2026/03/19 22:59:12 by julmajustus : : : : : : */ ++/* */ ++/* ************************************************************************** */ ++ ++typedef struct LayoutNode { ++ unsigned int is_client_node; ++ unsigned int is_split_vertically; ++ float split_ratio; ++ struct LayoutNode *left; ++ struct LayoutNode *right; ++ struct LayoutNode *split_node; ++ Client *client; ++} LayoutNode; ++ ++static void apply_layout(Monitor *m, LayoutNode *node, ++ struct wlr_box area, unsigned int is_root); ++static void btrtile(Monitor *m); ++static LayoutNode *create_client_node(Client *c); ++static LayoutNode *create_split_node(unsigned int is_split_vertically, ++ LayoutNode *left, LayoutNode *right); ++static void destroy_node(LayoutNode *node); ++static void destroy_tree(Monitor *m); ++static LayoutNode *find_client_node(LayoutNode *node, Client *c); ++static LayoutNode *find_suitable_split(LayoutNode *start, unsigned int need_vert); ++static void init_tree(Monitor *m); ++static void insert_client(Monitor *m, Client *focused_client, Client *new_client); ++static LayoutNode *remove_client_node(LayoutNode *node, Client *c); ++static void remove_client(Monitor *m, Client *c); ++static void setratio_h(const Arg *arg); ++static void setratio_v(const Arg *arg); ++static void swapclients(const Arg *arg); ++static unsigned int visible_count(LayoutNode *node, Monitor *m); ++static Client *xytoclient(double x, double y); ++ ++static int resizing_from_mouse = 0; ++static double resize_last_update_x, resize_last_update_y; ++static uint32_t last_resize_time = 0; ++ ++void ++apply_layout(Monitor *m, LayoutNode *node, ++ struct wlr_box area, unsigned int is_root) ++{ ++ Client *c; ++ float ratio; ++ unsigned int left_count, right_count, mid, e = m->gaps; ++ struct wlr_box left_area, right_area; ++ ++ if (!node) ++ return; ++ ++ if (is_root && e) { ++ area.x += gappx; ++ area.y += gappx; ++ area.width -= 2 * gappx; ++ area.height -= 2 * gappx; ++ } ++ ++ /* If this node is a client node, check if it is visible. */ ++ if (node->is_client_node) { ++ c = node->client; ++ if (!c || !VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ return; ++ resize(c, area, 0); ++ c->old_geom = area; ++ return; ++ } ++ ++ /* For a split node, we see how many visible children are on each side: */ ++ left_count = visible_count(node->left, m); ++ right_count = visible_count(node->right, m); ++ ++ if (left_count == 0 && right_count == 0) { ++ return; ++ } else if (left_count > 0 && right_count == 0) { ++ apply_layout(m, node->left, area, 0); ++ return; ++ } else if (left_count == 0 && right_count > 0) { ++ apply_layout(m, node->right, area, 0); ++ return; ++ } ++ ++ /* If we’re here, we have visible clients in both subtrees. */ ++ ratio = node->split_ratio; ++ if (ratio < 0.05f) ++ ratio = 0.05f; ++ if (ratio > 0.95f) ++ ratio = 0.95f; ++ ++ memset(&left_area, 0, sizeof(left_area)); ++ memset(&right_area, 0, sizeof(right_area)); ++ ++ if (node->is_split_vertically) { ++ mid = (unsigned int)(area.width * ratio); ++ left_area.x = area.x; ++ left_area.y = area.y; ++ left_area.width = mid; ++ left_area.height = area.height; ++ ++ right_area.x = area.x + mid; ++ right_area.y = area.y; ++ right_area.width = area.width - mid; ++ right_area.height = area.height; ++ ++ if (e) { ++ left_area.width -= gappx / 2; ++ right_area.x += gappx / 2; ++ right_area.width -= gappx / 2; ++ } ++ } else { ++ /* horizontal split */ ++ mid = (unsigned int)(area.height * ratio); ++ left_area.x = area.x; ++ left_area.y = area.y; ++ left_area.width = area.width; ++ left_area.height = mid; ++ ++ right_area.x = area.x; ++ right_area.y = area.y + mid; ++ right_area.width = area.width; ++ right_area.height= area.height - mid; ++ ++ if (e) { ++ left_area.height -= gappx / 2; ++ right_area.y += gappx / 2; ++ right_area.height -= gappx / 2; ++ } ++ } ++ ++ apply_layout(m, node->left, left_area, 0); ++ apply_layout(m, node->right, right_area, 0); ++} ++ ++void ++btrtile(Monitor *m) ++{ ++ Client *c, *focused = NULL; ++ int n = 0; ++ LayoutNode *found; ++ struct wlr_box full_area; ++ ++ if (!m || !m->root) ++ return; ++ ++ /* Remove non tiled clients from tree. */ ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon == m && !c->isfloating && !c->isfullscreen) { ++ } else { ++ remove_client(m, c); ++ } ++ } ++ ++ /* If no client is found under cursor, fallback to focustop(m) */ ++ if (!(focused = xytoclient(cursor->x, cursor->y))) ++ focused = focustop(m); ++ ++ /* Insert visible clients that are not part of the tree. */ ++ wl_list_for_each(c, &clients, link) { ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen && c->mon == m) { ++ found = find_client_node(m->root, c); ++ if (!found) { ++ insert_client(m, focused, c); ++ } ++ n++; ++ } ++ } ++ ++ if (n == 0) ++ return; ++ ++ full_area = m->w; ++ apply_layout(m, m->root, full_area, 1); ++} ++ ++LayoutNode * ++create_client_node(Client *c) ++{ ++ LayoutNode *node = calloc(1, sizeof(LayoutNode)); ++ ++ if (!node) ++ return NULL; ++ node->is_client_node = 1; ++ node->split_ratio = 0.5f; ++ node->client = c; ++ return node; ++} ++ ++LayoutNode * ++create_split_node(unsigned int is_split_vertically, ++ LayoutNode *left, LayoutNode *right) ++{ ++ LayoutNode *node = calloc(1, sizeof(LayoutNode)); ++ ++ if (!node) ++ return NULL; ++ node->is_client_node = 0; ++ node->split_ratio = 0.5f; ++ node->is_split_vertically = is_split_vertically; ++ node->left = left; ++ node->right = right; ++ if (left) ++ left->split_node = node; ++ if (right) ++ right->split_node = node; ++ return node; ++} ++ ++void ++destroy_node(LayoutNode *node) ++{ ++ if (!node) ++ return; ++ if (!node->is_client_node) { ++ destroy_node(node->left); ++ destroy_node(node->right); ++ } ++ free(node); ++} ++ ++void ++destroy_tree(Monitor *m) ++{ ++ if (!m || !m->root) ++ return; ++ destroy_node(m->root); ++ m->root = NULL; ++} ++ ++LayoutNode * ++find_client_node(LayoutNode *node, Client *c) ++{ ++ LayoutNode *res; ++ ++ if (!node || !c) ++ return NULL; ++ if (node->is_client_node) { ++ return (node->client == c) ? node : NULL; ++ } ++ res = find_client_node(node->left, c); ++ return res ? res : find_client_node(node->right, c); ++} ++ ++LayoutNode * ++find_suitable_split(LayoutNode *start_node, unsigned int need_vertical) ++{ ++ LayoutNode *n = start_node; ++ /* if we started from a client node, jump to its parent: */ ++ if (n && n->is_client_node) ++ n = n->split_node; ++ ++ while (n) { ++ if (!n->is_client_node && n->is_split_vertically == need_vertical && ++ visible_count(n->left, selmon) > 0 && visible_count(n->right, selmon) > 0) ++ return n; ++ n = n->split_node; ++ } ++ return NULL; ++} ++ ++void ++init_tree(Monitor *m) ++{ ++ if (!m) ++ return; ++ m->root = calloc(1, sizeof(LayoutNode)); ++ if (!m->root) ++ m->root = NULL; ++} ++ ++void ++insert_client(Monitor *m, Client *focused_client, Client *new_client) ++{ ++ Client *old_client; ++ LayoutNode **root = &m->root, *old_root, ++ *focused_node, *new_client_node, *old_client_node; ++ unsigned int wider, mid_x, mid_y; ++ ++ /* If no root , new client becomes the root. */ ++ if (!*root) { ++ *root = create_client_node(new_client); ++ return; ++ } ++ ++ /* Find the focused_client node, ++ * if not found split the root. */ ++ focused_node = focused_client ? ++ find_client_node(*root, focused_client) : NULL; ++ if (!focused_node) { ++ old_root = *root; ++ new_client_node = create_client_node(new_client); ++ *root = create_split_node(1, old_root, new_client_node); ++ return; ++ } ++ ++ /* Turn focused node from a client node into a split node, ++ * and attach old_client + new_client. */ ++ old_client = focused_node->client; ++ old_client_node = create_client_node(old_client); ++ new_client_node = create_client_node(new_client); ++ ++ /* Decide split direction. */ ++ wider = (focused_client->geom.width >= focused_client->geom.height); ++ focused_node->is_client_node = 0; ++ focused_node->client = NULL; ++ focused_node->is_split_vertically = (wider ? 1 : 0); ++ ++ /* Pick new_client side depending on the cursor position. */ ++ mid_x = focused_client->geom.x + focused_client->geom.width / 2; ++ mid_y = focused_client->geom.y + focused_client->geom.height / 2; ++ ++ if (wider) { ++ /* vertical split => left vs right */ ++ if (cursor->x <= mid_x) { ++ focused_node->left = new_client_node; ++ focused_node->right = old_client_node; ++ } else { ++ focused_node->left = old_client_node; ++ focused_node->right = new_client_node; ++ } ++ } else { ++ /* horizontal split => top vs bottom */ ++ if (cursor->y <= mid_y) { ++ focused_node->left = new_client_node; ++ focused_node->right = old_client_node; ++ } else { ++ focused_node->left = old_client_node; ++ focused_node->right = new_client_node; ++ } ++ } ++ old_client_node->split_node = focused_node; ++ new_client_node->split_node = focused_node; ++ focused_node->split_ratio = 0.5f; ++} ++ ++LayoutNode * ++remove_client_node(LayoutNode *node, Client *c) ++{ ++ LayoutNode *tmp; ++ if (!node) ++ return NULL; ++ if (node->is_client_node) { ++ /* If this client_node is the client we're removing, ++ * return NULL to remove it */ ++ if (node->client == c) { ++ free(node); ++ return NULL; ++ } ++ return node; ++ } ++ ++ node->left = remove_client_node(node->left, c); ++ node->right = remove_client_node(node->right, c); ++ ++ /* If one of the client node is NULL after removal and the other is not, ++ * we "lift" the other client node up to replace this split node. */ ++ if (!node->left && node->right) { ++ tmp = node->right; ++ ++ /* Save pointer to split node */ ++ if (tmp) ++ tmp->split_node = node->split_node; ++ ++ free(node); ++ return tmp; ++ } ++ ++ if (!node->right && node->left) { ++ tmp = node->left; ++ ++ /* Save pointer to split node */ ++ if (tmp) ++ tmp->split_node = node->split_node; ++ ++ free(node); ++ return tmp; ++ } ++ ++ /* If both children exist or both are NULL (empty tree), ++ * return node as is. */ ++ return node; ++} ++ ++void ++remove_client(Monitor *m, Client *c) ++{ ++ if (!m->root || !c) ++ return; ++ m->root = remove_client_node(m->root, c); ++} ++ ++void ++setratio_h(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ LayoutNode *client_node, *split_node; ++ float new_ratio; ++ ++ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ ++ client_node = find_client_node(selmon->root, sel); ++ if (!client_node) ++ return; ++ ++ split_node = find_suitable_split(client_node, 1); ++ if (!split_node) ++ return; ++ ++ new_ratio = (arg->f != 0.0f) ? (split_node->split_ratio + arg->f) : 0.5f; ++ if (new_ratio < 0.05f) ++ new_ratio = 0.05f; ++ if (new_ratio > 0.95f) ++ new_ratio = 0.95f; ++ split_node->split_ratio = new_ratio; ++ ++ /* Skip the arrange if done resizing by mouse, ++ * we call arrange from motionotify */ ++ if (!resizing_from_mouse) { ++ arrange(selmon); ++ } ++} ++ ++void ++setratio_v(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ LayoutNode *client_node, *split_node; ++ float new_ratio; ++ ++ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ ++ client_node = find_client_node(selmon->root, sel); ++ if (!client_node) ++ return; ++ ++ split_node = find_suitable_split(client_node, 0); ++ if (!split_node) ++ return; ++ ++ new_ratio = (arg->f != 0.0f) ? (split_node->split_ratio + arg->f) : 0.5f; ++ if (new_ratio < 0.05f) ++ new_ratio = 0.05f; ++ if (new_ratio > 0.95f) ++ new_ratio = 0.95f; ++ split_node->split_ratio = new_ratio; ++ ++ /* Skip the arrange if done resizing by mouse, ++ * we call arrange from motionotify */ ++ if (!resizing_from_mouse) { ++ arrange(selmon); ++ } ++} ++ ++void swapclients(const Arg *arg) { ++ Client *c, *tmp, *target = NULL, *sel = focustop(selmon); ++ LayoutNode *sel_node, *target_node; ++ int closest_dist = INT_MAX, dist, sel_center_x, sel_center_y, ++ cand_center_x, cand_center_y; ++ ++ if (!sel || sel->isfullscreen || ++ !selmon->root || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ ++ ++ /* Get the center coordinates of the selected client */ ++ sel_center_x = sel->geom.x + sel->geom.width / 2; ++ sel_center_y = sel->geom.y + sel->geom.height / 2; ++ ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, selmon) || c->isfloating || c->isfullscreen || c == sel) ++ continue; ++ ++ /* Get the center of candidate client */ ++ cand_center_x = c->geom.x + c->geom.width / 2; ++ cand_center_y = c->geom.y + c->geom.height / 2; ++ ++ /* Check that the candidate lies in the requested direction. */ ++ switch (arg->ui) { ++ case 0: ++ if (cand_center_x >= sel_center_x) ++ continue; ++ break; ++ case 1: ++ if (cand_center_x <= sel_center_x) ++ continue; ++ break; ++ case 2: ++ if (cand_center_y >= sel_center_y) ++ continue; ++ break; ++ case 3: ++ if (cand_center_y <= sel_center_y) ++ continue; ++ break; ++ default: ++ continue; ++ } ++ ++ /* Get distance between the centers */ ++ dist = abs(sel_center_x - cand_center_x) + abs(sel_center_y - cand_center_y); ++ if (dist < closest_dist) { ++ closest_dist = dist; ++ target = c; ++ } ++ } ++ ++ /* If target is found, swap the two clients’ positions in the layout tree */ ++ if (target) { ++ sel_node = find_client_node(selmon->root, sel); ++ target_node = find_client_node(selmon->root, target); ++ if (sel_node && target_node) { ++ tmp = sel_node->client; ++ sel_node->client = target_node->client; ++ target_node->client = tmp; ++ arrange(selmon); ++ } ++ } ++} ++ ++unsigned int ++visible_count(LayoutNode *node, Monitor *m) ++{ ++ Client *c; ++ ++ if (!node) ++ return 0; ++ /* Check if this client is visible. */ ++ if (node->is_client_node) { ++ c = node->client; ++ if (c && VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) ++ return 1; ++ return 0; ++ } ++ /* Else it’s a split node. */ ++ return visible_count(node->left, m) + visible_count(node->right, m); ++} ++ ++Client * ++xytoclient(double x, double y) { ++ Client *c, *closest = NULL; ++ double dist, mindist = INT_MAX, dx, dy; ++ ++ wl_list_for_each_reverse(c, &clients, link) { ++ if (VISIBLEON(c, selmon) && !c->isfloating && !c->isfullscreen && ++ x >= c->geom.x && x <= (c->geom.x + c->geom.width) && ++ y >= c->geom.y && y <= (c->geom.y + c->geom.height)){ ++ return c; ++ } ++ } ++ ++ /* If no client was found at cursor position fallback to closest. */ ++ wl_list_for_each_reverse(c, &clients, link) { ++ if (VISIBLEON(c, selmon) && !c->isfloating && !c->isfullscreen) { ++ dx = 0, dy = 0; ++ ++ if (x < c->geom.x) ++ dx = c->geom.x - x; ++ else if (x > (c->geom.x + c->geom.width)) ++ dx = x - (c->geom.x + c->geom.width); ++ ++ if (y < c->geom.y) ++ dy = c->geom.y - y; ++ else if (y > (c->geom.y + c->geom.height)) ++ dy = y - (c->geom.y + c->geom.height); ++ ++ dist = sqrt(dx * dx + dy * dy); ++ if (dist < mindist) { ++ mindist = dist; ++ closest = c; ++ } ++ } ++ } ++ return closest; ++} +diff --git a/config.def.h b/config.def.h +index 8a6eda0..bc04e3f 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -13,7 +13,10 @@ static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.0f, 0.0f, 0.0f, 1.0f}; /* You can also use glsl colors */ ++static const float resize_factor = 0.0002f; /* Resize multiplier for mouse resizing, depends on mouse sensivity. */ ++static const uint32_t resize_interval_ms = 16; /* Resize interval depends on framerate and screen refresh rate. */ + ++enum Direction { DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN }; + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) + +@@ -30,6 +33,7 @@ static const Rule rules[] = { + /* layout(s) */ + static const Layout layouts[] = { + /* symbol arrange function */ ++ { "|w|", btrtile }, + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, +@@ -144,6 +148,14 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, swapclients, {.i = DIR_UP} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, swapclients, {.i = DIR_DOWN} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, swapclients, {.i = DIR_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, swapclients, {.i = DIR_LEFT} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Right, setratio_h, {.f = +0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Left, setratio_h, {.f = -0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Up, setratio_v, {.f = -0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Down, setratio_v, {.f = +0.025f} }, + TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), + TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), + TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), +diff --git a/dwl.c b/dwl.c +index 8101ffa..e4e7074 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1,6 +1,7 @@ + /* + * See LICENSE file for copyright and license details. + */ ++#include + #include + #include + #include +@@ -104,6 +105,7 @@ typedef struct { + const Arg arg; + } Button; + ++typedef struct LayoutNode LayoutNode; + typedef struct Monitor Monitor; + typedef struct { + /* Must keep this field first */ +@@ -141,8 +143,9 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, was_tiled; + uint32_t resize; /* configure serial of a pending resize */ ++ struct wlr_box old_geom; + } Client; + + typedef struct { +@@ -209,6 +212,7 @@ struct Monitor { + int nmaster; + char ltsymbol[16]; + int asleep; ++ LayoutNode *root; + }; + + typedef struct { +@@ -251,6 +255,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); + static void axisnotify(struct wl_listener *listener, void *data); ++static void btrtile(Monitor *m); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); + static void checkidleinhibitor(struct wlr_surface *exclude); +@@ -333,6 +338,9 @@ static void setmon(Client *c, Monitor *m, uint32_t newtags); + static void setpsel(struct wl_listener *listener, void *data); + static void setsel(struct wl_listener *listener, void *data); + static void setup(void); ++static void setratio_h(const Arg *arg); ++static void setratio_v(const Arg *arg); ++static void swapclients(const Arg *arg); + static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); +@@ -458,6 +466,7 @@ static struct wlr_xwayland *xwayland; + + /* attempt to encapsulate suck into one file */ + #include "client.h" ++#include "btrtile.c" + + /* function implementations */ + void +@@ -628,7 +637,7 @@ buttonpress(struct wl_listener *listener, void *data) + struct wlr_pointer_button_event *event = data; + struct wlr_keyboard *keyboard; + uint32_t mods; +- Client *c; ++ Client *c, *target = NULL; + const Button *b; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); +@@ -649,7 +658,7 @@ buttonpress(struct wl_listener *listener, void *data) + mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; + for (b = buttons; b < END(buttons); b++) { + if (CLEANMASK(mods) == CLEANMASK(b->mod) && +- event->button == b->button && b->func) { ++ event->button == b->button && b->func) { + b->func(&b->arg); + return; + } +@@ -659,6 +668,27 @@ buttonpress(struct wl_listener *listener, void *data) + /* If you released any buttons, we exit interactive move/resize mode. */ + /* TODO: should reset to the pointer focus's current setcursor */ + if (!locked && cursor_mode != CurNormal && cursor_mode != CurPressed) { ++ c = grabc; ++ if (c && c->was_tiled && !strcmp(selmon->ltsymbol, "|w|")) { ++ if (cursor_mode == CurMove && c->isfloating) { ++ target = xytoclient(cursor->x, cursor->y); ++ ++ if (target && !target->isfloating && !target->isfullscreen) ++ insert_client(selmon, target, c); ++ else ++ selmon->root = create_client_node(c); ++ ++ setfloating(c, 0); ++ arrange(selmon); ++ ++ } else if (cursor_mode == CurResize && !c->isfloating) { ++ resizing_from_mouse = 0; ++ } ++ } else { ++ if (cursor_mode == CurResize && resizing_from_mouse) ++ resizing_from_mouse = 0; ++ } ++ /* Default behaviour */ + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + cursor_mode = CurNormal; + /* Drop the window off on its new monitor */ +@@ -750,6 +780,7 @@ cleanupmon(struct wl_listener *listener, void *data) + wlr_output_layout_remove(output_layout, m->wlr_output); + wlr_scene_output_destroy(m->scene_output); + ++ destroy_tree(m); + closemon(m); + wlr_scene_node_destroy(&m->fullscreen_bg->node); + free(m); +@@ -1094,6 +1125,7 @@ createmon(struct wl_listener *listener, void *data) + + wl_list_insert(&mons, &m->link); + printstatus(); ++ init_tree(m); + + /* The xdg-protocol specifies: + * +@@ -1336,6 +1368,10 @@ destroynotify(struct wl_listener *listener, void *data) + wl_list_remove(&c->destroy.link); + wl_list_remove(&c->set_title.link); + wl_list_remove(&c->fullscreen.link); ++ /* We check if the destroyed client was part of any tiled_list, to catch ++ * client removals even if they would not be currently managed by btrtile */ ++ if (selmon && selmon->root) ++ remove_client(selmon, c); + #ifdef XWAYLAND + if (c->type != XDGShell) { + wl_list_remove(&c->activate.link); +@@ -1866,7 +1902,8 @@ void + motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy, + double dx_unaccel, double dy_unaccel) + { +- double sx = 0, sy = 0, sx_confined, sy_confined; ++ int tiled = 0; ++ double sx = 0, sy = 0, sx_confined, sy_confined, dx_total, dy_total; + Client *c = NULL, *w = NULL; + LayerSurface *l = NULL; + struct wlr_surface *surface = NULL; +@@ -1920,18 +1957,56 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d + /* Update drag icon's position */ + wlr_scene_node_set_position(&drag_icon->node, (int)round(cursor->x), (int)round(cursor->y)); + +- /* If we are currently grabbing the mouse, handle and return */ ++ /* Skip if internal call or already resizing */ ++ if (time == 0 && resizing_from_mouse) ++ goto focus; ++ ++ tiled = grabc && !grabc->isfloating && !grabc->isfullscreen; + if (cursor_mode == CurMove) { + /* Move the grabbed client to the new position. */ +- resize(grabc, (struct wlr_box){.x = (int)round(cursor->x) - grabcx, .y = (int)round(cursor->y) - grabcy, +- .width = grabc->geom.width, .height = grabc->geom.height}, 1); +- return; ++ if (grabc && grabc->isfloating) { ++ resize(grabc, (struct wlr_box){ ++ .x = (int)round(cursor->x) - grabcx, ++ .y = (int)round(cursor->y) - grabcy, ++ .width = grabc->geom.width, ++ .height = grabc->geom.height ++ }, 1); ++ return; ++ } + } else if (cursor_mode == CurResize) { +- resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, +- .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1); +- return; ++ if (tiled && resizing_from_mouse) { ++ dx_total = cursor->x - resize_last_update_x; ++ dy_total = cursor->y - resize_last_update_y; ++ ++ if (time - last_resize_time >= resize_interval_ms) { ++ Arg a = {0}; ++ if (fabs(dx_total) > fabs(dy_total)) { ++ a.f = (float)(dx_total * resize_factor); ++ setratio_h(&a); ++ } else { ++ a.f = (float)(dy_total * resize_factor); ++ setratio_v(&a); ++ } ++ arrange(selmon); ++ ++ last_resize_time = time; ++ resize_last_update_x = cursor->x; ++ resize_last_update_y = cursor->y; ++ } ++ ++ } else if (grabc && grabc->isfloating) { ++ /* Floating resize as original */ ++ resize(grabc, (struct wlr_box){ ++ .x = grabc->geom.x, ++ .y = grabc->geom.y, ++ .width = (int)round(cursor->x) - grabc->geom.x, ++ .height = (int)round(cursor->y) - grabc->geom.y ++ }, 1); ++ return; ++ } + } + ++focus: + /* If there's no client surface under the cursor, set the cursor image to a + * default. This is what makes the cursor image appear when you move it + * off of a client or over its border. */ +@@ -1965,22 +2040,41 @@ moveresize(const Arg *arg) + if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen) + return; + +- /* Float the window and tell motionnotify to grab it */ +- setfloating(grabc, 1); +- switch (cursor_mode = arg->ui) { +- case CurMove: +- grabcx = (int)round(cursor->x) - grabc->geom.x; +- grabcy = (int)round(cursor->y) - grabc->geom.y; +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "all-scroll"); +- break; +- case CurResize: +- /* Doesn't work for X11 output - the next absolute motion event +- * returns the cursor to where it started */ +- wlr_cursor_warp_closest(cursor, NULL, +- grabc->geom.x + grabc->geom.width, +- grabc->geom.y + grabc->geom.height); +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); +- break; ++ cursor_mode = arg->ui; ++ grabc->was_tiled = (!grabc->isfloating && !grabc->isfullscreen); ++ ++ if (grabc->was_tiled) { ++ switch (cursor_mode) { ++ case CurMove: ++ setfloating(grabc, 1); ++ grabcx = (int)round(cursor->x) - grabc->geom.x; ++ grabcy = (int)round(cursor->y) - grabc->geom.y; ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); ++ break; ++ case CurResize: ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); ++ resize_last_update_x = cursor->x; ++ resize_last_update_y = cursor->y; ++ resizing_from_mouse = 1; ++ break; ++ } ++ } else { ++ /* Default floating logic */ ++ /* Float the window and tell motionnotify to grab it */ ++ setfloating(grabc, 1); ++ switch (cursor_mode) { ++ case CurMove: ++ grabcx = (int)round(cursor->x) - grabc->geom.x; ++ grabcy = (int)round(cursor->y) - grabc->geom.y; ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); ++ break; ++ case CurResize: ++ wlr_cursor_warp_closest(cursor, NULL, ++ grabc->geom.x + grabc->geom.width, ++ grabc->geom.y + grabc->geom.height); ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); ++ break; ++ } + } + } + +-- +2.52.0 + diff --git a/patches/btrtile/btrtile-v0.7.patch b/patches/btrtile/btrtile-wlroots-next-d41ecb7.patch similarity index 89% rename from patches/btrtile/btrtile-v0.7.patch rename to patches/btrtile/btrtile-wlroots-next-d41ecb7.patch index 0396500..87f4e99 100644 --- a/patches/btrtile/btrtile-v0.7.patch +++ b/patches/btrtile/btrtile-wlroots-next-d41ecb7.patch @@ -1,30 +1,32 @@ -From b9789420f166c20579f29ecd171a8956c681848d Mon Sep 17 00:00:00 2001 +From 912701a41b19c013a9adcb176a7b6503aa1cf9c1 Mon Sep 17 00:00:00 2001 From: julmajustus -Date: Thu, 13 Feb 2025 23:23:40 +0200 -Subject: [PATCH] btrtile with multi-tag support +Date: Thu, 19 Mar 2026 22:56:45 +0200 +Subject: [PATCH] Refactor btrtile to wlroots-next --- - btrtile.c | 563 +++++++++++++++++++++++++++++++++++++++++++++++++++ + btrtile.c | 565 +++++++++++++++++++++++++++++++++++++++++++++++++++ config.def.h | 12 ++ - dwl.c | 152 +++++++++++--- - 3 files changed, 698 insertions(+), 29 deletions(-) + dwl.c | 148 +++++++++++--- + 3 files changed, 698 insertions(+), 27 deletions(-) create mode 100644 btrtile.c diff --git a/btrtile.c b/btrtile.c new file mode 100644 -index 0000000..03f4680 +index 0000000..d706c3b --- /dev/null +++ b/btrtile.c -@@ -0,0 +1,563 @@ +@@ -0,0 +1,565 @@ +/* ************************************************************************** */ -+/* */ -+/* ::: :::::::: */ -+/* btrtile.c :+: :+: :+: */ -+/* +:+ +:+ +:+ */ -+/* By: jmakkone +#+ +:+ +#+ */ -+/* +#+#+#+#+#+ +#+ */ -+/* Created: 2024/12/15 00:26:07 by jmakkone #+# #+# */ -+/* Updated: 2025/02/13 23:22:33 by jmakkone ### ########.fr */ ++/* @@@ @@@@@@@@ */ ++/* @@@ @@@@@@@@@@ */ ++/* @@! @@! @@@@ */ ++/* !@! !@! @!@!@ */ ++/* btrtile.c @!! @!@ @! !@! */ ++/* !!! !@!!! !!! */ ++/* By: julmajustus !!: !!:! !!! */ ++/* ::! :!: !:! */ ++/* Created: 2024/12/15 00:26:07 by julmajustus :: ::::::: :: */ ++/* Updated: 2026/03/18 20:26:17 by julmajustus : : : : : : */ +/* */ +/* ************************************************************************** */ + @@ -580,21 +582,21 @@ index 0000000..03f4680 + return closest; +} diff --git a/config.def.h b/config.def.h -index 22d2171..92f3ad6 100644 +index 8a6eda0..bc04e3f 100644 --- a/config.def.h +++ b/config.def.h @@ -13,7 +13,10 @@ static const float focuscolor[] = COLOR(0x005577ff); static const float urgentcolor[] = COLOR(0xff0000ff); /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ - static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ -+static const float resize_factor = 0.0002f; /* Resize multiplier for mouse resizing, depends on mouse sensitivity. */ + static const float fullscreen_bg[] = {0.0f, 0.0f, 0.0f, 1.0f}; /* You can also use glsl colors */ ++static const float resize_factor = 0.0002f; /* Resize multiplier for mouse resizing, depends on mouse sensivity. */ +static const uint32_t resize_interval_ms = 16; /* Resize interval depends on framerate and screen refresh rate. */ +enum Direction { DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN }; /* tagging - TAGCOUNT must be no greater than 31 */ #define TAGCOUNT (9) -@@ -31,6 +34,7 @@ static const Rule rules[] = { +@@ -30,6 +33,7 @@ static const Rule rules[] = { /* layout(s) */ static const Layout layouts[] = { /* symbol arrange function */ @@ -602,23 +604,23 @@ index 22d2171..92f3ad6 100644 { "[]=", tile }, { "><>", NULL }, /* no layout function means floating behavior */ { "[M]", monocle }, -@@ -148,6 +152,14 @@ static const Key keys[] = { - { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, - { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, - { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, -+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, swapclients, {.i = DIR_UP} }, -+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, swapclients, {.i = DIR_DOWN} }, -+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, swapclients, {.i = DIR_RIGHT} }, -+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, swapclients, {.i = DIR_LEFT} }, -+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Right, setratio_h, {.f = +0.025f} }, -+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Left, setratio_h, {.f = -0.025f} }, -+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Up, setratio_v, {.f = -0.025f} }, -+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Down, setratio_v, {.f = +0.025f} }, - TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), - TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), - TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), +@@ -144,6 +148,14 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, swapclients, {.i = DIR_UP} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, swapclients, {.i = DIR_DOWN} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, swapclients, {.i = DIR_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, swapclients, {.i = DIR_LEFT} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Right, setratio_h, {.f = +0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Left, setratio_h, {.f = -0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Up, setratio_v, {.f = -0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Down, setratio_v, {.f = +0.025f} }, + TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), + TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), + TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), diff --git a/dwl.c b/dwl.c -index a2711f6..e49a061 100644 +index 8101ffa..e4e7074 100644 --- a/dwl.c +++ b/dwl.c @@ -1,6 +1,7 @@ @@ -629,15 +631,15 @@ index a2711f6..e49a061 100644 #include #include #include -@@ -103,6 +104,7 @@ typedef struct { +@@ -104,6 +105,7 @@ typedef struct { const Arg arg; } Button; +typedef struct LayoutNode LayoutNode; typedef struct Monitor Monitor; typedef struct { - /* Must keep these three elements in this order */ -@@ -139,8 +141,9 @@ typedef struct { + /* Must keep this field first */ +@@ -141,8 +143,9 @@ typedef struct { #endif unsigned int bw; uint32_t tags; @@ -648,7 +650,7 @@ index a2711f6..e49a061 100644 } Client; typedef struct { -@@ -208,6 +211,7 @@ struct Monitor { +@@ -209,6 +212,7 @@ struct Monitor { int nmaster; char ltsymbol[16]; int asleep; @@ -656,7 +658,7 @@ index a2711f6..e49a061 100644 }; typedef struct { -@@ -250,6 +254,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list, +@@ -251,6 +255,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list, struct wlr_box *usable_area, int exclusive); static void arrangelayers(Monitor *m); static void axisnotify(struct wl_listener *listener, void *data); @@ -674,7 +676,7 @@ index a2711f6..e49a061 100644 static void spawn(const Arg *arg); static void startdrag(struct wl_listener *listener, void *data); static void tag(const Arg *arg); -@@ -431,6 +439,7 @@ static xcb_atom_t netatom[NetLast]; +@@ -458,6 +466,7 @@ static struct wlr_xwayland *xwayland; /* attempt to encapsulate suck into one file */ #include "client.h" @@ -682,7 +684,7 @@ index a2711f6..e49a061 100644 /* function implementations */ void -@@ -601,7 +610,7 @@ buttonpress(struct wl_listener *listener, void *data) +@@ -628,7 +637,7 @@ buttonpress(struct wl_listener *listener, void *data) struct wlr_pointer_button_event *event = data; struct wlr_keyboard *keyboard; uint32_t mods; @@ -691,7 +693,7 @@ index a2711f6..e49a061 100644 const Button *b; wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); -@@ -622,7 +631,7 @@ buttonpress(struct wl_listener *listener, void *data) +@@ -649,7 +658,7 @@ buttonpress(struct wl_listener *listener, void *data) mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; for (b = buttons; b < END(buttons); b++) { if (CLEANMASK(mods) == CLEANMASK(b->mod) && @@ -700,9 +702,9 @@ index a2711f6..e49a061 100644 b->func(&b->arg); return; } -@@ -632,15 +641,36 @@ buttonpress(struct wl_listener *listener, void *data) +@@ -659,6 +668,27 @@ buttonpress(struct wl_listener *listener, void *data) /* If you released any buttons, we exit interactive move/resize mode. */ - /* TODO should reset to the pointer focus's current setcursor */ + /* TODO: should reset to the pointer focus's current setcursor */ if (!locked && cursor_mode != CurNormal && cursor_mode != CurPressed) { + c = grabc; + if (c && c->was_tiled && !strcmp(selmon->ltsymbol, "|w|")) { @@ -728,18 +730,7 @@ index a2711f6..e49a061 100644 wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); cursor_mode = CurNormal; /* Drop the window off on its new monitor */ - selmon = xytomon(cursor->x, cursor->y); - setmon(grabc, selmon, 0); -+ grabc = NULL; - return; -- } else { -- cursor_mode = CurNormal; - } -+ cursor_mode = CurNormal; - break; - } - /* If the event wasn't handled by the compositor, notify the client with -@@ -720,6 +750,7 @@ cleanupmon(struct wl_listener *listener, void *data) +@@ -750,6 +780,7 @@ cleanupmon(struct wl_listener *listener, void *data) wlr_output_layout_remove(output_layout, m->wlr_output); wlr_scene_output_destroy(m->scene_output); @@ -747,7 +738,7 @@ index a2711f6..e49a061 100644 closemon(m); wlr_scene_node_destroy(&m->fullscreen_bg->node); free(m); -@@ -1024,6 +1055,7 @@ createmon(struct wl_listener *listener, void *data) +@@ -1094,6 +1125,7 @@ createmon(struct wl_listener *listener, void *data) wl_list_insert(&mons, &m->link); printstatus(); @@ -755,7 +746,7 @@ index a2711f6..e49a061 100644 /* The xdg-protocol specifies: * -@@ -1263,6 +1295,10 @@ destroynotify(struct wl_listener *listener, void *data) +@@ -1336,6 +1368,10 @@ destroynotify(struct wl_listener *listener, void *data) wl_list_remove(&c->destroy.link); wl_list_remove(&c->set_title.link); wl_list_remove(&c->fullscreen.link); @@ -766,7 +757,7 @@ index a2711f6..e49a061 100644 #ifdef XWAYLAND if (c->type != XDGShell) { wl_list_remove(&c->activate.link); -@@ -1809,7 +1845,8 @@ void +@@ -1866,7 +1902,8 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy, double dx_unaccel, double dy_unaccel) { @@ -776,7 +767,7 @@ index a2711f6..e49a061 100644 Client *c = NULL, *w = NULL; LayerSurface *l = NULL; struct wlr_surface *surface = NULL; -@@ -1863,18 +1900,56 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d +@@ -1920,18 +1957,56 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d /* Update drag icon's position */ wlr_scene_node_set_position(&drag_icon->node, (int)round(cursor->x), (int)round(cursor->y)); @@ -840,7 +831,7 @@ index a2711f6..e49a061 100644 /* If there's no client surface under the cursor, set the cursor image to a * default. This is what makes the cursor image appear when you move it * off of a client or over its border. */ -@@ -1908,22 +1983,41 @@ moveresize(const Arg *arg) +@@ -1965,22 +2040,41 @@ moveresize(const Arg *arg) if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen) return; @@ -850,7 +841,7 @@ index a2711f6..e49a061 100644 - case CurMove: - grabcx = (int)round(cursor->x) - grabc->geom.x; - grabcy = (int)round(cursor->y) - grabc->geom.y; -- wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "all-scroll"); - break; - case CurResize: - /* Doesn't work for X11 output - the next absolute motion event @@ -899,5 +890,5 @@ index a2711f6..e49a061 100644 } -- -2.45.3 +2.52.0 From 039be1a6cf0c17832f2413f2eea856d28485163b Mon Sep 17 00:00:00 2001 From: nate zhou Date: Mon, 23 Mar 2026 01:34:20 +0800 Subject: [PATCH 3/5] Add: shifttag Shift to next/previous tag, with skipping occupied/unoccupied variants. This patch is an alternative to `shiftview` patch with more controls: - `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 patch. --- patches/shifttag/README.md | 22 +++++ patches/shifttag/shifttag-bar.patch | 146 ++++++++++++++++++++++++++++ patches/shifttag/shifttag.patch | 145 +++++++++++++++++++++++++++ 3 files changed, 313 insertions(+) create mode 100644 patches/shifttag/README.md create mode 100644 patches/shifttag/shifttag-bar.patch create mode 100644 patches/shifttag/shifttag.patch 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 + From 24fa6e04cecfde11fa62cf9b9f5853160c5e863b Mon Sep 17 00:00:00 2001 From: nate zhou Date: Mon, 23 Mar 2026 20:24:42 +0800 Subject: [PATCH 4/5] shifttag: fix shifting to unoccupied with multiple tags --- patches/shifttag/shifttag-bar.patch | 98 +++++++++++++++++------------ patches/shifttag/shifttag.patch | 98 +++++++++++++++++------------ 2 files changed, 114 insertions(+), 82 deletions(-) diff --git a/patches/shifttag/shifttag-bar.patch b/patches/shifttag/shifttag-bar.patch index 863b8e6..df781b5 100644 --- a/patches/shifttag/shifttag-bar.patch +++ b/patches/shifttag/shifttag-bar.patch @@ -1,13 +1,13 @@ -From 6d3a0506f45243cf11f0f7ba06048f453576567c Mon Sep 17 00:00:00 2001 +From 8dff34be15664705654e5e4d41f990f8eba8b79d 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(+) + config.def.h | 8 ++++ + shifttag.c | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 123 insertions(+) create mode 100644 shifttag.c diff --git a/config.def.h b/config.def.h @@ -38,10 +38,10 @@ index 7da50d2..9fbcf2a 100644 { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, diff --git a/shifttag.c b/shifttag.c new file mode 100644 -index 0000000..2615f4c +index 0000000..5f1d5c2 --- /dev/null +++ b/shifttag.c -@@ -0,0 +1,99 @@ +@@ -0,0 +1,115 @@ +// "arg->i" stores the number of tags to shift right (positive value) +// or left (negative value) + @@ -61,45 +61,61 @@ index 0000000..2615f4c +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; ++ uint32_t result = 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)); ++ for (int i = 0; i < LENGTH(tags); i++) { ++ uint32_t bit = 1 << i; ++ ++ if (current_tag & bit) { ++ uint32_t new_bit = bit; ++ ++ if (!skip_unoccupied && !skip_occupied) { ++ if (direction > 0) { ++ if (bit << 1 && (bit << 1) <= TAGMASK) ++ new_bit = bit << 1; ++ else ++ new_bit = 1; ++ } else { ++ if (bit >> 1) ++ new_bit = bit >> 1; ++ else ++ new_bit = 1 << (LENGTH(tags) - 1); ++ } ++ } else { ++ uint32_t test_bit = bit; ++ uint32_t start_bit = bit; ++ int count = 0; ++ ++ do { ++ if (direction > 0) { ++ if (test_bit << 1 && (test_bit << 1) <= TAGMASK) ++ test_bit = test_bit << 1; ++ else ++ test_bit = 1; ++ } else { ++ if (test_bit >> 1) ++ test_bit = test_bit >> 1; ++ else ++ test_bit = 1 << (LENGTH(tags) - 1); ++ } ++ ++ int is_occupied = (occupied & test_bit) != 0; ++ int should_select = (skip_unoccupied && is_occupied) || ++ (skip_occupied && !is_occupied); ++ ++ if (should_select) { ++ new_bit = test_bit; ++ break; ++ } ++ count++; ++ } while (test_bit != start_bit && count < LENGTH(tags)); ++ } ++ ++ result |= new_bit; + } -+ 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; ++ return result; +} + +static void diff --git a/patches/shifttag/shifttag.patch b/patches/shifttag/shifttag.patch index 82ccf15..1220a8f 100644 --- a/patches/shifttag/shifttag.patch +++ b/patches/shifttag/shifttag.patch @@ -1,12 +1,12 @@ -From 49b0da41f1f28a60cf216f5bffecc0ce1ea1ec9c Mon Sep 17 00:00:00 2001 +From 2a4417a6344c9d4b83989d63df17d9cdb0f81aea 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(+) + config.def.h | 8 ++++ + shifttag.c | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 123 insertions(+) create mode 100644 shifttag.c diff --git a/config.def.h b/config.def.h @@ -37,10 +37,10 @@ index 8a6eda0..2377fa5 100644 { MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} }, diff --git a/shifttag.c b/shifttag.c new file mode 100644 -index 0000000..5aeee00 +index 0000000..971216b --- /dev/null +++ b/shifttag.c -@@ -0,0 +1,99 @@ +@@ -0,0 +1,115 @@ +// "arg->i" stores the number of tags to shift right (positive value) +// or left (negative value) + @@ -60,45 +60,61 @@ index 0000000..5aeee00 +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; ++ uint32_t result = 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)); ++ for (int i = 0; i < TAGCOUNT; i++) { ++ uint32_t bit = 1 << i; ++ ++ if (current_tag & bit) { ++ uint32_t new_bit = bit; ++ ++ if (!skip_unoccupied && !skip_occupied) { ++ if (direction > 0) { ++ if (bit << 1 && (bit << 1) <= TAGMASK) ++ new_bit = bit << 1; ++ else ++ new_bit = 1; ++ } else { ++ if (bit >> 1) ++ new_bit = bit >> 1; ++ else ++ new_bit = 1 << (TAGCOUNT - 1); ++ } ++ } else { ++ uint32_t test_bit = bit; ++ uint32_t start_bit = bit; ++ int count = 0; ++ ++ do { ++ if (direction > 0) { ++ if (test_bit << 1 && (test_bit << 1) <= TAGMASK) ++ test_bit = test_bit << 1; ++ else ++ test_bit = 1; ++ } else { ++ if (test_bit >> 1) ++ test_bit = test_bit >> 1; ++ else ++ test_bit = 1 << (TAGCOUNT - 1); ++ } ++ ++ int is_occupied = (occupied & test_bit) != 0; ++ int should_select = (skip_unoccupied && is_occupied) || ++ (skip_occupied && !is_occupied); ++ ++ if (should_select) { ++ new_bit = test_bit; ++ break; ++ } ++ count++; ++ } while (test_bit != start_bit && count < TAGCOUNT); ++ } ++ ++ result |= new_bit; + } -+ 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; ++ return result; +} + +static void From 998808b30367ddf630f44c5e838463f13406b28f Mon Sep 17 00:00:00 2001 From: nate zhou Date: Sun, 8 Mar 2026 10:58:28 +0800 Subject: [PATCH 5/5] sticky: update for 0.8 --- patches/sticky/README.md | 3 ++- patches/sticky/sticky.patch | 28 ++++++++++++++-------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/patches/sticky/README.md b/patches/sticky/README.md index af0e81f..4f1b36d 100644 --- a/patches/sticky/README.md +++ b/patches/sticky/README.md @@ -4,7 +4,8 @@ Adds a toggleable function that makes a sticky client that is visible on all tag Originally based on [dwm sticky patch](https://dwm.suckless.org/patches/sticky). ### Download -- [2024-07-26](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/sticky/sticky.patch) +- [v0.8](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/sticky/sticky.patch) +- [v0.7](https://codeberg.org/dwl/dwl-patches/raw/commit/898bc7a946c7be14034a665bd7a7b042632465ea/patches/sticky/sticky.patch) - [v0.4](https://github.com/djpohly/dwl/compare/main...dm1tz:04-sticky.patch) - [git branch](https://codeberg.org/Rutherther/dwl/src/branch/v0.7/sticky) diff --git a/patches/sticky/sticky.patch b/patches/sticky/sticky.patch index ece8308..f0cd55d 100644 --- a/patches/sticky/sticky.patch +++ b/patches/sticky/sticky.patch @@ -1,17 +1,17 @@ -From f113cdc0b4cecceaaf28679489852ae61a1aa3f5 Mon Sep 17 00:00:00 2001 -From: Rutherther -Date: Fri, 19 Jul 2024 16:29:43 +0200 -Subject: [PATCH] sticky +From d36eee35fdc4b0e666fb04a580200e8c5d2b1f9e Mon Sep 17 00:00:00 2001 +From: nate zhou +Date: Sat, 28 Feb 2026 22:55:33 +0800 +Subject: [PATCH] Update sticky patch to v0.8 --- dwl.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/dwl.c b/dwl.c -index 5bf995e..820f4af 100644 +index 44f3ad9..05070ef 100644 --- a/dwl.c +++ b/dwl.c -@@ -73,7 +73,7 @@ +@@ -74,7 +74,7 @@ #define MAX(A, B) ((A) > (B) ? (A) : (B)) #define MIN(A, B) ((A) < (B) ? (A) : (B)) #define CLEANMASK(mask) (mask & ~WLR_MODIFIER_CAPS) @@ -20,7 +20,7 @@ index 5bf995e..820f4af 100644 #define LENGTH(X) (sizeof X / sizeof X[0]) #define END(A) ((A) + LENGTH(A)) #define TAGMASK ((1u << TAGCOUNT) - 1) -@@ -139,7 +139,7 @@ typedef struct { +@@ -137,7 +137,7 @@ typedef struct { #endif unsigned int bw; uint32_t tags; @@ -29,15 +29,15 @@ index 5bf995e..820f4af 100644 uint32_t resize; /* configure serial of a pending resize */ } Client; -@@ -326,6 +326,7 @@ static void setcursor(struct wl_listener *listener, void *data); +@@ -323,6 +323,7 @@ static void setcursor(struct wl_listener *listener, void *data); static void setcursorshape(struct wl_listener *listener, void *data); static void setfloating(Client *c, int floating); static void setfullscreen(Client *c, int fullscreen); +static void setsticky(Client *c, int sticky); - static void setgamma(struct wl_listener *listener, void *data); static void setlayout(const Arg *arg); static void setmfact(const Arg *arg); -@@ -339,6 +340,7 @@ static void tag(const Arg *arg); + static void setmon(Client *c, Monitor *m, uint32_t newtags); +@@ -335,6 +336,7 @@ static void tag(const Arg *arg); static void tagmon(const Arg *arg); static void tile(Monitor *m); static void togglefloating(const Arg *arg); @@ -45,8 +45,8 @@ index 5bf995e..820f4af 100644 static void togglefullscreen(const Arg *arg); static void toggletag(const Arg *arg); static void toggleview(const Arg *arg); -@@ -2351,6 +2353,17 @@ setgamma(struct wl_listener *listener, void *data) - wlr_output_schedule_frame(m->wlr_output); +@@ -2368,6 +2370,17 @@ setfullscreen(Client *c, int fullscreen) + printstatus(); } +void @@ -63,7 +63,7 @@ index 5bf995e..820f4af 100644 void setlayout(const Arg *arg) { -@@ -2738,6 +2751,16 @@ togglefullscreen(const Arg *arg) +@@ -2760,6 +2773,16 @@ togglefullscreen(const Arg *arg) setfullscreen(sel, !sel->isfullscreen); } @@ -81,5 +81,5 @@ index 5bf995e..820f4af 100644 toggletag(const Arg *arg) { -- -2.45.2 +2.53.0