/* * This file is part of the DXX-Rebirth project . * It is copyright by its individual contributors, as recorded in the * project's Git history. See COPYING.txt at the top level for license * terms and a link to the Git history. */ /* * * SDL keyboard input support * * */ #include #include #include #include #include "event.h" #include "dxxerror.h" #include "key.h" #include "timer.h" #include "window.h" #include "console.h" #include "args.h" #include "dxxsconf.h" #include "compiler-array.h" static unsigned char Installed = 0; //-------- Variable accessed by outside functions --------- int keyd_repeat = 0; // 1 = use repeats, 0 no repeats pressed_keys keyd_pressed; fix64 keyd_time_when_last_pressed; array unicode_frame_buffer; const array key_properties = {{ { "", 255, SDLK_UNKNOWN }, // 0 { "ESC", 255, SDLK_ESCAPE }, { "1", '1', SDLK_1 }, { "2", '2', SDLK_2 }, { "3", '3', SDLK_3 }, { "4", '4', SDLK_4 }, { "5", '5', SDLK_5 }, { "6", '6', SDLK_6 }, { "7", '7', SDLK_7 }, { "8", '8', SDLK_8 }, { "9", '9', SDLK_9 }, // 10 { "0", '0', SDLK_0 }, { "-", '-', SDLK_MINUS }, { "=", '=', SDLK_EQUALS }, { "BSPC", 255, SDLK_BACKSPACE }, { "TAB", 255, SDLK_TAB }, { "Q", 'q', SDLK_q }, { "W", 'w', SDLK_w }, { "E", 'e', SDLK_e }, { "R", 'r', SDLK_r }, { "T", 't', SDLK_t }, // 20 { "Y", 'y', SDLK_y }, { "U", 'u', SDLK_u }, { "I", 'i', SDLK_i }, { "O", 'o', SDLK_o }, { "P", 'p', SDLK_p }, { "[", '[', SDLK_LEFTBRACKET }, { "]", ']', SDLK_RIGHTBRACKET }, { "ENTER", 255, SDLK_RETURN }, { "LCTRL", 255, SDLK_LCTRL }, { "A", 'a', SDLK_a }, // 30 { "S", 's', SDLK_s }, { "D", 'd', SDLK_d }, { "F", 'f', SDLK_f }, { "G", 'g', SDLK_g }, { "H", 'h', SDLK_h }, { "J", 'j', SDLK_j }, { "K", 'k', SDLK_k }, { "L", 'l', SDLK_l }, { ";", ';', SDLK_SEMICOLON }, { "'", '\'', SDLK_QUOTE }, // 40 { "`", '`', SDLK_BACKQUOTE }, { "LSHFT", 255, SDLK_LSHIFT }, { "\\", '\\', SDLK_BACKSLASH }, { "Z", 'z', SDLK_z }, { "X", 'x', SDLK_x }, { "C", 'c', SDLK_c }, { "V", 'v', SDLK_v }, { "B", 'b', SDLK_b }, { "N", 'n', SDLK_n }, { "M", 'm', SDLK_m }, // 50 { ",", ',', SDLK_COMMA }, { ".", '.', SDLK_PERIOD }, { "/", '/', SDLK_SLASH }, { "RSHFT", 255, SDLK_RSHIFT }, { "PAD*", '*', SDLK_KP_MULTIPLY }, { "LALT", 255, SDLK_LALT }, { "SPC", ' ', SDLK_SPACE }, { "CPSLK", 255, SDLK_CAPSLOCK }, { "F1", 255, SDLK_F1 }, { "F2", 255, SDLK_F2 }, // 60 { "F3", 255, SDLK_F3 }, { "F4", 255, SDLK_F4 }, { "F5", 255, SDLK_F5 }, { "F6", 255, SDLK_F6 }, { "F7", 255, SDLK_F7 }, { "F8", 255, SDLK_F8 }, { "F9", 255, SDLK_F9 }, { "F10", 255, SDLK_F10 }, { "NMLCK", 255, SDLK_NUMLOCK }, { "SCLK", 255, SDLK_SCROLLOCK }, // 70 { "PAD7", 255, SDLK_KP7 }, { "PAD8", 255, SDLK_KP8 }, { "PAD9", 255, SDLK_KP9 }, { "PAD-", 255, SDLK_KP_MINUS }, { "PAD4", 255, SDLK_KP4 }, { "PAD5", 255, SDLK_KP5 }, { "PAD6", 255, SDLK_KP6 }, { "PAD+", 255, SDLK_KP_PLUS }, { "PAD1", 255, SDLK_KP1 }, { "PAD2", 255, SDLK_KP2 }, // 80 { "PAD3", 255, SDLK_KP3 }, { "PAD0", 255, SDLK_KP0 }, { "PAD.", 255, SDLK_KP_PERIOD }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "F11", 255, SDLK_F11 }, { "F12", 255, SDLK_F12 }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, // 90 { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "PAUSE", 255, SDLK_PAUSE }, { "W0", 255, SDLK_WORLD_0 }, { "W1", 255, SDLK_WORLD_1 }, { "W2", 255, SDLK_WORLD_2 }, // 100 { "W3", 255, SDLK_WORLD_3 }, { "W4", 255, SDLK_WORLD_4 }, { "W5", 255, SDLK_WORLD_5 }, { "W6", 255, SDLK_WORLD_6 }, { "W7", 255, SDLK_WORLD_7 }, { "W8", 255, SDLK_WORLD_8 }, { "W9", 255, SDLK_WORLD_9 }, { "W10", 255, SDLK_WORLD_10 }, { "W11", 255, SDLK_WORLD_11 }, { "W12", 255, SDLK_WORLD_12 }, // 110 { "W13", 255, SDLK_WORLD_13 }, { "W14", 255, SDLK_WORLD_14 }, { "W15", 255, SDLK_WORLD_15 }, { "W16", 255, SDLK_WORLD_16 }, { "W17", 255, SDLK_WORLD_17 }, { "W18", 255, SDLK_WORLD_18 }, { "W19", 255, SDLK_WORLD_19 }, { "W20", 255, SDLK_WORLD_20 }, { "W21", 255, SDLK_WORLD_21 }, { "W22", 255, SDLK_WORLD_22 }, // 120 { "W23", 255, SDLK_WORLD_23 }, { "W24", 255, SDLK_WORLD_24 }, { "W25", 255, SDLK_WORLD_25 }, { "W26", 255, SDLK_WORLD_26 }, { "W27", 255, SDLK_WORLD_27 }, { "W28", 255, SDLK_WORLD_28 }, { "W29", 255, SDLK_WORLD_29 }, { "W30", 255, SDLK_WORLD_30 }, { "W31", 255, SDLK_WORLD_31 }, { "W32", 255, SDLK_WORLD_32 }, // 130 { "W33", 255, SDLK_WORLD_33 }, { "W34", 255, SDLK_WORLD_34 }, { "W35", 255, SDLK_WORLD_35 }, { "W36", 255, SDLK_WORLD_36 }, { "W37", 255, SDLK_WORLD_37 }, { "W38", 255, SDLK_WORLD_38 }, { "W39", 255, SDLK_WORLD_39 }, { "W40", 255, SDLK_WORLD_40 }, { "W41", 255, SDLK_WORLD_41 }, { "W42", 255, SDLK_WORLD_42 }, // 140 { "W43", 255, SDLK_WORLD_43 }, { "W44", 255, SDLK_WORLD_44 }, { "W45", 255, SDLK_WORLD_45 }, { "W46", 255, SDLK_WORLD_46 }, { "W47", 255, SDLK_WORLD_47 }, { "W48", 255, SDLK_WORLD_48 }, { "W49", 255, SDLK_WORLD_49 }, { "W50", 255, SDLK_WORLD_50 }, { "W51", 255, SDLK_WORLD_51 }, { "", 255, SDLK_UNKNOWN }, // 150 { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "PAD", 255, SDLK_KP_ENTER }, { "RCTRL", 255, SDLK_RCTRL }, { "LCMD", 255, SDLK_LMETA }, { "RCMD", 255, SDLK_RMETA }, { "", 255, SDLK_UNKNOWN }, // 160 { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, // 170 { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, // 180 { "PAD/", 255, SDLK_KP_DIVIDE }, { "", 255, SDLK_UNKNOWN }, { "PRSCR", 255, SDLK_PRINT }, { "RALT", 255, SDLK_RALT }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, // 190 { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "", 255, SDLK_UNKNOWN }, { "HOME", 255, SDLK_HOME }, { "UP", 255, SDLK_UP }, // 200 { "PGUP", 255, SDLK_PAGEUP }, { "", 255, SDLK_UNKNOWN }, { "LEFT", 255, SDLK_LEFT }, { "", 255, SDLK_UNKNOWN }, { "RIGHT", 255, SDLK_RIGHT }, { "", 255, SDLK_UNKNOWN }, { "END", 255, SDLK_END }, { "DOWN", 255, SDLK_DOWN }, { "PGDN", 255, SDLK_PAGEDOWN }, { "INS", 255, SDLK_INSERT }, // 210 { "DEL", 255, SDLK_DELETE }, { "W52", 255, SDLK_WORLD_52 }, { "W53", 255, SDLK_WORLD_53 }, { "W54", 255, SDLK_WORLD_54 }, { "W55", 255, SDLK_WORLD_55 }, { "W56", 255, SDLK_WORLD_56 }, { "W57", 255, SDLK_WORLD_57 }, { "W58", 255, SDLK_WORLD_58 }, { "W59", 255, SDLK_WORLD_59 }, { "W60", 255, SDLK_WORLD_60 }, // 220 { "W61", 255, SDLK_WORLD_61 }, { "W62", 255, SDLK_WORLD_62 }, { "W63", 255, SDLK_WORLD_63 }, { "W64", 255, SDLK_WORLD_64 }, { "W65", 255, SDLK_WORLD_65 }, { "W66", 255, SDLK_WORLD_66 }, { "W67", 255, SDLK_WORLD_67 }, { "W68", 255, SDLK_WORLD_68 }, { "W69", 255, SDLK_WORLD_69 }, { "W70", 255, SDLK_WORLD_70 }, // 230 { "W71", 255, SDLK_WORLD_71 }, { "W72", 255, SDLK_WORLD_72 }, { "W73", 255, SDLK_WORLD_73 }, { "W74", 255, SDLK_WORLD_74 }, { "W75", 255, SDLK_WORLD_75 }, { "W76", 255, SDLK_WORLD_76 }, { "W77", 255, SDLK_WORLD_77 }, { "W78", 255, SDLK_WORLD_78 }, { "W79", 255, SDLK_WORLD_79 }, { "W80", 255, SDLK_WORLD_80 }, // 240 { "W81", 255, SDLK_WORLD_81 }, { "W82", 255, SDLK_WORLD_82 }, { "W83", 255, SDLK_WORLD_83 }, { "W84", 255, SDLK_WORLD_84 }, { "W85", 255, SDLK_WORLD_85 }, { "W86", 255, SDLK_WORLD_86 }, { "W87", 255, SDLK_WORLD_87 }, { "W88", 255, SDLK_WORLD_88 }, { "W89", 255, SDLK_WORLD_89 }, { "W90", 255, SDLK_WORLD_90 }, // 250 { "W91", 255, SDLK_WORLD_91 }, { "W92", 255, SDLK_WORLD_92 }, { "W93", 255, SDLK_WORLD_93 }, { "W94", 255, SDLK_WORLD_94 }, { "W95", 255, SDLK_WORLD_95 }, // 255 }}; namespace { struct d_event_keycommand : d_event { int keycode; }; } static int key_ismodlck(int keycode) { switch (keycode) { case KEY_LSHIFT: case KEY_RSHIFT: case KEY_LALT: case KEY_RALT: case KEY_LCTRL: case KEY_RCTRL: case KEY_LMETA: case KEY_RMETA: return KEY_ISMOD; case KEY_NUMLOCK: case KEY_SCROLLOCK: case KEY_CAPSLOCK: return KEY_ISLCK; default: return 0; } } unsigned char key_ascii() { using std::move; using std::next; static array unibuffer; auto src = begin(unicode_frame_buffer); auto dst = next(begin(unibuffer), strlen(reinterpret_cast(&unibuffer[0]))); // move temporal chars from unicode_frame_buffer to empty space behind last unibuffer char (if any) for (; dst != end(unibuffer); ++dst) if (*src != '\0') { *dst = *src; *src = '\0'; ++src; } // unibuffer is not empty. store first char, remove it, shift all chars one step left and then print our char if (unibuffer[0] != '\0') { unsigned char retval = unibuffer[0]; *move(next(unibuffer.begin()), unibuffer.end(), unibuffer.begin()) = 0; return retval; } else return 255; } void pressed_keys::update(const std::size_t keycode, const uint8_t pressed) { constexpr unsigned all_modifiers_combined = KEY_SHIFTED | KEY_ALTED | KEY_CTRLED | KEY_DEBUGGED | KEY_METAED; constexpr unsigned all_modifiers_shifted = all_modifiers_combined >> modifier_shift; static_assert(all_modifiers_combined == all_modifiers_shifted << modifier_shift, "shift error"); static_assert(all_modifiers_shifted == static_cast(all_modifiers_shifted), "truncation error"); uint8_t mask; keyd_pressed.update_pressed(keycode, pressed); switch (keycode) { case KEY_LSHIFT: case KEY_RSHIFT: mask = KEY_SHIFTED >> modifier_shift; break; case KEY_LALT: case KEY_RALT: mask = KEY_ALTED >> modifier_shift; break; case KEY_LCTRL: case KEY_RCTRL: mask = KEY_CTRLED >> modifier_shift; break; case KEY_DELETE: mask = KEY_DEBUGGED >> modifier_shift; break; case KEY_LMETA: case KEY_RMETA: mask = KEY_METAED >> modifier_shift; break; default: return; } if (pressed) modifier_cache |= mask; else modifier_cache &= ~mask; } void key_handler(SDL_KeyboardEvent *kevent) { int event_keysym=-1; // Read SDLK symbol and state event_keysym = kevent->keysym.sym; if (event_keysym == SDLK_UNKNOWN) return; const auto key_state = (kevent->state != SDL_RELEASED); // fill the unicode frame-related unicode buffer if (key_state && kevent->keysym.unicode > 31 && kevent->keysym.unicode < 255) { for (int i = 0; i < KEY_BUFFER_SIZE; i++) if (unicode_frame_buffer[i] == '\0') { unicode_frame_buffer[i] = kevent->keysym.unicode; break; } } //===================================================== auto re = key_properties.rend(); auto fi = std::find_if(key_properties.rbegin(), re, [event_keysym](const key_props &k) { return k.sym == event_keysym; }); if (fi == re) return; unsigned keycode = std::distance(key_properties.begin(), std::next(fi).base()); if (keycode == 0) return; /* * process the key if: * - it's a valid key AND * - if the keystate has changed OR * - key state same as last one and game accepts key repeats but keep out mod/lock keys */ if (key_state != keyd_pressed[keycode] || (keyd_repeat && !key_ismodlck(keycode))) { d_event_keycommand event; // now update the key props keyd_pressed.update(keycode, key_state); keycode |= keyd_pressed.get_modifiers(); // We allowed the key to be added to the queue for now, // because there are still input loops without associated windows event.type = key_state?EVENT_KEY_COMMAND:EVENT_KEY_RELEASE; event.keycode = keycode; con_printf(CON_DEBUG, "Sending event %s: %s %s %s %s %s %s", (key_state) ? "EVENT_KEY_COMMAND": "EVENT_KEY_RELEASE", (keycode & KEY_METAED) ? "META" : "", (keycode & KEY_DEBUGGED) ? "DEBUG" : "", (keycode & KEY_CTRLED) ? "CTRL" : "", (keycode & KEY_ALTED) ? "ALT" : "", (keycode & KEY_SHIFTED) ? "SHIFT" : "", key_properties[keycode & 0xff].key_text ); event_send(event); } } void key_close() { Installed = 0; } void key_init() { if (Installed) return; Installed=1; SDL_EnableUNICODE(1); key_toggle_repeat(1); keyd_time_when_last_pressed = timer_query(); // Clear the keyboard array key_flush(); } static void restore_sticky_key(const uint8_t *keystate, const unsigned i) { if (keystate[key_properties[i].sym]) // do not flush status of sticky keys { keyd_pressed.update_pressed(i, 1); } } void key_flush() { if (!Installed) key_init(); //Clear the unicode buffer unicode_frame_buffer = {}; keyd_pressed = {}; if (unlikely(CGameArg.CtlNoStickyKeys)) return; const auto &keystate = SDL_GetKeyState(NULL); restore_sticky_key(keystate, KEY_NUMLOCK); restore_sticky_key(keystate, KEY_SCROLLOCK); restore_sticky_key(keystate, KEY_CAPSLOCK); } int event_key_get(const d_event &event) { auto &e = static_cast(event); Assert(e.type == EVENT_KEY_COMMAND || e.type == EVENT_KEY_RELEASE); return e.keycode; } // same as above but without mod states int event_key_get_raw(const d_event &event) { auto &e = static_cast(event); Assert(e.type == EVENT_KEY_COMMAND || e.type == EVENT_KEY_RELEASE); auto keycode = e.keycode; if ( keycode & KEY_SHIFTED ) keycode &= ~KEY_SHIFTED; if ( keycode & KEY_ALTED ) keycode &= ~KEY_ALTED; if ( keycode & KEY_CTRLED ) keycode &= ~KEY_CTRLED; if ( keycode & KEY_DEBUGGED ) keycode &= ~KEY_DEBUGGED; if ( keycode & KEY_METAED ) keycode &= ~KEY_METAED; return keycode; } void key_toggle_repeat(int enable) { if (enable) { if (SDL_EnableKeyRepeat(KEY_REPEAT_DELAY, KEY_REPEAT_INTERVAL) == 0) keyd_repeat = 1; } else { SDL_EnableKeyRepeat(0, 0); keyd_repeat = 0; } key_flush(); }