From 5b5b67f28ccfe1fe69ec5569c90f1752de323a08 Mon Sep 17 00:00:00 2001 From: Wismill Date: Mon, 1 May 2023 22:30:41 +0200 Subject: [PATCH] Add support for modmap None (#291) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unlike current xkbcommon, X11’s xkbcomp allows to remove entries in the modifiers’ map using “modifier_map None { … }”. “None” is translated to the special value “XkbNoModifier” defined in “X11/extensions/XKB.h”. Then it relies on the fact that in "CopyModMapDef", the following code: 1U << entry->modifier ends up being zero when “entry->modifier” is “XkbNoModifier” (i.e. 0xFF). Indeed, it relies on the overflow behaviour of the left shift, which in practice resolves to use only the 5 low bits of the shift amount, i.e. 0x1F here. Then the result of “1U << 0xFF” is cast to “char”, i.e. 0. This is a good trick but too magical, so in libxkbcommon we will use an explicit test against our new constant XKB_MOD_NONE. --- meson.build | 5 + src/keymap.h | 3 + src/text.c | 3 + src/xkbcomp/ast.h | 1 + src/xkbcomp/symbols.c | 30 ++++-- test/data/keymaps/modmap-none.xkb | 148 +++++++++++++++++++++++++++ test/filecomp.c | 1 + test/modifiers.c | 160 ++++++++++++++++++++++++++++++ 8 files changed, 343 insertions(+), 8 deletions(-) create mode 100644 test/data/keymaps/modmap-none.xkb create mode 100644 test/modifiers.c diff --git a/meson.build b/meson.build index 61c2f82..d55e8f3 100644 --- a/meson.build +++ b/meson.build @@ -675,6 +675,11 @@ test( env: test_env, suite: ['python-tests'], ) +test( + 'modifiers', + executable('test-modifiers', 'test/modifiers.c', dependencies: test_dep), + env: test_env, +) if get_option('enable-x11') test( 'x11', diff --git a/src/keymap.h b/src/keymap.h index 7c5341d..f7ea5bd 100644 --- a/src/keymap.h +++ b/src/keymap.h @@ -109,6 +109,9 @@ /* Don't allow more leds than we can hold in xkb_led_mask_t. */ #define XKB_MAX_LEDS ((xkb_led_index_t) (sizeof(xkb_led_mask_t) * 8)) +/* Special value to handle modMap None {…} */ +#define XKB_MOD_NONE 0xffffffffU + /* These should all go away. */ enum mod_type { MOD_REAL = (1 << 0), diff --git a/src/text.c b/src/text.c index 60edb03..290a5ae 100644 --- a/src/text.c +++ b/src/text.c @@ -216,6 +216,9 @@ ModIndexText(struct xkb_context *ctx, const struct xkb_mod_set *mods, if (ndx == XKB_MOD_INVALID) return "none"; + if (ndx == XKB_MOD_NONE) + return "None"; + if (ndx >= mods->num_mods) return NULL; diff --git a/src/xkbcomp/ast.h b/src/xkbcomp/ast.h index 6c51ce4..8b0901f 100644 --- a/src/xkbcomp/ast.h +++ b/src/xkbcomp/ast.h @@ -303,6 +303,7 @@ typedef struct { typedef struct { ParseCommon common; enum merge_mode merge; + // NOTE: Can also be “None”, rather than a modifier name. xkb_atom_t modifier; ExprDef *keys; } ModMapDef; diff --git a/src/xkbcomp/symbols.c b/src/xkbcomp/symbols.c index eb78412..f990529 100644 --- a/src/xkbcomp/symbols.c +++ b/src/xkbcomp/symbols.c @@ -162,6 +162,8 @@ ClearKeyInfo(KeyInfo *keyi) typedef struct { enum merge_mode merge; bool haveSymbol; + // NOTE: Can also be XKB_MOD_NONE, meaning + // “don’t add a modifier to the modmap”. xkb_mod_index_t modifier; union { xkb_atom_t keyName; @@ -1152,14 +1154,21 @@ HandleModMapDef(SymbolsInfo *info, ModMapDef *def) xkb_mod_index_t ndx; bool ok; struct xkb_context *ctx = info->ctx; + const char *modifier_name = xkb_atom_text(ctx, def->modifier); - ndx = XkbModNameToIndex(&info->mods, def->modifier, MOD_REAL); - if (ndx == XKB_MOD_INVALID) { - log_err(info->ctx, - "Illegal modifier map definition; " - "Ignoring map for non-modifier \"%s\"\n", - xkb_atom_text(ctx, def->modifier)); - return false; + if (istreq(modifier_name, "none")) { + // Handle special "None" entry + ndx = XKB_MOD_NONE; + } else { + // Handle normal entry + ndx = XkbModNameToIndex(&info->mods, def->modifier, MOD_REAL); + if (ndx == XKB_MOD_INVALID) { + log_err(info->ctx, + "Illegal modifier map definition; " + "Ignoring map for non-modifier \"%s\"\n", + xkb_atom_text(ctx, def->modifier)); + return false; + } } ok = true; @@ -1523,7 +1532,12 @@ CopyModMapDefToKeymap(struct xkb_keymap *keymap, SymbolsInfo *info, } } - key->modmap |= (1u << entry->modifier); + // Handle modMap None + if (entry->modifier != XKB_MOD_NONE) { + // Convert modifier index to modifier mask + key->modmap |= (1u << entry->modifier); + } + return true; } diff --git a/test/data/keymaps/modmap-none.xkb b/test/data/keymaps/modmap-none.xkb new file mode 100644 index 0000000..a99b200 --- /dev/null +++ b/test/data/keymaps/modmap-none.xkb @@ -0,0 +1,148 @@ +xkb_keymap { +xkb_keycodes "test" { + minimum = 8; + maximum = 255; + = 92; + = 50; + = 62; + = 64; + = 108; + = 133; + = 134; + = 37; + = 105; + = 66; + + = 24; + = 25; + = 26; + = 27; + = 28; + = 29; + = 30; + = 31; + = 32; +}; + +xkb_types "complete" { + type "ONE_LEVEL" { + modifiers= none; + level_name[Level1]= "Any"; + }; + type "TWO_LEVEL" { + modifiers= Shift; + map[Shift]= 2; + level_name[1]= "Base"; + level_name[2]= "Shift"; + }; +}; +xkb_compatibility "complete" { + interpret.useModMapMods= AnyLevel; + interpret.repeat= False; + interpret.locking= False; + + interpret Any+AnyOf(all) { + action= SetMods(modifiers=modMapMods,clearLocks); + }; +}; +xkb_symbols { + name[group1]="Test"; + + // Reset modmap with a key with empty modmap + key { [VoidSymbol] }; + modmap None { }; + + // Reset modmap with one key + key { [Shift_L] }; + modmap Shift { }; + modmap none { }; + + // Reset modmap with one symbol + key { [Shift_R] }; + modmap Shift { Shift_R }; + modmap NONE { Shift_R }; + + // Cycle + key { [Super_L] }; + modmap Mod4 { }; + modmap None { }; + modmap Mod4 { }; + + // Cycle + key { [Super_R] }; + modmap Mod4 { }; + modmap None { }; + modmap Mod4 { Super_R }; + + // Mix keycode and keysym + key { [Control_L] }; + modmap Control { Control_L }; + modmap None { }; + + // Mix keycode and keysym + key { [Control_R] }; + modmap Control { }; + modmap None { Control_R }; + + // Re-set with different modifier + key { [Alt_L] }; + modmap Mod2 { }; + modmap None { }; + modmap Mod1 { }; + + // Re-set with different modifier with both key and keysym + key { [ISO_Level3_Shift] }; + modmap Mod1 { }; // Mod1 + modmap None { }; // None + modmap Mod2 { }; // Mod2 + modmap Mod3 { ISO_Level3_Shift }; // Mod2 | Mod3 + modmap None { ISO_Level3_Shift }; // Mod2 + modmap Mod5 { ISO_Level3_Shift }; // Mod2 | Mod5 + + // Re-set with different modifier with both key and keysym + key { [Caps_Lock] }; + modmap Mod1 { Caps_Lock }; // Mod1 + modmap None { }; // Mod1 + modmap None { Caps_Lock }; // None + modmap Lock { }; // Lock + + // Merge modes + key { [q] }; + modMap Mod1 { }; + augment modMap None { }; + + key { [w] }; + modMap Mod2 { }; + override modMap None { }; + + key { [e] }; + modMap Mod3 { }; + replace modMap None { }; + + key { [r] }; + modMap Mod1 { }; + augment modMap None { r }; + + key { [t] }; + modMap Mod2 { }; + replace modMap None { t }; + + key { [y] }; + modMap Mod3 { }; + override modMap None { y }; + + key { [u] }; + modMap Mod1 { }; + replace key { [U] }; // Won’t affect the modMap + + key { [i] }; + modMap Mod2 { i, I }; + replace key { [I] }; + modMap None { i }; // Does not resolve + + key { [1] }; + modMap Mod3 { 1, 2 }; + override key { [NoSymbol, 2] }; + modMap None { 1 }; +}; +}; diff --git a/test/filecomp.c b/test/filecomp.c index 827a19c..5fc7477 100644 --- a/test/filecomp.c +++ b/test/filecomp.c @@ -47,6 +47,7 @@ main(void) assert(test_file(ctx, "keymaps/no-types.xkb")); assert(test_file(ctx, "keymaps/quartz.xkb")); assert(test_file(ctx, "keymaps/no-aliases.xkb")); + assert(test_file(ctx, "keymaps/modmap-none.xkb")); assert(!test_file(ctx, "keymaps/divide-by-zero.xkb")); assert(!test_file(ctx, "keymaps/bad.xkb")); diff --git a/test/modifiers.c b/test/modifiers.c new file mode 100644 index 0000000..807c015 --- /dev/null +++ b/test/modifiers.c @@ -0,0 +1,160 @@ +/* + * Copyright © 2023 Pierre Le Marre + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +#include "config.h" + +#include +#include + +#include "test.h" +#include "keymap.h" + +// Standard real modifier masks +#define ShiftMask (1 << 0) +#define LockMask (1 << 1) +#define ControlMask (1 << 2) +#define Mod1Mask (1 << 3) +#define Mod2Mask (1 << 4) +#define Mod3Mask (1 << 5) +#define Mod4Mask (1 << 6) +#define Mod5Mask (1 << 7) +#define NoModifier 0 + +static void +test_modmap_none(void) +{ + struct xkb_context *context = test_get_context(0); + struct xkb_keymap *keymap; + const struct xkb_key *key; + xkb_keycode_t keycode; + + keymap = test_compile_file(context, "keymaps/modmap-none.xkb"); + assert(keymap); + + keycode = xkb_keymap_key_by_name(keymap, "LVL3"); + assert(keycode != XKB_KEYCODE_INVALID); + key = XkbKey(keymap, keycode); + assert(key->modmap == NoModifier); + + keycode = xkb_keymap_key_by_name(keymap, "LFSH"); + assert(keycode != XKB_KEYCODE_INVALID); + key = XkbKey(keymap, keycode); + assert(key->modmap == NoModifier); + + keycode = xkb_keymap_key_by_name(keymap, "RTSH"); + assert(keycode != XKB_KEYCODE_INVALID); + key = XkbKey(keymap, keycode); + assert(key->modmap == NoModifier); + + keycode = xkb_keymap_key_by_name(keymap, "LWIN"); + assert(keycode != XKB_KEYCODE_INVALID); + key = XkbKey(keymap, keycode); + assert(key->modmap == Mod4Mask); + + keycode = xkb_keymap_key_by_name(keymap, "RWIN"); + assert(keycode != XKB_KEYCODE_INVALID); + key = XkbKey(keymap, keycode); + assert(key->modmap == Mod4Mask); + + keycode = xkb_keymap_key_by_name(keymap, "LCTL"); + assert(keycode != XKB_KEYCODE_INVALID); + key = XkbKey(keymap, keycode); + assert(key->modmap == ControlMask); + + keycode = xkb_keymap_key_by_name(keymap, "RCTL"); + assert(keycode != XKB_KEYCODE_INVALID); + key = XkbKey(keymap, keycode); + assert(key->modmap == ControlMask); + + keycode = xkb_keymap_key_by_name(keymap, "LALT"); + assert(keycode != XKB_KEYCODE_INVALID); + key = XkbKey(keymap, keycode); + assert(key->modmap == Mod1Mask); + + keycode = xkb_keymap_key_by_name(keymap, "RALT"); + assert(keycode != XKB_KEYCODE_INVALID); + key = XkbKey(keymap, keycode); + assert(key->modmap == (Mod2Mask | Mod5Mask)); + + keycode = xkb_keymap_key_by_name(keymap, "CAPS"); + assert(keycode != XKB_KEYCODE_INVALID); + key = XkbKey(keymap, keycode); + assert(key->modmap == LockMask); + + keycode = xkb_keymap_key_by_name(keymap, "AD01"); + assert(keycode != XKB_KEYCODE_INVALID); + key = XkbKey(keymap, keycode); + assert(key->modmap == Mod1Mask); + + keycode = xkb_keymap_key_by_name(keymap, "AD02"); + assert(keycode != XKB_KEYCODE_INVALID); + key = XkbKey(keymap, keycode); + assert(key->modmap == NoModifier); + + keycode = xkb_keymap_key_by_name(keymap, "AD03"); + assert(keycode != XKB_KEYCODE_INVALID); + key = XkbKey(keymap, keycode); + assert(key->modmap == NoModifier); + + keycode = xkb_keymap_key_by_name(keymap, "AD04"); + assert(keycode != XKB_KEYCODE_INVALID); + key = XkbKey(keymap, keycode); + assert(key->modmap == Mod1Mask); + + keycode = xkb_keymap_key_by_name(keymap, "AD05"); + assert(keycode != XKB_KEYCODE_INVALID); + key = XkbKey(keymap, keycode); + assert(key->modmap == Mod2Mask); + + keycode = xkb_keymap_key_by_name(keymap, "AD06"); + assert(keycode != XKB_KEYCODE_INVALID); + key = XkbKey(keymap, keycode); + assert(key->modmap == Mod3Mask); + + keycode = xkb_keymap_key_by_name(keymap, "AD07"); + assert(keycode != XKB_KEYCODE_INVALID); + key = XkbKey(keymap, keycode); + assert(key->modmap == Mod1Mask); + + keycode = xkb_keymap_key_by_name(keymap, "AD08"); + assert(keycode != XKB_KEYCODE_INVALID); + key = XkbKey(keymap, keycode); + assert(key->modmap == Mod2Mask); + + keycode = xkb_keymap_key_by_name(keymap, "AD09"); + assert(keycode != XKB_KEYCODE_INVALID); + key = XkbKey(keymap, keycode); + assert(key->modmap == Mod3Mask); + + xkb_keymap_unref(keymap); + xkb_context_unref(context); +} + +int +main(void) +{ + test_modmap_none(); + + return 0; +}