/* * Copyright © 2023 Pierre Le Marre * SPDX-License-Identifier: MIT */ #include "config.h" #include "test-config.h" #include #include #include #include "xkbcommon/xkbcommon.h" #include "xkbcommon/xkbcommon-names.h" #include "evdev-scancodes.h" #include "test.h" #include "keymap.h" #include "utils.h" #define GOLDEN_TESTS_OUTPUTS "keymaps/" /* Standard real modifier masks */ enum real_mod_mask { ShiftMask = (UINT32_C(1) << XKB_MOD_INDEX_SHIFT), LockMask = (UINT32_C(1) << XKB_MOD_INDEX_CAPS), ControlMask = (UINT32_C(1) << XKB_MOD_INDEX_CTRL), Mod1Mask = (UINT32_C(1) << XKB_MOD_INDEX_MOD1), Mod2Mask = (UINT32_C(1) << XKB_MOD_INDEX_MOD2), Mod3Mask = (UINT32_C(1) << XKB_MOD_INDEX_MOD3), Mod4Mask = (UINT32_C(1) << XKB_MOD_INDEX_MOD4), Mod5Mask = (UINT32_C(1) << XKB_MOD_INDEX_MOD5), NoModifier = 0 }; /* Our keymap compiler is the xkbcommon buffer compiler */ static struct xkb_keymap * compile_buffer(struct xkb_context *context, enum xkb_keymap_format format, const char *buf, size_t len, void *private) { const enum xkb_keymap_compile_flags flags = (private) ? *(enum xkb_keymap_compile_flags*)private : TEST_KEYMAP_COMPILE_FLAGS; return test_compile_buffer2(context, format, flags, buf, len); } static bool test_real_mod(struct xkb_keymap *keymap, const char* name, xkb_mod_index_t idx, xkb_mod_mask_t mapping) { return xkb_keymap_mod_get_index(keymap, name) == idx && (keymap->mods.mods[idx].type == MOD_REAL) && mapping == keymap->mods.mods[idx].mapping && mapping == (UINT32_C(1) << idx) && xkb_keymap_mod_get_mask(keymap, name) == mapping && xkb_keymap_mod_get_mask2(keymap, idx) == mapping; } static bool test_virtual_mod(struct xkb_keymap *keymap, const char* name, xkb_mod_index_t idx, xkb_mod_mask_t mapping) { return xkb_keymap_mod_get_index(keymap, name) == idx && (keymap->mods.mods[idx].type == MOD_VIRT) && mapping == keymap->mods.mods[idx].mapping && xkb_keymap_mod_get_mask(keymap, name) == mapping && xkb_keymap_mod_get_mask2(keymap, idx) == mapping; } /* Check that the provided modifier names work */ static void test_modifiers_names(struct xkb_context *context) { struct xkb_keymap *keymap; keymap = test_compile_rules(context, XKB_KEYMAP_FORMAT_TEXT_V1, "evdev", "pc104", "us", NULL, NULL); assert(keymap); /* Real modifiers * The indices and masks are fixed and always valid */ assert(test_real_mod(keymap, XKB_MOD_NAME_SHIFT, XKB_MOD_INDEX_SHIFT, ShiftMask)); assert(test_real_mod(keymap, XKB_MOD_NAME_CAPS, XKB_MOD_INDEX_CAPS, LockMask)); assert(test_real_mod(keymap, XKB_MOD_NAME_CTRL, XKB_MOD_INDEX_CTRL, ControlMask)); assert(test_real_mod(keymap, XKB_MOD_NAME_MOD1, XKB_MOD_INDEX_MOD1, Mod1Mask)); assert(test_real_mod(keymap, XKB_MOD_NAME_MOD2, XKB_MOD_INDEX_MOD2, Mod2Mask)); assert(test_real_mod(keymap, XKB_MOD_NAME_MOD3, XKB_MOD_INDEX_MOD3, Mod3Mask)); assert(test_real_mod(keymap, XKB_MOD_NAME_MOD4, XKB_MOD_INDEX_MOD4, Mod4Mask)); assert(test_real_mod(keymap, XKB_MOD_NAME_MOD5, XKB_MOD_INDEX_MOD5, Mod5Mask)); /* Usual virtual mods mappings */ assert(test_real_mod(keymap, XKB_MOD_NAME_ALT, XKB_MOD_INDEX_MOD1, Mod1Mask)); assert(test_real_mod(keymap, XKB_MOD_NAME_NUM, XKB_MOD_INDEX_MOD2, Mod2Mask)); assert(test_real_mod(keymap, XKB_MOD_NAME_LOGO, XKB_MOD_INDEX_MOD4, Mod4Mask)); /* Virtual modifiers * The indices depends on the keymap files */ assert(test_virtual_mod(keymap, XKB_VMOD_NAME_ALT, XKB_MOD_INDEX_MOD5 + 2, Mod1Mask)); assert(test_virtual_mod(keymap, XKB_VMOD_NAME_META, XKB_MOD_INDEX_MOD5 + 11, Mod1Mask)); assert(test_virtual_mod(keymap, XKB_VMOD_NAME_NUM, XKB_MOD_INDEX_MOD5 + 1, Mod2Mask)); assert(test_virtual_mod(keymap, XKB_VMOD_NAME_SUPER, XKB_MOD_INDEX_MOD5 + 12, Mod4Mask)); assert(test_virtual_mod(keymap, XKB_VMOD_NAME_HYPER, XKB_MOD_INDEX_MOD5 + 13, Mod4Mask)); assert(test_virtual_mod(keymap, XKB_VMOD_NAME_LEVEL3, XKB_MOD_INDEX_MOD5 + 3, Mod5Mask)); assert(test_virtual_mod(keymap, XKB_VMOD_NAME_SCROLL, XKB_MOD_INDEX_MOD5 + 8, 0 )); /* TODO: current xkeyboard-config maps LevelFive to Nod3 by default */ assert(test_virtual_mod(keymap, XKB_VMOD_NAME_LEVEL5, XKB_MOD_INDEX_MOD5 + 9, 0)); /* Legacy stuff, removed from xkeyboard-config */ assert(keymap->mods.num_mods == 21); assert(test_virtual_mod(keymap, "LAlt", XKB_MOD_INDEX_MOD5 + 4, 0 )); assert(test_virtual_mod(keymap, "RAlt", XKB_MOD_INDEX_MOD5 + 5, 0 )); assert(test_virtual_mod(keymap, "LControl", XKB_MOD_INDEX_MOD5 + 7, 0 )); assert(test_virtual_mod(keymap, "RControl", XKB_MOD_INDEX_MOD5 + 6, 0 )); assert(test_virtual_mod(keymap, "AltGr", XKB_MOD_INDEX_MOD5 + 10, Mod5Mask)); xkb_keymap_unref(keymap); } static void test_modmap_none(struct xkb_context *context) { struct xkb_keymap *keymap; const struct xkb_key *key; xkb_keycode_t keycode; keymap = test_compile_file( context, XKB_KEYMAP_FORMAT_TEXT_V2, "keymaps/modmap-none.xkb" ); assert(keymap); static const struct { const char *key_name; enum real_mod_mask modmap; } tests[] = { { "LVL3", NoModifier }, { "LFSH", NoModifier }, { "RTSH", NoModifier }, { "LWIN", Mod4Mask }, { "RWIN", Mod4Mask }, { "LCTL", ControlMask }, { "RCTL", ControlMask }, { "LALT", Mod1Mask }, { "RALT", (Mod2Mask | Mod5Mask) }, { "CAPS", LockMask }, { "AD01", Mod1Mask }, { "AD02", NoModifier }, { "AD03", NoModifier }, { "AD04", Mod1Mask }, { "AD05", Mod2Mask }, { "AD06", Mod3Mask }, { "AD07", Mod1Mask }, { "AD08", Mod2Mask }, { "AD09", Mod3Mask }, { "AD10", (Mod3Mask | Mod4Mask) }, { "AD11", (Mod3Mask | Mod4Mask | Mod5Mask) }, }; for (size_t t = 0; t < ARRAY_SIZE(tests); t++) { fprintf(stderr, "------\n*** %s #%zu: key %s ***\n", __func__, t, tests[t].key_name); keycode = xkb_keymap_key_by_name(keymap, tests[t].key_name); assert(keycode != XKB_KEYCODE_INVALID); key = XkbKey(keymap, keycode); assert(key); assert(key->modmap == (xkb_mod_mask_t)tests[t].modmap); } xkb_keymap_unref(keymap); } static void test_modmap_mask(struct xkb_context *ctx, bool update_output_files) { static const struct { const char * keymap; struct { const char * v1; const char * v2; } expected; } tests[] = { { .keymap = "xkb_keymap {\n" " xkb_keycodes { <> = 1; };\n" " xkb_compat {\n" " virtual_modifiers Alt;\n" " interpret Shift_L + Any {\n" " action = SetMods(modifiers=Shift);\n" " };\n" " interpret Alt_L + Any {\n" " virtualModifier = Alt;\n" " action = SetMods(modifiers=Alt);\n" " };\n" " };\n" " xkb_types {\n" " type \"TWO_LEVEL\" {\n" " modifiers = Shift;\n" " map[Shift] = 2;\n" " };\n" " };\n" " xkb_symbols {\n" " key <> { [Alt_L, Shift_L] };\n" " modifier_map Shift { Shift_L };\n" " modifier_map Mod1 { <> };\n" " };\n" "};", .expected = { .v1 = GOLDEN_TESTS_OUTPUTS "modmap-key+keysym-v1.xkb", .v2 = GOLDEN_TESTS_OUTPUTS "modmap-key+keysym-v2.xkb", } }, { .keymap = "xkb_keymap {\n" " xkb_keycodes { <> = 1; };\n" " xkb_symbols {\n" " modifier_map Shift+Mod1 { <> };\n" " };\n" "};", .expected = { .v1 = NULL, .v2 = GOLDEN_TESTS_OUTPUTS "modmap-identifier-mask-v2.xkb", } }, { .keymap = "xkb_keymap {\n" " xkb_keycodes { <> = 1; };\n" " xkb_symbols {\n" " modifier_map 0x9 { <> };\n" " };\n" "};", .expected = { .v1 = NULL, .v2 = GOLDEN_TESTS_OUTPUTS "modmap-identifier-mask-v2.xkb", } }, }; for (uint8_t t = 0; t < (uint8_t)ARRAY_SIZE(tests); t++) { fprintf(stderr, "------\n*** %s: #%u ***\n", __func__, t); assert(test_compile_output(ctx, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_USE_ORIGINAL_FORMAT, compile_buffer, NULL, __func__, tests[t].keymap, strlen(tests[t].keymap), tests[t].expected.v1, update_output_files)); assert(test_compile_output(ctx, XKB_KEYMAP_FORMAT_TEXT_V2, XKB_KEYMAP_USE_ORIGINAL_FORMAT, compile_buffer, NULL, __func__, tests[t].keymap, strlen(tests[t].keymap), tests[t].expected.v2, update_output_files)); } } static void test_explicit_virtual_modifiers(struct xkb_context *context) { static const struct { const char* keymap; struct mod_props { enum mod_type type; xkb_mod_mask_t mapping; xkb_mod_mask_t mapping_effective; } m1; struct mod_props m2; } tests[] = { /* Test virtual modifiers with canonical mappings */ { .keymap = "xkb_keymap {\n" " xkb_compat {\n" /* Vmods map to themselves */ " virtual_modifiers M1 = 0x100, M2 = 0x200;\n" " };\n" "};", .m1 = { .type = MOD_VIRT, .mapping = 0x100, .mapping_effective = 0x100 }, .m2 = { .type = MOD_VIRT, .mapping = 0x200, .mapping_effective = 0x200 } }, /* Test virtual modifiers overlapping: identical */ { .keymap = "xkb_keymap {\n" " xkb_compat {\n" " virtual_modifiers M1 = 0x100, M2 = 0x100;\n" " };\n" "};", .m1 = { .type = MOD_VIRT, .mapping = 0x100, .mapping_effective = 0x100 }, .m2 = { .type = MOD_VIRT, .mapping = 0x100, .mapping_effective = 0x100 } }, /* Test virtual modifiers overlapping: non identical */ { .keymap = "xkb_keymap {\n" " xkb_compat {\n" " virtual_modifiers M1 = 0x100, M2 = 0x300;\n" " };\n" "};", .m1 = { .type = MOD_VIRT, .mapping = 0x100, .mapping_effective = 0x100 }, .m2 = { .type = MOD_VIRT, .mapping = 0x300, .mapping_effective = 0x300 } }, /* Test virtual modifiers with swapped mappings */ { .keymap = "xkb_keymap {\n" " xkb_compat {\n" /* The mapping of each modifier is the mask of the other */ " virtual_modifiers M1 = 0x200, M2 = 0x100;\n" " };\n" "};", .m1 = { .type = MOD_VIRT, .mapping = 0x200, .mapping_effective = 0x100 /* different from mapping! */ }, .m2 = { .type = MOD_VIRT, .mapping = 0x100, .mapping_effective = 0x200 /* different from mapping! */ } }, /* Test virtual modifiers mapping to undefined modifiers */ { .keymap = "xkb_keymap {\n" " xkb_compat {\n" " virtual_modifiers M1 = 0x400, M2 = 0x800;\n" " };\n" "};", .m1 = { .type = MOD_VIRT, .mapping = 0x400, .mapping_effective = 0 /* no mod entry */ }, .m2 = { .type = MOD_VIRT, .mapping = 0x800, .mapping_effective = 0 /* no mod entry */ } }, }; for (unsigned int k = 0; k < ARRAY_SIZE(tests); k++) { fprintf(stderr, "------\n*** %s: #%u ***\n", __func__, k); struct xkb_keymap *keymap = test_compile_buffer( context, XKB_KEYMAP_FORMAT_TEXT_V1, tests[k].keymap, strlen(tests[k].keymap) ); assert(keymap); const xkb_mod_index_t m1_idx = xkb_keymap_mod_get_index(keymap, "M1"); const xkb_mod_index_t m2_idx = xkb_keymap_mod_get_index(keymap, "M2"); assert(m1_idx == 8); assert(m2_idx == 9); assert(keymap->mods.mods[m1_idx].type == tests[k].m1.type); assert(keymap->mods.mods[m2_idx].type == tests[k].m2.type); const xkb_mod_mask_t m1 = UINT32_C(1) << m1_idx; const xkb_mod_mask_t m2 = UINT32_C(1) << m2_idx; const xkb_mod_mask_t m1_mapping = mod_mask_get_effective(keymap, m1); const xkb_mod_mask_t m2_mapping = mod_mask_get_effective(keymap, m2); assert(m1_mapping == tests[k].m1.mapping); assert(m2_mapping == tests[k].m2.mapping); /* mod_mask_get_effective is not idempotent */ assert(mod_mask_get_effective(keymap, m1_mapping) == tests[k].m1.mapping_effective); assert(mod_mask_get_effective(keymap, m2_mapping) == tests[k].m2.mapping_effective); struct xkb_state *state = xkb_state_new_with_mode(keymap, XKB_STATE_MODE_CLIENT); assert(state); /* Not in the canonical modifier mask nor denotes a *known* virtual * modifiers, so it will be discarded. */ const xkb_mod_mask_t noise = UINT32_C(0x8000); assert((keymap->canonical_state_mask & noise) == 0); /* Update the state, then check round-trip and mods state */ const xkb_mod_mask_t set_masks[] = {m1_mapping, m2_mapping}; for (unsigned int m = 0; m < ARRAY_SIZE(set_masks); m++) { const xkb_mod_mask_t expected = set_masks[m]; xkb_state_update_mask(state, expected | noise, 0, noise, 0, 0, 0); const xkb_mod_mask_t got = xkb_state_serialize_mods(state, XKB_STATE_MODS_EFFECTIVE); assert_printf(got == expected, /* round-trip */ "expected %#"PRIx32", got %#"PRIx32"\n", expected, got); assert( xkb_state_mod_index_is_active(state, m1_idx, XKB_STATE_MODS_EFFECTIVE) == ((expected & m1_mapping) == m1_mapping) ); assert( xkb_state_mod_index_is_active(state, m2_idx, XKB_STATE_MODS_EFFECTIVE) == ((expected & m2_mapping) == m2_mapping) ); } xkb_state_unref(state); xkb_keymap_unref(keymap); } } /* * Test the hack documented in the FAQ to get virtual modifiers mapping using * `xkb_state_update_mask`/`xkb_state_serialize_mods`. * * This should work without problem for keymap using only real mods to map * virtual modifiers. * * [NOTE] If the test requires an update, do not forget to update the FAQ as well! */ static void test_virtual_modifiers_mapping_hack(struct xkb_context *context) { struct xkb_keymap *keymap = test_compile_rules(context, XKB_KEYMAP_FORMAT_TEXT_V1, "evdev", "pc104", "us", NULL, NULL); assert(keymap); struct xkb_state* state = xkb_state_new_with_mode(keymap, XKB_STATE_MODE_CLIENT); assert(state); static const struct { const char* name; xkb_mod_index_t index; xkb_mod_mask_t mapping; } mods[] = { /* Real modifiers */ { .name = XKB_MOD_NAME_SHIFT, .index = XKB_MOD_INDEX_SHIFT, .mapping = UINT32_C(1) << XKB_MOD_INDEX_SHIFT }, { .name = XKB_MOD_NAME_CAPS, .index = XKB_MOD_INDEX_CAPS, .mapping = UINT32_C(1) << XKB_MOD_INDEX_CAPS }, { .name = XKB_MOD_NAME_CTRL, .index = XKB_MOD_INDEX_CTRL, .mapping = UINT32_C(1) << XKB_MOD_INDEX_CTRL }, { .name = XKB_MOD_NAME_MOD1, .index = XKB_MOD_INDEX_MOD1, .mapping = UINT32_C(1) << XKB_MOD_INDEX_MOD1 }, { .name = XKB_MOD_NAME_MOD2, .index = XKB_MOD_INDEX_MOD2, .mapping = UINT32_C(1) << XKB_MOD_INDEX_MOD2 }, { .name = XKB_MOD_NAME_MOD3, .index = XKB_MOD_INDEX_MOD3, .mapping = UINT32_C(1) << XKB_MOD_INDEX_MOD3 }, { .name = XKB_MOD_NAME_MOD4, .index = XKB_MOD_INDEX_MOD4, .mapping = UINT32_C(1) << XKB_MOD_INDEX_MOD4 }, { .name = XKB_MOD_NAME_MOD5, .index = XKB_MOD_INDEX_MOD5, .mapping = UINT32_C(1) << XKB_MOD_INDEX_MOD5 }, /* Virtual modifiers * The indices depends on the keymap files */ { .name = XKB_VMOD_NAME_ALT, .index = XKB_MOD_INDEX_MOD5 + 2, .mapping = Mod1Mask }, { .name = XKB_VMOD_NAME_META, .index = XKB_MOD_INDEX_MOD5 + 11, .mapping = Mod1Mask }, { .name = XKB_VMOD_NAME_NUM, .index = XKB_MOD_INDEX_MOD5 + 1, .mapping = Mod2Mask }, { .name = XKB_VMOD_NAME_SUPER, .index = XKB_MOD_INDEX_MOD5 + 12, .mapping = Mod4Mask }, { .name = XKB_VMOD_NAME_HYPER, .index = XKB_MOD_INDEX_MOD5 + 13, .mapping = Mod4Mask }, { .name = XKB_VMOD_NAME_LEVEL3, .index = XKB_MOD_INDEX_MOD5 + 3, .mapping = Mod5Mask }, { .name = XKB_VMOD_NAME_SCROLL, .index = XKB_MOD_INDEX_MOD5 + 8, .mapping = 0 }, { .name = XKB_VMOD_NAME_LEVEL5, .index = XKB_MOD_INDEX_MOD5 + 9, .mapping = 0 }, /* Legacy stuff, removed from xkeyboard-config */ { .name = "LAlt", .index = XKB_MOD_INDEX_MOD5 + 4, .mapping = 0 }, { .name = "RAlt", .index = XKB_MOD_INDEX_MOD5 + 5, .mapping = 0 }, { .name = "LControl", .index = XKB_MOD_INDEX_MOD5 + 7, .mapping = 0 }, { .name = "RControl", .index = XKB_MOD_INDEX_MOD5 + 6, .mapping = 0 }, { .name = "AltGr", .index = XKB_MOD_INDEX_MOD5 + 10, .mapping = Mod5Mask }, }; for (unsigned int k = 0; k < ARRAY_SIZE(mods); k++) { const xkb_mod_mask_t index = xkb_keymap_mod_get_index(keymap, mods[k].name); assert_printf(index == mods[k].index, "%s: expected %"PRIu32", got: %"PRIu32"\n", mods[k].name, mods[k].index, index); const xkb_mod_mask_t mask = UINT32_C(1) << index; xkb_state_update_mask(state, mask, 0, 0, 0, 0, 0); const xkb_mod_mask_t mapping = xkb_state_serialize_mods(state, XKB_STATE_MODS_EFFECTIVE); assert_printf(mapping == mods[k].mapping, "%s: expected %#"PRIx32", got: %#"PRIx32"\n", mods[k].name, mods[k].mapping, mapping); assert(mapping == xkb_keymap_mod_get_mask(keymap, mods[k].name)); assert(mapping == xkb_keymap_mod_get_mask2(keymap, mods[k].index)); } xkb_state_unref(state); xkb_keymap_unref(keymap); } static void test_pure_virtual_modifiers(struct xkb_context *context) { struct xkb_keymap *keymap; /* Test definition of >20 pure virtual modifiers. * We overcome the X11 limit of 16 virtual modifiers. */ struct { const char* path; enum xkb_keymap_format formats[2]; } keymaps[] = { { .path = "keymaps/pure-virtual-mods-explicit.xkb", .formats = { XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_FORMAT_TEXT_V2 } }, { .path = "keymaps/pure-virtual-mods-implicit.xkb", /* Auto canonical vmod available since keymap format v2 */ .formats = { XKB_KEYMAP_FORMAT_TEXT_V2 } } }; for (unsigned int k = 0; k < ARRAY_SIZE(keymaps); k++) { for (unsigned int f = 0; f < ARRAY_SIZE(keymaps[k].formats); f++) { if (!keymaps[k].formats[f]) break; fprintf(stderr, "------\n*** %s: #%u %s (%d) ***\n", __func__, k, keymaps[k].path, keymaps[k].formats[f]); keymap = test_compile_file(context, keymaps[k].formats[f], keymaps[k].path); assert(keymap); assert(test_key_seq(keymap, KEY_W, BOTH, XKB_KEY_w, NEXT, KEY_A, DOWN, XKB_KEY_a, NEXT, KEY_W, BOTH, XKB_KEY_a, NEXT, KEY_A, UP, XKB_KEY_a, NEXT, KEY_B, DOWN, XKB_KEY_b, NEXT, KEY_W, BOTH, XKB_KEY_b, NEXT, KEY_B, UP, XKB_KEY_b, NEXT, KEY_C, DOWN, XKB_KEY_c, NEXT, KEY_W, BOTH, XKB_KEY_c, NEXT, KEY_C, UP, XKB_KEY_c, NEXT, KEY_D, DOWN, XKB_KEY_d, NEXT, KEY_W, BOTH, XKB_KEY_d, NEXT, KEY_D, UP, XKB_KEY_d, NEXT, KEY_E, DOWN, XKB_KEY_e, NEXT, KEY_W, BOTH, XKB_KEY_e, NEXT, KEY_E, UP, XKB_KEY_e, NEXT, KEY_F, DOWN, XKB_KEY_f, NEXT, KEY_W, BOTH, XKB_KEY_f, NEXT, KEY_F, UP, XKB_KEY_f, NEXT, KEY_G, DOWN, XKB_KEY_g, NEXT, KEY_W, BOTH, XKB_KEY_g, NEXT, KEY_G, UP, XKB_KEY_g, NEXT, KEY_H, DOWN, XKB_KEY_h, NEXT, KEY_W, BOTH, XKB_KEY_h, NEXT, KEY_H, UP, XKB_KEY_h, NEXT, KEY_I, DOWN, XKB_KEY_i, NEXT, KEY_W, BOTH, XKB_KEY_i, NEXT, KEY_I, UP, XKB_KEY_i, NEXT, KEY_J, DOWN, XKB_KEY_j, NEXT, KEY_W, BOTH, XKB_KEY_j, NEXT, KEY_J, UP, XKB_KEY_j, NEXT, KEY_K, DOWN, XKB_KEY_k, NEXT, KEY_W, BOTH, XKB_KEY_k, NEXT, KEY_K, UP, XKB_KEY_k, NEXT, KEY_L, DOWN, XKB_KEY_l, NEXT, KEY_W, BOTH, XKB_KEY_l, NEXT, KEY_L, UP, XKB_KEY_l, NEXT, KEY_M, DOWN, XKB_KEY_m, NEXT, KEY_W, BOTH, XKB_KEY_m, NEXT, KEY_M, UP, XKB_KEY_m, NEXT, KEY_N, DOWN, XKB_KEY_n, NEXT, KEY_W, BOTH, XKB_KEY_n, NEXT, KEY_N, UP, XKB_KEY_n, NEXT, KEY_O, DOWN, XKB_KEY_o, NEXT, KEY_W, BOTH, XKB_KEY_o, NEXT, KEY_O, UP, XKB_KEY_o, NEXT, KEY_P, DOWN, XKB_KEY_p, NEXT, KEY_W, BOTH, XKB_KEY_p, NEXT, KEY_P, UP, XKB_KEY_p, NEXT, KEY_Q, DOWN, XKB_KEY_q, NEXT, KEY_W, BOTH, XKB_KEY_q, NEXT, KEY_Q, UP, XKB_KEY_q, NEXT, KEY_R, DOWN, XKB_KEY_r, NEXT, KEY_W, BOTH, XKB_KEY_r, NEXT, KEY_R, UP, XKB_KEY_r, NEXT, KEY_S, DOWN, XKB_KEY_s, NEXT, KEY_W, BOTH, XKB_KEY_s, NEXT, KEY_S, UP, XKB_KEY_s, NEXT, KEY_T, DOWN, XKB_KEY_t, NEXT, KEY_W, BOTH, XKB_KEY_t, NEXT, KEY_T, UP, XKB_KEY_t, NEXT, KEY_U, DOWN, XKB_KEY_u, NEXT, KEY_W, BOTH, XKB_KEY_u, NEXT, KEY_U, UP, XKB_KEY_u, NEXT, KEY_V, DOWN, XKB_KEY_v, NEXT, KEY_W, BOTH, XKB_KEY_v, NEXT, KEY_LEFTSHIFT, DOWN, XKB_KEY_Shift_L, NEXT, KEY_W, BOTH, XKB_KEY_V, NEXT, KEY_LEFTSHIFT, UP, XKB_KEY_Shift_L, NEXT, KEY_V, UP, XKB_KEY_v, NEXT, KEY_A, DOWN, XKB_KEY_a, NEXT, KEY_S, DOWN, XKB_KEY_s, NEXT, KEY_W, BOTH, XKB_KEY_1, NEXT, KEY_RIGHTALT, DOWN, XKB_KEY_ISO_Level3_Shift, NEXT, KEY_W, BOTH, XKB_KEY_4, NEXT, KEY_S, UP, XKB_KEY_s, NEXT, KEY_W, BOTH, XKB_KEY_3, NEXT, KEY_RIGHTALT, UP, XKB_KEY_ISO_Level3_Shift, NEXT, KEY_Q, DOWN, XKB_KEY_q, NEXT, KEY_W, BOTH, XKB_KEY_2, NEXT, KEY_Q, UP, XKB_KEY_q, NEXT, KEY_B, DOWN, XKB_KEY_b, NEXT, KEY_C, DOWN, XKB_KEY_c, NEXT, KEY_W, BOTH, XKB_KEY_5, NEXT, KEY_C, UP, XKB_KEY_c, NEXT, KEY_B, UP, XKB_KEY_b, NEXT, KEY_A, UP, XKB_KEY_a, NEXT, KEY_Y, BOTH, XKB_KEY_y, FINISH)); xkb_keymap_unref(keymap); }} /* Test invalid interpret using a virtual modifier */ const char keymap_str[] = "xkb_keymap {" " xkb_keycodes { include \"evdev\" };" " xkb_types { include \"complete\" };" " xkb_compat { include \"complete+basic(invalid-pure-virtual-modifiers)\" };" " xkb_symbols { include \"pc(pc105-pure-virtual-modifiers)\" };" "};"; keymap = test_compile_string(context, XKB_KEYMAP_FORMAT_TEXT_V1, keymap_str); assert(!keymap); } /* Test FAQ snippet */ // NOLINTBEGIN(readability-duplicate-include) //! [xkb_keymap_mod_get_codes] #include /** * Get the keycodes associated with a modifier by index. * * @param[in] keymap The keymap * @param[in] idx The index of the modifier * @param[out] codes_out A buffer in which the codes should be stored * @param[out] codes_size The size of the codes_out buffer * * @returns the number of key codes in the keysyms_out array, or a negative * in case of error. */ static ssize_t xkb_keymap_mod_get_codes(struct xkb_keymap *keymap, xkb_mod_index_t mod, xkb_keycode_t *codes_out, size_t codes_size) { if (mod >= xkb_keymap_num_mods(keymap)) return -1; ssize_t idx = 0; for (xkb_keycode_t k = xkb_keymap_min_keycode(keymap); k <= xkb_keymap_max_keycode(keymap) && idx >= 0; k++) { /* * We only test the first level of the first group. * * Since there is currently no way to reset the state, use a new one to * avoid side effects (latches, etc.) */ struct xkb_state * const state = xkb_state_new_with_mode(keymap, XKB_STATE_MODE_SERVER); assert(state); static const enum xkb_key_direction directions[] = { XKB_KEY_DOWN, XKB_KEY_UP }; for (size_t d = 0; d < ARRAY_SIZE(directions); d++) { if (xkb_state_update_key(state, k, directions[d]) && xkb_state_mod_index_is_active(state, mod, XKB_STATE_MODS_EFFECTIVE)) { if ((size_t) idx < codes_size) { assert(idx < (ssize_t) (SIZE_MAX / 2)); codes_out[idx++] = k; } else { idx = -2; } break; } } xkb_state_unref(state); } return idx; } //! [xkb_keymap_mod_get_codes] // NOLINTEND(readability-duplicate-include) static void test_get_modifier_keycodes(struct xkb_context *context) { struct xkb_keymap *keymap = test_compile_rules(context, XKB_KEYMAP_FORMAT_TEXT_V1, "evdev", "pc104", "cz", NULL, NULL); assert(keymap); #define MAX_KEYCODES_COUNT 10 #define KEY_LVL3 (92 - EVDEV_OFFSET) const struct { const char *mod; xkb_keycode_t keycodes[MAX_KEYCODES_COUNT]; } mods[] = { { .mod = XKB_MOD_NAME_SHIFT, .keycodes = { KEY_LEFTSHIFT, KEY_RIGHTSHIFT, 0 } }, { .mod = XKB_MOD_NAME_CAPS, .keycodes = { KEY_CAPSLOCK, 0 } }, { .mod = XKB_MOD_NAME_CTRL, .keycodes = { KEY_LEFTCTRL, KEY_RIGHTCTRL, 0 } }, { .mod = XKB_MOD_NAME_MOD1, .keycodes = { KEY_LEFTALT, 0 } }, { .mod = XKB_MOD_NAME_MOD2, .keycodes = { KEY_NUMLOCK, 0 } }, { .mod = XKB_MOD_NAME_MOD3, .keycodes = { 0 } }, { .mod = XKB_MOD_NAME_MOD4, .keycodes = { KEY_LEFTMETA, KEY_RIGHTMETA, 0 } }, { .mod = XKB_MOD_NAME_MOD5, .keycodes = { KEY_LVL3, KEY_RIGHTALT, 0 } }, { .mod = XKB_VMOD_NAME_ALT, .keycodes = { KEY_LEFTALT, 0 } }, { .mod = XKB_VMOD_NAME_META, .keycodes = { KEY_LEFTALT, 0 } }, { .mod = XKB_VMOD_NAME_SUPER, .keycodes = { KEY_LEFTMETA, KEY_RIGHTMETA, 0 } }, { .mod = XKB_VMOD_NAME_HYPER, .keycodes = { KEY_LEFTMETA, KEY_RIGHTMETA, 0 } }, { .mod = XKB_VMOD_NAME_NUM, .keycodes = { KEY_NUMLOCK, 0 } }, { .mod = XKB_VMOD_NAME_LEVEL3, .keycodes = { KEY_LVL3, KEY_RIGHTALT, 0 } }, { .mod = XKB_VMOD_NAME_LEVEL5, .keycodes = { 0 } }, }; xkb_keycode_t got[MAX_KEYCODES_COUNT] = {0}; for (size_t m = 0; m < ARRAY_SIZE(mods); m++) { const xkb_mod_index_t mod = xkb_keymap_mod_get_index(keymap, mods[m].mod); assert(mod != XKB_MOD_INVALID); const ssize_t count = xkb_keymap_mod_get_codes(keymap, mod, got, ARRAY_SIZE(got)); assert(count >= 0); const xkb_keycode_t * const expected = mods[m].keycodes; for (size_t k = 0; k < ARRAY_SIZE(got); k++) { if (expected[k] == 0) { assert_printf(k == (size_t) count, "Mod %s: expected %zu, got %zd\n", mods[m].mod, k, count); break; } assert_printf(k < (size_t) count, "Mod %s: Missing keycode 0x%"PRIu32"\n", mods[m].mod, expected[k]); assert_printf(got[k] == expected[k] + EVDEV_OFFSET, "Mod %s: Expected keycode %"PRIu32" but got: %"PRIu32"\n", mods[m].mod, expected[k] + EVDEV_OFFSET, got[k]); } } xkb_keymap_unref(keymap); } #undef MAX_KEYCODES_COUNT int main(int argc, char *argv[]) { test_init(); bool update_output_files = false; int arg_index = 0; while (++arg_index < argc) { if (streq(argv[arg_index], "update")) { /* Update files with *obtained* results */ update_output_files = true; } } struct xkb_context *context = test_get_context(CONTEXT_NO_FLAG); assert(context); test_modmap_none(context); test_modifiers_names(context); test_modmap_mask(context, update_output_files); test_explicit_virtual_modifiers(context); test_virtual_modifiers_mapping_hack(context); test_pure_virtual_modifiers(context); test_get_modifier_keycodes(context); xkb_context_unref(context); return 0; }