Usermode/GUI Terminal - Fix off-by-one in \e[H
[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 #endif
20
21 enum eExcapeMode {
22         MODE_NORMAL,
23         MODE_IGNORE,
24         MODE_STRING
25 };
26 enum eStringType {
27         STRING_IGNORE,
28         STRING_TITLE
29 };
30
31 #define FLAG_BOLD       0x01
32 #define FLAG_REVERSE    0x02
33
34 typedef struct {
35         uint32_t        Flags;
36         int     CurFG, CurBG;
37         
38         enum eExcapeMode        Mode;
39         
40         enum eStringType        StringType;
41         size_t  StringLen;
42         char    *StringCache;
43 } tVT100State;
44
45 const uint32_t  caVT100Colours[] = {
46         // Black,      Red,    Green,   Yellow,     Blue,  Magenta,     Cyan,      Gray
47         // Same again, but bright
48         0x000000, 0x770000, 0x007700, 0x777700, 0x000077, 0x770077, 0x007777, 0xAAAAAA,
49         0xCCCCCC, 0xFF0000, 0x00FF00, 0xFFFF00, 0x0000FF, 0xFF00FF, 0x00FFFF, 0xFFFFFF, 
50 };
51
52  int    _locate_eos(size_t Len, const char *Buf);
53  int    Term_HandleVT100_Short(tTerminal *Term, int Len, const char *Buf);
54  int    Term_HandleVT100_Long(tTerminal *Term, int Len, const char *Buf);
55  int    Term_HandleVT100_OSC(tTerminal *Term, int Len, const char *Buf);
56
57 static inline int min(int a, int b)
58 {
59         return a < b ? a : b;
60 }
61
62 int _locate_eos(size_t Len, const char *Buf)
63 {
64         for( size_t ret = 0; ret < Len; ret ++ )
65         {
66                 if( Buf[ret] == '\007' )
67                         return ret;
68                 if( Buf[ret] == '\x9c' )
69                         return ret;
70                 if( ret+1 < Len && Buf[ret] == '\x1b' && Buf[ret+1] == '\\' )
71                         return ret;
72         }
73         return -1;
74 }
75
76 int Term_HandleVT100(tTerminal *Term, int Len, const char *Buf)
77 {
78         #define MAX_VT100_ESCAPE_LEN    16
79         static char     inc_buf[MAX_VT100_ESCAPE_LEN];
80         static int      inc_len = 0;
81         tVT100State     *st = Display_GetTermState(Term);
82         
83         if( st == NULL ) {
84                 st = malloc( sizeof(*st) );
85                 memset(st, 0, sizeof(*st));
86                 Display_SetTermState(Term, st);
87         }
88
89         if( st->Mode == MODE_IGNORE ) {
90                 st->Mode = MODE_NORMAL;
91                 // Used for multi-byte EOS
92                 _SysDebug("Ignore 1 '%c'", *Buf);
93                 return 1;
94         }
95         else if( st->Mode == MODE_STRING )
96         {
97                 // We're in a string mode
98                 int pos = _locate_eos(Len, Buf);
99                 size_t bytes = (pos >= 0 ? pos : Len);
100                 char *tmp = realloc(st->StringCache, st->StringLen + bytes+1);
101                 if(!tmp)        return bytes;
102                 st->StringCache = tmp;
103                 memcpy(tmp+st->StringLen, Buf, bytes);
104                 tmp[st->StringLen+bytes] = 0;
105                 st->StringLen += bytes;
106                 
107                 _SysDebug("pos=%i", pos);
108                 _SysDebug("Buf[+%zi] = '%.*s'", bytes, bytes, Buf);
109                 // Only apply when we hit EOS at the start
110                 if( pos != 0 )
111                         return bytes;
112                 switch(st->StringType)
113                 {
114                 case STRING_TITLE:
115                         Display_SetTitle(Term, st->StringCache);
116                         break;
117                 case STRING_IGNORE:
118                         break;
119                 }
120                 free(st->StringCache);
121                 st->StringCache = 0;
122                 st->StringLen = 0;
123                 
124                 if( *Buf == '\x1b' ) {
125                         st->Mode = MODE_IGNORE;
126                         // skip the '\\'
127                 }
128                 else
129                         st->Mode = MODE_NORMAL;
130                 return 1;
131         }
132         else {
133                 // fall through
134         }
135
136         if( inc_len > 0 || *Buf == '\x1b' )
137         {
138                 // Handle VT100 (like) escape sequence
139                  int    new_bytes = min(MAX_VT100_ESCAPE_LEN - inc_len, Len);
140                  int    ret = 0;
141                  int    old_inc_len = inc_len;
142                 memcpy(inc_buf + inc_len, Buf, new_bytes);
143
144                 if( new_bytes == 0 ) {
145                         _SysDebug("Term_HandleVT100: Hit max? (Len=%i) Flushing cache", Len);
146                         inc_len = 0;
147                         return 0;
148                 }
149
150                 inc_len += new_bytes;
151                 //_SysDebug("inc_buf = %i '%.*s'", inc_len, inc_len, inc_buf);
152
153                 if( inc_len <= 1 )
154                         return 1;       // Skip 1 character (the '\x1b')
155
156                 ret = Term_HandleVT100_Short(Term, inc_len, inc_buf);
157
158                 if( ret != 0 ) {
159                         inc_len = 0;
160                         // Check that we actually used the new data (as should have happened)
161                         if( ret <= old_inc_len ) {
162                                 _SysDebug("Term_HandleVT100: ret(%i) <= old_inc_len(%i) '%.*s'\n",
163                                         ret, old_inc_len, inc_len, inc_buf);
164                                 assert(ret > old_inc_len);
165                         }
166                         //_SysDebug("%i bytes of escape code '%.*s' (return %i)",
167                         //      ret, ret, inc_buf, ret-old_inc_len);
168                         ret -= old_inc_len;     // counter cached bytes
169                 }
170                 else
171                         ret = new_bytes;
172                 return ret;
173         }
174
175         switch( *Buf )
176         {
177         // TODO: Need to handle \t and ^A-Z
178         case '\b':
179                 Display_MoveCursor(Term, 0, -1);
180                 Display_AddText(Term, 1, " ");
181                 Display_MoveCursor(Term, 0, -1);
182                 return 1;
183         case '\t':
184                 // TODO: tab (get current cursor pos, space until multiple of 8)
185                 _SysDebug("TODO: VT100 Support \\t tab");
186                 return 1;
187         case '\n':
188                 // TODO: Support disabling CR after NL
189                 Display_Newline(Term, 1);
190                 return 1;
191         case '\r':
192                 if( Len >= 2 && Buf[1] == '\n' ) {
193                         // Fast case for \r\n
194                         Display_Newline(Term, 1);
195                         return 2;
196                 }
197                 Display_MoveCursor(Term, 0, INT_MIN);
198                 return 1;
199         }
200
201          int    ret = 0;
202         while( ret < Len )
203         {
204                 switch(*Buf)
205                 {
206                 case '\x1b':
207                 case '\b':
208                 case '\t':
209                 case '\n':
210                 case '\r':
211                         // Force an exit right now
212                         Len = ret;
213                         break;
214                 default:
215                         ret ++;
216                         Buf ++;
217                         break;
218                 }
219         }
220         return -ret;
221 }
222
223 int Term_HandleVT100_Short(tTerminal *Term, int Len, const char *Buf)
224 {
225          int    tmp;
226         switch(Buf[1])
227         {
228         case '[':       // Multibyte, funtime starts    
229                 tmp = Term_HandleVT100_Long(Term, Len-2, Buf+2);
230                 assert(tmp >= 0);
231                 if( tmp == 0 )
232                         return 0;
233                 return tmp + 2;
234         case ']':
235                 tmp = Term_HandleVT100_OSC(Term, Len-2, Buf+2);
236                 assert(tmp >= 0);
237                 if( tmp == 0 )
238                         return 0;
239                 return tmp + 2;
240         case '=':
241                 _SysDebug("TODO: \\e= Application Keypad");
242                 return 2;
243         case '>':
244                 _SysDebug("TODO: \\e= Normal Keypad");
245                 return 2;
246         
247         case '(':       // Update G0 charset
248                 tmp = 0; if(0)
249         case ')':       // Update G1 charset
250                 tmp = 1; if(0)
251         case '*':
252                 tmp = 2; if(0)
253         case '+':
254                 tmp = 3;
255                 
256                 if( Len <= 2 )
257                         return 0;       // We need more
258                 switch(Buf[2])
259                 {
260                 case '0':       // DEC Special Character/Linedrawing set
261                 case 'A':       // UK
262                 case 'B':       // US ASCII
263                         break;
264                 }
265                 return 3;
266         // xterm C1 \eD and \eM are 'Index' and 'Reverse Index'
267         // - Aparently scroll?
268         //case 'D':
269         //      Display_ScrollDown(Term, 1);
270         //      ret = 2;
271         //      break;
272         //case 'M':
273         //      Display_ScrollDown(Term, -1);
274         //      ret = 2;
275         //      break;
276         default:
277                 _SysDebug("Unknown VT100 \\e%c", Buf[1]);
278                 return 2;
279         }
280 }
281
282 int Term_HandleVT100_Long(tTerminal *Term, int Len, const char *Buffer)
283 {
284         tVT100State     *st = Display_GetTermState(Term);
285         char    c;
286          int    argc = 0, j = 0;
287          int    args[6] = {0,0,0,0,0,0};
288          int    bQuestionMark = 0;
289         
290         // Get Arguments
291         if(j == Len)    return 0;
292         c = Buffer[j++];
293         if(c == '?') {
294                 bQuestionMark = 1;
295                 if(j == Len)    return 0;
296                 c = Buffer[j++];
297         }
298         if( '0' <= c && c <= '9' )
299         {
300                 do {
301                         if(c == ';') {
302                                 if(j == Len)    return 0;
303                                 c = Buffer[j++];
304                         }
305                         while('0' <= c && c <= '9') {
306                                 args[argc] *= 10;
307                                 args[argc] += c-'0';
308                                 if(j == Len)    return 0;
309                                 c = Buffer[j++];
310                         }
311                         argc ++;
312                 } while(c == ';');
313         }
314         
315         // Get Command
316         if( !isalpha(c) ) {
317                 // Bother.
318                 _SysDebug("Unexpected char 0x%x in VT100 escape code '\\e[%.*s'", c,
319                         Len, Buffer);
320                 return j;
321         }
322
323         if( bQuestionMark )
324         {
325                  int    set = 0;
326                 // Special commands
327                 switch( c )
328                 {
329                 case 'h':       // set
330                         set = 1;
331                 case 'l':       // unset
332                         switch(args[0])
333                         {
334                         case  1:        // Aplication cursor keys
335                                 _SysDebug("TODO: \\e[?1%c Application cursor keys", c);
336                                 break;
337                         case 12:
338                                 _SysDebug("TODO: \\e[?25%c Start/Stop blinking cursor", c);
339                                 break;
340                         case 25:        // Hide cursor
341                                 _SysDebug("TODO: \\e[?25%c Show/Hide cursor", c);
342                                 break;
343                         case 1047:      // Alternate buffer
344                                 Display_ShowAltBuffer(Term, set);
345                                 break;
346                         case 1048:      // Save/restore cursor in DECSC
347                                 _SysDebug("TODO: \\e[?1048%c Save/Restore cursor", c);
348                                 break;
349                         case 1049:      // Save/restore cursor in DECSC and use alternate buffer
350                                 _SysDebug("TODO: \\e[?1049%c Save/Restore cursor", c);
351                                 Display_ShowAltBuffer(Term, set);
352                                 break;
353                         default:
354                                 _SysDebug("TODO: \\e[?%i%c Unknow DEC private mode", args[0], c);
355                                 break;
356                         }
357                         break;
358                 default:
359                         _SysDebug("Unknown VT100 extended escape char 0x%x", c);
360                         break;
361                 }
362         }
363         else
364         {
365                 // Standard commands
366                 switch( c )
367                 {
368                 case 'A':
369                         Display_MoveCursor(Term, -(argc >= 1 ? args[0] : 1), 0);
370                         break;
371                 case 'B':
372                         Display_MoveCursor(Term, (argc >= 1 ? args[0] : 1), 0);
373                         break;
374                 case 'C':
375                         Display_MoveCursor(Term, 0, (argc >= 1 ? args[0] : 1));
376                         break;
377                 case 'D':
378                         Display_MoveCursor(Term, 0, -(argc >= 1 ? args[0] : 1));
379                         break;
380                 case 'H':
381                         if( argc != 2 ) {
382                         }
383                         else {
384                                 // Adjust 1-based cursor position to 0-based
385                                 Display_SetCursor(Term, args[0]-1, args[1]-1);
386                         }
387                         break;
388                 case 'J':       // Clear lines
389                         switch( args[0] )
390                         {
391                         case 0:
392                                 Display_ClearLines(Term, 1);    // Down
393                                 break;
394                         case 1:
395                                 Display_ClearLines(Term, -1);   // Up
396                                 break;
397                         case 2:
398                                 Display_ClearLines(Term, 0);    // All
399                                 break;
400                         default:
401                                 _SysDebug("Unknown VT100 %i J", args[0]);
402                                 break;
403                         }
404                         break;
405                 case 'K':
406                         switch( args[0] )
407                         {
408                         case 0: // To EOL
409                                 Display_ClearLine(Term, 1);
410                                 break;
411                         case 1: // To SOL
412                                 Display_ClearLine(Term, -1);
413                                 break;
414                         case 2:
415                                 Display_ClearLine(Term, 0);
416                                 break;
417                         default:
418                                 _SysDebug("Unknown VT100 %i K", args[0]);
419                                 break;
420                         }
421                 case 'S':       // Scroll up n=1
422                         Display_ScrollDown(Term, -(argc >= 1 ? args[0] : 1));
423                         break;
424                 case 'T':       // Scroll down n=1
425                         Display_ScrollDown(Term, (argc >= 1 ? args[0] : 1));
426                         break;
427                 case 'm':
428                         if( argc == 0 )
429                         {
430                                 // Reset
431                                 Display_ResetAttributes(Term);
432                         }
433                         else
434                         {
435                                 for( int i = 0; i < argc; i ++ )
436                                 {
437                                         switch(args[i])
438                                         {
439                                         case 0:
440                                                 st->Flags = 0;
441                                                 Display_ResetAttributes(Term);
442                                                 break;
443                                         case 1:
444                                                 st->Flags |= FLAG_BOLD;
445                                                 Display_SetForeground( Term, caVT100Colours[st->CurFG + 8] );
446                                                 break;
447                                         case 2:
448                                                 _SysDebug("TODO: VT100 \\e[1m - Reverse");
449                                                 break;
450                                         case 30 ... 37:
451                                                 st->CurFG = args[i]-30;
452                                                 if(0)
453                                         case 39:
454                                                 st->CurFG = 7;
455                                                 Display_SetForeground( Term,
456                                                         caVT100Colours[ st->CurFG + (st->Flags&FLAG_BOLD?8:0) ] );
457                                                 break;
458                                         case 40 ... 47:
459                                                 st->CurBG = args[i]-40;
460                                                 if(0)
461                                         case 49:
462                                                 st->CurBG = 0;
463                                                 Display_SetBackground( Term, caVT100Colours[ st->CurBG ] );
464                                                 break;
465                                         default:
466                                                 _SysDebug("TODO: VT100 \\e[%im", args[i]);
467                                                 break;
468                                         } 
469                                 }
470                         }
471                         break;
472                 // Set scrolling region
473                 case 'r':
474                         Display_SetScrollArea(Term, args[0], (args[1] - args[0]));
475                         break;
476                 
477                 case 's':
478                         Display_SaveCursor(Term);
479                         break;
480                 case 'u':
481                         Display_RestoreCursor(Term);
482                         break;
483                 default:
484                         _SysDebug("Unknown VT100 long escape char 0x%x '%c'", c, c);
485                         break;
486                 }
487         }
488         return j;
489 }
490
491 int Term_HandleVT100_OSC(tTerminal *Term, int Len, const char *Buf)
492 {
493         tVT100State     *st = Display_GetTermState(Term);
494
495          int    ofs = 0;
496         // OSC Ps ; Pt [ST/BEL]
497         if(Len < 2)     return 0;       // Need moar
498
499          int    Ps = 0;
500         while( ofs < Len && isdigit(Buf[ofs]) ) {
501                 Ps = Ps * 10 + (Buf[ofs] - '0');
502                 ofs ++;
503         }
504
505         if( ofs == Len )        return 0;
506         if( Buf[ofs] != ';' ) {
507                 // Error
508                 st->Mode = MODE_STRING;
509                 st->StringType = STRING_IGNORE;
510                 return ofs;
511         }
512         ofs ++;
513
514         switch(Ps)
515         {
516         case 0: // Icon Name + Window Title
517         case 1: // Icon Name
518         case 2: // Window Title
519                 st->Mode = MODE_STRING;
520                 st->StringType = STRING_TITLE;
521                 break;
522         case 3: // Set X Property
523                 _SysDebug("TODO: \\e]3; Support X properties");
524                 st->Mode = MODE_STRING;
525                 st->StringType = STRING_IGNORE;
526                 break;
527         case 4: // Change colour number
528         case 5: // Change special colour number
529                 _SysDebug("TODO: \\e]%i;c; Support X properties", Ps);
530                 st->Mode = MODE_STRING;
531                 st->StringType = STRING_IGNORE;
532                 break;  
533         case 10:        // Change foreground to Pt
534         case 11:        // Change background to Pt
535         case 52:
536                 // TODO: Can be Pt = [cps01234567]*;<str>
537                 // > Clipboard data in base-64, cut/primary/select buffers 0-7
538                 // > <str>='?' returns the clipbard in the same format
539         default:
540                 _SysDebug("Unknown VT100 OSC \\e]%i;", Ps);
541                 st->Mode = MODE_STRING;
542                 st->StringType = STRING_IGNORE;
543                 break;
544         }
545         return ofs;
546 }
547

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