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