From 09e16adf8b71d287490b68c3284d2059e933bd9e Mon Sep 17 00:00:00 2001 From: Jackie Li Date: Mon, 13 Oct 2025 20:07:47 +0100 Subject: [PATCH] Add draw_borders_when_focused option Adds a new configuration option `draw_borders_when_focused` that allows borders to be drawn around windows when the OS window is focused, regardless of the layout or number of windows. This is useful for: - Seeing which terminal window has focus in layouts like stack that normally don't show borders - Showing borders even when there's only a single window - Providing visual feedback about OS window focus state When enabled, borders use the active_border_color when the OS window is focused and inactive_border_color when unfocused. This behavior only applies when the option is enabled, preserving existing behavior by default. The implementation: - Adds the draw_borders_when_focused config option (default: no) - Modifies border drawing logic to check OS window focus state - Triggers border redraw when OS window focus changes - Only affects behavior when the option is explicitly enabled --- kitty/borders.py | 7 +++++-- kitty/boss.py | 5 +++++ kitty/options/definition.py | 11 +++++++++++ kitty/options/parse.py | 3 +++ kitty/options/types.py | 2 ++ kitty/tabs.py | 9 ++++++++- 6 files changed, 34 insertions(+), 3 deletions(-) diff --git a/kitty/borders.py b/kitty/borders.py index 6f305621c..d2f351c3e 100644 --- a/kitty/borders.py +++ b/kitty/borders.py @@ -6,7 +6,7 @@ from enum import IntFlag from functools import partial from typing import NamedTuple -from .fast_data_types import BORDERS_PROGRAM, get_options, init_borders_program, set_borders_rects +from .fast_data_types import BORDERS_PROGRAM, current_focused_os_window_id, get_options, init_borders_program, set_borders_rects from .shaders import program_for from .typing_compat import LayoutType from .utils import color_as_int @@ -95,13 +95,16 @@ class Borders: bw = groups[0].effective_border() draw_borders = bw > 0 and draw_window_borders active_group = all_windows.active_group + # Check if this OS window is focused (only if draw_borders_when_focused is enabled) + os_window_focused = current_focused_os_window_id() == self.os_window_id if opts.draw_borders_when_focused else True for i, wg in enumerate(groups): 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 - if wg is active_group and draw_active_borders: + # Only check OS window focus if draw_borders_when_focused is enabled + if wg is active_group and draw_active_borders and os_window_focused: color = BorderColor.active else: color = BorderColor.bell if wg.needs_attention else BorderColor.inactive diff --git a/kitty/boss.py b/kitty/boss.py index a586bbd33..bc9d4d47c 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -1854,6 +1854,11 @@ class Boss: if is_macos and focused: cocoa_set_menubar_title(w.title or '') tm.mark_tab_bar_dirty() + # Redraw borders when focus changes if draw_borders_when_focused is enabled + # This ensures borders change color based on OS window focus state + opts = get_options() + if opts.draw_borders_when_focused and tm.active_tab is not None: + tm.active_tab.relayout_borders() def on_activity_since_last_focus(self, window: Window) -> None: os_window_id = window.os_window_id diff --git a/kitty/options/definition.py b/kitty/options/definition.py index e0064a57f..ece41e2b2 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -1224,6 +1224,17 @@ drawn. ''' ) +opt('draw_borders_when_focused', 'no', + option_type='to_bool', + long_text=''' +Draw borders around windows when the OS window is focused, regardless of the +layout or number of windows. This allows you to see which terminal window has +focus even in layouts like stack that normally don't show borders, or when +there is only a single window. The border color will change to the active color +when focused and inactive color when unfocused. +''' + ) + opt('window_margin_width', '0', option_type='edge_width', long_text=''' diff --git a/kitty/options/parse.py b/kitty/options/parse.py index a74377f9f..75eb3250a 100644 --- a/kitty/options/parse.py +++ b/kitty/options/parse.py @@ -971,6 +971,9 @@ class Parser: def draw_minimal_borders(self, val: str, ans: dict[str, typing.Any]) -> None: ans['draw_minimal_borders'] = to_bool(val) + def draw_borders_when_focused(self, val: str, ans: dict[str, typing.Any]) -> None: + ans['draw_borders_when_focused'] = to_bool(val) + def dynamic_background_opacity(self, val: str, ans: dict[str, typing.Any]) -> None: ans['dynamic_background_opacity'] = to_bool(val) diff --git a/kitty/options/types.py b/kitty/options/types.py index b29c955fd..194bc1a2a 100644 --- a/kitty/options/types.py +++ b/kitty/options/types.py @@ -345,6 +345,7 @@ option_names = ( 'detect_urls', 'dim_opacity', 'disable_ligatures', + 'draw_borders_when_focused', 'draw_minimal_borders', 'dynamic_background_opacity', 'editor', @@ -543,6 +544,7 @@ class Options: detect_urls: bool = True dim_opacity: float = 0.4 disable_ligatures: int = 0 + draw_borders_when_focused: bool = False draw_minimal_borders: bool = True dynamic_background_opacity: bool = False editor: str = '.' diff --git a/kitty/tabs.py b/kitty/tabs.py index f7d4b9bb2..6aafa6634 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -418,10 +418,17 @@ class Tab: # {{{ tm = self.tab_manager_ref() if tm is not None: ly = self.current_layout + opts = get_options() + # Draw borders if: normal conditions OR new option enabled (regardless of focus) + # When unfocused, borders will automatically use inactive_border_color + draw_borders = ( + (ly.needs_window_borders and self.windows.num_visble_groups > 1) or ly.must_draw_borders + or opts.draw_borders_when_focused + ) self.borders( all_windows=self.windows, current_layout=ly, tab_bar_rects=tm.tab_bar_rects, - draw_window_borders=(ly.needs_window_borders and self.windows.num_visble_groups > 1) or ly.must_draw_borders + draw_window_borders=draw_borders ) def create_layout_object(self, name: str) -> Layout: