diff --git a/Core/GameEngine/Include/Common/MessageStream.h b/Core/GameEngine/Include/Common/MessageStream.h index e5dc1fc0896..eca4f579288 100644 --- a/Core/GameEngine/Include/Common/MessageStream.h +++ b/Core/GameEngine/Include/Common/MessageStream.h @@ -133,8 +133,8 @@ class GameMessage : public MemoryPoolObject MSG_RAW_MOUSE_WHEEL, ///< (Real spin, + is away, - is toward user) MSG_RAW_MOUSE_END, - 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 // Refined Mouse messages // NOTE: All processing should attempt to use these refined mouse messages, rather than the diff --git a/Core/GameEngine/Include/GameClient/Keyboard.h b/Core/GameEngine/Include/GameClient/Keyboard.h index 8c21aae40b0..1caa08c8cfb 100644 --- a/Core/GameEngine/Include/GameClient/Keyboard.h +++ b/Core/GameEngine/Include/GameClient/Keyboard.h @@ -76,6 +76,7 @@ struct KeyboardIO 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 UnsignedInt keyDownTimeMsec; // real-time in milliseconds when key went down }; @@ -124,7 +125,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 +141,7 @@ class Keyboard : public SubsystemInterface void setKeyStateData( KeyDefType key, UnsignedByte data ); ///< get key state UnsignedShort m_modifiers; + UnsignedShort m_lastPressedKeyState[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..e8eccef796d 100644 --- a/Core/GameEngine/Source/GameClient/Input/Keyboard.cpp +++ b/Core/GameEngine/Source/GameClient/Input/Keyboard.cpp @@ -86,6 +86,8 @@ void Keyboard::createStreamMessages() { msg->appendIntegerArgument( key->key ); msg->appendIntegerArgument( key->state ); + if( BitIsSet( key->state, KEY_STATE_UP ) ) + msg->appendIntegerArgument( key->pressedState ); } // next key please @@ -96,6 +98,23 @@ void Keyboard::createStreamMessages() } +//------------------------------------------------------------------------------------------------- +static Bool isCtrlShiftAltKey(KeyDefType key) +{ + switch (key) + { + case KEY_LCTRL: + case KEY_RCTRL: + case KEY_LSHIFT: + case KEY_RSHIFT: + case KEY_LALT: + case KEY_RALT: + return TRUE; + } + + return FALSE; +} + //------------------------------------------------------------------------------------------------- /** update all our key state data */ //------------------------------------------------------------------------------------------------- @@ -138,17 +157,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 isModifier = isCtrlShiftAltKey(key) || key == m_shift2Key; + + 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 +178,7 @@ 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 || isModifier ) { @@ -170,10 +186,28 @@ 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 the current + // modifier state for each buffered event. + BitSet( m_keys[ index ].state, m_modifiers ); + m_keys[ index ].pressedState = KEY_STATE_NONE; + if( !isModifier ) + { + if( BitIsSet( m_keys[ index ].state, KEY_STATE_DOWN ) ) + { + m_lastPressedKeyState[key] = m_modifiers; + } + else + { + // Keep the press state separate so a modified release cannot fire a plain hotkey. + m_keys[ index ].pressedState = m_lastPressedKeyState[key]; + m_lastPressedKeyState[key] = KEY_STATE_NONE; + } + } + index++; } @@ -181,22 +215,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 +251,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_keys[ index ].pressedState = KEY_STATE_NONE; m_keys[ index ].status = KeyboardIO::STATUS_UNUSED; // Set End Flag @@ -699,6 +719,7 @@ Keyboard::Keyboard() memset( m_keys, 0, sizeof( m_keys ) ); memset( m_keyStatus, 0, sizeof( m_keyStatus ) ); + memset( m_lastPressedKeyState, 0, sizeof( m_lastPressedKeyState ) ); m_modifiers = KEY_STATE_NONE; m_shift2Key = KEY_NONE; @@ -749,13 +770,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_lastPressedKeyState until that release or a new press arrives. m_modifiers = KEY_STATE_NONE; if( getCapsState() ) { @@ -765,24 +788,32 @@ 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)) - { - 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)) + if (BitIsSet(keyStatus[key].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); + msg->appendIntegerArgument(KEY_STATE_NONE); } } +//------------------------------------------------------------------------------------------------- +// 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); + if (m_shift2Key != KEY_NONE && !isCtrlShiftAltKey(m_shift2Key)) + emitRawKeyUpIfDown(m_keyStatus, m_shift2Key); +} + //------------------------------------------------------------------------------------------------- /** 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..2c55afdbb58 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 pressedKeyState = msg->getArgument(2)->integer; + if( (keyState | pressedKeyState) & KEY_STATE_MODIFIERS ) 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..dd24b1b6a3c 100644 --- a/Generals/Code/GameEngine/Include/GameClient/KeyDefs.h +++ b/Generals/Code/GameEngine/Include/GameClient/KeyDefs.h @@ -252,7 +252,8 @@ enum // modifier combinations when left/right isn't a factor KEY_STATE_CONTROL = (KEY_STATE_LCONTROL | KEY_STATE_RCONTROL), KEY_STATE_SHIFT = (KEY_STATE_LSHIFT | KEY_STATE_RSHIFT | KEY_STATE_SHIFT2 ), - KEY_STATE_ALT = (KEY_STATE_LALT | KEY_STATE_RALT) + KEY_STATE_ALT = (KEY_STATE_LALT | KEY_STATE_RALT), + KEY_STATE_MODIFIERS = (KEY_STATE_CONTROL | KEY_STATE_SHIFT | KEY_STATE_ALT) }; diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/KeyDefs.h b/GeneralsMD/Code/GameEngine/Include/GameClient/KeyDefs.h index 9f1978d20e0..ae8a0989c77 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/KeyDefs.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/KeyDefs.h @@ -252,7 +252,8 @@ enum // modifier combinations when left/right isn't a factor KEY_STATE_CONTROL = (KEY_STATE_LCONTROL | KEY_STATE_RCONTROL), KEY_STATE_SHIFT = (KEY_STATE_LSHIFT | KEY_STATE_RSHIFT | KEY_STATE_SHIFT2 ), - KEY_STATE_ALT = (KEY_STATE_LALT | KEY_STATE_RALT) + KEY_STATE_ALT = (KEY_STATE_LALT | KEY_STATE_RALT), + KEY_STATE_MODIFIERS = (KEY_STATE_CONTROL | KEY_STATE_SHIFT | KEY_STATE_ALT) };