Skip to content

fix(client): query host cell size when the ioctl reports no pixels - #2160

Merged
ogulcancelik merged 6 commits into
herdrdev:masterfrom
WakaTaira:issue/2146-wsl-cell-size
Aug 2, 2026
Merged

fix(client): query host cell size when the ioctl reports no pixels#2160
ogulcancelik merged 6 commits into
herdrdev:masterfrom
WakaTaira:issue/2146-wsl-cell-size

Conversation

@WakaTaira

@WakaTaira WakaTaira commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Summary

  • query the host terminal's cell size with XTWINOPS (CSI 16 t) once during
    attach when the terminal size ioctl reports zero pixels, and parse the
    CSI 6 ; height ; width t reply on the client's existing stdin path
  • cache the reported size and let the existing resize poller propagate it to
    the server — no wire protocol or server changes
  • keep the 8x16 fallback for terminals that never reply, and leave terminals
    whose ioctl reports pixels untouched

Under WSL, ConPTY zeroes the pixel fields of TIOCGWINSZ, so the client sent the
hardcoded 8x16 fallback to the server and pane graphics rendered at roughly 60%
of the real pixel count (blurry). The outer terminal knows the real cell size
and reports it over XTWINOPS: WezTerm answers CSI 16 t with 10x21 px.

Root cause vs. the issue

Issue #2146 pointed at HostCellSize::try_from_terminal in
src/kitty_graphics.rs. That path only serves the monolithic --no-session
mode; in the default client/server mode the value that reaches rendering comes
from current_terminal_geometry in src/client/mod.rs, which had the same
zero-pixel blind spot. Same problem and same goal as the issue, different
location — this PR fixes the client path. The monolithic path is left as is to
keep the change focused; happy to follow up if wanted.

Files to review (5, +305 / -27):

File Why
src/client/mod.rs (start here) query gating, cell-size cache, fallback order, resize poller baseline
src/raw_input.rs CSI 6 ; h ; w t parser and the new HostCellSizeReport event
src/app/runtime.rs, src/app/mod.rs, src/client/input.rs exhaustive-match arms for the new event (no behavior change)

Design notes

  • the query is sent once at attach, next to the existing host theme query, and
    the stdin framer is armed the same one-shot way — at most one reply is ever
    in flight
  • the reply feeds an AtomicU64 cache read by the resize poller; the poller's
    baseline starts from the handshake values, so the first report reliably
    triggers a resize message
  • live font-size tracking (re-querying after resize) is out of scope, per
    review; so is the monolithic path
  • Windows clients never send the query, mirroring
    should_query_host_terminal_theme

Verification

  • just ci (fmt + clippy + nextest) and just windows-lint — clean; failing
    tests on the dev box are identical on a clean master worktree, so no
    regressions from this change
  • live WSL smoke (WezTerm + WSL2): browser plugin pane goes from blurry to
    sharp on attach; cell size 8x16 → 10x21

refs #2146

@kangal-bot kangal-bot added the ai-review Trigger automated AI reviews for pull requests admitted by the PR gate label Aug 1, 2026
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The client parses XTWINOPS cell-size reports, tracks pending replies, stores reported dimensions, and uses them during terminal geometry and resize polling. Input and application handlers consume these reports without forwarding them as UI events.

Changes

Terminal cell-size reporting

Layer / File(s) Summary
Cell-size report parsing
src/raw_input.rs
Adds RawInputEvent::HostCellSizeReport, strict parsing for CSI 6;height;width t, split-reply buffering, query tracking, and timeout handling.
Client geometry and resize integration
src/client/mod.rs
Tracks cell dimensions through the handshake and resize polling. Queries eligible Unix graphics clients and uses cached reports for geometry fallback.
Reply buffering and event consumption
src/client/input.rs, src/app/mod.rs, src/app/runtime.rs
Arms outstanding cell-size reply holds, excludes reports from client input, and consumes reports without changing UI state.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ClientLoop
  participant HostTerminal
  participant RawInputParser
  participant ResizePoller
  ClientLoop->>HostTerminal: Request XTWINOPS cell size
  HostTerminal-->>RawInputParser: Return cell-size escape sequence
  RawInputParser-->>ClientLoop: Emit HostCellSizeReport
  ClientLoop->>ResizePoller: Update shared cell dimensions
  ResizePoller-->>ClientLoop: Calculate current terminal geometry
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main client change: querying host cell size when ioctl pixel data is unavailable.
Description check ✅ Passed The description directly explains the XTWINOPS query, parsing, caching, fallback behavior, and WSL/ConPTY problem addressed by the changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@greptile-apps

