Repository navigation
Description
Describe the bug
On a Mac with a 2x built-in screen and a 1x external monitor, the main window doesn't keep its size and position across restarts when the window was saved on one screen and Buzz starts on the other. The window comes back at double or half its size, gets clamped to the screen, and that wrong size is saved for the next launch. From the user's side, Buzz "forgets its window size and position every time it restarts."
Both monitors stay attached the whole time, so this isn't the unplugged-monitor case that #2405 fixes. It's the gap #2405 lists under "Not fixed" and #2732 lists under "Not changed": with a different-scale monitor still attached, the reachable path sends physical pixels that macOS converts at the startup scale.
To Reproduce:
Setup: MacBook built-in Retina screen (3024x1964 px, 1512x982 pt, scale 2) as the primary display, with a Samsung LS34A650U (3440x1440, scale 1) to its right.
- Move the Buzz window onto the built-in screen and size it to 1200x750 pt at (100, 33).
- Quit Buzz (Cmd-Q).
~/Library/Application Support/xyz.block.buzz.desktop/.window-state.jsonnow holds, correctly in physical pixels:{"main":{"width":2400,"height":1500,"x":200,"y":66,"prev_x":2212,"prev_y":-317,"maximized":false,"visible":true,"decorated":true,"fullscreen":false}} - Activate an app with a window on the 1x external (I used a Finder window), so the new Buzz window is created on the external.
- Launch Buzz.
- The window opens at 1512x949 pt at (0, 33): twice the saved size, clamped to the built-in screen's work area. The next quit saves that size, so it stays wrong.
Controls on the same machine:
- If the window was saved on the external and Buzz is relaunched, it comes back exactly (2212, -317, 2323x1089), so saving and the restore path work when the scales match.
- An earlier state file held
934x526at(2212, -317)on the external, which looks like an earlier halving (1868x1052 → 934x526) in the opposite direction.
Frames were sampled every 200 ms with System Events {position, size} of the Buzz window, in points.
Expected behavior:
The window reopens at the size and position it had when Buzz quit, whichever screen Buzz starts on.
Cause
In restore_geometry_from (src-tauri/src/window_state.rs at desktop-v0.7.17), the saved rect in the repro is reachable (it lies inside the built-in screen's physical work area), so relocated is None and the code calls:
window.set_position(position)?; // PhysicalPosition
window.set_size(size)?; // PhysicalSizetao's macOS setters convert physical values using the window's current scale factor, which is the scale of the screen the window was created on (here the 1x external). So 2400x1500 px saved at 2x is sent as 2400x1500 pt. Starting on 2x with a rect saved at 1x halves it instead. The relocated branch already avoids this by sending logical units at a known scale; the reachable branch doesn't.
Suggested fix
- Cheap, covers this repro: when exactly one monitor's physical rect contains the saved header, treat that monitor's scale as the save-time scale and send logical units, as the relocated branch already does (
position.to_logical(scale)/size.to_logical(scale)). In the repro,(200, 66)falls only inside the built-in screen (the external's physical rect starts at x=1512), so that would restore 1200x750 pt at (100, 33). - Complete fix: record the window's scale factor at save time, so overlapping physical rects (for example, a 1x external to the right of a 2x laptop, whose rects both cover x 1512–3024 px) aren't ambiguous. The plugin's file has no field for it, but Buzz can keep the plugin as the only writer of
.window-state.jsonand store{ "main": { "scale": 2.0 } }in a small file of its own. Write it from Buzz'sRunEvent::Exithandler (the plugin's own Exit save runs just before it, and the window still exists) and onCloseRequested. On restore, convert the saved rect with that scale and send logical units on macOS and Linux. macOS logical coordinates are one global point space, so this is unambiguous there. With no scale file present (older state), fall back to option 1, then to today's behavior.
Either way, a regression test on restore_request can check that a reachable rect saved at 2x comes back in logical units at scale 2 when the startup scale is 1, and the other way round.
Environment (please complete the following information):
- OS: macOS 27.2 (arm64)
- Version: Buzz 0.7.17 (
squareup/buzz-releasesv0.7.17, built fromblock/buzz-app@desktop-v0.7.17, 5a7f495)
Additional context
Related: #2405 (merged, unplugged monitor with one monitor left), #2732 (draft, several monitors left). Neither changes the reachable path.
Activity