fix(host): compile punktfunk-host on macOS #456

Merged
enricobuehler merged 3 commits from fix/macos-host-compile-holes into main 2026-08-30 23:34:48 +00:00
Owner

Tier A of #299. No macOS backend, no shipped macOS host — this is the seam only.

The measurement

cargo check -p punktfunk-host on macOS 26 / rustc 1.96.0:

Date Errors Files
2026-08-01 9 5
2026-08-18 10 6
2026-08-30 (this branch's base) 11 6
after this branch 0

Roughly one new unguarded call site a fortnight, with nobody working on macOS. That growth rate is the argument for the gate, not the fix.

What was actually wrong

Not one error was missing functionality. Every one was a call site or a dependency assuming Linux-or-Windows without saying so.

  1. opus and libc were declared only for the platforms with a capture backend. The code reaching for them is portable: the mic pump decodes, and both hooks.rs helpers are already #[cfg(unix)]. opus now covers cfg(any(unix, windows))punktfunk-core already vendors it for the Apple client, so cargo unifies the build and it costs nothing. libc moves to cfg(unix), which is what the code claimed all along.

  2. pf-vdisplay re-exported try_recover_session and cancel_pending_tv_restore behind a Linux gate — although both already have #[cfg(not(target_os = "linux"))] arms. The functions were portable; only the pub use was not. Moving them into the ungated block fixed three call sites at once (gamestream/stream.rs, native/compositor.rs ×2).

  3. cursor_blend_for split on Windows rather than on Linux. So a third platform fell into the "not Windows" arm, which reads encode::linux_zero_copy_is_vaapi(), zerocopy::enabled() and encode::cursor_blend_capable() — all Linux-only. Flipped to linux / not(linux). Windows behaviour is bit-identical: it returned false before and returns false now. The sibling gamescope_cursor_for is left alone, and still keeps gamescope_needs_host_cursor live off Linux.

  4. gamelease reached past procscan's own platform-neutral wrappers to crate::procscan::Scanner::system(), which that module's header explicitly forbids ("Everything above the scanner is platform-neutral"). Added procscan::alive, mirroring the names / resolve wrappers beside it.

The one behavioural note

end_previous_launch used to hoist one Scanner and reuse it across the TERM_GRACE poll loop; through the wrapper it builds one per call, as names and resolve already do. That is getuid + sysconf + a small PathBuf on Linux and a unit struct on Windows, a few times over a bounded loop. Measured cheap enough not to warrant an exception to the module's own boundary.

The gate

.gitea/workflows/macos-host.yml, a separate workflow. Two reasons it is not a job in apple.yml, which already owns this runner:

  • That file's paths: filter is deliberately narrow — punktfunk-core and clients/apple — because the mini is shared and an xcframework build is heavy. A host-crate edit would never have fired it, so the gate would have been decorative.
  • Its distribute job is needs: swift. Anything that later grows the same dependency can block a TestFlight upload over a host compile hole the client has no part in.

cargo check, not clippy, and no -D warnings: the graph carries code that is dead on macOS (pf-encode::vbv_frames_env, the hidout sink in native/input.rs). Failing the gate on that would only teach people to ignore it. It asks one question.

I did try silencing the hidout warning and reverted it — gating the closure just moves the warning onto the function parameter, which would have meant a per-platform signature. Both warnings pre-date this branch and are unchanged by it.

Verification

Check Result
cargo check -p punktfunk-host --locked (macOS) exit 0
cargo check -p punktfunk-host --all-targets --locked (macOS) exit 0
scripts/xcheck.sh windows clippy exit 0
scripts/xcheck.sh linux clippy exit 0
cargo fmt --all clean
Cargo.lock unchanged (both crates were already in the graph)

xcheck.sh covers pf-vdisplay, not punktfunk-host. The Linux and Windows compile of the host crate itself is only checked by this PR's CI run — it has never been compiled on either platform. Please read those two legs before merging.

Not in scope

Tier B (a real macOS host on ScreenCaptureKit + VideoToolbox) and Tier C. #299 stays open.

Refs #299

Tier A of #299. No macOS backend, no shipped macOS host — this is the seam only. ## The measurement `cargo check -p punktfunk-host` on macOS 26 / rustc 1.96.0: | Date | Errors | Files | | --- | --- | --- | | 2026-08-01 | 9 | 5 | | 2026-08-18 | 10 | 6 | | 2026-08-30 (this branch's base) | 11 | 6 | | after this branch | **0** | — | Roughly one new unguarded call site a fortnight, with nobody working on macOS. That growth rate is the argument for the gate, not the fix. ## What was actually wrong Not one error was missing functionality. Every one was a call site or a dependency assuming Linux-or-Windows without saying so. 1. **`opus` and `libc` were declared only for the platforms with a capture backend.** The code reaching for them is portable: the mic pump decodes, and both `hooks.rs` helpers are already `#[cfg(unix)]`. `opus` now covers `cfg(any(unix, windows))` — `punktfunk-core` already vendors it for the Apple client, so cargo unifies the build and it costs nothing. `libc` moves to `cfg(unix)`, which is what the code claimed all along. 2. **`pf-vdisplay` re-exported `try_recover_session` and `cancel_pending_tv_restore` behind a Linux gate — although both already have `#[cfg(not(target_os = "linux"))]` arms.** The functions were portable; only the `pub use` was not. Moving them into the ungated block fixed three call sites at once (`gamestream/stream.rs`, `native/compositor.rs` ×2). 3. **`cursor_blend_for` split on Windows rather than on Linux.** So a third platform fell into the "not Windows" arm, which reads `encode::linux_zero_copy_is_vaapi()`, `zerocopy::enabled()` and `encode::cursor_blend_capable()` — all Linux-only. Flipped to `linux` / `not(linux)`. Windows behaviour is bit-identical: it returned `false` before and returns `false` now. The sibling `gamescope_cursor_for` is left alone, and still keeps `gamescope_needs_host_cursor` live off Linux. 4. **`gamelease` reached past `procscan`'s own platform-neutral wrappers** to `crate::procscan::Scanner::system()`, which that module's header explicitly forbids ("Everything above the scanner is platform-neutral"). Added `procscan::alive`, mirroring the `names` / `resolve` wrappers beside it. ### The one behavioural note `end_previous_launch` used to hoist one `Scanner` and reuse it across the `TERM_GRACE` poll loop; through the wrapper it builds one per call, as `names` and `resolve` already do. That is `getuid` + `sysconf` + a small `PathBuf` on Linux and a unit struct on Windows, a few times over a bounded loop. Measured cheap enough not to warrant an exception to the module's own boundary. ## The gate `.gitea/workflows/macos-host.yml`, a **separate workflow**. Two reasons it is not a job in `apple.yml`, which already owns this runner: - That file's `paths:` filter is deliberately narrow — `punktfunk-core` and `clients/apple` — because the mini is shared and an xcframework build is heavy. A host-crate edit would never have fired it, so the gate would have been decorative. - Its `distribute` job is `needs: swift`. Anything that later grows the same dependency can block a TestFlight upload over a host compile hole the client has no part in. `cargo check`, not clippy, and no `-D warnings`: the graph carries code that is dead on macOS (`pf-encode::vbv_frames_env`, the `hidout` sink in `native/input.rs`). Failing the gate on that would only teach people to ignore it. It asks one question. I did try silencing the `hidout` warning and reverted it — gating the closure just moves the warning onto the function parameter, which would have meant a per-platform signature. Both warnings pre-date this branch and are unchanged by it. ## Verification | Check | Result | | --- | --- | | `cargo check -p punktfunk-host --locked` (macOS) | exit 0 | | `cargo check -p punktfunk-host --all-targets --locked` (macOS) | exit 0 | | `scripts/xcheck.sh windows clippy` | exit 0 | | `scripts/xcheck.sh linux clippy` | exit 0 | | `cargo fmt --all` | clean | | `Cargo.lock` | unchanged (both crates were already in the graph) | ⚠ **`xcheck.sh` covers `pf-vdisplay`, not `punktfunk-host`.** The Linux and Windows compile of the host crate itself is only checked by this PR's CI run — it has never been compiled on either platform. Please read those two legs before merging. ## Not in scope Tier B (a real macOS host on ScreenCaptureKit + VideoToolbox) and Tier C. #299 stays open. Refs #299
enricobuehler added 2 commits 2026-08-30 21:34:08 +00:00
`cargo check -p punktfunk-host` failed on macOS with 11 errors across
six files. Not one was missing functionality: every one was a call site
or a dependency that assumed Linux-or-Windows without saying so, and the
count had grown from 9 to 11 in four weeks with nobody working on macOS.

`opus` and `libc` were declared only for the platforms with a capture
backend, though the code reaching for them is portable — the mic pump
just decodes, and both `hooks.rs` helpers are already `#[cfg(unix)]`.
`pf-vdisplay` re-exported `try_recover_session` and
`cancel_pending_tv_restore` behind a Linux gate even though both already
carry off-Linux arms. `cursor_blend_for` split on Windows rather than on
Linux, so a third platform fell into the arm holding the VAAPI/CUDA
terms that exist only there. `gamelease` reached past `procscan`'s own
platform-neutral wrappers to `Scanner::system()`, which the module's
documented boundary says it must not.

That last one now goes through a new `procscan::alive`, which builds a
scanner per call exactly as the `names` and `resolve` wrappers beside it
do — two syscalls on Linux, a unit struct on Windows.

Linux and Windows behaviour is unchanged.

Refs #299
ci: check that punktfunk-host still compiles on macOS
ci / docs-drift (pull_request) Successful in 35s
ci / bun-nix (pull_request) Successful in 27s
ci / web (pull_request) Successful in 52s
ci / docs-site (pull_request) Successful in 1m9s
macos-host / check (pull_request) Successful in 1m27s
ci / rust-arm64 (pull_request) Successful in 2m16s
ci / rust (pull_request) Failing after 6m47s
android / android (pull_request) Successful in 9m23s
c5b554ffb9
The third-platform seam has no gate, which is why it rotted: the
unguarded call sites went from 9 to 11 in the four weeks after they were
first counted, and each one was only found by someone running the check
by hand.

Its own workflow rather than a job in apple.yml, for two reasons. That
file's paths filter is deliberately narrow — punktfunk-core and the
Apple client — because the mac mini is shared and an xcframework build
is heavy, so a host-crate edit would never have fired it. And its
`distribute` job is `needs: swift`, where anything that later grows the
same dependency can block a TestFlight upload over a host compile hole
the client has no part in.

`cargo check`, not clippy, and no `-D warnings`: the graph carries code
that is dead on macOS, and failing on that would only teach people to
ignore the gate. It asks one question.

Refs #299
enricobuehler added 1 commit 2026-08-30 22:44:17 +00:00
Merge branch 'main' into fix/macos-host-compile-holes
macos-host / check (pull_request) Successful in 1m4s
android / android (pull_request) Successful in 8m27s
ci / bun-nix (pull_request) Successful in 1m35s
ci / docs-drift (pull_request) Successful in 26s
ci / rust-arm64 (pull_request) Successful in 4m10s
ci / docs-site (pull_request) Successful in 3m4s
ci / web (pull_request) Failing after 10m34s
ci / rust (pull_request) Canceled after 12m0s
b07e0c2d68
enricobuehler merged commit 9f0a757999 into main 2026-08-30 23:34:47 +00:00
enricobuehler deleted branch fix/macos-host-compile-holes 2026-08-30 23:34:55 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: unom/punktfunk#456