diff --git a/docs/README-wayland.md b/docs/README-wayland.md index 6b4aa6ce2c..6dac845ca5 100644 --- a/docs/README-wayland.md +++ b/docs/README-wayland.md @@ -39,9 +39,10 @@ encounter limitations or behavior that is different from other windowing systems unknown. In most cases, applications don't actually need the global cursor position and should use the window-relative coordinates as provided by the mouse movement event or from ```SDL_GetMouseState()``` instead. -### Warping the global mouse cursor position via ```SDL_WarpMouseGlobal()``` doesn't work +### Warping the mouse cursor to or from a point outside the window doesn't work -- For security reasons, Wayland does not allow warping the global mouse cursor position. +- The cursor can be warped only within the window with mouse focus, provided that the `zwp_pointer_confinement_v1` + protocol is supported by the compositor. ### The application icon can't be set via ```SDL_SetWindowIcon()``` diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index 60ba7f3ca2..c8f12ce036 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -2995,19 +2995,21 @@ extern "C" { #define SDL_HINT_VIDEO_WAYLAND_ALLOW_LIBDECOR "SDL_VIDEO_WAYLAND_ALLOW_LIBDECOR" /** - * Enable or disable mouse pointer warp emulation, needed by some older games. + * Enable or disable hidden mouse pointer warp emulation, needed by some older games. * - * Wayland does not directly support warping the mouse. When this hint is set, - * any SDL will emulate mouse warps using relative mouse mode. This is - * required for some older games (such as Source engine games), which warp the - * mouse to the centre of the screen rather than using relative mouse motion. - * Note that relative mouse mode may have different mouse acceleration - * behaviour than pointer warps. + * Wayland requires the pointer confinement protocol to warp the mouse, but + * that is just a hint that the compositor is free to ignore, and warping the + * the pointer to or from regions outside of the focused window is prohibited. + * When this hint is set and the pointer is hidden, SDL will emulate mouse warps + * using relative mouse mode. This is required for some older games (such as Source + * engine games), which warp the mouse to the centre of the screen rather than using + * relative mouse motion. Note that relative mouse mode may have different mouse + * acceleration behaviour than pointer warps. * * The variable can be set to the following values: * - * - "0": All mouse warps fail, as mouse warping is not available under - * wayland. + * - "0": Attempts to warp the mouse will be made, if the appropriate protocol + * is available. * - "1": Some mouse warps will be emulated by forcing relative mouse mode. * * If not set, this is automatically enabled unless an application uses diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c index b8f23ead40..6e1be4c441 100644 --- a/src/video/wayland/SDL_waylandevents.c +++ b/src/video/wayland/SDL_waylandevents.c @@ -3197,32 +3197,47 @@ static const struct zwp_locked_pointer_v1_listener locked_pointer_listener = { locked_pointer_unlocked, }; -static void lock_pointer_to_window(SDL_Window *window, - struct SDL_WaylandInput *input) +int Wayland_input_lock_pointer(struct SDL_WaylandInput *input, SDL_Window *window) { SDL_WindowData *w = window->driverdata; SDL_VideoData *d = input->display; - struct zwp_locked_pointer_v1 *locked_pointer; if (!d->pointer_constraints || !input->pointer) { - return; + return -1; } + if (!w->locked_pointer) { + if (w->confined_pointer) { + /* If the pointer is already confined to the surface, the lock will fail with a protocol error. */ + Wayland_input_unconfine_pointer(input, window); + } + + w->locked_pointer = zwp_pointer_constraints_v1_lock_pointer(d->pointer_constraints, + w->surface, + input->pointer, + NULL, + ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); + zwp_locked_pointer_v1_add_listener(w->locked_pointer, + &locked_pointer_listener, + window); + } + + return 0; +} + +int Wayland_input_unlock_pointer(struct SDL_WaylandInput *input, SDL_Window *window) +{ + SDL_WindowData *w = window->driverdata; + if (w->locked_pointer) { - return; + zwp_locked_pointer_v1_destroy(w->locked_pointer); + w->locked_pointer = NULL; } - locked_pointer = - zwp_pointer_constraints_v1_lock_pointer(d->pointer_constraints, - w->surface, - input->pointer, - NULL, - ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); - zwp_locked_pointer_v1_add_listener(locked_pointer, - &locked_pointer_listener, - window); + /* Restore existing pointer confinement. */ + Wayland_input_confine_pointer(input, window); - w->locked_pointer = locked_pointer; + return 0; } static void pointer_confine_destroy(SDL_Window *window) @@ -3234,7 +3249,7 @@ static void pointer_confine_destroy(SDL_Window *window) } } -int Wayland_input_lock_pointer(struct SDL_WaylandInput *input) +int Wayland_input_enable_relative_pointer(struct SDL_WaylandInput *input) { SDL_VideoDevice *vd = SDL_GetVideoDevice(); SDL_VideoData *d = input->display; @@ -3261,10 +3276,7 @@ int Wayland_input_lock_pointer(struct SDL_WaylandInput *input) } if (!input->relative_pointer) { - relative_pointer = - zwp_relative_pointer_manager_v1_get_relative_pointer( - d->relative_pointer_manager, - input->pointer); + relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer(d->relative_pointer_manager, input->pointer); zwp_relative_pointer_v1_add_listener(relative_pointer, &relative_pointer_listener, input); @@ -3272,7 +3284,7 @@ int Wayland_input_lock_pointer(struct SDL_WaylandInput *input) } for (window = vd->windows; window; window = window->next) { - lock_pointer_to_window(window, input); + Wayland_input_lock_pointer(input, window); } d->relative_mouse_mode = 1; @@ -3280,19 +3292,14 @@ int Wayland_input_lock_pointer(struct SDL_WaylandInput *input) return 0; } -int Wayland_input_unlock_pointer(struct SDL_WaylandInput *input) +int Wayland_input_disable_relative_pointer(struct SDL_WaylandInput *input) { SDL_VideoDevice *vd = SDL_GetVideoDevice(); SDL_VideoData *d = input->display; SDL_Window *window; - SDL_WindowData *w; for (window = vd->windows; window; window = window->next) { - w = window->driverdata; - if (w->locked_pointer) { - zwp_locked_pointer_v1_destroy(w->locked_pointer); - } - w->locked_pointer = NULL; + Wayland_input_unlock_pointer(input, window); } if (input->relative_pointer) { diff --git a/src/video/wayland/SDL_waylandevents_c.h b/src/video/wayland/SDL_waylandevents_c.h index 2103209f00..5559901c4e 100644 --- a/src/video/wayland/SDL_waylandevents_c.h +++ b/src/video/wayland/SDL_waylandevents_c.h @@ -200,8 +200,11 @@ extern void Wayland_create_text_input(SDL_VideoData *d); extern void Wayland_input_initialize_seat(SDL_VideoData *d); extern void Wayland_display_destroy_input(SDL_VideoData *d); -extern int Wayland_input_lock_pointer(struct SDL_WaylandInput *input); -extern int Wayland_input_unlock_pointer(struct SDL_WaylandInput *input); +extern int Wayland_input_enable_relative_pointer(struct SDL_WaylandInput *input); +extern int Wayland_input_disable_relative_pointer(struct SDL_WaylandInput *input); + +extern int Wayland_input_lock_pointer(struct SDL_WaylandInput *input, SDL_Window *window); +extern int Wayland_input_unlock_pointer(struct SDL_WaylandInput *input, SDL_Window *window); extern int Wayland_input_confine_pointer(struct SDL_WaylandInput *input, SDL_Window *window); extern int Wayland_input_unconfine_pointer(struct SDL_WaylandInput *input, SDL_Window *window); diff --git a/src/video/wayland/SDL_waylandmouse.c b/src/video/wayland/SDL_waylandmouse.c index a95afae6e6..c5e1625289 100644 --- a/src/video/wayland/SDL_waylandmouse.c +++ b/src/video/wayland/SDL_waylandmouse.c @@ -36,6 +36,7 @@ #include "SDL_waylandshmbuffer.h" #include "cursor-shape-v1-client-protocol.h" +#include "pointer-constraints-unstable-v1-client-protocol.h" #include "../../SDL_hints_c.h" @@ -556,7 +557,7 @@ static int Wayland_ShowCursor(SDL_Cursor *cursor) input->cursor_visible = SDL_TRUE; if (input->relative_mode_override) { - Wayland_input_unlock_pointer(input); + Wayland_input_disable_relative_pointer(input); input->relative_mode_override = SDL_FALSE; } @@ -572,21 +573,62 @@ static int Wayland_WarpMouse(SDL_Window *window, float x, float y) { SDL_VideoDevice *vd = SDL_GetVideoDevice(); SDL_VideoData *d = vd->driverdata; + SDL_WindowData *wind = window->driverdata; struct SDL_WaylandInput *input = d->input; - if (input->cursor_visible == SDL_TRUE) { - return SDL_Unsupported(); + if (input->cursor_visible || (input->warp_emulation_prohibited && !d->relative_mouse_mode)) { + if (d->pointer_constraints) { + const SDL_bool toggle_lock = !wind->locked_pointer; + + /* The pointer confinement protocol allows setting a hint to warp the pointer, + * but only when the pointer is locked. + * + * Lock the pointer, set the position hint, unlock, and hope for the best. + */ + if (toggle_lock) { + Wayland_input_lock_pointer(input, window); + } + if (wind->locked_pointer) { + zwp_locked_pointer_v1_set_cursor_position_hint(wind->locked_pointer, wl_fixed_from_double(x), wl_fixed_from_double(y)); + wl_surface_commit(wind->surface); + } + if (toggle_lock) { + Wayland_input_unlock_pointer(input, window); + } + + /* NOTE: There is a pending warp event under discussion that should replace this when available. + * https://gitlab.freedesktop.org/wayland/wayland/-/merge_requests/340 + */ + SDL_SendMouseMotion(0, window, SDL_GLOBAL_MOUSE_ID, SDL_FALSE, x, y); + } else { + return SDL_SetError("wayland: mouse warp failed; compositor lacks support for the required zwp_pointer_confinement_v1 protocol"); + } } else if (input->warp_emulation_prohibited) { return SDL_Unsupported(); - } else { - if (!d->relative_mouse_mode) { - Wayland_input_lock_pointer(input); - input->relative_mode_override = SDL_TRUE; - } + } else if (!d->relative_mouse_mode) { + Wayland_input_lock_pointer(input, window); + input->relative_mode_override = SDL_TRUE; } + return 0; } +static int Wayland_WarpMouseGlobal(float x, float y) +{ + SDL_VideoDevice *vd = SDL_GetVideoDevice(); + SDL_VideoData *d = vd->driverdata; + struct SDL_WaylandInput *input = d->input; + SDL_WindowData *wind = input->pointer_focus; + + /* If the client wants the coordinates warped to within the focused window, just convert the coordinates to relative. */ + if (wind) { + SDL_Window *window = wind->sdlwindow; + return Wayland_WarpMouse(window, x - (float)window->x, y - (float)window->y); + } + + return SDL_SetError("wayland: can't warp the mouse when a window does not have focus"); +} + static int Wayland_SetRelativeMouseMode(SDL_bool enabled) { SDL_VideoDevice *vd = SDL_GetVideoDevice(); @@ -603,9 +645,9 @@ static int Wayland_SetRelativeMouseMode(SDL_bool enabled) * mouse warp emulation by default. */ data->input->warp_emulation_prohibited = SDL_TRUE; - return Wayland_input_lock_pointer(data->input); + return Wayland_input_enable_relative_pointer(data->input); } else { - return Wayland_input_unlock_pointer(data->input); + return Wayland_input_disable_relative_pointer(data->input); } } @@ -713,6 +755,7 @@ void Wayland_InitMouse(void) mouse->ShowCursor = Wayland_ShowCursor; mouse->FreeCursor = Wayland_FreeCursor; mouse->WarpMouse = Wayland_WarpMouse; + mouse->WarpMouseGlobal = Wayland_WarpMouseGlobal; mouse->SetRelativeMouseMode = Wayland_SetRelativeMouseMode; mouse->GetGlobalMouseState = Wayland_GetGlobalMouseState; diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c index 736d09c1a4..bfbde9e926 100644 --- a/src/video/wayland/SDL_waylandwindow.c +++ b/src/video/wayland/SDL_waylandwindow.c @@ -2350,7 +2350,7 @@ int Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Propert #endif if (c->relative_mouse_mode) { - Wayland_input_lock_pointer(c->input); + Wayland_input_enable_relative_pointer(c->input); } /* We may need to create an idle inhibitor for this new window */