Compare commits

...

23 Commits

Author SHA1 Message Date
Kovid Goyal
c127517c96 version 0.36.4 2024-09-27 10:20:49 +05:30
Kovid Goyal
f8bdc3d86b Fix #7904 2024-09-26 21:53:37 +05:30
Kovid Goyal
6834e366be macOS: Fix a regression in the previous release that caused junk to be rendered in font previews in the choose fonts kitten and crash on Intel macs
Fixes #7892
2024-09-26 01:44:14 +05:30
Kovid Goyal
866c14249b fix minor memory leak in buffers 2024-09-26 01:32:36 +05:30
Kovid Goyal
271665dbaf Temporary workaround for CVDisplayLink deprecation
Apple simply cannot get its APIs right on the first try, ever.
2024-09-26 01:13:10 +05:30
Kovid Goyal
0a7812ab20 Add nil check in send 2024-09-26 00:55:45 +05:30
Kovid Goyal
6f93fb22a4 Update changelog 2024-09-26 00:52:36 +05:30
Kovid Goyal
f48bcb1a17 Wayland GNOME: Fix a crash when using multiple monitors with different scales and starting on or moving to the monitor with lower scale
Fucking GNOME and its fucking lack of support for SSD. How much of my
life I have wasted on these nincompoops.

Fixes #7894
2024-09-26 00:49:04 +05:30
Kovid Goyal
216b6e2e8c Bell color should also not be rendered translucent 2024-09-26 00:29:20 +05:30
Kovid Goyal
cb13233606 Proper fix for always drawing borders opaque 2024-09-26 00:08:36 +05:30
Kovid Goyal
b854589761 Revert "Fix a regression when tinting of background images was introduced that caused window borders to have background_opacity applied to them"
This reverts commit 33e4a0f9cc.
Fixes #7895
2024-09-25 23:28:38 +05:30
Kovid Goyal
44039baa39 version 0.36.3 2024-09-25 09:34:28 +05:30
Kovid Goyal
100f472a08 Clarify docs 2024-09-24 20:15:39 +05:30
Kovid Goyal
126fca0224 ... 2024-09-24 19:34:56 +05:30
Kovid Goyal
dc9eefe050 ... 2024-09-24 19:17:27 +05:30
Kovid Goyal
c1fb18a6ef Implement changing transparent background colors via remote control 2024-09-24 19:02:13 +05:30
Kovid Goyal
c3130419a7 Implement dynamic control of transparent background colors via escape code
Still have to implement it via remote control
2024-09-24 19:02:13 +05:30
Kovid Goyal
dbfeb8d6a4 Store transparent colors on ColorProfile
This will eventually allow them to be changed using remote control and
escape codes.
2024-09-24 19:02:13 +05:30
Kovid Goyal
6ca187c42c Replace the second_transparent_bg option
This is backwards incompatible, but only for a feature released 3 weeks
ago.
2024-09-24 19:02:13 +05:30
Kovid Goyal
e78e86572e Prepare for allowing upto seven additional semi-transparent background colors 2024-09-24 19:02:13 +05:30
Kovid Goyal
d02b1c0b31 ... 2024-09-24 14:38:11 +05:30
Kovid Goyal
83c989d4e9 Splits layout: Allow setting the split_axis option to auto so that all new windows have their split axis chosen automatically unless explicitly specified in the launch command
Fixes #7887
2024-09-24 14:21:23 +05:30
Kovid Goyal
958ad0d8b4 Remote control: Fix --match=state:self not working 2024-09-24 14:09:27 +05:30
34 changed files with 400 additions and 141 deletions

View File

