Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SDL3] New SDL_GetKeyFromScancode always gives capital letters #11434

Open
beaubrueggemann opened this issue Nov 8, 2024 · 8 comments
Open
Assignees
Milestone

Comments

@beaubrueggemann
Copy link

TLDR: I think SDL_Get_Key_Name(SDL_GetKeyFromScancode(SDL_SCANCODE_A, SDL_KMOD_NONE, false)) should return lowercase "a" (on QWERTY layouts).

I just finished migrating a program I'm working on from SDL2 to SDL3. What motivated the migration for me was my excitement for the new version of SDL_GetKeyFromScancode, which now takes an SDL_Keymod.

The program I'm making has a Vim-like interface, where the user can configure a bunch of key bindings, many which have modifiers. Internally, these bindings are saved as the physical SDL_Scancodes, alongside any modifier state. But in the interface where the user learns/changes these bindings, I want to show the user names for the keys that match the way the user thinks about them.

In SDL2, the best I could manage was doing SDL_GetKeyName(SDL_GetKeyFromScancode(scancode)), which ignored modifiers. I then would combine the modifier manually into the string. This was mostly fine, but it bugged me that I had to show the binding as "Shift-3" instead of "#".

Hence my excitement for the new version of SDL_GetKeyFromScancode, which now takes modifiers! In SDL3 I can do SDL_Get_Key_Name(SDL_GetKeyFromScancode(scancode, modstate, false)), which gets me closer to what I want. But I was disappointed with the result.

My example above now works great. With scancode=SDL_SCANCODE_3 and modstate=SDL_KMOD_NONE, I get "3" as the result. Changing modstate to SDL_KMOD_SHIFT, I get "#". Perfect!

But when I do normal letters, the scheme breaks down. With scancode=SDL_SCANCODE_A, I get "A" as the result for both modstate=SDL_KMOD_NONE and modstate=SDL_KMOD_SHIFT. I very much would prefer that lowercase "a" was the result when SDL_KMOD_NONE is the modstate.

In my program, I have bindings for both capital "A" and lowercase "a". But the SDL method of getting a name for these bindings returns capital "A" in both cases.

@icculus
Copy link
Collaborator

icculus commented Nov 8, 2024

We did this for a reason but I can't remember why (maybe because we also capitalized the SDLK_* symbols...?), but yeah, this seems like a bug now when you put it like this.

@slouken, do you remember why we did that?

@slouken
Copy link
Collaborator

slouken commented Nov 9, 2024

Key names have always been upper case, since that's what you show in keybinding UI when binding keys (and also what you see when you look at the keyboard). The keycode can be lower or upper case, depending on modifiers.

@slouken slouken closed this as not planned Won't fix, can't repro, duplicate, stale Nov 9, 2024
@slouken
Copy link
Collaborator

slouken commented Nov 9, 2024

To be a little more clear, you should be precise when thinking about what you're binding to. Are you binding to SDL_SCANCODE_3 + SDL_KMOD_SHIFT, or are you binding to '#'? Depending on keyboard layout, these are very different things. Are you binding to SDL_SCANCODE_A + SDL_KMOD_SHIFT, which would be represented by Shift-A, or are you binding to 'A'? Again, on a DVORAK key layout, these are completely different.

Here's something @icculus whipped up to help people think through these:
https://wiki.libsdl.org/SDL3/BestKeyboardPractices

@icculus
Copy link
Collaborator

icculus commented Nov 9, 2024

Oh, I misunderstood. He's trying to do The Text Editor but also wants meaningful display symbols...but he should just treat the value he gets there as a Unicode codepoint and be done with it.

(And be prepared for misery if he wants more than a vi user would expect from the keypress, such as complicated Unicode composition.)

@beaubrueggemann
Copy link
Author

My program has a bunch of bindings. For the initial defaults, I don't want to bind them to SDL_Keycodes, such as 'I' for Inventory (what the guide calls The Specific Key), because, as the guide says: "some keyboards don't have an 'I' key!"

So the default bindings are all to physical keys, such as SDL_Scancode_I. This may not actually be an 'I' for the user, but there is at least some key there.

I have a lot of bindings, so a binding in my program is a combination of both an SDL_Scancode and an SDL_Keymod, to give me more possibilities.

This scheme works great except for one thing: I'd like to print a list of commands and their bindings to the user, but SDL doesn't really have a way to do that.

For example, if I have two bindings, represented internally as

Command1: {SDL_SCANCODE_B, SDL_KMOD_NONE}
Command2: {SDL_SCANCODE_B, SDL_KMOD_SHIFT}

Then I want some way to print this for a QWERTY keyboard:

Command1: b
Command2: B

And this for a DVORAK keyboard:

Command1: x
Command2: X

And so on...

I know this is possible, because other programs do it. For example, web browsers have a key property on their KeyboardEvents, which behaves in precisely the way I need:

If the pressed key has a printed representation, the returned value is a non-empty Unicode character string containing the printable representation of the key. For example: if the pressed key is the Space key, the returned value is a single space (" "). If the pressed key is the B key, the returned value is the string "b". However, if the Shift key is pressed at the same time (so shiftKey is true), the returned value is the string "B".

I realize that SDL was made with games in mind, and that most games probably don't use modifiers in this way that I am. For example, WASD are often directional controls. Even though Shift may modify their behavior (if it's bound to a "Run" action, say), it doesn't really change the meaning of the WASD keys ('A' is still "Left", regardless of whether you're running). So, in these more common scenarios, it makes sense that you'd always want to print the uppercase "A" when showing the "Left" binding (whether it's Shifted or not is irrelevant, so show the uppercase like the key label does).

