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!
-- v2: win32u: Don't deactivate the target thread when setting the foreground window. winewayland.drv: Prefer to lock the pointer if the confine area is small. winewayland.drv: Implement relative mouse motion. winewayland.drv: Implement ClipCursor.
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 | 107 ++++++ dlls/winewayland.drv/wayland_surface.c | 64 ++++ dlls/winewayland.drv/waylanddrv.h | 16 + dlls/winewayland.drv/waylanddrv_main.c | 1 + dlls/winewayland.drv/window.c | 18 + 8 files changed, 556 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..2d25a518b57 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,71 @@ static void wayland_set_cursor(HWND hwnd, HCURSOR hcursor, BOOL use_hcursor) pthread_mutex_unlock(&pointer->mutex); }
+/*********************************************************************** + * wayland_pointer_update_constraint + * + * Updates the pointer constraint. + * + * If the constraint type is not WAYLAND_POINTER_CONSTRAINT_NONE both wl_surface + * and confine_rect arguments must point to valid objects. + */ +void wayland_pointer_update_constraint(enum wayland_pointer_constraint constraint, + struct wl_surface *wl_surface, + RECT *confine_rect) +{ + struct wayland_pointer *pointer = &process_wayland.pointer; + + if (constraint == WAYLAND_POINTER_CONSTRAINT_CONFINE) + { + HWND hwnd; + struct wl_region *region; + + assert(wl_surface); + assert(confine_rect); + + hwnd = wl_surface_get_user_data(wl_surface); + + 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; + } +} + /*********************************************************************** * WAYLAND_SetCursor */ @@ -587,3 +658,39 @@ 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; + enum wayland_pointer_constraint constraint = WAYLAND_POINTER_CONSTRAINT_NONE; + 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; + constraint = wayland_surface_calc_constraint(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(constraint, wl_surface, &confine_rect); + 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..7b42da1f738 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_update_constraint(WAYLAND_POINTER_CONSTRAINT_NONE, NULL, NULL); pthread_mutex_unlock(&process_wayland.pointer.mutex);
pthread_mutex_lock(&process_wayland.keyboard.mutex); @@ -895,3 +897,65 @@ void wayland_surface_ensure_contents(struct wayland_surface *surface)
if (damage) NtGdiDeleteObjectApp(damage); } + +/********************************************************************** + * wayland_surface_calc_constraint + * + * Calculates the pointer constraint for the surface for the specified clip + * rectangle (in screen coords using thread dpi). + * + * If the return value is not WAYLAND_POINTER_CONSTRAINT_NONE, then the + * 'confine' argument is populated with the confine rect in surface-local coords. + */ +enum wayland_pointer_constraint wayland_surface_calc_constraint(struct wayland_surface *surface, + const RECT *clip, RECT *confine) +{ + RECT vscreen_rect, window_clip; + + /* Get individual system metrics to get coords in thread dpi + * (NtUserGetVirtualScreenRect would return values in system dpi). */ + vscreen_rect.left = NtUserGetSystemMetrics(SM_XVIRTUALSCREEN); + vscreen_rect.top = NtUserGetSystemMetrics(SM_YVIRTUALSCREEN); + vscreen_rect.right = vscreen_rect.left + + NtUserGetSystemMetrics(SM_CXVIRTUALSCREEN); + vscreen_rect.bottom = vscreen_rect.top + + NtUserGetSystemMetrics(SM_CYVIRTUALSCREEN); + + TRACE("hwnd=%p clip=%s vscreen=%s window=%s client=%s\n", + surface->hwnd, wine_dbgstr_rect(clip), + wine_dbgstr_rect(&vscreen_rect), + wine_dbgstr_rect(&surface->window.rect), + wine_dbgstr_rect(&surface->window.client_rect)); + + /* FIXME: surface->window.(client_)rect is in window dpi, whereas + * clip and vscreen_rect are in thread dpi. */ + + /* If the clip is the whole virtual screen don't constrain, + * unless we have a fullscreen surface. Although we don't strictly + * need the constraint in this latter case, it allows us to + * be more consistent. */ + if ((EqualRect(clip, &vscreen_rect) && + !EqualRect(&surface->window.client_rect, &vscreen_rect))) + { + return WAYLAND_POINTER_CONSTRAINT_NONE; + } + + /* If the clip doesn't overlap with the window don't constrain. */ + if (!intersect_rect(&window_clip, clip, &surface->window.rect) && + !IsRectEmpty(clip)) + { + return WAYLAND_POINTER_CONSTRAINT_NONE; + } + + 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); + + return WAYLAND_POINTER_CONSTRAINT_CONFINE; +} diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index 08abc247e16..56f8c1d6dbb 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" @@ -69,6 +70,12 @@ enum wayland_surface_config_state WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN = (1 << 3) };
+enum wayland_pointer_constraint +{ + WAYLAND_POINTER_CONSTRAINT_NONE = 0, + WAYLAND_POINTER_CONSTRAINT_CONFINE, +}; + struct wayland_keyboard { struct wl_keyboard *wl_keyboard; @@ -89,7 +96,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 +124,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; @@ -246,6 +256,8 @@ void wayland_surface_coords_to_window(struct wayland_surface *surface, struct wayland_client_surface *wayland_surface_get_client(struct wayland_surface *surface); BOOL wayland_client_surface_release(struct wayland_client_surface *client); void wayland_surface_ensure_contents(struct wayland_surface *surface); +enum wayland_pointer_constraint wayland_surface_calc_constraint(struct wayland_surface *surface, + const RECT *clip, RECT *confine);
/********************************************************************** * Wayland SHM buffer @@ -280,6 +292,9 @@ void WAYLAND_ReleaseKbdTables(const KBDTABLES *);
void wayland_pointer_init(struct wl_pointer *wl_pointer); void wayland_pointer_deinit(void); +void wayland_pointer_update_constraint(enum wayland_pointer_constraint constraint, + struct wl_surface *wl_surface, + RECT *confine_rect);
/********************************************************************** * Helpers @@ -305,6 +320,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..61e69090051 100644 --- a/dlls/winewayland.drv/window.c +++ b/dlls/winewayland.drv/window.c @@ -194,6 +194,8 @@ 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_rect, confine_rect; + enum wayland_pointer_constraint constraint;
TRACE("hwnd=%p\n", data->hwnd);
@@ -232,6 +234,22 @@ 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_rect)) + { + pthread_mutex_lock(&surface->mutex); + constraint = wayland_surface_calc_constraint(surface, &clip_rect, + &confine_rect); + pthread_mutex_unlock(&surface->mutex); + + pthread_mutex_lock(&process_wayland.pointer.mutex); + wayland_pointer_update_constraint(constraint, surface->wl_surface, + &confine_rect); + pthread_mutex_unlock(&process_wayland.pointer.mutex); + } + 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 2d25a518b57..fdae170e529 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); }
/*********************************************************************** @@ -597,6 +696,7 @@ void wayland_pointer_update_constraint(enum wayland_pointer_constraint constrain RECT *confine_rect) { struct wayland_pointer *pointer = &process_wayland.pointer; + BOOL needs_relative;
if (constraint == WAYLAND_POINTER_CONSTRAINT_CONFINE) { @@ -647,6 +747,27 @@ void wayland_pointer_update_constraint(enum wayland_pointer_constraint constrain 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"); + } }
/*********************************************************************** diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index 56f8c1d6dbb..b1a178d92b1 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" @@ -97,6 +98,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; @@ -125,6 +127,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 the confine area is smaller than the client area and the cursor is not visible. --- dlls/winewayland.drv/wayland_pointer.c | 59 +++++++++++++++++++++++--- dlls/winewayland.drv/wayland_surface.c | 14 +++++- dlls/winewayland.drv/waylanddrv.h | 2 + 3 files changed, 68 insertions(+), 7 deletions(-)
diff --git a/dlls/winewayland.drv/wayland_pointer.c b/dlls/winewayland.drv/wayland_pointer.c index fdae170e529..741ebfc750d 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); @@ -698,6 +703,33 @@ void wayland_pointer_update_constraint(enum wayland_pointer_constraint constrain struct wayland_pointer *pointer = &process_wayland.pointer; BOOL needs_relative;
+ /* Do not lock the pointer if the cursor is visible. */ + if (constraint == WAYLAND_POINTER_CONSTRAINT_MAYBE_LOCK && + pointer->cursor.wl_surface) + { + constraint = WAYLAND_POINTER_CONSTRAINT_CONFINE; + } + + if (constraint != WAYLAND_POINTER_CONSTRAINT_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; + if (constraint == WAYLAND_POINTER_CONSTRAINT_NONE) + pointer->constraint_hwnd = NULL; + } + + if (constraint != WAYLAND_POINTER_CONSTRAINT_MAYBE_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; + if (constraint == WAYLAND_POINTER_CONSTRAINT_NONE) + pointer->constraint_hwnd = NULL; + } + if (constraint == WAYLAND_POINTER_CONSTRAINT_CONFINE) { HWND hwnd; @@ -740,12 +772,29 @@ void wayland_pointer_update_constraint(enum wayland_pointer_constraint constrain
wl_region_destroy(region); } - else if (pointer->zwp_confined_pointer_v1) + else if (constraint == WAYLAND_POINTER_CONSTRAINT_MAYBE_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; + + assert(wl_surface); + assert(confine_rect); + + 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/wayland_surface.c b/dlls/winewayland.drv/wayland_surface.c index 7b42da1f738..6a75bce9a6b 100644 --- a/dlls/winewayland.drv/wayland_surface.c +++ b/dlls/winewayland.drv/wayland_surface.c @@ -910,7 +910,8 @@ void wayland_surface_ensure_contents(struct wayland_surface *surface) enum wayland_pointer_constraint wayland_surface_calc_constraint(struct wayland_surface *surface, const RECT *clip, RECT *confine) { - RECT vscreen_rect, window_clip; + RECT vscreen_rect, window_clip, tmp; + enum wayland_pointer_constraint constraint;
/* Get individual system metrics to get coords in thread dpi * (NtUserGetVirtualScreenRect would return values in system dpi). */ @@ -947,6 +948,15 @@ enum wayland_pointer_constraint wayland_surface_calc_constraint(struct wayland_s return WAYLAND_POINTER_CONSTRAINT_NONE; }
+ /* If the confine rect is smaller than the client area prefer to + * lock the pointer if possible, to make the constraint easy + * to enable. */ + intersect_rect(&tmp, &window_clip, &surface->window.client_rect); + if (EqualRect(&tmp, &surface->window.client_rect)) + constraint = WAYLAND_POINTER_CONSTRAINT_CONFINE; + else + constraint = WAYLAND_POINTER_CONSTRAINT_MAYBE_LOCK; + OffsetRect(&window_clip, -surface->window.rect.left, -surface->window.rect.top); @@ -957,5 +967,5 @@ enum wayland_pointer_constraint wayland_surface_calc_constraint(struct wayland_s window_clip.right, window_clip.bottom, (int *)&confine->right, (int *)&confine->bottom);
- return WAYLAND_POINTER_CONSTRAINT_CONFINE; + return constraint; } diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index b1a178d92b1..48cfd56bd9e 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -75,6 +75,7 @@ enum wayland_pointer_constraint { WAYLAND_POINTER_CONSTRAINT_NONE = 0, WAYLAND_POINTER_CONSTRAINT_CONFINE, + WAYLAND_POINTER_CONSTRAINT_MAYBE_LOCK };
struct wayland_keyboard @@ -98,6 +99,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;
From: Alexandros Frantzis alexandros.frantzis@collabora.com
If when updating the foreground window, both the old and new active window belong to the same thread, even if it's not the current thread, activate the new window without explicitly deactivating the previous one. This matches the current behavior of the code when both windows belong to the current thread.
This avoids the transient deactivation of the target thread which can lead to unwanted side effects (e.g., some apps may minimize when they become inactive). --- dlls/win32u/input.c | 16 ++++++++++------ include/wine/server_protocol.h | 4 ++-- server/protocol.def | 9 +++++---- server/queue.c | 2 ++ server/request.h | 1 + server/trace.c | 1 + 6 files changed, 21 insertions(+), 12 deletions(-)
diff --git a/dlls/win32u/input.c b/dlls/win32u/input.c index 3ee46f0bfcf..a04c8980b92 100644 --- a/dlls/win32u/input.c +++ b/dlls/win32u/input.c @@ -2056,7 +2056,7 @@ HWND WINAPI NtUserSetFocus( HWND hwnd ) */ BOOL set_foreground_window( HWND hwnd, BOOL mouse ) { - BOOL ret, send_msg_old = FALSE, send_msg_new = FALSE; + BOOL ret, send_msg_old = FALSE, send_msg_new = FALSE, deactivate_old = FALSE; HWND previous = 0;
if (mouse) hwnd = get_full_window_handle( hwnd ); @@ -2069,17 +2069,21 @@ BOOL set_foreground_window( HWND hwnd, BOOL mouse ) previous = wine_server_ptr_handle( reply->previous ); send_msg_old = reply->send_msg_old; send_msg_new = reply->send_msg_new; + deactivate_old = reply->deactivate_old; } } SERVER_END_REQ;
if (ret && previous != hwnd) { - if (send_msg_old) /* old window belongs to other thread */ - NtUserMessageCall( previous, WM_WINE_SETACTIVEWINDOW, 0, 0, - 0, NtUserSendNotifyMessage, FALSE ); - else if (send_msg_new) /* old window belongs to us but new one to other thread */ - ret = set_active_window( 0, NULL, mouse, TRUE ); + if (deactivate_old) + { + if (send_msg_old) /* old window belongs to other thread */ + NtUserMessageCall( previous, WM_WINE_SETACTIVEWINDOW, 0, 0, + 0, NtUserSendNotifyMessage, FALSE ); + else if (send_msg_new) /* old window belongs to us but new one to other thread */ + ret = set_active_window( 0, NULL, mouse, TRUE ); + }
if (send_msg_new) /* new window belongs to other thread */ NtUserMessageCall( hwnd, WM_WINE_SETACTIVEWINDOW, (WPARAM)hwnd, 0, diff --git a/include/wine/server_protocol.h b/include/wine/server_protocol.h index 139a7bca69e..0870a631fbe 100644 --- a/include/wine/server_protocol.h +++ b/include/wine/server_protocol.h @@ -4050,7 +4050,7 @@ struct set_foreground_window_reply user_handle_t previous; int send_msg_old; int send_msg_new; - char __pad_20[4]; + int deactivate_old; };
@@ -6507,7 +6507,7 @@ union generic_reply
/* ### protocol_version begin ### */
-#define SERVER_PROTOCOL_VERSION 786 +#define SERVER_PROTOCOL_VERSION 788
/* ### protocol_version end ### */
diff --git a/server/protocol.def b/server/protocol.def index 5d60e7fcda3..2ea410afea7 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -2900,11 +2900,12 @@ enum coords_relative
/* Set the system foreground window */ @REQ(set_foreground_window) - user_handle_t handle; /* handle to the foreground window */ + user_handle_t handle; /* handle to the foreground window */ @REPLY - user_handle_t previous; /* handle to the previous foreground window */ - int send_msg_old; /* whether we have to send a msg to the old window */ - int send_msg_new; /* whether we have to send a msg to the new window */ + user_handle_t previous; /* handle to the previous foreground window */ + int send_msg_old; /* whether we have to send a msg to the old window */ + int send_msg_new; /* whether we have to send a msg to the new window */ + int deactivate_old; /* whether we have to explicitly deactivate the old window */ @END
/* Set the current thread focus window */ diff --git a/server/queue.c b/server/queue.c index 0558bd111f9..0d19039e05f 100644 --- a/server/queue.c +++ b/server/queue.c @@ -3216,11 +3216,13 @@ DECL_HANDLER(set_foreground_window) reply->previous = desktop->foreground_input ? desktop->foreground_input->active : 0; reply->send_msg_old = (reply->previous && desktop->foreground_input != queue->input); reply->send_msg_new = FALSE; + reply->deactivate_old = FALSE;
if (is_valid_foreground_window( req->handle ) && (thread = get_window_thread( req->handle )) && thread->queue->input->desktop == desktop) { + reply->deactivate_old = (desktop->foreground_input != thread->queue->input); set_foreground_input( desktop, thread->queue->input ); reply->send_msg_new = (desktop->foreground_input != queue->input); } diff --git a/server/request.h b/server/request.h index 89d5a621b16..5185229b483 100644 --- a/server/request.h +++ b/server/request.h @@ -1808,6 +1808,7 @@ C_ASSERT( sizeof(struct set_foreground_window_request) == 16 ); C_ASSERT( FIELD_OFFSET(struct set_foreground_window_reply, previous) == 8 ); C_ASSERT( FIELD_OFFSET(struct set_foreground_window_reply, send_msg_old) == 12 ); C_ASSERT( FIELD_OFFSET(struct set_foreground_window_reply, send_msg_new) == 16 ); +C_ASSERT( FIELD_OFFSET(struct set_foreground_window_reply, deactivate_old) == 20 ); C_ASSERT( sizeof(struct set_foreground_window_reply) == 24 ); C_ASSERT( FIELD_OFFSET(struct set_focus_window_request, handle) == 12 ); C_ASSERT( sizeof(struct set_focus_window_request) == 16 ); diff --git a/server/trace.c b/server/trace.c index 1b65d2b977e..141acebb85b 100644 --- a/server/trace.c +++ b/server/trace.c @@ -3534,6 +3534,7 @@ static void dump_set_foreground_window_reply( const struct set_foreground_window fprintf( stderr, " previous=%08x", req->previous ); fprintf( stderr, ", send_msg_old=%d", req->send_msg_old ); fprintf( stderr, ", send_msg_new=%d", req->send_msg_new ); + fprintf( stderr, ", deactivate_old=%d", req->deactivate_old ); }
static void dump_set_focus_window_request( const struct set_focus_window_request *req )
v2 with several significant changes:
* Added forgotten protocol .xml files * Added comment to explain why we clip relative motion dx/dy to window size. * Ignore 'reset' value, only check for a valid 'clip' in ClipCursor. * Use the foreground window (instead of current pointer focus) as the target of clipping. * Ensure we get the virtual screen rect in thread dpi. * Lock instead of confining the pointer if the confine area is smaller than the client area and the cursor is hidden, to make it easier to actually enable the constraints for mouselook. * Take pointer constraint state into account to decide whether to switch to/from relative motion. * Update the pointer constraint when the window size/position is updated.
The last point required some code refactoring (see new functions `wayland_surface_calc_constraint` and `wayland_pointer_update_constraint`, to make the constraints related functionality available outside of `wayland_pointer.c` and in a manner that allows us to still avoid double locking of wayland_surface/wayland_pointer.
The last point required some code refactoring (see new functions wayland_surface_calc_constraint and wayland_pointer_update_constraint, to make the constraints related functionality available outside of wayland_pointer.c and in a manner that allows us to still avoid double locking of wayland_surface/wayland_pointer.
Although, as I write this I wonder if instead of this refactoring I could just reapply the cursor clip (like I have done on pointer enter), and basically get the same effect... hmmm...