mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-10 13:01:29 +00:00
Windows: Tesseract and the --control tools start without a console window (#6735)
This commit is contained in:
2 files changed
+64
-2
No files matched your search
@@ -343,8 +343,17 @@ impl TesseractCli {
|
||||
// Known limit: only the direct child is killed at the timeout or on an early cap. A
|
||||
// wrapper script's children (grandchildren of this process) may survive the kill; the
|
||||
// cap and the timeout still bound what this run reads and how long it waits.
|
||||
let mut command = std::process::Command::new(&self.program);
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::process::CommandExt as _;
|
||||
// `CREATE_NO_WINDOW`: tesseract is a console program and the app is not, so without
|
||||
// it Windows opens a console window for every run, which flashes up during OCR. The
|
||||
// output files above are unaffected.
|
||||
command.creation_flags(0x0800_0000);
|
||||
}
|
||||
let mut child =
|
||||
std::process::Command::new(&self.program)
|
||||
command
|
||||
.args(args)
|
||||
.stdin(std::process::Stdio::null())
|
||||
.stdout(out_file)
|
||||
@@ -894,6 +903,28 @@ mod tests {
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// The program starts without a console window (Windows): the app has no console, so a
|
||||
/// console program that isn't told otherwise gets a window of its own, which flashes up on
|
||||
/// every OCR run. PowerShell stands in for tesseract and reports, through the same output file
|
||||
/// a reading comes back in, its console's window and the processes attached to that console.
|
||||
/// Both are checked, because a program started without the flag by a process that has a
|
||||
/// console shares it instead, and that console may itself have no window.
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn the_program_starts_without_a_console_window() {
|
||||
const PROBE: &str = r#"Add-Type -Namespace PdfCraftOcrProbe -Name Console -MemberDefinition '[DllImport("kernel32.dll")] public static extern System.IntPtr GetConsoleWindow(); [DllImport("kernel32.dll")] public static extern uint GetConsoleProcessList(uint[] ids, uint count);'
|
||||
$ids = New-Object uint32[] 256
|
||||
$n = [PdfCraftOcrProbe.Console]::GetConsoleProcessList($ids, 256)
|
||||
"$([PdfCraftOcrProbe.Console]::GetConsoleWindow()) $($ids[0..($n-1)] -join ',')""#;
|
||||
let root = std::env::var_os("SystemRoot").map_or_else(|| PathBuf::from(r"C:\Windows"), PathBuf::from);
|
||||
let t = TesseractCli::new(root.join(r"System32\WindowsPowerShell\v1.0\powershell.exe"));
|
||||
let text = t.run(&["-NoProfile", "-NonInteractive", "-Command", PROBE], t.timeout, t.max_output).unwrap();
|
||||
let (window, attached) = text.trim().split_once(' ').unwrap();
|
||||
assert_eq!(window, "0", "the program has a console window: {text}");
|
||||
let attached: Vec<u32> = attached.split(',').map(|id| id.parse().unwrap()).collect();
|
||||
assert!(!attached.is_empty() && !attached.contains(&std::process::id()), "the program shares this process's console: {text}");
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn make_executable(p: &Path) {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
@@ -132,10 +132,19 @@ const ICACLS: &str = "icacls.exe";
|
||||
/// A tool Windows ships, started by its full path (review on #3956). Started by bare name, a
|
||||
/// `whoami.exe`, `powershell.exe` or `icacls.exe` found first (next to the app, or earlier on the
|
||||
/// search path) would answer instead, and `whoami`'s answer is what every check here trusts.
|
||||
///
|
||||
/// Each tool is a console program and the app is not, so without `CREATE_NO_WINDOW` Windows gives
|
||||
/// every one of them a console window of its own, which flashes up while the app writes its
|
||||
/// `--control` file. The flag doesn't touch the pipes `output()` reads the answer through.
|
||||
#[cfg(windows)]
|
||||
fn system_tool(relative: &str) -> std::process::Command {
|
||||
use std::os::windows::process::CommandExt as _;
|
||||
/// `CREATE_NO_WINDOW`, from the Windows process creation flags.
|
||||
const CREATE_NO_WINDOW: u32 = 0x0800_0000;
|
||||
let root = std::env::var_os("SystemRoot").map_or_else(|| std::path::PathBuf::from(r"C:\Windows"), std::path::PathBuf::from);
|
||||
std::process::Command::new(root.join("System32").join(relative))
|
||||
let mut command = std::process::Command::new(root.join("System32").join(relative));
|
||||
command.creation_flags(CREATE_NO_WINDOW);
|
||||
command
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
@@ -384,6 +393,28 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// The tools start without a console window: the app has no console, so a tool that isn't
|
||||
/// told otherwise gets a window of its own, which flashes up. The started PowerShell reports
|
||||
/// its console's window and the processes attached to that console. Both are checked, because
|
||||
/// a tool started without the flag by a process that has a console shares it instead, and that
|
||||
/// console may itself have no window (a test run started by a service or an IDE).
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn the_tools_start_without_a_console_window() {
|
||||
const PROBE: &str = r#"Add-Type -Namespace PdfCraftProbe -Name Console -MemberDefinition '[DllImport("kernel32.dll")] public static extern System.IntPtr GetConsoleWindow(); [DllImport("kernel32.dll")] public static extern uint GetConsoleProcessList(uint[] ids, uint count);'
|
||||
$ids = New-Object uint32[] 256
|
||||
$n = [PdfCraftProbe.Console]::GetConsoleProcessList($ids, 256)
|
||||
"$([PdfCraftProbe.Console]::GetConsoleWindow()) $($ids[0..($n-1)] -join ',')""#;
|
||||
let out = system_tool(POWERSHELL).args(["-NoProfile", "-NonInteractive", "-Command", PROBE]).output().unwrap();
|
||||
// The answer still comes back through the pipe.
|
||||
let text = String::from_utf8_lossy(&out.stdout);
|
||||
assert!(out.status.success(), "{text} {}", String::from_utf8_lossy(&out.stderr));
|
||||
let (window, attached) = text.trim().split_once(' ').unwrap();
|
||||
assert_eq!(window, "0", "the tool has a console window: {text}");
|
||||
let attached: Vec<u32> = attached.split(',').map(|id| id.parse().unwrap()).collect();
|
||||
assert!(!attached.is_empty() && !attached.contains(&std::process::id()), "the tool shares this process's console: {text}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn other_owners_readers_and_missing_lists_are_refused() {
|
||||
let other = "S-1-5-21-1-2-3-1002";
|
||||
|
||||
Reference in new issue
Block a user