A new option second_transparent_bg

Makes a second background color semi-transparent via background_opacity.
Useful for things like cursor line highlight in editors.

Fixes #7646
This commit is contained in:
Kovid Goyal 2024-07-21 20:22:31 +05:30
parent 573058d861
commit 0cf9a79760
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
25 changed files with 293 additions and 314 deletions

View file

@ -78,6 +78,8 @@ 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`)
- Sessions: A new command ``focus_matching_window`` to shift focus to a specific window, useful when creating complex layouts with splits (:disc:`7635`)
- Wayland: Allow fractional scales less than one (:pull:`7549`)

View file

@ -42,7 +42,6 @@ def patch_color_list(path: str, colors: List[str], name: str, spc: str = ' ')
def main(args: List[str]=sys.argv) -> None:
from kitty.options.definition import definition
write_output('kitty', definition)
nullable_colors = []
all_colors = []
for opt in definition.iter_all_options():
@ -52,9 +51,10 @@ def main(args: List[str]=sys.argv) -> None:
all_colors.append(opt.name)
elif opt.parser_func.__name__ in ('to_color', 'titlebar_color', 'macos_titlebar_color'):
all_colors.append(opt.name)
patch_color_list('kitty/rc/set_colors.py', nullable_colors, 'NULLABLE')
patch_color_list('tools/cmd/at/set_colors.go', nullable_colors, 'NULLABLE')
patch_color_list('tools/themes/collection.go', all_colors, 'ALL')
nc = '\n '.join(f'{x!r}' for x in nullable_colors)
write_output('kitty', definition, f'\nnullable_colors = frozenset({{\n {nc}\n}})')
if __name__ == '__main__':

View file

@ -179,14 +179,12 @@ class Foreground(Query):
@staticmethod
def get_result(opts: Options, window_id: int, os_window_id: int) -> str:
from kitty.fast_data_types import Color, get_boss
from kitty.fast_data_types import get_boss, get_options
boss = get_boss()
w = boss.window_id_map.get(window_id)
if w is None:
return opts.foreground.as_sharp
col = w.screen.color_profile.default_fg
r, g, b = col >> 16, (col >> 8) & 0xff, col & 0xff
return Color(r, g, b).as_sharp
return (w.screen.color_profile.default_fg or get_options().foreground).as_sharp
@query
@ -196,15 +194,12 @@ class Background(Query):
@staticmethod
def get_result(opts: Options, window_id: int, os_window_id: int) -> str:
from kitty.fast_data_types import Color, get_boss
from kitty.fast_data_types import get_boss, get_options
boss = get_boss()
w = boss.window_id_map.get(window_id)
if w is None:
return opts.background.as_sharp
col = w.screen.color_profile.default_bg
r, g, b = col >> 16, (col >> 8) & 0xff, col & 0xff
return Color(r, g, b).as_sharp
return (w.screen.color_profile.default_bg or get_options().background).as_sharp
@query

View file

@ -7,6 +7,7 @@ from typing import Iterable, NamedTuple, Sequence
from .fast_data_types import BORDERS_PROGRAM, add_borders_rect, get_options, init_borders_program, os_window_has_background_image
from .shaders import program_for
from .typing import LayoutType
from .utils import color_as_int
from .window_list import WindowGroup, WindowList
@ -96,7 +97,7 @@ class Borders:
active_group = all_windows.active_group
for i, wg in enumerate(groups):
window_bg = wg.default_bg
window_bg = color_as_int(wg.default_bg)
window_bg = (window_bg << 8) | BorderColor.window_bg
if draw_borders and not draw_minimal_borders:
# Draw the border rectangles

View file

@ -122,7 +122,7 @@ from .key_encoding import get_name_to_functional_number_map
from .keys import Mappings
from .layout.base import set_layout_options
from .notify import notification_activated
from .options.types import Options
from .options.types import Options, nullable_colors
from .options.utils import MINIMUM_FONT_SIZE, KeyboardMode, KeyDefinition
from .os_window_size import initial_window_size_func
from .rgb import color_from_int
@ -342,9 +342,9 @@ class Boss:
self.clipboard_buffers: Dict[str, str] = {}
self.update_check_process: Optional['PopenType[bytes]'] = None
self.window_id_map: WeakValueDictionary[int, Window] = WeakValueDictionary()
self.startup_colors = {k: opts[k] for k in opts if isinstance(opts[k], Color)}
self.color_settings_at_startup: Dict[str, Optional[Color]] = {
k: opts[k] for k in opts if isinstance(opts[k], Color) or k in nullable_colors}
self.current_visual_select: Optional[VisualSelect] = None
self.startup_cursor_text_color = opts.cursor_text_color
# A list of events received so far that are potentially part of a sequence keybinding.
self.cached_values = cached_values
self.os_window_map: Dict[int, TabManager] = {}
@ -2627,7 +2627,6 @@ class Boss:
window.refresh()
def patch_colors(self, spec: Dict[str, Optional[int]], configured: bool = False) -> None:
from kitty.rc.set_colors import nullable_colors
opts = get_options()
if configured:
for k, v in spec.items():

View file

@ -6,7 +6,7 @@
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;
uint default_fg, default_bg, highlight_fg, highlight_bg, cursor_fg, cursor_bg, url_color, url_style, inverted;
uint default_fg, default_bg, highlight_fg, highlight_bg, cursor_fg, cursor_bg, url_color, url_style, inverted, second_transparent_bg;
uint xnum, ynum, cursor_fg_sprite_idx;
float cursor_x, cursor_y, cursor_w, cursor_opacity;
@ -194,7 +194,9 @@ void main() {
// }}}
// Background {{{
float cell_has_non_default_bg = step(1, float(abs(bg_as_uint - default_colors[1])));
float cell_has_non_default_bg = step(1, float(abs(
(bg_as_uint - default_colors[1]) * (bg_as_uint - second_transparent_bg)
)));
draw_bg = 1;
#if (PHASE == PHASE_BACKGROUND)

View file

@ -47,7 +47,8 @@ init_FG_BG_table(void) {
}
}
PyObject* create_256_color_table(void) {
static PyObject*
create_256_color_table(void) {
init_FG_BG_table();
PyObject *ans = PyTuple_New(arraysz(FG_BG_256));
if (ans == NULL) return PyErr_NoMemory();
@ -59,21 +60,88 @@ PyObject* create_256_color_table(void) {
return ans;
}
static PyObject *
new_cp(PyTypeObject *type, PyObject UNUSED *args, PyObject UNUSED *kwds) {
ColorProfile *self;
static bool
set_configured_colors(ColorProfile *self, PyObject *opts) {
#define n(which, attr) { \
RAII_PyObject(t, PyObject_GetAttrString(opts, #attr)); \
if (t == NULL) return false; \
if (t == Py_None) { self->configured.which.rgb = 0; self->configured.which.type = COLOR_IS_SPECIAL; } \
else if (PyLong_Check(t)) { \
unsigned int x = PyLong_AsUnsignedLong(t); \
self->configured.which.rgb = x & 0xffffff; \
self->configured.which.type = COLOR_IS_RGB; \
} else if (PyObject_TypeCheck(t, &Color_Type)) { \
Color *c = (Color*)t; \
self->configured.which.rgb = c->color.rgb; \
self->configured.which.type = COLOR_IS_RGB; \
} else { PyErr_SetString(PyExc_TypeError, "colors must be integers or Color objects"); return false; } \
}
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);
#undef n
return true;
}
static bool
set_mark_colors(ColorProfile *self, PyObject *opts) {
char fgattr[] = "mark?_foreground", bgattr[] = "mark?_background";
#define n(i, attr, which) { \
attr[4] = '1' + i; \
RAII_PyObject(t, PyObject_GetAttrString(opts, attr)); \
if (t == NULL) return false; \
if (!PyObject_TypeCheck(t, &Color_Type)) { PyErr_SetString(PyExc_TypeError, "mark color is not Color object"); return false; } \
Color *c = (Color*)t; self->which[i] = c->color.rgb; \
}
#define m(i) n(i, fgattr, mark_foregrounds); n(i, bgattr, mark_backgrounds);
m(0); m(1); m(2);
#undef m
#undef n
return true;
}
static bool
set_colortable(ColorProfile *self, PyObject *opts) {
RAII_PyObject(ct, PyObject_GetAttrString(opts, "color_table"));
if (!ct) return false;
RAII_PyObject(ret, PyObject_CallMethod(ct, "buffer_info", NULL));
if (!ret) return false;
unsigned long *color_table = PyLong_AsVoidPtr(PyTuple_GET_ITEM(ret, 0));
size_t count = PyLong_AsSize_t(PyTuple_GET_ITEM(ret, 1));
if (!color_table || count != arraysz(FG_BG_256)) { PyErr_SetString(PyExc_TypeError, "color_table has incorrect length"); return false; }
RAII_PyObject(r2, PyObject_GetAttrString(ct, "itemsize")); if (!r2) return false;
size_t itemsize = PyLong_AsSize_t(r2);
if (itemsize != sizeof(unsigned long)) { PyErr_Format(PyExc_TypeError, "color_table has incorrect itemsize: %zu", itemsize); return false; }
for (size_t i = 0; i < arraysz(FG_BG_256); i++) self->color_table[i] = color_table[i];
memcpy(self->orig_color_table, self->color_table, arraysz(self->color_table) * sizeof(self->color_table[0]));
return true;
}
static PyObject*
new_cp(PyTypeObject *type, PyObject *args, PyObject *kwds) {
PyObject *opts = global_state.options_object;
ColorProfile *self;
static const char* kw[] = {"opts", NULL};
if (args && !PyArg_ParseTupleAndKeywords(args, kwds, "|O", (char**)kw, &opts)) return NULL;
self = (ColorProfile *)type->tp_alloc(type, 0);
RAII_PyObject(ans, (PyObject*)self);
if (self != NULL) {
init_FG_BG_table();
memcpy(self->color_table, FG_BG_256, sizeof(FG_BG_256));
memcpy(self->orig_color_table, FG_BG_256, sizeof(FG_BG_256));
#define S(which) self->mark_foregrounds[which] = OPT(mark##which##_foreground); self->mark_backgrounds[which] = OPT(mark##which##_background)
S(1); S(2); S(3);
#undef S
if (opts) {
if (!set_configured_colors(self, opts)) return NULL;
if (!set_mark_colors(self, opts)) return NULL;
if (!set_colortable(self, opts)) return NULL;
} else {
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 = true;
Py_INCREF(ans);
}
return (PyObject*) self;
return ans;
}
static void
@ -88,19 +156,6 @@ alloc_color_profile(void) {
}
static PyObject*
update_ansi_color_table(ColorProfile *self, PyObject *val) {
#define update_ansi_color_table_doc "Update the 256 basic colors"
if (!PyLong_Check(val)) { PyErr_SetString(PyExc_TypeError, "color table must be a long"); return NULL; }
unsigned long *color_table = PyLong_AsVoidPtr(val);
for (size_t i = 0; i < arraysz(FG_BG_256); i++) {
self->color_table[i] = color_table[i];
self->orig_color_table[i] = color_table[i];
}
self->dirty = true;
Py_RETURN_NONE;
}
void
copy_color_profile(ColorProfile *dest, ColorProfile *src) {
memcpy(dest->color_table, src->color_table, sizeof(dest->color_table));
@ -171,6 +226,7 @@ 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
Py_RETURN_NONE;
@ -239,7 +295,7 @@ 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(highlight_bg, selection_background); D(visual_bell_color, visual_bell_color); D(second_transparent_bg, second_transparent_bg);
#undef D
return ans;
@ -300,21 +356,6 @@ 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"
unsigned int default_fg, default_bg, cursor_color, cursor_text_color, highlight_fg, highlight_bg, visual_bell_color;
if (!PyArg_ParseTuple(args, "II|IIIII", &default_fg, &default_bg,
&cursor_color, &cursor_text_color, &highlight_fg, &highlight_bg, &visual_bell_color)) 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); S(visual_bell_color);
#undef S
self->dirty = true;
Py_RETURN_NONE;
}
void
copy_color_table_to_buffer(ColorProfile *self, color_type *buf, int offset, size_t stride) {
size_t i;
@ -404,23 +445,39 @@ 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).rgb); } \
static Color* alloc_color(unsigned char r, unsigned char g, unsigned char b, unsigned a);
#define CGETSET(name, nullable) \
static PyObject* name##_get(ColorProfile *self, void UNUSED *closure) { \
DynamicColor ans = colorprofile_to_color(self, self->overridden.name, self->configured.name); \
if (ans.type == COLOR_IS_SPECIAL) { \
if (nullable) Py_RETURN_NONE; \
return (PyObject*)alloc_color(0, 0, 0, 0); \
} \
return (PyObject*)alloc_color((ans.rgb >> 16) & 0xff, (ans.rgb >> 8) & 0xff, ans.rgb & 0xff, 0); \
} \
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; \
if (v == NULL) { self->overridden.name.val = 0; return 0; } \
if (PyLong_Check(v)) { \
unsigned long val = PyLong_AsUnsignedLong(v); \
self->overridden.name.rgb = val & 0xffffff; \
self->overridden.name.type = COLOR_IS_RGB; \
} else if (PyObject_TypeCheck(v, &Color_Type)) { \
Color *c = (Color*)v; self->overridden.name.rgb = c->color.rgb; self->overridden.name.type = COLOR_IS_RGB; \
} else if (v == Py_None) { \
if (!nullable) { PyErr_SetString(PyExc_ValueError, #name " cannot be set to None"); return -1; } \
self->overridden.name.type = COLOR_IS_SPECIAL; self->overridden.name.rgb = 0; \
} \
self->dirty = true; return 0; \
}
CGETSET(default_fg)
CGETSET(default_bg)
CGETSET(cursor_color)
CGETSET(cursor_text_color)
CGETSET(highlight_fg)
CGETSET(highlight_bg)
CGETSET(visual_bell_color)
CGETSET(default_fg, false)
CGETSET(default_bg, false)
CGETSET(cursor_color, true)
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[] = {
@ -431,6 +488,7 @@ static PyGetSetDef cp_getsetters[] = {
GETSET(highlight_fg)
GETSET(highlight_bg)
GETSET(visual_bell_color)
GETSET(second_transparent_bg)
{NULL} /* Sentinel */
};
@ -439,15 +497,25 @@ static PyMemberDef cp_members[] = {
{NULL}
};
static PyObject*
reload_from_opts(ColorProfile *self, PyObject *args UNUSED) {
PyObject *opts = global_state.options_object;
if (!PyArg_ParseTuple(args, "|O", &opts)) return NULL;
self->dirty = true;
if (!set_configured_colors(self, opts)) return NULL;
if (!set_mark_colors(self, opts)) return NULL;
if (!set_colortable(self, opts)) return NULL;
Py_RETURN_NONE;
}
static PyMethodDef cp_methods[] = {
METHOD(update_ansi_color_table, METH_O)
METHOD(reset_color_table, METH_NOARGS)
METHOD(as_dict, 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)
METHODB(reload_from_opts, METH_VARARGS),
{NULL} /* Sentinel */
};
@ -466,19 +534,21 @@ PyTypeObject ColorProfile_Type = {
};
// }}}
static PyObject *
new_color(PyTypeObject *type, PyObject *args, PyObject *kwds) {
static const char* kwlist[] = {"red", "green", "blue", "alpha", NULL};
Color *self;
unsigned char r = 0, g = 0, b = 0, a = 0;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|BBBB", (char**)kwlist, &r, &g, &b, &a)) return NULL;
self = (Color *)type->tp_alloc(type, 0);
static Color*
alloc_color(unsigned char r, unsigned char g, unsigned char b, unsigned a) {
Color *self = (Color *)(&Color_Type)->tp_alloc(&Color_Type, 0);
if (self != NULL) {
self->color.r = r; self->color.g = g; self->color.b = b; self->color.a = a;
}
return (PyObject*) self;
return self;
}
static PyObject *
new_color(PyTypeObject *type UNUSED, PyObject *args, PyObject *kwds) {
static const char* kwlist[] = {"red", "green", "blue", "alpha", NULL};
unsigned char r = 0, g = 0, b = 0, a = 0;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|BBBB", (char**)kwlist, &r, &g, &b, &a)) return NULL;
return (PyObject*) alloc_color(r, g, b, a);
}
static PyObject*
@ -612,7 +682,6 @@ static PyMethodDef module_methods[] = {
{NULL, NULL, 0, NULL} /* Sentinel */
};
int init_ColorProfile(PyObject *module) {\
if (PyType_Ready(&ColorProfile_Type) < 0) return 0;
if (PyModule_AddObject(module, "ColorProfile", (PyObject *)&ColorProfile_Type) != 0) return 0;

View file

@ -435,10 +435,11 @@ def generate_c_conversion(loc: str, ctypes: List[Union[Option, MultiOption]]) ->
return '\n'.join(preamble + ['', ''] + lines)
def write_output(loc: str, defn: Definition) -> None:
def write_output(loc: str, defn: Definition, extra_after_type_defn: str = '') -> None:
cls, tc = generate_class(defn, loc)
with open(os.path.join(*loc.split('.'), 'options', 'types.py'), 'w') as f:
f.write(f'{cls}\n')
f.write(extra_after_type_defn)
with open(os.path.join(*loc.split('.'), 'options', 'parse.py'), 'w') as f:
f.write(f'{tc}\n')
ctypes = []

View file

@ -20,15 +20,6 @@ def option_names_for_completion() -> Tuple[str, ...]:
return option_names
def build_ansi_color_table(opts: Optional[Options] = None) -> int:
if opts is None:
opts = defaults
addr, length = opts.color_table.buffer_info()
if length != 256 or opts.color_table.typecode != 'L':
raise TypeError(f'The color table has incorrect size length: {length} typecode: {opts.color_table.typecode}')
return addr
def atomic_save(data: bytes, path: str) -> None:
import shutil
import tempfile

View file

@ -319,22 +319,15 @@ typedef union DynamicColor {
} DynamicColor;
typedef struct {
DynamicColor default_fg, default_bg, cursor_color, cursor_text_color, highlight_fg, highlight_bg, visual_bell_color;
DynamicColor default_fg, default_bg, cursor_color, cursor_text_color, highlight_fg, highlight_bg, visual_bell_color, second_transparent_bg;
} DynamicColors;
typedef struct {
DynamicColors dynamic_colors;
uint32_t color_table[256];
} ColorStackEntry;
typedef struct {
PyObject_HEAD
bool dirty;
uint32_t color_table[256];
uint32_t orig_color_table[256];
ColorStackEntry *color_stack;
uint32_t color_table[256], orig_color_table[256];
struct { DynamicColors dynamic_colors; uint32_t color_table[256]; } *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];
@ -389,7 +382,6 @@ LineBuf* alloc_linebuf(unsigned int, unsigned int);
HistoryBuf* alloc_historybuf(unsigned int, unsigned int, unsigned int);
ColorProfile* alloc_color_profile(void);
void copy_color_profile(ColorProfile*, ColorProfile*);
PyObject* create_256_color_table(void);
PyObject* parse_bytes_dump(PyObject UNUSED *, PyObject *);
PyObject* parse_bytes(PyObject UNUSED *, PyObject *);
void cursor_reset(Cursor*);

View file

@ -755,8 +755,49 @@ class Color:
class ColorProfile:
default_fg: int
default_bg: int
# The dynamic color properties return the current color value. Use delattr
# to reset them to configured value.
@property
def default_fg(self) -> Color: ...
@default_fg.setter
def default_fg(self, val: Union[int|Color]) -> None: ...
@property
def default_bg(self) -> Color: ...
@default_bg.setter
def default_bg(self, val: Union[int|Color]) -> None: ...
@property
def cursor_color(self) -> Optional[Color]: ...
@cursor_color.setter
def cursor_color(self, val: Union[None|int|Color]) -> None: ...
@property
def cursor_text_color(self) -> Optional[Color]: ...
@cursor_text_color.setter
def cursor_text_color(self, val: Union[None|int|Color]) -> None: ...
@property
def highlight_fg(self) -> Optional[Color]: ...
@highlight_fg.setter
def highlight_fg(self, val: Union[None|int|Color]) -> None: ...
@property
def highlight_bg(self) -> Optional[Color]: ...
@highlight_bg.setter
def highlight_bg(self, val: Union[None|int|Color]) -> None: ...
@property
def visual_bell_color(self) -> Optional[Color]: ...
@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]]:
pass
@ -773,13 +814,7 @@ class ColorProfile:
def reset_color(self, num: int) -> None:
pass
def update_ansi_color_table(self, val: int) -> None:
pass
def set_configured_colors(
self, fg: int, bg: int, cursor: int = 0, cursor_text: int = 0, highlight_fg: int = 0, highlight_bg: int = 0, visual_bell_color: int = 0
) -> None:
pass
def reload_from_opts(self, opts: Optional[Options] = None) -> None: ...
def patch_color_profiles(

View file

@ -1471,7 +1471,8 @@ 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. Be aware that using a value less than 1.0 is a (possibly
launch your editor. See also :opt:`second_transparent_bg`.
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
general color of the desktop background, for best text rendering. If you want
@ -1516,6 +1517,13 @@ 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 background color.
Useful, in more complex UIs like editors where you could want more than a single background color
to be rendered as transparent.
''')
opt('dynamic_background_opacity', 'no',
option_type='to_bool', ctype='bool',
long_text='''
@ -1651,32 +1659,32 @@ opt('color15', '#ffffff',
)
opt('mark1_foreground', 'black',
option_type='to_color', ctype='color_as_int',
option_type='to_color',
long_text='Color for marks of type 1'
)
opt('mark1_background', '#98d3cb',
option_type='to_color', ctype='color_as_int',
option_type='to_color',
long_text='Color for marks of type 1 (light steel blue)'
)
opt('mark2_foreground', 'black',
option_type='to_color', ctype='color_as_int',
option_type='to_color',
long_text='Color for marks of type 2'
)
opt('mark2_background', '#f2dcd3',
option_type='to_color', ctype='color_as_int',
option_type='to_color',
long_text='Color for marks of type 1 (beige)'
)
opt('mark3_foreground', 'black',
option_type='to_color', ctype='color_as_int',
option_type='to_color',
long_text='Color for marks of type 3'
)
opt('mark3_background', '#f274bc',
option_type='to_color', ctype='color_as_int',
option_type='to_color',
long_text='Color for marks of type 3 (violet)'
)

View file

@ -1189,6 +1189,9 @@ 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)

View file

@ -902,84 +902,6 @@ convert_from_opts_dim_opacity(PyObject *py_opts, Options *opts) {
Py_DECREF(ret);
}
static void
convert_from_python_mark1_foreground(PyObject *val, Options *opts) {
opts->mark1_foreground = color_as_int(val);
}
static void
convert_from_opts_mark1_foreground(PyObject *py_opts, Options *opts) {
PyObject *ret = PyObject_GetAttrString(py_opts, "mark1_foreground");
if (ret == NULL) return;
convert_from_python_mark1_foreground(ret, opts);
Py_DECREF(ret);
}
static void
convert_from_python_mark1_background(PyObject *val, Options *opts) {
opts->mark1_background = color_as_int(val);
}
static void
convert_from_opts_mark1_background(PyObject *py_opts, Options *opts) {
PyObject *ret = PyObject_GetAttrString(py_opts, "mark1_background");
if (ret == NULL) return;
convert_from_python_mark1_background(ret, opts);
Py_DECREF(ret);
}
static void
convert_from_python_mark2_foreground(PyObject *val, Options *opts) {
opts->mark2_foreground = color_as_int(val);
}
static void
convert_from_opts_mark2_foreground(PyObject *py_opts, Options *opts) {
PyObject *ret = PyObject_GetAttrString(py_opts, "mark2_foreground");
if (ret == NULL) return;
convert_from_python_mark2_foreground(ret, opts);
Py_DECREF(ret);
}
static void
convert_from_python_mark2_background(PyObject *val, Options *opts) {
opts->mark2_background = color_as_int(val);
}
static void
convert_from_opts_mark2_background(PyObject *py_opts, Options *opts) {
PyObject *ret = PyObject_GetAttrString(py_opts, "mark2_background");
if (ret == NULL) return;
convert_from_python_mark2_background(ret, opts);
Py_DECREF(ret);
}
static void
convert_from_python_mark3_foreground(PyObject *val, Options *opts) {
opts->mark3_foreground = color_as_int(val);
}
static void
convert_from_opts_mark3_foreground(PyObject *py_opts, Options *opts) {
PyObject *ret = PyObject_GetAttrString(py_opts, "mark3_foreground");
if (ret == NULL) return;
convert_from_python_mark3_foreground(ret, opts);
Py_DECREF(ret);
}
static void
convert_from_python_mark3_background(PyObject *val, Options *opts) {
opts->mark3_background = color_as_int(val);
}
static void
convert_from_opts_mark3_background(PyObject *py_opts, Options *opts) {
PyObject *ret = PyObject_GetAttrString(py_opts, "mark3_background");
if (ret == NULL) return;
convert_from_python_mark3_background(ret, opts);
Py_DECREF(ret);
}
static void
convert_from_python_close_on_child_death(PyObject *val, Options *opts) {
opts->close_on_child_death = PyObject_IsTrue(val);
@ -1315,18 +1237,6 @@ convert_opts_from_python_opts(PyObject *py_opts, Options *opts) {
if (PyErr_Occurred()) return false;
convert_from_opts_dim_opacity(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_mark1_foreground(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_mark1_background(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_mark2_foreground(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_mark2_background(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_mark3_foreground(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_mark3_background(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_close_on_child_death(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_allow_hyperlinks(py_opts, opts);

14
kitty/options/types.py generated
View file

@ -408,6 +408,7 @@ option_names = ( # {{{
'scrollback_lines',
'scrollback_pager',
'scrollback_pager_history_size',
'second_transparent_bg',
'select_by_word_characters',
'select_by_word_characters_forward',
'selection_background',
@ -572,6 +573,7 @@ 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)
@ -1022,3 +1024,15 @@ defaults.mouse_map = [
# show_clicked_cmd_output_ungrabbed
MouseMapping(button=1, mods=5, definition='mouse_show_command_output'),
]
nullable_colors = frozenset({
'cursor'
'cursor_text_color'
'visual_bell_color'
'active_border_color'
'tab_bar_background'
'tab_bar_margin_color'
'second_transparent_bg'
'selection_foreground'
'selection_background'
})

View file

@ -27,22 +27,8 @@ if TYPE_CHECKING:
from kitty.cli_stub import SetColorsRCOptions as CLIOptions
nullable_colors = (
# generated by gen-config.py DO NOT edit
# NULLABLE_COLORS_START
'active_border_color',
'cursor',
'cursor_text_color',
'selection_background',
'selection_foreground',
'tab_bar_background',
'tab_bar_margin_color',
'visual_bell_color',
# NULLABLE_COLORS_END
)
def parse_colors(args: Iterable[str]) -> Dict[str, Optional[int]]:
from kitty.options.types import nullable_colors
colors: Dict[str, Optional[Color]] = {}
nullable_color_map: Dict[str, Optional[int]] = {}
for spec in args:
@ -121,8 +107,7 @@ this option, any color arguments are ignored and :option:`kitten @ set-colors --
windows = self.windows_for_payload(boss, window, payload_get)
colors: Dict[str, Optional[int]] = payload_get('colors')
if payload_get('reset'):
colors = {k: int(v) for k, v in boss.startup_colors.items()}
colors['cursor_text_color'] = None if boss.startup_cursor_text_color is None else int(boss.startup_cursor_text_color)
colors = {k: None if v is None else int(v) for k, v in boss.color_settings_at_startup.items()}
profiles = tuple(w.screen.color_profile for w in windows if w)
patch_color_profiles(colors, profiles, payload_get('configured'))
boss.patch_colors(colors, payload_get('configured'))

