Skip to content

Capture a surface at the revision its caller listed, in one round trip - #90

Merged
pcarrier merged 3 commits into
yas-run:mainfrom
indent-com:capture-at-revision
Sep 30, 2026
Merged

pcarrier merged 3 commits into
yas-run:mainfrom
indent-com:capture-at-revision

Conversation

@pcarrier

Copy link
Copy Markdown
Collaborator

What

This PR adds Client::capture_surface_at(id, revision, format) to yas-client. It sends CAPTURE with the revision the caller already has (SurfaceInfo::revision, from surfaces() or surface()), so a capture takes one round trip instead of two.

If the window changed since the caller saw it, the server answers STALE. The client then looks the window up again and captures it, as capture_surface does, so the call never fails because of a stale revision.

capture_surface is unchanged: it looks the revision up (a WATCH snapshot), then sends CAPTURE. The CAPTURE half is now a private capture(id, revision, format) that both methods share.

Why

Ultimator's desktop screenshot first lists the windows to find the one asked for, then captures it. With capture_surface that costs three round trips (the list, the lookup inside capture_surface, CAPTURE) before the PNG comes back. The lookup repeats what the list just said.

Measured

Ultimator's netem harness, desktop screenshot of an alacritty window, p50 in ms, n=15. The server is the same #85–#89 build in both runs. Before is capture_surface, after is capture_surface_at with the listed revision:

mode rtt before after saved
worker 0 111.8 45.2 66.6
ssh 0 119.7 44.7 75.0
uplink 0 125.4 45.3 80.1
ssh 30 217.2 105.2 112.0
uplink 30 158.5 105.2 53.3
ssh 100 436.3 247.0 189.3
uplink 100 421.6 310.4 111.2
ssh 200 721.0 460.9 260.1
uplink 200 734.2 473.1 261.1

With a remote machine, a screenshot now takes 2 RT plus the image instead of 3. At RTT 0 it also saves about 66 ms. That cost belongs to the second surface WATCH snapshot when it comes right after the first: the extra lookup waited that long even locally.

Wire and server

No change. Capture.revision and the STALE answer already exist; this only lets callers supply the revision.

Tests

  • client_host surfaces_capture_take_input_and_close_with_the_paste_probe (with YAS_CLIENT_TEST_PASTE_PROBE):
    • capture_surface_at at the revision just listed returns a PNG;
    • after a resize bumps the window's revision, a raw CAPTURE at the old revision is answered STALE, and capture_surface_at at that old revision still returns a PNG.
    • Negative control: with the fallback disabled, the test fails at that capture with Status { status: Stale }.
  • client_host surfaces_are_none_without_the_compositor: capture_surface_at of a missing window is NOT_FOUND.

Checks

Run locally at e6036db:

  • cargo fmt --check passes;
  • clippy -p yas-client -p yas-cli --all-targets gives no warnings;
  • yas-client lib 33 passes;
  • client_host surface tests 2/2, with the paste probe built.

Based on #83's branch, so fork CI runs past the stale journal test. It is independent of #85–#89 and merges into them cleanly. Fork CI results will be posted here.

Since yas-run#54 a command wait that runs out before any command it could
report on has started answers TIMEOUT (NOT_FOUND stays for an exited
terminal or an evicted index). The client_host test still expected
NOT_FOUND, so it failed on main.
Since yas-run#54 the server answers TIMEOUT when a command wait runs out before
any command it could report on has started; wait_terminal_command's
documentation still said NOT_FOUND, which stays for an evicted index or
an exited terminal.
capture_surface looks the window's revision up (a WATCH snapshot) before
CAPTURE: two round trips, three for a caller that listed the windows first
to choose one, as Ultimator's screenshots do. capture_surface_at(id,
revision, format) sends CAPTURE with the revision the caller has; when the
window changed since, the server answers STALE and it looks the window up
again and captures that, as capture_surface does.
@ultimator-agent

ultimator-agent Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Reviewed e6036db in 5m 21s: no issues · Session

@ultimator-agent ultimator-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ultimator's review of e6036db

Adds Client::capture_surface_at(id, revision, format), which sends CAPTURE with a revision the caller already listed. It takes one round trip, and on STALE it falls back to capture_surface, which looks the window up again. capture_surface now shares the private capture helper. I checked it against the server's CAPTURE handler, which answers NOT_FOUND for a missing record and STALE on a revision mismatch before any transfer starts, and against how the client maps errors: call_ok turns a non-OK status into Error::Status, so status() is Some(Stale). Surface handles are allocated once and never reused, so a listed revision can't point at another window, and the fallback covers every stale case. The docs and test updates from #83's terminal-wait commits match what wait_terminal_command does. I found nothing to flag.

No issues found.

Session · @ultimator-agent review reviews again · @ultimator-agent fix fixes the open issues

@pcarrier
pcarrier merged commit 9d5fa98 into yas-run:main Sep 30, 2026
11 checks passed
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