]> icculus.org git repositories - divverent/darkplaces.git/blob - keys.c
cl_capturevideo_framestep
[divverent/darkplaces.git] / keys.c
1 /*
2         Copyright (C) 1996-1997  Id Software, Inc.
3
4         This program is free software; you can redistribute it and/or
5         modify it under the terms of the GNU General Public License
6         as published by the Free Software Foundation; either version 2
7         of the License, or (at your option) any later version.
8
9         This program is distributed in the hope that it will be useful,
10         but WITHOUT ANY WARRANTY; without even the implied warranty of
11         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13         See the GNU General Public License for more details.
14
15         You should have received a copy of the GNU General Public License
16         along with this program; if not, write to:
17
18                 Free Software Foundation, Inc.
19                 59 Temple Place - Suite 330
20                 Boston, MA  02111-1307, USA
21 */
22
23 #include "quakedef.h"
24 #include "cl_video.h"
25
26 cvar_t con_closeontoggleconsole = {CVAR_SAVE, "con_closeontoggleconsole","1", "allows toggleconsole binds to close the console as well"};
27
28 /*
29 key up events are sent even if in console mode
30 */
31
32 char            key_line[MAX_INPUTLINE];
33 int                     key_linepos;
34 qboolean        key_insert = true;      // insert key toggle (for editing)
35 keydest_t       key_dest;
36 int                     key_consoleactive;
37 char            *keybindings[MAX_BINDMAPS][MAX_KEYS];
38 int         history_line;
39 char            history_savedline[MAX_INPUTLINE];
40 conbuffer_t history;
41 #define HIST_TEXTSIZE 262144
42 #define HIST_MAXLINES 4096
43
44 extern cvar_t   con_textsize;
45
46
47 static void Key_History_Init()
48 {
49         qfile_t *historyfile;
50         ConBuffer_Init(&history, HIST_TEXTSIZE, HIST_MAXLINES, zonemempool);
51
52         historyfile = FS_OpenRealFile("darkplaces_history.txt", "rb", false); // rb to handle unix line endings on windows too
53         if(historyfile)
54         {
55                 char buf[MAX_INPUTLINE];
56                 int bufpos;
57                 int c;
58
59                 bufpos = 0;
60                 for(;;)
61                 {
62                         c = FS_Getc(historyfile);
63                         if(c < 0 || c == 0 || c == '\r' || c == '\n')
64                         {
65                                 if(bufpos > 0)
66                                 {
67                                         buf[bufpos] = 0;
68                                         ConBuffer_AddLine(&history, buf, bufpos, 0);
69                                         bufpos = 0;
70                                 }
71                                 if(c < 0)
72                                         break;
73                         }
74                         else
75                         {
76                                 if(bufpos < MAX_INPUTLINE - 1)
77                                         buf[bufpos++] = c;
78                         }
79                 }
80
81                 FS_Close(historyfile);
82         }
83
84         history_line = -1;
85 }
86
87 static void Key_History_Shutdown()
88 {
89         // TODO write history to a file
90
91         qfile_t *historyfile = FS_OpenRealFile("darkplaces_history.txt", "w", false);
92         if(historyfile)
93         {
94                 int i;
95                 for(i = 0; i < CONBUFFER_LINES_COUNT(&history); ++i)
96                         FS_Printf(historyfile, "%s\n", ConBuffer_GetLine(&history, i));
97                 FS_Close(historyfile);
98         }
99
100         ConBuffer_Shutdown(&history);
101 }
102
103 static void Key_History_Push()
104 {
105         if(key_line[1]) // empty?
106         if(strcmp(key_line, "]quit")) // putting these into the history just sucks
107         if(strncmp(key_line, "]quit ", 6)) // putting these into the history just sucks
108                 ConBuffer_AddLine(&history, key_line + 1, strlen(key_line) - 1, 0);
109         Con_Printf("%s\n", key_line); // don't mark empty lines as history
110         history_line = -1;
111 }
112
113 static void Key_History_Up()
114 {
115         if(history_line == -1) // editing the "new" line
116                 strlcpy(history_savedline, key_line + 1, sizeof(history_savedline));
117
118         if(history_line == -1)
119         {
120                 history_line = CONBUFFER_LINES_COUNT(&history) - 1;
121                 if(history_line != -1)
122                 {
123                         strlcpy(key_line + 1, ConBuffer_GetLine(&history, history_line), sizeof(key_line) - 1);
124                         key_linepos = strlen(key_line);
125                 }
126         }
127         else if(history_line > 0)
128         {
129                 --history_line; // this also does -1 -> 0, so it is good
130                 strlcpy(key_line + 1, ConBuffer_GetLine(&history, history_line), sizeof(key_line) - 1);
131                 key_linepos = strlen(key_line);
132         }
133 }
134
135 static void Key_History_Down()
136 {
137         if(history_line == -1) // editing the "new" line
138                 return;
139
140         if(history_line < CONBUFFER_LINES_COUNT(&history) - 1)
141         {
142                 ++history_line;
143                 strlcpy(key_line + 1, ConBuffer_GetLine(&history, history_line), sizeof(key_line) - 1);
144         }
145         else
146         {
147                 history_line = -1;
148                 strlcpy(key_line + 1, history_savedline, sizeof(key_line) - 1);
149         }
150
151         key_linepos = strlen(key_line);
152 }
153
154 static int      key_bmap, key_bmap2;
155 static unsigned char keydown[MAX_KEYS]; // 0 = up, 1 = down, 2 = repeating
156
157 typedef struct keyname_s
158 {
159         const char      *name;
160         int                     keynum;
161 }
162 keyname_t;
163
164 static const keyname_t   keynames[] = {
165         {"TAB", K_TAB},
166         {"ENTER", K_ENTER},
167         {"ESCAPE", K_ESCAPE},
168         {"SPACE", K_SPACE},
169
170         // spacer so it lines up with keys.h
171
172         {"BACKSPACE", K_BACKSPACE},
173         {"UPARROW", K_UPARROW},
174         {"DOWNARROW", K_DOWNARROW},
175         {"LEFTARROW", K_LEFTARROW},
176         {"RIGHTARROW", K_RIGHTARROW},
177
178         {"ALT", K_ALT},
179         {"CTRL", K_CTRL},
180         {"SHIFT", K_SHIFT},
181
182         {"F1", K_F1},
183         {"F2", K_F2},
184         {"F3", K_F3},
185         {"F4", K_F4},
186         {"F5", K_F5},
187         {"F6", K_F6},
188         {"F7", K_F7},
189         {"F8", K_F8},
190         {"F9", K_F9},
191         {"F10", K_F10},
192         {"F11", K_F11},
193         {"F12", K_F12},
194
195         {"INS", K_INS},
196         {"DEL", K_DEL},
197         {"PGDN", K_PGDN},
198         {"PGUP", K_PGUP},
199         {"HOME", K_HOME},
200         {"END", K_END},
201
202         {"PAUSE", K_PAUSE},
203
204         {"NUMLOCK", K_NUMLOCK},
205         {"CAPSLOCK", K_CAPSLOCK},
206         {"SCROLLOCK", K_SCROLLOCK},
207
208         {"KP_INS",                      K_KP_INS },
209         {"KP_0", K_KP_0},
210         {"KP_END",                      K_KP_END },
211         {"KP_1", K_KP_1},
212         {"KP_DOWNARROW",        K_KP_DOWNARROW },
213         {"KP_2", K_KP_2},
214         {"KP_PGDN",                     K_KP_PGDN },
215         {"KP_3", K_KP_3},
216         {"KP_LEFTARROW",        K_KP_LEFTARROW },
217         {"KP_4", K_KP_4},
218         {"KP_5", K_KP_5},
219         {"KP_RIGHTARROW",       K_KP_RIGHTARROW },
220         {"KP_6", K_KP_6},
221         {"KP_HOME",                     K_KP_HOME },
222         {"KP_7", K_KP_7},
223         {"KP_UPARROW",          K_KP_UPARROW },
224         {"KP_8", K_KP_8},
225         {"KP_PGUP",                     K_KP_PGUP },
226         {"KP_9", K_KP_9},
227         {"KP_DEL",                      K_KP_DEL },
228         {"KP_PERIOD", K_KP_PERIOD},
229         {"KP_SLASH",            K_KP_SLASH },
230         {"KP_DIVIDE", K_KP_DIVIDE},
231         {"KP_MULTIPLY", K_KP_MULTIPLY},
232         {"KP_MINUS", K_KP_MINUS},
233         {"KP_PLUS", K_KP_PLUS},
234         {"KP_ENTER", K_KP_ENTER},
235         {"KP_EQUALS", K_KP_EQUALS},
236
237
238
239         {"MOUSE1", K_MOUSE1},
240
241         {"MOUSE2", K_MOUSE2},
242         {"MOUSE3", K_MOUSE3},
243         {"MWHEELUP", K_MWHEELUP},
244         {"MWHEELDOWN", K_MWHEELDOWN},
245         {"MOUSE4", K_MOUSE4},
246         {"MOUSE5", K_MOUSE5},
247         {"MOUSE6", K_MOUSE6},
248         {"MOUSE7", K_MOUSE7},
249         {"MOUSE8", K_MOUSE8},
250         {"MOUSE9", K_MOUSE9},
251         {"MOUSE10", K_MOUSE10},
252         {"MOUSE11", K_MOUSE11},
253         {"MOUSE12", K_MOUSE12},
254         {"MOUSE13", K_MOUSE13},
255         {"MOUSE14", K_MOUSE14},
256         {"MOUSE15", K_MOUSE15},
257         {"MOUSE16", K_MOUSE16},
258
259
260
261
262         {"JOY1",  K_JOY1},
263         {"JOY2",  K_JOY2},
264         {"JOY3",  K_JOY3},
265         {"JOY4",  K_JOY4},
266         {"JOY5",  K_JOY5},
267         {"JOY6",  K_JOY6},
268         {"JOY7",  K_JOY7},
269         {"JOY8",  K_JOY8},
270         {"JOY9",  K_JOY9},
271         {"JOY10", K_JOY10},
272         {"JOY11", K_JOY11},
273         {"JOY12", K_JOY12},
274         {"JOY13", K_JOY13},
275         {"JOY14", K_JOY14},
276         {"JOY15", K_JOY15},
277         {"JOY16", K_JOY16},
278
279
280
281
282
283
284         {"AUX1", K_AUX1},
285         {"AUX2", K_AUX2},
286         {"AUX3", K_AUX3},
287         {"AUX4", K_AUX4},
288         {"AUX5", K_AUX5},
289         {"AUX6", K_AUX6},
290         {"AUX7", K_AUX7},
291         {"AUX8", K_AUX8},
292         {"AUX9", K_AUX9},
293         {"AUX10", K_AUX10},
294         {"AUX11", K_AUX11},
295         {"AUX12", K_AUX12},
296         {"AUX13", K_AUX13},
297         {"AUX14", K_AUX14},
298         {"AUX15", K_AUX15},
299         {"AUX16", K_AUX16},
300         {"AUX17", K_AUX17},
301         {"AUX18", K_AUX18},
302         {"AUX19", K_AUX19},
303         {"AUX20", K_AUX20},
304         {"AUX21", K_AUX21},
305         {"AUX22", K_AUX22},
306         {"AUX23", K_AUX23},
307         {"AUX24", K_AUX24},
308         {"AUX25", K_AUX25},
309         {"AUX26", K_AUX26},
310         {"AUX27", K_AUX27},
311         {"AUX28", K_AUX28},
312         {"AUX29", K_AUX29},
313         {"AUX30", K_AUX30},
314         {"AUX31", K_AUX31},
315         {"AUX32", K_AUX32},
316
317         {"SEMICOLON", ';'},                     // because a raw semicolon separates commands
318         {"TILDE", '~'},
319         {"BACKQUOTE", '`'},
320         {"QUOTE", '"'},
321         {"APOSTROPHE", '\''},
322         {"BACKSLASH", '\\'},            // because a raw backslash is used for special characters
323
324         {NULL, 0}
325 };
326
327 /*
328 ==============================================================================
329
330                         LINE TYPING INTO THE CONSOLE
331
332 ==============================================================================
333 */
334
335 void
336 Key_ClearEditLine (int edit_line)
337 {
338         memset (key_line, '\0', sizeof(key_line));
339         key_line[0] = ']';
340         key_linepos = 1;
341 }
342
343 /*
344 ====================
345 Interactive line editing and console scrollback
346 ====================
347 */
348 static void
349 Key_Console (int key, int ascii)
350 {
351         // LordHavoc: copied most of this from Q2 to improve keyboard handling
352         switch (key)
353         {
354         case K_KP_SLASH:
355                 key = '/';
356                 break;
357         case K_KP_MINUS:
358                 key = '-';
359                 break;
360         case K_KP_PLUS:
361                 key = '+';
362                 break;
363         case K_KP_HOME:
364                 key = '7';
365                 break;
366         case K_KP_UPARROW:
367                 key = '8';
368                 break;
369         case K_KP_PGUP:
370                 key = '9';
371                 break;
372         case K_KP_LEFTARROW:
373                 key = '4';
374                 break;
375         case K_KP_5:
376                 key = '5';
377                 break;
378         case K_KP_RIGHTARROW:
379                 key = '6';
380                 break;
381         case K_KP_END:
382                 key = '1';
383                 break;
384         case K_KP_DOWNARROW:
385                 key = '2';
386                 break;
387         case K_KP_PGDN:
388                 key = '3';
389                 break;
390         case K_KP_INS:
391                 key = '0';
392                 break;
393         case K_KP_DEL:
394                 key = '.';
395                 break;
396         }
397
398         if ((key == 'v' && keydown[K_CTRL]) || ((key == K_INS || key == K_KP_INS) && keydown[K_SHIFT]))
399         {
400                 char *cbd, *p;
401                 if ((cbd = Sys_GetClipboardData()) != 0)
402                 {
403                         int i;
404 #if 1
405                         p = cbd;
406                         while (*p)
407                         {
408                                 if (*p == '\r' && *(p+1) == '\n')
409                                 {
410                                         *p++ = ';';
411                                         *p++ = ' ';
412                                 }
413                                 else if (*p == '\n' || *p == '\r' || *p == '\b')
414                                         *p++ = ';';
415                                 p++;
416                         }
417 #else
418                         strtok(cbd, "\n\r\b");
419 #endif
420                         i = (int)strlen(cbd);
421                         if (i + key_linepos >= MAX_INPUTLINE)
422                                 i= MAX_INPUTLINE - key_linepos - 1;
423                         if (i > 0)
424                         {
425                                 // terencehill: insert the clipboard text between the characters of the line
426                                 char *temp = (char *) Z_Malloc(MAX_INPUTLINE);
427                                 cbd[i]=0;
428                                 temp[0]=0;
429                                 if ( key_linepos < (int)strlen(key_line) )
430                                         strlcpy(temp, key_line + key_linepos, (int)strlen(key_line) - key_linepos +1);
431                                 key_line[key_linepos] = 0;
432                                 strlcat(key_line, cbd, sizeof(key_line));
433                                 if (temp[0])
434                                         strlcat(key_line, temp, sizeof(key_line));
435                                 Z_Free(temp);
436                                 key_linepos += i;
437                         }
438                         Z_Free(cbd);
439                 }
440                 return;
441         }
442
443         if (key == 'l' && keydown[K_CTRL])
444         {
445                 Cbuf_AddText ("clear\n");
446                 return;
447         }
448
449         if (key == 'u' && keydown[K_CTRL]) // like vi/readline ^u: delete currently edited line
450         {
451                 // clear line
452                 key_line[0] = ']';
453                 key_line[1] = 0;
454                 key_linepos = 1;
455                 return;
456         }
457
458         if (key == 'q' && keydown[K_CTRL]) // like zsh ^q: push line to history, don't execute, and clear
459         {
460                 // clear line
461                 Key_History_Push();
462                 key_line[0] = ']';
463                 key_line[1] = 0;
464                 key_linepos = 1;
465                 return;
466         }
467
468         if (key == K_ENTER || key == K_KP_ENTER)
469         {
470                 Cbuf_AddText (key_line+1);      // skip the ]
471                 Cbuf_AddText ("\n");
472                 Key_History_Push();
473                 key_line[0] = ']';
474                 key_line[1] = 0;        // EvilTypeGuy: null terminate
475                 key_linepos = 1;
476                 // force an update, because the command may take some time
477                 if (cls.state == ca_disconnected)
478                         CL_UpdateScreen ();
479                 return;
480         }
481
482         if (key == K_TAB)
483         {
484                 if(keydown[K_CTRL]) // append to the cvar its value
485                 {
486                         int             cvar_len, cvar_str_len, chars_to_move;
487                         char    k;
488                         char    cvar[MAX_INPUTLINE];
489                         const char *cvar_str;
490                         
491                         // go to the start of the variable
492                         while(--key_linepos)
493                         {
494                                 k = key_line[key_linepos];
495                                 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
496                                         break;
497                         }
498                         key_linepos++;
499                         
500                         // save the variable name in cvar
501                         for(cvar_len=0; (k = key_line[key_linepos + cvar_len]) != 0; cvar_len++)
502                         {
503                                 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
504                                         break;
505                                 cvar[cvar_len] = k;
506                         }
507                         if (cvar_len==0)
508                                 return;
509                         cvar[cvar_len] = 0;
510                         
511                         // go to the end of the cvar
512                         key_linepos += cvar_len;
513                         
514                         // save the content of the variable in cvar_str
515                         cvar_str = Cvar_VariableString(cvar);
516                         cvar_str_len = strlen(cvar_str);
517                         if (cvar_str_len==0)
518                                 return;
519                         
520                         // insert space and cvar_str in key_line
521                         chars_to_move = strlen(&key_line[key_linepos]);
522                         if (key_linepos + 1 + cvar_str_len + chars_to_move < MAX_INPUTLINE)
523                         {
524                                 if (chars_to_move)
525                                         memmove(&key_line[key_linepos + 1 + cvar_str_len], &key_line[key_linepos], chars_to_move);
526                                 key_line[key_linepos++] = ' ';
527                                 memcpy(&key_line[key_linepos], cvar_str, cvar_str_len);
528                                 key_linepos += cvar_str_len;
529                                 key_line[key_linepos + chars_to_move] = 0;
530                         }
531                         else
532                                 Con_Printf("Couldn't append cvar value, edit line too long.\n");
533                         return;
534                 }
535                 // Enhanced command completion
536                 // by EvilTypeGuy eviltypeguy@qeradiant.com
537                 // Thanks to Fett, Taniwha
538                 Con_CompleteCommandLine();
539                 return;
540         }
541
542         // Advanced Console Editing by Radix radix@planetquake.com
543         // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
544         // Enhanced by [515]
545         // Enhanced by terencehill
546
547         // move cursor to the previous character
548         if (key == K_LEFTARROW || key == K_KP_LEFTARROW)
549         {
550                 if (key_linepos < 2)
551                         return;
552                 if(keydown[K_CTRL]) // move cursor to the previous word
553                 {
554                         int             pos;
555                         char    k;
556                         pos = key_linepos-1;
557
558                         if(pos) // skip all "; ' after the word
559                                 while(--pos)
560                                 {
561                                         k = key_line[pos];
562                                         if (!(k == '\"' || k == ';' || k == ' ' || k == '\''))
563                                                 break;
564                                 }
565
566                         if(pos)
567                                 while(--pos)
568                                 {
569                                         k = key_line[pos];
570                                         if(k == '\"' || k == ';' || k == ' ' || k == '\'')
571                                                 break;
572                                 }
573                         key_linepos = pos + 1;
574                 }
575                 else if(keydown[K_SHIFT]) // move cursor to the previous character ignoring colors
576                 {
577                         int             pos;
578                         pos = key_linepos-1;
579                         while (pos)
580                                 if(pos-1 > 0 && key_line[pos-1] == STRING_COLOR_TAG && isdigit(key_line[pos]))
581                                         pos-=2;
582                                 else if(pos-4 > 0 && key_line[pos-4] == STRING_COLOR_TAG && key_line[pos-3] == STRING_COLOR_RGB_TAG_CHAR
583                                                 && isxdigit(key_line[pos-2]) && isxdigit(key_line[pos-1]) && isxdigit(key_line[pos]))
584                                         pos-=5;
585                                 else
586                                 {
587                                         if(pos-1 > 0 && key_line[pos-1] == STRING_COLOR_TAG && key_line[pos] == STRING_COLOR_TAG) // consider ^^ as a character
588                                                 pos--;
589                                         pos--;
590                                         break;
591                                 }
592                         key_linepos = pos + 1;
593                 }
594                 else
595                         key_linepos--;
596                 return;
597         }
598
599         // delete char before cursor
600         if (key == K_BACKSPACE || (key == 'h' && keydown[K_CTRL]))
601         {
602                 if (key_linepos > 1)
603                 {
604                         strlcpy(key_line + key_linepos - 1, key_line + key_linepos, sizeof(key_line) + 1 - key_linepos);
605                         key_linepos--;
606                 }
607                 return;
608         }
609
610         // delete char on cursor
611         if (key == K_DEL || key == K_KP_DEL)
612         {
613                 size_t linelen;
614                 linelen = strlen(key_line);
615                 if (key_linepos < (int)linelen)
616                         memmove(key_line + key_linepos, key_line + key_linepos + 1, linelen - key_linepos);
617                 return;
618         }
619
620
621         // move cursor to the next character
622         if (key == K_RIGHTARROW || key == K_KP_RIGHTARROW)
623         {
624                 if (key_linepos >= (int)strlen(key_line))
625                         return;
626                 if(keydown[K_CTRL]) // move cursor to the next word
627                 {
628                         int             pos, len;
629                         char    k;
630                         len = (int)strlen(key_line);
631                         pos = key_linepos;
632
633                         while(++pos < len)
634                         {
635                                 k = key_line[pos];
636                                 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
637                                         break;
638                         }
639                         
640                         if (pos < len) // skip all "; ' after the word
641                                 while(++pos < len)
642                                 {
643                                         k = key_line[pos];
644                                         if (!(k == '\"' || k == ';' || k == ' ' || k == '\''))
645                                                 break;
646                                 }
647                         key_linepos = pos;
648                 }
649                 else if(keydown[K_SHIFT]) // move cursor to the next character ignoring colors
650                 {
651                         int             pos, len;
652                         len = (int)strlen(key_line);
653                         pos = key_linepos;
654                         
655                         // go beyond all initial consecutive color tags, if any
656                         if(pos < len)
657                                 while (key_line[pos] == STRING_COLOR_TAG)
658                                 {
659                                         if(isdigit(key_line[pos+1]))
660                                                 pos+=2;
661                                         else if(key_line[pos+1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(key_line[pos+2]) && isxdigit(key_line[pos+3]) && isxdigit(key_line[pos+4]))
662                                                 pos+=5;
663                                         else
664                                                 break;
665                                 }
666                         
667                         // skip the char
668                         if (key_line[pos] == STRING_COLOR_TAG && key_line[pos+1] == STRING_COLOR_TAG) // consider ^^ as a character
669                                 pos++;
670                         pos++;
671                         
672                         // now go beyond all next consecutive color tags, if any
673                         if(pos < len)
674                                 while (key_line[pos] == STRING_COLOR_TAG)
675                                 {
676                                         if(isdigit(key_line[pos+1]))
677                                                 pos+=2;
678                                         else if(key_line[pos+1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(key_line[pos+2]) && isxdigit(key_line[pos+3]) && isxdigit(key_line[pos+4]))
679                                                 pos+=5;
680                                         else
681                                                 break;
682                                 }
683                         key_linepos = pos;
684                 }
685                 else
686                         key_linepos++;
687                 return;
688         }
689
690         if (key == K_INS || key == K_KP_INS) // toggle insert mode
691         {
692                 key_insert ^= 1;
693                 return;
694         }
695
696         // End Advanced Console Editing
697
698         if (key == K_UPARROW || key == K_KP_UPARROW || (key == 'p' && keydown[K_CTRL]))
699         {
700                 Key_History_Up();
701                 return;
702         }
703
704         if (key == K_DOWNARROW || key == K_KP_DOWNARROW || (key == 'n' && keydown[K_CTRL]))
705         {
706                 Key_History_Down();
707                 return;
708         }
709         // ~1.0795 = 82/76  using con_textsize 64 76 is height of the char, 6 is the distance between 2 lines
710         if (key == K_PGUP || key == K_KP_PGUP)
711         {
712                 if(keydown[K_CTRL])
713                 {
714                         con_backscroll += (int)floor((vid_conheight.integer >> 2) / con_textsize.integer)-1;
715                 }
716                 else
717                         con_backscroll += (int)floor((vid_conheight.integer >> 1) / con_textsize.integer)-3;
718                 return;
719         }
720
721         if (key == K_PGDN || key == K_KP_PGDN)
722         {
723                 if(keydown[K_CTRL])
724                 {
725                         con_backscroll -= (int)floor((vid_conheight.integer >> 2) / con_textsize.integer)-1;
726                 }
727                 else
728                         con_backscroll -= (int)floor((vid_conheight.integer >> 1) / con_textsize.integer)-3;
729                 return;
730         }
731  
732         if (key == K_MWHEELUP)
733         {
734                 if(keydown[K_CTRL])
735                         con_backscroll += 1;
736                 else if(keydown[K_SHIFT])
737                         con_backscroll += (int)floor((vid_conheight.integer >> 2) / con_textsize.integer)-1;
738                 else
739                         con_backscroll += 5;
740                 return;
741         }
742
743         if (key == K_MWHEELDOWN)
744         {
745                 if(keydown[K_CTRL])
746                         con_backscroll -= 1;
747                 else if(keydown[K_SHIFT])
748                         con_backscroll -= (int)floor((vid_conheight.integer >> 2) / con_textsize.integer)-1;
749                 else
750                         con_backscroll -= 5;
751                 return;
752         }
753
754         if (keydown[K_CTRL])
755         {
756                 // text zoom in
757                 if (key == '+' || key == K_KP_PLUS)
758                 {
759                         if (con_textsize.integer < 128)
760                                 Cvar_SetValueQuick(&con_textsize, con_textsize.integer + 1);
761                         return;
762                 }
763                 // text zoom out
764                 if (key == '-' || key == K_KP_MINUS)
765                 {
766                         if (con_textsize.integer > 1)
767                                 Cvar_SetValueQuick(&con_textsize, con_textsize.integer - 1);
768                         return;
769                 }
770                 // text zoom reset
771                 if (key == '0' || key == K_KP_INS)
772                 {
773                         Cvar_SetValueQuick(&con_textsize, atoi(Cvar_VariableDefString("con_textsize")));
774                         return;
775                 }
776         }
777
778         if (key == K_HOME || key == K_KP_HOME)
779         {
780                 if (keydown[K_CTRL])
781                         con_backscroll = INT_MAX;
782                 else
783                         key_linepos = 1;
784                 return;
785         }
786
787         if (key == K_END || key == K_KP_END)
788         {
789                 if (keydown[K_CTRL])
790                         con_backscroll = 0;
791                 else
792                         key_linepos = (int)strlen(key_line);
793                 return;
794         }
795
796         // non printable
797         if (ascii < 32)
798                 return;
799
800         if (key_linepos < MAX_INPUTLINE-1)
801         {
802                 int len;
803                 len = (int)strlen(&key_line[key_linepos]);
804                 // check insert mode, or always insert if at end of line
805                 if (key_insert || len == 0)
806                 {
807                         // can't use strcpy to move string to right
808                         len++;
809                         memmove(&key_line[key_linepos + 1], &key_line[key_linepos], len);
810                 }
811                 key_line[key_linepos] = ascii;
812                 key_linepos++;
813         }
814 }
815
816 //============================================================================
817
818 int chat_mode;
819 char            chat_buffer[MAX_INPUTLINE];
820 unsigned int    chat_bufferlen = 0;
821
822 extern int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos);
823
824 static void
825 Key_Message (int key, int ascii)
826 {
827
828         if (key == K_ENTER || ascii == 10 || ascii == 13)
829         {
830                 if(chat_mode < 0)
831                         Cmd_ExecuteString(chat_buffer, src_command); // not Cbuf_AddText to allow semiclons in args; however, this allows no variables then. Use aliases!
832                 else
833                         Cmd_ForwardStringToServer(va("%s %s", chat_mode ? "say_team" : "say ", chat_buffer));
834
835                 key_dest = key_game;
836                 chat_bufferlen = 0;
837                 chat_buffer[0] = 0;
838                 return;
839         }
840
841         // TODO add support for arrow keys and simple editing
842
843         if (key == K_ESCAPE) {
844                 key_dest = key_game;
845                 chat_bufferlen = 0;
846                 chat_buffer[0] = 0;
847                 return;
848         }
849
850         if (key == K_BACKSPACE) {
851                 if (chat_bufferlen) {
852                         chat_bufferlen--;
853                         chat_buffer[chat_bufferlen] = 0;
854                 }
855                 return;
856         }
857
858         if(key == K_TAB) {
859                 chat_bufferlen = Nicks_CompleteChatLine(chat_buffer, sizeof(chat_buffer), chat_bufferlen);
860                 return;
861         }
862
863         if (chat_bufferlen == sizeof (chat_buffer) - 1)
864                 return;                                                 // all full
865
866         if (!ascii)
867                 return;                                                 // non printable
868
869         chat_buffer[chat_bufferlen++] = ascii;
870         chat_buffer[chat_bufferlen] = 0;
871 }
872
873 //============================================================================
874
875
876 /*
877 ===================
878 Returns a key number to be used to index keybindings[] by looking at
879 the given string.  Single ascii characters return themselves, while
880 the K_* names are matched up.
881 ===================
882 */
883 int
884 Key_StringToKeynum (const char *str)
885 {
886         const keyname_t  *kn;
887
888         if (!str || !str[0])
889                 return -1;
890         if (!str[1])
891                 return tolower(str[0]);
892
893         for (kn = keynames; kn->name; kn++) {
894                 if (!strcasecmp (str, kn->name))
895                         return kn->keynum;
896         }
897         return -1;
898 }
899
900 /*
901 ===================
902 Returns a string (either a single ascii char, or a K_* name) for the
903 given keynum.
904 FIXME: handle quote special (general escape sequence?)
905 ===================
906 */
907 const char *
908 Key_KeynumToString (int keynum)
909 {
910         const keyname_t  *kn;
911         static char tinystr[2];
912
913         // -1 is an invalid code
914         if (keynum < 0)
915                 return "<KEY NOT FOUND>";
916
917         // search overrides first, because some characters are special
918         for (kn = keynames; kn->name; kn++)
919                 if (keynum == kn->keynum)
920                         return kn->name;
921
922         // if it is printable, output it as a single character
923         if (keynum > 32 && keynum < 256)
924         {
925                 tinystr[0] = keynum;
926                 tinystr[1] = 0;
927                 return tinystr;
928         }
929
930         // if it is not overridden and not printable, we don't know what to do with it
931         return "<UNKNOWN KEYNUM>";
932 }
933
934
935 void
936 Key_SetBinding (int keynum, int bindmap, const char *binding)
937 {
938         char *newbinding;
939         size_t l;
940
941         if (keynum == -1 || keynum >= MAX_KEYS)
942                 return;
943
944 // free old bindings
945         if (keybindings[bindmap][keynum]) {
946                 Z_Free (keybindings[bindmap][keynum]);
947                 keybindings[bindmap][keynum] = NULL;
948         }
949         if(!binding[0]) // make "" binds be removed --blub
950                 return;
951 // allocate memory for new binding
952         l = strlen (binding);
953         newbinding = (char *)Z_Malloc (l + 1);
954         memcpy (newbinding, binding, l + 1);
955         newbinding[l] = 0;
956         keybindings[bindmap][keynum] = newbinding;
957 }
958
959 static void
960 Key_In_Unbind_f (void)
961 {
962         int         b, m;
963         char *errchar = NULL;
964
965         if (Cmd_Argc () != 3) {
966                 Con_Print("in_unbind <bindmap> <key> : remove commands from a key\n");
967                 return;
968         }
969
970         m = strtol(Cmd_Argv (1), &errchar, 0);
971         if ((m < 0) || (m >= 8) || (errchar && *errchar)) {
972                 Con_Printf("%s isn't a valid bindmap\n", Cmd_Argv(1));
973                 return;
974         }
975
976         b = Key_StringToKeynum (Cmd_Argv (2));
977         if (b == -1) {
978                 Con_Printf("\"%s\" isn't a valid key\n", Cmd_Argv (2));
979                 return;
980         }
981
982         Key_SetBinding (b, m, "");
983 }
984
985 static void
986 Key_In_Bind_f (void)
987 {
988         int         i, c, b, m;
989         char        cmd[MAX_INPUTLINE];
990         char *errchar = NULL;
991
992         c = Cmd_Argc ();
993
994         if (c != 3 && c != 4) {
995                 Con_Print("in_bind <bindmap> <key> [command] : attach a command to a key\n");
996                 return;
997         }
998
999         m = strtol(Cmd_Argv (1), &errchar, 0);
1000         if ((m < 0) || (m >= 8) || (errchar && *errchar)) {
1001                 Con_Printf("%s isn't a valid bindmap\n", Cmd_Argv(1));
1002                 return;
1003         }
1004
1005         b = Key_StringToKeynum (Cmd_Argv (2));
1006         if (b == -1 || b >= MAX_KEYS) {
1007                 Con_Printf("\"%s\" isn't a valid key\n", Cmd_Argv (2));
1008                 return;
1009         }
1010
1011         if (c == 3) {
1012                 if (keybindings[m][b])
1013                         Con_Printf("\"%s\" = \"%s\"\n", Cmd_Argv (2), keybindings[m][b]);
1014                 else
1015                         Con_Printf("\"%s\" is not bound\n", Cmd_Argv (2));
1016                 return;
1017         }
1018 // copy the rest of the command line
1019         cmd[0] = 0;                                                     // start out with a null string
1020         for (i = 3; i < c; i++) {
1021                 strlcat (cmd, Cmd_Argv (i), sizeof (cmd));
1022                 if (i != (c - 1))
1023                         strlcat (cmd, " ", sizeof (cmd));
1024         }
1025
1026         Key_SetBinding (b, m, cmd);
1027 }
1028
1029 static void
1030 Key_In_Bindmap_f (void)
1031 {
1032         int         m1, m2, c;
1033         char *errchar = NULL;
1034
1035         c = Cmd_Argc ();
1036
1037         if (c != 3) {
1038                 Con_Print("in_bindmap <bindmap> <fallback>: set current bindmap and fallback\n");
1039                 return;
1040         }
1041
1042         m1 = strtol(Cmd_Argv (1), &errchar, 0);
1043         if ((m1 < 0) || (m1 >= 8) || (errchar && *errchar)) {
1044                 Con_Printf("%s isn't a valid bindmap\n", Cmd_Argv(1));
1045                 return;
1046         }
1047
1048         m2 = strtol(Cmd_Argv (2), &errchar, 0);
1049         if ((m2 < 0) || (m2 >= 8) || (errchar && *errchar)) {
1050                 Con_Printf("%s isn't a valid bindmap\n", Cmd_Argv(2));
1051                 return;
1052         }
1053
1054         key_bmap = m1;
1055         key_bmap2 = m2;
1056 }
1057
1058 static void
1059 Key_Unbind_f (void)
1060 {
1061         int         b;
1062
1063         if (Cmd_Argc () != 2) {
1064                 Con_Print("unbind <key> : remove commands from a key\n");
1065                 return;
1066         }
1067
1068         b = Key_StringToKeynum (Cmd_Argv (1));
1069         if (b == -1) {
1070                 Con_Printf("\"%s\" isn't a valid key\n", Cmd_Argv (1));
1071                 return;
1072         }
1073
1074         Key_SetBinding (b, 0, "");
1075 }
1076
1077 static void
1078 Key_Unbindall_f (void)
1079 {
1080         int         i, j;
1081
1082         for (j = 0; j < 8; j++)
1083                 for (i = 0; i < (int)(sizeof(keybindings[0])/sizeof(keybindings[0][0])); i++)
1084                         if (keybindings[j][i])
1085                                 Key_SetBinding (i, j, "");
1086 }
1087
1088 static void
1089 Key_PrintBindList(int j)
1090 {
1091         char bindbuf[MAX_INPUTLINE];
1092         const char *p;
1093         int i;
1094
1095         for (i = 0; i < (int)(sizeof(keybindings[0])/sizeof(keybindings[0][0])); i++)
1096         {
1097                 p = keybindings[j][i];
1098                 if (p)
1099                 {
1100                         Cmd_QuoteString(bindbuf, sizeof(bindbuf), p, "\"\\");
1101                         if (j == 0)
1102                                 Con_Printf("^2%s ^7= \"%s\"\n", Key_KeynumToString (i), bindbuf);
1103                         else
1104                                 Con_Printf("^3bindmap %d: ^2%s ^7= \"%s\"\n", j, Key_KeynumToString (i), bindbuf);
1105                 }
1106         }
1107 }
1108
1109 static void
1110 Key_In_BindList_f (void)
1111 {
1112         int m;
1113         char *errchar = NULL;
1114
1115         if(Cmd_Argc() >= 2)
1116         {
1117                 m = strtol(Cmd_Argv(1), &errchar, 0);
1118                 if ((m < 0) || (m >= 8) || (errchar && *errchar)) {
1119                         Con_Printf("%s isn't a valid bindmap\n", Cmd_Argv(1));
1120                         return;
1121                 }
1122                 Key_PrintBindList(m);
1123         }
1124         else
1125         {
1126                 for (m = 0; m < MAX_BINDMAPS; m++)
1127                         Key_PrintBindList(m);
1128         }
1129 }
1130
1131 static void
1132 Key_BindList_f (void)
1133 {
1134         Key_PrintBindList(0);
1135 }
1136
1137 static void
1138 Key_Bind_f (void)
1139 {
1140         int         i, c, b;
1141         char        cmd[MAX_INPUTLINE];
1142
1143         c = Cmd_Argc ();
1144
1145         if (c != 2 && c != 3) {
1146                 Con_Print("bind <key> [command] : attach a command to a key\n");
1147                 return;
1148         }
1149         b = Key_StringToKeynum (Cmd_Argv (1));
1150         if (b == -1 || b >= MAX_KEYS) {
1151                 Con_Printf("\"%s\" isn't a valid key\n", Cmd_Argv (1));
1152                 return;
1153         }
1154
1155         if (c == 2) {
1156                 if (keybindings[0][b])
1157                         Con_Printf("\"%s\" = \"%s\"\n", Cmd_Argv (1), keybindings[0][b]);
1158                 else
1159                         Con_Printf("\"%s\" is not bound\n", Cmd_Argv (1));
1160                 return;
1161         }
1162 // copy the rest of the command line
1163         cmd[0] = 0;                                                     // start out with a null string
1164         for (i = 2; i < c; i++) {
1165                 strlcat (cmd, Cmd_Argv (i), sizeof (cmd));
1166                 if (i != (c - 1))
1167                         strlcat (cmd, " ", sizeof (cmd));
1168         }
1169
1170         Key_SetBinding (b, 0, cmd);
1171 }
1172
1173 /*
1174 ============
1175 Writes lines containing "bind key value"
1176 ============
1177 */
1178 void
1179 Key_WriteBindings (qfile_t *f)
1180 {
1181         int         i, j;
1182         char bindbuf[MAX_INPUTLINE];
1183         const char *p;
1184
1185         for (j = 0; j < MAX_BINDMAPS; j++)
1186         {
1187                 for (i = 0; i < (int)(sizeof(keybindings[0])/sizeof(keybindings[0][0])); i++)
1188                 {
1189                         p = keybindings[j][i];
1190                         if (p)
1191                         {
1192                                 Cmd_QuoteString(bindbuf, sizeof(bindbuf), p, "\"\\");
1193                                 if (j == 0)
1194                                         FS_Printf(f, "bind %s \"%s\"\n", Key_KeynumToString (i), bindbuf);
1195                                 else
1196                                         FS_Printf(f, "in_bind %d %s \"%s\"\n", j, Key_KeynumToString (i), bindbuf);
1197                         }
1198                 }
1199         }
1200 }
1201
1202
1203 void
1204 Key_Init (void)
1205 {
1206         Key_History_Init();
1207         key_line[0] = ']';
1208         key_line[1] = 0;
1209         key_linepos = 1;
1210
1211 //
1212 // register our functions
1213 //
1214         Cmd_AddCommand ("in_bind", Key_In_Bind_f, "binds a command to the specified key in the selected bindmap");
1215         Cmd_AddCommand ("in_unbind", Key_In_Unbind_f, "removes command on the specified key in the selected bindmap");
1216         Cmd_AddCommand ("in_bindlist", Key_In_BindList_f, "bindlist: displays bound keys for all bindmaps, or the given bindmap");
1217         Cmd_AddCommand ("in_bindmap", Key_In_Bindmap_f, "selects active foreground and background (used only if a key is not bound in the foreground) bindmaps for typing");
1218
1219         Cmd_AddCommand ("bind", Key_Bind_f, "binds a command to the specified key in bindmap 0");
1220         Cmd_AddCommand ("unbind", Key_Unbind_f, "removes a command on the specified key in bindmap 0");
1221         Cmd_AddCommand ("bindlist", Key_BindList_f, "bindlist: displays bound keys for bindmap 0 bindmaps");
1222         Cmd_AddCommand ("unbindall", Key_Unbindall_f, "removes all commands from all keys in all bindmaps (leaving only shift-escape and escape)");
1223
1224         Cvar_RegisterVariable (&con_closeontoggleconsole);
1225 }
1226
1227 void
1228 Key_Shutdown (void)
1229 {
1230         Key_History_Shutdown();
1231 }
1232
1233 const char *Key_GetBind (int key)
1234 {
1235         const char *bind;
1236         if (key < 0 || key >= MAX_KEYS)
1237                 return NULL;
1238         bind = keybindings[key_bmap][key];
1239         if (!bind)
1240                 bind = keybindings[key_bmap2][key];
1241         return bind;
1242 }
1243
1244 qboolean CL_VM_InputEvent (qboolean down, int key, int ascii);
1245
1246 /*
1247 ===================
1248 Called by the system between frames for both key up and key down events
1249 Should NOT be called during an interrupt!
1250 ===================
1251 */
1252 static char tbl_keyascii[MAX_KEYS];
1253 static keydest_t tbl_keydest[MAX_KEYS];
1254
1255 void
1256 Key_Event (int key, int ascii, qboolean down)
1257 {
1258         const char *bind;
1259         qboolean q;
1260         keydest_t keydest = key_dest;
1261
1262         if (key < 0 || key >= MAX_KEYS)
1263                 return;
1264
1265         // get key binding
1266         bind = keybindings[key_bmap][key];
1267         if (!bind)
1268                 bind = keybindings[key_bmap2][key];
1269
1270         if (developer.integer >= 1000)
1271                 Con_Printf("Key_Event(%i, '%c', %s) keydown %i bind \"%s\"\n", key, ascii, down ? "down" : "up", keydown[key], bind ? bind : "");
1272
1273         if(key_consoleactive)
1274                 keydest = key_console;
1275         
1276         if (down)
1277         {
1278                 // increment key repeat count each time a down is received so that things
1279                 // which want to ignore key repeat can ignore it
1280                 keydown[key] = min(keydown[key] + 1, 2);
1281                 if(keydown[key] == 1) {
1282                         tbl_keyascii[key] = ascii;
1283                         tbl_keydest[key] = keydest;
1284                 } else {
1285                         ascii = tbl_keyascii[key];
1286                         keydest = tbl_keydest[key];
1287                 }
1288         }
1289         else
1290         {
1291                 // clear repeat count now that the key is released
1292                 keydown[key] = 0;
1293                 keydest = tbl_keydest[key];
1294                 ascii = tbl_keyascii[key];
1295         }
1296
1297         if(keydest == key_void)
1298                 return;
1299         
1300         // key_consoleactive is a flag not a key_dest because the console is a
1301         // high priority overlay ontop of the normal screen (designed as a safety
1302         // feature so that developers and users can rescue themselves from a bad
1303         // situation).
1304         //
1305         // this also means that toggling the console on/off does not lose the old
1306         // key_dest state
1307
1308         // specially handle escape (togglemenu) and shift-escape (toggleconsole)
1309         // engine bindings, these are not handled as normal binds so that the user
1310         // can recover from a completely empty bindmap
1311         if (key == K_ESCAPE)
1312         {
1313                 // ignore key repeats on escape
1314                 if (keydown[key] > 1)
1315                         return;
1316
1317                 // escape does these things:
1318                 // key_consoleactive - close console
1319                 // key_message - abort messagemode
1320                 // key_menu - go to parent menu (or key_game)
1321                 // key_game - open menu
1322
1323                 // in all modes shift-escape toggles console
1324                 if (keydown[K_SHIFT])
1325                 {
1326                         if(down)
1327                         {
1328                                 Con_ToggleConsole_f ();
1329                                 tbl_keydest[key] = key_void; // esc release should go nowhere (especially not to key_menu or key_game)
1330                         }
1331                         return;
1332                 }
1333
1334                 switch (keydest)
1335                 {
1336                         case key_console:
1337                                 if(down)
1338                                 {
1339                                         if(key_consoleactive & KEY_CONSOLEACTIVE_FORCED)
1340                                         {
1341                                                 key_consoleactive &= ~KEY_CONSOLEACTIVE_USER;
1342                                                 MR_ToggleMenu_f ();
1343                                         }
1344                                         else
1345                                                 Con_ToggleConsole_f();
1346                                 }
1347                                 break;
1348
1349                         case key_message:
1350                                 if (down)
1351                                         Key_Message (key, ascii); // that'll close the message input
1352                                 break;
1353
1354                         case key_menu:
1355                         case key_menu_grabbed:
1356                                 MR_KeyEvent (key, ascii, down);
1357                                 break;
1358
1359                         case key_game:
1360                                 // csqc has priority over toggle menu if it wants to (e.g. handling escape for UI stuff in-game.. :sick:)
1361                                 q = CL_VM_InputEvent(down, key, ascii);
1362                                 if (!q && down)
1363                                         MR_ToggleMenu_f ();
1364                                 break;
1365
1366                         default:
1367                                 Con_Printf ("Key_Event: Bad key_dest\n");
1368                 }
1369                 return;
1370         }
1371
1372         // send function keydowns to interpreter no matter what mode is (unless the menu has specifically grabbed the keyboard, for rebinding keys)
1373         if (keydest != key_menu_grabbed)
1374         if (key >= K_F1 && key <= K_F12)
1375         {
1376                 if (bind)
1377                 {
1378                         if(keydown[key] == 1 && down)
1379                         {
1380                                 // button commands add keynum as a parm
1381                                 if (bind[0] == '+')
1382                                         Cbuf_AddText (va("%s %i\n", bind, key));
1383                                 else
1384                                 {
1385                                         Cbuf_AddText (bind);
1386                                         Cbuf_AddText ("\n");
1387                                 }
1388                         } else if(bind[0] == '+' && !down && keydown[key] == 0)
1389                                 Cbuf_AddText(va("-%s %i\n", bind + 1, key));
1390                 }
1391                 return;
1392         }
1393
1394         // send input to console if it wants it
1395         if (keydest == key_console)
1396         {
1397                 if (!down)
1398                         return;
1399                 // con_closeontoggleconsole enables toggleconsole keys to close the
1400                 // console, as long as they are not the color prefix character
1401                 // (special exemption for german keyboard layouts)
1402                 if (con_closeontoggleconsole.integer && bind && !strncmp(bind, "toggleconsole", strlen("toggleconsole")) && (key_consoleactive & KEY_CONSOLEACTIVE_USER) && ascii != STRING_COLOR_TAG)
1403                 {
1404                         Con_ToggleConsole_f ();
1405                         return;
1406                 }
1407                 Key_Console (key, ascii);
1408                 return;
1409         }
1410
1411         // handle toggleconsole in menu too
1412         if (keydest == key_menu)
1413         {
1414                 if (down && con_closeontoggleconsole.integer && bind && !strncmp(bind, "toggleconsole", strlen("toggleconsole")) && ascii != STRING_COLOR_TAG)
1415                 {
1416                         Con_ToggleConsole_f ();
1417                         tbl_keydest[key] = key_void; // key release should go nowhere (especially not to key_menu or key_game)
1418                         return;
1419                 }
1420         }
1421
1422         // ignore binds while a video is played, let the video system handle the key event
1423         if (cl_videoplaying)
1424         {
1425                 CL_Video_KeyEvent (key, ascii, keydown[key] != 0);
1426                 return;
1427         }
1428
1429         // anything else is a key press into the game, chat line, or menu
1430         switch (keydest)
1431         {
1432                 case key_message:
1433                         if (down)
1434                                 Key_Message (key, ascii);
1435                         break;
1436                 case key_menu:
1437                 case key_menu_grabbed:
1438                         MR_KeyEvent (key, ascii, down);
1439                         break;
1440                 case key_game:
1441                         q = CL_VM_InputEvent(down, key, ascii);
1442                         // ignore key repeats on binds and only send the bind if the event hasnt been already processed by csqc
1443                         if (!q && bind)
1444                         {
1445                                 if(keydown[key] == 1 && down)
1446                                 {
1447                                         // button commands add keynum as a parm
1448                                         if (bind[0] == '+')
1449                                                 Cbuf_AddText (va("%s %i\n", bind, key));
1450                                         else
1451                                         {
1452                                                 Cbuf_AddText (bind);
1453                                                 Cbuf_AddText ("\n");
1454                                         }
1455                                 } else if(bind[0] == '+' && !down && keydown[key] == 0)
1456                                         Cbuf_AddText(va("-%s %i\n", bind + 1, key));
1457                         }
1458                         break;
1459                 default:
1460                         Con_Printf ("Key_Event: Bad key_dest\n");
1461         }
1462 }
1463
1464 /*
1465 ===================
1466 Key_ClearStates
1467 ===================
1468 */
1469 void
1470 Key_ClearStates (void)
1471 {
1472         memset(keydown, 0, sizeof(keydown));
1473 }