From e54ee86852ff1db63d9143de74715637b0e8ae0f Mon Sep 17 00:00:00 2001 From: Brian Bowman Date: Tue, 31 Jul 2018 03:26:37 -0500 Subject: [PATCH] Use ShellExecute rather than start.exe on windows The code is adapted from rustup. Using ShellExecute is more efficient and allows much better error handling. It also avoids some bugs, which is the reason rustup switched to using this method. Fixes #16 --- Cargo.toml | 3 +++ src/lib.rs | 65 ++++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 54 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b8b32af..093c769 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,6 @@ documentation = "http://byron.github.io/open-rs" test = false doc = false name = "open" + +[target.'cfg(windows)'.dependencies] +winapi = { version = "0.3", features = ["shellapi"] } diff --git a/src/lib.rs b/src/lib.rs index 79c22c2..dcaeac2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,9 +37,15 @@ //! } //! # } //! ``` +#[cfg(windows)] +extern crate winapi; + +#[cfg(not(target_os = "windows"))] +use std::process::Command; + use std::ffi::OsStr; use std::io; -use std::process::{Command, ExitStatus}; +use std::process::ExitStatus; #[cfg(not(any(target_os = "windows", target_os = "macos")))] pub fn that + Sized>(path: T) -> io::Result { @@ -59,22 +65,53 @@ pub fn that + Sized>(path: T) -> io::Result { #[cfg(target_os = "windows")] pub fn that + Sized>(path: T) -> io::Result { - Command::new("cmd") - .arg("/C") - .arg("start") - .arg("") - .arg({ - let path_ref = path.as_ref(); - match path_ref.to_str() { - Some(s) => s.replace("&", "^&"), - None => path_ref, - } - }) - .spawn()? - .wait() + use winapi::ctypes::c_int; + use winapi::um::shellapi::ShellExecuteW; + use std::os::windows::ffi::OsStrExt; + use std::os::windows::process::ExitStatusExt; + use std::ptr; + + const SW_SHOW: c_int = 5; + + let path = windows::convert_path(path.as_ref())?; + let operation: Vec = OsStr::new("open\0").encode_wide().collect(); + let result = unsafe { + ShellExecuteW( + ptr::null_mut(), + operation.as_ptr(), + path.as_ptr(), + ptr::null(), + ptr::null(), + SW_SHOW, + ) + }; + if result as c_int > 32 { + Ok(ExitStatus::from_raw(0)) + } else { + Err(io::Error::last_os_error()) + } } #[cfg(target_os = "macos")] pub fn that + Sized>(path: T) -> io::Result { Command::new("open").arg(path.as_ref()).spawn()?.wait() } + +#[cfg(windows)] +mod windows { + use std::io; + use std::ffi::OsStr; + use std::os::windows::ffi::OsStrExt; + + pub fn convert_path(path: &OsStr) -> io::Result> { + let mut maybe_result: Vec<_> = path.encode_wide().collect(); + if maybe_result.iter().any(|&u| u == 0) { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "path contains NUL byte(s)", + )); + } + maybe_result.push(0); + Ok(maybe_result) + } +}