Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
Hotkey.cpp
Go to the documentation of this file.
1 /* Copyright (C) 2013 Wildfire Games.
2  * This file is part of 0 A.D.
3  *
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.
8  *
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.
13  *
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/>.
16  */
17 
18 #include "precompiled.h"
19 #include "Hotkey.h"
20 
21 #include "lib/input.h"
22 #include "ConfigDB.h"
23 #include "CLogger.h"
24 #include "CConsole.h"
25 #include "CStr.h"
26 #include "ps/Globals.h"
27 #include "KeyName.h"
28 
30 
31 #if SDL_VERSION_ATLEAST(2, 0, 0)
32 #define SDLKEY SDL_Keycode
33 #else
34 #define SDLKEY SDLKey
35 #endif
36 
37 struct SKey
38 {
39  SDLKEY code; // keycode or MOUSE_ or UNIFIED_ value
40  bool negated; // whether the key must be pressed (false) or unpressed (true)
41 };
42 
43 // Hotkey data associated with an externally-specified 'primary' keycode
45 {
46  CStr name; // name of the hotkey
47  bool negated; // whether the primary key must be pressed (false) or unpressed (true)
48  std::vector<SKey> requires; // list of non-primary keys that must also be active
49 };
50 
51 typedef std::vector<SHotkeyMapping> KeyMapping;
52 
53 // A mapping of keycodes onto the hotkeys that are associated with that key.
54 // (A hotkey triggered by a combination of multiple keys will be in this map
55 // multiple times.)
56 static std::map<int, KeyMapping> g_HotkeyMap;
57 
58 // The current pressed status of hotkeys
59 std::map<std::string, bool> g_HotkeyStatus;
60 
61 // Look up each key binding in the config file and set the mappings for
62 // all key combinations that trigger it.
63 static void LoadConfigBindings()
64 {
65  std::map<CStr, CConfigValueSet> bindings = g_ConfigDB.GetValuesWithPrefix( CFG_COMMAND, "hotkey." );
66 
67  CParser multikeyParser;
68  multikeyParser.InputTaskType( "multikey", "<[~$arg(_negate)]$value_+_>_[~$arg(_negate)]$value" );
69 
70  for( std::map<CStr, CConfigValueSet>::iterator bindingsIt = bindings.begin(); bindingsIt != bindings.end(); ++bindingsIt )
71  {
72  std::string hotkeyName = bindingsIt->first.substr(7); // strip the "hotkey." prefix
73 
74  for( CConfigValueSet::iterator it = bindingsIt->second.begin(); it != bindingsIt->second.end(); ++it )
75  {
76  std::string hotkey;
77  if( it->GetString( hotkey ) )
78  {
79  std::vector<SKey> keyCombination;
80 
81  CParserLine multikeyIdentifier;
82  multikeyIdentifier.ParseString( multikeyParser, hotkey );
83 
84  // Iterate through multiple-key bindings (e.g. Ctrl+I)
85 
86  bool negateNext = false;
87 
88  for( size_t t = 0; t < multikeyIdentifier.GetArgCount(); t++ )
89  {
90 
91  if( multikeyIdentifier.GetArgString( (int)t, hotkey ) )
92  {
93  if( hotkey == "_negate" )
94  {
95  negateNext = true;
96  continue;
97  }
98 
99  // Attempt decode as key name
100  int mapping = FindKeyCode( hotkey );
101 
102  // Attempt to decode as a negation of a keyname
103  // Yes, it's going a bit far, perhaps.
104  // Too powerful for most uses, probably.
105  // However, it got some hardcoding out of the engine.
106  // Thus it makes me happy.
107 
108  if( !mapping )
109  {
110  LOGWARNING(L"Hotkey mapping used invalid key '%hs'", hotkey.c_str() );
111  continue;
112  }
113 
114  SKey key = { (SDLKEY)mapping, negateNext };
115  keyCombination.push_back(key);
116 
117  negateNext = false;
118 
119  }
120  }
121 
122  std::vector<SKey>::iterator itKey, itKey2;
123 
124  for( itKey = keyCombination.begin(); itKey != keyCombination.end(); ++itKey )
125  {
126  SHotkeyMapping bindCode;
127 
128  bindCode.name = hotkeyName;
129  bindCode.negated = itKey->negated;
130 
131  for( itKey2 = keyCombination.begin(); itKey2 != keyCombination.end(); ++itKey2 )
132  {
133  // Push any auxiliary keys.
134  if( itKey != itKey2 )
135  bindCode.requires.push_back( *itKey2 );
136  }
137 
138  g_HotkeyMap[itKey->code].push_back( bindCode );
139  }
140  }
141  }
142  }
143 }
144 
146 {
147  InitKeyNameMap();
148 
150 
151  // Set up the state of the hotkeys given no key is down.
152  // i.e. find those hotkeys triggered by all negations.
153 
154  for( std::map<int, KeyMapping>::iterator mapIt = g_HotkeyMap.begin(); mapIt != g_HotkeyMap.end(); ++mapIt )
155  {
156  KeyMapping& hotkeyMap = mapIt->second;
157 
158  for( std::vector<SHotkeyMapping>::iterator it = hotkeyMap.begin(); it != hotkeyMap.end(); ++it )
159  {
160  if( !it->negated )
161  continue;
162 
163  bool allNegated = true;
164 
165  for( std::vector<SKey>::iterator j = it->requires.begin(); j != it->requires.end(); ++j )
166  if( !j->negated )
167  allNegated = false;
168 
169  if( allNegated )
170  g_HotkeyStatus[it->name] = true;
171  }
172  }
173 }
174 
176 {
177  int keycode = 0;
178 
179  switch( ev->ev.type )
180  {
181  case SDL_KEYDOWN:
182  case SDL_KEYUP:
183  keycode = (int)ev->ev.key.keysym.sym;
184  break;
185 
186  case SDL_MOUSEBUTTONDOWN:
187  case SDL_MOUSEBUTTONUP:
188 #if SDL_VERSION_ATLEAST(2, 0, 0)
189  if ((int)ev->ev.button.button <= SDL_BUTTON_RIGHT)
190 #elif SDL_VERSION_ATLEAST(1, 2, 13)
191  if ((int)ev->ev.button.button <= SDL_BUTTON_X2)
192 #else
193  if ((int)ev->ev.button.button <= SDL_BUTTON_WHEELDOWN)
194 #endif
195  {
196  keycode = CUSTOM_SDL_KEYCODE + (int)ev->ev.button.button;
197  break;
198  }
199  return IN_PASS;
200 
201 #if SDL_VERSION_ATLEAST(2, 0, 0)
202  case SDL_MOUSEWHEEL:
203  if (ev->ev.wheel.y > 0)
204  {
205  keycode = MOUSE_WHEELUP;
206  break;
207  }
208  else if (ev->ev.wheel.y < 0)
209  {
210  keycode = MOUSE_WHEELDOWN;
211  break;
212  }
213  return IN_PASS;
214 #endif
215 
216  default:
217  return IN_PASS;
218  }
219 
220  // Rather ugly hack to make the '"' key work better on a MacBook Pro on Windows so it doesn't
221  // always close the console. (Maybe this would be better handled in wsdl or something?)
222  if (keycode == SDLK_BACKQUOTE && (ev->ev.key.keysym.unicode == '\'' || ev->ev.key.keysym.unicode == '"'))
223  keycode = ev->ev.key.keysym.unicode;
224 
225  // Somewhat hackish:
226  // Create phantom 'unified-modifier' events when left- or right- modifier keys are pressed
227  // Just send them to this handler; don't let the imaginary event codes leak back to real SDL.
228 
229  SDL_Event_ phantom;
230  phantom.ev.type = ( ( ev->ev.type == SDL_KEYDOWN ) || ( ev->ev.type == SDL_MOUSEBUTTONDOWN ) ) ? SDL_KEYDOWN : SDL_KEYUP;
231  if( ( keycode == SDLK_LSHIFT ) || ( keycode == SDLK_RSHIFT ) )
232  {
233  phantom.ev.key.keysym.sym = (SDLKEY)UNIFIED_SHIFT;
234  unified[0] = ( phantom.ev.type == SDL_KEYDOWN );
235  HotkeyInputHandler( &phantom );
236  }
237  else if( ( keycode == SDLK_LCTRL ) || ( keycode == SDLK_RCTRL ) )
238  {
239  phantom.ev.key.keysym.sym = (SDLKEY)UNIFIED_CTRL;
240  unified[1] = ( phantom.ev.type == SDL_KEYDOWN );
241  HotkeyInputHandler( &phantom );
242  }
243  else if( ( keycode == SDLK_LALT ) || ( keycode == SDLK_RALT ) )
244  {
245  phantom.ev.key.keysym.sym = (SDLKEY)UNIFIED_ALT;
246  unified[2] = ( phantom.ev.type == SDL_KEYDOWN );
247  HotkeyInputHandler( &phantom );
248  }
249 #if SDL_VERSION_ATLEAST(2, 0, 0)
250  else if( ( keycode == SDLK_LGUI ) || ( keycode == SDLK_RGUI ) )
251 #else // SDL 1.2
252  else if( ( keycode == SDLK_LSUPER ) || ( keycode == SDLK_RSUPER ) || ( keycode == SDLK_LMETA ) || ( keycode == SDLK_RMETA) )
253 #endif
254  {
255  phantom.ev.key.keysym.sym = (SDLKEY)UNIFIED_SUPER;
256  unified[3] = ( phantom.ev.type == SDL_KEYDOWN );
257  HotkeyInputHandler( &phantom );
258  }
259 
260  // Check whether we have any hotkeys registered for this particular keycode
261  if( g_HotkeyMap.find(keycode) == g_HotkeyMap.end() )
262  return( IN_PASS );
263 
264  // Inhibit the dispatch of hotkey events caused by real keys (not fake mouse button
265  // events) while the console is up.
266 
267  bool consoleCapture = false;
268 
269  if( g_Console->IsActive() && keycode < CUSTOM_SDL_KEYCODE )
270  consoleCapture = true;
271 
272  // Here's an interesting bit:
273  // If you have an event bound to, say, 'F', and another to, say, 'Ctrl+F', pressing
274  // 'F' while control is down would normally fire off both.
275 
276  // To avoid this, set the modifier keys for /all/ events this key would trigger
277  // (Ctrl, for example, is both group-save and bookmark-save)
278  // but only send a HotkeyDown event for the event with bindings most precisely
279  // matching the conditions (i.e. the event with the highest number of auxiliary
280  // keys, providing they're all down)
281 
282 #if SDL_VERSION_ATLEAST(2, 0, 0)
283  bool typeKeyDown = ( ev->ev.type == SDL_KEYDOWN ) || ( ev->ev.type == SDL_MOUSEBUTTONDOWN ) || (ev->ev.type == SDL_MOUSEWHEEL);
284 #else
285  bool typeKeyDown = ( ev->ev.type == SDL_KEYDOWN ) || ( ev->ev.type == SDL_MOUSEBUTTONDOWN );
286 #endif
287 
288  // -- KEYDOWN SECTION --
289 
290  std::vector<const char*> closestMapNames;
291  size_t closestMapMatch = 0;
292 
293  for( std::vector<SHotkeyMapping>::iterator it = g_HotkeyMap[keycode].begin(); it < g_HotkeyMap[keycode].end(); ++it )
294  {
295  // If a key has been pressed, and this event triggers on its release, skip it.
296  // Similarly, if the key's been released and the event triggers on a keypress, skip it.
297  if( it->negated == typeKeyDown )
298  continue;
299 
300  // Check to see if all auxiliary keys are down
301 
302  bool accept = true;
303 
304  for( std::vector<SKey>::iterator itKey = it->requires.begin(); itKey != it->requires.end(); ++itKey )
305  {
306  bool rqdState = !itKey->negated;
307 
308  if( (int)itKey->code < CUSTOM_SDL_KEYCODE )
309  {
310  if( g_keys[itKey->code] != rqdState ) accept = false;
311  }
312  else if( (int)itKey->code < UNIFIED_SHIFT )
313  {
314  if( g_mouse_buttons[itKey->code - CUSTOM_SDL_KEYCODE] != rqdState ) accept = false;
315  }
316  else if( (int)itKey->code < UNIFIED_LAST )
317  {
318  if( unified[itKey->code - UNIFIED_SHIFT] != rqdState ) accept = false;
319  }
320  }
321 
322  if( accept && !( consoleCapture && it->name != "console.toggle" ) )
323  {
324  // Tentatively set status to un-pressed, since it may be overridden by
325  // a closer match. (The closest matches will be set to pressed later.)
326  g_HotkeyStatus[it->name] = false;
327 
328  // Check if this is an equally precise or more precise match
329  if( it->requires.size() + 1 >= closestMapMatch )
330  {
331  // Check if more precise
332  if( it->requires.size() + 1 > closestMapMatch )
333  {
334  // Throw away the old less-precise matches
335  closestMapNames.clear();
336  closestMapMatch = it->requires.size() + 1;
337  }
338 
339  closestMapNames.push_back(it->name.c_str());
340  }
341  }
342  }
343 
344  for (size_t i = 0; i < closestMapNames.size(); ++i)
345  {
346  g_HotkeyStatus[closestMapNames[i]] = true;
347 
348  SDL_Event hotkeyNotification;
349  hotkeyNotification.type = SDL_HOTKEYDOWN;
350  hotkeyNotification.user.data1 = const_cast<char*>(closestMapNames[i]);
351  SDL_PushEvent(&hotkeyNotification);
352  }
353 
354  // -- KEYUP SECTION --
355 
356  for( std::vector<SHotkeyMapping>::iterator it = g_HotkeyMap[keycode].begin(); it < g_HotkeyMap[keycode].end(); ++it )
357  {
358  // If it's a keydown event, won't cause HotKeyUps in anything that doesn't
359  // use this key negated => skip them
360  // If it's a keyup event, won't cause HotKeyUps in anything that does use
361  // this key negated => skip them too.
362  if( it->negated != typeKeyDown )
363  continue;
364 
365  // Check to see if all auxiliary keys are down
366 
367  bool accept = true;
368 
369  for( std::vector<SKey>::iterator itKey = it->requires.begin(); itKey != it->requires.end(); ++itKey )
370  {
371  bool rqdState = !itKey->negated;
372 
373  if( (int)itKey->code < CUSTOM_SDL_KEYCODE )
374  {
375  if( g_keys[itKey->code] != rqdState ) accept = false;
376  }
377  else if( (int)itKey->code < UNIFIED_SHIFT )
378  {
379  if( g_mouse_buttons[itKey->code - CUSTOM_SDL_KEYCODE] != rqdState ) accept = false;
380  }
381  else if( (int)itKey->code < UNIFIED_LAST )
382  {
383  if( unified[itKey->code - UNIFIED_SHIFT] != rqdState ) accept = false;
384  }
385  }
386 
387  if( accept )
388  {
389  g_HotkeyStatus[it->name] = false;
390  SDL_Event hotkeyNotification;
391  hotkeyNotification.type = SDL_HOTKEYUP;
392  hotkeyNotification.user.data1 = const_cast<char*>(it->name.c_str());
393  SDL_PushEvent( &hotkeyNotification );
394  }
395  }
396 
397  return( IN_PASS );
398 }
399 
400 bool HotkeyIsPressed(const CStr& keyname)
401 {
402  return g_HotkeyStatus[keyname];
403 }
#define SDLKEY
Definition: Hotkey.cpp:34
SDL_KeyboardEvent key
Definition: wsdl.h:305
#define CUSTOM_SDL_KEYCODE
Definition: KeyName.h:34
int FindKeyCode(const CStr &keyname)
Definition: KeyName.cpp:345
static bool unified[UNIFIED_LAST-UNIFIED_SHIFT]
Definition: Hotkey.cpp:29
#define SDL_VERSION_ATLEAST(X, Y, Z)
Definition: wsdl.h:341
SDLKEY code
Definition: Hotkey.cpp:39
Definition: Hotkey.cpp:37
std::vector< SHotkeyMapping > KeyMapping
Definition: Hotkey.cpp:51
bool IsActive()
Definition: CConsole.h:88
bool negated
Definition: Hotkey.cpp:47
Hotkey system.
#define LOGWARNING
Definition: CLogger.h:34
const int SDL_HOTKEYUP
Definition: Hotkey.h:42
SDL_Event ev
Definition: libsdl.h:56
Uint8 type
Definition: wsdl.h:302
CConsole * g_Console
Definition: CConsole.cpp:46
SDL_MouseButtonEvent button
Definition: wsdl.h:308
static std::map< int, KeyMapping > g_HotkeyMap
Definition: Hotkey.cpp:56
InReaction
Definition: input.h:34
void LoadHotkeys()
Definition: Hotkey.cpp:145
Definition: input.h:40
void InitKeyNameMap()
Definition: KeyName.cpp:333
pthread_key_t key
Definition: wpthread.cpp:140
static void LoadConfigBindings()
Definition: Hotkey.cpp:63
bool g_mouse_buttons[6]
g_mouse_buttons: Mouse buttons states, indexed by SDL_BUTTON_* constants.
Definition: Globals.cpp:35
u16 unicode
Definition: wsdl.h:189
#define g_ConfigDB
Definition: ConfigDB.h:52
const int SDL_HOTKEYDOWN
Definition: Hotkey.h:41
std::map< std::string, bool > g_HotkeyStatus
Definition: Hotkey.cpp:59
bool ParseString(const CParser &parser, const std::string &line)
Definition: Parser.cpp:325
bool GetArgString(size_t arg, std::string &ret)
InReaction HotkeyInputHandler(const SDL_Event_ *ev)
Definition: Hotkey.cpp:175
size_t GetArgCount() const
Definition: Parser.h:219
bool InputTaskType(const std::string &strName, const std::string &strSyntax)
Definition: Parser.cpp:816
int SDL_PushEvent(SDL_Event *ev)
Definition: wsdl.cpp:1354
SDL_keysym keysym
Definition: wsdl.h:196
bool HotkeyIsPressed(const CStr &keyname)
Definition: Hotkey.cpp:400
bool negated
Definition: Hotkey.cpp:40
SDL_UserEvent user
Definition: wsdl.h:312
std::vector< SKey > requires
Definition: Hotkey.cpp:48
SDLKey sym
Definition: wsdl.h:188
std::map< int32_t, bool > g_keys
g_keys: Key states, indexed by SDLK* constants.
Definition: Globals.cpp:29
void * data1
Definition: wsdl.h:282