From ae2d3a7e992d4f4764303ff21bfc760d174cbccc Mon Sep 17 00:00:00 2001 From: Daniel Gibson Date: Sat, 3 Jul 2021 04:33:27 +0200 Subject: [PATCH] Support (hopefully) all keyboard keys via scancodes, #323 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If a key is pressed whichs SDL_Keycode isn't known to Doom3 (has no corresponding K_* constant), its SDL_Scancode is mapped to the corresponding newly added K_SC_* scancode constant. I think I have K_SC_* constants for all keys that differ between keyboard layouts (which is mostly printable characters; F1-F12, Ctrl, Shift, ... should be the same on all layouts, which means that e.g. SDL_SCANCODE_F1 always belongs to SDLK_F1 which the old code already maps to Doom3's K_F1). What's extra nice (IMO) is that when Doom3 requests a *localized* name of the key (like for showing in the bindings menu), we actually use the name of the SDL_Keycode that *currently* belongs to the scancode, and esp. the "Western High-ASCII characters" (ISO-8859-1) supported by Doom3 like Ä or Ñ are displayed correctly. (I already implemented a very similar hack in Yamagi Quake II and reused the list of scancodes) This should fix most of the problems reported in #323 --- neo/framework/KeyInput.cpp | 36 ++++++-- neo/framework/KeyInput.h | 72 +++++++++++++++- neo/sys/events.cpp | 165 ++++++++++++++++++++++++++++++++++++- neo/sys/sys_public.h | 11 +++ 4 files changed, 274 insertions(+), 10 deletions(-) diff --git a/neo/framework/KeyInput.cpp b/neo/framework/KeyInput.cpp index f2d80444..84451cec 100644 --- a/neo/framework/KeyInput.cpp +++ b/neo/framework/KeyInput.cpp @@ -182,13 +182,12 @@ static const keyname_t keynames[] = {"SEMICOLON", ';', "#str_07129"}, // because a raw semicolon separates commands {"APOSTROPHE", '\'', "#str_07130"}, // because a raw apostrophe messes with parsing + {"QUOTE", '"', ""}, // raw quote can't be good either {NULL, 0, NULL} }; - - -static const int MAX_KEYS = 256; +static const int MAX_KEYS = K_LAST_KEY+1; // DG: was 256, made it more flexible class idKey { public: @@ -250,6 +249,13 @@ void idKeyInput::ArgCompletion_KeyName( const idCmdArgs &args, void(*callback)( for ( kn = keynames; kn->name; kn++ ) { callback( va( "%s %s", args.Argv( 0 ), kn->name ) ); } + + for( int scKey = K_FIRST_SCANCODE; scKey <= K_LAST_SCANCODE; ++scKey ) { + const char* scName = Sys_GetScancodeName( scKey ); + if ( scName != NULL ) { + callback( va( "%s %s", args.Argv( 0 ), scName ) ); + } + } } /* @@ -330,6 +336,11 @@ int idKeyInput::StringToKeyNum( const char *str ) { return n1 * 16 + n2; } + // DG: scancode names start with "SC_" + if ( idStr::Icmpn( str, "SC_", 3 ) == 0 ) { + return Sys_GetKeynumForScancodeName( str ); + } + // scan for a text match for ( kn = keynames; kn->name; kn++ ) { if ( !idStr::Icmp( str, kn->name ) ) { @@ -346,6 +357,9 @@ idKeyInput::KeyNumToString Returns a string (either a single ascii char, a K_* name, or a 0x11 hex string) for the given keynum. + +NOTE: with localized = true, the returned string is only valid until the next call (at least for K_SC_*)! + (currently this is no problem) =================== */ const char *idKeyInput::KeyNumToString( int keynum, bool localized ) { @@ -357,7 +371,7 @@ const char *idKeyInput::KeyNumToString( int keynum, bool localized ) { return ""; } - if ( keynum < 0 || keynum > 255 ) { + if ( keynum < 0 || keynum >= MAX_KEYS ) { return ""; } @@ -368,6 +382,18 @@ const char *idKeyInput::KeyNumToString( int keynum, bool localized ) { return tinystr; } + if ( keynum >= K_FIRST_SCANCODE && keynum <= K_LAST_SCANCODE ) { + const char* scName = NULL; + if ( localized ) { + scName = Sys_GetLocalizedScancodeName( keynum ); + } else { + scName = Sys_GetScancodeName( keynum ); + } + if ( scName != NULL ) { + return scName; + } + } + // check for a key string for ( kn = keynames; kn->name; kn++ ) { if ( keynum == kn->keynum ) { @@ -709,7 +735,7 @@ void idKeyInput::PreliminaryKeyEvent( int keynum, bool down ) { keys[keynum].down = down; #ifdef ID_DOOM_LEGACY - if ( down ) { + if ( down && keynum < 127 ) { // DG: only ASCII keys are of interest here lastKeys[ 0 + ( lastKeyIndex & 15 )] = keynum; lastKeys[16 + ( lastKeyIndex & 15 )] = keynum; lastKeyIndex = ( lastKeyIndex + 1 ) & 15; diff --git a/neo/framework/KeyInput.h b/neo/framework/KeyInput.h index 6bff334c..155b038f 100644 --- a/neo/framework/KeyInput.h +++ b/neo/framework/KeyInput.h @@ -197,9 +197,79 @@ typedef enum { K_PRINT_SCR = 252, // SysRq / PrintScr K_RIGHT_ALT = 253, // used by some languages as "Alt-Gr" - K_LAST_KEY = 254 // this better be < 256! + + // DG: map all relevant scancodes from SDL to K_SC_* (taken from Yamagi Quake II) + // (relevant are ones that are likely to be keyboardlayout-dependent, + // i.e. printable characters of sorts, *not* Ctrl, Alt, F1, Del, ...) + K_FIRST_SCANCODE = 256, + + // !!! NOTE: if you add a scancode here, make sure to also add it to !!! + // !!! scancodemappings[] in sys/events.cpp (and preserve order) !!! + K_SC_A = K_FIRST_SCANCODE, + K_SC_B, + K_SC_C, + K_SC_D, + K_SC_E, + K_SC_F, + K_SC_G, + K_SC_H, + K_SC_I, + K_SC_J, + K_SC_K, + K_SC_L, + K_SC_M, + K_SC_N, + K_SC_O, + K_SC_P, + K_SC_Q, + K_SC_R, + K_SC_S, + K_SC_T, + K_SC_U, + K_SC_V, + K_SC_W, + K_SC_X, + K_SC_Y, + K_SC_Z, + // leaving out SDL_SCANCODE_1 ... _0, we handle them separately already + // also return, escape, backspace, tab, space, already handled as keycodes + K_SC_MINUS, + K_SC_EQUALS, + K_SC_LEFTBRACKET, + K_SC_RIGHTBRACKET, + K_SC_BACKSLASH, + K_SC_NONUSHASH, + K_SC_SEMICOLON, + K_SC_APOSTROPHE, + K_SC_GRAVE, + K_SC_COMMA, + K_SC_PERIOD, + K_SC_SLASH, + // leaving out lots of keys incl. from keypad, we already handle them as normal keys + K_SC_NONUSBACKSLASH, + K_SC_INTERNATIONAL1, /**< used on Asian keyboards, see footnotes in USB doc */ + K_SC_INTERNATIONAL2, + K_SC_INTERNATIONAL3, /**< Yen */ + K_SC_INTERNATIONAL4, + K_SC_INTERNATIONAL5, + K_SC_INTERNATIONAL6, + K_SC_INTERNATIONAL7, + K_SC_INTERNATIONAL8, + K_SC_INTERNATIONAL9, + K_SC_THOUSANDSSEPARATOR, + K_SC_DECIMALSEPARATOR, + K_SC_CURRENCYUNIT, + K_SC_CURRENCYSUBUNIT, + + K_LAST_SCANCODE = K_SC_CURRENCYSUBUNIT, // TODO: keep up to date! + + + // FIXME: maybe move everything joystick related here + + K_LAST_KEY // DG: this said "this better be < 256!"; I hope I fixed all places in code assuming this.. } keyNum_t; +enum { K_NUM_SCANCODES = K_LAST_SCANCODE - K_FIRST_SCANCODE + 1 }; class idKeyInput { public: diff --git a/neo/sys/events.cpp b/neo/sys/events.cpp index 8abd0b06..dc2e9136 100644 --- a/neo/sys/events.cpp +++ b/neo/sys/events.cpp @@ -94,6 +94,157 @@ struct mouse_poll_t { static idList kbd_polls; static idList mouse_polls; +struct scancodename_t { + int sdlScancode; + const char* name; +}; + +// scancodenames[keynum - K_FIRST_SCANCODE] belongs to keynum +static scancodename_t scancodemappings[] = { + // NOTE: must be kept in sync with the K_SC_* section of keyNum_t in framework/KeyInput.h ! + +#if SDL_VERSION_ATLEAST(2, 0, 0) + #define D3_SC_MAPPING(X) { SDL_SCANCODE_ ## X , "SC_" #X } +#else // SDL1.2 doesn't have scancodes + #define D3_SC_MAPPING(X) { 0 , "SC_" #X } +#endif + + D3_SC_MAPPING(A), // { SDL_SCANCODE_A, "SC_A" }, + D3_SC_MAPPING(B), + D3_SC_MAPPING(C), + D3_SC_MAPPING(D), + D3_SC_MAPPING(E), + D3_SC_MAPPING(F), + D3_SC_MAPPING(G), + D3_SC_MAPPING(H), + D3_SC_MAPPING(I), + D3_SC_MAPPING(J), + D3_SC_MAPPING(K), + D3_SC_MAPPING(L), + D3_SC_MAPPING(M), + D3_SC_MAPPING(N), + D3_SC_MAPPING(O), + D3_SC_MAPPING(P), + D3_SC_MAPPING(Q), + D3_SC_MAPPING(R), + D3_SC_MAPPING(S), + D3_SC_MAPPING(T), + D3_SC_MAPPING(U), + D3_SC_MAPPING(V), + D3_SC_MAPPING(W), + D3_SC_MAPPING(X), + D3_SC_MAPPING(Y), + D3_SC_MAPPING(Z), + // leaving out SDL_SCANCODE_1 ... _0, we handle them separately already + // also return, escape, backspace, tab, space, already handled as keycodes + D3_SC_MAPPING(MINUS), + D3_SC_MAPPING(EQUALS), + D3_SC_MAPPING(LEFTBRACKET), + D3_SC_MAPPING(RIGHTBRACKET), + D3_SC_MAPPING(BACKSLASH), + D3_SC_MAPPING(NONUSHASH), + D3_SC_MAPPING(SEMICOLON), + D3_SC_MAPPING(APOSTROPHE), + D3_SC_MAPPING(GRAVE), + D3_SC_MAPPING(COMMA), + D3_SC_MAPPING(PERIOD), + D3_SC_MAPPING(SLASH), + // leaving out lots of key incl. from keypad, we already handle them as normal keys + D3_SC_MAPPING(NONUSBACKSLASH), + D3_SC_MAPPING(INTERNATIONAL1), /**< used on Asian keyboards, see footnotes in USB doc */ + D3_SC_MAPPING(INTERNATIONAL2), + D3_SC_MAPPING(INTERNATIONAL3), /**< Yen */ + D3_SC_MAPPING(INTERNATIONAL4), + D3_SC_MAPPING(INTERNATIONAL5), + D3_SC_MAPPING(INTERNATIONAL6), + D3_SC_MAPPING(INTERNATIONAL7), + D3_SC_MAPPING(INTERNATIONAL8), + D3_SC_MAPPING(INTERNATIONAL9), + D3_SC_MAPPING(THOUSANDSSEPARATOR), + D3_SC_MAPPING(DECIMALSEPARATOR), + D3_SC_MAPPING(CURRENCYUNIT), + D3_SC_MAPPING(CURRENCYSUBUNIT) + +#undef D3_SC_MAPPING +}; + +// for keynums between K_FIRST_SCANCODE and K_LAST_SCANCODE +// returns e.g. "SC_A" for K_SC_A +const char* Sys_GetScancodeName( int key ) { + if ( key >= K_FIRST_SCANCODE && key <= K_LAST_SCANCODE ) { + int scIdx = key - K_FIRST_SCANCODE; + return scancodemappings[scIdx].name; + } + return NULL; +} + +static bool isAscii( const char* str_ ) { + const unsigned char* str = (const unsigned char*)str_; + while(*str != '\0') { + if(*str > 127) { + return false; + } + ++str; + } + return true; +} + +// returns localized name of the key (between K_FIRST_SCANCODE and K_LAST_SCANCODE), +// regarding the current keyboard layout - if that name is in ASCII or corresponds +// to a "High-ASCII" char supported by Doom3. +// Otherwise return same name as Sys_GetScancodeName() +// !! Returned string is only valid until next call to this function !! +const char* Sys_GetLocalizedScancodeName( int key ) { + if ( key >= K_FIRST_SCANCODE && key <= K_LAST_SCANCODE ) { + int scIdx = key - K_FIRST_SCANCODE; +#if SDL_VERSION_ATLEAST(2, 0, 0) + SDL_Scancode sc = ( SDL_Scancode ) scancodemappings[scIdx].sdlScancode; + SDL_Keycode k = SDL_GetKeyFromScancode( sc ); + if ( k >= 0xA1 && k <= 0xFF ) { + // luckily, the "High-ASCII" (ISO-8559-1) chars supported by Doom3 + // have the same values as the corresponding SDL_Keycodes. + static char oneCharStr[2] = {0, 0}; + oneCharStr[0] = (unsigned char)k; + return oneCharStr; + } else if ( k != SDLK_UNKNOWN ) { + const char *ret = SDL_GetKeyName( k ); + // the keyname from SDL2 is in UTF-8, which Doom3 can't print, + // so only return the name if it's ASCII, otherwise fall back to "SC_bla" + if ( ret && *ret != '\0' && isAscii( ret ) ) { + return ret; + } + } +#endif // SDL1.2 doesn't support this, use unlocalized name (also as fallback if we couldn't get a keyname) + return scancodemappings[scIdx].name; + + } + return NULL; +} + +// returns keyNum_t (K_SC_* constant) for given scancode name (like "SC_A") +// only makes sense to call it if name starts with "SC_" (or "sc_") +// returns -1 if not found +int Sys_GetKeynumForScancodeName( const char* name ) { + for( int scIdx = 0; scIdx < K_NUM_SCANCODES; ++scIdx ) { + if ( idStr::Icmp( name, scancodemappings[scIdx].name ) == 0 ) { + return scIdx + K_FIRST_SCANCODE; + } + } + return -1; +} + +#if SDL_VERSION_ATLEAST(2, 0, 0) +static int getKeynumForSDLscancode( SDL_Scancode scancode ) { + int sc = scancode; + for ( int scIdx=0; scIdx < K_NUM_SCANCODES; ++scIdx ) { + if ( scancodemappings[scIdx].sdlScancode == sc ) { + return scIdx + K_FIRST_SCANCODE; + } + } + return 0; +} +#endif + static byte mapkey(SDL_Keycode key) { switch (key) { case SDLK_BACKSPACE: @@ -285,6 +436,8 @@ void Sys_InitInput() { kbd_polls.SetGranularity(64); mouse_polls.SetGranularity(64); + assert(sizeof(scancodemappings)/sizeof(scancodemappings[0]) == K_NUM_SCANCODES && "scancodemappings incomplete?"); + #if !SDL_VERSION_ATLEAST(2, 0, 0) SDL_EnableUNICODE(1); SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); @@ -389,7 +542,7 @@ Sys_GetEvent sysEvent_t Sys_GetEvent() { SDL_Event ev; sysEvent_t res = { }; - byte key; + int key; static const sysEvent_t res_none = { SE_NONE, 0, 0, 0, NULL }; @@ -532,10 +685,14 @@ sysEvent_t Sys_GetEvent() { if (ev.key.keysym.scancode == SDL_SCANCODE_GRAVE) { // TODO: always do this check? key = Sys_GetConsoleKey(true); } else { - if (ev.type == SDL_KEYDOWN) { - common->Warning("unmapped SDL key %d", ev.key.keysym.sym); + // if the key couldn't be mapped so far, try to map the scancode to K_SC_* + key = getKeynumForSDLscancode(sc); + if(!key) { + if (ev.type == SDL_KEYDOWN) { + common->Warning("unmapped SDL key %d (scancode %d)", ev.key.keysym.sym, (int)sc); + } + continue; // handle next event } - continue; // handle next event } } } diff --git a/neo/sys/sys_public.h b/neo/sys/sys_public.h index 189a51ef..9d9105d3 100644 --- a/neo/sys/sys_public.h +++ b/neo/sys/sys_public.h @@ -166,6 +166,17 @@ unsigned char Sys_GetConsoleKey( bool shifted ); // does nothing on win32, as SE_KEY == SE_CHAR there // on other OSes, consider the keyboard mapping unsigned char Sys_MapCharForKey( int key ); +// for keynums between K_FIRST_SCANCODE and K_LAST_SCANCODE +// returns e.g. "SC_A" for K_SC_A +const char* Sys_GetScancodeName( int key ); +// returns localized name of the key (between K_FIRST_SCANCODE and K_LAST_SCANCODE), +// regarding the current keyboard layout - if that name is in ASCII or corresponds +// to a "High-ASCII" char supported by Doom3. +// Otherwise return same name as Sys_GetScancodeName() +// !! Returned string is only valid until next call to this function !! +const char* Sys_GetLocalizedScancodeName( int key ); +// returns keyNum_t (K_SC_* constant) for given scancode name (like "SC_A") +int Sys_GetKeynumForScancodeName( const char* name ); // keyboard input polling int Sys_PollKeyboardInputEvents( void );