ipp_printer_app/device.rs
1//! [`DeviceBackend`] trait: enumerate physical devices, resolve driver names,
2//! poll live status.
3
4use crate::flags::PrinterReason;
5use crate::printer::PrinterConfig;
6
7/// The media currently loaded in a device, as reported by a live poll. Drives
8/// the dynamic `media-ready` / `media-col-ready` IPP attributes.
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct ReadyMedia {
11 /// PWG self-describing media name (e.g. `om_40x30mm_40x30mm`).
12 pub name: String,
13 /// Width/height in hundredths of a millimetre (PWG units).
14 pub size_hmm: [i32; 2],
15 /// PWG media-type keyword (e.g. `labels`).
16 pub media_type: String,
17}
18
19/// Live status reported by [`DeviceBackend::poll_status`]. Returning `None`
20/// from the poll means "no update"; returning `Some(PollStatus)` replaces the
21/// reasons and, when the optional fields are set, the dynamic media/supply
22/// attributes.
23#[derive(Debug, Clone)]
24pub struct PollStatus {
25 /// Fresh `printer-state-reasons`.
26 pub reasons: PrinterReason,
27 /// Currently-loaded media, if the device can report it.
28 pub ready_media: Option<ReadyMedia>,
29 /// Remaining-supply level 0–100 (e.g. labels left on the roll), if known.
30 pub supply_percent: Option<u8>,
31}
32
33impl Default for PollStatus {
34 fn default() -> Self {
35 Self {
36 reasons: PrinterReason::empty(),
37 ready_media: None,
38 supply_percent: None,
39 }
40 }
41}
42
43impl PollStatus {
44 /// Convenience constructor for backends that only report reasons.
45 pub fn from_reasons(reasons: PrinterReason) -> Self {
46 Self {
47 reasons,
48 ..Default::default()
49 }
50 }
51}
52
53/// One physical device surfaced by [`DeviceBackend::list`].
54#[derive(Debug, Clone)]
55pub struct DiscoveredDevice {
56 /// Human-readable description (becomes the DNS-SD instance name / info).
57 pub info: String,
58 /// Device URI the framework opens and persists.
59 pub uri: String,
60 /// IEEE 1284 device-id string used to resolve a driver.
61 pub device_id: String,
62}
63
64/// Enumerate physical printers and report their live health.
65///
66/// Implementations describe how to discover devices (e.g. via sysfs, BlueZ,
67/// USB enumeration) and how to map their identifying strings to a driver
68/// name registered with the framework.
69///
70/// `driver_for_device` is synchronous (pure string matching); `list`,
71/// `poll_status`, and `identify` are async so a backend can await its device
72/// transport (USB, Bluetooth, BLE) — e.g. a BLE LE scan or a per-device probe.
73#[async_trait::async_trait]
74pub trait DeviceBackend: Send + Sync {
75 /// Discover the currently-attached devices. Returning an owned `Vec` (vs a
76 /// borrowed callback) keeps the method cleanly `async` — a backend can
77 /// `.await` a scan/probe without lifetime gymnastics around an emit closure.
78 async fn list(&self) -> Vec<DiscoveredDevice>;
79
80 /// Map a device's IEEE 1284 `device-id` string and URI to a driver name
81 /// that this backend recognises. Return `None` for "this isn't one of
82 /// mine, skip it".
83 fn driver_for_device(&self, device_id: &str, device_uri: &str) -> Option<String>;
84
85 /// Query live status for a registered printer. The background status loop
86 /// calls this on each idle printer and updates the IPP attributes when the
87 /// value changes. Returning `None` means "no update" (keep whatever the
88 /// registry already holds); returning `Some` carries the fresh reasons and,
89 /// optionally, the loaded media and remaining-supply level.
90 async fn poll_status(&self, _config: &PrinterConfig) -> Option<PollStatus> {
91 None
92 }
93
94 /// Handle an `Identify-Printer` request (PWG 5100.14 §5.1) — make the
95 /// physical device announce itself (beep, flash an LED, …). `actions`
96 /// holds the requested `identify-actions` keywords (`display`, `sound`,
97 /// `flash`, `speak`); an empty slice means "use the default action".
98 /// Default: no-op.
99 async fn identify(&self, _config: &PrinterConfig, _actions: &[String]) {}
100}