forked from dnkl/foot
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1680 lines
55 KiB
1680 lines
55 KiB
#include "csi.h" |
|
|
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <unistd.h> |
|
#include <errno.h> |
|
|
|
#if defined(_DEBUG) |
|
#include <stdio.h> |
|
#endif |
|
|
|
#include <sys/timerfd.h> |
|
|
|
#define LOG_MODULE "csi" |
|
#define LOG_ENABLE_DBG 0 |
|
#include "log.h" |
|
#include "config.h" |
|
#include "debug.h" |
|
#include "grid.h" |
|
#include "selection.h" |
|
#include "sixel.h" |
|
#include "util.h" |
|
#include "version.h" |
|
#include "vt.h" |
|
#include "xmalloc.h" |
|
#include "xsnprintf.h" |
|
|
|
#define UNHANDLED() LOG_DBG("unhandled: %s", csi_as_string(term, final, -1)) |
|
#define UNHANDLED_SGR(idx) LOG_DBG("unhandled: %s", csi_as_string(term, 'm', idx)) |
|
|
|
static void |
|
sgr_reset(struct terminal *term) |
|
{ |
|
memset(&term->vt.attrs, 0, sizeof(term->vt.attrs)); |
|
term->vt.attrs.fg = term->colors.fg; |
|
term->vt.attrs.bg = term->colors.bg; |
|
} |
|
|
|
static const char * |
|
csi_as_string(struct terminal *term, uint8_t final, int idx) |
|
{ |
|
static char msg[1024]; |
|
int c = snprintf(msg, sizeof(msg), "CSI: "); |
|
|
|
for (size_t i = idx >= 0 ? idx : 0; |
|
i < (idx >= 0 ? idx + 1 : term->vt.params.idx); |
|
i++) |
|
{ |
|
c += snprintf(&msg[c], sizeof(msg) - c, "%u", |
|
term->vt.params.v[i].value); |
|
|
|
for (size_t j = 0; j < term->vt.params.v[i].sub.idx; j++) { |
|
c += snprintf(&msg[c], sizeof(msg) - c, ":%u", |
|
term->vt.params.v[i].sub.value[j]); |
|
} |
|
|
|
c += snprintf(&msg[c], sizeof(msg) - c, "%s", |
|
i == term->vt.params.idx - 1 ? "" : ";"); |
|
} |
|
|
|
for (size_t i = 0; i < sizeof(term->vt.private); i++) { |
|
char value = (term->vt.private >> (i * 8)) & 0xff; |
|
if (value == 0) |
|
break; |
|
c += snprintf(&msg[c], sizeof(msg) - c, "%c", value); |
|
} |
|
|
|
snprintf(&msg[c], sizeof(msg) - c, "%c (%u parameters)", |
|
final, idx >= 0 ? 1 : term->vt.params.idx); |
|
return msg; |
|
} |
|
|
|
static void |
|
csi_sgr(struct terminal *term) |
|
{ |
|
if (term->vt.params.idx == 0) { |
|
sgr_reset(term); |
|
return; |
|
} |
|
|
|
for (size_t i = 0; i < term->vt.params.idx; i++) { |
|
const int param = term->vt.params.v[i].value; |
|
|
|
switch (param) { |
|
case 0: |
|
sgr_reset(term); |
|
break; |
|
|
|
case 1: term->vt.attrs.bold = true; break; |
|
case 2: term->vt.attrs.dim = true; break; |
|
case 3: term->vt.attrs.italic = true; break; |
|
case 4: term->vt.attrs.underline = true; break; |
|
case 5: term->vt.attrs.blink = true; break; |
|
case 6: LOG_WARN("ignored: rapid blink"); break; |
|
case 7: term->vt.attrs.reverse = true; break; |
|
case 8: term->vt.attrs.conceal = true; break; |
|
case 9: term->vt.attrs.strikethrough = true; break; |
|
|
|
case 21: break; /* double-underline, not implemented */ |
|
case 22: term->vt.attrs.bold = term->vt.attrs.dim = false; break; |
|
case 23: term->vt.attrs.italic = false; break; |
|
case 24: term->vt.attrs.underline = false; break; |
|
case 25: term->vt.attrs.blink = false; break; |
|
case 26: break; /* rapid blink, ignored */ |
|
case 27: term->vt.attrs.reverse = false; break; |
|
case 28: term->vt.attrs.conceal = false; break; |
|
case 29: term->vt.attrs.strikethrough = false; break; |
|
|
|
/* Regular foreground colors */ |
|
case 30: |
|
case 31: |
|
case 32: |
|
case 33: |
|
case 34: |
|
case 35: |
|
case 36: |
|
case 37: |
|
term->vt.attrs.have_fg = 1; |
|
term->vt.attrs.fg = term->colors.table[param - 30]; |
|
break; |
|
|
|
case 38: { |
|
/* Indexed: 38;5;<idx> */ |
|
if (term->vt.params.idx - i - 1 >= 2 && |
|
term->vt.params.v[i + 1].value == 5) |
|
{ |
|
uint8_t idx = term->vt.params.v[i + 2].value; |
|
term->vt.attrs.have_fg = 1; |
|
term->vt.attrs.fg = term->colors.table[idx]; |
|
i += 2; |
|
|
|
} |
|
|
|
/* RGB: 38;2;<r>;<g>;<b> */ |
|
else if (term->vt.params.idx - i - 1 >= 4 && |
|
term->vt.params.v[i + 1].value == 2) |
|
{ |
|
uint8_t r = term->vt.params.v[i + 2].value; |
|
uint8_t g = term->vt.params.v[i + 3].value; |
|
uint8_t b = term->vt.params.v[i + 4].value; |
|
term->vt.attrs.have_fg = 1; |
|
term->vt.attrs.fg = r << 16 | g << 8 | b; |
|
i += 4; |
|
} |
|
|
|
/* Indexed: 38:5:<idx> */ |
|
else if (term->vt.params.v[i].sub.idx >= 2 && |
|
term->vt.params.v[i].sub.value[0] == 5) |
|
{ |
|
const struct vt_param *param = &term->vt.params.v[i]; |
|
|
|
uint8_t idx = param->sub.value[1]; |
|
term->vt.attrs.have_fg = 1; |
|
term->vt.attrs.fg = term->colors.table[idx]; |
|
} |
|
|
|
/* |
|
* RGB: 38:2:<color-space>:r:g:b[:ignored:tolerance:tolerance-color-space] |
|
* RGB: 38:2:r:g:b |
|
* |
|
* The second version is a "bastard" version - many |
|
* programs "forget" the color space ID |
|
* parameter... *sigh* |
|
*/ |
|
else if (term->vt.params.v[i].sub.idx >= 4 && |
|
term->vt.params.v[i].sub.value[0] == 2) |
|
{ |
|
const struct vt_param *param = &term->vt.params.v[i]; |
|
bool have_color_space_id = param->sub.idx >= 5; |
|
|
|
/* 0 - color space (ignored) */ |
|
int r_idx = 2 - !have_color_space_id; |
|
int g_idx = 3 - !have_color_space_id; |
|
int b_idx = 4 - !have_color_space_id; |
|
/* 5 - unused */ |
|
/* 6 - CS tolerance */ |
|
/* 7 - color space associated with tolerance */ |
|
|
|
uint8_t r = param->sub.value[r_idx]; |
|
uint8_t g = param->sub.value[g_idx]; |
|
uint8_t b = param->sub.value[b_idx]; |
|
|
|
term->vt.attrs.have_fg = 1; |
|
term->vt.attrs.fg = r << 16 | g << 8 | b; |
|
} |
|
|
|
/* Transparent: 38:1 */ |
|
/* CMY: 38:3:<color-space>:c:m:y[:tolerance:tolerance-color-space] */ |
|
/* CMYK: 38:4:<color-space>:c:m:y:k[:tolerance:tolerance-color-space] */ |
|
|
|
/* Unrecognized */ |
|
else |
|
UNHANDLED_SGR(i); |
|
|
|
break; |
|
} |
|
|
|
case 39: |
|
term->vt.attrs.have_fg = 0; |
|
break; |
|
|
|
/* Regular background colors */ |
|
case 40: |
|
case 41: |
|
case 42: |
|
case 43: |
|
case 44: |
|
case 45: |
|
case 46: |
|
case 47: |
|
term->vt.attrs.have_bg = 1; |
|
term->vt.attrs.bg = term->colors.table[param - 40]; |
|
break; |
|
|
|
case 48: { |
|
/* Indexed: 48;5;<idx> */ |
|
if (term->vt.params.idx - i - 1 >= 2 && |
|
term->vt.params.v[i + 1].value == 5) |
|
{ |
|
uint8_t idx = term->vt.params.v[i + 2].value; |
|
term->vt.attrs.have_bg = 1; |
|
term->vt.attrs.bg = term->colors.table[idx]; |
|
i += 2; |
|
|
|
} |
|
|
|
/* RGB: 48;2;<r>;<g>;<b> */ |
|
else if (term->vt.params.idx - i - 1 >= 4 && |
|
term->vt.params.v[i + 1].value == 2) |
|
{ |
|
uint8_t r = term->vt.params.v[i + 2].value; |
|
uint8_t g = term->vt.params.v[i + 3].value; |
|
uint8_t b = term->vt.params.v[i + 4].value; |
|
term->vt.attrs.have_bg = 1; |
|
term->vt.attrs.bg = r << 16 | g << 8 | b; |
|
i += 4; |
|
} |
|
|
|
/* Indexed: 48:5:<idx> */ |
|
else if (term->vt.params.v[i].sub.idx >= 2 && |
|
term->vt.params.v[i].sub.value[0] == 5) |
|
{ |
|
const struct vt_param *param = &term->vt.params.v[i]; |
|
|
|
uint8_t idx = param->sub.value[1]; |
|
term->vt.attrs.have_bg = 1; |
|
term->vt.attrs.bg = term->colors.table[idx]; |
|
} |
|
|
|
/* |
|
* RGB: 48:2:<color-space>:r:g:b[:ignored:tolerance:tolerance-color-space] |
|
* RGB: 48:2:r:g:b |
|
* |
|
* The second version is a "bastard" version - many |
|
* programs "forget" the color space ID |
|
* parameter... *sigh* |
|
*/ |
|
else if (term->vt.params.v[i].sub.idx >= 4 && |
|
term->vt.params.v[i].sub.value[0] == 2) |
|
{ |
|
const struct vt_param *param = &term->vt.params.v[i]; |
|
bool have_color_space_id = param->sub.idx >= 5; |
|
|
|
/* 0 - color space (ignored) */ |
|
int r_idx = 2 - !have_color_space_id; |
|
int g_idx = 3 - !have_color_space_id; |
|
int b_idx = 4 - !have_color_space_id; |
|
/* 5 - unused */ |
|
/* 6 - CS tolerance */ |
|
/* 7 - color space associated with tolerance */ |
|
|
|
uint8_t r = param->sub.value[r_idx]; |
|
uint8_t g = param->sub.value[g_idx]; |
|
uint8_t b = param->sub.value[b_idx]; |
|
|
|
term->vt.attrs.have_bg = 1; |
|
term->vt.attrs.bg = r << 16 | g << 8 | b; |
|
} |
|
|
|
/* Transparent: 48:1 */ |
|
/* CMY: 48:3:<color-space>:c:m:y[:tolerance:tolerance-color-space] */ |
|
/* CMYK: 48:4:<color-space>:c:m:y:k[:tolerance:tolerance-color-space] */ |
|
|
|
else |
|
UNHANDLED_SGR(i); |
|
|
|
break; |
|
} |
|
case 49: |
|
term->vt.attrs.have_bg = 0; |
|
break; |
|
|
|
/* Bright foreground colors */ |
|
case 90: |
|
case 91: |
|
case 92: |
|
case 93: |
|
case 94: |
|
case 95: |
|
case 96: |
|
case 97: |
|
term->vt.attrs.have_fg = 1; |
|
term->vt.attrs.fg = term->colors.table[param - 90 + 8]; |
|
break; |
|
|
|
/* Bright background colors */ |
|
case 100: |
|
case 101: |
|
case 102: |
|
case 103: |
|
case 104: |
|
case 105: |
|
case 106: |
|
case 107: |
|
term->vt.attrs.have_bg = 1; |
|
term->vt.attrs.bg = term->colors.table[param - 100 + 8]; |
|
break; |
|
|
|
default: |
|
UNHANDLED_SGR(i); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
static void |
|
decset_decrst(struct terminal *term, unsigned param, bool enable) |
|
{ |
|
#if defined(_DEBUG) |
|
/* For UNHANDLED() */ |
|
int UNUSED final = enable ? 'h' : 'l'; |
|
#endif |
|
|
|
/* Note: update XTSAVE/XTRESTORE if adding/removing things here */ |
|
|
|
switch (param) { |
|
case 1: |
|
/* DECCKM */ |
|
term->cursor_keys_mode = |
|
enable ? CURSOR_KEYS_APPLICATION : CURSOR_KEYS_NORMAL; |
|
break; |
|
|
|
case 3: |
|
/* DECCOLM */ |
|
if (enable) |
|
LOG_WARN("unimplemented: 132 column mode (DECCOLM)"); |
|
|
|
term_erase( |
|
term, |
|
&(struct coord){0, 0}, |
|
&(struct coord){term->cols - 1, term->rows - 1}); |
|
term_cursor_home(term); |
|
break; |
|
|
|
case 4: |
|
/* DECSCLM - Smooth scroll */ |
|
if (enable) |
|
LOG_WARN("unimplemented: Smooth (Slow) Scroll (DECSCLM)"); |
|
break; |
|
|
|
case 5: |
|
/* DECSCNM */ |
|
term->reverse = enable; |
|
term_damage_all(term); |
|
term_damage_margins(term); |
|
break; |
|
|
|
case 6: { |
|
/* DECOM */ |
|
term->origin = enable ? ORIGIN_RELATIVE : ORIGIN_ABSOLUTE; |
|
term_cursor_home(term); |
|
break; |
|
} |
|
|
|
case 7: |
|
/* DECAWM */ |
|
term->auto_margin = enable; |
|
term->grid->cursor.lcf = false; |
|
break; |
|
|
|
case 9: |
|
if (enable) |
|
LOG_WARN("unimplemented: X10 mouse tracking mode"); |
|
#if 0 |
|
else if (term->mouse_tracking == MOUSE_X10) |
|
term->mouse_tracking = MOUSE_NONE; |
|
#endif |
|
break; |
|
|
|
case 12: |
|
term->cursor_blink.decset = enable; |
|
term_cursor_blink_update(term); |
|
break; |
|
|
|
case 25: |
|
/* DECTCEM */ |
|
term->hide_cursor = !enable; |
|
break; |
|
|
|
case 45: |
|
term->reverse_wrap = enable; |
|
break; |
|
|
|
case 80: |
|
term->sixel.scrolling = !enable; |
|
break; |
|
|
|
case 1000: |
|
if (enable) |
|
term->mouse_tracking = MOUSE_CLICK; |
|
else if (term->mouse_tracking == MOUSE_CLICK) |
|
term->mouse_tracking = MOUSE_NONE; |
|
term_xcursor_update(term); |
|
break; |
|
|
|
case 1001: |
|
if (enable) |
|
LOG_WARN("unimplemented: highlight mouse tracking"); |
|
break; |
|
|
|
case 1002: |
|
if (enable) |
|
term->mouse_tracking = MOUSE_DRAG; |
|
else if (term->mouse_tracking == MOUSE_DRAG) |
|
term->mouse_tracking = MOUSE_NONE; |
|
term_xcursor_update(term); |
|
break; |
|
|
|
case 1003: |
|
if (enable) |
|
term->mouse_tracking = MOUSE_MOTION; |
|
else if (term->mouse_tracking == MOUSE_MOTION) |
|
term->mouse_tracking = MOUSE_NONE; |
|
term_xcursor_update(term); |
|
break; |
|
|
|
case 1004: |
|
term->focus_events = enable; |
|
break; |
|
|
|
case 1005: |
|
if (enable) |
|
LOG_WARN("unimplemented: mouse reporting mode: UTF-8"); |
|
#if 0 |
|
else if (term->mouse_reporting == MOUSE_UTF8) |
|
term->mouse_reporting = MOUSE_NONE; |
|
#endif |
|
break; |
|
|
|
case 1006: |
|
if (enable) |
|
term->mouse_reporting = MOUSE_SGR; |
|
else if (term->mouse_reporting == MOUSE_SGR) |
|
term->mouse_reporting = MOUSE_NORMAL; |
|
break; |
|
|
|
case 1007: |
|
term->alt_scrolling = enable; |
|
break; |
|
|
|
case 1015: |
|
if (enable) |
|
term->mouse_reporting = MOUSE_URXVT; |
|
else if (term->mouse_reporting == MOUSE_URXVT) |
|
term->mouse_reporting = MOUSE_NORMAL; |
|
break; |
|
|
|
case 1034: |
|
/* smm */ |
|
LOG_DBG("%s 8-bit meta mode", enable ? "enabling" : "disabling"); |
|
term->meta.eight_bit = enable; |
|
break; |
|
|
|
case 1035: |
|
/* numLock */ |
|
LOG_DBG("%s Num Lock modifier", enable ? "enabling" : "disabling"); |
|
term->num_lock_modifier = enable; |
|
break; |
|
|
|
case 1036: |
|
/* metaSendsEscape */ |
|
LOG_DBG("%s meta-sends-escape", enable ? "enabling" : "disabling"); |
|
term->meta.esc_prefix = enable; |
|
break; |
|
|
|
case 1042: |
|
term->bell_action_enabled = enable; |
|
break; |
|
|
|
#if 0 |
|
case 1043: |
|
LOG_WARN("unimplemented: raise window on ctrl-g"); |
|
break; |
|
#endif |
|
|
|
case 1048: |
|
if (enable) |
|
term_save_cursor(term); |
|
else |
|
term_restore_cursor(term, &term->grid->saved_cursor); |
|
break; |
|
|
|
case 47: |
|
case 1047: |
|
case 1049: |
|
if (enable && term->grid != &term->alt) { |
|
selection_cancel(term); |
|
|
|
if (param == 1049) |
|
term_save_cursor(term); |
|
|
|
term->grid = &term->alt; |
|
|
|
/* Cursor retains its position from the normal grid */ |
|
term_cursor_to( |
|
term, |
|
min(term->normal.cursor.point.row, term->rows - 1), |
|
min(term->normal.cursor.point.col, term->cols - 1)); |
|
|
|
tll_free(term->normal.scroll_damage); |
|
term_erase( |
|
term, |
|
&(struct coord){0, 0}, |
|
&(struct coord){term->cols - 1, term->rows - 1}); |
|
} |
|
|
|
else if (!enable && term->grid == &term->alt) { |
|
selection_cancel(term); |
|
|
|
term->grid = &term->normal; |
|
|
|
/* Cursor retains its position from the alt grid */ |
|
term_cursor_to( |
|
term, min(term->alt.cursor.point.row, term->rows - 1), |
|
min(term->alt.cursor.point.col, term->cols - 1)); |
|
|
|
if (param == 1049) |
|
term_restore_cursor(term, &term->grid->saved_cursor); |
|
|
|
/* Delete all sixel images on the alt screen */ |
|
tll_foreach(term->alt.sixel_images, it) { |
|
sixel_destroy(&it->item); |
|
tll_remove(term->alt.sixel_images, it); |
|
} |
|
|
|
tll_free(term->alt.scroll_damage); |
|
term_damage_all(term); |
|
} |
|
term_update_ascii_printer(term); |
|
break; |
|
|
|
case 1070: |
|
term->sixel.use_private_palette = enable; |
|
break; |
|
|
|
case 2004: |
|
term->bracketed_paste = enable; |
|
break; |
|
|
|
case 2026: |
|
if (enable) |
|
term_enable_app_sync_updates(term); |
|
else |
|
term_disable_app_sync_updates(term); |
|
break; |
|
|
|
case 8452: |
|
term->sixel.cursor_right_of_graphics = enable; |
|
break; |
|
|
|
case 27127: |
|
term->modify_escape_key = enable; |
|
break; |
|
|
|
case 737769: |
|
if (enable) |
|
term_ime_enable(term); |
|
else |
|
term_ime_disable(term); |
|
break; |
|
|
|
default: |
|
UNHANDLED(); |
|
break; |
|
} |
|
} |
|
|
|
static void |
|
decset(struct terminal *term, unsigned param) |
|
{ |
|
decset_decrst(term, param, true); |
|
} |
|
|
|
static void |
|
decrst(struct terminal *term, unsigned param) |
|
{ |
|
decset_decrst(term, param, false); |
|
} |
|
|
|
static bool |
|
decrqm(const struct terminal *term, unsigned param, bool *enabled) |
|
{ |
|
switch (param) { |
|
case 1: *enabled = term->cursor_keys_mode == CURSOR_KEYS_APPLICATION; return true; |
|
case 3: *enabled = false; return true; |
|
case 4: *enabled = false; return true; |
|
case 5: *enabled = term->reverse; return true; |
|
case 6: *enabled = term->origin; return true; |
|
case 7: *enabled = term->auto_margin; return true; |
|
case 9: *enabled = false; /* term->mouse_tracking == MOUSE_X10; */ return true; |
|
case 12: *enabled = term->cursor_blink.decset; return true; |
|
case 25: *enabled = !term->hide_cursor; return true; |
|
case 45: *enabled = term->reverse_wrap; return true; |
|
case 80: *enabled = !term->sixel.scrolling; return true; |
|
case 1000: *enabled = term->mouse_tracking == MOUSE_CLICK; return true; |
|
case 1001: *enabled = false; return true; |
|
case 1002: *enabled = term->mouse_tracking == MOUSE_DRAG; return true; |
|
case 1003: *enabled = term->mouse_tracking == MOUSE_MOTION; return true; |
|
case 1004: *enabled = term->focus_events; return true; |
|
case 1005: *enabled = false; /* term->mouse_reporting == MOUSE_UTF8; */ return true; |
|
case 1006: *enabled = term->mouse_reporting == MOUSE_SGR; return true; |
|
case 1007: *enabled = term->alt_scrolling; return true; |
|
case 1015: *enabled = term->mouse_reporting == MOUSE_URXVT; return true; |
|
case 1034: *enabled = term->meta.eight_bit; return true; |
|
case 1035: *enabled = term->num_lock_modifier; return true; |
|
case 1036: *enabled = term->meta.esc_prefix; return true; |
|
case 1042: *enabled = term->bell_action_enabled; return true; |
|
case 47: /* FALLTHROUGH */ |
|
case 1047: /* FALLTHROUGH */ |
|
case 1049: *enabled = term->grid == &term->alt; return true; |
|
case 1079: *enabled = term->sixel.use_private_palette; return true; |
|
case 2004: *enabled = term->bracketed_paste; return true; |
|
case 2026: *enabled = term->render.app_sync_updates.enabled; return true; |
|
case 8452: *enabled = term->sixel.cursor_right_of_graphics; return true; |
|
case 27127: *enabled = term->modify_escape_key; return true; |
|
case 737769: *enabled = term_ime_is_enabled(term); return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
static void |
|
xtsave(struct terminal *term, unsigned param) |
|
{ |
|
switch (param) { |
|
case 1: term->xtsave.application_cursor_keys = term->cursor_keys_mode == CURSOR_KEYS_APPLICATION; break; |
|
case 3: break; |
|
case 4: break; |
|
case 5: term->xtsave.reverse = term->reverse; break; |
|
case 6: term->xtsave.origin = term->origin; break; |
|
case 7: term->xtsave.auto_margin = term->auto_margin; break; |
|
case 9: /* term->xtsave.mouse_x10 = term->mouse_tracking == MOUSE_X10; */ break; |
|
case 12: term->xtsave.cursor_blink = term->cursor_blink.decset; break; |
|
case 25: term->xtsave.show_cursor = !term->hide_cursor; break; |
|
case 45: term->xtsave.reverse_wrap = term->reverse_wrap; break; |
|
case 47: term->xtsave.alt_screen = term->grid == &term->alt; break; |
|
case 80: term->xtsave.sixel_display_mode = !term->sixel.scrolling; break; |
|
case 1000: term->xtsave.mouse_click = term->mouse_tracking == MOUSE_CLICK; break; |
|
case 1001: break; |
|
case 1002: term->xtsave.mouse_drag = term->mouse_tracking == MOUSE_DRAG; break; |
|
case 1003: term->xtsave.mouse_motion = term->mouse_tracking == MOUSE_MOTION; break; |
|
case 1004: term->xtsave.focus_events = term->focus_events; break; |
|
case 1005: /* term->xtsave.mouse_utf8 = term->mouse_reporting == MOUSE_UTF8; */ break; |
|
case 1006: term->xtsave.mouse_sgr = term->mouse_reporting == MOUSE_SGR; break; |
|
case 1007: term->xtsave.alt_scrolling = term->alt_scrolling; break; |
|
case 1015: term->xtsave.mouse_urxvt = term->mouse_reporting == MOUSE_URXVT; break; |
|
case 1034: term->xtsave.meta_eight_bit = term->meta.eight_bit; break; |
|
case 1035: term->xtsave.num_lock_modifier = term->num_lock_modifier; break; |
|
case 1036: term->xtsave.meta_esc_prefix = term->meta.esc_prefix; break; |
|
case 1042: term->xtsave.bell_action_enabled = term->bell_action_enabled; break; |
|
case 1047: term->xtsave.alt_screen = term->grid == &term->alt; break; |
|
case 1048: term_save_cursor(term); break; |
|
case 1049: term->xtsave.alt_screen = term->grid == &term->alt; break; |
|
case 1070: term->xtsave.sixel_private_palette = term->sixel.use_private_palette; break; |
|
case 2004: term->xtsave.bracketed_paste = term->bracketed_paste; break; |
|
case 2026: term->xtsave.app_sync_updates = term->render.app_sync_updates.enabled; break; |
|
case 8452: term->xtsave.sixel_cursor_right_of_graphics = term->sixel.cursor_right_of_graphics; break; |
|
case 27127: term->xtsave.modify_escape_key = term->modify_escape_key; break; |
|
case 737769: term->xtsave.ime = term_ime_is_enabled(term); break; |
|
} |
|
} |
|
|
|
static void |
|
xtrestore(struct terminal *term, unsigned param) |
|
{ |
|
bool enable; |
|
switch (param) { |
|
case 1: enable = term->xtsave.application_cursor_keys; break; |
|
case 3: return; |
|
case 4: return; |
|
case 5: enable = term->xtsave.reverse; break; |
|
case 6: enable = term->xtsave.origin; break; |
|
case 7: enable = term->xtsave.auto_margin; break; |
|
case 9: /* enable = term->xtsave.mouse_x10; break; */ return; |
|
case 12: enable = term->xtsave.cursor_blink; break; |
|
case 25: enable = term->xtsave.show_cursor; break; |
|
case 45: enable = term->xtsave.reverse_wrap; break; |
|
case 47: enable = term->xtsave.alt_screen; break; |
|
case 80: enable = term->xtsave.sixel_display_mode; break; |
|
case 1000: enable = term->xtsave.mouse_click; break; |
|
case 1001: return; |
|
case 1002: enable = term->xtsave.mouse_drag; break; |
|
case 1003: enable = term->xtsave.mouse_motion; break; |
|
case 1004: enable = term->xtsave.focus_events; break; |
|
case 1005: /* enable = term->xtsave.mouse_utf8; break; */ return; |
|
case 1006: enable = term->xtsave.mouse_sgr; break; |
|
case 1007: enable = term->xtsave.alt_scrolling; break; |
|
case 1015: enable = term->xtsave.mouse_urxvt; break; |
|
case 1034: enable = term->xtsave.meta_eight_bit; break; |
|
case 1035: enable = term->xtsave.num_lock_modifier; break; |
|
case 1036: enable = term->xtsave.meta_esc_prefix; break; |
|
case 1042: enable = term->xtsave.bell_action_enabled; break; |
|
case 1047: enable = term->xtsave.alt_screen; break; |
|
case 1048: enable = true; break; |
|
case 1049: enable = term->xtsave.alt_screen; break; |
|
case 1070: enable = term->xtsave.sixel_private_palette; break; |
|
case 2004: enable = term->xtsave.bracketed_paste; break; |
|
case 2026: enable = term->xtsave.app_sync_updates; break; |
|
case 8452: enable = term->xtsave.sixel_cursor_right_of_graphics; break; |
|
case 27127: enable = term->xtsave.modify_escape_key; break; |
|
case 737769: enable = term->xtsave.ime; break; |
|
|
|
default: return; |
|
} |
|
|
|
decset_decrst(term, param, enable); |
|
} |
|
|
|
void |
|
csi_dispatch(struct terminal *term, uint8_t final) |
|
{ |
|
LOG_DBG("%s (%08x)", csi_as_string(term, final, -1), term->vt.private); |
|
|
|
switch (term->vt.private) { |
|
case 0: { |
|
switch (final) { |
|
case 'b': |
|
if (term->vt.last_printed != 0) { |
|
/* |
|
* Note: we never reset 'last-printed'. According to |
|
* ECMA-48, the behaviour is undefined if REP was |
|
* _not_ preceded by a graphical character. |
|
*/ |
|
int count = vt_param_get(term, 0, 1); |
|
LOG_DBG("REP: '%lc' %d times", (wint_t)term->vt.last_printed, count); |
|
|
|
const int width = wcwidth(term->vt.last_printed); |
|
if (width > 0) { |
|
for (int i = 0; i < count; i++) |
|
term_print(term, term->vt.last_printed, width); |
|
} |
|
} |
|
break; |
|
|
|
case 'c': { |
|
if (vt_param_get(term, 0, 0) != 0) { |
|
UNHANDLED(); |
|
break; |
|
} |
|
|
|
/* Send Device Attributes (Primary DA) */ |
|
|
|
/* |
|
* Responses: |
|
* - CSI?1;2c vt100 with advanced video option |
|
* - CSI?1;0c vt101 with no options |
|
* - CSI?6c vt102 |
|
* - CSI?62;<Ps>c vt220 |
|
* - CSI?63;<Ps>c vt320 |
|
* - CSI?64;<Ps>c vt420 |
|
* |
|
* Ps (response may contain multiple): |
|
* - 1 132 columns |
|
* - 2 Printer. |
|
* - 3 ReGIS graphics. |
|
* - 4 Sixel graphics. |
|
* - 6 Selective erase. |
|
* - 8 User-defined keys. |
|
* - 9 National Replacement Character sets. |
|
* - 15 Technical characters. |
|
* - 16 Locator port. |
|
* - 17 Terminal state interrogation. |
|
* - 18 User windows. |
|
* - 21 Horizontal scrolling. |
|
* - 22 ANSI color, e.g., VT525. |
|
* - 28 Rectangular editing. |
|
* - 29 ANSI text locator (i.e., DEC Locator mode). |
|
* |
|
* Note: we report ourselves as a VT220, mainly to be able |
|
* to pass parameters, to indicate we support sixel, and |
|
* ANSI colors. |
|
* |
|
* The VT level must be synchronized with the secondary DA |
|
* response. |
|
* |
|
* Note: tertiary DA responds with "FOOT". |
|
*/ |
|
static const char reply[] = "\033[?62;4;22c"; |
|
term_to_slave(term, reply, sizeof(reply) - 1); |
|
break; |
|
} |
|
|
|
case 'd': { |
|
/* VPA - vertical line position absolute */ |
|
int rel_row = vt_param_get(term, 0, 1) - 1; |
|
int row = term_row_rel_to_abs(term, rel_row); |
|
term_cursor_to(term, row, term->grid->cursor.point.col); |
|
break; |
|
} |
|
|
|
case 'm': |
|
csi_sgr(term); |
|
break; |
|
|
|
case 'A': |
|
term_cursor_up(term, vt_param_get(term, 0, 1)); |
|
break; |
|
|
|
case 'e': |
|
case 'B': |
|
term_cursor_down(term, vt_param_get(term, 0, 1)); |
|
break; |
|
|
|
case 'a': |
|
case 'C': |
|
term_cursor_right(term, vt_param_get(term, 0, 1)); |
|
break; |
|
|
|
case 'D': |
|
term_cursor_left(term, vt_param_get(term, 0, 1)); |
|
break; |
|
|
|
case 'E': |
|
/* CNL - Cursor Next Line */ |
|
term_cursor_down(term, vt_param_get(term, 0, 1)); |
|
term_cursor_left(term, term->grid->cursor.point.col); |
|
break; |
|
|
|
case 'F': |
|
/* CPL - Cursor Previous Line */ |
|
term_cursor_up(term, vt_param_get(term, 0, 1)); |
|
term_cursor_left(term, term->grid->cursor.point.col); |
|
break; |
|
|
|
case 'g': { |
|
int param = vt_param_get(term, 0, 0); |
|
switch (param) { |
|
case 0: |
|
/* Clear tab stop at *current* column */ |
|
tll_foreach(term->tab_stops, it) { |
|
if (it->item == term->grid->cursor.point.col) |
|
tll_remove(term->tab_stops, it); |
|
else if (it->item > term->grid->cursor.point.col) |
|
break; |
|
} |
|
|
|
break; |
|
|
|
case 3: |
|
/* Clear *all* tabs */ |
|
tll_free(term->tab_stops); |
|
break; |
|
|
|
default: |
|
UNHANDLED(); |
|
break; |
|
} |
|
break; |
|
} |
|
|
|
case '`': |
|
case 'G': { |
|
/* Cursor horizontal absolute */ |
|
int col = min(vt_param_get(term, 0, 1), term->cols) - 1; |
|
term_cursor_to(term, term->grid->cursor.point.row, col); |
|
break; |
|
} |
|
|
|
case 'f': |
|
case 'H': { |
|
/* Move cursor */ |
|
int rel_row = vt_param_get(term, 0, 1) - 1; |
|
int row = term_row_rel_to_abs(term, rel_row); |
|
int col = min(vt_param_get(term, 1, 1), term->cols) - 1; |
|
term_cursor_to(term, row, col); |
|
break; |
|
} |
|
|
|
case 'J': { |
|
/* Erase screen */ |
|
|
|
int param = vt_param_get(term, 0, 0); |
|
switch (param) { |
|
case 0: |
|
/* From cursor to end of screen */ |
|
term_erase( |
|
term, |
|
&term->grid->cursor.point, |
|
&(struct coord){term->cols - 1, term->rows - 1}); |
|
term->grid->cursor.lcf = false; |
|
break; |
|
|
|
case 1: |
|
/* From start of screen to cursor */ |
|
term_erase(term, &(struct coord){0, 0}, &term->grid->cursor.point); |
|
term->grid->cursor.lcf = false; |
|
break; |
|
|
|
case 2: |
|
/* Erase entire screen */ |
|
term_erase( |
|
term, |
|
&(struct coord){0, 0}, |
|
&(struct coord){term->cols - 1, term->rows - 1}); |
|
term->grid->cursor.lcf = false; |
|
break; |
|
|
|
case 3: { |
|
/* Erase scrollback */ |
|
term_erase_scrollback(term); |
|
break; |
|
} |
|
|
|
default: |
|
UNHANDLED(); |
|
break; |
|
} |
|
break; |
|
} |
|
|
|
case 'K': { |
|
/* Erase line */ |
|
|
|
int param = vt_param_get(term, 0, 0); |
|
switch (param) { |
|
case 0: |
|
/* From cursor to end of line */ |
|
term_erase( |
|
term, |
|
&term->grid->cursor.point, |
|
&(struct coord){term->cols - 1, term->grid->cursor.point.row}); |
|
term->grid->cursor.lcf = false; |
|
break; |
|
|
|
case 1: |
|
/* From start of line to cursor */ |
|
term_erase( |
|
term, &(struct coord){0, term->grid->cursor.point.row}, &term->grid->cursor.point); |
|
term->grid->cursor.lcf = false; |
|
break; |
|
|
|
case 2: |
|
/* Entire line */ |
|
term_erase( |
|
term, |
|
&(struct coord){0, term->grid->cursor.point.row}, |
|
&(struct coord){term->cols - 1, term->grid->cursor.point.row}); |
|
term->grid->cursor.lcf = false; |
|
break; |
|
|
|
default: |
|
UNHANDLED(); |
|
break; |
|
} |
|
|
|
break; |
|
} |
|
|
|
case 'L': { |
|
if (term->grid->cursor.point.row < term->scroll_region.start || |
|
term->grid->cursor.point.row >= term->scroll_region.end) |
|
break; |
|
|
|
int count = min( |
|
vt_param_get(term, 0, 1), |
|
term->scroll_region.end - term->grid->cursor.point.row); |
|
|
|
term_scroll_reverse_partial( |
|
term, |
|
(struct scroll_region){ |
|
.start = term->grid->cursor.point.row, |
|
.end = term->scroll_region.end}, |
|
count); |
|
term->grid->cursor.lcf = false; |
|
term->grid->cursor.point.col = 0; |
|
break; |
|
} |
|
|
|
case 'M': { |
|
if (term->grid->cursor.point.row < term->scroll_region.start || |
|
term->grid->cursor.point.row >= term->scroll_region.end) |
|
break; |
|
|
|
int count = min( |
|
vt_param_get(term, 0, 1), |
|
term->scroll_region.end - term->grid->cursor.point.row); |
|
|
|
term_scroll_partial( |
|
term, |
|
(struct scroll_region){ |
|
.start = term->grid->cursor.point.row, |
|
.end = term->scroll_region.end}, |
|
count); |
|
term->grid->cursor.lcf = false; |
|
term->grid->cursor.point.col = 0; |
|
break; |
|
} |
|
|
|
case 'P': { |
|
/* DCH: Delete character(s) */ |
|
|
|
/* Number of characters to delete */ |
|
int count = min( |
|
vt_param_get(term, 0, 1), term->cols - term->grid->cursor.point.col); |
|
|
|
/* Number of characters left after deletion (on current line) */ |
|
int remaining = term->cols - (term->grid->cursor.point.col + count); |
|
|
|
/* 'Delete' characters by moving the remaining ones */ |
|
memmove(&term->grid->cur_row->cells[term->grid->cursor.point.col], |
|
&term->grid->cur_row->cells[term->grid->cursor.point.col + count], |
|
remaining * sizeof(term->grid->cur_row->cells[0])); |
|
|
|
for (size_t c = 0; c < remaining; c++) |
|
term->grid->cur_row->cells[term->grid->cursor.point.col + c].attrs.clean = 0; |
|
term->grid->cur_row->dirty = true; |
|
|
|
/* Erase the remainder of the line */ |
|
term_erase( |
|
term, |
|
&(struct coord){term->grid->cursor.point.col + remaining, term->grid->cursor.point.row}, |
|
&(struct coord){term->cols - 1, term->grid->cursor.point.row}); |
|
term->grid->cursor.lcf = false; |
|
break; |
|
} |
|
|
|
case '@': { |
|
/* ICH: insert character(s) */ |
|
|
|
/* Number of characters to insert */ |
|
int count = min( |
|
vt_param_get(term, 0, 1), term->cols - term->grid->cursor.point.col); |
|
|
|
/* Characters to move */ |
|
int remaining = term->cols - (term->grid->cursor.point.col + count); |
|
|
|
/* Push existing characters */ |
|
memmove(&term->grid->cur_row->cells[term->grid->cursor.point.col + count], |
|
&term->grid->cur_row->cells[term->grid->cursor.point.col], |
|
remaining * sizeof(term->grid->cur_row->cells[0])); |
|
for (size_t c = 0; c < remaining; c++) |
|
term->grid->cur_row->cells[term->grid->cursor.point.col + count + c].attrs.clean = 0; |
|
term->grid->cur_row->dirty = true; |
|
|
|
/* Erase (insert space characters) */ |
|
term_erase( |
|
term, |
|
&term->grid->cursor.point, |
|
&(struct coord){term->grid->cursor.point.col + count - 1, term->grid->cursor.point.row}); |
|
term->grid->cursor.lcf = false; |
|
break; |
|
} |
|
|
|
case 'S': { |
|
const struct scroll_region *r = &term->scroll_region; |
|
int amount = min(vt_param_get(term, 0, 1), r->end - r->start); |
|
term_scroll(term, amount); |
|
break; |
|
} |
|
|
|
case 'T': { |
|
const struct scroll_region *r = &term->scroll_region; |
|
int amount = min(vt_param_get(term, 0, 1), r->end - r->start); |
|
term_scroll_reverse(term, amount); |
|
break; |
|
} |
|
|
|
case 'X': { |
|
/* Erase chars */ |
|
int count = min( |
|
vt_param_get(term, 0, 1), term->cols - term->grid->cursor.point.col); |
|
|
|
term_erase( |
|
term, |
|
&term->grid->cursor.point, |
|
&(struct coord){term->grid->cursor.point.col + count - 1, term->grid->cursor.point.row}); |
|
term->grid->cursor.lcf = false; |
|
break; |
|
} |
|
|
|
case 'I': { |
|
/* CHT - Tab Forward (param is number of tab stops to move through) */ |
|
for (int i = 0; i < vt_param_get(term, 0, 1); i++) { |
|
int new_col = term->cols - 1; |
|
tll_foreach(term->tab_stops, it) { |
|
if (it->item > term->grid->cursor.point.col) { |
|
new_col = it->item; |
|
break; |
|
} |
|
} |
|
xassert(new_col >= term->grid->cursor.point.col); |
|
|
|
bool lcf = term->grid->cursor.lcf; |
|
term_cursor_right(term, new_col - term->grid->cursor.point.col); |
|
term->grid->cursor.lcf = lcf; |
|
} |
|
break; |
|
} |
|
|
|
case 'Z': |
|
/* CBT - Back tab (param is number of tab stops to move back through) */ |
|
for (int i = 0; i < vt_param_get(term, 0, 1); i++) { |
|
int new_col = 0; |
|
tll_rforeach(term->tab_stops, it) { |
|
if (it->item < term->grid->cursor.point.col) { |
|
new_col = it->item; |
|
break; |
|
} |
|
} |
|
xassert(term->grid->cursor.point.col >= new_col); |
|
term_cursor_left(term, term->grid->cursor.point.col - new_col); |
|
} |
|
break; |
|
|
|
case 'h': |
|
/* Set mode */ |
|
switch (vt_param_get(term, 0, 0)) { |
|
case 2: /* Keyboard Action Mode - AM */ |
|
LOG_WARN("unimplemented: keyboard action mode (AM)"); |
|
break; |
|
|
|
case 4: /* Insert Mode - IRM */ |
|
term->insert_mode = true; |
|
term_update_ascii_printer(term); |
|
break; |
|
|
|
case 12: /* Send/receive Mode - SRM */ |
|
LOG_WARN("unimplemented: send/receive mode (SRM)"); |
|
break; |
|
|
|
case 20: /* Automatic Newline Mode - LNM */ |
|
/* TODO: would be easy to implemented; when active |
|
* term_linefeed() would _also_ do a |
|
* term_carriage_return() */ |
|
LOG_WARN("unimplemented: automatic newline mode (LNM)"); |
|
break; |
|
} |
|
break; |
|
|
|
case 'l': |
|
/* Reset mode */ |
|
switch (vt_param_get(term, 0, 0)) { |
|
case 4: /* Insert Mode - IRM */ |
|
term->insert_mode = false; |
|
term_update_ascii_printer(term); |
|
break; |
|
|
|
case 2: /* Keyboard Action Mode - AM */ |
|
case 12: /* Send/receive Mode - SRM */ |
|
case 20: /* Automatic Newline Mode - LNM */ |
|
break; |
|
} |
|
break; |
|
|
|
case 'r': { |
|
int start = vt_param_get(term, 0, 1); |
|
int end = min(vt_param_get(term, 1, term->rows), term->rows); |
|
|
|
if (end > start) { |
|
|
|
/* 1-based */ |
|
term->scroll_region.start = start - 1; |
|
term->scroll_region.end = end; |
|
term_cursor_home(term); |
|
|
|
LOG_DBG("scroll region: %d-%d", |
|
term->scroll_region.start, |
|
term->scroll_region.end); |
|
} |
|
break; |
|
} |
|
|
|
case 's': |
|
term_save_cursor(term); |
|
break; |
|
|
|
case 'u': |
|
term_restore_cursor(term, &term->grid->saved_cursor); |
|
break; |
|
|
|
case 't': { |
|
/* |
|
* Window operations |
|
*/ |
|
|
|
const unsigned param = vt_param_get(term, 0, 0); |
|
|
|
switch (param) { |
|
case 1: LOG_WARN("unimplemented: de-iconify"); break; |
|
case 2: LOG_WARN("unimplemented: iconify"); break; |
|
case 3: LOG_WARN("unimplemented: move window to pixel position"); break; |
|
case 4: LOG_WARN("unimplemented: resize window in pixels"); break; |
|
case 5: LOG_WARN("unimplemented: raise window to front of stack"); break; |
|
case 6: LOG_WARN("unimplemented: raise window to back of stack"); break; |
|
case 7: LOG_WARN("unimplemented: refresh window"); break; |
|
case 8: LOG_WARN("unimplemented: resize window in chars"); break; |
|
case 9: LOG_WARN("unimplemented: maximize/unmaximize window"); break; |
|
case 10: LOG_WARN("unimplemented: to/from full screen"); break; |
|
case 20: LOG_WARN("unimplemented: report icon label"); break; |
|
case 21: LOG_WARN("unimplemented: report window title"); break; |
|
case 24: LOG_WARN("unimplemented: resize window (DECSLPP)"); break; |
|
|
|
case 11: /* report if window is iconified */ |
|
/* We don't know - always report *not* iconified */ |
|
/* 1=not iconified, 2=iconified */ |
|
term_to_slave(term, "\033[1t", 4); |
|
break; |
|
|
|
case 13: { /* report window position */ |
|
/* We don't know our position - always report (0,0) */ |
|
static const char reply[] = "\033[3;0;0t"; |
|
switch (vt_param_get(term, 1, 0)) { |
|
case 0: /* window position */ |
|
case 2: /* text area position */ |
|
term_to_slave(term, reply, sizeof(reply) - 1); |
|
break; |
|
|
|
default: |
|
UNHANDLED(); |
|
break; |
|
} |
|
|
|
break; |
|
} |
|
|
|
case 14: { /* report window size in pixels */ |
|
int width = -1; |
|
int height = -1; |
|
|
|
switch (vt_param_get(term, 1, 0)) { |
|
case 0: |
|
/* text area size */ |
|
width = term->width - term->margins.left - term->margins.right; |
|
height = term->height - term->margins.top - term->margins.bottom; |
|
break; |
|
|
|
case 2: |
|
/* window size */ |
|
width = term->width; |
|
height = term->height; |
|
break; |
|
|
|
default: |
|
UNHANDLED(); |
|
break; |
|
} |
|
|
|
if (width >= 0 && height >= 0) { |
|
char reply[64]; |
|
size_t n = xsnprintf(reply, sizeof(reply), "\033[4;%d;%dt", |
|
height / term->scale, width / term->scale); |
|
term_to_slave(term, reply, n); |
|
} |
|
break; |
|
} |
|
|
|
case 15: /* report screen size in pixels */ |
|
tll_foreach(term->window->on_outputs, it) { |
|
char reply[64]; |
|
size_t n = xsnprintf(reply, sizeof(reply), "\033[5;%d;%dt", |
|
it->item->dim.px_scaled.height, |
|
it->item->dim.px_scaled.width); |
|
term_to_slave(term, reply, n); |
|
break; |
|
} |
|
|
|
if (tll_length(term->window->on_outputs) == 0) |
|
term_to_slave(term, "\033[5;0;0t", 8); |
|
break; |
|
|
|
case 16: { /* report cell size in pixels */ |
|
char reply[64]; |
|
size_t n = xsnprintf(reply, sizeof(reply), "\033[6;%d;%dt", |
|
term->cell_height / term->scale, |
|
term->cell_width / term->scale); |
|
term_to_slave(term, reply, n); |
|
break; |
|
} |
|
|
|
case 18: { /* text area size in chars */ |
|
char reply[64]; |
|
size_t n = xsnprintf(reply, sizeof(reply), "\033[8;%d;%dt", |
|
term->rows, term->cols); |
|
term_to_slave(term, reply, n); |
|
break; |
|
} |
|
|
|
case 19: { /* report screen size in chars */ |
|
tll_foreach(term->window->on_outputs, it) { |
|
char reply[64]; |
|
size_t n = xsnprintf(reply, sizeof(reply), "\033[9;%d;%dt", |
|
it->item->dim.px_real.height / term->cell_height / term->scale, |
|
it->item->dim.px_real.width / term->cell_width / term->scale); |
|
term_to_slave(term, reply, n); |
|
break; |
|
} |
|
|
|
if (tll_length(term->window->on_outputs) == 0) |
|
term_to_slave(term, "\033[9;0;0t", 8); |
|
break; |
|
} |
|
|
|
case 22: { /* push window title */ |
|
/* 0 - icon + title, 1 - icon, 2 - title */ |
|
unsigned what = vt_param_get(term, 1, 0); |
|
if (what == 0 || what == 2) { |
|
tll_push_back( |
|
term->window_title_stack, xstrdup(term->window_title)); |
|
} |
|
break; |
|
} |
|
|
|
case 23: { /* pop window title */ |
|
/* 0 - icon + title, 1 - icon, 2 - title */ |
|
unsigned what = vt_param_get(term, 1, 0); |
|
if (what == 0 || what == 2) { |
|
if (tll_length(term->window_title_stack) > 0) { |
|
char *title = tll_pop_back(term->window_title_stack); |
|
term_set_window_title(term, title); |
|
free(title); |
|
} |
|
} |
|
break; |
|
} |
|
|
|
case 1001: { |
|
} |
|
|
|
default: |
|
LOG_DBG("ignoring %s", csi_as_string(term, final, -1)); |
|
break; |
|
} |
|
break; |
|
} |
|
|
|
case 'n': { |
|
if (term->vt.params.idx > 0) { |
|
int param = vt_param_get(term, 0, 0); |
|
switch (param) { |
|
case 5: |
|
/* Query device status */ |
|
term_to_slave(term, "\x1b[0n", 4); /* "Device OK" */ |
|
break; |
|
|
|
case 6: { |
|
/* u7 - cursor position query */ |
|
|
|
int row = term->origin == ORIGIN_ABSOLUTE |
|
? term->grid->cursor.point.row |
|
: term->grid->cursor.point.row - term->scroll_region.start; |
|
|
|
/* TODO: we use 0-based position, while the xterm |
|
* terminfo says the receiver of the reply should |
|
* decrement, hence we must add 1 */ |
|
char reply[64]; |
|
size_t n = xsnprintf(reply, sizeof(reply), "\x1b[%d;%dR", |
|
row + 1, term->grid->cursor.point.col + 1); |
|
term_to_slave(term, reply, n); |
|
break; |
|
} |
|
|
|
default: |
|
UNHANDLED(); |
|
break; |
|
} |
|
} else |
|
UNHANDLED(); |
|
|
|
break; |
|
} |
|
|
|
default: |
|
UNHANDLED(); |
|
break; |
|
} |
|
|
|
break; /* private[0] == 0 */ |
|
} |
|
|
|
case '?': { |
|
switch (final) { |
|
case 'h': |
|
/* DECSET - DEC private mode set */ |
|
for (size_t i = 0; i < term->vt.params.idx; i++) |
|
decset(term, term->vt.params.v[i].value); |
|
break; |
|
|
|
case 'l': |
|
/* DECRST - DEC private mode reset */ |
|
for (size_t i = 0; i < term->vt.params.idx; i++) |
|
decrst(term, term->vt.params.v[i].value); |
|
break; |
|
|
|
case 's': |
|
for (size_t i = 0; i < term->vt.params.idx; i++) |
|
xtsave(term, term->vt.params.v[i].value); |
|
break; |
|
|
|
case 'r': |
|
for (size_t i = 0; i < term->vt.params.idx; i++) |
|
xtrestore(term, term->vt.params.v[i].value); |
|
break; |
|
|
|
case 'S': { |
|
unsigned target = vt_param_get(term, 0, 0); |
|
unsigned operation = vt_param_get(term, 1, 0); |
|
|
|
switch (target) { |
|
case 1: |
|
switch (operation) { |
|
case 1: sixel_colors_report_current(term); break; |
|
case 2: sixel_colors_reset(term); break; |
|
case 3: sixel_colors_set(term, vt_param_get(term, 2, 0)); break; |
|
case 4: sixel_colors_report_max(term); break; |
|
default: UNHANDLED(); break; |
|
} |
|
break; |
|
|
|
case 2: |
|
switch (operation) { |
|
case 1: sixel_geometry_report_current(term); break; |
|
case 2: sixel_geometry_reset(term); break; |
|
case 3: sixel_geometry_set(term, vt_param_get(term, 2, 0), vt_param_get(term, 3, 0)); break; |
|
case 4: sixel_geometry_report_max(term); break; |
|
default: UNHANDLED(); break; |
|
} |
|
break; |
|
|
|
default: |
|
UNHANDLED(); |
|
break; |
|
} |
|
|
|
break; |
|
} |
|
|
|
default: |
|
UNHANDLED(); |
|
break; |
|
} |
|
|
|
break; /* private[0] == '?' */ |
|
} |
|
|
|
case '>': { |
|
switch (final) { |
|
case 'c': |
|
/* Send Device Attributes (Secondary DA) */ |
|
if (vt_param_get(term, 0, 0) != 0) { |
|
UNHANDLED(); |
|
break; |
|
} |
|
|
|
/* |
|
* Param 1 - terminal type: |
|
* 0 - vt100 |
|
* 1 - vt220 |
|
* 2 - vt240 |
|
* 18 - vt330 |
|
* 19 - vt340 |
|
* 24 - vt320 |
|
* 41 - vt420 |
|
* 61 - vt510 |
|
* 64 - vt520 |
|
* 65 - vt525 |
|
* |
|
* Param 2 - firmware version |
|
* xterm uses its version number. We use an xterm |
|
* version number too, since e.g. Emacs uses this to |
|
* determine level of support. |
|
* |
|
* We report ourselves as a VT220. This must be |
|
* synchronized with the primary DA response. |
|
* |
|
* Note: tertiary DA replies with "FOOT". |
|
*/ |
|
|
|
static_assert(FOOT_MAJOR < 100, "Major version must not exceed 99"); |
|
static_assert(FOOT_MINOR < 100, "Minor version must not exceed 99"); |
|
static_assert(FOOT_PATCH < 100, "Patch version must not exceed 99"); |
|
|
|
char reply[64]; |
|
size_t n = xsnprintf(reply, sizeof(reply), "\033[>1;%02u%02u%02u;0c", |
|
FOOT_MAJOR, FOOT_MINOR, FOOT_PATCH); |
|
|
|
term_to_slave(term, reply, n); |
|
break; |
|
|
|
case 'm': |
|
if (term->vt.params.idx == 0) { |
|
/* Reset all */ |
|
} else { |
|
int resource = vt_param_get(term, 0, 0); |
|
int value = vt_param_get(term, 1, -1); |
|
|
|
switch (resource) { |
|
case 0: /* modifyKeyboard */ |
|
break; |
|
|
|
case 1: /* modifyCursorKeys */ |
|
case 2: /* modifyFunctionKeys */ |
|
case 4: /* modifyOtherKeys */ |
|
/* Ignored, we always report modifiers */ |
|
if (value != 2 && value != -1 && |
|
!(resource == 4 && value == 1)) |
|
{ |
|
LOG_WARN("unimplemented: %s = %d", |
|
resource == 1 ? "modifyCursorKeys" : |
|
resource == 2 ? "modifyFunctionKeys" : |
|
resource == 4 ? "modifyOtherKeys" : "<invalid>", |
|
value); |
|
} |
|
break; |
|
|
|
default: |
|
LOG_WARN("invalid resource %d in %s", |
|
resource, csi_as_string(term, final, -1)); |
|
break; |
|
} |
|
} |
|
break; /* final == 'm' */ |
|
|
|
case 'q': { |
|
/* XTVERSION */ |
|
if (vt_param_get(term, 0, 0) != 0) { |
|
UNHANDLED(); |
|
break; |
|
} |
|
|
|
char reply[64]; |
|
size_t n = xsnprintf( |
|
reply, sizeof(reply), "\033P>|foot(%u.%u.%u%s%s)\033\\", |
|
FOOT_MAJOR, FOOT_MINOR, FOOT_PATCH, |
|
FOOT_EXTRA[0] != '\0' ? "-" : "", FOOT_EXTRA); |
|
term_to_slave(term, reply, n); |
|
break; |
|
} |
|
|
|
default: |
|
UNHANDLED(); |
|
break; |
|
} |
|
|
|
break; /* private[0] == '>' */ |
|
} |
|
|
|
case ' ': { |
|
switch (final) { |
|
case 'q': { |
|
int param = vt_param_get(term, 0, 0); |
|
switch (param) { |
|
case 0: /* blinking block, but we use it to reset to configured default */ |
|
term->cursor_style = term->conf->cursor.style; |
|
term->cursor_blink.deccsusr = term->conf->cursor.blink; |
|
term_cursor_blink_update(term); |
|
break; |
|
|
|
case 1: /* blinking block */ |
|
case 2: /* steady block */ |
|
term->cursor_style = CURSOR_BLOCK; |
|
break; |
|
|
|
case 3: /* blinking underline */ |
|
case 4: /* steady underline */ |
|
term->cursor_style = CURSOR_UNDERLINE; |
|
break; |
|
|
|
case 5: /* blinking bar */ |
|
case 6: /* steady bar */ |
|
term->cursor_style = CURSOR_BEAM; |
|
break; |
|
|
|
default: |
|
UNHANDLED(); |
|
break; |
|
} |
|
|
|
if (param > 0 && param <= 6) { |
|
term->cursor_blink.deccsusr = param & 1; |
|
term_cursor_blink_update(term); |
|
} |
|
break; |
|
} |
|
|
|
default: |
|
UNHANDLED(); |
|
break; |
|
} |
|
break; /* private[0] == ' ' */ |
|
} |
|
|
|
case '!': { |
|
if (final == 'p') { |
|
term_reset(term, false); |
|
break; |
|
} |
|
|
|
UNHANDLED(); |
|
break; /* private[0] == '!' */ |
|
} |
|
|
|
case '=': { |
|
switch (final) { |
|
case 'c': |
|
if (vt_param_get(term, 0, 0) != 0) { |
|
UNHANDLED(); |
|
break; |
|
} |
|
|
|
/* |
|
* Send Device Attributes (Tertiary DA) |
|
* |
|
* Reply format is "DCS ! | DDDDDDDD ST" |
|
* |
|
* D..D is the unit ID of the terminal, consisting of four |
|
* hexadecimal pairs. The first pair represents the |
|
* manufacturing site code. This code can be any |
|
* hexadecimal value from 00 through FF. |
|
*/ |
|
|
|
term_to_slave(term, "\033P!|464f4f54\033\\", 14); /* FOOT */ |
|
break; |
|
|
|
default: |
|
UNHANDLED(); |
|
break; |
|
} |
|
break; /* private[0] == '=' */ |
|
} |
|
|
|
case 0x243f: /* ?$ */ |
|
switch (final) { |
|
case 'p': { |
|
unsigned param = vt_param_get(term, 0, 0); |
|
|
|
/* |
|
* Request DEC private mode (DECRQM) |
|
* Reply: |
|
* 0 - not recognized |
|
* 1 - set |
|
* 2 - reset |
|
* 3 - permanently set |
|
* 4 - permantently reset |
|
*/ |
|
bool enabled; |
|
unsigned value; |
|
if (decrqm(term, param, &enabled)) |
|
value = enabled ? 1 : 2; |
|
else |
|
value = 0; |
|
|
|
char reply[32]; |
|
size_t n = xsnprintf(reply, sizeof(reply), "\033[?%u;%u$y", param, value); |
|
term_to_slave(term, reply, n); |
|
break; |
|
|
|
} |
|
|
|
default: |
|
UNHANDLED(); |
|
break; |
|
} |
|
|
|
break; /* private[0] == ‘?’ && private[1] == ‘$’ */ |
|
|
|
default: |
|
UNHANDLED(); |
|
break; |
|
} |
|
}
|
|
|