Skip to content

The desktop and its own clients draw at the display's scale, set live on a display port (scale, item 1) - #931

Merged
Japabu merged 8 commits into
mainfrom
wt/toyos-scale
Oct 14, 2026
Merged

Japabu merged 8 commits into
mainfrom
wt/toyos-scale

Conversation

@Japabu

@Japabu Japabu commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

The desktop and its own clients draw at the display's scale, which screen scale sets live: item 1 of issues/the-desktop-draws-at-the-panels-scale.md, "the rest of the scale" of #895's accepted design. #895 chose 1.5 for both benches' panels and nothing drew at it.

At scale 2 on a 1920x1080 panel, from the new guest row: at scale 2

What changes

  • The window protocol's scale is its own wire type, window::Scale (sdk/window/src/scale.rs): n/120 from 1 to 4, Wayland fractional-scale-v1's denominator. The one rounding is decided there and documented at the top of that file: half away from zero, in integers, a rectangle by its corners (round_ratio; the compositor's chrome, relayout and cursor hot spot, the terminal's scrollbar and the clients all convert through it). WindowInfo and ResizeInfo carry the scale; a client refuses one outside 1..4 as a protocol error. A window is asked for as window::Size::{Any, Logical, Physical}; a logical size is converted by the compositor at the output's scale. Input coordinates and damage stay in the window's physical pixels. Event::Resized means the size or the scale changed.
  • The display port (window::display), served by the compositor and received by toybox alone (build/build.rs's HELD_ALONE): the mode and the scale, read and set; one request per connection, answered with the state or a named refusal. screen, screen WxH, screen scale 1.25|auto. A scale that is not a quarter step, or that leaves less than the least screen (642x446 logical), is refused by name; a set scale is kept in the compositor's HOME (/state/compositor/scale) and a kept scale a later mode cannot hold is drawn held down, said, and kept. MSG_SET_RESOLUTION, MSG_GET_RESOLUTION and MSG_RESOLUTION_CHANGED leave the window protocol; a mode set on GOP is refused by name (NoModes).
  • The compositor draws at the scale: Chrome::scaled and Desk::new(screen, scale, apps); text from the TTF at the scaled cell; the title-bar icons and the cursor from their SVGs at the scaled edge; the hardware cursor while the sprite is at most 64 pixels (3.2x), software past it. Every window is relaid on a change (toyos_desktop::relayout: a normal window's rectangle scales by its corners and is fitted so its frame stays inside the work area, a maximized or snapped one is retargeted and where it goes back to fitted the same way) and given a buffer at the new scale. A logical request whose frame would pass the work area is fitted to it and said (toyos_desktop::requested), as winit's requested size is a request: doom's 960x600 logical at scale 2 on 1920x1080 opens fitted where it would otherwise be refused; a physical request past the screen is still refused.
  • Nothing flashes, and a window holds two buffers at most: WindowInfo and ResizeInfo give each buffer a generation, which MSG_PRESENT carries back (window::PresentRect). A window is shown from the buffer it last presented until a present carries the given buffer's generation, so a present drawn into the old buffer after a resize keeps the old one shown; a generation the window never held drops the client by name. While a given buffer is unpresented no other is given: the window is owed it and given it at the present that swaps (toyos_desktop::Buffers), so a hung client holds two, and the budget counts two per window.
  • One producer for text and icons. toyos-font gains Font::from_ttf, the build's rasterize_cells moved in, so the build's 8x16 raster and a guest's raster at any cell are one function; the shipped 8x16 raster is byte-identical to the build's before the move (sha256 41f922af… both). toyos-icon (sdk/icon) is build/icons.rs moved, with a mask at any edge and a safe blend; sprite and files' copy fold into it, which closes issues/sprite-dereferences-a-raw-pointer-behind-a-safe-fn.md. Its transcendental functions are libm's, so host and guest draw the same pixels; the resvg-compared digest of the shipped icons is unchanged. The build ships the SVGs and still refuses one past the subset by parsing it with the crate.
  • terminal, filepicker and its consent prompt, and files draw at their window's scale and follow a change; editor and paint stay at 1, as The loader hands the panel's EDID to the compositor, which chooses the display's scale #895's ruling has it; console stays at 1.
  • winit: ToyOSOrg/winit toyos-0.30.13 gains 48c7022a: scale_factor() answers the window's scale, a resize whose scale changed sends ScaleFactorChanged then Resized, and a requested Size goes on the wire as the kind it is. Cargo.lock pins it.
  • The desktop's assets: JetBrains Mono ships as its TTF beside its raster, with its OFL text at share/licenses/; the icons ship as their SVGs with their MIT notice (NOTICE updated).

Decisions for the orchestrator

  1. The scale an image starts at where no panel size exists is the compositor row's argument scale=N, not a kernel parameter. The brief suggested scale=2 on the kernel parameter line. Userland has no channel to that line today, and the kernel parameter line is in the image's signed header, so it is no more boot-time than ROOT; the virtio path (virtio-gpu leaves the kernel: the compositor drives it through its claim and follows the host's display #925) takes no framebuffer claim, so FramebufferInfo cannot carry it either. The row argument needs no kernel or ABI change: tests/images/scalecase.toml writes args = ["scale=2"], and cargo run -- --scale 2 adds it to the compositor's row (build::at_scale; the name is toyos_manifest::display::SCALE_ARG). The build cannot ask a macOS host's backing scale without a host-only tool, so the flag is documented in README.md rather than defaulted. Precedence: a kept screen scale choice, then the image's argument, then the panel's EDID.
  2. The cursor's hot spot is the shape's own (the arrow's tip, the crosshair's and resize arrow's centre), scaled, where it was each sprite's top-left corner, 2.7 logical pixels from the arrow's tip.
  3. toyos-desktop depends on toyos-window for the wire scale and its rounding, so the rounding has one home; it uses types only. toyos-font's rasterizer is its default ttf feature, which toyos-window turns off: a published SDK crate depends on the SDK alone (sdkversion's offline resolution holds that), and toyos-window reaches every winit app.

Checks

Review round 1 at head cdd9fd3f1 (main 335927b83 merged), evidence pr-931 at f437f090: --ci host exit 0, 86 steps green, with a clippy shape for toyos-font without ttf (log); --build-only exit 0; the guest rows below plus the virtio row, now booting at scale=2 (1280x800 drawn at 1.75, 1024x768 followed at 1.5), exit 0, 5 of 5 (log); new controls each red their tests: relay unfitted (B1), swap on any generation (B2), never owe (B3), logical request not fitted (B5) (log); the guest controls rerun (log); the 8x16 raster compared with main's producer, identical (log). Both benches PASS gop_edid_scale at c849397d3 (T14 handle 0, AMD handle 1, panel 1.5, a 200x100 window drawn 300x150; readbacks); the row is restaged at cdd9fd3f1 since B1 and B2 change the relay it exercises.

The first round's checks, at head c849397d3 (main bd5d4e0c0, #925, merged); evidence pr-931 at ccdb6eb3:

  • Host: cargo run -- --ci host, exit 0, 86 steps green (log). New host tests: the wire scale's range, decimal parsing and refusals, rounding at the halves and at negative coordinates, touching rectangles at every quarter step, the cell, title bar and icon at 1, 1.5 and 2 (sdk/window/src/scale.rs); the display port's requests, an absurd scale as no request, payloads of another length, state and refusals on the wire (display.rs); Size on the wire; the policy scale (quarter steps, the least screen, a kept scale held down and kept); chrome at a scale, relayout across 1→2→1 and 1→1.5→1 (at most a pixel's drift) and onto the work area with maximized windows; the two-buffer swap; two buffers in the budget; the cursor hot spot and the hardware plane's 64-pixel edge; text cells at each scale and a larger cell rasterized at its size rather than doubled; an icon at twice the edge drawn at its size; the mask's clipped blend.
  • Image: cargo run -- --build-only, exit 0 (log).
  • Guest: cargo test --test toyos-build -- a_window_is_drawn_at_the_scale_and_relaid_at_the_next desktop_without_x2apic screen_fatal_halt_composited the_opening_exchange_with_qemus_agent_completes the_compositor_drives_the_virtio_gpu, exit 0, 5 of 5 (log). window_refusal (a shared-boot member the harness cannot filter, with two new absurd-scale cases) through a scratch-only machine row on scalecase, reverted: PASS (log).
  • The new guest row, a_window_is_drawn_at_the_scale_and_relaid_at_the_next (tests/images/scalecase.toml, Profile::Metal's q35 with a 1920x1080 GOP panel and the i8042): claim — a client's window asked for as 200x100 logical is on the panel 400x200 under a 56-row title bar at scale 2, and screen scale 1, run by the client through the launcher as toybox, relays it to 200x100 under 28 rows. Read off QMP screendumps by the fill's extent; the host presses Enter (ps2server) once it has read each panel. Why a guest: what reaches the panel is the compositor's buffer swap, chrome and the protocol's scale meeting on a running desktop; no metal machine's glass can be read. Screens.
  • Metal: gop_edid_scale now also holds, on metalcase's existing boot, compositor: drawing at scale 1.5 and scale_window's 200x100 logical window drawn 300x150 at 1.5, on both benches, and names a bench's kept scale if it finds one. Staged with cargo test --test toyos-build -- --metal --metal-readback <dir> gop_edid_scale (exit 2, staged, log); not yet run on either bench.
  • Negative controls, each a checked patch applied, run and reverted in one run (host, guest, patches): rounding half to even reds a_half_rounds_away_from_zero; one buffer in the budget reds its test; a swap that never swaps, a hot spot at the corner, a relayout that keeps the physical rectangle and a display port that reads an absurd scale as auto each red theirs; the compositor ignoring the logical flag stalls the scale row at "the window at 2" (it drew 200x100 at 2); no relay at all stalls it at "relaid at 1"; a client taking an absurd scale reds window_refusal. One mutation passed and was investigated: dropping only the rescaled || clause leaves the row green, because a scale change changes every normal window's size and so relays it anyway; the clause keeps a window whose size survives a rescale told its scale.

Risks and what is left

  • virtio-gpu leaves the kernel: the compositor drives it through its claim and follows the host's display #925 is merged in: its flip and this branch's relayout are one lay_out(mode, scale); a display event's panel scale is the base where no scale is kept or named by the image, so a host window's resize changes the mode and the panel's scale only. On virtio-gpu the scale is read by log alone (the virtio row at scale=2); no screendump measures a window there, nor the hardware cursor's hot spot, nor screen WxH over the port — recorded in the issue with its exit.
  • editor, paint, calc and snake draw at 1 at any scale (editor and paint by The loader hands the panel's EDID to the compositor, which chooses the display's scale #895's ruling, calc and snake asking for a PhysicalSize on every host), recorded in the issue with owner and exit; a third-party app that ignores scale_factor() draws small (no compositor upscaling, by design).
  • screen scale writes the kept file in the compositor's loop (review S5, not taken: one small write per person's choice).
  • A normal window can drift by one pixel across a fractional scale and back (accepted in the design).
  • files and the compositor read the font and icons from /system/share, a path only this tree has (issues/five-apps-read-the-system-font-by-a-path-only-this-tree-has.md, updated).
  • virtio-gpu leaving the kernel is item 2 of the issue, unchanged.

🤖 Generated with Claude Code

https://claude.ai/code/session_016UzbDnM9m6nQWhbjGi6Lv1

Japabu and others added 6 commits October 14, 2026 19:43
…a display port

#895 chose a scale from the panel's EDID and nothing drew at it. This is
the rest of the scale as #895's accepted design says it.

- The window protocol carries the scale as its own wire type,
  `window::Scale`, n/120 from 1 to 4, with the one rounding every party
  converts with: half away from zero, a rectangle by its corners.
  `WindowInfo` and `ResizeInfo` carry it, a client refuses one outside the
  range, and a window asked for in logical pixels is converted by the
  compositor (`window::Size`). `Event::Resized` means the size or the scale
  changed.
- The compositor's `display` port, held by `toybox` alone for `screen`:
  the mode and the scale, read and set. `screen scale 1.25|auto` relays
  every window at the new scale and is kept in the compositor's HOME; a
  scale no quarter step, or one leaving less than the least screen, is
  refused by name, and a kept scale a mode cannot hold is drawn held down
  and stays kept. `MSG_SET_RESOLUTION`, `MSG_GET_RESOLUTION` and
  `MSG_RESOLUTION_CHANGED` leave the window protocol; a mode on GOP is
  refused by name.
- The compositor draws its chrome, text, icons and cursor at the scale:
  `Chrome` at a scale, the font rasterized from the TTF at the scaled cell
  (`toyos-font`, the build's rasterizer moved in, so build and guest share
  one producer and the 8x16 raster is byte-identical), and the icons and
  cursor drawn from their SVGs at the scaled edge (`toyos-icon`, which
  `build/icons.rs`, `sprite` and `files`' copy fold into; the build still
  refuses an SVG past the subset). The hardware cursor carries the sprite
  up to 64 pixels, 3.2x, and the hot spot is the shape's own, scaled.
- A window keeps the buffer it last presented until its first present into
  the new one (`toyos_desktop::Buffers`), so a relay never flashes; the
  window budget counts two buffers per window. `toyos_desktop::relayout`
  scales a normal window's rectangle by its corners and retargets a
  maximized or snapped one.
- terminal, filepicker and its consent prompt, and `files` draw at their
  window's scale; editor and paint stay at 1, as #895's ruling has it.
- The winit fork's ToyOS backend answers `scale_factor()` with the
  window's scale and sends `ScaleFactorChanged` before `Resized`
  (ToyOSOrg/winit `toyos-0.30.13` at 48c7022a).
- An image starts the desktop at a scale where the panel gives none with
  the compositor row's `scale=N` (`cargo run -- --scale 2`), since QEMU's
  firmware publishes no EDID.

The desktop font ships as its TTF beside its raster, with its OFL text,
and the icons as their SVGs with their MIT notice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016UzbDnM9m6nQWhbjGi6Lv1
…s without

A published SDK crate depends on the SDK's own crates alone, which is what
`sdkversion`'s offline resolution of the forks' requirements holds, and
`toyos-window` reaches every winit app. `ttf` is on for the desktop's
programs and the build, and `toyos-window` takes the raster's drawing
without it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016UzbDnM9m6nQWhbjGi6Lv1
One conflict: lib/manifest's module list, `device` beside `display`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016UzbDnM9m6nQWhbjGi6Lv1
toyos-desktop depends on toyos-window for the wire scale, so the host's
clippy shapes lint toyos-window as a dependency of a host member: `is_multiple_of`
in both scales, and toyos-window's own `Waiter::default` and `is_empty`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016UzbDnM9m6nQWhbjGi6Lv1
#925 moved virtio-gpu into the compositor behind `Display`. Kept both:

- One layout for a mode and a scale: #925's `relayout(mode)` and this
  branch's `lay_out(scale)` are `lay_out(mode, scale)`, which `change_mode`'s
  flip, a display event and the display port's `screen scale` all take. A
  display event's panel scale is the base where no scale is kept or named
  by the image, so a host window's resize changes the mode and, only then,
  the panel's scale.
- The display port's `screen WxH` takes #925's flip through `mode::follow`;
  a display that keeps the firmware's mode refuses it as `NoModes`
  (`Display::sets_modes`). `MSG_SET_RESOLUTION` stays gone.
- `Display::set_cursor` takes the hot spot; the cursor is the display's
  where it has a plane and the sprite fits 64 pixels, emptied otherwise.
- `mode::LEAST` is built from `TEXT_CELL`, and `Scale`'s least screen is it.
- clipboardcase's compositor serves `display` beside #925's claim.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016UzbDnM9m6nQWhbjGi6Lv1
@Japabu

Japabu commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator Author

Review of #931 at c849397d3 (HiDPI item 1: the desktop and its clients at the scale)

I read every changed file in full at c849397d3, its callers, the winit fork commit 48c7022a, the evidence branch pr-931 at ccdb6eb3 (logs, patches and screens), the accepted design on #895 (comment 6158220132) and the issue. Per reviewer.md I ran no build or test. The host claim rests on ci-host.log at this head: 86 step(s), all green, exit 0. The new toyos-window, toyos-font, toyos-icon and toyos-desktop tests all appear in that log.

Architecture

The ownership follows the accepted design, and I think it is sound:

  • window::Scale is the protocol's own wire type. It is not a toyos-abi type. One round_ratio handles the conversions, and the desktop's quarter-step policy is a newtype over it in toyos-desktop.
  • The display port is a userland protocol with its own refusals, and HELD_ALONE restricts it to toybox.
  • Text is rasterized from the TTF at the cell size, and icons and cursors from their SVGs at the edge size. Neither is upscaled, and both sides have host tests that refute doubling.
  • toyos-icon absorbs sprite and files' copy, which closes the unsafe-blit issue.
  • The winit fork change is platform support only. It drops in cleanly and replaces no app logic.

The grep supports the one-rounding claim. round, ceil and floor appear only in toyos-icon's rasterizer geometry. Chrome, relayout, hot spot, terminal scrollbar, files and filepicker all go through Scale::px/round_ratio. Pointer events and hit tests stay in physical pixels with no conversion. I checked the hot spot (34,34)/256 against cursor-bold.svg: the tip arc's centre is about (48,48) with r=20, so its furthest point up and left is about 33.9.

The bugs are in the relay and buffer handling (B1–B3), the issue record (B4) and one behaviour change for logical requests (B5).

Blockers

B1. system/compositor/desktop/src/layout.rs:46: a scale-up grows a normal window past the screen. relayout scales the content by its corners and then calls reflow. reflow is confine_to (rect.rs:167), which moves a rectangle but never shrinks it.

  • Concrete case: the terminal opens with Size::Any at scale 1 on 1920x1080. Its frame is (40,40,1840,968) and its content is 1838x938. After screen scale 2 the content is 3676x1876 and the frame is placed at (0,0,3680,1936). That window's maximize and close buttons are about 1700 px off the right edge, so the person cannot reach them. Its buffer is about 27.6 MB.
  • The same happens from 1.5 to 2 on the benches, where terminal and files start near full-screen.
  • This falsifies two statements:
    • budget.rs:16: "a screen-sized content buffer is the largest the compositor ever hands out … every path that grows a window … is bounded by the screen". The window budget rests on that.
    • The relayout doc ("brought back onto the work area").
  • a_relaid_window_stays_on_the_work_area (layout.rs:532) only uses windows that fit after scaling.
  • The guest row only scales down, from 2 to 1, so neither test sees the defect.
  • Fix: clamp the scaled content (and saved) so the frame fits the work area. Add a host case: initial_content(None, …) relaid from 1 to 2 has its frame inside work_area.

B2. system/compositor/src/session.rs:1225 with desktop/src/window.rs:179: the buffer swap triggers on the first present after a give, not the first present into the given buffer. The compositor cannot tell which buffer a MSG_PRESENT was drawn into, so the design's "until its first present into the new one" is not what the code does.

  • Concrete case: in the main use of the feature, screen scale 2 runs in the terminal.
    • The compositor sends MSG_WINDOW_RESIZED, then answers toybox, and toybox prints.
    • The terminal's loop (system/terminal/src/main.rs, stdout handled before TOKEN_WINDOW) draws that output into its old buffer and presents.
    • The compositor swaps on that present and shows the new, never-drawn (zero) buffer as a black window until the terminal reads Resized and presents again.
  • Any continuously presenting client (doom, snake) hits the same race on every relay.
  • The PR's "Nothing flashes" claim and the Buffers doc ("which it never drew into") are therefore not true as stated.
  • Fix: carry a buffer generation. WindowInfo and ResizeInfo assign it, the client sends it back with PresentRect (all inside toyos-window, so no fork change), and Buffers::presented swaps only on the given generation. Add a host test for a stale-generation present.

B3. system/compositor/desktop/src/budget.rs:16-22: "two buffers per window … for as long as a client that never presents again lives" is false.

  • Buffers::give drops the compositor's handle to a replaced, unpresented buffer. The handle sent with each MSG_WINDOW_RESIZED (session.rs:2299, deliver_with_handle) still sits in a hung client's receive queue and keeps that shared memory alive.
  • The queue holds up to MAX_QUEUED_BATCHES = 16 batches (sdk/abi/src/syscall.rs:1920). A hung window can therefore pin the shown buffer plus 16 queued ones, 8.5 times the budget, before the 17th send fails and the window is dropped.
  • How it is reached: 16 screen scale changes, or 16 virtio display events from dragging the QEMU window, while one client is stopped. With many windows this goes past WINDOW_BUDGET_SHARE.
  • Fix (which also makes "two" true): do not give a window another buffer while its given one is unpresented. Record the wanted size and scale, and give it at the next present into the outstanding buffer. This fits naturally with B2's generation.
  • Add a host test on Buffers: a second give while one is outstanding does not send.

B4. issues/the-desktop-draws-at-the-panels-scale.md:22-28: the track's record says nothing else is left, and that is not true. Its exit includes "under QEMU's virtio-gpu", but no test drives virtio at a scale other than 1, as the PR body admits. The record also leaves out what the PR body lists as remaining: editor, paint, calc and snake draw at 1. editor and paint are ruled to follow #892. calc and snake are "their job" in the design's roast. Root's "No silent debt" needs these in the track's record with an owner and an exit. This is a few lines in the issue; see S3 for the virtio test.

B5. system/compositor/src/session.rs:1310 (with budget.rs:79): a logical request is converted and then refused if it is larger than the screen. This stops doom from starting at supported scales. doom asks for LogicalSize(960, 600) (apps/doom/src/main.rs:38).

  • Concrete cases:
    • At screen scale 2 on the benches' own 1920x1080 panels (an allowed step, logical 960x540), doom's request becomes 1920x1200. That is TooLarge, and doom exits with no window.
    • The same happens at cargo run -- --gop --scale 2, where the 1280x720 mode holds the scale down to 1.5, giving 1440x900.
    • Before this PR doom opened in both cases.
  • winit documents a requested inner size as a request, and other platforms constrain it rather than refuse it.
  • Either clamp a Size::Logical request to the work area (an explicit, said shrink of a toolkit preference, not a silent shrink of a physical request), or have the orchestrator rule that refusing is intended and record that in the issue.

Repository rule

R1. sdk/font/Cargo.toml [features]: the new ttf feature's off arm is linted by no build/clippy.rs shape. Clippy reaches toyos-font only as a dependency of toyos-desktop and the build, both with ttf on. toyos-window, and through it every winit app (doom, for example), builds it with ttf off. Root's rule says changed features must be covered. One shape is enough: -p toyos-font --no-default-features --all-targets with its own target directory. toyos-font does not link toyos, so the host can build it.

Answers to the brief's questions

  1. Protocol.
    • Scale::from_wire is the single validation point. The client refuses an out-of-range scale in WindowInfo (Protocol(MSG_WINDOW_CREATED)) and in ResizeInfo (Event::Close).
    • The compositor refuses an absurd ScaleRequest at Request::decode and drops the connection with an error that names it. screen refuses an unparseable decimal by name, and the compositor answers NotAStep or BelowLeast.
    • The rounding and coordinates are consistent, as stated in the architecture section.
    • One extra rounding sits outside the rule: winit's LogicalSize<f64> is rounded to whole logical pixels before the compositor converts it. That is inherent to a wire that carries integer logical sizes, and it is acceptable.
  2. Relay.
    • Every window whose drawn size differs, or all windows when the scale changes, gets settle. The compositor keeps its own mapping of the shown buffer until the swap, so nothing is used after release.
    • The budget and swap defects are B1–B3.
    • A hung client does not block the compositor: try_send and send_handles fail instead of waiting, and the window is marked dead.
    • The relay uses compositor shm_create (2 MiB-aligned contiguous), not pcidev lends, so LEND_WINDOW is not involved. A refused allocation keeps the window at its old buffer and logs it.
  3. Drawing.
    • The byte-identical 8x16 raster holds by inspection. The codepoint set is identical, and the fit loop now uses fontdue::Font::metrics where it used rasterize's metrics; both take the same metrics path in fontdue 0.9. The panic font reads the same cells.
    • The evidence states the sha256 but records no command or log for it (S4).
    • The icon digest test is unchanged and green.
    • Terminal, filepicker, consent prompt and files re-rasterize when the scale changes.
    • On virtio, mode flip and relayout are one lay_out, and the hardware cursor passes the scaled hot spot to UPDATE_CURSOR.
    • A virtio test at a scale is possible (S3). The hardware hot spot itself cannot be read from a screendump; the host tests and the one call site cover it.
  4. The display port. HELD_ALONE restricts it to toybox. screen, screen WxH and screen scale refuse bad input by name. GOP answers NoModes. The kept file is validated through Scale::step (S2).
  5. Tests.
    • The scale row's claim is sound and well-justified. Enter is pressed only after each screendump, the extents are measured, and the title-bar rows separate a drawn-at-scale window from a scaled one. It only scales down, though (B1).
    • The new window_refusal cases add no boot. Root prefers a host test for a malformed peer message, and the_wire_carries_one_to_four_and_refuses_the_rest already establishes the decode, so they are redundant but harmless.
    • The nine controls are real. I checked the patches and logs: m1–m6 exit 101 by name, m7 and m8b stall at the named step, m9 reds "AbsurdScale(0) decoded wrongly", and each run restores the tree. The patches' blob index predates the virtio-gpu leaves the kernel: the compositor drives it through its claim and follows the host's display #925 merge but applied by context at the logged head.
    • On m8: keep the rescaled || clause. It is the protocol's contract (a window learns its scale from ResizeInfo). With the shipped chrome every window's content changes on any step change: corner scaling, title bar and taskbar heights. No reachable case exists today to test, so the clause must not depend on a geometric coincidence, and it needs no test.
  6. winit 48c7022a.
    • It has upstream shape and a rationale in the commit message.
    • scale_factor() returns the window's scale. ScaleFactorChanged is sent before Resized. Size::Logical and Size::Physical keep their kind on the wire. The ignored InnerSizeWriter matches the accepted design.
    • editor and paint drawing at 1 follows the The package repository's keys: toyos-root signs root 1 offline, and the publisher signs under it alone (stage 1 of apps leaving the tree) #892 ruling. calc and snake request PhysicalSize and ignore scale_factor(), so they would draw small on macOS too. That is app behaviour on every host, not a missing ToyOS platform piece, so it is consistent with "existing Rust just works". It still needs a line in the issue (B4).
  7. Host and readability. I did not run --ci host (see the start of this review); the evidence log at this head shows 86 steps green. Comments are generally concise. The two stale contracts are in B1 and B3. The PR body's "Nothing flashes" and "two per window" need correcting once B2 and B3 are fixed (that is the orchestrator's text). Net change: +2865/−1060, of which tests/ is +270 and docs/NOTICE +38/−67.

Suggestions

  • S1. system/compositor/desktop/Cargo.toml:13 uses toyos-font with default features for font::CELL alone, which pulls fontdue and keymap into the desk crate's build. Use default-features = false.
  • S2. session.rs:198: kept_scale ignores non-UTF-8 content and read errors other than absence without saying anything. The doc promises "refused by name". Log them.
  • S3. A virtio guest check at a scale can share the existing virtio boot: give clipboardcase's compositor args = ["scale=2"].
    • 1280x800 holds it down to 1.5, logged. The VNC resize to 1024x768 then exercises the flip with relay at 1.5.
    • Adding toybox to that image would also drive screen WxH over the port on virtio, which no test does now.
  • S4. Record the command and output of the scratch test that produced 41f922af… in the evidence.
  • S5. session.rs:1720: the compositor writes HOME/scale synchronously in its event loop, so a slow or wedged fileserver stalls the desktop. Consider writing it off the loop.
  • S6. gop_edid_scale assumes no kept scale on the bench's persistent /state. A manual screen scale on a bench would red it. Consider having the row say why if it finds a kept scale.
  • S7. issues/a-test-in-a-guest-built-crate-no-step-reads-runs-nowhere.md: the edit keeps "sdk/window, sdk/font … have neither" and "None of them holds a test today". Since 9d07ad3b7 those crates run in the guest-libraries step, and this PR's own new tests run there. Only loader is still uncovered. Leave this to the orchestrator if you prefer not to touch it.
  • S8. plan.scale is applied only in shipped_parts, so cargo run --scale with a case config is silently ignored. Refuse that combination by name.

Verdict

Five blockers and one repository-rule finding remain:

  • B1: relayout growth past the screen.
  • B2: the swap on a present drawn into the old buffer.
  • B3: the hung-client buffer bound.
  • B4: the issue record.
  • B5: logical requests refused at supported scales.
  • R1: clippy coverage of the ttf-off arm.

The bench rows are for the orchestrator to judge after these are fixed.

SEND BACK

Japabu and others added 2 commits October 14, 2026 23:34
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016UzbDnM9m6nQWhbjGi6Lv1
…des the swap, one buffer outstanding

- B1: a relay at a larger scale fits a normal window's content, and where
  a maximized or snapped one goes back to, to the work area
  (`Chrome::fit`), so no frame passes the screen and the budget's bound
  holds.
- B2: `WindowInfo` and `ResizeInfo` carry the buffer's generation, and
  `MSG_PRESENT` carries it back (`window::PresentRect`); the compositor shows
  the given buffer only on a present of its generation, so a present drawn
  into the old buffer after a resize keeps the old one shown. A generation
  the window never held drops the client by name.
- B3: while a given buffer is unpresented no other is given; the window is
  owed it, and given it at the present that swaps, so a window holds two
  buffers and no more, a hung client included.
- B4: the issue records what is left with owner and exit: virtio-gpu at a
  scale read by log alone, editor and paint, calc and snake at 1.
- B5: a logical request whose frame would pass the work area is fitted to
  it and said (`toyos_desktop::requested`), not refused; a physical one is
  still refused past the screen.
- R1: a clippy shape for `toyos-font` with `ttf` off.
- S3: the virtio boot's compositor starts at `scale=2`, held to 1.75 and
  then 1.5 across the host's resize. S1: toyos-desktop takes toyos-font
  without `ttf`. S2: a kept scale not read is said. S6: `gop_edid_scale`
  names a bench's kept scale. S8: `--scale` beside `--boot-config` is
  refused by name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016UzbDnM9m6nQWhbjGi6Lv1
@Japabu

Japabu commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator Author

Re-review of #931 at cdd9fd3f1: round-1 fixes only

Scope: commit cdd9fd3f1 against c849397d3. The main merge (335927b83) brings in nothing of this PR's. Evidence is pr-931 at f437f0902 (r1/ and bench/). I ran no build or test. The host claim rests on r1/ci-host.log at this head: exit 0, 86 steps, including the new clippy shape at line 7393.

Blockers from round 1

  • B1: resolved. relayout now runs reflow(fit(scaled(r))) on both the content and a non-empty saved (layout.rs:43-50). Chrome::fit keeps the origin and caps the extent at largest_content, the work area less the chrome, and reflow then puts the window back on screen. set_mode for an already-maximized window does not overwrite saved (save_if_normal). a_full_size_window_relaid_at_a_larger_scale_fits_the_work_area takes a Size::Any window and a maximized window's saved from scale 1 to 1.5, 2 and 4, and checks both frames stay inside the work area. m10 (fit removed) reds it. With this fix the budget's "screen-sized at most" statement holds again.
  • B2: resolved.
    • WindowInfo and ResizeInfo carry the generation, and Window::present_damage sends it back in PresentRect.
    • The compositor decodes PresentRect with the existing decode-or-drop handling. Buffers::presented (window.rs:204) behaves as follows:
      • The shown generation counts as Shown, so a stale present keeps the old buffer on screen.
      • Only the given generation swaps.
      • Any other generation is Unknown, and the compositor drops that client with a log line naming the generation (session.rs:1246).
    • A correct client cannot reach Unknown. The pipe keeps order, and the old shown buffer is retired only by that client's own present of the newer generation.
    • No other sender of MSG_PRESENT exists:
      • In the tree, it goes only through Window::present_damage.
      • softbuffer's ToyOS backend (b36854df) calls present().
      • The winit fork uses the unchanged window::Rect API, so it needs no fork change.
    • m11 reds the stale-present and unknown-generation tests.
  • B3: resolved.
    • rebuffer asks owe() first. While a given buffer is unpresented it records that the window is owed one and gives nothing. give asserts the invariant, which is an internal fence and correct to fail loudly.
    • At the swapping present the owed buffer is given, but only if the content size or scale differs from the shown buffer (Buffer now records its scale).
    • Applied to my queue argument: at most one buffer handle is ever in flight to a window. So a hung client pins the shown buffer and the one given buffer, whatever its receive queue holds, and the "two, never more" contract in budget.rs and Buffers is true.
    • m12 (never owe) reds a_second_buffer_is_owed_while_one_is_outstanding.
  • B4: resolved. The issue now lists what is left, owned by the orchestrator, with exits: virtio-gpu at a scale read only from the log (screendump, hardware hot spot, screen WxH), editor and paint (The package repository's keys: toyos-root signs root 1 offline, and the publisher signs under it alone (stage 1 of apps leaving the tree) #892), and calc and snake. The 1.75 and 1.5 steps it states are correct (1280x800 at 2 is 640x400, below 642x446; 1024x768 at 1.75 is 585 wide).
  • B5: resolved. toyos_desktop::requested fits a Size::Logical request whose frame would pass the work area to largest_content, and the compositor logs the requested and granted sizes. A Size::Physical request is unchanged and still refused past the screen. The host case uses doom's 960x600 at 2 on 1920x1080, and m13 reds it.
  • R1: resolved. The -p toyos-font --no-default-features --all-targets shape has its own target directory and ran green.

Controls and suggestions

  • S3: clipboardcase now runs at scale=2, and the_compositor_drives_the_virtio_gpu asserts both held-down lines (1.75, then 1.5 on the flip). r1/guest.log shows 5 of 5, exit 0.
  • Rewritten and new controls: they are real. m3, m10, m11, m12 and m13 each exit 101 on their named tests, with the tree restored. m5's first form failed to compile (exit 101 with no test run). The log says so, and the rewritten m5 reds three layout tests.
  • S4: r1/raster-compare.log compares main's producer with this head's: the sha256 matches and cmp reports them identical.
  • S1, S2, S6 and S8: taken, and they read correctly.

Benches

Benches: bench/ records PASS on the T14 and the AMD bench for the image staged at c849397d3. The orchestrator is judging the re-staged run.

No blocker remains.

LAND

@Japabu

Japabu commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator Author

Bench readings at the round-1 head cdd9fd3f1: gop_edid_scale PASS on the T14 (GOP handle 0, panel 309x173 mm, scale 1.5, a 200x100 window drawn 300x150) and on the AMD bench (handle 1, 309x174 mm, 1.5, 300x150). Readbacks, masked: pr-931 at 9a123357. Base unchanged: main 335927b83 is merged and is still main's head.

@Japabu
Japabu marked this pull request as ready for review October 14, 2026 22:01
@Japabu
Japabu added this pull request to the merge queue Oct 14, 2026
Merged via the queue into main with commit ca0b247 Oct 14, 2026
6 checks passed
@Japabu
Japabu deleted the wt/toyos-scale branch October 14, 2026 22:28
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