diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index b240402e2..b7c300e0b 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -228,56 +228,92 @@ add_child(ChildMonitor *self, PyObject *args) { Py_RETURN_NONE; } +#define schedule_write_to_child_generic(id, num, va_start, get_next_arg, va_end) \ + ChildMonitor *self = the_monitor; \ + bool found = false; \ + const char *data; \ + size_t szval, sz = 0; \ + va_start(ap, num); \ + for (unsigned int i = 0; i < num; i++) { \ + get_next_arg(ap); \ + sz += szval; \ + } \ + va_end(ap); \ + children_mutex(lock); \ + for (size_t i = 0; i < self->count; i++) { \ + if (children[i].id == id) { \ + Screen *screen = children[i].screen; \ + screen_mutex(lock, write); \ + size_t space_left = screen->write_buf_sz - screen->write_buf_used; \ + if (space_left < sz) { \ + if (screen->write_buf_used + sz > 100 * 1024 * 1024) { \ + log_error("Too much data being sent to child with id: %lu, ignoring it", id); \ + screen_mutex(unlock, write); \ + break; \ + } \ + screen->write_buf_sz = screen->write_buf_used + sz; \ + screen->write_buf = PyMem_RawRealloc(screen->write_buf, screen->write_buf_sz); \ + if (screen->write_buf == NULL) { fatal("Out of memory."); } \ + } \ + found = true; \ + va_start(ap, num); \ + for (unsigned int i = 0; i < num; i++) { \ + get_next_arg(ap); \ + memcpy(screen->write_buf + screen->write_buf_used, data, szval); \ + screen->write_buf_used += szval; \ + } \ + va_end(ap); \ + if (screen->write_buf_sz > BUFSIZ && screen->write_buf_used < BUFSIZ) { \ + screen->write_buf_sz = BUFSIZ; \ + screen->write_buf = PyMem_RawRealloc(screen->write_buf, screen->write_buf_sz); \ + if (screen->write_buf == NULL) { fatal("Out of memory."); } \ + } \ + if (screen->write_buf_used) wakeup_io_loop(self, false); \ + screen_mutex(unlock, write); \ + break; \ + } \ + } \ + children_mutex(unlock); \ + return found; + bool schedule_write_to_child(unsigned long id, unsigned int num, ...) { - ChildMonitor *self = the_monitor; - bool found = false; - const char *data; - size_t sz = 0; va_list ap; - va_start(ap, num); - for (unsigned int i = 0; i < num; i++) { - va_arg(ap, const char*); - sz += va_arg(ap, size_t); - } - va_end(ap); - children_mutex(lock); - for (size_t i = 0; i < self->count; i++) { - if (children[i].id == id) { - Screen *screen = children[i].screen; - screen_mutex(lock, write); - size_t space_left = screen->write_buf_sz - screen->write_buf_used; - if (space_left < sz) { - if (screen->write_buf_used + sz > 100 * 1024 * 1024) { - log_error("Too much data being sent to child with id: %lu, ignoring it", id); - screen_mutex(unlock, write); - break; - } - screen->write_buf_sz = screen->write_buf_used + sz; - screen->write_buf = PyMem_RawRealloc(screen->write_buf, screen->write_buf_sz); - if (screen->write_buf == NULL) { fatal("Out of memory."); } - } - found = true; - va_start(ap, num); - for (unsigned int i = 0; i < num; i++) { - data = va_arg(ap, const char*); - size_t dsz = va_arg(ap, size_t); - memcpy(screen->write_buf + screen->write_buf_used, data, dsz); - screen->write_buf_used += dsz; - } - va_end(ap); - if (screen->write_buf_sz > BUFSIZ && screen->write_buf_used < BUFSIZ) { - screen->write_buf_sz = BUFSIZ; - screen->write_buf = PyMem_RawRealloc(screen->write_buf, screen->write_buf_sz); - if (screen->write_buf == NULL) { fatal("Out of memory."); } - } - if (screen->write_buf_used) wakeup_io_loop(self, false); - screen_mutex(unlock, write); - break; - } - } - children_mutex(unlock); - return found; +#define get_next_arg(ap) data = va_arg(ap, const char*); szval = va_arg(ap, size_t); + schedule_write_to_child_generic(id, num, va_start, get_next_arg, va_end); +#undef get_next_arg +} + +bool +schedule_write_to_child_python(unsigned long id, const char *prefix, PyObject *ap, const char *suffix) { + if (!PyTuple_Check(ap)) return false; + bool has_prefix = prefix && prefix[0], has_suffix = suffix && suffix[0]; + const size_t extra = (has_prefix ? 1 : 0) + (has_suffix ? 1 : 0); + size_t num = PyTuple_GET_SIZE(ap) + extra; + Py_ssize_t pidx; +#define py_start(ap, num) pidx = 0; +#define py_end(ap) pidx = 0; +#define get_next_arg(ap) { \ + if (pidx == 0 && has_prefix) { data = prefix; szval = strlen(prefix); } \ + else { \ + size_t pidxf = pidx++; \ + if (has_prefix) pidxf--; \ + if (has_suffix && pidxf >= (size_t)PyBytes_GET_SIZE(ap)) { data = suffix; szval = strlen(suffix); } \ + else { \ + PyObject *t = PyTuple_GET_ITEM(ap, pidxf); \ + if (PyBytes_Check(t)) { data = PyBytes_AS_STRING(t); szval = PyBytes_GET_SIZE(t); } \ + else { \ + Py_ssize_t usz; \ + data = PyUnicode_AsUTF8AndSize(t, &usz); szval = usz; \ + if (!data) fatal("Failed to convert object to bytes in schedule_write_to_child_python"); \ + } \ + } \ + } \ +} + schedule_write_to_child_generic(id, num, py_start, get_next_arg, py_end); +#undef py_start +#undef py_end +#undef get_next_arg } static PyObject * diff --git a/kitty/data-types.h b/kitty/data-types.h index d23366a97..8f01d5c2f 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -326,6 +326,7 @@ const char* cursor_as_sgr(const Cursor *); PyObject* cm_thread_write(PyObject *self, PyObject *args); bool schedule_write_to_child(unsigned long id, unsigned int num, ...); +bool schedule_write_to_child_python(unsigned long id, const char *prefix, PyObject* tuple_of_str_or_bytes, const char *suffix); bool set_iutf8(int, bool); color_type colorprofile_to_color(ColorProfile *self, color_type entry, color_type defval); diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index 3e2620249..34c15ffc9 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -993,7 +993,7 @@ class Screen: def resize(self, width: int, height: int) -> None: pass - def send_escape_code_to_child(self, code: int, text: Union[str, bytes]) -> bool: + def send_escape_code_to_child(self, code: int, text: Union[str, bytes, Tuple[Union[str, bytes], ...]]) -> bool: pass def reset_callbacks(self) -> None: diff --git a/kitty/screen.c b/kitty/screen.c index 0918b95b3..df2f7071e 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -780,29 +780,35 @@ write_to_child(Screen *self, const char *data, size_t sz) { return written; } -bool -write_escape_code_to_child(Screen *self, unsigned char which, const char *data) { - bool written = false; - const char *prefix, *suffix = self->modes.eight_bit_controls ? "\x9c" : "\033\\"; +static void +get_prefix_and_suffix_for_escape_code(const Screen *self, unsigned char which, const char ** prefix, const char ** suffix) { + *suffix = self->modes.eight_bit_controls ? "\x9c" : "\033\\"; switch(which) { case DCS: - prefix = self->modes.eight_bit_controls ? "\x90" : "\033P"; + *prefix = self->modes.eight_bit_controls ? "\x90" : "\033P"; break; case CSI: - prefix = self->modes.eight_bit_controls ? "\x9b" : "\033["; suffix = ""; + *prefix = self->modes.eight_bit_controls ? "\x9b" : "\033["; *suffix = ""; break; case OSC: - prefix = self->modes.eight_bit_controls ? "\x9d" : "\033]"; + *prefix = self->modes.eight_bit_controls ? "\x9d" : "\033]"; break; case PM: - prefix = self->modes.eight_bit_controls ? "\x9e" : "\033^"; + *prefix = self->modes.eight_bit_controls ? "\x9e" : "\033^"; break; case APC: - prefix = self->modes.eight_bit_controls ? "\x9f" : "\033_"; + *prefix = self->modes.eight_bit_controls ? "\x9f" : "\033_"; break; default: fatal("Unknown escape code to write: %u", which); } +} + +bool +write_escape_code_to_child(Screen *self, unsigned char which, const char *data) { + bool written = false; + const char *prefix, *suffix; + get_prefix_and_suffix_for_escape_code(self, which, &prefix, &suffix); if (self->window_id) { if (suffix[0]) { written = schedule_write_to_child(self->window_id, 3, prefix, strlen(prefix), data, strlen(data), suffix, strlen(suffix)); @@ -818,6 +824,28 @@ write_escape_code_to_child(Screen *self, unsigned char which, const char *data) return written; } +static bool +write_escape_code_to_child_python(Screen *self, unsigned char which, PyObject *data) { + bool written = false; + const char *prefix, *suffix; + get_prefix_and_suffix_for_escape_code(self, which, &prefix, &suffix); + if (self->window_id) written = schedule_write_to_child_python(self->window_id, prefix, data, suffix); + if (self->test_child != Py_None) { + write_to_test_child(self, prefix, strlen(prefix)); + for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(data); i++) { + PyObject *t = PyTuple_GET_ITEM(data, i); + if (PyBytes_Check(t)) write_to_test_child(self, PyBytes_AS_STRING(t), PyBytes_GET_SIZE(t)); + else { + Py_ssize_t sz; + const char *d = PyUnicode_AsUTF8AndSize(t, &sz); + if (d) write_to_test_child(self, d, sz); + } + } + if (suffix[0]) write_to_test_child(self, suffix, strlen(suffix)); + } + return written; +} + static bool cursor_within_margins(Screen *self) { return self->margin_top <= self->cursor->y && self->cursor->y <= self->margin_bottom; @@ -3234,11 +3262,18 @@ toggle_alt_screen(Screen *self, PyObject *a UNUSED) { static PyObject* send_escape_code_to_child(Screen *self, PyObject *args) { int code; - char *text; - Py_ssize_t sz; - if (!PyArg_ParseTuple(args, "is#", &code, &text, &sz)) return NULL; - if (write_escape_code_to_child(self, code, text)) Py_RETURN_TRUE; - Py_RETURN_FALSE; + PyObject *O; + if (!PyArg_ParseTuple(args, "iO", &code, &O)) return NULL; + bool written = false; + if (PyBytes_Check(O)) written = write_escape_code_to_child(self, code, PyBytes_AS_STRING(O)); + else if (PyUnicode_Check(O)) { + const char *t = PyUnicode_AsUTF8(O); + if (t) written = write_escape_code_to_child(self, code, t); + else return NULL; + } else if (PyTuple_Check(O)) written = write_escape_code_to_child_python(self, code, O); + else PyErr_SetString(PyExc_TypeError, "escape code must be str, bytes or tuple"); + if (PyErr_Occurred()) return NULL; + if (written) { Py_RETURN_TRUE; } else { Py_RETURN_FALSE; } } static void