@afrantzis Can't seem to be possible to add you as a reviewer, probably you need to request "Access" to the wine/wine project (near the top of the project page).
What do you think of something like that? It is very different from the current winex11 code, but I believe it will match keyboard layouts in a much more accurate way, and it's also IMO much simpler. If that works well with Wayland, I think it could be a good hint that it might work as well in X11 and make a case for my other MR to use that approach there.
I was a bit annoyed that it doesn't seem possible to retrieve the Xkb "layout:variant" string here, but only the layout description, so I had to use xkbregistry to match it back to the known layouts.
It is mostly only there to provide a more accurate HKL value (which should match the layout langid), and scan to vk mapping table, and custom layouts should still work. The lang would be neutral then, and the scan to vk table is QWERTY by default, which is the most common case, and doesn't enforce any vkey -> unicode mapping anyway. So, if the xkbregistry dependency is an issue we could probably make it optional and dynamically loaded, and skip the langid and scan to vk specialized mappings.
-- v3: winewayland.drv: Implement Xkb composition using ImeProcessKey. winewayland.drv: Translate Xkb keyboard layouts to KBDTABLES. win32u: Allow KBDTABLES conversion from CTLR + ALT to WCHAR. win32u: Force US layout in ToUnicode when CTRL is pressed. win32u: Introduce KbdLayerDescriptor user driver entry. win32u: Avoid accessing NULL key name string pointer. winewayland.drv: Enumerate Xkb layouts and create matching HKL. winewayland.drv: Handle and parse Xkb keymap events. win32u: Implement opt-in auto-repeat for WM_(SYS)KEYDOWN messages. winewayland.drv: Configure win32u keyboard repeat delay and speed. winewayland.drv: Basic handling of Wayland keyboard events. gitlab: Install libxkbcommon and libxkbregistry dependencies.
From: Rémi Bernon rbernon@codeweavers.com
--- tools/gitlab/image.docker | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/tools/gitlab/image.docker b/tools/gitlab/image.docker index 50607d13872..8ff9055df2f 100644 --- a/tools/gitlab/image.docker +++ b/tools/gitlab/image.docker @@ -42,6 +42,8 @@ RUN export DEBIAN_FRONTEND=noninteractive; \ libxext-dev:amd64 libxext-dev:i386 \ libxi-dev:amd64 libxi-dev:i386 \ libxinerama-dev:amd64 libxinerama-dev:i386 \ + libxkbcommon-dev:amd64 libxkbcommon-dev:i386 \ + libxkbregistry-dev:amd64 libxkbregistry-dev:i386 \ libxrandr-dev:amd64 libxrandr-dev:i386 \ libxrender-dev:amd64 libxrender-dev:i386 \ libxxf86vm-dev:amd64 libxxf86vm-dev:i386 \
From: Alexandros Frantzis alexandros.frantzis@collabora.com
Handle Wayland keyboard events and translate them to Windows events, currently using a hardcoded US key mapping. --- configure.ac | 5 +- dlls/winewayland.drv/Makefile.in | 5 +- dlls/winewayland.drv/wayland.c | 5 + dlls/winewayland.drv/wayland_keyboard.c | 245 ++++++++++++++++++++++++ dlls/winewayland.drv/wayland_surface.c | 3 + dlls/winewayland.drv/waylanddrv.h | 27 +++ include/config.h.in | 3 + 7 files changed, 290 insertions(+), 3 deletions(-) create mode 100644 dlls/winewayland.drv/wayland_keyboard.c
diff --git a/configure.ac b/configure.ac index 599d18eee43..0975f6fa565 100644 --- a/configure.ac +++ b/configure.ac @@ -1372,8 +1372,11 @@ then [AC_PATH_PROG(WAYLAND_SCANNER,wayland-scanner, [`test -n "$PKG_CONFIG" && $PKG_CONFIG --variable=wayland_scanner wayland-scanner 2>/dev/null`])], [WAYLAND_CLIENT_LIBS=""],[$WAYLAND_CLIENT_LIBS])])]) + WINE_PACKAGE_FLAGS(XKBCOMMON,[xkbcommon],,,, + [AC_CHECK_HEADERS([xkbcommon/xkbcommon.h]) + AC_CHECK_LIB(xkbcommon,xkb_context_new,[:],[XKBCOMMON_LIBS=""],[$XKBCOMMON_LIBS])]) fi -WINE_NOTICE_WITH(wayland, [test -z "$WAYLAND_CLIENT_LIBS" -o -z "$WAYLAND_SCANNER"], +WINE_NOTICE_WITH(wayland, [test -z "$WAYLAND_CLIENT_LIBS" -o -z "$WAYLAND_SCANNER" -o -z "$XKBCOMMON_LIBS"], [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 e1019ad8348..4fbc516266e 100644 --- a/dlls/winewayland.drv/Makefile.in +++ b/dlls/winewayland.drv/Makefile.in @@ -1,13 +1,14 @@ MODULE = winewayland.drv UNIXLIB = winewayland.so -UNIX_CFLAGS = $(WAYLAND_CLIENT_CFLAGS) -UNIX_LIBS = -lwin32u $(WAYLAND_CLIENT_LIBS) $(PTHREAD_LIBS) -lm +UNIX_CFLAGS = $(WAYLAND_CLIENT_CFLAGS) $(XKBCOMMON_CFLAGS) +UNIX_LIBS = -lwin32u $(WAYLAND_CLIENT_LIBS) $(XKBCOMMON_LIBS) $(PTHREAD_LIBS) -lm
SOURCES = \ display.c \ dllmain.c \ version.rc \ wayland.c \ + wayland_keyboard.c \ wayland_output.c \ wayland_pointer.c \ wayland_surface.c \ diff --git a/dlls/winewayland.drv/wayland.c b/dlls/winewayland.drv/wayland.c index b8c69a105cb..31c225e76d7 100644 --- a/dlls/winewayland.drv/wayland.c +++ b/dlls/winewayland.drv/wayland.c @@ -66,6 +66,11 @@ static void wl_seat_handle_capabilities(void *data, struct wl_seat *seat, wayland_pointer_init(wl_seat_get_pointer(seat)); else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && process_wayland.pointer.wl_pointer) wayland_pointer_deinit(); + + if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !process_wayland.keyboard.wl_keyboard) + wayland_keyboard_init(&process_wayland.keyboard, &process_wayland, wl_seat_get_keyboard(seat)); + else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && process_wayland.keyboard.wl_keyboard) + wayland_keyboard_deinit(&process_wayland.keyboard); }
static void wl_seat_handle_name(void *data, struct wl_seat *seat, const char *name) diff --git a/dlls/winewayland.drv/wayland_keyboard.c b/dlls/winewayland.drv/wayland_keyboard.c new file mode 100644 index 00000000000..837d307ec1e --- /dev/null +++ b/dlls/winewayland.drv/wayland_keyboard.c @@ -0,0 +1,245 @@ +/* + * Keyboard related functions + * + * Copyright 2020 Alexandros Frantzis for Collabora Ltd. + * Copyright 2023 Rémi Bernon for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#if 0 +#pragma makedep unix +#endif + +#include "config.h" + +#include <linux/input.h> +#undef SW_MAX /* Also defined in winuser.rh */ +#include <unistd.h> + +#include "waylanddrv.h" +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(keyboard); +WINE_DECLARE_DEBUG_CHANNEL(key); + +static WORD key2scan(UINT key) +{ + /* base keys can be mapped directly */ + if (key <= KEY_KPDOT) return key; + + /* map keys found in KBDTABLES definitions (Txx Xxx Yxx macros) */ + switch (key) + { + case 84 /* ISO_Level3_Shift */: return 0x005a; /* T5A / VK_OEM_WSCTRL */ + case KEY_SYSRQ: return 0x0054; /* T54 / VK_SNAPSHOT */ + case KEY_102ND: return 0x0056; /* T56 / VK_OEM_102 */ + case KEY_F11: return 0x0057; /* T57 / VK_F11 */ + case KEY_F12: return 0x0058; /* T58 / VK_F12 */ + case KEY_LINEFEED: return 0x0059; /* T59 / VK_CLEAR */ + case KEY_EXIT: return 0x005b; /* T5B / VK_OEM_FINISH */ + case KEY_OPEN: return 0x005c; /* T5C / VK_OEM_JUMP */ + /* case KEY_EREOF: return 0x005d; */ /* T5D / VK_EREOF */ + /* case KEY_OEM_BACKTAB: return 0x005e; */ /* T5E / VK_OEM_BACKTAB */ + case KEY_COMPOSE: return 0x005f; /* T5F / VK_OEM_AUTO */ + case KEY_SCALE: return 0x0062; /* T62 / VK_ZOOM */ + case KEY_HELP: return 0x0063; /* T63 / VK_HELP */ + case KEY_F13: return 0x0064; /* T64 / VK_F13 */ + case KEY_F14: return 0x0065; /* T65 / VK_F14 */ + case KEY_F15: return 0x0066; /* T66 / VK_F15 */ + case KEY_F16: return 0x0067; /* T67 / VK_F16 */ + case KEY_F17: return 0x0068; /* T68 / VK_F17 */ + case KEY_F18: return 0x0069; /* T69 / VK_F18 */ + case KEY_F19: return 0x006a; /* T6A / VK_F19 */ + case KEY_F20: return 0x006b; /* T6B / VK_F20 */ + case KEY_F21: return 0x006c; /* T6C / VK_F21 */ + case KEY_F22: return 0x006d; /* T6D / VK_F22 */ + case KEY_F23: return 0x006e; /* T6E / VK_F23 */ + /* case KEY_OEM_PA3: return 0x006f; */ /* T6F / VK_OEM_PA3 */ + case KEY_COMPUTER: return 0x0071; /* T71 / VK_OEM_RESET */ + /* case KEY_ABNT_C1: return 0x0073; */ /* T73 / VK_ABNT_C1 */ + case KEY_F24: return 0x0076; /* T76 / VK_F24 */ + case KEY_KPPLUSMINUS: return 0x007b; /* T7B / VK_OEM_PA1 */ + case KEY_TAB: return 0x007c; /* T7C / VK_TAB */ + /* case KEY_ABNT_C2: return 0x007e; */ /* T7E / VK_ABNT_C2 */ + /* case KEY_OEM_PA2: return 0x007f; */ /* T7F / VK_OEM_PA2 */ + case KEY_PREVIOUSSONG: return 0x0110; /* X10 / VK_MEDIA_PREV_TRACK */ + case KEY_NEXTSONG: return 0x0119; /* X19 / VK_MEDIA_NEXT_TRACK */ + case KEY_KPENTER: return 0x011c; /* X1C / VK_RETURN */ + case KEY_RIGHTCTRL: return 0x011d; /* X1D / VK_RCONTROL */ + case KEY_MUTE: return 0x0120; /* X20 / VK_VOLUME_MUTE */ + case KEY_PROG2: return 0x0121; /* X21 / VK_LAUNCH_APP2 */ + case KEY_PLAYPAUSE: return 0x0122; /* X22 / VK_MEDIA_PLAY_PAUSE */ + case KEY_STOPCD: return 0x0124; /* X24 / VK_MEDIA_STOP */ + case KEY_VOLUMEDOWN: return 0x012e; /* X2E / VK_VOLUME_DOWN */ + case KEY_VOLUMEUP: return 0x0130; /* X30 / VK_VOLUME_UP */ + case KEY_HOMEPAGE: return 0x0132; /* X32 / VK_BROWSER_HOME */ + case KEY_KPSLASH: return 0x0135; /* X35 / VK_DIVIDE */ + case KEY_PRINT: return 0x0137; /* X37 / VK_SNAPSHOT */ + case KEY_RIGHTALT: return 0x0138; /* X38 / VK_RMENU */ + case KEY_CANCEL: return 0x0146; /* X46 / VK_CANCEL */ + case KEY_HOME: return 0x0147; /* X47 / VK_HOME */ + case KEY_UP: return 0x0148; /* X48 / VK_UP */ + case KEY_PAGEUP: return 0x0149; /* X49 / VK_PRIOR */ + case KEY_LEFT: return 0x014b; /* X4B / VK_LEFT */ + case KEY_RIGHT: return 0x014d; /* X4D / VK_RIGHT */ + case KEY_END: return 0x014f; /* X4F / VK_END */ + case KEY_DOWN: return 0x0150; /* X50 / VK_DOWN */ + case KEY_PAGEDOWN: return 0x0151; /* X51 / VK_NEXT */ + case KEY_INSERT: return 0x0152; /* X52 / VK_INSERT */ + case KEY_DELETE: return 0x0153; /* X53 / VK_DELETE */ + case KEY_LEFTMETA: return 0x015b; /* X5B / VK_LWIN */ + case KEY_RIGHTMETA: return 0x015c; /* X5C / VK_RWIN */ + case KEY_MENU: return 0x015d; /* X5D / VK_APPS */ + case KEY_POWER: return 0x015e; /* X5E / VK_POWER */ + case KEY_SLEEP: return 0x015f; /* X5F / VK_SLEEP */ + case KEY_FIND: return 0x0165; /* X65 / VK_BROWSER_SEARCH */ + case KEY_BOOKMARKS: return 0x0166; /* X66 / VK_BROWSER_FAVORITES */ + case KEY_REFRESH: return 0x0167; /* X67 / VK_BROWSER_REFRESH */ + case KEY_STOP: return 0x0168; /* X68 / VK_BROWSER_STOP */ + case KEY_FORWARD: return 0x0169; /* X69 / VK_BROWSER_FORWARD */ + case KEY_BACK: return 0x016a; /* X6A / VK_BROWSER_BACK */ + case KEY_PROG1: return 0x016b; /* X6B / VK_LAUNCH_APP1 */ + case KEY_MAIL: return 0x016c; /* X6C / VK_LAUNCH_MAIL */ + case KEY_MEDIA: return 0x016d; /* X6D / VK_LAUNCH_MEDIA_SELECT */ + case KEY_PAUSE: return 0x021d; /* Y1D / VK_PAUSE */ + } + + /* otherwise just make up some extended scancode */ + return 0x200 | (key & 0x7f); +} + +/********************************************************************** + * Keyboard handling + */ + +static void keyboard_handle_keymap(void *data, struct wl_keyboard *wl_keyboard, + uint32_t format, int fd, uint32_t size) +{ + FIXME("stub!\n"); + close(fd); +} + +static void keyboard_handle_enter(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, struct wl_surface *wl_surface, + struct wl_array *keys) +{ + struct wayland_keyboard *keyboard = &process_wayland.keyboard; + HWND hwnd; + + if (!wl_surface) return; + + /* The wl_surface user data remains valid and immutable for the whole + * lifetime of the object, so it's safe to access without locking. */ + hwnd = wl_surface_get_user_data(wl_surface); + TRACE("serial=%u hwnd=%p\n", serial, hwnd); + + keyboard->focused_hwnd = hwnd; + NtUserSetForegroundWindow(hwnd); +} + +static void keyboard_handle_leave(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, struct wl_surface *wl_surface) +{ + struct wayland_keyboard *keyboard = &process_wayland.keyboard; + HWND hwnd; + + if (!wl_surface) return; + + /* The wl_surface user data remains valid and immutable for the whole + * lifetime of the object, so it's safe to access without locking. */ + hwnd = wl_surface_get_user_data(wl_surface); + TRACE("serial=%u hwnd=%p\n", serial, hwnd); + + if (keyboard->focused_hwnd == hwnd) + keyboard->focused_hwnd = NULL; + + /* FIXME: update foreground window as well */ +} + +static void keyboard_handle_key(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, uint32_t time, uint32_t key, + uint32_t state) +{ + struct wayland_keyboard *keyboard = &process_wayland.keyboard; + UINT scan = key2scan(key); + INPUT input = {0}; + HWND hwnd; + + if (!(hwnd = keyboard->focused_hwnd)) return; + + TRACE_(key)("serial=%u hwnd=%p key=%d scan=%#x state=%#x\n", serial, hwnd, key, scan, state); + + input.type = INPUT_KEYBOARD; + input.ki.wScan = scan & 0xff; + input.ki.wVk = NtUserMapVirtualKeyEx(scan, MAPVK_VSC_TO_VK_EX, NtUserGetKeyboardLayout(0)); + if (scan & ~0xff) 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_modifiers(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, uint32_t mods_depressed, + uint32_t mods_latched, uint32_t mods_locked, + uint32_t xkb_group) +{ + FIXME("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); +} + +static void keyboard_handle_repeat_info(void *data, struct wl_keyboard *wl_keyboard, + int rate, int delay) +{ + FIXME("rate=%d delay=%d stub!\n", rate, delay); +} + +static const struct wl_keyboard_listener keyboard_listener = { + keyboard_handle_keymap, + keyboard_handle_enter, + keyboard_handle_leave, + keyboard_handle_key, + keyboard_handle_modifiers, + keyboard_handle_repeat_info, +}; + +/*********************************************************************** + * wayland_keyboard_init + */ +void wayland_keyboard_init(struct wayland_keyboard *keyboard, struct wayland *wayland, + struct wl_keyboard *wl_keyboard) +{ + keyboard->wl_keyboard = wl_keyboard; + + if (!(keyboard->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS))) + { + ERR("Failed to create XKB context\n"); + return; + } + + wl_keyboard_add_listener(keyboard->wl_keyboard, &keyboard_listener, wayland); +} + +/*********************************************************************** + * wayland_keyboard_deinit + */ +void wayland_keyboard_deinit(struct wayland_keyboard *keyboard) +{ + if (keyboard->wl_keyboard) + wl_keyboard_destroy(keyboard->wl_keyboard); + + xkb_context_unref(keyboard->xkb_context); + memset(keyboard, 0, sizeof(*keyboard)); +} diff --git a/dlls/winewayland.drv/wayland_surface.c b/dlls/winewayland.drv/wayland_surface.c index ae4812ebb08..97d2d438489 100644 --- a/dlls/winewayland.drv/wayland_surface.c +++ b/dlls/winewayland.drv/wayland_surface.c @@ -181,6 +181,9 @@ void wayland_surface_destroy(struct wayland_surface *surface) } pthread_mutex_unlock(&process_wayland.pointer.mutex);
+ if (process_wayland.keyboard.focused_hwnd == surface->hwnd) + process_wayland.keyboard.focused_hwnd = NULL; + pthread_mutex_lock(&surface->mutex);
if (surface->xdg_toplevel) diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index 4bcd9e6706e..de2167ddc49 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -27,6 +27,7 @@
#include <pthread.h> #include <wayland-client.h> +#include <xkbcommon/xkbcommon.h> #include "xdg-output-unstable-v1-client-protocol.h" #include "xdg-shell-client-protocol.h"
@@ -65,6 +66,13 @@ enum wayland_surface_config_state WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN = (1 << 3) };
+struct wayland_keyboard +{ + struct wl_keyboard *wl_keyboard; + struct xkb_context *xkb_context; + HWND focused_hwnd; +}; + struct wayland_cursor { struct wayland_shm_buffer *shm_buffer; @@ -100,6 +108,7 @@ struct wayland struct xdg_wm_base *xdg_wm_base; struct wl_shm *wl_shm; struct wayland_seat seat; + struct wayland_keyboard keyboard; struct wayland_pointer pointer; struct wl_list output_list; /* Protects the output_list and the wayland_output.current states. */ @@ -223,6 +232,14 @@ void wayland_window_surface_update_wayland_surface(struct window_surface *surfac struct wayland_surface *wayland_surface) DECLSPEC_HIDDEN; void wayland_window_flush(HWND hwnd) DECLSPEC_HIDDEN;
+/********************************************************************** + * Wayland Keyboard + */ + +void wayland_keyboard_init(struct wayland_keyboard *keyboard, struct wayland *wayland, + struct wl_keyboard *wl_keyboard) DECLSPEC_HIDDEN; +void wayland_keyboard_deinit(struct wayland_keyboard *keyboard) DECLSPEC_HIDDEN; + /********************************************************************** * Wayland pointer */ @@ -248,6 +265,16 @@ static inline LRESULT send_message(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lp return NtUserMessageCall(hwnd, msg, wparam, lparam, NULL, NtUserSendMessage, FALSE); }
+static inline LRESULT send_message_timeout(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, + UINT flags, UINT timeout, PDWORD_PTR res_ptr) +{ + struct send_message_timeout_params params = { .flags = flags, .timeout = timeout }; + LRESULT res = NtUserMessageCall(hwnd, msg, wparam, lparam, ¶ms, + NtUserSendMessageTimeout, FALSE); + if (res_ptr) *res_ptr = res; + return params.result; +} + RGNDATA *get_region_data(HRGN region) DECLSPEC_HIDDEN;
/********************************************************************** diff --git a/include/config.h.in b/include/config.h.in index fa234a82f84..0158be25799 100644 --- a/include/config.h.in +++ b/include/config.h.in @@ -675,6 +675,9 @@ /* Define to 1 if `callback' is a member of `XICCallback'. */ #undef HAVE_XICCALLBACK_CALLBACK
+/* Define to 1 if you have the <xkbcommon/xkbcommon.h> header file. */ +#undef HAVE_XKBCOMMON_XKBCOMMON_H + /* Define if Xrender has the XRenderCreateLinearGradient function */ #undef HAVE_XRENDERCREATELINEARGRADIENT
From: Rémi Bernon rbernon@codeweavers.com
--- dlls/win32u/message.c | 8 ++++++++ dlls/win32u/ntuser_private.h | 3 +++ dlls/win32u/sysparams.c | 3 +++ dlls/winewayland.drv/wayland_keyboard.c | 15 ++++++++++++++- include/ntuser.h | 1 + 5 files changed, 29 insertions(+), 1 deletion(-)
diff --git a/dlls/win32u/message.c b/dlls/win32u/message.c index d2909339983..e897ead329e 100644 --- a/dlls/win32u/message.c +++ b/dlls/win32u/message.c @@ -288,6 +288,7 @@ struct send_message_info };
static const INPUT_MESSAGE_SOURCE msg_source_unavailable = { IMDT_UNAVAILABLE, IMO_UNAVAILABLE }; +static BOOL keyboard_auto_repeat_enabled;
/* flag for messages that contain pointers */ /* 32 messages per entry, messages 0..31 map to bits 0..31 */ @@ -508,6 +509,13 @@ static inline BOOL check_hwnd_filter( const MSG *msg, HWND hwnd_filter ) return (msg->hwnd == hwnd_filter || is_child( hwnd_filter, msg->hwnd )); }
+BOOL set_keyboard_auto_repeat( BOOL enable ) +{ + BOOL enabled = keyboard_auto_repeat_enabled; + keyboard_auto_repeat_enabled = enable; + return enabled; +} + /*********************************************************************** * unpack_message * diff --git a/dlls/win32u/ntuser_private.h b/dlls/win32u/ntuser_private.h index b39e38db5d6..915aeb50bdc 100644 --- a/dlls/win32u/ntuser_private.h +++ b/dlls/win32u/ntuser_private.h @@ -242,6 +242,9 @@ HICON alloc_cursoricon_handle( BOOL is_icon ) DECLSPEC_HIDDEN; extern void free_dce( struct dce *dce, HWND hwnd ) DECLSPEC_HIDDEN; extern void invalidate_dce( WND *win, const RECT *extra_rect ) DECLSPEC_HIDDEN;
+/* message.c */ +extern BOOL set_keyboard_auto_repeat( BOOL enable ) DECLSPEC_HIDDEN; + /* window.c */ HANDLE alloc_user_handle( struct user_object *ptr, unsigned int type ) DECLSPEC_HIDDEN; void *free_user_handle( HANDLE handle, unsigned int type ) DECLSPEC_HIDDEN; diff --git a/dlls/win32u/sysparams.c b/dlls/win32u/sysparams.c index 5506abac7c6..57ee1f890f9 100644 --- a/dlls/win32u/sysparams.c +++ b/dlls/win32u/sysparams.c @@ -6315,6 +6315,9 @@ ULONG_PTR WINAPI NtUserCallOneParam( ULONG_PTR arg, ULONG code ) process_layout = arg; return TRUE;
+ case NtUserCallOneParam_SetKeyboardAutoRepeat: + return set_keyboard_auto_repeat( arg ); + /* temporary exports */ case NtUserGetDeskPattern: return get_entry( &entry_DESKPATTERN, 256, (WCHAR *)arg ); diff --git a/dlls/winewayland.drv/wayland_keyboard.c b/dlls/winewayland.drv/wayland_keyboard.c index 837d307ec1e..29ddb4591c4 100644 --- a/dlls/winewayland.drv/wayland_keyboard.c +++ b/dlls/winewayland.drv/wayland_keyboard.c @@ -203,7 +203,19 @@ static void keyboard_handle_modifiers(void *data, struct wl_keyboard *wl_keyboar static void keyboard_handle_repeat_info(void *data, struct wl_keyboard *wl_keyboard, int rate, int delay) { - FIXME("rate=%d delay=%d stub!\n", rate, delay); + UINT speed; + + TRACE("rate=%d delay=%d\n", rate, delay); + + /* Handle non-negative rate values, ignore invalid (negative) values. A + * rate of 0 disables repeat. */ + if (rate >= 80) speed = 31; + else if (rate >= 5) speed = rate * 400 / 1000 - 1; + else speed = 0; + + NtUserSystemParametersInfo(SPI_SETKEYBOARDSPEED, speed, NULL, 0); + NtUserSystemParametersInfo(SPI_SETKEYBOARDDELAY, max(0, min(3, delay / 250)), NULL, 0); + NtUserCallOneParam(rate > 0, NtUserCallOneParam_SetKeyboardAutoRepeat); }
static const struct wl_keyboard_listener keyboard_listener = { @@ -229,6 +241,7 @@ void wayland_keyboard_init(struct wayland_keyboard *keyboard, struct wayland *wa return; }
+ NtUserCallOneParam(TRUE, NtUserCallOneParam_SetKeyboardAutoRepeat); wl_keyboard_add_listener(keyboard->wl_keyboard, &keyboard_listener, wayland); }
diff --git a/include/ntuser.h b/include/ntuser.h index 171d32abe6e..ee2a677eabc 100644 --- a/include/ntuser.h +++ b/include/ntuser.h @@ -870,6 +870,7 @@ enum NtUserCallOneParam_ReplyMessage, NtUserCallOneParam_SetCaretBlinkTime, NtUserCallOneParam_SetProcessDefaultLayout, + NtUserCallOneParam_SetKeyboardAutoRepeat, /* temporary exports */ NtUserGetDeskPattern, };
From: Rémi Bernon rbernon@codeweavers.com
--- dlls/win32u/message.c | 47 ++++++++++++++++++++++++++++++++++++ dlls/win32u/ntuser_private.h | 4 +++ 2 files changed, 51 insertions(+)
diff --git a/dlls/win32u/message.c b/dlls/win32u/message.c index e897ead329e..3c59eb8ee1d 100644 --- a/dlls/win32u/message.c +++ b/dlls/win32u/message.c @@ -2292,6 +2292,22 @@ static void send_parent_notify( HWND hwnd, WORD event, WORD idChild, POINT pt ) } }
+ +static void handle_keyboard_repeat_message( HWND hwnd ) +{ + struct user_thread_info *thread_info = get_user_thread_info(); + MSG *msg = &thread_info->key_repeat_msg; + UINT speed; + + msg->lParam = (msg->lParam & ~(LPARAM)0xffff) + ((msg->lParam + 1) & 0xffff); + + if (NtUserSystemParametersInfo( SPI_GETKEYBOARDSPEED, 0, &speed, 0 )) + NtUserSetSystemTimer( hwnd, SYSTEM_TIMER_KEY_REPEAT, 400 / (speed + 1) ); + + NtUserPostMessage( hwnd, msg->message, msg->wParam, msg->lParam ); +} + + /*********************************************************************** * process_keyboard_message * @@ -2371,6 +2387,33 @@ static BOOL process_keyboard_message( MSG *msg, UINT hw_id, HWND hwnd_filter, if (ImmProcessKey( msg->hwnd, NtUserGetKeyboardLayout(0), msg->wParam, msg->lParam, 0 )) msg->wParam = VK_PROCESSKEY;
+ /* set/kill timers for key auto-repeat */ + if (remove && keyboard_auto_repeat_enabled) + { + struct user_thread_info *thread_info = get_user_thread_info(); + + switch (msg->message) + { + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + { + UINT delay; + + if (msg->wParam == VK_PROCESSKEY) break; + + thread_info->key_repeat_msg = *msg; + if (NtUserSystemParametersInfo( SPI_GETKEYBOARDDELAY, 0, &delay, 0 )) + NtUserSetSystemTimer( msg->hwnd, SYSTEM_TIMER_KEY_REPEAT, (delay + 1) * 250 ); + break; + } + + case WM_KEYUP: + case WM_SYSKEYUP: + kill_system_timer( thread_info->key_repeat_msg.hwnd, SYSTEM_TIMER_KEY_REPEAT ); + break; + } + } + return TRUE; }
@@ -3565,6 +3608,10 @@ LRESULT WINAPI NtUserDispatchMessage( const MSG *msg ) case SYSTEM_TIMER_TRACK_MOUSE: update_mouse_tracking_info( msg->hwnd ); return 0; + + case SYSTEM_TIMER_KEY_REPEAT: + handle_keyboard_repeat_message( msg->hwnd ); + return 0; } }
diff --git a/dlls/win32u/ntuser_private.h b/dlls/win32u/ntuser_private.h index 915aeb50bdc..72a2f24bc49 100644 --- a/dlls/win32u/ntuser_private.h +++ b/dlls/win32u/ntuser_private.h @@ -32,6 +32,9 @@ enum system_timer_id { SYSTEM_TIMER_TRACK_MOUSE = 0xfffa, SYSTEM_TIMER_CARET = 0xffff, + + /* not compatible with native */ + SYSTEM_TIMER_KEY_REPEAT = 0xfff0, };
struct rawinput_thread_data @@ -126,6 +129,7 @@ struct user_thread_info struct received_message_info *receive_info; /* Message being currently received */ struct user_key_state_info *key_state; /* Cache of global key state */ struct imm_thread_data *imm_thread_data; /* IMM thread data */ + MSG key_repeat_msg; /* Last WM_KEYDOWN message to repeat */ HKL kbd_layout; /* Current keyboard layout */ UINT kbd_layout_id; /* Current keyboard layout ID */ struct rawinput_thread_data *rawinput; /* RawInput thread local data / buffer */
From: Rémi Bernon rbernon@codeweavers.com
--- dlls/winewayland.drv/wayland_keyboard.c | 46 +++++++++++++++++++++++-- dlls/winewayland.drv/waylanddrv.h | 1 + 2 files changed, 45 insertions(+), 2 deletions(-)
diff --git a/dlls/winewayland.drv/wayland_keyboard.c b/dlls/winewayland.drv/wayland_keyboard.c index 29ddb4591c4..5566141e1cb 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" @@ -128,8 +129,39 @@ static WORD key2scan(UINT key) 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) + xkb_keymap = xkb_keymap_new_from_buffer(keyboard->xkb_context, keymap_str, size, + XKB_KEYMAP_FORMAT_TEXT_V1, 0); + else + FIXME("Unsupported keymap format %#x\n", format); + + 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))) + { + xkb_state_unref(keyboard->xkb_state); + keyboard->xkb_state = xkb_state; + } + + xkb_keymap_unref(xkb_keymap); }
static void keyboard_handle_enter(void *data, struct wl_keyboard *wl_keyboard, @@ -196,8 +228,17 @@ 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 (!keyboard->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); + + xkb_state_update_mask(keyboard->xkb_state, mods_depressed, mods_latched, + mods_locked, 0, 0, xkb_group); + + /* TODO: Sync wine modifier state with XKB modifier state. */ }
static void keyboard_handle_repeat_info(void *data, struct wl_keyboard *wl_keyboard, @@ -253,6 +294,7 @@ void wayland_keyboard_deinit(struct wayland_keyboard *keyboard) if (keyboard->wl_keyboard) wl_keyboard_destroy(keyboard->wl_keyboard);
+ xkb_state_unref(keyboard->xkb_state); xkb_context_unref(keyboard->xkb_context); memset(keyboard, 0, sizeof(*keyboard)); } diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index de2167ddc49..2fdb9fe679e 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -70,6 +70,7 @@ struct wayland_keyboard { struct wl_keyboard *wl_keyboard; struct xkb_context *xkb_context; + struct xkb_state *xkb_state; HWND focused_hwnd; };
From: Rémi Bernon rbernon@codeweavers.com
--- configure.ac | 5 +- dlls/winewayland.drv/Makefile.in | 4 +- dlls/winewayland.drv/wayland_keyboard.c | 245 +++++++++++++++++++++++- dlls/winewayland.drv/waylanddrv.h | 2 + 4 files changed, 251 insertions(+), 5 deletions(-)
diff --git a/configure.ac b/configure.ac index 0975f6fa565..8d4d9d4f4cb 100644 --- a/configure.ac +++ b/configure.ac @@ -1375,8 +1375,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"], +WINE_NOTICE_WITH(wayland, [test -z "$WAYLAND_CLIENT_LIBS" -o -z "$WAYLAND_SCANNER" -o -z "$XKBCOMMON_LIBS" -o -z "$XKBREGISTRY_LIBS"], [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 4fbc516266e..e096a422806 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 5566141e1cb..415357cb30c 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,21 @@ 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; +}; + +static struct list xkb_layouts = LIST_INIT(xkb_layouts); +static struct rxkb_context *rxkb_context; + static WORD key2scan(UINT key) { /* base keys can be mapped directly */ @@ -122,6 +138,194 @@ 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 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); + ptr += len; + + layout->xkb_group = xkb_group; + layout->lang = lang; + layout->index = index; + if (index) layout->layout_id = next_layout_id++; + + TRACE("Created layout entry %p, hkl %04x%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)); + HKL hkl = keyboard->last_hkl; + struct layout *layout; + + LIST_FOR_EACH_ENTRY(layout, &xkb_layouts, struct layout, entry) + if (layout->xkb_group == xkb_group) break; + if (&layout->entry == &xkb_layouts) + ERR("Failed to find Xkb Layout for group %d\n", xkb_group); + else + { + if (!layout->layout_id) hkl = (HKL)(UINT_PTR)MAKELONG(locale, layout->lang); + else hkl = (HKL)(UINT_PTR)MAKELONG(locale, 0xf000 | layout->layout_id); + } + + if (hkl == keyboard->last_hkl) return; + keyboard->last_hkl = hkl; + + TRACE("Changing keyboard layout to %p\n", hkl); + NtUserPostMessage(keyboard->focused_hwnd, WM_INPUTLANGCHANGEREQUEST, 0 /*FIXME*/, + (LPARAM)keyboard->last_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 */ @@ -131,7 +335,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); @@ -139,7 +345,7 @@ static void keyboard_handle_keymap(void *data, struct wl_keyboard *wl_keyboard, if ((keymap_str = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0)) != MAP_FAILED) { if (format == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) - xkb_keymap = xkb_keymap_new_from_buffer(keyboard->xkb_context, keymap_str, size, + xkb_keymap = xkb_keymap_new_from_string(keyboard->xkb_context, keymap_str, XKB_KEYMAP_FORMAT_TEXT_V1, 0); else FIXME("Unsupported keymap format %#x\n", format); @@ -155,6 +361,31 @@ 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))) { xkb_state_unref(keyboard->xkb_state); @@ -162,6 +393,8 @@ static void keyboard_handle_keymap(void *data, struct wl_keyboard *wl_keyboard, }
xkb_keymap_unref(xkb_keymap); + + set_current_xkb_group(0); }
static void keyboard_handle_enter(void *data, struct wl_keyboard *wl_keyboard, @@ -180,6 +413,9 @@ static void keyboard_handle_enter(void *data, struct wl_keyboard *wl_keyboard,
keyboard->focused_hwnd = hwnd; NtUserSetForegroundWindow(hwnd); + + NtUserPostMessage(keyboard->focused_hwnd, WM_INPUTLANGCHANGEREQUEST, 0 /*FIXME*/, + (LPARAM)keyboard->last_hkl); }
static void keyboard_handle_leave(void *data, struct wl_keyboard *wl_keyboard, @@ -216,7 +452,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->last_hkl); if (scan & ~0xff) input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
if (state == WL_KEYBOARD_KEY_STATE_RELEASED) input.ki.dwFlags |= KEYEVENTF_KEYUP; @@ -237,6 +473,7 @@ static void keyboard_handle_modifiers(void *data, struct wl_keyboard *wl_keyboar
xkb_state_update_mask(keyboard->xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, xkb_group); + set_current_xkb_group(xkb_group);
/* TODO: Sync wine modifier state with XKB modifier state. */ } @@ -282,6 +519,10 @@ void wayland_keyboard_init(struct wayland_keyboard *keyboard, struct wayland *wa return; }
+ rxkb_context = rxkb_context_new(RXKB_CONTEXT_NO_FLAGS); + if (!rxkb_context_parse_default_ruleset(rxkb_context)) + ERR("Failed to parse default Xkb ruleset\n"); + NtUserCallOneParam(TRUE, NtUserCallOneParam_SetKeyboardAutoRepeat); wl_keyboard_add_listener(keyboard->wl_keyboard, &keyboard_listener, wayland); } diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index 2fdb9fe679e..801cfd0e3ef 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 "xdg-output-unstable-v1-client-protocol.h" #include "xdg-shell-client-protocol.h"
@@ -72,6 +73,7 @@ struct wayland_keyboard struct xkb_context *xkb_context; struct xkb_state *xkb_state; HWND focused_hwnd; + HKL last_hkl; };
struct wayland_cursor
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 3e6e440de93..4c48f2ca9d0 100644 --- a/dlls/win32u/input.c +++ b/dlls/win32u/input.c @@ -1141,7 +1141,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
--- 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 4c48f2ca9d0..f718b274176 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 c4e859f21e5..dcaceee8b48 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
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 | 301 +++++++++++++++++++++++- dlls/winewayland.drv/waylanddrv.h | 2 + dlls/winewayland.drv/waylanddrv_main.c | 2 + 3 files changed, 299 insertions(+), 6 deletions(-)
diff --git a/dlls/winewayland.drv/wayland_keyboard.c b/dlls/winewayland.drv/wayland_keyboard.c index 415357cb30c..fb2f4b0ab6f 100644 --- a/dlls/winewayland.drv/wayland_keyboard.c +++ b/dlls/winewayland.drv/wayland_keyboard.c @@ -41,14 +41,96 @@ 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])]; + }; +}; + +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 pthread_mutex_t xkb_layouts_mutex = PTHREAD_MUTEX_INITIALIZER; static struct list xkb_layouts = LIST_INIT(xkb_layouts); static struct rxkb_context *rxkb_context;
@@ -250,27 +332,47 @@ static inline LANGID langid_from_xkb_layout(const char *layout, size_t layout_le return MAKELANGID(LANG_NEUTRAL, SUBLANG_CUSTOM_UNSPECIFIED); };
-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;
layout->xkb_layout = strcpy(ptr, xkb_layout); ptr += len; @@ -279,6 +381,130 @@ static void add_xkb_layout(const char *xkb_layout, xkb_layout_index_t 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, entry->vsc, 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, .Attributes = CAPLOK}; + BOOL found = FALSE; + unsigned int mod; + + for (mod = 0; mod < 8; ++mod) + { + xkb_mod_mask_t mod_mask = 0; + uint32_t ret = 0; + + 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 (ret) 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, hkl %04x%04x id %04x\n", layout, layout->index, layout->lang, layout->layout_id); list_add_tail(&xkb_layouts, &layout->entry); @@ -291,6 +517,8 @@ static void set_current_xkb_group(xkb_layout_index_t xkb_group) HKL hkl = keyboard->last_hkl; struct layout *layout;
+ 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) @@ -301,6 +529,8 @@ static void set_current_xkb_group(xkb_layout_index_t xkb_group) else hkl = (HKL)(UINT_PTR)MAKELONG(locale, 0xf000 | layout->layout_id); }
+ pthread_mutex_unlock(&xkb_layouts_mutex); + if (hkl == keyboard->last_hkl) return; keyboard->last_hkl = hkl;
@@ -361,10 +591,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++) @@ -383,9 +615,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))) { xkb_state_unref(keyboard->xkb_state); @@ -437,6 +671,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) @@ -450,6 +695,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->last_hkl); @@ -539,3 +787,44 @@ void wayland_keyboard_deinit(struct wayland_keyboard *keyboard) xkb_context_unref(keyboard->xkb_context); memset(keyboard, 0, sizeof(*keyboard)); } + +/*********************************************************************** + * 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 (!layout->layout_id && layout->lang == HIWORD(hkl)) break; + if (layout->layout_id && (layout->layout_id | 0xf000) == HIWORD(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 801cfd0e3ef..fa5e12acf77 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -242,6 +242,8 @@ void wayland_window_flush(HWND hwnd) DECLSPEC_HIDDEN; void wayland_keyboard_init(struct wayland_keyboard *keyboard, struct wayland *wayland, struct wl_keyboard *wl_keyboard) DECLSPEC_HIDDEN; void wayland_keyboard_deinit(struct wayland_keyboard *keyboard) 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 7151d7b931a..93d32e9e38a 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/winewayland.drv/wayland.c | 1 + dlls/winewayland.drv/wayland_keyboard.c | 231 ++++++++++++++++++++++++ dlls/winewayland.drv/waylanddrv.h | 8 +- dlls/winewayland.drv/waylanddrv_main.c | 2 + 4 files changed, 241 insertions(+), 1 deletion(-)
diff --git a/dlls/winewayland.drv/wayland.c b/dlls/winewayland.drv/wayland.c index 31c225e76d7..0740d4c179a 100644 --- a/dlls/winewayland.drv/wayland.c +++ b/dlls/winewayland.drv/wayland.c @@ -35,6 +35,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(waylanddrv); struct wayland process_wayland = { .seat.mutex = PTHREAD_MUTEX_INITIALIZER, + .keyboard.mutex = PTHREAD_MUTEX_INITIALIZER, .pointer.mutex = PTHREAD_MUTEX_INITIALIZER, .output_list = {&process_wayland.output_list, &process_wayland.output_list}, .output_mutex = PTHREAD_MUTEX_INITIALIZER diff --git a/dlls/winewayland.drv/wayland_keyboard.c b/dlls/winewayland.drv/wayland_keyboard.c index fb2f4b0ab6f..ac0b5128323 100644 --- a/dlls/winewayland.drv/wayland_keyboard.c +++ b/dlls/winewayland.drv/wayland_keyboard.c @@ -31,8 +31,11 @@ #include <sys/mman.h> #include <unistd.h>
+#include "ntstatus.h" +#define WIN32_NO_STATUS #include "waylanddrv.h" #include "wine/debug.h" +#include "ime.h"
WINE_DEFAULT_DEBUG_CHANNEL(keyboard); WINE_DECLARE_DEBUG_CHANNEL(key); @@ -220,6 +223,91 @@ static WORD key2scan(UINT key) return 0x200 | (key & 0x7f); }
+static UINT scan2key(WORD scan) +{ + /* base keys can be mapped directly */ + if (scan <= KEY_KPDOT) return scan; + + /* map keys found in KBDTABLES definitions (Txx Xxx Yxx macros) */ + switch (scan) + { + case 0x005a: return 84 /* ISO_Level3_Shift */; /* T5A / VK_OEM_WSCTRL */ + case 0x0054: return KEY_SYSRQ; /* T54 / VK_SNAPSHOT */ + case 0x0056: return KEY_102ND; /* T56 / VK_OEM_102 */ + case 0x0057: return KEY_F11; /* T57 / VK_F11 */ + case 0x0058: return KEY_F12; /* T58 / VK_F12 */ + case 0x0059: return KEY_LINEFEED; /* T59 / VK_CLEAR */ + case 0x005b: return KEY_EXIT; /* T5B / VK_OEM_FINISH */ + case 0x005c: return KEY_OPEN; /* T5C / VK_OEM_JUMP */ + /* case 0x005d: return KEY_EREOF; */ /* T5D / VK_EREOF */ + /* case 0x005e: return KEY_OEM_BACKTAB; */ /* T5E / VK_OEM_BACKTAB */ + case 0x005f: return KEY_COMPOSE; /* T5F / VK_OEM_AUTO */ + case 0x0062: return KEY_SCALE; /* T62 / VK_ZOOM */ + case 0x0063: return KEY_HELP; /* T63 / VK_HELP */ + case 0x0064: return KEY_F13; /* T64 / VK_F13 */ + case 0x0065: return KEY_F14; /* T65 / VK_F14 */ + case 0x0066: return KEY_F15; /* T66 / VK_F15 */ + case 0x0067: return KEY_F16; /* T67 / VK_F16 */ + case 0x0068: return KEY_F17; /* T68 / VK_F17 */ + case 0x0069: return KEY_F18; /* T69 / VK_F18 */ + case 0x006a: return KEY_F19; /* T6A / VK_F19 */ + case 0x006b: return KEY_F20; /* T6B / VK_F20 */ + case 0x006c: return KEY_F21; /* T6C / VK_F21 */ + case 0x006d: return KEY_F22; /* T6D / VK_F22 */ + case 0x006e: return KEY_F23; /* T6E / VK_F23 */ + /* case 0x006f: return KEY_OEM_PA3; */ /* T6F / VK_OEM_PA3 */ + case 0x0071: return KEY_COMPUTER; /* T71 / VK_OEM_RESET */ + /* case 0x0073: return KEY_ABNT_C1; */ /* T73 / VK_ABNT_C1 */ + case 0x0076: return KEY_F24; /* T76 / VK_F24 */ + case 0x007b: return KEY_KPPLUSMINUS; /* T7B / VK_OEM_PA1 */ + case 0x007c: return KEY_TAB; /* T7C / VK_TAB */ + /* case 0x007e: return KEY_ABNT_C2; */ /* T7E / VK_ABNT_C2 */ + /* case 0x007f: return KEY_OEM_PA2; */ /* T7F / VK_OEM_PA2 */ + case 0x0110: return KEY_PREVIOUSSONG; /* X10 / VK_MEDIA_PREV_TRACK */ + case 0x0119: return KEY_NEXTSONG; /* X19 / VK_MEDIA_NEXT_TRACK */ + case 0x011c: return KEY_KPENTER; /* X1C / VK_RETURN */ + case 0x011d: return KEY_RIGHTCTRL; /* X1D / VK_RCONTROL */ + case 0x0120: return KEY_MUTE; /* X20 / VK_VOLUME_MUTE */ + case 0x0121: return KEY_PROG2; /* X21 / VK_LAUNCH_APP2 */ + case 0x0122: return KEY_PLAYPAUSE; /* X22 / VK_MEDIA_PLAY_PAUSE */ + case 0x0124: return KEY_STOPCD; /* X24 / VK_MEDIA_STOP */ + case 0x012e: return KEY_VOLUMEDOWN; /* X2E / VK_VOLUME_DOWN */ + case 0x0130: return KEY_VOLUMEUP; /* X30 / VK_VOLUME_UP */ + case 0x0132: return KEY_HOMEPAGE; /* X32 / VK_BROWSER_HOME */ + case 0x0135: return KEY_KPSLASH; /* X35 / VK_DIVIDE */ + case 0x0137: return KEY_PRINT; /* X37 / VK_SNAPSHOT */ + case 0x0138: return KEY_RIGHTALT; /* X38 / VK_RMENU */ + case 0x0146: return KEY_CANCEL; /* X46 / VK_CANCEL */ + case 0x0147: return KEY_HOME; /* X47 / VK_HOME */ + case 0x0148: return KEY_UP; /* X48 / VK_UP */ + case 0x0149: return KEY_PAGEUP; /* X49 / VK_PRIOR */ + case 0x014b: return KEY_LEFT; /* X4B / VK_LEFT */ + case 0x014d: return KEY_RIGHT; /* X4D / VK_RIGHT */ + case 0x014f: return KEY_END; /* X4F / VK_END */ + case 0x0150: return KEY_DOWN; /* X50 / VK_DOWN */ + case 0x0151: return KEY_PAGEDOWN; /* X51 / VK_NEXT */ + case 0x0152: return KEY_INSERT; /* X52 / VK_INSERT */ + case 0x0153: return KEY_DELETE; /* X53 / VK_DELETE */ + case 0x015b: return KEY_LEFTMETA; /* X5B / VK_LWIN */ + case 0x015c: return KEY_RIGHTMETA; /* X5C / VK_RWIN */ + case 0x015d: return KEY_MENU; /* X5D / VK_APPS */ + case 0x015e: return KEY_POWER; /* X5E / VK_POWER */ + case 0x015f: return KEY_SLEEP; /* X5F / VK_SLEEP */ + case 0x0165: return KEY_FIND; /* X65 / VK_BROWSER_SEARCH */ + case 0x0166: return KEY_BOOKMARKS; /* X66 / VK_BROWSER_FAVORITES */ + case 0x0167: return KEY_REFRESH; /* X67 / VK_BROWSER_REFRESH */ + case 0x0168: return KEY_STOP; /* X68 / VK_BROWSER_STOP */ + case 0x0169: return KEY_FORWARD; /* X69 / VK_BROWSER_FORWARD */ + case 0x016a: return KEY_BACK; /* X6A / VK_BROWSER_BACK */ + case 0x016b: return KEY_PROG1; /* X6B / VK_LAUNCH_APP1 */ + case 0x016c: return KEY_MAIL; /* X6C / VK_LAUNCH_MAIL */ + case 0x016d: return KEY_MEDIA; /* X6D / VK_LAUNCH_MEDIA_SELECT */ + case 0x021d: return KEY_PAUSE; /* Y1D / VK_PAUSE */ + } + + return 0; +} + static inline LANGID langid_from_xkb_layout(const char *layout, size_t layout_len) { #define MAKEINDEX(c0, c1) (MAKEWORD(c0, c1) - MAKEWORD('a', 'a')) @@ -622,8 +710,10 @@ static void keyboard_handle_keymap(void *data, struct wl_keyboard *wl_keyboard,
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); @@ -719,8 +809,11 @@ static void keyboard_handle_modifiers(void *data, struct wl_keyboard *wl_keyboar TRACE("serial=%u mods_depressed=%#x mods_latched=%#x mods_locked=%#x xkb_group=%d stub!\n", serial, mods_depressed, mods_latched, mods_locked, xkb_group);
+ pthread_mutex_lock(&keyboard->mutex); xkb_state_update_mask(keyboard->xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, xkb_group); + pthread_mutex_unlock(&keyboard->mutex); + set_current_xkb_group(xkb_group);
/* TODO: Sync wine modifier state with XKB modifier state. */ @@ -759,6 +852,14 @@ static const struct wl_keyboard_listener keyboard_listener = { void wayland_keyboard_init(struct wayland_keyboard *keyboard, struct wayland *wayland, struct wl_keyboard *wl_keyboard) { + struct xkb_compose_table *compose_table; + const char *locale; + + locale = getenv("LC_ALL"); + if (!locale || !*locale) locale = getenv("LC_CTYPE"); + if (!locale || !*locale) locale = getenv("LANG"); + if (!locale || !*locale) locale = "C"; + keyboard->wl_keyboard = wl_keyboard;
if (!(keyboard->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS))) @@ -767,6 +868,17 @@ void wayland_keyboard_init(struct wayland_keyboard *keyboard, struct wayland *wa return; }
+ if ((compose_table = xkb_compose_table_new_from_locale(keyboard->xkb_context, locale, + XKB_COMPOSE_COMPILE_NO_FLAGS))) + { + keyboard->xkb_compose_state = + xkb_compose_state_new(compose_table, XKB_COMPOSE_STATE_NO_FLAGS); + xkb_compose_table_unref(compose_table); + } + + if (!keyboard->xkb_compose_state) + ERR("Failed to create XKB compose table\n"); + rxkb_context = rxkb_context_new(RXKB_CONTEXT_NO_FLAGS); if (!rxkb_context_parse_default_ruleset(rxkb_context)) ERR("Failed to parse default Xkb ruleset\n"); @@ -783,6 +895,7 @@ void wayland_keyboard_deinit(struct wayland_keyboard *keyboard) if (keyboard->wl_keyboard) wl_keyboard_destroy(keyboard->wl_keyboard);
+ xkb_compose_state_unref(keyboard->xkb_compose_state); xkb_state_unref(keyboard->xkb_state); xkb_context_unref(keyboard->xkb_context); memset(keyboard, 0, sizeof(*keyboard)); @@ -828,3 +941,121 @@ void WAYLAND_ReleaseKbdTables(const KBDTABLES *tables) struct layout *layout = CONTAINING_RECORD(tables, struct layout, tables); xkb_layout_release(layout); } + +/*********************************************************************** + * ImeProcessKey (WAYLANDDRV.@) + */ +UINT WAYLAND_ImeProcessKey(HIMC himc, UINT wparam, UINT lparam, const BYTE *key_state) +{ + struct wayland_keyboard *keyboard = &process_wayland.keyboard; + WORD scan = HIWORD(lparam) & 0x1ff, vkey = LOWORD(wparam); + BOOL repeat = !!(lparam >> 30), pressed = !(lparam >> 31); + UINT key = scan2key(scan); + xkb_keysym_t xkb_keysym; + BOOL ret = FALSE; + + TRACE_(key)("himc=%p scan=%#x vkey=%#x repeat=%u pressed=%u\n", + himc, scan, vkey, repeat, pressed); + + if (!pressed) return FALSE; + + pthread_mutex_lock(&keyboard->mutex); + + if ((xkb_keysym = xkb_state_key_get_one_sym(keyboard->xkb_state, key + 8)) != XKB_KEY_NoSymbol && + (xkb_compose_state_feed(keyboard->xkb_compose_state, xkb_keysym)) == XKB_COMPOSE_FEED_ACCEPTED) + { + enum xkb_compose_status compose_status = xkb_compose_state_get_status(keyboard->xkb_compose_state); + if (compose_status == XKB_COMPOSE_COMPOSING || compose_status == XKB_COMPOSE_COMPOSED) ret = TRUE; + } + + pthread_mutex_unlock(&keyboard->mutex); + + return ret; +} + +/*********************************************************************** + * ImeToAsciiEx (WAYLANDDRV.@) + */ +UINT WAYLAND_ImeToAsciiEx(UINT vkey, UINT vsc, const BYTE *state, COMPOSITIONSTRING *compstr, HIMC himc) +{ + DWORD needed = sizeof(COMPOSITIONSTRING), comp_len, result_len, len, size; + struct wayland_keyboard *keyboard = &process_wayland.keyboard; + enum xkb_compose_status compose_status; + char buffer[64]; + void *dst; + + TRACE_(key)("vkey=%#x vsc=%#x state=%p compstr=%p himc=%p\n", vkey, vsc, state, compstr, himc); + + pthread_mutex_lock(&keyboard->mutex); + compose_status = xkb_compose_state_get_status(keyboard->xkb_compose_state); + len = xkb_compose_state_get_utf8(keyboard->xkb_compose_state, buffer, sizeof(buffer)); + pthread_mutex_unlock(&keyboard->mutex); + + if (compose_status != XKB_COMPOSE_COMPOSING) comp_len = 0; + else + { + comp_len = len; + needed += comp_len * sizeof(WCHAR); /* GCS_COMPSTR */ + needed += comp_len; /* GCS_COMPATTR */ + needed += 2 * sizeof(DWORD); /* GCS_COMPCLAUSE */ + } + + if (compose_status != XKB_COMPOSE_COMPOSED) result_len = 0; + else + { + result_len = len; + needed += result_len * sizeof(WCHAR); /* GCS_RESULTSTR */ + needed += 2 * sizeof(DWORD); /* GCS_RESULTCLAUSE */ + } + + if (compstr->dwSize < needed) + { + compstr->dwSize = needed; + return STATUS_BUFFER_TOO_SMALL; + } + + memset(compstr, 0, sizeof(*compstr)); + compstr->dwSize = sizeof(*compstr); + + if (compose_status == XKB_COMPOSE_COMPOSING) + { + compstr->dwCursorPos = 0; + + compstr->dwCompStrOffset = compstr->dwSize; + dst = (BYTE *)compstr + compstr->dwCompStrOffset; + RtlUTF8ToUnicodeN(dst, len * sizeof(WCHAR), &size, buffer, len); + compstr->dwCompStrLen = size / sizeof(WCHAR); + compstr->dwSize += size; + + compstr->dwCompClauseLen = 2 * sizeof(DWORD); + compstr->dwCompClauseOffset = compstr->dwSize; + dst = (BYTE *)compstr + compstr->dwCompClauseOffset; + *((DWORD *)dst + 0) = 0; + *((DWORD *)dst + 1) = compstr->dwCompStrLen; + compstr->dwSize += compstr->dwCompClauseLen; + + compstr->dwCompAttrLen = compstr->dwCompStrLen; + compstr->dwCompAttrOffset = compstr->dwSize; + dst = (BYTE *)compstr + compstr->dwCompAttrOffset; + memset(dst, ATTR_INPUT, compstr->dwCompAttrLen); + compstr->dwSize += compstr->dwCompAttrLen; + } + + if (compose_status == XKB_COMPOSE_COMPOSED) + { + compstr->dwResultStrOffset = compstr->dwSize; + dst = (BYTE *)compstr + compstr->dwResultStrOffset; + RtlUTF8ToUnicodeN(dst, len * sizeof(WCHAR), &size, buffer, len); + compstr->dwResultStrLen = size / sizeof(WCHAR); + compstr->dwSize += size; + + compstr->dwResultClauseLen = 2 * sizeof(DWORD); + compstr->dwResultClauseOffset = compstr->dwSize; + dst = (BYTE *)compstr + compstr->dwResultClauseOffset; + *((DWORD *)dst + 0) = 0; + *((DWORD *)dst + 1) = compstr->dwResultStrLen; + compstr->dwSize += compstr->dwResultClauseLen; + } + + return 0; +} diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index fa5e12acf77..52d56280d78 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/xkbcommon-compose.h> #include <xkbcommon/xkbregistry.h> #include "xdg-output-unstable-v1-client-protocol.h" #include "xdg-shell-client-protocol.h" @@ -71,9 +72,12 @@ struct wayland_keyboard { struct wl_keyboard *wl_keyboard; struct xkb_context *xkb_context; - struct xkb_state *xkb_state; HWND focused_hwnd; HKL last_hkl; + + pthread_mutex_t mutex; + struct xkb_state *xkb_state; + struct xkb_compose_state *xkb_compose_state; };
struct wayland_cursor @@ -244,6 +248,8 @@ void wayland_keyboard_init(struct wayland_keyboard *keyboard, struct wayland *wa void wayland_keyboard_deinit(struct wayland_keyboard *keyboard) DECLSPEC_HIDDEN; const KBDTABLES *WAYLAND_KbdLayerDescriptor(HKL hkl) DECLSPEC_HIDDEN; void WAYLAND_ReleaseKbdTables(const KBDTABLES *) DECLSPEC_HIDDEN; +UINT WAYLAND_ImeProcessKey(HIMC himc, UINT wparam, UINT lparam, const BYTE *key_state) DECLSPEC_HIDDEN; +UINT WAYLAND_ImeToAsciiEx(UINT vkey, UINT vsc, const BYTE *state, COMPOSITIONSTRING *compstr, HIMC himc) DECLSPEC_HIDDEN;
/********************************************************************** * Wayland pointer diff --git a/dlls/winewayland.drv/waylanddrv_main.c b/dlls/winewayland.drv/waylanddrv_main.c index 93d32e9e38a..89a32edf9e6 100644 --- a/dlls/winewayland.drv/waylanddrv_main.c +++ b/dlls/winewayland.drv/waylanddrv_main.c @@ -35,6 +35,8 @@ static const struct user_driver_funcs waylanddrv_funcs = .pDestroyWindow = WAYLAND_DestroyWindow, .pKbdLayerDescriptor = WAYLAND_KbdLayerDescriptor, .pReleaseKbdTables = WAYLAND_ReleaseKbdTables, + .pImeProcessKey = WAYLAND_ImeProcessKey, + .pImeToAsciiEx = WAYLAND_ImeToAsciiEx, .pSetCursor = WAYLAND_SetCursor, .pSysCommand = WAYLAND_SysCommand, .pUpdateDisplayDevices = WAYLAND_UpdateDisplayDevices,
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=138945
Your paranoid android.
=== debian11 (build log) ===
error: patch failed: configure.ac:1372 error: patch failed: configure.ac:1375 Task: Patch failed to apply
=== debian11b (build log) ===
error: patch failed: configure.ac:1372 error: patch failed: configure.ac:1375 Task: Patch failed to apply
On Sat Oct 21 07:43:04 2023 +0000, darkblaze69 wrote:
Tab key enters infinite loop. ![Screencast_from_2023-10-20_23-00-49](/uploads/9a54bb9b70cfc241a211ec66f34e26bb/Screencast_from_2023-10-20_23-00-49.webm)
Thanks, should be working better now.
On Sat Oct 21 07:43:04 2023 +0000, Rémi Bernon wrote:
Thanks, should be working better now.
works for me, thanks
@rbernon Thanks for working on this! The code looks better/simpler than the experimental branch code (and WineX11).
Concerning `xkbregistry`, it is present in `xkbcommon >= 1.0.0`. On the one hand, making it mandatory would preclude distros that are still in use/supported, like Debian 10 and Ubuntu 20.04. On the other hand, these older distro versions are not likely to have good Wayland support in the various compositors, so perhaps not supporting them is not a big loss? I guess if it is straightforward to make this optional, that's a way forward for the short-term, and we can revisit the decision at a later point. A related upstream issue which hasn't seen much traction is: https://gitlab.freedesktop.org/wayland/wayland/-/issues/273.
I haven't yet looked at the code in more detail yet (should I, or is the branch not at that point yet?), but here some first observations and test results with this branch (other than the points below, this branch works well for me):
1. On GNOME the first keyboard layout is always used (even if it's not the active one) when first creating a window. After investigating a bit, this turns out this is a bug in GNOME, since it violates the spec and does not send `keyboard.modifiers` after `keyboard.enter` as it should (it sends `modifiers` before `enter`). This is already known upstream, and I'll try to move the fix forward on the GNOME side.
In the meantime, the workaround on our side could be to not check the focus when handling `modifiers`. This is not ideal, but the intention (which is not clearly stated in the spec), is that only focused surfaces should be getting `modifiers` events, so by getting that event the implication is that the surface has the focus (or in this case it will soon have it). I think it would work out since the only thing we won't be able to do is send `WM_INPUTLANGCHANGEREQUEST`, which we will send in the upcoming `enter` event.
2. The behavior of composing is different compared to native Wayland apps and winex11 (native X11). In particular:
Same behavior: * `´ + α` (valid next key) = winex11/native wayland: `ά` this: `ά`
Different behavior: * `´ (waiting for next key)` = winex11/native wayland: `´` this: `(nothing)` * `´ + π` (invalid next key) = winex11/native wayland: `´π` this: `π` * `´ + <backspace>` = winex11/native wayland: `deletes just added ´` this: `deletes previous character`
Not sure what's the correct/expected behavior here or if it matters.
3. Using Alt+Shift to switch layouts in GNOME leaves the window in a state where nothing can be typed, and we get stuck with a repetition of `WM_SYSKEYDOWN` for Alt. It turns out that when changing layouts, GNOME temporarily switches away the focus away from the surface (presumably to the language switcher HUD widget), and we get a leave and reenter of the Wine window, without getting a key up event. This is fine, since according to the `wl_keyboard.leave` spec:
After this event client must assume that all keys, including modifiers, are lifted and also it must stop key repeating if there's some going on.
The current version is not clearing the key/repeat state on leave, and I think that's the reason we get stuck with the Alt key repeats and broken state.
4. Some layouts (e.g., colemak) change the effect of Caps Lock to become only `<Backspace>`. This branch seems to apply both effects, so `a<Caps>a` results in `A` instead of `a`.
A related upstream issue which hasn't seen much traction is: https://gitlab.freedesktop.org/wayland/wayland/-/issues/273.
Yeah, looks like something we could use. I don't know what the reporter intends to do with it but I think the concerns raised in comment there is not an issue for us: we're interested in the locale information and a bit more detail to get an appropriate scan2vkey table, and we'd use the proper xkb keymap for everything else.
I haven't yet looked at the code in more detail yet (should I, or is the branch not at that point yet?)
Sure, I think it's mostly good, except for the issues you noticed. I think I would just split it, to get the most basic things first.
The behavior of composing is different compared to native Wayland apps and winex11 (native X11). In particular:
- `´ (waiting for next key)` = winex11/native wayland: `´` this: `(nothing)`
Somehow I couldn't figure how to get the temporary text out of the xkb compose state.
- `´ + π` (invalid next key) = winex11/native wayland: `´π` this: `π`
- `´ + <backspace>` = winex11/native wayland: `deletes just added ´` this: `deletes previous character`
Not sure what's the correct/expected behavior here or if it matters.
I'll need to investigate this a little bit.
Fwiw I've used the IME integration to implement the composition, because it seemed very appropriate, but I was also considering the KBDTABLES dead key support.
The IME composition has some disadvantages that it will use a popup to show the temporary text in most cases. This won't work well in wayland as the popup will appear at a random place.
The KBDTABLES deadkey support are just not supported yet, and they could probably only handle simple cases. It's also not clear if we can fill them without trying every key sequence (which may or may not be an issue?).
Alexandros Frantzis (@afrantzis) commented about dlls/winewayland.drv/waylanddrv.h:
return NtUserMessageCall(hwnd, msg, wparam, lparam, NULL, NtUserSendMessage, FALSE);
}
+static inline LRESULT send_message_timeout(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam,
I guess this helper is a leftover from the experimental branch commit, it's not needed in this MR.
Alexandros Frantzis (@afrantzis) commented about dlls/winewayland.drv/wayland_keyboard.c:
- FIXME("rate=%d delay=%d stub!\n", rate, delay);
+}
+static const struct wl_keyboard_listener keyboard_listener = {
- keyboard_handle_keymap,
- keyboard_handle_enter,
- keyboard_handle_leave,
- keyboard_handle_key,
- keyboard_handle_modifiers,
- keyboard_handle_repeat_info,
+};
+/***********************************************************************
wayland_keyboard_init
- */
+void wayland_keyboard_init(struct wayland_keyboard *keyboard, struct wayland *wayland,
In upstream (in contrast to the exp. branch) we have a single `process_wayland` so there is no need to explicitly pass it around. The same applies to `struct wayland_keyboard` (i.e. `process_wayland.keyboard`).
In the future we may want to support multiple seats/keyboards/pointers, but for now we use this simplified model and code.
Alexandros Frantzis (@afrantzis) commented about dlls/winewayland.drv/wayland_surface.c:
} pthread_mutex_unlock(&process_wayland.pointer.mutex);
- if (process_wayland.keyboard.focused_hwnd == surface->hwnd)
process_wayland.keyboard.focused_hwnd = NULL;
This compare-exchange operation needs to be explicitly atomic or locked, to avoid the following thread interleaving scenario:
1. [Wayland] keyboard enter hwnd1 2. [Window] destroy hwnd1, up until and including the `focused_hwnd == surface->hwnd` check above. 3. [Wayland] keyboard leave hwnd1 4. [Wayland] keyboard enter hwnd2 5. [Window] continue destroy hwnd1, setting `focused_hwnd = NULL`, so hwnd2 loses focus
This also means that all other accesses to `focused_hwnd` should ideally also be locked or explicitly atomic (to guarantee memory order). Looking at subsequent commits, I think both approaches would work, but since we are going to have a lock anyway (for xkb_state), perhaps it would be simpler to just use that for all keyboard synchronization needs.
Alexandros Frantzis (@afrantzis) commented about dlls/winewayland.drv/wayland_keyboard.c:
- case KEY_F14: return 0x0065; /* T65 / VK_F14 */
- case KEY_F15: return 0x0066; /* T66 / VK_F15 */
- case KEY_F16: return 0x0067; /* T67 / VK_F16 */
- case KEY_F17: return 0x0068; /* T68 / VK_F17 */
- case KEY_F18: return 0x0069; /* T69 / VK_F18 */
- case KEY_F19: return 0x006a; /* T6A / VK_F19 */
- case KEY_F20: return 0x006b; /* T6B / VK_F20 */
- case KEY_F21: return 0x006c; /* T6C / VK_F21 */
- case KEY_F22: return 0x006d; /* T6D / VK_F22 */
- case KEY_F23: return 0x006e; /* T6E / VK_F23 */
- /* case KEY_OEM_PA3: return 0x006f; */ /* T6F / VK_OEM_PA3 */
- case KEY_COMPUTER: return 0x0071; /* T71 / VK_OEM_RESET */
- /* case KEY_ABNT_C1: return 0x0073; */ /* T73 / VK_ABNT_C1 */
- case KEY_F24: return 0x0076; /* T76 / VK_F24 */
- case KEY_KPPLUSMINUS: return 0x007b; /* T7B / VK_OEM_PA1 */
- case KEY_TAB: return 0x007c; /* T7C / VK_TAB */
Is this correct? `KEY_TAB <= KEY_KPDOT` so this is case never reached. My online searches haven't provide any useful info about 0x7c, other than MS recommends against using it (as with all PS/2 set 1 scan codes above 0x79).
Alexandros Frantzis (@afrantzis) commented about dlls/winewayland.drv/wayland_keyboard.c:
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;
+};
+static struct list xkb_layouts = LIST_INIT(xkb_layouts);
Nit: We have been using `wl_list` in the driver so far, perhaps we can also use it here for consistency (but it's fine either way).
Alexandros Frantzis (@afrantzis) commented about dlls/winewayland.drv/wayland_keyboard.c:
if ((keymap_str = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0)) != MAP_FAILED) { if (format == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1)
xkb_keymap = xkb_keymap_new_from_buffer(keyboard->xkb_context, keymap_str, size,
xkb_keymap = xkb_keymap_new_from_string(keyboard->xkb_context, keymap_str,
This is correct (WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1 guarantees a null-terminated string), we can use this function already from the first commit that introduce xkb_keymaps.
Alexandros Frantzis (@afrantzis) commented about dlls/winewayland.drv/wayland_keyboard.c:
+}
+static void set_current_xkb_group(xkb_layout_index_t xkb_group) +{
- struct wayland_keyboard *keyboard = &process_wayland.keyboard;
- LCID locale = LOWORD(NtUserGetKeyboardLayout(0));
- HKL hkl = keyboard->last_hkl;
- struct layout *layout;
- LIST_FOR_EACH_ENTRY(layout, &xkb_layouts, struct layout, entry)
if (layout->xkb_group == xkb_group) break;
- if (&layout->entry == &xkb_layouts)
ERR("Failed to find Xkb Layout for group %d\n", xkb_group);
- else
- {
if (!layout->layout_id) hkl = (HKL)(UINT_PTR)MAKELONG(locale, layout->lang);
Could you please elaborate on why the first layout in a keymap for each language is treated specially in terms of HKL (i.e., HIWORD is `lang`)? Is there some API (or internal Wine) expectation for this being the case?
I have started doing a deep dive into the code and have left some comments and questions. I am still only half-way through the series, and there are a few aspects of this MR (esp. second half) I am not familiar with, so some more reading is in order.
It's great that you have introduced a nice solution in Wine core for the client side key repeat functionality!
Somehow I couldn't figure how to get the temporary text out of the xkb compose state.
I don't think there is a way to extract that info from the compose state. You will need to keep track of the temporary/dead xkb_keysyms, and emit the appropriate text manually as needed. I haven't yet looked at the KBDTABLES and IME part of this MR, but I assume we can do something similar to what winex11 (and experimental winewayland) do in ToUnicode, manually mapping from dead keys to wchar?
On GNOME the first keyboard layout is always used (even if it's not the active one) when first creating a window. After investigating a bit, this turns out this is a bug in GNOME, since it violates the spec and does not send keyboard.modifiers after keyboard.enter as it should (it sends modifiers before enter). This is already known upstream, and I'll try to move the fix forward on the GNOME side.
The [fix](https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3341) for this was merged and also backported to GNOME 44 and 45, so hopefully more users will be able to avoid the problematic behavior even if we decide not to implement the work around.
With this patch (on top of 8.18+r83+g0c7a09cb1f9-1) menus in Firefox 3.6 portable are immediately closed. With clean wine 8.18 and 8.19 they can be opened.
https://sourceforge.net/projects/portableapps/files/Mozilla%20Firefox,%20Por...
![Screencast_from_2023-11-06_12-21-21](/uploads/ae2a87d1219ab9a3d36d515b7dfe8ac3/Screencast_from_2023-11-06_12-21-21.webm)
On Thu Nov 2 11:59:28 2023 +0000, Alexandros Frantzis wrote:
This compare-exchange operation needs to be explicitly atomic or locked, to avoid the following thread interleaving scenario:
- [Wayland] keyboard enter hwnd1
- [Window] destroy hwnd1, up until and including the `focused_hwnd ==
surface->hwnd` check above. 3. [Wayland] keyboard leave hwnd1 4. [Wayland] keyboard enter hwnd2 5. [Window] continue destroy hwnd1, setting `focused_hwnd = NULL`, so hwnd2 loses focus This also means that all other accesses to `focused_hwnd` should ideally also be locked or explicitly atomic (to guarantee memory order). Looking at subsequent commits, I think both approaches would work, but since we are going to have a lock anyway (for xkb_state), perhaps it would be simpler to just use that for all keyboard synchronization needs.
Right, I'm not completely sure we really need to clear the HWND from wayland_surface_destroy, which would then leave only the even thread responsible for writing focused_hwnd, and make the mutex unnecessary, but I'll make it work the same way as the cursor for consistency.
On Thu Nov 2 11:59:28 2023 +0000, Alexandros Frantzis wrote:
Is this correct? `KEY_TAB <= KEY_KPDOT` so this is case never reached. My online searches haven't provide any useful info about 0x7c, other than MS recommends against using it (as with all PS/2 set 1 scan codes above 0x79).
Indeed, I trusted the default T7C mapping to VK_TAB, but apparently it's a dupe. I will add some FIXME comment to the commented lines and make it one as well.
On Thu Nov 2 11:59:31 2023 +0000, Alexandros Frantzis wrote:
Could you please elaborate on why the first layout in a keymap for each language is treated specially in terms of HKL (i.e., HIWORD is `lang`)? Is there some API (or internal Wine) expectation for this being the case?
The low word of HKL is not dependent on the keyboard layout but instead represents the user locale. For instance on Windows if you change the language to "English UI language" / "French keyboard", you will get a HKL value of 0x040c0409, if you change it to "French UI language" / "English keyboard" it will be 0x0409040c.
In theory, on Windows, you switch user locale at the same time you change the keyboard layout, but we do not support changing user locale in Wine for the moment and it is initialized from `LC_MESSAGES` and not from the keyboard layout. So, I'm getting the current user locale from the default HKL, and using it for all the exposed layouts.
On Mon Nov 6 12:38:40 2023 +0000, Rémi Bernon wrote:
The low word of HKL is not dependent on the keyboard layout but instead represents the user locale. For instance on Windows if you change the language to "English UI language" / "French keyboard", you will get a HKL value of 0x040c0409, if you change it to "French UI language" / "English keyboard" it will be 0x0409040c. In theory, on Windows, you switch user locale at the same time you change the keyboard layout, but we do not support changing user locale in Wine for the moment and it is initialized from `LC_MESSAGES` and not from the keyboard layout. So, I'm getting the current user locale from the default HKL, and using it for all the exposed layouts.
Sorry, I got it the other way, edited to swap low / high word.
If you meant the `layout_id` part, this is the mechanism used by Windows to support multiple layouts for the same input language. The first, or single, layout for a given input language uses the keyboard language in its HKL. Additional layouts have an additional indirection, through the "Layout Id" property which is, both available in the HKL with the highest nibble set (0xf000 prefix), and in the registry where one can use this unique id to lookup the layout and find its input language / name back.
I've addressed the comments and split the first changes to https://gitlab.winehq.org/wine/wine/-/merge_requests/4204.
On Thu Nov 9 17:06:35 2023 +0000, darkblaze69 wrote:
On GNOME with this patch (on top of 8.18+r83+g0c7a09cb1f9-1) menus in Firefox 3.6 are immediately closed. With clean wine 8.18 and 8.19 they can be opened. https://ftp.mozilla.org/pub/firefox/releases/3.6.28/win32/en-US/ ![Screencast_from_2023-11-06_12-21-21](/uploads/ae2a87d1219ab9a3d36d515b7dfe8ac3/Screencast_from_2023-11-06_12-21-21.webm)
What seems to be causing the problem (which I can also reproduce with GIMP win32, although the details are not exactly the same), is that the menu window reacts badly (i.e., it closes) when we try to make it active. Under Wayland we don't have control over which surfaces get the keyboard focus, so we will need to have some way to detect windows that shouldn't have the focus, and focus an ancestor instead (all of this from the Wine perspective, since we can't affect the compositor's focus).
From a quick look, it seems winex11 deals with this with a combination of making such windows not "managed", and/or some special handling of WM_TAKE_FOCUS. Perhaps we could take some inspiration from these concepts and methods.
On Thu Nov 9 17:06:35 2023 +0000, Alexandros Frantzis wrote:
What seems to be causing the problem (which I can also reproduce with GIMP win32, although the details are not exactly the same), is that the menu window reacts badly (i.e., it closes) when we try to make it active. Under Wayland we don't have control over which surfaces get the keyboard focus, so we will need to have some way to detect windows that shouldn't have the focus, and focus an ancestor instead (all of this from the Wine perspective, since we can't affect the compositor's focus). From a quick look, it seems winex11 deals with this with a combination of making such windows not "managed", and/or some special handling of WM_TAKE_FOCUS. Perhaps we could take some inspiration from these concepts and methods.
With latest https://gitlab.winehq.org/wine/wine/-/merge_requests/4204 the problem is gone.
On Mon Nov 6 12:42:40 2023 +0000, Rémi Bernon wrote:
Sorry, I got it the other way, edited to swap low / high word. If you meant the `layout_id` part, this is the mechanism used by Windows to support multiple layouts for the same input language. The first, or single, layout for a given input language uses the keyboard language in its HKL. Additional layouts have an additional indirection, through the "Layout Id" property which is, both available in the HKL with the highest nibble set (0xf000 prefix), and in the registry where one can use this unique id to lookup the layout and find its input language / name back.
Thanks, this was very helpful (I referenced this while reviewing the final MR).
On Thu Nov 2 13:46:33 2023 +0000, Alexandros Frantzis wrote:
Somehow I couldn't figure how to get the temporary text out of the xkb
compose state. I don't think there is a way to extract that info from the compose state. You will need to keep track of the temporary/dead xkb_keysyms, and emit the appropriate text manually as needed. I haven't yet looked at the KBDTABLES and IME part of this MR, but I assume we can do something similar to what winex11 (and experimental winewayland) do in ToUnicode, manually mapping from dead keys to wchar?
On GNOME the first keyboard layout is always used (even if it's not
the active one) when first creating a window. After investigating a bit, this turns out this is a bug in GNOME, since it violates the spec and does not send keyboard.modifiers after keyboard.enter as it should (it sends modifiers before enter). This is already known upstream, and I'll try to move the fix forward on the GNOME side. The [fix](https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3341) for this was merged and also backported to GNOME 44 and 45, so hopefully more users will be able to avoid the problematic behavior even if we decide not to implement the work around.
Looks like libxkbcommon 1.6 now has an API to inspect composition tables. This could be used to implement KBDTABLES dead key support, though the API is very recent.
I don't think the IME path is practical, at least not until the wayland driver has proper popup window positioning. Also, having to manually figure the conversion between dead keysym and temporary character doesn't feel very nice.
In any case this could take some time to implement (either dead key or another mechanism), and I think maybe it might be better to delay it until after the freeze.
This merge request was closed by Rémi Bernon.