Fix infinite loop detection when placing players randomly on the newer random map...
[0ad.git] / source / ps / Hotkey.cpp
blob0989ebb7fba2eccc353096c10fd8cc53797390c6
1 /* Copyright (C) 2015 Wildfire Games.
2 * This file is part of 0 A.D.
4 * 0 A.D. is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
9 * 0 A.D. is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
18 #include "precompiled.h"
19 #include "Hotkey.h"
21 #include <boost/tokenizer.hpp>
23 #include "lib/input.h"
24 #include "ps/CConsole.h"
25 #include "ps/CLogger.h"
26 #include "ps/CStr.h"
27 #include "ps/ConfigDB.h"
28 #include "ps/Globals.h"
29 #include "ps/KeyName.h"
31 static bool unified[UNIFIED_LAST - UNIFIED_SHIFT];
33 struct SKey
35 SDL_Keycode code; // keycode or MOUSE_ or UNIFIED_ value
36 bool negated; // whether the key must be pressed (false) or unpressed (true)
39 // Hotkey data associated with an externally-specified 'primary' keycode
40 struct SHotkeyMapping
42 CStr name; // name of the hotkey
43 bool negated; // whether the primary key must be pressed (false) or unpressed (true)
44 std::vector<SKey> requires; // list of non-primary keys that must also be active
47 typedef std::vector<SHotkeyMapping> KeyMapping;
49 // A mapping of keycodes onto the hotkeys that are associated with that key.
50 // (A hotkey triggered by a combination of multiple keys will be in this map
51 // multiple times.)
52 static std::map<int, KeyMapping> g_HotkeyMap;
54 // The current pressed status of hotkeys
55 std::map<std::string, bool> g_HotkeyStatus;
57 // Look up each key binding in the config file and set the mappings for
58 // all key combinations that trigger it.
59 static void LoadConfigBindings()
61 for (const std::pair<CStr, CConfigValueSet>& configPair : g_ConfigDB.GetValuesWithPrefix(CFG_COMMAND, "hotkey."))
63 std::string hotkeyName = configPair.first.substr(7); // strip the "hotkey." prefix
64 for (const CStr& hotkey : configPair.second)
66 if (hotkey.LowerCase() == "unused")
67 continue;
69 std::vector<SKey> keyCombination;
71 // Iterate through multiple-key bindings (e.g. Ctrl+I)
72 boost::char_separator<char> sep("+");
73 typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
74 tokenizer tok(hotkey, sep);
75 for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it)
77 // Attempt decode as key name
78 int mapping = FindKeyCode(*it);
79 if (!mapping)
81 LOGWARNING("Hotkey mapping used invalid key '%s'", hotkey.c_str());
82 continue;
85 SKey key = { (SDL_Keycode)mapping, false };
86 keyCombination.push_back(key);
89 std::vector<SKey>::iterator itKey, itKey2;
90 for (itKey = keyCombination.begin(); itKey != keyCombination.end(); ++itKey)
92 SHotkeyMapping bindCode;
94 bindCode.name = hotkeyName;
95 bindCode.negated = itKey->negated;
97 for (itKey2 = keyCombination.begin(); itKey2 != keyCombination.end(); ++itKey2)
98 if (itKey != itKey2) // Push any auxiliary keys
99 bindCode.requires.push_back(*itKey2);
101 g_HotkeyMap[itKey->code].push_back(bindCode);
107 void LoadHotkeys()
109 InitKeyNameMap();
111 LoadConfigBindings();
113 // Set up the state of the hotkeys given no key is down.
114 // i.e. find those hotkeys triggered by all negations.
116 for (const std::pair<int, KeyMapping>& p : g_HotkeyMap)
117 for (const SHotkeyMapping& hotkey : p.second)
119 if (!hotkey.negated)
120 continue;
122 bool allNegated = true;
124 for (const SKey& k : hotkey.requires)
125 if (!k.negated)
126 allNegated = false;
128 if (allNegated)
129 g_HotkeyStatus[hotkey.name] = true;
133 void UnloadHotkeys()
135 g_HotkeyMap.clear();
136 g_HotkeyStatus.clear();
139 bool isNegated(const SKey& key)
141 // Normal keycodes are below EXTRA_KEYS_BASE
142 if ((int)key.code < EXTRA_KEYS_BASE && g_keys[key.code] == key.negated)
143 return false;
144 // Mouse 'keycodes' are after the modifier keys
145 else if ((int)key.code > UNIFIED_LAST && g_mouse_buttons[key.code - UNIFIED_LAST] == key.negated)
146 return false;
147 // Modifier keycodes are between the normal keys and the mouse 'keys'
148 else if ((int)key.code < UNIFIED_LAST && (int)key.code > SDL_SCANCODE_TO_KEYCODE(SDL_NUM_SCANCODES) && unified[key.code - UNIFIED_SHIFT] == key.negated)
149 return false;
150 else
151 return true;
154 InReaction HotkeyInputHandler(const SDL_Event_* ev)
156 int keycode = 0;
158 switch(ev->ev.type)
160 case SDL_KEYDOWN:
161 case SDL_KEYUP:
162 keycode = (int)ev->ev.key.keysym.sym;
163 break;
165 case SDL_MOUSEBUTTONDOWN:
166 case SDL_MOUSEBUTTONUP:
167 // Mousewheel events are no longer buttons, but we want to maintain the order
168 // expected by g_mouse_buttons for compatibility
169 if (ev->ev.button.button >= SDL_BUTTON_X1)
170 keycode = MOUSE_BASE + (int)ev->ev.button.button + 2;
171 else
172 keycode = MOUSE_BASE + (int)ev->ev.button.button;
173 break;
175 case SDL_MOUSEWHEEL:
176 if (ev->ev.wheel.y > 0)
178 keycode = MOUSE_WHEELUP;
179 break;
181 else if (ev->ev.wheel.y < 0)
183 keycode = MOUSE_WHEELDOWN;
184 break;
186 else if (ev->ev.wheel.x > 0)
188 keycode = MOUSE_X2;
189 break;
191 else if (ev->ev.wheel.x < 0)
193 keycode = MOUSE_X1;
194 break;
196 return IN_PASS;
198 case SDL_HOTKEYDOWN:
199 g_HotkeyStatus[static_cast<const char*>(ev->ev.user.data1)] = true;
200 return IN_PASS;
202 case SDL_HOTKEYUP:
203 g_HotkeyStatus[static_cast<const char*>(ev->ev.user.data1)] = false;
204 return IN_PASS;
206 default:
207 return IN_PASS;
210 // Somewhat hackish:
211 // Create phantom 'unified-modifier' events when left- or right- modifier keys are pressed
212 // Just send them to this handler; don't let the imaginary event codes leak back to real SDL.
214 SDL_Event_ phantom;
215 phantom.ev.type = ((ev->ev.type == SDL_KEYDOWN) || (ev->ev.type == SDL_MOUSEBUTTONDOWN)) ? SDL_KEYDOWN : SDL_KEYUP;
216 if ((keycode == SDLK_LSHIFT) || (keycode == SDLK_RSHIFT))
218 phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_SHIFT;
219 unified[0] = (phantom.ev.type == SDL_KEYDOWN);
220 HotkeyInputHandler(&phantom);
222 else if ((keycode == SDLK_LCTRL) || (keycode == SDLK_RCTRL))
224 phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_CTRL;
225 unified[1] = (phantom.ev.type == SDL_KEYDOWN);
226 HotkeyInputHandler(&phantom);
228 else if ((keycode == SDLK_LALT) || (keycode == SDLK_RALT))
230 phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_ALT;
231 unified[2] = (phantom.ev.type == SDL_KEYDOWN);
232 HotkeyInputHandler(&phantom);
234 else if ((keycode == SDLK_LGUI) || (keycode == SDLK_RGUI))
236 phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_SUPER;
237 unified[3] = (phantom.ev.type == SDL_KEYDOWN);
238 HotkeyInputHandler(&phantom);
241 // Check whether we have any hotkeys registered for this particular keycode
242 if (g_HotkeyMap.find(keycode) == g_HotkeyMap.end())
243 return (IN_PASS);
245 // Inhibit the dispatch of hotkey events caused by real keys (not fake mouse button
246 // events) while the console is up.
248 bool consoleCapture = false;
250 if (g_Console->IsActive() && keycode < SDL_SCANCODE_TO_KEYCODE(SDL_NUM_SCANCODES))
251 consoleCapture = true;
253 // Here's an interesting bit:
254 // If you have an event bound to, say, 'F', and another to, say, 'Ctrl+F', pressing
255 // 'F' while control is down would normally fire off both.
257 // To avoid this, set the modifier keys for /all/ events this key would trigger
258 // (Ctrl, for example, is both group-save and bookmark-save)
259 // but only send a HotkeyDown event for the event with bindings most precisely
260 // matching the conditions (i.e. the event with the highest number of auxiliary
261 // keys, providing they're all down)
263 bool typeKeyDown = ( ev->ev.type == SDL_KEYDOWN ) || ( ev->ev.type == SDL_MOUSEBUTTONDOWN ) || (ev->ev.type == SDL_MOUSEWHEEL);
265 // -- KEYDOWN SECTION --
267 std::vector<const char*> closestMapNames;
268 size_t closestMapMatch = 0;
270 for (const SHotkeyMapping& hotkey : g_HotkeyMap[keycode])
272 // If a key has been pressed, and this event triggers on its release, skip it.
273 // Similarly, if the key's been released and the event triggers on a keypress, skip it.
274 if (hotkey.negated == typeKeyDown)
275 continue;
277 // Check for no unpermitted keys
278 bool accept = true;
279 for (const SKey& k : hotkey.requires)
281 accept = isNegated(k);
282 if (!accept)
283 break;
286 if (accept && !(consoleCapture && hotkey.name != "console.toggle"))
288 // Check if this is an equally precise or more precise match
289 if (hotkey.requires.size() + 1 >= closestMapMatch)
291 // Check if more precise
292 if (hotkey.requires.size() + 1 > closestMapMatch)
294 // Throw away the old less-precise matches
295 closestMapNames.clear();
296 closestMapMatch = hotkey.requires.size() + 1;
299 closestMapNames.push_back(hotkey.name.c_str());
304 for (size_t i = 0; i < closestMapNames.size(); ++i)
306 SDL_Event_ hotkeyNotification;
307 hotkeyNotification.ev.type = SDL_HOTKEYDOWN;
308 hotkeyNotification.ev.user.data1 = const_cast<char*>(closestMapNames[i]);
309 in_push_priority_event(&hotkeyNotification);
312 // -- KEYUP SECTION --
314 for (const SHotkeyMapping& hotkey : g_HotkeyMap[keycode])
316 // If it's a keydown event, won't cause HotKeyUps in anything that doesn't
317 // use this key negated => skip them
318 // If it's a keyup event, won't cause HotKeyUps in anything that does use
319 // this key negated => skip them too.
320 if (hotkey.negated != typeKeyDown)
321 continue;
323 // Check for no unpermitted keys
324 bool accept = true;
325 for (const SKey& k : hotkey.requires)
327 accept = isNegated(k);
328 if (!accept)
329 break;
332 if (accept)
334 SDL_Event_ hotkeyNotification;
335 hotkeyNotification.ev.type = SDL_HOTKEYUP;
336 hotkeyNotification.ev.user.data1 = const_cast<char*>(hotkey.name.c_str());
337 in_push_priority_event(&hotkeyNotification);
341 return IN_PASS;
344 bool HotkeyIsPressed(const CStr& keyname)
346 return g_HotkeyStatus[keyname];