diff options
Diffstat (limited to 'engine/keys.cpp')
| -rw-r--r-- | engine/keys.cpp | 764 |
1 files changed, 764 insertions, 0 deletions
diff --git a/engine/keys.cpp b/engine/keys.cpp new file mode 100644 index 0000000..d02aa73 --- /dev/null +++ b/engine/keys.cpp @@ -0,0 +1,764 @@ +//========= 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 <key> : 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 <key> : 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; i<BUTTON_CODE_LAST; i++ ) + { + if ( !s_pKeyInfo[i].m_pKeyBinding ) + continue; + + // Don't ever unbind escape or console key + if ( i == KEY_ESCAPE ) + continue; + + if ( i == KEY_BACKQUOTE ) + continue; + + Key_SetBinding( (ButtonCode_t)i, "" ); + } +} + +#ifndef SWDS +CON_COMMAND_F( escape, "Escape key pressed.", FCVAR_CLIENTCMD_CAN_EXECUTE ) +{ + EngineVGui()->HideGameUI(); +} +#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 <key> [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 <key> [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 ; i<BUTTON_CODE_LAST ; i++) + { + if (s_pKeyInfo[i].m_pKeyBinding) + { + if (*s_pKeyInfo[i].m_pKeyBinding) + { + if ( s_pKeyInfo[i].m_pKeyBinding[0] == '+' ) + { + if ( !Q_strcasecmp( s_pKeyInfo[i].m_pKeyBinding+1, (char *)pBind ) ) + return g_pInputSystem->ButtonCodeToString( (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 ; i<BUTTON_CODE_LAST ; i++) + { + if (s_pKeyInfo[i].m_pKeyBinding) + { + if (*s_pKeyInfo[i].m_pKeyBinding) + { + if ( !Q_strcasecmp( s_pKeyInfo[i].m_pKeyBinding, pBinding ) ) + return g_pInputSystem->ButtonCodeToString( (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 ; i<BUTTON_CODE_LAST ; i++) + { + const char *pBinding = Key_BindingForKey( (ButtonCode_t)i ); + if ( !pBinding || !pBinding[0] ) + continue; + + ConMsg( "\"%s\" = \"%s\"\n", 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 ; i<BUTTON_CODE_LAST ; i++) + { + const char *pBinding = Key_BindingForKey( (ButtonCode_t)i ); + if ( !pBinding || !pBinding[0] ) + continue; + + if ( Q_strstr( pBinding, substring ) ) + { + ConMsg( "\"%s\" = \"%s\"\n", + 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_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 +} + + |