@@ -74,9 +74,21 @@ consumption to do the same tasks.
Detailed list of changes
-------------------------------------
0.36.3 [future]
0.36.4 [2024-09-27]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Fix a regression in the previous release that caused window padding to be rendered opaque even when :opt:`background_opacity` is less than 1 (:iss:`7895`)
- Wayland GNOME: Fix a crash when using multiple monitors with different scales and starting on or moving to the monitor with lower scale (:iss:`7894`)
- macOS: Fix a regression in the previous release that caused junk to be rendered in font previews in the choose fonts kitten and crash on Intel macs (:iss:`7892`)
0.36.3 [2024-09-25]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- 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
@@ -91,6 +103,10 @@ Detailed list of changes
- kitten @ ls: Fix the ``--self`` flag not working (:iss:`7864`)
- Remote control: Fix ``--match state:self`` not working (:disc:`7886`)
- Splits layout: Allow setting the ``split_axis`` option to ``auto`` so that all new windows have their split axis chosen automatically unless explicitly specified in the launch command (:iss:`7887`)
0.36.2 [2024-09-06]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -132,7 +148,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

@@ -184,11 +184,13 @@ in a split using the ``rotate`` action with an argument of ``180`` and rotate
and swap with an argument of ``270``.
This layout takes one option, ``split_axis`` that controls whether new windows
are placed into vertical or horizontal splits when a :option:`--location <launch
--location>` is not specified. A value of ``horizontal`` (same as
``--location=vsplit``) means when a new split is created the two windows will be
placed side by side and a value of ``vertical`` (same as ``--location=hsplit``)
means the two windows will be placed one on top of the other. By default::
are placed into vertical or horizontal splits when a :option:`--location
<launch --location>` is not specified. A value of ``horizontal`` (same as
``--location=vsplit``) means when a new split is created the two windows will
be placed side by side and a value of ``vertical`` (same as
``--location=hsplit``) means the two windows will be placed one on top of the
other. A value of ``auto`` means the axis of the split is chosen automatically
(same as ``--location=split``). By default::
enabled_layouts splits:split_axis=horizontal

View File

@@ -27,6 +27,7 @@
// It is fine to use C99 in this file because it will not be built with VS
//========================================================================
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#include "internal.h"
#include <stdlib.h>

View File

@@ -26,6 +26,7 @@
// It is fine to use C99 in this file because it will not be built with VS
//========================================================================
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#include "../kitty/monotonic.h"
#include "glfw3.h"
#include "internal.h"

View File

