How is QKeyEvent supposed to be used for games?
-
As a quick experiment I tried the following sequence of keypresses on macOS:
- press the key labelled with . and >;
- press shift;
- release the ./> key;
- release shift.
Via QKeyEvent::key() I received:
- an initial
Key_Periodpress event, followed by a bunch more (as key repeat); - a
Key_Shiftpress event; - a
Key_Greaterpress event (followed by key repeats); - a
Key_Greaterrelease event; and - a
Key_Shiftrelease event.
The most obvious obstacles with trying to use
key()for a game:- at no point was a
Key_Periodrelease received — per the reported events, that key is pressed indefinitely; and - conflating the two keypresses to produce
Key_Greateris obviously unhelpful.
The documentation notes that
nativeScanCodeisn't usable on the Mac, so I skipped that.Via
::nativeVirtualKeyI received the same sequence of messages, obviously, but with a separate flaw:- I received a press for
kVK_ANSI_Period(in oldHIToolboxterms); - I then get a press with a
nativeVirtualKeyof0when I also press shift. So for some reason Qt blocks macOS's built-in value for shift of 0x38 (kVK_Shift) and thereby blocks the built-in value forkVK_ANSI_A, which actually is0; - these are followed with appropriately-timed release events for the same virtual keys.
So the most obvious obstacles with trying to use
nativeScanCodefor a game:- Qt declines to pass on the scan codes for anything it considers a modifier; and
- as a result it makes
kVK_ANSI_Aambiguous. I cannot safely support the 'a' key.
In an ideal world I'd be able to use
::key()but tell Qt not to apply modifiers. I assume that's not an option so what is the intended use ofQKeyEvent? -
I'm not on my mac right now, but I tryed on windows and it seems to work. If I understood you correctly you're pressing 3 keys: "." "<" and "shift" then releasing them one at the time. I can catch the all 3 press and release events.
Either way if you're trying to move a person in your game while keeping a key pressed with auto repeat, while catching the key press events, probably the result will be very ugly.
AFAIK there's not much consistency in the velocity the key press is fired and if your pressing, for example, the arrow up key for move up then alternate between left and right keys things can get messy.
This is how I do it: I usually disable auto repeat and catch only key press and realese events. I create a bool variable for each key I want to use, something like isKeyUp_pressed, wich are set and unset with the key press and key release events, you get the picture. Whenever some key is pressed I run a timer at my desired frame per second velocity. While the timer is running I just check wich keys are pressed and act upon it. There's a great advantage in using a timer because you can control the speed of the actions to take for the keys pressed and that gives you a very smooth movement of your charaters.
I usually also create a signal for each key. If in qml then I can create my own keyboard component with convenient signals for each key, then I can connect thoose signals to command whatever actions I want in the game.
Hope this helps. -
No, to explain again:
There is a single key on my keyboard labelled with both '.' and '>'. You will see this key on, at least, the standard layouts for the US and every European keyboard that I'm familiar with. I'll just call it the '.' key.
My sequence of presses is:
- press the '.' key;
- press the shift key;
- release the '.' key;
- release the shift key.
I don't care about key repeat, it isn't interesting to me.
From that sequence I see:
- I cannot use QKeyEvent::key() to track which keys are up and which are down because — again, ignoring key repeat — it reports three key presses, for
Key_Period, thenKey_Shift, thenKey_Greaterbut only two key releases, forKey_GreaterthenKey_Shift. It never reports a key release forKey_Period; - I cannot use
QKeyEvent::nativeScanCodebecause the documentation is explicit that it isn't supported on macOS; and - I cannot use
QKeyEvent::nativeVirtualKeybecause the value for the 'a' key and the sentinel value of0are identical, so I can't actually sense every key in the standard alphabet, including one that is in the default mapping for almost every modern game, and it doesn't report native virtual key values for modifiers, so I also can't use shift, control, alt, etc.
The question is purely about Qt seemingly being not fit for purpose since there is no reliable way to track key states.
This piece of software is already fully functional under other toolkits, this isn't a logic question.
-
No, to explain again:
There is a single key on my keyboard labelled with both '.' and '>'. You will see this key on, at least, the standard layouts for the US and every European keyboard that I'm familiar with. I'll just call it the '.' key.
My sequence of presses is:
- press the '.' key;
- press the shift key;
- release the '.' key;
- release the shift key.
I don't care about key repeat, it isn't interesting to me.
From that sequence I see:
- I cannot use QKeyEvent::key() to track which keys are up and which are down because — again, ignoring key repeat — it reports three key presses, for
Key_Period, thenKey_Shift, thenKey_Greaterbut only two key releases, forKey_GreaterthenKey_Shift. It never reports a key release forKey_Period; - I cannot use
QKeyEvent::nativeScanCodebecause the documentation is explicit that it isn't supported on macOS; and - I cannot use
QKeyEvent::nativeVirtualKeybecause the value for the 'a' key and the sentinel value of0are identical, so I can't actually sense every key in the standard alphabet, including one that is in the default mapping for almost every modern game, and it doesn't report native virtual key values for modifiers, so I also can't use shift, control, alt, etc.
The question is purely about Qt seemingly being not fit for purpose since there is no reliable way to track key states.
This piece of software is already fully functional under other toolkits, this isn't a logic question.
@ThomH Now I understand what you mean and I can confirm that behaviour in my mac. (Btw that key in portuguese layout is "." and ":" ). The only solution I can think of is for you to emit a "false" release event for Key_Period when Key_Greater is realeased. This may be a bit of work if you have other keys combinations with shift, that you want to catch.
-
@ThomH Now I understand what you mean and I can confirm that behaviour in my mac. (Btw that key in portuguese layout is "." and ":" ). The only solution I can think of is for you to emit a "false" release event for Key_Period when Key_Greater is realeased. This may be a bit of work if you have other keys combinations with shift, that you want to catch.
@johngod Yeah, it seems to be all of the keys other than alphabetics that do three pressed but only two releases if you press shift second.
To be even more clear for other readers, with a very basic application I added the following to my
QMainWindowsubclass:void MainWindow::keyPressEvent(QKeyEvent *event) { if(event->isAutoRepeat()) return; qDebug() << "Press" << event->key() << event->nativeVirtualKey() << event->modifiers() << event->nativeScanCode(); } void MainWindow::keyReleaseEvent(QKeyEvent *event) { if(event->isAutoRepeat()) return; qDebug() << "Release" << event->key() << event->nativeVirtualKey() << event->modifiers() << event->nativeScanCode(); }I then tested the sequence as above under both macOS — press the key labelled both . and > on US keyboards, press shift, release ., release shift — and Ubuntu 19.04.
Observing that
isAutoRepeat()is unreliable, output on Ubuntu is:Press 46 46 QFlags<Qt::KeyboardModifier>(NoModifier) 60 Release 46 46 QFlags<Qt::KeyboardModifier>(NoModifier) 60 Press 46 46 QFlags<Qt::KeyboardModifier>(NoModifier) 60 Release 46 46 QFlags<Qt::KeyboardModifier>(NoModifier) 60 Press 46 46 QFlags<Qt::KeyboardModifier>(NoModifier) 60 Release 46 46 QFlags<Qt::KeyboardModifier>(NoModifier) 60 Press 46 46 QFlags<Qt::KeyboardModifier>(NoModifier) 60 Press 16777248 65506 QFlags<Qt::KeyboardModifier>(ShiftModifier) 62 Release 62 62 QFlags<Qt::KeyboardModifier>(ShiftModifier) 60 Press 62 62 QFlags<Qt::KeyboardModifier>(ShiftModifier) 60 Release 62 62 QFlags<Qt::KeyboardModifier>(ShiftModifier) 60 Press 62 62 QFlags<Qt::KeyboardModifier>(ShiftModifier) 60 Release 62 62 QFlags<Qt::KeyboardModifier>(ShiftModifier) 60 Press 62 62 QFlags<Qt::KeyboardModifier>(ShiftModifier) 60 Release 62 62 QFlags<Qt::KeyboardModifier>(ShiftModifier) 60 Press 62 62 QFlags<Qt::KeyboardModifier>(ShiftModifier) 60 Release 62 62 QFlags<Qt::KeyboardModifier>(ShiftModifier) 60 Press 62 62 QFlags<Qt::KeyboardModifier>(ShiftModifier) 60 Release 62 62 QFlags<Qt::KeyboardModifier>(ShiftModifier) 60 Press 62 62 QFlags<Qt::KeyboardModifier>(ShiftModifier) 60 Release 62 62 QFlags<Qt::KeyboardModifier>(ShiftModifier) 60 Release 16777248 65506 QFlags<Qt::KeyboardModifier>(NoModifier) 62If I remove most of the ones that obviously are unflagged autorepeat then:
Press 46 46 QFlags<Qt::KeyboardModifier>(NoModifier) 60 Press 16777248 65506 QFlags<Qt::KeyboardModifier>(ShiftModifier) 62 Release 62 62 QFlags<Qt::KeyboardModifier>(ShiftModifier) 60 Press 62 62 QFlags<Qt::KeyboardModifier>(ShiftModifier) 60 Release 62 62 QFlags<Qt::KeyboardModifier>(ShiftModifier) 60 Release 16777248 65506 QFlags<Qt::KeyboardModifier>(NoModifier) 62So, no release for
key()46, ever. But I do get a release for 62 before I get a press. You'd think maybe I can use thenativeScanCode()to dig myself out of that hole, but testing the same code and key sequence on the same computer under macOS 10.15.5 produces:Press 46 47 QFlags<Qt::KeyboardModifier>(NoModifier) 1 Press 16777248 0 QFlags<Qt::KeyboardModifier>(ShiftModifier) 0 Release 62 47 QFlags<Qt::KeyboardModifier>(ShiftModifier) 1 Release 16777248 0 QFlags<Qt::KeyboardModifier>(NoModifier) 0In this case
isAutoRepeat()is possibly trustworthy butnativeScanCode()explicitly isn't supported under macOS per the documentation (and, in practice, seems to return only either1or0).So on both platforms I get no release for
key()46ever, and the only empirically-suggested workaround seem to be: if X11, match by thenativeScanCodefield; but if macOS then match by thenativeVirtualKey()field. I don't think that's sustainable, besides anything else because I don't have time to test every OS Qt supports and try to figure out if/how to undo its wacky failure to report key releases on each.I think @johngod's suggestion — that I have to make assumptions about which keys are really the same keys — is probably most sustainable since it relies on locale+platform keyboard layout, which is heavily nuanced but at least a fact.
Either that or pull in something like SDL just for its keyboard API (!)
If anybody could answer as to what the Qt engineers intended here that might help to inform a better strategy for dealing with this.
-
You can try asking on qt interest mailing list, I think there are more developers over there that would answer better about their plans.
Any way, you din't mention what's your use case, I guessing you want to do something like this:if (Key_Greater is release) doKeyGreaterReleaseStuff(); if (Key_Period is release) doKeyPeriodReleaseStuff(); //doenst workAssuming that KeyPeriod release should be fired along with keyGreater released, may you can get way with the following logic:
if (Key_Greater is release) { doKeyGreaterReleaseStuff(); doKeyPeriodReleaseStuff(); }About locale I'm not sure if you need to worry about that, I'm on portuguese keyboard and got the same 46 code for that "key period", even if my the symbols are diferent.
Cheers
-
I've been having a similar issue, since I'm developing a music program that treats the computer keyboard as a piano-shaped layout for note entry. I decided to do some research on how other Qt-based programs get physical keyboard layout.
QtWebEngine and Chromium
If I understand correctly, QtWebEngine translates
QKeyEventto platform-independent JavaScriptKeyboardEvent.code(MDN docs). I tried visiting the W3C "Keyboard Event Viewer" web app, and it works fine in the QtWebEngine-based Falkon browser on Linux Wayland (only tested QWERTY) and Xorg (QWERTY and Dvorak layouts).If this functionality is fully working, I think I could extract this functionality into a reusable library, to obtain platform-independent physical keycodes from Qt, using DOM names for these keycodes. There's certainly demand, as this thread, another qt.io thread, and several broken Qt programs (pressing 1 then Shift causes a stuck key) demonstrate.
My concern is, if the ambiguity between
kVK_ANSI_Aand modifiers is truly unavoidable on Mac, then QtWebEngine will have KeyboardEvent/etc. input errors on Mac, and so will my key conversion library. Has anyone tested to see if QtWebEngine has key input issues? I don't have a Mac to test with.QuteBrowser seems like an active QtWebEngine-based browser available on Mac. Falkon isn't available on Mac. Otter Browser's binary packages look awfully unmaintained, and I couldn't find a .dmg download on SourceForge.
I haven't found any Mac-specific issues on the bug tracker searching for https://bugreports.qt.io/secure/QuickSearch.jspa?searchString=KeyboardEvent, nor searching for
QtWebEngine keyorQtWebEngine keyboard.Deep dive
Qt's
nativeKeyCodeForKeyEvent()translates QKeyEvent into platform-specific keycodes understood by Chromium. The function contains the comment, "Ifdefs here should match <ui/events/keycodes/dom/keycode_converter.cc>, since that is where the native key code is eventually used." I think this function could be improved to mention the specific functionNativeKeycodeToDomCode().Qt's
WebEventFactory::toWebKeyboardEvent(QKeyEvent *ev)callsnativeKeyCodeForKeyEvent(), stores it intowebKitEvent.native_key_code, and passes it into Chromium'sNativeKeycodeToDomCode().Chromium's
DomCode NativeKeycodeToDomCode(uint32_t usb_keycode)has the doc comment "Convert a native (Mac/Win/Linux) keycode into a DomCode." The implementation is fairly simple:DomCode KeycodeConverter::NativeKeycodeToDomCode(int native_keycode) { for (auto& mapping : kDomCodeMappings) { if (mapping.native_keycode == native_keycode) return static_cast<DomCode>(mapping.usb_keycode); } return DomCode::NONE; }The file dom_code_data.inc is a massive table much like an X-macro. This file allows the user to construct an arbitrary mapping between different representations of keycodes. It repeatedly invokes the
DOM_CODEmacro on USB scancodes (also used forDomCodeenum values), evdev/XKB/Windows/Mac keycodes (I think XKB is always evdev+8), nullablechar const *string literals, and C identifiers.Chromium's
DomCodeis an enum with keycode names and USB scancode values, populated usingdom_code_data.inc. Chromium uses USB scancode values to uniquely identify/represent hardware keys. (Wouldn't it be a lot simpler if Qt gave you a hardware key directly? 😉 Sadly OSes don't give you this information.)Chromium's
kDomCodeMappingsis an array ofKeycodeMapEntry, also populated usingdom_code_data.inc.Chromium's
KeycodeMapEntryis a struct containing a USB keycode, native keycode (platform-dependent), andKeyboardEvent.codename.The platform-dependent values (sent by Qt) are found in the
KeycodeMapEntry::native_keycodefield, with the following description:// Contains one of the following: // On Linux: XKB scancode // On Windows: Windows OEM scancode // On Mac: Mac keycode // On Fuchsia: 16-bit Code from the USB Keyboard Usage Page. int native_keycode;The JS DOM key names are found in the
codefield, with the following description:// The UIEvents (aka: DOM4Events) |code| value as defined in: // http://www.w3.org/TR/DOM-Level-3-Events-code/ const char* code;How do https://www.w3.org/TR/DOM-Level-3-Events-code/#code-value-tables and https://www.w3.org/TR/uievents/#keys-codevalues and https://www.w3.org/TR/uievents-code/ compare? I haven't looked through them yet.
I think
DomCodeToCodeString()convertsDomCodetoKeyboardEvent.code, but I haven't verified.If I were to write my own API, I would have to choose between exposing the
DomCodeenum to users (fast comparison), or aCodeStringstring-based API (introspecting for key names), or both.
Back to Qt... Qt's
WebEventFactory::toWebKeyboardEvent(QKeyEvent *ev)callsNativeKeycodeToDomCode(), and assigns the return towebKitEvent.dom_code. The function eventually returnswebKitEvent.Qt's
content::NativeWebKeyboardEvent WebEventFactory::toWebKeyboardEvent(QKeyEvent *ev)is called in exactly one place where Qt forwards key events to Chromium.toWebKeyboardEvent()'s return value is stored aswebEventand passed tom_rwhv->host()->ForwardKeyboardEventWithCommands(webEvent, latency, &commands, nullptr);. I don't understand this, but it's not relevant to how QtWebEngine convertsQKeyEventto platform-independent keycodes.
webKitEventis of Chromium's typecontent::NativeWebKeyboardEventinheriting fromWebKeyboardEvent.(Chromium's WebKeyboardEvent) has the following interesting comment:
// The actual key code genenerated by the platform. The DOM spec runs // on Windows-equivalent codes (thus |windows_key_code| above) but it // doesn't hurt to have this one around. int native_key_code = 0;I theorize
windows_key_codemaps toKeyboardEvent.keyCode(deprecated),dom_codemaps toKeyboardEvent.code(physical key location), anddom_keymaps toKeyboardEvent.key(locale-specific and affected by shift). Of course, for our purposes, we only care aboutdom_code.Is that correct? https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/core/events/keyboard_event.cc;l=130-134;drc=052831f0220b79fe0c3343b49f6d2863ea6de05d It's complicated and I don't fully understand.
Unresolved questions
- Does QtWebEngine actually use QKeyEvent? I'd have to build it from scratch and stub out the functions to find out.
Other apps
Prior to figuring out QtWebEngine, I also explored other Qt apps with special handling of
QKeyEvent.nativeScanCode()ornativeVirtualKey.OpenClonk
OpenClonk (a game with a Qt-based GUI) has an interesting approach to handling keycodes, subtracting 8 from Linux XKB keycodes and doing something with Mac keycodes. I wonder if the game's keyboard input is unusable on Mac, since it returns
event.nativeScanCode();which is useless on Mac. https://github.com/openclonk/openclonk/blob/2bb4b62f0b8940812c4fd5269288813790a9102b/src/editor/C4ConsoleQtViewport.cpp#L313I get the feeling that QKeyEvent is only used for the game's editor. I think the actual game uses a different keyboard input with platform dependencies: https://github.com/openclonk/openclonk/blob/master/src/gui/C4KeyboardInput.cpp
In the end, I decided to use QtWebEngine's key handling code as a path towards platform-independent key events.
QtWebKit
QtWebKit is no longer developed in Qt. I'm not sure if there are any browsers using https://github.com/qtwebkit/qtwebkit, and I haven't looked into how it extracts platform-independent position-based keycodes/scancodes. I can't search this repo because it's a clone.
So I spent the time to shallow-clone to disk and
rgthrough the files.Source/WebKit/UIProcess/API/wpe/qt/WPEQtViewBackend.cppmight be relevant, butSource/WebCore/platform/qt/PlatformKeyboardEventQt.cpplooks disappointing once I actually open the file.Dolphin Emulator
Dolphin uses platform-specific APIs for keyboard/mouse/gamepad input. That might be a workable solution, but I don't have a Mac and don't know how well this will mesh with Qt widget event propagation. And I don't need gamepad or background input (but you might).
-
I released the library: https://github.com/nyanpasu64/qkeycode/
Qt declines to pass on the scan codes for anything it considers a modifier; and
as a result it makes kVK_ANSI_A ambiguous. I cannot safely support the 'a' key.I worked around this problem by treating nativeVirtualKey()=0 as "A" when nativeScanCode() is 1, and ignoring it otherwise. Is that a good solution, or will it run into other issues?
-
Fantastic work! I'll need to do some research re: licensing but hopefully you've solved my problem!
Otherwise, I tested quitebrowser under macOS with the W3C keyboard event viewer. Pressing and releasing 'a' seemed to work correctly; reported events for the 'legacy' portion of the table were:
# Event type charCode keyCode which 5 keyup 0 65 a 65 4 input - - - 3 beforeinput - - - 2 keypress 97 a 97 97 1 keydown 0 65 a 65 -
Fantastic work! I'll need to do some research re: licensing but hopefully you've solved my problem!
Otherwise, I tested quitebrowser under macOS with the W3C keyboard event viewer. Pressing and releasing 'a' seemed to work correctly; reported events for the 'legacy' portion of the table were:
# Event type charCode keyCode which 5 keyup 0 65 a 65 4 input - - - 3 beforeinput - - - 2 keypress 97 a 97 97 1 keydown 0 65 a 65 Otherwise, I tested quitebrowser under macOS with the W3C keyboard event viewer. Pressing and releasing 'a' seemed to work correctly; reported events for the 'legacy' portion of the table were:
When pressing the QWERY
akey (right of caps lock), QtWebEngine synthesizes DOMcodefromQKeyEvent::key(). Which means that switching keyboard layouts causes the key to produce incorrect or missingcode. I reported this at https://bugreports.qt.io/browse/QTBUG-85660, and my library has a workaround for this issue.Incidentally I found that Qt Creator's QtWebEngine sample browser is easier to use than qutebrowser, and sends keys directly to the site instead of capturing them unless you're in the right mode.