1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2
3//! This crate provides the [`open`] function, which opens a file or link with the default program
4//! configured on the system:
5//!
6//! ```no_run
7//! # fn main() -> Result<(), ::opener::OpenError> {
8//! // open a website
9//! opener::open("https://www.rust-lang.org")?;
10//!
11//! // open a file
12//! opener::open("../Cargo.toml")?;
13//! # Ok(())
14//! # }
15//! ```
16//!
17//! An [`open_browser`] function is also provided, for when you intend on opening a file or link in
18//! a browser, specifically. This function works like the [`open`] function, but explicitly allows
19//! overriding the browser launched by setting the `$BROWSER` environment variable.
20//!
21//! # Crate features
22//!
23//! - **reveal** - Enables usage of the [`reveal`] function. On Linux this will pull in the
24//! [`dbus`](https://docs.rs/dbus) crate. If the **dbus-vendored** feature is enabled (which it
25//! is by default), dbus will use static linking. Otherwise it will be dynamically linked, and
26//! some system dependencies will be required to build `dbus` and also to run any built binaries.
27//! Refer to the [`dbus` docs](https://github.com/diwic/dbus-rs#requirements) for the specifics.
28//! - **dbus-vendored** - See the **reveal** feature.
29
30#![warn(
31 rust_2018_idioms,
32 deprecated_in_future,
33 macro_use_extern_crate,
34 missing_debug_implementations,
35 unused_qualifications
36)]
37
38#[cfg(all(feature = "reveal", target_os = "linux"))]
39mod freedesktop;
40#[cfg(not(any(target_os = "windows", target_os = "macos")))]
41mod linux_and_more;
42#[cfg(target_os = "macos")]
43mod macos;
44#[cfg(target_os = "windows")]
45mod windows;
46
47#[cfg(not(any(target_os = "windows", target_os = "macos")))]
48use crate::linux_and_more as sys;
49#[cfg(target_os = "macos")]
50use crate::macos as sys;
51#[cfg(target_os = "windows")]
52use crate::windows as sys;
53
54use std::error::Error;
55use std::ffi::{OsStr, OsString};
56use std::fmt::{self, Display, Formatter};
57use std::process::{Command, ExitStatus, Stdio};
58use std::{env, io};
59
60/// Opens a file or link with the system default program.
61///
62/// Note that a path like "rustup.rs" could potentially refer to either a file or a website. If you
63/// want to open the website, you should add the "http://" prefix, for example.
64///
65/// Also note that a result of `Ok(())` just means a way of opening the path was found, and no error
66/// occurred as a direct result of opening the path. Errors beyond that point aren't caught. For
67/// example, `Ok(())` would be returned even if a file was opened with a program that can't read the
68/// file, or a dead link was opened in a browser.
69///
70/// ## Platform Implementation Details
71///
72/// - On Windows the `ShellExecuteW` Windows API function is used.
73/// - On Mac the system `open` command is used.
74/// - On Windows Subsystem for Linux (WSL), the system `wslview` from [`wslu`] is used if available,
75/// otherwise the system `xdg-open` is used, if available.
76/// - On non-WSL Linux and other platforms,
77/// the system `xdg-open` script is used if available, otherwise an `xdg-open` script embedded in
78/// this library is used.
79///
80/// [`wslu`]: https://github.com/wslutilities/wslu/
81pub fn open<P>(path: P) -> Result<(), OpenError>
82where
83 P: AsRef<OsStr>,
84{
85 sys::open(path.as_ref())
86}
87
88/// Opens a file or link with the system default program, using the `BROWSER` environment variable
89/// when set.
90///
91/// If the `BROWSER` environment variable is set, the program specified by it is used to open the
92/// path. If not, behavior is identical to [`open()`].
93pub fn open_browser<P>(path: P) -> Result<(), OpenError>
94where
95 P: AsRef<OsStr>,
96{
97 let mut path = path.as_ref();
98 if let Ok(browser_var) = env::var("BROWSER") {
99 let windows_path;
100 if is_wsl() && browser_var.ends_with(".exe") {
101 if let Some(windows_path_2) = wsl_to_windows_path(path) {
102 windows_path = windows_path_2;
103 path = &windows_path;
104 }
105 };
106
107 Command::new(&browser_var)
108 .arg(path)
109 .stdin(Stdio::null())
110 .stdout(Stdio::null())
111 .stderr(Stdio::piped())
112 .spawn()
113 .map_err(|err| OpenError::Spawn {
114 cmds: browser_var,
115 source: err,
116 })?;
117
118 Ok(())
119 } else {
120 sys::open(path)
121 }
122}
123
124/// Opens the default file explorer and reveals a file or folder in its containing folder.
125///
126/// ## Errors
127/// This function may or may not return an error if the path does not exist.
128///
129/// ## Platform Implementation Details
130/// - On Windows and Windows Subsystem for Linux (WSL) the `explorer.exe /select, <path>` command is used.
131/// - On Mac the system `open -R` command is used.
132/// - On non-WSL Linux the [`file-manager-interface`] or the [`org.freedesktop.portal.OpenURI`] DBus Interface is used if available,
133/// falling back to opening the containing folder with [`open`].
134/// - On other platforms, the containing folder is shown with [`open`].
135///
136/// [`org.freedesktop.portal.OpenURI`]: https://flatpak.github.io/xdg-desktop-portal/#gdbus-org.freedesktop.portal.OpenURI
137/// [`file-manager-interface`]: https://freedesktop.org/wiki/Specifications/file-manager-interface/
138#[cfg(feature = "reveal")]
139pub fn reveal<P>(path: P) -> Result<(), OpenError>
140where
141 P: AsRef<std::path::Path>,
142{
143 sys::reveal(path.as_ref())
144}
145
146/// An error type representing the failure to open a path. Possibly returned by the [`open`]
147/// function.
148#[non_exhaustive]
149#[derive(Debug)]
150pub enum OpenError {
151 /// An IO error occurred.
152 Io(io::Error),
153
154 /// There was an error spawning command(s).
155 Spawn {
156 /// The command(s) that failed to spawn.
157 cmds: String,
158
159 /// The underlying error.
160 source: io::Error,
161 },
162
163 /// A command exited with a non-zero exit status.
164 ExitStatus {
165 /// A string that identifies the command.
166 cmd: &'static str,
167
168 /// The failed process's exit status.
169 status: ExitStatus,
170
171 /// Anything the process wrote to stderr.
172 stderr: String,
173 },
174}
175
176impl Display for OpenError {
177 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
178 match self {
179 OpenError::Io(_) => {
180 write!(f, "IO error")?;
181 }
182 OpenError::Spawn { cmds, source: _ } => {
183 write!(f, "error spawning command(s) '{cmds}'")?;
184 }
185 OpenError::ExitStatus {
186 cmd,
187 status,
188 stderr,
189 } => {
190 write!(f, "command '{cmd}' did not execute successfully; {status}")?;
191
192 let stderr = stderr.trim();
193 if !stderr.is_empty() {
194 write!(f, "\ncommand stderr:\n{stderr}")?;
195 }
196 }
197 }
198
199 Ok(())
200 }
201}
202
203impl Error for OpenError {
204 fn source(&self) -> Option<&(dyn Error + 'static)> {
205 match self {
206 OpenError::Io(inner: &Error) => Some(inner),
207 OpenError::Spawn { cmds: _, source: &Error } => Some(source),
208 OpenError::ExitStatus { .. } => None,
209 }
210 }
211}
212
213#[cfg(target_os = "linux")]
214fn is_wsl() -> bool {
215 sys::is_wsl()
216}
217
218#[cfg(not(target_os = "linux"))]
219fn is_wsl() -> bool {
220 false
221}
222
223#[cfg(target_os = "linux")]
224fn wsl_to_windows_path(path: &OsStr) -> Option<OsString> {
225 use bstr::ByteSlice;
226 use std::os::unix::ffi::OsStringExt;
227
228 let output: ! = Command&mut Command::new("wslpath")
229 .arg("-w")
230 .arg(path)
231 .stdin(Stdio::null())
232 .stdout(Stdio::piped())
233 .stderr(cfg:Stdio::null())
234 .output()
235 .ok()?;
236
237 if !output.status.success() {
238 return None;
239 }
240
241 Some(OsString::from_vec(output.stdout.trim_end().to_vec()))
242}
243
244#[cfg(not(target_os = "linux"))]
245fn wsl_to_windows_path(_path: &OsStr) -> Option<OsString> {
246 unreachable!()
247}
248
249#[cfg(not(target_os = "windows"))]
250fn wait_child(child: &mut std::process::Child, cmd_name: &'static str) -> Result<(), OpenError> {
251 use std::io::Read;
252
253 let exit_status: ExitStatus = child.wait().map_err(OpenError::Io)?;
254 if exit_status.success() {
255 Ok(())
256 } else {
257 let mut stderr_output = String::new();
258 if let Some(stderr) = child.stderr.as_mut() {
259 stderr.read_to_string(&mut stderr_output).ok();
260 }
261
262 Err(OpenError::ExitStatus {
263 cmd: cmd_name,
264 status: exit_status,
265 stderr: stderr_output,
266 })
267 }
268}
269