Compare commits

...

4 Commits
x11 ... trans

Author SHA1 Message Date
Kovid Goyal
c10e421e9b Implement dynamic control of transparent background colors via escape code
Still have to implement it via remote control
2024-09-20 12:56:16 +05:30
Kovid Goyal
958cc6d98f Store transparent colors on ColorProfile
This will eventually allow them to be changed using remote control and
escape codes.
2024-09-20 10:02:28 +05:30
Kovid Goyal
f1f4a1a4a3 Replace the second_transparent_bg option
This is backwards incompatible, but only for a feature released 3 weeks
ago.
2024-09-20 09:11:39 +05:30
Kovid Goyal
55859f4b56 Prepare for allowing upto seven additional semi-transparent background colors 2024-09-20 09:11:39 +05:30
17 changed files with 249 additions and 84 deletions

View File

@@ -77,6 +77,8 @@ Detailed list of changes
0.36.3 [future]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- The option ``second_transparent_bg`` has been removed and replaced by :opt:`transparent_background_colors` which allows setting up to seven additional colors that will be transparent, with individual opacities per color (:iss:`7646`)
- Fix a regression in the previous release that broke use of the ``cd`` command in session files (:iss:`7829`)
- macOS: Fix shortcuts that become entries in the global menubar being reported as removed shortcuts in the debug output
@@ -132,7 +134,7 @@ Detailed list of changes
- launch command: A new :option:`launch --bias` option to adjust the size of newly created windows declaratively (:iss:`7634`)
- A new option :opt:`second_transparent_bg` to make a second background color semi-transparent via :opt:`background_opacity`. Useful for things like cursor line highlight in editors (:iss:`7646`)
- A new option :opt:`transparent_background_colors` to make a second background color semi-transparent via :opt:`background_opacity`. Useful for things like cursor line highlight in editors (:iss:`7646`)
- A new :doc:`notify </kittens/notify>` kitten to show desktop notifications
from the command line with support for icons, buttons and more.

View File

@@ -69,8 +69,11 @@ selection_foreground The foreground color of selections
cursor The color of the text cursor Foreground color
cursor_text The color of text under the cursor Background color
visual_bell The color of a visual bell Automatic color selection based on current screen colors
second_transparent_background A color that might be rendered semi-transparent No second color is made transparent
in addition to the default background color
transparent_background_color1..8 A background color that is rendered Unset
with the specified opacity in cells that have
the specified background color. An opacity
value less than zero means, use the
:opt:`background_opacity` value.
================================= =============================================== ===============================
In this table the third column shows what effect setting the color to *dynamic*
@@ -160,8 +163,19 @@ RGB colors are encoded in one of three forms:
``rgbi:<red>/<green>/<blue>``
red, green, and blue are floating-point values between 0.0 and 1.0, inclusive. The input format for these values is an optional
sign, a string of numbers possibly containing a decimal point, and an optional exponent field containing an E or e followed by a possibly
signed integer string.
sign, a string of numbers possibly containing a decimal point, and an optional exponent field containing an E or e followed by a possibly
signed integer string. Values outside the ``0 - 1`` range must be clipped to be within the range.
If a color should have an alpha component, it must be suffixed to the color
specification in the form :code:`@number between zero and one`. For example::
red@0.5 rgb:ff0000@0.1 #ff0000@0.3
The syntax for the floating point alpha component is the same as used for the
components of ``rgbi`` defined above. When not specified, the default alpha
value is ``1.0``. Values outside the range ``0 - 1`` must be clipped
to be within the range, negative values may have special context dependent
meaning.
In addition, the following color names are accepted (case-insensitively) corresponding to the
specified RGB values.

View File

