Repository: nathanbabcock/ffmpeg-sidecar (language: rust)

Issue:
[Windows] Commands create popup cmd windows on each call
On Windows 10, I get several command popup windows when running commands such as `ffmpeg_version`, `check_latest_version`, and `download_ffmpeg_package`.

![cmd popup window](https://github.com/user-attachments/assets/1c1a797c-5747-489f-8580-a0e9bb1afca4)

I see that for the `FfmpegCommand` there is a `create_no_window` method that I think it does that, it flags the command process to create no windows or keep them hidden.

Could we do the same for all other commands like `curl`, `curl_to_file` etc.?


Discussion / hints:
Yes, absolutely. The fix that `create_no_window` applies is this:

```
std::os::windows::process::CommandExt::creation_flags(command, 0x08000000);
```

We can apply that to all other `Command`'s used in the library, and probably make it the default behavior as well.
Hi @nathanbabcock is there a workaround to this at the moment or calling this already fixes it for everything?

`std::os::windows::process::CommandExt::creation_flags(command, 0x08000000);`

Or do we need to wait for a PR for this? 

Thanks for the help

Fix patch:
diff --git a/src/command.rs b/src/command.rs
index a1a19d1..93878be 100644
--- a/src/command.rs
+++ b/src/command.rs
@@ -647,12 +647,10 @@ impl FfmpegCommand {
     self
   }
 
-  /// Disable creating a new console window for the spawned process on Windows.
-  /// Has no effect on other platforms. This can be useful when spawning FFmpeg
-  /// from a GUI program.
+  /// Disable subprocess console window creation on Windows.
+  /// Called automatically in the constructor.
   pub fn create_no_window(&mut self) -> &mut Self {
-    #[cfg(target_os = "windows")]
-    std::os::windows::process::CommandExt::creation_flags(self.as_inner_mut(), 0x08000000);
+    self.as_inner_mut().create_no_window();
     self
   }
 
@@ -671,7 +669,7 @@ impl FfmpegCommand {
     // Configure `FfmpegCommand`
     let mut ffmpeg_command = Self { inner };
     ffmpeg_command.set_expected_loglevel();
-    // todo: ffmpeg_command.no_overwrite();
+    ffmpeg_command.create_no_window();
     ffmpeg_command
   }
 
@@ -724,9 +722,25 @@ impl From<FfmpegCommand> for Command {
 pub fn ffmpeg_is_installed() -> bool {
   Command::new(ffmpeg_path())
     .arg("-version")
+    .create_no_window()
     .stderr(Stdio::null())
     .stdout(Stdio::null())
     .status()
     .map(|s| s.success())
     .unwrap_or_else(|_| false)
 }
+
+pub(crate) trait BackgroundCommand {
+  fn create_no_window(&mut self) -> &mut Self;
+}
+
+impl BackgroundCommand for Command {
+  /// Disable creating a new console window for the spawned process on Windows.
+  /// Has no effect on other platforms. This can be useful when spawning a command
+  /// from a GUI program.
+  fn create_no_window(&mut self) -> &mut Self {
+    #[cfg(target_os = "windows")]
+    std::os::windows::process::CommandExt::creation_flags(self, 0x08000000);
+    self
+  }
+}
diff --git a/src/download.rs b/src/download.rs
index dcb0b2f..cb69476 100644
--- a/src/download.rs
+++ b/src/download.rs
@@ -7,7 +7,10 @@ use std::{
 
 use anyhow::Context;
 
-use crate::{command::ffmpeg_is_installed, paths::sidecar_dir};
+use crate::{
+  command::{ffmpeg_is_installed, BackgroundCommand},
+  paths::sidecar_dir,
+};
 
 pub const UNPACK_DIRNAME: &str = "ffmpeg_release_temp";
 
@@ -110,6 +113,7 @@ pub fn parse_linux_version(version: &str) -> Option<String> {
 /// Invoke cURL on the command line to download a file, returning it as a string.
 pub fn curl(url: &str) -> anyhow::Result<String> {
   let mut child = Command::new("curl")
+    .create_no_window()
     .args(["-L", url])
     .stderr(Stdio::null())
     .stdout(Stdio::piped())
@@ -125,6 +129,7 @@ pub fn curl(url: &str) -> anyhow::Result<String> {
 /// Invoke cURL on the command line to download a file, writing to a file.
 pub fn curl_to_file(url: &str, destination: &str) -> anyhow::Result<ExitStatus> {
   Command::new("curl")
+    .create_no_window()
     .args(["-L", url])
     .args(["-o", destination])
     .status()
@@ -180,6 +185,7 @@ pub fn unpack_ffmpeg(from_archive: &PathBuf, binary_folder: &Path) -> anyhow::Re
 
   // Extract archive
   Command::new("tar")
+    .create_no_window()
     .arg("-xf")
     .arg(from_archive)
     .current_dir(&temp_folder)
diff --git a/src/ffprobe.rs b/src/ffprobe.rs
index 4ff2fbd..c845803 100644
--- a/src/ffprobe.rs
+++ b/src/ffprobe.rs
@@ -1,11 +1,11 @@
+use crate::command::BackgroundCommand;
+use anyhow::Context;
 use std::{env::current_exe, ffi::OsStr, path::PathBuf};
 use std::{
   path::Path,
   process::{Command, Stdio},
 };
 
-use anyhow::Context;
-
 /// Returns the path of the downloaded FFprobe executable, or falls back to
 /// assuming its installed in the system path. Note that not all FFmpeg
 /// distributions include FFprobe.
@@ -43,7 +43,10 @@ pub fn ffprobe_version() -> anyhow::Result<String> {
 /// Lower level variant of `ffprobe_version` that exposes a customized the path
 /// to the ffmpeg binary.
 pub fn ffprobe_version_with_path<S: AsRef<OsStr>>(path: S) -> anyhow::Result<String> {
-  let output = Command::new(&path).arg("-version").output()?;
+  let output = Command::new(&path)
+    .arg("-version")
+    .create_no_window()
+    .output()?;
 
   // note:version parsing is not implemented for ffprobe
 
@@ -55,6 +58,7 @@ pub fn ffprobe_version_with_path<S: AsRef<OsStr>>(path: S) -> anyhow::Result<Str
 /// executable.
 pub fn ffprobe_is_installed() -> bool {
   Command::new(ffprobe_path())
+    .create_no_window()
     .arg("-version")
     .stderr(Stdio::null())
     .stdout(Stdio::null())
diff --git a/src/log_parser.rs b/src/log_parser.rs
index 1da1d54..0a18000 100644
--- a/src/log_parser.rs
+++ b/src/log_parser.rs
@@ -499,7 +499,7 @@ pub fn parse_time_str(str: &str) -> Option<f64> {
 #[cfg(test)]
 mod tests {
   use super::*;
-  use crate::paths::ffmpeg_path;
+  use crate::{command::BackgroundCommand, paths::ffmpeg_path};
   use std::{
     io::{Cursor, Seek, SeekFrom, Write},
     process::{Command, Stdio},
@@ -508,6 +508,7 @@ mod tests {
   #[test]
   fn test_parse_version() {
     let cmd = Command::new(ffmpeg_path())
+      .create_no_window()
       .arg("-version")
       .stdout(Stdio::piped())
       // ⚠ notice that ffmpeg emits on stdout when `-version` or `-help` is passed!
@@ -527,6 +528,7 @@ mod tests {
   #[test]
   fn test_parse_configuration() {
     let cmd = Command::new(ffmpeg_path())
+      .create_no_window()
       .arg("-version")
       .stdout(Stdio::piped())
       // ⚠ notice that ffmpeg emits on stdout when `-version` or `-help` is passed!
diff --git a/src/version.rs b/src/version.rs
index 0216f67..e8fc93d 100644
--- a/src/version.rs
+++ b/src/version.rs
@@ -1,10 +1,7 @@
 use anyhow::Context;
 
-use crate::{
-  event::FfmpegEvent,
-  log_parser::FfmpegLogParser,
-  paths::ffmpeg_path,
-};
+use crate::command::BackgroundCommand;
+use crate::{event::FfmpegEvent, log_parser::FfmpegLogParser, paths::ffmpeg_path};
 use std::ffi::OsStr;
 use std::process::{Command, Stdio};
 
@@ -13,10 +10,11 @@ pub fn ffmpeg_version() -> anyhow::Result<String> {
   ffmpeg_version_with_path(ffmpeg_path())
 }
 
-/// Lower level variant of `ffmpeg_version` that exposes a customized the path
+/// Lower level variant of `ffmpeg_version` that exposes a customized path
 /// to the ffmpeg binary.
 pub fn ffmpeg_version_with_path<S: AsRef<OsStr>>(path: S) -> anyhow::Result<String> {
   let mut cmd = Command::new(&path)
+    .create_no_window()
     .arg("-version")
     .stdout(Stdio::piped()) // not stderr when calling `-version`
     .spawn()?;


Test patch: