11#![ doc( html_root_url = "https://docs.rs/opener/0.4.1" ) ]
22
33//! This crate provides the [`open`] function, which opens a file or link with the default program
4- //! configured on the system.
4+ //! configured on the system:
55//!
66//! ```no_run
77//! # fn main() -> Result<(), ::opener::OpenError> {
1414//! # }
1515//! ```
1616//!
17- //! ## Platform Implementation Details
18- //! On Windows the `ShellExecuteW` Windows API function is used. On Mac the system `open` command is
19- //! used. On other platforms, the `xdg-open` script is used. The system `xdg-open` is not used;
20- //! instead a version is embedded within this library.
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.
2120
2221#![ warn(
2322 rust_2018_idioms,
@@ -35,18 +34,21 @@ mod macos;
3534mod windows;
3635
3736#[ cfg( not( any( target_os = "windows" , target_os = "macos" ) ) ) ]
38- use crate :: linux_and_more:: open as open_sys ;
37+ use crate :: linux_and_more as sys ;
3938#[ cfg( target_os = "macos" ) ]
40- use crate :: macos:: open as open_sys ;
39+ use crate :: macos as sys ;
4140#[ cfg( target_os = "windows" ) ]
42- use crate :: windows:: open as open_sys ;
41+ use crate :: windows as sys ;
4342
4443use std:: {
44+ borrow:: Cow ,
45+ env,
4546 error:: Error ,
46- ffi:: OsStr ,
47+ ffi:: { OsStr , OsString } ,
4748 fmt:: { self , Display , Formatter } ,
4849 io,
49- process:: ExitStatus ,
50+ io:: Read ,
51+ process:: { Child , Command , ExitStatus , Stdio } ,
5052} ;
5153
5254/// Opens a file or link with the system default program.
@@ -58,11 +60,56 @@ use std::{
5860/// occurred as a direct result of opening the path. Errors beyond that point aren't caught. For
5961/// example, `Ok(())` would be returned even if a file was opened with a program that can't read the
6062/// file, or a dead link was opened in a browser.
63+ ///
64+ /// ## Platform Implementation Details
65+ ///
66+ /// - On Windows the `ShellExecuteW` Windows API function is used.
67+ /// - On Mac the system `open` command is used.
68+ /// - On Windows Subsystem for Linux (WSL), the system `xdg-open` will be used if available,
69+ /// otherwise the system `wslview` from [`wslu`] is used.
70+ /// - On non-WSL Linux and other platforms,
71+ /// the system `xdg-open` script is used if available, otherwise an `xdg-open` script embedded in
72+ /// this library is used.
73+ ///
74+ /// [`wslu`]: https://github.com/wslutilities/wslu/
6175pub fn open < P > ( path : P ) -> Result < ( ) , OpenError >
6276where
6377 P : AsRef < OsStr > ,
6478{
65- open_sys ( path. as_ref ( ) )
79+ sys:: open ( path. as_ref ( ) )
80+ }
81+
82+ /// Opens a file or link with the system default program, using the `BROWSER` environment variable
83+ /// when set.
84+ ///
85+ /// If the `BROWSER` environment variable is set, the program specified by it is used to open the
86+ /// path. If not, behavior is identical to [`open()`].
87+ pub fn open_browser < P > ( path : P ) -> Result < ( ) , OpenError >
88+ where
89+ P : AsRef < OsStr > ,
90+ {
91+ let mut path = path. as_ref ( ) ;
92+ if let Ok ( browser_var) = env:: var ( "BROWSER" ) {
93+ let windows_path;
94+ if is_wsl ( ) && browser_var. ends_with ( ".exe" ) {
95+ if let Some ( windows_path_2) = wsl_to_windows_path ( path) {
96+ windows_path = windows_path_2;
97+ path = & windows_path;
98+ }
99+ } ;
100+
101+ let mut browser_child = Command :: new ( & browser_var)
102+ . arg ( path)
103+ . stdin ( Stdio :: null ( ) )
104+ . stdout ( Stdio :: null ( ) )
105+ . stderr ( Stdio :: piped ( ) )
106+ . spawn ( )
107+ . map_err ( OpenError :: Io ) ?;
108+
109+ wait_child ( & mut browser_child, browser_var. into ( ) )
110+ } else {
111+ sys:: open ( path)
112+ }
66113}
67114
68115/// An error type representing the failure to open a path. Possibly returned by the [`open`]
@@ -74,10 +121,10 @@ pub enum OpenError {
74121 /// An IO error occurred.
75122 Io ( io:: Error ) ,
76123
77- /// The command exited with a non-zero exit status.
124+ /// A command exited with a non-zero exit status.
78125 ExitStatus {
79126 /// A string that identifies the command.
80- cmd : & ' static str ,
127+ cmd : Cow < ' static , str > ,
81128
82129 /// The failed process's exit status.
83130 status : ExitStatus ,
@@ -123,3 +170,56 @@ impl Error for OpenError {
123170 }
124171 }
125172}
173+
174+ #[ cfg( target_os = "linux" ) ]
175+ fn is_wsl ( ) -> bool {
176+ wsl:: is_wsl ( )
177+ }
178+
179+ #[ cfg( not( target_os = "linux" ) ) ]
180+ fn is_wsl ( ) -> bool {
181+ false
182+ }
183+
184+ #[ cfg( target_os = "linux" ) ]
185+ fn wsl_to_windows_path ( path : & OsStr ) -> Option < OsString > {
186+ use std:: os:: unix:: ffi:: OsStringExt ;
187+
188+ let output = Command :: new ( "wslpath" )
189+ . arg ( "-w" )
190+ . arg ( path)
191+ . stdin ( Stdio :: null ( ) )
192+ . stdout ( Stdio :: piped ( ) )
193+ . stderr ( Stdio :: null ( ) )
194+ . output ( )
195+ . ok ( ) ?;
196+
197+ if !output. status . success ( ) {
198+ return None ;
199+ }
200+
201+ Some ( OsString :: from_vec ( output. stdout ) )
202+ }
203+
204+ #[ cfg( not( target_os = "linux" ) ) ]
205+ fn wsl_to_windows_path ( path : & OsStr ) -> Option < OsString > {
206+ unreachable ! ( )
207+ }
208+
209+ fn wait_child ( child : & mut Child , cmd_name : Cow < ' static , str > ) -> Result < ( ) , OpenError > {
210+ let exit_status = child. wait ( ) . map_err ( OpenError :: Io ) ?;
211+ if exit_status. success ( ) {
212+ Ok ( ( ) )
213+ } else {
214+ let mut stderr_output = String :: new ( ) ;
215+ if let Some ( stderr) = child. stderr . as_mut ( ) {
216+ stderr. read_to_string ( & mut stderr_output) . ok ( ) ;
217+ }
218+
219+ Err ( OpenError :: ExitStatus {
220+ cmd : cmd_name,
221+ status : exit_status,
222+ stderr : stderr_output,
223+ } )
224+ }
225+ }
0 commit comments