Skip to main content

winprint_ext/test_utils/
null_device.rs

1use crate::printer::PrinterDevice;
2use sha2::{Digest, Sha256};
3use std::{cell::OnceCell, process::Stdio, sync::OnceLock};
4
5thread_local! {
6    static NULL_DEVICE: OnceCell<NullPrinterDevice> = const { OnceCell::new() };
7}
8static DEVICE_ID_COUNTER: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
9static PORT_AND_DRIVER_INSTALLER: OnceLock<()> = OnceLock::new();
10
11struct NullPrinterDevice {
12    printer: PrinterDevice,
13}
14
15impl NullPrinterDevice {
16    fn new() -> Self {
17        PORT_AND_DRIVER_INSTALLER.get_or_init(|| {
18            std::process::Command::new("powershell")
19                .stdin(Stdio::null())
20                .stdout(Stdio::inherit())
21                .stderr(Stdio::inherit())
22                .args([
23                    "-Command",
24                    "if (-not (Get-PrinterDriver -Name 'Generic / Text Only' -ErrorAction SilentlyContinue)) {Add-PrinterDriver 'Generic / Text Only' -ErrorAction Continue} if (-not (Get-PrinterPort -Name 'nul:' -ErrorAction SilentlyContinue)) {Add-PrinterPort -Name 'nul:' -ErrorAction Continue}",
25                ])
26                .spawn()
27                .unwrap()
28                .wait()
29                .unwrap();
30        });
31
32        let id = DEVICE_ID_COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
33
34        let exe_path = std::env::current_exe().unwrap().canonicalize().unwrap();
35        let exe_id =
36            bs58::encode(Sha256::digest(exe_path.to_string_lossy().as_bytes())).into_string();
37
38        let printer_name = format!("null-device-{}-{}", exe_id, id);
39
40        let printer = PrinterDevice::all()
41            .unwrap()
42            .into_iter()
43            .find(|p| p.name() == printer_name);
44        if let Some(printer) = printer {
45            return NullPrinterDevice { printer };
46        }
47
48        std::process::Command::new("powershell")
49            .stdin(Stdio::null())
50            .stdout(Stdio::inherit())
51            .stderr(Stdio::inherit())
52            .args([
53                "-Command",
54                &format!(
55                    "Add-Printer -Name '{}' -PortName 'nul:' -DriverName 'Generic / Text Only'",
56                    printer_name
57                ),
58            ])
59            .spawn()
60            .unwrap()
61            .wait()
62            .unwrap();
63
64        let printer = PrinterDevice::all()
65            .unwrap()
66            .into_iter()
67            .find(|p| p.name() == printer_name)
68            .unwrap();
69        NullPrinterDevice { printer }
70    }
71}
72
73impl Drop for NullPrinterDevice {
74    fn drop(&mut self) {
75        std::process::Command::new("powershell")
76            .stdin(Stdio::null())
77            .stdout(Stdio::inherit())
78            .stderr(Stdio::inherit())
79            .args([
80                "-Command",
81                &format!("Remove-Printer -Name '{}'", self.printer.name()),
82            ])
83            .spawn()
84            .unwrap()
85            .wait()
86            .unwrap();
87    }
88}
89
90/// Get a thread-local null printer device.
91/// This device is automatically managed and to be deleted when the thread ends.
92pub fn thread_local() -> PrinterDevice {
93    NULL_DEVICE.with(|f| f.get_or_init(NullPrinterDevice::new).printer.clone())
94}