use std::env;
use std::os::unix::process::CommandExt;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use crate::path_ext::IsExecutable;
pub struct DesktopInfo {
pub name: String,
pub exec: String,
pub terminal: bool,
}
pub fn parse_desktop_info(path: &PathBuf) -> Option<DesktopInfo> {
let content = std::fs::read_to_string(path).ok()?;
let (mut name, mut exec, mut terminal, mut no_display, mut hidden, mut app_type) =
(None, None, false, false, false, None);
let mut in_de = false;
for line in content.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
if line == "[Desktop Entry]" {
in_de = true;
continue;
}
if !in_de || line.starts_with('[') {
if in_de {
break;
}
continue;
}
if let Some(v) = line.strip_prefix("Type=") {
app_type = Some(v);
} else if let Some(v) = line.strip_prefix("Name=") {
name = Some(v.to_owned());
} else if let Some(v) = line.strip_prefix("Exec=") {
exec = Some(v.to_owned());
} else if line == "Terminal=true" {
terminal = true;
} else if line == "NoDisplay=true" {
no_display = true;
} else if line == "Hidden=true" {
hidden = true;
} else if let Some(v) = line.strip_prefix("TryExec=") {
let exists = if v.starts_with('/') {
PathBuf::from(v).is_executable()
} else {
env::var_os("PATH")
.map(|p| env::split_paths(&p).collect::<Vec<_>>())
.unwrap_or_default()
.iter()
.any(|dir| dir.join(v).is_executable())
};
if !exists {
return None;
}
}
}
if app_type? != "Application" {
return None;
}
if no_display || hidden {
return None;
}
Some(DesktopInfo {
name: name?,
exec: exec?,
terminal,
})
}
fn tokenize(s: &str) -> Vec<String> {
let mut args = Vec::new();
let mut current = String::new();
let mut chars = s.chars();
while let Some(c) = chars.next() {
match c {
'\\' => {
if let Some(next) = chars.next() {
current.push(next);
}
}
'"' => {
while let Some(c) = chars.next() {
match c {
'\\' => {
if let Some(next) = chars.next() {
current.push(next);
}
}
'"' => break,
_ => current.push(c),
}
}
}
c if c.is_ascii_whitespace() => {
if !current.is_empty() {
args.push(current.clone());
current.clear();
}
}
_ => current.push(c),
}
}
if !current.is_empty() {
args.push(current);
}
args
}
fn is_field_code(t: &str) -> bool {
t.len() == 2 && t.starts_with('%') && t.as_bytes()[1].is_ascii_alphabetic()
}
fn parse_exec(s: &str) -> Vec<String> {
tokenize(s)
.into_iter()
.filter(|t| !is_field_code(t))
.map(|t| t.replace("%%", "%"))
.collect()
}
pub fn launch(info: &DesktopInfo, cli_terminal: Option<&str>) -> bool {
let args = parse_exec(&info.exec);
if args.is_empty() {
return false;
}
if info.terminal {
return crate::terminal::exec(&args, cli_terminal);
}
unsafe {
Command::new(&args[0])
.args(&args[1..])
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.pre_exec(|| {
libc::setsid();
Ok(())
})
.spawn()
.is_ok()
}
}