If a surface is being clipped and hides the cursor, drawing its own one, winewayland constrains using a pointer lock and enables Wayland relative motion. If the application decides to stop drawing its own cursor and make the cursor visible, winewayland will disable relative motion and pointer lock, and enable pointer confinement. The user will perceive a pointer jump from the win32/application drawn cursor to where the Wayland pointer is after being unlocked and an absolute motion event is received, because they were desynchronized due to the Wayland one being locked in place.
The pointer constraints protocol says this:
If the client is drawing its own cursor, it should update the position hint to the position of its own cursor. A compositor may use this information to warp the pointer upon unlock in order to avoid pointer jumps.
So, right before unlocking, make a request for the compositor to warp the pointer to the win32 position on pointer unlock.
-- v3: winewayland: Update locked pointer position hint.
From: Attila Fidan dev@print0.net
This may be used by the compositor to warp the Wayland pointer to where the win32 cursor is upon unlock, if it's within surface bounds. --- dlls/winewayland.drv/wayland_pointer.c | 72 ++++++++++++++++++++++---- 1 file changed, 63 insertions(+), 9 deletions(-)
diff --git a/dlls/winewayland.drv/wayland_pointer.c b/dlls/winewayland.drv/wayland_pointer.c index c20ba170285..ea449dd3cc9 100644 --- a/dlls/winewayland.drv/wayland_pointer.c +++ b/dlls/winewayland.drv/wayland_pointer.c @@ -46,6 +46,63 @@ static HWND wayland_pointer_get_focused_hwnd(void) return hwnd; }
+static void reapply_cursor_clipping(void) +{ + RECT rect; + UINT context = NtUserSetThreadDpiAwarenessContext(NTUSER_DPI_PER_MONITOR_AWARE); + if (NtUserGetClipCursor(&rect)) NtUserClipCursor(&rect); + NtUserSetThreadDpiAwarenessContext(context); +} + +static void update_cursor_position_hint(HWND hwnd) +{ + struct wayland_pointer *pointer = &process_wayland.pointer; + struct wayland_win_data *data; + struct wayland_surface *surface; + POINT cursor_pos; + int warp_x, warp_y; + BOOL hint_updated = FALSE; + + NtUserGetCursorPos(&cursor_pos); + + if (!(data = wayland_win_data_get(hwnd))) return; + if (!(surface = data->wayland_surface)) + { + wayland_win_data_release(data); + return; + } + wayland_surface_coords_from_window(surface, + cursor_pos.x - surface->window.rect.left, + cursor_pos.y - surface->window.rect.top, + &warp_x, &warp_y); + wayland_win_data_release(data); + + pthread_mutex_lock(&pointer->mutex); + if (hwnd == pointer->constraint_hwnd && pointer->zwp_locked_pointer_v1) + { + hint_updated = TRUE; + zwp_locked_pointer_v1_set_cursor_position_hint( + pointer->zwp_locked_pointer_v1, + wl_fixed_from_int(warp_x), + wl_fixed_from_int(warp_y)); + } + pthread_mutex_unlock(&pointer->mutex); + + if (hint_updated) + { + if (!(data = wayland_win_data_get(hwnd))) return; + if (!(surface = data->wayland_surface)) + { + wayland_win_data_release(data); + return; + } + wl_surface_commit(surface->wl_surface); + wayland_win_data_release(data); + TRACE("hwnd=%p wayland_xy=%d,%d screen_xy=%d,%d\n", + hwnd, warp_x, warp_y, (int)cursor_pos.x, (int)cursor_pos.y); + } +} + static void pointer_handle_motion_internal(wl_fixed_t sx, wl_fixed_t sy) { INPUT input = {0}; @@ -272,6 +329,7 @@ static void relative_pointer_v1_relative_motion(void *private,
wayland_win_data_release(data);
+ reapply_cursor_clipping();
input.type = INPUT_MOUSE; input.mi.dx = screen.x; @@ -624,14 +682,6 @@ clear_cursor: } }
-static void reapply_cursor_clipping(void) -{ - RECT rect; - UINT context = NtUserSetThreadDpiAwarenessContext(NTUSER_DPI_PER_MONITOR_AWARE); - if (NtUserGetClipCursor(&rect)) NtUserClipCursor(&rect); - NtUserSetThreadDpiAwarenessContext(context); -} - static void wayland_set_cursor(HWND hwnd, HCURSOR hcursor, BOOL use_hcursor) { struct wayland_pointer *pointer = &process_wayland.pointer; @@ -885,6 +935,7 @@ void WAYLAND_SetCursor(HWND hwnd, HCURSOR hcursor) BOOL WAYLAND_ClipCursor(const RECT *clip, BOOL reset) { struct wayland_pointer *pointer = &process_wayland.pointer; + HWND hwnd; struct wl_surface *wl_surface = NULL; struct wayland_surface *surface = NULL; struct wayland_win_data *data; @@ -893,7 +944,8 @@ BOOL WAYLAND_ClipCursor(const RECT *clip, BOOL reset)
TRACE("clip=%s reset=%d\n", wine_dbgstr_rect(clip), reset);
- if (!(data = wayland_win_data_get(NtUserGetForegroundWindow()))) return FALSE; + hwnd = NtUserGetForegroundWindow(); + if (!(data = wayland_win_data_get(hwnd))) return FALSE; if ((surface = data->wayland_surface)) { wl_surface = surface->wl_surface; @@ -902,6 +954,8 @@ BOOL WAYLAND_ClipCursor(const RECT *clip, BOOL reset) } wayland_win_data_release(data);
+ update_cursor_position_hint(hwnd); + /* Since we are running in the context of the foreground thread we know * that the wl_surface of the foreground HWND will not be invalidated, * so we can access it without having the win data lock. */
On Sat Feb 22 05:50:46 2025 +0000, Attila Fidan wrote:
changed this line in [version 3 of the diff](/wine/wine/-/merge_requests/7352/diffs?diff_id=159659&start_sha=f0cbe94c470a811b59e3bd0faf7b92e0b46c7c49#ed873a9226289213ee847a41e40cf5b866d6cd6d_797_825)
Thanks for the feedback. The pointer and win_data mutexes are not held at the same time anymore and the commit is written while holding the foreground win_data. I also started updating the hint on relative motion events so it warps after a user/compositior initiated unlock.
Alexandros Frantzis (@afrantzis) commented about dlls/winewayland.drv/wayland_pointer.c:
return hwnd;
}
+static void reapply_cursor_clipping(void) +{
- RECT rect;
- UINT context = NtUserSetThreadDpiAwarenessContext(NTUSER_DPI_PER_MONITOR_AWARE);
- if (NtUserGetClipCursor(&rect)) NtUserClipCursor(&rect);
- NtUserSetThreadDpiAwarenessContext(context);
+}
+static void update_cursor_position_hint(HWND hwnd)
I think we can integrate the functionality directly into WAYLAND_ClipCursor instead of having it as a separate function, in order to minimize (re)acquiring the same locks: 1. The warp coordinates can be calculated inside the existing win_data lock region in WAYLAND_ClipCursor. 2. The zwp_locked_pointer_v1_set_cursor_position_hint call can be performed inside the pointer->mutex lock region in WAYLAND_ClipCursor, with a temporary unlock to commit the surface.
Alexandros Frantzis (@afrantzis) commented about dlls/winewayland.drv/wayland_pointer.c:
wayland_win_data_release(data);
- reapply_cursor_clipping();
The cursor clip reapplication involves some non-trivial overhead (call to wineserver -> send a message to the fg thread -> handle that message in the fg thread -> driver callback), so we should try to avoid performing it as often as every motion event. It's better if we set the position hint manually here, or even just ignore this case for now because we don't really support the compositor cancelling the lock on us anyway.