Skip to main content

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}