diff --git a/kitty/boss.py b/kitty/boss.py index d812c08bd..a6572e3b3 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -3,7 +3,6 @@ # License: GPL v3 Copyright: 2016, Kovid Goyal from gettext import gettext as _ -from time import monotonic from weakref import WeakValueDictionary from .char_grid import load_shader_programs @@ -16,7 +15,7 @@ from .fast_data_types import ( GLFW_CURSOR, GLFW_CURSOR_HIDDEN, GLFW_CURSOR_NORMAL, GLFW_MOUSE_BUTTON_1, GLFW_PRESS, GLFW_REPEAT, ChildMonitor, Timers as _Timers, destroy_global_data, destroy_sprite_map, glfw_post_empty_event, - layout_sprite_map, resize_gl_viewport + layout_sprite_map ) from .fonts.render import render_cell_wrapper, set_font_family from .keys import ( @@ -91,7 +90,6 @@ class Boss: def __init__(self, glfw_window, opts, args): self.window_id_map = WeakValueDictionary() startup_session = create_session(opts, args) - self.cursor_blink_zero_time = monotonic() self.cursor_blinking = True self.window_is_focused = True self.glfw_window_title = None @@ -119,7 +117,6 @@ class Boss: layout_sprite_map(cell_size.width, cell_size.height, render_cell_wrapper) self.glfw_window.set_click_cursor(False) self.show_mouse_cursor() - self.start_cursor_blink() @property def current_tab_bar_height(self): @@ -165,14 +162,8 @@ class Boss: self.io_thread_started = True def on_window_resize(self, window, w, h): - # debounce resize events - if w > 100 and h > 100: - viewport_size.width, viewport_size.height = w, h - self.tab_manager.resize() - resize_gl_viewport(w, h) - glfw_post_empty_event() - else: - safe_print('Ignoring resize request for sizes under 100x100') + viewport_size.width, viewport_size.height = w, h + self.tab_manager.resize() def increase_font_size(self): self.change_font_size( @@ -232,8 +223,6 @@ class Boss: def on_key(self, window, key, scancode, action, mods): is_key_pressed[key] = action == GLFW_PRESS - self.start_cursor_blink() - self.cursor_blink_zero_time = monotonic() func = None if action == GLFW_PRESS or action == GLFW_REPEAT: func = get_shortcut(self.opts.keymap, mods, key, scancode) @@ -346,16 +335,6 @@ class Boss: except AttributeError: pass # needs glfw 3.3 - def start_cursor_blink(self): - self.cursor_blinking = True - if self.opts.cursor_stop_blinking_after > 0: - self.ui_timers.add( - self.opts.cursor_stop_blinking_after, - self.stop_cursor_blinking) - - def stop_cursor_blinking(self): - self.cursor_blinking = False - def render(self): tab = self.active_tab if tab is None: @@ -365,19 +344,6 @@ class Boss: self.glfw_window.set_title(self.glfw_window_title) if isosx: cocoa_update_title(self.glfw_window_title) - active = self.active_window - if active is not None: - draw_cursor = True - if self.cursor_blinking and self.opts.cursor_blink_interval > 0 and self.window_is_focused: - now = monotonic() - self.cursor_blink_zero_time - t = int(now * 1000) - d = int(self.opts.cursor_blink_interval * 1000) - n = t // d - draw_cursor = n % 2 == 0 - self.ui_timers.add_if_before( - ((n + 1) * d / 1000) - now, None) - if draw_cursor: - active.char_grid.render_cursor(self.window_is_focused) def gui_close_window(self, window): window.destroy() diff --git a/kitty/char_grid.py b/kitty/char_grid.py index 299a26555..d197a0d11 100644 --- a/kitty/char_grid.py +++ b/kitty/char_grid.py @@ -3,22 +3,16 @@ # License: GPL v3 Copyright: 2016, Kovid Goyal import re -from collections import namedtuple from enum import Enum from .config import build_ansi_color_table from .constants import ScreenGeometry, cell_size, viewport_size from .fast_data_types import ( - CELL_PROGRAM, CURSOR_BEAM, CURSOR_BLOCK, CURSOR_PROGRAM, CURSOR_UNDERLINE, - compile_program, draw_cursor, init_cell_program, init_cursor_program + CELL_PROGRAM, CURSOR_PROGRAM, compile_program, init_cell_program, + init_cursor_program ) from .rgb import to_color -from .utils import ( - color_as_int, get_logical_dpi, load_shaders, open_url, - set_primary_selection -) - -Cursor = namedtuple('Cursor', 'x y shape blink') +from .utils import color_as_int, load_shaders, open_url, set_primary_selection class DynamicColor(Enum): @@ -52,9 +46,7 @@ class CharGrid: self.screen.color_profile.update_ansi_color_table(build_ansi_color_table(opts)) self.screen.color_profile.set_configured_colors(*map(color_as_int, ( opts.foreground, opts.background, opts.cursor, opts.selection_foreground, opts.selection_background))) - self.dpix, self.dpiy = get_logical_dpi() self.opts = opts - self.default_cursor = Cursor(0, 0, opts.cursor_shape, opts.cursor_blink_interval > 0) self.opts = opts def update_position(self, window_geometry): @@ -158,28 +150,3 @@ class CharGrid: def text_for_selection(self): return ''.join(self.screen.text_for_selection()) - - def render_cursor(self, is_focused): - if not self.screen.cursor_visible or self.screen.scrolled_by: - return - cursor = self.screen.cursor - - def width(w=2, vert=True): - dpi = self.dpix if vert else self.dpiy - w *= dpi / 72.0 # as pixels - factor = 2 / (viewport_size.width if vert else viewport_size.height) - return w * factor - - sg = self.screen_geometry - left = sg.xstart + cursor.x * sg.dx - top = sg.ystart - cursor.y * sg.dy - col = self.screen.color_profile.cursor_color - shape = cursor.shape or self.default_cursor.shape - alpha = self.opts.cursor_opacity - mult = self.screen.current_char_width() - right = left + (width(1.5) if shape == CURSOR_BEAM else sg.dx * mult) - bottom = top - sg.dy - if shape == CURSOR_UNDERLINE: - top = bottom + width(vert=False) - semi_transparent = alpha < 1.0 and shape == CURSOR_BLOCK - draw_cursor(semi_transparent, is_focused, col, alpha, left, right, top, bottom) diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index 38fc8fee9..1cc018d90 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -16,6 +16,7 @@ extern int pthread_setname_np(const char *name); #undef _GNU_SOURCE #endif #include "state.h" +#include "screen.h" #include #include #include @@ -426,6 +427,46 @@ pyset_iutf8(ChildMonitor *self, PyObject *args) { static double last_render_at = -DBL_MAX; draw_borders_func draw_borders = NULL; draw_cells_func draw_cells = NULL; +draw_cursor_func draw_cursor = NULL; + +static inline double +cursor_width(unsigned int 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; +} + +static inline void +render_cursor(ChildMonitor *self, Window *w, double now) { + ScreenRenderData *rd = &w->render_data; + if (rd->screen->scrolled_by || ! screen_is_cursor_visible(rd->screen)) return; + double time_since_start_blink = now - global_state.cursor_blink_zero_time; + bool cursor_blinking = OPT(cursor_blink_interval) > 0 && global_state.application_focused && time_since_start_blink <= OPT(cursor_stop_blinking_after) ? true : false; + bool do_draw_cursor = true; + if (cursor_blinking) { + int t = (int)(time_since_start_blink * 1000); + int d = (int)(OPT(cursor_blink_interval) * 1000); + int n = t / d; + do_draw_cursor = n % 2 == 0 ? true : false; + double delay = MAX(0, ((n + 1) * d / 1000) - time_since_start_blink); + timers_add_if_before(self->timers, delay, Py_None, NULL); + } + if (do_draw_cursor) { + Cursor *cursor = rd->screen->cursor; + double left = rd->xstart + cursor->x * rd->dx; + double top = rd->ystart - cursor->y * rd->dy; + int shape = cursor->shape ? cursor->shape : OPT(cursor_shape); + unsigned long mult = screen_current_char_width(rd->screen); + double right = left + (shape == CURSOR_BEAM ? cursor_width(1.5, true) : rd->dx * mult); + double bottom = top - rd->dy; + if (shape == CURSOR_UNDERLINE) top = bottom + cursor_width(2.0, false); + bool semi_transparent = OPT(cursor_opacity) < 1.0 && shape == CURSOR_BLOCK ? true : false; + ColorProfile *cp = rd->screen->color_profile; + color_type col = colorprofile_to_color(cp, cp->overridden.cursor_color, cp->configured.cursor_color); + draw_cursor(semi_transparent, global_state.application_focused, col, OPT(cursor_opacity), left, right, top, bottom); + } +} static inline bool render(ChildMonitor *self, double *timeout) { @@ -443,8 +484,10 @@ render(ChildMonitor *self, double *timeout) { Window *w = tab->windows + i; #define WD w->render_data if (w->visible && WD.screen) draw_cells(WD.vao_idx, WD.xstart, WD.ystart, WD.dx, WD.dy, WD.screen); -#undef WD } + Window *w = tab->windows + tab->active_window; + if (w->visible && WD.screen) render_cursor(self, w, now); +#undef WD } ret = PyObject_CallFunctionObjArgs(self->render_func, NULL); diff --git a/kitty/cursor.c b/kitty/cursor.c index aef382725..03cdef8ed 100644 --- a/kitty/cursor.c +++ b/kitty/cursor.c @@ -27,12 +27,15 @@ static int __eq__(Cursor *a, Cursor *b) { return EQ(bold) && EQ(italic) && EQ(strikethrough) && EQ(reverse) && EQ(decoration) && EQ(fg) && EQ(bg) && EQ(decoration_fg) && EQ(x) && EQ(y) && EQ(shape) && EQ(blink); } +static const char* cursor_names[NUM_OF_CURSOR_SHAPES] = { "NO_SHAPE", "BLOCK", "BEAM", "UNDERLINE" }; + #define BOOL(x) ((x) ? Py_True : Py_False) static PyObject * repr(Cursor *self) { return PyUnicode_FromFormat( - "Cursor(x=%u, y=%u, shape=%d, blink=%R, fg=#%08x, bg=#%08x, bold=%R, italic=%R, reverse=%R, strikethrough=%R, decoration=%d, decoration_fg=#%08x)", - self->x, self->y, self->shape, BOOL(self->blink), self->fg, self->bg, BOOL(self->bold), BOOL(self->italic), BOOL(self->reverse), BOOL(self->strikethrough), self->decoration, self->decoration_fg + "Cursor(x=%u, y=%u, shape=%s, blink=%R, fg=#%08x, bg=#%08x, bold=%R, italic=%R, reverse=%R, strikethrough=%R, decoration=%d, decoration_fg=#%08x)", + self->x, self->y, (self->shape < NUM_OF_CURSOR_SHAPES && self->shape >= 0 ? cursor_names[self->shape] : "INVALID"), + BOOL(self->blink), self->fg, self->bg, BOOL(self->bold), BOOL(self->italic), BOOL(self->reverse), BOOL(self->strikethrough), self->decoration, self->decoration_fg ); } @@ -51,7 +54,7 @@ reset_display_attrs(Cursor *self) { void cursor_reset(Cursor *self) { cursor_reset_display_attrs(self); self->x = 0; self->y = 0; - self->shape = 0; self->blink = false; + self->shape = NO_CURSOR_SHAPE; self->blink = false; } void cursor_copy_to(Cursor *src, Cursor *dest) { @@ -75,7 +78,7 @@ BOOL_GETSET(Cursor, blink) static PyMemberDef members[] = { {"x", T_UINT, offsetof(Cursor, x), 0, "x"}, {"y", T_UINT, offsetof(Cursor, y), 0, "y"}, - {"shape", T_UBYTE, offsetof(Cursor, shape), 0, "shape"}, + {"shape", T_INT, offsetof(Cursor, shape), 0, "shape"}, {"decoration", T_UBYTE, offsetof(Cursor, decoration), 0, "decoration"}, {"fg", T_ULONG, offsetof(Cursor, fg), 0, "fg"}, {"bg", T_ULONG, offsetof(Cursor, bg), 0, "bg"}, diff --git a/kitty/data-types.h b/kitty/data-types.h index db01f36a4..bfef26a4d 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -29,6 +29,7 @@ typedef uint32_t color_type; typedef uint32_t combining_type; typedef unsigned int index_type; typedef uint16_t sprite_index; +typedef enum CursorShapes { NO_CURSOR_SHAPE, CURSOR_BLOCK, CURSOR_BEAM, CURSOR_UNDERLINE, NUM_OF_CURSOR_SHAPES } CursorShape; #define ERROR_PREFIX "[PARSE ERROR]" #define ANY_MODE 3 @@ -61,9 +62,6 @@ typedef uint16_t sprite_index; #define DECORATION_FG_CODE 58 #define CHAR_IS_BLANK(ch) ((ch & CHAR_MASK) == 32 || (ch & CHAR_MASK) == 0) -#define CURSOR_BLOCK 1 -#define CURSOR_BEAM 2 -#define CURSOR_UNDERLINE 3 #define FG 1 #define BG 2 @@ -172,7 +170,8 @@ typedef struct { bool bold, italic, reverse, strikethrough, blink; unsigned int x, y; - uint8_t decoration, shape; + uint8_t decoration; + CursorShape shape; unsigned long fg, bg, decoration_fg; } Cursor; diff --git a/kitty/glfw.c b/kitty/glfw.c index b7f2d2ff2..e78a264e5 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -39,19 +39,27 @@ typedef struct { // callbacks {{{ static WindowWrapper* the_window = NULL; +update_viewport_size_func update_viewport_size = NULL; static void framebuffer_size_callback(GLFWwindow UNUSED *w, int width, int height) { - WINDOW_CALLBACK(framebuffer_size_callback, "ii", width, height); + if (width > 100 && height > 100) { + update_viewport_size(width, height); + global_state.viewport_width = width; global_state.viewport_height = height; + WINDOW_CALLBACK(framebuffer_size_callback, "ii", width, height); + glfwPostEmptyEvent(); + } else fprintf(stderr, "Ignoring resize request for tiny size: %dx%d\n", width, height); } static void char_mods_callback(GLFWwindow UNUSED *w, unsigned int codepoint, int mods) { + global_state.cursor_blink_zero_time = monotonic(); WINDOW_CALLBACK(char_mods_callback, "Ii", codepoint, mods); } static void key_callback(GLFWwindow UNUSED *w, int key, int scancode, int action, int mods) { + global_state.cursor_blink_zero_time = monotonic(); WINDOW_CALLBACK(key_callback, "iiii", key, scancode, action, mods); } @@ -67,12 +75,14 @@ scroll_callback(GLFWwindow UNUSED *w, double xoffset, double yoffset) { static void cursor_pos_callback(GLFWwindow UNUSED *w, double x, double y) { + global_state.cursor_blink_zero_time = monotonic(); WINDOW_CALLBACK(cursor_pos_callback, "dd", x, y); } static void window_focus_callback(GLFWwindow UNUSED *w, int focused) { global_state.application_focused = focused ? true : false; + global_state.cursor_blink_zero_time = monotonic(); WINDOW_CALLBACK(window_focus_callback, "O", focused ? Py_True : Py_False); } // }}} @@ -90,6 +100,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { the_window = self; self->window = glfwCreateWindow(width, height, title, NULL, NULL); if (self->window == NULL) { Py_CLEAR(self); the_window = NULL; PyErr_SetString(PyExc_ValueError, "Failed to create GLFWwindow"); return NULL; } + global_state.viewport_width = width; global_state.viewport_height = height; self->standard_cursor = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR); self->click_cursor = glfwCreateStandardCursor(GLFW_HAND_CURSOR); if (self->standard_cursor == NULL || self->click_cursor == NULL) { Py_CLEAR(self); PyErr_SetString(PyExc_ValueError, "Failed to create standard mouse cursors"); return NULL; } diff --git a/kitty/main.py b/kitty/main.py index 29cf1bfaa..337e84f99 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -23,10 +23,10 @@ from .fast_data_types import ( GLFW_STENCIL_BITS, GLFWWindow, change_wcwidth, check_for_extensions, clear_buffers, glewInit, glfw_init, glfw_init_hint_string, glfw_set_error_callback, glfw_swap_interval, glfw_terminate, - glfw_window_hint, set_options + glfw_window_hint, set_logical_dpi, set_options ) from .layout import all_layouts -from .utils import color_as_int, detach, safe_print +from .utils import color_as_int, detach, get_logical_dpi, safe_print try: from .fast_data_types import GLFW_X11_WM_CLASS_NAME, GLFW_X11_WM_CLASS_CLASS @@ -186,6 +186,7 @@ def run_app(opts, args): else: with open(logo_data_file, 'rb') as f: window.set_icon(f.read(), 256, 256) + set_logical_dpi(*get_logical_dpi()) viewport_size.width, viewport_size.height = window.get_framebuffer_size() w, h = window.get_window_size() viewport_size.x_ratio = viewport_size.width / float(w) diff --git a/kitty/screen.c b/kitty/screen.c index c73f28210..a6ed8982b 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -511,6 +511,20 @@ void screen_reset_mode(Screen *self, unsigned int mode) { // Cursor {{{ +unsigned long +screen_current_char_width(Screen *self) { + unsigned long ans = 1; + if (self->cursor->x < self->columns - 1 && self->cursor->y < self->lines) { + ans = linebuf_char_width_at(self->linebuf, self->cursor->x, self->cursor->y); + } + return ans; +} + +bool +screen_is_cursor_visible(Screen *self) { + return self->modes.mDECTCEM; +} + void screen_backspace(Screen *self) { screen_cursor_back(self, 1, -1); @@ -1443,11 +1457,7 @@ mark_as_dirty(Screen *self) { static PyObject* current_char_width(Screen *self) { #define current_char_width_doc "The width of the character under the cursor" - unsigned long ans = 1; - if (self->cursor->x < self->columns - 1 && self->cursor->y < self->lines) { - ans = linebuf_char_width_at(self->linebuf, self->cursor->x, self->cursor->y); - } - return PyLong_FromUnsignedLong(ans); + return PyLong_FromUnsignedLong(screen_current_char_width(self)); } static PyObject* diff --git a/kitty/screen.h b/kitty/screen.h index d2df3119a..09b3451b2 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -63,6 +63,8 @@ void screen_apply_selection(Screen *self, void *address, size_t size); bool screen_is_selection_dirty(Screen *self); bool screen_invert_colors(Screen *self); void screen_update_cell_data(Screen *self, void *address, size_t sz); +bool screen_is_cursor_visible(Screen *self); +unsigned long screen_current_char_width(Screen *self); #define DECLARE_CH_SCREEN_HANDLER(name) void screen_##name(Screen *screen); DECLARE_CH_SCREEN_HANDLER(bell) DECLARE_CH_SCREEN_HANDLER(backspace) diff --git a/kitty/shaders.c b/kitty/shaders.c index 7385339f4..345ba4d90 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -84,6 +84,11 @@ glew_init(PyObject UNUSED *self) { glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); Py_RETURN_NONE; } + +static void +update_viewport_size_impl(int w, int h) { + glViewport(0, 0, w, h); check_gl(); +} // }}} // Programs {{{ @@ -668,7 +673,7 @@ init_cursor_program() { } static void -draw_cursor(bool semi_transparent, bool is_focused, color_type color, float alpha, float left, float right, float top, float bottom) { +draw_cursor_impl(bool semi_transparent, bool is_focused, color_type color, float alpha, float left, float right, float top, float bottom) { if (semi_transparent) { glEnable(GL_BLEND); check_gl(); } bind_program(CURSOR_PROGRAM); bind_vertex_array(cursor_vertex_array); glUniform4f(cursor_uniform_locations[CURSOR_color], ((color >> 16) & 0xff) / 255.0, ((color >> 8) & 0xff) / 255.0, (color & 0xff) / 255.0, alpha); @@ -852,12 +857,6 @@ PYWRAP1(layout_sprite_map) { Py_RETURN_NONE; } -PYWRAP1(resize_gl_viewport) { - unsigned int w, h; PA("II", &w, &h); - glViewport(0, 0, w, h); - Py_RETURN_NONE; -} - PYWRAP1(clear_buffers) { PyObject *swap_buffers; unsigned int bg; @@ -918,7 +917,6 @@ static PyMethodDef module_methods[] = { MW(create_cell_vao, METH_NOARGS), MW(layout_sprite_map, METH_VARARGS), MW(destroy_sprite_map, METH_NOARGS), - MW(resize_gl_viewport, METH_VARARGS), MW(clear_buffers, METH_VARARGS), {NULL, NULL, 0, NULL} /* Sentinel */ @@ -962,8 +960,10 @@ init_shaders(PyObject *module) { #undef C PyModule_AddObject(module, "GL_VERSION_REQUIRED", Py_BuildValue("II", REQUIRED_VERSION_MAJOR, REQUIRED_VERSION_MINOR)); if (PyModule_AddFunctions(module, module_methods) != 0) return false; + update_viewport_size = &update_viewport_size_impl; draw_borders = &draw_borders_impl; draw_cells = &draw_cells_impl; + draw_cursor = &draw_cursor_impl; return true; } // }}} diff --git a/kitty/state.c b/kitty/state.c index 28830d900..9016e30c0 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -103,9 +103,13 @@ swap_windows(unsigned int tab_id, unsigned int a, unsigned int b) { #define THREE_UINT(name) PYWRAP1(name) { unsigned int a, b, c; PA("III", &a, &b, &c); name(a, b, c); Py_RETURN_NONE; } PYWRAP1(set_options) { -#define S(name, convert) { PyObject *ret = PyObject_GetAttrString(args, #name); if (ret == NULL) return NULL; global_state.opts.name = convert(ret); Py_DECREF(ret); } +#define S(name, convert) { PyObject *ret = PyObject_GetAttrString(args, #name); if (ret == NULL) return NULL; global_state.opts.name = convert(ret); Py_DECREF(ret); if (PyErr_Occurred()) return NULL; } S(visual_bell_duration, PyFloat_AsDouble); S(enable_audio_bell, PyObject_IsTrue); + S(cursor_blink_interval, PyFloat_AsDouble); + S(cursor_stop_blinking_after, PyFloat_AsDouble); + S(cursor_shape, PyLong_AsLong); + S(cursor_opacity, PyFloat_AsDouble); #undef S Py_RETURN_NONE; } @@ -144,6 +148,11 @@ PYWRAP1(update_window_visibility) { Py_RETURN_NONE; } +PYWRAP1(set_logical_dpi) { + PA("dd", &global_state.logical_dpi_x, &global_state.logical_dpi_y); + Py_RETURN_NONE; +} + PYWRAP0(destroy_global_data) { Py_CLEAR(global_state.tab_bar_render_data.screen); Py_RETURN_NONE; @@ -163,6 +172,7 @@ THREE_UINT(swap_windows) static PyMethodDef module_methods[] = { MW(set_options, METH_O), + MW(set_logical_dpi, METH_VARARGS), MW(add_tab, METH_O), MW(add_window, METH_VARARGS), MW(remove_tab, METH_O), @@ -183,6 +193,7 @@ static PyMethodDef module_methods[] = { bool init_state(PyObject *module) { global_state.application_focused = true; + global_state.cursor_blink_zero_time = monotonic(); if (PyModule_AddFunctions(module, module_methods) != 0) return false; return true; } diff --git a/kitty/state.h b/kitty/state.h index fd780b23c..aba866d23 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -7,9 +7,13 @@ #pragma once #include "data-types.h" +#define OPT(name) global_state.opts.name + typedef struct { - double visual_bell_duration; + double visual_bell_duration, cursor_blink_interval, cursor_stop_blinking_after; bool enable_audio_bell; + CursorShape cursor_shape; + double cursor_opacity; } Options; typedef struct { @@ -36,9 +40,14 @@ typedef struct { unsigned int active_tab, num_tabs; ScreenRenderData tab_bar_render_data; bool application_focused; + double cursor_blink_zero_time; + double logical_dpi_x, logical_dpi_y; + int viewport_width, viewport_height; } GlobalState; -typedef void (*draw_borders_func)(); -extern draw_borders_func draw_borders; -typedef void (*draw_cells_func)(ssize_t, float, float, float, float, Screen *); -extern draw_cells_func draw_cells; +#define EXTERNAL_FUNC(name, ret, ...) typedef ret (*name##_func)(__VA_ARGS__); extern name##_func name +#define EXTERNAL_FUNC0(name, ret) typedef ret (*name##_func)(); extern name##_func name +EXTERNAL_FUNC0(draw_borders, void); +EXTERNAL_FUNC(draw_cells, void, ssize_t, float, float, float, float, Screen *); +EXTERNAL_FUNC(draw_cursor, void, bool, bool, color_type, float, float, float, float, float); +EXTERNAL_FUNC(update_viewport_size, void, int, int);