Description
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:
- KeyPress Left Shift happened at time t1
- KeyRelease Left Shift happened at time t2
- X server sets the mod state to 0 between t2 and t3
X11_HandleKeyEvent
called at time t3,X11_XQueryPointer
getsviddata->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
.