Edit

kc3-lang/libxkbcommon/src/keysym.c

Branch :

  • Show log

    Commit

  • Author : Pierre Le Marre
    Date : 2024-03-05 10:28:11
    Hash : 53d9881e
    Message : keysyms: Fix inconsistent case-insensitive name lookup `xkb_keysym_from_name` has inconsistent behavior when used with the flag `XKB_KEYSYM_CASE_INSENSITIVE`: ```c xkb_keysym_from_name("a", XKB_KEYSYM_CASE_INSENSITIVE) == XKB_KEY_a; xkb_keysym_from_name("A", XKB_KEYSYM_CASE_INSENSITIVE) == XKB_KEY_a; xkb_keysym_from_name("dead_a", XKB_KEYSYM_CASE_INSENSITIVE) == XKB_KEY_dead_A; xkb_keysym_from_name("dead_A", XKB_KEYSYM_CASE_INSENSITIVE) == XKB_KEY_dead_A; xkb_keysym_from_name("dead_o", XKB_KEYSYM_CASE_INSENSITIVE) == XKB_KEY_dead_o; xkb_keysym_from_name("dead_O", XKB_KEYSYM_CASE_INSENSITIVE) == XKB_KEY_dead_o; xkb_keysym_from_name("KANA_tsu", XKB_KEYSYM_CASE_INSENSITIVE) == XKB_KEY_kana_tsu; xkb_keysym_from_name("KANA_TSU", XKB_KEYSYM_CASE_INSENSITIVE) == XKB_KEY_kana_tsu; xkb_keysym_from_name("KANA_ya", XKB_KEYSYM_CASE_INSENSITIVE) == XKB_KEY_kana_YA; xkb_keysym_from_name("KANA_YA", XKB_KEYSYM_CASE_INSENSITIVE) == XKB_KEY_kana_YA; xkb_keysym_from_name("XF86Screensaver", XKB_KEYSYM_CASE_INSENSITIVE) == XKB_KEY_XF86ScreenSaver; xkb_keysym_from_name("XF86ScreenSaver", XKB_KEYSYM_CASE_INSENSITIVE) == XKB_KEY_XF86ScreenSaver; ``` So currently, if two keysym names differ only by case, then the lower-case *keysym* is returned, not the keysym corresponding to the lower-case keysym *name*. Indeed, `xkb_keysym_from_name` uses `xkb_keysym_is_lower` to test if a keysym is a lower-case keysym. Let’s look at the example for keysyms `a` and `A`: we get the keysym `a` not because its name is lower case, but because `xkb_keysym_is_lower(XKB_KEY_a)` returns true and `xkb_keysym_is_lower(XKB_KEY_A)` returns false. So the results are correct according to the doc: - Katakana is not a bicameral script, so e.g. `kana_ya` is *not* the lower case of `kana_YA`. - As for the `dead_*` keysyms, they are not cased either because they do not correspond to characters. - `XF86ScreenSaver` and `XF86Screensaver` are two different keysyms. But this is also very counter-intuitive: `xkb_keysym_is_lower` is not the right function to use in this case, because one would expect to check only the name, not the corresponding character case: ```c xkb_keysym_from_name("KANA_YA", XKB_KEYSYM_CASE_INSENSITIVE) == XKB_KEY_kana_ya; xkb_keysym_from_name("XF86ScreenSaver", XKB_KEYSYM_CASE_INSENSITIVE) == XKB_KEY_XF86Screensaver; ``` Fixed by making the order of the keysyms names consistent in `src/ks_tables.h`: 1. Sort by the casefolded name: e.g. `kana_ya` < `kana_YO`. 2. If same casefolded name, then sort by cased name, i.e for ASCII: upper before lower: e.g `kana_YA` < `kana_ya`. Thus we now have e.g. `kana_YA` < `kana_ya` < `kana_YO` < `kana_yo`. The lookup logic has also been simplified. Added exhaustive test for ambiguous case-insensitive names.

  • src/keysym.c
  • /*
     * Copyright 1985, 1987, 1990, 1998  The Open Group
     *
     * 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 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 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.
     *
     * Except as contained in this notice, the names of the authors or their
     * institutions shall not be used in advertising or otherwise to promote the
     * sale, use or other dealings in this Software without prior written
     * authorization from the authors.
     */
    
    /*
     * Copyright © 2009 Dan Nicholson
     *
     * Permission is hereby granted, free of charge, to any person obtaining a
     * copy of this software and associated documentation files (the "Software"),
     * to deal in the Software without restriction, including without limitation
     * the rights to use, copy, modify, merge, publish, distribute, sublicense,
     * and/or sell copies of the Software, and to permit persons to whom the
     * Software is furnished to do so, subject to the following conditions:
     *
     * The above copyright notice and this permission notice (including the next
     * paragraph) shall be included in all copies or substantial portions of the
     * Software.
     *
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
     * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
     * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
     * DEALINGS IN THE SOFTWARE.
     */
    
    #include "config.h"
    
    #include <stdlib.h>
    #include "xkbcommon/xkbcommon.h"
    #include "utils.h"
    #include "keysym.h"
    #include "ks_tables.h"
    
    static ssize_t
    find_keysym_index(xkb_keysym_t ks)
    {
        /* Lower bound:
         * keysym_to_name[0].keysym
         * == XKB_KEYSYM_MIN_EXPLICIT == XKB_KEYSYM_MIN == 0
         * No need to check: xkb_keysym_t is unsigned.
         *
         * Upper bound:
         * keysym_to_name[ARRAY_SIZE(keysym_to_name) - 1].keysym
         * == XKB_KEYSYM_MAX_EXPLICIT <= XKB_KEYSYM_MAX
         */
        if (ks > XKB_KEYSYM_MAX_EXPLICIT)
            return -1;
    
        ssize_t lo = 0, hi = ARRAY_SIZE(keysym_to_name) - 1;
        while (hi >= lo) {
            ssize_t mid = (lo + hi) / 2;
            if (ks > keysym_to_name[mid].keysym) {
                lo = mid + 1;
            } else if (ks < keysym_to_name[mid].keysym) {
                hi = mid - 1;
            } else {
                return mid;
            }
        }
    
        return -1;
    }
    
    static inline const char *
    get_name(const struct name_keysym *entry)
    {
        return keysym_names + entry->offset;
    }
    
    /* Unnamed Unicode codepoint. */
    static inline int
    get_unicode_name(xkb_keysym_t ks, char *buffer, size_t size)
    {
        const int width = (ks & 0xff0000UL) ? 8 : 4;
        return snprintf(buffer, size, "U%0*lX", width, ks & 0xffffffUL);
    }
    
    XKB_EXPORT int
    xkb_keysym_get_name(xkb_keysym_t ks, char *buffer, size_t size)
    {
        if (ks > XKB_KEYSYM_MAX) {
            snprintf(buffer, size, "Invalid");
            return -1;
        }
    
        ssize_t index = find_keysym_index(ks);
        if (index != -1)
            return snprintf(buffer, size, "%s", get_name(&keysym_to_name[index]));
    
        /* Unnamed Unicode codepoint. */
        if (ks >= XKB_KEYSYM_UNICODE_MIN && ks <= XKB_KEYSYM_UNICODE_MAX)
            return get_unicode_name(ks, buffer, size);
    
        /* Unnamed, non-Unicode, symbol (shouldn't generally happen). */
        return snprintf(buffer, size, "0x%08x", ks);
    }
    
    bool
    xkb_keysym_is_assigned(xkb_keysym_t ks)
    {
        return (XKB_KEYSYM_UNICODE_MIN <= ks && ks <= XKB_KEYSYM_UNICODE_MAX) ||
               find_keysym_index(ks) != -1;
    }
    
    struct xkb_keysym_iterator {
        bool explicit;       /* If true, traverse only explicitly named keysyms */
        int32_t index;       /* Current index in `keysym_to_name` */
        xkb_keysym_t keysym; /* Current keysym */
    };
    
    struct xkb_keysym_iterator*
    xkb_keysym_iterator_new(bool iterate_only_explicit_keysyms)
    {
        struct xkb_keysym_iterator* iter = calloc(1, sizeof(*iter));
        iter->explicit = iterate_only_explicit_keysyms;
        iter->index = -1;
        iter->keysym = XKB_KEYSYM_UNICODE_MAX;
        return iter;
    }
    
    struct xkb_keysym_iterator*
    xkb_keysym_iterator_unref(struct xkb_keysym_iterator *iter)
    {
        free(iter);
        return NULL;
    }
    
    xkb_keysym_t
    xkb_keysym_iterator_get_keysym(struct xkb_keysym_iterator *iter)
    {
        return iter->keysym;
    }
    
    bool
    xkb_keysym_iterator_is_explicitly_named(struct xkb_keysym_iterator *iter)
    {
        return iter->index >= 0 &&
               iter->index < (int32_t)ARRAY_SIZE(keysym_to_name) &&
               (iter->explicit ||
                iter->keysym == keysym_to_name[iter->index].keysym);
    }
    
    int
    xkb_keysym_iterator_get_name(struct xkb_keysym_iterator *iter,
                                 char *buffer, size_t size)
    {
        if (iter->index < 0 || iter->index >= (int32_t)ARRAY_SIZE(keysym_to_name))
            return -1;
        if (iter->explicit || iter->keysym == keysym_to_name[iter->index].keysym)
            return snprintf(buffer, size, "%s",
                            get_name(&keysym_to_name[iter->index]));
        return get_unicode_name(iter->keysym, buffer, size);
    }
    
    /* Iterate over the *assigned* keysyms.
     *
     * Use:
     *
     * ```c
     * struct xkb_keysym_iterator *iter = xkb_keysym_iterator_new(true);
     * while (xkb_keysym_iterator_next(iter)) {
     *   ...
     * }
     * iter = xkb_keysym_iterator_unref(iter);
     * ```
     */
    bool
    xkb_keysym_iterator_next(struct xkb_keysym_iterator *iter)
    {
        if (iter->index >= (int32_t)ARRAY_SIZE(keysym_to_name) - 1)
            return false;
    
        /* Next keysym */
        if (iter->explicit || iter->keysym >= XKB_KEYSYM_UNICODE_MAX ||
            keysym_to_name[iter->index + 1].keysym < XKB_KEYSYM_UNICODE_MIN) {
            /* Explicitly named keysyms only */
            iter->keysym = keysym_to_name[++iter->index].keysym;
            assert(iter->explicit ||
                   iter->keysym <= XKB_KEYSYM_UNICODE_MIN ||
                   iter->keysym >= XKB_KEYSYM_UNICODE_MAX);
        } else {
            /* Unicode keysyms
             * NOTE: Unicode keysyms are within keysym_to_name keysyms range. */
            if (iter->keysym >= keysym_to_name[iter->index].keysym)
                iter->index++;
            if (iter->keysym >= XKB_KEYSYM_UNICODE_MIN) {
                /* Continue Unicode keysyms */
                iter->keysym++;
            } else {
                /* Start Unicode keysyms */
                iter->keysym = XKB_KEYSYM_UNICODE_MIN;
            }
        }
        return true;
    }
    
    /*
     * Parse the numeric part of a 0xXXXX and UXXXX keysym.
     * Not using strtoul -- it's slower and accepts a bunch of stuff
     * we don't want to allow, like signs, spaces, even locale stuff.
     */
    static bool
    parse_keysym_hex(const char *s, uint32_t *out)
    {
        uint32_t result = 0;
        int i;
        for (i = 0; i < 8 && s[i] != '\0'; i++) {
            result <<= 4;
            if ('0' <= s[i] && s[i] <= '9')
                result += s[i] - '0';
            else if ('a' <= s[i] && s[i] <= 'f')
                result += 10 + s[i] - 'a';
            else if ('A' <= s[i] && s[i] <= 'F')
                result += 10 + s[i] - 'A';
            else
                return false;
        }
        *out = result;
        return s[i] == '\0' && i > 0;
    }
    
    XKB_EXPORT xkb_keysym_t
    xkb_keysym_from_name(const char *name, enum xkb_keysym_flags flags)
    {
        const struct name_keysym *entry = NULL;
        char *tmp;
        uint32_t val;
        bool icase = (flags & XKB_KEYSYM_CASE_INSENSITIVE);
    
        if (flags & ~XKB_KEYSYM_CASE_INSENSITIVE)
            return XKB_KEY_NoSymbol;
    
        /*
         * We need to !icase case to be fast, for e.g. Compose file parsing.
         * So do it in a fast path.
         */
        if (!icase) {
            size_t pos = keysym_name_perfect_hash(name);
            if (pos < ARRAY_SIZE(name_to_keysym)) {
                const char *s = get_name(&name_to_keysym[pos]);
                if (strcmp(name, s) == 0)
                    return name_to_keysym[pos].keysym;
            }
        }
        /*
        * Find the correct keysym for case-insensitive match.
        *
        * The name_to_keysym table is sorted by istrcmp(). So the binary
        * search may return _any_ of all possible case-insensitive duplicates. The
        * duplicates are sorted so that the "best" case-insensitive match comes
        * last. So the code searches the entry and all next entries that match by
        * case-insensitive comparison and returns the "best" case-insensitive match.
        *
        * The "best" case-insensitive match is the lower-case keysym name. Most
        * keysyms names that only differ by letter-case are keysyms that are
        * available as “small” and “big” variants. For example:
        *
        * - Bicameral scripts: Lower-case and upper-case variants,
        *   e.g. KEY_a and KEY_A.
        * - Non-bicameral scripts: e.g. KEY_kana_a and KEY_kana_A.
        *
        * There are some exceptions, e.g. `XF86Screensaver` and `XF86ScreenSaver`.
        */
        else {
            int32_t lo = 0, hi = ARRAY_SIZE(name_to_keysym) - 1;
            while (hi >= lo) {
                int32_t mid = (lo + hi) / 2;
                int cmp = istrcmp(name, get_name(&name_to_keysym[mid]));
                if (cmp > 0) {
                    lo = mid + 1;
                } else if (cmp < 0) {
                    hi = mid - 1;
                } else {
                    entry = &name_to_keysym[mid];
                    break;
                }
            }
            if (entry) {
                const struct name_keysym *last;
                last = name_to_keysym + ARRAY_SIZE(name_to_keysym) - 1;
                /* Keep going until we reach end of array
                 * or non case-insensitve match */
                while (entry < last &&
                       istrcmp(get_name(entry + 1), get_name(entry)) == 0) {
                    entry++;
                }
                return entry->keysym;
            }
        }
    
        if (*name == 'U' || (icase && *name == 'u')) {
            if (!parse_keysym_hex(&name[1], &val))
                return XKB_KEY_NoSymbol;
    
            if (val < 0x20 || (val > 0x7e && val < 0xa0))
                return XKB_KEY_NoSymbol;
            if (val < 0x100)
                return (xkb_keysym_t) val;
            if (val > 0x10ffff)
                return XKB_KEY_NoSymbol;
            return (xkb_keysym_t) val | XKB_KEYSYM_UNICODE_OFFSET;
        }
        else if (name[0] == '0' && (name[1] == 'x' || (icase && name[1] == 'X'))) {
            if (!parse_keysym_hex(&name[2], &val))
                return XKB_KEY_NoSymbol;
            if (val > XKB_KEYSYM_MAX)
                return XKB_KEY_NoSymbol;
            return (xkb_keysym_t) val;
        }
    
        /* Stupid inconsistency between the headers and XKeysymDB: the former has
         * no separating underscore, while some XF86* syms in the latter did.
         * As a last ditch effort, try without. */
        if (strncmp(name, "XF86_", 5) == 0 ||
            (icase && istrncmp(name, "XF86_", 5) == 0)) {
            xkb_keysym_t ret;
            tmp = strdup(name);
            if (!tmp)
                return XKB_KEY_NoSymbol;
            memmove(&tmp[4], &tmp[5], strlen(name) - 5 + 1);
            ret = xkb_keysym_from_name(tmp, flags);
            free(tmp);
            return ret;
        }
    
        return XKB_KEY_NoSymbol;
    }
    
    bool
    xkb_keysym_is_keypad(xkb_keysym_t keysym)
    {
        return keysym >= XKB_KEY_KP_Space && keysym <= XKB_KEY_KP_Equal;
    }
    
    
    bool
    xkb_keysym_is_modifier(xkb_keysym_t keysym)
    {
        return
            (keysym >= XKB_KEY_Shift_L && keysym <= XKB_KEY_Hyper_R) ||
            (keysym >= XKB_KEY_ISO_Lock && keysym <= XKB_KEY_ISO_Level5_Lock) ||
            keysym == XKB_KEY_Mode_switch ||
            keysym == XKB_KEY_Num_Lock;
    }
    
    static void
    XConvertCase(xkb_keysym_t sym, xkb_keysym_t *lower, xkb_keysym_t *upper);
    
    bool
    xkb_keysym_is_lower(xkb_keysym_t ks)
    {
        xkb_keysym_t lower, upper;
    
        XConvertCase(ks, &lower, &upper);
    
        if (lower == upper)
            return false;
    
        return (ks == lower ? true : false);
    }
    
    bool
    xkb_keysym_is_upper(xkb_keysym_t ks)
    {
        xkb_keysym_t lower, upper;
    
        XConvertCase(ks, &lower, &upper);
    
        if (lower == upper)
            return false;
    
        return (ks == upper ? true : false);
    }
    
    XKB_EXPORT xkb_keysym_t
    xkb_keysym_to_lower(xkb_keysym_t ks)
    {
        xkb_keysym_t lower, upper;
    
        XConvertCase(ks, &lower, &upper);
    
        return lower;
    }
    
    XKB_EXPORT xkb_keysym_t
    xkb_keysym_to_upper(xkb_keysym_t ks)
    {
        xkb_keysym_t lower, upper;
    
        XConvertCase(ks, &lower, &upper);
    
        return upper;
    }
    
    /*
     * The following is copied verbatim from libX11:src/KeyBind.c, commit
     * d45b3fc19fbe95c41afc4e51d768df6d42332010, with the following changes:
     *  - unsigned -> uint32_t
     *  - unsigend short -> uint16_t
     *  - s/XK_/XKB_KEY_
     *
     * XXX: If newlocale() and iswlower_l()/iswupper_l() interface ever
     *      become portable, we should use that in conjunction with
     *      xkb_keysym_to_utf32(), instead of all this stuff.  We should
     *      be sure to give the same results as libX11, though, and be
     *      locale independent; this information is used by xkbcomp to
     *      find the automatic type to assign to key groups.
     */
    
    static void
    UCSConvertCase(uint32_t code, xkb_keysym_t *lower, xkb_keysym_t *upper)
    {
        /* Case conversion for UCS, as in Unicode Data version 4.0.0 */
        /* NB: Only converts simple one-to-one mappings. */
    
        /* Tables are used where they take less space than     */
        /* the code to work out the mappings. Zero values mean */
        /* undefined code points.                              */
    
        static uint16_t const IPAExt_upper_mapping[] = { /* part only */
                                0x0181, 0x0186, 0x0255, 0x0189, 0x018A,
        0x0258, 0x018F, 0x025A, 0x0190, 0x025C, 0x025D, 0x025E, 0x025F,
        0x0193, 0x0261, 0x0262, 0x0194, 0x0264, 0x0265, 0x0266, 0x0267,
        0x0197, 0x0196, 0x026A, 0x026B, 0x026C, 0x026D, 0x026E, 0x019C,
        0x0270, 0x0271, 0x019D, 0x0273, 0x0274, 0x019F, 0x0276, 0x0277,
        0x0278, 0x0279, 0x027A, 0x027B, 0x027C, 0x027D, 0x027E, 0x027F,
        0x01A6, 0x0281, 0x0282, 0x01A9, 0x0284, 0x0285, 0x0286, 0x0287,
        0x01AE, 0x0289, 0x01B1, 0x01B2, 0x028C, 0x028D, 0x028E, 0x028F,
        0x0290, 0x0291, 0x01B7
        };
    
        static uint16_t const LatinExtB_upper_mapping[] = { /* first part only */
        0x0180, 0x0181, 0x0182, 0x0182, 0x0184, 0x0184, 0x0186, 0x0187,
        0x0187, 0x0189, 0x018A, 0x018B, 0x018B, 0x018D, 0x018E, 0x018F,
        0x0190, 0x0191, 0x0191, 0x0193, 0x0194, 0x01F6, 0x0196, 0x0197,
        0x0198, 0x0198, 0x019A, 0x019B, 0x019C, 0x019D, 0x0220, 0x019F,
        0x01A0, 0x01A0, 0x01A2, 0x01A2, 0x01A4, 0x01A4, 0x01A6, 0x01A7,
        0x01A7, 0x01A9, 0x01AA, 0x01AB, 0x01AC, 0x01AC, 0x01AE, 0x01AF,
        0x01AF, 0x01B1, 0x01B2, 0x01B3, 0x01B3, 0x01B5, 0x01B5, 0x01B7,
        0x01B8, 0x01B8, 0x01BA, 0x01BB, 0x01BC, 0x01BC, 0x01BE, 0x01F7,
        0x01C0, 0x01C1, 0x01C2, 0x01C3, 0x01C4, 0x01C4, 0x01C4, 0x01C7,
        0x01C7, 0x01C7, 0x01CA, 0x01CA, 0x01CA
        };
    
        static uint16_t const LatinExtB_lower_mapping[] = { /* first part only */
        0x0180, 0x0253, 0x0183, 0x0183, 0x0185, 0x0185, 0x0254, 0x0188,
        0x0188, 0x0256, 0x0257, 0x018C, 0x018C, 0x018D, 0x01DD, 0x0259,
        0x025B, 0x0192, 0x0192, 0x0260, 0x0263, 0x0195, 0x0269, 0x0268,
        0x0199, 0x0199, 0x019A, 0x019B, 0x026F, 0x0272, 0x019E, 0x0275,
        0x01A1, 0x01A1, 0x01A3, 0x01A3, 0x01A5, 0x01A5, 0x0280, 0x01A8,
        0x01A8, 0x0283, 0x01AA, 0x01AB, 0x01AD, 0x01AD, 0x0288, 0x01B0,
        0x01B0, 0x028A, 0x028B, 0x01B4, 0x01B4, 0x01B6, 0x01B6, 0x0292,
        0x01B9, 0x01B9, 0x01BA, 0x01BB, 0x01BD, 0x01BD, 0x01BE, 0x01BF,
        0x01C0, 0x01C1, 0x01C2, 0x01C3, 0x01C6, 0x01C6, 0x01C6, 0x01C9,
        0x01C9, 0x01C9, 0x01CC, 0x01CC, 0x01CC
        };
    
        static uint16_t const Greek_upper_mapping[] = {
        0x0000, 0x0000, 0x0000, 0x0000, 0x0374, 0x0375, 0x0000, 0x0000,
        0x0000, 0x0000, 0x037A, 0x0000, 0x0000, 0x0000, 0x037E, 0x0000,
        0x0000, 0x0000, 0x0000, 0x0000, 0x0384, 0x0385, 0x0386, 0x0387,
        0x0388, 0x0389, 0x038A, 0x0000, 0x038C, 0x0000, 0x038E, 0x038F,
        0x0390, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397,
        0x0398, 0x0399, 0x039A, 0x039B, 0x039C, 0x039D, 0x039E, 0x039F,
        0x03A0, 0x03A1, 0x0000, 0x03A3, 0x03A4, 0x03A5, 0x03A6, 0x03A7,
        0x03A8, 0x03A9, 0x03AA, 0x03AB, 0x0386, 0x0388, 0x0389, 0x038A,
        0x03B0, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397,
        0x0398, 0x0399, 0x039A, 0x039B, 0x039C, 0x039D, 0x039E, 0x039F,
        0x03A0, 0x03A1, 0x03A3, 0x03A3, 0x03A4, 0x03A5, 0x03A6, 0x03A7,
        0x03A8, 0x03A9, 0x03AA, 0x03AB, 0x038C, 0x038E, 0x038F, 0x0000,
        0x0392, 0x0398, 0x03D2, 0x03D3, 0x03D4, 0x03A6, 0x03A0, 0x03D7,
        0x03D8, 0x03D8, 0x03DA, 0x03DA, 0x03DC, 0x03DC, 0x03DE, 0x03DE,
        0x03E0, 0x03E0, 0x03E2, 0x03E2, 0x03E4, 0x03E4, 0x03E6, 0x03E6,
        0x03E8, 0x03E8, 0x03EA, 0x03EA, 0x03EC, 0x03EC, 0x03EE, 0x03EE,
        0x039A, 0x03A1, 0x03F9, 0x03F3, 0x03F4, 0x0395, 0x03F6, 0x03F7,
        0x03F7, 0x03F9, 0x03FA, 0x03FA, 0x0000, 0x0000, 0x0000, 0x0000
        };
    
        static uint16_t const Greek_lower_mapping[] = {
        0x0000, 0x0000, 0x0000, 0x0000, 0x0374, 0x0375, 0x0000, 0x0000,
        0x0000, 0x0000, 0x037A, 0x0000, 0x0000, 0x0000, 0x037E, 0x0000,
        0x0000, 0x0000, 0x0000, 0x0000, 0x0384, 0x0385, 0x03AC, 0x0387,
        0x03AD, 0x03AE, 0x03AF, 0x0000, 0x03CC, 0x0000, 0x03CD, 0x03CE,
        0x0390, 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7,
        0x03B8, 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF,
        0x03C0, 0x03C1, 0x0000, 0x03C3, 0x03C4, 0x03C5, 0x03C6, 0x03C7,
        0x03C8, 0x03C9, 0x03CA, 0x03CB, 0x03AC, 0x03AD, 0x03AE, 0x03AF,
        0x03B0, 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7,
        0x03B8, 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF,
        0x03C0, 0x03C1, 0x03C2, 0x03C3, 0x03C4, 0x03C5, 0x03C6, 0x03C7,
        0x03C8, 0x03C9, 0x03CA, 0x03CB, 0x03CC, 0x03CD, 0x03CE, 0x0000,
        0x03D0, 0x03D1, 0x03D2, 0x03D3, 0x03D4, 0x03D5, 0x03D6, 0x03D7,
        0x03D9, 0x03D9, 0x03DB, 0x03DB, 0x03DD, 0x03DD, 0x03DF, 0x03DF,
        0x03E1, 0x03E1, 0x03E3, 0x03E3, 0x03E5, 0x03E5, 0x03E7, 0x03E7,
        0x03E9, 0x03E9, 0x03EB, 0x03EB, 0x03ED, 0x03ED, 0x03EF, 0x03EF,
        0x03F0, 0x03F1, 0x03F2, 0x03F3, 0x03B8, 0x03F5, 0x03F6, 0x03F8,
        0x03F8, 0x03F2, 0x03FB, 0x03FB, 0x0000, 0x0000, 0x0000, 0x0000
        };
    
        static uint16_t const GreekExt_lower_mapping[] = {
        0x1F00, 0x1F01, 0x1F02, 0x1F03, 0x1F04, 0x1F05, 0x1F06, 0x1F07,
        0x1F00, 0x1F01, 0x1F02, 0x1F03, 0x1F04, 0x1F05, 0x1F06, 0x1F07,
        0x1F10, 0x1F11, 0x1F12, 0x1F13, 0x1F14, 0x1F15, 0x0000, 0x0000,
        0x1F10, 0x1F11, 0x1F12, 0x1F13, 0x1F14, 0x1F15, 0x0000, 0x0000,
        0x1F20, 0x1F21, 0x1F22, 0x1F23, 0x1F24, 0x1F25, 0x1F26, 0x1F27,
        0x1F20, 0x1F21, 0x1F22, 0x1F23, 0x1F24, 0x1F25, 0x1F26, 0x1F27,
        0x1F30, 0x1F31, 0x1F32, 0x1F33, 0x1F34, 0x1F35, 0x1F36, 0x1F37,
        0x1F30, 0x1F31, 0x1F32, 0x1F33, 0x1F34, 0x1F35, 0x1F36, 0x1F37,
        0x1F40, 0x1F41, 0x1F42, 0x1F43, 0x1F44, 0x1F45, 0x0000, 0x0000,
        0x1F40, 0x1F41, 0x1F42, 0x1F43, 0x1F44, 0x1F45, 0x0000, 0x0000,
        0x1F50, 0x1F51, 0x1F52, 0x1F53, 0x1F54, 0x1F55, 0x1F56, 0x1F57,
        0x0000, 0x1F51, 0x0000, 0x1F53, 0x0000, 0x1F55, 0x0000, 0x1F57,
        0x1F60, 0x1F61, 0x1F62, 0x1F63, 0x1F64, 0x1F65, 0x1F66, 0x1F67,
        0x1F60, 0x1F61, 0x1F62, 0x1F63, 0x1F64, 0x1F65, 0x1F66, 0x1F67,
        0x1F70, 0x1F71, 0x1F72, 0x1F73, 0x1F74, 0x1F75, 0x1F76, 0x1F77,
        0x1F78, 0x1F79, 0x1F7A, 0x1F7B, 0x1F7C, 0x1F7D, 0x0000, 0x0000,
        0x1F80, 0x1F81, 0x1F82, 0x1F83, 0x1F84, 0x1F85, 0x1F86, 0x1F87,
        0x1F80, 0x1F81, 0x1F82, 0x1F83, 0x1F84, 0x1F85, 0x1F86, 0x1F87,
        0x1F90, 0x1F91, 0x1F92, 0x1F93, 0x1F94, 0x1F95, 0x1F96, 0x1F97,
        0x1F90, 0x1F91, 0x1F92, 0x1F93, 0x1F94, 0x1F95, 0x1F96, 0x1F97,
        0x1FA0, 0x1FA1, 0x1FA2, 0x1FA3, 0x1FA4, 0x1FA5, 0x1FA6, 0x1FA7,
        0x1FA0, 0x1FA1, 0x1FA2, 0x1FA3, 0x1FA4, 0x1FA5, 0x1FA6, 0x1FA7,
        0x1FB0, 0x1FB1, 0x1FB2, 0x1FB3, 0x1FB4, 0x0000, 0x1FB6, 0x1FB7,
        0x1FB0, 0x1FB1, 0x1F70, 0x1F71, 0x1FB3, 0x1FBD, 0x1FBE, 0x1FBF,
        0x1FC0, 0x1FC1, 0x1FC2, 0x1FC3, 0x1FC4, 0x0000, 0x1FC6, 0x1FC7,
        0x1F72, 0x1F73, 0x1F74, 0x1F75, 0x1FC3, 0x1FCD, 0x1FCE, 0x1FCF,
        0x1FD0, 0x1FD1, 0x1FD2, 0x1FD3, 0x0000, 0x0000, 0x1FD6, 0x1FD7,
        0x1FD0, 0x1FD1, 0x1F76, 0x1F77, 0x0000, 0x1FDD, 0x1FDE, 0x1FDF,
        0x1FE0, 0x1FE1, 0x1FE2, 0x1FE3, 0x1FE4, 0x1FE5, 0x1FE6, 0x1FE7,
        0x1FE0, 0x1FE1, 0x1F7A, 0x1F7B, 0x1FE5, 0x1FED, 0x1FEE, 0x1FEF,
        0x0000, 0x0000, 0x1FF2, 0x1FF3, 0x1FF4, 0x0000, 0x1FF6, 0x1FF7,
        0x1F78, 0x1F79, 0x1F7C, 0x1F7D, 0x1FF3, 0x1FFD, 0x1FFE, 0x0000
        };
    
        static uint16_t const GreekExt_upper_mapping[] = {
        0x1F08, 0x1F09, 0x1F0A, 0x1F0B, 0x1F0C, 0x1F0D, 0x1F0E, 0x1F0F,
        0x1F08, 0x1F09, 0x1F0A, 0x1F0B, 0x1F0C, 0x1F0D, 0x1F0E, 0x1F0F,
        0x1F18, 0x1F19, 0x1F1A, 0x1F1B, 0x1F1C, 0x1F1D, 0x0000, 0x0000,
        0x1F18, 0x1F19, 0x1F1A, 0x1F1B, 0x1F1C, 0x1F1D, 0x0000, 0x0000,
        0x1F28, 0x1F29, 0x1F2A, 0x1F2B, 0x1F2C, 0x1F2D, 0x1F2E, 0x1F2F,
        0x1F28, 0x1F29, 0x1F2A, 0x1F2B, 0x1F2C, 0x1F2D, 0x1F2E, 0x1F2F,
        0x1F38, 0x1F39, 0x1F3A, 0x1F3B, 0x1F3C, 0x1F3D, 0x1F3E, 0x1F3F,
        0x1F38, 0x1F39, 0x1F3A, 0x1F3B, 0x1F3C, 0x1F3D, 0x1F3E, 0x1F3F,
        0x1F48, 0x1F49, 0x1F4A, 0x1F4B, 0x1F4C, 0x1F4D, 0x0000, 0x0000,
        0x1F48, 0x1F49, 0x1F4A, 0x1F4B, 0x1F4C, 0x1F4D, 0x0000, 0x0000,
        0x1F50, 0x1F59, 0x1F52, 0x1F5B, 0x1F54, 0x1F5D, 0x1F56, 0x1F5F,
        0x0000, 0x1F59, 0x0000, 0x1F5B, 0x0000, 0x1F5D, 0x0000, 0x1F5F,
        0x1F68, 0x1F69, 0x1F6A, 0x1F6B, 0x1F6C, 0x1F6D, 0x1F6E, 0x1F6F,
        0x1F68, 0x1F69, 0x1F6A, 0x1F6B, 0x1F6C, 0x1F6D, 0x1F6E, 0x1F6F,
        0x1FBA, 0x1FBB, 0x1FC8, 0x1FC9, 0x1FCA, 0x1FCB, 0x1FDA, 0x1FDB,
        0x1FF8, 0x1FF9, 0x1FEA, 0x1FEB, 0x1FFA, 0x1FFB, 0x0000, 0x0000,
        0x1F88, 0x1F89, 0x1F8A, 0x1F8B, 0x1F8C, 0x1F8D, 0x1F8E, 0x1F8F,
        0x1F88, 0x1F89, 0x1F8A, 0x1F8B, 0x1F8C, 0x1F8D, 0x1F8E, 0x1F8F,
        0x1F98, 0x1F99, 0x1F9A, 0x1F9B, 0x1F9C, 0x1F9D, 0x1F9E, 0x1F9F,
        0x1F98, 0x1F99, 0x1F9A, 0x1F9B, 0x1F9C, 0x1F9D, 0x1F9E, 0x1F9F,
        0x1FA8, 0x1FA9, 0x1FAA, 0x1FAB, 0x1FAC, 0x1FAD, 0x1FAE, 0x1FAF,
        0x1FA8, 0x1FA9, 0x1FAA, 0x1FAB, 0x1FAC, 0x1FAD, 0x1FAE, 0x1FAF,
        0x1FB8, 0x1FB9, 0x1FB2, 0x1FBC, 0x1FB4, 0x0000, 0x1FB6, 0x1FB7,
        0x1FB8, 0x1FB9, 0x1FBA, 0x1FBB, 0x1FBC, 0x1FBD, 0x0399, 0x1FBF,
        0x1FC0, 0x1FC1, 0x1FC2, 0x1FCC, 0x1FC4, 0x0000, 0x1FC6, 0x1FC7,
        0x1FC8, 0x1FC9, 0x1FCA, 0x1FCB, 0x1FCC, 0x1FCD, 0x1FCE, 0x1FCF,
        0x1FD8, 0x1FD9, 0x1FD2, 0x1FD3, 0x0000, 0x0000, 0x1FD6, 0x1FD7,
        0x1FD8, 0x1FD9, 0x1FDA, 0x1FDB, 0x0000, 0x1FDD, 0x1FDE, 0x1FDF,
        0x1FE8, 0x1FE9, 0x1FE2, 0x1FE3, 0x1FE4, 0x1FEC, 0x1FE6, 0x1FE7,
        0x1FE8, 0x1FE9, 0x1FEA, 0x1FEB, 0x1FEC, 0x1FED, 0x1FEE, 0x1FEF,
        0x0000, 0x0000, 0x1FF2, 0x1FFC, 0x1FF4, 0x0000, 0x1FF6, 0x1FF7,
        0x1FF8, 0x1FF9, 0x1FFA, 0x1FFB, 0x1FFC, 0x1FFD, 0x1FFE, 0x0000
        };
    
        *lower = code;
        *upper = code;
    
        /* Basic Latin and Latin-1 Supplement, U+0000 to U+00FF */
        if (code <= 0x00ff) {
            if (code >= 0x0041 && code <= 0x005a)             /* A-Z */
                *lower += 0x20;
            else if (code >= 0x0061 && code <= 0x007a)        /* a-z */
                *upper -= 0x20;
            else if ( (code >= 0x00c0 && code <= 0x00d6) ||
    	          (code >= 0x00d8 && code <= 0x00de) )
                *lower += 0x20;
            else if ( (code >= 0x00e0 && code <= 0x00f6) ||
    	          (code >= 0x00f8 && code <= 0x00fe) )
                *upper -= 0x20;
            else if (code == 0x00ff)      /* y with diaeresis */
                *upper = 0x0178;
            else if (code == 0x00b5)      /* micro sign */
                *upper = 0x039c;
    	return;
        }
    
        /* Latin Extended-A, U+0100 to U+017F */
        if (code >= 0x0100 && code <= 0x017f) {
            if ( (code >= 0x0100 && code <= 0x012f) ||
                 (code >= 0x0132 && code <= 0x0137) ||
                 (code >= 0x014a && code <= 0x0177) ) {
                *upper = code & ~1;
                *lower = code | 1;
            }
            else if ( (code >= 0x0139 && code <= 0x0148) ||
                      (code >= 0x0179 && code <= 0x017e) ) {
                if (code & 1)
    	        *lower += 1;
                else
    	        *upper -= 1;
            }
            else if (code == 0x0130)
                *lower = 0x0069;
            else if (code == 0x0131)
                *upper = 0x0049;
            else if (code == 0x0178)
                *lower = 0x00ff;
            else if (code == 0x017f)
                *upper = 0x0053;
            return;
        }
    
        /* Latin Extended-B, U+0180 to U+024F */
        if (code >= 0x0180 && code <= 0x024f) {
            if (code >= 0x01cd && code <= 0x01dc) {
    	    if (code & 1)
    	       *lower += 1;
    	    else
    	       *upper -= 1;
            }
            else if ( (code >= 0x01de && code <= 0x01ef) ||
                      (code >= 0x01f4 && code <= 0x01f5) ||
                      (code >= 0x01f8 && code <= 0x021f) ||
                      (code >= 0x0222 && code <= 0x0233) ) {
                *lower |= 1;
                *upper &= ~1;
            }
            else if (code >= 0x0180 && code <= 0x01cc) {
                *lower = LatinExtB_lower_mapping[code - 0x0180];
                *upper = LatinExtB_upper_mapping[code - 0x0180];
            }
            else if (code == 0x01dd)
                *upper = 0x018e;
            else if (code == 0x01f1 || code == 0x01f2) {
                *lower = 0x01f3;
                *upper = 0x01f1;
            }
            else if (code == 0x01f3)
                *upper = 0x01f1;
            else if (code == 0x01f6)
                *lower = 0x0195;
            else if (code == 0x01f7)
                *lower = 0x01bf;
            else if (code == 0x0220)
                *lower = 0x019e;
            return;
        }
    
        /* IPA Extensions, U+0250 to U+02AF */
        if (code >= 0x0253 && code <= 0x0292) {
            *upper = IPAExt_upper_mapping[code - 0x0253];
        }
    
        /* Combining Diacritical Marks, U+0300 to U+036F */
        if (code == 0x0345) {
            *upper = 0x0399;
        }
    
        /* Greek and Coptic, U+0370 to U+03FF */
        if (code >= 0x0370 && code <= 0x03ff) {
            *lower = Greek_lower_mapping[code - 0x0370];
            *upper = Greek_upper_mapping[code - 0x0370];
            if (*upper == 0)
                *upper = code;
            if (*lower == 0)
                *lower = code;
        }
    
        /* Cyrillic and Cyrillic Supplementary, U+0400 to U+052F */
        if ( (code >= 0x0400 && code <= 0x04ff) ||
             (code >= 0x0500 && code <= 0x052f) ) {
            if (code >= 0x0400 && code <= 0x040f)
                *lower += 0x50;
            else if (code >= 0x0410 && code <= 0x042f)
                *lower += 0x20;
            else if (code >= 0x0430 && code <= 0x044f)
                *upper -= 0x20;
            else if (code >= 0x0450 && code <= 0x045f)
                *upper -= 0x50;
            else if ( (code >= 0x0460 && code <= 0x0481) ||
                      (code >= 0x048a && code <= 0x04bf) ||
    	          (code >= 0x04d0 && code <= 0x04f5) ||
    	          (code >= 0x04f8 && code <= 0x04f9) ||
                      (code >= 0x0500 && code <= 0x050f) ) {
                *upper &= ~1;
                *lower |= 1;
            }
            else if (code >= 0x04c1 && code <= 0x04ce) {
    	    if (code & 1)
    	        *lower += 1;
    	    else
    	        *upper -= 1;
            }
        }
    
        /* Armenian, U+0530 to U+058F */
        if (code >= 0x0530 && code <= 0x058f) {
            if (code >= 0x0531 && code <= 0x0556)
                *lower += 0x30;
            else if (code >=0x0561 && code <= 0x0586)
                *upper -= 0x30;
        }
    
        /* Latin Extended Additional, U+1E00 to U+1EFF */
        if (code >= 0x1e00 && code <= 0x1eff) {
            if ( (code >= 0x1e00 && code <= 0x1e95) ||
                 (code >= 0x1ea0 && code <= 0x1ef9) ) {
                *upper &= ~1;
                *lower |= 1;
            }
            else if (code == 0x1e9b)
                *upper = 0x1e60;
            else if (code == 0x1e9e)
                *lower = 0x00df; /* ssharp */
        }
    
        /* Greek Extended, U+1F00 to U+1FFF */
        if (code >= 0x1f00 && code <= 0x1fff) {
            *lower = GreekExt_lower_mapping[code - 0x1f00];
            *upper = GreekExt_upper_mapping[code - 0x1f00];
            if (*upper == 0)
                *upper = code;
            if (*lower == 0)
                *lower = code;
        }
    
        /* Letterlike Symbols, U+2100 to U+214F */
        if (code >= 0x2100 && code <= 0x214f) {
            switch (code) {
            case 0x2126: *lower = 0x03c9; break;
            case 0x212a: *lower = 0x006b; break;
            case 0x212b: *lower = 0x00e5; break;
            }
        }
        /* Number Forms, U+2150 to U+218F */
        else if (code >= 0x2160 && code <= 0x216f)
            *lower += 0x10;
        else if (code >= 0x2170 && code <= 0x217f)
            *upper -= 0x10;
        /* Enclosed Alphanumerics, U+2460 to U+24FF */
        else if (code >= 0x24b6 && code <= 0x24cf)
            *lower += 0x1a;
        else if (code >= 0x24d0 && code <= 0x24e9)
            *upper -= 0x1a;
        /* Halfwidth and Fullwidth Forms, U+FF00 to U+FFEF */
        else if (code >= 0xff21 && code <= 0xff3a)
            *lower += 0x20;
        else if (code >= 0xff41 && code <= 0xff5a)
            *upper -= 0x20;
        /* Deseret, U+10400 to U+104FF */
        else if (code >= 0x10400 && code <= 0x10427)
            *lower += 0x28;
        else if (code >= 0x10428 && code <= 0x1044f)
            *upper -= 0x28;
    }
    
    static void
    XConvertCase(xkb_keysym_t sym, xkb_keysym_t *lower, xkb_keysym_t *upper)
    {
        /* Latin 1 keysym (first part: fast path) */
        if (sym < 0xb5) {
            UCSConvertCase(sym, lower, upper);
    	return;
        }
    
        /* Unicode keysym */
        if ((sym & 0xff000000) == XKB_KEYSYM_UNICODE_OFFSET) {
            UCSConvertCase((sym & 0x00ffffff), lower, upper);
            *upper |= XKB_KEYSYM_UNICODE_OFFSET;
            *lower |= XKB_KEYSYM_UNICODE_OFFSET;
            return;
        }
    
        /* Legacy keysym */
    
        *lower = sym;
        *upper = sym;
    
        switch(sym >> 8) {
        case 0: /* Latin 1 (second part) */
        if (sym == XKB_KEY_mu)
            *upper = XKB_KEY_Greek_MU;
        else if (sym == XKB_KEY_ydiaeresis)
            *upper = XKB_KEY_Ydiaeresis;
        else
            UCSConvertCase(sym, lower, upper);
        break;
        case 1: /* Latin 2 */
    	/* Assume the KeySym is a legal value (ignore discontinuities) */
    	if (sym == XKB_KEY_Aogonek)
    	    *lower = XKB_KEY_aogonek;
    	else if (sym >= XKB_KEY_Lstroke && sym <= XKB_KEY_Sacute)
    	    *lower += (XKB_KEY_lstroke - XKB_KEY_Lstroke);
    	else if (sym >= XKB_KEY_Scaron && sym <= XKB_KEY_Zacute)
    	    *lower += (XKB_KEY_scaron - XKB_KEY_Scaron);
    	else if (sym >= XKB_KEY_Zcaron && sym <= XKB_KEY_Zabovedot)
    	    *lower += (XKB_KEY_zcaron - XKB_KEY_Zcaron);
    	else if (sym == XKB_KEY_aogonek)
    	    *upper = XKB_KEY_Aogonek;
    	else if (sym >= XKB_KEY_lstroke && sym <= XKB_KEY_sacute)
    	    *upper -= (XKB_KEY_lstroke - XKB_KEY_Lstroke);
    	else if (sym >= XKB_KEY_scaron && sym <= XKB_KEY_zacute)
    	    *upper -= (XKB_KEY_scaron - XKB_KEY_Scaron);
    	else if (sym >= XKB_KEY_zcaron && sym <= XKB_KEY_zabovedot)
    	    *upper -= (XKB_KEY_zcaron - XKB_KEY_Zcaron);
    	else if (sym >= XKB_KEY_Racute && sym <= XKB_KEY_Tcedilla)
    	    *lower += (XKB_KEY_racute - XKB_KEY_Racute);
    	else if (sym >= XKB_KEY_racute && sym <= XKB_KEY_tcedilla)
    	    *upper -= (XKB_KEY_racute - XKB_KEY_Racute);
    	break;
        case 2: /* Latin 3 */
    	/* Assume the KeySym is a legal value (ignore discontinuities) */
    	if (sym >= XKB_KEY_Hstroke && sym <= XKB_KEY_Hcircumflex)
    	    *lower += (XKB_KEY_hstroke - XKB_KEY_Hstroke);
    	else if (sym >= XKB_KEY_Gbreve && sym <= XKB_KEY_Jcircumflex)
    	    *lower += (XKB_KEY_gbreve - XKB_KEY_Gbreve);
    	else if (sym >= XKB_KEY_hstroke && sym <= XKB_KEY_hcircumflex)
    	    *upper -= (XKB_KEY_hstroke - XKB_KEY_Hstroke);
    	else if (sym >= XKB_KEY_gbreve && sym <= XKB_KEY_jcircumflex)
    	    *upper -= (XKB_KEY_gbreve - XKB_KEY_Gbreve);
    	else if (sym >= XKB_KEY_Cabovedot && sym <= XKB_KEY_Scircumflex)
    	    *lower += (XKB_KEY_cabovedot - XKB_KEY_Cabovedot);
    	else if (sym >= XKB_KEY_cabovedot && sym <= XKB_KEY_scircumflex)
    	    *upper -= (XKB_KEY_cabovedot - XKB_KEY_Cabovedot);
    	break;
        case 3: /* Latin 4 */
    	/* Assume the KeySym is a legal value (ignore discontinuities) */
    	if (sym >= XKB_KEY_Rcedilla && sym <= XKB_KEY_Tslash)
    	    *lower += (XKB_KEY_rcedilla - XKB_KEY_Rcedilla);
    	else if (sym >= XKB_KEY_rcedilla && sym <= XKB_KEY_tslash)
    	    *upper -= (XKB_KEY_rcedilla - XKB_KEY_Rcedilla);
    	else if (sym == XKB_KEY_ENG)
    	    *lower = XKB_KEY_eng;
    	else if (sym == XKB_KEY_eng)
    	    *upper = XKB_KEY_ENG;
    	else if (sym >= XKB_KEY_Amacron && sym <= XKB_KEY_Umacron)
    	    *lower += (XKB_KEY_amacron - XKB_KEY_Amacron);
    	else if (sym >= XKB_KEY_amacron && sym <= XKB_KEY_umacron)
    	    *upper -= (XKB_KEY_amacron - XKB_KEY_Amacron);
    	break;
        case 6: /* Cyrillic */
    	/* Assume the KeySym is a legal value (ignore discontinuities) */
    	if (sym >= XKB_KEY_Serbian_DJE && sym <= XKB_KEY_Cyrillic_DZHE)
    	    *lower -= (XKB_KEY_Serbian_DJE - XKB_KEY_Serbian_dje);
    	else if (sym >= XKB_KEY_Serbian_dje && sym <= XKB_KEY_Cyrillic_dzhe)
    	    *upper += (XKB_KEY_Serbian_DJE - XKB_KEY_Serbian_dje);
    	else if (sym >= XKB_KEY_Cyrillic_YU && sym <= XKB_KEY_Cyrillic_HARDSIGN)
    	    *lower -= (XKB_KEY_Cyrillic_YU - XKB_KEY_Cyrillic_yu);
    	else if (sym >= XKB_KEY_Cyrillic_yu && sym <= XKB_KEY_Cyrillic_hardsign)
    	    *upper += (XKB_KEY_Cyrillic_YU - XKB_KEY_Cyrillic_yu);
            break;
        case 7: /* Greek */
    	/* Assume the KeySym is a legal value (ignore discontinuities) */
    	if (sym >= XKB_KEY_Greek_ALPHAaccent && sym <= XKB_KEY_Greek_OMEGAaccent)
    	    *lower += (XKB_KEY_Greek_alphaaccent - XKB_KEY_Greek_ALPHAaccent);
    	else if (sym >= XKB_KEY_Greek_alphaaccent && sym <= XKB_KEY_Greek_omegaaccent &&
    		 sym != XKB_KEY_Greek_iotaaccentdieresis &&
    		 sym != XKB_KEY_Greek_upsilonaccentdieresis)
    	    *upper -= (XKB_KEY_Greek_alphaaccent - XKB_KEY_Greek_ALPHAaccent);
    	else if (sym >= XKB_KEY_Greek_ALPHA && sym <= XKB_KEY_Greek_OMEGA)
    	    *lower += (XKB_KEY_Greek_alpha - XKB_KEY_Greek_ALPHA);
    	else if (sym >= XKB_KEY_Greek_alpha && sym <= XKB_KEY_Greek_omega &&
    		 sym != XKB_KEY_Greek_finalsmallsigma)
    	    *upper -= (XKB_KEY_Greek_alpha - XKB_KEY_Greek_ALPHA);
            break;
        case 0x13: /* Latin 9 */
            if (sym == XKB_KEY_OE)
                *lower = XKB_KEY_oe;
            else if (sym == XKB_KEY_oe)
                *upper = XKB_KEY_OE;
            else if (sym == XKB_KEY_Ydiaeresis)
                *lower = XKB_KEY_ydiaeresis;
            break;
        }
    }