diff --git a/code/client/cl_keys.cpp b/code/client/cl_keys.cpp index 768c135937..21423b894a 100644 --- a/code/client/cl_keys.cpp +++ b/code/client/cl_keys.cpp @@ -42,6 +42,11 @@ field_t historyEditLines[COMMAND_HISTORY]; keyGlobals_t kg; +cvar_t *cl_keyModifiersOnlyIfUnbound; +cvar_t *cl_keyModifiersFallbackToDefault; +cvar_t *cl_keyModifiersEnableLocks; +cvar_t *cl_keyModifiersPassToUI; + // do NOT blithely change any of the key names (3rd field) here, since they have to match the key binds // in the CFG files, they're also prepended with "KEYNAME_" when looking up StripEd references // @@ -809,10 +814,13 @@ the K_* names are matched up. to be configured even if they don't have defined names. =================== */ -int Key_StringToKeynum( char *str ) { +int Key_StringToKeynum( const char *str ) { if ( !VALIDSTRING( str ) ) return -1; + // Skip all modifiers + str = Key_SkipModifiers( str ); + // If single char bind, presume ascii char bind if ( !str[1] ) return keynames[(unsigned char)str[0]].upper; @@ -824,10 +832,10 @@ int Key_StringToKeynum( char *str ) { } // check for hex code - if ( strlen( str ) == 4 ) { - int n = Com_HexStrToInt( str ); + if ( strlen( str ) >= 3 ) { + size_t n = Com_HexStrToInt( str ); - if ( n >= 0 ) + if ( n >= 0 && n < ARRAY_LEN(keynames) ) return n; } @@ -922,24 +930,105 @@ const char *Key_KeynumToString( int keynum ) { return name; } +/* +=================== +Key_ModifiersFromString +=================== +*/ +static stringID_table_t keyModifiers[] { + // Regular modifiers + { "LSHIFT", KEYMOD_LSHIFT }, + { "RSHIFT", KEYMOD_RSHIFT }, + { "LCTRL", KEYMOD_LCTRL }, + { "RCTRL", KEYMOD_RCTRL }, + { "LALT", KEYMOD_LALT }, + { "RALT", KEYMOD_RALT }, + { "LSUPER", KEYMOD_LSUPER }, + { "RSUPER", KEYMOD_RSUPER }, + + // LOCK modifiers + { "NUMLOCK", KEYMOD_NUMLOCK }, + { "CAPSLOCK", KEYMOD_CAPSLOCK }, +}; +static int keyModifiersAmount = ARRAY_LEN( keyModifiers ); + +int Key_ModifiersFromString( const char *str ) { + int modifiers = 0; + + int i = 0; + int strLen = strlen( str ); + int modLen; + + for ( i = 0; i < keyModifiersAmount; i++ ) { + modLen = strlen( keyModifiers[i].name ); + if ( strLen < modLen+1 ) continue; + if ( !Q_stricmpn(str, keyModifiers[i].name, modLen) && str[modLen] == '+' ) { + modifiers |= keyModifiers[i].id; + str += modLen+1; + i = -1; // Reset loop + } + } + return modifiers; +} + +/* +=================== +Key_SkipModifiers +=================== +*/ +const char *Key_SkipModifiers( const char *str ) { + int i = 0; + int strLen = strlen( str ); + int modLen; + + for ( i = 0; i < keyModifiersAmount; i++ ) { + modLen = strlen( keyModifiers[i].name ); + if ( strLen < modLen+1 ) continue; + if ( !Q_stricmpn(str, keyModifiers[i].name, modLen) && str[modLen] == '+' ) { + str += modLen+1; + i = -1; // Reset loop + } + } + return str; +} + +/* +=================== +Key_ModifiersStringFromModifiers +=================== +*/ +char *Key_ModifiersStringFromModifiers( int modifiers ) { + static char modifiersStr[128]; // All current modifier strings above, sepearated by '+' are 66 chars in length. + int i; + + modifiersStr[0] = 0; + for ( i = 0; i < keyModifiersAmount; i++ ) { + if ( modifiers & keyModifiers[i].id ) { + Q_strcat( modifiersStr, sizeof(modifiersStr), keyModifiers[i].name ); + Q_strcat( modifiersStr, sizeof(modifiersStr), "+" ); + } + } + return modifiersStr; +} + /* =================== Key_SetBinding =================== */ -void Key_SetBinding( int keynum, const char *binding ) { +void Key_SetBinding( int keynum, int modifiers, const char *binding ) { if ( keynum < 0 || keynum >= MAX_KEYS ) return; // free old bindings - if ( kg.keys[keynames[keynum].upper].binding ) { - Z_Free( kg.keys[keynames[keynum].upper].binding ); - kg.keys[keynames[keynum].upper].binding = NULL; + if ( kg.keys[keynames[keynum].upper].binding[modifiers] ) { + Z_Free( kg.keys[keynames[keynum].upper].binding[modifiers] ); + kg.keys[keynames[keynum].upper].binding[modifiers] = NULL; } // allocate memory for new binding if ( binding ) - kg.keys[keynames[keynum].upper].binding = CopyString( binding ); + kg.keys[keynames[keynum].upper].binding[modifiers] = CopyString( binding ); // consider this like modifying an archived cvar, so the // file write will be triggered at the next oportunity @@ -951,11 +1040,11 @@ void Key_SetBinding( int keynum, const char *binding ) { Key_GetBinding =================== */ -const char *Key_GetBinding( int keynum ) { +const char *Key_GetBinding( int keynum, int modifiers ) { if ( keynum < 0 || keynum >= MAX_KEYS ) return ""; - return kg.keys[keynum].binding; + return kg.keys[keynum].binding[modifiers]; } /* @@ -963,10 +1052,10 @@ const char *Key_GetBinding( int keynum ) { Key_GetKey =================== */ -int Key_GetKey( const char *binding ) { +int Key_GetKey( const char *binding, int modifiers ) { if ( binding ) { for ( int i=0; i= sizeof(suggestion) ) { + Com_Printf( "Key_Completion: input too long\n" ); + return; + } else { + if ( skipped-input <= 0 ) suggestion[0] = 0; + else { + Q_strncpyz( suggestion, input, skipped-input ); + Q_strcat( suggestion, sizeof(suggestion), "+" ); + } + } + + for ( mod = 0; mod < keyModifiersAmount; mod++ ) { + if ( keyModifiers[mod].name && !Q_stricmpn(keyModifiers[mod].name, skipped, strlen(skipped)) ) { + modifierMatches++; + lastModifierMatch = keyModifiers[mod].name; + } + } + for ( key = 0; key < numKeynames; key++ ) { + if ( keynames[key].name && !Q_stricmpn(keynames[key].name, skipped, strlen(skipped)) ) { + keyMatches++; + } + } + + if ( modifierMatches == 1 && keyMatches == 0 ) + { // We want to complete the modifier and suggest all keys with that modifier + Q_strcat( suggestion, sizeof(suggestion), lastModifierMatch ); + Q_strcat( suggestion, sizeof(suggestion), "+" ); + } + + for ( mod = 0; mod < keyModifiersAmount; mod++ ) { + if ( keyModifiers[mod].name && !(modifiers & keyModifiers[mod].id) ) { + callback( va("%s%s+", suggestion, keyModifiers[mod].name) ); + } + } + for ( key = 0; key < numKeynames; key++ ) { + if ( keynames[key].name ) { + callback( va("%s%s", suggestion, keynames[key].name) ); + } } } @@ -1134,6 +1282,12 @@ void CL_InitKeyCommands( void ) { Cmd_SetCommandCompletionFunc( "unbind", Key_CompleteUnbind ); Cmd_AddCommand( "unbindall", Key_Unbindall_f ); Cmd_AddCommand( "bindlist", Key_Bindlist_f ); + + // Register cvars (the default values should have legacy configs play the same way without behavior changes) + cl_keyModifiersOnlyIfUnbound = Cvar_Get( "cl_keyModifiersOnlyIfUnbound", "1", CVAR_ARCHIVE_ND ); + cl_keyModifiersFallbackToDefault = Cvar_Get( "cl_keyModifiersFallbackToDefault", "1", CVAR_ARCHIVE_ND ); + cl_keyModifiersEnableLocks = Cvar_Get( "cl_keyModifiersEnableLocks", "0", CVAR_ARCHIVE_ND ); + cl_keyModifiersPassToUI = Cvar_Get( "cl_keyModifiersPassToUI", "0", CVAR_ARCHIVE_ND ); } /* @@ -1162,16 +1316,21 @@ CL_ParseBinding Execute the commands in the bind string =================== */ -void CL_ParseBinding( int key, qboolean down, unsigned time ) +void CL_ParseBinding( int key, int modifiers, qboolean down, unsigned time ) { char buf[ MAX_STRING_CHARS ], *p = buf, *end; qboolean allCommands, allowUpCmds; if( cls.state == CA_DISCONNECTED && Key_GetCatcher( ) == 0 ) return; - if( !kg.keys[keynames[key].upper].binding || !kg.keys[keynames[key].upper].binding[0] ) - return; - Q_strncpyz( buf, kg.keys[keynames[key].upper].binding, sizeof( buf ) ); + if( !kg.keys[keynames[key].upper].binding[modifiers] || !kg.keys[keynames[key].upper].binding[modifiers][0] ) { + if ( !modifiers || !cl_keyModifiersFallbackToDefault->integer ) return; + else modifiers = 0; + + if( !kg.keys[keynames[key].upper].binding[modifiers] || !kg.keys[keynames[key].upper].binding[modifiers][0] ) + return; + } + Q_strncpyz( buf, kg.keys[keynames[key].upper].binding[modifiers], sizeof( buf ) ); // run all bind commands if console, ui, etc aren't reading keys allCommands = (qboolean)( Key_GetCatcher( ) == 0 ); @@ -1224,6 +1383,7 @@ void CL_KeyDownEvent( int key, unsigned time ) kg.keys[keynames[key].upper].down = qtrue; kg.keys[keynames[key].upper].repeats++; if( kg.keys[keynames[key].upper].repeats == 1 ) { + kg.keys[keynames[key].upper].downModifiers = kg.modifiers; kg.keyDownCount++; kg.anykeydown = qtrue; } @@ -1267,18 +1427,23 @@ void CL_KeyDownEvent( int key, unsigned time ) return; } - _UI_KeyEvent( key, qtrue ); + _UI_KeyEvent( key, cl_keyModifiersPassToUI->integer ? kg.keys[keynames[key].upper].downModifiers : 0, qtrue ); return; } // send the bound action - CL_ParseBinding( key, qtrue, time ); + if ( !cls.cursorActive ) CL_ParseBinding( key, kg.keys[keynames[key].upper].downModifiers, qtrue, time ); // distribute the key down event to the apropriate handler if ( Key_GetCatcher() & KEYCATCH_CONSOLE ) { Console_Key( key ); } else if ( Key_GetCatcher() & KEYCATCH_UI ) { - _UI_KeyEvent( key, qtrue ); + if ( cl_keyModifiersPassToUI->integer ) { + if ( key == A_ALT && (kg.modifiersReal & (KEYMOD_LALT|KEYMOD_RALT)) ) return; + if ( key == A_CTRL && (kg.modifiersReal & (KEYMOD_LCTRL|KEYMOD_RCTRL)) ) return; + if ( key == A_SHIFT && (kg.modifiersReal & (KEYMOD_LSHIFT|KEYMOD_RSHIFT)) ) return; + } + _UI_KeyEvent( key, cl_keyModifiersPassToUI->integer ? kg.modifiersReal : 0, qtrue ); } else if ( cls.state == CA_DISCONNECTED ) { Console_Key( key ); } @@ -1293,8 +1458,10 @@ Called by CL_KeyEvent to handle a keyrelease */ void CL_KeyUpEvent( int key, unsigned time ) { + int modifiers = kg.keys[keynames[key].upper].downModifiers; kg.keys[keynames[key].upper].repeats = 0; kg.keys[keynames[key].upper].down = qfalse; + kg.keys[keynames[key].upper].downModifiers = 0; kg.keyDownCount--; if (kg.keyDownCount <= 0) { @@ -1312,10 +1479,10 @@ void CL_KeyUpEvent( int key, unsigned time ) // console mode and menu mode, to keep the character from continuing // an action started before a mode switch. // - CL_ParseBinding( key, qfalse, time ); + CL_ParseBinding( key, modifiers, qfalse, time ); if ( Key_GetCatcher( ) & KEYCATCH_UI ) - _UI_KeyEvent( key, qfalse ); + _UI_KeyEvent( key, cl_keyModifiersPassToUI->integer ? modifiers : 0, qfalse ); } /* @@ -1346,10 +1513,48 @@ void CL_CharEvent( int key ) { // distribute the key down event to the apropriate handler if ( Key_GetCatcher() & KEYCATCH_CONSOLE ) Field_CharEvent( &g_consoleField, key ); - else if ( Key_GetCatcher() & KEYCATCH_UI ) _UI_KeyEvent( key|K_CHAR_FLAG, qtrue ); + else if ( Key_GetCatcher() & KEYCATCH_UI ) _UI_KeyEvent( key|K_CHAR_FLAG, 0, qtrue ); else if ( cls.state == CA_DISCONNECTED ) Field_CharEvent( &g_consoleField, key ); } +/* +=================== +CL_ModifierEvent +=================== +*/ +void CL_ModifierEvent( int modifiers ) { + kg.modifiers = modifiers; + kg.modifiersReal = modifiers; + + if ( cl_keyModifiersOnlyIfUnbound->integer ) { + if ( kg.keys[A_SHIFT].binding[0] && kg.keys[A_SHIFT].binding[0][0] ) { + kg.modifiers &= ~KEYMOD_LSHIFT; + kg.modifiers &= ~KEYMOD_RSHIFT; + } + if ( kg.keys[A_CTRL].binding[0] && kg.keys[A_CTRL].binding[0][0] ) { + kg.modifiers &= ~KEYMOD_LCTRL; + kg.modifiers &= ~KEYMOD_RCTRL; + } + if ( kg.keys[A_ALT].binding[0] && kg.keys[A_ALT].binding[0][0] ) { + kg.modifiers &= ~KEYMOD_LALT; + kg.modifiers &= ~KEYMOD_RALT; + } + if ( cl_keyModifiersOnlyIfUnbound->integer != 2 ) { + // Special case: the lock modifiers are not held down, they are toggled. So we might want to bind something to the toggle. + if ( kg.keys[A_CAPSLOCK].binding[0] && kg.keys[A_CAPSLOCK].binding[0][0] ) { + kg.modifiers &= ~KEYMOD_CAPSLOCK; + } + if ( kg.keys[A_NUMLOCK].binding[0] && kg.keys[A_NUMLOCK].binding[0][0] ) { + kg.modifiers &= ~KEYMOD_NUMLOCK; + } + } + } + if ( !cl_keyModifiersEnableLocks->integer ) { + kg.modifiers &= ~KEYMOD_CAPSLOCK; + kg.modifiers &= ~KEYMOD_NUMLOCK; + } +} + /* =================== Key_ClearStates @@ -1364,6 +1569,7 @@ void Key_ClearStates( void ) { CL_KeyEvent( i, qfalse, 0 ); kg.keys[i].down = qfalse; kg.keys[i].repeats = 0; + kg.keys[i].downModifiers = 0; } } diff --git a/code/client/cl_ui.cpp b/code/client/cl_ui.cpp index 80d002f1f3..6ef77de4fb 100644 --- a/code/client/cl_ui.cpp +++ b/code/client/cl_ui.cpp @@ -96,7 +96,7 @@ Key_KeynumToStringBuf // only ever called by binding-display code, therefore returns non-technical "friendly" names // in any language that don't necessarily match those in the config file... // -void Key_KeynumToStringBuf( int keynum, char *buf, int buflen ) +void Key_KeynumToStringBuf( int keynum, int modifiers, char *buf, int buflen ) { const char *psKeyName = Key_KeynumToString( keynum/*, qtrue */); @@ -104,7 +104,8 @@ void Key_KeynumToStringBuf( int keynum, char *buf, int buflen ) // const char *psKeyNameFriendly = SE_GetString( va("KEYNAMES_KEYNAME_%s",psKeyName) ); - Q_strncpyz( buf, (psKeyNameFriendly && psKeyNameFriendly[0]) ? psKeyNameFriendly : psKeyName, buflen ); + Q_strncpyz( buf, Key_ModifiersStringFromModifiers(modifiers), buflen ); + Q_strcat( buf, buflen, (psKeyNameFriendly && psKeyNameFriendly[0]) ? psKeyNameFriendly : psKeyName ); } /* @@ -112,10 +113,10 @@ void Key_KeynumToStringBuf( int keynum, char *buf, int buflen ) Key_GetBindingBuf ==================== */ -void Key_GetBindingBuf( int keynum, char *buf, int buflen ) { +void Key_GetBindingBuf( int keynum, int modifiers, char *buf, int buflen ) { const char *value; - value = Key_GetBinding( keynum ); + value = Key_GetBinding( keynum, modifiers ); if ( value ) { Q_strncpyz( buf, value, buflen ); } @@ -482,11 +483,11 @@ intptr_t CL_UISystemCalls( intptr_t *args ) return 0; case UI_KEY_SETBINDING: - Key_SetBinding( args[1], (const char *) VMA(2) ); + Key_SetBinding( args[1], args[2], (const char *) VMA(3) ); return 0; case UI_KEY_KEYNUMTOSTRINGBUF: - Key_KeynumToStringBuf( args[1],(char *) VMA(2), args[3] ); + Key_KeynumToStringBuf( args[1], args[2],(char *) VMA(3), args[4] ); return 0; case UI_CIN_SETEXTENTS: @@ -494,7 +495,7 @@ intptr_t CL_UISystemCalls( intptr_t *args ) return 0; case UI_KEY_GETBINDINGBUF: - Key_GetBindingBuf( args[1], (char *) VMA(2), args[3] ); + Key_GetBindingBuf( args[1], args[2], (char *) VMA(3), args[4] ); return 0; diff --git a/code/client/client_ui.h b/code/client/client_ui.h index 67789f91e8..4299d76ddf 100644 --- a/code/client/client_ui.h +++ b/code/client/client_ui.h @@ -27,7 +27,7 @@ along with this program; if not, see . #include "../ui/ui_public.h" -void _UI_KeyEvent( int key, qboolean down ); +void _UI_KeyEvent( int key, int modifiers, qboolean down ); void UI_SetActiveMenu( const char* menuname,const char *menuID ); void UI_UpdateConnectionMessageString( char *string ); qboolean UI_ConsoleCommand( void ) ; diff --git a/code/client/keycodes.h b/code/client/keycodes.h index 77cda3f298..eb413c02c4 100644 --- a/code/client/keycodes.h +++ b/code/client/keycodes.h @@ -367,4 +367,17 @@ typedef enum // distinguished by or'ing in K_CHAR_FLAG (ugly) #define K_CHAR_FLAG 1024 +// Supported modifiers +#define KEYMOD_LSHIFT (1) +#define KEYMOD_RSHIFT (1 << 1) +#define KEYMOD_LCTRL (1 << 2) +#define KEYMOD_RCTRL (1 << 3) +#define KEYMOD_LALT (1 << 4) +#define KEYMOD_RALT (1 << 5) +#define KEYMOD_LSUPER (1 << 6) +#define KEYMOD_RSUPER (1 << 7) +#define KEYMOD_NUMLOCK (1 << 8) +#define KEYMOD_CAPSLOCK (1 << 9) +#define KEYMOD_COMBINATIONS ((1 << 10)-1) // NOTE: This must be the last entry + #endif diff --git a/code/client/keys.h b/code/client/keys.h index 8a503b3c2f..f71b3df523 100644 --- a/code/client/keys.h +++ b/code/client/keys.h @@ -25,14 +25,17 @@ along with this program; if not, see . typedef struct qkey_s { qboolean down; + int downModifiers; int repeats; // if > 1, it is autorepeating - char *binding; + char *binding[KEYMOD_COMBINATIONS]; } qkey_t; typedef struct keyGlobals_s { qboolean anykeydown; qboolean key_overstrikeMode; int keyDownCount; + int modifiers; + int modifiersReal; // Unfiltered qkey_t keys[MAX_KEYS]; } keyGlobals_t; @@ -58,11 +61,14 @@ void Field_CharEvent ( field_t *edit, int ch ); void Field_Draw ( field_t *edit, int x, int y, qboolean showCursor, qboolean noColorEscape ); void Field_BigDraw ( field_t *edit, int x, int y, qboolean showCursor, qboolean noColorEscape ); -void Key_SetBinding ( int keynum, const char *binding ); -const char *Key_GetBinding ( int keynum ); +int Key_ModifiersFromString ( const char *str ); +const char *Key_SkipModifiers ( const char *str ); +char * Key_ModifiersStringFromModifiers( int modifiers ); +void Key_SetBinding ( int keynum, int modifiers, const char *binding ); +const char *Key_GetBinding ( int keynum, int modifiers ); qboolean Key_IsDown ( int keynum ); -int Key_StringToKeynum ( char *str ); +int Key_StringToKeynum ( const char *str ); qboolean Key_GetOverstrikeMode ( void ); void Key_SetOverstrikeMode ( qboolean state ); void Key_ClearStates ( void ); -int Key_GetKey ( const char *binding ); \ No newline at end of file +int Key_GetKey ( const char *binding, int modifiers ); \ No newline at end of file diff --git a/code/qcommon/common.cpp b/code/qcommon/common.cpp index 397abd71bd..52191872b1 100644 --- a/code/qcommon/common.cpp +++ b/code/qcommon/common.cpp @@ -911,6 +911,9 @@ int Com_EventLoop( void ) { Cbuf_AddText( (char *)ev.evPtr ); Cbuf_AddText( "\n" ); break; + case SE_MODIFIER: + CL_ModifierEvent( ev.evValue ); + break; } // free any block data @@ -1798,10 +1801,10 @@ void Field_CompleteKeyname( void ) matchCount = 0; shortestMatch[ 0 ] = 0; - Key_KeynameCompletion( FindMatches ); + Key_Completion( FindMatches, completionString ); if( !Field_Complete( ) ) - Key_KeynameCompletion( PrintKeyMatches ); + Key_Completion( PrintKeyMatches, completionString ); } /* diff --git a/code/qcommon/qcommon.h b/code/qcommon/qcommon.h index b1647f473b..a08cf3e57f 100644 --- a/code/qcommon/qcommon.h +++ b/code/qcommon/qcommon.h @@ -731,6 +731,7 @@ void CL_Shutdown( void ); void CL_Frame( int msec,float fractionMsec ); qboolean CL_GameCommand( void ); void CL_KeyEvent (int key, qboolean down, unsigned time); +void CL_ModifierEvent( int modifiers ); void CL_CharEvent( int key ); // char events are for field typing, not game control @@ -759,7 +760,7 @@ void CL_FlushMemory( void ); void CL_StartHunkUsers( void ); -void Key_KeynameCompletion ( callbackFunc_t callback ); +void Key_Completion( void(*callback)( const char *s ), const char *input ); // for keyname autocompletion void Key_WriteBindings( fileHandle_t f ); diff --git a/code/ui/ui_local.h b/code/ui/ui_local.h index 6767efd5dd..fa70db08bf 100644 --- a/code/ui/ui_local.h +++ b/code/ui/ui_local.h @@ -212,7 +212,7 @@ extern uiInfo_t uiInfo; void _UI_Init( qboolean inGameLoad ); void _UI_DrawRect( float x, float y, float width, float height, float size, const float *color ); void _UI_MouseEvent( int dx, int dy ); -void _UI_KeyEvent( int key, qboolean down ); +void _UI_KeyEvent( int key, int modifiers, qboolean down ); void UI_Report(void); extern char GoToMenu[]; @@ -229,7 +229,7 @@ void trap_GetGlconfig( glconfig_t *glconfig ); void trap_Key_ClearStates( void ); int trap_Key_GetCatcher( void ); qboolean trap_Key_GetOverstrikeMode( void ); -void trap_Key_SetBinding( int keynum, const char *binding ); +void trap_Key_SetBinding( int keynum, int modifiers, const char *binding ); void trap_Key_SetCatcher( int catcher ); void trap_Key_SetOverstrikeMode( qboolean state ); void trap_R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader ); diff --git a/code/ui/ui_main.cpp b/code/ui/ui_main.cpp index 4a20f0eec8..8e16d84035 100644 --- a/code/ui/ui_main.cpp +++ b/code/ui/ui_main.cpp @@ -2050,8 +2050,8 @@ extern void Item_RunScript(itemDef_t *item, const char *s); //from ui_shared; */ } -void Key_KeynumToStringBuf( int keynum, char *buf, int buflen ); -void Key_GetBindingBuf( int keynum, char *buf, int buflen ); +void Key_KeynumToStringBuf( int keynum, int modifiers, char *buf, int buflen ); +void Key_GetBindingBuf( int keynum, int modifiers, char *buf, int buflen ); static qboolean UI_Crosshair_HandleKey(int flags, float *special, int key) { @@ -4042,7 +4042,7 @@ void _UI_MouseEvent( int dx, int dy ) UI_KeyEvent ================= */ -void _UI_KeyEvent( int key, qboolean down ) +void _UI_KeyEvent( int key, int modifiers, qboolean down ) { /* extern qboolean SwallowBadNumLockedKPKey( int iKey ); if (SwallowBadNumLockedKPKey(key)){ @@ -4062,7 +4062,7 @@ void _UI_KeyEvent( int key, qboolean down ) } else { - Menu_HandleKey(menu, key, down ); + Menu_HandleKey(menu, key, modifiers, down ); } } else diff --git a/code/ui/ui_public.h b/code/ui/ui_public.h index e4f4ced11a..914d42651f 100644 --- a/code/ui/ui_public.h +++ b/code/ui/ui_public.h @@ -129,9 +129,9 @@ typedef struct { // =========== data shared with the client system ============= // keyboard and key binding interaction - void (*Key_KeynumToStringBuf)( int keynum, char *buf, int buflen ); - void (*Key_GetBindingBuf)( int keynum, char *buf, int buflen ); - void (*Key_SetBinding)( int keynum, const char *binding ); + void (*Key_KeynumToStringBuf)( int keynum, int modifiers, char *buf, int buflen ); + void (*Key_GetBindingBuf)( int keynum, int modifiers, char *buf, int buflen ); + void (*Key_SetBinding)( int keynum, int modifiers, const char *binding ); qboolean (*Key_IsDown)( int keynum ); qboolean (*Key_GetOverstrikeMode)( void ); void (*Key_SetOverstrikeMode)( qboolean state ); diff --git a/code/ui/ui_shared.cpp b/code/ui/ui_shared.cpp index 2f5cbdf877..f7929b1b63 100644 --- a/code/ui/ui_shared.cpp +++ b/code/ui/ui_shared.cpp @@ -5570,28 +5570,33 @@ static const char *g_bindCommands[] = { #define g_bindCount ARRAY_LEN(g_bindCommands) static int g_bindKeys[g_bindCount][2]; +static int g_bindModifiers[g_bindCount][2]; /* ================= Controls_GetKeyAssignment ================= */ -static void Controls_GetKeyAssignment( const char *command, int *twokeys ) +static void Controls_GetKeyAssignment( const char *command, int *twokeys, int *twomodifiers ) { int count; int j; char b[256]; + int modifiers; twokeys[0] = twokeys[1] = -1; count = 0; - for ( j=0; jgetBindingBuf( j, b, sizeof( b ) ); - if ( *b && !Q_stricmp( b, command ) ) { - twokeys[count] = j; - count++; - if ( count == 2 ) - break; + for ( modifiers=0; modifiersgetBindingBuf( j, modifiers, b, sizeof( b ) ); + if ( *b && !Q_stricmp( b, command ) ) { + twokeys[count] = j; + twomodifiers[count] = modifiers; + count++; + if ( count == 2 ) + break; + } } } } @@ -5607,7 +5612,7 @@ void Controls_GetConfig( void ) // iterate each command, get its numeric binding for ( i = 0; i < g_bindCount; i++ ) - Controls_GetKeyAssignment( g_bindCommands[i], g_bindKeys[i] ); + Controls_GetKeyAssignment( g_bindCommands[i], g_bindKeys[i], g_bindModifiers[i] ); } @@ -6894,6 +6899,7 @@ BindingFromName void BindingFromName( const char *cvar ) { size_t i; int b1, b2; + int b1mod, b2mod; char sOR[32]; // iterate each command, set its default binding @@ -6901,15 +6907,17 @@ void BindingFromName( const char *cvar ) { if ( !Q_stricmp(cvar, g_bindCommands[i] ) ) { b2 = g_bindKeys[i][1]; b1 = g_bindKeys[i][0]; + b2mod = g_bindModifiers[i][1]; + b1mod = g_bindModifiers[i][0]; if ( b1 == -1 ) break; if ( b2 != -1 ) { char keyname[2][32]; - DC->keynumToStringBuf( b1, keyname[0], sizeof( keyname[0] ) ); + DC->keynumToStringBuf( b1, b1mod, keyname[0], sizeof( keyname[0] ) ); // do NOT do this or it corrupts asian text!!! Q_strupr(keyname[0]); - DC->keynumToStringBuf( b2, keyname[1], sizeof( keyname[1] ) ); + DC->keynumToStringBuf( b2, b2mod, keyname[1], sizeof( keyname[1] ) ); // do NOT do this or it corrupts asian text!!! Q_strupr(keyname[1]); #ifdef JK2_MODE @@ -6921,7 +6929,7 @@ void BindingFromName( const char *cvar ) { Com_sprintf( g_nameBind, sizeof( g_nameBind ), "%s %s %s", keyname[0], sOR, keyname[1] ); } else { - DC->keynumToStringBuf( b1, g_nameBind, sizeof( g_nameBind ) ); + DC->keynumToStringBuf( b1, b1mod, g_nameBind, sizeof( g_nameBind ) ); // do NOT do this or it corrupts asian text!!! Q_strupr(g_nameBind); } return; @@ -9497,10 +9505,10 @@ void Controls_SetConfig( void ) // iterate each command, get its numeric binding for ( i=0; isetBinding( g_bindKeys[i][0], g_bindCommands[i] ); + DC->setBinding( g_bindKeys[i][0], g_bindModifiers[i][0], g_bindCommands[i] ); if ( g_bindKeys[i][1] != -1 ) - DC->setBinding( g_bindKeys[i][1], g_bindCommands[i] ); + DC->setBinding( g_bindKeys[i][1], g_bindModifiers[i][1], g_bindCommands[i] ); } } } @@ -9512,6 +9520,9 @@ void Controls_SetDefaults( void ) for ( i=0; isetBinding( g_bindKeys[id][0], "" ); + DC->setBinding( g_bindKeys[id][0], g_bindModifiers[id][0], "" ); if ( g_bindKeys[id][1] != -1 ) - DC->setBinding( g_bindKeys[id][1], "" ); + DC->setBinding( g_bindKeys[id][1], g_bindModifiers[id][1], "" ); g_bindKeys[id][0] = -1; g_bindKeys[id][1] = -1; + + g_bindModifiers[id][0] = 0; + g_bindModifiers[id][1] = 0; } Controls_SetConfig(); g_waitingForKey = qfalse; @@ -9634,12 +9648,16 @@ qboolean Item_Bind_HandleKey(itemDef_t *item, int key, qboolean down) for ( b=0; bsetBinding( g_bindKeys[id][0], "" ); + DC->setBinding( g_bindKeys[id][0], g_bindModifiers[id][0], "" ); g_bindKeys[id][0] = -1; + g_bindModifiers[id][0] = 0; } if ( g_bindKeys[id][1] != -1 ) { - DC->setBinding( g_bindKeys[id][1], "" ); + DC->setBinding( g_bindKeys[id][1], g_bindModifiers[id][1], "" ); g_bindKeys[id][1] = -1; + g_bindModifiers[id][1] = 0; } } - else if ( g_bindKeys[id][0] == -1 ) + else if ( g_bindKeys[id][0] == -1 ) { g_bindKeys[id][0] = key; - else if ( g_bindKeys[id][0] != key && g_bindKeys[id][1] == -1 ) + g_bindModifiers[id][0] = modifiers; + } + else if ( g_bindKeys[id][0] != key && g_bindKeys[id][1] == -1 ) { g_bindKeys[id][1] = key; + g_bindModifiers[id][1] = modifiers; + } else { - DC->setBinding( g_bindKeys[id][0], "" ); - DC->setBinding( g_bindKeys[id][1], "" ); + DC->setBinding( g_bindKeys[id][0], g_bindModifiers[id][0], "" ); + DC->setBinding( g_bindKeys[id][1], g_bindModifiers[id][1], "" ); g_bindKeys[id][0] = key; g_bindKeys[id][1] = -1; + g_bindModifiers[id][0] = modifiers; + g_bindModifiers[id][1] = 0; } } @@ -10136,7 +10162,7 @@ static void Display_CloseCinematics() Menus_HandleOOBClick ================= */ -void Menus_HandleOOBClick(menuDef_t *menu, int key, qboolean down) +void Menus_HandleOOBClick(menuDef_t *menu, int key, int modifiers, qboolean down) { if (menu) { @@ -10158,7 +10184,7 @@ void Menus_HandleOOBClick(menuDef_t *menu, int key, qboolean down) menu->window.flags &= ~(WINDOW_HASFOCUS | WINDOW_VISIBLE); // Menus_Activate(&Menus[i]); Menu_HandleMouseMove(&Menus[i], DC->cursorx, DC->cursory); - Menu_HandleKey(&Menus[i], key, down); + Menu_HandleKey(&Menus[i], key, modifiers, down); } } @@ -11024,7 +11050,7 @@ qboolean Item_Slider_HandleKey(itemDef_t *item, int key, qboolean down) Item_HandleKey ================= */ -qboolean Item_HandleKey(itemDef_t *item, int key, qboolean down) +qboolean Item_HandleKey(itemDef_t *item, int key, int modifiers, qboolean down) { if (itemCapture) @@ -11082,7 +11108,7 @@ qboolean Item_HandleKey(itemDef_t *item, int key, qboolean down) return Item_OwnerDraw_HandleKey(item, key); break; case ITEM_TYPE_BIND: - return Item_Bind_HandleKey(item, key, down); + return Item_Bind_HandleKey(item, key, modifiers, down); break; case ITEM_TYPE_SLIDER: return Item_Slider_HandleKey(item, key, down); @@ -11172,7 +11198,7 @@ void Item_Action(itemDef_t *item) Menu_HandleKey ================= */ -void Menu_HandleKey(menuDef_t *menu, int key, qboolean down) +void Menu_HandleKey(menuDef_t *menu, int key, int modifiers, qboolean down) { int i; itemDef_t *item = NULL; @@ -11186,7 +11212,7 @@ void Menu_HandleKey(menuDef_t *menu, int key, qboolean down) inHandler = qtrue; if (g_waitingForKey && down) { - Item_Bind_HandleKey(g_bindItem, key, down); + Item_Bind_HandleKey(g_bindItem, key, modifiers, down); inHandler = qfalse; return; } @@ -11226,7 +11252,7 @@ void Menu_HandleKey(menuDef_t *menu, int key, qboolean down) if (!inHandleKey && (key == A_MOUSE1 || key == A_MOUSE2 || key == A_MOUSE3)) { inHandleKey = qtrue; - Menus_HandleOOBClick(menu, key, down); + Menus_HandleOOBClick(menu, key, modifiers, down); inHandleKey = qfalse; inHandler = qfalse; return; @@ -11251,7 +11277,7 @@ void Menu_HandleKey(menuDef_t *menu, int key, qboolean down) if (item != NULL) { - if (Item_HandleKey(item, key, down)) + if (Item_HandleKey(item, key, modifiers, down)) //JLFLISTBOX { // It is possible for an item to be disable after Item_HandleKey is run (like in Voice Chat) @@ -11275,7 +11301,7 @@ void Menu_HandleKey(menuDef_t *menu, int key, qboolean down) if (!(key & K_CHAR_FLAG) ) { //only check keys not chars char b[256]; - DC->getBindingBuf( key, b, 256 ); + DC->getBindingBuf( key, modifiers, b, 256 ); if (Q_stricmp(b,"datapad") == 0) // They hit the datapad key again. { if (( Q_stricmp(menu->window.name,"datapadMissionMenu") == 0) || diff --git a/code/ui/ui_shared.h b/code/ui/ui_shared.h index 1eb849e844..e6c0db9f5e 100644 --- a/code/ui/ui_shared.h +++ b/code/ui/ui_shared.h @@ -180,12 +180,12 @@ typedef struct { int (*feederCount)(float feederID); void (*feederSelection)(float feederID, int index, struct itemDef_s *item); void (*fillRect) ( float x, float y, float w, float h, const vec4_t color); - void (*getBindingBuf)( int keynum, char *buf, int buflen ); + void (*getBindingBuf)( int keynum, int modifiers, char *buf, int buflen ); void (*getCVarString)(const char *cvar, char *buffer, int bufsize); float (*getCVarValue)(const char *cvar); qboolean (*getOverstrikeMode)(); float (*getValue) (int ownerDraw); - void (*keynumToStringBuf)( int keynum, char *buf, int buflen ); + void (*keynumToStringBuf)( int keynum, int modifiers, char *buf, int buflen ); void (*modelBounds) (qhandle_t model, vec3_t min, vec3_t max); qboolean (*ownerDrawHandleKey)(int ownerDraw, int flags, float *special, int key); void (*ownerDrawItem) (float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, int ownerDrawFlags, int align, float special, float scale, vec4_t color, qhandle_t shader, int textStyle, int iFontIndex); @@ -200,7 +200,7 @@ typedef struct { void (*renderScene) ( const refdef_t *fd ); qboolean (*runScript)(const char **p); qboolean (*deferScript)(const char **p); - void (*setBinding)( int keynum, const char *binding ); + void (*setBinding)( int keynum, int modifiers, const char *binding ); void (*setColor) (const vec4_t v); void (*setCVar)(const char *cvar, const char *value); void (*setOverstrikeMode)(qboolean b); @@ -485,7 +485,7 @@ void Menu_ItemDisable(menuDef_t *menu, const char *name, qboolean disableFlag); int Menu_ItemsMatchingGroup(menuDef_t *menu, const char *name); itemDef_t *Menu_GetMatchingItemByNumber(menuDef_t *menu, int index, const char *name); -void Menu_HandleKey(menuDef_t *menu, int key, qboolean down); +void Menu_HandleKey(menuDef_t *menu, int key, int modifiers, qboolean down); void Menu_New(char *buffer); void Menus_OpenByName(const char *p); void Menu_PaintAll(void); diff --git a/code/ui/ui_syscalls.cpp b/code/ui/ui_syscalls.cpp index a6a00b30ae..53336c7cdc 100644 --- a/code/ui/ui_syscalls.cpp +++ b/code/ui/ui_syscalls.cpp @@ -86,9 +86,9 @@ sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ) return S_RegisterSound(sample); } -void trap_Key_SetBinding( int keynum, const char *binding ) +void trap_Key_SetBinding( int keynum, int modifiers, const char *binding ) { - Key_SetBinding( keynum, binding); + Key_SetBinding( keynum, modifiers, binding); } qboolean trap_Key_GetOverstrikeMode( void ) diff --git a/codemp/client/cl_cgameapi.cpp b/codemp/client/cl_cgameapi.cpp index 15267443a6..965fb01aff 100644 --- a/codemp/client/cl_cgameapi.cpp +++ b/codemp/client/cl_cgameapi.cpp @@ -816,6 +816,10 @@ static void CL_G2API_GetSurfaceName( void *ghoul2, int surfNumber, int modelInde strcpy( fillBuf, tmp ); } +static int CL_Key_GetKey( const char *binding ) { + return Key_GetKey( binding, 0 ); +} + static void CL_Key_SetCatcher( int catcher ) { // Don't allow the cgame module to close the console Key_SetCatcher( catcher | ( Key_GetCatcher( ) & KEYCATCH_CONSOLE ) ); @@ -1259,7 +1263,7 @@ intptr_t CL_CgameSystemCalls( intptr_t *args ) { return 0; case CG_KEY_GETKEY: - return Key_GetKey( (const char *)VMA(1) ); + return Key_GetKey( (const char *)VMA(1), 0 ); case CG_PC_ADD_GLOBAL_DEFINE: return botlib_export->PC_AddGlobalDefine( (char *)VMA(1) ); @@ -1817,7 +1821,7 @@ void CL_BindCGame( void ) { cgi.SetClientForceAngle = CL_SetClientForceAngle; cgi.SetUserCmdValue = _CL_SetUserCmdValue; cgi.Key_GetCatcher = Key_GetCatcher; - cgi.Key_GetKey = Key_GetKey; + cgi.Key_GetKey = CL_Key_GetKey; cgi.Key_IsDown = Key_IsDown; cgi.Key_SetCatcher = CL_Key_SetCatcher; cgi.PC_AddGlobalDefine = botlib_export->PC_AddGlobalDefine; diff --git a/codemp/client/cl_keys.cpp b/codemp/client/cl_keys.cpp index b64de77b0e..0a8ee5df1e 100644 --- a/codemp/client/cl_keys.cpp +++ b/codemp/client/cl_keys.cpp @@ -45,6 +45,10 @@ int chat_playerNum; keyGlobals_t kg; +cvar_t *cl_keyModifiersOnlyIfUnbound; +cvar_t *cl_keyModifiersFallbackToDefault; +cvar_t *cl_keyModifiersEnableLocks; + // do NOT blithely change any of the key names (3rd field) here, since they have to match the key binds // in the CFG files, they're also prepended with "KEYNAME_" when looking up StringEd references // @@ -867,10 +871,13 @@ the K_* names are matched up. to be configured even if they don't have defined names. =================== */ -int Key_StringToKeynum( char *str ) { +int Key_StringToKeynum( const char *str ) { if ( !VALIDSTRING( str ) ) return -1; + // Skip all modifiers + str = Key_SkipModifiers( str ); + // If single char bind, presume ascii char bind if ( !str[1] ) return keynames[(unsigned char)str[0]].upper; @@ -882,10 +889,10 @@ int Key_StringToKeynum( char *str ) { } // check for hex code - if ( strlen( str ) == 4 ) { - int n = Com_HexStrToInt( str ); + if ( strlen( str ) >= 3 ) { + size_t n = Com_HexStrToInt( str ); - if ( n >= 0 ) + if ( n >= 0 && n < ARRAY_LEN(keynames) ) return n; } @@ -981,24 +988,105 @@ const char *Key_KeynumToString( int keynum ) { return name; } +/* +=================== +Key_ModifiersFromString +=================== +*/ +static stringID_table_t keyModifiers[] { + // Regular modifiers + { "LSHIFT", KEYMOD_LSHIFT }, + { "RSHIFT", KEYMOD_RSHIFT }, + { "LCTRL", KEYMOD_LCTRL }, + { "RCTRL", KEYMOD_RCTRL }, + { "LALT", KEYMOD_LALT }, + { "RALT", KEYMOD_RALT }, + { "LSUPER", KEYMOD_LSUPER }, + { "RSUPER", KEYMOD_RSUPER }, + + // LOCK modifiers + { "NUMLOCK", KEYMOD_NUMLOCK }, + { "CAPSLOCK", KEYMOD_CAPSLOCK }, +}; +static int keyModifiersAmount = ARRAY_LEN( keyModifiers ); + +int Key_ModifiersFromString( const char *str ) { + int modifiers = 0; + + int i = 0; + int strLen = strlen( str ); + int modLen; + + for ( i = 0; i < keyModifiersAmount; i++ ) { + modLen = strlen( keyModifiers[i].name ); + if ( strLen < modLen+1 ) continue; + if ( !Q_stricmpn(str, keyModifiers[i].name, modLen) && str[modLen] == '+' ) { + modifiers |= keyModifiers[i].id; + str += modLen+1; + i = -1; // Reset loop + } + } + return modifiers; +} + +/* +=================== +Key_SkipModifiers +=================== +*/ +const char *Key_SkipModifiers( const char *str ) { + int i = 0; + int strLen = strlen( str ); + int modLen; + + for ( i = 0; i < keyModifiersAmount; i++ ) { + modLen = strlen( keyModifiers[i].name ); + if ( strLen < modLen+1 ) continue; + if ( !Q_stricmpn(str, keyModifiers[i].name, modLen) && str[modLen] == '+' ) { + str += modLen+1; + i = -1; // Reset loop + } + } + return str; +} + +/* +=================== +Key_ModifiersStringFromModifiers +=================== +*/ +char *Key_ModifiersStringFromModifiers( int modifiers ) { + static char modifiersStr[128]; // All current modifier strings above, sepearated by '+' are 66 chars in length. + int i; + + modifiersStr[0] = 0; + for ( i = 0; i < keyModifiersAmount; i++ ) { + if ( modifiers & keyModifiers[i].id ) { + Q_strcat( modifiersStr, sizeof(modifiersStr), keyModifiers[i].name ); + Q_strcat( modifiersStr, sizeof(modifiersStr), "+" ); + } + } + return modifiersStr; +} + /* =================== Key_SetBinding =================== */ -void Key_SetBinding( int keynum, const char *binding ) { +void Key_SetBinding( int keynum, int modifiers, const char *binding ) { if ( keynum < 0 || keynum >= MAX_KEYS ) return; // free old bindings - if ( kg.keys[keynames[keynum].upper].binding ) { - Z_Free( kg.keys[keynames[keynum].upper].binding ); - kg.keys[keynames[keynum].upper].binding = NULL; + if ( kg.keys[keynames[keynum].upper].binding[modifiers] ) { + Z_Free( kg.keys[keynames[keynum].upper].binding[modifiers] ); + kg.keys[keynames[keynum].upper].binding[modifiers] = NULL; } // allocate memory for new binding if ( binding ) - kg.keys[keynames[keynum].upper].binding = CopyString( binding ); + kg.keys[keynames[keynum].upper].binding[modifiers] = CopyString( binding ); // consider this like modifying an archived cvar, so the // file write will be triggered at the next oportunity @@ -1010,11 +1098,11 @@ void Key_SetBinding( int keynum, const char *binding ) { Key_GetBinding =================== */ -char *Key_GetBinding( int keynum ) { +char *Key_GetBinding( int keynum, int modifiers ) { if ( keynum < 0 || keynum >= MAX_KEYS ) return ""; - return kg.keys[keynum].binding; + return kg.keys[keynum].binding[modifiers]; } /* @@ -1022,10 +1110,10 @@ char *Key_GetBinding( int keynum ) { Key_GetKey =================== */ -int Key_GetKey( const char *binding ) { +int Key_GetKey( const char *binding, int modifiers ) { if ( binding ) { for ( int i=0; i= sizeof(suggestion) ) { + Com_Printf( "Key_Completion: input too long\n" ); + return; + } else { + if ( skipped-input <= 0 ) suggestion[0] = 0; + else { + Q_strncpyz( suggestion, input, skipped-input ); + Q_strcat( suggestion, sizeof(suggestion), "+" ); + } + } + + for ( mod = 0; mod < keyModifiersAmount; mod++ ) { + if ( keyModifiers[mod].name && !Q_stricmpn(keyModifiers[mod].name, skipped, strlen(skipped)) ) { + modifierMatches++; + lastModifierMatch = keyModifiers[mod].name; + } + } + for ( key = 0; key < numKeynames; key++ ) { + if ( keynames[key].name && !Q_stricmpn(keynames[key].name, skipped, strlen(skipped)) ) { + keyMatches++; + } + } + + if ( modifierMatches == 1 && keyMatches == 0 ) + { // We want to complete the modifier and suggest all keys with that modifier + Q_strcat( suggestion, sizeof(suggestion), lastModifierMatch ); + Q_strcat( suggestion, sizeof(suggestion), "+" ); + } + + for ( mod = 0; mod < keyModifiersAmount; mod++ ) { + if ( keyModifiers[mod].name && !(modifiers & keyModifiers[mod].id) ) { + callback( va("%s%s+", suggestion, keyModifiers[mod].name) ); + } + } + for ( key = 0; key < numKeynames; key++ ) { + if ( keynames[key].name ) { + callback( va("%s%s", suggestion, keynames[key].name) ); + } } } @@ -1193,6 +1340,11 @@ void CL_InitKeyCommands( void ) { Cmd_SetCommandCompletionFunc( "unbind", Key_CompleteUnbind ); Cmd_AddCommand( "unbindall", Key_Unbindall_f, "Delete all key bindings" ); Cmd_AddCommand( "bindlist", Key_Bindlist_f, "Show all bindings in the console" ); + + // Register cvars (the default values should have legacy configs play the same way without behavior changes) + cl_keyModifiersOnlyIfUnbound = Cvar_Get( "cl_keyModifiersOnlyIfUnbound", "1", CVAR_ARCHIVE_ND, "If set modifiers (like LCTRL, LALT, LSHIFT, LSUPER, RCTRL, RALT, RSHIFT, RSUPER, CAPSLOCK, NUMLOCK) are only usable when the associated key is unbound (CTRL, ALT, SHIFT, CAPSLOCK, KP_NUMLOCK)." ); + cl_keyModifiersFallbackToDefault = Cvar_Get( "cl_keyModifiersFallbackToDefault", "1", CVAR_ARCHIVE_ND, "If a key is not bound on a modifier layer, fallback to the bind without a modifier." ); + cl_keyModifiersEnableLocks = Cvar_Get( "cl_keyModifiersEnableLocks", "0", CVAR_ARCHIVE_ND, "If enabled the CAPSLOCK and NUMLOCK keys switch to another keyboard layer." ); } /* @@ -1221,16 +1373,21 @@ CL_ParseBinding Execute the commands in the bind string =================== */ -void CL_ParseBinding( int key, qboolean down, unsigned time ) +void CL_ParseBinding( int key, int modifiers, qboolean down, unsigned time ) { char buf[ MAX_STRING_CHARS ], *p = buf, *end; qboolean allCommands, allowUpCmds; if( cls.state == CA_DISCONNECTED && Key_GetCatcher( ) == 0 ) return; - if( !kg.keys[keynames[key].upper].binding || !kg.keys[keynames[key].upper].binding[0] ) - return; - Q_strncpyz( buf, kg.keys[keynames[key].upper].binding, sizeof( buf ) ); + if( !kg.keys[keynames[key].upper].binding[modifiers] || !kg.keys[keynames[key].upper].binding[modifiers][0] ) { + if ( !modifiers || !cl_keyModifiersFallbackToDefault->integer ) return; + else modifiers = 0; + + if( !kg.keys[keynames[key].upper].binding[modifiers] || !kg.keys[keynames[key].upper].binding[modifiers][0] ) + return; + } + Q_strncpyz( buf, kg.keys[keynames[key].upper].binding[modifiers], sizeof( buf ) ); // run all bind commands if console, ui, etc aren't reading keys allCommands = (qboolean)( Key_GetCatcher( ) == 0 ); @@ -1304,6 +1461,7 @@ void CL_KeyDownEvent( int key, unsigned time ) kg.keys[keynames[key].upper].down = qtrue; kg.keys[keynames[key].upper].repeats++; if( kg.keys[keynames[key].upper].repeats == 1 ) { + kg.keys[keynames[key].upper].downModifiers = kg.modifiers; kg.keyDownCount++; kg.anykeydown = qtrue; } @@ -1366,7 +1524,7 @@ void CL_KeyDownEvent( int key, unsigned time ) } // send the bound action - if ( !cls.cursorActive ) CL_ParseBinding( key, qtrue, time ); + if ( !cls.cursorActive ) CL_ParseBinding( key, kg.keys[keynames[key].upper].downModifiers, qtrue, time ); // distribute the key down event to the appropriate handler // console @@ -1402,8 +1560,10 @@ Called by CL_KeyEvent to handle a keyrelease */ void CL_KeyUpEvent( int key, unsigned time ) { + int modifiers = kg.keys[keynames[key].upper].downModifiers; kg.keys[keynames[key].upper].repeats = 0; kg.keys[keynames[key].upper].down = qfalse; + kg.keys[keynames[key].upper].downModifiers = 0; kg.keyDownCount--; if (kg.keyDownCount <= 0) { @@ -1421,7 +1581,7 @@ void CL_KeyUpEvent( int key, unsigned time ) // console mode and menu mode, to keep the character from continuing // an action started before a mode switch. // - CL_ParseBinding( key, qfalse, time ); + CL_ParseBinding( key, modifiers, qfalse, time ); if ( Key_GetCatcher( ) & KEYCATCH_UI && cls.uiStarted ) UIVM_KeyEvent( key, qfalse ); @@ -1463,6 +1623,43 @@ void CL_CharEvent( int key ) { else if ( cls.state == CA_DISCONNECTED ) Field_CharEvent( &g_consoleField, key ); } +/* +=================== +CL_ModifierEvent +=================== +*/ +void CL_ModifierEvent( int modifiers ) { + kg.modifiers = modifiers; + + if ( cl_keyModifiersOnlyIfUnbound->integer ) { + if ( kg.keys[A_SHIFT].binding[0] && kg.keys[A_SHIFT].binding[0][0] ) { + kg.modifiers &= ~KEYMOD_LSHIFT; + kg.modifiers &= ~KEYMOD_RSHIFT; + } + if ( kg.keys[A_CTRL].binding[0] && kg.keys[A_CTRL].binding[0][0] ) { + kg.modifiers &= ~KEYMOD_LCTRL; + kg.modifiers &= ~KEYMOD_RCTRL; + } + if ( kg.keys[A_ALT].binding[0] && kg.keys[A_ALT].binding[0][0] ) { + kg.modifiers &= ~KEYMOD_LALT; + kg.modifiers &= ~KEYMOD_RALT; + } + if ( cl_keyModifiersOnlyIfUnbound->integer != 2 ) { + // Special case: the lock modifiers are not held down, they are toggled. So we might want to bind something to the toggle. + if ( kg.keys[A_CAPSLOCK].binding[0] && kg.keys[A_CAPSLOCK].binding[0][0] ) { + kg.modifiers &= ~KEYMOD_CAPSLOCK; + } + if ( kg.keys[A_NUMLOCK].binding[0] && kg.keys[A_NUMLOCK].binding[0][0] ) { + kg.modifiers &= ~KEYMOD_NUMLOCK; + } + } + } + if ( !cl_keyModifiersEnableLocks->integer ) { + kg.modifiers &= ~KEYMOD_CAPSLOCK; + kg.modifiers &= ~KEYMOD_NUMLOCK; + } +} + /* =================== Key_ClearStates @@ -1476,6 +1673,7 @@ void Key_ClearStates( void ) { CL_KeyEvent( i, qfalse, 0 ); kg.keys[i].down = qfalse; kg.keys[i].repeats = 0; + kg.keys[i].downModifiers = 0; } } diff --git a/codemp/client/cl_uiapi.cpp b/codemp/client/cl_uiapi.cpp index 4ed911798b..c5c678fe3b 100644 --- a/codemp/client/cl_uiapi.cpp +++ b/codemp/client/cl_uiapi.cpp @@ -198,7 +198,7 @@ static int GetConfigString(int index, char *buf, int size) static void Key_GetBindingBuf( int keynum, char *buf, int buflen ) { char *value; - value = Key_GetBinding( keynum ); + value = Key_GetBinding( keynum, 0 ); if ( value ) { Q_strncpyz( buf, value, buflen ); } @@ -713,6 +713,10 @@ static qboolean CL_G2API_AttachG2Model( void *ghoul2From, int modelIndexFrom, vo return re->G2API_AttachG2Model(*g2From, modelIndexFrom, *g2To, toBoltIndex, toModel); } +static void CL_Key_SetBinding( int keynum, const char *binding ) { + Key_SetBinding( keynum, 0, binding ); +} + static void CL_Key_SetCatcher( int catcher ) { // Don't allow the ui module to close the console Key_SetCatcher( catcher | ( Key_GetCatcher( ) & KEYCATCH_CONSOLE ) ); @@ -939,7 +943,7 @@ intptr_t CL_UISystemCalls( intptr_t *args ) { return 0; case UI_KEY_SETBINDING: - Key_SetBinding( args[1], (const char *)VMA(2) ); + Key_SetBinding( args[1], 0, (const char *)VMA(2) ); return 0; case UI_KEY_ISDOWN: @@ -1316,7 +1320,7 @@ void CL_BindUI( void ) { uii.Key_GetBindingBuf = Key_GetBindingBuf; uii.Key_IsDown = Key_IsDown; uii.Key_KeynumToStringBuf = Key_KeynumToStringBuf; - uii.Key_SetBinding = Key_SetBinding; + uii.Key_SetBinding = CL_Key_SetBinding; uii.Key_GetCatcher = Key_GetCatcher; uii.Key_GetOverstrikeMode = Key_GetOverstrikeMode; uii.Key_SetCatcher = CL_Key_SetCatcher; diff --git a/codemp/client/keys.h b/codemp/client/keys.h index 6ecf701893..32e8c3f05d 100644 --- a/codemp/client/keys.h +++ b/codemp/client/keys.h @@ -27,14 +27,16 @@ along with this program; if not, see . typedef struct qkey_s { qboolean down; + int downModifiers; int repeats; // if > 1, it is autorepeating - char *binding; + char *binding[KEYMOD_COMBINATIONS]; } qkey_t; typedef struct keyGlobals_s { qboolean anykeydown; qboolean key_overstrikeMode; int keyDownCount; + int modifiers; qkey_t keys[MAX_KEYS]; } keyGlobals_t; @@ -65,11 +67,14 @@ void Field_CharEvent ( field_t *edit, int ch ); void Field_Draw ( field_t *edit, int x, int y, qboolean showCursor, qboolean noColorEscape ); void Field_BigDraw ( field_t *edit, int x, int y, qboolean showCursor, qboolean noColorEscape ); -void Key_SetBinding ( int keynum, const char *binding ); -char * Key_GetBinding ( int keynum ); +int Key_ModifiersFromString ( const char *str ); +const char *Key_SkipModifiers ( const char *str ); +char * Key_ModifiersStringFromModifiers( int modifiers ); +void Key_SetBinding ( int keynum, int modifiers, const char *binding ); +char * Key_GetBinding ( int keynum, int modifiers ); qboolean Key_IsDown ( int keynum ); -int Key_StringToKeynum ( char *str ); +int Key_StringToKeynum ( const char *str ); qboolean Key_GetOverstrikeMode ( void ); void Key_SetOverstrikeMode ( qboolean state ); void Key_ClearStates ( void ); -int Key_GetKey ( const char *binding ); +int Key_GetKey ( const char *binding, int modifiers ); diff --git a/codemp/null/null_client.cpp b/codemp/null/null_client.cpp index fff73ff1c0..ce605021bd 100644 --- a/codemp/null/null_client.cpp +++ b/codemp/null/null_client.cpp @@ -60,6 +60,9 @@ qboolean CL_GameCommand( void ) { void CL_KeyEvent (int key, qboolean down, unsigned time) { } +void CL_ModifierEvent( int modifiers ) { +} + qboolean UI_GameCommand( void ) { return qfalse; } diff --git a/codemp/qcommon/common.cpp b/codemp/qcommon/common.cpp index d8c901d294..248facd933 100644 --- a/codemp/qcommon/common.cpp +++ b/codemp/qcommon/common.cpp @@ -952,6 +952,9 @@ int Com_EventLoop( void ) { } Cbuf_AddText( "\n" ); break; + case SE_MODIFIER: + CL_ModifierEvent( ev.evValue ); + break; } // free any block data @@ -1905,10 +1908,10 @@ void Field_CompleteKeyname( void ) matchCount = 0; shortestMatch[ 0 ] = 0; - Key_KeynameCompletion( FindMatches ); + Key_Completion( FindMatches, completionString ); if( !Field_Complete( ) ) - Key_KeynameCompletion( PrintKeyMatches ); + Key_Completion( PrintKeyMatches, completionString ); } #endif diff --git a/codemp/qcommon/qcommon.h b/codemp/qcommon/qcommon.h index 92d6724f35..a360cefb15 100644 --- a/codemp/qcommon/qcommon.h +++ b/codemp/qcommon/qcommon.h @@ -928,6 +928,7 @@ void CL_Shutdown( void ); void CL_Frame( int msec ); qboolean CL_GameCommand( void ); void CL_KeyEvent (int key, qboolean down, unsigned time); +void CL_ModifierEvent( int modifiers ); void CL_CharEvent( int key ); // char events are for field typing, not game control @@ -963,7 +964,7 @@ void CL_StartHunkUsers( void ); qboolean CL_ConnectedToRemoteServer( void ); // returns qtrue if connected to a server -void Key_KeynameCompletion ( void(*callback)( const char *s ) ); +void Key_Completion( void(*callback)( const char *s ), const char *input ); // for keyname autocompletion void Key_WriteBindings( fileHandle_t f ); diff --git a/codemp/ui/keycodes.h b/codemp/ui/keycodes.h index 43b514a170..6bee55b664 100644 --- a/codemp/ui/keycodes.h +++ b/codemp/ui/keycodes.h @@ -365,3 +365,16 @@ typedef enum // to avoid duplicating the paths, the char events are just // distinguished by or'ing in K_CHAR_FLAG (ugly) #define K_CHAR_FLAG 1024 + +// Supported modifiers +#define KEYMOD_LSHIFT (1) +#define KEYMOD_RSHIFT (1 << 1) +#define KEYMOD_LCTRL (1 << 2) +#define KEYMOD_RCTRL (1 << 3) +#define KEYMOD_LALT (1 << 4) +#define KEYMOD_RALT (1 << 5) +#define KEYMOD_LSUPER (1 << 6) +#define KEYMOD_RSUPER (1 << 7) +#define KEYMOD_NUMLOCK (1 << 8) +#define KEYMOD_CAPSLOCK (1 << 9) +#define KEYMOD_COMBINATIONS ((1 << 10)-1) // NOTE: This must be the last entry diff --git a/shared/sdl/sdl_input.cpp b/shared/sdl/sdl_input.cpp index 8153513a65..b20b1eb77d 100644 --- a/shared/sdl/sdl_input.cpp +++ b/shared/sdl/sdl_input.cpp @@ -410,6 +410,28 @@ static fakeAscii_t IN_TranslateSDLToJKKey( SDL_Keysym *keysym, qboolean down ) { return key; } +/* +=============== +IN_TranslateModifiers +=============== +*/ +static int IN_TranslateModifiers( SDL_Keysym *keysym ) +{ + int modifiers = 0; + if( keysym->mod & KMOD_LSHIFT ) modifiers |= KEYMOD_LSHIFT; + if( keysym->mod & KMOD_RSHIFT ) modifiers |= KEYMOD_RSHIFT; + if( keysym->mod & KMOD_LCTRL ) modifiers |= KEYMOD_LCTRL; + if( keysym->mod & KMOD_RCTRL ) modifiers |= KEYMOD_RCTRL; + if( keysym->mod & KMOD_LALT ) modifiers |= KEYMOD_LALT; + if( keysym->mod & KMOD_RALT ) modifiers |= KEYMOD_RALT; + if( keysym->mod & KMOD_MODE ) modifiers |= KEYMOD_RALT; // Mode is Alt GR depending on keyboard layout, so just consider it Right Alt + if( keysym->mod & KMOD_LGUI ) modifiers |= KEYMOD_LSUPER; + if( keysym->mod & KMOD_RGUI ) modifiers |= KEYMOD_RSUPER; + if( keysym->mod & KMOD_NUM ) modifiers |= KEYMOD_NUMLOCK; + if( keysym->mod & KMOD_CAPS ) modifiers |= KEYMOD_CAPSLOCK; + return modifiers; +} + /* =============== IN_GobbleMotionEvents @@ -819,6 +841,9 @@ static void IN_ProcessEvents( void ) fakeAscii_t key = A_NULL; static fakeAscii_t lastKeyDown = A_NULL; + static int lastModifiers = 0; + int modifiers = 0; + if( !SDL_WasInit( SDL_INIT_VIDEO ) ) return; @@ -828,6 +853,12 @@ static void IN_ProcessEvents( void ) { case SDL_KEYDOWN: key = IN_TranslateSDLToJKKey( &e.key.keysym, qtrue ); + modifiers = IN_TranslateModifiers( &e.key.keysym ); + if ( modifiers != lastModifiers ) + { // Send changes to modifiers as events. That way the gamecode can remember the latest modifiers and apply them to mouse buttons etc. as well and we don't have to extend the events to support 3 values. + lastModifiers = modifiers; + Sys_QueEvent( 0, SE_MODIFIER, modifiers, 0, 0, NULL ); + } if ( key != A_NULL ) Sys_QueEvent( 0, SE_KEY, key, qtrue, 0, NULL ); @@ -841,6 +872,12 @@ static void IN_ProcessEvents( void ) case SDL_KEYUP: key = IN_TranslateSDLToJKKey( &e.key.keysym, qfalse ); + modifiers = IN_TranslateModifiers( &e.key.keysym ); + if ( modifiers != lastModifiers ) + { // Send changes to modifiers as events. That way the gamecode can remember the latest modifiers and apply them to mouse buttons etc. as well and we don't have to extend the events to support 3 values. + lastModifiers = modifiers; + Sys_QueEvent( 0, SE_MODIFIER, modifiers, 0, 0, NULL ); + } if( key != A_NULL ) Sys_QueEvent( 0, SE_KEY, key, qfalse, 0, NULL ); diff --git a/shared/sys/sys_event.cpp b/shared/sys/sys_event.cpp index 6bf884f42e..b424a06a22 100644 --- a/shared/sys/sys_event.cpp +++ b/shared/sys/sys_event.cpp @@ -49,7 +49,8 @@ static const char *Sys_EventName( sysEventType_t evType ) { "SE_CHAR", "SE_MOUSE", "SE_JOYSTICK_AXIS", - "SE_CONSOLE" + "SE_CONSOLE", + "SE_MODIFIER", }; if ( evType >= SE_MAX ) { diff --git a/shared/sys/sys_public.h b/shared/sys/sys_public.h index 62e2244508..574130fc59 100644 --- a/shared/sys/sys_public.h +++ b/shared/sys/sys_public.h @@ -70,6 +70,7 @@ typedef enum { SE_MOUSE, // evValue and evValue2 are reletive signed x / y moves SE_JOYSTICK_AXIS, // evValue is an axis number and evValue2 is the current state (-127 to 127) SE_CONSOLE, // evPtr is a char* + SE_MODIFIER, // evValue is the current key modifier SE_MAX } sysEventType_t;