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