use std::fs;
use std::process::Command;
use hyprland::data::{Client, Clients};
use hyprland::shared::HyprData;
pub fn command_exists_in_path(command: &str) -> bool {
if command.is_empty() {
return false;
}
Command::new("which")
.arg(command.split_whitespace().next().unwrap())
.output()
.map(|output| output.status.success())
.unwrap_or(false)
}
fn extract_binary_name(command: &str) -> String {
let parts = command.split_whitespace();
let binary = parts
.clone()
.next()
.unwrap_or("")
.split('/')
.last()
.unwrap_or("");
format!("{} {}", binary, parts.skip(1).collect::<Vec<&str>>().join(" ")).trim().to_string()
}
fn handle_proc_cmdline(client: &Client) -> Result<String, std::io::Error> {
let cmdline_path = format!("/proc/{}/cmdline", client.pid);
if let Ok(cmdline) = fs::read_to_string(&cmdline_path) {
let cleaned: String = cmdline
.replace('\0', " ")
.trim()
.to_string();
Ok(extract_binary_name(&cleaned))
} else {
Err(std::io::Error::new(std::io::ErrorKind::NotFound, "Cmdline not found"))
}
}
fn handle_proc_exe(client: &Client) -> Result<String, std::io::Error> {
let exe_path = format!("/proc/{}/exe", client.pid);
if let Ok(exe_target) = fs::read_link(&exe_path) {
if let Some(exe_name) = exe_target.file_name() {
return Ok(exe_name.to_string_lossy().to_string());
}
}
Err(std::io::Error::new(std::io::ErrorKind::NotFound, "Exe not found"))
}
fn handle_initial_class(client: &Client) -> Result<String, std::io::Error> {
Ok(client.initial_class.to_lowercase())
}
fn handle_initial_title(client: &Client) -> Result<String, std::io::Error> {
Ok(client.initial_title.to_lowercase())
}
pub fn fetch_command(client: &Client) -> Result<String, std::io::Error> {
let handlers = vec![
handle_proc_cmdline,
handle_proc_exe,
handle_initial_class,
handle_initial_title,
];
for handler in handlers {
if let Ok(command) = handler(client) {
if command_exists_in_path(&command) {
return Ok(command);
}
}
}
handle_proc_cmdline(client)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_binary_name() {
assert_eq!(extract_binary_name("/usr/bin/firefox"), "firefox");
assert_eq!(extract_binary_name("code"), "code");
assert_eq!(extract_binary_name("/usr/bin/firefox --new-window"), "firefox --new-window");
assert_eq!(extract_binary_name("/nix/store/.firefox-wrapped"), ".firefox-wrapped");
}
#[test]
#[ignore] fn test_command_exists_in_path() {
assert!(command_exists_in_path("ls"));
assert!(command_exists_in_path("cat"));
assert!(command_exists_in_path("which"));
assert!(!command_exists_in_path("definitely_not_a_real_command_123456"));
assert!(!command_exists_in_path(""));
}
}