/*
 * Keyboard related functions
 *
 * Copyright 2020 Alexandros Frantzis for Collabora Ltd.
 * Copyright 2023 Rémi Bernon for CodeWeavers
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#if 0
#pragma makedep unix
#endif

#include "config.h"
#include <stdlib.h>

#include <linux/input.h>
#undef SW_MAX /* Also defined in winuser.rh */
#include <sys/mman.h>
#include <unistd.h>

#include "waylanddrv.h"
#include "wine/debug.h"
#include "wine/server.h"

WINE_DEFAULT_DEBUG_CHANNEL(keyboard);
WINE_DECLARE_DEBUG_CHANNEL(key);

struct layout
{
    struct list entry;
    char *xkb_layout;
    LONG ref;

    int xkb_group;
    LANGID lang;
    WORD index;
    /* "Layout Id", used by NtUserGetKeyboardLayoutName / LoadKeyboardLayoutW */
    WORD layout_id;

    KBDTABLES tables;
    VSC_LPWSTR key_names[0x100];
    VSC_LPWSTR key_names_ext[0x200];
    WCHAR *key_names_str;

    USHORT vsc2vk[0x100];
    VSC_VK vsc2vk_e0[0x100];
    VSC_VK vsc2vk_e1[0x100];

    VK_TO_WCHAR_TABLE vk_to_wchar_table[2];
    VK_TO_WCHARS8 vk_to_wchars8[0x100 * 2 /* SGCAPS */];
    VK_TO_BIT vk2bit[4];
    union
    {
        MODIFIERS modifiers;
        char modifiers_buf[offsetof(MODIFIERS, ModNumber[8])];
    };
};

static pthread_mutex_t xkb_layouts_mutex = PTHREAD_MUTEX_INITIALIZER;
static struct list xkb_layouts = LIST_INIT(xkb_layouts);

/* These are only used from the wayland event thread and don't need locking */
static struct rxkb_context *rxkb_context;
static HKL keyboard_hkl; /* the HKL matching the currently active xkb group */

static void xkb_layout_addref(struct layout *layout)
{
    InterlockedIncrement(&layout->ref);
}

static void xkb_layout_release(struct layout *layout)
{
    if (!InterlockedDecrement(&layout->ref))
        free(layout);
}

#define EXTRA_SCAN2VK \
    T36 | KBDEXT, T37 | KBDMULTIVK, \
    T38, T39, T3A, T3B, T3C, T3D, T3E, T3F, \
    T40, T41, T42, T43, T44, T45 | KBDEXT | KBDMULTIVK, T46 | KBDMULTIVK, T47 | KBDNUMPAD | KBDSPECIAL, \
    T48 | KBDNUMPAD | KBDSPECIAL, T49 | KBDNUMPAD | KBDSPECIAL, T4A, T4B | KBDNUMPAD | KBDSPECIAL, \
    T4C | KBDNUMPAD | KBDSPECIAL, T4D | KBDNUMPAD | KBDSPECIAL, T4E, T4F | KBDNUMPAD | KBDSPECIAL, \
    T50 | KBDNUMPAD | KBDSPECIAL, T51 | KBDNUMPAD | KBDSPECIAL, T52 | KBDNUMPAD | KBDSPECIAL, \
    T53 | KBDNUMPAD | KBDSPECIAL, T54, T55, T56, T57, \
    T58, T59, T5A, T5B, T5C, T5D, T5E, T5F, \
    T60, T61, T62, T63, T64, T65, T66, T67, \
    T68, T69, T6A, T6B, T6C, T6D, T6E, T6F, \
    T70, T71, T72, T73, T74, T75, T76, T77, \
    T78, T79, T7A, T7B, T7C, T7D, T7E, \
    [0x110] = X10 | KBDEXT, [0x119] = X19 | KBDEXT, [0x11d] = X1D | KBDEXT, [0x120] = X20 | KBDEXT, \
    [0x121] = X21 | KBDEXT, [0x122] = X22 | KBDEXT, [0x124] = X24 | KBDEXT, [0x12e] = X2E | KBDEXT, \
    [0x130] = X30 | KBDEXT, [0x132] = X32 | KBDEXT, [0x135] = X35 | KBDEXT, [0x137] = X37 | KBDEXT, \
    [0x138] = X38 | KBDEXT, [0x147] = X47 | KBDEXT, [0x148] = X48 | KBDEXT, [0x149] = X49 | KBDEXT, \
    [0x14b] = X4B | KBDEXT, [0x14d] = X4D | KBDEXT, [0x14f] = X4F | KBDEXT, [0x150] = X50 | KBDEXT, \
    [0x151] = X51 | KBDEXT, [0x152] = X52 | KBDEXT, [0x153] = X53 | KBDEXT, [0x15b] = X5B | KBDEXT, \
    [0x15c] = X5C | KBDEXT, [0x15d] = X5D | KBDEXT, [0x15f] = X5F | KBDEXT, [0x165] = X65 | KBDEXT, \
    [0x166] = X66 | KBDEXT, [0x167] = X67 | KBDEXT, [0x168] = X68 | KBDEXT, [0x169] = X69 | KBDEXT, \
    [0x16a] = X6A | KBDEXT, [0x16b] = X6B | KBDEXT, [0x16c] = X6C | KBDEXT, [0x16d] = X6D | KBDEXT, \
    [0x11c] = X1C | KBDEXT, [0x146] = X46 | KBDEXT, [0x21d] = Y1D,

static const USHORT scan2vk_qwerty[0x280] =
{
    T00, T01, T02, T03, T04, T05, T06, T07, T08, T09, T0A, T0B, T0C, T0D, T0E,
    T0F, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T1A, T1B, T1C,
    T1D, T1E, T1F, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29,
    T2A, T2B, T2C, T2D, T2E, T2F, T30, T31, T32, T33, T34, T35,
    EXTRA_SCAN2VK
};

static const USHORT scan2vk_azerty[0x280] =
{
    T00, T01, T02, T03, T04, T05, T06, T07, T08, T09, T0A, T0B, VK_OEM_4, T0D, T0E,
    T0F, 'A', 'Z', T12, T13, T14, T15, T16, T17, T18, T19, VK_OEM_6, VK_OEM_1, T1C,
    T1D, 'Q', T1F, T20, T21, T22, T23, T24, T25, T26, 'M', VK_OEM_3, VK_OEM_7,
    T2A, T2B, 'W', T2D, T2E, T2F, T30, T31, VK_OEM_COMMA, VK_OEM_PERIOD, VK_OEM_2, VK_OEM_8,
    EXTRA_SCAN2VK
};

