1 /*
2 Copyright (C) 1996-2001 Id Software, Inc.
3 Copyright (C) 2002-2009 John Fitzgibbons and others
4 Copyright (C) 2010-2014 QuakeSpasm developers
5 
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation; either version 2
9 of the License, or (at your option) any later version.
10 
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14 
15 See the GNU General Public License for more details.
16 
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
20 
21 */
22 // console.c
23 
24 #include <sys/types.h>
25 #include <time.h>
26 #include <sys/stat.h>
27 #include <fcntl.h>
28 #ifdef _WIN32
29 #include <io.h>
30 #else
31 #include <unistd.h>
32 #endif
33 #include "quakedef.h"
34 
35 int 		con_linewidth;
36 
37 float		con_cursorspeed = 4;
38 
39 #define		CON_TEXTSIZE (1024 * 1024) //ericw -- was 65536. johnfitz -- new default size
40 #define		CON_MINSIZE  16384 //johnfitz -- old default, now the minimum size
41 
42 int		con_buffersize; //johnfitz -- user can now override default
43 
44 qboolean 	con_forcedup;		// because no entities to refresh
45 
46 int		con_totallines;		// total lines in console scrollback
47 int		con_backscroll;		// lines up from bottom to display
48 int		con_current;		// where next message will be printed
49 int		con_x;				// offset in current line for next print
50 char		*con_text = NULL;
51 
52 cvar_t		con_notifytime = {"con_notifytime","3",CVAR_NONE};	//seconds
53 cvar_t		con_logcenterprint = {"con_logcenterprint", "1", CVAR_NONE}; //johnfitz
54 
55 char		con_lastcenterstring[1024]; //johnfitz
56 void (*con_redirect_flush)(const char *buffer);	//call this to flush the redirection buffer (for rcon)
57 char con_redirect_buffer[8192];
58 
59 #define	NUM_CON_TIMES 4
60 float		con_times[NUM_CON_TIMES];	// realtime time the line was generated
61 						// for transparent notify lines
62 
63 int			con_vislines;
64 
65 qboolean	con_debuglog = false;
66 
67 qboolean	con_initialized;
68 
69 
70 /*
71 ================
72 Con_Quakebar -- johnfitz -- returns a bar of the desired length, but never wider than the console
73 
74 includes a newline, unless len >= con_linewidth.
75 ================
76 */
Con_Quakebar(int len)77 const char *Con_Quakebar (int len)
78 {
79 	static char bar[42];
80 	int i;
81 
82 	len = q_min(len, (int)sizeof(bar) - 2);
83 	len = q_min(len, con_linewidth);
84 
85 	bar[0] = '\35';
86 	for (i = 1; i < len - 1; i++)
87 		bar[i] = '\36';
88 	bar[len-1] = '\37';
89 
90 	if (len < con_linewidth)
91 	{
92 		bar[len] = '\n';
93 		bar[len+1] = 0;
94 	}
95 	else
96 		bar[len] = 0;
97 
98 	return bar;
99 }
100 
101 /*
102 ================
103 Con_ToggleConsole_f
104 ================
105 */
106 extern int history_line; //johnfitz
107 
Con_ToggleConsole_f(void)108 void Con_ToggleConsole_f (void)
109 {
110 	if (key_dest == key_console/* || (key_dest == key_game && con_forcedup)*/)
111 	{
112 		key_lines[edit_line][1] = 0;	// clear any typing
113 		key_linepos = 1;
114 		con_backscroll = 0; //johnfitz -- toggleconsole should return you to the bottom of the scrollback
115 		history_line = edit_line; //johnfitz -- it should also return you to the bottom of the command history
116 
117 		if (cls.state == ca_connected)
118 		{
119 			IN_Activate();
120 			key_dest = key_game;
121 		}
122 		else
123 		{
124 			M_Menu_Main_f ();
125 		}
126 	}
127 	else
128 	{
129 		IN_Deactivate(modestate == MS_WINDOWED);
130 		key_dest = key_console;
131 	}
132 
133 	SCR_EndLoadingPlaque ();
134 	memset (con_times, 0, sizeof(con_times));
135 }
136 
137 /*
138 ================
139 Con_Clear_f
140 ================
141 */
Con_Clear_f(void)142 static void Con_Clear_f (void)
143 {
144 	if (con_text)
145 		Q_memset (con_text, ' ', con_buffersize); //johnfitz -- con_buffersize replaces CON_TEXTSIZE
146 	con_backscroll = 0; //johnfitz -- if console is empty, being scrolled up is confusing
147 }
148 
149 /*
150 ================
151 Con_Dump_f -- johnfitz -- adapted from quake2 source
152 ================
153 */
Con_Dump_f(void)154 static void Con_Dump_f (void)
155 {
156 	int		l, x;
157 	const char	*line;
158 	FILE	*f;
159 	char	buffer[1024];
160 	char	name[MAX_OSPATH];
161 
162 	q_snprintf (name, sizeof(name), "%s/condump.txt", com_gamedir);
163 	COM_CreatePath (name);
164 	f = fopen (name, "w");
165 	if (!f)
166 	{
167 		Con_Printf ("ERROR: couldn't open file %s.\n", name);
168 		return;
169 	}
170 
171 	// skip initial empty lines
172 	for (l = con_current - con_totallines + 1; l <= con_current; l++)
173 	{
174 		line = con_text + (l % con_totallines)*con_linewidth;
175 		for (x = 0; x < con_linewidth; x++)
176 			if (line[x] != ' ')
177 				break;
178 		if (x != con_linewidth)
179 			break;
180 	}
181 
182 	// write the remaining lines
183 	buffer[con_linewidth] = 0;
184 	for ( ; l <= con_current; l++)
185 	{
186 		line = con_text + (l%con_totallines)*con_linewidth;
187 		strncpy (buffer, line, con_linewidth);
188 		for (x = con_linewidth - 1; x >= 0; x--)
189 		{
190 			if (buffer[x] == ' ')
191 				buffer[x] = 0;
192 			else
193 				break;
194 		}
195 		for (x = 0; buffer[x]; x++)
196 			buffer[x] &= 0x7f;
197 
198 		fprintf (f, "%s\n", buffer);
199 	}
200 
201 	fclose (f);
202 	Con_Printf ("Dumped console text to %s.\n", name);
203 }
204 
205 /*
206 ================
207 Con_ClearNotify
208 ================
209 */
Con_ClearNotify(void)210 void Con_ClearNotify (void)
211 {
212 	int		i;
213 
214 	for (i = 0; i < NUM_CON_TIMES; i++)
215 		con_times[i] = 0;
216 }
217 
218 
219 /*
220 ================
221 Con_MessageMode_f
222 ================
223 */
Con_MessageMode_f(void)224 static void Con_MessageMode_f (void)
225 {
226 	if (cls.state != ca_connected || cls.demoplayback)
227 		return;
228 	chat_team = false;
229 	key_dest = key_message;
230 }
231 
232 /*
233 ================
234 Con_MessageMode2_f
235 ================
236 */
Con_MessageMode2_f(void)237 static void Con_MessageMode2_f (void)
238 {
239 	if (cls.state != ca_connected || cls.demoplayback)
240 		return;
241 	chat_team = true;
242 	key_dest = key_message;
243 }
244 
245 
246 /*
247 ================
248 Con_CheckResize
249 
250 If the line width has changed, reformat the buffer.
251 ================
252 */
Con_CheckResize(void)253 void Con_CheckResize (void)
254 {
255 	int	i, j, width, oldwidth, oldtotallines, numlines, numchars;
256 	char	*tbuf; //johnfitz -- tbuf no longer a static array
257 	int mark; //johnfitz
258 
259 	width = (vid.conwidth >> 3) - 2; //johnfitz -- use vid.conwidth instead of vid.width
260 
261 	if (width == con_linewidth)
262 		return;
263 
264 	oldwidth = con_linewidth;
265 	con_linewidth = width;
266 	oldtotallines = con_totallines;
267 	con_totallines = con_buffersize / con_linewidth; //johnfitz -- con_buffersize replaces CON_TEXTSIZE
268 	numlines = oldtotallines;
269 
270 	if (con_totallines < numlines)
271 		numlines = con_totallines;
272 
273 	numchars = oldwidth;
274 
275 	if (con_linewidth < numchars)
276 		numchars = con_linewidth;
277 
278 	mark = Hunk_LowMark (); //johnfitz
279 	tbuf = (char *) Hunk_Alloc (con_buffersize); //johnfitz
280 
281 	Q_memcpy (tbuf, con_text, con_buffersize);//johnfitz -- con_buffersize replaces CON_TEXTSIZE
282 	Q_memset (con_text, ' ', con_buffersize);//johnfitz -- con_buffersize replaces CON_TEXTSIZE
283 
284 	for (i = 0; i < numlines; i++)
285 	{
286 		for (j = 0; j < numchars; j++)
287 		{
288 			con_text[(con_totallines - 1 - i) * con_linewidth + j] =
289 					tbuf[((con_current - i + oldtotallines) % oldtotallines) * oldwidth + j];
290 		}
291 	}
292 
293 	Hunk_FreeToLowMark (mark); //johnfitz
294 
295 	Con_ClearNotify ();
296 
297 	con_backscroll = 0;
298 	con_current = con_totallines - 1;
299 }
300 
301 
302 /*
303 ================
304 Con_Init
305 ================
306 */
Con_Init(void)307 void Con_Init (void)
308 {
309 	int i;
310 
311 	//johnfitz -- user settable console buffer size
312 	i = COM_CheckParm("-consize");
313 	if (i && i < com_argc-1)
314 		con_buffersize = q_max(CON_MINSIZE,Q_atoi(com_argv[i+1])*1024);
315 	else
316 		con_buffersize = CON_TEXTSIZE;
317 	//johnfitz
318 
319 	con_text = (char *) Hunk_AllocName (con_buffersize, "context");//johnfitz -- con_buffersize replaces CON_TEXTSIZE
320 	Q_memset (con_text, ' ', con_buffersize);//johnfitz -- con_buffersize replaces CON_TEXTSIZE
321 	con_linewidth = -1;
322 
323 	//johnfitz -- no need to run Con_CheckResize here
324 	con_linewidth = 38;
325 	con_totallines = con_buffersize / con_linewidth;//johnfitz -- con_buffersize replaces CON_TEXTSIZE
326 	con_backscroll = 0;
327 	con_current = con_totallines - 1;
328 	//johnfitz
329 
330 	Con_Printf ("Console initialized.\n");
331 
332 	Cvar_RegisterVariable (&con_notifytime);
333 	Cvar_RegisterVariable (&con_logcenterprint); //johnfitz
334 
335 	Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f);
336 	Cmd_AddCommand ("messagemode", Con_MessageMode_f);
337 	Cmd_AddCommand ("messagemode2", Con_MessageMode2_f);
338 	Cmd_AddCommand ("clear", Con_Clear_f);
339 	Cmd_AddCommand ("condump", Con_Dump_f); //johnfitz
340 	con_initialized = true;
341 }
342 
343 
344 /*
345 ===============
346 Con_Linefeed
347 ===============
348 */
Con_Linefeed(void)349 static void Con_Linefeed (void)
350 {
351 	//johnfitz -- improved scrolling
352 	if (con_backscroll)
353 		con_backscroll++;
354 	if (con_backscroll > con_totallines - (glheight>>3) - 1)
355 		con_backscroll = con_totallines - (glheight>>3) - 1;
356 	//johnfitz
357 
358 	con_x = 0;
359 	con_current++;
360 	Q_memset (&con_text[(con_current%con_totallines)*con_linewidth], ' ', con_linewidth);
361 }
362 
363 /*
364 ================
365 Con_Print
366 
367 Handles cursor positioning, line wrapping, etc
368 All console printing must go through this in order to be logged to disk
369 If no console is visible, the notify window will pop up.
370 ================
371 */
Con_Print(const char * txt)372 static void Con_Print (const char *txt)
373 {
374 	int		y;
375 	int		c, l;
376 	static int	cr;
377 	int		mask;
378 	qboolean	boundary;
379 
380 	//con_backscroll = 0; //johnfitz -- better console scrolling
381 
382 	if (txt[0] == 1)
383 	{
384 		mask = 128;		// go to colored text
385 		S_LocalSound ("misc/talk.wav");	// play talk wav
386 		txt++;
387 	}
388 	else if (txt[0] == 2)
389 	{
390 		mask = 128;		// go to colored text
391 		txt++;
392 	}
393 	else
394 		mask = 0;
395 
396 	boundary = true;
397 
398 	while ( (c = *txt) )
399 	{
400 		if (c <= ' ')
401 		{
402 			boundary = true;
403 		}
404 		else if (boundary)
405 		{
406 			// count word length
407 			for (l = 0; l < con_linewidth; l++)
408 				if (txt[l] <= ' ')
409 					break;
410 
411 			// word wrap
412 			if (l != con_linewidth && (con_x + l > con_linewidth))
413 				con_x = 0;
414 
415 			boundary = false;
416 		}
417 
418 		txt++;
419 
420 		if (cr)
421 		{
422 			con_current--;
423 			cr = false;
424 		}
425 
426 		if (!con_x)
427 		{
428 			Con_Linefeed ();
429 		// mark time for transparent overlay
430 			if (con_current >= 0)
431 				con_times[con_current % NUM_CON_TIMES] = realtime;
432 		}
433 
434 		switch (c)
435 		{
436 		case '\n':
437 			con_x = 0;
438 			break;
439 
440 		case '\r':
441 			con_x = 0;
442 			cr = 1;
443 			break;
444 
445 		default:	// display character and advance
446 			y = con_current % con_totallines;
447 			con_text[y*con_linewidth+con_x] = c | mask;
448 			con_x++;
449 			if (con_x >= con_linewidth)
450 				con_x = 0;
451 			break;
452 		}
453 	}
454 }
455 
456 
457 // borrowed from uhexen2 by S.A. for new procs, LOG_Init, LOG_Close
458 
459 static char	logfilename[MAX_OSPATH];	// current logfile name
460 static int	log_fd = -1;			// log file descriptor
461 
462 /*
463 ================
464 Con_DebugLog
465 ================
466 */
Con_DebugLog(const char * msg)467 void Con_DebugLog(const char *msg)
468 {
469 	if (log_fd == -1)
470 		return;
471 
472 	size_t msg_len = strlen(msg);
473 	if (write(log_fd, msg, msg_len) != msg_len)
474 		return; // Nonsense to supress warning
475 }
476 
477 
478 /*
479 ================
480 Con_Printf
481 
482 Handles cursor positioning, line wrapping, etc
483 ================
484 */
485 #define	MAXPRINTMSG	4096
Con_Printf(const char * fmt,...)486 void Con_Printf (const char *fmt, ...)
487 {
488 	va_list		argptr;
489 	char		msg[MAXPRINTMSG];
490 	static qboolean	inupdate;
491 
492 	va_start (argptr, fmt);
493 	q_vsnprintf (msg, sizeof(msg), fmt, argptr);
494 	va_end (argptr);
495 
496 	if (con_redirect_flush)
497 		q_strlcat(con_redirect_buffer, msg, sizeof(con_redirect_buffer));
498 // also echo to debugging console
499 	Sys_Printf ("%s", msg);
500 
501 // log all messages to file
502 	if (con_debuglog)
503 		Con_DebugLog(msg);
504 
505 	if (!con_initialized)
506 		return;
507 
508 	if (cls.state == ca_dedicated)
509 		return;		// no graphics mode
510 
511 // write it to the scrollable buffer
512 	Con_Print (msg);
513 
514 // update the screen if the console is displayed
515 	if (cls.signon != SIGNONS && !scr_disabled_for_loading )
516 	{
517 	// protect against infinite loop if something in SCR_UpdateScreen calls
518 	// Con_Printd
519 		if (!inupdate)
520 		{
521 			inupdate = true;
522 			SCR_UpdateScreen ();
523 			inupdate = false;
524 		}
525 	}
526 }
527 
528 /*
529 ================
530 Con_DWarning -- ericw
531 
532 same as Con_Warning, but only prints if "developer" cvar is set.
533 use for "exceeds standard limit of" messages, which are only relevant for developers
534 targetting vanilla engines
535 ================
536 */
Con_DWarning(const char * fmt,...)537 void Con_DWarning (const char *fmt, ...)
538 {
539 	va_list		argptr;
540 	char		msg[MAXPRINTMSG];
541 
542 	if (developer.value >= 2)
543 	{	// don't confuse non-developers with techie stuff...
544 		// (this is limit exceeded warnings)
545 
546 		va_start (argptr, fmt);
547 		q_vsnprintf (msg, sizeof(msg), fmt, argptr);
548 		va_end (argptr);
549 
550 		Con_SafePrintf ("\x02Warning: ");
551 		Con_Printf ("%s", msg);
552 	}
553 }
554 
555 /*
556 ================
557 Con_Warning -- johnfitz -- prints a warning to the console
558 ================
559 */
Con_Warning(const char * fmt,...)560 void Con_Warning (const char *fmt, ...)
561 {
562 	va_list		argptr;
563 	char		msg[MAXPRINTMSG];
564 
565 	va_start (argptr, fmt);
566 	q_vsnprintf (msg, sizeof(msg), fmt, argptr);
567 	va_end (argptr);
568 
569 	Con_SafePrintf ("\x02Warning: ");
570 	Con_Printf ("%s", msg);
571 }
572 
573 /*
574 ================
575 Con_DPrintf
576 
577 A Con_Printf that only shows up if the "developer" cvar is set
578 ================
579 */
Con_DPrintf(const char * fmt,...)580 void Con_DPrintf (const char *fmt, ...)
581 {
582 	va_list		argptr;
583 	char		msg[MAXPRINTMSG];
584 
585 	if (!developer.value)
586 		return;			// don't confuse non-developers with techie stuff...
587 
588 	va_start (argptr, fmt);
589 	q_vsnprintf (msg, sizeof(msg), fmt, argptr);
590 	va_end (argptr);
591 
592 	Con_SafePrintf ("%s", msg); //johnfitz -- was Con_Printf
593 }
594 
595 /*
596 ================
597 Con_DPrintf2 -- johnfitz -- only prints if "developer" >= 2
598 
599 currently not used
600 ================
601 */
Con_DPrintf2(const char * fmt,...)602 void Con_DPrintf2 (const char *fmt, ...)
603 {
604 	va_list		argptr;
605 	char		msg[MAXPRINTMSG];
606 
607 	if (developer.value >= 2)
608 	{
609 		va_start (argptr, fmt);
610 		q_vsnprintf (msg, sizeof(msg), fmt, argptr);
611 		va_end (argptr);
612 		Con_Printf ("%s", msg);
613 	}
614 }
615 
616 
617 /*
618 ==================
619 Con_SafePrintf
620 
621 Okay to call even when the screen can't be updated
622 ==================
623 */
Con_SafePrintf(const char * fmt,...)624 void Con_SafePrintf (const char *fmt, ...)
625 {
626 	va_list		argptr;
627 	char		msg[1024];
628 	int		temp;
629 
630 	va_start (argptr, fmt);
631 	q_vsnprintf (msg, sizeof(msg), fmt, argptr);
632 	va_end (argptr);
633 
634 	temp = scr_disabled_for_loading;
635 	scr_disabled_for_loading = true;
636 	Con_Printf ("%s", msg);
637 	scr_disabled_for_loading = temp;
638 }
639 
640 /*
641 ================
642 Con_CenterPrintf -- johnfitz -- pad each line with spaces to make it appear centered
643 ================
644 */
645 void Con_CenterPrintf (int linewidth, const char *fmt, ...) FUNC_PRINTF(2,3);
Con_CenterPrintf(int linewidth,const char * fmt,...)646 void Con_CenterPrintf (int linewidth, const char *fmt, ...)
647 {
648 	va_list	argptr;
649 	char	msg[MAXPRINTMSG]; //the original message
650 	char	line[MAXPRINTMSG]; //one line from the message
651 	char	spaces[21]; //buffer for spaces
652 	char	*src, *dst;
653 	int		len, s;
654 
655 	va_start (argptr, fmt);
656 	q_vsnprintf (msg, sizeof(msg), fmt, argptr);
657 	va_end (argptr);
658 
659 	linewidth = q_min(linewidth, con_linewidth);
660 	for (src = msg; *src; )
661 	{
662 		dst = line;
663 		while (*src && *src != '\n')
664 			*dst++ = *src++;
665 		*dst = 0;
666 		if (*src == '\n')
667 			src++;
668 
669 		len = strlen(line);
670 		if (len < linewidth)
671 		{
672 			s = (linewidth-len)/2;
673 			memset (spaces, ' ', s);
674 			spaces[s] = 0;
675 			Con_Printf ("%s%s\n", spaces, line);
676 		}
677 		else
678 			Con_Printf ("%s\n", line);
679 	}
680 }
681 
682 /*
683 ==================
684 Con_LogCenterPrint -- johnfitz -- echo centerprint message to the console
685 ==================
686 */
Con_LogCenterPrint(const char * str)687 void Con_LogCenterPrint (const char *str)
688 {
689 	if (!strcmp(str, con_lastcenterstring))
690 		return; //ignore duplicates
691 
692 	if (cl.gametype == GAME_DEATHMATCH && con_logcenterprint.value != 2)
693 		return; //don't log in deathmatch
694 
695 	strcpy(con_lastcenterstring, str);
696 
697 	if (con_logcenterprint.value)
698 	{
699 		Con_Printf ("%s", Con_Quakebar(40));
700 		Con_CenterPrintf (40, "%s\n", str);
701 		Con_Printf ("%s", Con_Quakebar(40));
702 		Con_ClearNotify ();
703 	}
704 }
705 
Con_IsRedirected(void)706 qboolean Con_IsRedirected(void)
707 {
708 	return !!con_redirect_flush;
709 }
Con_Redirect(void (* flush)(const char *))710 void Con_Redirect(void(*flush)(const char *))
711 {
712 	if (con_redirect_flush)
713 		con_redirect_flush(con_redirect_buffer);
714 	*con_redirect_buffer = 0;
715 	con_redirect_flush = flush;
716 }
717 
718 /*
719 ==============================================================================
720 
721 	TAB COMPLETION
722 
723 ==============================================================================
724 */
725 
726 //johnfitz -- tab completion stuff
727 //unique defs
728 char key_tabpartial[MAXCMDLINE];
729 typedef struct tab_s
730 {
731 	const char	*name;
732 	const char	*type;
733 	struct tab_s	*next;
734 	struct tab_s	*prev;
735 } tab_t;
736 tab_t	*tablist;
737 
738 //defs from elsewhere
739 extern qboolean	keydown[256];
740 extern	cmd_function_t	*cmd_functions;
741 #define	MAX_ALIAS_NAME	32
742 typedef struct cmdalias_s
743 {
744 	struct cmdalias_s	*next;
745 	char	name[MAX_ALIAS_NAME];
746 	char	*value;
747 } cmdalias_t;
748 extern	cmdalias_t	*cmd_alias;
749 
750 /*
751 ============
752 AddToTabList -- johnfitz
753 
754 tablist is a doubly-linked loop, alphabetized by name
755 ============
756 */
757 
758 // bash_partial is the string that can be expanded,
759 // aka Linux Bash shell. -- S.A.
760 static char	bash_partial[80];
761 static qboolean	bash_singlematch;
762 
AddToTabList(const char * name,const char * type)763 void AddToTabList (const char *name, const char *type)
764 {
765 	tab_t	*t,*insert;
766 	char	*i_bash;
767 	const char *i_name;
768 
769 	if (!*bash_partial)
770 	{
771 		strncpy (bash_partial, name, 79);
772 		bash_partial[79] = '\0';
773 	}
774 	else
775 	{
776 		bash_singlematch = 0;
777 		// find max common between bash_partial and name
778 		i_bash = bash_partial;
779 		i_name = name;
780 		while (*i_bash && (*i_bash == *i_name))
781 		{
782 			i_bash++;
783 			i_name++;
784 		}
785 		*i_bash = 0;
786 	}
787 
788 	t = (tab_t *) Hunk_Alloc(sizeof(tab_t));
789 	t->name = name;
790 	t->type = type;
791 
792 	if (!tablist) //create list
793 	{
794 		tablist = t;
795 		t->next = t;
796 		t->prev = t;
797 	}
798 	else if (strcmp(name, tablist->name) < 0) //insert at front
799 	{
800 		t->next = tablist;
801 		t->prev = tablist->prev;
802 		t->next->prev = t;
803 		t->prev->next = t;
804 		tablist = t;
805 	}
806 	else //insert later
807 	{
808 		insert = tablist;
809 		do
810 		{
811 			if (strcmp(name, insert->name) < 0)
812 				break;
813 			insert = insert->next;
814 		} while (insert != tablist);
815 
816 		t->next = insert;
817 		t->prev = insert->prev;
818 		t->next->prev = t;
819 		t->prev->next = t;
820 	}
821 }
822 
823 typedef struct arg_completion_type_s
824 {
825 	const char		*command;
826 	filelist_item_t	**filelist;
827 } arg_completion_type_t;
828 
829 static const arg_completion_type_t arg_completion_types[] =
830 {
831 	{ "map ", &extralevels },
832 	{ "changelevel ", &extralevels },
833 	{ "game ", &modlist },
834 	{ "record ", &demolist },
835 	{ "playdemo ", &demolist },
836 	{ "timedemo ", &demolist }
837 };
838 
839 static const int num_arg_completion_types =
840 	sizeof(arg_completion_types)/sizeof(arg_completion_types[0]);
841 
842 /*
843 ============
844 FindCompletion -- stevenaaus
845 ============
846 */
FindCompletion(const char * partial,filelist_item_t * filelist,int * nummatches_out)847 const char *FindCompletion (const char *partial, filelist_item_t *filelist, int *nummatches_out)
848 {
849 	static char matched[32];
850 	char *i_matched, *i_name;
851 	filelist_item_t	*file;
852 	int   init, match, plen;
853 
854 	memset(matched, 0, sizeof(matched));
855 	plen = strlen(partial);
856 	match = 0;
857 
858 	for (file = filelist, init = 0; file; file = file->next)
859 	{
860 		if (!strncmp(file->name, partial, plen))
861 		{
862 			if (init == 0)
863 			{
864 				init = 1;
865 				strncpy (matched, file->name, sizeof(matched)-1);
866 				matched[sizeof(matched)-1] = '\0';
867 			}
868 			else
869 			{ // find max common
870 				i_matched = matched;
871 				i_name = file->name;
872 				while (*i_matched && (*i_matched == *i_name))
873 				{
874 					i_matched++;
875 					i_name++;
876 				}
877 				*i_matched = 0;
878 			}
879 			match++;
880 		}
881 	}
882 
883 	*nummatches_out = match;
884 
885 	if (match > 1)
886 	{
887 		for (file = filelist; file; file = file->next)
888 		{
889 			if (!strncmp(file->name, partial, plen))
890 				Con_SafePrintf ("   %s\n", file->name);
891 		}
892 		Con_SafePrintf ("\n");
893 	}
894 
895 	return matched;
896 }
897 
898 /*
899 ============
900 BuildTabList -- johnfitz
901 ============
902 */
BuildTabList(const char * partial)903 void BuildTabList (const char *partial)
904 {
905 	cmdalias_t		*alias;
906 	cvar_t			*cvar;
907 	cmd_function_t		*cmd;
908 	int		len;
909 
910 	tablist = NULL;
911 	len = strlen(partial);
912 
913 	bash_partial[0] = 0;
914 	bash_singlematch = 1;
915 
916 	cvar = Cvar_FindVarAfter ("", CVAR_NONE);
917 	for ( ; cvar ; cvar=cvar->next)
918 		if (!Q_strncmp (partial, cvar->name, len))
919 			AddToTabList (cvar->name, "cvar");
920 
921 	for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
922 		if (!Q_strncmp (partial,cmd->name, len))
923 			AddToTabList (cmd->name, "command");
924 
925 	for (alias=cmd_alias ; alias ; alias=alias->next)
926 		if (!Q_strncmp (partial, alias->name, len))
927 			AddToTabList (alias->name, "alias");
928 }
929 
930 /*
931 ============
932 Con_TabComplete -- johnfitz
933 ============
934 */
Con_TabComplete(void)935 void Con_TabComplete (void)
936 {
937 	char	partial[MAXCMDLINE];
938 	const char	*match;
939 	static char	*c;
940 	tab_t		*t;
941 	int		mark, i, j;
942 
943 // if editline is empty, return
944 	if (key_lines[edit_line][1] == 0)
945 		return;
946 
947 // get partial string (space -> cursor)
948 	if (!key_tabpartial[0]) //first time through, find new insert point. (Otherwise, use previous.)
949 	{
950 		//work back from cursor until you find a space, quote, semicolon, or prompt
951 		c = key_lines[edit_line] + key_linepos - 1; //start one space left of cursor
952 		while (*c!=' ' && *c!='\"' && *c!=';' && c!=key_lines[edit_line])
953 			c--;
954 		c++; //start 1 char after the separator we just found
955 	}
956 	for (i = 0; c + i < key_lines[edit_line] + key_linepos; i++)
957 		partial[i] = c[i];
958 	partial[i] = 0;
959 
960 // Map autocomplete function -- S.A
961 // Since we don't have argument completion, this hack will do for now...
962 	for (j=0; j<num_arg_completion_types; j++)
963 	{
964 	// arg_completion contains a command we can complete the arguments
965 	// for (like "map ") and a list of all the maps.
966 		arg_completion_type_t arg_completion = arg_completion_types[j];
967 		const char *command_name = arg_completion.command;
968 
969 		if (!strncmp (key_lines[edit_line] + 1, command_name, strlen(command_name)))
970 		{
971 			int nummatches = 0;
972 			const char *matched_map = FindCompletion(partial, *arg_completion.filelist, &nummatches);
973 			if (!*matched_map)
974 				return;
975 			q_strlcpy (partial, matched_map, MAXCMDLINE);
976 			*c = '\0';
977 			q_strlcat (key_lines[edit_line], partial, MAXCMDLINE);
978 			key_linepos = c - key_lines[edit_line] + Q_strlen(matched_map); //set new cursor position
979 			if (key_linepos >= MAXCMDLINE)
980 				key_linepos = MAXCMDLINE - 1;
981 			// if only one match, append a space
982 			if (key_linepos < MAXCMDLINE - 1 &&
983 			    key_lines[edit_line][key_linepos] == 0 && (nummatches == 1))
984 			{
985 				key_lines[edit_line][key_linepos] = ' ';
986 				key_linepos++;
987 				key_lines[edit_line][key_linepos] = 0;
988 			}
989 			c = key_lines[edit_line] + key_linepos;
990 			return;
991 		}
992 	}
993 
994 //if partial is empty, return
995 	if (partial[0] == 0)
996 		return;
997 
998 //trim trailing space becuase it screws up string comparisons
999 	if (i > 0 && partial[i-1] == ' ')
1000 		partial[i-1] = 0;
1001 
1002 // find a match
1003 	mark = Hunk_LowMark();
1004 	if (!key_tabpartial[0]) //first time through
1005 	{
1006 		q_strlcpy (key_tabpartial, partial, MAXCMDLINE);
1007 		BuildTabList (key_tabpartial);
1008 
1009 		if (!tablist)
1010 			return;
1011 
1012 		// print list if length > 1
1013 		if (tablist->next != tablist)
1014 		{
1015 			t = tablist;
1016 			Con_SafePrintf("\n");
1017 			do
1018 			{
1019 				Con_SafePrintf("   %s (%s)\n", t->name, t->type);
1020 				t = t->next;
1021 			} while (t != tablist);
1022 			Con_SafePrintf("\n");
1023 		}
1024 
1025 	//	match = tablist->name;
1026 	// First time, just show maximum matching chars -- S.A.
1027 		match = bash_partial;
1028 	}
1029 	else
1030 	{
1031 		BuildTabList (key_tabpartial);
1032 
1033 		if (!tablist)
1034 			return;
1035 
1036 		//find current match -- can't save a pointer because the list will be rebuilt each time
1037 		t = tablist;
1038 		match = keydown[K_SHIFT] ? t->prev->name : t->name;
1039 		do
1040 		{
1041 			if (!Q_strcmp(t->name, partial))
1042 			{
1043 				match = keydown[K_SHIFT] ? t->prev->name : t->next->name;
1044 				break;
1045 			}
1046 			t = t->next;
1047 		} while (t != tablist);
1048 	}
1049 	Hunk_FreeToLowMark(mark); //it's okay to free it here because match is a pointer to persistent data
1050 
1051 // insert new match into edit line
1052 	q_strlcpy (partial, match, MAXCMDLINE); //first copy match string
1053 	q_strlcat (partial, key_lines[edit_line] + key_linepos, MAXCMDLINE); //then add chars after cursor
1054 	*c = '\0';	//now copy all of this into edit line
1055 	q_strlcat (key_lines[edit_line], partial, MAXCMDLINE);
1056 	key_linepos = c - key_lines[edit_line] + Q_strlen(match); //set new cursor position
1057 	if (key_linepos >= MAXCMDLINE)
1058 		key_linepos = MAXCMDLINE - 1;
1059 
1060 // if cursor is at end of string, let's append a space to make life easier
1061 	if (key_linepos < MAXCMDLINE - 1 &&
1062 	    key_lines[edit_line][key_linepos] == 0 && bash_singlematch)
1063 	{
1064 		key_lines[edit_line][key_linepos] = ' ';
1065 		key_linepos++;
1066 		key_lines[edit_line][key_linepos] = 0;
1067 	// S.A.: the map argument completion (may be in combination with the bash-style
1068 	// display behavior changes, causes weirdness when completing the arguments for
1069 	// the changelevel command. the line below "fixes" it, although I'm not sure about
1070 	// the reason, yet, neither do I know any possible side effects of it:
1071 		c = key_lines[edit_line] + key_linepos;
1072 	}
1073 }
1074 
1075 /*
1076 ==============================================================================
1077 
1078 DRAWING
1079 
1080 ==============================================================================
1081 */
1082 
1083 /*
1084 ================
1085 Con_DrawNotify
1086 
1087 Draws the last few lines of output transparently over the game top
1088 ================
1089 */
Con_DrawNotify(void)1090 void Con_DrawNotify (void)
1091 {
1092 	int	i, x, v;
1093 	const char	*text;
1094 	float	time;
1095 
1096 	GL_SetCanvas (CANVAS_CONSOLE); //johnfitz
1097 	v = vid.conheight; //johnfitz
1098 
1099 	for (i = con_current-NUM_CON_TIMES+1; i <= con_current; i++)
1100 	{
1101 		if (i < 0)
1102 			continue;
1103 		time = con_times[i % NUM_CON_TIMES];
1104 		if (time == 0)
1105 			continue;
1106 		time = realtime - time;
1107 		if (time > con_notifytime.value)
1108 			continue;
1109 		text = con_text + (i % con_totallines)*con_linewidth;
1110 
1111 		clearnotify = 0;
1112 
1113 		for (x = 0; x < con_linewidth; x++)
1114 			Draw_Character ((x+1)<<3, v, text[x]);
1115 
1116 		v += 8;
1117 
1118 		scr_tileclear_updates = 0; //johnfitz
1119 	}
1120 
1121 	if (key_dest == key_message)
1122 	{
1123 		clearnotify = 0;
1124 
1125 		if (chat_team)
1126 		{
1127 			Draw_String (8, v, "say_team:");
1128 			x = 11;
1129 		}
1130 		else
1131 		{
1132 			Draw_String (8, v, "say:");
1133 			x = 6;
1134 		}
1135 
1136 		text = Key_GetChatBuffer();
1137 		i = Key_GetChatMsgLen();
1138 		if (i > con_linewidth - x - 1)
1139 			text += i - con_linewidth + x + 1;
1140 
1141 		while (*text)
1142 		{
1143 			Draw_Character (x<<3, v, *text);
1144 			x++;
1145 			text++;
1146 		}
1147 
1148 		Draw_Character (x<<3, v, 10 + ((int)(realtime*con_cursorspeed)&1));
1149 		v += 8;
1150 
1151 		scr_tileclear_updates = 0; //johnfitz
1152 	}
1153 }
1154 
1155 /*
1156 ================
1157 Con_DrawInput -- johnfitz -- modified to allow insert editing
1158 
1159 The input line scrolls horizontally if typing goes beyond the right edge
1160 ================
1161 */
1162 extern	qpic_t *pic_ovr, *pic_ins; //johnfitz -- new cursor handling
1163 
Con_DrawInput(void)1164 void Con_DrawInput (void)
1165 {
1166 	int	i, ofs;
1167 
1168 	if (key_dest != key_console && !con_forcedup)
1169 		return;		// don't draw anything
1170 
1171 // prestep if horizontally scrolling
1172 	if (key_linepos >= con_linewidth)
1173 		ofs = 1 + key_linepos - con_linewidth;
1174 	else
1175 		ofs = 0;
1176 
1177 // draw input string
1178 	for (i = 0; key_lines[edit_line][i+ofs] && i < con_linewidth; i++)
1179 		Draw_Character ((i+1)<<3, vid.conheight - 16, key_lines[edit_line][i+ofs]);
1180 
1181 // johnfitz -- new cursor handling
1182 	if (!((int)((realtime-key_blinktime)*con_cursorspeed) & 1))
1183 	{
1184 		i = key_linepos - ofs;
1185 		Draw_Pic ((i+1)<<3, vid.conheight - 16, key_insert ? pic_ins : pic_ovr, 1.0f, false);
1186 	}
1187 }
1188 
1189 /*
1190 ================
1191 Con_DrawConsole -- johnfitz -- heavy revision
1192 
1193 Draws the console with the solid background
1194 The typing input line at the bottom should only be drawn if typing is allowed
1195 ================
1196 */
Con_DrawConsole(int lines,qboolean drawinput)1197 void Con_DrawConsole (int lines, qboolean drawinput)
1198 {
1199 	int	i, x, y, j, sb, rows;
1200 	const char	*text;
1201 	char	ver[32];
1202 
1203 	if (lines <= 0)
1204 		return;
1205 
1206 	con_vislines = lines * vid.conheight / glheight;
1207 	GL_SetCanvas (CANVAS_CONSOLE);
1208 
1209 // draw the background
1210 	Draw_ConsoleBackground ();
1211 
1212 // draw the buffer text
1213 	rows = (con_vislines +7)/8;
1214 	y = vid.conheight - rows*8;
1215 	rows -= 2; //for input and version lines
1216 	sb = (con_backscroll) ? 2 : 0;
1217 
1218 	for (i = con_current - rows + 1; i <= con_current - sb; i++, y += 8)
1219 	{
1220 		j = i - con_backscroll;
1221 		if (j < 0)
1222 			j = 0;
1223 		text = con_text + (j % con_totallines)*con_linewidth;
1224 
1225 		for (x = 0; x < con_linewidth; x++)
1226 			Draw_Character ( (x + 1)<<3, y, text[x]);
1227 	}
1228 
1229 // draw scrollback arrows
1230 	if (con_backscroll)
1231 	{
1232 		y += 8; // blank line
1233 		for (x = 0; x < con_linewidth; x += 4)
1234 			Draw_Character ((x + 1)<<3, y, '^');
1235 		y += 8;
1236 	}
1237 
1238 // draw the input prompt, user text, and cursor
1239 	if (drawinput)
1240 		Con_DrawInput ();
1241 
1242 //draw version number in bottom right
1243 	y += 8;
1244 	q_snprintf (ver, sizeof(ver), "vkQuake " VKQUAKE_VER_STRING);
1245 	for (x = 0; x < (int)strlen(ver); x++)
1246 		Draw_Character ((con_linewidth - strlen(ver) + x + 2)<<3, y, ver[x] /*+ 128*/);
1247 }
1248 
1249 
1250 /*
1251 ==================
1252 Con_NotifyBox
1253 ==================
1254 */
Con_NotifyBox(const char * text)1255 void Con_NotifyBox (const char *text)
1256 {
1257 	double		t1, t2;
1258 	int		lastkey, lastchar;
1259 
1260 // during startup for sound / cd warnings
1261 	Con_Printf ("\n\n%s", Con_Quakebar(40)); //johnfitz
1262 	Con_Printf ("%s", text);
1263 	Con_Printf ("Press a key.\n");
1264 	Con_Printf ("%s", Con_Quakebar(40)); //johnfitz
1265 
1266 	IN_Deactivate(modestate == MS_WINDOWED);
1267 	key_dest = key_console;
1268 
1269 	Key_BeginInputGrab ();
1270 	do
1271 	{
1272 		t1 = Sys_DoubleTime ();
1273 		SCR_UpdateScreen ();
1274 		Sys_SendKeyEvents ();
1275 		Key_GetGrabbedInput (&lastkey, &lastchar);
1276 		Sys_Sleep (16);
1277 		t2 = Sys_DoubleTime ();
1278 		realtime += t2-t1;		// make the cursor blink
1279 	} while (lastkey == -1 && lastchar == -1);
1280 	Key_EndInputGrab ();
1281 
1282 	Con_Printf ("\n");
1283 	IN_Activate();
1284 	key_dest = key_game;
1285 	realtime = 0;		// put the cursor back to invisible
1286 }
1287 
1288 
LOG_Init(quakeparms_t * parms)1289 void LOG_Init (quakeparms_t *parms)
1290 {
1291 	time_t	inittime;
1292 	char	session[24];
1293 
1294 	if (!COM_CheckParm("-condebug"))
1295 		return;
1296 
1297 	inittime = time (NULL);
1298 	strftime (session, sizeof(session), "%m/%d/%Y %H:%M:%S", localtime(&inittime));
1299 	q_snprintf (logfilename, sizeof(logfilename), "%s/qconsole.log", parms->basedir);
1300 
1301 //	unlink (logfilename);
1302 
1303 	log_fd = open (logfilename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
1304 	if (log_fd == -1)
1305 	{
1306 		fprintf (stderr, "Error: Unable to create log file %s\n", logfilename);
1307 		return;
1308 	}
1309 
1310 	con_debuglog = true;
1311 	Con_DebugLog (va("LOG started on: %s \n", session));
1312 
1313 }
1314 
LOG_Close(void)1315 void LOG_Close (void)
1316 {
1317 	if (log_fd == -1)
1318 		return;
1319 	close (log_fd);
1320 	log_fd = -1;
1321 }
1322 
1323