Skip to content
This repository was archived by the owner on Nov 8, 2026. It is now read-only.

Commit 37c8cd8

Browse files
techmoreclaude
andauthored
Compositor: rounded windows that hug the MacBook display, one token-driven radius (#641)
* Compositor: rounded windows that hug the display, token-driven radius (rebased from #623 without build artifacts); screenshots skip the screen-corner mask Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Screenshots skip the screen-corner mask; move tests to the end of screenshot_pixels; nested output-change cancel Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
1 parent fc2c369 commit 37c8cd8

15 files changed

Lines changed: 1491 additions & 384 deletions

File tree

‎mseries/BUG-FIX-ROADMAP.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ Ranked by how often the owner hits it, how small the fix is, and what it depends
180180
| P2 | Ambient keyboard lighting has a successful sensor/API check, but lock/lid transitions and suspend/resume are not qualified. | Verify that the helper leaves the light alone while locked or closed, then resumes safely after unlock/open and suspend. | Needs interactive check |
181181
| P2 | The transformed desktop can play through the Apple speaker safety/DSP chain using packages already available in Ubuntu resolute, but the factory image does not install them or the matching PipeWire LV2 plugin. Correction 2026-10-13: Ubuntu does ship that plugin, in `libspa-0.2-modules-extra`, so it can be installed rather than built; untested with the Asahi chain. AVD firmware and its VA-API bridge are also absent. | Package the tested audio chain into the Ubuntu image without bypassing speakersafetyd; separately identify compatible AVD kernel, firmware, userspace sources, and licensing. | Image packaging gap |
182182
| P2 | Wi-Fi firmware provisioning and resume recovery are present in the image layer but lack a fresh end-to-end test on an M1 Pro booted from that image. | Confirm firmware source, connect, suspend/resume repeatedly, and capture service/kernel logs from the factory image. | Needs image test |
183-
| P2 | App corners do not yet match the MacBook Pro's built-in panel curve. The user-session override had a 20 logical px display mask and a 12 logical px Gala window clip, while the GTK CSD theme used 20 px. On 2026-10-22 the Gala clip override was changed to 20 px and the systemd user manager was reloaded; it applies next Gala session. The source now uses a continuous n=4 superellipse approximation for the screen mask and Gala window clip. The physical panel and curve have not been visually checked. | Compare a clean full-screen capture against the panel edge and calibrate one logical-pixel radius and continuous curve for the built-in display, GTK CSD, and Gala clipping. Qualify native GTK, libadwaita, third-party windows, maximized/fullscreen windows, dialogs, popovers, shadows and external monitors. Keep the desktop usable at the panel edge and leave external displays square. Apple HIG does not prescribe one numeric radius for every app window and distinguishes continuous from circular corner styles: [Windows](https://developer.apple.com/design/human-interface-guidelines/windows), [UICornerCurve.continuous](https://developer.apple.com/documentation/uikit/uicornercurve/continuous). | Shared continuous curve approximation added; hardware qualification open |
183+
| P2 | App corners do not yet match the MacBook Pro's built-in panel curve. The user-session override had a 20 logical px display mask and a 12 logical px Gala window clip, while the GTK CSD theme used 20 px. On 2026-10-22 the Gala clip override was changed to 20 px and the systemd user manager was reloaded; it applies next Gala session. The source now uses a continuous n=4 superellipse approximation for the screen mask and Gala window clip. The physical panel and curve have not been visually checked. | Compare a clean full-screen capture against the panel edge and calibrate one logical-pixel radius and continuous curve for the built-in display, GTK CSD, and Gala clipping. Qualify native GTK, libadwaita, third-party windows, maximized/fullscreen windows, dialogs, popovers, shadows and external monitors. Keep the desktop usable at the panel edge and leave external displays square. Apple HIG does not prescribe one numeric radius for every app window and distinguishes continuous from circular corner styles: [Windows](https://developer.apple.com/design/human-interface-guidelines/windows), [UICornerCurve.continuous](https://developer.apple.com/documentation/uikit/uicornercurve/continuous). | Shared continuous curve approximation added; hardware qualification open. **2026-10-27:** the Rust compositor now clips every window to the display curve (rebased PR #555: window clip shader, hug-the-display rule, popups, panels, external outputs square) and draws the screen mask from the same tokens, replacing the provisional mask; one radius (`corners.screen`, now 20 like the earlier session overrides) drives both, `PANTHEON_SCREEN_CORNER_RADIUS` still overrides it. Not installed; needs visual calibration on the built-in display |
184184
| P2 | The blue and red palette still feels harsh in use, especially for accents and error/destructive states, despite the existing softened GTK overrides. Current overrides do not necessarily reach every app or toolkit. | Review the reds and blues on the M1 Pro display and move them toward a calmer pastel treatment where appropriate. Apply through shared theme/system settings so native and installed apps follow when supported; check text and icon contrast, selection/focus visibility, and error/warning/success distinction. Record toolkit-specific exceptions rather than assuming one GTK stylesheet covers every app. | New; needs palette review |
185185
| P2 | The network usage/speed readouts in the network menu and panel do not always recover correctly when Ethernet is present across suspend/resume or reboot. | Reproduce separately for suspend/resume and reboot with Ethernet connected. Compare the network indicator and panel readout with interface counters before sleep, immediately after wake/startup, and after the interface settles; inspect sampling timer/baseline resets and interface re-enumeration. Verify both displayed rates and the history graph recover without stale spikes or a panel restart. | New; needs reproduction |
186186
| P2 | The network speed readout gives no visual cue when traffic is approaching a saturated link. The owner wants a muted pastel red-orange hue at that point. Current counters aggregate physical-interface throughput but do not determine link capacity or upstream service congestion. | Define “saturated” in measurable terms (for example, sustained utilization near the negotiated interface rate, with a short hold and hysteresis to prevent flicker). Decide whether the cue applies per-interface or to the aggregate readout, then test on Wi-Fi and Ethernet. Use a soft red-orange that remains legible and distinguishable from error states; document that interface utilization cannot by itself prove an ISP/WAN bottleneck. | New; needs saturation definition |

‎mseries/SDK-CORNERS.md‎

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ Logical pixels, shared by SDK apps and meant to be read by the compositor:
1919

2020
| Token | Value | Used for |
2121
|---|---:|---|
22-
| `screen` | 14 | The built-in display's physical corner (the mask) |
23-
| `window` | 12 | Floating window outline and title band |
22+
| `screen` | 20 | The built-in display's physical corner (the mask) |
23+
| `window` | 20 | Floating window outline and title band |
2424
| `popover` | 10 | Menus, popovers |
2525
| `card` | 12 | Cards, editors, the Transcript bubbles' parent shapes |
2626
| `bubble` | 14 | Chat bubbles |
@@ -33,7 +33,12 @@ Logical pixels, shared by SDK apps and meant to be read by the compositor:
3333
has a larger radius than the display (`window <= screen`; tested). Maximized and fullscreen SDK windows have radius 0 in their own
3434
stylesheet because an app cannot know whether it is on the built-in display or a square external one; the compositor decides (below).
3535

36-
## 3. Compositor contract (not built in the Rust compositor)
36+
> **2026-10-27:** `screen` and `window` are now 20, the value the owner had already set by hand for the display mask, the
37+
> window clip and the GTK theme on 2026-10-22 (the tokens said 14 and 12, so three different radii were in use). The
38+
> compositor draws the screen mask and clips windows from these same tokens; `PANTHEON_SCREEN_CORNER_RADIUS` (0-128,
39+
> `0` = off) overrides `screen` for one session. 20 is still a calibration starting point, not a measured panel radius.
40+
41+
## 3. Compositor contract (built in the Rust compositor 2026-10-23; see section 7)
3742

3843
The compositor is the only layer that can make every app, including third-party and client-side-decorated windows, follow the curve.
3944
It should read the same `corners` tokens and implement:
@@ -72,3 +77,27 @@ number in `tokens.json` and this file. The current 14 came from eye-tuning (rais
7277
3. Calibration pass and a record of the result.
7378
4. Qualify native GTK, libadwaita, Electron/Chromium and X11 windows, CSD and SSD, maximized, fullscreen, dialogs and popovers, and an
7479
external display, with screenshots.
80+
81+
## 7. Receipt: compositor implementation (2026-10-23, branch `claude/compositor-corners`)
82+
83+
What it does (`rust/pantheon-compositor`, `src/corners.rs` = numbers and policy, `src/corner_render.rs` = drawing):
84+
85+
- Reads `corners` from `rust/msdk/data/tokens.json` at start-up through the `msdk` crate (`CornerTokens::load`), including `style`.
86+
- **Screen mask:** four premultiplied-black bitmaps (not a shader) in the four display corners, front-most after the pointer, so above windows, dock, panels and the lock screen; built-in output only.
87+
- **Window clip:** each toplevel's surface elements that touch a corner box are drawn through a GLES texture shader (signed distance of a circle or of a superellipse, antialiased over one pixel). Only the pixels inside the four corner boxes of the *geometry* rectangle change, so client-drawn shadows outside the geometry, the interior and the client's size are untouched. Opaque regions exclude the corner boxes (so the corners blend), and clipped elements are never scanned out directly. `circular` is exponent 2; `continuous` is exponent 3 with the corner box 1.2x the radius (`CONTINUOUS_EXPONENT`, `CONTINUOUS_EXTENT` in `corners.rs`; eye-tuning, like the radii, is the owner's call).
88+
- **Hug the display (geometry based, no placement state):** a toplevel corner within 1 px of a display corner uses `screen`; a corner on a screen edge but not a display corner is square; every other corner uses `window`. This covers maximized (with a top bar the top corners sit under the bar, so they are square and the bottom ones follow the display), fullscreen, tiled halves and quarters, and a floating window dragged into a corner. Radii are capped at half the shorter side.
89+
- **Popups and menus:** `popover` on all four corners (X11 override-redirect windows are treated as popups). Dialogs are toplevels, so `window`.
90+
- **Panels and the dock:** a corner is rounded (`window`) only if it touches no screen edge. A full-width top bar therefore stays square and a floating dock is rounded.
91+
- **Per-output policy:** only the built-in output is rounded; every other output is square (no clipper, no mask, no cost). Built-in means the connector names the display configuration already treats as the laptop panel (`eDP-*`, plus `LVDS-*`, `DSI-*`); the nested development window (`winit`) counts as built-in so the corners can be seen without a second display.
92+
93+
Verified (2026-10-23):
94+
95+
- `cargo fmt --check`, `cargo clippy --all-targets -D warnings` (default features and `native-drm`) clean; `cargo test -p pantheon-compositor --features native-drm`: 183 + 19 passed, 28 of them new (which corner gets which radius for floating, maximized with and without the bar, fullscreen, tiled halves and quarters with and without the bar, one pixel of slack, external outputs, popups, bar and dock, tiny surfaces, token loading, the distance function, the mask bitmap and its area).
96+
- The nested compositor starts and draws, in a private Xvfb with its own D-Bus and runtime directory (software GL, not the Asahi GPU): a plain GTK 3 window gets 12 px corners against the wallpaper; the same window maximized and fullscreen is cut at the display curve, flush with the black mask; no shader error in the log. The shader first applied to nothing on the nested output because Winit renders a vertically flipped frame (`Transform::Flipped180`); `gl_FragCoord` is now mapped for `Normal` and `Flipped180` frames. Rotated frames are drawn unclipped.
97+
98+
Not verified:
99+
100+
- **Visual acceptance on the built-in display** (curve match against the glass, light/dark, the 14 and 12 values, the continuous shape): needs the owner's eyes. Nothing was installed and nothing was loaded: the owner's running session still has the old compositor binary; the new build lives only in the shared cargo target (`debug/pantheon-compositor`) of the worktree `~/Projects/mseries-corners`.
101+
- The **native DRM path on the Asahi GPU**: it compiles (`MultiRenderer` hands out the render device's GLES renderer and frame) but was not run there. It assumes the native frame is `Normal` (as in Smithay's DRM compositor) and that the clip shader works on the Honeykrisp driver; if the shader fails to compile the log says so and windows stay square.
102+
- Surfaces that are **not** clipped: Snap Assist thumbnails and the workspace/overview gesture previews (scaled copies), and X11 windows are clipped only through the same Wayland surface elements XWayland provides (not separately qualified). Direct scanout of a clipped window is intentionally off while it has rounded corners. Chromium/Electron, libadwaita, CSD with shadows, dialogs and popovers on real apps are not yet qualified (work item 4); a window that straddles two outputs is clipped per output with that output's rules.
103+
- Damage when only the radii change without a geometry change (a token edit) is not tracked; the radii are read once at start-up, so a restart applies a changed token.

‎rust/Cargo.lock‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎rust/msdk/data/tokens.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@
7070
"bubble_alpha": 0.22
7171
},
7272
"corners": {
73-
"screen": 14,
74-
"window": 12,
73+
"screen": 20,
74+
"window": 20,
7575
"popover": 10,
7676
"card": 12,
7777
"bubble": 14,

‎rust/pantheon-compositor/Cargo.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ wayland-server = "0.31.14"
4545
zbus = "=5.19.0"
4646
gio = "0.22"
4747
xcursor = "0.3"
48+
msdk = { path = "../msdk" }
4849
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "webp"] }
4950

5051
[dependencies.smithay]

0 commit comments

Comments
 (0)