Skip to main content

beam_worker/
backend.rs

1use std::time::Duration;
2
3use anyhow::Result;
4use async_trait::async_trait;
5use tokio::sync::broadcast;
6
7pub(crate) const RAW_INPUT_ENTER_DELAY: Duration = Duration::from_millis(200);
8pub(crate) const ZELLIJ_PANE_DISCOVERY_RETRY_INTERVAL: Duration = Duration::from_millis(200);
9pub(crate) const ZELLIJ_PANE_DISCOVERY_MAX_ATTEMPTS: usize = 15;
10/// Upper bound for `zellij attach --create-background` during spawn. When the
11/// zellij server panics during session setup the client retries the socket
12/// forever; without a timeout the worker would block here indefinitely.
13pub(crate) const ZELLIJ_SPAWN_TIMEOUT: Duration = Duration::from_secs(30);
14/// Spawn attempts per session. zellij 0.44.3 intermittently reports the pane
15/// as created while the pty is never registered ("failed to find terminal fd
16/// for id 0"), leaving an empty pane with no process; a fresh retry usually
17/// lands on the healthy path.
18pub(crate) const ZELLIJ_SPAWN_MAX_ATTEMPTS: usize = 2;
19pub(crate) const ZELLIJ_SPAWN_RETRY_BACKOFF: Duration = Duration::from_millis(500);
20pub(crate) const ZELLIJ_PANE_PROCESS_CHECK_INTERVAL: Duration = Duration::from_millis(300);
21pub(crate) const ZELLIJ_PANE_PROCESS_CHECK_ATTEMPTS: usize = 3;
22/// Upper bound for a single `zellij action` / probe command (write-chars,
23/// paste, dump-screen, list-sessions, ...). Previously these were synchronous
24/// `std::process::Command::output()` calls with no timeout: any zellij server
25/// hiccup could block a tokio thread forever and cascade into a fully stuck
26/// worker. Now every external call is bounded and errors out instead.
27pub(crate) const ZELLIJ_ACTION_TIMEOUT: Duration = Duration::from_secs(8);
28
29#[derive(Debug, Clone)]
30pub struct SpawnOpts {
31    pub cwd: String,
32    #[allow(dead_code)]
33    /// Desired terminal columns (passed as layout intent; actual pane size is
34    /// managed by the terminal proxy anchor).
35    pub cols: u16,
36    #[allow(dead_code)]
37    /// Desired terminal rows (passed as layout intent; actual pane size is
38    /// managed by the terminal proxy anchor).
39    pub rows: u16,
40    pub env: Vec<(String, String)>,
41}
42
43/// Backend trait. All methods take `&self`: implementations synchronize
44/// internally at per-operation granularity, so callers can share one
45/// `Arc<dyn SessionBackend>` across tasks without an outer Mutex. This is
46/// what keeps a long `write_input()` (paste + confirm loop) from blocking
47/// screen capture, terminal keys, and the screenshot coordinator.
48#[allow(dead_code)]
49#[async_trait]
50pub trait SessionBackend: Send + Sync {
51    async fn spawn(&self, bin: &str, args: &[String], opts: SpawnOpts) -> Result<()>;
52    async fn send_text(&self, text: &str) -> Result<()>;
53    async fn send_enter(&self) -> Result<()>;
54    async fn send_special_keys(&self, keys: &[String]) -> Result<()>;
55    async fn paste_text(&self, text: &str) -> Result<()>;
56    async fn write_raw(&self, text: &str) -> Result<()>;
57    async fn raw_input(&self, text: &str) -> Result<()>;
58    /// Capture the visible viewport only (current pane dimensions).
59    async fn capture_viewport(&self) -> Result<String>;
60    /// Capture the last visible screen (alias for capture_viewport by default).
61    async fn capture_current_screen(&self) -> Result<String>;
62    async fn is_alive(&self) -> Result<bool>;
63    async fn child_pid(&self) -> Result<Option<u32>>;
64    async fn kill(&self) -> Result<()>;
65    async fn destroy_session(&self) -> Result<()>;
66    /// Return the real cursor position as 0-based (x, y) if available.
67    async fn cursor_position(&self) -> Result<Option<(u16, u16)>>;
68    fn subscribe(&self) -> broadcast::Receiver<String>;
69}
70
71pub(crate) mod herdr;
72mod observe;
73pub(crate) mod select;
74mod subscribe;
75mod zellij;
76
77pub use herdr::{HerdrBackend, HerdrObserveBackend};
78pub use observe::ZellijObserveBackend;
79pub use zellij::ZellijBackend;
80
81#[cfg(test)]
82#[path = "backend/tests.rs"]
83mod tests;