Skip to content

Implement ext-image-capture-source and ext-image-copy-capture protocols (staging)#3942

Open
powersemmi wants to merge 2 commits into
niri-wm:mainfrom
powersemmi:feat/image-capture-protocols
Open

Implement ext-image-capture-source and ext-image-copy-capture protocols (staging)#3942
powersemmi wants to merge 2 commits into
niri-wm:mainfrom
powersemmi:feat/image-capture-protocols

Conversation

@powersemmi
Copy link
Copy Markdown

@powersemmi powersemmi commented Apr 30, 2026

Implement the following Wayland protocols from the ext-image-capture-source and ext-image-copy-capture staging protocol families:

Protocol Description Status
ext_image_capture_source_v1 Opaque image capture source objects Added via smithay's ImageCaptureSourceState
ext_output_image_capture_source_manager_v1 Create capture sources from outputs Added via smithay's OutputCaptureSourceState
ext_foreign_toplevel_image_capture_source_manager_v1 Create capture sources from toplevels Custom implementation for niri's foreign_toplevel
ext_image_copy_capture_v1 Capture image content into SHM/DMABuf buffers Full rendering support
ext_foreign_toplevel_list_v1 List of toplevel windows Already implemented (added public lookup method)

Architecture

Capture Sources

  • Output sources: store WeakOutput in the source's user_data. Created via ext_output_image_capture_source_manager_v1.
  • Toplevel sources: store WlSurface in the source's user_data. Created via ext_foreign_toplevel_image_capture_source_manager_v1, with a custom handler that looks up the window by its ExtForeignToplevelHandleV1 through niri's existing ForeignToplevelManagerState.

Copy Capture

  • ImageCopyCaptureState registered as a global.
  • capture_constraints() returns the output or window size plus supported SHM formats (Argb8888, Xrgb8888).
  • frame() renders the captured content into the client-provided buffer:
    • Output capture: renders the full output via Niri::render() with RenderTarget::ScreenCapture.
    • Toplevel capture: finds the mapped window via find_root_shell_surface() + Layout::find_window_and_output(), renders via Mapped::render_for_screen_cast().
    • Supports both SHM buffers (via render_to_shm) and DMA-BUF buffers (via render_to_dmabuf).
    • Uses OutputDamageTracker with full damage (age=0) for correct sizing and rendering.
    • Signals completion with frame.success().

Toplevel Manager

Since niri implements ext_foreign_toplevel_list_v1 with its own state (not smithay's ForeignToplevelHandle), a custom ToplevelImageCaptureManagerState was implemented that:

  1. Registers the ExtForeignToplevelImageCaptureSourceManagerV1 global.
  2. On CreateSource, looks up the ExtForeignToplevelHandleV1 in ForeignToplevelManagerState::find_surface_for_handle() to find the corresponding WlSurface.
  3. Creates an ImageCaptureSource and stores the WlSurface in its user_data.

Notes

  • The ext_foreign_toplevel_list_v1 protocol was already fully implemented. A new find_surface_for_handle() method was added to ForeignToplevelManagerState for the toplevel capture source manager.
  • Frame rendering is synchronous within the Wayland dispatch handler, using self.backend.with_primary_renderer() to access the GlesRenderer.

Closes #1558

@powersemmi powersemmi changed the title feat: implement ext-image-capture-source and ext-image-copy-capture p… Implement ext-image-capture-source and ext-image-copy-capture protocols (staging) Apr 30, 2026
@powersemmi powersemmi marked this pull request as ready for review April 30, 2026 06:17
@powersemmi
Copy link
Copy Markdown
Author

Hi! I'd appreciate some help from the maintainer with reviewing and
possibly refactoring this PR.

A few things I'm unsure about and would love feedback on:

  1. The ToplevelImageCaptureManagerState in
    src/protocols/toplevel_image_capture_source.rs — since niri has its own
    ext_foreign_toplevel_list_v1 implementation (not using smithay's
    ForeignToplevelHandle), I wrote a custom handler. It works, but maybe there's a
    cleaner way to integrate it with the existing foreign_toplevel state.

  2. The synchronous rendering in ImageCopyCaptureHandler::frame() — currently it
    renders directly in the Wayland dispatch handler via
    self.backend.with_primary_renderer(). This might have performance implications or
    issues with renderer state. Should the rendering be deferred to the next output
    redraw instead?

  3. The frame constraints and rendering for toplevel/window capture — I'm computing
    the window bbox size on the fly, but maybe this should be cached or handled
    differently.

Also, a heads-up: this PR was primarily created to test my tool
RemoteWay — a remote desktop solution
that uses these protocols. So while the implementation works for the basic use case,
it could definitely use a proper review and cleanup from someone more familiar with
niri's internals.

Any guidance would be greatly appreciated! 🙏

@Sempyos Sempyos added area:screencasting Screen sharing, PipeWire, screencast portal pr kind:feature New features and functionality labels Apr 30, 2026
@powersemmi powersemmi force-pushed the feat/image-capture-protocols branch 2 times, most recently from fa1d16b to e46fdaa Compare May 2, 2026 23:27
…rotocols

Implement the following Wayland protocols:
- ext_image_capture_source_v1: opaque image capture source objects
- ext_image_copy_capture_v1: copy image content to SHM/DMABuf buffers
- ext_output_image_capture_source_manager_v1: create capture sources from outputs
- ext_foreign_toplevel_image_capture_source_manager_v1: create capture sources from toplevels

The ext_foreign_toplevel_list_v1 protocol was already implemented.

The implementation provides:
- ImageCaptureSourceState for core capture source handling
- OutputCaptureSourceState for output capture sources
- ToplevelImageCaptureManagerState for toplevel capture sources (custom
  implementation that integrates with niri's existing foreign_toplevel state)
- ImageCopyCaptureState for the copy capture protocol (frame capture fails
  with Unknown for now, can be extended with actual rendering later)
…opy-capture protocols

Complete implementation with actual rendering support:

Protocols implemented:
- ext_image_capture_source_v1: opaque capture source objects
- ext_image_copy_capture_v1: copy image content to SHM/DMABuf buffers
- ext_output_image_capture_source_manager_v1: create sources from outputs
- ext_foreign_toplevel_image_capture_source_manager_v1: create sources from toplevels

Rendering:
- Output capture: renders full output content into client-provided buffer
- Toplevel capture: renders window content (with popups) into client-provided buffer
- Supports both SHM and DMA-BUF buffers
- Uses the existing rendering pipeline (glES renderer, damage tracking)

Frame capture flow in ImageCopyCaptureHandler::frame():
1. Determine source type (output or toplevel) from ImageCaptureSource user_data
2. Build render elements using niri's existing rendering infrastructure
3. Create OutputDamageTracker for size/scale info
4. Render to the client buffer via render_to_shm or render_to_dmabuf
5. Signal completion with frame.success() or frame.fail()
@powersemmi powersemmi force-pushed the feat/image-capture-protocols branch from e46fdaa to eeca81b Compare May 14, 2026 08:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:screencasting Screen sharing, PipeWire, screencast portal pr kind:feature New features and functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement ext-image-copy-capture

2 participants