static const USHORT scan2vk_qwertz[0x280] =
{
    T00, T01, T02, T03, T04, T05, T06, T07, T08, T09, T0A, T0B, VK_OEM_4, VK_OEM_6, T0E,
    T0F, T10, T11, T12, T13, T14, 'Z', T16, T17, T18, T19, VK_OEM_1, VK_OEM_3, T1C,
    T1D, T1E, T1F, T20, T21, T22, T23, T24, T25, T26, VK_OEM_7, VK_OEM_5, VK_OEM_2,
    T2A, VK_OEM_8, 'Y', T2D, T2E, T2F, T30, T31, T32, T33, T34, VK_OEM_MINUS,
    EXTRA_SCAN2VK
};

static const USHORT scan2vk_dvorak[0x280] =
{
    T00, T01, T02, T03, T04, T05, T06, T07, T08, T09, T0A, T0B, VK_OEM_4, VK_OEM_6, T0E,
    T0F, VK_OEM_7, VK_OEM_COMMA, VK_OEM_PERIOD, 'P', 'Y', 'F', 'G', 'C', 'R', 'L', VK_OEM_2, VK_OEM_PLUS, T1C,
    T1D, T1E, 'O', 'E', 'U', 'I', 'D', 'H', 'T', 'N', 'S', VK_OEM_MINUS, T29,
    T2A, T2B, VK_OEM_1, 'Q', 'J', 'K', 'X', 'B', 'M', 'W', 'V', 'Z',
    EXTRA_SCAN2VK
};

static WORD key2scan(UINT key)
{
    /* base keys can be mapped directly */
    if (key <= KEY_KPDOT) return key;

    /* map keys found in KBDTABLES definitions (Txx Xxx Yxx macros) */
    switch (key)
    {
    case 84 /* ISO_Level3_Shift */: return 0x005a; /* T5A / VK_OEM_WSCTRL */
    case KEY_SYSRQ: return 0x0054; /* T54 / VK_SNAPSHOT */
    case KEY_102ND: return 0x0056; /* T56 / VK_OEM_102 */
    case KEY_F11: return 0x0057; /* T57 / VK_F11 */
    case KEY_F12: return 0x0058; /* T58 / VK_F12 */
    case KEY_LINEFEED: return 0x0059; /* T59 / VK_CLEAR */
    case KEY_EXIT: return 0x005b; /* T5B / VK_OEM_FINISH */
    case KEY_OPEN: return 0x005c; /* T5C / VK_OEM_JUMP */
    /* FIXME: map a KEY to T5D / VK_EREOF */
    /* FIXME: map a KEY to T5E / VK_OEM_BACKTAB */
    case KEY_COMPOSE: return 0x005f; /* T5F / VK_OEM_AUTO */
    case KEY_SCALE: return 0x0062; /* T62 / VK_ZOOM */
    case KEY_HELP: return 0x0063; /* T63 / VK_HELP */
    case KEY_F13: return 0x0064; /* T64 / VK_F13 */
    case KEY_F14: return 0x0065; /* T65 / VK_F14 */
    case KEY_F15: return 0x0066; /* T66 / VK_F15 */
    case KEY_F16: return 0x0067; /* T67 / VK_F16 */
    case KEY_F17: return 0x0068; /* T68 / VK_F17 */
    case KEY_F18: return 0x0069; /* T69 / VK_F18 */
    case KEY_F19: return 0x006a; /* T6A / VK_F19 */
    case KEY_F20: return 0x006b; /* T6B / VK_F20 */
    case KEY_F21: return 0x006c; /* T6C / VK_F21 */
    case KEY_F22: return 0x006d; /* T6D / VK_F22 */
    case KEY_F23: return 0x006e; /* T6E / VK_F23 */
    /* FIXME: map a KEY to T6F / VK_OEM_PA3 */
    case KEY_COMPUTER: return 0x0071; /* T71 / VK_OEM_RESET */
    /* FIXME: map a KEY to T73 / VK_ABNT_C1 */
    case KEY_F24: return 0x0076; /* T76 / VK_F24 */
    case KEY_KPPLUSMINUS: return 0x007b; /* T7B / VK_OEM_PA1 */
    /* FIXME: map a KEY to T7C / VK_TAB */
    /* FIXME: map a KEY to T7E / VK_ABNT_C2 */
    /* FIXME: map a KEY to T7F / VK_OEM_PA2 */
    case KEY_PREVIOUSSONG: return 0x0110; /* X10 / VK_MEDIA_PREV_TRACK */
    case KEY_NEXTSONG: return 0x0119; /* X19 / VK_MEDIA_NEXT_TRACK */
    case KEY_KPENTER: return 0x011c; /* X1C / VK_RETURN */
    case KEY_RIGHTCTRL: return 0x011d; /* X1D / VK_RCONTROL */
    case KEY_MUTE: return 0x0120; /* X20 / VK_VOLUME_MUTE */
    case KEY_PROG2: return 0x0121; /* X21 / VK_LAUNCH_APP2 */
    case KEY_PLAYPAUSE: return 0x0122; /* X22 / VK_MEDIA_PLAY_PAUSE */
    case KEY_STOPCD: return 0x0124; /* X24 / VK_MEDIA_STOP */
    case KEY_VOLUMEDOWN: return 0x012e; /* X2E / VK_VOLUME_DOWN */
    case KEY_VOLUMEUP: return 0x0130; /* X30 / VK_VOLUME_UP */
    case KEY_HOMEPAGE: return 0x0132; /* X32 / VK_BROWSER_HOME */
    case KEY_KPSLASH: return 0x0135; /* X35 / VK_DIVIDE */
    case KEY_PRINT: return 0x0137; /* X37 / VK_SNAPSHOT */
    case KEY_RIGHTALT: return 0x0138; /* X38 / VK_RMENU */
    case KEY_CANCEL: return 0x0146; /* X46 / VK_CANCEL */
    case KEY_HOME: return 0x0147; /* X47 / VK_HOME */
    case KEY_UP: return 0x0148; /* X48 / VK_UP */
    case KEY_PAGEUP: return 0x0149; /* X49 / VK_PRIOR */
    case KEY_LEFT: return 0x014b; /* X4B / VK_LEFT */
    case KEY_RIGHT: return 0x014d; /* X4D / VK_RIGHT */
    case KEY_END: return 0x014f; /* X4F / VK_END */
    case KEY_DOWN: return 0x0150; /* X50 / VK_DOWN */
    case KEY_PAGEDOWN: return 0x0151; /* X51 / VK_NEXT */
    case KEY_INSERT: return 0x0152; /* X52 / VK_INSERT */
    case KEY_DELETE: return 0x0153; /* X53 / VK_DELETE */
    case KEY_LEFTMETA: return 0x015b; /* X5B / VK_LWIN */
    case KEY_RIGHTMETA: return 0x015c; /* X5C / VK_RWIN */
    case KEY_MENU: return 0x015d; /* X5D / VK_APPS */
    case KEY_POWER: return 0x015e; /* X5E / VK_POWER */
    case KEY_SLEEP: return 0x015f; /* X5F / VK_SLEEP */
    case KEY_FIND: return 0x0165; /* X65 / VK_BROWSER_SEARCH */
    case KEY_BOOKMARKS: return 0x0166; /* X66 / VK_BROWSER_FAVORITES */
    case KEY_REFRESH: return 0x0167; /* X67 / VK_BROWSER_REFRESH */
    case KEY_STOP: return 0x0168; /* X68 / VK_BROWSER_STOP */
    case KEY_FORWARD: return 0x0169; /* X69 / VK_BROWSER_FORWARD */
    case KEY_BACK: return 0x016a; /* X6A / VK_BROWSER_BACK */
    case KEY_PROG1: return 0x016b; /* X6B / VK_LAUNCH_APP1 */
    case KEY_MAIL: return 0x016c; /* X6C / VK_LAUNCH_MAIL */
    case KEY_MEDIA: return 0x016d; /* X6D / VK_LAUNCH_MEDIA_SELECT */
    case KEY_PAUSE: return 0x021d; /* Y1D / VK_PAUSE */
    }

    /* otherwise just make up some extended scancode */
    return 0x200 | (key & 0x7f);
}

static inline LANGID langid_from_xkb_layout(const char *layout, size_t layout_len)
{
#define MAKEINDEX(c0, c1) (MAKEWORD(c0, c1) - MAKEWORD('a', 'a'))
    static const LANGID langids[] =
    {
        [MAKEINDEX('a','f')] = MAKELANGID(LANG_DARI, SUBLANG_DEFAULT),
        [MAKEINDEX('a','l')] = MAKELANGID(LANG_ALBANIAN, SUBLANG_DEFAULT),
        [MAKEINDEX('a','m')] = MAKELANGID(LANG_ARMENIAN, SUBLANG_DEFAULT),
        [MAKEINDEX('a','t')] = MAKELANGID(LANG_GERMAN, SUBLANG_GERMAN_AUSTRIAN),
        [MAKEINDEX('a','z')] = MAKELANGID(LANG_AZERBAIJANI, SUBLANG_DEFAULT),
        [MAKEINDEX('a','u')] = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_AUS),
        [MAKEINDEX('b','a')] = MAKELANGID(LANG_BOSNIAN, SUBLANG_BOSNIAN_BOSNIA_HERZEGOVINA_CYRILLIC),
        [MAKEINDEX('b','d')] = MAKELANGID(LANG_BANGLA, SUBLANG_DEFAULT),
        [MAKEINDEX('b','e')] = MAKELANGID(LANG_FRENCH, SUBLANG_FRENCH_BELGIAN),
        [MAKEINDEX('b','g')] = MAKELANGID(LANG_BULGARIAN, SUBLANG_DEFAULT),
        [MAKEINDEX('b','r')] = MAKELANGID(LANG_PORTUGUESE, 2),
        [MAKEINDEX('b','t')] = MAKELANGID(LANG_TIBETAN, 3),
        [MAKEINDEX('b','w')] = MAKELANGID(LANG_TSWANA, SUBLANG_TSWANA_BOTSWANA),
        [MAKEINDEX('b','y')] = MAKELANGID(LANG_BELARUSIAN, SUBLANG_DEFAULT),
        [MAKEINDEX('c','a')] = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_CAN),
        [MAKEINDEX('c','d')] = MAKELANGID(LANG_FRENCH, SUBLANG_CUSTOM_UNSPECIFIED),
        [MAKEINDEX('c','h')] = MAKELANGID(LANG_GERMAN, SUBLANG_GERMAN_SWISS),
        [MAKEINDEX('c','m')] = MAKELANGID(LANG_FRENCH, 11),
        [MAKEINDEX('c','n')] = MAKELANGID(LANG_CHINESE, SUBLANG_DEFAULT),
        [MAKEINDEX('c','z')] = MAKELANGID(LANG_CZECH, SUBLANG_DEFAULT),
        [MAKEINDEX('d','e')] = MAKELANGID(LANG_GERMAN, SUBLANG_DEFAULT),
        [MAKEINDEX('d','k')] = MAKELANGID(LANG_DANISH, SUBLANG_DEFAULT),
        [MAKEINDEX('d','z')] = MAKELANGID(LANG_TAMAZIGHT, SUBLANG_TAMAZIGHT_ALGERIA_LATIN),
        [MAKEINDEX('e','e')] = MAKELANGID(LANG_ESTONIAN, SUBLANG_DEFAULT),
        [MAKEINDEX('e','s')] = MAKELANGID(LANG_SPANISH, SUBLANG_DEFAULT),
        [MAKEINDEX('e','t')] = MAKELANGID(LANG_AMHARIC, SUBLANG_DEFAULT),
        [MAKEINDEX('f','i')] = MAKELANGID(LANG_FINNISH, SUBLANG_DEFAULT),
        [MAKEINDEX('f','o')] = MAKELANGID(LANG_FAEROESE, SUBLANG_DEFAULT),
        [MAKEINDEX('f','r')] = MAKELANGID(LANG_FRENCH, SUBLANG_DEFAULT),
        [MAKEINDEX('g','b')] = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_UK),
        [MAKEINDEX('g','e')] = MAKELANGID(LANG_GEORGIAN, SUBLANG_DEFAULT),
        [MAKEINDEX('g','h')] = MAKELANGID(LANG_ENGLISH, SUBLANG_CUSTOM_UNSPECIFIED),
        [MAKEINDEX('g','n')] = MAKELANGID(LANG_NEUTRAL, SUBLANG_CUSTOM_DEFAULT),
        [MAKEINDEX('g','r')] = MAKELANGID(LANG_GREEK, SUBLANG_DEFAULT),
        [MAKEINDEX('h','r')] = MAKELANGID(LANG_CROATIAN, SUBLANG_DEFAULT),
        [MAKEINDEX('h','u')] = MAKELANGID(LANG_HUNGARIAN, SUBLANG_DEFAULT),
        [MAKEINDEX('i','d')] = MAKELANGID(LANG_INDONESIAN, SUBLANG_DEFAULT),
        [MAKEINDEX('i','e')] = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_EIRE),
        [MAKEINDEX('i','l')] = MAKELANGID(LANG_HEBREW, SUBLANG_DEFAULT),
        [MAKEINDEX('i','n')] = MAKELANGID(LANG_HINDI, SUBLANG_DEFAULT),
        [MAKEINDEX('i','q')] = MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_IRAQ),
        [MAKEINDEX('i','r')] = MAKELANGID(LANG_PERSIAN, SUBLANG_DEFAULT),
        [MAKEINDEX('i','s')] = MAKELANGID(LANG_ICELANDIC, SUBLANG_DEFAULT),
        [MAKEINDEX('i','t')] = MAKELANGID(LANG_ITALIAN, SUBLANG_DEFAULT),
        [MAKEINDEX('j','p')] = MAKELANGID(LANG_JAPANESE, SUBLANG_DEFAULT),
        [MAKEINDEX('k','e')] = MAKELANGID(LANG_NEUTRAL, SUBLANG_CUSTOM_DEFAULT),
        [MAKEINDEX('k','g')] = MAKELANGID(LANG_KYRGYZ, SUBLANG_DEFAULT),
        [MAKEINDEX('k','h')] = MAKELANGID(LANG_KHMER, SUBLANG_DEFAULT),
        [MAKEINDEX('k','r')] = MAKELANGID(LANG_KOREAN, SUBLANG_DEFAULT),
        [MAKEINDEX('k','z')] = MAKELANGID(LANG_KAZAK, SUBLANG_DEFAULT),
        [MAKEINDEX('l','a')] = MAKELANGID(LANG_LAO, SUBLANG_DEFAULT),
        [MAKEINDEX('l','k')] = MAKELANGID(LANG_SINHALESE, SUBLANG_DEFAULT),
        [MAKEINDEX('l','t')] = MAKELANGID(LANG_LITHUANIAN, SUBLANG_DEFAULT),
        [MAKEINDEX('l','v')] = MAKELANGID(LANG_LATVIAN, SUBLANG_DEFAULT),
        [MAKEINDEX('m','a')] = MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_MOROCCO),
        [MAKEINDEX('m','d')] = MAKELANGID(LANG_ROMANIAN, SUBLANG_CUSTOM_UNSPECIFIED),
        [MAKEINDEX('m','e')] = MAKELANGID(LANG_SERBIAN, SUBLANG_SERBIAN_MONTENEGRO_LATIN),
        [MAKEINDEX('m','k')] = MAKELANGID(LANG_MACEDONIAN, SUBLANG_DEFAULT),
        [MAKEINDEX('m','l')] = MAKELANGID(LANG_NEUTRAL, SUBLANG_CUSTOM_DEFAULT),
        [MAKEINDEX('m','m')] = MAKELANGID(0x55 /*LANG_BURMESE*/, SUBLANG_DEFAULT),
        [MAKEINDEX('m','n')] = MAKELANGID(LANG_MONGOLIAN, SUBLANG_DEFAULT),
        [MAKEINDEX('m','t')] = MAKELANGID(LANG_MALTESE, SUBLANG_DEFAULT),
        [MAKEINDEX('m','v')] = MAKELANGID(LANG_DIVEHI, SUBLANG_DEFAULT),
        [MAKEINDEX('m','y')] = MAKELANGID(LANG_MALAY, SUBLANG_DEFAULT),
        [MAKEINDEX('n','g')] = MAKELANGID(LANG_ENGLISH, SUBLANG_CUSTOM_UNSPECIFIED),
        [MAKEINDEX('n','l')] = MAKELANGID(LANG_DUTCH, SUBLANG_DEFAULT),
        [MAKEINDEX('n','o')] = MAKELANGID(LANG_NORWEGIAN, SUBLANG_DEFAULT),
        [MAKEINDEX('n','p')] = MAKELANGID(LANG_NEPALI, SUBLANG_DEFAULT),
        [MAKEINDEX('p','h')] = MAKELANGID(LANG_FILIPINO, SUBLANG_DEFAULT),
        [MAKEINDEX('p','k')] = MAKELANGID(LANG_URDU, SUBLANG_DEFAULT),
        [MAKEINDEX('p','l')] = MAKELANGID(LANG_POLISH, SUBLANG_DEFAULT),
        [MAKEINDEX('p','t')] = MAKELANGID(LANG_PORTUGUESE, SUBLANG_DEFAULT),
        [MAKEINDEX('r','o')] = MAKELANGID(LANG_ROMANIAN, SUBLANG_DEFAULT),
        [MAKEINDEX('r','s')] = MAKELANGID(LANG_SERBIAN, SUBLANG_SERBIAN_LATIN),
        [MAKEINDEX('r','u')] = MAKELANGID(LANG_RUSSIAN, SUBLANG_DEFAULT),
        [MAKEINDEX('s','e')] = MAKELANGID(LANG_SWEDISH, SUBLANG_DEFAULT),
        [MAKEINDEX('s','i')] = MAKELANGID(LANG_SLOVENIAN, SUBLANG_DEFAULT),
        [MAKEINDEX('s','k')] = MAKELANGID(LANG_SLOVAK, SUBLANG_DEFAULT),
        [MAKEINDEX('s','n')] = MAKELANGID(LANG_WOLOF, SUBLANG_DEFAULT),
        [MAKEINDEX('s','y')] = MAKELANGID(LANG_SYRIAC, SUBLANG_DEFAULT),
        [MAKEINDEX('t','g')] = MAKELANGID(LANG_FRENCH, SUBLANG_CUSTOM_UNSPECIFIED),
        [MAKEINDEX('t','h')] = MAKELANGID(LANG_THAI, SUBLANG_DEFAULT),
        [MAKEINDEX('t','j')] = MAKELANGID(LANG_TAJIK, SUBLANG_DEFAULT),
        [MAKEINDEX('t','m')] = MAKELANGID(LANG_TURKMEN, SUBLANG_DEFAULT),
        [MAKEINDEX('t','r')] = MAKELANGID(LANG_TURKISH, SUBLANG_DEFAULT),
        [MAKEINDEX('t','w')] = MAKELANGID(LANG_CHINESE, SUBLANG_CUSTOM_UNSPECIFIED),
        [MAKEINDEX('t','z')] = MAKELANGID(LANG_SWAHILI, SUBLANG_CUSTOM_UNSPECIFIED),
        [MAKEINDEX('u','a')] = MAKELANGID(LANG_UKRAINIAN, SUBLANG_DEFAULT),
        [MAKEINDEX('u','s')] = MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT),
        [MAKEINDEX('u','z')] = MAKELANGID(LANG_UZBEK, 2),
        [MAKEINDEX('v','n')] = MAKELANGID(LANG_VIETNAMESE, SUBLANG_DEFAULT),
        [MAKEINDEX('z','a')] = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_SOUTH_AFRICA),
    };
    LANGID langid;

    if (layout_len == 2 && (langid = langids[MAKEINDEX(layout[0], layout[1])])) return langid;
    if (layout_len == 3 && !memcmp(layout, "ara", layout_len)) return MAKELANGID(LANG_ARABIC, SUBLANG_DEFAULT);
    if (layout_len == 3 && !memcmp(layout, "epo", layout_len)) return MAKELANGID(LANG_NEUTRAL, SUBLANG_CUSTOM_DEFAULT);
    if (layout_len == 3 && !memcmp(layout, "mao", layout_len)) return MAKELANGID(LANG_MAORI, SUBLANG_DEFAULT);
    if (layout_len == 4 && !memcmp(layout, "brai", layout_len)) return MAKELANGID(LANG_NEUTRAL, SUBLANG_CUSTOM_DEFAULT);
    if (layout_len == 5 && !memcmp(layout, "latam", layout_len)) return MAKELANGID(LANG_SPANISH, SUBLANG_CUSTOM_UNSPECIFIED);
