Skip to content

Commit

Permalink
Add xkb_keymap_mod_get_overlapping_mods to API
Browse files Browse the repository at this point in the history
This new function enables:
- Checking if a modifier overlaps with another one.
  xkb_keymap_mod_get_overlapping_mods(keymap, mod, XKB_MOD_OVERLAPPING_NON_CANONICAL)
  For example, it allows to check if `Alt` and `Meta` are distinct
  modifiers and act in consequence.
- Get the mapping of a modifier
  xkb_keymap_mod_get_overlapping_mods(keymap, mod, XKB_MOD_OVERLAPPING_CANONICAL)
- Check if a modifier is mapped. For example:
  xkb_keymap_mod_get_overlapping_mods(keymap, mod, XKB_MOD_OVERLAPPING_CANONICAL)
  would return 0 if the modifier mod is not mapped.
  • Loading branch information
wismill committed Jul 4, 2023
1 parent 2b39a25 commit be5d231
Show file tree
Hide file tree
Showing 5 changed files with 341 additions and 3 deletions.
31 changes: 31 additions & 0 deletions include/xkbcommon/xkbcommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -1091,6 +1091,37 @@ xkb_keymap_mod_get_name(struct xkb_keymap *keymap, xkb_mod_index_t idx);
xkb_mod_index_t
xkb_keymap_mod_get_index(struct xkb_keymap *keymap, const char *name);

/**
* Overlapping modifier modes.
*
* @sa xkb_keymap_mod_get_overlapping_mods
* @since 1.6.0
* */
enum xkb_mod_overlapping_type {
/** Return only canonical overlapping modifiers. */
XKB_MOD_OVERLAPPING_CANONICAL = (1 << 1),
/** Return only non canonical overlapping modifiers. */
XKB_MOD_OVERLAPPING_NON_CANONICAL = (1 << 2),
/** Return every overlapping modifiers. */
XKB_MOD_OVERLAPPING_ALL = (XKB_MOD_OVERLAPPING_CANONICAL | XKB_MOD_OVERLAPPING_NON_CANONICAL),
};

/**
* Get the overlapping modifiers of a given modifier. Some modifiers may be
* encoded internally with overlaps.
*
* @returns The mask of overlapping modifiers. If no modifier is overlapping,
* returns 0.
*
* @sa xkb_mod_mask_t
* @memberof xkb_keymap
* @since 1.6.0
*/
xkb_mod_mask_t
xkb_keymap_mod_get_overlapping_mods(struct xkb_keymap *keymap,
xkb_mod_index_t idx,
enum xkb_mod_overlapping_type type);

/**
* Get the number of layouts in the keymap.
*
Expand Down
35 changes: 35 additions & 0 deletions src/keymap.c
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,41 @@ xkb_keymap_mod_get_index(struct xkb_keymap *keymap, const char *name)
return XkbModNameToIndex(&keymap->mods, atom, MOD_BOTH);
}

/**
* Return a mask of overlapping modifiers
*/
XKB_EXPORT xkb_mod_mask_t
xkb_keymap_mod_get_overlapping_mods(struct xkb_keymap *keymap,
xkb_mod_index_t idx,
enum xkb_mod_overlapping_type type)
{
if (idx >= keymap->mods.num_mods) {
return 0;
}

xkb_mod_mask_t mask = (is_real_mod(keymap, idx))
? (1u << idx)
: keymap->mods.mods[idx].mapping;

xkb_mod_mask_t overlapping = 0;

for (xkb_mod_index_t mod = 0; mod < keymap->mods.num_mods; mod++) {
if (mod == idx) {
continue;
} else if (keymap->mods.mods[mod].type & MOD_REAL) {
if ((type & XKB_MOD_OVERLAPPING_CANONICAL) && (mask & (1u << mod))) {
overlapping |= (1u << mod);
}
} else if ((type & XKB_MOD_OVERLAPPING_NON_CANONICAL) &&
keymap->mods.mods[mod].mapping &&
((mask & keymap->mods.mods[mod].mapping) ==
keymap->mods.mods[mod].mapping)) {
overlapping |= (1u << mod);
}
}
return overlapping;
}

/**
* Return the total number of active groups in the keymap.
*/
Expand Down
148 changes: 148 additions & 0 deletions test/data/keymaps/modifier-mapping.xkb
Original file line number Diff line number Diff line change
@@ -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;
<NMLK> = 77;
<LVL5> = 203;
<ALT> = 204;
<META> = 205;
<SUPR> = 206;
<HYPR> = 207;
<COMP> = 135;
};

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" {
virtual_modifiers NumLock,Alt,LevelThree,LevelFive,Meta,Super,Hyper,ScrollLock;

