8000 SDL3, X11 Duplicated mouse move events when using the pen · Issue #12968 · libsdl-org/SDL · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content
SDL3, X11 Duplicated mouse move events when using the pen #12968
Open
@deiflou

Description

@deiflou

When using the pen under X11, SDL synthesizes a mouse move when the pen is moved over a window. That is fine, but using the test code below one can see that two mouse events are sent, not one.

I think the issue is that xinput2 may be also emulating xi mouse events itself when using the pen. I'm not familiar with "master/slave" devices (just read a bit) but it seems like 2 xi events are sent: the first with same deviceid and sourceid (that device id is then used to check if the device is a pen); the second has the same sourceid as before but a lower deviceid (here it is 2). The 2 xi events are sent even if I use the mouse.

I see there is some code to check if the device is a master device in the button press/release and motion event handlers, something like:

if (xev->deviceid != xev->sourceid) {
    // Discard events from "Master" devices to avoid duplicates.
    break;
}

In the case of button press/release it seems to work: only one sdl event is sent at the end, but in the case of the motion, two are sent.

This is the code that handles the motion event:

    case XI_Motion:
    {
        const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data;
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_MULTITOUCH
        bool pointer_emulated = ((xev->flags & XIPointerEmulated) != 0);
#else
        bool pointer_emulated = false;
#endif

        videodata->global_mouse_changed = true;

        X11_PenHandle *pen = X11_FindPenByDeviceID(xev->deviceid);
        if (pen) {
            if (xev->deviceid != xev->sourceid) {
                // Discard events from "Master" devices to avoid duplicates.
                break;
            }

            SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event);
            SDL_SendPenMotion(0, pen->pen, window, (float) xev->event_x, (float) xev->event_y);

            float axes[SDL_PEN_AXIS_COUNT];
            X11_PenAxesFromValuators(pen, xev->valuators.values, xev->valuators.mask, xev->valuators.mask_len, axes);

            for (int i = 0; i < SDL_arraysize(axes); i++) {
                if (pen->valuator_for_axis[i] != SDL_X11_PEN_AXIS_VALUATOR_MISSING) {
                    SDL_SendPenAxis(0, pen->pen, window, (SDL_PenAxis) i, axes[i]);
                }
            }
        } else if (!pointer_emulated && xev->deviceid == videodata->xinput_master_pointer_device) {
            // Use the master device for non-relative motion, as the slave devices can seemingly lag behind.
            SDL_Mouse *mouse = SDL_GetMouse();
            if (!mouse->relative_mode) {
                SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event);
                if (window) {
                    X11_ProcessHitTest(_this, window->internal, (float)xev->event_x, (float)xev->event_y, false);
                    SDL_SendMouseMotion(0, window, SDL_GLOBAL_MOUSE_ID, false, (float)xev->event_x, (float)xev->event_y);
                }
            }
        }
    } break;

The code checks if the device is a pen using xev->deviceid, which is only true when the first xi event is sent (when deviceid and sourceid are the same). In that case a sdl mouse event is emulated in SDL_SendPenMotion.
But then the second xi event is sent, which has a different deviceid and so it is not considered a pen and the "else" path is executed. There a sdl mouse motion event is sent with SDL_SendMouseMotion. So at the end nothing prevents that 2 sdl events are sent.
In the else path, xev->deviceid == videodata->xinput_master_pointer_device is checked, but that is also true for the second xi pen event emited.

I did some tests and it seems that using the sourceid (instead of the deviceid) to check if the device is a pen is enough. Although if that is used in the press/release then other things need to be changed. This is what I tried and seemed to work, although I could not check stuff like the eraser tip (I don't know if that is important since the eraser is listed as a different device I think). As I said, I'm not really familiar with x11 or xinput code, so I don't know if this is the correct way of handling it:

diff --git a/src/video/x11/SDL_x11xinput2.c b/src/video/x11/SDL_x11xinput2.c
index afe4a7c85..98080ccfd 100644
--- a/src/video/x11/SDL_x11xinput2.c
+++ b/src/video/x11/SDL_x11xinput2.c
@@ -406,11 +406,15 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
     case XI_ButtonRelease:
     {
         const XIDeviceEvent *xev = (const XIDeviceEvent *)cookie->data;
-        X11_PenHandle *pen = X11_FindPenByDeviceID(xev->deviceid);
+        X11_PenHandle *pen = X11_FindPenByDeviceID(xev->sourceid);
         const int button = xev->detail;
         const bool down = (cookie->evtype == XI_ButtonPress);
 
         if (pen) {
+            if (xev->deviceid != xev->sourceid) {
+                // Discard events from "Master" devices to avoid duplicates.
+                break;
+            }
             // Only report button event; if there was also pen movement / pressure changes, we expect an XI_Motion event first anyway.
             SDL_Window *window = xinput2_get_sdlwindow(videodata, xev->event);
             if (button == 1) { // button 1 is the pen tip
@@ -422,11 +426,6 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
             // Otherwise assume a regular mouse
             SDL_WindowData *windowdata = xinput2_get_sdlwindowdata(videodata, xev->event);
 
-            if (xev->deviceid != xev->sourceid) {
-                // Discard events from "Master" devices to avoid duplicates.
-                break;
-            }
-
             if (down) {
                 X11_HandleButtonPress(_this, windowdata, (SDL_MouseID)xev->sourceid, button,
                                       (float)xev->event_x, (float)xev->event_y, xev->time);
@@ -449,7 +448,8 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie)
 
         videodata->global_mouse_changed = true;
 
-        X11_PenHandle *pen = X11_FindPenByDeviceID(xev->deviceid);
+        X11_PenHandle *pen = X11_FindPenByDeviceID(xev->sourceid);
+
         if (pen) {
             if (xev->deviceid != xev->sourceid) {
                 // Discard events from "Master" devices to avoid duplicates.

Test code:

#include <SDL3/SDL.h>

int main()
{
    if (!SDL_Init(SDL_INIT_VIDEO)) {
        SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    SDL_Window *window = SDL_CreateWindow("hello", 800, 600, 0);
    if (!window) {
        SDL_Log("Couldn't create window: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }

    SDL_ShowWindow(window);

    bool exit_requested = false;
    while(!exit_requested)
    {
        SDL_Event sdl_event;
        while (SDL_PollEvent(&sdl_event))
        {
            switch (sdl_event.type)
            {
                case SDL_EVENT_QUIT:
                case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
                {
                    exit_requested = true;
                    break;
                }
                case SDL_EVENT_MOUSE_BUTTON_DOWN:
                {
                    SDL_Log("SDL_EVENT_MOUSE_BUTTON_DOWN");
                    break;
                }
                case SDL_EVENT_MOUSE_BUTTON_UP:
                {
                    SDL_Log("SDL_EVENT_MOUSE_BUTTON_UP");
                    break;
                }
                case SDL_EVENT_MOUSE_MOTION:
                {
                    SDL_Log("SDL_EVENT_MOUSE_MOTION");
                    break;
                }
                case SDL_EVENT_PEN_DOWN:
                {
                    SDL_Log("SDL_EVENT_PEN_DOWN");
                    break;
                }
                case SDL_EVENT_PEN_UP:
                {
                    SDL_Log("SDL_EVENT_PEN_UP");
                    break;
                }
                case SDL_EVENT_PEN_MOTION:
                {
                    SDL_Log("SDL_EVENT_PEN_MOTION");
                    break;
                }
                default:
                    break;
            }
        }
    }

    return 0;
}

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

    0