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