commit 1b881c96fc17f257b235684704470bee442cc559
parent 85878e73f4d115bc4b223f1912d6408f5df8df93
Author: Lucy <tkf.x1os@gmail.com>
Date: Mon, 23 Mar 2026 17:29:26 +1300
Added a new "canvas" patch for dwm.
Diffstat:
2 files changed, 491 insertions(+), 0 deletions(-)
diff --git a/dwm.suckless.org/patches/canvas/dwm-canvas-6.2.diff b/dwm.suckless.org/patches/canvas/dwm-canvas-6.2.diff
@@ -0,0 +1,472 @@
+diff --git a/config.def.h b/config.def.h
+index 81c3fc0..9154d29 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -18,6 +18,9 @@ static const char *colors[][3] = {
+ [SchemeSel] = { col_gray4, col_cyan, col_cyan },
+ };
+
++#define MOVE_CANVAS_STEP 120
++#define COORDINATES_DIVISOR 10
++
+ /* tagging */
+ static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" };
+
+@@ -86,6 +89,12 @@ static const Key keys[] = {
+ { MODKEY, XK_period, focusmon, {.i = +1 } },
+ { MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } },
+ { MODKEY|ShiftMask, XK_period, tagmon, {.i = +1 } },
++ { MODKEY, XK_r, homecanvas, {0} }, // Return to x:0, y:0 position
++ { MODKEY|ShiftMask, XK_Left, movecanvas, {.i = 0} }, // Move your position to left
++ { MODKEY|ShiftMask, XK_Right, movecanvas, {.i = 1} }, // Move your position to right
++ { MODKEY|ShiftMask, XK_Up, movecanvas, {.i = 2} }, // Move your position up
++ { MODKEY|ShiftMask, XK_Down, movecanvas, {.i = 3} }, // Move your position down
++ { MODKEY|ShiftMask, XK_d, centerwindow, {0} },
+ TAGKEYS( XK_1, 0)
+ TAGKEYS( XK_2, 1)
+ TAGKEYS( XK_3, 2)
+@@ -113,5 +122,7 @@ static const Button buttons[] = {
+ { ClkTagBar, 0, Button3, toggleview, {0} },
+ { ClkTagBar, MODKEY, Button1, tag, {0} },
+ { ClkTagBar, MODKEY, Button3, toggletag, {0} },
++ { ClkRootWin, MODKEY|ShiftMask, Button1, manuallymovecanvas, {0} },
++ { ClkClientWin, MODKEY|ShiftMask, Button1, manuallymovecanvas, {0} }
+ };
+
+diff --git a/drw.o b/drw.o
+new file mode 100644
+index 0000000..522c426
+Binary files /dev/null and b/drw.o differ
+diff --git a/dwm.c b/dwm.c
+index ab3a84c..c70ff19 100644
+--- a/dwm.c
++++ b/dwm.c
+@@ -56,6 +56,11 @@
+ #define TAGMASK ((1 << LENGTH(tags)) - 1)
+ #define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad)
+
++#if !WINDOWMAP
++ #undef WINDOWMAP
++ #define WINDOWMAP 1
++#endif
++
+ /* enums */
+ enum { CurNormal, CurResize, CurMove, CurLast }; /* cursor */
+ enum { SchemeNorm, SchemeSel }; /* color schemes */
+@@ -96,6 +101,10 @@ struct Client {
+ Client *snext;
+ Monitor *mon;
+ Window win;
++ int saved_cx, saved_cy;
++ int saved_cw, saved_ch;
++ int was_on_canvas;
++ int ismapped;
+ };
+
+ typedef struct {
+@@ -110,6 +119,11 @@ typedef struct {
+ void (*arrange)(Monitor *);
+ } Layout;
+
++typedef struct {
++ int cx, cy;
++ int saved_cx, saved_cy;
++} CanvasOffset;
++
+ struct Monitor {
+ char ltsymbol[16];
+ float mfact;
+@@ -129,6 +143,7 @@ struct Monitor {
+ Monitor *next;
+ Window barwin;
+ const Layout *lt[2];
++ CanvasOffset *canvas;
+ };
+
+ typedef struct {
+@@ -267,8 +282,11 @@ static Drw *drw;
+ static Monitor *mons, *selmon;
+ static Window root, wmcheckwin;
+
++#include "infinitetags.h"
++
+ /* configuration, allows nested code to access above variables */
+ #include "config.h"
++#include "infinitetags.c"
+
+ /* compile-time check if all tags fit into an unsigned int bit array. */
+ struct NumTags { char limitexceeded[LENGTH(tags) > 31 ? -1 : 1]; };
+@@ -643,6 +661,12 @@ createmon(void)
+ m->lt[0] = &layouts[0];
+ m->lt[1] = &layouts[1 % LENGTH(layouts)];
+ strncpy(m->ltsymbol, layouts[0].symbol, sizeof m->ltsymbol);
++ m->canvas = ecalloc(LENGTH(tags), sizeof(CanvasOffset));
++ unsigned int i;
++ for (i = 0; i < LENGTH(tags); i++){
++ m->canvas[i].cx = 0;
++ m->canvas[i].cy = 0;
++ }
+ return m;
+ }
+
+@@ -719,6 +743,7 @@ drawbar(Monitor *m)
+ urg |= c->tags;
+ }
+ x = 0;
++
+ for (i = 0; i < LENGTH(tags); i++) {
+ w = TEXTW(tags[i]);
+ drw_setscheme(drw, scheme[m->tagset[m->seltags] & 1 << i ? SchemeSel : SchemeNorm]);
+@@ -732,6 +757,20 @@ drawbar(Monitor *m)
+ w = TEXTW(m->ltsymbol);
+ drw_setscheme(drw, scheme[SchemeNorm]);
+ x = drw_text(drw, x, 0, w, bh, lrpad / 2, m->ltsymbol, 0);
++
++ /* Draw the coordinates in canvas mode */
++ if (selmon->lt[selmon->sellt]->arrange == NULL){
++ int tagidx = getcurrenttag(m);
++ char coords[64];
++ snprintf(coords, sizeof(coords), "[x%d y%d]",
++ m->canvas[tagidx].cx / COORDINATES_DIVISOR,
++ m->canvas[tagidx].cy / COORDINATES_DIVISOR);
++ w = TEXTW(coords);
++ drw_setscheme(drw, scheme[SchemeNorm]);
++ drw_text(drw, x, 0, w, bh, lrpad / 2, coords, 0);
++ x += w;
++ }
++
+
+ if ((w = m->ww - tw - x) > bh) {
+ if (m->sel) {
+@@ -744,6 +783,7 @@ drawbar(Monitor *m)
+ drw_rect(drw, x, 0, w, bh, 1, 1);
+ }
+ }
++
+ drw_map(drw, m->barwin, 0, 0, m->ww, bh);
+ }
+
+@@ -856,7 +896,7 @@ focusstack(const Arg *arg)
+ }
+ if (c) {
+ focus(c);
+- restack(selmon);
++ centerwindow(NULL);
+ }
+ }
+
+@@ -1510,10 +1550,32 @@ setfullscreen(Client *c, int fullscreen)
+ void
+ setlayout(const Arg *arg)
+ {
++ const Layout *temp_new_layout = (arg && arg->v) ? (Layout *)arg->v : selmon->lt[selmon->sellt ^ 1];
++ if (temp_new_layout == selmon->lt[selmon->sellt]) return;
++
++ const Layout *old_layout = selmon->lt[selmon->sellt];
++
+ if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt])
+ selmon->sellt ^= 1;
+ if (arg && arg->v)
+ selmon->lt[selmon->sellt] = (Layout *)arg->v;
++
++ const Layout *new_layout = selmon->lt[selmon->sellt];
++ if (old_layout->arrange == NULL && new_layout->arrange != NULL) {
++ save_canvas_positions(selmon);
++ homecanvas(NULL);
++ Client *c;
++ for (c = selmon->clients; c; c = c->next)
++ if (!c->isfixed) c->isfloating = 0;
++ }
++
++ if (new_layout->arrange == NULL) {
++ restore_canvas_positions(selmon);
++ Client *c;
++ for (c = selmon->clients; c; c = c->next)
++ c->isfloating = 1;
++ }
++
+ strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, sizeof selmon->ltsymbol);
+ if (selmon->sel)
+ arrange(selmon);
+@@ -1670,6 +1732,21 @@ void
+ tag(const Arg *arg)
+ {
+ if (selmon->sel && arg->ui & TAGMASK) {
++ Client *c = selmon->sel;
++ unsigned int target_tag_mask = arg->ui & TAGMASK;
++ int i;
++
++ for (i = 0; i < LENGTH(tags); i++) {
++ if (target_tag_mask & (1 << i)) {
++ c->saved_cx = selmon->canvas[i].cx + (selmon->ww - WIDTH(c)) / 2;
++ c->saved_cy = selmon->canvas[i].cy + (selmon->wh - HEIGHT(c)) / 2;
++ c->saved_cw = c->w;
++ c->saved_ch = c->h;
++ c->was_on_canvas = 1;
++ break;
++ }
++ }
++
+ selmon->sel->tags = arg->ui & TAGMASK;
+ focus(NULL);
+ arrange(selmon);
+@@ -2053,11 +2130,26 @@ updatewmhints(Client *c)
+ void
+ view(const Arg *arg)
+ {
++ if (selmon->lt[selmon->sellt]->arrange == NULL)
++ save_canvas_positions(selmon);
++
+ if ((arg->ui & TAGMASK) == selmon->tagset[selmon->seltags])
+ return;
+ selmon->seltags ^= 1; /* toggle sel tagset */
+ if (arg->ui & TAGMASK)
+ selmon->tagset[selmon->seltags] = arg->ui & TAGMASK;
++
++ int newtag = getcurrenttag(selmon);
++ if (selmon->lt[selmon->sellt]->arrange != NULL){
++ selmon->canvas[newtag].cx = 0;
++ selmon->canvas[newtag].cy = 0;
++ } else {
++ restore_canvas_positions(selmon);
++ Client *c;
++ for (c = selmon->clients;c; c = c->next)
++ if (ISVISIBLE(c))
++ c->isfloating = 1;
++ }
+ focus(NULL);
+ arrange(selmon);
+ }
+diff --git a/dwm.o b/dwm.o
+new file mode 100644
+index 0000000..65173d6
+Binary files /dev/null and b/dwm.o differ
+diff --git a/infinitetags.c b/infinitetags.c
+new file mode 100644
+index 0000000..46267ee
+--- /dev/null
++++ b/infinitetags.c
+@@ -0,0 +1,206 @@
++int
++getcurrenttag(Monitor *m) {
++ unsigned int i;
++ for (i = 0; i < LENGTH(tags) && !(m->tagset[m->seltags] & (1 << i)); i++);
++ return i < LENGTH(tags) ? i : 0;
++}
++
++void
++homecanvas(const Arg *arg) {
++ Client *c;
++ int tagidx = getcurrenttag(selmon);
++ int cx = selmon->canvas[tagidx].cx;
++ int cy = selmon->canvas[tagidx].cy;
++
++ for (c = selmon->clients; c; c = c->next) {
++ if (c->tags & (1 << tagidx)) {
++ c->x -= cx;
++ c->y -= cy;
++ XMoveWindow(dpy, c->win, c->x, c->y);
++ }
++ }
++
++ selmon->canvas[tagidx].cx = 0;
++ selmon->canvas[tagidx].cy = 0;
++ drawbar(selmon);
++ XFlush(dpy);
++}
++
++void
++movecanvas(const Arg *arg)
++{
++ if (selmon->lt[selmon->sellt]->arrange != NULL)
++ return;
++ if (selmon->sel && selmon->sel->isfullscreen)
++ return;
++
++ int tagidx = getcurrenttag(selmon);
++ int dx = 0, dy = 0;
++
++#ifndef MOVE_CANVAS_STEP
++#define MOVE_CANVAS_STEP 120
++#endif
++
++ switch(arg->i) {
++ case 0: dx = -MOVE_CANVAS_STEP; break;
++ case 1: dx = MOVE_CANVAS_STEP; break;
++ case 2: dy = -MOVE_CANVAS_STEP; break;
++ case 3: dy = MOVE_CANVAS_STEP; break;
++ }
++
++ selmon->canvas[tagidx].cx -= dx;
++ selmon->canvas[tagidx].cy -= dy;
++
++ Client *c;
++ for (c = selmon->clients; c; c = c->next) {
++ if (ISVISIBLE(c)) {
++ c->x -= dx;
++ c->y -= dy;
++ XMoveWindow(dpy, c->win, c->x, c->y);
++ }
++ }
++
++ drawbar(selmon);
++}
++
++void
++manuallymovecanvas(const Arg *arg) {
++ if (selmon->lt[selmon->sellt]->arrange != NULL)
++ return;
++ if (selmon->sel && selmon->sel->isfullscreen)
++ return;
++ int start_x, start_y;
++ Window dummy;
++ int di;
++ unsigned int dui;
++ int tagidx = getcurrenttag(selmon);
++#if LOCK_MOVE_RESIZE_REFRESH_RATE
++ Time lasttime = 0;
++#endif
++
++ if (!XQueryPointer(dpy, root, &dummy, &dummy, &start_x, &start_y, &di, &di, &dui))
++ return;
++
++ if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync,
++ None, cursor[CurMove]->cursor, CurrentTime) != GrabSuccess)
++ return;
++
++ XEvent ev;
++ do {
++ XMaskEvent(dpy, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev);
++
++ switch (ev.type) {
++ case MotionNotify:
++ {
++#if LOCK_MOVE_RESIZE_REFRESH_RATE
++ if ((ev.xmotion.time - lasttime) <= (1000 / refreshrate))
++ continue;
++ lasttime = ev.xmotion.time;
++#endif
++ int nx = ev.xmotion.x - start_x;
++ int ny = ev.xmotion.y - start_y;
++
++ for (Client *c = selmon->clients; c; c = c->next) {
++ if (c->tags & (1 << tagidx)) {
++ c->x += nx;
++ c->y += ny;
++ XMoveWindow(dpy, c->win, c->x, c->y);
++ }
++ }
++
++ selmon->canvas[tagidx].cx += nx;
++ selmon->canvas[tagidx].cy += ny;
++ drawbar(selmon);
++ start_x = ev.xmotion.x;
++ start_y = ev.xmotion.y;
++ } break;
++ }
++ } while (ev.type != ButtonRelease);
++
++ XUngrabPointer(dpy, CurrentTime);
++}
++
++void
++save_canvas_positions(Monitor *m) {
++ Client *c;
++ int tagidx = getcurrenttag(m);
++
++ m->canvas[tagidx].saved_cx = m->canvas[tagidx].cx;
++ m->canvas[tagidx].saved_cy = m->canvas[tagidx].cy;
++
++ for (c = m->clients; c; c = c->next) {
++ if (ISVISIBLE(c)) {
++ c->saved_cx = c->x + m->canvas[tagidx].cx;
++ c->saved_cy = c->y + m->canvas[tagidx].cy;
++ c->saved_cw = c->w;
++ c->saved_ch = c->h;
++ c->was_on_canvas = 1;
++ }
++ }
++}
++
++void
++restore_canvas_positions(Monitor *m) {
++ Client *c;
++ int tagidx = getcurrenttag(m);
++
++ m->canvas[tagidx].cx = m->canvas[tagidx].saved_cx;
++ m->canvas[tagidx].cy = m->canvas[tagidx].saved_cy;
++
++ for (c = m->clients; c; c = c->next) {
++ if (ISVISIBLE(c) && c->was_on_canvas) {
++ c->isfloating = 1;
++
++ int target_x = c->saved_cx - m->canvas[tagidx].cx;
++ int target_y = c->saved_cy - m->canvas[tagidx].cy;
++
++ c->x = target_x;
++ c->y = target_y;
++ c->w = c->saved_cw;
++ c->h = c->saved_ch;
++
++ XMoveResizeWindow(dpy, c->win, target_x, target_y, c->w, c->h);
++
++ configure(c);
++ }
++ }
++ XSync(dpy, False);
++}
++
++void
++centerwindow(const Arg *arg)
++{
++ Client *c = (arg && arg->v) ? (Client *)arg->v : selmon->sel;
++
++ if (!c || !c->mon || c->mon->lt[c->mon->sellt]->arrange != NULL)
++ return;
++
++ Monitor *m = c->mon;
++ int tagidx = getcurrenttag(m);
++
++ int screen_center_x = m->wx + (m->ww / 2);
++ int screen_center_y = m->wy + (m->wh / 2);
++
++ int win_center_x = c->x + (c->w + 2 * c->bw) / 2;
++ int win_center_y = c->y + (c->h + 2 * c->bw) / 2;
++
++ int dx = screen_center_x - win_center_x;
++ int dy = screen_center_y - win_center_y;
++
++ if (dx == 0 && dy == 0)
++ return;
++
++ Client *tmp;
++ for (tmp = m->clients; tmp; tmp = tmp->next) {
++ if (ISVISIBLE(tmp)) {
++ tmp->x += dx;
++ tmp->y += dy;
++ XMoveWindow(dpy, tmp->win, tmp->x, tmp->y);
++ }
++ }
++
++ m->canvas[tagidx].cx += dx;
++ m->canvas[tagidx].cy += dy;
++
++ drawbar(m);
++}
+diff --git a/infinitetags.h b/infinitetags.h
+new file mode 100644
+index 0000000..adcb69e
+--- /dev/null
++++ b/infinitetags.h
+@@ -0,0 +1,7 @@
++static void movecanvas(const Arg *arg);
++static void manuallymovecanvas(const Arg *arg);
++static void homecanvas(const Arg *arg);
++static int getcurrenttag(Monitor *m);
++static void save_canvas_positions(Monitor *m);
++static void restore_canvas_positions(Monitor *m);
++static void centerwindow(const Arg *arg);
+diff --git a/util.o b/util.o
+new file mode 100644
+index 0000000..34da63d
+Binary files /dev/null and b/util.o differ
diff --git a/dwm.suckless.org/patches/canvas/index.md b/dwm.suckless.org/patches/canvas/index.md
@@ -0,0 +1,19 @@
+canvas
+===
+
+Description
+-----------
+Canvas is a patch that implements the "canvas" mode
+from [vxwm](https://codeberg.org/wh1tepearl/vxwm).
+
+See [here](https://github.com/x3hy/canvas) for more
+information.
+
+Download
+--------
+* [dwm-canvas-6.2.diff](dwm-canvas-6.2.diff)
+
+Authors
+-------
+* Lucy Adams - <x3hy@protonmail.com>
+* "wh1tepearl" - <https://codeberg.org/wh1tepearl/vxwm> (vxwm creator)