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"
21 #include <boost/tokenizer.hpp>
23 #include "lib/input.h"
24 #include "ps/CConsole.h"
25 #include "ps/CLogger.h"
27 #include "ps/ConfigDB.h"
28 #include "ps/Globals.h"
29 #include "ps/KeyName.h"
31 static bool unified
[UNIFIED_LAST
- UNIFIED_SHIFT
];
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
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
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")
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
);
81 LOGWARNING("Hotkey mapping used invalid key '%s'", hotkey
.c_str());
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
);
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
)
122 bool allNegated
= true;
124 for (const SKey
& k
: hotkey
.requires
)
129 g_HotkeyStatus
[hotkey
.name
] = true;
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
)
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
)
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
)
154 InReaction
HotkeyInputHandler(const SDL_Event_
* ev
)
162 keycode
= (int)ev
->ev
.key
.keysym
.sym
;
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;
172 keycode
= MOUSE_BASE
+ (int)ev
->ev
.button
.button
;
176 if (ev
->ev
.wheel
.y
> 0)
178 keycode
= MOUSE_WHEELUP
;
181 else if (ev
->ev
.wheel
.y
< 0)
183 keycode
= MOUSE_WHEELDOWN
;
186 else if (ev
->ev
.wheel
.x
> 0)
191 else if (ev
->ev
.wheel
.x
< 0)
199 g_HotkeyStatus
[static_cast<const char*>(ev
->ev
.user
.data1
)] = true;
203 g_HotkeyStatus
[static_cast<const char*>(ev
->ev
.user
.data1
)] = false;
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.
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())
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
)
277 // Check for no unpermitted keys
279 for (const SKey
& k
: hotkey
.requires
)
281 accept
= isNegated(k
);
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
)
323 // Check for no unpermitted keys
325 for (const SKey
& k
: hotkey
.requires
)
327 accept
= isNegated(k
);
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
);
344 bool HotkeyIsPressed(const CStr
& keyname
)
346 return g_HotkeyStatus
[keyname
];