Fix unintented newline in rP20809.
[0ad.git] / source / ps / Hotkey.cpp
blobdd6bf4db3ce36a053acc6dded34af8bb119f159a
1 /* Copyright (C) 2017 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 "ps/CConsole.h"
24 #include "ps/CLogger.h"
25 #include "ps/CStr.h"
26 #include "ps/ConfigDB.h"
27 #include "ps/Globals.h"
28 #include "ps/KeyName.h"
30 static bool unified[UNIFIED_LAST - UNIFIED_SHIFT];
32 struct SKey
34 SDL_Keycode code; // keycode or MOUSE_ or UNIFIED_ value
35 bool negated; // whether the key must be pressed (false) or unpressed (true)
38 // Hotkey data associated with an externally-specified 'primary' keycode
39 struct SHotkeyMapping
41 CStr name; // name of the hotkey
42 bool negated; // whether the primary key must be pressed (false) or unpressed (true)
43 std::vector<SKey> requires; // list of non-primary keys that must also be active
46 typedef std::vector<SHotkeyMapping> KeyMapping;
48 // A mapping of keycodes onto the hotkeys that are associated with that key.
49 // (A hotkey triggered by a combination of multiple keys will be in this map
50 // multiple times.)
51 static std::map<int, KeyMapping> g_HotkeyMap;
53 // The current pressed status of hotkeys
54 std::map<std::string, bool> g_HotkeyStatus;
56 // Look up each key binding in the config file and set the mappings for
57 // all key combinations that trigger it.
58 static void LoadConfigBindings()
60 for (const std::pair<CStr, CConfigValueSet>& configPair : g_ConfigDB.GetValuesWithPrefix(CFG_COMMAND, "hotkey."))
62 std::string hotkeyName = configPair.first.substr(7); // strip the "hotkey." prefix
63 for (const CStr& hotkey : configPair.second)
65 if (hotkey.LowerCase() == "unused")
66 continue;
68 std::vector<SKey> keyCombination;
70 // Iterate through multiple-key bindings (e.g. Ctrl+I)
71 boost::char_separator<char> sep("+");
72 typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
73 tokenizer tok(hotkey, sep);
74 for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it)
76 // Attempt decode as key name
77 int mapping = FindKeyCode(*it);
78 if (!mapping)
79 mapping = SDL_GetKeyFromName(it->c_str());
80 if (!mapping)
82 LOGWARNING("Hotkey mapping used invalid key '%s'", hotkey.c_str());
83 continue;
86 SKey key = { (SDL_Keycode)mapping, false };
87 keyCombination.push_back(key);
90 std::vector<SKey>::iterator itKey, itKey2;
91 for (itKey = keyCombination.begin(); itKey != keyCombination.end(); ++itKey)
93 SHotkeyMapping bindCode;
95 bindCode.name = hotkeyName;
96 bindCode.negated = itKey->negated;
98 for (itKey2 = keyCombination.begin(); itKey2 != keyCombination.end(); ++itKey2)
99 if (itKey != itKey2) // Push any auxiliary keys
100 bindCode.requires.push_back(*itKey2);
102 g_HotkeyMap[itKey->code].push_back(bindCode);
108 void LoadHotkeys()
110 InitKeyNameMap();
112 LoadConfigBindings();
114 // Set up the state of the hotkeys given no key is down.
115 // i.e. find those hotkeys triggered by all negations.
117 for (const std::pair<int, KeyMapping>& p : g_HotkeyMap)
118 for (const SHotkeyMapping& hotkey : p.second)
120 if (!hotkey.negated)
121 continue;
123 bool allNegated = true;
125 for (const SKey& k : hotkey.requires)
126 if (!k.negated)
127 allNegated = false;
129 if (allNegated)
130 g_HotkeyStatus[hotkey.name] = true;
134 void UnloadHotkeys()
136 g_HotkeyMap.clear();
137 g_HotkeyStatus.clear();
140 bool isNegated(const SKey& key)
142 // Normal keycodes are below EXTRA_KEYS_BASE
143 if ((int)key.code < EXTRA_KEYS_BASE && g_keys[key.code] == key.negated)
144 return false;
145 // Mouse 'keycodes' are after the modifier keys
146 else if ((int)key.code < MOUSE_LAST && (int)key.code > MOUSE_BASE && g_mouse_buttons[key.code - MOUSE_BASE] == key.negated)
147 return false;
148 // Modifier keycodes are between the normal keys and the mouse 'keys'
149 else if ((int)key.code < UNIFIED_LAST && (int)key.code > SDL_SCANCODE_TO_KEYCODE(SDL_NUM_SCANCODES) && unified[key.code - UNIFIED_SHIFT] == key.negated)
150 return false;
151 else
152 return true;
155 InReaction HotkeyInputHandler(const SDL_Event_* ev)
157 int keycode = 0;
159 switch(ev->ev.type)
161 case SDL_KEYDOWN:
162 case SDL_KEYUP:
163 keycode = (int)ev->ev.key.keysym.sym;
164 break;
166 case SDL_MOUSEBUTTONDOWN:
167 case SDL_MOUSEBUTTONUP:
168 // Mousewheel events are no longer buttons, but we want to maintain the order
169 // expected by g_mouse_buttons for compatibility
170 if (ev->ev.button.button >= SDL_BUTTON_X1)
171 keycode = MOUSE_BASE + (int)ev->ev.button.button + 2;
172 else
173 keycode = MOUSE_BASE + (int)ev->ev.button.button;
174 break;
176 case SDL_MOUSEWHEEL:
177 if (ev->ev.wheel.y > 0)
179 keycode = MOUSE_WHEELUP;
180 break;
182 else if (ev->ev.wheel.y < 0)
184 keycode = MOUSE_WHEELDOWN;
185 break;
187 else if (ev->ev.wheel.x > 0)
189 keycode = MOUSE_X2;
190 break;
192 else if (ev->ev.wheel.x < 0)
194 keycode = MOUSE_X1;
195 break;
197 return IN_PASS;
199 case SDL_HOTKEYDOWN:
200 g_HotkeyStatus[static_cast<const char*>(ev->ev.user.data1)] = true;
201 return IN_PASS;
203 case SDL_HOTKEYUP:
204 g_HotkeyStatus[static_cast<const char*>(ev->ev.user.data1)] = false;
205 return IN_PASS;
207 default:
208 return IN_PASS;
211 // Somewhat hackish:
212 // Create phantom 'unified-modifier' events when left- or right- modifier keys are pressed
213 // Just send them to this handler; don't let the imaginary event codes leak back to real SDL.
215 SDL_Event_ phantom;
216 phantom.ev.type = ((ev->ev.type == SDL_KEYDOWN) || (ev->ev.type == SDL_MOUSEBUTTONDOWN)) ? SDL_KEYDOWN : SDL_KEYUP;
217 if ((keycode == SDLK_LSHIFT) || (keycode == SDLK_RSHIFT))
219 phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_SHIFT;
220 unified[0] = (phantom.ev.type == SDL_KEYDOWN);
221 HotkeyInputHandler(&phantom);
223 else if ((keycode == SDLK_LCTRL) || (keycode == SDLK_RCTRL))
225 phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_CTRL;
226 unified[1] = (phantom.ev.type == SDL_KEYDOWN);
227 HotkeyInputHandler(&phantom);
229 else if ((keycode == SDLK_LALT) || (keycode == SDLK_RALT))
231 phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_ALT;
232 unified[2] = (phantom.ev.type == SDL_KEYDOWN);
233 HotkeyInputHandler(&phantom);
235 else if ((keycode == SDLK_LGUI) || (keycode == SDLK_RGUI))
237 phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_SUPER;
238 unified[3] = (phantom.ev.type == SDL_KEYDOWN);
239 HotkeyInputHandler(&phantom);
242 // Check whether we have any hotkeys registered for this particular keycode
243 if (g_HotkeyMap.find(keycode) == g_HotkeyMap.end())
244 return (IN_PASS);
246 // Inhibit the dispatch of hotkey events caused by real keys (not fake mouse button
247 // events) while the console is up.
249 bool consoleCapture = false;
251 if (g_Console->IsActive() && keycode < SDL_SCANCODE_TO_KEYCODE(SDL_NUM_SCANCODES))
252 consoleCapture = true;
254 // Here's an interesting bit:
255 // If you have an event bound to, say, 'F', and another to, say, 'Ctrl+F', pressing
256 // 'F' while control is down would normally fire off both.
258 // To avoid this, set the modifier keys for /all/ events this key would trigger
259 // (Ctrl, for example, is both group-save and bookmark-save)
260 // but only send a HotkeyDown event for the event with bindings most precisely
261 // matching the conditions (i.e. the event with the highest number of auxiliary
262 // keys, providing they're all down)
264 bool typeKeyDown = ( ev->ev.type == SDL_KEYDOWN ) || ( ev->ev.type == SDL_MOUSEBUTTONDOWN ) || (ev->ev.type == SDL_MOUSEWHEEL);
266 // -- KEYDOWN SECTION --
268 std::vector<const char*> closestMapNames;
269 size_t closestMapMatch = 0;
271 for (const SHotkeyMapping& hotkey : g_HotkeyMap[keycode])
273 // If a key has been pressed, and this event triggers on its release, skip it.
274 // Similarly, if the key's been released and the event triggers on a keypress, skip it.
275 if (hotkey.negated == typeKeyDown)
276 continue;
278 // Check for no unpermitted keys
279 bool accept = true;
280 for (const SKey& k : hotkey.requires)
282 accept = isNegated(k);
283 if (!accept)
284 break;
287 if (accept && !(consoleCapture && hotkey.name != "console.toggle"))
289 // Check if this is an equally precise or more precise match
290 if (hotkey.requires.size() + 1 >= closestMapMatch)
292 // Check if more precise
293 if (hotkey.requires.size() + 1 > closestMapMatch)
295 // Throw away the old less-precise matches
296 closestMapNames.clear();
297 closestMapMatch = hotkey.requires.size() + 1;
300 closestMapNames.push_back(hotkey.name.c_str());
305 for (size_t i = 0; i < closestMapNames.size(); ++i)
307 SDL_Event_ hotkeyNotification;
308 hotkeyNotification.ev.type = SDL_HOTKEYDOWN;
309 hotkeyNotification.ev.user.data1 = const_cast<char*>(closestMapNames[i]);
310 in_push_priority_event(&hotkeyNotification);
313 // -- KEYUP SECTION --
315 for (const SHotkeyMapping& hotkey : g_HotkeyMap[keycode])
317 // If it's a keydown event, won't cause HotKeyUps in anything that doesn't
318 // use this key negated => skip them
319 // If it's a keyup event, won't cause HotKeyUps in anything that does use
320 // this key negated => skip them too.
321 if (hotkey.negated != typeKeyDown)
322 continue;
324 // Check for no unpermitted keys
325 bool accept = true;
326 for (const SKey& k : hotkey.requires)
328 accept = isNegated(k);
329 if (!accept)
330 break;
333 if (accept)
335 SDL_Event_ hotkeyNotification;
336 hotkeyNotification.ev.type = SDL_HOTKEYUP;
337 hotkeyNotification.ev.user.data1 = const_cast<char*>(hotkey.name.c_str());
338 in_push_priority_event(&hotkeyNotification);
342 return IN_PASS;
345 bool HotkeyIsPressed(const CStr& keyname)
347 return g_HotkeyStatus[keyname];