From 65c9b2e5ec895058af06ebef992f30aaa91e2dee Mon Sep 17 00:00:00 2001 From: SteamSmith <341182916+SteamSmith@users.noreply.github.com> Date: Mon, 12 Oct 2026 01:16:07 -0400 Subject: [PATCH] speech: launch Windows PowerShell by full path for Read Aloud (#792) Read Aloud on Windows started `powershell` by name, so the program search decided which executable ran. Start %SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe instead (C:\Windows when SystemRoot is unset), as PdfCraft already does for rundll32. The command line and stdin handling are unchanged. Adds speech::tests::powershell_is_launched_from_the_system_folder. Co-authored-by: Claude Opus 5.5 Co-authored-by: Brandon Thomas --- crates/engine/src/speech.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/speech.rs b/crates/engine/src/speech.rs index e97cce9..9530c81 100644 --- a/crates/engine/src/speech.rs +++ b/crates/engine/src/speech.rs @@ -496,6 +496,15 @@ fn worker(shared: &(Mutex, Condvar), backend: Backend) { } } +/// Windows PowerShell by its full path, `\System32\WindowsPowerShell\v1.0\powershell.exe` +/// (`C:\Windows` when `SystemRoot` is unset), rather than whichever `powershell` the program search +/// finds first. +#[cfg(any(target_os = "windows", test))] +fn windows_powershell(system_root: Option) -> std::path::PathBuf { + let root = system_root.filter(|r| !r.is_empty()).unwrap_or_else(|| "C:\\Windows".into()); + std::path::Path::new(&root).join("System32").join("WindowsPowerShell").join("v1.0").join("powershell.exe") +} + /// Speak one sentence with the system's speech program, stopping early when `changed()`. #[cfg(not(target_arch = "wasm32"))] fn speak(text: &str, rate: f32, changed: &dyn Fn() -> bool) -> Spoken { @@ -517,7 +526,7 @@ fn speak(text: &str, rate: f32, changed: &dyn Fn() -> bool) -> Spoken { let script = format!( "Add-Type -AssemblyName System.Speech; $s = New-Object System.Speech.Synthesis.SpeechSynthesizer; $s.Rate = {r}; $s.Speak([Console]::In.ReadToEnd())" ); - let mut c = Command::new("powershell"); + let mut c = Command::new(windows_powershell(std::env::var_os("SystemRoot"))); c.args(["-NoProfile", "-NonInteractive", "-Command", &script]); tries.push(c); } @@ -571,6 +580,18 @@ mod tests { v.iter().map(|u| u.text.as_str()).collect() } + #[test] + fn powershell_is_launched_from_the_system_folder() { + let tail = std::path::Path::new("System32").join("WindowsPowerShell").join("v1.0").join("powershell.exe"); + let p = windows_powershell(Some("D:\\Win".into())); + assert!(p.starts_with("D:\\Win") && p.ends_with(&tail), "{}", p.display()); + // No SystemRoot, or an empty one: the default Windows folder. + for root in [None, Some("".into())] { + let p = windows_powershell(root); + assert!(p.starts_with("C:\\Windows") && p.ends_with(&tail), "{}", p.display()); + } + } + #[test] fn splits_sentences_but_not_abbreviations() { let t = "Smith et al. found 3.5 times more (see Fig. 2). J. Smith agreed! Did they? Yes… Pick one, e.g. this one.";