3 * - By John Hodge (thePowersGang)
6 * - VT100/xterm Emulation
9 #include "include/vt100.h"
10 #include "include/display.h"
11 #include <ctype.h> // isalpha
13 # define _SysDebug(v...) Debug("VT100 "v)
15 # include <acess/sys.h> // _SysDebug
18 # include <stdlib.h> // malloc/free
20 static inline int MIN(int a, int b)
36 #define FLAG_BOLD 0x01
37 #define FLAG_REVERSE 0x02
39 #define MAX_VT100_ESCAPE_LEN 32
44 char cache[MAX_VT100_ESCAPE_LEN];
47 enum eExcapeMode Mode;
49 enum eStringType StringType;
54 const uint32_t caVT100Colours[] = {
55 // Black, Red, Green, Yellow, Blue, Magenta, Cyan, Gray
56 // Same again, but bright
57 0x000000, 0x770000, 0x007700, 0x777700, 0x000077, 0x770077, 0x007777, 0xAAAAAA,
58 0xCCCCCC, 0xFF0000, 0x00FF00, 0xFFFF00, 0x0000FF, 0xFF00FF, 0x00FFFF, 0xFFFFFF,
61 int _locate_eos(size_t Len, const char *Buf);
62 int Term_HandleVT100_Short(tTerminal *Term, int Len, const char *Buf);
63 int Term_HandleVT100_Long(tTerminal *Term, int Len, const char *Buf);
64 int Term_HandleVT100_OSC(tTerminal *Term, int Len, const char *Buf);
66 int _locate_eos(size_t Len, const char *Buf)
68 for( size_t ret = 0; ret < Len; ret ++ )
70 if( Buf[ret] == '\007' )
72 if( Buf[ret] == '\x9c' )
74 if( ret+1 < Len && Buf[ret] == '\x1b' && Buf[ret+1] == '\\' )
81 * \brief Detect and handle VT100/ANSI/xterm escape sequences
82 * \param Term Terminal handle (opaque)
83 * \param Len Number of avaliable bytes
84 * \param Buf Input buffer (\a Len bytes long)
85 * \return -ve : Number of bytes that should be sent to screen
86 * \return +ve/0 : Number of bytes consumed by this function
88 int Term_HandleVT100(tTerminal *Term, int Len, const char *Buf)
90 tVT100State *st = Display_GetTermState(Term);
93 st = malloc( sizeof(*st) );
94 memset(st, 0, sizeof(*st));
95 Display_SetTermState(Term, st);
98 if( st->Mode == MODE_IGNORE ) {
99 st->Mode = MODE_NORMAL;
100 // Used for multi-byte EOS
101 _SysDebug("Ignore 1 '%c'", *Buf);
104 else if( st->Mode == MODE_STRING )
106 // We're in a string mode
107 int pos = _locate_eos(Len, Buf);
108 size_t bytes = (pos >= 0 ? pos : Len);
109 char *tmp = realloc(st->StringCache, st->StringLen + bytes+1);
110 if(!tmp) return bytes;
111 st->StringCache = tmp;
112 memcpy(tmp+st->StringLen, Buf, bytes);
113 tmp[st->StringLen+bytes] = 0;
114 st->StringLen += bytes;
116 _SysDebug("pos=%i", pos);
117 _SysDebug("Buf[+%zi] = '%.*s'", bytes, bytes, Buf);
118 // Only apply when we hit EOS at the start
121 switch(st->StringType)
124 Display_SetTitle(Term, st->StringCache);
129 free(st->StringCache);
133 if( *Buf == '\x1b' ) {
134 st->Mode = MODE_IGNORE;
138 st->Mode = MODE_NORMAL;
145 if( st->cache_len > 0 || *Buf == '\x1b' )
147 // Handle VT100 (like) escape sequence
148 int new_bytes = MIN(MAX_VT100_ESCAPE_LEN - st->cache_len, Len);
150 int old_inc_len = st->cache_len;
152 memcpy(st->cache + st->cache_len, Buf, new_bytes);
154 if( new_bytes == 0 ) {
155 _SysDebug("Term_HandleVT100: Hit max? (Len=%i) Flushing cache", Len);
160 st->cache_len += new_bytes;
161 assert(st->cache_len > old_inc_len);
163 if( st->cache_len <= 1 )
164 return 1; // Skip 1 character (the '\x1b')
166 ret = Term_HandleVT100_Short(Term, st->cache_len, st->cache);
169 // Check that we actually used the new data (as should have happened)
170 if( ret <= old_inc_len ) {
171 _SysDebug("Term_HandleVT100: ret(%i) <= old_inc_len(%i), inc_len=%i, '%*C'",
172 ret, old_inc_len, st->cache_len, st->cache_len, st->cache);
173 assert(ret > old_inc_len);
176 //_SysDebug("%i bytes of escape code '%.*s' (return %i)",
177 // ret, ret, inc_buf, ret-old_inc_len);
178 ret -= old_inc_len; // counter cached bytes
182 _SysDebug("Term_HandleVT100: Caching %i bytes '%*C'", ret, ret, st->cache);
191 //Display_SoundBell(Term);
194 // backspace is aprarently just supposed to cursor left (if possible)
195 Display_MoveCursor(Term, 0, -1);
198 // TODO: tab (get current cursor pos, space until multiple of 8)
199 _SysDebug("TODO: VT100 Support \\t tab");
200 Display_AddText(Term, 1, "\t"); // pass the buck for now
203 // TODO: Support disabling CR after NL
204 Display_Newline(Term, 1);
207 if( Len >= 2 && Buf[1] == '\n' ) {
208 // Fast case for \r\n
209 Display_Newline(Term, 1);
212 Display_MoveCursor(Term, 0, INT_MIN);
226 // Force an exit right now
239 * \brief Handle an escape code beginning with '\x1b'
240 * \return 0 : Insufficient data in buffer, wait for more
241 * \return +ve : Number of bytes in escape sequence
243 int Term_HandleVT100_Short(tTerminal *Term, int Len, const char *Buf)
248 case '[': // Multibyte, funtime starts
249 tmp = Term_HandleVT100_Long(Term, Len-2, Buf+2);
255 tmp = Term_HandleVT100_OSC(Term, Len-2, Buf+2);
267 _SysDebug("TODO \\e#%c DECALN - Fill screen with 'E'", Buf[2]);
270 _SysDebug("Unknown \\e#%c", Buf[2]);
276 _SysDebug("TODO: \\e= Application Keypad");
279 _SysDebug("TODO: \\e= Normal Keypad");
282 case '(': // Update G0 charset
284 case ')': // Update G1 charset
292 return 0; // We need more
295 case '0': // DEC Special Character/Linedrawing set
297 case 'B': // US ASCII
303 Display_SaveCursor(Term);
307 Display_RestoreCursor(Term);
310 // Cursor down, if at bottom scroll
311 Display_MoveCursor(Term, 1, 0);
312 // TODO: Scroll if at bottom (impl in _MoveCursor)
315 Display_MoveCursor(Term, 1, INT_MIN);
316 // TODO: Scroll if at bottom (impl in _MoveCursor)
319 // Cursor up, scroll if at top
320 Display_MoveCursor(Term, -1, 0);
323 _SysDebug("Unknown VT100 \\e%c", Buf[1]);
329 * \brief Handle CSI escape sequences '\x1b['
330 * \return 0 : insufficient data
331 * \return +ve : Number of bytes consumed
333 int Term_HandleVT100_Long(tTerminal *Term, int Len, const char *Buffer)
335 tVT100State *st = Display_GetTermState(Term);
338 int args[6] = {0,0,0,0,0,0};
339 int bQuestionMark = 0;
342 if(j == Len) return 0;
347 if(j == Len) return 0;
350 if( ('0' <= c && c <= '9') || c == ';' )
356 if(j == Len) return 0;
359 while('0' <= c && c <= '9') {
362 if(j == Len) return 0;
372 _SysDebug("Unexpected char 0x%x in VT100 escape code '\\e[%.*s'", c,
388 case 1: // Aplication cursor keys
389 _SysDebug("TODO: \\e[?1%c Application cursor keys", c);
391 case 3: // 132 Column mode
392 _SysDebug("TODO: \\e[?3%c 132 Column mode", c);
394 case 4: // Smooth (Slow) Scroll
395 _SysDebug("TODO: \\e[?4%c Smooth (Slow) Scroll", c);
397 case 5: // Reverse Video
398 _SysDebug("TODO: \\e[?5%c Reverse Video", c);
400 case 6: // Origin Mode
401 _SysDebug("TODO: \\e[?6%c Origin Mode", c);
404 //_SysDebug("TODO: \\e[?25%c Start/Stop blinking cursor", c);
405 //Display_SolidCursor(Term, !set);
407 case 25: // Hide cursor
408 //_SysDebug("TODO: \\e[?25%c Show/Hide cursor", c);
409 //Display_ShowCursor(Term, set);
411 case 1047: // Alternate buffer
412 Display_ShowAltBuffer(Term, set);
414 case 1048: // Save/restore cursor in DECSC
415 _SysDebug("TODO: \\e[?1048%c Save/Restore cursor", c);
417 case 1049: // Save/restore cursor in DECSC and use alternate buffer
418 _SysDebug("TODO: \\e[?1049%c Save/Restore cursor", c);
419 Display_ShowAltBuffer(Term, set);
422 _SysDebug("TODO: \\e[?%i%c Unknow DEC private mode", args[0], c);
427 _SysDebug("Unknown VT100 extended escape char 0x%x", c);
437 Display_MoveCursor(Term, -(args[0] != 0 ? args[0] : 1), 0);
440 Display_MoveCursor(Term, (args[0] != 0 ? args[0] : 1), 0);
443 Display_MoveCursor(Term, 0, (args[0] != 0 ? args[0] : 1));
446 Display_MoveCursor(Term, 0, -(args[0] != 0 ? args[0] : 1));
452 // Adjust 1-based cursor position to 0-based
453 Display_SetCursor(Term, args[0]-1, args[1]-1);
456 case 'J': // Clear lines
460 Display_ClearLines(Term, 1); // Down
463 Display_ClearLines(Term, -1); // Up
466 Display_ClearLines(Term, 0); // All
469 _SysDebug("Unknown VT100 %i J", args[0]);
477 Display_ClearLine(Term, 1);
480 Display_ClearLine(Term, -1);
483 Display_ClearLine(Term, 0);
486 _SysDebug("Unknown VT100 %i K", args[0]);
490 case 'S': // Scroll text up n=1
491 Display_ScrollDown(Term, (argc >= 1 ? args[0] : 1));
493 case 'T': // Scroll text down n=1
494 Display_ScrollDown(Term, -(argc >= 1 ? args[0] : 1));
496 case 'c': // Send Device Attributes
499 case 0: // Request attributes from terminal
500 // "VT100 with Advanced Video Option" (same as screen returns)
501 Display_SendInput(Term, "\x1b[?1;2c");
504 _SysDebug("TODO: Request device attributes \\e[%ic", args[0]);
510 Display_SetCursor(Term, 0, 0);
513 // Adjust 1-based cursor position to 0-based
514 Display_SetCursor(Term, args[0]-1, args[1]-1);
519 for( int i = 0; i < argc; i ++ )
524 _SysDebug("Unknown VT100 mode \e[%i%c",
534 Display_ResetAttributes(Term);
536 else if( args[0] == 48 )
538 // ISO-8613-3 Background
541 col |= (uint32_t)args[2] << 16;
542 col |= (uint32_t)args[3] << 8;
543 col |= (uint32_t)args[4] << 0;
544 Display_SetBackground(Term, col);
546 else if( args[1] == 5 ) {
547 _SysDebug("TODO: Support xterm palette BG %i", args[2]);
550 _SysDebug("VT100 Unknown mode set \e[48;%im", args[1]);
553 else if( args[0] == 38 )
555 // ISO-8613-3 Foreground
558 col |= (uint32_t)args[2] << 16;
559 col |= (uint32_t)args[3] << 8;
560 col |= (uint32_t)args[4] << 0;
561 Display_SetForeground(Term, col);
563 else if( args[1] == 5 ) {
564 _SysDebug("TODO: Support xterm palette FG %i", args[2]);
567 _SysDebug("VT100 Unknown mode set \e[38;%im", args[1]);
572 for( int i = 0; i < argc; i ++ )
578 Display_ResetAttributes(Term);
581 st->Flags |= FLAG_BOLD;
582 Display_SetForeground( Term, caVT100Colours[st->CurFG + 8] );
585 _SysDebug("TODO: \\e[2m - Reverse");
588 _SysDebug("TODO: \\e[4m - Underscore");
591 // _SysDebug("TODO: \\e[5m - Blink/bold");
594 _SysDebug("TODO: \\e[7m - Reverse");
596 case 24: // Not underlined
597 case 27: // Not inverse
600 st->CurFG = args[i]-30;
604 Display_SetForeground( Term,
605 caVT100Colours[ st->CurFG + (st->Flags&FLAG_BOLD?8:0) ] );
608 st->CurBG = args[i]-40;
612 Display_SetBackground( Term, caVT100Colours[ st->CurBG ] );
615 st->CurFG = args[i]-90 + 8;
616 Display_SetForeground( Term, caVT100Colours[ st->CurBG ] );
619 st->CurBG = args[i]-100 + 8;
620 Display_SetBackground( Term, caVT100Colours[ st->CurBG ] );
623 _SysDebug("Unknown mode set \\e[%im", args[i]);
629 // Device Status Report
632 // Set scrolling region
634 Display_SetScrollArea(Term, args[0], (args[1] - args[0]));
638 Display_SaveCursor(Term);
641 Display_RestoreCursor(Term);
644 _SysDebug("Unknown VT100 long escape char 0x%x '%c'", c, c);
651 int Term_HandleVT100_OSC(tTerminal *Term, int Len, const char *Buf)
653 tVT100State *st = Display_GetTermState(Term);
656 // OSC Ps ; Pt [ST/BEL]
657 if(Len < 2) return 0; // Need moar
660 while( ofs < Len && isdigit(Buf[ofs]) ) {
661 Ps = Ps * 10 + (Buf[ofs] - '0');
665 if( ofs == Len ) return 0;
666 if( Buf[ofs] != ';' ) {
668 st->Mode = MODE_STRING;
669 st->StringType = STRING_IGNORE;
676 case 0: // Icon Name + Window Title
678 case 2: // Window Title
679 st->Mode = MODE_STRING;
680 st->StringType = STRING_TITLE;
682 case 3: // Set X Property
683 _SysDebug("TODO: \\e]3; Support X properties");
684 st->Mode = MODE_STRING;
685 st->StringType = STRING_IGNORE;
687 case 4: // Change colour number
688 case 5: // Change special colour number
689 _SysDebug("TODO: \\e]%i;c; Support X properties", Ps);
690 st->Mode = MODE_STRING;
691 st->StringType = STRING_IGNORE;
693 case 10: // Change foreground to Pt
694 case 11: // Change background to Pt
696 // TODO: Can be Pt = [cps01234567]*;<str>
697 // > Clipboard data in base-64, cut/primary/select buffers 0-7
698 // > <str>='?' returns the clipbard in the same format
700 _SysDebug("Unknown VT100 OSC \\e]%i;", Ps);
701 st->Mode = MODE_STRING;
702 st->StringType = STRING_IGNORE;