fix(input): Prevent modified key releases from firing plain hotkeys - #3233
fix(input): Prevent modified key releases from firing plain hotkeys#3233CryoTheRenegade wants to merge 6 commits into
Conversation
… out of order Signed-off-by: Jacob Ledbetter <jledbetter460@gmail.com>
PR Summary by QodoPrevent stuck modifier combos on out-of-order key releases
AI Description
Diagram
High-Level Assessment
Files changed (6)
|
Code Review by Qodo🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)
Great, no issues found!Qodo reviewed your code and found no material issues that require reviewTip of the day💡 Did you know, you can reply 'qodo' on any finding to push back, ask questions, or dig deeper |
|
| Filename | Overview |
|---|---|
| Core/GameEngine/Source/GameClient/Input/Keyboard.cpp | Captures modifier state per input event, tracks press-time state per key, and emits synthetic modifier releases during resets. |
| Core/GameEngine/Source/GameClient/MessageStream/HotKey.cpp | Suppresses plain hotkey execution when either the press or release carried a keyboard modifier. |
| Core/GameEngine/Include/GameClient/Keyboard.h | Extends keyboard event and persistent keyboard state storage with press-time modifier information. |
| Core/GameEngine/Include/Common/MessageStream.h | Documents the expanded raw keyboard message argument contract. |
| Generals/Code/GameEngine/Include/GameClient/KeyDefs.h | Adds a combined Ctrl, Shift, and Alt modifier-state mask for Generals. |
| GeneralsMD/Code/GameEngine/Include/GameClient/KeyDefs.h | Adds the equivalent combined modifier-state mask for Zero Hour. |
Sequence Diagram
sequenceDiagram
participant OS as Keyboard input
participant K as Keyboard
participant MS as MessageStream
participant HK as HotKeyTranslator
OS->>K: Key down with modifier held
K->>K: Save press-time modifier state
OS->>K: Modifier up
OS->>K: Key up
K->>MS: MSG_RAW_KEY_UP(key, currentState, pressedState)
MS->>HK: Translate key-up
HK->>HK: Check currentState OR pressedState
alt Press or release was modified
HK-->>MS: Do not execute plain hotkey
else No modifier involved
HK->>HK: Execute matching plain hotkey
end
Reviews (6): Last reviewed commit: "Keep key release state separate from pre..." | Re-trigger Greptile
HotKey.h referenced KeyDefType/KEY_COUNT without the key header, and MetaEvent.cpp called a reset helper that was never declared. Co-authored-by: Cursor <cursoragent@cursor.com>
xezon
left a comment
There was a problem hiding this comment.
I assume this was entirely generated by LLM? It looks like slop the way the new logic is laid out. It is incomprehensible and unmaintainable. It needs to be unsloppified.
Keep the GUI-hotkey and focus-loss behavior, but store the modifier-down flag on HotKeyTranslator itself and reuse the existing alt-tab key-up path for CTRL and SHIFT. Co-authored-by: Cursor <cursoragent@cursor.com>
|
How do we know the new generated revision is no slop? |
|
Fair criticism. I used LLM assistance on the first pass, and I should have reviewed and simplified the result before asking you to review it. I own that. I’ve since rewritten the fix. The hotkey translator is stateless now. Modifier press state lives in |
|
I tried to understand this change but I was unable to. It is lacking context. What was the issue and how was it reproduced, how was it fixed and why is it fixed the way it was fixed. |
|
I've reworded the PR description with a simple example and an explanation of the cause, the fix, and why the state is stored in |
| if( !isModifier ) | ||
| { | ||
| BitClear( m_keys[ index ].state, KEY_STATE_MODIFIERS ); | ||
| BitSet( m_keys[ index ].state, lastPressedKeyState ); |
There was a problem hiding this comment.
Does this overwrites the key modifiers for a Key up event, right?
It looks like the UP event is only triggered when a non-modifier key is released. Is that right?
In MetaEvent we implemented this differently I think: Each modifier release triggers a key up event. See MetaEventTranslator::onKeyModStateRemoved
So what happens is:
CTRL + SHIFT + F Down = triggers event and remembers that this combo was pressed.
Release CTRL = triggers CTRL + SHIFT + F Up event, because any of the keys were released.
The state is tracked with KeyDownInfo m_keyDownInfos[KEY_COUNT]
If these are functionally not the same, I suggest to look into making them behave the same. As far as I am aware the implementation in MetaEventTranslator is fundamentally correct, minus any bugs it may have. (It was not created or extensively reviewed with AI)
I suggest to ask the LLM how Meta Event implements the Key up events with modifiers, then how it fundamentally differs with the current Keyboard implementation, and which implementation direction makes more sense for the Keyboard.
To me it looks suspiciuos that the key UP event gets its state overwritten. It means it is not truthful anymore.
There was a problem hiding this comment.
I removed the changes to MetaEvent. It still ends the combo when a required modifier is released.
The key-up event now reports the modifiers held at release. It carries the modifiers from the original press separately. HotKeyTranslator checks both, so releasing F after Ctrl+F does not run the plain F command.
I also changed Keyboard to save m_modifiers only for non-modifier keys, as suggested.
There was a problem hiding this comment.
Ok wow the implementation now looks completely different to before 😅
| UnsignedByte key; // KeyDefType, key data | ||
| UnsignedByte status; // StatusType, above | ||
| UnsignedShort state; // KEY_STATE_* in KeyDefs.h | ||
| UnsignedShort pressedState; // Modifier flags from the matching press, for key-up events |
There was a problem hiding this comment.
This field increases the struct size from 8 to 12 bytes. Can it not be outside of it, specific for down key presses?
It is confusing why we need pressedState here and then another with m_lastPressedKeyState. I expected that m_lastPressedKeyState is enough.
| MSG_RAW_KEY_DOWN, ///< (KeyDefType) the given key was pressed (uses Microsoft VK_ codes) | ||
| MSG_RAW_KEY_UP, ///< (KeyDefType) the given key was released | ||
| MSG_RAW_KEY_DOWN, ///< (KeyDefType, current KEY_STATE_* flags) the given key was pressed | ||
| MSG_RAW_KEY_UP, ///< (KeyDefType, current KEY_STATE_* flags, modifier flags from the matching press) the given key was released |
There was a problem hiding this comment.
I suggest put a typedef UnsignedShort KeyState; below the KEY_STATE_* enum and then use that type to refer to the KeyStates everywhere it is used.
| if(newModState != 0) | ||
| const KeyDefType key = (KeyDefType)msg->getArgument(0)->integer; | ||
| const Int keyState = msg->getArgument(1)->integer; | ||
| const Int pressedKeyState = msg->getArgument(2)->integer; |
| msg->appendIntegerArgument( key->key ); | ||
| msg->appendIntegerArgument( key->state ); | ||
| if( BitIsSet( key->state, KEY_STATE_UP ) ) | ||
| msg->appendIntegerArgument( key->pressedState ); |
There was a problem hiding this comment.
Would m_lastPressedKeyState[key->key] work here?
| @@ -74,33 +73,15 @@ GameMessageDisposition HotKeyTranslator::translateGameMessage(const GameMessage | |||
|
|
|||
| if ( t == GameMessage::MSG_RAW_KEY_UP) | |||
There was a problem hiding this comment.
Would MSG_RAW_KEY_DOWN perhaps be an option for hotkeys? All the MetaEvents are key down events. Or is there a good reason why hotkey needs to be posted on down?
Thanks to DrGoldFish, who reported this issue and tested the fix.
The bug can happen in this order using Legi's keybinds:
The game can treat the last step as a normal F key press and run the F command. It should remember that F was pressed with Ctrl.
There is also a focus problem. If Ctrl or Shift is released while the game is not focused, the game may miss the release. This can leave force-attack or selection mode active.
The keyboard code reads several events at once. It previously gave every event the modifier state from the end of that group. This could give an event the wrong Ctrl, Shift, or Alt state.
The keyboard code now saves the modifier state when each event is handled. It remembers which modifier was held when a key was pressed. It passes that information to the matching key release.
HotKeyTranslatorthen knows that the release is part of a modified key press and does not run the normal hotkey.This state is stored in
Keyboardbecause another message handler may remove the key-down event beforeHotKeyTranslatorreceives it.The reset code now sends key-up events for Ctrl, Shift, and Alt. The existing
MetaEventcode is unchanged.