Index: ps/trunk/source/ps/Globals.cpp =================================================================== --- ps/trunk/source/ps/Globals.cpp (revision 19612) +++ ps/trunk/source/ps/Globals.cpp (revision 19613) @@ -1,98 +1,96 @@ -/* Copyright (C) 2014 Wildfire Games. +/* Copyright (C) 2017 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "Globals.h" -#include "lib/external_libraries/libsdl.h" #include "network/NetClient.h" #include "ps/GameSetup/Config.h" #include "soundmanager/ISoundManager.h" bool g_app_minimized = false; bool g_app_has_focus = true; std::map g_keys; int g_mouse_x = 50, g_mouse_y = 50; bool g_mouse_active = true; -// unused, left, right, middle, wheel up, wheel down -// (order is given by SDL_BUTTON_* constants). -bool g_mouse_buttons[6] = {0}; +// g_mouse_buttons[0] is unused. The order of entries is as in KeyName.h for MOUSE_* +bool g_mouse_buttons[MOUSE_LAST - MOUSE_BASE] = {0}; PIFrequencyFilter g_frequencyFilter; // updates the state of the above; never swallows messages. InReaction GlobalsInputHandler(const SDL_Event_* ev) { size_t c; switch(ev->ev.type) { case SDL_WINDOWEVENT: switch(ev->ev.window.event) { case SDL_WINDOWEVENT_MINIMIZED: g_app_minimized = true; break; case SDL_WINDOWEVENT_EXPOSED: case SDL_WINDOWEVENT_RESTORED: g_app_minimized = false; break; case SDL_WINDOWEVENT_FOCUS_GAINED: g_app_has_focus = true; break; case SDL_WINDOWEVENT_FOCUS_LOST: g_app_has_focus = false; break; case SDL_WINDOWEVENT_ENTER: g_mouse_active = true; break; case SDL_WINDOWEVENT_LEAVE: g_mouse_active = false; break; } return IN_PASS; case SDL_MOUSEMOTION: g_mouse_x = ev->ev.motion.x; g_mouse_y = ev->ev.motion.y; return IN_PASS; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: c = ev->ev.button.button; if(c < ARRAY_SIZE(g_mouse_buttons)) g_mouse_buttons[c] = (ev->ev.type == SDL_MOUSEBUTTONDOWN); else { // don't complain: just ignore people with too many mouse buttons //debug_warn(L"invalid mouse button"); } return IN_PASS; case SDL_KEYDOWN: case SDL_KEYUP: g_keys[ev->ev.key.keysym.sym] = (ev->ev.type == SDL_KEYDOWN); return IN_PASS; default: return IN_PASS; } UNREACHABLE; } Index: ps/trunk/source/ps/Globals.h =================================================================== --- ps/trunk/source/ps/Globals.h (revision 19612) +++ ps/trunk/source/ps/Globals.h (revision 19613) @@ -1,63 +1,63 @@ -/* Copyright (C) 2012 Wildfire Games. +/* Copyright (C) 2017 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_PS_GLOBALS #define INCLUDED_PS_GLOBALS #include "lib/input.h" #include "lib/frequency_filter.h" +#include "ps/KeyName.h" #include // thin abstraction layer on top of SDL. // game code should use it instead of SDL_GetMouseState etc. because // Atlas does not completely emulate SDL (it can only send events). extern bool g_app_minimized; extern bool g_app_has_focus; extern int g_mouse_x, g_mouse_y; /** * Indicates whether the mouse is focused on the game window (mouse positions * should usually be considered inaccurate if this is false) */ extern bool g_mouse_active; /** * g_keys: Key states, indexed by SDLK* constants. If an entry is true, * it represents a pressed key. * Updated by GlobalsInputHandler in response to key press/release events. */ extern std::map g_keys; /** * g_mouse_buttons: Mouse buttons states, indexed by SDL_BUTTON_* constants. * If an entry is true, it represents a pressed button. * Updated by GlobalsInputHandler in response to mouse button up/down events. * * Be aware that SDL_BUTTON_* constants start at 1. Therefore, - * g_mouse_buttons[0] is unused. The order of entries is: - * { unused, left, right, middle, wheel up, wheel down } + * g_mouse_buttons[0] is unused. The order of entries is as in KeyName.h for MOUSE_* */ -extern bool g_mouse_buttons[6]; +extern bool g_mouse_buttons[MOUSE_LAST - MOUSE_BASE]; extern InReaction GlobalsInputHandler(const SDL_Event_* ev); extern PIFrequencyFilter g_frequencyFilter; #endif // INCLUDED_PS_GLOBALS Index: ps/trunk/source/ps/Hotkey.cpp =================================================================== --- ps/trunk/source/ps/Hotkey.cpp (revision 19612) +++ ps/trunk/source/ps/Hotkey.cpp (revision 19613) @@ -1,347 +1,346 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2017 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "Hotkey.h" #include -#include "lib/input.h" #include "ps/CConsole.h" #include "ps/CLogger.h" #include "ps/CStr.h" #include "ps/ConfigDB.h" #include "ps/Globals.h" #include "ps/KeyName.h" static bool unified[UNIFIED_LAST - UNIFIED_SHIFT]; struct SKey { SDL_Keycode code; // keycode or MOUSE_ or UNIFIED_ value bool negated; // whether the key must be pressed (false) or unpressed (true) }; // Hotkey data associated with an externally-specified 'primary' keycode struct SHotkeyMapping { CStr name; // name of the hotkey bool negated; // whether the primary key must be pressed (false) or unpressed (true) std::vector requires; // list of non-primary keys that must also be active }; typedef std::vector KeyMapping; // A mapping of keycodes onto the hotkeys that are associated with that key. // (A hotkey triggered by a combination of multiple keys will be in this map // multiple times.) static std::map g_HotkeyMap; // The current pressed status of hotkeys std::map g_HotkeyStatus; // Look up each key binding in the config file and set the mappings for // all key combinations that trigger it. static void LoadConfigBindings() { for (const std::pair& configPair : g_ConfigDB.GetValuesWithPrefix(CFG_COMMAND, "hotkey.")) { std::string hotkeyName = configPair.first.substr(7); // strip the "hotkey." prefix for (const CStr& hotkey : configPair.second) { if (hotkey.LowerCase() == "unused") continue; std::vector keyCombination; // Iterate through multiple-key bindings (e.g. Ctrl+I) boost::char_separator sep("+"); typedef boost::tokenizer > tokenizer; tokenizer tok(hotkey, sep); for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it) { // Attempt decode as key name int mapping = FindKeyCode(*it); if (!mapping) { LOGWARNING("Hotkey mapping used invalid key '%s'", hotkey.c_str()); continue; } SKey key = { (SDL_Keycode)mapping, false }; keyCombination.push_back(key); } std::vector::iterator itKey, itKey2; for (itKey = keyCombination.begin(); itKey != keyCombination.end(); ++itKey) { SHotkeyMapping bindCode; bindCode.name = hotkeyName; bindCode.negated = itKey->negated; for (itKey2 = keyCombination.begin(); itKey2 != keyCombination.end(); ++itKey2) if (itKey != itKey2) // Push any auxiliary keys bindCode.requires.push_back(*itKey2); g_HotkeyMap[itKey->code].push_back(bindCode); } } } } void LoadHotkeys() { InitKeyNameMap(); LoadConfigBindings(); // Set up the state of the hotkeys given no key is down. // i.e. find those hotkeys triggered by all negations. for (const std::pair& p : g_HotkeyMap) for (const SHotkeyMapping& hotkey : p.second) { if (!hotkey.negated) continue; bool allNegated = true; for (const SKey& k : hotkey.requires) if (!k.negated) allNegated = false; if (allNegated) g_HotkeyStatus[hotkey.name] = true; } } void UnloadHotkeys() { g_HotkeyMap.clear(); g_HotkeyStatus.clear(); } bool isNegated(const SKey& key) { // Normal keycodes are below EXTRA_KEYS_BASE if ((int)key.code < EXTRA_KEYS_BASE && g_keys[key.code] == key.negated) return false; // Mouse 'keycodes' are after the modifier keys - else if ((int)key.code > UNIFIED_LAST && g_mouse_buttons[key.code - UNIFIED_LAST] == key.negated) + else if ((int)key.code < MOUSE_LAST && (int)key.code > MOUSE_BASE && g_mouse_buttons[key.code - MOUSE_BASE] == key.negated) return false; // Modifier keycodes are between the normal keys and the mouse 'keys' else if ((int)key.code < UNIFIED_LAST && (int)key.code > SDL_SCANCODE_TO_KEYCODE(SDL_NUM_SCANCODES) && unified[key.code - UNIFIED_SHIFT] == key.negated) return false; else return true; } InReaction HotkeyInputHandler(const SDL_Event_* ev) { int keycode = 0; switch(ev->ev.type) { case SDL_KEYDOWN: case SDL_KEYUP: keycode = (int)ev->ev.key.keysym.sym; break; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: // Mousewheel events are no longer buttons, but we want to maintain the order // expected by g_mouse_buttons for compatibility if (ev->ev.button.button >= SDL_BUTTON_X1) keycode = MOUSE_BASE + (int)ev->ev.button.button + 2; else keycode = MOUSE_BASE + (int)ev->ev.button.button; break; case SDL_MOUSEWHEEL: if (ev->ev.wheel.y > 0) { keycode = MOUSE_WHEELUP; break; } else if (ev->ev.wheel.y < 0) { keycode = MOUSE_WHEELDOWN; break; } else if (ev->ev.wheel.x > 0) { keycode = MOUSE_X2; break; } else if (ev->ev.wheel.x < 0) { keycode = MOUSE_X1; break; } return IN_PASS; case SDL_HOTKEYDOWN: g_HotkeyStatus[static_cast(ev->ev.user.data1)] = true; return IN_PASS; case SDL_HOTKEYUP: g_HotkeyStatus[static_cast(ev->ev.user.data1)] = false; return IN_PASS; default: return IN_PASS; } // Somewhat hackish: // Create phantom 'unified-modifier' events when left- or right- modifier keys are pressed // Just send them to this handler; don't let the imaginary event codes leak back to real SDL. SDL_Event_ phantom; phantom.ev.type = ((ev->ev.type == SDL_KEYDOWN) || (ev->ev.type == SDL_MOUSEBUTTONDOWN)) ? SDL_KEYDOWN : SDL_KEYUP; if ((keycode == SDLK_LSHIFT) || (keycode == SDLK_RSHIFT)) { phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_SHIFT; unified[0] = (phantom.ev.type == SDL_KEYDOWN); HotkeyInputHandler(&phantom); } else if ((keycode == SDLK_LCTRL) || (keycode == SDLK_RCTRL)) { phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_CTRL; unified[1] = (phantom.ev.type == SDL_KEYDOWN); HotkeyInputHandler(&phantom); } else if ((keycode == SDLK_LALT) || (keycode == SDLK_RALT)) { phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_ALT; unified[2] = (phantom.ev.type == SDL_KEYDOWN); HotkeyInputHandler(&phantom); } else if ((keycode == SDLK_LGUI) || (keycode == SDLK_RGUI)) { phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_SUPER; unified[3] = (phantom.ev.type == SDL_KEYDOWN); HotkeyInputHandler(&phantom); } // Check whether we have any hotkeys registered for this particular keycode if (g_HotkeyMap.find(keycode) == g_HotkeyMap.end()) return (IN_PASS); // Inhibit the dispatch of hotkey events caused by real keys (not fake mouse button // events) while the console is up. bool consoleCapture = false; if (g_Console->IsActive() && keycode < SDL_SCANCODE_TO_KEYCODE(SDL_NUM_SCANCODES)) consoleCapture = true; // Here's an interesting bit: // If you have an event bound to, say, 'F', and another to, say, 'Ctrl+F', pressing // 'F' while control is down would normally fire off both. // To avoid this, set the modifier keys for /all/ events this key would trigger // (Ctrl, for example, is both group-save and bookmark-save) // but only send a HotkeyDown event for the event with bindings most precisely // matching the conditions (i.e. the event with the highest number of auxiliary // keys, providing they're all down) bool typeKeyDown = ( ev->ev.type == SDL_KEYDOWN ) || ( ev->ev.type == SDL_MOUSEBUTTONDOWN ) || (ev->ev.type == SDL_MOUSEWHEEL); // -- KEYDOWN SECTION -- std::vector closestMapNames; size_t closestMapMatch = 0; for (const SHotkeyMapping& hotkey : g_HotkeyMap[keycode]) { // If a key has been pressed, and this event triggers on its release, skip it. // Similarly, if the key's been released and the event triggers on a keypress, skip it. if (hotkey.negated == typeKeyDown) continue; // Check for no unpermitted keys bool accept = true; for (const SKey& k : hotkey.requires) { accept = isNegated(k); if (!accept) break; } if (accept && !(consoleCapture && hotkey.name != "console.toggle")) { // Check if this is an equally precise or more precise match if (hotkey.requires.size() + 1 >= closestMapMatch) { // Check if more precise if (hotkey.requires.size() + 1 > closestMapMatch) { // Throw away the old less-precise matches closestMapNames.clear(); closestMapMatch = hotkey.requires.size() + 1; } closestMapNames.push_back(hotkey.name.c_str()); } } } for (size_t i = 0; i < closestMapNames.size(); ++i) { SDL_Event_ hotkeyNotification; hotkeyNotification.ev.type = SDL_HOTKEYDOWN; hotkeyNotification.ev.user.data1 = const_cast(closestMapNames[i]); in_push_priority_event(&hotkeyNotification); } // -- KEYUP SECTION -- for (const SHotkeyMapping& hotkey : g_HotkeyMap[keycode]) { // If it's a keydown event, won't cause HotKeyUps in anything that doesn't // use this key negated => skip them // If it's a keyup event, won't cause HotKeyUps in anything that does use // this key negated => skip them too. if (hotkey.negated != typeKeyDown) continue; // Check for no unpermitted keys bool accept = true; for (const SKey& k : hotkey.requires) { accept = isNegated(k); if (!accept) break; } if (accept) { SDL_Event_ hotkeyNotification; hotkeyNotification.ev.type = SDL_HOTKEYUP; hotkeyNotification.ev.user.data1 = const_cast(hotkey.name.c_str()); in_push_priority_event(&hotkeyNotification); } } return IN_PASS; } bool HotkeyIsPressed(const CStr& keyname) { return g_HotkeyStatus[keyname]; } Index: ps/trunk/source/ps/KeyName.h =================================================================== --- ps/trunk/source/ps/KeyName.h (revision 19612) +++ ps/trunk/source/ps/KeyName.h (revision 19613) @@ -1,55 +1,56 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2017 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_KEYNAME #define INCLUDED_KEYNAME // Need SDLK_* enum values. #include "lib/external_libraries/libsdl.h" class CStr8; extern void InitKeyNameMap(); extern CStr8 FindKeyName(int keycode); extern int FindKeyCode(const CStr8& keyname); enum { // Start sequential IDs in the right place // Pick a code which is greater than any keycodes used by SDL itself EXTRA_KEYS_BASE = SDL_SCANCODE_TO_KEYCODE(SDL_NUM_SCANCODES), // 'Keycodes' for the unified modifier keys UNIFIED_SHIFT, UNIFIED_CTRL, UNIFIED_ALT, UNIFIED_SUPER, UNIFIED_LAST, // 'Keycodes' for the mouse buttons // Base for mouse buttons. // Everything less than MOUSE_BASE is not reported by an SDL mouse button event. // Everything greater than MOUSE_BASE is reported by an SDL mouse button event. MOUSE_BASE, MOUSE_LEFT = MOUSE_BASE + SDL_BUTTON_LEFT, MOUSE_MIDDLE = MOUSE_BASE + SDL_BUTTON_MIDDLE, MOUSE_RIGHT = MOUSE_BASE + SDL_BUTTON_RIGHT, // SDL2 doesn't count wheels as buttons, so just give them the previous sequential IDs MOUSE_WHEELUP = MOUSE_BASE + 4, MOUSE_WHEELDOWN = MOUSE_BASE + 5, MOUSE_X1 = MOUSE_BASE + SDL_BUTTON_X1 + 2, MOUSE_X2 = MOUSE_BASE + SDL_BUTTON_X2 + 2, + MOUSE_LAST, }; #endif // #ifndef INCLUDED_KEYNAME