feat(clients): Wake-on-LAN in apple/linux/windows/android/decky
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
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>
This commit is contained in:
@@ -245,6 +245,7 @@ fn connect_with(
|
||||
port: target.port,
|
||||
fp_hex: trust::hex(&fingerprint),
|
||||
paired: persist_paired,
|
||||
mac: target.mac.clone(),
|
||||
});
|
||||
let _ = k.save();
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ use windows_reactor::*;
|
||||
/// Overflow-menu item labels — `on_item_clicked` reports the clicked item by its text.
|
||||
const MENU_CONNECT: &str = "Connect";
|
||||
const MENU_SPEED: &str = "Test network speed\u{2026}";
|
||||
const MENU_WAKE: &str = "Wake host";
|
||||
const MENU_RENAME: &str = "Rename\u{2026}";
|
||||
const MENU_FORGET: &str = "Forget\u{2026}";
|
||||
|
||||
@@ -318,10 +319,19 @@ pub(crate) fn hosts_page(props: &HostsProps, cx: &mut RenderCx) -> Element {
|
||||
port: k.port,
|
||||
fp_hex: Some(k.fp_hex.clone()),
|
||||
pair_optional: false,
|
||||
mac: k.mac.clone(),
|
||||
};
|
||||
let online = hosts
|
||||
.iter()
|
||||
.any(|h| h.fp_hex == k.fp_hex || (h.addr == k.addr && h.port == k.port));
|
||||
// Learn this host's wake MAC(s) from its live advert while it's online, so we can wake
|
||||
// it once it sleeps (no-op / no disk write when unchanged).
|
||||
if let Some(a) = hosts.iter().find(|h| {
|
||||
(h.fp_hex == k.fp_hex || (h.addr == k.addr && h.port == k.port)) && !h.mac.is_empty()
|
||||
}) {
|
||||
crate::trust::learn_mac(&k.fp_hex, &k.addr, k.port, &a.mac);
|
||||
}
|
||||
let can_wake = !online && !k.mac.is_empty();
|
||||
let menu = {
|
||||
let (svc, target) = (props.svc.clone(), target.clone());
|
||||
let (sf, sr) = (set_forget.clone(), set_rename.clone());
|
||||
@@ -331,17 +341,22 @@ pub(crate) fn hosts_page(props: &HostsProps, cx: &mut RenderCx) -> Element {
|
||||
.subtle()
|
||||
.tooltip("More options")
|
||||
.automation_name("More options")
|
||||
.menu_flyout(vec![
|
||||
menu_item(MENU_CONNECT),
|
||||
menu_item(MENU_SPEED),
|
||||
menu_item(MENU_RENAME),
|
||||
menu_separator(),
|
||||
menu_item(MENU_FORGET),
|
||||
])
|
||||
.menu_flyout({
|
||||
let mut items = vec![menu_item(MENU_CONNECT), menu_item(MENU_SPEED)];
|
||||
// Offer an explicit wake only when the host is offline and we have a MAC.
|
||||
if can_wake {
|
||||
items.push(menu_item(MENU_WAKE));
|
||||
}
|
||||
items.push(menu_item(MENU_RENAME));
|
||||
items.push(menu_separator());
|
||||
items.push(menu_item(MENU_FORGET));
|
||||
items
|
||||
})
|
||||
.on_item_clicked(move |item: String| match item.as_str() {
|
||||
MENU_CONNECT => {
|
||||
initiate(&svc.ctx, target.clone(), &svc.set_screen, &svc.set_status)
|
||||
}
|
||||
MENU_WAKE => crate::wol::wake(&target.mac, target.addr.parse().ok()),
|
||||
MENU_SPEED => {
|
||||
*svc.ctx.shared.target.lock().unwrap() = target.clone();
|
||||
// New run: invalidate any still-in-flight probe, reset the screen.
|
||||
@@ -369,7 +384,14 @@ pub(crate) fn hosts_page(props: &HostsProps, cx: &mut RenderCx) -> Element {
|
||||
if k.paired { Pill::Good } else { Pill::Info },
|
||||
),
|
||||
Some(menu),
|
||||
Some(Box::new(move || initiate(&ctx2, target.clone(), &ss, &st))),
|
||||
Some(Box::new(move || {
|
||||
// Auto-wake an offline saved host before connecting; the connect's own
|
||||
// retry/timeout gives a woken host time to come up.
|
||||
if can_wake {
|
||||
crate::wol::wake(&target.mac, target.addr.parse().ok());
|
||||
}
|
||||
initiate(&ctx2, target.clone(), &ss, &st)
|
||||
})),
|
||||
));
|
||||
}
|
||||
body.push(tile_grid(tiles, cols));
|
||||
@@ -406,6 +428,7 @@ pub(crate) fn hosts_page(props: &HostsProps, cx: &mut RenderCx) -> Element {
|
||||
port: h.port,
|
||||
fp_hex: (!h.fp_hex.is_empty()).then(|| h.fp_hex.clone()),
|
||||
pair_optional: h.pair == "optional",
|
||||
mac: h.mac.clone(),
|
||||
};
|
||||
let (ctx2, ss, st) = (ctx.clone(), set_screen.clone(), set_status.clone());
|
||||
let (badge, kind) = if h.pair == "required" {
|
||||
@@ -486,6 +509,7 @@ pub(crate) fn hosts_page(props: &HostsProps, cx: &mut RenderCx) -> Element {
|
||||
port,
|
||||
fp_hex: None,
|
||||
pair_optional: false,
|
||||
mac: Vec::new(),
|
||||
},
|
||||
&ss,
|
||||
&st,
|
||||
|
||||
@@ -68,6 +68,9 @@ pub(crate) struct Target {
|
||||
pub(crate) port: u16,
|
||||
pub(crate) fp_hex: Option<String>,
|
||||
pub(crate) pair_optional: bool,
|
||||
/// Wake-on-LAN MAC(s) for this host (from the saved store or the live advert) — used to send a
|
||||
/// magic packet before connecting to an offline host. Empty when none is known.
|
||||
pub(crate) mac: Vec<String>,
|
||||
}
|
||||
|
||||
/// Stable app services handed to the page components as props. Each routed screen that uses
|
||||
|
||||
@@ -50,6 +50,7 @@ pub(crate) fn pair_page(props: &Svc, cx: &mut RenderCx) -> Element {
|
||||
port: target3.port,
|
||||
fp_hex: trust::hex(&fp),
|
||||
paired: true,
|
||||
mac: target3.mac.clone(),
|
||||
});
|
||||
let _ = k.save();
|
||||
connect(&ctx3, &target3, Some(fp), &ss, &st);
|
||||
|
||||
Reference in New Issue
Block a user