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"
21 #include <boost/tokenizer.hpp>
23 #include "ps/CConsole.h"
24 #include "ps/CLogger.h"
26 #include "ps/ConfigDB.h"
27 #include "ps/Globals.h"
28 #include "ps/KeyName.h"
30 static bool unified
[UNIFIED_LAST
- UNIFIED_SHIFT
];
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
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
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")
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
);
79 mapping
= SDL_GetKeyFromName(it
->c_str());
82 LOGWARNING("Hotkey mapping used invalid key '%s'", hotkey
.c_str());
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
);
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
)
123 bool allNegated
= true;
125 for (const SKey
& k
: hotkey
.requires
)
130 g_HotkeyStatus
[hotkey
.name
] = true;
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
)
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
)
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
)
155 InReaction
HotkeyInputHandler(const SDL_Event_
* ev
)
163 keycode
= (int)ev
->ev
.key
.keysym
.sym
;
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;
173 keycode
= MOUSE_BASE
+ (int)ev
->ev
.button
.button
;
177 if (ev
->ev
.wheel
.y
> 0)
179 keycode
= MOUSE_WHEELUP
;
182 else if (ev
->ev
.wheel
.y
< 0)
184 keycode
= MOUSE_WHEELDOWN
;
187 else if (ev
->ev
.wheel
.x
> 0)
192 else if (ev
->ev
.wheel
.x
< 0)
200 g_HotkeyStatus
[static_cast<const char*>(ev
->ev
.user
.data1
)] = true;
204 g_HotkeyStatus
[static_cast<const char*>(ev
->ev
.user
.data1
)] = false;
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.
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())
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
)
278 // Check for no unpermitted keys
280 for (const SKey
& k
: hotkey
.requires
)
282 accept
= isNegated(k
);
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
)
324 // Check for no unpermitted keys
326 for (const SKey
& k
: hotkey
.requires
)
328 accept
= isNegated(k
);
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
);
345 bool HotkeyIsPressed(const CStr
& keyname
)
347 return g_HotkeyStatus
[keyname
];