1 #![doc(html_root_url = "https://docs.rs/opener/0.5.0")]
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 a
18 //! 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 #![warn(
22 rust_2018_idioms,
23 deprecated_in_future,
24 macro_use_extern_crate,
25 missing_debug_implementations,
26 unused_qualifications
27 )]
28
29 #[cfg(not(any(target_os = "windows", target_os = "macos")))]
30 mod linux_and_more;
31 #[cfg(target_os = "macos")]
32 mod macos;
33 #[cfg(target_os = "windows")]
34 mod windows;
35
36 #[cfg(not(any(target_os = "windows", target_os = "macos")))]
37 use crate::linux_and_more as sys;
38 #[cfg(target_os = "macos")]
39 use crate::macos as sys;
40 #[cfg(target_os = "windows")]
41 use crate::windows as sys;
42
43 use std::error::Error;
44 use std::ffi::{OsStr, OsString};
45 use std::fmt::{self, Display, Formatter};
46 use std::process::{Command, ExitStatus, Stdio};
47 use std::{env, io};
48
49 /// Opens a file or link with the system default program.
50 ///
51 /// Note that a path like "rustup.rs" could potentially refer to either a file or a website. If you
52 /// want to open the website, you should add the "http://" prefix, for example.
53 ///
54 /// Also note that a result of `Ok(())` just means a way of opening the path was found, and no error
55 /// occurred as a direct result of opening the path. Errors beyond that point aren't caught. For
56 /// example, `Ok(())` would be returned even if a file was opened with a program that can't read the
57 /// file, or a dead link was opened in a browser.
58 ///
59 /// ## Platform Implementation Details
60 ///
61 /// - On Windows the `ShellExecuteW` Windows API function is used.
62 /// - On Mac the system `open` command is used.
63 /// - On Windows Subsystem for Linux (WSL), the system `wslview` from [`wslu`] is used if available,
64 /// otherwise the system `xdg-open` is used, if available.
65 /// - On non-WSL Linux and other platforms,
66 /// the system `xdg-open` script is used if available, otherwise an `xdg-open` script embedded in
67 /// this library is used.
68 ///
69 /// [`wslu`]: https://github.com/wslutilities/wslu/
open<P>(path: P) -> Result<(), OpenError> where P: AsRef<OsStr>,70 pub fn open<P>(path: P) -> Result<(), OpenError>
71 where
72 P: AsRef<OsStr>,
73 {
74 sys::open(path.as_ref())
75 }
76
77 /// Opens a file or link with the system default program, using the `BROWSER` environment variable
78 /// when set.
79 ///
80 /// If the `BROWSER` environment variable is set, the program specified by it is used to open the
81 /// path. If not, behavior is identical to [`open()`].
open_browser<P>(path: P) -> Result<(), OpenError> where P: AsRef<OsStr>,82 pub fn open_browser<P>(path: P) -> Result<(), OpenError>
83 where
84 P: AsRef<OsStr>,
85 {
86 let mut path = path.as_ref();
87 if let Ok(browser_var) = env::var("BROWSER") {
88 let windows_path;
89 if is_wsl() && browser_var.ends_with(".exe") {
90 if let Some(windows_path_2) = wsl_to_windows_path(path) {
91 windows_path = windows_path_2;
92 path = &windows_path;
93 }
94 };
95
96 Command::new(&browser_var)
97 .arg(path)
98 .stdin(Stdio::null())
99 .stdout(Stdio::null())
100 .stderr(Stdio::piped())
101 .spawn()
102 .map_err(OpenError::Io)?;
103
104 Ok(())
105 } else {
106 sys::open(path)
107 }
108 }
109
110 /// An error type representing the failure to open a path. Possibly returned by the [`open`]
111 /// function.
112 ///
113 /// The `ExitStatus` variant will never be returned on Windows.
114 #[derive(Debug)]
115 pub enum OpenError {
116 /// An IO error occurred.
117 Io(io::Error),
118
119 /// A command exited with a non-zero exit status.
120 ExitStatus {
121 /// A string that identifies the command.
122 cmd: &'static str,
123
124 /// The failed process's exit status.
125 status: ExitStatus,
126
127 /// Anything the process wrote to stderr.
128 stderr: String,
129 },
130 }
131
132 impl Display for OpenError {
fmt(&self, f: &mut Formatter<'_>) -> fmt::Result133 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
134 match self {
135 OpenError::Io(_) => {
136 write!(f, "IO error")?;
137 }
138 OpenError::ExitStatus {
139 cmd,
140 status,
141 stderr,
142 } => {
143 write!(
144 f,
145 "command '{}' did not execute successfully; {}",
146 cmd, status
147 )?;
148
149 let stderr = stderr.trim();
150 if !stderr.is_empty() {
151 write!(f, "\ncommand stderr:\n{}", stderr)?;
152 }
153 }
154 }
155
156 Ok(())
157 }
158 }
159
160 impl Error for OpenError {
source(&self) -> Option<&(dyn Error + 'static)>161 fn source(&self) -> Option<&(dyn Error + 'static)> {
162 match self {
163 OpenError::Io(inner) => Some(inner),
164 OpenError::ExitStatus { .. } => None,
165 }
166 }
167 }
168
169 #[cfg(target_os = "linux")]
is_wsl() -> bool170 fn is_wsl() -> bool {
171 sys::is_wsl()
172 }
173
174 #[cfg(not(target_os = "linux"))]
is_wsl() -> bool175 fn is_wsl() -> bool {
176 false
177 }
178
179 #[cfg(target_os = "linux")]
wsl_to_windows_path(path: &OsStr) -> Option<OsString>180 fn wsl_to_windows_path(path: &OsStr) -> Option<OsString> {
181 use bstr::ByteSlice;
182 use std::os::unix::ffi::OsStringExt;
183
184 let output = Command::new("wslpath")
185 .arg("-w")
186 .arg(path)
187 .stdin(Stdio::null())
188 .stdout(Stdio::piped())
189 .stderr(Stdio::null())
190 .output()
191 .ok()?;
192
193 if !output.status.success() {
194 return None;
195 }
196
197 Some(OsString::from_vec(output.stdout.trim_end().to_vec()))
198 }
199
200 #[cfg(not(target_os = "linux"))]
wsl_to_windows_path(_path: &OsStr) -> Option<OsString>201 fn wsl_to_windows_path(_path: &OsStr) -> Option<OsString> {
202 unreachable!()
203 }
204
205 #[cfg(not(target_os = "windows"))]
wait_child(child: &mut std::process::Child, cmd_name: &'static str) -> Result<(), OpenError>206 fn wait_child(child: &mut std::process::Child, cmd_name: &'static str) -> Result<(), OpenError> {
207 use std::io::Read;
208
209 let exit_status = child.wait().map_err(OpenError::Io)?;
210 if exit_status.success() {
211 Ok(())
212 } else {
213 let mut stderr_output = String::new();
214 if let Some(stderr) = child.stderr.as_mut() {
215 stderr.read_to_string(&mut stderr_output).ok();
216 }
217
218 Err(OpenError::ExitStatus {
219 cmd: cmd_name,
220 status: exit_status,
221 stderr: stderr_output,
222 })
223 }
224 }
225