newtree/source/keys.c
Bill Currie eae11661e4 strncat parameter audit. Unfortunatly, strncat is counter-intutite: the n in
strncat is not the maximum length of the destination string, but of the SOURCE
string, thus strncat (dest, src, sizeof (dest)) is incorrect. It should be
strncat (dest, src, sizeof (text) - strlen (dest)). Even then, no terminating
nul will be written if src is too long, but at least it won't crash the stack:)
2000-12-05 16:04:12 +00:00

947 lines
18 KiB
C

/*
keys.c
(description)
Copyright (C) 1996-1997 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:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
$Id$
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#ifdef _WIN32
#include <windows.h>
#endif
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif
#include "qtypes.h"
#include "sys.h"
#include "keys.h"
#include "menu.h"
#include "cmd.h"
#include "console.h"
#include "cvar.h"
#include "screen.h"
#include "client.h"
/*
key up events are sent even if in console mode
*/
cvar_t *cl_chatmode;
#define MAXCMDLINE 256
char key_lines[32][MAXCMDLINE];
int key_linepos;
int key_lastpress;
int edit_line=0;
int history_line=0;
keydest_t key_dest;
char *keybindings[256];
qboolean consolekeys[256]; // if true, can't be rebound while in console
qboolean menubound[256]; // if true, can't be rebound while in menu
int keyshift[256]; // key to map to if shift held down in console
int key_repeats[256]; // if > 1, it is autorepeating
qboolean keydown[256];
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},
{"CAPSLOCK",K_CAPSLOCK},
{"PRINTSCR", K_PRNTSCR},
{"SCRLCK", K_SCRLCK},
{"PAUSE", K_PAUSE},
{"UPARROW", K_UPARROW},
{"DOWNARROW", K_DOWNARROW},
{"LEFTARROW", K_LEFTARROW},
{"RIGHTARROW", K_RIGHTARROW},
{"ALT", K_ALT},
{"CTRL", K_CTRL},
{"SHIFT", K_SHIFT},
// Keypad stuff..
// These are duplicated
{"NUMLOCK", KP_NUMLCK},
{"KP_NUMLCK", KP_NUMLCK},
{"KP_NUMLOCK", KP_NUMLCK},
{"KP_SLASH", KP_DIVIDE},
{"KP_DIVIDE", KP_DIVIDE},
{"KP_STAR", KP_MULTIPLY},
{"KP_MULTIPLY", KP_MULTIPLY},
{"KP_MINUS", KP_MINUS},
{"KP_HOME", KP_HOME},
{"KP_UPARROW", KP_UPARROW},
{"KP_PGUP", KP_PGUP},
{"KP_PLUS", KP_PLUS},
{"KP_LEFTARROW", KP_LEFTARROW},
{"KP_5", KP_5},
{"KP_RIGHTARROW", KP_RIGHTARROW},
{"KP_END", KP_END},
{"KP_DOWNARROW", KP_DOWNARROW},
{"KP_PGDN", KP_PGDN},
{"KP_INS", KP_INS},
{"KP_DEL", KP_DEL},
{"KP_ENTER", KP_ENTER},
{"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},
{"JOY1", K_JOY1},
{"JOY2", K_JOY2},
{"JOY3", K_JOY3},
{"JOY4", K_JOY4},
{"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},
{"MWHEELUP", K_MWHEELUP},
{"MWHEELDOWN", K_MWHEELDOWN},
{"SEMICOLON", ';'}, // because a raw semicolon seperates commands
{NULL,0}
};
/*
==============================================================================
LINE TYPING INTO THE CONSOLE
==============================================================================
*/
qboolean
CheckForCommand ( void )
{
char command[128];
char *cmd, *s;
int i;
s = key_lines[edit_line]+1;
for (i=0 ; i<127 ; i++)
if (s[i] <= ' ')
break;
else
command[i] = s[i];
command[i] = 0;
cmd = Cmd_CompleteCommand (command);
if (!cmd || strcmp (cmd, command))
cmd = Cvar_CompleteVariable (command);
if (!cmd || strcmp (cmd, command) )
return false; // just a chat message
return true;
}
void
CompleteCommand ( void )
{
char *cmd, *s;
s = key_lines[edit_line]+1;
if (*s == '\\' || *s == '/')
s++;
cmd = Cmd_CompleteCommand (s);
if (!cmd)
cmd = Cvar_CompleteVariable (s);
if (cmd)
{
key_lines[edit_line][1] = '/';
strcpy (key_lines[edit_line]+2, cmd);
key_linepos = strlen(cmd)+2;
key_lines[edit_line][key_linepos] = ' ';
key_linepos++;
key_lines[edit_line][key_linepos] = 0;
return;
}
}
/*
====================
Key_Console
Interactive line editing and console scrollback
====================
*/
void
Key_Console ( int key )
{
int i;
#ifdef _WIN32
HANDLE th;
char *clipText, *textCopied;
#endif
switch (key)
{
case K_ENTER:
// backslash text are commands
if (key_lines[edit_line][1] == '/' && key_lines[edit_line][2] == '/')
goto no_lf;
else if (key_lines[edit_line][1] == '\\' || key_lines[edit_line][1] == '/')
Cbuf_AddText (key_lines[edit_line]+2); // skip the ]/
else if (cl_chatmode->int_val != 1 && CheckForCommand())
Cbuf_AddText (key_lines[edit_line]+1); // valid command
else if ((cls.state >= ca_connected && cl_chatmode->int_val == 2) || cl_chatmode->int_val == 1)
{
if (cls.state < ca_connected) // can happen if cl_chatmode is 1
goto no_lf; // the text goes to /dev/null :)
// convert to a chat message
Cbuf_AddText ("say ");
Cbuf_AddText (key_lines[edit_line]+1);
}
else
Cbuf_AddText (key_lines[edit_line]+1); // skip the ]
Cbuf_AddText ("\n");
no_lf:
Con_Printf ("%s\n",key_lines[edit_line]);
edit_line = (edit_line + 1) & 31;
history_line = edit_line;
key_lines[edit_line][0] = ']';
key_lines[edit_line][1] = 0;
key_linepos = 1;
if (cls.state == ca_disconnected)
SCR_UpdateScreen (); // force an update, because the command
// may take some time
return;
case K_TAB:
// command completion
CompleteCommand ();
return;
case K_BACKSPACE:
if (key_linepos > 1)
{
strcpy(key_lines[edit_line] + key_linepos - 1, key_lines[edit_line] + key_linepos);
key_linepos--;
}
return;
case K_DEL:
if (key_linepos < strlen(key_lines[edit_line]))
strcpy(key_lines[edit_line] + key_linepos, key_lines[edit_line] + key_linepos + 1);
return;
case K_RIGHTARROW:
if (key_linepos < strlen(key_lines[edit_line]))
key_linepos++;
return;
case K_LEFTARROW:
if (key_linepos > 1)
key_linepos--;
return;
case K_UPARROW:
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 = strlen(key_lines[edit_line]);
return;
case K_DOWNARROW:
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_lines[edit_line][1] = 0;
key_linepos = 1;
} else {
strcpy(key_lines[edit_line], key_lines[history_line]);
key_linepos = strlen(key_lines[edit_line]);
}
return;
case K_MWHEELUP:
case K_PGUP:
if (con->display - con->current + con->numlines > 2)
con->display -= 2;
return;
case K_MWHEELDOWN:
case K_PGDN:
con->display += 2;
if (con->display > con->current)
con->display = con->current;
return;
case K_HOME:
if (keydown[K_CTRL])
{
if (con->numlines > 10)
con->display = con->current - con->numlines + 10;
}
else
key_linepos = 1;
return;
case K_END:
if (keydown[K_CTRL])
con->display = con->current;
else
key_linepos = strlen(key_lines[edit_line]);
return;
}
#ifdef _WIN32
if ((key=='V' || key=='v') && GetKeyState(VK_CONTROL)<0) {
if (OpenClipboard(NULL)) {
th = GetClipboardData(CF_TEXT);
if (th) {
clipText = GlobalLock(th);
if (clipText) {
textCopied = malloc(GlobalSize(th)+1);
strcpy(textCopied, clipText);
/* Substitutes a NULL for every token */
strtok(textCopied, "\n\r\b");
i = strlen(textCopied);
if (i + strlen(key_lines[edit_line]) > MAXCMDLINE-1)
i = MAXCMDLINE-1 - strlen(key_lines[edit_line]);
if (i > 0)
{ // insert the string
memmove (key_lines[edit_line] + key_linepos + i,
key_lines[edit_line] + key_linepos, strlen(key_lines[edit_line]) - key_linepos + 1);
memcpy (key_lines[edit_line] + key_linepos, textCopied, i);
key_linepos += i;
}
free(textCopied);
}
GlobalUnlock(th);
}
CloseClipboard();
return;
}
}
#endif
if (key < 32 || key > 127)
return; // non printable
i = strlen(key_lines[edit_line]);
if (i >= MAXCMDLINE-1)
return;
// This also moves the ending \0
memmove (key_lines[edit_line]+key_linepos+1, key_lines[edit_line]+key_linepos, i-key_linepos+1);
key_lines[edit_line][key_linepos] = key;
key_linepos++;
}
//============================================================================
qboolean chat_team;
char chat_buffer[MAXCMDLINE];
int chat_bufferlen = 0;
void
Key_Message ( int key )
{
if (key == K_ENTER)
{
if (chat_team)
Cbuf_AddText ("say_team \"");
else
Cbuf_AddText ("say \"");
Cbuf_AddText(chat_buffer);
Cbuf_AddText("\"\n");
key_dest = key_game;
chat_bufferlen = 0;
chat_buffer[0] = 0;
return;
}
if (key == K_ESCAPE)
{
key_dest = key_game;
chat_bufferlen = 0;
chat_buffer[0] = 0;
return;
}
if (key < 32 || key > 127)
return; // non printable
if (key == K_BACKSPACE)
{
if (chat_bufferlen)
{
chat_bufferlen--;
chat_buffer[chat_bufferlen] = 0;
}
return;
}
if (chat_bufferlen == sizeof(chat_buffer)-1)
return; // all full
chat_buffer[chat_bufferlen++] = key;
chat_buffer[chat_bufferlen] = 0;
}
//============================================================================
/*
===================
Key_StringToKeynum
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 (!strcasecmp(str,kn->name))
return kn->keynum;
}
return -1;
}
/*
===================
Key_KeynumToString
Returns a string (either a single ascii char, or a K_* name) for the
given keynum.
FIXME: handle quote special (general escape sequence?)
===================
*/
char *
Key_KeynumToString ( int keynum )
{
keyname_t *kn;
static char tinystr[2];
if (keynum == -1)
return "<KEY NOT FOUND>";
if (keynum > 32 && keynum < 127)
{ // printable ascii
tinystr[0] = keynum;
tinystr[1] = 0;
return tinystr;
}
for (kn=keynames ; kn->name ; kn++)
if (keynum == kn->keynum)
return kn->name;
return "<UNKNOWN KEYNUM>";
}
/*
===================
Key_SetBinding
===================
*/
void
Key_SetBinding ( int keynum, char *binding )
{
char *new;
int l;
if (keynum == -1)
return;
// free old bindings
if (keybindings[keynum])
{
free (keybindings[keynum]);
keybindings[keynum] = NULL;
}
// allocate memory for new binding
l = strlen (binding);
new = malloc (l+1);
strcpy (new, binding);
new[l] = 0;
keybindings[keynum] = new;
}
/*
===================
Key_Unbind_f
===================
*/
void
Key_Unbind_f ( void )
{
int b;
if (Cmd_Argc() != 2)
{
Con_Printf ("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, "");
}
void
Key_Unbindall_f ( void )
{
int i;
for (i=0 ; i<256 ; i++)
if (keybindings[i])
Key_SetBinding (i, "");
}
/*
===================
Key_Bind_f
===================
*/
void
Key_Bind_f ( void )
{
int i, c, b;
char cmd[1024];
c = Cmd_Argc();
if (c < 2)
{
Con_Printf ("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 (keybindings[b])
Con_Printf ("\"%s\" = \"%s\"\n", Cmd_Argv(1), keybindings[b] );
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++)
{
strncat (cmd, Cmd_Argv(i), sizeof(cmd) - strlen (cmd));
if (i != (c-1))
strncat (cmd, " ", sizeof(cmd) - strlen (cmd));
}
Key_SetBinding (b, cmd);
}
/*
============
Key_WriteBindings
Writes lines containing "bind key value"
============
*/
void
Key_WriteBindings ( QFile *f )
{
int i;
for (i=0 ; i<256 ; i++)
if (keybindings[i])
Qprintf (f, "bind \"%s\" \"%s\"\n", Key_KeynumToString(i), keybindings[i]); // 1999-12-26 bound keys not saved in quotes fix by Maddes
}
/*
===================
Key_Init
===================
*/
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 ascii characters in console mode
//
for (i=32 ; i<128 ; i++)
consolekeys[i] = true;
consolekeys[K_ENTER] = true;
consolekeys[K_TAB] = true;
consolekeys[K_LEFTARROW] = true;
consolekeys[K_RIGHTARROW] = true;
consolekeys[K_UPARROW] = true;
consolekeys[K_DOWNARROW] = true;
consolekeys[K_BACKSPACE] = true;
consolekeys[K_DEL] = true;
consolekeys[K_HOME] = true;
consolekeys[K_END] = true;
consolekeys[K_PGUP] = true;
consolekeys[K_PGDN] = true;
consolekeys[K_SHIFT] = true;
consolekeys[K_MWHEELUP] = true;
consolekeys[K_MWHEELDOWN] = true;
consolekeys['`'] = false;
consolekeys['~'] = false;
for (i=0 ; i<256 ; 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);
}
void
Key_Init_Cvars ( void )
{
cl_chatmode = Cvar_Get ("cl_chatmode", "2", 0,
"Controls when console text will be treated as a chat message\n"
"0 - never, 1 - always, 2 - smart");
}
/*
===================
Key_Event
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, int alt_key, qboolean down )
{
char *kb;
char cmd[1024];
// Con_Printf ("%i : %i\n", key, down); //@@@
// They don't prove it, fall back to interal MESS.
if (alt_key == -1) {
if (keydown[K_SHIFT]) {
alt_key = keyshift[key];
}
}
keydown[key] = down;
if (!down)
key_repeats[key] = 0;
key_lastpress = key;
// update auto-repeat status
if (down)
{
key_repeats[key]++;
if (key_repeats[key] > 1)
{
if ((key != K_BACKSPACE && key != K_DEL
&& key != K_LEFTARROW && key != K_RIGHTARROW
&& key != K_PGUP && key != K_PGDN)
|| (key_dest == key_game && cls.state == ca_active))
return; // ignore most autorepeats
}
if (key >= 200 && !keybindings[key])
Con_Printf ("%s is unbound, hit F4 to set.\n", Key_KeynumToString (key) );
}
// Exit message mode is disconnected
if (key_dest == key_message && cls.state != ca_active)
key_dest = key_console;
//
// handle escape specialy, so the user can never unbind it
//
if (key == K_ESCAPE)
{
if (!down)
return;
switch (key_dest)
{
case key_message:
Key_Message (key);
break;
case key_menu:
M_Keydown (key);
break;
case key_game:
case key_console:
M_ToggleMenu_f ();
break;
default:
Sys_Error ("Bad key_dest");
}
return;
}
//
// 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] == '+') {
snprintf (cmd, sizeof(cmd), "-%s %i\n", kb+1, key);
Cbuf_AddText (cmd);
}
if (islower(key)) {
kb = keybindings[toupper(key)];
if (kb && kb[0] == '+') {
snprintf (cmd, sizeof(cmd), "-%s %i\n", kb+1, key);
Cbuf_AddText (cmd);
}
}
return;
}
//
// during demo playback, most keys bring up the main menu
//
if (cls.demoplayback && down && consolekeys[key] && key_dest == key_game
&& key != K_CTRL && key != K_DEL && key != K_HOME && key != K_END && key != K_TAB)
{
M_ToggleMenu_f ();
return;
}
//
// if not a consolekey, send to the interpreter no matter what mode is
//
if ( (key_dest == key_menu && menubound[key])
|| (key_dest == key_console && !consolekeys[key])
|| (key_dest == key_game && ( cls.state == ca_active || !consolekeys[key] ) ) )
{
kb = keybindings[key];
if (kb)
{
if (kb[0] == '+')
{ // button commands add keynum as a parm
snprintf (cmd, sizeof(cmd), "%s %i\n", kb, key);
Cbuf_AddText (cmd);
}
else
{
Cbuf_AddText (kb);
Cbuf_AddText ("\n");
}
}
return;
}
if (!down)
return; // other systems only care about key down events
if (alt_key > 0)
key = alt_key;
switch (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:
Sys_Error ("Bad key_dest");
}
}
/*
===================
Key_ClearStates
===================
*/
void
Key_ClearStates ( void )
{
int i;
for (i=0 ; i<256 ; i++)
{
keydown[i] = false;
key_repeats[i] = false;
}
}