Custom shaders: Support tinting complete inactive windows

This commit is contained in:
白鸽 2026-09-14 22:41:26 +08:00
parent a54b409785
commit cf33820973
6 changed files with 80 additions and 2 deletions

View file

@ -204,6 +204,9 @@ Detailed list of changes
- Support for :doc:`/custom-shaders` for adding various graphical effects (:iss:`10344`)
- Custom shaders: Allow tinting inactive windows toward a configurable color,
preserving the tab bar and active window padding.
- Various throughput performance improvements for a 15-35% real world improvement depending on workload
- Add :opt:`window_border_radius` for rounded window borders (:pull:`10421`)

View file

@ -79,6 +79,29 @@ mouse shader. In the first group we have the crt shader and its color has been
customised. The second group defines the spotlight shader and specifies exactly
when the spotlight animation should start and stop.
For example, to blend inactive windows toward gray instead of black, save the
following as :file:`shaders/dim-gray.pipeline` in your kitty config directory and
set ``custom_shaders dim-gray`` and ``inactive_text_alpha 1`` in :file:`kitty.conf`::
startgroup
var float HIGHLIGHT_INTENSITY = 0
var float BORDER_WIDTH = 0
var float INACTIVE_DIM = 0.74
var float3 INACTIVE_DIM_COLOR = float3(0.2158605)
var bool DIM_CENTRAL_AREA_ONLY = true
var bool INCLUDE_WINDOW_PADDING = true
shaders focus-highlight
endgroup
This uses the same shader as ``dim-inactive-windows``. ``INACTIVE_DIM`` is the
fraction of the original color retained: ``0.74`` blends in 26% of the target
color. ``INACTIVE_DIM_COLOR`` is in linear RGB; ``0.2158605`` corresponds to
sRGB ``#808080``. The default target is black, preserving the existing dimming
effect. The two optional flags preserve the tab bar and include the active
window's padding in its unchanged area. Explicit cell backgrounds and images
are blended together with text, while transparency and colors reported to
terminal applications are preserved.
Anatomy of a custom shader
----------------------------

View file

@ -2433,6 +2433,7 @@ run_custom_end_shader(OSWindow *os_window, float sx, float sy, monotonic_t now)
float mouse_pos[4];
float mouse_button_pressed[4];
float active_window_geometry[4];
float active_window_padding[4];
float bell_window_geometry[4];
float central_area[4];
float cursor_trail_corners_x[4];
@ -2475,6 +2476,13 @@ run_custom_end_shader(OSWindow *os_window, float sx, float sy, monotonic_t now)
if (t->num_windows) {
Window *w = t->windows + t->active_window;
active_win_geom = w->render_data.geometry;
if (os_window->viewport_width && os_window->viewport_height) {
const float width = (float)os_window->viewport_width, height = (float)os_window->viewport_height;
d->active_window_padding[0] = (float)(w->padding.left + w->size_mismatch_padding.left) / width;
d->active_window_padding[1] = (float)(w->padding.bottom + w->size_mismatch_padding.bottom) / height;
d->active_window_padding[2] = (float)(w->padding.right + w->size_mismatch_padding.right) / width;
d->active_window_padding[3] = (float)(w->padding.top + w->size_mismatch_padding.top) / height;
}
Screen *s = w->render_data.screen;
if (s) {
active_bg = colorprofile_to_color(s->color_profile, s->color_profile->overridden.default_bg, s->color_profile->configured.default_bg).rgb;

View file

@ -16,14 +16,28 @@ static const float HIGHLIGHT_INTENSITY = 0.30;
static const float BORDER_WIDTH = 0.04;
// Peak additive brightness multiplier at the window border edge.
static const float BORDER_GLOW = 0.55;
// Dim factor applied to pixels outside the active window (1.0 = unchanged, 0.0 = black).
// Fraction of the original color retained outside the active window (1.0 = unchanged, 0.0 = blend target).
static const float INACTIVE_DIM = 0.3;
// Blend target in linear RGB; black preserves the original dimming behavior.
static const float3 INACTIVE_DIM_COLOR = float3(0.0);
// Keep the tab bar outside the effect when requested by a pipeline.
static const bool DIM_CENTRAL_AREA_ONLY = false;
// Include the active window's padding in its unchanged/highlighted area.
static const bool INCLUDE_WINDOW_PADDING = false;
public float4
fragment_main(float4 color, KittyTextures t, KittyCustomShaderData d) {
if (DIM_CENTRAL_AREA_ONLY) {
float4 area = d.central_area;
if (any(t.pos < area.xy) || any(t.pos > area.xy + area.zw)) return color;
}
float4 g = d.active_window_geometry;
// Pass through unchanged if no active window geometry is known.
if (g.z <= 0.0 || g.w <= 0.0) return color;
if (INCLUDE_WINDOW_PADDING) {
g.xy -= d.active_window_padding.xy;
g.zw += d.active_window_padding.xy + d.active_window_padding.zw;
}
float2 uv = t.pos;
float2 winMin = g.xy;
@ -48,5 +62,5 @@ fragment_main(float4 color, KittyTextures t, KittyCustomShaderData d) {
}
// Slightly dim inactive areas to draw the eye to the focused window.
float dim = lerp(1.0, INACTIVE_DIM, strength);
return float4(color.rgb * dim, color.a);
return float4(lerp(INACTIVE_DIM_COLOR, color.rgb, dim), color.a);
}

View file

@ -22,6 +22,9 @@ public struct KittyCustomShaderData {
// .xy = lower-left corner of the window, .zw = width and height.
// All components are zero when no active window was rendered this frame.
public float4 active_window_geometry;
// Effective padding of the active window, including cell-size compensation.
// .xy = left/bottom, .zw = right/top, in viewport UV units.
public float4 active_window_padding;
// Geometry of the most recently belled window in UV co-ordinates (origin bottom-left).
// .xy = lower-left corner of the window, .zw = width and height.
// If the belled window is not currently visible in the active tab/layout, equals central_area.

View file

@ -33,6 +33,33 @@ _SUPPORT_SHADER_NAMES = frozenset(('types', 'pipeline'))
class TestSlang(BaseTest):
def test_inactive_window_tint_pipeline(self):
if not shutil.which(slangc()[0]):
self.skipTest(f'slangc ({slangc()[0]}) not found in PATH')
pipeline = parse_pipeline_definition(
"""
startgroup
var float HIGHLIGHT_INTENSITY = 0
var float BORDER_WIDTH = 0
var float INACTIVE_DIM = 0.74
var float3 INACTIVE_DIM_COLOR = float3(0.2158605)
var bool DIM_CENTRAL_AREA_ONLY = true
var bool INCLUDE_WINDOW_PADDING = true
shaders focus-highlight
endgroup
""".splitlines(),
'dim-gray',
)
with tempfile.TemporaryDirectory() as cache_dir:
clear_caches()
try:
vertex, fragment, _ = build_custom_shader_pipeline_glsl(pipeline, cache_dir=cache_dir)
finally:
clear_caches()
self.assertTrue(vertex)
self.assertTrue(fragment)
def test_slang_parser(self):
def check(src: str, expected: SlangFile) -> None:
actual = parse_slang_text(src)