Dealing with keyboard layouts for input on multiple platforms
-
For the purpose of discussion let's say I'm working on a game.
My game receives keyboard inputs from keyPressEvent and keyReleaseEvent, which gives me a QKeyEvent. Currently I'm using key() to get which key has been pressed. This has the problem of being dependent on keyboard layout - if a user uses Dvorak for example, the input will change to all over the place, while other layouts like AZERTY and QWERTZ will be just somewhat off. Non-Latin layouts like Hebrew will not work at all.
I must use another method which identifies a PHYSICAL key rather than the function assigned to it, with a constant code independent of the active keyboard layout.
QKeyEvent provides two other functions: nativeScanCode() and nativeVirtualKey().
nativeScanCode can be ruled out immediately because it doesn't work on mac:
Note: On Mac OS/X, this function is not useful, because there is no way to get the scan code from Carbon or Cocoa.
So we're left with nativeVirtualKey. This does actually provide a constant code like we want, but now we have a couple of new problems.
Problem 1: Different codes on different platforms
Each platform has a different set of virtual key codes. For Windows, the list can be found here. For mac, it's in Events.h. As a simple comparison to demonstrate that they are different, the virtual key code for the letter key O on Windows is 0x4F, while on mac the same key's code is 0x1F. Not to mention, I couldn't even find any relevant info for Linux.
This makes it impossible to set good default settings that would work on all platforms, forcing me to create separate defaults for each platform (unlike key()).
Problem 2: Display
I need to display the assigned keys to the user. The displayed key needs to change depending on the current layout, and the key to display is not the result of a key press event. There is no universal way to do this.
On Windows, I can use a variation of this code snippet: http://www.setnode.com/blog/mapvirtualkey-getkeynametext-and-a-story-of-how-to/
But that's just Windows. I have no idea how to do the same for mac or Linux. And besides, having to use different code for different platforms for such a basic thing is a real letdown.
So now I've come here to ask: does anyone know how to do this, or have a better suggestion? Why isn't this kind of feature built into Qt?
Thanks. Hope I didn't scare too many people away with this freaking essay..
-
Hi and welcome to devnet,
Short answer for the why: because it's outside of Qt's scope.
As for your keyboard setup issue, since we are dealing with a game. I would expect to simply have a configuration panel that allows me to assign the keys I want to the corresponding action.
Have sensible defaults like awsd for moving the character left/up/down/right and make it easy for people to re-assign the keys they want.
-
You could have a set of defaults based on the locale.
-
It's more complicated than that, which is the source of the problem.
For starters, there's hundreds of different locales used worldwide. It isn't practical for me to create a separate set of defaults for each one when all I want is to have the exact same physical layout for all of them.
To make matters worse, many users have more than one keyboard layout installed and switch between them on-the-fly, normally using Alt+Shift (at least on Windows). This means that even if the user sets their own key bindings, if they try to play the game while the wrong layout is active, it wouldn't work or may shuffle around all the keys if the two layouts share letters/keys but at different positions (e.g. QWERTY, QWERTZ, AZERTY, Dvorak).
Qt has an event type called KeyboardLayoutChange, which should be helpful, but what Qt doesn't have is any other info about keyboard layouts whatsoever. This event has no extra info in it either.
QLocale doesn't have any keyboard layout info either.
I am just finding no way to even find the active layout, let alone set defaults based on it.
-
Well, it's not something that's provided by Qt out of the box, so you'll have to check the platforms you want to support for how they provide that information.
You could even consider contributing that to Qt to improve it for everybody.
You might also open a feature request on the bug report system for that.
-
I found the next solution only. I changed the system keyboard layout to English using this command:
PostMessage(GetForegroundWindow(), WM_INPUTLANGCHANGEREQUEST, 1, 0x04090409);
But it is for Windows only, for example:
#ifdef _WIN32 #include <windows.h> #endif OpenGLWindow::OpenGLWindow() { setTitle("OpenGL ES 2.0, Qt6, C++"); resize(350, 350); #ifdef _WIN32 PostMessage(GetForegroundWindow(), WM_INPUTLANGCHANGEREQUEST, 1, 0x04090409); #endif QSurfaceFormat surfaceFormat; surfaceFormat.setSwapInterval(1); connect(this, SIGNAL(frameSwapped()), this, SLOT(update())); setFormat(surfaceFormat); }
-
This is a typical problem in any software (not just games) that is designed without actively thinking about different keyboard layouts.
An example is assigning an application shortcut such as Ctrl+" which works fine in In US keyboard layout since " is a single key press. But in the euro layouts " is not a single key press button but it's a character translated by the keyboard layout system and is typically a combination of a modifier key + some other key. So for myself it's Shift+2.
This means that Ctrl+" shortcut is completely fubar. Would would it be? Ctrl+Shift+2 ? Forget it, wont' work.
There are really 3(+) layers in keyboard event processing.
-
Primitive keyboard / driver specific scan codes that map directly to to the physical buttons without any regard what the button face (engraving says). Think of this simply as an electronic digital signal that indicates which button on the matrix was pressed.
-
Virtual keys coming from the device keyboard layout driver. You can think of this as the "universal set of scan codes that map to known IDs". Most keyboards regardless of the engraving have keys such as A, B, C,D, 1, 2, 3 etc and the drivers and the layouts are capable of mapping these universally (at least on QWERTY layouts) to virtual keys that have then designed identifiers. If you program against the native WIn32 you immediately recognize these, VK_ENTER or 'A' 0x41. On Linux X11 provides XK_Return, XK_a, XK_k etc.
-
Translated character input that can comes through to your application via the layout and IME system. This has nothing to do with key presses per se, even though mostly there were some key presses but the combination of modifier keys and non-modifier keys were mapped to some character. This is how Unicode input works, how IME works and how for example Chinese input works. The user hammers the keyboard and the system translates and maps the key presses to a character
-
Application level processing triggered by some keyboard input. Logical operation such as "fire weapon" in a video game.
Conclusions:
- Your application should never rely on characters for shortcuts etc actions since they're always going to fail for some users.
- Your application can't rely on scan codes (ahead of time) since they're device specific and not portable.
- Your application might have a fighting chance if you rely only on the small subset of virtual keys.
In my experience there are enough quirks and surprising platforms, layouts etc that the perfect solution that never fails for anyone is impossible. But with a few assumptions it's possible to get close enough to an acceptable working solution.
When defining shortcut keys
- Rely on simple virtual keys and modifier key combinations as the default and process these using the OS's VK events.
- Don't rely on characters.
- Let the user to re-map keys.
- For lower latency (such as games) first map the user specified virtual keys to device / platform specific scan codes and then check against the scan codes.
-
-
I have the same question for WebAssembly: https://forum.qt.io/topic/155454/dealing-with-keyboard-layout-for-input-on-qt-webassembly