Detecting the host layout languages from their Xkb identifiers, and introducing a new driver interface using the KBDTABLES structure to translate them to Win32.
-- v4: winewayland.drv: Implement CAPLOK and SGCAPS in KBDTABLES. win32u: Support SGCAPS attributes in KBDTABLES. winewayland.drv: Translate Xkb keyboard layouts to KBDTABLES. win32u: Allow KBDTABLES conversion from CTRL + ALT to WCHAR. win32u: Force US layout in ToUnicode when CTRL is pressed. win32u: Avoid accessing NULL key name string pointer. win32u: Introduce KbdLayerDescriptor user driver entry. winewayland.drv: Enumerate Xkb layouts and create matching HKL. winewayland.drv: Handle and parse Xkb keymap events.
From: Rémi Bernon rbernon@codeweavers.com
--- dlls/winewayland.drv/wayland_keyboard.c | 56 ++++++++++++++++++++++++- dlls/winewayland.drv/waylanddrv.h | 1 + 2 files changed, 55 insertions(+), 2 deletions(-)
diff --git a/dlls/winewayland.drv/wayland_keyboard.c b/dlls/winewayland.drv/wayland_keyboard.c index 741dbfe1157..ad7b3c4bbc5 100644 --- a/dlls/winewayland.drv/wayland_keyboard.c +++ b/dlls/winewayland.drv/wayland_keyboard.c @@ -27,6 +27,7 @@
#include <linux/input.h> #undef SW_MAX /* Also defined in winuser.rh */ +#include <sys/mman.h> #include <unistd.h>
#include "waylanddrv.h" @@ -140,8 +141,43 @@ static HWND wayland_keyboard_get_focused_hwnd(void) static void keyboard_handle_keymap(void *data, struct wl_keyboard *wl_keyboard, uint32_t format, int fd, uint32_t size) { - FIXME("stub!\n"); + struct wayland_keyboard *keyboard = &process_wayland.keyboard; + struct xkb_keymap *xkb_keymap = NULL; + struct xkb_state *xkb_state; + 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; + } + + 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); + } + + xkb_keymap_unref(xkb_keymap); }
static void keyboard_handle_enter(void *data, struct wl_keyboard *wl_keyboard, @@ -212,8 +248,19 @@ static void keyboard_handle_modifiers(void *data, struct wl_keyboard *wl_keyboar uint32_t mods_latched, uint32_t mods_locked, uint32_t xkb_group) { - FIXME("serial=%u mods_depressed=%#x mods_latched=%#x mods_locked=%#x xkb_group=%d stub!\n", + 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); + + /* FIXME: Sync wine modifier state with XKB modifier state. */ }
static void keyboard_handle_repeat_info(void *data, struct wl_keyboard *wl_keyboard, @@ -270,5 +317,10 @@ void wayland_keyboard_deinit(void) 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); } diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index 00c9112d5de..adbe2170997 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -71,6 +71,7 @@ struct wayland_keyboard { struct wl_keyboard *wl_keyboard; struct xkb_context *xkb_context; + struct xkb_state *xkb_state; HWND focused_hwnd; pthread_mutex_t mutex; };
From: Rémi Bernon rbernon@codeweavers.com
--- configure.ac | 5 +- dlls/winewayland.drv/Makefile.in | 4 +- dlls/winewayland.drv/wayland_keyboard.c | 259 +++++++++++++++++++++++- dlls/winewayland.drv/waylanddrv.h | 1 + 4 files changed, 264 insertions(+), 5 deletions(-)
diff --git a/configure.ac b/configure.ac index aba623ee6e7..cf659c5e90c 100644 --- a/configure.ac +++ b/configure.ac @@ -1371,8 +1371,11 @@ then WINE_PACKAGE_FLAGS(XKBCOMMON,[xkbcommon],,,, [AC_CHECK_HEADERS([xkbcommon/xkbcommon.h]) AC_CHECK_LIB(xkbcommon,xkb_context_new,[:],[XKBCOMMON_LIBS=""],[$XKBCOMMON_LIBS])]) + WINE_PACKAGE_FLAGS(XKBREGISTRY,[xkbregistry],,,, + [AC_CHECK_HEADERS([xkbcommon/xkbregistry.h]) + AC_CHECK_LIB(xkbregistry,rxkb_context_new,[:],[XKBREGISTRY_LIBS=""],[$XKBREGISTRY_LIBS])]) fi -WINE_NOTICE_WITH(wayland, [test -z "$WAYLAND_CLIENT_LIBS" -o -z "$WAYLAND_SCANNER" -o -z "$XKBCOMMON_LIBS" -o "$ac_cv_header_linux_input_h" = "no"], +WINE_NOTICE_WITH(wayland, [test -z "$WAYLAND_CLIENT_LIBS" -o -z "$WAYLAND_SCANNER" -o -z "$XKBCOMMON_LIBS" -o -z "$XKBREGISTRY_LIBS" -o "$ac_cv_header_linux_input_h" = "no"], [Wayland ${notice_platform}development files not found, the Wayland driver won't be supported.], [enable_winewayland_drv])
diff --git a/dlls/winewayland.drv/Makefile.in b/dlls/winewayland.drv/Makefile.in index fa2beaad79c..8be78bd2080 100644 --- a/dlls/winewayland.drv/Makefile.in +++ b/dlls/winewayland.drv/Makefile.in @@ -1,7 +1,7 @@ MODULE = winewayland.drv UNIXLIB = winewayland.so -UNIX_CFLAGS = $(WAYLAND_CLIENT_CFLAGS) $(XKBCOMMON_CFLAGS) -UNIX_LIBS = -lwin32u $(WAYLAND_CLIENT_LIBS) $(XKBCOMMON_LIBS) $(PTHREAD_LIBS) -lm +UNIX_CFLAGS = $(WAYLAND_CLIENT_CFLAGS) $(XKBCOMMON_CFLAGS) $(XKBREGISTRY_CFLAGS) +UNIX_LIBS = -lwin32u $(WAYLAND_CLIENT_LIBS) $(XKBCOMMON_LIBS) $(XKBREGISTRY_LIBS) $(PTHREAD_LIBS) -lm
SOURCES = \ display.c \ diff --git a/dlls/winewayland.drv/wayland_keyboard.c b/dlls/winewayland.drv/wayland_keyboard.c index ad7b3c4bbc5..b2e778f9a65 100644 --- a/dlls/winewayland.drv/wayland_keyboard.c +++ b/dlls/winewayland.drv/wayland_keyboard.c @@ -24,6 +24,7 @@ #endif
#include "config.h" +#include <stdlib.h>
#include <linux/input.h> #undef SW_MAX /* Also defined in winuser.rh */ @@ -36,6 +37,23 @@ WINE_DEFAULT_DEBUG_CHANNEL(keyboard); WINE_DECLARE_DEBUG_CHANNEL(key);
+struct layout +{ + struct list entry; + char *xkb_layout; + + int xkb_group; + LANGID lang; + WORD index; + /* "Layout Id", used by NtUserGetKeyboardLayoutName / LoadKeyboardLayoutW */ + WORD layout_id; +}; + +/* These are only used from the wayland event thread and don't need locking */ +static struct list xkb_layouts = LIST_INIT(xkb_layouts); +static struct rxkb_context *rxkb_context; +static HKL keyboard_hkl; /* the HKL matching the currently active xkb group */ + static WORD key2scan(UINT key) { /* base keys can be mapped directly */ @@ -122,6 +140,198 @@ static WORD key2scan(UINT key) 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, xkb_layout_index_t xkb_group, LANGID lang) +{ + static WORD next_layout_id = 1; + + struct layout *layout; + unsigned int len; + WORD index = 0; + char *ptr; + + TRACE("xkb_layout=%s xkb_group=%u lang=%04x\n", xkb_layout, xkb_group, lang); + + LIST_FOR_EACH_ENTRY(layout, &xkb_layouts, struct layout, entry) + if (layout->lang == lang) index++; + + len = strlen(xkb_layout) + 1; + if (!(layout = calloc(1, sizeof(*layout) + len))) + { + ERR("Failed to allocate memory for Xkb layout entry\n"); + return; + } + ptr = (char *)(layout + 1); + + layout->xkb_layout = strcpy(ptr, xkb_layout); + layout->xkb_group = xkb_group; + layout->lang = lang; + layout->index = index; + if (index) layout->layout_id = next_layout_id++; + + 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; + + 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; + } + + 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; +} + /********************************************************************** * Keyboard handling */ @@ -143,7 +353,9 @@ static void keyboard_handle_keymap(void *data, struct wl_keyboard *wl_keyboard, { 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); @@ -169,12 +381,39 @@ static void keyboard_handle_keymap(void *data, struct wl_keyboard *wl_keyboard, return; }
+ LIST_FOR_EACH_ENTRY_SAFE(entry, next, &xkb_layouts, struct layout, entry) + { + list_remove(&entry->entry); + free(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_group, lang); + } + 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); @@ -198,7 +437,8 @@ static void keyboard_handle_enter(void *data, struct wl_keyboard *wl_keyboard, keyboard->focused_hwnd = hwnd; pthread_mutex_unlock(&keyboard->mutex);
- /* FIXME: update foreground window as well */ + NtUserPostMessage(keyboard->focused_hwnd, WM_INPUTLANGCHANGEREQUEST, 0 /*FIXME*/, + (LPARAM)keyboard_hkl); }
static void keyboard_handle_leave(void *data, struct wl_keyboard *wl_keyboard, @@ -236,7 +476,7 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *wl_keyboard,
input.type = INPUT_KEYBOARD; input.ki.wScan = scan & 0xff; - input.ki.wVk = NtUserMapVirtualKeyEx(scan, MAPVK_VSC_TO_VK_EX, NtUserGetKeyboardLayout(0)); + input.ki.wVk = NtUserMapVirtualKeyEx(scan, MAPVK_VSC_TO_VK_EX, keyboard_hkl); if (scan & ~0xff) input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
if (state == WL_KEYBOARD_KEY_STATE_RELEASED) input.ki.dwFlags |= KEYEVENTF_KEYUP; @@ -260,6 +500,8 @@ static void keyboard_handle_modifiers(void *data, struct wl_keyboard *wl_keyboar 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. */ }
@@ -286,6 +528,13 @@ 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"); @@ -323,4 +572,10 @@ void wayland_keyboard_deinit(void) keyboard->xkb_state = NULL; } pthread_mutex_unlock(&keyboard->mutex); + + if (rxkb_context) + { + rxkb_context_unref(rxkb_context); + rxkb_context = NULL; + } } diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index adbe2170997..31c3f786aff 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -28,6 +28,7 @@ #include <pthread.h> #include <wayland-client.h> #include <xkbcommon/xkbcommon.h> +#include <xkbcommon/xkbregistry.h> #include "viewporter-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h" #include "xdg-shell-client-protocol.h"
From: Rémi Bernon rbernon@codeweavers.com
--- dlls/win32u/driver.c | 23 +++++++++++++++++++++++ dlls/win32u/input.c | 30 ++++++++++++++++++++++-------- include/wine/gdi_driver.h | 3 +++ 3 files changed, 48 insertions(+), 8 deletions(-)
diff --git a/dlls/win32u/driver.c b/dlls/win32u/driver.c index e6a24d1a46c..15b1df41ede 100644 --- a/dlls/win32u/driver.c +++ b/dlls/win32u/driver.c @@ -715,6 +715,15 @@ static SHORT nulldrv_VkKeyScanEx( WCHAR ch, HKL layout ) return -256; /* use default implementation */ }
+static const KBDTABLES *nulldrv_KbdLayerDescriptor( HKL layout ) +{ + return NULL; +} + +static void nulldrv_ReleaseKbdTables( const KBDTABLES *tables ) +{ +} + static UINT nulldrv_ImeProcessKey( HIMC himc, UINT wparam, UINT lparam, const BYTE *state ) { return 0; @@ -1087,6 +1096,16 @@ static SHORT loaderdrv_VkKeyScanEx( WCHAR ch, HKL layout ) return load_driver()->pVkKeyScanEx( ch, layout ); }
+static const KBDTABLES *loaderdrv_KbdLayerDescriptor( HKL layout ) +{ + return load_driver()->pKbdLayerDescriptor( layout ); +} + +static void loaderdrv_ReleaseKbdTables( const KBDTABLES *tables ) +{ + return load_driver()->pReleaseKbdTables( tables ); +} + static UINT loaderdrv_ImeProcessKey( HIMC himc, UINT wparam, UINT lparam, const BYTE *state ) { return load_driver()->pImeProcessKey( himc, wparam, lparam, state ); @@ -1213,6 +1232,8 @@ static const struct user_driver_funcs lazy_load_driver = loaderdrv_ToUnicodeEx, loaderdrv_UnregisterHotKey, loaderdrv_VkKeyScanEx, + loaderdrv_KbdLayerDescriptor, + loaderdrv_ReleaseKbdTables, loaderdrv_ImeProcessKey, loaderdrv_ImeToAsciiEx, loaderdrv_NotifyIMEStatus, @@ -1296,6 +1317,8 @@ void __wine_set_user_driver( const struct user_driver_funcs *funcs, UINT version SET_USER_FUNC(ToUnicodeEx); SET_USER_FUNC(UnregisterHotKey); SET_USER_FUNC(VkKeyScanEx); + SET_USER_FUNC(KbdLayerDescriptor); + SET_USER_FUNC(ReleaseKbdTables); SET_USER_FUNC(ImeProcessKey); SET_USER_FUNC(ImeToAsciiEx); SET_USER_FUNC(NotifyIMEStatus); diff --git a/dlls/win32u/input.c b/dlls/win32u/input.c index 3e6e440de93..d8862beb118 100644 --- a/dlls/win32u/input.c +++ b/dlls/win32u/input.c @@ -1016,13 +1016,16 @@ BOOL WINAPI NtUserSetKeyboardState( BYTE *state ) */ WORD WINAPI NtUserVkKeyScanEx( WCHAR chr, HKL layout ) { - const KBDTABLES *kbd_tables = &kbdus_tables; + const KBDTABLES *kbd_tables; SHORT ret;
TRACE_(keyboard)( "chr %s, layout %p\n", debugstr_wn(&chr, 1), layout );
if ((ret = user_driver->pVkKeyScanEx( chr, layout )) != -256) return ret; + + if (!(kbd_tables = user_driver->pKbdLayerDescriptor( layout ))) kbd_tables = &kbdus_tables; ret = kbd_tables_wchar_to_vkey( kbd_tables, chr ); + if (kbd_tables != &kbdus_tables) user_driver->pReleaseKbdTables( kbd_tables );
TRACE_(keyboard)( "ret %04x\n", ret ); return ret; @@ -1034,14 +1037,16 @@ WORD WINAPI NtUserVkKeyScanEx( WCHAR chr, HKL layout ) */ UINT WINAPI NtUserMapVirtualKeyEx( UINT code, UINT type, HKL layout ) { - const KBDTABLES *kbd_tables = &kbdus_tables; BYTE vsc2vk[0x300], vk2char[0x100]; - UINT ret; + const KBDTABLES *kbd_tables; + UINT ret = 0;
TRACE_(keyboard)( "code %u, type %u, layout %p.\n", code, type, layout );
if ((ret = user_driver->pMapVirtualKeyEx( code, type, layout )) != -1) return ret;
+ if (!(kbd_tables = user_driver->pKbdLayerDescriptor( layout ))) kbd_tables = &kbdus_tables; + switch (type) { case MAPVK_VK_TO_VSC_EX: @@ -1101,9 +1106,11 @@ UINT WINAPI NtUserMapVirtualKeyEx( UINT code, UINT type, HKL layout ) break; default: FIXME_(keyboard)( "unknown type %d\n", type ); - return 0; + break; }
+ if (kbd_tables != &kbdus_tables) user_driver->pReleaseKbdTables( kbd_tables ); + TRACE_(keyboard)( "returning 0x%04x\n", ret ); return ret; } @@ -1114,7 +1121,8 @@ UINT WINAPI NtUserMapVirtualKeyEx( UINT code, UINT type, HKL layout ) INT WINAPI NtUserGetKeyNameText( LONG lparam, WCHAR *buffer, INT size ) { INT code = ((lparam >> 16) & 0x1ff), vkey, len; - const KBDTABLES *kbd_tables = &kbdus_tables; + HKL layout = NtUserGetKeyboardLayout( 0 ); + const KBDTABLES *kbd_tables; VSC_LPWSTR *key_name;
TRACE_(keyboard)( "lparam %#x, buffer %p, size %d.\n", (int)lparam, buffer, size ); @@ -1122,6 +1130,8 @@ INT WINAPI NtUserGetKeyNameText( LONG lparam, WCHAR *buffer, INT size ) if (!buffer || !size) return 0; if ((len = user_driver->pGetKeyNameText( lparam, buffer, size )) >= 0) return len;
+ if (!(kbd_tables = user_driver->pKbdLayerDescriptor( layout ))) kbd_tables = &kbdus_tables; + if (lparam & 0x2000000) { BYTE vsc2vk[0x300]; @@ -1155,6 +1165,8 @@ INT WINAPI NtUserGetKeyNameText( LONG lparam, WCHAR *buffer, INT size ) } buffer[len] = 0;
+ if (kbd_tables != &kbdus_tables) user_driver->pReleaseKbdTables( kbd_tables ); + TRACE_(keyboard)( "ret %d, str %s.\n", len, debugstr_w(buffer) ); return len; } @@ -1165,7 +1177,7 @@ INT WINAPI NtUserGetKeyNameText( LONG lparam, WCHAR *buffer, INT size ) INT WINAPI NtUserToUnicodeEx( UINT virt, UINT scan, const BYTE *state, WCHAR *str, int size, UINT flags, HKL layout ) { - const KBDTABLES *kbd_tables = &kbdus_tables; + const KBDTABLES *kbd_tables; WCHAR buffer[2] = {0}; INT len;
@@ -1173,9 +1185,9 @@ INT WINAPI NtUserToUnicodeEx( UINT virt, UINT scan, const BYTE *state, virt, scan, state, str, size, flags, layout );
if (!state) return 0; - if ((len = user_driver->pToUnicodeEx( virt, scan, state, str, size, flags, layout )) >= -1) - return len; + if ((len = user_driver->pToUnicodeEx( virt, scan, state, str, size, flags, layout )) >= -1) return len;
+ if (!(kbd_tables = user_driver->pKbdLayerDescriptor( layout ))) kbd_tables = &kbdus_tables; if (scan & 0x8000) buffer[0] = 0; /* key up */ else buffer[0] = kbd_tables_vkey_to_wchar( kbd_tables, virt, state );
@@ -1184,6 +1196,8 @@ INT WINAPI NtUserToUnicodeEx( UINT virt, UINT scan, const BYTE *state,
lstrcpynW( str, buffer, size );
+ if (kbd_tables != &kbdus_tables) user_driver->pReleaseKbdTables( kbd_tables ); + TRACE_(keyboard)( "ret %d, str %s.\n", len, debugstr_w(str) ); return len; } diff --git a/include/wine/gdi_driver.h b/include/wine/gdi_driver.h index aa59a256482..c5803fd7228 100644 --- a/include/wine/gdi_driver.h +++ b/include/wine/gdi_driver.h @@ -29,6 +29,7 @@ #include "ntuser.h" #include "immdev.h" #include "ddk/d3dkmthk.h" +#include "kbd.h" #include "wine/list.h"
struct gdi_dc_funcs; @@ -292,6 +293,8 @@ struct user_driver_funcs INT (*pToUnicodeEx)(UINT,UINT,const BYTE *,LPWSTR,int,UINT,HKL); void (*pUnregisterHotKey)(HWND, UINT, UINT); SHORT (*pVkKeyScanEx)(WCHAR, HKL); + const KBDTABLES *(*pKbdLayerDescriptor)(HKL); + void (*pReleaseKbdTables)(const KBDTABLES *); /* IME functions */ UINT (*pImeProcessKey)(HIMC,UINT,UINT,const BYTE*); UINT (*pImeToAsciiEx)(UINT,UINT,const BYTE*,COMPOSITIONSTRING*,HIMC);
From: Rémi Bernon rbernon@codeweavers.com
--- dlls/win32u/input.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dlls/win32u/input.c b/dlls/win32u/input.c index d8862beb118..f718b274176 100644 --- a/dlls/win32u/input.c +++ b/dlls/win32u/input.c @@ -1151,7 +1151,7 @@ INT WINAPI NtUserGetKeyNameText( LONG lparam, WCHAR *buffer, INT size ) else key_name = kbd_tables->pKeyNamesExt; while (key_name->vsc && key_name->vsc != (BYTE)code) key_name++;
- if (key_name->vsc == (BYTE)code) + if (key_name->vsc == (BYTE)code && key_name->pwsz) { len = min( size - 1, wcslen( key_name->pwsz ) ); memcpy( buffer, key_name->pwsz, len * sizeof(WCHAR) );
From: Rémi Bernon rbernon@codeweavers.com
The host layout behave very differently in that case and we have tests that check the exact results. --- dlls/win32u/input.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/dlls/win32u/input.c b/dlls/win32u/input.c index f718b274176..ae03f99adc9 100644 --- a/dlls/win32u/input.c +++ b/dlls/win32u/input.c @@ -516,6 +516,11 @@ static WCHAR kbd_tables_vkey_to_wchar( const KBDTABLES *tables, UINT vkey, const
if (ctrl && alt) return WCH_NONE; if (!ctrl && vkey == VK_ESCAPE) return VK_ESCAPE; + if (ctrl && !alt) + { + if (vkey >= 'A' && vkey <= 'Z') return vkey - 'A' + 1; + tables = &kbdus_tables; + }
mod = caps_mod = kbd_tables_get_mod_num( tables, state, FALSE ); if (caps) caps_mod = kbd_tables_get_mod_num( tables, state, TRUE ); @@ -531,7 +536,6 @@ static WCHAR kbd_tables_vkey_to_wchar( const KBDTABLES *tables, UINT vkey, const } }
- if (ctrl && vkey >= 'A' && vkey <= 'Z') return vkey - 'A' + 1; return WCH_NONE; }
From: Rémi Bernon rbernon@codeweavers.com
--- dlls/win32u/input.c | 2 +- include/kbd.h | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/dlls/win32u/input.c b/dlls/win32u/input.c index ae03f99adc9..fa64de881cf 100644 --- a/dlls/win32u/input.c +++ b/dlls/win32u/input.c @@ -514,7 +514,7 @@ static WCHAR kbd_tables_vkey_to_wchar( const KBDTABLES *tables, UINT vkey, const ctrl = state[VK_CONTROL] & 0x80; caps = state[VK_CAPITAL] & 1;
- if (ctrl && alt) return WCH_NONE; + if (ctrl && alt && !(tables->fLocaleFlags & KLLF_ALTGR)) return WCH_NONE; if (!ctrl && vkey == VK_ESCAPE) return VK_ESCAPE; if (ctrl && !alt) { diff --git a/include/kbd.h b/include/kbd.h index 9bbcd886b1c..da805b7cefc 100644 --- a/include/kbd.h +++ b/include/kbd.h @@ -40,6 +40,10 @@ #define KANALOK 0x08 #define GRPSELTAP 0x80
+#define KLLF_ALTGR 0x0001 +#define KLLF_SHIFTLOCK 0x0002 +#define KLLF_LRM_RLM 0x0004 + typedef struct { BYTE Vk;
From: Rémi Bernon rbernon@codeweavers.com
--- dlls/winewayland.drv/wayland_keyboard.c | 306 +++++++++++++++++++++++- dlls/winewayland.drv/waylanddrv.h | 2 + dlls/winewayland.drv/waylanddrv_main.c | 2 + 3 files changed, 302 insertions(+), 8 deletions(-)
diff --git a/dlls/winewayland.drv/wayland_keyboard.c b/dlls/winewayland.drv/wayland_keyboard.c index b2e778f9a65..4efc2c58fb4 100644 --- a/dlls/winewayland.drv/wayland_keyboard.c +++ b/dlls/winewayland.drv/wayland_keyboard.c @@ -41,19 +41,102 @@ 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]; + VK_TO_BIT vk2bit[4]; + union + { + MODIFIERS modifiers; + char modifiers_buf[offsetof(MODIFIERS, ModNumber[8])]; + }; };
-/* These are only used from the wayland event thread and don't need locking */ +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 WORD key2scan(UINT key) { /* base keys can be mapped directly */ @@ -258,33 +341,180 @@ static HKL get_layout_hkl(struct layout *layout, LCID locale) else return (HKL)(UINT_PTR)MAKELONG(locale, 0xf000 | layout->layout_id); }
-static void add_xkb_layout(const char *xkb_layout, xkb_layout_index_t xkb_group, LANGID lang) +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; + VSC_LPWSTR *names_entry, *names_ext_entry; + VSC_VK *vsc2vk_e0_entry, *vsc2vk_e1_entry; + VK_TO_WCHARS8 *vk2wchars_entry; struct layout *layout; - unsigned int len; + const USHORT *scan2vk; + WCHAR *names_str; WORD index = 0; char *ptr;
- TRACE("xkb_layout=%s xkb_group=%u lang=%04x\n", xkb_layout, xkb_group, lang); + 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) + len))) + if (!(layout = calloc(1, sizeof(*layout) + names_len + len))) { ERR("Failed to allocate memory for Xkb layout entry\n"); return; } - ptr = (char *)(layout + 1); + 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; + case MAKELANGID(LANG_GERMAN, SUBLANG_DEFAULT): scan2vk = scan2vk_qwertz; + case MAKELANGID(LANG_GERMAN, SUBLANG_GERMAN_SWISS): scan2vk = scan2vk_qwertz; + default: scan2vk = scan2vk_qwerty; + } + + 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); + altgr_mask = 1 << xkb_keymap_mod_get_index(xkb_keymap, "Mod5"); + + for (keyc = min_keycode; keyc <= max_keycode; keyc++) + { + WORD scan = key2scan(keyc - 8), vkey = scan2vk[scan]; + VK_TO_WCHARS8 vkey2wch = {.VirtualKey = vkey}; + BOOL found = FALSE; + unsigned int mod; + + 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; + } + + if (!found) continue; + + 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); @@ -297,6 +527,8 @@ static void set_current_xkb_group(xkb_layout_index_t xkb_group) 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) @@ -307,6 +539,8 @@ static void set_current_xkb_group(xkb_layout_index_t xkb_group) hkl = keyboard_hkl; }
+ pthread_mutex_unlock(&xkb_layouts_mutex); + if (hkl == keyboard_hkl) return; keyboard_hkl = hkl;
@@ -381,10 +615,12 @@ static void keyboard_handle_keymap(void *data, struct wl_keyboard *wl_keyboard, return; }
+ pthread_mutex_lock(&xkb_layouts_mutex); + LIST_FOR_EACH_ENTRY_SAFE(entry, next, &xkb_layouts, struct layout, entry) { list_remove(&entry->entry); - free(entry); + xkb_layout_release(entry); }
for (xkb_group = 0; xkb_group < xkb_keymap_num_layouts(xkb_keymap); xkb_group++) @@ -403,9 +639,11 @@ static void keyboard_handle_keymap(void *data, struct wl_keyboard *wl_keyboard,
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_group, lang); + 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); @@ -462,6 +700,17 @@ static void keyboard_handle_leave(void *data, struct wl_keyboard *wl_keyboard, /* 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 = key2scan(KEY_RIGHTCTRL); + input.ki.wVk = VK_RCONTROL; + input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; + if (state == WL_KEYBOARD_KEY_STATE_RELEASED) input.ki.dwFlags |= KEYEVENTF_KEYUP; + __wine_send_input(hwnd, &input, NULL); +} + static void keyboard_handle_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) @@ -474,6 +723,9 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *wl_keyboard,
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 & 0xff; input.ki.wVk = NtUserMapVirtualKeyEx(scan, MAPVK_VSC_TO_VK_EX, keyboard_hkl); @@ -579,3 +831,41 @@ void wayland_keyboard_deinit(void) 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); +} diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index 31c3f786aff..72d84377333 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -266,6 +266,8 @@ void wayland_window_flush(HWND hwnd) DECLSPEC_HIDDEN;
void wayland_keyboard_init(struct wl_keyboard *wl_keyboard) DECLSPEC_HIDDEN; void wayland_keyboard_deinit(void) DECLSPEC_HIDDEN; +const KBDTABLES *WAYLAND_KbdLayerDescriptor(HKL hkl) DECLSPEC_HIDDEN; +void WAYLAND_ReleaseKbdTables(const KBDTABLES *) DECLSPEC_HIDDEN;
/********************************************************************** * Wayland pointer diff --git a/dlls/winewayland.drv/waylanddrv_main.c b/dlls/winewayland.drv/waylanddrv_main.c index be898f9e2c3..435a6d2b36c 100644 --- a/dlls/winewayland.drv/waylanddrv_main.c +++ b/dlls/winewayland.drv/waylanddrv_main.c @@ -33,6 +33,8 @@ static const struct user_driver_funcs waylanddrv_funcs = { .pDesktopWindowProc = WAYLAND_DesktopWindowProc, .pDestroyWindow = WAYLAND_DestroyWindow, + .pKbdLayerDescriptor = WAYLAND_KbdLayerDescriptor, + .pReleaseKbdTables = WAYLAND_ReleaseKbdTables, .pSetCursor = WAYLAND_SetCursor, .pSysCommand = WAYLAND_SysCommand, .pUpdateDisplayDevices = WAYLAND_UpdateDisplayDevices,
From: Rémi Bernon rbernon@codeweavers.com
--- dlls/win32u/input.c | 5 +++++ 1 file changed, 5 insertions(+)
diff --git a/dlls/win32u/input.c b/dlls/win32u/input.c index fa64de881cf..3ee46f0bfcf 100644 --- a/dlls/win32u/input.c +++ b/dlls/win32u/input.c @@ -531,6 +531,11 @@ static WCHAR kbd_tables_vkey_to_wchar( const KBDTABLES *tables, UINT vkey, const for (entry = table->pVkToWchars; entry->VirtualKey; entry = NEXT_ENTRY(table, entry)) { if (entry->VirtualKey != vkey) continue; + /* SGCAPS attribute may be set on entries where VK_CAPITAL and VK_SHIFT behave differently. + * The entry corresponds to the mapping when Caps Lock is on, and a second entry follows it + * with the mapping when Caps Lock is off. + */ + if ((entry->Attributes & SGCAPS) && !caps) entry = NEXT_ENTRY(table, entry); if ((entry->Attributes & CAPLOK) && table->nModifications > caps_mod) return entry->wch[caps_mod]; return entry->wch[mod]; }
From: Rémi Bernon rbernon@codeweavers.com
--- dlls/winewayland.drv/wayland_keyboard.c | 34 ++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-)
diff --git a/dlls/winewayland.drv/wayland_keyboard.c b/dlls/winewayland.drv/wayland_keyboard.c index 4efc2c58fb4..0064a23d449 100644 --- a/dlls/winewayland.drv/wayland_keyboard.c +++ b/dlls/winewayland.drv/wayland_keyboard.c @@ -59,7 +59,7 @@ struct layout VSC_VK vsc2vk_e1[0x100];
VK_TO_WCHAR_TABLE vk_to_wchar_table[2]; - VK_TO_WCHARS8 vk_to_wchars8[0x100]; + VK_TO_WCHARS8 vk_to_wchars8[0x100 * 2 /* SGCAPS */]; VK_TO_BIT vk2bit[4]; union { @@ -348,7 +348,7 @@ static void add_xkb_layout(const char *xkb_layout, struct xkb_keymap *xkb_keymap
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; + xkb_mod_mask_t shift_mask, control_mask, altgr_mask, capslock_mask; VSC_LPWSTR *names_entry, *names_ext_entry; VSC_VK *vsc2vk_e0_entry, *vsc2vk_e1_entry; VK_TO_WCHARS8 *vk2wchars_entry; @@ -480,13 +480,15 @@ static void add_xkb_layout(const char *xkb_layout, struct xkb_keymap *xkb_keymap
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");
for (keyc = min_keycode; keyc <= max_keycode; keyc++) { WORD scan = key2scan(keyc - 8), vkey = scan2vk[scan]; - VK_TO_WCHARS8 vkey2wch = {.VirtualKey = vkey}; - BOOL found = FALSE; + VK_TO_WCHARS8 vkey2wch = {.VirtualKey = vkey}, caps_vkey2wch = vkey2wch; + BOOL found = FALSE, caps_found = FALSE; + uint32_t caps_ret, shift_ret; unsigned int mod;
for (mod = 0; mod < 8; ++mod) @@ -506,10 +508,34 @@ static void add_xkb_layout(const char *xkb_layout, struct xkb_keymap *xkb_keymap 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; }
On Thu Nov 23 09:37:31 2023 +0000, Alexandros Frantzis wrote:
I assume that the way to add other layouts (e.g., dvorak, colemak) would be to create new scan2vk tables, correct? I wonder if it would be useful, in some cases at least, to have an `xkb_keysym_to_vk` function to fill the VSCtoVK table(s) automatically in the code below. So, instead of the current `keyc -> scan -> vk` we would have `keyc -> scan, keyc -> keysym -> vk`.
I think the scancode to vkey tables are very Windows-specific, and although vkey are supposed to abstract the scancodes, I believe it's very legacy and except for some specific ones, the vkey don't really have a true meaning like the Xkb keysym.
I don't think there are many tables out there, and many Windows layout just re-use qwerty with minor variations (which I don't think really matter) but a completely different vkey -> wchar mapping. Having dynamic vkey tables, which is what winex11 is doing, is just confusing applications even more.
On Thu Nov 23 14:38:38 2023 +0000, Rémi Bernon wrote:
changed this line in [version 4 of the diff](/wine/wine/-/merge_requests/4455/diffs?diff_id=85588&start_sha=eb5fb05e7ea40f7ff1224d33ce2f1bee493b0e3e#61204a3bfaf089b3588b75ee6440092f482a6098_482_489)
Not necessarily but indeed the CAPLOK attribute should only be there for keys which have the same behavior between Caps Lock and Shift. I've split this to a separate change.
I also took that opportunity to implement the case where Caps Lock and Shift have a *different* behavior, which is the case on Linux (but not on Windows) with a French keyboard for instance to type accented caps like É. Some other Windows keyboard layouts apparently have this as well, and it's anyway probably best to behave more similarly to other Linux apps.
On Thu Nov 23 14:38:47 2023 +0000, Alexandros Frantzis wrote:
Nit: I guess this is only needed in upcoming commits?
Indeed.
On Thu Nov 23 14:38:36 2023 +0000, Rémi Bernon wrote:
changed this line in [version 4 of the diff](/wine/wine/-/merge_requests/4455/diffs?diff_id=85588&start_sha=eb5fb05e7ea40f7ff1224d33ce2f1bee493b0e3e#61204a3bfaf089b3588b75ee6440092f482a6098_512_545)
Done.
On Thu Nov 23 14:38:37 2023 +0000, Rémi Bernon wrote:
changed this line in [version 4 of the diff](/wine/wine/-/merge_requests/4455/diffs?diff_id=85588&start_sha=eb5fb05e7ea40f7ff1224d33ce2f1bee493b0e3e#61204a3bfaf089b3588b75ee6440092f482a6098_453_459)
Done.
On Thu Nov 23 14:38:45 2023 +0000, Rémi Bernon wrote:
I think the scancode to vkey tables are very Windows-specific, and although vkey are supposed to abstract the scancodes, I believe it's very legacy and except for some specific ones, the vkey don't really have a true meaning like the Xkb keysym. I don't think there are many tables out there, and many Windows layout just re-use qwerty with minor variations (which I don't think really matter) but a completely different vkey -> wchar mapping. Having dynamic vkey tables, which is what winex11 is doing, is just confusing applications even more.
Regarding dvorak, the Windows dvorak layout has indeed a quite different scancode to vkey mapping. I don't think it matters very much but if it does then we can probably detect it from the xkb_variant like I was doing on winex11 side (I actually dropped it before finding a way to get the xkb_variant back).