How to open a webbrowser at a URL on windows


#1

I want a rust program to try to open a URL on windows using the default browser. I tried using std::process::Command::new("cmd.exe").arg("/C").arg("start").arg(&url) but it just errors out on Windows 7. I found the webbrowser crate but in the source code it does exactly the same thing. (I tried it anyway, and it didn’t work.)

I know that rustup has this code (or at least a non-stable one does), but I only use stable.


#2

The open crate should do the trick. That said, it has a really weird bug where it won’t work if your program terminates too soon after you tell it to open a URL. It’s fine for everything else, though.

As for the cmd thing, have you tried passing "start URL" as a single argument?


#3

I looked at open’s source code and mine was almost the same. So I changed mine to match and it still doesn’t work. I’ve tried with simple urls like http://www.some.org and complex ones like https://www.some.org/where?x=123&y=456 but neither works.

Opening a URL seems pretty fundamental to me so I hope it ends up being added to the std library.


#4

I have now tried using ShellExecuteW as used here: rustup code (scroll to line 377) which is similar to that used here: url_open crate (scroll to line 34).

My code (Windows-specific) is here: Playground.

The ShellExecuteW call appears to work, but it does not start up a web browser. My default browser is Firefox, but Windows start understands that.

I’ve now found the problem (hinted at by someone earlier): timing. I now call open_url and sleep for 10 secs and this is sufficient for the browser to be started.

But now I have a new problem: when my program ends (which it does after opening the browser), it closes the browser. I want it to open the web page, terminate, and leave the web browser running…

So maybe ShellExecuteW is the wrong thing to be calling?


#5

My use case is that someone might run my console app with -h and get the usual help text, but I also want to offer a --doc option which will print a URL of where the docs are on my website, and also open the user’s browser at that URL if possible.

I tried ShellExecuteW, libc’s system but neither are any better than process::Command. Here’s what I’ve got:

fn open_url(url: &str) -> bool {
    if let Ok(mut child) = Command::new("cmd.exe")
            .arg("/C").arg("start").arg("").arg(&url).spawn() {
        thread::sleep(time::Duration::new(3, 0)); // On windows need to allow time for browser to start
        if let Ok(status) = child.wait() {
            return status.success();
        }
    }
    false
}

Good behavior: If the user’s web browser is already open, this works fine. It opens a new tab in the browser at the given url and then continues (in my case terminates), leaving the new tab in the browser.

Bad behavior: But if the user doesn’t have a browser open, it opens the browser at the url, then after terminating, the browser is unceremoniously shut down. And because of this failure case, the open_url() function shown here is useless.

Incidentally I’ve observed the same good and bad behaviors when using ShellExecuteW.


#6

I’ve had another go at this. I’ve copied & slightly modified code from sccache.

Here’s the calling code:

fn doc() -> Result<i32> {
    let reply = run_detached("cmd.exe /C start \"\" doc.html");
    thread::sleep(time::Duration::new(5, 0)); // Windows needs time to get going...
    reply
}

and here’s the copied/modified code:

fn run_detached(cmd: &str) -> Result<i32> {
    //use std::error::Error as StdError;
    use std::io::Error;
    use std::mem;
    use std::ptr;
    use winapi::shared::minwindef::{TRUE, FALSE, DWORD};
    use winapi::um::handleapi::CloseHandle;
    use winapi::um::processthreadsapi::{CreateProcessW, PROCESS_INFORMATION,
                                        STARTUPINFOW};
    use winapi::um::winbase::{CREATE_UNICODE_ENVIRONMENT, DETACHED_PROCESS,
                              CREATE_NEW_PROCESS_GROUP};
    use widestring::WideCString;
    let cmd = cmd.replace("&", "^&");
    let cli = WideCString::from_str(&cmd).unwrap();
    let mut pi = PROCESS_INFORMATION {
        hProcess: ptr::null_mut(),
        hThread: ptr::null_mut(),
        dwProcessId: 0,
        dwThreadId: 0,
    };
    let mut si: STARTUPINFOW = unsafe { mem::zeroed() };
    si.cb = mem::size_of::<STARTUPINFOW>() as DWORD;
    if unsafe {
            CreateProcessW(
                cli.as_ptr(),       // appname or command line
                ptr::null_mut(),    // command line or NULL and all above
                ptr::null_mut(),    // security
                ptr::null_mut(),    // security
                FALSE,              // inherit handles
                CREATE_UNICODE_ENVIRONMENT | DETACHED_PROCESS |
                CREATE_NEW_PROCESS_GROUP,   // flags
                ptr::null_mut(),    // env
                ptr::null(),        // cwd
                &mut si,            // startup info
                &mut pi)            // process info
                == TRUE } {
        unsafe {
            CloseHandle(pi.hProcess);
            CloseHandle(pi.hThread);
        }
        Ok(0)
    } else {
        bail!("failed to run '{}': {:?}", cmd, Error::last_os_error())
        //bail!("failed to run {}: {}",
        //      cmd, Error::last_os_error().description())
    }
}

When I do this in the console it works perfectly:
cmd.exe /C start "" doc.html
But when I run the program, it doesn’t work:

V:\myapp>myapp.exe --doc
Error: failed to run 'cmd.exe /C start "" doc.html': Os { code: 123, kind: Other, message: "The filename, directory name, or volume label syntax is incorrect." }

BTW It is best to give start an empty string as its first argument.

PS Cargo.toml:

[dependencies]
...
widestring = "0.3"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = ["winuser", "handleapi",
                                        "processthreadsapi"] }

#7

My mistake!

Using std::process::Command works fine. The problem I had was I always tested using cargo run. If I run the .exe directly it correctly opens the web browser if not already running, or opens a new tab if it is, and in either case, leaves the web browser after the rust .exe terminates.

See https://github.com/rust-lang/rust/issues/50679