Skip to content

On a retina screen the image in the about dialog displays shrunken #301

Description

@jamescrosswell

Opportunity

On the maintainer's MacBook, the one image TuiCode draws is a quarter of the size it reserved room for — and the dialog leaves the empty half on screen.

Open About (ab) in iTerm2 on a Retina screen and the artwork fills the left half of the dialog and the top half of the space set aside for it, with a blank band underneath down to the version line. On an external non-Retina monitor the same build looks right. It's the first thing a new user sees when they check what version they're running, and it's wrong on the display most Mac developers use.

It is exactly half in each direction, which is the tell.

The cause: we read iTerm2's cell size in points and use it as pixels

SixelProbe.FindCellPixels asks iTerm2 OSC 1337 ; ReportCellSize. iTerm2's documented reply is:

OSC 1337 ; ReportCellSize=[height];[width];[scale] ST
"[height] and [width] are floating point values giving the size in points of a single character cell."
"[scale] gives the number of pixels (physical units) to points (logical units). 1.0 means non-retina, 2.0 means retina."
— iTerm2 escape codes, e.g. ReportCellSize=17.50;8.00;2.0

ParseIterm2CellSize matches ReportCellSize=([\d.]+);([\d.]+) and drops the third field. So on a Retina display we believe a cell is 8×17.5 pixels when it is really 16×35. AboutImage.Fit then encodes an image half as wide and half as tall as the area it just reserved, sixel data is drawn in device pixels, and the result covers a quarter of the area. At scale 1.0 points are pixels, so the external monitor is fine — and so is every dev machine without a Retina screen, which is why this shipped.

So it isn't the About dialog, the artwork, the encoder or iTerm2. It's one ignored regex group, in the measurement everything we ever draw as an image will depend on.

