What's wrong
wait_for_terminal_resize loops only while stty size is exactly 24 80:
while ((SECONDS < deadline)) && [[ $(stty size 2>/dev/null) == "24 80" ]]; doThat assumes every terminal allocates the pty at the classic 24x80. Not all do. On foot, the pty can come up at foot's own default geometry instead, so the condition is false on the very first iteration and the function returns without waiting at all. ttfx then measures that intermediate grid and paints a small canvas into the corner of a window that is about to go fullscreen.
I logged stty size on entry across several runs on the same machine:
| Run | pty size on entry | Guard behaviour |
|---|---|---|
| 1 | 24 80 | waits correctly |
| 2 | 15 48 | returns after 0 ms |
So the failure is intermittent on a single machine, which matches the "sometimes it's fine" reports.
The fix
Wait for the size to stop changing rather than comparing it against a hardcoded default. That holds for any terminal regardless of its initial geometry, and still skips the 24 80 starting value. The deadline goes from 2 s to 5 s as a safety net; in practice the wait returns in ~300 ms.
Testing
Verified on Omarchy 4.0.3-1, Hyprland 0.56.2, foot 1.28.0-1, ttfx 0.3.2-1, on a dual-monitor setup — eDP-1 1920x1080 @ scale 1.6 (grid 20x83) and HDMI-A-1 1920x1080 @ scale 1 (grid 32x137).
Rather than judging by eye, I captured the running screensaver with grim -o <monitor> and measured the bounding box of non-black pixels against the character grid:
| Scaled panel (20x83 grid) | Before | After |
|---|---|---|
| Columns covered (of 83) | a few, top-left | 0.0 – 82.4 |
| Rows covered (of 20) | ~3, top | 0.1 – 19.1 |
| Content centre | top-left corner | (954, 520) vs ideal (960, 540) |
Both monitors now size correctly, and the screensaver has run normally since.
Scope
Deliberately minimal — only the wait. I also tried a variant that watches stty size while ttfx runs and restarts it if the geometry changes, so a late resize self-corrects regardless of timing. Across the verification runs it never had to fire once this wait was in place, so it does not seem necessary here, but I'm happy to add it if you'd rather be defensive about compositors that resize later than Hyprland.
Context and fuller measurements in #15253, which suspected this function but hadn't pinned down why it fails.
🤖 Generated with Claude Code
[Low impact] The change is limited to Omarchy screensaver startup.
No blocking behavior defect was found, but the explicit comparison-style requirement must be satisfied before merging.
Findings
Summary
Updates
wait_for_terminal_resizeto wait for an unchanged terminal size instead of returning at the first non-default size.ttfx.Reviews (1) · Last reviewed commit: "Wait for the terminal size to settle bef..." · Reviewed by Greptile