Implement parsing of OSC codes
This commit is contained in:
parent
1edba3ce42
commit
4ac03a0064
@ -16,10 +16,11 @@ from queue import Queue, Empty
|
|||||||
import glfw
|
import glfw
|
||||||
from pyte.streams import Stream, DebugStream
|
from pyte.streams import Stream, DebugStream
|
||||||
|
|
||||||
|
from .constants import appname
|
||||||
from .char_grid import CharGrid
|
from .char_grid import CharGrid
|
||||||
from .keys import interpret_text_event, interpret_key_event
|
from .keys import interpret_text_event, interpret_key_event
|
||||||
from .screen import Screen
|
from .screen import Screen
|
||||||
from .utils import resize_pty, create_pty
|
from .utils import resize_pty, create_pty, sanitize_title
|
||||||
from .fast_data_types import BRACKETED_PASTE_START, BRACKETED_PASTE_END
|
from .fast_data_types import BRACKETED_PASTE_START, BRACKETED_PASTE_END
|
||||||
|
|
||||||
|
|
||||||
@ -144,7 +145,8 @@ class Boss(Thread):
|
|||||||
|
|
||||||
def render(self):
|
def render(self):
|
||||||
if self.pending_title_change is not None:
|
if self.pending_title_change is not None:
|
||||||
glfw.glfwSetWindowTitle(self.window, self.pending_title_change)
|
t = sanitize_title(self.pending_title_change or appname)
|
||||||
|
glfw.glfwSetWindowTitle(self.window, t)
|
||||||
self.pending_title_change = None
|
self.pending_title_change = None
|
||||||
if self.pending_icon_change is not None:
|
if self.pending_icon_change is not None:
|
||||||
self.pending_icon_change = None # TODO: Implement this
|
self.pending_icon_change = None # TODO: Implement this
|
||||||
|
|||||||
@ -332,6 +332,8 @@ void screen_delete_lines(Screen *self, unsigned int count/*=1*/);
|
|||||||
void screen_delete_characters(Screen *self, unsigned int count);
|
void screen_delete_characters(Screen *self, unsigned int count);
|
||||||
void screen_erase_characters(Screen *self, unsigned int count);
|
void screen_erase_characters(Screen *self, unsigned int count);
|
||||||
void screen_set_margins(Screen *self, unsigned int top, unsigned int bottom);
|
void screen_set_margins(Screen *self, unsigned int top, unsigned int bottom);
|
||||||
|
void set_title(Screen *self, const uint8_t *buf, unsigned int sz);
|
||||||
|
void set_icon(Screen *self, const uint8_t *buf, unsigned int sz);
|
||||||
void report_device_attributes(Screen *self, unsigned int UNUSED mode, bool UNUSED secondary);
|
void report_device_attributes(Screen *self, unsigned int UNUSED mode, bool UNUSED secondary);
|
||||||
void select_graphic_rendition(Screen *self, unsigned int *params, unsigned int count);
|
void select_graphic_rendition(Screen *self, unsigned int *params, unsigned int count);
|
||||||
void report_device_status(Screen *self, unsigned int which, bool UNUSED);
|
void report_device_status(Screen *self, unsigned int which, bool UNUSED);
|
||||||
|
|||||||
@ -414,8 +414,73 @@ HANDLER(csi) {
|
|||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
// Parse OSC {{{
|
// Parse OSC {{{
|
||||||
|
|
||||||
|
static inline void handle_osc(Screen *screen, PyObject UNUSED *dump_callback) {
|
||||||
|
unsigned int code = 0;
|
||||||
|
unsigned int start = screen->parser_buf[0] ? screen->parser_buf[0] : 2;
|
||||||
|
unsigned int sz = screen->parser_buf_pos > start ? screen->parser_buf_pos - start : 0;
|
||||||
|
screen->parser_buf[screen->parser_buf_pos] = 0;
|
||||||
|
if (screen->parser_buf[0] && screen->parser_buf[1]) code = (unsigned int)atoi((const char*)screen->parser_buf + 2);
|
||||||
|
#define DISPATCH_OSC(name) \
|
||||||
|
REPORT_COMMAND(name, sz); \
|
||||||
|
name(screen, screen->parser_buf + start, sz);
|
||||||
|
|
||||||
|
switch(code) {
|
||||||
|
case 0:
|
||||||
|
DISPATCH_OSC(set_title);
|
||||||
|
DISPATCH_OSC(set_icon);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
DISPATCH_OSC(set_icon);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
DISPATCH_OSC(set_title);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
REPORT_ERROR("Unknown OSC code: %u", code);
|
||||||
|
}
|
||||||
|
#undef DISPATCH_OSC
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
HANDLER(osc) {
|
HANDLER(osc) {
|
||||||
screen->parser_state = NORMAL_STATE;
|
#ifdef DUMP_COMMANDS
|
||||||
|
#define HANDLE_OSC handle_osc(screen, dump_callback);
|
||||||
|
#else
|
||||||
|
#define HANDLE_OSC handle_osc(screen, NULL);
|
||||||
|
#endif
|
||||||
|
uint8_t ch = buf[(*pos)++];
|
||||||
|
if (screen->parser_buf_pos == 0) {
|
||||||
|
screen->parser_buf[0] = 0;
|
||||||
|
screen->parser_buf[1] = 1;
|
||||||
|
screen->parser_buf_pos = 2;
|
||||||
|
}
|
||||||
|
switch(ch) {
|
||||||
|
case ST:
|
||||||
|
case BEL:
|
||||||
|
HANDLE_OSC;
|
||||||
|
SET_STATE(NORMAL_STATE);
|
||||||
|
break;
|
||||||
|
case 0:
|
||||||
|
break; // ignore null bytes
|
||||||
|
case ';':
|
||||||
|
if (!screen->parser_buf[0] && screen->parser_buf_pos < 10) {
|
||||||
|
// Initial numeric parameter found
|
||||||
|
screen->parser_buf[0] = screen->parser_buf_pos;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if (!screen->parser_buf[0] && (ch < '0' || ch > '9')) {
|
||||||
|
screen->parser_buf[1] = 0; // No initial numeric parameter
|
||||||
|
}
|
||||||
|
if (screen->parser_buf_pos >= PARSER_BUF_SZ - 1) {
|
||||||
|
REPORT_ERROR("OSC control sequence too long, truncating");
|
||||||
|
HANDLE_OSC;
|
||||||
|
SET_STATE(NORMAL_STATE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
screen->parser_buf[screen->parser_buf_pos++] = ch;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
|
|||||||
@ -741,16 +741,16 @@ void screen_erase_characters(Screen *self, unsigned int count) {
|
|||||||
|
|
||||||
// Device control {{{
|
// Device control {{{
|
||||||
|
|
||||||
static inline void write_to_child(Screen *self, const char *data, unsigned int sz) {
|
static inline void callback(const char *name, Screen *self, const char *data, unsigned int sz) {
|
||||||
if (sz) PyObject_CallMethod(self->callbacks, "write_to_child", "y#", data, sz);
|
if (sz) PyObject_CallMethod(self->callbacks, name, "y#", data, sz);
|
||||||
else PyObject_CallMethod(self->callbacks, "write_to_child", "y", data);
|
else PyObject_CallMethod(self->callbacks, name, "y", data);
|
||||||
if (PyErr_Occurred()) PyErr_Print();
|
if (PyErr_Occurred()) PyErr_Print();
|
||||||
PyErr_Clear();
|
PyErr_Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void report_device_attributes(Screen *self, unsigned int UNUSED mode, bool UNUSED secondary) {
|
void report_device_attributes(Screen *self, unsigned int UNUSED mode, bool UNUSED secondary) {
|
||||||
// Do the same as libvte, which gives the below response regardless of mode and secondary
|
// Do the same as libvte, which gives the below response regardless of mode and secondary
|
||||||
write_to_child(self, "\x1b[?62c", 0); // Corresponds to VT-220
|
callback("write_to_child", self, "\x1b[?62c", 0); // Corresponds to VT-220
|
||||||
}
|
}
|
||||||
|
|
||||||
void report_device_status(Screen *self, unsigned int which, bool UNUSED private) {
|
void report_device_status(Screen *self, unsigned int which, bool UNUSED private) {
|
||||||
@ -760,7 +760,7 @@ void report_device_status(Screen *self, unsigned int which, bool UNUSED private)
|
|||||||
char buf[50] = {0};
|
char buf[50] = {0};
|
||||||
switch(which) {
|
switch(which) {
|
||||||
case 5: // device status
|
case 5: // device status
|
||||||
write_to_child(self, "\x1b[0n", 0);
|
callback("write_to_child", self, "\x1b[0n", 0);
|
||||||
break;
|
break;
|
||||||
case 6: // cursor position
|
case 6: // cursor position
|
||||||
x = self->cursor->x; y = self->cursor->y;
|
x = self->cursor->x; y = self->cursor->y;
|
||||||
@ -770,7 +770,7 @@ void report_device_status(Screen *self, unsigned int which, bool UNUSED private)
|
|||||||
}
|
}
|
||||||
if (self->modes.mDECOM) y -= MAX(y, self->margin_top);
|
if (self->modes.mDECOM) y -= MAX(y, self->margin_top);
|
||||||
x++; y++; // 1-based indexing
|
x++; y++; // 1-based indexing
|
||||||
if (snprintf(buf, sizeof(buf) - 1, "\x1b[%u;%uR", y, x) > 0) write_to_child(self, buf, 0);
|
if (snprintf(buf, sizeof(buf) - 1, "\x1b[%u;%uR", y, x) > 0) callback("write_to_child", self, buf, 0);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -813,6 +813,14 @@ void screen_set_cursor(Screen *self, unsigned int mode, uint8_t secondary) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_title(Screen *self, const uint8_t *buf, unsigned int sz) {
|
||||||
|
callback("title_changed", self, (const char*)buf, sz);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_icon(Screen *self, const uint8_t *buf, unsigned int sz) {
|
||||||
|
callback("icon_changed", self, (const char*)buf, sz);
|
||||||
|
}
|
||||||
|
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
// Python interface {{{
|
// Python interface {{{
|
||||||
|
|||||||
@ -113,6 +113,8 @@ def timeit(name, do_timing=False):
|
|||||||
|
|
||||||
|
|
||||||
def sanitize_title(x):
|
def sanitize_title(x):
|
||||||
|
if isinstance(x, bytes):
|
||||||
|
x = x.decode('utf-8', 'replace')
|
||||||
return re.sub(r'\s+', ' ', re.sub(r'[\0-\x19]', '', x))
|
return re.sub(r'\s+', ' ', re.sub(r'[\0-\x19]', '', x))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -25,8 +25,14 @@ class Callbacks:
|
|||||||
def write_to_child(self, data):
|
def write_to_child(self, data):
|
||||||
self.wtcbuf += data
|
self.wtcbuf += data
|
||||||
|
|
||||||
|
def title_changed(self, data):
|
||||||
|
self.titlebuf += data
|
||||||
|
|
||||||
|
def icon_changed(self, data):
|
||||||
|
self.iconbuf += data
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
self.wtcbuf = b''
|
self.wtcbuf = self.iconbuf = self.titlebuf = b''
|
||||||
|
|
||||||
|
|
||||||
class TestScreen(BaseTest):
|
class TestScreen(BaseTest):
|
||||||
@ -123,3 +129,20 @@ class TestScreen(BaseTest):
|
|||||||
pb('\033[1 q', ('screen_set_cursor', 1, ord(' ')))
|
pb('\033[1 q', ('screen_set_cursor', 1, ord(' ')))
|
||||||
self.assertTrue(s.cursor.blink)
|
self.assertTrue(s.cursor.blink)
|
||||||
self.ae(s.cursor.shape, CURSOR_BLOCK)
|
self.ae(s.cursor.shape, CURSOR_BLOCK)
|
||||||
|
|
||||||
|
def test_osc_codes(self):
|
||||||
|
s = self.create_screen()
|
||||||
|
pb = partial(self.parse_bytes_dump, s)
|
||||||
|
c = Callbacks()
|
||||||
|
s.callbacks = c
|
||||||
|
pb(b'a\033]2;xyz\x9cbcde', ('set_title', 3))
|
||||||
|
self.ae(str(s.line(0)), 'abcde')
|
||||||
|
self.ae(c.titlebuf, b'xyz')
|
||||||
|
c.clear()
|
||||||
|
pb('\033]\x07', ('set_title', 0), ('set_icon', 0))
|
||||||
|
self.ae(c.titlebuf, b''), self.ae(c.iconbuf, b'')
|
||||||
|
pb('\033]23\x07', ('set_title', 2), ('set_icon', 2))
|
||||||
|
self.ae(c.titlebuf, b'23'), self.ae(c.iconbuf, b'23')
|
||||||
|
c.clear()
|
||||||
|
pb('\033]2;;;;\x07', ('set_title', 3))
|
||||||
|
self.ae(c.titlebuf, b';;;')
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user