Edit

kc3-lang/libxkbcommon/test/keyseq.c

Branch :

  • Show log

    Commit

  • Author : Ran Benita
    Date : 2013-02-17 14:59:50
    Hash : 089c3a18
    Message : state: fix unbound virtual modifier bug Recent xkeyboard-config introduced the following line in symbols/level3: vmods = LevelThree, However, the XKM format which xkbcomp produces for the X server can't handle explicit virtual modifiers such as this: https://bugs.freedesktop.org/show_bug.cgi?id=4927 So by doing the following, for example: setxkbmap -layout de (or another 3-level layouts) xkbcomp $DISPLAY out.xkb xkbcomp out.xkb $DISPLAY The modifier is lost and can't be used for switching to Level3 (see the included test). We, however, are affected worse by this bug when we load the out.xkb keymap. First, the FOUR_LEVEL_ALPHABETIC key type has these entries: map[None] = Level1; map[Shift] = Level2; map[Lock] = Level2; map[LevelThree] = Level3; [...] Now, because the LevelThree virtual modifier is not bound to anything, the effective mask of the "map[LevelThree]" entry is just 0. So when the modifier state is empty (initial state), this entry is chosen, and we get Level3, instead of failing to match any entry and getting the default Level1. The difference in behavior from the xserver stems from this commit: acdad6058d52dc8a3e724dc95448300850d474f2 Which removed the entry->active field. Without bugs, this would be correct; however, it seems in this case we should just follow the server's behavior. The server sets the entry->active field like so in XKBMisc.c: /* entry is active if vmods are bound */ entry->active = (mask != 0); The xkblib spec explains this field, but does not specify how to initialize it. This commit does the same as above but more directly. Signed-off-by: Ran Benita <ran234@gmail.com>

  • test/keyseq.c
  • /*
     * Copyright © 2012 Ran Benita <ran234@gmail.com>
     *
     * 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 <linux/input.h>
    
    #include "test.h"
    
    enum {
        DOWN,
        REPEAT,
        UP,
        BOTH,
        NEXT,
        FINISH,
    };
    
    #define EVDEV_OFFSET 8
    
    /*
     * Test a sequence of keysyms, resulting from a sequence of key presses,
     * against the keysyms they're supposed to generate.
     *
     * - Each test runs with a clean state.
     * - Each line in the test is made up of:
     *   + A keycode, given as a KEY_* from linux/input.h.
     *   + A direction - DOWN for press, UP for release, BOTH for
     *     immediate press + release, REPEAT to just get the syms.
     *   + A sequence of keysyms that should result from this keypress.
     *
     * The vararg format is:
     * <KEY_*>  <DOWN | UP | BOTH>  <XKB_KEY_* (zero or more)>  <NEXT | FINISH>
     *
     * See below for examples.
     */
    static int
    test_key_seq(struct xkb_keymap *keymap, ...)
    {
        struct xkb_state *state;
    
        va_list ap;
        xkb_keycode_t kc;
        int op;
        xkb_keysym_t keysym;
    
        const xkb_keysym_t *syms;
        unsigned int nsyms, i;
        char ksbuf[64];
    
        fprintf(stderr, "----\n");
    
        state = xkb_state_new(keymap);
        assert(state);
    
        va_start(ap, keymap);
    
        for (;;) {
            kc = va_arg(ap, int) + EVDEV_OFFSET;
            op = va_arg(ap, int);
    
            nsyms = xkb_state_key_get_syms(state, kc, &syms);
            fprintf(stderr, "got %d syms for key 0x%x: [", nsyms, kc);
    
            if (op == DOWN || op == BOTH)
                xkb_state_update_key(state, kc, XKB_KEY_DOWN);
            if (op == UP || op == BOTH)
                xkb_state_update_key(state, kc, XKB_KEY_UP);
    
            for (i = 0; i < nsyms; i++) {
                keysym = va_arg(ap, int);
                xkb_keysym_get_name(syms[i], ksbuf, sizeof(ksbuf));
                fprintf(stderr, "%s%s", (i != 0) ? ", " : "", ksbuf);
    
                if (keysym == FINISH || keysym == NEXT) {
                    xkb_keysym_get_name(syms[i], ksbuf, sizeof(ksbuf));
                    fprintf(stderr, "Did not expect keysym: %s.\n", ksbuf);
                    goto fail;
                }
    
                if (keysym != syms[i]) {
                    xkb_keysym_get_name(keysym, ksbuf, sizeof(ksbuf));
                    fprintf(stderr, "Expected keysym: %s. ", ksbuf);;
                    xkb_keysym_get_name(syms[i], ksbuf, sizeof(ksbuf));
                    fprintf(stderr, "Got keysym: %s.\n", ksbuf);;
                    goto fail;
                }
            }
    
            fprintf(stderr, "]\n");
    
            keysym = va_arg(ap, int);
            if (keysym == NEXT)
                continue;
            if (keysym == FINISH)
                break;
    
            xkb_keysym_get_name(keysym, ksbuf, sizeof(ksbuf));
            fprintf(stderr, "Expected keysym: %s. Didn't get it.\n", ksbuf);
            goto fail;
        }
    
        va_end(ap);
        xkb_state_unref(state);
        return 1;
    
    fail:
        va_end(ap);
        xkb_state_unref(state);
        return 0;
    }
    
    int
    main(void)
    {
        struct xkb_context *ctx = test_get_context();
        struct xkb_keymap *keymap;
    
        assert(ctx);
        keymap = test_compile_rules(ctx, "evdev", "evdev",
                                    "us,il,ru,de", ",,phonetic,neo",
                                    "grp:alt_shift_toggle,grp:menu_toggle");
        assert(keymap);
    
        assert(test_key_seq(keymap,
                            KEY_H,  BOTH,  XKB_KEY_h,  NEXT,
                            KEY_E,  BOTH,  XKB_KEY_e,  NEXT,
                            KEY_L,  BOTH,  XKB_KEY_l,  NEXT,
                            KEY_L,  BOTH,  XKB_KEY_l,  NEXT,
                            KEY_O,  BOTH,  XKB_KEY_o,  FINISH));
    
        /* Simple shifted level. */
        assert(test_key_seq(keymap,
                            KEY_H,          BOTH,  XKB_KEY_h,        NEXT,
                            KEY_LEFTSHIFT,  DOWN,  XKB_KEY_Shift_L,  NEXT,
                            KEY_E,          BOTH,  XKB_KEY_E,        NEXT,
                            KEY_L,          BOTH,  XKB_KEY_L,        NEXT,
                            KEY_LEFTSHIFT,  UP,    XKB_KEY_Shift_L,  NEXT,
                            KEY_L,          BOTH,  XKB_KEY_l,        NEXT,
                            KEY_O,          BOTH,  XKB_KEY_o,        FINISH));
    
        /* Key repeat shifted and unshifted in the middle. */
        assert(test_key_seq(keymap,
                            KEY_H,           DOWN,    XKB_KEY_h,        NEXT,
                            KEY_H,           REPEAT,  XKB_KEY_h,        NEXT,
                            KEY_H,           REPEAT,  XKB_KEY_h,        NEXT,
                            KEY_LEFTSHIFT,   DOWN,    XKB_KEY_Shift_L,  NEXT,
                            KEY_H,           REPEAT,  XKB_KEY_H,        NEXT,
                            KEY_H,           REPEAT,  XKB_KEY_H,        NEXT,
                            KEY_LEFTSHIFT,   UP,      XKB_KEY_Shift_L,  NEXT,
                            KEY_H,           REPEAT,  XKB_KEY_h,        NEXT,
                            KEY_H,           REPEAT,  XKB_KEY_h,        NEXT,
                            KEY_H,           UP,      XKB_KEY_h,        NEXT,
                            KEY_H,           BOTH,    XKB_KEY_h,        FINISH));
    
        /* Base modifier cleared on key release... */
        assert(test_key_seq(keymap,
                            KEY_H,          BOTH,  XKB_KEY_h,        NEXT,
                            KEY_LEFTSHIFT,  DOWN,  XKB_KEY_Shift_L,  NEXT,
                            KEY_E,          BOTH,  XKB_KEY_E,        NEXT,
                            KEY_L,          BOTH,  XKB_KEY_L,        NEXT,
                            KEY_LEFTSHIFT,  DOWN,  XKB_KEY_Shift_L,  NEXT,
                            KEY_L,          BOTH,  XKB_KEY_L,        NEXT,
                            KEY_O,          BOTH,  XKB_KEY_O,        FINISH));
    
        /* ... But only by the keycode that set it. */
        assert(test_key_seq(keymap,
                            KEY_H,           BOTH,  XKB_KEY_h,        NEXT,
                            KEY_LEFTSHIFT,   DOWN,  XKB_KEY_Shift_L,  NEXT,
                            KEY_E,           BOTH,  XKB_KEY_E,        NEXT,
                            KEY_L,           BOTH,  XKB_KEY_L,        NEXT,
                            KEY_RIGHTSHIFT,  UP,    XKB_KEY_Shift_R,  NEXT,
                            KEY_L,           BOTH,  XKB_KEY_L,        NEXT,
                            KEY_O,           BOTH,  XKB_KEY_O,        FINISH));
    
        /*
         * A base modifier should only be cleared when no other key affecting
         * the modifier is down.
         */
        assert(test_key_seq(keymap,
                            KEY_H,           BOTH,  XKB_KEY_h,        NEXT,
                            KEY_LEFTSHIFT,   DOWN,  XKB_KEY_Shift_L,  NEXT,
                            KEY_E,           BOTH,  XKB_KEY_E,        NEXT,
                            KEY_RIGHTSHIFT,  DOWN,  XKB_KEY_Shift_R,  NEXT,
                            KEY_L,           BOTH,  XKB_KEY_L,        NEXT,
                            KEY_RIGHTSHIFT,  UP,    XKB_KEY_Shift_R,  NEXT,
                            KEY_L,           BOTH,  XKB_KEY_L,        NEXT,
                            KEY_LEFTSHIFT,   UP,    XKB_KEY_Shift_L,  NEXT,
                            KEY_O,           BOTH,  XKB_KEY_o,        FINISH));
    
        /* Group switching / locking. */
        assert(test_key_seq(keymap,
                            KEY_H,        BOTH,  XKB_KEY_h,               NEXT,
                            KEY_E,        BOTH,  XKB_KEY_e,               NEXT,
                            KEY_COMPOSE,  BOTH,  XKB_KEY_ISO_Next_Group,  NEXT,
                            KEY_K,        BOTH,  XKB_KEY_hebrew_lamed,    NEXT,
                            KEY_F,        BOTH,  XKB_KEY_hebrew_kaph,     NEXT,
                            KEY_COMPOSE,  BOTH,  XKB_KEY_ISO_Next_Group,  NEXT,
                            KEY_COMPOSE,  BOTH,  XKB_KEY_ISO_Next_Group,  NEXT,
                            KEY_COMPOSE,  BOTH,  XKB_KEY_ISO_Next_Group,  NEXT,
                            KEY_O,        BOTH,  XKB_KEY_o,               FINISH));
    
        assert(test_key_seq(keymap,
                            KEY_LEFTSHIFT, DOWN, XKB_KEY_Shift_L,        NEXT,
                            KEY_LEFTALT,   DOWN, XKB_KEY_ISO_Next_Group, NEXT,
                            KEY_LEFTALT,   UP,   XKB_KEY_ISO_Next_Group, NEXT,
                            KEY_LEFTSHIFT, UP,   XKB_KEY_Shift_L,        FINISH));
    
        assert(test_key_seq(keymap,
                            KEY_LEFTALT,   DOWN, XKB_KEY_Alt_L,          NEXT,
                            KEY_LEFTSHIFT, DOWN, XKB_KEY_ISO_Next_Group, NEXT,
                            KEY_LEFTSHIFT, UP,   XKB_KEY_ISO_Next_Group, NEXT,
                            KEY_LEFTALT,   UP,   XKB_KEY_Alt_L,          FINISH));
    
        /* Locked modifiers. */
        assert(test_key_seq(keymap,
                            KEY_CAPSLOCK,  BOTH,  XKB_KEY_Caps_Lock,  NEXT,
                            KEY_H,         BOTH,  XKB_KEY_H,          NEXT,
                            KEY_E,         BOTH,  XKB_KEY_E,          NEXT,
                            KEY_L,         BOTH,  XKB_KEY_L,          NEXT,
                            KEY_L,         BOTH,  XKB_KEY_L,          NEXT,
                            KEY_O,         BOTH,  XKB_KEY_O,          FINISH));
    
        assert(test_key_seq(keymap,
                            KEY_H,         BOTH,  XKB_KEY_h,          NEXT,
                            KEY_E,         BOTH,  XKB_KEY_e,          NEXT,
                            KEY_CAPSLOCK,  BOTH,  XKB_KEY_Caps_Lock,  NEXT,
                            KEY_L,         BOTH,  XKB_KEY_L,          NEXT,
                            KEY_L,         BOTH,  XKB_KEY_L,          NEXT,
                            KEY_CAPSLOCK,  BOTH,  XKB_KEY_Caps_Lock,  NEXT,
                            KEY_O,         BOTH,  XKB_KEY_o,          FINISH));
    
        assert(test_key_seq(keymap,
                            KEY_H,         BOTH,  XKB_KEY_h,          NEXT,
                            KEY_CAPSLOCK,  DOWN,  XKB_KEY_Caps_Lock,  NEXT,
                            KEY_E,         BOTH,  XKB_KEY_E,          NEXT,
                            KEY_L,         BOTH,  XKB_KEY_L,          NEXT,
                            KEY_L,         BOTH,  XKB_KEY_L,          NEXT,
                            KEY_CAPSLOCK,  UP,    XKB_KEY_Caps_Lock,  NEXT,
                            KEY_O,         BOTH,  XKB_KEY_O,          FINISH));
    
        assert(test_key_seq(keymap,
                            KEY_H,         BOTH,  XKB_KEY_h,          NEXT,
                            KEY_E,         BOTH,  XKB_KEY_e,          NEXT,
                            KEY_CAPSLOCK,  UP,    XKB_KEY_Caps_Lock,  NEXT,
                            KEY_L,         BOTH,  XKB_KEY_l,          NEXT,
                            KEY_L,         BOTH,  XKB_KEY_l,          NEXT,
                            KEY_O,         BOTH,  XKB_KEY_o,          FINISH));
    
        /*
         * A key release affecting a locked modifier should clear it
         * regardless of the key press.
         */
        /* assert(test_key_seq(keymap, */
        /*                     KEY_H,         BOTH,  XKB_KEY_h,          NEXT, */
        /*                     KEY_CAPSLOCK,  DOWN,  XKB_KEY_Caps_Lock,  NEXT, */
        /*                     KEY_E,         BOTH,  XKB_KEY_E,          NEXT, */
        /*                     KEY_L,         BOTH,  XKB_KEY_L,          NEXT, */
        /*                     KEY_CAPSLOCK,  UP,    XKB_KEY_Caps_Lock,  NEXT, */
        /*                     KEY_L,         BOTH,  XKB_KEY_L,          NEXT, */
        /*                     KEY_CAPSLOCK,  UP,    XKB_KEY_Caps_Lock,  NEXT, */
        /*                     KEY_O,         BOTH,  XKB_KEY_o,          FINISH)); */
    
        /* Simple Num Lock sanity check. */
        assert(test_key_seq(keymap,
                            KEY_KP1,      BOTH,  XKB_KEY_KP_End,    NEXT,
                            KEY_NUMLOCK,  BOTH,  XKB_KEY_Num_Lock,  NEXT,
                            KEY_KP1,      BOTH,  XKB_KEY_KP_1,      NEXT,
                            KEY_KP2,      BOTH,  XKB_KEY_KP_2,      NEXT,
                            KEY_NUMLOCK,  BOTH,  XKB_KEY_Num_Lock,  NEXT,
                            KEY_KP2,      BOTH,  XKB_KEY_KP_Down,   FINISH));
    
        /* Test that the aliases in the ru(phonetic) symbols map work. */
        assert(test_key_seq(keymap,
                            KEY_COMPOSE,     BOTH,  XKB_KEY_ISO_Next_Group,  NEXT,
                            KEY_COMPOSE,     BOTH,  XKB_KEY_ISO_Next_Group,  NEXT,
                            KEY_1,           BOTH,  XKB_KEY_1,               NEXT,
                            KEY_Q,           BOTH,  XKB_KEY_Cyrillic_ya,     NEXT,
                            KEY_LEFTSHIFT,   DOWN,  XKB_KEY_Shift_L,         NEXT,
                            KEY_1,           BOTH,  XKB_KEY_exclam,          NEXT,
                            KEY_Q,           BOTH,  XKB_KEY_Cyrillic_YA,     NEXT,
                            KEY_LEFTSHIFT,   UP,    XKB_KEY_Shift_L,         NEXT,
                            KEY_V,           BOTH,  XKB_KEY_Cyrillic_zhe,    NEXT,
                            KEY_CAPSLOCK,    BOTH,  XKB_KEY_Caps_Lock,       NEXT,
                            KEY_1,           BOTH,  XKB_KEY_1,               NEXT,
                            KEY_V,           BOTH,  XKB_KEY_Cyrillic_ZHE,    NEXT,
                            KEY_RIGHTSHIFT,  DOWN,  XKB_KEY_Shift_R,         NEXT,
                            KEY_V,           BOTH,  XKB_KEY_Cyrillic_zhe,    NEXT,
                            KEY_RIGHTSHIFT,  UP,    XKB_KEY_Shift_R,         NEXT,
                            KEY_V,           BOTH,  XKB_KEY_Cyrillic_ZHE,    FINISH));
    
    #define KS(name) xkb_keysym_from_name(name, 0)
    
        /* Test that levels (1-5) in de(neo) symbols map work. */
        assert(test_key_seq(keymap,
                            /* Switch to the group. */
                            KEY_COMPOSE,     BOTH,  XKB_KEY_ISO_Next_Group,    NEXT,
                            KEY_COMPOSE,     BOTH,  XKB_KEY_ISO_Next_Group,    NEXT,
                            KEY_COMPOSE,     BOTH,  XKB_KEY_ISO_Next_Group,    NEXT,
    
                            /* Level 1. */
                            KEY_1,           BOTH,  XKB_KEY_1,                 NEXT,
                            KEY_Q,           BOTH,  XKB_KEY_x,                 NEXT,
                            KEY_KP7,         BOTH,  XKB_KEY_KP_7,              NEXT,
                            KEY_ESC,         BOTH,  XKB_KEY_Escape,            NEXT,
    
                            /* Level 2 with Shift. */
                            KEY_LEFTSHIFT,   DOWN,  XKB_KEY_Shift_L,           NEXT,
                            KEY_1,           BOTH,  XKB_KEY_degree,            NEXT,
                            KEY_Q,           BOTH,  XKB_KEY_X,                 NEXT,
                            KEY_KP7,         BOTH,  KS("U2714"),               NEXT,
                            KEY_ESC,         BOTH,  XKB_KEY_Escape,            NEXT,
                            /*
                             * XXX: de(neo) uses shift(both_capslock) which causes
                             * the interesting result in the next line. Since it's
                             * a key release, it doesn't actually lock the modifier,
                             * and applications by-and-large ignore the keysym on
                             * release(?). Is this a problem?
                             */
                            KEY_LEFTSHIFT,   UP,    XKB_KEY_Caps_Lock,         NEXT,
    
                            /* Level 2 with the Lock modifier. */
                            KEY_LEFTSHIFT,   DOWN,  XKB_KEY_Shift_L,           NEXT,
                            KEY_RIGHTSHIFT,  BOTH,  XKB_KEY_Caps_Lock,         NEXT,
                            KEY_LEFTSHIFT,   UP,    XKB_KEY_Caps_Lock,         NEXT,
                            KEY_6,           BOTH,  XKB_KEY_6,                 NEXT,
                            KEY_H,           BOTH,  XKB_KEY_S,                 NEXT,
                            KEY_KP3,         BOTH,  XKB_KEY_KP_3,              NEXT,
                            KEY_ESC,         BOTH,  XKB_KEY_Escape,            NEXT,
                            KEY_LEFTSHIFT,   DOWN,  XKB_KEY_Shift_L,           NEXT,
                            KEY_RIGHTSHIFT,  BOTH,  XKB_KEY_Caps_Lock,         NEXT,
                            KEY_LEFTSHIFT,   UP,    XKB_KEY_Caps_Lock,         NEXT,
    
                            /* Level 3. */
                            KEY_CAPSLOCK,    DOWN,  XKB_KEY_ISO_Level3_Shift,  NEXT,
                            KEY_6,           BOTH,  XKB_KEY_cent,              NEXT,
                            KEY_Q,           BOTH,  XKB_KEY_ellipsis,          NEXT,
                            KEY_KP7,         BOTH,  KS("U2195"),               NEXT,
                            KEY_ESC,         BOTH,  XKB_KEY_Escape,            NEXT,
                            KEY_CAPSLOCK,    UP,    XKB_KEY_ISO_Level3_Shift,  NEXT,
    
                            /* Level 4. */
                            KEY_CAPSLOCK,    DOWN,  XKB_KEY_ISO_Level3_Shift,  NEXT,
                            KEY_LEFTSHIFT,   DOWN,  XKB_KEY_Shift_L,           NEXT,
                            KEY_5,           BOTH,  XKB_KEY_malesymbol,        NEXT,
                            KEY_E,           BOTH,  XKB_KEY_Greek_lambda,      NEXT,
                            KEY_SPACE,       BOTH,  XKB_KEY_nobreakspace,      NEXT,
                            KEY_KP8,         BOTH,  XKB_KEY_intersection,      NEXT,
                            KEY_ESC,         BOTH,  XKB_KEY_Escape,            NEXT,
                            KEY_LEFTSHIFT,   UP,    XKB_KEY_Caps_Lock,         NEXT,
                            KEY_CAPSLOCK,    UP,    XKB_KEY_ISO_Level3_Shift,  NEXT,
    
                            /* Level 5. */
                            KEY_RIGHTALT,    DOWN,  XKB_KEY_ISO_Level5_Shift,  NEXT,
                            KEY_5,           BOTH,  XKB_KEY_periodcentered,    NEXT,
                            KEY_E,           BOTH,  XKB_KEY_Up,                NEXT,
                            KEY_SPACE,       BOTH,  XKB_KEY_KP_0,              NEXT,
                            KEY_KP8,         BOTH,  XKB_KEY_KP_Up,             NEXT,
                            KEY_ESC,         BOTH,  XKB_KEY_Escape,            NEXT,
                            KEY_RIGHTALT,    UP,    XKB_KEY_ISO_Level5_Shift,  NEXT,
    
                            KEY_V,           BOTH,  XKB_KEY_p,               FINISH));
    
        xkb_keymap_unref(keymap);
        assert(ctx);
        keymap = test_compile_rules(ctx, "evdev", "", "us,il,ru", "",
                                    "grp:alt_shift_toggle_bidir,grp:menu_toggle");
        assert(keymap);
    
        assert(test_key_seq(keymap,
                            KEY_LEFTSHIFT, DOWN, XKB_KEY_Shift_L,        NEXT,
                            KEY_LEFTALT,   DOWN, XKB_KEY_ISO_Prev_Group, NEXT,
                            KEY_LEFTALT,   UP,   XKB_KEY_ISO_Prev_Group, NEXT,
                            KEY_LEFTSHIFT, UP,   XKB_KEY_Shift_L,        FINISH));
    
        assert(test_key_seq(keymap,
                            KEY_LEFTALT,   DOWN, XKB_KEY_Alt_L,          NEXT,
                            KEY_LEFTSHIFT, DOWN, XKB_KEY_ISO_Prev_Group, NEXT,
                            KEY_LEFTSHIFT, UP,   XKB_KEY_ISO_Prev_Group, NEXT,
                            KEY_LEFTALT,   UP,   XKB_KEY_Alt_L,          FINISH));
    
        /* Check backwards (negative) group switching and wrapping. */
        assert(test_key_seq(keymap,
                            KEY_H,         BOTH, XKB_KEY_h,              NEXT,
                            KEY_COMPOSE,   BOTH, XKB_KEY_ISO_Next_Group, NEXT,
                            KEY_H,         BOTH, XKB_KEY_hebrew_yod,     NEXT,
                            KEY_COMPOSE,   BOTH, XKB_KEY_ISO_Next_Group, NEXT,
                            KEY_H,         BOTH, XKB_KEY_Cyrillic_er,    NEXT,
                            KEY_LEFTSHIFT, DOWN, XKB_KEY_Shift_L,        NEXT,
                            KEY_LEFTALT,   BOTH, XKB_KEY_ISO_Prev_Group, NEXT,
                            KEY_LEFTSHIFT, UP,   XKB_KEY_Shift_L,        NEXT,
                            KEY_H,         BOTH, XKB_KEY_hebrew_yod,     NEXT,
                            KEY_LEFTSHIFT, DOWN, XKB_KEY_Shift_L,        NEXT,
                            KEY_LEFTALT,   BOTH, XKB_KEY_ISO_Prev_Group, NEXT,
                            KEY_LEFTSHIFT, UP,   XKB_KEY_Shift_L,        NEXT,
                            KEY_H,         BOTH, XKB_KEY_h,              NEXT,
                            KEY_LEFTSHIFT, DOWN, XKB_KEY_Shift_L,        NEXT,
                            KEY_LEFTALT,   BOTH, XKB_KEY_ISO_Prev_Group, NEXT,
                            KEY_LEFTSHIFT, UP,   XKB_KEY_Shift_L,        NEXT,
                            KEY_H,         BOTH, XKB_KEY_Cyrillic_er,    NEXT,
                            KEY_COMPOSE,   BOTH, XKB_KEY_ISO_Next_Group, NEXT,
                            KEY_H,         BOTH, XKB_KEY_h,              FINISH));
    
        xkb_keymap_unref(keymap);
        keymap = test_compile_rules(ctx, "evdev", "", "us,il,ru", "",
                                    "grp:switch,grp:lswitch,grp:menu_toggle");
        assert(keymap);
    
        /* Test depressed group works (Mode_switch). */
        assert(test_key_seq(keymap,
                            KEY_H,         BOTH, XKB_KEY_h,                 NEXT,
                            KEY_RIGHTALT,  DOWN, XKB_KEY_Mode_switch,       NEXT,
                            KEY_H,         BOTH, XKB_KEY_hebrew_yod,        NEXT,
                            KEY_RIGHTALT,  UP,   XKB_KEY_ISO_Level3_Shift,  NEXT,
                            KEY_H,         BOTH, XKB_KEY_h,                 NEXT,
                            KEY_RIGHTALT,  DOWN, XKB_KEY_Mode_switch,       NEXT,
                            KEY_H,         BOTH, XKB_KEY_hebrew_yod,        NEXT,
                            KEY_RIGHTALT,  UP,   XKB_KEY_ISO_Level3_Shift,  NEXT,
                            KEY_H,         BOTH, XKB_KEY_h,                 FINISH));
    
        /* Test locked+depressed group works, with wrapping and accumulation. */
        assert(test_key_seq(keymap,
                            KEY_H,         BOTH, XKB_KEY_h,                 NEXT,
                            KEY_COMPOSE,   BOTH, XKB_KEY_ISO_Next_Group,    NEXT,
                            KEY_LEFTALT,   DOWN, XKB_KEY_Mode_switch,       NEXT,
                            KEY_H,         BOTH, XKB_KEY_Cyrillic_er,       NEXT,
                            KEY_LEFTALT,   UP,   XKB_KEY_Mode_switch,       NEXT,
                            KEY_H,         BOTH, XKB_KEY_hebrew_yod,        NEXT,
                            KEY_COMPOSE,   BOTH, XKB_KEY_ISO_Next_Group,    NEXT,
                            KEY_LEFTALT,   DOWN, XKB_KEY_Mode_switch,       NEXT,
                            /* Should wrap back to first group. */
                            KEY_H,         BOTH, XKB_KEY_h,                 NEXT,
                            KEY_LEFTALT,   UP,   XKB_KEY_Mode_switch,       NEXT,
                            KEY_H,         BOTH, XKB_KEY_Cyrillic_er,       NEXT,
                            KEY_COMPOSE,   BOTH, XKB_KEY_ISO_Next_Group,    NEXT,
                            KEY_H,         BOTH, XKB_KEY_h,                 NEXT,
                            /* Two SetGroup(+1)'s should add up. */
                            KEY_RIGHTALT,  DOWN, XKB_KEY_Mode_switch,       NEXT,
                            KEY_H,         BOTH, XKB_KEY_hebrew_yod,        NEXT,
                            KEY_LEFTALT,   DOWN, XKB_KEY_Mode_switch,       NEXT,
                            KEY_H,         BOTH, XKB_KEY_Cyrillic_er,       NEXT,
                            KEY_LEFTALT,   UP,   XKB_KEY_Mode_switch,       NEXT,
                            KEY_H,         BOTH, XKB_KEY_hebrew_yod,        NEXT,
                            KEY_RIGHTALT,  UP,   XKB_KEY_ISO_Level3_Shift,  NEXT,
                            KEY_H,         BOTH, XKB_KEY_h,                 FINISH));
    
        xkb_keymap_unref(keymap);
        keymap = test_compile_file(ctx, "keymaps/unbound-vmod.xkb");
        assert(keymap);
    
        assert(test_key_seq(keymap,
                            KEY_H,         BOTH, XKB_KEY_h,                 NEXT,
                            KEY_Z,         BOTH, XKB_KEY_y,                 NEXT,
                            KEY_MINUS,     BOTH, XKB_KEY_ssharp,            NEXT,
                            KEY_Z,         BOTH, XKB_KEY_y,                 FINISH));
    
        xkb_keymap_unref(keymap);
        xkb_context_unref(ctx);
        return 0;
    }