Skip to content

Commit d21c48e

Browse files
committed
Merge branch 'feat/detached-process'
2 parents 8e47876 + 7ad9cb3 commit d21c48e

File tree

2 files changed

+69
-1
lines changed

2 files changed

+69
-1
lines changed

Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,6 @@ pathdiff = "0.2.0"
2222

2323
[target.'cfg(any(target_os = "linux", target_os = "android", target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd", target_os = "openbsd", target_os = "illumos", target_os = "solaris" ))'.dependencies]
2424
is-wsl = "0.4.0"
25+
26+
[target."cfg(unix)".dependencies]
27+
libc = "0.2"

src/lib.rs

+66-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ use std::{
138138
/// # Beware
139139
///
140140
/// Sometimes, depending on the platform and system configuration, launchers *can* block.
141-
/// If you want to be sure they don't, use [`that_in_background()`] instead.
141+
/// If you want to be sure they don't, use [`that_in_background()`] or [`that_detached`] instead.
142142
pub fn that(path: impl AsRef<OsStr>) -> io::Result<()> {
143143
let mut last_err = None;
144144
for mut cmd in commands(path) {
@@ -232,6 +232,33 @@ pub fn with_in_background<T: AsRef<OsStr>>(
232232
thread::spawn(|| with(path, app))
233233
}
234234

235+
/// Open path with the default application using a detached process. which is useful if
236+
/// the program ends up to be blocking or want to out-live your app
237+
///
238+
/// See documentation of [`that()`] for more details.
239+
pub fn that_detached(path: impl AsRef<OsStr>) -> io::Result<()> {
240+
let mut last_err = None;
241+
for mut cmd in commands(path) {
242+
match cmd.spawn_detached() {
243+
Ok(_) => {
244+
return Ok(());
245+
}
246+
Err(err) => last_err = Some(err),
247+
}
248+
}
249+
Err(last_err.expect("no launcher worked, at least one error"))
250+
}
251+
252+
/// Open path with the given application using a detached process, which is useful if
253+
/// the program ends up to be blocking or want to out-live your app. Otherwise, prefer [`with()`] for
254+
/// straightforward error handling.
255+
///
256+
/// See documentation of [`with()`] for more details.
257+
pub fn with_detached<T: AsRef<OsStr>>(path: T, app: impl Into<String>) -> io::Result<()> {
258+
let mut cmd = with_command(path, app);
259+
cmd.spawn_detached()
260+
}
261+
235262
trait IntoResult<T> {
236263
fn into_result(self, cmd: &Command) -> T;
237264
}
@@ -251,6 +278,7 @@ impl IntoResult<io::Result<()>> for io::Result<std::process::ExitStatus> {
251278

252279
trait CommandExt {
253280
fn status_without_output(&mut self) -> io::Result<std::process::ExitStatus>;
281+
fn spawn_detached(&mut self) -> io::Result<()>;
254282
}
255283

256284
impl CommandExt for Command {
@@ -260,6 +288,43 @@ impl CommandExt for Command {
260288
.stderr(Stdio::null())
261289
.status()
262290
}
291+
292+
fn spawn_detached(&mut self) -> io::Result<()> {
293+
// This is pretty much lifted from the implementation in Alacritty:
294+
// https://github.com/alacritty/alacritty/blob/b9c886872d1202fc9302f68a0bedbb17daa35335/alacritty/src/daemon.rs
295+
296+
self.stdin(Stdio::null())
297+
.stdout(Stdio::null())
298+
.stderr(Stdio::null());
299+
300+
#[cfg(unix)]
301+
unsafe {
302+
use std::os::unix::process::CommandExt as _;
303+
304+
self.pre_exec(move || {
305+
match libc::fork() {
306+
-1 => return Err(io::Error::last_os_error()),
307+
0 => (),
308+
_ => libc::_exit(0),
309+
}
310+
311+
if libc::setsid() == -1 {
312+
return Err(io::Error::last_os_error());
313+
}
314+
315+
Ok(())
316+
});
317+
}
318+
#[cfg(windows)]
319+
{
320+
use std::os::windows::process::CommandExt;
321+
const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
322+
const CREATE_NO_WINDOW: u32 = 0x08000000;
323+
self.creation_flags(CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW);
324+
}
325+
326+
self.spawn().map(|_| ())
327+
}
263328
}
264329

265330
#[cfg(windows)]

0 commit comments

Comments
 (0)