diff --git a/crates/print/README.md b/crates/print/README.md index 547af90f..49a11a2b 100644 --- a/crates/print/README.md +++ b/crates/print/README.md @@ -37,7 +37,10 @@ opens Windows printer preferences and submits jobs with copies, collation, duple - **Windows:** in this process, with no PowerShell, script or other program started, so printing works where App Control for Business or AppLocker enforce script rules. Printers come from `EnumPrinters` (local queues and `\\server\queue` connections) and the default from - `GetDefaultPrinter`. A job is drawn as vectors by the in-box `Windows.Data.Pdf` with Direct2D + `GetDefaultPrinter`. `printers` also reads each driver's PrintCapabilities to report two-sided + support, which can wait on the device (a networked printer in power save); `printer_queues` + lists the same queues without asking any driver, so a Print dialog's list never waits on a + device. A job is drawn as vectors by the in-box `Windows.Data.Pdf` with Direct2D into the XPS print API (the `winprint` crate), and copies, collation, duplex, colour, paper, orientation and "no scaling" travel in a PrintTicket that the driver validates first; a driver that would change the paper, orientation or scaling is refused before anything is diff --git a/crates/print/src/lib.rs b/crates/print/src/lib.rs index accf8392..b117cb20 100644 --- a/crates/print/src/lib.rs +++ b/crates/print/src/lib.rs @@ -422,6 +422,22 @@ pub fn printers() -> Vec { } } +/// The printers the system knows, without asking their drivers anything: [`printers`] with +/// `two_sided` unknown. On Windows [`printers`] reads each driver's PrintCapabilities, which can +/// wait on the device (a networked printer in power save), so a Print dialog that only needs +/// names lists them from this without that wait. Submission still checks the job's options with +/// the driver. Elsewhere it is [`printers`], whose `two_sided` is already unknown. +pub fn printer_queues() -> Vec { + #[cfg(windows)] + { + windows::printer_queues() + } + #[cfg(not(windows))] + { + printers() + } +} + /// 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 diff --git a/crates/print/src/tests/windows_native.rs b/crates/print/src/tests/windows_native.rs index f25f0cfb..04dcff75 100644 --- a/crates/print/src/tests/windows_native.rs +++ b/crates/print/src/tests/windows_native.rs @@ -410,6 +410,8 @@ fn windows_jobs_reach_the_spooler() { return; } let listed = spool::printers().into_iter().find(|p| p.name == name); + // The list a Print dialog shows first: the same queue, named without asking its driver. + let queue = spool::printer_queues().into_iter().find(|p| p.name == 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(); @@ -450,6 +452,7 @@ fn windows_jobs_reach_the_spooler() { 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!(queue.as_ref().is_some_and(|p| p.two_sided.is_none()), "the new printer is listed with no driver query: {queue:?}"); 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:?}"); diff --git a/crates/print/src/windows.rs b/crates/print/src/windows.rs index 0c8bbb43..20fb7176 100644 --- a/crates/print/src/windows.rs +++ b/crates/print/src/windows.rs @@ -43,6 +43,17 @@ fn print_ticket_provider() -> MutexGuard<'static, ()> { } pub(crate) fn printers() -> Vec { + spooler_list(two_sided) +} + +/// The queues and the default without asking any driver: a networked printer asleep in power +/// save can hold its driver's PrintCapabilities query for a long time, and the Print dialog +/// must not wait on it to show the queue (or any other). +pub(crate) fn printer_queues() -> Vec { + spooler_list(|_| None) +} + +fn spooler_list(two_sided: impl Fn(&PrinterDevice) -> Option) -> Vec { match PrinterDevice::all() { Ok(devices) => crate::printer_list(devices.iter().map(|d| (d.name().to_string(), two_sided(d))), default_printer().as_deref()), Err(_) => Vec::new(),