#undef MAKEINDEX

    FIXME("Unknown layout language %s\n", debugstr_a(layout));
    return MAKELANGID(LANG_NEUTRAL, SUBLANG_CUSTOM_UNSPECIFIED);
};

static HKL get_layout_hkl(struct layout *layout, LCID locale)
{
    if (!layout->layout_id) return (HKL)(UINT_PTR)MAKELONG(locale, layout->lang);
    else return (HKL)(UINT_PTR)MAKELONG(locale, 0xf000 | layout->layout_id);
}

static void add_xkb_layout(const char *xkb_layout, struct xkb_keymap *xkb_keymap,
                            xkb_layout_index_t xkb_group, LANGID lang)
{
    static WORD next_layout_id = 1;

    unsigned int mod, keyc, len, names_len, min_keycode, max_keycode;
    struct xkb_state *xkb_state = xkb_state_new(xkb_keymap);
    xkb_mod_mask_t shift_mask, control_mask, altgr_mask, capslock_mask, numlock_mask;
    VSC_LPWSTR *names_entry, *names_ext_entry;
    VSC_VK *vsc2vk_e0_entry, *vsc2vk_e1_entry;
    VK_TO_WCHARS8 *vk2wchars_entry;
    struct layout *layout;
    const USHORT *scan2vk;
    WCHAR *names_str;
    WORD index = 0;
    char *ptr;

    min_keycode = xkb_keymap_min_keycode(xkb_keymap);
    max_keycode = xkb_keymap_max_keycode(xkb_keymap);

    TRACE("xkb_layout=%s xkb_keymap=%p xkb_group=%u lang=%04x\n", xkb_layout, xkb_keymap, xkb_group, lang);

    LIST_FOR_EACH_ENTRY(layout, &xkb_layouts, struct layout, entry)
        if (layout->lang == lang) index++;

    for (names_len = 0, keyc = min_keycode; keyc <= max_keycode; keyc++)
    {
        const xkb_keysym_t *keysym;
        if (!xkb_keymap_key_get_syms_by_level(xkb_keymap, keyc, xkb_group, 0, &keysym)) continue;
        names_len += xkb_keysym_get_name(*keysym, NULL, 0) + 1;
    }

    names_len *= sizeof(WCHAR);
    len = strlen(xkb_layout) + 1;
    if (!(layout = calloc(1, sizeof(*layout) + names_len + len)))
    {
        ERR("Failed to allocate memory for Xkb layout entry\n");
        return;
    }
    layout->ref = 1;

    ptr = (char *)(layout + 1);
    layout->xkb_layout = strcpy(ptr, xkb_layout);
    ptr += len;

    layout->xkb_group = xkb_group;
    layout->lang = lang;
    layout->index = index;
    if (index) layout->layout_id = next_layout_id++;
    layout->key_names_str = names_str = (void *)ptr;

    switch (lang)
    {
    case MAKELANGID(LANG_FRENCH, SUBLANG_DEFAULT): scan2vk = scan2vk_azerty; break;
    case MAKELANGID(LANG_GERMAN, SUBLANG_DEFAULT): scan2vk = scan2vk_qwertz; break;
    case MAKELANGID(LANG_GERMAN, SUBLANG_GERMAN_SWISS): scan2vk = scan2vk_qwertz; break;
    default: scan2vk = scan2vk_qwerty; break;
    }
    if (strstr(xkb_layout, "dvorak")) scan2vk = scan2vk_dvorak;

    layout->tables.pKeyNames = layout->key_names;
    layout->tables.pKeyNamesExt = layout->key_names_ext;
    layout->tables.bMaxVSCtoVK = 0xff;
    layout->tables.pusVSCtoVK = layout->vsc2vk;
    layout->tables.pVSCtoVK_E0 = layout->vsc2vk_e0;
    layout->tables.pVSCtoVK_E1 = layout->vsc2vk_e1;
    layout->tables.pCharModifiers = &layout->modifiers;
    layout->tables.pVkToWcharTable = layout->vk_to_wchar_table;
    layout->tables.fLocaleFlags = MAKELONG(KLLF_ALTGR, KBD_VERSION);

    layout->vk_to_wchar_table[0].pVkToWchars = (VK_TO_WCHARS1 *)layout->vk_to_wchars8;
    layout->vk_to_wchar_table[0].cbSize = sizeof(*layout->vk_to_wchars8);
    layout->vk_to_wchar_table[0].nModifications = 8;

    layout->vk2bit[0].Vk = VK_SHIFT;
    layout->vk2bit[0].ModBits = KBDSHIFT;
    layout->vk2bit[1].Vk = VK_CONTROL;
    layout->vk2bit[1].ModBits = KBDCTRL;
    layout->vk2bit[2].Vk = VK_MENU;
    layout->vk2bit[2].ModBits = KBDALT;

    layout->modifiers.pVkToBit = layout->vk2bit;
    for (mod = 0; mod <= (KBDSHIFT | KBDCTRL | KBDALT); ++mod)
    {
        BYTE num = 0;
        if (mod & KBDSHIFT) num |= 1 << 0;
        if (mod & KBDCTRL)  num |= 1 << 1;
        if (mod & KBDALT)   num |= 1 << 2;
        layout->modifiers.ModNumber[mod] = num;
    }
    layout->modifiers.wMaxModBits = 7;

    names_entry = layout->tables.pKeyNames;
    names_ext_entry = layout->tables.pKeyNamesExt;
    vsc2vk_e0_entry = layout->tables.pVSCtoVK_E0;
    vsc2vk_e1_entry = layout->tables.pVSCtoVK_E1;
    vk2wchars_entry = layout->vk_to_wchars8;

    for (keyc = min_keycode; keyc <= max_keycode; keyc++)
    {
        WORD scan = key2scan(keyc - 8);
        const xkb_keysym_t *keysym;
        VSC_LPWSTR *entry;
        char name[256];

        if (!xkb_keymap_key_get_syms_by_level(xkb_keymap, keyc, xkb_group, 0, &keysym)) continue;
        len = xkb_keysym_get_name(*keysym, name, sizeof(name));

        if (!(scan & 0xff) || !len) continue;
        if (!(scan & 0x300)) entry = names_entry++;
        else entry = names_ext_entry++;

        entry->vsc = (BYTE)scan;
        entry->pwsz = names_str;
        names_str += ntdll_umbstowcs(name, len + 1, entry->pwsz, len + 1);

        TRACE("keyc %#04x, scan %#04x -> name %s\n", keyc, scan, debugstr_w(entry->pwsz));
    }

    for (keyc = min_keycode; keyc <= max_keycode; keyc++)
    {
        WORD scan = key2scan(keyc - 8), vkey = scan2vk[scan];
        VSC_VK *entry = NULL;

        if (!(scan & 0xff) || !vkey) continue;
        if (scan & 0x100) entry = vsc2vk_e0_entry++;
        else if (scan & 0x200) entry = vsc2vk_e1_entry++;
        else layout->tables.pusVSCtoVK[scan & 0xff] = vkey;

        if (entry)
        {
            entry->Vsc = scan & 0xff;
            entry->Vk = vkey;
        }

        TRACE("keyc %#04x, scan %#05x -> vkey %#06x\n", keyc, scan, vkey);
    }

    shift_mask = 1 << xkb_keymap_mod_get_index(xkb_keymap, XKB_MOD_NAME_SHIFT);
    control_mask = 1 << xkb_keymap_mod_get_index(xkb_keymap, XKB_MOD_NAME_CTRL);
    capslock_mask = 1 << xkb_keymap_mod_get_index(xkb_keymap, XKB_MOD_NAME_CAPS);
    altgr_mask = 1 << xkb_keymap_mod_get_index(xkb_keymap, "Mod5");
    numlock_mask = 1 << xkb_keymap_mod_get_index(xkb_keymap, XKB_MOD_NAME_NUM);

    for (keyc = min_keycode; keyc <= max_keycode; keyc++)
    {
        WORD scan = key2scan(keyc - 8), vkey = scan2vk[scan];
        VK_TO_WCHARS8 vkey2wch = {.VirtualKey = vkey}, caps_vkey2wch = vkey2wch;
        BOOL found = FALSE, caps_found = FALSE;
        uint32_t caps_ret, shift_ret;
        unsigned int mod;

        if ((vkey & KBDNUMPAD) && (vkey & 0xff) == VK_DELETE)
        {
            VK_TO_WCHARS8 num_vkey2wch = {.VirtualKey = VK_DECIMAL};

            xkb_state_update_mask(xkb_state, 0, 0, numlock_mask, 0, 0, xkb_group);
            if (!(num_vkey2wch.wch[0] = xkb_state_key_get_utf32(xkb_state, keyc)))
                num_vkey2wch.wch[0] = WCH_NONE;
            for (mod = 1; mod < 8; ++mod) num_vkey2wch.wch[mod] = WCH_NONE;
            num_vkey2wch.Attributes = 0;
            TRACE("vkey %#06x -> %s\n", num_vkey2wch.VirtualKey, debugstr_wn(num_vkey2wch.wch, 8));
            *vk2wchars_entry++ = num_vkey2wch;
        }

        for (mod = 0; mod < 8; ++mod)
        {
            xkb_mod_mask_t mod_mask = 0;
            uint32_t ret;

            if (mod & (1 << 0)) mod_mask |= shift_mask;
            if (mod & (1 << 1)) mod_mask |= control_mask;
            /* Windows uses VK_CTRL + VK_MENU for AltGr, we cannot combine Ctrl and Alt */
            if (mod & (1 << 2)) mod_mask = (mod_mask & ~control_mask) | altgr_mask;

            xkb_state_update_mask(xkb_state, 0, 0, mod_mask, 0, 0, xkb_group);

            if (mod_mask & control_mask) vkey2wch.wch[mod] = WCH_NONE; /* on Windows CTRL+key behave specifically */
            else if (!(ret = xkb_state_key_get_utf32(xkb_state, keyc))) vkey2wch.wch[mod] = WCH_NONE;
            else vkey2wch.wch[mod] = ret;

            if (vkey2wch.wch[mod] != WCH_NONE) found = TRUE;

            xkb_state_update_mask(xkb_state, 0, 0, mod_mask | capslock_mask, 0, 0, xkb_group);

            if (mod_mask & control_mask) caps_vkey2wch.wch[mod] = WCH_NONE; /* on Windows CTRL+key behave specifically */
            else if (!(ret = xkb_state_key_get_utf32(xkb_state, keyc))) caps_vkey2wch.wch[mod] = WCH_NONE;
            else if (ret == vkey2wch.wch[mod]) caps_vkey2wch.wch[mod] = WCH_NONE;
            else caps_vkey2wch.wch[mod] = ret;

            if (caps_vkey2wch.wch[mod] != WCH_NONE) caps_found = TRUE;
        }

        if (!found) continue;

        if (caps_found)
        {
            TRACE("vkey %#06x + CAPS -> %s\n", caps_vkey2wch.VirtualKey, debugstr_wn(caps_vkey2wch.wch, 8));
            caps_vkey2wch.Attributes = SGCAPS;
            *vk2wchars_entry++ = caps_vkey2wch;
        }
        else
        {
            xkb_state_update_mask(xkb_state, 0, 0, capslock_mask, 0, 0, xkb_group);
            caps_ret = xkb_state_key_get_utf32(xkb_state, keyc);
            xkb_state_update_mask(xkb_state, 0, 0, shift_mask, 0, 0, xkb_group);
            shift_ret = xkb_state_key_get_utf32(xkb_state, keyc);
            if (caps_ret && caps_ret == shift_ret) vkey2wch.Attributes |= CAPLOK;
        }

        TRACE("vkey %#06x -> %s\n", vkey2wch.VirtualKey, debugstr_wn(vkey2wch.wch, 8));
        *vk2wchars_entry++ = vkey2wch;
    }

    xkb_state_unref(xkb_state);

    TRACE("Created layout entry=%p index=%04x lang=%04x id=%04x\n", layout, layout->index, layout->lang, layout->layout_id);
    list_add_tail(&xkb_layouts, &layout->entry);
}

