WPrefs: improve capture_shortcut function

10 views
Skip to first unread message

david.m...@gmail.com

unread,
Mar 14, 2026, 10:07:32 AM (4 days ago) Mar 14
to Window Maker Development
This patch is improving the capture_shortcut function to be able
to capture a sequence of multiple key pressed.
---
 WPrefs.app/KeyboardShortcuts.c | 119 +++++++++++++++++++++++++--------
 1 file changed, 92 insertions(+), 27 deletions(-)

diff --git a/WPrefs.app/KeyboardShortcuts.c b/WPrefs.app/KeyboardShortcuts.c
index 8df69a9e..dc8b0995 100644
--- a/WPrefs.app/KeyboardShortcuts.c
+++ b/WPrefs.app/KeyboardShortcuts.c
@@ -23,6 +23,8 @@
 
 #include "WPrefs.h"
 #include <ctype.h>
+#include <sys/select.h>
+#include <sys/time.h>
 
 #include <X11/keysym.h>
 #include <X11/XKBlib.h>
@@ -307,14 +309,53 @@ static int NumLockMask(Display *dpy)
  return mask;
 }
 
+/* Append the modifier prefix and key name to the keybuf */
+static void build_key_combo(unsigned int xkstate, const char *keyname,
+                             unsigned int numlock_mask, char keybuf[64])
+{
+ if (xkstate & ControlMask)
+ strcat(keybuf, "Control+");
+ if (xkstate & ShiftMask)
+ strcat(keybuf, "Shift+");
+ if ((numlock_mask != Mod1Mask) && (xkstate & Mod1Mask))
+ strcat(keybuf, "Mod1+");
+ if ((numlock_mask != Mod2Mask) && (xkstate & Mod2Mask))
+ strcat(keybuf, "Mod2+");
+ if ((numlock_mask != Mod3Mask) && (xkstate & Mod3Mask))
+ strcat(keybuf, "Mod3+");
+ if ((numlock_mask != Mod4Mask) && (xkstate & Mod4Mask))
+ strcat(keybuf, "Mod4+");
+ if ((numlock_mask != Mod5Mask) && (xkstate & Mod5Mask))
+ strcat(keybuf, "Mod5+");
+ wstrlcat(keybuf, keyname, 64);
+}
+
+/*
+ * Interactively capture a key shortcut or keychain,
+ * function waits KeychainTimeoutDelay or 300 ms after
+ * each key press for another key in the chain,
+ * and returns the full key specification string.
+ */
 char *capture_shortcut(Display *dpy, Bool *capturing, Bool convert_case)
 {
  XEvent ev;
  KeySym ksym, lksym, uksym;
- char buffer[64];
- char *key = NULL;
+ /* Large enough for several chained chords */
+ char buffer[512];
+ char keybuf[64];
+ char *key;
  unsigned int numlock_mask;
+ Bool have_key = False;
+ Bool chain_mode;
+ int timeout_ms;
 
+ timeout_ms = GetIntegerForKey("KeychainTimeoutDelay");
+ if (timeout_ms <= 0)
+ timeout_ms = 300;
+
+ buffer[0] = '\0';
+
+ /* ---- Phase 1: capture the first key (blocking) ---- */
  while (*capturing) {
  XAllowEvents(dpy, AsyncKeyboard, CurrentTime);
  WMNextEvent(dpy, &ev);
@@ -332,41 +373,62 @@ char *capture_shortcut(Display *dpy, Bool *capturing, Bool convert_case)
  key = XKeysymToString(ksym);
  }
 
- *capturing = 0;
+ keybuf[0] = '\0';
+ build_key_combo(ev.xkey.state, key, numlock_mask, keybuf);
+ wstrlcat(buffer, keybuf, sizeof(buffer));
+ have_key = True;
  break;
  }
  }
  WMHandleEvent(&ev);
  }
 
