raysan5 / raylib

A simple and easy-to-use library to enjoy videogames programming

Home Page:http://www.raylib.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[rcore] Caps Lock release is not registered on Desktop platforms

DenizBasgoren opened this issue · comments

  • [ X] I tested it on latest raylib version from master branch
  • [ X] I checked there is no similar issue already reported
  • [ X] I checked the documentation on the wiki
  • [ X] My code has no errors or misuse of raylib

Issue description

KEY_CAPS_LOCK is up at the program startup. When it's pressed, it goes down (IsKeyDown and IsKeyPressed work correctly), but then when I release it it gets stuck at down forever (IsKeyUp and IsKeyReleased don't work, and IsKeyDown, IsKeyPressed don't work correctly anymore).

Environment

OS: Linux (kernel version 6.9.3-arch1-1).

Code Example

#include <stdio.h>
#include <raylib.h>
#include <unistd.h>
#include <time.h>


void clearScreen(void) {
    printf("\x1b[2J\x1b[H");
}

void printTime(void) {
    time_t current_time = time(NULL);
    char* time_string = ctime(&current_time);
    printf("%s\n", time_string);
}

void logCapsLock(void) {
    if (IsKeyPressed(KEY_CAPS_LOCK)) {
        puts("Capslock is pressed!");
    }
    if (IsKeyReleased(KEY_CAPS_LOCK)) {
        puts("Capslock is released!");
    }
    if (IsKeyDown(KEY_CAPS_LOCK)) {
        puts("Capslock is down!");
    }
    if (IsKeyUp(KEY_CAPS_LOCK)) {
        puts("Capslock is up!");
    }
}

int main(void) {

    InitWindow(100, 100, "title");
    SetTargetFPS(1);

    while(true) {
        clearScreen();

        BeginDrawing();

        if (IsKeyDown(KEY_Q)) {
            break;
        }

        printTime();
        logCapsLock();

        EndDrawing();
    }

    puts("Done");

    return 0;
}

Notes

I suspect that https://github.com/raysan5/raylib/blob/master/src/platforms/rcore_desktop.c is the file to be fixed. Particularly, these lines:

// GLFW3 Keyboard Callback, runs on key pressed
static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods)
{
    // ...
    
    // WARNING: Check if CAPS/NUM key modifiers are enabled and force down state for those keys
    if (((key == KEY_CAPS_LOCK) && ((mods & GLFW_MOD_CAPS_LOCK) > 0)) ||
        ((key == KEY_NUM_LOCK) && ((mods & GLFW_MOD_NUM_LOCK) > 0))) CORE.Input.Keyboard.currentKeyState[key] = 1;
)

I just spent the better part of ~2 hours staring at this and trying stuff. it might be a bit ranty but hopefully this helps someone else

  1. mods bitwise flags do not seem to be updated until the next frame. that is on GLFW, but this is what raylib is using to identify the caps/num lock states. this is VERY important
  2. the callback triggers (at least) twice. once for press and once for release (and hold for a 3rd trigger). this is important to note because the if statement to set the .state = 1 does NOT depend on the action
  3. GLFW "mods" and raylibs internal keyboard states are entirely separate so the logic to "fix" this is a bit awkward imo

these steps may make it easier to understand (assuming 1 FPS but you get the point):

  • start the app with caps lock disabled
  • enable caps lock.
    • frame 1 (press): the output will be mods = 32 (disabled)
    • frame 1 (release): the output will be mods = 48 (enabled)
    • frame 2: IsKeyPressed(KEY_CAPS_LOCK) = true
  • disable caps lock.
    • frame 3 (press): the output will be mods = 48 (enabled)
    • frame 3 (release): the output will be mods = 48 (enabled)
    • note: this should disable caps lock but it can not due to the 1 frame delay on mods and raylib forcing state = 1
  • press any other key, such as "a"
    • frame 4 (press): the output will be mods = 32 (disabled)
    • frame 4 (release): the output will be mods = 32 (disabled)
    • frame 5: IsKeyReleased(KEY_CAPS_LOCK) = true

notice the GLFW mods updates on the NEXT press. however, raylib never disables it unless you press another key and wait another frame or two
also notice the IsKeyReleased only happens when raylib internally sets CORE.Input.Keyboard.currentKeyState = 0, which only happens after mods updates

i dont know an easy solution for this. running it locally and playing with static void KeyCallback the logic gets really messy due to GLFW updating 1 frame later

it would be nice to do something like this, but we cant since mods is not updated until 1 frame later and the callback only happens on key press

if ((mods & GLFW_MOD_CAPS_LOCK) > 0) {
   CORE.Input.Keyboard.currentKeyState[KEY_CAPS_LOCK] = 1;
} else {
   CORE.Input.Keyboard.currentKeyState[KEY_CAPS_LOCK] = 0;
}

modified sample from OP:

#include <stdio.h>
#include <raylib.h>

void logCapsLock() {
    if (IsKeyPressed(KEY_CAPS_LOCK)) {
        printf("Capslock is pressed!\n");
    }
    if (IsKeyReleased(KEY_CAPS_LOCK)) {
        printf("Capslock is released!\n");
    }
    if (IsKeyDown(KEY_CAPS_LOCK)) {
        printf("Capslock is enabled!\n");
    }
    if (IsKeyUp(KEY_CAPS_LOCK)) {
        printf("Capslock is NOT enabled!\n");
    }
}

int main() {
    InitWindow(100, 100, "");
    SetTargetFPS(1);

    int curFrame = 1;
    while(!WindowShouldClose()) {
        printf("starting frame %d\n", curFrame);
        BeginDrawing();
        ClearBackground(RAYWHITE);

            logCapsLock();

        EndDrawing();
        curFrame++;
        printf("\n\n");
    }

    return 0;
}

modified raylib for logging:

// GLFW3 Keyboard Callback, runs on key pressed
static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods)
{
    if (key < 0) return;    // Security check, macOS fn key generates -1
    
    printf("new key: %d, action: %d, mods: %d\n", key, action, mods);
    
    // ...

OS: Linux (kernel version 6.9.4-zen1-1-zen).

commented

@DenizBasgoren @CrackedPixel This could be a very sensitive change and can break some codebases, not sure about supporting it. It's also dependant on GLFW behaviour...

@DenizBasgoren @CrackedPixel This could be a very sensitive change and can break some codebases, not sure about supporting it. It's also dependant on GLFW behaviour...

Agreed. I think it should be fixed in GLFW because that's the part that's not working in the desired way

commented

@CrackedPixel Agree, this behaviour comes from a lower-level API...