diff --git a/kitty/boss.py b/kitty/boss.py index d19c5f2eb..9992a22da 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -104,7 +104,8 @@ class Boss(Thread): read_wakeup_fd, write_wakeup_fd = pipe2() self.ui_timers = Timers() self.child_monitor = ChildMonitor( - read_wakeup_fd, write_wakeup_fd, self.signal_fd, self.on_child_death, self.update_screen, + read_wakeup_fd, write_wakeup_fd, self.signal_fd, opts.repaint_delay / 1000.0, + self.on_child_death, self.update_screen, self.ui_timers, DumpCommands(args) if args.dump_commands or args.dump_bytes else None) set_boss(self) self.current_font_size = opts.font_size diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index e9bc7880c..d26c81fe7 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -20,6 +20,7 @@ typedef struct { bool needs_removal; int fd; unsigned long id; + double last_paint_at; } Child; static const Child EMPTY_CHILD = {0}; @@ -52,11 +53,12 @@ static uint8_t drain_buf[1024]; static PyObject * new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { ChildMonitor *self; - PyObject *dump_callback, *death_notify, *update_screen; + PyObject *dump_callback, *death_notify, *update_screen, *timers; int wakeup_fd, write_wakeup_fd, signal_fd, ret; + double repaint_delay; if (created) { PyErr_SetString(PyExc_RuntimeError, "Can have only a single ChildMonitor instance"); return NULL; } - if (!PyArg_ParseTuple(args, "iiiOOO", &wakeup_fd, &write_wakeup_fd, &signal_fd, &death_notify, &update_screen, &dump_callback)) return NULL; + if (!PyArg_ParseTuple(args, "iiidOOOO", &wakeup_fd, &write_wakeup_fd, &signal_fd, &repaint_delay, &death_notify, &update_screen, &timers, &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)); @@ -66,6 +68,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { if (self == NULL) return PyErr_NoMemory(); self->death_notify = death_notify; Py_INCREF(death_notify); self->update_screen = update_screen; Py_INCREF(self->update_screen); + self->timers = (Timers*)timers; Py_INCREF(timers); if (dump_callback != Py_None) { self->dump_callback = dump_callback; Py_INCREF(dump_callback); parse_func = parse_worker_dump; @@ -74,6 +77,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { fds[0].fd = wakeup_fd; fds[1].fd = signal_fd; fds[0].events = POLLIN; fds[1].events = POLLIN; self->write_wakeup_fd = write_wakeup_fd; + self->repaint_delay = repaint_delay; return (PyObject*) self; } @@ -84,6 +88,7 @@ dealloc(ChildMonitor* self) { Py_CLEAR(self->dump_callback); Py_CLEAR(self->death_notify); Py_CLEAR(self->update_screen); + Py_CLEAR(self->timers); for (size_t i = 0; i < self->count; i++) { FREE_CHILD(children[i]); } @@ -162,6 +167,20 @@ shutdown(ChildMonitor *self) { Py_RETURN_NONE; } +static inline void +do_parse(ChildMonitor *self, Screen *screen, unsigned long child_id) { + screen_mutex(lock, read); + if (screen->read_buf_sz) { + parse_func(screen, self->dump_callback); + if (screen->read_buf_sz >= READ_BUF_SZ) wakeup_(self->write_wakeup_fd); // Ensure the read fd has POLLIN set + screen->read_buf_sz = 0; + PyObject *t = PyObject_CallFunction(self->update_screen, "k", child_id); + if (t == NULL) PyErr_Print(); + else Py_DECREF(t); + } + screen_mutex(unlock, read); +} + static PyObject * parse_input(ChildMonitor *self) { #define parse_input_doc "parse_input() -> Parse all available input that was read in the I/O thread." @@ -179,24 +198,25 @@ parse_input(ChildMonitor *self) { scratch[i] = children[i]; INCREF_CHILD(scratch[i]); } - // TODO: Implement repaint delay here. children_mutex(unlock); + + double wait_for = self->repaint_delay; 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); - if (screen->read_buf_sz >= READ_BUF_SZ) wakeup_(self->write_wakeup_fd); // Ensure the read fd has POLLIN set - 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); + double now = monotonic(); + double time_since_last_repaint = now - scratch[i].last_paint_at; + if (time_since_last_repaint >= self->repaint_delay) { + do_parse(self, scratch[i].screen, scratch[i].id); + children[i].last_paint_at = now; + } else { + wait_for = MIN(wait_for, self->repaint_delay - time_since_last_repaint); } - screen_mutex(unlock, read); } DECREF_CHILD(scratch[i]); } + if (wait_for < self->repaint_delay) { + timers_add(self->timers, wait_for, false, Py_None, NULL); + } if (sr) { Py_RETURN_TRUE; } Py_RETURN_FALSE; } diff --git a/kitty/data-types.h b/kitty/data-types.h index 9e8940af7..b01eb1c52 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -280,7 +280,9 @@ typedef struct { PyObject_HEAD PyObject *dump_callback, *update_screen, *death_notify; + Timers *timers; int write_wakeup_fd; + double repaint_delay; unsigned int count; bool shutting_down; } ChildMonitor; @@ -351,6 +353,7 @@ void historybuf_init_line(HistoryBuf *self, index_type num, Line *l); double monotonic(); double timers_timeout(Timers*); void timers_call(Timers*); +bool timers_add(Timers *self, double delay, bool, PyObject *callback, PyObject *args); bool timers_add_if_missing(Timers *self, double delay, PyObject *callback, PyObject *args); color_type colorprofile_to_color(ColorProfile *self, color_type entry, color_type defval); diff --git a/kitty/timers.c b/kitty/timers.c index 2f925cd3c..1c4c9ca4c 100644 --- a/kitty/timers.c +++ b/kitty/timers.c @@ -96,28 +96,31 @@ _add(Timers *self, double at, PyObject *callback, PyObject *args) { return true; } +bool +timers_add(Timers *self, double delay, bool update, PyObject *callback, PyObject *args) { + double at = monotonic_() + delay; -static PyObject * -add(Timers *self, PyObject *fargs) { -#define add_doc "add(delay, callback, args) -> Add callback, replacing it if it already exists" - size_t i; - PyObject *callback, *args = NULL; - double delay, at; - if (!PyArg_ParseTuple(fargs, "dO|O", &delay, &callback, &args)) return NULL; - at = monotonic_() + delay; - - for (i = 0; i < self->count; i++) { + for (size_t i = 0; i < self->count; i++) { if (self->events[i].callback == callback) { - self->events[i].at = at; + self->events[i].at = update ? at : MIN(at, self->events[i].at); Py_CLEAR(self->events[i].args); self->events[i].args = args; Py_XINCREF(args); qsort(self->events, self->count, sizeof(TimerEvent), compare_events); - Py_RETURN_NONE; + return true; } } + return _add(self, at, callback, args); +} - if (!_add(self, at, callback, args)) return NULL; + +static PyObject * +add(Timers *self, PyObject *fargs) { +#define add_doc "add(delay, callback, args) -> Add callback, replacing it if it already exists" + PyObject *callback, *args = NULL; + double delay; + if (!PyArg_ParseTuple(fargs, "dO|O", &delay, &callback, &args)) return NULL; + if (!timers_add(self, delay, true, callback, args)) return NULL; Py_RETURN_NONE; } @@ -185,10 +188,12 @@ timers_call(Timers *self) { size_t i, j; for (i = 0, j = 0; i < self->count; i++) { if (self->events[i].at <= now) { // expired, call it - PyObject *ret = PyObject_CallObject(self->events[i].callback, self->events[i].args); + if (self->events[i].callback != Py_None) { + PyObject *ret = PyObject_CallObject(self->events[i].callback, self->events[i].args); + if (ret == NULL) PyErr_Print(); + else Py_DECREF(ret); + } Py_CLEAR(self->events[i].callback); Py_CLEAR(self->events[i].args); - if (ret == NULL) PyErr_Print(); - else Py_DECREF(ret); } else { other[j].callback = self->events[i].callback; other[j].at = self->events[i].at; other[j].args = self->events[i].args; j++;