- if (!key)
- return NULL;
-
- buffer[0] = 0;
-
- if (ev.xkey.state & ControlMask)
- strcat(buffer, "Control+");
-
- if (ev.xkey.state & ShiftMask)
- strcat(buffer, "Shift+");
-
- if ((numlock_mask != Mod1Mask) && (ev.xkey.state & Mod1Mask))
- strcat(buffer, "Mod1+");
-
- if ((numlock_mask != Mod2Mask) && (ev.xkey.state & Mod2Mask))
- strcat(buffer, "Mod2+");
+ /* ---- Phase 2: collect additional chain keys with timeout ---- */
+ chain_mode = (timeout_ms > 0);
+ while (*capturing && chain_mode) {
+ int xfd = ConnectionNumber(dpy);
+ fd_set rfds;
+ struct timeval tv;
+
+ if (!XPending(dpy)) {
+ FD_ZERO(&rfds);
+ FD_SET(xfd, &rfds);
+ tv.tv_sec  = timeout_ms / 1000;
+ tv.tv_usec = (timeout_ms % 1000) * 1000;
+ XFlush(dpy);
+ if (select(xfd + 1, &rfds, NULL, NULL, &tv) == 0)
+ break;  /* timeout: the chain is complete */
+ }
 
- if ((numlock_mask != Mod3Mask) && (ev.xkey.state & Mod3Mask))
- strcat(buffer, "Mod3+");
+ XAllowEvents(dpy, AsyncKeyboard, CurrentTime);
+ WMNextEvent(dpy, &ev);
+ if (ev.type == KeyPress && ev.xkey.keycode != 0) {
+ numlock_mask = NumLockMask(dpy);
+ ksym = W_KeycodeToKeysym(dpy, ev.xkey.keycode,
+                          ev.xkey.state & numlock_mask ? 1 : 0);
 
- if ((numlock_mask != Mod4Mask) && (ev.xkey.state & Mod4Mask))
- strcat(buffer, "Mod4+");
+ if (!IsModifierKey(ksym)) {
+ if (convert_case) {
+ XConvertCase(ksym, &lksym, &uksym);
+ key = XKeysymToString(uksym);
+ } else {
+ key = XKeysymToString(ksym);
+ }
 
- if ((numlock_mask != Mod5Mask) && (ev.xkey.state & Mod5Mask))
- strcat(buffer, "Mod5+");
+ keybuf[0] = '\0';
+ build_key_combo(ev.xkey.state, key, numlock_mask, keybuf);
+ wstrlcat(buffer, " ", sizeof(buffer));
+ wstrlcat(buffer, keybuf, sizeof(buffer));
+ }
+ } else {
+ WMHandleEvent(&ev);
+ }
+ }
 
- wstrlcat(buffer, key, sizeof(buffer));
+ if (!have_key || !*capturing)
+ return NULL;
 
+ *capturing = 0;
  return wstrdup(buffer);
 }
 
@@ -444,7 +506,7 @@ static void captureClick(WMWidget * w, void *data)
  }
  panel->capturing = 0;
  WMSetButtonText(w, _("Capture"));
- WMSetLabelText(panel->instructionsL, _("Click on Capture to interactively define the shortcut key."));
+ WMSetLabelText(panel->instructionsL, _("Click on Capture to interactively define the shortcut key(s)."));
  XUngrabKeyboard(dpy, CurrentTime);
 }
 
@@ -456,6 +518,9 @@ static void clearShortcut(WMWidget * w, void *data)
  /* Parameter not used, but tell the compiler that it is ok */
  (void) w;
 
+ /* Cancel any ongoing capture so the keychain loop is unblocked */
+ panel->capturing = 0;
+
  WMSetTextFieldText(panel->shoT, NULL);
 
  if (row >= 0) {
--
2.43.0
0001-WPrefs-improve-capture_shortcut-function.patch

david.m...@gmail.com

unread,
Mar 14, 2026, 10:14:06 AM (4 days ago) Mar 14
to Window Maker Development
Forgot to put [PATCH] in the email subject, I hope it's okay.
Reply all
Reply to author
Forward
0 new messages