Skip to content

Possible race condition in X11_HandleKeyEvent #12267

Closed
@PeterD1524

Description

@PeterD1524

Background

I am using an SDL application A (scrcpy with sdl2-compat) that receives SDL_KeyboardEvent and acts like a keyboard.

I have another application B (keepassxc auto typing) that sends X11 XKeyEvent to application A.

Application A fails to type uppercase letters from events received from application B (application A makes use of SDL_KeyboardEvent.mod).

I am using sdl2-compat 2.30.52 and sdl3 3.2.2.

Problem

This is what I currently understand:

The PR (commit) adds X11_UpdateSystemKeyModifiers.

X11_UpdateSystemKeyModifiers updates the viddata->xkb.xkb_modifiers to the current keyboard state via X11_XQueryPointer which may not match the XKeyEvent it is processing.
SDL3 will set the modifier in viddata->xkb.sdl_modifiers only if the modifier is also set in viddata->xkb.xkb_modifiers in X11_ReconcileModifiers.
Events are sent to applications with SDL_SendKeyboardKeyIgnoreModifiers.

Possible problematic scenario:

  1. KeyPress Left Shift happened at time t1
  2. KeyRelease Left Shift happened at time t2
  3. X server sets the mod state to 0 between t2 and t3
  4. X11_HandleKeyEvent called at time t3, X11_XQueryPointer gets viddata->xkb.xkb_modifiers=0

where t1 < t2 < t3.

When an SDL3 application receives the SDL_KeyboardEvent, it will get SDL_KeyboardEvent.mod=0.

Here is a example SDL3 application that does some long task so X11_HandleKeyEvent may lag behind:

#include <cstdio>

#define SDL_MAIN_USE_CALLBACKS

#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>

SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) {
  if (!SDL_Init(SDL_INIT_VIDEO)) {
    SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
    return SDL_APP_FAILURE;
  }

  auto window = SDL_CreateWindow("example", 640, 480, 0);
  if (!window) {
    SDL_Log("Couldn't create window: %s", SDL_GetError());
    return SDL_APP_FAILURE;
  }

  return SDL_APP_CONTINUE;
}

void long_task() {
  SDL_Delay(2000);
}

SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) {
  if (event->type == SDL_EVENT_QUIT) {
    return SDL_APP_SUCCESS;
  }
  if (event->type == SDL_EVENT_KEY_DOWN || event->type == SDL_EVENT_KEY_UP) {
    long_task();
    printf("type=%s key=%s mod=%d\n",
           event->type == SDL_EVENT_KEY_DOWN ? "SDL_EVENT_KEY_DOWN"
                                             : "SDL_EVENT_KEY_UP",
           SDL_GetKeyName(event->key.key), event->key.mod);
  }
  return SDL_APP_CONTINUE;
}

SDL_AppResult SDL_AppIterate(void *appstate) {
  SDL_Delay(0);
  return SDL_APP_CONTINUE;
}

void SDL_AppQuit(void *appstate, SDL_AppResult result) {}

When I type Left Shift + A in the window, I expect to consistently get:

type=SDL_EVENT_KEY_DOWN key=Left Shift mod=1
type=SDL_EVENT_KEY_DOWN key=A mod=1
type=SDL_EVENT_KEY_UP key=A mod=1
type=SDL_EVENT_KEY_UP key=Left Shift mod=0

But I got:

type=SDL_EVENT_KEY_DOWN key=Left Shift mod=1
type=SDL_EVENT_KEY_DOWN key=N mod=0
type=SDL_EVENT_KEY_UP key=N mod=0
type=SDL_EVENT_KEY_UP key=Left Shift mod=0

In the auto typing case, the auto typing application types too fast.
Therefore, when SDL3 calls X11_XQueryPointer, the Left Shift key has already been released.

Is the current behavior correct? I think this is a race condition problem.

I think SDL_KeyboardEvent.mod should match the mod state when the event happened. To get the current mod state one should use SDL_GetModState.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions