Repository navigation
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR adds an initial Linux CLI recording backend built around the Wayland ScreenCast portal, PipeWire DMA-BUF transport, VA-API encoding, and GStreamer muxing. It also integrates Linux daemon lifecycle handling, relocatable packaging, documentation, and CI coverage.
Confidence Score: 4/5The PR should not merge until stopping a job before recorder startup completes yields a successful cancellation rather than a failed job. The coordinator exposes stop control before invoking the Linux recorder’s start method, while the recorder converts that reachable pre-start stop into an error that the coordinator records as failure; the remaining CI action-pinning concern is non-blocking hardening. Files Needing Attention: crates/linux/src/lib.rs, crates/daemon/src/coordinator.rs, .github/workflows/ci.yml
|
| Filename | Overview |
|---|---|
| crates/linux/src/lib.rs | Implements the Linux recorder worker and control lifecycle; pre-start cancellation is incorrectly surfaced as startup failure. |
| crates/linux/src/pipeline.rs | Builds the DMA-BUF-to-VA-API recording pipeline with audio, metrics, pause/resume, finalization, and synthetic tests. |
| crates/linux/src/portal.rs | Implements ScreenCast portal discovery, source selection, cancellation, PipeWire access, and bounded cleanup. |
| crates/daemon/src/coordinator.rs | Defers Recording status and duration accounting until the backend reports its first encoded frame. |
| crates/daemon/src/server.rs | Selects the platform runtime and gives Linux captures a bounded shutdown-finalization period. |
| crates/control/src/client.rs | Adds release-prefix-relative Linux daemon discovery and direct executable launching. |
| .github/workflows/ci.yml | Adds Linux build, portal, packaging, and smoke-test coverage, but executes newly referenced actions through mutable tags. |
| scripts/package-cli-linux.sh | Produces a relocatable Linux CLI and daemon archive with documentation and licensing. |
| scripts/test-cli-linux.py | Exercises daemon discovery, headless failures, cleanup, restart, and signal handling from the extracted archive. |
Sequence Diagram
sequenceDiagram
participant CLI as wrec CLI
participant D as Linux daemon
participant C as Coordinator
participant P as Desktop portal
participant PW as PipeWire
participant G as GStreamer/VA-API
CLI->>D: record display:0/window:0
D->>C: create Starting job
C->>P: create session and open picker
P-->>C: selected stream and PipeWire node
C->>PW: open remote FD
C->>G: start DMA-BUF pipeline
G-->>C: first encoded frame
C-->>CLI: job becomes Recording
CLI->>C: pause/resume/stop
C->>G: state change or EOS
G-->>C: finalized fragmented MOV
C->>P: close portal session
C-->>CLI: Completed or Failed
Reviews (1): Last reviewed commit: "feat(linux): add Wayland CLI recording w..." | Re-trigger Greptile
| status = Path(f"/proc/{daemon_pid}/status").read_text() | ||
| idle_rss_mib = int(next(line.split()[1] for line in status.splitlines() if line.startswith("VmRSS:"))) / 1024 | ||
| assert idle_rss_mib < 256, f"4K encoder retained {idle_rss_mib:.1f} MiB after teardown" |
There was a problem hiding this comment.
🔍 4K memory check depends on daemon baseline
The 256 MiB limit includes unrelated daemon and plugin memory, so CI host differences can fail this test without encoder retention. Compare idle RSS against a measured pre-recording baseline as well as a fixed budget.
Was this helpful? React with 👍 or 👎 to provide feedback.
Audio-loss warnings gave only an amount, so a loss in a test VM could not be traced to the audio server or to wrec afterwards. The PulseAudio reader now marks the first buffer after each gap with its cause. A loss counts as wrec's when the server's last answers showed it holding at least half a second that wrec had not read. Otherwise the server lost it, as when the machine froze. The warning now reads, for example, "microphone is missing 0.20 s of audio (the audio server lost 0.20 s)". A worker stopped for 2 seconds left a held video picture with no warning. The run loop now measures how far its 100 ms bus wait overran. More than half a second means the process did not run, and the job warns where and for how long. Only the wait is measured, so the loop's own disk calls cannot cause a false warning. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a real pipewire-pulse server, 2 of 11 runs that froze the server for 0.8 s reported 0.10 to 0.13 s as missing "without its source saying why". When pipewire-pulse confirms a graph loss, wrec discards held audio whose place is unknown. The audio kept after it can start before the step, so that gap fell on a buffer with no cause. The reader now also marks where the discarded audio ends. A hand-built test of that ordering fails without this change. Afterwards 10 of 10 server freezes read "the audio server lost", 3 of 3 reader stalls read "wrec fell behind", and 2 clean runs had no gap. A backlog now counts against wrec for one buffer length (1 s) instead of two, which still covers the answers that confirm a loss, so an earlier slow read is less likely to take the blame for a later server loss. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Recommend adding a GitHub action workflow for benching performance and make sure there is no major regressions moving forward: |
When audio the server lost and audio it dropped because wrec fell behind reached wrec as one gap, the whole gap was counted as wrec's. The warning now says both caused it and does not split the amount, since the shares are unknown. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a still screen, stock xdg-desktop-portal-wlr 0.8.4 sends no first frame. wrec's 15 s timeout then told the user to check DMA-BUF support and VA-API driver access, which were not the problem. When no frame reached the encoder at all, the error now says the screen-capture source sent no picture and points to the portal patches. The README records the 0.8.4 result: stock 0.8.4 passed all 21 native Sway cases on a moving screen, but on a still screen it sent nothing and then exited on retry, failing every later recording, in 2 of 2 runs. The patched 0.8.1 passed all 21 on the same still screen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Linux recording remains experimental. This PR adds Wayland capture through the ScreenCast portal and PipeWire, X11 display/window capture, hardware/software encoder selection, audio, and an isolated capture worker. Linux movies use MP4. There is no Linux GUI yet.
Current pushed head:
a43a801.f8e11db,44ce315andf0a7963change the engine only to say what lost audio and to warn when wrec stopped running;a43a801names a source that sent no picture instead of blaming the GPU (see below). Untila4cb3e1the engine source was unchanged from85100c3, whose CLI/engine binaries are byte-identical to the tested99fb801binaries. The pinned KWin and portal-wlr source bundles are committed. All six checks/statuses passed on a4cb3e1; all six passed again on 44ce315, f0a7963 and a43a801. Fresh independent review verified the KWin opt-in permissions fix on a4cb3e1. The two historical production audio losses stay unexplained, but a recurrence now names its cause in the warning. No merge or approval has been performed. This PR has not been merged.Behavior and fixes
The parent reproduced the unsafe first-frame crash before the memory fix, exit 139, then held the first frame across producer death after the fix and obtained the node-loss error with successful subsequent recording. Before capture-time pause filtering, three marked paused frames appeared in a decoded movie; afterward none appeared. Targeted reverted/mutant builds fail the relevant tests. Real producer timing tests cover both PipeWire timestamp domains and producer-allocated buffers.
Tested configurations and results
Native moving capture delivered about 23 fps under the fixture's CPU cap. Requested 60 fps was a ceiling test, not proof of sustained 60 fps. A moving A/V soak and an idle HEVC soak ran for 60 seconds each. Older long sessions retain their original revision/environment attribution in the historical reports; they are not fresh measurements of this head.
Upstream desktop dependencies
Stock KWin 6.6.6 stamps newly rendered pixels with the previous presentation time. Across 13 recordings, 167/168 short-idle flashes appeared about 310-679 ms early. Stock/control lost all 12 tested first changes after resume. KDE's upstream capture-time fix addresses the producer bug. Its Plasma 6.8 implementation has not been tested here.
The opt-in KWin backport bundle pins Ubuntu KWin
4:6.6.6-0ubuntu0.1, builds only its screencast plugin and supplies checksums, attribution, opt-in and rollback. On the same wrec 99fb801 binary, 19 patched recordings placed 205 short-idle flashes within -7 to +22 ms of app commit; all nine first changes after resume appeared, with no paused private markers. Fetch/build/load/record/undo ran in the guest; stock restoration and package verification passed. Eleven refusal cases and 29 parent fetch checks covered foreign files, symlinks, checksums, interrupted downloads and destination races. The independent review demonstrated that the earlier opt-in script accepted non-sticky group-writable ancestors, where another group member could replace the plugin path. Commit a4cb3e1 now refuses group write on every ancestor while preserving the sticky-directory exception. The parent independently passed 14 filesystem permission/config-preservation cases, then added a real named-user ACL case using the installed libacl library. All 15 passed after the fix; the old script incorrectly accepted precisely the five group/ACL write cases. Refusals happen before drop-in writes or any service call. Package/service calls were mocked; no login session was changed. Users must opt out before upgrading KWin because the old plugin can break login against another ABI. wrec does not install or load it.Native Sway results use four local portal-wlr patches on v0.8.1, bffd83b, binary SHA256
8ac22a934b4d6b9eea55461f8aef1e55c6a9ae1968aa94e838698686c01e2677. They fix pending-frame cancellation crashes, missing static first frames, invalid nonempty buffers and a cancelled-first-frame startup race. Earlier same-tool tests reproduced the stock crash and missing frames; the final patched source completed 25/25 static startups where its three-patch predecessor stalled in 14/25. Stock affected portal releases remain unreliable. These are patched-stack results. The newest release, stock v0.8.4 (built on Debian 12 with the same build-onlyunistd.hinclude), passed all 21 native cases on a moving screen. On a still screen it sent no first frame within 15 s, then exited with "session already has a frame object" when wrec retried in another mode, so every later recording in the run failed; this happened in 2 of 2 runs. The patched v0.8.1 passed all 21 on the same still screen in between. Since a43a801, when no frame reaches the encoder wrec says the source sent no picture and points to these patches, instead of asking the user to check DMA-BUF and VA-API. The portal source bundle is committed, with both legacy and modern patch series, upstream MIT notices, SHA256 checksums, pinned upstream commits/source trees and a build script. Nothing is installed automatically. All seven patch files match the tested copies byte for byte. Four serial stock/patched builds succeeded on Debian 12, and the parent independently rebuilt the modern series. All 31 refusal checks passed independently, including existing files/symlinks and changed patch/tag/tree pins. Six isolated manual setup/undo cases passed with mocked service calls; real systemd parsed the drop-in. Switching the service in a real logged-in desktop remains untested because it would disrupt unrelated screen sharing. The script never reuses or deletes an existing output directory. Ubuntu host rebuilding was not performed; native results retain their original tested binary attribution.Older portal frontends can deadlock opening PipeWire. The README describes a reversible frontend-only workaround; wrec does not change portal configuration.
Audio outcomes and remaining uncertainty
Recording warnings remain enabled. A decodable movie with missing audio is counted as a failure.
Further KDE production tests at 85100c3 reproduced startup loss in 7/88 capped-VM recordings, 30-70 ms in the first second. Chirp analysis showed missing content. An independent client without wrec lost no audio in 27 control runs and reproduced loss in all six tests that froze its guest for 80 ms. Server-index traces and host quota logs establish a guest-freeze mechanism.
The final 34 production recordings added a separate libpulse client recording both sources, a guest freeze watchdog and host scheduling/IO monitoring. Four recordings lost 26-66 ms of audio. Every loss matched independent-client evidence of audio loss upstream of wrec. Three occurred during startup quota throttling; the fourth matched a 59.8 ms guest freeze mid-recording. The host cause of that non-quota freeze remains unknown. These four recordings failed their loss-free criterion.
Since f8e11db, every audio-loss warning says what lost the audio, so a recurrence explains itself. The PulseAudio reader marks the first buffer after each gap. A loss counts as wrec's ("wrec fell behind reading and the audio server dropped 0.20 s") when, within the last second, the server's timing answers showed it holding at least half a second wrec had not read; a server drops audio for a reader only after it holds a full second. Otherwise the server lost it ("the audio server lost 0.20 s"), as in a guest freeze, which stops the server along with wrec. A gap the source did not explain is reported as such, and a missing end at stop separately. Checked by the fake-server reader tests for a pipewire-pulse main-thread stall and graph loss (server) and a reader stopped 2.5 s (wrec), a new PulseAudio drop-for-a-stopped-reader case (wrec), and 15 real private-PulseAudio pipeline tests, four of which now assert the cause: three server stops (server) and a reader stalled 3.5 s (wrec). A real pipewire-pulse server (PipeWire 0.3.65 with WirePlumber, private to the test) was checked the same way. Freezing the whole server for 0.8 s first showed a gap: 2 of 11 runs reported 0.10 to 0.13 s as unexplained, because the audio wrec discards around a confirmed graph loss could end on a buffer before the marked step.
44ce315marks where the discarded audio ends; a hand-built fake-server test of that ordering fails without it. Afterwards 10 of 10 server freezes read "the audio server lost 0.80 s", 3 of 3 reader stalls read "wrec fell behind ... 2.38 to 2.39 s", and 2 clean runs reported nothing. A fresh read-only review of f8e11db found no definite defect; it raised the pipewire-pulse question answered above and a too-wide 2 s blame window, now 1 s. Since f0a7963, a gap with both causes reads "the audio server lost and dropped X s while wrec also fell behind" instead of being counted as wrec's.Round R17 repeated the desktop tests on f0a7963 with the same caps, guards, host and guest monitors and the independent libpulse oracle. Across the KDE and GNOME VMs, 10 recordings lost audio in 13 track losses of 7 to 200 ms. wrec labeled every one "the audio server lost". 12 matched an oracle step or a host/guest stall within 1.5 s; one 26 ms KDE microphone loss had no event in any monitor, so its label is consistent but unconfirmed. No recording was labeled "wrec fell behind" or unexplained except the deliberate worker stops. A 2 s worker SIGSTOP produced the freeze warning in all four places it was tried (packaged X11, KDE with audio, native video-only and native A/V) and in no other recording. With audio on, that stop read "wrec fell behind" on KDE (2.37 s) and native Sway (1.90 s). Native Sway on the patched portal passed the 21-case matrix and the five sync scenarios again, with the same DMA-BUF/VA selection and the same CPU and memory as 85100c3 (8.2 to 9.4% of one core, about 87 MiB PSS). Two runs on f0a7963 lost 40 to 200 ms mid-recording, all labeled server losses. Six more alternating 60 s runs with a per-process CPU monitor showed the same small startup loss once on each version (1 of 3 each), during the fixture's CPU-quota throttling; the fixture's private PipeWire shares that quota. No difference between versions was found.
Two historical production events remain individually unproven: 200 ms on both tracks mid-recording, and 40 ms on system audio near stop. The final review agrees that the later independent-client evidence cannot retrospectively classify these events, and more blind repetition cannot recover the original upstream samples that were never observed. No speculative timing fix is justified; neither failure is counted as a pass. A similar recurrence would now be classified by the warning itself, without the external oracle. A historical diagnostic build also stalled its trace-writing readers for approximately 1.4 s during a guest-wide disk-writer stall. Production readers do not write trace files, but the exact original kernel wait was not reproduced. Injected guest-disk stalls up to six seconds did not reproduce it. No speculative timing change or warning suppression was applied.
One native moving privacy recording lost 45 ms microphone and 31 ms system audio during startup throttling of the capped fixture, which includes its private audio server. Its privacy check passed; its loss-free A/V criterion failed. The moving and idle 60-second A/V soaks passed their audio checks.
Stopping the native worker for two seconds produced a two-second held video frame without a video warning. Since f8e11db the run loop measures how far its 100 ms bus wait overran; past half a second the process did not run, and the job warns, for example, "wrec did not run for 2.0 s from 1.3 s, so the video may show one picture held there". Only the wait is timed, so the loop's own disk calls cannot cause a false warning. The packaged X11 test now stops a worker for 2 s and requires that warning; an unfrozen recording on the same build had none. wrec still cannot tell an idle screen from a compositor that stopped sending frames, so a compositor stall alone stays silent. Existing early-placement/discard limits remain documented in the README.
Resource measurements
Alternating two 9406b70 baseline and two current 85100c3 runs performed the same 60-second moving AMD/Sway A/V task. CPU was about 8% of one core and peak PSS about 83 MiB for both. No measurable regression or efficiency gain is claimed. Caps and descriptors showed DMA_DRM with an AMD tiled modifier and 17 DRM-exported DMA-BUFs; no DMA-BUF CPU mapping was observed in sampled process maps. Samples do not prove that a transient mapping can never occur, and conversion may allocate GPU surfaces.
Repeated current X11 4K software recordings peaked at 572 MiB worker RSS and left 10.1 MiB idle daemon RSS after teardown. This is software/Xvfb evidence, not a native GPU benchmark.
Screenshots from actual decoded recordings
Public images were downloaded and pixel-verified against their local artifacts. Engine evidence at 99fb801 applies because the current engine binaries are identical. Historical before/after images retain their revision attribution.
Environment and support limits
VMs ran one at a time with two vCPUs, 4 GiB guest RAM, low CPU/IO priority, 150% CPU quota, 5 GiB memory limit, disk limits and a host-pressure guard. Native Sway used an isolated 100% CPU/2 GiB unit at nice 19 with private audio. Guards took no action. All owned fixtures have stopped; host settings/packages/clock/AppArmor were not changed. These controls reduce competition and do not establish zero shared-host impact.
Earlier Chromium playback covered H.264 from both desktops, seeking and playing to the end. That build could not decode HEVC; FFmpeg decoded those movies. Safari/QuickTime requires macOS and is untested here. Intel/NVIDIA, physical microphone/ALSA/Bluetooth sync, native GNOME/KDE GPU performance, HDR, sustained 60 fps, GPU memory and power remain unverified. VM animation was 10 fps. Interrupted recordings may lose unfinished fragments; partial-movie readability is not guaranteed.
The f6fedc4 report and e0ba723 report preserve historical results. Current media, raw logs, commands, hashes and parent verification are retained under the thread's
wrec-pr31-current-2026-10-09attachment folder for independent review.