diff --git a/tools/tui/loop/api.go b/tools/tui/loop/api.go index 653ed612a..51396f911 100644 --- a/tools/tui/loop/api.go +++ b/tools/tui/loop/api.go @@ -41,6 +41,7 @@ type Loop struct { controlling_term *tty.Term terminal_options TerminalStateOptions screen_size ScreenSize + seen_inband_resize bool escape_code_parser wcswidth.EscapeCodeParser keep_going bool death_signal unix.Signal diff --git a/tools/tui/loop/run.go b/tools/tui/loop/run.go index fb6ec6a45..db72a7c57 100644 --- a/tools/tui/loop/run.go +++ b/tools/tui/loop/run.go @@ -10,6 +10,7 @@ import ( "io" "os" "os/signal" + "strconv" "strings" "time" @@ -82,17 +83,46 @@ func (self *Loop) update_screen_size() error { return nil } -func (self *Loop) handle_csi(raw []byte) error { +func (self *Loop) handle_csi(raw []byte) (err error) { csi := string(raw) - ke := KeyEventFromCSI(csi) - if ke != nil { - return self.handle_key_event(ke) - } - sz, err := self.ScreenSize() - if err == nil { - me := MouseEventFromCSI(csi, sz) - if me != nil { - return self.handle_mouse_event(me) + if len(csi) > 2 { + if strings.HasSuffix(csi, "t") && strings.HasPrefix(csi, "48;") { + if parts := strings.Split(csi[3:len(csi)-1], ";"); len(parts) > 3 { + var parsed [4]int + ok := true + for i, x := range parts { + x, _, _ = strings.Cut(x, ":") + if parsed[i], err = strconv.Atoi(x); err != nil { + ok = false + break + } + } + if ok { + self.seen_inband_resize = true + old_size := self.screen_size + s := &self.screen_size + s.updated = true + s.HeightCells, s.WidthCells = uint(parsed[0]), uint(parsed[1]) + s.HeightPx, s.WidthPx = uint(parsed[2]), uint(parsed[2]) + s.CellWidth = s.WidthPx / s.WidthCells + s.CellHeight = s.HeightPx / s.HeightCells + if self.OnResize != nil { + return self.OnResize(old_size, self.screen_size) + } + return nil + } + } + } + ke := KeyEventFromCSI(csi) + if ke != nil { + return self.handle_key_event(ke) + } + sz, err := self.ScreenSize() + if err == nil { + me := MouseEventFromCSI(csi, sz) + if me != nil { + return self.handle_mouse_event(me) + } } } if self.OnEscapeCode != nil { @@ -272,6 +302,9 @@ func (self *Loop) on_SIGPIPE() error { } func (self *Loop) on_SIGWINCH() error { + if self.seen_inband_resize { + return nil + } self.screen_size.updated = false if self.OnResize != nil { old_size := self.screen_size @@ -313,6 +346,7 @@ func (self *Loop) run() (err error) { }() self.keep_going = true + self.seen_inband_resize = false self.pending_mouse_events = utils.NewRingBuffer[MouseEvent](4) // tty_write_channel is buffered so there is no race between initial // queueing and startup of writer thread and also as a performance diff --git a/tools/tui/loop/terminal-state.go b/tools/tui/loop/terminal-state.go index 96cf72211..d5ac47d3c 100644 --- a/tools/tui/loop/terminal-state.go +++ b/tools/tui/loop/terminal-state.go @@ -46,26 +46,27 @@ type Mode uint32 const private Mode = 1 << 31 const ( - LNM Mode = 20 - IRM Mode = 4 - DECKM Mode = 1 | private - DECSCNM Mode = 5 | private - DECOM Mode = 6 | private - DECAWM Mode = 7 | private - DECARM Mode = 8 | private - DECTCEM Mode = 25 | private - MOUSE_BUTTON_TRACKING Mode = 1000 | private - MOUSE_MOTION_TRACKING Mode = 1002 | private - MOUSE_MOVE_TRACKING Mode = 1003 | private - FOCUS_TRACKING Mode = 1004 | private - MOUSE_UTF8_MODE Mode = 1005 | private - MOUSE_SGR_MODE Mode = 1006 | private - MOUSE_URXVT_MODE Mode = 1015 | private - MOUSE_SGR_PIXEL_MODE Mode = 1016 | private - ALTERNATE_SCREEN Mode = 1049 | private - BRACKETED_PASTE Mode = 2004 | private - PENDING_UPDATE Mode = 2026 | private - HANDLE_TERMIOS_SIGNALS Mode = kitty.HandleTermiosSignals | private + LNM Mode = 20 + IRM Mode = 4 + DECKM Mode = 1 | private + DECSCNM Mode = 5 | private + DECOM Mode = 6 | private + DECAWM Mode = 7 | private + DECARM Mode = 8 | private + DECTCEM Mode = 25 | private + MOUSE_BUTTON_TRACKING Mode = 1000 | private + MOUSE_MOTION_TRACKING Mode = 1002 | private + MOUSE_MOVE_TRACKING Mode = 1003 | private + FOCUS_TRACKING Mode = 1004 | private + MOUSE_UTF8_MODE Mode = 1005 | private + MOUSE_SGR_MODE Mode = 1006 | private + MOUSE_URXVT_MODE Mode = 1015 | private + MOUSE_SGR_PIXEL_MODE Mode = 1016 | private + ALTERNATE_SCREEN Mode = 1049 | private + BRACKETED_PASTE Mode = 2004 | private + PENDING_UPDATE Mode = 2026 | private + INBAND_RESIZE_NOTIFICATION Mode = 2048 | private + HANDLE_TERMIOS_SIGNALS Mode = kitty.HandleTermiosSignals | private ) func (self Mode) escape_code(which string) string { @@ -127,7 +128,7 @@ func (self *TerminalStateOptions) SetStateEscapeCodes() string { reset_modes(&sb, IRM, DECKM, DECSCNM, BRACKETED_PASTE, FOCUS_TRACKING, MOUSE_BUTTON_TRACKING, MOUSE_MOTION_TRACKING, MOUSE_MOVE_TRACKING, MOUSE_UTF8_MODE, MOUSE_SGR_MODE) - set_modes(&sb, DECARM, DECAWM, DECTCEM) + set_modes(&sb, DECARM, DECAWM, DECTCEM, INBAND_RESIZE_NOTIFICATION) if self.Alternate_screen { set_modes(&sb, ALTERNATE_SCREEN) sb.WriteString(CLEAR_SCREEN) @@ -169,6 +170,7 @@ func (self *TerminalStateOptions) ResetStateEscapeCodes() string { sb.WriteString(RESTORE_COLORS) } sb.WriteString(RESTORE_CURSOR) + reset_modes(&sb, INBAND_RESIZE_NOTIFICATION) return sb.String() }