aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordivens <divens@users.noreply.github.com>2026-02-20 16:25:01 +0100
committerGitHub <noreply@github.com>2026-02-20 16:25:01 +0100
commit523fda9f6369307815f3ea247700bd7130e7a0ca (patch)
tree300a8af8d39e8a71aa220fed906bebf0dfa79aa6
parentfix: prevent watchers from stealing renders from non-watchers (#4739) (diff)
downloadsl0ppij-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.toml4
-rw-r--r--zellij-client/src/lib.rs7
-rw-r--r--zellij-client/src/os_input_output.rs65
-rw-r--r--zellij-client/src/os_input_output_unix.rs70
-rw-r--r--zellij-client/src/os_input_output_windows.rs38
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")
+ }
+}