ci / web (push) Successful in 1m59s
ci / docs-site (push) Successful in 2m13s
ci / rust-arm64 (push) Successful in 5m17s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 5s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 4s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 4s
android / android (push) Successful in 5m52s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
deb / build-publish (push) Successful in 5m38s
ci / rust (push) Failing after 5m52s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 9s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 18s
arch / build-publish (push) Failing after 6m7s
deb / build-publish-client-arm64 (push) Successful in 3m55s
docker / builders-arm64cross (push) Successful in 19s
deb / build-publish-host (push) Failing after 4m24s
apple / swift (push) Successful in 4m35s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m13s
docker / deploy-docs (push) Successful in 31s
windows-host / package (push) Failing after 6m22s
windows-host / canary-manifest (push) Skipped
windows-host / winget-source (push) Skipped
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 2m35s
flatpak / build-publish (push) Successful in 6m33s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m22s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Failing after 10m8s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 2m56s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Failing after 11m43s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 4m1s
release / apple (push) Successful in 25m5s
apple / screenshots (push) Successful in 20m50s
The host's capture tick and a client's panel vsync are independent ~120 Hz oscillators; their drifting phase sweeps every frame's wait-for-latch across a full refresh period (measured on-glass: latch p50 oscillating 5.4-8.9 ms with a fixed margin) — the beat is the residual judder and the fat p95, and no client can fix it alone. Design: punktfunk-planning design/phase-locked-capture.md. Protocol (punktfunk-core): - PhaseReport (control 0x32, next to the clock family): the client's next display latch ALREADY CONVERTED to host clock (the skew offset lives only client-side), panel period, uncertainty, and the measured median arrival-lead — the controller's error signal. ~1 Hz, latest-wins. CLIENT_CAP_PHASE_LOCK advertises it; CtrlRequest::Phase + report_phase() + the C ABI mirror carry it. - The 0xCF host-timing tail grows a phase ACK (applied_phase_ns, 29-byte form) under the same strict-prefix append discipline — old readers parse the shorter forms; degradation pinned by tests. Host engine (arrival-slaved loop — no backend can move the source vsync, per the tick-ownership audit in the design doc): - PhaseCtl bridges control task → encode loop (the fec_target pattern, multi-field). PhaseController walks a per-frame HOLD before submit toward the client's reported lead hitting target = max(2.5 ms, uncertainty+1ms): 1 Hz adjust, 2 ms max step, 300 µs deadband, period-wrapping (the newest-wins capture slot makes a wrapped hold sample fresher content, not staler). A loop local, so every mid-stream rebuild keeps the lock; a new session re-acquires. PUNKTFUNK_PHASE_LOCK=0 disarms. Android reporter: the presenter's 1 Hz pf.present flush returns the window's measured latch p50; the async loop converts the vsync clock's next timeline monotonic→realtime→host and reports. Inert toward old hosts. Gates: docker amd64 clippy --all-targets -D warnings (host+core, nvenc) clean; core suite incl. the new wire tests; cargo ndk arm64 check/clippy clean. On-glass A/B vs the .173 host owed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
77 lines
4.3 KiB
Rust
77 lines
4.3 KiB
Rust
//! `CtrlRequest` (the embedder's control-stream requests) and `Negotiated` (the handshake result).
|
|
|
|
use crate::config::{CompositorPref, GamepadPref, Mode};
|
|
use crate::quic::{ClipControl, ClipOffer, ColorInfo, LossReport, ProbeRequest, RfiRequest};
|
|
|
|
/// A control-stream request the embedder makes on the open handshake stream: a mode switch or a
|
|
/// speed test. One outbound channel carries both so the worker's `select!` has a single writer
|
|
/// (two `&mut ctrl_send` borrows across select branches don't compile).
|
|
pub(crate) enum CtrlRequest {
|
|
Mode(Mode),
|
|
Probe(ProbeRequest),
|
|
Keyframe,
|
|
/// Reference-frame-invalidation recovery: the client saw a `frame_index` gap and reports the
|
|
/// invalidation range so an RFI-capable host re-references a known-good picture instead of
|
|
/// forcing a full IDR. See [`RfiRequest`].
|
|
Rfi(RfiRequest),
|
|
Loss(LossReport),
|
|
/// Adaptive bitrate: ask the host to re-target its encoder (kbps). Sent by the pump's
|
|
/// [`BitrateController`] when the user's bitrate setting is Automatic.
|
|
SetBitrate(u32),
|
|
/// Start a mid-stream clock re-sync batch now (see [`ClockResync`]). Sent by the pump on
|
|
/// its report tick after the first no-op clock flush — the "the clock stepped under me"
|
|
/// signal; the control task also self-triggers one every [`CLOCK_RESYNC_INTERVAL`].
|
|
ClockResync,
|
|
/// Shared-clipboard enable/disable for this session (`design/clipboard-and-file-transfer.md`
|
|
/// §3.1). Idempotent; carries the file-permission flag.
|
|
ClipControl(ClipControl),
|
|
/// Announce that the local clipboard changed — the lazy format-list offer (bytes cross later on
|
|
/// a fetch stream). Symmetric message; the host may send one too.
|
|
ClipOffer(ClipOffer),
|
|
/// Who renders the pointer (cursor-forward sessions): `true` = client draws locally (the
|
|
/// desktop mouse model — host excludes + forwards), `false` = host composites into the
|
|
/// video (the capture model). Sent on every mouse-model flip; idempotent, latest-wins.
|
|
CursorRender(crate::quic::CursorRenderMode),
|
|
/// The client's display-latch grid, ~1 Hz, host-clock timestamps — feeds the host's
|
|
/// phase-locked capture (design/phase-locked-capture.md). Latest-wins; an old host
|
|
/// ignores the message.
|
|
Phase(crate::quic::PhaseReport),
|
|
}
|
|
|
|
/// What the worker reports to [`NativeClient::connect`] once the handshake lands: the
|
|
/// [`Welcome`]-resolved session parameters (mode, backends, encode/colour/audio geometry) plus the
|
|
/// host certificate fingerprint and the connect-time clock offset. Mirrored one-to-one onto the
|
|
/// public `NativeClient` fields of the same names.
|
|
#[derive(Clone, Copy)]
|
|
pub(crate) struct Negotiated {
|
|
pub(crate) mode: Mode,
|
|
/// Wire shard payload — the chunk-aligned parse window (plan §4.4).
|
|
pub(crate) shard_payload: u16,
|
|
pub(crate) compositor: CompositorPref,
|
|
pub(crate) gamepad: GamepadPref,
|
|
/// SHA-256 of the certificate the host actually presented (TOFU callers persist this).
|
|
pub(crate) host_fingerprint: [u8; 32],
|
|
/// The encoder bitrate the host actually configured (kbps); `0` = an older host.
|
|
pub(crate) bitrate_kbps: u32,
|
|
/// Host clock minus client clock (ns); `0` = no skew handshake (old host / synced clocks).
|
|
pub(crate) clock_offset_ns: i64,
|
|
/// Min RTT of the connect-time skew handshake (ns); `None` = the host never answered —
|
|
/// mid-stream re-syncs are pointless then and stay off. Seeds the re-sync admission
|
|
/// guard's session-floor baseline ([`ResyncGuard`]).
|
|
pub(crate) clock_rtt_ns: Option<u64>,
|
|
/// Resolved encode bit depth: `8`, or `10` for a Main10 / HDR session.
|
|
pub(crate) bit_depth: u8,
|
|
/// Resolved CICP colour signalling.
|
|
pub(crate) color: ColorInfo,
|
|
/// Resolved chroma subsampling as the HEVC `chroma_format_idc` (1 = 4:2:0, 3 = 4:4:4).
|
|
pub(crate) chroma_format: u8,
|
|
/// Resolved audio channel count (2/6/8) — what the Opus decoders must be built from.
|
|
pub(crate) audio_channels: u8,
|
|
/// The single codec the host will emit (`quic::CODEC_*`).
|
|
pub(crate) codec: u8,
|
|
/// The host capability bitfield ([`crate::quic::Welcome::host_caps`]):
|
|
/// [`crate::quic::HOST_CAP_GAMEPAD_STATE`], [`crate::quic::HOST_CAP_CLIPBOARD`]. Exposed to the
|
|
/// embedder via [`NativeClient::host_caps`] so a native client greys out unsupported toggles.
|
|
pub(crate) host_caps: u8,
|
|
}
|