mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-10 20:42:12 +00:00
Merge pull request #618 from arnav-hiwarkar/fix/print-queues-without-driver-queries
craft-print: printer_queues lists the queues without asking any driver
This commit is contained in:
4 files changed
+54
-1
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -409,7 +409,15 @@ fn windows_jobs_reach_the_spooler() {
|
||||
eprintln!("skipping the real print: the test printer could not be prepared: {why}");
|
||||
return;
|
||||
}
|
||||
let queries = || crate::windows::CAPABILITY_QUERIES.with(std::cell::Cell::get);
|
||||
let start = queries();
|
||||
let listed = spool::printers().into_iter().find(|p| p.name == name);
|
||||
let probed = queries().saturating_sub(start);
|
||||
// The list a Print dialog shows first: the same queue, named without asking its driver (a
|
||||
// networked printer in power save can hold that query, storytold/wordcraft#5710).
|
||||
let start = queries();
|
||||
let queue = spool::printer_queues().into_iter().find(|p| p.name == name);
|
||||
let unprobed = queries().saturating_sub(start);
|
||||
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 +458,9 @@ 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, its duplex support unknown: {queue:?}");
|
||||
assert!(probed > 0, "printers() asks each driver, so the count sees driver queries: {probed}");
|
||||
assert_eq!(unprobed, 0, "printer_queues() asks no driver");
|
||||
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:?}");
|
||||
|
||||
@@ -43,16 +43,39 @@ 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)
|
||||
}
|
||||
|
||||
/// The spooler's queues (`EnumPrinters`, local queues and connections) with the default marked,
|
||||
/// each given the two-sided support `two_sided` reports for it; empty when the spooler can't list
|
||||
/// them.
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
thread_local! {
|
||||
/// The driver capability queries printer discovery made on this thread, so a test can show
|
||||
/// that [`printer_queues`] makes none.
|
||||
pub(crate) static CAPABILITY_QUERIES: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
|
||||
}
|
||||
|
||||
/// 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.
|
||||
fn two_sided(device: &PrinterDevice) -> Option<bool> {
|
||||
#[cfg(test)]
|
||||
CAPABILITY_QUERIES.with(|n| n.set(n.get().saturating_add(1)));
|
||||
let capabilities = {
|
||||
let _provider = print_ticket_provider();
|
||||
PrintCapabilities::fetch(device)
|
||||
|
||||
Reference in new issue
Block a user