static void set_current_xkb_group(xkb_layout_index_t xkb_group)
{
    struct wayland_keyboard *keyboard = &process_wayland.keyboard;
    LCID locale = LOWORD(NtUserGetKeyboardLayout(0));
    struct layout *layout;
    HKL hkl;

    pthread_mutex_lock(&xkb_layouts_mutex);

    LIST_FOR_EACH_ENTRY(layout, &xkb_layouts, struct layout, entry)
        if (layout->xkb_group == xkb_group) break;
    if (&layout->entry != &xkb_layouts)
        hkl = get_layout_hkl(layout, locale);
    else
    {
        ERR("Failed to find Xkb Layout for group %d\n", xkb_group);
        hkl = keyboard_hkl;
    }

    pthread_mutex_unlock(&xkb_layouts_mutex);

    if (hkl == keyboard_hkl) return;
    keyboard_hkl = hkl;

    TRACE("Changing keyboard layout to %p\n", hkl);
    NtUserPostMessage(keyboard->focused_hwnd, WM_INPUTLANGCHANGEREQUEST, 0 /*FIXME*/,
                      (LPARAM)keyboard_hkl);
}

static BOOL find_xkb_layout_variant(const char *name, const char **layout, const char **variant)
{
    struct rxkb_layout *iter;

    for (iter = rxkb_layout_first(rxkb_context); iter; iter = rxkb_layout_next(iter))
    {
        if (!strcmp(name, rxkb_layout_get_description(iter)))
        {
            *layout = rxkb_layout_get_name(iter);
            *variant = rxkb_layout_get_variant(iter);
            return TRUE;
        }
    }

    return FALSE;
}

