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