From eabddc287043083e25f57b236df7f0c9883760a5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 7 Jul 2025 08:45:58 +0530 Subject: [PATCH] Report support for OSC52 write to clipboard in DA1 There are apparently some applications that want to only turn on OSC52 if they can be sure the terminal supports it. https://github.com/contour-terminal/vt-extensions/blob/master/clipboard-extension.md Seems harmless enough, though IMO the correct query mechanism for runtime controllable settings is XTGETTCAP, but, let's be a good citizen and co-operate. The overhead is not too large and I have more important windmills to tilt at. Fixes #8788 --- kitty/screen.c | 2 +- kitty/window.py | 12 ++++++++++++ kitty_tests/__init__.py | 6 +++++- kitty_tests/screen.py | 9 +++++++++ 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/kitty/screen.c b/kitty/screen.c index dcff61c10..61b838870 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -2647,7 +2647,7 @@ report_device_attributes(Screen *self, unsigned int mode, char start_modifier) { if (mode == 0) { switch(start_modifier) { case 0: - write_escape_code_to_child(self, ESC_CSI, "?62;c"); + CALLBACK("on_da1", NULL); break; case '>': write_escape_code_to_child(self, ESC_CSI, ">1;" xstr(PRIMARY_VERSION) ";" xstr(SECONDARY_VERSION) "c"); // VT-220 + primary version + secondary version diff --git a/kitty/window.py b/kitty/window.py index 7771bc92b..951a77803 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -88,6 +88,7 @@ from .fast_data_types import ( wakeup_main_loop, ) from .keys import keyboard_mode_name, mod_mask +from .options.types import Options from .progress import Progress from .rgb import to_color from .terminfo import get_capabilities @@ -544,6 +545,14 @@ def color_control(cp: ColorProfile, code: int, value: str | bytes | memoryview = return '' +def da1(opts: Options) -> str: + ans = '?62;' + if 'write-clipboard' in opts.clipboard_control: + # see https://github.com/contour-terminal/vt-extensions/blob/master/clipboard-extension.md + ans += '52;' + return ans + 'c' + + class EdgeWidths: left: float | None top: float | None @@ -1279,6 +1288,9 @@ class Window: return True return False + def on_da1(self) -> None: + self.screen.send_escape_code_to_child(ESC_CSI, da1(get_options())) + def on_bell(self) -> None: cb = get_options().command_on_bell if cb and cb != ['none']: diff --git a/kitty_tests/__init__.py b/kitty_tests/__init__.py index efab07d2c..1ceb489f8 100644 --- a/kitty_tests/__init__.py +++ b/kitty_tests/__init__.py @@ -24,7 +24,7 @@ from kitty.options.types import Options, defaults from kitty.rgb import to_color from kitty.types import MouseEvent from kitty.utils import read_screen_size -from kitty.window import decode_cmdline, process_remote_print, process_title_from_child +from kitty.window import da1, decode_cmdline, process_remote_print, process_title_from_child def parse_bytes(screen, data, dump_callback=None): @@ -132,10 +132,14 @@ class Callbacks: self.last_cmd_cmdline = '' self.last_cmd_at = 0 self.num_of_resize_events = 0 + self.da1 = [] def on_bell(self) -> None: self.bell_count += 1 + def on_da1(self) -> None: + self.da1.append(da1(get_options())) + def on_activity_since_last_focus(self) -> None: pass diff --git a/kitty_tests/screen.py b/kitty_tests/screen.py index 01f51de81..881dd0cad 100644 --- a/kitty_tests/screen.py +++ b/kitty_tests/screen.py @@ -407,6 +407,15 @@ class TestScreen(BaseTest): parse_bytes(s, b'\x1b[?2048h') # ] self.ae(c.num_of_resize_events, 2) + def test_da1(self): + s = self.create_screen() + parse_bytes(s, b'\x1b[c\x1b[0c') # ]] + self.ae(s.callbacks.da1, ['?62;52;c', '?62;52;c']) # ]] + s.callbacks.clear() + self.create_screen(options={'clipboard_control': 'read-clipboard'}) + parse_bytes(s, b'\x1b[c') # ]] + self.ae(s.callbacks.da1, ['?62;c']) # ]] + def test_cursor_after_resize(self): def draw(text, end_line=True):