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