Relative mouse mode drag does not report mouse button state.
I have implemented a slider which supports relative mouse mode, to drag more precise than the pixels on the screen. The SDL_MouseMotionEvent::state field loses its mouse button flags when enabling relative mouse mode in Wayland and XWayland. I did not experience this issue before migrating from SDL2 to SDL3.
Eyeballing the code (without proper debugging so far), I'm a bit suspicious about these: https://github.com/libsdl-org/SDL/blob/4612db21a3450a5c2a0353558692dd32ccd3a794/src/events/SDL_mouse.c#L741-L744
https://github.com/libsdl-org/SDL/blob/4612db21a3450a5c2a0353558692dd32ccd3a794/src/events/SDL_mouse.c#L932-L937
This suggests that if we enter relative mouse mode, there is a different SDL_MouseID used. I fear that that might cause it to keep track of the button state in a different SDL_MouseInputSource, which gets obtained here:
https://github.com/libsdl-org/SDL/blob/4612db21a3450a5c2a0353558692dd32ccd3a794/src/events/SDL_mouse.c#L849
So what is happening is:
- The application gets a
SDL_EVENT_MOUSE_BUTTON_DOWN(eg. left button) - The application determines that a slider was clicked and calls
SDL_SetWindowRelativeMouseMode(window, true) - The application gets a
SDL_EVENT_MOUSE_MOTIONwith all 0state- The application considers the left button as released and stops the slider, disables relative mouse mode, etc.
- The user releases the left mouse button and you get a
SDL_EVENT_MOUSE_BUTTON_UP- The application seemingly ignores this event, as the mouse button is already released?
Do I understand it correctly?
I'm curious what SDL_GetMouseState returns if called in step 3. It's supposed to be a global function, maybe it just combines all the mice into one. Or try SDL_GetGlobalMouseState.
You are almost correct in your understanding, but that actually doesn't matter for SDL. The to-SDL3-irrelevant subtle difference is that I had programmed the application to ignore the MOUSE_MOTION events with the state 0, as that was not considered a drag, but a simple mouse motion.
I'm curious what
SDL_GetMouseStatereturns if called in step 3. It's supposed to be a global function, maybe it just combines all the mice into one. Or trySDL_GetGlobalMouseState.
I tested this, and indeed, these give the correct state:
...
Dragging: event.state=0, GetMouseState=1, GetGlobalMouseState=1
Dragging: event.state=0, GetMouseState=1, GetGlobalMouseState=1
Dragging: event.state=0, GetMouseState=1, GetGlobalMouseState=1
...
I can reproduce this with this simple modification to testsprite:
diff --git a/test/testsprite.c b/test/testsprite.c
index dd146a9bc..fc6f50331 100644
--- a/test/testsprite.c
+++ b/test/testsprite.c
@@ -558,6 +558,13 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
{
+ if (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
+ SDL_SetWindowRelativeMouseMode(state->windows[0], true);
+ } else if (event->type == SDL_EVENT_MOUSE_BUTTON_UP) {
+ SDL_SetWindowRelativeMouseMode(state->windows[0], false);
+ } else if (event->type == SDL_EVENT_MOUSE_MOTION) {
+ SDL_Log("Mouse motion from mouse %d, state: %x\n", event->motion.which, event->motion.state);
+ }
if (event->type == SDL_EVENT_RENDER_DEVICE_RESET) {
LoadSprite(icon);
}
The problem is that we never get a mouse down event for the raw (relative mouse mode) mouseID, and we have no way of querying it.
I think for now the workaround of querying the mouse state with SDL_GetMouseState to determine whether drag is active is the right way to go.