1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
//! Putting the terminal back, on every way out.
//!
//! A TUI borrows the terminal: the alternate screen, raw mode, mouse
//! reporting. The borrow has to be returned on **every** exit, and there are
//! four — the ordinary one, a panic, a signal, and a write that fails
//! because the reader has gone.
//!
//! Before 0.7.0 two of the four were covered. `main` restored on the way out
//! and installed a panic hook that says why in as many words: "A panic that
//! unwinds past the event loop would otherwise leave mouse reporting on, and
//! the shell printing escape codes at every click." A **signal** reached the
//! same state by a path with no guard at all: under a pty, `kill -INT` on
//! either binary emitted zero bytes to the tty, leaving the shell inside the
//! alternate screen with mouse tracking on, ECHO, ICANON and ISIG off, and
//! no working Ctrl-C. The way out is to type `reset` blind, which a
//! first-time user does not know, and nothing on screen says so — the
//! failure is in what was *not* written.
//!
//! Whoever reaches for a signal is already having a bad time: the chart is
//! sitting on "loading" because `gh` is slow, so they `kill %1` from another
//! pane, or they wrapped it in `timeout`, or a script signalled the process
//! group.
//!
//! ## Why `signal-hook` rather than `libc::sigaction`
//!
//! `Cargo.toml` is `unsafe_code = "forbid"`, and `SECURITY.md` advertises
//! that posture ("the one libc dependency is used for a single constant, not
//! a call"). `sigaction` is an unsafe call, so the obvious route is barred.
//!
//! `signal-hook` registers safely and — the part that matters — is *already
//! in the tree*: crossterm pulls it through ratatui for its event stream, at
//! the same version pinned here. So this adds an import, not a dependency:
//! no new third-party code, no new licence to review, no new supply chain.
//!
//! The work happens on a thread rather than in a handler, which is what
//! makes it safe to do anything at all: a real signal handler may call only
//! async-signal-safe functions, and writing escape sequences through Rust's
//! stdout is not one of them.
use io;
use Show;
use ;
use execute;
/// Give the terminal back: mouse reporting off, alternate screen off, raw
/// mode off, cursor shown.
///
/// Best-effort by construction. This runs on the way out of a process that
/// may already be failing, and on a terminal that may already have gone; an
/// error here must not mask the reason we are leaving.
/// Restore the terminal on a panic, then let the previous hook run.
///
/// Kept beside [`on_signal`] because they are the same obligation: the two
/// exits nobody writes code for.
/// Restore the terminal on `SIGINT`, `SIGTERM` and `SIGHUP`, then die of the
/// signal.
///
/// Re-raising with the default disposition matters: a process killed by a
/// signal must still *report* as killed by that signal, or a shell's `$?`, a
/// `timeout` wrapper and a supervisor all learn the wrong thing about why it
/// stopped. `emulate_default_handler` does exactly that.
///
/// Ctrl-C *typed into* a TUI is a key, handled by the event loop, and does
/// not come through here — the damage always needed an actual signal.
///
/// Failing to register is not worth failing the run over: the terminal is
/// no worse off than it was before 0.7.0, and the user asked to draw a
/// chart, not to install a signal handler.
/// No-op off Unix.
///
/// Windows has no POSIX signals: a console application is torn down through
/// a control handler with a different lifetime and a different contract, and
/// pretending otherwise here would mean claiming a guarantee that is not
/// installed. The panic hook covers the other unguarded exit on every
/// platform.
/// Everything a TUI owes the terminal, installed in one call.
///
/// Both binaries take the alternate screen and both enable mouse reporting,
/// so both need all three guards. `mossaic-art --draw` had none of them.
/// Turn mouse reporting on, for a view that reads the pointer.
///
/// Here rather than at the call sites so that the enable and the disable are
/// written next to each other and cannot drift apart.
///
/// # Errors
///
/// Propagates the write to stdout.