1 #include "vterm_internal.h"
2 
3 #include <stdio.h>
4 #include <string.h>
5 
6 #define CSI_ARGS_MAX 16
7 #define CSI_LEADER_MAX 16
8 #define CSI_INTERMED_MAX 16
9 
do_control(VTerm * vt,unsigned char control)10 static void do_control(VTerm *vt, unsigned char control)
11 {
12   if(vt->parser_callbacks && vt->parser_callbacks->control)
13     if((*vt->parser_callbacks->control)(control, vt->cbdata))
14       return;
15 
16   fprintf(stderr, "libvterm: Unhandled control 0x%02x\n", control);
17 }
18 
do_string_csi(VTerm * vt,const char * args,size_t arglen,char command)19 static void do_string_csi(VTerm *vt, const char *args, size_t arglen, char command)
20 {
21   int i = 0;
22 
23   int leaderlen = 0;
24   char leader[CSI_LEADER_MAX];
25 
26   // Extract leader bytes 0x3c to 0x3f
27   for( ; i < arglen; i++) {
28     if(args[i] < 0x3c || args[i] > 0x3f)
29       break;
30     if(leaderlen < CSI_LEADER_MAX-1)
31       leader[leaderlen++] = args[i];
32   }
33 
34   leader[leaderlen] = 0;
35 
36   int argcount = 1; // Always at least 1 arg
37 
38   for( ; i < arglen; i++)
39     if(args[i] == 0x3b || args[i] == 0x3a) // ; or :
40       argcount++;
41 
42   /* TODO: Consider if these buffers should live in the VTerm struct itself */
43   long csi_args[CSI_ARGS_MAX];
44   if(argcount > CSI_ARGS_MAX)
45     argcount = CSI_ARGS_MAX;
46 
47   int argi;
48   for(argi = 0; argi < argcount; argi++)
49     csi_args[argi] = CSI_ARG_MISSING;
50 
51   argi = 0;
52   for(i = leaderlen; i < arglen && argi < argcount; i++) {
53     switch(args[i]) {
54     case 0x30: case 0x31: case 0x32: case 0x33: case 0x34:
55     case 0x35: case 0x36: case 0x37: case 0x38: case 0x39:
56       if(csi_args[argi] == CSI_ARG_MISSING)
57         csi_args[argi] = 0;
58       csi_args[argi] *= 10;
59       csi_args[argi] += args[i] - '0';
60       break;
61     case 0x3a:
62       csi_args[argi] |= CSI_ARG_FLAG_MORE;
63       /* FALLTHROUGH */
64     case 0x3b:
65       argi++;
66       break;
67     default:
68       goto done_leader;
69     }
70   }
71 done_leader: ;
72 
73   int intermedlen = 0;
74   char intermed[CSI_INTERMED_MAX];
75 
76   for( ; i < arglen; i++) {
77     if((args[i] & 0xf0) != 0x20)
78       break;
79 
80     if(intermedlen < CSI_INTERMED_MAX-1)
81       intermed[intermedlen++] = args[i];
82   }
83 
84   intermed[intermedlen] = 0;
85 
86   if(i < arglen) {
87     fprintf(stderr, "libvterm: TODO unhandled CSI bytes \"%.*s\"\n", (int)(arglen - i), args + i);
88   }
89 
90   //printf("Parsed CSI args %.*s as:\n", arglen, args);
91   //printf(" leader: %s\n", leader);
92   //for(argi = 0; argi < argcount; argi++) {
93   //  printf(" %lu", CSI_ARG(csi_args[argi]));
94   //  if(!CSI_ARG_HAS_MORE(csi_args[argi]))
95   //    printf("\n");
96   //printf(" intermed: %s\n", intermed);
97   //}
98 
99   if(vt->parser_callbacks && vt->parser_callbacks->csi)
100     if((*vt->parser_callbacks->csi)(leaderlen ? leader : NULL, csi_args, argcount, intermedlen ? intermed : NULL, command, vt->cbdata))
101       return;
102 
103   fprintf(stderr, "libvterm: Unhandled CSI %.*s %c\n", (int)arglen, args, command);
104 }
105 
append_strbuffer(VTerm * vt,const char * str,size_t len)106 static void append_strbuffer(VTerm *vt, const char *str, size_t len)
107 {
108   if(len > vt->strbuffer_len - vt->strbuffer_cur) {
109     len = vt->strbuffer_len - vt->strbuffer_cur;
110     fprintf(stderr, "Truncating strbuffer preserve to %zd bytes\n", len);
111   }
112 
113   if(len > 0) {
114     strncpy(vt->strbuffer + vt->strbuffer_cur, str, len);
115     vt->strbuffer_cur += len;
116   }
117 }
118 
do_string(VTerm * vt,const char * str_frag,size_t len)119 static size_t do_string(VTerm *vt, const char *str_frag, size_t len)
120 {
121   if(vt->strbuffer_cur) {
122     if(str_frag)
123       append_strbuffer(vt, str_frag, len);
124 
125     str_frag = vt->strbuffer;
126     len = vt->strbuffer_cur;
127   }
128   else if(!str_frag) {
129     fprintf(stderr, "parser.c: TODO: No strbuffer _and_ no final fragment???\n");
130     len = 0;
131   }
132 
133   vt->strbuffer_cur = 0;
134 
135   size_t eaten;
136 
137   switch(vt->parser_state) {
138   case NORMAL:
139     if(vt->parser_callbacks && vt->parser_callbacks->text)
140       if((eaten = (*vt->parser_callbacks->text)(str_frag, len, vt->cbdata)))
141         return eaten;
142 
143     fprintf(stderr, "libvterm: Unhandled text (%zu chars)\n", len);
144     return 0;
145 
146   case ESC:
147     if(len == 1 && str_frag[0] >= 0x40 && str_frag[0] < 0x60) {
148       // C1 emulations using 7bit clean
149       // ESC 0x40 == 0x80
150       do_control(vt, str_frag[0] + 0x40);
151       return 0;
152     }
153 
154     if(vt->parser_callbacks && vt->parser_callbacks->escape)
155       if((*vt->parser_callbacks->escape)(str_frag, len, vt->cbdata))
156         return 0;
157 
158     fprintf(stderr, "libvterm: Unhandled escape ESC 0x%02x\n", str_frag[len-1]);
159     return 0;
160 
161   case CSI:
162     do_string_csi(vt, str_frag, len - 1, str_frag[len - 1]);
163     return 0;
164 
165   case OSC:
166     if(vt->parser_callbacks && vt->parser_callbacks->osc)
167       if((*vt->parser_callbacks->osc)(str_frag, len, vt->cbdata))
168         return 0;
169 
170     fprintf(stderr, "libvterm: Unhandled OSC %.*s\n", (int)len, str_frag);
171     return 0;
172 
173   case DCS:
174     if(vt->parser_callbacks && vt->parser_callbacks->dcs)
175       if((*vt->parser_callbacks->dcs)(str_frag, len, vt->cbdata))
176         return 0;
177 
178     fprintf(stderr, "libvterm: Unhandled DCS %.*s\n", (int)len, str_frag);
179     return 0;
180 
181   case ESC_IN_OSC:
182   case ESC_IN_DCS:
183     fprintf(stderr, "libvterm: ARGH! Should never do_string() in ESC_IN_{OSC,DCS}\n");
184     return 0;
185   }
186 
187   return 0;
188 }
189 
vterm_push_bytes(VTerm * vt,const char * bytes,size_t len)190 void vterm_push_bytes(VTerm *vt, const char *bytes, size_t len)
191 {
192   size_t pos = 0;
193   const char *string_start;
194 
195   switch(vt->parser_state) {
196   case NORMAL:
197     string_start = NULL;
198     break;
199   case ESC:
200   case ESC_IN_OSC:
201   case ESC_IN_DCS:
202   case CSI:
203   case OSC:
204   case DCS:
205     string_start = bytes;
206     break;
207   }
208 
209 #define ENTER_STRING_STATE(st) do { vt->parser_state = st; string_start = bytes + pos + 1; } while(0)
210 #define ENTER_NORMAL_STATE()   do { vt->parser_state = NORMAL; string_start = NULL; } while(0)
211 
212   for( ; pos < len; pos++) {
213     unsigned char c = bytes[pos];
214 
215     if(c == 0x00 || c == 0x7f) { // NUL, DEL
216       if(vt->parser_state != NORMAL) {
217         append_strbuffer(vt, string_start, bytes + pos - string_start);
218         string_start = bytes + pos + 1;
219       }
220       continue;
221     }
222     if(c == 0x18 || c == 0x1a) { // CAN, SUB
223       ENTER_NORMAL_STATE();
224       continue;
225     }
226     else if(c == 0x1b) { // ESC
227       if(vt->parser_state == OSC)
228         vt->parser_state = ESC_IN_OSC;
229       else if(vt->parser_state == DCS)
230         vt->parser_state = ESC_IN_DCS;
231       else
232         ENTER_STRING_STATE(ESC);
233       continue;
234     }
235     else if(c == 0x07 &&  // BEL, can stand for ST in OSC or DCS state
236             (vt->parser_state == OSC || vt->parser_state == DCS)) {
237       // fallthrough
238     }
239     else if(c < 0x20) { // other C0
240       if(vt->parser_state != NORMAL)
241         append_strbuffer(vt, string_start, bytes + pos - string_start);
242       do_control(vt, c);
243       if(vt->parser_state != NORMAL)
244         string_start = bytes + pos + 1;
245       continue;
246     }
247     // else fallthrough
248 
249     switch(vt->parser_state) {
250     case ESC_IN_OSC:
251     case ESC_IN_DCS:
252       if(c == 0x5c) { // ST
253         switch(vt->parser_state) {
254           case ESC_IN_OSC: vt->parser_state = OSC; break;
255           case ESC_IN_DCS: vt->parser_state = DCS; break;
256           default: break;
257         }
258         do_string(vt, string_start, bytes + pos - string_start - 1);
259         ENTER_NORMAL_STATE();
260         break;
261       }
262       vt->parser_state = ESC;
263       string_start = bytes + pos;
264       // else fallthrough
265 
266     case ESC:
267       switch(c) {
268       case 0x50: // DCS
269         ENTER_STRING_STATE(DCS);
270         break;
271       case 0x5b: // CSI
272         ENTER_STRING_STATE(CSI);
273         break;
274       case 0x5d: // OSC
275         ENTER_STRING_STATE(OSC);
276         break;
277       default:
278         if(c >= 0x30 && c < 0x7f) {
279           /* +1 to pos because we want to include this command byte as well */
280           do_string(vt, string_start, bytes + pos - string_start + 1);
281           ENTER_NORMAL_STATE();
282         }
283         else if(c >= 0x20 && c < 0x30) {
284           /* intermediate byte */
285         }
286         else {
287           fprintf(stderr, "TODO: Unhandled byte %02x in Escape\n", c);
288         }
289       }
290       break;
291 
292     case CSI:
293       if(c >= 0x40 && c <= 0x7f) {
294         /* +1 to pos because we want to include this command byte as well */
295         do_string(vt, string_start, bytes + pos - string_start + 1);
296         ENTER_NORMAL_STATE();
297       }
298       break;
299 
300     case OSC:
301     case DCS:
302       if(c == 0x07 || (c == 0x9c && !vt->mode.utf8)) {
303         do_string(vt, string_start, bytes + pos - string_start);
304         ENTER_NORMAL_STATE();
305       }
306       break;
307 
308     case NORMAL:
309       if(c >= 0x80 && c < 0xa0 && !vt->mode.utf8) {
310         switch(c) {
311         case 0x90: // DCS
312           ENTER_STRING_STATE(DCS);
313           break;
314         case 0x9b: // CSI
315           ENTER_STRING_STATE(CSI);
316           break;
317         case 0x9d: // OSC
318           ENTER_STRING_STATE(OSC);
319           break;
320         default:
321           do_control(vt, c);
322           break;
323         }
324       }
325       else {
326         size_t text_eaten = do_string(vt, bytes + pos, len - pos);
327 
328         if(text_eaten == 0) {
329           string_start = bytes + pos;
330           goto pause;
331         }
332 
333         pos += (text_eaten - 1); // we'll ++ it again in a moment
334       }
335       break;
336     }
337   }
338 
339 pause:
340   if(string_start && string_start < len + bytes) {
341     size_t remaining = len - (string_start - bytes);
342     append_strbuffer(vt, string_start, remaining);
343   }
344 }
345