e9c5030190
apple / swift (push) Successful in 1m7s
audit / cargo-audit (push) Successful in 1m14s
ci / rust (push) Failing after 49s
ci / web (push) Successful in 52s
windows-host / package (push) Failing after 2m58s
ci / docs-site (push) Successful in 1m5s
android / android (push) Successful in 4m7s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m15s
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Successful in 1m15s
windows / build (aarch64-pc-windows-msvc) (push) Failing after 48s
windows / build (x86_64-pc-windows-msvc) (push) Failing after 49s
ci / bench (push) Successful in 5m5s
decky / build-publish (push) Successful in 29s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 4s
release / apple (push) Successful in 8m30s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 3s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
deb / build-publish (push) Has been cancelled
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
flatpak / build-publish (push) Has been cancelled
apple / screenshots (push) Has been cancelled
docker / deploy-docs (push) Successful in 19s
Each client learns a host's MAC from the mDNS `mac` TXT while it's awake, persists it on the saved-host record, and — when reconnecting to an offline host — sends a magic packet before connecting, plus an explicit "Wake host" action. Apple wraps the C-ABI; linux/windows call the core fn directly (linux also gains a --wake CLI mode); android via a new nativeWakeOnLan JNI export (the mDNS browse record gains a 7th mac field); decky shells out to the linux client's --wake before launching the stream. iOS/tvOS need the managed com.apple.developer.networking.multicast entitlement (pending Apple approval), so the wake path + UI are gated off via PunktfunkConnection.wakeOnLANAvailable and the entitlement is commented out — keeping iOS/tvOS releasable. MAC-learning stays active on every platform so it lights up the moment it's ungated. macOS works today. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
85 lines
3.4 KiB
Rust
85 lines
3.4 KiB
Rust
//! LAN host discovery: browse the host's mDNS advert (`_punktfunk._udp`, TXT keys
|
|
//! `fp`/`pair`/`id` — see the host crate's `discovery.rs`) on a worker thread and stream
|
|
//! results to the UI. Ported verbatim from the GTK client (`mdns-sd` is cross-platform).
|
|
|
|
use mdns_sd::{ServiceDaemon, ServiceEvent};
|
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct DiscoveredHost {
|
|
/// Stable row key: the advertised host id, falling back to the mDNS fullname.
|
|
pub key: String,
|
|
pub name: String,
|
|
pub addr: String,
|
|
pub port: u16,
|
|
/// Host certificate fingerprint to pin (lowercase hex), empty if not advertised.
|
|
pub fp_hex: String,
|
|
/// Pairing requirement: `"required"` or `"optional"`.
|
|
pub pair: String,
|
|
/// Wake-on-LAN MAC(s) from the mDNS `mac` TXT (comma-separated `aa:bb:cc:dd:ee:ff`), which the
|
|
/// hosts page persists onto the matching saved host so it can wake it later. Empty if absent.
|
|
pub mac: Vec<String>,
|
|
}
|
|
|
|
/// Browse continuously for the app's lifetime. The thread exits when the receiver is
|
|
/// dropped (the send fails) or the daemon dies.
|
|
pub fn browse() -> async_channel::Receiver<DiscoveredHost> {
|
|
let (tx, rx) = async_channel::unbounded();
|
|
std::thread::Builder::new()
|
|
.name("punktfunk-mdns".into())
|
|
.spawn(move || {
|
|
let daemon = match ServiceDaemon::new() {
|
|
Ok(d) => d,
|
|
Err(e) => {
|
|
tracing::warn!(error = %e, "mDNS daemon failed — discovery disabled");
|
|
return;
|
|
}
|
|
};
|
|
let receiver = match daemon.browse("_punktfunk._udp.local.") {
|
|
Ok(r) => r,
|
|
Err(e) => {
|
|
tracing::warn!(error = %e, "mDNS browse failed — discovery disabled");
|
|
return;
|
|
}
|
|
};
|
|
while let Ok(event) = receiver.recv() {
|
|
if let ServiceEvent::ServiceResolved(info) = event {
|
|
let props = info.get_properties();
|
|
let val = |k: &str| props.get_property_val_str(k).unwrap_or("").to_string();
|
|
let Some(addr) = info.get_addresses().iter().next().map(|a| a.to_string())
|
|
else {
|
|
continue;
|
|
};
|
|
let id = val("id");
|
|
let host = DiscoveredHost {
|
|
key: if id.is_empty() {
|
|
info.get_fullname().to_string()
|
|
} else {
|
|
id
|
|
},
|
|
name: info
|
|
.get_fullname()
|
|
.split('.')
|
|
.next()
|
|
.unwrap_or("?")
|
|
.to_string(),
|
|
addr,
|
|
port: info.get_port(),
|
|
fp_hex: val("fp"),
|
|
pair: val("pair"),
|
|
mac: val("mac")
|
|
.split(',')
|
|
.map(|s| s.trim().to_string())
|
|
.filter(|s| !s.is_empty())
|
|
.collect(),
|
|
};
|
|
if tx.send_blocking(host).is_err() {
|
|
break; // UI gone — stop browsing
|
|
}
|
|
}
|
|
}
|
|
let _ = daemon.shutdown();
|
|
})
|
|
.expect("spawn mdns thread");
|
|
rx
|
|
}
|