Skip to content

Commit

Permalink
feat: use ShellExecuteW for detached spawning on Windows
Browse files Browse the repository at this point in the history
  • Loading branch information
amrbashir committed Feb 29, 2024
1 parent f4ef7c9 commit 191cb0e
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 9 deletions.
34 changes: 25 additions & 9 deletions src/lib.rs
Expand Up @@ -237,16 +237,24 @@ pub fn with_in_background<T: AsRef<OsStr>>(
///
/// See documentation of [`that()`] for more details.
pub fn that_detached(path: impl AsRef<OsStr>) -> 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
Expand All @@ -255,8 +263,16 @@ pub fn that_detached(path: impl AsRef<OsStr>) -> io::Result<()> {
///
/// See documentation of [`with()`] for more details.
pub fn with_detached<T: AsRef<OsStr>>(path: T, app: impl Into<String>) -> 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<T> {
Expand Down
81 changes: 81 additions & 0 deletions 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;
Expand Down Expand Up @@ -35,3 +38,81 @@ fn wrap_in_quotes<T: AsRef<OsStr>>(path: T) -> OsString {

result
}

pub fn that_detached<T: AsRef<OsStr>>(path: T) -> std::io::Result<()> {
detached(path, None::<&str>)
}

pub fn with_detached<T: AsRef<OsStr>>(path: T, app: impl Into<String>) -> std::io::Result<()> {
detached(path, Some(app))
}

#[inline]
fn detached<T: AsRef<OsStr>>(path: T, app: Option<impl Into<String>>) -> 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<T: AsRef<OsStr>>(input: T) -> Vec<u16> {
input.as_ref().encode_wide().chain(iter::once(0)).collect()
}

/// Performs an operation on a specified file.
///
/// <https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutew>
#[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.
///
/// <https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow>
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;
}
}

0 comments on commit 191cb0e

Please sign in to comment.