diff --git a/include/SDL_hints.h b/include/SDL_hints.h index c23925eeb..f7ff74836 100644 --- a/include/SDL_hints.h +++ b/include/SDL_hints.h @@ -1712,6 +1712,23 @@ extern "C" { */ #define SDL_HINT_VIDEO_WAYLAND_MODE_EMULATION "SDL_VIDEO_WAYLAND_MODE_EMULATION" +/** + * \brief Enable or disable mouse pointer warp emulation, needed by some older games. + * + * 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. + * + * This variable can be set to the following values: + * "0" - All mouse warps fail, as mouse warping is not available under wayland. + * "1" - Some mouse warps will be emulated by forcing relative mouse mode. + * + * If not set, this is automatically enabled unless an application uses relative mouse + * mode directly. + */ +#define SDL_HINT_VIDEO_WAYLAND_EMULATE_MOUSE_WARP "SDL_VIDEO_WAYLAND_EMULATE_MOUSE_WARP" + /** * \brief A variable that is the address of another SDL_Window* (as a hex string formatted with "%p"). * diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c index 94b13c13e..6b504e5ab 100644 --- a/src/video/wayland/SDL_waylandevents.c +++ b/src/video/wayland/SDL_waylandevents.c @@ -2654,6 +2654,10 @@ int Wayland_input_confine_pointer(struct SDL_WaylandInput *input, SDL_Window *wi if (d->relative_mouse_mode) return 0; + /* Don't confine the pointer if it shouldn't be confined. */ + if (SDL_RectEmpty(&window->mouse_rect) && !(window->flags & SDL_WINDOW_MOUSE_GRABBED)) + return 0; + if (SDL_RectEmpty(&window->mouse_rect)) { confine_rect = NULL; } else { diff --git a/src/video/wayland/SDL_waylandevents_c.h b/src/video/wayland/SDL_waylandevents_c.h index 9d240d7bd..7554a9e88 100644 --- a/src/video/wayland/SDL_waylandevents_c.h +++ b/src/video/wayland/SDL_waylandevents_c.h @@ -132,6 +132,11 @@ struct SDL_WaylandInput { SDL_WaylandKeyboardRepeat keyboard_repeat; struct SDL_WaylandTabletInput* tablet; + + /* are we forcing relative mouse mode? */ + SDL_bool cursor_visible; + SDL_bool relative_mode_override; + SDL_bool warp_emulation_prohibited; }; extern void Wayland_PumpEvents(_THIS); diff --git a/src/video/wayland/SDL_waylandmouse.c b/src/video/wayland/SDL_waylandmouse.c index a0f9d0e0c..a5a45e724 100644 --- a/src/video/wayland/SDL_waylandmouse.c +++ b/src/video/wayland/SDL_waylandmouse.c @@ -40,6 +40,11 @@ #include "wayland-cursor.h" #include "SDL_waylandmouse.h" +#include "SDL_hints.h" +#include "../../SDL_hints_c.h" + +static int +Wayland_SetRelativeMouseMode(SDL_bool enabled); typedef struct { struct wl_buffer *buffer; @@ -510,9 +515,18 @@ Wayland_ShowCursor(SDL_Cursor *cursor) wl_surface_attach(data->surface, data->buffer, 0, 0); wl_surface_damage(data->surface, 0, 0, data->w, data->h); wl_surface_commit(data->surface); + + input->cursor_visible = SDL_TRUE; + + if (input->relative_mode_override) { + Wayland_input_unlock_pointer(input); + input->relative_mode_override = SDL_FALSE; + } + } else { + input->cursor_visible = SDL_FALSE; wl_pointer_set_cursor(pointer, input->pointer_enter_serial, NULL, 0, 0); } @@ -522,7 +536,20 @@ Wayland_ShowCursor(SDL_Cursor *cursor) static void Wayland_WarpMouse(SDL_Window *window, int x, int y) { - SDL_Unsupported(); + SDL_VideoDevice *vd = SDL_GetVideoDevice(); + SDL_VideoData *d = vd->driverdata; + struct SDL_WaylandInput *input = d->input; + + if (input->cursor_visible == SDL_TRUE) { + SDL_Unsupported(); + } else if (input->warp_emulation_prohibited) { + SDL_Unsupported(); + } else { + if (!d->relative_mouse_mode) { + Wayland_input_lock_pointer(input); + input->relative_mode_override = SDL_TRUE; + } + } } static int @@ -537,16 +564,38 @@ Wayland_SetRelativeMouseMode(SDL_bool enabled) SDL_VideoDevice *vd = SDL_GetVideoDevice(); SDL_VideoData *data = (SDL_VideoData *) vd->driverdata; - if (enabled) + + 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_lock_pointer(data->input); - else + } else { return Wayland_input_unlock_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); } void Wayland_InitMouse(void) { SDL_Mouse *mouse = SDL_GetMouse(); + SDL_VideoDevice *vd = SDL_GetVideoDevice(); + SDL_VideoData *d = vd->driverdata; + struct SDL_WaylandInput *input = d->input; mouse->CreateCursor = Wayland_CreateCursor; mouse->CreateSystemCursor = Wayland_CreateSystemCursor; @@ -556,17 +605,27 @@ Wayland_InitMouse(void) mouse->WarpMouseGlobal = Wayland_WarpMouseGlobal; mouse->SetRelativeMouseMode = Wayland_SetRelativeMouseMode; + input->relative_mode_override = SDL_FALSE; + input->cursor_visible = SDL_TRUE; + 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; for (i = 0; i < data->num_cursor_themes; i += 1) { WAYLAND_wl_cursor_theme_destroy(data->cursor_themes[i].theme); } SDL_free(data->cursor_themes); + + SDL_DelHintCallback(SDL_HINT_VIDEO_WAYLAND_EMULATE_MOUSE_WARP, + Wayland_EmulateMouseWarpChanged, input); } #endif /* SDL_VIDEO_DRIVER_WAYLAND */