mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-04 04:06:06 -05:00
2529 lines
86 KiB
C
2529 lines
86 KiB
C
#include "csi.h"
|
||
|
||
#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;
|
||
}
|
||
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;
|
||
}
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
|
||
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 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 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;
|
||
|
||
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 1016:
|
||
if (enable)
|
||
term->mouse_reporting = MOUSE_SGR_PIXELS;
|
||
else if (term->mouse_reporting == MOUSE_SGR_PIXELS)
|
||
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, 0, 0, term->rows - 1, term->cols - 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_view(term);
|
||
}
|
||
|
||
term->bits_affecting_ascii_printer.sixels =
|
||
tll_length(term->grid->sixel_images) > 0;
|
||
term_update_ascii_printer(term);
|
||
term_remove_all_multi_cursors(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 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;
|
||
|
||
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);
|
||
}
|
||
|
||
/*
|
||
* 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;
|
||
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;
|
||
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;
|
||
}
|
||
}
|
||
|
||
static void
|
||
xtrestore(struct terminal *term, unsigned param)
|
||
{
|
||
bool enable;
|
||
switch (param) {
|
||
case 1: enable = term->xtsave.application_cursor_keys; break;
|
||
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 66: enable = term->xtsave.application_keypad_keys; break;
|
||
case 67: return;
|
||
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 1016: enable = term->xtsave.mouse_sgr_pixels; 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 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;
|
||
}
|
||
|
||
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: {
|
||
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);
|
||
|
||
int width;
|
||
|
||
if (term->vt.last_printed >= CELL_COMB_CHARS_LO) {
|
||
const struct composed *comp = composed_lookup(
|
||
term->composed, term->vt.last_printed - CELL_COMB_CHARS_LO);
|
||
|
||
xassert(comp != NULL);
|
||
width = comp->forced_width > 0 ? comp->forced_width : comp->width;
|
||
} else
|
||
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);
|
||
}
|
||
}
|
||
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).
|
||
* - 52 Clipboard access
|
||
*
|
||
* 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".
|
||
*/
|
||
char reply[32];
|
||
|
||
int len = snprintf(
|
||
reply, sizeof(reply), "\033[?62%s;22;28%sc",
|
||
term->conf->tweak.sixel ? ";4" : "",
|
||
(term->conf->security.osc52 == OSC52_ENABLED ||
|
||
term->conf->security.osc52 == OSC52_COPY_ENABLED ? ";52" : ""));
|
||
|
||
term_to_slave(term, reply, len);
|
||
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_col(term, 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 */
|
||
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;
|
||
break;
|
||
}
|
||
|
||
case 1: {
|
||
/* 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;
|
||
break;
|
||
}
|
||
|
||
case 2:
|
||
/* Erase entire screen */
|
||
term_erase(term, 0, 0, term->rows - 1, term->cols - 1);
|
||
term->grid->cursor.lcf = false;
|
||
term_remove_all_multi_cursors(term);
|
||
break;
|
||
|
||
case 3: {
|
||
/* Erase scrollback */
|
||
term_erase_scrollback(term);
|
||
term_remove_all_multi_cursors(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 */
|
||
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;
|
||
break;
|
||
}
|
||
|
||
case 1: {
|
||
/* 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;
|
||
break;
|
||
}
|
||
|
||
case 2: {
|
||
/* 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;
|
||
break;
|
||
}
|
||
|
||
default:
|
||
UNHANDLED();
|
||
break;
|
||
}
|
||
|
||
break;
|
||
}
|
||
|
||
case 'L': { /* IL */
|
||
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': { /* DL */
|
||
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 */
|
||
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;
|
||
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);
|
||
}
|
||
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;
|
||
}
|
||
|
||
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 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, 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];
|
||
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];
|
||
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) {
|
||
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': {
|
||
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;
|
||
}
|
||
|
||
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
|
||
* 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:
|
||
LOG_WARN("XTMODKEYS: invalid resource '%d' in '%s'",
|
||
resource, csi_as_string(term, final, -1));
|
||
break;
|
||
}
|
||
}
|
||
break; /* final == 'm' */
|
||
|
||
case 'n': {
|
||
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;
|
||
|
||
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();
|
||
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;
|
||
|
||
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_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->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;
|
||
|
||
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);
|
||
row->dirty = true;
|
||
|
||
for (int c = left; c <= right; c++) {
|
||
struct attributes *a = &row->cells[c].attrs;
|
||
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);
|
||
row->dirty = true;
|
||
|
||
for (int c = left; c <= right; c++) {
|
||
struct attributes *a = &row->cells[c].attrs;
|
||
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 don’t 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
|
||
* we’re 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] == ‘$’ */
|
||
}
|
||
|
||
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) */
|
||
term_damage_view(term);
|
||
term_damage_margins(term);
|
||
} 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",
|
||
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] == '$' */
|
||
|
||
case 0x203e: /* '> ' */
|
||
switch (final) {
|
||
case 'q': {
|
||
if (term->vt.params.idx == 0) {
|
||
term_to_slave(term, "\033[>1;2;3;29;30;40;100;101 q", 27);
|
||
break;
|
||
}
|
||
|
||
const unsigned shape = vt_param_get(term, 0, 0);
|
||
switch (shape) {
|
||
case MULTI_CURSOR_SHAPE_NONE:
|
||
case MULTI_CURSOR_SHAPE_BLOCK:
|
||
case MULTI_CURSOR_SHAPE_BEAM:
|
||
case MULTI_CURSOR_SHAPE_UNDERLINE:
|
||
case MULTI_CURSOR_SHAPE_PRIMARY:
|
||
LOG_DBG("multi-cursor: %s",
|
||
shape == MULTI_CURSOR_SHAPE_NONE ? "no cursor" :
|
||
shape == MULTI_CURSOR_SHAPE_BLOCK ? "block cursor" :
|
||
shape == MULTI_CURSOR_SHAPE_BEAM ? "beam cursor" :
|
||
shape == MULTI_CURSOR_SHAPE_UNDERLINE ? "underline cursor" : "main cursor");
|
||
|
||
pixman_region32_t modified;
|
||
pixman_region32_init(&modified);
|
||
|
||
for (size_t i = 1; i < term->vt.params.idx; i++) {
|
||
const unsigned coord_type = vt_param_get(term, i, 0);
|
||
const struct vt_param *param = &term->vt.params.v[i];
|
||
|
||
switch (coord_type) {
|
||
case 0:
|
||
LOG_DBG("multi-cursor: unimplemented: "
|
||
"coordinate relative to main cursor");
|
||
break;
|
||
|
||
case 2:{
|
||
const size_t pair_count = param->sub.idx / 2;
|
||
|
||
for (size_t j = 0; j < pair_count; j++) {
|
||
const unsigned row = min(param->sub.value[j * 2 + 0], term->rows) - 1;
|
||
const unsigned col = min(param->sub.value[j * 2 + 1], term->cols) - 1;
|
||
|
||
LOG_DBG("pair: row=%u, col=%u", row, col);
|
||
pixman_region32_union_rect(&modified, &modified, col, row, 1, 1);
|
||
}
|
||
break;
|
||
}
|
||
|
||
case 4: {
|
||
if (param->sub.idx == 0) {
|
||
/* Special case, rectangle is the entire screen */
|
||
LOG_DBG("rectangle: all screen");
|
||
pixman_region32_union_rect(&modified, &modified, 0, 0, term->cols, term->rows);
|
||
} else {
|
||
const size_t rect_count = param->sub.idx / 4;
|
||
for (size_t j = 0; j < rect_count; j++) {
|
||
const unsigned top = min(param->sub.value[j * 4 + 0], term->rows) - 1;
|
||
const unsigned left = min(param->sub.value[j * 4 + 1], term->cols) - 1;
|
||
const unsigned bottom = max(min(param->sub.value[j * 4 + 2], term->rows) - 1, top);
|
||
const unsigned right = max(min(param->sub.value[j * 4 + 3], term->cols) - 1, left);
|
||
|
||
LOG_DBG(
|
||
"rectangle top=%u, left=%u, bottom=%u, right=%u (width=%u, height=%u",
|
||
top, left, bottom, right, right - left + 1, bottom - top + 1);
|
||
|
||
pixman_region32_union_rect(&modified, &modified, left, top, right - left + 1, bottom - top + 1);
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
|
||
default:
|
||
LOG_WARN("multi-cursor: invalid coordinate type: %u", coord_type);
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (shape == MULTI_CURSOR_SHAPE_NONE) {
|
||
/* Cursors disabled, remove from active set */
|
||
pixman_region32_subtract(
|
||
&term->multi_cursor.active,
|
||
&term->multi_cursor.active,
|
||
&modified);
|
||
} else {
|
||
/* Add new/updated cursors to active set */
|
||
pixman_region32_union(
|
||
&term->multi_cursor.active,
|
||
&term->multi_cursor.active,
|
||
&modified);
|
||
}
|
||
|
||
if (pixman_region32_empty(&term->multi_cursor.active)) {
|
||
free(term->multi_cursor.shapes);
|
||
term->multi_cursor.shapes = NULL;
|
||
} else {
|
||
if (term->multi_cursor.shapes == NULL) {
|
||
term->multi_cursor.shapes = xcalloc(
|
||
term->grid->num_cols * term->grid->num_rows,
|
||
sizeof(enum multi_cursor_shape));
|
||
}
|
||
}
|
||
|
||
int rect_count = 0;
|
||
const pixman_box32_t *boxes =
|
||
pixman_region32_rectangles(&modified, &rect_count);
|
||
|
||
/* Set shape, and dirty affected cells */
|
||
for (const pixman_box32_t *box = boxes; box < &boxes[rect_count]; box++) {
|
||
for (int r = box->y1; r < box->y2; r++) {
|
||
struct row *row = grid_row(term->grid, r);
|
||
xassert(row != NULL);
|
||
|
||
row->dirty = true;
|
||
|
||
for (int c = box->x1; c < box->x2; c++) {
|
||
if (term->multi_cursor.shapes != NULL)
|
||
term->multi_cursor.shapes[r * term->cols + c] = shape;
|
||
row->cells[c].attrs.clean = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
pixman_region32_fini(&modified);
|
||
break;
|
||
|
||
case MULTI_CURSOR_SHAPE_TEXT_COLOR:
|
||
case MULTI_CURSOR_SHAPE_CURSOR_COLOR: {
|
||
const unsigned color_space = vt_param_get(term, 1, 0);
|
||
|
||
enum multi_cursor_color_source color_source = MULTI_CURSOR_COLOR_PRIMARY;
|
||
uint32_t color = shape == MULTI_CURSOR_SHAPE_TEXT_COLOR
|
||
? term->multi_cursor.text_color
|
||
: term->multi_cursor.cursor_color;
|
||
|
||
switch (color_space) {
|
||
case MULTI_CURSOR_COLOR_PRIMARY:
|
||
color_source = MULTI_CURSOR_COLOR_PRIMARY;
|
||
break;
|
||
|
||
case MULTI_CURSOR_COLOR_SPECIAL:
|
||
color_source = MULTI_CURSOR_COLOR_SPECIAL;
|
||
break;
|
||
|
||
case MULTI_CURSOR_COLOR_RGB: {
|
||
const struct vt_param *param = &term->vt.params.v[1];
|
||
if (param->sub.idx == 3) {
|
||
const uint8_t red = param->sub.value[0];
|
||
const uint8_t green = param->sub.value[1];
|
||
const uint8_t blue = param->sub.value[2];
|
||
color_source = MULTI_CURSOR_COLOR_RGB;
|
||
color = 0xffu << 24 | red << 16 | green << 8 | blue;
|
||
}
|
||
break;
|
||
}
|
||
|
||
case MULTI_CURSOR_COLOR_256: {
|
||
const struct vt_param *param = &term->vt.params.v[1];
|
||
if (param->sub.idx == 1) {
|
||
const unsigned color_index = min(
|
||
param->sub.value[0], ALEN(term->colors.table) - 1);
|
||
|
||
color_source = MULTI_CURSOR_COLOR_256;
|
||
color = color_index;
|
||
}
|
||
break;
|
||
}
|
||
|
||
default:
|
||
LOG_WARN("multi-cursor: invalid color space: %u", color_space);
|
||
return;
|
||
}
|
||
|
||
if (shape == MULTI_CURSOR_SHAPE_TEXT_COLOR) {
|
||
term->multi_cursor.text_color_source = color_source;
|
||
term->multi_cursor.text_color = color;
|
||
} else {
|
||
term->multi_cursor.cursor_color_source = color_source;
|
||
term->multi_cursor.cursor_color = color;
|
||
}
|
||
break;
|
||
}
|
||
|
||
case MULTI_CURSOR_SHAPE_QUERY_CURSORS: {
|
||
term_to_slave(term, "\033[>100", 6);
|
||
|
||
int rect_count = 0;
|
||
const pixman_box32_t *boxes =
|
||
pixman_region32_rectangles(
|
||
&term->multi_cursor.active, &rect_count);
|
||
|
||
xassert(rect_count > 0 || term->multi_cursor.shapes == NULL);
|
||
|
||
for (int j = 0; j < rect_count; j++) {
|
||
const pixman_box32_t *box = &boxes[j];
|
||
for (int r = box->y1; r < box->y2; r++) {
|
||
for (int c = box->x1; c < box->x2; c++) {
|
||
const enum multi_cursor_shape shape =
|
||
term->multi_cursor.shapes[r * term->cols + c];
|
||
|
||
xassert(shape != MULTI_CURSOR_SHAPE_NONE);
|
||
char reply[64];
|
||
int len = snprintf(
|
||
reply, sizeof(reply),
|
||
";%u:2:%d:%d", shape, r + 1, c + 1);
|
||
|
||
term_to_slave(term, reply, len);
|
||
}
|
||
}
|
||
}
|
||
term_to_slave(term, " q", 2);
|
||
break;
|
||
}
|
||
|
||
case MULTI_CURSOR_SHAPE_QUERY_COLORS:
|
||
term_to_slave(term, "\033[>101", 7);
|
||
|
||
char reply[64];
|
||
int len = 0;
|
||
|
||
switch (term->multi_cursor.text_color_source) {
|
||
case MULTI_CURSOR_COLOR_PRIMARY:
|
||
len = snprintf(reply, sizeof(reply), ";30:%u", MULTI_CURSOR_COLOR_PRIMARY);
|
||
break;
|
||
|
||
case MULTI_CURSOR_COLOR_SPECIAL:
|
||
len = snprintf(reply, sizeof(reply), ";30:%u", MULTI_CURSOR_COLOR_SPECIAL);
|
||
break;
|
||
|
||
case MULTI_CURSOR_COLOR_RGB: {
|
||
const uint32_t color = term->multi_cursor.text_color;
|
||
const uint8_t red = (color >> 16) & 0xff;
|
||
const uint8_t green = (color >> 8) & 0xff;
|
||
const uint8_t blue = (color >> 0) & 0xff;
|
||
|
||
len = snprintf(reply, sizeof(reply), ";30:%u:%hhu:%hhu:%hhu",
|
||
MULTI_CURSOR_COLOR_RGB, red, green, blue);
|
||
break;
|
||
}
|
||
|
||
case MULTI_CURSOR_COLOR_256:
|
||
len = snprintf(reply, sizeof(reply), ";30:%u:%u",
|
||
MULTI_CURSOR_COLOR_256,
|
||
term->multi_cursor.text_color);
|
||
break;
|
||
}
|
||
|
||
term_to_slave(term, reply, len);
|
||
|
||
len = 0;
|
||
switch (term->multi_cursor.cursor_color_source) {
|
||
case MULTI_CURSOR_COLOR_PRIMARY:
|
||
len = snprintf(reply, sizeof(reply), ";40:%u", MULTI_CURSOR_COLOR_PRIMARY);
|
||
break;
|
||
|
||
case MULTI_CURSOR_COLOR_SPECIAL:
|
||
len = snprintf(reply, sizeof(reply), ";40:%u", MULTI_CURSOR_COLOR_SPECIAL);
|
||
break;
|
||
|
||
case MULTI_CURSOR_COLOR_RGB: {
|
||
const uint32_t color = term->multi_cursor.cursor_color;
|
||
const uint8_t red = (color >> 16) & 0xff;
|
||
const uint8_t green = (color >> 8) & 0xff;
|
||
const uint8_t blue = (color >> 0) & 0xff;
|
||
|
||
len = snprintf(reply, sizeof(reply), ";40:%u:%hhu:%hhu:%hhu",
|
||
MULTI_CURSOR_COLOR_RGB, red, green, blue);
|
||
break;
|
||
}
|
||
|
||
case MULTI_CURSOR_COLOR_256:
|
||
len = snprintf(reply, sizeof(reply), ";40:%u:%u",
|
||
MULTI_CURSOR_COLOR_256,
|
||
term->multi_cursor.cursor_color);
|
||
break;
|
||
}
|
||
|
||
term_to_slave(term, reply, len);
|
||
|
||
term_to_slave(term, " q", 2);
|
||
break;
|
||
|
||
default:
|
||
LOG_WARN("multi-cursor: invalid shape/color command: %u", shape);
|
||
return;
|
||
}
|
||
|
||
break;
|
||
}
|
||
|
||
default:
|
||
UNHANDLED();
|
||
break;
|
||
}
|
||
break; /* private[0] == '>' && private[1] == ' ' */
|
||
|
||
default:
|
||
UNHANDLED();
|
||
break;
|
||
}
|
||
}
|