Added a "log_file" cvar to control the log file name (default: "" which means no...
[xonotic/darkplaces.git] / console.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 the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20 // console.c
21
22 #ifndef WIN32
23 # include <unistd.h>
24 #endif
25 #include "quakedef.h"
26
27 int con_linewidth;
28
29 float con_cursorspeed = 4;
30
31 #define         CON_TEXTSIZE    131072
32
33 // total lines in console scrollback
34 int con_totallines;
35 // lines up from bottom to display
36 int con_backscroll;
37 // where next message will be printed
38 int con_current;
39 // offset in current line for next print
40 int con_x;
41 char *con_text = 0;
42
43 //seconds
44 cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3"};
45 cvar_t con_notify = {CVAR_SAVE, "con_notify","4"};
46
47 #define MAX_NOTIFYLINES 32
48 // cl.time time the line was generated for transparent notify lines
49 float con_times[MAX_NOTIFYLINES];
50
51 int con_vislines;
52
53 #define MAXCMDLINE      256
54 extern char key_lines[32][MAXCMDLINE];
55 extern int edit_line;
56 extern int key_linepos;
57 extern int key_insert;
58
59
60 qboolean con_initialized;
61
62 mempool_t *console_mempool;
63
64
65 /*
66 ==============================================================================
67
68 LOGGING
69
70 ==============================================================================
71 */
72
73 cvar_t log_file = {0, "log_file",""};
74 cvar_t log_sync = {0, "log_sync","0"};
75 qfile_t* logfile = NULL;
76
77 qbyte* logqueue = NULL;
78 size_t logq_ind = 0;
79 size_t logq_size = 0;
80
81 /*
82 ====================
83 Log_Init
84 ====================
85 */
86 void Log_Init (void)
87 {
88         Cvar_RegisterVariable (&log_file);
89         Cvar_RegisterVariable (&log_sync);
90
91         // support for the classic Quake option
92         if (COM_CheckParm ("-condebug") != 0)
93         {
94                 Cvar_SetQuick (&log_file, "qconsole.log");
95                 Cvar_SetValueQuick (&log_sync, 1);
96         }
97
98         // Allocate a log queue
99         logq_size = 4;
100         logqueue = Mem_Alloc (tempmempool, logq_size);
101         logq_ind = 0;
102 }
103
104
105 /*
106 ====================
107 Log_Start
108 ====================
109 */
110 void Log_Start (void)
111 {
112         if (log_file.string[0] != '\0')
113                 logfile = FS_Open (log_file.string, "wt", false);
114
115         // Dump the contents of the log queue into the log file and free it
116         if (logqueue != NULL)
117         {
118                 if (logfile != NULL && logq_ind != 0)
119                         FS_Write (logfile, logqueue, logq_ind);
120                 Mem_Free (logqueue);
121                 logqueue = NULL;
122                 logq_ind = 0;
123                 logq_size = 0;
124         }
125 }
126
127
128 /*
129 ================
130 Log_ConPrint
131 ================
132 */
133 void Log_ConPrint (const char *msg)
134 {
135         // Easy case: a log has been started
136         if (logfile != NULL)
137         {
138                 FS_Print (logfile, msg);
139                 if (log_sync.integer)
140                         FS_Flush (logfile);
141                 return;
142         }
143
144         // Until the host is completely initialized, we maintain a log queue
145         // to store the messages, since the log can't be started before
146         if (logqueue != NULL)
147         {
148                 size_t remain = logq_size - logq_ind;
149                 size_t len = strlen (msg);
150
151                 // If we need to enlarge the log queue
152                 if (len > remain)
153                 {
154                         unsigned int factor = ((logq_ind + len) / logq_size) + 1;
155                         qbyte* newqueue;
156
157                         logq_size *= factor;
158                         newqueue = Mem_Alloc (tempmempool, logq_size);
159                         memcpy (newqueue, logqueue, logq_ind);
160                         Mem_Free (logqueue);
161                         logqueue = newqueue;
162                         remain = logq_size - logq_ind;
163                 }
164                 memcpy (&logqueue[logq_ind], msg, len);
165                 logq_ind += len;
166         }
167 }
168
169
170 /*
171 ================
172 Log_Print
173 ================
174 */
175 void Log_Print (const char *logfilename, const char *msg)
176 {
177         qfile_t *file;
178         file = FS_Open(logfilename, "at", true);
179         if (file)
180         {
181                 FS_Print(file, msg);
182                 FS_Close(file);
183         }
184 }
185
186 /*
187 ================
188 Log_Printf
189 ================
190 */
191 void Log_Printf (const char *logfilename, const char *fmt, ...)
192 {
193         qfile_t *file;
194
195         file = FS_Open (logfilename, "at", true);
196         if (file != NULL)
197         {
198                 va_list argptr;
199
200                 va_start (argptr, fmt);
201                 FS_VPrintf (file, fmt, argptr);
202                 va_end (argptr);
203
204                 FS_Close (file);
205         }
206 }
207
208
209 /*
210 ==============================================================================
211
212 CONSOLE
213
214 ==============================================================================
215 */
216
217 /*
218 ================
219 Con_ToggleConsole_f
220 ================
221 */
222 void Con_ToggleConsole_f (void)
223 {
224         // toggle the 'user wants console' bit
225         key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
226         memset (con_times, 0, sizeof(con_times));
227 }
228
229 /*
230 ================
231 Con_Clear_f
232 ================
233 */
234 void Con_Clear_f (void)
235 {
236         if (con_text)
237                 memset (con_text, ' ', CON_TEXTSIZE);
238 }
239
240
241 /*
242 ================
243 Con_ClearNotify
244 ================
245 */
246 void Con_ClearNotify (void)
247 {
248         int i;
249
250         for (i=0 ; i<MAX_NOTIFYLINES ; i++)
251                 con_times[i] = 0;
252 }
253
254
255 /*
256 ================
257 Con_MessageMode_f
258 ================
259 */
260 void Con_MessageMode_f (void)
261 {
262         key_dest = key_message;
263         chat_team = false;
264 }
265
266
267 /*
268 ================
269 Con_MessageMode2_f
270 ================
271 */
272 void Con_MessageMode2_f (void)
273 {
274         key_dest = key_message;
275         chat_team = true;
276 }
277
278
279 /*
280 ================
281 Con_CheckResize
282
283 If the line width has changed, reformat the buffer.
284 ================
285 */
286 void Con_CheckResize (void)
287 {
288         int i, j, width, oldwidth, oldtotallines, numlines, numchars;
289         char tbuf[CON_TEXTSIZE];
290
291         width = (vid.conwidth >> 3);
292
293         if (width == con_linewidth)
294                 return;
295
296         if (width < 1)                  // video hasn't been initialized yet
297         {
298                 width = 80;
299                 con_linewidth = width;
300                 con_totallines = CON_TEXTSIZE / con_linewidth;
301                 memset (con_text, ' ', CON_TEXTSIZE);
302         }
303         else
304         {
305                 oldwidth = con_linewidth;
306                 con_linewidth = width;
307                 oldtotallines = con_totallines;
308                 con_totallines = CON_TEXTSIZE / con_linewidth;
309                 numlines = oldtotallines;
310
311                 if (con_totallines < numlines)
312                         numlines = con_totallines;
313
314                 numchars = oldwidth;
315
316                 if (con_linewidth < numchars)
317                         numchars = con_linewidth;
318
319                 memcpy (tbuf, con_text, CON_TEXTSIZE);
320                 memset (con_text, ' ', CON_TEXTSIZE);
321
322                 for (i=0 ; i<numlines ; i++)
323                 {
324                         for (j=0 ; j<numchars ; j++)
325                         {
326                                 con_text[(con_totallines - 1 - i) * con_linewidth + j] =
327                                                 tbuf[((con_current - i + oldtotallines) %
328                                                           oldtotallines) * oldwidth + j];
329                         }
330                 }
331
332                 Con_ClearNotify ();
333         }
334
335         con_backscroll = 0;
336         con_current = con_totallines - 1;
337 }
338
339 /*
340 ================
341 Con_Init
342 ================
343 */
344 void Con_Init (void)
345 {
346         console_mempool = Mem_AllocPool("console");
347         con_text = Mem_Alloc(console_mempool, CON_TEXTSIZE);
348         memset (con_text, ' ', CON_TEXTSIZE);
349         con_linewidth = -1;
350         Con_CheckResize ();
351
352         Con_Print("Console initialized.\n");
353
354         // register our cvars
355         Cvar_RegisterVariable (&con_notifytime);
356         Cvar_RegisterVariable (&con_notify);
357
358         // register our commands
359         Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f);
360         Cmd_AddCommand ("messagemode", Con_MessageMode_f);
361         Cmd_AddCommand ("messagemode2", Con_MessageMode2_f);
362         Cmd_AddCommand ("clear", Con_Clear_f);
363         con_initialized = true;
364 }
365
366
367 /*
368 ===============
369 Con_Linefeed
370 ===============
371 */
372 void Con_Linefeed (void)
373 {
374         con_x = 0;
375         con_current++;
376         memset (&con_text[(con_current%con_totallines)*con_linewidth], ' ', con_linewidth);
377 }
378
379 /*
380 ================
381 Con_PrintToHistory
382
383 Handles cursor positioning, line wrapping, etc
384 All console printing must go through this in order to be displayed
385 If no console is visible, the notify window will pop up.
386 ================
387 */
388 void Con_PrintToHistory(const char *txt)
389 {
390         int y, c, l, mask;
391         static int cr;
392
393         con_backscroll = 0;
394
395         if (txt[0] == 1)
396         {
397                 mask = 128;             // go to colored text
398                 S_LocalSound ("misc/talk.wav");
399         // play talk wav
400                 txt++;
401         }
402         else if (txt[0] == 2)
403         {
404                 mask = 128;             // go to colored text
405                 txt++;
406         }
407         else
408                 mask = 0;
409
410
411         while ( (c = *txt) )
412         {
413         // count word length
414                 for (l=0 ; l< con_linewidth ; l++)
415                         if ( txt[l] <= ' ')
416                                 break;
417
418         // word wrap
419                 if (l != con_linewidth && (con_x + l > con_linewidth) )
420                         con_x = 0;
421
422                 txt++;
423
424                 if (cr)
425                 {
426                         con_current--;
427                         cr = false;
428                 }
429
430
431                 if (!con_x)
432                 {
433                         Con_Linefeed ();
434                 // mark time for transparent overlay
435                         if (con_current >= 0)
436                         {
437                                 if (con_notify.integer < 0)
438                                         Cvar_SetValueQuick(&con_notify, 0);
439                                 if (con_notify.integer > MAX_NOTIFYLINES)
440                                         Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
441                                 if (con_notify.integer > 0)
442                                         con_times[con_current % con_notify.integer] = cl.time;
443                         }
444                 }
445
446                 switch (c)
447                 {
448                 case '\n':
449                         con_x = 0;
450                         break;
451
452                 case '\r':
453                         con_x = 0;
454                         cr = 1;
455                         break;
456
457                 default:        // display character and advance
458                         y = con_current % con_totallines;
459                         con_text[y*con_linewidth+con_x] = c | mask;
460                         con_x++;
461                         if (con_x >= con_linewidth)
462                                 con_x = 0;
463                         break;
464                 }
465
466         }
467 }
468
469 /*
470 ================
471 Con_Print
472
473 Prints to all appropriate console targets
474 ================
475 */
476 void Con_Print(const char *msg)
477 {
478         // also echo to debugging console
479         Sys_Print(msg);
480
481         // log all messages to file
482         Log_ConPrint (msg);
483
484         if (!con_initialized)
485                 return;
486
487         if (cls.state == ca_dedicated)
488                 return;         // no graphics mode
489
490         // write it to the scrollable buffer
491         Con_PrintToHistory(msg);
492 }
493
494
495 // LordHavoc: increased from 4096 to 16384
496 #define MAXPRINTMSG     16384
497
498 /*
499 ================
500 Con_Printf
501
502 Prints to all appropriate console targets
503 ================
504 */
505 void Con_Printf(const char *fmt, ...)
506 {
507         va_list argptr;
508         char msg[MAXPRINTMSG];
509
510         va_start(argptr,fmt);
511         vsprintf(msg,fmt,argptr);
512         va_end(argptr);
513
514         Con_Print(msg);
515 }
516
517 /*
518 ================
519 Con_DPrint
520
521 A Con_Print that only shows up if the "developer" cvar is set
522 ================
523 */
524 void Con_DPrint(const char *msg)
525 {
526         if (!developer.integer)
527                 return;                 // don't confuse non-developers with techie stuff...
528         Con_Print(msg);
529 }
530
531 /*
532 ================
533 Con_DPrintf
534
535 A Con_Printf that only shows up if the "developer" cvar is set
536 ================
537 */
538 void Con_DPrintf(const char *fmt, ...)
539 {
540         va_list argptr;
541         char msg[MAXPRINTMSG];
542
543         if (!developer.integer)
544                 return;                 // don't confuse non-developers with techie stuff...
545
546         va_start(argptr,fmt);
547         vsprintf(msg,fmt,argptr);
548         va_end(argptr);
549
550         Con_Print(msg);
551 }
552
553
554 /*
555 ================
556 Con_SafePrint
557
558 Okay to call even when the screen can't be updated
559 ==================
560 */
561 void Con_SafePrint(const char *msg)
562 {
563         Con_Print(msg);
564 }
565
566 /*
567 ==================
568 Con_SafePrintf
569
570 Okay to call even when the screen can't be updated
571 ==================
572 */
573 void Con_SafePrintf(const char *fmt, ...)
574 {
575         va_list argptr;
576         char msg[MAXPRINTMSG];
577
578         va_start(argptr,fmt);
579         vsprintf(msg,fmt,argptr);
580         va_end(argptr);
581
582         Con_Print(msg);
583 }
584
585
586 /*
587 ==============================================================================
588
589 DRAWING
590
591 ==============================================================================
592 */
593
594
595 /*
596 ================
597 Con_DrawInput
598
599 The input line scrolls horizontally if typing goes beyond the right edge
600
601 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
602 ================
603 */
604 void Con_DrawInput (void)
605 {
606         char editlinecopy[257], *text;
607
608         if (!key_consoleactive)
609                 return;         // don't draw anything
610
611         text = strcpy(editlinecopy, key_lines[edit_line]);
612
613         // Advanced Console Editing by Radix radix@planetquake.com
614         // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
615         // use strlen of edit_line instead of key_linepos to allow editing
616         // of early characters w/o erasing
617
618         // add the cursor frame
619         if ((int)(realtime*con_cursorspeed) & 1)                // cursor is visible
620                 text[key_linepos] = 11 + 130 * key_insert;      // either solid or triangle facing right
621
622         text[key_linepos + 1] = 0;
623
624         // prestep if horizontally scrolling
625         if (key_linepos >= con_linewidth)
626                 text += 1 + key_linepos - con_linewidth;
627
628         // draw it
629         DrawQ_String(0, con_vislines - 16, text, con_linewidth, 8, 8, 1, 1, 1, 1, 0);
630
631         // remove cursor
632         key_lines[edit_line][key_linepos] = 0;
633 }
634
635
636 /*
637 ================
638 Con_DrawNotify
639
640 Draws the last few lines of output transparently over the game top
641 ================
642 */
643 void Con_DrawNotify (void)
644 {
645         int             x, v;
646         char    *text;
647         int             i;
648         float   time;
649         extern char chat_buffer[];
650         char    temptext[256];
651
652         if (con_notify.integer < 0)
653                 Cvar_SetValueQuick(&con_notify, 0);
654         if (con_notify.integer > MAX_NOTIFYLINES)
655                 Cvar_SetValueQuick(&con_notify, MAX_NOTIFYLINES);
656         v = 0;
657         for (i= con_current-con_notify.integer+1 ; i<=con_current ; i++)
658         {
659                 if (i < 0)
660                         continue;
661                 time = con_times[i % con_notify.integer];
662                 if (time == 0)
663                         continue;
664                 time = cl.time - time;
665                 if (time > con_notifytime.value)
666                         continue;
667                 text = con_text + (i % con_totallines)*con_linewidth;
668
669                 clearnotify = 0;
670
671                 DrawQ_String(0, v, text, con_linewidth, 8, 8, 1, 1, 1, 1, 0);
672
673                 v += 8;
674         }
675
676
677         if (key_dest == key_message)
678         {
679                 clearnotify = 0;
680
681                 x = 0;
682
683                 // LordHavoc: speedup, and other improvements
684                 if (chat_team)
685                         sprintf(temptext, "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
686                 else
687                         sprintf(temptext, "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1));
688                 while (strlen(temptext) >= (size_t) con_linewidth)
689                 {
690                         DrawQ_String (0, v, temptext, con_linewidth, 8, 8, 1, 1, 1, 1, 0);
691                         strcpy(temptext, &temptext[con_linewidth]);
692                         v += 8;
693                 }
694                 if (strlen(temptext) > 0)
695                 {
696                         DrawQ_String (0, v, temptext, 0, 8, 8, 1, 1, 1, 1, 0);
697                         v += 8;
698                 }
699         }
700 }
701
702 /*
703 ================
704 Con_DrawConsole
705
706 Draws the console with the solid background
707 The typing input line at the bottom should only be drawn if typing is allowed
708 ================
709 */
710 extern char engineversion[40];
711 void Con_DrawConsole (int lines)
712 {
713         int i, y, rows, j;
714         char *text;
715
716         if (lines <= 0)
717                 return;
718
719 // draw the background
720         if (scr_conbrightness.value >= 0.01f)
721                 DrawQ_Pic(0, lines - vid.conheight, "gfx/conback", vid.conwidth, vid.conheight, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, scr_conalpha.value, 0);
722         else
723                 DrawQ_Fill(0, lines - vid.conheight, vid.conwidth, vid.conheight, 0, 0, 0, scr_conalpha.value, 0);
724         DrawQ_String(vid.conwidth - strlen(engineversion) * 8 - 8, lines - 8, engineversion, 0, 8, 8, 1, 0, 0, 1, 0);
725
726 // draw the text
727         con_vislines = lines;
728
729         rows = (lines-16)>>3;           // rows of text to draw
730         y = lines - 16 - (rows<<3);     // may start slightly negative
731
732         for (i = con_current - rows + 1;i <= con_current;i++, y += 8)
733         {
734                 j = max(i - con_backscroll, 0);
735                 text = con_text + (j % con_totallines)*con_linewidth;
736
737                 DrawQ_String(0, y, text, con_linewidth, 8, 8, 1, 1, 1, 1, 0);
738         }
739
740 // draw the input prompt, user text, and cursor if desired
741         Con_DrawInput ();
742 }
743
744 /*
745         Con_DisplayList
746
747         New function for tab-completion system
748         Added by EvilTypeGuy
749         MEGA Thanks to Taniwha
750
751 */
752 void Con_DisplayList(const char **list)
753 {
754         int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
755         const char **walk = list;
756
757         while (*walk) {
758                 len = strlen(*walk);
759                 if (len > maxlen)
760                         maxlen = len;
761                 walk++;
762         }
763         maxlen += 1;
764
765         while (*list) {
766                 len = strlen(*list);
767                 if (pos + maxlen >= width) {
768                         Con_Print("\n");
769                         pos = 0;
770                 }
771
772                 Con_Print(*list);
773                 for (i = 0; i < (maxlen - len); i++)
774                         Con_Print(" ");
775
776                 pos += maxlen;
777                 list++;
778         }
779
780         if (pos)
781                 Con_Print("\n\n");
782 }
783
784 /*
785         Con_CompleteCommandLine
786
787         New function for tab-completion system
788         Added by EvilTypeGuy
789         Thanks to Fett erich@heintz.com
790         Thanks to taniwha
791
792 */
793 void Con_CompleteCommandLine (void)
794 {
795         const char *cmd = "", *s;
796         const char **list[3] = {0, 0, 0};
797         int c, v, a, i, cmd_len;
798
799         s = key_lines[edit_line] + 1;
800         // Count number of possible matches
801         c = Cmd_CompleteCountPossible(s);
802         v = Cvar_CompleteCountPossible(s);
803         a = Cmd_CompleteAliasCountPossible(s);
804
805         if (!(c + v + a))       // No possible matches
806                 return;
807
808         if (c + v + a == 1) {
809                 if (c)
810                         list[0] = Cmd_CompleteBuildList(s);
811                 else if (v)
812                         list[0] = Cvar_CompleteBuildList(s);
813                 else
814                         list[0] = Cmd_CompleteAliasBuildList(s);
815                 cmd = *list[0];
816                 cmd_len = strlen (cmd);
817         } else {
818                 if (c)
819                         cmd = *(list[0] = Cmd_CompleteBuildList(s));
820                 if (v)
821                         cmd = *(list[1] = Cvar_CompleteBuildList(s));
822                 if (a)
823                         cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
824
825                 cmd_len = strlen (s);
826                 do {
827                         for (i = 0; i < 3; i++) {
828                                 char ch = cmd[cmd_len];
829                                 const char **l = list[i];
830                                 if (l) {
831                                         while (*l && (*l)[cmd_len] == ch)
832                                                 l++;
833                                         if (*l)
834                                                 break;
835                                 }
836                         }
837                         if (i == 3)
838                                 cmd_len++;
839                 } while (i == 3);
840                 // 'quakebar'
841                 Con_Print("\n\35");
842                 for (i = 0; i < con_linewidth - 4; i++)
843                         Con_Print("\36");
844                 Con_Print("\37\n");
845
846                 // Print Possible Commands
847                 if (c) {
848                         Con_Printf("%i possible command%s\n", c, (c > 1) ? "s: " : ":");
849                         Con_DisplayList(list[0]);
850                 }
851
852                 if (v) {
853                         Con_Printf("%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
854                         Con_DisplayList(list[1]);
855                 }
856
857                 if (a) {
858                         Con_Printf("%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
859                         Con_DisplayList(list[2]);
860                 }
861         }
862
863         if (cmd) {
864                 strncpy(key_lines[edit_line] + 1, cmd, cmd_len);
865                 key_linepos = cmd_len + 1;
866                 if (c + v + a == 1) {
867                         key_lines[edit_line][key_linepos] = ' ';
868                         key_linepos++;
869                 }
870                 key_lines[edit_line][key_linepos] = 0;
871         }
872         for (i = 0; i < 3; i++)
873                 if (list[i])
874                         Mem_Free((void *)list[i]);
875 }
876