Skip to content

Releases

v0.2.9

Choose a tag to compare

@Itz-Agasta Itz-Agasta released this 01 Nov 20:13
fb42aa0

A small one: Windows audio behaves properly when you fall behind, and pinray is easier to find on crates.io.

Big thanks to @AndreIsraelAO for the Windows fix, their first contribution to pinray. They picked up #36, rewrote the WASAPI queue, and went through a full round of review including a drop guard so a panicking capture thread can't leave next_audio hanging forever.

Fixed

  • Windows system audio drops the oldest packets, not the newest (#38, fixes #36). If your loop stalled for a moment, WASAPI kept the stale packets and threw away the fresh ones. Tested on a real Windows machine: after a ~2 s stall, main handed back packets 1-64 and then jumped to 200, so you'd hear old audio right after catching up. Now you get 137-200 and then 201, the most recent audio. That's the same behavior as Linux, and sequence jumps still show you exactly how much was dropped.

Changed

  • Better crates.io listing (#40). The description now mentions Wayland and screen recording, the keywords match what pinray actually does (screenshot comes back when there's a screenshot API), and the crates are listed in the specific video and audio categories, with each platform crate under its own OS API category, instead of the generic ones.

No API changes, so cargo update is all you need.


Nao Tomori from Charlotte

v0.2.8

Choose a tag to compare

@Itz-Agasta Itz-Agasta released this 01 Nov 14:54
e0bcaa7

Wayland can now list your displays, and docs.rs finally matches what pinray actually does.

On a Wayland session, enumerate_sources() used to return only the system-audio source. Thanks to @prushton2, it now lists every monitor straight from the compositor, with fractional scale where xdg-output is available, and no portal dialog (#26). Tested on COSMIC and KDE Plasma. The ids are informational, since the ScreenCast portal still decides which output gets captured.

The rest is docs. Every public type, method, and field now has a doc comment, with examples on the main entry points and an # Errors section on anything that returns Result. While writing them I checked each claim against all the backends, which turned up a few things the old docs got wrong or never said:

  • X11's default frame rate is 60 like everywhere else, not 30. 30 is only what you get with frame_rate(None).
  • Audio queues drop the oldest packets on Linux, but the newest on Windows and macOS.
  • On macOS, next_audio() returns BackendNotSelected. Audio comes through next_event there (#34).
  • Wayland ignores crop_rect, and no backend uses the builder's color_space yet (#30, #32).

Those are now documented, and the ones that are actually bugs have issues with enough detail for anyone to pick them up. Several are labeled good first issue.

Added

  • Native Wayland display enumeration through wl_output, no portal dialog (#26, by @prushton2)

Documentation

  • Doc comments on the whole public API, with missing_docs enabled so it stays that way (#28, closes #6)
  • Rewrote the README, getting-started guide, platform matrix, troubleshooting page, and llms.txt; the README examples now compile as doctests (#35)
  • Added CONTRIBUTING.md, SECURITY.md (private vulnerability reporting is on), and a bug report form (#35)

Changed

  • More specific crates.io descriptions: pinray covers audio and names the native backends, and each sub-crate describes only what it contains

anime girl laughing happily

v0.2.7

Choose a tag to compare

@Itz-Agasta Itz-Agasta released this 14 Oct 15:25
0f85907

A small macOS release, but a nice one: capture frames finally come out at the right size. Window capture used to give you a display-sized frame with your window squished somewhere inside it, and display capture assumed every screen is Retina. Both are fixed now.

Big thanks to @MohammediYunus for this one. I don't have a Mac, so he did the real work here: tracked down both bugs, fixed them, and tested everything on actual hardware with before/after recordings and a benchmark. Really appreciate it 🙏

Fixed

  • Window capture is sized from the window (#19, fixes #15). Before, a 640x360 window came back as a full 3024x1964 display frame with the window letterboxed inside. Now you get the window at its own pixel size.
  • Display capture uses the display's real scale (#21, fixes #20). Pinray assumed 2x everywhere, so a regular 1080p external monitor came out as 3840x2160. It now comes out at 1920x1080, and Retina displays are unchanged. That's 75% less pixel data per frame on 1x screens, and in his benchmark the capture process used about 64% less memory and 67% less CPU.

Behavior change

  • macOS 13.0 is now the minimum (#18, closes #16). The stream setup uses audio settings that only exist on 13.0+, so 12.x was most likely crashing anyway. On older versions pinray now says so cleanly: available_backends() is empty and try_resolve() returns BackendUnavailable.

Verified on

macOS 26.7.1 on an M5 Pro, with a built-in 2x display and an external 1x display. The recordings are on #19 and #21. Not tested yet: macOS 13, scaled "looks like" modes and rotated displays.

Still open

Crop rect sizing (#22), enumerate_sources placeholders (#23), audio channel layout (#24) and live window resize (#25). If you have a Mac and want to help, these are all up for grabs.


Chika yay

v0.2.6

Choose a tag to compare

@Itz-Agasta Itz-Agasta released this 03 Oct 17:15
8034022

Wayland frame_rate and queue_depth now actually do something, and dropped frames show up as gaps in sequence on every backend.

Fixed

  • Wayland frame_rate reaches the portal (#12, from #11). Screencast portals offer a variable framerate (0/1) and pace by maxFramerate, but pinray only capped framerate, so the limit never applied. It now goes into maxFramerate. frame_rate(None) is uncapped and lands on the portal's own maximum, instead of silently becoming 60.
  • Wayland honors queue_depth (#12). The event channel was unbounded, so queue_depth did nothing and memory could grow without limit behind a slow consumer. It is now bounded and drops the newest frame when full, like the other backends.
  • Wayland frames are no longer batched (#12). Frames sat in a queue that was only drained every 20 ms, adding up to 20 ms of latency and delivering them in bursts. They are now sent straight from the PipeWire callback.
  • macOS frame drops show up in sequence (#14, tracked in #13). The video and audio counters only advanced when a frame made it into the queue_depth channel, so frames dropped on a full queue left no gap and callers could not tell anything was lost. They now advance for every extracted frame, matching the other backends.

Behavior change

On Wayland, the default frame_rate: Some(60) is now passed to the portal as a maximum. Portals that honor maxFramerate will cap a faster display at 60 fps where earlier versions ran at whatever the portal produced. Pass .frame_rate(None) for uncapped. How strictly the cap is enforced is up to the portal: xdg-desktop-portal-hyprland, for example, overshoots it (about 40 fps delivered for a 30 fps cap).

Wayland capture rate itself is set by the compositor's portal, which only produces a frame when the screen content changes. Capture below the refresh rate on a busy screen is usually the portal, not pinray. See #11 for the measurements.

Also

  • VideoFrame::sequence and the getting-started guide now describe the counter correctly: it advances per captured frame, including frames dropped on a full queue, and frames skipped by frame_rate pacing are not counted.
  • docs/platforms.md explains how the Wayland frame cadence works.

Verified on

Arch Linux, Hyprland 0.56, xdg-desktop-portal-hyprland 1.4.1, PipeWire 1.6.9, 1920x1080 @ 60 Hz, Intel iGPU. frame_rate(None) negotiates the portal's max (120) with throughput unchanged, frame_rate(Some(30)) negotiates 30, and queue_depth 1 and 4 buffer exactly 1 and 4 frames behind a stalled consumer. GNOME and KDE portals were not tested. The macOS fix is compile and CI verified only, not yet run on a Mac.


anime girl waving happily

v0.2.5

Choose a tag to compare

@Itz-Agasta Itz-Agasta released this 22 Sep 17:48

frame_rate is now honored on Windows. Both backends previously ignored it, so a session asking for 30 fps was handed frames at whatever rate the display or desktop produced.

Added

  • CaptureSession::restore_token() surfaces the Wayland portal restore token to callers, so a later session can skip the permission dialog. This landed after the 0.2.4 release and ships here.

Fixed

  • WGC honors frame_rate (#8, reported in #7). The free-threaded frame pool delivered a frame on every DWM composition, and frames overflowing the bounded channel were discarded only after texture_to_host had already run, so the GPU to CPU staging copy was paid for frames no consumer ever saw. Pacing now happens in the arrival callback before the copy. Measured at 1920x1080 @ 60 Hz with an encoder consuming at 30 fps: wasted copies went from 309 to 8 over ten seconds, and CPU from 29% of a core to 15%.
  • DXGI honors frame_rate (#10, tracked in #9). Duplication is pull model, so it never wasted work, but a caller who set .frame_rate(Some(30)) and looped on next_event got frames as fast as the desktop changed. On a 144 Hz panel that is 144 fps of full frame copies into your encoder. The acquire is now held off until the frame is due, so a skipped frame is never copied. Unthrottled to 30 fps at 1080p, that is 59.9 fps and 32% of a core down to 29.6 fps and 11%.
  • MinUpdateInterval is set on WGC where the OS supports it, so the compositor throttles at the source. Note this lives on IGraphicsCaptureSession5 and is Windows 11 only; Windows 10 returns E_NOINTERFACE and the software pacing carries the rate limit there.

Behavior change

On a display faster than 60 Hz, the default frame_rate: Some(60) now caps delivery at 60 fps where earlier versions ran at full refresh. This is what the setting has always meant on the Linux and macOS backends, and Windows was the odd one out. Pass .frame_rate(None) to keep the old behavior and stream every frame the display or desktop produces.

Pacing is a ceiling, not a floor. A desktop changing more slowly than the requested rate still delivers at its own rate.

Two details worth knowing if you care about exact spacing:

  • WGC can only select whole vblanks, so where the refresh rate is not an integer multiple of the request (60 fps on a 90 Hz panel) the gap between delivered frames alternates while the average holds.
  • Frames skipped by pacing are not reported as Gap or Dropped. You asked for that rate.

Also

  • chore: adopted as_chunks_mut across the platform crates for clippy::chunks_exact_to_as_chunks, new in Rust 1.98. CI pins stable with -D warnings, so this was breaking every job regardless of source changes.
  • docs/platforms.md now records what each Windows backend does with frame_rate, and the feature matrix is corrected.

Verified on

Windows 10 19045, 1920x1080 @ 60 Hz, Intel UHD Graphics. Both backends deliver within 1% of the requested rate at 60, 30, 15 and 5 fps, with CPU and staging bandwidth scaling linearly. The MinUpdateInterval success path is compile verified only, since it needs a Windows 11 build that exposes IGraphicsCaptureSession5.


thumbs up

pinray 0.2.4 — Minor docs and crates.io tweaks

Choose a tag to compare

@Itz-Agasta Itz-Agasta released this 08 Jul 13:54

Fixed

  • Fixed the README banner rendering & doc links incorrectly on crates.io.

Happy blue hair GIF

pinray 0.2.2 — Stride fix, reachable docs, agent-friendly reference

Choose a tag to compare

@Itz-Agasta Itz-Agasta released this 08 Jul 13:19

Small follow-up to 0.2.0 - no breaking changes, all additive.

Highlights

  • Fixed a real silent-wrong bug: VideoFrame.stride can exceed the tightly-packed row size, and copying FrameData::Host bytes directly into a rawvideo pipe or encoder skewed every row after the first. Added VideoFrame::to_tight_bytes() and PixelFormat::bytes_per_pixel() to fix it at the API level.
  • The getting-started guide is now the docs.rs front page (include_str!'d into crate-level docs), instead of living only in docs/ where docs.rs visitors never saw it.
  • Documented the frame-pacing footgun (declared constant fps vs push-driven actual capture cadence desyncs muxed audio/video) with a worked example.
  • Audio-only capture now has its own first-class example instead of a one-line mention, plus an explicit callout that AudioCapture::Microphone isn't implemented on any backend yet (.build() fails eagerly with PinrayError::Unsupported).
  • Clarified why the docs.rs page redirects to a fixed target (no libpipewire on their Linux builders) and linked docs.rs/pinray-core as the always-correct, target-independent type reference.
  • Added llms.txt at the repo root - a reference checked directly against crates/core/src/*.rs, for agents/tools integrating the crate.

Upgrading

Drop-in - no API removed or changed, only additions.

Kana Arima GIF

pinray 0.2.0 — Native Screen & System Audio Capture Across Linux, macOS, and Windows

Choose a tag to compare

@Itz-Agasta Itz-Agasta released this 06 Jul 19:55
29242b2

Pinray 0.2.0 is the first feature-complete release, bringing native video capture and system audio support across Linux, macOS, and Windows through a unified Rust API.

Highlights

  • Native display and window capture across all desktop platforms.
  • System audio capture on Linux, macOS, and Windows.
  • Consistent timestamps for audio/video synchronization.
  • Improved documentation, examples, and CI.

Platform Support

Windows

  • DXGI Desktop Duplication backend for high-performance display capture.
  • Windows Graphics Capture (WGC) backend for window capture with automatic fallback.
  • WASAPI shared-mode loopback for system audio capture.
  • Shared boot-relative timestamps across audio and video.

macOS

  • ScreenCaptureKit backend for display and window capture.
  • System audio capture through SCStream.
  • Improved screen-recording permission handling.
  • Boot-relative timestamps with optional BGRA → RGBA conversion.

Linux

  • Native X11 backend for display and window capture.
  • PipeWire system audio capture.
  • Shared monotonic timestamps for synchronized audio and video.

Core Improvements

  • Added new backend kinds:
    • WindowsDxgi
    • WindowsWgc
    • WindowsWasapi
    • LinuxX11
    • LinuxPipeWireAudio
    • MacosSckit
  • Improved session muxing to prevent audio starvation.
  • Some(Duration::ZERO) is now guaranteed to perform a non-blocking poll.

Documentation & CI

  • New getting-started guide and platform support matrix.
  • Expanded rustdoc with runnable examples.
  • CI now runs on Linux, macOS, and Windows.
  • Fixed docs.rs builds.

⚠️ Breaking Changes

  • BackendKind and BackendPreference gained new variants.
  • Timestamp semantics are now explicitly defined per platform. Consumers should compare timestamp deltas rather than absolute values across machines.

Pinray Demo

Thanks to everyone who tested Pinray during the 0.1.x releases! Feedback, bug reports, and contributions are always welcome.