Search existing issues
Describe the bug
Recording a window on Linux (PipeWire/portal capture) puts the cursor overlay in the wrong position for the whole take. The offset is constant, it is there from the first frame, and it shows in clips of any length — a three-second recording is as wrong as a three-minute one. Recording a full screen is unaffected.
The cause is a coordinate-space mismatch between the video and the cursor telemetry.
On mutter, a window stream is not a stream of the window. The compositor pins the stream to the window's monitor and carves the window out with SPA_META_VideoCrop. The helper already handles that correctly for pixels — the encoder is opened at the crop's size and each frame is read from the crop's origin (Capture::read_origin, electron/native/pipewire-capture/src/capture.rs:308-321), so the MP4 contains the window.
The cursor never got the same treatment. The portal reports pointer positions in stream pixels, i.e. measured from the corner of the monitor, and emit_sample normalised them against the negotiated stream format:
// electron/native/pipewire-capture/src/main.rs
size = Some((format.width, format.height)); // the MONITOR, for a window stream
...
let visible = state.x >= 0 && state.y >= 0 && state.x < width && state.y < height;
let _ = emitter.emit(&Event::CursorSample { x: state.x, y: state.y, width, height, ... });
The consumer then divides by the width/height it was handed (electron/native-bridge/cursor/recording/pipeWireCursorAccumulator.ts:158-167), and the compositor treats the result as a fraction of the decoded video's visible area (crates/compositor/src/frame_geometry.rs:1251-1274). So a monitor-relative position gets divided by monitor dimensions and painted onto window-sized footage: wrong origin and wrong scale.
Concretely, for a 640×480 window at (100, 50) on a 1920×1080 monitor, a pointer at the window's centre (monitor 420, 290) serialises as cx = 0.219, cy = 0.269 instead of 0.5, 0.5.
Nothing anywhere subtracts crop.x / crop.y or rescales to crop.width / crop.height for the cursor — there is no crop handling on the TypeScript cursor side at all.
There is a second symptom from the same line: visible was tested against the monitor too, so a pointer that had left the recorded window still reported visible: true.
Expected behavior
The cursor should be drawn where the pointer actually was, relative to the recorded window — the same as it already is for a full-screen recording.
To Reproduce
- On Wayland (GNOME/mutter), start a recording and pick a window in the portal picker — ideally one that is not at the top-left of the screen, since the error is the crop origin.
- Move the pointer to an obvious landmark inside that window (a button, a corner).
- Stop and open the recording in the editor.
- The cursor overlay sits away from the landmark, offset toward the top-left and compressed toward it. The further the window is from (0, 0) and the smaller it is relative to the monitor, the larger the error.
Recording the full screen instead: correct.
Screenshots
No response
OS
Linux
OS Version
Ubuntu (GNOME / Wayland, mutter)
Other OS
No response
Browser
Chrome
Browser Version
No response
Other Browser
No response
Device Type
Desktop
Other Device
No response
Additional context
- Affects v1.10.0. Reproduced from the released AppImage, then confirmed against the source.
Search existing issues
Describe the bug
Recording a window on Linux (PipeWire/portal capture) puts the cursor overlay in the wrong position for the whole take. The offset is constant, it is there from the first frame, and it shows in clips of any length — a three-second recording is as wrong as a three-minute one. Recording a full screen is unaffected.
The cause is a coordinate-space mismatch between the video and the cursor telemetry.
On mutter, a window stream is not a stream of the window. The compositor pins the stream to the window's monitor and carves the window out with
SPA_META_VideoCrop. The helper already handles that correctly for pixels — the encoder is opened at the crop's size and each frame is read from the crop's origin (Capture::read_origin,electron/native/pipewire-capture/src/capture.rs:308-321), so the MP4 contains the window.The cursor never got the same treatment. The portal reports pointer positions in stream pixels, i.e. measured from the corner of the monitor, and
emit_samplenormalised them against the negotiated stream format:The consumer then divides by the
width/heightit was handed (electron/native-bridge/cursor/recording/pipeWireCursorAccumulator.ts:158-167), and the compositor treats the result as a fraction of the decoded video's visible area (crates/compositor/src/frame_geometry.rs:1251-1274). So a monitor-relative position gets divided by monitor dimensions and painted onto window-sized footage: wrong origin and wrong scale.Concretely, for a 640×480 window at (100, 50) on a 1920×1080 monitor, a pointer at the window's centre (monitor 420, 290) serialises as
cx = 0.219, cy = 0.269instead of0.5, 0.5.Nothing anywhere subtracts
crop.x/crop.yor rescales tocrop.width/crop.heightfor the cursor — there is no crop handling on the TypeScript cursor side at all.There is a second symptom from the same line:
visiblewas tested against the monitor too, so a pointer that had left the recorded window still reportedvisible: true.Expected behavior
The cursor should be drawn where the pointer actually was, relative to the recorded window — the same as it already is for a full-screen recording.
To Reproduce
Recording the full screen instead: correct.
Screenshots
No response
OS
Linux
OS Version
Ubuntu (GNOME / Wayland, mutter)
Other OS
No response
Browser
Chrome
Browser Version
No response
Other Browser
No response
Device Type
Desktop
Other Device
No response
Additional context