This MR implements mouselook, which requires support for ClipCursor and relative motion events. The latter we can implement directly without using `SetCursorPos` for which no Wayland protocol exists at the moment.
1. Implement setting the foreground window, as this is required for clipping to work properly (i.e., for the ClipCursor driver callback to be called in the proper process/thread, see commit (2)). We can't unconditionally set the foreground window on keyboard focus, since compositors are eager to give the focus and some windows strongly dislike that (see https://gitlab.winehq.org/wine/wine/-/merge_requests/4102#note_51733). This MR borrows the "managed" window concept from WineX11 to detect windows that we shouldn't allow the compositor to manage, and avoids setting such windows as the foreground on keyboard focus. 2. Implement cursor clipping using the pointer-constraints protocol. 3. Switch to emitting relative motion events using the relative-pointer protocol when the cursor is not visible. --- 4. Finally, a potential fix for an issue I have been seeing where apps may transiently lose the active status (see commit message for more) as a result of the foreground state changes in this MR, which has undesirable side-effects. I have included this here for MR completeness/correctness, but it can be moved to a subsequent MR if preferred.
Note: the velocity of relative motion events when scaling is involved is somewhat inconsistent among Wayland compositors.
Thanks!
-- v5: https://gitlab.winehq.org/wine/wine/-/merge_requests/4593
From: Alexandros Frantzis alexandros.frantzis@collabora.com
Borrow the concept of "managed" windows from WineX11 and use it to decide whether a window should become the foreground window when receiving the Wayland keyboard focus. --- dlls/winewayland.drv/wayland_keyboard.c | 7 ++ dlls/winewayland.drv/waylanddrv.h | 1 + dlls/winewayland.drv/window.c | 99 +++++++++++++++++++++++++ 3 files changed, 107 insertions(+)
diff --git a/dlls/winewayland.drv/wayland_keyboard.c b/dlls/winewayland.drv/wayland_keyboard.c index 26cf3e8a7af..ba6a66995a6 100644 --- a/dlls/winewayland.drv/wayland_keyboard.c +++ b/dlls/winewayland.drv/wayland_keyboard.c @@ -698,6 +698,7 @@ static void keyboard_handle_enter(void *data, struct wl_keyboard *wl_keyboard, struct wl_array *keys) { struct wayland_keyboard *keyboard = &process_wayland.keyboard; + struct wayland_surface *surface; HWND hwnd;
if (!wl_surface) return; @@ -713,6 +714,12 @@ static void keyboard_handle_enter(void *data, struct wl_keyboard *wl_keyboard,
NtUserPostMessage(keyboard->focused_hwnd, WM_INPUTLANGCHANGEREQUEST, 0 /*FIXME*/, (LPARAM)keyboard_hkl); + + if ((surface = wayland_surface_lock_hwnd(hwnd))) + { + if (surface->window.managed) NtUserSetForegroundWindow(hwnd); + pthread_mutex_unlock(&surface->mutex); + } }
static void keyboard_handle_leave(void *data, struct wl_keyboard *wl_keyboard, diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index 0e781352519..08abc247e16 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -167,6 +167,7 @@ struct wayland_window_config /* The scale (i.e., normalized dpi) the window is rendering at. */ double scale; BOOL visible; + BOOL managed; };
struct wayland_client_surface diff --git a/dlls/winewayland.drv/window.c b/dlls/winewayland.drv/window.c index 31ecb6542c6..93a730e8ada 100644 --- a/dlls/winewayland.drv/window.c +++ b/dlls/winewayland.drv/window.c @@ -27,6 +27,9 @@ #include <assert.h> #include <stdlib.h>
+#include "ntstatus.h" +#define WIN32_NO_STATUS + #include "waylanddrv.h"
#include "wine/debug.h" @@ -47,6 +50,7 @@ struct wayland_win_data RECT window_rect; /* USER client rectangle relative to win32 parent window client area */ RECT client_rect; + BOOL managed; };
static int wayland_win_data_cmp_rb(const void *key, @@ -182,6 +186,7 @@ static void wayland_win_data_get_config(struct wayland_win_data *data, conf->state = window_state; conf->scale = NtUserGetDpiForWindow(data->hwnd) / 96.0; conf->visible = (style & WS_VISIBLE) == WS_VISIBLE; + conf->managed = data->managed; }
static void wayland_win_data_update_wayland_surface(struct wayland_win_data *data) @@ -287,6 +292,99 @@ out: wl_display_flush(process_wayland.wl_display); }
+static BOOL is_managed(HWND hwnd) +{ + struct wayland_win_data *data = wayland_win_data_get(hwnd); + BOOL ret = data && data->managed; + if (data) wayland_win_data_release(data); + return ret; +} + +static HWND *build_hwnd_list(void) +{ + NTSTATUS status; + HWND *list; + ULONG count = 128; + + for (;;) + { + if (!(list = malloc(count * sizeof(*list)))) return NULL; + status = NtUserBuildHwndList(0, 0, 0, 0, 0, count, list, &count); + if (!status) return list; + free(list); + if (status != STATUS_BUFFER_TOO_SMALL) return NULL; + } +} + +static BOOL has_owned_popups(HWND hwnd) +{ + HWND *list; + UINT i; + BOOL ret = FALSE; + + if (!(list = build_hwnd_list())) return FALSE; + + for (i = 0; list[i] != HWND_BOTTOM; i++) + { + if (list[i] == hwnd) break; /* popups are always above owner */ + if (NtUserGetWindowRelative(list[i], GW_OWNER) != hwnd) continue; + if ((ret = is_managed(list[i]))) break; + } + + free(list); + return ret; +} + +static inline HWND get_active_window(void) +{ + GUITHREADINFO info; + info.cbSize = sizeof(info); + return NtUserGetGUIThreadInfo(GetCurrentThreadId(), &info) ? info.hwndActive : 0; +} + +/*********************************************************************** + * is_window_managed + * + * Check if a given window should be managed + */ +static BOOL is_window_managed(HWND hwnd, UINT swp_flags, const RECT *window_rect) +{ + DWORD style, ex_style; + + /* child windows are not managed */ + style = NtUserGetWindowLongW(hwnd, GWL_STYLE); + if ((style & (WS_CHILD|WS_POPUP)) == WS_CHILD) return FALSE; + /* activated windows are managed */ + if (!(swp_flags & (SWP_NOACTIVATE|SWP_HIDEWINDOW))) return TRUE; + if (hwnd == get_active_window()) return TRUE; + /* windows with caption are managed */ + if ((style & WS_CAPTION) == WS_CAPTION) return TRUE; + /* windows with thick frame are managed */ + if (style & WS_THICKFRAME) return TRUE; + if (style & WS_POPUP) + { + HMONITOR hmon; + MONITORINFO mi; + + /* popup with sysmenu == caption are managed */ + if (style & WS_SYSMENU) return TRUE; + /* full-screen popup windows are managed */ + hmon = NtUserMonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY); + mi.cbSize = sizeof(mi); + NtUserGetMonitorInfo(hmon, &mi); + if (window_rect->left <= mi.rcWork.left && window_rect->right >= mi.rcWork.right && + window_rect->top <= mi.rcWork.top && window_rect->bottom >= mi.rcWork.bottom) + return TRUE; + } + /* application windows are managed */ + ex_style = NtUserGetWindowLongW(hwnd, GWL_EXSTYLE); + if (ex_style & WS_EX_APPWINDOW) return TRUE; + /* windows that own popups are managed */ + if (has_owned_popups(hwnd)) return TRUE; + /* default: not managed */ + return FALSE; +} + /*********************************************************************** * WAYLAND_DestroyWindow */ @@ -369,6 +467,7 @@ void WAYLAND_WindowPosChanged(HWND hwnd, HWND insert_after, UINT swp_flags,
data->window_rect = *window_rect; data->client_rect = *client_rect; + data->managed = is_window_managed(hwnd, swp_flags, window_rect);
if (surface) window_surface_add_ref(surface); if (data->window_surface) window_surface_release(data->window_surface);
From: Alexandros Frantzis alexandros.frantzis@collabora.com
--- dlls/winewayland.drv/wayland.c | 5 ----- 1 file changed, 5 deletions(-)
diff --git a/dlls/winewayland.drv/wayland.c b/dlls/winewayland.drv/wayland.c index f7119367543..31cd27f7a76 100644 --- a/dlls/winewayland.drv/wayland.c +++ b/dlls/winewayland.drv/wayland.c @@ -154,11 +154,6 @@ static void registry_handle_global(void *data, struct wl_registry *registry, process_wayland.wl_subcompositor = wl_registry_bind(registry, id, &wl_subcompositor_interface, 1); } - else if (strcmp(interface, "wp_viewporter") == 0) - { - process_wayland.wp_viewporter = - wl_registry_bind(registry, id, &wp_viewporter_interface, 1); - } }
static void registry_handle_global_remove(void *data, struct wl_registry *registry,
From: Alexandros Frantzis alexandros.frantzis@collabora.com
Use the zwp_pointer_constraints_v1 protocol to implement cursor clipping. Note that Wayland only allows us to constrain the cursor within the extents of a particular target surface. --- dlls/winewayland.drv/Makefile.in | 1 + .../pointer-constraints-unstable-v1.xml | 339 ++++++++++++++++++ dlls/winewayland.drv/wayland.c | 10 + dlls/winewayland.drv/wayland_pointer.c | 141 ++++++++ dlls/winewayland.drv/wayland_surface.c | 2 + dlls/winewayland.drv/waylanddrv.h | 6 + dlls/winewayland.drv/waylanddrv_main.c | 1 + dlls/winewayland.drv/window.c | 6 + 8 files changed, 506 insertions(+) create mode 100644 dlls/winewayland.drv/pointer-constraints-unstable-v1.xml
diff --git a/dlls/winewayland.drv/Makefile.in b/dlls/winewayland.drv/Makefile.in index 8be78bd2080..ec1eff8d97c 100644 --- a/dlls/winewayland.drv/Makefile.in +++ b/dlls/winewayland.drv/Makefile.in @@ -6,6 +6,7 @@ UNIX_LIBS = -lwin32u $(WAYLAND_CLIENT_LIBS) $(XKBCOMMON_LIBS) $(XKBREGISTRY_LIBS SOURCES = \ display.c \ dllmain.c \ + pointer-constraints-unstable-v1.xml \ version.rc \ viewporter.xml \ vulkan.c \ diff --git a/dlls/winewayland.drv/pointer-constraints-unstable-v1.xml b/dlls/winewayland.drv/pointer-constraints-unstable-v1.xml new file mode 100644 index 00000000000..efd64b6603c --- /dev/null +++ b/dlls/winewayland.drv/pointer-constraints-unstable-v1.xml @@ -0,0 +1,339 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="pointer_constraints_unstable_v1"> + + <copyright> + Copyright © 2014 Jonas Ådahl + Copyright © 2015 Red Hat Inc. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + </copyright> + + <description summary="protocol for constraining pointer motions"> + This protocol specifies a set of interfaces used for adding constraints to + the motion of a pointer. Possible constraints include confining pointer + motions to a given region, or locking it to its current position. + + In order to constrain the pointer, a client must first bind the global + interface "wp_pointer_constraints" which, if a compositor supports pointer + constraints, is exposed by the registry. Using the bound global object, the + client uses the request that corresponds to the type of constraint it wants + to make. See wp_pointer_constraints for more details. + + Warning! The protocol described in this file is experimental and backward + incompatible changes may be made. Backward compatible changes may be added + together with the corresponding interface version bump. Backward + incompatible changes are done by bumping the version number in the protocol + and interface names and resetting the interface version. Once the protocol + is to be declared stable, the 'z' prefix and the version number in the + protocol and interface names are removed and the interface version number is + reset. + </description> + + <interface name="zwp_pointer_constraints_v1" version="1"> + <description summary="constrain the movement of a pointer"> + The global interface exposing pointer constraining functionality. It + exposes two requests: lock_pointer for locking the pointer to its + position, and confine_pointer for locking the pointer to a region. + + The lock_pointer and confine_pointer requests create the objects + wp_locked_pointer and wp_confined_pointer respectively, and the client can + use these objects to interact with the lock. + + For any surface, only one lock or confinement may be active across all + wl_pointer objects of the same seat. If a lock or confinement is requested + when another lock or confinement is active or requested on the same surface + and with any of the wl_pointer objects of the same seat, an + 'already_constrained' error will be raised. + </description> + + <enum name="error"> + <description summary="wp_pointer_constraints error values"> + These errors can be emitted in response to wp_pointer_constraints + requests. + </description> + <entry name="already_constrained" value="1" + summary="pointer constraint already requested on that surface"/> + </enum> + + <enum name="lifetime"> + <description summary="constraint lifetime"> + These values represent different lifetime semantics. They are passed + as arguments to the factory requests to specify how the constraint + lifetimes should be managed. + </description> + <entry name="oneshot" value="1"> + <description summary="the pointer constraint is defunct once deactivated"> + A oneshot pointer constraint will never reactivate once it has been + deactivated. See the corresponding deactivation event + (wp_locked_pointer.unlocked and wp_confined_pointer.unconfined) for + details. + </description> + </entry> + <entry name="persistent" value="2"> + <description summary="the pointer constraint may reactivate"> + A persistent pointer constraint may again reactivate once it has + been deactivated. See the corresponding deactivation event + (wp_locked_pointer.unlocked and wp_confined_pointer.unconfined) for + details. + </description> + </entry> + </enum> + + <request name="destroy" type="destructor"> + <description summary="destroy the pointer constraints manager object"> + Used by the client to notify the server that it will no longer use this + pointer constraints object. + </description> + </request> + + <request name="lock_pointer"> + <description summary="lock pointer to a position"> + The lock_pointer request lets the client request to disable movements of + the virtual pointer (i.e. the cursor), effectively locking the pointer + to a position. This request may not take effect immediately; in the + future, when the compositor deems implementation-specific constraints + are satisfied, the pointer lock will be activated and the compositor + sends a locked event. + + The protocol provides no guarantee that the constraints are ever + satisfied, and does not require the compositor to send an error if the + constraints cannot ever be satisfied. It is thus possible to request a + lock that will never activate. + + There may not be another pointer constraint of any kind requested or + active on the surface for any of the wl_pointer objects of the seat of + the passed pointer when requesting a lock. If there is, an error will be + raised. See general pointer lock documentation for more details. + + The intersection of the region passed with this request and the input + region of the surface is used to determine where the pointer must be + in order for the lock to activate. It is up to the compositor whether to + warp the pointer or require some kind of user interaction for the lock + to activate. If the region is null the surface input region is used. + + A surface may receive pointer focus without the lock being activated. + + The request creates a new object wp_locked_pointer which is used to + interact with the lock as well as receive updates about its state. See + the the description of wp_locked_pointer for further information. + + Note that while a pointer is locked, the wl_pointer objects of the + corresponding seat will not emit any wl_pointer.motion events, but + relative motion events will still be emitted via wp_relative_pointer + objects of the same seat. wl_pointer.axis and wl_pointer.button events + are unaffected. + </description> + <arg name="id" type="new_id" interface="zwp_locked_pointer_v1"/> + <arg name="surface" type="object" interface="wl_surface" + summary="surface to lock pointer to"/> + <arg name="pointer" type="object" interface="wl_pointer" + summary="the pointer that should be locked"/> + <arg name="region" type="object" interface="wl_region" allow-null="true" + summary="region of surface"/> + <arg name="lifetime" type="uint" enum="lifetime" summary="lock lifetime"/> + </request> + + <request name="confine_pointer"> + <description summary="confine pointer to a region"> + The confine_pointer request lets the client request to confine the + pointer cursor to a given region. This request may not take effect + immediately; in the future, when the compositor deems implementation- + specific constraints are satisfied, the pointer confinement will be + activated and the compositor sends a confined event. + + The intersection of the region passed with this request and the input + region of the surface is used to determine where the pointer must be + in order for the confinement to activate. It is up to the compositor + whether to warp the pointer or require some kind of user interaction for + the confinement to activate. If the region is null the surface input + region is used. + + The request will create a new object wp_confined_pointer which is used + to interact with the confinement as well as receive updates about its + state. See the the description of wp_confined_pointer for further + information. + </description> + <arg name="id" type="new_id" interface="zwp_confined_pointer_v1"/> + <arg name="surface" type="object" interface="wl_surface" + summary="surface to lock pointer to"/> + <arg name="pointer" type="object" interface="wl_pointer" + summary="the pointer that should be confined"/> + <arg name="region" type="object" interface="wl_region" allow-null="true" + summary="region of surface"/> + <arg name="lifetime" type="uint" enum="lifetime" summary="confinement lifetime"/> + </request> + </interface> + + <interface name="zwp_locked_pointer_v1" version="1"> + <description summary="receive relative pointer motion events"> + The wp_locked_pointer interface represents a locked pointer state. + + While the lock of this object is active, the wl_pointer objects of the + associated seat will not emit any wl_pointer.motion events. + + This object will send the event 'locked' when the lock is activated. + Whenever the lock is activated, it is guaranteed that the locked surface + will already have received pointer focus and that the pointer will be + within the region passed to the request creating this object. + + To unlock the pointer, send the destroy request. This will also destroy + the wp_locked_pointer object. + + If the compositor decides to unlock the pointer the unlocked event is + sent. See wp_locked_pointer.unlock for details. + + When unlocking, the compositor may warp the cursor position to the set + cursor position hint. If it does, it will not result in any relative + motion events emitted via wp_relative_pointer. + + If the surface the lock was requested on is destroyed and the lock is not + yet activated, the wp_locked_pointer object is now defunct and must be + destroyed. + </description> + + <request name="destroy" type="destructor"> + <description summary="destroy the locked pointer object"> + Destroy the locked pointer object. If applicable, the compositor will + unlock the pointer. + </description> + </request> + + <request name="set_cursor_position_hint"> + <description summary="set the pointer cursor position hint"> + Set the cursor position hint relative to the top left corner of the + surface. + + 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. + + The cursor position hint is double buffered. The new hint will only take + effect when the associated surface gets it pending state applied. See + wl_surface.commit for details. + </description> + <arg name="surface_x" type="fixed" + summary="surface-local x coordinate"/> + <arg name="surface_y" type="fixed" + summary="surface-local y coordinate"/> + </request> + + <request name="set_region"> + <description summary="set a new lock region"> + Set a new region used to lock the pointer. + + The new lock region is double-buffered. The new lock region will + only take effect when the associated surface gets its pending state + applied. See wl_surface.commit for details. + + For details about the lock region, see wp_locked_pointer. + </description> + <arg name="region" type="object" interface="wl_region" allow-null="true" + summary="region of surface"/> + </request> + + <event name="locked"> + <description summary="lock activation event"> + Notification that the pointer lock of the seat's pointer is activated. + </description> + </event> + + <event name="unlocked"> + <description summary="lock deactivation event"> + Notification that the pointer lock of the seat's pointer is no longer + active. If this is a oneshot pointer lock (see + wp_pointer_constraints.lifetime) this object is now defunct and should + be destroyed. If this is a persistent pointer lock (see + wp_pointer_constraints.lifetime) this pointer lock may again + reactivate in the future. + </description> + </event> + </interface> + + <interface name="zwp_confined_pointer_v1" version="1"> + <description summary="confined pointer object"> + The wp_confined_pointer interface represents a confined pointer state. + + This object will send the event 'confined' when the confinement is + activated. Whenever the confinement is activated, it is guaranteed that + the surface the pointer is confined to will already have received pointer + focus and that the pointer will be within the region passed to the request + creating this object. It is up to the compositor to decide whether this + requires some user interaction and if the pointer will warp to within the + passed region if outside. + + To unconfine the pointer, send the destroy request. This will also destroy + the wp_confined_pointer object. + + If the compositor decides to unconfine the pointer the unconfined event is + sent. The wp_confined_pointer object is at this point defunct and should + be destroyed. + </description> + + <request name="destroy" type="destructor"> + <description summary="destroy the confined pointer object"> + Destroy the confined pointer object. If applicable, the compositor will + unconfine the pointer. + </description> + </request> + + <request name="set_region"> + <description summary="set a new confine region"> + Set a new region used to confine the pointer. + + The new confine region is double-buffered. The new confine region will + only take effect when the associated surface gets its pending state + applied. See wl_surface.commit for details. + + If the confinement is active when the new confinement region is applied + and the pointer ends up outside of newly applied region, the pointer may + warped to a position within the new confinement region. If warped, a + wl_pointer.motion event will be emitted, but no + wp_relative_pointer.relative_motion event. + + The compositor may also, instead of using the new region, unconfine the + pointer. + + For details about the confine region, see wp_confined_pointer. + </description> + <arg name="region" type="object" interface="wl_region" allow-null="true" + summary="region of surface"/> + </request> + + <event name="confined"> + <description summary="pointer confined"> + Notification that the pointer confinement of the seat's pointer is + activated. + </description> + </event> + + <event name="unconfined"> + <description summary="pointer unconfined"> + Notification that the pointer confinement of the seat's pointer is no + longer active. If this is a oneshot pointer confinement (see + wp_pointer_constraints.lifetime) this object is now defunct and should + be destroyed. If this is a persistent pointer confinement (see + wp_pointer_constraints.lifetime) this pointer confinement may again + reactivate in the future. + </description> + </event> + </interface> + +</protocol> diff --git a/dlls/winewayland.drv/wayland.c b/dlls/winewayland.drv/wayland.c index 31cd27f7a76..066e9e7c963 100644 --- a/dlls/winewayland.drv/wayland.c +++ b/dlls/winewayland.drv/wayland.c @@ -154,6 +154,11 @@ static void registry_handle_global(void *data, struct wl_registry *registry, process_wayland.wl_subcompositor = wl_registry_bind(registry, id, &wl_subcompositor_interface, 1); } + else if (strcmp(interface, "zwp_pointer_constraints_v1") == 0) + { + process_wayland.zwp_pointer_constraints_v1 = + wl_registry_bind(registry, id, &zwp_pointer_constraints_v1_interface, 1); + } }
static void registry_handle_global_remove(void *data, struct wl_registry *registry, @@ -259,6 +264,11 @@ BOOL wayland_process_init(void) ERR("Wayland compositor doesn't support wl_subcompositor\n"); return FALSE; } + if (!process_wayland.zwp_pointer_constraints_v1) + { + ERR("Wayland compositor doesn't support zwp_pointer_constraints_v1\n"); + return FALSE; + }
wayland_init_display_devices(FALSE);
diff --git a/dlls/winewayland.drv/wayland_pointer.c b/dlls/winewayland.drv/wayland_pointer.c index 33fd14fa0c5..59749563409 100644 --- a/dlls/winewayland.drv/wayland_pointer.c +++ b/dlls/winewayland.drv/wayland_pointer.c @@ -24,6 +24,7 @@
#include "config.h"
+#include <assert.h> #include <linux/input.h> #undef SW_MAX /* Also defined in winuser.rh */ #include <math.h> @@ -245,6 +246,11 @@ void wayland_pointer_deinit(void) struct wayland_pointer *pointer = &process_wayland.pointer;
pthread_mutex_lock(&pointer->mutex); + if (pointer->zwp_confined_pointer_v1) + { + zwp_confined_pointer_v1_destroy(pointer->zwp_confined_pointer_v1); + pointer->zwp_confined_pointer_v1 = NULL; + } wl_pointer_release(pointer->wl_pointer); pointer->wl_pointer = NULL; pointer->focused_hwnd = NULL; @@ -578,6 +584,106 @@ static void wayland_set_cursor(HWND hwnd, HCURSOR hcursor, BOOL use_hcursor) pthread_mutex_unlock(&pointer->mutex); }
+/********************************************************************** + * wayland_surface_calc_confine + * + * Calculates the pointer confine rect (in surface-local coords) + * for the specified clip rectangle (in screen coords using thread dpi). + */ +static void wayland_surface_calc_confine(struct wayland_surface *surface, + const RECT *clip, RECT *confine) +{ + RECT window_clip; + + TRACE("hwnd=%p clip=%s window=%s\n", + surface->hwnd, wine_dbgstr_rect(clip), + wine_dbgstr_rect(&surface->window.rect)); + + /* FIXME: surface->window.(client_)rect is in window dpi, whereas + * clip is in thread dpi. */ + + if (!intersect_rect(&window_clip, clip, &surface->window.rect)) + { + SetRectEmpty(confine); + return; + } + + OffsetRect(&window_clip, + -surface->window.rect.left, + -surface->window.rect.top); + wayland_surface_coords_from_window(surface, + window_clip.left, window_clip.top, + (int *)&confine->left, (int *)&confine->top); + wayland_surface_coords_from_window(surface, + window_clip.right, window_clip.bottom, + (int *)&confine->right, (int *)&confine->bottom); +} + +/*********************************************************************** + * wayland_pointer_update_constraint + * + * Enables/disables pointer confinement. + * + * Passing a NULL confine_rect disables all constraints. + */ +static void wayland_pointer_update_constraint(RECT *confine_rect, + struct wl_surface *wl_surface) +{ + struct wayland_pointer *pointer = &process_wayland.pointer; + + assert(!confine_rect || wl_surface); + + if (confine_rect) + { + HWND hwnd = wl_surface_get_user_data(wl_surface); + struct wl_region *region; + + region = wl_compositor_create_region(process_wayland.wl_compositor); + wl_region_add(region, confine_rect->left, confine_rect->top, + confine_rect->right - confine_rect->left, + confine_rect->bottom - confine_rect->top); + + if (!pointer->zwp_confined_pointer_v1 || pointer->constraint_hwnd != hwnd) + { + if (pointer->zwp_confined_pointer_v1) + zwp_confined_pointer_v1_destroy(pointer->zwp_confined_pointer_v1); + pointer->zwp_confined_pointer_v1 = + zwp_pointer_constraints_v1_confine_pointer( + process_wayland.zwp_pointer_constraints_v1, + wl_surface, + pointer->wl_pointer, + region, + ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); + pointer->constraint_hwnd = hwnd; + } + else + { + zwp_confined_pointer_v1_set_region(pointer->zwp_confined_pointer_v1, + region); + } + + TRACE("Confining to hwnd=%p wayland=%d,%d+%d,%d\n", + pointer->constraint_hwnd, + (int)confine_rect->left, (int)confine_rect->top, + (int)(confine_rect->right - confine_rect->left), + (int)(confine_rect->bottom - confine_rect->top)); + + wl_region_destroy(region); + } + else if (pointer->zwp_confined_pointer_v1) + { + TRACE("Unconfining from hwnd=%p\n", pointer->constraint_hwnd); + zwp_confined_pointer_v1_destroy(pointer->zwp_confined_pointer_v1); + pointer->zwp_confined_pointer_v1 = NULL; + pointer->constraint_hwnd = NULL; + } +} + +void wayland_pointer_clear_constraint(void) +{ + wayland_pointer_update_constraint(NULL, NULL); +} + /*********************************************************************** * WAYLAND_SetCursor */ @@ -587,3 +693,38 @@ void WAYLAND_SetCursor(HWND hwnd, HCURSOR hcursor)
wayland_set_cursor(hwnd, hcursor, TRUE); } + +/*********************************************************************** + * WAYLAND_ClipCursor + */ +BOOL WAYLAND_ClipCursor(const RECT *clip, BOOL reset) +{ + struct wayland_pointer *pointer = &process_wayland.pointer; + struct wl_surface *wl_surface = NULL; + RECT confine_rect; + + TRACE("clip=%s reset=%d\n", wine_dbgstr_rect(clip), reset); + + if (clip) + { + struct wayland_surface *surface = NULL; + + if ((surface = wayland_surface_lock_hwnd(NtUserGetForegroundWindow()))) + { + wl_surface = surface->wl_surface; + wayland_surface_calc_confine(surface, clip, &confine_rect); + pthread_mutex_unlock(&surface->mutex); + } + } + + /* 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 surface lock. */ + pthread_mutex_lock(&pointer->mutex); + wayland_pointer_update_constraint(wl_surface ? &confine_rect : NULL, wl_surface); + pthread_mutex_unlock(&pointer->mutex); + + wl_display_flush(process_wayland.wl_display); + + return TRUE; +} diff --git a/dlls/winewayland.drv/wayland_surface.c b/dlls/winewayland.drv/wayland_surface.c index 285fb9a38c5..a955f3688c5 100644 --- a/dlls/winewayland.drv/wayland_surface.c +++ b/dlls/winewayland.drv/wayland_surface.c @@ -188,6 +188,8 @@ void wayland_surface_destroy(struct wayland_surface *surface) process_wayland.pointer.focused_hwnd = NULL; process_wayland.pointer.enter_serial = 0; } + if (process_wayland.pointer.constraint_hwnd == surface->hwnd) + wayland_pointer_clear_constraint(); pthread_mutex_unlock(&process_wayland.pointer.mutex);
pthread_mutex_lock(&process_wayland.keyboard.mutex); diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index 08abc247e16..929481aaa35 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -29,6 +29,7 @@ #include <wayland-client.h> #include <xkbcommon/xkbcommon.h> #include <xkbcommon/xkbregistry.h> +#include "pointer-constraints-unstable-v1-client-protocol.h" #include "viewporter-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h" #include "xdg-shell-client-protocol.h" @@ -89,7 +90,9 @@ struct wayland_cursor struct wayland_pointer { struct wl_pointer *wl_pointer; + struct zwp_confined_pointer_v1 *zwp_confined_pointer_v1; HWND focused_hwnd; + HWND constraint_hwnd; uint32_t enter_serial; uint32_t button_serial; struct wayland_cursor cursor; @@ -115,6 +118,7 @@ struct wayland struct wl_shm *wl_shm; struct wp_viewporter *wp_viewporter; struct wl_subcompositor *wl_subcompositor; + struct zwp_pointer_constraints_v1 *zwp_pointer_constraints_v1; struct wayland_seat seat; struct wayland_keyboard keyboard; struct wayland_pointer pointer; @@ -280,6 +284,7 @@ void WAYLAND_ReleaseKbdTables(const KBDTABLES *);
void wayland_pointer_init(struct wl_pointer *wl_pointer); void wayland_pointer_deinit(void); +void wayland_pointer_clear_constraint(void);
/********************************************************************** * Helpers @@ -305,6 +310,7 @@ RGNDATA *get_region_data(HRGN region); * USER driver functions */
+BOOL WAYLAND_ClipCursor(const RECT *clip, BOOL reset); LRESULT WAYLAND_DesktopWindowProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp); void WAYLAND_DestroyWindow(HWND hwnd); void WAYLAND_SetCursor(HWND hwnd, HCURSOR hcursor); diff --git a/dlls/winewayland.drv/waylanddrv_main.c b/dlls/winewayland.drv/waylanddrv_main.c index 435a6d2b36c..b60d282aacb 100644 --- a/dlls/winewayland.drv/waylanddrv_main.c +++ b/dlls/winewayland.drv/waylanddrv_main.c @@ -31,6 +31,7 @@
static const struct user_driver_funcs waylanddrv_funcs = { + .pClipCursor = WAYLAND_ClipCursor, .pDesktopWindowProc = WAYLAND_DesktopWindowProc, .pDestroyWindow = WAYLAND_DestroyWindow, .pKbdLayerDescriptor = WAYLAND_KbdLayerDescriptor, diff --git a/dlls/winewayland.drv/window.c b/dlls/winewayland.drv/window.c index 93a730e8ada..4775b64fa76 100644 --- a/dlls/winewayland.drv/window.c +++ b/dlls/winewayland.drv/window.c @@ -194,6 +194,7 @@ static void wayland_win_data_update_wayland_surface(struct wayland_win_data *dat struct wayland_surface *surface = data->wayland_surface; HWND parent = NtUserGetAncestor(data->hwnd, GA_PARENT); BOOL visible, xdg_visible; + RECT clip;
TRACE("hwnd=%p\n", data->hwnd);
@@ -232,6 +233,11 @@ static void wayland_win_data_update_wayland_surface(struct wayland_win_data *dat if (data->window_surface) wayland_window_surface_update_wayland_surface(data->window_surface, surface);
+ /* Size/position changes affect the effective pointer constraint, so update + * it as needed. */ + if (data->hwnd == NtUserGetForegroundWindow() && NtUserGetClipCursor(&clip)) + NtUserClipCursor(&clip); + out: TRACE("hwnd=%p surface=%p=>%p\n", data->hwnd, data->wayland_surface, surface); data->wayland_surface = surface;
From: Alexandros Frantzis alexandros.frantzis@collabora.com
When the cursor is hidden and a pointer constraint is active, transition to relative mouse motion to enable mouselook in 3D games. --- dlls/winewayland.drv/Makefile.in | 1 + .../relative-pointer-unstable-v1.xml | 136 ++++++++++++++++++ dlls/winewayland.drv/wayland.c | 10 ++ dlls/winewayland.drv/wayland_pointer.c | 127 +++++++++++++++- dlls/winewayland.drv/waylanddrv.h | 3 + 5 files changed, 274 insertions(+), 3 deletions(-) create mode 100644 dlls/winewayland.drv/relative-pointer-unstable-v1.xml
diff --git a/dlls/winewayland.drv/Makefile.in b/dlls/winewayland.drv/Makefile.in index ec1eff8d97c..b47bdb262c0 100644 --- a/dlls/winewayland.drv/Makefile.in +++ b/dlls/winewayland.drv/Makefile.in @@ -7,6 +7,7 @@ SOURCES = \ display.c \ dllmain.c \ pointer-constraints-unstable-v1.xml \ + relative-pointer-unstable-v1.xml \ version.rc \ viewporter.xml \ vulkan.c \ diff --git a/dlls/winewayland.drv/relative-pointer-unstable-v1.xml b/dlls/winewayland.drv/relative-pointer-unstable-v1.xml new file mode 100644 index 00000000000..ca6f81d12ac --- /dev/null +++ b/dlls/winewayland.drv/relative-pointer-unstable-v1.xml @@ -0,0 +1,136 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="relative_pointer_unstable_v1"> + + <copyright> + Copyright © 2014 Jonas Ådahl + Copyright © 2015 Red Hat Inc. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + </copyright> + + <description summary="protocol for relative pointer motion events"> + This protocol specifies a set of interfaces used for making clients able to + receive relative pointer events not obstructed by barriers (such as the + monitor edge or other pointer barriers). + + To start receiving relative pointer events, a client must first bind the + global interface "wp_relative_pointer_manager" which, if a compositor + supports relative pointer motion events, is exposed by the registry. After + having created the relative pointer manager proxy object, the client uses + it to create the actual relative pointer object using the + "get_relative_pointer" request given a wl_pointer. The relative pointer + motion events will then, when applicable, be transmitted via the proxy of + the newly created relative pointer object. See the documentation of the + relative pointer interface for more details. + + Warning! The protocol described in this file is experimental and backward + incompatible changes may be made. Backward compatible changes may be added + together with the corresponding interface version bump. Backward + incompatible changes are done by bumping the version number in the protocol + and interface names and resetting the interface version. Once the protocol + is to be declared stable, the 'z' prefix and the version number in the + protocol and interface names are removed and the interface version number is + reset. + </description> + + <interface name="zwp_relative_pointer_manager_v1" version="1"> + <description summary="get relative pointer objects"> + A global interface used for getting the relative pointer object for a + given pointer. + </description> + + <request name="destroy" type="destructor"> + <description summary="destroy the relative pointer manager object"> + Used by the client to notify the server that it will no longer use this + relative pointer manager object. + </description> + </request> + + <request name="get_relative_pointer"> + <description summary="get a relative pointer object"> + Create a relative pointer interface given a wl_pointer object. See the + wp_relative_pointer interface for more details. + </description> + <arg name="id" type="new_id" interface="zwp_relative_pointer_v1"/> + <arg name="pointer" type="object" interface="wl_pointer"/> + </request> + </interface> + + <interface name="zwp_relative_pointer_v1" version="1"> + <description summary="relative pointer object"> + A wp_relative_pointer object is an extension to the wl_pointer interface + used for emitting relative pointer events. It shares the same focus as + wl_pointer objects of the same seat and will only emit events when it has + focus. + </description> + + <request name="destroy" type="destructor"> + <description summary="release the relative pointer object"/> + </request> + + <event name="relative_motion"> + <description summary="relative pointer motion"> + Relative x/y pointer motion from the pointer of the seat associated with + this object. + + A relative motion is in the same dimension as regular wl_pointer motion + events, except they do not represent an absolute position. For example, + moving a pointer from (x, y) to (x', y') would have the equivalent + relative motion (x' - x, y' - y). If a pointer motion caused the + absolute pointer position to be clipped by for example the edge of the + monitor, the relative motion is unaffected by the clipping and will + represent the unclipped motion. + + This event also contains non-accelerated motion deltas. The + non-accelerated delta is, when applicable, the regular pointer motion + delta as it was before having applied motion acceleration and other + transformations such as normalization. + + Note that the non-accelerated delta does not represent 'raw' events as + they were read from some device. Pointer motion acceleration is device- + and configuration-specific and non-accelerated deltas and accelerated + deltas may have the same value on some devices. + + Relative motions are not coupled to wl_pointer.motion events, and can be + sent in combination with such events, but also independently. There may + also be scenarios where wl_pointer.motion is sent, but there is no + relative motion. The order of an absolute and relative motion event + originating from the same physical motion is not guaranteed. + + If the client needs button events or focus state, it can receive them + from a wl_pointer object of the same seat that the wp_relative_pointer + object is associated with. + </description> + <arg name="utime_hi" type="uint" + summary="high 32 bits of a 64 bit timestamp with microsecond granularity"/> + <arg name="utime_lo" type="uint" + summary="low 32 bits of a 64 bit timestamp with microsecond granularity"/> + <arg name="dx" type="fixed" + summary="the x component of the motion vector"/> + <arg name="dy" type="fixed" + summary="the y component of the motion vector"/> + <arg name="dx_unaccel" type="fixed" + summary="the x component of the unaccelerated motion vector"/> + <arg name="dy_unaccel" type="fixed" + summary="the y component of the unaccelerated motion vector"/> + </event> + </interface> + +</protocol> diff --git a/dlls/winewayland.drv/wayland.c b/dlls/winewayland.drv/wayland.c index 066e9e7c963..be7f96920bf 100644 --- a/dlls/winewayland.drv/wayland.c +++ b/dlls/winewayland.drv/wayland.c @@ -159,6 +159,11 @@ static void registry_handle_global(void *data, struct wl_registry *registry, process_wayland.zwp_pointer_constraints_v1 = wl_registry_bind(registry, id, &zwp_pointer_constraints_v1_interface, 1); } + else if (strcmp(interface, "zwp_relative_pointer_manager_v1") == 0) + { + process_wayland.zwp_relative_pointer_manager_v1 = + wl_registry_bind(registry, id, &zwp_relative_pointer_manager_v1_interface, 1); + } }
static void registry_handle_global_remove(void *data, struct wl_registry *registry, @@ -269,6 +274,11 @@ BOOL wayland_process_init(void) ERR("Wayland compositor doesn't support zwp_pointer_constraints_v1\n"); return FALSE; } + if (!process_wayland.zwp_relative_pointer_manager_v1) + { + ERR("Wayland compositor doesn't support zwp_relative_pointer_manager_v1\n"); + return FALSE; + }
wayland_init_display_devices(FALSE);
diff --git a/dlls/winewayland.drv/wayland_pointer.c b/dlls/winewayland.drv/wayland_pointer.c index 59749563409..c422e7e958e 100644 --- a/dlls/winewayland.drv/wayland_pointer.c +++ b/dlls/winewayland.drv/wayland_pointer.c @@ -47,8 +47,7 @@ static HWND wayland_pointer_get_focused_hwnd(void) return hwnd; }
-static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, - uint32_t time, wl_fixed_t sx, wl_fixed_t sy) +static void pointer_handle_motion_internal(wl_fixed_t sx, wl_fixed_t sy) { INPUT input = {0}; RECT *window_rect; @@ -91,6 +90,17 @@ static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, __wine_send_input(hwnd, &input, NULL); }
+static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, + uint32_t time, wl_fixed_t sx, wl_fixed_t sy) +{ + struct wayland_pointer *pointer = &process_wayland.pointer; + + /* Ignore absolute motion events if in relative mode. */ + if (pointer->zwp_relative_pointer_v1) return; + + pointer_handle_motion_internal(sx, sy); +} + static void wayland_set_cursor(HWND hwnd, HCURSOR hcursor, BOOL use_hcursor);
static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, @@ -119,7 +129,7 @@ static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, /* Handle the enter as a motion, to account for cases where the * window first appears beneath the pointer and won't get a separate * motion event. */ - pointer_handle_motion(data, wl_pointer, 0, sx, sy); + pointer_handle_motion_internal(sx, sy); }
static void pointer_handle_leave(void *data, struct wl_pointer *wl_pointer, @@ -229,6 +239,83 @@ static const struct wl_pointer_listener pointer_listener = pointer_handle_axis_discrete };
+static void relative_pointer_v1_relative_motion(void *data, + struct zwp_relative_pointer_v1 *zwp_relative_pointer_v1, + uint32_t utime_hi, uint32_t utime_lo, + wl_fixed_t dx, wl_fixed_t dy, + wl_fixed_t dx_unaccel, wl_fixed_t dy_unaccel) +{ + INPUT input = {0}; + HWND hwnd; + POINT screen, origin; + struct wayland_surface *surface; + RECT window_rect; + + if (!(hwnd = wayland_pointer_get_focused_hwnd())) return; + if (!(surface = wayland_surface_lock_hwnd(hwnd))) return; + + window_rect = surface->window.rect; + + wayland_surface_coords_to_window(surface, + wl_fixed_to_double(dx), + wl_fixed_to_double(dy), + (int *)&screen.x, (int *)&screen.y); + + pthread_mutex_unlock(&surface->mutex); + + /* We clip the relative motion within the window rectangle so that + * the NtUserLogicalToPerMonitorDPIPhysicalPoint calls later succeed. + * TODO: Avoid clipping by using a more versatile dpi mapping function. */ + if (screen.x >= 0) + { + origin.x = window_rect.left; + screen.x += origin.x; + if (screen.x >= window_rect.right) screen.x = window_rect.right - 1; + } + else + { + origin.x = window_rect.right; + screen.x += origin.x; + if (screen.x < window_rect.left) screen.x = window_rect.left; + } + + if (screen.y >= 0) + { + origin.y = window_rect.top; + screen.y += origin.y; + if (screen.y >= window_rect.bottom) screen.y = window_rect.bottom - 1; + } + else + { + origin.y = window_rect.bottom; + screen.y += origin.y; + if (screen.y < window_rect.top) screen.y = window_rect.top; + } + + /* Transform the relative motion from window coordinates to physical + * coordinates required for the input event. */ + if (!NtUserLogicalToPerMonitorDPIPhysicalPoint(hwnd, &screen)) return; + if (!NtUserLogicalToPerMonitorDPIPhysicalPoint(hwnd, &origin)) return; + screen.x -= origin.x; + screen.y -= origin.y; + + input.type = INPUT_MOUSE; + input.mi.dx = screen.x; + input.mi.dy = screen.y; + input.mi.dwFlags = MOUSEEVENTF_MOVE; + + TRACE("hwnd=%p wayland_dxdy=%.2f,%.2f screen_dxdy=%d,%d\n", + hwnd, wl_fixed_to_double(dx), wl_fixed_to_double(dy), + (int)screen.x, (int)screen.y); + + __wine_send_input(hwnd, &input, NULL); +} + +static const struct zwp_relative_pointer_v1_listener relative_pointer_v1_listener = +{ + relative_pointer_v1_relative_motion +}; + void wayland_pointer_init(struct wl_pointer *wl_pointer) { struct wayland_pointer *pointer = &process_wayland.pointer; @@ -251,6 +338,11 @@ void wayland_pointer_deinit(void) zwp_confined_pointer_v1_destroy(pointer->zwp_confined_pointer_v1); pointer->zwp_confined_pointer_v1 = NULL; } + if (pointer->zwp_relative_pointer_v1) + { + zwp_relative_pointer_v1_destroy(pointer->zwp_relative_pointer_v1); + pointer->zwp_relative_pointer_v1 = NULL; + } wl_pointer_release(pointer->wl_pointer); pointer->wl_pointer = NULL; pointer->focused_hwnd = NULL; @@ -558,6 +650,8 @@ static void wayland_set_cursor(HWND hwnd, HCURSOR hcursor, BOOL use_hcursor) struct wayland_pointer *pointer = &process_wayland.pointer; struct wayland_surface *surface; double scale; + RECT clip; + BOOL reapply_clip = FALSE;
if ((surface = wayland_surface_lock_hwnd(hwnd))) { @@ -580,8 +674,13 @@ static void wayland_set_cursor(HWND hwnd, HCURSOR hcursor, BOOL use_hcursor) pointer->cursor.hotspot_x, pointer->cursor.hotspot_y); wl_display_flush(process_wayland.wl_display); + reapply_clip = TRUE; } pthread_mutex_unlock(&pointer->mutex); + + /* Reapply cursor clip since cursor visibility affects pointer constraint + * behavior. */ + if (reapply_clip && NtUserGetClipCursor(&clip)) NtUserClipCursor(&clip); }
/********************************************************************** @@ -630,6 +729,7 @@ static void wayland_pointer_update_constraint(RECT *confine_rect, struct wl_surface *wl_surface) { struct wayland_pointer *pointer = &process_wayland.pointer; + BOOL needs_relative;
assert(!confine_rect || wl_surface);
@@ -677,6 +777,27 @@ static void wayland_pointer_update_constraint(RECT *confine_rect, pointer->zwp_confined_pointer_v1 = NULL; pointer->constraint_hwnd = NULL; } + + needs_relative = !pointer->cursor.wl_surface && + pointer->constraint_hwnd && + pointer->constraint_hwnd == pointer->focused_hwnd; + + if (needs_relative && !pointer->zwp_relative_pointer_v1) + { + pointer->zwp_relative_pointer_v1 = + zwp_relative_pointer_manager_v1_get_relative_pointer( + process_wayland.zwp_relative_pointer_manager_v1, + pointer->wl_pointer); + zwp_relative_pointer_v1_add_listener(pointer->zwp_relative_pointer_v1, + &relative_pointer_v1_listener, NULL); + TRACE("Enabling relative motion\n"); + } + else if (!needs_relative && pointer->zwp_relative_pointer_v1) + { + zwp_relative_pointer_v1_destroy(pointer->zwp_relative_pointer_v1); + pointer->zwp_relative_pointer_v1 = NULL; + TRACE("Disabling relative motion\n"); + } }
void wayland_pointer_clear_constraint(void) diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index 929481aaa35..8a8a4fe798b 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -30,6 +30,7 @@ #include <xkbcommon/xkbcommon.h> #include <xkbcommon/xkbregistry.h> #include "pointer-constraints-unstable-v1-client-protocol.h" +#include "relative-pointer-unstable-v1-client-protocol.h" #include "viewporter-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h" #include "xdg-shell-client-protocol.h" @@ -91,6 +92,7 @@ struct wayland_pointer { struct wl_pointer *wl_pointer; struct zwp_confined_pointer_v1 *zwp_confined_pointer_v1; + struct zwp_relative_pointer_v1 *zwp_relative_pointer_v1; HWND focused_hwnd; HWND constraint_hwnd; uint32_t enter_serial; @@ -119,6 +121,7 @@ struct wayland struct wp_viewporter *wp_viewporter; struct wl_subcompositor *wl_subcompositor; struct zwp_pointer_constraints_v1 *zwp_pointer_constraints_v1; + struct zwp_relative_pointer_manager_v1 *zwp_relative_pointer_manager_v1; struct wayland_seat seat; struct wayland_keyboard keyboard; struct wayland_pointer pointer;
From: Alexandros Frantzis alexandros.frantzis@collabora.com
Pointer confinement may only be enabled by the compositor if the pointer enters the confine region. If the region is small (as in many mouselook cases) it's very likely that this will never happen and the pointer will remained unconfined.
To allow mouselook to work more reliably, prefer to lock the pointer if a window is confined and the cursor is not visible. --- dlls/winewayland.drv/wayland_pointer.c | 50 ++++++++++++++++++++++---- dlls/winewayland.drv/waylanddrv.h | 1 + 2 files changed, 44 insertions(+), 7 deletions(-)
diff --git a/dlls/winewayland.drv/wayland_pointer.c b/dlls/winewayland.drv/wayland_pointer.c index c422e7e958e..ccd477b3c91 100644 --- a/dlls/winewayland.drv/wayland_pointer.c +++ b/dlls/winewayland.drv/wayland_pointer.c @@ -338,6 +338,11 @@ void wayland_pointer_deinit(void) zwp_confined_pointer_v1_destroy(pointer->zwp_confined_pointer_v1); pointer->zwp_confined_pointer_v1 = NULL; } + if (pointer->zwp_locked_pointer_v1) + { + zwp_locked_pointer_v1_destroy(pointer->zwp_locked_pointer_v1); + pointer->zwp_locked_pointer_v1 = NULL; + } if (pointer->zwp_relative_pointer_v1) { zwp_relative_pointer_v1_destroy(pointer->zwp_relative_pointer_v1); @@ -729,11 +734,30 @@ static void wayland_pointer_update_constraint(RECT *confine_rect, struct wl_surface *wl_surface) { struct wayland_pointer *pointer = &process_wayland.pointer; - BOOL needs_relative; + BOOL needs_relative, needs_lock, needs_confine;
assert(!confine_rect || wl_surface);
- if (confine_rect) + needs_lock = confine_rect && !pointer->cursor.wl_surface; + needs_confine = confine_rect && pointer->cursor.wl_surface; + + if (!needs_confine && pointer->zwp_confined_pointer_v1) + { + TRACE("Unconfining from hwnd=%p\n", pointer->constraint_hwnd); + zwp_confined_pointer_v1_destroy(pointer->zwp_confined_pointer_v1); + pointer->zwp_confined_pointer_v1 = NULL; + pointer->constraint_hwnd = NULL; + } + + if (!needs_lock && pointer->zwp_locked_pointer_v1) + { + TRACE("Unlocking from hwnd=%p\n", pointer->constraint_hwnd); + zwp_locked_pointer_v1_destroy(pointer->zwp_locked_pointer_v1); + pointer->zwp_locked_pointer_v1 = NULL; + pointer->constraint_hwnd = NULL; + } + + if (needs_confine) { HWND hwnd = wl_surface_get_user_data(wl_surface); struct wl_region *region; @@ -770,12 +794,24 @@ static void wayland_pointer_update_constraint(RECT *confine_rect,
wl_region_destroy(region); } - else if (pointer->zwp_confined_pointer_v1) + else if (needs_lock) { - TRACE("Unconfining from hwnd=%p\n", pointer->constraint_hwnd); - zwp_confined_pointer_v1_destroy(pointer->zwp_confined_pointer_v1); - pointer->zwp_confined_pointer_v1 = NULL; - pointer->constraint_hwnd = NULL; + HWND hwnd = wl_surface_get_user_data(wl_surface); + + if (!pointer->zwp_locked_pointer_v1 || pointer->constraint_hwnd != hwnd) + { + if (pointer->zwp_locked_pointer_v1) + zwp_locked_pointer_v1_destroy(pointer->zwp_locked_pointer_v1); + pointer->zwp_locked_pointer_v1 = + zwp_pointer_constraints_v1_lock_pointer( + process_wayland.zwp_pointer_constraints_v1, + wl_surface, + pointer->wl_pointer, + NULL, + ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); + pointer->constraint_hwnd = hwnd; + TRACE("Locking to hwnd=%p\n", pointer->constraint_hwnd); + } }
needs_relative = !pointer->cursor.wl_surface && diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index 8a8a4fe798b..c6db31369b2 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -92,6 +92,7 @@ struct wayland_pointer { struct wl_pointer *wl_pointer; struct zwp_confined_pointer_v1 *zwp_confined_pointer_v1; + struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1; struct zwp_relative_pointer_v1 *zwp_relative_pointer_v1; HWND focused_hwnd; HWND constraint_hwnd;
This merge request was approved by Rémi Bernon.
On Thu Dec 7 18:42:26 2023 +0000, Rémi Bernon wrote:
Looks good to me but would you mind dropping the win32u change? I think it's probably correct but I would be more comfortable if that was fixed separately. I have reasons to believe that win32u window activation code is incorrect and needs fixing, and that could be part of it.
v5: * Drop set foreground window win32u fix.
Would you like me to propose this separately, or do you plan to include a fix along with the activation code changes you mention?
If we don't expect some fix for this to land before the end of year, and since the driver is particularly susceptible to this (due to calling `NtUserSetForegroundWindow` from the separate, dispatch thread), perhaps it would be worth implementing a temporary mitigation for this issue in the Wayland driver. I was thinking of using a driver internal message to set the foreground window from within the proper window thread (I haven't tried it yet).
On Thu Dec 7 18:42:26 2023 +0000, Alexandros Frantzis wrote:
v5:
- Drop set foreground window win32u fix.
Would you like me to propose this separately, or do you plan to include a fix along with the activation code changes you mention? If we don't expect some fix for this to land before the end of year, and since the driver is particularly susceptible to this (due to calling `NtUserSetForegroundWindow` from the separate, dispatch thread), perhaps it would be worth implementing a temporary mitigation for this issue in the Wayland driver. I was thinking of using a driver internal message to set the foreground window from within the proper window thread (I haven't tried it yet).
Well, code freeze is tomorrow so I don't intend to do anything soon.
You can submit it in a separate MR if you think it's critical for the well behaving of the driver, and then @julliard will decide what to do with it, but I would personally wait until we try to fix these activation issues.