diff --git a/kitty/cursor.c b/kitty/cursor.c index 0f9f4a5fe..dccef6aa5 100644 --- a/kitty/cursor.c +++ b/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 */ }; diff --git a/kitty/data-types.h b/kitty/data-types.h index 556ce2fbb..daa749982 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -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(); diff --git a/kitty/parser.c b/kitty/parser.c index 6be325bfe..54bf32f48 100644 --- a/kitty/parser.c +++ b/kitty/parser.c @@ -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: diff --git a/kitty/screen.c b/kitty/screen.c index b61704517..95d3f3c3e 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -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; } diff --git a/kitty/screen.h b/kitty/screen.h index 4264f91e1..c96ae6c1a 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -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); diff --git a/kitty_tests/parser.py b/kitty_tests/parser.py index 0509fb66b..7f2691f28 100644 --- a/kitty_tests/parser.py +++ b/kitty_tests/parser.py @@ -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)