1 #include "csi.h"
2
3 #include <stdlib.h>
4 #include <string.h>
5 #include <unistd.h>
6 #include <errno.h>
7
8 #if defined(_DEBUG)
9 #include <stdio.h>
10 #endif
11
12 #include <sys/timerfd.h>
13
14 #define LOG_MODULE "csi"
15 #define LOG_ENABLE_DBG 0
16 #include "log.h"
17 #include "config.h"
18 #include "debug.h"
19 #include "grid.h"
20 #include "selection.h"
21 #include "sixel.h"
22 #include "util.h"
23 #include "version.h"
24 #include "vt.h"
25 #include "xmalloc.h"
26 #include "xsnprintf.h"
27
28 #define UNHANDLED() LOG_DBG("unhandled: %s", csi_as_string(term, final, -1))
29 #define UNHANDLED_SGR(idx) LOG_DBG("unhandled: %s", csi_as_string(term, 'm', idx))
30
31 static void
sgr_reset(struct terminal * term)32 sgr_reset(struct terminal *term)
33 {
34 memset(&term->vt.attrs, 0, sizeof(term->vt.attrs));
35 term->vt.attrs.fg = term->colors.fg;
36 term->vt.attrs.bg = term->colors.bg;
37 }
38
39 static const char *
csi_as_string(struct terminal * term,uint8_t final,int idx)40 csi_as_string(struct terminal *term, uint8_t final, int idx)
41 {
42 static char msg[1024];
43 int c = snprintf(msg, sizeof(msg), "CSI: ");
44
45 for (size_t i = idx >= 0 ? idx : 0;
46 i < (idx >= 0 ? idx + 1 : term->vt.params.idx);
47 i++)
48 {
49 c += snprintf(&msg[c], sizeof(msg) - c, "%u",
50 term->vt.params.v[i].value);
51
52 for (size_t j = 0; j < term->vt.params.v[i].sub.idx; j++) {
53 c += snprintf(&msg[c], sizeof(msg) - c, ":%u",
54 term->vt.params.v[i].sub.value[j]);
55 }
56
57 c += snprintf(&msg[c], sizeof(msg) - c, "%s",
58 i == term->vt.params.idx - 1 ? "" : ";");
59 }
60
61 for (size_t i = 0; i < sizeof(term->vt.private); i++) {
62 char value = (term->vt.private >> (i * 8)) & 0xff;
63 if (value == 0)
64 break;
65 c += snprintf(&msg[c], sizeof(msg) - c, "%c", value);
66 }
67
68 snprintf(&msg[c], sizeof(msg) - c, "%c (%u parameters)",
69 final, idx >= 0 ? 1 : term->vt.params.idx);
70 return msg;
71 }
72
73 static void
csi_sgr(struct terminal * term)74 csi_sgr(struct terminal *term)
75 {
76 if (term->vt.params.idx == 0) {
77 sgr_reset(term);
78 return;
79 }
80
81 for (size_t i = 0; i < term->vt.params.idx; i++) {
82 const int param = term->vt.params.v[i].value;
83
84 switch (param) {
85 case 0:
86 sgr_reset(term);
87 break;
88
89 case 1: term->vt.attrs.bold = true; break;
90 case 2: term->vt.attrs.dim = true; break;
91 case 3: term->vt.attrs.italic = true; break;
92 case 4: term->vt.attrs.underline = true; break;
93 case 5: term->vt.attrs.blink = true; break;
94 case 6: LOG_WARN("ignored: rapid blink"); break;
95 case 7: term->vt.attrs.reverse = true; break;
96 case 8: term->vt.attrs.conceal = true; break;
97 case 9: term->vt.attrs.strikethrough = true; break;
98
99 case 21: break; /* double-underline, not implemented */
100 case 22: term->vt.attrs.bold = term->vt.attrs.dim = false; break;
101 case 23: term->vt.attrs.italic = false; break;
102 case 24: term->vt.attrs.underline = false; break;
103 case 25: term->vt.attrs.blink = false; break;
104 case 26: break; /* rapid blink, ignored */
105 case 27: term->vt.attrs.reverse = false; break;
106 case 28: term->vt.attrs.conceal = false; break;
107 case 29: term->vt.attrs.strikethrough = false; break;
108
109 /* Regular foreground colors */
110 case 30:
111 case 31:
112 case 32:
113 case 33:
114 case 34:
115 case 35:
116 case 36:
117 case 37:
118 term->vt.attrs.fg_src = COLOR_BASE16;
119 term->vt.attrs.fg = term->colors.table[param - 30];
120 break;
121
122 case 38: {
123 /* Indexed: 38;5;<idx> */
124 if (term->vt.params.idx - i - 1 >= 2 &&
125 term->vt.params.v[i + 1].value == 5)
126 {
127 uint8_t idx = term->vt.params.v[i + 2].value;
128 term->vt.attrs.fg_src = COLOR_BASE256;
129 term->vt.attrs.fg = term->colors.table[idx];
130 i += 2;
131
132 }
133
134 /* RGB: 38;2;<r>;<g>;<b> */
135 else if (term->vt.params.idx - i - 1 >= 4 &&
136 term->vt.params.v[i + 1].value == 2)
137 {
138 uint8_t r = term->vt.params.v[i + 2].value;
139 uint8_t g = term->vt.params.v[i + 3].value;
140 uint8_t b = term->vt.params.v[i + 4].value;
141 term->vt.attrs.fg_src = COLOR_RGB;
142 term->vt.attrs.fg = r << 16 | g << 8 | b;
143 i += 4;
144 }
145
146 /* Indexed: 38:5:<idx> */
147 else if (term->vt.params.v[i].sub.idx >= 2 &&
148 term->vt.params.v[i].sub.value[0] == 5)
149 {
150 const struct vt_param *param = &term->vt.params.v[i];
151
152 uint8_t idx = param->sub.value[1];
153 term->vt.attrs.fg_src = COLOR_BASE256;
154 term->vt.attrs.fg = term->colors.table[idx];
155 }
156
157 /*
158 * RGB: 38:2:<color-space>:r:g:b[:ignored:tolerance:tolerance-color-space]
159 * RGB: 38:2:r:g:b
160 *
161 * The second version is a "bastard" version - many
162 * programs "forget" the color space ID
163 * parameter... *sigh*
164 */
165 else if (term->vt.params.v[i].sub.idx >= 4 &&
166 term->vt.params.v[i].sub.value[0] == 2)
167 {
168 const struct vt_param *param = &term->vt.params.v[i];
169 bool have_color_space_id = param->sub.idx >= 5;
170
171 /* 0 - color space (ignored) */
172 int r_idx = 2 - !have_color_space_id;
173 int g_idx = 3 - !have_color_space_id;
174 int b_idx = 4 - !have_color_space_id;
175 /* 5 - unused */
176 /* 6 - CS tolerance */
177 /* 7 - color space associated with tolerance */
178
179 uint8_t r = param->sub.value[r_idx];
180 uint8_t g = param->sub.value[g_idx];
181 uint8_t b = param->sub.value[b_idx];
182
183 term->vt.attrs.fg_src = COLOR_RGB;
184 term->vt.attrs.fg = r << 16 | g << 8 | b;
185 }
186
187 /* Transparent: 38:1 */
188 /* CMY: 38:3:<color-space>:c:m:y[:tolerance:tolerance-color-space] */
189 /* CMYK: 38:4:<color-space>:c:m:y:k[:tolerance:tolerance-color-space] */
190
191 /* Unrecognized */
192 else
193 UNHANDLED_SGR(i);
194
195 break;
196 }
197
198 case 39:
199 term->vt.attrs.fg_src = COLOR_DEFAULT;
200 break;
201
202 /* Regular background colors */
203 case 40:
204 case 41:
205 case 42:
206 case 43:
207 case 44:
208 case 45:
209 case 46:
210 case 47:
211 term->vt.attrs.bg_src = COLOR_BASE16;
212 term->vt.attrs.bg = term->colors.table[param - 40];
213 break;
214
215 case 48: {
216 /* Indexed: 48;5;<idx> */
217 if (term->vt.params.idx - i - 1 >= 2 &&
218 term->vt.params.v[i + 1].value == 5)
219 {
220 uint8_t idx = term->vt.params.v[i + 2].value;
221 term->vt.attrs.bg_src = COLOR_BASE256;
222 term->vt.attrs.bg = term->colors.table[idx];
223 i += 2;
224
225 }
226
227 /* RGB: 48;2;<r>;<g>;<b> */
228 else if (term->vt.params.idx - i - 1 >= 4 &&
229 term->vt.params.v[i + 1].value == 2)
230 {
231 uint8_t r = term->vt.params.v[i + 2].value;
232 uint8_t g = term->vt.params.v[i + 3].value;
233 uint8_t b = term->vt.params.v[i + 4].value;
234 term->vt.attrs.bg_src = COLOR_RGB;
235 term->vt.attrs.bg = r << 16 | g << 8 | b;
236 i += 4;
237 }
238
239 /* Indexed: 48:5:<idx> */
240 else if (term->vt.params.v[i].sub.idx >= 2 &&
241 term->vt.params.v[i].sub.value[0] == 5)
242 {
243 const struct vt_param *param = &term->vt.params.v[i];
244
245 uint8_t idx = param->sub.value[1];
246 term->vt.attrs.bg_src = COLOR_BASE256;
247 term->vt.attrs.bg = term->colors.table[idx];
248 }
249
250 /*
251 * RGB: 48:2:<color-space>:r:g:b[:ignored:tolerance:tolerance-color-space]
252 * RGB: 48:2:r:g:b
253 *
254 * The second version is a "bastard" version - many
255 * programs "forget" the color space ID
256 * parameter... *sigh*
257 */
258 else if (term->vt.params.v[i].sub.idx >= 4 &&
259 term->vt.params.v[i].sub.value[0] == 2)
260 {
261 const struct vt_param *param = &term->vt.params.v[i];
262 bool have_color_space_id = param->sub.idx >= 5;
263
264 /* 0 - color space (ignored) */
265 int r_idx = 2 - !have_color_space_id;
266 int g_idx = 3 - !have_color_space_id;
267 int b_idx = 4 - !have_color_space_id;
268 /* 5 - unused */
269 /* 6 - CS tolerance */
270 /* 7 - color space associated with tolerance */
271
272 uint8_t r = param->sub.value[r_idx];
273 uint8_t g = param->sub.value[g_idx];
274 uint8_t b = param->sub.value[b_idx];
275
276 term->vt.attrs.bg_src = COLOR_RGB;
277 term->vt.attrs.bg = r << 16 | g << 8 | b;
278 }
279
280 /* Transparent: 48:1 */
281 /* CMY: 48:3:<color-space>:c:m:y[:tolerance:tolerance-color-space] */
282 /* CMYK: 48:4:<color-space>:c:m:y:k[:tolerance:tolerance-color-space] */
283
284 else
285 UNHANDLED_SGR(i);
286
287 break;
288 }
289 case 49:
290 term->vt.attrs.bg_src = COLOR_DEFAULT;
291 break;
292
293 /* Bright foreground colors */
294 case 90:
295 case 91:
296 case 92:
297 case 93:
298 case 94:
299 case 95:
300 case 96:
301 case 97:
302 term->vt.attrs.fg_src = COLOR_BASE16;
303 term->vt.attrs.fg = term->colors.table[param - 90 + 8];
304 break;
305
306 /* Bright background colors */
307 case 100:
308 case 101:
309 case 102:
310 case 103:
311 case 104:
312 case 105:
313 case 106:
314 case 107:
315 term->vt.attrs.bg_src = COLOR_BASE16;
316 term->vt.attrs.bg = term->colors.table[param - 100 + 8];
317 break;
318
319 default:
320 UNHANDLED_SGR(i);
321 break;
322 }
323 }
324 }
325
326 static void
decset_decrst(struct terminal * term,unsigned param,bool enable)327 decset_decrst(struct terminal *term, unsigned param, bool enable)
328 {
329 #if defined(_DEBUG)
330 /* For UNHANDLED() */
331 int UNUSED final = enable ? 'h' : 'l';
332 #endif
333
334 /* Note: update XTSAVE/XTRESTORE if adding/removing things here */
335
336 switch (param) {
337 case 1:
338 /* DECCKM */
339 term->cursor_keys_mode =
340 enable ? CURSOR_KEYS_APPLICATION : CURSOR_KEYS_NORMAL;
341 break;
342
343 case 3:
344 /* DECCOLM */
345 if (enable)
346 LOG_WARN("unimplemented: 132 column mode (DECCOLM)");
347
348 term_erase(
349 term,
350 &(struct coord){0, 0},
351 &(struct coord){term->cols - 1, term->rows - 1});
352 term_cursor_home(term);
353 break;
354
355 case 4:
356 /* DECSCLM - Smooth scroll */
357 if (enable)
358 LOG_WARN("unimplemented: Smooth (Slow) Scroll (DECSCLM)");
359 break;
360
361 case 5:
362 /* DECSCNM */
363 term->reverse = enable;
364 term_damage_all(term);
365 term_damage_margins(term);
366 break;
367
368 case 6: {
369 /* DECOM */
370 term->origin = enable ? ORIGIN_RELATIVE : ORIGIN_ABSOLUTE;
371 term_cursor_home(term);
372 break;
373 }
374
375 case 7:
376 /* DECAWM */
377 term->auto_margin = enable;
378 term->grid->cursor.lcf = false;
379 break;
380
381 case 9:
382 if (enable)
383 LOG_WARN("unimplemented: X10 mouse tracking mode");
384 #if 0
385 else if (term->mouse_tracking == MOUSE_X10)
386 term->mouse_tracking = MOUSE_NONE;
387 #endif
388 break;
389
390 case 12:
391 term->cursor_blink.decset = enable;
392 term_cursor_blink_update(term);
393 break;
394
395 case 25:
396 /* DECTCEM */
397 term->hide_cursor = !enable;
398 break;
399
400 case 45:
401 term->reverse_wrap = enable;
402 break;
403
404 case 80:
405 term->sixel.scrolling = !enable;
406 break;
407
408 case 1000:
409 if (enable)
410 term->mouse_tracking = MOUSE_CLICK;
411 else if (term->mouse_tracking == MOUSE_CLICK)
412 term->mouse_tracking = MOUSE_NONE;
413 term_xcursor_update(term);
414 break;
415
416 case 1001:
417 if (enable)
418 LOG_WARN("unimplemented: highlight mouse tracking");
419 break;
420
421 case 1002:
422 if (enable)
423 term->mouse_tracking = MOUSE_DRAG;
424 else if (term->mouse_tracking == MOUSE_DRAG)
425 term->mouse_tracking = MOUSE_NONE;
426 term_xcursor_update(term);
427 break;
428
429 case 1003:
430 if (enable)
431 term->mouse_tracking = MOUSE_MOTION;
432 else if (term->mouse_tracking == MOUSE_MOTION)
433 term->mouse_tracking = MOUSE_NONE;
434 term_xcursor_update(term);
435 break;
436
437 case 1004:
438 term->focus_events = enable;
439 break;
440
441 case 1005:
442 if (enable)
443 LOG_WARN("unimplemented: mouse reporting mode: UTF-8");
444 #if 0
445 else if (term->mouse_reporting == MOUSE_UTF8)
446 term->mouse_reporting = MOUSE_NONE;
447 #endif
448 break;
449
450 case 1006:
451 if (enable)
452 term->mouse_reporting = MOUSE_SGR;
453 else if (term->mouse_reporting == MOUSE_SGR)
454 term->mouse_reporting = MOUSE_NORMAL;
455 break;
456
457 case 1007:
458 term->alt_scrolling = enable;
459 break;
460
461 case 1015:
462 if (enable)
463 term->mouse_reporting = MOUSE_URXVT;
464 else if (term->mouse_reporting == MOUSE_URXVT)
465 term->mouse_reporting = MOUSE_NORMAL;
466 break;
467
468 case 1034:
469 /* smm */
470 LOG_DBG("%s 8-bit meta mode", enable ? "enabling" : "disabling");
471 term->meta.eight_bit = enable;
472 break;
473
474 case 1035:
475 /* numLock */
476 LOG_DBG("%s Num Lock modifier", enable ? "enabling" : "disabling");
477 term->num_lock_modifier = enable;
478 break;
479
480 case 1036:
481 /* metaSendsEscape */
482 LOG_DBG("%s meta-sends-escape", enable ? "enabling" : "disabling");
483 term->meta.esc_prefix = enable;
484 break;
485
486 case 1042:
487 term->bell_action_enabled = enable;
488 break;
489
490 #if 0
491 case 1043:
492 LOG_WARN("unimplemented: raise window on ctrl-g");
493 break;
494 #endif
495
496 case 1048:
497 if (enable)
498 term_save_cursor(term);
499 else
500 term_restore_cursor(term, &term->grid->saved_cursor);
501 break;
502
503 case 47:
504 case 1047:
505 case 1049:
506 if (enable && term->grid != &term->alt) {
507 selection_cancel(term);
508
509 if (param == 1049)
510 term_save_cursor(term);
511
512 term->grid = &term->alt;
513
514 /* Cursor retains its position from the normal grid */
515 term_cursor_to(
516 term,
517 min(term->normal.cursor.point.row, term->rows - 1),
518 min(term->normal.cursor.point.col, term->cols - 1));
519
520 tll_free(term->normal.scroll_damage);
521 term_erase(
522 term,
523 &(struct coord){0, 0},
524 &(struct coord){term->cols - 1, term->rows - 1});
525 }
526
527 else if (!enable && term->grid == &term->alt) {
528 selection_cancel(term);
529
530 term->grid = &term->normal;
531
532 /* Cursor retains its position from the alt grid */
533 term_cursor_to(
534 term, min(term->alt.cursor.point.row, term->rows - 1),
535 min(term->alt.cursor.point.col, term->cols - 1));
536
537 if (param == 1049)
538 term_restore_cursor(term, &term->grid->saved_cursor);
539
540 /* Delete all sixel images on the alt screen */
541 tll_foreach(term->alt.sixel_images, it) {
542 sixel_destroy(&it->item);
543 tll_remove(term->alt.sixel_images, it);
544 }
545
546 tll_free(term->alt.scroll_damage);
547 term_damage_all(term);
548 }
549 term_update_ascii_printer(term);
550 break;
551
552 case 1070:
553 term->sixel.use_private_palette = enable;
554 break;
555
556 case 2004:
557 term->bracketed_paste = enable;
558 break;
559
560 case 2026:
561 if (enable)
562 term_enable_app_sync_updates(term);
563 else
564 term_disable_app_sync_updates(term);
565 break;
566
567 case 8452:
568 term->sixel.cursor_right_of_graphics = enable;
569 break;
570
571 case 27127:
572 term->modify_escape_key = enable;
573 break;
574
575 case 737769:
576 if (enable)
577 term_ime_enable(term);
578 else
579 term_ime_disable(term);
580 break;
581
582 default:
583 UNHANDLED();
584 break;
585 }
586 }
587
588 static void
decset(struct terminal * term,unsigned param)589 decset(struct terminal *term, unsigned param)
590 {
591 decset_decrst(term, param, true);
592 }
593
594 static void
decrst(struct terminal * term,unsigned param)595 decrst(struct terminal *term, unsigned param)
596 {
597 decset_decrst(term, param, false);
598 }
599
600 static bool
decrqm(const struct terminal * term,unsigned param,bool * enabled)601 decrqm(const struct terminal *term, unsigned param, bool *enabled)
602 {
603 switch (param) {
604 case 1: *enabled = term->cursor_keys_mode == CURSOR_KEYS_APPLICATION; return true;
605 case 3: *enabled = false; return true;
606 case 4: *enabled = false; return true;
607 case 5: *enabled = term->reverse; return true;
608 case 6: *enabled = term->origin; return true;
609 case 7: *enabled = term->auto_margin; return true;
610 case 9: *enabled = false; /* term->mouse_tracking == MOUSE_X10; */ return true;
611 case 12: *enabled = term->cursor_blink.decset; return true;
612 case 25: *enabled = !term->hide_cursor; return true;
613 case 45: *enabled = term->reverse_wrap; return true;
614 case 80: *enabled = !term->sixel.scrolling; return true;
615 case 1000: *enabled = term->mouse_tracking == MOUSE_CLICK; return true;
616 case 1001: *enabled = false; return true;
617 case 1002: *enabled = term->mouse_tracking == MOUSE_DRAG; return true;
618 case 1003: *enabled = term->mouse_tracking == MOUSE_MOTION; return true;
619 case 1004: *enabled = term->focus_events; return true;
620 case 1005: *enabled = false; /* term->mouse_reporting == MOUSE_UTF8; */ return true;
621 case 1006: *enabled = term->mouse_reporting == MOUSE_SGR; return true;
622 case 1007: *enabled = term->alt_scrolling; return true;
623 case 1015: *enabled = term->mouse_reporting == MOUSE_URXVT; return true;
624 case 1034: *enabled = term->meta.eight_bit; return true;
625 case 1035: *enabled = term->num_lock_modifier; return true;
626 case 1036: *enabled = term->meta.esc_prefix; return true;
627 case 1042: *enabled = term->bell_action_enabled; return true;
628 case 47: /* FALLTHROUGH */
629 case 1047: /* FALLTHROUGH */
630 case 1049: *enabled = term->grid == &term->alt; return true;
631 case 1079: *enabled = term->sixel.use_private_palette; return true;
632 case 2004: *enabled = term->bracketed_paste; return true;
633 case 2026: *enabled = term->render.app_sync_updates.enabled; return true;
634 case 8452: *enabled = term->sixel.cursor_right_of_graphics; return true;
635 case 27127: *enabled = term->modify_escape_key; return true;
636 case 737769: *enabled = term_ime_is_enabled(term); return true;
637 }
638
639 return false;
640 }
641
642 static void
xtsave(struct terminal * term,unsigned param)643 xtsave(struct terminal *term, unsigned param)
644 {
645 switch (param) {
646 case 1: term->xtsave.application_cursor_keys = term->cursor_keys_mode == CURSOR_KEYS_APPLICATION; break;
647 case 3: break;
648 case 4: break;
649 case 5: term->xtsave.reverse = term->reverse; break;
650 case 6: term->xtsave.origin = term->origin; break;
651 case 7: term->xtsave.auto_margin = term->auto_margin; break;
652 case 9: /* term->xtsave.mouse_x10 = term->mouse_tracking == MOUSE_X10; */ break;
653 case 12: term->xtsave.cursor_blink = term->cursor_blink.decset; break;
654 case 25: term->xtsave.show_cursor = !term->hide_cursor; break;
655 case 45: term->xtsave.reverse_wrap = term->reverse_wrap; break;
656 case 47: term->xtsave.alt_screen = term->grid == &term->alt; break;
657 case 80: term->xtsave.sixel_display_mode = !term->sixel.scrolling; break;
658 case 1000: term->xtsave.mouse_click = term->mouse_tracking == MOUSE_CLICK; break;
659 case 1001: break;
660 case 1002: term->xtsave.mouse_drag = term->mouse_tracking == MOUSE_DRAG; break;
661 case 1003: term->xtsave.mouse_motion = term->mouse_tracking == MOUSE_MOTION; break;
662 case 1004: term->xtsave.focus_events = term->focus_events; break;
663 case 1005: /* term->xtsave.mouse_utf8 = term->mouse_reporting == MOUSE_UTF8; */ break;
664 case 1006: term->xtsave.mouse_sgr = term->mouse_reporting == MOUSE_SGR; break;
665 case 1007: term->xtsave.alt_scrolling = term->alt_scrolling; break;
666 case 1015: term->xtsave.mouse_urxvt = term->mouse_reporting == MOUSE_URXVT; break;
667 case 1034: term->xtsave.meta_eight_bit = term->meta.eight_bit; break;
668 case 1035: term->xtsave.num_lock_modifier = term->num_lock_modifier; break;
669 case 1036: term->xtsave.meta_esc_prefix = term->meta.esc_prefix; break;
670 case 1042: term->xtsave.bell_action_enabled = term->bell_action_enabled; break;
671 case 1047: term->xtsave.alt_screen = term->grid == &term->alt; break;
672 case 1048: term_save_cursor(term); break;
673 case 1049: term->xtsave.alt_screen = term->grid == &term->alt; break;
674 case 1070: term->xtsave.sixel_private_palette = term->sixel.use_private_palette; break;
675 case 2004: term->xtsave.bracketed_paste = term->bracketed_paste; break;
676 case 2026: term->xtsave.app_sync_updates = term->render.app_sync_updates.enabled; break;
677 case 8452: term->xtsave.sixel_cursor_right_of_graphics = term->sixel.cursor_right_of_graphics; break;
678 case 27127: term->xtsave.modify_escape_key = term->modify_escape_key; break;
679 case 737769: term->xtsave.ime = term_ime_is_enabled(term); break;
680 }
681 }
682
683 static void
xtrestore(struct terminal * term,unsigned param)684 xtrestore(struct terminal *term, unsigned param)
685 {
686 bool enable;
687 switch (param) {
688 case 1: enable = term->xtsave.application_cursor_keys; break;
689 case 3: return;
690 case 4: return;
691 case 5: enable = term->xtsave.reverse; break;
692 case 6: enable = term->xtsave.origin; break;
693 case 7: enable = term->xtsave.auto_margin; break;
694 case 9: /* enable = term->xtsave.mouse_x10; break; */ return;
695 case 12: enable = term->xtsave.cursor_blink; break;
696 case 25: enable = term->xtsave.show_cursor; break;
697 case 45: enable = term->xtsave.reverse_wrap; break;
698 case 47: enable = term->xtsave.alt_screen; break;
699 case 80: enable = term->xtsave.sixel_display_mode; break;
700 case 1000: enable = term->xtsave.mouse_click; break;
701 case 1001: return;
702 case 1002: enable = term->xtsave.mouse_drag; break;
703 case 1003: enable = term->xtsave.mouse_motion; break;
704 case 1004: enable = term->xtsave.focus_events; break;
705 case 1005: /* enable = term->xtsave.mouse_utf8; break; */ return;
706 case 1006: enable = term->xtsave.mouse_sgr; break;
707 case 1007: enable = term->xtsave.alt_scrolling; break;
708 case 1015: enable = term->xtsave.mouse_urxvt; break;
709 case 1034: enable = term->xtsave.meta_eight_bit; break;
710 case 1035: enable = term->xtsave.num_lock_modifier; break;
711 case 1036: enable = term->xtsave.meta_esc_prefix; break;
712 case 1042: enable = term->xtsave.bell_action_enabled; break;
713 case 1047: enable = term->xtsave.alt_screen; break;
714 case 1048: enable = true; break;
715 case 1049: enable = term->xtsave.alt_screen; break;
716 case 1070: enable = term->xtsave.sixel_private_palette; break;
717 case 2004: enable = term->xtsave.bracketed_paste; break;
718 case 2026: enable = term->xtsave.app_sync_updates; break;
719 case 8452: enable = term->xtsave.sixel_cursor_right_of_graphics; break;
720 case 27127: enable = term->xtsave.modify_escape_key; break;
721 case 737769: enable = term->xtsave.ime; break;
722
723 default: return;
724 }
725
726 decset_decrst(term, param, enable);
727 }
728
729 void
csi_dispatch(struct terminal * term,uint8_t final)730 csi_dispatch(struct terminal *term, uint8_t final)
731 {
732 LOG_DBG("%s (%08x)", csi_as_string(term, final, -1), term->vt.private);
733
734 switch (term->vt.private) {
735 case 0: {
736 switch (final) {
737 case 'b':
738 if (term->vt.last_printed != 0) {
739 /*
740 * Note: we never reset 'last-printed'. According to
741 * ECMA-48, the behaviour is undefined if REP was
742 * _not_ preceded by a graphical character.
743 */
744 int count = vt_param_get(term, 0, 1);
745 LOG_DBG("REP: '%lc' %d times", (wint_t)term->vt.last_printed, count);
746
747 const int width = wcwidth(term->vt.last_printed);
748 if (width > 0) {
749 for (int i = 0; i < count; i++)
750 term_print(term, term->vt.last_printed, width);
751 }
752 }
753 break;
754
755 case 'c': {
756 if (vt_param_get(term, 0, 0) != 0) {
757 UNHANDLED();
758 break;
759 }
760
761 /* Send Device Attributes (Primary DA) */
762
763 /*
764 * Responses:
765 * - CSI?1;2c vt100 with advanced video option
766 * - CSI?1;0c vt101 with no options
767 * - CSI?6c vt102
768 * - CSI?62;<Ps>c vt220
769 * - CSI?63;<Ps>c vt320
770 * - CSI?64;<Ps>c vt420
771 *
772 * Ps (response may contain multiple):
773 * - 1 132 columns
774 * - 2 Printer.
775 * - 3 ReGIS graphics.
776 * - 4 Sixel graphics.
777 * - 6 Selective erase.
778 * - 8 User-defined keys.
779 * - 9 National Replacement Character sets.
780 * - 15 Technical characters.
781 * - 16 Locator port.
782 * - 17 Terminal state interrogation.
783 * - 18 User windows.
784 * - 21 Horizontal scrolling.
785 * - 22 ANSI color, e.g., VT525.
786 * - 28 Rectangular editing.
787 * - 29 ANSI text locator (i.e., DEC Locator mode).
788 *
789 * Note: we report ourselves as a VT220, mainly to be able
790 * to pass parameters, to indicate we support sixel, and
791 * ANSI colors.
792 *
793 * The VT level must be synchronized with the secondary DA
794 * response.
795 *
796 * Note: tertiary DA responds with "FOOT".
797 */
798 static const char reply[] = "\033[?62;4;22c";
799 term_to_slave(term, reply, sizeof(reply) - 1);
800 break;
801 }
802
803 case 'd': {
804 /* VPA - vertical line position absolute */
805 int rel_row = vt_param_get(term, 0, 1) - 1;
806 int row = term_row_rel_to_abs(term, rel_row);
807 term_cursor_to(term, row, term->grid->cursor.point.col);
808 break;
809 }
810
811 case 'm':
812 csi_sgr(term);
813 break;
814
815 case 'A':
816 term_cursor_up(term, vt_param_get(term, 0, 1));
817 break;
818
819 case 'e':
820 case 'B':
821 term_cursor_down(term, vt_param_get(term, 0, 1));
822 break;
823
824 case 'a':
825 case 'C':
826 term_cursor_right(term, vt_param_get(term, 0, 1));
827 break;
828
829 case 'D':
830 term_cursor_left(term, vt_param_get(term, 0, 1));
831 break;
832
833 case 'E':
834 /* CNL - Cursor Next Line */
835 term_cursor_down(term, vt_param_get(term, 0, 1));
836 term_cursor_left(term, term->grid->cursor.point.col);
837 break;
838
839 case 'F':
840 /* CPL - Cursor Previous Line */
841 term_cursor_up(term, vt_param_get(term, 0, 1));
842 term_cursor_left(term, term->grid->cursor.point.col);
843 break;
844
845 case 'g': {
846 int param = vt_param_get(term, 0, 0);
847 switch (param) {
848 case 0:
849 /* Clear tab stop at *current* column */
850 tll_foreach(term->tab_stops, it) {
851 if (it->item == term->grid->cursor.point.col)
852 tll_remove(term->tab_stops, it);
853 else if (it->item > term->grid->cursor.point.col)
854 break;
855 }
856
857 break;
858
859 case 3:
860 /* Clear *all* tabs */
861 tll_free(term->tab_stops);
862 break;
863
864 default:
865 UNHANDLED();
866 break;
867 }
868 break;
869 }
870
871 case '`':
872 case 'G': {
873 /* Cursor horizontal absolute */
874 int col = min(vt_param_get(term, 0, 1), term->cols) - 1;
875 term_cursor_to(term, term->grid->cursor.point.row, col);
876 break;
877 }
878
879 case 'f':
880 case 'H': {
881 /* Move cursor */
882 int rel_row = vt_param_get(term, 0, 1) - 1;
883 int row = term_row_rel_to_abs(term, rel_row);
884 int col = min(vt_param_get(term, 1, 1), term->cols) - 1;
885 term_cursor_to(term, row, col);
886 break;
887 }
888
889 case 'J': {
890 /* Erase screen */
891
892 int param = vt_param_get(term, 0, 0);
893 switch (param) {
894 case 0:
895 /* From cursor to end of screen */
896 term_erase(
897 term,
898 &term->grid->cursor.point,
899 &(struct coord){term->cols - 1, term->rows - 1});
900 term->grid->cursor.lcf = false;
901 break;
902
903 case 1:
904 /* From start of screen to cursor */
905 term_erase(term, &(struct coord){0, 0}, &term->grid->cursor.point);
906 term->grid->cursor.lcf = false;
907 break;
908
909 case 2:
910 /* Erase entire screen */
911 term_erase(
912 term,
913 &(struct coord){0, 0},
914 &(struct coord){term->cols - 1, term->rows - 1});
915 term->grid->cursor.lcf = false;
916 break;
917
918 case 3: {
919 /* Erase scrollback */
920 term_erase_scrollback(term);
921 break;
922 }
923
924 default:
925 UNHANDLED();
926 break;
927 }
928 break;
929 }
930
931 case 'K': {
932 /* Erase line */
933
934 int param = vt_param_get(term, 0, 0);
935 switch (param) {
936 case 0:
937 /* From cursor to end of line */
938 term_erase(
939 term,
940 &term->grid->cursor.point,
941 &(struct coord){term->cols - 1, term->grid->cursor.point.row});
942 term->grid->cursor.lcf = false;
943 break;
944
945 case 1:
946 /* From start of line to cursor */
947 term_erase(
948 term, &(struct coord){0, term->grid->cursor.point.row}, &term->grid->cursor.point);
949 term->grid->cursor.lcf = false;
950 break;
951
952 case 2:
953 /* Entire line */
954 term_erase(
955 term,
956 &(struct coord){0, term->grid->cursor.point.row},
957 &(struct coord){term->cols - 1, term->grid->cursor.point.row});
958 term->grid->cursor.lcf = false;
959 break;
960
961 default:
962 UNHANDLED();
963 break;
964 }
965
966 break;
967 }
968
969 case 'L': {
970 if (term->grid->cursor.point.row < term->scroll_region.start ||
971 term->grid->cursor.point.row >= term->scroll_region.end)
972 break;
973
974 int count = min(
975 vt_param_get(term, 0, 1),
976 term->scroll_region.end - term->grid->cursor.point.row);
977
978 term_scroll_reverse_partial(
979 term,
980 (struct scroll_region){
981 .start = term->grid->cursor.point.row,
982 .end = term->scroll_region.end},
983 count);
984 term->grid->cursor.lcf = false;
985 term->grid->cursor.point.col = 0;
986 break;
987 }
988
989 case 'M': {
990 if (term->grid->cursor.point.row < term->scroll_region.start ||
991 term->grid->cursor.point.row >= term->scroll_region.end)
992 break;
993
994 int count = min(
995 vt_param_get(term, 0, 1),
996 term->scroll_region.end - term->grid->cursor.point.row);
997
998 term_scroll_partial(
999 term,
1000 (struct scroll_region){
1001 .start = term->grid->cursor.point.row,
1002 .end = term->scroll_region.end},
1003 count);
1004 term->grid->cursor.lcf = false;
1005 term->grid->cursor.point.col = 0;
1006 break;
1007 }
1008
1009 case 'P': {
1010 /* DCH: Delete character(s) */
1011
1012 /* Number of characters to delete */
1013 int count = min(
1014 vt_param_get(term, 0, 1), term->cols - term->grid->cursor.point.col);
1015
1016 /* Number of characters left after deletion (on current line) */
1017 int remaining = term->cols - (term->grid->cursor.point.col + count);
1018
1019 /* 'Delete' characters by moving the remaining ones */
1020 memmove(&term->grid->cur_row->cells[term->grid->cursor.point.col],
1021 &term->grid->cur_row->cells[term->grid->cursor.point.col + count],
1022 remaining * sizeof(term->grid->cur_row->cells[0]));
1023
1024 for (size_t c = 0; c < remaining; c++)
1025 term->grid->cur_row->cells[term->grid->cursor.point.col + c].attrs.clean = 0;
1026 term->grid->cur_row->dirty = true;
1027
1028 /* Erase the remainder of the line */
1029 term_erase(
1030 term,
1031 &(struct coord){term->grid->cursor.point.col + remaining, term->grid->cursor.point.row},
1032 &(struct coord){term->cols - 1, term->grid->cursor.point.row});
1033 term->grid->cursor.lcf = false;
1034 break;
1035 }
1036
1037 case '@': {
1038 /* ICH: insert character(s) */
1039
1040 /* Number of characters to insert */
1041 int count = min(
1042 vt_param_get(term, 0, 1), term->cols - term->grid->cursor.point.col);
1043
1044 /* Characters to move */
1045 int remaining = term->cols - (term->grid->cursor.point.col + count);
1046
1047 /* Push existing characters */
1048 memmove(&term->grid->cur_row->cells[term->grid->cursor.point.col + count],
1049 &term->grid->cur_row->cells[term->grid->cursor.point.col],
1050 remaining * sizeof(term->grid->cur_row->cells[0]));
1051 for (size_t c = 0; c < remaining; c++)
1052 term->grid->cur_row->cells[term->grid->cursor.point.col + count + c].attrs.clean = 0;
1053 term->grid->cur_row->dirty = true;
1054
1055 /* Erase (insert space characters) */
1056 term_erase(
1057 term,
1058 &term->grid->cursor.point,
1059 &(struct coord){term->grid->cursor.point.col + count - 1, term->grid->cursor.point.row});
1060 term->grid->cursor.lcf = false;
1061 break;
1062 }
1063
1064 case 'S': {
1065 const struct scroll_region *r = &term->scroll_region;
1066 int amount = min(vt_param_get(term, 0, 1), r->end - r->start);
1067 term_scroll(term, amount);
1068 break;
1069 }
1070
1071 case 'T': {
1072 const struct scroll_region *r = &term->scroll_region;
1073 int amount = min(vt_param_get(term, 0, 1), r->end - r->start);
1074 term_scroll_reverse(term, amount);
1075 break;
1076 }
1077
1078 case 'X': {
1079 /* Erase chars */
1080 int count = min(
1081 vt_param_get(term, 0, 1), term->cols - term->grid->cursor.point.col);
1082
1083 term_erase(
1084 term,
1085 &term->grid->cursor.point,
1086 &(struct coord){term->grid->cursor.point.col + count - 1, term->grid->cursor.point.row});
1087 term->grid->cursor.lcf = false;
1088 break;
1089 }
1090
1091 case 'I': {
1092 /* CHT - Tab Forward (param is number of tab stops to move through) */
1093 for (int i = 0; i < vt_param_get(term, 0, 1); i++) {
1094 int new_col = term->cols - 1;
1095 tll_foreach(term->tab_stops, it) {
1096 if (it->item > term->grid->cursor.point.col) {
1097 new_col = it->item;
1098 break;
1099 }
1100 }
1101 xassert(new_col >= term->grid->cursor.point.col);
1102
1103 bool lcf = term->grid->cursor.lcf;
1104 term_cursor_right(term, new_col - term->grid->cursor.point.col);
1105 term->grid->cursor.lcf = lcf;
1106 }
1107 break;
1108 }
1109
1110 case 'Z':
1111 /* CBT - Back tab (param is number of tab stops to move back through) */
1112 for (int i = 0; i < vt_param_get(term, 0, 1); i++) {
1113 int new_col = 0;
1114 tll_rforeach(term->tab_stops, it) {
1115 if (it->item < term->grid->cursor.point.col) {
1116 new_col = it->item;
1117 break;
1118 }
1119 }
1120 xassert(term->grid->cursor.point.col >= new_col);
1121 term_cursor_left(term, term->grid->cursor.point.col - new_col);
1122 }
1123 break;
1124
1125 case 'h':
1126 /* Set mode */
1127 switch (vt_param_get(term, 0, 0)) {
1128 case 2: /* Keyboard Action Mode - AM */
1129 LOG_WARN("unimplemented: keyboard action mode (AM)");
1130 break;
1131
1132 case 4: /* Insert Mode - IRM */
1133 term->insert_mode = true;
1134 term_update_ascii_printer(term);
1135 break;
1136
1137 case 12: /* Send/receive Mode - SRM */
1138 LOG_WARN("unimplemented: send/receive mode (SRM)");
1139 break;
1140
1141 case 20: /* Automatic Newline Mode - LNM */
1142 /* TODO: would be easy to implemented; when active
1143 * term_linefeed() would _also_ do a
1144 * term_carriage_return() */
1145 LOG_WARN("unimplemented: automatic newline mode (LNM)");
1146 break;
1147 }
1148 break;
1149
1150 case 'l':
1151 /* Reset mode */
1152 switch (vt_param_get(term, 0, 0)) {
1153 case 4: /* Insert Mode - IRM */
1154 term->insert_mode = false;
1155 term_update_ascii_printer(term);
1156 break;
1157
1158 case 2: /* Keyboard Action Mode - AM */
1159 case 12: /* Send/receive Mode - SRM */
1160 case 20: /* Automatic Newline Mode - LNM */
1161 break;
1162 }
1163 break;
1164
1165 case 'r': {
1166 int start = vt_param_get(term, 0, 1);
1167 int end = min(vt_param_get(term, 1, term->rows), term->rows);
1168
1169 if (end > start) {
1170
1171 /* 1-based */
1172 term->scroll_region.start = start - 1;
1173 term->scroll_region.end = end;
1174 term_cursor_home(term);
1175
1176 LOG_DBG("scroll region: %d-%d",
1177 term->scroll_region.start,
1178 term->scroll_region.end);
1179 }
1180 break;
1181 }
1182
1183 case 's':
1184 term_save_cursor(term);
1185 break;
1186
1187 case 'u':
1188 term_restore_cursor(term, &term->grid->saved_cursor);
1189 break;
1190
1191 case 't': {
1192 /*
1193 * Window operations
1194 */
1195
1196 const unsigned param = vt_param_get(term, 0, 0);
1197
1198 switch (param) {
1199 case 1: LOG_WARN("unimplemented: de-iconify"); break;
1200 case 2: LOG_WARN("unimplemented: iconify"); break;
1201 case 3: LOG_WARN("unimplemented: move window to pixel position"); break;
1202 case 4: LOG_WARN("unimplemented: resize window in pixels"); break;
1203 case 5: LOG_WARN("unimplemented: raise window to front of stack"); break;
1204 case 6: LOG_WARN("unimplemented: raise window to back of stack"); break;
1205 case 7: LOG_WARN("unimplemented: refresh window"); break;
1206 case 8: LOG_WARN("unimplemented: resize window in chars"); break;
1207 case 9: LOG_WARN("unimplemented: maximize/unmaximize window"); break;
1208 case 10: LOG_WARN("unimplemented: to/from full screen"); break;
1209 case 20: LOG_WARN("unimplemented: report icon label"); break;
1210 case 21: LOG_WARN("unimplemented: report window title"); break;
1211 case 24: LOG_WARN("unimplemented: resize window (DECSLPP)"); break;
1212
1213 case 11: /* report if window is iconified */
1214 /* We don't know - always report *not* iconified */
1215 /* 1=not iconified, 2=iconified */
1216 term_to_slave(term, "\033[1t", 4);
1217 break;
1218
1219 case 13: { /* report window position */
1220 /* We don't know our position - always report (0,0) */
1221 static const char reply[] = "\033[3;0;0t";
1222 switch (vt_param_get(term, 1, 0)) {
1223 case 0: /* window position */
1224 case 2: /* text area position */
1225 term_to_slave(term, reply, sizeof(reply) - 1);
1226 break;
1227
1228 default:
1229 UNHANDLED();
1230 break;
1231 }
1232
1233 break;
1234 }
1235
1236 case 14: { /* report window size in pixels */
1237 int width = -1;
1238 int height = -1;
1239
1240 switch (vt_param_get(term, 1, 0)) {
1241 case 0:
1242 /* text area size */
1243 width = term->width - term->margins.left - term->margins.right;
1244 height = term->height - term->margins.top - term->margins.bottom;
1245 break;
1246
1247 case 2:
1248 /* window size */
1249 width = term->width;
1250 height = term->height;
1251 break;
1252
1253 default:
1254 UNHANDLED();
1255 break;
1256 }
1257
1258 if (width >= 0 && height >= 0) {
1259 char reply[64];
1260 size_t n = xsnprintf(reply, sizeof(reply), "\033[4;%d;%dt",
1261 height / term->scale, width / term->scale);
1262 term_to_slave(term, reply, n);
1263 }
1264 break;
1265 }
1266
1267 case 15: /* report screen size in pixels */
1268 tll_foreach(term->window->on_outputs, it) {
1269 char reply[64];
1270 size_t n = xsnprintf(reply, sizeof(reply), "\033[5;%d;%dt",
1271 it->item->dim.px_scaled.height,
1272 it->item->dim.px_scaled.width);
1273 term_to_slave(term, reply, n);
1274 break;
1275 }
1276
1277 if (tll_length(term->window->on_outputs) == 0)
1278 term_to_slave(term, "\033[5;0;0t", 8);
1279 break;
1280
1281 case 16: { /* report cell size in pixels */
1282 char reply[64];
1283 size_t n = xsnprintf(reply, sizeof(reply), "\033[6;%d;%dt",
1284 term->cell_height / term->scale,
1285 term->cell_width / term->scale);
1286 term_to_slave(term, reply, n);
1287 break;
1288 }
1289
1290 case 18: { /* text area size in chars */
1291 char reply[64];
1292 size_t n = xsnprintf(reply, sizeof(reply), "\033[8;%d;%dt",
1293 term->rows, term->cols);
1294 term_to_slave(term, reply, n);
1295 break;
1296 }
1297
1298 case 19: { /* report screen size in chars */
1299 tll_foreach(term->window->on_outputs, it) {
1300 char reply[64];
1301 size_t n = xsnprintf(reply, sizeof(reply), "\033[9;%d;%dt",
1302 it->item->dim.px_real.height / term->cell_height / term->scale,
1303 it->item->dim.px_real.width / term->cell_width / term->scale);
1304 term_to_slave(term, reply, n);
1305 break;
1306 }
1307
1308 if (tll_length(term->window->on_outputs) == 0)
1309 term_to_slave(term, "\033[9;0;0t", 8);
1310 break;
1311 }
1312
1313 case 22: { /* push window title */
1314 /* 0 - icon + title, 1 - icon, 2 - title */
1315 unsigned what = vt_param_get(term, 1, 0);
1316 if (what == 0 || what == 2) {
1317 tll_push_back(
1318 term->window_title_stack, xstrdup(term->window_title));
1319 }
1320 break;
1321 }
1322
1323 case 23: { /* pop window title */
1324 /* 0 - icon + title, 1 - icon, 2 - title */
1325 unsigned what = vt_param_get(term, 1, 0);
1326 if (what == 0 || what == 2) {
1327 if (tll_length(term->window_title_stack) > 0) {
1328 char *title = tll_pop_back(term->window_title_stack);
1329 term_set_window_title(term, title);
1330 free(title);
1331 }
1332 }
1333 break;
1334 }
1335
1336 case 1001: {
1337 }
1338
1339 default:
1340 LOG_DBG("ignoring %s", csi_as_string(term, final, -1));
1341 break;
1342 }
1343 break;
1344 }
1345
1346 case 'n': {
1347 if (term->vt.params.idx > 0) {
1348 int param = vt_param_get(term, 0, 0);
1349 switch (param) {
1350 case 5:
1351 /* Query device status */
1352 term_to_slave(term, "\x1b[0n", 4); /* "Device OK" */
1353 break;
1354
1355 case 6: {
1356 /* u7 - cursor position query */
1357
1358 int row = term->origin == ORIGIN_ABSOLUTE
1359 ? term->grid->cursor.point.row
1360 : term->grid->cursor.point.row - term->scroll_region.start;
1361
1362 /* TODO: we use 0-based position, while the xterm
1363 * terminfo says the receiver of the reply should
1364 * decrement, hence we must add 1 */
1365 char reply[64];
1366 size_t n = xsnprintf(reply, sizeof(reply), "\x1b[%d;%dR",
1367 row + 1, term->grid->cursor.point.col + 1);
1368 term_to_slave(term, reply, n);
1369 break;
1370 }
1371
1372 default:
1373 UNHANDLED();
1374 break;
1375 }
1376 } else
1377 UNHANDLED();
1378
1379 break;
1380 }
1381
1382 default:
1383 UNHANDLED();
1384 break;
1385 }
1386
1387 break; /* private[0] == 0 */
1388 }
1389
1390 case '?': {
1391 switch (final) {
1392 case 'h':
1393 /* DECSET - DEC private mode set */
1394 for (size_t i = 0; i < term->vt.params.idx; i++)
1395 decset(term, term->vt.params.v[i].value);
1396 break;
1397
1398 case 'l':
1399 /* DECRST - DEC private mode reset */
1400 for (size_t i = 0; i < term->vt.params.idx; i++)
1401 decrst(term, term->vt.params.v[i].value);
1402 break;
1403
1404 case 's':
1405 for (size_t i = 0; i < term->vt.params.idx; i++)
1406 xtsave(term, term->vt.params.v[i].value);
1407 break;
1408
1409 case 'r':
1410 for (size_t i = 0; i < term->vt.params.idx; i++)
1411 xtrestore(term, term->vt.params.v[i].value);
1412 break;
1413
1414 case 'S': {
1415 unsigned target = vt_param_get(term, 0, 0);
1416 unsigned operation = vt_param_get(term, 1, 0);
1417
1418 switch (target) {
1419 case 1:
1420 switch (operation) {
1421 case 1: sixel_colors_report_current(term); break;
1422 case 2: sixel_colors_reset(term); break;
1423 case 3: sixel_colors_set(term, vt_param_get(term, 2, 0)); break;
1424 case 4: sixel_colors_report_max(term); break;
1425 default: UNHANDLED(); break;
1426 }
1427 break;
1428
1429 case 2:
1430 switch (operation) {
1431 case 1: sixel_geometry_report_current(term); break;
1432 case 2: sixel_geometry_reset(term); break;
1433 case 3: sixel_geometry_set(term, vt_param_get(term, 2, 0), vt_param_get(term, 3, 0)); break;
1434 case 4: sixel_geometry_report_max(term); break;
1435 default: UNHANDLED(); break;
1436 }
1437 break;
1438
1439 default:
1440 UNHANDLED();
1441 break;
1442 }
1443
1444 break;
1445 }
1446
1447 case 'u': {
1448 enum kitty_kbd_flags flags =
1449 term->grid->kitty_kbd.flags[term->grid->kitty_kbd.idx];
1450
1451 char reply[8];
1452 int chars = snprintf(reply, sizeof(reply), "\033[?%uu", flags);
1453 term_to_slave(term, reply, chars);
1454 break;
1455 }
1456
1457 default:
1458 UNHANDLED();
1459 break;
1460 }
1461
1462 break; /* private[0] == '?' */
1463 }
1464
1465 case '>': {
1466 switch (final) {
1467 case 'c':
1468 /* Send Device Attributes (Secondary DA) */
1469 if (vt_param_get(term, 0, 0) != 0) {
1470 UNHANDLED();
1471 break;
1472 }
1473
1474 /*
1475 * Param 1 - terminal type:
1476 * 0 - vt100
1477 * 1 - vt220
1478 * 2 - vt240
1479 * 18 - vt330
1480 * 19 - vt340
1481 * 24 - vt320
1482 * 41 - vt420
1483 * 61 - vt510
1484 * 64 - vt520
1485 * 65 - vt525
1486 *
1487 * Param 2 - firmware version
1488 * xterm uses its version number. We use an xterm
1489 * version number too, since e.g. Emacs uses this to
1490 * determine level of support.
1491 *
1492 * We report ourselves as a VT220. This must be
1493 * synchronized with the primary DA response.
1494 *
1495 * Note: tertiary DA replies with "FOOT".
1496 */
1497
1498 static_assert(FOOT_MAJOR < 100, "Major version must not exceed 99");
1499 static_assert(FOOT_MINOR < 100, "Minor version must not exceed 99");
1500 static_assert(FOOT_PATCH < 100, "Patch version must not exceed 99");
1501
1502 char reply[64];
1503 size_t n = xsnprintf(reply, sizeof(reply), "\033[>1;%02u%02u%02u;0c",
1504 FOOT_MAJOR, FOOT_MINOR, FOOT_PATCH);
1505
1506 term_to_slave(term, reply, n);
1507 break;
1508
1509 case 'm':
1510 if (term->vt.params.idx == 0) {
1511 /* Reset all */
1512 } else {
1513 int resource = vt_param_get(term, 0, 0);
1514 int value = vt_param_get(term, 1, -1);
1515
1516 switch (resource) {
1517 case 0: /* modifyKeyboard */
1518 break;
1519
1520 case 1: /* modifyCursorKeys */
1521 case 2: /* modifyFunctionKeys */
1522 /* Ignored, we always report modifiers */
1523 if (value != 2 && value != -1) {
1524 LOG_WARN(
1525 "unimplemented: %s = %d",
1526 resource == 1 ? "modifyCursorKeys" :
1527 resource == 2 ? "modifyFunctionKeys" : "<invalid>",
1528 value);
1529 }
1530 break;
1531
1532 case 4: /* modifyOtherKeys */
1533 term->modify_other_keys_2 = value == 2;
1534 LOG_DBG("modifyOtherKeys=%d", value);
1535 break;
1536
1537 default:
1538 LOG_WARN("invalid resource %d in %s",
1539 resource, csi_as_string(term, final, -1));
1540 break;
1541 }
1542 }
1543 break; /* final == 'm' */
1544
1545 case 'u': {
1546 int flags = vt_param_get(term, 0, 0) & KITTY_KBD_SUPPORTED;
1547
1548 struct grid *grid = term->grid;
1549 uint8_t idx = grid->kitty_kbd.idx;
1550
1551 if (idx + 1 >= ALEN(grid->kitty_kbd.flags)) {
1552 /* Stack full, evict oldest by wrapping around */
1553 idx = 0;
1554 } else
1555 idx++;
1556
1557 grid->kitty_kbd.flags[idx] = flags;
1558 grid->kitty_kbd.idx = idx;
1559
1560 LOG_DBG("kitty kbd: pushed new flags: 0x%03x", flags);
1561 break;
1562 }
1563
1564 case 'q': {
1565 /* XTVERSION */
1566 if (vt_param_get(term, 0, 0) != 0) {
1567 UNHANDLED();
1568 break;
1569 }
1570
1571 char reply[64];
1572 size_t n = xsnprintf(
1573 reply, sizeof(reply), "\033P>|foot(%u.%u.%u%s%s)\033\\",
1574 FOOT_MAJOR, FOOT_MINOR, FOOT_PATCH,
1575 FOOT_EXTRA[0] != '\0' ? "-" : "", FOOT_EXTRA);
1576 term_to_slave(term, reply, n);
1577 break;
1578 }
1579
1580 default:
1581 UNHANDLED();
1582 break;
1583 }
1584
1585 break; /* private[0] == '>' */
1586 }
1587
1588 case '<': {
1589 switch (final) {
1590 case 'u': {
1591 int count = vt_param_get(term, 0, 1);
1592 LOG_DBG("kitty kbd: popping %d levels of flags", count);
1593
1594 struct grid *grid = term->grid;
1595 uint8_t idx = grid->kitty_kbd.idx;
1596
1597 for (int i = 0; i < count; i++) {
1598 /* Reset flags. This ensures we get flags=0 when
1599 * over-popping */
1600 grid->kitty_kbd.flags[idx] = 0;
1601
1602 if (idx == 0)
1603 idx = ALEN(grid->kitty_kbd.flags) - 1;
1604 else
1605 idx--;
1606 }
1607
1608 grid->kitty_kbd.idx = idx;
1609
1610 LOG_DBG("kitty kbd: flags after pop: 0x%03x",
1611 term->grid->kitty_kbd.flags[idx]);
1612 break;
1613 }
1614 }
1615 break; /* private[0] == ‘<’ */
1616 }
1617
1618 case ' ': {
1619 switch (final) {
1620 case 'q': {
1621 int param = vt_param_get(term, 0, 0);
1622 switch (param) {
1623 case 0: /* blinking block, but we use it to reset to configured default */
1624 term->cursor_style = term->conf->cursor.style;
1625 term->cursor_blink.deccsusr = term->conf->cursor.blink;
1626 term_cursor_blink_update(term);
1627 break;
1628
1629 case 1: /* blinking block */
1630 case 2: /* steady block */
1631 term->cursor_style = CURSOR_BLOCK;
1632 break;
1633
1634 case 3: /* blinking underline */
1635 case 4: /* steady underline */
1636 term->cursor_style = CURSOR_UNDERLINE;
1637 break;
1638
1639 case 5: /* blinking bar */
1640 case 6: /* steady bar */
1641 term->cursor_style = CURSOR_BEAM;
1642 break;
1643
1644 default:
1645 UNHANDLED();
1646 break;
1647 }
1648
1649 if (param > 0 && param <= 6) {
1650 term->cursor_blink.deccsusr = param & 1;
1651 term_cursor_blink_update(term);
1652 }
1653 break;
1654 }
1655
1656 default:
1657 UNHANDLED();
1658 break;
1659 }
1660 break; /* private[0] == ' ' */
1661 }
1662
1663 case '!': {
1664 if (final == 'p') {
1665 term_reset(term, false);
1666 break;
1667 }
1668
1669 UNHANDLED();
1670 break; /* private[0] == '!' */
1671 }
1672
1673 case '=': {
1674 switch (final) {
1675 case 'c':
1676 if (vt_param_get(term, 0, 0) != 0) {
1677 UNHANDLED();
1678 break;
1679 }
1680
1681 /*
1682 * Send Device Attributes (Tertiary DA)
1683 *
1684 * Reply format is "DCS ! | DDDDDDDD ST"
1685 *
1686 * D..D is the unit ID of the terminal, consisting of four
1687 * hexadecimal pairs. The first pair represents the
1688 * manufacturing site code. This code can be any
1689 * hexadecimal value from 00 through FF.
1690 */
1691
1692 term_to_slave(term, "\033P!|464f4f54\033\\", 14); /* FOOT */
1693 break;
1694
1695 case 'u': {
1696 int flag_set = vt_param_get(term, 0, 0) & KITTY_KBD_SUPPORTED;
1697 int mode = vt_param_get(term, 1, 1);
1698
1699 struct grid *grid = term->grid;
1700 uint8_t idx = grid->kitty_kbd.idx;
1701
1702 switch (mode) {
1703 case 1:
1704 /* set bits are set, unset bits are reset */
1705 grid->kitty_kbd.flags[idx] = flag_set;
1706 break;
1707
1708 case 2:
1709 /* set bits are set, unset bits are left unchanged */
1710 grid->kitty_kbd.flags[idx] |= flag_set;
1711 break;
1712
1713 case 3:
1714 /* set bits are reset, unset bits are left unchanged */
1715 grid->kitty_kbd.flags[idx] &= ~flag_set;
1716 break;
1717
1718 default:
1719 UNHANDLED();
1720 break;
1721 }
1722
1723 LOG_DBG("kitty kbd: flags after update: 0x%03x",
1724 grid->kitty_kbd.flags[idx]);
1725 break;
1726 }
1727
1728 default:
1729 UNHANDLED();
1730 break;
1731 }
1732 break; /* private[0] == '=' */
1733 }
1734
1735 case 0x243f: /* ?$ */
1736 switch (final) {
1737 case 'p': {
1738 unsigned param = vt_param_get(term, 0, 0);
1739
1740 /*
1741 * Request DEC private mode (DECRQM)
1742 * Reply:
1743 * 0 - not recognized
1744 * 1 - set
1745 * 2 - reset
1746 * 3 - permanently set
1747 * 4 - permantently reset
1748 */
1749 bool enabled;
1750 unsigned value;
1751 if (decrqm(term, param, &enabled))
1752 value = enabled ? 1 : 2;
1753 else
1754 value = 0;
1755
1756 char reply[32];
1757 size_t n = xsnprintf(reply, sizeof(reply), "\033[?%u;%u$y", param, value);
1758 term_to_slave(term, reply, n);
1759 break;
1760
1761 }
1762
1763 default:
1764 UNHANDLED();
1765 break;
1766 }
1767
1768 break; /* private[0] == ‘?’ && private[1] == ‘$’ */
1769
1770 default:
1771 UNHANDLED();
1772 break;
1773 }
1774 }
1775