Support (hopefully) all keyboard keys via scancodes, #323

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
This commit is contained in:
Daniel Gibson 2021-07-03 04:33:27 +02:00
parent 61a49a2547
commit ae2d3a7e99
4 changed files with 274 additions and 10 deletions

View file

@ -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 "<KEY NOT FOUND>";
}
if ( keynum < 0 || keynum > 255 ) {
if ( keynum < 0 || keynum >= MAX_KEYS ) {
return "<OUT OF RANGE>";
}
@ -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;

View file

@ -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:

View file

@ -94,6 +94,157 @@ struct mouse_poll_t {
static idList<kbd_poll_t> kbd_polls;
static idList<mouse_poll_t> 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
}
}
}

View file

@ -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 );