But even though my case is the rarer case, I think it's silly for SDL to prevent it. An API could support both use cases. For example, SDL3 could keep the old SDL_GetKeyFromScancode from SDL2 (without the modstate) for the more common case in games, where you just want uppercase "A" and don't care about modifiers. And then the new function could be SDL_GetKeyFromScancodeAndMods, which takes the modifiers and actually applies them to the result. (Isn't it kind of weird how SDL3's current SDL_GetKeyFromScancode takes modifiers, but then applies them so selectively? Why differentiate "3" from "#" on QWERTY, but not "b" from "B"?)

Maybe the ship has sailed, and it's too late to make that kind of change to SDL3. But I really think it would be an improvement. (And maybe support for my case could be added in a backwards-compatible way, such as by adding a "case_sensitive" option to SDL_HINT_KEYCODE_OPTIONS).

I want to emphasize that I'm treating these keyboard keys like buttons. It's not text input (for which I use the text input API as described in The Chat Box, and it works well!). I'm really treating these bindings as buttons, (not text), and want to program in terms of KEY_DOWN and KEY_UP events.

I don't think I'm doing what you call The Text Editor (though it's not fully clear to me what that section is suggesting). I treat these bindings as buttons, like The 101-Button Joystick, but also with modifiers (effectively expanding to an even-more-button-joystick). And I just want to be able to tell the user what these bindings are, using simple and natural names.

I think this:

Command1: B
Command2: Shift-B
Command3: 3
Command4: Shift-3

is less simple and natural than this:

Command1: b
Command2: B
Command3: 3
Command4: #

(especially when you have dozens of bindings listed on screen).

And I think it would be good for SDL to support keyboard-binding-heavy games.

Anyways, I hope I'm not being a pest by posting all this. I just wanted to try and explain myself clearly (and it's strangely hard to talk about keyboard input clearly, lol). I think SDL is a lovely platform layer to program upon, with a well-organized and clean API, and I greatly appreciate all your work on it.

@slouken
Copy link
Collaborator

slouken commented Nov 9, 2024

Everything that you said makes sense, and SDL supports what you want to do, we just have a difference of terminology. You don’t want the key name, you just want the key code.

I’m not at a keyboard right now, but tomorrow I can show you want you want to do.

@beaubrueggemann
Copy link
Author

Oh, I get it now. I missed/forgot that the SDL_Keycode is just the unicode code point (when it doesn't have the SDLK_SCANCODE_MASK bit set).

So I can just print the code point's character (or pass the keycode to SDL_GetKeyName when the SDLK_SCANCODE_MASK is set, or when the code point is not printable).

I tested this out, and everything is working nicely. Thanks for the quick help!

I have a few thoughts I'd like to share, in case they are useful for you.

This paragraph in Migrating to SDL 3.0 confused me:

The keycode in key events is affected by modifiers by default. e.g. pressing the A key would generate the keycode SDLK_A, or 'a', and pressing it while holding the shift key would generate the keycode SDLK_A, or 'A'. This behavior can be customized with SDL_HINT_KEYCODE_OPTIONS.

The double SDLK_A references are what confuse me. SDLK_A is 'a', but then later it is 'A'.

And then I think the bottom section of the keyboard guide, Showing key names to users, might want to mention how sometimes you just want to print the keycode as a Unicode code point (and not use SDL_GetKeyName, which gives both 'a' and 'A' the same string "A").

And then, and it may be too late for this if SDL3 is locked in, but I really think "uppercase", defined in the first line of SDL_GetKeyName:

const bool uppercase = true;

should be made a parameter of SDL_GetKeyName:

const char *SDL_GetKeyName(SDL_Keycode key, bool uppercase);

Most games want uppercase == true, since the bindings ignore modifiers (A is left, regardless of shift), and we just want to show what's on the key label.

But some games, typically with LOTS of bindings, want to show 'b' or 'B' depending on the SDL_Keycode's code point. So they want uppercase == false.

Not having "uppercase" be a parameter means people with the second use case have extra work. First, they have to check whether the keycode is a printable Unicode code point (with the SDLK_SCANCODE_MASK unset). And then, depending on this result, they either have to print that code point's character, or have to call SDL_GetKeyName. I think the "uppercase" parameter makes things much simpler and clearer.

And, finally, if you don't decide to add an "uppercase" parameter to SDL_GetKeyName, then I think the documentation for SDL_GetKeyName should mention that it uppercases letters for you.

This is mentioned in the implementation of SDL_GetKeyName:

// SDL_Keycode is defined as the unshifted key on the keyboard,
// but the key name is defined as the letter printed on that key,
// which is usually the shifted capital letter.

But this isn't mentioned in the header file comment for SDL_GetKeyName.

That's all my thoughts! I hope you don't mind them. I thought they might be good food for thought toward helping others avoid tripping like I did. Again, thanks for all your work on SDL, and for helping me resolve my issue.

@icculus icculus self-assigned this Nov 11, 2024
@icculus icculus reopened this Nov 11, 2024
@icculus icculus added this to the 3.2.0 milestone Nov 11, 2024
@icculus
Copy link
Collaborator

icculus commented Nov 11, 2024

Reopening so I remember to tweak the wiki a little about this topic. Thanks!

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

No branches or pull requests

3 participants