0f00bd994e4b9802a42d80b697c26e3a04d4a4d8
[tpg/acess2.git] / Usermode / Applications / gui_terminal_src / vt100.c
1 /*
2  * Acess GUI Terminal
3  * - By John Hodge (thePowersGang)
4  *
5  * vt100.c
6  * - VT100/xterm Emulation
7  */
8 #include <limits.h>
9 #include "include/vt100.h"
10 #include "include/display.h"
11 #include <ctype.h>      // isalpha
12 #ifdef KERNEL_VERSION
13 # define _SysDebug(v...)        Debug("VT100 "v)
14 #else
15 # include <acess/sys.h> // _SysDebug
16 # include <string.h>
17 # include <assert.h>
18 # include <stdlib.h>    // malloc/free
19
20 static inline int MIN(int a, int b)
21 {
22         return a < b ? a : b;
23 }
24 #endif
25
26 enum eExcapeMode {
27         MODE_NORMAL,
28         MODE_IGNORE,
29         MODE_STRING
30 };
31 enum eStringType {
32         STRING_IGNORE,
33         STRING_TITLE
34 };
35
36 #define FLAG_BOLD       0x01
37 #define FLAG_REVERSE    0x02
38
39 #define MAX_VT100_ESCAPE_LEN    32
40 typedef struct {
41         uint32_t        Flags;
42         int     CurFG, CurBG;
43
44         char    cache[MAX_VT100_ESCAPE_LEN];
45          int    cache_len;      
46
47         enum eExcapeMode        Mode;
48         
49         enum eStringType        StringType;
50         size_t  StringLen;
51         char    *StringCache;
52 } tVT100State;
53
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, 
59 };
60
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);
65
66 int _locate_eos(size_t Len, const char *Buf)
67 {
68         for( size_t ret = 0; ret < Len; ret ++ )
69         {
70                 if( Buf[ret] == '\007' )
71                         return ret;
72                 if( Buf[ret] == '\x9c' )
73                         return ret;
74                 if( ret+1 < Len && Buf[ret] == '\x1b' && Buf[ret+1] == '\\' )
75                         return ret;
76         }
77         return -1;
78 }
79
80 /**
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
87  */
88 int Term_HandleVT100(tTerminal *Term, int Len, const char *Buf)
89 {
90         tVT100State     *st = Display_GetTermState(Term);
91         
92         if( st == NULL ) {
93                 st = malloc( sizeof(*st) );
94                 memset(st, 0, sizeof(*st));
95                 Display_SetTermState(Term, st);
96         }
97
98         if( st->Mode == MODE_IGNORE ) {
99                 st->Mode = MODE_NORMAL;
100                 // Used for multi-byte EOS
101                 _SysDebug("Ignore 1 '%c'", *Buf);
102                 return 1;
103         }
104         else if( st->Mode == MODE_STRING )
105         {
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;
115                 
116                 _SysDebug("pos=%i", pos);
117                 _SysDebug("Buf[+%zi] = '%.*s'", bytes, bytes, Buf);
118                 // Only apply when we hit EOS at the start
119                 if( pos != 0 )
120                         return bytes;
121                 switch(st->StringType)
122                 {
123                 case STRING_TITLE:
124                         Display_SetTitle(Term, st->StringCache);
125                         break;
126                 case STRING_IGNORE:
127                         break;
128                 }
129                 free(st->StringCache);
130                 st->StringCache = 0;
131                 st->StringLen = 0;
132                 
133                 if( *Buf == '\x1b' ) {
134                         st->Mode = MODE_IGNORE;
135                         // skip the '\\'
136                 }
137                 else
138                         st->Mode = MODE_NORMAL;
139                 return 1;
140         }
141         else {
142                 // fall through
143         }
144
145         if( st->cache_len > 0   || *Buf == '\x1b' )
146         {
147                 // Handle VT100 (like) escape sequence
148                  int    new_bytes = MIN(MAX_VT100_ESCAPE_LEN - st->cache_len, Len);
149                  int    ret = 0;
150                  int    old_inc_len = st->cache_len;
151                 
152                 memcpy(st->cache + st->cache_len, Buf, new_bytes);
153
154                 if( new_bytes == 0 ) {
155                         _SysDebug("Term_HandleVT100: Hit max? (Len=%i) Flushing cache", Len);
156                         st->cache_len = 0;
157                         return 0;
158                 }
159
160                 st->cache_len += new_bytes;
161                 assert(st->cache_len > old_inc_len);
162
163                 if( st->cache_len <= 1 )
164                         return 1;       // Skip 1 character (the '\x1b')
165
166                 ret = Term_HandleVT100_Short(Term, st->cache_len, st->cache);
167
168                 if( ret != 0 ) {
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);
174                         }
175                         st->cache_len = 0;
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
179                 }
180                 else {
181                         ret = new_bytes;
182                         _SysDebug("Term_HandleVT100: Caching %i bytes '%*C'", ret, ret, st->cache);
183                 }
184                 return ret;
185         }
186
187         switch( *Buf )
188         {
189         case '\b':
190                 // backspace is aprarently just supposed to cursor left (if possible)
191                 Display_MoveCursor(Term, 0, -1);
192                 return 1;
193         case '\t':
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
197                 return 1;
198         case '\n':
199                 // TODO: Support disabling CR after NL
200                 Display_Newline(Term, 1);
201                 return 1;
202         case '\r':
203                 if( Len >= 2 && Buf[1] == '\n' ) {
204                         // Fast case for \r\n
205                         Display_Newline(Term, 1);
206                         return 2;
207                 }
208                 Display_MoveCursor(Term, 0, INT_MIN);
209                 return 1;
210         }
211
212          int    ret = 0;
213         while( ret < Len )
214         {
215                 switch(*Buf)
216                 {
217                 case '\x1b':
218                 case '\b':
219                 case '\t':
220                 case '\n':
221                 case '\r':
222                         // Force an exit right now
223                         Len = ret;
224                         break;
225                 default:
226                         ret ++;
227                         Buf ++;
228                         break;
229                 }
230         }
231         return -ret;
232 }
233
234 /**
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
238  */
239 int Term_HandleVT100_Short(tTerminal *Term, int Len, const char *Buf)
240 {
241          int    tmp;
242         switch(Buf[1])
243         {
244         case '[':       // Multibyte, funtime starts    
245                 tmp = Term_HandleVT100_Long(Term, Len-2, Buf+2);
246                 assert(tmp >= 0);
247                 if( tmp == 0 )
248                         return 0;
249                 return tmp + 2;
250         case ']':
251                 tmp = Term_HandleVT100_OSC(Term, Len-2, Buf+2);
252                 assert(tmp >= 0);
253                 if( tmp == 0 )
254                         return 0;
255                 return tmp + 2;
256         
257         case '#':
258                 if( Len == 2 )
259                         return 0;
260                 switch(Buf[2])
261                 {
262                 case 8:
263                         _SysDebug("TODO \\e#%c DECALN - Fill screen with 'E'", Buf[2]);
264                         break;
265                 default:
266                         _SysDebug("Unknown \\e#%c", Buf[2]);
267                         break;
268                 }
269                 return 3;
270                 
271         case '=':
272                 _SysDebug("TODO: \\e= Application Keypad");
273                 return 2;
274         case '>':
275                 _SysDebug("TODO: \\e= Normal Keypad");
276                 return 2;
277         
278         case '(':       // Update G0 charset
279                 tmp = 0; if(0)
280         case ')':       // Update G1 charset
281                 tmp = 1; if(0)
282         case '*':
283                 tmp = 2; if(0)
284         case '+':
285                 tmp = 3;
286                 
287                 if( Len <= 2 )
288                         return 0;       // We need more
289                 switch(Buf[2])
290                 {
291                 case '0':       // DEC Special Character/Linedrawing set
292                 case 'A':       // UK
293                 case 'B':       // US ASCII
294                         break;
295                 }
296                 return 3;
297         case '7':
298                 // Save cursor
299                 Display_SaveCursor(Term);
300                 return 2;
301         case '8':
302                 // Restore cursor
303                 Display_RestoreCursor(Term);
304                 return 2;
305         case 'D':
306                 // Cursor down, if at bottom scroll
307                 Display_MoveCursor(Term, 1, 0);
308                 // TODO: Scroll if at bottom (impl in _MoveCursor)
309                 return 2;
310         case 'E':
311                 Display_MoveCursor(Term, 1, INT_MIN);
312                 // TODO: Scroll if at bottom (impl in _MoveCursor)
313                 return 2;
314         case 'M':
315                 // Cursor up, scroll if at top
316                 Display_MoveCursor(Term, -1, 0);
317                 return 2;
318         default:
319                 _SysDebug("Unknown VT100 \\e%c", Buf[1]);
320                 return 2;
321         }
322 }
323
324 /**
325  * \brief Handle CSI escape sequences '\x1b['
326  * \return 0   : insufficient data
327  * \return +ve : Number of bytes consumed
328  */
329 int Term_HandleVT100_Long(tTerminal *Term, int Len, const char *Buffer)
330 {
331         tVT100State     *st = Display_GetTermState(Term);
332         char    c;
333          int    argc = 0, j = 0;
334          int    args[6] = {0,0,0,0,0,0};
335          int    bQuestionMark = 0;
336         
337         // Get Arguments
338         if(j == Len)    return 0;
339         c = Buffer[j++];
340         if(c == '?')
341         {
342                 bQuestionMark = 1;
343                 if(j == Len)    return 0;
344                 c = Buffer[j++];
345         }
346         if( ('0' <= c && c <= '9') || c == ';' )
347         {
348                 if(c == ';')
349                         argc ++;
350                 do {
351                         if(c == ';') {
352                                 if(j == Len)    return 0;
353                                 c = Buffer[j++];
354                         }
355                         while('0' <= c && c <= '9') {
356                                 args[argc] *= 10;
357                                 args[argc] += c-'0';
358                                 if(j == Len)    return 0;
359                                 c = Buffer[j++];
360                         }
361                         argc ++;
362                 } while(c == ';');
363         }
364         
365         // Get Command
366         if( !isalpha(c) ) {
367                 // Bother.
368                 _SysDebug("Unexpected char 0x%x in VT100 escape code '\\e[%.*s'", c,
369                         Len, Buffer);
370                 return j;
371         }
372
373         if( bQuestionMark )
374         {
375                  int    set = 0;
376                 // Special commands
377                 switch( c )
378                 {
379                 case 'h':       // set
380                         set = 1;
381                 case 'l':       // unset
382                         switch(args[0])
383                         {
384                         case  1:        // Aplication cursor keys
385                                 _SysDebug("TODO: \\e[?1%c Application cursor keys", c);
386                                 break;
387                         case  3:        // 132 Column mode
388                                 _SysDebug("TODO: \\e[?3%c 132 Column mode", c);
389                                 break;
390                         case  4:        // Smooth (Slow) Scroll
391                                 _SysDebug("TODO: \\e[?4%c Smooth (Slow) Scroll", c);
392                                 break;
393                         case  5:        // Reverse Video
394                                 _SysDebug("TODO: \\e[?5%c Reverse Video", c);
395                                 break;
396                         case  6:        // Origin Mode
397                                 _SysDebug("TODO: \\e[?6%c Origin Mode", c);
398                                 break;
399                         case 12:
400                                 //_SysDebug("TODO: \\e[?25%c Start/Stop blinking cursor", c);
401                                 //Display_SolidCursor(Term, !set);
402                                 break;
403                         case 25:        // Hide cursor
404                                 //_SysDebug("TODO: \\e[?25%c Show/Hide cursor", c);
405                                 //Display_ShowCursor(Term, set);
406                                 break;
407                         case 1047:      // Alternate buffer
408                                 Display_ShowAltBuffer(Term, set);
409                                 break;
410                         case 1048:      // Save/restore cursor in DECSC
411                                 _SysDebug("TODO: \\e[?1048%c Save/Restore cursor", c);
412                                 break;
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);
416                                 break;
417                         default:
418                                 _SysDebug("TODO: \\e[?%i%c Unknow DEC private mode", args[0], c);
419                                 break;
420                         }
421                         break;
422                 default:
423                         _SysDebug("Unknown VT100 extended escape char 0x%x", c);
424                         break;
425                 }
426         }
427         else
428         {
429                 // Standard commands
430                 switch( c )
431                 {
432                 case 'A':
433                         Display_MoveCursor(Term, -(args[0] != 0 ? args[0] : 1), 0);
434                         break;
435                 case 'B':
436                         Display_MoveCursor(Term, (args[0] != 0 ? args[0] : 1), 0);
437                         break;
438                 case 'C':
439                         Display_MoveCursor(Term, 0, (args[0] != 0 ? args[0] : 1));
440                         break;
441                 case 'D':
442                         Display_MoveCursor(Term, 0, -(args[0] != 0 ? args[0] : 1));
443                         break;
444                 case 'H':
445                         if( argc != 2 ) {
446                         }
447                         else {
448                                 // Adjust 1-based cursor position to 0-based
449                                 Display_SetCursor(Term, args[0]-1, args[1]-1);
450                         }
451                         break;
452                 case 'J':       // Clear lines
453                         switch( args[0] )
454                         {
455                         case 0:
456                                 Display_ClearLines(Term, 1);    // Down
457                                 break;
458                         case 1:
459                                 Display_ClearLines(Term, -1);   // Up
460                                 break;
461                         case 2:
462                                 Display_ClearLines(Term, 0);    // All
463                                 break;
464                         default:
465                                 _SysDebug("Unknown VT100 %i J", args[0]);
466                                 break;
467                         }
468                         break;
469                 case 'K':
470                         switch( args[0] )
471                         {
472                         case 0: // To EOL
473                                 Display_ClearLine(Term, 1);
474                                 break;
475                         case 1: // To SOL
476                                 Display_ClearLine(Term, -1);
477                                 break;
478                         case 2:
479                                 Display_ClearLine(Term, 0);
480                                 break;
481                         default:
482                                 _SysDebug("Unknown VT100 %i K", args[0]);
483                                 break;
484                         }
485                         break;
486                 case 'S':       // Scroll text up n=1
487                         Display_ScrollDown(Term, (argc >= 1 ? args[0] : 1));
488                         break;
489                 case 'T':       // Scroll text down n=1
490                         Display_ScrollDown(Term, -(argc >= 1 ? args[0] : 1));
491                         break;
492                 case 'c':       // Send Device Attributes
493                         switch(args[0])
494                         {
495                         case 0: // Request attributes from terminal
496                                 // "VT100 with Advanced Video Option" (same as screen returns)
497                                 Display_SendInput(Term, "\x1b[?1;2c");
498                                 break;
499                         default:
500                                 _SysDebug("TODO: Request device attributes \\e[%ic", args[0]);
501                                 break;
502                         }
503                         break;
504                 case 'f':
505                         if( argc != 2 ) {
506                                 Display_SetCursor(Term, 0, 0);
507                         }
508                         else {
509                                 // Adjust 1-based cursor position to 0-based
510                                 Display_SetCursor(Term, args[0]-1, args[1]-1);
511                         }
512                         break;
513                 case 'h':
514                 case 'l':
515                         for( int i = 0; i < argc; i ++ )
516                         {
517                                 switch(args[i])
518                                 {
519                                 default:
520                                         _SysDebug("Unknown VT100 mode \e[%i%c",
521                                                 args[i], c);
522                                         break;
523                                 }
524                         }
525                         break;
526                 case 'm':
527                         if( argc == 0 )
528                         {
529                                 // Reset
530                                 Display_ResetAttributes(Term);
531                         }
532                         else
533                         {
534                                 for( int i = 0; i < argc; i ++ )
535                                 {
536                                         switch(args[i])
537                                         {
538                                         case 0:
539                                                 st->Flags = 0;
540                                                 Display_ResetAttributes(Term);
541                                                 break;
542                                         case 1:
543                                                 st->Flags |= FLAG_BOLD;
544                                                 Display_SetForeground( Term, caVT100Colours[st->CurFG + 8] );
545                                                 break;
546                                         case 2:
547                                                 _SysDebug("TODO: \\e[2m - Reverse");
548                                                 break;
549                                         case 4:
550                                                 _SysDebug("TODO: \\e[4m - Underscore");
551                                                 break;
552                                         case 7:
553                                                 _SysDebug("TODO: \\e[7m - Reverse");
554                                                 break;
555                                         case 24:        // Not underlined
556                                         case 27:        // Not inverse
557                                                 break;
558                                         case 30 ... 37:
559                                                 st->CurFG = args[i]-30;
560                                                 if(0)
561                                         case 39:
562                                                 st->CurFG = 7;
563                                                 Display_SetForeground( Term,
564                                                         caVT100Colours[ st->CurFG + (st->Flags&FLAG_BOLD?8:0) ] );
565                                                 break;
566                                         case 40 ... 47:
567                                                 st->CurBG = args[i]-40;
568                                                 if(0)
569                                         case 49:
570                                                 st->CurBG = 0;
571                                                 Display_SetBackground( Term, caVT100Colours[ st->CurBG ] );
572                                                 break;
573                                         default:
574                                                 _SysDebug("Unknown mode set \\e[%im", args[i]);
575                                                 break;
576                                         } 
577                                 }
578                         }
579                         break;
580                 // Device Status Report
581                 case 'n':
582                         break;
583                 // Set scrolling region
584                 case 'r':
585                         Display_SetScrollArea(Term, args[0], (args[1] - args[0]));
586                         break;
587                 
588                 case 's':
589                         Display_SaveCursor(Term);
590                         break;
591                 case 'u':
592                         Display_RestoreCursor(Term);
593                         break;
594                 default:
595                         _SysDebug("Unknown VT100 long escape char 0x%x '%c'", c, c);
596                         break;
597                 }
598         }
599         return j;
600 }
601
602 int Term_HandleVT100_OSC(tTerminal *Term, int Len, const char *Buf)
603 {
604         tVT100State     *st = Display_GetTermState(Term);
605
606          int    ofs = 0;
607         // OSC Ps ; Pt [ST/BEL]
608         if(Len < 2)     return 0;       // Need moar
609
610          int    Ps = 0;
611         while( ofs < Len && isdigit(Buf[ofs]) ) {
612                 Ps = Ps * 10 + (Buf[ofs] - '0');
613                 ofs ++;
614         }
615
616         if( ofs == Len )        return 0;
617         if( Buf[ofs] != ';' ) {
618                 // Error
619                 st->Mode = MODE_STRING;
620                 st->StringType = STRING_IGNORE;
621                 return ofs;
622         }
623         ofs ++;
624
625         switch(Ps)
626         {
627         case 0: // Icon Name + Window Title
628         case 1: // Icon Name
629         case 2: // Window Title
630                 st->Mode = MODE_STRING;
631                 st->StringType = STRING_TITLE;
632                 break;
633         case 3: // Set X Property
634                 _SysDebug("TODO: \\e]3; Support X properties");
635                 st->Mode = MODE_STRING;
636                 st->StringType = STRING_IGNORE;
637                 break;
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;
643                 break;  
644         case 10:        // Change foreground to Pt
645         case 11:        // Change background to Pt
646         case 52:
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
650         default:
651                 _SysDebug("Unknown VT100 OSC \\e]%i;", Ps);
652                 st->Mode = MODE_STRING;
653                 st->StringType = STRING_IGNORE;
654                 break;
655         }
656         return ofs;
657 }
658

UCC git Repository :: git.ucc.asn.au