From 191cb0e2201c911d1bf0df3ba03062c6d9b6e738 Mon Sep 17 00:00:00 2001 From: amrbashir Date: Thu, 29 Feb 2024 22:58:59 +0200 Subject: [PATCH 1/2] feat: use `ShellExecuteW` for detached spawning on Windows ref: https://github.com/Byron/open-rs/issues/90 & https://github.com/tauri-apps/plugins-workspace/issues/1003 --- src/lib.rs | 34 +++++++++++++++------ src/windows.rs | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f21897b..9afcd8c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -237,16 +237,24 @@ pub fn with_in_background>( /// /// See documentation of [`that()`] for more details. pub fn that_detached(path: impl AsRef) -> io::Result<()> { - let mut last_err = None; - for mut cmd in commands(path) { - match cmd.spawn_detached() { - Ok(_) => { - return Ok(()); + #[cfg(not(windows))] + { + let mut last_err = None; + for mut cmd in commands(path) { + match cmd.spawn_detached() { + Ok(_) => { + return Ok(()); + } + Err(err) => last_err = Some(err), } - Err(err) => last_err = Some(err), } + Err(last_err.expect("no launcher worked, at least one error")) + } + + #[cfg(windows)] + { + windows::that_detached(path) } - Err(last_err.expect("no launcher worked, at least one error")) } /// Open path with the given application using a detached process, which is useful if @@ -255,8 +263,16 @@ pub fn that_detached(path: impl AsRef) -> io::Result<()> { /// /// See documentation of [`with()`] for more details. pub fn with_detached>(path: T, app: impl Into) -> io::Result<()> { - let mut cmd = with_command(path, app); - cmd.spawn_detached() + #[cfg(not(windows))] + { + let mut cmd = with_command(path, app); + cmd.spawn_detached() + } + + #[cfg(windows)] + { + windows::with_detached(path, app) + } } trait IntoResult { diff --git a/src/windows.rs b/src/windows.rs index 74ced51..2bd22ef 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -1,6 +1,9 @@ use std::{ ffi::{OsStr, OsString}, + io, iter, + os::windows::ffi::OsStrExt, process::Command, + ptr, }; use std::os::windows::process::CommandExt; @@ -35,3 +38,81 @@ fn wrap_in_quotes>(path: T) -> OsString { result } + +pub fn that_detached>(path: T) -> std::io::Result<()> { + detached(path, None::<&str>) +} + +pub fn with_detached>(path: T, app: impl Into) -> std::io::Result<()> { + detached(path, Some(app)) +} + +#[inline] +fn detached>(path: T, app: Option>) -> std::io::Result<()> { + let path = wide(path); + let app = app.map(|a| wide(a.into())); + + let (app, args) = match app { + Some(app) => (app.as_ptr(), path.as_ptr()), + None => (path.as_ptr(), ptr::null()), + }; + + unsafe { ShellExecuteW(0, ffi::OPEN, app, args, ptr::null(), ffi::SW_SHOW) } +} + +/// Encodes as wide and adds a null character. +fn wide>(input: T) -> Vec { + input.as_ref().encode_wide().chain(iter::once(0)).collect() +} + +/// Performs an operation on a specified file. +/// +/// +#[allow(non_snake_case)] +pub unsafe fn ShellExecuteW( + hwnd: isize, + lpoperation: *const u16, + lpfile: *const u16, + lpparameters: *const u16, + lpdirectory: *const u16, + nshowcmd: i32, +) -> std::io::Result<()> { + let hr = ffi::ShellExecuteW( + hwnd, + lpoperation, + lpfile, + lpparameters, + lpdirectory, + nshowcmd, + ); + + // ShellExecuteW returns > 32 on success + // https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutew#return-value + if hr > 32 { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } +} + +mod ffi { + /// Activates the window and displays it in its current size and position. + /// + /// + pub const SW_SHOW: i32 = 5; + + /// Null-terminated UTF-16 encoding of `open`. + pub const OPEN: *const u16 = [111, 112, 101, 110, 0].as_ptr(); + + #[link(name = "Shell32")] + extern "C" { + pub fn ShellExecuteW( + hwnd: isize, + lpoperation: *const u16, + lpfile: *const u16, + lpparameters: *const u16, + lpdirectory: *const u16, + nshowcmd: i32, + ) -> i32; + } +} From 4506b2f8ac51579932b76884a11133ce5c49c21f Mon Sep 17 00:00:00 2001 From: amrbashir Date: Thu, 29 Feb 2024 23:03:57 +0200 Subject: [PATCH 2/2] split into two functions for better readability --- src/windows.rs | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/windows.rs b/src/windows.rs index 2bd22ef..2977c78 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -40,24 +40,34 @@ fn wrap_in_quotes>(path: T) -> OsString { } pub fn that_detached>(path: T) -> std::io::Result<()> { - detached(path, None::<&str>) -} + let path = wide(path); -pub fn with_detached>(path: T, app: impl Into) -> std::io::Result<()> { - detached(path, Some(app)) + unsafe { + ShellExecuteW( + 0, + ffi::OPEN, + path.as_ptr(), + ptr::null(), + ptr::null(), + ffi::SW_SHOW, + ) + } } -#[inline] -fn detached>(path: T, app: Option>) -> std::io::Result<()> { +pub fn with_detached>(path: T, app: impl Into) -> std::io::Result<()> { + let app = wide(app.into()); let path = wide(path); - let app = app.map(|a| wide(a.into())); - - let (app, args) = match app { - Some(app) => (app.as_ptr(), path.as_ptr()), - None => (path.as_ptr(), ptr::null()), - }; - unsafe { ShellExecuteW(0, ffi::OPEN, app, args, ptr::null(), ffi::SW_SHOW) } + unsafe { + ShellExecuteW( + 0, + ffi::OPEN, + app.as_ptr(), + path.as_ptr(), + ptr::null(), + ffi::SW_SHOW, + ) + } } /// Encodes as wide and adds a null character.