/* * Copyright (C) 1997-2001 Id Software, Inc. * * 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 2 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, * USA. * * ======================================================================= * * Upper layer of the keyboard implementation. This file processes all * keyboard events which are generated by the low level keyboard layer. * * ======================================================================= */ #include "../header/client.h" /* * key up events are sent even if in console mode */ #define MAXCMDLINE 256 char key_lines [ 32 ] [ MAXCMDLINE ]; int key_linepos; int shift_down = false; int anykeydown; int edit_line = 0; int history_line = 0; int key_waiting; char *keybindings [ K_LAST ]; qboolean consolekeys [ K_LAST ]; /* if true, can't be rebound while in console */ qboolean menubound [ K_LAST ]; /* if true, can't be rebound while in menu */ int keyshift [ K_LAST ]; /* key to map to if shift held down in console */ int key_repeats [ K_LAST ]; /* if > 1, it is autorepeating */ qboolean keydown [ K_LAST ]; qboolean Cmd_IsComplete ( char *cmd ); typedef struct { char *name; int keynum; } keyname_t; keyname_t keynames[] = { { "TAB", K_TAB }, { "ENTER", K_ENTER }, { "ESCAPE", K_ESCAPE }, { "SPACE", K_SPACE }, { "BACKSPACE", K_BACKSPACE }, { "UPARROW", K_UPARROW }, { "DOWNARROW", K_DOWNARROW }, { "LEFTARROW", K_LEFTARROW }, { "RIGHTARROW", K_RIGHTARROW }, { "ALT", K_ALT }, { "CTRL", K_CTRL }, { "SHIFT", K_SHIFT }, { "F1", K_F1 }, { "F2", K_F2 }, { "F3", K_F3 }, { "F4", K_F4 }, { "F5", K_F5 }, { "F6", K_F6 }, { "F7", K_F7 }, { "F8", K_F8 }, { "F9", K_F9 }, { "F10", K_F10 }, { "F11", K_F11 }, { "F12", K_F12 }, { "INS", K_INS }, { "DEL", K_DEL }, { "PGDN", K_PGDN }, { "PGUP", K_PGUP }, { "HOME", K_HOME }, { "END", K_END }, { "MOUSE1", K_MOUSE1 }, { "MOUSE2", K_MOUSE2 }, { "MOUSE3", K_MOUSE3 }, { "MOUSE4", K_MOUSE4 }, { "MOUSE5", K_MOUSE5 }, { "AUX1", K_AUX1 }, { "AUX2", K_AUX2 }, { "AUX3", K_AUX3 }, { "AUX4", K_AUX4 }, { "AUX5", K_AUX5 }, { "AUX6", K_AUX6 }, { "AUX7", K_AUX7 }, { "AUX8", K_AUX8 }, { "AUX9", K_AUX9 }, { "AUX10", K_AUX10 }, { "AUX11", K_AUX11 }, { "AUX12", K_AUX12 }, { "AUX13", K_AUX13 }, { "AUX14", K_AUX14 }, { "AUX15", K_AUX15 }, { "AUX16", K_AUX16 }, { "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 }, { "KP_HOME", K_KP_HOME }, { "KP_UPARROW", K_KP_UPARROW }, { "KP_PGUP", K_KP_PGUP }, { "KP_LEFTARROW", K_KP_LEFTARROW }, { "KP_5", K_KP_5 }, { "KP_RIGHTARROW", K_KP_RIGHTARROW }, { "KP_END", K_KP_END }, { "KP_DOWNARROW", K_KP_DOWNARROW }, { "KP_PGDN", K_KP_PGDN }, { "KP_ENTER", K_KP_ENTER }, { "KP_INS", K_KP_INS }, { "KP_DEL", K_KP_DEL }, { "KP_SLASH", K_KP_SLASH }, { "KP_MINUS", K_KP_MINUS }, { "KP_PLUS", K_KP_PLUS }, { "MWHEELUP", K_MWHEELUP }, { "MWHEELDOWN", K_MWHEELDOWN }, { "PAUSE", K_PAUSE }, { "SEMICOLON", ';' }, /* because a raw semicolon seperates commands */ { NULL, 0 } }; void CompleteCommand ( void ) { char *cmd, *s; s = key_lines [ edit_line ] + 1; if ( ( *s == '\\' ) || ( *s == '/' ) ) { s++; } cmd = Cmd_CompleteCommand( s ); if ( cmd ) { key_lines [ edit_line ] [ 1 ] = '/'; strcpy( key_lines [ edit_line ] + 2, cmd ); key_linepos = strlen( cmd ) + 2; if ( Cmd_IsComplete( cmd ) ) { key_lines [ edit_line ] [ key_linepos ] = ' '; key_linepos++; key_lines [ edit_line ] [ key_linepos ] = 0; } else { key_lines [ edit_line ] [ key_linepos ] = 0; } return; } } /* * Interactive line editing and console scrollback */ void Key_Console ( int key ) { switch ( key ) { case K_KP_SLASH: key = '/'; break; case K_KP_MINUS: key = '-'; break; case K_KP_PLUS: key = '+'; break; case K_KP_HOME: key = '7'; break; case K_KP_UPARROW: key = '8'; break; case K_KP_PGUP: key = '9'; break; case K_KP_LEFTARROW: key = '4'; break; case K_KP_5: key = '5'; break; case K_KP_RIGHTARROW: key = '6'; break; case K_KP_END: key = '1'; break; case K_KP_DOWNARROW: key = '2'; break; case K_KP_PGDN: key = '3'; break; case K_KP_INS: key = '0'; break; case K_KP_DEL: key = '.'; break; default: break; } if ( key == 'l' ) { if ( keydown [ K_CTRL ] ) { Cbuf_AddText( "clear\n" ); return; } } if ( ( key == K_ENTER ) || ( key == K_KP_ENTER ) ) { /* slash text are commands, else chat */ if ( ( key_lines [ edit_line ] [ 1 ] == '\\' ) || ( key_lines [ edit_line ] [ 1 ] == '/' ) ) { Cbuf_AddText( key_lines [ edit_line ] + 2 ); /* skip the > */ } else { Cbuf_AddText( key_lines [ edit_line ] + 1 ); /* valid command */ } Cbuf_AddText( "\n" ); Com_Printf( "%s\n", key_lines [ edit_line ] ); edit_line = ( edit_line + 1 ) & 31; history_line = edit_line; key_lines [ edit_line ] [ 0 ] = ']'; key_linepos = 1; if ( cls.state == ca_disconnected ) { SCR_UpdateScreen(); /* force an update, because the command may take some time */ } return; } if ( key == K_TAB ) { /* command completion */ CompleteCommand(); return; } if ( ( key == K_BACKSPACE ) || ( key == K_LEFTARROW ) || ( key == K_KP_LEFTARROW ) || ( ( key == 'h' ) && ( keydown [ K_CTRL ] ) ) ) { if ( key_linepos > 1 ) { key_linepos--; } return; } if ( key == K_DEL ) { memmove( key_lines [ edit_line ] + key_linepos, key_lines [ edit_line ] + key_linepos + 1, sizeof ( key_lines [ edit_line ] ) - key_linepos - 1 ); return; } if ( ( key == K_UPARROW ) || ( key == K_KP_UPARROW ) || ( ( key == 'p' ) && keydown [ K_CTRL ] ) ) { do { history_line = ( history_line - 1 ) & 31; } while ( history_line != edit_line && !key_lines [ history_line ] [ 1 ] ); if ( history_line == edit_line ) { history_line = ( edit_line + 1 ) & 31; } strcpy( key_lines [ edit_line ], key_lines [ history_line ] ); key_linepos = (int) strlen( key_lines [ edit_line ] ); return; } if ( ( key == K_DOWNARROW ) || ( key == K_KP_DOWNARROW ) || ( ( key == 'n' ) && keydown [ K_CTRL ] ) ) { if ( history_line == edit_line ) { return; } do { history_line = ( history_line + 1 ) & 31; } while ( history_line != edit_line && !key_lines [ history_line ] [ 1 ] ); if ( history_line == edit_line ) { key_lines [ edit_line ] [ 0 ] = ']'; key_linepos = 1; } else { strcpy( key_lines [ edit_line ], key_lines [ history_line ] ); key_linepos = (int) strlen( key_lines [ edit_line ] ); } return; } if ( ( key == K_PGUP ) || ( key == K_KP_PGUP ) || ( key == K_MWHEELUP ) || ( key == K_MOUSE4 ) ) { con.display -= 2; return; } if ( ( key == K_PGDN ) || ( key == K_KP_PGDN ) || ( key == K_MWHEELDOWN ) || ( key == K_MOUSE5 ) ) { con.display += 2; if ( con.display > con.current ) { con.display = con.current; } return; } if ( ( key == K_HOME ) || ( key == K_KP_HOME ) ) { if ( keydown [ K_CTRL ] ) { con.display = con.current - con.totallines + 10; } else { key_linepos = 1; } return; } if ( ( key == K_END ) || ( key == K_KP_END ) ) { if ( keydown [ K_CTRL ] ) { con.display = con.current; } else { key_linepos = (int) strlen( key_lines [ edit_line ] ); } return; } if ( ( key < 32 ) || ( key > 127 ) ) { return; /* non printable character */ } if ( key_linepos < MAXCMDLINE - 1 ) { int last; int length; length = strlen( key_lines [ edit_line ] ); if ( length >= MAXCMDLINE - 1 ) { return; } last = key_lines [ edit_line ] [ key_linepos ]; memmove( key_lines [ edit_line ] + key_linepos + 1, key_lines [ edit_line ] + key_linepos, length - key_linepos ); key_lines [ edit_line ] [ key_linepos ] = key; key_linepos++; if ( !last ) { key_lines [ edit_line ] [ key_linepos ] = 0; } } } qboolean chat_team; char chat_buffer [ MAXCMDLINE ]; int chat_bufferlen = 0; int chat_cursorpos = 0; void Key_Message ( int key ) { char last; if ( ( key == K_ENTER ) || ( key == K_KP_ENTER ) ) { if ( chat_team ) { Cbuf_AddText( "say_team \"" ); } else { Cbuf_AddText( "say \"" ); } Cbuf_AddText( chat_buffer ); Cbuf_AddText( "\"\n" ); cls.key_dest = key_game; chat_bufferlen = 0; chat_buffer [ 0 ] = 0; chat_cursorpos = 0; return; } if ( key == K_ESCAPE ) { cls.key_dest = key_game; chat_cursorpos = 0; chat_bufferlen = 0; chat_buffer [ 0 ] = 0; return; } if ( key == K_BACKSPACE ) { if ( chat_cursorpos ) { memmove( chat_buffer + chat_cursorpos - 1, chat_buffer + chat_cursorpos, chat_bufferlen - chat_cursorpos + 1 ); chat_cursorpos--; chat_bufferlen--; } return; } if ( key == K_DEL ) { if ( chat_bufferlen && ( chat_cursorpos != chat_bufferlen ) ) { memmove( chat_buffer + chat_cursorpos, chat_buffer + chat_cursorpos + 1, chat_bufferlen - chat_cursorpos + 1 ); chat_bufferlen--; } return; } if ( key == K_LEFTARROW ) { if ( chat_cursorpos > 0 ) { chat_cursorpos--; } return; } if ( key == K_HOME ) { chat_cursorpos = 0; return; } if ( key == K_END ) { chat_cursorpos = chat_bufferlen; return; } if ( key == K_RIGHTARROW ) { if ( chat_buffer [ chat_cursorpos ] ) { chat_cursorpos++; } return; } if ( ( key < 32 ) || ( key > 127 ) ) { return; /* non printable charcter */ } if ( chat_bufferlen == sizeof ( chat_buffer ) - 1 ) { return; /* all full, this should never happen on modern systems */ } memmove( chat_buffer + chat_cursorpos + 1, chat_buffer + chat_cursorpos, chat_bufferlen - chat_cursorpos + 1 ); last = chat_buffer [ chat_cursorpos ]; chat_buffer [ chat_cursorpos ] = key; chat_bufferlen++; chat_cursorpos++; if ( !last ) { chat_buffer [ chat_cursorpos ] = 0; } } /* * Returns a key number to be used to index keybindings[] by looking at * the given string. Single ascii characters return themselves, while * the K_* names are matched up. */ int Key_StringToKeynum ( char *str ) { keyname_t *kn; if ( !str || !str [ 0 ] ) { return ( -1 ); } if ( !str [ 1 ] ) { return ( str [ 0 ] ); } for ( kn = keynames; kn->name; kn++ ) { if ( !Q_stricmp( str, kn->name ) ) { return ( kn->keynum ); } } return ( -1 ); } /* * Returns a string (either a single ascii char, or a K_* name) for the * given keynum. */ char * Key_KeynumToString ( int keynum ) { keyname_t *kn; static char tinystr [ 2 ] = { 0 }; if ( keynum == -1 ) { return ( "" ); } if ( ( keynum > 32 ) && ( keynum < 127 ) ) { /* printable ascii */ tinystr [ 0 ] = keynum; return ( tinystr ); } for ( kn = keynames; kn->name; kn++ ) { if ( keynum == kn->keynum ) { return ( kn->name ); } } return ( "" ); } void Key_SetBinding ( int keynum, char *binding ) { char *new; int l; if ( keynum == -1 ) { return; } /* free old bindings */ if ( keybindings [ keynum ] ) { Z_Free( keybindings [ keynum ] ); keybindings [ keynum ] = NULL; } /* allocate memory for new binding */ l = strlen( binding ); new = Z_Malloc( l + 1 ); strcpy( new, binding ); new [ l ] = 0; keybindings [ keynum ] = new; } void Key_Unbind_f ( void ) { int b; if ( Cmd_Argc() != 2 ) { Com_Printf( "unbind : remove commands from a key\n" ); return; } b = Key_StringToKeynum( Cmd_Argv( 1 ) ); if ( b == -1 ) { Com_Printf( "\"%s\" isn't a valid key\n", Cmd_Argv( 1 ) ); return; } Key_SetBinding( b, "" ); } void Key_Unbindall_f ( void ) { int i; for ( i = 0; i < K_LAST; i++ ) { if ( keybindings [ i ] ) { Key_SetBinding( i, "" ); } } } void Key_Bind_f ( void ) { int i, c, b; char cmd [ 1024 ]; c = Cmd_Argc(); if ( c < 2 ) { Com_Printf( "bind [command] : attach a command to a key\n" ); return; } b = Key_StringToKeynum( Cmd_Argv( 1 ) ); if ( b == -1 ) { Com_Printf( "\"%s\" isn't a valid key\n", Cmd_Argv( 1 ) ); return; } if ( c == 2 ) { if ( keybindings [ b ] ) { Com_Printf( "\"%s\" = \"%s\"\n", Cmd_Argv( 1 ), keybindings [ b ] ); } else { Com_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++ ) { strcat( cmd, Cmd_Argv( i ) ); if ( i != ( c - 1 ) ) { strcat( cmd, " " ); } } Key_SetBinding( b, cmd ); } /* * Writes lines containing "bind key value" */ void Key_WriteBindings ( FILE *f ) { int i; for ( i = 0; i < K_LAST; i++ ) { if ( keybindings [ i ] && keybindings [ i ] [ 0 ] ) { fprintf( f, "bind %s \"%s\"\n", Key_KeynumToString( i ), keybindings [ i ] ); } } } void Key_Bindlist_f ( void ) { int i; for ( i = 0; i < K_LAST; i++ ) { if ( keybindings [ i ] && keybindings [ i ] [ 0 ] ) { Com_Printf( "%s \"%s\"\n", Key_KeynumToString( i ), keybindings [ i ] ); } } } void Key_Init ( void ) { int i; for ( i = 0; i < 32; i++ ) { key_lines [ i ] [ 0 ] = ']'; key_lines [ i ] [ 1 ] = 0; } key_linepos = 1; /* init 128 bit ascii characters in console mode */ for ( i = 32; i < 128; i++ ) { consolekeys [ i ] = true; } consolekeys [ K_ENTER ] = true; consolekeys [ K_KP_ENTER ] = true; consolekeys [ K_TAB ] = true; consolekeys [ K_LEFTARROW ] = true; consolekeys [ K_KP_LEFTARROW ] = true; consolekeys [ K_RIGHTARROW ] = true; consolekeys [ K_KP_RIGHTARROW ] = true; consolekeys [ K_UPARROW ] = true; consolekeys [ K_KP_UPARROW ] = true; consolekeys [ K_DOWNARROW ] = true; consolekeys [ K_KP_DOWNARROW ] = true; consolekeys [ K_BACKSPACE ] = true; consolekeys [ K_HOME ] = true; consolekeys [ K_KP_HOME ] = true; consolekeys [ K_END ] = true; consolekeys [ K_KP_END ] = true; consolekeys [ K_PGUP ] = true; consolekeys [ K_KP_PGUP ] = true; consolekeys [ K_PGDN ] = true; consolekeys [ K_KP_PGDN ] = true; consolekeys [ K_SHIFT ] = true; consolekeys [ K_INS ] = true; consolekeys [ K_KP_INS ] = true; consolekeys [ K_KP_DEL ] = true; consolekeys [ K_KP_SLASH ] = true; consolekeys [ K_KP_PLUS ] = true; consolekeys [ K_KP_MINUS ] = true; consolekeys [ K_KP_5 ] = true; consolekeys [ '`' ] = false; consolekeys [ '~' ] = false; consolekeys [ '^' ] = false; for ( i = 0; i < K_LAST; i++ ) { keyshift [ i ] = i; } for ( i = 'a'; i <= 'z'; i++ ) { keyshift [ i ] = i - 'a' + 'A'; } keyshift [ '1' ] = '!'; keyshift [ '2' ] = '@'; keyshift [ '3' ] = '#'; keyshift [ '4' ] = '$'; keyshift [ '5' ] = '%'; keyshift [ '6' ] = '^'; keyshift [ '7' ] = '&'; keyshift [ '8' ] = '*'; keyshift [ '9' ] = '('; keyshift [ '0' ] = ')'; keyshift [ '-' ] = '_'; keyshift [ '=' ] = '+'; keyshift [ ',' ] = '<'; keyshift [ '.' ] = '>'; keyshift [ '/' ] = '?'; keyshift [ ';' ] = ':'; keyshift [ '\'' ] = '"'; keyshift [ '[' ] = '{'; keyshift [ ']' ] = '}'; keyshift [ '`' ] = '~'; keyshift [ '\\' ] = '|'; menubound [ K_ESCAPE ] = true; for ( i = 0; i < 12; i++ ) { menubound [ K_F1 + i ] = true; } /* register our functions */ Cmd_AddCommand( "bind", Key_Bind_f ); Cmd_AddCommand( "unbind", Key_Unbind_f ); Cmd_AddCommand( "unbindall", Key_Unbindall_f ); Cmd_AddCommand( "bindlist", Key_Bindlist_f ); } /* * Called by the system between frames for both key up and key down events * Should NOT be called during an interrupt! */ void Key_Event ( int key, qboolean down, unsigned time ) { char *kb; char cmd [ 1024 ]; /* hack for modal presses */ if ( key_waiting == -1 ) { if ( down ) { key_waiting = key; } return; } /* update auto-repeat status */ if ( down ) { key_repeats [ key ]++; if ( ( key != K_BACKSPACE ) && ( key != K_PAUSE ) && ( key != K_PGUP ) && ( key != K_KP_PGUP ) && ( key != K_PGDN ) && ( key != K_KP_PGDN ) && ( key_repeats [ key ] > 1 ) ) { return; /* ignore most autorepeats */ } if ( ( key >= 200 ) && !keybindings [ key ] && ( cls.key_dest != key_console ) ) { Com_Printf( "%s is unbound, hit F4 to set.\n", Key_KeynumToString( key ) ); } } else { key_repeats [ key ] = 0; } if ( key == K_SHIFT ) { shift_down = down; } /* console key is hardcoded, so the user can never unbind it */ if ( ( key == '^' ) || ( key == '~' ) || ( key == '`' ) ) { if ( !down ) { return; } Con_ToggleConsole_f(); return; } /* any key during the attract mode will bring up the menu */ if ( cl.attractloop && ( cls.key_dest != key_menu ) && !( ( key >= K_F1 ) && ( key <= K_F12 ) ) ) { key = K_ESCAPE; } /* menu key is hardcoded, so the user can never unbind it */ if ( key == K_ESCAPE ) { if ( !down ) { return; } if ( cl.frame.playerstate.stats [ STAT_LAYOUTS ] && ( cls.key_dest == key_game ) ) { /* put away help computer / inventory */ Cbuf_AddText( "cmd putaway\n" ); return; } switch ( cls.key_dest ) { case key_message: Key_Message( key ); break; case key_menu: M_Keydown( key ); break; case key_game: case key_console: M_Menu_Main_f(); break; default: Com_Error( ERR_FATAL, "Bad cls.key_dest" ); } return; } /* track if any key is down for BUTTON_ANY */ keydown [ key ] = down; if ( down ) { if ( key_repeats [ key ] == 1 ) { anykeydown++; } } else { anykeydown--; if ( anykeydown < 0 ) { anykeydown = 0; } } /* 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 */ if ( !down ) { kb = keybindings [ key ]; if ( kb && ( kb [ 0 ] == '+' ) ) { Com_sprintf( cmd, sizeof ( cmd ), "-%s %i %i\n", kb + 1, key, time ); Cbuf_AddText( cmd ); } if ( keyshift [ key ] != key ) { kb = keybindings [ keyshift [ key ] ]; if ( kb && ( kb [ 0 ] == '+' ) ) { Com_sprintf( cmd, sizeof ( cmd ), "-%s %i %i\n", kb + 1, key, time ); Cbuf_AddText( cmd ); } } return; } /* if not a consolekey, send to the interpreter no matter what mode is */ if ( ( ( cls.key_dest == key_menu ) && menubound [ key ] ) || ( ( cls.key_dest == key_console ) && !consolekeys [ key ] ) || ( ( cls.key_dest == key_game ) && ( ( cls.state == ca_active ) || !consolekeys [ key ] ) ) ) { kb = keybindings [ key ]; if ( kb ) { if ( kb [ 0 ] == '+' ) { /* button commands add keynum and time as a parm */ Com_sprintf( cmd, sizeof ( cmd ), "%s %i %i\n", kb, key, time ); Cbuf_AddText( cmd ); } else { Cbuf_AddText( kb ); Cbuf_AddText( "\n" ); } } return; } if ( !down ) { return; /* other systems only care about key down events */ } if ( shift_down ) { key = keyshift [ key ]; } switch ( cls.key_dest ) { case key_message: Key_Message( key ); break; case key_menu: M_Keydown( key ); break; case key_game: case key_console: Key_Console( key ); break; default: Com_Error( ERR_FATAL, "Bad cls.key_dest" ); } } void Key_ClearStates ( void ) { int i; anykeydown = false; for ( i = 0; i < K_LAST; i++ ) { if ( keydown [ i ] || key_repeats [ i ] ) { Key_Event( i, false, 0 ); } keydown [ i ] = 0; key_repeats [ i ] = 0; } } int Key_GetKey ( void ) { key_waiting = -1; while ( key_waiting == -1 ) { Sys_SendKeyEvents(); } return ( key_waiting ); }