david.m...@gmail.com unread, Mar 14, 2026, 10:07:32 AM (4 days ago) Mar 14
Sign in to reply to author
Sign in to forward
You do not have permission to delete messages in this group
Either email addresses are anonymous for this group or you need the view member email addresses permission to view the original message
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