From 96d5c9c7c6a5228ed14ce79bcdbc4585d0269acf Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 12 Mar 2025 11:41:27 +0530 Subject: [PATCH] A new option to clear selections when they no longer reflect the contents of the clipboard --- docs/changelog.rst | 2 ++ kittens/hints/main.py | 2 ++ kitty/boss.py | 16 +++++++++++++++- kitty/launch.py | 2 ++ kitty/options/definition.py | 11 +++++++++++ kitty/options/parse.py | 3 +++ kitty/options/types.py | 2 ++ 7 files changed, 37 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index b995b739d..763dd71d8 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -99,6 +99,8 @@ Detailed list of changes - Do not count background processes by default for :opt:`confirm_os_window_close` (:iss:`8358`) +- A new option :opt:`clear_selection_on_clipboard_loss` to clear selections when they no longer reflect the contents of the clipboard + - Fix flickering of hyperlink underline when client program continuously redraws on mouse movement (:iss:`8414`) diff --git a/kittens/hints/main.py b/kittens/hints/main.py index b037eeb59..b655de616 100644 --- a/kittens/hints/main.py +++ b/kittens/hints/main.py @@ -296,8 +296,10 @@ def linenum_handle_result(args: list[str], data: dict[str, Any], target_window_i w.paste_bytes(text) elif program == '@': set_clipboard_string(text) + boss.handle_clipboard_loss('clipboard') elif program == '*': set_primary_selection(text) + boss.handle_clipboard_loss('primary') elif program.startswith('@'): boss.set_clipboard_buffer(program[1:], text) else: diff --git a/kitty/boss.py b/kitty/boss.py index 0994a7ac9..f7f92f60e 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -2243,6 +2243,7 @@ class Boss: text = w.text_for_selection() if text: set_primary_selection(text) + self.handle_clipboard_loss('primary', w.id) if get_options().copy_on_select: self.copy_to_buffer(get_options().copy_on_select) @@ -2280,8 +2281,10 @@ class Boss: if text: if buffer_name == 'clipboard': set_clipboard_string(text) + self.handle_clipboard_loss('clipboard', w.id) elif buffer_name == 'primary': set_primary_selection(text) + self.handle_clipboard_loss('primary', w.id) else: self.set_clipboard_buffer(buffer_name, text) @@ -2508,8 +2511,10 @@ class Boss: if stdin: if dest == 'clipboard': set_clipboard_string(stdin) + self.handle_clipboard_loss('clipboard') else: set_primary_selection(stdin) + self.handle_clipboard_loss('primary') else: env, stdin = self.process_stdin_source(stdin=source, window=window) self.run_background_process(cmd, cwd_from=cwd_from, stdin=stdin, env=env) @@ -3062,6 +3067,7 @@ class Boss: if w is not None: output = debug_config(get_options(), self.mappings.global_shortcuts) set_clipboard_string(re.sub(r'\x1b.+?m', '', output)) + self.handle_clipboard_loss('clipboard') output += '\n\x1b[35mThis debug output has been copied to the clipboard\x1b[m' # ]]] self.display_scrollback(w, output, title=_('Current kitty options'), report_cursor=False) @@ -3112,4 +3118,12 @@ class Boss: cocoa_show_progress_bar_on_dock_icon() def on_clipboard_lost(self, which: Literal['clipboard', 'primary']) -> None: - pass + self.handle_clipboard_loss(which) + + def handle_clipboard_loss(self, which: Literal['clipboard', 'primary'], exception: int = 0) -> None: + opts = get_options() + if opts.clear_selection_on_clipboard_loss and (which == 'primary' or opts.copy_on_select == 'clipboard'): + for wid, window in self.window_id_map.items(): + if wid == exception: + continue + window.screen.clear_selection() diff --git a/kitty/launch.py b/kitty/launch.py index 3e71d83d3..2cc35aacc 100644 --- a/kitty/launch.py +++ b/kitty/launch.py @@ -709,8 +709,10 @@ def _launch( if stdin is not None: if opts.type == 'clipboard': set_clipboard_string(stdin) + boss.handle_clipboard_loss('clipboard') else: set_primary_selection(stdin) + boss.handle_clipboard_loss('primary') else: kw['hold'] = opts.hold if force_target_tab and target_tab is not None: diff --git a/kitty/options/definition.py b/kitty/options/definition.py index c30b1d577..dc4585b70 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -603,6 +603,17 @@ clipboard. ''' ) +opt('clear_selection_on_clipboard_loss', 'no', option_type='to_bool', long_text=''' +When the contents of the clipboard no longer reflect the current selection, clear it. +This is primarily useful on platforms such as Linux where selecting text automatically +copies it to a special "primary selection" clipboard or if you have :opt:`copy_on_select` +set to :code:`clipboard`. + +Note that on macOS the system does not provide notifications when the clipboard owner +is changed, so there, copying to clipboard in a non-kitty application will not clear +selections even if :opt:`copy_on_select` is enabled. +''') + opt('paste_actions', 'quote-urls-at-prompt,confirm', option_type='paste_actions', long_text=''' diff --git a/kitty/options/parse.py b/kitty/options/parse.py index 4a3248166..5ef090a26 100644 --- a/kitty/options/parse.py +++ b/kitty/options/parse.py @@ -119,6 +119,9 @@ class Parser: def clear_all_shortcuts(self, val: str, ans: dict[str, typing.Any]) -> None: clear_all_shortcuts(val, ans) + def clear_selection_on_clipboard_loss(self, val: str, ans: dict[str, typing.Any]) -> None: + ans['clear_selection_on_clipboard_loss'] = to_bool(val) + def click_interval(self, val: str, ans: dict[str, typing.Any]) -> None: ans['click_interval'] = float(val) diff --git a/kitty/options/types.py b/kitty/options/types.py index d27bf8525..0909b44e6 100644 --- a/kitty/options/types.py +++ b/kitty/options/types.py @@ -63,6 +63,7 @@ option_names = ( 'box_drawing_scale', 'clear_all_mouse_actions', 'clear_all_shortcuts', + 'clear_selection_on_clipboard_loss', 'click_interval', 'clipboard_control', 'clipboard_max_size', @@ -500,6 +501,7 @@ class Options: box_drawing_scale: tuple[float, float, float, float] = (0.001, 1.0, 1.5, 2.0) clear_all_mouse_actions: bool = False clear_all_shortcuts: bool = False + clear_selection_on_clipboard_loss: bool = False click_interval: float = -1.0 clipboard_control: tuple[str, ...] = ('write-clipboard', 'write-primary', 'read-clipboard-ask', 'read-primary-ask') clipboard_max_size: float = 512.0