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

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