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 # define ASSERTC(a, r, b) assert(a r b)
22 static inline int MIN(int a, int b)
38 #define FLAG_BOLD 0x01
39 #define FLAG_REVERSE 0x02
41 #define MAX_VT100_ESCAPE_LEN 32
46 char cache[MAX_VT100_ESCAPE_LEN];
49 enum eExcapeMode Mode;
51 enum eStringType StringType;
56 const uint32_t caVT100Colours[] = {
57 // Black, Red, Green, Yellow, Blue, Magenta, Cyan, Gray
58 // Same again, but bright
59 0x000000, 0x770000, 0x007700, 0x777700, 0x000077, 0x770077, 0x007777, 0xAAAAAA,
60 0xCCCCCC, 0xFF0000, 0x00FF00, 0xFFFF00, 0x0000FF, 0xFF00FF, 0x00FFFF, 0xFFFFFF,
63 int _locate_eos(size_t Len, const char *Buf);
64 int Term_HandleVT100_Short(tTerminal *Term, int Len, const char *Buf);
65 int Term_HandleVT100_Long(tTerminal *Term, int Len, const char *Buf);
66 int Term_HandleVT100_OSC(tTerminal *Term, int Len, const char *Buf);
68 int _locate_eos(size_t Len, const char *Buf)
70 for( size_t ret = 0; ret < Len; ret ++ )
72 if( Buf[ret] == '\007' )
74 if( Buf[ret] == '\x9c' )
76 if( ret+1 < Len && Buf[ret] == '\x1b' && Buf[ret+1] == '\\' )
83 * \brief Detect and handle VT100/ANSI/xterm escape sequences
84 * \param Term Terminal handle (opaque)
85 * \param Len Number of avaliable bytes
86 * \param Buf Input buffer (\a Len bytes long)
87 * \return -ve : Number of bytes that should be sent to screen
88 * \return +ve/0 : Number of bytes consumed by this function
90 int Term_HandleVT100(tTerminal *Term, int Len, const char *Buf)
92 tVT100State *st = Display_GetTermState(Term);
95 st = malloc( sizeof(*st) );
96 memset(st, 0, sizeof(*st));
97 Display_SetTermState(Term, st);
100 if( st->Mode == MODE_IGNORE ) {
101 st->Mode = MODE_NORMAL;
102 // Used for multi-byte EOS
103 _SysDebug("Ignore 1 '%c'", *Buf);
106 else if( st->Mode == MODE_STRING )
108 // We're in a string mode
109 int pos = _locate_eos(Len, Buf);
110 size_t bytes = (pos >= 0 ? pos : Len);
111 char *tmp = realloc(st->StringCache, st->StringLen + bytes+1);
112 if(!tmp) return bytes;
113 st->StringCache = tmp;
114 memcpy(tmp+st->StringLen, Buf, bytes);
115 tmp[st->StringLen+bytes] = 0;
116 st->StringLen += bytes;
118 _SysDebug("pos=%i", pos);
119 _SysDebug("Buf[+%zi] = '%.*s'", bytes, bytes, Buf);
120 // Only apply when we hit EOS at the start
123 switch(st->StringType)
126 Display_SetTitle(Term, st->StringCache);
131 free(st->StringCache);
135 if( *Buf == '\x1b' ) {
136 st->Mode = MODE_IGNORE;
140 st->Mode = MODE_NORMAL;
147 if( st->cache_len > 0 || *Buf == '\x1b' )
149 // Handle VT100 (like) escape sequence
150 int new_bytes = MIN(MAX_VT100_ESCAPE_LEN - st->cache_len, Len);
152 int old_inc_len = st->cache_len;
154 memcpy(st->cache + st->cache_len, Buf, new_bytes);
156 if( new_bytes == 0 ) {
157 _SysDebug("Term_HandleVT100: Hit max? (Len=%i) Flushing cache", Len);
162 st->cache_len += new_bytes;
163 assert(st->cache_len > old_inc_len);
165 if( st->cache_len <= 1 )
166 return 1; // Skip 1 character (the '\x1b')
168 ret = Term_HandleVT100_Short(Term, st->cache_len, st->cache);
171 // Check that we actually used the new data (as should have happened)
172 if( ret <= old_inc_len ) {
173 _SysDebug("Term_HandleVT100: ret(%i) <= old_inc_len(%i), inc_len=%i, '%*C'",
174 ret, old_inc_len, st->cache_len, st->cache_len, st->cache);
175 ASSERTC(ret, >, old_inc_len);
178 //_SysDebug("%i bytes of escape code '%.*s' (return %i)",
179 // ret, ret, inc_buf, ret-old_inc_len);
180 ret -= old_inc_len; // counter cached bytes
184 _SysDebug("Term_HandleVT100: Caching %i bytes '%*C'", ret, ret, st->cache);
193 //Display_SoundBell(Term);
196 // backspace is aprarently just supposed to cursor left (if possible)
197 Display_MoveCursor(Term, 0, -1);
200 // TODO: tab (get current cursor pos, space until multiple of 8)
201 _SysDebug("TODO: VT100 Support \\t tab");
202 Display_AddText(Term, 1, "\t"); // pass the buck for now
205 // TODO: Support disabling CR after NL
206 Display_Newline(Term, 1);
209 if( Len >= 2 && Buf[1] == '\n' ) {
210 // Fast case for \r\n
211 Display_Newline(Term, 1);
214 Display_MoveCursor(Term, 0, INT_MIN);
228 // Force an exit right now
241 * \brief Handle an escape code beginning with '\x1b'
242 * \return 0 : Insufficient data in buffer, wait for more
243 * \return +ve : Number of bytes in escape sequence
245 int Term_HandleVT100_Short(tTerminal *Term, int Len, const char *Buf)
250 case '[': // Multibyte, funtime starts
251 tmp = Term_HandleVT100_Long(Term, Len-2, Buf+2);
257 tmp = Term_HandleVT100_OSC(Term, Len-2, Buf+2);
269 _SysDebug("TODO \\e#%c DECALN - Fill screen with 'E'", Buf[2]);
272 _SysDebug("Unknown \\e#%c", Buf[2]);
278 _SysDebug("TODO: \\e= Application Keypad");
281 _SysDebug("TODO: \\e= Normal Keypad");
284 case '(': // Update G0 charset
286 case ')': // Update G1 charset
294 return 0; // We need more
297 case '0': // DEC Special Character/Linedrawing set
299 case 'B': // US ASCII
305 Display_SaveCursor(Term);
309 Display_RestoreCursor(Term);
312 // Cursor down, if at bottom scroll
313 Display_MoveCursor(Term, 1, 0);
314 // TODO: Scroll if at bottom (impl in _MoveCursor)
317 Display_MoveCursor(Term, 1, INT_MIN);
318 // TODO: Scroll if at bottom (impl in _MoveCursor)
321 // Cursor up, scroll if at top
322 Display_MoveCursor(Term, -1, 0);
325 _SysDebug("Unknown VT100 \\e%c", Buf[1]);
331 * \brief Handle CSI escape sequences '\x1b['
332 * \return 0 : insufficient data
333 * \return +ve : Number of bytes consumed
335 int Term_HandleVT100_Long(tTerminal *Term, int Len, const char *Buffer)
337 tVT100State *st = Display_GetTermState(Term);
340 int args[6] = {0,0,0,0,0,0};
341 int bQuestionMark = 0;
344 if(j == Len) return 0;
349 if(j == Len) return 0;
352 if( ('0' <= c && c <= '9') || c == ';' )
358 if(j == Len) return 0;
361 while('0' <= c && c <= '9') {
364 if(j == Len) return 0;
374 _SysDebug("Unexpected char 0x%x in VT100 escape code '\\e[%.*s'", c,
390 case 1: // Aplication cursor keys
391 _SysDebug("TODO: \\e[?1%c Application cursor keys", c);
393 case 3: // 132 Column mode
394 _SysDebug("TODO: \\e[?3%c 132 Column mode", c);
396 case 4: // Smooth (Slow) Scroll
397 _SysDebug("TODO: \\e[?4%c Smooth (Slow) Scroll", c);
399 case 5: // Reverse Video
400 _SysDebug("TODO: \\e[?5%c Reverse Video", c);
402 case 6: // Origin Mode
403 _SysDebug("TODO: \\e[?6%c Origin Mode", c);
406 //_SysDebug("TODO: \\e[?25%c Start/Stop blinking cursor", c);
407 //Display_SolidCursor(Term, !set);
409 case 25: // Hide cursor
410 //_SysDebug("TODO: \\e[?25%c Show/Hide cursor", c);
411 //Display_ShowCursor(Term, set);
413 case 1047: // Alternate buffer
414 Display_ShowAltBuffer(Term, set);
416 case 1048: // Save/restore cursor in DECSC
417 _SysDebug("TODO: \\e[?1048%c Save/Restore cursor", c);
419 case 1049: // Save/restore cursor in DECSC and use alternate buffer
420 _SysDebug("TODO: \\e[?1049%c Save/Restore cursor", c);
421 Display_ShowAltBuffer(Term, set);
424 _SysDebug("TODO: \\e[?%i%c Unknow DEC private mode", args[0], c);
429 _SysDebug("Unknown VT100 extended escape char 0x%x", c);
439 Display_MoveCursor(Term, -(args[0] != 0 ? args[0] : 1), 0);
442 Display_MoveCursor(Term, (args[0] != 0 ? args[0] : 1), 0);
445 Display_MoveCursor(Term, 0, (args[0] != 0 ? args[0] : 1));
448 Display_MoveCursor(Term, 0, -(args[0] != 0 ? args[0] : 1));
452 Display_SetCursor(Term, 0, 0);
454 else if( argc == 1 ) {
455 Display_SetCursor(Term, args[0]-1, 0);
457 else if( argc == 2 ) {
458 // Adjust 1-based cursor position to 0-based
459 Display_SetCursor(Term, args[0]-1, args[1]-1);
462 case 'J': // Clear lines
466 Display_ClearLines(Term, 1); // Down
469 Display_ClearLines(Term, -1); // Up
472 Display_ClearLines(Term, 0); // All
475 _SysDebug("Unknown VT100 %i J", args[0]);
483 Display_ClearLine(Term, 1);
486 Display_ClearLine(Term, -1);
489 Display_ClearLine(Term, 0);
492 _SysDebug("Unknown VT100 %i K", args[0]);
496 case 'S': // Scroll text up n=1 (expose bottom)
497 Display_ScrollDown(Term, -(argc >= 1 ? args[0] : 1));
499 case 'T': // Scroll text down n=1 (expose top)
500 Display_ScrollDown(Term, (argc >= 1 ? args[0] : 1));
502 case 'c': // Send Device Attributes
505 case 0: // Request attributes from terminal
506 // "VT100 with Advanced Video Option" (same as screen returns)
507 Display_SendInput(Term, "\x1b[?1;2c");
510 _SysDebug("TODO: Request device attributes \\e[%ic", args[0]);
516 Display_SetCursor(Term, 0, 0);
519 // Adjust 1-based cursor position to 0-based
520 Display_SetCursor(Term, args[0]-1, args[1]-1);
525 for( int i = 0; i < argc; i ++ )
530 _SysDebug("Unknown VT100 mode \e[%i%c",
540 Display_ResetAttributes(Term);
542 else if( args[0] == 48 )
544 // ISO-8613-3 Background
547 col |= (uint32_t)args[2] << 16;
548 col |= (uint32_t)args[3] << 8;
549 col |= (uint32_t)args[4] << 0;
550 Display_SetBackground(Term, col);
552 else if( args[1] == 5 ) {
553 _SysDebug("TODO: Support xterm palette BG %i", args[2]);
556 _SysDebug("VT100 Unknown mode set \e[48;%im", args[1]);
559 else if( args[0] == 38 )
561 // ISO-8613-3 Foreground
564 col |= (uint32_t)args[2] << 16;
565 col |= (uint32_t)args[3] << 8;
566 col |= (uint32_t)args[4] << 0;
567 Display_SetForeground(Term, col);
569 else if( args[1] == 5 ) {
570 _SysDebug("TODO: Support xterm palette FG %i", args[2]);
573 _SysDebug("VT100 Unknown mode set \e[38;%im", args[1]);
578 for( int i = 0; i < argc; i ++ )
584 Display_ResetAttributes(Term);
587 st->Flags |= FLAG_BOLD;
588 Display_SetForeground( Term, caVT100Colours[st->CurFG + 8] );
591 _SysDebug("TODO: \\e[2m - Reverse");
594 _SysDebug("TODO: \\e[4m - Underscore");
597 // _SysDebug("TODO: \\e[5m - Blink/bold");
600 _SysDebug("TODO: \\e[7m - Reverse");
602 case 24: // Not underlined
603 case 27: // Not inverse
606 st->CurFG = args[i]-30;
610 Display_SetForeground( Term,
611 caVT100Colours[ st->CurFG + (st->Flags&FLAG_BOLD?8:0) ] );
614 st->CurBG = args[i]-40;
618 Display_SetBackground( Term, caVT100Colours[ st->CurBG ] );
621 st->CurFG = args[i]-90 + 8;
622 Display_SetForeground( Term, caVT100Colours[ st->CurBG ] );
625 st->CurBG = args[i]-100 + 8;
626 Display_SetBackground( Term, caVT100Colours[ st->CurBG ] );
629 _SysDebug("Unknown mode set \\e[%im", args[i]);
635 // Device Status Report
638 // Set scrolling region
640 Display_SetScrollArea(Term, args[0]-1, (args[1] - args[0])+1);
644 Display_SaveCursor(Term);
647 Display_RestoreCursor(Term);
650 _SysDebug("Unknown VT100 long escape char 0x%x '%c'", c, c);
657 int Term_HandleVT100_OSC(tTerminal *Term, int Len, const char *Buf)
659 tVT100State *st = Display_GetTermState(Term);
662 // OSC Ps ; Pt [ST/BEL]
663 if(Len < 2) return 0; // Need moar
666 while( ofs < Len && isdigit(Buf[ofs]) ) {
667 Ps = Ps * 10 + (Buf[ofs] - '0');
671 if( ofs == Len ) return 0;
672 if( Buf[ofs] != ';' ) {
674 st->Mode = MODE_STRING;
675 st->StringType = STRING_IGNORE;
682 case 0: // Icon Name + Window Title
684 case 2: // Window Title
685 st->Mode = MODE_STRING;
686 st->StringType = STRING_TITLE;
688 case 3: // Set X Property
689 _SysDebug("TODO: \\e]3; Support X properties");
690 st->Mode = MODE_STRING;
691 st->StringType = STRING_IGNORE;
693 case 4: // Change colour number
694 case 5: // Change special colour number
695 _SysDebug("TODO: \\e]%i;c; Support X properties", Ps);
696 st->Mode = MODE_STRING;
697 st->StringType = STRING_IGNORE;
699 case 10: // Change foreground to Pt
700 case 11: // Change background to Pt
702 // TODO: Can be Pt = [cps01234567]*;<str>
703 // > Clipboard data in base-64, cut/primary/select buffers 0-7
704 // > <str>='?' returns the clipbard in the same format
706 _SysDebug("Unknown VT100 OSC \\e]%i;", Ps);
707 st->Mode = MODE_STRING;
708 st->StringType = STRING_IGNORE;