From b9798c74d463acf525dc8a39efb8852079645e43 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 2 Dec 2017 14:35:06 +0530 Subject: [PATCH] Support for DCS status and capabilities reporting codes --- kitty/cursor.c | 119 +++++++++++++++++++++++++++++++++++++- kitty/data-types.h | 2 + kitty/parser.c | 12 ++-- kitty/screen.c | 123 ++++++++++++++-------------------------- kitty/screen.h | 2 +- kitty/terminfo.py | 2 +- kitty_tests/__init__.py | 5 +- kitty_tests/parser.py | 24 +++++++- 8 files changed, 197 insertions(+), 92 deletions(-) diff --git a/kitty/cursor.c b/kitty/cursor.c index 16579a31f..aab732ff8 100644 --- a/kitty/cursor.c +++ b/kitty/cursor.c @@ -39,11 +39,128 @@ repr(Cursor *self) { ); } -void cursor_reset_display_attrs(Cursor *self) { +void +cursor_reset_display_attrs(Cursor *self) { self->bg = 0; self->fg = 0; self->decoration_fg = 0; self->decoration = 0; self->bold = false; self->italic = false; self->reverse = false; self->strikethrough = false; } +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) { \ + r = params[i++] & 0xFF; \ + g = params[i++] & 0xFF; \ + b = params[i++] & 0xFF; \ + self->which = r << 24 | g << 16 | b << 8 | 2; \ + }\ + break; \ + } \ + } \ + break; + + unsigned int i = 0, attr; + uint8_t r, g, b; + if (!count) { params[0] = 0; count = 1; } + while (i < count) { + attr = params[i++]; + switch(attr) { + case 0: + cursor_reset_display_attrs(self); break; + case 1: + self->bold = true; break; + case 3: + self->italic = true; break; + case 4: + self->decoration = 1; break; + case UNDERCURL_CODE: + self->decoration = 2; break; + case 7: + self->reverse = true; break; + case 9: + self->strikethrough = true; break; + case 22: + self->bold = false; break; + case 23: + self->italic = false; break; + case 24: + self->decoration = 0; break; + case 27: + 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: + SET_COLOR(fg); + case 39: + self->fg = 0; break; + case 40 ... 47: + self->bg = ((attr - 40) << 8) | 1; break; + case 48: + SET_COLOR(bg); + case 49: + self->bg = 0; break; + case 90 ... 97: + 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); + case DECORATION_FG_CODE + 1: + self->decoration_fg = 0; break; + } + } +} + +static inline int +color_as_sgr(char *buf, size_t sz, unsigned long val, unsigned simple_code, unsigned aix_code, unsigned complex_code) { + switch(val & 0xff) { + case 1: + val >>= 8; + if (val < 16 && simple_code) { + return snprintf(buf, sz, "%ld;", (val < 8) ? simple_code + val : aix_code + (val - 8)); + } + return snprintf(buf, sz, "%d:5:%ld;", complex_code, val); + case 2: + return snprintf(buf, sz, "%d:2:%ld:%ld:%ld;", complex_code, (val >> 24) & 0xff, (val >> 16) & 0xff, (val >> 8) & 0xff); + default: + return 0; + } +} + +const char* +cursor_as_sgr(Cursor *self) { + static char buf[128], *p; +#define SZ sizeof(buf) - (p - buf) - 2 +#define P(fmt, ...) { p += snprintf(p, SZ, fmt ";", __VA_ARGS__); } + p = buf; + if (self->bold) P("%d", 1); + if (self->italic) P("%d", 3); + if (self->reverse) P("%d", 7); + if (self->strikethrough) P("%d", 9); + if (self->decoration) P("%d", self->decoration == 1 ? 4 : UNDERCURL_CODE); + if (self->fg) p += color_as_sgr(p, SZ, self->fg, 30, 90, 38); + if (self->bg) p += color_as_sgr(p, SZ, self->bg, 40, 100, 48); + if (self->decoration_fg) p += color_as_sgr(p, SZ, self->decoration_fg, 0, 0, DECORATION_FG_CODE); +#undef P +#undef SZ + if (p > buf) *(p - 1) = 0; // remove trailing semi-colon + else *(p++) = '0'; // no formatting + *p = 0; // ensure string is null-terminated + return buf; +} + static PyObject * reset_display_attrs(Cursor *self) { #define reset_display_attrs_doc "Reset all display attributes to unset" diff --git a/kitty/data-types.h b/kitty/data-types.h index f40338c5a..5431d0292 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -255,6 +255,8 @@ void cursor_reset(Cursor*); 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); +const char* cursor_as_sgr(Cursor*); double monotonic(); PyObject* cm_thread_write(PyObject *self, PyObject *args); diff --git a/kitty/parser.c b/kitty/parser.c index 7edbaed66..d9608bc74 100644 --- a/kitty/parser.c +++ b/kitty/parser.c @@ -663,19 +663,19 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { // DCS mode {{{ static inline void dispatch_dcs(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { - PyObject *string = NULL; if (screen->parser_buf_pos < 2) return; switch(screen->parser_buf[0]) { case '+': + case '$': if (screen->parser_buf[1] == 'q') { - string = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, screen->parser_buf + 2, screen->parser_buf_pos - 2); + PyObject *string = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, screen->parser_buf + 2, screen->parser_buf_pos - 2); if (string != NULL) { - REPORT_OSC(screen_request_capabilities, string); - screen_request_capabilities(screen, string); - Py_CLEAR(string); + REPORT_OSC2(screen_request_capabilities, (char)screen->parser_buf[0], string); + screen_request_capabilities(screen, (char)screen->parser_buf[0], string); + Py_DECREF(string); } } else { - REPORT_ERROR("Unrecognized DCS+ code: 0x%x", screen->parser_buf[1]); + REPORT_ERROR("Unrecognized DCS %c code: 0x%x", (char)screen->parser_buf[0], screen->parser_buf[1]); } break; default: diff --git a/kitty/screen.c b/kitty/screen.c index f1199c459..acc09b969 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -346,80 +346,7 @@ screen_alignment_display(Screen *self) { void select_graphic_rendition(Screen *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->cursor->which = (params[i++] & 0xFF) << 8 | 1; \ - break; \ - case 2: \ - if (i < count - 2) { \ - r = params[i++] & 0xFF; \ - g = params[i++] & 0xFF; \ - b = params[i++] & 0xFF; \ - self->cursor->which = r << 24 | g << 16 | b << 8 | 2; \ - }\ - break; \ - } \ - } \ - break; - - unsigned int i = 0, attr; - uint8_t r, g, b; - if (!count) { params[0] = 0; count = 1; } - while (i < count) { - attr = params[i++]; - switch(attr) { - case 0: - cursor_reset_display_attrs(self->cursor); break; - case 1: - self->cursor->bold = true; break; - case 3: - self->cursor->italic = true; break; - case 4: - self->cursor->decoration = 1; break; - case UNDERCURL_CODE: - self->cursor->decoration = 2; break; - case 7: - self->cursor->reverse = true; break; - case 9: - self->cursor->strikethrough = true; break; - case 22: - self->cursor->bold = false; break; - case 23: - self->cursor->italic = false; break; - case 24: - self->cursor->decoration = 0; break; - case 27: - self->cursor->reverse = false; break; - case 29: - self->cursor->strikethrough = false; break; -START_ALLOW_CASE_RANGE - case 30 ... 37: - self->cursor->fg = ((attr - 30) << 8) | 1; break; - case 38: - SET_COLOR(fg); - case 39: - self->cursor->fg = 0; break; - case 40 ... 47: - self->cursor->bg = ((attr - 40) << 8) | 1; break; - case 48: - SET_COLOR(bg); - case 49: - self->cursor->bg = 0; break; - case 90 ... 97: - self->cursor->fg = ((attr - 90 + 8) << 8) | 1; break; - case 100 ... 107: - self->cursor->bg = ((attr - 100 + 8) << 8) | 1; break; -END_ALLOW_CASE_RANGE - case DECORATION_FG_CODE: - SET_COLOR(decoration_fg); - case DECORATION_FG_CODE + 1: - self->cursor->decoration_fg = 0; break; - } - } + cursor_from_sgr(self->cursor, params, count); } static inline void @@ -1123,22 +1050,60 @@ set_title(Screen *self, PyObject *title) { CALLBACK("title_changed", "O", title); } -void set_icon(Screen *self, PyObject *icon) { +void +set_icon(Screen *self, PyObject *icon) { CALLBACK("icon_changed", "O", icon); } -void set_dynamic_color(Screen *self, unsigned int code, PyObject *color) { +void +set_dynamic_color(Screen *self, unsigned int code, PyObject *color) { if (color == NULL) { CALLBACK("set_dynamic_color", "Is", code, ""); } else { CALLBACK("set_dynamic_color", "IO", code, color); } } -void set_color_table_color(Screen *self, unsigned int code, PyObject *color) { +void +set_color_table_color(Screen *self, unsigned int code, PyObject *color) { if (color == NULL) { CALLBACK("set_color_table_color", "Is", code, ""); } else { CALLBACK("set_color_table_color", "IO", code, color); } } -void screen_request_capabilities(Screen *self, PyObject *q) { - CALLBACK("request_capabilities", "O", q); +void +screen_request_capabilities(Screen *self, char c, PyObject *q) { + static char buf[128]; + int shape = 0; + const char *query; + switch(c) { + case '+': + CALLBACK("request_capabilities", "O", q); + break; + case '$': + // report status + query = PyUnicode_AsUTF8(q); + if (strcmp(" q", query) == 0) { + // cursor shape + switch(self->cursor->shape) { + case NO_CURSOR_SHAPE: + case NUM_OF_CURSOR_SHAPES: + shape = 1; break; + case CURSOR_BLOCK: + shape = self->cursor->blink ? 0 : 2; break; + case CURSOR_UNDERLINE: + shape = self->cursor->blink ? 3 : 4; break; + case CURSOR_BEAM: + shape = self->cursor->blink ? 5 : 6; break; + } + shape = snprintf(buf, sizeof(buf), "\033P1$r%d q\033\\", shape); + } else if (strcmp("m", query) == 0) { + // SGR + shape = snprintf(buf, sizeof(buf), "\033P1$r%sm\033\\", cursor_as_sgr(self->cursor)); + } else if (strcmp("r", query) == 0) { + shape = snprintf(buf, sizeof(buf), "\033P1$r%u;%ur\033\\", self->margin_top + 1, self->margin_bottom + 1); + } else { + shape = snprintf(buf, sizeof(buf), "\033P0$r%s\033\\", query); + } + if (shape) write_to_child(self, buf, shape); + break; + } } // }}} diff --git a/kitty/screen.h b/kitty/screen.h index e2b8a73bd..8ab543e14 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -110,7 +110,7 @@ void set_icon(Screen *self, PyObject*); void set_dynamic_color(Screen *self, unsigned int code, PyObject*); void set_color_table_color(Screen *self, unsigned int code, PyObject*); uint32_t* translation_table(uint32_t which); -void screen_request_capabilities(Screen *, PyObject *); +void screen_request_capabilities(Screen *, char, PyObject *); 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 report_device_status(Screen *self, unsigned int which, bool UNUSED); diff --git a/kitty/terminfo.py b/kitty/terminfo.py index ec8cb1961..e301e53af 100644 --- a/kitty/terminfo.py +++ b/kitty/terminfo.py @@ -448,7 +448,7 @@ def get_capabilities(query_string): except Exception as e: safe_print(ERROR_PREFIX, 'Unknown terminfo property:', name) raise - ans.append(q + '=' + hexlify(str(val))) + ans.append(q + '=' + hexlify(str(val).encode('utf-8')).decode('ascii')) return b'\033P1+r' + ';'.join(ans).encode('utf-8') + b'\033\\' except Exception: return b'\033P0+r' + query_string.encode('utf-8') + b'\033\\' diff --git a/kitty_tests/__init__.py b/kitty_tests/__init__.py index 30224b52f..4162f5239 100644 --- a/kitty_tests/__init__.py +++ b/kitty_tests/__init__.py @@ -28,14 +28,15 @@ class Callbacks: self.ctbuf += '' def request_capabilities(self, q): - self.qbuf += q + from kitty.terminfo import get_capabilities + self.write(get_capabilities(q)) def use_utf8(self, on): self.iutf8 = on def clear(self): self.wtcbuf = b'' - self.iconbuf = self.titlebuf = self.colorbuf = self.qbuf = self.ctbuf = '' + self.iconbuf = self.titlebuf = self.colorbuf = self.ctbuf = '' self.iutf8 = True diff --git a/kitty_tests/parser.py b/kitty_tests/parser.py index 28813428f..ae365b4a8 100644 --- a/kitty_tests/parser.py +++ b/kitty_tests/parser.py @@ -3,11 +3,13 @@ # License: GPL v3 Copyright: 2016, Kovid Goyal import os +from binascii import hexlify from functools import partial from unittest import skipIf +from kitty.fast_data_types import CURSOR_BLOCK, parse_bytes, parse_bytes_dump + from . import BaseTest -from kitty.fast_data_types import parse_bytes, parse_bytes_dump, CURSOR_BLOCK class CmdDump(list): @@ -191,9 +193,27 @@ class TestParser(BaseTest): def test_dcs_codes(self): s = self.create_screen() + c = s.callbacks pb = partial(self.parse_bytes_dump, s) - pb('a\033P+q436f\x9cbcde', 'a', ('screen_request_capabilities', '436f'), 'bcde') + q = hexlify(b'kind').decode('ascii') + pb('a\033P+q{}\x9cbcde'.format(q), 'a', ('screen_request_capabilities', 43, q), 'bcde') self.ae(str(s.line(0)), 'abcde') + self.ae(c.wtcbuf, '\033P1+r{}={}\033\\'.format(q, '5c455b313b3242').encode('ascii')) + c.clear() + pb('\033P$q q\033\\', ('screen_request_capabilities', ord('$'), ' q')) + self.ae(c.wtcbuf, b'\033P1$r1 q\033\\') + c.clear() + pb('\033P$qm\033\\', ('screen_request_capabilities', ord('$'), 'm')) + self.ae(c.wtcbuf, b'\033P1$r0m\033\\') + for sgr in '0;34;102;1;3;4 0;38:5:200;58:2:10:11:12'.split(): + expected = set(sgr.split(';')) - {'0'} + c.clear() + parse_bytes(s, '\033[{}m\033P$qm\033\\'.format(sgr).encode('ascii')) + r = c.wtcbuf.decode('ascii').partition('r')[2].partition('m')[0] + self.ae(expected, set(r.split(';'))) + c.clear() + pb('\033P$qr\033\\', ('screen_request_capabilities', ord('$'), 'r')) + self.ae(c.wtcbuf, '\033P1$r{};{}r\033\\'.format(s.margin_top + 1, s.margin_bottom + 1).encode('ascii')) def test_oth_codes(self): s = self.create_screen()