[PATCH v5 0/5] MR10550: winex11: Keyboard layouts rework, part 1
The idea is to improve the detection of the available keyboard layouts, normally via Xkb group names + xkbregistry, and then propagate the current layout to win32u and the PE / win32 side state. Along the way also start using `KBDTABLES` and the related newer win32u machinery that was introduced for winewayland. I'm taking a lot of inspiration (and also actual patches, or pieces of code) from the winewayland driver and Rémi's draft MR !2122. The main difference from that attempt is that I'm going to keep around `X11DRV_KEYBOARD_DetectLayout()` and use it as a fallback detection mechanism (e.g. for vncserver), but still move winex11 to `KBDTABLES` and such. You can find the current (still WIP, especially for later patches) branch at https://gitlab.winehq.org/Mystral/wine/-/commits/hl-kbl?ref_type=heads -- v5: winex11: Decode current keyboard layout from the Xkb group name. winex11: Look up keysyms for the currently selected Xkb group in X11DRV_KEYBOARD_DetectLayout(). winex11: Get Xkb group names. winex11: Factor out mapping keycodes to scancodes / keysyms into a separate function. winex11: Fix some X11DRV_InitKeyboard loop indentation. https://gitlab.winehq.org/wine/wine/-/merge_requests/10550
From: Rémi Bernon <rbernon@codeweavers.com> --- dlls/winex11.drv/keyboard.c | 69 ++++++++++++++++------------------ dlls/winex11.drv/x11drv.h | 2 +- dlls/winex11.drv/x11drv_main.c | 3 +- 3 files changed, 35 insertions(+), 39 deletions(-) diff --git a/dlls/winex11.drv/keyboard.c b/dlls/winex11.drv/keyboard.c index e2281bef017..a8f5f5c16f0 100644 --- a/dlls/winex11.drv/keyboard.c +++ b/dlls/winex11.drv/keyboard.c @@ -1435,16 +1435,8 @@ BOOL X11DRV_KeyEvent( HWND hwnd, XEvent *xev ) return TRUE; } -/********************************************************************** - * X11DRV_KEYBOARD_DetectLayout - * - * Called from X11DRV_InitKeyboard - * This routine walks through the defined keyboard layouts and selects - * whichever matches most closely. - * kbd_section must be held. - */ -static void -X11DRV_KEYBOARD_DetectLayout( Display *display ) +/* fuzzy layout detection through keysym / keycode matching, kbd_section must be held */ +static void detect_keyboard_layout( Display *display ) { unsigned current, match, mismatch, seq, i, syms; int score, keyc, key, pkey, ok; @@ -1547,10 +1539,8 @@ X11DRV_KEYBOARD_DetectLayout( Display *display ) } -/********************************************************************** - * X11DRV_InitKeyboard - */ -void X11DRV_InitKeyboard( Display *display ) +/* initialize or update keyboard layouts */ +static void init_keyboard_layouts( Display *display ) { XModifierKeymap *mmp; KeySym keysym; @@ -1582,37 +1572,34 @@ void X11DRV_InitKeyboard( Display *display ) int vkey_range; pthread_mutex_lock( &kbd_mutex ); - XDisplayKeycodes(display, &min_keycode, &max_keycode); + XDisplayKeycodes( display, &min_keycode, &max_keycode ); XFree( XGetKeyboardMapping( display, min_keycode, max_keycode + 1 - min_keycode, &keysyms_per_keycode ) ); - mmp = XGetModifierMapping(display); + mmp = XGetModifierMapping( display ); kcp = mmp->modifiermap; for (i = 0; i < 8; i += 1) /* There are 8 modifier keys */ { - int j; - - for (j = 0; j < mmp->max_keypermod; j += 1, kcp += 1) - if (*kcp) + for (int j = 0; j < mmp->max_keypermod; j += 1, kcp += 1) + { + for (int k = 0; *kcp && k < keysyms_per_keycode; k += 1) { - int k; - - for (k = 0; k < keysyms_per_keycode; k += 1) - if (XkbKeycodeToKeysym( display, *kcp, 0, k ) == XK_Num_Lock) - { - NumLockMask = 1 << i; - TRACE_(key)("NumLockMask is %x\n", NumLockMask); - } - else if (XkbKeycodeToKeysym( display, *kcp, 0, k ) == XK_Scroll_Lock) - { - ScrollLockMask = 1 << i; - TRACE_(key)("ScrollLockMask is %x\n", ScrollLockMask); - } + if (XkbKeycodeToKeysym( display, *kcp, 0, k ) == XK_Num_Lock) + { + NumLockMask = 1 << i; + TRACE_(key)( "NumLockMask is %x\n", NumLockMask ); + } + else if (XkbKeycodeToKeysym( display, *kcp, 0, k ) == XK_Scroll_Lock) + { + ScrollLockMask = 1 << i; + TRACE_(key)( "ScrollLockMask is %x\n", ScrollLockMask ); + } } + } } - XFreeModifiermap(mmp); + XFreeModifiermap( mmp ); /* Detect the keyboard layout */ - X11DRV_KEYBOARD_DetectLayout( display ); + detect_keyboard_layout( display ); lkey = main_key_tab[kbd_layout].key; syms = (keysyms_per_keycode > 4) ? 4 : keysyms_per_keycode; @@ -1844,7 +1831,7 @@ BOOL X11DRV_MappingNotify( HWND dummy, XEvent *event ) HWND hwnd; XRefreshKeyboardMapping(&event->xmapping); - X11DRV_InitKeyboard( event->xmapping.display ); + init_keyboard_layouts( event->xmapping.display ); hwnd = get_focus(); if (!hwnd) hwnd = get_active_window(); @@ -1854,6 +1841,16 @@ BOOL X11DRV_MappingNotify( HWND dummy, XEvent *event ) } +/*********************************************************************** + * x11drv_init_keyboard + */ +void x11drv_init_keyboard( Display *display ) +{ + XkbUseExtension( display, NULL, NULL ); + init_keyboard_layouts( display ); +} + + /*********************************************************************** * VkKeyScanEx (X11DRV.@) * diff --git a/dlls/winex11.drv/x11drv.h b/dlls/winex11.drv/x11drv.h index 8b0c0a62f91..e69ac5a8ae0 100644 --- a/dlls/winex11.drv/x11drv.h +++ b/dlls/winex11.drv/x11drv.h @@ -753,7 +753,7 @@ extern void X11DRV_ActivateWindow( HWND hwnd, HWND previous ); extern void reapply_cursor_clipping(void); extern void ungrab_clipping_window(void); extern void move_resize_window( HWND hwnd, int dir, POINT pos ); -extern void X11DRV_InitKeyboard( Display *display ); +extern void x11drv_init_keyboard( Display *display ); extern BOOL X11DRV_ProcessEvents( DWORD mask ); typedef int (*x11drv_error_callback)( Display *display, XErrorEvent *event, void *arg ); diff --git a/dlls/winex11.drv/x11drv_main.c b/dlls/winex11.drv/x11drv_main.c index 1bdcd282f2e..1c8002e263c 100644 --- a/dlls/winex11.drv/x11drv_main.c +++ b/dlls/winex11.drv/x11drv_main.c @@ -672,8 +672,7 @@ NTSTATUS __wine_unix_lib_init(void) #endif x11drv_xinput2_load(); - XkbUseExtension( gdi_display, NULL, NULL ); - X11DRV_InitKeyboard( gdi_display ); + x11drv_init_keyboard( gdi_display ); if (use_xim) use_xim = xim_init( input_style ); init_icm_profile(); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10550
From: Matteo Bruni <mbruni@codeweavers.com> We'll entirely get rid of the new x11drv_init_layout() function after introducing the new implementation based on KBDTABLES. --- dlls/winex11.drv/keyboard.c | 74 ++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/dlls/winex11.drv/keyboard.c b/dlls/winex11.drv/keyboard.c index a8f5f5c16f0..72ddf8c12cc 100644 --- a/dlls/winex11.drv/keyboard.c +++ b/dlls/winex11.drv/keyboard.c @@ -1539,12 +1539,10 @@ static void detect_keyboard_layout( Display *display ) } -/* initialize or update keyboard layouts */ -static void init_keyboard_layouts( Display *display ) +/* initialize keyc2scan and keyc2vkey */ +static void init_keycode_mappings( Display *display ) { - XModifierKeymap *mmp; KeySym keysym; - KeyCode *kcp; XKeyEvent e2; WORD scan, vkey; int keyc, i, keyn, syms; @@ -1571,35 +1569,6 @@ static void init_keyboard_layouts( Display *display ) }; int vkey_range; - pthread_mutex_lock( &kbd_mutex ); - XDisplayKeycodes( display, &min_keycode, &max_keycode ); - XFree( XGetKeyboardMapping( display, min_keycode, max_keycode + 1 - min_keycode, &keysyms_per_keycode ) ); - - mmp = XGetModifierMapping( display ); - kcp = mmp->modifiermap; - for (i = 0; i < 8; i += 1) /* There are 8 modifier keys */ - { - for (int j = 0; j < mmp->max_keypermod; j += 1, kcp += 1) - { - for (int k = 0; *kcp && k < keysyms_per_keycode; k += 1) - { - if (XkbKeycodeToKeysym( display, *kcp, 0, k ) == XK_Num_Lock) - { - NumLockMask = 1 << i; - TRACE_(key)( "NumLockMask is %x\n", NumLockMask ); - } - else if (XkbKeycodeToKeysym( display, *kcp, 0, k ) == XK_Scroll_Lock) - { - ScrollLockMask = 1 << i; - TRACE_(key)( "ScrollLockMask is %x\n", ScrollLockMask ); - } - } - } - } - XFreeModifiermap( mmp ); - - /* Detect the keyboard layout */ - detect_keyboard_layout( display ); lkey = main_key_tab[kbd_layout].key; syms = (keysyms_per_keycode > 4) ? 4 : keysyms_per_keycode; @@ -1800,6 +1769,45 @@ static void init_keyboard_layouts( Display *display ) TRACE_(key)("assigning scancode %02x to unidentified keycode %u (%s)\n",scan,keyc,ksname); keyc2scan[keyc]=scan++; } +} + + +/* initialize or update keyboard layouts */ +void init_keyboard_layouts( Display *display ) +{ + XModifierKeymap *mmp; + KeyCode *kcp; + + pthread_mutex_lock( &kbd_mutex ); + XDisplayKeycodes( display, &min_keycode, &max_keycode ); + XFree( XGetKeyboardMapping( display, min_keycode, max_keycode + 1 - min_keycode, &keysyms_per_keycode ) ); + + mmp = XGetModifierMapping( display ); + kcp = mmp->modifiermap; + for (int i = 0; i < 8; i += 1) /* There are 8 modifier keys */ + { + for (int j = 0; j < mmp->max_keypermod; j += 1, kcp += 1) + { + for (int k = 0; *kcp && k < keysyms_per_keycode; k += 1) + { + if (XkbKeycodeToKeysym( display, *kcp, 0, k ) == XK_Num_Lock) + { + NumLockMask = 1 << i; + TRACE_(key)( "NumLockMask is %x\n", NumLockMask ); + } + else if (XkbKeycodeToKeysym( display, *kcp, 0, k ) == XK_Scroll_Lock) + { + ScrollLockMask = 1 << i; + TRACE_(key)( "ScrollLockMask is %x\n", ScrollLockMask ); + } + } + } + } + XFreeModifiermap( mmp ); + + detect_keyboard_layout( display ); + + init_keycode_mappings( display ); pthread_mutex_unlock( &kbd_mutex ); } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10550
From: Matteo Bruni <mbruni@codeweavers.com> --- dlls/winex11.drv/keyboard.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/dlls/winex11.drv/keyboard.c b/dlls/winex11.drv/keyboard.c index 72ddf8c12cc..b08a0594c8e 100644 --- a/dlls/winex11.drv/keyboard.c +++ b/dlls/winex11.drv/keyboard.c @@ -1775,7 +1775,11 @@ static void init_keycode_mappings( Display *display ) /* initialize or update keyboard layouts */ void init_keyboard_layouts( Display *display ) { + unsigned int xkb_group; + XkbStateRec xkb_state; XModifierKeymap *mmp; + XkbDescRec *xkb_desc; + Status status; KeyCode *kcp; pthread_mutex_lock( &kbd_mutex ); @@ -1805,6 +1809,29 @@ void init_keyboard_layouts( Display *display ) } XFreeModifiermap( mmp ); + status = XkbGetState( display, XkbUseCoreKbd, &xkb_state ); + xkb_group = status ? 0 : xkb_state.group; + TRACE( "current group %u (status %#x)\n", xkb_group, status ); + + if ((xkb_desc = XkbGetMap( display, XkbAllClientInfoMask, XkbUseCoreKbd ))) + { + char *names[4]; + int count; + + XkbGetNames( display, XkbGroupNamesMask, xkb_desc ); + for (count = 0; count < ARRAY_SIZE(xkb_desc->names->groups); count++) + if (!xkb_desc->names->groups[count]) break; + + if (!XGetAtomNames( display, xkb_desc->names->groups, count, names )) count = 0; + for (int i = 0; i < count; i++) + { + TRACE( "group %u name %s\n", i, debugstr_a(names[i]) ); + XFree( names[i] ); + } + + XkbFreeKeyboard( xkb_desc, 0, True ); + } + detect_keyboard_layout( display ); init_keycode_mappings( display ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10550
From: Matteo Bruni <mbruni@codeweavers.com> --- dlls/winex11.drv/keyboard.c | 42 +++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/dlls/winex11.drv/keyboard.c b/dlls/winex11.drv/keyboard.c index b08a0594c8e..60e733630b6 100644 --- a/dlls/winex11.drv/keyboard.c +++ b/dlls/winex11.drv/keyboard.c @@ -1388,15 +1388,6 @@ BOOL X11DRV_KeyEvent( HWND hwnd, XEvent *xev ) pthread_mutex_lock( &kbd_mutex ); - /* If XKB extensions are used, the state mask for AltGr will use the group - index instead of the modifier mask. The group index is set in bits - 13-14 of the state field in the XKeyEvent structure. So if AltGr is - pressed, look if the group index is different than 0. From XKB - extension documentation, the group index for AltGr should be 2 - (event->state = 0x2000). It's probably better to not assume a - predefined group index and find it dynamically - - Ref: X Keyboard Extension: Library specification (section 14.1.1 and 17.1.1) */ /* Save also all possible modifier states. */ AltGrMask = event->state & (0x6000 | Mod1Mask | Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask); @@ -1436,15 +1427,28 @@ BOOL X11DRV_KeyEvent( HWND hwnd, XEvent *xev ) } /* fuzzy layout detection through keysym / keycode matching, kbd_section must be held */ -static void detect_keyboard_layout( Display *display ) +static void detect_keyboard_layout( Display *display, XModifierKeymap *modmap, unsigned int xkb_group ) { unsigned current, match, mismatch, seq, i, syms; int score, keyc, key, pkey, ok; - KeySym keysym = 0; + KeySym keysym; const char (*lkey)[MAIN_LEN][4]; unsigned max_seq = 0; int max_score = INT_MIN, ismatch = 0; char ckey[256][4]; + unsigned int state, altgr_mod = 0, dummy, mod; + + TRACE( "display %p, mmp %p, xkb_group %u\n", display, modmap, xkb_group ); + + for (mod = 0; mod < 8 * modmap->max_keypermod; mod++) + { + int xmod = 1 << (mod / modmap->max_keypermod); + + if (!(keyc = modmap->modifiermap[mod])) continue; + XkbLookupKeySym( display, keyc, xkb_group * 0x2000, &dummy, &keysym ); + if (keysym == XK_ISO_Level3_Shift) altgr_mod = xmod; + } + TRACE( "AltGr is mapped to mod %#x\n", altgr_mod ); syms = keysyms_per_keycode; if (syms > 4) { @@ -1453,10 +1457,16 @@ static void detect_keyboard_layout( Display *display ) } memset( ckey, 0, sizeof(ckey) ); - for (keyc = min_keycode; keyc <= max_keycode; keyc++) { + for (keyc = min_keycode; keyc <= max_keycode; keyc++) + { /* get data for keycode from X server */ - for (i = 0; i < syms; i++) { - if (!(keysym = XkbKeycodeToKeysym( display, keyc, 0, i ))) continue; + for (i = 0; i < syms; i++) + { + /* With Xkb, the current group index is encoded in bits 13-14 of the state field. + * Ref: X Keyboard Extension: Library specification (section 18.1.1) + */ + state = xkb_group << 13 | (i & 1 ? ShiftMask : 0) | (i & 2 ? altgr_mod : 0); + if (!XkbLookupKeySym( display, keyc, state, &dummy, &keysym )) continue; /* Allow both one-byte and two-byte national keysyms */ if ((keysym < 0x8000) && (keysym != ' ')) { @@ -1807,7 +1817,6 @@ void init_keyboard_layouts( Display *display ) } } } - XFreeModifiermap( mmp ); status = XkbGetState( display, XkbUseCoreKbd, &xkb_state ); xkb_group = status ? 0 : xkb_state.group; @@ -1832,7 +1841,8 @@ void init_keyboard_layouts( Display *display ) XkbFreeKeyboard( xkb_desc, 0, True ); } - detect_keyboard_layout( display ); + detect_keyboard_layout( display, mmp, xkb_group ); + XFreeModifiermap( mmp ); init_keycode_mappings( display ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10550
From: Matteo Bruni <mbruni@codeweavers.com> For large parts lifted from Rémi's winewayland commit d64ea8e4a6c93ba77208cb680b91dc0c2830049d. --- configure.ac | 9 +- dlls/winex11.drv/Makefile.in | 2 +- dlls/winex11.drv/keyboard.c | 219 ++++++++++++++++++++++++++++++++++- 3 files changed, 226 insertions(+), 4 deletions(-) diff --git a/configure.ac b/configure.ac index f35ac4376d9..869ed04b9b8 100644 --- a/configure.ac +++ b/configure.ac @@ -1433,6 +1433,13 @@ else X_LIBS="" fi +if test "x$have_x$with_wayland" != "xnono" +then + WINE_PACKAGE_FLAGS(XKBREGISTRY,[xkbregistry],,,, + [AC_CHECK_HEADERS([xkbcommon/xkbregistry.h]) + WINE_CHECK_SONAME(xkbregistry,rxkb_context_new,[:],[XKBREGISTRY_LIBS=""],[$XKBREGISTRY_LIBS])]) +fi + if test "x$with_wayland" != "xno" then WINE_PACKAGE_FLAGS(WAYLAND_CLIENT,[wayland-client],,,, @@ -1443,8 +1450,6 @@ then [WAYLAND_CLIENT_LIBS=""],[$WAYLAND_CLIENT_LIBS])])]) WINE_PACKAGE_FLAGS(XKBCOMMON,[xkbcommon],,,, [AC_CHECK_LIB(xkbcommon,xkb_context_new,[:],[XKBCOMMON_LIBS=""],[$XKBCOMMON_LIBS])]) - WINE_PACKAGE_FLAGS(XKBREGISTRY,[xkbregistry],,,, - [AC_CHECK_LIB(xkbregistry,rxkb_context_new,[:],[XKBREGISTRY_LIBS=""],[$XKBREGISTRY_LIBS])]) if test "x$with_opengl" != "xno" then WINE_PACKAGE_FLAGS(WAYLAND_EGL,[wayland-egl],,,, diff --git a/dlls/winex11.drv/Makefile.in b/dlls/winex11.drv/Makefile.in index f9ddd05175b..b10be2c519b 100644 --- a/dlls/winex11.drv/Makefile.in +++ b/dlls/winex11.drv/Makefile.in @@ -1,6 +1,6 @@ MODULE = winex11.drv UNIXLIB = winex11.so -UNIX_CFLAGS = $(X_CFLAGS) +UNIX_CFLAGS = $(X_CFLAGS) $(XKBREGISTRY_CFLAGS) UNIX_LIBS = -lwin32u $(X_LIBS) $(PTHREAD_LIBS) -lm VER_FILEDESCRIPTION_STR = "Wine X11 driver" diff --git a/dlls/winex11.drv/keyboard.c b/dlls/winex11.drv/keyboard.c index 60e733630b6..611e9b88194 100644 --- a/dlls/winex11.drv/keyboard.c +++ b/dlls/winex11.drv/keyboard.c @@ -36,10 +36,16 @@ #include <X11/Xutil.h> #include <X11/XKBlib.h> +#ifdef HAVE_XKBCOMMON_XKBREGISTRY_H +#include <xkbcommon/xkbregistry.h> +#endif + #include <ctype.h> #include <stdarg.h> #include <string.h> +#include <dlfcn.h> + #include "x11drv.h" #include "wingdi.h" @@ -939,6 +945,20 @@ static const struct { {0, NULL, NULL, NULL, NULL} /* sentinel */ }; static unsigned kbd_layout=0; /* index into above table of layouts */ +#ifdef SONAME_LIBXKBREGISTRY +static struct rxkb_context *rxkb_context; + +static void *xkbregistry_handle; +#define MAKE_FUNCPTR(f) static typeof(f) * p_##f; +MAKE_FUNCPTR(rxkb_context_new) +MAKE_FUNCPTR(rxkb_context_parse_default_ruleset) +MAKE_FUNCPTR(rxkb_layout_first) +MAKE_FUNCPTR(rxkb_layout_get_description) +MAKE_FUNCPTR(rxkb_layout_get_name) +MAKE_FUNCPTR(rxkb_layout_get_variant) +MAKE_FUNCPTR(rxkb_layout_next) +#undef MAKE_FUNCPTR +#endif /* maybe more of these scancodes should be extended? */ /* extended must be set for ALT_R, CTRL_R, @@ -1426,6 +1446,157 @@ BOOL X11DRV_KeyEvent( HWND hwnd, XEvent *xev ) return TRUE; } +static BOOL find_xkb_layout_variant( const char *name, const char **layout, const char **variant ) +{ +#ifdef SONAME_LIBXKBREGISTRY + struct rxkb_layout *iter; + + if (rxkb_context) + { + for (iter = p_rxkb_layout_first( rxkb_context ); iter; iter = p_rxkb_layout_next( iter )) + { + const char *desc = p_rxkb_layout_get_description( iter ); + + if (desc && !strcmp( name, desc )) + { + *layout = p_rxkb_layout_get_name( iter ); + *variant = p_rxkb_layout_get_variant( iter ); + return TRUE; + } + } + + WARN( "Unknown Xkb layout name %s\n", debugstr_a(name) ); + } + else + WARN( "libxkbregistry not available, falling back to fuzzy layout detection\n" ); +#else + WARN( "libxkbregistry support not compiled in, falling back to fuzzy layout detection\n" ); +#endif + return FALSE; +} + +static const struct layout_id_map_entry +{ + const char *name; + LANGID langid; +} layout_ids[] = +{ + { "af", MAKELANGID(LANG_DARI, SUBLANG_DEFAULT) }, + { "al", MAKELANGID(LANG_ALBANIAN, SUBLANG_DEFAULT) }, + { "am", MAKELANGID(LANG_ARMENIAN, SUBLANG_DEFAULT) }, + { "ara", MAKELANGID(LANG_ARABIC, SUBLANG_DEFAULT) }, + { "at", MAKELANGID(LANG_GERMAN, SUBLANG_GERMAN_AUSTRIAN) }, + { "az", MAKELANGID(LANG_AZERBAIJANI, SUBLANG_DEFAULT) }, + { "au", MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_AUS) }, + { "ba", MAKELANGID(LANG_BOSNIAN, SUBLANG_BOSNIAN_BOSNIA_HERZEGOVINA_CYRILLIC) }, + { "bd", MAKELANGID(LANG_BANGLA, SUBLANG_DEFAULT) }, + { "be", MAKELANGID(LANG_FRENCH, SUBLANG_FRENCH_BELGIAN) }, + { "bg", MAKELANGID(LANG_BULGARIAN, SUBLANG_DEFAULT) }, + { "br", MAKELANGID(LANG_PORTUGUESE, 2) }, + { "brai", MAKELANGID(LANG_NEUTRAL, SUBLANG_CUSTOM_DEFAULT) }, + { "bt", MAKELANGID(LANG_TIBETAN, 3) }, + { "bw", MAKELANGID(LANG_TSWANA, SUBLANG_TSWANA_BOTSWANA) }, + { "by", MAKELANGID(LANG_BELARUSIAN, SUBLANG_DEFAULT) }, + { "ca", MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_CAN) }, + { "cd", MAKELANGID(LANG_FRENCH, SUBLANG_CUSTOM_UNSPECIFIED) }, + { "ch", MAKELANGID(LANG_GERMAN, SUBLANG_GERMAN_SWISS) }, + { "cm", MAKELANGID(LANG_FRENCH, 11) }, + { "cn", MAKELANGID(LANG_CHINESE, SUBLANG_DEFAULT) }, + { "cz", MAKELANGID(LANG_CZECH, SUBLANG_DEFAULT) }, + { "de", MAKELANGID(LANG_GERMAN, SUBLANG_DEFAULT) }, + { "dk", MAKELANGID(LANG_DANISH, SUBLANG_DEFAULT) }, + { "dz", MAKELANGID(LANG_TAMAZIGHT, SUBLANG_TAMAZIGHT_ALGERIA_LATIN) }, + { "ee", MAKELANGID(LANG_ESTONIAN, SUBLANG_DEFAULT) }, + { "epo", MAKELANGID(LANG_NEUTRAL, SUBLANG_CUSTOM_DEFAULT) }, + { "es", MAKELANGID(LANG_SPANISH, SUBLANG_DEFAULT) }, + { "et", MAKELANGID(LANG_AMHARIC, SUBLANG_DEFAULT) }, + { "fi", MAKELANGID(LANG_FINNISH, SUBLANG_DEFAULT) }, + { "fo", MAKELANGID(LANG_FAEROESE, SUBLANG_DEFAULT) }, + { "fr", MAKELANGID(LANG_FRENCH, SUBLANG_DEFAULT) }, + { "gb", MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_UK) }, + { "ge", MAKELANGID(LANG_GEORGIAN, SUBLANG_DEFAULT) }, + { "gh", MAKELANGID(LANG_ENGLISH, SUBLANG_CUSTOM_UNSPECIFIED) }, + { "gn", MAKELANGID(LANG_NEUTRAL, SUBLANG_CUSTOM_DEFAULT) }, + { "gr", MAKELANGID(LANG_GREEK, SUBLANG_DEFAULT) }, + { "hr", MAKELANGID(LANG_CROATIAN, SUBLANG_DEFAULT) }, + { "hu", MAKELANGID(LANG_HUNGARIAN, SUBLANG_DEFAULT) }, + { "id", MAKELANGID(LANG_INDONESIAN, SUBLANG_DEFAULT) }, + { "ie", MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_EIRE) }, + { "il", MAKELANGID(LANG_HEBREW, SUBLANG_DEFAULT) }, + { "in", MAKELANGID(LANG_HINDI, SUBLANG_DEFAULT) }, + { "iq", MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_IRAQ) }, + { "ir", MAKELANGID(LANG_PERSIAN, SUBLANG_DEFAULT) }, + { "is", MAKELANGID(LANG_ICELANDIC, SUBLANG_DEFAULT) }, + { "it", MAKELANGID(LANG_ITALIAN, SUBLANG_DEFAULT) }, + { "jp", MAKELANGID(LANG_JAPANESE, SUBLANG_DEFAULT) }, + { "ke", MAKELANGID(LANG_NEUTRAL, SUBLANG_CUSTOM_DEFAULT) }, + { "kg", MAKELANGID(LANG_KYRGYZ, SUBLANG_DEFAULT) }, + { "kh", MAKELANGID(LANG_KHMER, SUBLANG_DEFAULT) }, + { "kr", MAKELANGID(LANG_KOREAN, SUBLANG_DEFAULT) }, + { "kz", MAKELANGID(LANG_KAZAK, SUBLANG_DEFAULT) }, + { "la", MAKELANGID(LANG_LAO, SUBLANG_DEFAULT) }, + { "latam", MAKELANGID(LANG_SPANISH, SUBLANG_CUSTOM_UNSPECIFIED) }, + { "lk", MAKELANGID(LANG_SINHALESE, SUBLANG_DEFAULT) }, + { "lt", MAKELANGID(LANG_LITHUANIAN, SUBLANG_DEFAULT) }, + { "lv", MAKELANGID(LANG_LATVIAN, SUBLANG_DEFAULT) }, + { "ma", MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_MOROCCO) }, + { "mao", MAKELANGID(LANG_MAORI, SUBLANG_DEFAULT) }, + { "md", MAKELANGID(LANG_ROMANIAN, SUBLANG_CUSTOM_UNSPECIFIED) }, + { "me", MAKELANGID(LANG_SERBIAN, SUBLANG_SERBIAN_MONTENEGRO_LATIN) }, + { "mk", MAKELANGID(LANG_MACEDONIAN, SUBLANG_DEFAULT) }, + { "ml", MAKELANGID(LANG_NEUTRAL, SUBLANG_CUSTOM_DEFAULT) }, + { "mm", MAKELANGID(0x55 /*LANG_BURMESE*/, SUBLANG_DEFAULT) }, + { "mn", MAKELANGID(LANG_MONGOLIAN, SUBLANG_DEFAULT) }, + { "mt", MAKELANGID(LANG_MALTESE, SUBLANG_DEFAULT) }, + { "mv", MAKELANGID(LANG_DIVEHI, SUBLANG_DEFAULT) }, + { "my", MAKELANGID(LANG_MALAY, SUBLANG_DEFAULT) }, + { "ng", MAKELANGID(LANG_ENGLISH, SUBLANG_CUSTOM_UNSPECIFIED) }, + { "nl", MAKELANGID(LANG_DUTCH, SUBLANG_DEFAULT) }, + { "no", MAKELANGID(LANG_NORWEGIAN, SUBLANG_DEFAULT) }, + { "np", MAKELANGID(LANG_NEPALI, SUBLANG_DEFAULT) }, + { "ph", MAKELANGID(LANG_FILIPINO, SUBLANG_DEFAULT) }, + { "pk", MAKELANGID(LANG_URDU, SUBLANG_DEFAULT) }, + { "pl", MAKELANGID(LANG_POLISH, SUBLANG_DEFAULT) }, + { "pt", MAKELANGID(LANG_PORTUGUESE, SUBLANG_DEFAULT) }, + { "ro", MAKELANGID(LANG_ROMANIAN, SUBLANG_DEFAULT) }, + { "rs", MAKELANGID(LANG_SERBIAN, SUBLANG_SERBIAN_LATIN) }, + { "ru", MAKELANGID(LANG_RUSSIAN, SUBLANG_DEFAULT) }, + { "se", MAKELANGID(LANG_SWEDISH, SUBLANG_DEFAULT) }, + { "si", MAKELANGID(LANG_SLOVENIAN, SUBLANG_DEFAULT) }, + { "sk", MAKELANGID(LANG_SLOVAK, SUBLANG_DEFAULT) }, + { "sn", MAKELANGID(LANG_WOLOF, SUBLANG_DEFAULT) }, + { "sy", MAKELANGID(LANG_SYRIAC, SUBLANG_DEFAULT) }, + { "tg", MAKELANGID(LANG_FRENCH, SUBLANG_CUSTOM_UNSPECIFIED) }, + { "th", MAKELANGID(LANG_THAI, SUBLANG_DEFAULT) }, + { "tj", MAKELANGID(LANG_TAJIK, SUBLANG_DEFAULT) }, + { "tm", MAKELANGID(LANG_TURKMEN, SUBLANG_DEFAULT) }, + { "tr", MAKELANGID(LANG_TURKISH, SUBLANG_DEFAULT) }, + { "tw", MAKELANGID(LANG_CHINESE, SUBLANG_CUSTOM_UNSPECIFIED) }, + { "tz", MAKELANGID(LANG_SWAHILI, SUBLANG_CUSTOM_UNSPECIFIED) }, + { "ua", MAKELANGID(LANG_UKRAINIAN, SUBLANG_DEFAULT) }, + { "us", MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT) }, + { "uz", MAKELANGID(LANG_UZBEK, 2) }, + { "vn", MAKELANGID(LANG_VIETNAMESE, SUBLANG_DEFAULT) }, + { "za", MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_SOUTH_AFRICA) }, +}; + +static int layout_id_map_cmp( const void *key, const void *element ) +{ + const struct layout_id_map_entry *entry = element; + return strcmp( key, entry->name ); +} + +static LANGID langid_from_xkb_layout( const char *layout ) +{ + struct layout_id_map_entry *entry; + + entry = bsearch( layout, layout_ids, ARRAY_SIZE(layout_ids), sizeof(*layout_ids), layout_id_map_cmp ); + if (entry) return entry->langid; + + FIXME( "Unknown layout %s\n", debugstr_a(layout) ); + return MAKELANGID(LANG_NEUTRAL, SUBLANG_CUSTOM_UNSPECIFIED); +}; + /* fuzzy layout detection through keysym / keycode matching, kbd_section must be held */ static void detect_keyboard_layout( Display *display, XModifierKeymap *modmap, unsigned int xkb_group ) { @@ -1789,6 +1960,7 @@ void init_keyboard_layouts( Display *display ) XkbStateRec xkb_state; XModifierKeymap *mmp; XkbDescRec *xkb_desc; + LANGID xkb_lang = 0; Status status; KeyCode *kcp; @@ -1834,7 +2006,17 @@ void init_keyboard_layouts( Display *display ) if (!XGetAtomNames( display, xkb_desc->names->groups, count, names )) count = 0; for (int i = 0; i < count; i++) { - TRACE( "group %u name %s\n", i, debugstr_a(names[i]) ); + const char *layout, *variant = NULL; + LANGID lang; + + if (names[i] && find_xkb_layout_variant( names[i], &layout, &variant )) + { + lang = langid_from_xkb_layout( layout ); + if (i == xkb_group) xkb_lang = lang; + + TRACE( "Found group %u with name %s -> layout %s:%s, lang %04x\n", i, debugstr_a(names[i]), + debugstr_a(layout), debugstr_a(variant), lang ); + } XFree( names[i] ); } @@ -1844,6 +2026,10 @@ void init_keyboard_layouts( Display *display ) detect_keyboard_layout( display, mmp, xkb_group ); XFreeModifiermap( mmp ); + if (xkb_lang && xkb_lang != main_key_tab[kbd_layout].lcid) + WARN( "Xkb langid %04x differs from detected langid %04x\n", + xkb_lang, main_key_tab[kbd_layout].lcid ); + init_keycode_mappings( display ); pthread_mutex_unlock( &kbd_mutex ); @@ -1892,6 +2078,37 @@ BOOL X11DRV_MappingNotify( HWND dummy, XEvent *event ) void x11drv_init_keyboard( Display *display ) { XkbUseExtension( display, NULL, NULL ); + +#ifdef SONAME_LIBXKBREGISTRY + if (!(xkbregistry_handle = dlopen( SONAME_LIBXKBREGISTRY, RTLD_NOW ))) + { + ERR( "Failed to load %s\n", SONAME_LIBXKBREGISTRY ); + goto xkbregistry_init_done; + } +#define LOAD_FUNCPTR( f ) \ + if (!(p_##f = dlsym( xkbregistry_handle, #f ))) \ + { \ + ERR( "Failed to find " #f "\n" ); \ + dlclose( xkbregistry_handle ); \ + goto xkbregistry_init_done; \ + } + + LOAD_FUNCPTR(rxkb_context_new) + LOAD_FUNCPTR(rxkb_context_parse_default_ruleset) + LOAD_FUNCPTR(rxkb_layout_first) + LOAD_FUNCPTR(rxkb_layout_get_description) + LOAD_FUNCPTR(rxkb_layout_get_name) + LOAD_FUNCPTR(rxkb_layout_get_variant) + LOAD_FUNCPTR(rxkb_layout_next) +#undef LOAD_FUNCPTR + + if (!(rxkb_context = p_rxkb_context_new( RXKB_CONTEXT_NO_FLAGS )) || + !p_rxkb_context_parse_default_ruleset( rxkb_context )) + ERR( "Failed to parse default Xkb ruleset\n" ); + +xkbregistry_init_done: +#endif + init_keyboard_layouts( display ); } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10550
Rémi Bernon (@rbernon) commented about dlls/winex11.drv/keyboard.c:
}; static unsigned kbd_layout=0; /* index into above table of layouts */ +#ifdef SONAME_LIBXKBREGISTRY +static struct rxkb_context *rxkb_context; + +static void *xkbregistry_handle; +#define MAKE_FUNCPTR(f) static typeof(f) * p_##f; +MAKE_FUNCPTR(rxkb_context_new) +MAKE_FUNCPTR(rxkb_context_parse_default_ruleset) +MAKE_FUNCPTR(rxkb_layout_first) +MAKE_FUNCPTR(rxkb_layout_get_description) +MAKE_FUNCPTR(rxkb_layout_get_name) +MAKE_FUNCPTR(rxkb_layout_get_variant) +MAKE_FUNCPTR(rxkb_layout_next) +#undef MAKE_FUNCPTR +#endif Well this rather requires HAVE_XKBCOMMON_XKBREGISTRY_H more than SONAME_LIBXKBREGISTRY.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10550#note_136791
Rémi Bernon (@rbernon) commented about dlls/winex11.drv/keyboard.c:
if (!XGetAtomNames( display, xkb_desc->names->groups, count, names )) count = 0; for (int i = 0; i < count; i++) { - TRACE( "group %u name %s\n", i, debugstr_a(names[i]) ); + const char *layout, *variant = NULL; + LANGID lang; + + if (names[i] && find_xkb_layout_variant( names[i], &layout, &variant )) + { + lang = langid_from_xkb_layout( layout ); + if (i == xkb_group) xkb_lang = lang; + + TRACE( "Found group %u with name %s -> layout %s:%s, lang %04x\n", i, debugstr_a(names[i]), + debugstr_a(layout), debugstr_a(variant), lang ); + } XFree( names[i] );
I wondered before but now that you added one nesting level, I think it'd be better to avoid it and the question is: can names[i] be NULL? If yes I think a simple `if (!names[i]) continue;` is more readable than an extra indentation level. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10550#note_136792
Rémi Bernon (@rbernon) commented about dlls/winex11.drv/keyboard.c:
+ + LOAD_FUNCPTR(rxkb_context_new) + LOAD_FUNCPTR(rxkb_context_parse_default_ruleset) + LOAD_FUNCPTR(rxkb_layout_first) + LOAD_FUNCPTR(rxkb_layout_get_description) + LOAD_FUNCPTR(rxkb_layout_get_name) + LOAD_FUNCPTR(rxkb_layout_get_variant) + LOAD_FUNCPTR(rxkb_layout_next) +#undef LOAD_FUNCPTR + + if (!(rxkb_context = p_rxkb_context_new( RXKB_CONTEXT_NO_FLAGS )) || + !p_rxkb_context_parse_default_ruleset( rxkb_context )) + ERR( "Failed to parse default Xkb ruleset\n" ); + +xkbregistry_init_done: +#endif Some parts more than others but this now also requires both SONAME_LIBXKBREGISTRY and HAVE_XKBCOMMON_XKBREGISTRY_H.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10550#note_136793
Rémi Bernon (@rbernon) commented about dlls/winex11.drv/keyboard.c:
+ { + *layout = p_rxkb_layout_get_name( iter ); + *variant = p_rxkb_layout_get_variant( iter ); + return TRUE; + } + } + + WARN( "Unknown Xkb layout name %s\n", debugstr_a(name) ); + } + else + WARN( "libxkbregistry not available, falling back to fuzzy layout detection\n" ); +#else + WARN( "libxkbregistry support not compiled in, falling back to fuzzy layout detection\n" ); +#endif + return FALSE; +} Fwiw here as well I find the extra nesting level just to handle the uncommon case quite bad to read. It also requires HAVE_XKBCOMMON_XKBREGISTRY_H rather than SONAME_LIBXKBREGISTRY. What about:
```suggestion:-27+0 static BOOL find_xkb_layout_variant( const char *name, const char **layout, const char **variant ) { #ifdef HAVE_XKBCOMMON_XKBREGISTRY_H struct rxkb_layout *iter; if (!rxkb_context) WARN( "libxkbregistry not found, falling back to fuzzy layout detection\n" ); else for (iter = p_rxkb_layout_first( rxkb_context ); iter; iter = p_rxkb_layout_next( iter )) { const char *desc = p_rxkb_layout_get_description( iter ); if (desc && !strcmp( name, desc )) { *layout = p_rxkb_layout_get_name( iter ); *variant = p_rxkb_layout_get_variant( iter ); return TRUE; } } WARN( "Unknown Xkb layout name %s\n", debugstr_a(name) ); #else WARN( "libxkbregistry support not compiled in, falling back to fuzzy layout detection\n" ); #endif return FALSE; } ``` -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10550#note_136794
On Fri Apr 17 17:19:04 2026 +0000, Rémi Bernon wrote:
Well this rather requires HAVE_XKBCOMMON_XKBREGISTRY_H more than SONAME_LIBXKBREGISTRY. Once the initialization of those pointers is keyed onto `SONAME_LIBXKBREGISTRY`, this has to as well, otherwise if the header is present but the library isn't (maybe because the 32-bit version isn't around) you'd get a bunch of unused static variable declarations. Probably an uncommon case, especially with the new wow64 becoming the standard, but it's a relatively sane setup.
I think the nicest way of handling this is to check just `HAVE_XKBCOMMON_XKBREGISTRY_H` but undef it when there is no `SONAME_LIBXKBREGISTRY`. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10550#note_136802
On Fri Apr 17 17:19:05 2026 +0000, Rémi Bernon wrote:
I wondered before but now that you added one nesting level, I think it'd be better to avoid it and the question is: can names[i] be NULL? If yes I think a simple `if (!names[i]) continue;` is more readable than an extra indentation level. I think I've seen it on vncserver, I'll check again.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10550#note_136803
On Fri Apr 17 17:34:07 2026 +0000, Matteo Bruni wrote:
Once the initialization of those pointers is keyed onto `SONAME_LIBXKBREGISTRY`, this has to as well, otherwise if the header is present but the library isn't (maybe because the 32-bit version isn't around) you'd get a bunch of unused static variable declarations. Probably an uncommon case, especially with the new wow64 becoming the standard, but it's a relatively sane setup. I think the nicest way of handling this is to check just `HAVE_XKBCOMMON_XKBREGISTRY_H` but undef it when there is no `SONAME_LIBXKBREGISTRY`. Or, actually, the other way around, making `SONAME_LIBXKBREGISTRY` only be defined if the header is present.
if test "x$have_x$with_wayland" != "xnono"
then
WINE_PACKAGE_FLAGS(XKBREGISTRY,[xkbregistry],,,,
[AC_CHECK_HEADER([xkbcommon/xkbregistry.h],
[WINE_CHECK_SONAME(xkbregistry,rxkb_context_new,[:],[XKBREGISTRY_LIBS=""],[$XKBREGISTRY_LIBS])])])
fi
Does that look alright to you? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10550#note_136807
On Fri Apr 17 17:50:13 2026 +0000, Matteo Bruni wrote:
Or, actually, the other way around, making `SONAME_LIBXKBREGISTRY` only be defined if the header is present. ``` if test "x$have_x$with_wayland" != "xnono" then WINE_PACKAGE_FLAGS(XKBREGISTRY,[xkbregistry],,,, [AC_CHECK_HEADER([xkbcommon/xkbregistry.h], [WINE_CHECK_SONAME(xkbregistry,rxkb_context_new,[:],[XKBREGISTRY_LIBS=""],[$XKBREGISTRY_LIBS])])]) fi ``` Does that look alright to you? `AC_CHECK_HEADER` doesn't define the HAVE_XKBCOMMON_XKBREGISTRY_H macro, you need `AC_CHECK_HEADERS` and it doesn't let you do things on success / failure, you'd have to manually write a test.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10550#note_136808
On Fri Apr 17 17:52:24 2026 +0000, Rémi Bernon wrote:
`AC_CHECK_HEADER` doesn't define the HAVE_XKBCOMMON_XKBREGISTRY_H macro, you need `AC_CHECK_HEADERS` and it doesn't let you do things on success / failure, you'd have to manually write a test. `AC_CHECK_HEADER` seems to work fine here, but regardless, the idea is to use `#ifdef SONAME_LIBXKBREGISTRY` everywhere, since with that it implies both the .h and the .so are available at build time.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10550#note_136809
participants (4)
-
Matteo Bruni -
Matteo Bruni (@Mystral) -
Rémi Bernon -
Rémi Bernon (@rbernon)