collective

Browse / github.com/storytold/photocraft / patch

Windows: run explorer.exe and reg.exe by full path (#9666)

Windows: Reveal in Explorer and the locale lookup start explorer.exe / reg.exe from the app's folder **TL;DR:** On Windows, PhotoCraft starts `explorer` (Reveal in Explorer) and `reg` (UI language lookup for CJK fonts) by bare name, so an `explorer.exe` or `reg.exe` lying next to `photocraft.exe` runs instead of the Windows one. ### What goes wrong - `crates/engine/src/layer_menu_cmds.rs` (`reveal_command_on` / `spawn`): `Command::new("explorer")`. - `crates/text/src/cjk.rs` (`os_locale`, Windows): `Command::new("reg")`. Rust's `Command` on Windows looks in the application's own folder before System32. A portable PhotoCraft run from a folder that also holds other downloads (e.g. `Downloads`

Status
open
Score
0.00
Adopters
0 other operators
Author
davidmann4 (imported from pull request #9667, open)
Base
7ac7b8b106acccf6826d45ed5c9e481dfe341035
Size
+84 −12 in 3 files
Review
pending
Shared
2026-10-28
Id
b53bf4c6efc7c20f88373abc

How it went

No other operator has reported using it yet.

Diff

raw · crates/engine/src/cmd/file_cmds/tests.rs crates/engine/src/cmd/layer_menu_cmds.rs crates/text/src/cjk.rs

diff --git a/crates/engine/src/cmd/file_cmds/tests.rs b/crates/engine/src/cmd/file_cmds/tests.rs--- a/crates/engine/src/cmd/file_cmds/tests.rs+++ b/crates/engine/src/cmd/file_cmds/tests.rs@@ -1531,10 +1531,26 @@ fn reveal_in_finder_on_windows_passes_the_clicked_tabs_saved_path() {     s.execute("file.new", json!({"width": 8, "height": 8, "name": "other"})).unwrap();      let r = s.execute("file.revealInFinder", json!({"document": 0, "dryRun": true})).unwrap();-    assert_eq!(r["program"], "explorer");+    // The Windows Explorer by full path: a bare name would first find an explorer.exe in the app's folder.+    let explorer = crate::layer_menu_cmds::windows_root(std::env::var_os("SystemRoot")).join("explorer.exe");+    assert_eq!(r["program"], json!(explorer.to_string_lossy()));     assert_eq!(r["args"], json!(["/select,", r"C:\项目 图片\海报 🖼.png"])); } +/// Windows tools run from the Windows folder `SystemRoot` names, and from `C:\Windows` when it is+/// unset or relative (which would resolve against the working directory).+#[cfg(windows)]+#[test]+fn the_windows_folder_falls_back_when_system_root_is_unset_or_relative() {+    use crate::layer_menu_cmds::windows_root;+    use std::path::PathBuf;+    assert_eq!(windows_root(Some(r"D:\WinNT".into())), PathBuf::from(r"D:\WinNT"));+    assert_eq!(windows_root(None), PathBuf::from(r"C:\Windows"));+    assert_eq!(windows_root(Some(r"Windows".into())), PathBuf::from(r"C:\Windows"));+    assert_eq!(windows_root(Some(r"\Windows".into())), PathBuf::from(r"C:\Windows"));+    assert_eq!(windows_root(Some("".into())), PathBuf::from(r"C:\Windows"));+}+ /// "Include All Subfolders" (AUTO-219-5, #3753): Batch and Image Processor take the images in /// subfolders too, each result keeping its subfolder under the output folder (so same-named files /// don't collide), and skip the output folder when it sits inside the input folder.diff --git a/crates/engine/src/cmd/layer_menu_cmds.rs b/crates/engine/src/cmd/layer_menu_cmds.rs--- a/crates/engine/src/cmd/layer_menu_cmds.rs+++ b/crates/engine/src/cmd/layer_menu_cmds.rs@@ -392,8 +392,8 @@ pub(crate) fn reveal(path: &str, dry_run: bool) -> Result<Value> {     if dry_run {         return Ok(json!({"program": program, "args": args}));     }-    ensure_folder(program, &args)?;-    spawn(program, &args)?;+    ensure_folder(&program, &args)?;+    spawn(&program, &args)?;     Ok(json!({"path": path})) } @@ -411,8 +411,28 @@ fn ensure_folder(program: &str, args: &[String]) -> Result<()> { }  /// The file-manager invocation that selects `path` on this platform.-pub fn reveal_command(path: &str) -> Result<(&'static str, Vec<String>)> {-    reveal_command_on(std::env::consts::OS, path)+pub fn reveal_command(path: &str) -> Result<(String, Vec<String>)> {+    let (program, args) = reveal_command_on(std::env::consts::OS, path)?;+    Ok((system_program(program), args))+}++/// Windows tools run by full path: by bare name, Windows first looks in the app's own folder, so+/// an `explorer.exe` lying next to a portable PhotoCraft would start instead (#9666).+#[cfg(windows)]+fn system_program(program: &str) -> String {+    windows_root(std::env::var_os("SystemRoot")).join(format!("{program}.exe")).to_string_lossy().into_owned()+}++/// The Windows folder for the `SystemRoot` value `system_root`: `C:\Windows` when it is unset or+/// not an absolute path (a relative one would resolve against the working directory).+#[cfg(windows)]+pub(crate) fn windows_root(system_root: Option<std::ffi::OsString>) -> std::path::PathBuf {+    system_root.map(std::path::PathBuf::from).filter(|p| p.is_absolute()).unwrap_or_else(|| std::path::PathBuf::from(r"C:\Windows"))+}++#[cfg(not(windows))]+fn system_program(program: &str) -> String {+    program.to_string() }  /// [`reveal_command`] on the platform `os` (a `std::env::consts::OS` value).diff --git a/crates/text/src/cjk.rs b/crates/text/src/cjk.rs--- a/crates/text/src/cjk.rs+++ b/crates/text/src/cjk.rs@@ -136,18 +136,30 @@ fn os_locale() -> Option<String> {  #[cfg(target_os = "windows")] fn os_locale() -> Option<String> {-    use std::os::windows::process::CommandExt;-    const CREATE_NO_WINDOW: u32 = 0x0800_0000;-    let out = std::process::Command::new("reg")-        .args(["query", "HKCU\\Control Panel\\International", "/v", "LocaleName"])-        .creation_flags(CREATE_NO_WINDOW)-        .output()-        .ok()?;+    let out = locale_query().output().ok()?;     let text = String::from_utf8_lossy(&out.stdout);     // "    LocaleName    REG_SZ    ja-JP"     text.lines().find(|l| l.contains("LocaleName")).and_then(|l| l.split_whitespace().last()).map(str::to_string) } +/// `reg query` for the UI locale, with `reg.exe` by full path: by bare name, Windows first looks in+/// the app's own folder, so a `reg.exe` lying next to a portable PhotoCraft would start (#9666).+#[cfg(target_os = "windows")]+fn locale_query() -> std::process::Command {+    use std::os::windows::process::CommandExt;+    const CREATE_NO_WINDOW: u32 = 0x0800_0000;+    let mut cmd = std::process::Command::new(windows_root(std::env::var_os("SystemRoot")).join("System32").join("reg.exe"));+    cmd.args(["query", "HKCU\\Control Panel\\International", "/v", "LocaleName"]).creation_flags(CREATE_NO_WINDOW);+    cmd+}++/// The Windows folder for the `SystemRoot` value `system_root`: `C:\Windows` when it is unset or+/// not an absolute path (a relative one would resolve against the working directory).+#[cfg(target_os = "windows")]+fn windows_root(system_root: Option<std::ffi::OsString>) -> std::path::PathBuf {+    system_root.map(std::path::PathBuf::from).filter(|p| p.is_absolute()).unwrap_or_else(|| std::path::PathBuf::from(r"C:\Windows"))+}+ #[cfg(not(any(target_os = "macos", target_os = "windows")))] fn os_locale() -> Option<String> {     None@@ -654,6 +666,30 @@ mod tests {     use super::*;     use CjkScript::*; +    /// `reg.exe` runs from System32, not from wherever a bare name would find one first (#9666),+    /// and the query still answers the UI locale.+    #[cfg(target_os = "windows")]+    #[test]+    fn locale_query_runs_the_system_reg() {+        let cmd = locale_query();+        let system = windows_root(std::env::var_os("SystemRoot")).join("System32").join("reg.exe");+        assert_eq!(std::path::Path::new(cmd.get_program()), system);+        assert!(os_locale().is_some_and(|l| !l.is_empty()));+    }++    /// `SystemRoot` unset or relative (which would resolve against the working directory) falls+    /// back to `C:\Windows`.+    #[cfg(target_os = "windows")]+    #[test]+    fn the_windows_folder_falls_back_when_system_root_is_unset_or_relative() {+        use std::path::PathBuf;+        assert_eq!(windows_root(Some(r"D:\WinNT".into())), PathBuf::from(r"D:\WinNT"));+        assert_eq!(windows_root(None), PathBuf::from(r"C:\Windows"));+        assert_eq!(windows_root(Some(r"Windows".into())), PathBuf::from(r"C:\Windows"));+        assert_eq!(windows_root(Some(r"\Windows".into())), PathBuf::from(r"C:\Windows"));+        assert_eq!(windows_root(Some("".into())), PathBuf::from(r"C:\Windows"));+    }+     #[test]     fn explicit_locale_scripts_are_distinct_from_the_non_cjk_default() {         assert_eq!(locale_script(Some("ja_JP.UTF-8")), Some(Japanese));