| 1 | /* |
| 2 | * LegacyClonk |
| 3 | * |
| 4 | * Copyright (c) RedWolf Design |
| 5 | * Copyright (c) 2005, Sven2 |
| 6 | * Copyright (c) 2017-2020, The LegacyClonk Team and contributors |
| 7 | * |
| 8 | * Distributed under the terms of the ISC license; see accompanying file |
| 9 | * "COPYING" for details. |
| 10 | * |
| 11 | * "Clonk" is a registered trademark of Matthes Bender, used with permission. |
| 12 | * See accompanying file "TRADEMARK" for details. |
| 13 | * |
| 14 | * To redistribute this file separately, substitute the full license texts |
| 15 | * for the above references. |
| 16 | */ |
| 17 | |
| 18 | // Keyboard input mapping to engine functions |
| 19 | |
| 20 | #include <C4Include.h> |
| 21 | #include <C4KeyboardInput.h> |
| 22 | |
| 23 | #include <C4Game.h> |
| 24 | #include <C4Wrappers.h> |
| 25 | |
| 26 | #ifdef USE_X11 |
| 27 | #include <X11/Xlib.h> |
| 28 | #endif |
| 29 | |
| 30 | #ifdef USE_SDL_MAINLOOP |
| 31 | #include <SDL.h> |
| 32 | #endif |
| 33 | |
| 34 | // Key maps |
| 35 | |
| 36 | struct C4KeyShiftMapEntry |
| 37 | { |
| 38 | C4KeyShiftState eShift; |
| 39 | const char *szName; |
| 40 | }; |
| 41 | |
| 42 | const C4KeyShiftMapEntry KeyShiftMap[] = |
| 43 | { |
| 44 | { .eShift: KEYS_Alt, .szName: "Alt" }, |
| 45 | { .eShift: KEYS_Control, .szName: "Ctrl" }, |
| 46 | { .eShift: KEYS_Shift, .szName: "Shift" }, |
| 47 | { .eShift: KEYS_Undefined, .szName: nullptr } |
| 48 | }; |
| 49 | |
| 50 | C4KeyShiftState C4KeyCodeEx::String2KeyShift(const StdStrBuf &sName) |
| 51 | { |
| 52 | // query map |
| 53 | const C4KeyShiftMapEntry *pCheck = KeyShiftMap; |
| 54 | while (pCheck->szName) |
| 55 | if (SEqualNoCase(szStr1: sName.getData(), szStr2: pCheck->szName)) break; else ++pCheck; |
| 56 | return pCheck->eShift; |
| 57 | } |
| 58 | |
| 59 | std::string C4KeyCodeEx::KeyShift2String(C4KeyShiftState eShift) |
| 60 | { |
| 61 | // query map |
| 62 | const C4KeyShiftMapEntry *pCheck = KeyShiftMap; |
| 63 | while (pCheck->szName) |
| 64 | if (eShift == pCheck->eShift) break; else ++pCheck; |
| 65 | return pCheck->szName; |
| 66 | } |
| 67 | |
| 68 | struct C4KeyCodeMapEntry |
| 69 | { |
| 70 | C4KeyCode wCode; |
| 71 | const char *szName; |
| 72 | const char *szShortName; |
| 73 | }; |
| 74 | |
| 75 | #ifdef _WIN32 |
| 76 | const C4KeyCodeMapEntry KeyCodeMap[] = |
| 77 | { |
| 78 | { VK_CANCEL, "Cancel" , nullptr }, |
| 79 | |
| 80 | { VK_BACK, "Back" , nullptr }, |
| 81 | { VK_TAB, "Tab" , nullptr }, |
| 82 | { VK_CLEAR, "Clear" , nullptr }, |
| 83 | { VK_RETURN, "Return" , nullptr }, |
| 84 | |
| 85 | { VK_SHIFT, "KeyShift" , "Shift" }, |
| 86 | { VK_CONTROL, "KeyControl" , "Control" }, |
| 87 | { VK_MENU, "Menu" , nullptr }, |
| 88 | { VK_PAUSE, "Pause" , nullptr }, |
| 89 | |
| 90 | { VK_CAPITAL, "Capital" , nullptr }, |
| 91 | { VK_KANA, "Kana" , nullptr }, |
| 92 | { VK_HANGEUL, "Hangeul" , nullptr }, |
| 93 | { VK_HANGUL, "Hangul" , nullptr }, |
| 94 | { VK_JUNJA, "Junja" , nullptr }, |
| 95 | { VK_FINAL, "Final" , nullptr }, |
| 96 | { VK_HANJA, "Hanja" , nullptr }, |
| 97 | { VK_KANJI, "Kanji" , nullptr }, |
| 98 | { VK_ESCAPE, "Escape" , "Esc" }, |
| 99 | { VK_ESCAPE, "Esc" , nullptr }, |
| 100 | { VK_CONVERT, "Convert" , nullptr }, |
| 101 | { VK_NONCONVERT, "Noconvert" , nullptr }, |
| 102 | { VK_ACCEPT, "Accept" , nullptr }, |
| 103 | { VK_MODECHANGE, "Modechange" , nullptr }, |
| 104 | |
| 105 | { VK_SPACE, "Space" , "Sp" }, |
| 106 | |
| 107 | { VK_PRIOR, "Prior" , nullptr }, |
| 108 | { VK_NEXT, "Next" , nullptr }, |
| 109 | { VK_END, "End" , nullptr }, |
| 110 | { VK_HOME, "Home" , nullptr }, |
| 111 | { VK_LEFT, "Left" , nullptr }, |
| 112 | { VK_UP, "Up" , nullptr }, |
| 113 | { VK_RIGHT, "Right" , nullptr }, |
| 114 | { VK_DOWN, "Down" , nullptr }, |
| 115 | { VK_SELECT, "Select" , nullptr }, |
| 116 | { VK_PRINT, "Print" , nullptr }, |
| 117 | { VK_EXECUTE, "Execute" , nullptr }, |
| 118 | { VK_SNAPSHOT, "Snapshot" , nullptr }, |
| 119 | { VK_INSERT, "Insert" , "Ins" }, |
| 120 | { VK_DELETE, "Delete" , "Del" }, |
| 121 | { VK_HELP, "Help" , nullptr }, |
| 122 | |
| 123 | { '0', "0" , nullptr }, |
| 124 | { '1', "1" , nullptr }, |
| 125 | { '2', "2" , nullptr }, |
| 126 | { '3', "3" , nullptr }, |
| 127 | { '4', "4" , nullptr }, |
| 128 | { '5', "5" , nullptr }, |
| 129 | { '6', "6" , nullptr }, |
| 130 | { '7', "7" , nullptr }, |
| 131 | { '8', "8" , nullptr }, |
| 132 | { '9', "9" , nullptr }, |
| 133 | |
| 134 | { 'A', "A" , nullptr }, |
| 135 | { 'B', "B" , nullptr }, |
| 136 | { 'C', "C" , nullptr }, |
| 137 | { 'D', "D" , nullptr }, |
| 138 | { 'E', "E" , nullptr }, |
| 139 | { 'F', "F" , nullptr }, |
| 140 | { 'G', "G" , nullptr }, |
| 141 | { 'H', "H" , nullptr }, |
| 142 | { 'I', "I" , nullptr }, |
| 143 | { 'J', "J" , nullptr }, |
| 144 | { 'K', "K" , nullptr }, |
| 145 | { 'L', "L" , nullptr }, |
| 146 | { 'M', "M" , nullptr }, |
| 147 | { 'N', "N" , nullptr }, |
| 148 | { 'O', "O" , nullptr }, |
| 149 | { 'P', "P" , nullptr }, |
| 150 | { 'Q', "Q" , nullptr }, |
| 151 | { 'R', "R" , nullptr }, |
| 152 | { 'S', "S" , nullptr }, |
| 153 | { 'T', "T" , nullptr }, |
| 154 | { 'U', "U" , nullptr }, |
| 155 | { 'V', "V" , nullptr }, |
| 156 | { 'W', "W" , nullptr }, |
| 157 | { 'X', "X" , nullptr }, |
| 158 | { 'Y', "Y" , nullptr }, |
| 159 | { 'Z', "Z" , nullptr }, |
| 160 | |
| 161 | { VK_LWIN, "WinLeft" , nullptr }, |
| 162 | { VK_RWIN, "WinRight" , nullptr }, |
| 163 | { VK_APPS, "Apps" , nullptr }, |
| 164 | |
| 165 | { VK_NUMPAD0, "Num0" , "N0" }, |
| 166 | { VK_NUMPAD1, "Num1" , "N1" }, |
| 167 | { VK_NUMPAD2, "Num2" , "N2" }, |
| 168 | { VK_NUMPAD3, "Num3" , "N3" }, |
| 169 | { VK_NUMPAD4, "Num4" , "N4" }, |
| 170 | { VK_NUMPAD5, "Num5" , "N5" }, |
| 171 | { VK_NUMPAD6, "Num6" , "N6" }, |
| 172 | { VK_NUMPAD7, "Num7" , "N7" }, |
| 173 | { VK_NUMPAD8, "Num8" , "N8" }, |
| 174 | { VK_NUMPAD9, "Num9" , "N9" }, |
| 175 | { VK_MULTIPLY, "Multiply" , "N*" }, |
| 176 | { VK_ADD, "Add" , "N+" }, |
| 177 | { VK_SEPARATOR, "Separator" , "NSep" }, |
| 178 | { VK_SUBTRACT, "Subtract" , "N-" }, |
| 179 | { VK_DECIMAL, "Decimal" , "N," }, |
| 180 | { VK_DIVIDE, "Divide" , "N/" }, |
| 181 | { VK_F1, "F1" , nullptr }, |
| 182 | { VK_F2, "F2" , nullptr }, |
| 183 | { VK_F3, "F3" , nullptr }, |
| 184 | { VK_F4, "F4" , nullptr }, |
| 185 | { VK_F5, "F5" , nullptr }, |
| 186 | { VK_F6, "F6" , nullptr }, |
| 187 | { VK_F7, "F7" , nullptr }, |
| 188 | { VK_F8, "F8" , nullptr }, |
| 189 | { VK_F9, "F9" , nullptr }, |
| 190 | { VK_F10, "F10" , nullptr }, |
| 191 | { VK_F11, "F11" , nullptr }, |
| 192 | { VK_F12, "F12" , nullptr }, |
| 193 | { VK_F13, "F13" , nullptr }, |
| 194 | { VK_F14, "F14" , nullptr }, |
| 195 | { VK_F15, "F15" , nullptr }, |
| 196 | { VK_F16, "F16" , nullptr }, |
| 197 | { VK_F17, "F17" , nullptr }, |
| 198 | { VK_F18, "F18" , nullptr }, |
| 199 | { VK_F19, "F19" , nullptr }, |
| 200 | { VK_F20, "F20" , nullptr }, |
| 201 | { VK_F21, "F21" , nullptr }, |
| 202 | { VK_F22, "F22" , nullptr }, |
| 203 | { VK_F23, "F23" , nullptr }, |
| 204 | { VK_F24, "F24" , nullptr }, |
| 205 | { VK_NUMLOCK, "NumLock" , "NLock" }, |
| 206 | { K_SCROLL, "Scroll" , nullptr }, |
| 207 | |
| 208 | { VK_PROCESSKEY, "PROCESSKEY" , nullptr }, |
| 209 | |
| 210 | #if defined(VK_SLEEP) && defined(VK_OEM_NEC_EQUAL) |
| 211 | { VK_SLEEP, "Sleep" , nullptr }, |
| 212 | |
| 213 | { VK_OEM_NEC_EQUAL, "OEM_NEC_EQUAL" , nullptr }, |
| 214 | |
| 215 | { VK_OEM_FJ_JISHO, "OEM_FJ_JISHO" , nullptr }, |
| 216 | { VK_OEM_FJ_MASSHOU, "OEM_FJ_MASSHOU" , nullptr }, |
| 217 | { VK_OEM_FJ_TOUROKU, "OEM_FJ_TOUROKU" , nullptr }, |
| 218 | { VK_OEM_FJ_LOYA, "OEM_FJ_LOYA" , nullptr }, |
| 219 | { VK_OEM_FJ_ROYA, "OEM_FJ_ROYA" , nullptr }, |
| 220 | |
| 221 | { VK_BROWSER_BACK, "BROWSER_BACK" , nullptr }, |
| 222 | { VK_BROWSER_FORWARD, "BROWSER_FORWARD" , nullptr }, |
| 223 | { VK_BROWSER_REFRESH, "BROWSER_REFRESH" , nullptr }, |
| 224 | { VK_BROWSER_STOP, "BROWSER_STOP" , nullptr }, |
| 225 | { VK_BROWSER_SEARCH, "BROWSER_SEARCH" , nullptr }, |
| 226 | { VK_BROWSER_FAVORITES, "BROWSER_FAVORITES" , nullptr }, |
| 227 | { VK_BROWSER_HOME, "BROWSER_HOME" , nullptr }, |
| 228 | |
| 229 | { VK_VOLUME_MUTE, "VOLUME_MUTE" , nullptr }, |
| 230 | { VK_VOLUME_DOWN, "VOLUME_DOWN" , nullptr }, |
| 231 | { VK_VOLUME_UP, "VOLUME_UP" , nullptr }, |
| 232 | { VK_MEDIA_NEXT_TRACK, "MEDIA_NEXT_TRACK" , nullptr }, |
| 233 | { VK_MEDIA_PREV_TRACK, "MEDIA_PREV_TRACK" , nullptr }, |
| 234 | { VK_MEDIA_STOP, "MEDIA_STOP" , nullptr }, |
| 235 | { VK_MEDIA_PLAY_PAUSE, "MEDIA_PLAY_PAUSE" , nullptr }, |
| 236 | { VK_LAUNCH_MAIL, "LAUNCH_MAIL" , nullptr }, |
| 237 | { VK_LAUNCH_MEDIA_SELECT, "LAUNCH_MEDIA_SELECT" , nullptr }, |
| 238 | { VK_LAUNCH_APP1, "LAUNCH_APP1" , nullptr }, |
| 239 | { VK_LAUNCH_APP2, "LAUNCH_APP2" , nullptr }, |
| 240 | |
| 241 | { VK_OEM_1, "OEM \xdc" , "\xdc" }, // German hax: Ü |
| 242 | { VK_OEM_PLUS, "OEM +" , "+" }, |
| 243 | { VK_OEM_COMMA, "OEM ," , "," }, |
| 244 | { VK_OEM_MINUS, "OEM -" , "-" }, |
| 245 | { VK_OEM_PERIOD, "OEM ." , "." }, |
| 246 | { VK_OEM_2, "OEM 2" , "2" }, |
| 247 | { VK_OEM_3, "OEM \xd6" , "\xd6" }, // German hax: Ö |
| 248 | { VK_OEM_4, "OEM 4" , "4" }, |
| 249 | { VK_OEM_5, "OEM 5" , "5" }, |
| 250 | { VK_OEM_6, "OEM 6" , "6" }, |
| 251 | { VK_OEM_7, "OEM \xc4" , "\xc4" }, // German hax: Ä |
| 252 | { VK_OEM_8, "OEM 8" , "8" }, |
| 253 | { VK_OEM_AX, "AX" , "AX" }, |
| 254 | { VK_OEM_102, "< > |" , "<" }, // German hax |
| 255 | { VK_ICO_HELP, "Help" , "Help" }, |
| 256 | { VK_ICO_00, "ICO_00" , "00" }, |
| 257 | |
| 258 | { VK_ICO_CLEAR, "ICO_CLEAR" , nullptr }, |
| 259 | |
| 260 | { VK_PACKET, "PACKET" , nullptr }, |
| 261 | |
| 262 | { VK_OEM_RESET, "OEM_RESET" , nullptr }, |
| 263 | { VK_OEM_JUMP, "OEM_JUMP" , nullptr }, |
| 264 | { VK_OEM_PA1, "OEM_PA1" , nullptr }, |
| 265 | { VK_OEM_PA2, "OEM_PA2" , nullptr }, |
| 266 | { VK_OEM_PA3, "OEM_PA3" , nullptr }, |
| 267 | { VK_OEM_WSCTRL, "OEM_WSCTRL" , nullptr }, |
| 268 | { VK_OEM_CUSEL, "OEM_CUSEL" , nullptr }, |
| 269 | { VK_OEM_ATTN, "OEM_ATTN" , nullptr }, |
| 270 | { VK_OEM_FINISH, "OEM_FINISH" , nullptr }, |
| 271 | { VK_OEM_COPY, "OEM_COPY" , nullptr }, |
| 272 | { VK_OEM_AUTO, "OEM_AUTO" , nullptr }, |
| 273 | { VK_OEM_ENLW, "OEM_ENLW" , nullptr }, |
| 274 | { VK_OEM_BACKTAB, "OEM_BACKTAB" , nullptr }, |
| 275 | #endif |
| 276 | |
| 277 | { VK_ATTN, "ATTN" , nullptr }, |
| 278 | { VK_CRSEL, "CRSEL" , nullptr }, |
| 279 | { VK_EXSEL, "EXSEL" , nullptr }, |
| 280 | { VK_EREOF, "EREOF" , nullptr }, |
| 281 | { VK_PLAY, "PLAY" , nullptr }, |
| 282 | { VK_ZOOM, "ZOOM" , nullptr }, |
| 283 | { VK_NONAME, "NONAME" , nullptr }, |
| 284 | { VK_PA1, "PA1" , nullptr }, |
| 285 | { VK_OEM_CLEAR, "OEM_CLEAR" , nullptr }, |
| 286 | |
| 287 | { KEY_Any, "Any" , nullptr }, |
| 288 | { KEY_Default, "None" , nullptr }, |
| 289 | { KEY_Undefined, nullptr, nullptr } |
| 290 | }; |
| 291 | #endif |
| 292 | |
| 293 | C4KeyCode C4KeyCodeEx::String2KeyCode(const StdStrBuf &sName) |
| 294 | { |
| 295 | // direct key code? |
| 296 | if (sName.getLength() > 2) |
| 297 | { |
| 298 | uint32_t dwRVal; |
| 299 | if (sscanf(s: sName.getData(), format: "\\x%x" , &dwRVal) == 1) return dwRVal; |
| 300 | // direct gamepad code |
| 301 | #ifdef _WIN32 |
| 302 | if (!strnicmp(sName.getData(), "Joy" , 3)) |
| 303 | #else |
| 304 | if (!strncasecmp(s1: sName.getData(), s2: "Joy" , n: 3)) |
| 305 | #endif |
| 306 | { |
| 307 | int iGamepad; char cGamepadButton; int iAxis; |
| 308 | if (sscanf(s: sName.getData(), format: "Joy%dLeft" , &iGamepad) == 1) return KEY_Gamepad(idGamepad: iGamepad - 1, idButton: KEY_JOY_Left); |
| 309 | if (sscanf(s: sName.getData(), format: "Joy%dUp" , &iGamepad) == 1) return KEY_Gamepad(idGamepad: iGamepad - 1, idButton: KEY_JOY_Up); |
| 310 | if (sscanf(s: sName.getData(), format: "Joy%dDown" , &iGamepad) == 1) return KEY_Gamepad(idGamepad: iGamepad - 1, idButton: KEY_JOY_Down); |
| 311 | if (sscanf(s: sName.getData(), format: "Joy%dRight" , &iGamepad) == 1) return KEY_Gamepad(idGamepad: iGamepad - 1, idButton: KEY_JOY_Right); |
| 312 | if (sscanf(s: sName.getData(), format: "Joy%d%c" , &iGamepad, &cGamepadButton) == 2) return KEY_Gamepad(idGamepad: iGamepad - 1, idButton: cGamepadButton - 'A'); |
| 313 | if (sscanf(s: sName.getData(), format: "Joy%dAxis%dMin" , &iGamepad, &iAxis) == 1) return KEY_Gamepad(idGamepad: iGamepad - 1, idButton: KEY_JOY_Axis(idx: iAxis, fMax: false)); |
| 314 | if (sscanf(s: sName.getData(), format: "Joy%dAxis%dMax" , &iGamepad, &iAxis) == 1) return KEY_Gamepad(idGamepad: iGamepad - 1, idButton: KEY_JOY_Axis(idx: iAxis, fMax: true)); |
| 315 | } |
| 316 | } |
| 317 | #ifdef _WIN32 |
| 318 | // query map |
| 319 | const C4KeyCodeMapEntry *pCheck = KeyCodeMap; |
| 320 | while (pCheck->szName) |
| 321 | if (SEqualNoCase(sName.getData(), pCheck->szName)) break; else ++pCheck; |
| 322 | return pCheck->wCode; |
| 323 | #elif defined(USE_X11) |
| 324 | return XStringToKeysym(sName.getData()); |
| 325 | #elif defined(USE_SDL_MAINLOOP) |
| 326 | const auto code = SDL_GetScancodeFromName(sName.getData()); |
| 327 | return code != SDL_SCANCODE_UNKNOWN ? code : KEY_Default; |
| 328 | #else |
| 329 | return KEY_Default; |
| 330 | #endif |
| 331 | } |
| 332 | |
| 333 | std::string C4KeyCodeEx::KeyCode2String(C4KeyCode wCode, bool fHumanReadable, bool fShort) |
| 334 | { |
| 335 | // Gamepad keys |
| 336 | if (Key_IsGamepad(key: wCode)) |
| 337 | { |
| 338 | int iGamepad = Key_GetGamepad(key: wCode); |
| 339 | int iGamepadButton = Key_GetGamepadButton(key: wCode); |
| 340 | switch (iGamepadButton) |
| 341 | { |
| 342 | case KEY_JOY_Left: return std::format(fmt: "Joy{}Left" , args: iGamepad + 1); |
| 343 | case KEY_JOY_Up: return std::format(fmt: "Joy{}Up" , args: iGamepad + 1); |
| 344 | case KEY_JOY_Down: return std::format(fmt: "Joy{}Down" , args: iGamepad + 1); |
| 345 | case KEY_JOY_Right: return std::format(fmt: "Joy{}Right" , args: iGamepad + 1); |
| 346 | default: |
| 347 | if (Key_IsGamepadAxis(key: wCode)) |
| 348 | { |
| 349 | if (fHumanReadable) |
| 350 | // This is still not great, but it is not really possible to assign unknown axes to "left/right" "up/down"... |
| 351 | return std::format(fmt: "[{}] {}" , args: 1 + Key_GetGamepadAxisIndex(key: wCode), args: Key_IsGamepadAxisHigh(key: wCode) ? "Max" : "Min" ); |
| 352 | else |
| 353 | return std::format(fmt: "Joy{}Axis{}{}" , args: iGamepad + 1, args: Key_GetGamepadAxisIndex(key: wCode), args: Key_IsGamepadAxisHigh(key: wCode) ? "Max" : "Min" ); |
| 354 | } |
| 355 | else |
| 356 | { |
| 357 | // button |
| 358 | if (fHumanReadable) |
| 359 | // If there should be gamepads around with A B C D... on the buttons, we might create a display option to show letters instead... |
| 360 | return std::format(fmt: "< {} >" , args: 1 + Key_GetGamepadButtonIndex(key: wCode)); |
| 361 | else |
| 362 | return std::format(fmt: "Joy{}{}" , args: iGamepad + 1, args: static_cast<char>(Key_GetGamepadButtonIndex(key: wCode) + 'A')); |
| 363 | } |
| 364 | } |
| 365 | } |
| 366 | #ifdef _WIN32 |
| 367 | // query map |
| 368 | const C4KeyCodeMapEntry *pCheck = KeyCodeMap; |
| 369 | while (pCheck->szName) |
| 370 | if (wCode == pCheck->wCode) return (pCheck->szShortName && fShort) ? pCheck->szShortName : pCheck->szName; else ++pCheck; |
| 371 | // not found: Compose as direct code |
| 372 | return std::format("\\x{:x}" , static_cast<uint32_t>(wCode)); |
| 373 | #elif defined(USE_X11) |
| 374 | return XKeysymToString(wCode); |
| 375 | #elif defined(USE_SDL_MAINLOOP) |
| 376 | const auto name = SDL_GetScancodeName(static_cast<SDL_Scancode>(wCode)); |
| 377 | if (!name) |
| 378 | { |
| 379 | return "invalid" ; |
| 380 | } |
| 381 | return name; |
| 382 | #else |
| 383 | return "unknown" ; |
| 384 | #endif |
| 385 | } |
| 386 | |
| 387 | std::string C4KeyCodeEx::ToString(bool fHumanReadable, bool fShort) |
| 388 | { |
| 389 | std::string result; |
| 390 | // Add shift |
| 391 | for (uint32_t dwShiftCheck = KEYS_First; dwShiftCheck <= KEYS_Max; dwShiftCheck <<= 1) |
| 392 | if (dwShiftCheck & dwShift) |
| 393 | { |
| 394 | result = std::format(fmt: "{}+" , args: KeyShift2String(eShift: static_cast<C4KeyShiftState>(dwShiftCheck))); |
| 395 | } |
| 396 | |
| 397 | return result.append(str: KeyCode2String(wCode: Key, fHumanReadable, fShort)); |
| 398 | } |
| 399 | |
| 400 | // C4KeyCodeEx |
| 401 | |
| 402 | void C4KeyCodeEx::CompileFunc(StdCompiler *pComp) |
| 403 | { |
| 404 | if (pComp->isCompiler()) |
| 405 | { |
| 406 | // reading from file |
| 407 | StdStrBuf sCode; |
| 408 | uint32_t dwSetShift = 0; |
| 409 | for (;;) |
| 410 | { |
| 411 | pComp->Value(rStruct: mkParAdapt(rObj&: sCode, rPar: StdCompiler::RCT_Idtf)); |
| 412 | if (!pComp->Separator(eSep: StdCompiler::SEP_PLUS)) break; // no more separator: Parse this as keyboard code |
| 413 | // try to convert to shift state |
| 414 | C4KeyShiftState eAddState = String2KeyShift(sName: sCode); |
| 415 | if (eAddState == KEYS_Undefined) |
| 416 | pComp->excCorrupt(message: "undefined key shift state: {}" , args: sCode.getData()); |
| 417 | dwSetShift |= eAddState; |
| 418 | } |
| 419 | // any code given? Otherwise, keep default |
| 420 | if (sCode.getLength()) |
| 421 | { |
| 422 | // last section: convert to key code |
| 423 | C4KeyCode eCode = String2KeyCode(sName: sCode); |
| 424 | if (eCode == KEY_Undefined) |
| 425 | pComp->excCorrupt(message: "undefined key code: {}" , args: sCode.getData()); |
| 426 | dwShift = dwSetShift; |
| 427 | Key = eCode; |
| 428 | } |
| 429 | } |
| 430 | else |
| 431 | { |
| 432 | // write shift states |
| 433 | for (uint32_t dwShiftCheck = KEYS_First; dwShiftCheck <= KEYS_Max; dwShiftCheck <<= 1) |
| 434 | if (dwShiftCheck & dwShift) |
| 435 | { |
| 436 | pComp->Value(rStruct: mkDecompileAdapt(rValue: KeyShift2String(eShift: static_cast<C4KeyShiftState>(dwShiftCheck)))); |
| 437 | pComp->Separator(eSep: StdCompiler::SEP_PLUS); |
| 438 | } |
| 439 | // write key |
| 440 | pComp->Value(rStruct: mkDecompileAdapt(rValue: KeyCode2String(wCode: Key, fHumanReadable: false, fShort: false))); |
| 441 | } |
| 442 | } |
| 443 | |
| 444 | // C4CustomKey |
| 445 | |
| 446 | C4CustomKey::C4CustomKey(C4KeyCodeEx DefCode, const char *szName, C4KeyScope Scope, C4KeyboardCallbackInterface *pCallback, unsigned int uiPriority) |
| 447 | : Scope(Scope), Name(), uiPriority(uiPriority), iRef(0) |
| 448 | { |
| 449 | // generate code |
| 450 | if (DefCode.Key != KEY_Default) DefaultCodes.push_back(x: DefCode); |
| 451 | // ctor for default key |
| 452 | Name.Copy(pnData: szName); |
| 453 | if (pCallback) |
| 454 | { |
| 455 | pCallback->Ref(); |
| 456 | vecCallbacks.push_back(x: pCallback); |
| 457 | pCallback->pOriginalKey = this; |
| 458 | } |
| 459 | } |
| 460 | |
| 461 | C4CustomKey::C4CustomKey(const CodeList &rDefCodes, const char *szName, C4KeyScope Scope, C4KeyboardCallbackInterface *pCallback, unsigned int uiPriority) |
| 462 | : DefaultCodes(rDefCodes), Scope(Scope), Name(), uiPriority(uiPriority), iRef(0) |
| 463 | { |
| 464 | // ctor for default key |
| 465 | Name.Copy(pnData: szName); |
| 466 | if (pCallback) |
| 467 | { |
| 468 | pCallback->Ref(); |
| 469 | vecCallbacks.push_back(x: pCallback); |
| 470 | pCallback->pOriginalKey = this; |
| 471 | } |
| 472 | } |
| 473 | |
| 474 | C4CustomKey::C4CustomKey(const C4CustomKey &rCpy, bool fCopyCallbacks) |
| 475 | : Codes(rCpy.Codes), DefaultCodes(rCpy.DefaultCodes), Scope(rCpy.Scope), Name(), uiPriority(rCpy.uiPriority), iRef(0) |
| 476 | { |
| 477 | Name.Copy(Buf2: rCpy.GetName()); |
| 478 | if (fCopyCallbacks) |
| 479 | { |
| 480 | for (CBVec::const_iterator i = rCpy.vecCallbacks.begin(); i != rCpy.vecCallbacks.end(); ++i) |
| 481 | { |
| 482 | (*i)->Ref(); |
| 483 | vecCallbacks.push_back(x: *i); |
| 484 | } |
| 485 | } |
| 486 | } |
| 487 | |
| 488 | C4CustomKey::~C4CustomKey() |
| 489 | { |
| 490 | // free callback handles |
| 491 | for (CBVec::const_iterator i = vecCallbacks.begin(); i != vecCallbacks.end(); ++i) |
| 492 | (*i)->Deref(); |
| 493 | } |
| 494 | |
| 495 | void C4CustomKey::Update(const C4CustomKey *pByKey) |
| 496 | { |
| 497 | assert(pByKey); |
| 498 | assert(Name == pByKey->Name); |
| 499 | // transfer any assigned data, except name which should be equal anyway |
| 500 | if (pByKey->DefaultCodes.size()) DefaultCodes = pByKey->DefaultCodes; |
| 501 | if (pByKey->Codes.size()) Codes = pByKey->Codes; |
| 502 | if (pByKey->Scope != KEYSCOPE_None) Scope = pByKey->Scope; |
| 503 | if (pByKey->uiPriority != PRIO_None) uiPriority = pByKey->uiPriority; |
| 504 | for (CBVec::const_iterator i = pByKey->vecCallbacks.begin(); i != pByKey->vecCallbacks.end(); ++i) |
| 505 | { |
| 506 | (*i)->Ref(); |
| 507 | vecCallbacks.push_back(x: *i); |
| 508 | } |
| 509 | } |
| 510 | |
| 511 | void C4CustomKey::KillCallbacks(const C4CustomKey *pOfKey) |
| 512 | { |
| 513 | // remove all instances from list |
| 514 | for (;;) |
| 515 | { |
| 516 | const auto it = std::find_if(first: vecCallbacks.cbegin(), last: vecCallbacks.cend(), |
| 517 | pred: [&](const auto &pIntfc) { return pIntfc->IsOriginalKey(pOfKey); }); |
| 518 | if (it == vecCallbacks.cend()) break; |
| 519 | const auto pItfc = *it; |
| 520 | vecCallbacks.erase(position: it); |
| 521 | pItfc->Deref(); |
| 522 | } |
| 523 | } |
| 524 | |
| 525 | void C4CustomKey::CompileFunc(StdCompiler *pComp) |
| 526 | { |
| 527 | pComp->Value(rStruct: mkNamingAdapt(rValue: mkSTLContainerAdapt(rTarget&: Codes), szName: Name.getData(), rDefault: DefaultCodes)); |
| 528 | } |
| 529 | |
| 530 | bool C4CustomKey::Execute(C4KeyEventType eEv, C4KeyCodeEx key) |
| 531 | { |
| 532 | // execute all callbacks |
| 533 | for (CBVec::iterator i = vecCallbacks.begin(); i != vecCallbacks.end(); ++i) |
| 534 | if ((*i)->OnKeyEvent(key, eEv)) |
| 535 | return true; |
| 536 | // no event processed it |
| 537 | return false; |
| 538 | } |
| 539 | |
| 540 | // C4KeyBinding |
| 541 | |
| 542 | C4KeyBinding::C4KeyBinding(C4KeyCodeEx DefCode, const char *szName, C4KeyScope Scope, C4KeyboardCallbackInterface *pCallback, unsigned int uiPriority) |
| 543 | : C4CustomKey(DefCode, szName, Scope, pCallback, uiPriority) |
| 544 | { |
| 545 | // self holds a ref |
| 546 | Ref(); |
| 547 | // register into keyboard input class |
| 548 | C4KeyboardInput_Init().RegisterKey(pRegKey: this); |
| 549 | } |
| 550 | |
| 551 | C4KeyBinding::C4KeyBinding(const CodeList &rDefCodes, const char *szName, C4KeyScope Scope, C4KeyboardCallbackInterface *pCallback, unsigned int uiPriority) |
| 552 | : C4CustomKey(rDefCodes, szName, Scope, pCallback, uiPriority) |
| 553 | { |
| 554 | // self holds a ref |
| 555 | Ref(); |
| 556 | // register into keyboard input class |
| 557 | C4KeyboardInput_Init().RegisterKey(pRegKey: this); |
| 558 | } |
| 559 | |
| 560 | C4KeyBinding::~C4KeyBinding() |
| 561 | { |
| 562 | // deregister from keyboard input class, if that class still exists |
| 563 | if (C4KeyboardInput::IsValid) |
| 564 | Game.KeyboardInput.UnregisterKeyBinding(pKey: this); |
| 565 | // shouldn't be refed now |
| 566 | assert(iRef == 1); |
| 567 | iRef = 0; |
| 568 | } |
| 569 | |
| 570 | // C4KeyboardInput |
| 571 | |
| 572 | bool C4KeyboardInput::IsValid = false; |
| 573 | |
| 574 | void C4KeyboardInput::Clear() |
| 575 | { |
| 576 | // release all keys - name map is guarantueed to contain them all |
| 577 | for (KeyNameMap::const_iterator i = KeysByName.begin(); i != KeysByName.end(); ++i) |
| 578 | i->second->Deref(); |
| 579 | // clear maps |
| 580 | KeysByCode.clear(); |
| 581 | KeysByName.clear(); |
| 582 | } |
| 583 | |
| 584 | void C4KeyboardInput::UpdateKeyCodes(C4CustomKey *pKey, const C4CustomKey::CodeList &rOldCodes, const C4CustomKey::CodeList &rNewCodes) |
| 585 | { |
| 586 | // new key codes must be the new current key codes |
| 587 | assert(pKey->GetCodes() == rNewCodes); |
| 588 | // kill from old list |
| 589 | C4CustomKey::CodeList::const_iterator iCode; |
| 590 | for (iCode = rOldCodes.begin(); iCode != rOldCodes.end(); ++iCode) |
| 591 | { |
| 592 | // no need to kill if code stayed |
| 593 | if (std::find(first: rNewCodes.begin(), last: rNewCodes.end(), val: *iCode) != rNewCodes.end()) continue; |
| 594 | std::pair<KeyCodeMap::iterator, KeyCodeMap::iterator> KeyRange = KeysByCode.equal_range(x: *iCode); |
| 595 | for (KeyCodeMap::iterator i = KeyRange.first; i != KeyRange.second; ++i) |
| 596 | if (i->second == pKey) |
| 597 | { |
| 598 | KeysByCode.erase(position: i); |
| 599 | break; |
| 600 | } |
| 601 | } |
| 602 | // readd new codes |
| 603 | for (iCode = rNewCodes.begin(); iCode != rNewCodes.end(); ++iCode) |
| 604 | { |
| 605 | // no double-add if it was in old list already |
| 606 | if (std::find(first: rOldCodes.begin(), last: rOldCodes.end(), val: *iCode) != rOldCodes.end()) continue; |
| 607 | KeysByCode.insert(x: std::make_pair(x: *iCode, y&: pKey)); |
| 608 | } |
| 609 | } |
| 610 | |
| 611 | void C4KeyboardInput::RegisterKey(C4CustomKey *pRegKey) |
| 612 | { |
| 613 | assert(pRegKey); if (!pRegKey) return; |
| 614 | // key will be added: ref it |
| 615 | pRegKey->Ref(); |
| 616 | // search key of same name first |
| 617 | C4CustomKey *pDupKey = KeysByName[pRegKey->GetName().getData()]; |
| 618 | if (pDupKey) |
| 619 | { |
| 620 | // key of this name exists: Merge them (old codes copied cuz they'll be overwritten) |
| 621 | C4CustomKey::CodeList OldCodes = pDupKey->GetCodes(); |
| 622 | const C4CustomKey::CodeList &rNewCodes = pRegKey->GetCodes(); |
| 623 | pDupKey->Update(pByKey: pRegKey); |
| 624 | // update access map if key changed |
| 625 | if (!(OldCodes == rNewCodes)) UpdateKeyCodes(pKey: pDupKey, rOldCodes: OldCodes, rNewCodes); |
| 626 | // key to be registered no longer used |
| 627 | pRegKey->Deref(); |
| 628 | } |
| 629 | else |
| 630 | { |
| 631 | // new unique key: Insert into both maps |
| 632 | KeysByName[pRegKey->GetName().getData()] = pRegKey; |
| 633 | for (C4CustomKey::CodeList::const_iterator i = pRegKey->GetCodes().begin(); i != pRegKey->GetCodes().end(); ++i) |
| 634 | KeysByCode.insert(x: std::make_pair(x: *i, y&: pRegKey)); |
| 635 | } |
| 636 | } |
| 637 | |
| 638 | void C4KeyboardInput::UnregisterKey(const StdStrBuf &rsName) |
| 639 | { |
| 640 | // kill from name map |
| 641 | KeyNameMap::iterator in = KeysByName.find(x: rsName.getData()); |
| 642 | if (in == KeysByName.end()) return; |
| 643 | C4CustomKey *pKey = in->second; |
| 644 | KeysByName.erase(position: in); |
| 645 | // kill all key bindings from key map |
| 646 | for (C4CustomKey::CodeList::const_iterator iCode = pKey->GetCodes().begin(); iCode != pKey->GetCodes().end(); ++iCode) |
| 647 | { |
| 648 | std::pair<KeyCodeMap::iterator, KeyCodeMap::iterator> KeyRange = KeysByCode.equal_range(x: *iCode); |
| 649 | for (KeyCodeMap::iterator i = KeyRange.first; i != KeyRange.second; ++i) |
| 650 | if (i->second == pKey) |
| 651 | { |
| 652 | KeysByCode.erase(position: i); |
| 653 | break; |
| 654 | } |
| 655 | } |
| 656 | // release reference to key |
| 657 | pKey->Deref(); |
| 658 | } |
| 659 | |
| 660 | void C4KeyboardInput::UnregisterKeyBinding(C4CustomKey *pUnregKey) |
| 661 | { |
| 662 | // find key in name map |
| 663 | KeyNameMap::iterator in = KeysByName.find(x: pUnregKey->GetName().getData()); |
| 664 | if (in == KeysByName.end()) return; |
| 665 | C4CustomKey *pKey = in->second; |
| 666 | // is this key in the map? |
| 667 | if (pKey != pUnregKey) |
| 668 | { |
| 669 | // Other key is in the list: Just remove the callbacks |
| 670 | pKey->KillCallbacks(pOfKey: pUnregKey); |
| 671 | return; |
| 672 | } |
| 673 | // this key is in the list: Replace by a duplicate... |
| 674 | C4CustomKey *pNewKey = new C4CustomKey(*pUnregKey, true); |
| 675 | // ...without the own callbacks |
| 676 | pNewKey->KillCallbacks(pOfKey: pUnregKey); |
| 677 | // and replace current key by duplicate |
| 678 | UnregisterKey(rsName: pUnregKey->GetName()); |
| 679 | RegisterKey(pRegKey: pNewKey); |
| 680 | } |
| 681 | |
| 682 | bool C4KeyboardInput::DoInput(const C4KeyCodeEx &InKey, C4KeyEventType InEvent, uint32_t InScope) |
| 683 | { |
| 684 | // check all key events generated by this key: First the keycode itself, then any more generic key events like KEY_Any |
| 685 | const int32_t iKeyRangeMax = 5; |
| 686 | int32_t iKeyRangeCnt = 0, j; |
| 687 | std::pair<KeyCodeMap::iterator, KeyCodeMap::iterator> KeyRanges[iKeyRangeMax]; |
| 688 | KeyRanges[iKeyRangeCnt++] = KeysByCode.equal_range(x: InKey); |
| 689 | if (Key_IsGamepadButton(key: InKey.Key)) |
| 690 | { |
| 691 | uint8_t byGamepad = Key_GetGamepad(key: InKey.Key); |
| 692 | uint8_t byBtnIndex = Key_GetGamepadButtonIndex(key: InKey.Key); |
| 693 | // even/odd button events: Add even button indices as odd events, because byBtnIndex is zero-based and the event naming scheme is for one-based button indices |
| 694 | if (byBtnIndex % 2) KeyRanges[iKeyRangeCnt++] = KeysByCode.equal_range(x: C4KeyCodeEx(KEY_Gamepad(idGamepad: byGamepad, idButton: KEY_JOY_AnyEvenButton))); |
| 695 | else KeyRanges[iKeyRangeCnt++] = KeysByCode.equal_range(x: C4KeyCodeEx(KEY_Gamepad(idGamepad: byGamepad, idButton: KEY_JOY_AnyOddButton))); |
| 696 | // high/low button events |
| 697 | if (byBtnIndex < 4) KeyRanges[iKeyRangeCnt++] = KeysByCode.equal_range(x: C4KeyCodeEx(KEY_Gamepad(idGamepad: byGamepad, idButton: KEY_JOY_AnyLowButton))); |
| 698 | else KeyRanges[iKeyRangeCnt++] = KeysByCode.equal_range(x: C4KeyCodeEx(KEY_Gamepad(idGamepad: byGamepad, idButton: KEY_JOY_AnyHighButton))); |
| 699 | // "any gamepad button"-event |
| 700 | KeyRanges[iKeyRangeCnt++] = KeysByCode.equal_range(x: C4KeyCodeEx(KEY_Gamepad(idGamepad: byGamepad, idButton: KEY_JOY_AnyButton))); |
| 701 | } |
| 702 | else if (Key_IsGamepadAxis(key: InKey.Key)) |
| 703 | { |
| 704 | // xy-axis-events for all even/odd axises |
| 705 | uint8_t byGamepad = Key_GetGamepad (key: InKey.Key); |
| 706 | uint8_t byAxis = Key_GetGamepadAxisIndex(key: InKey.Key); |
| 707 | bool fHigh = Key_IsGamepadAxisHigh (key: InKey.Key); |
| 708 | C4KeyCode keyAxisDir; |
| 709 | if (byAxis % 2) |
| 710 | if (fHigh) keyAxisDir = KEY_JOY_Down; else keyAxisDir = KEY_JOY_Up; |
| 711 | else if (fHigh) keyAxisDir = KEY_JOY_Right; else keyAxisDir = KEY_JOY_Left; |
| 712 | KeyRanges[iKeyRangeCnt++] = KeysByCode.equal_range(x: C4KeyCodeEx(KEY_Gamepad(idGamepad: byGamepad, idButton: static_cast<uint8_t>(keyAxisDir)))); |
| 713 | } |
| 714 | if (InKey.Key != KEY_Any) KeyRanges[iKeyRangeCnt++] = KeysByCode.equal_range(x: C4KeyCodeEx(KEY_Any, C4KeyShiftState(InKey.dwShift))); |
| 715 | assert(iKeyRangeCnt <= iKeyRangeMax); |
| 716 | // check all assigned keys |
| 717 | // exec from highest to lowest priority |
| 718 | unsigned int uiLastPrio = C4CustomKey::PRIO_MoreThanMax; |
| 719 | for (;;) |
| 720 | { |
| 721 | KeyCodeMap::const_iterator i; |
| 722 | // get priority to exec |
| 723 | unsigned int uiExecPrio = C4CustomKey::PRIO_None, uiCurr; |
| 724 | for (j = 0; j < iKeyRangeCnt; ++j) |
| 725 | for (i = KeyRanges[j].first; i != KeyRanges[j].second; ++i) |
| 726 | { |
| 727 | uiCurr = i->second->GetPriority(); |
| 728 | if (uiCurr > uiExecPrio && uiCurr < uiLastPrio) uiExecPrio = uiCurr; |
| 729 | } |
| 730 | // nothing with correct priority set left? |
| 731 | if (uiExecPrio == C4CustomKey::PRIO_None) break; |
| 732 | // exec all of this priority |
| 733 | for (j = 0; j < iKeyRangeCnt; ++j) |
| 734 | for (i = KeyRanges[j].first; i != KeyRanges[j].second; ++i) |
| 735 | { |
| 736 | C4CustomKey *pKey = i->second; |
| 737 | assert(pKey); |
| 738 | // check priority |
| 739 | if (pKey->GetPriority() == uiExecPrio) |
| 740 | // check scope |
| 741 | if (pKey->GetScope() & InScope) |
| 742 | // exec it |
| 743 | if (pKey->Execute(eEv: InEvent, key: InKey)) |
| 744 | return true; |
| 745 | } |
| 746 | // nothing found in this priority: exec next |
| 747 | uiLastPrio = uiExecPrio; |
| 748 | } |
| 749 | // no key matched or all returned false in Execute: Not processed |
| 750 | return false; |
| 751 | } |
| 752 | |
| 753 | void C4KeyboardInput::CompileFunc(StdCompiler *pComp) |
| 754 | { |
| 755 | // compile all keys that are already defined |
| 756 | // no definition of new keys with current compiler... |
| 757 | auto name = pComp->Name(szName: "Keys" ); |
| 758 | try |
| 759 | { |
| 760 | for (KeyNameMap::const_iterator i = KeysByName.begin(); i != KeysByName.end(); ++i) |
| 761 | { |
| 762 | // naming done in C4CustomKey, because default is determined by key only |
| 763 | C4CustomKey::CodeList OldCodes = i->second->GetCodes(); |
| 764 | pComp->Value(rStruct&: *i->second); |
| 765 | // resort in secondary map if key changed |
| 766 | if (pComp->isCompiler()) |
| 767 | { |
| 768 | const C4CustomKey::CodeList &rNewCodes = i->second->GetCodes(); |
| 769 | if (!(OldCodes == rNewCodes)) UpdateKeyCodes(pKey: i->second, rOldCodes: OldCodes, rNewCodes); |
| 770 | } |
| 771 | } |
| 772 | } |
| 773 | catch (const StdCompiler::Exception &) |
| 774 | { |
| 775 | name.Abort(); |
| 776 | throw; |
| 777 | } |
| 778 | } |
| 779 | |
| 780 | bool C4KeyboardInput::LoadCustomConfig() |
| 781 | { |
| 782 | // load from INI file (2do: load from registry) |
| 783 | C4Group ; |
| 784 | if (!GrpExtra.Open(C4CFN_Extra)) return false; |
| 785 | StdStrBuf sFileContentsString; |
| 786 | if (!GrpExtra.LoadEntryString(C4CFN_KeyConfig, Buf&: sFileContentsString)) return false; |
| 787 | if (!CompileFromBuf_LogWarn<StdCompilerINIRead>(TargetStruct&: *this, SrcBuf: sFileContentsString, szName: "Custom keys from" C4CFN_Extra DirSep C4CFN_KeyConfig)) |
| 788 | return false; |
| 789 | Log(id: C4ResStrTableKey::IDS_PRC_LOADEDKEYCONF, C4CFN_Extra DirSep C4CFN_KeyConfig); |
| 790 | return true; |
| 791 | } |
| 792 | |
| 793 | C4CustomKey *C4KeyboardInput::GetKeyByName(const char *szKeyName) |
| 794 | { |
| 795 | KeyNameMap::const_iterator i = KeysByName.find(x: szKeyName); |
| 796 | if (i == KeysByName.end()) return nullptr; else return (*i).second; |
| 797 | } |
| 798 | |
| 799 | std::string C4KeyboardInput::GetKeyCodeNameByKeyName(const char *szKeyName, bool fShort, int32_t iIndex) |
| 800 | { |
| 801 | C4CustomKey *pKey = GetKeyByName(szKeyName); |
| 802 | if (pKey) |
| 803 | { |
| 804 | const C4CustomKey::CodeList &codes = pKey->GetCodes(); |
| 805 | if (static_cast<size_t>(iIndex) < codes.size()) |
| 806 | { |
| 807 | C4KeyCodeEx code = codes[iIndex]; |
| 808 | return code.ToString(fHumanReadable: true, fShort); |
| 809 | } |
| 810 | } |
| 811 | // Error |
| 812 | return "" ; |
| 813 | } |
| 814 | |
| 815 | C4KeyboardInput &C4KeyboardInput_Init() |
| 816 | { |
| 817 | static C4KeyboardInput keyinp; |
| 818 | return keyinp; |
| 819 | } |
| 820 | |