mirror of
https://github.com/storytold/craft-launcher.git
synced 2026-11-02 18:07:04 +00:00
fix(windows): launch system helpers by absolute path (#815)
Co-authored-by: Codex <codex@openai.com> Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
//! Starting programs: the Craft Apps themselves, the file manager, and helper tools.
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::{Duration, Instant};
|
||||
@@ -313,16 +313,16 @@ pub fn launch_binary(exe: &Path, args: &[&str]) -> Result<(), String> {
|
||||
|
||||
/// Opens a file, folder or URL with the system's default handler.
|
||||
pub fn open(target: &std::ffi::OsStr) -> Result<(), String> {
|
||||
spawn_detached(opener(target))
|
||||
spawn_detached(opener(target)?)
|
||||
}
|
||||
|
||||
/// The system's default opener for `target`: `explorer` on Windows, `open` on macOS, and
|
||||
/// `xdg-open` elsewhere (on the host, through `flatpak-spawn --host`, inside the sandbox).
|
||||
fn opener(target: &std::ffi::OsStr) -> Command {
|
||||
fn opener(target: &std::ffi::OsStr) -> Result<Command, String> {
|
||||
let mut cmd = if cfg!(target_os = "macos") {
|
||||
command("open")
|
||||
} else if cfg!(windows) {
|
||||
command("explorer")
|
||||
command(windows_system_program("explorer.exe")?)
|
||||
} else if in_sandbox() {
|
||||
let mut c = command("flatpak-spawn");
|
||||
c.arg("--host").arg("xdg-open");
|
||||
@@ -331,7 +331,7 @@ fn opener(target: &std::ffi::OsStr) -> Command {
|
||||
command("xdg-open")
|
||||
};
|
||||
cmd.arg(target);
|
||||
cmd
|
||||
Ok(cmd)
|
||||
}
|
||||
|
||||
/// Opens `primary_url` with the system's default handler. If opening fails (for example,
|
||||
@@ -354,7 +354,13 @@ pub fn open_url_with_fallback(primary_url: &str, fallback_url: &str) {
|
||||
/// `explorer` exits with an error status even when it opens the target, so on Windows only a
|
||||
/// failure to start counts; the registry check in `detect_discord_scheme_handler` covers the rest.
|
||||
fn open_and_wait_success(target: &str, timeout: Duration) -> bool {
|
||||
let mut cmd = opener(std::ffi::OsStr::new(target));
|
||||
let mut cmd = match opener(std::ffi::OsStr::new(target)) {
|
||||
Ok(cmd) => cmd,
|
||||
Err(error) => {
|
||||
log::warn!("couldn't resolve the system opener for {target}: {error}");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
if cfg!(windows) {
|
||||
return spawn_detached(cmd).is_ok();
|
||||
}
|
||||
@@ -397,7 +403,7 @@ pub fn reveal(path: &Path) -> Result<(), String> {
|
||||
#[cfg(windows)]
|
||||
fn reveal_in_explorer(path: &Path) -> Result<(), String> {
|
||||
use std::os::windows::process::CommandExt;
|
||||
let mut cmd = command("explorer");
|
||||
let mut cmd = command(windows_system_program("explorer.exe")?);
|
||||
// Passed raw: `arg` would wrap all of `/select,...` in quotes when the path has a space
|
||||
// (`C:\Program Files\...`), and Explorer then ignores the switch and opens Documents.
|
||||
// A quote in the path would end the quoted part early and let the rest reach Explorer as
|
||||
@@ -432,8 +438,10 @@ pub fn is_running(path: &Path) -> bool {
|
||||
if cfg!(windows) {
|
||||
let Some(name) = path.file_name().and_then(OsStr::to_str) else { return false };
|
||||
let filter = format!("IMAGENAME eq {name}");
|
||||
capture("tasklist", &["/FI", &filter, "/NH", "/FO", "CSV"], Duration::from_secs(5))
|
||||
.is_some_and(|out| out.to_ascii_lowercase().contains(&name.to_ascii_lowercase()))
|
||||
let Ok(tasklist) = windows_system_program(r"System32\tasklist.exe") else { return false };
|
||||
let mut cmd = command(tasklist);
|
||||
cmd.args(["/FI", &filter, "/NH", "/FO", "CSV"]);
|
||||
run_command(cmd, "tasklist", Duration::from_secs(5)).ok().is_some_and(|out| out.to_ascii_lowercase().contains(&name.to_ascii_lowercase()))
|
||||
} else {
|
||||
let Some(p) = path.to_str() else { return false };
|
||||
let pattern = pgrep_pattern(p);
|
||||
@@ -446,6 +454,17 @@ pub fn is_running(path: &Path) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
fn windows_system_program(relative: &str) -> Result<PathBuf, String> {
|
||||
let root = std::env::var_os("SystemRoot")
|
||||
.filter(|root| !root.is_empty())
|
||||
.map(PathBuf::from)
|
||||
.ok_or_else(|| "Windows didn't provide its system folder".to_string())?;
|
||||
if !root.is_absolute() {
|
||||
return Err("Windows provided a non-absolute system folder".into());
|
||||
}
|
||||
Ok(root.join(relative))
|
||||
}
|
||||
|
||||
/// Quote the path's metacharacters for pgrep's extended regular expression.
|
||||
fn pgrep_pattern(path: &str) -> String {
|
||||
let mut pattern = String::with_capacity(path.len());
|
||||
@@ -573,6 +592,14 @@ mod run_tests {
|
||||
mod explorer_tests {
|
||||
use super::*;
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn system_helpers_use_absolute_system_paths() {
|
||||
let root = PathBuf::from(std::env::var_os("SystemRoot").unwrap());
|
||||
assert_eq!(windows_system_program("explorer.exe").unwrap(), root.join("explorer.exe"));
|
||||
assert_eq!(windows_system_program(r"System32\tasklist.exe").unwrap(), root.join(r"System32\tasklist.exe"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explorer_select_quotes_only_the_path() {
|
||||
let arg = explorer_select_arg(Path::new(r"C:\Program Files\PhotoCraft\photocraft.exe"));
|
||||
|
||||
Reference in New Issue
Block a user