static void release_all_keys(HWND hwnd)
{
    BYTE state[256];
    int vkey;
    INPUT input = {.type = INPUT_KEYBOARD};

    NtUserGetAsyncKeyboardState(state);

    for (vkey = 1; vkey < 256; vkey++)
    {
        /* Skip mouse buttons. */
        if (vkey < 7 && vkey != VK_CANCEL) continue;
        /* Skip left/right-agnostic modifier vkeys. */
        if (vkey == VK_SHIFT || vkey == VK_CONTROL || vkey == VK_MENU) continue;

        if (state[vkey] & 0x80)
        {
            UINT scan = NtUserMapVirtualKeyEx(vkey, MAPVK_VK_TO_VSC_EX,
                                              keyboard_hkl);
            input.ki.wVk = vkey;
            input.ki.wScan = scan & 0xff;
            input.ki.dwFlags = KEYEVENTF_KEYUP;
            if (scan & ~0xff) input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
            NtUserSendHardwareInput(hwnd, 0, &input, 0);
        }
    }
}

/**********************************************************************
 *          Keyboard handling
 */

static HWND wayland_keyboard_get_focused_hwnd(void)
{
    struct wayland_keyboard *keyboard = &process_wayland.keyboard;
    HWND hwnd;

    pthread_mutex_lock(&keyboard->mutex);
    hwnd = keyboard->focused_hwnd;
    pthread_mutex_unlock(&keyboard->mutex);

    return hwnd;
}

