From 0fdafd83986d46ea5eecdd6174617bd71eafe728 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 28 Oct 2021 00:19:36 +0530 Subject: [PATCH] Allow rendering the cursor with reverse video Also clean up handling of dynamic global colors. TODO: Implement none for selection_fg TODO: Add some tests TODO: Check that changing colors via remote control works Fixes #126 --- docs/changelog.rst | 3 ++ kitty/cell_vertex.glsl | 10 ++-- kitty/child-monitor.c | 4 +- kitty/colors.c | 105 +++++++++++++++++++----------------- kitty/data-types.h | 26 ++++++--- kitty/fast_data_types.pyi | 2 +- kitty/glfw.c | 2 +- kitty/options/definition.py | 12 +++-- kitty/options/parse.py | 2 +- kitty/options/to-c.h | 1 - kitty/options/types.py | 2 +- kitty/rc/set_colors.py | 2 +- kitty/screen.c | 24 ++++++++- kitty/screen.h | 1 + kitty/shaders.c | 40 +++++++++----- kitty/window.py | 32 +++++------ 16 files changed, 167 insertions(+), 101 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 4c4ec5394..a3c571aa4 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -16,6 +16,9 @@ To update |kitty|, :doc:`follow the instructions `. the keyboard. Pressing it causes numbers to appear over each visible window and you can press the number to focus the corresponding window (:iss:`4110`) +- Allow rendering the cursor with a *reverse video* effect. See :opt:`cursor` + for details (:iss:`126`) + - A new option :opt:`tab_bar_align` to draw the tab bar centered or right aligned (:iss:`3946`) diff --git a/kitty/cell_vertex.glsl b/kitty/cell_vertex.glsl index d57bb1f49..bf90bff96 100644 --- a/kitty/cell_vertex.glsl +++ b/kitty/cell_vertex.glsl @@ -12,9 +12,9 @@ // Inputs {{{ layout(std140) uniform CellRenderData { - float xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity, cursor_text_uses_bg; + float xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity; - uint default_fg, default_bg, highlight_fg, highlight_bg, cursor_color, cursor_text_color, url_color, url_style, inverted; + uint default_fg, default_bg, highlight_fg, highlight_bg, cursor_fg, cursor_bg, url_color, url_style, inverted; uint xnum, ynum, cursor_fg_sprite_idx; float cursor_x, cursor_y, cursor_w; @@ -198,8 +198,8 @@ void main() { strike_pos = to_sprite_pos(pos, ((text_attrs >> STRIKE_SHIFT) & ONE) * FOUR, ZERO, ZERO); // Cursor - cursor_color_vec = vec4(color_to_vec(cursor_color), 1.0); - vec3 final_cursor_text_color = mix(color_to_vec(cursor_text_color), bg, cursor_text_uses_bg); + cursor_color_vec = vec4(color_to_vec(cursor_bg), 1.0); + vec3 final_cursor_text_color = color_to_vec(cursor_fg); foreground = choose_color(cell_has_block_cursor, final_cursor_text_color, foreground); decoration_fg = choose_color(cell_has_block_cursor, final_cursor_text_color, decoration_fg); cursor_pos = to_sprite_pos(pos, cursor_fg_sprite_idx * uint(cell_has_cursor), ZERO, ZERO); @@ -237,7 +237,7 @@ void main() { #if defined(SPECIAL) || defined(SIMPLE) // Selection and cursor bg = choose_color(float(is_selected & ONE), color_to_vec(highlight_bg), bg); - background = choose_color(cell_has_block_cursor, color_to_vec(cursor_color), bg); + background = choose_color(cell_has_block_cursor, color_to_vec(cursor_bg), bg); #if !defined(TRANSPARENT) && defined(SPECIAL) float is_special_cell = cell_has_block_cursor + float(is_selected & ONE); bg_alpha = step(0.5, is_special_cell); diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index 39346ff9b..857e446d0 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -574,9 +574,7 @@ collect_cursor_info(CursorRenderInfo *ans, Window *w, monotonic_t now, OSWindow } if (!do_draw_cursor) { ans->is_visible = false; return; } ans->is_visible = true; - ColorProfile *cp = rd->screen->color_profile; ans->shape = cursor->shape ? cursor->shape : OPT(cursor_shape); - ans->color = colorprofile_to_color(cp, cp->overridden.cursor_color, cp->configured.cursor_color); ans->is_focused = os_window->is_focused; } @@ -617,7 +615,7 @@ prepare_to_render_os_window(OSWindow *os_window, monotonic_t now, unsigned int * #define WD w->render_data if (w->visible && WD.screen) { *num_visible_windows += 1; - color_type window_bg = colorprofile_to_color(WD.screen->color_profile, WD.screen->color_profile->overridden.default_bg, WD.screen->color_profile->configured.default_bg); + color_type window_bg = colorprofile_to_color(WD.screen->color_profile, WD.screen->color_profile->overridden.default_bg, WD.screen->color_profile->configured.default_bg).rgb; if (*num_visible_windows == 1) first_window_bg = window_bg; if (first_window_bg != window_bg) *all_windows_have_same_bg = false; if (w->last_drag_scroll_at > 0) { diff --git a/kitty/colors.c b/kitty/colors.c index 2d2f4b14b..edf498f76 100644 --- a/kitty/colors.c +++ b/kitty/colors.c @@ -151,65 +151,65 @@ patch_color_profiles(PyObject *module UNUSED, PyObject *args) { S(background, i); S(foreground, i); #undef S } +#define SI(profile_name) \ + DynamicColor color; \ + if (PyLong_Check(v)) { \ + color.rgb = PyLong_AsUnsignedLong(v); color.type = COLOR_IS_RGB; \ + } else { color.rgb = 0; color.type = COLOR_IS_SPECIAL; }\ + self->overridden.profile_name = color; \ + if (change_configured) self->configured.profile_name = color; \ + self->dirty = true; + #define S(config_name, profile_name) { \ v = PyDict_GetItemString(spec, #config_name); \ - if (v && PyLong_Check(v)) { \ - color_type color = PyLong_AsUnsignedLong(v); \ + if (v) { \ for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(profiles); i++) { \ self = (ColorProfile*)PyTuple_GET_ITEM(profiles, i); \ - self->overridden.profile_name = (color << 8) | 2; \ - if (change_configured) self->configured.profile_name = color; \ - self->dirty = true; \ + SI(profile_name); \ } \ } \ } S(foreground, default_fg); S(background, default_bg); S(cursor, cursor_color); S(selection_foreground, highlight_fg); S(selection_background, highlight_bg); + S(cursor_text_color, cursor_text_color); +#undef SI #undef S - PyObject *cursor_text_color = PyDict_GetItemString(spec, "cursor_text_color"); - if (cursor_text_color) { - for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(profiles); i++) { - self = (ColorProfile*)PyTuple_GET_ITEM(profiles, i); - self->overridden.cursor_text_color = 0x111111; - self->overridden.cursor_text_uses_bg = 3; - if (PyLong_Check(cursor_text_color)) { - self->overridden.cursor_text_color = (PyLong_AsUnsignedLong(cursor_text_color) << 8) | 2; - self->overridden.cursor_text_uses_bg = 1; - } - if (change_configured) { - self->configured.cursor_text_color = self->overridden.cursor_text_color; - self->configured.cursor_text_uses_bg = self->overridden.cursor_text_uses_bg; - } - self->dirty = true; - } - } - 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: +DynamicColor +colorprofile_to_color(ColorProfile *self, DynamicColor entry, DynamicColor defval) { + switch(entry.type) { + case COLOR_NOT_SET: return defval; + case COLOR_IS_INDEX: { + DynamicColor ans; + ans.rgb = self->color_table[entry.rgb & 0xff] & 0xffffff; + ans.type = COLOR_IS_RGB; + return ans; + } + case COLOR_IS_RGB: + case COLOR_IS_SPECIAL: + return entry; } + return entry; } -float -cursor_text_as_bg(ColorProfile *self) { - if (self->overridden.cursor_text_uses_bg & 1) { - return self->overridden.cursor_text_uses_bg & 2 ? 1.f : 0.f; +color_type +colorprofile_to_color_with_fallback(ColorProfile *self, DynamicColor entry, DynamicColor defval, DynamicColor fallback, DynamicColor falback_defval) { + switch(entry.type) { + case COLOR_NOT_SET: + case COLOR_IS_SPECIAL: + if (defval.type == COLOR_IS_SPECIAL) return colorprofile_to_color(self, fallback, falback_defval).rgb; + return defval.rgb; + case COLOR_IS_RGB: + return entry.rgb; + case COLOR_IS_INDEX: + return self->color_table[entry.rgb & 0xff] & 0xffffff; } - return self->configured.cursor_text_uses_bg & 2 ? 1.f : 0.f; + return entry.rgb; } - static PyObject* as_dict(ColorProfile *self, PyObject *args UNUSED) { #define as_dict_doc "Return all colors as a dictionary of color_name to integer (names are the same as used in kitty.conf)" @@ -225,8 +225,8 @@ as_dict(ColorProfile *self, PyObject *args UNUSED) { if (ret != 0) { Py_CLEAR(ans); return NULL; } } #define D(attr, name) { \ - color_type c = colorprofile_to_color(self, self->overridden.attr, 0xffffffff); \ - if (c != 0xffffffff) { \ + if (self->overridden.attr.type != COLOR_NOT_SET) { \ + color_type c = colorprofile_to_color(self, self->overridden.attr, self->configured.attr).rgb; \ PyObject *val = PyLong_FromUnsignedLong(c); \ if (!val) { Py_CLEAR(ans); return PyErr_NoMemory(); } \ int ret = PyDict_SetItemString(ans, #name, val); \ @@ -296,11 +296,14 @@ set_color(ColorProfile *self, PyObject *args) { static PyObject* set_configured_colors(ColorProfile *self, PyObject *args) { #define set_configured_colors_doc "Set the configured colors" - if (!PyArg_ParseTuple( - args, "II|IIIII", - &(self->configured.default_fg), &(self->configured.default_bg), - &(self->configured.cursor_color), &(self->configured.cursor_text_color), &(self->configured.cursor_text_uses_bg), - &(self->configured.highlight_fg), &(self->configured.highlight_bg))) return NULL; + unsigned int default_fg, default_bg, cursor_color, cursor_text_color, highlight_fg, highlight_bg; + if (!PyArg_ParseTuple(args, "II|IIII", &default_fg, &default_bg, + &cursor_color, &cursor_text_color, &highlight_fg, &highlight_bg)) return NULL; +#define S(which) \ + self->configured.which.rgb = which & 0xffffff; \ + self->configured.which.type = (which & 0xff000000) ? COLOR_IS_RGB : COLOR_IS_SPECIAL; + S(default_fg); S(default_bg); S(cursor_color); S(cursor_text_color); S(highlight_fg); S(highlight_bg); +#undef S self->dirty = true; Py_RETURN_NONE; } @@ -395,8 +398,14 @@ default_color_table(PyObject *self UNUSED, PyObject *args UNUSED) { // 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); self->dirty = true; return 0; } + static PyObject* name##_get(ColorProfile *self, void UNUSED *closure) { return PyLong_FromUnsignedLong(colorprofile_to_color(self, self->overridden.name, self->configured.name).rgb); } \ + static int name##_set(ColorProfile *self, PyObject *v, void UNUSED *closure) { \ + if (v == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete attribute: " #name); return -1; } \ + unsigned long val = PyLong_AsUnsignedLong(v); \ + self->overridden.name.rgb = val & 0xffffff; \ + self->overridden.name.type = (val & 0xff000000) ? COLOR_IS_RGB : COLOR_NOT_SET; \ + self->dirty = true; return 0; \ + } CGETSET(default_fg) CGETSET(default_bg) diff --git a/kitty/data-types.h b/kitty/data-types.h index dd2f45a22..d361835a6 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -239,16 +239,27 @@ typedef struct { bool is_visible, is_focused; CursorShape shape; unsigned int x, y; - color_type color; } CursorRenderInfo; -typedef struct { - color_type default_fg, default_bg, cursor_color, cursor_text_color, cursor_text_uses_bg, highlight_fg, highlight_bg; +typedef enum DynamicColorType { + COLOR_NOT_SET, COLOR_IS_SPECIAL, COLOR_IS_INDEX, COLOR_IS_RGB +} DynamicColorType; + +typedef union DynamicColor { + struct { + color_type rgb: 24; + DynamicColorType type: 8; + }; + color_type val; } DynamicColor; +typedef struct { + DynamicColor default_fg, default_bg, cursor_color, cursor_text_color, highlight_fg, highlight_bg; +} DynamicColors; + typedef struct { - DynamicColor dynamic_colors; + DynamicColors dynamic_colors; uint32_t color_table[256]; } ColorStackEntry; @@ -260,7 +271,7 @@ typedef struct { uint32_t orig_color_table[256]; ColorStackEntry *color_stack; unsigned int color_stack_idx, color_stack_sz; - DynamicColor configured, overridden; + DynamicColors configured, overridden; color_type mark_foregrounds[MARK_MASK+1], mark_backgrounds[MARK_MASK+1]; } ColorProfile; @@ -332,8 +343,9 @@ bool schedule_write_to_child(unsigned long id, unsigned int num, ...); bool schedule_write_to_child_python(unsigned long id, const char *prefix, PyObject* tuple_of_str_or_bytes, const char *suffix); bool set_iutf8(int, bool); -color_type colorprofile_to_color(ColorProfile *self, color_type entry, color_type defval); -float cursor_text_as_bg(ColorProfile *self); +DynamicColor colorprofile_to_color(ColorProfile *self, DynamicColor entry, DynamicColor defval); +color_type +colorprofile_to_color_with_fallback(ColorProfile *self, DynamicColor entry, DynamicColor defval, DynamicColor fallback, DynamicColor falback_defval); void copy_color_table_to_buffer(ColorProfile *self, color_type *address, int offset, size_t stride); bool colorprofile_push_colors(ColorProfile*, unsigned int); bool colorprofile_pop_colors(ColorProfile*, unsigned int); diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index 2800a8e21..adb5bc8a9 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -642,7 +642,7 @@ class ColorProfile: pass def set_configured_colors( - self, fg: int, bg: int, cursor: int = 0, cursor_text: int = 0, cursor_text_uses_bg: int = 0, highlight_fg: int = 0, highlight_bg: int = 0 + self, fg: int, bg: int, cursor: int = 0, cursor_text: int = 0, highlight_fg: int = 0, highlight_bg: int = 0 ) -> None: pass diff --git a/kitty/glfw.c b/kitty/glfw.c index 181b0a278..9ee30e6f0 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -162,7 +162,7 @@ blank_os_window(OSWindow *w) { Window *w = t->windows + t->active_window; Screen *s = w->render_data.screen; if (s) { - color = colorprofile_to_color(s->color_profile, s->color_profile->overridden.default_bg, s->color_profile->configured.default_bg); + color = colorprofile_to_color(s->color_profile, s->color_profile->overridden.default_bg, s->color_profile->configured.default_bg).rgb; } } } diff --git a/kitty/options/definition.py b/kitty/options/definition.py index d137f274e..c9497ab65 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -213,8 +213,14 @@ egr() # }}} agr('cursor', 'Cursor customization') opt('cursor', '#cccccc', - option_type='to_color', - long_text='Default cursor color' + option_type='to_color_or_none', long_text=''' +Default cursor color. If set to the special value :code:`none` the cursor will be +rendered with a "reverse video" effect. It's color will be the color of the text in +the cell it is over and the text will be rendered with the background color of the cell. +Note that if the program running in the terminal sets a cursor color, this takes precedence. +Also, the reverse video effect is not applied if the cell's foreground and background colors +are the same. +''' ) opt('cursor_text_color', '#111111', @@ -222,7 +228,7 @@ opt('cursor_text_color', '#111111', long_text=''' Choose the color of text under the cursor. If you want it rendered with the background color of the cell underneath instead, use the special keyword: -background +background. ''' ) diff --git a/kitty/options/parse.py b/kitty/options/parse.py index 8f8b135e1..06b1a74a5 100644 --- a/kitty/options/parse.py +++ b/kitty/options/parse.py @@ -888,7 +888,7 @@ class Parser: ans['copy_on_select'] = copy_on_select(val) def cursor(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: - ans['cursor'] = to_color(val) + ans['cursor'] = to_color_or_none(val) def cursor_beam_thickness(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: ans['cursor_beam_thickness'] = positive_float(val) diff --git a/kitty/options/to-c.h b/kitty/options/to-c.h index 8c98eb064..562878d9b 100644 --- a/kitty/options/to-c.h +++ b/kitty/options/to-c.h @@ -27,7 +27,6 @@ color_or_none_as_int(PyObject *color) { return color_as_int(color); } - static inline color_type active_border_color(PyObject *color) { if (color == Py_None) return 0x00ff00; diff --git a/kitty/options/types.py b/kitty/options/types.py index 9bb0917c2..511f2bccc 100644 --- a/kitty/options/types.py +++ b/kitty/options/types.py @@ -472,7 +472,7 @@ class Options: command_on_bell: typing.List[str] = ['none'] confirm_os_window_close: int = 0 copy_on_select: str = '' - cursor: Color = Color(red=204, green=204, blue=204) + cursor: typing.Optional[kitty.rgb.Color] = Color(red=204, green=204, blue=204) cursor_beam_thickness: float = 1.5 cursor_blink_interval: float = -1.0 cursor_shape: int = 1 diff --git a/kitty/rc/set_colors.py b/kitty/rc/set_colors.py index dff7fbb64..917365f53 100644 --- a/kitty/rc/set_colors.py +++ b/kitty/rc/set_colors.py @@ -18,7 +18,7 @@ if TYPE_CHECKING: from kitty.cli_stub import SetColorsRCOptions as CLIOptions -nullable_colors = ('cursor_text_color', 'tab_bar_background', 'tab_bar_margin_color') +nullable_colors = ('cursor', 'cursor_text_color', 'tab_bar_background', 'tab_bar_margin_color', 'selection_foreground') def parse_colors(args: Iterable[str]) -> Dict[str, Optional[int]]: diff --git a/kitty/screen.c b/kitty/screen.c index 27afbc914..8a8d61bb3 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -168,7 +168,7 @@ screen_reset(Screen *self) { self->modes = empty_modes; self->saved_modes = empty_modes; self->active_hyperlink_id = 0; -#define R(name) self->color_profile->overridden.name = 0 +#define R(name) self->color_profile->overridden.name.val = 0 R(default_fg); R(default_bg); R(cursor_color); R(highlight_fg); R(highlight_bg); #undef R RESET_CHARSETS; @@ -1509,6 +1509,28 @@ screen_fake_move_cursor_to_position(Screen *self, index_type x, index_type y) { return count > 0; } +static color_type +resolve_color(ColorProfile *cp, color_type val, color_type defval) { + switch(val & 0xff) { + case 1: + return cp->color_table[(val >> 8) & 0xff]; + case 2: + return val >> 8; + default: + return defval; + } +} + +bool +resolve_cell_colors(Screen *self, index_type x, index_type y, color_type *fg, color_type *bg, color_type default_fg, color_type default_bg) { + if (x < self->columns && y < self->lines) { + linebuf_init_line(self->linebuf, y); + GPUCell *g = self->linebuf->line->gpu_cells + x; + *fg = resolve_color(self->color_profile, g->fg, default_fg); + *bg = resolve_color(self->color_profile, g->bg, default_bg); + return true; + } else { *fg = 0; *bg = 0; return false; } +} // }}} // Editing {{{ diff --git a/kitty/screen.h b/kitty/screen.h index 50c518e7c..86352f636 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -249,6 +249,7 @@ void screen_report_key_encoding_flags(Screen *self); bool screen_detect_url(Screen *screen, unsigned int x, unsigned int y); int screen_cursor_at_a_shell_prompt(const Screen *); bool screen_fake_move_cursor_to_position(Screen *, index_type x, index_type y); +bool resolve_cell_colors(Screen *self, index_type x, index_type y, color_type *fg, color_type *bg, color_type default_fg, color_type default_bg); #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 c05d2c896..110bfc2d4 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -260,9 +260,9 @@ send_graphics_data_to_gpu(size_t image_count, ssize_t gvao_idx, const ImageRende static void cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, CursorRenderInfo *cursor, bool inverted, OSWindow *os_window) { struct CellRenderData { - GLfloat xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity, cursor_text_uses_bg; + GLfloat xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity; - GLuint default_fg, default_bg, highlight_fg, highlight_bg, cursor_color, cursor_text_color, url_color, url_style, inverted; + GLuint default_fg, default_bg, highlight_fg, highlight_bg, cursor_fg, cursor_bg, url_color, url_style, inverted; GLuint xnum, ynum, cursor_fg_sprite_idx; GLfloat cursor_x, cursor_y, cursor_w; @@ -274,6 +274,9 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, G if (UNLIKELY(screen->color_profile->dirty || screen->reload_all_gpu_data)) { copy_color_table_to_buffer(screen->color_profile, (GLuint*)rd, cell_program_layouts[CELL_PROGRAM].color_table.offset / sizeof(GLuint), cell_program_layouts[CELL_PROGRAM].color_table.stride / sizeof(GLuint)); } +#define COLOR(name) colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.name, screen->color_profile->configured.name).rgb +#define IS_SPECIAL_COLOR(name) (screen->color_profile->overridden.name.type == COLOR_IS_SPECIAL || (screen->color_profile->overridden.name.type == COLOR_NOT_SET && screen->color_profile->configured.name.type == COLOR_IS_SPECIAL)) + rd->default_fg = COLOR(default_fg); rd->default_bg = COLOR(default_bg); rd->highlight_fg = COLOR(highlight_fg); rd->highlight_bg = COLOR(highlight_bg); // Cursor position enum { BLOCK_IDX = 0, BEAM_IDX = 6, UNDERLINE_IDX = 7, UNFOCUSED_IDX = 8 }; if (cursor->is_visible) { @@ -288,6 +291,21 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, G rd->cursor_fg_sprite_idx = UNDERLINE_IDX; break; } } else rd->cursor_fg_sprite_idx = UNFOCUSED_IDX; + color_type cell_fg, cell_bg; + resolve_cell_colors(screen, screen->cursor->x, screen->cursor->y, &cell_fg, &cell_bg, rd->default_fg, rd->default_bg); + if (screen->color_profile->overridden.cursor_color.type == COLOR_IS_INDEX || screen->color_profile->overridden.cursor_color.type == COLOR_IS_RGB) { + // since the program is controlling the cursor color we hope it has chosen one + // that has good contrast with the text color of the cell + rd->cursor_fg = cell_fg; rd->cursor_bg = COLOR(cursor_color); + } else if (IS_SPECIAL_COLOR(cursor_color)) { + if (cell_bg == cell_fg) { + rd->cursor_fg = rd->default_bg; rd->cursor_bg = rd->default_fg; + } else { rd->cursor_fg = cell_bg; rd->cursor_bg = cell_fg; } + } else { + rd->cursor_bg = COLOR(cursor_color); + if (IS_SPECIAL_COLOR(cursor_text_color)) rd->cursor_fg = cell_bg; + else rd->cursor_fg = COLOR(cursor_text_color); + } } else rd->cursor_x = screen->columns, rd->cursor_y = screen->lines; rd->cursor_w = rd->cursor_x; if ( @@ -304,12 +322,9 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, G rd->inverted = inverted ? 1 : 0; rd->background_opacity = os_window->is_semi_transparent ? os_window->background_opacity : 1.0f; -#define COLOR(name) colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.name, screen->color_profile->configured.name) - rd->default_fg = COLOR(default_fg); rd->default_bg = COLOR(default_bg); rd->highlight_fg = COLOR(highlight_fg); rd->highlight_bg = COLOR(highlight_bg); - rd->cursor_text_color = COLOR(cursor_text_color); #undef COLOR - rd->cursor_color = cursor->color; rd->url_color = OPT(url_color); rd->url_style = OPT(url_style); - rd->cursor_text_uses_bg = cursor_text_as_bg(screen->color_profile); +#undef IS_SPECIAL_COLOR + rd->url_color = OPT(url_color); rd->url_style = OPT(url_style); unmap_vao_buffer(vao_idx, uniform_buffer); rd = NULL; } @@ -456,7 +471,7 @@ has_bgimage(OSWindow *w) { static void draw_tint(bool premult, Screen *screen, GLfloat xstart, GLfloat ystart, GLfloat width, GLfloat height) { bind_program(TINT_PROGRAM); - color_type window_bg = colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.default_bg, screen->color_profile->configured.default_bg); + color_type window_bg = colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.default_bg, screen->color_profile->configured.default_bg).rgb; #define C(shift) ((((GLfloat)((window_bg >> shift) & 0xFF)) / 255.0f)) float alpha = OPT(background_tint); if (premult) glUniform4f(tint_program_layout.tint_color_location, C(16) * alpha, C(8) * alpha, C(0) * alpha, alpha); @@ -506,8 +521,8 @@ render_window_title(OSWindow *os_window, Screen *screen UNUSED, GLfloat xstart, static char title[2048] = {0}; if (window->title_bar_data.last_drawn_title_object_id != window->title) { snprintf(title, arraysz(title), " %s", PyUnicode_AsUTF8(window->title)); -#define RGBCOL(which) ( 0xff000000 | colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.which, screen->color_profile->configured.which) ) - if (!draw_window_title(os_window, title, RGBCOL(highlight_fg), RGBCOL(highlight_bg), window->title_bar_data.buf, bar_width, bar_height)) return 0; +#define RGBCOL(which, fallback) ( 0xff000000 | colorprofile_to_color_with_fallback(screen->color_profile, screen->color_profile->overridden.which, screen->color_profile->configured.which, screen->color_profile->overridden.fallback, screen->color_profile->configured.fallback)) + if (!draw_window_title(os_window, title, RGBCOL(highlight_fg, default_fg), RGBCOL(highlight_bg, default_bg), window->title_bar_data.buf, bar_width, bar_height)) return 0; #undef RGBCOL window->title_bar_data.last_drawn_title_object_id = window->title; } @@ -550,7 +565,7 @@ draw_window_number(OSWindow *os_window, Screen *screen, GLfloat xstart, GLfloat bind_program(SEVEN_SEGMENT_PROGRAM); glUniform4f(seven_segment_program_layout.edges_location, xstart, ystart - height, xstart + width, ystart); glUniform4f(seven_segment_program_layout.area_bounds_location, left, top, right - left, bottom - top); - color_type digit_color = colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.highlight_bg, screen->color_profile->configured.highlight_bg); + color_type digit_color = colorprofile_to_color_with_fallback(screen->color_profile, screen->color_profile->overridden.highlight_bg, screen->color_profile->configured.highlight_bg, screen->color_profile->overridden.default_fg, screen->color_profile->configured.default_fg); #define C(shift) ((((GLfloat)((digit_color >> shift) & 0xFF)) / 255.0f)) glUniform4f(seven_segment_program_layout.digit_color_location, C(16), C(8), C(0), 1.); #undef C @@ -566,7 +581,8 @@ draw_visual_bell_flash(GLfloat intensity, GLfloat xstart, GLfloat ystart, GLfloa glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); bind_program(TINT_PROGRAM); GLfloat attenuation = 0.4f; - const color_type flash = colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.highlight_bg, screen->color_profile->configured.highlight_bg); + const color_type flash = colorprofile_to_color_with_fallback( + screen->color_profile, screen->color_profile->overridden.highlight_bg, screen->color_profile->configured.highlight_bg, screen->color_profile->overridden.default_fg, screen->color_profile->configured.default_fg); #define C(shift) ((((GLfloat)((flash >> shift) & 0xFF)) / 255.0f) ) const GLfloat r = C(16), g = C(8), b = C(0); const GLfloat max_channel = r > g ? (r > b ? r : b) : (g > b ? g : b); diff --git a/kitty/window.py b/kitty/window.py index 48b804f9b..37617b3b4 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -36,13 +36,13 @@ from .fast_data_types import ( from .keys import keyboard_mode_name, mod_mask from .notify import NotificationCommand, handle_notification_cmd from .options.types import Options -from .rgb import to_color +from .rgb import Color, to_color from .terminfo import get_capabilities from .types import MouseEvent, ScreenGeometry, WindowGeometry, ac from .typing import BossType, ChildType, EdgeLiteral, TabType, TypedDict from .utils import ( - color_as_int, get_primary_selection, load_shaders, log_error, open_cmd, - open_url, parse_color_set, sanitize_title, set_primary_selection + get_primary_selection, load_shaders, log_error, open_cmd, open_url, + parse_color_set, sanitize_title, set_primary_selection ) MatchPatternType = Union[Pattern[str], Tuple[Pattern[str], Optional[Pattern[str]]]] @@ -264,14 +264,14 @@ load_shader_programs = LoadShaderPrograms() def setup_colors(screen: Screen, opts: Options) -> None: screen.color_profile.update_ansi_color_table(build_ansi_color_table(opts)) - cursor_text_color = opts.cursor_text_color or (12, 12, 12) - cursor_text_color_as_bg = 3 if opts.cursor_text_color is None else 1 - sfg = (0, 0, 0) if opts.selection_foreground is None else opts.selection_foreground - screen.color_profile.set_configured_colors(*map(color_as_int, ( - opts.foreground, opts.background, opts.cursor, - cursor_text_color, (0, 0, cursor_text_color_as_bg), - sfg, opts.selection_background) - )) + + def s(c: Optional[Color]) -> int: + return 0 if c is None else (0xff000000 | int(c)) + screen.color_profile.set_configured_colors( + s(opts.foreground), s(opts.background), + s(opts.cursor), s(opts.cursor_text_color), + s(opts.selection_foreground), s(opts.selection_background) + ) def text_sanitizer(as_ansi: bool, add_wrap_markers: bool) -> Callable[[str], str]: @@ -762,16 +762,16 @@ class Window: def change_colors(self, changes: Dict[DynamicColor, Optional[str]]) -> None: dirtied = default_bg_changed = False - def item(raw: Optional[str]) -> Optional[int]: + def item(raw: Optional[str]) -> int: if raw is None: return 0 - val = to_color(raw) - return None if val is None else (color_as_int(val) << 8) | 2 + v = to_color(raw) + if v is None: + return 0 + return 0xff000000 | int(v) for which, val_ in changes.items(): val = item(val_) - if val is None: - continue dirtied = True setattr(self.screen.color_profile, which.name, val) if which.name == 'default_bg':