rusty_pv/signals.rs
1//! Unix signal handlers for SIGUSR1 + SIGWINCH (Unix-only, CLI-feature-gated).
2//!
3//! FR-030, AD-010, HINT-013. SIGUSR1 sets an atomic flag the main loop polls
4//! to re-stat the input file and refresh the `-s` size hint. SIGWINCH sets an
5//! atomic flag the display tick polls to re-query terminal width.
6//!
7//! On Windows, this module is intentionally absent — callers that import it
8//! must `#[cfg(unix)]`-gate the import.
9
10#![cfg(unix)]
11
12use std::sync::Arc;
13use std::sync::atomic::{AtomicBool, Ordering};
14
15/// Shared flags set by signal handlers and polled by the main loop.
16#[derive(Debug, Clone, Default)]
17pub struct SignalFlags {
18 /// Set when SIGUSR1 has been received (FR-030 — re-stat input file).
19 pub sigusr1: Arc<AtomicBool>,
20 /// Set when SIGWINCH has been received (FR-019 — re-query terminal width).
21 pub sigwinch: Arc<AtomicBool>,
22}
23
24impl SignalFlags {
25 /// Construct a fresh, cleared set of flags.
26 #[must_use]
27 pub fn new() -> Self {
28 Self::default()
29 }
30
31 /// Atomically consume the SIGUSR1 flag. Returns `true` if it was set.
32 pub fn take_sigusr1(&self) -> bool {
33 self.sigusr1.swap(false, Ordering::AcqRel)
34 }
35
36 /// Atomically consume the SIGWINCH flag. Returns `true` if it was set.
37 pub fn take_sigwinch(&self) -> bool {
38 self.sigwinch.swap(false, Ordering::AcqRel)
39 }
40}
41
42/// Register signal-hook handlers for SIGUSR1 + SIGWINCH backed by `flags`.
43///
44/// Returns the registered guards; drop them to unregister. The handlers
45/// themselves are signal-safe: they only `store(true)` on the relevant atomic
46/// and return immediately.
47///
48/// # Errors
49///
50/// Returns the underlying `signal-hook` registration error if the syscall
51/// fails (typically not possible for SIGUSR1/SIGWINCH on standard Unix).
52pub fn register(flags: &SignalFlags) -> Result<Registration, std::io::Error> {
53 use signal_hook::consts::{SIGUSR1, SIGWINCH};
54 use signal_hook::flag;
55
56 let sigusr1_id = flag::register(SIGUSR1, flags.sigusr1.clone())?;
57 let sigwinch_id = flag::register(SIGWINCH, flags.sigwinch.clone())?;
58 Ok(Registration {
59 _sigusr1: sigusr1_id,
60 _sigwinch: sigwinch_id,
61 })
62}
63
64/// RAII registration guard. Drop to remove the handlers.
65#[derive(Debug)]
66pub struct Registration {
67 _sigusr1: signal_hook::SigId,
68 _sigwinch: signal_hook::SigId,
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[test]
76 fn flags_round_trip() {
77 let f = SignalFlags::new();
78 assert!(!f.take_sigusr1());
79 assert!(!f.take_sigwinch());
80 f.sigusr1.store(true, Ordering::Release);
81 f.sigwinch.store(true, Ordering::Release);
82 assert!(f.take_sigusr1());
83 assert!(f.take_sigwinch());
84 // After taking, flags are cleared.
85 assert!(!f.take_sigusr1());
86 assert!(!f.take_sigwinch());
87 }
88}