static void keyboard_handle_keymap(void *data, struct wl_keyboard *wl_keyboard,
                                   uint32_t format, int fd, uint32_t size)
{
    struct wayland_keyboard *keyboard = &process_wayland.keyboard;
    struct xkb_keymap *xkb_keymap = NULL;
    xkb_layout_index_t xkb_group;
    struct xkb_state *xkb_state;
    struct layout *entry, *next;
    char *keymap_str;

    TRACE("format=%d fd=%d size=%d\n", format, fd, size);

    if ((keymap_str = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0)) != MAP_FAILED)
    {
        if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1)
            FIXME("Unsupported keymap format %#x\n", format);
        else
        {
            xkb_keymap = xkb_keymap_new_from_string(keyboard->xkb_context, keymap_str,
                                                    XKB_KEYMAP_FORMAT_TEXT_V1, 0);
        }

        munmap(keymap_str, size);
    }

    close(fd);

    if (!xkb_keymap)
    {
        ERR("Failed to load Xkb keymap\n");
        return;
    }

    pthread_mutex_lock(&xkb_layouts_mutex);

    LIST_FOR_EACH_ENTRY_SAFE(entry, next, &xkb_layouts, struct layout, entry)
    {
        list_remove(&entry->entry);
        xkb_layout_release(entry);
    }

    for (xkb_group = 0; xkb_group < xkb_keymap_num_layouts(xkb_keymap); xkb_group++)
    {
        const char *layout_name = xkb_keymap_layout_get_name(xkb_keymap, xkb_group);
        const char *layout, *variant = NULL;
        int layout_len, variant_len = 0;
        char buffer[1024];
        LANGID lang;

        if (!find_xkb_layout_variant(layout_name, &layout, &variant)) layout = "us";
        if (variant) variant_len = strlen(variant);
        layout_len = strlen(layout);

        TRACE("Found layout %u name %s -> %s:%s\n", xkb_group, layout_name, layout, variant);

        lang = langid_from_xkb_layout(layout, layout_len);
        snprintf(buffer, ARRAY_SIZE(buffer), "%.*s:%.*s", layout_len, layout, variant_len, variant);
        add_xkb_layout(buffer, xkb_keymap, xkb_group, lang);
    }

    pthread_mutex_unlock(&xkb_layouts_mutex);

    if ((xkb_state = xkb_state_new(xkb_keymap)))
    {
        pthread_mutex_lock(&keyboard->mutex);
        xkb_state_unref(keyboard->xkb_state);
        keyboard->xkb_state = xkb_state;
        pthread_mutex_unlock(&keyboard->mutex);

        set_current_xkb_group(0);
    }

    xkb_keymap_unref(xkb_keymap);
}

static void keyboard_handle_enter(void *private, struct wl_keyboard *wl_keyboard,
                                  uint32_t serial, struct wl_surface *wl_surface,
                                  struct wl_array *keys)
{
    struct wayland_keyboard *keyboard = &process_wayland.keyboard;
    struct wayland_surface *surface;
    struct wayland_win_data *data;
    HWND hwnd;

    if (!wl_surface) return;

    /* The wl_surface user data remains valid and immutable for the whole
     * lifetime of the object, so it's safe to access without locking. */
    hwnd = wl_surface_get_user_data(wl_surface);
    TRACE("serial=%u hwnd=%p\n", serial, hwnd);

    pthread_mutex_lock(&keyboard->mutex);
    keyboard->focused_hwnd = hwnd;
    pthread_mutex_unlock(&keyboard->mutex);

    NtUserPostMessage(keyboard->focused_hwnd, WM_INPUTLANGCHANGEREQUEST, 0 /*FIXME*/,
                      (LPARAM)keyboard_hkl);

    if (!(data = wayland_win_data_get(hwnd))) return;

    if ((surface = data->wayland_surface))
    {
        /* TODO: Drop the internal message and call NtUserSetForegroundWindow
         * directly once it's updated to not explicitly deactivate the old
         * foreground window when both the old and new foreground windows
         * are in the same non-current thread. */
        if (surface->window.managed)
            NtUserPostMessage(hwnd, WM_WAYLAND_SET_FOREGROUND, 0, 0);
    }

    wayland_win_data_release(data);
}

static void keyboard_handle_leave(void *data, struct wl_keyboard *wl_keyboard,
                                  uint32_t serial, struct wl_surface *wl_surface)
{
    struct wayland_keyboard *keyboard = &process_wayland.keyboard;
    HWND hwnd;

    if (!wl_surface) return;

    /* The wl_surface user data remains valid and immutable for the whole
     * lifetime of the object, so it's safe to access without locking. */
    hwnd = wl_surface_get_user_data(wl_surface);
    TRACE("serial=%u hwnd=%p\n", serial, hwnd);

    pthread_mutex_lock(&keyboard->mutex);
    if (keyboard->focused_hwnd == hwnd)
        keyboard->focused_hwnd = NULL;
    pthread_mutex_unlock(&keyboard->mutex);

    /* The spec for the leave event tells us to treat all keys as released,
     * and for any key repetition to stop. */
    release_all_keys(hwnd);

    /* FIXME: update foreground window as well */
}

