Skip to content

Acknowledge _NET_WM_SYNC_REQUEST only after the resized frame on X11 - #193

Open
treeform wants to merge 1 commit into
masterfrom
x11-sync-request
Open

Acknowledge _NET_WM_SYNC_REQUEST only after the resized frame on X11#193
treeform wants to merge 1 commit into
masterfrom
x11-sync-request

Conversation

@treeform

@treeform treeform commented Sep 6, 2026

Copy link
Copy Markdown
Owner

Summary

On X11 the _NET_WM_SYNC_REQUEST counter was set at the top of every pollEvents, before that poll's events were handled. If the window manager's sync request arrived at the end of one poll and its ConfigureNotify in the next, windy acknowledged the frame while the window was still drawn at the old size. KWin then composited that frame, which is the one-frame black stripe on the right and bottom edge reported in #156.

This tracks the handshake per window:

  • sync request → SyncRequested, remember the counter value
  • ConfigureNotifySyncConfigured
  • next poll (after a frame at the new size went by) → XSyncSetCounter, back to SyncIdle

The event loop also waits up to 10 ms for the ConfigureNotify that follows a sync request, so the frame drawn after the poll is already the new size. A request that never gets a configure is never acknowledged, which is what the EWMH protocol expects.

This follows the intent of #156 by @levovix0. Left out on purpose: the temporary vsync disable after a resize and the cached size getter, which are unrelated to the sync handshake (the cache also made size= wait its full timeout, since blockUntil never polls events).

Test

tests/test_x11.nim plays the window manager side under a bare Xvfb: it sends the _NET_WM_SYNC_REQUEST client message, resizes with XResizeWindow, and reads the counter back with XSyncQueryCounter. It checks:

  • a resize without a request never touches the counter
  • request and configure in one poll: no ack until the next poll
  • request in one poll, configure in a later poll: no ack until the configure was handled and a frame went by, and the waiting poll gives up quickly
  • the full 64-bit value round-trips and a newer request wins
  • an idle window's counter is left alone

Against the old unconditional ack the test fails at the split case, which is exactly the reported bug. CI runs it on Ubuntu under xvfb-run with Mesa llvmpipe.

Validation

  • nim r tests/test_x11.nim under Xvfb (Docker, linux/amd64, Nim 2.2.4): passes
  • same test against the old ack: fails at the split-poll case
  • nim c tests/test.nim, examples/basic.nim, examples/callbacks.nim, examples/property_changes.nim on Linux: compile

Not verified on a real KWin session, I do not have one at hand. @levovix0 if you can try this on your setup that would confirm the visual fix.

🤖 Generated with Claude Code

The sync counter was updated at the top of every poll, before the
events of that poll were handled. When the window manager's sync
request landed at the end of one poll and its ConfigureNotify in the
next, the counter told the compositor the frame was done while the
window was still drawn at the old size, so KWin showed one frame with
black stripes on the right and bottom edge.

The window now tracks the handshake: a request marks it pending, the
ConfigureNotify marks it configured, and the counter is only set on the
following poll, after a frame at the new size went by. The event loop
also waits briefly for the ConfigureNotify that follows a request so
the next frame is already the new size.

Adds tests/test_x11.nim, which plays the window manager side under
Xvfb and checks the counter against XSyncQueryCounter, and runs it in
CI on Ubuntu.

Follows the intent of #156 by levovix0 without the temporary vsync
toggle and the cached size, which are unrelated to the sync handshake.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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