Skip to content

Commit 17a48f6

Browse files
committed
TUI: exit cleanly when I/O to terminal fails
1 parent 5c7a1d1 commit 17a48f6

File tree

2 files changed

+16
-4
lines changed

2 files changed

+16
-4
lines changed

kittens/tui/handler.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class Handler:
4242
image_manager_class: Optional[Type[ImageManagerType]] = None
4343
use_alternate_screen = True
4444
mouse_tracking = MouseTracking.none
45+
terminal_io_ended = False
4546

4647
def _initialize(
4748
self,
@@ -112,6 +113,10 @@ def quit_loop(self, return_code: Optional[int] = None) -> None:
112113
def on_term(self) -> None:
113114
self._tui_loop.quit(1)
114115

116+
def on_hup(self) -> None:
117+
self.terminal_io_ended = True
118+
self._tui_loop.quit(1)
119+
115120
def on_key_event(self, key_event: KeyEventType, in_bracketed_paste: bool = False) -> None:
116121
if key_event.text:
117122
self.on_text(key_event.text, in_bracketed_paste)

kittens/tui/loop.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,18 +196,21 @@ def __init__(
196196
on_winch: Callable[[], None],
197197
on_interrupt: Callable[[], None],
198198
on_term: Callable[[], None],
199+
on_hup: Callable[[], None],
199200
) -> None:
200201
self.asyncio_loop = loop
201202
self.on_winch, self.on_interrupt, self.on_term = on_winch, on_interrupt, on_term
203+
self.on_hup = on_hup
202204

203205
def __enter__(self) -> None:
204206
self.asyncio_loop.add_signal_handler(signal.SIGWINCH, self.on_winch)
205207
self.asyncio_loop.add_signal_handler(signal.SIGINT, self.on_interrupt)
206208
self.asyncio_loop.add_signal_handler(signal.SIGTERM, self.on_term)
209+
self.asyncio_loop.add_signal_handler(signal.SIGHUP, self.on_hup)
207210

208211
def __exit__(self, *a: Any) -> None:
209212
tuple(map(self.asyncio_loop.remove_signal_handler, (
210-
signal.SIGWINCH, signal.SIGINT, signal.SIGTERM)))
213+
signal.SIGWINCH, signal.SIGINT, signal.SIGTERM, signal.SIGHUP)))
211214

212215

213216
sanitize_bracketed_paste: str = '[\x03\x04\x0e\x0f\r\x07\x7f\x8d\x8e\x8f\x90\x9b\x9d\x9e\x9f]'
@@ -248,7 +251,9 @@ def _read_ready(self, handler: Handler, fd: int) -> None:
248251
except BlockingIOError:
249252
return
250253
if not bdata:
251-
raise EOFError('The input stream is closed')
254+
handler.terminal_io_ended = True
255+
self.quit(1)
256+
return
252257
data = self.decoder.decode(bdata)
253258
if self.read_buf:
254259
data = self.read_buf + data
@@ -373,7 +378,9 @@ def _write_ready(self, handler: Handler, fd: int) -> None:
373378
except BlockingIOError:
374379
return
375380
if not written:
376-
raise EOFError('The output stream is closed')
381+
handler.terminal_io_ended = True
382+
self.quit(1)
383+
return
377384
else:
378385
written = 0
379386
if written >= total_size:
@@ -441,7 +448,7 @@ def _on_sigwinch() -> None:
441448
handler.screen_size = self._get_screen_size()
442449
handler.on_resize(handler.screen_size)
443450

444-
signal_manager = SignalManager(self.asyncio_loop, _on_sigwinch, handler.on_interrupt, handler.on_term)
451+
signal_manager = SignalManager(self.asyncio_loop, _on_sigwinch, handler.on_interrupt, handler.on_term, handler.on_hup)
445452
with TermManager(self.optional_actions, handler.use_alternate_screen, handler.mouse_tracking) as term_manager, signal_manager:
446453
self._get_screen_size: ScreenSizeGetter = screen_size_function(term_manager.tty_fd)
447454
image_manager = None

0 commit comments

Comments
 (0)