667e8dec10
bind command now makes it a bit easier to edit the binding. and any alias. autoid tweaks. slightly better q1qvm/ktx support. extend the api a little. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@4927 fc73d0e0-1445-4013-8a0c-d673dee63da5
2433 lines
57 KiB
C
2433 lines
57 KiB
C
/*
|
|
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 the Free Software
|
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
*/
|
|
#include "quakedef.h"
|
|
#ifdef _WIN32
|
|
#include "winquake.h"
|
|
#endif
|
|
/*
|
|
|
|
key up events are sent even if in console mode
|
|
|
|
*/
|
|
void Editor_Key(int key, int unicode);
|
|
void Key_ConsoleInsert(char *instext);
|
|
void Key_ClearTyping (void);
|
|
|
|
unsigned char *key_lines[CON_EDIT_LINES_MASK+1];
|
|
int key_linepos;
|
|
int shift_down=false;
|
|
int key_lastpress;
|
|
|
|
int edit_line=0;
|
|
int history_line=0;
|
|
|
|
unsigned int key_dest_mask;
|
|
qboolean key_dest_console;
|
|
unsigned int key_dest_absolutemouse;
|
|
|
|
struct key_cursor_s key_customcursor[kc_max];
|
|
|
|
int key_count; // incremented every key event
|
|
|
|
char *keybindings[K_MAX][KEY_MODIFIERSTATES];
|
|
qbyte bindcmdlevel[K_MAX][KEY_MODIFIERSTATES];
|
|
qboolean consolekeys[K_MAX]; // if true, can't be rebound while in console
|
|
qboolean menubound[K_MAX]; // if true, can't be rebound while in menu
|
|
int keyshift[K_MAX]; // key to map to if shift held down in console
|
|
int key_repeats[K_MAX]; // if > 1, it is autorepeating
|
|
qboolean keydown[K_MAX];
|
|
|
|
#define MAX_INDEVS 8
|
|
|
|
char *releasecommand[K_MAX][MAX_INDEVS]; //this is the console command to be invoked when the key is released. should free it.
|
|
qbyte releasecommandlevel[K_MAX][MAX_INDEVS]; //and this is the cbuf level it is to be run at.
|
|
|
|
static void QDECL Con_Selectioncolour_Callback(struct cvar_s *var, char *oldvalue);
|
|
|
|
extern cvar_t con_displaypossibilities;
|
|
cvar_t con_selectioncolour = CVARFC("con_selectioncolour", "0", CVAR_RENDERERCALLBACK, Con_Selectioncolour_Callback);
|
|
cvar_t con_echochat = CVAR("con_echochat", "0");
|
|
extern cvar_t cl_chatmode;
|
|
|
|
static int KeyModifier (qboolean shift, qboolean alt, qboolean ctrl)
|
|
{
|
|
int stateset = 0;
|
|
if (shift)
|
|
stateset |= 1;
|
|
if (alt)
|
|
stateset |= 2;
|
|
if (ctrl)
|
|
stateset |= 4;
|
|
|
|
return stateset;
|
|
}
|
|
|
|
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},
|
|
|
|
{"LALT", K_LALT},
|
|
{"RALT", K_RALT},
|
|
{"LCTRL", K_LCTRL},
|
|
{"RCTRL", K_RCTRL},
|
|
{"LSHIFT", K_LSHIFT},
|
|
{"RSHIFT", K_RSHIFT},
|
|
{"ALT", K_LALT}, //depricated name
|
|
{"CTRL", K_CTRL}, //depricated name
|
|
{"SHIFT", K_SHIFT}, //depricated name
|
|
|
|
{"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},
|
|
|
|
|
|
{"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},
|
|
{"KP_NUMLOCK", K_KP_NUMLOCK},
|
|
{"KP_STAR", K_KP_STAR},
|
|
{"KP_MULTIPLY", K_KP_STAR},
|
|
{"KP_EQUALS", K_KP_EQUALS},
|
|
|
|
//fuhquake compatible.
|
|
{"KP_0", K_KP_INS},
|
|
{"KP_1", K_KP_END},
|
|
{"KP_2", K_KP_DOWNARROW},
|
|
{"KP_3", K_KP_PGDN},
|
|
{"KP_4", K_KP_LEFTARROW},
|
|
{"KP_6", K_KP_RIGHTARROW},
|
|
{"KP_7", K_KP_HOME},
|
|
{"KP_8", K_KP_UPARROW},
|
|
{"KP_9", K_KP_PGUP},
|
|
|
|
{"MOUSE1", K_MOUSE1},
|
|
{"MOUSE2", K_MOUSE2},
|
|
{"MOUSE3", K_MOUSE3},
|
|
{"MOUSE4", K_MOUSE4},
|
|
{"MOUSE5", K_MOUSE5},
|
|
{"MOUSE6", K_MOUSE6},
|
|
{"MOUSE7", K_MOUSE7},
|
|
{"MOUSE8", K_MOUSE8},
|
|
{"MOUSE9", K_MOUSE9},
|
|
{"MOUSE10", K_MOUSE10},
|
|
|
|
{"LWIN", K_LWIN},
|
|
{"RWIN", K_RWIN},
|
|
{"APP", K_APP},
|
|
{"MENU", K_APP},
|
|
{"SEARCH", K_SEARCH},
|
|
{"POWER", K_POWER},
|
|
{"VOLUP", K_VOLUP},
|
|
{"VOLDOWN", K_VOLDOWN},
|
|
|
|
{"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},
|
|
|
|
{"PAUSE", K_PAUSE},
|
|
|
|
{"MWHEELUP", K_MWHEELUP},
|
|
{"MWHEELDOWN", K_MWHEELDOWN},
|
|
|
|
{"PRINTSCREEN", K_PRINTSCREEN},
|
|
{"CAPSLOCK", K_CAPSLOCK},
|
|
{"SCROLLLOCK", K_SCRLCK},
|
|
|
|
{"SEMICOLON", ';'}, // because a raw semicolon seperates commands
|
|
|
|
{"TILDE", '~'},
|
|
{"BACKQUOTE", '`'},
|
|
{"BACKSLASH", '\\'},
|
|
|
|
{NULL,0}
|
|
};
|
|
|
|
#if defined(CSQC_DAT) || defined(MENU_DAT)
|
|
int MP_TranslateFTEtoQCCodes(int code);
|
|
void Key_PrintQCDefines(vfsfile_t *f)
|
|
{
|
|
int i, j;
|
|
for (i = 0; keynames[i].name; i++)
|
|
{
|
|
for (j = 0; j < i; j++)
|
|
if (keynames[j].keynum == keynames[i].keynum)
|
|
break;
|
|
if (j == i)
|
|
VFS_PRINTF(f, "#define K_%s\t%i\n", keynames[i].name, MP_TranslateFTEtoQCCodes(keynames[j].keynum));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
==============================================================================
|
|
|
|
LINE TYPING INTO THE CONSOLE
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
qboolean Cmd_IsCommand (char *line)
|
|
{
|
|
char command[128];
|
|
char *cmd, *s;
|
|
int i;
|
|
|
|
s = line;
|
|
|
|
for (i=0 ; i<127 ; i++)
|
|
if (s[i] <= ' ' || s[i] == ';')
|
|
break;
|
|
else
|
|
command[i] = s[i];
|
|
command[i] = 0;
|
|
|
|
cmd = Cmd_CompleteCommand (command, true, false, -1, NULL);
|
|
if (!cmd || strcmp (cmd, command) )
|
|
return false; // just a chat message
|
|
return true;
|
|
}
|
|
|
|
#define COLUMNWIDTH 20
|
|
#define MINCOLUMNWIDTH 18
|
|
|
|
int PaddedPrint (char *s, int x)
|
|
{
|
|
Con_Printf ("^4%s\t", s);
|
|
x+=strlen(s);
|
|
|
|
return x;
|
|
}
|
|
|
|
int con_commandmatch;
|
|
void CompleteCommand (qboolean force)
|
|
{
|
|
char *cmd, *s;
|
|
char *desc;
|
|
|
|
s = key_lines[edit_line]+1;
|
|
if (*s == '\\' || *s == '/')
|
|
s++;
|
|
|
|
for (cmd = s; *cmd; cmd++)
|
|
{
|
|
if (*cmd == ' ' || *cmd == '\t')
|
|
break;
|
|
}
|
|
if (*cmd)
|
|
cmd = s;
|
|
else
|
|
{
|
|
//check for singular matches and complete if found
|
|
cmd = Cmd_CompleteCommand (s, true, true, 2, NULL);
|
|
if (!cmd || force)
|
|
{
|
|
if (!force)
|
|
cmd = Cmd_CompleteCommand (s, false, true, 1, &desc);
|
|
else
|
|
cmd = Cmd_CompleteCommand (s, true, true, con_commandmatch, &desc);
|
|
if (cmd)
|
|
{
|
|
//complete to that (maybe partial) cmd.
|
|
Key_ClearTyping();
|
|
Key_ConsoleInsert("/");
|
|
Key_ConsoleInsert(cmd);
|
|
s = key_lines[edit_line]+2;
|
|
|
|
//if its the only match, add a space ready for arguments.
|
|
cmd = Cmd_CompleteCommand (s, true, true, 0, NULL);
|
|
if (cmd && !strcmp(s, cmd))
|
|
{
|
|
Key_ConsoleInsert(" ");
|
|
}
|
|
|
|
if (!con_commandmatch)
|
|
con_commandmatch = 1;
|
|
|
|
if (desc)
|
|
Con_Footerf(NULL, false, "%s: %s", cmd, desc);
|
|
else
|
|
Con_Footerf(NULL, false, "");
|
|
return;
|
|
}
|
|
}
|
|
//complete to a partial match.
|
|
cmd = Cmd_CompleteCommand (s, false, true, 0, &desc);
|
|
if (cmd)
|
|
{
|
|
int i = key_lines[edit_line][1] == '/'?2:1;
|
|
if (i != 2 || strcmp(key_lines[edit_line]+i, cmd))
|
|
{ //if successful, use that instead.
|
|
Key_ClearTyping();
|
|
Key_ConsoleInsert("/");
|
|
Key_ConsoleInsert(cmd);
|
|
|
|
s = key_lines[edit_line]+1; //readjust to cope with the insertion of a /
|
|
if (*s == '\\' || *s == '/')
|
|
s++;
|
|
}
|
|
}
|
|
}
|
|
con_commandmatch++;
|
|
cmd = Cmd_CompleteCommand(s, true, true, con_commandmatch, &desc);
|
|
if (!cmd)
|
|
{
|
|
con_commandmatch = 1;
|
|
cmd = Cmd_CompleteCommand(s, true, true, con_commandmatch, &desc);
|
|
}
|
|
if (cmd)
|
|
{
|
|
cvar_t *var = Cvar_FindVar(cmd);
|
|
if (var)
|
|
{
|
|
if (desc)
|
|
Con_Footerf(NULL, false, "%s %s\n%s", cmd, var->string, desc);
|
|
else
|
|
Con_Footerf(NULL, false, "%s %s", cmd, var->string);
|
|
}
|
|
else
|
|
{
|
|
if (desc)
|
|
Con_Footerf(NULL, false, "%s: %s", cmd, desc);
|
|
else
|
|
Con_Footerf(NULL, false, "");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Con_Footerf(NULL, false, "");
|
|
con_commandmatch = 1;
|
|
}
|
|
}
|
|
|
|
//lines typed at the main console enter here
|
|
int Con_ExecuteLine(console_t *con, char *line)
|
|
{
|
|
qboolean waschat = false;
|
|
char deutf8[8192];
|
|
if (com_parseutf8.ival <= 0)
|
|
{
|
|
unsigned int unicode;
|
|
int err;
|
|
int len = 0;
|
|
while(*line)
|
|
{
|
|
unicode = utf8_decode(&err, line, &line);
|
|
len += unicode_encode(deutf8+len, unicode, sizeof(deutf8)-1 - len, true);
|
|
}
|
|
deutf8[len] = 0;
|
|
line = deutf8;
|
|
}
|
|
|
|
con_commandmatch=1;
|
|
Con_Footerf(con, false, "");
|
|
|
|
if (cls.state >= ca_connected && cl_chatmode.value == 2)
|
|
{
|
|
waschat = true;
|
|
if (keydown[K_CTRL])
|
|
Cbuf_AddText ("say_team ", RESTRICT_LOCAL);
|
|
else if (keydown[K_SHIFT] || *line == ' ')
|
|
Cbuf_AddText ("say ", RESTRICT_LOCAL);
|
|
else
|
|
waschat = false;
|
|
}
|
|
while (*line == ' ')
|
|
line++;
|
|
if (waschat)
|
|
Cbuf_AddText (line, RESTRICT_LOCAL);
|
|
else
|
|
{
|
|
char *exec = NULL;
|
|
if (line[0] == '\\' || line[0] == '/')
|
|
exec = line+1; // skip the slash
|
|
else if (cl_chatmode.value == 2 && Cmd_IsCommand(line))
|
|
exec = line; // valid command
|
|
#ifdef Q2CLIENT
|
|
else if (cls.protocol == CP_QUAKE2)
|
|
exec = line; // send the command to the server via console, and let the server convert to chat
|
|
#endif
|
|
else if (*line)
|
|
{ // convert to a chat message
|
|
if ((cl_chatmode.value == 1 || ((cls.state >= ca_connected && cl_chatmode.value == 2) && (strncmp(line, "say ", 4)))))
|
|
{
|
|
if (keydown[K_CTRL])
|
|
Cbuf_AddText ("say_team ", RESTRICT_LOCAL);
|
|
else
|
|
Cbuf_AddText ("say ", RESTRICT_LOCAL);
|
|
waschat = true;
|
|
Cbuf_AddText (line, RESTRICT_LOCAL);
|
|
}
|
|
}
|
|
|
|
if (exec)
|
|
{
|
|
#ifdef TEXTEDITOR
|
|
extern qboolean editormodal;
|
|
if (editormodal)
|
|
{
|
|
char cvarname[128];
|
|
COM_ParseOut(exec, cvarname, sizeof(cvarname));
|
|
if (Cvar_FindVar(cvarname) && !strchr(line, ';') && !strchr(line, '\n'))
|
|
{
|
|
Con_Printf ("]%s\n",line);
|
|
Cmd_ExecuteString(exec, RESTRICT_SERVER);
|
|
return true;
|
|
}
|
|
|
|
Con_Footerf(con, false, "Commands cannot be execed while debugging QC");
|
|
}
|
|
#endif
|
|
Cbuf_AddText (exec, RESTRICT_LOCAL);
|
|
}
|
|
}
|
|
|
|
Cbuf_AddText ("\n", RESTRICT_LOCAL);
|
|
if (!waschat || con_echochat.value)
|
|
Con_Printf ("]%s\n",line);
|
|
|
|
if (cls.state == ca_disconnected)
|
|
SCR_UpdateScreen (); // force an update, because the command
|
|
// may take some time
|
|
|
|
return true;
|
|
}
|
|
|
|
vec3_t sccolor;
|
|
|
|
static void QDECL Con_Selectioncolour_Callback(struct cvar_s *var, char *oldvalue)
|
|
{
|
|
if (qrenderer != QR_NONE)
|
|
SCR_StringToRGB(var->string, sccolor, 1);
|
|
}
|
|
|
|
qboolean Key_GetConsoleSelectionBox(console_t *con, int *sx, int *sy, int *ex, int *ey)
|
|
{
|
|
*sx = *sy = *ex = *ey = 0;
|
|
|
|
if (con->buttonsdown == CB_SCROLL)
|
|
{
|
|
//left-mouse.
|
|
//scroll the console with the mouse. trigger links on release.
|
|
while (con->mousecursor[1] - con->mousedown[1] > 8 && con->display->older)
|
|
{
|
|
con->mousedown[1] += 8;
|
|
con->display = con->display->older;
|
|
}
|
|
while (con->mousecursor[1] - con->mousedown[1] < -8 && con->display->newer)
|
|
{
|
|
con->mousedown[1] -= 8;
|
|
con->display = con->display->newer;
|
|
}
|
|
|
|
*sx = con->mousecursor[0];
|
|
*sy = con->mousecursor[1];
|
|
*ex = con->mousecursor[0];
|
|
*ey = con->mousecursor[1];
|
|
return true;
|
|
}
|
|
else if (con->buttonsdown == CB_COPY)
|
|
{
|
|
//right-mouse
|
|
//select. copy-to-clipboard on release.
|
|
*sx = con->mousedown[0];
|
|
*sy = con->mousedown[1];
|
|
*ex = con->mousecursor[0];
|
|
*ey = con->mousecursor[1];
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if (con_curwindow == con && con->buttonsdown)
|
|
{
|
|
if (con->buttonsdown == CB_MOVE)
|
|
{ //move window to track the cursor
|
|
con->wnd_x += con->mousecursor[0] - con->mousedown[0];
|
|
// con->mousedown[0] = con->mousecursor[0];
|
|
con->wnd_y += con->mousecursor[1] - con->mousedown[1];
|
|
// con->mousedown[1] = con->mousecursor[1];
|
|
}
|
|
if (con->buttonsdown & CB_SIZELEFT)
|
|
{
|
|
if (con->wnd_w - (con->mousecursor[0] - con->mousedown[0]) >= 64)
|
|
{
|
|
con->wnd_w -= con->mousecursor[0] - con->mousedown[0];
|
|
con->wnd_x += con->mousecursor[0] - con->mousedown[0];
|
|
}
|
|
}
|
|
if (con->buttonsdown & CB_SIZERIGHT)
|
|
{
|
|
if (con->wnd_w + (con->mousecursor[0] - con->mousedown[0]) >= 64)
|
|
{
|
|
con->wnd_w += con->mousecursor[0] - con->mousedown[0];
|
|
con->mousedown[0] = con->mousecursor[0];
|
|
}
|
|
}
|
|
if (con->buttonsdown & CB_SIZEBOTTOM)
|
|
{
|
|
if (con->wnd_h + (con->mousecursor[1] - con->mousedown[1]) >= 64)
|
|
{
|
|
con->wnd_h += con->mousecursor[1] - con->mousedown[1];
|
|
con->mousedown[1] = con->mousecursor[1];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
con->buttonsdown = CB_NONE;
|
|
|
|
*sx = con->mousecursor[0];
|
|
*sy = con->mousecursor[1];
|
|
*ex = con->mousecursor[0];
|
|
*ey = con->mousecursor[1];
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/*insert the given text at the console input line at the current cursor pos*/
|
|
void Key_ConsoleInsert(char *instext)
|
|
{
|
|
int i;
|
|
int len, olen;
|
|
char *old;
|
|
if (!*instext)
|
|
return;
|
|
|
|
old = key_lines[edit_line];
|
|
len = strlen(instext);
|
|
olen = strlen(old);
|
|
key_lines[edit_line] = BZ_Malloc(olen + len + 1);
|
|
memcpy(key_lines[edit_line], old, key_linepos);
|
|
memcpy(key_lines[edit_line]+key_linepos, instext, len);
|
|
memcpy(key_lines[edit_line]+key_linepos+len, old+key_linepos, olen - key_linepos+1);
|
|
Z_Free(old);
|
|
for (i = key_linepos; i < key_linepos+len; i++)
|
|
{
|
|
if (key_lines[edit_line][i] == '\r')
|
|
key_lines[edit_line][i] = ' ';
|
|
else if (key_lines[edit_line][i] == '\n')
|
|
key_lines[edit_line][i] = ';';
|
|
}
|
|
key_linepos += len;
|
|
}
|
|
void Key_ConsoleReplace(char *instext)
|
|
{
|
|
if (!*instext)
|
|
return;
|
|
|
|
key_linepos = 1;
|
|
key_lines[edit_line][key_linepos] = 0;
|
|
Key_ConsoleInsert(instext);
|
|
}
|
|
|
|
void Key_DefaultLinkClicked(console_t *con, char *text, char *info)
|
|
{
|
|
char *c;
|
|
/*the engine supports specific default links*/
|
|
/*we don't support everything. a: there's no point. b: unbindall links are evil.*/
|
|
c = Info_ValueForKey(info, "player");
|
|
if (*c)
|
|
{
|
|
unsigned int player = atoi(c);
|
|
int i;
|
|
if (player >= cl.allocated_client_slots || !*cl.players[player].name)
|
|
return;
|
|
|
|
c = Info_ValueForKey(info, "action");
|
|
if (*c)
|
|
{
|
|
if (!strcmp(c, "mute"))
|
|
{
|
|
if (!cl.players[player].vignored)
|
|
{
|
|
cl.players[player].vignored = true;
|
|
Con_Printf("^[%s\\player\\%i^] muted\n", cl.players[player].name, player);
|
|
}
|
|
else
|
|
{
|
|
cl.players[player].vignored = false;
|
|
Con_Printf("^[%s\\player\\%i^] unmuted\n", cl.players[player].name, player);
|
|
}
|
|
}
|
|
else if (!strcmp(c, "ignore"))
|
|
{
|
|
if (!cl.players[player].ignored)
|
|
{
|
|
cl.players[player].ignored = true;
|
|
cl.players[player].vignored = true;
|
|
Con_Printf("^[%s\\player\\%i^] ignored\n", cl.players[player].name, player);
|
|
}
|
|
else
|
|
{
|
|
cl.players[player].ignored = false;
|
|
cl.players[player].vignored = false;
|
|
Con_Printf("^[%s\\player\\%i^] unignored\n", cl.players[player].name, player);
|
|
}
|
|
}
|
|
else if (!strcmp(c, "spec"))
|
|
{
|
|
Cam_TrackPlayer(0, "spectate", cl.players[player].name);
|
|
}
|
|
else if (!strcmp(c, "kick"))
|
|
{
|
|
#ifndef CLIENTONLY
|
|
if (sv.active)
|
|
{
|
|
//use the q3 command, because we can.
|
|
Cbuf_AddText(va("\nclientkick %i\n", player), RESTRICT_LOCAL);
|
|
}
|
|
else
|
|
#endif
|
|
Cbuf_AddText(va("\nrcon kick %s\n", cl.players[player].name), RESTRICT_LOCAL);
|
|
}
|
|
else if (!strcmp(c, "ban"))
|
|
{
|
|
#ifndef CLIENTONLY
|
|
if (sv.active)
|
|
{
|
|
//use the q3 command, because we can.
|
|
Cbuf_AddText(va("\nbanname %s QuickBan\n", cl.players[player].name), RESTRICT_LOCAL);
|
|
}
|
|
else
|
|
#endif
|
|
Cbuf_AddText(va("\nrcon banname %s QuickBan\n", cl.players[player].name), RESTRICT_LOCAL);
|
|
}
|
|
return;
|
|
}
|
|
|
|
Con_Footerf(con, false, "^m#^m ^[%s\\player\\%i^]: %if %ims", cl.players[player].name, player, cl.players[player].frags, cl.players[player].ping);
|
|
|
|
for (i = 0; i < cl.splitclients; i++)
|
|
{
|
|
if (cl.playerview[i].playernum == player)
|
|
break;
|
|
}
|
|
if (i == cl.splitclients)
|
|
{
|
|
extern cvar_t rcon_password;
|
|
if (cl.spectator || cls.demoplayback)
|
|
{
|
|
//we're spectating, or an mvd
|
|
Con_Footerf(con, true, " ^[Spectate\\player\\%i\\action\\spec^]", player);
|
|
}
|
|
else
|
|
{
|
|
//we're playing.
|
|
if (cls.protocol == CP_QUAKEWORLD && strcmp(cl.players[cl.playerview[0].playernum].team, cl.players[player].team))
|
|
Con_Footerf(con, true, " ^[[Join Team %s]\\cmd\\setinfo team %s^]", cl.players[player].team, cl.players[player].team);
|
|
}
|
|
Con_Footerf(con, true, " ^[%sgnore\\player\\%i\\action\\ignore^]", cl.players[player].ignored?"Uni":"I", player);
|
|
// if (cl_voip_play.ival)
|
|
Con_Footerf(con, true, " ^[%sute\\player\\%i\\action\\mute^]", cl.players[player].vignored?"Unm":"M", player);
|
|
|
|
if (!cls.demoplayback && (*rcon_password.string
|
|
#ifndef CLIENTONLY
|
|
|| (sv.state && svs.clients[player].netchan.remote_address.type != NA_LOOPBACK)
|
|
#endif
|
|
))
|
|
{
|
|
Con_Footerf(con, true, " ^[Kick\\player\\%i\\action\\kick^]", player);
|
|
Con_Footerf(con, true, " ^[Ban\\player\\%i\\action\\ban^]", player);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
char cmdprefix[6];
|
|
snprintf(cmdprefix, sizeof(cmdprefix), "%i ", i);
|
|
|
|
//hey look! its you!
|
|
|
|
if (cl.spectator || cls.demoplayback)
|
|
{
|
|
//need join option here or something
|
|
}
|
|
else
|
|
{
|
|
Con_Footerf(con, true, " ^[Suicide\\cmd\\kill^]");
|
|
#ifndef CLIENTONLY
|
|
if (!sv.state)
|
|
Con_Footerf(con, true, " ^[Disconnect\\cmd\\disconnect^]");
|
|
if (cls.allow_cheats || (sv.state && sv.allocated_client_slots == 1))
|
|
#else
|
|
Con_Footerf(con, true, " ^[Disconnect\\cmd\\disconnect^]");
|
|
if (cls.allow_cheats)
|
|
#endif
|
|
{
|
|
Con_Footerf(con, true, " ^[Noclip\\cmd\\noclip^]");
|
|
Con_Footerf(con, true, " ^[Fly\\cmd\\fly^]");
|
|
Con_Footerf(con, true, " ^[God\\cmd\\god^]");
|
|
Con_Footerf(con, true, " ^[Give\\impulse\\9^]");
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
c = Info_ValueForKey(info, "menu");
|
|
if (*c && !strchr(c, ';') && !strchr(c, '\n'))
|
|
{
|
|
Cbuf_AddText(va("\nmenu_cmd conlink %s\n", c), RESTRICT_LOCAL);
|
|
return;
|
|
}
|
|
c = Info_ValueForKey(info, "connect");
|
|
if (*c && !strchr(c, ';') && !strchr(c, '\n'))
|
|
{
|
|
Cbuf_AddText(va("\nconnect %s\n", c), RESTRICT_LOCAL);
|
|
return;
|
|
}
|
|
c = Info_ValueForKey(info, "join");
|
|
if (*c && !strchr(c, ';') && !strchr(c, '\n'))
|
|
{
|
|
Cbuf_AddText(va("\njoin %s\n", c), RESTRICT_LOCAL);
|
|
return;
|
|
}
|
|
/*c = Info_ValueForKey(info, "url");
|
|
if (*c && !strchr(c, ';') && !strchr(c, '\n'))
|
|
{
|
|
Cbuf_AddText(va("\nplayfilm %s\n", c), RESTRICT_LOCAL);
|
|
return;
|
|
}*/
|
|
c = Info_ValueForKey(info, "observe");
|
|
if (*c && !strchr(c, ';') && !strchr(c, '\n'))
|
|
{
|
|
Cbuf_AddText(va("\nobserve %s\n", c), RESTRICT_LOCAL);
|
|
return;
|
|
}
|
|
c = Info_ValueForKey(info, "qtv");
|
|
if (*c && !strchr(c, ';') && !strchr(c, '\n'))
|
|
{
|
|
Cbuf_AddText(va("\nqtvplay %s\n", c), RESTRICT_LOCAL);
|
|
return;
|
|
}
|
|
c = Info_ValueForKey(info, "demo");
|
|
if (*c && !strchr(c, ';') && !strchr(c, '\n'))
|
|
{
|
|
Cbuf_AddText(va("\nplaydemo %s\n", c), RESTRICT_LOCAL);
|
|
return;
|
|
}
|
|
c = Info_ValueForKey(info, "map");
|
|
if (*c && !strchr(c, ';') && !strchr(c, '\n'))
|
|
{
|
|
Cbuf_AddText(va("\nmap %s\n", c), RESTRICT_LOCAL);
|
|
return;
|
|
}
|
|
c = Info_ValueForKey(info, "type");
|
|
if (*c)
|
|
{
|
|
Key_ConsoleReplace(c);
|
|
return;
|
|
}
|
|
c = Info_ValueForKey(info, "cmd");
|
|
if (*c && !strchr(c, ';') && !strchr(c, '\n'))
|
|
{
|
|
Cbuf_AddText(va("\ncmd %s\n", c), RESTRICT_LOCAL);
|
|
return;
|
|
}
|
|
c = Info_ValueForKey(info, "edit");
|
|
if (*c && !strchr(c, ';') && !strchr(c, '\n'))
|
|
{
|
|
Cbuf_AddText(va("\nedit %s\n", c), RESTRICT_LOCAL);
|
|
return;
|
|
}
|
|
c = Info_ValueForKey(info, "impulse");
|
|
if (*c && !strchr(c, ';') && !strchr(c, '\n'))
|
|
{
|
|
Cbuf_AddText(va("\nimpulse %s\n", c), RESTRICT_LOCAL);
|
|
return;
|
|
}
|
|
c = Info_ValueForKey(info, "film");
|
|
if (*c && !strchr(c, ';') && !strchr(c, '\n'))
|
|
{
|
|
Cbuf_AddText(va("\nplayfilm \"%s\"\n", c), RESTRICT_LOCAL);
|
|
return;
|
|
}
|
|
c = Info_ValueForKey(info, "desc");
|
|
if (*c)
|
|
{
|
|
Con_Footerf(con, false, "%s", c);
|
|
return;
|
|
}
|
|
|
|
//if there's no info and the text starts with a leading / then insert it as a suggested/completed console command
|
|
//skip any leading colour code.
|
|
if (text[0] == '^' && text[1] >= '0' && text[1] <= '9')
|
|
text+=2;
|
|
if (*text == '/')
|
|
{
|
|
int tlen = info - text;
|
|
Z_Free(key_lines[edit_line]);
|
|
key_lines[edit_line] = BZ_Malloc(tlen + 2);
|
|
key_lines[edit_line][0] = ']';
|
|
memcpy(key_lines[edit_line]+1, text, tlen);
|
|
key_lines[edit_line][1+tlen] = 0;
|
|
key_linepos = strlen(key_lines[edit_line]);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void Key_ConsoleRelease(console_t *con, int key, int unicode)
|
|
{
|
|
char *buffer;
|
|
|
|
if (key == K_MOUSE1 && con->buttonsdown == CB_SCROLL)
|
|
{
|
|
con->buttonsdown = CB_NONE;
|
|
if (abs(con->mousedown[0] - con->mousecursor[0]) < 5 && abs(con->mousedown[1] - con->mousecursor[1]) < 5)
|
|
{
|
|
buffer = Con_CopyConsole(con, false, false);
|
|
Con_Footerf(con, false, "");
|
|
if (!buffer)
|
|
return;
|
|
if (keydown[K_SHIFT])
|
|
{
|
|
int len;
|
|
len = strlen(buffer);
|
|
//strip any trailing dots/elipsis
|
|
while (len > 1 && !strcmp(buffer+len-1, "."))
|
|
{
|
|
len-=1;
|
|
buffer[len] = 0;
|
|
}
|
|
//strip any enclosing quotes
|
|
while (*buffer == '\"' && len > 2 && !strcmp(buffer+len-1, "\""))
|
|
{
|
|
len-=2;
|
|
memmove(buffer, buffer+1, len);
|
|
buffer[len] = 0;
|
|
}
|
|
Key_ConsoleInsert(buffer);
|
|
}
|
|
else
|
|
{
|
|
if (buffer[0] == '^' && buffer[1] == '[')
|
|
{
|
|
//looks like it might be a link!
|
|
char *end = NULL;
|
|
char *info;
|
|
for (info = buffer + 2; *info; )
|
|
{
|
|
if (info[0] == '^' && info[1] == ']')
|
|
{
|
|
break;
|
|
}
|
|
if (*info == '\\')
|
|
break;
|
|
else if (info[0] == '^' && info[1] == '^')
|
|
info+=2;
|
|
else
|
|
info++;
|
|
}
|
|
for(end = info; *end; )
|
|
{
|
|
if (end[0] == '^' && end[1] == ']')
|
|
{
|
|
//okay, its a valid link that they clicked
|
|
*end = 0;
|
|
#ifdef PLUGINS
|
|
if (!Plug_ConsoleLink(buffer+2, info))
|
|
#endif
|
|
#ifdef CSQC_DAT
|
|
if (!CSQC_ConsoleLink(buffer+2, info))
|
|
#endif
|
|
{
|
|
Key_DefaultLinkClicked(con, buffer+2, info);
|
|
}
|
|
|
|
break;
|
|
}
|
|
if (end[0] == '^' && end[1] == '^')
|
|
end+=2;
|
|
else
|
|
end++;
|
|
}
|
|
}
|
|
}
|
|
Z_Free(buffer);
|
|
}
|
|
else
|
|
Con_Footerf(con, false, "");
|
|
}
|
|
if (key == K_MOUSE2 && con->buttonsdown == CB_COPY)
|
|
{
|
|
con->buttonsdown = CB_NONE;
|
|
buffer = Con_CopyConsole(con, true, false); //don't keep markup if we're copying to the clipboard
|
|
if (!buffer)
|
|
return;
|
|
Sys_SaveClipboard(buffer);
|
|
Z_Free(buffer);
|
|
}
|
|
if (con->buttonsdown == CB_CLOSE)
|
|
{ //window X (close)
|
|
if (con->mousecursor[0] > con->wnd_w-8 && con->mousecursor[1] < 8)
|
|
{
|
|
Con_Destroy (con);
|
|
return;
|
|
}
|
|
}
|
|
// if (con->buttonsdown == CB_MOVE) //window title(move)
|
|
con->buttonsdown = CB_NONE;
|
|
}
|
|
//if the referenced (trailing) chevron is doubled up, then it doesn't act as part of any markup and should be ignored for such things.
|
|
static qboolean utf_specialchevron(unsigned char *start, unsigned char *chev)
|
|
{
|
|
int count = 0;
|
|
while (chev >= start)
|
|
{
|
|
if (*chev-- == '^')
|
|
count++;
|
|
else
|
|
break;
|
|
}
|
|
return count&1;
|
|
}
|
|
//move the cursor one char to the left. cursor must be within the 'start' string.
|
|
static unsigned char *utf_left(unsigned char *start, unsigned char *cursor)
|
|
{
|
|
if (cursor == start)
|
|
return cursor;
|
|
if (1)//com_parseutf8.ival>0)
|
|
{
|
|
cursor--;
|
|
while ((*cursor & 0xc0) == 0x80 && cursor > start)
|
|
cursor--;
|
|
}
|
|
else
|
|
cursor--;
|
|
|
|
//FIXME: should verify that the ^ isn't doubled.
|
|
if (*cursor == ']' && cursor > start && utf_specialchevron(start, cursor-1))
|
|
{
|
|
//just stepped onto a link
|
|
unsigned char *linkstart;
|
|
linkstart = cursor-1;
|
|
while(linkstart >= start)
|
|
{
|
|
//FIXME: should verify that the ^ isn't doubled.
|
|
if (utf_specialchevron(start, linkstart) && linkstart[1] == '[')
|
|
return linkstart;
|
|
linkstart--;
|
|
}
|
|
}
|
|
|
|
return cursor;
|
|
}
|
|
|
|
//move the cursor one char to the right.
|
|
static unsigned char *utf_right(unsigned char *start, unsigned char *cursor)
|
|
{
|
|
//FIXME: should make sure this is not doubled.
|
|
if (utf_specialchevron(start, cursor) && cursor[1] == '[')
|
|
{
|
|
//just stepped over a link
|
|
char *linkend;
|
|
linkend = cursor+2;
|
|
while(*linkend)
|
|
{
|
|
if (utf_specialchevron(start, linkend) && linkend[1] == ']')
|
|
return linkend+2;
|
|
else
|
|
linkend++;
|
|
}
|
|
return linkend;
|
|
}
|
|
|
|
if (1)//com_parseutf8.ival>0)
|
|
{
|
|
int skip = 1;
|
|
//figure out the length of the char
|
|
if ((*cursor & 0xc0) == 0x80)
|
|
skip = 1; //error
|
|
else if ((*cursor & 0xe0) == 0xc0)
|
|
skip = 2;
|
|
else if ((*cursor & 0xf0) == 0xe0)
|
|
skip = 3;
|
|
else if ((*cursor & 0xf1) == 0xf0)
|
|
skip = 4;
|
|
else if ((*cursor & 0xf3) == 0xf1)
|
|
skip = 5;
|
|
else if ((*cursor & 0xf7) == 0xf3)
|
|
skip = 6;
|
|
else if ((*cursor & 0xff) == 0xf7)
|
|
skip = 7;
|
|
else skip = 1;
|
|
|
|
while (*cursor && skip)
|
|
{
|
|
cursor++;
|
|
skip--;
|
|
}
|
|
}
|
|
else if (*cursor)
|
|
cursor++;
|
|
|
|
return cursor;
|
|
}
|
|
|
|
void Key_EntryInsert(unsigned char **line, int *linepos, char *instext)
|
|
{
|
|
int i;
|
|
int len, olen;
|
|
char *old;
|
|
|
|
if (!*instext)
|
|
return;
|
|
|
|
old = (*line);
|
|
len = strlen(instext);
|
|
olen = strlen(old);
|
|
*line = BZ_Malloc(olen + len + 1);
|
|
memcpy(*line, old, *linepos);
|
|
memcpy(*line+*linepos, instext, len);
|
|
memcpy(*line+*linepos+len, old+*linepos, olen - *linepos+1);
|
|
Z_Free(old);
|
|
for (i = *linepos; i < *linepos+len; i++)
|
|
{
|
|
if ((*line)[i] == '\r')
|
|
(*line)[i] = ' ';
|
|
else if ((*line)[i] == '\n')
|
|
(*line)[i] = ';';
|
|
}
|
|
*linepos += len;
|
|
}
|
|
|
|
qboolean Key_EntryLine(unsigned char **line, int lineoffset, int *linepos, int key, unsigned int unicode)
|
|
{
|
|
qboolean ctrl = keydown[K_LCTRL] || keydown[K_RCTRL];
|
|
qboolean shift = keydown[K_LSHIFT] || keydown[K_RSHIFT];
|
|
char utf8[8];
|
|
|
|
if (key == K_LEFTARROW || key == K_KP_LEFTARROW)
|
|
{
|
|
if (ctrl)
|
|
{
|
|
//ignore whitespace if we're at the end of the word
|
|
while (*linepos > 0 && (*line)[*linepos-1] == ' ')
|
|
*linepos = utf_left((*line)+lineoffset, (*line) + *linepos) - (*line);
|
|
//keep skipping until we find the start of that word
|
|
while (ctrl && *linepos > lineoffset && (*line)[*linepos-1] != ' ')
|
|
*linepos = utf_left((*line)+lineoffset, (*line) + *linepos) - (*line);
|
|
}
|
|
else
|
|
*linepos = utf_left((*line)+lineoffset, (*line) + *linepos) - (*line);
|
|
return true;
|
|
}
|
|
if (key == K_RIGHTARROW || key == K_KP_RIGHTARROW)
|
|
{
|
|
if ((*line)[*linepos])
|
|
{
|
|
*linepos = utf_right((*line)+lineoffset, (*line) + *linepos) - (*line);
|
|
if (ctrl)
|
|
{
|
|
//skip over the word
|
|
while ((*line)[*linepos] && (*line)[*linepos] != ' ')
|
|
*linepos = utf_right((*line)+lineoffset, (*line) + *linepos) - (*line);
|
|
//as well as any trailing whitespace
|
|
while ((*line)[*linepos] == ' ')
|
|
*linepos = utf_right((*line)+lineoffset, (*line) + *linepos) - (*line);
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
unicode = ' ';
|
|
}
|
|
|
|
if (key == K_DEL || key == K_KP_DEL)
|
|
{
|
|
if ((*line)[*linepos])
|
|
{
|
|
int charlen = utf_right((*line)+lineoffset, (*line) + *linepos) - ((*line) + *linepos);
|
|
memmove((*line)+*linepos, (*line)+*linepos+charlen, strlen((*line)+*linepos+charlen)+1);
|
|
return true;
|
|
}
|
|
else
|
|
key = K_BACKSPACE;
|
|
}
|
|
|
|
if (key == K_BACKSPACE)
|
|
{
|
|
if (*linepos > lineoffset)
|
|
{
|
|
int charlen = ((*line)+*linepos) - utf_left((*line)+lineoffset, (*line) + *linepos);
|
|
memmove((*line)+*linepos-charlen, (*line)+*linepos, strlen((*line)+*linepos)+1);
|
|
*linepos -= charlen;
|
|
}
|
|
if (!(*line)[lineoffset]) //oops?
|
|
con_commandmatch = 0;
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
if (key == K_HOME || key == K_KP_HOME)
|
|
{
|
|
*linepos = lineoffset;
|
|
return true;
|
|
}
|
|
|
|
if (key == K_END || key == K_KP_END)
|
|
{
|
|
*linepos = strlen(*line);
|
|
return true;
|
|
}
|
|
|
|
//beware that windows translates ctrl+c and ctrl+v to a control char
|
|
if (((unicode=='C' || unicode=='c' || unicode==3) && ctrl) || (ctrl && key == K_INS))
|
|
{
|
|
Sys_SaveClipboard(*line);
|
|
return true;
|
|
}
|
|
|
|
if (((unicode=='V' || unicode=='v' || unicode==22) && ctrl) || (shift && key == K_INS))
|
|
{
|
|
char *clipText = Sys_GetClipboard();
|
|
if (clipText)
|
|
{
|
|
Key_EntryInsert(line, linepos, clipText);
|
|
Sys_CloseClipboard(clipText);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (unicode < ' ')
|
|
{
|
|
//if the user is entering control codes, then the ctrl+foo mechanism is probably unsupported by the unicode input stuff, so give best-effort replacements.
|
|
switch(unicode)
|
|
{
|
|
case 27/*'['*/: unicode = 0xe010; break;
|
|
case 29/*']'*/: unicode = 0xe011; break;
|
|
case 7/*'g'*/: unicode = 0xe086; break;
|
|
case 18/*'r'*/: unicode = 0xe087; break;
|
|
case 25/*'y'*/: unicode = 0xe088; break;
|
|
case 2/*'b'*/: unicode = 0xe089; break;
|
|
case 19/*'s'*/: unicode = 0xe080; break;
|
|
case 4/*'d'*/: unicode = 0xe081; break;
|
|
case 6/*'f'*/: unicode = 0xe082; break;
|
|
case 1/*'a'*/: unicode = 0xe083; break;
|
|
case 21/*'u'*/: unicode = 0xe01d; break;
|
|
case 9/*'i'*/: unicode = 0xe01e; break;
|
|
case 15/*'o'*/: unicode = 0xe01f; break;
|
|
case 10/*'j'*/: unicode = 0xe01c; break;
|
|
case 16/*'p'*/: unicode = 0xe09c; break;
|
|
case 13/*'m'*/: unicode = 0xe08b; break;
|
|
case 11/*'k'*/: unicode = 0xe08d; break;
|
|
case 14/*'n'*/: unicode = '\r'; break;
|
|
default:
|
|
// if (unicode)
|
|
// Con_Printf("escape code %i\n", unicode);
|
|
|
|
//even if we don't print these, we still need to cancel them in the caller.
|
|
if (key == K_LALT || key == K_RALT ||
|
|
key == K_LCTRL || key == K_RCTRL ||
|
|
key == K_LSHIFT || key == K_RSHIFT)
|
|
return true;
|
|
return false;
|
|
}
|
|
}
|
|
else if (com_parseutf8.ival >= 0) //don't do this for iso8859-1. the major user of that is hexen2 which doesn't have these chars.
|
|
{
|
|
if (ctrl && !keydown[K_RALT])
|
|
{
|
|
if (unicode >= '0' && unicode <= '9')
|
|
unicode = unicode - '0' + 0xe012; // yellow number
|
|
else switch (unicode)
|
|
{
|
|
case '[': unicode = 0xe010; break;
|
|
case ']': unicode = 0xe011; break;
|
|
case 'g': unicode = 0xe086; break;
|
|
case 'r': unicode = 0xe087; break;
|
|
case 'y': unicode = 0xe088; break;
|
|
case 'b': unicode = 0xe089; break;
|
|
case '(': unicode = 0xe080; break;
|
|
case '=': unicode = 0xe081; break;
|
|
case ')': unicode = 0xe082; break;
|
|
case 'a': unicode = 0xe083; break;
|
|
case '<': unicode = 0xe01d; break;
|
|
case '-': unicode = 0xe01e; break;
|
|
case '>': unicode = 0xe01f; break;
|
|
case ',': unicode = 0xe01c; break;
|
|
case '.': unicode = 0xe09c; break;
|
|
case 'B': unicode = 0xe08b; break;
|
|
case 'C': unicode = 0xe08d; break;
|
|
case 'n': unicode = '\r'; break;
|
|
}
|
|
}
|
|
|
|
if (keydown[K_LALT] && unicode > 32 && unicode < 128)
|
|
unicode |= 0xe080; // red char
|
|
}
|
|
|
|
unicode = utf8_encode(utf8, unicode, sizeof(utf8)-1);
|
|
if (unicode)
|
|
{
|
|
utf8[unicode] = 0;
|
|
Key_EntryInsert(line, linepos, utf8);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
Key_Console
|
|
|
|
Interactive line editing and console scrollback
|
|
====================
|
|
*/
|
|
qboolean Key_Console (console_t *con, unsigned int unicode, int key)
|
|
{
|
|
qboolean ctrl = keydown[K_LCTRL] || keydown[K_RCTRL];
|
|
qboolean shift = keydown[K_LSHIFT] || keydown[K_RSHIFT];
|
|
|
|
//weirdness for the keypad.
|
|
if ((unicode >= '0' && unicode <= '9') || unicode == '.')
|
|
key = 0;
|
|
|
|
if (con->redirect)
|
|
{
|
|
if (key == K_TAB)
|
|
{ // command completion
|
|
if (ctrl || shift)
|
|
{
|
|
Con_CycleConsole();
|
|
return true;
|
|
}
|
|
}
|
|
con->redirect(con_current, key);
|
|
return true;
|
|
}
|
|
|
|
if ((key == K_MOUSE1 || key == K_MOUSE2))
|
|
{
|
|
if (con->flags & CONF_ISWINDOW)
|
|
if (con->mousecursor[0] < 0 || con->mousecursor[1] < 0 || con->mousecursor[0] > con->wnd_w || con->mousecursor[1] > con->wnd_h)
|
|
return true;
|
|
if (con == con_mouseover)
|
|
{
|
|
con->buttonsdown = CB_NONE;
|
|
|
|
if ((con->flags & CONF_ISWINDOW) && !keydown[K_SHIFT])
|
|
Con_SetActive(con);
|
|
}
|
|
con->mousedown[0] = con->mousecursor[0];
|
|
con->mousedown[1] = con->mousecursor[1];
|
|
if (con_mouseover && con->mousedown[1] < 8)//(8.0*vid.height)/vid.pixelheight)
|
|
{
|
|
if (key == K_MOUSE2 && !(con->flags & CONF_ISWINDOW))
|
|
Con_Destroy (con);
|
|
else
|
|
{
|
|
Con_SetActive(con);
|
|
if ((con->flags & CONF_ISWINDOW))
|
|
con->buttonsdown = (con->mousedown[0] > con->wnd_w-8)?CB_CLOSE:CB_MOVE;
|
|
}
|
|
}
|
|
else if (key == K_MOUSE2)
|
|
con->buttonsdown = CB_COPY;
|
|
else
|
|
{
|
|
con->buttonsdown = CB_NONE;
|
|
if ((con->flags & CONF_ISWINDOW) && con->mousedown[0] < 8)
|
|
con->buttonsdown |= CB_SIZELEFT;
|
|
if ((con->flags & CONF_ISWINDOW) && con->mousedown[0] > con->wnd_w-8)
|
|
con->buttonsdown |= CB_SIZERIGHT;
|
|
if ((con->flags & CONF_ISWINDOW) && con->mousedown[1] > con->wnd_h-8)
|
|
con->buttonsdown |= CB_SIZEBOTTOM;
|
|
if (con->buttonsdown == CB_NONE)
|
|
con->buttonsdown = CB_SCROLL;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//console does not have any way to accept input, so don't try giving it any.
|
|
if (!con->linebuffered)
|
|
return false;
|
|
|
|
if (key == K_ENTER || key == K_KP_ENTER)
|
|
{ // backslash text are commands, else chat
|
|
int oldl = edit_line;
|
|
|
|
if (con->linebuffered)
|
|
{
|
|
if (con->linebuffered(con, key_lines[oldl]+1) != 2)
|
|
{
|
|
edit_line = (edit_line + 1) & (CON_EDIT_LINES_MASK);
|
|
history_line = edit_line;
|
|
}
|
|
}
|
|
con_commandmatch = 0;
|
|
|
|
Z_Free(key_lines[edit_line]);
|
|
key_lines[edit_line] = BZ_Malloc(2);
|
|
key_lines[edit_line][0] = ']';
|
|
key_lines[edit_line][1] = '\0';
|
|
key_linepos = 1;
|
|
|
|
return true;
|
|
}
|
|
|
|
if (key == K_SPACE && ctrl && con->commandcompletion)
|
|
{
|
|
char *txt = key_lines[edit_line]+1;
|
|
if (*txt == '/')
|
|
txt++;
|
|
if (Cmd_CompleteCommand(txt, true, true, con->commandcompletion, NULL))
|
|
{
|
|
CompleteCommand (true);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (key == K_TAB)
|
|
{ // command completion
|
|
if (shift)
|
|
{
|
|
Con_CycleConsole();
|
|
return true;
|
|
}
|
|
|
|
if (con->commandcompletion)
|
|
CompleteCommand (ctrl);
|
|
return true;
|
|
}
|
|
if (key != K_CTRL && key != K_SHIFT && con_commandmatch)
|
|
con_commandmatch=1;
|
|
|
|
if (key == K_UPARROW || key == K_KP_UPARROW)
|
|
{
|
|
do
|
|
{
|
|
history_line = (history_line - 1) & CON_EDIT_LINES_MASK;
|
|
} while (history_line != edit_line
|
|
&& !key_lines[history_line][1]);
|
|
if (history_line == edit_line)
|
|
history_line = (edit_line+1)&CON_EDIT_LINES_MASK;
|
|
key_lines[edit_line] = BZ_Realloc(key_lines[edit_line], strlen(key_lines[history_line])+1);
|
|
Q_strcpy(key_lines[edit_line], key_lines[history_line]);
|
|
key_linepos = Q_strlen(key_lines[edit_line]);
|
|
|
|
key_lines[edit_line][0] = ']';
|
|
if (!key_lines[edit_line][1])
|
|
con_commandmatch = 0;
|
|
return true;
|
|
}
|
|
|
|
if (key == K_DOWNARROW || key == K_KP_DOWNARROW)
|
|
{
|
|
if (history_line == edit_line)
|
|
{
|
|
key_lines[edit_line][0] = ']';
|
|
key_lines[edit_line][1] = '\0';
|
|
key_linepos=1;
|
|
con_commandmatch = 0;
|
|
return true;
|
|
}
|
|
do
|
|
{
|
|
history_line = (history_line + 1) & CON_EDIT_LINES_MASK;
|
|
}
|
|
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
|
|
{
|
|
key_lines[edit_line] = BZ_Realloc(key_lines[edit_line], strlen(key_lines[history_line])+1);
|
|
Q_strcpy(key_lines[edit_line], key_lines[history_line]);
|
|
key_linepos = Q_strlen(key_lines[edit_line]);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (key == K_PGUP || key == K_KP_PGUP || key==K_MWHEELUP)
|
|
{
|
|
int i = 2;
|
|
if (ctrl)
|
|
i = 8;
|
|
if (!con->display)
|
|
return true;
|
|
if (con->display == con->current)
|
|
i+=2; //skip over the blank input line, and extra so we actually move despite the addition of the ^^^^^ line
|
|
while (i-->0)
|
|
{
|
|
if (con->display->older == NULL)
|
|
break;
|
|
con->display = con->display->older;
|
|
}
|
|
return true;
|
|
}
|
|
if (key == K_PGDN || key == K_KP_PGDN || key==K_MWHEELDOWN)
|
|
{
|
|
int i = 2;
|
|
if (ctrl)
|
|
i = 8;
|
|
if (!con->display)
|
|
return true;
|
|
while (i-->0)
|
|
{
|
|
if (con->display->newer == NULL)
|
|
break;
|
|
con->display = con->display->newer;
|
|
}
|
|
if (con->display->newer && con->display->newer == con->current)
|
|
con->display = con->current;
|
|
return true;
|
|
}
|
|
|
|
if ((key == K_HOME || key == K_KP_HOME) && ctrl)
|
|
{
|
|
con->display = con->oldest;
|
|
return true;
|
|
}
|
|
|
|
if ((key == K_END || key == K_KP_END) && ctrl)
|
|
{
|
|
con->display = con->current;
|
|
return true;
|
|
}
|
|
|
|
Key_EntryLine(&key_lines[edit_line], 1, &key_linepos, key, unicode);
|
|
return true;
|
|
}
|
|
|
|
//============================================================================
|
|
|
|
qboolean chat_team;
|
|
unsigned char *chat_buffer;
|
|
int chat_bufferpos;
|
|
|
|
void Key_Message (int key, int unicode)
|
|
{
|
|
if (!chat_buffer)
|
|
{
|
|
chat_buffer = BZ_Malloc(1);
|
|
chat_buffer[0] = 0;
|
|
chat_bufferpos = 0;
|
|
}
|
|
|
|
if (key == K_ENTER || key == K_KP_ENTER)
|
|
{
|
|
if (chat_buffer && chat_buffer[0])
|
|
{ //send it straight into the command.
|
|
char *line = chat_buffer;
|
|
char deutf8[8192];
|
|
if (com_parseutf8.ival <= 0)
|
|
{
|
|
unsigned int unicode;
|
|
int err;
|
|
int len = 0;
|
|
while(*line)
|
|
{
|
|
unicode = utf8_decode(&err, line, &line);
|
|
len += unicode_encode(deutf8+len, unicode, sizeof(deutf8)-1 - len, true);
|
|
}
|
|
deutf8[len] = 0;
|
|
line = deutf8;
|
|
}
|
|
|
|
Cmd_TokenizeString(va("%s %s", chat_team?"say_team":"say", line), true, false);
|
|
CL_Say(chat_team, "");
|
|
}
|
|
|
|
Key_Dest_Remove(kdm_message);
|
|
chat_bufferpos = 0;
|
|
chat_buffer[0] = 0;
|
|
return;
|
|
}
|
|
|
|
if (key == K_ESCAPE)
|
|
{
|
|
Key_Dest_Remove(kdm_message);
|
|
chat_bufferpos = 0;
|
|
chat_buffer[0] = 0;
|
|
return;
|
|
}
|
|
|
|
Key_EntryLine(&chat_buffer, 0, &chat_bufferpos, key, unicode);
|
|
}
|
|
|
|
//============================================================================
|
|
|
|
char *Key_GetBinding(int keynum)
|
|
{
|
|
if (keynum >= 0 && keynum < K_MAX)
|
|
return keybindings[keynum][0];
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
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 (const char *str, int *modifier)
|
|
{
|
|
keyname_t *kn;
|
|
char *underscore;
|
|
|
|
if (!strnicmp(str, "std_", 4))
|
|
*modifier = 0;
|
|
else
|
|
{
|
|
*modifier = 0;
|
|
while(1)
|
|
{
|
|
underscore = strchr(str, '_');
|
|
if (!underscore || !underscore[1])
|
|
break; //nothing afterwards or no underscore.
|
|
if (!strnicmp(str, "shift_", 6))
|
|
*modifier |= KEY_MODIFIER_SHIFT;
|
|
else if (!strnicmp(str, "alt_", 4))
|
|
*modifier |= KEY_MODIFIER_ALT;
|
|
else if (!strnicmp(str, "ctrl_", 5))
|
|
*modifier |= KEY_MODIFIER_CTRL;
|
|
else
|
|
break;
|
|
str = underscore+1; //next char.
|
|
}
|
|
if (!*modifier)
|
|
*modifier = ~0;
|
|
}
|
|
|
|
if (!str || !str[0])
|
|
return -1;
|
|
if (!str[1]) //single char.
|
|
{
|
|
#if 0//def _WIN32
|
|
return VkKeyScan(str[0]);
|
|
#else
|
|
return str[0];
|
|
#endif
|
|
}
|
|
|
|
if (!strncmp(str, "K_", 2))
|
|
str+=2;
|
|
|
|
for (kn=keynames ; kn->name ; kn++)
|
|
{
|
|
if (!Q_strcasecmp(str,kn->name))
|
|
return kn->keynum;
|
|
}
|
|
if (atoi(str)) //assume ascii code. (prepend with a 0 if needed)
|
|
{
|
|
return atoi(str);
|
|
}
|
|
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 && keynum != '\'' && keynum != '\"')
|
|
{ // printable ascii
|
|
tinystr[0] = keynum;
|
|
tinystr[1] = 0;
|
|
return tinystr;
|
|
}
|
|
|
|
for (kn=keynames ; kn->name ; kn++)
|
|
if (keynum == kn->keynum)
|
|
return kn->name;
|
|
|
|
{
|
|
if (keynum < 10) //don't let it be a single character
|
|
return va("0%i", keynum);
|
|
return va("%i", keynum);
|
|
}
|
|
|
|
return "<UNKNOWN KEYNUM>";
|
|
}
|
|
|
|
|
|
/*
|
|
===================
|
|
Key_SetBinding
|
|
===================
|
|
*/
|
|
void Key_SetBinding (int keynum, int modifier, char *binding, int level)
|
|
{
|
|
char *newc;
|
|
int l;
|
|
|
|
if (modifier == ~0) //all of the possibilities.
|
|
{
|
|
for (l = 0; l < KEY_MODIFIERSTATES; l++)
|
|
Key_SetBinding(keynum, l, binding, level);
|
|
return;
|
|
}
|
|
|
|
if (keynum < 0 || keynum >= K_MAX)
|
|
return;
|
|
|
|
//just so the quit menu realises it needs to show something.
|
|
Cvar_ConfigChanged();
|
|
|
|
// free old bindings
|
|
if (keybindings[keynum][modifier])
|
|
{
|
|
Z_Free (keybindings[keynum][modifier]);
|
|
keybindings[keynum][modifier] = NULL;
|
|
}
|
|
|
|
|
|
if (!binding)
|
|
{
|
|
keybindings[keynum][modifier] = NULL;
|
|
return;
|
|
}
|
|
// allocate memory for new binding
|
|
l = Q_strlen (binding);
|
|
newc = Z_Malloc (l+1);
|
|
Q_strcpy (newc, binding);
|
|
newc[l] = 0;
|
|
keybindings[keynum][modifier] = newc;
|
|
bindcmdlevel[keynum][modifier] = level;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
Key_Unbind_f
|
|
===================
|
|
*/
|
|
void Key_Unbind_f (void)
|
|
{
|
|
int b, modifier;
|
|
|
|
if (Cmd_Argc() != 2)
|
|
{
|
|
Con_Printf ("unbind <key> : remove commands from a key\n");
|
|
return;
|
|
}
|
|
|
|
b = Key_StringToKeynum (Cmd_Argv(1), &modifier);
|
|
if (b==-1)
|
|
{
|
|
if (cl_warncmd.ival)
|
|
Con_Printf ("\"%s\" isn't a valid key\n", Cmd_Argv(1));
|
|
return;
|
|
}
|
|
|
|
Key_SetBinding (b, modifier, NULL, Cmd_ExecLevel);
|
|
}
|
|
|
|
void Key_Unbindall_f (void)
|
|
{
|
|
int i;
|
|
|
|
for (i=0 ; i<K_MAX ; i++)
|
|
if (keybindings[i])
|
|
Key_SetBinding (i, ~0, NULL, Cmd_ExecLevel);
|
|
}
|
|
|
|
void Key_AliasEdit_f (void)
|
|
{
|
|
char *alias = Cmd_AliasExist(Cmd_Argv(1), RESTRICT_LOCAL);
|
|
char quotedalias[2048];
|
|
if (alias)
|
|
{
|
|
COM_QuotedString(alias, quotedalias, sizeof(quotedalias), false);
|
|
Key_ConsoleReplace(va("alias %s %s", Cmd_Argv(1), quotedalias));
|
|
}
|
|
else
|
|
Con_Printf("Not an alias\n");
|
|
}
|
|
|
|
/*
|
|
===================
|
|
Key_Bind_f
|
|
===================
|
|
*/
|
|
void Key_Bind_f (void)
|
|
{
|
|
int i, c, b, modifier;
|
|
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), &modifier);
|
|
if (b==-1)
|
|
{
|
|
if (cl_warncmd.ival)
|
|
Con_Printf ("\"%s\" isn't a valid key\n", Cmd_Argv(1));
|
|
return;
|
|
}
|
|
|
|
if (c == 2)
|
|
{
|
|
if (modifier == ~0) //modifier unspecified. default to no modifier
|
|
modifier = 0;
|
|
if (keybindings[b][modifier])
|
|
{
|
|
char *alias = Cmd_AliasExist(keybindings[b][modifier], RESTRICT_LOCAL);
|
|
char quotedbind[2048];
|
|
char quotedalias[2048];
|
|
COM_QuotedString(keybindings[b][modifier], quotedbind, sizeof(quotedbind), false);
|
|
if (alias)
|
|
{
|
|
COM_QuotedString(alias, quotedalias, sizeof(quotedalias), false);
|
|
Con_Printf ("^[\"%s\"\\type\\bind %s %s^] = ^[\"%s\"\\type\\alias %s %s^]\n", Cmd_Argv(1), Cmd_Argv(1), quotedbind, keybindings[b][modifier], keybindings[b][modifier], quotedalias);
|
|
}
|
|
else
|
|
Con_Printf ("^[\"%s\"\\type\\bind %s %s^] = \"%s\"\n", Cmd_Argv(1), keybindings[b][modifier], Cmd_Argv(1), keybindings[b][modifier] );
|
|
}
|
|
else
|
|
Con_Printf ("\"%s\" is not bound\n", Cmd_Argv(1) );
|
|
return;
|
|
}
|
|
|
|
if (c > 3)
|
|
{
|
|
Cmd_ShiftArgs(1, Cmd_ExecLevel==RESTRICT_LOCAL);
|
|
Key_SetBinding (b, modifier, Cmd_Args(), Cmd_ExecLevel);
|
|
return;
|
|
}
|
|
|
|
// copy the rest of the command line
|
|
cmd[0] = 0; // start out with a null string
|
|
for (i=2 ; i< c ; i++)
|
|
{
|
|
Q_strncatz (cmd, Cmd_Argv(i), sizeof(cmd));
|
|
if (i != (c-1))
|
|
Q_strncatz (cmd, " ", sizeof(cmd));
|
|
}
|
|
|
|
Key_SetBinding (b, modifier, cmd, Cmd_ExecLevel);
|
|
}
|
|
|
|
void Key_BindLevel_f (void)
|
|
{
|
|
int i, c, b, modifier;
|
|
char cmd[1024];
|
|
|
|
c = Cmd_Argc();
|
|
|
|
if (c != 2 && c != 4)
|
|
{
|
|
Con_Printf ("%s <key> [<level> <command>] : attach a command to a key for a specific level of access\n", Cmd_Argv(0));
|
|
return;
|
|
}
|
|
b = Key_StringToKeynum (Cmd_Argv(1), &modifier);
|
|
if (b==-1)
|
|
{
|
|
if (cl_warncmd.ival)
|
|
Con_Printf ("\"%s\" isn't a valid key\n", Cmd_Argv(1));
|
|
return;
|
|
}
|
|
|
|
if (modifier == ~0) //modifier unspecified. default to no modifier
|
|
modifier = 0;
|
|
|
|
if (c == 2)
|
|
{
|
|
if (keybindings[b][modifier])
|
|
Con_Printf ("\"%s\" (%i)= \"%s\"\n", Cmd_Argv(1), bindcmdlevel[b][modifier], keybindings[b][modifier] );
|
|
else
|
|
Con_Printf ("\"%s\" is not bound\n", Cmd_Argv(1) );
|
|
return;
|
|
}
|
|
|
|
if (Cmd_IsInsecure())
|
|
{
|
|
Con_Printf("Server attempted usage of %s\n", Cmd_Argv(0));
|
|
return;
|
|
}
|
|
|
|
// copy the rest of the command line
|
|
cmd[0] = 0; // start out with a null string
|
|
for (i=3 ; i< c ; i++)
|
|
{
|
|
Q_strncatz (cmd, Cmd_Argv(i), sizeof(cmd));
|
|
if (i != (c-1))
|
|
Q_strncatz (cmd, " ", sizeof(cmd));
|
|
}
|
|
|
|
Key_SetBinding (b, modifier, cmd, atoi(Cmd_Argv(2)));
|
|
}
|
|
|
|
/*
|
|
============
|
|
Key_WriteBindings
|
|
|
|
Writes lines containing "bind key value"
|
|
============
|
|
*/
|
|
void Key_WriteBindings (vfsfile_t *f)
|
|
{
|
|
const char *s;
|
|
int i, m;
|
|
char *binding, *base;
|
|
|
|
char prefix[128];
|
|
char keybuf[256];
|
|
char commandbuf[2048];
|
|
|
|
for (i=0 ; i<K_MAX ; i++) //we rebind the key with all modifiers to get the standard bind, then change the specific ones.
|
|
{ //this does two things, it normally allows us to skip 7 of the 8 possibilities
|
|
base = keybindings[i][0]; //plus we can use the config with other clients.
|
|
if (!base)
|
|
base = "";
|
|
for (m = 0; m < KEY_MODIFIERSTATES; m++)
|
|
{
|
|
binding = keybindings[i][m];
|
|
if (!binding)
|
|
binding = "";
|
|
if (strcmp(binding, base) || (m==0 && keybindings[i][0]) || bindcmdlevel[i][m] != bindcmdlevel[i][0])
|
|
{
|
|
*prefix = '\0';
|
|
if (m & KEY_MODIFIER_CTRL)
|
|
strcat(prefix, "CTRL_");
|
|
if (m & KEY_MODIFIER_ALT)
|
|
strcat(prefix, "ALT_");
|
|
if (m & KEY_MODIFIER_SHIFT)
|
|
strcat(prefix, "SHIFT_");
|
|
|
|
s = va("%s%s", prefix, Key_KeynumToString(i));
|
|
//quote it as required
|
|
if (i == ';' || i <= ' ' || i == '\"')
|
|
s = COM_QuotedString(s, keybuf, sizeof(keybuf), false);
|
|
|
|
if (bindcmdlevel[i][m] != bindcmdlevel[i][0])
|
|
s = va("bindlevel %s %i %s\n", s, bindcmdlevel[i][m], COM_QuotedString(binding, commandbuf, sizeof(commandbuf), false));
|
|
else
|
|
s = va("bind %s %s\n", s, COM_QuotedString(binding, commandbuf, sizeof(commandbuf), false));
|
|
VFS_WRITE(f, s, strlen(s));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
===================
|
|
Key_Init
|
|
===================
|
|
*/
|
|
void Key_Init (void)
|
|
{
|
|
int i;
|
|
|
|
for (i=0 ; i<=CON_EDIT_LINES_MASK ; i++)
|
|
{
|
|
key_lines[i] = Z_Malloc(2);
|
|
key_lines[i][0] = ']';
|
|
}
|
|
key_linepos = 1;
|
|
|
|
key_dest_mask = kdm_game;
|
|
key_dest_absolutemouse = kdm_console | kdm_editor | kdm_cwindows;
|
|
|
|
//
|
|
// init 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_RIGHTARROW] = true;
|
|
consolekeys[K_UPARROW] = true;
|
|
consolekeys[K_DOWNARROW] = true;
|
|
consolekeys[K_KP_LEFTARROW] = true;
|
|
consolekeys[K_KP_RIGHTARROW] = true;
|
|
consolekeys[K_KP_UPARROW] = true;
|
|
consolekeys[K_KP_DOWNARROW] = true;
|
|
consolekeys[K_BACKSPACE] = true;
|
|
consolekeys[K_DEL] = true;
|
|
consolekeys[K_KP_DEL] = 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_LSHIFT] = true;
|
|
consolekeys[K_RSHIFT] = true;
|
|
consolekeys[K_MWHEELUP] = true;
|
|
consolekeys[K_MWHEELDOWN] = true;
|
|
consolekeys[K_LCTRL] = true;
|
|
consolekeys[K_RCTRL] = true;
|
|
consolekeys[K_LALT] = true;
|
|
consolekeys[K_RALT] = true;
|
|
consolekeys['`'] = false;
|
|
consolekeys['~'] = false;
|
|
|
|
for (i=K_MOUSE1 ; i<K_MOUSE10 ; i++)
|
|
{
|
|
consolekeys[i] = true;
|
|
}
|
|
consolekeys[K_MWHEELUP] = true;
|
|
consolekeys[K_MWHEELDOWN] = true;
|
|
|
|
for (i=0 ; i<K_MAX ; 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 ("bindlevel",Key_BindLevel_f);
|
|
Cmd_AddCommand ("unbind",Key_Unbind_f);
|
|
Cmd_AddCommand ("unbindall",Key_Unbindall_f);
|
|
Cmd_AddCommand ("aliasedit",Key_AliasEdit_f);
|
|
|
|
Cvar_Register (&con_selectioncolour, "Console variables");
|
|
Cvar_Register (&con_echochat, "Console variables");
|
|
}
|
|
|
|
qboolean Key_MouseShouldBeFree(void)
|
|
{
|
|
//returns if the mouse should be a cursor or if it should go to the menu
|
|
|
|
//if true, the input code is expected to return mouse cursor positions rather than deltas
|
|
extern cvar_t cl_prydoncursor;
|
|
if (key_dest_absolutemouse & key_dest_mask)
|
|
return true;
|
|
|
|
if (Key_Dest_Has(kdm_editor))
|
|
return true;
|
|
|
|
// if (!ActiveApp)
|
|
// return true;
|
|
|
|
if (Key_Dest_Has(kdm_menu))
|
|
{
|
|
if (m_state == m_complex || m_state == m_plugin /*|| m_state == m_menu_dat*/)
|
|
return true;
|
|
}
|
|
|
|
#ifdef VM_UI
|
|
if (UI_MenuState())
|
|
return false;
|
|
#endif
|
|
|
|
if (Media_PlayingFullScreen())
|
|
return true;
|
|
|
|
if (cl_prydoncursor.ival)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
int Sbar_TranslateHudClick(void);
|
|
/*
|
|
===================
|
|
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 devid, int key, unsigned int unicode, qboolean down)
|
|
{
|
|
int bl, bkey;
|
|
char *dc, *uc;
|
|
char p[16];
|
|
int modifierstate;
|
|
int conkey = consolekeys[key] || ((unicode || key == '`') && (key != '`' || key_linepos>1)); //if the input line is empty, allow ` to toggle the console, otherwise enter it as actual text.
|
|
|
|
// Con_Printf ("%i : %i : %i\n", key, unicode, down); //@@@
|
|
|
|
//bug: my keyboard doesn't fire release events if the other shift is already pressed.
|
|
//hack around that by just force-releasing eg left if right is pressed, but only on inital press to avoid potential infinite loops if the state got bad.
|
|
//ctrl+alt don't seem to have the problem.
|
|
//you can still see a difference in that
|
|
if (key == K_LSHIFT && !keydown[K_LSHIFT] && keydown[K_RSHIFT])
|
|
Key_Event(devid, K_RSHIFT, 0, false);
|
|
if (key == K_RSHIFT && !keydown[K_RSHIFT] && keydown[K_LSHIFT])
|
|
Key_Event(devid, K_LSHIFT, 0, false);
|
|
|
|
modifierstate = KeyModifier(keydown[K_LSHIFT]|keydown[K_RSHIFT], keydown[K_LALT]|keydown[K_RALT], keydown[K_LCTRL]|keydown[K_RCTRL]);
|
|
|
|
keydown[key] = down;
|
|
|
|
if (!down)
|
|
key_repeats[key] = 0;
|
|
|
|
key_lastpress = key;
|
|
key_count++;
|
|
if (key_count <= 0)
|
|
{
|
|
return; // just catching keys for Con_NotifyBox
|
|
}
|
|
|
|
// update auto-repeat status
|
|
if (down)
|
|
{
|
|
key_repeats[key]++;
|
|
|
|
// if (key >= 200 && !keybindings[key]) //is this too annoying?
|
|
// Con_Printf ("%s is unbound, hit F4 to set.\n", Key_KeynumToString (key) );
|
|
}
|
|
|
|
if (key == K_LSHIFT || key == K_RSHIFT)
|
|
{
|
|
shift_down = keydown[K_LSHIFT]|keydown[K_RSHIFT];
|
|
}
|
|
|
|
if (key == K_ESCAPE)
|
|
if (shift_down)
|
|
{
|
|
extern cvar_t con_stayhidden;
|
|
if (down && con_stayhidden.ival < 2)
|
|
{
|
|
if (!Key_Dest_Has(kdm_console)) //don't toggle it when the console is already down. this allows typing blind to not care if its already active.
|
|
Con_ToggleConsole_Force();
|
|
return;
|
|
}
|
|
}
|
|
|
|
//yes, csqc is allowed to steal the escape key.
|
|
if (key != '`' && (!down || key != K_ESCAPE || (!Key_Dest_Has(~kdm_game) && !shift_down)))
|
|
if (!Key_Dest_Has(~kdm_game) && !Media_PlayingFullScreen())
|
|
{
|
|
#ifdef CSQC_DAT
|
|
if (CSQC_KeyPress(key, unicode, down, devid)) //give csqc a chance to handle it.
|
|
return;
|
|
#endif
|
|
#ifdef VM_CG
|
|
if (CG_KeyPress(key, unicode, down))
|
|
return;
|
|
#endif
|
|
}
|
|
|
|
//
|
|
// handle escape specialy, so the user can never unbind it
|
|
//
|
|
if (key == K_ESCAPE)
|
|
{
|
|
#ifdef VM_UI
|
|
#ifdef TEXTEDITOR
|
|
if (!Key_Dest_Has(~kdm_game) && !Key_Dest_Has(kdm_console))
|
|
#endif
|
|
{
|
|
if (down && Media_PlayingFullScreen())
|
|
{
|
|
Media_StopFilm(false);
|
|
return;
|
|
}
|
|
if (UI_KeyPress(key, unicode, down)) //Allow the UI to see the escape key. It is possible that a developer may get stuck at a menu.
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (!down)
|
|
return;
|
|
|
|
if (Key_Dest_Has(kdm_console))
|
|
{
|
|
Key_Dest_Remove(kdm_console);
|
|
Key_Dest_Remove(kdm_cwindows);
|
|
if (!cls.state && !Key_Dest_Has(~kdm_game) && !Media_PlayingFullScreen())
|
|
M_ToggleMenu_f ();
|
|
}
|
|
else if (Key_Dest_Has(kdm_cwindows))
|
|
{
|
|
Key_Dest_Remove(kdm_cwindows);
|
|
if (!cls.state && !Key_Dest_Has(~kdm_game) && !Media_PlayingFullScreen())
|
|
M_ToggleMenu_f ();
|
|
}
|
|
#ifdef TEXTEDITOR
|
|
else if (Key_Dest_Has(kdm_editor))
|
|
Editor_Key (key, unicode);
|
|
#endif
|
|
else if (Key_Dest_Has(kdm_menu))
|
|
M_Keydown (key, unicode);
|
|
else if (Key_Dest_Has(kdm_message))
|
|
Key_Dest_Remove(kdm_message);
|
|
else
|
|
{
|
|
if (Media_PlayingFullScreen())
|
|
{
|
|
Media_StopFilm(true);
|
|
if (!cls.state)
|
|
M_ToggleMenu_f ();
|
|
}
|
|
else
|
|
M_ToggleMenu_f ();
|
|
}
|
|
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 keynum as a parameter, so multiple
|
|
// downs can be matched with ups
|
|
//
|
|
if (!down)
|
|
{
|
|
if (Key_Dest_Has(kdm_console|kdm_cwindows))
|
|
{
|
|
console_t *con = Key_Dest_Has(kdm_console)?con_current:con_curwindow;
|
|
if (con_mouseover && key >= K_MOUSE1 && key <= K_MWHEELDOWN)
|
|
con = con_mouseover;
|
|
if (con_curwindow && con_curwindow != con)
|
|
con_curwindow->buttonsdown = CB_NONE;
|
|
if (con)
|
|
{
|
|
con->mousecursor[0] = mousecursor_x - ((con->flags & CONF_ISWINDOW)?con->wnd_x:0);
|
|
con->mousecursor[1] = mousecursor_y - ((con->flags & CONF_ISWINDOW)?con->wnd_y:0);
|
|
Key_ConsoleRelease(con, key, unicode);
|
|
}
|
|
}
|
|
if (Key_Dest_Has(kdm_menu))
|
|
M_Keyup (key, unicode);
|
|
#ifndef NOMEDIA
|
|
if (Media_PlayingFullScreen())
|
|
Media_Send_KeyEvent(NULL, key, unicode, down?0:1);
|
|
#endif
|
|
|
|
uc = releasecommand[key][devid%MAX_INDEVS];
|
|
if (uc) //this wasn't down, so don't crash on bad commands.
|
|
{
|
|
releasecommand[key][devid%MAX_INDEVS] = NULL;
|
|
Cbuf_AddText (uc, releasecommandlevel[key][devid%MAX_INDEVS]);
|
|
Z_Free(uc);
|
|
}
|
|
return;
|
|
}
|
|
|
|
//
|
|
// during demo playback, most keys bring up the main menu
|
|
//
|
|
if (cls.demoplayback && cls.demoplayback != DPB_MVD && cls.demoplayback != DPB_EZTV && down && conkey && !Key_Dest_Has(~kdm_game))
|
|
{
|
|
switch (key)
|
|
{ //these keys don't force the menu to appear while playing the demo reel
|
|
case K_LSHIFT:
|
|
case K_RSHIFT:
|
|
case K_LALT:
|
|
case K_RALT:
|
|
case K_LCTRL:
|
|
// case K_RCTRL:
|
|
break;
|
|
default:
|
|
dc = keybindings[key][modifierstate];
|
|
//toggleconsole or +showFOO keys should do their regular bind action
|
|
if (!dc || (strcmp(dc, "toggleconsole") && strncmp(dc, "+show", 5)))
|
|
{
|
|
M_ToggleMenu_f ();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// if not a consolekey, send to the interpreter no matter what mode is
|
|
//
|
|
#ifdef VM_UI
|
|
if (key != '`' && key != '~')
|
|
if (!Key_Dest_Has(~kdm_game) || !down)
|
|
{
|
|
if (UI_KeyPress(key, unicode, down) && down) //UI is allowed to take these keydowns. Keyups are always maintained.
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
|
|
if (conkey && Key_Dest_Has(kdm_console|kdm_cwindows))
|
|
{
|
|
console_t *con = Key_Dest_Has(kdm_console)?con_current:con_curwindow;
|
|
if ((con_mouseover||!Key_Dest_Has(kdm_console)) && key >= K_MOUSE1 && key <= K_MWHEELDOWN)
|
|
con = con_mouseover;
|
|
if (con)
|
|
{
|
|
con->mousecursor[0] = mousecursor_x - ((con->flags & CONF_ISWINDOW)?con->wnd_x:0);
|
|
con->mousecursor[1] = mousecursor_y - ((con->flags & CONF_ISWINDOW)?con->wnd_y:0);
|
|
if (Key_Console (con, unicode, key))
|
|
return;
|
|
}
|
|
else
|
|
Key_Dest_Remove(kdm_cwindows);
|
|
|
|
}
|
|
#ifdef TEXTEDITOR
|
|
if (Key_Dest_Has(kdm_editor))
|
|
{
|
|
Editor_Key (key, unicode);
|
|
return;
|
|
}
|
|
#endif
|
|
if (Key_Dest_Has(kdm_menu))
|
|
{
|
|
M_Keydown (key, unicode);
|
|
return;
|
|
}
|
|
if (Key_Dest_Has(kdm_message))
|
|
{
|
|
Key_Message (key, unicode);
|
|
return;
|
|
}
|
|
#ifndef NOMEDIA
|
|
if (Media_PlayingFullScreen())
|
|
{
|
|
Media_Send_KeyEvent(NULL, key, unicode, down?0:1);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
//anything else is a key binding.
|
|
|
|
/*don't auto-repeat binds as it breaks too many scripts*/
|
|
if (key_repeats[key] > 1)
|
|
return;
|
|
|
|
//first player is normally assumed anyway.
|
|
if (devid)
|
|
Q_snprintfz (p, sizeof(p), "p %i ", devid+1);
|
|
else
|
|
*p = 0;
|
|
|
|
dc = keybindings[key][modifierstate];
|
|
bl = bindcmdlevel[key][modifierstate];
|
|
|
|
//simulate singular shift+alt+ctrl for binds (no left/right). really though, this code should translate to csqc/menu keycodes and back to resolve the weirdness instead.
|
|
if (key == K_RALT && (!dc || !*dc))
|
|
{
|
|
bkey = K_LALT;
|
|
dc = keybindings[bkey][modifierstate];
|
|
bl = bindcmdlevel[bkey][modifierstate];
|
|
}
|
|
else if (key == K_RCTRL && (!dc || !*dc))
|
|
{
|
|
bkey = K_LCTRL;
|
|
dc = keybindings[bkey][modifierstate];
|
|
bl = bindcmdlevel[bkey][modifierstate];
|
|
}
|
|
else if (key == K_RSHIFT && (!dc || !*dc))
|
|
{
|
|
bkey = K_LSHIFT;
|
|
dc = keybindings[bkey][modifierstate];
|
|
bl = bindcmdlevel[bkey][modifierstate];
|
|
}
|
|
else
|
|
bkey = key;
|
|
|
|
if (key == K_MOUSE1 && IN_MouseDevIsTouch(devid))
|
|
{
|
|
char *button = SCR_ShowPics_ClickCommand(mousecursor_x, mousecursor_y);
|
|
if (button)
|
|
{
|
|
dc = button;
|
|
bl = RESTRICT_INSECURE;
|
|
}
|
|
else
|
|
{
|
|
int bkey = Sbar_TranslateHudClick();
|
|
if (bkey)
|
|
{
|
|
dc = keybindings[bkey][modifierstate];
|
|
bl = bindcmdlevel[bkey][modifierstate];
|
|
}
|
|
else
|
|
{
|
|
bkey = IN_TranslateMButtonPress(devid);
|
|
if (bkey)
|
|
{
|
|
dc = keybindings[bkey][modifierstate];
|
|
bl = bindcmdlevel[bkey][modifierstate];
|
|
}
|
|
else if (!Key_MouseShouldBeFree())
|
|
{
|
|
key_repeats[key] = 0;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (dc)
|
|
{
|
|
if (dc[0] == '+')
|
|
{
|
|
uc = va("-%s%s %i\n", p, dc+1, bkey);
|
|
dc = va("+%s%s %i\n", p, dc+1, bkey);
|
|
}
|
|
else
|
|
{
|
|
uc = NULL;
|
|
dc = va("%s%s\n", p, dc);
|
|
}
|
|
}
|
|
else
|
|
uc = NULL;
|
|
|
|
//don't mess up if we ran out of devices, just silently release the one that it conflicted with (and only if its in conflict).
|
|
if (releasecommand[key][devid%MAX_INDEVS] && (!uc || strcmp(uc, releasecommand[key][devid%MAX_INDEVS])))
|
|
{
|
|
Cbuf_AddText (releasecommand[key][devid%MAX_INDEVS], releasecommandlevel[key][devid%MAX_INDEVS]);
|
|
Z_Free(releasecommand[key][devid%MAX_INDEVS]);
|
|
releasecommand[key][devid%MAX_INDEVS] = NULL;
|
|
}
|
|
if (dc)
|
|
Cbuf_AddText (dc, bl);
|
|
if (uc)
|
|
releasecommand[key][devid%MAX_INDEVS] = Z_StrDup(uc);
|
|
releasecommandlevel[key][devid%MAX_INDEVS] = bl;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
Key_ClearStates
|
|
===================
|
|
*/
|
|
void Key_ClearStates (void)
|
|
{
|
|
int i;
|
|
|
|
for (i=0 ; i<K_MAX ; i++)
|
|
{
|
|
keydown[i] = false;
|
|
key_repeats[i] = false;
|
|
}
|
|
}
|
|
|