commit d166954782572f6ca3c36136c31b9e9e6e5efb2e
parent 648d2edf20bb3fb153660f48a27a03adfd121dea
Author: TUVIMEN <hexderm@gmail.com>
Date: Fri, 6 Jun 2025 09:18:30 +0200
[dwm][patch][keysequence] Add new patch
Add patch that allows for defining sequential key bindings
Diffstat:
2 files changed, 208 insertions(+), 0 deletions(-)
diff --git a/dwm.suckless.org/patches/keysequence/index.md b/dwm.suckless.org/patches/keysequence/index.md
@@ -0,0 +1,50 @@
+keysequence
+===========
+
+Description
+-----------
+
+This patch allows for defining sequential keybindings, for example `MOD+A W`.
+This is done not through the `XGrabKey()` which is used for root bindings,
+but by the `XGrabKeyboard()` so any key presses that are not defined will
+stop the matching without passing them to programs. This behaviour is
+less confusing.
+
+It defines new bindable function `keypress_other()` that as argument takes
+a pointer to array of bindings.
+
+ static Key keyseq_a[] = {
+ { 0, XK_t, setlayout, {.v = &layouts[0]}},
+ { ShiftMask, XK_t, setlayout, {.v = &layouts[1]}},
+ { MODKEY, XK_y, setlayout, {.v = &layouts[2]}},
+ {0}
+ }
+
+ static Key keys[] = {
+ { MODKEY, XK_a, keypress_other, {.v = keyseq_a}},
+ {0}
+ }
+
+This assigns `MOD+a t`, `MOD+a T`, `MOD+a MOD+y` to changing layout, you can
+nest bindings endlessly.
+
+Notice that now keybinding arrays are ended by `{0}` empty element,
+this is necessary because `Arg` structure can take only pointer
+sized elements, there's no place to specify size so it has to be
+calculated while running.
+
+While typing sequence all events are ignored other than key presses
+and mouse, moving mouse exits sequence.
+
+Save your hands! Don't make long sequences, stick to simple, easy to
+access binding and use plain letters, realistically speaking you'll
+use it for dmenu scripts, layout bindings, and other rarely used
+commands.
+
+Download
+--------
+* [keysequence-20250606-0d6af14.diff](keysequence-20250606-0d6af14.diff)
+
+Author
+------
+* TUVIMEN <hexderm@gmail.com>
diff --git a/dwm.suckless.org/patches/keysequence/keysequence-20250606-0d6af14.diff b/dwm.suckless.org/patches/keysequence/keysequence-20250606-0d6af14.diff
@@ -0,0 +1,158 @@
+From 0d6af14efa1250631b081ad9d1f3aa0263221fd8 Mon Sep 17 00:00:00 2001
+From: TUVIMEN <hexderm@gmail.com>
+Date: Fri, 6 Jun 2025 09:08:40 +0200
+Subject: [PATCH] [PATCH] 6.5 keysequence patch
+
+---
+ config.def.h | 1 +
+ dwm.c | 86 +++++++++++++++++++++++++++++++++++++++++++++++-----
+ 2 files changed, 80 insertions(+), 7 deletions(-)
+
+diff --git a/config.def.h b/config.def.h
+index 9efa774..6104343 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -95,6 +95,7 @@ static const Key keys[] = {
+ TAGKEYS( XK_8, 7)
+ TAGKEYS( XK_9, 8)
+ { MODKEY|ShiftMask, XK_q, quit, {0} },
++ {0}
+ };
+
+ /* button definitions */
+diff --git a/dwm.c b/dwm.c
+index f1d86b2..89a4bc5 100644
+--- a/dwm.c
++++ b/dwm.c
+@@ -28,6 +28,7 @@
+ #include <stdlib.h>
+ #include <string.h>
+ #include <unistd.h>
++#include <time.h>
+ #include <sys/types.h>
+ #include <sys/wait.h>
+ #include <X11/cursorfont.h>
+@@ -177,6 +178,7 @@ static void grabbuttons(Client *c, int focused);
+ static void grabkeys(void);
+ static void incnmaster(const Arg *arg);
+ static void keypress(XEvent *e);
++static void keypress_other(const Arg *arg);
+ static void killclient(const Arg *arg);
+ static void manage(Window w, XWindowAttributes *wa);
+ static void mappingnotify(XEvent *e);
+@@ -949,6 +951,13 @@ grabbuttons(Client *c, int focused)
+ }
+ }
+
++static char
++key_not_empty(const Key *key)
++{
++ static const Key empty = {0};
++ return memcmp(key, &empty, sizeof(Key)) != 0;
++}
++
+ void
+ grabkeys(void)
+ {
+@@ -965,7 +974,7 @@ grabkeys(void)
+ if (!syms)
+ return;
+ for (k = start; k <= end; k++)
+- for (i = 0; i < LENGTH(keys); i++)
++ for (i = 0; key_not_empty(keys+i); i++)
+ /* skip modifier codes, we do that ourselves */
+ if (keys[i].keysym == syms[(k - start) * skip])
+ for (j = 0; j < LENGTH(modifiers); j++)
+@@ -996,8 +1005,8 @@ isuniquegeom(XineramaScreenInfo *unique, size_t n, XineramaScreenInfo *info)
+ }
+ #endif /* XINERAMA */
+
+-void
+-keypress(XEvent *e)
++static void
++keypress_r(XEvent *e, const Key *keys)
+ {
+ unsigned int i;
+ KeySym keysym;
+@@ -1005,11 +1014,74 @@ keypress(XEvent *e)
+
+ ev = &e->xkey;
+ keysym = XKeycodeToKeysym(dpy, (KeyCode)ev->keycode, 0);
+- for (i = 0; i < LENGTH(keys); i++)
++ for (i = 0; key_not_empty(keys+i); i++)
+ if (keysym == keys[i].keysym
+- && CLEANMASK(keys[i].mod) == CLEANMASK(ev->state)
+- && keys[i].func)
+- keys[i].func(&(keys[i].arg));
++ && CLEANMASK(keys[i].mod) == CLEANMASK(ev->state)
++ && keys[i].func)
++ keys[i].func(&(keys[i].arg));
++}
++
++static char
++grabkeyboard(void) //taken from dmenu
++{
++ struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 };
++
++ /* try to grab keyboard, we may have to wait for another process to ungrab */
++ for (int i = 0; i < 1000; i++) {
++ if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync,
++ GrabModeAsync, CurrentTime) == GrabSuccess)
++ return 0;
++ nanosleep(&ts, NULL);
++ }
++ return 1; //failed
++}
++
++static char
++keysym_is_modifier(KeySym s)
++{
++ return (s == XK_Shift_L ||
++ s == XK_Shift_R ||
++ s == XK_Control_L ||
++ s == XK_Control_R ||
++ s == XK_Caps_Lock ||
++ s == XK_Shift_Lock ||
++ s == XK_Meta_L ||
++ s == XK_Meta_R ||
++ s == XK_Alt_L ||
++ s == XK_Alt_R ||
++ s == XK_Super_L ||
++ s == XK_Super_R ||
++ s == XK_Hyper_L ||
++ s == XK_Hyper_R);
++}
++
++void
++keypress_other(const Arg *arg)
++{
++ if (grabkeyboard())
++ return;
++
++ XEvent ev;
++ while (!XNextEvent(dpy, &ev)) {
++ if (ev.type == ButtonPress)
++ break;
++ if (ev.type == KeyPress) {
++ KeySym keysym = XKeycodeToKeysym(dpy, (KeyCode)ev.xkey.keycode, 0);
++ if (keysym_is_modifier(keysym))
++ continue;
++ keypress_r(&ev, (Key*)arg->v);
++ break;
++ }
++ }
++
++ XUngrabKeyboard(dpy, CurrentTime);
++ grabkeys();
++}
++
++void
++keypress(XEvent *e)
++{
++ keypress_r(e,keys);
+ }
+
+ void
+--
+2.49.0
+