#include "keyboard.h" #include #include // Qt is the worst. // // Assume your keyboard has a key labelled both . and >, as they do on US and UK keyboards. Call it the dot key. // Perform the following: // 1. press dot key; // 2. press shift key; // 3. release dot key; // 4. release shift key. // // Per empirical testing, and key repeat aside, on both macOS and Ubuntu 19.04 that sequence will result in // _three_ keypress events, but only _two_ key release events. You'll get presses for Qt::Key_Period, Qt::Key_Greater // and Qt::Key_Shift. You'll get releases only for Qt::Key_Greater and Qt::Key_Shift. // // How can you detect at runtime that Key_Greater and Key_Period are the same physical key? // // You can't. On Ubuntu they have the same values for QKeyEvent::nativeScanCode(), which are unique to the key, // but they have different ::nativeVirtualKey()s. // // On macOS they have the same ::nativeScanCode() only because on macOS [almost] all keys have the same // ::nativeScanCode(). So that's not usable. They have the same ::nativeVirtualKey()s, but since that isn't true // on Ubuntu, that's also not usable. // // So how can you track the physical keys on a keyboard via Qt? // // You can't. Qt is the worst. SDL doesn't have this problem, including in X11, but I'm not sure I want the extra // dependency. I may need to reassess. #ifdef Q_OS_LINUX #define HAS_X11 #endif #ifdef HAS_X11 #include #include #include #endif KeyboardMapper::KeyboardMapper() { #ifdef HAS_X11 struct DesiredMapping { KeySym source; Inputs::Keyboard::Key destination; }; constexpr DesiredMapping mappings[] = { {XK_Escape, Escape}, {XK_F1, F1}, {XK_F2, F2}, {XK_F3, F3}, {XK_F4, F4}, {XK_F5, F5}, {XK_F6, F6}, {XK_F7, F7}, {XK_F8, F8}, {XK_F9, F9}, {XK_F10, F10}, {XK_F11, F11}, {XK_F12, F12}, {XK_Sys_Req, PrintScreen}, {XK_Scroll_Lock, ScrollLock}, {XK_Pause, Pause}, {XK_grave, BackTick}, {XK_1, k1}, {XK_2, k2}, {XK_3, k3}, {XK_4, k4}, {XK_5, k5}, {XK_6, k6}, {XK_7, k7}, {XK_8, k8}, {XK_9, k9}, {XK_0, k0}, {XK_minus, Hyphen}, {XK_equal, Equals}, {XK_BackSpace, Backspace}, {XK_Tab, Tab}, {XK_Q, Q}, {XK_W, W}, {XK_E, E}, {XK_R, R}, {XK_T, T}, {XK_Y, Y}, {XK_U, U}, {XK_I, I}, {XK_O, O}, {XK_P, P}, {XK_bracketleft, OpenSquareBracket}, {XK_bracketright, CloseSquareBracket}, {XK_backslash, Backslash}, {XK_Caps_Lock, CapsLock}, {XK_A, A}, {XK_S, S}, {XK_D, D}, {XK_F, F}, {XK_G, G}, {XK_H, H}, {XK_J, J}, {XK_K, K}, {XK_L, L}, {XK_semicolon, Semicolon}, {XK_apostrophe, Quote}, {XK_Return, Enter}, {XK_Shift_L, LeftShift}, {XK_Z, Z}, {XK_X, X}, {XK_C, C}, {XK_V, V}, {XK_B, B}, {XK_N, N}, {XK_M, M}, {XK_comma, Comma}, {XK_period, FullStop}, {XK_slash, ForwardSlash}, {XK_Shift_R, RightShift}, {XK_Control_L, LeftControl}, {XK_Control_R, RightControl}, {XK_Alt_L, LeftOption}, {XK_Alt_R, RightOption}, {XK_Meta_L, LeftMeta}, {XK_Meta_R, RightMeta}, {XK_space, Space}, {XK_Left, Left}, {XK_Right, Right}, {XK_Up, Up}, {XK_Down, Down}, {XK_Insert, Insert}, {XK_Delete, Delete}, {XK_Home, Home}, {XK_End, End}, {XK_Num_Lock, NumLock}, {XK_KP_Divide, KeypadSlash}, {XK_KP_Multiply, KeypadAsterisk}, {XK_KP_Delete, KeypadDelete}, {XK_KP_7, Keypad7}, {XK_KP_8, Keypad8}, {XK_KP_9, Keypad9}, {XK_KP_Add, KeypadPlus}, {XK_KP_4, Keypad4}, {XK_KP_5, Keypad5}, {XK_KP_6, Keypad6}, {XK_KP_Subtract, KeypadMinus}, {XK_KP_1, Keypad1}, {XK_KP_2, Keypad2}, {XK_KP_3, Keypad3}, {XK_KP_Enter, KeypadEnter}, {XK_KP_0, Keypad0}, {XK_KP_Decimal, KeypadDecimalPoint}, {XK_KP_Equal, KeypadEquals}, {XK_Help, Help}, {0} }; const DesiredMapping *mapping = mappings; while(mapping->source != 0) { const auto sym = XKeysymToKeycode(QX11Info::display(), mapping->source); keyByKeySym[sym] = mapping->destination; ++mapping; } #endif } std::optional KeyboardMapper::keyForEvent(QKeyEvent *event) { // Workaround for X11: assume PC-esque mapping. #ifdef HAS_X11 if(QGuiApplication::platformName() == QLatin1String("xcb")) { const auto sym = keyByKeySym.find(event->nativeScancode()); if(sym == keyByKeySym.end()) return std::nullopt; return sym->destination; } #endif // Fall back on a limited, faulty adaptation. #define BIND2(qtKey, clkKey) case Qt::qtKey: return Inputs::Keyboard::Key::clkKey; #define BIND(key) BIND2(Key_##key, key) switch(event->key()) { default: return {}; BIND(Escape); BIND(F1); BIND(F2); BIND(F3); BIND(F4); BIND(F5); BIND(F6); BIND(F7); BIND(F8); BIND(F9); BIND(F10); BIND(F11); BIND(F12); BIND2(Key_Print, PrintScreen); BIND(ScrollLock); BIND(Pause); BIND2(Key_AsciiTilde, BackTick); BIND2(Key_1, k1); BIND2(Key_2, k2); BIND2(Key_3, k3); BIND2(Key_4, k4); BIND2(Key_5, k5); BIND2(Key_6, k6); BIND2(Key_7, k7); BIND2(Key_8, k8); BIND2(Key_9, k9); BIND2(Key_0, k0); BIND2(Key_Minus, Hyphen); BIND2(Key_Plus, Equals); BIND(Backspace); BIND(Tab); BIND(Q); BIND(W); BIND(E); BIND(R); BIND(T); BIND(Y); BIND(U); BIND(I); BIND(O); BIND(P); BIND2(Key_BraceLeft, OpenSquareBracket); BIND2(Key_BraceRight, CloseSquareBracket); BIND(Backslash); BIND(CapsLock); BIND(A); BIND(S); BIND(D); BIND(F); BIND(G); BIND(H); BIND(J); BIND(K); BIND(L); BIND(Semicolon); BIND2(Key_Apostrophe, Quote); BIND2(Key_QuoteDbl, Quote); // TODO: something to hash? BIND2(Key_Return, Enter); BIND2(Key_Shift, LeftShift); BIND(Z); BIND(X); BIND(C); BIND(V); BIND(B); BIND(N); BIND(M); BIND(Comma); BIND2(Key_Period, FullStop); BIND2(Key_Slash, ForwardSlash); // Omitted: right shift. BIND2(Key_Control, LeftControl); BIND2(Key_Alt, LeftOption); BIND2(Key_Meta, LeftMeta); BIND(Space); BIND2(Key_AltGr, RightOption); BIND(Left); BIND(Right); BIND(Up); BIND(Down); BIND(Insert); BIND(Home); BIND(PageUp); BIND(Delete); BIND(End); BIND(PageDown); BIND(NumLock); } #undef BIND #undef BIND2 }