/* * OpenClonk, http://www.openclonk.org * * Copyright (c) 2005-2009, RedWolf Design GmbH, http://www.clonk.de/ * Copyright (c) 2009-2013, The OpenClonk Team and contributors * * Distributed under the terms of the ISC license; see accompanying file * "COPYING" for details. * * "Clonk" is a registered trademark of Matthes Bender, used with permission. * See accompanying file "TRADEMARK" for details. * * To redistribute this file separately, substitute the full license texts * for the above references. */ // Keyboard input mapping to engine functions #include #include #include #include #include #ifdef USE_GTK #include #ifdef GDK_WINDOWING_X11 #include #include #endif #endif #include #ifdef USE_SDL_MAINLOOP #include #endif /* ----------------- Key maps ------------------ */ struct C4KeyShiftMapEntry { C4KeyShiftState eShift; const char *szName; }; const C4KeyShiftMapEntry KeyShiftMap [] = { { KEYS_Alt, "Alt" }, { KEYS_Control, "Ctrl" }, { KEYS_Shift, "Shift" }, { KEYS_Undefined, NULL } }; C4KeyShiftState C4KeyCodeEx::String2KeyShift(const StdStrBuf &sName) { // query map const C4KeyShiftMapEntry *pCheck = KeyShiftMap; while (pCheck->szName) if (SEqualNoCase(sName.getData(), pCheck->szName)) break; else ++pCheck; return pCheck->eShift; } StdStrBuf C4KeyCodeEx::KeyShift2String(C4KeyShiftState eShift) { // query map const C4KeyShiftMapEntry *pCheck = KeyShiftMap; while (pCheck->szName) if (eShift == pCheck->eShift) break; else ++pCheck; return StdStrBuf(pCheck->szName); } struct C4KeyCodeMapEntry { C4KeyCode wCode; const char *szName; const char *szShortName; }; #if defined(USE_COCOA) #include "CocoaKeycodeMap.h" #else const C4KeyCodeMapEntry KeyCodeMap[] = { {K_ESCAPE, "Escape", "Esc"}, {K_1, "1", NULL}, {K_2, "2", NULL}, {K_3, "3", NULL}, {K_4, "4", NULL}, {K_5, "5", NULL}, {K_6, "6", NULL}, {K_7, "7", NULL}, {K_8, "8", NULL}, {K_9, "9", NULL}, {K_0, "0", NULL}, {K_MINUS, "Minus", "-"}, {K_EQUAL, "Equal", "="}, {K_BACK, "BackSpace", NULL}, {K_TAB, "Tab", NULL}, {K_Q, "Q", NULL}, {K_W, "W", NULL}, {K_E, "E", NULL}, {K_R, "R", NULL}, {K_T, "T", NULL}, {K_Y, "Y", NULL}, {K_U, "U", NULL}, {K_I, "I", NULL}, {K_O, "O", NULL}, {K_P, "P", NULL}, {K_LEFT_BRACKET,"LeftBracket", "["}, {K_RIGHT_BRACKET,"RightBracket","]"}, {K_RETURN, "Return", "Ret"}, {K_CONTROL_L, "LeftControl", "LCtrl"}, {K_A, "A", NULL}, {K_S, "S", NULL}, {K_D, "D", NULL}, {K_F, "F", NULL}, {K_G, "G", NULL}, {K_H, "H", NULL}, {K_J, "J", NULL}, {K_K, "K", NULL}, {K_L, "L", NULL}, {K_SEMICOLON, "Semicolon", ";"}, {K_APOSTROPHE, "Apostrophe", "'"}, {K_GRAVE_ACCENT,"GraveAccent", "`"}, {K_SHIFT_L, "LeftShift", "LShift"}, {K_BACKSLASH, "Backslash", "\\"}, {K_Z, "Z", NULL}, {K_X, "X", NULL}, {K_C, "C", NULL}, {K_V, "V", NULL}, {K_B, "B", NULL}, {K_N, "N", NULL}, {K_M, "M", NULL}, {K_COMMA, "Comma", ","}, {K_PERIOD, "Period", "."}, {K_SLASH, "Slash", "/"}, {K_SHIFT_R, "RightShift", "RShift"}, {K_MULTIPLY, "Multiply", "N*"}, {K_ALT_L, "LeftAlt", "LAlt"}, {K_SPACE, "Space", "Sp"}, {K_CAPS, "Capslock", NULL}, {K_F1, "F1", NULL}, {K_F2, "F2", NULL}, {K_F3, "F3", NULL}, {K_F4, "F4", NULL}, {K_F5, "F5", NULL}, {K_F6, "F6", NULL}, {K_F7, "F7", NULL}, {K_F8, "F8", NULL}, {K_F9, "F9", NULL}, {K_F10, "F10", NULL}, {K_NUM, "NumLock", "NLock"}, {K_SCROLL, "ScrollLock", "SLock"}, {K_NUM7, "Num7", "N7"}, {K_NUM8, "Num8", "N8"}, {K_NUM9, "Num9", "N9"}, {K_SUBTRACT, "Subtract", "N-"}, {K_NUM4, "Num4", "N4"}, {K_NUM5, "Num5", "N5"}, {K_NUM6, "Num6", "N6"}, {K_ADD, "Add", "N+"}, {K_NUM1, "Num1", "N1"}, {K_NUM2, "Num2", "N2"}, {K_NUM3, "Num3", "N3"}, {K_NUM0, "Num0", "N0"}, {K_DECIMAL, "Decimal", "N,"}, {K_86, "|<>", NULL}, {K_F11, "F11", NULL}, {K_F12, "F12", NULL}, {K_NUM_RETURN, "NumReturn", "NRet"}, {K_CONTROL_R, "RightControl", "RCtrl"}, {K_DIVIDE, "Divide", "N/"}, {K_ALT_R, "RightAlt", "RAlt"}, {K_HOME, "Home", NULL}, {K_UP, "Up", NULL}, {K_PAGEUP, "PageUp", NULL}, {K_LEFT, "Left", NULL}, {K_RIGHT, "Right", NULL}, {K_END, "End", NULL}, {K_DOWN, "Down", NULL}, {K_PAGEDOWN, "PageDown", NULL}, {K_INSERT, "Insert", "Ins"}, {K_DELETE, "Delete", "Del"}, {K_PAUSE, "Pause", NULL}, {K_WIN_L, "LeftWin", "LWin"}, {K_WIN_R, "RightWin", "RWin"}, {K_MENU, "Menu", NULL}, {K_PRINT, "Print", NULL}, {0x00, NULL, NULL} }; #endif void C4KeyCodeEx::FixShiftKeys() { // reduce stuff like Ctrl+RightCtrl to simply RightCtrl if ((dwShift & KEYS_Control) && (Key == K_CONTROL_L || Key == K_CONTROL_R)) dwShift &= ~KEYS_Control; if ((dwShift & KEYS_Shift) && (Key == K_SHIFT_L || Key == K_SHIFT_R)) dwShift &= ~KEYS_Shift; } C4KeyCode C4KeyCodeEx::GetKeyByScanCode(const char *scan_code) { // scan code is in hex format unsigned int scan_code_int; if (sscanf(scan_code, "$%x", &scan_code_int) != 1) return KEY_Undefined; return scan_code_int; } C4KeyCode C4KeyCodeEx::String2KeyCode(const StdStrBuf &sName) { // direct key code? if (sName.getLength() > 2) { unsigned int dwRVal; if (sscanf(sName.getData(), "\\x%x", &dwRVal) == 1) return dwRVal; // scan code if (*sName.getData() == '$') return GetKeyByScanCode(sName.getData()); // direct gamepad code #ifdef _WIN32 if (!strnicmp(sName.getData(), "Joy", 3)) #else if (!strncasecmp(sName.getData(), "Joy", 3)) #endif { int iGamepad; if (sscanf(sName.getData(), "Joy%d", &iGamepad) == 1) { // skip Joy[number] const char *key_str = sName.getData()+4; while (isdigit(*key_str)) ++key_str; // check for button (single, uppercase letter) (e.g. Joy1A) if (*key_str && !key_str[1]) { char cGamepadButton = toupper(*key_str); if (Inside(cGamepadButton, 'A', 'Z')) { cGamepadButton = cGamepadButton - 'A'; return KEY_Gamepad(iGamepad-1, KEY_JOY_Button(cGamepadButton)); } } else { // check for standard axis (e.g. Joy1Left) if (!stricmp(key_str, "Left")) return KEY_Gamepad(iGamepad-1, KEY_JOY_Left); if (!stricmp(key_str, "Up")) return KEY_Gamepad(iGamepad-1, KEY_JOY_Up); if (!stricmp(key_str, "Down")) return KEY_Gamepad(iGamepad-1, KEY_JOY_Down); if (!stricmp(key_str, "Right")) return KEY_Gamepad(iGamepad-1, KEY_JOY_Right); // check for specific axis (e.g. Joy1Axis1Min) int iAxis; if (sscanf(key_str, "Axis%d", &iAxis) == 1 && iAxis>0) { --iAxis; // axis is 0-based internally but written 1-based in config key_str += 5; while (isdigit(*key_str)) ++key_str; if (!stricmp(key_str, "Min")) return KEY_Gamepad(iGamepad-1, KEY_JOY_Axis(iAxis, false)); if (!stricmp(key_str, "Max")) return KEY_Gamepad(iGamepad-1, KEY_JOY_Axis(iAxis, true)); } } } } bool is_mouse_key; #ifdef _WIN32 is_mouse_key = !strnicmp(sName.getData(), "Mouse", 5); #else is_mouse_key = !strncasecmp(sName.getData(), "Mouse", 5); #endif if (is_mouse_key) { // skip Mouse/GameMouse const char *key_str = sName.getData()+5; int mouse_id; if (sscanf(key_str, "%d", &mouse_id) == 1) { // skip number while (isdigit(*key_str)) ++key_str; // check for known mouse events (e.g. Mouse1Move or GameMouse1Wheel) if (!stricmp(key_str, "Move")) return KEY_Mouse(mouse_id-1, KEY_MOUSE_Move); if (!stricmp(key_str, "Wheel1Up")) return KEY_Mouse(mouse_id-1, KEY_MOUSE_Wheel1Up); if (!stricmp(key_str, "Wheel1Down")) return KEY_Mouse(mouse_id-1, KEY_MOUSE_Wheel1Down); if (SEqualNoCase(key_str, "Button", 6)) // e.g. Mouse1ButtonLeft or GameMouse1ButtonRightDouble { // check for known mouse button events uint8_t mouseevent_id = 0; key_str += 6; if (SEqualNoCase(key_str, "Left",4)) { mouseevent_id=KEY_MOUSE_ButtonLeft; key_str += 4; } else if (SEqualNoCase(key_str, "Right",5)) { mouseevent_id=KEY_MOUSE_ButtonRight; key_str += 5; } else if (SEqualNoCase(key_str, "Middle",6)) { mouseevent_id=KEY_MOUSE_ButtonMiddle; key_str += 6; } else if (isdigit(*key_str)) { // indexed mouse button (e.g. Mouse1Button4 or Mouse1Button4Double) int button_index; if (sscanf(key_str, "%d", &button_index) == 1) { mouseevent_id=static_cast(KEY_MOUSE_Button1+button_index-1); while (isdigit(*key_str)) ++key_str; } } if (mouseevent_id) { // valid event if finished or followed by "Double" if (!*key_str) return KEY_Mouse(mouse_id-1, mouseevent_id); if (!stricmp(key_str, "Double")) return KEY_Mouse(mouse_id-1, mouseevent_id+(KEY_MOUSE_Button1Double-KEY_MOUSE_Button1)); // invalid mouse key... } } } } } // query map const C4KeyCodeMapEntry *pCheck = KeyCodeMap; while (pCheck->szName) { if (SEqualNoCase(sName.getData(), pCheck->szName)) { return(pCheck->wCode); } ++pCheck; } #if defined(USE_SDL_MAINLOOP) SDL_Scancode s = SDL_GetScancodeFromName(sName.getData()); if (s != SDL_SCANCODE_UNKNOWN) return s; #endif return KEY_Undefined; } StdStrBuf C4KeyCodeEx::KeyCode2String(C4KeyCode wCode, bool fHumanReadable, bool fShort) { // Gamepad keys if (Key_IsGamepad(wCode)) { int iGamepad = Key_GetGamepad(wCode); int gamepad_event = Key_GetGamepadEvent(wCode); switch (gamepad_event) { case KEY_JOY_Left: return FormatString("Joy%dLeft", iGamepad+1); case KEY_JOY_Up: return FormatString("Joy%dUp", iGamepad+1); case KEY_JOY_Down: return FormatString("Joy%dDown", iGamepad+1); case KEY_JOY_Right: return FormatString("Joy%dRight", iGamepad+1); default: if (Key_IsGamepadAxis(wCode)) { if (fHumanReadable) // This is still not great, but it is not really possible to assign unknown axes to "left/right" "up/down"... return FormatString("[%d] %s", int(1 + Key_GetGamepadAxisIndex(wCode)), Key_IsGamepadAxisHigh(wCode) ? "Max" : "Min"); else return FormatString("Joy%dAxis%d%s", iGamepad+1, static_cast(Key_GetGamepadAxisIndex(wCode)+1), Key_IsGamepadAxisHigh(wCode) ? "Max" : "Min"); } else { // button if (fHumanReadable) // If there should be gamepads around with A B C D... on the buttons, we might create a display option to show letters instead... return FormatString("< %d >", int(1 + Key_GetGamepadButtonIndex(wCode))); else return FormatString("Joy%d%c", iGamepad+1, static_cast(Key_GetGamepadButtonIndex(wCode) + 'A')); } } } // Mouse keys if (Key_IsMouse(wCode)) { int mouse_id = Key_GetMouse(wCode); int mouse_event = Key_GetMouseEvent(wCode); const char *mouse_str = "Mouse"; switch (mouse_event) { case KEY_MOUSE_Move: return FormatString("%s%dMove", mouse_str, mouse_id); case KEY_MOUSE_Wheel1Up: return FormatString("%s%dWheel1Up", mouse_str, mouse_id); case KEY_MOUSE_Wheel1Down: return FormatString("%s%dWheel1Down", mouse_str, mouse_id); case KEY_MOUSE_ButtonLeft: return FormatString("%s%dLeft", mouse_str, mouse_id); case KEY_MOUSE_ButtonRight: return FormatString("%s%dRight", mouse_str, mouse_id); case KEY_MOUSE_ButtonMiddle: return FormatString("%s%dMiddle", mouse_str, mouse_id); case KEY_MOUSE_ButtonLeftDouble: return FormatString("%s%dLeftDouble", mouse_str, mouse_id); case KEY_MOUSE_ButtonRightDouble: return FormatString("%s%dRightDouble", mouse_str, mouse_id); case KEY_MOUSE_ButtonMiddleDouble:return FormatString("%s%dMiddleDouble", mouse_str, mouse_id); default: // extended mouse button { uint8_t btn = Key_GetMouseEvent(wCode); if (btn >= KEY_MOUSE_Button1Double) return FormatString("%s%dButton%dDouble", mouse_str, mouse_id, int(btn-KEY_MOUSE_Button1Double)); else return FormatString("%s%dButton%d", mouse_str, mouse_id, int(btn-KEY_MOUSE_Button1)); } } } // it's a keyboard key if (!fHumanReadable) { // for config files and such: dump scancode return FormatString("$%x", static_cast(wCode)); } #if defined(USE_WIN32_WINDOWS) || (defined(_WIN32) && defined(USE_GTK)) // Query map const C4KeyCodeMapEntry *pCheck = KeyCodeMap; while (pCheck->szName) if (wCode == pCheck->wCode) return StdStrBuf((pCheck->szShortName && fShort) ? pCheck->szShortName : pCheck->szName); else ++pCheck; // TODO: Works? // StdStrBuf Name; Name.SetLength(1000); // int res = GetKeyNameText(wCode, Name.getMData(), Name.getSize()); // if(!res) // // not found: Compose as direct code // return FormatString("\\x%x", (DWORD) wCode); // // Set size // Name.SetLength(res); // return Name; wchar_t buf[100]; int len = GetKeyNameText(wCode<<16, buf, 100); if (len > 0) { // buf is nullterminated name return StdStrBuf(buf); } #elif defined (USE_COCOA) // query map const C4KeyCodeMapEntry *pCheck = KeyCodeMap; while (pCheck->szName) if (wCode == pCheck->wCode) return StdStrBuf((pCheck->szShortName && fShort) ? pCheck->szShortName : pCheck->szName); else ++pCheck; // not found: Compose as direct code return FormatString("\\x%x", static_cast(wCode)); #elif defined(USE_GTK) Display * const dpy = gdk_x11_display_get_xdisplay(gdk_display_get_default()); KeySym keysym = (KeySym)XkbKeycodeToKeysym(dpy,wCode+8,0,0); char* name = NULL; if (keysym != NoSymbol) { // is the keycode without shift modifiers mapped to a symbol? name = gtk_accelerator_get_label_with_keycode(gdk_display_get_default(), keysym, wCode+8, (GdkModifierType)0); } if (name) { // is there a string representation of the keysym? // prevent memleak StdStrBuf buf; buf.Copy(name); g_free(name); return buf; } #elif defined(USE_SDL_MAINLOOP) StdStrBuf buf; buf.Copy(SDL_GetScancodeName(static_cast(wCode))); if (!buf.getLength()) buf.Format("\\x%x", wCode); return buf; #endif return FormatString("$%x", static_cast(wCode)); } StdStrBuf C4KeyCodeEx::ToString(bool fHumanReadable, bool fShort) const { static StdStrBuf sResult; sResult.Clear(); // Add shift for (DWORD dwShiftCheck = KEYS_First; dwShiftCheck <= KEYS_Max; dwShiftCheck <<= 1) if (dwShiftCheck & dwShift) { sResult.Append(KeyShift2String((C4KeyShiftState) dwShiftCheck)); sResult.AppendChar('+'); } // Add key if (sResult.getLength()) { sResult.Append(KeyCode2String(Key, fHumanReadable, fShort)); return sResult; } else { return KeyCode2String(Key, fHumanReadable, fShort); } } /* ----------------- C4KeyCodeEx ------------------ */ void C4KeyCodeEx::CompileFunc(StdCompiler *pComp, StdStrBuf *pOutBuf) { if (pComp->isCompiler()) { // reading from file StdStrBuf sCode; bool is_scan_code; // read shifts DWORD dwSetShift = 0; for (;;) { is_scan_code = pComp->Separator(StdCompiler::SEP_DOLLAR); if (!is_scan_code) pComp->NoSeparator(); pComp->Value(mkParAdapt(sCode, StdCompiler::RCT_Idtf)); if (is_scan_code) // scan codes start with $. Reassamble the two tokens that were split by StdCompiler { sCode.Take(FormatString("$%s", sCode.getData())); break; } if (!pComp->Separator(StdCompiler::SEP_PLUS)) break; // no more separator: Parse this as keyboard code // try to convert to shift state C4KeyShiftState eAddState = String2KeyShift(sCode); if (eAddState == KEYS_Undefined) pComp->excCorrupt("undefined key shift state: %s", sCode.getData()); dwSetShift |= eAddState; } // any code given? Otherwise, keep default if (sCode.getLength()) { // last section: convert to key code C4KeyCode eCode = String2KeyCode(sCode); if (eCode == KEY_Undefined) { if (pOutBuf) { // unknown key, but an output buffer for unknown keys was provided. No failure; caller might resolve key. eCode = KEY_Default; } else { pComp->excCorrupt("undefined key code: %s", sCode.getData()); } } dwShift = dwSetShift; Key = eCode; if (pOutBuf) pOutBuf->Take(std::move(sCode)); } } else { // write shift states for (DWORD dwShiftCheck = KEYS_First; dwShiftCheck <= KEYS_Max; dwShiftCheck <<= 1) if (dwShiftCheck & dwShift) { pComp->Value(mkDecompileAdapt(KeyShift2String((C4KeyShiftState) dwShiftCheck))); pComp->Separator(StdCompiler::SEP_PLUS); } // write key pComp->Value(mkDecompileAdapt(KeyCode2String(Key, false, false))); } } void C4KeyEventData::CompileFunc(StdCompiler *pComp) { pComp->Value(iStrength); pComp->Separator(); pComp->Value(game_x); pComp->Separator(); pComp->Value(game_y); pComp->Separator(); pComp->Value(vp_x); pComp->Separator(); pComp->Value(vp_y); } bool C4KeyEventData::operator ==(const struct C4KeyEventData &cmp) const { return iStrength == cmp.iStrength && game_x == cmp.game_x && game_y == cmp.game_y && vp_x == cmp.vp_x && vp_y == cmp.vp_y; } /* ----------------- C4CustomKey------------------ */ C4CustomKey::C4CustomKey(const C4KeyCodeEx &DefCode, const char *szName, C4KeyScope Scope, C4KeyboardCallbackInterface *pCallback, unsigned int uiPriority) : Scope(Scope), Name(), uiPriority(uiPriority), iRef(0), is_down(false) { // generate code if (DefCode.Key != KEY_Default) DefaultCodes.push_back(DefCode); // ctor for default key Name.Copy(szName); if (pCallback) { pCallback->Ref(); vecCallbacks.push_back(pCallback); pCallback->pOriginalKey = this; } } C4CustomKey::C4CustomKey(const CodeList &rDefCodes, const char *szName, C4KeyScope Scope, C4KeyboardCallbackInterface *pCallback, unsigned int uiPriority) : DefaultCodes(rDefCodes), Scope(Scope), Name(), uiPriority(uiPriority), iRef(0), is_down(false) { // ctor for default key Name.Copy(szName); if (pCallback) { pCallback->Ref(); vecCallbacks.push_back(pCallback); pCallback->pOriginalKey = this; } } C4CustomKey::C4CustomKey(const C4CustomKey &rCpy, bool fCopyCallbacks) : Codes(rCpy.Codes), DefaultCodes(rCpy.DefaultCodes), Scope(rCpy.Scope), Name(), uiPriority(rCpy.uiPriority), iRef(0) { Name.Copy(rCpy.GetName()); if (fCopyCallbacks) { for (CBVec::const_iterator i = rCpy.vecCallbacks.begin(); i != rCpy.vecCallbacks.end(); ++i) { (*i)->Ref(); vecCallbacks.push_back(*i); } } } C4CustomKey::~C4CustomKey() { // free callback handles for (CBVec::const_iterator i = vecCallbacks.begin(); i != vecCallbacks.end(); ++i) (*i)->Deref(); } void C4CustomKey::Update(const C4CustomKey *pByKey) { assert(pByKey); assert(Name == pByKey->Name); // transfer any assigned data, except name which should be equal anyway if (pByKey->DefaultCodes.size()) DefaultCodes = pByKey->DefaultCodes; if (pByKey->Codes.size()) Codes = pByKey->Codes; if (pByKey->Scope != KEYSCOPE_None) Scope = pByKey->Scope; if (pByKey->uiPriority != PRIO_None) uiPriority = pByKey->uiPriority; for (CBVec::const_iterator i = pByKey->vecCallbacks.begin(); i != pByKey->vecCallbacks.end(); ++i) { (*i)->Ref(); vecCallbacks.push_back(*i); } } bool C4KeyboardCallbackInterfaceHasOriginalKey(C4KeyboardCallbackInterface *pIntfc, const C4CustomKey *pCheckKey) { return pIntfc->IsOriginalKey(pCheckKey); } void C4CustomKey::KillCallbacks(const C4CustomKey *pOfKey) { // remove all instances from list CBVec::iterator i; while ((i = std::find_if(vecCallbacks.begin(), vecCallbacks.end(), std::bind2nd(std::ptr_fun(&C4KeyboardCallbackInterfaceHasOriginalKey), pOfKey))) != vecCallbacks.end()) { C4KeyboardCallbackInterface *pItfc = *i; vecCallbacks.erase(i); pItfc->Deref(); } } void C4CustomKey::CompileFunc(StdCompiler *pComp) { pComp->Value(mkNamingAdapt(mkSTLContainerAdapt(Codes), Name.getData(), DefaultCodes)); } bool C4CustomKey::Execute(C4KeyEventType eEv, C4KeyCodeEx key) { // remember down-state is_down = (eEv == KEYEV_Down); // execute all callbacks for (CBVec::iterator i = vecCallbacks.begin(); i != vecCallbacks.end(); ++i) if ((*i)->OnKeyEvent(key, eEv)) return true; // no event processed it return false; } /* ----------------- C4KeyBinding ------------------ */ C4KeyBinding::C4KeyBinding(const C4KeyCodeEx &DefCode, const char *szName, C4KeyScope Scope, C4KeyboardCallbackInterface *pCallback, unsigned int uiPriority) : C4CustomKey(DefCode, szName, Scope, pCallback, uiPriority) { // self holds a ref Ref(); // register into keyboard input class C4KeyboardInput_Init().RegisterKey(this); } C4KeyBinding::C4KeyBinding(const CodeList &rDefCodes, const char *szName, C4KeyScope Scope, C4KeyboardCallbackInterface *pCallback, unsigned int uiPriority) : C4CustomKey(rDefCodes, szName, Scope, pCallback, uiPriority) { // self holds a ref Ref(); // register into keyboard input class C4KeyboardInput_Init().RegisterKey(this); } C4KeyBinding::~C4KeyBinding() { // deregister from keyboard input class, if that class still exists if (C4KeyboardInput::IsValid) Game.KeyboardInput.UnregisterKeyBinding(this); // shouldn't be refed now assert(iRef==1); iRef = 0; } /* ----------------- C4KeyboardInput ------------------ */ bool C4KeyboardInput::IsValid = false; void C4KeyboardInput::Clear() { LastKeyExtraData = C4KeyEventData(); // release all keys - name map is guarantueed to contain them all for (KeyNameMap::const_iterator i = KeysByName.begin(); i != KeysByName.end(); ++i) i->second->Deref(); // clear maps KeysByCode.clear(); KeysByName.clear(); } void C4KeyboardInput::UpdateKeyCodes(C4CustomKey *pKey, const C4CustomKey::CodeList &rOldCodes, const C4CustomKey::CodeList &rNewCodes) { // new key codes must be the new current key codes assert(pKey->GetCodes() == rNewCodes); // kill from old list C4CustomKey::CodeList::const_iterator iCode; for (iCode = rOldCodes.begin(); iCode != rOldCodes.end(); ++iCode) { // no need to kill if code stayed if (std::find(rNewCodes.begin(), rNewCodes.end(), *iCode) != rNewCodes.end()) continue; std::pair KeyRange = KeysByCode.equal_range((*iCode).Key); for (KeyCodeMap::iterator i = KeyRange.first; i != KeyRange.second; ++i) if (i->second == pKey) { KeysByCode.erase(i); break; } } // readd new codes for (iCode = rNewCodes.begin(); iCode != rNewCodes.end(); ++iCode) { // no double-add if it was in old list already if (std::find(rOldCodes.begin(), rOldCodes.end(), *iCode) != rOldCodes.end()) continue; KeysByCode.insert(std::make_pair((*iCode).Key, pKey)); } } void C4KeyboardInput::RegisterKey(C4CustomKey *pRegKey) { assert(pRegKey); if (!pRegKey) return; // key will be added: ref it pRegKey->Ref(); // search key of same name first C4CustomKey *pDupKey = KeysByName[pRegKey->GetName().getData()]; if (pDupKey) { // key of this name exists: Merge them (old codes copied cuz they'll be overwritten) C4CustomKey::CodeList OldCodes = pDupKey->GetCodes(); const C4CustomKey::CodeList &rNewCodes = pRegKey->GetCodes(); pDupKey->Update(pRegKey); // update access map if key changed if (!(OldCodes == rNewCodes)) UpdateKeyCodes(pDupKey, OldCodes, rNewCodes); // key to be registered no longer used pRegKey->Deref(); } else { // new unique key: Insert into map KeysByName[pRegKey->GetName().getData()] = pRegKey; for (C4CustomKey::CodeList::const_iterator i = pRegKey->GetCodes().begin(); i != pRegKey->GetCodes().end(); ++i) { KeysByCode.insert(std::make_pair((*i).Key, pRegKey)); } } } void C4KeyboardInput::UnregisterKey(const StdStrBuf &rsName) { // kill from name map KeyNameMap::iterator in = KeysByName.find(rsName.getData()); if (in == KeysByName.end()) return; C4CustomKey *pKey = in->second; KeysByName.erase(in); // kill all key bindings from key map for (C4CustomKey::CodeList::const_iterator iCode = pKey->GetCodes().begin(); iCode != pKey->GetCodes().end(); ++iCode) { std::pair KeyRange = KeysByCode.equal_range((*iCode).Key); for (KeyCodeMap::iterator i = KeyRange.first; i != KeyRange.second; ++i) if (i->second == pKey) { KeysByCode.erase(i); break; } } // release reference to key pKey->Deref(); } void C4KeyboardInput::UnregisterKeyBinding(C4CustomKey *pUnregKey) { // find key in name map KeyNameMap::iterator in = KeysByName.find(pUnregKey->GetName().getData()); if (in == KeysByName.end()) return; C4CustomKey *pKey = in->second; // is this key in the map? if (pKey != pUnregKey) { // Other key is in the list: Just remove the callbacks pKey->KillCallbacks(pUnregKey); return; } // this key is in the list: Replace by a duplicate... C4CustomKey *pNewKey = new C4CustomKey(*pUnregKey, true); // ...without the own callbacks pNewKey->KillCallbacks(pUnregKey); // and replace current key by duplicate UnregisterKey(pUnregKey->GetName()); RegisterKey(pNewKey); } bool C4KeyboardInput::DoInput(const C4KeyCodeEx &InKey, C4KeyEventType InEvent, DWORD InScope, int32_t iStrength) { // store last-key-info LastKeyExtraData.iStrength = (iStrength >= 0) ? iStrength : ((InEvent != KEYEV_Up) * 100); LastKeyExtraData.game_x = LastKeyExtraData.game_y = LastKeyExtraData.vp_x = LastKeyExtraData.vp_y = C4KeyEventData::KeyPos_None; // check all key events generated by this key: First the keycode itself, then any more generic key events like KEY_Any const int32_t iKeyRangeMax = 5; int32_t iKeyRangeCnt=0, j; C4KeyCode FallbackKeys[iKeyRangeMax]; FallbackKeys[iKeyRangeCnt++] = InKey.Key; if (Key_IsGamepadButton(InKey.Key)) { uint8_t byGamepad = Key_GetGamepad(InKey.Key); uint8_t byBtnIndex = Key_GetGamepadButtonIndex(InKey.Key); // 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 if (byBtnIndex % 2) FallbackKeys[iKeyRangeCnt++] = KEY_Gamepad(byGamepad, KEY_JOY_AnyEvenButton); else FallbackKeys[iKeyRangeCnt++] = KEY_Gamepad(byGamepad, KEY_JOY_AnyOddButton); // high/low button events if (byBtnIndex < 4) FallbackKeys[iKeyRangeCnt++] = KEY_Gamepad(byGamepad, KEY_JOY_AnyLowButton); else FallbackKeys[iKeyRangeCnt++] = KEY_Gamepad(byGamepad, KEY_JOY_AnyHighButton); // "any gamepad button"-event FallbackKeys[iKeyRangeCnt++] = KEY_Gamepad(byGamepad, KEY_JOY_AnyButton); } else if (Key_IsGamepadAxis(InKey.Key)) { // xy-axis-events for all even/odd axises uint8_t byGamepad = Key_GetGamepad(InKey.Key); uint8_t byAxis = Key_GetGamepadAxisIndex(InKey.Key); bool fHigh = Key_IsGamepadAxisHigh(InKey.Key); C4KeyCode keyAxisDir; if (byAxis % 2) if (fHigh) keyAxisDir = KEY_JOY_Down; else keyAxisDir = KEY_JOY_Up; else if (fHigh) keyAxisDir = KEY_JOY_Right; else keyAxisDir = KEY_JOY_Left; FallbackKeys[iKeyRangeCnt++] = KEY_Gamepad(byGamepad, (uint8_t)keyAxisDir); } if (InKey.Key != KEY_Any) FallbackKeys[iKeyRangeCnt++] = KEY_Any; // now get key ranges for fallback chain std::pair KeyRanges[iKeyRangeMax]; assert(iKeyRangeCnt <= iKeyRangeMax); for (int32_t i = 0; isecond->GetPriority(); if (uiCurr > uiExecPrio && uiCurr < uiLastPrio) uiExecPrio = uiCurr; } // nothing with correct priority set left? if (uiExecPrio == C4CustomKey::PRIO_None) break; // exec all of this priority for (j = 0; j < iKeyRangeCnt; ++j) for (i = KeyRanges[j].first; i != KeyRanges[j].second; ++i) { C4CustomKey *pKey = i->second; assert(pKey); // check priority if (pKey->GetPriority() == uiExecPrio) // check scope and modifier // (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!) if ((InEvent == KEYEV_Up && pKey->IsDown()) || ((pKey->GetScope() & InScope) && pKey->IsCodeMatched(C4KeyCodeEx(FallbackKeys[j], C4KeyShiftState(InKey.dwShift))))) // exec it if (pKey->Execute(InEvent, InKey)) return true; } // nothing found in this priority: exec next uiLastPrio = uiExecPrio; } // no key matched or all returned false in Execute: Not processed return false; } void C4KeyboardInput::CompileFunc(StdCompiler *pComp) { // compile all keys that are already defined // no definition of new keys with current compiler... pComp->Name("Keys"); try { for (KeyNameMap::const_iterator i = KeysByName.begin(); i != KeysByName.end(); ++i) { // naming done in C4CustomKey, because default is determined by key only C4CustomKey::CodeList OldCodes = i->second->GetCodes(); pComp->Value(*i->second); // resort in secondary map if key changed if (pComp->isCompiler()) { const C4CustomKey::CodeList &rNewCodes = i->second->GetCodes(); if (!(OldCodes == rNewCodes)) UpdateKeyCodes(i->second, OldCodes, rNewCodes); } } } catch (StdCompiler::Exception *pEx) { pComp->NameEnd(true); throw pEx; } pComp->NameEnd(); } bool C4KeyboardInput::LoadCustomConfig() { // load from INI file (2do: load from registry) C4Group GrpExtra; if (!GrpExtra.Open(C4CFN_Extra)) return false; StdBuf sFileContents; if (!GrpExtra.LoadEntry(C4CFN_KeyConfig, &sFileContents)) return false; StdStrBuf sFileContentsString((const char *) sFileContents.getData()); if (!CompileFromBuf_LogWarn(*this, sFileContentsString, "Custom keys from" C4CFN_Extra DirSep C4CFN_KeyConfig)) return false; LogF(LoadResStr("IDS_PRC_LOADEDKEYCONF"), C4CFN_Extra DirSep C4CFN_KeyConfig); return true; } C4CustomKey *C4KeyboardInput::GetKeyByName(const char *szKeyName) { KeyNameMap::const_iterator i = KeysByName.find(szKeyName); if (i == KeysByName.end()) return NULL; else return (*i).second; } StdStrBuf C4KeyboardInput::GetKeyCodeNameByKeyName(const char *szKeyName, bool fShort, int32_t iIndex) { C4CustomKey *pKey = GetKeyByName(szKeyName); if (pKey) { const C4CustomKey::CodeList &codes = pKey->GetCodes(); if ((size_t)iIndex < codes.size()) { C4KeyCodeEx code = codes[iIndex]; return code.ToString(true, fShort); } } // Error return StdStrBuf(); } C4KeyboardInput &C4KeyboardInput_Init() { static C4KeyboardInput keyinp; return keyinp; }