codex-wrangler 1.0.11

Linux/X11/i3 tray switcher for live coding-agent TUI sessions
use std::process::Command;

use anyhow::{Context as _, Result, bail};
use serde::Deserialize;
use x11rb::protocol::xproto::Window;

#[derive(Deserialize)]
struct Node {
    window: Option<Window>,
    #[serde(default)]
    floating: Floating,
    #[serde(default)]
    nodes: Vec<Self>,
    #[serde(default)]
    floating_nodes: Vec<Self>,
}

#[derive(Clone, Copy, Default, Deserialize)]
#[serde(rename_all = "snake_case")]
enum Floating {
    AutoOn,
    UserOn,
    #[default]
    AutoOff,
    UserOff,
}

impl Node {
    fn floating(&self, quarry: Window, inherited: bool) -> Option<bool> {
        let floating = inherited || matches!(self.floating, Floating::AutoOn | Floating::UserOn);
        if self.window == Some(quarry) {
            return Some(floating);
        }
        self.nodes
            .iter()
            .find_map(|node| node.floating(quarry, floating))
            .or_else(|| {
                self.floating_nodes
                    .iter()
                    .find_map(|node| node.floating(quarry, true))
            })
    }
}

pub fn window_floating(window: Window) -> Result<Option<bool>> {
    let output = Command::new("i3-msg")
        .args(["-r", "-t", "get_tree"])
        .output()
        .context("query i3 layout tree")?;
    if !output.status.success() {
        bail!(
            "i3 layout query failed: {}",
            String::from_utf8_lossy(&output.stderr).trim()
        );
    }
    let tree: Node = serde_json::from_slice(&output.stdout).context("decode i3 layout tree")?;
    Ok(tree.floating(window, false))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn floating_branch_dominates_a_stale_child_flag() {
        let tree: Node = serde_json::from_str(
            r#"{
              "window": null,
              "nodes": [{"window": 7, "floating": "user_off"}],
              "floating_nodes": [{
                "window": null,
                "floating": "auto_on",
                "nodes": [{"window": 9, "floating": "user_off"}]
              }]
            }"#,
        )
        .expect("tree");
        assert_eq!(tree.floating(7, false), Some(false));
        assert_eq!(tree.floating(9, false), Some(true));
        assert_eq!(tree.floating(11, false), None);
    }
}