1 #include "vterm_internal.h"
2
3 #include <stdio.h>
4 #include <string.h>
5
6 #define strneq(a,b,n) (strncmp(a,b,n)==0)
7
8 #include "utf8.h"
9
10 #if defined(DEBUG) && DEBUG > 1
11 # define DEBUG_GLYPH_COMBINE
12 #endif
13
14 #define MOUSE_WANT_CLICK 0x01
15 #define MOUSE_WANT_DRAG 0x02
16 #define MOUSE_WANT_MOVE 0x04
17
18 /* Some convenient wrappers to make callback functions easier */
19
putglyph(VTermState * state,const uint32_t chars[],int width,VTermPos pos)20 static void putglyph(VTermState *state, const uint32_t chars[], int width, VTermPos pos)
21 {
22 VTermGlyphInfo info = {
23 .chars = chars,
24 .width = width,
25 .protected_cell = state->protected_cell,
26 .dwl = state->lineinfo[pos.row].doublewidth,
27 .dhl = state->lineinfo[pos.row].doubleheight,
28 };
29
30 if(state->callbacks && state->callbacks->putglyph)
31 if((*state->callbacks->putglyph)(&info, pos, state->cbdata))
32 return;
33
34 fprintf(stderr, "libvterm: Unhandled putglyph U+%04x at (%d,%d)\n", chars[0], pos.col, pos.row);
35 }
36
updatecursor(VTermState * state,VTermPos * oldpos,int cancel_phantom)37 static void updatecursor(VTermState *state, VTermPos *oldpos, int cancel_phantom)
38 {
39 if(state->pos.col == oldpos->col && state->pos.row == oldpos->row)
40 return;
41
42 if(cancel_phantom)
43 state->at_phantom = 0;
44
45 if(state->callbacks && state->callbacks->movecursor)
46 if((*state->callbacks->movecursor)(state->pos, *oldpos, state->mode.cursor_visible, state->cbdata))
47 return;
48 }
49
erase(VTermState * state,VTermRect rect,int selective)50 static void erase(VTermState *state, VTermRect rect, int selective)
51 {
52 if(state->callbacks && state->callbacks->erase)
53 if((*state->callbacks->erase)(rect, selective, state->cbdata))
54 return;
55 }
56
vterm_state_new(VTerm * vt)57 static VTermState *vterm_state_new(VTerm *vt)
58 {
59 VTermState *state = vterm_allocator_malloc(vt, sizeof(VTermState));
60
61 state->vt = vt;
62
63 state->rows = vt->rows;
64 state->cols = vt->cols;
65
66 vterm_state_newpen(state);
67
68 state->bold_is_highbright = 0;
69
70 return state;
71 }
72
vterm_state_free(VTermState * state)73 INTERNAL void vterm_state_free(VTermState *state)
74 {
75 vterm_allocator_free(state->vt, state->tabstops);
76 vterm_allocator_free(state->vt, state->lineinfo);
77 vterm_allocator_free(state->vt, state->combine_chars);
78 vterm_allocator_free(state->vt, state);
79 }
80
scroll(VTermState * state,VTermRect rect,int downward,int rightward)81 static void scroll(VTermState *state, VTermRect rect, int downward, int rightward)
82 {
83 if(!downward && !rightward)
84 return;
85
86 // Update lineinfo if full line
87 if(rect.start_col == 0 && rect.end_col == state->cols && rightward == 0) {
88 int height = rect.end_row - rect.start_row - abs(downward);
89
90 if(downward > 0)
91 memmove(state->lineinfo + rect.start_row,
92 state->lineinfo + rect.start_row + downward,
93 height * sizeof(state->lineinfo[0]));
94 else
95 memmove(state->lineinfo + rect.start_row - downward,
96 state->lineinfo + rect.start_row,
97 height * sizeof(state->lineinfo[0]));
98 }
99
100 if(state->callbacks && state->callbacks->scrollrect)
101 if((*state->callbacks->scrollrect)(rect, downward, rightward, state->cbdata))
102 return;
103
104 if(state->callbacks)
105 vterm_scroll_rect(rect, downward, rightward,
106 state->callbacks->moverect, state->callbacks->erase, state->cbdata);
107 }
108
linefeed(VTermState * state)109 static void linefeed(VTermState *state)
110 {
111 if(state->pos.row == SCROLLREGION_BOTTOM(state) - 1) {
112 VTermRect rect = {
113 .start_row = state->scrollregion_top,
114 .end_row = SCROLLREGION_BOTTOM(state),
115 .start_col = SCROLLREGION_LEFT(state),
116 .end_col = SCROLLREGION_RIGHT(state),
117 };
118
119 scroll(state, rect, 1, 0);
120 }
121 else if(state->pos.row < state->rows-1)
122 state->pos.row++;
123 }
124
grow_combine_buffer(VTermState * state)125 static void grow_combine_buffer(VTermState *state)
126 {
127 size_t new_size = state->combine_chars_size * 2;
128 uint32_t *new_chars = vterm_allocator_malloc(state->vt, new_size * sizeof(new_chars[0]));
129
130 memcpy(new_chars, state->combine_chars, state->combine_chars_size * sizeof(new_chars[0]));
131
132 vterm_allocator_free(state->vt, state->combine_chars);
133
134 state->combine_chars = new_chars;
135 state->combine_chars_size = new_size;
136 }
137
set_col_tabstop(VTermState * state,int col)138 static void set_col_tabstop(VTermState *state, int col)
139 {
140 unsigned char mask = 1 << (col & 7);
141 state->tabstops[col >> 3] |= mask;
142 }
143
clear_col_tabstop(VTermState * state,int col)144 static void clear_col_tabstop(VTermState *state, int col)
145 {
146 unsigned char mask = 1 << (col & 7);
147 state->tabstops[col >> 3] &= ~mask;
148 }
149
is_col_tabstop(VTermState * state,int col)150 static int is_col_tabstop(VTermState *state, int col)
151 {
152 unsigned char mask = 1 << (col & 7);
153 return state->tabstops[col >> 3] & mask;
154 }
155
tab(VTermState * state,int count,int direction)156 static void tab(VTermState *state, int count, int direction)
157 {
158 while(count--)
159 while(state->pos.col >= 0 && state->pos.col < THISROWWIDTH(state)-1) {
160 state->pos.col += direction;
161
162 if(is_col_tabstop(state, state->pos.col))
163 break;
164 }
165 }
166
167 #define NO_FORCE 0
168 #define FORCE 1
169
170 #define DWL_OFF 0
171 #define DWL_ON 1
172
173 #define DHL_OFF 0
174 #define DHL_TOP 1
175 #define DHL_BOTTOM 2
176
set_lineinfo(VTermState * state,int row,int force,int dwl,int dhl)177 static void set_lineinfo(VTermState *state, int row, int force, int dwl, int dhl)
178 {
179 VTermLineInfo info = state->lineinfo[row];
180
181 if(dwl == DWL_OFF)
182 info.doublewidth = DWL_OFF;
183 else if(dwl == DWL_ON)
184 info.doublewidth = DWL_ON;
185 // else -1 to ignore
186
187 if(dhl == DHL_OFF)
188 info.doubleheight = DHL_OFF;
189 else if(dhl == DHL_TOP)
190 info.doubleheight = DHL_TOP;
191 else if(dhl == DHL_BOTTOM)
192 info.doubleheight = DHL_BOTTOM;
193
194 if((state->callbacks &&
195 state->callbacks->setlineinfo &&
196 (*state->callbacks->setlineinfo)(row, &info, state->lineinfo + row, state->cbdata))
197 || force)
198 state->lineinfo[row] = info;
199 }
200
on_text(const char bytes[],size_t len,void * user)201 static int on_text(const char bytes[], size_t len, void *user)
202 {
203 VTermState *state = user;
204
205 VTermPos oldpos = state->pos;
206
207 // We'll have at most len codepoints
208 uint32_t codepoints[len];
209 int npoints = 0;
210 size_t eaten = 0;
211 int i = 0;
212
213 VTermEncodingInstance *encoding =
214 state->gsingle_set ? &state->encoding[state->gsingle_set] :
215 !(bytes[eaten] & 0x80) ? &state->encoding[state->gl_set] :
216 state->vt->mode.utf8 ? &state->encoding_utf8 :
217 &state->encoding[state->gr_set];
218
219 (*encoding->enc->decode)(encoding->enc, encoding->data,
220 codepoints, &npoints, state->gsingle_set ? 1 : len,
221 bytes, &eaten, len);
222
223 if(state->gsingle_set && npoints)
224 state->gsingle_set = 0;
225
226 /* This is a combining char. that needs to be merged with the previous
227 * glyph output */
228 if(vterm_unicode_is_combining(codepoints[i])) {
229 /* See if the cursor has moved since */
230 if(state->pos.row == state->combine_pos.row && state->pos.col == state->combine_pos.col + state->combine_width) {
231 unsigned saved_i = 0;
232 #ifdef DEBUG_GLYPH_COMBINE
233 int printpos;
234 printf("DEBUG: COMBINING SPLIT GLYPH of chars {");
235 for(printpos = 0; state->combine_chars[printpos]; printpos++)
236 printf("U+%04x ", state->combine_chars[printpos]);
237 printf("} + {");
238 #endif
239
240 /* Find where we need to append these combining chars */
241 while(state->combine_chars[saved_i])
242 saved_i++;
243
244 /* Add extra ones */
245 while(i < npoints && vterm_unicode_is_combining(codepoints[i])) {
246 if(saved_i >= state->combine_chars_size)
247 grow_combine_buffer(state);
248 state->combine_chars[saved_i++] = codepoints[i++];
249 }
250 if(saved_i >= state->combine_chars_size)
251 grow_combine_buffer(state);
252 state->combine_chars[saved_i] = 0;
253
254 #ifdef DEBUG_GLYPH_COMBINE
255 for(; state->combine_chars[printpos]; printpos++)
256 printf("U+%04x ", state->combine_chars[printpos]);
257 printf("}\n");
258 #endif
259
260 /* Now render it */
261 putglyph(state, state->combine_chars, state->combine_width, state->combine_pos);
262 }
263 else {
264 fprintf(stderr, "libvterm: TODO: Skip over split char+combining\n");
265 }
266 }
267
268 for(; i < npoints; i++) {
269 // Try to find combining characters following this
270 int glyph_starts = i;
271 int glyph_ends;
272 int width = 0;
273 uint32_t* chars;
274
275 for(glyph_ends = i + 1; glyph_ends < npoints; glyph_ends++)
276 if(!vterm_unicode_is_combining(codepoints[glyph_ends]))
277 break;
278
279 chars = alloca(glyph_ends - glyph_starts + 1);
280
281 for( ; i < glyph_ends; i++) {
282 chars[i - glyph_starts] = codepoints[i];
283 width += vterm_unicode_width(codepoints[i]);
284 }
285
286 chars[glyph_ends - glyph_starts] = 0;
287 i--;
288
289 #ifdef DEBUG_GLYPH_COMBINE
290 {
291 int printpos;
292 printf("DEBUG: COMBINED GLYPH of %d chars {", glyph_ends - glyph_starts);
293 for(printpos = 0; printpos < glyph_ends - glyph_starts; printpos++)
294 printf("U+%04x ", chars[printpos]);
295 printf("}, onscreen width %d\n", width);
296 }
297 #endif
298
299 if(state->at_phantom || state->pos.col + width > THISROWWIDTH(state)) {
300 linefeed(state);
301 state->pos.col = 0;
302 state->at_phantom = 0;
303 }
304
305 if(state->mode.insert) {
306 /* TODO: This will be a little inefficient for large bodies of text, as
307 * it'll have to 'ICH' effectively before every glyph. We should scan
308 * ahead and ICH as many times as required
309 */
310 VTermRect rect = {
311 .start_row = state->pos.row,
312 .end_row = state->pos.row + 1,
313 .start_col = state->pos.col,
314 .end_col = THISROWWIDTH(state),
315 };
316 scroll(state, rect, 0, -1);
317 }
318
319 putglyph(state, chars, width, state->pos);
320
321 if(i == npoints - 1) {
322 /* End of the buffer. Save the chars in case we have to combine with
323 * more on the next call */
324 unsigned save_i;
325 for(save_i = 0; chars[save_i]; save_i++) {
326 if(save_i >= state->combine_chars_size)
327 grow_combine_buffer(state);
328 state->combine_chars[save_i] = chars[save_i];
329 }
330 if(save_i >= state->combine_chars_size)
331 grow_combine_buffer(state);
332 state->combine_chars[save_i] = 0;
333 state->combine_width = width;
334 state->combine_pos = state->pos;
335 }
336
337 if(state->pos.col + width >= THISROWWIDTH(state)) {
338 if(state->mode.autowrap)
339 state->at_phantom = 1;
340 }
341 else {
342 state->pos.col += width;
343 }
344 }
345
346 updatecursor(state, &oldpos, 0);
347
348 return eaten;
349 }
350
on_control(unsigned char control,void * user)351 static int on_control(unsigned char control, void *user)
352 {
353 VTermState *state = user;
354
355 VTermPos oldpos = state->pos;
356
357 switch(control) {
358 case 0x07: // BEL - ECMA-48 8.3.3
359 if(state->callbacks && state->callbacks->bell)
360 (*state->callbacks->bell)(state->cbdata);
361 break;
362
363 case 0x08: // BS - ECMA-48 8.3.5
364 if(state->pos.col > 0)
365 state->pos.col--;
366 break;
367
368 case 0x09: // HT - ECMA-48 8.3.60
369 tab(state, 1, +1);
370 break;
371
372 case 0x0a: // LF - ECMA-48 8.3.74
373 case 0x0b: // VT
374 case 0x0c: // FF
375 linefeed(state);
376 if(state->mode.newline)
377 state->pos.col = 0;
378 break;
379
380 case 0x0d: // CR - ECMA-48 8.3.15
381 state->pos.col = 0;
382 break;
383
384 case 0x0e: // LS1 - ECMA-48 8.3.76
385 state->gl_set = 1;
386 break;
387
388 case 0x0f: // LS0 - ECMA-48 8.3.75
389 state->gl_set = 0;
390 break;
391
392 case 0x84: // IND - DEPRECATED but implemented for completeness
393 linefeed(state);
394 break;
395
396 case 0x85: // NEL - ECMA-48 8.3.86
397 linefeed(state);
398 state->pos.col = 0;
399 break;
400
401 case 0x88: // HTS - ECMA-48 8.3.62
402 set_col_tabstop(state, state->pos.col);
403 break;
404
405 case 0x8d: // RI - ECMA-48 8.3.104
406 if(state->pos.row == state->scrollregion_top) {
407 VTermRect rect = {
408 .start_row = state->scrollregion_top,
409 .end_row = SCROLLREGION_BOTTOM(state),
410 .start_col = SCROLLREGION_LEFT(state),
411 .end_col = SCROLLREGION_RIGHT(state),
412 };
413
414 scroll(state, rect, -1, 0);
415 }
416 else if(state->pos.row > 0)
417 state->pos.row--;
418 break;
419
420 case 0x8e: // SS2 - ECMA-48 8.3.141
421 state->gsingle_set = 2;
422 break;
423
424 case 0x8f: // SS3 - ECMA-48 8.3.142
425 state->gsingle_set = 3;
426 break;
427
428 default:
429 return 0;
430 }
431
432 updatecursor(state, &oldpos, 1);
433
434 return 1;
435 }
436
output_mouse(VTermState * state,int code,int pressed,int modifiers,int col,int row)437 static void output_mouse(VTermState *state, int code, int pressed, int modifiers, int col, int row)
438 {
439 modifiers <<= 2;
440
441 switch(state->mouse_protocol) {
442 case MOUSE_X10:
443 if(col + 0x21 > 0xff)
444 col = 0xff - 0x21;
445 if(row + 0x21 > 0xff)
446 row = 0xff - 0x21;
447
448 if(!pressed)
449 code = 3;
450
451 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%c%c%c",
452 (code | modifiers) + 0x20, col + 0x21, row + 0x21);
453 break;
454
455 case MOUSE_UTF8:
456 {
457 char utf8[18]; size_t len = 0;
458
459 if(!pressed)
460 code = 3;
461
462 len += fill_utf8((code | modifiers) + 0x20, utf8 + len);
463 len += fill_utf8(col + 0x21, utf8 + len);
464 len += fill_utf8(row + 0x21, utf8 + len);
465 utf8[len] = 0;
466
467 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%s", utf8);
468 }
469 break;
470
471 case MOUSE_SGR:
472 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "<%d;%d;%d%c",
473 code | modifiers, col + 1, row + 1, pressed ? 'M' : 'm');
474 break;
475
476 case MOUSE_RXVT:
477 if(!pressed)
478 code = 3;
479
480 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%d;%d;%dM",
481 code | modifiers, col + 1, row + 1);
482 break;
483 }
484 }
485
mousefunc(int col,int row,int button,int pressed,int modifiers,void * data)486 static void mousefunc(int col, int row, int button, int pressed, int modifiers, void *data)
487 {
488 VTermState *state = data;
489
490 int old_col = state->mouse_col;
491 int old_row = state->mouse_row;
492 int old_buttons = state->mouse_buttons;
493
494 state->mouse_col = col;
495 state->mouse_row = row;
496
497 if(button > 0 && button <= 3) {
498 if(pressed)
499 state->mouse_buttons |= (1 << (button-1));
500 else
501 state->mouse_buttons &= ~(1 << (button-1));
502 }
503
504 modifiers &= 0x7;
505
506
507 /* Most of the time we don't get button releases from 4/5 */
508 if(state->mouse_buttons != old_buttons || button >= 4) {
509 if(button < 4) {
510 output_mouse(state, button-1, pressed, modifiers, col, row);
511 }
512 else if(button < 6) {
513 output_mouse(state, button-4 + 0x40, pressed, modifiers, col, row);
514 }
515 }
516 else if(col != old_col || row != old_row) {
517 if((state->mouse_flags & MOUSE_WANT_DRAG && state->mouse_buttons) ||
518 (state->mouse_flags & MOUSE_WANT_MOVE)) {
519 int button = state->mouse_buttons & 0x01 ? 1 :
520 state->mouse_buttons & 0x02 ? 2 :
521 state->mouse_buttons & 0x04 ? 3 : 4;
522 output_mouse(state, button-1 + 0x20, 1, modifiers, col, row);
523 }
524 }
525 }
526
settermprop_bool(VTermState * state,VTermProp prop,int v)527 static int settermprop_bool(VTermState *state, VTermProp prop, int v)
528 {
529 VTermValue val = { .boolean = v };
530 return vterm_state_set_termprop(state, prop, &val);
531 }
532
settermprop_int(VTermState * state,VTermProp prop,int v)533 static int settermprop_int(VTermState *state, VTermProp prop, int v)
534 {
535 VTermValue val = { .number = v };
536 return vterm_state_set_termprop(state, prop, &val);
537 }
538
settermprop_string(VTermState * state,VTermProp prop,const char * str,size_t len)539 static int settermprop_string(VTermState *state, VTermProp prop, const char *str, size_t len)
540 {
541 char strvalue[len+1];
542 VTermValue val = { .string = strvalue };
543
544 strncpy(strvalue, str, len);
545 strvalue[len] = 0;
546
547 return vterm_state_set_termprop(state, prop, &val);
548 }
549
savecursor(VTermState * state,int save)550 static void savecursor(VTermState *state, int save)
551 {
552 if(save) {
553 state->saved.pos = state->pos;
554 state->saved.mode.cursor_visible = state->mode.cursor_visible;
555 state->saved.mode.cursor_blink = state->mode.cursor_blink;
556 state->saved.mode.cursor_shape = state->mode.cursor_shape;
557
558 vterm_state_savepen(state, 1);
559 }
560 else {
561 VTermPos oldpos = state->pos;
562
563 state->pos = state->saved.pos;
564
565 settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, state->saved.mode.cursor_visible);
566 settermprop_bool(state, VTERM_PROP_CURSORBLINK, state->saved.mode.cursor_blink);
567 settermprop_int (state, VTERM_PROP_CURSORSHAPE, state->saved.mode.cursor_shape);
568
569 vterm_state_savepen(state, 0);
570
571 updatecursor(state, &oldpos, 1);
572 }
573 }
574
on_escape(const char * bytes,size_t len,void * user)575 static int on_escape(const char *bytes, size_t len, void *user)
576 {
577 VTermState *state = user;
578
579 /* Easier to decode this from the first byte, even though the final
580 * byte terminates it
581 */
582 switch(bytes[0]) {
583 case ' ':
584 if(len != 2)
585 return 0;
586
587 switch(bytes[1]) {
588 case 'F': // S7C1T
589 state->vt->mode.ctrl8bit = 0;
590 break;
591
592 case 'G': // S8C1T
593 state->vt->mode.ctrl8bit = 1;
594 break;
595
596 default:
597 return 0;
598 }
599 return 2;
600
601 case '#':
602 if(len != 2)
603 return 0;
604
605 switch(bytes[1]) {
606 case '3': // DECDHL top
607 if(state->mode.leftrightmargin)
608 break;
609 set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_TOP);
610 break;
611
612 case '4': // DECDHL bottom
613 if(state->mode.leftrightmargin)
614 break;
615 set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_BOTTOM);
616 break;
617
618 case '5': // DECSWL
619 if(state->mode.leftrightmargin)
620 break;
621 set_lineinfo(state, state->pos.row, NO_FORCE, DWL_OFF, DHL_OFF);
622 break;
623
624 case '6': // DECDWL
625 if(state->mode.leftrightmargin)
626 break;
627 set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_OFF);
628 break;
629
630 case '8': // DECALN
631 {
632 VTermPos pos;
633 uint32_t E[] = { 'E', 0 };
634 for(pos.row = 0; pos.row < state->rows; pos.row++)
635 for(pos.col = 0; pos.col < ROWWIDTH(state, pos.row); pos.col++)
636 putglyph(state, E, 1, pos);
637 break;
638 }
639
640 default:
641 return 0;
642 }
643 return 2;
644
645 case '(': case ')': case '*': case '+': // SCS
646 if(len != 2)
647 return 0;
648
649 {
650 int setnum = bytes[0] - 0x28;
651 VTermEncoding *newenc = vterm_lookup_encoding(ENC_SINGLE_94, bytes[1]);
652
653 if(newenc) {
654 state->encoding[setnum].enc = newenc;
655
656 if(newenc->init)
657 (*newenc->init)(newenc, state->encoding[setnum].data);
658 }
659 }
660
661 return 2;
662
663 case '7': // DECSC
664 savecursor(state, 1);
665 return 1;
666
667 case '8': // DECRC
668 savecursor(state, 0);
669 return 1;
670
671 case '<': // Ignored by VT100. Used in VT52 mode to switch up to VT100
672 return 1;
673
674 case '=': // DECKPAM
675 state->mode.keypad = 1;
676 return 1;
677
678 case '>': // DECKPNM
679 state->mode.keypad = 0;
680 return 1;
681
682 case 'c': // RIS - ECMA-48 8.3.105
683 {
684 VTermPos oldpos = state->pos;
685 vterm_state_reset(state, 1);
686 if(state->callbacks && state->callbacks->movecursor)
687 (*state->callbacks->movecursor)(state->pos, oldpos, state->mode.cursor_visible, state->cbdata);
688 return 1;
689 }
690
691 case 'n': // LS2 - ECMA-48 8.3.78
692 state->gl_set = 2;
693 return 1;
694
695 case 'o': // LS3 - ECMA-48 8.3.80
696 state->gl_set = 3;
697 return 1;
698
699 case '~': // LS1R - ECMA-48 8.3.77
700 state->gr_set = 1;
701 return 1;
702
703 case '}': // LS2R - ECMA-48 8.3.79
704 state->gr_set = 2;
705 return 1;
706
707 case '|': // LS3R - ECMA-48 8.3.81
708 state->gr_set = 3;
709 return 1;
710
711 default:
712 return 0;
713 }
714 }
715
set_mode(VTermState * state,int num,int val)716 static void set_mode(VTermState *state, int num, int val)
717 {
718 switch(num) {
719 case 4: // IRM - ECMA-48 7.2.10
720 state->mode.insert = val;
721 break;
722
723 case 20: // LNM - ANSI X3.4-1977
724 state->mode.newline = val;
725 break;
726
727 default:
728 fprintf(stderr, "libvterm: Unknown mode %d\n", num);
729 return;
730 }
731 }
732
set_dec_mode(VTermState * state,int num,int val)733 static void set_dec_mode(VTermState *state, int num, int val)
734 {
735 switch(num) {
736 case 1:
737 state->mode.cursor = val;
738 break;
739
740 case 5: // DECSCNM - screen mode
741 settermprop_bool(state, VTERM_PROP_REVERSE, val);
742 break;
743
744 case 6: // DECOM - origin mode
745 {
746 VTermPos oldpos = state->pos;
747 state->mode.origin = val;
748 state->pos.row = state->mode.origin ? state->scrollregion_top : 0;
749 state->pos.col = state->mode.origin ? SCROLLREGION_LEFT(state) : 0;
750 updatecursor(state, &oldpos, 1);
751 }
752 break;
753
754 case 7:
755 state->mode.autowrap = val;
756 break;
757
758 case 12:
759 settermprop_bool(state, VTERM_PROP_CURSORBLINK, val);
760 break;
761
762 case 25:
763 settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, val);
764 break;
765
766 case 69: // DECVSSM - vertical split screen mode
767 // DECLRMM - left/right margin mode
768 state->mode.leftrightmargin = val;
769 if(val) {
770 int row;
771 // Setting DECVSSM must clear doublewidth/doubleheight state of every line
772 for(row = 0; row < state->rows; row++)
773 set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
774 }
775
776 break;
777
778 case 1000:
779 case 1002:
780 case 1003:
781 if(val) {
782 state->mouse_col = 0;
783 state->mouse_row = 0;
784 state->mouse_buttons = 0;
785
786 state->mouse_flags = MOUSE_WANT_CLICK;
787 state->mouse_protocol = MOUSE_X10;
788
789 if(num == 1002)
790 state->mouse_flags |= MOUSE_WANT_DRAG;
791 if(num == 1003)
792 state->mouse_flags |= MOUSE_WANT_MOVE;
793 }
794 else {
795 state->mouse_flags = 0;
796 }
797
798 if(state->callbacks && state->callbacks->setmousefunc)
799 (*state->callbacks->setmousefunc)(val ? mousefunc : NULL, state, state->cbdata);
800
801 break;
802
803 case 1005:
804 state->mouse_protocol = val ? MOUSE_UTF8 : MOUSE_X10;
805 break;
806
807 case 1006:
808 state->mouse_protocol = val ? MOUSE_SGR : MOUSE_X10;
809 break;
810
811 case 1015:
812 state->mouse_protocol = val ? MOUSE_RXVT : MOUSE_X10;
813 break;
814
815 case 1047:
816 settermprop_bool(state, VTERM_PROP_ALTSCREEN, val);
817 break;
818
819 case 1048:
820 savecursor(state, val);
821 break;
822
823 case 1049:
824 settermprop_bool(state, VTERM_PROP_ALTSCREEN, val);
825 savecursor(state, val);
826 break;
827
828 default:
829 fprintf(stderr, "libvterm: Unknown DEC mode %d\n", num);
830 return;
831 }
832 }
833
request_dec_mode(VTermState * state,int num)834 static void request_dec_mode(VTermState *state, int num)
835 {
836 int reply;
837
838 switch(num) {
839 case 1:
840 reply = state->mode.cursor;
841 break;
842
843 case 5:
844 reply = state->mode.screen;
845 break;
846
847 case 6:
848 reply = state->mode.origin;
849 break;
850
851 case 7:
852 reply = state->mode.autowrap;
853 break;
854
855 case 12:
856 reply = state->mode.cursor_blink;
857 break;
858
859 case 25:
860 reply = state->mode.cursor_visible;
861 break;
862
863 case 69:
864 reply = state->mode.leftrightmargin;
865 break;
866
867 case 1000:
868 reply = state->mouse_flags == MOUSE_WANT_CLICK;
869 break;
870
871 case 1002:
872 reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_DRAG);
873 break;
874
875 case 1003:
876 reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_MOVE);
877 break;
878
879 case 1005:
880 reply = state->mouse_protocol == MOUSE_UTF8;
881 break;
882
883 case 1006:
884 reply = state->mouse_protocol == MOUSE_SGR;
885 break;
886
887 case 1015:
888 reply = state->mouse_protocol == MOUSE_RXVT;
889 break;
890
891 case 1047:
892 reply = state->mode.alt_screen;
893 break;
894
895 default:
896 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, 0);
897 return;
898 }
899
900 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, reply ? 1 : 2);
901 }
902
on_csi(const char * leader,const long args[],int argcount,const char * intermed,char command,void * user)903 static int on_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user)
904 {
905 VTermState *state = user;
906 VTermPos oldpos;
907 int leader_byte = 0;
908 int intermed_byte = 0;
909
910 // Some temporaries for later code
911 int count, val;
912 int row, col;
913 VTermRect rect;
914 int selective;
915
916 if(leader && leader[0]) {
917 if(leader[1]) // longer than 1 char
918 return 0;
919
920 switch(leader[0]) {
921 case '?':
922 case '>':
923 leader_byte = leader[0];
924 break;
925 default:
926 return 0;
927 }
928 }
929
930 if(intermed && intermed[0]) {
931 if(intermed[1]) // longer than 1 char
932 return 0;
933
934 switch(intermed[0]) {
935 case ' ':
936 case '"':
937 case '$':
938 case '\'':
939 intermed_byte = intermed[0];
940 break;
941 default:
942 return 0;
943 }
944 }
945
946 oldpos = state->pos;
947
948 #define LBOUND(v,min) if((v) < (min)) (v) = (min)
949 #define UBOUND(v,max) if((v) > (max)) (v) = (max)
950
951 #define LEADER(l,b) ((l << 8) | b)
952 #define INTERMED(i,b) ((i << 16) | b)
953
954 switch(intermed_byte << 16 | leader_byte << 8 | command) {
955 case 0x40: // ICH - ECMA-48 8.3.64
956 count = CSI_ARG_COUNT(args[0]);
957
958 rect.start_row = state->pos.row;
959 rect.end_row = state->pos.row + 1;
960 rect.start_col = state->pos.col;
961 if(state->mode.leftrightmargin)
962 rect.end_col = SCROLLREGION_RIGHT(state);
963 else
964 rect.end_col = THISROWWIDTH(state);
965
966 scroll(state, rect, 0, -count);
967
968 break;
969
970 case 0x41: // CUU - ECMA-48 8.3.22
971 count = CSI_ARG_COUNT(args[0]);
972 state->pos.row -= count;
973 state->at_phantom = 0;
974 break;
975
976 case 0x42: // CUD - ECMA-48 8.3.19
977 count = CSI_ARG_COUNT(args[0]);
978 state->pos.row += count;
979 state->at_phantom = 0;
980 break;
981
982 case 0x43: // CUF - ECMA-48 8.3.20
983 count = CSI_ARG_COUNT(args[0]);
984 state->pos.col += count;
985 state->at_phantom = 0;
986 break;
987
988 case 0x44: // CUB - ECMA-48 8.3.18
989 count = CSI_ARG_COUNT(args[0]);
990 state->pos.col -= count;
991 state->at_phantom = 0;
992 break;
993
994 case 0x45: // CNL - ECMA-48 8.3.12
995 count = CSI_ARG_COUNT(args[0]);
996 state->pos.col = 0;
997 state->pos.row += count;
998 state->at_phantom = 0;
999 break;
1000
1001 case 0x46: // CPL - ECMA-48 8.3.13
1002 count = CSI_ARG_COUNT(args[0]);
1003 state->pos.col = 0;
1004 state->pos.row -= count;
1005 state->at_phantom = 0;
1006 break;
1007
1008 case 0x47: // CHA - ECMA-48 8.3.9
1009 val = CSI_ARG_OR(args[0], 1);
1010 state->pos.col = val-1;
1011 state->at_phantom = 0;
1012 break;
1013
1014 case 0x48: // CUP - ECMA-48 8.3.21
1015 row = CSI_ARG_OR(args[0], 1);
1016 col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]);
1017 // zero-based
1018 state->pos.row = row-1;
1019 state->pos.col = col-1;
1020 if(state->mode.origin) {
1021 state->pos.row += state->scrollregion_top;
1022 state->pos.col += SCROLLREGION_LEFT(state);
1023 }
1024 state->at_phantom = 0;
1025 break;
1026
1027 case 0x49: // CHT - ECMA-48 8.3.10
1028 count = CSI_ARG_COUNT(args[0]);
1029 tab(state, count, +1);
1030 break;
1031
1032 case 0x4a: // ED - ECMA-48 8.3.39
1033 case LEADER('?', 0x4a): // DECSED - Selective Erase in Display
1034 selective = (leader_byte == '?');
1035 switch(CSI_ARG(args[0])) {
1036 case CSI_ARG_MISSING:
1037 case 0:
1038 rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1;
1039 rect.start_col = state->pos.col; rect.end_col = state->cols;
1040 if(rect.end_col > rect.start_col)
1041 erase(state, rect, selective);
1042
1043 rect.start_row = state->pos.row + 1; rect.end_row = state->rows;
1044 rect.start_col = 0;
1045 for(row = rect.start_row; row < rect.end_row; row++)
1046 set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
1047 if(rect.end_row > rect.start_row)
1048 erase(state, rect, selective);
1049 break;
1050
1051 case 1:
1052 rect.start_row = 0; rect.end_row = state->pos.row;
1053 rect.start_col = 0; rect.end_col = state->cols;
1054 for(row = rect.start_row; row < rect.end_row; row++)
1055 set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
1056 if(rect.end_col > rect.start_col)
1057 erase(state, rect, selective);
1058
1059 rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1;
1060 rect.end_col = state->pos.col + 1;
1061 if(rect.end_row > rect.start_row)
1062 erase(state, rect, selective);
1063 break;
1064
1065 case 2:
1066 rect.start_row = 0; rect.end_row = state->rows;
1067 rect.start_col = 0; rect.end_col = state->cols;
1068 for(row = rect.start_row; row < rect.end_row; row++)
1069 set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
1070 erase(state, rect, selective);
1071 break;
1072 }
1073 break;
1074
1075 case 0x4b: // EL - ECMA-48 8.3.41
1076 case LEADER('?', 0x4b): // DECSEL - Selective Erase in Line
1077 selective = (leader_byte == '?');
1078 rect.start_row = state->pos.row;
1079 rect.end_row = state->pos.row + 1;
1080
1081 switch(CSI_ARG(args[0])) {
1082 case CSI_ARG_MISSING:
1083 case 0:
1084 rect.start_col = state->pos.col; rect.end_col = THISROWWIDTH(state); break;
1085 case 1:
1086 rect.start_col = 0; rect.end_col = state->pos.col + 1; break;
1087 case 2:
1088 rect.start_col = 0; rect.end_col = THISROWWIDTH(state); break;
1089 default:
1090 return 0;
1091 }
1092
1093 if(rect.end_col > rect.start_col)
1094 erase(state, rect, selective);
1095
1096 break;
1097
1098 case 0x4c: // IL - ECMA-48 8.3.67
1099 count = CSI_ARG_COUNT(args[0]);
1100
1101 rect.start_row = state->pos.row;
1102 rect.end_row = SCROLLREGION_BOTTOM(state);
1103 rect.start_col = SCROLLREGION_LEFT(state);
1104 rect.end_col = SCROLLREGION_RIGHT(state);
1105
1106 scroll(state, rect, -count, 0);
1107
1108 break;
1109
1110 case 0x4d: // DL - ECMA-48 8.3.32
1111 count = CSI_ARG_COUNT(args[0]);
1112
1113 rect.start_row = state->pos.row;
1114 rect.end_row = SCROLLREGION_BOTTOM(state);
1115 rect.start_col = SCROLLREGION_LEFT(state);
1116 rect.end_col = SCROLLREGION_RIGHT(state);
1117
1118 scroll(state, rect, count, 0);
1119
1120 break;
1121
1122 case 0x50: // DCH - ECMA-48 8.3.26
1123 count = CSI_ARG_COUNT(args[0]);
1124
1125 rect.start_row = state->pos.row;
1126 rect.end_row = state->pos.row + 1;
1127 rect.start_col = state->pos.col;
1128 if(state->mode.leftrightmargin)
1129 rect.end_col = SCROLLREGION_RIGHT(state);
1130 else
1131 rect.end_col = THISROWWIDTH(state);
1132
1133 scroll(state, rect, 0, count);
1134
1135 break;
1136
1137 case 0x53: // SU - ECMA-48 8.3.147
1138 count = CSI_ARG_COUNT(args[0]);
1139
1140 rect.start_row = state->scrollregion_top;
1141 rect.end_row = SCROLLREGION_BOTTOM(state);
1142 rect.start_col = SCROLLREGION_LEFT(state);
1143 rect.end_col = SCROLLREGION_RIGHT(state);
1144
1145 scroll(state, rect, count, 0);
1146
1147 break;
1148
1149 case 0x54: // SD - ECMA-48 8.3.113
1150 count = CSI_ARG_COUNT(args[0]);
1151
1152 rect.start_row = state->scrollregion_top;
1153 rect.end_row = SCROLLREGION_BOTTOM(state);
1154 rect.start_col = SCROLLREGION_LEFT(state);
1155 rect.end_col = SCROLLREGION_RIGHT(state);
1156
1157 scroll(state, rect, -count, 0);
1158
1159 break;
1160
1161 case 0x58: // ECH - ECMA-48 8.3.38
1162 count = CSI_ARG_COUNT(args[0]);
1163
1164 rect.start_row = state->pos.row;
1165 rect.end_row = state->pos.row + 1;
1166 rect.start_col = state->pos.col;
1167 rect.end_col = state->pos.col + count;
1168 UBOUND(rect.end_col, THISROWWIDTH(state));
1169
1170 erase(state, rect, 0);
1171 break;
1172
1173 case 0x5a: // CBT - ECMA-48 8.3.7
1174 count = CSI_ARG_COUNT(args[0]);
1175 tab(state, count, -1);
1176 break;
1177
1178 case 0x60: // HPA - ECMA-48 8.3.57
1179 col = CSI_ARG_OR(args[0], 1);
1180 state->pos.col = col-1;
1181 state->at_phantom = 0;
1182 break;
1183
1184 case 0x61: // HPR - ECMA-48 8.3.59
1185 count = CSI_ARG_COUNT(args[0]);
1186 state->pos.col += count;
1187 state->at_phantom = 0;
1188 break;
1189
1190 case 0x63: // DA - ECMA-48 8.3.24
1191 val = CSI_ARG_OR(args[0], 0);
1192 if(val == 0)
1193 // DEC VT100 response
1194 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?1;2c");
1195 break;
1196
1197 case LEADER('>', 0x63): // DEC secondary Device Attributes
1198 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, ">%d;%d;%dc", 0, 100, 0);
1199 break;
1200
1201 case 0x64: // VPA - ECMA-48 8.3.158
1202 row = CSI_ARG_OR(args[0], 1);
1203 state->pos.row = row-1;
1204 if(state->mode.origin)
1205 state->pos.row += state->scrollregion_top;
1206 state->at_phantom = 0;
1207 break;
1208
1209 case 0x65: // VPR - ECMA-48 8.3.160
1210 count = CSI_ARG_COUNT(args[0]);
1211 state->pos.row += count;
1212 state->at_phantom = 0;
1213 break;
1214
1215 case 0x66: // HVP - ECMA-48 8.3.63
1216 row = CSI_ARG_OR(args[0], 1);
1217 col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]);
1218 // zero-based
1219 state->pos.row = row-1;
1220 state->pos.col = col-1;
1221 if(state->mode.origin) {
1222 state->pos.row += state->scrollregion_top;
1223 state->pos.col += SCROLLREGION_LEFT(state);
1224 }
1225 state->at_phantom = 0;
1226 break;
1227
1228 case 0x67: // TBC - ECMA-48 8.3.154
1229 val = CSI_ARG_OR(args[0], 0);
1230
1231 switch(val) {
1232 case 0:
1233 clear_col_tabstop(state, state->pos.col);
1234 break;
1235 case 3:
1236 case 5:
1237 for(col = 0; col < state->cols; col++)
1238 clear_col_tabstop(state, col);
1239 break;
1240 case 1:
1241 case 2:
1242 case 4:
1243 break;
1244 /* TODO: 1, 2 and 4 aren't meaningful yet without line tab stops */
1245 default:
1246 return 0;
1247 }
1248 break;
1249
1250 case 0x68: // SM - ECMA-48 8.3.125
1251 if(!CSI_ARG_IS_MISSING(args[0]))
1252 set_mode(state, CSI_ARG(args[0]), 1);
1253 break;
1254
1255 case LEADER('?', 0x68): // DEC private mode set
1256 if(!CSI_ARG_IS_MISSING(args[0]))
1257 set_dec_mode(state, CSI_ARG(args[0]), 1);
1258 break;
1259
1260 case 0x6a: // HPB - ECMA-48 8.3.58
1261 count = CSI_ARG_COUNT(args[0]);
1262 state->pos.col -= count;
1263 state->at_phantom = 0;
1264 break;
1265
1266 case 0x6b: // VPB - ECMA-48 8.3.159
1267 count = CSI_ARG_COUNT(args[0]);
1268 state->pos.row -= count;
1269 state->at_phantom = 0;
1270 break;
1271
1272 case 0x6c: // RM - ECMA-48 8.3.106
1273 if(!CSI_ARG_IS_MISSING(args[0]))
1274 set_mode(state, CSI_ARG(args[0]), 0);
1275 break;
1276
1277 case LEADER('?', 0x6c): // DEC private mode reset
1278 if(!CSI_ARG_IS_MISSING(args[0]))
1279 set_dec_mode(state, CSI_ARG(args[0]), 0);
1280 break;
1281
1282 case 0x6d: // SGR - ECMA-48 8.3.117
1283 vterm_state_setpen(state, args, argcount);
1284 break;
1285
1286 case 0x6e: // DSR - ECMA-48 8.3.35
1287 case LEADER('?', 0x6e): // DECDSR
1288 val = CSI_ARG_OR(args[0], 0);
1289
1290 {
1291 char *qmark = (leader_byte == '?') ? "?" : "";
1292
1293 switch(val) {
1294 case 0: case 1: case 2: case 3: case 4:
1295 // ignore - these are replies
1296 break;
1297 case 5:
1298 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s0n", qmark);
1299 break;
1300 case 6: // CPR - cursor position report
1301 vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s%d;%dR", qmark, state->pos.row + 1, state->pos.col + 1);
1302 break;
1303 }
1304 }
1305 break;
1306
1307
1308 case LEADER('!', 0x70): // DECSTR - DEC soft terminal reset
1309 vterm_state_reset(state, 0);
1310 break;
1311
1312 case LEADER('?', INTERMED('$', 0x70)):
1313 request_dec_mode(state, CSI_ARG(args[0]));
1314 break;
1315
1316 case INTERMED(' ', 0x71): // DECSCUSR - DEC set cursor shape
1317 val = CSI_ARG_OR(args[0], 1);
1318
1319 switch(val) {
1320 case 0: case 1:
1321 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
1322 settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK);
1323 break;
1324 case 2:
1325 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0);
1326 settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK);
1327 break;
1328 case 3:
1329 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
1330 settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE);
1331 break;
1332 case 4:
1333 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0);
1334 settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE);
1335 break;
1336 case 5:
1337 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
1338 settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT);
1339 break;
1340 case 6:
1341 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0);
1342 settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT);
1343 break;
1344 }
1345
1346 break;
1347
1348 case INTERMED('"', 0x71): // DECSCA - DEC select character protection attribute
1349 val = CSI_ARG_OR(args[0], 0);
1350
1351 switch(val) {
1352 case 0: case 2:
1353 state->protected_cell = 0;
1354 break;
1355 case 1:
1356 state->protected_cell = 1;
1357 break;
1358 }
1359
1360 break;
1361
1362 case 0x72: // DECSTBM - DEC custom
1363 state->scrollregion_top = CSI_ARG_OR(args[0], 1) - 1;
1364 state->scrollregion_bottom = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]);
1365 LBOUND(state->scrollregion_top, -1);
1366 UBOUND(state->scrollregion_top, state->rows);
1367 LBOUND(state->scrollregion_bottom, -1);
1368 if(state->scrollregion_top == 0 && state->scrollregion_bottom == state->rows)
1369 state->scrollregion_bottom = -1;
1370 else
1371 UBOUND(state->scrollregion_bottom, state->rows);
1372
1373 break;
1374
1375 case 0x73: // DECSLRM - DEC custom
1376 // Always allow setting these margins, just they won't take effect without DECVSSM
1377 state->scrollregion_left = CSI_ARG_OR(args[0], 1) - 1;
1378 state->scrollregion_right = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]);
1379 LBOUND(state->scrollregion_left, -1);
1380 UBOUND(state->scrollregion_left, state->cols);
1381 LBOUND(state->scrollregion_right, -1);
1382 if(state->scrollregion_left == 0 && state->scrollregion_right == state->cols)
1383 state->scrollregion_right = -1;
1384 else
1385 UBOUND(state->scrollregion_right, state->cols);
1386
1387 break;
1388
1389 case INTERMED('\'', 0x7D): // DECIC
1390 count = CSI_ARG_COUNT(args[0]);
1391
1392 rect.start_row = state->scrollregion_top;
1393 rect.end_row = SCROLLREGION_BOTTOM(state);
1394 rect.start_col = state->pos.col;
1395 rect.end_col = SCROLLREGION_RIGHT(state);
1396
1397 scroll(state, rect, 0, -count);
1398
1399 break;
1400
1401 case INTERMED('\'', 0x7E): // DECDC
1402 count = CSI_ARG_COUNT(args[0]);
1403
1404 rect.start_row = state->scrollregion_top;
1405 rect.end_row = SCROLLREGION_BOTTOM(state);
1406 rect.start_col = state->pos.col;
1407 rect.end_col = SCROLLREGION_RIGHT(state);
1408
1409 scroll(state, rect, 0, count);
1410
1411 break;
1412
1413 default:
1414 return 0;
1415 }
1416
1417 if(state->mode.origin) {
1418 LBOUND(state->pos.row, state->scrollregion_top);
1419 UBOUND(state->pos.row, state->scrollregion_bottom-1);
1420 LBOUND(state->pos.col, SCROLLREGION_LEFT(state));
1421 UBOUND(state->pos.col, SCROLLREGION_RIGHT(state)-1);
1422 }
1423 else {
1424 LBOUND(state->pos.row, 0);
1425 UBOUND(state->pos.row, state->rows-1);
1426 LBOUND(state->pos.col, 0);
1427 UBOUND(state->pos.col, THISROWWIDTH(state)-1);
1428 }
1429
1430 updatecursor(state, &oldpos, 1);
1431
1432 return 1;
1433 }
1434
on_osc(const char * command,size_t cmdlen,void * user)1435 static int on_osc(const char *command, size_t cmdlen, void *user)
1436 {
1437 VTermState *state = user;
1438
1439 if(cmdlen < 2)
1440 return 0;
1441
1442 if(strneq(command, "0;", 2)) {
1443 settermprop_string(state, VTERM_PROP_ICONNAME, command + 2, cmdlen - 2);
1444 settermprop_string(state, VTERM_PROP_TITLE, command + 2, cmdlen - 2);
1445 return 1;
1446 }
1447 else if(strneq(command, "1;", 2)) {
1448 settermprop_string(state, VTERM_PROP_ICONNAME, command + 2, cmdlen - 2);
1449 return 1;
1450 }
1451 else if(strneq(command, "2;", 2)) {
1452 settermprop_string(state, VTERM_PROP_TITLE, command + 2, cmdlen - 2);
1453 return 1;
1454 }
1455
1456 return 0;
1457 }
1458
request_status_string(VTermState * state,const char * command,size_t cmdlen)1459 static void request_status_string(VTermState *state, const char *command, size_t cmdlen)
1460 {
1461 if(cmdlen == 1)
1462 switch(command[0]) {
1463 case 'm': // Query SGR
1464 {
1465 long args[20];
1466 int argc = vterm_state_getpen(state, args, sizeof(args)/sizeof(args[0]));
1467 int argi;
1468 vterm_push_output_sprintf_ctrl(state->vt, C1_DCS, "1$r");
1469 for(argi = 0; argi < argc; argi++)
1470 vterm_push_output_sprintf(state->vt,
1471 argi == argc - 1 ? "%d" :
1472 CSI_ARG_HAS_MORE(args[argi]) ? "%d:" :
1473 "%d;",
1474 CSI_ARG(args[argi]));
1475 vterm_push_output_sprintf(state->vt, "m");
1476 vterm_push_output_sprintf_ctrl(state->vt, C1_ST, "");
1477 }
1478 return;
1479 case 'r': // Query DECSTBM
1480 vterm_push_output_sprintf_dcs(state->vt, "1$r%d;%dr", state->scrollregion_top+1, SCROLLREGION_BOTTOM(state));
1481 return;
1482 case 's': // Query DECSLRM
1483 vterm_push_output_sprintf_dcs(state->vt, "1$r%d;%ds", SCROLLREGION_LEFT(state)+1, SCROLLREGION_RIGHT(state));
1484 return;
1485 }
1486
1487 if(cmdlen == 2) {
1488 if(strneq(command, " q", 2)) {
1489 int reply = 0;
1490 switch(state->mode.cursor_shape) {
1491 case VTERM_PROP_CURSORSHAPE_BLOCK: reply = 2; break;
1492 case VTERM_PROP_CURSORSHAPE_UNDERLINE: reply = 4; break;
1493 case VTERM_PROP_CURSORSHAPE_BAR_LEFT: reply = 6; break;
1494 }
1495 if(state->mode.cursor_blink)
1496 reply--;
1497 vterm_push_output_sprintf_dcs(state->vt, "1$r%d q", reply);
1498 return;
1499 }
1500 else if(strneq(command, "\"q", 2)) {
1501 vterm_push_output_sprintf_dcs(state->vt, "1$r%d\"q", state->protected_cell ? 1 : 2);
1502 return;
1503 }
1504 }
1505
1506 vterm_push_output_sprintf_dcs(state->vt, "0$r%.s", (int)cmdlen, command);
1507 }
1508
on_dcs(const char * command,size_t cmdlen,void * user)1509 static int on_dcs(const char *command, size_t cmdlen, void *user)
1510 {
1511 VTermState *state = user;
1512
1513 if(cmdlen >= 2 && strneq(command, "$q", 2)) {
1514 request_status_string(state, command+2, cmdlen-2);
1515 return 1;
1516 }
1517
1518 return 0;
1519 }
1520
on_resize(int rows,int cols,void * user)1521 static int on_resize(int rows, int cols, void *user)
1522 {
1523 VTermState *state = user;
1524 VTermPos oldpos = state->pos;
1525 VTermPos delta = { 0, 0 };
1526
1527 if(cols != state->cols) {
1528 unsigned char *newtabstops = vterm_allocator_malloc(state->vt, (cols + 7) / 8);
1529
1530 /* TODO: This can all be done much more efficiently bytewise */
1531 int col;
1532 for(col = 0; col < state->cols && col < cols; col++) {
1533 unsigned char mask = 1 << (col & 7);
1534 if(state->tabstops[col >> 3] & mask)
1535 newtabstops[col >> 3] |= mask;
1536 else
1537 newtabstops[col >> 3] &= ~mask;
1538 }
1539
1540 for( ; col < cols; col++) {
1541 unsigned char mask = 1 << (col & 7);
1542 if(col % 8 == 0)
1543 newtabstops[col >> 3] |= mask;
1544 else
1545 newtabstops[col >> 3] &= ~mask;
1546 }
1547
1548 vterm_allocator_free(state->vt, state->tabstops);
1549 state->tabstops = newtabstops;
1550 }
1551
1552 if(rows != state->rows) {
1553 VTermLineInfo *newlineinfo = vterm_allocator_malloc(state->vt, rows * sizeof(VTermLineInfo));
1554
1555 int row;
1556 for(row = 0; row < state->rows && row < rows; row++) {
1557 newlineinfo[row] = state->lineinfo[row];
1558 }
1559
1560 for( ; row < rows; row++) {
1561 newlineinfo[row] = (VTermLineInfo){
1562 .doublewidth = 0,
1563 };
1564 }
1565
1566 vterm_allocator_free(state->vt, state->lineinfo);
1567 state->lineinfo = newlineinfo;
1568 }
1569
1570 state->rows = rows;
1571 state->cols = cols;
1572
1573 if(state->callbacks && state->callbacks->resize)
1574 (*state->callbacks->resize)(rows, cols, &delta, state->cbdata);
1575
1576 if(state->at_phantom && state->pos.col < cols-1) {
1577 state->at_phantom = 0;
1578 state->pos.col++;
1579 }
1580
1581 state->pos.row += delta.row;
1582 state->pos.col += delta.col;
1583
1584 if(state->pos.row >= rows)
1585 state->pos.row = rows - 1;
1586 if(state->pos.col >= cols)
1587 state->pos.col = cols - 1;
1588
1589 updatecursor(state, &oldpos, 1);
1590
1591 return 1;
1592 }
1593
1594 static const VTermParserCallbacks parser_callbacks = {
1595 .text = on_text,
1596 .control = on_control,
1597 .escape = on_escape,
1598 .csi = on_csi,
1599 .osc = on_osc,
1600 .dcs = on_dcs,
1601 .resize = on_resize,
1602 };
1603
vterm_obtain_state(VTerm * vt)1604 VTermState *vterm_obtain_state(VTerm *vt)
1605 {
1606 VTermState *state;
1607 if(vt->state)
1608 return vt->state;
1609
1610 state = vterm_state_new(vt);
1611 vt->state = state;
1612
1613 state->combine_chars_size = 16;
1614 state->combine_chars = vterm_allocator_malloc(state->vt, state->combine_chars_size * sizeof(state->combine_chars[0]));
1615
1616 state->tabstops = vterm_allocator_malloc(state->vt, (state->cols + 7) / 8);
1617
1618 state->lineinfo = vterm_allocator_malloc(state->vt, state->rows * sizeof(VTermLineInfo));
1619
1620 state->encoding_utf8.enc = vterm_lookup_encoding(ENC_UTF8, 'u');
1621 if(*state->encoding_utf8.enc->init)
1622 (*state->encoding_utf8.enc->init)(state->encoding_utf8.enc, state->encoding_utf8.data);
1623
1624 vterm_set_parser_callbacks(vt, &parser_callbacks, state);
1625
1626 return state;
1627 }
1628
vterm_state_reset(VTermState * state,int hard)1629 void vterm_state_reset(VTermState *state, int hard)
1630 {
1631 int col, i, row;
1632 VTermEncoding *default_enc;
1633
1634 state->scrollregion_top = 0;
1635 state->scrollregion_bottom = -1;
1636 state->scrollregion_left = 0;
1637 state->scrollregion_right = -1;
1638
1639 state->mode.keypad = 0;
1640 state->mode.cursor = 0;
1641 state->mode.autowrap = 1;
1642 state->mode.insert = 0;
1643 state->mode.newline = 0;
1644 state->mode.alt_screen = 0;
1645 state->mode.origin = 0;
1646 state->mode.leftrightmargin = 0;
1647
1648 state->vt->mode.ctrl8bit = 0;
1649
1650 for(col = 0; col < state->cols; col++)
1651 if(col % 8 == 0)
1652 set_col_tabstop(state, col);
1653 else
1654 clear_col_tabstop(state, col);
1655
1656 for(row = 0; row < state->rows; row++)
1657 set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
1658
1659 if(state->callbacks && state->callbacks->initpen)
1660 (*state->callbacks->initpen)(state->cbdata);
1661
1662 vterm_state_resetpen(state);
1663
1664 default_enc = state->vt->mode.utf8 ?
1665 vterm_lookup_encoding(ENC_UTF8, 'u') :
1666 vterm_lookup_encoding(ENC_SINGLE_94, 'B');
1667
1668 for(i = 0; i < 4; i++) {
1669 state->encoding[i].enc = default_enc;
1670 if(default_enc->init)
1671 (*default_enc->init)(default_enc, state->encoding[i].data);
1672 }
1673
1674 state->gl_set = 0;
1675 state->gr_set = 1;
1676 state->gsingle_set = 0;
1677
1678 state->protected_cell = 0;
1679
1680 // Initialise the props
1681 settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, 1);
1682 settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
1683 settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK);
1684
1685 if(hard) {
1686 VTermRect rect = { 0, state->rows, 0, state->cols };
1687 state->pos.row = 0;
1688 state->pos.col = 0;
1689 state->at_phantom = 0;
1690
1691 erase(state, rect, 0);
1692 }
1693 }
1694
vterm_state_get_cursorpos(const VTermState * state,VTermPos * cursorpos)1695 void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos)
1696 {
1697 *cursorpos = state->pos;
1698 }
1699
vterm_state_set_callbacks(VTermState * state,const VTermStateCallbacks * callbacks,void * user)1700 void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user)
1701 {
1702 if(callbacks) {
1703 state->callbacks = callbacks;
1704 state->cbdata = user;
1705
1706 if(state->callbacks && state->callbacks->initpen)
1707 (*state->callbacks->initpen)(state->cbdata);
1708 }
1709 else {
1710 state->callbacks = NULL;
1711 state->cbdata = NULL;
1712 }
1713 }
1714
vterm_state_set_termprop(VTermState * state,VTermProp prop,VTermValue * val)1715 int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val)
1716 {
1717 /* Only store the new value of the property if usercode said it was happy.
1718 * This is especially important for altscreen switching */
1719 if(state->callbacks && state->callbacks->settermprop)
1720 if(!(*state->callbacks->settermprop)(prop, val, state->cbdata))
1721 return 0;
1722
1723 switch(prop) {
1724 case VTERM_PROP_TITLE:
1725 case VTERM_PROP_ICONNAME:
1726 // we don't store these, just transparently pass through
1727 return 1;
1728 case VTERM_PROP_CURSORVISIBLE:
1729 state->mode.cursor_visible = val->boolean;
1730 return 1;
1731 case VTERM_PROP_CURSORBLINK:
1732 state->mode.cursor_blink = val->boolean;
1733 return 1;
1734 case VTERM_PROP_CURSORSHAPE:
1735 state->mode.cursor_shape = val->number;
1736 return 1;
1737 case VTERM_PROP_REVERSE:
1738 state->mode.screen = val->boolean;
1739 return 1;
1740 case VTERM_PROP_ALTSCREEN:
1741 state->mode.alt_screen = val->boolean;
1742 if(state->mode.alt_screen) {
1743 VTermRect rect = {
1744 .start_row = 0,
1745 .start_col = 0,
1746 .end_row = state->rows,
1747 .end_col = state->cols,
1748 };
1749 erase(state, rect, 0);
1750 }
1751 return 1;
1752 }
1753
1754 return 0;
1755 }
1756
vterm_state_get_lineinfo(const VTermState * state,int row)1757 const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row)
1758 {
1759 return state->lineinfo + row;
1760 }
1761