Evidence

  • The reviewer's screenshot on On a retina screen the image in the about dialog displays shrunken #301: the image occupies 50% of the dialog width and 50% of the reserved rows; the version line sits where a full-height image would have ended.
  • iTerm2's own documentation, quoted above. The [scale] field exists precisely for this.
  • The reviewer checked iTerm2 3.6.9 on a Retina MacBook: CSI 16 t gets no answer, CSI 14 t replies ESC[4;687;907t for 80×25 cells of 11×26pt (points, not the ~1760px wide pixels), and ReportCellSize=26.0;11.0;2.0. iTerm2 reports points everywhere; only ReportCellSize carries the scale. Other terminal projects have had to untangle the same units (SwiftTerm #686).
  • CellPixels has exactly one consumer today (AboutView.Present), so the blast radius is small — and every future one inherits the bug.

Vision fit: daily-driver gaps — "every rough edge [the maintainer] hits in daily use is a legitimate priority" — and honest about the terminal: we ask the terminal how big a cell is and then misread the answer.

Options considered

  1. Do nothing. Good: free; it's cosmetic and only in About. Bad: it's the maintainer's own report on their daily machine, it's the first screen anyone checks, and the wrong measurement is now load-bearing for any image feature we add later.

  2. Read [scale] and multiply (proposed). One more regex group, one multiply, default 1.0 when the field is absent (older iTerm2s send two fields). Good: it fixes the cause, it's what the documentation says to do, it handles any scale factor including 1.5 and a monitor change between runs, and it's a handful of lines behind an existing unit-tested pure parser. Bad: iTerm2-specific — though iTerm2's reply is the only one we read in points, so there's nothing else to fix.

  3. Prefer CSI 16 t and use ReportCellSize only as a fallback. Good: would sidestep the units question where CSI 16 t is answered in pixels. Bad: iTerm2 3.6.9 doesn't answer CSI 16 t at all (checked), so on the broken terminal we'd sit through TG's ~1s abandon and still land on ReportCellSize. Ruled out.

  4. Compute the cell from CSI 14 t ÷ CSI 18 t (what TG's own SixelSupportDetector falls back to). Good: device pixels, very widely supported, no scale factor anywhere. Bad: two more round trips before anything draws, wrong by however much padding the terminal adds, and iTerm2's CSI 14 t is in points too (checked), so it would halve the same way.

  5. Replace SixelProbe with TG's SixelSupportDetector. Good: less of our own code to own. Bad: it never asks ReportCellSize, so it bets everything on CSI 16 t (option 3's risk without option 3's fallback), it doesn't do our "ask both, take the first answer" trick, and it would mean re-verifying sixel on every terminal to fix a Retina bug.

  6. Stop sizing the image from the cell at all — encode at a fixed pixel size and reserve rows from a hardcoded guess. Good: no detection to get wrong. Bad: then the image matches the reserved rows on no terminal rather than on most, and iTerm2 blanks a partly covered row, so the dialog would have a gap or a clipped image everywhere.

  7. Let the user set a scale factor in Settings. Good: an escape hatch when detection fails. Bad: a setting to work around our own arithmetic, that a user has to know exists and know the right value for. The vision asks that a new user find things without reading docs; this is the opposite.

  8. Ask for the artwork at double size always. Good: one line. Bad: wrong by 2× on every non-Retina screen, which is the bug we have, mirrored.

Proposing 2. It's the only option that reads the one number iTerm2 gives us that carries the scale.

Proposal

Read the scale factor iTerm2 sends, and measure cells in device pixels.

  • ParseIterm2CellSize takes the optional third field and returns width × scale, height × scale. No field (older iTerm2) means scale 1.0, which is today's behaviour.
  • Nonsense is rejected as it is now — a non-positive or unparseable size returns null and the other query's answer or the 10×20 default is used. A scale is clamped silently to a sane range so a garbled reply can't ask for a 40,000-pixel image; the diagnostics row shows what was used.
  • Nothing else changes: same queries, same "first answer wins", same fallback, same encoder, same cache. The About dialog then fills the space it already reserves, on a Retina screen and on an external monitor alike.

And the measurement stops being invisible. The diagnostics overlay — F12, or the sd mnemonic (Show diagnostics) — gains a Cell size row: the pixels-per-cell we settled on, and which query told us. It's for diagnosing cell size, not for choosing between queries — in iTerm2 it will always show ReportCellSize as the winner. This bug was visible for the length of a release and needed a screenshot to spot; with the row it's one keystroke on the machine that's wrong, over SSH, and it's the same argument the reviewer already accepted for the cursor colour row in #243.

Nothing new to learn. No command, key, setting or theme colour. The picture is simply the right size.

Mockup

Today, About (ab) on a Retina screen in iTerm2 — the artwork at half scale in the top-left of the area reserved for it, and the blank remainder left on screen:

┌─ About ───────────────────────────────────────────────┐
│ ▛▀▀▀▀▀▀▀▀▀▀▀▀▜                                        │
│ ▌ ▗▄ TUI  ▖  ▐   ← image, half width and half height  │
│ ▙▄▄▄▄▄▄▄▄▄▄▄▄▟                                        │
│                                                       │
│                                                       │
│                   ← rows reserved, nothing drawn      │
│                                                       │
│                Version 0.0.8-alpha.0.4                │
│          https://github.com/mentaldesk/TuiCode        │
│                                                       │
│                  Esc · Enter  close                   │
└───────────────────────────────────────────────────────┘

Proposed — the same dialog, the image filling what was set aside for it, exactly as it already does on a non-Retina monitor:

┌─ About ───────────────────────────────────────────────┐
│ ▛▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▜ │
│ ▌                                                   ▐ │
│ ▌            ▗▄▄  T U I   c o d e ▖                 ▐ │
│ ▌                                                   ▐ │
│ ▌                  code::editor                     ▐ │
│ ▙▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▟ │
│                                                       │
│                Version 0.0.8-alpha.0.4                │
│          https://github.com/mentaldesk/TuiCode        │
│                                                       │
│                  Esc · Enter  close                   │
└───────────────────────────────────────────────────────┘

Slice 2 — one Label pair in the diagnostics overlay (F12 / sd), beside the rows already there:

┌ Diagnostics ──────────────────────────────────────────────────────┐
│   Driver          dotnet                                          │
│   Kitty kb        Yes (…)                                         │
│   Cell size       16 × 35 px  •  iTerm2 report, scale 2.0         │
│                                                                   │
│   Last key                                                        │
│     Name          Ctrl+G                                          │
...

and what the maintainer would have seen a release ago:

│   Cell size       8 × 17 px  •  iTerm2 report, scale 1.0          │

the other terminals, and the case where nobody answered:

│   Cell size       10 × 20 px  •  CSI 16 t                         │
│   Cell size       10 × 20 px  •  no answer, assumed               │

Scope

In

  • [scale] parsed from iTerm2's ReportCellSize reply and applied to both dimensions; absent means 1.0.
  • A sane clamp on the scale, so a garbled reply can't drive an enormous encode.
  • One Cell size row in DiagnosticsView: the pixels per cell in use, and which source it came from (iTerm2's report with its scale, CSI 16 t, or the assumed default).
  • The About dialog filling its reserved rows on a Retina screen, and unchanged everywhere it's already right.
  • Tests: the parser against iTerm2's documented example and the two- and three-field forms; the diagnostics row for each source.
  • The AGENTS.md note on sixel detection saying iTerm2 reports points everywhere (XTWINOPS replies included) and only ReportCellSize carries the scale — so nobody re-introduces this.

Out

  • Changing which query wins, or the order we send them (option 3).
  • Replacing SixelProbe with TG's SixelSupportDetector (option 5).
  • Any change to the artwork, AboutImage.Fit/Cover/Scale, SixelEncoder, the encode cache, or the ASCII fallback.
  • The dark band at the very bottom edge of the image (sixel bands are 6 pixels tall). Separate, cosmetic, and not what was reported.
  • Re-detecting when the window moves to a different monitor mid-session, or when the font size changes. The size is read once at startup; About is already open-and-close.
  • A setting for the scale factor (option 7).
  • Images anywhere else in TuiCode. There are none yet.
  • Sixel transparency, colour registers, or terminals that don't do sixel at all.
  • CSI 14 t / CSI 18 t as a third fallback (option 4).
  • Snapping to a row boundary for non-integer scales (1.5, scaled fonts); multiply and round is enough.

Rough breakdown

  1. The About image fills the dialog on a Retina screen. The [scale] field, the clamp, the parser tests, and the AGENTS.md note. Closes the report as filed; verifiable by the reviewer in one keystroke on the machine that's wrong, and unchanged on the monitor that's right.
  2. Diagnostics tells me how big it thinks a cell is. The row, and where the number came from. Small, and it's what would have caught this in a release rather than after one. It follows the cursor colour row Diagnostics tells me what the terminal did with our cursor colour #300 added to the same overlay.

Slice 2 is the one I'd drop if you don't want it; slice 1 stands alone.

Answered

  1. Slice 2 — keep it, for diagnosing cell size. (Reviewer.)
  2. Does iTerm2 answer CSI 16 t? No — checked by the reviewer on iTerm2 3.6.9. Option 3 is out; slice 1 ships as proposed, no Dev check needed. (Reviewer.)
  3. Silent clamp — yes. (Reviewer.)
  4. Non-integer scales — multiply and round, no snap. (Reviewer.)

Original idea

When I view the about dialog on my large monitor, all is well in the world.

On a retina screen on my macbook, it shrinks the image though:
Image

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpitchAn a-team pitch: Lead shapes it, reviewer approves it

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions