diff --git a/kitty/cell_defines.glsl b/kitty/cell_defines.glsl index 23b1ffd07..2df43379d 100644 --- a/kitty/cell_defines.glsl +++ b/kitty/cell_defines.glsl @@ -1,4 +1,3 @@ -#define FG_OVERRIDE_THRESHOLD {FG_OVERRIDE_THRESHOLD} #define FG_OVERRIDE_ALGO {FG_OVERRIDE_ALGO} #define TEXT_NEW_GAMMA {TEXT_NEW_GAMMA} diff --git a/kitty/cell_vertex.glsl b/kitty/cell_vertex.glsl index cabed1495..7c58706cb 100644 --- a/kitty/cell_vertex.glsl +++ b/kitty/cell_vertex.glsl @@ -25,6 +25,7 @@ uniform float gamma_lut[256]; uniform uint draw_bg_bitfield; uniform usampler2D sprite_decorations_map; uniform float row_offset; +uniform float fg_override_threshold; // Have to use fixed locations here as all variants of the cell program share the same VAOs layout(location=0) in uvec3 colors; @@ -268,7 +269,7 @@ vec3 fg_override(float under_luminance, float over_lumininace, vec3 under, vec3 // If the difference in luminance is too small, // force the foreground color to be black or white. float diff_luminance = abs(under_luminance - over_lumininace); - float override_level = (1.f - colored_sprite) * step(diff_luminance, FG_OVERRIDE_THRESHOLD); + float override_level = (1.f - colored_sprite) * step(diff_luminance, fg_override_threshold); float original_level = 1.f - override_level; return original_level * over + override_level * vec3(step(under_luminance, 0.5f)); } @@ -279,7 +280,7 @@ vec3 fg_override(float under_luminance, float over_luminance, vec3 under, vec3 o float ratio = contrast_ratio(under_luminance, over_luminance); vec3 diff = abs(under - over); vec3 over_hsluv = rgbToHsluv(over); - const float min_contrast_ratio = FG_OVERRIDE_THRESHOLD; + const float min_contrast_ratio = fg_override_threshold; float target_lum_a = clamp((under_luminance + 0.05f) * min_contrast_ratio - 0.05f, 0.f, 1.f); float target_lum_b = clamp((under_luminance + 0.05f) / min_contrast_ratio - 0.05f, 0.f, 1.f); vec3 result_a = clamp(hsluvToRgb(vec3(over_hsluv.x, over_hsluv.y, target_lum_a * 100.f)), 0.f, 1.f); diff --git a/kitty/options/definition.py b/kitty/options/definition.py index 6e42a6bfd..e3f859a6a 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -267,7 +267,7 @@ Then adjust the second parameter until it looks good. Then switch to a light the and adjust the first parameter until the perceived thickness matches the dark theme. ''') -opt('text_fg_override_threshold', '0', option_type='text_fg_override_threshold', long_text=''' +opt('text_fg_override_threshold', '0', option_type='text_fg_override_threshold', ctype='text_fg_override_threshold', long_text=''' A setting to prevent low contrast between foreground and background colors. Useful when working with applications that use colors that do not contrast well with your preferred color scheme. The default value is :code:`0`, which means no color overriding is performed. diff --git a/kitty/options/to-c-generated.h b/kitty/options/to-c-generated.h index 6d601cc75..0e0a37691 100644 --- a/kitty/options/to-c-generated.h +++ b/kitty/options/to-c-generated.h @@ -122,6 +122,19 @@ convert_from_opts_text_composition_strategy(PyObject *py_opts, Options *opts) { Py_DECREF(ret); } +static void +convert_from_python_text_fg_override_threshold(PyObject *val, Options *opts) { + opts->text_fg_override_threshold = text_fg_override_threshold(val); +} + +static void +convert_from_opts_text_fg_override_threshold(PyObject *py_opts, Options *opts) { + PyObject *ret = PyObject_GetAttrString(py_opts, "text_fg_override_threshold"); + if (ret == NULL) return; + convert_from_python_text_fg_override_threshold(ret, opts); + Py_DECREF(ret); +} + static void convert_from_python_cursor_shape(PyObject *val, Options *opts) { opts->cursor_shape = PyLong_AsLong(val); @@ -1520,6 +1533,8 @@ convert_opts_from_python_opts(PyObject *py_opts, Options *opts) { if (PyErr_Occurred()) return false; convert_from_opts_text_composition_strategy(py_opts, opts); if (PyErr_Occurred()) return false; + convert_from_opts_text_fg_override_threshold(py_opts, opts); + if (PyErr_Occurred()) return false; convert_from_opts_cursor_shape(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_cursor_shape_unfocused(py_opts, opts); diff --git a/kitty/options/to-c.h b/kitty/options/to-c.h index 84ec7475c..c69dd484d 100644 --- a/kitty/options/to-c.h +++ b/kitty/options/to-c.h @@ -45,6 +45,16 @@ parse_ms_long_to_monotonic_t(PyObject *val) { return ms_to_monotonic_t(PyLong_AsUnsignedLong(val)); } +static inline float +text_fg_override_threshold(PyObject *val) { + double ans = PyFloat_AsDouble(PyTuple_GET_ITEM(val, 0)); + switch (PyUnicode_AsUTF8(PyTuple_GET_ITEM(val, 1))[0]) { + case '%': ans = MAX(0, MIN(ans, 100)) * 0.01; break; + default: ans = MAX(0, MIN(ans, 21)); break; + } + return (float)ans; +} + static inline WindowTitleIn window_title_in(PyObject *title_in) { const char *in = PyUnicode_AsUTF8(title_in); diff --git a/kitty/options/types.py b/kitty/options/types.py index a271ca046..3d036ef5d 100644 --- a/kitty/options/types.py +++ b/kitty/options/types.py @@ -681,7 +681,7 @@ class Options: tab_separator: str = ' ┇' tab_switch_strategy: choices_for_tab_switch_strategy = 'previous' tab_title_max_length: int = 0 - tab_title_template: str = '{fmt.fg.red}{bell_symbol}{activity_symbol}{fmt.fg.tab}{tab.last_focused_progress_percent}{title}' + tab_title_template: str = '{fmt.fg.red}{bell_symbol}{activity_symbol}{secure_input_symbol}{fmt.fg.tab}{tab.last_focused_progress_percent}{title}' term: str = 'xterm-kitty' terminfo_type: choices_for_terminfo_type = 'path' text_composition_strategy: str = 'platform' diff --git a/kitty/shaders.c b/kitty/shaders.c index 8d9913642..4dd6cdeac 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -1336,6 +1336,7 @@ call_cell_program(int program, const UIRenderData *ui, ssize_t vao_idx, bool for bind_vao_uniform_buffer(vao_idx, color_table_buffer, COLOR_TABLE_BINDING_POINT); glUniform1ui(cell_program_layouts[program].uniforms.draw_bg_bitfield, draw_bg_bitfield); glUniform1f(cell_program_layouts[program].uniforms.row_offset, row_offset_for_screen(ui->screen)); + glUniform1f(cell_program_layouts[program].uniforms.fg_override_threshold, OPT(text_fg_override_threshold)); if (for_final_output) glEnable(GL_FRAMEBUFFER_SRGB); draw_quad(!for_final_output, render_lines_for_screen(ui->screen) * ui->screen->columns); if (for_final_output) glDisable(GL_FRAMEBUFFER_SRGB); diff --git a/kitty/shaders/legacy.py b/kitty/shaders/legacy.py index 144b6cad9..7a10ba114 100644 --- a/kitty/shaders/legacy.py +++ b/kitty/shaders/legacy.py @@ -5,7 +5,7 @@ import re from collections.abc import Callable, Iterator from functools import lru_cache, partial from itertools import count -from typing import Any, Literal, NamedTuple, Optional +from typing import Any, Literal, Optional from kitty.constants import read_kitty_resource from kitty.fast_data_types import ( @@ -136,14 +136,8 @@ class MultiReplacer: null_replacer = MultiReplacer() -class TextFgOverrideThreshold(NamedTuple): - value: float = 0 - unit: Literal['%', 'ratio'] = '%' - scaled_value: float = 0 - - class LoadShaderPrograms: - text_fg_override_threshold: TextFgOverrideThreshold = TextFgOverrideThreshold() + text_fg_override_threshold: tuple[float, Literal['%', 'ratio']] = 0, '%' text_old_gamma: bool = False cell_program_replacer: MultiReplacer = null_replacer @@ -151,8 +145,9 @@ class LoadShaderPrograms: def needs_recompile(self) -> bool: opts = get_options() return ( - opts.text_fg_override_threshold != (self.text_fg_override_threshold.value, self.text_fg_override_threshold.unit) - or (opts.text_composition_strategy == 'legacy') != self.text_old_gamma + bool(opts.text_fg_override_threshold[0]) != bool(self.text_fg_override_threshold[0]) or + opts.text_fg_override_threshold[1] != self.text_fg_override_threshold[1] or + (opts.text_composition_strategy == 'legacy') != self.text_old_gamma ) def recompile_if_needed(self) -> None: @@ -162,15 +157,7 @@ class LoadShaderPrograms: def __call__(self, allow_recompile: bool = False) -> None: opts = get_options() self.text_old_gamma = opts.text_composition_strategy == 'legacy' - - text_fg_override_threshold: float = opts.text_fg_override_threshold[0] - match opts.text_fg_override_threshold[1]: - case '%': - text_fg_override_threshold = max(0, min(text_fg_override_threshold, 100.0)) * 0.01 - case 'ratio': - text_fg_override_threshold = max(0, min(text_fg_override_threshold, 21.0)) - self.text_fg_override_threshold = TextFgOverrideThreshold( - opts.text_fg_override_threshold[0], opts.text_fg_override_threshold[1], text_fg_override_threshold) + self.text_fg_override_threshold = opts.text_fg_override_threshold cell = program_for('cell') if self.cell_program_replacer is null_replacer: @@ -190,14 +177,13 @@ class LoadShaderPrograms: ) def resolve_cell_defines(only_fg: int, only_bg: int, src: str) -> str: - algo = '1' if self.text_fg_override_threshold.unit == '%' else '2' - if not self.text_fg_override_threshold.scaled_value: + algo = '1' if self.text_fg_override_threshold[1] == '%' else '2' + if not self.text_fg_override_threshold[0]: algo = '0' r = self.cell_program_replacer.replacements r['ONLY_FOREGROUND'] = str(only_fg) r['ONLY_BACKGROUND'] = str(only_bg) r['FG_OVERRIDE_ALGO'] = algo - r['FG_OVERRIDE_THRESHOLD'] = str(self.text_fg_override_threshold.scaled_value) r['TEXT_NEW_GAMMA'] = '0' if self.text_old_gamma else '1' return self.cell_program_replacer(src) for prog, (only_fg, only_bg) in { diff --git a/kitty/state.h b/kitty/state.h index 2eecb356c..e4b47ac1e 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -163,6 +163,7 @@ typedef struct Options { double window_drag_tolerance; bool generate_256_palette; int drag_threshold; + float text_fg_override_threshold; } Options; typedef struct WindowLogoRenderData {