CI / Build + Clippy + Test (pull_request) Failing after 1m13s
CI / Security audit (RUSTSEC) (pull_request) Successful in 1m17s
apt-get ignores HTTP_PROXY/HTTPS_PROXY env vars and tries to connect
directly to archive.ubuntu.com. Clash DNS returns fake-ip (198.18.x.x)
which only works through TUN (disabled). Fix: write apt proxy config to
/etc/apt/apt.conf.d/99proxy before apt-get update.
CI / Build + Clippy + Test (pull_request) Failing after 1m13s
CI / Security audit (RUSTSEC) (pull_request) Successful in 1m18s
act_runner does not support container.env in config.yaml — job containers
get NO proxy env vars. Set HTTP_PROXY/HTTPS_PROXY/NO_PROXY directly in
the workflow's job-level env block.
NO_PROXY includes gitea.com (actions checkout goes direct, faster) and
gitea.dailz.cn (self-hosted Gitea, internal).
CI / Build + Clippy + Test (pull_request) Failing after 2m17s
CI / Security audit (RUSTSEC) (pull_request) Failing after 2m15s
Network testing revealed:
- static.rust-lang.org: direct 200/0.24s (fast, no proxy needed)
- sh.rustup.rs: direct 200/0.11s (fast)
- gitea.com: direct 303/0.68s (fast)
- mirrors.ustc.edu.cn: UNREACHABLE (5s timeout)
- rsproxy.cn: UNREACHABLE (5s timeout)
The machine has good direct internet access to official Rust/crates servers.
Domestic mirrors are the ones that are blocked. Remove all mirror config and
use defaults.
CI / Security audit (RUSTSEC) (pull_request) Failing after 2m15s
CI / Build + Clippy + Test (pull_request) Failing after 31m46s
rsproxy.cn/rustup returns 404 for /dist/x86_64-unknown-linux-gnu/
rustup-init — different path structure from static.rust-lang.org.
Switch to mirrors.ustc.edu.cn/rust-static which is a full mirror with
identical path structure.
CI / Build + Clippy + Test (pull_request) Failing after 5s
CI / Security audit (RUSTSEC) (pull_request) Failing after 14m4s
rustup components (~60MB) from static.rust-lang.org are slow even
through proxy. Switch to rsproxy.cn/rustup mirror (domestic, direct
connection via NO_PROXY).
CI / Security audit (RUSTSEC) (pull_request) Failing after 3s
CI / Build + Clippy + Test (pull_request) Failing after 44m45s
The catthehacker/ubuntu:act-latest image does not have rustup in PATH
('rustup: command not found', exit 127). Replace the direct rustup
invocation with the official install script from sh.rustup.rs, which
installs rustup + the stable toolchain in one step.
Also adds ~/.cargo/bin to GITHUB_PATH so subsequent steps (cargo clippy,
cargo build, cargo test, cargo audit) can find cargo/rustc.
CI / Build + Clippy + Test (pull_request) Failing after 7s
CI / Security audit (RUSTSEC) (pull_request) Failing after 3s
The actions/cache@v4 clone from gitea.com has been hanging for 12+
minutes in run 17. The actions/checkout clone took 2.5 min (slow but
completed), but actions/cache is stuck indefinitely.
Removing the cache step entirely. Trade-off: CI recompiles from scratch
each run (slower), but actually progresses past the action-clone phase.
Can re-add once gitea.com access is faster or actions are pre-cached
on the runner.
CI / Build + Clippy + Test (pull_request) Failing after 14m11s
CI / Security audit (RUSTSEC) (pull_request) Failing after 4s
The act_runner network also blocks crates.io index access (both sparse
and git protocols). Previous workaround used git protocol to github.com,
which is also blocked. Replace with rsproxy.cn sparse mirror, accessible
from China networks.
Added to both build-test and audit jobs. Config is written to
~/.cargo/config.toml at runtime (CI-only; does not affect local dev).
CI / Security audit (RUSTSEC) (pull_request) Has been cancelled
CI / Build + Clippy + Test (pull_request) Has been cancelled
The self-hosted act_runner cannot reach github.com (network timeout
on actions/checkout clone). Replace:
- actions/checkout@v4 -> https://gitea.com/actions/checkout@v4 (3 sites)
- actions/cache@v4 -> https://gitea.com/actions/cache@v4
- dtolnay/rust-toolchain@stable -> rustup toolchain install (inline run)
gitea.com maintains official mirrors of the actions/* org. dtolnay's
rust-toolchain is third-party (no gitea.com mirror), so replaced with
a direct rustup invocation — the act_runner ubuntu image has rustup
pre-installed.
This unblocks CI which has been red since the original PR #26 was
opened 6 weeks ago. No code changes.
CI / Security audit (RUSTSEC) (push) Has been cancelled
CI / Build + Clippy + Test (push) Has been cancelled
Two stacked refactors merged as one PR:
Part 1 (June 2026, original scope): avhw module split + cargo-audit fixes
Part 2 (July 2026): file-level decomposition of state / cap_portal /
state_portal / webrtc + bench binary cleanup
Verification: 82 tests pass, clippy clean, fmt clean, all 3 binaries
smoke-tested. See PR #26 description for full details.
Pre-refactor baseline tag: 75ad4bb (covers Part 2 only).
CI / Security audit (RUSTSEC) (pull_request) Has been cancelled
CI / Build + Clippy + Test (pull_request) Has been cancelled
Update the 'Runtime architecture' section to reflect that state.rs /
cap_portal.rs / state_portal.rs / webrtc.rs are now parent modules of
directory trees:
- src/state.rs -> src/state/mod.rs (+ src/state/dispatch/ for the 13
Wayland Dispatch impls)
- src/state_portal.rs still exists; helpers split into
src/state_portal/{bitrate,threads}.rs
- src/cap_portal.rs holds the struct; setup/token_fs/pipewire_thread
split into src/cap_portal/
- src/webrtc.rs gains src/webrtc/html_page.rs sibling
No content changes beyond the path references; the rest of AGENTS.md
remains accurate.
Step 2b.1: structural split (no function decomposition — that's 2b.2).
src/cap_portal.rs (1313 -> 176 LOC) now contains only the CapPortal struct,
its constructor (new), accessors (frame_receiver/event_receiver/dropped_count/
capture_queue_depth), and Drop impl. Six new sibling submodules under
src/cap_portal/:
- types.rs (79 LOC) timeout constants, PortalPhaseTimeout enum,
pub types PwDmaBufFrame / PortalFormatInfo /
PwCtrlEvent
- logging.rs (18 LOC) log_portal_phase_timeout helper
- fourcc.rs (73 LOC) spa_to_drm_fourcc + its 2 tests
- token_fs.rs (362 LOC) 8 restore-token fs helpers + 11 security tests
- setup.rs (192 LOC) impl CapPortal { setup_portal + _setup_portal_inner }
(associated fns; no self access — clean extract)
- pipewire_thread.rs (446 LOC) PwThreadCtx (now private to this file),
pipewire_thread body (verbatim, 18 SAFETY
comments preserved), new spawn_pipewire_thread
helper that constructs PwThreadCtx internally
and returns JoinHandle. CapPortal::new now calls
pipewire_thread::spawn_pipewire_thread(...) instead
of inlining the PwThreadCtx construction.
Oracle audit points honored:
- PwThreadCtx moved as a whole; Drop in mod.rs and pipewire_thread in
pipewire_thread.rs share zero state through it (PwThreadCtx consumed
by-value inside pipewire_thread; spawn helper owns the construction).
- All // SAFETY comments travel verbatim with their unsafe blocks.
- The 18 SAFETY comments in pipewire_thread are intact; clippy
undocumented_unsafe_blocks=deny still passes.
API stability:
- pub use types::{PwCtrlEvent, PwDmaBufFrame} preserves the existing
wl_webrtc::cap_portal::{PwCtrlEvent, PwDmaBufFrame} paths used by
both bench binaries (verified by cargo check --bin vaapi_import_bench
--bin sw_encode_bench).
- PortalFormatInfo was nominally pub in the original file but never
referenced outside cap_portal; kept pub in types.rs (for cross-
submodule access) but not re-exported from cap_portal.rs, so the
accidental over-exposure is now scoped back.
Verification (all green):
- cargo build / cargo build --release
- cargo test (79 lib + 3 integration = 82 pass, 1 ignored — unchanged)
- cap_portal test count: 13 (fourcc=2 + token_fs=11) — matches baseline
- cargo clippy --all-targets -- -D warnings
- cargo fmt --check
- cargo check --bin vaapi_import_bench --bin sw_encode_bench
Step 2a: eliminate cross-bench duplication identified by the Explore audit.
Changes:
- src/avhw/util.rs: av_err_to_string promoted pub(crate) -> pub (the only
change to src/avhw/ in this whole refactor plan).
- src/avhw/mod.rs: re-export av_err_to_string; #[allow(unused_imports)]
silences rustc's per-bin unused-import false positive (the pub use is
consumed by the bench bins, not by the main bin).
- src/bin/common/mod.rs (new): shared receive_first_frame + drain_encoder.
These were byte-identical between the two bench binaries modulo a type-path
alias (ff::codec::encoder::video::Video vs ff::encoder::video::Video) and
SAFETY-comment line wrapping. Both binaries now wire it via
#[path = "common/mod.rs"] mod common;.
- src/bin/vaapi_import_bench.rs: 1039 -> 947 LOC (av_err_to_string,
receive_first_frame, drain_encoder all removed; 3 call sites updated).
- src/bin/sw_encode_bench.rs: 614 -> 545 LOC (receive_first_frame,
drain_encoder removed; 3 call sites updated).
- use ffmpeg_next::packet::Mut moved to common/mod.rs (was needed only for
pkt.as_mut_ptr() inside drain_encoder).
Verification (all green):
- cargo build --bins / cargo build --release
- cargo test (79 lib + 3 integration = 82 pass, 1 ignored — unchanged)
- cargo clippy --all-targets -- -D warnings
- cargo fmt --check
- Test counts unchanged from baseline
Step 1 of file-level refactor: prove the file->directory pattern with the
cleanest possible extraction.
- src/webrtc.rs: 913 -> 741 LOC
- New src/webrtc/html_page.rs: 170-line HTML test page as pub(super) const
- Parent module re-exports via `mod html_page; use html_page::HTML_PAGE;`
so all references in handle_signaling stay unchanged.
Verification (all green):
- cargo build / cargo build --release
- cargo test (79 lib + 3 integration = 82 pass, 1 ignored — unchanged)
- cargo clippy --all-targets -- -D warnings
- cargo fmt --check
- cargo check --bin vaapi_import_bench --bin sw_encode_bench
- Test count in webrtc.rs: 18 (unchanged from baseline)
Oracle audit note: HTML_PAGE had a single use site (handle_signaling L257-258)
and zero #[cfg(test)] references, so the extraction is provably behavior-
preserving.
CI / Build + Clippy + Test (pull_request) Failing after 30s
CI / Security audit (RUSTSEC) (pull_request) Failing after 30s
Oracle audit of all 5 `unsafe impl Send` in src/avhw/ found soundness
intact but reasoning wrong in 3 of 5:
- AvHwDevCtx: claimed '&mut self ensures exclusive access' — false,
ref_clone() hands raw pointers to other threads / FFmpeg-internal
codec workers. Real basis is AVBufferRef atomic_uint refcount +
libva VADisplay thread safety.
- AvHwFrameCtx: claimed 'send/receive pattern is thread-safe' —
misdirection. Real basis is AVBufferPool atomic get/put.
- EncState: claimed 'raw pointers not shared across threads' — false
when FFmpeg frame/slice threading is enabled. Real basis is the
hw device/frames contexts being designed for such sharing.
SwEncState and SwEncEncode comments were acceptable; improved for
clarity (note that contained FFmpeg handles are non-thread-safe but
Send-sound under exclusive access, and that crossbeam/Arc fields are
already Send by design).
Added module-level convention doc to src/avhw/mod.rs centralizing
the C-API-level justification rule and explicitly calling out the
'&mut self as Send basis' anti-pattern so future contributors don't
repeat the category error.
Fixed AGENTS.md:
- Stale claim that Cargo.toml 'only warns' on undocumented_unsafe_blocks
(it's been 'deny' for a while)
- Stale path src/avhw.rs → src/avhw/ (split in d53e881)
- Stale 'avoid moving wrappers across threads' guidance — Send is sound,
the audit just confirmed why
No code behavior change. Verified: cargo build --release,
cargo clippy --release --all-targets (0 warnings),
cargo test --release (79 unit + 3 integration, 1 ignored).
CI / Build + Clippy + Test (pull_request) Failing after 11s
CI / Security audit (RUSTSEC) (pull_request) Failing after 31s
Drops PartialOutputInfo.physical_size and .logical_position. After the
warning cleanup in 6332472 these fields were probe-time-only gates with
no downstream consumer (encoder reads geometry from the dmabuf frame
itself, not from wl_output description).
Readiness simplification (try_finalize_output):
- xdg-output path (Sway/Hyprland): done_count >= 2 + name + transform
- wlr-output-management path (niri): done_count >= 1 + wlr_manager_done + transform
Safe because Wayland protocol guarantees Geometry/Mode/Position events
fire before Done, so done_count >= N implies the prior events arrived.
done_count is the real signal; the per-field .is_none() checks were
redundant belt-and-suspenders.
Cascading cleanup of writers that only fed the deleted fields:
- WlOutput::Geometry handler — drop physical_size write, keep transform
- XdgOutputEvent::LogicalPosition arm — deleted
- WlrHeadEvent::Position arm — deleted (was the only reader of
wlr_head_proxy_to_name; both maps now write-only markers)
- WlrHeadInfo.position field — deleted; struct becomes empty marker
Behavior change risk: probe may finalize slightly earlier in cases where
a compositor fires Done before physical_size/logical_position events
(protocol violation, but possible). Verified with cargo test --release
(79 unit + 3 integration, 1 ignored). Hardware/Wayland-session test
deferred to user.
Net: -48 lines.
CI / Build + Clippy + Test (pull_request) Failing after 38m47s
CI / Security audit (RUSTSEC) (pull_request) Failing after 1m30s
Brings `cargo clippy --release --all-targets` and `cargo build --release`
to zero warnings. Three categories:
Truly dead code (deleted):
- OutputInfo.physical_size / .logical_position — copied from PartialOutputInfo
at construction but never read on OutputInfo; PartialOutputInfo still uses
them as probe-completion gates
- EncConstructionStage::Streaming.output_info — stored at ->Streaming
transition, all 9 match arms discard via `..` or `output_info: _`
- State.starting_timestamp — vestigial Phase 1 stub; PTS normalization lives
in EncState / SwEncState instead (commit 079611a)
- cap_portal::fourcc() const fn — superseded by drm_fourcc::DrmFourcc
- PwThreadCtx.fps — destructured as `fps: _`, never consumed
Lifetime/ownership invariants (kept with #[allow(dead_code)] + reason):
- CapPortal.rt — ashpd caches a zbus::Connection in a process-global
OnceCell; runtime must outlive CapPortal or the connection hangs
- EncState.hw_device_ctx — root AVHWDeviceContext; consumers hold their
own ref_clone() but the root ref must stay alive for ownership
False-positive warning (annotated):
- state_portal use AsRawFd — required at FFI boundary but rustc mis-attributes
the call to OwnedFd's inherent method; E0599 if removed
Cosmetic:
- drop 5 redundant `as *const i32` casts on linesize.as_ptr() in
avhw/encode.rs and bin/vaapi_import_bench.rs