@@ -468,10 +468,8 @@ render_shadows(_GLFWwindow *window) {
static bool
create_shm_buffers(_GLFWwindow* window) {
const double scale = _glfwWaylandWindowScale(window);
decs.mapping.size = 0;
#define bp(which, width, height) decs.mapping.size += init_buffer_pair(&decs.which.buffer, width, height, scale);
#define bp(which, width, height) decs.mapping.size += init_buffer_pair(&decs.which.buffer, width, height, decs.for_window_state.fscale);
bp(titlebar, window->wl.width, decs.metrics.visible_titlebar_height);
bp(shadow_top, window->wl.width, decs.metrics.width);
bp(shadow_bottom, window->wl.width, decs.metrics.width);
@@ -504,7 +502,7 @@ create_shm_buffers(_GLFWwindow* window) {
wl_shm_pool_destroy(pool);
render_title_bar(window, true);
render_shadows(window);
debug("Created decoration buffers at scale: %f\n", scale);
debug("Created decoration buffers at scale: %f\n", decs.for_window_state.fscale);
return true;
}
@@ -577,10 +575,11 @@ ensure_csd_resources(_GLFWwindow *window) {
if (!window_is_csd_capable(window)) return false;
const bool is_focused = window->id == _glfw.focusedWindowId;
const bool focus_changed = is_focused != decs.for_window_state.focused;
const double current_scale = _glfwWaylandWindowScale(window);
const bool size_changed = (
decs.for_window_state.width != window->wl.width ||
decs.for_window_state.height != window->wl.height ||
decs.for_window_state.fscale != _glfwWaylandWindowScale(window) ||
decs.for_window_state.fscale != current_scale ||
!decs.mapping.data
);
const bool state_changed = decs.for_window_state.toplevel_states != window->wl.current.toplevel_states;
@@ -589,6 +588,7 @@ ensure_csd_resources(_GLFWwindow *window) {
decs.for_window_state.width, decs.for_window_state.height, window->wl.width, window->wl.height, needs_update,
size_changed, state_changed, decs.buffer_destroyed);
if (!needs_update) return false;
decs.for_window_state.fscale = current_scale; // used in create_shm_buffers
if (size_changed || decs.buffer_destroyed) {
free_csd_buffers(window);
if (!create_shm_buffers(window)) return false;
@@ -618,7 +618,6 @@ ensure_csd_resources(_GLFWwindow *window) {
decs.for_window_state.width = window->wl.width;
decs.for_window_state.height = window->wl.height;
decs.for_window_state.fscale = _glfwWaylandWindowScale(window);
decs.for_window_state.focused = is_focused;
decs.for_window_state.toplevel_states = window->wl.current.toplevel_states;
return true;

View File

@@ -65,6 +65,9 @@ func (k *kitty_font_backend_type) start() (err error) {
var kitty_font_backend kitty_font_backend_type
func (k *kitty_font_backend_type) send(v any) error {
if k.to == nil {
return fmt.Errorf("Trying to send data when to pipe is nil")
}
data, err := json.Marshal(v)
if err != nil {
return fmt.Errorf("Could not encode message to kitty with error: %w", err)

View File

@@ -92,7 +92,8 @@ available or broken, using an alternate interpreter can be useful.
opt('remote_dir', '.local/share/kitty-ssh-kitten', long_text='''
The location on the remote host where the files needed for this kitten are
installed. Relative paths are resolved with respect to :code:`$HOME`.
installed. Relative paths are resolved with respect to :code:`$HOME`. Absolute
paths have their leading / removed and so are also resolved with respect to $HOME.
''')
opt('+copy', '', add_to_default=False, ctype='CopyInstruction', long_text=f'''

View File

@@ -1,5 +1,6 @@
uniform uvec2 viewport;
uniform uint colors[9];
uniform float background_opacity;
uniform float tint_opacity, tint_premult;
uniform float gamma_lut[256];
in vec4 rect; // left, top, right, bottom
@@ -41,7 +42,11 @@ void main() {
float is_window_bg = is_integer_value(rc, 3.);
float is_default_bg = is_integer_value(rc, 0.);
color3 = is_window_bg * window_bg + (1. - is_window_bg) * color3;
float final_opacity = is_default_bg * tint_opacity + (1. - is_default_bg);
float final_premult_opacity = is_default_bg * tint_premult + (1. - is_default_bg);
// Border must be always drawn opaque
float is_border_bg = 1. - step(0.5, abs((float(rc) - 2.) * (float(rc) - 1.) * (float(rc) - 4.))); // 1 if rc in (1, 2, 4) else 0
float final_opacity = is_default_bg * tint_opacity + (1. - is_default_bg) * background_opacity;
final_opacity = is_border_bg + (1. - is_border_bg) * final_opacity;
float final_premult_opacity = is_default_bg * tint_premult + (1. - is_default_bg) * background_opacity;
final_premult_opacity = is_border_bg + (1. - is_border_bg) * final_premult_opacity;
color = vec4(color3 * final_premult_opacity, final_opacity);
}

View File

@@ -2617,7 +2617,7 @@ class Boss:
window.screen.disable_ligatures = strategy
window.refresh()
def patch_colors(self, spec: dict[str, Optional[int]], configured: bool = False) -> None:
def patch_colors(self, spec: dict[str, Optional[int]], transparent_background_colors: tuple[tuple[Color, float], ...], configured: bool = False) -> None:
opts = get_options()
if configured:
for k, v in spec.items():
@@ -2627,6 +2627,7 @@ class Boss:
setattr(opts, k, None)
else:
setattr(opts, k, color_from_int(v))
opts.transparent_background_colors = transparent_background_colors
for tm in self.all_tab_managers:
tm.tab_bar.patch_colors(spec)
tm.tab_bar.layout()

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

@@ -60,6 +60,17 @@ create_256_color_table(void) {
return ans;
}
static void
set_transparent_background_colors(TransparentDynamicColor *dest, PyObject *src) {
memset(dest, 0, sizeof(((ColorProfile*)0)->configured_transparent_colors));
for (Py_ssize_t i = 0; i < MIN(PyTuple_GET_SIZE(src), (Py_ssize_t)arraysz(((ColorProfile*)0)->configured_transparent_colors)); i++) {
PyObject *e = PyTuple_GET_ITEM(src, i);
dest[i].color = ((Color*)(PyTuple_GET_ITEM(e, 0)))->color.val & 0xffffff;
dest[i].opacity = (float)PyFloat_AsDouble(PyTuple_GET_ITEM(e, 1));
dest[i].is_set = true;
}
}
static bool
set_configured_colors(ColorProfile *self, PyObject *opts) {
#define n(which, attr) { \
@@ -80,9 +91,12 @@ 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;
RAII_PyObject(src, PyObject_GetAttrString(opts, "transparent_background_colors"));
if (!src) { PyErr_SetString(PyExc_TypeError, "No transparent_background_colors on opts object"); return false; }
set_transparent_background_colors(self->configured_transparent_colors, src);
return PyErr_Occurred() ? false : true;
}
static bool
@@ -162,6 +176,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;
}
@@ -193,8 +209,8 @@ patch_color_table(const char *key, PyObject *profiles, PyObject *spec, size_t wh
static PyObject*
patch_color_profiles(PyObject *module UNUSED, PyObject *args) {
PyObject *spec, *profiles, *v; ColorProfile *self; int change_configured;
if (!PyArg_ParseTuple(args, "O!O!p", &PyDict_Type, &spec, &PyTuple_Type, &profiles, &change_configured)) return NULL;
PyObject *spec, *transparent_background_colors, *profiles, *v; ColorProfile *self; int change_configured;
if (!PyArg_ParseTuple(args, "O!O!O!p", &PyDict_Type, &spec, &PyTuple_Type, &transparent_background_colors, &PyTuple_Type, &profiles, &change_configured)) return NULL;
char key[32] = {0};
for (size_t i = 0; i < arraysz(FG_BG_256); i++) {
snprintf(key, sizeof(key) - 1, "color%zu", i);
@@ -226,12 +242,35 @@ 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
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(profiles); i++) {
self = (ColorProfile*)PyTuple_GET_ITEM(profiles, i);
set_transparent_background_colors(self->overriden_transparent_colors, transparent_background_colors);
if (change_configured) set_transparent_background_colors(self->configured_transparent_colors, transparent_background_colors);
}
if (PyErr_Occurred()) return NULL;
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) {
@@ -264,20 +303,21 @@ colorprofile_to_color_with_fallback(ColorProfile *self, DynamicColor entry, Dyna
}
return entry.rgb;
}
static Color* alloc_color(unsigned char r, unsigned char g, unsigned char b, unsigned a);
static PyObject*
as_dict(ColorProfile *self, PyObject *args UNUSED) {
#define as_dict_doc "Return all colors as a dictionary of color_name to integer or None (names are the same as used in kitty.conf)"
PyObject *ans = PyDict_New();
RAII_PyObject(ans, PyDict_New());
if (ans == NULL) return PyErr_NoMemory();
for (unsigned i = 0; i < arraysz(self->color_table); i++) {
static char buf[32] = {0};
snprintf(buf, sizeof(buf) - 1, "color%u", i);
PyObject *val = PyLong_FromUnsignedLong(self->color_table[i]);
if (!val) { Py_CLEAR(ans); return PyErr_NoMemory(); }
if (!val) { return PyErr_NoMemory(); }
int ret = PyDict_SetItemString(ans, buf, val);
Py_CLEAR(val);
if (ret != 0) { Py_CLEAR(ans); return NULL; }
if (ret != 0) { return NULL; }
}
#define D(attr, name) { \
if (self->overridden.attr.type != COLOR_NOT_SET) { \
@@ -288,17 +328,33 @@ as_dict(ColorProfile *self, PyObject *args UNUSED) {
color_type c = colorprofile_to_color(self, self->overridden.attr, self->configured.attr).rgb; \
val = PyLong_FromUnsignedLong(c); \
} \
if (!val) { Py_CLEAR(ans); return NULL; } \
if (!val) { return NULL; } \
ret = PyDict_SetItemString(ans, #name, val); \
Py_CLEAR(val); \
if (ret != 0) { Py_CLEAR(ans); return NULL; } \
if (ret != 0) { return NULL; } \
}}
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);
RAII_PyObject(transparent_background_colors, PyList_New(0));
if (!transparent_background_colors) return NULL;
for (size_t i = 0; i < arraysz(self->overriden_transparent_colors); i++) {
TransparentDynamicColor *c = NULL;
if (self->overriden_transparent_colors[i].is_set) c = self->overriden_transparent_colors + i;
else if (self->configured_transparent_colors[i].is_set) c = self->configured_transparent_colors + i;
if (c) {
RAII_PyObject(t, Py_BuildValue("Nf", alloc_color((c->color >> 16) & 0xff, (c->color >> 8) & 0xff, c->color & 0xff, 0), c->opacity));
if (!t) return NULL;
if (PyList_Append(transparent_background_colors, t) != 0) return NULL;
}
}
if (PyList_GET_SIZE(transparent_background_colors)) {
RAII_PyObject(t, PyList_AsTuple(transparent_background_colors));
if (!t) return NULL;
if (PyDict_SetItemString(ans, "transparent_background_colors", t) != 0) return NULL;
}
#undef D
return ans;
return Py_NewRef(ans);
}
static PyObject*
@@ -373,6 +429,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 +439,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
@@ -445,7 +504,6 @@ default_color_table(PyObject *self UNUSED, PyObject *args UNUSED) {
// Boilerplate {{{
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); \
@@ -477,7 +535,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 +545,6 @@ static PyGetSetDef cp_getsetters[] = {
GETSET(highlight_fg)
GETSET(highlight_bg)
GETSET(visual_bell_color)
GETSET(second_transparent_bg)
{NULL} /* Sentinel */
};
@@ -508,6 +564,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 +601,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 +640,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 +654,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

@@ -23,7 +23,7 @@ class Version(NamedTuple):
appname: str = 'kitty'
kitty_face = '🐱'
version: Version = Version(0, 36, 2)
version: Version = Version(0, 36, 4)
str_version: str = '.'.join(map(str, version))
_plat = sys.platform.lower()
is_macos: bool = 'darwin' in _plat

View File

@@ -744,6 +744,7 @@ ensure_render_space(size_t width, size_t height, size_t num_glyphs) {
}
if (buffers.sz < num_glyphs) {
buffers.sz = MAX(128, num_glyphs * 2);
free(buffers.boxes); free(buffers.glyphs); free(buffers.positions);
buffers.boxes = calloc(sizeof(buffers.boxes[0]), buffers.sz);
buffers.glyphs = calloc(sizeof(buffers.glyphs[0]), buffers.sz);
buffers.positions = calloc(sizeof(buffers.positions[0]), buffers.sz);
@@ -812,13 +813,13 @@ render_sample_text(CTFace *self, PyObject *args) {
if (!PyArg_ParseTuple(args, "Ukk|k", &ptext, &canvas_width, &canvas_height, &fg)) return NULL;
unsigned int cell_width, cell_height, baseline, underline_position, underline_thickness, strikethrough_position, strikethrough_thickness;
cell_metrics((PyObject*)self, &cell_width, &cell_height, &baseline, &underline_position, &underline_thickness, &strikethrough_position, &strikethrough_thickness);
RAII_PyObject(pbuf, PyBytes_FromStringAndSize(NULL, sizeof(pixel) * canvas_width * canvas_height));
if (!pbuf) return NULL;
memset(PyBytes_AS_STRING(pbuf), 0, PyBytes_GET_SIZE(pbuf));
if (!cell_width || !cell_height) return Py_BuildValue("OII", pbuf, cell_width, cell_height);
if (!cell_width || !cell_height) return Py_BuildValue("yII", "", cell_width, cell_height);
size_t num_chars = PyUnicode_GET_LENGTH(ptext);
int num_chars_per_line = canvas_width / cell_width, num_of_lines = (int)ceil((float)num_chars / (float)num_chars_per_line);
canvas_height = MIN(canvas_height, num_of_lines * cell_height);
RAII_PyObject(pbuf, PyBytes_FromStringAndSize(NULL, sizeof(pixel) * canvas_width * canvas_height));
if (!pbuf) return NULL;
memset(PyBytes_AS_STRING(pbuf), 0, PyBytes_GET_SIZE(pbuf));
__attribute__((cleanup(destroy_hb_buffer))) hb_buffer_t *hb_buffer = hb_buffer_create();
if (!hb_buffer_pre_allocate(hb_buffer, 4*num_chars)) { PyErr_NoMemory(); return NULL; }
@@ -857,9 +858,10 @@ render_sample_text(CTFace *self, PyObject *args) {
render_glyphs(font, canvas_width, canvas_height, baseline, num_glyphs);
uint8_t r = (fg >> 16) & 0xff, g = (fg >> 8) & 0xff, b = fg & 0xff;
const uint8_t *last_pixel = (uint8_t*)PyBytes_AS_STRING(pbuf) + PyBytes_GET_SIZE(pbuf) - sizeof(pixel);
const uint8_t *s_limit = buffers.render_buf + canvas_width * canvas_height;
for (
uint8_t *p = (uint8_t*)PyBytes_AS_STRING(pbuf), *s = buffers.render_buf;
p <= last_pixel;
p <= last_pixel && s < s_limit;
p += sizeof(pixel), s++
) {
p[0] = r; p[1] = g; p[2] = b; p[3] = s[0];

View File

@@ -17,6 +17,20 @@
#include <sys/mman.h>
#include <structmember.h>
#ifdef LIBRESSL_VERSION_NUMBER
/* from: https://github.com/libressl/portable/blob/master/include/compat/string.h#L63 */
#define explicit_bzero libressl_explicit_bzero
void explicit_bzero(void *, size_t);
/* from: https://github.com/libressl/portable/blob/master/crypto/compat/freezero.c */
void
freezero(void *ptr, size_t sz) {
if (ptr == NULL) return;
explicit_bzero(ptr, sz);
free(ptr);
}
#define OPENSSL_clear_free freezero
#endif
#define SHA1_DIGEST_LENGTH SHA_DIGEST_LENGTH
typedef enum HASH_ALGORITHM { SHA1_HASH, SHA224_HASH, SHA256_HASH, SHA384_HASH, SHA512_HASH } HASH_ALGORITHM;

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,14 +809,9 @@ 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]]:
def as_dict(self) -> Dict[str, int | None | tuple[tuple[Color, float], ...]]:
pass
def as_color(self, val: int) -> Optional[Color]:
@@ -833,9 +828,13 @@ 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
spec: Dict[str, Optional[int]], transparent_background_colors: tuple[tuple[Color, float], ...],
profiles: Tuple[ColorProfile, ...], change_configured: bool
) -> None:
pass

View File

@@ -478,9 +478,9 @@ class LaunchKwds(TypedDict):
def apply_colors(window: Window, spec: Sequence[str]) -> None:
from kitty.rc.set_colors import parse_colors
colors = parse_colors(spec)
colors, transparent_background_colors = parse_colors(spec)
profiles = window.screen.color_profile,
patch_color_profiles(colors, profiles, True)
patch_color_profiles(colors, transparent_background_colors, profiles, True)
def parse_var(defn: Iterable[str]) -> Iterator[tuple[str, str]]:

View File

@@ -423,10 +423,14 @@ class Pair:
class SplitsLayoutOpts(LayoutOpts):
default_axis_is_horizontal: bool = True
default_axis_is_horizontal: Optional[bool] = True
def __init__(self, data: Dict[str, str]):
self.default_axis_is_horizontal = data.get('split_axis', 'horizontal') == 'horizontal'
q = data.get('split_axis', 'horizontal')
if q == 'auto':
self.default_axis_is_horizontal = None
else:
self.default_axis_is_horizontal = q == 'horizontal'
def serialized(self) -> Dict[str, Any]:
return {'default_axis_is_horizontal': self.default_axis_is_horizontal}
@@ -439,14 +443,17 @@ class Splits(Layout):
no_minimal_window_borders = True
@property
def default_axis_is_horizontal(self) -> bool:
def default_axis_is_horizontal(self) -> Optional[bool]:
return self.layout_opts.default_axis_is_horizontal
@property
def pairs_root(self) -> Pair:
root: Optional[Pair] = getattr(self, '_pairs_root', None)
if root is None:
self._pairs_root = root = Pair(horizontal=self.default_axis_is_horizontal)
horizontal = self.default_axis_is_horizontal
if horizontal is None:
horizontal = True
self._pairs_root = root = Pair(horizontal=horizontal)
return root
@pairs_root.setter
@@ -508,7 +515,7 @@ class Splits(Layout):
group_id = ag.id
pair = self.pairs_root.pair_for_window(group_id)
if pair is not None:
if location == 'split':
if location == 'split' or horizontal is None:
wwidth = aw.geometry.right - aw.geometry.left
wheight = aw.geometry.bottom - aw.geometry.top
horizontal = wwidth >= wheight

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

@@ -398,7 +398,7 @@ class RemoteCommand:
window = window or boss.active_window
windows = [window] if window else []
if payload_get(window_match_name):
windows = list(boss.match_windows(payload_get(window_match_name)))
windows = list(boss.match_windows(payload_get(window_match_name), window))
if not windows:
raise MatchError(payload_get(window_match_name))
if payload_get(tab_match_name):

View File

@@ -49,7 +49,7 @@ configured colors.
for k, v in windows[0].current_colors.items():
if v is None:
ans.pop(k, None)
else:
elif isinstance(v, int):
ans[k] = color_from_int(v)
tab = windows[0].tabref()
tm = None if tab is None else tab.tab_manager_ref()

View File

@@ -194,7 +194,7 @@ on bracketed paste mode.
def response_from_kitty(self, boss: Boss, window: Optional[Window], payload_get: PayloadGetType) -> ResponseType:
sid = payload_get('session_id', '')
windows = self.windows_for_payload(boss, None, payload_get, window_match_name='match')
windows = self.windows_for_payload(boss, window, payload_get, window_match_name='match')
pdata: str = payload_get('data')
encoding, _, q = pdata.partition(':')
session = ''

View File

@@ -27,16 +27,19 @@ if TYPE_CHECKING:
from kitty.cli_stub import SetColorsRCOptions as CLIOptions
def parse_colors(args: Iterable[str]) -> Dict[str, Optional[int]]:
def parse_colors(args: Iterable[str]) -> tuple[Dict[str, Optional[int]], tuple[tuple[Color, float], ...]]:
from kitty.options.types import nullable_colors
colors: Dict[str, Optional[Color]] = {}
nullable_color_map: Dict[str, Optional[int]] = {}
transparent_background_colors = ()
for spec in args:
if '=' in spec:
colors.update(parse_config((spec.replace('=', ' '),)))
conf = parse_config((spec.replace('=', ' '),))
else:
with open(os.path.expanduser(spec), encoding='utf-8', errors='replace') as f:
colors.update(parse_config(f))
conf = parse_config(f)
transparent_background_colors = conf.pop('transparent_background_colors', ())
colors.update(conf)
for k in nullable_colors:
q = colors.pop(k, False)
if q is not False:
@@ -44,13 +47,13 @@ def parse_colors(args: Iterable[str]) -> Dict[str, Optional[int]]:
nullable_color_map[k] = val
ans: Dict[str, Optional[int]] = {k: int(v) for k, v in colors.items() if isinstance(v, Color)}
ans.update(nullable_color_map)
return ans
return ans, transparent_background_colors
class SetColors(RemoteCommand):
protocol_spec = __doc__ = '''
colors+/dict.colors: An object mapping names to colors as 24-bit RGB integers or null for nullable colors
colors+/dict.colors: An object mapping names to colors as 24-bit RGB integers or null for nullable colors. Or a string for transparent_background_colors.
match_window/str: Window to change colors in
match_tab/str: Tab to change colors in
all/bool: Boolean indicating change colors everywhere or not
@@ -88,14 +91,18 @@ this option, any color arguments are ignored and :option:`kitten @ set-colors --
completion=RemoteCommand.CompletionSpec.from_string('type:file group:"CONF files", ext:conf'))
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
final_colors: Dict[str, Optional[int]] = {}
final_colors: Dict[str, int | None | str] = {}
transparent_background_colors: tuple[tuple[Color, float], ...] = ()
if not opts.reset:
try:
final_colors = parse_colors(args)
fc, transparent_background_colors = parse_colors(args)
except FileNotFoundError as err:
raise ParsingOfArgsFailed(f'The colors configuration file {emph(err.filename)} was not found.') from err
except Exception as err:
raise ParsingOfArgsFailed(str(err)) from err
final_colors.update(fc)
if transparent_background_colors:
final_colors['transparent_background_colors'] = ' '.join(f'{c.as_sharp}@{f}' for c, f in transparent_background_colors)
ans = {
'match_window': opts.match, 'match_tab': opts.match_tab,
'all': opts.all or opts.reset, 'configured': opts.configured or opts.reset,
@@ -105,12 +112,18 @@ this option, any color arguments are ignored and :option:`kitten @ set-colors --
def response_from_kitty(self, boss: Boss, window: Optional[Window], payload_get: PayloadGetType) -> ResponseType:
windows = self.windows_for_payload(boss, window, payload_get)
colors: Dict[str, Optional[int]] = payload_get('colors')
colors: Dict[str, int | None] = payload_get('colors')
tbc = colors.get('transparent_background_colors')
if payload_get('reset'):
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'))
if tbc:
from kitty.options.utils import transparent_background_colors
parsed_tbc = transparent_background_colors(str(tbc))
else:
parsed_tbc = ()
patch_color_profiles(colors, parsed_tbc, profiles, payload_get('configured'))
boss.patch_colors(colors, parsed_tbc, payload_get('configured'))
default_bg_changed = 'background' in colors
for w in windows:
if w:

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);
@@ -1082,13 +1087,15 @@ create_border_vao(void) {
void
draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_buf, bool rect_data_is_dirty, uint32_t viewport_width, uint32_t viewport_height, color_type active_window_bg, unsigned int num_visible_windows, bool all_windows_have_same_bg, OSWindow *w) {
float tint_opacity = w->is_semi_transparent ? w->background_opacity: 1.0f;
float tint_premult = tint_opacity;
float background_opacity = w->is_semi_transparent ? w->background_opacity: 1.0f;
float tint_opacity = background_opacity;
float tint_premult = background_opacity;
if (has_bgimage(w)) {
glEnable(GL_BLEND);
BLEND_ONTO_OPAQUE;
draw_background_image(w);
BLEND_ONTO_OPAQUE;
background_opacity = 1.0f;
tint_opacity = OPT(background_tint) * OPT(background_tint_gaps);
tint_premult = w->is_semi_transparent ? OPT(background_tint) : 1.0f;
}
@@ -1109,6 +1116,7 @@ draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_bu
w->tab_bar_edge_color.left, w->tab_bar_edge_color.right
};
glUniform1uiv(border_program_layout.uniforms.colors, arraysz(colors), colors);
glUniform1f(border_program_layout.uniforms.background_opacity, background_opacity);
glUniform1f(border_program_layout.uniforms.tint_opacity, tint_opacity);
glUniform1f(border_program_layout.uniforms.tint_premult, tint_premult);
glUniform2ui(border_program_layout.uniforms.viewport, viewport_width, viewport_height);

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))
@@ -774,7 +805,7 @@ class Window:
return tab.overlay_parent(self)
@property
def current_colors(self) -> dict[str, Optional[int]]:
def current_colors(self) -> dict[str, Union[int, None, tuple[tuple[Color, float], ...]]]:
return self.screen.color_profile.as_dict()
@property

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
}
@@ -36,6 +35,8 @@ func set_color_in_color_map(key, val string, ans map[string]any, check_nullable,
return fmt.Errorf("The color %s cannot be set to none", key)
}
ans[key] = nil
} else if key == "transparent_background_colors" {
ans[key] = val
} else {
col, err := style.ParseColor(val)
if err != nil {

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,