Skip to content

Add Wayland screencopy video capture - #14

Draft
chruffins wants to merge 3 commits into
masterfrom
hypeship/wayland-capture
Draft

Add Wayland screencopy video capture#14
chruffins wants to merge 3 commits into
masterfrom
hypeship/wayland-capture

Conversation

@chruffins

@chruffins chruffins commented Aug 17, 2026

Copy link
Copy Markdown

summary

not a serious PR, was just trying a long-running agent on investigating Wayland for browser image

  • add an opt-in Wayland video capture backend for WebRTC
  • read compositor frames from a configurable wf-recorder executable via wlr-screencopy-unstable-v1
  • feed raw BGRx frames into Neko’s existing GStreamer/WebRTC pipeline
  • add a Wayland desktop backend using /dev/uinput for pointer, keyboard, button, and scroll input
  • resize wlroots outputs through configurable wlr-randr
  • preserve the existing X11 backends by default
  • document the new configuration options

configuration

Enable:

  • NEKO_CAPTURE_VIDEO_WAYLAND=true
  • NEKO_DESKTOP_WAYLAND=true

The runtime needs wf-recorder, wlr-randr, and access to /dev/uinput. Output resizing defaults to HEADLESS-1 and can be configured with NEKO_DESKTOP_WAYLAND_OUTPUT and NEKO_DESKTOP_WAYLAND_RESIZE_COMMAND.

validation

  • go test ./...
  • go vet ./...
  • built wf-recorder 0.6.0 and wlr-randr 0.5.0 successfully in the Ubuntu 22.04 image toolchain

limitations

The Wayland backend targets wlroots compositors exposing wlr-screencopy-unstable-v1 and output management. Clipboard, file chooser, and drag-and-drop paths remain X11-specific and are not enabled by this backend.


Note

Medium Risk
Touches live WebRTC capture and remote input paths (subprocess recorder, uinput, pipeline lifecycle); behavior is gated off by default but misconfiguration or recorder failures could break sessions on Wayland deployments.

Overview
Adds an opt-in Wayland stack alongside the existing X11 defaults: separate flags for video capture (capture.video.wayland) and desktop control (desktop.wayland).

Video: When Wayland capture is enabled, pipelines use GStreamer appsrc instead of ximagesrc, with raw BGRx frames from a configurable recorder (default wf-recorder via wlr-screencopy). Custom gst_pipeline strings are rejected in this mode. StreamSinkManager gains an optional frame source that starts/stops with the pipeline and pushes into appsrc.

Desktop: With desktop.wayland, startup skips X11 and drives pointer/keyboard through a /dev/uinput virtual device (X11 keycodes mapped to Linux codes). Resolution changes call wlr-randr (output name and command configurable). Keyboard layout/modifiers, cursor image, and screenshots are no-ops or stubs on Wayland compared to X11.

Configuration and docs are updated for the new capture and desktop options.

Reviewed by Cursor Bugbot for commit cdbc229. Bugbot is set up for automated code reviews on this repo. Configure here.

"appsrc name=appsrc is-live=true format=time do-timestamp=true "+
"caps=video/x-raw,format=BGRx,width=%d,height=%d,framerate=%d/1 "+
"%s ! appsink name=appsink", screen.Width, screen.Height, fps, pipeline,
), nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Appsrc framerate conflicts with pipeline

High Severity

Wayland appsrc caps and wf-recorder both use screen.Rate, while the encoding chain from GetPipeline often forces a different framerate via VideoConfig.Fps (default "25"). Fixed appsrc caps cannot renegotiate against that capsfilter, so the default desktop rate (30) yields a not-negotiated pipeline and live video never starts.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0cd3a36. Configure here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 5 potential issues.

There are 6 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit cdbc229. Configure here.

if code < 8 || code > 263 {
return 0, false
}
return uint16(code - 8), true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Broken Wayland keyboard mapping

High Severity

mapKey treats incoming values as X11 keycodes (code - 8), but the WebRTC client and X11 path send X11 keysyms (for example XK_a 0x61, XK_Escape 0xff1b). Most keys are rejected or emit the wrong evdev codes, so Wayland keyboard input fails for normal typing and shortcuts.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit cdbc229. Configure here.

for key := 0; key <= 0xff; key++ {
if err := input.ioctl(uiSetKeybit, uintptr(key)); err != nil {
return fmt.Errorf("enable uinput key %d: %w", key, err)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mouse buttons never registered

High Severity

create only registers UI_SET_KEYBIT for codes 0..0xff, but mouse clicks emit BTN_LEFT/BTN_RIGHT/BTN_MIDDLE (0x1100x112). Without those keybits, the kernel drops button events, so Wayland pointer clicks do not reach the compositor.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit cdbc229. Configure here.


input, err := newWaylandInput(screenSize.Width, screenSize.Height)
if err != nil {
return manager.screenSize, err

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resize leaves compositor mismatched

Medium Severity

SetScreenSize runs the Wayland resize command before creating the new uinput device. If newWaylandInput fails afterward, the compositor is already resized while screenSize and the old device geometry stay unchanged, so capture and input stay out of sync with the display.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit cdbc229. Configure here.

manager.screenSize = screenSize
manager.emmiter.Emit("after_screen_size_change")
mu.Unlock()
oldInput.close()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use-after-close input race

Medium Severity

Callers load manager.waylandInput without synchronization, while resize/shutdown swap and close the device, niling fd. A concurrent move/button/key on the old instance can reach emit with a nil fd and panic inside binary.Write.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit cdbc229. Configure here.

if returnErr != nil {
manager.logger.Warn().Err(returnErr).Msg("Wayland scroll failed")
}
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ctrl-scroll ignored on Wayland

Medium Severity

The Wayland Scroll path drops the controlKey argument and only emits wheel events. The WebRTC client still sends Ctrl+wheel for zoom-style actions, so that behavior works on X11 and silently does nothing on Wayland.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit cdbc229. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant