
The `.editorconfig` file in this repo is configured to trim all trailing whitespace regardless of whether the line is modified. Trims all trailing whitespace in the repository to make the codebase easier to work with in editors that respect `.editorconfig`. `git blame` becomes less useful on these lines but it already isn't very useful. Commands: ``` find . -type f -name '*.h' -exec sed --in-place 's/[[:space:]]\+$//' {} \+ find . -type f -name '*.c' -exec sed --in-place 's/[[:space:]]\+$//' {} \+ ```
1211 lines
24 KiB
C
1211 lines
24 KiB
C
/*
|
|
keys.c - console key events
|
|
Copyright (C) 2007 Uncle Mike
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
*/
|
|
|
|
#include "common.h"
|
|
#include "input.h"
|
|
#include "client.h"
|
|
#include "vgui_draw.h"
|
|
#include "platform/platform.h"
|
|
|
|
typedef struct
|
|
{
|
|
qboolean down;
|
|
qboolean gamedown;
|
|
int repeats; // if > 1, it is autorepeating
|
|
const char *binding;
|
|
} enginekey_t;
|
|
|
|
typedef struct keyname_s
|
|
{
|
|
char *name; // key name
|
|
int keynum; // key number
|
|
const char *binding; // default bind
|
|
} keyname_t;
|
|
|
|
enginekey_t keys[256];
|
|
|
|
keyname_t keynames[] =
|
|
{
|
|
{"TAB", K_TAB, "" },
|
|
{"ENTER", K_ENTER, "" },
|
|
{"ESCAPE", K_ESCAPE, "escape" }, // hardcoded
|
|
{"SPACE", K_SPACE, "+jump" },
|
|
{"BACKSPACE", K_BACKSPACE, "" },
|
|
{"UPARROW", K_UPARROW, "+forward" },
|
|
{"DOWNARROW", K_DOWNARROW, "+back" },
|
|
{"LEFTARROW", K_LEFTARROW, "+left" },
|
|
{"RIGHTARROW", K_RIGHTARROW, "+right" },
|
|
{"ALT", K_ALT, "+strafe" },
|
|
{"CTRL", K_CTRL, "+attack" },
|
|
{"SHIFT", K_SHIFT, "+speed" },
|
|
{"CAPSLOCK", K_CAPSLOCK, "" },
|
|
{"SCROLLOCK", K_SCROLLOCK, "" },
|
|
{"F1", K_F1, "cmd help" },
|
|
{"F2", K_F2, "menu_savegame" },
|
|
{"F3", K_F3, "menu_loadgame" },
|
|
{"F4", K_F4, "menu_controls" },
|
|
{"F5", K_F5, "menu_creategame" },
|
|
{"F6", K_F6, "savequick" },
|
|
{"F7", K_F7, "loadquick" },
|
|
{"F8", K_F8, "stop" },
|
|
{"F9", K_F9, "" },
|
|
{"F10", K_F10, "menu_main" },
|
|
{"F11", K_F11, "" },
|
|
{"F12", K_F12, "snapshot" },
|
|
{"INS", K_INS, "" },
|
|
{"DEL", K_DEL, "+lookdown" },
|
|
{"PGDN", K_PGDN, "+lookup" },
|
|
{"PGUP", K_PGUP, "" },
|
|
{"HOME", K_HOME, "" },
|
|
{"END", K_END, "centerview" },
|
|
|
|
// mouse buttouns
|
|
{"MOUSE1", K_MOUSE1, "+attack" },
|
|
{"MOUSE2", K_MOUSE2, "+attack2" },
|
|
{"MOUSE3", K_MOUSE3, "" },
|
|
{"MOUSE4", K_MOUSE4, "" },
|
|
{"MOUSE5", K_MOUSE5, "" },
|
|
{"MWHEELUP", K_MWHEELUP, "" },
|
|
{"MWHEELDOWN", K_MWHEELDOWN, "" },
|
|
|
|
// digital keyboard
|
|
{"KP_HOME", K_KP_HOME, "" },
|
|
{"KP_UPARROW", K_KP_UPARROW, "+forward" },
|
|
{"KP_PGUP", K_KP_PGUP, "" },
|
|
{"KP_LEFTARROW", K_KP_LEFTARROW, "+left" },
|
|
{"KP_5", K_KP_5, "" },
|
|
{"KP_RIGHTARROW", K_KP_RIGHTARROW, "+right" },
|
|
{"KP_END", K_KP_END, "centerview" },
|
|
{"KP_DOWNARROW", K_KP_DOWNARROW, "+back" },
|
|
{"KP_PGDN", K_KP_PGDN, "+lookup" },
|
|
{"KP_ENTER", K_KP_ENTER, "" },
|
|
{"KP_INS", K_KP_INS, "" },
|
|
{"KP_DEL", K_KP_DEL, "+lookdown" },
|
|
{"KP_SLASH", K_KP_SLASH, "" },
|
|
{"KP_MINUS", K_KP_MINUS, "" },
|
|
{"KP_PLUS", K_KP_PLUS, "" },
|
|
{"PAUSE", K_PAUSE, "pause" },
|
|
|
|
{"A_BUTTON", K_A_BUTTON, ""}, // they match xbox controller
|
|
{"B_BUTTON", K_B_BUTTON, ""},
|
|
{"X_BUTTON", K_X_BUTTON, ""},
|
|
{"Y_BUTTON", K_Y_BUTTON, ""},
|
|
{"L1_BUTTON", K_L1_BUTTON, ""},
|
|
{"R1_BUTTON", K_R1_BUTTON, ""},
|
|
{"BACK", K_BACK_BUTTON, ""},
|
|
{"MODE", K_MODE_BUTTON, ""},
|
|
{"START", K_START_BUTTON, ""},
|
|
{"STICK1", K_LSTICK, ""},
|
|
{"STICK2", K_RSTICK, ""},
|
|
{"L2_BUTTON", K_L2_BUTTON, ""}, // in case...
|
|
{"R2_BUTTON", K_R2_BUTTON, ""},
|
|
{"C_BUTTON", K_C_BUTTON, ""},
|
|
{"Z_BUTTON", K_Z_BUTTON, ""},
|
|
{"AUX16", K_AUX16, ""}, // generic
|
|
{"AUX17", K_AUX17, ""},
|
|
{"AUX18", K_AUX18, ""},
|
|
{"AUX19", K_AUX19, ""},
|
|
{"AUX20", K_AUX20, ""},
|
|
{"AUX21", K_AUX21, ""},
|
|
{"AUX22", K_AUX22, ""},
|
|
{"AUX23", K_AUX23, ""},
|
|
{"AUX24", K_AUX24, ""},
|
|
{"AUX25", K_AUX25, ""},
|
|
{"AUX26", K_AUX26, ""},
|
|
{"AUX27", K_AUX27, ""},
|
|
{"AUX28", K_AUX28, ""},
|
|
{"AUX29", K_AUX29, ""},
|
|
{"AUX30", K_AUX30, ""},
|
|
{"AUX31", K_AUX31, ""},
|
|
{"AUX32", K_AUX32, ""},
|
|
{"LTRIGGER" , K_JOY1 , ""},
|
|
{"RTRIGGER" , K_JOY2 , ""},
|
|
{"JOY3" , K_JOY3 , ""},
|
|
{"JOY4" , K_JOY4 , ""},
|
|
|
|
// raw semicolon seperates commands
|
|
{"SEMICOLON", ';', "" },
|
|
{NULL, 0, NULL },
|
|
};
|
|
|
|
static void OSK_EnableTextInput( qboolean enable, qboolean force );
|
|
static qboolean OSK_KeyEvent( int key, int down );
|
|
static convar_t *osk_enable;
|
|
static convar_t *key_rotate;
|
|
|
|
/*
|
|
===================
|
|
Key_IsDown
|
|
===================
|
|
*/
|
|
int GAME_EXPORT Key_IsDown( int keynum )
|
|
{
|
|
if( keynum == -1 )
|
|
return false;
|
|
return keys[keynum].down;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
Key_GetBind
|
|
===================
|
|
*/
|
|
const char *Key_IsBind( int keynum )
|
|
{
|
|
if( keynum == -1 || !keys[keynum].binding )
|
|
return NULL;
|
|
return keys[keynum].binding;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
Key_StringToKeynum
|
|
|
|
Returns a key number to be used to index keys[] by looking at
|
|
the given string. Single ascii characters return themselves, while
|
|
the K_* names are matched up.
|
|
|
|
0x11 will be interpreted as raw hex, which will allow new controlers
|
|
|
|
to be configured even if they don't have defined names.
|
|
===================
|
|
*/
|
|
int Key_StringToKeynum( const char *str )
|
|
{
|
|
keyname_t *kn;
|
|
|
|
if( !str || !str[0] ) return -1;
|
|
if( !str[1] ) return str[0];
|
|
|
|
// check for hex code
|
|
if( str[0] == '0' && str[1] == 'x' && Q_strlen( str ) == 4 )
|
|
{
|
|
int n1, n2;
|
|
|
|
n1 = str[2];
|
|
if( n1 >= '0' && n1 <= '9' )
|
|
{
|
|
n1 -= '0';
|
|
}
|
|
else if( n1 >= 'a' && n1 <= 'f' )
|
|
{
|
|
n1 = n1 - 'a' + 10;
|
|
}
|
|
else n1 = 0;
|
|
|
|
n2 = str[3];
|
|
if( n2 >= '0' && n2 <= '9' )
|
|
{
|
|
n2 -= '0';
|
|
}
|
|
else if( n2 >= 'a' && n2 <= 'f' )
|
|
{
|
|
n2 = n2 - 'a' + 10;
|
|
}
|
|
else n2 = 0;
|
|
|
|
return n1 * 16 + n2;
|
|
}
|
|
|
|
// scan for a text match
|
|
for( kn = keynames; kn->name; kn++ )
|
|
{
|
|
if( !Q_stricmp( str, kn->name ))
|
|
return kn->keynum;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
Key_KeynumToString
|
|
|
|
Returns a string (either a single ascii char, a K_* name, or a 0x11 hex string) for the
|
|
given keynum.
|
|
===================
|
|
*/
|
|
const char *Key_KeynumToString( int keynum )
|
|
{
|
|
keyname_t *kn;
|
|
static char tinystr[5];
|
|
int i, j;
|
|
|
|
if ( keynum == -1 ) return "<KEY NOT FOUND>";
|
|
if ( keynum < 0 || keynum > 255 ) return "<OUT OF RANGE>";
|
|
|
|
// check for printable ascii (don't use quote)
|
|
if( keynum > 32 && keynum < 127 && keynum != '"' && keynum != ';' && keynum != K_SCROLLOCK )
|
|
{
|
|
tinystr[0] = keynum;
|
|
tinystr[1] = 0;
|
|
return tinystr;
|
|
}
|
|
|
|
// check for a key string
|
|
for( kn = keynames; kn->name; kn++ )
|
|
{
|
|
if( keynum == kn->keynum )
|
|
return kn->name;
|
|
}
|
|
|
|
// make a hex string
|
|
i = keynum >> 4;
|
|
j = keynum & 15;
|
|
|
|
tinystr[0] = '0';
|
|
tinystr[1] = 'x';
|
|
tinystr[2] = i > 9 ? i - 10 + 'a' : i + '0';
|
|
tinystr[3] = j > 9 ? j - 10 + 'a' : j + '0';
|
|
tinystr[4] = 0;
|
|
|
|
return tinystr;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
Key_SetBinding
|
|
===================
|
|
*/
|
|
void GAME_EXPORT Key_SetBinding( int keynum, const char *binding )
|
|
{
|
|
if( keynum == -1 ) return;
|
|
|
|
// free old bindings
|
|
if( keys[keynum].binding )
|
|
{
|
|
Mem_Free((char *)keys[keynum].binding );
|
|
keys[keynum].binding = NULL;
|
|
}
|
|
|
|
// allocate memory for new binding
|
|
keys[keynum].binding = copystring( binding );
|
|
}
|
|
|
|
|
|
/*
|
|
===================
|
|
Key_GetBinding
|
|
===================
|
|
*/
|
|
const char *Key_GetBinding( int keynum )
|
|
{
|
|
if( keynum == -1 ) return NULL;
|
|
return keys[keynum].binding;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
Key_GetKey
|
|
===================
|
|
*/
|
|
int Key_GetKey( const char *pBinding )
|
|
{
|
|
int i, len;
|
|
const char *p;
|
|
|
|
if( !pBinding ) return -1;
|
|
|
|
len = Q_strlen( pBinding );
|
|
|
|
for( i = 0; i < 256; i++ )
|
|
{
|
|
if( !keys[i].binding )
|
|
continue;
|
|
|
|
p = keys[i].binding;
|
|
|
|
if( *p == '+' )
|
|
p++;
|
|
|
|
if( !Q_strnicmp( p, pBinding, len ) )
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
Key_Unbind_f
|
|
===================
|
|
*/
|
|
void Key_Unbind_f( void )
|
|
{
|
|
int b;
|
|
|
|
if( Cmd_Argc() != 2 )
|
|
{
|
|
Con_Printf( S_USAGE "unbind <key> : remove commands from a key\n" );
|
|
return;
|
|
}
|
|
|
|
b = Key_StringToKeynum( Cmd_Argv( 1 ));
|
|
|
|
if( b == -1 )
|
|
{
|
|
Con_Printf( "\"%s\" isn't a valid key\n", Cmd_Argv( 1 ));
|
|
return;
|
|
}
|
|
|
|
Key_SetBinding( b, "" );
|
|
}
|
|
|
|
/*
|
|
===================
|
|
Key_Unbindall_f
|
|
===================
|
|
*/
|
|
void Key_Unbindall_f( void )
|
|
{
|
|
int i;
|
|
|
|
for( i = 0; i < 256; i++ )
|
|
{
|
|
if( keys[i].binding )
|
|
Key_SetBinding( i, "" );
|
|
}
|
|
|
|
// set some defaults
|
|
Key_SetBinding( K_ESCAPE, "escape" );
|
|
}
|
|
|
|
/*
|
|
===================
|
|
Key_Reset_f
|
|
===================
|
|
*/
|
|
void Key_Reset_f( void )
|
|
{
|
|
keyname_t *kn;
|
|
int i;
|
|
|
|
// clear all keys first
|
|
for( i = 0; i < 256; i++ )
|
|
{
|
|
if( keys[i].binding )
|
|
Key_SetBinding( i, "" );
|
|
}
|
|
|
|
// apply default values
|
|
for( kn = keynames; kn->name; kn++ )
|
|
Key_SetBinding( kn->keynum, kn->binding );
|
|
}
|
|
|
|
/*
|
|
===================
|
|
Key_Bind_f
|
|
===================
|
|
*/
|
|
void Key_Bind_f( void )
|
|
{
|
|
char cmd[1024];
|
|
int i, c, b;
|
|
|
|
c = Cmd_Argc();
|
|
|
|
if( c < 2 )
|
|
{
|
|
Con_Printf( S_USAGE "bind <key> [command] : attach a command to a key\n" );
|
|
return;
|
|
}
|
|
|
|
b = Key_StringToKeynum( Cmd_Argv( 1 ));
|
|
|
|
if( b == -1 )
|
|
{
|
|
Con_Printf( "\"%s\" isn't a valid key\n", Cmd_Argv( 1 ));
|
|
return;
|
|
}
|
|
|
|
if( c == 2 )
|
|
{
|
|
if( keys[b].binding )
|
|
Con_Printf( "\"%s\" = \"%s\"\n", Cmd_Argv( 1 ), keys[b].binding );
|
|
else Con_Printf( "\"%s\" is not bound\n", Cmd_Argv( 1 ));
|
|
return;
|
|
}
|
|
|
|
// copy the rest of the command line
|
|
cmd[0] = 0; // start out with a null string
|
|
|
|
for( i = 2; i < c; i++ )
|
|
{
|
|
Q_strcat( cmd, Cmd_Argv( i ));
|
|
if( i != ( c - 1 )) Q_strcat( cmd, " " );
|
|
}
|
|
|
|
Key_SetBinding( b, cmd );
|
|
}
|
|
|
|
/*
|
|
============
|
|
Key_WriteBindings
|
|
|
|
Writes lines containing "bind key value"
|
|
============
|
|
*/
|
|
void Key_WriteBindings( file_t *f )
|
|
{
|
|
int i;
|
|
|
|
if( !f ) return;
|
|
|
|
FS_Printf( f, "unbindall\n" );
|
|
|
|
for( i = 0; i < 256; i++ )
|
|
{
|
|
if( !COM_CheckString( keys[i].binding ))
|
|
continue;
|
|
|
|
FS_Printf( f, "bind %s \"%s\"\n", Key_KeynumToString( i ), keys[i].binding );
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
Key_Bindlist_f
|
|
|
|
============
|
|
*/
|
|
void Key_Bindlist_f( void )
|
|
{
|
|
int i;
|
|
|
|
for( i = 0; i < 256; i++ )
|
|
{
|
|
if( !COM_CheckString( keys[i].binding ))
|
|
continue;
|
|
|
|
Con_Printf( "%s \"%s\"\n", Key_KeynumToString( i ), keys[i].binding );
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============================================================================
|
|
|
|
LINE TYPING INTO THE CONSOLE
|
|
|
|
==============================================================================
|
|
*/
|
|
/*
|
|
===================
|
|
Key_Init
|
|
===================
|
|
*/
|
|
void Key_Init( void )
|
|
{
|
|
keyname_t *kn;
|
|
|
|
// register our functions
|
|
Cmd_AddCommand( "bind", Key_Bind_f, "binds a command to the specified key in bindmap" );
|
|
Cmd_AddCommand( "unbind", Key_Unbind_f, "removes a command on the specified key in bindmap" );
|
|
Cmd_AddCommand( "unbindall", Key_Unbindall_f, "removes all commands from all keys in bindmap" );
|
|
Cmd_AddCommand( "resetkeys", Key_Reset_f, "reset all keys to their default values" );
|
|
Cmd_AddCommand( "bindlist", Key_Bindlist_f, "display current key bindings" );
|
|
Cmd_AddCommand( "makehelp", Key_EnumCmds_f, "write help.txt that contains all console cvars and cmds" );
|
|
|
|
// setup default binding. "unbindall" from config.cfg will be reset it
|
|
for( kn = keynames; kn->name; kn++ ) Key_SetBinding( kn->keynum, kn->binding );
|
|
|
|
osk_enable = Cvar_Get( "osk_enable", "0", FCVAR_ARCHIVE, "enable built-in on-screen keyboard" );
|
|
key_rotate = Cvar_Get( "key_rotate", "0", FCVAR_ARCHIVE, "rotate arrow keys (0-3)" );
|
|
|
|
}
|
|
|
|
/*
|
|
===================
|
|
Key_AddKeyCommands
|
|
===================
|
|
*/
|
|
void Key_AddKeyCommands( int key, const char *kb, qboolean down )
|
|
{
|
|
char button[1024];
|
|
char *buttonPtr;
|
|
char cmd[1024];
|
|
int i;
|
|
|
|
if( !kb ) return;
|
|
buttonPtr = button;
|
|
|
|
for( i = 0; ; i++ )
|
|
{
|
|
if( kb[i] == ';' || !kb[i] )
|
|
{
|
|
*buttonPtr = '\0';
|
|
if( button[0] == '+' )
|
|
{
|
|
// button commands add keynum as a parm
|
|
if( down ) Q_sprintf( cmd, "%s %i\n", button, key );
|
|
else Q_sprintf( cmd, "-%s %i\n", button + 1, key );
|
|
Cbuf_AddText( cmd );
|
|
}
|
|
else if( down )
|
|
{
|
|
// down-only command
|
|
Cbuf_AddText( button );
|
|
Cbuf_AddText( "\n" );
|
|
}
|
|
|
|
buttonPtr = button;
|
|
while(( kb[i] <= ' ' || kb[i] == ';' ) && kb[i] != 0 )
|
|
i++;
|
|
}
|
|
|
|
*buttonPtr++ = kb[i];
|
|
if( !kb[i] ) break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===================
|
|
Key_IsAllowedAutoRepeat
|
|
|
|
List of keys that allows auto-repeat
|
|
===================
|
|
*/
|
|
static qboolean Key_IsAllowedAutoRepeat( int key )
|
|
{
|
|
if( cls.key_dest != key_game )
|
|
return true;
|
|
|
|
switch( key )
|
|
{
|
|
case K_BACKSPACE:
|
|
case K_PAUSE:
|
|
case K_PGUP:
|
|
case K_KP_PGUP:
|
|
case K_PGDN:
|
|
case K_KP_PGDN:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static int Key_Rotate( int key )
|
|
{
|
|
if( key_rotate->value == 1.0f ) // CW
|
|
{
|
|
if( key == K_UPARROW )
|
|
key = K_LEFTARROW;
|
|
else if( key == K_LEFTARROW )
|
|
key = K_DOWNARROW;
|
|
else if( key == K_RIGHTARROW )
|
|
key = K_UPARROW;
|
|
else if( key == K_DOWNARROW )
|
|
key = K_RIGHTARROW;
|
|
}
|
|
|
|
else if( key_rotate->value == 3.0f ) // CCW
|
|
{
|
|
if( key == K_UPARROW )
|
|
key = K_RIGHTARROW;
|
|
else if( key == K_LEFTARROW )
|
|
key = K_UPARROW;
|
|
else if( key == K_RIGHTARROW )
|
|
key = K_DOWNARROW;
|
|
else if( key == K_DOWNARROW )
|
|
key = K_LEFTARROW;
|
|
}
|
|
|
|
else if( key_rotate->value == 2.0f )
|
|
{
|
|
if( key == K_UPARROW )
|
|
key = K_DOWNARROW;
|
|
else if( key == K_LEFTARROW )
|
|
key = K_RIGHTARROW;
|
|
else if( key == K_RIGHTARROW )
|
|
key = K_LEFTARROW;
|
|
else if( key == K_DOWNARROW )
|
|
key = K_UPARROW;
|
|
}
|
|
|
|
return key;
|
|
}
|
|
|
|
|
|
/*
|
|
===================
|
|
Key_Event
|
|
|
|
Called by the system for both key up and key down events
|
|
===================
|
|
*/
|
|
void GAME_EXPORT Key_Event( int key, int down )
|
|
{
|
|
const char *kb;
|
|
|
|
key = Key_Rotate( key );
|
|
|
|
if( OSK_KeyEvent( key, down ) )
|
|
return;
|
|
|
|
// key was pressed before engine was run
|
|
if( !keys[key].down && !down )
|
|
return;
|
|
|
|
kb = keys[key].binding;
|
|
keys[key].down = down;
|
|
|
|
#ifdef HACKS_RELATED_HLMODS
|
|
if(( cls.key_dest == key_game ) && ( cls.state == ca_cinematic ) && ( key != K_ESCAPE || !down ))
|
|
{
|
|
// only escape passed when cinematic is playing
|
|
// HLFX 0.6 bug: crash in vgui3.dll while press +attack during movie playback
|
|
return;
|
|
}
|
|
#endif
|
|
// distribute the key down event to the apropriate handler
|
|
if( cls.key_dest == key_game && ( down || keys[key].gamedown ))
|
|
{
|
|
if( !clgame.dllFuncs.pfnKey_Event( down, key, keys[key].binding ))
|
|
{
|
|
if( keys[key].repeats == 0 && down )
|
|
{
|
|
keys[key].gamedown = true;
|
|
}
|
|
|
|
if( !down )
|
|
{
|
|
keys[key].gamedown = false;
|
|
keys[key].repeats = 0;
|
|
}
|
|
return; // handled in client.dll
|
|
}
|
|
}
|
|
|
|
// update auto-repeat status
|
|
if( down )
|
|
{
|
|
keys[key].repeats++;
|
|
|
|
if( !Key_IsAllowedAutoRepeat( key ) && keys[key].repeats > 1 )
|
|
{
|
|
// ignore most autorepeats
|
|
return;
|
|
}
|
|
|
|
if( key >= 200 && !kb )
|
|
Con_Printf( "%s is unbound.\n", Key_KeynumToString( key ));
|
|
}
|
|
else
|
|
{
|
|
keys[key].gamedown = false;
|
|
keys[key].repeats = 0;
|
|
}
|
|
|
|
VGui_KeyEvent( key, down );
|
|
Touch_KeyEvent( key, down );
|
|
|
|
// console key is hardcoded, so the user can never unbind it
|
|
if( key == '`' || key == '~' )
|
|
{
|
|
// we are in typing mode, so don't switch to console
|
|
if( cls.key_dest == key_message || !down )
|
|
return;
|
|
|
|
Con_ToggleConsole_f();
|
|
return;
|
|
}
|
|
|
|
// escape is always handled special
|
|
if( key == K_ESCAPE && down )
|
|
{
|
|
switch( cls.key_dest )
|
|
{
|
|
case key_game:
|
|
if( CVAR_TO_BOOL( gl_showtextures ))
|
|
{
|
|
// close texture atlas
|
|
Cvar_SetValue( "r_showtextures", 0.0f );
|
|
return;
|
|
}
|
|
else if( host.mouse_visible && cls.state != ca_cinematic )
|
|
{
|
|
clgame.dllFuncs.pfnKey_Event( down, key, keys[key].binding );
|
|
return; // handled in client.dll
|
|
}
|
|
break;
|
|
case key_message:
|
|
Key_Message( key );
|
|
return;
|
|
case key_console:
|
|
if( cls.state == ca_active && !cl.background )
|
|
Key_SetKeyDest( key_game );
|
|
else UI_SetActiveMenu( true );
|
|
return;
|
|
case key_menu:
|
|
UI_KeyEvent( key, true );
|
|
return;
|
|
default: return;
|
|
}
|
|
}
|
|
|
|
if( cls.key_dest == key_menu )
|
|
{
|
|
// only non printable keys passed
|
|
if( !gameui.use_text_api )
|
|
Key_EnableTextInput( true, false );
|
|
//pass printable chars for old menus
|
|
if( !gameui.use_text_api && !host.textmode && down && ( key >= 32 ) && ( key <= 'z' ) )
|
|
{
|
|
if( Key_IsDown( K_SHIFT ) )
|
|
{
|
|
key += 'A' - 'a';
|
|
}
|
|
UI_CharEvent( key );
|
|
}
|
|
UI_KeyEvent( key, down );
|
|
return;
|
|
}
|
|
|
|
// key up events only perform actions if the game key binding is
|
|
// a button command (leading + sign). These will be processed even in
|
|
// console mode and menu mode, to keep the character from continuing
|
|
// an action started before a mode switch.
|
|
if( !down )
|
|
{
|
|
Key_AddKeyCommands( key, kb, down );
|
|
return;
|
|
}
|
|
|
|
// distribute the key down event to the apropriate handler
|
|
if( cls.key_dest == key_game )
|
|
{
|
|
Key_AddKeyCommands( key, kb, down );
|
|
}
|
|
else if( cls.key_dest == key_console )
|
|
{
|
|
Key_Console( key );
|
|
}
|
|
else if( cls.key_dest == key_message )
|
|
{
|
|
Key_Message( key );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
Key_EnableTextInput
|
|
|
|
================
|
|
*/
|
|
void Key_EnableTextInput( qboolean enable, qboolean force )
|
|
{
|
|
if( CVAR_TO_BOOL( osk_enable ) )
|
|
{
|
|
OSK_EnableTextInput( enable, force );
|
|
return;
|
|
}
|
|
if( enable && ( !host.textmode || force ) )
|
|
Platform_EnableTextInput( true );
|
|
else if( !enable )
|
|
Platform_EnableTextInput( false );
|
|
|
|
host.textmode = enable;
|
|
}
|
|
|
|
/*
|
|
=========
|
|
Key_SetKeyDest
|
|
=========
|
|
*/
|
|
void GAME_EXPORT Key_SetKeyDest( int key_dest )
|
|
{
|
|
IN_ToggleClientMouse( key_dest, cls.key_dest );
|
|
|
|
switch( key_dest )
|
|
{
|
|
case key_game:
|
|
Key_EnableTextInput( false, false );
|
|
cls.key_dest = key_game;
|
|
break;
|
|
case key_menu:
|
|
Key_EnableTextInput( false, false );
|
|
cls.key_dest = key_menu;
|
|
break;
|
|
case key_console:
|
|
Key_EnableTextInput( true, false );
|
|
cls.key_dest = key_console;
|
|
break;
|
|
case key_message:
|
|
Key_EnableTextInput( true, false );
|
|
cls.key_dest = key_message;
|
|
break;
|
|
default:
|
|
Host_Error( "Key_SetKeyDest: wrong destination (%i)\n", key_dest );
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===================
|
|
Key_ClearStates
|
|
===================
|
|
*/
|
|
void GAME_EXPORT Key_ClearStates( void )
|
|
{
|
|
int i;
|
|
|
|
// don't clear keys during changelevel
|
|
if( cls.changelevel ) return;
|
|
|
|
for( i = 0; i < 256; i++ )
|
|
{
|
|
if( keys[i].down )
|
|
Key_Event( i, false );
|
|
|
|
keys[i].down = 0;
|
|
keys[i].repeats = 0;
|
|
keys[i].gamedown = 0;
|
|
}
|
|
|
|
if( clgame.hInstance )
|
|
clgame.dllFuncs.IN_ClearStates();
|
|
}
|
|
|
|
/*
|
|
===================
|
|
CL_CharEvent
|
|
|
|
Normal keyboard characters, already shifted / capslocked / etc
|
|
===================
|
|
*/
|
|
void CL_CharEvent( int key )
|
|
{
|
|
// the console key should never be used as a char
|
|
if( key == '`' || key == '~' ) return;
|
|
|
|
if( cls.key_dest == key_console && !Con_Visible( ))
|
|
{
|
|
if((char)key == '`' || (char)key == '?' )
|
|
return; // don't pass '`' when we open the console
|
|
}
|
|
|
|
// distribute the key down event to the apropriate handler
|
|
if( cls.key_dest == key_console || cls.key_dest == key_message )
|
|
{
|
|
Con_CharEvent( key );
|
|
}
|
|
else if( cls.key_dest == key_menu )
|
|
{
|
|
UI_CharEvent( key );
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
Key_ToUpper
|
|
|
|
A helper function if platform input doesn't support text mode properly
|
|
============
|
|
*/
|
|
int Key_ToUpper( int keynum )
|
|
{
|
|
keynum = Q_toupper( keynum );
|
|
if( keynum == '-' )
|
|
keynum = '_';
|
|
if( keynum == '=' )
|
|
keynum = '+';
|
|
if( keynum == ';' )
|
|
keynum = ':';
|
|
if( keynum == '\'' )
|
|
keynum = '"';
|
|
|
|
return keynum;
|
|
}
|
|
|
|
/* On-screen keyboard:
|
|
*
|
|
* 4 lines with 13 buttons each
|
|
* Left trigger == backspace
|
|
* Right trigger == space
|
|
* Any button press is button press on keyboard
|
|
*
|
|
* Our layout:
|
|
* 0 1 2 3 4 5 6 7 8 9 10 11 12
|
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
|
|
* |` |1 |2 |3 |4 |5 |6 |7 |8 |9 |0 |- |= | 0
|
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
|
|
* |q |w |e |r |t |y |u |i |o |p |[ |] |\ | 1
|
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
|
|
* |CL|a |s |d |f |g |h |j |k |l |; |' |BS| 2
|
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
|
|
* |SH|z |x |c |v |b |n |m |, |. |/ |SP|EN| 3
|
|
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
|
|
*/
|
|
|
|
#define MAX_OSK_ROWS 13
|
|
#define MAX_OSK_LINES 4
|
|
|
|
enum
|
|
{
|
|
OSK_DEFAULT = 0,
|
|
OSK_UPPER, // on caps, shift
|
|
/*
|
|
OSK_RUSSIAN,
|
|
OSK_RUSSIAN_UPPER,
|
|
*/
|
|
OSK_LAST
|
|
};
|
|
|
|
enum
|
|
{
|
|
OSK_TAB = 16,
|
|
OSK_SHIFT,
|
|
OSK_BACKSPACE,
|
|
OSK_ENTER,
|
|
OSK_SPECKEY_LAST
|
|
};
|
|
static const char *osk_keylayout[][4] =
|
|
{
|
|
{
|
|
"`1234567890-=", // 13
|
|
"qwertyuiop[]\\", // 13
|
|
"\x10" "asdfghjkl;'" "\x12", // 11 + caps on a left, enter on a right
|
|
"\x11" "zxcvbnm,./ " "\x13" // 10 + esc on left + shift on a left/right
|
|
},
|
|
{
|
|
"~!@#$%^&*()_+",
|
|
"QWERTYUIOP{}|",
|
|
"\x10" "ASDFGHJKL:\"" "\x12",
|
|
"\x11" "ZXCVBNM<>? " "\x13"
|
|
}
|
|
};
|
|
|
|
struct osk_s
|
|
{
|
|
qboolean enable;
|
|
int curlayout;
|
|
qboolean shift;
|
|
qboolean sending;
|
|
struct {
|
|
signed char x;
|
|
signed char y;
|
|
char val;
|
|
} curbutton;
|
|
} osk;
|
|
|
|
static qboolean OSK_KeyEvent( int key, int down )
|
|
{
|
|
if( !osk.enable || !CVAR_TO_BOOL( osk_enable ) )
|
|
return false;
|
|
|
|
if( osk.sending )
|
|
{
|
|
osk.sending = false;
|
|
return false;
|
|
}
|
|
|
|
if( osk.curbutton.val == 0 )
|
|
{
|
|
if( key == K_ENTER )
|
|
{
|
|
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
switch ( key )
|
|
{
|
|
case K_ENTER:
|
|
switch( osk.curbutton.val )
|
|
{
|
|
case OSK_ENTER:
|
|
osk.sending = true;
|
|
Key_Event( K_ENTER, down );
|
|
//osk_enable = false; // TODO: handle multiline
|
|
break;
|
|
case OSK_SHIFT:
|
|
if( !down )
|
|
break;
|
|
|
|
if( osk.curlayout & 1 )
|
|
osk.curlayout--;
|
|
else
|
|
osk.curlayout++;
|
|
|
|
osk.shift = osk.curbutton.val == OSK_SHIFT;
|
|
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
|
|
break;
|
|
case OSK_BACKSPACE:
|
|
Key_Event( K_BACKSPACE, down ); break;
|
|
case OSK_TAB:
|
|
Key_Event( K_TAB, down ); break;
|
|
default:
|
|
{
|
|
int ch;
|
|
|
|
if( !down )
|
|
{
|
|
if( osk.shift && osk.curlayout & 1 )
|
|
osk.curlayout--;
|
|
|
|
osk.shift = false;
|
|
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
|
|
break;
|
|
}
|
|
|
|
if( !Q_stricmp( cl_charset->string, "utf-8" ) )
|
|
ch = (unsigned char)osk.curbutton.val;
|
|
else
|
|
ch = Con_UtfProcessCharForce( (unsigned char)osk.curbutton.val );
|
|
|
|
if( !ch )
|
|
break;
|
|
|
|
Con_CharEvent( ch );
|
|
if( cls.key_dest == key_menu )
|
|
UI_CharEvent ( ch );
|
|
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case K_UPARROW:
|
|
if( down && --osk.curbutton.y < 0 )
|
|
{
|
|
osk.curbutton.y = MAX_OSK_LINES - 1;
|
|
osk.curbutton.val = 0;
|
|
return true;
|
|
}
|
|
break;
|
|
case K_DOWNARROW:
|
|
if( down && ++osk.curbutton.y >= MAX_OSK_LINES )
|
|
{
|
|
osk.curbutton.y = 0;
|
|
osk.curbutton.val = 0;
|
|
return true;
|
|
}
|
|
break;
|
|
case K_LEFTARROW:
|
|
if( down && --osk.curbutton.x < 0 )
|
|
osk.curbutton.x = MAX_OSK_ROWS - 1;
|
|
break;
|
|
case K_RIGHTARROW:
|
|
if( down && ++osk.curbutton.x >= MAX_OSK_ROWS )
|
|
osk.curbutton.x = 0;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
|
|
return true;
|
|
|
|
}
|
|
|
|
/*
|
|
=============
|
|
Joy_EnableTextInput
|
|
|
|
Enables built-in IME
|
|
=============
|
|
*/
|
|
static void OSK_EnableTextInput( qboolean enable, qboolean force )
|
|
{
|
|
qboolean old = osk.enable;
|
|
|
|
osk.enable = enable;
|
|
|
|
if( osk.enable && (!old || force) )
|
|
{
|
|
osk.curlayout = 0;
|
|
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
|
|
|
|
}
|
|
}
|
|
|
|
#define X_START 0.1347475f
|
|
#define Y_START 0.567f
|
|
#define X_STEP 0.05625
|
|
#define Y_STEP 0.0825
|
|
|
|
/*
|
|
============
|
|
Joy_DrawSymbolButton
|
|
|
|
Draw button with symbol on it
|
|
============
|
|
*/
|
|
static void OSK_DrawSymbolButton( int symb, float x, float y, float width, float height )
|
|
{
|
|
char str[] = {symb & 255, 0};
|
|
byte color[] = { 255, 255, 255, 255 };
|
|
int x1 = x * refState.width,
|
|
y1 = y * refState.height,
|
|
w = width * refState.width,
|
|
h = height * refState.height;
|
|
|
|
if( symb == osk.curbutton.val )
|
|
{
|
|
ref.dllFuncs.FillRGBABlend( x1, y1, w, h, 255, 160, 0, 100 );
|
|
}
|
|
|
|
if( !symb || symb == ' ' || (symb >= OSK_TAB && symb < OSK_SPECKEY_LAST ) )
|
|
return;
|
|
|
|
Con_DrawCharacter( x1 + 1, y1, symb, color );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
Joy_DrawSpecialButton
|
|
|
|
Draw special button, like shift, enter or esc
|
|
=============
|
|
*/
|
|
static void OSK_DrawSpecialButton( const char *name, float x, float y, float width, float height )
|
|
{
|
|
byte color[] = { 0, 255, 0, 255 };
|
|
|
|
Con_DrawString( x * refState.width, y * refState.height, name, color );
|
|
}
|
|
|
|
|
|
/*
|
|
=============
|
|
Joy_DrawOnScreenKeyboard
|
|
|
|
Draw on screen keyboard, if enabled
|
|
=============
|
|
*/
|
|
void OSK_Draw( void )
|
|
{
|
|
const char **curlayout = osk_keylayout[osk.curlayout]; // shortcut :)
|
|
float x, y;
|
|
int i, j;
|
|
|
|
if( !osk.enable || !CVAR_TO_BOOL(osk_enable) || !osk.curbutton.val )
|
|
return;
|
|
|
|
// draw keyboard
|
|
ref.dllFuncs.FillRGBABlend( X_START * refState.width, Y_START * refState.height,
|
|
X_STEP * MAX_OSK_ROWS * refState.width,
|
|
Y_STEP * MAX_OSK_LINES * refState.height, 100, 100, 100, 100 );
|
|
|
|
OSK_DrawSpecialButton( "-]", X_START, Y_START + Y_STEP * 2, X_STEP, Y_STEP );
|
|
OSK_DrawSpecialButton( "<-", X_START + X_STEP * 12, Y_START + Y_STEP * 2, X_STEP, Y_STEP );
|
|
|
|
OSK_DrawSpecialButton( "sh", X_START, Y_START + Y_STEP * 3, X_STEP, Y_STEP );
|
|
OSK_DrawSpecialButton( "en", X_START + X_STEP * 12, Y_START + Y_STEP * 3, X_STEP, Y_STEP );
|
|
|
|
for( y = Y_START, j = 0; j < MAX_OSK_LINES; j++, y += Y_STEP )
|
|
for( x = X_START, i = 0; i < MAX_OSK_ROWS; i++, x += X_STEP )
|
|
OSK_DrawSymbolButton( curlayout[j][i], x, y, X_STEP, Y_STEP );
|
|
}
|