@@ -187,7 +187,7 @@ void main() {
#ifdef TRANSPARENT
final_color = vec4_premul(background, bg_alpha);
#else
final_color = vec4(background, draw_bg);
final_color = vec4(background, draw_bg * bg_alpha);
#endif
#endif

View File

@@ -4,13 +4,16 @@
// Inputs {{{
layout(std140) uniform CellRenderData {
float xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity, use_cell_bg_for_selection_fg, use_cell_fg_for_selection_fg, use_cell_for_selection_bg;
float xstart, ystart, dx, dy, sprite_dx, sprite_dy, use_cell_bg_for_selection_fg, use_cell_fg_for_selection_fg, use_cell_for_selection_bg;
uint default_fg, default_bg, highlight_fg, highlight_bg, cursor_fg, cursor_bg, url_color, url_style, inverted, second_transparent_bg;
uint default_fg, 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, cursor_opacity;
// must have unique entries with 0 being default_bg and unset being UINT32_MAX
uint bg_colors0, bg_colors1, bg_colors2, bg_colors3, bg_colors4, bg_colors5, bg_colors6, bg_colors7;
float bg_opacities0, bg_opacities1, bg_opacities2, bg_opacities3, bg_opacities4, bg_opacities5, bg_opacities6, bg_opacities7;
uint color_table[NUM_COLORS + MARK_MASK + MARK_MASK + 2];
};
#if (PHASE == PHASE_BACKGROUND)
@@ -145,12 +148,30 @@ CellData set_vertex_position() {
return CellData(has_cursor, has_cursor * is_block_cursor, pos);
}
float background_opacity_for(uint bg, uint colorval, float opacity_if_matched) { // opacity_if_matched if bg == colorval else 1
float not_matched = step(1.f, abs(float(colorval - bg))); // not_matched = 0 if bg == colorval else 1
return not_matched + opacity_if_matched * (1.f - not_matched);
}
float calc_background_opacity(uint bg) {
return (
background_opacity_for(bg, bg_colors0, bg_opacities0) *
background_opacity_for(bg, bg_colors1, bg_opacities1) *
background_opacity_for(bg, bg_colors2, bg_opacities2) *
background_opacity_for(bg, bg_colors3, bg_opacities3) *
background_opacity_for(bg, bg_colors4, bg_opacities4) *
background_opacity_for(bg, bg_colors5, bg_opacities5) *
background_opacity_for(bg, bg_colors6, bg_opacities6) *
background_opacity_for(bg, bg_colors7, bg_opacities7)
);
}
void main() {
CellData cell_data = set_vertex_position();
// set cell color indices {{{
uvec2 default_colors = uvec2(default_fg, default_bg);
uvec2 default_colors = uvec2(default_fg, bg_colors0);
uint text_attrs = sprite_coords[3];
uint is_reversed = ((text_attrs >> REVERSE_SHIFT) & ONE);
uint is_inverted = is_reversed + inverted;
@@ -194,42 +215,34 @@ void main() {
// }}}
// Background {{{
float bg_is_not_transparent = step(1, float(abs(
(bg_as_uint - default_colors[1]) * (bg_as_uint - second_transparent_bg)
))); // bg_is_not_transparent = 0 if bg_as_uint in (default_colors[1], second_transparent_bg) else 1
#if PHASE == PHASE_BOTH && !defined(TRANSPARENT) // fast case single pass opaque background
bg_alpha = 1;
draw_bg = 1;
#else
bg_alpha = calc_background_opacity(bg_as_uint);
#if (PHASE == PHASE_BACKGROUND)
// draw_bg_bitfield has bit 0 set to draw default bg cells and bit 1 set to draw non-default bg cells
uint draw_bg_mask = uint(2 * bg_is_not_transparent + (1 - bg_is_not_transparent));
draw_bg = step(1, float(draw_bg_bitfield & draw_bg_mask));
float cell_has_non_default_bg = step(1.f, abs(float(bg_as_uint - bg_colors0))); // 0 if has default bg else 1
uint draw_bg_mask = uint(2.f * cell_has_non_default_bg + (1.f - cell_has_non_default_bg)); // 1 if has default bg else 2
draw_bg = step(0.5, float(draw_bg_bitfield & draw_bg_mask));
#else
draw_bg = 1;
#endif
bg_alpha = 1.f;
#ifdef TRANSPARENT
// Set bg_alpha to background_opacity on cells that have the default background color
// Which means they must not have a block cursor or a selection or reverse video
// On other cells it should be 1. For the SPECIAL program it should be 1 on cells with
// selections/block cursor and 0 everywhere else.
float is_special_cell = cell_data.has_block_cursor + float(is_selected & ONE);
#if (PHASE != PHASE_SPECIAL)
is_special_cell += bg_is_not_transparent + float(is_reversed);
#endif
#if PHASE == PHASE_SPECIAL
// Only special cells must be drawn and they must have bg_alpha 1
bg_alpha = step(0.5, is_special_cell); // bg_alpha = 1 if is_special_cell else 0
#if (PHASE != PHASE_SPECIAL)
bg_alpha = bg_alpha + (1.0f - bg_alpha) * background_opacity; // bg_alpha = 1 if bg_alpha else background_opacity
bg_alpha *= draw_bg; // if not draw_bg: bg_alpha = 0
#endif
#else
is_special_cell += float(is_reversed); // bg_alpha should be 1 for reverse video cells as well
is_special_cell = step(0.5, is_special_cell); // is_special_cell = 1 if is_special_cell else 0
bg_alpha = bg_alpha * (1. - float(is_special_cell)) + is_special_cell; // bg_alpha = 1 if is_special_cell else bg_alpha
#endif
bg_alpha *= draw_bg;
#endif // ends fast case #if else
// Selection and cursor
bg = choose_color(float(is_selected & ONE), choose_color(use_cell_for_selection_bg, color_to_vec(fg_as_uint), color_to_vec(highlight_bg)), bg);
background = choose_color(cell_data.has_block_cursor, mix(bg, color_to_vec(cursor_bg), cursor_opacity), bg);
#if !defined(TRANSPARENT) && (PHASE == PHASE_SPECIAL)
float is_special_cell = cell_data.has_block_cursor + float(is_selected & ONE);
bg_alpha = step(0.5, is_special_cell);
#endif
// }}}
}

View File

@@ -80,9 +80,18 @@ set_configured_colors(ColorProfile *self, PyObject *opts) {
n(default_fg, foreground); n(default_bg, background);
n(cursor_color, cursor); n(cursor_text_color, cursor_text_color);
n(highlight_fg, selection_foreground); n(highlight_bg, selection_background);
n(visual_bell_color, visual_bell_color); n(second_transparent_bg, second_transparent_bg);
n(visual_bell_color, visual_bell_color);
#undef n
return true;
memset(self->configured_transparent_colors, 0, sizeof(self->configured_transparent_colors));
RAII_PyObject(src, PyObject_GetAttrString(opts, "transparent_background_colors"));
if (!src) { PyErr_SetString(PyExc_TypeError, "No transparent_background_colors on opts object"); return false; }
for (Py_ssize_t i = 0; i < MIN(PyTuple_GET_SIZE(src), (Py_ssize_t)arraysz(self->configured_transparent_colors)); i++) {
PyObject *e = PyTuple_GET_ITEM(src, i);
self->configured_transparent_colors[i].color = ((Color*)(PyTuple_GET_ITEM(e, 0)))->color.val & 0xffffff;
self->configured_transparent_colors[i].opacity = (float)PyFloat_AsDouble(PyTuple_GET_ITEM(e, 1));
self->configured_transparent_colors[i].is_set = true;
}
return PyErr_Occurred() ? false : true;
}
static bool
@@ -162,6 +171,8 @@ copy_color_profile(ColorProfile *dest, ColorProfile *src) {
memcpy(dest->orig_color_table, src->orig_color_table, sizeof(dest->color_table));
memcpy(&dest->configured, &src->configured, sizeof(dest->configured));
memcpy(&dest->overridden, &src->overridden, sizeof(dest->overridden));
memcpy(dest->overriden_transparent_colors, src->overriden_transparent_colors, sizeof(dest->overriden_transparent_colors));
memcpy(dest->configured_transparent_colors, src->configured_transparent_colors, sizeof(dest->configured_transparent_colors));
dest->dirty = true;
}
@@ -226,12 +237,30 @@ patch_color_profiles(PyObject *module UNUSED, PyObject *args) {
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); S(visual_bell_color, visual_bell_color);
S(second_transparent_bg, second_transparent_bg);
#undef SI
#undef S
// TODO: Patch transparent_colors
Py_RETURN_NONE;
}
bool
colorprofile_to_transparent_color(const ColorProfile *self, unsigned index, color_type *color, float *opacity) {
*color = UINT32_MAX; *opacity = 1.0;
if (index < arraysz(self->configured_transparent_colors)) {
if (self->overriden_transparent_colors[index].is_set) {
*color = self->overriden_transparent_colors[index].color; *opacity = self->overriden_transparent_colors[index].opacity;
if (*opacity < 0) *opacity = OPT(background_opacity);
return true;
}
if (self->configured_transparent_colors[index].is_set) {
*color = self->configured_transparent_colors[index].color; *opacity = self->configured_transparent_colors[index].opacity;
if (*opacity < 0) *opacity = OPT(background_opacity);
return true;
}
}
return false;
}
DynamicColor
colorprofile_to_color(const ColorProfile *self, DynamicColor entry, DynamicColor defval) {
switch(entry.type) {
@@ -295,9 +324,10 @@ as_dict(ColorProfile *self, PyObject *args UNUSED) {
}}
D(default_fg, foreground); D(default_bg, background);
D(cursor_color, cursor); D(cursor_text_color, cursor_text); D(highlight_fg, selection_foreground);
D(highlight_bg, selection_background); D(visual_bell_color, visual_bell_color); D(second_transparent_bg, second_transparent_bg);
D(highlight_bg, selection_background); D(visual_bell_color, visual_bell_color);
#undef D
// TODO: Add transparent_colors
return ans;
}
@@ -373,6 +403,8 @@ copy_color_table_to_buffer(ColorProfile *self, color_type *buf, int offset, size
static void
push_onto_color_stack_at(ColorProfile *self, unsigned int i) {
self->color_stack[i].dynamic_colors = self->overridden;
memcpy(self->color_stack[i].transparent_colors, self->overriden_transparent_colors, sizeof(self->overriden_transparent_colors));
self->color_stack[i].dynamic_colors = self->overridden;
memcpy(self->color_stack[i].color_table, self->color_table, sizeof(self->color_stack->color_table));
}
@@ -381,6 +413,7 @@ static void
copy_from_color_stack_at(ColorProfile *self, unsigned int i) {
self->overridden = self->color_stack[i].dynamic_colors;
memcpy(self->color_table, self->color_stack[i].color_table, sizeof(self->color_table));
memcpy(self->overriden_transparent_colors, self->color_stack[i].transparent_colors, sizeof(self->overriden_transparent_colors));
}
bool
@@ -477,7 +510,6 @@ CGETSET(cursor_text_color, true)
CGETSET(highlight_fg, true)
CGETSET(highlight_bg, true)
CGETSET(visual_bell_color, true)
CGETSET(second_transparent_bg, true)
#undef CGETSET
static PyGetSetDef cp_getsetters[] = {
@@ -488,7 +520,6 @@ static PyGetSetDef cp_getsetters[] = {
GETSET(highlight_fg)
GETSET(highlight_bg)
GETSET(visual_bell_color)
GETSET(second_transparent_bg)
{NULL} /* Sentinel */
};
@@ -508,6 +539,36 @@ reload_from_opts(ColorProfile *self, PyObject *args UNUSED) {
Py_RETURN_NONE;
}
static PyObject*
get_transparent_background_color(ColorProfile *self, PyObject *index) {
if (!PyLong_Check(index)) { PyErr_SetString(PyExc_TypeError, "index must be an int"); return NULL; }
unsigned long idx = PyLong_AsUnsignedLong(index);
if (PyErr_Occurred()) return NULL;
if (idx >= arraysz(self->configured_transparent_colors)) Py_RETURN_NONE;
TransparentDynamicColor *c = self->overriden_transparent_colors[idx].is_set ? self->overriden_transparent_colors + idx : self->configured_transparent_colors + idx;
if (!c->is_set) Py_RETURN_NONE;
float opacity = c->opacity >= 0 ? c->opacity : OPT(background_opacity);
return (PyObject*)alloc_color((c->color >> 16) & 0xff, (c->color >> 8) & 0xff, c->color & 0xff, (unsigned)(255.f * opacity));
}
static PyObject*
set_transparent_background_color(ColorProfile *self, PyObject *const *args, Py_ssize_t nargs) {
if (nargs < 1) { PyErr_SetString(PyExc_TypeError, "must specify index"); return NULL; }
if (!PyLong_Check(args[0])) { PyErr_SetString(PyExc_TypeError, "index must be an int"); return NULL; }
unsigned long idx = PyLong_AsUnsignedLong(args[0]);
if (PyErr_Occurred()) return NULL;
if (idx >= arraysz(self->configured_transparent_colors)) Py_RETURN_NONE;
if (nargs < 2) { self->overriden_transparent_colors[idx].is_set = false; Py_RETURN_NONE; }
if (!PyObject_TypeCheck(args[1], &Color_Type)) { PyErr_SetString(PyExc_TypeError, "color must be Color object"); return NULL; }
Color *c = (Color*)args[1];
float opacity = (float)(c->color.alpha) / 255.f;
if (nargs > 2 && PyFloat_Check(args[2])) opacity = (float)PyFloat_AsDouble(args[2]);
self->overriden_transparent_colors[idx].is_set = true;
self->overriden_transparent_colors[idx].color = c->color.rgb;
self->overriden_transparent_colors[idx].opacity = MAX(-1.f, MIN(opacity, 1.f));
Py_RETURN_NONE;
}
static PyMethodDef cp_methods[] = {
METHOD(reset_color_table, METH_NOARGS)
METHOD(as_dict, METH_NOARGS)
@@ -515,7 +576,9 @@ static PyMethodDef cp_methods[] = {
METHOD(as_color, METH_O)
METHOD(reset_color, METH_O)
METHOD(set_color, METH_VARARGS)
METHODB(get_transparent_background_color, METH_O),
METHODB(reload_from_opts, METH_VARARGS),
{"set_transparent_background_color", (PyCFunction)(void(*)(void))set_transparent_background_color, METH_FASTCALL, ""},
{NULL} /* Sentinel */
};
@@ -552,7 +615,7 @@ new_color(PyTypeObject *type UNUSED, PyObject *args, PyObject *kwds) {
}
static PyObject*
color_as_int(Color *self) {
Color_as_int(Color *self) {
return PyLong_FromUnsignedLong(self->color.val);
}
@@ -566,7 +629,7 @@ color_truediv(Color *self, PyObject *divisor) {
}
static PyNumberMethods color_number_methods = {
.nb_int = (unaryfunc)color_as_int,
.nb_int = (unaryfunc)Color_as_int,
.nb_true_divide = (binaryfunc)color_truediv,
};

View File

@@ -323,15 +323,21 @@ typedef union DynamicColor {
} DynamicColor;
typedef struct {
DynamicColor default_fg, default_bg, cursor_color, cursor_text_color, highlight_fg, highlight_bg, visual_bell_color, second_transparent_bg;
DynamicColor default_fg, default_bg, cursor_color, cursor_text_color, highlight_fg, highlight_bg, visual_bell_color;
} DynamicColors;
typedef struct TransparentDynamicColor {
color_type color; float opacity; bool is_set;
} TransparentDynamicColor;
typedef struct {
PyObject_HEAD
bool dirty;
uint32_t color_table[256], orig_color_table[256];
struct { DynamicColors dynamic_colors; uint32_t color_table[256]; } *color_stack;
TransparentDynamicColor configured_transparent_colors[8], overriden_transparent_colors[8];
struct { DynamicColors dynamic_colors; uint32_t color_table[256]; TransparentDynamicColor transparent_colors[8]; } *color_stack;
unsigned int color_stack_idx, color_stack_sz;
DynamicColors configured, overridden;
color_type mark_foregrounds[MARK_MASK+1], mark_backgrounds[MARK_MASK+1];
@@ -403,6 +409,7 @@ bool schedule_write_to_child_python(unsigned long id, const char *prefix, PyObje
bool set_iutf8(int, bool);
DynamicColor colorprofile_to_color(const ColorProfile *self, DynamicColor entry, DynamicColor defval);
bool colorprofile_to_transparent_color(const ColorProfile *self, unsigned index, color_type *color, float *opacity);
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);

View File

@@ -809,11 +809,6 @@ class ColorProfile:
@visual_bell_color.setter
def visual_bell_color(self, val: Union[None|int|Color]) -> None: ...
@property
def second_transparent_bg(self) -> Optional[Color]: ...
@second_transparent_bg.setter
def second_transparent_bg(self, val: Union[None|int|Color]) -> None: ...
def __init__(self, opts: Optional[Options] = None): ...
def as_dict(self) -> Dict[str, Optional[int]]:
@@ -833,6 +828,9 @@ class ColorProfile:
def reload_from_opts(self, opts: Optional[Options] = None) -> None: ...
def get_transparent_background_color(self, index: int) -> Color | None: ...
def set_transparent_background_color(self, index: int, color: Color | None = None, opacity: float | None = None) -> None: ...
def patch_color_profiles(
spec: Dict[str, Optional[int]], profiles: Tuple[ColorProfile, ...], change_configured: bool

View File

@@ -1481,7 +1481,7 @@ theme with a background color in your editor, it will not be rendered as
transparent. Instead you should change the default background color in your
kitty config and not use a background color in the editor color scheme. Or use
the escape codes to set the terminals default colors in a shell script to
launch your editor. See also :opt:`second_transparent_bg`.
launch your editor. See also :opt:`transparent_background_colors`.
Be aware that using a value less than 1.0 is a (possibly
significant) performance hit. When using a low value for this setting, it is
desirable that you set the :opt:`background` color to a color the matches the
@@ -1527,14 +1527,22 @@ opt('background_image_linear', 'no',
long_text='When background image is scaled, whether linear interpolation should be used.'
)
opt('second_transparent_bg', 'none', option_type='to_color_or_none', long_text='''
When the background color matches this color, :opt:`background_opacity` is applied to it
to render it as semi-transparent, just as for colors matching the main :opt:`background` color.
opt('transparent_background_colors', '', option_type='transparent_background_colors', long_text='''
A space separated list of upto 7 colors, with opacity. When the background color of a cell matches one of these colors,
it is rendered semi-transparent using the specified opacity.
Useful in more complex UIs like editors where you could want more than a single background color
to be rendered as transparent, for instance, for a cursor highlight line background.
to be rendered as transparent, for instance, for a cursor highlight line background or a highlighted block.
Terminal applications can set this color using :ref:`The kitty color control <color_control>`
escape code.
''')
The syntax for specifiying colors is: :code:`color@opacity`, where the :code:`@opacity`
part is optional. When unspecified, the value of :opt:`background_opacity` is used. For example::
transparent_background_colors red@0.5 #00ff00@0.3
'''
)
opt('dynamic_background_opacity', 'no',
option_type='to_bool', ctype='bool',

11
kitty/options/parse.py generated
View File

@@ -19,8 +19,9 @@ from kitty.options.utils import (
shell_integration, store_multiple, symbol_map, tab_activity_symbol, tab_bar_edge,
tab_bar_margin_height, tab_bar_min_tabs, tab_fade, tab_font_style, tab_separator,
tab_title_template, titlebar_color, to_cursor_shape, to_cursor_unfocused_shape, to_font_size,
to_layout_names, to_modifiers, url_prefixes, url_style, visual_bell_duration,
visual_window_select_characters, window_border_width, window_logo_scale, window_size
to_layout_names, to_modifiers, transparent_background_colors, url_prefixes, url_style,
visual_bell_duration, visual_window_select_characters, window_border_width, window_logo_scale,
window_size
)
@@ -1193,9 +1194,6 @@ class Parser:
def scrollback_pager_history_size(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['scrollback_pager_history_size'] = scrollback_pager_history_size(val)
def second_transparent_bg(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['second_transparent_bg'] = to_color_or_none(val)
def select_by_word_characters(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['select_by_word_characters'] = str(val)
@@ -1326,6 +1324,9 @@ class Parser:
def touch_scroll_multiplier(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['touch_scroll_multiplier'] = float(val)
def transparent_background_colors(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['transparent_background_colors'] = transparent_background_colors(val)
def undercurl_style(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
val = val.lower()
if val not in self.choices_for_undercurl_style:

View File

@@ -409,7 +409,6 @@ option_names = ( # {{{
'scrollback_lines',
'scrollback_pager',
'scrollback_pager_history_size',
'second_transparent_bg',
'select_by_word_characters',
'select_by_word_characters_forward',
'selection_background',
@@ -443,6 +442,7 @@ option_names = ( # {{{
'text_composition_strategy',
'text_fg_override_threshold',
'touch_scroll_multiplier',
'transparent_background_colors',
'undercurl_style',
'underline_hyperlinks',
'update_check_interval',
@@ -574,7 +574,6 @@ class Options:
scrollback_lines: int = 2000
scrollback_pager: typing.List[str] = ['less', '--chop-long-lines', '--RAW-CONTROL-CHARS', '+INPUT_LINE_NUMBER']
scrollback_pager_history_size: int = 0
second_transparent_bg: typing.Optional[kitty.fast_data_types.Color] = None
select_by_word_characters: str = '@-./_~?&=%+#'
select_by_word_characters_forward: str = ''
selection_background: typing.Optional[kitty.fast_data_types.Color] = Color(255, 250, 205)
@@ -607,6 +606,7 @@ class Options:
text_composition_strategy: str = 'platform'
text_fg_override_threshold: float = 0.0
touch_scroll_multiplier: float = 1.0
transparent_background_colors: typing.Tuple[typing.Tuple[kitty.fast_data_types.Color, float], ...] = ()
undercurl_style: choices_for_undercurl_style = 'thin-sparse'
underline_hyperlinks: choices_for_underline_hyperlinks = 'hover'
update_check_interval: float = 24.0
@@ -1035,7 +1035,6 @@ nullable_colors = frozenset({
'active_border_color'
'tab_bar_background'
'tab_bar_margin_color'
'second_transparent_bg'
'selection_foreground'
'selection_background'
})

View File

@@ -1554,6 +1554,23 @@ def visual_bell_duration(spec: str) -> Tuple[float, EasingFunction, EasingFuncti
return parse_animation(spec, interval=0.)
def transparent_background_colors(spec: str) -> Tuple[Tuple[Color, float], ...]:
if not spec:
return ()
ans: list[tuple[Color, float]] = []
seen: dict[Color, int] = {}
for part in spec.split():
col, sep, alpha = part.partition('@')
c = to_color(col)
o = unit_float(alpha) if alpha else -1
if (idx := seen.get(c)) is not None:
ans[idx] = c, o
continue
seen[c] = len(ans)
ans.append((c, o))
return tuple(ans[:7])
def deprecated_hide_window_decorations_aliases(key: str, val: str, ans: Dict[str, Any]) -> None:
if not hasattr(deprecated_hide_window_decorations_aliases, key):
setattr(deprecated_hide_window_decorations_aliases, key, True)

View File

@@ -293,12 +293,14 @@ pick_cursor_color(Line *line, const ColorProfile *color_profile, color_type cell
static void
cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, const CellRenderData *crd, CursorRenderInfo *cursor, OSWindow *os_window) {
struct GPUCellRenderData {
GLfloat xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity, use_cell_bg_for_selection_fg, use_cell_fg_for_selection_color, use_cell_for_selection_bg;
GLfloat xstart, ystart, dx, dy, sprite_dx, sprite_dy, use_cell_bg_for_selection_fg, use_cell_fg_for_selection_color, use_cell_for_selection_bg;
GLuint default_fg, default_bg, highlight_fg, highlight_bg, cursor_fg, cursor_bg, url_color, url_style, inverted, second_transparent_bg;
GLuint default_fg, 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, cursor_opacity;
GLuint bg_colors0, bg_colors1, bg_colors2, bg_colors3, bg_colors4, bg_colors5, bg_colors6, bg_colors7;
GLfloat bg_opacities0, bg_opacities1, bg_opacities2, bg_opacities3, bg_opacities4, bg_opacities5, bg_opacities6, bg_opacities7;
};
// Send the uniform data
struct GPUCellRenderData *rd = (struct GPUCellRenderData*)map_vao_buffer(vao_idx, uniform_buffer, GL_WRITE_ONLY);
@@ -307,8 +309,13 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, c
copy_color_table_to_buffer(cp, (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(cp, cp->overridden.name, cp->configured.name).rgb
rd->default_fg = COLOR(default_fg); rd->default_bg = COLOR(default_bg);
rd->default_fg = COLOR(default_fg);
rd->highlight_fg = COLOR(highlight_fg); rd->highlight_bg = COLOR(highlight_bg);
rd->bg_colors0 = COLOR(default_bg);
rd->bg_opacities0 = os_window->is_semi_transparent ? os_window->background_opacity : 1.0f;
#define SETBG(which) colorprofile_to_transparent_color(cp, which - 1, &rd->bg_colors##which, &rd->bg_opacities##which)
SETBG(1); SETBG(2); SETBG(3); SETBG(4); SETBG(5); SETBG(6); SETBG(7);
#undef SETBG
// selection
if (IS_SPECIAL_COLOR(highlight_fg)) {
if (IS_SPECIAL_COLOR(highlight_bg)) {
@@ -320,7 +327,6 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, c
rd->use_cell_bg_for_selection_fg = 0.f; rd->use_cell_fg_for_selection_color = 0.f;
}
rd->use_cell_for_selection_bg = IS_SPECIAL_COLOR(highlight_bg) ? 1. : 0.;
rd->second_transparent_bg = IS_SPECIAL_COLOR(second_transparent_bg) ? rd->default_bg : COLOR(second_transparent_bg);
// Cursor position
enum { BLOCK_IDX = 0, BEAM_IDX = NUM_UNDERLINE_STYLES + 3, UNDERLINE_IDX = NUM_UNDERLINE_STYLES + 4, UNFOCUSED_IDX = NUM_UNDERLINE_STYLES + 5 };
Line *line_for_cursor = NULL;
@@ -338,7 +344,7 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, c
case CURSOR_HOLLOW:
rd->cursor_fg_sprite_idx = UNFOCUSED_IDX; break;
};
color_type cell_fg = rd->default_fg, cell_bg = rd->default_bg;
color_type cell_fg = rd->default_fg, cell_bg = rd->bg_colors0;
index_type cell_color_x = cursor->x;
bool reversed = false;
if (cursor->x < screen->columns && cursor->y < screen->lines) {
@@ -352,10 +358,10 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, c
colors_for_cell(line_for_cursor, cp, &cell_color_x, &cell_fg, &cell_bg, &reversed);
}
if (IS_SPECIAL_COLOR(cursor_color)) {
if (line_for_cursor) pick_cursor_color(line_for_cursor, cp, cell_fg, cell_bg, cell_color_x, &rd->cursor_fg, &rd->cursor_bg, rd->default_fg, rd->default_bg);
else { rd->cursor_fg = rd->default_bg; rd->cursor_bg = rd->default_fg; }
if (line_for_cursor) pick_cursor_color(line_for_cursor, cp, cell_fg, cell_bg, cell_color_x, &rd->cursor_fg, &rd->cursor_bg, rd->default_fg, rd->bg_colors0);
else { rd->cursor_fg = rd->bg_colors0; rd->cursor_bg = rd->default_fg; }
if (cell_bg == cell_fg) {
rd->cursor_fg = rd->default_bg; rd->cursor_bg = rd->default_fg;
rd->cursor_fg = rd->bg_colors0; rd->cursor_bg = rd->default_fg;
} else { rd->cursor_fg = cell_bg; rd->cursor_bg = cell_fg; }
} else {
rd->cursor_bg = COLOR(cursor_color);
@@ -375,7 +381,6 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, c
sprite_tracker_current_layout(os_window->fonts_data, &x, &y, &z);
rd->sprite_dx = 1.0f / (float)x; rd->sprite_dy = 1.0f / (float)y;
rd->inverted = screen_invert_colors(screen) ? 1 : 0;
rd->background_opacity = os_window->is_semi_transparent ? os_window->background_opacity : 1.0f;
#undef COLOR
rd->url_color = OPT(url_color); rd->url_style = OPT(url_style);

View File

@@ -447,17 +447,46 @@ def process_remote_print(msg: memoryview) -> str:
return replace_c0_codes_except_nl_space_tab(base64_decode(msg)).decode('utf-8', 'replace')
def transparent_background_color_control(cp: ColorProfile, responses: dict[str, str], index: int, key: str, sep: str, val: str) -> None:
if sep == '=':
if val == '?':
if index > 8:
responses[key] = '?'
else:
c = cp.get_transparent_background_color(index - 1)
if c is None:
responses[key] = ''
else:
opacity = max(0, min(c.alpha / 255.0, 1))
responses[key] = f'rgb:{c.red:02x}/{c.green:02x}/{c.blue:02x}@{opacity:.4f}'
elif index <= 8:
col, _, o = val.partition('@')
try:
opacity = float(o)
except Exception:
opacity = -1.0
c = to_color(col)
if c is not None:
cp.set_transparent_background_color(index - 1, c, opacity)
elif index <= 8:
cp.set_transparent_background_color(index - 1)
def color_control(cp: ColorProfile, code: int, value: Union[str, bytes, memoryview] = '') -> str:
if isinstance(value, (bytes, memoryview)):
value = str(value, 'utf-8', 'replace')
responses = {}
responses: dict[str, str] = {}
for rec in value.split(';'):
key, sep, val = rec.partition('=')
if key.startswith('transparent_background_color'):
index = int(key[len('transparent_background_color'):])
transparent_background_color_control(cp, responses, index, key, sep, val)
continue
attr = {
'foreground': 'default_fg', 'background': 'default_bg',
'selection_background': 'highlight_bg', 'selection_foreground': 'highlight_fg',
'cursor': 'cursor_color', 'cursor_text': 'cursor_text_color',
'visual_bell': 'visual_bell_color', 'second_transparent_background': 'second_transparent_bg',
'visual_bell': 'visual_bell_color',
}.get(key, '')
colnum = -1
with suppress(Exception):
@@ -480,6 +509,7 @@ def color_control(cp: ColorProfile, code: int, value: Union[str, bytes, memoryvi
else:
if attr:
if val:
val = val.partition('@')[0]
col = to_color(val)
if col is not None:
setattr(cp, attr, col)
@@ -488,6 +518,7 @@ def color_control(cp: ColorProfile, code: int, value: Union[str, bytes, memoryvi
setattr(cp, attr, None)
else:
if 0 <= colnum <= 255:
val = val.partition('@')[0]
col = to_color(val)
if col is not None:
cp.set_color(colnum, color_as_int(col))

View File

@@ -59,6 +59,8 @@ class Callbacks:
response = color_control(self.color_profile, code, data)
if response:
def p(x):
if '@' in x:
return (to_color(x.partition('@')[0]), int(255 * float(x.partition('@')[2])))
ans = to_color(x)
if ans is None:
ans = x

View File

@@ -1280,3 +1280,10 @@ class TestScreen(BaseTest):
q({'selection_background': ''})
self.assertIsNone(s.color_profile.highlight_bg)
q({'selection_background': '?'}, {'selection_background': ''})
s.color_profile.reload_from_opts(defaults)
q({'transparent_background_color9': '?'}, {'transparent_background_color9': '?'})
q({'transparent_background_color2': '?'}, {'transparent_background_color2': ''})
q({'transparent_background_color2': 'red@0.5'})
q({'transparent_background_color2': '?'}, {'transparent_background_color2': (Color(255, 0, 0), 126)})
q({'transparent_background_color2': '#ffffff@-1'})
q({'transparent_background_color2': '?'}, {'transparent_background_color2': (Color(255, 255, 255), 255)})

View File

@@ -15,15 +15,14 @@ import (
var nullable_colors = map[string]bool{
// generated by gen-config.py do not edit
// NULLABLE_COLORS_START
"active_border_color": true,
"cursor": true,
"cursor_text_color": true,
"second_transparent_bg": true,
"selection_background": true,
"selection_foreground": true,
"tab_bar_background": true,
"tab_bar_margin_color": true,
"visual_bell_color": true,
"active_border_color": true,
"cursor": true,
"cursor_text_color": true,
"selection_background": true,
"selection_foreground": true,
"tab_bar_background": true,
"tab_bar_margin_color": true,
"visual_bell_color": true,
// NULLABLE_COLORS_END
}

View File

@@ -309,7 +309,6 @@ var AllColorSettingNames = map[string]bool{ // {{{
"mark2_foreground": true,
"mark3_background": true,
"mark3_foreground": true,
"second_transparent_bg": true,
"selection_background": true,
"selection_foreground": true,
"tab_bar_background": true,