greptile-apps Bot commented Aug 1, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds a one-shot XTWINOPS cell-size query when terminal ioctls provide no pixel dimensions, then caches the response for the existing resize path.

  • Adds exact parsing and framing for CSI 6 ; height ; width t reports.
  • Propagates reported cell dimensions through the thin-client resize poller without changing the wire protocol.
  • Adds exhaustive handling for the new raw-input event in other runtime paths.

Confidence Score: 5/5

The PR appears safe to merge because no blocking failure remains eligible or outstanding in the supplied follow-up-review context.

No blocking failure remains.

Important Files Changed

Filename Overview
src/client/mod.rs Adds query gating, atomic cell-size caching, fallback selection, and pixel-aware resize propagation.
src/raw_input.rs Adds exact XTWINOPS response parsing and bounded framing support for split or malformed replies.
src/client/input.rs Arms the Unix stdin framer when the one-shot host cell-size query is sent.
src/app/runtime.rs Explicitly ignores the thin-client-only cell-size report in the application runtime.
src/app/mod.rs Adds exhaustive no-op handling for the new raw-input event.

Reviews (9): Last reviewed commit: "fix(input): discard timed-out host cell ..." | Re-trigger Greptile

@coderabbitai coderabbitai 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.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5b577b9b-0b9c-402f-bcdb-e981f5a21a94

📥 Commits

Reviewing files that changed from the base of the PR and between 26a7bc8 and 4c83047.

📒 Files selected for processing (5)
  • src/app/mod.rs
  • src/app/runtime.rs
  • src/client/input.rs
  • src/client/mod.rs
  • src/raw_input.rs

Comment thread src/client/mod.rs Outdated
@WakaTaira
WakaTaira force-pushed the issue/2146-wsl-cell-size branch from 4c83047 to 39766a7 Compare August 1, 2026 12:47

@coderabbitai coderabbitai 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.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f199a0e3-6f77-40bc-b438-344d09feb1ad

📥 Commits

Reviewing files that changed from the base of the PR and between 39766a7 and 46ee13b.

📒 Files selected for processing (3)
  • src/client/input.rs
  • src/client/mod.rs
  • src/raw_input.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/client/mod.rs

Comment thread src/client/input.rs Outdated
@WakaTaira

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/client/input.rs (1)

100-123: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Drain pending queries before the second timeout flush.

A resize query can increment pending_host_cell_size_queries while stdin_read_ready() waits at Lines 115-118. Line 120 then calls flush_timeout() without registering that query. The framer can release the held Escape and clear its reply holds before the delayed reply arrives. A reply split after ESC can then reach pane input.

Drain the counter immediately before the second flush_timeout(). Add a regression test where a second query is registered between the first and second timeout flushes.

Proposed fix
                     if held_escape
                         && stdin_read_ready(
                             &reader,
                             crate::raw_input::RAW_INPUT_IDLE_FLUSH_TIMEOUT_MS,
                         ) == Some(false)
-                        && !send_unix_input_chunks(
-                            framer.flush_timeout(),
-                            &event_tx,
-                            &mut pending_palette,
-                        )
                     {
-                        return;
+                        arm_pending_host_cell_size_queries(
+                            &mut framer,
+                            &pending_host_cell_size_queries,
+                        );
+                        if !send_unix_input_chunks(
+                            framer.flush_timeout(),
+                            &event_tx,
+                            &mut pending_palette,
+                        ) {
+                            return;
+                        }
                     }

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: db1b1f08-ca12-415b-ac45-07ff5225e2fb

📥 Commits

Reviewing files that changed from the base of the PR and between 46ee13b and bd5c26f.

📒 Files selected for processing (3)
  • src/client/input.rs
  • src/client/mod.rs
  • src/raw_input.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/raw_input.rs
  • src/client/mod.rs

@WakaTaira

Copy link
Copy Markdown
Contributor Author

Addressed the outside-diff-range finding (drain before the retry flush) in 130d3bb: the held-escape retry path now drains the shared pending-queries counter immediately before the second timeout flush, so a resize re-query registered during the retry wait extends the hold instead of leaking the held escape. Added the suggested regression test (second_held_escape_flush_drains_a_requery_registered_while_waiting).

@ogulcancelik ogulcancelik left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

thanks for the detailed reproduction. the underlying fix makes sense, but the implementation has grown too complex for this scope. re-querying after every resize introduces cross-thread outstanding-reply accounting and timeout races that now dominate the patch.

