Changeset View
Changeset View
Standalone View
Standalone View
source/ps/Hotkey.cpp
Show All 14 Lines | |||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. | * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. | ||||
*/ | */ | ||||
#include "precompiled.h" | #include "precompiled.h" | ||||
#include "Hotkey.h" | #include "Hotkey.h" | ||||
#include <boost/tokenizer.hpp> | #include <boost/tokenizer.hpp> | ||||
#include "lib/external_libraries/libsdl.h" | |||||
#include "ps/CConsole.h" | #include "ps/CConsole.h" | ||||
#include "ps/CLogger.h" | #include "ps/CLogger.h" | ||||
#include "ps/CStr.h" | #include "ps/CStr.h" | ||||
#include "ps/ConfigDB.h" | #include "ps/ConfigDB.h" | ||||
#include "ps/Globals.h" | #include "ps/Globals.h" | ||||
#include "ps/KeyName.h" | #include "ps/KeyName.h" | ||||
static bool unified[UNIFIED_LAST - UNIFIED_SHIFT]; | static bool unified[UNIFIED_LAST - UNIFIED_SHIFT]; | ||||
struct SKey | std::unordered_map<int, KeyMapping> g_HotkeyMap; | ||||
{ | std::unordered_map<std::string, bool> g_HotkeyStatus; | ||||
SDL_Keycode code; // keycode or MOUSE_ or UNIFIED_ value | |||||
bool negated; // whether the key must be pressed (false) or unpressed (true) | |||||
}; | |||||
// Hotkey data associated with an externally-specified 'primary' keycode | |||||
struct SHotkeyMapping | |||||
{ | |||||
CStr name; // name of the hotkey | |||||
bool negated; // whether the primary key must be pressed (false) or unpressed (true) | |||||
std::vector<SKey> requires; // list of non-primary keys that must also be active | |||||
}; | |||||
typedef std::vector<SHotkeyMapping> KeyMapping; | static_assert(std::is_integral<std::underlying_type<SDL_Scancode>::type>::value, "SDL_Scancode is not an integral enum."); | ||||
Stan: ? | |||||
Not Done Inline ActionsDo we need sorting ? Stan: Do we need sorting ? | |||||
Not Done Inline Actions? Stan: ? | |||||
static_assert(SDL_USEREVENT_ == SDL_USEREVENT, "SDL_USEREVENT_ is not the same type as the real SDL_USEREVENT"); | |||||
// A mapping of keycodes onto the hotkeys that are associated with that key. | |||||
// (A hotkey triggered by a combination of multiple keys will be in this map | |||||
// multiple times.) | |||||
static std::map<int, KeyMapping> g_HotkeyMap; | |||||
// The current pressed status of hotkeys | |||||
std::map<std::string, bool> g_HotkeyStatus; | |||||
// Look up each key binding in the config file and set the mappings for | // Look up each key binding in the config file and set the mappings for | ||||
// all key combinations that trigger it. | // all key combinations that trigger it. | ||||
static void LoadConfigBindings() | static void LoadConfigBindings() | ||||
{ | { | ||||
for (const std::pair<CStr, CConfigValueSet>& configPair : g_ConfigDB.GetValuesWithPrefix(CFG_COMMAND, "hotkey.")) | for (const std::pair<CStr, CConfigValueSet>& configPair : g_ConfigDB.GetValuesWithPrefix(CFG_COMMAND, "hotkey.")) | ||||
{ | { | ||||
std::string hotkeyName = configPair.first.substr(7); // strip the "hotkey." prefix | std::string hotkeyName = configPair.first.substr(7); // strip the "hotkey." prefix | ||||
for (const CStr& hotkey : configPair.second) | for (const CStr& hotkey : configPair.second) | ||||
{ | { | ||||
if (hotkey.LowerCase() == "unused") | if (hotkey.LowerCase() == "unused") | ||||
continue; | continue; | ||||
std::vector<SKey> keyCombination; | std::vector<SKey> keyCombination; | ||||
// Iterate through multiple-key bindings (e.g. Ctrl+I) | // Iterate through multiple-key bindings (e.g. Ctrl+I) | ||||
boost::char_separator<char> sep("+"); | boost::char_separator<char> sep("+"); | ||||
typedef boost::tokenizer<boost::char_separator<char> > tokenizer; | typedef boost::tokenizer<boost::char_separator<char> > tokenizer; | ||||
tokenizer tok(hotkey, sep); | tokenizer tok(hotkey, sep); | ||||
for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it) | for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it) | ||||
{ | { | ||||
// Attempt decode as key name | // Attempt decode as key name | ||||
Not Done Inline ActionsIs it used ? Stan: Is it used ? | |||||
int mapping = FindKeyCode(*it); | SDL_Scancode scancode = FindScancode(it->c_str()); | ||||
if (!mapping) | if (!scancode) | ||||
mapping = SDL_GetKeyFromName(it->c_str()); | |||||
Done Inline ActionsReverts D303 by moving it to KeyName wraitii: Reverts D303 by moving it to KeyName | |||||
if (!mapping) | |||||
{ | { | ||||
LOGWARNING("Hotkey mapping used invalid key '%s'", hotkey.c_str()); | LOGWARNING("Hotkey mapping used invalid key '%s'", hotkey.c_str()); | ||||
continue; | continue; | ||||
} | } | ||||
SKey key = { (SDL_Keycode)mapping, false }; | SKey key = { scancode, false }; | ||||
keyCombination.push_back(key); | keyCombination.push_back(key); | ||||
} | } | ||||
std::vector<SKey>::iterator itKey, itKey2; | std::vector<SKey>::iterator itKey, itKey2; | ||||
for (itKey = keyCombination.begin(); itKey != keyCombination.end(); ++itKey) | for (itKey = keyCombination.begin(); itKey != keyCombination.end(); ++itKey) | ||||
{ | { | ||||
SHotkeyMapping bindCode; | SHotkeyMapping bindCode; | ||||
bindCode.name = hotkeyName; | bindCode.name = hotkeyName; | ||||
bindCode.negated = itKey->negated; | bindCode.negated = itKey->negated; | ||||
for (itKey2 = keyCombination.begin(); itKey2 != keyCombination.end(); ++itKey2) | for (itKey2 = keyCombination.begin(); itKey2 != keyCombination.end(); ++itKey2) | ||||
if (itKey != itKey2) // Push any auxiliary keys | if (itKey != itKey2) // Push any auxiliary keys | ||||
bindCode.requires.push_back(*itKey2); | bindCode.requires.push_back(*itKey2); | ||||
g_HotkeyMap[itKey->code].push_back(bindCode); | g_HotkeyMap[itKey->code].push_back(bindCode); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
void LoadHotkeys() | void LoadHotkeys() | ||||
{ | { | ||||
InitKeyNameMap(); | |||||
LoadConfigBindings(); | LoadConfigBindings(); | ||||
// Set up the state of the hotkeys given no key is down. | // Set up the state of the hotkeys given no key is down. | ||||
// i.e. find those hotkeys triggered by all negations. | // i.e. find those hotkeys triggered by all negations. | ||||
for (const std::pair<int, KeyMapping>& p : g_HotkeyMap) | for (const std::pair<int, KeyMapping>& p : g_HotkeyMap) | ||||
for (const SHotkeyMapping& hotkey : p.second) | for (const SHotkeyMapping& hotkey : p.second) | ||||
{ | { | ||||
Show All 21 Lines | |||||
{ | { | ||||
// Normal keycodes are below EXTRA_KEYS_BASE | // Normal keycodes are below EXTRA_KEYS_BASE | ||||
if ((int)key.code < EXTRA_KEYS_BASE && g_keys[key.code] == key.negated) | if ((int)key.code < EXTRA_KEYS_BASE && g_keys[key.code] == key.negated) | ||||
return false; | return false; | ||||
// Mouse 'keycodes' are after the modifier keys | // Mouse 'keycodes' are after the modifier keys | ||||
else if ((int)key.code < MOUSE_LAST && (int)key.code > MOUSE_BASE && g_mouse_buttons[key.code - MOUSE_BASE] == key.negated) | else if ((int)key.code < MOUSE_LAST && (int)key.code > MOUSE_BASE && g_mouse_buttons[key.code - MOUSE_BASE] == key.negated) | ||||
return false; | return false; | ||||
// Modifier keycodes are between the normal keys and the mouse 'keys' | // Modifier keycodes are between the normal keys and the mouse 'keys' | ||||
else if ((int)key.code < UNIFIED_LAST && (int)key.code > SDL_SCANCODE_TO_KEYCODE(SDL_NUM_SCANCODES) && unified[key.code - UNIFIED_SHIFT] == key.negated) | else if ((int)key.code < UNIFIED_LAST && (int)key.code > SDL_NUM_SCANCODES && unified[key.code - UNIFIED_SHIFT] == key.negated) | ||||
return false; | return false; | ||||
else | else | ||||
return true; | return true; | ||||
} | } | ||||
InReaction HotkeyStateChange(const SDL_Event_* ev) | InReaction HotkeyStateChange(const SDL_Event_* ev) | ||||
{ | { | ||||
if (ev->ev.type == SDL_HOTKEYPRESS) | if (ev->ev.type == SDL_HOTKEYPRESS) | ||||
g_HotkeyStatus[static_cast<const char*>(ev->ev.user.data1)] = true; | g_HotkeyStatus[static_cast<const char*>(ev->ev.user.data1)] = true; | ||||
else if (ev->ev.type == SDL_HOTKEYUP) | else if (ev->ev.type == SDL_HOTKEYUP) | ||||
g_HotkeyStatus[static_cast<const char*>(ev->ev.user.data1)] = false; | g_HotkeyStatus[static_cast<const char*>(ev->ev.user.data1)] = false; | ||||
return IN_PASS; | return IN_PASS; | ||||
} | } | ||||
InReaction HotkeyInputHandler(const SDL_Event_* ev) | InReaction HotkeyInputHandler(const SDL_Event_* ev) | ||||
{ | { | ||||
int keycode = 0; | int scancode = SDL_SCANCODE_UNKNOWN; | ||||
Not Done Inline ActionsSDL_Scancode Stan: SDL_Scancode | |||||
Done Inline Actionseasier to have int here, since SDL_Scancode converts to int without casting but not the opposite, and the MOUSE_X below are integer. wraitii: easier to have int here, since SDL_Scancode converts to int without casting but not the… | |||||
switch(ev->ev.type) | switch(ev->ev.type) | ||||
{ | { | ||||
case SDL_KEYDOWN: | case SDL_KEYDOWN: | ||||
case SDL_KEYUP: | case SDL_KEYUP: | ||||
keycode = (int)ev->ev.key.keysym.sym; | scancode = ev->ev.key.keysym.scancode; | ||||
break; | break; | ||||
case SDL_MOUSEBUTTONDOWN: | case SDL_MOUSEBUTTONDOWN: | ||||
case SDL_MOUSEBUTTONUP: | case SDL_MOUSEBUTTONUP: | ||||
// Mousewheel events are no longer buttons, but we want to maintain the order | // Mousewheel events are no longer buttons, but we want to maintain the order | ||||
// expected by g_mouse_buttons for compatibility | // expected by g_mouse_buttons for compatibility | ||||
if (ev->ev.button.button >= SDL_BUTTON_X1) | if (ev->ev.button.button >= SDL_BUTTON_X1) | ||||
keycode = MOUSE_BASE + (int)ev->ev.button.button + 2; | scancode = MOUSE_BASE + (int)ev->ev.button.button + 2; | ||||
else | else | ||||
keycode = MOUSE_BASE + (int)ev->ev.button.button; | scancode = MOUSE_BASE + (int)ev->ev.button.button; | ||||
break; | break; | ||||
case SDL_MOUSEWHEEL: | case SDL_MOUSEWHEEL: | ||||
if (ev->ev.wheel.y > 0) | if (ev->ev.wheel.y > 0) | ||||
{ | { | ||||
keycode = MOUSE_WHEELUP; | scancode = MOUSE_WHEELUP; | ||||
break; | break; | ||||
} | } | ||||
else if (ev->ev.wheel.y < 0) | else if (ev->ev.wheel.y < 0) | ||||
{ | { | ||||
keycode = MOUSE_WHEELDOWN; | scancode = MOUSE_WHEELDOWN; | ||||
break; | break; | ||||
} | } | ||||
else if (ev->ev.wheel.x > 0) | else if (ev->ev.wheel.x > 0) | ||||
{ | { | ||||
keycode = MOUSE_X2; | scancode = MOUSE_X2; | ||||
break; | break; | ||||
} | } | ||||
else if (ev->ev.wheel.x < 0) | else if (ev->ev.wheel.x < 0) | ||||
{ | { | ||||
keycode = MOUSE_X1; | scancode = MOUSE_X1; | ||||
break; | break; | ||||
} | } | ||||
return IN_PASS; | return IN_PASS; | ||||
default: | default: | ||||
return IN_PASS; | return IN_PASS; | ||||
} | } | ||||
// Somewhat hackish: | // Somewhat hackish: | ||||
// Create phantom 'unified-modifier' events when left- or right- modifier keys are pressed | // Create phantom 'unified-modifier' events when left- or right- modifier keys are pressed | ||||
// Just send them to this handler; don't let the imaginary event codes leak back to real SDL. | // Just send them to this handler; don't let the imaginary event codes leak back to real SDL. | ||||
SDL_Event_ phantom; | SDL_Event_ phantom; | ||||
phantom.ev.type = ((ev->ev.type == SDL_KEYDOWN) || (ev->ev.type == SDL_MOUSEBUTTONDOWN)) ? SDL_KEYDOWN : SDL_KEYUP; | phantom.ev.type = ((ev->ev.type == SDL_KEYDOWN) || (ev->ev.type == SDL_MOUSEBUTTONDOWN)) ? SDL_KEYDOWN : SDL_KEYUP; | ||||
if (phantom.ev.type == SDL_KEYDOWN) | if (phantom.ev.type == SDL_KEYDOWN) | ||||
phantom.ev.key.repeat = ev->ev.type == SDL_KEYDOWN ? ev->ev.key.repeat : 0; | phantom.ev.key.repeat = ev->ev.type == SDL_KEYDOWN ? ev->ev.key.repeat : 0; | ||||
if ((keycode == SDLK_LSHIFT) || (keycode == SDLK_RSHIFT)) | if (scancode == SDL_SCANCODE_LSHIFT || scancode == SDL_SCANCODE_RSHIFT) | ||||
{ | { | ||||
phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_SHIFT; | phantom.ev.key.keysym.scancode = static_cast<SDL_Scancode>(UNIFIED_SHIFT); | ||||
unified[0] = (phantom.ev.type == SDL_KEYDOWN); | unified[0] = (phantom.ev.type == SDL_KEYDOWN); | ||||
HotkeyInputHandler(&phantom); | HotkeyInputHandler(&phantom); | ||||
} | } | ||||
else if ((keycode == SDLK_LCTRL) || (keycode == SDLK_RCTRL)) | else if (scancode == SDL_SCANCODE_LCTRL || scancode == SDL_SCANCODE_RCTRL) | ||||
{ | { | ||||
phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_CTRL; | phantom.ev.key.keysym.scancode = static_cast<SDL_Scancode>(UNIFIED_CTRL); | ||||
unified[1] = (phantom.ev.type == SDL_KEYDOWN); | unified[1] = (phantom.ev.type == SDL_KEYDOWN); | ||||
HotkeyInputHandler(&phantom); | HotkeyInputHandler(&phantom); | ||||
} | } | ||||
else if ((keycode == SDLK_LALT) || (keycode == SDLK_RALT)) | else if (scancode == SDL_SCANCODE_LALT || scancode == SDL_SCANCODE_RALT) | ||||
{ | { | ||||
phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_ALT; | phantom.ev.key.keysym.scancode = static_cast<SDL_Scancode>(UNIFIED_ALT); | ||||
unified[2] = (phantom.ev.type == SDL_KEYDOWN); | unified[2] = (phantom.ev.type == SDL_KEYDOWN); | ||||
HotkeyInputHandler(&phantom); | HotkeyInputHandler(&phantom); | ||||
} | } | ||||
else if ((keycode == SDLK_LGUI) || (keycode == SDLK_RGUI)) | else if (scancode == SDL_SCANCODE_LGUI || scancode == SDL_SCANCODE_RGUI) | ||||
{ | { | ||||
phantom.ev.key.keysym.sym = (SDL_Keycode)UNIFIED_SUPER; | phantom.ev.key.keysym.scancode = static_cast<SDL_Scancode>(UNIFIED_SUPER); | ||||
unified[3] = (phantom.ev.type == SDL_KEYDOWN); | unified[3] = (phantom.ev.type == SDL_KEYDOWN); | ||||
HotkeyInputHandler(&phantom); | HotkeyInputHandler(&phantom); | ||||
} | } | ||||
// Check whether we have any hotkeys registered for this particular keycode | // Check whether we have any hotkeys registered for this particular keycode | ||||
if (g_HotkeyMap.find(keycode) == g_HotkeyMap.end()) | if (g_HotkeyMap.find(scancode) == g_HotkeyMap.end()) | ||||
return (IN_PASS); | return (IN_PASS); | ||||
// Inhibit the dispatch of hotkey events caused by real keys (not fake mouse button | // Inhibit the dispatch of hotkey events caused by real keys (not fake mouse button | ||||
// events) while the console is up. | // events) while the console is up. | ||||
bool consoleCapture = false; | bool consoleCapture = false; | ||||
if (g_Console && g_Console->IsActive() && keycode < SDL_SCANCODE_TO_KEYCODE(SDL_NUM_SCANCODES)) | if (g_Console && g_Console->IsActive() && scancode < SDL_NUM_SCANCODES) | ||||
consoleCapture = true; | consoleCapture = true; | ||||
// Here's an interesting bit: | // Here's an interesting bit: | ||||
// If you have an event bound to, say, 'F', and another to, say, 'Ctrl+F', pressing | // If you have an event bound to, say, 'F', and another to, say, 'Ctrl+F', pressing | ||||
// 'F' while control is down would normally fire off both. | // 'F' while control is down would normally fire off both. | ||||
// To avoid this, set the modifier keys for /all/ events this key would trigger | // To avoid this, set the modifier keys for /all/ events this key would trigger | ||||
// (Ctrl, for example, is both group-save and bookmark-save) | // (Ctrl, for example, is both group-save and bookmark-save) | ||||
// but only send a HotkeyPress/HotkeyDown event for the event with bindings most precisely | // but only send a HotkeyPress/HotkeyDown event for the event with bindings most precisely | ||||
// matching the conditions (i.e. the event with the highest number of auxiliary | // matching the conditions (i.e. the event with the highest number of auxiliary | ||||
// keys, providing they're all down) | // keys, providing they're all down) | ||||
bool typeKeyDown = ( ev->ev.type == SDL_KEYDOWN ) || ( ev->ev.type == SDL_MOUSEBUTTONDOWN ) || (ev->ev.type == SDL_MOUSEWHEEL); | bool typeKeyDown = ( ev->ev.type == SDL_KEYDOWN ) || ( ev->ev.type == SDL_MOUSEBUTTONDOWN ) || (ev->ev.type == SDL_MOUSEWHEEL); | ||||
// -- KEYDOWN SECTION -- | // -- KEYDOWN SECTION -- | ||||
std::vector<const char*> closestMapNames; | std::vector<const char*> closestMapNames; | ||||
size_t closestMapMatch = 0; | size_t closestMapMatch = 0; | ||||
for (const SHotkeyMapping& hotkey : g_HotkeyMap[keycode]) | for (const SHotkeyMapping& hotkey : g_HotkeyMap[scancode]) | ||||
{ | { | ||||
// If a key has been pressed, and this event triggers on its release, skip it. | // If a key has been pressed, and this event triggers on its release, skip it. | ||||
// Similarly, if the key's been released and the event triggers on a keypress, skip it. | // Similarly, if the key's been released and the event triggers on a keypress, skip it. | ||||
if (hotkey.negated == typeKeyDown) | if (hotkey.negated == typeKeyDown) | ||||
continue; | continue; | ||||
// Check for no unpermitted keys | // Check for no unpermitted keys | ||||
bool accept = true; | bool accept = true; | ||||
Show All 39 Lines | for (size_t i = 0; i < closestMapNames.size(); ++i) | ||||
SDL_Event_ hotkeyDownNotification; | SDL_Event_ hotkeyDownNotification; | ||||
hotkeyDownNotification.ev.type = SDL_HOTKEYDOWN; | hotkeyDownNotification.ev.type = SDL_HOTKEYDOWN; | ||||
hotkeyDownNotification.ev.user.data1 = const_cast<char*>(closestMapNames[i]); | hotkeyDownNotification.ev.user.data1 = const_cast<char*>(closestMapNames[i]); | ||||
in_push_priority_event(&hotkeyDownNotification); | in_push_priority_event(&hotkeyDownNotification); | ||||
} | } | ||||
// -- KEYUP SECTION -- | // -- KEYUP SECTION -- | ||||
for (const SHotkeyMapping& hotkey : g_HotkeyMap[keycode]) | for (const SHotkeyMapping& hotkey : g_HotkeyMap[scancode]) | ||||
{ | { | ||||
// If it's a keydown event, won't cause HotKeyUps in anything that doesn't | // If it's a keydown event, won't cause HotKeyUps in anything that doesn't | ||||
// use this key negated => skip them | // use this key negated => skip them | ||||
// If it's a keyup event, won't cause HotKeyUps in anything that does use | // If it's a keyup event, won't cause HotKeyUps in anything that does use | ||||
// this key negated => skip them too. | // this key negated => skip them too. | ||||
if (hotkey.negated != typeKeyDown) | if (hotkey.negated != typeKeyDown) | ||||
continue; | continue; | ||||
Show All 25 Lines |
Wildfire Games · Phabricator
?