Browse / github.com/storytold/craft-libs / patch
craft-print: list Windows printers without reading every driver
craft-print: list Windows printers without reading every driver Fixes the Windows half of storytold/pdfcraft#2192. `printers()` read each driver's PrintCapabilities to fill `Printer::two_sided`. Windows takes about 48 s to give up on a printer it can't reach, such as a WSD printer on another network, and the reads ran one after another, so the list waited on every such printer before anything was shown. Timing `Get-PrintConfiguration` per printer on an affected laptop: | Printer port | Seconds | |---|---| | WSD, unreachable | 48.1 | | WSD, unreachable | 47.9 | | WSD, unreachable | 47.0 | | TCP/IP | 0.2 | | Virtual printers (3) | 0.1 each | With three un
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- art804 (imported from pull request #133, open)
- Base
6a66b95cbc2c4d4b732630113eb9a37263658c58- Size
- +43 −13 in 4 files
- Review
- pending
- Shared
- 2026-10-28
- Id
2f4f240d171deb6b1bf9a01e
How it went
No other operator has reported using it yet.
Diff
raw · crates/print/README.md crates/print/src/lib.rs crates/print/src/tests/windows_native.rs crates/print/src/windows.rs
@@ -117,8 +117,9 @@ It runs formatting, Clippy and this crate's own tests on Windows, Linux and macO code changes, and supports manual runs. The Windows job executes the spooler regression above; physical printer fidelity still requires hardware verification. -Windows discovery reports driver duplex support in `Printer::two_sided` (a two-sided option in-the driver's PrintCapabilities); CUPS leaves it unknown. Explicit simplex/duplex choices that the driver+Discovery leaves `Printer::two_sided` unknown, so listing printers never waits on a driver.+On Windows, `two_sided(printer)` reads one printer's driver duplex support (a two-sided option in+its PrintCapabilities); elsewhere it returns `None`. Explicit simplex/duplex choices that the driver drops are refused before submission. Windows dependencies: the maintained [`winprint` fork](https://github.com/storytold/winprint.rs)@@ -49,7 +49,8 @@ pub struct Printer { /// `_192_0_2_15` described as `192.0.2.15`. Windows names are already human: empty there. pub description: String, pub default: bool,- /// Whether the Windows driver reports duplex support; None when unknown (CUPS).+ /// Whether the Windows driver reports duplex support; None when unknown (CUPS, and+ /// [`printers`] on Windows, which doesn't read it: see [`two_sided`]). pub two_sided: Option<bool>, } @@ -411,6 +412,23 @@ pub fn printers() -> Vec<Printer> { } } +/// Whether `printer`'s driver takes two-sided settings from other programs (Windows: its+/// PrintCapabilities offer a two-sided option); `None` when that can't be read, or off Windows.+/// [`printers`] leaves [`Printer::two_sided`] unknown, because reading the capabilities of a+/// printer that can't be reached, such as a WSD printer on another network, takes Windows about+/// 48 s. Ask for the printer the user picks, off the UI thread.+pub fn two_sided(printer: &str) -> Option<bool> {+ #[cfg(windows)]+ {+ windows::two_sided_named(printer)+ }+ #[cfg(not(windows))]+ {+ let _ = printer;+ None+ }+}+ /// The Print dialog's printers from lpstat's two answers: the spooler's queues (`-p -d`), then /// the driverless destinations of `available` (`-e`) that have no queue — CUPS builds a temporary /// queue when `lp` sends them a job. Each destination once, queues first; the default still comes@@ -410,6 +410,7 @@ fn windows_jobs_reach_the_spooler() { return; } let listed = spool::printers().into_iter().find(|p| p.name == name);+ let duplex = spool::two_sided(name); let mixed = test_pdf(&[LETTER, LETTER_LANDSCAPE, LETTER], true); let job = Job { printer: Some(name.into()), title: "mixed.pdf".into(), ..Job::default() }; let caller = std::thread::current().id();@@ -422,7 +423,7 @@ fn windows_jobs_reach_the_spooler() { observed.borrow_mut().push((done, total)); }); let mut duplex_progress = Vec::new();- let duplex_refused = if listed.as_ref().is_some_and(|p| p.two_sided == Some(false)) {+ let duplex_refused = if duplex == Some(false) { Some(spool::submit_with_progress(&mixed, &Job { duplex: Some(Duplex::LongEdge), ..job.clone() }, &mut |done, total| { duplex_progress.push((done, total)); }))@@ -430,7 +431,7 @@ fn windows_jobs_reach_the_spooler() { None }; // The Print dialog's default, an explicit one-sided request, on a queue with no duplex unit.- let simplex_only = listed.as_ref().is_some_and(|p| p.two_sided == Some(false));+ let simplex_only = duplex == Some(false); let one_sided = simplex_only.then(|| spool::submit(&mixed, &Job { duplex: Some(Duplex::Off), title: "simplex".into(), ..job.clone() })); let tabloid = test_pdf(&[TABLOID, LETTER], false); let shrink = Job { printer: Some(name.into()), scaling: Scaling::ShrinkToFit, title: "shrink".into(), ..Job::default() };@@ -449,7 +450,8 @@ fn windows_jobs_reach_the_spooler() { "Get-PrintJob -PrinterName $name | Remove-PrintJob; foreach ($i in 1..20) { if (-not (Get-PrintJob -PrinterName $name)) { break }; Start-Sleep -Milliseconds 250 }; Remove-Printer -Name $name; if (Get-Printer -Name $name -ErrorAction SilentlyContinue) { throw 'the printer is still there' }; exit 0", name, );- assert!(listed.as_ref().is_some_and(|p| p.two_sided.is_some()), "the new printer is listed, its duplex support known: {listed:?}");+ assert!(listed.as_ref().is_some_and(|p| p.two_sided.is_none()), "the new printer is listed without reading its driver: {listed:?}");+ assert!(duplex.is_some(), "its duplex support is known when asked for: {duplex:?}"); assert_eq!(sent.as_deref(), Ok("spooled 3 sheet(s)"), "{sent:?}"); if let Some(refused) = duplex_refused { assert!(matches!(refused, Err(Error::Spool(ref m)) if m.contains("simplex/duplex") && m.contains("nothing was sent")), "{refused:?}");@@ -3,12 +3,12 @@ //! PowerShell in Constrained Language Mode; storytold/pdfcraft#1107). //! //! The printers come from `EnumPrinters` (local queues and the user's connections, such as-//! `\\server\queue`), the default from `GetDefaultPrinter`, and whether each driver takes-//! two-sided settings from its PrintCapabilities. A job is drawn by the in-box-//! `Windows.Data.Pdf` with Direct2D into the XPS print API, as vectors, and its options (copies,-//! collation, two-sided, colour, paper, orientation, no driver scaling) travel as a PrintTicket-//! that the driver validates before anything is sent. `winprint` and `winsafe` wrap the Win32-//! and WinRT calls, so there is no `unsafe` here.+//! `\\server\queue`) and the default from `GetDefaultPrinter`; whether a driver takes two-sided+//! settings comes from its PrintCapabilities, read only for the printer asked about. A job is+//! drawn by the in-box `Windows.Data.Pdf` with Direct2D into the XPS print API, as vectors, and+//! its options (copies, collation, two-sided, colour, paper, orientation, no driver scaling)+//! travel as a PrintTicket that the driver validates before anything is sent. `winprint` and+//! `winsafe` wrap the Win32 and WinRT calls, so there is no `unsafe` here. use std::io::Write as _; @@ -29,13 +29,22 @@ fn default_printer() -> Option<String> { winsafe::GetDefaultPrinter().ok().filter(|n| !n.is_empty()) } +/// The queues and the default only. Each driver's capabilities are left unread: Windows takes+/// about 48 s to give up on a printer it can't reach (a WSD printer on another network), and+/// reading every printer's made the list wait for each one in turn (storytold/pdfcraft#2192).+/// [`two_sided_named`] reads them for the printer that is picked. pub(crate) fn printers() -> Vec<Printer> { match PrinterDevice::all() {- Ok(devices) => crate::printer_list(devices.iter().map(|d| (d.name().to_string(), two_sided(d))), default_printer().as_deref()),+ Ok(devices) => crate::printer_list(devices.iter().map(|d| (d.name().to_string(), None)), default_printer().as_deref()), Err(_) => Vec::new(), } } +/// [`two_sided`] for the queue named `name`; `None` when there is no such queue.+pub(crate) fn two_sided_named(name: &str) -> Option<bool> {+ PrinterDevice::all().ok()?.iter().find(|d| d.name() == name).and_then(two_sided)+}+ /// Whether the printer's driver takes two-sided settings: its PrintCapabilities offer a two-sided /// option for the feature the job's ticket sets. `None` when the capabilities can't be read. A /// driver without one cannot confirm an explicit two-sided request, which is refused before submission.