Edit

kc3-lang/libxkbcommon/test/modifiers.c

Branch :

  • Show log

    Commit

  • Author : Pierre Le Marre
    Date : 2025-08-06 20:29:26
    Hash : 39726cac
    Message : Add xkb_keymap_mod_get_mask2() Retrospectively, `xkb_keymap_mod_get_mask()` should have used a modifier index rather that a modifier name in its type. Since we already published a version with this API, it’s too late to change that, so instead add a new function `xkb_keymap_mod_get_mask2()`.

  • test/modifiers.c
  • /*
     * Copyright © 2023 Pierre Le Marre <dev@wismill.eu>
     * SPDX-License-Identifier: MIT
     */
    
    #include "config.h"
    
    #include <assert.h>
    #include <stdlib.h>
    
    #include "xkbcommon/xkbcommon.h"
    #include "evdev-scancodes.h"
    #include "test.h"
    #include "keymap.h"
    #include "utils.h"
    
    /* 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
    };
    
    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_V1, "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);
    }
    
    static void
    test_explicit_virtual_modifiers(struct xkb_context *context)
    {
        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(keymap);
            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(keymap);
        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);
    }
    
    int
    main(void)
    {
        test_init();
    
        struct xkb_context *context = test_get_context(CONTEXT_NO_FLAG);
        assert(context);
    
        test_modmap_none(context);
        test_modifiers_names(context);
        test_explicit_virtual_modifiers(context);
        test_virtual_modifiers_mapping_hack(context);
        test_pure_virtual_modifiers(context);
    
        xkb_context_unref(context);
    
        return 0;
    }