wayland: Add mouse pointer warp support

The pointer confinement protocol does allow attempted warping the pointer via a hint, provided that the pointer is locked at the time of the request, and the requested coordinates fall within the bounds of the window.

Toggle the pointer locked state and request the pointer warp when the required protocol is available. This is similar to what XWayland does internally.
pull/9685/head
Frank Praznik 2024-05-01 12:57:39 -04:00
parent 22016228ca
commit 3a6d9c59f4
6 changed files with 108 additions and 52 deletions

View File

@ -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()```

View File

@ -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

View File

@ -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) {

View File

@ -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);

View File

@ -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;

View File

@ -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 */