Skip to content

fix(moq-video): end X11 window capture when the window is destroyed - #3383

Merged
kixelated merged 1 commit into
mainfrom
quest/m0/x11-window-identity
Sep 4, 2026
Merged

fix(moq-video): end X11 window capture when the window is destroyed#3383
kixelated merged 1 commit into
mainfrom
quest/m0/x11-window-identity

Conversation

@kixelated

Copy link
Copy Markdown
Collaborator

Unverified. Written on a Mac, and capture/x11.rs is cfg(target_os = "linux"), so nothing local compiled or ran the changed file. The check that matters is in What a reviewer with an X session should check below.

Summary

  • Root cause. capture/x11.rs identified its target by XID alone and revalidated it each frame with get_geometry. An XID is not an identity: X clients allocate window ids out of their own range and do reuse freed ones, so a window destroyed and replaced by the same client can inherit the id. If the replacement's even-clamped dimensions match, get_geometry keeps succeeding and capture keeps publishing, now against a window the user never selected. The exposure is narrow (destroy, reuse, and a size match all inside one frame interval) but the failure mode is publishing content nobody chose, the same class as the layered-window leak fixed during feat(video): complete native screen capture #3244 review.
  • Fix. Select StructureNotify on the target at open, and drain the event queue before each frame; a DestroyNotify for the captured id ends the stream regardless of what happens to the id afterwards. The subscription goes in before the get_window_attributes / get_geometry round trips, so everything read back describes a window already being watched. Event masks are per-client, so the window's owner is unaffected, and this is a read-only interest rather than the SetPropW marker the Windows backend writes into a foreign window.
  • Drain ordering. The drain sits after the per-frame get_geometry, not before: the server writes events and replies down one ordered stream, so a DestroyNotify generated before that reply is already queued locally by the time the reply lands. A destroyed window whose id nobody reused makes get_geometry fail instead, which was already terminal. Draining every frame is also what keeps x11rb's event queue from growing for the life of the stream.
  • ConfigureNotify rides the same subscription and can later replace the per-frame get_geometry round trip. That is out of scope here; /quest/m2/x11-capture-shm.md owns it, and its plan is updated to say the subscription now exists.

What a reviewer with an X session should check

  1. Capture a window, destroy it, confirm the stream ends. This is the pre-existing behavior and should be unchanged.
  2. The case this PR is for: destroy the captured window and have the same client immediately create a replacement of the same size, so the server hands the freed id back out. Before this change capture silently continued against the replacement; after it the stream ends with window destroyed. A small Xlib program that creates, destroys, and recreates a window in a tight loop is enough to hit it.
  3. The window's owner is unaffected while capture runs: no lost events, no WM misbehavior, no other capture client displaced. Event masks are per-client, but worth confirming in practice.
  4. No regression in the ordinary paths: capture starts, an interactive resize still ends the stream with resized from ..., cursor blending still works, and display (non-window) capture is untouched.

Test plan

  • just fix, just check, just test on macOS, all clean. All three skip the changed file, since capture/x11.rs is Linux-only. x11.rs was additionally run through rustfmt directly, because macOS just fix does not reach cfg-gated modules.
  • A cross-compile of moq-video --features capture to x86_64-unknown-linux-gnu was attempted (rustup's Linux std, zig as the C/C++ cross compiler). The whole Rust dependency graph and openh264's C++ built for Linux; it stops at v4l2-sys-mit's bindgen, which needs a real cross libc header set. So the changed file itself was not compiled for Linux.
  • What was verified instead: the exact x11rb 0.14 surface this uses (change_window_attributes with ChangeWindowAttributesAux::new().event_mask(EventMask::STRUCTURE_NOTIFY), VoidCookie::check, Connection::poll_for_event, Event::DestroyNotify) was lifted verbatim into a throwaway crate and built and run against x11rb 0.14 on the host, so the calls type-check and the match behaves.
  • Also read x11rb's enqueue_packet to confirm the drain cannot swallow an error a live cookie is waiting for: errors matching a sent request go to pending_replies, and only unclaimed ones reach the event queue.
  • New unit test a_destroyed_window_ends_capture_even_when_its_id_lives_on. It is a genuine but partial regression test: it pins the "is this event about my window" decision, not the round trip to a real server, which is the part that needs an X session.

Public API changes

None. The subscription, Capture::destroyed, and destroys are all private to the module. (moq-video is 0.0.x regardless.)

Cross-package sync

No rows apply: no wire format, catalog, FFI, or CLI surface changed.

Quest

Completes and deletes /quest/m0/x11-window-identity.md, drops its entry from /quest/m0/README.md, and refreshes the sentence in /quest/m2/x11-capture-shm.md that assumed no event subscription existed yet.

🤖 Generated with Claude Code

(Written by Claude Opus 5)

X clients reuse freed window ids, so revalidating the target with
get_geometry alone cannot tell the selected window from a replacement
that inherited its id. Select StructureNotify at open and drain the
queue each frame so DestroyNotify ends the stream either way.

Completes the x11-window-identity quest.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-03T21:07:03.575273Z 5d090f5 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 7 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 9b153523-0c31-45a7-87ad-7d0d58bf7887

📥 Commits

Reviewing files that changed from the base of the PR and between 9e2054a and 5d090f5.

📒 Files selected for processing (4)
  • quest/m0/README.md
  • quest/m0/x11-window-identity.md
  • quest/m2/x11-capture-shm.md
  • rs/moq-video/src/capture/x11.rs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5d090f56f4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// before this reply is already queued here. A destroyed window whose id
// nobody took makes the request above fail instead, which is equally
// terminal.
if self.destroyed()? {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Drain destroy events after the image round trip

If the owner destroys the target and reuses its XID after this get_geometry reply but before GetImage is processed, this drain can return empty, while get_image().reply() later queues the DestroyNotify and successfully captures the replacement. The code then publishes one frame from a window the user never selected before noticing the event on the next iteration. Drain again after a successful image reply and cover this ordering in the regression test, since the current predicate-only test cannot catch it. (Written by GPT-5.6 Sol)

AGENTS.md reference: AGENTS.md:L148-L152

Useful? React with 👍 / 👎.

@kixelated
kixelated merged commit 049d018 into main Sep 4, 2026
3 checks passed
@kixelated
kixelated deleted the quest/m0/x11-window-identity branch September 4, 2026 17:27
@moq-bot moq-bot Bot mentioned this pull request Sep 4, 2026
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