fix(linux): update PipeWire engine to the two-level Frame enum + SystemTime display_time#183
Open
Tristan-Stoltz-ERC wants to merge 1 commit into
Conversation
…Time display_time The Frame enum was refactored into a two-level shape (Frame::Audio / Frame::Video(VideoFrame::*)) and the per-format structs' display_time field was changed from u64 to SystemTime, but the Linux PipeWire engine in src/capturer/engine/linux/mod.rs was not updated to match. Result: 8 compile errors on Linux, no crates.io release compiles there. This commit: - Wraps the four emit sites (RGBx / RGB / xBGR / BGRx) in Frame::Video(VideoFrame::...(...)) to match frame::Frame's current two-level shape. - Replaces `display_time: timestamp as u64` with `display_time: SystemTime::now()`, matching what the macOS and Windows engines already do. The raw PipeWire pts from spa_meta_header is monotonic ns since an arbitrary reference, not wall-clock, so it cannot be converted losslessly to SystemTime. Relative frame ordering survives via channel-send order. - Adds SystemTime to the std::time imports and VideoFrame to the crate::frame imports. `cargo check` on Linux (Ubuntu 24, pipewire 1.0, via nix develop) now succeeds — 16 warnings remaining, all pre-existing lifetime- elision nits unrelated to this fix.
Tristan-Stoltz-ERC
added a commit
to Luminous-Dynamics/xenia-peer
that referenced
this pull request
Jun 26, 2026
* unify(u4): xenia-inject crate — input-injection abstraction
Ports Symthaea's rdp_input.rs as a standalone library crate under
the unified xenia stack. Apache-2.0 OR MIT per ADR-002.
- InputEvent enum (Pointer / Key / Touch, serde + bincode-compatible)
- InputInjector trait with default process_events dispatch
- NoopInjector + LoggingInjector (for tests and dry-run)
- Feature-gated scaffolds: wayland-virtual (wlroots) + uinput
X11 backend intentionally dropped per ADR-001 "Wayland-only" stance.
5 unit tests covering coordinate denormalization, clamping, event
dispatch, and bincode round-trip.
Real backend plumbing (wayland-client + uinput kernel calls) lands
with the matching xenia-capture backends — scaffold today so the
daemon can accept InputEvent envelopes from xenia-wire and route
them to a real injector when the compositor-specific code arrives.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* unify(u3): xenia-handshake crate — PQC hybrid handshake (Ed25519 + ML-KEM-768)
Fresh implementation against RustCrypto's ml-kem (FIPS 203),
ed25519-dalek 2.x, and hkdf 0.12. Apache-2.0 OR MIT per ADR-002.
Not a carry-over of symthaea/src/swarm/pqc_handshake.rs — that
version is tightly coupled to mycelix_crypto's abstractions
(TaggedPublicKey, AlgorithmId, KeyEncapsulator trait) which would
pull the mycelix-identity monorepo into every xenia-peer deployment.
The API shape is aligned (kem_public_key_bytes, receive_kem_public_key,
encapsulate_for_peer, decapsulate_and_derive, session_key,
remove_session) so migrating Symthaea's RDP pipeline over is a
search-and-replace, but there is zero cross-repo coupling.
Protocol:
1. Each node generates a long-lived Ed25519 identity + per-session
ML-KEM-768 keypair.
2. Initiator receives responder's encapsulation key (1184 bytes),
calls encapsulate_for_peer() -> 1088-byte ciphertext.
3. Responder calls decapsulate_and_derive() on the ciphertext.
4. Both sides derive the same 32-byte session key via
HKDF-SHA-256(ikm = classical_nonce || kem_shared_secret,
salt = b"xenia-handshake-v1",
info = b"xenia-session-key").
Key design decisions:
- FIPS 203 implicit rejection is honored as-is — wrong ciphertexts
yield a pseudorandom shared secret rather than erroring. Session
authenticity is established at the Ed25519/HKDF layer when both
sides compare derived keys, not at the KEM layer.
- SessionKey zeroizes on drop (zeroize::ZeroizeOnDrop).
- KEM keypair regenerated per HandshakeManager instance — long-lived
identity is Ed25519 only, consistent with hybrid-forward-secrecy
guidance in Bindel et al. 2019.
- HKDF-SHA-256 (not BLAKE3 derive_key as Symthaea's version uses) —
sticks to NIST-approved primitives end-to-end for auditability.
15 unit tests: key sizes, round-trip matching-key derivation, per-peer
and per-nonce key separation, length validation, FIPS 203 implicit
rejection, Ed25519 sign/verify round-trip, session lifecycle, HKDF
determinism, bincode wire-format round-trip.
Closes ROADMAP's B1 blocker ("PQC handshake replaces FIXTURE_KEY") at
the crate layer. Wiring into xenia-peer binaries is a separate step.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* unify(u5): ROADMAP refresh — U1-U4 landed, B1 crate-complete
Records the unification arc (U1-U4) and flips status for B1 from
"not started" to "crate shipped, wiring pending". Resolves
open-question #3 (license drift) with a pointer to ADR-002. Adds
explicit Symthaea-side migration note as the next-session handoff.
No code changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(xenia-capture): scaffold scap backend + document upstream Linux breakage
Wires the `scap` crate (primary cross-platform capture choice per
mycelix-sovereign ADR 0001) into xenia-capture as a new `scap-backend`
feature. Exposes `ScapCapture`, `ScapOptions`, `ScapResolution` via the
pre-existing `ScreenCapture` trait — no new trait added; the prior-art
abstraction already provides the moat.
Design:
- Worker thread owns scap's `Capturer` (which is `!Send` on Windows per
upstream issue #145; constructing it inside the closure sidesteps
the Send bound)
- Main thread polls via `mpsc::Receiver::try_recv`, mapping
`TryRecvError::Empty` -> `Ok(None)` per the trait's non-blocking
contract; `Disconnected` -> `CaptureError::Backend`
- BGRA -> RGBA conversion in-place via `chunks_exact_mut(4).swap(0, 2)`
- macOS TCC permission -> `CapturerBuildError::PermissionNotGranted`
-> `CaptureError::ConsentDenied`
- Defensive: drops 0-byte frames (upstream #159)
Upstream blocker found during cargo check:
scap 0.1.0-beta.1 (and tip of main c03f15a4) does NOT compile on Linux.
Eight errors in scap/src/capturer/engine/linux/mod.rs: Frame::XBGR /
Frame::BGRx variants not found; timestamp as u64 vs expected SystemTime.
The Frame enum was simplified without updating the Linux engine.
Feature marked scaffolded-but-not-usable-on-Linux pending scap
0.1.0-beta.2. ADR 0001 documents three forward paths (watch-and-wait /
escalate to xcap / upstream PR).
Baseline cargo check -p xenia-capture (feature off) still passes.
The ScapCapture code will compile against a working upstream without
changes - the trait-moat architecture validated its first real test.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(xenia-capture): repoint scap at our fork; handle all 6 VideoFrame variants
scap 0.1.0-beta.1 and main are both broken on Linux (the Frame enum was
refactored to the two-level Frame::Video(VideoFrame::*) shape but the
Linux PipeWire engine was not updated; display_time changed from u64 to
SystemTime at the same time and the Linux engine still casts to u64).
Authored the upstream fix and submitted it as PR:
CapSoftware/scap#183
This commit:
1. Repoints the scap git dependency at our Luminous-Dynamics fork's
fix/linux-engine-two-level-frame-enum branch. When the upstream PR
merges and a release cuts, we flip back to the crates.io version.
2. Updates scap_backend.rs frame_to_rgba() to the two-level
Frame::Video(VideoFrame::*) enum shape. Along the way, adds handlers
for every VideoFrame variant scap can emit on Linux — not just BGRA.
On Linux scap's PipeWire negotiation emits whichever of RGBx / RGB /
XBGR / BGRx / BGR0 the compositor offers (upstream #151: FrameType
request is ignored on Linux). If we only matched BGRA we'd silently
drop every Linux frame.
All conversions normalize to our trait's RGBA top-left contract.
NV12 YUVFrame is discarded (420 subsampling + color-matrix choice
are out of scope for this wrapper).
3. Adds a length_matches() defensive check for upstream #159 (scap
occasionally emits 0-byte frames after which latency spikes 4-5x).
cargo check --features scap-backend inside xenia-peer's nix develop:
green on Linux. 7/7 tests pass (4 pre-existing + 3 scap_backend).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(xenia-ledger): scaffold AGPL verifiable-consent ledger crate
New crate in the xenia-peer workspace, licensed AGPL-3.0-or-later —
the cryptographic moat of the Mycelix Sovereign commercial suite.
Intentional exception to ADR-001 Decision 3 (library crates = permissive
Apache/MIT). Rationale in crates/xenia-ledger/README.md: this library is
functionally the commercial product, not shared-commons infrastructure.
A community-built compatible Xenia client can use xenia-wire / xenia-
peer-core / xenia-handshake / xenia-capture / xenia-inject freely — they
just need to bring their own ledger. Exception to be formally recorded
in a follow-up ADR.
Design:
- Append-only hash chain: blake3 over (seq, prev_hash, timestamp, event)
for each entry; seq monotonic from 0; genesis prev_hash = [0; 32].
- Ed25519 signature over each entry_hash using the operator's key.
- Stateless Verifier checks sequence continuity, hash linkage,
entry_hash recomputation, and signatures — can be run offline by any
third-party auditor holding only the operator's public key.
- No persistence in this crate: callers decide their own storage
(JSON, CBOR, SQLite, Holochain entries). `Chain::from_entries`
rehydrates any persisted sequence.
- bincode v1 for canonical serialization, locked via workspace.
Dependencies:
- blake3 1.5, ed25519-dalek 2 (default-features off + std/rand_core/serde)
- serde-big-array 0.5 for [u8; 64] signature ser/de
- uuid 1 for session/request IDs (v4 + serde features)
Tests: 9 passing, covering every attack vector (tampering with event
data, tampering with entry_hash, reordering, wrong public key, forged
genesis with nonzero prev_hash) plus construction/rehydration happy
paths.
Out of scope here (tracked separately):
- External xenia-ledger-verify binary (AGPL)
- PQC signature option (Dilithium / ML-DSA)
- Chain-to-chain merkle anchoring for inter-operator attestation
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(xenia-capture): capture_bench example — W0 scap validation harness
Timed capture-session harness producing the numbers the ADR 0001
"Pending validation" subsection needs: effective fps, first-frame
latency, bytes/frame + MB/s, try_recv Empty-poll count, error count,
VERDICT: PASS/FAIL against the 15-fps-at-native-resolution floor.
Env overrides: FRAMES=N, DURATION_SECS=N, FPS=N, DUMP_FRAME=path
(dumps first RGBA frame to disk for alpha-byte correctness checks).
Registered in Cargo.toml via [[example]] with required-features =
["scap-backend"] so default cargo build/test runs don't pull the scap
dep. Compiles cleanly against our Luminous-Dynamics/scap fork inside
xenia-peer's nix develop shell.
Per-OS invocation is documented in the companion runbook:
mycelix-sovereign/docs/capture-validation-runbook.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(xenia-admin): scaffold Leptos CSR admin console (AGPL)
W1 Stream A starter move — the operator surface of the Mycelix
Sovereign suite. Leptos 0.8 CSR (matching the Mycelix ecosystem
convention). AGPL-3.0-or-later per the application-layer license
policy. Serves at port 8134 (dev: trunk serve; prod: Cloudflare
Tunnel -> admin.sovereign.mycelix.net).
Layout:
- src/main.rs mount entry
- src/app.rs Router + top nav + AuthStatus
- src/auth.rs AuthState (signal + localStorage
rehydration)
- src/pages/{login,devices, four routes: /, /login, /devices,
sessions,policy} /sessions, /policy
- index.html Trunk entry
- Trunk.toml port 8134
- styles/main.css minimal dark-theme scaffold
Scaffold scope:
- LoginPage validates DID shape ('did:' prefix + minimum length), no
cryptographic verification yet. Real resolve_did / MFA challenge
integration is W1 follow-up.
- DevicesPage and SessionsPage render mock rows so the console has
shape before xenia-peer-core session registry + xenia-ledger
integration lands. Both include TODO headers pointing at the
relevant plan sections.
- PolicyPage is a stub that lists the planned controls (tier
thresholds, session TTL, MFA enforcement, per-device capability
grants, emergency revocation).
Workspace wiring:
- xenia-admin registered in Cargo.toml [workspace.members]
- default-members added to exclude xenia-admin from host `cargo
check` / `cargo build` default runs (it's wasm32-unknown-unknown
only). Other crates' host builds unchanged.
Verification: cargo check -p xenia-admin --target wasm32-unknown-
unknown green in 1m 29s cold (Leptos 0.8 full tree).
Not wired yet (W1 tail-end):
- DID resolution against mycelix-identity via mycelix-bridge-common
- xenia-ledger Verifier invocation from the Sessions page
- Cross-cluster auth context threading
- NIS2 Art. 21 mapping as an in-app compliance helper
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(xenia-admin): live xenia-ledger demo on Sessions page + README
The NIS2 Art. 21(f) "admin cannot rewrite the audit log" property
rendered as 15 seconds of clickable UX. On mount the Sessions page:
- Generates a fresh Ed25519 key pair via OsRng (browser Web Crypto
through getrandom/js feature)
- Builds a 5-entry synthetic consent chain (Request -> Approval ->
Request broader scope -> Approval -> Revocation) using xenia-ledger's
Chain::append
- Runs xenia_ledger::Verifier::verify_chain against the full entries
slice + the freshly-generated public key
- Renders a green "Chain verified" badge, the operator's full public
key in hex, and a per-entry table showing seq / kind / scope /
truncated entry_hash
Every row has a "Tamper" button that flips that entry's ConsentKind
(e.g. Approval -> Denial) and re-runs the Verifier. Because the
entry_hash and signature cover the original event, verification flips
to the matching VerifyError variant (EntryHashMismatch / BadSignature)
and the badge goes red. Clicking the same button again restores the
kind and the badge goes green. Zero network traffic — every byte is
computed by ed25519-dalek + blake3 in the browser.
Changes:
- crates/xenia-admin/Cargo.toml
- xenia-ledger = { path = "../xenia-ledger" } (AGPL, fine — admin
console is AGPL too)
- ed25519-dalek 2, rand_core 0.6 (with getrandom feature), uuid
with js feature, getrandom 0.2 with js feature (forces WebCrypto
backend in the browser)
- crates/xenia-admin/src/pages/sessions.rs
- Full rewrite from mocked rows to live LedgerDemo component
- crates/xenia-admin/styles/main.css
- .badge, .verify-row, .ledger-table, .kind-*, .pub-key, .footnote
- crates/xenia-admin/README.md (new)
- How to trunk serve; 3-min E2E walkthrough; what's worth showing
a CISO vs what's still scaffold; W1 follow-up checklist
- crates/xenia-admin/.gitignore (new)
- Exclude trunk's /dist/ build output
- crates/xenia-ledger/src/lib.rs
- #[derive(Clone)] on VerifyError so it can live in a Leptos
RwSignal alongside the entries vector. No behavioral change.
Verification:
- cargo check -p xenia-admin --target wasm32-unknown-unknown: green
- trunk build --release: green, 592 KB wasm-opt'd (up from 476 KB
scaffold; +116 KB is the actual cryptography — ed25519-dalek +
blake3 + bincode + xenia-ledger + uuid)
Not wired yet (deliberate; see README W1 follow-up checklist):
- Real mycelix-identity::resolve_did on login (currently string-shape
validation only)
- Session-registry integration (currently synthetic chain only)
- WebAuthn/TOTP MFA step
- Import-persisted-chain flow (paste a JSON, verify it, render badges)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(xenia-admin): export + import round-trip for ledger chains
Closes the W1 follow-up "replace SessionsPage synthetic chain with an
import+verify flow." The live in-browser chain can now be serialized
to portable JSON and re-verified from paste — same code path a third-
party auditor would use offline.
Export:
- New <ExportSection> under the live ledger table.
- Memo derives pretty-printed JSON from the current entries + operator
public-key hex on every tamper.
- Toggle button reveals a readonly textarea with the self-contained
attestation (public key + entries in bincode-identical serde-json
form).
Import:
- New <ChainImporter> section at the bottom of Sessions.
- Textarea + Verify button.
- parse_and_verify() distinguishes three failure modes:
1. JSON parse error (e.g. user pastes garbage)
2. public_key_hex not 64 hex chars / invalid key bytes
3. VerifyError from xenia_ledger::Verifier
- Pass path renders "✓ Verified — N entries; chain integrity confirmed
against embedded public key PUB_KEY_PREFIX…".
- Hex decoder is inline (10 lines) — no `hex` crate dep.
Round-trip demo on /sessions:
1. Copy JSON from Show JSON export
2. Modify a byte in an editor ("Approval" -> "Denial" in any entry)
3. Paste into Verify a chain from JSON
4. See the specific VerifyError variant surfaced
Zero network traffic throughout.
ExportedChain shape (stable):
{ "public_key_hex": "<64 hex chars>", "entries": [LedgerEntry, ...] }
Deps added:
- serde_json 1 (pretty + parse)
Impl notes:
- ImportedSummary derives Clone (RwSignal<Option<Result<_, _>>> needs it).
- ChainImporter uses prop:value/on:input pair for textarea so Clear
resets the bound signal correctly.
Verification:
- cargo check -p xenia-admin --target wasm32-unknown-unknown: green
- trunk build --release: green, 752 KB wasm-opt'd (up from 592 KB;
+160 KB is serde_json + the new components)
README updated:
- New step 6 in the E2E walkthrough covers the round-trip demo.
- W1 follow-up checklist marks the import+verify box as shipped.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(xenia-admin): wire LoginPage to real resolve_did via mycelix-leptos-client
Closes the W1 Stream A "DID resolution is string-shape-only scaffold"
placeholder. LoginPage now submits the DID to the Mycelix identity
hApp via a real Holochain conductor WebSocket call.
Flow on submit:
1. Shape-check the DID (starts with "did:", length >= 8)
2. spawn_local async:
- BrowserWsTransport connects to ws://localhost:8888 (default;
XENIA_ADMIN_CONDUCTOR_URL overridable at build time)
- HolochainClient::call_zome("did_registry", "resolve_did", &did)
- Deserializes the Option<Record> response into
Option<serde_json::Value> (opaque — we only need existence,
avoids pulling holochain_types into wasm)
3. Branches on three outcomes:
- Some(_) -> auth.sign_in(did) + navigate to /devices
- None -> "DID not found in the identity registry"
- Err(_) -> "Zome call failed: {e}" (connect, protocol, or
deserialization failure surfaces distinctly)
New LoginStatus enum drives the UI (Idle / InvalidShape / Resolving /
NotFound / Error) — button disables during Resolving, status banner
surfaces the specific failure mode.
Build-time overrides (option_env!, const match):
- XENIA_ADMIN_CONDUCTOR_URL (default ws://localhost:8888)
- XENIA_ADMIN_APP_ID (default mycelix-unified)
- XENIA_ADMIN_IDENTITY_ROLE (default identity)
Same values are surfaced in the footer as readable codespans so
operators can diagnose conductor-mismatch failures without opening
DevTools.
Dep added: mycelix-leptos-client = { version = "0.1",
default-features = false, features = ["browser"] } — our own
freshly-published crates.io crate, pure-Rust WASM Holochain client
using web-sys::WebSocket + rmp-serde.
Verification:
- cargo check -p xenia-admin --target wasm32-unknown-unknown: green
- trunk build --release: green, 1016 KB wasm-opt'd (up from 752 KB;
+264 KB is the real Holochain client + rmp-serde + additional
WebSocket/Blob web-sys features)
Runtime verification pending: requires a live conductor at :8888 with
the identity hApp installed (per CLAUDE.md "Installed apps"). Next
session's human check: `cargo run` via trunk serve, paste a
registered DID, confirm the call reaches the conductor and the
response flows back.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(xenia-admin): runtime window.__HC_* overrides (match Praxis convention)
Replaces build-time option_env! gates with runtime window-global reads
— matches the pattern used by mycelix-leptos-core::holochain_provider
in the production Praxis frontend. Lets us ship one WASM bundle and
deploy it against different conductors without a rebuild.
Override surface (all optional, set in index.html <script> or at
runtime before wasm init):
window.__HC_CONDUCTOR_URL (default ws://localhost:8888)
window.__HC_APP_ID (default mycelix-unified)
window.__HC_IDENTITY_ROLE (default identity)
window.__HC_AUTH_TOKEN (default none — fails on authenticated conductors)
Compile-time XENIA_ADMIN_* env vars retained as fallback defaults in
case a deployment-specific bundle makes sense, but the window globals
always win.
The footnote under the sign-in form now surfaces which values are in
effect (including "auth: none (will fail against authenticated
conductor)" when the token is missing) so operators can diagnose a
bad deployment without opening DevTools.
index.html gains a commented-out <script> block documenting the four
overrides, matching the shape other Mycelix ecosystem apps use.
Runtime verification this session (native E2E probe, /tmp/e2e-probe):
- Admin WebSocket connect at :33800 OK
- issue_app_auth_token for mycelix-unified OK (64-byte token)
- App WebSocket connect with token OK
- authorize_signing_credentials for identity cell OK
- resolve_did("did:mycelix:<real agent pubkey>") -> Ok(None) [1 byte,
MessagePack None = 0xc0]
- resolve_did("did:mycelix:bogus") -> zome-side format rejection
The zome call path is architecturally verified end-to-end against
the live shared conductor. The browser flow via mycelix-leptos-client
uses unsigned zome calls (zeroed signature, raw agent_pub_key as
provenance) — works against conductors with Unrestricted capability
grants, which this conductor has.
cargo check -p xenia-admin --target wasm32-unknown-unknown: green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Normalize Xenia peer workspace layout and validation gates
* Record Xenia normalization follow-up gates
* Resolve initial Xenia RC1 follow-up gates (#2)
* Resolve initial Xenia RC1 follow-up gates
* Replace runtime unwraps with explicit fallbacks (#3)
* Replace runtime unwraps with explicit fallbacks
* Fix xenia-peer clippy and handshake rustdoc warnings
* Fix viewer clippy and capture rustdoc warnings
* Replace semantic runtime expects with explicit handling (#4)
* Replace fallible runtime expects with explicit errors
* Handle missing admin app context without panics
* Document HKDF expansion invariant without expect
* Remove H264 decoder unsafe scaler cache (#5)
* Record reviewed secure-default warning exceptions (#6)
* Mark normalization layout state in release manifests
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The
Frameenum was refactored into a two-level shape (Frame::Audio(AudioFrame)/Frame::Video(VideoFrame::*)) and the per-format structs'display_timefield was changed fromu64toSystemTime, but the Linux PipeWire engine insrc/capturer/engine/linux/mod.rswas not updated to match.Result: 8 compile errors on Linux;
0.1.0-beta.1on crates.io does not build there, and neither does the currentmain(verified on Ubuntu 24 with pipewire 1.0).fix-windowsaddressed the Windows path but left Linux untouched.Changes
RGBx/RGB/xBGR/BGRx) inFrame::Video(VideoFrame::...(...))to matchframe::Frame's current two-level shape.display_time: timestamp as u64withdisplay_time: SystemTime::now(), matching what the macOS (src/capturer/engine/mac/mod.rs) and Windows (src/capturer/engine/win/mod.rs) engines already do. The raw PipeWireptsfromspa_meta_headeris monotonic ns since an arbitrary reference, not wall-clock — it cannot be converted losslessly toSystemTime. Relative frame ordering survives via channel-send order.SystemTimeto thestd::timeimports andVideoFrameto thecrate::frameimports.Verification
16 warnings remain, all pre-existing lifetime-elision nits unrelated to this PR.
Follow-ups not in this PR
ptsvalue is currently discarded (let _ = timestamp;) since we can't faithfully convert monotonic-ns to wall-clockSystemTime. A follow-up could add amonotonic_time: Option<Duration>sibling field if callers want sub-ms buffer timing on Linux. Left as a separate concern.portal.rsand a few other files are out of scope here but would be an easy clippy pass.Context
Found while integrating scap into xenia-capture, the screen-capture abstraction of the Xenia remote-session stack — we picked scap as our primary cross-platform backend specifically because of your PipeWire-native Wayland path. Thanks for the work on this crate.