use std::path::PathBuf;
use std::process::{Command, Stdio};
pub const WINDOW_MARK: &str = "window=1";
fn marked(url: &str) -> String {
let sep = if url.contains('?') { '&' } else { '?' };
format!("{url}{sep}{WINDOW_MARK}")
}
pub const SCHEME: &str = "snyvi";
pub fn app_url(id: &str) -> String {
format!("{SCHEME}://d/{id}")
}
pub fn resolve(target: &str) -> String {
let base = crate::config::base_url();
if let Some(rest) = target.strip_prefix(&format!("{SCHEME}:")) {
let rest = rest.trim_start_matches('/');
return format!("{base}/{rest}");
}
if target.starts_with("http://") || target.starts_with("https://") {
return target.to_string();
}
format!("{base}/d/{target}")
}
pub fn window_installed() -> bool {
window_binary().is_some()
}
fn window_binary() -> Option<PathBuf> {
let name = crate::platform::exe("snyvi-app");
if let Ok(exe) = std::env::current_exe() {
let exe = exe.canonicalize().unwrap_or(exe);
if let Some(sibling) = exe.parent().map(|d| d.join(&name)) {
if sibling.is_file() {
return Some(sibling);
}
}
}
if let Some(path) = std::env::var_os("PATH") {
if let Some(found) = std::env::split_paths(&path)
.map(|d| d.join(&name))
.find(|c| c.is_file())
{
return Some(found);
}
}
crate::platform::app_bundles("snyvi.app")
.into_iter()
.map(|app| app.join("Contents/MacOS").join(&name))
.find(|c| c.is_file())
}
pub fn hand_to_window(url: &str) -> bool {
if !crate::platform::has_display() {
return false;
}
let Some(bin) = window_binary() else {
return false;
};
Command::new(&bin)
.arg(url)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.is_ok()
}
pub fn open(url: &str) -> anyhow::Result<()> {
if crate::platform::has_display() {
if let Some(bin) = window_binary() {
let marked = marked(url);
#[cfg(unix)]
{
use std::os::unix::process::CommandExt;
let e = Command::new(&bin).arg(&marked).exec();
eprintln!("snyvi: {}: {e}", bin.display());
}
#[cfg(not(unix))]
match Command::new(&bin).arg(&marked).status() {
Ok(_) => return Ok(()),
Err(e) => eprintln!("snyvi: {}: {e}", bin.display()),
}
}
}
for browser in crate::platform::app_mode_browsers() {
let ok = Command::new(&browser)
.arg(format!("--app={url}"))
.arg("--window-size=1280,860")
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.is_ok();
if ok {
say_rung(&format!("this is {} in app mode", program_name(&browser)));
return Ok(());
}
}
if crate::platform::open_url(url) {
say_rung("this is the default browser");
} else {
say_rung("and no browser could be opened from here");
eprintln!(" open this yourself: {url}");
}
Ok(())
}
fn say_rung(what: &str) {
if !crate::platform::has_display() {
eprintln!("snyvi: no display, so no window; {what}");
return;
}
if window_binary().is_some() {
eprintln!("snyvi: the native window could not be started; {what}");
return;
}
let get = if cfg!(target_os = "macos") {
"put snyvi.app in Applications for one"
} else if cfg!(windows) {
"put snyvi-app.exe beside snyvi.exe for one"
} else {
"install the snyvi-app package for one"
};
eprintln!("snyvi: no native window installed, so {what}; {get}");
}
fn program_name(path: &str) -> String {
std::path::Path::new(path)
.file_stem()
.map(|s| s.to_string_lossy().to_string())
.unwrap_or_else(|| path.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_link_a_url_and_an_id_all_resolve_on_the_daemon() {
let base = crate::config::base_url();
assert_eq!(resolve("snyvi://d/abc"), format!("{base}/d/abc"));
assert_eq!(resolve("snyvi://"), format!("{base}/"));
assert_eq!(resolve("abc"), format!("{base}/d/abc"));
assert_eq!(
resolve("http://127.0.0.1:7777/d/abc"),
"http://127.0.0.1:7777/d/abc"
);
}
#[test]
fn the_mark_joins_whatever_query_is_there() {
assert_eq!(marked("http://h:1"), "http://h:1?window=1");
assert_eq!(marked("http://h:1/d/x?v=2"), "http://h:1/d/x?v=2&window=1");
}
#[test]
fn app_url_is_the_scheme_and_the_document() {
assert_eq!(app_url("abc"), "snyvi://d/abc");
}
}