diff --git a/kitty/char_grid.py b/kitty/char_grid.py index 93cf13654..be738aad1 100644 --- a/kitty/char_grid.py +++ b/kitty/char_grid.py @@ -121,7 +121,7 @@ def calculate_gl_geometry(window_geometry): return ScreenGeometry(xstart, ystart, window_geometry.xnum, window_geometry.ynum, dx, dy) -def render_cells(vao_id, sg, cell_program, sprites, invert_colors=False): +def render_cells(vao_id, sg, cell_program, sprites, color_profile, invert_colors=False): ul = cell_program.uniform_location glUniform2ui(ul('dimensions'), sg.xnum, sg.ynum) glUniform2i(ul('color_indices'), 1 if invert_colors else 0, 0 if invert_colors else 1) @@ -145,15 +145,15 @@ class CharGrid: self.render_data = None self.scrolled_by = 0 self.screen = screen - self.screen.color_profile.update_ansi_color_table(build_ansi_color_table(opts)) self.opts = opts - self.default_bg = color_as_int(opts.background) - self.default_fg = color_as_int(opts.foreground) + 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.screen.color_profile.dirty = True self.dpix, self.dpiy = get_logical_dpi() self.opts = opts self.default_cursor = self.current_cursor = Cursor(0, 0, opts.cursor_shape, opts.cursor_blink_interval > 0) self.opts = opts - self.highlight_fg, self.highlight_bg = map(color_as_int, (opts.selection_foreground, opts.selection_background)) self.sprite_map_type = self.main_sprite_map = self.scroll_sprite_map = self.render_buf = None def escape(chars): @@ -198,8 +198,9 @@ class CharGrid: if val is None: continue dirtied = True - setattr(self.screen, which.name, val) + setattr(self.screen.color_profile, which.name, val) if dirtied: + self.screen.color_profile.dirty = True self.screen.mark_as_dirty() def scroll(self, amt, upwards=True): @@ -216,15 +217,12 @@ class CharGrid: sprites = get_boss().sprites is_dirty = self.screen.is_dirty() with sprites.lock: - fg, bg = self.screen.default_fg, self.screen.default_bg - fg = fg >> 8 if fg & 2 else self.default_fg - bg = bg >> 8 if bg & 2 else self.default_bg cursor_changed, history_line_added_count = self.screen.update_cell_data( - sprites.backend, addressof(self.main_sprite_map), fg, bg, force_full_refresh) + sprites.backend, addressof(self.main_sprite_map), force_full_refresh) if self.scrolled_by: self.scrolled_by = min(self.scrolled_by + history_line_added_count, self.screen.historybuf.count) self.screen.set_scroll_cell_data( - sprites.backend, addressof(self.main_sprite_map), fg, bg, + sprites.backend, addressof(self.main_sprite_map), self.scrolled_by, addressof(self.scroll_sprite_map)) data = self.scroll_sprite_map if self.scrolled_by else self.main_sprite_map @@ -367,10 +365,8 @@ class CharGrid: buf = self.selection_buf if self.render_buf_is_dirty or sel != self.last_rendered_selection: memmove(buf, self.render_buf, sizeof(type(buf))) - fg = self.screen.highlight_fg - fg = fg >> 8 if fg & 2 else self.highlight_fg - bg = self.screen.highlight_bg - bg = bg >> 8 if bg & 2 else self.highlight_bg + fg = self.screen.color_profile.highlight_fg + bg = self.screen.color_profile.highlight_bg self.screen.apply_selection(addressof(buf), start[0], start[1], end[0], end[1], fg, bg) if self.render_buf_is_dirty or self.last_rendered_selection != sel: cell_program.send_vertex_data(self.vao_id, buf) @@ -379,7 +375,7 @@ class CharGrid: return sg def render_cells(self, sg, cell_program, sprites, invert_colors=False): - render_cells(self.vao_id, sg, cell_program, sprites, invert_colors=invert_colors) + render_cells(self.vao_id, sg, cell_program, sprites, self.screen.color_profile, invert_colors=invert_colors) def render_cursor(self, sg, cursor_program, is_focused): cursor = self.current_cursor @@ -395,8 +391,7 @@ class CharGrid: ul = cursor_program.uniform_location left = sg.xstart + cursor.x * sg.dx top = sg.ystart - cursor.y * sg.dy - cc = self.screen.cursor_color - col = color_from_int(cc >> 8) if cc & 2 else self.opts.cursor + col = color_from_int(self.screen.color_profile.cursor_color) shape = cursor.shape or self.default_cursor.shape alpha = self.opts.cursor_opacity if alpha < 1.0 and shape == CURSOR_BLOCK: diff --git a/kitty/colors.c b/kitty/colors.c index f38e491b4..686fec14b 100644 --- a/kitty/colors.c +++ b/kitty/colors.c @@ -6,6 +6,7 @@ */ #include "data-types.h" +#include static uint32_t FG_BG_256[256] = { 0x000000, // 0 @@ -59,12 +60,14 @@ new(PyTypeObject *type, PyObject UNUSED *args, PyObject UNUSED *kwds) { if (FG_BG_256[255] == 0) create_256_color_table(); memcpy(self->color_table, FG_BG_256, sizeof(FG_BG_256)); memcpy(self->orig_color_table, FG_BG_256, sizeof(FG_BG_256)); + self->dirty = Py_True; Py_INCREF(Py_True); } return (PyObject*) self; } static void dealloc(ColorProfile* self) { + Py_DECREF(self->dirty); Py_TYPE(self)->tp_free((PyObject*)self); } @@ -88,6 +91,21 @@ update_ansi_color_table(ColorProfile *self, PyObject *val) { Py_RETURN_NONE; } +color_type +colorprofile_to_color(ColorProfile *self, color_type entry, color_type defval) { + color_type t = entry & 0xFF, r; + switch(t) { + case 1: + r = (entry >> 8) & 0xff; + return self->color_table[r]; + case 2: + return entry >> 8; + default: + return defval; + } +} + + static PyObject* as_color(ColorProfile *self, PyObject *val) { #define as_color_doc "Convert the specified terminal color into an (r, g, b) tuple based on the current profile values" @@ -137,16 +155,55 @@ set_color(ColorProfile *self, PyObject *args) { Py_RETURN_NONE; } +static PyObject* +set_configured_colors(ColorProfile *self, PyObject *args) { +#define set_configured_colors_doc "Set the configured colors" + if (!PyArg_ParseTuple(args, "II|III", &(self->configured.default_fg), &(self->configured.default_bg), &(self->configured.cursor_color), &(self->configured.highlight_fg), &(self->configured.highlight_bg))) return NULL; + Py_RETURN_NONE; +} + +static PyObject* +color_table_address(ColorProfile *self) { +#define color_table_address_doc "Pointer address to start of color table" + return PyLong_FromVoidPtr((void*)self->color_table); +} + // Boilerplate {{{ +#define CGETSET(name) \ + static PyObject* name##_get(ColorProfile *self, void UNUSED *closure) { return PyLong_FromUnsignedLong(colorprofile_to_color(self, self->overridden.name, self->configured.name)); } \ + static int name##_set(ColorProfile *self, PyObject *val, void UNUSED *closure) { if (val == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete attribute"); return -1; } self->overridden.name = (color_type) PyLong_AsUnsignedLong(val); return 0; } + +CGETSET(default_fg) +CGETSET(default_bg) +CGETSET(cursor_color) +CGETSET(highlight_fg) +CGETSET(highlight_bg) + +static PyGetSetDef getsetters[] = { + GETSET(default_fg) + GETSET(default_bg) + GETSET(cursor_color) + GETSET(highlight_fg) + GETSET(highlight_bg) + {NULL} /* Sentinel */ +}; + + +static PyMemberDef members[] = { + {"dirty", T_OBJECT_EX, offsetof(ColorProfile, dirty), 0, "dirty"}, + {NULL} +}; static PyMethodDef methods[] = { METHOD(update_ansi_color_table, METH_O) METHOD(reset_color_table, METH_NOARGS) + METHOD(color_table_address, METH_NOARGS) METHOD(as_color, METH_O) METHOD(reset_color, METH_O) METHOD(set_color, METH_VARARGS) + METHOD(set_configured_colors, METH_VARARGS) {NULL} /* Sentinel */ }; @@ -158,7 +215,9 @@ PyTypeObject ColorProfile_Type = { .tp_dealloc = (destructor)dealloc, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "ColorProfile", + .tp_members = members, .tp_methods = methods, + .tp_getset = getsetters, .tp_new = new, }; diff --git a/kitty/data-types.h b/kitty/data-types.h index c75ea0f55..378cd7bf7 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -177,12 +177,17 @@ PyTypeObject Cursor_Type; PyTypeObject Face_Type; PyTypeObject Window_Type; +typedef struct { + color_type default_fg, default_bg, cursor_color, highlight_fg, highlight_bg; +} DynamicColor; + typedef struct { PyObject_HEAD + PyObject *dirty; uint32_t color_table[256]; uint32_t orig_color_table[256]; - + DynamicColor configured, overridden; } ColorProfile; PyTypeObject ColorProfile_Type; @@ -271,7 +276,6 @@ typedef struct { unsigned int parser_state, parser_text_start, parser_buf_pos; bool parser_has_pending_text; uint8_t read_buf[READ_BUF_SZ]; - uint32_t default_fg, default_bg, highlight_fg, highlight_bg, cursor_color; } Screen; PyTypeObject Screen_Type; @@ -347,7 +351,7 @@ void cursor_reset(Cursor*); Cursor* cursor_copy(Cursor*); void cursor_copy_to(Cursor *src, Cursor *dest); void cursor_reset_display_attrs(Cursor*); -bool update_cell_range_data(ScreenModes *modes, SpriteMap *, Line *, unsigned int, unsigned int, ColorProfile *, const uint32_t, const uint32_t, unsigned int *); +bool update_cell_range_data(ScreenModes *modes, SpriteMap *, Line *, unsigned int, unsigned int, ColorProfile *, unsigned int *); PyObject* line_text_at(char_type, combining_type); void line_clear_text(Line *self, unsigned int at, unsigned int num, int ch); @@ -378,6 +382,8 @@ double timers_timeout(Timers*); void timers_call(Timers*); 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); + unsigned int safe_wcwidth(uint32_t ch); void change_wcwidth(bool use9); void screen_align(Screen*); diff --git a/kitty/screen.c b/kitty/screen.c index 8fa88d5f2..61f401513 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -45,9 +45,6 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { self->columns = columns; self->lines = lines; self->modes = empty_modes; self->margin_top = 0; self->margin_bottom = self->lines - 1; - self->default_fg = 0; self->default_bg = 0; - self->highlight_fg = 0; self->highlight_bg = 0; - self->cursor_color = 0; RESET_CHARSETS; self->callbacks = callbacks; Py_INCREF(callbacks); self->cursor = alloc_cursor(); @@ -73,9 +70,9 @@ screen_reset(Screen *self) { if (self->linebuf == self->alt_linebuf) screen_toggle_screen_buffer(self); linebuf_clear(self->linebuf, BLANK_CHAR); self->modes = empty_modes; - self->default_fg = 0; self->default_bg = 0; - self->highlight_fg = 0; self->highlight_bg = 0; - self->cursor_color = 0; +#define RC(name) self->color_profile->overridden.name = 0 + RC(default_fg); RC(default_bg); RC(cursor_color); RC(highlight_fg); RC(highlight_bg); +#undef RC RESET_CHARSETS; self->margin_top = 0; self->margin_bottom = self->lines - 1; screen_normal_keypad_mode(self); @@ -1173,14 +1170,13 @@ screen_update_cell_data(Screen *self, PyObject *args) { SpriteMap *spm; PyObject *dp; unsigned int *data; - unsigned long default_bg, default_fg; int force_screen_refresh; - if (!PyArg_ParseTuple(args, "O!O!kkp", &SpriteMap_Type, &spm, &PyLong_Type, &dp, &default_fg, &default_bg, &force_screen_refresh)) return NULL; + if (!PyArg_ParseTuple(args, "O!O!p", &SpriteMap_Type, &spm, &PyLong_Type, &dp, &force_screen_refresh)) return NULL; data = PyLong_AsVoidPtr(dp); PyObject *cursor_changed = self->change_tracker->cursor_changed ? Py_True : Py_False; unsigned int history_line_added_count = self->change_tracker->history_line_added_count; - if (!tracker_update_cell_data(&(self->modes), self->change_tracker, self->linebuf, spm, self->color_profile, data, default_fg, default_bg, (bool)force_screen_refresh)) return NULL; + if (!tracker_update_cell_data(&(self->modes), self->change_tracker, self->linebuf, spm, self->color_profile, data, (bool)force_screen_refresh)) return NULL; return Py_BuildValue("OI", cursor_changed, history_line_added_count); } @@ -1189,8 +1185,7 @@ set_scroll_cell_data(Screen *self, PyObject *args) { SpriteMap *spm; PyObject *dp, *sp; unsigned int *data, *src, scrolled_by; - unsigned long default_bg, default_fg; - if (!PyArg_ParseTuple(args, "O!O!kkIO", &SpriteMap_Type, &spm, &PyLong_Type, &sp, &default_fg, &default_bg, &scrolled_by, &dp)) return NULL; + if (!PyArg_ParseTuple(args, "O!O!IO", &SpriteMap_Type, &spm, &PyLong_Type, &sp, &scrolled_by, &dp)) return NULL; data = PyLong_AsVoidPtr(dp); src = PyLong_AsVoidPtr(sp); @@ -1199,7 +1194,7 @@ set_scroll_cell_data(Screen *self, PyObject *args) { for (index_type y = 0; y < MIN(self->lines, scrolled_by); y++) { historybuf_init_line(self->historybuf, scrolled_by - 1 - y, self->historybuf->line); self->historybuf->line->ynum = y; - if (!update_cell_range_data(&(self->modes), spm, self->historybuf->line, 0, self->columns - 1, self->color_profile, default_bg, default_fg, data)) return NULL; + if (!update_cell_range_data(&(self->modes), spm, self->historybuf->line, 0, self->columns - 1, self->color_profile, data)) return NULL; } if (scrolled_by < self->lines) { // Less than a full screen has been scrolled, copy some lines from the screen buffer to the scroll buffer @@ -1352,11 +1347,6 @@ static PyMemberDef members[] = { {"columns", T_UINT, offsetof(Screen, columns), READONLY, "columns"}, {"margin_top", T_UINT, offsetof(Screen, margin_top), READONLY, "margin_top"}, {"margin_bottom", T_UINT, offsetof(Screen, margin_bottom), READONLY, "margin_bottom"}, - {"default_fg", T_COL, offsetof(Screen, default_fg), 0, "default_fg"}, - {"default_bg", T_COL, offsetof(Screen, default_bg), 0, "default_bg"}, - {"highlight_fg", T_COL, offsetof(Screen, highlight_fg), 0, "highlight_fg"}, - {"highlight_bg", T_COL, offsetof(Screen, highlight_bg), 0, "highlight_bg"}, - {"cursor_color", T_COL, offsetof(Screen, cursor_color), 0, "cursor_color"}, {NULL} }; diff --git a/kitty/sprites.c b/kitty/sprites.c index fb6b8d982..119740a79 100644 --- a/kitty/sprites.c +++ b/kitty/sprites.c @@ -138,27 +138,15 @@ position_for(SpriteMap *self, PyObject *args) { return Py_BuildValue("III", pos->x, pos->y, pos->z); } -static inline uint32_t -to_color(ColorProfile *self, uint32_t entry, uint32_t defval) { - unsigned int t = entry & 0xFF, r; - switch(t) { - case 1: - r = (entry >> 8) & 0xff; - return self->color_table[r]; - case 2: - return entry >> 8; - default: - return defval; - } -} - bool -update_cell_range_data(ScreenModes *modes, SpriteMap *self, Line *line, unsigned int xstart, unsigned int xmax, ColorProfile *color_profile, const uint32_t default_bg, const uint32_t default_fg, unsigned int *data) { +update_cell_range_data(ScreenModes *modes, SpriteMap *self, Line *line, unsigned int xstart, unsigned int xmax, ColorProfile *color_profile, unsigned int *data) { SpritePosition *sp; char_type previous_ch=0, ch; uint8_t previous_width = 0; int err = 0; const bool screen_reversed = modes->mDECSCNM; + color_type fg = colorprofile_to_color(color_profile, color_profile->overridden.default_fg, color_profile->configured.default_fg); + color_type bg = colorprofile_to_color(color_profile, color_profile->overridden.default_bg, color_profile->configured.default_bg); size_t base = line->ynum * line->xnum * DATA_CELL_SIZE; for (size_t i = xstart, offset = base + xstart * DATA_CELL_SIZE; i <= xmax; i++, offset += DATA_CELL_SIZE) { @@ -173,9 +161,9 @@ update_cell_range_data(ScreenModes *modes, SpriteMap *self, Line *line, unsigned data[offset] = sp->x; data[offset+1] = sp->y; data[offset+2] = sp->z; - data[offset+(reverse ? 4 : 3)] = to_color(color_profile, line->cells[i].fg & COL_MASK, default_fg); - data[offset+(reverse ? 3 : 4)] = to_color(color_profile, line->cells[i].bg & COL_MASK, default_bg); - unsigned int decoration_fg = to_color(color_profile, line->cells[i].decoration_fg & COL_MASK, data[offset+3]); + data[offset+(reverse ? 4 : 3)] = colorprofile_to_color(color_profile, line->cells[i].fg & COL_MASK, fg); + data[offset+(reverse ? 3 : 4)] = colorprofile_to_color(color_profile, line->cells[i].bg & COL_MASK, bg); + unsigned int decoration_fg = colorprofile_to_color(color_profile, line->cells[i].decoration_fg & COL_MASK, data[offset+3]); data[offset+5] = (decoration_fg & COL_MASK) | (decoration << 24) | (strikethrough << 26); previous_ch = ch; previous_width = (attrs) & WIDTH_MASK; } diff --git a/kitty/tabs.py b/kitty/tabs.py index c6dd415af..005e79582 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -239,6 +239,8 @@ class TabManager: s = Screen(None, 1, ncells) s.reset_mode(DECAWM) s.color_profile.update_ansi_color_table(self.color_table) + s.color_profile.set_configured_colors(self.default_fg, self.default_bg) + s.color_profile.dirty = True self.sprite_map_type = (GLuint * (s.lines * s.columns * DATA_CELL_SIZE)) with self.tabbar_lock: self.sprite_map = self.sprite_map_type() @@ -339,7 +341,7 @@ class TabManager: break s.erase_in_line(0, False) # Ensure no long titles bleed after the last tab s.update_cell_data( - sprites.backend, addressof(self.sprite_map), self.default_fg, self.default_bg, True) + sprites.backend, addressof(self.sprite_map), True) sprites.render_dirty_cells() if self.vao_id is None: self.vao_id = cell_program.create_sprite_map() @@ -364,4 +366,4 @@ class TabManager: with self.tabbar_lock: if self.tabbar_dirty: self.update_tab_bar_data(sprites, cell_program) - render_cells(self.vao_id, self.screen_geometry, cell_program, sprites) + render_cells(self.vao_id, self.screen_geometry, cell_program, sprites, self.tab_bar_screen.color_profile) diff --git a/kitty/tracker.c b/kitty/tracker.c index 516cb1b9a..e68af980e 100644 --- a/kitty/tracker.c +++ b/kitty/tracker.c @@ -97,15 +97,13 @@ update_cell_range(ChangeTracker *self, PyObject *args) { Py_RETURN_NONE; } -bool tracker_update_cell_data(ScreenModes *modes, ChangeTracker *self, LineBuf *lb, SpriteMap *spm, ColorProfile *color_profile, unsigned int *data, unsigned long default_fg, unsigned long default_bg, bool force_screen_refresh) { +bool tracker_update_cell_data(ScreenModes *modes, ChangeTracker *self, LineBuf *lb, SpriteMap *spm, ColorProfile *color_profile, unsigned int *data, bool force_screen_refresh) { unsigned int y; Py_ssize_t start; - default_fg &= COL_MASK; - default_bg &= COL_MASK; #define UPDATE_RANGE(xstart, xmax) \ linebuf_init_line(lb, y); \ - if (!update_cell_range_data(modes, spm, lb->line, (xstart), (xmax), color_profile, default_bg, default_fg, data)) return false; + if (!update_cell_range_data(modes, spm, lb->line, (xstart), (xmax), color_profile, data)) return false; if (self->screen_changed || force_screen_refresh) { for (y = 0; y < self->ynum; y++) { diff --git a/kitty/tracker.h b/kitty/tracker.h index 7557e815b..f17a0b78f 100644 --- a/kitty/tracker.h +++ b/kitty/tracker.h @@ -52,4 +52,4 @@ static inline void tracker_reset(ChangeTracker *self) { PyObject* tracker_consolidate_changes(ChangeTracker *self); bool tracker_resize(ChangeTracker *self, unsigned int ynum, unsigned int xnum); -bool tracker_update_cell_data(ScreenModes*, ChangeTracker *, LineBuf *, SpriteMap *, ColorProfile *, unsigned int *, unsigned long, unsigned long, bool); +bool tracker_update_cell_data(ScreenModes*, ChangeTracker *, LineBuf *, SpriteMap *, ColorProfile *, unsigned int *, bool); diff --git a/kitty/window.py b/kitty/window.py index ab2f5e00d..a0aee3624 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -174,6 +174,7 @@ class Window: if code == 4: for c, val in parse_color_set(value): cp.set_color(c, val) + cp.dirty = True self.refresh() elif code == 104: if not value.strip(): @@ -186,6 +187,7 @@ class Window: continue if 0 <= c <= 255: cp.reset_color(c) + cp.dirty = True self.refresh() def request_capabilities(self, q):