Add support for modmap None (#291)

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.
master
Wismill 2023-05-01 22:30:41 +02:00 committed by GitHub
parent 0e9c2ec97e
commit 5b5b67f28c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 343 additions and 8 deletions

View File

@ -675,6 +675,11 @@ test(
env: test_env, env: test_env,
suite: ['python-tests'], suite: ['python-tests'],
) )
test(
'modifiers',
executable('test-modifiers', 'test/modifiers.c', dependencies: test_dep),
env: test_env,
)
if get_option('enable-x11') if get_option('enable-x11')
test( test(
'x11', 'x11',

View File

@ -109,6 +109,9 @@
/* Don't allow more leds than we can hold in xkb_led_mask_t. */ /* 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)) #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. */ /* These should all go away. */
enum mod_type { enum mod_type {
MOD_REAL = (1 << 0), MOD_REAL = (1 << 0),

View File

@ -216,6 +216,9 @@ ModIndexText(struct xkb_context *ctx, const struct xkb_mod_set *mods,
if (ndx == XKB_MOD_INVALID) if (ndx == XKB_MOD_INVALID)
return "none"; return "none";
if (ndx == XKB_MOD_NONE)
return "None";
if (ndx >= mods->num_mods) if (ndx >= mods->num_mods)
return NULL; return NULL;

View File

@ -303,6 +303,7 @@ typedef struct {
typedef struct { typedef struct {
ParseCommon common; ParseCommon common;
enum merge_mode merge; enum merge_mode merge;
// NOTE: Can also be “None”, rather than a modifier name.
xkb_atom_t modifier; xkb_atom_t modifier;
ExprDef *keys; ExprDef *keys;
} ModMapDef; } ModMapDef;

View File

@ -162,6 +162,8 @@ ClearKeyInfo(KeyInfo *keyi)
typedef struct { typedef struct {
enum merge_mode merge; enum merge_mode merge;
bool haveSymbol; bool haveSymbol;
// NOTE: Can also be XKB_MOD_NONE, meaning
// “dont add a modifier to the modmap”.
xkb_mod_index_t modifier; xkb_mod_index_t modifier;
union { union {
xkb_atom_t keyName; xkb_atom_t keyName;
@ -1152,7 +1154,13 @@ HandleModMapDef(SymbolsInfo *info, ModMapDef *def)
xkb_mod_index_t ndx; xkb_mod_index_t ndx;
bool ok; bool ok;
struct xkb_context *ctx = info->ctx; struct xkb_context *ctx = info->ctx;
const char *modifier_name = xkb_atom_text(ctx, def->modifier);
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); ndx = XkbModNameToIndex(&info->mods, def->modifier, MOD_REAL);
if (ndx == XKB_MOD_INVALID) { if (ndx == XKB_MOD_INVALID) {
log_err(info->ctx, log_err(info->ctx,
@ -1161,6 +1169,7 @@ HandleModMapDef(SymbolsInfo *info, ModMapDef *def)
xkb_atom_text(ctx, def->modifier)); xkb_atom_text(ctx, def->modifier));
return false; return false;
} }
}
ok = true; ok = true;
tmp.modifier = ndx; tmp.modifier = ndx;
@ -1523,7 +1532,12 @@ CopyModMapDefToKeymap(struct xkb_keymap *keymap, SymbolsInfo *info,
} }
} }
// Handle modMap None
if (entry->modifier != XKB_MOD_NONE) {
// Convert modifier index to modifier mask
key->modmap |= (1u << entry->modifier); key->modmap |= (1u << entry->modifier);
}
return true; return true;
} }

View File

@ -0,0 +1,148 @@
xkb_keymap {
xkb_keycodes "test" {
minimum = 8;
maximum = 255;
<LVL3> = 92;
<LFSH> = 50;
<RTSH> = 62;
<LALT> = 64;
<RALT> = 108;
<LWIN> = 133;
<RWIN> = 134;
<LCTL> = 37;
<RCTL> = 105;
<CAPS> = 66;
<AD01> = 24;
<AD02> = 25;
<AD03> = 26;
<AD04> = 27;
<AD05> = 28;
<AD06> = 29;
<AD07> = 30;
<AD08> = 31;
<AD09> = 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 <LVL3> { [VoidSymbol] };
modmap None { <LVL3> };
// Reset modmap with one key
key <LFSH> { [Shift_L] };
modmap Shift { <LFSH> };
modmap none { <LFSH> };
// Reset modmap with one symbol
key <RTSH> { [Shift_R] };
modmap Shift { Shift_R };
modmap NONE { Shift_R };
// Cycle
key <LWIN> { [Super_L] };
modmap Mod4 { <LWIN> };
modmap None { <LWIN> };
modmap Mod4 { <LWIN> };
// Cycle
key <RWIN> { [Super_R] };
modmap Mod4 { <RWIN> };
modmap None { <RWIN> };
modmap Mod4 { Super_R };
// Mix keycode and keysym
key <LCTL> { [Control_L] };
modmap Control { Control_L };
modmap None { <LCTL> };
// Mix keycode and keysym
key <RCTL> { [Control_R] };
modmap Control { <RCTL> };
modmap None { Control_R };
// Re-set with different modifier
key <LALT> { [Alt_L] };
modmap Mod2 { <LALT> };
modmap None { <LALT> };
modmap Mod1 { <LALT> };
// Re-set with different modifier with both key and keysym
key <RALT> { [ISO_Level3_Shift] };
modmap Mod1 { <RALT> }; // Mod1
modmap None { <RALT> }; // None
modmap Mod2 { <RALT> }; // 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> { [Caps_Lock] };
modmap Mod1 { Caps_Lock }; // Mod1
modmap None { <CAPS> }; // Mod1
modmap None { Caps_Lock }; // None
modmap Lock { <CAPS> }; // Lock
// Merge modes
key <AD01> { [q] };
modMap Mod1 { <AD01> };
augment modMap None { <AD01> };
key <AD02> { [w] };
modMap Mod2 { <AD02> };
override modMap None { <AD02> };
key <AD03> { [e] };
modMap Mod3 { <AD03> };
replace modMap None { <AD03> };
key <AD04> { [r] };
modMap Mod1 { <AD04> };
augment modMap None { r };
key <AD05> { [t] };
modMap Mod2 { <AD05> };
replace modMap None { t };
key <AD06> { [y] };
modMap Mod3 { <AD06> };
override modMap None { y };
key <AD07> { [u] };
modMap Mod1 { <AD07> };
replace key <AD07> { [U] }; // Wont affect the modMap
key <AD08> { [i] };
modMap Mod2 { i, I };
replace key <AD08> { [I] };
modMap None { i }; // Does not resolve
key <AD09> { [1] };
modMap Mod3 { 1, 2 };
override key <AD09> { [NoSymbol, 2] };
modMap None { 1 };
};
};

View File

@ -47,6 +47,7 @@ main(void)
assert(test_file(ctx, "keymaps/no-types.xkb")); assert(test_file(ctx, "keymaps/no-types.xkb"));
assert(test_file(ctx, "keymaps/quartz.xkb")); assert(test_file(ctx, "keymaps/quartz.xkb"));
assert(test_file(ctx, "keymaps/no-aliases.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/divide-by-zero.xkb"));
assert(!test_file(ctx, "keymaps/bad.xkb")); assert(!test_file(ctx, "keymaps/bad.xkb"));

160
test/modifiers.c Normal file
View File

@ -0,0 +1,160 @@
/*
* Copyright © 2023 Pierre Le Marre <dev@wismill.eu>
*
* 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 <assert.h>
#include <stdlib.h>
#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;
}