SDL icon indicating copy to clipboard operation
SDL copied to clipboard

Relative mouse mode drag does not report mouse button state.

Open mcourteaux opened this issue 1 year ago • 2 comments

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

mcourteaux avatar Oct 17 '24 11:10 mcourteaux

So what is happening is:

  1. The application gets a SDL_EVENT_MOUSE_BUTTON_DOWN (eg. left button)
  2. The application determines that a slider was clicked and calls SDL_SetWindowRelativeMouseMode(window, true)
  3. The application gets a SDL_EVENT_MOUSE_MOTION with all 0 state
    • The application considers the left button as released and stops the slider, disables relative mouse mode, etc.
  4. 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.

Susko3 avatar Oct 30 '24 20:10 Susko3

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_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.

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
...

mcourteaux avatar Oct 31 '24 11:10 mcourteaux

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);
     }

slouken avatar Sep 14 '25 20:09 slouken

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.

slouken avatar Sep 14 '25 20:09 slouken