Home | History | Annotate | Download | only in src
      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 
     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 
     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 
    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 
    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 
    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