1 /*
2 * OpenClonk, http://www.openclonk.org
3 *
4 * Copyright (c) 2005-2009, RedWolf Design GmbH, http://www.clonk.de/
5 * Copyright (c) 2009-2016, The OpenClonk Team and contributors
6 *
7 * Distributed under the terms of the ISC license; see accompanying file
8 * "COPYING" for details.
9 *
10 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11 * See accompanying file "TRADEMARK" for details.
12 *
13 * To redistribute this file separately, substitute the full license texts
14 * for the above references.
15 */
16 // Keyboard input mapping to engine functions
17
18 #include "C4Include.h"
19 #include "gui/C4KeyboardInput.h"
20
21 #include "gui/C4MouseControl.h"
22 #include "c4group/C4Components.h"
23 #include "platform/C4Window.h"
24
25 #include <unordered_map>
26
27 #ifdef HAVE_SDL
28 #include <SDL.h>
29 #endif
30
31 #ifdef USE_SDL_MAINLOOP
32 // Required for KeycodeToString translation table.
33 #include "platform/C4App.h"
34 #endif
35
36 /* ----------------- Key maps ------------------ */
37
38 struct C4KeyShiftMapEntry
39 {
40 C4KeyShiftState eShift;
41 const char *szName;
42 };
43
44 const C4KeyShiftMapEntry KeyShiftMap [] =
45 {
46 { KEYS_Alt, "Alt" },
47 { KEYS_Control, "Ctrl" },
48 { KEYS_Shift, "Shift" },
49 { KEYS_Undefined, nullptr }
50 };
51
String2KeyShift(const StdStrBuf & sName)52 C4KeyShiftState C4KeyCodeEx::String2KeyShift(const StdStrBuf &sName)
53 {
54 // query map
55 const C4KeyShiftMapEntry *pCheck = KeyShiftMap;
56 while (pCheck->szName)
57 if (SEqualNoCase(sName.getData(), pCheck->szName)) break; else ++pCheck;
58 return pCheck->eShift;
59 }
60
KeyShift2String(C4KeyShiftState eShift)61 StdStrBuf C4KeyCodeEx::KeyShift2String(C4KeyShiftState eShift)
62 {
63 // query map
64 const C4KeyShiftMapEntry *pCheck = KeyShiftMap;
65 while (pCheck->szName)
66 if (eShift == pCheck->eShift) break; else ++pCheck;
67 return StdStrBuf(pCheck->szName);
68 }
69
70 struct C4KeyCodeMapEntry
71 {
72 C4KeyCode wCode;
73 const char *szName;
74 const char *szShortName;
75 };
76
77 #if defined(USE_COCOA)
78 #include "platform/CocoaKeycodeMap.h"
79 #else
80 const C4KeyCodeMapEntry KeyCodeMap[] = {
81 {K_ESCAPE, "Escape", "Esc"},
82 {K_1, "1", nullptr},
83 {K_2, "2", nullptr},
84 {K_3, "3", nullptr},
85 {K_4, "4", nullptr},
86 {K_5, "5", nullptr},
87 {K_6, "6", nullptr},
88 {K_7, "7", nullptr},
89 {K_8, "8", nullptr},
90 {K_9, "9", nullptr},
91 {K_0, "0", nullptr},
92 {K_MINUS, "Minus", "-"},
93 {K_EQUAL, "Equal", "="},
94 {K_BACK, "BackSpace", nullptr},
95 {K_TAB, "Tab", nullptr},
96 {K_Q, "Q", nullptr},
97 {K_W, "W", nullptr},
98 {K_E, "E", nullptr},
99 {K_R, "R", nullptr},
100 {K_T, "T", nullptr},
101 {K_Y, "Y", nullptr},
102 {K_U, "U", nullptr},
103 {K_I, "I", nullptr},
104 {K_O, "O", nullptr},
105 {K_P, "P", nullptr},
106 {K_LEFT_BRACKET, "LeftBracket", "["},
107 {K_RIGHT_BRACKET, "RightBracket", "]"},
108 {K_RETURN, "Return", "Ret"},
109 {K_CONTROL_L, "LeftControl", "LCtrl"},
110 {K_A, "A", nullptr},
111 {K_S, "S", nullptr},
112 {K_D, "D", nullptr},
113 {K_F, "F", nullptr},
114 {K_G, "G", nullptr},
115 {K_H, "H", nullptr},
116 {K_J, "J", nullptr},
117 {K_K, "K", nullptr},
118 {K_L, "L", nullptr},
119 {K_SEMICOLON, "Semicolon", ";"},
120 {K_APOSTROPHE, "Apostrophe", "'"},
121 {K_GRAVE_ACCENT, "GraveAccent", "`"},
122 {K_SHIFT_L, "LeftShift", "LShift"},
123 {K_BACKSLASH, "Backslash", R"(\)"},
124 {K_Z, "Z", nullptr},
125 {K_X, "X", nullptr},
126 {K_C, "C", nullptr},
127 {K_V, "V", nullptr},
128 {K_B, "B", nullptr},
129 {K_N, "N", nullptr},
130 {K_M, "M", nullptr},
131 {K_COMMA, "Comma", ","},
132 {K_PERIOD, "Period", "."},
133 {K_SLASH, "Slash", "/"},
134 {K_SHIFT_R, "RightShift", "RShift"},
135 {K_MULTIPLY, "Multiply", "N*"},
136 {K_ALT_L, "LeftAlt", "LAlt"},
137 {K_SPACE, "Space", "Sp"},
138 {K_CAPS, "Capslock", nullptr},
139 {K_F1, "F1", nullptr},
140 {K_F2, "F2", nullptr},
141 {K_F3, "F3", nullptr},
142 {K_F4, "F4", nullptr},
143 {K_F5, "F5", nullptr},
144 {K_F6, "F6", nullptr},
145 {K_F7, "F7", nullptr},
146 {K_F8, "F8", nullptr},
147 {K_F9, "F9", nullptr},
148 {K_F10, "F10", nullptr},
149 {K_NUM, "NumLock", "NLock"},
150 {K_SCROLL, "ScrollLock", "SLock"},
151 {K_NUM7, "Num7", "N7"},
152 {K_NUM8, "Num8", "N8"},
153 {K_NUM9, "Num9", "N9"},
154 {K_SUBTRACT, "Subtract", "N-"},
155 {K_NUM4, "Num4", "N4"},
156 {K_NUM5, "Num5", "N5"},
157 {K_NUM6, "Num6", "N6"},
158 {K_ADD, "Add", "N+"},
159 {K_NUM1, "Num1", "N1"},
160 {K_NUM2, "Num2", "N2"},
161 {K_NUM3, "Num3", "N3"},
162 {K_NUM0, "Num0", "N0"},
163 {K_DECIMAL, "Decimal", "N,"},
164 {K_86, "|<>", nullptr},
165 {K_F11, "F11", nullptr},
166 {K_F12, "F12", nullptr},
167 {K_NUM_RETURN, "NumReturn", "NRet"},
168 {K_CONTROL_R, "RightControl", "RCtrl"},
169 {K_DIVIDE, "Divide", "N/"},
170 {K_ALT_R, "RightAlt", "RAlt"},
171 {K_HOME, "Home", nullptr},
172 {K_UP, "Up", nullptr},
173 {K_PAGEUP, "PageUp", nullptr},
174 {K_LEFT, "Left", nullptr},
175 {K_RIGHT, "Right", nullptr},
176 {K_END, "End", nullptr},
177 {K_DOWN, "Down", nullptr},
178 {K_PAGEDOWN, "PageDown", nullptr},
179 {K_INSERT, "Insert", "Ins"},
180 {K_DELETE, "Delete", "Del"},
181 {K_PAUSE, "Pause", nullptr},
182 {K_WIN_L, "LeftWin", "LWin"},
183 {K_WIN_R, "RightWin", "RWin"},
184 {K_MENU, "Menu", nullptr},
185 {K_PRINT, "Print", nullptr},
186 {0x00, nullptr, nullptr}
187 };
188 #endif
189
C4KeyCodeEx(C4KeyCode key,DWORD Shift,bool fIsRepeated,int32_t deviceId)190 C4KeyCodeEx::C4KeyCodeEx(C4KeyCode key, DWORD Shift, bool fIsRepeated, int32_t deviceId)
191 : Key(key), dwShift(Shift), fRepeated(fIsRepeated), deviceId(deviceId)
192 {
193 }
194
FromC4MC(int8_t mouse_id,int32_t iButton,DWORD dwKeyParam,bool * is_down)195 C4KeyCodeEx C4KeyCodeEx::FromC4MC(int8_t mouse_id, int32_t iButton, DWORD dwKeyParam, bool *is_down)
196 {
197 bool dummy;
198 if (!is_down)
199 is_down = &dummy;
200 *is_down = true;
201 C4KeyCode mouseevent_code;
202 int wheel_dir = 0;
203 if (iButton == C4MC_Button_Wheel) wheel_dir = (short)(dwKeyParam >> 16);
204 switch (iButton)
205 {
206 case C4MC_Button_None: mouseevent_code = KEY_MOUSE_Move; break;
207 case C4MC_Button_LeftDown: mouseevent_code = KEY_MOUSE_ButtonLeft; break;
208 case C4MC_Button_LeftUp: mouseevent_code = KEY_MOUSE_ButtonLeft; *is_down = false; break;
209 case C4MC_Button_LeftDouble: mouseevent_code = KEY_MOUSE_ButtonLeftDouble; break;
210 case C4MC_Button_RightDown: mouseevent_code = KEY_MOUSE_ButtonRight; break;
211 case C4MC_Button_RightDouble: mouseevent_code = KEY_MOUSE_ButtonRightDouble; break;
212 case C4MC_Button_RightUp: mouseevent_code = KEY_MOUSE_ButtonRight; *is_down = false; break;
213 case C4MC_Button_MiddleDown: mouseevent_code = KEY_MOUSE_ButtonMiddle; break;
214 case C4MC_Button_MiddleUp: mouseevent_code = KEY_MOUSE_ButtonMiddle; *is_down = false; break;
215 case C4MC_Button_MiddleDouble: mouseevent_code = KEY_MOUSE_ButtonMiddleDouble; break;
216 case C4MC_Button_X1Down: mouseevent_code = KEY_MOUSE_ButtonX1; break;
217 case C4MC_Button_X1Up: mouseevent_code = KEY_MOUSE_ButtonX1; *is_down = false; break;
218 case C4MC_Button_X1Double: mouseevent_code = KEY_MOUSE_ButtonX1Double; break;
219 case C4MC_Button_X2Down: mouseevent_code = KEY_MOUSE_ButtonX2; break;
220 case C4MC_Button_X2Up: mouseevent_code = KEY_MOUSE_ButtonX2; *is_down = false; break;
221 case C4MC_Button_X2Double: mouseevent_code = KEY_MOUSE_ButtonX2Double; break;
222 case C4MC_Button_Wheel:
223 if (!wheel_dir) assert("Attempted to record mouse wheel movement without a direction");
224 mouseevent_code = (wheel_dir > 0) ? KEY_MOUSE_Wheel1Up : KEY_MOUSE_Wheel1Down; break;
225 }
226 C4KeyCodeEx key{KEY_Mouse(mouse_id, mouseevent_code), KEYS_None};
227 if (dwKeyParam & MK_CONTROL) key.dwShift |= KEYS_Control;
228 if (dwKeyParam & MK_SHIFT) key.dwShift |= KEYS_Shift;
229 if (dwKeyParam & MK_ALT) key.dwShift |= KEYS_Alt;
230 return key;
231 }
232
FixShiftKeys()233 void C4KeyCodeEx::FixShiftKeys()
234 {
235 // reduce stuff like Ctrl+RightCtrl to simply RightCtrl
236 if ((dwShift & KEYS_Alt) && (Key == K_ALT_L || Key == K_ALT_R)) dwShift &= ~KEYS_Alt;
237 if ((dwShift & KEYS_Control) && (Key == K_CONTROL_L || Key == K_CONTROL_R)) dwShift &= ~KEYS_Control;
238 if ((dwShift & KEYS_Shift) && (Key == K_SHIFT_L || Key == K_SHIFT_R)) dwShift &= ~KEYS_Shift;
239 }
240
GetKeyByScanCode(const char * scan_code)241 C4KeyCode C4KeyCodeEx::GetKeyByScanCode(const char *scan_code)
242 {
243 // scan code is in hex format
244 unsigned int scan_code_int;
245 if (sscanf(scan_code, "$%x", &scan_code_int) != 1) return KEY_Undefined;
246 return scan_code_int;
247 }
248
249 static const std::unordered_map<std::string, C4KeyCode> controllercodes =
250 {
251 { "ButtonA", KEY_CONTROLLER_ButtonA },
252 { "ButtonB", KEY_CONTROLLER_ButtonB },
253 { "ButtonX", KEY_CONTROLLER_ButtonX },
254 { "ButtonY", KEY_CONTROLLER_ButtonY },
255 { "ButtonBack", KEY_CONTROLLER_ButtonBack },
256 { "ButtonGuide", KEY_CONTROLLER_ButtonGuide },
257 { "ButtonStart", KEY_CONTROLLER_ButtonStart },
258 { "ButtonLeftStick", KEY_CONTROLLER_ButtonLeftStick },
259 { "ButtonRightStick", KEY_CONTROLLER_ButtonRightStick },
260 { "ButtonLeftShoulder", KEY_CONTROLLER_ButtonLeftShoulder },
261 { "ButtonRightShoulder", KEY_CONTROLLER_ButtonRightShoulder },
262 { "ButtonDpadUp", KEY_CONTROLLER_ButtonDpadUp },
263 { "ButtonDpadDown", KEY_CONTROLLER_ButtonDpadDown },
264 { "ButtonDpadLeft", KEY_CONTROLLER_ButtonDpadLeft },
265 { "ButtonDpadRight", KEY_CONTROLLER_ButtonDpadRight },
266 { "AnyButton", KEY_CONTROLLER_AnyButton },
267 { "LeftStickLeft", KEY_CONTROLLER_AxisLeftXLeft },
268 { "LeftStickRight", KEY_CONTROLLER_AxisLeftXRight },
269 { "LeftStickUp", KEY_CONTROLLER_AxisLeftYUp },
270 { "LeftStickDown", KEY_CONTROLLER_AxisLeftYDown },
271 { "RightStickLeft", KEY_CONTROLLER_AxisRightXLeft },
272 { "RightStickRight", KEY_CONTROLLER_AxisRightXRight },
273 { "RightStickUp", KEY_CONTROLLER_AxisRightYUp },
274 { "RightStickDown", KEY_CONTROLLER_AxisRightYDown },
275 { "LeftTrigger", KEY_CONTROLLER_AxisTriggerLeft },
276 { "RightTrigger", KEY_CONTROLLER_AxisTriggerRight },
277 };
278
String2KeyCode(const StdStrBuf & sName)279 C4KeyCode C4KeyCodeEx::String2KeyCode(const StdStrBuf &sName)
280 {
281 // direct key code, e.g. "$e" (Backspace)?
282 if (sName.getLength() > 1)
283 {
284 unsigned int dwRVal;
285 if (sscanf(sName.getData(), R"(\x%x)", &dwRVal) == 1) return dwRVal;
286 // scan code
287 if (*sName.getData() == '$') return GetKeyByScanCode(sName.getData());
288 // direct gamepad code
289 std::regex controller_re(R"/(^Controller(\w+)$)/");
290 std::cmatch matches;
291 if (std::regex_match(sName.getData(), matches, controller_re))
292 {
293 auto keycode_it = controllercodes.find(matches[1].str());
294 if (keycode_it != controllercodes.end())
295 return KEY_Gamepad(keycode_it->second);
296 else
297 return KEY_Undefined;
298
299 }
300 bool is_mouse_key;
301 #ifdef _WIN32
302 is_mouse_key = !strnicmp(sName.getData(), "Mouse", 5);
303 #else
304 is_mouse_key = !strncasecmp(sName.getData(), "Mouse", 5);
305 #endif
306 if (is_mouse_key)
307 {
308 // skip Mouse/GameMouse
309 const char *key_str = sName.getData()+5;
310 int mouse_id;
311 if (sscanf(key_str, "%d", &mouse_id) == 1)
312 {
313 // skip number
314 while (isdigit(*key_str)) ++key_str;
315 // check for known mouse events (e.g. Mouse0Move or GameMouse0Wheel)
316 if (!stricmp(key_str, "Move")) return KEY_Mouse(mouse_id, KEY_MOUSE_Move);
317 if (!stricmp(key_str, "Wheel1Up")) return KEY_Mouse(mouse_id, KEY_MOUSE_Wheel1Up);
318 if (!stricmp(key_str, "Wheel1Down")) return KEY_Mouse(mouse_id, KEY_MOUSE_Wheel1Down);
319 // check for known mouse button events
320 if (SEqualNoCase(key_str, "Button", 6)) // e.g. Mouse0ButtonLeft or GameMouse0ButtonRightDouble (This line is left here to not break anything, the buttons are now named Mouse0Left)
321 key_str += 6;
322 uint8_t mouseevent_id = 0;
323 if (SEqualNoCase(key_str, "Left",4)) { mouseevent_id=KEY_MOUSE_ButtonLeft; key_str += 4; }
324 else if (SEqualNoCase(key_str, "Right",5)) { mouseevent_id=KEY_MOUSE_ButtonRight; key_str += 5; }
325 else if (SEqualNoCase(key_str, "Middle",6)) { mouseevent_id=KEY_MOUSE_ButtonMiddle; key_str += 6; }
326 else if (SEqualNoCase(key_str, "X1",2)) { mouseevent_id=KEY_MOUSE_ButtonX1; key_str += 2; }
327 else if (SEqualNoCase(key_str, "X2",2)) { mouseevent_id=KEY_MOUSE_ButtonX2; key_str += 2; }
328 else if (isdigit(*key_str))
329 {
330 // indexed mouse button (e.g. Mouse0Button4 or Mouse0Button4Double)
331 int button_index;
332 if (sscanf(key_str, "%d", &button_index) == 1)
333 {
334 mouseevent_id=static_cast<uint8_t>(KEY_MOUSE_Button1+button_index);
335 while (isdigit(*key_str)) ++key_str;
336 }
337 }
338 if (mouseevent_id)
339 {
340 // valid event if finished or followed by "Double"
341 if (!*key_str) return KEY_Mouse(mouse_id, mouseevent_id);
342 if (!stricmp(key_str, "Double")) return KEY_Mouse(mouse_id, mouseevent_id+(KEY_MOUSE_Button1Double-KEY_MOUSE_Button1));
343 // invalid mouse key...
344 }
345 }
346 }
347
348 }
349 // query map
350 const C4KeyCodeMapEntry *pCheck = KeyCodeMap;
351 while (pCheck->szName) {
352 if (SEqualNoCase(sName.getData(), pCheck->szName)) {
353 return(pCheck->wCode);
354 }
355 ++pCheck;
356 }
357 #if defined(USE_SDL_MAINLOOP)
358 SDL_Scancode s = SDL_GetScancodeFromName(sName.getData());
359 if (s != SDL_SCANCODE_UNKNOWN) return s;
360 #endif
361 return KEY_Undefined;
362 }
363
KeyCode2String(C4KeyCode wCode,bool fHumanReadable,bool fShort)364 StdStrBuf C4KeyCodeEx::KeyCode2String(C4KeyCode wCode, bool fHumanReadable, bool fShort)
365 {
366 // Gamepad keys
367 if (Key_IsGamepad(wCode))
368 {
369 if (fHumanReadable)
370 {
371 switch (Key_GetGamepadEvent(wCode))
372 {
373 case KEY_CONTROLLER_ButtonA : return StdStrBuf("{{@Ico:A}}");
374 case KEY_CONTROLLER_ButtonB : return StdStrBuf("{{@Ico:B}}");
375 case KEY_CONTROLLER_ButtonX : return StdStrBuf("{{@Ico:X}}");
376 case KEY_CONTROLLER_ButtonY : return StdStrBuf("{{@Ico:Y}}");
377 case KEY_CONTROLLER_ButtonBack : return StdStrBuf("{{@Ico:Back}}");
378 case KEY_CONTROLLER_ButtonGuide : return StdStrBuf("Guide");
379 case KEY_CONTROLLER_ButtonStart : return StdStrBuf("{{@Ico:Start}}");
380 case KEY_CONTROLLER_ButtonLeftStick : return StdStrBuf("{{@Ico:LeftStick}}");
381 case KEY_CONTROLLER_ButtonRightStick : return StdStrBuf("{{@Ico:RightStick}}");
382 case KEY_CONTROLLER_ButtonLeftShoulder : return StdStrBuf("{{@Ico:LeftShoulder}}");
383 case KEY_CONTROLLER_ButtonRightShoulder : return StdStrBuf("{{@Ico:RightShoulder}}");
384 case KEY_CONTROLLER_ButtonDpadUp : return StdStrBuf("{{@Ico:DpadUp}}");
385 case KEY_CONTROLLER_ButtonDpadDown : return StdStrBuf("{{@Ico:DpadDown}}");
386 case KEY_CONTROLLER_ButtonDpadLeft : return StdStrBuf("{{@Ico:DpadLeft}}");
387 case KEY_CONTROLLER_ButtonDpadRight : return StdStrBuf("{{@Ico:DpadRight}}");
388 case KEY_CONTROLLER_AnyButton : return StdStrBuf("Any Button");
389 case KEY_CONTROLLER_AxisLeftXLeft : return StdStrBuf("{{@Ico:LeftStick}} Left");
390 case KEY_CONTROLLER_AxisLeftXRight : return StdStrBuf("{{@Ico:LeftStick}} Right");
391 case KEY_CONTROLLER_AxisLeftYUp : return StdStrBuf("{{@Ico:LeftStick}} Up");
392 case KEY_CONTROLLER_AxisLeftYDown : return StdStrBuf("{{@Ico:LeftStick}} Down");
393 case KEY_CONTROLLER_AxisRightXLeft : return StdStrBuf("{{@Ico:RightStick}} Left");
394 case KEY_CONTROLLER_AxisRightXRight : return StdStrBuf("{{@Ico:RightStick}} Right");
395 case KEY_CONTROLLER_AxisRightYUp : return StdStrBuf("{{@Ico:RightStick}} Up");
396 case KEY_CONTROLLER_AxisRightYDown : return StdStrBuf("{{@Ico:RightStick}} Down");
397 case KEY_CONTROLLER_AxisTriggerLeft : return StdStrBuf("{{@Ico:LeftTrigger}}");
398 case KEY_CONTROLLER_AxisTriggerRight : return StdStrBuf("{{@Ico:RightTrigger}}");
399 }
400 }
401 else
402 {
403 // A linear search in our small map is probably fast enough.
404 auto it = std::find_if(controllercodes.begin(), controllercodes.end(), [wCode](const auto &p)
405 {
406 return p.second == Key_GetGamepadEvent(wCode);
407 });
408 if (it != controllercodes.end())
409 return FormatString("Controller%s", it->first.c_str());
410 }
411 return StdStrBuf("Unknown");
412 }
413
414 // Mouse keys
415 if (Key_IsMouse(wCode))
416 {
417 int mouse_id = Key_GetMouse(wCode);
418 int mouse_event = Key_GetMouseEvent(wCode);
419 const char *mouse_str = "Mouse";
420 switch (mouse_event)
421 {
422 case KEY_MOUSE_Move: return FormatString("%s%dMove", mouse_str, mouse_id);
423 case KEY_MOUSE_Wheel1Up: return FormatString("%s%dWheel1Up", mouse_str, mouse_id);
424 case KEY_MOUSE_Wheel1Down: return FormatString("%s%dWheel1Down", mouse_str, mouse_id);
425 case KEY_MOUSE_ButtonLeft: return FormatString("%s%dLeft", mouse_str, mouse_id);
426 case KEY_MOUSE_ButtonRight: return FormatString("%s%dRight", mouse_str, mouse_id);
427 case KEY_MOUSE_ButtonMiddle: return FormatString("%s%dMiddle", mouse_str, mouse_id);
428 case KEY_MOUSE_ButtonX1: return FormatString("%s%dX1", mouse_str, mouse_id);
429 case KEY_MOUSE_ButtonX2: return FormatString("%s%dX2", mouse_str, mouse_id);
430 case KEY_MOUSE_ButtonLeftDouble: return FormatString("%s%dLeftDouble", mouse_str, mouse_id);
431 case KEY_MOUSE_ButtonRightDouble: return FormatString("%s%dRightDouble", mouse_str, mouse_id);
432 case KEY_MOUSE_ButtonMiddleDouble:return FormatString("%s%dMiddleDouble", mouse_str, mouse_id);
433 case KEY_MOUSE_ButtonX1Double: return FormatString("%s%dX1Double", mouse_str, mouse_id);
434 case KEY_MOUSE_ButtonX2Double: return FormatString("%s%dX2Double", mouse_str, mouse_id);
435 default:
436 // extended mouse button
437 {
438 uint8_t btn = Key_GetMouseEvent(wCode);
439 if (btn >= KEY_MOUSE_Button1Double)
440 return FormatString("%s%dButton%dDouble", mouse_str, mouse_id, int(btn-KEY_MOUSE_Button1Double));
441 else
442 return FormatString("%s%dButton%d", mouse_str, mouse_id, int(btn-KEY_MOUSE_Button1));
443 }
444 }
445 }
446
447 // it's a keyboard key
448 if (!fHumanReadable) {
449 // for config files and such: dump scancode
450 return FormatString("$%x", static_cast<unsigned int>(wCode));
451 }
452 #if defined(USE_WIN32_WINDOWS)
453
454 // Query map
455 const C4KeyCodeMapEntry *pCheck = KeyCodeMap;
456 while (pCheck->szName)
457 if (wCode == pCheck->wCode) return StdStrBuf((pCheck->szShortName && fShort) ? pCheck->szShortName : pCheck->szName); else ++pCheck;
458
459 // TODO: Works?
460 // StdStrBuf Name; Name.SetLength(1000);
461 // int res = GetKeyNameText(wCode, Name.getMData(), Name.getSize());
462 // if(!res)
463 // // not found: Compose as direct code
464 // return FormatString("\\x%x", (DWORD) wCode);
465 // // Set size
466 // Name.SetLength(res);
467 // return Name;
468
469 wchar_t buf[100];
470 int len = GetKeyNameText(wCode<<16, buf, 100);
471 if (len > 0) {
472 // buf is nullterminated name
473 return StdStrBuf(buf);
474 }
475 #elif defined (USE_COCOA)
476 // query map
477 const C4KeyCodeMapEntry *pCheck = KeyCodeMap;
478 while (pCheck->szName)
479 if (wCode == pCheck->wCode) return StdStrBuf((pCheck->szShortName && fShort) ? pCheck->szShortName : pCheck->szName); else ++pCheck;
480 // not found: Compose as direct code
481 return FormatString("\\x%x", static_cast<unsigned int>(wCode));
482 #elif defined(USE_SDL_MAINLOOP)
483 StdStrBuf buf;
484 auto name = KeycodeToString(wCode);
485 if (name) buf.Copy(name);
486 if (!buf.getLength()) buf.Format("\\x%lx", wCode);
487 return buf;
488 #endif
489 return FormatString("$%x", static_cast<unsigned int>(wCode));
490 }
491
ToString(bool fHumanReadable,bool fShort) const492 StdStrBuf C4KeyCodeEx::ToString(bool fHumanReadable, bool fShort) const
493 {
494 static StdStrBuf sResult;
495 sResult.Clear();
496 // Add shift
497 for (DWORD dwShiftCheck = KEYS_First; dwShiftCheck <= KEYS_Max; dwShiftCheck <<= 1)
498 if (dwShiftCheck & dwShift)
499 {
500 sResult.Append(KeyShift2String((C4KeyShiftState) dwShiftCheck));
501 sResult.AppendChar('+');
502 }
503 // Add key
504 if (sResult.getLength())
505 {
506 sResult.Append(KeyCode2String(Key, fHumanReadable, fShort));
507 return sResult;
508 }
509 else
510 {
511 return KeyCode2String(Key, fHumanReadable, fShort);
512 }
513 }
514
515
516
517 /* ----------------- C4KeyCodeEx ------------------ */
518
CompileFunc(StdCompiler * pComp,StdStrBuf * pOutBuf)519 void C4KeyCodeEx::CompileFunc(StdCompiler *pComp, StdStrBuf *pOutBuf)
520 {
521 if (pComp->isDeserializer())
522 {
523 // reading from file
524 StdStrBuf sCode;
525 bool is_scan_code;
526 // read shifts
527 DWORD dwSetShift = 0;
528 for (;;)
529 {
530 is_scan_code = pComp->Separator(StdCompiler::SEP_DOLLAR);
531 if (!is_scan_code) pComp->NoSeparator();
532 pComp->Value(mkParAdapt(sCode, StdCompiler::RCT_Idtf));
533 if (is_scan_code) // scan codes start with $. Reassamble the two tokens that were split by StdCompiler
534 {
535 sCode.Take(FormatString("$%s", sCode.getData()));
536 break;
537 }
538 if (!pComp->Separator(StdCompiler::SEP_PLUS)) break; // no more separator: Parse this as keyboard code
539 // try to convert to shift state
540 C4KeyShiftState eAddState = String2KeyShift(sCode);
541 if (eAddState == KEYS_Undefined)
542 pComp->excCorrupt("undefined key shift state: %s", sCode.getData());
543 dwSetShift |= eAddState;
544 }
545 // any code given? Otherwise, keep default
546 if (sCode.getLength())
547 {
548 // last section: convert to key code
549 C4KeyCode eCode = String2KeyCode(sCode);
550 if (eCode == KEY_Undefined)
551 {
552 if (pOutBuf)
553 {
554 // unknown key, but an output buffer for unknown keys was provided. No failure; caller might resolve key.
555 eCode = KEY_Default;
556 }
557 else
558 {
559 pComp->excCorrupt("undefined key code: %s", sCode.getData());
560 }
561 }
562 dwShift = dwSetShift;
563 Key = eCode;
564 if (pOutBuf) {
565 // FIXME: This function is used both, to deserialize things like CON_Right and Shift+$12
566 // For CON_…, eCode and dwShift will be zero, and sCode will contain the key name.
567 // For Shift+… sCode will only contain the last token. What is correct here?
568 // Reading C4PlayerControlAssignment::KeyComboItem::CompileFunc suggests that setting not value for parsed combinations may be correct.
569 if (eCode == 0)
570 pOutBuf->Take(std::move(sCode));
571 else
572 pOutBuf->Copy(ToString(false, false));
573 }
574 }
575 }
576 else
577 {
578 // write shift states
579 for (DWORD dwShiftCheck = KEYS_First; dwShiftCheck <= KEYS_Max; dwShiftCheck <<= 1)
580 if (dwShiftCheck & dwShift)
581 {
582 pComp->Value(mkDecompileAdapt(KeyShift2String((C4KeyShiftState) dwShiftCheck)));
583 pComp->Separator(StdCompiler::SEP_PLUS);
584 }
585 // write key
586 pComp->Value(mkDecompileAdapt(KeyCode2String(Key, false, false)));
587 }
588 }
589
CompileFunc(StdCompiler * pComp)590 void C4KeyEventData::CompileFunc(StdCompiler *pComp)
591 {
592 pComp->Value(iStrength);
593 pComp->Separator();
594 pComp->Value(game_x);
595 pComp->Separator();
596 pComp->Value(game_y);
597 pComp->Separator();
598 pComp->Value(vp_x);
599 pComp->Separator();
600 pComp->Value(vp_y);
601 }
602
operator ==(const struct C4KeyEventData & cmp) const603 bool C4KeyEventData::operator ==(const struct C4KeyEventData &cmp) const
604 {
605 return iStrength == cmp.iStrength
606 && game_x == cmp.game_x && game_y == cmp.game_y
607 && vp_x == cmp.vp_x && vp_y == cmp.vp_y;
608
609 }
610
KEY_IsModifier(C4KeyCode k)611 bool KEY_IsModifier(C4KeyCode k) {
612 return k == K_CONTROL_L || k == K_SHIFT_L || k == K_ALT_L ||
613 k == K_CONTROL_R || k == K_SHIFT_R || k == K_ALT_R;
614 }
615
616
617 /* ----------------- C4CustomKey------------------ */
618
C4CustomKey(const C4KeyCodeEx & DefCode,const char * szName,C4KeyScope Scope,C4KeyboardCallbackInterface * pCallback,unsigned int uiPriority)619 C4CustomKey::C4CustomKey(const C4KeyCodeEx &DefCode, const char *szName, C4KeyScope Scope, C4KeyboardCallbackInterface *pCallback, unsigned int uiPriority)
620 : Scope(Scope), Name(), uiPriority(uiPriority), iRef(0), is_down(false)
621 {
622 // generate code
623 if (DefCode.Key != KEY_Default) DefaultCodes.push_back(DefCode);
624 // ctor for default key
625 Name.Copy(szName);
626 if (pCallback)
627 {
628 pCallback->Ref();
629 vecCallbacks.push_back(pCallback);
630 pCallback->pOriginalKey = this;
631 }
632 }
633
C4CustomKey(CodeList rDefCodes,const char * szName,C4KeyScope Scope,C4KeyboardCallbackInterface * pCallback,unsigned int uiPriority)634 C4CustomKey::C4CustomKey(CodeList rDefCodes, const char *szName, C4KeyScope Scope, C4KeyboardCallbackInterface *pCallback, unsigned int uiPriority)
635 : DefaultCodes(std::move(rDefCodes)), Scope(Scope), Name(), uiPriority(uiPriority), iRef(0), is_down(false)
636 {
637 // ctor for default key
638 Name.Copy(szName);
639 if (pCallback)
640 {
641 pCallback->Ref();
642 vecCallbacks.push_back(pCallback);
643 pCallback->pOriginalKey = this;
644 }
645 }
646
C4CustomKey(const C4CustomKey & rCpy,bool fCopyCallbacks)647 C4CustomKey::C4CustomKey(const C4CustomKey &rCpy, bool fCopyCallbacks)
648 : Codes(rCpy.Codes), DefaultCodes(rCpy.DefaultCodes), Scope(rCpy.Scope), Name(), uiPriority(rCpy.uiPriority), iRef(0), is_down(false)
649 {
650 Name.Copy(rCpy.GetName());
651 if (fCopyCallbacks)
652 {
653 for (auto callback : rCpy.vecCallbacks)
654 {
655 callback->Ref();
656 vecCallbacks.push_back(callback);
657 }
658 }
659 }
660
~C4CustomKey()661 C4CustomKey::~C4CustomKey()
662 {
663 // free callback handles
664 for (CBVec::const_iterator i = vecCallbacks.begin(); i != vecCallbacks.end(); ++i)
665 (*i)->Deref();
666 }
667
IsCodeMatched(const C4KeyCodeEx & key) const668 bool C4CustomKey::IsCodeMatched(const C4KeyCodeEx &key) const
669 {
670 const CodeList &codes = GetCodes();
671 for (const auto &code : codes)
672 if (code == key)
673 return true;
674 return false;
675 }
676
Update(const C4CustomKey * pByKey)677 void C4CustomKey::Update(const C4CustomKey *pByKey)
678 {
679 assert(pByKey);
680 assert(Name == pByKey->Name);
681 // transfer any assigned data, except name which should be equal anyway
682 if (pByKey->DefaultCodes.size()) DefaultCodes = pByKey->DefaultCodes;
683 if (pByKey->Codes.size()) Codes = pByKey->Codes;
684 if (pByKey->Scope != KEYSCOPE_None) Scope = pByKey->Scope;
685 if (pByKey->uiPriority != PRIO_None) uiPriority = pByKey->uiPriority;
686 for (auto callback : pByKey->vecCallbacks)
687 {
688 callback->Ref();
689 vecCallbacks.push_back(callback);
690 }
691 }
692
C4KeyboardCallbackInterfaceHasOriginalKey(C4KeyboardCallbackInterface * pIntfc,const C4CustomKey * pCheckKey)693 bool C4KeyboardCallbackInterfaceHasOriginalKey(C4KeyboardCallbackInterface *pIntfc, const C4CustomKey *pCheckKey)
694 {
695 return pIntfc->IsOriginalKey(pCheckKey);
696 }
697
KillCallbacks(const C4CustomKey * pOfKey)698 void C4CustomKey::KillCallbacks(const C4CustomKey *pOfKey)
699 {
700 // remove all instances from list
701 CBVec::iterator i;
702 while ((i = std::find_if(vecCallbacks.begin(), vecCallbacks.end(), std::bind2nd(std::ptr_fun(&C4KeyboardCallbackInterfaceHasOriginalKey), pOfKey))) != vecCallbacks.end())
703 {
704 C4KeyboardCallbackInterface *pItfc = *i;
705 vecCallbacks.erase(i);
706 pItfc->Deref();
707 }
708 }
709
CompileFunc(StdCompiler * pComp)710 void C4CustomKey::CompileFunc(StdCompiler *pComp)
711 {
712 pComp->Value(mkNamingAdapt(mkSTLContainerAdapt(Codes), Name.getData(), DefaultCodes));
713 }
714
Execute(C4KeyEventType eEv,C4KeyCodeEx key)715 bool C4CustomKey::Execute(C4KeyEventType eEv, C4KeyCodeEx key)
716 {
717 // remember down-state
718 is_down = (eEv == KEYEV_Down);
719 // execute all callbacks
720 for (auto & callback : vecCallbacks)
721 if (callback->OnKeyEvent(key, eEv))
722 return true;
723 // no event processed it
724 return false;
725 }
726
727
728
729 /* ----------------- C4KeyBinding ------------------ */
730
C4KeyBinding(const C4KeyCodeEx & DefCode,const char * szName,C4KeyScope Scope,C4KeyboardCallbackInterface * pCallback,unsigned int uiPriority)731 C4KeyBinding::C4KeyBinding(const C4KeyCodeEx &DefCode, const char *szName, C4KeyScope Scope, C4KeyboardCallbackInterface *pCallback, unsigned int uiPriority)
732 : C4CustomKey(DefCode, szName, Scope, pCallback, uiPriority)
733 {
734 // self holds a ref
735 Ref();
736 // register into keyboard input class
737 C4KeyboardInput_Init().RegisterKey(this);
738 }
739
C4KeyBinding(const CodeList & rDefCodes,const char * szName,C4KeyScope Scope,C4KeyboardCallbackInterface * pCallback,unsigned int uiPriority)740 C4KeyBinding::C4KeyBinding(const CodeList &rDefCodes, const char *szName, C4KeyScope Scope, C4KeyboardCallbackInterface *pCallback, unsigned int uiPriority)
741 : C4CustomKey(rDefCodes, szName, Scope, pCallback, uiPriority)
742 {
743 // self holds a ref
744 Ref();
745 // register into keyboard input class
746 C4KeyboardInput_Init().RegisterKey(this);
747 }
748
~C4KeyBinding()749 C4KeyBinding::~C4KeyBinding()
750 {
751 // deregister from keyboard input class, if that class still exists
752 if (C4KeyboardInput::IsValid)
753 Game.KeyboardInput.UnregisterKeyBinding(this);
754 // shouldn't be refed now
755 assert(iRef==1);
756 iRef = 0;
757 }
758
759
760 /* ----------------- C4KeyboardInput ------------------ */
761
762 bool C4KeyboardInput::IsValid = false;
763
Clear()764 void C4KeyboardInput::Clear()
765 {
766 LastKeyExtraData = C4KeyEventData();
767 // release all keys - name map is guarantueed to contain them all
768 for (KeyNameMap::const_iterator i = KeysByName.begin(); i != KeysByName.end(); ++i)
769 i->second->Deref();
770 // clear maps
771 KeysByCode.clear();
772 KeysByName.clear();
773 }
774
UpdateKeyCodes(C4CustomKey * pKey,const C4CustomKey::CodeList & rOldCodes,const C4CustomKey::CodeList & rNewCodes)775 void C4KeyboardInput::UpdateKeyCodes(C4CustomKey *pKey, const C4CustomKey::CodeList &rOldCodes, const C4CustomKey::CodeList &rNewCodes)
776 {
777 // new key codes must be the new current key codes
778 assert(pKey->GetCodes() == rNewCodes);
779 // kill from old list
780 C4CustomKey::CodeList::const_iterator iCode;
781 for (iCode = rOldCodes.begin(); iCode != rOldCodes.end(); ++iCode)
782 {
783 // no need to kill if code stayed
784 if (std::find(rNewCodes.begin(), rNewCodes.end(), *iCode) != rNewCodes.end()) continue;
785 std::pair<KeyCodeMap::iterator, KeyCodeMap::iterator> KeyRange = KeysByCode.equal_range((*iCode).Key);
786 for (KeyCodeMap::iterator i = KeyRange.first; i != KeyRange.second; ++i)
787 if (i->second == pKey)
788 {
789 KeysByCode.erase(i);
790 break;
791 }
792 }
793 // readd new codes
794 for (iCode = rNewCodes.begin(); iCode != rNewCodes.end(); ++iCode)
795 {
796 // no double-add if it was in old list already
797 if (std::find(rOldCodes.begin(), rOldCodes.end(), *iCode) != rOldCodes.end()) continue;
798 KeysByCode.insert(std::make_pair((*iCode).Key, pKey));
799 }
800 }
801
RegisterKey(C4CustomKey * pRegKey)802 void C4KeyboardInput::RegisterKey(C4CustomKey *pRegKey)
803 {
804 assert(pRegKey); if (!pRegKey) return;
805 // key will be added: ref it
806 pRegKey->Ref();
807 // search key of same name first
808 C4CustomKey *pDupKey = KeysByName[pRegKey->GetName().getData()];
809 if (pDupKey)
810 {
811 // key of this name exists: Merge them (old codes copied cuz they'll be overwritten)
812 C4CustomKey::CodeList OldCodes = pDupKey->GetCodes();
813 const C4CustomKey::CodeList &rNewCodes = pRegKey->GetCodes();
814 pDupKey->Update(pRegKey);
815 // update access map if key changed
816 if (!(OldCodes == rNewCodes)) UpdateKeyCodes(pDupKey, OldCodes, rNewCodes);
817 // key to be registered no longer used
818 pRegKey->Deref();
819 }
820 else
821 {
822 // new unique key: Insert into map
823 KeysByName[pRegKey->GetName().getData()] = pRegKey;
824 for (C4CustomKey::CodeList::const_iterator i = pRegKey->GetCodes().begin(); i != pRegKey->GetCodes().end(); ++i)
825 {
826 KeysByCode.insert(std::make_pair((*i).Key, pRegKey));
827 }
828 }
829 }
830
UnregisterKey(const StdStrBuf & rsName)831 void C4KeyboardInput::UnregisterKey(const StdStrBuf &rsName)
832 {
833 // kill from name map
834 KeyNameMap::iterator in = KeysByName.find(rsName.getData());
835 if (in == KeysByName.end()) return;
836 C4CustomKey *pKey = in->second;
837 KeysByName.erase(in);
838 // kill all key bindings from key map
839 for (C4CustomKey::CodeList::const_iterator iCode = pKey->GetCodes().begin(); iCode != pKey->GetCodes().end(); ++iCode)
840 {
841 std::pair<KeyCodeMap::iterator, KeyCodeMap::iterator> KeyRange = KeysByCode.equal_range((*iCode).Key);
842 for (KeyCodeMap::iterator i = KeyRange.first; i != KeyRange.second; ++i)
843 if (i->second == pKey)
844 {
845 KeysByCode.erase(i);
846 break;
847 }
848 }
849 // release reference to key
850 pKey->Deref();
851 }
852
UnregisterKeyBinding(C4CustomKey * pUnregKey)853 void C4KeyboardInput::UnregisterKeyBinding(C4CustomKey *pUnregKey)
854 {
855 // find key in name map
856 KeyNameMap::iterator in = KeysByName.find(pUnregKey->GetName().getData());
857 if (in == KeysByName.end()) return;
858 C4CustomKey *pKey = in->second;
859 // is this key in the map?
860 if (pKey != pUnregKey)
861 {
862 // Other key is in the list: Just remove the callbacks
863 pKey->KillCallbacks(pUnregKey);
864 return;
865 }
866 // this key is in the list: Replace by a duplicate...
867 C4CustomKey *pNewKey = new C4CustomKey(*pUnregKey, true);
868 // ...without the own callbacks
869 pNewKey->KillCallbacks(pUnregKey);
870 // and replace current key by duplicate
871 UnregisterKey(pUnregKey->GetName());
872 RegisterKey(pNewKey);
873 }
874
DoInput(const C4KeyCodeEx & InKey,C4KeyEventType InEvent,DWORD InScope,int32_t iStrength)875 bool C4KeyboardInput::DoInput(const C4KeyCodeEx &InKey, C4KeyEventType InEvent, DWORD InScope, int32_t iStrength)
876 {
877 // store last-key-info
878 LastKeyExtraData.iStrength = (iStrength >= 0) ? iStrength : ((InEvent != KEYEV_Up) * 100);
879 LastKeyExtraData.game_x = LastKeyExtraData.game_y = LastKeyExtraData.vp_x = LastKeyExtraData.vp_y = C4KeyEventData::KeyPos_None;
880 // check all key events generated by this key: First the keycode itself, then any more generic key events like KEY_Any
881 const int32_t iKeyRangeMax = 5;
882 int32_t iKeyRangeCnt=0, j;
883 C4KeyCode FallbackKeys[iKeyRangeMax];
884 FallbackKeys[iKeyRangeCnt++] = InKey.Key;
885 if (Key_IsGamepadButton(InKey.Key))
886 {
887 // "any gamepad button"-event
888 FallbackKeys[iKeyRangeCnt++] = KEY_Gamepad(KEY_CONTROLLER_AnyButton);
889 }
890 else if (Key_IsGamepadAxis(InKey.Key))
891 {
892 // TODO: do we need "any axis" events?
893 }
894 if (InKey.Key != KEY_Any) FallbackKeys[iKeyRangeCnt++] = KEY_Any;
895 // now get key ranges for fallback chain
896 std::pair<KeyCodeMap::iterator, KeyCodeMap::iterator> KeyRanges[iKeyRangeMax];
897 assert(iKeyRangeCnt <= iKeyRangeMax);
898 for (int32_t i = 0; i<iKeyRangeCnt; ++i)
899 {
900 KeyRanges[i] = KeysByCode.equal_range(FallbackKeys[i]);
901 }
902 // check all assigned keys
903 // exec from highest to lowest priority
904 unsigned int uiLastPrio = C4CustomKey::PRIO_MoreThanMax;
905 for (;;)
906 {
907 KeyCodeMap::const_iterator i;
908 // get priority to exec
909 unsigned int uiExecPrio = C4CustomKey::PRIO_None, uiCurr;
910 for (j = 0; j < iKeyRangeCnt; ++j)
911 for (i = KeyRanges[j].first; i != KeyRanges[j].second; ++i)
912 {
913 uiCurr = i->second->GetPriority();
914 if (uiCurr > uiExecPrio && uiCurr < uiLastPrio) uiExecPrio = uiCurr;
915 }
916 // nothing with correct priority set left?
917 if (uiExecPrio == C4CustomKey::PRIO_None) break;
918 // exec all of this priority
919 for (j = 0; j < iKeyRangeCnt; ++j)
920 for (i = KeyRanges[j].first; i != KeyRanges[j].second; ++i)
921 {
922 C4CustomKey *pKey = i->second;
923 assert(pKey);
924 // check priority
925 if (pKey->GetPriority() == uiExecPrio)
926 // check scope and modifier
927 // (not on release of a key that has been down, because a key release might happen with a different modifier or in different scope than its pressing!)
928 if ((InEvent == KEYEV_Up && pKey->IsDown())
929 || ((pKey->GetScope() & InScope) && pKey->IsCodeMatched(C4KeyCodeEx(FallbackKeys[j], C4KeyShiftState(InKey.dwShift)))))
930 // exec it
931 if (pKey->Execute(InEvent, InKey))
932 return true;
933 }
934 // nothing found in this priority: exec next
935 uiLastPrio = uiExecPrio;
936 }
937 // no key matched or all returned false in Execute: Not processed
938 return false;
939 }
940
CompileFunc(StdCompiler * pComp)941 void C4KeyboardInput::CompileFunc(StdCompiler *pComp)
942 {
943 // compile all keys that are already defined
944 // no definition of new keys with current compiler...
945 pComp->Name("Keys");
946 try
947 {
948 for (KeyNameMap::const_iterator i = KeysByName.begin(); i != KeysByName.end(); ++i)
949 {
950 // naming done in C4CustomKey, because default is determined by key only
951 C4CustomKey::CodeList OldCodes = i->second->GetCodes();
952 pComp->Value(*i->second);
953 // resort in secondary map if key changed
954 if (pComp->isDeserializer())
955 {
956 const C4CustomKey::CodeList &rNewCodes = i->second->GetCodes();
957 if (!(OldCodes == rNewCodes)) UpdateKeyCodes(i->second, OldCodes, rNewCodes);
958 }
959 }
960 }
961 catch (StdCompiler::Exception *pEx)
962 {
963 pComp->NameEnd(true);
964 throw pEx;
965 }
966 pComp->NameEnd();
967 }
968
LoadCustomConfig()969 bool C4KeyboardInput::LoadCustomConfig()
970 {
971 // load from INI file (2do: load from registry)
972 C4Group GrpExtra;
973 if (!GrpExtra.Open(C4CFN_Extra)) return false;
974 StdBuf sFileContents;
975 if (!GrpExtra.LoadEntry(C4CFN_KeyConfig, &sFileContents)) return false;
976 StdStrBuf sFileContentsString((const char *) sFileContents.getData());
977 if (!CompileFromBuf_LogWarn<StdCompilerINIRead>(*this, sFileContentsString, "Custom keys from" C4CFN_Extra DirSep C4CFN_KeyConfig))
978 return false;
979 LogF(LoadResStr("IDS_PRC_LOADEDKEYCONF"), C4CFN_Extra DirSep C4CFN_KeyConfig);
980 return true;
981 }
982
GetKeyByName(const char * szKeyName)983 C4CustomKey *C4KeyboardInput::GetKeyByName(const char *szKeyName)
984 {
985 KeyNameMap::const_iterator i = KeysByName.find(szKeyName);
986 if (i == KeysByName.end()) return nullptr; else return (*i).second;
987 }
988
GetKeyCodeNameByKeyName(const char * szKeyName,bool fShort,int32_t iIndex)989 StdStrBuf C4KeyboardInput::GetKeyCodeNameByKeyName(const char *szKeyName, bool fShort, int32_t iIndex)
990 {
991 C4CustomKey *pKey = GetKeyByName(szKeyName);
992 if (pKey)
993 {
994 const C4CustomKey::CodeList &codes = pKey->GetCodes();
995 if ((size_t)iIndex < codes.size())
996 {
997 C4KeyCodeEx code = codes[iIndex];
998 return code.ToString(true, fShort);
999 }
1000 }
1001 // Error
1002 return StdStrBuf();
1003 }
1004
C4KeyboardInput_Init()1005 C4KeyboardInput &C4KeyboardInput_Init()
1006 {
1007 static C4KeyboardInput keyinp;
1008 return keyinp;
1009 }
1010