please simplify this to one cell-size query during initial attach, cache the valid response, and propagate it through the existing resize path. live font-size tracking can follow separately if needed.

please also substantially reduce the inline comments and duplicated tests. comments should explain only non-obvious invariants or reasoning, not restate the code. if you are using claude, instruct it to write comments only when they are strictly necessary. i’m not comfortable merging the current shape.

@WakaTaira

Copy link
Copy Markdown
Contributor Author

Simplified in 7e98404 as requested: the client now sends a single CSI 16t query during attach, caches the valid reply, and lets the existing resize poller propagate it. Dropping the per-resize re-query removes the cross-thread accounting entirely — the shared pending-query counter, its drain points in the stdin reader, and the accumulating framer counter all collapse into the same one-shot bool handoff the theme query already uses. Net -304 lines against the previous head.

Comments are trimmed to non-obvious invariants (the poller baseline race, the packed cache format) and the duplicated tests are gone — the split-reply hold is covered once, in the framer's own tests. PR description updated to match.

@WakaTaira

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@WakaTaira
WakaTaira requested a review from ogulcancelik August 2, 2026 11:47
WakaTaira and others added 6 commits August 2, 2026 16:31
Terminals behind ConPTY (WSL) report zero pixel sizes through
TIOCGWINSZ, so the client fell back to a hardcoded 8x16 cell size and
pane graphics rendered blurry. Ask the host terminal with CSI 16 t when
the ioctl reports no pixels, parse the CSI 6 ; height ; width t reply on
the existing stdin path, and let the resize poller propagate the
reported size through the existing resize message. Terminals that never
reply keep the 8x16 fallback.

refs herdrdev#2146
A cell size reply split right after its ESC introducer could leak a
plain Escape key through the idle flush, since only the host color
query armed the framer hold. Arm the hold for the cell size query the
same way and cover the split reply in tests.

refs herdrdev#2146
The framer hold was armed only for the startup query, so a reply to a
resize-time re-query split at its ESC introducer could still leak a
plain Escape key. Count each emitted query in a shared counter that the
stdin reader drains into the framer, accumulating outstanding replies
instead of resetting them.

refs herdrdev#2146
The held-escape retry path flushed without draining the shared query
counter, so a resize re-query issued during the retry wait could not
extend the framer hold and the held escape leaked as a plain Escape
key. Drain the counter right before the second timeout flush.

refs herdrdev#2146
Simplify the cell size query to a single probe during initial attach,
as requested in review. Dropping the per-resize re-query removes the
cross-thread outstanding-reply accounting: the shared pending-query
counter, its drain points in the stdin reader, and the accumulating
framer counter all reduce to the same one-shot bool handoff the theme
query already uses. The cached reply still propagates through the
existing resize poll path. Also trims restating comments and duplicated
tests across the touched files.

refs herdrdev#2146
@ogulcancelik
ogulcancelik force-pushed the issue/2146-wsl-cell-size branch from 425917d to f6f551a Compare August 2, 2026 13:33

@ogulcancelik ogulcancelik left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

the simplified implementation looks good. i fixed a couple of remaining input-framing edge cases.

@ogulcancelik
ogulcancelik merged commit 8901139 into herdrdev:master Aug 2, 2026
8 checks passed
@ogulcancelik

Copy link
Copy Markdown
Collaborator

ty!

@WakaTaira
WakaTaira deleted the issue/2146-wsl-cell-size branch August 2, 2026 13:56
abhijit-s pushed a commit to abhijit-s/herdr that referenced this pull request Aug 3, 2026
Range 9a4ce5e..10eb0bc (8 commits): host color-scheme propagation to
pane apps (herdrdev#2214), preserve hover during extended-button drags, older-git
snapshot-check support, publish only released docs, non-blocking foreground
cwd checks (herdrdev#2213), non-UTF-8 CLI arg reporting (herdrdev#2207), host cell-size
query when ioctl reports no pixels (herdrdev#2160), preserve claude settings
formatting (herdrdev#2089).

Resolutions: Cargo.toml/Cargo.lock both-add reconciled to keep fork's jiff
alongside upstream's jsonc-parser. justfile install-local recipe preserved
while adopting upstream justfile changes. Fork status strip, StatusConfig,
and herdrdev#1876 non-US shifted-keybinding revert (3c15069) all preserved; input
and terminal_theme fixes adopted additively.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-review Trigger automated AI reviews for pull requests admitted by the PR gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants