Implement DECCARA
Ability to set text attributes/colors in arbitrary screen regions, instead of working via the cursor. Note that kitty extends the original DECCARA spec from the VT-510 to allow setting all supported SGR attributes.
This commit is contained in:
parent
2d62e10a7e
commit
d48b76508b
137
kitty/cursor.c
137
kitty/cursor.c
@ -45,33 +45,38 @@ cursor_reset_display_attrs(Cursor *self) {
|
||||
self->decoration = 0; self->bold = false; self->italic = false; self->reverse = false; self->strikethrough = false;
|
||||
}
|
||||
|
||||
|
||||
static inline void
|
||||
parse_color(unsigned int *params, unsigned int *i, unsigned int count, uint32_t *result) {
|
||||
unsigned int attr;
|
||||
uint8_t r, g, b;
|
||||
if (*i < count) {
|
||||
attr = params[(*i)++];
|
||||
switch(attr) {
|
||||
case 5:
|
||||
if (*i < count) *result = (params[(*i)++] & 0xFF) << 8 | 1;
|
||||
break;
|
||||
case 2: \
|
||||
if (*i < count - 2) {
|
||||
/* Ignore the first parameter in a four parameter RGB */
|
||||
/* sequence (unused color space id), see https://github.com/kovidgoyal/kitty/issues/227 */
|
||||
if (*i < count - 3) (*i)++;
|
||||
r = params[(*i)++] & 0xFF;
|
||||
g = params[(*i)++] & 0xFF;
|
||||
b = params[(*i)++] & 0xFF;
|
||||
*result = r << 24 | g << 16 | b << 8 | 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
cursor_from_sgr(Cursor *self, unsigned int *params, unsigned int count) {
|
||||
#define SET_COLOR(which) \
|
||||
if (i < count) { \
|
||||
attr = params[i++];\
|
||||
switch(attr) { \
|
||||
case 5: \
|
||||
if (i < count) \
|
||||
self->which = (params[i++] & 0xFF) << 8 | 1; \
|
||||
break; \
|
||||
case 2: \
|
||||
if (i < count - 2) { \
|
||||
/* Ignore the first parameter in a four parameter RGB */ \
|
||||
/* sequence (unused color space id), see https://github.com/kovidgoyal/kitty/issues/227 */ \
|
||||
if (i < count - 3) i++; \
|
||||
r = params[i++] & 0xFF; \
|
||||
g = params[i++] & 0xFF; \
|
||||
b = params[i++] & 0xFF; \
|
||||
self->which = r << 24 | g << 16 | b << 8 | 2; \
|
||||
}\
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
break;
|
||||
|
||||
#define SET_COLOR(which) { parse_color(params, &i, count, &self->which); } break;
|
||||
START_ALLOW_CASE_RANGE
|
||||
unsigned int i = 0, attr;
|
||||
uint8_t r, g, b;
|
||||
if (!count) { params[0] = 0; count = 1; }
|
||||
while (i < count) {
|
||||
attr = params[i++];
|
||||
@ -102,7 +107,6 @@ cursor_from_sgr(Cursor *self, unsigned int *params, unsigned int count) {
|
||||
self->reverse = false; break;
|
||||
case 29:
|
||||
self->strikethrough = false; break;
|
||||
START_ALLOW_CASE_RANGE
|
||||
case 30 ... 37:
|
||||
self->fg = ((attr - 30) << 8) | 1; break;
|
||||
case 38:
|
||||
@ -119,13 +123,86 @@ START_ALLOW_CASE_RANGE
|
||||
self->fg = ((attr - 90 + 8) << 8) | 1; break;
|
||||
case 100 ... 107:
|
||||
self->bg = ((attr - 100 + 8) << 8) | 1; break;
|
||||
END_ALLOW_CASE_RANGE
|
||||
case DECORATION_FG_CODE:
|
||||
SET_COLOR(decoration_fg);
|
||||
SET_COLOR(decoration_fg);
|
||||
case DECORATION_FG_CODE + 1:
|
||||
self->decoration_fg = 0; break;
|
||||
}
|
||||
}
|
||||
#undef SET_COLOR
|
||||
END_ALLOW_CASE_RANGE
|
||||
}
|
||||
|
||||
void
|
||||
apply_sgr_to_cells(Cell *first_cell, unsigned int cell_count, unsigned int *params, unsigned int count) {
|
||||
#define RANGE for(unsigned c = 0; c < cell_count; c++, cell++)
|
||||
#define SET(shift) RANGE { cell->attrs |= (1 << shift); } break;
|
||||
#define RESET(shift) RANGE { cell->attrs &= ~(1 << shift); } break;
|
||||
#define SETM(val, mask, shift) { RANGE { cell->attrs &= ~(mask << shift); cell->attrs |= ((val) << shift); } break; }
|
||||
#define SET_COLOR(which) { color_type color = 0; parse_color(params, &i, count, &color); if (color) { RANGE { cell->which = color; }} } break;
|
||||
#define SIMPLE(which, val) RANGE { cell->which = (val); } break;
|
||||
|
||||
unsigned int i = 0, attr;
|
||||
if (!count) { params[0] = 0; count = 1; }
|
||||
while (i < count) {
|
||||
Cell *cell = first_cell;
|
||||
attr = params[i++];
|
||||
switch(attr) {
|
||||
case 0:
|
||||
RANGE { cell->attrs &= WIDTH_MASK; cell->fg = 0; cell->bg = 0; cell->decoration_fg = 0; }
|
||||
break;
|
||||
case 1:
|
||||
SET(BOLD_SHIFT);
|
||||
case 3:
|
||||
SET(ITALIC_SHIFT);
|
||||
case 4:
|
||||
if (i < count) { uint8_t val = MIN(3, params[i]); i++; SETM(val, DECORATION_MASK, DECORATION_SHIFT); }
|
||||
else { SETM(1, DECORATION_MASK, DECORATION_SHIFT); }
|
||||
case 7:
|
||||
SET(REVERSE_SHIFT);
|
||||
case 9:
|
||||
SET(STRIKE_SHIFT);
|
||||
case 21:
|
||||
SETM(2, DECORATION_MASK, DECORATION_SHIFT);
|
||||
case 22:
|
||||
RESET(BOLD_SHIFT);
|
||||
case 23:
|
||||
RESET(ITALIC_SHIFT);
|
||||
case 24:
|
||||
SETM(0, DECORATION_MASK, DECORATION_SHIFT);
|
||||
case 27:
|
||||
RESET(REVERSE_SHIFT);
|
||||
case 29:
|
||||
RESET(STRIKE_SHIFT);
|
||||
START_ALLOW_CASE_RANGE
|
||||
case 30 ... 37:
|
||||
SIMPLE(fg, ((attr - 30) << 8) | 1);
|
||||
case 38:
|
||||
SET_COLOR(fg);
|
||||
case 39:
|
||||
SIMPLE(fg, 0);
|
||||
case 40 ... 47:
|
||||
SIMPLE(bg, ((attr - 40) << 8) | 1);
|
||||
case 48:
|
||||
SET_COLOR(bg);
|
||||
case 49:
|
||||
SIMPLE(bg, 0);
|
||||
case 90 ... 97:
|
||||
SIMPLE(fg, ((attr - 90 + 8) << 8) | 1);
|
||||
case 100 ... 107:
|
||||
SIMPLE(bg, ((attr - 100 + 8) << 8) | 1);
|
||||
END_ALLOW_CASE_RANGE
|
||||
case DECORATION_FG_CODE:
|
||||
SET_COLOR(decoration_fg);
|
||||
case DECORATION_FG_CODE + 1:
|
||||
SIMPLE(decoration_fg, 0);
|
||||
}
|
||||
}
|
||||
#undef RESET
|
||||
#undef SET_COLOR
|
||||
#undef SET
|
||||
#undef SETM
|
||||
#undef RANGE
|
||||
}
|
||||
|
||||
static inline int
|
||||
@ -211,9 +288,9 @@ static PyMemberDef members[] = {
|
||||
{"y", T_UINT, offsetof(Cursor, y), 0, "y"},
|
||||
{"shape", T_INT, offsetof(Cursor, shape), 0, "shape"},
|
||||
{"decoration", T_UBYTE, offsetof(Cursor, decoration), 0, "decoration"},
|
||||
{"fg", T_ULONG, offsetof(Cursor, fg), 0, "fg"},
|
||||
{"bg", T_ULONG, offsetof(Cursor, bg), 0, "bg"},
|
||||
{"decoration_fg", T_ULONG, offsetof(Cursor, decoration_fg), 0, "decoration_fg"},
|
||||
{"fg", T_UINT, offsetof(Cursor, fg), 0, "fg"},
|
||||
{"bg", T_UINT, offsetof(Cursor, bg), 0, "bg"},
|
||||
{"decoration_fg", T_UINT, offsetof(Cursor, decoration_fg), 0, "decoration_fg"},
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
|
||||
@ -176,7 +176,7 @@ typedef struct {
|
||||
unsigned int x, y;
|
||||
uint8_t decoration;
|
||||
CursorShape shape;
|
||||
unsigned long fg, bg, decoration_fg;
|
||||
color_type fg, bg, decoration_fg;
|
||||
|
||||
} Cursor;
|
||||
|
||||
@ -260,6 +260,7 @@ Cursor* cursor_copy(Cursor*);
|
||||
void cursor_copy_to(Cursor *src, Cursor *dest);
|
||||
void cursor_reset_display_attrs(Cursor*);
|
||||
void cursor_from_sgr(Cursor *self, unsigned int *params, unsigned int count);
|
||||
void apply_sgr_to_cells(Cell *first_cell, unsigned int cell_count, unsigned int *params, unsigned int count);
|
||||
const char* cursor_as_sgr(Cursor*, Cursor*);
|
||||
|
||||
double monotonic();
|
||||
|
||||
@ -75,10 +75,11 @@ _report_error(PyObject *dump_callback, const char *fmt, ...) {
|
||||
}
|
||||
|
||||
static void
|
||||
_report_params(PyObject *dump_callback, const char *name, unsigned int *params, unsigned int count) {
|
||||
_report_params(PyObject *dump_callback, const char *name, unsigned int *params, unsigned int count, Region *r) {
|
||||
static char buf[MAX_PARAMS*3] = {0};
|
||||
unsigned int i, p;
|
||||
for(i = 0, p=0; i < count && p < MAX_PARAMS*3-20; i++) {
|
||||
unsigned int i, p=0;
|
||||
if (r) p += snprintf(buf + p, sizeof(buf) - 2, "%u %u %u %u ", r->top, r->left, r->bottom, r->right);
|
||||
for(i = 0; i < count && p < MAX_PARAMS*3-20; i++) {
|
||||
int n = snprintf(buf + p, MAX_PARAMS*3 - p, "%u ", params[i]);
|
||||
if (n < 0) break;
|
||||
p += n;
|
||||
@ -107,7 +108,7 @@ _report_params(PyObject *dump_callback, const char *name, unsigned int *params,
|
||||
#define REPORT_DRAW(ch) \
|
||||
Py_XDECREF(PyObject_CallFunction(dump_callback, "sC", "draw", ch)); PyErr_Clear();
|
||||
|
||||
#define REPORT_PARAMS(name, params, num) _report_params(dump_callback, #name, params, num_params)
|
||||
#define REPORT_PARAMS(name, params, num, region) _report_params(dump_callback, name, params, num_params, region)
|
||||
|
||||
#define FLUSH_DRAW \
|
||||
Py_XDECREF(PyObject_CallFunction(dump_callback, "sO", "draw", Py_None)); PyErr_Clear();
|
||||
@ -395,13 +396,13 @@ repr_csi_params(unsigned int *params, unsigned int num_params) {
|
||||
}
|
||||
|
||||
static inline void
|
||||
parse_sgr(Screen *screen, uint32_t *buf, unsigned int num, unsigned int *params, PyObject DUMP_UNUSED *dump_callback) {
|
||||
parse_sgr(Screen *screen, uint32_t *buf, unsigned int num, unsigned int *params, PyObject DUMP_UNUSED *dump_callback, const char *report_name DUMP_UNUSED, Region *region) {
|
||||
enum State { START, NORMAL, MULTIPLE, COLOR, COLOR1, COLOR3 };
|
||||
enum State state = START;
|
||||
unsigned int num_params, num_start, i;
|
||||
|
||||
#define READ_PARAM { params[num_params++] = utoi(buf + num_start, i - num_start); }
|
||||
#define SEND_SGR { REPORT_PARAMS(select_graphic_rendition, params, num_params); select_graphic_rendition(screen, params, num_params); state = START; num_params = 0; }
|
||||
#define SEND_SGR { REPORT_PARAMS(report_name, params, num_params, region); select_graphic_rendition(screen, params, num_params, region); state = START; num_params = 0; }
|
||||
|
||||
for (i=0, num_start=0, num_params=0; i < num && num_params < MAX_PARAMS; i++) {
|
||||
switch(buf[i]) {
|
||||
@ -521,6 +522,40 @@ parse_sgr(Screen *screen, uint32_t *buf, unsigned int num, unsigned int *params,
|
||||
#undef SEND_SGR
|
||||
}
|
||||
|
||||
static inline unsigned int
|
||||
parse_region(Region *r, uint32_t *buf, unsigned int num) {
|
||||
unsigned int i, start, params[8] = {0}, num_params=0;
|
||||
for (i=0, start=0; i < num && num_params < 4; i++) {
|
||||
switch(buf[i]) {
|
||||
IS_DIGIT
|
||||
break;
|
||||
default:
|
||||
if (i > start) params[num_params++] = utoi(buf + start, i - start);
|
||||
else if (i == start && buf[i] == ';') params[num_params++] = 0;
|
||||
start = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch(num_params) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
r->top = params[0];
|
||||
break;
|
||||
case 2:
|
||||
r->top = params[0]; r->left = params[1];
|
||||
break;
|
||||
case 3:
|
||||
r->top = params[0]; r->left = params[1]; r->bottom = params[2];
|
||||
break;
|
||||
default:
|
||||
r->top = params[0]; r->left = params[1]; r->bottom = params[2]; r->right = params[3];
|
||||
break;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static inline void
|
||||
dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
||||
#define CALL_CSI_HANDLER1(name, defval) \
|
||||
@ -573,9 +608,18 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
||||
buf++; num--;
|
||||
}
|
||||
if (code == SGR && !start_modifier) {
|
||||
parse_sgr(screen, buf, num, params, dump_callback);
|
||||
parse_sgr(screen, buf, num, params, dump_callback, "select_graphic_rendition", NULL);
|
||||
return;
|
||||
}
|
||||
if (code == 'r' && !start_modifier && num > 0 && buf[num - 1] == '$') {
|
||||
// DECCARA
|
||||
Region r = {0};
|
||||
unsigned int consumed = parse_region(&r, buf, --num);
|
||||
num -= consumed; buf += consumed;
|
||||
parse_sgr(screen, buf, num, params, dump_callback, "deccara", &r);
|
||||
return;
|
||||
}
|
||||
|
||||
if (num > 0) {
|
||||
switch(buf[num-1]) {
|
||||
CSI_SECONDARY
|
||||
@ -656,6 +700,12 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
||||
}
|
||||
REPORT_ERROR("Unknown CSI r sequence with start and end modifiers: '%c' '%c'", start_modifier, end_modifier);
|
||||
break;
|
||||
case 'x':
|
||||
if (!start_modifier && end_modifier == '*') {
|
||||
CALL_CSI_HANDLER1(screen_decsace, 0);
|
||||
}
|
||||
REPORT_ERROR("Unknown CSI x sequence with start and end modifiers: '%c' '%c'", start_modifier, end_modifier);
|
||||
break;
|
||||
case DECSCUSR:
|
||||
CALL_CSI_HANDLER1M(screen_set_cursor, 1);
|
||||
case SU:
|
||||
|
||||
@ -347,8 +347,36 @@ screen_alignment_display(Screen *self) {
|
||||
}
|
||||
|
||||
void
|
||||
select_graphic_rendition(Screen *self, unsigned int *params, unsigned int count) {
|
||||
cursor_from_sgr(self->cursor, params, count);
|
||||
select_graphic_rendition(Screen *self, unsigned int *params, unsigned int count, Region *region_) {
|
||||
if (region_) {
|
||||
Region region = *region_;
|
||||
if (!region.top) region.top = 1;
|
||||
if (!region.left) region.left = 1;
|
||||
if (!region.bottom) region.bottom = self->lines;
|
||||
if (!region.right) region.right = self->columns;
|
||||
if (self->modes.mDECOM) {
|
||||
region.top += self->margin_top; region.bottom += self->margin_top;
|
||||
}
|
||||
region.left -= 1; region.top -= 1; region.right -= 1; region.bottom -= 1; // switch to zero based indexing
|
||||
if (self->modes.mDECSACE) {
|
||||
index_type x = MIN(region.left, self->columns - 1);
|
||||
index_type num = region.right >= x ? region.right - x + 1 : 0;
|
||||
num = MIN(num, self->columns - x);
|
||||
for (index_type y = region.top; y < MIN(region.bottom + 1, self->lines); y++) {
|
||||
linebuf_init_line(self->linebuf, y);
|
||||
apply_sgr_to_cells(self->linebuf->line->cells + x, num, params, count);
|
||||
}
|
||||
} else {
|
||||
index_type x, num;
|
||||
for (index_type y = region.top; y < MIN(region.bottom + 1, self->lines); y++) {
|
||||
if (y == region.top) { x = MIN(region.left, self->columns - 1); num = self->columns - x; }
|
||||
else if (y == region.bottom) { x = 0; num = MIN(region.right + 1, self->columns); }
|
||||
else { x = 0; num = self->columns; }
|
||||
linebuf_init_line(self->linebuf, y);
|
||||
apply_sgr_to_cells(self->linebuf->line->cells + x, num, params, count);
|
||||
}
|
||||
}
|
||||
} else cursor_from_sgr(self->cursor, params, count);
|
||||
}
|
||||
|
||||
static inline void
|
||||
@ -501,6 +529,11 @@ screen_set_mode(Screen *self, unsigned int mode) {
|
||||
set_mode_from_const(self, mode, true);
|
||||
}
|
||||
|
||||
void
|
||||
screen_decsace(Screen *self, unsigned int val) {
|
||||
self->modes.mDECSACE = val == 2 ? true : false;
|
||||
}
|
||||
|
||||
void
|
||||
screen_reset_mode(Screen *self, unsigned int mode) {
|
||||
set_mode_from_const(self, mode, false);
|
||||
@ -1390,7 +1423,7 @@ static PyObject*
|
||||
_select_graphic_rendition(Screen *self, PyObject *args) {
|
||||
unsigned int params[256] = {0};
|
||||
for (int i = 0; i < PyTuple_GET_SIZE(args); i++) { params[i] = PyLong_AsUnsignedLong(PyTuple_GET_ITEM(args, i)); }
|
||||
select_graphic_rendition(self, params, PyList_GET_SIZE(args));
|
||||
select_graphic_rendition(self, params, PyList_GET_SIZE(args), NULL);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ typedef enum ScrollTypes { SCROLL_LINE = -999999, SCROLL_PAGE, SCROLL_FULL } Scr
|
||||
|
||||
typedef struct {
|
||||
bool mLNM, mIRM, mDECTCEM, mDECSCNM, mDECOM, mDECAWM, mDECCOLM, mDECARM, mDECCKM,
|
||||
mBRACKETED_PASTE, mFOCUS_TRACKING, mEXTENDED_KEYBOARD;
|
||||
mBRACKETED_PASTE, mFOCUS_TRACKING, mEXTENDED_KEYBOARD, mDECSACE;
|
||||
MouseTrackingMode mouse_tracking_mode;
|
||||
MouseTrackingProtocol mouse_tracking_protocol;
|
||||
bool eight_bit_controls; // S8C1T
|
||||
@ -90,6 +90,7 @@ void screen_backtab(Screen *self, unsigned int);
|
||||
void screen_clear_tab_stop(Screen *self, unsigned int how);
|
||||
void screen_set_mode(Screen *self, unsigned int mode);
|
||||
void screen_reset_mode(Screen *self, unsigned int mode);
|
||||
void screen_decsace(Screen *self, unsigned int);
|
||||
void screen_insert_characters(Screen *self, unsigned int count);
|
||||
void screen_cursor_up(Screen *self, unsigned int count/*=1*/, bool do_carriage_return/*=false*/, int move_direction/*=-1*/);
|
||||
void screen_set_cursor(Screen *self, unsigned int mode, uint8_t secondary);
|
||||
@ -115,7 +116,7 @@ uint32_t* translation_table(uint32_t which);
|
||||
void screen_request_capabilities(Screen *, char, PyObject *);
|
||||
void screen_set_8bit_controls(Screen *, bool);
|
||||
void report_device_attributes(Screen *self, unsigned int UNUSED mode, char start_modifier);
|
||||
void select_graphic_rendition(Screen *self, unsigned int *params, unsigned int count);
|
||||
void select_graphic_rendition(Screen *self, unsigned int *params, unsigned int count, Region*);
|
||||
void report_device_status(Screen *self, unsigned int which, bool UNUSED);
|
||||
void report_mode_status(Screen *self, unsigned int which, bool);
|
||||
void screen_apply_selection(Screen *self, void *address, size_t size);
|
||||
|
||||
@ -278,3 +278,37 @@ class TestParser(BaseTest):
|
||||
e('s=', 'Malformed graphics control block, expecting an integer value')
|
||||
e('s==', 'Malformed graphics control block, expecting an integer value for key: s')
|
||||
e('s=1=', 'Malformed graphics control block, expecting a comma or semi-colon after a value, found: 0x3d')
|
||||
|
||||
def test_deccara(self):
|
||||
s = self.create_screen()
|
||||
pb = partial(self.parse_bytes_dump, s)
|
||||
pb('\033[$r', ('deccara', '0 0 0 0 0 '))
|
||||
pb('\033[;;;;4:3;38:5:10;48:2:1:2:3;1$r',
|
||||
('deccara', '0 0 0 0 4 3 '), ('deccara', '0 0 0 0 38 5 10 '), ('deccara', '0 0 0 0 48 2 1 2 3 '), ('deccara', '0 0 0 0 1 '))
|
||||
for y in range(s.lines):
|
||||
line = s.line(y)
|
||||
for x in range(s.columns):
|
||||
c = line.cursor_from(x)
|
||||
self.ae(c.bold, True)
|
||||
self.ae(c.italic, False)
|
||||
self.ae(c.decoration, 3)
|
||||
self.ae(c.fg, (10 << 8) | 1)
|
||||
self.ae(c.bg, (1 << 24 | 2 << 16 | 3 << 8 | 2))
|
||||
self.ae(s.line(0).cursor_from(0).bold, True)
|
||||
pb('\033[1;2;2;3;22;39$r', ('deccara', '1 2 2 3 22 '), ('deccara', '1 2 2 3 39 '))
|
||||
self.ae(s.line(0).cursor_from(0).bold, True)
|
||||
line = s.line(0)
|
||||
for x in range(1, s.columns):
|
||||
c = line.cursor_from(x)
|
||||
self.ae(c.bold, False)
|
||||
self.ae(c.fg, 0)
|
||||
line = s.line(1)
|
||||
for x in range(0, 3):
|
||||
c = line.cursor_from(x)
|
||||
self.ae(c.bold, False)
|
||||
self.ae(line.cursor_from(3).bold, True)
|
||||
pb('\033[2*x\033[3;2;4;3;34$r\033[*x', ('screen_decsace', 2), ('deccara', '3 2 4 3 34 '), ('screen_decsace', 0))
|
||||
for y in range(2, 4):
|
||||
line = s.line(y)
|
||||
for x in range(s.columns):
|
||||
self.ae(line.cursor_from(x).fg, (10 << 8 | 1) if x < 1 or x > 2 else (4 << 8) | 1)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user