diff --git a/Core/GameEngine/Include/Common/MessageStream.h b/Core/GameEngine/Include/Common/MessageStream.h index e5dc1fc0896..cdd371099bd 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, KeyState) the given key was pressed + MSG_RAW_KEY_UP, ///< (KeyDefType, KeyState, KeyState from 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..825cb448262 100644 --- a/Core/GameEngine/Include/GameClient/Keyboard.h +++ b/Core/GameEngine/Include/GameClient/Keyboard.h @@ -75,7 +75,7 @@ struct KeyboardIO UnsignedByte key; // KeyDefType, key data UnsignedByte status; // StatusType, above - UnsignedShort state; // KEY_STATE_* in KeyDefs.h + KeyState state; // KEY_STATE_* in KeyDefs.h UnsignedInt keyDownTimeMsec; // real-time in milliseconds when key went down }; @@ -112,7 +112,7 @@ class Keyboard : public SubsystemInterface Bool isShift(); Bool isCtrl(); Bool isAlt(); - Int getModifierFlags() { return m_modifiers; } + KeyState getModifierFlags() { return m_modifiers; } // access methods for key data void resetKeys(); ///< reset the state of the keys @@ -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 @@ -136,10 +136,11 @@ class Keyboard : public SubsystemInterface void updateKeys(); ///< update the state of our key data Bool checkKeyRepeat(); ///< check for repeating keys UnsignedByte getKeyStatusData( KeyDefType key ); ///< get key status - Bool getKeyStateBit( KeyDefType key, Int bit ); ///< get key state bit - void setKeyStateData( KeyDefType key, UnsignedByte data ); ///< get key state + Bool getKeyStateBit( KeyDefType key, KeyState bit ); ///< get key state bit + void setKeyStateData( KeyDefType key, KeyState data ); ///< get key state - UnsignedShort m_modifiers; + KeyState m_modifiers; + KeyState 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/Include/GameClient/MetaEvent.h b/Core/GameEngine/Include/GameClient/MetaEvent.h index a610c4f5e7d..742d5822f08 100644 --- a/Core/GameEngine/Include/GameClient/MetaEvent.h +++ b/Core/GameEngine/Include/GameClient/MetaEvent.h @@ -28,6 +28,7 @@ #pragma once #include "Common/SubsystemInterface.h" +#include "GameClient/KeyDefs.h" #include "GameClient/InGameUI.h" @@ -447,10 +448,10 @@ class MetaEventTranslator : public GameMessageTranslator void onKeyEvent(const GameMessage *msg, GameMessageDisposition &disp); void onKeyModStateRemoved(GameMessageDisposition &disp, MappableKeyModState keyModState); - void onKeyPressed(GameMessageDisposition &disp, Int systemKeyState, MappableKeyType keyType, MappableKeyModState keyModState); + void onKeyPressed(GameMessageDisposition &disp, KeyState systemKeyState, MappableKeyType keyType, MappableKeyModState keyModState); static MappableKeyType getActionKeyType(Int systemKey); ///< CRTL, ALT, SHIFT will be treated as MK_NONE - static MappableKeyModState getKeyModState(Int systemKeyState); ///< Extract CTRL, ALT, SHIFT key mod state + static MappableKeyModState getKeyModState(KeyState systemKeyState); ///< Extract CTRL, ALT, SHIFT key mod state }; //----------------------------------------------------------------------------- diff --git a/Core/GameEngine/Source/GameClient/Input/Keyboard.cpp b/Core/GameEngine/Source/GameClient/Input/Keyboard.cpp index ea1c5e4425a..1e23d623a89 100644 --- a/Core/GameEngine/Source/GameClient/Input/Keyboard.cpp +++ b/Core/GameEngine/Source/GameClient/Input/Keyboard.cpp @@ -43,6 +43,23 @@ Keyboard *TheKeyboard = nullptr; // PRIVATE PROTOTYPES ///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// +//------------------------------------------------------------------------------------------------- +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; +} + //------------------------------------------------------------------------------------------------- /** Given the state of the device, create messages from the input and * place them on the message stream */ @@ -62,6 +79,12 @@ void Keyboard::createStreamMessages() // add message to stream if( BitIsSet( key->state, KEY_STATE_DOWN ) ) { + const Bool isModifier = isCtrlShiftAltKey((KeyDefType)key->key) || key->key == m_shift2Key; + if( !isModifier && !BitIsSet( key->state, KEY_STATE_AUTOREPEAT ) ) + { + // Track presses in message order, including multiple presses of one key in a frame. + m_lastPressedKeyState[key->key] = key->state; + } msg = TheMessageStream->appendMessage( GameMessage::MSG_RAW_KEY_DOWN ); DEBUG_ASSERTCRASH( msg, ("Unable to append key down message to stream") ); @@ -86,6 +109,11 @@ void Keyboard::createStreamMessages() { msg->appendIntegerArgument( key->key ); msg->appendIntegerArgument( key->state ); + if( BitIsSet( key->state, KEY_STATE_UP ) ) + { + msg->appendIntegerArgument( m_lastPressedKeyState[key->key] ); + m_lastPressedKeyState[key->key] = KEY_STATE_NONE; + } } // next key please @@ -138,17 +166,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 +187,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 +195,14 @@ 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 ); + index++; } @@ -181,22 +210,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 +246,8 @@ 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 ].status = KeyboardIO::STATUS_UNUSED; // Set End Flag @@ -699,6 +713,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 +764,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 +782,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 */ //------------------------------------------------------------------------------------------------- @@ -816,7 +841,7 @@ UnsignedByte Keyboard::getKeyStatusData( KeyDefType key ) //------------------------------------------------------------------------------------------------- /** Get the key state data as a Bool for the specified key */ //------------------------------------------------------------------------------------------------- -Bool Keyboard::getKeyStateBit( KeyDefType key, Int bit ) +Bool Keyboard::getKeyStateBit( KeyDefType key, KeyState bit ) { return (m_keyStatus[ key ].state & bit) ? 1 : 0; } @@ -832,7 +857,7 @@ void Keyboard::setKeyStatusData( KeyDefType key, KeyboardIO::StatusType data ) //------------------------------------------------------------------------------------------------- /** set the key state data */ //------------------------------------------------------------------------------------------------- -void Keyboard::setKeyStateData( KeyDefType key, UnsignedByte data ) +void Keyboard::setKeyStateData( KeyDefType key, KeyState data ) { m_keyStatus[ key ].state = data; } diff --git a/Core/GameEngine/Source/GameClient/MessageStream/HotKey.cpp b/Core/GameEngine/Source/GameClient/MessageStream/HotKey.cpp index 0b7ecf22d1e..7808b4172c9 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 KeyState keyState = (KeyState)msg->getArgument(1)->integer; + const KeyState pressedKeyState = (KeyState)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/Core/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp b/Core/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp index 31a954e67b5..c8471cd91e3 100644 --- a/Core/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp +++ b/Core/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp @@ -535,7 +535,7 @@ void MetaEventTranslator::onMouseEvent(const GameMessage *msg) void MetaEventTranslator::onKeyEvent(const GameMessage *msg, GameMessageDisposition &disp) { const Int systemKey = msg->getArgument(0)->integer; - const Int systemKeyState = msg->getArgument(1)->integer; + const KeyState systemKeyState = (KeyState)msg->getArgument(1)->integer; const MappableKeyType keyType = getActionKeyType(systemKey); const MappableKeyModState keyModState = getKeyModState(systemKeyState); @@ -598,7 +598,7 @@ void MetaEventTranslator::onKeyModStateRemoved(GameMessageDisposition &disp, Map } //------------------------------------------------------------------------------------------------- -void MetaEventTranslator::onKeyPressed(GameMessageDisposition &disp, Int systemKeyState, MappableKeyType keyType, MappableKeyModState keyModState) +void MetaEventTranslator::onKeyPressed(GameMessageDisposition &disp, KeyState systemKeyState, MappableKeyType keyType, MappableKeyModState keyModState) { // TheSuperHackers @info The regular key handler only triggers events when the mapped key is pressed, // not when the modifier (CTRL, ALT, SHIFT) is pressed, unless the key is MK_NONE. @@ -701,7 +701,7 @@ MappableKeyType MetaEventTranslator::getActionKeyType(Int systemKey) } //------------------------------------------------------------------------------------------------- -MappableKeyModState MetaEventTranslator::getKeyModState(Int systemKeyState) +MappableKeyModState MetaEventTranslator::getKeyModState(KeyState systemKeyState) { // for our purposes here, we don't care to distinguish between right and left keys, // so just fudge a little to simplify things. diff --git a/Generals/Code/GameEngine/Include/GameClient/KeyDefs.h b/Generals/Code/GameEngine/Include/GameClient/KeyDefs.h index 3c9f14394de..a37352e5aa5 100644 --- a/Generals/Code/GameEngine/Include/GameClient/KeyDefs.h +++ b/Generals/Code/GameEngine/Include/GameClient/KeyDefs.h @@ -252,10 +252,13 @@ 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) }; +typedef UnsignedShort KeyState; + // INLINING /////////////////////////////////////////////////////////////////// // EXTERNALS ////////////////////////////////////////////////////////////////// diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/KeyDefs.h b/GeneralsMD/Code/GameEngine/Include/GameClient/KeyDefs.h index 9f1978d20e0..5fcab9933ee 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/KeyDefs.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/KeyDefs.h @@ -252,10 +252,13 @@ 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) }; +typedef UnsignedShort KeyState; + // INLINING /////////////////////////////////////////////////////////////////// // EXTERNALS //////////////////////////////////////////////////////////////////