1use std::collections::HashMap;
9
10use mdns_sd::{ServiceDaemon, ServiceInfo};
11
12use crate::printer::PrinterRegistry;
13
14const IPP_SERVICE: &str = "_ipp._tcp.local.";
15
16pub struct Advertiser {
19 daemon: ServiceDaemon,
20 fullnames: Vec<String>,
21}
22
23impl Advertiser {
24 pub fn register_all(registry: &PrinterRegistry, port: u16) -> mdns_sd::Result<Self> {
26 let daemon = ServiceDaemon::new()?;
27 let host = hostname();
28 let mut fullnames = Vec::new();
29 for rec in registry.read().iter() {
30 let info = service_info(&host, port, &rec.config.name, &rec.config.make_and_model)?;
31 let fullname = info.get_fullname().to_string();
32 daemon.register(info)?;
33 log::info!("mdns: registered {fullname}");
34 fullnames.push(fullname);
35 }
36 Ok(Self { daemon, fullnames })
37 }
38}
39
40impl Drop for Advertiser {
41 fn drop(&mut self) {
42 for fullname in &self.fullnames {
43 let _ = self.daemon.unregister(fullname);
44 }
45 let _ = self.daemon.shutdown();
46 }
47}
48
49fn hostname() -> String {
50 let h = std::process::Command::new("hostname")
51 .output()
52 .ok()
53 .and_then(|o| String::from_utf8(o.stdout).ok())
54 .map(|s| s.trim().to_string())
55 .filter(|s| !s.is_empty())
56 .unwrap_or_else(|| "localhost".to_string());
57 h
59}
60
61fn service_info(
62 host: &str,
63 port: u16,
64 name: &str,
65 make_and_model: &str,
66) -> mdns_sd::Result<ServiceInfo> {
67 let mut txt: HashMap<String, String> = HashMap::new();
68 txt.insert("rp".into(), format!("ipp/print/{name}"));
69 txt.insert("ty".into(), make_and_model.to_string());
70 txt.insert("note".into(), make_and_model.to_string());
71 txt.insert("product".into(), format!("({make_and_model})"));
72 txt.insert(
74 "pdl".into(),
75 "image/pwg-raster,application/vnd.cups-raster,application/octet-stream".into(),
76 );
77 txt.insert("URF".into(), "W8,SRGB24,CP1,RS203".into());
79 txt.insert("Color".into(), "F".into());
80 txt.insert("Duplex".into(), "F".into());
81 txt.insert("adminurl".into(), format!("http://{host}.local:{port}/"));
82 txt.insert("priority".into(), "0".into());
83 txt.insert("qtotal".into(), "1".into());
84 txt.insert("txtvers".into(), "1".into());
86
87 let info = ServiceInfo::new(
88 IPP_SERVICE,
89 name,
90 &format!("{host}.local."),
91 "", port,
93 txt,
94 )?
95 .enable_addr_auto();
96 Ok(info)
97}