interpret.useModMapMods= AnyLevel;
interpret.repeat= False;

interpret Any+AnyOf(all) {
action= SetMods(modifiers=modMapMods,clearLocks);
};

interpret Caps_Lock+AnyOfOrNone(all) {
action= LockMods(modifiers=Lock);
};

interpret Num_Lock+AnyOf(all) {
virtualModifier= NumLock;
action= LockMods(modifiers=NumLock);
};

interpret Alt_L+AnyOf(all) {
virtualModifier= Alt;
action= SetMods(modifiers=modMapMods,clearLocks);
};
interpret Alt_R+AnyOf(all) {
virtualModifier= Alt;
action= SetMods(modifiers=modMapMods,clearLocks);
};
interpret Meta_L+AnyOf(all) {
virtualModifier= Meta;
action= SetMods(modifiers=modMapMods,clearLocks);
};
interpret Meta_R+AnyOf(all) {
virtualModifier= Meta;
action= SetMods(modifiers=modMapMods,clearLocks);
};
interpret Super_L+AnyOf(all) {
virtualModifier= Super;
action= SetMods(modifiers=modMapMods,clearLocks);
};
interpret Super_R+AnyOf(all) {
virtualModifier= Super;
action= SetMods(modifiers=modMapMods,clearLocks);
};
interpret Hyper_L+AnyOf(all) {
virtualModifier= Hyper;
action= SetMods(modifiers=modMapMods,clearLocks);
};
interpret Hyper_R+AnyOf(all) {
virtualModifier= Hyper;
action= SetMods(modifiers=modMapMods,clearLocks);
};
interpret ISO_Level3_Shift+AnyOf(all) {
virtualModifier= LevelThree;
useModMapMods=level1;
action= SetMods(modifiers=LevelThree,clearLocks);
};
interpret ISO_Level5_Shift+AnyOf(all) {
virtualModifier= LevelFive;
useModMapMods=level1;
action= SetMods(modifiers=LevelFive,clearLocks);
};
interpret Scroll_Lock+AnyOf(all) {
virtualModifier= ScrollLock;
action= LockMods(modifiers=modMapMods);
};
};

xkb_symbols {
name[group1]="Test";

key <CAPS> {[ Caps_Lock ]};
key <LFSH> {[ Shift_L ]};
key <RTSH> {[ Shift_R ]};
key <LCTL> {[ Control_L ]};
key <RCTL> {[ Control_R ]};
key <LALT> {[ Alt_L, Meta_L ]};
key <RALT> {[ Alt_R, Meta_R ]};
key <LWIN> {[ Super_L ]};
key <RWIN> {[ Super_R ]};
key <NMLK> {[ Num_Lock ]};

modifier_map Shift { Shift_L, Shift_R };
modifier_map Lock { Caps_Lock };
modifier_map Control { Control_L, Control_R };
modifier_map Mod1 { Alt_L, Alt_R, Meta_L, Meta_R };
modifier_map Mod2 { Num_Lock };
modifier_map Mod4 { Super_L, Super_R };

// Six fake keys for virtual<->real modifiers mapping:
key <LVL3> {[ ISO_Level3_Shift ]};
modifier_map Mod5 { <LVL3> };

key <LVL5> {[ ISO_Level5_Shift ]};
modifier_map Mod3 { <LVL5> };

key <ALT> {[ NoSymbol, Alt_L ]};
modifier_map Mod1 { <ALT> };

key <META> {[ NoSymbol, Meta_L ]};
modifier_map Mod1 { <META> };

key <SUPR> {[ NoSymbol, Super_L ]};
modifier_map Mod4 { <SUPR> };

// Make Hyper mapped to multiple real modifiers
key <HYPR> {[ NoSymbol, Hyper_L ]};
modifier_map Mod4 { <HYPR> };
key <COMP> {[ NoSymbol, Hyper_L ]};
modifier_map Mod3 { <COMP> };

};
};
125 changes: 122 additions & 3 deletions test/state.c
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,118 @@ print_state(struct xkb_state *state)
}
}

static void
test_overlapping_mods(struct xkb_keymap *keymap)
{
xkb_mod_index_t shift, caps, ctrl, alt, mod1, mod2, mod3, mod4, mod5,
meta, super, hyper, num, level3, level5, scroll;

/* Real modifiers */
shift = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_SHIFT);
assert(shift != XKB_MOD_INVALID);
caps = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_CAPS);
assert(caps != XKB_MOD_INVALID);
ctrl = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_CTRL);
assert(ctrl != XKB_MOD_INVALID);
mod1 = xkb_keymap_mod_get_index(keymap, "Mod1");
assert(mod1 != XKB_MOD_INVALID);
mod2 = xkb_keymap_mod_get_index(keymap, "Mod2");
assert(mod2 != XKB_MOD_INVALID);
mod3 = xkb_keymap_mod_get_index(keymap, "Mod3");
assert(mod3 != XKB_MOD_INVALID);
mod4 = xkb_keymap_mod_get_index(keymap, "Mod4");
assert(mod4 != XKB_MOD_INVALID);
mod5 = xkb_keymap_mod_get_index(keymap, "Mod5");
assert(mod5 != XKB_MOD_INVALID);

