diff options
| author | divens <divens@users.noreply.github.com> | 2026-02-20 16:25:01 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-02-20 16:25:01 +0100 |
| commit | 523fda9f6369307815f3ea247700bd7130e7a0ca (patch) | |
| tree | 300a8af8d39e8a71aa220fed906bebf0dfa79aa6 | |
| parent | fix: prevent watchers from stealing renders from non-watchers (#4739) (diff) | |
| download | sl0ppij-523fda9f6369307815f3ea247700bd7130e7a0ca.tar.gz sl0ppij-523fda9f6369307815f3ea247700bd7130e7a0ca.tar.xz sl0ppij-523fda9f6369307815f3ea247700bd7130e7a0ca.zip | |
refactor: split client signal handling into platform files (#4738)
Extract signal handling from os_input_output.rs into platform-specific
files, mirroring the server-side architecture from PR3:
- os_input_output_unix.rs: AsyncSignalListener (tokio unix signals),
BlockingSignalIterator (signal_hook)
- os_input_output_windows.rs: stubs (unimplemented)
- os_input_output.rs: shared SignalEvent enum, AsyncSignals trait,
handle_signals() now uses BlockingSignalIterator + SignalEvent
Moved signal-hook to cfg(unix)-only dependency in zellij-client.
Co-authored-by: divens <divens.dev@gmail.com>
| -rw-r--r-- | zellij-client/Cargo.toml | 4 | ||||
| -rw-r--r-- | zellij-client/src/lib.rs | 7 | ||||
| -rw-r--r-- | zellij-client/src/os_input_output.rs | 65 | ||||
| -rw-r--r-- | zellij-client/src/os_input_output_unix.rs | 70 | ||||
| -rw-r--r-- | zellij-client/src/os_input_output_windows.rs | 38 |
5 files changed, 128 insertions, 56 deletions
diff --git a/zellij-client/Cargo.toml b/zellij-client/Cargo.toml index 2e1a07fc..bdcace07 100644 --- a/zellij-client/Cargo.toml +++ b/zellij-client/Cargo.toml @@ -36,7 +36,6 @@ regex = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } serde_yaml = { version = "0.8", default-features = false } -signal-hook = { workspace = true } termwiz = { workspace = true } url = { workspace = true } uuid = { workspace = true } @@ -49,6 +48,9 @@ tokio-tungstenite = { version = "0.20", features = ["native-tls"] } #TODO: see a futures-util = "0.3" urlencoding = "2.1" +[target.'cfg(not(windows))'.dependencies] +signal-hook = { workspace = true } + [dev-dependencies] insta = "1.6.0" serde_json = "1.0" diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs index 8f0ed253..4447c191 100644 --- a/zellij-client/src/lib.rs +++ b/zellij-client/src/lib.rs @@ -1,5 +1,12 @@ pub mod os_input_output; +#[cfg(not(windows))] +#[path = "os_input_output_unix.rs"] +mod os_input_output_unix; +#[cfg(windows)] +#[path = "os_input_output_windows.rs"] +mod os_input_output_windows; + pub mod cli_client; mod command_is_executing; mod input_handler; diff --git a/zellij-client/src/os_input_output.rs b/zellij-client/src/os_input_output.rs index b1f95fa4..3fa8c896 100644 --- a/zellij-client/src/os_input_output.rs +++ b/zellij-client/src/os_input_output.rs @@ -1,11 +1,14 @@ use anyhow::{Context, Result}; use async_trait::async_trait; use interprocess; -use signal_hook; use zellij_utils::pane_size::Size; +#[cfg(not(windows))] +use crate::os_input_output_unix::{AsyncSignalListener, BlockingSignalIterator}; +#[cfg(windows)] +use crate::os_input_output_windows::{AsyncSignalListener, BlockingSignalIterator}; + use interprocess::local_socket::LocalSocketStream; -use signal_hook::{consts::signal::*, iterator::Signals}; use std::io::prelude::*; use std::io::IsTerminal; use std::path::Path; @@ -65,53 +68,6 @@ pub trait AsyncSignals: Send { async fn recv(&mut self) -> Option<SignalEvent>; } -pub struct AsyncSignalListener { - sigwinch: tokio::signal::unix::Signal, - sigterm: tokio::signal::unix::Signal, - sigint: tokio::signal::unix::Signal, - sigquit: tokio::signal::unix::Signal, - sighup: tokio::signal::unix::Signal, -} - -impl AsyncSignalListener { - pub fn new() -> io::Result<Self> { - use tokio::signal::unix::{signal, SignalKind}; - Ok(Self { - sigwinch: signal(SignalKind::window_change())?, - sigterm: signal(SignalKind::terminate())?, - sigint: signal(SignalKind::interrupt())?, - sigquit: signal(SignalKind::quit())?, - sighup: signal(SignalKind::hangup())?, - }) - } - - pub async fn recv_resize(&mut self) -> Option<()> { - self.sigwinch.recv().await - } - - pub async fn recv_quit(&mut self) -> Option<()> { - tokio::select! { - result = self.sigterm.recv() => result, - result = self.sigint.recv() => result, - result = self.sigquit.recv() => result, - result = self.sighup.recv() => result, - } - } -} - -#[async_trait] -impl AsyncSignals for AsyncSignalListener { - async fn recv(&mut self) -> Option<SignalEvent> { - tokio::select! { - result = self.sigwinch.recv() => result.map(|_| SignalEvent::Resize), - result = self.sigterm.recv() => result.map(|_| SignalEvent::Quit), - result = self.sigint.recv() => result.map(|_| SignalEvent::Quit), - result = self.sigquit.recv() => result.map(|_| SignalEvent::Quit), - result = self.sighup.recv() => result.map(|_| SignalEvent::Quit), - } - } -} - pub(crate) fn get_terminal_size() -> Size { match crossterm::terminal::size() { Ok((cols, rows)) => { @@ -282,10 +238,10 @@ impl ClientOsApi for ClientOsInputOutput { } fn handle_signals(&self, sigwinch_cb: Box<dyn Fn()>, quit_cb: Box<dyn Fn()>) { let mut sigwinch_cb_timestamp = time::Instant::now(); - let mut signals = Signals::new(&[SIGWINCH, SIGTERM, SIGINT, SIGQUIT, SIGHUP]).unwrap(); - for signal in signals.forever() { - match signal { - SIGWINCH => { + let signals = BlockingSignalIterator::new().unwrap(); + for event in signals { + match event { + SignalEvent::Resize => { // throttle sigwinch_cb calls, reduce excessive renders while resizing if sigwinch_cb_timestamp.elapsed() < SIGWINCH_CB_THROTTLE_DURATION { thread::sleep(SIGWINCH_CB_THROTTLE_DURATION); @@ -293,11 +249,10 @@ impl ClientOsApi for ClientOsInputOutput { sigwinch_cb_timestamp = time::Instant::now(); sigwinch_cb(); }, - SIGTERM | SIGINT | SIGQUIT | SIGHUP => { + SignalEvent::Quit => { quit_cb(); break; }, - _ => unreachable!(), } } } diff --git a/zellij-client/src/os_input_output_unix.rs b/zellij-client/src/os_input_output_unix.rs new file mode 100644 index 00000000..d087eb7d --- /dev/null +++ b/zellij-client/src/os_input_output_unix.rs @@ -0,0 +1,70 @@ +use crate::os_input_output::SignalEvent; + +use async_trait::async_trait; +use signal_hook::consts::signal::*; +use signal_hook::iterator::Signals; +use tokio::signal::unix::{signal, SignalKind}; + +use std::io; + +/// Async signal listener that maps Unix signals to `SignalEvent` variants. +pub(crate) struct AsyncSignalListener { + sigwinch: tokio::signal::unix::Signal, + sigterm: tokio::signal::unix::Signal, + sigint: tokio::signal::unix::Signal, + sigquit: tokio::signal::unix::Signal, + sighup: tokio::signal::unix::Signal, +} + +impl AsyncSignalListener { + pub fn new() -> io::Result<Self> { + Ok(Self { + sigwinch: signal(SignalKind::window_change())?, + sigterm: signal(SignalKind::terminate())?, + sigint: signal(SignalKind::interrupt())?, + sigquit: signal(SignalKind::quit())?, + sighup: signal(SignalKind::hangup())?, + }) + } +} + +#[async_trait] +impl crate::os_input_output::AsyncSignals for AsyncSignalListener { + async fn recv(&mut self) -> Option<SignalEvent> { + tokio::select! { + result = self.sigwinch.recv() => result.map(|_| SignalEvent::Resize), + result = self.sigterm.recv() => result.map(|_| SignalEvent::Quit), + result = self.sigint.recv() => result.map(|_| SignalEvent::Quit), + result = self.sigquit.recv() => result.map(|_| SignalEvent::Quit), + result = self.sighup.recv() => result.map(|_| SignalEvent::Quit), + } + } +} + +/// Blocking signal iterator that maps Unix signals to `SignalEvent` variants. +/// Used by `handle_signals()` on a dedicated thread. +pub(crate) struct BlockingSignalIterator { + signals: Signals, +} + +impl BlockingSignalIterator { + pub fn new() -> io::Result<Self> { + let signals = Signals::new([SIGWINCH, SIGTERM, SIGINT, SIGQUIT, SIGHUP])?; + Ok(Self { signals }) + } +} + +impl Iterator for BlockingSignalIterator { + type Item = SignalEvent; + + fn next(&mut self) -> Option<SignalEvent> { + for signal in self.signals.forever() { + match signal { + SIGWINCH => return Some(SignalEvent::Resize), + SIGTERM | SIGINT | SIGQUIT | SIGHUP => return Some(SignalEvent::Quit), + _ => {}, + } + } + None + } +} diff --git a/zellij-client/src/os_input_output_windows.rs b/zellij-client/src/os_input_output_windows.rs new file mode 100644 index 00000000..bfced557 --- /dev/null +++ b/zellij-client/src/os_input_output_windows.rs @@ -0,0 +1,38 @@ +use crate::os_input_output::SignalEvent; + +use async_trait::async_trait; + +use std::io; + +/// Windows async signal listener stub. Not yet implemented. +pub(crate) struct AsyncSignalListener; + +impl AsyncSignalListener { + pub fn new() -> io::Result<Self> { + unimplemented!("Windows AsyncSignalListener not yet implemented") + } +} + +#[async_trait] +impl crate::os_input_output::AsyncSignals for AsyncSignalListener { + async fn recv(&mut self) -> Option<SignalEvent> { + unimplemented!("Windows AsyncSignalListener not yet implemented") + } +} + +/// Windows blocking signal iterator stub. Not yet implemented. +pub(crate) struct BlockingSignalIterator; + +impl BlockingSignalIterator { + pub fn new() -> io::Result<Self> { + unimplemented!("Windows BlockingSignalIterator not yet implemented") + } +} + +impl Iterator for BlockingSignalIterator { + type Item = SignalEvent; + + fn next(&mut self) -> Option<SignalEvent> { + unimplemented!("Windows BlockingSignalIterator not yet implemented") + } +} |
