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
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`) - 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] 0.35.2 [2024-06-22]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

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

View File

@@ -85,5 +85,8 @@
// Pending updates mode // Pending updates mode
#define PENDING_UPDATE (2026 << 5) #define PENDING_UPDATE (2026 << 5)
// In-band resize notification mode
#define INBAND_RESIZE_NOTIFICATION (2048 << 5)
// Handle Ctrl-C/Ctrl-Z mode // Handle Ctrl-C/Ctrl-Z mode
#define HANDLE_TERMIOS_SIGNALS (19997 << 5) #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); log_error("Pending mode change to already current mode (%d) requested. Either pending mode expired or there is an application bug.", val);
} }
break; 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: default:
private = mode >= 1 << 5; private = mode >= 1 << 5;
if (private) mode >>= 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(DECARM)
SIMPLE_MODE(BRACKETED_PASTE) SIMPLE_MODE(BRACKETED_PASTE)
SIMPLE_MODE(FOCUS_TRACKING) SIMPLE_MODE(FOCUS_TRACKING)
SIMPLE_MODE(INBAND_RESIZE_NOTIFICATION)
SIMPLE_MODE(DECCKM) SIMPLE_MODE(DECCKM)
SIMPLE_MODE(DECTCEM) SIMPLE_MODE(DECTCEM)
SIMPLE_MODE(DECAWM) 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, DECARM, src, dest);
copy_specific_mode(self, BRACKETED_PASTE, src, dest); copy_specific_mode(self, BRACKETED_PASTE, src, dest);
copy_specific_mode(self, FOCUS_TRACKING, 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, DECCKM, src, dest);
copy_specific_mode(self, DECTCEM, src, dest); copy_specific_mode(self, DECTCEM, src, dest);
copy_specific_mode(self, DECAWM, 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(DECCKM);
KNOWN_MODE(BRACKETED_PASTE); KNOWN_MODE(BRACKETED_PASTE);
KNOWN_MODE(FOCUS_TRACKING); KNOWN_MODE(FOCUS_TRACKING);
KNOWN_MODE(INBAND_RESIZE_NOTIFICATION);
#undef KNOWN_MODE #undef KNOWN_MODE
case ALTERNATE_SCREEN: case ALTERNATE_SCREEN:
ans = self->linebuf == self->alt_linebuf ? 1 : 2; break; 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(in_bracketed_paste_mode, BRACKETED_PASTE)
MODE_GETSET(focus_tracking_enabled, FOCUS_TRACKING) MODE_GETSET(focus_tracking_enabled, FOCUS_TRACKING)
MODE_GETSET(in_band_resize_notification, INBAND_RESIZE_NOTIFICATION)
MODE_GETSET(auto_repeat_enabled, DECARM) MODE_GETSET(auto_repeat_enabled, DECARM)
MODE_GETSET(cursor_visible, DECTCEM) MODE_GETSET(cursor_visible, DECTCEM)
MODE_GETSET(cursor_key_mode, DECCKM) MODE_GETSET(cursor_key_mode, DECCKM)
@@ -4852,6 +4862,7 @@ static PyGetSetDef getsetters[] = {
GETSET(in_bracketed_paste_mode) GETSET(in_bracketed_paste_mode)
GETSET(auto_repeat_enabled) GETSET(auto_repeat_enabled)
GETSET(focus_tracking_enabled) GETSET(focus_tracking_enabled)
GETSET(in_band_resize_notification)
GETSET(cursor_visible) GETSET(cursor_visible)
GETSET(cursor_key_mode) GETSET(cursor_key_mode)
GETSET(disable_ligatures) GETSET(disable_ligatures)

View File

@@ -14,7 +14,7 @@ typedef enum ScrollTypes { SCROLL_LINE = -999999, SCROLL_PAGE, SCROLL_FULL } Scr
typedef struct { typedef struct {
bool mLNM, mIRM, mDECTCEM, mDECSCNM, mDECOM, mDECAWM, mDECCOLM, mDECARM, mDECCKM, 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; MouseTrackingMode mouse_tracking_mode;
MouseTrackingProtocol mouse_tracking_protocol; MouseTrackingProtocol mouse_tracking_protocol;
} ScreenModes; } ScreenModes;

View File

@@ -45,6 +45,7 @@ from .fast_data_types import (
CURSOR_BEAM, CURSOR_BEAM,
CURSOR_BLOCK, CURSOR_BLOCK,
CURSOR_UNDERLINE, CURSOR_UNDERLINE,
ESC_CSI,
ESC_DCS, ESC_DCS,
ESC_OSC, ESC_OSC,
GLFW_MOD_CONTROL, GLFW_MOD_CONTROL,
@@ -862,6 +863,8 @@ class Window:
boss = get_boss() boss = get_boss()
boss.child_monitor.resize_pty(self.id, *current_pty_size) boss.child_monitor.resize_pty(self.id, *current_pty_size)
self.last_resized_at = monotonic() self.last_resized_at = monotonic()
self.last_reported_pty_size = current_pty_size
self.notify_child_of_resize()
if not self.child_is_launched: if not self.child_is_launched:
self.child.mark_terminal_ready() self.child.mark_terminal_ready()
self.child_is_launched = True self.child_is_launched = True
@@ -871,7 +874,6 @@ class Window:
print(f'[{now:.3f}] Child launched', file=sys.stderr) print(f'[{now:.3f}] Child launched', file=sys.stderr)
elif boss.args.debug_rendering: 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) 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: else:
mark_os_window_dirty(self.os_window_id) mark_os_window_dirty(self.os_window_id)
@@ -1222,6 +1224,10 @@ class Window:
identifier = sanitize_identifier_pat().sub('', identifier) identifier = sanitize_identifier_pat().sub('', identifier)
self.screen.send_escape_code_to_child(ESC_OSC, f'99;i={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: def set_dynamic_color(self, code: int, value: Union[str, bytes, memoryview] = '') -> None:
if isinstance(value, (bytes, memoryview)): if isinstance(value, (bytes, memoryview)):

View File

@@ -50,6 +50,9 @@ class Callbacks:
def write(self, data) -> None: def write(self, data) -> None:
self.wtcbuf += bytes(data) 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: def title_changed(self, data, is_base64=False) -> None:
self.titlebuf.append(process_title_from_child(data, is_base64, '')) self.titlebuf.append(process_title_from_child(data, is_base64, ''))
@@ -106,6 +109,7 @@ class Callbacks:
self.last_cmd_exit_status = sys.maxsize self.last_cmd_exit_status = sys.maxsize
self.last_cmd_cmdline = '' self.last_cmd_cmdline = ''
self.last_cmd_at = 0 self.last_cmd_at = 0
self.num_of_resize_events = 0
def on_bell(self) -> None: def on_bell(self) -> None:
self.bell_count += 1 self.bell_count += 1

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> # 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.marks import marker_from_function, marker_from_regex
from kitty.window import pagerhist from kitty.window import pagerhist
@@ -304,6 +304,14 @@ class TestScreen(BaseTest):
s.draw('x' * len(str(s.line(1)))) s.draw('x' * len(str(s.line(1))))
s.resize(s.lines, s.columns + 4) s.resize(s.lines, s.columns + 4)
self.ae(str(s.linebuf), 'xxx\nxx\nbb\n\n') 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): def test_cursor_after_resize(self):