foot/csi.c

2220 lines
73 KiB
C
Raw Permalink Normal View History

#include "csi.h"
2019-06-17 18:57:12 +02:00
#include <stdlib.h>
#include <string.h>
#include <unistd.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 "char32.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)
{
term->vt.attrs = (struct attributes){0};
term->vt.underline = (struct underline_range_data){0};
term->bits_affecting_ascii_printer.underline_style = false;
term->bits_affecting_ascii_printer.underline_color = false;
term_update_ascii_printer(term);
}
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;
term->vt.underline.style = UNDERLINE_SINGLE;
if (unlikely(term->vt.params.v[i].sub.idx == 1)) {
enum underline_style style = term->vt.params.v[i].sub.value[0];
switch (style) {
default:
case UNDERLINE_NONE:
term->vt.attrs.underline = false;
term->vt.underline.style = UNDERLINE_NONE;
term->bits_affecting_ascii_printer.underline_style = false;
break;
case UNDERLINE_SINGLE:
case UNDERLINE_DOUBLE:
case UNDERLINE_CURLY:
case UNDERLINE_DOTTED:
case UNDERLINE_DASHED:
term->vt.underline.style = style;
term->bits_affecting_ascii_printer.underline_style =
style > UNDERLINE_SINGLE;
break;
}
term_update_ascii_printer(term);
}
break;
}
2019-07-16 13:19:17 +02:00
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:
term->vt.attrs.underline = true;
term->vt.underline.style = UNDERLINE_DOUBLE;
term->bits_affecting_ascii_printer.underline_style = true;
term_update_ascii_printer(term);
break;
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;
term->vt.underline.style = UNDERLINE_NONE;
term->bits_affecting_ascii_printer.underline_style = false;
term_update_ascii_printer(term);
break;
}
2019-07-16 13:19:17 +02:00
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.fg_src = COLOR_BASE16;
term->vt.attrs.fg = param - 30;
break;
case 38:
case 48:
case 58: {
uint32_t color;
enum color_source src;
/* Indexed: 38;5;<idx> */
if (term->vt.params.idx - i - 1 >= 2 &&
term->vt.params.v[i + 1].value == 5)
{
src = COLOR_BASE256;
color = min(term->vt.params.v[i + 2].value,
ALEN(term->colors.table) - 1);
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;
src = COLOR_RGB;
color = 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)
{
src = COLOR_BASE256;
color = min(term->vt.params.v[i].sub.value[1],
ALEN(term->colors.table) - 1);
}
/*
* 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];
src = COLOR_RGB;
color = 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;
}
if (unlikely(param == 58)) {
term->vt.underline.color_src = src;
term->vt.underline.color = color;
term->bits_affecting_ascii_printer.underline_color = true;
term_update_ascii_printer(term);
} else if (param == 38) {
term->vt.attrs.fg_src = src;
term->vt.attrs.fg = color;
} else {
xassert(param == 48);
term->vt.attrs.bg_src = src;
term->vt.attrs.bg = color;
}
break;
}
case 39:
term->vt.attrs.fg_src = COLOR_DEFAULT;
break;
/* Regular background colors */
case 40:
case 41:
case 42:
case 43:
case 44:
case 45:
case 46:
case 47:
term->vt.attrs.bg_src = COLOR_BASE16;
term->vt.attrs.bg = param - 40;
break;
case 49:
term->vt.attrs.bg_src = COLOR_DEFAULT;
break;
case 59:
term->vt.underline.color_src = COLOR_DEFAULT;
term->vt.underline.color = 0;
term->bits_affecting_ascii_printer.underline_color = false;
term_update_ascii_printer(term);
break;
/* Bright foreground colors */
case 90:
case 91:
case 92:
case 93:
case 94:
case 95:
case 96:
case 97:
term->vt.attrs.fg_src = COLOR_BASE16;
term->vt.attrs.fg = 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.bg_src = COLOR_BASE16;
term->vt.attrs.bg = param - 100 + 8;
break;
default:
UNHANDLED_SGR(i);
break;
}
}
}
2020-08-16 16:20:47 +02:00
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 */
2020-08-16 16:20:47 +02:00
switch (param) {
case 1:
/* DECCKM */
2020-08-16 16:20:47 +02:00
term->cursor_keys_mode =
enable ? CURSOR_KEYS_APPLICATION : CURSOR_KEYS_NORMAL;
break;
case 5:
/* DECSCNM */
2020-08-16 16:20:47 +02:00
term->reverse = enable;
term_damage_all(term);
term_damage_margins(term);
2020-08-16 16:20:47 +02:00
break;
case 6: {
/* DECOM */
2020-08-16 16:20:47 +02:00
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)
2020-08-16 16:20:47 +02:00
term->mouse_tracking = MOUSE_NONE;
#endif
2020-08-16 16:20:47 +02:00
break;
case 12:
term: split cursor blink state into two There are two different escape sequences that can be used to set the cursor blink state: ‘CSI ? 12 h/l’ and ‘CSI Ps SP q’. Up until now, they both modified the same internal state in foot. This meant you could enable a blinking cursor with e.g. ‘CSI ? 12 h’ and then disable it with ‘CSI 2 SP q’. Since the ‘CSI ? 12’ escapes are used in the civis/cnorm/cvvis terminfo entries, applications often ended up disabling the blink state on exit (typically be emitting ‘cnorm’), requiring users to manually re-enable blinking. By splitting the internal state into two separate states, we can improve the situation. The cursor will blink if at least one of the two have been enabled. The setting in foot.ini sets the default state of the ‘CSI Ps SP q’ escape. This means if the user has enabled blinking in the configuration, the cursor will blink regardless of civis/cnorm/cvvis. Which probably is what the user wants. If the user has NOT enabled blinking, civis/cnorm/cvvis act as intended: cvvis blink, civis and cnorm do not. If an application overrides the cursor blink/style with ‘CSI Ps SP q’, that will override the user’s setting in foot.ini. But most likely that too is intended (for example, the user may have configured the application to use a different cursor style). And, a well written application will emit the ‘Se’ terminfo sequence on exit, which in foot is defined to ‘CSI SP q’, which will reset both the style and blink state to the user configured style/state. Closes #218
2020-11-26 18:09:32 +01:00
term->cursor_blink.decset = enable;
term_cursor_blink_update(term);
2020-08-16 16:20:47 +02:00
break;
case 25:
/* DECTCEM */
2020-08-16 16:20:47 +02:00
term->hide_cursor = !enable;
break;
case 45:
term->reverse_wrap = enable;
break;
case 66:
/* DECNKM */
term->keypad_keys_mode = enable ? KEYPAD_APPLICATION : KEYPAD_NUMERICAL;
break;
case 67:
if (enable)
LOG_WARN("unimplemented: DECBKM");
break;
case 80:
term->sixel.scrolling = !enable;
break;
2020-08-16 16:20:47 +02:00
case 1000:
if (enable)
term->mouse_tracking = MOUSE_CLICK;
else if (term->mouse_tracking == MOUSE_CLICK)
term->mouse_tracking = MOUSE_NONE;
2020-08-16 16:20:47 +02:00
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;
2020-08-16 16:20:47 +02:00
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;
2020-08-16 16:20:47 +02:00
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)
2020-08-16 16:20:47 +02:00
term->mouse_reporting = MOUSE_NONE;
#endif
2020-08-16 16:20:47 +02:00
break;
case 1006:
if (enable)
term->mouse_reporting = MOUSE_SGR;
else if (term->mouse_reporting == MOUSE_SGR)
term->mouse_reporting = MOUSE_NORMAL;
2020-08-16 16:20:47 +02:00
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;
2020-08-16 16:20:47 +02:00
break;
2021-12-30 05:13:45 -06:00
case 1016:
if (enable)
term->mouse_reporting = MOUSE_SGR_PIXELS;
else if (term->mouse_reporting == MOUSE_SGR_PIXELS)
term->mouse_reporting = MOUSE_NORMAL;
break;
2020-08-16 16:20:47 +02:00
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;
2020-08-16 16:20:47 +02:00
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;
2020-08-16 16:20:47 +02:00
break;
#if 0
2020-08-16 16:20:47 +02:00
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:
2020-08-16 16:20:47 +02:00
case 1049:
if (enable && term->grid != &term->alt) {
selection_cancel(term);
if (param == 1049)
term_save_cursor(term);
2020-08-16 16:20:47 +02:00
term->grid = &term->alt;
/* Cursor retains its position from the normal grid */
2020-08-16 16:20:47 +02:00
term_cursor_to(
term,
min(term->normal.cursor.point.row, term->rows - 1),
min(term->normal.cursor.point.col, term->cols - 1));
2020-08-16 16:20:47 +02:00
tll_free(term->normal.scroll_damage);
term_erase(term, 0, 0, term->rows - 1, term->cols - 1);
2020-08-16 16:20:47 +02:00
}
else if (!enable && term->grid == &term->alt) {
selection_cancel(term);
term->grid = &term->normal;
/* Cursor retains its position from the alt grid */
2020-08-16 16:20:47 +02:00
term_cursor_to(
term, min(term->alt.cursor.point.row, term->rows - 1),
min(term->alt.cursor.point.col, term->cols - 1));
2020-08-16 16:20:47 +02:00
if (param == 1049)
term_restore_cursor(term, &term->grid->saved_cursor);
2020-08-16 16:20:47 +02:00
/* 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_view(term);
2020-08-16 16:20:47 +02:00
}
term->bits_affecting_ascii_printer.sixels =
tll_length(term->grid->sixel_images) > 0;
term_update_ascii_printer(term);
2020-08-16 16:20:47 +02:00
break;
case 1070:
term->sixel.use_private_palette = enable;
break;
2020-08-16 16:20:47 +02:00
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 2027:
#if defined(FOOT_GRAPHEME_CLUSTERING)
term->grapheme_shaping = enable;
#endif
break;
case 2031:
term->report_theme_changes = enable;
break;
case 2048:
if (enable)
term_enable_size_notifications(term);
else
term_disable_size_notifications(term);
break;
case 8452:
term->sixel.cursor_right_of_graphics = enable;
break;
case 737769:
if (enable)
term_ime_enable(term);
else {
term_ime_disable(term);
term->ime_reenable_after_url_mode = false;
}
break;
2020-08-16 16:20:47 +02:00
default:
UNHANDLED();
break;
}
}
static void
decset(struct terminal *term, unsigned param)
{
decset_decrst(term, param, true);
}
static void
decrst(struct terminal *term, unsigned param)
{
2020-08-16 16:38:30 +02:00
decset_decrst(term, param, false);
2020-08-16 16:20:47 +02:00
}
/*
* These values represent the current state of a DEC private mode,
* as returned in the DECRPM reply to a DECRQM query.
*/
enum decrpm_status {
DECRPM_NOT_RECOGNIZED = 0,
DECRPM_SET = 1,
DECRPM_RESET = 2,
DECRPM_PERMANENTLY_SET = 3,
DECRPM_PERMANENTLY_RESET = 4,
};
static enum decrpm_status
decrpm(bool enabled)
{
return enabled ? DECRPM_SET : DECRPM_RESET;
}
static enum decrpm_status
decrqm(const struct terminal *term, unsigned param)
{
switch (param) {
case 1: return decrpm(term->cursor_keys_mode == CURSOR_KEYS_APPLICATION);
case 5: return decrpm(term->reverse);
case 6: return decrpm(term->origin);
case 7: return decrpm(term->auto_margin);
case 9: return DECRPM_PERMANENTLY_RESET; /* term->mouse_tracking == MOUSE_X10; */
case 12: return decrpm(term->cursor_blink.decset);
case 25: return decrpm(!term->hide_cursor);
case 45: return decrpm(term->reverse_wrap);
case 66: return decrpm(term->keypad_keys_mode == KEYPAD_APPLICATION);
case 67: return DECRPM_PERMANENTLY_RESET; /* https://vt100.net/docs/vt510-rm/DECBKM */
case 80: return decrpm(!term->sixel.scrolling);
case 1000: return decrpm(term->mouse_tracking == MOUSE_CLICK);
case 1001: return DECRPM_PERMANENTLY_RESET;
case 1002: return decrpm(term->mouse_tracking == MOUSE_DRAG);
case 1003: return decrpm(term->mouse_tracking == MOUSE_MOTION);
case 1004: return decrpm(term->focus_events);
case 1005: return DECRPM_PERMANENTLY_RESET; /* term->mouse_reporting == MOUSE_UTF8; */
case 1006: return decrpm(term->mouse_reporting == MOUSE_SGR);
case 1007: return decrpm(term->alt_scrolling);
case 1015: return decrpm(term->mouse_reporting == MOUSE_URXVT);
case 1016: return decrpm(term->mouse_reporting == MOUSE_SGR_PIXELS);
case 1034: return decrpm(term->meta.eight_bit);
case 1035: return decrpm(term->num_lock_modifier);
case 1036: return decrpm(term->meta.esc_prefix);
case 1042: return decrpm(term->bell_action_enabled);
case 47: /* FALLTHROUGH */
case 1047: /* FALLTHROUGH */
case 1049: return decrpm(term->grid == &term->alt);
case 1070: return decrpm(term->sixel.use_private_palette);
case 2004: return decrpm(term->bracketed_paste);
case 2026: return decrpm(term->render.app_sync_updates.enabled);
case 2027: return term->conf->tweak.grapheme_width_method != GRAPHEME_WIDTH_DOUBLE
? DECRPM_PERMANENTLY_RESET
: decrpm(term->grapheme_shaping);
case 2031: return decrpm(term->report_theme_changes);
case 2048: return decrpm(term->size_notifications);
case 8452: return decrpm(term->sixel.cursor_right_of_graphics);
case 737769: return decrpm(term_ime_is_enabled(term));
}
return DECRPM_NOT_RECOGNIZED;
}
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 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;
term: split cursor blink state into two There are two different escape sequences that can be used to set the cursor blink state: ‘CSI ? 12 h/l’ and ‘CSI Ps SP q’. Up until now, they both modified the same internal state in foot. This meant you could enable a blinking cursor with e.g. ‘CSI ? 12 h’ and then disable it with ‘CSI 2 SP q’. Since the ‘CSI ? 12’ escapes are used in the civis/cnorm/cvvis terminfo entries, applications often ended up disabling the blink state on exit (typically be emitting ‘cnorm’), requiring users to manually re-enable blinking. By splitting the internal state into two separate states, we can improve the situation. The cursor will blink if at least one of the two have been enabled. The setting in foot.ini sets the default state of the ‘CSI Ps SP q’ escape. This means if the user has enabled blinking in the configuration, the cursor will blink regardless of civis/cnorm/cvvis. Which probably is what the user wants. If the user has NOT enabled blinking, civis/cnorm/cvvis act as intended: cvvis blink, civis and cnorm do not. If an application overrides the cursor blink/style with ‘CSI Ps SP q’, that will override the user’s setting in foot.ini. But most likely that too is intended (for example, the user may have configured the application to use a different cursor style). And, a well written application will emit the ‘Se’ terminfo sequence on exit, which in foot is defined to ‘CSI SP q’, which will reset both the style and blink state to the user configured style/state. Closes #218
2020-11-26 18:09:32 +01:00
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 66: term->xtsave.application_keypad_keys = term->keypad_keys_mode == KEYPAD_APPLICATION; break;
case 67: 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;
2021-12-30 05:13:45 -06:00
case 1016: term->xtsave.mouse_sgr_pixels = term->mouse_reporting == MOUSE_SGR_PIXELS; 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 2027: term->xtsave.grapheme_shaping = term->grapheme_shaping; break;
case 2031: term->xtsave.report_theme_changes = term->report_theme_changes; break;
case 2048: term->xtsave.size_notifications = term->size_notifications; break;
case 8452: term->xtsave.sixel_cursor_right_of_graphics = term->sixel.cursor_right_of_graphics; break;
case 737769: term->xtsave.ime = term_ime_is_enabled(term); break;
}
}
2020-08-16 17:02:52 +02:00
static void
xtrestore(struct terminal *term, unsigned param)
{
bool enable;
switch (param) {
2020-08-18 21:13:50 +02:00
case 1: enable = term->xtsave.application_cursor_keys; break;
2020-08-16 17:02:52 +02:00
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;
2020-08-16 17:02:52 +02:00
case 25: enable = term->xtsave.show_cursor; break;
2020-10-03 11:53:03 +02:00
case 45: enable = term->xtsave.reverse_wrap; break;
case 47: enable = term->xtsave.alt_screen; break;
case 66: enable = term->xtsave.application_keypad_keys; break;
case 67: return;
case 80: enable = term->xtsave.sixel_display_mode; break;
2020-08-16 17:02:52 +02:00
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;
2021-12-30 05:13:45 -06:00
case 1016: enable = term->xtsave.mouse_sgr_pixels; break;
2020-08-16 17:02:52 +02:00
case 1034: enable = term->xtsave.meta_eight_bit; break;
case 1035: enable = term->xtsave.num_lock_modifier; break;
2020-08-16 17:02:52 +02:00
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;
2020-08-16 17:02:52 +02:00
case 1049: enable = term->xtsave.alt_screen; break;
case 1070: enable = term->xtsave.sixel_private_palette; break;
2020-08-16 17:02:52 +02:00
case 2004: enable = term->xtsave.bracketed_paste; break;
case 2026: enable = term->xtsave.app_sync_updates; break;
case 2027: enable = term->xtsave.grapheme_shaping; break;
case 2031: enable = term->xtsave.report_theme_changes; break;
case 2048: enable = term->xtsave.size_notifications; break;
case 8452: enable = term->xtsave.sixel_cursor_right_of_graphics; break;
case 737769: enable = term->xtsave.ime; break;
default: return;
2020-08-16 17:02:52 +02:00
}
decset_decrst(term, param, enable);
}
static bool
params_to_rectangular_area(const struct terminal *term, int first_idx,
int *top, int *left, int *bottom, int *right)
{
int rel_top = vt_param_get(term, first_idx + 0, 1) - 1;
*left = min(vt_param_get(term, first_idx + 1, 1) - 1, term->cols - 1);
int rel_bottom = vt_param_get(term, first_idx + 2, term->rows) - 1;
*right = min(vt_param_get(term, first_idx + 3, term->cols) - 1, term->cols - 1);
if (rel_top > rel_bottom || *left > *right)
return false;
*top = term_row_rel_to_abs(term, rel_top);
*bottom = term_row_rel_to_abs(term, rel_bottom);
return true;
}
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: {
2019-06-17 18:57:12 +02:00
switch (final) {
2020-01-20 18:37:20 +01:00
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
2020-08-15 19:39:00 +01:00
* _not_ preceded by a graphical character.
2020-01-20 18:37:20 +01:00
*/
int count = vt_param_get(term, 0, 1);
LOG_DBG("REP: '%lc' %d times", (wint_t)term->vt.last_printed, count);
2020-01-20 18:37:20 +01:00
const int width = c32width(term->vt.last_printed);
if (width > 0) {
for (int i = 0; i < count; i++)
term_print(term, term->vt.last_printed, width, false);
}
2020-01-20 18:37:20 +01:00
}
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
2020-08-15 19:39:00 +01:00
* 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".
*/
if (term->conf->tweak.sixel) {
static const char reply[] = "\033[?62;4;22;28c";
term_to_slave(term, reply, sizeof(reply) - 1);
} else {
static const char reply[] = "\033[?62;22;28c";
term_to_slave(term, reply, sizeof(reply) - 1);
}
break;
}
2019-06-17 18:57:12 +02:00
case 'd': {
2019-06-24 20:05:24 +02:00
/* 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;
}
2019-06-17 18:57:12 +02:00
case 'm':
csi_sgr(term);
break;
2019-06-17 18:57:12 +02:00
case 'A':
2019-07-10 16:04:16 +02:00
term_cursor_up(term, vt_param_get(term, 0, 1));
break;
case 'e':
case 'B':
2019-07-10 16:04:16 +02:00
term_cursor_down(term, vt_param_get(term, 0, 1));
break;
case 'a':
case 'C':
2019-07-10 16:04:16 +02:00
term_cursor_right(term, vt_param_get(term, 0, 1));
break;
case 'D':
2019-07-10 16:04:16 +02:00
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_col(term, col);
break;
}
2019-11-05 11:10:19 +01:00
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;
}
2019-06-17 18:57:12 +02:00
case 'J': {
2019-06-19 10:04:47 +02:00
/* Erase screen */
2019-07-10 16:04:16 +02:00
int param = vt_param_get(term, 0, 0);
2019-06-19 10:04:47 +02:00
switch (param) {
case 0: {
2019-06-19 10:04:47 +02:00
/* From cursor to end of screen */
const struct coord *cursor = &term->grid->cursor.point;
term_erase(
term,
cursor->row, cursor->col,
term->rows - 1, term->cols - 1);
term->grid->cursor.lcf = false;
2019-06-19 10:04:47 +02:00
break;
}
2019-06-19 10:04:47 +02:00
case 1: {
2019-06-19 10:04:47 +02:00
/* From start of screen to cursor */
const struct coord *cursor = &term->grid->cursor.point;
term_erase(term, 0, 0, cursor->row, cursor->col);
term->grid->cursor.lcf = false;
2019-06-19 10:04:47 +02:00
break;
}
2019-06-19 10:04:47 +02:00
case 2:
/* Erase entire screen */
term_erase(term, 0, 0, term->rows - 1, term->cols - 1);
term->grid->cursor.lcf = false;
2019-06-19 10:04:47 +02:00
break;
case 3: {
/* Erase scrollback */
term_erase_scrollback(term);
break;
}
2019-06-19 10:04:47 +02:00
default:
UNHANDLED();
break;
2019-06-19 10:04:47 +02:00
}
break;
2019-06-17 18:57:12 +02:00
}
case 'K': {
2019-06-19 10:04:47 +02:00
/* Erase line */
2019-07-10 16:04:16 +02:00
int param = vt_param_get(term, 0, 0);
2019-06-19 10:04:47 +02:00
switch (param) {
case 0: {
2019-06-19 10:04:47 +02:00
/* From cursor to end of line */
const struct coord *cursor = &term->grid->cursor.point;
term_erase(
term,
cursor->row, cursor->col,
cursor->row, term->cols - 1);
term->grid->cursor.lcf = false;
2019-06-19 10:04:47 +02:00
break;
}
2019-06-19 10:04:47 +02:00
case 1: {
2019-06-19 10:04:47 +02:00
/* From start of line to cursor */
const struct coord *cursor = &term->grid->cursor.point;
term_erase(term, cursor->row, 0, cursor->row, cursor->col);
term->grid->cursor.lcf = false;
2019-06-19 10:04:47 +02:00
break;
}
2019-06-19 10:04:47 +02:00
case 2: {
2019-06-19 10:04:47 +02:00
/* Entire line */
const struct coord *cursor = &term->grid->cursor.point;
term_erase(term, cursor->row, 0, cursor->row, term->cols - 1);
term->grid->cursor.lcf = false;
2019-06-19 10:04:47 +02:00
break;
}
2019-06-19 10:04:47 +02:00
default:
UNHANDLED();
break;
2019-06-19 10:04:47 +02:00
}
2019-06-17 18:57:12 +02:00
2019-06-19 10:04:47 +02:00
break;
}
case 'L': { /* IL */
if (term->grid->cursor.point.row < term->scroll_region.start ||
term->grid->cursor.point.row >= term->scroll_region.end)
2019-06-23 21:12:32 +02:00
break;
int count = min(
2019-07-10 16:04:16 +02:00
vt_param_get(term, 0, 1),
term->scroll_region.end - term->grid->cursor.point.row);
2019-06-23 21:12:32 +02:00
2019-06-29 21:03:28 +02:00
term_scroll_reverse_partial(
term,
(struct scroll_region){
.start = term->grid->cursor.point.row,
.end = term->scroll_region.end},
2019-06-23 21:12:32 +02:00
count);
2020-07-14 10:52:15 +02:00
term->grid->cursor.lcf = false;
term->grid->cursor.point.col = 0;
2019-06-23 21:12:32 +02:00
break;
}
case 'M': { /* DL */
if (term->grid->cursor.point.row < term->scroll_region.start ||
term->grid->cursor.point.row >= term->scroll_region.end)
2019-06-23 21:12:32 +02:00
break;
int count = min(
2019-07-10 16:04:16 +02:00
vt_param_get(term, 0, 1),
term->scroll_region.end - term->grid->cursor.point.row);
2019-06-23 21:12:32 +02:00
2019-06-29 21:03:28 +02:00
term_scroll_partial(
term,
(struct scroll_region){
.start = term->grid->cursor.point.row,
.end = term->scroll_region.end},
2019-06-23 21:12:32 +02:00
count);
2020-07-14 10:52:15 +02:00
term->grid->cursor.lcf = false;
term->grid->cursor.point.col = 0;
2019-06-23 21:12:32 +02:00
break;
}
case 'P': {
/* DCH: Delete character(s) */
2019-06-19 10:04:47 +02:00
/* Number of characters to delete */
int count = min(
vt_param_get(term, 0, 1), term->cols - term->grid->cursor.point.col);
2019-06-17 18:57:12 +02:00
/* 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 */
const struct coord *cursor = &term->grid->cursor.point;
term_erase(
term,
cursor->row, cursor->col + remaining,
cursor->row, term->cols - 1);
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) */
const struct coord *cursor = &term->grid->cursor.point;
term_erase(
term,
cursor->row, cursor->col,
cursor->row, cursor->col + count - 1);
term->grid->cursor.lcf = false;
2019-06-19 10:04:47 +02:00
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);
const struct coord *cursor = &term->grid->cursor.point;
term_erase(
term,
cursor->row, cursor->col,
cursor->row, cursor->col + count - 1);
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);
}
2019-07-22 20:33:50 +02:00
break;
case 'h':
case 'l': {
/* Set/Reset Mode (SM/RM) */
int param = vt_param_get(term, 0, 0);
bool sm = final == 'h';
if (param == 4) {
/* Insertion Replacement Mode (IRM) */
term->insert_mode = sm;
term->bits_affecting_ascii_printer.insert_mode = sm;
term_update_ascii_printer(term);
break;
}
/*
* ECMA-48 defines modes 1-22, all of which were optional
* (§7.1; "may have one state only") and are considered
* deprecated (§7.1) in the latest (5th) edition. xterm only
* documents modes 2, 4, 12 and 20, the last of which was
* outright removed (§8.3.106) in 5th edition ECMA-48.
*/
if (sm) {
LOG_WARN("SM with unimplemented mode: %d", param);
}
break;
}
2019-06-17 18:57:12 +02:00
case 'r': {
2019-07-10 16:04:16 +02:00
int start = vt_param_get(term, 0, 1);
int end = min(vt_param_get(term, 1, term->rows), term->rows);
2019-06-23 21:12:32 +02:00
if (end > start) {
2019-06-23 21:12:32 +02:00
/* 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;
2024-09-13 08:57:07 +02:00
case 20: LOG_WARN("unimplemented: report icon label"); 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];
2023-06-22 14:21:51 +02:00
size_t n = xsnprintf(
reply, sizeof(reply), "\033[4;%d;%dt", height, width);
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_real.height,
it->item->dim.px_real.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];
2023-06-22 14:21:51 +02:00
size_t n = xsnprintf(
reply, sizeof(reply), "\033[6;%d;%dt",
term->cell_height, term->cell_width);
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];
2023-06-22 14:21:51 +02:00
size_t n = xsnprintf(
reply, sizeof(reply), "\033[9;%d;%dt",
it->item->dim.px_real.height / term->cell_height,
it->item->dim.px_real.width / term->cell_width);
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 21: {
#if 0 /* Disabled for now, see #1894 */
char reply[3 + strlen(term->window_title) + 2 + 1];
int chars = xsnprintf(
reply, sizeof(reply), "\033]l%s\033\\", term->window_title);
term_to_slave(term, reply, chars);
#else
LOG_WARN("CSI 21 t (report window title) ignored");
#endif
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) {
2019-07-10 16:04:16 +02:00
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;
}
2019-06-17 18:57:12 +02:00
default:
UNHANDLED();
break;
2019-06-17 18:57:12 +02:00
}
break; /* private[0] == 0 */
}
case '?': {
switch (final) {
2020-08-16 16:20:47 +02:00
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;
2020-08-16 16:20:47 +02:00
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':
2020-08-16 16:47:46 +02:00
for (size_t i = 0; i < term->vt.params.idx; i++)
xtsave(term, term->vt.params.v[i].value);
break;
case 'r':
2020-08-16 17:02:52 +02:00
for (size_t i = 0; i < term->vt.params.idx; i++)
xtrestore(term, term->vt.params.v[i].value);
break;
case 'S': {
if (!term->conf->tweak.sixel) {
UNHANDLED();
break;
}
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;
}
2023-04-26 18:30:09 +02:00
case 'm': {
int resource = vt_param_get(term, 0, 0);
int value = -1;
switch (resource) {
case 0: /* modifyKeyboard */
value = 0;
break;
case 1: /* modifyCursorKeys */
case 2: /* modifyFunctionKeys */
value = 1;
break;
case 4: /* modifyOtherKeys */
value = term->modify_other_keys_2 ? 2 : 1;
break;
default:
LOG_WARN("XTQMODKEYS: invalid resource '%d' in '%s'",
resource, csi_as_string(term, final, -1));
break;
}
if (value >= 0) {
char reply[16] = {0};
int chars = snprintf(reply, sizeof(reply),
"\033[>%d;%dm", resource, value);
term_to_slave(term, reply, chars);
}
break;
}
case 'n': {
const int param = vt_param_get(term, 0, 0);
switch (param) {
case 996: { /* Query current theme mode (see private mode 2031) */
/*
* 1 - dark mode
* 2 - light mode
*
* In foot, the themes aren't necessarily light/dark,
* but by convention, the primary theme is dark, and
* the alternative theme is light.
*/
char reply[16] = {0};
int chars = snprintf(
reply, sizeof(reply),
"\033[?997;%dn",
term->colors.active_theme == COLOR_THEME1 ? 1 : 2);
term_to_slave(term, reply, chars);
break;
}
}
break;
}
case 'p': {
/*
* Request status of ECMA-48/"ANSI" private mode (DECRQM
* for SM/RM modes; see private="?$" case further below for
* DECSET/DECRST modes)
*/
unsigned param = vt_param_get(term, 0, 0);
unsigned status = DECRPM_NOT_RECOGNIZED;
if (param == 4) {
status = decrpm(term->insert_mode);
}
char reply[32];
size_t n = xsnprintf(reply, sizeof(reply), "\033[%u;%u$y", param, status);
term_to_slave(term, reply, n);
break;
}
case 'u': {
enum kitty_kbd_flags flags =
term->grid->kitty_kbd.flags[term->grid->kitty_kbd.idx];
char reply[8];
int chars = snprintf(reply, sizeof(reply), "\033[?%uu", flags);
term_to_slave(term, reply, chars);
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
2020-08-15 19:39:00 +01:00
* 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 */
/* Ignored, we always report modifiers */
if (value != 2 && value != -1) {
LOG_WARN(
"unimplemented: %s = %d",
resource == 1 ? "modifyCursorKeys" :
resource == 2 ? "modifyFunctionKeys" : "<invalid>",
value);
}
break;
case 4: /* modifyOtherKeys */
term->modify_other_keys_2 = value == 2;
LOG_DBG("modifyOtherKeys=%d", value);
break;
default:
2023-04-26 18:30:09 +02:00
LOG_WARN("XTMODKEYS: invalid resource '%d' in '%s'",
resource, csi_as_string(term, final, -1));
break;
}
}
break; /* final == 'm' */
case 'n': {
2023-03-29 00:31:49 +03:00
int resource = vt_param_get(term, 0, 2); /* Default is modifyFunctionKeys */
switch (resource) {
case 0: /* modifyKeyboard */
case 1: /* modifyCursorKeys */
case 2: /* modifyFunctionKeys */
break;
case 4: /* modifyOtherKeys */
/* We don't support fully disabling modifyOtherKeys,
* but simply revert back to mode '1' */
term->modify_other_keys_2 = false;
LOG_DBG("modifyOtherKeys=1");
break;
}
break;
}
case 'u': {
int flags = vt_param_get(term, 0, 0) & KITTY_KBD_SUPPORTED;
struct grid *grid = term->grid;
uint8_t idx = grid->kitty_kbd.idx;
if (idx + 1 >= ALEN(grid->kitty_kbd.flags)) {
/* Stack full, evict oldest by wrapping around */
idx = 0;
} else
idx++;
grid->kitty_kbd.flags[idx] = flags;
grid->kitty_kbd.idx = idx;
2021-11-16 16:37:10 +01:00
LOG_DBG("kitty kbd: pushed new flags: 0x%03x", flags);
break;
}
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();
2019-07-16 10:20:47 +02:00
break;
}
break; /* private[0] == '>' */
}
case '<': {
switch (final) {
case 'u': {
int count = vt_param_get(term, 0, 1);
LOG_DBG("kitty kbd: popping %d levels of flags", count);
struct grid *grid = term->grid;
uint8_t idx = grid->kitty_kbd.idx;
for (int i = 0; i < count; i++) {
/* Reset flags. This ensures we get flags=0 when
* over-popping */
grid->kitty_kbd.flags[idx] = 0;
if (idx == 0)
idx = ALEN(grid->kitty_kbd.flags) - 1;
else
idx--;
}
grid->kitty_kbd.idx = idx;
2021-11-16 16:37:10 +01:00
LOG_DBG("kitty kbd: flags after pop: 0x%03x",
term->grid->kitty_kbd.flags[idx]);
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.enabled;
term: split cursor blink state into two There are two different escape sequences that can be used to set the cursor blink state: ‘CSI ? 12 h/l’ and ‘CSI Ps SP q’. Up until now, they both modified the same internal state in foot. This meant you could enable a blinking cursor with e.g. ‘CSI ? 12 h’ and then disable it with ‘CSI 2 SP q’. Since the ‘CSI ? 12’ escapes are used in the civis/cnorm/cvvis terminfo entries, applications often ended up disabling the blink state on exit (typically be emitting ‘cnorm’), requiring users to manually re-enable blinking. By splitting the internal state into two separate states, we can improve the situation. The cursor will blink if at least one of the two have been enabled. The setting in foot.ini sets the default state of the ‘CSI Ps SP q’ escape. This means if the user has enabled blinking in the configuration, the cursor will blink regardless of civis/cnorm/cvvis. Which probably is what the user wants. If the user has NOT enabled blinking, civis/cnorm/cvvis act as intended: cvvis blink, civis and cnorm do not. If an application overrides the cursor blink/style with ‘CSI Ps SP q’, that will override the user’s setting in foot.ini. But most likely that too is intended (for example, the user may have configured the application to use a different cursor style). And, a well written application will emit the ‘Se’ terminfo sequence on exit, which in foot is defined to ‘CSI SP q’, which will reset both the style and blink state to the user configured style/state. Closes #218
2020-11-26 18:09:32 +01:00
term_cursor_blink_update(term);
break;
case 1: /* blinking block */
case 2: /* steady block */
term->cursor_style = term->conf->cursor.style == CURSOR_HOLLOW
? CURSOR_HOLLOW : 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: split cursor blink state into two There are two different escape sequences that can be used to set the cursor blink state: ‘CSI ? 12 h/l’ and ‘CSI Ps SP q’. Up until now, they both modified the same internal state in foot. This meant you could enable a blinking cursor with e.g. ‘CSI ? 12 h’ and then disable it with ‘CSI 2 SP q’. Since the ‘CSI ? 12’ escapes are used in the civis/cnorm/cvvis terminfo entries, applications often ended up disabling the blink state on exit (typically be emitting ‘cnorm’), requiring users to manually re-enable blinking. By splitting the internal state into two separate states, we can improve the situation. The cursor will blink if at least one of the two have been enabled. The setting in foot.ini sets the default state of the ‘CSI Ps SP q’ escape. This means if the user has enabled blinking in the configuration, the cursor will blink regardless of civis/cnorm/cvvis. Which probably is what the user wants. If the user has NOT enabled blinking, civis/cnorm/cvvis act as intended: cvvis blink, civis and cnorm do not. If an application overrides the cursor blink/style with ‘CSI Ps SP q’, that will override the user’s setting in foot.ini. But most likely that too is intended (for example, the user may have configured the application to use a different cursor style). And, a well written application will emit the ‘Se’ terminfo sequence on exit, which in foot is defined to ‘CSI SP q’, which will reset both the style and blink state to the user configured style/state. Closes #218
2020-11-26 18:09:32 +01:00
term->cursor_blink.deccsusr = param & 1;
term_cursor_blink_update(term);
}
break;
}
default:
UNHANDLED();
break;
}
break; /* private[0] == ' ' */
}
case '!': {
if (final == 'p') {
2019-08-01 20:51:11 +02:00
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;
case 'u': {
int flag_set = vt_param_get(term, 0, 0) & KITTY_KBD_SUPPORTED;
int mode = vt_param_get(term, 1, 1);
struct grid *grid = term->grid;
uint8_t idx = grid->kitty_kbd.idx;
switch (mode) {
case 1:
/* set bits are set, unset bits are reset */
grid->kitty_kbd.flags[idx] = flag_set;
break;
case 2:
/* set bits are set, unset bits are left unchanged */
grid->kitty_kbd.flags[idx] |= flag_set;
break;
case 3:
/* set bits are reset, unset bits are left unchanged */
grid->kitty_kbd.flags[idx] &= ~flag_set;
break;
default:
UNHANDLED();
break;
}
LOG_DBG("kitty kbd: flags after update: 0x%03x",
grid->kitty_kbd.flags[idx]);
break;
}
default:
UNHANDLED();
break;
}
break; /* private[0] == '=' */
}
case '$': {
switch (final) {
case 'r': { /* DECCARA */
int top, left, bottom, right;
if (!params_to_rectangular_area(
term, 0, &top, &left, &bottom, &right))
{
break;
}
for (int r = top; r <= bottom; r++) {
struct row *row = grid_row(term->grid, r);
2021-12-28 17:14:50 +01:00
row->dirty = true;
for (int c = left; c <= right; c++) {
struct attributes *a = &row->cells[c].attrs;
2021-12-28 17:14:50 +01:00
a->clean = 0;
for (size_t i = 4; i < term->vt.params.idx; i++) {
const int param = term->vt.params.v[i].value;
/* DECCARA only supports a sub-set of SGR parameters */
switch (param) {
case 0:
a->bold = false;
a->underline = false;
a->blink = false;
a->reverse = false;
break;
case 1: a->bold = true; break;
case 4: a->underline = true; break;
case 5: a->blink = true; break;
case 7: a->reverse = true; break;
case 22: a->bold = false; break;
case 24: a->underline = false; break;
case 25: a->blink = false; break;
case 27: a->reverse = false; break;
}
}
}
}
break;
}
case 't': { /* DECRARA */
int top, left, bottom, right;
if (!params_to_rectangular_area(
term, 0, &top, &left, &bottom, &right))
{
break;
}
for (int r = top; r <= bottom; r++) {
struct row *row = grid_row(term->grid, r);
2021-12-28 17:14:50 +01:00
row->dirty = true;
for (int c = left; c <= right; c++) {
struct attributes *a = &row->cells[c].attrs;
2021-12-28 17:14:50 +01:00
a->clean = 0;
for (size_t i = 4; i < term->vt.params.idx; i++) {
const int param = term->vt.params.v[i].value;
/* DECRARA only supports a sub-set of SGR parameters */
switch (param) {
case 0:
a->bold = !a->bold;
a->underline = !a->underline;
a->blink = !a->blink;
a->reverse = !a->reverse;
break;
case 1: a->bold = !a->bold; break;
case 4: a->underline = !a->underline; break;
case 5: a->blink = !a->blink; break;
case 7: a->reverse = !a->reverse; break;
}
}
}
}
break;
}
case 'v': { /* DECCRA */
int src_top, src_left, src_bottom, src_right;
if (!params_to_rectangular_area(
term, 0, &src_top, &src_left, &src_bottom, &src_right))
{
break;
}
int src_page = vt_param_get(term, 4, 1);
int dst_rel_top = vt_param_get(term, 5, 1) - 1;
int dst_left = vt_param_get(term, 6, 1) - 1;
int dst_page = vt_param_get(term, 7, 1);
if (unlikely(src_page != 1 || dst_page != 1)) {
/* We dont support “pages” */
break;
}
int dst_rel_bottom = dst_rel_top + (src_bottom - src_top);
int dst_right = min(dst_left + (src_right - src_left), term->cols - 1);
int dst_top = term_row_rel_to_abs(term, dst_rel_top);
int dst_bottom = term_row_rel_to_abs(term, dst_rel_bottom);
/* Target area outside the screen is clipped */
const size_t row_count = min(src_bottom - src_top,
dst_bottom - dst_top) + 1;
const size_t cell_count = min(src_right - src_left,
dst_right - dst_left) + 1;
sixel_overwrite_by_rectangle(
term, dst_top, dst_left, row_count, cell_count);
/*
* Copy source area
*
* Note: since source and destination may overlap, we need
* to copy out the entire source region first, and _then_
* write the destination. I.e. this is similar to how
* memmove() behaves, but adapted to our row/cell
* structure.
*/
struct cell **copy = xmalloc(row_count * sizeof(copy[0]));
for (int r = 0; r < row_count; r++) {
copy[r] = xmalloc(cell_count * sizeof(copy[r][0]));
const struct row *row = grid_row(term->grid, src_top + r);
const struct cell *cell = &row->cells[src_left];
memcpy(copy[r], cell, cell_count * sizeof(copy[r][0]));
}
/* Paste into destination area */
for (int r = 0; r < row_count; r++) {
struct row *row = grid_row(term->grid, dst_top + r);
row->dirty = true;
struct cell *cell = &row->cells[dst_left];
memcpy(cell, copy[r], cell_count * sizeof(copy[r][0]));
free(copy[r]);
for (;cell < &row->cells[dst_left + cell_count]; cell++)
cell->attrs.clean = 0;
if (unlikely(row->extra != NULL)) {
/* TODO: technically, we should copy the source URIs... */
grid_row_uri_range_erase(row, dst_left, dst_right);
}
}
free(copy);
break;
}
case 'x': { /* DECFRA */
const uint8_t c = vt_param_get(term, 0, 0);
if (unlikely(!((c >= 32 && c < 126) || c >= 160)))
break;
int top, left, bottom, right;
if (!params_to_rectangular_area(
term, 1, &top, &left, &bottom, &right))
{
break;
}
/* Erase the entire region at once (MUCH cheaper than
* doing it row by row, or even character by
* character). */
sixel_overwrite_by_rectangle(
term, top, left, bottom - top + 1, right - left + 1);
for (int r = top; r <= bottom; r++)
term_fill(term, r, left, c, right - left + 1, true);
break;
}
case 'z': { /* DECERA */
int top, left, bottom, right;
if (!params_to_rectangular_area(
term, 0, &top, &left, &bottom, &right))
{
break;
}
/*
* Note: term_erase() _also_ erases sixels, but since
* were forced to erase one row at a time, erasing the
* entire sixel here is more efficient.
*/
sixel_overwrite_by_rectangle(
term, top, left, bottom - top + 1, right - left + 1);
for (int r = top; r <= bottom; r++)
term_erase(term, r, left, r, right);
break;
}
}
break; /* private[0] == $ */
}
csi: implement XTPUSHCOLORS+XTPOPCOLORS+XTREPORTCOLORS The documentation of these sequences are vague and lacking, as is often the case with XTerm invented control sequences. I've tried to replicate what XTerm does (as of xterm-392). The stack represents *stashed/stored* palettes. The currently active palette is *not* stored on the stack. The stack is dynamically allocated, and starts out with zero elements. Now, XTerm has a somewhat weird definition of "pushing" and "popping" in this context, and the documentation is somewhat misleading. What a push does is this: it stores the current palette to the stack at the specified slot. If the specified slot number (Pm) is 0, the slot used is the current slot index incremented by 1. The "current" slot index is then set to the specified slot (which is current slot + 1 if Pm == 0). Thus, "push" (i.e. when Pm == 0 is used) means store to the "next" slot. This is true even if the current slot index points into the middle of stack. Pop works in a similar way. The palette is restored from the specified slot index. If the specified slot number is 0, we use the current slot index. The "current" slot index is then set to the specified slot - 1 (current slot - 1 if Pm == 0). XTREPORTCOLORS return the current slot index, and the number of palettes stored on the stack, on the format CSI ? <slot index> ; <palette count> # Q When XTPUSHCOLORS grows the stack with more than one element (i.e. via a 'CSI N # P' sequence), make sure *all* new slots are initialized (to the current color palette). This avoids uninitialized slots, that could then be popped with XTPOPCOLORS. Closes #856
2024-07-01 17:40:45 +02:00
case '#': {
switch (final) {
case 'P': { /* XTPUSHCOLORS */
int slot = vt_param_get(term, 0, 0);
/* Pm == 0, "push" (what xterm does is take take the
*current* slot + 1, even if that's in the middle of the
stack, and overwrites whatever is already in that
slot) */
if (slot == 0)
slot = term->color_stack.idx + 1;
if (term->color_stack.size < slot) {
const size_t new_size = slot;
term->color_stack.stack = xrealloc(
term->color_stack.stack,
new_size * sizeof(term->color_stack.stack[0]));
/* Initialize new slots (except the selected slot,
which is done below) */
xassert(new_size > 0);
for (size_t i = term->color_stack.size; i < new_size - 1; i++) {
memcpy(&term->color_stack.stack[i], &term->colors,
sizeof(term->colors));
}
term->color_stack.size = new_size;
}
xassert(slot > 0);
xassert(slot <= term->color_stack.size);
term->color_stack.idx = slot;
memcpy(&term->color_stack.stack[slot - 1], &term->colors,
sizeof(term->colors));
break;
}
case 'Q': { /* XTPOPCOLORS */
int slot = vt_param_get(term, 0, 0);
/* Pm == 0, "pop" (what xterm does is copy colors from the
*current* slot, *and* decrease the current slot index,
even if that's in the middle of the stack) */
if (slot == 0)
slot = term->color_stack.idx;
if (slot > 0 && slot <= term->color_stack.size) {
memcpy(&term->colors, &term->color_stack.stack[slot - 1],
sizeof(term->colors));
term->color_stack.idx = slot - 1;
/* Assume a full palette switch *will* affect almost
all cells. The alternative is to call
term_damage_color() for all 256 palette entries
*and* the default fg/bg (256 + 2 calls in total) */
csi: implement XTPUSHCOLORS+XTPOPCOLORS+XTREPORTCOLORS The documentation of these sequences are vague and lacking, as is often the case with XTerm invented control sequences. I've tried to replicate what XTerm does (as of xterm-392). The stack represents *stashed/stored* palettes. The currently active palette is *not* stored on the stack. The stack is dynamically allocated, and starts out with zero elements. Now, XTerm has a somewhat weird definition of "pushing" and "popping" in this context, and the documentation is somewhat misleading. What a push does is this: it stores the current palette to the stack at the specified slot. If the specified slot number (Pm) is 0, the slot used is the current slot index incremented by 1. The "current" slot index is then set to the specified slot (which is current slot + 1 if Pm == 0). Thus, "push" (i.e. when Pm == 0 is used) means store to the "next" slot. This is true even if the current slot index points into the middle of stack. Pop works in a similar way. The palette is restored from the specified slot index. If the specified slot number is 0, we use the current slot index. The "current" slot index is then set to the specified slot - 1 (current slot - 1 if Pm == 0). XTREPORTCOLORS return the current slot index, and the number of palettes stored on the stack, on the format CSI ? <slot index> ; <palette count> # Q When XTPUSHCOLORS grows the stack with more than one element (i.e. via a 'CSI N # P' sequence), make sure *all* new slots are initialized (to the current color palette). This avoids uninitialized slots, that could then be popped with XTPOPCOLORS. Closes #856
2024-07-01 17:40:45 +02:00
term_damage_view(term);
term_damage_margins(term);
csi: implement XTPUSHCOLORS+XTPOPCOLORS+XTREPORTCOLORS The documentation of these sequences are vague and lacking, as is often the case with XTerm invented control sequences. I've tried to replicate what XTerm does (as of xterm-392). The stack represents *stashed/stored* palettes. The currently active palette is *not* stored on the stack. The stack is dynamically allocated, and starts out with zero elements. Now, XTerm has a somewhat weird definition of "pushing" and "popping" in this context, and the documentation is somewhat misleading. What a push does is this: it stores the current palette to the stack at the specified slot. If the specified slot number (Pm) is 0, the slot used is the current slot index incremented by 1. The "current" slot index is then set to the specified slot (which is current slot + 1 if Pm == 0). Thus, "push" (i.e. when Pm == 0 is used) means store to the "next" slot. This is true even if the current slot index points into the middle of stack. Pop works in a similar way. The palette is restored from the specified slot index. If the specified slot number is 0, we use the current slot index. The "current" slot index is then set to the specified slot - 1 (current slot - 1 if Pm == 0). XTREPORTCOLORS return the current slot index, and the number of palettes stored on the stack, on the format CSI ? <slot index> ; <palette count> # Q When XTPUSHCOLORS grows the stack with more than one element (i.e. via a 'CSI N # P' sequence), make sure *all* new slots are initialized (to the current color palette). This avoids uninitialized slots, that could then be popped with XTPOPCOLORS. Closes #856
2024-07-01 17:40:45 +02:00
} else if (slot == 0) {
LOG_ERR("XTPOPCOLORS: cannot pop beyond the first element");
} else {
LOG_ERR(
"XTPOPCOLORS: invalid color slot: %d "
"(stack has %zu slots, current slot is %zu)",
vt_param_get(term, 0, 0),
term->color_stack.size, term->color_stack.idx);
}
break;
}
case 'R': { /* XTREPORTCOLORS */
char reply[64];
size_t n = xsnprintf(reply, sizeof(reply), "\033[?%zu;%zu#Q",
csi: implement XTPUSHCOLORS+XTPOPCOLORS+XTREPORTCOLORS The documentation of these sequences are vague and lacking, as is often the case with XTerm invented control sequences. I've tried to replicate what XTerm does (as of xterm-392). The stack represents *stashed/stored* palettes. The currently active palette is *not* stored on the stack. The stack is dynamically allocated, and starts out with zero elements. Now, XTerm has a somewhat weird definition of "pushing" and "popping" in this context, and the documentation is somewhat misleading. What a push does is this: it stores the current palette to the stack at the specified slot. If the specified slot number (Pm) is 0, the slot used is the current slot index incremented by 1. The "current" slot index is then set to the specified slot (which is current slot + 1 if Pm == 0). Thus, "push" (i.e. when Pm == 0 is used) means store to the "next" slot. This is true even if the current slot index points into the middle of stack. Pop works in a similar way. The palette is restored from the specified slot index. If the specified slot number is 0, we use the current slot index. The "current" slot index is then set to the specified slot - 1 (current slot - 1 if Pm == 0). XTREPORTCOLORS return the current slot index, and the number of palettes stored on the stack, on the format CSI ? <slot index> ; <palette count> # Q When XTPUSHCOLORS grows the stack with more than one element (i.e. via a 'CSI N # P' sequence), make sure *all* new slots are initialized (to the current color palette). This avoids uninitialized slots, that could then be popped with XTPOPCOLORS. Closes #856
2024-07-01 17:40:45 +02:00
term->color_stack.idx, term->color_stack.size);
term_to_slave(term, reply, n);
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
*/
unsigned status = decrqm(term, param);
char reply[32];
size_t n = xsnprintf(reply, sizeof(reply), "\033[?%u;%u$y", param, status);
term_to_slave(term, reply, n);
break;
}
default:
UNHANDLED();
break;
}
break; /* private[0] == '?' && private[1] == '$' */
default:
UNHANDLED();
break;
}
}