libsdl-org / SDL

Simple Directmedia Layer
https://libsdl.org
zlib License
9.53k stars 1.77k forks source link

Incorrect SDL_WINDOWEVENT_ENTER and SDL_MOUSEMOTION events with KDE Plasma #9156

Open ell1e opened 7 months ago

ell1e commented 7 months ago

It seems like that with SDL 2.30.0-1.1 as packaged by OpenSUSE, KDE Plasma Shell 5.27.10 with a Wayland session, SDL defaulting to SDL_VIDEODRIVER=x11 in this setup, causes the window creates a bogus SDL_WINDOWEVENT_ENTER and bogus SDL_MOUSEMOTION event. This leads to the app incorrectly believing the mouse is inside the window and to incorrectly assume a wrong position. This is especially problematic once the mouse does actually enter the window later but at a different spot, causing possibly huge relative movements to be assumed by the application.

Steps to reproduce:

  1. Run this:
#include <SDL2/SDL.h>
#include <stdio.h>

int main(int argc, const char **argv) {
    SDL_Window *window = NULL;
    if (SDL_Init(SDL_INIT_VIDEO) < 0) return 1;
    window = SDL_CreateWindow("Test",
        SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
        200, 200, SDL_WINDOW_SHOWN);
    if (window == NULL) return 1;

    while (1) {
        SDL_Event e;
        while (SDL_PollEvent(&e) != 0) {
            if (e.type == SDL_MOUSEMOTION) {
                printf("Mouse at: %d, %d\n",
                    (int)e.motion.x, (int)e.motion.y);
            } else if (e.type == SDL_WINDOWEVENT) {
                if (e.window.event == SDL_WINDOWEVENT_ENTER) {
                    printf("Mouse enters window.\n");
                } else if (e.window.event == SDL_WINDOWEVENT_LEAVE) {
                    printf("Mouse leaves window.\n");
                }
            }
        }

        SDL_Surface *srf = SDL_GetWindowSurface(window);
        SDL_UpdateWindowSurface(window);
    }
    return 0;
}
  1. Observe what happens:

https://github.com/libsdl-org/SDL/assets/64124388/a5a3b2f3-ff68-4e27-b5ad-5faba40c1960

ell1e commented 7 months ago

I found yet another bug:

  1. With above example, let the mouse leave the window to the left. You'll see ouput similar to:

    Mouse at: 1, 37
    Mouse leaves window.

    This is toward the left side, as the x=1 clearly indicates, where you moved the mouse last. This makes sense.

  2. Wait a few seconds and keep the mouse away from the window at all cost.

  3. Now move the mouse around the window without touching it, and move the mouse back in from the right side. You'll see something like this output:

    Mouse enters window.
    Mouse at: 1, 37
    Mouse at: 199, 28
    Mouse at: 199, 29

The 1, 37 after the mouse was announced back inside the window by SDL2 is multiple seconds and hundreds pixels away outdated and really shouldn't be there.

ell1e commented 7 months ago

I was going to test if with SDL_VIDEODRIVER=wayland I get more correct results, but #5596 then makes the window not show up with above minimal code example.

Kontrabant commented 7 months ago

I'll have to check this on KDE a bit later, but it sounds like a window manager bug as it doesn't happen on GNOME. SDL just forwards events from the window manager. If it's getting weird coordinates at random times, it's because something is sending them.

I was going to test if with SDL_VIDEODRIVER=wayland I get more correct results, but #5596 then makes the window not show up with above minimal code example.

You need to present a frame for a Wayland window to become fully mapped. That's just how it works. Trying to hack around this with subsurfaces or some other solution just to show a blank window in the most trivial of cases would become very messy and have undesirable side effects, such as potentially interfering with direct scanout on some window managers.

You can just create a renderer, clear the screen, and present for the blank window to become visible.

ell1e commented 7 months ago

Isn't there plenty of SDL code that uses SDL_UpdateWindowSurface instead of a renderer as the entire way of the application to work? It doesn't make sense to me that this approach would be broken. I would get it if neither SDL_RenderPresent nor SDL_UpdateWindowSurface was called, but one of the two surely should make the window visible. Otherwise, why does SDL_UpdateWindowSurface even still exist at this point?

Kontrabant commented 7 months ago

You need to call SDL_GetWindowSurface() before calling SDL_UpdateWindowSurface(). If you check the return code in your example, it's actually failing and setting an error "Window surface is invalid, please call SDL_GetWindowSurface() to get a new surface".

ell1e commented 7 months ago

Thanks so much! With your help I confirmed that this bug exists also with SDL_VIDEODRIVER=wayland, but only the 2nd part of it. The 1st part of having a bogus window enter to start with seems to be limited to X11. The 2nd part is also way harder to reproduce with wayland, I needed to try around 20 times until it happened, but then I managed multiple times. I can therefore only assume it's a race condition since it's so random.