use std::path::Path; use std::process::Command; use crate::error::{PinakesError, Result}; pub trait Opener: Send + Sync { fn open(&self, path: &Path) -> Result<()>; } /// Linux opener using xdg-open pub struct XdgOpener; impl Opener for XdgOpener { fn open(&self, path: &Path) -> Result<()> { let status = Command::new("xdg-open") .arg(path) .status() .map_err(|e| PinakesError::InvalidOperation(format!("failed to run xdg-open: {e}")))?; if status.success() { Ok(()) } else { Err(PinakesError::InvalidOperation(format!( "xdg-open exited with status {status}" ))) } } } /// macOS opener using the `open` command pub struct MacOpener; impl Opener for MacOpener { fn open(&self, path: &Path) -> Result<()> { let status = Command::new("open") .arg(path) .status() .map_err(|e| PinakesError::InvalidOperation(format!("failed to run open: {e}")))?; if status.success() { Ok(()) } else { Err(PinakesError::InvalidOperation(format!( "open exited with status {status}" ))) } } } /// Windows opener using `cmd /c start` pub struct WindowsOpener; impl Opener for WindowsOpener { fn open(&self, path: &Path) -> Result<()> { let status = Command::new("cmd") .args(["/C", "start", ""]) .arg(path) .status() .map_err(|e| { PinakesError::InvalidOperation(format!("failed to run cmd /c start: {e}")) })?; if status.success() { Ok(()) } else { Err(PinakesError::InvalidOperation(format!( "cmd /c start exited with status {status}" ))) } } } /// Returns the platform-appropriate opener. pub fn default_opener() -> Box { if cfg!(target_os = "macos") { Box::new(MacOpener) } else if cfg!(target_os = "windows") { Box::new(WindowsOpener) } else { Box::new(XdgOpener) } }