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
}