1 #include "vterm_internal.h"
2 
3 #include <stdio.h>
4 #include <string.h>
5 
6 #undef DEBUG_PARSER
7 
is_intermed(unsigned char c)8 static int is_intermed(unsigned char c)
9 {
10   return c >= 0x20 && c <= 0x2f;
11 }
12 
do_control(VTerm * vt,unsigned char control)13 static void do_control(VTerm *vt, unsigned char control)
14 {
15   if(vt->parser.callbacks && vt->parser.callbacks->control)
16     if((*vt->parser.callbacks->control)(control, vt->parser.cbdata))
17       return;
18 
19   DEBUG_LOG1("libvterm: Unhandled control 0x%02x\n", control);
20 }
21 
do_csi(VTerm * vt,char command)22 static void do_csi(VTerm *vt, char command)
23 {
24 #ifdef DEBUG_PARSER
25   printf("Parsed CSI args as:\n", arglen, args);
26   printf(" leader: %s\n", vt->parser.v.csi.leader);
27   for(int argi = 0; argi < vt->parser.v.csi.argi; argi++) {
28     printf(" %lu", CSI_ARG(vt->parser.v.csi.args[argi]));
29     if(!CSI_ARG_HAS_MORE(vt->parser.v.csi.args[argi]))
30       printf("\n");
31   printf(" intermed: %s\n", vt->parser.intermed);
32   }
33 #endif
34 
35   if(vt->parser.callbacks && vt->parser.callbacks->csi)
36     if((*vt->parser.callbacks->csi)(
37           vt->parser.v.csi.leaderlen ? vt->parser.v.csi.leader : NULL,
38           vt->parser.v.csi.args,
39           vt->parser.v.csi.argi,
40           vt->parser.intermedlen ? vt->parser.intermed : NULL,
41           command,
42           vt->parser.cbdata))
43       return;
44 
45   DEBUG_LOG1("libvterm: Unhandled CSI %c\n", command);
46 }
47 
do_escape(VTerm * vt,char command)48 static void do_escape(VTerm *vt, char command)
49 {
50   char seq[INTERMED_MAX+1];
51 
52   size_t len = vt->parser.intermedlen;
53   strncpy(seq, vt->parser.intermed, len);
54   seq[len++] = command;
55   seq[len]   = 0;
56 
57   if(vt->parser.callbacks && vt->parser.callbacks->escape)
58     if((*vt->parser.callbacks->escape)(seq, len, vt->parser.cbdata))
59       return;
60 
61   DEBUG_LOG1("libvterm: Unhandled escape ESC 0x%02x\n", command);
62 }
63 
string_fragment(VTerm * vt,const char * str,size_t len,int final)64 static void string_fragment(VTerm *vt, const char *str, size_t len, int final)
65 {
66   VTermStringFragment frag;
67 
68   frag.str = str;
69   frag.len = len;
70   frag.initial = vt->parser.string_initial;
71   frag.final = final;
72 
73   switch(vt->parser.state) {
74     case OSC:
75       if(vt->parser.callbacks && vt->parser.callbacks->osc)
76         (*vt->parser.callbacks->osc)(vt->parser.v.osc.command, frag, vt->parser.cbdata);
77       break;
78 
79     case DCS:
80       if(vt->parser.callbacks && vt->parser.callbacks->dcs)
81         (*vt->parser.callbacks->dcs)(vt->parser.v.dcs.command, vt->parser.v.dcs.commandlen, frag, vt->parser.cbdata);
82       break;
83 
84     case APC:
85       if(vt->parser.callbacks && vt->parser.callbacks->apc)
86         (*vt->parser.callbacks->apc)(frag, vt->parser.cbdata);
87       break;
88 
89     case PM:
90       if(vt->parser.callbacks && vt->parser.callbacks->pm)
91         (*vt->parser.callbacks->pm)(frag, vt->parser.cbdata);
92       break;
93 
94     case SOS:
95       if(vt->parser.callbacks && vt->parser.callbacks->sos)
96         (*vt->parser.callbacks->sos)(frag, vt->parser.cbdata);
97       break;
98 
99     case NORMAL:
100     case CSI_LEADER:
101     case CSI_ARGS:
102     case CSI_INTERMED:
103     case OSC_COMMAND:
104     case DCS_COMMAND:
105       break;
106   }
107 
108   vt->parser.string_initial = FALSE;
109 }
110 
vterm_input_write(VTerm * vt,const char * bytes,size_t len)111 size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len)
112 {
113   size_t pos = 0;
114   const char *string_start = NULL;  // init to avoid gcc warning
115 
116   vt->in_backspace = 0;		    // Count down with BS key and activate when
117 				    // it reaches 1
118 
119   switch(vt->parser.state) {
120   case NORMAL:
121   case CSI_LEADER:
122   case CSI_ARGS:
123   case CSI_INTERMED:
124   case OSC_COMMAND:
125   case DCS_COMMAND:
126     string_start = NULL;
127     break;
128   case OSC:
129   case DCS:
130   case APC:
131   case PM:
132   case SOS:
133     string_start = bytes;
134     break;
135   }
136 
137 #define ENTER_STATE(st)        do { vt->parser.state = st; string_start = NULL; } while(0)
138 #define ENTER_NORMAL_STATE()   ENTER_STATE(NORMAL)
139 
140 #define IS_STRING_STATE()      (vt->parser.state >= OSC_COMMAND)
141 
142   for( ; pos < len; pos++) {
143     unsigned char c = bytes[pos];
144     int c1_allowed = !vt->mode.utf8;
145     size_t string_len;
146 
147     if(c == 0x00 || c == 0x7f) { // NUL, DEL
148       if(IS_STRING_STATE()) {
149         string_fragment(vt, string_start, bytes + pos - string_start, FALSE);
150         string_start = bytes + pos + 1;
151       }
152       continue;
153     }
154     if(c == 0x18 || c == 0x1a) { // CAN, SUB
155       vt->parser.in_esc = FALSE;
156       ENTER_NORMAL_STATE();
157       continue;
158     }
159     else if(c == 0x1b) { // ESC
160       vt->parser.intermedlen = 0;
161       if(!IS_STRING_STATE())
162         vt->parser.state = NORMAL;
163       vt->parser.in_esc = TRUE;
164       continue;
165     }
166     else if(c == 0x07 &&  // BEL, can stand for ST in OSC or DCS state
167             IS_STRING_STATE()) {
168       // fallthrough
169     }
170     else if(c < 0x20) { // other C0
171       if(vt->parser.state == SOS)
172         continue; // All other C0s permitted in SOS
173 
174       if(vterm_get_special_pty_type() == 2) {
175         if(c == 0x08) // BS
176           // Set the trick for BS output after a sequence, to delay backspace
177           // activation
178           if(pos + 2 < len && bytes[pos + 1] == 0x20 && bytes[pos + 2] == 0x08)
179             vt->in_backspace = 2; // Trigger when count down to 1
180       }
181       if(IS_STRING_STATE())
182         string_fragment(vt, string_start, bytes + pos - string_start, FALSE);
183       do_control(vt, c);
184       if(IS_STRING_STATE())
185         string_start = bytes + pos + 1;
186       continue;
187     }
188     // else fallthrough
189 
190     string_len = bytes + pos - string_start;
191 
192     if(vt->parser.in_esc) {
193       // Hoist an ESC letter into a C1 if we're not in a string mode
194       // Always accept ESC \ == ST even in string mode
195       if(!vt->parser.intermedlen &&
196           c >= 0x40 && c < 0x60 &&
197           ((!IS_STRING_STATE() || c == 0x5c))) {
198         c += 0x40;
199         c1_allowed = TRUE;
200         if(string_len)
201           string_len -= 1;
202         vt->parser.in_esc = FALSE;
203       }
204       else {
205         string_start = NULL;
206         vt->parser.state = NORMAL;
207       }
208     }
209 
210     switch(vt->parser.state) {
211     case CSI_LEADER:
212       /* Extract leader bytes 0x3c to 0x3f */
213       if(c >= 0x3c && c <= 0x3f) {
214         if(vt->parser.v.csi.leaderlen < CSI_LEADER_MAX-1)
215           vt->parser.v.csi.leader[vt->parser.v.csi.leaderlen++] = c;
216         break;
217       }
218 
219       /* else fallthrough */
220       vt->parser.v.csi.leader[vt->parser.v.csi.leaderlen] = 0;
221 
222       vt->parser.v.csi.argi = 0;
223       vt->parser.v.csi.args[0] = CSI_ARG_MISSING;
224       vt->parser.state = CSI_ARGS;
225 
226       /* fallthrough */
227     case CSI_ARGS:
228       /* Numerical value of argument */
229       if(c >= '0' && c <= '9') {
230         if(vt->parser.v.csi.args[vt->parser.v.csi.argi] == CSI_ARG_MISSING)
231           vt->parser.v.csi.args[vt->parser.v.csi.argi] = 0;
232         vt->parser.v.csi.args[vt->parser.v.csi.argi] *= 10;
233         vt->parser.v.csi.args[vt->parser.v.csi.argi] += c - '0';
234         break;
235       }
236       if(c == ':') {
237         vt->parser.v.csi.args[vt->parser.v.csi.argi] |= CSI_ARG_FLAG_MORE;
238         c = ';';
239       }
240       if(c == ';') {
241         vt->parser.v.csi.argi++;
242         vt->parser.v.csi.args[vt->parser.v.csi.argi] = CSI_ARG_MISSING;
243         break;
244       }
245 
246       /* else fallthrough */
247       vt->parser.v.csi.argi++;
248       vt->parser.intermedlen = 0;
249       vt->parser.state = CSI_INTERMED;
250       // fallthrough
251     case CSI_INTERMED:
252       if(is_intermed(c)) {
253         if(vt->parser.intermedlen < INTERMED_MAX-1)
254           vt->parser.intermed[vt->parser.intermedlen++] = c;
255         break;
256       }
257       else if(c == 0x1b) {
258         /* ESC in CSI cancels */
259       }
260       else if(c >= 0x40 && c <= 0x7e) {
261         vt->parser.intermed[vt->parser.intermedlen] = 0;
262         do_csi(vt, c);
263       }
264       /* else was invalid CSI */
265 
266       ENTER_NORMAL_STATE();
267       break;
268 
269     case OSC_COMMAND:
270       /* Numerical value of command */
271       if(c >= '0' && c <= '9') {
272         if(vt->parser.v.osc.command == -1)
273           vt->parser.v.osc.command = 0;
274         else
275           vt->parser.v.osc.command *= 10;
276         vt->parser.v.osc.command += c - '0';
277         break;
278       }
279       if(c == ';') {
280         vt->parser.state = OSC;
281         string_start = bytes + pos + 1;
282         break;
283       }
284 
285       /* else fallthrough */
286       string_start = bytes + pos;
287       string_len   = 0;
288       vt->parser.state = OSC;
289       goto string_state;
290 
291     case DCS_COMMAND:
292       if(vt->parser.v.dcs.commandlen < CSI_LEADER_MAX)
293         vt->parser.v.dcs.command[vt->parser.v.dcs.commandlen++] = c;
294 
295       if(c >= 0x40 && c<= 0x7e) {
296         string_start = bytes + pos + 1;
297         vt->parser.state = DCS;
298       }
299       break;
300 
301 string_state:
302     case OSC:
303     case DCS:
304     case APC:
305     case PM:
306     case SOS:
307       if(c == 0x07 || (c1_allowed && c == 0x9c)) {
308         string_fragment(vt, string_start, string_len, TRUE);
309         ENTER_NORMAL_STATE();
310       }
311       break;
312 
313     case NORMAL:
314       if(vt->parser.in_esc) {
315         if(is_intermed(c)) {
316           if(vt->parser.intermedlen < INTERMED_MAX-1)
317             vt->parser.intermed[vt->parser.intermedlen++] = c;
318         }
319         else if(c >= 0x30 && c < 0x7f) {
320           do_escape(vt, c);
321           vt->parser.in_esc = 0;
322           ENTER_NORMAL_STATE();
323         }
324         else {
325           DEBUG_LOG1("TODO: Unhandled byte %02x in Escape\n", c);
326         }
327         break;
328       }
329       if(c1_allowed && c >= 0x80 && c < 0xa0) {
330         switch(c) {
331         case 0x90: // DCS
332           vt->parser.string_initial = TRUE;
333           vt->parser.v.dcs.commandlen = 0;
334           ENTER_STATE(DCS_COMMAND);
335           break;
336         case 0x98: // SOS
337           vt->parser.string_initial = TRUE;
338           ENTER_STATE(SOS);
339           string_start = bytes + pos + 1;
340           string_len = 0;
341           break;
342         case 0x9b: // CSI
343           vt->parser.v.csi.leaderlen = 0;
344           ENTER_STATE(CSI_LEADER);
345           break;
346         case 0x9d: // OSC
347           vt->parser.v.osc.command = -1;
348           vt->parser.string_initial = TRUE;
349           string_start = bytes + pos + 1;
350           ENTER_STATE(OSC_COMMAND);
351           break;
352         case 0x9e: // PM
353           vt->parser.string_initial = TRUE;
354           ENTER_STATE(PM);
355           string_start = bytes + pos + 1;
356           string_len = 0;
357           break;
358         case 0x9f: // APC
359           vt->parser.string_initial = TRUE;
360           ENTER_STATE(APC);
361           string_start = bytes + pos + 1;
362           string_len = 0;
363           break;
364         default:
365           do_control(vt, c);
366           break;
367         }
368       }
369       else {
370         size_t eaten = 0;
371         if(vt->parser.callbacks && vt->parser.callbacks->text)
372           eaten = (*vt->parser.callbacks->text)(bytes + pos, len - pos, vt->parser.cbdata);
373 
374         if(!eaten) {
375           DEBUG_LOG("libvterm: Text callback did not consume any input\n");
376           /* force it to make progress */
377           eaten = 1;
378         }
379 
380         pos += (eaten - 1); // we'll ++ it again in a moment
381       }
382       break;
383     }
384   }
385 
386   if(string_start) {
387     size_t string_len = bytes + pos - string_start;
388     if(vt->parser.in_esc)
389       string_len -= 1;
390     string_fragment(vt, string_start, string_len, FALSE);
391   }
392 
393   return len;
394 }
395 
vterm_parser_set_callbacks(VTerm * vt,const VTermParserCallbacks * callbacks,void * user)396 void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user)
397 {
398   vt->parser.callbacks = callbacks;
399   vt->parser.cbdata = user;
400 }
401 
vterm_parser_get_cbdata(VTerm * vt)402 void *vterm_parser_get_cbdata(VTerm *vt)
403 {
404   return vt->parser.cbdata;
405 }
406