Skip to main content

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}