Changeset View
Changeset View
Standalone View
Standalone View
source/ps/Hotkey.cpp
Show All 22 Lines | ||||||||||||||||||
#include "lib/external_libraries/libsdl.h" | #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_assert(std::is_integral<std::underlying_type<SDL_Scancode>::type>::value, "SDL_Scancode is not an integral enum."); | ||||||||||||||||||
static_assert(SDL_USEREVENT_ == SDL_USEREVENT, "SDL_USEREVENT_ is not the same type as the real SDL_USEREVENT"); | ||||||||||||||||||
static_assert(UNUSED_HOTKEY_CODE == SDL_SCANCODE_UNKNOWN); | ||||||||||||||||||
static bool unified[UNIFIED_LAST - UNIFIED_SHIFT]; | static bool unified[UNIFIED_LAST - UNIFIED_SHIFT]; | |||||||||||||||||
std::unordered_map<int, KeyMapping> g_HotkeyMap; | std::unordered_map<int, KeyMapping> g_HotkeyMap; | |||||||||||||||||
namespace { | namespace | |||||||||||||||||
std::unordered_map<std::string, bool> g_HotkeyStatus; | ||||||||||||||||||
struct PressedHotkey | ||||||||||||||||||
{ | { | |||||||||||||||||
PressedHotkey(const SHotkeyMapping* m, bool t) : mapping(m), retriggered(t) {}; | std::unordered_map<std::string, bool> g_HotkeyStatus; | |||||||||||||||||
// NB: this points to one of g_HotkeyMap's mappings. It works because that std::unordered_map is stable once constructed. | ||||||||||||||||||
const SHotkeyMapping* mapping; | ||||||||||||||||||
// Whether the hotkey was triggered by a key release (silences "press" and "up" events). | ||||||||||||||||||
bool retriggered; | ||||||||||||||||||
}; | ||||||||||||||||||
struct ReleasedHotkey | ||||||||||||||||||
{ | ||||||||||||||||||
ReleasedHotkey(const char* n, bool t) : name(n), wasRetriggered(t) {}; | ||||||||||||||||||
const char* name; | ||||||||||||||||||
bool wasRetriggered; | ||||||||||||||||||
}; | ||||||||||||||||||
// 'In-flight' state used because the hotkey triggering process is split in two phase. | ||||||||||||||||||
// These hotkeys may still be stopped if the event responsible for triggering them is handled | ||||||||||||||||||
// before it can be used to generate the hotkeys. | ||||||||||||||||||
std::vector<PressedHotkey> newPressedHotkeys; | ||||||||||||||||||
// Stores the 'specificity' of the newly pressed hotkeys. | ||||||||||||||||||
size_t closestMapMatch = 0; | ||||||||||||||||||
// This is merely used to ensure consistency in EventWillFireHotkey. | ||||||||||||||||||
const SDL_Event_* currentEvent; | ||||||||||||||||||
// List of currently pressed hotkeys. This is used to quickly reset hotkeys. | ||||||||||||||||||
// This is an unsorted vector because there will generally be very few elements, | ||||||||||||||||||
// so it's presumably faster than std::set. | ||||||||||||||||||
std::vector<PressedHotkey> pressedHotkeys; | ||||||||||||||||||
// List of active keys relevant for hotkeys. | ||||||||||||||||||
std::vector<SDL_Scancode_> activeScancodes; | ||||||||||||||||||
} | ||||||||||||||||||
static_assert(std::is_integral<std::underlying_type<SDL_Scancode>::type>::value, "SDL_Scancode is not an integral enum."); | ||||||||||||||||||
static_assert(SDL_USEREVENT_ == SDL_USEREVENT, "SDL_USEREVENT_ is not the same type as the real SDL_USEREVENT"); | ||||||||||||||||||
static_assert(UNUSED_HOTKEY_CODE == SDL_SCANCODE_UNKNOWN); | ||||||||||||||||||
// 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(CConfigDB& configDB) | void LoadConfigBindings(CConfigDB& configDB) | |||||||||||||||||
{ | { | |||||||||||||||||
#if OS_MACOSX | ||||||||||||||||||
bool macMouse = false; | ||||||||||||||||||
CFG_GET_VAL("macmouse", macMouse); | ||||||||||||||||||
#endif | ||||||||||||||||||
ResetActiveHotkeys(); | ||||||||||||||||||
g_HotkeyMap.clear(); | ||||||||||||||||||
g_HotkeyStatus.clear(); | ||||||||||||||||||
for (const std::pair<const CStr, CConfigValueSet>& configPair : configDB.GetValuesWithPrefix(CFG_COMMAND, "hotkey.")) | for (const std::pair<const CStr, CConfigValueSet>& configPair : 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 | |||||||||||||||||
// "unused" is kept or the A23->24 migration, this can likely be removed in A25. | // "unused" is kept or the A23->24 migration, this can likely be removed in A25. | |||||||||||||||||
if (configPair.second.empty() || (configPair.second.size() == 1 && configPair.second.front() == "unused")) | if (configPair.second.empty() || (configPair.second.size() == 1 && configPair.second.front() == "unused")) | |||||||||||||||||
{ | { | |||||||||||||||||
// Unused hotkeys must still be registered in the map to appear in the hotkey editor. | // Unused hotkeys must still be registered in the map to appear in the hotkey editor. | |||||||||||||||||
SHotkeyMapping unusedCode; | SHotkeyMapping unusedCode; | |||||||||||||||||
unusedCode.name = hotkeyName; | unusedCode.name = hotkeyName; | |||||||||||||||||
unusedCode.primary = SKey{ UNUSED_HOTKEY_CODE }; | unusedCode.primary = SKey{ UNUSED_HOTKEY_CODE }; | |||||||||||||||||
g_HotkeyMap[UNUSED_HOTKEY_CODE].push_back(unusedCode); | g_HotkeyMap[UNUSED_HOTKEY_CODE].push_back(unusedCode); | |||||||||||||||||
continue; | continue; | |||||||||||||||||
} | } | |||||||||||||||||
for (const CStr& hotkey : configPair.second) | for (const CStr& hotkey : configPair.second) | |||||||||||||||||
{ | { | |||||||||||||||||
std::vector<SKey> keyCombination; | std::vector<SKey> keyCombination; | |||||||||||||||||
#if OS_MACOSX | ||||||||||||||||||
// When 'macmouse' is on, replace hotkeys that are exactly 'ctrl' with 'cmd' instead. | ||||||||||||||||||
if (macMouse && hotkey == "Ctrl") | ||||||||||||||||||
keyCombination = { { UNIFIED_SUPER } }; | ||||||||||||||||||
else | ||||||||||||||||||
#endif | ||||||||||||||||||
// Indent for convenience in the above #if | ||||||||||||||||||
{ | ||||||||||||||||||
// 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 | |||||||||||||||||
SDL_Scancode scancode = FindScancode(it->c_str()); | SDL_Scancode scancode = FindScancode(it->c_str()); | |||||||||||||||||
if (!scancode) | if (!scancode) | |||||||||||||||||
{ | { | |||||||||||||||||
LOGWARNING("Hotkey mapping used invalid key '%s'", hotkey.c_str()); | LOGWARNING("Hotkey mapping used invalid key '%s'", hotkey.c_str()); | |||||||||||||||||
continue; | continue; | |||||||||||||||||
} | } | |||||||||||||||||
SKey key = { scancode }; | SKey key = { scancode }; | |||||||||||||||||
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.primary = SKey{ itKey->code }; | bindCode.primary = SKey{ itKey->code }; | |||||||||||||||||
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 CheckMacMouse() | ||||||||||||||||||
{ | ||||||||||||||||||
#if OS_MACOSX | ||||||||||||||||||
bool macMouse = false; | ||||||||||||||||||
CFG_GET_VAL("macmouse", macMouse); | ||||||||||||||||||
SDL_SetHint(SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK, macMouse ? "1" : "0"); | ||||||||||||||||||
// This clears existing hotkeys. | ||||||||||||||||||
LoadConfigBindings(g_ConfigDB); | ||||||||||||||||||
#endif | ||||||||||||||||||
} | ||||||||||||||||||
std::unique_ptr<CConfigDBHook> m_MacMouseHook; | ||||||||||||||||||
struct PressedHotkey | ||||||||||||||||||
{ | ||||||||||||||||||
PressedHotkey(const SHotkeyMapping* m, bool t) : mapping(m), retriggered(t) {}; | ||||||||||||||||||
// NB: this points to one of g_HotkeyMap's mappings. It works because that std::unordered_map is stable once constructed. | ||||||||||||||||||
const SHotkeyMapping* mapping; | ||||||||||||||||||
// Whether the hotkey was triggered by a key release (silences "press" and "up" events). | ||||||||||||||||||
bool retriggered; | ||||||||||||||||||
}; | ||||||||||||||||||
struct ReleasedHotkey | ||||||||||||||||||
{ | ||||||||||||||||||
ReleasedHotkey(const char* n, bool t) : name(n), wasRetriggered(t) {}; | ||||||||||||||||||
const char* name; | ||||||||||||||||||
bool wasRetriggered; | ||||||||||||||||||
}; | ||||||||||||||||||
// 'In-flight' state used because the hotkey triggering process is split in two phase. | ||||||||||||||||||
// These hotkeys may still be stopped if the event responsible for triggering them is handled | ||||||||||||||||||
// before it can be used to generate the hotkeys. | ||||||||||||||||||
std::vector<PressedHotkey> newPressedHotkeys; | ||||||||||||||||||
// Stores the 'specificity' of the newly pressed hotkeys. | ||||||||||||||||||
size_t closestMapMatch = 0; | ||||||||||||||||||
// This is merely used to ensure consistency in EventWillFireHotkey. | ||||||||||||||||||
const SDL_Event_* currentEvent; | ||||||||||||||||||
// List of currently pressed hotkeys. This is used to quickly reset hotkeys. | ||||||||||||||||||
// This is an unsorted vector because there will generally be very few elements, | ||||||||||||||||||
// so it's presumably faster than std::set. | ||||||||||||||||||
std::vector<PressedHotkey> pressedHotkeys; | ||||||||||||||||||
// List of active keys relevant for hotkeys. | ||||||||||||||||||
std::vector<SDL_Scancode_> activeScancodes; | ||||||||||||||||||
} // anonymous namespace | ||||||||||||||||||
Stan: Revert that. | ||||||||||||||||||
Done Inline ActionsHow should I revert that? I need the key variable. Schweini: How should I revert that? I need the key variable. | ||||||||||||||||||
void LoadHotkeys(CConfigDB& configDB) | void LoadHotkeys(CConfigDB& configDB) | |||||||||||||||||
Done Inline ActionsUse the preprocessor as was done before -> #if OS_MACOSX wraitii: Use the preprocessor as was done before -> `#if OS_MACOSX` | ||||||||||||||||||
{ | { | |||||||||||||||||
pressedHotkeys.clear(); | ||||||||||||||||||
LoadConfigBindings(configDB); | LoadConfigBindings(configDB); | |||||||||||||||||
m_MacMouseHook = std::make_unique<CConfigDBHook>(configDB.RegisterHook("macmouse", CheckMacMouse)); | ||||||||||||||||||
Done Inline Actions
Stan: | ||||||||||||||||||
} | } | |||||||||||||||||
Not Done Inline Actions
Stan: | ||||||||||||||||||
void UnloadHotkeys() | void UnloadHotkeys() | |||||||||||||||||
{ | { | |||||||||||||||||
pressedHotkeys.clear(); | m_MacMouseHook.reset(); | |||||||||||||||||
ResetActiveHotkeys(); | ||||||||||||||||||
g_HotkeyMap.clear(); | g_HotkeyMap.clear(); | |||||||||||||||||
g_HotkeyStatus.clear(); | g_HotkeyStatus.clear(); | |||||||||||||||||
} | } | |||||||||||||||||
Done Inline ActionsThis shouldn't be in the loop IMO. Move it at the beginning of the function or something along those lines. wraitii: This shouldn't be in the loop IMO. Move it at the beginning of the function or something along… | ||||||||||||||||||
bool isPressed(const SKey& key) | bool isPressed(const SKey& key) | |||||||||||||||||
{ | { | |||||||||||||||||
// Normal keycodes are below EXTRA_KEYS_BASE | // Normal keycodes are below EXTRA_KEYS_BASE | |||||||||||||||||
if ((int)key.code < EXTRA_KEYS_BASE) | if ((int)key.code < EXTRA_KEYS_BASE) | |||||||||||||||||
return g_scancodes[key.code]; | return g_scancodes[key.code]; | |||||||||||||||||
// 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) | else if ((int)key.code < MOUSE_LAST && (int)key.code > MOUSE_BASE) | |||||||||||||||||
return g_mouse_buttons[key.code - MOUSE_BASE]; | return g_mouse_buttons[key.code - MOUSE_BASE]; | |||||||||||||||||
▲ Show 20 Lines • Show All 317 Lines • ▼ Show 20 Lines | void ResetActiveHotkeys() | |||||||||||||||||
pressedHotkeys.clear(); | pressedHotkeys.clear(); | |||||||||||||||||
activeScancodes.clear(); | activeScancodes.clear(); | |||||||||||||||||
currentEvent = nullptr; | currentEvent = nullptr; | |||||||||||||||||
} | } | |||||||||||||||||
bool HotkeyIsPressed(const CStr& keyname) | bool HotkeyIsPressed(const CStr& keyname) | |||||||||||||||||
{ | { | |||||||||||||||||
return g_HotkeyStatus[keyname]; | return g_HotkeyStatus[keyname]; | |||||||||||||||||
} | } | |||||||||||||||||
Not Done Inline ActionsPut the if body on a newline. wraitii: Put the if body on a newline. |
Wildfire Games · Phabricator
Revert that.