mirror of
https://github.com/gryf/wmaker.git
synced 2026-03-24 21:53:31 +01:00
Compare commits
3 Commits
ae050ceb40
...
0aeba6064b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0aeba6064b | ||
|
|
ec115fedf7 | ||
|
|
29177f94ed |
@@ -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)
|
||||
/* ---- 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 */
|
||||
}
|
||||
|
||||
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 (!IsModifierKey(ksym)) {
|
||||
if (convert_case) {
|
||||
XConvertCase(ksym, &lksym, &uksym);
|
||||
key = XKeysymToString(uksym);
|
||||
} else {
|
||||
key = XKeysymToString(ksym);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (!have_key || !*capturing)
|
||||
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+");
|
||||
|
||||
if ((numlock_mask != Mod3Mask) && (ev.xkey.state & Mod3Mask))
|
||||
strcat(buffer, "Mod3+");
|
||||
|
||||
if ((numlock_mask != Mod4Mask) && (ev.xkey.state & Mod4Mask))
|
||||
strcat(buffer, "Mod4+");
|
||||
|
||||
if ((numlock_mask != Mod5Mask) && (ev.xkey.state & Mod5Mask))
|
||||
strcat(buffer, "Mod5+");
|
||||
|
||||
wstrlcat(buffer, key, sizeof(buffer));
|
||||
|
||||
*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) {
|
||||
|
||||
@@ -555,7 +555,9 @@ AC_ARG_ENABLE([modelock],
|
||||
m4_divert_pop([INIT_PREPARE])dnl
|
||||
|
||||
AS_IF([test "x$enable_modelock" = "xyes"],
|
||||
[AC_DEFINE([XKB_MODELOCK], [1], [whether XKB language MODELOCK should be enabled]) ])
|
||||
[WM_XEXT_CHECK_XKBFILE
|
||||
AS_IF([test "x$enable_modelock" = "xyes"],
|
||||
[AC_DEFINE([XKB_MODELOCK], [1], [whether XKB language MODELOCK should be enabled])])])
|
||||
|
||||
|
||||
dnl XDND Drag-nd-Drop support
|
||||
|
||||
@@ -232,3 +232,35 @@ AC_DEFUN_ONCE([WM_XEXT_CHECK_XRANDR],
|
||||
[supported_xext], [LIBXRANDR], [], [-])dnl
|
||||
AC_SUBST([LIBXRANDR])dnl
|
||||
]) dnl AC_DEFUN
|
||||
|
||||
|
||||
# WM_XEXT_CHECK_XKBFILE
|
||||
# ---------------------
|
||||
#
|
||||
# Check for the XKB File extension library (libxkbfile)
|
||||
# The check depends on variable 'enable_modelock' being either:
|
||||
# yes - detect, fail if not found
|
||||
# no - do not detect, disable support
|
||||
#
|
||||
# When found, append appropriate stuff in LIBXKBFILE, and append info to
|
||||
# the variable 'supported_xext'
|
||||
# When not found, generate an error because it's required for modelock
|
||||
AC_DEFUN_ONCE([WM_XEXT_CHECK_XKBFILE],
|
||||
[WM_LIB_CHECK([XKBFile], [-lxkbfile], [XkbRF_GetNamesProp], [$XLIBS],
|
||||
[wm_save_CFLAGS="$CFLAGS"
|
||||
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([dnl
|
||||
@%:@include <stdio.h>
|
||||
@%:@include <X11/Xlib.h>
|
||||
@%:@include <X11/XKBlib.h>
|
||||
@%:@include <X11/extensions/XKBfile.h>
|
||||
@%:@include <X11/extensions/XKBrules.h>
|
||||
], [dnl
|
||||
Display *dpy = NULL;
|
||||
XkbRF_VarDefsRec vd;
|
||||
XkbRF_GetNamesProp(dpy, NULL, &vd);])],
|
||||
[],
|
||||
[AC_MSG_ERROR([found $CACHEVAR but cannot compile using XKBfile header])])
|
||||
CFLAGS="$wm_save_CFLAGS"],
|
||||
[supported_xext], [LIBXKBFILE], [enable_modelock], [-])dnl
|
||||
AC_SUBST([LIBXKBFILE])dnl
|
||||
]) dnl AC_DEFUN
|
||||
|
||||
@@ -45,6 +45,8 @@ wmaker_SOURCES = \
|
||||
icon.c \
|
||||
icon.h \
|
||||
keybind.h \
|
||||
keytree.c \
|
||||
keytree.h \
|
||||
main.c \
|
||||
main.h \
|
||||
menu.c \
|
||||
@@ -163,6 +165,7 @@ wmaker_LDADD = \
|
||||
@XLFLAGS@ \
|
||||
@LIBXRANDR@ \
|
||||
@LIBXINERAMA@ \
|
||||
@LIBXKBFILE@ \
|
||||
@XLIBS@ \
|
||||
@LIBM@ \
|
||||
@INTLIBS@
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#include <WINGs/WINGs.h>
|
||||
#include "keytree.h"
|
||||
|
||||
|
||||
/* class codes */
|
||||
@@ -470,6 +471,8 @@ extern struct WPreferences {
|
||||
int clip_auto_expand_delay; /* Delay after which the clip will expand when entered */
|
||||
int clip_auto_collapse_delay; /* Delay after which the clip will collapse when leaved */
|
||||
|
||||
int keychain_timeout_delay; /* Delay after which a keychain is reset, 0 means disabled */
|
||||
|
||||
RImage *swtileImage;
|
||||
RImage *swbackImage[9];
|
||||
|
||||
@@ -649,6 +652,17 @@ extern struct wmaker_global_variables {
|
||||
* impact the shortcuts (typically: CapsLock, NumLock, ScrollLock)
|
||||
*/
|
||||
unsigned int modifiers_mask;
|
||||
|
||||
/*
|
||||
* Key-chain trie cursor.
|
||||
*
|
||||
* curpos == NULL : idle, no active chain.
|
||||
* curpos != NULL : inside a chain; curpos points to the last matched
|
||||
* internal node in wKeyTreeRoot. The next expected
|
||||
* key is one of curpos->first_child's siblings.
|
||||
*/
|
||||
WKeyNode *curpos;
|
||||
WMHandlerID chain_timeout_handler; /* non-NULL while chain timer is armed */
|
||||
} shortcut;
|
||||
} w_global;
|
||||
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@@ -57,5 +57,5 @@ void wDefaultChangeIcon(const char *instance, const char* class, const char *fil
|
||||
RImage *get_rimage_from_file(WScreen *scr, const char *file_name, int max_size);
|
||||
|
||||
void wDefaultPurgeInfo(const char *instance, const char *class);
|
||||
|
||||
void wKeyTreeRebuild(WScreen *scr); /* Rebuild the key-chain trie from the current key bindings */
|
||||
#endif /* WMDEFAULTS_H_ */
|
||||
|
||||
238
src/event.c
238
src/event.c
@@ -384,6 +384,12 @@ static void handle_inotify_events(void)
|
||||
/* move to next event in the buffer */
|
||||
i += sizeof(struct inotify_event) + pevent->len;
|
||||
}
|
||||
|
||||
for (i = 0; i < w_global.screen_count; i++) {
|
||||
WScreen *scr = wScreenWithNumber(i);
|
||||
if (scr)
|
||||
wKeyTreeRebuild(scr);
|
||||
}
|
||||
}
|
||||
#endif /* HAVE_INOTIFY */
|
||||
|
||||
@@ -587,10 +593,12 @@ static void handleExtensions(XEvent * event)
|
||||
handleShapeNotify(event);
|
||||
}
|
||||
#endif
|
||||
if (w_global.xext.xkb.supported && event->type == w_global.xext.xkb.event_base) {
|
||||
if (w_global.xext.xkb.supported && event->type >= w_global.xext.xkb.event_base
|
||||
&& event->type <= w_global.xext.xkb.event_base + 255) {
|
||||
XkbEvent *xkbevent = (XkbEvent *) event;
|
||||
int xkb_type = xkbevent->any.xkb_type;
|
||||
|
||||
if (xkbevent->any.xkb_type == XkbNewKeyboardNotify) {
|
||||
if (xkb_type == XkbNewKeyboardNotify) {
|
||||
int j;
|
||||
WScreen *scr;
|
||||
|
||||
@@ -601,8 +609,10 @@ static void handleExtensions(XEvent * event)
|
||||
}
|
||||
#ifdef KEEP_XKB_LOCK_STATUS
|
||||
else {
|
||||
if (wPreferences.modelock && (xkbevent->any.xkb_type == XkbIndicatorStateNotify)) {
|
||||
handleXkbIndicatorStateNotify((XkbEvent *) event);
|
||||
/* Listen not only for IndicatorStateNotify but also for StateNotify
|
||||
* which is commonly emitted on group (layout) changes. */
|
||||
if (wPreferences.modelock && (xkb_type == XkbIndicatorStateNotify || xkb_type == XkbStateNotify)) {
|
||||
handleXkbIndicatorStateNotify(xkbevent);
|
||||
}
|
||||
}
|
||||
#endif /*KEEP_XKB_LOCK_STATUS */
|
||||
@@ -1318,6 +1328,8 @@ static void handleXkbIndicatorStateNotify(XkbEvent *event)
|
||||
if (wwin->frame->languagemode != staterec.group) {
|
||||
wwin->frame->last_languagemode = wwin->frame->languagemode;
|
||||
wwin->frame->languagemode = staterec.group;
|
||||
wWindowGetLanguageLabel(wwin->frame->languagemode, wwin->frame->language_label);
|
||||
wFrameWindowUpdateLanguageButton(wwin->frame);
|
||||
}
|
||||
#ifdef XKB_BUTTON_HINT
|
||||
if (wwin->frame->titlebar) {
|
||||
@@ -1422,56 +1434,55 @@ static int CheckFullScreenWindowFocused(WScreen * scr)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void handleKeyPress(XEvent * event)
|
||||
/* ------------------------------------------------------------------ *
|
||||
* Key-chain timeout support *
|
||||
* *
|
||||
* wPreferences.keychain_timeout_delay in milliseconds after a chain *
|
||||
* leader is pressed, the chain is automatically cancelled so the *
|
||||
* user is not stuck in a half-entered sequence. Set to 0 to disable. *
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
/* Cancels the chain on inactivity */
|
||||
static void chainTimeoutCallback(void *data)
|
||||
{
|
||||
WScreen *scr = wScreenForRootWindow(event->xkey.root);
|
||||
WWindow *wwin = scr->focused_window;
|
||||
short i, widx;
|
||||
int modifiers;
|
||||
int command = -1;
|
||||
(void)data;
|
||||
|
||||
XUngrabKeyboard(dpy, CurrentTime);
|
||||
w_global.shortcut.curpos = NULL;
|
||||
w_global.shortcut.chain_timeout_handler = NULL;
|
||||
}
|
||||
|
||||
/* Start (or restart) the chain inactivity timer */
|
||||
static void wStartChainTimer(void)
|
||||
{
|
||||
if (wPreferences.keychain_timeout_delay > 0) {
|
||||
if (w_global.shortcut.chain_timeout_handler)
|
||||
WMDeleteTimerHandler(w_global.shortcut.chain_timeout_handler);
|
||||
w_global.shortcut.chain_timeout_handler =
|
||||
WMAddTimerHandler(wPreferences.keychain_timeout_delay, chainTimeoutCallback, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/* Cancel the chain inactivity timer, if armed */
|
||||
static void wCancelChainTimer(void)
|
||||
{
|
||||
if (w_global.shortcut.chain_timeout_handler) {
|
||||
WMDeleteTimerHandler(w_global.shortcut.chain_timeout_handler);
|
||||
w_global.shortcut.chain_timeout_handler = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#define ISMAPPED(w) ((w) && !(w)->flags.miniaturized && ((w)->flags.mapped || (w)->flags.shaded))
|
||||
#define ISFOCUSED(w) ((w) && (w)->flags.focused)
|
||||
|
||||
static void dispatchWKBDCommand(int command, WScreen *scr, WWindow *wwin, XEvent *event)
|
||||
{
|
||||
short widx;
|
||||
int i;
|
||||
#ifdef KEEP_XKB_LOCK_STATUS
|
||||
XkbStateRec staterec;
|
||||
#endif /*KEEP_XKB_LOCK_STATUS */
|
||||
|
||||
/* ignore CapsLock */
|
||||
modifiers = event->xkey.state & w_global.shortcut.modifiers_mask;
|
||||
|
||||
for (i = 0; i < WKBD_LAST; i++) {
|
||||
if (wKeyBindings[i].keycode == 0)
|
||||
continue;
|
||||
|
||||
if (wKeyBindings[i].keycode == event->xkey.keycode && ( /*wKeyBindings[i].modifier==0
|
||||
|| */ wKeyBindings[i].modifier ==
|
||||
modifiers)) {
|
||||
command = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (command < 0) {
|
||||
|
||||
if (!wRootMenuPerformShortcut(event)) {
|
||||
static int dontLoop = 0;
|
||||
|
||||
if (dontLoop > 10) {
|
||||
wwarning("problem with key event processing code");
|
||||
return;
|
||||
}
|
||||
dontLoop++;
|
||||
/* if the focused window is an internal window, try redispatching
|
||||
* the event to the managed window, as it can be a WINGs window */
|
||||
if (wwin && wwin->flags.internal_window && wwin->client_leader != None) {
|
||||
/* client_leader contains the WINGs toplevel */
|
||||
event->xany.window = wwin->client_leader;
|
||||
WMHandleEvent(event);
|
||||
}
|
||||
dontLoop--;
|
||||
}
|
||||
return;
|
||||
}
|
||||
#define ISMAPPED(w) ((w) && !(w)->flags.miniaturized && ((w)->flags.mapped || (w)->flags.shaded))
|
||||
#define ISFOCUSED(w) ((w) && (w)->flags.focused)
|
||||
|
||||
switch (command) {
|
||||
|
||||
case WKBD_ROOTMENU:
|
||||
@@ -1962,7 +1973,8 @@ static void handleKeyPress(XEvent * event)
|
||||
wwin->frame->languagemode = wwin->frame->last_languagemode;
|
||||
wwin->frame->last_languagemode = staterec.group;
|
||||
XkbLockGroup(dpy, XkbUseCoreKbd, wwin->frame->languagemode);
|
||||
|
||||
/* Update the language label text */
|
||||
wWindowGetLanguageLabel(wwin->frame->languagemode, wwin->frame->language_label);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -1970,6 +1982,132 @@ static void handleKeyPress(XEvent * event)
|
||||
}
|
||||
}
|
||||
|
||||
static void handleKeyPress(XEvent * event)
|
||||
{
|
||||
WScreen *scr = wScreenForRootWindow(event->xkey.root);
|
||||
WWindow *wwin = scr->focused_window;
|
||||
WKeyNode *siblings;
|
||||
WKeyNode *match;
|
||||
WKeyAction *act;
|
||||
int modifiers;
|
||||
|
||||
/* ignore CapsLock */
|
||||
modifiers = event->xkey.state & w_global.shortcut.modifiers_mask;
|
||||
|
||||
/* ------------------------------------------------------------------ *
|
||||
* Trie-based key-chain matching *
|
||||
* *
|
||||
* wKeyTreeRoot is a prefix trie covering ALL key bindings *
|
||||
* (wKeyBindings and root-menu shortcuts combined). *
|
||||
* curpos tracks the last matched internal node. *
|
||||
* NULL means we are at the root (idle). *
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
if (w_global.shortcut.curpos != NULL) {
|
||||
/* Inside a chain: look for the next key among children */
|
||||
if (event->xkey.keycode == wKeyBindings[WKBD_KEYCHAIN_CANCEL].keycode &&
|
||||
modifiers == wKeyBindings[WKBD_KEYCHAIN_CANCEL].modifier) {
|
||||
wCancelChainTimer();
|
||||
XUngrabKeyboard(dpy, CurrentTime);
|
||||
w_global.shortcut.curpos = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
siblings = w_global.shortcut.curpos->first_child;
|
||||
match = wKeyTreeFind(siblings, modifiers, event->xkey.keycode);
|
||||
|
||||
if (match != NULL && match->first_child != NULL) {
|
||||
/* Internal node: advance and keep waiting */
|
||||
w_global.shortcut.curpos = match;
|
||||
wStartChainTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (match == NULL) {
|
||||
/* Unrecognized key inside chain: exit chain mode */
|
||||
wCancelChainTimer();
|
||||
XUngrabKeyboard(dpy, CurrentTime);
|
||||
w_global.shortcut.curpos = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sticky-chain mode: when a KeychainCancelKey is configured,
|
||||
* stay at the parent level after executing a leaf instead of always
|
||||
* returning to root.
|
||||
*/
|
||||
if (wKeyBindings[WKBD_KEYCHAIN_CANCEL].keycode != 0) {
|
||||
WKeyNode *parent = match->parent;
|
||||
WKeyNode *child;
|
||||
int nchildren = 0;
|
||||
|
||||
for (child = parent->first_child; child != NULL; child = child->next_sibling)
|
||||
nchildren++;
|
||||
|
||||
if (nchildren > 1) {
|
||||
/* Multi-branch parent: stay in chain mode at this level */
|
||||
w_global.shortcut.curpos = parent;
|
||||
wStartChainTimer();
|
||||
} else {
|
||||
/* Single-branch parent: nothing left to wait for, exit chain */
|
||||
wCancelChainTimer();
|
||||
XUngrabKeyboard(dpy, CurrentTime);
|
||||
w_global.shortcut.curpos = NULL;
|
||||
}
|
||||
} else {
|
||||
/* No cancel key configured: always exit chain after a leaf */
|
||||
wCancelChainTimer();
|
||||
XUngrabKeyboard(dpy, CurrentTime);
|
||||
w_global.shortcut.curpos = NULL;
|
||||
}
|
||||
} else {
|
||||
/* Idle: look for a root-level match */
|
||||
match = wKeyTreeFind(wKeyTreeRoot, modifiers, event->xkey.keycode);
|
||||
|
||||
if (match == NULL) {
|
||||
/* Not a known shortcut: try to redispatch it */
|
||||
static int dontLoop = 0;
|
||||
|
||||
if (dontLoop > 10) {
|
||||
wwarning("problem with key event processing code");
|
||||
return;
|
||||
}
|
||||
dontLoop++;
|
||||
if (wwin && wwin->flags.internal_window &&
|
||||
wwin->client_leader != None) {
|
||||
event->xany.window = wwin->client_leader;
|
||||
WMHandleEvent(event);
|
||||
}
|
||||
dontLoop--;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (match->first_child != NULL) {
|
||||
/* Internal node: enter chain mode */
|
||||
w_global.shortcut.curpos = match;
|
||||
XGrabKeyboard(dpy, scr->root_win, False,
|
||||
GrabModeAsync, GrabModeAsync, CurrentTime);
|
||||
wStartChainTimer();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Execute all leaf actions for this key sequence */
|
||||
for (act = match->actions; act != NULL; act = act->next) {
|
||||
if (act->type == WKN_MENU) {
|
||||
WMenu *menu = (WMenu *) act->u.menu.menu;
|
||||
WMenuEntry *entry = (WMenuEntry *) act->u.menu.entry;
|
||||
|
||||
(*entry->callback)(menu, entry);
|
||||
} else {
|
||||
dispatchWKBDCommand(act->u.wkbd_idx, scr, wwin, event);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
#define CORNER_NONE 0
|
||||
#define CORNER_TOPLEFT 1
|
||||
#define CORNER_TOPRIGHT 2
|
||||
|
||||
198
src/framewin.c
198
src/framewin.c
@@ -54,7 +54,7 @@ static void resizebarMouseDown(WObjDescriptor * desc, XEvent * event);
|
||||
static void checkTitleSize(WFrameWindow * fwin);
|
||||
|
||||
static void paintButton(WCoreWindow * button, WTexture * texture,
|
||||
unsigned long color, WPixmap * image, int pushed);
|
||||
unsigned long color, WPixmap * image, int pushed, int from_xpm);
|
||||
|
||||
static void updateTitlebar(WFrameWindow * fwin);
|
||||
|
||||
@@ -98,6 +98,7 @@ WFrameWindow *wFrameWindowCreate(WScreen * scr, int wlevel, int x, int y,
|
||||
#ifdef KEEP_XKB_LOCK_STATUS
|
||||
fwin->languagemode = XkbGroup1Index;
|
||||
fwin->last_languagemode = XkbGroup2Index;
|
||||
wWindowGetLanguageLabel(fwin->languagemode, fwin->language_label);
|
||||
#endif
|
||||
|
||||
fwin->depth = depth;
|
||||
@@ -145,6 +146,7 @@ void wFrameWindowUpdateBorders(WFrameWindow * fwin, int flags)
|
||||
theight = *fwin->title_min_height;
|
||||
} else {
|
||||
theight = 0;
|
||||
fwin->flags.titlebar = 0;
|
||||
}
|
||||
|
||||
if (wPreferences.new_style == TS_NEW) {
|
||||
@@ -536,6 +538,8 @@ static void updateTitlebar(WFrameWindow * fwin)
|
||||
#ifdef XKB_BUTTON_HINT
|
||||
else {
|
||||
int bsize = theight - 7;
|
||||
if (wPreferences.new_style == TS_NEXT)
|
||||
bsize -= 1;
|
||||
if (fwin->flags.hide_left_button || !fwin->left_button || fwin->flags.lbutton_dont_fit) {
|
||||
if (fwin->language_button)
|
||||
wCoreConfigure(fwin->language_button, 3, (theight - bsize) / 2,
|
||||
@@ -950,6 +954,10 @@ void wFrameWindowPaint(WFrameWindow * fwin)
|
||||
remakeTexture(fwin, i);
|
||||
}
|
||||
}
|
||||
#ifdef XKB_BUTTON_HINT
|
||||
if (wPreferences.modelock)
|
||||
wFrameWindowUpdateLanguageButton(fwin);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (fwin->flags.need_texture_change) {
|
||||
@@ -1023,7 +1031,8 @@ void wFrameWindowPaint(WFrameWindow * fwin)
|
||||
allButtons = 0;
|
||||
}
|
||||
#ifdef XKB_BUTTON_HINT
|
||||
fwin->languagebutton_image = scr->b_pixmaps[WBUT_XKBGROUP1 + fwin->languagemode];
|
||||
if (fwin->flags.language_button && !fwin->languagebutton_image[0])
|
||||
wFrameWindowUpdateLanguageButton(fwin);
|
||||
#endif
|
||||
|
||||
if (fwin->title) {
|
||||
@@ -1228,10 +1237,145 @@ int wFrameWindowChangeTitle(WFrameWindow *fwin, const char *new_title)
|
||||
}
|
||||
|
||||
#ifdef XKB_BUTTON_HINT
|
||||
|
||||
static int wFrameWindowSetLanguageButtonImages(WFrameWindow *fwin, Pixmap *pixmaps, int count)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < count; i++) {
|
||||
WPixmap *wp = wPixmapCreate(pixmaps[i], None);
|
||||
if (!wp) {
|
||||
int j;
|
||||
XFreePixmap(dpy, pixmaps[i]);
|
||||
for (j = i + 1; j < count; j++)
|
||||
XFreePixmap(dpy, pixmaps[j]);
|
||||
return 0;
|
||||
}
|
||||
wp->client_owned = 0;
|
||||
wp->client_owned_mask = 0;
|
||||
if (fwin->languagebutton_image[i] && !fwin->languagebutton_image[i]->shared)
|
||||
wPixmapDestroy(fwin->languagebutton_image[i]);
|
||||
fwin->languagebutton_image[i] = wp;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void wFrameWindowUpdateLanguageButton(WFrameWindow * fwin)
|
||||
{
|
||||
paintButton(fwin->language_button, fwin->title_texture[fwin->flags.state],
|
||||
WMColorPixel(fwin->title_color[fwin->flags.state]), fwin->languagebutton_image, True);
|
||||
WScreen *scr = fwin->screen_ptr;
|
||||
WCoreWindow *button = fwin->language_button;
|
||||
GC gc = scr->copy_gc;
|
||||
int i, text_width, text_height;
|
||||
int border_thickness = 3;
|
||||
int key_width, key_height;
|
||||
int group_index;
|
||||
Pixmap tmp[2]; /* focused, unfocused */
|
||||
|
||||
if (!fwin->flags.titlebar || fwin->core->descriptor.parent_type != WCLASS_WINDOW)
|
||||
return;
|
||||
|
||||
if (!button || fwin->language_label[0] == '\0')
|
||||
return;
|
||||
|
||||
group_index = fwin->languagemode;
|
||||
if (group_index < 0 || group_index >= 4)
|
||||
return;
|
||||
|
||||
/* Calculate text dimensions */
|
||||
int small_px = WMFontHeight(*fwin->font) * 55 / 100;
|
||||
WMFont *small = WMBoldSystemFontOfSize(scr->wmscreen, small_px);
|
||||
text_width = WMWidthOfString(small, fwin->language_label, strlen(fwin->language_label));
|
||||
text_height = WMFontHeight(small);
|
||||
|
||||
key_width = button->width - border_thickness;
|
||||
key_height = button->height - border_thickness;
|
||||
|
||||
/* Ensure dimensions are valid */
|
||||
if (key_width < 1 || key_height < 1) {
|
||||
WMReleaseFont(small);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create temporary pixmaps for immediate drawing */
|
||||
tmp[0] = XCreatePixmap(dpy, button->window, 2*key_width, key_height, scr->w_depth);
|
||||
if (tmp[0] == None) {
|
||||
WMReleaseFont(small);
|
||||
return;
|
||||
}
|
||||
|
||||
tmp[1] = None;
|
||||
if (wPreferences.new_style == TS_NEW) {
|
||||
tmp[1] = XCreatePixmap(dpy, button->window, 2*key_width, key_height, scr->w_depth);
|
||||
if (tmp[1] == None) {
|
||||
XFreePixmap(dpy, tmp[0]);
|
||||
WMReleaseFont(small);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Reset GC to ensure clean state for drawing */
|
||||
XSetClipMask(dpy, gc, None);
|
||||
|
||||
/* Draw the language label centered in the button */
|
||||
int text_x = (key_width - text_width) / 2;
|
||||
int text_y = (key_height - text_height) / 2;
|
||||
|
||||
/* Fill the color pixmap depending on the style */
|
||||
if (wPreferences.new_style == TS_NEW) {
|
||||
for (i = 0; i < 2; i++) {
|
||||
if (fwin->title_texture[i]->any.type != WTEX_SOLID && fwin->title_back[i] != None) {
|
||||
XCopyArea(dpy, fwin->languagebutton_back[i], tmp[i], scr->copy_gc,
|
||||
0, 0, button->width, button->height, -1, -1);
|
||||
} else {
|
||||
unsigned long bg_pixel = fwin->title_texture[i]->solid.normal.pixel;
|
||||
XSetForeground(dpy, gc, bg_pixel);
|
||||
XFillRectangle(dpy, tmp[i], gc, 0, 0, key_width, key_height);
|
||||
}
|
||||
WMDrawString(scr->wmscreen, tmp[i], fwin->title_color[i],
|
||||
small, text_x, text_y, fwin->language_label, strlen(fwin->language_label));
|
||||
}
|
||||
} else if (wPreferences.new_style == TS_OLD) {
|
||||
unsigned long bg_pixel = scr->widget_texture->normal.pixel;
|
||||
XSetForeground(dpy, gc, bg_pixel);
|
||||
XFillRectangle(dpy, tmp[0], gc, 0, 0, key_width, key_height);
|
||||
WMDrawString(scr->wmscreen, tmp[0], WMBlackColor(scr->wmscreen),
|
||||
small, text_x, text_y, fwin->language_label, strlen(fwin->language_label));
|
||||
} else {
|
||||
unsigned long bg_pixel = scr->widget_texture->dark.pixel;
|
||||
XSetForeground(dpy, gc, bg_pixel);
|
||||
XFillRectangle(dpy, tmp[0], gc, 0, 0, key_width, key_height);
|
||||
WMColor *silver = WMCreateRGBColor(scr->wmscreen, 0xc0c0, 0xc0c0, 0xc0c0, True);
|
||||
WMDrawString(scr->wmscreen, tmp[0], silver,
|
||||
small, text_x, text_y, fwin->language_label, strlen(fwin->language_label));
|
||||
WMReleaseColor(silver);
|
||||
}
|
||||
|
||||
/* pushed button next to normal for easy access when painting */
|
||||
text_x = key_width + (key_width - text_width) / 2;
|
||||
if (wPreferences.new_style == TS_NEW) {
|
||||
XSetForeground(dpy, gc, scr->white_pixel);
|
||||
for (i = 0; i < 2; i++) {
|
||||
XFillRectangle(dpy, tmp[i], gc, key_width, 0, key_width, key_height);
|
||||
WMDrawString(scr->wmscreen, tmp[i], WMBlackColor(scr->wmscreen),
|
||||
small, text_x, text_y, fwin->language_label, strlen(fwin->language_label));
|
||||
}
|
||||
} else if (wPreferences.new_style == TS_OLD) {
|
||||
XSetForeground(dpy, gc, scr->white_pixel);
|
||||
XFillRectangle(dpy, tmp[0], gc, key_width, 0, key_width, key_height);
|
||||
WMDrawString(scr->wmscreen, tmp[0], WMDarkGrayColor(scr->wmscreen),
|
||||
small, text_x, text_y, fwin->language_label, strlen(fwin->language_label));
|
||||
} else {
|
||||
unsigned long bg_pixel = scr->widget_texture->dark.pixel;
|
||||
WMColor *silver = WMCreateRGBColor(scr->wmscreen, 0xc0c0, 0xc0c0, 0xc0c0, True);
|
||||
XSetForeground(dpy, gc, bg_pixel);
|
||||
XFillRectangle(dpy, tmp[0], gc, key_width, 0, key_width, key_height);
|
||||
WMDrawString(scr->wmscreen, tmp[0], silver,
|
||||
small, text_x, text_y, fwin->language_label, strlen(fwin->language_label));
|
||||
WMReleaseColor(silver);
|
||||
}
|
||||
|
||||
WMReleaseFont(small);
|
||||
|
||||
wFrameWindowSetLanguageButtonImages(fwin, tmp, (wPreferences.new_style == TS_NEW) ? 2 : 1);
|
||||
}
|
||||
#endif /* XKB_BUTTON_HINT */
|
||||
|
||||
@@ -1286,7 +1430,7 @@ static void checkTitleSize(WFrameWindow * fwin)
|
||||
fwin->flags.incomplete_title = 0;
|
||||
}
|
||||
|
||||
static void paintButton(WCoreWindow * button, WTexture * texture, unsigned long color, WPixmap * image, int pushed)
|
||||
static void paintButton(WCoreWindow * button, WTexture * texture, unsigned long color, WPixmap * image, int pushed, int from_xpm)
|
||||
{
|
||||
WScreen *scr = button->screen_ptr;
|
||||
GC copy_gc = scr->copy_gc;
|
||||
@@ -1364,8 +1508,12 @@ static void paintButton(WCoreWindow * button, WTexture * texture, unsigned long
|
||||
} else {
|
||||
if (wPreferences.new_style == TS_OLD) {
|
||||
XSetForeground(dpy, copy_gc, scr->dark_pixel);
|
||||
XFillRectangle(dpy, button->window, copy_gc, 0, 0,
|
||||
button->width, button->height);
|
||||
if (from_xpm)
|
||||
XFillRectangle(dpy, button->window, copy_gc, 0, 0,
|
||||
button->width, button->height);
|
||||
else
|
||||
XCopyArea(dpy, image->image, button->window, copy_gc,
|
||||
left, 0, width, image->height, x, y);
|
||||
} else {
|
||||
XSetForeground(dpy, copy_gc, scr->black_pixel);
|
||||
XCopyArea(dpy, image->image, button->window, copy_gc,
|
||||
@@ -1379,7 +1527,11 @@ static void paintButton(WCoreWindow * button, WTexture * texture, unsigned long
|
||||
XSetForeground(dpy, copy_gc, color);
|
||||
XSetBackground(dpy, copy_gc, texture->any.color.pixel);
|
||||
}
|
||||
XFillRectangle(dpy, button->window, copy_gc, 0, 0, button->width, button->height);
|
||||
if (from_xpm)
|
||||
XFillRectangle(dpy, button->window, copy_gc, 0, 0, button->width, button->height);
|
||||
else
|
||||
XCopyArea(dpy, image->image, button->window, copy_gc,
|
||||
left, 0, width, image->height, x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1394,18 +1546,23 @@ static void handleButtonExpose(WObjDescriptor * desc, XEvent * event)
|
||||
|
||||
#ifdef XKB_BUTTON_HINT
|
||||
if (button == fwin->language_button) {
|
||||
if (!fwin->flags.hide_language_button)
|
||||
if (!fwin->flags.hide_language_button) {
|
||||
/* map focused and pfocused states to focus language button image */
|
||||
int lb_index = wPreferences.new_style == TS_NEW && (fwin->flags.state == 1) ? 1 : 0;
|
||||
|
||||
paintButton(button, fwin->title_texture[fwin->flags.state],
|
||||
WMColorPixel(fwin->title_color[fwin->flags.state]),
|
||||
fwin->languagebutton_image, False);
|
||||
fwin->languagebutton_image[lb_index],
|
||||
False, False);
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
if (button == fwin->left_button)
|
||||
paintButton(button, fwin->title_texture[fwin->flags.state],
|
||||
WMColorPixel(fwin->title_color[fwin->flags.state]), fwin->lbutton_image, False);
|
||||
WMColorPixel(fwin->title_color[fwin->flags.state]), fwin->lbutton_image, False, True);
|
||||
else
|
||||
paintButton(button, fwin->title_texture[fwin->flags.state],
|
||||
WMColorPixel(fwin->title_color[fwin->flags.state]), fwin->rbutton_image, False);
|
||||
WMColorPixel(fwin->title_color[fwin->flags.state]), fwin->rbutton_image, False, True);
|
||||
}
|
||||
|
||||
static void titlebarMouseDown(WObjDescriptor * desc, XEvent * event)
|
||||
@@ -1437,7 +1594,7 @@ static void buttonMouseDown(WObjDescriptor * desc, XEvent * event)
|
||||
WCoreWindow *button = desc->self;
|
||||
WPixmap *image;
|
||||
XEvent ev;
|
||||
int done = 0, execute = 1;
|
||||
int done = 0, execute = 1, from_xpm = True;
|
||||
WTexture *texture;
|
||||
unsigned long pixel;
|
||||
int clickButton = event->xbutton.button;
|
||||
@@ -1458,13 +1615,18 @@ static void buttonMouseDown(WObjDescriptor * desc, XEvent * event)
|
||||
if (button == fwin->language_button) {
|
||||
if (!wPreferences.modelock)
|
||||
return;
|
||||
image = fwin->languagebutton_image;
|
||||
|
||||
/* map focused and pfocused states to focus language button image */
|
||||
int lb_index = wPreferences.new_style == TS_NEW && (fwin->flags.state == 1) ? 1 : 0;
|
||||
|
||||
image = fwin->languagebutton_image[lb_index];
|
||||
from_xpm = False;
|
||||
}
|
||||
#endif
|
||||
|
||||
pixel = WMColorPixel(fwin->title_color[fwin->flags.state]);
|
||||
texture = fwin->title_texture[fwin->flags.state];
|
||||
paintButton(button, texture, pixel, image, True);
|
||||
paintButton(button, texture, pixel, image, True, from_xpm);
|
||||
|
||||
while (!done) {
|
||||
WMMaskEvent(dpy, LeaveWindowMask | EnterWindowMask | ButtonReleaseMask
|
||||
@@ -1472,12 +1634,12 @@ static void buttonMouseDown(WObjDescriptor * desc, XEvent * event)
|
||||
switch (ev.type) {
|
||||
case LeaveNotify:
|
||||
execute = 0;
|
||||
paintButton(button, texture, pixel, image, False);
|
||||
paintButton(button, texture, pixel, image, False, from_xpm);
|
||||
break;
|
||||
|
||||
case EnterNotify:
|
||||
execute = 1;
|
||||
paintButton(button, texture, pixel, image, True);
|
||||
paintButton(button, texture, pixel, image, True, from_xpm);
|
||||
break;
|
||||
|
||||
case ButtonPress:
|
||||
@@ -1492,7 +1654,7 @@ static void buttonMouseDown(WObjDescriptor * desc, XEvent * event)
|
||||
WMHandleEvent(&ev);
|
||||
}
|
||||
}
|
||||
paintButton(button, texture, pixel, image, False);
|
||||
paintButton(button, texture, pixel, image, False, from_xpm);
|
||||
|
||||
if (execute) {
|
||||
if (button == fwin->left_button) {
|
||||
|
||||
@@ -80,7 +80,7 @@ typedef struct WFrameWindow {
|
||||
WPixmap *lbutton_image;
|
||||
WPixmap *rbutton_image;
|
||||
#ifdef XKB_BUTTON_HINT
|
||||
WPixmap *languagebutton_image;
|
||||
WPixmap *languagebutton_image[2]; /* focused, unfocused */
|
||||
#endif
|
||||
|
||||
union WTexture **title_texture;
|
||||
@@ -93,6 +93,7 @@ typedef struct WFrameWindow {
|
||||
#ifdef KEEP_XKB_LOCK_STATUS
|
||||
int languagemode;
|
||||
int last_languagemode;
|
||||
char language_label[3]; /* 2-letter language code */
|
||||
#endif /* KEEP_XKB_LOCK_STATUS */
|
||||
|
||||
/* thing that uses this frame. passed as data to callbacks */
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Window Maker window manager
|
||||
*
|
||||
* Copyright (c) 1997-2003 Alfredo K. Kojima
|
||||
* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -22,6 +22,8 @@
|
||||
#ifndef WMKEYBIND_H
|
||||
#define WMKEYBIND_H
|
||||
|
||||
#define MAX_SHORTCUT_LENGTH 64
|
||||
|
||||
/* <X11/X.h> doesn't define these, even though XFree supports them */
|
||||
#ifndef Button6
|
||||
#define Button6 6
|
||||
@@ -44,6 +46,7 @@ enum {
|
||||
WKBD_ROOTMENU,
|
||||
WKBD_WINDOWMENU,
|
||||
WKBD_WINDOWLIST,
|
||||
WKBD_KEYCHAIN_CANCEL,
|
||||
|
||||
/* window */
|
||||
WKBD_MINIATURIZE,
|
||||
@@ -166,8 +169,13 @@ enum {
|
||||
};
|
||||
|
||||
typedef struct WShortKey {
|
||||
unsigned int modifier;
|
||||
KeyCode keycode;
|
||||
unsigned int modifier; /* leader (or only) key modifier - always valid */
|
||||
KeyCode keycode; /* leader (or only) key code - always valid */
|
||||
|
||||
/* Key-chain support */
|
||||
int chain_length;
|
||||
unsigned int *chain_modifiers; /* heap-allocated, NULL for single keys */
|
||||
KeyCode *chain_keycodes; /* heap-allocated, NULL for single keys */
|
||||
} WShortKey;
|
||||
|
||||
/* ---[ Global Variables ]------------------------------------------------ */
|
||||
@@ -177,5 +185,6 @@ extern WShortKey wKeyBindings[WKBD_LAST];
|
||||
/* ---[ Functions ]------------------------------------------------------- */
|
||||
|
||||
void wKeyboardInitialize(void);
|
||||
void wShortKeyFree(WShortKey *key);
|
||||
|
||||
#endif /* WMKEYBIND_H */
|
||||
|
||||
107
src/keytree.c
Normal file
107
src/keytree.c
Normal file
@@ -0,0 +1,107 @@
|
||||
/* keytree.c - Trie (prefix tree) for key-chain bindings
|
||||
*
|
||||
* Window Maker window manager
|
||||
*
|
||||
* Copyright (c) 2026 Window Maker Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "wconfig.h"
|
||||
#include <string.h>
|
||||
#include <WINGs/WUtil.h>
|
||||
#include "keytree.h"
|
||||
|
||||
/* Global trie root */
|
||||
WKeyNode *wKeyTreeRoot = NULL;
|
||||
|
||||
WKeyNode *wKeyTreeFind(WKeyNode *siblings, unsigned int mod, KeyCode key)
|
||||
{
|
||||
WKeyNode *p = siblings;
|
||||
|
||||
while (p != NULL) {
|
||||
if (p->modifier == mod && p->keycode == key)
|
||||
return p;
|
||||
p = p->next_sibling;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
WKeyNode *wKeyTreeInsert(WKeyNode **root, unsigned int *mods, KeyCode *keys, int nkeys)
|
||||
{
|
||||
WKeyNode **slot = root;
|
||||
WKeyNode *parent = NULL;
|
||||
int i;
|
||||
|
||||
if (nkeys <= 0)
|
||||
return NULL;
|
||||
|
||||
for (i = 0; i < nkeys; i++) {
|
||||
WKeyNode *node = wKeyTreeFind(*slot, mods[i], keys[i]);
|
||||
|
||||
if (node == NULL) {
|
||||
node = wmalloc(sizeof(WKeyNode));
|
||||
memset(node, 0, sizeof(WKeyNode));
|
||||
node->modifier = mods[i];
|
||||
node->keycode = keys[i];
|
||||
node->parent = parent;
|
||||
node->next_sibling = *slot;
|
||||
*slot = node;
|
||||
}
|
||||
|
||||
parent = node;
|
||||
slot = &node->first_child;
|
||||
}
|
||||
|
||||
return parent; /* leaf */
|
||||
}
|
||||
|
||||
|
||||
void wKeyTreeDestroy(WKeyNode *node)
|
||||
{
|
||||
/* Iterates siblings at each level, recurses only into children */
|
||||
while (node != NULL) {
|
||||
WKeyNode *next = node->next_sibling;
|
||||
WKeyAction *act, *next_act;
|
||||
|
||||
wKeyTreeDestroy(node->first_child);
|
||||
for (act = node->actions; act != NULL; act = next_act) {
|
||||
next_act = act->next;
|
||||
wfree(act);
|
||||
}
|
||||
wfree(node);
|
||||
node = next;
|
||||
}
|
||||
}
|
||||
|
||||
WKeyAction *wKeyNodeAddAction(WKeyNode *leaf, WKeyActionType type)
|
||||
{
|
||||
WKeyAction *act = wmalloc(sizeof(WKeyAction));
|
||||
WKeyAction *p;
|
||||
|
||||
memset(act, 0, sizeof(WKeyAction));
|
||||
act->type = type;
|
||||
|
||||
/* Append to end of list to preserve insertion order */
|
||||
if (leaf->actions == NULL) {
|
||||
leaf->actions = act;
|
||||
} else {
|
||||
p = leaf->actions;
|
||||
while (p->next)
|
||||
p = p->next;
|
||||
p->next = act;
|
||||
}
|
||||
return act;
|
||||
}
|
||||
97
src/keytree.h
Normal file
97
src/keytree.h
Normal file
@@ -0,0 +1,97 @@
|
||||
/* keytree.h - Trie (prefix tree) for key-chain bindings
|
||||
*
|
||||
* Window Maker window manager
|
||||
*
|
||||
* Copyright (c) 2026 Window Maker Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef WMKEYTREE_H
|
||||
#define WMKEYTREE_H
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
|
||||
/*
|
||||
* Each key in a binding sequence occupies one node in the trie.
|
||||
* Internal nodes (first_child != NULL) represent a prefix that has been
|
||||
* typed so far, leaf nodes carry the action payload.
|
||||
*/
|
||||
|
||||
typedef enum {
|
||||
WKN_WKBD, /* action: wKeyBindings command (WKBD_* index) */
|
||||
WKN_MENU /* action: root-menu entry callback */
|
||||
} WKeyActionType;
|
||||
|
||||
/*
|
||||
* A single action attached to a trie leaf node. Multiple actions may share
|
||||
* the same key sequence and are chained through the 'next' pointer, allowing
|
||||
* one key press to trigger several commands simultaneously.
|
||||
*/
|
||||
typedef struct WKeyAction {
|
||||
WKeyActionType type;
|
||||
union {
|
||||
int wkbd_idx; /* WKN_WKBD: WKBD_* enum value */
|
||||
struct {
|
||||
void *menu; /* WKN_MENU: cast to WMenu */
|
||||
void *entry; /* WKN_MENU: cast to WMenuEntry */
|
||||
} menu;
|
||||
} u;
|
||||
struct WKeyAction *next; /* next action for this key sequence, or NULL */
|
||||
} WKeyAction;
|
||||
|
||||
typedef struct WKeyNode {
|
||||
unsigned int modifier;
|
||||
KeyCode keycode;
|
||||
|
||||
WKeyAction *actions; /* non-NULL only for leaf nodes (first_child == NULL) */
|
||||
|
||||
struct WKeyNode *parent;
|
||||
struct WKeyNode *first_child; /* first key of next step in chain */
|
||||
struct WKeyNode *next_sibling; /* alternative binding at same depth */
|
||||
} WKeyNode;
|
||||
|
||||
/* Global trie root */
|
||||
extern WKeyNode *wKeyTreeRoot;
|
||||
|
||||
/*
|
||||
* Insert a key sequence into *root.
|
||||
* mods[0]/keys[0] - root (leader) key
|
||||
* mods[1..n-1]/keys[1..n-1] - follower keys
|
||||
* Shared prefixes are merged automatically.
|
||||
* Returns the leaf node (caller must set its type/payload).
|
||||
* Returns NULL if nkeys <= 0.
|
||||
*/
|
||||
WKeyNode *wKeyTreeInsert(WKeyNode **root, unsigned int *mods, KeyCode *keys, int nkeys);
|
||||
|
||||
/*
|
||||
* Find the first sibling in the list starting at 'siblings' that matches
|
||||
* (mod, key). Returns NULL if not found.
|
||||
*/
|
||||
WKeyNode *wKeyTreeFind(WKeyNode *siblings, unsigned int mod, KeyCode key);
|
||||
|
||||
/*
|
||||
* Recursively free the entire subtree rooted at 'node'.
|
||||
*/
|
||||
void wKeyTreeDestroy(WKeyNode *node);
|
||||
|
||||
/*
|
||||
* Allocate a new WKeyAction of the given type, append it to leaf->actions,
|
||||
* and return it for the caller to set the payload (wkbd_idx or menu.*).
|
||||
* Multiple calls with the same leaf accumulate actions in insertion order.
|
||||
*/
|
||||
WKeyAction *wKeyNodeAddAction(WKeyNode *leaf, WKeyActionType type);
|
||||
|
||||
#endif /* WMKEYTREE_H */
|
||||
85
src/misc.c
85
src/misc.c
@@ -881,46 +881,85 @@ char *GetShortcutString(const char *shortcut)
|
||||
|
||||
char *GetShortcutKey(WShortKey key)
|
||||
{
|
||||
const char *key_name;
|
||||
char buffer[256];
|
||||
char *wr;
|
||||
char buf[MAX_SHORTCUT_LENGTH];
|
||||
char *wr, *result, *seg;
|
||||
int step;
|
||||
|
||||
void append_string(const char *text)
|
||||
{
|
||||
const char *string = text;
|
||||
const char *s = text;
|
||||
|
||||
while (*string) {
|
||||
if (wr >= buffer + sizeof(buffer) - 1)
|
||||
while (*s) {
|
||||
if (wr >= buf + sizeof(buf) - 1)
|
||||
break;
|
||||
*wr++ = *string++;
|
||||
*wr++ = *s++;
|
||||
}
|
||||
}
|
||||
|
||||
void append_modifier(int modifier_index, const char *fallback_name)
|
||||
{
|
||||
if (wPreferences.modifier_labels[modifier_index]) {
|
||||
if (wPreferences.modifier_labels[modifier_index])
|
||||
append_string(wPreferences.modifier_labels[modifier_index]);
|
||||
} else {
|
||||
else
|
||||
append_string(fallback_name);
|
||||
}
|
||||
}
|
||||
|
||||
key_name = XKeysymToString(W_KeycodeToKeysym(dpy, key.keycode, 0));
|
||||
if (!key_name)
|
||||
Bool build_token(unsigned int mod, KeyCode kcode)
|
||||
{
|
||||
const char *kname = XKeysymToString(W_KeycodeToKeysym(dpy, kcode, 0));
|
||||
|
||||
if (!kname)
|
||||
return False;
|
||||
|
||||
wr = buf;
|
||||
if (mod & ControlMask) append_modifier(1, "Control+");
|
||||
if (mod & ShiftMask) append_modifier(0, "Shift+");
|
||||
if (mod & Mod1Mask) append_modifier(2, "Mod1+");
|
||||
if (mod & Mod2Mask) append_modifier(3, "Mod2+");
|
||||
if (mod & Mod3Mask) append_modifier(4, "Mod3+");
|
||||
if (mod & Mod4Mask) append_modifier(5, "Mod4+");
|
||||
if (mod & Mod5Mask) append_modifier(6, "Mod5+");
|
||||
append_string(kname);
|
||||
*wr = '\0';
|
||||
|
||||
return True;
|
||||
}
|
||||
|
||||
if (!build_token(key.modifier, key.keycode))
|
||||
return NULL;
|
||||
|
||||
wr = buffer;
|
||||
if (key.modifier & ControlMask) append_modifier(1, "Control+");
|
||||
if (key.modifier & ShiftMask) append_modifier(0, "Shift+");
|
||||
if (key.modifier & Mod1Mask) append_modifier(2, "Mod1+");
|
||||
if (key.modifier & Mod2Mask) append_modifier(3, "Mod2+");
|
||||
if (key.modifier & Mod3Mask) append_modifier(4, "Mod3+");
|
||||
if (key.modifier & Mod4Mask) append_modifier(5, "Mod4+");
|
||||
if (key.modifier & Mod5Mask) append_modifier(6, "Mod5+");
|
||||
append_string(key_name);
|
||||
*wr = '\0';
|
||||
/* Convert the leader token to its display string */
|
||||
result = GetShortcutString(buf);
|
||||
|
||||
return GetShortcutString(buffer);
|
||||
/* Append each chain follower separated by a space */
|
||||
for (step = 0; step < key.chain_length - 1; step++) {
|
||||
char *combined;
|
||||
|
||||
if (key.chain_keycodes[step] == 0)
|
||||
break;
|
||||
|
||||
if (!build_token(key.chain_modifiers[step], key.chain_keycodes[step]))
|
||||
break;
|
||||
|
||||
seg = GetShortcutString(buf);
|
||||
combined = wstrconcat(result, " ");
|
||||
wfree(result);
|
||||
result = wstrconcat(combined, seg);
|
||||
wfree(combined);
|
||||
wfree(seg);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void wShortKeyFree(WShortKey *key)
|
||||
{
|
||||
if (!key)
|
||||
return;
|
||||
|
||||
wfree(key->chain_modifiers);
|
||||
wfree(key->chain_keycodes);
|
||||
memset(key, 0, sizeof(*key));
|
||||
}
|
||||
|
||||
char *EscapeWM_CLASS(const char *name, const char *class)
|
||||
|
||||
198
src/rootmenu.c
198
src/rootmenu.c
@@ -60,8 +60,6 @@
|
||||
|
||||
#include <WINGs/WUtil.h>
|
||||
|
||||
#define MAX_SHORTCUT_LENGTH 32
|
||||
|
||||
static WMenu *readMenuPipe(WScreen * scr, char **file_name);
|
||||
static WMenu *readPLMenuPipe(WScreen * scr, char **file_name);
|
||||
static WMenu *readMenuFile(WScreen *scr, const char *file_name);
|
||||
@@ -75,6 +73,11 @@ typedef struct Shortcut {
|
||||
KeyCode keycode;
|
||||
WMenuEntry *entry;
|
||||
WMenu *menu;
|
||||
|
||||
/* Key-chain support */
|
||||
int chain_length;
|
||||
unsigned int *chain_modifiers; /* heap-allocated, NULL for single keys */
|
||||
KeyCode *chain_keycodes; /* heap-allocated, NULL for single keys */
|
||||
} Shortcut;
|
||||
|
||||
static Shortcut *shortcutList = NULL;
|
||||
@@ -320,27 +323,44 @@ static char *getLocalizedMenuFile(const char *menu)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Bool wRootMenuPerformShortcut(XEvent * event)
|
||||
/*
|
||||
* Insert all root-menu shortcuts into the
|
||||
* global key-binding trie (wKeyTreeRoot)
|
||||
*/
|
||||
void wRootMenuInsertIntoTree(void)
|
||||
{
|
||||
WScreen *scr = wScreenForRootWindow(event->xkey.root);
|
||||
Shortcut *ptr;
|
||||
int modifiers;
|
||||
int done = 0;
|
||||
|
||||
/* ignore CapsLock */
|
||||
modifiers = event->xkey.state & w_global.shortcut.modifiers_mask;
|
||||
|
||||
for (ptr = shortcutList; ptr != NULL; ptr = ptr->next) {
|
||||
if (ptr->keycode == 0 || ptr->menu->menu->screen_ptr != scr)
|
||||
unsigned int *mods;
|
||||
KeyCode *keys;
|
||||
int len, j;
|
||||
WKeyNode *leaf;
|
||||
|
||||
if (ptr->keycode == 0)
|
||||
continue;
|
||||
|
||||
if (ptr->keycode == event->xkey.keycode && ptr->modifier == modifiers) {
|
||||
(*ptr->entry->callback) (ptr->menu, ptr->entry);
|
||||
done = True;
|
||||
len = (ptr->chain_length > 1) ? ptr->chain_length : 1;
|
||||
mods = wmalloc(len * sizeof(unsigned int));
|
||||
keys = wmalloc(len * sizeof(KeyCode));
|
||||
mods[0] = ptr->modifier;
|
||||
keys[0] = ptr->keycode;
|
||||
|
||||
for (j = 1; j < len; j++) {
|
||||
mods[j] = ptr->chain_modifiers[j - 1];
|
||||
keys[j] = ptr->chain_keycodes[j - 1];
|
||||
}
|
||||
|
||||
leaf = wKeyTreeInsert(&wKeyTreeRoot, mods, keys, len);
|
||||
wfree(mods);
|
||||
wfree(keys);
|
||||
|
||||
if (leaf) {
|
||||
WKeyAction *act = wKeyNodeAddAction(leaf, WKN_MENU);
|
||||
act->u.menu.menu = ptr->menu;
|
||||
act->u.menu.entry = ptr->entry;
|
||||
}
|
||||
}
|
||||
|
||||
return done;
|
||||
}
|
||||
|
||||
void wRootMenuBindShortcuts(Window window)
|
||||
@@ -377,6 +397,16 @@ static void rebindKeygrabs(WScreen * scr)
|
||||
}
|
||||
}
|
||||
|
||||
static void freeShortcut(Shortcut *s)
|
||||
{
|
||||
if (!s)
|
||||
return;
|
||||
|
||||
wfree(s->chain_modifiers);
|
||||
wfree(s->chain_keycodes);
|
||||
wfree(s);
|
||||
}
|
||||
|
||||
static void removeShortcutsForMenu(WMenu * menu)
|
||||
{
|
||||
Shortcut *ptr, *tmp;
|
||||
@@ -386,7 +416,7 @@ static void removeShortcutsForMenu(WMenu * menu)
|
||||
while (ptr != NULL) {
|
||||
tmp = ptr->next;
|
||||
if (ptr->menu == menu) {
|
||||
wfree(ptr);
|
||||
freeShortcut(ptr);
|
||||
} else {
|
||||
ptr->next = newList;
|
||||
newList = ptr;
|
||||
@@ -400,51 +430,76 @@ static void removeShortcutsForMenu(WMenu * menu)
|
||||
static Bool addShortcut(const char *file, const char *shortcutDefinition, WMenu *menu, WMenuEntry *entry)
|
||||
{
|
||||
Shortcut *ptr;
|
||||
KeySym ksym;
|
||||
char *k;
|
||||
char buf[MAX_SHORTCUT_LENGTH], *b;
|
||||
char buf[MAX_SHORTCUT_LENGTH];
|
||||
char *token, *saveptr;
|
||||
int step;
|
||||
|
||||
ptr = wmalloc(sizeof(Shortcut));
|
||||
|
||||
wstrlcpy(buf, shortcutDefinition, MAX_SHORTCUT_LENGTH);
|
||||
b = (char *)buf;
|
||||
|
||||
/* get modifiers */
|
||||
ptr->modifier = 0;
|
||||
while ((k = strchr(b, '+')) != NULL) {
|
||||
int mod;
|
||||
/*
|
||||
* Parse space-separated tokens.
|
||||
* The first token is the leader key, subsequent tokens are chain steps
|
||||
*/
|
||||
step = 0;
|
||||
token = strtok_r(buf, " ", &saveptr);
|
||||
while (token != NULL) {
|
||||
KeySym ksym;
|
||||
KeyCode kcode;
|
||||
unsigned int mod = 0;
|
||||
char tmp[MAX_SHORTCUT_LENGTH];
|
||||
char *b, *k;
|
||||
|
||||
*k = 0;
|
||||
mod = wXModifierFromKey(b);
|
||||
if (mod < 0) {
|
||||
wwarning(_("%s: invalid key modifier \"%s\""), file, b);
|
||||
wfree(ptr);
|
||||
wstrlcpy(tmp, token, MAX_SHORTCUT_LENGTH);
|
||||
b = tmp;
|
||||
|
||||
while ((k = strchr(b, '+')) != NULL) {
|
||||
int m;
|
||||
*k = 0;
|
||||
m = wXModifierFromKey(b);
|
||||
if (m < 0) {
|
||||
wwarning(_("%s: invalid key modifier \"%s\""), file, b);
|
||||
freeShortcut(ptr);
|
||||
return False;
|
||||
}
|
||||
mod |= m;
|
||||
b = k + 1;
|
||||
}
|
||||
|
||||
ksym = XStringToKeysym(b);
|
||||
if (ksym == NoSymbol) {
|
||||
wwarning(_("%s: invalid kbd shortcut specification \"%s\" for entry %s"),
|
||||
file, shortcutDefinition, entry->text);
|
||||
freeShortcut(ptr);
|
||||
return False;
|
||||
}
|
||||
ptr->modifier |= mod;
|
||||
|
||||
b = k + 1;
|
||||
kcode = XKeysymToKeycode(dpy, ksym);
|
||||
if (kcode == 0) {
|
||||
wwarning(_("%s: invalid key in shortcut \"%s\" for entry %s"),
|
||||
file, shortcutDefinition, entry->text);
|
||||
freeShortcut(ptr);
|
||||
return False;
|
||||
}
|
||||
|
||||
if (step == 0) {
|
||||
ptr->modifier = mod;
|
||||
ptr->keycode = kcode;
|
||||
} else {
|
||||
ptr->chain_modifiers = wrealloc(ptr->chain_modifiers,
|
||||
step * sizeof(unsigned int));
|
||||
ptr->chain_keycodes = wrealloc(ptr->chain_keycodes,
|
||||
step * sizeof(KeyCode));
|
||||
ptr->chain_modifiers[step - 1] = mod;
|
||||
ptr->chain_keycodes[step - 1] = kcode;
|
||||
}
|
||||
|
||||
step++;
|
||||
token = strtok_r(NULL, " ", &saveptr);
|
||||
}
|
||||
|
||||
/* get key */
|
||||
ksym = XStringToKeysym(b);
|
||||
|
||||
if (ksym == NoSymbol) {
|
||||
wwarning(_("%s:invalid kbd shortcut specification \"%s\" for entry %s"),
|
||||
file, shortcutDefinition, entry->text);
|
||||
wfree(ptr);
|
||||
return False;
|
||||
}
|
||||
|
||||
ptr->keycode = XKeysymToKeycode(dpy, ksym);
|
||||
if (ptr->keycode == 0) {
|
||||
wwarning(_("%s:invalid key in shortcut \"%s\" for entry %s"), file,
|
||||
shortcutDefinition, entry->text);
|
||||
wfree(ptr);
|
||||
return False;
|
||||
}
|
||||
|
||||
ptr->menu = menu;
|
||||
ptr->chain_length = (step > 1) ? step : 1;
|
||||
ptr->menu = menu;
|
||||
ptr->entry = entry;
|
||||
|
||||
ptr->next = shortcutList;
|
||||
@@ -1563,6 +1618,47 @@ WMenu *configureMenu(WScreen *scr, WMPropList *definition)
|
||||
return menu;
|
||||
}
|
||||
|
||||
/*
|
||||
*----------------------------------------------------------------------
|
||||
* wRootMenuReparse--
|
||||
* Rebuild the root menu (and its shortcuts / key-grabs) from the
|
||||
* current WMRootMenu dictionary without mapping the menu.
|
||||
*----------------------------------------------------------------------
|
||||
*/
|
||||
void wRootMenuReparse(WScreen *scr)
|
||||
{
|
||||
WMenu *menu = NULL;
|
||||
WMPropList *definition;
|
||||
|
||||
definition = w_global.domain.root_menu->dictionary;
|
||||
if (!definition)
|
||||
return;
|
||||
|
||||
scr->flags.root_menu_changed_shortcuts = 0;
|
||||
scr->flags.added_workspace_menu = 0;
|
||||
scr->flags.added_windows_menu = 0;
|
||||
|
||||
if (WMIsPLArray(definition)) {
|
||||
if (!scr->root_menu ||
|
||||
w_global.domain.root_menu->timestamp > scr->root_menu->timestamp) {
|
||||
menu = configureMenu(scr, definition);
|
||||
if (menu)
|
||||
menu->timestamp = w_global.domain.root_menu->timestamp;
|
||||
}
|
||||
} else {
|
||||
menu = configureMenu(scr, definition);
|
||||
}
|
||||
|
||||
if (menu) {
|
||||
if (scr->root_menu)
|
||||
wMenuDestroy(scr->root_menu, True);
|
||||
scr->root_menu = menu;
|
||||
}
|
||||
|
||||
if (scr->flags.root_menu_changed_shortcuts)
|
||||
rebindKeygrabs(scr);
|
||||
}
|
||||
|
||||
/*
|
||||
*----------------------------------------------------------------------
|
||||
* OpenRootMenu--
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
#ifndef WMROOTMENU_H
|
||||
#define WMROOTMENU_H
|
||||
|
||||
Bool wRootMenuPerformShortcut(XEvent * event);
|
||||
void wRootMenuInsertIntoTree(void);
|
||||
void wRootMenuReparse(WScreen *scr);
|
||||
void wRootMenuBindShortcuts(Window window);
|
||||
void OpenRootMenu(WScreen * scr, int x, int y, int keyboard);
|
||||
WMenu *configureMenu(WScreen *scr, WMPropList *definition);
|
||||
|
||||
@@ -658,11 +658,10 @@ WScreen *wScreenInit(int screen_number)
|
||||
XSelectInput(dpy, scr->root_win, event_mask);
|
||||
|
||||
#ifdef KEEP_XKB_LOCK_STATUS
|
||||
/* Only GroupLock doesn't work correctly in my system since right-alt
|
||||
* can change mode while holding it too - ]d
|
||||
*/
|
||||
if (w_global.xext.xkb.supported)
|
||||
XkbSelectEvents(dpy, XkbUseCoreKbd, XkbIndicatorStateNotifyMask|XkbNewKeyboardNotifyMask, XkbIndicatorStateNotifyMask|XkbNewKeyboardNotifyMask);
|
||||
XkbSelectEvents(dpy, XkbUseCoreKbd,
|
||||
XkbIndicatorStateNotifyMask | XkbStateNotifyMask | XkbNewKeyboardNotifyMask,
|
||||
XkbIndicatorStateNotifyMask | XkbStateNotifyMask | XkbNewKeyboardNotifyMask);
|
||||
#else
|
||||
if (w_global.xext.xkb.supported)
|
||||
XkbSelectEvents(dpy, XkbUseCoreKbd, XkbNewKeyboardNotifyMask, XkbNewKeyboardNotifyMask);
|
||||
|
||||
@@ -425,6 +425,10 @@ void StartUp(Bool defaultScreenOnly)
|
||||
*/
|
||||
w_global.shortcut.modifiers_mask &= ~(_NumLockMask | _ScrollLockMask);
|
||||
|
||||
/* No active key chain at startup */
|
||||
w_global.shortcut.curpos = NULL;
|
||||
w_global.shortcut.chain_timeout_handler = NULL;
|
||||
|
||||
memset(&wKeyBindings, 0, sizeof(wKeyBindings));
|
||||
|
||||
w_global.context.client_win = XUniqueContext();
|
||||
@@ -704,6 +708,10 @@ void StartUp(Bool defaultScreenOnly)
|
||||
wSessionRestoreLastWorkspace(wScreen[j]);
|
||||
}
|
||||
|
||||
for (j = 0; j < w_global.screen_count; j++) {
|
||||
wKeyTreeRebuild(wScreen[j]);
|
||||
}
|
||||
|
||||
if (w_global.screen_count == 0) {
|
||||
wfatal(_("could not manage any screen"));
|
||||
Exit(1);
|
||||
|
||||
@@ -78,9 +78,6 @@
|
||||
|
||||
#include "framewin.h"
|
||||
|
||||
#define MAX_SHORTCUT_LENGTH 32
|
||||
|
||||
|
||||
typedef struct {
|
||||
WScreen *screen;
|
||||
WShortKey *key;
|
||||
|
||||
93
src/window.c
93
src/window.c
@@ -24,6 +24,7 @@
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/Xatom.h>
|
||||
#ifdef USE_XSHAPE
|
||||
#include <X11/extensions/shape.h>
|
||||
#endif
|
||||
@@ -38,6 +39,12 @@
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <math.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#ifdef XKB_BUTTON_HINT
|
||||
#include <X11/extensions/XKBfile.h> // Required for XkbRF_VarDefsRec
|
||||
#include <X11/extensions/XKBrules.h> // Required for XkbRF_GetNamesProp
|
||||
#endif
|
||||
|
||||
/* For getting mouse wheel mappings from WINGs */
|
||||
#include <WINGs/WINGs.h>
|
||||
@@ -2304,14 +2311,6 @@ void wWindowUpdateButtonImages(WWindow *wwin)
|
||||
fwin->lbutton_image = scr->b_pixmaps[WBUT_ICONIFY];
|
||||
}
|
||||
}
|
||||
#ifdef XKB_BUTTON_HINT
|
||||
if (!WFLAGP(wwin, no_language_button)) {
|
||||
if (fwin->languagebutton_image && !fwin->languagebutton_image->shared)
|
||||
wPixmapDestroy(fwin->languagebutton_image);
|
||||
|
||||
fwin->languagebutton_image = scr->b_pixmaps[WBUT_XKBGROUP1 + fwin->languagemode];
|
||||
}
|
||||
#endif
|
||||
|
||||
/* close button */
|
||||
|
||||
@@ -2639,6 +2638,11 @@ void wWindowSetKeyGrabs(WWindow * wwin)
|
||||
|
||||
if (key->keycode == 0)
|
||||
continue;
|
||||
|
||||
/* WKBD_KEYCHAIN_CANCEL is only meaningful while inside an active key chain */
|
||||
if (i == WKBD_KEYCHAIN_CANCEL)
|
||||
continue;
|
||||
|
||||
if (key->modifier != AnyModifier) {
|
||||
XGrabKey(dpy, key->keycode, key->modifier | LockMask,
|
||||
wwin->frame->core->window, True, GrabModeAsync, GrabModeAsync);
|
||||
@@ -3140,6 +3144,37 @@ static void windowCloseDblClick(WCoreWindow *sender, void *data, XEvent *event)
|
||||
}
|
||||
|
||||
#ifdef XKB_BUTTON_HINT
|
||||
/* Helper function to extract the 2-letter language code for a given XKB group index */
|
||||
void wWindowGetLanguageLabel(int group_index, char *label)
|
||||
{
|
||||
XkbRF_VarDefsRec vd;
|
||||
/* Default to empty - will fallback to pixmap if we can't get the label */
|
||||
label[0] = '\0';
|
||||
|
||||
if (XkbRF_GetNamesProp(dpy, NULL, &vd) && vd.layout) {
|
||||
int i;
|
||||
char *layout_list = strdup(vd.layout);
|
||||
char *tok = strtok(layout_list, ",");
|
||||
|
||||
/* Iterate to the requested group index */
|
||||
for (i = 0; i < group_index && tok != NULL; i++) {
|
||||
tok = strtok(NULL, ",");
|
||||
}
|
||||
|
||||
if (tok) {
|
||||
/* Copy exactly the first two bytes, then format: first uppercase, second lowercase */
|
||||
strncpy(label, tok, 2);
|
||||
label[2] = '\0';
|
||||
if (label[0])
|
||||
label[0] = (char) toupper((unsigned char) label[0]);
|
||||
if (label[1])
|
||||
label[1] = (char) tolower((unsigned char) label[1]);
|
||||
}
|
||||
|
||||
free(layout_list);
|
||||
}
|
||||
}
|
||||
|
||||
static void windowLanguageClick(WCoreWindow *sender, void *data, XEvent *event)
|
||||
{
|
||||
WWindow *wwin = data;
|
||||
@@ -3153,11 +3188,45 @@ static void windowLanguageClick(WCoreWindow *sender, void *data, XEvent *event)
|
||||
if (event->xbutton.button != Button1 && event->xbutton.button != Button3)
|
||||
return;
|
||||
tl = wwin->frame->languagemode;
|
||||
wwin->frame->languagemode = wwin->frame->last_languagemode;
|
||||
wwin->frame->last_languagemode = tl;
|
||||
|
||||
/* Try to advance to the next available XKB group */
|
||||
XkbDescPtr desc = XkbGetKeyboard(dpy, XkbAllComponentsMask, XkbUseCoreKbd);
|
||||
int newgroup = -1;
|
||||
if (desc && desc->names) {
|
||||
int i;
|
||||
const int MAX_GROUPS = 4; /* typical XKB max groups */
|
||||
for (i = 1; i <= MAX_GROUPS; i++) {
|
||||
int cand = (tl + i) % MAX_GROUPS;
|
||||
Atom a = desc->names->groups[cand];
|
||||
if (a != None) {
|
||||
/* Use XGetAtomName to ensure the atom actually has a name */
|
||||
char *nm = XGetAtomName(dpy, a);
|
||||
if (nm && nm[0] != '\0') {
|
||||
newgroup = cand;
|
||||
XFree(nm);
|
||||
break;
|
||||
}
|
||||
if (nm)
|
||||
XFree(nm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newgroup >= 0) {
|
||||
wwin->frame->last_languagemode = tl;
|
||||
wwin->frame->languagemode = newgroup;
|
||||
XkbLockGroup(dpy, XkbUseCoreKbd, wwin->frame->languagemode);
|
||||
} else {
|
||||
/* fallback to previous toggle behaviour for setups with only two
|
||||
* groups or when group info is not available */
|
||||
wwin->frame->languagemode = wwin->frame->last_languagemode;
|
||||
wwin->frame->last_languagemode = tl;
|
||||
XkbLockGroup(dpy, XkbUseCoreKbd, wwin->frame->languagemode);
|
||||
}
|
||||
/* Update label */
|
||||
wWindowGetLanguageLabel(wwin->frame->languagemode, wwin->frame->language_label);
|
||||
|
||||
wSetFocusTo(scr, wwin);
|
||||
wwin->frame->languagebutton_image =
|
||||
wwin->frame->screen_ptr->b_pixmaps[WBUT_XKBGROUP1 + wwin->frame->languagemode];
|
||||
wFrameWindowUpdateLanguageButton(wwin->frame);
|
||||
if (event->xbutton.button == Button3)
|
||||
return;
|
||||
|
||||
@@ -405,4 +405,9 @@ void wWindowDeleteSavedState(WMagicNumber id);
|
||||
Bool wWindowObscuresWindow(WWindow *wwin, WWindow *obscured);
|
||||
|
||||
void wWindowSetOmnipresent(WWindow *wwin, Bool flag);
|
||||
|
||||
#ifdef XKB_BUTTON_HINT
|
||||
void wWindowGetLanguageLabel(int group_index, char *label);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user