diff --git a/.dockerignore b/.dockerignore index bae4bbe..7d2d835 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,9 +1,9 @@ # Root build context is used only by web/Dockerfile, which needs web/ and -# docs/api/openapi.json. Allowlist those; keep everything else (target/, .git, crates) +# api/openapi.json. Allowlist those; keep everything else (target/, .git, crates) # out of the context upload. * !web -!docs/api/openapi.json +!api/openapi.json web/node_modules web/.output web/dist diff --git a/.gitea/workflows/flatpak.yml b/.gitea/workflows/flatpak.yml index c470ee1..baeee86 100644 --- a/.gitea/workflows/flatpak.yml +++ b/.gitea/workflows/flatpak.yml @@ -24,7 +24,7 @@ on: push: branches: [main] # The flatpak is the CLIENT — only rebuild when the client/core/manifest change, not on every - # docs/host push (this is a heavy flatpak-builder run). Tags (v*, the client release) build too. + # design/host push (this is a heavy flatpak-builder run). Tags (v*, the client release) build too. paths: - 'clients/linux/**' - 'crates/punktfunk-core/**' diff --git a/.gitea/workflows/windows-drivers-provision.yml b/.gitea/workflows/windows-drivers-provision.yml index dbade10..d590bd1 100644 --- a/.gitea/workflows/windows-drivers-provision.yml +++ b/.gitea/workflows/windows-drivers-provision.yml @@ -1,5 +1,5 @@ # One-shot provisioning of the WDK + cargo-wdk onto the persistent self-hosted windows-amd64 runner, so -# the all-Rust UMDF drivers can build there (docs/windows-host-rewrite.md, M0). The runner has the base +# the all-Rust UMDF drivers can build there (design/windows-host-rewrite.md, M0). The runner has the base # Windows SDK + MSVC + LLVM + Rust but NOT the WDK (no km/wdf/iddcx headers) or cargo-wdk. # # Dispatch manually (workflow_dispatch). Idempotent: re-running is a near no-op once provisioned. The diff --git a/.gitea/workflows/windows-drivers.yml b/.gitea/workflows/windows-drivers.yml index 2d90408..677a867 100644 --- a/.gitea/workflows/windows-drivers.yml +++ b/.gitea/workflows/windows-drivers.yml @@ -1,5 +1,5 @@ # Windows driver workspace CI — runs on the self-hosted Windows runner (home-windows-1, host mode; -# label windows-amd64). Part of the Windows-host rewrite (docs/windows-host-rewrite.md, M0). +# label windows-amd64). Part of the Windows-host rewrite (design/windows-host-rewrite.md, M0). # # Stage 1 (this file): PROBE the runner's driver toolchain (WDK / EWDK / cargo-make / LLVM / the # inf2cat/stampinf/devgen/signtool tools) so we know what's provisioned BEFORE writing driver code, diff --git a/CLAUDE.md b/CLAUDE.md index 562996f..4c7c3d2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,7 +2,7 @@ Low-latency desktop/game streaming stack, Linux-first, with a shared Rust protocol core (`punktfunk-core`) exposed over a C ABI and native clients per platform. Full design: -[`docs/implementation-plan.md`](docs/implementation-plan.md). Status table: `README.md`. +[`design/implementation-plan.md`](design/implementation-plan.md). Status table: `README.md`. ## Where the work stands @@ -104,9 +104,16 @@ Low-latency desktop/game streaming stack, Linux-first, with a shared Rust protoc captures the HDR desktop as FP16/Rgb10a2 (DDA FP16 for the secure desktop), the encoder forces HEVC Main10 + BT.2020 PQ (NVENC ABGR10/P010; AMF/QSV P010 + a swscale Rgb10a2→P010 fallback), the client auto-detects PQ from the HEVC VUI — gated by `PUNKTFUNK_10BIT` + client `VIDEO_CAP_10BIT`; **Windows - host only** (the Linux host stays 8-bit, blocked upstream). **AMF/QSV is CI-green but not yet - on-glass validated** (no AMD/Intel Windows box in the lab); NVENC is live-validated. Newer/less - battle-tested than the Linux host. Packaging: `packaging/windows/`. + host only** (the Linux host stays 8-bit, blocked upstream). **Vulkan-game HDR over the virtual + display**: NVIDIA/AMD Vulkan ICDs refuse to *advertise* an HDR color space for a surface on an IddCx + indirect display (so Vulkan games — Doom: The Dark Ages, id Tech, etc. — say "device does not support + HDR"), even though the ICD happily *accepts + presents* a forced HDR swapchain there. A tiny always-on + Vulkan **implicit layer** (`packaging/windows/pf-vkhdr-layer/`, `VK_LAYER_PUNKTFUNK_hdr_inject`) + injects the `HDR10_ST2084`/scRGB surface formats into `vkGetPhysicalDeviceSurfaceFormats[2]KHR`, + self-gated on the display's actual advanced-color state (no-op on SDR / real monitors); bundled + + HKLM-registered by the installer. **Live-validated: Doom: The Dark Ages enables HDR over the virtual + display.** **AMF/QSV is CI-green but not yet on-glass validated** (no AMD/Intel Windows box in the + lab); NVENC is live-validated. Newer/less battle-tested than the Linux host. Packaging: `packaging/windows/`. ## What's left @@ -245,8 +252,8 @@ bash crates/punktfunk-core/tests/c/run.sh # standalone C-ABI link + round-trip ``` Generated artifacts are **checked in** and CI fails on drift: `include/punktfunk_core.h` -(cbindgen from `punktfunk-core/src/abi.rs`) and `docs/api/openapi.json` (regenerate with -`cargo run -p punktfunk-host -- openapi > docs/api/openapi.json`; spec lives in `mgmt.rs`). +(cbindgen from `punktfunk-core/src/abi.rs`) and `api/openapi.json` (regenerate with +`cargo run -p punktfunk-host -- openapi > api/openapi.json`; spec lives in `mgmt.rs`). CI is Gitea Actions (`.gitea/workflows/`, guide: docs-site `ci.md`): `ci.yml` runs the workspace checks inside the `git.unom.io/unom/punktfunk-rust-ci` image plus web/docs-site diff --git a/clients/apple/README.md b/clients/apple/README.md index 12ffccf..f8e6b30 100644 --- a/clients/apple/README.md +++ b/clients/apple/README.md @@ -361,4 +361,4 @@ ever switched to a logged-in GUI session, re-adding macOS to the job's capture s - Mid-stream renegotiation (resolution change without reconnect) is designed-for but not implemented (the Welcome is one-shot today). - Host-side gamepad injection needs `/dev/uinput` access on the box (udev rule from - `docs/linux-setup.md`). + `design/linux-setup.md`). diff --git a/crates/pf-driver-proto/src/lib.rs b/crates/pf-driver-proto/src/lib.rs index b7c98b9..900c587 100644 --- a/crates/pf-driver-proto/src/lib.rs +++ b/crates/pf-driver-proto/src/lib.rs @@ -276,7 +276,7 @@ pub mod frame { /// These were hand-duplicated as `OFF_*`/`SHM_*` constants in `inject/{gamepad,dualsense}_windows.rs` /// and (as bare literals — `*view.add(140)`) in the standalone `xusb-driver`/`dualsense-driver` /// workspaces, guarded only by "must match" comments — the top ABI-drift hazard the audit flagged -/// (`docs/windows-host-rewrite.md` §2.7). Owning them here with `Pod` derives + `offset_of!` +/// (`design/windows-host-rewrite.md` §2.7). Owning them here with `Pod` derives + `offset_of!` /// asserts makes a one-sided edit a compile error. /// /// The host creates the section (privileged, permissive DACL so the restricted WUDFHost token can diff --git a/crates/punktfunk-host/src/capture/windows/desktop_watch.rs b/crates/punktfunk-host/src/capture/windows/desktop_watch.rs index 8ee9ff3..459d68c 100644 --- a/crates/punktfunk-host/src/capture/windows/desktop_watch.rs +++ b/crates/punktfunk-host/src/capture/windows/desktop_watch.rs @@ -1,5 +1,5 @@ //! Input-desktop watcher (Windows) — the authoritative "normal vs secure desktop" signal for the -//! two-process secure-desktop design (docs/windows-secure-desktop.md). +//! two-process secure-desktop design (design/windows-secure-desktop.md). //! //! Windows switches the *input desktop* to "Winlogon" (the secure desktop) for UAC elevation, the //! lock screen and the login screen, and back to "Default" for the normal session. WGC captures only diff --git a/crates/punktfunk-host/src/capture/windows/idd_push.rs b/crates/punktfunk-host/src/capture/windows/idd_push.rs index e599cf0..a36436c 100644 --- a/crates/punktfunk-host/src/capture/windows/idd_push.rs +++ b/crates/punktfunk-host/src/capture/windows/idd_push.rs @@ -562,7 +562,7 @@ impl IddPushCapturer { /// Block (bounded) until the driver has ATTACHED to the host ring (`DRV_STATUS_OPENED`) **and published /// a first frame**, else fail so the caller can fall back to DDA (audit §5.1 + - /// `docs/windows-host-rewrite.md` §2.5 — the GB1 game-capture fix). + /// `design/windows-host-rewrite.md` §2.5 — the GB1 game-capture fix). /// /// Requiring the first frame — not just the attach — catches the *reconnect-into-a-broken-state* case: /// a fullscreen game can leave the virtual display in a format/size that the driver's `publish()` guard diff --git a/crates/punktfunk-host/src/capture/windows/wgc_relay.rs b/crates/punktfunk-host/src/capture/windows/wgc_relay.rs index 3a6f170..3cf20ca 100644 --- a/crates/punktfunk-host/src/capture/windows/wgc_relay.rs +++ b/crates/punktfunk-host/src/capture/windows/wgc_relay.rs @@ -1,5 +1,5 @@ //! Host-side WGC helper relay (Windows two-process secure-desktop design, -//! docs/windows-secure-desktop.md — step 4). +//! design/windows-secure-desktop.md — step 4). //! //! WGC won't activate under the SYSTEM account, so the SYSTEM host can't capture the normal desktop //! itself. Instead it spawns `punktfunk-host wgc-helper` in the **interactive user session** (so WGC works) diff --git a/crates/punktfunk-host/src/config.rs b/crates/punktfunk-host/src/config.rs index 054c0df..991dcc0 100644 --- a/crates/punktfunk-host/src/config.rs +++ b/crates/punktfunk-host/src/config.rs @@ -4,7 +4,7 @@ //! environment before the host starts, and **for the knobs captured here the environment is constant for the //! process lifetime**, so a lazily-parsed global is equivalent to "parsed once at startup". //! -//! **Goal-1 stages 1–2** (`docs/windows-host-rewrite.md` §2.2): stage 1 stood this up; stage 2 migrated the +//! **Goal-1 stages 1–2** (`design/windows-host-rewrite.md` §2.2): stage 1 stood this up; stage 2 migrated the //! genuinely-constant operator/dispatch knobs onto it (the dispatch-disagreement bug class: `idd_push`, //! `capture_backend`, `encoder_pref`, `render_adapter`, `no_wgc`, the vdisplay backend select — plus the //! plan-named `secure_dda`/`idd_depth`/`zerocopy`/`ten_bit` and the multi-site `perf`/`compositor`/ diff --git a/crates/punktfunk-host/src/gamestream/crypto.rs b/crates/punktfunk-host/src/gamestream/crypto.rs index 437f751..53d1a58 100644 --- a/crates/punktfunk-host/src/gamestream/crypto.rs +++ b/crates/punktfunk-host/src/gamestream/crypto.rs @@ -1,7 +1,7 @@ //! Pairing crypto primitives (control plane only — distinct from `punktfunk_core`'s AES-GCM //! data-plane sealing). GameStream pairing uses: AES-128-**ECB** with **no padding**, //! SHA-256 (host appversion major ≥ 7), and RSA-PKCS1v15-SHA256 signatures. See the -//! `serverinfo + pairing` section of `docs/research/gamestream-protocol-research.json`. +//! `serverinfo + pairing` section of `design/research/gamestream-protocol-research.json`. use aes::cipher::generic_array::GenericArray; use aes::cipher::{BlockDecrypt, BlockEncrypt, KeyInit}; diff --git a/crates/punktfunk-host/src/gamestream/mod.rs b/crates/punktfunk-host/src/gamestream/mod.rs index 83111ad..083dd5c 100644 --- a/crates/punktfunk-host/src/gamestream/mod.rs +++ b/crates/punktfunk-host/src/gamestream/mod.rs @@ -1,7 +1,7 @@ //! GameStream (P1) control plane — what a stock Moonlight/Artemis client talks to around //! the media streams: mDNS discovery, the nvhttp serverinfo + pairing HTTP(S) API, RTSP, //! and the ENet control stream. `tokio`/`axum` live here (control plane, I/O-bound — never -//! the per-frame hot path; that is `punktfunk_core`'s P1 wire codec). See `docs/gamestream-host-plan.md`. +//! the per-frame hot path; that is `punktfunk_core`'s P1 wire codec). See `design/gamestream-host-plan.md`. //! //! Status: P1.1 — mDNS `_nvstream._tcp` advertisement + `/serverinfo`. Pairing, RTSP, and //! the media streams follow (see the GameStream host task list / plan). diff --git a/crates/punktfunk-host/src/gamestream/pairing.rs b/crates/punktfunk-host/src/gamestream/pairing.rs index 3ad0ecc..f763a01 100644 --- a/crates/punktfunk-host/src/gamestream/pairing.rs +++ b/crates/punktfunk-host/src/gamestream/pairing.rs @@ -1,7 +1,7 @@ //! The 4-phase GameStream pairing state machine (over HTTP), keyed by `uniqueid`. Proves //! both sides know the PIN (via the SHA-256(salt||pin) AES-ECB key) and own their certs //! (RSA signatures), then pins the client cert. The final `pairchallenge` happens over -//! HTTPS (handled in `nvhttp`). Byte-exact spec: `docs/research/…-research.json`. +//! HTTPS (handled in `nvhttp`). Byte-exact spec: `design/research/…-research.json`. use super::cert::ServerIdentity; use super::crypto; diff --git a/crates/punktfunk-host/src/gamestream/video.rs b/crates/punktfunk-host/src/gamestream/video.rs index 341aec1..2c64463 100644 --- a/crates/punktfunk-host/src/gamestream/video.rs +++ b/crates/punktfunk-host/src/gamestream/video.rs @@ -3,7 +3,7 @@ //! `RTP_PACKET(12, big-endian) + reserved[4] + NV_VIDEO_PACKET(16, little-endian) + payload` //! and the frame's bitstream is prefixed with an 8-byte `video_short_frame_header_t`, then //! striped into ≤4 FEC blocks of ≤255 shards. Byte-exact spec: -//! `docs/research/gamestream-protocol-research.json` (video plane). +//! `design/research/gamestream-protocol-research.json` (video plane). //! //! FEC (P1.5): each block carries `m = ⌈k·pct/100⌉` Reed–Solomon parity shards generated by //! `punktfunk_core::fec::Gf8Coder` (the nanors-compatible Cauchy GF(2⁸) coder). Crucially, RS runs diff --git a/crates/punktfunk-host/src/inject/windows/dualsense_windows.rs b/crates/punktfunk-host/src/inject/windows/dualsense_windows.rs index 67c6645..e7bda48 100644 --- a/crates/punktfunk-host/src/inject/windows/dualsense_windows.rs +++ b/crates/punktfunk-host/src/inject/windows/dualsense_windows.rs @@ -109,7 +109,7 @@ pub(super) struct SwDeviceProfile<'a> { /// `profile.instance`). The returned `HSWDEVICE` owns it — `SwDeviceClose` removes it on drop, so the /// pad appears/disappears with the session and nothing persists. /// -/// **Game-detection identity** (see `docs/windows-dualsense-game-detection.md`). `HIDD_ATTRIBUTES` +/// **Game-detection identity** (see `design/windows-dualsense-game-detection.md`). `HIDD_ATTRIBUTES` /// alone (VID/PID via the IOCTL) satisfies SDL/HIDAPI/RawInput, but a native PS5 path (libScePad- /// style raw HID) classifies the *connection type* by walking from the HID child to its parent /// (`CM_Get_Parent`) and string-matching `"USB"`/`"BTHENUM"` in that parent's diff --git a/crates/punktfunk-host/src/main.rs b/crates/punktfunk-host/src/main.rs index 026418a..bf9643d 100644 --- a/crates/punktfunk-host/src/main.rs +++ b/crates/punktfunk-host/src/main.rs @@ -389,7 +389,7 @@ fn real_main() -> Result<()> { } // USER-session WGC helper (Windows two-process secure-desktop design): capture the EXISTING // SudoVDA via WGC + NVENC, stream AUs on stdout to the SYSTEM host. Spawned by the host - // (CreateProcessAsUser), not run by hand. See docs/windows-secure-desktop.md. + // (CreateProcessAsUser), not run by hand. See design/windows-secure-desktop.md. #[cfg(target_os = "windows")] Some("wgc-helper") => { let get = |flag: &str| { @@ -704,7 +704,7 @@ SPIKE OPTIONS: NOTES: 'portal' needs headless Sway + xdg-desktop-portal-wlr running in this session - (see docs/linux-setup.md). 'synthetic' needs no capture session and always runs. + (see design/linux-setup.md). 'synthetic' needs no capture session and always runs. Encoded AUs are written to a playable file AND (unless --no-loopback) fed through a punktfunk_core host→client loopback that reassembles and byte-verifies each one. Both 'serve' and 'punktfunk1-host' advertise the native service over mDNS diff --git a/crates/punktfunk-host/src/mgmt.rs b/crates/punktfunk-host/src/mgmt.rs index 0c480ef..09f1a6b 100644 --- a/crates/punktfunk-host/src/mgmt.rs +++ b/crates/punktfunk-host/src/mgmt.rs @@ -6,7 +6,7 @@ //! The API is versioned under `/api/v1` and described by an OpenAPI 3.1 document generated //! at compile time with `utoipa` — `punktfunk-host openapi` prints it for client codegen, the //! running server serves it at `/api/v1/openapi.json` plus interactive docs at `/api/docs`, -//! and a copy is checked in at `docs/api/openapi.json` (a test fails if it drifts, like the +//! and a copy is checked in at `api/openapi.json` (a test fails if it drifts, like the //! cbindgen header). //! //! Security: binds loopback by default, serves HTTPS with the host's identity cert, and requires @@ -164,7 +164,7 @@ fn api_router_parts() -> (Router>, utoipa::openapi::OpenApi) { } /// The OpenAPI document as pretty JSON — what `punktfunk-host openapi` prints and what is -/// checked in at `docs/api/openapi.json` for client codegen. +/// checked in at `api/openapi.json` for client codegen. pub fn openapi_json() -> String { let (_, api) = api_router_parts(); let mut json = api.to_pretty_json().expect("serialize OpenAPI document"); @@ -1663,14 +1663,14 @@ mod tests { serde_json::json!([{}]) ); - let checked_in = include_str!("../../../docs/api/openapi.json"); + let checked_in = include_str!("../../../api/openapi.json"); // Compare content, not line-ending style: the generated `json` is LF (serde_json), but git // may check the file out CRLF on Windows. assert_eq!( json.trim().replace('\r', ""), checked_in.trim().replace('\r', ""), - "docs/api/openapi.json is stale — regenerate with: \ - cargo run -p punktfunk-host -- openapi > docs/api/openapi.json" + "api/openapi.json is stale — regenerate with: \ + cargo run -p punktfunk-host -- openapi > api/openapi.json" ); } diff --git a/crates/punktfunk-host/src/punktfunk1.rs b/crates/punktfunk-host/src/punktfunk1.rs index 0890bb3..65c065a 100644 --- a/crates/punktfunk-host/src/punktfunk1.rs +++ b/crates/punktfunk-host/src/punktfunk1.rs @@ -2221,7 +2221,7 @@ fn virtual_stream(ctx: SessionContext) -> Result<()> { // Windows two-process secure-desktop path: when the host runs as SYSTEM (required for the secure // desktop + SendInput), WGC can't activate in-process, so we capture the normal desktop via a // helper spawned in the user session and relay its AUs. (Single-process WGC/DDA is used as the - // user, and stays the path on Linux.) See docs/windows-secure-desktop.md. + // user, and stays the path on Linux.) See design/windows-secure-desktop.md. #[cfg(target_os = "windows")] if plan.topology == crate::session_plan::SessionTopology::TwoProcessRelay { return virtual_stream_relay(ctx); diff --git a/crates/punktfunk-host/src/session_plan.rs b/crates/punktfunk-host/src/session_plan.rs index f9a5b88..f94b608 100644 --- a/crates/punktfunk-host/src/session_plan.rs +++ b/crates/punktfunk-host/src/session_plan.rs @@ -1,7 +1,7 @@ //! `SessionPlan` — the per-session capture / topology / encoder decision, resolved **once** from //! [`HostConfig`](crate::config) (+ the handshake-negotiated bit depth) into a typed, logged value. //! -//! **Goal-1 stage 3** (`docs/windows-host-rewrite.md` §2.2): before this, the Windows session decision was +//! **Goal-1 stage 3** (`design/windows-host-rewrite.md` §2.2): before this, the Windows session decision was //! re-derived at three call sites — the capture backend inside `capture::capture_virtual_output`, the //! process topology in `punktfunk1::should_use_helper`, and the encode backend in //! `encode::windows_resolved_backend` — each reading [`config`](crate::config) independently, with no diff --git a/crates/punktfunk-host/src/session_tuning.rs b/crates/punktfunk-host/src/session_tuning.rs index e5da2c8..fcc04ba 100644 --- a/crates/punktfunk-host/src/session_tuning.rs +++ b/crates/punktfunk-host/src/session_tuning.rs @@ -9,7 +9,7 @@ //! Raw C-ABI FFI (winmm/kernel32/dwmapi/avrt) rather than the `windows` crate so it builds without //! pulling new windows-rs features. No-op on non-Windows. Per-thread effects (MMCSS, execution //! state) auto-revert at thread exit (= session end); the process-wide bits revert at process exit. -//! See `docs/host-latency-plan.md` Tier 3A. +//! See `design/host-latency-plan.md` Tier 3A. // Every `unsafe` block in this file carries a `// SAFETY:` proof; enforce it (unsafe-proof program). #![deny(clippy::undocumented_unsafe_blocks)] diff --git a/crates/punktfunk-host/src/vdisplay/windows/pf_vdisplay.rs b/crates/punktfunk-host/src/vdisplay/windows/pf_vdisplay.rs index 87d93aa..99a5ad8 100644 --- a/crates/punktfunk-host/src/vdisplay/windows/pf_vdisplay.rs +++ b/crates/punktfunk-host/src/vdisplay/windows/pf_vdisplay.rs @@ -6,7 +6,7 @@ //! //! Control surface: a device-interface-GUID + `CreateFileW` + `DeviceIoControl` IOCTL protocol, with //! the wire contract OWNED by [`pf_driver_proto::control`] (versioned + `#[repr(C)] Pod` structs, -//! NOT the SudoVDA ABI). No DLL, no named pipe. See `docs/windows-host-rewrite.md`. +//! NOT the SudoVDA ABI). No DLL, no named pipe. See `design/windows-host-rewrite.md`. //! //! This is a faithful clone of [`super::sudovda`] (the shipping fallback) repointed at the new driver: //! same reference-counted/lingering monitor lifecycle, same CCD isolation + active-mode forcing — those diff --git a/crates/punktfunk-host/src/windows/wgc_helper.rs b/crates/punktfunk-host/src/windows/wgc_helper.rs index a4dbf85..3ac9ef8 100644 --- a/crates/punktfunk-host/src/windows/wgc_helper.rs +++ b/crates/punktfunk-host/src/windows/wgc_helper.rs @@ -1,5 +1,5 @@ //! USER-session WGC helper (Windows) — part of the two-process secure-desktop design -//! (docs/windows-secure-desktop.md). +//! (design/windows-secure-desktop.md). //! //! WGC won't activate under the SYSTEM account, but the host must run as SYSTEM for the secure //! desktop. So the SYSTEM host spawns THIS helper in the interactive user session diff --git a/design/apollo-comparison.md b/design/apollo-comparison.md index 64bedc1..e98b1bb 100644 --- a/design/apollo-comparison.md +++ b/design/apollo-comparison.md @@ -43,7 +43,7 @@ Apollo is host-only. A stream flows: **nvhttp** (HTTPS pairing + serverinfo/appl | Apollo — Audio capture, encode, transport (Windows host) | `audio.cpp`; `audio.h`; `audio.cpp`; `common.h`; `stream.cpp` | `audio.rs`; `audio/wasapi_cap.rs`; `audio/linux.rs`; `gamestream/audio.rs`; `punktfunk1.rs` | | Apollo (Sunshine fork) — Input handling & injection | `input.cpp`; `input.cpp`; `keylayout.h`; `misc.cpp` | — | | Apollo: App/process launch & display configuration (Windows host) | `process.cpp`; `display_device.cpp`; `process.h`; `virtual_display.h`; `misc.cpp`; `utils.cpp` | `vdisplay/sudovda.rs`; `vdisplay.rs`; `gamestream/apps.rs`; `library.rs`; `punktfunk1.rs`; `capture/wgc_relay.rs` | -| Apollo: Config, management/web UI, system tray | `config.h`; `config.cpp`; `confighttp.cpp`; `confighttp.h`; `system_tray.cpp`; `system_tray.h` | `mgmt.rs`; `mgmt_token.rs`; `main.rs`; `native_pairing.rs`; `library.rs`; `docs/windows-host.md` | +| Apollo: Config, management/web UI, system tray | `config.h`; `config.cpp`; `confighttp.cpp`; `confighttp.h`; `system_tray.cpp`; `system_tray.h` | `mgmt.rs`; `mgmt_token.rs`; `main.rs`; `native_pairing.rs`; `library.rs`; `design/windows-host.md` | ### Apollo — Protocol & streaming (RTP/FEC/ENet/RTSP/crypto) @@ -354,7 +354,7 @@ The `formats[]` table (258-277) maps 2/6/8 channels to Stereo/5.1/7.1 with the G - **Decouple ingest from injection via task-pool queue with lock-then-release batching** — The control-stream thread only enqueues bytes and schedules a task (src/input.cpp:1639-1643). A pool thread pops one packet, coalesces later same-type packets into it while holding the queue lock, then RELEASES the lock before the (potentially slow) SendInput/ViGEm call (src/input.cpp:1486-1520). — _For a low-latency streaming host this is the core anti-head-of-line-blocking pattern: a slow OS input call (e.g. SendInput crossing a desktop switch) never stalls the network/control thread, and bursts of mouse/scroll/controller packets collapse to one OS event per drain. punktfunk should mirror this: never call SendInput on the QUIC/control thread._ - **Type-aware packet batching with batch_result_e (batched / not_batchable / terminate_batch)** — batch() overloads (src/input.cpp:1208-1475) sum relative-mouse deltas and scroll amounts (with __builtin_add_overflow guards that terminate the batch on 16-bit overflow), take the latest absolute position, and collapse controller/touch/pen move/hover runs. terminate_batch stops at a state-changing event (button change, eventType change, active-mask change) so ordering semantics are preserved; not_batchable skips a non-matching controller but keeps scanning. — _Moonlight 'spams controller packets even when not necessary' (src/input.cpp:282). Batching cuts injected-event count under load without dropping state transitions — directly reduces input-to-screen jitter and OS overhead._ - **VK→scancode injection with normalization fallback ladder** — keyboard_update (src/platform/windows/input.cpp:608) prefers KEYEVENTF_SCANCODE using the static US-English VK_TO_SCANCODE_MAP (keylayout.h). If the client flagged the VK as non-normalized (SS_KBE_FLAG_NON_NORMALIZED) it falls back to MapVirtualKey under config::input.always_send_scancodes (excluding VK_LWIN/RWIN/PAUSE which misbehave), else sends a raw VK event. A curated switch adds KEYEVENTF_EXTENDEDKEY for the extended-key set (arrows, nav cluster, RWIN/RMENU/RCONTROL, numpad divide, apps). — _Many games read DirectInput/raw scancodes, not VK events; sending scancodes is essential for in-game key compatibility. The extended-key flag is required or arrow keys / right-modifiers misfire. This is a concrete table+logic punktfunk's Windows VK path can adopt verbatim._ -- **Desktop-switch retry on every SendInput / InjectSyntheticPointerInput** — send_input (src/platform/windows/input.cpp:477) and inject_synthetic_pointer_input (line 499) retry once after calling syncThreadDesktop() (misc.cpp:251 — OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK)+SetThreadDesktop) when the call fails and the input desktop handle changed, tracked in a thread_local _lastKnownInputDesktop. — _On Windows the input desktop changes on UAC prompts, lock screen, and Ctrl+Alt+Del (secure desktop / Winlogon). Without re-binding the thread to the new desktop, all injected input silently fails. This is exactly the secure-desktop problem area called out in punktfunk's docs/memory — Apollo solves it cheaply per-call rather than with a second process._ +- **Desktop-switch retry on every SendInput / InjectSyntheticPointerInput** — send_input (src/platform/windows/input.cpp:477) and inject_synthetic_pointer_input (line 499) retry once after calling syncThreadDesktop() (misc.cpp:251 — OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK)+SetThreadDesktop) when the call fails and the input desktop handle changed, tracked in a thread_local _lastKnownInputDesktop. — _On Windows the input desktop changes on UAC prompts, lock screen, and Ctrl+Alt+Del (secure desktop / Winlogon). Without re-binding the thread to the new desktop, all injected input silently fails. This is exactly the secure-desktop problem area called out in punktfunk's design/memory — Apollo solves it cheaply per-call rather than with a second process._ - **ViGEm dual-target gamepad with client-negotiated type selection** — alloc_gamepad (src/platform/windows/input.cpp:1175) picks X360 vs DS4 by precedence: explicit config (x360/ds4) > client-reported LI_CTYPE_PS/XBOX > motion_as_ds4 if accel/gyro present > touchpad_as_ds4 > default X360. It warns when capabilities (motion/touchpad/RGB) will be lost on X360. DS4 path packs motion, touchpad, and battery into DS4_REPORT_EX. — _DS4 is the only ViGEm target that carries gyro/accel, touchpad, and lightbar; X360 is the safe default. punktfunk already does client-negotiated pad type — Apollo's capability-driven auto-selection (motion/touchpad presence → DS4) and the explicit 'feature will be lost' warnings are a more refined policy worth porting._ - **DS4 timestamped resend loop (ds4_update_ts_and_send)** — Every DS4 report advances wTimestamp by elapsed time in 5.333µs units and re-arms a 100ms repeat_task (src/platform/windows/input.cpp:1454-1481), so the 16-bit timestamp never stalls/overflows even when no new input arrives. — _'Some applications require updated timestamp values to register DS4 input' (line 1450). Without the heartbeat, motion-aware games ignore a held DS4. Non-obvious gotcha that any DS4-emulating host must replicate._ - **Synthetic pen/touch via InjectSyntheticPointerInput with periodic refresh and slot compaction** — Per-client synthetic pointer devices (CreateSyntheticPointerDevice, Win10 1809+). Touch slots are kept contiguous via perform_touch_compaction (line 715, required by the API), edge-triggered flags (DOWN/UP/CANCELED/UPDATE) are cleared after each frame (line 900/1020), and a 50ms repeat task (ISPI_REPEAT_INTERVAL) re-injects held state because Windows auto-cancels untouched interactions after ~1s. — _Touch/pen are stateful, slot-indexed, and self-cancelling — a fundamentally different injection model than mouse/keyboard. If punktfunk grows touch/pen, this is the reference for the Windows-specific contiguity + refresh requirements._ @@ -479,7 +479,7 @@ A single static `struct tray` (l.112) holds icon path, tooltip, a fixed menu arr - **Per-vendor encoder enum string translators** — Whole namespaces (nv/amd/qsv/vt/sw, config.cpp l.53-357) map human strings ('ultralowlatency','cqp','superfast') to encoder SDK integer constants, with low-latency presets as the DEFAULTS (e.g. amd usage = ultralowlatency l.469-471, sw preset 'superfast'/'zerolatency' l.451-453, nvenc realtime HAGS + high-power mode on by default l.457-459). — _Defaults are explicitly tuned for latency, not quality — the encoder is configured ultra-low-latency out of the box. A low-latency host's config defaults should bias the same way; this is the concrete table punktfunk can port for AMD/QSV/VT vendor parity._ - **Embedded HTTPS server sharing the host TLS identity** — confighttp uses SimpleWeb::Server seeded with nvhttp.cert/pkey (confighttp.cpp l.1511) — the SAME cert the Moonlight/GameStream pairing uses — on a fixed port offset (PORT_HTTPS=1 → base+1). — _One identity, one cert, management UI and stream control on adjacent ports. punktfunk already shares its cert.pem between GameStream pairing and punktfunk/1; the lesson is the web console can reuse it rather than carrying a separate mgmt TLS story._ - **Single-string session cookie with salted-hash validation** — authenticate() (l.179) validates hex(hash(cookie + salt)) against an in-memory sessionCookie with a 15-day steady_clock expiry; login (l.1469) rand_alphabet(64) the raw cookie and stores only its hash. checkIPOrigin gates by pc/lan/wan BEFORE auth. — _Contrast with punktfunk's mgmt API (bearer token in ~/.config/punktfunk/mgmt-token + web login gate). Apollo's cookie+IP-origin model is simpler for a desktop single-operator host and avoids a static long-lived token; worth considering for the web console's UX._ -- **Windows service↔UI self-elevation handshake** — config::parse (l.1490-1534): a non-admin Start-Menu shortcut self-relaunches as admin (ShellExecuteExW 'runas' --shortcut-admin l.1511), starts the service, wait_for_ui_ready() polls the Win32 TCP table for the LISTEN socket (entry_handler.cpp l.236), then launch_ui(), and returns 1 so the shortcut process never starts a stream. — _This is the mature answer to the exact problem punktfunk's Windows host hit (docs/windows-host.md 'secure-desktop two-process design', Session-0 vs interactive session). Apollo solves UI-launch-from-service cleanly; the TCP-table readiness poll is directly portable._ +- **Windows service↔UI self-elevation handshake** — config::parse (l.1490-1534): a non-admin Start-Menu shortcut self-relaunches as admin (ShellExecuteExW 'runas' --shortcut-admin l.1511), starts the service, wait_for_ui_ready() polls the Win32 TCP table for the LISTEN socket (entry_handler.cpp l.236), then launch_ui(), and returns 1 so the shortcut process never starts a stream. — _This is the mature answer to the exact problem punktfunk's Windows host hit (design/windows-host.md 'secure-desktop two-process design', Session-0 vs interactive session). Apollo solves UI-launch-from-service cleanly; the TCP-table readiness poll is directly portable._ - **Tray thread DACL hardening for SYSTEM-context survival** — init_tray() (l.143-197) adds an EXPLICIT_ACCESS ACE granting SYNCHRONIZE to Everyone on the current thread handle before registering the icon, and busy-waits for GetShellWindow() (l.201) so the icon registers reliably across logoff/logon. — _When the host runs as a Windows service (SYSTEM), Explorer can't open the thread to detect termination → ghost tray icons forever. punktfunk's Windows host, if it ever runs as a service with a tray, needs this exact DACL fix._ - **JSON-list config values parsed via ptree wrapping** — Multi-line bracketed values (global_prep_cmd, server_cmd, dd_mode_remapping) are extracted as raw strings by the flat parser, wrapped in a synthetic JSON object, then parsed by boost ptree (list_prep_cmd_f l.949, mode_remapping_from_view l.411). — _A pragmatic hybrid: flat key=value for the human-editable 90%, embedded JSON for structured fields, without committing to full-JSON config. Shows how to grow a flat config without a rewrite._ @@ -680,7 +680,7 @@ Both transports use the persistent `AudioCapSlot` (gamestream/audio.rs:251-257) ### Input handling & injection — 🔴 Apollo ahead -For the Windows host specifically, Apollo is ahead on input breadth and robustness. Apollo covers mouse (rel+abs), keyboard (with a static US-layout VK→scancode table for game compatibility), Unicode text, scroll, **touch + pen via CreateSyntheticPointerDevice**, and **both X360 and DS4** gamepads with rumble/LED/motion/touchpad/battery feedback (Apollo src/platform/windows/input.cpp). punktfunk's Windows host covers mouse/keyboard/scroll/X360-only; touch and pen are explicit no-ops (sendinput.rs:231-237), there is no Unicode text path (gamestream/input.rs:83-84), and only the Xbox 360 virtual pad exists on Windows. Apollo also has the more efficient secure-desktop model (retry-only) vs punktfunk's per-event reattach (sendinput.rs:97), and Apollo's task-pool queue + type-aware batching (Apollo src/input.cpp:1481-1571, 1208-1475) coalesces input spam off the network thread — punktfunk's GameStream path injects inline on the ENet thread (control.rs:207-211) with no batching anywhere. punktfunk's design is cleaner and its m3 path's session-end held-key release + backend-follow logic is genuinely nicer than Apollo, but those are punktfunk/1-specific; on the shared Windows-host injection surface Apollo is the more complete, battle-tested implementation. punktfunk's docs/windows-secure-desktop.md already flags the retry-only refactor as planned-but-unshipped, confirming the gap. +For the Windows host specifically, Apollo is ahead on input breadth and robustness. Apollo covers mouse (rel+abs), keyboard (with a static US-layout VK→scancode table for game compatibility), Unicode text, scroll, **touch + pen via CreateSyntheticPointerDevice**, and **both X360 and DS4** gamepads with rumble/LED/motion/touchpad/battery feedback (Apollo src/platform/windows/input.cpp). punktfunk's Windows host covers mouse/keyboard/scroll/X360-only; touch and pen are explicit no-ops (sendinput.rs:231-237), there is no Unicode text path (gamestream/input.rs:83-84), and only the Xbox 360 virtual pad exists on Windows. Apollo also has the more efficient secure-desktop model (retry-only) vs punktfunk's per-event reattach (sendinput.rs:97), and Apollo's task-pool queue + type-aware batching (Apollo src/input.cpp:1481-1571, 1208-1475) coalesces input spam off the network thread — punktfunk's GameStream path injects inline on the ENet thread (control.rs:207-211) with no batching anywhere. punktfunk's design is cleaner and its m3 path's session-end held-key release + backend-follow logic is genuinely nicer than Apollo, but those are punktfunk/1-specific; on the shared Windows-host injection surface Apollo is the more complete, battle-tested implementation. punktfunk's design/windows-secure-desktop.md already flags the retry-only refactor as planned-but-unshipped, confirming the gap. **How punktfunk does it.** @@ -748,7 +748,7 @@ For the Windows host specifically, Apollo is clearly ahead on this subsystem. Ap - punktfunk has TWO app surfaces by design: the GameStream apps.json catalog (Moonlight compat) AND a richer punktfunk/1 library (Steam local scan + custom store + CDN art + uniform GameEntry grid). Apollo has only the apps.json catalog because it ships no client. - punktfunk's launch security model is deliberately client-can't-inject: the client sends only a store-qualified id and the host resolves it against its OWN library (library.rs:394-412), with steam appid validated digits-only. Apollo trusts its own apps.json cmds (it has no untrusted remote launch id). - punktfunk keeps NO async on the per-frame path; the SudoVDA watchdog pinger and capture are native threads. Apollo's libdisplaydevice RetryScheduler is its own machinery; punktfunk has no equivalent scheduler by choice (yet — see candidate improvements). -- punktfunk's Windows virtual display is the SOLE primary output (isolate_displays + CDS_SET_PRIMARY) specifically to capture the secure/Winlogon desktop — a deliberate, documented design (docs/windows-secure-desktop.md) that goes beyond what stock Apollo needs. +- punktfunk's Windows virtual display is the SOLE primary output (isolate_displays + CDS_SET_PRIMARY) specifically to capture the secure/Winlogon desktop — a deliberate, documented design (design/windows-secure-desktop.md) that goes beyond what stock Apollo needs. **Transfer candidates from Apollo (6):** _Actually launch the app/game on Windows (CreateProcessAsUserW into the user session)_, _Display-config apply/revert with a retry scheduler and guaranteed revert on disconnect_, _Set HDR on the virtual display and advertise IsHdrSupported when the client requests it_, _Per-(app,client) stable virtual-display GUID instead of one fixed MONITOR_GUID_, _Inject per-app launch env (client res/fps/HDR/audio + status) for launch scripts_, _auto_detach heuristic for launcher-style apps (Steam/UWP) that exit immediately_ — see Part 4. @@ -765,7 +765,7 @@ On the API itself punktfunk is arguably ahead (versioned `/api/v1`, compile-time punktfunk splits the control surface into three pieces and deliberately keeps them OUT of the host binary where Apollo bundles them in. ##### 1. Management plane = a versioned REST API only (`crates/punktfunk-host/src/mgmt.rs`) -- An axum `Router` (`mgmt.rs:166` `fn app`) under `/api/v1`, single source of truth shared between the live server and the `openapi` subcommand (`mgmt.rs:195` `api_router_parts`, `main.rs:86`). The OpenAPI 3.1 doc is generated at compile time with `utoipa` and a checked-in copy is drift-tested against `docs/api/openapi.json` (`mgmt.rs:1582` `openapi_document_is_complete_and_checked_in`). This is a real maturity advantage over Apollo, which has no machine-readable API spec. +- An axum `Router` (`mgmt.rs:166` `fn app`) under `/api/v1`, single source of truth shared between the live server and the `openapi` subcommand (`mgmt.rs:195` `api_router_parts`, `main.rs:86`). The OpenAPI 3.1 doc is generated at compile time with `utoipa` and a checked-in copy is drift-tested against `api/openapi.json` (`mgmt.rs:1582` `openapi_document_is_complete_and_checked_in`). This is a real maturity advantage over Apollo, which has no machine-readable API spec. - Routes: host info/capabilities/port map (`mgmt.rs:590`), live status (`mgmt.rs:671`), paired GameStream clients list/unpair (`mgmt.rs:707`,`752`), the GameStream PIN flow (`mgmt.rs:789`,`814`), the native punktfunk/1 pairing surface — arm/disarm/status/list/unpair (`mgmt.rs:870`-`994`), **delegated pairing approval** via a pending-device queue (`mgmt.rs:1011`,`1049`,`1094`), session stop + force-IDR (`mgmt.rs:1120`,`1144`), and game-library CRUD (`mgmt.rs:1171`-`1252`). - **HTTPS always, even on loopback** (`mgmt.rs:75` `run`): it runs the rustls handshake itself via tokio-rustls so it can surface the verified peer cert to handlers (`mgmt.rs:115` `serve_https`), reusing the host's persistent identity cert that clients already pin (`mgmt.rs:90`). - **Dual auth** (`mgmt.rs:518` `require_auth`): a paired native client authenticates by its **mTLS certificate fingerprint** (matched against the native paired store, no token needed); everyone else (the web console / admin) uses a bearer token compared in constant time (`mgmt.rs:551` `token_eq` via SHA-256 digest compare). `/api/v1/health` is the only unauthenticated route. This is stronger than Apollo's single-global-session-cookie scheme (Apollo `confighttp.cpp` has exactly one `std::string sessionCookie`). @@ -784,7 +784,7 @@ A token always exists with zero operator steps: env `PUNKTFUNK_MGMT_TOKEN` wins, There is no system tray, no balloon notifications, and no "open the UI in the browser" entry point anywhere in `crates/punktfunk-host`. Apollo has a full cross-platform tray (`system_tray.cpp`) with state-driven icon/notification updates and menu callbacks. ##### 6. Windows launch story = scripts, not in-binary -The two-process secure-desktop design exists for *capture* (`main.rs:204` `wgc-helper` subcommand + `capture/wgc_relay.rs` `CreateProcessAsUserW`), but the service/desktop launch dance is handled by external scripts (scheduled task -> PsExec64 -> launch.vbs -> host-run.cmd; `docs/windows-host.md:77-96`). punktfunk has no in-binary service install, no self-elevation, no "launch UI in browser", and no tray — all of which Apollo bakes into `config.cpp`/`entry_handler.cpp`/`system_tray.cpp`. +The two-process secure-desktop design exists for *capture* (`main.rs:204` `wgc-helper` subcommand + `capture/wgc_relay.rs` `CreateProcessAsUserW`), but the service/desktop launch dance is handled by external scripts (scheduled task -> PsExec64 -> launch.vbs -> host-run.cmd; `design/windows-host.md:77-96`). punktfunk has no in-binary service install, no self-elevation, no "launch UI in browser", and no tray — all of which Apollo bakes into `config.cpp`/`entry_handler.cpp`/`system_tray.cpp`. **Intentional divergences (by design, not gaps):** @@ -1555,14 +1555,14 @@ punktfunk's **secure-desktop / desktop-switch capture recovery is genuinely matu ##### Where punktfunk is weaker / missing / fragile -1. **No real Windows service — relies on a PsExec scheduled task.** The launch chain is a scheduled task → `PsExec64 -s -i 1` → `wscript.exe launch.vbs` → hidden `host-run.cmd` (`docs/windows-host.md:78-84`). There is **no `SERVICE_CONTROL_SESSIONCHANGE` relaunch** — the doc even lists it as unimplemented "step 6" (`docs/windows-secure-desktop.md:89`). PsExec is a 3rd-party SysInternals tool, not redistributable cleanly, and `-s -i 1` hard-codes session 1. None of the launch scripts (`launch.vbs`, `host-run.cmd`) are checked into the repo (only `scripts/headless/win-build.cmd` exists). This is the single biggest fragility vs Apollo's `sunshinesvc.cpp`. +1. **No real Windows service — relies on a PsExec scheduled task.** The launch chain is a scheduled task → `PsExec64 -s -i 1` → `wscript.exe launch.vbs` → hidden `host-run.cmd` (`design/windows-host.md:78-84`). There is **no `SERVICE_CONTROL_SESSIONCHANGE` relaunch** — the doc even lists it as unimplemented "step 6" (`design/windows-secure-desktop.md:89`). PsExec is a 3rd-party SysInternals tool, not redistributable cleanly, and `-s -i 1` hard-codes session 1. None of the launch scripts (`launch.vbs`, `host-run.cmd`) are checked into the repo (only `scripts/headless/win-build.cmd` exists). This is the single biggest fragility vs Apollo's `sunshinesvc.cpp`. 2. **No nvprefs / NvAPI at all.** `grep` for `nvprefs|NvAPI|DRS_|PREFERRED_PSTATE|DXPRESENT` across the host returns nothing. No PREFERRED_PSTATE_MAX for the encoder, no OGL_CPL_PREFER_DXPRESENT (so GL/Vulkan fullscreen apps may not be capturable via WGC/DDA), and no undo-file crash safety. 3. **No DXGI GPU-preference / output-reparenting hook.** No MinHook of `NtGdiDdDDIGetCachedHybridQueryValue`. On a hybrid/Optimus box DXGI can reparent the SudoVDA output onto the render GPU and break DDA. punktfunk's "search all adapters" partly papers over this but does not prevent the reparenting itself. -4. **mDNS uses the cross-platform `mdns-sd` crate, not Windows-native `DnsServiceRegister`** (`discovery.rs:17`). It works, but it does NOT carry Apollo's RFC-1035 empty-TXT fix — and the GameStream/Moonlight mDNS path on Windows is unverified (`docs/windows-host.md:46`). A non-RFC-compliant TXT can be rejected by Apple's resolver. +4. **mDNS uses the cross-platform `mdns-sd` crate, not Windows-native `DnsServiceRegister`** (`discovery.rs:17`). It works, but it does NOT carry Apollo's RFC-1035 empty-TXT fix — and the GameStream/Moonlight mDNS path on Windows is unverified (`design/windows-host.md:46`). A non-RFC-compliant TXT can be rejected by Apple's resolver. 5. **No stream-start system tuning.** No `NtSetTimerResolution`/`timeBeginPeriod`, no `DwmEnableMMCSS`, no `SetPriorityClass(HIGH_PRIORITY_CLASS)`, no `SetThreadExecutionState(ES_DISPLAY_REQUIRED)`, no WLAN media-streaming mode, no Mouse-Keys-on-headless trick. (Linux has none of this either, but on Windows these are real latency/jitter levers Apollo proves out.) 6. **No `factory->IsCurrent()` per-frame check.** punktfunk reacts to errors from `AcquireNextFrame` but does not proactively detect HDR/topology changes the way Apollo does each frame (`display_base.cpp:235`) — it relies on ACCESS_LOST firing, which it usually does, but IsCurrent is the cleaner signal. 7. **No `is_user_session_locked()` / CCD pre-flight.** Before a mode-set or isolation, Apollo checks `WTSQuerySessionInformationW` + `SetDisplayConfig(SDC_VALIDATE)` (`utils.cpp:184-237`); punktfunk just attempts and handles failure, which can thrash the display during a lock. -8. **Clock epoch is `SystemTime::now()` (`dxgi.rs:1530`), not `GetSystemTimePreciseAsFileTime`.** The doc itself flags this as a cross-machine-latency risk (`docs/windows-host.md:284-286`); std SystemTime on Windows historically has coarser (~1–15 ms) resolution than the precise FILETIME API, which can corrupt the ClockProbe/ClockEcho skew handshake. +8. **Clock epoch is `SystemTime::now()` (`dxgi.rs:1530`), not `GetSystemTimePreciseAsFileTime`.** The doc itself flags this as a cross-machine-latency risk (`design/windows-host.md:284-286`); std SystemTime on Windows historically has coarser (~1–15 ms) resolution than the precise FILETIME API, which can corrupt the ClockProbe/ClockEcho skew handshake. #### Transfer opportunities @@ -1772,7 +1772,7 @@ GameStream `SO_SNDBUF`), **#8** (move GameStream input injection off the ENet se *Area:* `cmp:input` · *Windows-host:* yes · *Severity:* high · *Effort:* small - **Apollo does:** send_input() / inject_synthetic_pointer_input() call SendInput FIRST, and only on failure (0 injected) re-run syncThreadDesktop() (OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK)+SetThreadDesktop) and retry once, tracking the desktop in a thread_local _lastKnownInputDesktop — src/platform/windows/input.cpp:477,499 + src/platform/windows/misc.cpp:251 -- **punktfunk gap:** SendInputInjector::inject() calls reattach_input_desktop() (an OpenInputDesktop+SetThreadDesktop+CloseDesktop) at the TOP of EVERY event — crates/punktfunk-host/src/inject/sendinput.rs:97,50-69. This is a syscall triple per mouse-move; punktfunk's own docs/windows-secure-desktop.md:78-80 lists this exact refactor (step 2) as planned but unshipped. +- **punktfunk gap:** SendInputInjector::inject() calls reattach_input_desktop() (an OpenInputDesktop+SetThreadDesktop+CloseDesktop) at the TOP of EVERY event — crates/punktfunk-host/src/inject/sendinput.rs:97,50-69. This is a syscall triple per mouse-move; punktfunk's own design/windows-secure-desktop.md:78-80 lists this exact refactor (step 2) as planned but unshipped. - **Proposal:** Inject first; cache the HDESK thread-local; only on a 0/partial SendInput result call reattach_input_desktop() and retry once. Use DF_ALLOWOTHERACCOUNTHOOK in the OpenInputDesktop access (sendinput.rs:52-56 currently passes DESKTOP_CONTROL_FLAGS(0)) so the secure desktop is reachable. Keeps the steady-state hot path to a single SendInput call. #### 2. Detect resolution/format change on the acquire hot path, not only during rebuild @@ -1846,7 +1846,7 @@ GameStream `SO_SNDBUF`), **#8** (move GameStream input injection off the ENet se *Area:* `cmp:config-management` · *Windows-host:* yes · *Severity:* high · *Effort:* medium - **Apollo does:** system_tray.cpp builds a single static tray struct with a menu (Open/Force-stop/Reset-display/Restart/Quit, l.112-141) and pushes state changes from the streaming pipeline — update_tray_playing/pausing/stopped/launch_error/require_pin/paired/client_connected (l.238-412) each swap the icon + raise a balloon notification; init_tray hardens the thread DACL so the icon survives running as SYSTEM (l.143-204); a 50 ms polling thread drives it (tray_thread_worker l.415). -- **punktfunk gap:** No tray code exists anywhere in crates/punktfunk-host (grep for tray/notify-rust/balloon returns nothing). On Windows the host runs windowless as SYSTEM in Session 1 via external scripts (docs/windows-host.md:77-84) with the only operator feedback being a redirected log file — there is no visible, clickable status/control surface for a desktop user. +- **punktfunk gap:** No tray code exists anywhere in crates/punktfunk-host (grep for tray/notify-rust/balloon returns nothing). On Windows the host runs windowless as SYSTEM in Session 1 via external scripts (design/windows-host.md:77-84) with the only operator feedback being a redirected log file — there is no visible, clickable status/control surface for a desktop user. - **Proposal:** Add an optional system-tray plane behind a feature/flag using a Rust tray crate (e.g. tray-icon) spawned on its own native thread (no async on the per-frame path). Drive it from the existing AppState atomics/locks already exposed by mgmt.rs get_status (streaming/audio_streaming/pin_pending/session) — poll or push on state change to swap icon + show balloons (connected, pairing PIN, launch error). Menu items call the SAME primitives the API uses (stop_session, force_idr, native arm-pairing, quit). On Windows replicate Apollo's thread-DACL hardening so the icon shows when launched as SYSTEM in the interactive session. #### 11. Treat S_OK-with-no-change frames as timeouts via DXGI update flags @@ -1923,7 +1923,7 @@ GameStream `SO_SNDBUF`), **#8** (move GameStream input injection off the ENet se *Area:* `cmp:config-management` · *Windows-host:* yes · *Severity:* high · *Effort:* large - **Apollo does:** config.cpp:1490-1534 handles the Windows shortcut/service launch dance inside the binary: --shortcut/--shortcut-admin handling, ShellExecuteExW(runas, --shortcut-admin) to self-elevate when the service isn't running, waits for the service, wait_for_ui_ready(), launch_ui(), then returns 1 so the foreground process does NOT also start a stream host. This is Sunshine/Apollo's mature service<->UI two-process split that makes one-click launch work. -- **punktfunk gap:** punktfunk has no service-install / self-elevation / interactive-session bring-up in the binary. Deployment is documented as a manual chain of external scripts — scheduled task -> PsExec64 -i 1 -> launch.vbs -> host-run.cmd (docs/windows-host.md:77-96) — fragile and operator-hostile. main.rs has no install/service subcommand. +- **punktfunk gap:** punktfunk has no service-install / self-elevation / interactive-session bring-up in the binary. Deployment is documented as a manual chain of external scripts — scheduled task -> PsExec64 -i 1 -> launch.vbs -> host-run.cmd (design/windows-host.md:77-96) — fragile and operator-hostile. main.rs has no install/service subcommand. - **Proposal:** Add `punktfunk-host install`/`uninstall`/`service` subcommands (Windows-gated) that register a service or an Interactive/Highest scheduled task to launch the host in Session 1 (the documented requirement for DXGI duplication + SendInput), and the self-elevate-if-not-running shortcut path. Reuse the existing capture/wgc_relay CreateProcessAsUserW machinery already in the crate. This codifies the script chain into the binary without touching the per-frame path or core. #### 21. Composite the moved cursor onto a clean copy even when DDA returns no new desktop frame @@ -1962,7 +1962,7 @@ GameStream `SO_SNDBUF`), **#8** (move GameStream input injection off the ENet se *Area:* `win:system-secure-desktop` · *Windows-host:* yes · *Severity:* high · *Effort:* large - **Apollo does:** SunshineSvc.exe runs as LocalSystem in Session 0, loops on WTSGetActiveConsoleSessionId, clones its own token with DuplicateTokenEx(TokenPrimary)+SetTokenInformation(TokenSessionId) and CreateProcessAsUserW into winsta0\\default inside a per-session job object (JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE|BREAKAWAY_OK); opts into SERVICE_ACCEPT_SESSIONCHANGE and on WTS_CONSOLE_CONNECT terminates+relaunches the host in the new session (tools/sunshinesvc.cpp:95,111,239,256,267,276-294) -- **punktfunk gap:** punktfunk has no Windows service; launch is a PsExec64 -s -i 1 scheduled task hard-coded to session 1 (docs/windows-host.md:78-84), with the SERVICE_CONTROL_SESSIONCHANGE relaunch listed as unimplemented step 6 (docs/windows-secure-desktop.md:89). Launch scripts are not even in the repo. +- **punktfunk gap:** punktfunk has no Windows service; launch is a PsExec64 -s -i 1 scheduled task hard-coded to session 1 (design/windows-host.md:78-84), with the SERVICE_CONTROL_SESSIONCHANGE relaunch listed as unimplemented step 6 (design/windows-secure-desktop.md:89). Launch scripts are not even in the repo. - **Proposal:** Add a small Rust service binary (new crate or punktfunk-host `service` subcommand) using windows::Win32::System::Services (RegisterServiceCtrlHandlerEx, StartServiceCtrlDispatcher) that mirrors sunshinesvc.cpp: WTSGetActiveConsoleSessionId -> DuplicateTokenEx+SetTokenInformation(TokenSessionId) -> CreateProcessAsUserW(lpDesktop=winsta0\\default) into a kill-on-close job, accept SERVICE_ACCEPT_SESSIONCHANGE, and relaunch the host on a genuine console-session change. Ship an installer and drop the PsExec dependency. #### 25. Elevate capture/encode/send thread priority on the host hot path diff --git a/design/ci.md b/design/ci.md index 04c34a5..1c4b483 100644 --- a/design/ci.md +++ b/design/ci.md @@ -40,7 +40,7 @@ the GPU/compositor stack of the box it runs on). What is: | Image | Source | Notes | |---|---|---| -| `git.unom.io/unom/punktfunk-web` | `web/Dockerfile` (repo-root context — orval needs `docs/api/openapi.json`) | Nitro `bun` bundle; `PORT` (3000) and `PUNKTFUNK_MGMT_URL` env at runtime | +| `git.unom.io/unom/punktfunk-web` | `web/Dockerfile` (repo-root context — orval needs `api/openapi.json`) | Nitro `bun` bundle; `PORT` (3000) and `PUNKTFUNK_MGMT_URL` env at runtime | | `git.unom.io/unom/punktfunk-docs` | `docs-site/Dockerfile` | This site; `PORT` (3000) | | `git.unom.io/unom/punktfunk-rust-ci` | `ci/rust-ci.Dockerfile` | Ubuntu 26.04 + FFmpeg 8/PipeWire/GL/GBM dev libs + a libcuda **link stub** (driver userspace, no kernel module) + pinned rustup — the container `ci.yml`'s Rust job runs in | diff --git a/design/windows-client-bootstrap.md b/design/windows-client-bootstrap.md index 11bcfe3..1867d98 100644 --- a/design/windows-client-bootstrap.md +++ b/design/windows-client-bootstrap.md @@ -51,7 +51,7 @@ back — i.e. the Windows analogue of the **GTK4 Linux client** (`clients/linux` which is the architectural template. The Windows client is close to a 1:1 port of the Linux client with the platform layers swapped. -## Locked decisions (from the Windows-host/client plan, `docs/windows-host.md` + project memory) +## Locked decisions (from the Windows-host/client plan, `design/windows-host.md` + project memory) - **Pure Rust.** `windows-rs` + **Windows App SDK "Reactor"** (WinUI 3 from Rust, merged windows-rs PR #4479). No C++/C#. De-risk Reactor + `SwapChainPanel` FIRST — it's the only novel/uncertain @@ -165,6 +165,6 @@ Windows client should mirror it: - **Core client API:** `crates/punktfunk-core/src/client.rs` (`NativeClient`). - **Protocol:** `crates/punktfunk-core/src/quic.rs` (`Hello.video_caps`, `Welcome.bit_depth`, `VIDEO_CAP_10BIT`/`VIDEO_CAP_HDR`). -- **Full Windows plan + SudoVDA/host details:** `docs/windows-host.md`. +- **Full Windows plan + SudoVDA/host details:** `design/windows-host.md`. - **Host HDR conversion (for the inverse math):** `crates/punktfunk-host/src/capture/dxgi.rs` (`HDR_PS`, `HdrConverter`) + `crates/punktfunk-host/src/encode/nvenc.rs` (BT.2020/PQ VUI). diff --git a/design/windows-host-rewrite.md b/design/windows-host-rewrite.md index 8df31ea..d02d7d1 100644 --- a/design/windows-host-rewrite.md +++ b/design/windows-host-rewrite.md @@ -428,5 +428,5 @@ This file replaces five docs (recoverable from git history): - `windows-host-rewrite-game-capture-bug.md` (the GB1 investigation + fix) — **fixed**; the resolution is §2.5 (capture). The full investigation narrative is in git history. -(The older `docs/windows-host.md`, a pre-rewrite implementation plan from 2026-06-22, is a separate +(The older `design/windows-host.md`, a pre-rewrite implementation plan from 2026-06-22, is a separate lineage and is left as-is.) diff --git a/docs-site/README.md b/docs-site/README.md index 6dce01e..1b38dd9 100644 --- a/docs-site/README.md +++ b/docs-site/README.md @@ -16,8 +16,8 @@ sidebar, and the landing page). It reads [`public/openapi.json`](public/openapi. ```sh # from the repo root — regenerate the spec, then copy the snapshot in: -cargo run -p punktfunk-host -- openapi > docs/api/openapi.json -cp docs/api/openapi.json docs-site/public/openapi.json +cargo run -p punktfunk-host -- openapi > api/openapi.json +cp api/openapi.json docs-site/public/openapi.json ``` ## Develop diff --git a/packaging/arch/PKGBUILD b/packaging/arch/PKGBUILD index 655d2d8..c8838bd 100644 --- a/packaging/arch/PKGBUILD +++ b/packaging/arch/PKGBUILD @@ -94,7 +94,7 @@ package_punktfunk-host() { install -Dm0644 "$R/scripts/host.env.example" "$pkgdir/usr/share/punktfunk/host.env.example" install -Dm0644 "$R/packaging/bazzite/host.env" "$pkgdir/usr/share/punktfunk/host.env.bazzite" install -Dm0644 "$R/packaging/kde/host.env" "$pkgdir/usr/share/punktfunk/host.env.kde" - install -Dm0644 "$R/docs/api/openapi.json" "$pkgdir/usr/share/punktfunk/openapi.json" + install -Dm0644 "$R/api/openapi.json" "$pkgdir/usr/share/punktfunk/openapi.json" install -Dm0644 "$R/LICENSE-MIT" "$pkgdir/usr/share/licenses/punktfunk-host/LICENSE-MIT" install -Dm0644 "$R/LICENSE-APACHE" "$pkgdir/usr/share/licenses/punktfunk-host/LICENSE-APACHE" install -Dm0644 "$R/README.md" "$pkgdir/usr/share/doc/punktfunk-host/README.md" diff --git a/packaging/bazzite/README.md b/packaging/bazzite/README.md index 2e6e6a0..0f13a17 100644 --- a/packaging/bazzite/README.md +++ b/packaging/bazzite/README.md @@ -257,7 +257,7 @@ journalctl --user -u punktfunk-host -f > ⚠️ **There is no firewall script or firewall doc in the repo.** The ports below are derived > directly from the code constants (`crates/punktfunk-host/src/gamestream/mod.rs`, `mgmt.rs`) and -> the GameStream-host port-map (`docs/gamestream-host-plan.md`). Treat the `firewall-cmd` lines as recommended-but-verified, +> the GameStream-host port-map (`design/gamestream-host-plan.md`). Treat the `firewall-cmd` lines as recommended-but-verified, > not a checked-in script. **GameStream / Moonlight ports** (fixed; Moonlight derives them from the HTTP base). These only apply diff --git a/packaging/debian/build-deb.sh b/packaging/debian/build-deb.sh index 001ab94..b8942e7 100755 --- a/packaging/debian/build-deb.sh +++ b/packaging/debian/build-deb.sh @@ -57,7 +57,7 @@ install -Dm0644 scripts/headless/punktfunk-sink.conf "$SHAREDIR/headless/punkt install -Dm0644 scripts/host.env.example "$SHAREDIR/host.env.example" install -Dm0644 packaging/bazzite/host.env "$SHAREDIR/host.env.bazzite" install -Dm0644 packaging/kde/host.env "$SHAREDIR/host.env.kde" -install -Dm0644 docs/api/openapi.json "$SHAREDIR/openapi.json" +install -Dm0644 api/openapi.json "$SHAREDIR/openapi.json" install -Dm0644 LICENSE-MIT "$DOCDIR/LICENSE-MIT" install -Dm0644 LICENSE-APACHE "$DOCDIR/LICENSE-APACHE" install -Dm0644 README.md "$DOCDIR/README.md" diff --git a/packaging/rpm/punktfunk.spec b/packaging/rpm/punktfunk.spec index 9206307..fc29a8c 100644 --- a/packaging/rpm/punktfunk.spec +++ b/packaging/rpm/punktfunk.spec @@ -224,7 +224,7 @@ install -Dm0644 packaging/kde/host.env %{buildroot}%{_datadir}/% # Bazzite KDE Desktop-mode one-shot setup (KWIN_WAYLAND_NO_PERMISSION_CHECKS + RemoteDesktop grant). install -d %{buildroot}%{_datadir}/%{name}/bazzite install -Dm0755 packaging/bazzite/kde-desktop-setup.sh %{buildroot}%{_datadir}/%{name}/bazzite/kde-desktop-setup.sh -install -Dm0644 docs/api/openapi.json %{buildroot}%{_datadir}/%{name}/openapi.json +install -Dm0644 api/openapi.json %{buildroot}%{_datadir}/%{name}/openapi.json %if %{with web} # --- web console subpackage (punktfunk-web) --- @@ -246,7 +246,7 @@ install -Dm0644 web/web.env.example %{buildroot}%{_datadir}/punkt %files %license LICENSE-MIT LICENSE-APACHE -%doc README.md docs/implementation-plan.md packaging/README.md +%doc README.md design/implementation-plan.md packaging/README.md %{_bindir}/punktfunk-host %{_udevrulesdir}/60-punktfunk.rules %{_prefix}/lib/sysctl.d/99-punktfunk-net.conf diff --git a/packaging/windows/README.md b/packaging/windows/README.md index eddd80a..ab462cc 100644 --- a/packaging/windows/README.md +++ b/packaging/windows/README.md @@ -76,10 +76,11 @@ read it from `%ProgramData%\punktfunk\web-password`. | `reset-pf-vdisplay.ps1` | **Dev:** recover a wedged driver — stop host → reap ghost monitor nodes → reload the adapter → start host (no reboot). See *Dev iteration* below. | | `redeploy-pf-vdisplay.ps1` | **Dev:** one-shot redeploy — (optional) build → stop host → `deploy-dev.ps1 -Install` → reload adapter → start host. | | `nvenc/nvenc.def`, `nvenc/gen-nvenc-importlib.ps1` | Synthesise `nvencodeapi.lib` for the `--features nvenc` link (llvm-dlltool / lib.exe). | +| `pf-vkhdr-layer/` | **HDR Vulkan layer** (standalone `cdylib`): lets Vulkan games (Doom: The Dark Ages, etc.) enable HDR over the virtual display by advertising the HDR surface formats the NVIDIA/AMD ICDs hide on an indirect display. Built by the packer, laid into `{app}\vklayer`, registered under `HKLM64\…\Khronos\Vulkan\ImplicitLayers` (opt-out *Install the HDR Vulkan layer* task). Self-gated on the display's HDR state. See its README. | > **Vendored driver:** pf-vdisplay is our **all-Rust IddCx** virtual display (UMDF2), built from > `packaging/windows/drivers/`. It replaced the vendored SudoVDA C++ driver — full story in -> [`docs/windows-virtual-display-rust-port.md`](../../docs/windows-virtual-display-rust-port.md). The +> [`design/windows-virtual-display-rust-port.md`](../../design/windows-virtual-display-rust-port.md). The > **signed** output (`pf_vdisplay.dll`/`.inf`/`.cat` + `punktfunk-driver.cer`; signer > `punktfunk-ds-test` — the same cert the gamepad drivers ship, Class=Display, HWID `root\pf_vdisplay`) > is checked in under `pf-vdisplay/`. To refresh it after a driver-source change, rebuild + re-sign with diff --git a/packaging/windows/drivers/Cargo.toml b/packaging/windows/drivers/Cargo.toml index a869bb3..f7dc964 100644 --- a/packaging/windows/drivers/Cargo.toml +++ b/packaging/windows/drivers/Cargo.toml @@ -1,6 +1,6 @@ # Unified in-tree workspace for punktfunk's all-Rust UMDF drivers, on microsoft/windows-drivers-rs # (crates.io wdk/wdk-sys/wdk-build — NOT the dev-box ../../crates/wdk* path-deps). Part of the -# Windows-host rewrite (docs/windows-host-rewrite.md, M1). pf-vdisplay + the gamepad drivers move here. +# Windows-host rewrite (design/windows-host-rewrite.md, M1). pf-vdisplay + the gamepad drivers move here. # # Separate from the main cargo workspace (own [workspace] root) because driver crates are cdylibs built # with the WDK toolchain (cargo-wdk / wdk-build) on Windows only. Path-deps the shared ABI crate diff --git a/packaging/windows/drivers/pf-vdisplay/Cargo.toml b/packaging/windows/drivers/pf-vdisplay/Cargo.toml index 93287ea..650c07d 100644 --- a/packaging/windows/drivers/pf-vdisplay/Cargo.toml +++ b/packaging/windows/drivers/pf-vdisplay/Cargo.toml @@ -1,6 +1,6 @@ # pf-vdisplay — the all-Rust UMDF IddCx virtual-display driver (M1 step-2 rewrite onto wdk-sys + the # owned pf-driver-proto ABI). Replaces the vendored-binding oracle at packaging/windows/vdisplay-driver/ -# (deleted once on-glass parity is reached, per docs/windows-host-rewrite.md §14 STEP 8). +# (deleted once on-glass parity is reached, per design/windows-host-rewrite.md §14 STEP 8). [package] name = "pf-vdisplay" edition.workspace = true diff --git a/packaging/windows/drivers/pf-vdisplay/src/adapter.rs b/packaging/windows/drivers/pf-vdisplay/src/adapter.rs index 43297cc..033c4d3 100644 --- a/packaging/windows/drivers/pf-vdisplay/src/adapter.rs +++ b/packaging/windows/drivers/pf-vdisplay/src/adapter.rs @@ -137,7 +137,7 @@ pub(crate) fn adapter() -> Option { /// iGPU+dGPU box the OS may otherwise pick the iGPU to render the virtual monitor, so the host's shared /// ring textures (created on the NVENC dGPU) can't be opened → `DRV_STATUS_TEX_FAIL` → the host's 20 s /// black bail. Pinning the render adapter to the encode GPU fixes that. Unconditional — NOT the -/// SudoVDA-parity default-off branch (`docs/windows-host-rewrite.md` §2.8). Returns +/// SudoVDA-parity default-off branch (`design/windows-host-rewrite.md` §2.8). Returns /// `STATUS_NOT_FOUND` if called before the adapter exists. pub fn set_render_adapter(luid_low: u32, luid_high: i32) -> NTSTATUS { let Some(adapter) = adapter() else { diff --git a/packaging/windows/drivers/pf-vdisplay/src/control.rs b/packaging/windows/drivers/pf-vdisplay/src/control.rs index 3f9c3bc..22c6bc8 100644 --- a/packaging/windows/drivers/pf-vdisplay/src/control.rs +++ b/packaging/windows/drivers/pf-vdisplay/src/control.rs @@ -27,7 +27,7 @@ static WATCHDOG_STARTED: AtomicBool = AtomicBool::new(false); /// without a cooperative REMOVE (crash / `TerminateProcess`) left its virtual monitor + swap-chain /// worker + pooled D3D device wedged in WUDFHost until the next host start's CLEAR_ALL, and a /// not-restarted host left the orphan monitor in the desktop topology indefinitely -/// (`docs/windows-host-rewrite.md` §2.8). This thread closes that: if no IOCTL arrives for +/// (`design/windows-host-rewrite.md` §2.8). This thread closes that: if no IOCTL arrives for /// `WATCHDOG_TIMEOUT_S` while monitors exist, it departs them all. /// /// (A WDF `EvtFileClose` on the control handle would be more immediate — the plan's preferred §3.4 diff --git a/packaging/windows/drivers/pf-vdisplay/src/lib.rs b/packaging/windows/drivers/pf-vdisplay/src/lib.rs index b855ce3..397a666 100644 --- a/packaging/windows/drivers/pf-vdisplay/src/lib.rs +++ b/packaging/windows/drivers/pf-vdisplay/src/lib.rs @@ -1,5 +1,5 @@ //! pf-vdisplay — the all-Rust UMDF IddCx virtual-display driver (M1 step-2 rewrite, on wdk-sys + the -//! owned pf-driver-proto ABI). See docs/windows-host-rewrite.md §14 for the full port plan. +//! owned pf-driver-proto ABI). See design/windows-host-rewrite.md §14 for the full port plan. //! //! STEP 2: the IddCx driver SKELETON — DriverEntry → driver_add builds the full `IDD_CX_CLIENT_CONFIG` //! (14 IddCx callbacks + the PnP `EvtDeviceD0Entry`, all stubs) sized via the versioned diff --git a/scripts/bootstrap-ubuntu.sh b/scripts/bootstrap-ubuntu.sh index 8951ad0..221b92e 100755 --- a/scripts/bootstrap-ubuntu.sh +++ b/scripts/bootstrap-ubuntu.sh @@ -205,5 +205,5 @@ cat <<'NEXT' export XDG_RUNTIME_DIR=/run/user/$(id -u) WAYLAND_DISPLAY=wayland-1 swaymsg -t get_outputs # confirm HEADLESS-1 bash scripts/headless/capture-smoke-test.sh # wf-recorder -> hevc_nvenc -> /tmp/*.mkv - 5. Then start M0 proper: see docs/linux-setup.md. + 5. Then start M0 proper: see design/linux-setup.md. NEXT diff --git a/scripts/ci/provision-windows-wdk.ps1 b/scripts/ci/provision-windows-wdk.ps1 index f6ef5aa..7077d04 100644 --- a/scripts/ci/provision-windows-wdk.ps1 +++ b/scripts/ci/provision-windows-wdk.ps1 @@ -1,6 +1,6 @@ # Provision the Windows Driver Kit (WDK) + cargo-wdk on the self-hosted windows-amd64 runner, so the # all-Rust UMDF drivers (pf-vdisplay + the gamepad drivers, unified on microsoft/windows-drivers-rs) -# can build there. See docs/windows-host-rewrite.md (M0). +# can build there. See design/windows-host-rewrite.md (M0). # # The runner already has the base Windows SDK 10.0.26100 (um/ headers) + MSVC + LLVM + Rust, but NOT the # WDK — no km/ + wdf/ + um/iddcx headers, no inf2cat/stampinf/devgen. wdk-sys's bindgen needs those. diff --git a/scripts/headless/run-headless-sway.sh b/scripts/headless/run-headless-sway.sh index 12c0559..e9b658a 100755 --- a/scripts/headless/run-headless-sway.sh +++ b/scripts/headless/run-headless-sway.sh @@ -5,7 +5,7 @@ # ScreenCast portal (xdg-desktop-portal-wlr) and the punktfunk host share one bus. After this # is up, run `prepare-session.sh` from a second shell to set the mode + portal env. # -# Prereqs (see docs/linux-setup.md / scripts/bootstrap-ubuntu.sh): +# Prereqs (see design/linux-setup.md / scripts/bootstrap-ubuntu.sh): # - nvidia-drm.modeset=Y # - the NVIDIA GL/EGL userspace (libnvidia-gl-NNN) — provides libEGL_nvidia + the GLVND # vendor JSON; without it wlroots can't init EGL on the GPU and falls back to pixman, diff --git a/web/Dockerfile b/web/Dockerfile index 38a2dbf..a17b221 100644 --- a/web/Dockerfile +++ b/web/Dockerfile @@ -1,6 +1,6 @@ # punktfunk management console — TanStack Start built with Bun, served by the Nitro `bun` # preset bundle. Build context is the REPO ROOT (orval generates the API client from -# docs/api/openapi.json, referenced as ../docs/api/openapi.json from web/): +# api/openapi.json, referenced as ../api/openapi.json from web/): # # docker build -f web/Dockerfile -t punktfunk-web . # @@ -15,7 +15,7 @@ WORKDIR /repo/web COPY web/package.json web/bun.lock ./ RUN bun install --frozen-lockfile --ignore-scripts -COPY docs/api/openapi.json /repo/docs/api/openapi.json +COPY api/openapi.json /repo/api/openapi.json COPY web/ ./ # prebuild runs orval (openapi → src/api/gen); the paraglide vite plugin compiles i18n. RUN bun run build diff --git a/web/README.md b/web/README.md index 7d3234c..be2b423 100644 --- a/web/README.md +++ b/web/README.md @@ -1,7 +1,7 @@ # punktfunk web — management console The browser UI for the punktfunk host's **management REST API** (`crates/punktfunk-host/src/mgmt.rs`, -OpenAPI at `docs/api/openapi.json`). It shows live status, host capabilities, paired +OpenAPI at `api/openapi.json`). It shows live status, host capabilities, paired clients, the pairing-PIN flow, and session controls. Stack: **TanStack Start** (full SSR) on **Bun** via **Nitro v2** (`bun` preset) · **React @@ -88,7 +88,7 @@ Generated code is **not committed** (gitignored) — reproduced from sources: `bun install` (`prepare`) and before `dev`/`build` (`pre*` for orval; the Vite plugin compiles paraglide on dev/build). - After a management-API change, regenerate the spec on the Rust side first: - `cargo run -p punktfunk-host -- openapi > docs/api/openapi.json`, then `bun run api:gen`. + `cargo run -p punktfunk-host -- openapi > api/openapi.json`, then `bun run api:gen`. ## Layout diff --git a/web/orval.config.ts b/web/orval.config.ts index e076fb6..fe6e396 100644 --- a/web/orval.config.ts +++ b/web/orval.config.ts @@ -2,11 +2,11 @@ import { defineConfig } from "orval"; // Generates a typed React Query client from the host's checked-in OpenAPI document. // Regenerate after any management-API change: `pnpm api:gen` (the Rust side regenerates -// docs/api/openapi.json via `cargo run -p punktfunk-host -- openapi`). +// api/openapi.json via `cargo run -p punktfunk-host -- openapi`). export default defineConfig({ punktfunk: { input: { - target: "../docs/api/openapi.json", + target: "../api/openapi.json", }, output: { mode: "tags-split", diff --git a/web/src/styles.css b/web/src/styles.css index d5f05d5..fae655a 100644 --- a/web/src/styles.css +++ b/web/src/styles.css @@ -11,7 +11,7 @@ /* ── punktfunk brand · violet product chrome ──────────────────────────────── Two themes on one violet identity: LIGHT (lavender docs surface — the same - palette the docs/Scalar reference uses: white bg, faint-violet cards/borders, + palette the design/Scalar reference uses: white bg, faint-violet cards/borders, #6c5bf3 brand) is the :root default; DARK (the violet-tinted app-icon chrome #141019 / #1c1530, #a79ff8 brand) is the `.dark` override. The live console pins `` so it stays dark by default — removing or toggling