Support for DCS status and capabilities reporting codes

This commit is contained in:
Kovid Goyal 2017-12-02 14:35:06 +05:30
parent c12bce3d2f
commit b9798c74d4
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
8 changed files with 197 additions and 92 deletions

View File

@ -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"

View File

@ -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);

View File

@ -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:

View File

@ -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;
}
}
// }}}

View File

@ -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);

View File

@ -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\\'

View File

@ -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

View File

@ -3,11 +3,13 @@
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
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()