From 6d8b59cb61a2b067365b9c6fbd29f941b2d97d17 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 13 Sep 2017 13:02:11 +0530 Subject: [PATCH] Track window and application titles in C Allows removing of the last bit fo python from the render loop --- kitty/boss.py | 17 ++--------------- kitty/child-monitor.c | 29 ++++++++++++++++------------- kitty/cocoa_window.m | 5 ++--- kitty/data-types.h | 2 +- kitty/glfw.c | 10 ---------- kitty/main.py | 1 - kitty/state.c | 32 +++++++++++++++++++++++++++++--- kitty/state.h | 2 ++ kitty/tabs.py | 2 +- kitty/window.py | 3 ++- 10 files changed, 55 insertions(+), 48 deletions(-) diff --git a/kitty/boss.py b/kitty/boss.py index 38fe87f08..b042b19d3 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -8,7 +8,7 @@ from weakref import WeakValueDictionary from .char_grid import load_shader_programs from .config import MINIMUM_FONT_SIZE from .constants import ( - MODIFIER_KEYS, cell_size, is_key_pressed, isosx, mouse_button_pressed, + MODIFIER_KEYS, cell_size, is_key_pressed, mouse_button_pressed, mouse_cursor_pos, set_boss, viewport_size, wakeup ) from .fast_data_types import ( @@ -24,9 +24,6 @@ from .session import create_session from .tabs import SpecialWindow, TabManager from .utils import safe_print -if isosx: - from .fast_data_types import cocoa_update_title - class Timers(_Timers): @@ -96,7 +93,7 @@ class Boss: self.ui_timers = Timers() self.child_monitor = ChildMonitor( opts.repaint_delay / 1000.0, glfw_window.window_id(), - self.on_child_death, self.ui_timers, self.render, + self.on_child_death, 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 @@ -321,16 +318,6 @@ class Boss: except AttributeError: pass # needs glfw 3.3 - def render(self): - tab = self.active_tab - if tab is None: - return - if tab.title != self.glfw_window_title: - self.glfw_window_title = tab.title - self.glfw_window.set_title(self.glfw_window_title) - if isosx: - cocoa_update_title(self.glfw_window_title) - def gui_close_window(self, window): window.destroy() for tab in self.tab_manager: diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index 502ea224b..87bf43637 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -112,12 +112,12 @@ self_pipe(int fds[2]) { static PyObject * new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { ChildMonitor *self; - PyObject *dump_callback, *death_notify, *timers, *wid, *render_func; + PyObject *dump_callback, *death_notify, *timers, *wid; int ret; double repaint_delay; if (created) { PyErr_SetString(PyExc_RuntimeError, "Can have only a single ChildMonitor instance"); return NULL; } - if (!PyArg_ParseTuple(args, "dOOOOO", &repaint_delay, &wid, &death_notify, &timers, &render_func, &dump_callback)) return NULL; + if (!PyArg_ParseTuple(args, "dOOOO", &repaint_delay, &wid, &death_notify, &timers, &dump_callback)) return NULL; glfw_window_id = PyLong_AsVoidPtr(wid); created = true; if ((ret = pthread_mutex_init(&children_lock, NULL)) != 0) { @@ -133,7 +133,6 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { self = (ChildMonitor *)type->tp_alloc(type, 0); if (self == NULL) return PyErr_NoMemory(); self->death_notify = death_notify; Py_INCREF(death_notify); - self->render_func = render_func; Py_INCREF(self->render_func); self->timers = (Timers*)timers; Py_INCREF(timers); if (dump_callback != Py_None) { self->dump_callback = dump_callback; Py_INCREF(dump_callback); @@ -153,7 +152,6 @@ dealloc(ChildMonitor* self) { Py_CLEAR(self->dump_callback); Py_CLEAR(self->death_notify); Py_CLEAR(self->timers); - Py_CLEAR(self->render_func); Py_TYPE(self)->tp_free((PyObject*)self); while (remove_queue_count) { remove_queue_count--; @@ -430,13 +428,15 @@ draw_cells_func draw_cells = NULL; draw_cursor_func draw_cursor = NULL; static inline double -cursor_width(unsigned int w, bool vert) { +cursor_width(double w, bool vert) { double dpi = vert ? global_state.logical_dpi_x : global_state.logical_dpi_y; double ans = w * dpi / 72.0; // as pixels double factor = 2.0 / (vert ? global_state.viewport_width : global_state.viewport_height); return ans * factor; } +extern void cocoa_update_title(PyObject*); + static inline void render_cursor(ChildMonitor *self, Window *w, double now) { ScreenRenderData *rd = &w->render_data; @@ -470,7 +470,6 @@ render_cursor(ChildMonitor *self, Window *w, double now) { static inline bool render(ChildMonitor *self, double *timeout, double now) { - PyObject *ret; double time_since_last_render = now - last_render_at; if (time_since_last_render > self->repaint_delay) { draw_borders(); @@ -486,12 +485,15 @@ render(ChildMonitor *self, double *timeout, double now) { } Window *w = tab->windows + tab->active_window; if (w->visible && WD.screen) render_cursor(self, w, now); + if (w->title && w->title != global_state.application_title) { + global_state.application_title = w->title; + glfwSetWindowTitle(glfw_window_id, PyUnicode_AsUTF8(w->title)); +#ifdef __APPLE__ + cocoa_update_title(w->title); +#endif + } #undef WD } - - ret = PyObject_CallFunctionObjArgs(self->render_func, NULL); - if (ret == NULL) { PyErr_Print(); return false; } - else Py_DECREF(ret); glfwSwapBuffers(glfw_window_id); last_render_at = now; } else { @@ -548,8 +550,6 @@ cm_thread_write(PyObject UNUSED *self, PyObject *args) { Py_RETURN_NONE; } -extern void hide_mouse_cursor(); - static PyObject* main_loop(ChildMonitor *self) { #define main_loop_doc "The main thread loop" @@ -558,7 +558,10 @@ main_loop(ChildMonitor *self) { while (!glfwWindowShouldClose(glfw_window_id)) { double now = monotonic(); if (!render(self, &timeout, now)) break; - if (global_state.mouse_visible && OPT(mouse_hide_wait) > 0 && now - global_state.last_mouse_activity_at > OPT(mouse_hide_wait)) hide_mouse_cursor(); + if (global_state.mouse_visible && OPT(mouse_hide_wait) > 0 && now - global_state.last_mouse_activity_at > OPT(mouse_hide_wait)) { + glfwSetInputMode(glfw_window_id, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); + global_state.mouse_visible = false; + } t = timers_timeout(self->timers); timeout = MIN(timeout, t); if (timeout < 0) glfwWaitEvents(); diff --git a/kitty/cocoa_window.m b/kitty/cocoa_window.m index f3e3d321c..45c239762 100644 --- a/kitty/cocoa_window.m +++ b/kitty/cocoa_window.m @@ -131,8 +131,8 @@ cocoa_create_global_menu(PyObject UNUSED *_self) { Py_RETURN_NONE; } -PyObject* -cocoa_update_title(PyObject UNUSED *self, PyObject *pytitle) { +void +cocoa_update_title(PyObject *pytitle) { NSString *title = [[NSString alloc] initWithUTF8String:PyUnicode_AsUTF8(pytitle)]; NSMenu *bar = [NSApp mainMenu]; if (title_menu != NULL) { @@ -178,7 +178,6 @@ static PyMethodDef module_methods[] = { {"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""}, \ {"cocoa_make_window_resizable", (PyCFunction)cocoa_make_window_resizable, METH_O, ""}, \ {"cocoa_create_global_menu", (PyCFunction)cocoa_create_global_menu, METH_NOARGS, ""}, \ - {"cocoa_update_title", (PyCFunction)cocoa_update_title, METH_O, ""}, {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/kitty/data-types.h b/kitty/data-types.h index bfef26a4d..bc2a39674 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -278,7 +278,7 @@ PyTypeObject Timers_Type; typedef struct { PyObject_HEAD - PyObject *dump_callback, *update_screen, *death_notify, *render_func; + PyObject *dump_callback, *update_screen, *death_notify; Timers *timers; double repaint_delay; unsigned int count; diff --git a/kitty/glfw.c b/kitty/glfw.c index da9b3836b..39f782424 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -331,15 +331,6 @@ set_window_icon(WindowWrapper *self, PyObject *args) { } -static PyObject* -_set_title(WindowWrapper *self, PyObject *args) { - char *title; - if(!PyArg_ParseTuple(args, "s", &title)) return NULL; - glfwSetWindowTitle(self->window, title); - Py_RETURN_NONE; -} - - static PyObject* get_framebuffer_size(WindowWrapper *self) { int w, h; @@ -437,7 +428,6 @@ static PyMethodDef methods[] = { MND(set_clipboard_string, METH_VARARGS), MND(make_context_current, METH_NOARGS), MND(window_id, METH_NOARGS), - {"set_title", (PyCFunction)_set_title, METH_VARARGS, ""}, {"set_icon", (PyCFunction)set_window_icon, METH_VARARGS, ""}, {NULL} /* Sentinel */ }; diff --git a/kitty/main.py b/kitty/main.py index 337e84f99..8a16c084d 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -175,7 +175,6 @@ def run_app(opts, args): viewport_size.width = 640 viewport_size.height = 400 window = GLFWWindow(viewport_size.width, viewport_size.height, args.cls) - window.set_title(appname) window.make_context_current() if isosx: from .fast_data_types import cocoa_make_window_resizable, cocoa_create_global_menu diff --git a/kitty/state.c b/kitty/state.c index 4ff1fc2ca..a1f315969 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -40,16 +40,32 @@ add_tab(unsigned int id) { } static inline void -add_window(unsigned int tab_id, unsigned int id) { +add_window(unsigned int tab_id, unsigned int id, PyObject *title) { WITH_TAB(tab_id); ensure_can_add(tab->windows, tab->num_windows, "Too many children (add_window)"); tab->windows[tab->num_windows] = EMPTY_WINDOW; tab->windows[tab->num_windows].id = id; tab->windows[tab->num_windows].visible = true; + tab->windows[tab->num_windows].title = title; + Py_INCREF(tab->windows[tab->num_windows].title); tab->num_windows++; END_WITH_TAB; } +static inline void +update_window_title(unsigned int tab_id, unsigned int window_id, PyObject *title) { + WITH_TAB(tab_id); + for (size_t i = 0; i < tab->num_windows; i++) { + if (tab->windows[i].id == window_id) { + Py_CLEAR(tab->windows[i].title); + tab->windows[i].title = title; + Py_INCREF(tab->windows[i].title); + break; + } + } + END_WITH_TAB; +} + static inline void remove_tab(unsigned int id) { REMOVER(global_state.tabs, id, global_state.num_tabs, EMPTY_TAB, Tab, noop); @@ -58,7 +74,7 @@ remove_tab(unsigned int id) { static inline void remove_window(unsigned int tab_id, unsigned int id) { WITH_TAB(tab_id); -#define destroy_window(w) Py_CLEAR(w.render_data.screen) +#define destroy_window(w) Py_CLEAR(w.render_data.screen); Py_CLEAR(w.title); REMOVER(tab->windows, id, tab->num_windows, EMPTY_WINDOW, Window, destroy_window); #undef destroy_window END_WITH_TAB; @@ -159,8 +175,17 @@ PYWRAP0(destroy_global_data) { Py_RETURN_NONE; } +#define WF(name) PYWRAP1(name) { \ + unsigned int tab_id, window_id; \ + PyObject *title; \ + PA("IIO", &tab_id, &window_id, &title); \ + name(tab_id, window_id, title); \ + Py_RETURN_NONE; \ +} +WF(add_window) +WF(update_window_title) + ONE_UINT(add_tab) -TWO_UINT(add_window) ONE_UINT(remove_tab) TWO_UINT(remove_window) ONE_UINT(set_active_tab) @@ -176,6 +201,7 @@ static PyMethodDef module_methods[] = { MW(set_logical_dpi, METH_VARARGS), MW(add_tab, METH_O), MW(add_window, METH_VARARGS), + MW(update_window_title, METH_VARARGS), MW(remove_tab, METH_O), MW(remove_window, METH_VARARGS), MW(set_active_tab, METH_O), diff --git a/kitty/state.h b/kitty/state.h index af43a6fa4..331d7d249 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -25,6 +25,7 @@ typedef struct { typedef struct { unsigned int id; bool visible; + PyObject *title; ScreenRenderData render_data; } Window; @@ -43,6 +44,7 @@ typedef struct { double cursor_blink_zero_time, last_mouse_activity_at; double logical_dpi_x, logical_dpi_y; int viewport_width, viewport_height; + PyObject *application_title; } GlobalState; #define EXTERNAL_FUNC(name, ret, ...) typedef ret (*name##_func)(__VA_ARGS__); extern name##_func name diff --git a/kitty/tabs.py b/kitty/tabs.py index 08528a0e0..315a97e7c 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -127,7 +127,7 @@ class Tab: # {{{ window.title = window.override_title = override_title # Must add child before laying out so that resize_pty succeeds get_boss().add_child(window) - add_window(self.id, window.id) + add_window(self.id, window.id, window.override_title or window.title or appname) self.active_window_idx = self.current_layout.add_window(self.windows, window, self.active_window_idx) set_active_window(self.id, self.active_window_idx) self.relayout_borders() diff --git a/kitty/window.py b/kitty/window.py index dfd048191..524b383ef 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -20,7 +20,7 @@ from .fast_data_types import ( GLFW_MOUSE_BUTTON_MIDDLE, GLFW_PRESS, GLFW_RELEASE, MOTION_MODE, SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE, Screen, create_cell_vao, glfw_post_empty_event, remove_vao, set_window_render_data, - update_window_visibility + update_window_title, update_window_visibility ) from .keys import get_key_map from .mouse import DRAG, MOVE, PRESS, RELEASE, encode_mouse_event @@ -143,6 +143,7 @@ class Window: def title_changed(self, new_title): if self.override_title is None: self.title = sanitize_title(new_title or appname) + update_window_title(self.tab_id, self.id, self.title) t = self.tabref() if t is not None: t.title_changed(self)