Wayland: Emulate mouse warp using relative mouse mode Several games (including Source and GoldSrc games, and Bioshock Infinite) attempt to "fake" relative mouse mode by repeatedly warping the cursor to the centre of the screen. Since mouse warping is not supported under Wayland, the viewport ends up "stuck" in a rectangular area. Detect this case (mouse warp while the cursor is not visible), and enable relative mouse mode, which tracks the cursor position independently, and so can Warp successfully. This is behind the SDL_HINT_VIDEO_WAYLAND_EMULATE_MOUSE_WARP hint, which is enabled by default, unless the application enables relative mouse mode itself using SDL_SetRelativeMouseMode(SDL_TRUE). Note that there is a behavoural difference, in that relative mouse mode typically doesn't take mouse accelleration into account, but the repeated-warping technique does, so mouse movement can seem very slow with this (unless the game has its own mouse accelleration option, such as in Portal 2).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
diff --git a/include/SDL_hints.h b/include/SDL_hints.h
index c23925e..f7ff748 100644
--- a/include/SDL_hints.h
+++ b/include/SDL_hints.h
@@ -1713,6 +1713,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").
*
* If this hint is set before SDL_CreateWindowFrom() and the SDL_Window* it is set to has
diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c
index 94b13c1..6b504e5 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 9d240d7..7554a9e 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 a0f9d0e..a5a45e7 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 */