Add support for in-band window resize notifications

Fixes #7642
This commit is contained in:
Kovid Goyal 2024-07-18 20:32:33 +05:30
parent b17c2dd06b
commit 06b5eff6e6
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
8 changed files with 38 additions and 3 deletions

View file

@ -92,6 +92,8 @@ Detailed list of changes
- Splits layout: Fix the ``move_to_screen_edge`` action breaking when only a single window is present (:iss:`7621`)
- Add support for in-band window resize notifications (:iss:`7642`)
0.35.2 [2024-06-22]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View file

@ -1131,6 +1131,7 @@ class Screen:
historybuf: HistoryBuf
linebuf: LineBuf
in_bracketed_paste_mode: bool
in_band_resize_notification: bool
cursor_visible: bool
scrolled_by: int
cursor: Cursor

View file

@ -85,5 +85,8 @@
// Pending updates mode
#define PENDING_UPDATE (2026 << 5)
// In-band resize notification mode
#define INBAND_RESIZE_NOTIFICATION (2048 << 5)
// Handle Ctrl-C/Ctrl-Z mode
#define HANDLE_TERMIOS_SIGNALS (19997 << 5)

View file

@ -1153,6 +1153,12 @@ set_mode_from_const(Screen *self, unsigned int mode, bool val) {
log_error("Pending mode change to already current mode (%d) requested. Either pending mode expired or there is an application bug.", val);
}
break;
case INBAND_RESIZE_NOTIFICATION:
if (val != self->modes.mINBAND_RESIZE_NOTIFICATION) {
self->modes.mINBAND_RESIZE_NOTIFICATION = val;
if (val) CALLBACK("notify_child_of_resize", NULL);
}
break;
default:
private = mode >= 1 << 5;
if (private) mode >>= 5;
@ -1662,6 +1668,7 @@ copy_specific_mode(Screen *self, unsigned int mode, const ScreenModes *src, Scre
SIMPLE_MODE(DECARM)
SIMPLE_MODE(BRACKETED_PASTE)
SIMPLE_MODE(FOCUS_TRACKING)
SIMPLE_MODE(INBAND_RESIZE_NOTIFICATION)
SIMPLE_MODE(DECCKM)
SIMPLE_MODE(DECTCEM)
SIMPLE_MODE(DECAWM)
@ -1702,6 +1709,7 @@ copy_specific_modes(Screen *self, const ScreenModes *src, ScreenModes *dest) {
copy_specific_mode(self, DECARM, src, dest);
copy_specific_mode(self, BRACKETED_PASTE, src, dest);
copy_specific_mode(self, FOCUS_TRACKING, src, dest);
copy_specific_mode(self, INBAND_RESIZE_NOTIFICATION, src, dest);
copy_specific_mode(self, DECCKM, src, dest);
copy_specific_mode(self, DECTCEM, src, dest);
copy_specific_mode(self, DECAWM, src, dest);
@ -2196,6 +2204,7 @@ report_mode_status(Screen *self, unsigned int which, bool private) {
KNOWN_MODE(DECCKM);
KNOWN_MODE(BRACKETED_PASTE);
KNOWN_MODE(FOCUS_TRACKING);
KNOWN_MODE(INBAND_RESIZE_NOTIFICATION);
#undef KNOWN_MODE
case ALTERNATE_SCREEN:
ans = self->linebuf == self->alt_linebuf ? 1 : 2; break;
@ -3830,6 +3839,7 @@ WRAP0(clear_scrollback)
MODE_GETSET(in_bracketed_paste_mode, BRACKETED_PASTE)
MODE_GETSET(focus_tracking_enabled, FOCUS_TRACKING)
MODE_GETSET(in_band_resize_notification, INBAND_RESIZE_NOTIFICATION)
MODE_GETSET(auto_repeat_enabled, DECARM)
MODE_GETSET(cursor_visible, DECTCEM)
MODE_GETSET(cursor_key_mode, DECCKM)
@ -4852,6 +4862,7 @@ static PyGetSetDef getsetters[] = {
GETSET(in_bracketed_paste_mode)
GETSET(auto_repeat_enabled)
GETSET(focus_tracking_enabled)
GETSET(in_band_resize_notification)
GETSET(cursor_visible)
GETSET(cursor_key_mode)
GETSET(disable_ligatures)

View file

@ -14,7 +14,7 @@ typedef enum ScrollTypes { SCROLL_LINE = -999999, SCROLL_PAGE, SCROLL_FULL } Scr
typedef struct {
bool mLNM, mIRM, mDECTCEM, mDECSCNM, mDECOM, mDECAWM, mDECCOLM, mDECARM, mDECCKM,
mBRACKETED_PASTE, mFOCUS_TRACKING, mDECSACE, mHANDLE_TERMIOS_SIGNALS;
mBRACKETED_PASTE, mFOCUS_TRACKING, mDECSACE, mHANDLE_TERMIOS_SIGNALS, mINBAND_RESIZE_NOTIFICATION;
MouseTrackingMode mouse_tracking_mode;
MouseTrackingProtocol mouse_tracking_protocol;
} ScreenModes;

View file

@ -45,6 +45,7 @@
CURSOR_BEAM,
CURSOR_BLOCK,
CURSOR_UNDERLINE,
ESC_CSI,
ESC_DCS,
ESC_OSC,
GLFW_MOD_CONTROL,
@ -862,6 +863,8 @@ def set_geometry(self, new_geometry: WindowGeometry) -> None:
boss = get_boss()
boss.child_monitor.resize_pty(self.id, *current_pty_size)
self.last_resized_at = monotonic()
self.last_reported_pty_size = current_pty_size
self.notify_child_of_resize()
if not self.child_is_launched:
self.child.mark_terminal_ready()
self.child_is_launched = True
@ -871,7 +874,6 @@ def set_geometry(self, new_geometry: WindowGeometry) -> None:
print(f'[{now:.3f}] Child launched', file=sys.stderr)
elif boss.args.debug_rendering:
print(f'[{monotonic():.3f}] SIGWINCH sent to child in window: {self.id} with size: {current_pty_size}', file=sys.stderr)
self.last_reported_pty_size = current_pty_size
else:
mark_os_window_dirty(self.os_window_id)
@ -1222,6 +1224,10 @@ def report_notification_activated(self, identifier: str) -> None:
identifier = sanitize_identifier_pat().sub('', identifier)
self.screen.send_escape_code_to_child(ESC_OSC, f'99;i={identifier};')
def notify_child_of_resize(self) -> None:
pty_size = self.last_reported_pty_size
if pty_size[0] > -1 and self.screen.in_band_resize_notification:
self.screen.send_escape_code_to_child(ESC_CSI, f'48;{pty_size[0]};{pty_size[1]};{pty_size[3]};{pty_size[2]}t')
def set_dynamic_color(self, code: int, value: Union[str, bytes, memoryview] = '') -> None:
if isinstance(value, (bytes, memoryview)):

View file

@ -50,6 +50,9 @@ def __init__(self, pty=None) -> None:
def write(self, data) -> None:
self.wtcbuf += bytes(data)
def notify_child_of_resize(self):
self.num_of_resize_events += 1
def title_changed(self, data, is_base64=False) -> None:
self.titlebuf.append(process_title_from_child(data, is_base64, ''))
@ -106,6 +109,7 @@ def clear(self) -> None:
self.last_cmd_exit_status = sys.maxsize
self.last_cmd_cmdline = ''
self.last_cmd_at = 0
self.num_of_resize_events = 0
def on_bell(self) -> None:
self.bell_count += 1

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
from kitty.fast_data_types import DECAWM, DECCOLM, DECOM, IRM, VT_PARSER_BUFFER_SIZE, Cursor
from kitty.fast_data_types import DECAWM, DECCOLM, DECOM, IRM, VT_PARSER_BUFFER_SIZE, Cursor, ESC_CSI
from kitty.marks import marker_from_function, marker_from_regex
from kitty.window import pagerhist
@ -304,6 +304,14 @@ def test_resize(self):
s.draw('x' * len(str(s.line(1))))
s.resize(s.lines, s.columns + 4)
self.ae(str(s.linebuf), 'xxx\nxx\nbb\n\n')
s = self.create_screen()
c = s.callbacks
parse_bytes(s, b'\x1b[?2048$p') # ]
self.ae(c.wtcbuf, b'\x1b[?2048;2$y') # ]
c.clear()
parse_bytes(s, b'\x1b[?2048h\x1b[?2048$p') # ]
self.ae(c.wtcbuf, b'\x1b[?2048;1$y') # ]
self.ae(c.num_of_resize_events, 1)
def test_cursor_after_resize(self):