1
0
forked from 0ad/0ad

Fix hotkeys not releasing properly.

a4852c4c01 changed hotkeys to use scancode, but didn't change g_keys to
reflect that, and this could break hotkey release.
This fixes that by explicitly using scancodes. Note that we might want a
g_keys map in the future, but it seems un-necessary at the moment.

Also remove the last remnants of 'negated' hotkeys, which were disabled
following b995135138.

Tested By: OptimusShepard
Fixes #5922

Differential Revision: https://code.wildfiregames.com/D3379
This was SVN commit r24645.
This commit is contained in:
wraitii 2021-01-16 15:24:58 +00:00
parent 7f1ee23d88
commit 12cceed3d9
7 changed files with 46 additions and 93 deletions

View File

@ -231,9 +231,9 @@ InReaction CInput::ManuallyHandleKeys(const SDL_Event_* ev)
// TODO: there are probably other keys that we could ignore, but recognizing "non-glyph" keys isn't that trivial.
// Further, don't input text if modifiers other than shift are pressed (the user is presumably trying to perform a hotkey).
if (keyCode == SDLK_ESCAPE ||
g_keys[SDLK_LCTRL] || g_keys[SDLK_RCTRL] ||
g_keys[SDLK_LALT] || g_keys[SDLK_RALT] ||
g_keys[SDLK_LGUI] || g_keys[SDLK_RGUI])
g_scancodes[SDL_SCANCODE_LCTRL] || g_scancodes[SDL_SCANCODE_LCTRL] ||
g_scancodes[SDL_SCANCODE_LALT] || g_scancodes[SDL_SCANCODE_RALT] ||
g_scancodes[SDL_SCANCODE_LGUI] || g_scancodes[SDL_SCANCODE_RGUI])
return IN_PASS;
if (m_ComposingText)
@ -369,7 +369,7 @@ void CInput::ManuallyMutableHandleKeyDownEvent(const SDL_Keycode keyCode)
void CInput::ManuallyImmutableHandleKeyDownEvent(const SDL_Keycode keyCode)
{
bool shiftKeyPressed = g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT];
bool shiftKeyPressed = g_scancodes[SDL_SCANCODE_LSHIFT] || g_scancodes[SDL_SCANCODE_RSHIFT];
switch (keyCode)
{
@ -609,7 +609,7 @@ void CInput::SetupGeneratedPlaceholderText()
InReaction CInput::ManuallyHandleHotkeyEvent(const SDL_Event_* ev)
{
bool shiftKeyPressed = g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT];
bool shiftKeyPressed = g_scancodes[SDL_SCANCODE_LSHIFT] || g_scancodes[SDL_SCANCODE_RSHIFT];
std::string hotkey = static_cast<const char*>(ev->ev.user.data1);
@ -962,7 +962,7 @@ void CInput::HandleMessage(SGUIMessage& Message)
// instance, if we press between a and b, the point
// should of course be placed accordingly. Other
// special cases are handled like the input box norms.
if (g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT])
if (g_scancodes[SDL_SCANCODE_LSHIFT] || g_scancodes[SDL_SCANCODE_RSHIFT])
m_iBufferPos = GetMouseHoveringTextPosition();
else
m_iBufferPos = m_iBufferPos_Tail = GetMouseHoveringTextPosition();

View File

