Skip to main content

katui_rt/
pane.rs

1//! Where our multiplexer pane stands. A multiplexer drops graphics from
2//! panes that are unfocused or out of view, so an application pauses
3//! there and sends everything again when the pane comes back.
4
5use std::process::Command;
6use std::sync::Arc;
7use std::sync::atomic::{AtomicBool, Ordering};
8use std::time::Duration;
9
10const POLL: Duration = Duration::from_millis(200);
11const FORMAT: &str = "#{session_attached} #{window_active} #{pane_active} #{window_zoomed_flag}";
12
13/// What a multiplexer lets a pane show.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum PaneView {
16    /// On screen and focused: graphics reach the terminal.
17    Active,
18    /// On screen but unfocused: the multiplexer drops graphics, so what
19    /// the terminal shows stays as it was.
20    Frozen,
21    /// Off screen: another window, a zoomed sibling, or no client.
22    Hidden,
23}
24
25/// Report through `report` each time multiplexer `program` changes what
26/// our pane can show, until `stop` is set or `report` returns `false`.
27pub(crate) fn watch(
28    program: String,
29    stop: Arc<AtomicBool>,
30    report: impl Fn(PaneView) -> bool + Send + 'static,
31) {
32    std::thread::spawn(move || {
33        let mut shown = PaneView::Active;
34        while !stop.load(Ordering::Relaxed) {
35            std::thread::sleep(POLL);
36            let Some(view) = query(&program) else {
37                continue;
38            };
39            if view == shown {
40                continue;
41            }
42            shown = view;
43            if !report(view) {
44                return;
45            }
46        }
47    });
48}
49
50fn query(program: &str) -> Option<PaneView> {
51    let pane = std::env::var("TMUX_PANE").ok()?;
52    let output = Command::new(program)
53        .args(["display-message", "-p", "-t", &pane, FORMAT])
54        .output()
55        .ok()?;
56    parse(std::str::from_utf8(&output.stdout).ok()?)
57}
58
59fn parse(fields: &str) -> Option<PaneView> {
60    let mut values = fields.split_whitespace();
61    let mut flag = || values.next()?.parse::<u32>().ok();
62    let attached = flag()?;
63    let window_active = flag()?;
64    let pane_active = flag()?;
65    let zoomed = flag()?;
66    Some(
67        if attached == 0 || window_active == 0 || (zoomed == 1 && pane_active == 0) {
68            PaneView::Hidden
69        } else if pane_active == 0 {
70            PaneView::Frozen
71        } else {
72            PaneView::Active
73        },
74    )
75}
76
77#[cfg(test)]
78mod tests {
79    use super::{PaneView, parse};
80
81    #[test]
82    fn a_pane_view_follows_focus_window_and_zoom() {
83        assert_eq!(parse("1 1 1 0\n"), Some(PaneView::Active));
84        assert_eq!(parse("1 1 0 0"), Some(PaneView::Frozen));
85        assert_eq!(parse("1 0 1 0"), Some(PaneView::Hidden));
86        assert_eq!(parse("1 1 0 1"), Some(PaneView::Hidden));
87        assert_eq!(parse("1 1 1 1"), Some(PaneView::Active));
88        assert_eq!(parse("0 1 1 0"), Some(PaneView::Hidden));
89        assert_eq!(parse("error"), None);
90    }
91}