mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-24 09:18:08 +02:00
Initial attempt at moving all threading code into a single file
This commit is contained in:
@@ -9,6 +9,7 @@ import struct
|
|||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from time import monotonic
|
from time import monotonic
|
||||||
|
from weakref import WeakValueDictionary
|
||||||
|
|
||||||
from .borders import BordersProgram
|
from .borders import BordersProgram
|
||||||
from .char_grid import load_shader_programs
|
from .char_grid import load_shader_programs
|
||||||
@@ -20,8 +21,8 @@ from .constants import (
|
|||||||
from .fast_data_types import (
|
from .fast_data_types import (
|
||||||
GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA, GLFW_CURSOR, GLFW_CURSOR_HIDDEN,
|
GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA, GLFW_CURSOR, GLFW_CURSOR_HIDDEN,
|
||||||
GLFW_CURSOR_NORMAL, GLFW_MOUSE_BUTTON_1, GLFW_PRESS, GLFW_REPEAT,
|
GLFW_CURSOR_NORMAL, GLFW_MOUSE_BUTTON_1, GLFW_PRESS, GLFW_REPEAT,
|
||||||
ChildMonitor, Timers as _Timers, drain_read, glBlendFunc,
|
ChildMonitor, Timers as _Timers, glBlendFunc, glfw_post_empty_event,
|
||||||
glfw_post_empty_event, glViewport
|
glViewport
|
||||||
)
|
)
|
||||||
from .fonts.render import set_font_family
|
from .fonts.render import set_font_family
|
||||||
from .keys import (
|
from .keys import (
|
||||||
@@ -91,6 +92,7 @@ class Boss(Thread):
|
|||||||
|
|
||||||
def __init__(self, glfw_window, opts, args):
|
def __init__(self, glfw_window, opts, args):
|
||||||
Thread.__init__(self, name='ChildMonitor')
|
Thread.__init__(self, name='ChildMonitor')
|
||||||
|
self.window_id_map = WeakValueDictionary()
|
||||||
startup_session = create_session(opts, args)
|
startup_session = create_session(opts, args)
|
||||||
self.cursor_blink_zero_time = monotonic()
|
self.cursor_blink_zero_time = monotonic()
|
||||||
self.cursor_blinking = True
|
self.cursor_blinking = True
|
||||||
@@ -100,11 +102,9 @@ class Boss(Thread):
|
|||||||
self.shutting_down = False
|
self.shutting_down = False
|
||||||
self.signal_fd = handle_unix_signals()
|
self.signal_fd = handle_unix_signals()
|
||||||
self.read_wakeup_fd, self.write_wakeup_fd = pipe2()
|
self.read_wakeup_fd, self.write_wakeup_fd = pipe2()
|
||||||
self.timers = Timers()
|
|
||||||
self.ui_timers = Timers()
|
self.ui_timers = Timers()
|
||||||
self.child_monitor = ChildMonitor(
|
self.child_monitor = ChildMonitor(
|
||||||
self.read_wakeup_fd, self.on_wakeup, self.signal_fd, self.signal_received, self.timers,
|
self.read_wakeup_fd, self.signal_fd, self.on_child_death, self.update_screen,
|
||||||
opts.repaint_delay / 1000.0,
|
|
||||||
DumpCommands(args) if args.dump_commands or args.dump_bytes else None)
|
DumpCommands(args) if args.dump_commands or args.dump_bytes else None)
|
||||||
set_boss(self)
|
set_boss(self)
|
||||||
self.current_font_size = opts.font_size
|
self.current_font_size = opts.font_size
|
||||||
@@ -118,7 +118,8 @@ class Boss(Thread):
|
|||||||
glfw_window.scroll_callback = self.on_mouse_scroll
|
glfw_window.scroll_callback = self.on_mouse_scroll
|
||||||
glfw_window.cursor_pos_callback = self.on_mouse_move
|
glfw_window.cursor_pos_callback = self.on_mouse_move
|
||||||
glfw_window.window_focus_callback = self.on_focus
|
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 = Sprites()
|
||||||
self.sprites.do_layout(cell_size.width, cell_size.height)
|
self.sprites.do_layout(cell_size.width, cell_size.height)
|
||||||
self.cell_program, self.cursor_program = load_shader_programs()
|
self.cell_program, self.cursor_program = load_shader_programs()
|
||||||
@@ -151,23 +152,28 @@ class Boss(Thread):
|
|||||||
for t in self:
|
for t in self:
|
||||||
yield from t
|
yield from t
|
||||||
|
|
||||||
def on_wakeup(self):
|
def add_child(self, window):
|
||||||
if not self.shutting_down:
|
self.child_monitor.add_child(window.id, window.child_fd, window.screen)
|
||||||
drain_read(self.read_wakeup_fd)
|
self.window_id_map[window.id] = window
|
||||||
|
wakeup()
|
||||||
|
|
||||||
def add_child_fd(self, child_fd, window):
|
def on_child_death(self, window_id):
|
||||||
' Must be called in child thread '
|
w = self.window_id_map.get(window_id)
|
||||||
self.child_monitor.add_child(child_fd, window.screen, window.close, window.write_ready, window.update_screen)
|
if w is not None:
|
||||||
|
w.on_child_death()
|
||||||
|
|
||||||
def remove_child_fd(self, child_fd):
|
def update_screen(self, window_id):
|
||||||
' Must be called in child thread '
|
w = self.window_id_map.get(window_id)
|
||||||
self.child_monitor.remove_child(child_fd)
|
if w is not None:
|
||||||
|
w.update_screen()
|
||||||
|
|
||||||
def close_window(self, window=None):
|
def close_window(self, window=None):
|
||||||
if window is None:
|
if window is None:
|
||||||
window = self.active_window
|
window = self.active_window
|
||||||
|
self.child_monitor.mark_for_close(window.screen.child_fd)
|
||||||
|
self.gui_close_window()
|
||||||
window.destroy()
|
window.destroy()
|
||||||
self.gui_close_window(window)
|
wakeup()
|
||||||
|
|
||||||
def close_tab(self, tab=None):
|
def close_tab(self, tab=None):
|
||||||
if tab is None:
|
if tab is None:
|
||||||
|
|||||||
@@ -6,178 +6,136 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "data-types.h"
|
#include "data-types.h"
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <GLFW/glfw3.h>
|
||||||
|
|
||||||
#define EXTRA_FDS 2
|
#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 *
|
static PyObject *
|
||||||
new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||||
ChildMonitor *self;
|
ChildMonitor *self;
|
||||||
PyObject *wakeup_func, *signal_func, *dump_callback;
|
PyObject *dump_callback, *death_notify, *update_screen;
|
||||||
Timers *timers;
|
int wakeup_fd, signal_fd, ret;
|
||||||
int wakeup_fd, signal_fd;
|
|
||||||
double delay;
|
|
||||||
|
|
||||||
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);
|
self = (ChildMonitor *)type->tp_alloc(type, 0);
|
||||||
if (self == NULL) return PyErr_NoMemory();
|
if (self == NULL) return PyErr_NoMemory();
|
||||||
self->wakeup_func = wakeup_func; Py_INCREF(wakeup_func);
|
self->death_notify = death_notify; Py_INCREF(death_notify);
|
||||||
self->signal_func = signal_func; Py_INCREF(signal_func);
|
self->update_screen = update_screen; Py_INCREF(self->update_screen);
|
||||||
self->timers = timers; Py_INCREF(timers);
|
|
||||||
if (dump_callback != Py_None) {
|
if (dump_callback != Py_None) {
|
||||||
self->dump_callback = dump_callback; Py_INCREF(dump_callback);
|
self->dump_callback = dump_callback; Py_INCREF(dump_callback);
|
||||||
read_func = read_bytes_dump;
|
parse_func = parse_worker_dump;
|
||||||
} else read_func = read_bytes;
|
} else parse_func = parse_worker;
|
||||||
self->count = 0; self->children = NULL;
|
self->count = 0;
|
||||||
self->fds = (struct pollfd*)PyMem_Calloc(EXTRA_FDS, sizeof(struct pollfd));
|
fds[0].fd = wakeup_fd; fds[1].fd = signal_fd;
|
||||||
self->repaint_delay = delay;
|
fds[0].events = POLLIN; fds[1].events = POLLIN;
|
||||||
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;
|
|
||||||
|
|
||||||
return (PyObject*) self;
|
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
|
static void
|
||||||
dealloc(ChildMonitor* self) {
|
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++) {
|
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);
|
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 *
|
static PyObject *
|
||||||
add_child(ChildMonitor *self, PyObject *args) {
|
add_child(ChildMonitor *self, PyObject *args) {
|
||||||
#define add_child_doc "add_child(fd, screen, on_exit, write_func, update_screen) -> Add a child."
|
#define add_child_doc "add_child(id, fd, screen) -> Add a child."
|
||||||
int fd;
|
children_mutex(lock);
|
||||||
PyObject *on_exit, *write_func, *update_screen;
|
if (self->count + add_queue_count >= MAX_CHILDREN) { PyErr_SetString(PyExc_ValueError, "Too many children"); children_mutex(unlock); return NULL; }
|
||||||
Screen *screen;
|
add_queue[add_queue_count] = EMPTY_CHILD;
|
||||||
if (!PyArg_ParseTuple(args, "iOOOO", &fd, &screen, &on_exit, &write_func, &update_screen)) return NULL;
|
#define A(attr) &add_queue[add_queue_count].attr
|
||||||
|
if (!PyArg_ParseTuple(args, "kiO", A(id), A(fd), A(screen))) {
|
||||||
Child *children = (Child*)PyMem_Calloc(self->count + 1, sizeof(Child));
|
children_mutex(unlock);
|
||||||
struct pollfd *fds = (struct pollfd*)PyMem_Calloc(EXTRA_FDS + 1 + self->count, sizeof(struct pollfd));
|
return NULL;
|
||||||
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]);
|
|
||||||
}
|
}
|
||||||
for (size_t i = 0; i < self->count + EXTRA_FDS; i++) {
|
#undef A
|
||||||
COPY_POLL_FD(self->fds[i], fds[i]);
|
INCREF_CHILD(add_queue[add_queue_count]);
|
||||||
}
|
add_queue_count++;
|
||||||
fds[EXTRA_FDS + self->count].fd = fd;
|
children_mutex(unlock);
|
||||||
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;
|
|
||||||
Py_RETURN_NONE;
|
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 *
|
static PyObject *
|
||||||
needs_write(ChildMonitor *self, PyObject *args) {
|
needs_write(ChildMonitor *self, PyObject *args) {
|
||||||
#define needs_write_doc "needs_write(fd, yesno) -> Mark a child as needing write or not."
|
#define needs_write_doc "needs_write(id, data) -> Queue data to be written to child."
|
||||||
int fd, yesno;
|
unsigned long id, sz;
|
||||||
if (!PyArg_ParseTuple(args, "ip", &fd, &yesno)) return NULL;
|
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++) {
|
for (size_t i = 0; i < self->count; i++) {
|
||||||
if (self->fds[EXTRA_FDS + i].fd == fd) {
|
if (children[i].id == id) {
|
||||||
self->children[i].needs_write = (bool)yesno;
|
Screen *screen = children[i].screen;
|
||||||
Py_RETURN_TRUE;
|
screen_mutex(lock, write);
|
||||||
}
|
uint8_t *buf = PyMem_RawRealloc(screen->write_buf, screen->write_buf_sz + sz);
|
||||||
}
|
if (buf == NULL) PyErr_NoMemory();
|
||||||
Py_RETURN_FALSE;
|
else {
|
||||||
}
|
memcpy(buf + screen->write_buf_sz, data, sz);
|
||||||
|
screen->write_buf = buf;
|
||||||
static PyObject *
|
screen->write_buf_sz += sz;
|
||||||
loop(ChildMonitor *self) {
|
}
|
||||||
#define loop_doc "loop() -> The monitor loop."
|
screen_mutex(unlock, write);
|
||||||
size_t i;
|
break;
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
children_mutex(unlock);
|
||||||
|
if (PyErr_Occurred()) return NULL;
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
shutdown(ChildMonitor *self) {
|
shutdown(ChildMonitor *self) {
|
||||||
#define shutdown_doc "shutdown() -> Shutdown the monitor loop."
|
#define shutdown_doc "shutdown() -> Shutdown the monitor loop."
|
||||||
@@ -185,13 +143,238 @@ shutdown(ChildMonitor *self) {
|
|||||||
Py_RETURN_NONE;
|
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 {{{
|
// Boilerplate {{{
|
||||||
static PyMethodDef methods[] = {
|
static PyMethodDef methods[] = {
|
||||||
METHOD(add_child, METH_VARARGS)
|
METHOD(add_child, METH_VARARGS)
|
||||||
METHOD(remove_child, METH_O)
|
|
||||||
METHOD(needs_write, METH_VARARGS)
|
METHOD(needs_write, METH_VARARGS)
|
||||||
METHOD(loop, METH_NOARGS)
|
METHOD(loop, METH_NOARGS)
|
||||||
METHOD(shutdown, METH_NOARGS)
|
METHOD(shutdown, METH_NOARGS)
|
||||||
|
METHOD(parse_input, METH_NOARGS)
|
||||||
|
METHOD(mark_for_close, METH_VARARGS)
|
||||||
{NULL} /* Sentinel */
|
{NULL} /* Sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -99,7 +99,6 @@ class Child:
|
|||||||
except ProcessLookupError:
|
except ProcessLookupError:
|
||||||
return
|
return
|
||||||
os.killpg(pgrp, signal.SIGHUP)
|
os.killpg(pgrp, signal.SIGHUP)
|
||||||
os.close(self.child_fd)
|
|
||||||
self.child_fd = None
|
self.child_fd = None
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
|
|||||||
@@ -15,15 +15,6 @@
|
|||||||
#include <gperftools/profiler.h>
|
#include <gperftools/profiler.h>
|
||||||
#endif
|
#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*
|
static PyObject*
|
||||||
wcwidth_wrap(PyObject UNUSED *self, PyObject *chr) {
|
wcwidth_wrap(PyObject UNUSED *self, PyObject *chr) {
|
||||||
@@ -68,7 +59,6 @@ stop_profiler(PyObject UNUSED *self) {
|
|||||||
|
|
||||||
static PyMethodDef module_methods[] = {
|
static PyMethodDef module_methods[] = {
|
||||||
GL_METHODS
|
GL_METHODS
|
||||||
{"drain_read", (PyCFunction)drain_read, METH_O, ""},
|
|
||||||
{"parse_bytes", (PyCFunction)parse_bytes, METH_VARARGS, ""},
|
{"parse_bytes", (PyCFunction)parse_bytes, METH_VARARGS, ""},
|
||||||
{"parse_bytes_dump", (PyCFunction)parse_bytes_dump, METH_VARARGS, ""},
|
{"parse_bytes_dump", (PyCFunction)parse_bytes_dump, METH_VARARGS, ""},
|
||||||
{"redirect_std_streams", (PyCFunction)redirect_std_streams, METH_VARARGS, ""},
|
{"redirect_std_streams", (PyCFunction)redirect_std_streams, METH_VARARGS, ""},
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
|
#include <pthread.h>
|
||||||
#define PY_SSIZE_T_CLEAN
|
#define PY_SSIZE_T_CLEAN
|
||||||
#include <Python.h>
|
#include <Python.h>
|
||||||
#define UNUSED __attribute__ ((unused))
|
#define UNUSED __attribute__ ((unused))
|
||||||
@@ -253,7 +254,9 @@ typedef struct {
|
|||||||
uint32_t parser_buf[PARSER_BUF_SZ];
|
uint32_t parser_buf[PARSER_BUF_SZ];
|
||||||
unsigned int parser_state, parser_text_start, parser_buf_pos;
|
unsigned int parser_state, parser_text_start, parser_buf_pos;
|
||||||
bool parser_has_pending_text;
|
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;
|
} Screen;
|
||||||
PyTypeObject Screen_Type;
|
PyTypeObject Screen_Type;
|
||||||
@@ -273,23 +276,12 @@ typedef struct {
|
|||||||
} Timers;
|
} Timers;
|
||||||
PyTypeObject Timers_Type;
|
PyTypeObject Timers_Type;
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
Screen *screen;
|
|
||||||
PyObject *on_exit, *write_func, *update_screen;
|
|
||||||
bool needs_write;
|
|
||||||
} Child;
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
PyObject_HEAD
|
PyObject_HEAD
|
||||||
|
|
||||||
PyObject *wakeup_func, *signal_func, *dump_callback;
|
PyObject *dump_callback, *update_screen, *death_notify;
|
||||||
Timers *timers;
|
unsigned int count;
|
||||||
int wakeup_fd, singal_fd;
|
|
||||||
struct pollfd *fds;
|
|
||||||
Child *children;
|
|
||||||
size_t count;
|
|
||||||
bool shutting_down;
|
bool shutting_down;
|
||||||
double repaint_delay;
|
|
||||||
} ChildMonitor;
|
} ChildMonitor;
|
||||||
PyTypeObject ChildMonitor_Type;
|
PyTypeObject ChildMonitor_Type;
|
||||||
|
|
||||||
@@ -319,8 +311,8 @@ int init_Screen(PyObject *);
|
|||||||
int init_Face(PyObject *);
|
int init_Face(PyObject *);
|
||||||
int init_Window(PyObject *);
|
int init_Window(PyObject *);
|
||||||
PyObject* create_256_color_table();
|
PyObject* create_256_color_table();
|
||||||
bool read_bytes(int fd, Screen *screen, PyObject *dump_callback);
|
void parse_worker(Screen *screen, PyObject *dump_callback);
|
||||||
bool read_bytes_dump(int fd, Screen *screen, PyObject *dump_callback);
|
void parse_worker_dump(Screen *screen, PyObject *dump_callback);
|
||||||
PyObject* parse_bytes_dump(PyObject UNUSED *, PyObject *);
|
PyObject* parse_bytes_dump(PyObject UNUSED *, PyObject *);
|
||||||
PyObject* parse_bytes(PyObject UNUSED *, PyObject *);
|
PyObject* parse_bytes(PyObject UNUSED *, PyObject *);
|
||||||
uint32_t decode_utf8(uint32_t*, uint32_t*, uint8_t byte);
|
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_rewrap(HistoryBuf *self, HistoryBuf *other);
|
||||||
void historybuf_init_line(HistoryBuf *self, index_type num, Line *l);
|
void historybuf_init_line(HistoryBuf *self, index_type num, Line *l);
|
||||||
|
|
||||||
|
double monotonic();
|
||||||
double timers_timeout(Timers*);
|
double timers_timeout(Timers*);
|
||||||
void timers_call(Timers*);
|
void timers_call(Timers*);
|
||||||
bool timers_add_if_missing(Timers *self, double delay, PyObject *callback, PyObject *args);
|
bool timers_add_if_missing(Timers *self, double delay, PyObject *callback, PyObject *args);
|
||||||
|
|||||||
@@ -168,6 +168,8 @@ def clear_buffers(window, opts):
|
|||||||
|
|
||||||
def dispatch_pending_calls(boss):
|
def dispatch_pending_calls(boss):
|
||||||
boss.ui_timers.call()
|
boss.ui_timers.call()
|
||||||
|
if boss.child_monitor.parse_input():
|
||||||
|
boss.signal_received()
|
||||||
|
|
||||||
|
|
||||||
def run_app(opts, args):
|
def run_app(opts, args):
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include "data-types.h"
|
#include "data-types.h"
|
||||||
#include "control-codes.h"
|
#include "control-codes.h"
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
// utils {{{
|
// utils {{{
|
||||||
static unsigned int pow10[10] = {
|
static unsigned int pow10[10] = {
|
||||||
@@ -724,7 +725,7 @@ FLUSH_DRAW;
|
|||||||
}
|
}
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
// Boilerplate {{{
|
// Entry points {{{
|
||||||
#ifdef DUMP_COMMANDS
|
#ifdef DUMP_COMMANDS
|
||||||
#define FNAME(x) x##_dump
|
#define FNAME(x) x##_dump
|
||||||
#else
|
#else
|
||||||
@@ -745,28 +746,13 @@ FNAME(parse_bytes)(PyObject UNUSED *self, PyObject *args) {
|
|||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
|
||||||
FNAME(read_bytes)(int fd, Screen *screen, PyObject *dump_callback) {
|
|
||||||
Py_ssize_t len;
|
|
||||||
|
|
||||||
while(true) {
|
void
|
||||||
Py_BEGIN_ALLOW_THREADS;
|
FNAME(parse_worker)(Screen *screen, PyObject *dump_callback) {
|
||||||
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;
|
|
||||||
#ifdef DUMP_COMMANDS
|
#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
|
#endif
|
||||||
_parse_bytes(screen, screen->read_buf, len, dump_callback);
|
_parse_bytes(screen, screen->read_buf, screen->read_buf_sz, dump_callback);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
#undef FNAME
|
#undef FNAME
|
||||||
|
}
|
||||||
// }}}
|
// }}}
|
||||||
|
|||||||
@@ -36,13 +36,23 @@ init_tabstops(bool *tabstops, index_type count) {
|
|||||||
static PyObject*
|
static PyObject*
|
||||||
new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||||
Screen *self;
|
Screen *self;
|
||||||
|
int ret = 0;
|
||||||
PyObject *callbacks = Py_None;
|
PyObject *callbacks = Py_None;
|
||||||
unsigned int columns=80, lines=24, scrollback=0;
|
unsigned int columns=80, lines=24, scrollback=0;
|
||||||
if (!PyArg_ParseTuple(args, "|OIII", &callbacks, &lines, &columns, &scrollback)) return NULL;
|
if (!PyArg_ParseTuple(args, "|OIII", &callbacks, &lines, &columns, &scrollback)) return NULL;
|
||||||
|
|
||||||
self = (Screen *)type->tp_alloc(type, 0);
|
self = (Screen *)type->tp_alloc(type, 0);
|
||||||
if (self != NULL) {
|
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->columns = columns; self->lines = lines;
|
||||||
|
self->write_buf = NULL;
|
||||||
self->modes = empty_modes;
|
self->modes = empty_modes;
|
||||||
self->margin_top = 0; self->margin_bottom = self->lines - 1;
|
self->margin_top = 0; self->margin_bottom = self->lines - 1;
|
||||||
RESET_CHARSETS;
|
RESET_CHARSETS;
|
||||||
@@ -162,6 +172,9 @@ screen_change_scrollback_size(Screen *self, unsigned int size) {
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
dealloc(Screen* self) {
|
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->callbacks);
|
||||||
Py_CLEAR(self->cursor);
|
Py_CLEAR(self->cursor);
|
||||||
Py_CLEAR(self->main_linebuf);
|
Py_CLEAR(self->main_linebuf);
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ class Tab:
|
|||||||
window = Window(self, child, self.opts, self.args)
|
window = Window(self, child, self.opts, self.args)
|
||||||
if override_title is not None:
|
if override_title is not None:
|
||||||
window.title = window.override_title = override_title
|
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.active_window_idx = self.current_layout.add_window(self.windows, window, self.active_window_idx)
|
||||||
self.relayout_borders()
|
self.relayout_borders()
|
||||||
glfw_post_empty_event()
|
glfw_post_empty_event()
|
||||||
@@ -247,7 +247,6 @@ class TabBar:
|
|||||||
else:
|
else:
|
||||||
self.tab_bar_blank_rects = ()
|
self.tab_bar_blank_rects = ()
|
||||||
self.screen_geometry = calculate_gl_geometry(g, viewport_width, viewport_height, cell_width, cell_height)
|
self.screen_geometry = calculate_gl_geometry(g, viewport_width, viewport_height, cell_width, cell_height)
|
||||||
self.update()
|
|
||||||
|
|
||||||
def update(self, data):
|
def update(self, data):
|
||||||
if self.render_buf is None:
|
if self.render_buf is None:
|
||||||
@@ -303,12 +302,17 @@ class TabBar:
|
|||||||
|
|
||||||
class TabManager:
|
class TabManager:
|
||||||
|
|
||||||
def __init__(self, opts, args, startup_session):
|
def __init__(self, opts, args):
|
||||||
self.opts, self.args = opts, args
|
self.opts, self.args = opts, args
|
||||||
self.tabs = [Tab(opts, args, self.title_changed, t) for t in startup_session.tabs]
|
self.tabs = []
|
||||||
self.active_tab_idx = startup_session.active_tab_idx
|
|
||||||
self.tab_bar = TabBar(self.tab_bar_data, opts)
|
self.tab_bar = TabBar(self.tab_bar_data, opts)
|
||||||
self.tab_bar.layout(*self.tab_bar_layout_data)
|
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):
|
def update_tab_bar(self):
|
||||||
if len(self.tabs) > 1:
|
if len(self.tabs) > 1:
|
||||||
@@ -317,6 +321,7 @@ class TabManager:
|
|||||||
def resize(self, only_tabs=False):
|
def resize(self, only_tabs=False):
|
||||||
if not only_tabs:
|
if not only_tabs:
|
||||||
self.tab_bar.layout(*self.tab_bar_layout_data)
|
self.tab_bar.layout(*self.tab_bar_layout_data)
|
||||||
|
self.update_tab_bar()
|
||||||
for tab in self.tabs:
|
for tab in self.tabs:
|
||||||
tab.relayout()
|
tab.relayout()
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
# vim:fileencoding=utf-8
|
# vim:fileencoding=utf-8
|
||||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
import os
|
|
||||||
import weakref
|
import weakref
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from time import monotonic
|
from time import monotonic
|
||||||
|
from itertools import count
|
||||||
|
|
||||||
from .char_grid import CharGrid, DynamicColor
|
from .char_grid import CharGrid, DynamicColor
|
||||||
from .constants import (
|
from .constants import (
|
||||||
@@ -32,11 +32,14 @@ DYNAMIC_COLOR_CODES = {
|
|||||||
19: DynamicColor.highlight_fg,
|
19: DynamicColor.highlight_fg,
|
||||||
}
|
}
|
||||||
DYNAMIC_COLOR_CODES.update({k+100: v for k, v in DYNAMIC_COLOR_CODES.items()})
|
DYNAMIC_COLOR_CODES.update({k+100: v for k, v in DYNAMIC_COLOR_CODES.items()})
|
||||||
|
window_counter = count()
|
||||||
|
next(window_counter)
|
||||||
|
|
||||||
|
|
||||||
class Window:
|
class Window:
|
||||||
|
|
||||||
def __init__(self, tab, child, opts, args):
|
def __init__(self, tab, child, opts, args):
|
||||||
|
self.id = next(window_counter)
|
||||||
self.tabref = weakref.ref(tab)
|
self.tabref = weakref.ref(tab)
|
||||||
self.override_title = None
|
self.override_title = None
|
||||||
self.last_mouse_cursor_pos = 0, 0
|
self.last_mouse_cursor_pos = 0, 0
|
||||||
@@ -50,7 +53,6 @@ class Window:
|
|||||||
self.child_fd = child.child_fd
|
self.child_fd = child.child_fd
|
||||||
self.start_visual_bell_at = None
|
self.start_visual_bell_at = None
|
||||||
self.screen = Screen(self, 24, 80, opts.scrollback_lines)
|
self.screen = Screen(self, 24, 80, opts.scrollback_lines)
|
||||||
self.write_buf = memoryview(b'')
|
|
||||||
self.char_grid = CharGrid(self.screen, opts)
|
self.char_grid = CharGrid(self.screen, opts)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@@ -90,28 +92,19 @@ class Window:
|
|||||||
def close(self):
|
def close(self):
|
||||||
get_boss().close_window(self)
|
get_boss().close_window(self)
|
||||||
|
|
||||||
|
def on_child_death(self):
|
||||||
|
self.destroy()
|
||||||
|
get_boss().gui_close_window(self)
|
||||||
|
|
||||||
def destroy(self):
|
def destroy(self):
|
||||||
if not self.destroyed:
|
if not self.destroyed:
|
||||||
self.destroyed = True
|
self.destroyed = True
|
||||||
self.child.hangup()
|
self.child.hangup()
|
||||||
self.child.get_child_status() # Ensure child does not become zombie
|
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):
|
def write_to_child(self, data):
|
||||||
if data:
|
if data:
|
||||||
self.write_buf = memoryview(self.write_buf.tobytes() + data)
|
get_boss().child_monitor.needs_write(self.id, data)
|
||||||
get_boss().child_monitor.needs_write(self.child_fd, True)
|
|
||||||
wakeup()
|
wakeup()
|
||||||
|
|
||||||
def bell(self):
|
def bell(self):
|
||||||
@@ -247,7 +240,7 @@ class Window:
|
|||||||
self.char_grid.update_drag(None, x, y)
|
self.char_grid.update_drag(None, x, y)
|
||||||
margin = cell_size.height // 2
|
margin = cell_size.height // 2
|
||||||
if y <= margin or y >= self.geometry.bottom - margin:
|
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):
|
def drag_scroll(self):
|
||||||
x, y = self.last_mouse_cursor_pos
|
x, y = self.last_mouse_cursor_pos
|
||||||
@@ -256,7 +249,7 @@ class Window:
|
|||||||
if y <= margin or y >= self.geometry.bottom - margin:
|
if y <= margin or y >= self.geometry.bottom - margin:
|
||||||
self.scroll_line_up() if y < 50 else self.scroll_line_down()
|
self.scroll_line_up() if y < 50 else self.scroll_line_down()
|
||||||
self.char_grid.update_drag(None, x, y)
|
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):
|
def on_mouse_scroll(self, x, y):
|
||||||
s = int(round(y * self.opts.wheel_scroll_multiplier))
|
s = int(round(y * self.opts.wheel_scroll_multiplier))
|
||||||
|
|||||||
Reference in New Issue
Block a user