fornwall / rust-script

Run Rust files and expressions as scripts without any setup or compilation step.
https://rust-script.org
Apache License 2.0
1.2k stars 41 forks source link

Run Hidden (On Windows) #100

Open collindutrow opened 1 year ago

collindutrow commented 1 year ago

Normally running Rust applications with #![windows_subsystem = "windows"] effectively hides the console window.

It would be nice if there were some config for rust-script or an automated way to detect scripts containing this line and ensure that rust-script itself doesn't create a console window to run the application.

I feel this is important when running automated scripts from Task Scheduler or other scripts. And I think rust-script shouldn't necessarily be popping up a new console window each time when it's undesired that it does so. Since new windows are Focus grabbing and can interfere with other things you may be doing, it would be nice to have an official way around this.

fornwall commented 1 year ago

Thanks for reporting!

I don't know much about Windows myself.

What would be the drawback of rust-script having #![windows_subsystem = "windows"] (what happens in that case when rust-script is run from the console)? Created https://github.com/fornwall/rust-script/pull/101 if you want a branch to test with.

collindutrow commented 1 year ago

Well, the normal action of a terminal application being run from the GUI on Windows is to open a console and close the console immediately when the application completes. So for most console applications on Windows, this means the console will flash on the screen and disappear a second later if you tried to double-click their binaries from the file explorer.

In Rust a way around this that I know is to specify using the windows subsystem (typically used for GUI applications under Rust to make the console not show), and then reattach the console allowing console output to still display if run from Windows command prompt but nothing to show if run directly from explorer. This might be the most appropriate approach for rust-script on Windows.

Here's a working example. Maybe someone else can comment on the efficacy or shortcomings of this method but I've never had an issue with it. (I'm running Windows 11 just to be clear)

cargo.toml

[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.9", features = ["wincon"] }

main.rs

#![windows_subsystem = "windows"]
fn main() {
    #[cfg(target_os = "windows")]
    {
        use winapi::um::wincon::{AttachConsole, ATTACH_PARENT_PROCESS};

        unsafe {
            AttachConsole(ATTACH_PARENT_PROCESS);
        }
    }

    println!("Hello, world!");
}
fornwall commented 1 year ago

@wintersnare Thanks a lot!

I added AttachConsole as you show to https://github.com/fornwall/rust-script/pull/101 (the windows-subsystem branch), so please test it out if you can!

collindutrow commented 1 year ago

https://user-images.githubusercontent.com/6328213/232650929-58855135-0017-4543-9e32-5c948e43da6e.mp4

I think windows_subsystem = "windows" may not be appropriate for the main rust-script executable. It works great when running from the GUI, but when running Rust scripts from the console even ones that do not define windows_subsystem = "windows" they cause the console to hang until the user presses Enter. I'm not sure of a way around this.

I've attached a video demonstrating this. The .ers does not have windows_subsystem = "windows" defined anywhere.

[Test 1] Using the executable included with the crate, it works normally from the console. [Test 2] The second test is run from the #101 build. [Test 3] The third test is executing the compiled script executable directly from the rust-script cache directory.

Test 1 and 3 work as expected. Test 2 'hangs' (even though the process no longer exists) until I press enter and echo __DONE__ is launched asynchronously as when running rust-script (experimental) it's returning immediately as it's not a true console application.

In my use cases windows_subsystem = "windows" hasn't been an issue as I'm not using it for console output, but looking at it from this perspective it will definitely produce unexpected results for people.

Python on Windows handles this by providing 2 executables. python.exe (console) and pythonw.exe (windows) and on Windows .py is associated with python.exe and .pyw is associated with pythonw.exe. Any script where you want to avoid a console from popping up you just change the extension to .pyw

So maybe inspiration can be taken from that.

fornwall commented 1 year ago

@wintersnare Thanks for all this helpful information!

Perhaps the python way - distributing two binaries, and using two file extensions - is the best way then?

collindutrow commented 1 year ago

@fornwall Yes, I think this would be the best way for Windows environments.

I was thinking though that in the event of an error, it should probably write a unique .log file to the user's temp directory and automatically open it for the user to still be able to read the output.

The environment variable in Windows for the user's temp directory is TEMP