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