//========= Copyright 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 "cheatcodes.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" enum KeyUpTarget_t { KEY_UP_ANYTARGET = 0, KEY_UP_ENGINE, KEY_UP_VGUI, KEY_UP_TOOLS, KEY_UP_CLIENT, }; struct KeyInfo_t { char *m_pKeyBinding; unsigned char m_nKeyUpTarget : 3; // see KeyUpTarget_t unsigned char m_bKeyDown : 1; }; //----------------------------------------------------------------------------- // Current keypress state //----------------------------------------------------------------------------- static KeyInfo_t s_pKeyInfo[BUTTON_CODE_LAST]; //----------------------------------------------------------------------------- // Trap mode is used by the keybinding UI //----------------------------------------------------------------------------- static bool s_bTrapMode = false; static bool s_bDoneTrapping = false; static ButtonCode_t s_nTrapKeyUp = BUTTON_CODE_INVALID; static ButtonCode_t s_nTrapKey = BUTTON_CODE_INVALID; //----------------------------------------------------------------------------- // Can keys be passed to various targets? //----------------------------------------------------------------------------- static inline bool ShouldPassKeyUpToTarget( ButtonCode_t code, KeyUpTarget_t target ) { return ( s_pKeyInfo[code].m_nKeyUpTarget == target ) || ( s_pKeyInfo[code].m_nKeyUpTarget == KEY_UP_ANYTARGET ); } /* =================== Key_SetBinding =================== */ void Key_SetBinding( ButtonCode_t keynum, const char *pBinding ) { char *pNewBinding; int l; if ( keynum == BUTTON_CODE_INVALID ) return; // free old bindings if ( s_pKeyInfo[keynum].m_pKeyBinding ) { // Exactly the same, don't re-bind and fragment memory if ( !Q_strcmp( s_pKeyInfo[keynum].m_pKeyBinding, pBinding ) ) return; delete[] s_pKeyInfo[keynum].m_pKeyBinding; s_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_pKeyInfo[keynum].m_pKeyBinding = pNewBinding; } /* =================== Key_Unbind_f =================== */ CON_COMMAND_F( unbind, "Unbind a key.", FCVAR_DONTRECORD ) { 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_F( unbind_mac, "Unbind a key on the Mac only.", FCVAR_DONTRECORD ) { if ( IsOSX() ) { 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_F( unbindall, "Unbind all keys.", FCVAR_DONTRECORD ) { int i; for ( i=0; iHideGameUI(); } #endif /* =================== Key_Bind_f =================== */ void BindKey( const char *pchBind, bool bShow, const char *pchCmd ) { if ( !g_pInputSystem ) return; ButtonCode_t b = g_pInputSystem->StringToButtonCode( pchBind ); if ( b == BUTTON_CODE_INVALID ) { ConMsg( "\"%s\" isn't a valid key\n", pchBind ); return; } if ( bShow ) { if (s_pKeyInfo[b].m_pKeyBinding) { ConMsg( "\"%s\" = \"%s\"\n", pchBind, s_pKeyInfo[b].m_pKeyBinding ); } else { ConMsg( "\"%s\" is not bound\n", pchBind ); } return; } if ( b == KEY_ESCAPE ) { pchCmd = "cancelselect"; } Key_SetBinding( b, pchCmd ); } CON_COMMAND_F( bind, "Bind a key.", FCVAR_DONTRECORD ) { int i, c; char cmd[1024]; c = args.ArgC(); if ( c != 2 && c != 3 ) { ConMsg( "bind [command] : attach a command to a key\n" ); return; } // 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 ); } BindKey( args[1], c == 2, cmd ); } CON_COMMAND_F( bind_mac, "Bind this key but only on Mac, not win32", FCVAR_DONTRECORD ) { if ( IsOSX() ) { int i, c; char cmd[1024]; c = args.ArgC(); if ( c != 2 && c != 3 ) { ConMsg( "bind [command] : attach a command to a key\n" ); return; } // 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 ); } BindKey( args[1], c == 2, cmd ); } } /* ============ 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_pKeyInfo[i].m_pKeyBinding || !s_pKeyInfo[i].m_pKeyBinding[0] ) continue; c++; } return c; } /* ============ Key_WriteBindings Writes lines containing "bind key value" ============ */ void Key_WriteBindings( CUtlBuffer &buf ) { int i; for ( i = 0 ; i < BUTTON_CODE_LAST ; i++ ) { if ( !s_pKeyInfo[i].m_pKeyBinding || !s_pKeyInfo[i].m_pKeyBinding[0] ) continue; buf.Printf( "bind \"%s\" \"%s\"\n", g_pInputSystem->ButtonCodeToString( (ButtonCode_t)i ), s_pKeyInfo[i].m_pKeyBinding ); } } /* ============ 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" ============ */ const char *Key_NameForBinding( const char *pBinding ) { int i; const char *pBind = pBinding; if ( pBinding[0] == '+' ) { ++pBind; } for (i=0 ; iButtonCodeToString( (ButtonCode_t)i ); } else { if ( !Q_strcasecmp( s_pKeyInfo[i].m_pKeyBinding, (char *)pBind ) ) return g_pInputSystem->ButtonCodeToString( (ButtonCode_t)i ); } } } } // Xbox 360 controller: Handle the dual bindings for duck and zoom if ( !Q_stricmp( "duck", pBind ) ) return Key_NameForBinding( "toggle_duck" ); if ( !Q_stricmp( "zoom", pBind ) ) return Key_NameForBinding( "toggle_zoom" ); return NULL; } /* ============ 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" Does not perform "helpful" removal of '+' character from bindings. ============ */ const char *Key_NameForBindingExact( const char *pBinding ) { int i; for (i=0 ; iButtonCodeToString( (ButtonCode_t)i ); } } } return NULL; } const char *Key_BindingForKey( ButtonCode_t code ) { if ( code < 0 || code > BUTTON_CODE_LAST ) return NULL; if ( !s_pKeyInfo[ code ].m_pKeyBinding ) return NULL; return s_pKeyInfo[ code ].m_pKeyBinding; } CON_COMMAND( key_listboundkeys, "List bound keys with bindings." ) { int i; for (i=0 ; iButtonCodeToString( (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 ); } } } //----------------------------------------------------------------------------- // 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_pKeyInfo ); ++i ) { delete[] s_pKeyInfo[ i ].m_pKeyBinding; s_pKeyInfo[ i ].m_pKeyBinding = NULL; } ClearCheatCommands(); } //----------------------------------------------------------------------------- // Purpose: Starts trap mode (used for keybinding UI) //----------------------------------------------------------------------------- void Key_StartTrapMode( void ) { if ( s_bTrapMode ) return; Assert( !s_bDoneTrapping && s_nTrapKeyUp == BUTTON_CODE_INVALID ); s_bDoneTrapping = false; s_bTrapMode = true; s_nTrapKeyUp = BUTTON_CODE_INVALID; } //----------------------------------------------------------------------------- // We're done trapping once the first key is hit //----------------------------------------------------------------------------- bool Key_CheckDoneTrapping( ButtonCode_t& code ) { if ( s_bTrapMode ) return false; if ( !s_bDoneTrapping ) return false; code = s_nTrapKey; s_nTrapKey = BUTTON_CODE_INVALID; // Reset since we retrieved the results s_bDoneTrapping = false; return true; } #ifndef SWDS //----------------------------------------------------------------------------- // 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_nTrapKeyUp == code && !bDown ) { s_nTrapKeyUp = BUTTON_CODE_INVALID; return true; } // Only key down events are trapped if ( s_bTrapMode && bDown ) { s_nTrapKey = code; s_bTrapMode = false; s_bDoneTrapping = true; s_nTrapKeyUp = code; return true; } return false; } //----------------------------------------------------------------------------- // Lets tools have a whack at key events //----------------------------------------------------------------------------- static bool HandleToolKey( const InputEvent_t &event ) { IToolSystem *toolsys = toolframework->GetTopmostTool(); return toolsys && toolsys->TrapKey( (ButtonCode_t)event.m_nData, ( event.m_nType != IE_ButtonReleased ) ); } #endif // !SWDS //----------------------------------------------------------------------------- // Lets vgui have a whack at key events //----------------------------------------------------------------------------- #ifndef SWDS static bool HandleVGuiKey( const InputEvent_t &event ) { bool bDown = event.m_nType != IE_ButtonReleased; ButtonCode_t code = (ButtonCode_t)event.m_nData; if ( bDown && IsX360() ) { LogKeyPress( code ); CheckCheatCodes(); } return EngineVGui()->Key_Event( event ); } //----------------------------------------------------------------------------- // Lets the client have a whack at key events //----------------------------------------------------------------------------- static bool HandleClientKey( const InputEvent_t &event ) { 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_pKeyInfo[ code ].m_pKeyBinding ) == 0 ) return true; return false; } #endif //----------------------------------------------------------------------------- // Lets the engine have a whack at key events //----------------------------------------------------------------------------- #ifndef SWDS static bool HandleEngineKey( const InputEvent_t &event ) { 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 ) && !IsSteamControllerCode( code ) && !s_pKeyInfo[code].m_pKeyBinding ) { ConDMsg( "%s is unbound.\n", g_pInputSystem->ButtonCodeToString( code ) ); } } // Allow the client to handle mouse wheel events while the game has focus, without having to bind keys. #if !defined( SWDS ) if ( ( code == MOUSE_WHEEL_UP || code == MOUSE_WHEEL_DOWN ) && g_pClientReplay ) { g_ClientDLL->IN_OnMouseWheeled( code == MOUSE_WHEEL_UP ? 1 : -1 ); } #endif // 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_pKeyInfo[ code ].m_pKeyBinding; if ( !kb || !kb[0] ) return false; char cmd[1024]; if ( !bDown ) { if ( kb[0] == '+' ) { Q_snprintf( cmd, sizeof( cmd ), "-%s %i\n", kb+1, code ); Cbuf_AddText( cmd ); 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( cmd ); 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_pKeyInfo[KEY_LALT].m_bKeyDown || s_pKeyInfo[KEY_LSHIFT].m_bKeyDown || s_pKeyInfo[KEY_LCONTROL].m_bKeyDown || s_pKeyInfo[KEY_RALT].m_bKeyDown || s_pKeyInfo[KEY_RSHIFT].m_bKeyDown || s_pKeyInfo[KEY_RCONTROL].m_bKeyDown ) return false; } Cbuf_AddText( kb ); Cbuf_AddText( "\n" ); return true; } #endif // !SWDS //----------------------------------------------------------------------------- // Helper function to make sure key down/key up events go to the right places //----------------------------------------------------------------------------- #ifndef SWDS typedef bool (*FilterKeyFunc_t)( const InputEvent_t &event ); static bool FilterKey( const InputEvent_t &event, KeyUpTarget_t target, FilterKeyFunc_t func ) { bool bDown = event.m_nType != IE_ButtonReleased; ButtonCode_t code = (ButtonCode_t)event.m_nData; // Don't pass the key up to tools if some other system wants it if ( !bDown && !ShouldPassKeyUpToTarget( code, target ) ) return false; bool bFiltered = func( event ); // If we filtered it, then we need to get the key up event if ( bDown ) { if ( bFiltered ) { Assert( s_pKeyInfo[code].m_nKeyUpTarget == KEY_UP_ANYTARGET ); s_pKeyInfo[code].m_nKeyUpTarget = target; } } else // Up case { if ( s_pKeyInfo[code].m_nKeyUpTarget == target ) { s_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 ); } } return bFiltered; } #endif // !SWDS //----------------------------------------------------------------------------- // Called by the system between frames for both key up and key down events //----------------------------------------------------------------------------- void Key_Event( const InputEvent_t &event ) { #ifdef SWDS return; #else ASSERT_NO_REENTRY(); bool bDown = event.m_nType != IE_ButtonReleased; ButtonCode_t code = (ButtonCode_t)event.m_nData; #ifdef LINUX // We're getting some crashes referencing s_pKeyInfo[ code ]. Let's try to // hard error here and see if we can get code and type at http://minidump. if ( code < 0 || code >= ARRAYSIZE( s_pKeyInfo ) ) { Error( "Key_Event: invalid code! type:%d code:%d\n", event.m_nType, code ); } #endif // Don't handle key ups if the key's already up. // NOTE: This should already be taken care of by the input system Assert( s_pKeyInfo[code].m_bKeyDown != bDown ); if ( s_pKeyInfo[code].m_bKeyDown == bDown ) return; s_pKeyInfo[code].m_bKeyDown = bDown; // Deal with trapped keys if ( FilterTrappedKey( code, bDown ) ) 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. EngineVGui()->UpdateButtonState( event ); // Let tools have a whack at keys if ( FilterKey( event, KEY_UP_TOOLS, HandleToolKey ) ) return; // Let vgui have a whack at keys if ( FilterKey( event, KEY_UP_VGUI, HandleVGuiKey ) ) return; // Let the client have a whack at keys if ( FilterKey( event, KEY_UP_CLIENT, HandleClientKey ) ) return; // Finally, let the engine deal. Here's where keybindings occur. FilterKey( event, KEY_UP_ENGINE, HandleEngineKey ); #endif }