Skip to content

Commit

Permalink
Merge pull request #91 from amrbashir/feat/windows/detachded-using-sh…
Browse files Browse the repository at this point in the history
…ellexecutew

feat: use `ShellExecuteW` for detached spawning on Windows
  • Loading branch information
Byron committed Mar 1, 2024
2 parents f4ef7c9 + 4506b2f commit b268647
Show file tree
Hide file tree
Showing 2 changed files with 116 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
91 changes: 91 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,91 @@ fn wrap_in_quotes<T: AsRef<OsStr>>(path: T) -> OsString {

result
}

pub fn that_detached<T: AsRef<OsStr>>(path: T) -> std::io::Result<()> {
let path = wide(path);

unsafe {
ShellExecuteW(
0,
ffi::OPEN,
path.as_ptr(),
ptr::null(),
ptr::null(),
ffi::SW_SHOW,
)
}
}

pub fn with_detached<T: AsRef<OsStr>>(path: T, app: impl Into<String>) -> std::io::Result<()> {
let app = wide(app.into());
let path = wide(path);

unsafe {
ShellExecuteW(
0,
ffi::OPEN,
app.as_ptr(),
path.as_ptr(),
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 b268647

Please sign in to comment.