/* Virtual modifiers */
alt = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_ALT);
assert(alt != XKB_MOD_INVALID);
meta = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_META);
assert(alt != XKB_MOD_INVALID);
super = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_SUPER);
assert(super != XKB_MOD_INVALID);
hyper = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_HYPER);
assert(hyper != XKB_MOD_INVALID);
num = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_NUM);
assert(num != XKB_MOD_INVALID);
level3 = xkb_keymap_mod_get_index(keymap, "LevelThree");
assert(level3 != XKB_MOD_INVALID);
level5 = xkb_keymap_mod_get_index(keymap, "LevelFive");
assert(level5 != XKB_MOD_INVALID);
scroll = xkb_keymap_mod_get_index(keymap, "ScrollLock");
assert(scroll != XKB_MOD_INVALID);

xkb_mod_mask_t mod1_mask = (1u << mod1);
xkb_mod_mask_t mod2_mask = (1u << mod2);
xkb_mod_mask_t mod3_mask = (1u << mod3);
xkb_mod_mask_t mod4_mask = (1u << mod4);
xkb_mod_mask_t mod5_mask = (1u << mod5);
xkb_mod_mask_t alt_mask = (1u << alt);
xkb_mod_mask_t meta_mask = (1u << meta);
xkb_mod_mask_t super_mask = (1u << super);
// xkb_mod_mask_t hyper_mask = (1u << hyper);
xkb_mod_mask_t num_mask = (1u << num);
xkb_mod_mask_t level3_mask = (1u << level3);
xkb_mod_mask_t level5_mask = (1u << level5);
// xkb_mod_mask_t scroll_mask = (1u << scroll);

/* Real modifiers */
assert(xkb_keymap_mod_get_overlapping_mods(keymap, shift, XKB_MOD_OVERLAPPING_ALL) == 0);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, shift, XKB_MOD_OVERLAPPING_CANONICAL) == 0);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, shift, XKB_MOD_OVERLAPPING_NON_CANONICAL) == 0);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, caps, XKB_MOD_OVERLAPPING_ALL) == 0);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, caps, XKB_MOD_OVERLAPPING_CANONICAL) == 0);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, caps, XKB_MOD_OVERLAPPING_NON_CANONICAL) == 0);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, ctrl, XKB_MOD_OVERLAPPING_ALL) == 0);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, ctrl, XKB_MOD_OVERLAPPING_CANONICAL) == 0);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, ctrl, XKB_MOD_OVERLAPPING_NON_CANONICAL) == 0);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, mod1, XKB_MOD_OVERLAPPING_ALL) == (alt_mask | meta_mask));
assert(xkb_keymap_mod_get_overlapping_mods(keymap, mod1, XKB_MOD_OVERLAPPING_CANONICAL) == 0);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, mod1, XKB_MOD_OVERLAPPING_NON_CANONICAL) == (alt_mask | meta_mask));
assert(xkb_keymap_mod_get_overlapping_mods(keymap, mod2, XKB_MOD_OVERLAPPING_ALL) == num_mask);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, mod2, XKB_MOD_OVERLAPPING_CANONICAL) == 0);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, mod2, XKB_MOD_OVERLAPPING_NON_CANONICAL) == num_mask);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, mod3, XKB_MOD_OVERLAPPING_ALL) == level5_mask);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, mod3, XKB_MOD_OVERLAPPING_CANONICAL) == 0);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, mod3, XKB_MOD_OVERLAPPING_NON_CANONICAL) == level5_mask);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, mod4, XKB_MOD_OVERLAPPING_ALL) == super_mask);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, mod4, XKB_MOD_OVERLAPPING_CANONICAL) == 0);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, mod4, XKB_MOD_OVERLAPPING_NON_CANONICAL) == super_mask);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, mod5, XKB_MOD_OVERLAPPING_ALL) == level3_mask);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, mod5, XKB_MOD_OVERLAPPING_CANONICAL) == 0);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, mod5, XKB_MOD_OVERLAPPING_NON_CANONICAL) == level3_mask);

