From bf1cabd18fdce203176d783976b8347222ac621e Mon Sep 17 00:00:00 2001 From: Alexandros Frantzis Date: Thu, 30 Nov 2023 11:03:11 +0200 Subject: [PATCH] winewayland.drv: Implement ClipCursor. 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 @@ + + + + + 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. + + + + 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. + + + + + 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. + + + + + These errors can be emitted in response to wp_pointer_constraints + requests. + + + + + + + These values represent different lifetime semantics. They are passed + as arguments to the factory requests to specify how the constraint + lifetimes should be managed. + + + + 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. + + + + + 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. + + + + + + + Used by the client to notify the server that it will no longer use this + pointer constraints object. + + + + + + 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. + + + + + + + + + + + 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. + + + + + + + + + + + + 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. + + + + + Destroy the locked pointer object. If applicable, the compositor will + unlock the pointer. + + + + + + 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. + + + + + + + + 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. + + + + + + + Notification that the pointer lock of the seat's pointer is activated. + + + + + + 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. + + + + + + + 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. + + + + + Destroy the confined pointer object. If applicable, the compositor will + unconfine the pointer. + + + + + + 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. + + + + + + + Notification that the pointer confinement of the seat's pointer is + activated. + + + + + + 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. + + + + + 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 #include #undef SW_MAX /* Also defined in winuser.rh */ #include @@ -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 #include #include +#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;