Skip to content

Obtaining translated keycodes? #1502

@ocornut

Description

@ocornut

Background

As discussed from #672 (comment) (note that the thread title comes a slightly tangential topic, I suggest reading from the linked post).

In SDL and Win32 api, the emitted keycodes are translated (which correspond to a letter that the user sees on their keyboard, or matches whichever keyboard layout/locale is active) and differs from scancodes (which correspond to a physical location).

Both information are useful for different reasons. As stated in #672:

Default mapping for games typically want to use scancode to ensure a known physical location, whereas default mapping for interfaces which have a connection to the actual letter (e.g application shortcuts) want to use keycodes.

Scancodes/keycodes/chars are utterly confusing especially when we add localization to the mix, and lots of code are getting this wrong, I certainly got this wrong many times myself. But as I am focusing on user interface this issue is becoming more apparent to me.

Right now it is not possible with GLFW to obtain the translated keycode. Since 3.1, GLFW performs some mumbo-jumbo which essentially undo what the OS is doing, and align keycodes with scancodes. So e.g. in a key callback although key and scancode have different value they convey the same data, whereas in other framework they are intended to convey different info.

That change was done to align mismatching behavior on various GLFW platforms. IHMO it should have been changed the other way, the behavior for the platform that was "corrected" was the correct one and other platforms should have been corrected.

There is a workaround as mentioned by Doug:

What @juliettef and I do for shortcuts like CTRL+S etc. is look for a matching translation using glfwGetKeyName() and use that key, or a default key if there is no matching translation. Deciding when/how to do it is somewhat awkward, as users can change language on the fly.

But it is extremely flaky as the key names comes from the OS, are not guaranteed by GLFW, and themselves are "translated" (the numeric keypad 5 key is "Num 5" in english and "5 (PAVE NUM)" in french). So while this technique is usable for the simplest cases (e.g. "A" to "Z") it requires the app to enter flaky grounds that a library like GLFW wants us to avoid.

Solution?

Rather than undoing this I would propose if desired an additional callback for translated keys, or a config parameter.

I think a config parameter would makes code sharing problematic. In the specific case of dear imgui, I provide GLFW ready to use back-ends, but the user will it use and access GLFW its own way.

My suggestion would be to add a new callback:

glfwSetTranslatedCharCallback

Where the function signature would change from:

void MyKeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods)

To

void MyKeyCallback(GLFWwindow* window, int key, int translated_key, int scancode, int action, int mods)

And both values are GLFW_KEY_ xxx enums.

It is a simple structural change but would require a little bit of work on each back-end.
It is a little awkard to be adding another callback but I don't see another option?

Addendum

Reference log of printing out data from glfw key callback, I'm only pasting this to help clarify the conversation:

English keyboard

pressed key: 66 (GLFW_KEY_B), scancode: 48, glfwGetKeyName: "B"
released key: 66 (GLFW_KEY_B), scancode: 48, glfwGetKeyName: "B"
pressed key: 81 (GLFW_KEY_Q), scancode: 16, glfwGetKeyName: "Q"
released key: 81 (GLFW_KEY_Q), scancode: 16, glfwGetKeyName: "Q"
pressed key: 65 (GLFW_KEY_A), scancode: 30, glfwGetKeyName: "A"
released key: 65 (GLFW_KEY_A), scancode: 30, glfwGetKeyName: "A"
pressed key: 77 (GLFW_KEY_M), scancode: 50, glfwGetKeyName: "M"
released key: 77 (GLFW_KEY_M), scancode: 50, glfwGetKeyName: "M"
pressed key: 59 (GLFW_KEY_SEMICOLON), scancode: 39, glfwGetKeyName: ";"
released key: 59 (GLFW_KEY_SEMICOLON), scancode: 39, glfwGetKeyName: ";"
pressed key: 325 (GLFW_KEY_KP_5), scancode: 76, glfwGetKeyName: "Num 5"
released key: 325 (GLFW_KEY_KP_5), scancode: 76, glfwGetKeyName: "Num 5"

French keyboard

pressed key: 66 (GLFW_KEY_B), scancode: 48, glfwGetKeyName: "B"
released key: 66 (GLFW_KEY_B), scancode: 48, glfwGetKeyName: "B"
pressed key: 81 (GLFW_KEY_Q), scancode: 16, glfwGetKeyName: "A"
released key: 81 (GLFW_KEY_Q), scancode: 16, glfwGetKeyName: "A"
pressed key: 65 (GLFW_KEY_A), scancode: 30, glfwGetKeyName: "Q"
released key: 65 (GLFW_KEY_A), scancode: 30, glfwGetKeyName: "Q"
pressed key: 77 (GLFW_KEY_M), scancode: 50, glfwGetKeyName: ","
released key: 77 (GLFW_KEY_M), scancode: 50, glfwGetKeyName: ","
pressed key: 59 (GLFW_KEY_SEMICOLON), scancode: 39, glfwGetKeyName: "M"
released key: 59 (GLFW_KEY_SEMICOLON), scancode: 39, glfwGetKeyName: "M"
pressed key: 325 (GLFW_KEY_KP_5), scancode: 76, glfwGetKeyName: "5 (PAVE NUM.)"
released key: 325 (GLFW_KEY_KP_5), scancode: 76, glfwGetKeyName: "5 (PAVE NUM.)"

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions