craft-print: printer_queues lists the queues without asking any driver

On Windows, printers() reads each driver's PrintCapabilities to report
two-sided support before it returns. A networked printer in power save
can hold that query for a long time, so a Print dialog showed no printer
at all, not even the ones that were awake (storytold/wordcraft#5710,
storytold/wordcraft#5645, storytold/wordcraft#5413).

printer_queues() returns the same queues and default from EnumPrinters
and GetDefaultPrinter with two_sided unknown, and makes no driver call.
Submission still checks the job's options with the driver. On other
platforms it is printers(). printers() is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
arnav-hiwarkar
2026-11-06 04:54:11 +05:30
co-authored by Claude Opus 5.5
parent a8b996d207
commit 62b0fe5edb
4 changed files with 34 additions and 1 deletions
+4 -1
View File
@@ -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
+16
View File
@@ -422,6 +422,22 @@ pub fn printers() -> Vec<Printer> {
}
}
/// 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<Printer> {
#[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
+3
View File
@@ -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:?}");
+11
View File
@@ -43,6 +43,17 @@ fn print_ticket_provider() -> MutexGuard<'static, ()> {
}
pub(crate) fn printers() -> Vec<Printer> {
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<Printer> {
spooler_list(|_| None)
}
fn spooler_list(two_sided: impl Fn(&PrinterDevice) -> Option<bool>) -> 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()),
Err(_) => Vec::new(),