[PATCH v3 0/5] MR10689: winex11: Keyboard layouts rework, part 1
Same as https://gitlab.winehq.org/wine/wine/-/merge_requests/10550 with misc tweaks. -- v3: winex11: Decode current keyboard layout from the Xkb group name. https://gitlab.winehq.org/wine/wine/-/merge_requests/10689
From: Rémi Bernon <rbernon@codeweavers.com> --- dlls/winex11.drv/keyboard.c | 71 ++++++++++++++++------------------ dlls/winex11.drv/x11drv.h | 2 +- dlls/winex11.drv/x11drv_main.c | 3 +- 3 files changed, 36 insertions(+), 40 deletions(-) diff --git a/dlls/winex11.drv/keyboard.c b/dlls/winex11.drv/keyboard.c index e2281bef017..a432859ae69 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 */ + for (int 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/10689
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 a432859ae69..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 (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 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/10689
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/10689
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/10689
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 | 4 +- dlls/winex11.drv/keyboard.c | 178 ++++++++++++++++++++++++++++++++++- 3 files changed, 186 insertions(+), 5 deletions(-) diff --git a/configure.ac b/configure.ac index f35ac4376d9..1ff84000e96 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]) + AC_CHECK_LIB(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..31768e670da 100644 --- a/dlls/winex11.drv/Makefile.in +++ b/dlls/winex11.drv/Makefile.in @@ -1,7 +1,7 @@ MODULE = winex11.drv UNIXLIB = winex11.so -UNIX_CFLAGS = $(X_CFLAGS) -UNIX_LIBS = -lwin32u $(X_LIBS) $(PTHREAD_LIBS) -lm +UNIX_CFLAGS = $(X_CFLAGS) $(XKBREGISTRY_CFLAGS) +UNIX_LIBS = -lwin32u $(X_LIBS) $(XKBREGISTRY_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..414bb9355d9 100644 --- a/dlls/winex11.drv/keyboard.c +++ b/dlls/winex11.drv/keyboard.c @@ -36,6 +36,10 @@ #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> @@ -939,6 +943,9 @@ static const struct { {0, NULL, NULL, NULL, NULL} /* sentinel */ }; static unsigned kbd_layout=0; /* index into above table of layouts */ +#ifdef HAVE_XKBCOMMON_XKBREGISTRY_H +static struct rxkb_context *rxkb_context; +#endif /* maybe more of these scancodes should be extended? */ /* extended must be set for ALT_R, CTRL_R, @@ -1426,6 +1433,152 @@ 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 HAVE_XKBCOMMON_XKBREGISTRY_H + struct rxkb_layout *iter; + + for (iter = rxkb_layout_first( rxkb_context ); iter; iter = rxkb_layout_next( iter )) + { + const char *desc = rxkb_layout_get_description( iter ); + + if (desc && !strcmp( name, desc )) + { + *layout = rxkb_layout_get_name( iter ); + *variant = 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; +} + +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 +1942,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 +1988,15 @@ 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 )) layout = "us"; + 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 +2006,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 +2058,16 @@ BOOL X11DRV_MappingNotify( HWND dummy, XEvent *event ) void x11drv_init_keyboard( Display *display ) { XkbUseExtension( display, NULL, NULL ); + +#ifdef HAVE_XKBCOMMON_XKBREGISTRY_H + 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; + } +#endif + init_keyboard_layouts( display ); } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10689
Matteo Bruni (@Mystral) commented about dlls/winex11.drv/keyboard.c:
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 */ + for (int i = 0; i < 8; i += 1) /* There are 8 modifier keys */
This (until the next patch) shadows the other `i` declaration above, which seems unintended. At some point (relatively soon) I'm going to have another loop in the same function which could use the same `i`, but doesn't have to. Do you prefer if I get rid of the earlier declaration at the top of the function or that I revert this one change? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10689#note_136775
Matteo Bruni (@Mystral) commented about dlls/winex11.drv/keyboard.c:
#include <X11/Xutil.h> #include <X11/XKBlib.h>
+#ifdef HAVE_XKBCOMMON_XKBREGISTRY_H +#include <xkbcommon/xkbregistry.h> +#endif This is definitely an improvement, thanks!
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10689#note_136777
Matteo Bruni (@Mystral) commented about dlls/winex11.drv/keyboard.c:
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 ); +
FWIW in my branch I'm going to get rid of this sanity check pretty soon after, this is mostly a placeholder. Is that alright? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10689#note_136776
Matteo Bruni (@Mystral) commented about dlls/winex11.drv/keyboard.c:
void x11drv_init_keyboard( Display *display ) { XkbUseExtension( display, NULL, NULL ); + +#ifdef HAVE_XKBCOMMON_XKBREGISTRY_H + 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; + } +#endif I'm less sure about this. I guess the main question is: do we want to dynamically load xkbregistry and call `rxkb_` functions through function pointers? Or in other words, do we want to allow xkbregistry to be an optional runtime dependency for winex11?
I think that was my original intention but I forgot and ended up with those `#ifdef SONAME_LIBXKBREGISTRY` which aren't helpful... -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10689#note_136778
Thanks for the updates! I'm going to further tweak it slightly and push onto !10550, then work on rebasing the rest of the branch on top of it. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10689#note_136779
On Fri Apr 17 14:36:45 2026 +0000, Matteo Bruni wrote:
I'm less sure about this. I guess the main question is: do we want to dynamically load xkbregistry and call `rxkb_` functions through function pointers? Or in other words, do we want to allow xkbregistry to be an optional runtime dependency for winex11? I think that was my original intention but I forgot and ended up with those `#ifdef SONAME_LIBXKBREGISTRY` which aren't helpful... It is a required dependency for the wayland driver, I'm not sure how we decide to prefer runtime vs link time resolution in general, it seems to me that link time is overall more convenient.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10689#note_136781
On Fri Apr 17 14:36:45 2026 +0000, Matteo Bruni wrote:
This (until the next patch) shadows the other `i` declaration above, which seems unintended. At some point (relatively soon) I'm going to have another loop in the same function which could use the same `i`, but doesn't have to. Do you prefer if I get rid of the earlier declaration at the top of the function or that I revert this one change? It doesn't matter much, I like the ability we have now to inline loop index declaration when they are only needed for the iteration but I don't really mind. It was mostly only to better show that code was indeed moved around in next patch.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10689#note_136782
On Fri Apr 17 14:36:45 2026 +0000, Matteo Bruni wrote:
FWIW in my branch I'm going to get rid of this sanity check pretty soon after, this is mostly a placeholder. Is that alright? Sure.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10689#note_136783
On Fri Apr 17 14:41:54 2026 +0000, Rémi Bernon wrote:
It is a required dependency for the wayland driver, I'm not sure how we decide to prefer runtime vs link time resolution in general, it seems to me that link time is overall more convenient. Yeah, but I'm kinda feeling like we're generally more conservative with winex11 and it's just a few function calls, localized in 2 places. I'm giving it a try now, it doesn't seem much of a hassle.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10689#note_136784
This merge request was closed by Rémi Bernon. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10689
participants (4)
-
Matteo Bruni -
Matteo Bruni (@Mystral) -
Rémi Bernon -
Rémi Bernon (@rbernon)