mouse: Make pointer warp emulation via relative mode available to all platforms

Move the Wayland pointer warp emulation code up to the SDL mouse layer, and activate it when a client attempts to warp a hidden mouse cursor when the hint is set.

testrelative adds the ability to test the warp emulation activation/deactivation with the --warp parameter and 'c' key for toggling cursor visibility.
This commit is contained in:
Frank Praznik
2024-07-24 17:43:02 -04:00
parent 92667e3066
commit 66eb2ea443
8 changed files with 212 additions and 122 deletions

View File

@@ -119,6 +119,18 @@ static void SDLCALL SDL_MouseRelativeSystemScaleChanged(void *userdata, const ch
mouse->enable_relative_system_scale = SDL_GetStringBoolean(hint, SDL_FALSE);
}
static void SDLCALL SDL_MouseWarpEmulationChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
{
SDL_Mouse *mouse = (SDL_Mouse *)userdata;
mouse->warp_emulation_hint = SDL_GetStringBoolean(hint, SDL_TRUE);
if (!mouse->warp_emulation_hint && mouse->warp_emulation_active) {
SDL_SetRelativeMouseMode(SDL_FALSE);
mouse->warp_emulation_active = SDL_FALSE;
}
}
static void SDLCALL SDL_TouchMouseEventsChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
{
SDL_Mouse *mouse = (SDL_Mouse *)userdata;
@@ -211,6 +223,9 @@ int SDL_PreInitMouse(void)
SDL_AddHintCallback(SDL_HINT_MOUSE_RELATIVE_SYSTEM_SCALE,
SDL_MouseRelativeSystemScaleChanged, mouse);
SDL_AddHintCallback(SDL_HINT_MOUSE_EMULATE_WARP_WITH_RELATIVE,
SDL_MouseWarpEmulationChanged, mouse);
SDL_AddHintCallback(SDL_HINT_TOUCH_MOUSE_EVENTS,
SDL_TouchMouseEventsChanged, mouse);
@@ -724,7 +739,7 @@ static int SDL_PrivateSendMouseMotion(Uint64 timestamp, SDL_Window *window, SDL_
float xrel = 0.0f;
float yrel = 0.0f;
if (!mouse->relative_mode && mouseID != SDL_TOUCH_MOUSEID && mouseID != SDL_PEN_MOUSEID) {
if ((!mouse->relative_mode || mouse->warp_emulation_active) && mouseID != SDL_TOUCH_MOUSEID && mouseID != SDL_PEN_MOUSEID) {
/* We're not in relative mode, so all mouse events are global mouse events */
mouseID = SDL_GLOBAL_MOUSE_ID;
}
@@ -1132,6 +1147,9 @@ void SDL_QuitMouse(void)
SDL_DelHintCallback(SDL_HINT_MOUSE_RELATIVE_SYSTEM_SCALE,
SDL_MouseRelativeSystemScaleChanged, mouse);
SDL_DelHintCallback(SDL_HINT_MOUSE_EMULATE_WARP_WITH_RELATIVE,
SDL_MouseWarpEmulationChanged, mouse);
SDL_DelHintCallback(SDL_HINT_TOUCH_MOUSE_EVENTS,
SDL_TouchMouseEventsChanged, mouse);
@@ -1253,9 +1271,24 @@ void SDL_PerformWarpMouseInWindow(SDL_Window *window, float x, float y, SDL_bool
}
}
static void SDL_EnableWarpEmulation(SDL_Mouse *mouse)
{
if (!mouse->cursor_shown && mouse->warp_emulation_hint && !mouse->warp_emulation_prohibited) {
if (SDL_SetRelativeMouseMode(SDL_TRUE) == 0) {
mouse->warp_emulation_active = SDL_TRUE;
}
/* Disable attempts at enabling warp emulation until further notice. */
mouse->warp_emulation_prohibited = SDL_TRUE;
}
}
void SDL_WarpMouseInWindow(SDL_Window *window, float x, float y)
{
SDL_PerformWarpMouseInWindow(window, x, y, SDL_FALSE);
SDL_Mouse *mouse = SDL_GetMouse();
SDL_EnableWarpEmulation(mouse);
SDL_PerformWarpMouseInWindow(window, x, y, mouse->warp_emulation_active);
}
int SDL_WarpMouseGlobal(float x, float y)
@@ -1284,6 +1317,18 @@ int SDL_SetRelativeMouseMode(SDL_bool enabled)
SDL_Mouse *mouse = SDL_GetMouse();
SDL_Window *focusWindow = SDL_GetKeyboardFocus();
if (enabled) {
if (mouse->warp_emulation_active) {
mouse->warp_emulation_active = SDL_FALSE;
}
/* If the app has used relative mode before, it probably shouldn't
* also be emulating it using repeated mouse warps, so disable
* mouse warp emulation by default.
*/
mouse->warp_emulation_prohibited = SDL_TRUE;
}
if (enabled == mouse->relative_mode) {
return 0;
}
@@ -1642,6 +1687,12 @@ int SDL_ShowCursor(void)
{
SDL_Mouse *mouse = SDL_GetMouse();
if (mouse->warp_emulation_active) {
SDL_SetRelativeMouseMode(SDL_FALSE);
mouse->warp_emulation_active = SDL_FALSE;
mouse->warp_emulation_prohibited = SDL_FALSE;
}
if (!mouse->cursor_shown) {
mouse->cursor_shown = SDL_TRUE;
SDL_SetCursor(NULL);

View File

@@ -92,6 +92,9 @@ typedef struct
SDL_bool relative_mode_warp;
SDL_bool relative_mode_warp_motion;
SDL_bool relative_mode_cursor_visible;
SDL_bool warp_emulation_hint;
SDL_bool warp_emulation_active;
SDL_bool warp_emulation_prohibited;
int relative_mode_clip_interval;
SDL_bool enable_normal_speed_scale;
float normal_speed_scale;

View File

@@ -171,10 +171,6 @@ struct SDL_WaylandInput
struct SDL_WaylandTabletInput *tablet;
/* are we forcing relative mouse mode? */
SDL_bool cursor_visible;
SDL_bool relative_mode_override;
SDL_bool warp_emulation_prohibited;
SDL_bool keyboard_is_virtual;
/* Current SDL modifier flags */

View File

@@ -624,14 +624,8 @@ static int Wayland_ShowCursor(SDL_Cursor *cursor)
if (input->cursor_shape) {
Wayland_SetSystemCursorShape(input, data->cursor_data.system.id);
input->cursor_visible = SDL_TRUE;
input->current_cursor = data;
if (input->relative_mode_override) {
Wayland_input_disable_relative_pointer(input);
input->relative_mode_override = SDL_FALSE;
}
return 0;
} else if (!wayland_get_system_cursor(d, data, &scale)) {
return -1;
@@ -662,18 +656,10 @@ static int Wayland_ShowCursor(SDL_Cursor *cursor)
} else {
wl_surface_damage(data->surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32);
}
wl_surface_commit(data->surface);
input->cursor_visible = SDL_TRUE;
input->current_cursor = data;
if (input->relative_mode_override) {
Wayland_input_disable_relative_pointer(input);
input->relative_mode_override = SDL_FALSE;
}
} else {
input->cursor_visible = SDL_FALSE;
input->current_cursor = NULL;
wl_pointer_set_cursor(pointer, input->pointer_enter_serial, NULL, 0, 0);
}
@@ -688,40 +674,33 @@ static int Wayland_WarpMouse(SDL_Window *window, float x, float y)
SDL_WindowData *wind = window->internal;
struct SDL_WaylandInput *input = d->input;
if (input->cursor_visible || (input->warp_emulation_prohibited && !d->relative_mouse_mode)) {
if (d->pointer_constraints) {
const SDL_bool toggle_lock = !wind->locked_pointer;
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) {
const wl_fixed_t f_x = wl_fixed_from_double(x / wind->pointer_scale.x);
const wl_fixed_t f_y = wl_fixed_from_double(y / wind->pointer_scale.y);
zwp_locked_pointer_v1_set_cursor_position_hint(wind->locked_pointer, f_x, f_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");
/* 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);
}
} else if (input->warp_emulation_prohibited) {
return SDL_Unsupported();
} else if (!d->relative_mouse_mode) {
Wayland_input_enable_relative_pointer(input);
input->relative_mode_override = SDL_TRUE;
if (wind->locked_pointer) {
const wl_fixed_t f_x = wl_fixed_from_double(x / wind->pointer_scale.x);
const wl_fixed_t f_y = wl_fixed_from_double(y / wind->pointer_scale.y);
zwp_locked_pointer_v1_set_cursor_position_hint(wind->locked_pointer, f_x, f_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");
}
return 0;
@@ -749,29 +728,12 @@ static int Wayland_SetRelativeMouseMode(SDL_bool enabled)
SDL_VideoData *data = vd->internal;
if (enabled) {
/* Disable mouse warp emulation if it's enabled. */
if (data->input->relative_mode_override) {
data->input->relative_mode_override = SDL_FALSE;
}
/* If the app has used relative mode before, it probably shouldn't
* also be emulating it using repeated mouse warps, so disable
* mouse warp emulation by default.
*/
data->input->warp_emulation_prohibited = SDL_TRUE;
return Wayland_input_enable_relative_pointer(data->input);
} else {
return Wayland_input_disable_relative_pointer(data->input);
}
}
static void SDLCALL Wayland_EmulateMouseWarpChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
{
struct SDL_WaylandInput *input = (struct SDL_WaylandInput *)userdata;
input->warp_emulation_prohibited = !SDL_GetStringBoolean(hint, !input->warp_emulation_prohibited);
}
/* Wayland doesn't support getting the true global cursor position, but it can
* be faked well enough for what most applications use it for: querying the
* global cursor coordinates and transforming them to the window-relative
@@ -862,7 +824,6 @@ void Wayland_InitMouse(void)
SDL_Mouse *mouse = SDL_GetMouse();
SDL_VideoDevice *vd = SDL_GetVideoDevice();
SDL_VideoData *d = vd->internal;
struct SDL_WaylandInput *input = d->input;
mouse->CreateCursor = Wayland_CreateCursor;
mouse->CreateSystemCursor = Wayland_CreateSystemCursor;
@@ -873,9 +834,6 @@ void Wayland_InitMouse(void)
mouse->SetRelativeMouseMode = Wayland_SetRelativeMouseMode;
mouse->GetGlobalMouseState = Wayland_GetGlobalMouseState;
input->relative_mode_override = SDL_FALSE;
input->cursor_visible = SDL_TRUE;
SDL_HitTestResult r = SDL_HITTEST_NORMAL;
while (r <= SDL_HITTEST_RESIZE_LEFT) {
switch (r) {
@@ -918,26 +876,17 @@ void Wayland_InitMouse(void)
#endif
SDL_SetDefaultCursor(Wayland_CreateDefaultCursor());
SDL_AddHintCallback(SDL_HINT_VIDEO_WAYLAND_EMULATE_MOUSE_WARP,
Wayland_EmulateMouseWarpChanged, input);
}
void Wayland_FiniMouse(SDL_VideoData *data)
{
struct SDL_WaylandInput *input = data->input;
int i;
Wayland_FreeCursorThemes(data);
#ifdef SDL_USE_LIBDBUS
Wayland_DBusFinishCursorProperties();
#endif
SDL_DelHintCallback(SDL_HINT_VIDEO_WAYLAND_EMULATE_MOUSE_WARP,
Wayland_EmulateMouseWarpChanged, input);
for (i = 0; i < SDL_arraysize(sys_cursors); i++) {
for (int i = 0; i < SDL_arraysize(sys_cursors); i++) {
Wayland_FreeCursor(sys_cursors[i]);
sys_cursors[i] = NULL;
}