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 **************************************************************************/
22 #include "libvideo/video.h"
28 ////////////////////////////////////////////////////////////////////////////////
29 char *strprintfVA (const char *fmt
, va_list vaorig
) {
33 if (buf
== NULL
) { fprintf(stderr
, "\nFATAL: out of memory!\n"); abort(); }
38 olen
= vsnprintf(buf
, len
, fmt
, 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(); }
50 __attribute__((format(printf
,1,2))) char *strprintf (const char *fmt
, ...) {
54 buf
= strprintfVA(fmt
, va
);
60 ////////////////////////////////////////////////////////////////////////////////
66 static ConChar conlines
[MAX_CON_LINES
][CON_WIDTH
]; // not 0-terminated!
67 static int conTopLine
;
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
;
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
);
97 concolor
= CON_FG_COLOR
;
98 if (concmdline
!= NULL
) free(concmdline
);
100 conTopLine
= MAX_CON_LINES
-CON_HEIGHT
+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
;
128 concmdline
[len
++] = ch
;
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;
161 static void concmdlineSetString (const char *s
) {
162 if (s
== NULL
) s
= "";
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) {
180 if (constrdumpbuflen
) {
181 write(STDOUT_FILENO
, constrdumpbuf
, (size_t)constrdumpbuflen
);
186 void conPutChar (char ch
) {
187 unsigned int c
= (ch
&0xff);
191 if (ch
== '\n' && optConDumpToStdout
) {
192 if (constrdumpbuflen
< sizeof(constrdumpbuf
)-1) {
193 constrdumpbuf
[constrdumpbuflen
++] = ch
;
197 constrdumpbuf
[0] = '\n';
198 constrdumpbuflen
= 1;
201 constrdumpbuflen
= 0;
206 if (constrdumpbuflen
>= sizeof(constrdumpbuf
)-2) {
207 if (optConDumpToStdout
) dumpToStdout();
208 constrdumpbuflen
= 0;
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
;
228 concolor
= CON_FG_COLOR
;
243 if (conpos
> 0) --conpos
;
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(' ');
251 if (concolor
!= 10) concolor
= CON_FG_COLOR
;
252 if (conpos
== 0 || conpos
>= CON_WIDTH
) { conpos
= 0; conScroll(); } else conpos
= CON_WIDTH
;
255 if (concolor
!= 10) concolor
= CON_FG_COLOR
;
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
;
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
278 __attribute__((format(printf
,1,2))) void cprintf (const char *fmt
, ...) {
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;
298 static Jim_Obj
*conParseArgs (const char *s
) {
299 Jim_Obj
*lst
= Jim_NewListObj(jim
, NULL
, 0);
305 while (*s
&& isspace(*s
)) ++s
;
307 if ((qch
= s
[0]) == '"' || qch
== '\'') {
308 const char *pst
= ++s
;
310 js
= Jim_NewStringObj(jim
, NULL
, 0);
311 while (*s
&& *s
!= qch
) {
315 if (pst
+1 < s
) Jim_AppendString(jim
, js
, pst
, s
-pst
-1);
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;
328 if (x2dig(*s
) >= 0) ch
= 16*x2dig(*s
++);
329 if (x2dig(*s
) >= 0) ch
|= x2dig(*s
++);
330 if (ch
== 0) ch
= ' ';
336 Jim_AppendString(jim
, js
, &ch
, 1);
341 if (pst
< s
) Jim_AppendString(jim
, js
, pst
, s
-pst
);
342 //fprintf(stderr, "quoted: [%s]\n", Jim_String(js));
347 for (e
= s
; *e
&& !isspace(*e
); ++e
) ;
348 js
= Jim_NewStringObj(jim
, s
, e
-s
);
351 //fprintf(stderr, "[%s]\n", Jim_String(js));
352 Jim_ListAppendElement(jim
, lst
, js
);
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
));
374 s
= Jim_String(Jim_GetResult(jim
));
375 if (s
!= NULL
&& s
[0]) {
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
));
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);
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");
440 static void conRecordHistory (const char *str
) {
444 // check for an empty string
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];
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
]);
475 ////////////////////////////////////////////////////////////////////////////////
476 int conKeyEvent (SDL_KeyboardEvent
*key
) {
479 if (key
->type
== SDL_KEYDOWN
&& (key
->keysym
.mod
&KMOD_CTRL
) != 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;
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
)
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]) {
509 if (key
->keysym
.unicode
>= 32 && key
->keysym
.unicode
< 127) {
511 concmdlineAppendChar(key
->keysym
.unicode
);
515 switch (key
->keysym
.sym
) {
516 case SDLK_ESCAPE
: conVisible
= 0; return 1;
520 if (conhistorypos
< 0) {
521 if (concmdlinesize
> 0 && concmdline
[0]) {
522 s
= strdup(concmdline
);
525 } else if (conhistorypos
< MAX_CON_HISTORY
&& conhistory
[conhistorypos
] != NULL
) {
526 s
= strdup(conhistory
[conhistorypos
]);
533 conExecute(s
, ctrleon
);
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;
551 ////////////////////////////////////////////////////////////////////////////////
552 void conDraw (void) {
557 clearVO(conOverlay
, CON_BG_COLOR
);
558 drawFrameVO(conOverlay
, 1, 1, conOverlay
->w
-2, conOverlay
->h
-2, CON_FG_COLOR
);
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);
568 cline
= (conhistorypos
< 0 ? concmdline
: conhistorypos
< MAX_CON_HISTORY
? conhistory
[conhistorypos
] : 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);
581 memset(buf
, 32, sizeof(buf
));
582 buf
[sizeof(buf
)-1] = '\0';
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
);
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
);
596 if (debuggerActive
&& !dbgIsHidden()) {
597 for (int y
= 0; y
< CON_HEIGHT
; ++y
) {
598 vt_writechars(0, y
, VID_TEXT_WIDTH
, ' ', 7);
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
);
613 cline
= (conhistorypos
< 0 ? concmdline
: conhistorypos
< MAX_CON_HISTORY
? conhistory
[conhistorypos
] : NULL
);
615 vt_writechars(0, CON_HEIGHT
-1, VID_TEXT_WIDTH
, ' ', 0x17);
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);
625 memset(buf
, 32, sizeof(buf
));
626 buf
[sizeof(buf
)-1] = '\0';
628 vt_writestrz(0, CON_HEIGHT
-1, buf
, 0x1e);
630 vt_writechar(0, CON_HEIGHT
-1, '<', 0x1d);
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);
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
, ...) {
654 if (msg
!= NULL
) free(msg
);
655 if (fmt
!= NULL
&& fmt
[0]) {
657 msg
= strprintfVA(fmt
, va
);
659 msg_len
= (int)strlen(msg
);
661 msg_hide_time
= timerGetMS()+4000; //TODO: make configurable
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);