arreliny-term 0.1.1

A simple terminal for keeping multiple shells in one window
use eframe::egui;
use std::process::Command;
use std::thread;
use std::time::Duration;

pub(crate) fn read_primary_selection() -> Option<Vec<u8>> {
    let output = Command::new("wl-paste")
        .args(["--primary", "--no-newline"])
        .output()
        .ok()
        .filter(|output| output.status.success())
        .or_else(|| {
            Command::new("xclip")
                .args(["-selection", "primary", "-out"])
                .output()
                .ok()
                .filter(|output| output.status.success())
        })?;
    Some(output.stdout)
}

pub(crate) fn publish_primary_selection(text: String) {
    use std::io::Write;
    thread::spawn(move || {
        if let Ok(mut child) = Command::new("wl-copy")
            .arg("--primary")
            .stdin(std::process::Stdio::piped())
            .spawn()
        {
            if let Some(stdin) = &mut child.stdin {
                let _ = stdin.write_all(text.as_bytes());
            }
            let _ = child.wait();
            return;
        }
        if let Ok(mut child) = Command::new("xclip")
            .args(["-selection", "primary", "-in"])
            .stdin(std::process::Stdio::piped())
            .spawn()
        {
            if let Some(stdin) = &mut child.stdin {
                let _ = stdin.write_all(text.as_bytes());
            }
            let _ = child.wait();
        }
    });
}

pub(crate) fn open_uri(uri: String) {
    thread::spawn(move || {
        let _ = Command::new("xdg-open").arg(uri).status();
    });
}

pub(crate) fn play_bell() {
    thread::spawn(|| {
        let played = Command::new("canberra-gtk-play")
            .args(["--id", "bell"])
            .status()
            .is_ok_and(|status| status.success());
        if !played {
            let _ = Command::new("paplay")
                .arg("/usr/share/sounds/freedesktop/stereo/bell.oga")
                .status();
        }
    });
}

pub(crate) fn desktop_work_area_size() -> Option<egui::Vec2> {
    let output = Command::new("xprop")
        .args(["-root", "_NET_WORKAREA"])
        .output()
        .ok()?;
    if !output.status.success() {
        return None;
    }
    let text = String::from_utf8(output.stdout).ok()?;
    let values = text
        .split(['=', ','])
        .flat_map(|part| part.split_whitespace())
        .filter_map(|part| part.parse::<f32>().ok())
        .collect::<Vec<_>>();
    let values = values.get(values.len().checked_sub(4)?..)?;
    (values[2] > 0.0 && values[3] > 0.0).then(|| egui::vec2(values[2], values[3]))
}

pub(crate) fn center_with_compositor() -> bool {
    use std::env;
    use std::fs;
    let desktop = env::var("XDG_CURRENT_DESKTOP").unwrap_or_default();
    let desktop_lower = desktop.to_ascii_lowercase();
    if env::var_os("HYPRLAND_INSTANCE_SIGNATURE").is_some() || env::var_os("SWAYSOCK").is_some() {
        return true;
    }
    let kde_session = env::var_os("KDE_FULL_SESSION").is_some();
    let kwin_available = Command::new("qdbus6")
        .args([
            "org.kde.KWin",
            "/Scripting",
            "org.freedesktop.DBus.Peer.Ping",
        ])
        .output()
        .is_ok_and(|output| output.status.success());
    if !desktop_lower.contains("kde") && !kde_session && !kwin_available {
        return false;
    }
    thread::spawn(|| {
        thread::sleep(Duration::from_millis(20));
        let cache = env::var_os("XDG_CACHE_HOME")
            .map(std::path::PathBuf::from)
            .or_else(|| {
                env::var_os("HOME").map(|path| std::path::PathBuf::from(path).join(".cache"))
            })
            .unwrap_or_else(env::temp_dir)
            .join("arreliny");
        if fs::create_dir_all(&cache).is_err() {
            return;
        }
        let script = cache.join("center.js");
        let source = format!(
            "const windows = workspace.stackingOrder;\nfor (let i = windows.length - 1; i >= 0; --i) {{\n    const window = windows[i];\n    if (window.pid === {}) {{\n        const area = workspace.clientArea(KWin.MaximizeArea, window);\n        const geometry = window.frameGeometry;\n        window.frameGeometry = {{ x: area.x + Math.round((area.width - geometry.width) / 2), y: area.y + Math.round((area.height - geometry.height) / 2), width: geometry.width, height: geometry.height }};\n        break;\n    }}\n}}\n",
            std::process::id()
        );
        if fs::write(&script, source).is_err() {
            return;
        }
        let plugin = "dev.arreliny.center";
        let _ = Command::new("qdbus6")
            .args([
                "org.kde.KWin",
                "/Scripting",
                "org.kde.kwin.Scripting.unloadScript",
                plugin,
            ])
            .output();
        let loaded = Command::new("qdbus6")
            .arg("org.kde.KWin")
            .arg("/Scripting")
            .arg("org.kde.kwin.Scripting.loadScript")
            .arg(&script)
            .arg(plugin)
            .output();
        if loaded.is_ok_and(|output| output.status.success()) {
            let _ = Command::new("qdbus6")
                .args(["org.kde.KWin", "/Scripting", "org.kde.kwin.Scripting.start"])
                .output();
        }
    });
    true
}