/* Virtual modifiers */
assert(xkb_keymap_mod_get_overlapping_mods(keymap, alt, XKB_MOD_OVERLAPPING_ALL) == (mod1_mask | meta_mask));
assert(xkb_keymap_mod_get_overlapping_mods(keymap, alt, XKB_MOD_OVERLAPPING_CANONICAL) == mod1_mask);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, alt, XKB_MOD_OVERLAPPING_NON_CANONICAL) == meta_mask);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, meta, XKB_MOD_OVERLAPPING_ALL) == (mod1_mask | alt_mask));
assert(xkb_keymap_mod_get_overlapping_mods(keymap, meta, XKB_MOD_OVERLAPPING_CANONICAL) == mod1_mask);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, meta, XKB_MOD_OVERLAPPING_NON_CANONICAL) == alt_mask);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, super, XKB_MOD_OVERLAPPING_ALL) == mod4_mask);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, super, XKB_MOD_OVERLAPPING_CANONICAL) == mod4_mask);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, super, XKB_MOD_OVERLAPPING_NON_CANONICAL) == 0);
/* NOTE: hyper is mapped to multiple real modifiers, so it matches
* multiple virtual modifiers as well */
assert(xkb_keymap_mod_get_overlapping_mods(keymap, hyper, XKB_MOD_OVERLAPPING_ALL) == (mod3_mask | mod4_mask | super_mask | level5_mask));
assert(xkb_keymap_mod_get_overlapping_mods(keymap, hyper, XKB_MOD_OVERLAPPING_CANONICAL) == (mod3_mask | mod4_mask));
assert(xkb_keymap_mod_get_overlapping_mods(keymap, hyper, XKB_MOD_OVERLAPPING_NON_CANONICAL) == (super_mask | level5_mask));
assert(xkb_keymap_mod_get_overlapping_mods(keymap, num, XKB_MOD_OVERLAPPING_ALL) == mod2_mask);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, num, XKB_MOD_OVERLAPPING_CANONICAL) == mod2_mask);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, num, XKB_MOD_OVERLAPPING_NON_CANONICAL) == 0);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, level3, XKB_MOD_OVERLAPPING_ALL) == mod5_mask);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, level3, XKB_MOD_OVERLAPPING_CANONICAL) == mod5_mask);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, level3, XKB_MOD_OVERLAPPING_NON_CANONICAL) == 0);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, level5, XKB_MOD_OVERLAPPING_ALL) == mod3_mask);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, level5, XKB_MOD_OVERLAPPING_CANONICAL) == mod3_mask);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, level5, XKB_MOD_OVERLAPPING_NON_CANONICAL) == 0);
/* NOTE: scroll not mapped */
assert(xkb_keymap_mod_get_overlapping_mods(keymap, scroll, XKB_MOD_OVERLAPPING_ALL) == 0);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, scroll, XKB_MOD_OVERLAPPING_CANONICAL) == 0);
assert(xkb_keymap_mod_get_overlapping_mods(keymap, scroll, XKB_MOD_OVERLAPPING_NON_CANONICAL) == 0);
}

static void
test_update_key(struct xkb_keymap *keymap)
{
Expand Down Expand Up @@ -361,11 +473,11 @@ test_update_mask_mods(struct xkb_keymap *keymap)
assert(caps != XKB_MOD_INVALID);
shift = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_SHIFT);
assert(shift != XKB_MOD_INVALID);
num = xkb_keymap_mod_get_index(keymap, "NumLock");
num = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_NUM);
assert(num != XKB_MOD_INVALID);
alt = xkb_keymap_mod_get_index(keymap, "Alt");
alt = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_ALT);
assert(alt != XKB_MOD_INVALID);
meta = xkb_keymap_mod_get_index(keymap, "Meta");
meta = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_META);
assert(alt != XKB_MOD_INVALID);
mod1 = xkb_keymap_mod_get_index(keymap, "Mod1");
assert(mod1 != XKB_MOD_INVALID);
Expand Down Expand Up @@ -779,6 +891,13 @@ main(void)
xkb_keymap_unref(NULL);
xkb_state_unref(NULL);

keymap = test_compile_file(context, "keymaps/modifier-mapping.xkb");
assert(keymap);

test_overlapping_mods(keymap);

xkb_keymap_unref(keymap);

keymap = test_compile_rules(context, "evdev", "pc104", "us,ru", NULL, "grp:menu_toggle");
assert(keymap);

Expand Down
5 changes: 5 additions & 0 deletions xkbcommon.map
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,8 @@ global:
xkb_utf32_to_keysym;
xkb_keymap_key_get_mods_for_level;
} V_0.8.0;

V_1.6.0 {
global:
xkb_keymap_mod_get_overlapping_mods;
} V_1.0.0;

0 comments on commit be5d231

Please sign in to comment.