@ -360,7 +360,7 @@ void CConsole::InsertChar(const int szChar, const wchar_t cooked)
}
else
{
if (g_keys[SDLK_RCTRL] || g_keys[SDLK_LCTRL])
if (g_scancodes[SDL_SCANCODE_LCTRL] || g_scancodes[SDL_SCANCODE_LCTRL])
{
// Make Ctrl-Delete delete up to end of line
m_szBuffer[m_iBufferPos] = '\0';
@ -379,7 +379,7 @@ void CConsole::InsertChar(const int szChar, const wchar_t cooked)
return;
case SDLK_HOME:
if (g_keys[SDLK_RCTRL] || g_keys[SDLK_LCTRL])
if (g_scancodes[SDL_SCANCODE_LCTRL] || g_scancodes[SDL_SCANCODE_LCTRL])
{
std::lock_guard<std::mutex> lock(m_Mutex); // needed for safe access to m_deqMsgHistory
@ -393,7 +393,7 @@ void CConsole::InsertChar(const int szChar, const wchar_t cooked)
return;
case SDLK_END:
if (g_keys[SDLK_RCTRL] || g_keys[SDLK_LCTRL])
if (g_scancodes[SDL_SCANCODE_LCTRL] || g_scancodes[SDL_SCANCODE_LCTRL])
{
m_iMsgHistPos = 1;
}

View File

@ -25,7 +25,8 @@
bool g_app_minimized = false;
bool g_app_has_focus = true;
std::map<int32_t, bool> g_keys;
std::unordered_map<int32_t, bool> g_scancodes;
int g_mouse_x = 50, g_mouse_y = 50;
bool g_mouse_active = true;
@ -85,7 +86,7 @@ InReaction GlobalsInputHandler(const SDL_Event_* ev)
case SDL_KEYDOWN:
case SDL_KEYUP:
g_keys[ev->ev.key.keysym.sym] = (ev->ev.type == SDL_KEYDOWN);
g_scancodes[ev->ev.key.keysym.scancode] = (ev->ev.type == SDL_KEYDOWN);
return IN_PASS;
default:

View File

@ -22,7 +22,7 @@
#include "lib/frequency_filter.h"
#include "ps/KeyName.h"
#include <map>
#include <unordered_map>
// thin abstraction layer on top of SDL.
// game code should use it instead of SDL_GetMouseState etc. because
@ -40,11 +40,11 @@ extern int g_mouse_x, g_mouse_y;
extern bool g_mouse_active;
/**
* g_keys: Key states, indexed by SDLK* constants. If an entry is true,
* g_scancodes: Key states, indexed by SDL_Scancode constants. If an entry is true,
* it represents a pressed key.
* Updated by GlobalsInputHandler in response to key press/release events.
*/
extern std::map<int32_t, bool> g_keys;
extern std::unordered_map<int32_t, bool> g_scancodes;
/**
* g_mouse_buttons: Mouse buttons states, indexed by SDL_BUTTON_* constants.

View File

@ -50,7 +50,6 @@ static void LoadConfigBindings()
// Unused hotkeys must still be registered in the map to appear in the hotkey editor.
SHotkeyMapping unusedCode;
unusedCode.name = hotkeyName;
unusedCode.negated = false;
g_HotkeyMap[UNUSED_HOTKEY_CODE].push_back(unusedCode);
continue;
}
@ -73,7 +72,7 @@ static void LoadConfigBindings()
continue;
}
SKey key = { scancode, false };
SKey key = { scancode };
keyCombination.push_back(key);
}
@ -83,7 +82,6 @@ static void LoadConfigBindings()
SHotkeyMapping bindCode;
bindCode.name = hotkeyName;
bindCode.negated = itKey->negated;
for (itKey2 = keyCombination.begin(); itKey2 != keyCombination.end(); ++itKey2)
if (itKey != itKey2) // Push any auxiliary keys
@ -98,25 +96,6 @@ static void LoadConfigBindings()
void LoadHotkeys()
{
LoadConfigBindings();
// Set up the state of the hotkeys given no key is down.
// i.e. find those hotkeys triggered by all negations.
for (const std::pair<const int, KeyMapping>& p : g_HotkeyMap)
for (const SHotkeyMapping& hotkey : p.second)
{
if (!hotkey.negated)
continue;
bool allNegated = true;
for (const SKey& k : hotkey.requires)
if (!k.negated)
allNegated = false;
if (allNegated)
g_HotkeyStatus[hotkey.name] = true;
}
}
void UnloadHotkeys()
@ -125,19 +104,19 @@ void UnloadHotkeys()
g_HotkeyStatus.clear();
}
bool isNegated(const SKey& key)
bool isPressed(const SKey& key)
{
// Normal keycodes are below EXTRA_KEYS_BASE
if ((int)key.code < EXTRA_KEYS_BASE && g_keys[key.code] == key.negated)
return false;
if ((int)key.code < EXTRA_KEYS_BASE)
return g_scancodes[key.code];
// 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)
return false;
else if ((int)key.code < MOUSE_LAST && (int)key.code > MOUSE_BASE)
return g_mouse_buttons[key.code - MOUSE_BASE];
// Modifier keycodes are between the normal keys and the mouse 'keys'
else if ((int)key.code < UNIFIED_LAST && (int)key.code > SDL_NUM_SCANCODES && unified[key.code - UNIFIED_SHIFT] == key.negated)
return false;
else
return true;
else if ((int)key.code < UNIFIED_LAST && (int)key.code > SDL_NUM_SCANCODES)
return unified[key.code - UNIFIED_SHIFT];
}
InReaction HotkeyStateChange(const SDL_Event_* ev)
@ -256,23 +235,17 @@ InReaction HotkeyInputHandler(const SDL_Event_* ev)
bool typeKeyDown = ( ev->ev.type == SDL_KEYDOWN ) || ( ev->ev.type == SDL_MOUSEBUTTONDOWN ) || (ev->ev.type == SDL_MOUSEWHEEL);
// -- KEYDOWN SECTION --
std::vector<const char*> closestMapNames;
std::vector<const char*> pressedHotkeys;
std::vector<const char*> releasedHotkeys;
size_t closestMapMatch = 0;
for (const SHotkeyMapping& hotkey : g_HotkeyMap[scancode])
{
// 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.
if (hotkey.negated == typeKeyDown)
continue;
// Check for no unpermitted keys
bool accept = true;
for (const SKey& k : hotkey.requires)
{
accept = isNegated(k);
accept = isPressed(k);
if (!accept)
break;
}
@ -286,23 +259,26 @@ InReaction HotkeyInputHandler(const SDL_Event_* ev)
if (hotkey.requires.size() + 1 > closestMapMatch)
{
// Throw away the old less-precise matches
closestMapNames.clear();
pressedHotkeys.clear();
releasedHotkeys.clear();
closestMapMatch = hotkey.requires.size() + 1;
}
closestMapNames.push_back(hotkey.name.c_str());
if (typeKeyDown)
pressedHotkeys.push_back(hotkey.name.c_str());
else
releasedHotkeys.push_back(hotkey.name.c_str());
}
}
}
for (size_t i = 0; i < closestMapNames.size(); ++i)
for (const char* hotkeyName : pressedHotkeys)
{
// Send a KeyPress event when a key is pressed initially and on mouseButton and mouseWheel events.
// Send a KeyPress event when a hotkey is pressed initially and on mouseButton and mouseWheel events.
if (ev->ev.type != SDL_KEYDOWN || ev->ev.key.repeat == 0)
{
SDL_Event_ hotkeyPressNotification;
hotkeyPressNotification.ev.type = SDL_HOTKEYPRESS;
hotkeyPressNotification.ev.user.data1 = const_cast<char*>(closestMapNames[i]);
hotkeyPressNotification.ev.user.data1 = const_cast<char*>(hotkeyName);
in_push_priority_event(&hotkeyPressNotification);
}
@ -311,37 +287,16 @@ InReaction HotkeyInputHandler(const SDL_Event_* ev)
// On linux, modifier keys (shift, alt, ctrl) are not repeated, see https://github.com/SFML/SFML/issues/122.
SDL_Event_ hotkeyDownNotification;
hotkeyDownNotification.ev.type = SDL_HOTKEYDOWN;
hotkeyDownNotification.ev.user.data1 = const_cast<char*>(closestMapNames[i]);
hotkeyDownNotification.ev.user.data1 = const_cast<char*>(hotkeyName);
in_push_priority_event(&hotkeyDownNotification);
}
// -- KEYUP SECTION --
for (const SHotkeyMapping& hotkey : g_HotkeyMap[scancode])
for (const char* hotkeyName : releasedHotkeys)
{
// If it's a keydown event, won't cause HotKeyUps in anything that doesn't
// use this key negated => skip them
// If it's a keyup event, won't cause HotKeyUps in anything that does use
// this key negated => skip them too.
if (hotkey.negated != typeKeyDown)
continue;
// Check for no unpermitted keys
bool accept = true;
for (const SKey& k : hotkey.requires)
{
accept = isNegated(k);
if (!accept)
break;
}
if (accept)
{
SDL_Event_ hotkeyNotification;
hotkeyNotification.ev.type = SDL_HOTKEYUP;
hotkeyNotification.ev.user.data1 = const_cast<char*>(hotkey.name.c_str());
in_push_priority_event(&hotkeyNotification);
}
SDL_Event_ hotkeyNotification;
hotkeyNotification.ev.type = SDL_HOTKEYUP;
hotkeyNotification.ev.user.data1 = const_cast<char*>(hotkeyName);
in_push_priority_event(&hotkeyNotification);
}
return IN_PASS;

View File

@ -52,17 +52,14 @@ constexpr SDL_Scancode_ UNUSED_HOTKEY_CODE = 0; // == SDL_SCANCODE_UNKNOWN
struct SKey
{
SDL_Scancode_ code; // scancode or MOUSE_ or UNIFIED_ value
bool negated; // whether the key must be pressed (false) or unpressed (true)
bool operator<(const SKey& o) const { return code < o.code && negated < o.negated; }
bool operator==(const SKey& o) const { return code == o.code && negated == o.negated; }
bool operator<(const SKey& o) const { return code < o.code; }
bool operator==(const SKey& o) const { return code == o.code; }
};
// 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
};

View File

@ -145,7 +145,7 @@ JS::Value GetConflicts(ScriptInterface::CmptPrivate* pCmptPrivate, JS::HandleVal
std::set<SKey> codes;
for (const std::string& key : keys)
codes.insert(SKey{ FindScancode(key), false });
codes.insert(SKey{ FindScancode(key) });
std::vector<CStr> conflicts;
// This isn't very efficient, but we shouldn't iterate too many hotkeys