static void send_right_control(HWND hwnd, uint32_t state)
{
    INPUT input = {0};
    input.type = INPUT_KEYBOARD;
    input.ki.wScan = 0xe000 | (key2scan(KEY_RIGHTCTRL) & 0xff);
    input.ki.dwFlags = KEYEVENTF_SCANCODE | KEYEVENTF_EXTENDEDKEY;
    if (state == WL_KEYBOARD_KEY_STATE_RELEASED) input.ki.dwFlags |= KEYEVENTF_KEYUP;
    NtUserSendHardwareInput(hwnd, 0, &input, 0);
}

static void keyboard_handle_key(void *data, struct wl_keyboard *wl_keyboard,
                                uint32_t serial, uint32_t time, uint32_t key,
                                uint32_t state)
{
    UINT scan = key2scan(key);
    INPUT input = {0};
    HWND hwnd;

    if (!(hwnd = wayland_keyboard_get_focused_hwnd())) return;

    TRACE_(key)("serial=%u hwnd=%p key=%d scan=%#x state=%#x\n", serial, hwnd, key, scan, state);

    /* NOTE: Windows normally sends VK_CONTROL + VK_MENU only if the layout has KLLF_ALTGR */
    if (key == KEY_RIGHTALT) send_right_control(hwnd, state);

    input.type = INPUT_KEYBOARD;
    input.ki.wScan = (scan & 0x300) ? scan + 0xdf00 : scan;
    input.ki.dwFlags = KEYEVENTF_SCANCODE;
    if (scan & ~0xff) input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;

    if (state == WL_KEYBOARD_KEY_STATE_RELEASED) input.ki.dwFlags |= KEYEVENTF_KEYUP;
    NtUserSendHardwareInput(hwnd, 0, &input, 0);
}

static void keyboard_handle_modifiers(void *data, struct wl_keyboard *wl_keyboard,
                                      uint32_t serial, uint32_t mods_depressed,
                                      uint32_t mods_latched, uint32_t mods_locked,
                                      uint32_t xkb_group)
{
    struct wayland_keyboard *keyboard = &process_wayland.keyboard;

    if (!wayland_keyboard_get_focused_hwnd()) return;

    TRACE("serial=%u mods_depressed=%#x mods_latched=%#x mods_locked=%#x xkb_group=%d stub!\n",
          serial, mods_depressed, mods_latched, mods_locked, xkb_group);

    pthread_mutex_lock(&keyboard->mutex);
    xkb_state_update_mask(keyboard->xkb_state, mods_depressed, mods_latched,
                          mods_locked, 0, 0, xkb_group);
    pthread_mutex_unlock(&keyboard->mutex);

    set_current_xkb_group(xkb_group);

    /* FIXME: Sync wine modifier state with XKB modifier state. */
}

static void keyboard_handle_repeat_info(void *data, struct wl_keyboard *wl_keyboard,
                                        int rate, int delay)
{
    UINT speed;

    TRACE("rate=%d delay=%d\n", rate, delay);

    /* Handle non-negative rate values, ignore invalid (negative) values.  A
     * rate of 0 disables repeat. */
    if (rate >= 80) speed = 31;
    else if (rate >= 5) speed = rate * 400 / 1000 - 1;
    else speed = 0;

    delay = max(0, min(3, round(delay / 250.0) - 1));
    NtUserSystemParametersInfo(SPI_SETKEYBOARDSPEED, speed, NULL, 0);
    NtUserSystemParametersInfo(SPI_SETKEYBOARDDELAY, delay, NULL, 0);
    NtUserCallOneParam(rate > 0, NtUserCallOneParam_SetKeyboardAutoRepeat);
}

static const struct wl_keyboard_listener keyboard_listener = {
    keyboard_handle_keymap,
    keyboard_handle_enter,
    keyboard_handle_leave,
    keyboard_handle_key,
    keyboard_handle_modifiers,
    keyboard_handle_repeat_info,
};

/***********************************************************************
 *           wayland_keyboard_init
 */
void wayland_keyboard_init(struct wl_keyboard *wl_keyboard)
{
    struct wayland_keyboard *keyboard = &process_wayland.keyboard;
    struct xkb_context *xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);

    if (!(rxkb_context = rxkb_context_new(RXKB_CONTEXT_NO_FLAGS))
            || !rxkb_context_parse_default_ruleset(rxkb_context))
    {
        ERR("Failed to parse default Xkb ruleset\n");
        return;
    }

    if (!xkb_context)
    {
        ERR("Failed to create XKB context\n");
        return;
    }

    NtUserCallOneParam(TRUE, NtUserCallOneParam_SetKeyboardAutoRepeat);
    pthread_mutex_lock(&keyboard->mutex);
    keyboard->wl_keyboard = wl_keyboard;
    keyboard->xkb_context = xkb_context;
    pthread_mutex_unlock(&keyboard->mutex);
    wl_keyboard_add_listener(keyboard->wl_keyboard, &keyboard_listener, NULL);
}

/***********************************************************************
 *           wayland_keyboard_deinit
 */
void wayland_keyboard_deinit(void)
{
    struct wayland_keyboard *keyboard = &process_wayland.keyboard;

    pthread_mutex_lock(&keyboard->mutex);
    if (keyboard->wl_keyboard)
    {
        wl_keyboard_destroy(keyboard->wl_keyboard);
        keyboard->wl_keyboard = NULL;
    }
    if (keyboard->xkb_context)
    {
        xkb_context_unref(keyboard->xkb_context);
        keyboard->xkb_context = NULL;
    }
    if (keyboard->xkb_state)
    {
        xkb_state_unref(keyboard->xkb_state);
        keyboard->xkb_state = NULL;
    }
    pthread_mutex_unlock(&keyboard->mutex);

    if (rxkb_context)
    {
        rxkb_context_unref(rxkb_context);
        rxkb_context = NULL;
    }
}

/***********************************************************************
 *    KbdLayerDescriptor (WAYLANDDRV.@)
 */
const KBDTABLES *WAYLAND_KbdLayerDescriptor(HKL hkl)
{
    struct layout *layout;

    TRACE("hkl=%p\n", hkl);

    pthread_mutex_lock(&xkb_layouts_mutex);

    LIST_FOR_EACH_ENTRY(layout, &xkb_layouts, struct layout, entry)
        if (hkl == get_layout_hkl(layout, LOWORD(hkl))) break;
    if (&layout->entry == &xkb_layouts) layout = NULL;
    else xkb_layout_addref(layout);

    pthread_mutex_unlock(&xkb_layouts_mutex);

    if (!layout)
    {
        WARN("Failed to find Xkb layout for HKL %p\n", hkl);
        return NULL;
    }

    TRACE("Found layout entry %p, hkl %04x%04x id %04x\n",
           layout, layout->index, layout->lang, layout->layout_id);
    return &layout->tables;
}

/***********************************************************************
 *    ReleaseKbdTables (WAYLANDDRV.@)
 */
void WAYLAND_ReleaseKbdTables(const KBDTABLES *tables)
{
    struct layout *layout = CONTAINING_RECORD(tables, struct layout, tables);
    xkb_layout_release(layout);
}
