Cursor problems on Wayland
vanfanel opened this issue · comments
Describe the bug
OpenFodder cursor moves erratically on Wayland unless I set alternate-mouse=true
in openfodder.ini
The problem is that, this way, mouse is "sticky" when moving it beyond the lower and right screen limits.
My understanding is that SDL2 mouse grabbing doesn't seem to work "as expected" on Wayland, as it does on X11.
To Reproduce
Run the engine on Wayland,
Move the cursor.
Expected behavior
Cursor should behave normally on Wayland too.
Desktop (please complete the following information):
- OS: Debian GNU/Linux
- Version 12
There's more information here:
https://wiki.archlinux.org/title/wayland
Look for "Input grabbing in games, remote desktop and VM windows", where it's explained.
OF doesn't actually grab the mouse,
however; the x/y position is set every cycle of the engine. I have seen issues with this before.. possibly in remote desktop and/or VNC
its currently implemented how the original engine does it, alternative-mouse was the approach we used at first, but continually ran into problems and it was eventually scrapped (although it ended up coming back for the Emscripten build or remote desktop.. i cant recall exactly)
behavior is:
every cycle of the engine,
- system mouse x/y is read
- system mouse x/y is set to center of the window
- difference between center and the last read x/y is added to internal cursor position
Behavior only varies from the original when the internal mouse x/y passes the screen edge or when the window loses focus, when that happens we stop setting the mouse x/y
@segrax I have found the problem with OpenFodder in Wayland.
The OpenFodder engine uses SDL_WarpMouseGlobal() and SDL_WarpMouseInWindow(), here:
Line 468 in 9518ee2
Line 480 in 9518ee2
That does NOT work on Wayland, since SDL_WarpMouse* functions are unsupported on Wayland:
https://github.com/libsdl-org/SDL/blob/b8d6023a918c3930861c42ea5ea207da28ce9182/src/video/wayland/SDL_waylandmouse.c#L622
As you will see there, all possible outcomes of Wayland_WarpMouse()
end in SDL_Unsupported()
unless the cursor is invisible.
So, the solution would be avoiding SDL_WarpMouse* functions.
This is how Scummvm fixed the cursor problems in Wayland:
And this is a previous discussion with SDL2 devs.
In a nutshell, please use SDL_SetWindowMouseRect
instead of SDL_WarpMouse*
SDL_SetWindowMouseRect only restricts the cursor to a rectangle area inside the window, doing this wont have the desired outcome
The purpose of setting the cursor location to window center is so cursor acceleration speed can be calculated per engine cycle
but its also used to update the position of the internal cursor x/y,
which is why in alternate-mouse, when the camera pans, the cursor moves (as the cursor is now using the raw x/y relative to the window).. whereas in the original, the cursor remains over the tile it was hovering before the camera pan begun
Ouch, then I don't know...
Well, I will wait patiently. Wayland is almost everywhere so the engine will have to adapt someday :D
Which version of SDL2 are you compiling against?
SDL_HINT_VIDEO_WAYLAND_EMULATE_MOUSE_WARP was added in 2.26.0
I am building against latest SDL2 stable: 2.28.0
As for SDL_HINT_VIDEO_WAYLAND_EMULATE_MOUSE_WARP, it seems that only works under certain circumstances:
https://discourse.libsdl.org/t/sdl-wayland-emulate-mouse-warp-using-relative-mouse-mode/39112
Well, at least here, it doesn't work as intended.
If there's something I can test in the code to try and solve this, tell me so I can rebuild and test.
I think this engine should work correctly on Wayland.
it would seem "fixing" this isn't going to be a simple/quick task
its going to require some sort of overhaul/replacement of a number of pieces of functionality relating to the mouse, window and camera panning.
@drnovice do you remember the mouse issues back in 2018 and what else we tried?
There was a few approaches tested which all ran into walls.
To make matters more complex, the ingame sidebar is referenced in terms of negative X values.
The current solution was the last option we wanted to implement, but its also the one which matches the original Amiga/Dos behavior. it also took a week of tinkering to get it perfect (cursor leaving/entering the window at the exact right time)
Some quick thoughts/issues
- cursor leaving the window needs to be in sync with system cursor to look fluid
- In game cursor position must remain static when camera pans, unless mouse is still moving
- cursor has to be able to go to -32 and -4 before it leaves the window
- window size can change at any time which affects scale, and tracking if an internal x/y position is used instead of actual relative position
- actual relative position is problematic because it cant goto -32 and -4
Knowns
- Mouse position follows the terrain when camera moves (func: Camera_Update_Mouse_Position_For_Pan())
- Playfield begins at X=0, Sidebar begins at X=-32
- On wayland we cant change the mouse x/y
Problems
- using the system provided x/y for cursor, we cant change the cursor position when the camera pans
- using internal x/y to track position, the cursor goes out of sync with the system provided x/y, and cursor will leave window when user isn't expecting it
Possible Options
- Force full screen on Wayland (then the real cursor position is irrelevant and we can just track changes between cycles) -- or is it? what happens when it reaches the screen edge
- Force alternate mouse on Wayland in Window mode
@segrax Fullscreen on Wayland: cursor works right only when alternate-mouse=true
, but even then, it gets "stuck" on the left and down edges of the screen.
What's happening (I guess) is that the cursor coordinates go beyond the game area.
Using SDL_SetWindowMouseRect
would correct that, I guess.
@vanfanel I believe the problem is to refer to SDL2 current (lack of) support for Wayland.
SDL_SetWindowMouseRect is just a current workaround, but we can't twist the engine every time a new library or system tries to interface (without full compatibility) with what has been developed previously.
I can understand that now you're on Wayland (because of the very short time I don't know it at all, but my world is under microsoft azure and c# technologies, c++ systems always remains a "hobby" for me) but you have to also understand that the latest big development on these SDL2 primitive was done in 2018, it's been 5 years ago.
The good news is that this is an opensource project, if you can find support (with the current version of SDL2) on OF for Wayland that is backwards compatible to X11 of course, feel free to fork the project and send in the PR: we will it evaluate carefully.
@drnovice Wayland support in SDL2 is mature and bugs are constantly being worked out.
Where do you get the 2018 figure from??
https://github.com/libsdl-org/SDL/tree/main/src/video/wayland
Also, do you imply that incorporating SDL_SetWindowMouseRect
as a workaround won't be done?
I am not a native english speaker, so many subtleties are beyond me.
But I think it's an easy fix for someone familiar with OF code.
The function is pretty simple:
https://wiki.libsdl.org/SDL2/SDL_SetWindowMouseRect
It should be called when alternate-mouse=true
is used, as simple as that, and the game would be fixed on Wayland.
Note that this solution is also X11 backwards compatible.
SDL_SetWindowMouseRect could fix the SDL_WarpMouseInWindow function, where you work with the window.
But again, it would be temporary workaround: I would wait the full implementation of SDL about Wayland_WarpMouse() to use the right way the Wayland system.
And what about SDL_WarpMouseGlobal? It moves the cursor in the full screen background over the SDL window.
Wayland seems not work outside the window "relative" mode:
https://github.com/libsdl-org/SDL/blob/17e95345e39b2b3ce436ce3029175055c3d48869/src/video/wayland/SDL_waylandmouse.c#L670
Now I can't remember when SDL_WarpMouseGlobal is used (probably fullscreen mode?) but it's not so simple as it could seem.
I have no time to test on the code, @vanfanel could you try to check how to fix the global mouse position?
[...]
The function is pretty simple: https://wiki.libsdl.org/SDL2/SDL_SetWindowMouseRect It should be called whenalternate-mouse=true
is used, as simple as that, and the game would be fixed on Wayland.Note that this solution is also X11 backwards compatible.
I don't recommend using "alternate-mouse=true" , the user experience in game it's different and you can't aim well by the cursor while camera pans, IIRC
Ok, then I will forget about the alternate-mouse workaround...
I am trying to understand. Am I right that SDL_WarpMouse* isn't implemented on Wayland because, as the comment you point out says, Wayland doesn't support getting the true global cursor position?
If that's the case, SDL_WarpMouse* on Wayland won't be implemented, right?
I think if it was implemented, it will be implement with a "fake mode" as done for Wayland_GetGlobalMouseState()
It's all new also for us, so we need patience to understand how to do the support, if possibile.
It doesn't seem it will be implemented...
I think if it was implemented, it will be implement with a "fake mode" as done for Wayland_GetGlobalMouseState() It's all new also for us, so we need patience to understand how to do the support, if possibile.
SDL can fake the global coordinates because, on Wayland, windows are always considered to be at the origin of the last display they entered, so the window-relative coordinates can just be offset by the window origin, giving the correct window-relative coordinates when the transformation is reversed, which is what applications like the Unreal editor and most others that want global mouse coordinates do. It doesn't work at all when the cursor is outside an application window.
Wayland doesn't support warping the mouse cursor position at all, either locally or globally, for security reasons, so the only case where it can be faked is in relative mode. If an application really needs full control over the cursor position within window bounds, it would need to use relative mode and draw its own software cursor that it has 100% control over.
It doesn't seem it will be implemented...
Honestly, would if we could, but unless Wayland gains support for it, we can't.
Thanks for the explanation @Kontrabant
Seems I've been able to replicate the original behavior using SDL_SetRelativeMouseMode and SDL_HINT_MOUSE_RELATIVE_MODE_WARP
the cursor transition from inside to outside the window is a little rough, but only on platforms where SDL_WarpMouseGlobal doesnt work
@vanfanel I've just pushed the changes if you'd like to give it a test
@segrax Cursor movement with alternate-mouse=false
is now good!
However, when I click, the cursor is teleported to the top of the screen (and the click has no effect).
NOTE: I tested it as soon as I got home... sorry I couldn't test earlier.
@vanfanel no problem, no rush!
Ok i see, just learnt that SDL_GetGlobalMouseState is also not supported on Wayland, which we only use to determine if the cursor is over the window.. now replaced with sdl_getmousefocus,
have just pushed another fix, see how that goes
@segrax Same problem: the cursor is teleported to the upper part of the screen when clicking.
hm i think i know whats happening, but really have no idea why.
given a lack of a setup with wayland, and no time right now to set one up...
line 2876 in Fodder.cpp,
mWindow->SetRelativeMouseMode(false);
try comment it out/remove it.. if it works the cursor wont be able to leave the window
Still the exact same problem: cursor is teleported to the upper border of the window when clicking on any part of the screen (or the upper border of the window, if the game is in windowed mode)
when you say cursor in this instance, is that the in-game cursor, or the system cursor?
I mean the in-game cursor (there's only that cursor once I launch the game).
ok last guess and then it will have to wait until i can setup something to debug on,
in Window.cpp, line41: mHasFocus = false;
try
mHasFocus = true;
@segrax That change doesn't have any impact on the issue, either.
@vanfanel ok i found the issue, at-least one of them
During a button press (SDL_MOUSEBUTTONUP or SDL_MOUSEBUTTONDOWN), on Windows xrel and yrel are set to zero. but on Linux, im getting high/strange values, the yrel is typically in the negative, thus causing the cursor to head to the top border.
not sure if theres a different data structure in play or not. but seems to be the cause
Event.mPositionRelative = cPosition(SysEvent.motion.xrel, SysEvent.motion.yrel);
ive just pushed a fix for it
@segrax Thanks, this worked!
OpenFodder now works perfectly on Wayland! Yay! :D