libfusefdc: better TR-DOS boot setting
[zymosis.git] / src / ZXEmuT / console.c
blob63ef54613e8d7958972359c922dca2ffd3980b0c
1 /***************************************************************************
3 * ZXEmuT -- ZX Spectrum Emulator with Tcl scripting
5 * Copyright (C) 2012-2020 Ketmar Dark <ketmar@ketmar.no-ip.org>
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, version 3 of the License ONLY.
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. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 **************************************************************************/
20 #include "console.h"
21 #include "debugger.h"
22 #include "libvideo/video.h"
24 #include <ctype.h>
25 #include <unistd.h>
28 ////////////////////////////////////////////////////////////////////////////////
29 char *strprintfVA (const char *fmt, va_list vaorig) {
30 char *buf = NULL;
31 int olen, len = 128;
32 buf = malloc(len);
33 if (buf == NULL) { fprintf(stderr, "\nFATAL: out of memory!\n"); abort(); }
34 for (;;) {
35 char *nb;
36 va_list va;
37 va_copy(va, vaorig);
38 olen = vsnprintf(buf, len, fmt, va);
39 va_end(va);
40 if (olen >= 0 && olen < len) return buf;
41 if (olen < 0) olen = len*2-1;
42 nb = realloc(buf, olen+1);
43 if (nb == NULL) { fprintf(stderr, "\nFATAL: out of memory!\n"); abort(); }
44 buf = nb;
45 len = olen+1;
50 __attribute__((format(printf,1,2))) char *strprintf (const char *fmt, ...) {
51 char *buf = NULL;
52 va_list va;
53 va_start(va, fmt);
54 buf = strprintfVA(fmt, va);
55 va_end(va);
56 return buf;
60 ////////////////////////////////////////////////////////////////////////////////
61 typedef struct {
62 unsigned char ch;
63 Uint8 color;
64 } ConChar;
66 static ConChar conlines[MAX_CON_LINES][CON_WIDTH]; // not 0-terminated!
67 static int conTopLine;
68 int conpos;
69 static Uint8 concolor;
70 static char *concmdline = NULL;
71 static int concmdlinesize = 0;
72 static VOverlay *conOverlay = NULL;
73 static char *conhistory[MAX_CON_HISTORY];
74 static int conhistorypos;
75 FILE *condumpfl = NULL;
76 static int conistty = 1;
79 ////////////////////////////////////////////////////////////////////////////////
80 static void conClearLine (int y) {
81 if (y >= 0 && y < MAX_CON_LINES) {
82 for (int x = 0; x < CON_WIDTH; ++x) {
83 conlines[y][x].ch = ' ';
84 conlines[y][x].color = CON_FG_COLOR;
90 void conInit (void) {
91 conistty = isatty(STDOUT_FILENO);
93 conOverlay = createVO(CON_WIDTH*6+6, CON_HEIGHT*8+6);
95 for (int f = 0; f < MAX_CON_LINES; ++f) conClearLine(f);
96 conpos = 0;
97 concolor = CON_FG_COLOR;
98 if (concmdline != NULL) free(concmdline);
99 concmdline = NULL;
100 conTopLine = MAX_CON_LINES-CON_HEIGHT+1;
102 conhistorypos = -1;
103 for (int f = 0; f < MAX_CON_HISTORY; ++f) conhistory[f] = NULL;
107 ////////////////////////////////////////////////////////////////////////////////
108 static void contopMove (int cnt) {
109 if ((conTopLine += cnt) < 0) conTopLine = 0;
110 else if (conTopLine > MAX_CON_LINES-CON_HEIGHT+1) conTopLine = MAX_CON_LINES-CON_HEIGHT+1;
114 ////////////////////////////////////////////////////////////////////////////////
115 static void concmdlineAppendChar (char ch) {
116 if ((unsigned char)ch >= 32) {
117 int len = (int)strlen(concmdline != NULL ? concmdline : "");
119 if (len >= 8192) return;
120 if (len+2 > concmdlinesize) {
121 int newsz = len+1024;
122 char *nb = realloc(concmdline, newsz);
124 if (nb == NULL) return;
125 concmdlinesize = newsz;
126 concmdline = nb;
128 concmdline[len++] = ch;
129 concmdline[len] = 0;
134 static void concmdlineClear (void) {
135 if (concmdlinesize > 0) concmdline[0] = 0;
139 static void concmdlineChopChar (void) {
140 if (concmdlinesize > 0) {
141 int len = (int)strlen(concmdline);
143 if (len > 0) concmdline[--len] = 0;
148 //FIXME: parse line and correctly kill quoted words?
149 static void concmdlineKillWord (void) {
150 if (concmdlinesize > 0) {
151 int len = (int)strlen(concmdline)-1;
153 while (len >= 0 && isspace(concmdline[len])) --len;
154 while (len >= 0 && !isspace(concmdline[len])) --len;
155 if (++len < 0) len = 0;
156 concmdline[len] = 0;
161 static void concmdlineSetString (const char *s) {
162 if (s == NULL) s = "";
163 concmdlineClear();
164 while (*s) concmdlineAppendChar(*s++);
168 ////////////////////////////////////////////////////////////////////////////////
169 static void conScroll (void) {
170 for (int f = 0; f < MAX_CON_LINES-1; ++f) memcpy(conlines[f], conlines[f+1], sizeof(conlines[f]));
171 conClearLine(MAX_CON_LINES-1);
174 static char constrdumpbuf[1024];
175 static size_t constrdumpbuflen = 0;
178 static inline void dumpToStdout (void) {
179 //if (conistty)
180 if (constrdumpbuflen) {
181 write(STDOUT_FILENO, constrdumpbuf, (size_t)constrdumpbuflen);
186 void conPutChar (char ch) {
187 unsigned int c = (ch&0xff);
189 int skipcdb = 0;
191 if (ch == '\n' && optConDumpToStdout) {
192 if (constrdumpbuflen < sizeof(constrdumpbuf)-1) {
193 constrdumpbuf[constrdumpbuflen++] = ch;
194 dumpToStdout();
195 } else {
196 dumpToStdout();
197 constrdumpbuf[0] = '\n';
198 constrdumpbuflen = 1;
199 dumpToStdout();
201 constrdumpbuflen = 0;
202 skipcdb = 1;
205 // flush
206 if (constrdumpbuflen >= sizeof(constrdumpbuf)-2) {
207 if (optConDumpToStdout) dumpToStdout();
208 constrdumpbuflen = 0;
211 if (optConDump) {
212 if (condumpfl == NULL) condumpfl = fopen("/tmp/zxemut.log", "a");
213 if (condumpfl != NULL) {
214 if (c) fputc(c, condumpfl);
218 if (!skipcdb && ch) {
219 const unsigned uch = (unsigned)(ch&0xffu);
220 if (uch == 8 || uch == 9 || uch == '\n' || uch >= 32) {
221 constrdumpbuf[constrdumpbuflen++] = ch;
225 switch (c) {
226 case 0: // normal
227 case 31:
228 concolor = CON_FG_COLOR;
229 return;
230 case 1: // white
231 concolor = 15;
232 return;
233 case 2: // yellow
234 concolor = 14;
235 return;
236 case 3: // gray
237 concolor = 7;
238 return;
239 case 4: // light red
240 concolor = 10;
241 return;
242 case 8:
243 if (conpos > 0) --conpos;
244 return;
245 case 9:
246 if (conpos >= CON_WIDTH) { conScroll(); conpos = 0; }
247 if (conpos%CON_TAB_SIZE == 0) conPutChar(' ');
248 while (conpos < CON_WIDTH && conpos%CON_TAB_SIZE) conPutChar(' ');
249 return;
250 case 10:
251 if (concolor != 10) concolor = CON_FG_COLOR;
252 if (conpos == 0 || conpos >= CON_WIDTH) { conpos = 0; conScroll(); } else conpos = CON_WIDTH;
253 return;
254 case 13:
255 if (concolor != 10) concolor = CON_FG_COLOR;
256 conpos = 0;
257 return;
258 default:
259 if (c < 32) return;
262 if (conpos >= CON_WIDTH) { conScroll(); conpos = 0; }
263 conlines[MAX_CON_LINES-1][conpos].ch = ch;
264 conlines[MAX_CON_LINES-1][conpos].color = concolor;
265 ++conpos;
269 void cprintfVA (const char *fmt, va_list va) {
270 char *buf = strprintfVA(fmt, va);
272 for (const char *t = buf; *t; ++t) conPutChar(*t);
273 conPutChar(0); // reset color
274 free(buf);
278 __attribute__((format(printf,1,2))) void cprintf (const char *fmt, ...) {
279 va_list va;
281 va_start(va, fmt);
282 cprintfVA(fmt, va);
283 va_end(va);
287 ////////////////////////////////////////////////////////////////////////////////
288 static inline int x2dig (char ch) {
289 if (ch > 0 && isxdigit(ch)) {
290 if ((ch -= '0') > 9) if ((ch -= 39) < 10) ch += 39-7;
291 } else {
292 ch = -1;
294 return ch;
298 static Jim_Obj *conParseArgs (const char *s) {
299 Jim_Obj *lst = Jim_NewListObj(jim, NULL, 0);
301 while (*s) {
302 Jim_Obj *js;
303 char qch;
305 while (*s && isspace(*s)) ++s;
306 if (!s[0]) break;
307 if ((qch = s[0]) == '"' || qch == '\'') {
308 const char *pst = ++s;
310 js = Jim_NewStringObj(jim, NULL, 0);
311 while (*s && *s != qch) {
312 if (*s++ == '\\') {
313 char ch;
315 if (pst+1 < s) Jim_AppendString(jim, js, pst, s-pst-1);
316 switch (*s++) {
317 case 'a': ch = '\a'; break;
318 case 'b': ch = '\b'; break;
319 case 'f': ch = '\f'; break;
320 case 'n': ch = '\n'; break;
321 case 'r': ch = '\r'; break;
322 case 't': ch = '\t'; break;
323 case 'v': ch = '\v'; break;
324 case 'e': ch = 27; break;
325 case '~': ch = 127; break;
326 case 'x':
327 ch = 0;
328 if (x2dig(*s) >= 0) ch = 16*x2dig(*s++);
329 if (x2dig(*s) >= 0) ch |= x2dig(*s++);
330 if (ch == 0) ch = ' ';
331 break;
332 default:
333 ch = s[-1];
334 break;
336 Jim_AppendString(jim, js, &ch, 1);
337 pst = s;
338 continue;
341 if (pst < s) Jim_AppendString(jim, js, pst, s-pst);
342 //fprintf(stderr, "quoted: [%s]\n", Jim_String(js));
343 if (*s == qch) ++s;
344 } else {
345 const char *e;
347 for (e = s; *e && !isspace(*e); ++e) ;
348 js = Jim_NewStringObj(jim, s, e-s);
349 s = e;
351 //fprintf(stderr, "[%s]\n", Jim_String(js));
352 Jim_ListAppendElement(jim, lst, js);
354 return lst;
358 static void conAutocomplete (void) {
359 if (concmdlinesize > 0 && concmdline[0]) {
360 const char *s = concmdline;
362 while (*s && isspace(*s)) ++s;
363 if (*s && isalnum(*s)) {
364 Jim_Obj *lst = conParseArgs(concmdline), *jcmd[1];
366 Jim_IncrRefCount(lst);
367 jcmd[0] = Jim_NewStringObj(jim, "::conautocomplete", -1);
368 Jim_ListInsertElements(jim, lst, 0, 1, jcmd);
370 if (Jim_EvalObjList(jim, lst) != JIM_OK) {
371 Jim_MakeErrorMessage(jim);
372 cprintf("\4=== JIM ERROR ===\n%s\n=================\n", Jim_GetString(Jim_GetResult(jim), NULL));
373 } else {
374 s = Jim_String(Jim_GetResult(jim));
375 if (s != NULL && s[0]) {
376 free(concmdline);
377 concmdline = strdup(s);
378 concmdlinesize = (int)strlen(concmdline)+1;
381 Jim_DecrRefCount(jim, lst);
387 void conSetInputString (const char *str) {
388 if (concmdline != NULL) free(concmdline);
389 concmdline = strdup((str != NULL && str[0] ? str :""));
390 concmdlinesize = (int)strlen(concmdline)+1;
394 ////////////////////////////////////////////////////////////////////////////////
395 void conExecute (const char *str, int astcl) {
396 if (str != NULL && str[0]) {
397 if (str[0] == '.' || astcl) {
398 if (Jim_Eval(jim, str+(astcl ? 0 : 1)) != JIM_OK) {
399 Jim_MakeErrorMessage(jim);
400 cprintf("\4=== JIM ERROR ===\n%s\n=================\n", Jim_GetString(Jim_GetResult(jim), NULL));
402 } else {
403 Jim_Obj *lst = conParseArgs(str), *jcmd[1];
404 Jim_IncrRefCount(lst);
405 jcmd[0] = Jim_NewStringObj(jim, "::conexec", -1);
406 Jim_ListInsertElements(jim, lst, 0, 1, jcmd);
407 if (Jim_EvalObjList(jim, lst) != JIM_OK) {
408 Jim_MakeErrorMessage(jim);
409 cprintf("\4=== JIM ERROR ===\n%s\n=================\n", Jim_GetString(Jim_GetResult(jim), NULL));
411 Jim_DecrRefCount(jim, lst);
417 ////////////////////////////////////////////////////////////////////////////////
418 static int conStrEqu (const void *str0, const void *str1) {
419 if (!str0) str0 = "";
420 if (!str1) str1 = "";
421 const uint8_t *s0 = (const uint8_t *)str0;
422 const uint8_t *s1 = (const uint8_t *)str1;
423 while (s0[0] && s0[0] <= 32) ++s0;
424 while (s1[0] && s1[0] <= 32) ++s1;
425 //fprintf(stderr, "000: EQU: <%s> <%s>\n", str0, str1);
426 while (*s0 || *s1) {
427 while (s0[0] && s0[0] <= 32 && s0[1] <= 32) ++s0;
428 while (s1[0] && s1[0] <= 32 && s1[1] <= 32) ++s1;
429 //fprintf(stderr, " <%s> <%s>\n", s0, s1);
430 const uint8_t c0 = *s0++;
431 const uint8_t c1 = *s1++;
432 if (c0 != c1) return 0;
433 if (!c0 || !c1) return (c0 == c1);
435 //fprintf(stderr, "!\n");
436 return 1;
440 static void conRecordHistory (const char *str) {
441 conhistorypos = -1;
442 if (!str) return;
444 // check for an empty string
445 size_t pos = 0;
446 while (str[pos] && ((const uint8_t *)str)[pos] <= 32) ++pos;
447 if (!str[pos]) return;
449 // find duplicate, if there is any
450 for (int f = 0; conhistory[f] != NULL; ++f) {
451 if (conStrEqu(conhistory[f], str)) {
452 // duplicate found, move it to history top
453 char *s = conhistory[f];
454 for (int c = f; c > 0; --c) conhistory[c] = conhistory[c-1];
455 conhistory[0] = s;
456 return;
460 // no duplicate, append to history
461 if (conhistory[MAX_CON_HISTORY-1] != NULL) free(conhistory[MAX_CON_HISTORY-1]);
462 for (int f = MAX_CON_HISTORY-1; f > 0; --f) conhistory[f] = conhistory[f-1];
463 conhistory[0] = strdup(str);
467 static void conhistoryCopy (void) {
468 if (conhistorypos >= 0) {
469 concmdlineSetString(conhistory[conhistorypos]);
470 conhistorypos = -1;
475 ////////////////////////////////////////////////////////////////////////////////
476 int conKeyEvent (SDL_KeyboardEvent *key) {
477 int ctrleon = 0;
479 if (key->type == SDL_KEYDOWN && (key->keysym.mod&KMOD_CTRL) != 0) {
480 int dontexit = 0;
482 switch (key->keysym.sym) {
483 case SDLK_BACKSPACE: case SDLK_w: conhistoryCopy(); concmdlineKillWord(); return 1;
484 case SDLK_y: conhistoryCopy(); concmdlineClear(); return 1;
485 case SDLK_PAGEUP: contopMove(-(CON_HEIGHT-1)); return 1;
486 case SDLK_PAGEDOWN: contopMove(CON_HEIGHT-1); return 1;
487 case SDLK_RETURN: dontexit = 1; break;
488 default: ;
490 if (!dontexit) return 0;
493 if (key->type == SDL_KEYDOWN && (key->keysym.mod&(KMOD_ALT|KMOD_META)) == 0 && (key->keysym.mod&KMOD_CTRL) &&
494 key->keysym.sym == SDLK_RETURN)
496 ctrleon = 1;
499 if (key->type == SDL_KEYDOWN && (ctrleon || (key->keysym.mod&(KMOD_CTRL|KMOD_ALT|KMOD_META)) == 0)) {
500 if (key->keysym.unicode == 96) {
501 const char *cline = (conhistorypos < 0 ? concmdline : conhistorypos < MAX_CON_HISTORY ? conhistory[conhistorypos] : NULL);
503 if (cline == NULL || !cline[0]) {
504 conVisible = 0;
505 return 1;
509 if (key->keysym.unicode >= 32 && key->keysym.unicode < 127) {
510 conhistoryCopy();
511 concmdlineAppendChar(key->keysym.unicode);
512 return 1;
515 switch (key->keysym.sym) {
516 case SDLK_ESCAPE: conVisible = 0; return 1;
517 case SDLK_RETURN: {
518 char *s = NULL;
520 if (conhistorypos < 0) {
521 if (concmdlinesize > 0 && concmdline[0]) {
522 s = strdup(concmdline);
523 //conHistoryAdd(s);
525 } else if (conhistorypos < MAX_CON_HISTORY && conhistory[conhistorypos] != NULL) {
526 s = strdup(conhistory[conhistorypos]);
529 concmdlineClear();
530 conRecordHistory(s);
532 if (s != NULL) {
533 conExecute(s, ctrleon);
534 free(s);
536 return 1; }
537 case SDLK_TAB: conhistoryCopy(); conAutocomplete(); return 1;
538 case SDLK_BACKSPACE: conhistoryCopy(); concmdlineChopChar(); return 1;
539 case SDLK_PAGEUP: contopMove(-1); return 1;
540 case SDLK_PAGEDOWN: contopMove(1); return 1;
541 case SDLK_UP: if (++conhistorypos >= MAX_CON_HISTORY || conhistory[conhistorypos] == NULL) --conhistorypos; return 1;
542 case SDLK_DOWN: if (--conhistorypos < -1) conhistorypos = -1; return 1;
543 default: ;
547 return 0;
551 ////////////////////////////////////////////////////////////////////////////////
552 void conDraw (void) {
553 int cp;
554 const char *cline;
556 #if 0
557 clearVO(conOverlay, CON_BG_COLOR);
558 drawFrameVO(conOverlay, 1, 1, conOverlay->w-2, conOverlay->h-2, CON_FG_COLOR);
559 // draw console text
560 for (int y = 0; y < CON_HEIGHT-1; ++y) {
561 int ln = conTopLine+y;//MAX_CON_LINES-CON_HEIGHT+y+1;
563 for (int x = 0; x < CON_WIDTH; ++x) {
564 drawChar6VO(conOverlay, conlines[ln][x].ch, x*6+3, y*8+3, conlines[ln][x].color, 255);
567 // draw command line
568 cline = (conhistorypos < 0 ? concmdline : conhistorypos < MAX_CON_HISTORY ? conhistory[conhistorypos] : NULL);
570 if (cline != NULL) {
571 int len = (int)strlen(cline);
572 const char *cl = cline;
573 char buf[CON_WIDTH+1];
575 //fprintf(stderr, "len=%d; MAX_CON_WIDTH=%d\n", len, MAX_CON_WIDTH);
576 if (len > CON_WIDTH-1) {
577 cl = cline+len-(CON_WIDTH-1);
578 len = CON_WIDTH-1;
580 cp = len;
581 memset(buf, 32, sizeof(buf));
582 buf[sizeof(buf)-1] = '\0';
583 strcpy(buf, cl);
584 drawStr6VO(conOverlay, buf, 3, (CON_HEIGHT-1)*8+3, 14, 255);
585 if (cl > cline) drawChar6VO(conOverlay, '<', 3, (CON_HEIGHT-1)*8+3, 13, CON_BG_COLOR);
586 } else {
587 cp = 0;
589 // draw cursor
590 //TODO: add 'curblink' console command
591 if (timerGetMS()%800 < 400) fillRectVO(conOverlay, cp*6+3, (CON_HEIGHT-1)*8+3, 6, 8, 15);
593 blitVO(conOverlay, (320-conOverlay->w)/2, 0, conAlpha);
594 #else
596 if (debuggerActive && !dbgIsHidden()) {
597 for (int y = 0; y < CON_HEIGHT; ++y) {
598 vt_writechars(0, y, VID_TEXT_WIDTH, ' ', 7);
600 } else {
601 vt_cls(' ', 0x0a);
604 // draw console text
605 for (int y = 0; y < CON_HEIGHT-1; ++y) {
606 int ln = conTopLine+y;//MAX_CON_LINES-CON_HEIGHT+y+1;
607 for (int x = 0; x < CON_WIDTH; ++x) {
608 vt_writechar(x, y, conlines[ln][x].ch, conlines[ln][x].color);
612 // draw command line
613 cline = (conhistorypos < 0 ? concmdline : conhistorypos < MAX_CON_HISTORY ? conhistory[conhistorypos] : NULL);
615 vt_writechars(0, CON_HEIGHT-1, VID_TEXT_WIDTH, ' ', 0x17);
616 if (cline != NULL) {
617 int len = (int)strlen(cline);
618 const char *cl = cline;
619 char buf[CON_WIDTH+1];
620 if (len > CON_WIDTH-1) {
621 cl = cline+len-(CON_WIDTH-1);
622 len = CON_WIDTH-1;
624 cp = len;
625 memset(buf, 32, sizeof(buf));
626 buf[sizeof(buf)-1] = '\0';
627 strcpy(buf, cl);
628 vt_writestrz(0, CON_HEIGHT-1, buf, 0x1e);
629 if (cl > cline) {
630 vt_writechar(0, CON_HEIGHT-1, '<', 0x1d);
632 } else {
633 cp = 0;
636 // draw cursor
637 //TODO: add 'curblink' console command
638 if (timerGetMS()%800 < 400) {
639 //fillRectVO(conOverlay, cp*6+3, (CON_HEIGHT-1)*8+3, 6, 8, 15);
640 vt_writechar(cp, CON_HEIGHT-1, 0, 15<<4);
642 #endif
646 ////////////////////////////////////////////////////////////////////////////////
647 static char *msg = NULL;
648 static int msg_len = 0;
649 static int64_t msg_hide_time = 0;
652 __attribute__((format(printf,1,2))) void conMessage (const char *fmt, ...) {
653 va_list va;
654 if (msg != NULL) free(msg);
655 if (fmt != NULL && fmt[0]) {
656 va_start(va, fmt);
657 msg = strprintfVA(fmt, va);
658 va_end(va);
659 msg_len = (int)strlen(msg);
660 if (msg_len > 0) {
661 msg_hide_time = timerGetMS()+4000; //TODO: make configurable
662 } else {
663 free(msg);
664 msg = NULL;
665 msg_hide_time = 0;
667 } else {
668 msg = NULL;
669 msg_hide_time = 0;
674 void conDrawMessage (void) {
675 if (msg_hide_time && msg != NULL) {
676 int64_t ct = timerGetMS();
677 if (ct < msg_hide_time) {
678 int x = (frameSfc->w-msg_len*6)/2;
679 drawStr6Outline(msg, x, frameSfc->h-10, 14, 0);
680 } else {
681 msg_hide_time = 0;
682 free(msg);
683 msg = NULL;