wayclip-daemon 0.2.0

Recording and management of Wayclip daemon
use std::{
    env,
    io::{Read, Write},
    os::unix::net::UnixStream,
    time::Duration,
};

#[derive(serde::Deserialize)]
struct ActiveWindow {
    pid: i64,
}

pub struct WindowFocusScanner;

const HYPR_SIGNATURE: &str = "HYPRLAND_INSTANCE_SIGNATURE";
const SWAY_SIGNATURE: &str = "SWAYSOCK";

impl WindowFocusScanner {
    pub fn focused_pid() -> Option<u32> {
        if let Some(sig) = env::var_os(HYPR_SIGNATURE) {
            return Self::hyprland(&sig.as_os_str().to_string_lossy());
        }
        if let Some(sig) = env::var_os(SWAY_SIGNATURE) {
            return Self::sway(&sig.as_os_str().to_string_lossy());
        }
        None
    }

    fn hyprland(sig: &str) -> Option<u32> {
        let xdg_runtime = dirs::runtime_dir().unwrap_or_default();
        let socket = format!(
            "{}/hypr/{}/.socket.sock",
            xdg_runtime.to_string_lossy(),
            sig
        );

        let mut stream = UnixStream::connect(socket).ok()?;
        stream
            .set_read_timeout(Some(Duration::from_millis(100)))
            .ok()?;
        stream.write_all(b"j/activewindow").ok()?;

        let mut res = Vec::new();
        stream.read_to_end(&mut res).ok()?;

        let parsed: ActiveWindow = serde_json::from_slice(&res).ok()?;
        if parsed.pid > 0 {
            Some(parsed.pid as u32)
        } else {
            None
        }
    }

    fn sway(sig: &str) -> Option<u32> {
        let mut stream = UnixStream::connect(sig).ok()?;
        stream
            .set_read_timeout(Some(Duration::from_millis(100)))
            .ok()?;

        let mut payload = Vec::with_capacity(14);
        payload.extend_from_slice(b"i3-ipc");
        payload.extend_from_slice(&0u32.to_le_bytes());
        payload.extend_from_slice(&4u32.to_le_bytes());

        stream.write_all(&payload).ok()?;
        let mut header = [0u8; 14];
        stream.read_exact(&mut header).ok()?;
        if &header[0..6] != b"i3-ipc" {
            return None;
        }

        let len = u32::from_le_bytes(header[6..10].try_into().ok()?) as usize;
        let mut body = vec![0u8; len];
        stream.read_exact(&mut body).ok()?;

        let tree: serde_json::Value = serde_json::from_slice(&body).ok()?;
        Self::search_node(&tree)
    }

    fn search_node(node: &serde_json::Value) -> Option<u32> {
        if node["focused"] == true {
            return node["pid"].as_u64().map(|p| p as u32);
        }

        ["nodes", "floating_nodes"]
            .into_iter()
            .filter_map(|k| node[k].as_array())
            .flatten()
            .find_map(Self::search_node)
    }
}