From 5cbb5a34446aea4d6db93577d96085e4c4ec8f26 Mon Sep 17 00:00:00 2001 From: Jeremy Bernstein Date: Mon, 2 Mar 2026 09:35:00 +0100 Subject: [PATCH] winex11.drv: Fix mouse input for builds without XInput2 Update dlls_winex11_drv_mouse_c.patch with relative motion synthesis and WM_INPUT generation fix for no-XInput2 builds (Android/Termux). Based on Smuger's WM_INPUT fix from PR #6. --- .../patches/dlls_winex11_drv_mouse_c.patch | 151 +++++++++++++++++- 1 file changed, 149 insertions(+), 2 deletions(-) diff --git a/android/patches/dlls_winex11_drv_mouse_c.patch b/android/patches/dlls_winex11_drv_mouse_c.patch index 8aaa5cfd1f30..19913979c302 100644 --- a/android/patches/dlls_winex11_drv_mouse_c.patch +++ b/android/patches/dlls_winex11_drv_mouse_c.patch @@ -1,8 +1,107 @@ diff --git a/dlls/winex11.drv/mouse.c b/dlls/winex11.drv/mouse.c -index 2ba4960..74cdb87 100644 +index 2ba49607bd2..0c579d015b9 100644 --- a/dlls/winex11.drv/mouse.c +++ b/dlls/winex11.drv/mouse.c -@@ -1488,10 +1488,14 @@ BOOL X11DRV_SetCursorPos( INT x, INT y ) +@@ -127,6 +127,8 @@ static const UINT button_up_data[NB_BUTTONS] = + XContext cursor_context = 0; + + static RECT clip_rect; ++static POINT clip_center; /* center of clipping rect for relative motion synthesis */ ++static BOOL needs_relative_motion; /* TRUE when game wants clipping but xinput2 unavailable */ + static Cursor create_cursor( HANDLE handle ); + + #ifdef HAVE_X11_EXTENSIONS_XINPUT2_H +@@ -141,6 +143,21 @@ MAKE_FUNCPTR(XISelectEvents); + #undef MAKE_FUNCPTR + #endif + ++/* When XInput2 is available, explorer.exe centralises WM_INPUT generation via ++ * SEND_HWMSG_NO_MSG raw events, so game processes must suppress their own raw ++ * input dispatch to avoid duplicates (SEND_HWMSG_NO_RAW = legacy only). ++ * When XInput2 is absent (e.g. Android / --without-xinput2 builds) there is no ++ * explorer.exe raw input pipeline at all, so each process must generate WM_INPUT ++ * itself alongside the legacy message (flags = 0). */ ++#ifdef HAVE_X11_EXTENSIONS_XINPUT2_H ++static inline UINT get_send_mouse_flags(void) ++{ ++ return xinput2_available ? SEND_HWMSG_NO_RAW : 0; ++} ++#else ++static inline UINT get_send_mouse_flags(void) { return 0; } ++#endif ++ + #ifdef HAVE_X11_EXTENSIONS_XINPUT_H + #define MAKE_FUNCPTR(f) static typeof(f) * p##f + MAKE_FUNCPTR(XOpenDevice); +@@ -459,7 +476,6 @@ void x11drv_xinput2_init( struct x11drv_thread_data *data ) + */ + static BOOL grab_clipping_window( const RECT *clip ) + { +-#ifdef HAVE_X11_EXTENSIONS_XINPUT2_H + struct x11drv_thread_data *data = x11drv_thread_data(); + Window clip_window; + HCURSOR cursor; +@@ -478,13 +494,17 @@ static BOOL grab_clipping_window( const RECT *clip ) + WARN( "refusing to clip to %s\n", wine_dbgstr_rect(clip) ); + return FALSE; + } ++#ifdef HAVE_X11_EXTENSIONS_XINPUT2_H + if (!xinput2_available) ++#endif + { +- WARN( "XInput2 not supported, refusing to clip to %s\n", wine_dbgstr_rect(clip) ); +- NtUserClipCursor( NULL ); ++ WARN( "XInput2 not available, enabling relative motion for %s\n", wine_dbgstr_rect(clip) ); ++ clip_rect = *clip; ++ clip_center.x = (clip->left + clip->right) / 2; ++ clip_center.y = (clip->top + clip->bottom) / 2; ++ needs_relative_motion = TRUE; + return TRUE; + } +- + /* enable XInput2 unless we are already clipping */ + if (!data->clipping_cursor) x11drv_xinput2_enable( data->display, DefaultRootWindow( data->display ) ); + +@@ -523,12 +543,8 @@ static BOOL grab_clipping_window( const RECT *clip ) + return FALSE; + } + clip_rect = *clip; + data->clipping_cursor = TRUE; + return TRUE; +-#else +- WARN( "XInput2 was not available at compile time\n" ); +- return FALSE; +-#endif + } + + /*********************************************************************** +@@ -552,6 +571,7 @@ void ungrab_clipping_window(void) + } + clipping_cursor = FALSE; + data->clipping_cursor = FALSE; ++ needs_relative_motion = FALSE; + x11drv_xinput2_disable( data->display, DefaultRootWindow( data->display ) ); + } + +@@ -640,7 +660,7 @@ static void send_mouse_input( HWND hwnd, Window window, unsigned int state, INPU + { + struct x11drv_thread_data *thread_data = x11drv_thread_data(); + if (!thread_data->clipping_cursor || thread_data->clip_window != window) return; +- NtUserSendHardwareInput( hwnd, SEND_HWMSG_NO_RAW, input, 0 ); ++ NtUserSendHardwareInput( hwnd, get_send_mouse_flags(), input, 0 ); + return; + } + +@@ -664,7 +684,7 @@ static void send_mouse_input( HWND hwnd, Window window, unsigned int state, INPU + SERVER_END_REQ; + } + +- NtUserSendHardwareInput( hwnd, SEND_HWMSG_NO_RAW, input, 0 ); ++ NtUserSendHardwareInput( hwnd, get_send_mouse_flags(), input, 0 ); + } + + #ifdef SONAME_LIBXCURSOR +@@ -1488,10 +1508,14 @@ BOOL X11DRV_SetCursorPos( INT x, INT y ) return FALSE; } @@ -17,3 +116,51 @@ index 2ba4960..74cdb87 100644 XFlush( data->display ); /* avoids bad mouse lag in games that do their own mouse warping */ TRACE( "warped to %d,%d serial %lu\n", x, y, data->warp_serial ); return TRUE; +@@ -1661,18 +1685,41 @@ BOOL X11DRV_MotionNotify( HWND hwnd, XEvent *xev ) + TRACE( "hwnd %p/%lx pos %d,%d is_hint %d serial %lu\n", + hwnd, event->window, event->x, event->y, event->is_hint, event->serial ); + +- input.mi.dx = event->x; +- input.mi.dy = event->y; ++ if (is_old_motion_event( event->serial )) ++ { ++ TRACE( "pos %d,%d old serial %lu, ignoring\n", event->x, event->y, event->serial ); ++ return FALSE; ++ } ++ + input.mi.mouseData = 0; +- input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; + input.mi.time = EVENT_x11_time_to_win32_time( event->time ); + input.mi.dwExtraInfo = 0; + +- if (is_old_motion_event( event->serial )) ++ /* Synthesize relative motion when game wants clipping but xinput2 unavailable */ ++ if (needs_relative_motion && hwnd) + { +- TRACE( "pos %d,%d old serial %lu, ignoring\n", event->x, event->y, event->serial ); +- return FALSE; ++ int dx = event->x_root - clip_center.x; ++ int dy = event->y_root - clip_center.y; ++ ++ if (dx == 0 && dy == 0) ++ return FALSE; ++ ++ input.mi.dx = dx; ++ input.mi.dy = dy; ++ input.mi.dwFlags = MOUSEEVENTF_MOVE; ++ ++ /* Warp cursor back to center */ ++ XWarpPointer( event->display, None, root_window, 0, 0, 0, 0, clip_center.x, clip_center.y ); ++ ++ send_mouse_input( hwnd, event->window, event->state, &input ); ++ return TRUE; + } ++ ++ /* Normal absolute motion */ ++ input.mi.dx = event->x; ++ input.mi.dy = event->y; ++ input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; ++ + map_event_coords( hwnd, event->window, event->root, event->x_root, event->y_root, &input ); + send_mouse_input( hwnd, event->window, event->state, &input ); + return TRUE;