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);
190 // backspace is aprarently just supposed to cursor left (if possible)
191 Display_MoveCursor(Term, 0, -1);
194 // TODO: tab (get current cursor pos, space until multiple of 8)
195 _SysDebug("TODO: VT100 Support \\t tab");
196 Display_AddText(Term, 1, "\t"); // pass the buck for now
199 // TODO: Support disabling CR after NL
200 Display_Newline(Term, 1);
203 if( Len >= 2 && Buf[1] == '\n' ) {
204 // Fast case for \r\n
205 Display_Newline(Term, 1);
208 Display_MoveCursor(Term, 0, INT_MIN);
222 // Force an exit right now
235 * \brief Handle an escape code beginning with '\x1b'
236 * \return 0 : Insufficient data in buffer, wait for more
237 * \return +ve : Number of bytes in escape sequence
239 int Term_HandleVT100_Short(tTerminal *Term, int Len, const char *Buf)
244 case '[': // Multibyte, funtime starts
245 tmp = Term_HandleVT100_Long(Term, Len-2, Buf+2);
251 tmp = Term_HandleVT100_OSC(Term, Len-2, Buf+2);
263 _SysDebug("TODO \\e#%c DECALN - Fill screen with 'E'", Buf[2]);
266 _SysDebug("Unknown \\e#%c", Buf[2]);
272 _SysDebug("TODO: \\e= Application Keypad");
275 _SysDebug("TODO: \\e= Normal Keypad");
278 case '(': // Update G0 charset
280 case ')': // Update G1 charset
288 return 0; // We need more
291 case '0': // DEC Special Character/Linedrawing set
293 case 'B': // US ASCII
299 Display_SaveCursor(Term);
303 Display_RestoreCursor(Term);
306 // Cursor down, if at bottom scroll
307 Display_MoveCursor(Term, 1, 0);
308 // TODO: Scroll if at bottom (impl in _MoveCursor)
311 Display_MoveCursor(Term, 1, INT_MIN);
312 // TODO: Scroll if at bottom (impl in _MoveCursor)
315 // Cursor up, scroll if at top
316 Display_MoveCursor(Term, -1, 0);
319 _SysDebug("Unknown VT100 \\e%c", Buf[1]);
325 * \brief Handle CSI escape sequences '\x1b['
326 * \return 0 : insufficient data
327 * \return +ve : Number of bytes consumed
329 int Term_HandleVT100_Long(tTerminal *Term, int Len, const char *Buffer)
331 tVT100State *st = Display_GetTermState(Term);
334 int args[6] = {0,0,0,0,0,0};
335 int bQuestionMark = 0;
338 if(j == Len) return 0;
343 if(j == Len) return 0;
346 if( ('0' <= c && c <= '9') || c == ';' )
352 if(j == Len) return 0;
355 while('0' <= c && c <= '9') {
358 if(j == Len) return 0;
368 _SysDebug("Unexpected char 0x%x in VT100 escape code '\\e[%.*s'", c,
384 case 1: // Aplication cursor keys
385 _SysDebug("TODO: \\e[?1%c Application cursor keys", c);
387 case 3: // 132 Column mode
388 _SysDebug("TODO: \\e[?3%c 132 Column mode", c);
390 case 4: // Smooth (Slow) Scroll
391 _SysDebug("TODO: \\e[?4%c Smooth (Slow) Scroll", c);
393 case 5: // Reverse Video
394 _SysDebug("TODO: \\e[?5%c Reverse Video", c);
396 case 6: // Origin Mode
397 _SysDebug("TODO: \\e[?6%c Origin Mode", c);
400 //_SysDebug("TODO: \\e[?25%c Start/Stop blinking cursor", c);
401 //Display_SolidCursor(Term, !set);
403 case 25: // Hide cursor
404 //_SysDebug("TODO: \\e[?25%c Show/Hide cursor", c);
405 //Display_ShowCursor(Term, set);
407 case 1047: // Alternate buffer
408 Display_ShowAltBuffer(Term, set);
410 case 1048: // Save/restore cursor in DECSC
411 _SysDebug("TODO: \\e[?1048%c Save/Restore cursor", c);
413 case 1049: // Save/restore cursor in DECSC and use alternate buffer
414 _SysDebug("TODO: \\e[?1049%c Save/Restore cursor", c);
415 Display_ShowAltBuffer(Term, set);
418 _SysDebug("TODO: \\e[?%i%c Unknow DEC private mode", args[0], c);
423 _SysDebug("Unknown VT100 extended escape char 0x%x", c);
433 Display_MoveCursor(Term, -(args[0] != 0 ? args[0] : 1), 0);
436 Display_MoveCursor(Term, (args[0] != 0 ? args[0] : 1), 0);
439 Display_MoveCursor(Term, 0, (args[0] != 0 ? args[0] : 1));
442 Display_MoveCursor(Term, 0, -(args[0] != 0 ? args[0] : 1));
448 // Adjust 1-based cursor position to 0-based
449 Display_SetCursor(Term, args[0]-1, args[1]-1);
452 case 'J': // Clear lines
456 Display_ClearLines(Term, 1); // Down
459 Display_ClearLines(Term, -1); // Up
462 Display_ClearLines(Term, 0); // All
465 _SysDebug("Unknown VT100 %i J", args[0]);
473 Display_ClearLine(Term, 1);
476 Display_ClearLine(Term, -1);
479 Display_ClearLine(Term, 0);
482 _SysDebug("Unknown VT100 %i K", args[0]);
486 case 'S': // Scroll text up n=1
487 Display_ScrollDown(Term, (argc >= 1 ? args[0] : 1));
489 case 'T': // Scroll text down n=1
490 Display_ScrollDown(Term, -(argc >= 1 ? args[0] : 1));
492 case 'c': // Send Device Attributes
495 case 0: // Request attributes from terminal
496 // "VT100 with Advanced Video Option" (same as screen returns)
497 Display_SendInput(Term, "\x1b[?1;2c");
500 _SysDebug("TODO: Request device attributes \\e[%ic", args[0]);
506 Display_SetCursor(Term, 0, 0);
509 // Adjust 1-based cursor position to 0-based
510 Display_SetCursor(Term, args[0]-1, args[1]-1);
515 for( int i = 0; i < argc; i ++ )
520 _SysDebug("Unknown VT100 mode \e[%i%c",
530 Display_ResetAttributes(Term);
534 for( int i = 0; i < argc; i ++ )
540 Display_ResetAttributes(Term);
543 st->Flags |= FLAG_BOLD;
544 Display_SetForeground( Term, caVT100Colours[st->CurFG + 8] );
547 _SysDebug("TODO: \\e[2m - Reverse");
550 _SysDebug("TODO: \\e[4m - Underscore");
553 _SysDebug("TODO: \\e[7m - Reverse");
555 case 24: // Not underlined
556 case 27: // Not inverse
559 st->CurFG = args[i]-30;
563 Display_SetForeground( Term,
564 caVT100Colours[ st->CurFG + (st->Flags&FLAG_BOLD?8:0) ] );
567 st->CurBG = args[i]-40;
571 Display_SetBackground( Term, caVT100Colours[ st->CurBG ] );
574 _SysDebug("Unknown mode set \\e[%im", args[i]);
580 // Device Status Report
583 // Set scrolling region
585 Display_SetScrollArea(Term, args[0], (args[1] - args[0]));
589 Display_SaveCursor(Term);
592 Display_RestoreCursor(Term);
595 _SysDebug("Unknown VT100 long escape char 0x%x '%c'", c, c);
602 int Term_HandleVT100_OSC(tTerminal *Term, int Len, const char *Buf)
604 tVT100State *st = Display_GetTermState(Term);
607 // OSC Ps ; Pt [ST/BEL]
608 if(Len < 2) return 0; // Need moar
611 while( ofs < Len && isdigit(Buf[ofs]) ) {
612 Ps = Ps * 10 + (Buf[ofs] - '0');
616 if( ofs == Len ) return 0;
617 if( Buf[ofs] != ';' ) {
619 st->Mode = MODE_STRING;
620 st->StringType = STRING_IGNORE;
627 case 0: // Icon Name + Window Title
629 case 2: // Window Title
630 st->Mode = MODE_STRING;
631 st->StringType = STRING_TITLE;
633 case 3: // Set X Property
634 _SysDebug("TODO: \\e]3; Support X properties");
635 st->Mode = MODE_STRING;
636 st->StringType = STRING_IGNORE;
638 case 4: // Change colour number
639 case 5: // Change special colour number
640 _SysDebug("TODO: \\e]%i;c; Support X properties", Ps);
641 st->Mode = MODE_STRING;
642 st->StringType = STRING_IGNORE;
644 case 10: // Change foreground to Pt
645 case 11: // Change background to Pt
647 // TODO: Can be Pt = [cps01234567]*;<str>
648 // > Clipboard data in base-64, cut/primary/select buffers 0-7
649 // > <str>='?' returns the clipbard in the same format
651 _SysDebug("Unknown VT100 OSC \\e]%i;", Ps);
652 st->Mode = MODE_STRING;
653 st->StringType = STRING_IGNORE;