//===== Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ======// // // Purpose: // // $NoKeywords: $ // //===========================================================================// #include "keys.h" #include "cdll_engine_int.h" #include "cmd.h" #include "toolframework/itoolframework.h" #include "toolframework/itoolsystem.h" #include "tier1/utlbuffer.h" #include "vgui_baseui_interface.h" #include "tier2/tier2.h" #include "inputsystem/iinputsystem.h" #include "keyvalues.h" #include "host.h" #include "filesystem.h" #include "filesystem_engine.h" #include "cheatcodes.h" #include "baseclientstate.h" // splitscreen interface #include "tier1/fmtstr.h" #include "EngineSoundInternal.h" #ifndef DEDICATED #include "cl_splitscreen.h" #endif #if defined( INCLUDE_SCALEFORM ) #include "scaleformui/scaleformui.h" #endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" ConVar in_forceuser( "in_forceuser", "0", FCVAR_CHEAT, "Force user input to this split screen player." ); enum KeyUpTarget_t { KEY_UP_ANYTARGET = 0, KEY_UP_ENGINE, KEY_UP_VGUI, KEY_UP_TOOLS, KEY_UP_CLIENT, KEY_UP_GAMEUI, KEY_UP_SCALEFORM, KEY_UP_OVERLAY, KEY_UP_TARGET_COUNT }; struct KeyInfo_t { char *m_pKeyBinding; unsigned char m_nKeyUpTarget : 4; // see KeyUpTarget_t unsigned char m_bKeyDown : 1; }; //----------------------------------------------------------------------------- // Current keypress state //----------------------------------------------------------------------------- struct KeyContext_t { KeyContext_t() { // NOTE: If this compile-time assert fails, modify the bit count // in KeyInfo_t::m_nKeyUpTarget COMPILE_TIME_ASSERT( KEY_UP_TARGET_COUNT < 16 ); Q_memset( m_pKeyInfo, 0, sizeof( m_pKeyInfo ) ); m_bTrapMode = false; m_bDoneTrapping = false; m_nTrapKeyUp = BUTTON_CODE_INVALID; m_nTrapKey = BUTTON_CODE_INVALID; } KeyInfo_t m_pKeyInfo[BUTTON_CODE_LAST]; //----------------------------------------------------------------------------- // Trap mode is used by the keybinding UI //----------------------------------------------------------------------------- bool m_bTrapMode; bool m_bDoneTrapping; ButtonCode_t m_nTrapKeyUp; ButtonCode_t m_nTrapKey; }; static KeyContext_t s_KeyContext; //----------------------------------------------------------------------------- // Can keys be passed to various targets? //----------------------------------------------------------------------------- static inline bool ShouldPassKeyUpToTarget( ButtonCode_t code, KeyUpTarget_t target ) { if (code < 0 || code >= BUTTON_CODE_LAST) return true; return ( s_KeyContext.m_pKeyInfo[code].m_nKeyUpTarget == target ) || ( s_KeyContext.m_pKeyInfo[code].m_nKeyUpTarget == KEY_UP_ANYTARGET ); } ButtonCode_t GetSplitPlayerJoystickCode( ButtonCode_t bc ) { #ifdef DEDICATED return bc; #else if ( !IsJoystickCode( bc ) && !IsSteamControllerCode( bc ) ) return bc; int nJoystick = GET_ACTIVE_SPLITSCREEN_SLOT(); return ButtonCodeToJoystickButtonCode( bc, nJoystick ); #endif } void Key_SetBinding( ButtonCode_t keynum, const char *pBinding ) { char *pNewBinding; int l; if ( keynum == BUTTON_CODE_INVALID ) return; keynum = GetSplitPlayerJoystickCode( keynum ); // free old bindings if ( s_KeyContext.m_pKeyInfo[keynum].m_pKeyBinding ) { // Exactly the same, don't re-bind and fragment memory if ( !Q_strcmp( s_KeyContext.m_pKeyInfo[keynum].m_pKeyBinding, pBinding ) ) return; delete[] s_KeyContext.m_pKeyInfo[keynum].m_pKeyBinding; s_KeyContext.m_pKeyInfo[keynum].m_pKeyBinding = NULL; } // allocate memory for new binding l = Q_strlen( pBinding ); pNewBinding = (char *)new char[ l+1 ]; Q_strncpy( pNewBinding, pBinding, l + 1 ); pNewBinding[l] = 0; s_KeyContext.m_pKeyInfo[keynum].m_pKeyBinding = pNewBinding; #if defined( INCLUDE_SCALEFORM ) if ( g_pScaleformUI ) { g_pScaleformUI->UpdateBindingForButton( keynum, pBinding ); } #endif #ifndef DEDICATED if ( g_ClientDLL ) { g_ClientDLL->OnKeyBindingChanged( keynum, g_pInputSystem->ButtonCodeToString( (ButtonCode_t)keynum ), pNewBinding ); } #endif } /* =================== Key_Unbind_f =================== */ CON_COMMAND( unbind, "Unbind a key." ) { ButtonCode_t b; if ( args.ArgC() != 2 ) { ConMsg( "unbind : remove commands from a key\n" ); return; } b = g_pInputSystem->StringToButtonCode( args[1] ); if ( b == BUTTON_CODE_INVALID ) { ConMsg( "\"%s\" isn't a valid key\n", args[1] ); return; } if ( b == KEY_ESCAPE ) { ConMsg( "Can't unbind ESCAPE key\n" ); return; } Key_SetBinding( b, "" ); } CON_COMMAND( unbindall, "Unbind all keys." ) { int i; for ( i=0; iHideGameUI(); } #endif /* =================== Key_Bind_f =================== */ static void BindHelper( const CCommand &args ) { int i, c; ButtonCode_t b; char cmd[1024]; c = args.ArgC(); if ( c != 2 && c != 3 ) { ConMsg( "bind [command] : attach a command to a key\n" ); return; } b = g_pInputSystem->StringToButtonCode( args[1] ); if ( b == BUTTON_CODE_INVALID ) { ConMsg( "\"%s\" isn't a valid key\n", args[1] ); return; } if ( c == 2 ) { b = GetSplitPlayerJoystickCode( b ); if (s_KeyContext.m_pKeyInfo[b].m_pKeyBinding) { ConMsg( "\"%s\" = \"%s\"\n", args[1], s_KeyContext.m_pKeyInfo[b].m_pKeyBinding ); } else { ConMsg( "\"%s\" is not bound\n", args[1] ); } return; } if ( b == KEY_ESCAPE ) { Q_strncpy( cmd, "cancelselect", sizeof( cmd ) ); } else { // copy the rest of the command line cmd[0] = 0; // start out with a null string for ( i=2 ; i< c ; i++ ) { if (i > 2) { Q_strncat( cmd, " ", sizeof( cmd ), COPY_ALL_CHARACTERS ); } Q_strncat( cmd, args[i], sizeof( cmd ), COPY_ALL_CHARACTERS ); } } Key_SetBinding( b, cmd ); } CON_COMMAND( bind, "Bind a key." ) { BindHelper( args ); } CON_COMMAND( bind_osx, "Bind a key for OSX only." ) { if ( IsOSX() ) BindHelper( args ); } /* ============ GetDefaultKeyBindings Returns a KeyValues object holding default keybindings. Caller is responsible for freeing the KeyValues object. ============ */ KeyValues *GetDefaultKeyBindings( void ) { KeyValues *defaults = new KeyValues( "defaults" ); FileHandle_t f; char szFileName[ _MAX_PATH ]; char token[ 1024 ]; char szKeyName[ 256 ]; // read kb_def file to get default key binds Q_snprintf( szFileName, sizeof( szFileName ), "%skb_def" PLATFORM_EXT ".lst", SCRIPT_DIR ); f = g_pFileSystem->Open( szFileName, "r"); if ( !f ) { ConMsg( "Couldn't open kb_def.lst\n" ); return defaults; } // read file into memory int size = g_pFileSystem->Size(f); char *startbuf = new char[ size + 1 ]; g_pFileSystem->Read( startbuf, size, f ); g_pFileSystem->Close( f ); startbuf[size] = 0; const char *buf = startbuf; while ( 1 ) { buf = COM_ParseFile( buf, token, sizeof( token ) ); if ( strlen( token ) <= 0 ) break; Q_strncpy ( szKeyName, token, sizeof( szKeyName ) ); buf = COM_ParseFile( buf, token, sizeof( token ) ); if ( strlen( token ) <= 0 ) // Error break; defaults->SetString( token, szKeyName ); } delete [] startbuf; // cleanup on the way out return defaults; } const char *GetSuggestedBinding( const char *command, KeyValues *defaults ) { if ( !defaults ) return NULL; const char *suggestedKeyString = defaults->GetString( command, NULL ); if ( suggestedKeyString ) { return suggestedKeyString; } // If no exact match, scan for substring matches for ( KeyValues *keybind = defaults->GetFirstSubKey(); keybind != NULL; keybind = keybind->GetNextKey() ) { const char *suggestedKeyString = keybind->GetString(); const char *suggestedCommand = keybind->GetName(); const char *subStr = V_stristr( suggestedCommand, command ); if ( subStr != NULL ) { return suggestedKeyString; } } return NULL; } /* ============ Key_ForceBind_f Find empty keys for the incoming command strings and bind them ============ */ void Key_ForceBind_f( const CCommand &args ) { int argc = args.ArgC(); if ( argc < 2 ) return; KeyValues *defaults = GetDefaultKeyBindings(); Color boundColor( 0, 255, 64, 255 ); Color unboundColor( 255, 0, 64, 255 ); for ( int arg = 1; argStringToButtonCode( currentKeyBinding ); if ( key == - 1 || key == KEY_ESCAPE ) { continue; } ConColorMsg( boundColor, "Unbound obsolete command \"%s\"\n", command ); Key_SetBinding( key, "" ); continue; } if ( currentKeyBinding != NULL ) continue; // Already bound if ( suggestedKeyString == NULL ) continue; // Was a key to be unbound ButtonCode_t suggestedKeynum = g_pInputSystem->StringToButtonCode( suggestedKeyString ); const char *suggestedBind = Key_BindingForKey( suggestedKeynum ); if ( suggestedKeynum != -1 && (suggestedBind == NULL || *suggestedBind == '\0') ) { ConColorMsg( boundColor, "Bound \"%s\" to key %s\n", command, suggestedKeyString ); Key_SetBinding(suggestedKeynum, command); } else { // Their suggestion was taken, so we need to find a key since we can't return empty handed. int newTry = suggestedKeynum + 1; if ( newTry > KEY_SCROLLLOCKTOGGLE ) newTry = KEY_0; while( newTry != suggestedKeynum ) { const char *newString = g_pInputSystem->ButtonCodeToString( (ButtonCode_t)newTry ); const char *newTryBind = Key_BindingForKey( (ButtonCode_t)newTry ); if ( newTryBind == NULL || *newTryBind == '\0' ) // Never set, or unset. { ConColorMsg( boundColor, "Bound \"%s\" to key %s\n", command, newString ); Key_SetBinding( (ButtonCode_t)newTry, command ); break; } newTry++; if ( newTry > KEY_SCROLLLOCKTOGGLE ) newTry = KEY_0; } } if ( Key_NameForBinding(command) == NULL ) { ConColorMsg( unboundColor, "Unable to bind \"%s\" to a key\n", command ); } } defaults->deleteThis(); } static ConCommand forcebind("forcebind",Key_ForceBind_f, "Bind a command to an available key. (forcebind command opt:suggestedKey)" ); /* ============ Key_CountBindings Count number of lines of bindings we'll be writing ============ */ int Key_CountBindings( void ) { int i; int c = 0; for ( i = 0; i < BUTTON_CODE_LAST; i++ ) { if ( !s_KeyContext.m_pKeyInfo[i].m_pKeyBinding || !s_KeyContext.m_pKeyInfo[i].m_pKeyBinding[0] ) continue; c++; } return c; } /* ============ Key_WriteBindings Writes lines containing "bind key value" ============ */ void Key_WriteBindings( CUtlBuffer &buf, const int iSplitscreenSlot /*= -1*/ ) { int i; CUtlVector< CUtlString > deferred[ MAX_SPLITSCREEN_CLIENTS ]; for ( i = 0 ; i < BUTTON_CODE_LAST ; i++ ) { char const *pszBinding = s_KeyContext.m_pKeyInfo[i].m_pKeyBinding; if ( !pszBinding || !*pszBinding) continue; char const *pszKeyName = g_pInputSystem->ButtonCodeToString( (ButtonCode_t)i ); int nSlot = GetJoystickForCode( (ButtonCode_t)i ); if ( iSplitscreenSlot >= 0 ) { // only bind commands for this controller, don't use cmd2 if ( iSplitscreenSlot != nSlot ) continue; buf.Printf( "bind \"%s\" \"%s\"\n", pszKeyName, pszBinding ); } else { if ( nSlot == 0 ) { buf.Printf( "bind \"%s\" \"%s\"\n", pszKeyName, pszBinding ); } else { CUtlString str; str = CFmtStrN< 2048 >( "cmd%d bind \"%s\" \"%s\"\n", nSlot + 1, pszKeyName, pszBinding ); deferred[ nSlot ].AddToTail( str ); } } } for ( int nSlot = 1; nSlot < host_state.max_splitscreen_players; ++nSlot ) { int c = deferred[ nSlot ].Count(); for ( int i = 0; i < c; ++i ) { buf.Printf( "%s", deferred[ nSlot ][ i ].String() ); } } } /* ============ Key_NameForBinding Returns the keyname to which a binding string is bound. E.g., if TAB is bound to +use then searching for +use will return "TAB" ============ */ static bool IsKeyBoundedToBinding( int i, const char* pBind ) { char *pszBinding = s_KeyContext.m_pKeyInfo[i].m_pKeyBinding; if ( pszBinding && *pszBinding ) { if ( !strchr( pszBinding, ';' ) ) { // Not a list of commands, do it the easy way if ( pszBinding[ 0 ] == '+' ) { ++pszBinding; } if ( !Q_strcasecmp( pszBinding, (char *)pBind ) ) { return true; } } else { // Tokenize the binding name (could be more than one binding) char szBinding[ 256 ]; Q_strncpy( szBinding, pszBinding, sizeof( szBinding ) ); pszBinding = strtok( szBinding, ";" ); while ( pszBinding ) { if ( pszBinding[ 0 ] == '+' ) { ++pszBinding; } if ( !Q_strcasecmp( pszBinding, (char *)pBind ) ) { return true; } pszBinding = strtok( NULL, ";" ); } } } return false; } const char *Key_NameForBinding( const char *pBinding, int userId, int iStartCount, BindingLookupOption_t nFlags ) { int i = Key_CodeForBinding( pBinding, userId, iStartCount, nFlags ); if( pBinding && i != BUTTON_CODE_INVALID ) { const char *pBind = pBinding; if ( pBinding[0] == '+' ) { ++pBind; } if ( IsKeyBoundedToBinding( i, pBind ) ) { return g_pInputSystem->ButtonCodeToString( (ButtonCode_t)i ); } } return NULL; } /* ============ Key_CodeForBinding Returns the keycode to which a binding string is bound. E.g., if TAB is bound to +use then searching for +use will return KEY_TAB ============ */ int Key_CodeForBinding( const char *pBinding, int userId, int iStartCount, BindingLookupOption_t nFlags ) { if( pBinding ) { const char *pBind = pBinding; if ( pBinding[0] == '+' ) { ++pBind; } int iCount = 0; if ( IsPC() || userId < 0 || nFlags == BINDINGLOOKUP_ALL ) { for( int i=0 ; i < BUTTON_CODE_LAST ; i++ ) { if( IsKeyBoundedToBinding( i, pBind ) ) { if ( nFlags == BINDINGLOOKUP_ALL || ( nFlags == BINDINGLOOKUP_STEAMCONTROLLER_ONLY && ( i >= STEAMCONTROLLER_FIRST && i <= STEAMCONTROLLER_LAST ) ) || ( nFlags == BINDINGLOOKUP_KEYBOARD_ONLY && ( i < JOYSTICK_FIRST || i > JOYSTICK_LAST ) && ( i < STEAMCONTROLLER_FIRST || i > STEAMCONTROLLER_LAST ) ) || ( nFlags == BINDINGLOOKUP_JOYSTICK_ONLY && ( i >= JOYSTICK_FIRST && i <= JOYSTICK_LAST ) ) ) { if ( iCount == iStartCount ) { return i; } else { ++iCount; } } } } } else { int first = userId * JOYSTICK_MAX_BUTTON_COUNT + JOYSTICK_FIRST_BUTTON; int last = first + JOYSTICK_MAX_BUTTON_COUNT; for( int i = first; i < last; ++i ) { if( IsKeyBoundedToBinding( i, pBind ) ) { if ( iCount == iStartCount ) { return i; } else { ++iCount; } } } first = userId * JOYSTICK_POV_BUTTON_COUNT + JOYSTICK_FIRST_POV_BUTTON; last = first + JOYSTICK_POV_BUTTON_COUNT; for( int i = first; i < last; ++i ) { if( IsKeyBoundedToBinding( i, pBind ) ) { if ( iCount == iStartCount ) { return i; } else { ++iCount; } } } first = userId * JOYSTICK_AXIS_BUTTON_COUNT + JOYSTICK_FIRST_AXIS_BUTTON; last = first + JOYSTICK_AXIS_BUTTON_COUNT; for( int i = first; i < last; ++i ) { if( IsKeyBoundedToBinding( i, pBind ) ) { if ( iCount == iStartCount ) { return i; } else { ++iCount; } } } first = userId * STEAMCONTROLLER_MAX_BUTTON_COUNT + STEAMCONTROLLER_FIRST_BUTTON; last = first + STEAMCONTROLLER_MAX_BUTTON_COUNT; for( int i = first; i < last; ++i ) { if( IsKeyBoundedToBinding( i, pBind ) ) { if ( iCount == iStartCount ) { return i; } else { ++iCount; } } } first = userId * STEAMCONTROLLER_AXIS_BUTTON_COUNT + STEAMCONTROLLER_FIRST_AXIS_BUTTON; last = first + STEAMCONTROLLER_AXIS_BUTTON_COUNT; for( int i = first; i < last; ++i ) { if( IsKeyBoundedToBinding( i, pBind ) ) { if ( iCount == iStartCount ) { return i; } else { ++iCount; } } } } // Xbox 360 controller: Handle the dual bindings for duck and zoom if ( !Q_stricmp( "duck", pBind ) ) return Key_CodeForBinding( "toggle_duck", userId, iCount ); if ( !Q_stricmp( "zoom", pBind ) ) return Key_CodeForBinding( "toggle_zoom", userId, iCount ); } return BUTTON_CODE_INVALID; } const char *Key_BindingForKey( ButtonCode_t code ) { if ( code < 0 || code > BUTTON_CODE_LAST ) return NULL; return s_KeyContext.m_pKeyInfo[ code ].m_pKeyBinding; } CON_COMMAND( key_listboundkeys, "List bound keys with bindings." ) { int i; for (i=0 ; iButtonCodeToString( (ButtonCode_t)i ), pBinding ); } else { int nSlot = GetJoystickForCode( (ButtonCode_t)i ); ConMsg( "[%d:%d]\"%s\" = \"%s\"\n", i, nSlot, g_pInputSystem->ButtonCodeToString( (ButtonCode_t)i ), pBinding ); } } } CON_COMMAND( key_findbinding, "Find key bound to specified command string." ) { if ( args.ArgC() != 2 ) { ConMsg( "usage: key_findbinding substring\n" ); return; } const char *substring = args[1]; if ( !substring || !substring[ 0 ] ) { ConMsg( "usage: key_findbinding substring\n" ); return; } int i; for (i=0 ; iButtonCodeToString( (ButtonCode_t)i ), pBinding ); } else { int nSlot = GetJoystickForCode( (ButtonCode_t)i ); ConMsg( "[%d] \"%s\" = \"%s\"\n", nSlot, g_pInputSystem->ButtonCodeToString( (ButtonCode_t)i ), pBinding ); } } } } //----------------------------------------------------------------------------- // Initialization, shutdown //----------------------------------------------------------------------------- void Key_Init (void) { ReadCheatCommandsFromFile( "scripts/cheatcodes.txt" ); ReadCheatCommandsFromFile( "scripts/mod_cheatcodes.txt" ); } void Key_Shutdown( void ) { for ( int i = 0; i < ARRAYSIZE( s_KeyContext.m_pKeyInfo ); ++i ) { delete[] s_KeyContext.m_pKeyInfo[ i ].m_pKeyBinding; s_KeyContext.m_pKeyInfo[ i ].m_pKeyBinding = NULL; } ClearCheatCommands(); } //----------------------------------------------------------------------------- // Purpose: Starts trap mode (used for keybinding UI) //----------------------------------------------------------------------------- void Key_StartTrapMode( void ) { if ( s_KeyContext.m_bTrapMode ) return; Assert( !s_KeyContext.m_bDoneTrapping && s_KeyContext.m_nTrapKeyUp == BUTTON_CODE_INVALID ); s_KeyContext.m_bDoneTrapping = false; s_KeyContext.m_bTrapMode = true; s_KeyContext.m_nTrapKeyUp = BUTTON_CODE_INVALID; } //----------------------------------------------------------------------------- // We're done trapping once the first key is hit //----------------------------------------------------------------------------- bool Key_CheckDoneTrapping( ButtonCode_t& code ) { if ( s_KeyContext.m_bTrapMode ) return false; if ( !s_KeyContext.m_bDoneTrapping ) return false; code = s_KeyContext.m_nTrapKey; s_KeyContext.m_nTrapKey = BUTTON_CODE_INVALID; // Reset since we retrieved the results s_KeyContext.m_bDoneTrapping = false; return true; } //----------------------------------------------------------------------------- // Filter out trapped keys //----------------------------------------------------------------------------- static bool FilterTrappedKey( ButtonCode_t code, bool bDown ) { // After we've trapped a key, we want to capture the button up message for that key if ( s_KeyContext.m_nTrapKeyUp == code && !bDown ) { s_KeyContext.m_nTrapKeyUp = BUTTON_CODE_INVALID; return true; } // Only key down events are trapped if ( s_KeyContext.m_bTrapMode && bDown ) { s_KeyContext.m_nTrapKey = code; s_KeyContext.m_bTrapMode = false; s_KeyContext.m_bDoneTrapping = true; s_KeyContext.m_nTrapKeyUp = code; return true; } return false; } #ifndef DEDICATED //----------------------------------------------------------------------------- // Lets tools have a whack at key events //----------------------------------------------------------------------------- static bool HandleToolKey( const InputEvent_t &event ) { // Tools are not given typed messages if ( event.m_nType == IE_KeyTyped || event.m_nType == IE_KeyCodeTyped ) return false; IToolSystem *toolsys = toolframework->GetTopmostTool(); return toolsys && toolsys->TrapKey( (ButtonCode_t)event.m_nData, ( event.m_nType != IE_ButtonReleased ) ); } //----------------------------------------------------------------------------- // Lets vgui have a whack at key events //----------------------------------------------------------------------------- static bool HandleVGuiKey( const InputEvent_t &event ) { bool bDown = ( event.m_nType == IE_ButtonPressed ) || ( event.m_nType == IE_ButtonDoubleClicked ); if ( bDown && IsGameConsole() ) { ButtonCode_t code = (ButtonCode_t)event.m_nData; LogKeyPress( code ); CheckCheatCodes(); } return EngineVGui()->Key_Event( event ); } #if defined( INCLUDE_SCALEFORM ) //----------------------------------------------------------------------------- // Lets scaleform have a whack at key events //----------------------------------------------------------------------------- static bool HandleScaleformKey( const InputEvent_t &event ) { ButtonCode_t code = ( ButtonCode_t ) event.m_nData; bool bDown = ( event.m_nType == IE_ButtonPressed ) || ( event.m_nType == IE_ButtonDoubleClicked ); if ( bDown ) { if ( IsX360() ) { LogKeyPress( code ); CheckCheatCodes(); } #if defined ( CSTRIKE15 ) if ( !g_ClientDLL->IsLoadingScreenRaised() && !g_ClientDLL->IsChatRaised() && !g_ClientDLL->IsBindMenuRaised() && !g_ClientDLL->IsRadioPanelRaised() && !g_ClientDLL->IsTeamMenuRaised() ) #endif { const char * szBinding = s_KeyContext.m_pKeyInfo[ code ].m_pKeyBinding; if ( szBinding && szBinding[0] ) { // Add entries to this list if you want to PREVENT scaleform from filtering them static const char *szNoFilterList[] = { "screenshot", }; static const int kNumNoFilterEntries = sizeof( szNoFilterList ) / sizeof( szNoFilterList[0] ); for ( int idx=0; idx < kNumNoFilterEntries; ++idx ) { if ( StringHasPrefix( szBinding, szNoFilterList[idx] ) ) { return false; } } // Only filter these binds when the GameUI is active static const char *szNoFilterListGameUI[] = { "messagemode", "messagemode2", "+showscores", "togglescores", "+voicerecord", }; static const int kNumNoFilterEntriesGameUI = sizeof( szNoFilterListGameUI ) / sizeof( szNoFilterListGameUI[0] ); if ( !EngineVGui()->IsGameUIVisible() ) { for ( int idx=0; idx < kNumNoFilterEntriesGameUI; ++idx ) { if ( StringHasPrefix( szBinding, szNoFilterListGameUI[idx] ) ) { return false; } } } } } } return g_pScaleformUI->HandleInputEvent( event ); } #endif //----------------------------------------------------------------------------- // Lets the client have a whack at key events //----------------------------------------------------------------------------- static bool HandleClientKey( const InputEvent_t &event ) { // KeyTyped events are not handled by the client if ( ( event.m_nType == IE_KeyCodeTyped ) || ( event.m_nType == IE_KeyTyped ) ) return false; bool bDown = event.m_nType != IE_ButtonReleased; ButtonCode_t code = (ButtonCode_t)event.m_nData; if ( g_ClientDLL && g_ClientDLL->IN_KeyEvent( bDown ? 1 : 0, code, s_KeyContext.m_pKeyInfo[ code ].m_pKeyBinding ) == 0 ) return true; return false; } //----------------------------------------------------------------------------- // Lets the new Game UI system have a whack at key events //----------------------------------------------------------------------------- static bool HandleGameUIKey( const InputEvent_t &event ) { if ( g_ClientDLL && g_ClientDLL->HandleGameUIEvent( event ) ) return true; return false; } #endif //----------------------------------------------------------------------------- // Lets the engine have a whack at key events //----------------------------------------------------------------------------- static bool HandleEngineKey( const InputEvent_t &event ) { // KeyTyped events are not handled by the client if ( ( event.m_nType == IE_KeyCodeTyped ) || ( event.m_nType == IE_KeyTyped ) ) return false; bool bDown = event.m_nType != IE_ButtonReleased; ButtonCode_t code = (ButtonCode_t)event.m_nData; // Warn about unbound keys if ( IsPC() && bDown ) { if ( IsJoystickCode( code ) && !IsJoystickAxisCode( code ) && !s_KeyContext.m_pKeyInfo[code].m_pKeyBinding ) { ConDMsg( "[joy %d]%s is unbound.\n", GetJoystickForCode( code ), g_pInputSystem->ButtonCodeToString( code ) ); } } // key up events only generate commands if the game key binding is // a button command (leading + sign). These will occur even in console mode, // to keep the character from continuing an action started before a console // switch. Button commands include the kenum as a parameter, so multiple // downs can be matched with ups char *kb = s_KeyContext.m_pKeyInfo[ code ].m_pKeyBinding; if ( !kb || !kb[0] ) return false; #if !defined( DEDICATED ) // Prevent keybindings from doing funky stuff unless in game (except toggleconsole) if ( Q_stricmp( kb, "toggleconsole" ) ) { extern IVEngineClient *engineClient; if ( engineClient && !engineClient->IsConnected() ) return false; // prevent the keybinding from being processed without a game } #endif char cmd[1024]; if ( !bDown ) { if ( kb[0] == '+' ) { Q_snprintf( cmd, sizeof( cmd ), "-%s %i\n", kb+1, code ); Cbuf_AddText( Cbuf_GetCurrentPlayer(), cmd, kCommandSrcUserInput ); return true; } return false; } // Send to the interpreter if (kb[0] == '+') { // button commands add keynum as a parm Q_snprintf( cmd, sizeof( cmd ), "%s %i\n", kb, code ); Cbuf_AddText( Cbuf_GetCurrentPlayer(), cmd, kCommandSrcUserInput ); return true; } // Swallow console toggle if any modifier keys are down if it's bound to toggleconsole (the default) if ( !Q_stricmp( kb, "toggleconsole" ) ) { if ( s_KeyContext.m_pKeyInfo[KEY_LALT].m_bKeyDown || s_KeyContext.m_pKeyInfo[KEY_LSHIFT].m_bKeyDown || s_KeyContext.m_pKeyInfo[KEY_LCONTROL].m_bKeyDown || s_KeyContext.m_pKeyInfo[KEY_RALT].m_bKeyDown || s_KeyContext.m_pKeyInfo[KEY_RSHIFT].m_bKeyDown || s_KeyContext.m_pKeyInfo[KEY_RCONTROL].m_bKeyDown ) return false; } Cbuf_AddText( Cbuf_GetCurrentPlayer(), kb, kCommandSrcUserInput ); return true; } //----------------------------------------------------------------------------- // Helper function to make sure key down/key up events go to the right places //----------------------------------------------------------------------------- typedef bool (*FilterKeyFunc_t)( const InputEvent_t &event ); static bool FilterKey( const InputEvent_t &event, KeyUpTarget_t target, FilterKeyFunc_t func ) { // Don't pass the key up or keytyped messages to this target if some other system wants it ButtonCode_t code = (ButtonCode_t)event.m_nData; bool bFilterableEvent = ( event.m_nType == IE_ButtonReleased ) || ( event.m_nType == IE_KeyTyped ) || ( event.m_nType == IE_KeyCodeTyped ); if ( bFilterableEvent && !ShouldPassKeyUpToTarget( code, target ) ) return false; // Try to process the input message bool bFiltered = func( event ); // If we filtered it, then we need to get the key up event if ( ( event.m_nType == IE_ButtonPressed ) || ( event.m_nType == IE_ButtonDoubleClicked ) ) { if ( bFiltered ) { Assert( s_KeyContext.m_pKeyInfo[code].m_nKeyUpTarget == KEY_UP_ANYTARGET ); s_KeyContext.m_pKeyInfo[code].m_nKeyUpTarget = target; } } else if ( event.m_nType == IE_ButtonReleased ) // Up case { if ( s_KeyContext.m_pKeyInfo[code].m_nKeyUpTarget == target ) { s_KeyContext.m_pKeyInfo[code].m_nKeyUpTarget = KEY_UP_ANYTARGET; bFiltered = true; } else { // NOTE: It is illegal to trap up key events. The system will do it for us Assert( !bFiltered ); } } // We do nothing special for the KeyTyped/KeyCodeTyped cases; they do not change state return bFiltered; } inline bool IsESC( const InputEvent_t &event ) { switch ( event.m_nType ) { case IE_ButtonPressed: case IE_ButtonReleased: case IE_ButtonDoubleClicked: case IE_KeyCodeTyped: { return ( (ButtonCode_t)event.m_nData == KEY_ESCAPE ); } break; case IE_KeyTyped: { // ASCII Escape character return ( event.m_nData == 27 ); } break; } return false; } void Key_Event( const InputEvent_t &event ) { #ifdef DEDICATED return; #else ASSERT_NO_REENTRY(); ButtonCode_t code = (ButtonCode_t)event.m_nData; int nSplitScreenPlayerSlot = 0; if ( !IsJoystickCode( code ) && !IsSteamControllerCode( code ) ) { if ( splitscreen->IsValidSplitScreenSlot( in_forceuser.GetInt() ) ) { nSplitScreenPlayerSlot = in_forceuser.GetInt(); } } else { nSplitScreenPlayerSlot = GetJoystickForCode( code ); } ACTIVE_SPLITSCREEN_PLAYER_GUARD( nSplitScreenPlayerSlot ); // Don't handle key ups if the key's already up. // NOTE: This should already be taken care of by the input system // NOTE ALSO: KeyTyped and KeyCodeTyped messages are going to come through here also // Only the VGui and the new GameUI system should accept them; and only if // they accepted the keydown event if ( ( event.m_nType != IE_KeyCodeTyped ) && ( event.m_nType != IE_KeyTyped ) ) { bool bDown = ( event.m_nType == IE_ButtonPressed ) || ( event.m_nType == IE_ButtonDoubleClicked ); Assert( s_KeyContext.m_pKeyInfo[code].m_bKeyDown != bDown ); if ( s_KeyContext.m_pKeyInfo[code].m_bKeyDown == bDown ) return; s_KeyContext.m_pKeyInfo[code].m_bKeyDown = bDown; // Deal with trapped keys if ( FilterTrappedKey( code, bDown ) ) return; } // Make sure vgui is initialzied if ( !EngineVGui()->IsInitialized() ) return; // Keep vgui's notion of which keys are down up-to-date regardless of filtering // Necessary because vgui has multiple input contexts, so vgui can't directly // ask the input system for this information. // QUESTION: Should GameUI do the same thing? EngineVGui()->UpdateButtonState( event ); if ( IsPC() && EngineVGui()->IsGameUIVisible() && scr_drawloading && IsESC( event ) ) { // prevent ESC key during loading return; } // Let tools have a whack at keys if ( FilterKey( event, KEY_UP_TOOLS, HandleToolKey ) ) return; // If we happen to be checking the MAGICAL ESC key then // let's have the client check first since we do magical things // with this key otherwise. // [jason] Bypass the ButtonPress event for the BACKQUOTE key ("~") so that HandleGameUIKey can intercept it and bring up the Console Window if ( !IsESC( event ) && !(event.m_nType == IE_ButtonPressed && code == KEY_BACKQUOTE) ) { // Let vgui have a whack at keys if ( FilterKey( event, KEY_UP_VGUI, HandleVGuiKey ) ) return; #if defined( INCLUDE_SCALEFORM ) // scaleform goes first if ( FilterKey( event, KEY_UP_SCALEFORM, HandleScaleformKey ) ) return; #endif } #if defined ( CSTRIKE15 ) else if ( g_ClientDLL->IsChatRaised() || g_ClientDLL->IsBindMenuRaised() ) { if ( FilterKey( event, KEY_UP_SCALEFORM, HandleScaleformKey ) ) return; } #endif // Let the new GameUI system have a whack at keys if ( FilterKey( event, KEY_UP_GAMEUI, HandleGameUIKey ) ) return; // Let the client have a whack at keys if ( FilterKey( event, KEY_UP_CLIENT, HandleClientKey ) ) return; // Ok the client wants nothing to do with the magical ESC key // let's see if VGUI wants to do something with it. if ( IsESC( event ) ) { #if defined( INCLUDE_SCALEFORM ) bool bAllowScaleformFilter = true; static ConVarRef cv_console_window_open( "console_window_open" ); static ConVarRef cv_server_browser_dialog_open( "server_browser_dialog_open" ); if ( ( cv_console_window_open.GetBool() ) || ( cv_server_browser_dialog_open.GetBool() ) || ( EngineSoundClient() && EngineSoundClient()->IsMoviePlaying() ) ) { // make closing the console window, server browser dialog, or exiting a movie priority bAllowScaleformFilter = false; } if ( bAllowScaleformFilter && FilterKey( event, KEY_UP_SCALEFORM, HandleScaleformKey ) ) return; #endif // INCLUDE_SCALEFORM // Let vgui have a whack at keys if ( FilterKey( event, KEY_UP_VGUI, HandleVGuiKey ) ) return; } // Finally, let the engine deal. Here's where keybindings occur. FilterKey( event, KEY_UP_ENGINE, HandleEngineKey ); #endif }