View file

@ -295,7 +295,7 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, c
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;
GLuint default_fg, default_bg, highlight_fg, highlight_bg, cursor_fg, cursor_bg, url_color, url_style, inverted;
GLuint default_fg, default_bg, highlight_fg, highlight_bg, cursor_fg, cursor_bg, url_color, url_style, inverted, second_transparent_bg;
GLuint xnum, ynum, cursor_fg_sprite_idx;
GLfloat cursor_x, cursor_y, cursor_w, cursor_opacity;
@ -320,6 +320,7 @@ 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;

View file

@ -719,15 +719,14 @@ PYWRAP1(handle_for_window_id) {
return NULL;
}
static PyObject* options_object = NULL;
PYWRAP0(get_options) {
if (!options_object) {
if (!global_state.options_object) {
PyErr_SetString(PyExc_RuntimeError, "Must call set_options() before using get_options()");
return NULL;
}
Py_INCREF(options_object);
return options_object;
Py_INCREF(global_state.options_object);
return global_state.options_object;
}
PYWRAP1(set_options) {
@ -735,7 +734,7 @@ PYWRAP1(set_options) {
int is_wayland = 0, debug_rendering = 0, debug_font_fallback = 0;
PA("O|ppp", &opts, &is_wayland, &debug_rendering, &debug_font_fallback);
if (opts == Py_None) {
Py_CLEAR(options_object);
Py_CLEAR(global_state.options_object);
Py_RETURN_NONE;
}
global_state.is_wayland = is_wayland ? true : false;
@ -746,8 +745,8 @@ PYWRAP1(set_options) {
global_state.debug_rendering = debug_rendering ? true : false;
global_state.debug_font_fallback = debug_font_fallback ? true : false;
if (!convert_opts_from_python_opts(opts, &global_state.opts)) return NULL;
options_object = opts;
Py_INCREF(options_object);
global_state.options_object = opts;
Py_INCREF(global_state.options_object);
Py_RETURN_NONE;
}
@ -1159,8 +1158,6 @@ PYWRAP1(patch_global_colors) {
P(active_border_color); P(inactive_border_color); P(bell_border_color); P(tab_bar_background); P(tab_bar_margin_color);
if (configured) {
P(background); P(url_color);
P(mark1_background); P(mark1_foreground); P(mark2_background); P(mark2_foreground);
P(mark3_background); P(mark3_foreground);
}
if (PyErr_Occurred()) return NULL;
Py_RETURN_NONE;
@ -1461,7 +1458,7 @@ finalize(void) {
#define F(x) free(OPT(x)); OPT(x) = NULL;
F(background_image); F(bell_path); F(bell_theme); F(default_window_logo);
#undef F
Py_CLEAR(options_object);
Py_CLEAR(global_state.options_object);
free_animation(OPT(animation.cursor));
free_animation(OPT(animation.visual_bell));
// we leak the texture here since it is not guaranteed

View file

@ -52,7 +52,6 @@ typedef struct {
char_type *select_by_word_characters;
char_type *select_by_word_characters_forward;
color_type url_color, background, foreground, active_border_color, inactive_border_color, bell_border_color, tab_bar_background, tab_bar_margin_color;
color_type mark1_foreground, mark1_background, mark2_foreground, mark2_background, mark3_foreground, mark3_background;
monotonic_t repaint_delay, input_delay;
bool focus_follows_mouse;
unsigned int hide_window_decorations;
@ -307,6 +306,7 @@ typedef struct {
bool redirect_mouse_handling;
WindowLogoTable *all_window_logos;
int gl_version;
PyObject *options_object;
} GlobalState;
extern GlobalState global_state;

View file

@ -17,7 +17,6 @@ from typing import (
)
from .borders import Border, BorderColor
from .config import build_ansi_color_table
from .constants import config_dir
from .fast_data_types import (
DECAWM,
@ -495,11 +494,8 @@ class TabBar:
self.screen = s = Screen(None, 1, 10, 0, self.cell_width, cell_height)
else:
s = self.screen
s.color_profile.update_ansi_color_table(build_ansi_color_table(opts))
s.color_profile.set_configured_colors(
color_as_int(opts.inactive_tab_foreground),
color_as_int(opts.tab_bar_background or opts.background)
)
s.color_profile.default_fg = opts.inactive_tab_foreground
s.color_profile.default_bg = opts.tab_bar_background or opts.background
sep = opts.tab_separator
self.trailing_spaces = self.leading_spaces = 0
while sep and sep[0] == ' ':
@ -575,8 +571,9 @@ class TabBar:
ifg = color_from_int(fg)
if ifg is not None:
self.draw_data = self.draw_data._replace(inactive_fg=ifg)
self.screen.color_profile.set_configured_colors(fg, bg)
self.screen.color_profile.update_ansi_color_table(build_ansi_color_table(opts))
self.screen.color_profile.reload_from_opts()
self.screen.color_profile.default_fg = color_from_int(fg)
self.screen.color_profile.default_bg = color_from_int(bg)
@property
def current_colors(self) -> Dict[str, Color]:

View file

@ -33,7 +33,6 @@ from typing import (
from .child import ProcessDesc
from .cli_stub import CLIOptions
from .clipboard import ClipboardRequestManager, set_clipboard_string
from .config import build_ansi_color_table
from .constants import (
appname,
clear_handled_signals,
@ -98,7 +97,6 @@ from .notify import (
notify_with_command,
sanitize_identifier_pat,
)
from .options.types import Options
from .rgb import to_color
from .terminfo import get_capabilities
from .types import MouseEvent, OverlayType, WindowGeometry, ac, run_once
@ -407,19 +405,6 @@ def as_text(
def setup_colors(screen: Screen, opts: Options) -> None:
screen.color_profile.update_ansi_color_table(build_ansi_color_table(opts))
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),
s(opts.visual_bell_color)
)
@run_once
def load_paste_filter() -> Callable[[str], str]:
import runpy
@ -605,8 +590,6 @@ class Window:
self.screen: Screen = Screen(self, 24, 80, opts.scrollback_lines, cell_width, cell_height, self.id)
if copy_colors_from is not None:
self.screen.copy_colors_from(copy_colors_from.screen)
else:
setup_colors(self.screen, opts)
self.remote_control_passwords = remote_control_passwords
self.allow_remote_control = allow_remote_control
@ -681,9 +664,8 @@ class Window:
return int(val)
def apply_options(self, is_active: bool) -> None:
opts = get_options()
self.update_effective_padding()
setup_colors(self.screen, opts)
self.screen.color_profile.reload_from_opts()
@property
def title(self) -> str:
@ -1188,33 +1170,12 @@ class Window:
tab.relayout_borders()
tab.on_bell(self)
def change_colors(self, changes: Dict[DynamicColor, Optional[str]]) -> None:
dirtied = default_bg_changed = False
def item(raw: Optional[str]) -> int:
if raw is None:
return 0
v = to_color(raw)
if v is None:
return 0
return 0xff000000 | int(v)
for which, val_ in changes.items():
val = item(val_)
dirtied = True
setattr(self.screen.color_profile, which.name, val)
if which.name == 'default_bg':
default_bg_changed = True
if dirtied:
self.screen.mark_as_dirty()
if default_bg_changed:
get_boss().default_bg_changed_for(self.id)
def color_profile_popped(self, bg_changed: bool) -> None:
if bg_changed:
get_boss().default_bg_changed_for(self.id)
def report_color(self, code: str, r: int, g: int, b: int) -> None:
def report_color(self, code: str, col: Color) -> None:
r, g, b = col.red, col.green, col.blue
r |= r << 8
g |= g << 8
b |= b << 8
@ -1236,19 +1197,33 @@ class Window:
ret = set_pointer_shape(self.screen, value, self.os_window_id)
if ret:
self.screen.send_escape_code_to_child(ESC_OSC, '22:' + ret)
color_changes: Dict[DynamicColor, Optional[str]] = {}
dirtied = default_bg_changed = False
def change(which: DynamicColor, val: str) -> None:
nonlocal dirtied, default_bg_changed
dirtied = True
if which.name == 'default_bg':
default_bg_changed = True
v = to_color(val) if val else None
if v is None:
delattr(self.screen.color_profile, which.name)
else:
setattr(self.screen.color_profile, which.name, v)
for val in value.split(';'):
w = DYNAMIC_COLOR_CODES.get(code)
if w is not None:
if val == '?':
col = getattr(self.screen.color_profile, w.name)
self.report_color(str(code), col >> 16, (col >> 8) & 0xff, col & 0xff)
col = getattr(self.screen.color_profile, w.name) or Color()
self.report_color(str(code), col)
else:
q = None if code >= 100 else val
color_changes[w] = q
q = '' if code >= 100 else val
change(w, q)
code += 1
if color_changes:
self.change_colors(color_changes)
if dirtied:
self.screen.mark_as_dirty()
if default_bg_changed:
get_boss().default_bg_changed_for(self.id)
def set_color_table_color(self, code: int, bvalue: Optional[memoryview] = None) -> None:
value = str(bvalue or b'', 'utf-8', 'replace')
@ -1259,7 +1234,7 @@ class Window:
if val is None: # color query
qc = self.screen.color_profile.as_color((c << 8) | 1)
assert qc is not None
self.report_color(f'4;{c}', qc.red, qc.green, qc.blue)
self.report_color(f'4;{c}', qc)
else:
changed = True
cp.set_color(c, val)

View file

@ -7,6 +7,7 @@ from contextlib import suppress
from itertools import count
from typing import Any, Deque, Dict, Iterator, List, Optional, Tuple, Union
from .fast_data_types import Color, get_options
from .types import OverlayType, WindowGeometry
from .typing import EdgeLiteral, TabType, WindowType
@ -120,23 +121,23 @@ class WindowGroup:
w.set_geometry(geom)
@property
def default_bg(self) -> int:
def default_bg(self) -> Color:
if self.windows:
w: WindowType = self.windows[-1]
return w.screen.color_profile.default_bg
return 0
w = self.windows[-1]
return w.screen.color_profile.default_bg or get_options().background
return get_options().background
@property
def geometry(self) -> Optional[WindowGeometry]:
if self.windows:
w: WindowType = self.windows[-1]
w = self.windows[-1]
return w.geometry
return None
@property
def is_visible_in_layout(self) -> bool:
if self.windows:
w: WindowType = self.windows[-1]
w = self.windows[-1]
return w.is_visible_in_layout
return False

View file

@ -5,7 +5,7 @@ import os
import sys
import tempfile
from kitty.config import build_ansi_color_table, defaults
from kitty.config import defaults
from kitty.fast_data_types import (
Color,
ColorProfile,
@ -477,8 +477,7 @@ class TestDataTypes(BaseTest):
'h://a\u0430b.com/El%20Ni%C3%B1o/'), 'h://xn--ab-7kc.com/El Niño/')
def test_color_profile(self):
c = ColorProfile()
c.update_ansi_color_table(build_ansi_color_table())
c = ColorProfile(defaults)
for i in range(8):
col = getattr(defaults, f'color{i}')
self.ae(c.as_color(i << 8 | 1), col)

View file

@ -15,14 +15,15 @@ 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,
"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,
"second_transparent_bg": 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,6 +309,7 @@ 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,