mirror of
https://github.com/gryf/wmaker.git
synced 2026-03-31 03:23:33 +02:00
wmaker: extend default keybinding for multikeys support and add sticky-chain mode
This patch extends the existing keybindings to support multiple keys and add an optional "sticky chain" mode that lets a prefix remain active until users press a cancel key so users can enter the continuation key without re-pressing the prefix. The idea is to bring Emacs shortcuts keybinding to wmaker. Normal (existing and enhanced) mode: Prefix behaves like a one-shot release before the next key if any. For example: Mod1+h -> hide the active application, that is still working as usual. But if you want for example to have all your window management keys under the same leader key you can now do something like that: "Mod4+w h" which is pressing the Super key with w, releasing them and pressing h. You can assign that key sequence to an action. Sticky chain mode: Pressing a configured prefix enters a short-lived sticky state. Sticky state expires on timeout or when explicitly canceled (with KeychainCancelKey). For example, you can define: "Mod4+a x" -> run xterm "Mod4+a b f" -> run firefox "Mod4+a b c" -> run google chrome In sticky mode, "Mod4+a x x b f", then KeychainCancelKey or KeychainTimeoutDelay, will launch 2 xterm and firefox. New options for WindowMaker conf file: KeychainTimeoutDelay: timeout in milliseconds (can be set to 0) Default: 500 Example: KeychainTimeoutDelay = 500; KeychainCancelKey: explicit keybinding used to cancel an active sticky chain. If set to None the feature has no dedicated cancel key and the chain only ends by timeout or naturally if the keybind pressed is not defined. Default: None Example: KeychainCancelKey = Escape;
This commit is contained in:
committed by
Carlos R. Mafra
parent
ae050ceb40
commit
29177f94ed
216
src/defaults.c
216
src/defaults.c
@@ -4,7 +4,7 @@
|
||||
*
|
||||
* Copyright (c) 1997-2003 Alfredo K. Kojima
|
||||
* Copyright (c) 1998-2003 Dan Pascu
|
||||
* Copyright (c) 2014-2023 Window Maker Team
|
||||
* Copyright (c) 2014-2026 Window Maker Team
|
||||
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -64,8 +64,7 @@
|
||||
#include "properties.h"
|
||||
#include "misc.h"
|
||||
#include "winmenu.h"
|
||||
|
||||
#define MAX_SHORTCUT_LENGTH 32
|
||||
#include "rootmenu.h"
|
||||
|
||||
typedef struct _WDefaultEntry WDefaultEntry;
|
||||
typedef int (WDECallbackConvert) (WScreen *scr, WDefaultEntry *entry, WMPropList *plvalue, void *addr, void **tdata);
|
||||
@@ -539,6 +538,8 @@ WDefaultEntry optionList[] = {
|
||||
&wPreferences.window_list_app_icons, getBool, NULL, NULL, NULL},
|
||||
{"MouseWheelFocus", "NO", NULL,
|
||||
&wPreferences.mouse_wheel_focus, getBool, NULL, NULL, NULL},
|
||||
{"KeychainTimeoutDelay", "500", NULL,
|
||||
&wPreferences.keychain_timeout_delay, getInt, NULL, NULL, NULL},
|
||||
|
||||
/* style options */
|
||||
|
||||
@@ -648,6 +649,8 @@ WDefaultEntry optionList[] = {
|
||||
NULL, getKeybind, setKeyGrab, NULL, NULL},
|
||||
{"WindowMenuKey", "Control+Escape", (void *)WKBD_WINDOWMENU,
|
||||
NULL, getKeybind, setKeyGrab, NULL, NULL},
|
||||
{"KeychainCancelKey", "None", (void *)WKBD_KEYCHAIN_CANCEL,
|
||||
NULL, getKeybind, setKeyGrab, NULL, NULL},
|
||||
{"DockRaiseLowerKey", "None", (void*)WKBD_DOCKRAISELOWER,
|
||||
NULL, getKeybind, setKeyGrab, NULL, NULL},
|
||||
{"ClipRaiseLowerKey", "None", (void *)WKBD_CLIPRAISELOWER,
|
||||
@@ -1174,6 +1177,13 @@ void wDefaultsCheckDomains(void* arg)
|
||||
wwarning(_("could not load domain %s from user defaults database"), "WMRootMenu");
|
||||
}
|
||||
w_global.domain.root_menu->timestamp = stbuf.st_mtime;
|
||||
|
||||
/* Rebuild the root menu (without mapping) so that shortcuts take effect immediately. */
|
||||
for (i = 0; i < w_global.screen_count; i++) {
|
||||
WScreen *s = wScreenWithNumber(i);
|
||||
if (s)
|
||||
wRootMenuReparse(s);
|
||||
}
|
||||
}
|
||||
#ifndef HAVE_INOTIFY
|
||||
if (!arg)
|
||||
@@ -1243,7 +1253,6 @@ void wReadDefaults(WScreen * scr, WMPropList * new_dict)
|
||||
|
||||
if (entry->update)
|
||||
needs_refresh |= (*entry->update) (scr, entry, tdata, entry->extra_data);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1314,6 +1323,7 @@ void wReadKeybindings(WScreen *scr, WMPropList *dict)
|
||||
{
|
||||
WDefaultEntry *entry;
|
||||
unsigned int i;
|
||||
Bool keybindings_changed = False;
|
||||
void *tdata;
|
||||
|
||||
for (i = 0; i < wlengthof(optionList); i++) {
|
||||
@@ -1326,11 +1336,37 @@ void wReadKeybindings(WScreen *scr, WMPropList *dict)
|
||||
plvalue = entry->plvalue;
|
||||
if (plvalue) {
|
||||
int ok = (*entry->convert)(scr, entry, plvalue, entry->addr, &tdata);
|
||||
if (ok && entry->update)
|
||||
if (ok && entry->update) {
|
||||
/* Check whether the (re-)computed binding differs from
|
||||
* the one already in wKeyBindings[] */
|
||||
long widx = (long)entry->extra_data;
|
||||
WShortKey *nw = (WShortKey *)tdata;
|
||||
WShortKey *cur = &wKeyBindings[widx];
|
||||
Bool binding_changed =
|
||||
(cur->modifier != nw->modifier ||
|
||||
cur->keycode != nw->keycode ||
|
||||
cur->chain_length != nw->chain_length);
|
||||
|
||||
if (!binding_changed && nw->chain_length > 1 &&
|
||||
cur->chain_modifiers && cur->chain_keycodes) {
|
||||
int n = nw->chain_length - 1;
|
||||
|
||||
binding_changed =
|
||||
(memcmp(cur->chain_modifiers, nw->chain_modifiers,
|
||||
n * sizeof(unsigned int)) != 0 ||
|
||||
memcmp(cur->chain_keycodes, nw->chain_keycodes,
|
||||
n * sizeof(KeyCode)) != 0);
|
||||
}
|
||||
(*entry->update)(scr, entry, tdata, entry->extra_data);
|
||||
if (binding_changed)
|
||||
keybindings_changed = True;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (keybindings_changed)
|
||||
wKeyTreeRebuild(scr);
|
||||
}
|
||||
|
||||
void wDefaultUpdateIcons(WScreen *scr)
|
||||
@@ -1359,6 +1395,57 @@ void wDefaultUpdateIcons(WScreen *scr)
|
||||
}
|
||||
}
|
||||
|
||||
/* Rebuild the global key-binding trie from scratch */
|
||||
void wKeyTreeRebuild(WScreen *scr)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* Parameter not used */
|
||||
(void)scr;
|
||||
|
||||
wKeyTreeDestroy(wKeyTreeRoot);
|
||||
wKeyTreeRoot = NULL;
|
||||
|
||||
/* Insert all wKeyBindings[] entries */
|
||||
for (i = 0; i < WKBD_LAST; i++) {
|
||||
WShortKey *k = &wKeyBindings[i];
|
||||
WKeyAction *act;
|
||||
KeyCode *keys;
|
||||
WKeyNode *leaf;
|
||||
int len, j;
|
||||
unsigned int *mods;
|
||||
|
||||
/* WKBD_KEYCHAIN_CANCEL is only meaningful while inside an active key chain */
|
||||
if (i == WKBD_KEYCHAIN_CANCEL)
|
||||
continue;
|
||||
|
||||
if (k->keycode == 0)
|
||||
continue;
|
||||
|
||||
len = (k->chain_length > 1) ? k->chain_length : 1;
|
||||
mods = wmalloc(len * sizeof(unsigned int));
|
||||
keys = wmalloc(len * sizeof(KeyCode));
|
||||
mods[0] = k->modifier;
|
||||
keys[0] = k->keycode;
|
||||
|
||||
for (j = 1; j < len; j++) {
|
||||
mods[j] = k->chain_modifiers[j - 1];
|
||||
keys[j] = k->chain_keycodes[j - 1];
|
||||
}
|
||||
|
||||
leaf = wKeyTreeInsert(&wKeyTreeRoot, mods, keys, len);
|
||||
wfree(mods);
|
||||
wfree(keys);
|
||||
|
||||
act = wKeyNodeAddAction(leaf, WKN_WKBD);
|
||||
if (act)
|
||||
act->u.wkbd_idx = i;
|
||||
}
|
||||
|
||||
/* Insert root-menu shortcuts */
|
||||
wRootMenuInsertIntoTree();
|
||||
}
|
||||
|
||||
/* --------------------------- Local ----------------------- */
|
||||
|
||||
#define GET_STRING_OR_DEFAULT(x, var) if (!WMIsPLString(value)) { \
|
||||
@@ -2210,13 +2297,51 @@ static int getColor(WScreen * scr, WDefaultEntry * entry, WMPropList * value, vo
|
||||
return True;
|
||||
}
|
||||
|
||||
static Bool parseOneKey(WDefaultEntry *entry, const char *token,
|
||||
unsigned int *out_mod, KeyCode *out_code)
|
||||
{
|
||||
char tmp[MAX_SHORTCUT_LENGTH];
|
||||
char *b, *k;
|
||||
KeySym ksym;
|
||||
|
||||
wstrlcpy(tmp, token, MAX_SHORTCUT_LENGTH);
|
||||
b = tmp;
|
||||
|
||||
*out_mod = 0;
|
||||
while ((k = strchr(b, '+')) != NULL) {
|
||||
int mod;
|
||||
*k = 0;
|
||||
mod = wXModifierFromKey(b);
|
||||
if (mod < 0) {
|
||||
wwarning(_("%s: invalid key modifier \"%s\""), entry->key, b);
|
||||
return False;
|
||||
}
|
||||
*out_mod |= mod;
|
||||
b = k + 1;
|
||||
}
|
||||
|
||||
ksym = XStringToKeysym(b);
|
||||
if (ksym == NoSymbol) {
|
||||
wwarning(_("%s: invalid kbd shortcut specification \"%s\""), entry->key, token);
|
||||
return False;
|
||||
}
|
||||
|
||||
*out_code = XKeysymToKeycode(dpy, ksym);
|
||||
if (*out_code == 0) {
|
||||
wwarning(_("%s: invalid key in shortcut \"%s\""), entry->key, token);
|
||||
return False;
|
||||
}
|
||||
|
||||
return True;
|
||||
}
|
||||
|
||||
static int getKeybind(WScreen * scr, WDefaultEntry * entry, WMPropList * value, void *addr, void **ret)
|
||||
{
|
||||
static WShortKey shortcut;
|
||||
KeySym ksym;
|
||||
const char *val;
|
||||
char *k;
|
||||
char buf[MAX_SHORTCUT_LENGTH], *b;
|
||||
char buf[MAX_SHORTCUT_LENGTH];
|
||||
char *token, *saveptr;
|
||||
int step;
|
||||
|
||||
/* Parameter not used, but tell the compiler that it is ok */
|
||||
(void) scr;
|
||||
@@ -2224,9 +2349,11 @@ static int getKeybind(WScreen * scr, WDefaultEntry * entry, WMPropList * value,
|
||||
|
||||
GET_STRING_OR_DEFAULT("Key spec", val);
|
||||
|
||||
/* Free old chain arrays before overwriting */
|
||||
wShortKeyFree(&shortcut);
|
||||
|
||||
if (!val || strcasecmp(val, "NONE") == 0) {
|
||||
shortcut.keycode = 0;
|
||||
shortcut.modifier = 0;
|
||||
shortcut.chain_length = 1;
|
||||
if (ret)
|
||||
*ret = &shortcut;
|
||||
return True;
|
||||
@@ -2234,37 +2361,36 @@ static int getKeybind(WScreen * scr, WDefaultEntry * entry, WMPropList * value,
|
||||
|
||||
wstrlcpy(buf, val, MAX_SHORTCUT_LENGTH);
|
||||
|
||||
b = (char *)buf;
|
||||
/*
|
||||
* Support both the traditional single-key syntax and the
|
||||
* key-chain syntax where space-separated tokens represent
|
||||
* keys that must be pressed in sequence
|
||||
*/
|
||||
step = 0;
|
||||
token = strtok_r(buf, " ", &saveptr);
|
||||
while (token != NULL) {
|
||||
unsigned int mod;
|
||||
KeyCode kcode;
|
||||
|
||||
/* get modifiers */
|
||||
shortcut.modifier = 0;
|
||||
while ((k = strchr(b, '+')) != NULL) {
|
||||
int mod;
|
||||
|
||||
*k = 0;
|
||||
mod = wXModifierFromKey(b);
|
||||
if (mod < 0) {
|
||||
wwarning(_("%s: invalid key modifier \"%s\""), entry->key, b);
|
||||
if (!parseOneKey(entry, token, &mod, &kcode))
|
||||
return False;
|
||||
|
||||
if (step == 0) {
|
||||
shortcut.modifier = mod;
|
||||
shortcut.keycode = kcode;
|
||||
} else {
|
||||
shortcut.chain_modifiers = wrealloc(shortcut.chain_modifiers,
|
||||
step * sizeof(unsigned int));
|
||||
shortcut.chain_keycodes = wrealloc(shortcut.chain_keycodes,
|
||||
step * sizeof(KeyCode));
|
||||
shortcut.chain_modifiers[step - 1] = mod;
|
||||
shortcut.chain_keycodes[step - 1] = kcode;
|
||||
}
|
||||
shortcut.modifier |= mod;
|
||||
|
||||
b = k + 1;
|
||||
step++;
|
||||
token = strtok_r(NULL, " ", &saveptr);
|
||||
}
|
||||
|
||||
/* get key */
|
||||
ksym = XStringToKeysym(b);
|
||||
|
||||
if (ksym == NoSymbol) {
|
||||
wwarning(_("%s:invalid kbd shortcut specification \"%s\""), entry->key, val);
|
||||
return False;
|
||||
}
|
||||
|
||||
shortcut.keycode = XKeysymToKeycode(dpy, ksym);
|
||||
if (shortcut.keycode == 0) {
|
||||
wwarning(_("%s:invalid key in shortcut \"%s\""), entry->key, val);
|
||||
return False;
|
||||
}
|
||||
shortcut.chain_length = (step > 1) ? step : 1;
|
||||
|
||||
if (ret)
|
||||
*ret = &shortcut;
|
||||
@@ -3267,7 +3393,25 @@ static int setKeyGrab(WScreen * scr, WDefaultEntry * entry, void *tdata, void *e
|
||||
/* Parameter not used, but tell the compiler that it is ok */
|
||||
(void) entry;
|
||||
|
||||
/* Free old chain arrays before overwriting */
|
||||
wShortKeyFree(&wKeyBindings[widx]);
|
||||
|
||||
/* Shallow copy, then deep-copy the heap arrays */
|
||||
wKeyBindings[widx] = *shortcut;
|
||||
if (shortcut->chain_length > 1) {
|
||||
int n = shortcut->chain_length - 1;
|
||||
|
||||
wKeyBindings[widx].chain_modifiers = wmalloc(n * sizeof(unsigned int));
|
||||
wKeyBindings[widx].chain_keycodes = wmalloc(n * sizeof(KeyCode));
|
||||
|
||||
memcpy(wKeyBindings[widx].chain_modifiers, shortcut->chain_modifiers,
|
||||
n * sizeof(unsigned int));
|
||||
memcpy(wKeyBindings[widx].chain_keycodes, shortcut->chain_keycodes,
|
||||
n * sizeof(KeyCode));
|
||||
} else {
|
||||
wKeyBindings[widx].chain_modifiers = NULL;
|
||||
wKeyBindings[widx].chain_keycodes = NULL;
|
||||
}
|
||||
|
||||
wwin = scr->focused_window;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user