Skip to content

Conversation

@livinamuk
Copy link
Contributor

Description

SDL was keeping a single boolean keystate per scancode. When two physical keyboards pressed the same key and then released it at roughly the same time, the first KEY_UP cleared the global state and the second KEY_UP was dropped.

This change adds a per-scancode reference count (keyrefcount[]) to track how many devices currently hold that key, updates SDL_SendKeyboardKeyInternal to decrement the count on key up, and only clears keystate and modifier state when the last device releases the key.

This matches the behavior expected in #12920 and works regardless of whether the events came from Windows raw input or other backends, because the drop was happening in the shared keyboard layer.

Existing Issue(s)

Fixes #12920

@slouken slouken added this to the 3.4.0 milestone Nov 11, 2025
Comment on lines 575 to 578
} else {
if (keyboard->keyrefcount[scancode] == 0) {
keyboard->keystate[scancode] = false;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
} else {
if (keyboard->keyrefcount[scancode] == 0) {
keyboard->keystate[scancode] = false;
}
} else if (last_release) {
keyboard->keystate[scancode] = false;

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was resolved without accepting the suggestion. Was it incorrect?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functionaly they're the same, but yours is tighter.

@slouken
Copy link
Collaborator

slouken commented Nov 11, 2025

I simplified this a little bit, does that work and make sense?

@livinamuk
Copy link
Contributor Author

Yeah, that’s cleaner, makes sense, and it still fixes the repro. This is the test program I’m using to verify two keyboards both get KEY_UP:

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

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

int main() {
    SDL_SetHint(SDL_HINT_WINDOWS_RAW_KEYBOARD, "1");
    SDL_SetHint(SDL_HINT_WINDOWS_GAMEINPUT, "1");

    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0) {
        std::cout << "SDL_Init failed: " << SDL_GetError() << "\n";
        return 1;
    }

    SDL_Window* window = SDL_CreateWindow("SDL Input", 800, 600, SDL_WINDOW_RESIZABLE);
    if (!window) {
        std::cout << "SDL_CreateWindow failed: " << SDL_GetError() << "\n";
        return 1;
    }
    SDL_RaiseWindow(window);

    bool running = true;

    while (running) {
        SDL_Event e;
        while (SDL_PollEvent(&e)) {
            switch (e.type) {
                case SDL_EVENT_KEY_DOWN: {
                    SDL_KeyboardID id = e.key.which;
                    std::cout << "KEY DOWN: " << e.key.key << " device id: " << id << "\n";

                    if (e.key.key == SDLK_ESCAPE) {
                        running = false;
                    }
                    break;
                }
                case SDL_EVENT_KEY_UP: {
                    SDL_KeyboardID id = e.key.which;
                    std::cout << "KEY UP:   " << e.key.key << " device id: " << id << "\n";
                    break;
                }
                case SDL_EVENT_QUIT: {
                    running = false;
                    break;
                }
                default:
                break;
            }
        }
        SDL_Delay(1);
    }

    SDL_DestroyWindow(window);
    SDL_Quit();
    return 0;
}

@slouken
Copy link
Collaborator

slouken commented Nov 11, 2025

Apologies for the extensive nit-picking. This is core SDL code though, and needs to be as easy to read as possible.

@livinamuk
Copy link
Contributor Author

It's no problem, I'm just glad the fix was this simple, it's been causing problems in a game of mine.

@slouken slouken merged commit 3dab15d into libsdl-org:main Nov 12, 2025
43 checks passed
@Alexej
Copy link

Alexej commented Nov 12, 2025

would it be an issue with the following code:

const bool* state = SDL_GetKeyboardState(NULL);

static int counter = 0;
if (state[SDL_SCANCODE_SPACE]) {
    printf("Space is held down! %i \n", counter++);
}

since you increment the ref count on every key down event but decrement only once when a key is released, which results in imbalance between increments and decrements. You set keystate to false only when ref is 0.

@slouken
Copy link
Collaborator

slouken commented Nov 12, 2025

would it be an issue with the following code:

const bool* state = SDL_GetKeyboardState(NULL);

static int counter = 0;
if (state[SDL_SCANCODE_SPACE]) {
    printf("Space is held down! %i \n", counter++);
}

since you increment the ref count on every key down event but decrement only once when a key is released, which results in imbalance between increments and decrements. You set keystate to false only when ref is 0.

That's a good point, any repeated key will be reported as held down indefinitely. We also can't just match against the keyboard ID because it may change as we enter and leave raw keyboard mode. I'll go ahead and revert this until we can find a better solution.

slouken added a commit that referenced this pull request Nov 12, 2025
…cancode state (#14446)"

This reverts commit 3dab15d.

With this commit any repeated key will be reported as held down indefinitely.
@jlecavelier
Copy link

Couldn't the states be reset when we enter / leave raw keyboard mode?

@slouken
Copy link
Collaborator

slouken commented Nov 13, 2025

Couldn't the states be reset when we enter / leave raw keyboard mode?

Maybe, it's not something that toggles frequently like relative mouse mode.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

When using multiple keyboards and raw input, a KEY_UP event is lost if the keys are released together

4 participants