Files
punktfunk/crates/punktfunk-client-windows/src/discovery.rs
T
enricobuehler 4994f7f4ba
audit / cargo-audit (push) Failing after 1m5s
apple / swift (push) Successful in 3m37s
ci / rust (push) Failing after 3m46s
android / android (push) Successful in 5m20s
ci / web (push) Successful in 33s
ci / docs-site (push) Successful in 27s
ci / bench (push) Successful in 4m39s
decky / build-publish (push) Successful in 22s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 3m12s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 16s
deb / build-publish (push) Successful in 9m20s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 2m38s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 21s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m10s
flatpak / build-publish (push) Failing after 4m55s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Failing after 4m36s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m18s
docker / deploy-docs (push) Successful in 20s
feat(windows-client): WinUI 3 (windows-reactor) UI — host list, settings, pairing, SwapChainPanel present
Replaces the winit + raw-HWND-D3D11 shell with a native WinUI 3 UI via windows-reactor (a
declarative React-like framework backed by WinUI). The earlier "Reactor can't host a
swapchain" read was wrong — PR #4499 (merged 2026-06-01) added a SwapChainPanel widget with
`set_swap_chain` over `CreateSwapChainForComposition`. Builds + clippy + fmt green on
x86_64-pc-windows-msvc.

- Cargo: drop winit/raw-window-handle; add windows-reactor + the `windows` crate, both pinned
  to the SAME windows-rs commit (b4129fcc) so the `IDXGISwapChain1` handed to `set_swap_chain`
  satisfies reactor's `windows_core::Interface`. Reactor's build.rs downloads the Windows App
  SDK NuGets + stages the bootstrap DLL/resources.pri — it requires `CARGO_WORKSPACE_DIR` set
  (now in the VM build env); /temp + /winmd gitignored.
- present.rs: composition swapchain (B8G8R8A8 FLIP_SEQUENTIAL premultiplied) bound to the
  SwapChainPanel; WARP fallback, runtime D3DCompile shaders, dynamic RGBA texture, Contain-fit
  letterbox; driven by reactor's per-frame `on_rendering`.
- app.rs: the WinUI 3 shell — host list (live mDNS + saved + manual), settings (resolution/
  refresh/mic combos+toggle), in-app SPAKE2 PIN pairing screen, and the stream page. Trust gate
  mirrors the GTK client (pinned → silent, pair=optional → TOFU, else PIN); a pinned-fp
  mismatch routes to re-pair. The session pump + decoded-frame handoff cross to the UI thread
  via a Mutex side-channel + thread-locals (the SwapChainPanel sample's pattern).
- gamepad: `ctl` sender now `Arc<Mutex<…>>` so GamepadService is `Sync` (shared across the UI
  and session-pump threads). main.rs: windowed = in-app UI; `--headless`/`--discover` keep the
  CLI paths.

Not yet wired: raw stream keyboard/mouse input (next commit — reactor exposes no raw key/
pointer events, so it needs Win32 low-level hooks or Microsoft.UI.Xaml bindings). On-glass
validation pending a display (the dev VM is headless/GPU-less).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 22:58:48 +00:00

77 lines
3.0 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,
}
/// 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"),
};
if tx.send_blocking(host).is_err() {
break; // UI gone — stop browsing
}
}
}
let _ = daemon.shutdown();
})
.expect("spawn mdns thread");
rx
}