diff --git a/kitty/boss.py b/kitty/boss.py index d02603699..74282e0ca 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -9,6 +9,7 @@ import struct from gettext import gettext as _ from threading import Thread from time import monotonic +from weakref import WeakValueDictionary from .borders import BordersProgram from .char_grid import load_shader_programs @@ -20,8 +21,8 @@ from .constants import ( from .fast_data_types import ( GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA, GLFW_CURSOR, GLFW_CURSOR_HIDDEN, GLFW_CURSOR_NORMAL, GLFW_MOUSE_BUTTON_1, GLFW_PRESS, GLFW_REPEAT, - ChildMonitor, Timers as _Timers, drain_read, glBlendFunc, - glfw_post_empty_event, glViewport + ChildMonitor, Timers as _Timers, glBlendFunc, glfw_post_empty_event, + glViewport ) from .fonts.render import set_font_family from .keys import ( @@ -91,6 +92,7 @@ class Boss(Thread): def __init__(self, glfw_window, opts, args): Thread.__init__(self, name='ChildMonitor') + self.window_id_map = WeakValueDictionary() startup_session = create_session(opts, args) self.cursor_blink_zero_time = monotonic() self.cursor_blinking = True @@ -100,11 +102,9 @@ class Boss(Thread): self.shutting_down = False self.signal_fd = handle_unix_signals() self.read_wakeup_fd, self.write_wakeup_fd = pipe2() - self.timers = Timers() self.ui_timers = Timers() self.child_monitor = ChildMonitor( - self.read_wakeup_fd, self.on_wakeup, self.signal_fd, self.signal_received, self.timers, - opts.repaint_delay / 1000.0, + self.read_wakeup_fd, self.signal_fd, self.on_child_death, self.update_screen, DumpCommands(args) if args.dump_commands or args.dump_bytes else None) set_boss(self) self.current_font_size = opts.font_size @@ -118,7 +118,8 @@ class Boss(Thread): glfw_window.scroll_callback = self.on_mouse_scroll glfw_window.cursor_pos_callback = self.on_mouse_move glfw_window.window_focus_callback = self.on_focus - self.tab_manager = TabManager(opts, args, startup_session) + self.tab_manager = TabManager(opts, args) + self.tab_manager.init(startup_session) self.sprites = Sprites() self.sprites.do_layout(cell_size.width, cell_size.height) self.cell_program, self.cursor_program = load_shader_programs() @@ -151,23 +152,28 @@ class Boss(Thread): for t in self: yield from t - def on_wakeup(self): - if not self.shutting_down: - drain_read(self.read_wakeup_fd) + def add_child(self, window): + self.child_monitor.add_child(window.id, window.child_fd, window.screen) + self.window_id_map[window.id] = window + wakeup() - def add_child_fd(self, child_fd, window): - ' Must be called in child thread ' - self.child_monitor.add_child(child_fd, window.screen, window.close, window.write_ready, window.update_screen) + def on_child_death(self, window_id): + w = self.window_id_map.get(window_id) + if w is not None: + w.on_child_death() - def remove_child_fd(self, child_fd): - ' Must be called in child thread ' - self.child_monitor.remove_child(child_fd) + def update_screen(self, window_id): + w = self.window_id_map.get(window_id) + if w is not None: + w.update_screen() def close_window(self, window=None): if window is None: window = self.active_window + self.child_monitor.mark_for_close(window.screen.child_fd) + self.gui_close_window() window.destroy() - self.gui_close_window(window) + wakeup() def close_tab(self, tab=None): if tab is None: diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index adc381b0a..4b4ae917e 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -6,178 +6,136 @@ */ #include "data-types.h" +#include +#include +#include #define EXTRA_FDS 2 +#define MAX_CHILDREN 256 -static bool (*read_func)(int, Screen*, PyObject*); +static void (*parse_func)(Screen*, PyObject*); + +typedef struct { + Screen *screen; + bool needs_removal; + int fd; + unsigned long id; +} Child; + +static const Child EMPTY_CHILD = {0}; +#define FREE_CHILD(x) \ + Py_CLEAR((x).screen); x = EMPTY_CHILD; + +#define XREF_CHILD(x, OP) OP(x.screen); +#define INCREF_CHILD(x) XREF_CHILD(x, Py_INCREF) +#define DECREF_CHILD(x) XREF_CHILD(x, Py_DECREF) +#define screen_mutex(op, which) \ + pthread_mutex_##op(&screen->which##_buf_lock); +#define children_mutex(op) \ + pthread_mutex_##op(&children_lock); + + +static Child children[MAX_CHILDREN] = {{0}}; +static Child scratch[MAX_CHILDREN] = {{0}}; +static Child add_queue[MAX_CHILDREN] = {{0}}; +static unsigned long dead_children[MAX_CHILDREN] = {0}; +static size_t num_dead_children = 0; +static size_t add_queue_count = 0; +static struct pollfd fds[MAX_CHILDREN + EXTRA_FDS] = {{0}}; +static pthread_mutex_t children_lock = {{0}}; +static bool created = false, signal_received = false; +static uint8_t read_buf[READ_BUF_SZ]; + + +// Main thread functions {{{ static PyObject * new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { ChildMonitor *self; - PyObject *wakeup_func, *signal_func, *dump_callback; - Timers *timers; - int wakeup_fd, signal_fd; - double delay; + PyObject *dump_callback, *death_notify, *update_screen; + int wakeup_fd, signal_fd, ret; - if (!PyArg_ParseTuple(args, "iOiOOdO", &wakeup_fd, &wakeup_func, &signal_fd, &signal_func, &timers, &delay, &dump_callback)) return NULL; + if (created) { PyErr_SetString(PyExc_RuntimeError, "Can have only a single ChildMonitor instance"); return NULL; } + if (!PyArg_ParseTuple(args, "iiOOO", &wakeup_fd, &signal_fd, &death_notify, &update_screen, &dump_callback)) return NULL; + created = true; + if ((ret = pthread_mutex_init(&children_lock, NULL)) != 0) { + PyErr_Format(PyExc_RuntimeError, "Failed to create children_lock mutex: %s", strerror(ret)); + return NULL; + } self = (ChildMonitor *)type->tp_alloc(type, 0); if (self == NULL) return PyErr_NoMemory(); - self->wakeup_func = wakeup_func; Py_INCREF(wakeup_func); - self->signal_func = signal_func; Py_INCREF(signal_func); - self->timers = timers; Py_INCREF(timers); + self->death_notify = death_notify; Py_INCREF(death_notify); + self->update_screen = update_screen; Py_INCREF(self->update_screen); if (dump_callback != Py_None) { self->dump_callback = dump_callback; Py_INCREF(dump_callback); - read_func = read_bytes_dump; - } else read_func = read_bytes; - self->count = 0; self->children = NULL; - self->fds = (struct pollfd*)PyMem_Calloc(EXTRA_FDS, sizeof(struct pollfd)); - self->repaint_delay = delay; - if (self->fds == NULL) { Py_CLEAR(self); return PyErr_NoMemory(); } - self->fds[0].fd = wakeup_fd; self->fds[1].fd = signal_fd; - self->fds[0].events = POLLIN; self->fds[0].events = POLLIN; + parse_func = parse_worker_dump; + } else parse_func = parse_worker; + self->count = 0; + fds[0].fd = wakeup_fd; fds[1].fd = signal_fd; + fds[0].events = POLLIN; fds[1].events = POLLIN; return (PyObject*) self; } -#define FREE_CHILD(x) \ - Py_CLEAR((x).screen); Py_CLEAR((x).on_exit); Py_CLEAR((x).write_func); Py_CLEAR((x).update_screen); - static void dealloc(ChildMonitor* self) { - Py_CLEAR(self->wakeup_func); Py_CLEAR(self->signal_func); Py_CLEAR(self->timers); Py_CLEAR(self->dump_callback); + pthread_mutex_destroy(&children_lock); + Py_CLEAR(self->dump_callback); + Py_CLEAR(self->death_notify); + Py_CLEAR(self->update_screen); for (size_t i = 0; i < self->count; i++) { - FREE_CHILD(self->children[i]); + FREE_CHILD(children[i]); } - PyMem_Free(self->fds); PyMem_Free(self->children); Py_TYPE(self)->tp_free((PyObject*)self); } -#define COPY_CHILD(src, dest) \ - dest.screen = src.screen; dest.on_exit = src.on_exit; dest.write_func = src.write_func; dest.needs_write = src.needs_write; dest.update_screen = src.update_screen - -#define COPY_POLL_FD(src, dest) \ - dest.fd = src.fd; dest.events = src.events; static PyObject * add_child(ChildMonitor *self, PyObject *args) { -#define add_child_doc "add_child(fd, screen, on_exit, write_func, update_screen) -> Add a child." - int fd; - PyObject *on_exit, *write_func, *update_screen; - Screen *screen; - if (!PyArg_ParseTuple(args, "iOOOO", &fd, &screen, &on_exit, &write_func, &update_screen)) return NULL; - - Child *children = (Child*)PyMem_Calloc(self->count + 1, sizeof(Child)); - struct pollfd *fds = (struct pollfd*)PyMem_Calloc(EXTRA_FDS + 1 + self->count, sizeof(struct pollfd)); - if (children == NULL || fds == NULL) { PyMem_Free(children); PyMem_Free(fds); return PyErr_NoMemory();} - for (size_t i = 0; i < self->count; i++) { - COPY_CHILD(self->children[i], children[i]); +#define add_child_doc "add_child(id, fd, screen) -> Add a child." + children_mutex(lock); + if (self->count + add_queue_count >= MAX_CHILDREN) { PyErr_SetString(PyExc_ValueError, "Too many children"); children_mutex(unlock); return NULL; } + add_queue[add_queue_count] = EMPTY_CHILD; +#define A(attr) &add_queue[add_queue_count].attr + if (!PyArg_ParseTuple(args, "kiO", A(id), A(fd), A(screen))) { + children_mutex(unlock); + return NULL; } - for (size_t i = 0; i < self->count + EXTRA_FDS; i++) { - COPY_POLL_FD(self->fds[i], fds[i]); - } - fds[EXTRA_FDS + self->count].fd = fd; - fds[EXTRA_FDS + self->count].events = POLLIN; -#define ADDATTR(x) children[self->count].x = x; Py_INCREF(x); - ADDATTR(screen); ADDATTR(on_exit); ADDATTR(write_func); ADDATTR(update_screen); - self->count++; - PyMem_Free(self->fds); PyMem_Free(self->children); - self->fds = fds; self->children = children; +#undef A + INCREF_CHILD(add_queue[add_queue_count]); + add_queue_count++; + children_mutex(unlock); Py_RETURN_NONE; } - -static PyObject * -remove_child(ChildMonitor *self, PyObject *pyfd) { -#define remove_child_doc "remove_child(fd) -> Remove a child." - size_t i; bool found = false; - int fd = (int)PyLong_AsLong(pyfd); - for (i = 0; i < self->count; i++) { - if (self->fds[EXTRA_FDS + i].fd == fd) { found = true; break; } - } - if (!found) { Py_RETURN_FALSE; } - Child *children = self->count > 1 ? (Child*)PyMem_Calloc(self->count - 1, sizeof(Child)) : NULL; - struct pollfd *fds = (struct pollfd*)PyMem_Calloc(EXTRA_FDS + self->count - 1, sizeof(struct pollfd)); - if ((self->count > 1 && children == NULL) || fds == NULL) { PyMem_Free(children); PyMem_Free(fds); return PyErr_NoMemory();} - for (size_t s = 0, d = 0; s < self->count; s++) { - if (s != i) { - COPY_CHILD(self->children[s], children[d]); - COPY_POLL_FD(self->fds[s + EXTRA_FDS], fds[d + EXTRA_FDS]); - d++; - } else { - FREE_CHILD(self->children[s]); - } - } - for (i = 0; i < EXTRA_FDS; i++) { - COPY_POLL_FD(self->fds[i], fds[i]); - } - self->count--; - PyMem_Free(self->fds); PyMem_Free(self->children); - self->fds = fds; self->children = children; - Py_RETURN_TRUE; -} static PyObject * needs_write(ChildMonitor *self, PyObject *args) { -#define needs_write_doc "needs_write(fd, yesno) -> Mark a child as needing write or not." - int fd, yesno; - if (!PyArg_ParseTuple(args, "ip", &fd, &yesno)) return NULL; +#define needs_write_doc "needs_write(id, data) -> Queue data to be written to child." + unsigned long id, sz; + const char *data; + if (!PyArg_ParseTuple(args, "ks#", &id, &data, &sz)) return NULL; + if (!sz) { Py_RETURN_NONE; } + children_mutex(lock); for (size_t i = 0; i < self->count; i++) { - if (self->fds[EXTRA_FDS + i].fd == fd) { - self->children[i].needs_write = (bool)yesno; - Py_RETURN_TRUE; - } - } - Py_RETURN_FALSE; -} - -static PyObject * -loop(ChildMonitor *self) { -#define loop_doc "loop() -> The monitor loop." - size_t i; - double interval; - int ret, timeout; - bool has_more; - PyObject *t; - while (LIKELY(!self->shutting_down)) { - for (i = 0; i < self->count + EXTRA_FDS; i++) self->fds[i].revents = 0; - for (i = EXTRA_FDS; i < EXTRA_FDS + self->count; i++) self->fds[i].events = self->children[i - EXTRA_FDS].needs_write ? POLLOUT | POLLIN : POLLIN; - interval = timers_timeout(self->timers); - Py_BEGIN_ALLOW_THREADS; - if (interval < 0) timeout = -1; - else if (interval == 0) timeout = 0; - else timeout = MAX(1, (int)(interval * 1000)); // Sub-millisecond interval will become 0, so round up to 1ms as this is the resolution of poll() - ret = poll(self->fds, self->count + EXTRA_FDS, timeout); - Py_END_ALLOW_THREADS; - if (ret > 0) { -#define PYCALL(x) t = PyObject_CallObject(x, NULL); if (t == NULL) PyErr_Print(); else Py_DECREF(t); - if (self->fds[0].revents && POLLIN) { PYCALL(self->wakeup_func); } - if (self->fds[1].revents && POLLIN) { PYCALL(self->signal_func); } - for (i = 0; i < self->count; i++) { - if (self->fds[EXTRA_FDS + i].revents & (POLLIN | POLLHUP)) { - has_more = read_func(self->fds[EXTRA_FDS + i].fd, self->children[i].screen, self->dump_callback); - if (!has_more) { PYCALL(self->children[i].on_exit); } - } - if (self->fds[EXTRA_FDS + i].revents & POLLOUT) { - PYCALL(self->children[i].write_func); - } - } - } else if (ret < 0) { - if (errno != EAGAIN && errno != EINTR) { - perror("Call to poll() failed"); - } - continue; - } - timers_call(self->timers); - for (i = 0; i < self->count; i++) { - if (self->children[i].screen->change_tracker->dirty) { - if (!timers_add_if_missing(self->timers, self->repaint_delay, self->children[i].update_screen, NULL)) PyErr_Print(); - // update_screen() is responsible for clearing the dirty indication - } + if (children[i].id == id) { + Screen *screen = children[i].screen; + screen_mutex(lock, write); + uint8_t *buf = PyMem_RawRealloc(screen->write_buf, screen->write_buf_sz + sz); + if (buf == NULL) PyErr_NoMemory(); + else { + memcpy(buf + screen->write_buf_sz, data, sz); + screen->write_buf = buf; + screen->write_buf_sz += sz; + } + screen_mutex(unlock, write); + break; } } + children_mutex(unlock); + if (PyErr_Occurred()) return NULL; Py_RETURN_NONE; } - static PyObject * shutdown(ChildMonitor *self) { #define shutdown_doc "shutdown() -> Shutdown the monitor loop." @@ -185,13 +143,238 @@ shutdown(ChildMonitor *self) { Py_RETURN_NONE; } +static PyObject * +parse_input(ChildMonitor *self) { +#define parse_input_doc "parse_input() -> Parse all available input that was read in the I/O thread." + children_mutex(lock); + while (num_dead_children) { + PyObject *t = PyObject_CallFunction(self->death_notify, "k", dead_children[--num_dead_children]); + if (t == NULL) PyErr_Print(); + else Py_DECREF(t); + } + + size_t count = self->count; + bool sr = signal_received; + signal_received = false; + for (size_t i = 0; i < count; i++) { + scratch[i] = children[i]; + INCREF_CHILD(scratch[i]); + } + // TODO: Implement repaint delay here. + children_mutex(unlock); + for (size_t i = 0; i < count; i++) { + if (!scratch[i].needs_removal) { + Screen *screen = scratch[i].screen; + screen_mutex(lock, read); + if (screen->read_buf_sz) { + parse_func(screen, self->dump_callback); + screen->read_buf_sz = 0; + PyObject *t = PyObject_CallFunction(self->update_screen, "k", scratch[i].id); + if (t == NULL) PyErr_Print(); + else Py_DECREF(t); + } + screen_mutex(unlock, read); + } + DECREF_CHILD(scratch[i]); + } + if (sr) { Py_RETURN_TRUE; } + Py_RETURN_FALSE; +} + +static PyObject * +mark_for_close(ChildMonitor *self, PyObject *args) { +#define mark_for_close_doc "Mark a child to be removed from the child monitor" + int fd; + if (!PyArg_ParseTuple(args, "i", &fd)) return NULL; + children_mutex(lock); + for (size_t i = 0; i < self->count; i++) { + if (children[i].fd == fd) { + children[i].needs_removal = true; + break; + } + } + children_mutex(unlock); + Py_RETURN_NONE; +} + + +// }}} + + +// I/O thread functions {{{ + +static inline void +add_children(ChildMonitor *self) { + children_mutex(lock); + for (; add_queue_count > 0 && self->count < MAX_CHILDREN;) { + add_queue_count--; + children[self->count] = add_queue[add_queue_count]; + fds[EXTRA_FDS + self->count].fd = add_queue[add_queue_count].fd; + fds[EXTRA_FDS + self->count].events = POLLIN; + INCREF_CHILD(children[self->count]); + self->count++; + DECREF_CHILD(add_queue[add_queue_count]); + } + children_mutex(unlock); +} + +static inline bool +remove_children(ChildMonitor *self) { + size_t count = 0; + children_mutex(lock); + if (self->count == 0) goto end; + for (ssize_t i = self->count - 1; i >= 0; i--) { + if (children[i].needs_removal) { + count++; + close(fds[EXTRA_FDS + i].fd); + FREE_CHILD(children[i]); + size_t num_to_right = self->count - 1 - i; + if (num_to_right > 0) { + memmove(children + i, children + i + 1, num_to_right * sizeof(Child)); + } + } + } + self->count -= count; +end: + children_mutex(unlock); + return count ? true : false; +} + + +static inline bool +wait_for_read_buf(Screen *screen) { + int c = 1000; + do { + screen_mutex(lock, read); + if (screen->read_buf_sz == 0) return true; + screen_mutex(unlock, read); + struct timespec sleep_time = { .tv_sec = 0, .tv_nsec = 1000000 }; + nanosleep(&sleep_time, NULL); + } while(--c > 0); + return false; +} + + +static bool +read_bytes(int fd, Screen *screen) { + Py_ssize_t len; + + while(true) { + len = read(fd, read_buf, READ_BUF_SZ); + if (len < 0) { + if (errno == EINTR) continue; + if (errno != EIO) perror("Call to read() from child fd failed"); + return false; + } + break; + } + if (UNLIKELY(len == 0)) return false; + if (!wait_for_read_buf(screen)) { + fprintf(stderr, "Screen->read_buf was not emptied after waiting a long time, discarding input."); + return true; + } + memcpy(screen->read_buf, read_buf, len); + screen->read_buf_sz = len; + screen_mutex(unlock, read); + return true; +} + + +static inline void +drain_wakeup(int fd) { + while(true) { + ssize_t len = read(fd, read_buf, READ_BUF_SZ); + if (len < 0) { + if (errno == EINTR) continue; + if (errno != EIO) perror("Call to read() from wakeup fd failed"); + break; + } + break; + } +} + +static inline void +write_to_child(int fd, Screen *screen) { + size_t written = 0; + ssize_t ret = 0; + screen_mutex(lock, write); + while (written < screen->write_buf_sz) { + ret = write(fd, screen->write_buf + written, screen->write_buf_sz - written); + if (ret > 0) { written += ret; } + else if (ret == 0) { + // could mean anything, ignore + break; + } else { + if (errno == EINTR) continue; + if (errno == EWOULDBLOCK || errno == EAGAIN) break; + perror("Call to write() to child fd failed, discarding data."); + written = screen->write_buf_sz; + } + } + screen->write_buf_sz -= written; + screen_mutex(unlock, write); +} + +static PyObject * +loop(ChildMonitor *self) { +#define loop_doc "loop() -> The monitor loop." + size_t i; + int ret; + bool has_more, data_received; + while (LIKELY(!self->shutting_down)) { + data_received = false; + remove_children(self); + add_children(self); + Py_BEGIN_ALLOW_THREADS; + for (i = 0; i < self->count + EXTRA_FDS; i++) fds[i].revents = 0; + for (i = 0; i < self->count; i++) fds[EXTRA_FDS + i].events = children[i].screen->write_buf_sz ? POLLOUT | POLLIN : POLLIN; + ret = poll(fds, self->count + EXTRA_FDS, -1); + if (ret > 0) { + if (fds[0].revents && POLLIN) drain_wakeup(fds[0].fd); + if (fds[1].revents && POLLIN) { + data_received = true; + children_mutex(lock); + signal_received = true; + children_mutex(unlock); + } + for (i = 0; i < self->count; i++) { + if (fds[EXTRA_FDS + i].revents & (POLLIN | POLLHUP)) { + data_received = true; + has_more = read_bytes(fds[EXTRA_FDS + i].fd, children[i].screen); + if (!has_more) { + children_mutex(lock); + children[i].needs_removal = true; + dead_children[num_dead_children++] = children[i].id; + children_mutex(unlock); + } + } + if (fds[EXTRA_FDS + i].revents & POLLOUT) { + write_to_child(children[i].fd, children[i].screen); + } + } + } else if (ret < 0) { + if (errno != EAGAIN && errno != EINTR) { + perror("Call to poll() failed"); + } + } + Py_END_ALLOW_THREADS; + if (data_received) glfwPostEmptyEvent(); + } + for (i = 0; i < self->count; i++) children[i].needs_removal = true; + remove_children(self); + for (i = 0; i < EXTRA_FDS; i++) close(fds[i].fd); + Py_RETURN_NONE; +} +// }}} + // Boilerplate {{{ static PyMethodDef methods[] = { METHOD(add_child, METH_VARARGS) - METHOD(remove_child, METH_O) METHOD(needs_write, METH_VARARGS) METHOD(loop, METH_NOARGS) METHOD(shutdown, METH_NOARGS) + METHOD(parse_input, METH_NOARGS) + METHOD(mark_for_close, METH_VARARGS) {NULL} /* Sentinel */ }; diff --git a/kitty/child.py b/kitty/child.py index 32d429f28..a7dcb3d68 100644 --- a/kitty/child.py +++ b/kitty/child.py @@ -99,7 +99,6 @@ class Child: except ProcessLookupError: return os.killpg(pgrp, signal.SIGHUP) - os.close(self.child_fd) self.child_fd = None def __del__(self): diff --git a/kitty/data-types.c b/kitty/data-types.c index 88a42c31b..37f301dd4 100644 --- a/kitty/data-types.c +++ b/kitty/data-types.c @@ -15,15 +15,6 @@ #include #endif -static char drain_buf[1024] = {0}; - -static PyObject* -drain_read(PyObject UNUSED *self, PyObject *fd) { - ALLOW_UNUSED_RESULT - read(PyLong_AsLong(fd), drain_buf, sizeof(drain_buf)); - END_ALLOW_UNUSED_RESULT - Py_RETURN_NONE; -} static PyObject* wcwidth_wrap(PyObject UNUSED *self, PyObject *chr) { @@ -68,7 +59,6 @@ stop_profiler(PyObject UNUSED *self) { static PyMethodDef module_methods[] = { GL_METHODS - {"drain_read", (PyCFunction)drain_read, METH_O, ""}, {"parse_bytes", (PyCFunction)parse_bytes, METH_VARARGS, ""}, {"parse_bytes_dump", (PyCFunction)parse_bytes_dump, METH_VARARGS, ""}, {"redirect_std_streams", (PyCFunction)redirect_std_streams, METH_VARARGS, ""}, diff --git a/kitty/data-types.h b/kitty/data-types.h index 77b08c32b..f43663440 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -11,6 +11,7 @@ #include #include #include +#include #define PY_SSIZE_T_CLEAN #include #define UNUSED __attribute__ ((unused)) @@ -253,7 +254,9 @@ typedef struct { uint32_t parser_buf[PARSER_BUF_SZ]; unsigned int parser_state, parser_text_start, parser_buf_pos; bool parser_has_pending_text; - uint8_t read_buf[READ_BUF_SZ]; + uint8_t read_buf[READ_BUF_SZ], *write_buf; + size_t read_buf_sz, write_buf_sz; + pthread_mutex_t read_buf_lock, write_buf_lock; } Screen; PyTypeObject Screen_Type; @@ -273,23 +276,12 @@ typedef struct { } Timers; PyTypeObject Timers_Type; -typedef struct { - Screen *screen; - PyObject *on_exit, *write_func, *update_screen; - bool needs_write; -} Child; - typedef struct { PyObject_HEAD - PyObject *wakeup_func, *signal_func, *dump_callback; - Timers *timers; - int wakeup_fd, singal_fd; - struct pollfd *fds; - Child *children; - size_t count; + PyObject *dump_callback, *update_screen, *death_notify; + unsigned int count; bool shutting_down; - double repaint_delay; } ChildMonitor; PyTypeObject ChildMonitor_Type; @@ -319,8 +311,8 @@ int init_Screen(PyObject *); int init_Face(PyObject *); int init_Window(PyObject *); PyObject* create_256_color_table(); -bool read_bytes(int fd, Screen *screen, PyObject *dump_callback); -bool read_bytes_dump(int fd, Screen *screen, PyObject *dump_callback); +void parse_worker(Screen *screen, PyObject *dump_callback); +void parse_worker_dump(Screen *screen, PyObject *dump_callback); PyObject* parse_bytes_dump(PyObject UNUSED *, PyObject *); PyObject* parse_bytes(PyObject UNUSED *, PyObject *); uint32_t decode_utf8(uint32_t*, uint32_t*, uint8_t byte); @@ -355,6 +347,7 @@ void historybuf_add_line(HistoryBuf *self, const Line *line); void historybuf_rewrap(HistoryBuf *self, HistoryBuf *other); void historybuf_init_line(HistoryBuf *self, index_type num, Line *l); +double monotonic(); double timers_timeout(Timers*); void timers_call(Timers*); bool timers_add_if_missing(Timers *self, double delay, PyObject *callback, PyObject *args); diff --git a/kitty/main.py b/kitty/main.py index ccc45d22d..932d936ec 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -168,6 +168,8 @@ def clear_buffers(window, opts): def dispatch_pending_calls(boss): boss.ui_timers.call() + if boss.child_monitor.parse_input(): + boss.signal_received() def run_app(opts, args): diff --git a/kitty/parser.c b/kitty/parser.c index d2a1c783a..346673835 100644 --- a/kitty/parser.c +++ b/kitty/parser.c @@ -6,6 +6,7 @@ #include "data-types.h" #include "control-codes.h" +#include // utils {{{ static unsigned int pow10[10] = { @@ -724,7 +725,7 @@ FLUSH_DRAW; } // }}} -// Boilerplate {{{ +// Entry points {{{ #ifdef DUMP_COMMANDS #define FNAME(x) x##_dump #else @@ -745,28 +746,13 @@ FNAME(parse_bytes)(PyObject UNUSED *self, PyObject *args) { Py_RETURN_NONE; } -bool -FNAME(read_bytes)(int fd, Screen *screen, PyObject *dump_callback) { - Py_ssize_t len; - while(true) { - Py_BEGIN_ALLOW_THREADS; - len = read(fd, screen->read_buf, READ_BUF_SZ); - Py_END_ALLOW_THREADS; - if (len == -1) { - if (errno == EINTR) continue; - if (errno != EIO) perror("Call to read() from child fd failed"); - return false; - } - /* PyObject_Print(Py_BuildValue("y#", screen->read_buf, len), stderr, 0); */ - break; - } - if (UNLIKELY(len == 0)) return false; +void +FNAME(parse_worker)(Screen *screen, PyObject *dump_callback) { #ifdef DUMP_COMMANDS - Py_XDECREF(PyObject_CallFunction(dump_callback, "sy#", "bytes", screen->read_buf, len)); PyErr_Clear(); + Py_XDECREF(PyObject_CallFunction(dump_callback, "sy#", "bytes", screen->read_buf, screen->read_buf_sz)); PyErr_Clear(); #endif - _parse_bytes(screen, screen->read_buf, len, dump_callback); - return true; -} + _parse_bytes(screen, screen->read_buf, screen->read_buf_sz, dump_callback); #undef FNAME +} // }}} diff --git a/kitty/screen.c b/kitty/screen.c index 627bd3611..21ccf4249 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -36,13 +36,23 @@ init_tabstops(bool *tabstops, index_type count) { static PyObject* new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { Screen *self; + int ret = 0; PyObject *callbacks = Py_None; unsigned int columns=80, lines=24, scrollback=0; if (!PyArg_ParseTuple(args, "|OIII", &callbacks, &lines, &columns, &scrollback)) return NULL; self = (Screen *)type->tp_alloc(type, 0); if (self != NULL) { + if ((ret = pthread_mutex_init(&self->read_buf_lock, NULL)) != 0) { + Py_CLEAR(self); PyErr_Format(PyExc_RuntimeError, "Failed to create Screen read_buf_lock mutex: %s", strerror(ret)); + return NULL; + } + if ((ret = pthread_mutex_init(&self->write_buf_lock, NULL)) != 0) { + Py_CLEAR(self); PyErr_Format(PyExc_RuntimeError, "Failed to create Screen write_buf_lock mutex: %s", strerror(ret)); + return NULL; + } self->columns = columns; self->lines = lines; + self->write_buf = NULL; self->modes = empty_modes; self->margin_top = 0; self->margin_bottom = self->lines - 1; RESET_CHARSETS; @@ -162,6 +172,9 @@ screen_change_scrollback_size(Screen *self, unsigned int size) { static void dealloc(Screen* self) { + pthread_mutex_destroy(&self->read_buf_lock); + pthread_mutex_destroy(&self->write_buf_lock); + PyMem_RawFree(self->write_buf); Py_CLEAR(self->callbacks); Py_CLEAR(self->cursor); Py_CLEAR(self->main_linebuf); diff --git a/kitty/tabs.py b/kitty/tabs.py index c71e7d3ee..5061a1861 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -118,7 +118,7 @@ class Tab: window = Window(self, child, self.opts, self.args) if override_title is not None: window.title = window.override_title = override_title - get_boss().add_child_fd(child.child_fd, window) + get_boss().add_child(window) self.active_window_idx = self.current_layout.add_window(self.windows, window, self.active_window_idx) self.relayout_borders() glfw_post_empty_event() @@ -247,7 +247,6 @@ class TabBar: else: self.tab_bar_blank_rects = () self.screen_geometry = calculate_gl_geometry(g, viewport_width, viewport_height, cell_width, cell_height) - self.update() def update(self, data): if self.render_buf is None: @@ -303,12 +302,17 @@ class TabBar: class TabManager: - def __init__(self, opts, args, startup_session): + def __init__(self, opts, args): self.opts, self.args = opts, args - self.tabs = [Tab(opts, args, self.title_changed, t) for t in startup_session.tabs] - self.active_tab_idx = startup_session.active_tab_idx + self.tabs = [] self.tab_bar = TabBar(self.tab_bar_data, opts) self.tab_bar.layout(*self.tab_bar_layout_data) + self.active_tab_idx = 0 + + def init(self, startup_session): + self.tabs = [Tab(self.opts, self.args, self.title_changed, t) for t in startup_session.tabs] + self.active_tab_idx = startup_session.active_tab_idx + self.update_tab_bar() def update_tab_bar(self): if len(self.tabs) > 1: @@ -317,6 +321,7 @@ class TabManager: def resize(self, only_tabs=False): if not only_tabs: self.tab_bar.layout(*self.tab_bar_layout_data) + self.update_tab_bar() for tab in self.tabs: tab.relayout() diff --git a/kitty/window.py b/kitty/window.py index 8f9f0d85b..339c75b64 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -2,10 +2,10 @@ # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2016, Kovid Goyal -import os import weakref from collections import deque from time import monotonic +from itertools import count from .char_grid import CharGrid, DynamicColor from .constants import ( @@ -32,11 +32,14 @@ DYNAMIC_COLOR_CODES = { 19: DynamicColor.highlight_fg, } DYNAMIC_COLOR_CODES.update({k+100: v for k, v in DYNAMIC_COLOR_CODES.items()}) +window_counter = count() +next(window_counter) class Window: def __init__(self, tab, child, opts, args): + self.id = next(window_counter) self.tabref = weakref.ref(tab) self.override_title = None self.last_mouse_cursor_pos = 0, 0 @@ -50,7 +53,6 @@ class Window: self.child_fd = child.child_fd self.start_visual_bell_at = None self.screen = Screen(self, 24, 80, opts.scrollback_lines) - self.write_buf = memoryview(b'') self.char_grid = CharGrid(self.screen, opts) def __repr__(self): @@ -90,28 +92,19 @@ class Window: def close(self): get_boss().close_window(self) + def on_child_death(self): + self.destroy() + get_boss().gui_close_window(self) + def destroy(self): if not self.destroyed: self.destroyed = True self.child.hangup() self.child.get_child_status() # Ensure child does not become zombie - def write_ready(self): - while self.write_buf: - try: - n = os.write(self.child_fd, self.write_buf) - except BlockingIOError: - n = 0 - if not n: - return - self.write_buf = self.write_buf[n:] - if not self.write_buf: - get_boss().child_monitor.needs_write(self.child_fd, False) - def write_to_child(self, data): if data: - self.write_buf = memoryview(self.write_buf.tobytes() + data) - get_boss().child_monitor.needs_write(self.child_fd, True) + get_boss().child_monitor.needs_write(self.id, data) wakeup() def bell(self): @@ -247,7 +240,7 @@ class Window: self.char_grid.update_drag(None, x, y) margin = cell_size.height // 2 if y <= margin or y >= self.geometry.bottom - margin: - get_boss().timers.add(0.02, self.drag_scroll) + get_boss().ui_timers.add(0.02, self.drag_scroll) def drag_scroll(self): x, y = self.last_mouse_cursor_pos @@ -256,7 +249,7 @@ class Window: if y <= margin or y >= self.geometry.bottom - margin: self.scroll_line_up() if y < 50 else self.scroll_line_down() self.char_grid.update_drag(None, x, y) - tm.timers.add(0.02, self.drag_scroll) + tm.ui_timers.add(0.02, self.drag_scroll) def on_mouse_scroll(self, x, y): s = int(round(y * self.opts.wheel_scroll_multiplier))