1use std::io;
8use std::sync::Arc;
9use std::sync::atomic::{AtomicBool, Ordering};
10
11use signal_hook::consts::signal::{SIGCHLD, SIGWINCH};
12use signal_hook::iterator::Signals;
13use tokio::sync::mpsc;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum PtySignalEvent {
18 WindowChanged,
20 ChildStateChanged,
22}
23
24#[derive(Debug)]
26pub struct SignalHandle {
27 shutdown: Arc<AtomicBool>,
29}
30
31impl SignalHandle {
32 pub fn shutdown(&self) {
34 self.shutdown.store(true, Ordering::SeqCst);
35 }
36}
37
38impl Drop for SignalHandle {
39 fn drop(&mut self) {
40 self.shutdown();
41 }
42}
43
44pub fn start_signal_handler() -> io::Result<(mpsc::UnboundedReceiver<PtySignalEvent>, SignalHandle)>
52{
53 let mut signals = Signals::new([SIGWINCH, SIGCHLD])?;
54 let (tx, rx) = mpsc::unbounded_channel();
55 let shutdown = Arc::new(AtomicBool::new(false));
56 let shutdown_clone = Arc::clone(&shutdown);
57
58 std::thread::Builder::new()
59 .name("pty-signal-handler".into())
60 .spawn(move || {
61 for signal in signals.forever() {
62 if shutdown_clone.load(Ordering::SeqCst) {
63 break;
64 }
65
66 let event = match signal {
67 SIGWINCH => PtySignalEvent::WindowChanged,
68 SIGCHLD => PtySignalEvent::ChildStateChanged,
69 _ => continue,
70 };
71
72 if tx.send(event).is_err() {
73 break;
75 }
76 }
77 })?;
78
79 Ok((rx, SignalHandle { shutdown }))
80}
81
82pub fn on_window_change<F>(callback: F) -> io::Result<SignalHandle>
91where
92 F: Fn() + Send + 'static,
93{
94 let mut signals = Signals::new([SIGWINCH])?;
95 let shutdown = Arc::new(AtomicBool::new(false));
96 let shutdown_clone = Arc::clone(&shutdown);
97
98 std::thread::Builder::new()
99 .name("pty-sigwinch-handler".into())
100 .spawn(move || {
101 for _ in signals.forever() {
102 if shutdown_clone.load(Ordering::SeqCst) {
103 break;
104 }
105 callback();
106 }
107 })?;
108
109 Ok(SignalHandle { shutdown })
110}
111
112#[must_use]
114pub const fn is_sigchld(signal: i32) -> bool {
115 signal == SIGCHLD
116}
117
118#[must_use]
120pub const fn is_sigwinch(signal: i32) -> bool {
121 signal == SIGWINCH
122}
123
124#[must_use]
126pub const fn sigwinch() -> i32 {
127 SIGWINCH
128}
129
130#[must_use]
132pub const fn sigchld() -> i32 {
133 SIGCHLD
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn signal_constants() {
142 assert!(is_sigchld(sigchld()));
143 assert!(is_sigwinch(sigwinch()));
144 assert!(!is_sigchld(sigwinch()));
145 assert!(!is_sigwinch(sigchld()));
146 }
147
148 #[test]
149 fn signal_handle_shutdown() {
150 let shutdown = Arc::new(AtomicBool::new(false));
151 let handle = SignalHandle {
152 shutdown: Arc::clone(&shutdown),
153 };
154
155 assert!(!shutdown.load(Ordering::SeqCst));
156 handle.shutdown();
157 assert!(shutdown.load(Ordering::SeqCst));
158 }
159}