1 //**************************************************************************
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
13 //** This program is free software: you can redistribute it and/or modify
14 //** it under the terms of the GNU General Public License as published by
15 //** the Free Software Foundation, version 3 of the License ONLY.
17 //** This program is distributed in the hope that it will be useful,
18 //** but WITHOUT ANY WARRANTY; without even the implied warranty of
19 //** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 //** GNU General Public License for more details.
22 //** You should have received a copy of the GNU General Public License
23 //** along with this program. If not, see <http://www.gnu.org/licenses/>.
25 //**************************************************************************
26 #ifdef VAVOOM_CUSTOM_SPECIAL_SDL
29 # include <SDL2/SDL.h>
32 #include "host.h" /* host_frametime */
42 # include <android/log.h>
45 #define MAXHISTORY (64)
46 #define MAX_LINES (1024)
47 #define MAX_LINE_LENGTH (80)
50 extern const char *cli_LogFileName
;
52 static bool GConTTYLogPrevious
= false;
53 static bool GConTTYLogDisabled
= false;
54 static bool GConSplashActive
= false;
55 bool GConTTYLogForced
= true;
58 //==========================================================================
62 //==========================================================================
63 void C_SplashActive (bool v
) {
68 //==========================================================================
72 //==========================================================================
73 void C_DisableTTYLogs () {
74 if (GConTTYLogDisabled
|| GConTTYLogForced
) return;
76 if (GLogTTYLog
) GCon
->Logf(NAME_Warning
, "tty logs disabled to avoid random slowdowns and disconnects.");
78 GConTTYLogDisabled
= true;
79 GConTTYLogPrevious
= GLogTTYLog
;
84 //==========================================================================
88 //==========================================================================
89 void C_EnableTTYLogs () {
90 if (!GConTTYLogDisabled
|| GConTTYLogForced
) return;
91 GConTTYLogDisabled
= false;
92 GLogTTYLog
= GConTTYLogPrevious
;
94 if (GLogTTYLog
) GCon
->Logf(NAME_Warning
, "tty logs reenabled.");
109 class FConsoleDevice
: public FOutputDevice
{
111 FConsoleDevice () noexcept
;
112 virtual void Serialise (const char *V
, EName Event
) noexcept override
;
116 class FConsoleLog
: public VLogListener
{
118 virtual void Serialise (const char *V
, EName Event
) noexcept override
;
122 static mythread_mutex conLogLock
;
123 FConsoleDevice Console
;
124 FOutputDevice
*GCon
= &Console
;
126 FConsoleDevice::FConsoleDevice () noexcept
{
127 mythread_mutex_init(&conLogLock
);
131 static TILine c_iline
;
133 static cons_state_t consolestate
= cons_closed
;
140 ConLine () : str(nullptr), len(0), alloced(0) {}
143 static ConLine clines
[MAX_LINES
];
144 static int num_lines
= 0;
145 static int first_line
= 0;
146 static int last_line
= 0;
152 int bufsize
; // including trailing zero; NOT line length!
155 VV_DISABLE_COPY(HistoryLine
)
157 HistoryLine () noexcept
: str(nullptr), bufsize(0) {}
158 ~HistoryLine () noexcept
{ Z_Free(str
); str
= nullptr; bufsize
= 0; }
160 inline void swap (HistoryLine
&src
) noexcept
{
161 if (&src
== this) return;
162 { char *tmp
= str
; str
= src
.str
; src
.str
= tmp
; }
163 { int tmp
= bufsize
; bufsize
= src
.bufsize
; src
.bufsize
= tmp
; }
166 inline const char *getCStr () const noexcept
{ return (str
? str
: ""); }
168 inline bool strEqu (const char *s
, bool doStrip
) const noexcept
{
169 if (!str
) return false; // not initialized
170 if (!s
|| !s
[0]) return (str
[0] == 0);
171 if (doStrip
) return strEqu(VStr(s
), true);
172 return (VStr::Cmp(str
, s
) == 0);
175 inline bool strEqu (VStr s
, bool doStrip
) const noexcept
{
176 if (!str
) return false; // not initialized
177 if (s
.isEmpty()) return (str
[0] == 0);
178 if (!doStrip
) return s
.strEqu(str
);
179 return s
.xstrip().strEqu(VStr(str
).xstrip());
182 inline void putStr (const char *s
) noexcept
{
184 int slen
= (int)strlen(s
)+1;
185 int newsz
= (slen
|0xff)+1;
186 if (bufsize
< newsz
) {
187 str
= (char *)Z_Realloc(str
, newsz
);
194 static int c_history_size
= 0;
195 static int c_history_current
= -1;
196 static HistoryLine c_history
[MAXHISTORY
]; // 0 is oldest
199 static float cons_h
= 0;
201 static VCvarF
con_height("con_height", "240", "Console height.", CVAR_Archive
|CVAR_NoShadow
);
202 static VCvarF
con_speed("con_speed", "6666", "Console sliding speed.", CVAR_Archive
|CVAR_NoShadow
);
203 static VCvarB
con_clear_input_on_open("con_clear_input_on_open", true, "Clear input line when console opens?", CVAR_Archive
|CVAR_NoShadow
);
205 static FConsoleLog ConsoleLog
;
207 static FILE *logfout
= nullptr;
210 //==========================================================================
212 // onShowCompletionMatchCB
214 //==========================================================================
215 static void onShowCompletionMatchCB (bool isheader
, VStr s
) {
217 GCon
->Logf("\034K%s", *s
);
219 GCon
->Logf("\034D %s", *s
);
224 //==========================================================================
226 // C_SysErrorCallback
228 //==========================================================================
229 static void C_SysErrorCallback (const char *msg
) noexcept
{
231 fprintf(logfout
, "%s\n", (msg
? msg
: ""));
238 //==========================================================================
242 // Console initialization
244 //==========================================================================
246 VCommand::onShowCompletionMatch
= &onShowCompletionMatchCB
;
248 if (cli_LogFileName
&& cli_LogFileName
[0]) {
249 logfout
= fopen(cli_LogFileName
, "w");
252 #if defined(_WIN32) || (defined(__SWITCH__) && !defined(SWITCH_NXLINK))
253 if (!logfout
) logfout
= fopen("conlog.log", "w");
256 SysErrorCB
= &C_SysErrorCallback
;
258 memset((void *)&clines
[0], 0, sizeof(clines
));
260 c_history_current
= -1;
261 for (int f
= 0; f
< MAXHISTORY
; ++f
) {
262 vassert(c_history
[f
].str
== nullptr);
263 vassert(c_history
[f
].bufsize
== 0);
265 GLog
.AddListener(&ConsoleLog
);
267 // prompt, cursor, and one char reserved
268 c_iline
.SetVisChars(MAX_LINE_LENGTH
-3);
273 //==========================================================================
277 //==========================================================================
279 if (logfout
) fclose(logfout
);
284 //==========================================================================
290 //==========================================================================
291 void C_Start (bool immediate
) {
293 MyThreadLocker
lock(&conLogLock
);
294 if (consolestate
== cons_closed
) {
295 if (con_clear_input_on_open
) c_iline
.Init();
296 last_line
= num_lines
;
298 consolestate
= (immediate
? cons_opening_imm
: cons_opening
);
299 c_history_current
= -1;
301 SDL_StartTextInput();
306 //==========================================================================
310 //==========================================================================
311 void C_StartFull () {
313 MyThreadLocker
lock(&conLogLock
);
315 last_line
= num_lines
;
316 consolestate
= cons_open
;
317 c_history_current
= -1;
319 cons_h
= fmax(con_height
.asFloat(), 128.0f
);
321 SDL_StartTextInput();
326 //==========================================================================
332 //==========================================================================
333 void C_Stop (bool immediate
) {
335 consolestate
= (immediate
? cons_closing_imm
: cons_closing
);
339 //==========================================================================
343 //==========================================================================
344 COMMAND(ToggleConsole
) {
346 if (consolestate
== cons_closed
) C_Start(); else C_Stop();
350 //==========================================================================
354 //==========================================================================
355 COMMAND(ShowConsole
) {
356 C_Start(Args
.length() > 1 && Args
[1].startsWithCI("imm"));
360 //==========================================================================
364 //==========================================================================
365 COMMAND(HideConsole
) {
366 C_Stop(Args
.length() > 1 && Args
[1].startsWithCI("imm"));
370 //==========================================================================
374 //==========================================================================
376 return (consolestate
== cons_opening
|| consolestate
== cons_open
);
380 //==========================================================================
384 // font and text mode should be already set
386 //==========================================================================
387 static void DrawInputLine (int y
) {
389 c_iline
.cursorChar
= '\x0b';
390 T_DrawText(4, y
, ">", CR_YELLOW
);
391 c_iline
.DrawAt(12, y
, CR_ORANGE
, CR_FIRE
);
395 //==========================================================================
401 //==========================================================================
403 // scroll console up when closing
404 if (consolestate
== cons_closing
) {
405 cons_h
-= con_speed
*host_frametime
;
409 consolestate
= cons_closed
;
411 } else if (consolestate
== cons_closing_imm
) {
413 consolestate
= cons_closed
;
416 // scroll console down when opening
417 if (consolestate
== cons_opening
) {
418 cons_h
+= con_speed
*host_frametime
;
419 if (cons_h
>= con_height
) {
422 consolestate
= cons_open
;
424 } else if (consolestate
== cons_opening_imm
) {
426 consolestate
= cons_open
;
429 if (!consolestate
) return;
432 Drawer
->DrawConsoleBackground((int)(fScaleY
*cons_h
));
435 T_SetAlign(hleft
, vtop
);
438 int y
= (int)cons_h
-10;
443 MyThreadLocker
lock(&conLogLock
);
445 while ((y
+9 > 0) && i
--) {
446 int lidx
= (i
+first_line
)%MAX_LINES
;
447 const ConLine
&line
= clines
[lidx
];
448 int trans
= CR_UNTRANSLATED
;
449 //if (line[0] == 1) { trans = line[1]; line += 2; }
450 T_DrawText(4, y
, (line
.str
? line
.str
: ""), trans
);
456 //==========================================================================
460 // Handles the events
462 //==========================================================================
463 bool C_Responder (event_t
*ev
) {
467 // respond to events only when console is active
468 if (!C_Active()) return false;
470 // we are iterested only in key down events
471 if (ev
->type
!= ev_keydown
) return false;
472 // k8: nope, eat all keyboard events
473 // oops, console (de)activation is processed down the chain
474 //if (ev->type != ev_keydown && ev->type != ev_keyup) return false;
476 // shit; i have to perform more fine-grained locking
477 switch (ev
->keycode
) {
480 if (consolestate
!= cons_open
) return false;
484 //GCon->Logf("::: %d", (int)(ev->keycode == K_BACKQUOTE));
485 if (ev
->isShiftDown()) {
486 c_iline
.AddChar('~');
488 if (consolestate
== cons_closing
) C_Start(); else C_Stop();
492 // execute entered command
496 VStr ccmds
= VStr(c_iline
.getCStr()).xstrip();
497 if (ccmds
.length() != 0) {
498 VStr ccmdfull
= VStr(c_iline
.getCStr()).trimLeft();
499 // leave only one trailing space
500 if (!ccmdfull
.isEmpty() && (vuint8
)ccmdfull
[ccmdfull
.length()-1] <= ' ') ccmdfull
= ccmdfull
.xstrip()+" ";
502 GCon
->Logf(">%s", *ccmds
);
503 MyThreadLocker
lock(&conLogLock
);
505 // add to history (but if it is a duplicate, move it to history top)
507 for (int f
= 0; f
< c_history_size
; ++f
) {
508 if (c_history
[f
].strEqu(ccmds
, true)) {
510 // if we have a space at the end, replace
511 if (ccmdfull
.length() > ccmds
.length()) c_history
[f
].putStr(*ccmdfull
);
517 // move to history bottom (or top, it depends of your PoV)
518 for (int f
= dupidx
+1; f
<= c_history_size
-1; ++f
) c_history
[f
-1].swap(c_history
[f
]);
520 // no duplicate, append to history buffer
521 if (c_history_size
== MAXHISTORY
) {
522 // move oldest line to bottom, and reuse it
523 for (int f
= 1; f
< MAXHISTORY
; ++f
) c_history
[f
-1].swap(c_history
[f
]);
527 c_history
[c_history_size
-1].putStr(*ccmdfull
);
529 c_history_current
= -1;
531 // add to command buffer
532 GCmdBuf
<< ccmds
<< "\n";
543 MyThreadLocker
lock(&conLogLock
);
544 for (int i
= 0; i
< (ev
->isShiftDown() ? 1 : max2(2, (int)con_height
/9-2)); ++i
) {
545 if (last_line
> 1) --last_line
;
553 MyThreadLocker
lock(&conLogLock
);
554 for (int i
= 0; i
< (ev
->isShiftDown() ? 1 : max2(2, (int)con_height
/9-2)); ++i
) {
555 if (last_line
< num_lines
) ++last_line
;
562 if (ev
->isShiftDown()) {
563 MyThreadLocker
lock(&conLogLock
);
567 return c_iline
.Key(*ev
);
571 if (ev
->isShiftDown()) {
572 MyThreadLocker
lock(&conLogLock
);
573 last_line
= num_lines
;
576 return c_iline
.Key(*ev
);
578 // command history up
580 if (c_history_size
> 0 && c_history_current
< c_history_size
-1) {
583 cp
= c_history
[c_history_size
-c_history_current
-1].getCStr();
584 while (*cp
) c_iline
.AddChar(*cp
++);
588 // command history down
590 if (c_history_size
> 0 && c_history_current
>= 0) {
593 if (c_history_current
>= 0) {
594 cp
= c_history
[c_history_size
-c_history_current
-1].getCStr();
595 while (*cp
) c_iline
.AddChar(*cp
++);
602 if (c_iline
.length() != 0) {
603 VStr clineRest
= c_iline
.getCStr();
604 VStr cline
= clineRest
.left(c_iline
.getCurPos());
605 clineRest
.chopLeft(c_iline
.getCurPos());
606 if (cline
.length() && clineRest
.length() && clineRest
[0] == '"') {
607 cline
+= clineRest
[0];
608 clineRest
.chopLeft(1);
611 int cmdstart
= cline
.findNextCommand();
613 const int cmdstnext
= cline
.findNextCommand(cmdstart
);
614 if (cmdstnext
== cmdstart
) break;
615 cmdstart
= cmdstnext
;
618 oldpfx
.chopLeft(cmdstart
); // remove completed commands
619 VStr newpfx
= VCommand::GetAutoComplete(oldpfx
);
620 if (oldpfx
!= newpfx
) {
622 c_iline
.AddString(cline
.left(cmdstart
));
623 c_iline
.AddString(newpfx
);
624 // append rest of cline
625 if (clineRest
.length()) {
626 int cpos
= c_iline
.getCurPos();
627 c_iline
.AddString(clineRest
);
628 c_iline
.setCurPos(cpos
);
634 // add character to input line
636 return c_iline
.Key(*ev
);
641 //==========================================================================
645 //==========================================================================
647 MyThreadLocker
lock(&conLogLock
);
654 //==========================================================================
658 // Ads a line to console strings
660 //==========================================================================
661 static void AddLine (const char *Data
) noexcept
{
662 if (!Data
) Data
= "";
663 //MyThreadLocker lock(&conLogLock);
664 if (num_lines
>= MAX_LINES
) {
668 int len
= VStr::length(Data
);
669 int lidx
= (num_lines
+first_line
)%MAX_LINES
;
670 ConLine
&line
= clines
[lidx
];
671 if (len
+1 > line
.alloced
) {
672 int newsz
= ((len
+1)|0xff)+1;
673 line
.str
= (char *)Z_Realloc(line
.str
, newsz
);
674 line
.alloced
= newsz
;
677 if (len
) memcpy(line
.str
, Data
, len
);
680 VStr::NCpy(clines[(num_lines+first_line)%MAX_LINES], Data, MAX_LINE_LENGTH);
681 clines[(num_lines+first_line)%MAX_LINES][MAX_LINE_LENGTH-1] = 0;
684 if (last_line
== num_lines
-1) last_line
= num_lines
;
686 // update splash if necessary
687 if (GConSplashActive
&& Drawer
) {
688 if (Drawer
->IsLoadingSplashActive()) {
689 Drawer
->DrawLoadingSplashText(Data
, len
);
691 GConSplashActive
= false;
698 //==========================================================================
702 // can be called to force splash screen update
704 //==========================================================================
705 void C_FlushSplash () {
707 if (GConSplashActive
&& Drawer
) {
708 if (Drawer
->IsLoadingSplashActive()) {
709 Drawer
->DrawLoadingSplashText(nullptr, 0);
711 GConSplashActive
= false;
718 //==========================================================================
722 //==========================================================================
723 static ConLine cpbuf
;
724 static int cpCurrLineLen
= 0;
725 static VStr cpLastColor
;
726 static bool cpLogFileNeedName
= true;
729 static void cpAppendChar (char ch
) noexcept
{
730 if (ch
== 0) ch
= ' ';
731 int nlen
= cpbuf
.len
+1;
732 if (nlen
+1 > cpbuf
.alloced
) {
733 int newsz
= ((nlen
+1)|0xff)+1;
734 cpbuf
.str
= (char *)Z_Realloc(cpbuf
.str
, newsz
);
735 cpbuf
.alloced
= newsz
;
737 cpbuf
.str
[cpbuf
.len
++] = ch
;
738 cpbuf
.str
[cpbuf
.len
] = 0;
742 static void cpPrintCurrColor () noexcept
{
743 for (const char *s
= cpLastColor
.getCStr(); *s
; ++s
) cpAppendChar(*s
);
747 static void cpFlushCurrent (bool asNewline
) noexcept
{
750 if (cpbuf
.str
) cpbuf
.str
[0] = 0;
760 // *ch should be TEXT_COLOR_ESCAPE
761 static const char *cpProcessColorEscape (const char *ch
) noexcept
{
762 vassert(*ch
== TEXT_COLOR_ESCAPE
);
764 ++ch
; // skip TEXT_COLOR_ESCAPE
767 cpAppendChar(TEXT_COLOR_ESCAPE
);
768 cpAppendChar('L'); // untranslated
771 cpLastColor
+= TEXT_COLOR_ESCAPE
;
773 cpLastColor
+= *ch
++;
774 while (*ch
&& *ch
!= ']') cpLastColor
+= *ch
++;
775 if (*ch
) cpLastColor
+= *ch
++; else cpLastColor
+= ']';
777 cpLastColor
+= *ch
++;
784 // *ch should be TEXT_COLOR_ESCAPE
785 static const char *cpSkipColorEscape (const char *ch
) noexcept
{
786 vassert(*ch
== TEXT_COLOR_ESCAPE
);
787 ++ch
; // skip TEXT_COLOR_ESCAPE
788 if (!ch
[0]) return ch
;
790 while (*ch
&& *ch
!= ']') ++ch
;
797 static void DoPrint (const char *buf
) noexcept
{
798 const char *ch
= buf
;
801 cpFlushCurrent(true);
803 } else if (*ch
== TEXT_COLOR_ESCAPE
) {
804 // new color sequence
805 ch
= cpProcessColorEscape(ch
);
806 } else if (*(const vuint8
*)ch
> ' ') {
810 while (*(const vuint8
*)p
> ' ') {
811 if (*p
== TEXT_COLOR_ESCAPE
) {
812 p
= cpSkipColorEscape(p
);
819 if (cpCurrLineLen
+wlen
>= MAX_LINE_LENGTH
) {
821 // word too long and it is not a first word
822 // add current buffer and try again
823 cpFlushCurrent(false); // don't clear current color
825 // a very long first word, add partially
826 while (*(const vuint8
*)ch
> ' ' && cpCurrLineLen
< MAX_LINE_LENGTH
) {
827 if (*ch
== TEXT_COLOR_ESCAPE
) {
828 ch
= cpProcessColorEscape(ch
);
834 cpFlushCurrent(false); // don't clear current color
837 // add word to buffer
838 while (*(const vuint8
*)ch
> ' ') {
839 if (*ch
== TEXT_COLOR_ESCAPE
) {
840 ch
= cpProcessColorEscape(ch
);
849 if (cpCurrLineLen
< MAX_LINE_LENGTH
) {
853 count
= 8-cpCurrLineLen
%8;
868 //==========================================================================
872 // tty output is done by standard logger
874 //==========================================================================
875 static void ConSerialise (const char *str
, EName Event
, bool fromGLog
) noexcept
{
876 if (Event
== NAME_Dev
&& !developer
) return;
877 if (!fromGLog
) { GLog
.WriteLine(Event
, "%s", str
); return; }
879 MyThreadLocker
lock(&conLogLock
);
880 //fprintf(stderr, "<<<%s>>>", str);
881 //HACK! if string starts with "Sys_Error:", print it, and close log file
882 if (VStr::NCmp(str
, "Sys_Error:", 10) == 0) {
883 if (logfout
) { fflush(logfout
); fprintf(logfout
, "*** %s\n", str
); fclose(logfout
); logfout
= nullptr; }
885 bool resetColor
= true;
886 const char *cs
= VLog::GetColorInfoEngine(Event
, resetColor
);
888 cpLastColor
= VStr(cs
);
894 VStr rc
= VStr(str
).RemoveColors();
895 const char *rstr
= *rc
;
896 __android_log_print(ANDROID_LOG_DEBUG
, "K8VAVOOM", "%s", rstr
);
901 VStr rc
= VStr(str
).RemoveColors();
902 const char *rstr
= *rc
;
903 while (rstr
&& *rstr
) {
904 const char *eol
= strchr(rstr
, '\n');
905 if (!eol
) eol
= rstr
+strlen(rstr
);
906 if (cpLogFileNeedName
) {
908 if (Event
== NAME_DevNet
) {
909 unsigned msecs
= unsigned(Sys_Time()*1000);
910 snprintf(buf
, sizeof(buf
), "%u:", msecs
);
911 fprintf(logfout
, "%s:%s%s", VName::SafeString(Event
), buf
, (rstr
== eol
? "" : " "));
915 fprintf(logfout
, "%s:%s%s", VName::SafeString(Event
), buf
, (rstr
== eol
? "" : " "));
916 cpLogFileNeedName
= false;
918 if (eol
!= rstr
) fwrite(rstr
, (ptrdiff_t)(eol
-rstr
), 1, logfout
);
921 vassert(rstr
[0] == '\n');
922 fprintf(logfout
, "\n");
923 cpLogFileNeedName
= true;
926 //fprintf(logfout, "%s: %s", VName::SafeString(Event), *rc);
931 //==========================================================================
933 // FConsoleDevice::Serialise
935 //==========================================================================
936 void FConsoleDevice::Serialise (const char *V
, EName Event
) noexcept
{
937 ConSerialise(V
, Event
, false);
941 //==========================================================================
943 // FConsoleLog::Serialise
945 //==========================================================================
946 void FConsoleLog::Serialise (const char *Text
, EName Event
) noexcept
{
947 ConSerialise(Text
, Event
, true);