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