feat(clients): reachability-probed presence + shareable Decky host management
apple / swift (push) Failing after 27s
release / apple (push) Failing after 26s
apple / screenshots (push) Has been skipped
windows-host / package (push) Has been cancelled
arch / build-publish (push) Has been cancelled
android / android (push) Has been cancelled
ci / rust (push) Has been cancelled
ci / web (push) Has been cancelled
ci / docs-site (push) Has been cancelled
ci / bench (push) Has been cancelled
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Has been cancelled
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Has been cancelled
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Has been cancelled
docker / deploy-docs (push) Has been cancelled
flatpak / build-publish (push) Has been cancelled
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Has been cancelled
windows / build (aarch64-pc-windows-msvc) (push) Has been cancelled
windows / build (x86_64-pc-windows-msvc) (push) Has been cancelled
deb / build-publish (push) Has been cancelled
decky / build-publish (push) Has been cancelled
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Has been cancelled
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Has been cancelled
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Has been cancelled
apple / swift (push) Failing after 27s
release / apple (push) Failing after 26s
apple / screenshots (push) Has been skipped
windows-host / package (push) Has been cancelled
arch / build-publish (push) Has been cancelled
android / android (push) Has been cancelled
ci / rust (push) Has been cancelled
ci / web (push) Has been cancelled
ci / docs-site (push) Has been cancelled
ci / bench (push) Has been cancelled
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Has been cancelled
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Has been cancelled
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Has been cancelled
docker / deploy-docs (push) Has been cancelled
flatpak / build-publish (push) Has been cancelled
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Has been cancelled
windows / build (aarch64-pc-windows-msvc) (push) Has been cancelled
windows / build (x86_64-pc-windows-msvc) (push) Has been cancelled
deb / build-publish (push) Has been cancelled
decky / build-publish (push) Has been cancelled
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Has been cancelled
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Has been cancelled
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Has been cancelled
Add a bounded, trust-agnostic, mDNS-INDEPENDENT QUIC reachability probe and surface it everywhere saved-host presence is shown, so a host reached over a routed network (Tailscale/VPN/multicast-filtering LAN) no longer reads Offline just because it isn't advertising — the display-side companion to the 0.8.4 dial-first connect fix. Core: - punktfunk-core: NativeClient::probe (bounded handshake; a real host answers even on trust mismatch, a wrong/closed/TCP-only port fails) + punktfunk_probe C ABI (ABI_VERSION 3->4, header regenerated). - pf-client-core: trust::probe_reachable_many (parallel per-host sweep). Presence pips now read `advertising OR probed-reachable`, refreshed by a ~10-12s background sweep off the UI thread: - Linux (relm4): ui_hosts probed map + HostsMsg::Probed sweep. - Windows (windows-reactor): pf-probe worker -> HostsProps.probed. - Apple (SwiftUI): HostStore.refreshReachability, driven by HomeView + GamepadHomeView .task. - Android (Compose): nativeProbe JNI seam + periodic LaunchedEffect (LNP-gated), online dot added to the touch HostCard. - Decky already probes via --list-hosts --probe. Decky client: make the flatpak client's known-hosts store the single source of truth via new headless CLI modes (--list-hosts / --add-host / --set-host / --forget-host / --reset / --reachable). The plugin can now add a host by address, edit/forget hosts, reset all state (keeping the client identity), and shows probe-backed online pips — state is shared with the desktop client, not duplicated. Also lands in-progress Android 17 LNP groundwork (targetSdk 37 + ACCESS_LOCAL_NETWORK runtime flow, permission dialogs) that was already present in the working tree. Verified: cargo check + clippy clean (punktfunk-core, pf-client-core, linux, android native); android assembleDebug BUILD SUCCESSFUL; decky typecheck + rollup build clean; probe true/false-positive behaviour exercised against a live host. Windows and Apple were not compiled locally (no MSVC/Xcode on this Linux box). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+33
-18
@@ -18,10 +18,14 @@ import {
|
||||
applyUpdate,
|
||||
checkForUpdatesNow,
|
||||
hasUpdate,
|
||||
resolvePinHost,
|
||||
mergeHosts,
|
||||
needsPair,
|
||||
pinIsOnline,
|
||||
startStream,
|
||||
toHost,
|
||||
useHosts,
|
||||
usePins,
|
||||
useSavedHosts,
|
||||
useUpdate,
|
||||
} from "./hooks";
|
||||
import { streamPin } from "./library";
|
||||
@@ -33,10 +37,18 @@ import { PairModal } from "./pair";
|
||||
// and pinned games.
|
||||
// ----------------------------------------------------------------------------------------
|
||||
const QamPanel: FC = () => {
|
||||
const { hosts, scanning, refresh } = useHosts();
|
||||
const { hosts: discovered, scanning, refresh: refreshDiscovered } = useHosts();
|
||||
const { saved, loading: loadingSaved, refresh: refreshSaved } = useSavedHosts();
|
||||
const { info: update, checking, check } = useUpdate();
|
||||
const pins = usePins();
|
||||
|
||||
const hosts = mergeHosts(saved, discovered);
|
||||
const busy = scanning || loadingSaved;
|
||||
const refresh = () => {
|
||||
void refreshDiscovered();
|
||||
void refreshSaved();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{hasUpdate(update) && (
|
||||
@@ -82,12 +94,12 @@ const QamPanel: FC = () => {
|
||||
{pins.pins.length > 0 && (
|
||||
<PanelSection title="Pinned Games">
|
||||
{pins.pins.map((pin) => {
|
||||
const { online } = resolvePinHost(pin, hosts);
|
||||
const online = pinIsOnline(pin, hosts);
|
||||
return (
|
||||
<PanelSectionRow key={`${pin.host_fp}:${pin.game_id}`}>
|
||||
<ButtonItem
|
||||
layout="below"
|
||||
onClick={() => streamPin(pin, hosts, pins)}
|
||||
onClick={() => streamPin(pin, hosts.map(toHost), pins)}
|
||||
label={pin.title}
|
||||
description={`${pin.host_name}${online ? "" : " · offline?"}${
|
||||
pin.paired ? "" : " · pairing required"
|
||||
@@ -104,49 +116,52 @@ const QamPanel: FC = () => {
|
||||
|
||||
<PanelSection title="Hosts">
|
||||
<PanelSectionRow>
|
||||
<ButtonItem layout="below" onClick={refresh} disabled={scanning}>
|
||||
{scanning ? (
|
||||
<ButtonItem layout="below" onClick={refresh} disabled={busy}>
|
||||
{busy ? (
|
||||
<Spinner style={{ height: "1em", marginRight: "0.5em" }} />
|
||||
) : (
|
||||
<FaSyncAlt style={{ marginRight: "0.5em" }} />
|
||||
)}
|
||||
{scanning ? "Scanning…" : "Refresh"}
|
||||
{busy ? "Scanning…" : "Refresh"}
|
||||
</ButtonItem>
|
||||
</PanelSectionRow>
|
||||
{hosts.length === 0 && scanning && (
|
||||
{hosts.length === 0 && busy && (
|
||||
<PanelSectionRow>
|
||||
<Field focusable={false} description="Scanning your network…" />
|
||||
</PanelSectionRow>
|
||||
)}
|
||||
{hosts.length === 0 && !scanning && (
|
||||
{hosts.length === 0 && !busy && (
|
||||
<PanelSectionRow>
|
||||
<Field
|
||||
focusable={false}
|
||||
label="No hosts found"
|
||||
description="Start a Punktfunk host on this network, then refresh."
|
||||
description="Open Punktfunk to add a host by address, or start a host on this network and refresh."
|
||||
/>
|
||||
</PanelSectionRow>
|
||||
)}
|
||||
{hosts.map((h) => {
|
||||
const needsPair = h.pair === "required" && !h.paired;
|
||||
{hosts.map((v) => {
|
||||
const pair = needsPair(v);
|
||||
const h = toHost(v);
|
||||
return (
|
||||
<PanelSectionRow key={h.fp || `${h.host}:${h.port}`}>
|
||||
<PanelSectionRow key={v.fp || `${v.addr}:${v.port}`}>
|
||||
<ButtonItem
|
||||
layout="below"
|
||||
onClick={() =>
|
||||
needsPair
|
||||
pair
|
||||
? showModal(<PairModal host={h} onPaired={() => startStream(h)} />)
|
||||
: startStream(h)
|
||||
}
|
||||
label={
|
||||
<span style={{ display: "inline-flex", alignItems: "center", gap: "0.4em" }}>
|
||||
{needsPair ? <FaLock /> : <FaLockOpen />}
|
||||
{h.name}
|
||||
{pair ? <FaLock /> : <FaLockOpen />}
|
||||
{v.name}
|
||||
</span>
|
||||
}
|
||||
description={`${h.host}:${h.port}${h.paired ? " · paired" : ""}`}
|
||||
description={`${v.addr}:${v.port} · ${v.online ? "online" : "offline"}${
|
||||
pair ? " · pairing required" : v.paired ? " · paired" : ""
|
||||
}`}
|
||||
>
|
||||
{needsPair ? "Pair & Stream" : "Stream"}
|
||||
{pair ? "Pair & Stream" : "Stream"}
|
||||
</ButtonItem>
|
||||
</PanelSectionRow>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user