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
36struct C4KeyShiftMapEntry
37{
38 C4KeyShiftState eShift;
39 const char *szName;
40};
41
42const 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
50C4KeyShiftState 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
59std::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
68struct C4KeyCodeMapEntry
69{
70 C4KeyCode wCode;
71 const char *szName;
72 const char *szShortName;
73};
74
75#ifdef _WIN32
76const 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
293C4KeyCode 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
333std::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
387std::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
402void 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
446C4CustomKey::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
461C4CustomKey::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
474C4CustomKey::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
488C4CustomKey::~C4CustomKey()
489{
490 // free callback handles
491 for (CBVec::const_iterator i = vecCallbacks.begin(); i != vecCallbacks.end(); ++i)
492 (*i)->Deref();
493}
494
495void 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
511void 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
525void C4CustomKey::CompileFunc(StdCompiler *pComp)
526{
527 pComp->Value(rStruct: mkNamingAdapt(rValue: mkSTLContainerAdapt(rTarget&: Codes), szName: Name.getData(), rDefault: DefaultCodes));
528}
529
530bool 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
542C4KeyBinding::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
551C4KeyBinding::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
560C4KeyBinding::~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
572bool C4KeyboardInput::IsValid = false;
573
574void 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
584void 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
611void 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
638void 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
660void 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
682bool 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
753void 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
780bool C4KeyboardInput::LoadCustomConfig()
781{
782 // load from INI file (2do: load from registry)
783 C4Group GrpExtra;
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
793C4CustomKey *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
799std::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
815C4KeyboardInput &C4KeyboardInput_Init()
816{
817 static C4KeyboardInput keyinp;
818 return keyinp;
819}
820