mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-05 04:06:08 -05:00
1303 lines
40 KiB
C
1303 lines
40 KiB
C
#include "csi.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <assert.h>
|
|
|
|
#if defined(_DEBUG)
|
|
#include <stdio.h>
|
|
#endif
|
|
|
|
#define LOG_MODULE "csi"
|
|
#define LOG_ENABLE_DBG 0
|
|
#include "log.h"
|
|
#include "grid.h"
|
|
#include "vt.h"
|
|
#include "selection.h"
|
|
|
|
#define min(x, y) ((x) < (y) ? (x) : (y))
|
|
|
|
#define UNHANDLED() LOG_DBG("unhandled: %s", csi_as_string(term, final))
|
|
#define UNHANDLED_SGR() LOG_DBG("unhandled: %s", csi_as_string(term, 'm'))
|
|
|
|
static void
|
|
sgr_reset(struct terminal *term)
|
|
{
|
|
memset(&term->vt.attrs, 0, sizeof(term->vt.attrs));
|
|
term->vt.attrs.fg = term->colors.fg;
|
|
term->vt.attrs.bg = term->colors.bg;
|
|
}
|
|
|
|
static const char *
|
|
csi_as_string(struct terminal *term, uint8_t final)
|
|
{
|
|
static char msg[1024];
|
|
int c = snprintf(msg, sizeof(msg), "CSI: ");
|
|
|
|
for (size_t i = 0; i < sizeof(term->vt.private) / sizeof(term->vt.private[0]); i++) {
|
|
if (term->vt.private[i] == 0)
|
|
break;
|
|
c += snprintf(&msg[c], sizeof(msg) - c, "%c", term->vt.private[i]);
|
|
}
|
|
|
|
for (size_t i = 0; i < term->vt.params.idx; i++){
|
|
c += snprintf(&msg[c], sizeof(msg) - c, "%d",
|
|
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, ":%d",
|
|
term->vt.params.v[i].sub.value[j]);
|
|
}
|
|
|
|
c += snprintf(&msg[c], sizeof(msg) - c, "%s",
|
|
i == term->vt.params.idx - 1 ? "" : ";");
|
|
}
|
|
|
|
snprintf(&msg[c], sizeof(msg) - c, "%c (%zu parameters)",
|
|
final, term->vt.params.idx);
|
|
return msg;
|
|
}
|
|
|
|
static void
|
|
csi_sgr(struct terminal *term)
|
|
{
|
|
if (term->vt.params.idx == 0) {
|
|
sgr_reset(term);
|
|
return;
|
|
}
|
|
|
|
for (size_t i = 0; i < term->vt.params.idx; i++) {
|
|
const int param = term->vt.params.v[i].value;
|
|
|
|
switch (param) {
|
|
case 0:
|
|
sgr_reset(term);
|
|
break;
|
|
|
|
case 1: term->vt.attrs.bold = true; break;
|
|
case 2: term->vt.attrs.dim = true; break;
|
|
case 3: term->vt.attrs.italic = true; break;
|
|
case 4: term->vt.attrs.underline = true; break;
|
|
case 5: term->vt.attrs.blink = true; break;
|
|
case 6: LOG_WARN("ignored: rapid blink"); break;
|
|
case 7: term->vt.attrs.reverse = true; break;
|
|
case 8: term->vt.attrs.conceal = true; break;
|
|
case 9: term->vt.attrs.strikethrough = true; break;
|
|
|
|
case 21: term->vt.attrs.bold = false; 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; break;
|
|
case 25: term->vt.attrs.blink = false; break;
|
|
case 26: break; /* rapid blink, ignored */
|
|
case 27: term->vt.attrs.reverse = false; break;
|
|
case 28: term->vt.attrs.conceal = false; break;
|
|
case 29: term->vt.attrs.strikethrough = false; break;
|
|
|
|
/* Regular foreground colors */
|
|
case 30:
|
|
case 31:
|
|
case 32:
|
|
case 33:
|
|
case 34:
|
|
case 35:
|
|
case 36:
|
|
case 37:
|
|
term->vt.attrs.have_fg = 1;
|
|
term->vt.attrs.fg = term->colors.table[param - 30];
|
|
break;
|
|
|
|
case 38: {
|
|
/* Indexed: 38;5;<idx> */
|
|
if (term->vt.params.idx - i - 1 >= 2 &&
|
|
term->vt.params.v[i + 1].value == 5)
|
|
{
|
|
uint8_t idx = term->vt.params.v[i + 2].value;
|
|
term->vt.attrs.have_fg = 1;
|
|
term->vt.attrs.fg = term->colors.table[idx];
|
|
i += 2;
|
|
|
|
}
|
|
|
|
/* RGB: 38;2;<r>;<g>;<b> */
|
|
else if (term->vt.params.idx - i - 1 >= 4 &&
|
|
term->vt.params.v[i + 1].value == 2)
|
|
{
|
|
uint8_t r = term->vt.params.v[i + 2].value;
|
|
uint8_t g = term->vt.params.v[i + 3].value;
|
|
uint8_t b = term->vt.params.v[i + 4].value;
|
|
term->vt.attrs.have_fg = 1;
|
|
term->vt.attrs.fg = r << 16 | g << 8 | b;
|
|
i += 4;
|
|
}
|
|
|
|
/* Sub-parameter style: 38:2:... */
|
|
else if (term->vt.params.v[i].sub.idx >= 2 &&
|
|
term->vt.params.v[i].sub.value[0] == 2)
|
|
{
|
|
const struct vt_param *param = &term->vt.params.v[i];
|
|
const int color_space_id = param->sub.value[1];
|
|
|
|
switch (color_space_id) {
|
|
case 0: /* Implementation defined - we map it to '2' */
|
|
case 2: { /* RGB - 38:2:2:<r>:<g>:<b> */
|
|
if (param->sub.idx < 5) {
|
|
UNHANDLED_SGR();
|
|
break;
|
|
}
|
|
|
|
uint8_t r = param->sub.value[2];
|
|
uint8_t g = param->sub.value[3];
|
|
uint8_t b = param->sub.value[4];
|
|
/* 5 - unused */
|
|
/* 6 - CS tolerance */
|
|
/* 7 - color space associated with tolerance */
|
|
|
|
term->vt.attrs.have_fg = 1;
|
|
term->vt.attrs.fg = r << 16 | g << 8 | b;
|
|
break;
|
|
}
|
|
|
|
case 5: { /* Indexed - 38:2:5:<idx> */
|
|
if (param->sub.idx < 3) {
|
|
UNHANDLED_SGR();
|
|
break;
|
|
}
|
|
|
|
uint8_t idx = param->sub.value[2];
|
|
term->vt.attrs.have_fg = 1;
|
|
term->vt.attrs.fg = term->colors.table[idx];
|
|
break;
|
|
}
|
|
|
|
case 1: /* Transparent */
|
|
case 3: /* CMY */
|
|
case 4: /* CMYK */
|
|
UNHANDLED_SGR();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Unrecognized */
|
|
else
|
|
UNHANDLED_SGR();
|
|
|
|
break;
|
|
}
|
|
|
|
case 39:
|
|
term->vt.attrs.have_fg = 0;
|
|
break;
|
|
|
|
/* Regular background colors */
|
|
case 40:
|
|
case 41:
|
|
case 42:
|
|
case 43:
|
|
case 44:
|
|
case 45:
|
|
case 46:
|
|
case 47:
|
|
term->vt.attrs.have_bg = 1;
|
|
term->vt.attrs.bg = term->colors.table[param - 40];
|
|
break;
|
|
|
|
case 48: {
|
|
/* Indexed: 48;5;<idx> */
|
|
if (term->vt.params.idx - i - 1 >= 2 &&
|
|
term->vt.params.v[i + 1].value == 5)
|
|
{
|
|
uint8_t idx = term->vt.params.v[i + 2].value;
|
|
term->vt.attrs.have_bg = 1;
|
|
term->vt.attrs.bg = term->colors.table[idx];
|
|
i += 2;
|
|
|
|
}
|
|
|
|
/* RGB: 48;2;<r>;<g>;<b> */
|
|
else if (term->vt.params.idx - i - 1 >= 4 &&
|
|
term->vt.params.v[i + 1].value == 2)
|
|
{
|
|
uint8_t r = term->vt.params.v[i + 2].value;
|
|
uint8_t g = term->vt.params.v[i + 3].value;
|
|
uint8_t b = term->vt.params.v[i + 4].value;
|
|
term->vt.attrs.have_bg = 1;
|
|
term->vt.attrs.bg = r << 16 | g << 8 | b;
|
|
i += 4;
|
|
}
|
|
|
|
/* Sub-parameter style: 48:2:... */
|
|
else if (term->vt.params.v[i].sub.idx >= 2 &&
|
|
term->vt.params.v[i].sub.value[0] == 2)
|
|
{
|
|
const struct vt_param *param = &term->vt.params.v[i];
|
|
const int color_space_id = param->sub.value[1];
|
|
|
|
switch (color_space_id) {
|
|
case 0: /* Implementation defined - we map it to '2' */
|
|
case 2: { /* RGB - 48:2:2:<r>:<g>:<b> */
|
|
if (param->sub.idx < 5) {
|
|
UNHANDLED_SGR();
|
|
break;
|
|
}
|
|
|
|
uint8_t r = param->sub.value[2];
|
|
uint8_t g = param->sub.value[3];
|
|
uint8_t b = param->sub.value[4];
|
|
/* 5 - unused */
|
|
/* 6 - CS tolerance */
|
|
/* 7 - color space associated with tolerance */
|
|
|
|
term->vt.attrs.have_bg = 1;
|
|
term->vt.attrs.bg = r << 16 | g << 8 | b;
|
|
break;
|
|
}
|
|
|
|
case 5: { /* Indexed - 48:2:5:<idx> */
|
|
if (param->sub.idx < 3) {
|
|
UNHANDLED_SGR();
|
|
break;
|
|
}
|
|
|
|
uint8_t idx = param->sub.value[2];
|
|
term->vt.attrs.have_bg = 1;
|
|
term->vt.attrs.bg = term->colors.table[idx];
|
|
break;
|
|
}
|
|
|
|
case 1: /* Transparent */
|
|
case 3: /* CMY */
|
|
case 4: /* CMYK */
|
|
UNHANDLED_SGR();
|
|
break;
|
|
}
|
|
}
|
|
|
|
else
|
|
UNHANDLED_SGR();
|
|
|
|
break;
|
|
}
|
|
case 49:
|
|
term->vt.attrs.have_bg = 0;
|
|
break;
|
|
|
|
/* Bright foreground colors */
|
|
case 90:
|
|
case 91:
|
|
case 92:
|
|
case 93:
|
|
case 94:
|
|
case 95:
|
|
case 96:
|
|
case 97:
|
|
term->vt.attrs.have_fg = 1;
|
|
term->vt.attrs.fg = term->colors.table[param - 90 + 8];
|
|
break;
|
|
|
|
/* Bright background colors */
|
|
case 100:
|
|
case 101:
|
|
case 102:
|
|
case 103:
|
|
case 104:
|
|
case 105:
|
|
case 106:
|
|
case 107:
|
|
term->vt.attrs.have_bg = 1;
|
|
term->vt.attrs.bg = term->colors.table[param - 100 + 8];
|
|
break;
|
|
|
|
default:
|
|
UNHANDLED_SGR();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
csi_dispatch(struct terminal *term, uint8_t final)
|
|
{
|
|
LOG_DBG("%s", csi_as_string(term, final));
|
|
|
|
switch (term->vt.private[0]) {
|
|
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_ preceeded by a graphical character.
|
|
*/
|
|
int count = vt_param_get(term, 0, 1);
|
|
LOG_DBG("REP: '%C' %d times", term->vt.last_printed, count);
|
|
|
|
const int width = wcwidth(term->vt.last_printed);
|
|
for (int i = 0; i < count; i++)
|
|
term_print(term, term->vt.last_printed, width);
|
|
}
|
|
break;
|
|
|
|
case 'c': {
|
|
/* 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).
|
|
*/
|
|
const char *reply = "\033[?62;6;15;17;22;28c";
|
|
term_to_slave(term, reply, strlen(reply));
|
|
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->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->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->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->cursor.point.col)
|
|
tll_remove(term->tab_stops, it);
|
|
else if (it->item > term->cursor.point.col)
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
case 3:
|
|
/* Clear *all* tabs */
|
|
tll_free(term->tab_stops);
|
|
break;
|
|
|
|
default:
|
|
UNHANDLED();
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case '`':
|
|
case 'G': {
|
|
/* Cursor horizontal absolute */
|
|
int col = min(vt_param_get(term, 0, 1), term->cols) - 1;
|
|
term_cursor_to(term, term->cursor.point.row, col);
|
|
break;
|
|
}
|
|
|
|
case 'f':
|
|
case 'H': {
|
|
/* Move cursor */
|
|
int rel_row = vt_param_get(term, 0, 1) - 1;
|
|
int row = term_row_rel_to_abs(term, rel_row);
|
|
int col = min(vt_param_get(term, 1, 1), term->cols) - 1;
|
|
term_cursor_to(term, row, col);
|
|
break;
|
|
}
|
|
|
|
case 'J': {
|
|
/* Erase screen */
|
|
|
|
int param = vt_param_get(term, 0, 0);
|
|
switch (param) {
|
|
case 0:
|
|
/* From cursor to end of screen */
|
|
term_erase(
|
|
term,
|
|
&term->cursor.point,
|
|
&(struct coord){term->cols - 1, term->rows - 1});
|
|
term->cursor.lcf = false;
|
|
break;
|
|
|
|
case 1:
|
|
/* From start of screen to cursor */
|
|
term_erase(term, &(struct coord){0, 0}, &term->cursor.point);
|
|
term->cursor.lcf = false;
|
|
break;
|
|
|
|
case 2:
|
|
/* Erase entire screen */
|
|
term_erase(
|
|
term,
|
|
&(struct coord){0, 0},
|
|
&(struct coord){term->cols - 1, term->rows - 1});
|
|
term->cursor.lcf = false;
|
|
break;
|
|
|
|
case 3: {
|
|
/* Erase scrollback */
|
|
int end = (term->grid->offset + term->rows - 1) % term->grid->num_rows;
|
|
for (size_t i = 0; i < term->grid->num_rows; i++) {
|
|
if (end >= term->grid->offset) {
|
|
/* Not wrapped */
|
|
if (i >= term->grid->offset && i <= end)
|
|
continue;
|
|
} else {
|
|
/* Wrapped */
|
|
if (i >= term->grid->offset || i <= end)
|
|
continue;
|
|
}
|
|
|
|
grid_row_free(term->grid->rows[i]);
|
|
term->grid->rows[i] = NULL;
|
|
}
|
|
term->grid->view = term->grid->offset;
|
|
term_damage_view(term);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
UNHANDLED();
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'K': {
|
|
/* Erase line */
|
|
|
|
int param = vt_param_get(term, 0, 0);
|
|
switch (param) {
|
|
case 0:
|
|
/* From cursor to end of line */
|
|
term_erase(
|
|
term,
|
|
&term->cursor.point,
|
|
&(struct coord){term->cols - 1, term->cursor.point.row});
|
|
term->cursor.lcf = false;
|
|
break;
|
|
|
|
case 1:
|
|
/* From start of line to cursor */
|
|
term_erase(
|
|
term, &(struct coord){0, term->cursor.point.row}, &term->cursor.point);
|
|
term->cursor.lcf = false;
|
|
break;
|
|
|
|
case 2:
|
|
/* Entire line */
|
|
term_erase(
|
|
term,
|
|
&(struct coord){0, term->cursor.point.row},
|
|
&(struct coord){term->cols - 1, term->cursor.point.row});
|
|
term->cursor.lcf = false;
|
|
break;
|
|
|
|
default:
|
|
UNHANDLED();
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case 'L': {
|
|
if (term->cursor.point.row < term->scroll_region.start ||
|
|
term->cursor.point.row >= term->scroll_region.end)
|
|
break;
|
|
|
|
int count = min(
|
|
vt_param_get(term, 0, 1),
|
|
term->scroll_region.end - term->cursor.point.row);
|
|
|
|
term_scroll_reverse_partial(
|
|
term,
|
|
(struct scroll_region){
|
|
.start = term->cursor.point.row,
|
|
.end = term->scroll_region.end},
|
|
count);
|
|
break;
|
|
}
|
|
|
|
case 'M': {
|
|
if (term->cursor.point.row < term->scroll_region.start ||
|
|
term->cursor.point.row >= term->scroll_region.end)
|
|
break;
|
|
|
|
int count = min(
|
|
vt_param_get(term, 0, 1),
|
|
term->scroll_region.end - term->cursor.point.row);
|
|
|
|
term_scroll_partial(
|
|
term,
|
|
(struct scroll_region){
|
|
.start = term->cursor.point.row,
|
|
.end = term->scroll_region.end},
|
|
count);
|
|
break;
|
|
}
|
|
|
|
case 'P': {
|
|
/* DCH: Delete character(s) */
|
|
|
|
/* Number of characters to delete */
|
|
int count = min(
|
|
vt_param_get(term, 0, 1), term->cols - term->cursor.point.col);
|
|
|
|
/* Number of characters left after deletion (on current line) */
|
|
int remaining = term->cols - (term->cursor.point.col + count);
|
|
|
|
/* 'Delete' characters by moving the remaining ones */
|
|
memmove(&term->grid->cur_row->cells[term->cursor.point.col],
|
|
&term->grid->cur_row->cells[term->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->cursor.point.col + c].attrs.clean = 0;
|
|
term->grid->cur_row->dirty = true;
|
|
|
|
/* Erase the remainder of the line */
|
|
term_erase(
|
|
term,
|
|
&(struct coord){term->cursor.point.col + remaining, term->cursor.point.row},
|
|
&(struct coord){term->cols - 1, term->cursor.point.row});
|
|
term->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->cursor.point.col);
|
|
|
|
/* Characters to move */
|
|
int remaining = term->cols - (term->cursor.point.col + count);
|
|
|
|
/* Push existing characters */
|
|
memmove(&term->grid->cur_row->cells[term->cursor.point.col + count],
|
|
&term->grid->cur_row->cells[term->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->cursor.point.col + count + c].attrs.clean = 0;
|
|
term->grid->cur_row->dirty = true;
|
|
|
|
/* Erase (insert space characters) */
|
|
term_erase(
|
|
term,
|
|
&term->cursor.point,
|
|
&(struct coord){term->cursor.point.col + count - 1, term->cursor.point.row});
|
|
term->cursor.lcf = false;
|
|
break;
|
|
}
|
|
|
|
case 'S':
|
|
term_scroll(term, vt_param_get(term, 0, 1));
|
|
break;
|
|
|
|
case 'T':
|
|
term_scroll_reverse(term, vt_param_get(term, 0, 1));
|
|
break;
|
|
|
|
case 'X': {
|
|
/* Erase chars */
|
|
int count = min(
|
|
vt_param_get(term, 0, 1), term->cols - term->cursor.point.col);
|
|
|
|
term_erase(
|
|
term,
|
|
&term->cursor.point,
|
|
&(struct coord){term->cursor.point.col + count - 1, term->cursor.point.row});
|
|
term->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->cursor.point.col) {
|
|
new_col = it->item;
|
|
break;
|
|
}
|
|
}
|
|
assert(new_col >= term->cursor.point.col);
|
|
term_cursor_right(term, new_col - term->cursor.point.col);
|
|
}
|
|
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->cursor.point.col) {
|
|
new_col = it->item;
|
|
break;
|
|
}
|
|
}
|
|
assert(term->cursor.point.col >= new_col);
|
|
term_cursor_left(term, term->cursor.point.col - new_col);
|
|
}
|
|
break;
|
|
|
|
case 'h':
|
|
/* Set mode */
|
|
switch (vt_param_get(term, 0, 0)) {
|
|
case 2: /* Keyboard Action Mode - AM */
|
|
LOG_WARN("unimplemented: keyboard action mode (AM)");
|
|
break;
|
|
|
|
case 4: /* Insert Mode - IRM */
|
|
term->insert_mode = true;
|
|
break;
|
|
|
|
case 12: /* Send/receive Mode - SRM */
|
|
LOG_WARN("unimplemented: send/receive mode (SRM)");
|
|
break;
|
|
|
|
case 20: /* Automatic Newline Mode - LNM */
|
|
LOG_WARN("unimplemented: automatic newline mode (LNM)");
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 'l':
|
|
/* Reset mode */
|
|
switch (vt_param_get(term, 0, 0)) {
|
|
case 4: /* Insert Mode - IRM */
|
|
term->insert_mode = false;
|
|
break;
|
|
|
|
case 2: /* Keyboard Action Mode - AM */
|
|
case 12: /* Send/receive Mode - SRM */
|
|
case 20: /* Automatic Newline Mode - LNM */
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 'r': {
|
|
int start = vt_param_get(term, 0, 1);
|
|
int end = min(vt_param_get(term, 1, term->rows), term->rows);
|
|
|
|
if (end > start) {
|
|
|
|
/* 1-based */
|
|
term->scroll_region.start = start - 1;
|
|
term->scroll_region.end = end;
|
|
term_cursor_home(term);
|
|
|
|
LOG_DBG("scroll region: %d-%d",
|
|
term->scroll_region.start,
|
|
term->scroll_region.end);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 's':
|
|
term->saved_cursor = term->cursor;
|
|
break;
|
|
|
|
case 'u':
|
|
term_restore_cursor(term);
|
|
break;
|
|
|
|
case 't': {
|
|
unsigned param = vt_param_get(term, 0, 0);
|
|
|
|
switch (param) {
|
|
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, strdup(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));
|
|
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->cursor.point.row
|
|
: term->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];
|
|
snprintf(reply, sizeof(reply), "\x1b[%d;%dR",
|
|
row + 1, term->cursor.point.col + 1);
|
|
term_to_slave(term, reply, strlen(reply));
|
|
break;
|
|
}
|
|
|
|
default:
|
|
UNHANDLED();
|
|
break;
|
|
}
|
|
} else
|
|
UNHANDLED();
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
UNHANDLED();
|
|
break;
|
|
}
|
|
|
|
break; /* private == 0 */
|
|
}
|
|
|
|
case '?': {
|
|
switch (final) {
|
|
case 'h': {
|
|
for (size_t i = 0; i < term->vt.params.idx; i++) {
|
|
switch (term->vt.params.v[i].value) {
|
|
case 1:
|
|
term->cursor_keys_mode = CURSOR_KEYS_APPLICATION;
|
|
break;
|
|
|
|
case 3:
|
|
LOG_WARN("unimplemented: 132 column mode (DECCOLM, %s)",
|
|
csi_as_string(term, final));
|
|
term_erase(
|
|
term,
|
|
&(struct coord){0, 0},
|
|
&(struct coord){term->cols - 1, term->rows - 1});
|
|
term_cursor_home(term);
|
|
break;
|
|
|
|
case 4:
|
|
/* DECSCLM - Smooth scroll */
|
|
LOG_WARN("unimplemented: Smooth (Slow) Scroll (DECSCLM, %s)",
|
|
csi_as_string(term, final));
|
|
break;
|
|
|
|
case 5:
|
|
term->reverse = true;
|
|
term_damage_all(term);
|
|
break;
|
|
|
|
case 6: { /* DECOM */
|
|
term->origin = ORIGIN_RELATIVE;
|
|
term_cursor_home(term);
|
|
break;
|
|
}
|
|
|
|
case 7:
|
|
term->auto_margin = true;
|
|
break;
|
|
|
|
case 12:
|
|
term_cursor_blink_enable(term);
|
|
break;
|
|
|
|
case 25:
|
|
term->hide_cursor = false;
|
|
break;
|
|
|
|
case 1000:
|
|
term->mouse_tracking = MOUSE_CLICK;
|
|
term_xcursor_update(term);
|
|
break;
|
|
|
|
case 1002:
|
|
term->mouse_tracking = MOUSE_DRAG;
|
|
term_xcursor_update(term);
|
|
break;
|
|
|
|
case 1003:
|
|
term->mouse_tracking = MOUSE_MOTION;
|
|
term_xcursor_update(term);
|
|
break;
|
|
|
|
case 1004:
|
|
term->focus_events = true;
|
|
break;
|
|
|
|
case 1005:
|
|
LOG_WARN("unimplemented: mouse reporting mode: UTF-8");
|
|
/* term->mouse_reporting = MOUSE_UTF8; */
|
|
break;
|
|
|
|
case 1006:
|
|
LOG_DBG("mouse reporting mode: SGR");
|
|
term->mouse_reporting = MOUSE_SGR;
|
|
break;
|
|
|
|
case 1007:
|
|
term->alt_scrolling = true;
|
|
break;
|
|
|
|
case 1015:
|
|
LOG_DBG("mouse reporting mode: urxvt");
|
|
term->mouse_reporting = MOUSE_URXVT;
|
|
break;
|
|
|
|
case 1036:
|
|
/* metaSendsEscape - we always send escape */
|
|
break;
|
|
|
|
#if 0
|
|
case 1042:
|
|
LOG_WARN("unimplemented: 'urgency' window manager hint on ctrl-g");
|
|
break;
|
|
|
|
case 1043:
|
|
LOG_WARN("unimplemented: raise window on ctrl-g");
|
|
break;
|
|
#endif
|
|
|
|
case 1049:
|
|
if (term->grid != &term->alt) {
|
|
selection_cancel(term);
|
|
|
|
term->grid = &term->alt;
|
|
term->saved_cursor = term->cursor;
|
|
|
|
term_cursor_to(term, term->cursor.point.row, term->cursor.point.col);
|
|
|
|
tll_free(term->alt.damage);
|
|
tll_free(term->alt.scroll_damage);
|
|
|
|
term_erase(
|
|
term,
|
|
&(struct coord){0, 0},
|
|
&(struct coord){term->cols - 1, term->rows - 1});
|
|
}
|
|
break;
|
|
|
|
case 2004:
|
|
term->bracketed_paste = true;
|
|
break;
|
|
|
|
default:
|
|
UNHANDLED();
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'l': {
|
|
for (size_t i = 0; i < term->vt.params.idx; i++) {
|
|
switch (term->vt.params.v[i].value) {
|
|
case 1:
|
|
term->cursor_keys_mode = CURSOR_KEYS_NORMAL;
|
|
break;
|
|
|
|
case 3:
|
|
/* DECCOLM - 80 column mode */
|
|
term_erase(
|
|
term,
|
|
&(struct coord){0, 0},
|
|
&(struct coord){term->cols - 1, term->rows - 1});
|
|
term_cursor_home(term);
|
|
break;
|
|
|
|
case 4:
|
|
/* DECSCLM - Jump scroll */
|
|
break;
|
|
|
|
case 5:
|
|
term->reverse = false;
|
|
term_damage_all(term);
|
|
break;
|
|
|
|
case 6: { /* DECOM */
|
|
term->origin = ORIGIN_ABSOLUTE;
|
|
term_cursor_home(term);
|
|
break;
|
|
}
|
|
|
|
case 7:
|
|
term->auto_margin = false;
|
|
break;
|
|
|
|
case 12:
|
|
term_cursor_blink_disable(term);
|
|
break;
|
|
|
|
case 25:
|
|
term->hide_cursor = true;
|
|
break;
|
|
|
|
case 1000: /* MOUSE_NORMAL */
|
|
case 1002: /* MOUSE_BUTTON_EVENT */
|
|
case 1003: /* MOUSE_ANY_EVENT */
|
|
term->mouse_tracking = MOUSE_NONE;
|
|
term_xcursor_update(term);
|
|
break;
|
|
|
|
case 1005: /* MOUSE_UTF8 */
|
|
case 1006: /* MOUSE_SGR */
|
|
case 1015: /* MOUSE_URXVT */
|
|
LOG_DBG("mouse reporting mode: legacy");
|
|
term->mouse_reporting = MOUSE_NORMAL;
|
|
break;
|
|
|
|
case 1004:
|
|
term->focus_events = false;
|
|
break;
|
|
|
|
case 1007:
|
|
term->alt_scrolling = false;
|
|
break;
|
|
|
|
case 1036:
|
|
/* metaSendsEscape - we always send escape */
|
|
LOG_WARN("unimplemented: meta does *not* send escape");
|
|
break;
|
|
|
|
#if 0
|
|
case 1042:
|
|
/* 'urgency' window manager hint on ctrl-g */
|
|
break;
|
|
|
|
case 1043:
|
|
/* raise window on ctrl-g */
|
|
break;
|
|
#endif
|
|
|
|
case 1049:
|
|
if (term->grid == &term->alt) {
|
|
selection_cancel(term);
|
|
|
|
term->grid = &term->normal;
|
|
term_restore_cursor(term);
|
|
|
|
tll_free(term->alt.damage);
|
|
tll_free(term->alt.scroll_damage);
|
|
|
|
term_damage_all(term);
|
|
}
|
|
break;
|
|
|
|
case 2004:
|
|
term->bracketed_paste = false;
|
|
break;
|
|
|
|
default:
|
|
UNHANDLED();
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'p': {
|
|
if (term->vt.private[1] != '$') {
|
|
UNHANDLED();
|
|
break;
|
|
}
|
|
|
|
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
|
|
*/
|
|
char reply[32];
|
|
snprintf(reply, sizeof(reply), "\033[?%u;2$y", param);
|
|
term_to_slave(term, reply, strlen(reply));
|
|
break;
|
|
}
|
|
|
|
case 's':
|
|
for (size_t i = 0; i < term->vt.params.idx; i++) {
|
|
switch (term->vt.params.v[i].value) {
|
|
case 1001: /* save old highlight mouse tracking mode? */
|
|
LOG_WARN(
|
|
"unimplemented: %s "
|
|
"(save 'highlight mouse tracking' mode)",
|
|
csi_as_string(term, final));
|
|
break;
|
|
|
|
default:
|
|
UNHANDLED();
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'r':
|
|
for (size_t i = 0; i < term->vt.params.idx; i++) {
|
|
switch (term->vt.params.v[i].value) {
|
|
case 1001: /* restore old highlight mouse tracking mode? */
|
|
LOG_WARN(
|
|
"unimplemented: %s "
|
|
"(restore 'highlight mouse tracking' mode)",
|
|
csi_as_string(term, final));
|
|
break;
|
|
|
|
default:
|
|
UNHANDLED();
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
UNHANDLED();
|
|
break;
|
|
}
|
|
|
|
break; /* private == '?' */
|
|
}
|
|
|
|
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.
|
|
*/
|
|
|
|
term_to_slave(term, "\033[>41;347;0c", 12);
|
|
break;
|
|
|
|
case 'm':
|
|
if (term->vt.params.idx == 0) {
|
|
/* Reset all */
|
|
} else {
|
|
int resource = vt_param_get(term, 0, 0);
|
|
int value = vt_param_get(term, 1, -1);
|
|
|
|
switch (resource) {
|
|
case 0: /* modifyKeyboard */
|
|
break;
|
|
|
|
case 1: /* modifyCursorKeys */
|
|
case 2: /* modifyFunctionKeys */
|
|
case 4: /* modifyOtherKeys */
|
|
/* Ignored, we always report modifiers */
|
|
if (value != 2 && value != -1 &&
|
|
!(resource == 4 && value == 1))
|
|
{
|
|
LOG_WARN("unimplemented: %s = %d",
|
|
resource == 1 ? "modifyCursorKeys" :
|
|
resource == 2 ? "modifyFunctionKeys" :
|
|
resource == 4 ? "modifyOtherKeys" : "<invalid>",
|
|
value);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
LOG_WARN("invalid resource %d in %s",
|
|
resource, csi_as_string(term, final));
|
|
break;
|
|
}
|
|
}
|
|
break; /* final == 'm' */
|
|
|
|
default:
|
|
UNHANDLED();
|
|
break;
|
|
}
|
|
|
|
break; /* private == '>' */
|
|
}
|
|
|
|
case ' ': {
|
|
switch (final) {
|
|
case 'q': {
|
|
int param = vt_param_get(term, 0, 0);
|
|
switch (param) {
|
|
case 0: case 1: /* blinking block */
|
|
term->cursor_style = CURSOR_BLOCK;
|
|
break;
|
|
|
|
case 2: /* steady block - but can be overriden in footrc */
|
|
term->cursor_style = term->default_cursor_style;
|
|
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_BAR;
|
|
break;
|
|
}
|
|
|
|
if (param == 0 || param & 1)
|
|
term_cursor_blink_enable(term);
|
|
else
|
|
term_cursor_blink_disable(term);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
UNHANDLED();
|
|
break;
|
|
}
|
|
break; /* private == ' ' */
|
|
}
|
|
|
|
case '!': {
|
|
switch (final) {
|
|
case 'p':
|
|
term_reset(term, false);
|
|
break;
|
|
|
|
default:
|
|
UNHANDLED();
|
|
break;
|
|
}
|
|
break; /* private == '!' */
|
|
}
|
|
|
|
case '=': {
|
|
switch (final) {
|
|
case 'c':
|
|
if (vt_param_get(term, 0, 0) != 0) {
|
|
UNHANDLED();
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Send Device Attributes (Tertiary DA)
|
|
*
|
|
* Reply format is "DCS ! | DDDDDDDD ST"
|
|
*
|
|
* D..D is the unit ID of the terminal, consisting of four
|
|
* hexadecimal pairs. The first pair represents the
|
|
* manufacturing site code. This code can be any
|
|
* hexadecimal value from 00 through FF.
|
|
*/
|
|
|
|
term_to_slave(term, "\033P!|464f4f54\033\\", 14); /* FOOT */
|
|
break;
|
|
|
|
default:
|
|
UNHANDLED();
|
|
break;
|
|
}
|
|
break; /* private == '=' */
|
|
}
|
|
|
|
default:
|
|
UNHANDLED();
|
|
break;
|
|
}
|
|
}
|