diff --git a/Core/GameEngine/Include/GameClient/Keyboard.h b/Core/GameEngine/Include/GameClient/Keyboard.h index 8c21aae40b0..8b150e10c66 100644 --- a/Core/GameEngine/Include/GameClient/Keyboard.h +++ b/Core/GameEngine/Include/GameClient/Keyboard.h @@ -124,7 +124,7 @@ class Keyboard : public SubsystemInterface WideChar getPrintableKey( KeyDefType key, Int state ); enum { MAX_KEY_STATES = 3}; private: - void refreshAltKeys() const; ///< refresh the state of the alt keys, necessary after alt tab + void emitModifierKeyUps() const; ///< emit key-ups for held CTRL/SHIFT/ALT after focus loss protected: /** get the key data for a single key, KEY_NONE should be returned when @@ -140,6 +140,7 @@ class Keyboard : public SubsystemInterface void setKeyStateData( KeyDefType key, UnsignedByte data ); ///< get key state UnsignedShort m_modifiers; + Bool m_pressedWithModifier[KEY_COUNT]; // internal keyboard data members //Bool m_capsState; // 1 if caps lock is on //Bool m_shiftState; // 1 if either shift key is pressed diff --git a/Core/GameEngine/Source/GameClient/Input/Keyboard.cpp b/Core/GameEngine/Source/GameClient/Input/Keyboard.cpp index ea1c5e4425a..219773dd9a2 100644 --- a/Core/GameEngine/Source/GameClient/Input/Keyboard.cpp +++ b/Core/GameEngine/Source/GameClient/Input/Keyboard.cpp @@ -138,17 +138,20 @@ void Keyboard::updateKeys() /** @todo -- if we don't have focus, we could destroy all the keys retrieved here so that we don't process anything */ - m_keyStatus[ m_keys[ index ].key ].state = m_keys[ index ].state; - m_keyStatus[ m_keys[ index ].key ].status = m_keys[ index ].status; + const KeyDefType key = (KeyDefType)m_keys[ index ].key; + const Bool pressedWithModifier = m_pressedWithModifier[key]; + + m_keyStatus[ key ].state = m_keys[ index ].state; + m_keyStatus[ key ].status = m_keys[ index ].status; // Update key down time for new key presses if( BitIsSet( m_keys[ index ].state, KEY_STATE_DOWN ) ) { - m_keyStatus[ m_keys[ index ].key ].keyDownTimeMsec = m_keys[ index ].keyDownTimeMsec; + m_keyStatus[ key ].keyDownTimeMsec = m_keys[ index ].keyDownTimeMsec; } // prevent ALT-TAB from causing a TAB event - if( m_keys[ index ].key == KEY_TAB ) + if( key == KEY_TAB ) { if( BitIsSet( m_keyStatus[ KEY_LALT ].state, KEY_STATE_DOWN ) || BitIsSet( m_keyStatus[ KEY_RALT ].state, KEY_STATE_DOWN ) ) @@ -156,13 +159,13 @@ void Keyboard::updateKeys() m_keys[index].status = KeyboardIO::STATUS_USED; } } - else if( m_keys[ index ].key == KEY_CAPS || - m_keys[ index ].key == KEY_LCTRL || - m_keys[ index ].key == KEY_RCTRL || - m_keys[ index ].key == KEY_LSHIFT || - m_keys[ index ].key == KEY_RSHIFT || - m_keys[ index ].key == KEY_LALT || - m_keys[ index ].key == KEY_RALT ) + else if( key == KEY_CAPS || + key == KEY_LCTRL || + key == KEY_RCTRL || + key == KEY_LSHIFT || + key == KEY_RSHIFT || + key == KEY_LALT || + key == KEY_RALT ) { @@ -170,10 +173,29 @@ void Keyboard::updateKeys() // this keeps our internal key state accurate event though we don't // use the returned translation ... kinda weird I think // - translateKey( m_keys[ index ].key ); + translateKey( key ); } + // TheSuperHackers @bugfix CryoTheRenegade 31/08/2026 Preserve modifier state for + // each buffered event and carry it from a key-down to its matching key-up. + BitSet( m_keys[ index ].state, m_modifiers ); + if( BitIsSet( m_keys[ index ].state, KEY_STATE_DOWN ) ) + { + const Int keyModifiers = KEY_STATE_CONTROL | KEY_STATE_SHIFT | KEY_STATE_ALT; + m_pressedWithModifier[key] = (m_modifiers & keyModifiers) != 0; + if( m_pressedWithModifier[key] ) + { + BitSet( m_keys[ index ].state, KEY_STATE_MODIFIER_ON_DOWN ); + } + } + else + { + m_pressedWithModifier[key] = FALSE; + if( pressedWithModifier ) + BitSet( m_keys[ index ].state, KEY_STATE_MODIFIER_ON_DOWN ); + } + index++; } @@ -181,22 +203,6 @@ void Keyboard::updateKeys() // check for key repeats checkKeyRepeat(); - if( m_modifiers ) - { - index = 0; - while( m_keys[ index ].key != KEY_NONE ) - { - - // set in the modifier data into the already existing up/down state - BitSet( m_keys[ index ].state, m_modifiers ); - - // next key - index++; - - } - - } - } //------------------------------------------------------------------------------------------------- @@ -233,7 +239,9 @@ Bool Keyboard::checkKeyRepeat() { // Add key to this frame m_keys[ index ].key = (UnsignedByte)key; - m_keys[ index ].state = KEY_STATE_DOWN | KEY_STATE_AUTOREPEAT; // note: not a bitset; this is an assignment + // This is an assignment, not a bit set. + m_keys[ index ].state = KEY_STATE_DOWN | KEY_STATE_AUTOREPEAT | m_modifiers + | (m_pressedWithModifier[key] ? KEY_STATE_MODIFIER_ON_DOWN : 0); m_keys[ index ].status = KeyboardIO::STATUS_UNUSED; // Set End Flag @@ -699,6 +707,7 @@ Keyboard::Keyboard() memset( m_keys, 0, sizeof( m_keys ) ); memset( m_keyStatus, 0, sizeof( m_keyStatus ) ); + memset( m_pressedWithModifier, 0, sizeof( m_pressedWithModifier ) ); m_modifiers = KEY_STATE_NONE; m_shift2Key = KEY_NONE; @@ -749,13 +758,15 @@ void Keyboard::update() //------------------------------------------------------------------------------------------------- void Keyboard::resetKeys() { - // TheSuperHackers @fix Caball009 13/12/2025 Fix bug where game remains in waypoint mode // because the key up state for the alt key is not detected after alt tab. - refreshAltKeys(); + // CTRL and SHIFT have the same stuck-mode problem (force-attack, prefer-selection). + emitModifierKeyUps(); memset( m_keys, 0, sizeof( m_keys ) ); memset( m_keyStatus, 0, sizeof( m_keyStatus ) ); + // A held key can still report its release after focus returns. Do not clear + // m_pressedWithModifier until that release or a new press arrives. m_modifiers = KEY_STATE_NONE; if( getCapsState() ) { @@ -765,24 +776,29 @@ void Keyboard::resetKeys() } //------------------------------------------------------------------------------------------------- -// Refresh the state of the alt keys, necessary after alt tab -//------------------------------------------------------------------------------------------------- -void Keyboard::refreshAltKeys() const +static void emitRawKeyUpIfDown(const KeyboardIO *keyStatus, KeyDefType key) { - if (BitIsSet(m_keyStatus[KEY_LALT].state, KEY_STATE_DOWN)) + if (BitIsSet(keyStatus[key].state, KEY_STATE_DOWN)) { GameMessage* msg = TheMessageStream->appendMessage(GameMessage::MSG_RAW_KEY_UP); - msg->appendIntegerArgument(KEY_LALT); - msg->appendIntegerArgument(KEY_STATE_UP); - } - if (BitIsSet(m_keyStatus[KEY_RALT].state, KEY_STATE_DOWN)) - { - GameMessage* msg = TheMessageStream->appendMessage(GameMessage::MSG_RAW_KEY_UP); - msg->appendIntegerArgument(KEY_RALT); + msg->appendIntegerArgument(key); msg->appendIntegerArgument(KEY_STATE_UP); } } +//------------------------------------------------------------------------------------------------- +// Emit RAW_KEY_UP for still-held modifiers so MetaEvent can end force-attack / waypoints / etc. +//------------------------------------------------------------------------------------------------- +void Keyboard::emitModifierKeyUps() const +{ + emitRawKeyUpIfDown(m_keyStatus, KEY_LCTRL); + emitRawKeyUpIfDown(m_keyStatus, KEY_RCTRL); + emitRawKeyUpIfDown(m_keyStatus, KEY_LSHIFT); + emitRawKeyUpIfDown(m_keyStatus, KEY_RSHIFT); + emitRawKeyUpIfDown(m_keyStatus, KEY_LALT); + emitRawKeyUpIfDown(m_keyStatus, KEY_RALT); +} + //------------------------------------------------------------------------------------------------- /** get the first key in our current state of the keyboard */ //------------------------------------------------------------------------------------------------- diff --git a/Core/GameEngine/Source/GameClient/MessageStream/HotKey.cpp b/Core/GameEngine/Source/GameClient/MessageStream/HotKey.cpp index 0b7ecf22d1e..9c3dc0d1e56 100644 --- a/Core/GameEngine/Source/GameClient/MessageStream/HotKey.cpp +++ b/Core/GameEngine/Source/GameClient/MessageStream/HotKey.cpp @@ -52,7 +52,6 @@ //----------------------------------------------------------------------------- #include "GameClient/HotKey.h" #include "GameClient/KeyDefs.h" -#include "GameClient/MetaEvent.h" #include "GameClient/GameWindow.h" #include "GameClient/GameWindowManager.h" #include "GameClient/Keyboard.h" @@ -74,33 +73,15 @@ GameMessageDisposition HotKeyTranslator::translateGameMessage(const GameMessage if ( t == GameMessage::MSG_RAW_KEY_UP) { - - //char key = msg->getArgument(0)->integer; - Int keyState = msg->getArgument(1)->integer; - - // for our purposes here, we don't care to distinguish between right and left keys, - // so just fudge a little to simplify things. - Int newModState = 0; - - if( keyState & KEY_STATE_CONTROL ) - { - newModState |= CTRL; - } - - if( keyState & KEY_STATE_SHIFT ) - { - newModState |= SHIFT; - } - - if( keyState & KEY_STATE_ALT ) - { - newModState |= ALT; - } - if(newModState != 0) + const KeyDefType key = (KeyDefType)msg->getArgument(0)->integer; + const Int keyState = msg->getArgument(1)->integer; + const Int ignoredModifiers = KEY_STATE_CONTROL | KEY_STATE_SHIFT | KEY_STATE_ALT | KEY_STATE_MODIFIER_ON_DOWN; + if( keyState & ignoredModifiers ) return disp; - WideChar key = TheKeyboard->getPrintableKey((KeyDefType)msg->getArgument(0)->integer, 0); + + WideChar printableKey = TheKeyboard->getPrintableKey(key, 0); UnicodeString uKey; - uKey.concat(key); + uKey.concat(printableKey); AsciiString aKey; aKey.translate(uKey); if(TheHotKeyManager && TheHotKeyManager->executeHotKey(aKey)) diff --git a/Generals/Code/GameEngine/Include/GameClient/KeyDefs.h b/Generals/Code/GameEngine/Include/GameClient/KeyDefs.h index 3c9f14394de..c195dd0312b 100644 --- a/Generals/Code/GameEngine/Include/GameClient/KeyDefs.h +++ b/Generals/Code/GameEngine/Include/GameClient/KeyDefs.h @@ -248,6 +248,7 @@ enum KEY_STATE_AUTOREPEAT = 0x0100, // Key is down due to autorepeat (only seen in conjunction with KEY_STATE_DOWN) KEY_STATE_CAPSLOCK = 0x0200, // Caps Lock key is on. KEY_STATE_SHIFT2 = 0x0400, // Alternate shift key is pressed (I think this is for foreign keyboards..) + KEY_STATE_MODIFIER_ON_DOWN = 0x0800, // CTRL, SHIFT, or ALT was held when this key was pressed. // modifier combinations when left/right isn't a factor KEY_STATE_CONTROL = (KEY_STATE_LCONTROL | KEY_STATE_RCONTROL), diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/KeyDefs.h b/GeneralsMD/Code/GameEngine/Include/GameClient/KeyDefs.h index 9f1978d20e0..cca8b33100b 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/KeyDefs.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/KeyDefs.h @@ -248,6 +248,7 @@ enum KEY_STATE_AUTOREPEAT = 0x0100, // Key is down due to autorepeat (only seen in conjunction with KEY_STATE_DOWN) KEY_STATE_CAPSLOCK = 0x0200, // Caps Lock key is on. KEY_STATE_SHIFT2 = 0x0400, // Alternate shift key is pressed (I think this is for foreign keyboards..) + KEY_STATE_MODIFIER_ON_DOWN = 0x0800, // CTRL, SHIFT, or ALT was held when this key was pressed. // modifier combinations when left/right isn't a factor KEY_STATE_CONTROL = (KEY_STATE_LCONTROL | KEY_STATE_RCONTROL),