Skip to content

menu bar: stop the countdown redraw while the displays sleep - #137

Open
salvcy wants to merge 2 commits into
krishhgg:mainfrom
salvcy:perf/countdown-display-sleep
Open

salvcy wants to merge 2 commits into
krishhgg:mainfrom
salvcy:perf/countdown-display-sleep

Conversation

@salvcy

@salvcy salvcy commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

What changes

The menu bar countdown redraws every second for the whole session, also when every display is asleep and nobody can see it. The spec's event table already says the redraw costs one wake per second "while active and visible", but only a closed lid stopped it. With the display set to sleep after a few idle minutes, most of a session spent away from the Mac is spent with the screen off.

The countdown now stops while the displays sleep:

  • A small DisplaySleepObserver, alive as long as the app, passes NSWorkspace's screensDidSleep and screensDidWake to the session manager. Nothing runs between the two. It also reads the displays once at launch, so a session resumed while they sleep starts with the redraw off.
  • While the displays sleep, the 1 Hz timer is off. A wake redraws at once, so the countdown is right the moment the screen lights up, and restarts the timer unless the lid keeps it stopped.
  • A lid open restarts it as before. Opening the lid wakes the displays, so it doesn't wait for their wake to arrive.

On my Mac (M5 Pro, macOS 27.0.1, lid closed on an external display with #135) the redraw cost about 0.33% of a core with the display asleep: 0.19 to 0.21 s of CPU a minute over five one-minute windows, about what it costs with the display on. With this change, three such minutes cost 0.03 s, 0 s and 0 s (lid open, built-in display; details in the real-Mac report). Small per minute, but it ran for as long as the session did.

With #135, a MacBook closed on an external display runs its countdown, so this also covers that monitor going to sleep.

How it was tested

  • DisplaySleepTests: a display sleep stops the redraw and a wake redraws at once with the right time; a wake with the lid closed leaves it stopped; a lid open with the displays asleep redraws at once; a session started, or resumed at launch, while the displays sleep starts redrawing at the wake; a wake with no session arms nothing; the observer reports the state at launch, then turns the two workspace notifications into the calls.
  • The full suite, and the release build with -warnings-as-errors.
  • A new item under "Menu bar" in docs/releasing.md.

Real-Mac evidence

In a comment for the head commit.

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge, with the previous startup issue fixed and no new actionable issues found.

Summary

Stops the menu bar countdown redraw while displays sleep and redraws immediately on wake.

  • The menu countdown pauses in display sleep and refreshes when screens wake.

Reviews (2) · Last reviewed commit: "Menu bar: from Greptile's review, read a..." · Reviewed by Greptile

The countdown redrew every second for the whole session, also with every
display asleep, where nobody can see it. The spec's event table already
says the redraw costs one wake per second while active and visible, but
only a closed lid stopped it. With the display set to sleep after a few
idle minutes, that is most of a session spent away from the Mac.

A DisplaySleepObserver, alive as long as the app, passes NSWorkspace's
screensDidSleep and screensDidWake to the session manager. While the
displays sleep the 1 Hz timer is off. A wake redraws at once and starts
it again, unless the lid keeps it stopped. A lid open starts it as
before, without waiting for the wake: opening the lid wakes the displays.
@salvcy salvcy changed the title Menu bar: stop the countdown redraw while the displays sleep menu bar: stop the countdown redraw while the displays sleep Oct 21, 2026
@salvcy

salvcy commented Oct 21, 2026

Copy link
Copy Markdown
Contributor Author

Real-Mac report for 8e2958801ecba1069efd8073eb20aa9ff52b84ca.

Build. A clean checkout of that commit, no other pull requests applied, installed with ./scripts/install.sh. The installer quit the running app and installed the new one; I opened it at 20:51:05Z.

$ git rev-parse HEAD
8e2958801ecba1069efd8073eb20aa9ff52b84ca
$ git status --short
$

Mac.

ProductName:		macOS
ProductVersion:		27.0.1
BuildVersion:		26A434
Mac17,9
Apple M5 Pro

Lid open, the built-in display only, on battery (99 to 100%). Times are UTC; pmset -g log prints local time, UTC-5. CPU is the app's own CPU time from ps -o time= -p <pid> across each window.

Checks.

  1. Displays asleep stop the countdown redraw (the item this PR adds under "Menu bar"). Pass.

    Time Step Result
    20:52:11 session started, lid open
    20:52:19 to 20:53:19 display held on (caffeinate -u), countdown ticking 0.25 s of CPU in 60 s, 0.42% of a core
    20:53:19 pmset displaysleepnow displays asleep: countdown redraw stopped
    20:53:22 to 20:56:36 three 60 s windows, display off and no input 0.03 s, 0 s, 0 s
    20:56:36 display woken (caffeinate -u) display awake: countdown redraw restarted
    21:14:09 a second session, 5 minutes, ending 21:19:04
    21:14:15 pmset displaysleepnow displays asleep: countdown redraw stopped
    21:16:47 display woken with a key press display awake: countdown redraw restarted; the countdown showed the time left (about 2:17) the moment the screen lit, not the 4:49 it had when the screen went off

    For comparison, the build before this change (Lid: a lid closed on an external display counts as open #135's head, lid closed on an external display) spent 0.19 to 0.21 s of CPU a minute with the display asleep, over five one-minute windows: the same as with the display on.

  2. A session with the lid open holds the app near idle (the existing "Menu bar" item). Pass: 0.42% of a core with the display on, measured from the CPU time over 60 s instead of %cpu samples, and the countdown changed every second.

  3. Sleep: pmset -g log | grep -E ' (Sleep|Wake|DarkWake) ' has no line between 15:49 and 16:18 local (20:49 to 21:18Z). SleepDisabled read 1 during the sessions and 0 after each ended.

insomnia.log, 20:49 to 21:17:30Z
2026-10-21T20:51:05Z [info] insomnia: startup lid state open
2026-10-21T20:51:05Z [info] insomnia: startup battery 100%
2026-10-21T20:51:05Z [info] insomnia: startup thermal state nominal
2026-10-21T20:51:05Z [info] insomnia: launched, version 0.1.0 (1)
2026-10-21T20:51:05Z [info] insomnia: status item installed: window {{0, 0}, {16, 0}} button {{0, 0}, {32, 22}} host {{0, 0}, {32, 22}} fitting {32, 22} length 32.0
2026-10-21T20:51:05Z [info] insomnia: status item installed: visible true alpha 1.0 occlusion 8192 screen Built-in Retina Display {{0, 0}, {1512, 982}} safe {32.0,0.0,0.0,0.0} button hidden false alpha 1.0 host hidden false alpha 1.0
2026-10-21T20:51:05Z [info] insomnia: wifi interface en0
2026-10-21T20:51:05Z [info] insomnia: reconcile: no session, nothing to restore
2026-10-21T20:51:06Z [info] insomnia: status item after 1s: window {{1265, 946}, {48, 39}} button {{0, 0}, {32, 22}} host {{0, 0}, {32, 22}} fitting {32, 22} length 32.0
2026-10-21T20:51:06Z [info] insomnia: status item after 1s: visible true alpha 1.0 occlusion 8194 screen Built-in Retina Display {{0, 0}, {1512, 982}} safe {32.0,0.0,0.0,0.0} button hidden false alpha 1.0 host hidden false alpha 1.0
2026-10-21T20:52:11Z [info] insomnia: session started until 2026-10-21T21:02:06Z (600s requested)
2026-10-21T20:52:11Z [info] insomnia: lid observer started (lid open)
2026-10-21T20:52:11Z [error] insomnia: notification authorization failed: Notifications are not allowed for this application
2026-10-21T20:52:11Z [info] insomnia: notification authorization denied
2026-10-21T20:52:11Z [info] insomnia: power monitor started (battery 100%, on battery, thermal nominal)
2026-10-21T20:52:11Z [error] insomnia: notification authorization failed: Notifications are not allowed for this application
2026-10-21T20:52:11Z [info] insomnia: wifi interface en0
2026-10-21T20:52:11Z [info] insomnia: network failover started (wifi en0)
2026-10-21T20:53:19Z [info] insomnia: displays asleep: countdown redraw stopped
2026-10-21T20:56:36Z [info] insomnia: display awake: countdown redraw restarted
2026-10-21T20:56:43Z [info] insomnia: battery 99% on battery
2026-10-21T20:56:44Z [info] insomnia: session end (quit)
2026-10-21T20:56:44Z [info] insomnia: sleep restored
2026-10-21T20:56:44Z [info] insomnia: notify: Session ended - Insomnia quit. Sleep is back to normal.
2026-10-21T20:56:45Z [info] insomnia: startup lid state open
2026-10-21T20:56:45Z [info] insomnia: startup battery 99%
2026-10-21T20:56:45Z [info] insomnia: startup thermal state nominal
2026-10-21T20:56:45Z [info] insomnia: launched, version 0.1.0 (1)
2026-10-21T20:56:45Z [info] insomnia: status item installed: window {{0, 0}, {16, 0}} button {{0, 0}, {32, 22}} host {{0, 0}, {32, 22}} fitting {32, 22} length 32.0
2026-10-21T20:56:45Z [info] insomnia: status item installed: visible true alpha 1.0 occlusion 8192 screen Built-in Retina Display {{0, 0}, {1512, 982}} safe {32.0,0.0,0.0,0.0} button hidden false alpha 1.0 host hidden false alpha 1.0
2026-10-21T20:56:45Z [info] insomnia: wifi interface en0
2026-10-21T20:56:45Z [info] insomnia: reconcile: no session, nothing to restore
2026-10-21T20:56:46Z [info] insomnia: status item after 1s: window {{1265, 946}, {48, 39}} button {{0, 0}, {32, 22}} host {{0, 0}, {32, 22}} fitting {32, 22} length 32.0
2026-10-21T20:56:46Z [info] insomnia: status item after 1s: visible true alpha 1.0 occlusion 8194 screen Built-in Retina Display {{0, 0}, {1512, 982}} safe {32.0,0.0,0.0,0.0} button hidden false alpha 1.0 host hidden false alpha 1.0
2026-10-21T21:14:09Z [info] insomnia: session started until 2026-10-21T21:19:04Z (300s requested)
2026-10-21T21:14:09Z [info] insomnia: lid observer started (lid open)
2026-10-21T21:14:09Z [error] insomnia: notification authorization failed: Notifications are not allowed for this application
2026-10-21T21:14:09Z [info] insomnia: notification authorization denied
2026-10-21T21:14:09Z [info] insomnia: power monitor started (battery 99%, on battery, thermal nominal)
2026-10-21T21:14:09Z [info] insomnia: wifi interface en0
2026-10-21T21:14:09Z [info] insomnia: network failover started (wifi en0)
2026-10-21T21:14:15Z [info] insomnia: displays asleep: countdown redraw stopped
2026-10-21T21:16:47Z [info] insomnia: display awake: countdown redraw restarted
2026-10-21T21:16:58Z [info] insomnia: battery 98% on battery
2026-10-21T21:17:21Z [info] insomnia: session end (quit)
2026-10-21T21:17:21Z [info] insomnia: sleep restored
2026-10-21T21:17:21Z [info] insomnia: notify: Session ended - Insomnia quit. Sleep is back to normal.
2026-10-21T21:17:21Z [info] insomnia: startup lid state open
2026-10-21T21:17:21Z [info] insomnia: startup battery 98%
2026-10-21T21:17:21Z [info] insomnia: startup thermal state nominal
2026-10-21T21:17:21Z [info] insomnia: launched, version 0.1.0 (1)
2026-10-21T21:17:21Z [info] insomnia: status item installed: window {{0, 0}, {16, 0}} button {{0, 0}, {32, 22}} host {{0, 0}, {32, 22}} fitting {32, 22} length 32.0
2026-10-21T21:17:21Z [info] insomnia: status item installed: visible true alpha 1.0 occlusion 8192 screen Built-in Retina Display {{0, 0}, {1512, 982}} safe {32.0,0.0,0.0,0.0} button hidden false alpha 1.0 host hidden false alpha 1.0
2026-10-21T21:17:21Z [info] insomnia: wifi interface en0
2026-10-21T21:17:21Z [info] insomnia: reconcile: no session, nothing to restore
2026-10-21T21:17:22Z [info] insomnia: status item after 1s: window {{1265, 946}, {48, 39}} button {{0, 0}, {32, 22}} host {{0, 0}, {32, 22}} fitting {32, 22} length 32.0
2026-10-21T21:17:22Z [info] insomnia: status item after 1s: visible true alpha 1.0 occlusion 8194 screen Built-in Retina Display {{0, 0}, {1512, 982}} safe {32.0,0.0,0.0,0.0} button hidden false alpha 1.0 host hidden false alpha 1.0
The measuring scripts' output
20:50:30Z waiting for a session start
20:52:12Z session running, app pid 71017; display kept on for 60 s
20:53:19Z display on: cpu .25s in 60 s
20:54:25Z display asleep, window 1: cpu .03s in 60 s
20:55:31Z display asleep, window 2: cpu 0s in 60 s
20:56:36Z display asleep, window 3: cpu 0s in 60 s
20:56:36Z waking the display
20:56:44Z ending the session: quitting and reopening the app
20:56:45Z  SleepDisabled 0
20:56:52Z done
21:12:36Z waiting for a session start
21:14:10Z session started; waiting for 10 s without input
21:14:15Z displaysleepnow sent; the user wakes it
21:16:48Z woken
pmset samples every 5 s during the first session (SleepDisabled, Battery Power powermode)
15:50:30 SleepDisabled 0 powermode 0
15:50:35 SleepDisabled 0 powermode 0
15:50:40 SleepDisabled 0 powermode 0
15:50:45 SleepDisabled 0 powermode 0
15:50:50 SleepDisabled 0 powermode 0
15:50:55 SleepDisabled 0 powermode 0
15:51:00 SleepDisabled 0 powermode 0
15:51:05 SleepDisabled 0 powermode 0
15:51:10 SleepDisabled 0 powermode 0
15:51:15 SleepDisabled 0 powermode 0
15:51:20 SleepDisabled 0 powermode 0
15:51:25 SleepDisabled 0 powermode 0
15:51:30 SleepDisabled 0 powermode 0
15:51:35 SleepDisabled 0 powermode 0
15:51:40 SleepDisabled 0 powermode 0
15:51:45 SleepDisabled 0 powermode 0
15:51:50 SleepDisabled 0 powermode 0
15:51:55 SleepDisabled 0 powermode 0
15:52:00 SleepDisabled 0 powermode 0
15:52:05 SleepDisabled 0 powermode 0
15:52:10 SleepDisabled 1 powermode 0
15:52:15 SleepDisabled 1 powermode 0
15:52:20 SleepDisabled 1 powermode 0
15:52:25 SleepDisabled 1 powermode 0
15:52:31 SleepDisabled 1 powermode 0
15:52:36 SleepDisabled 1 powermode 0
15:52:41 SleepDisabled 1 powermode 0
15:52:46 SleepDisabled 1 powermode 0
15:52:51 SleepDisabled 1 powermode 0
15:52:56 SleepDisabled 1 powermode 0
15:53:01 SleepDisabled 1 powermode 0
15:53:06 SleepDisabled 1 powermode 0
15:53:11 SleepDisabled 1 powermode 0
15:53:16 SleepDisabled 1 powermode 0
15:53:21 SleepDisabled 1 powermode 0
15:53:26 SleepDisabled 1 powermode 0
15:53:31 SleepDisabled 1 powermode 0
15:53:36 SleepDisabled 1 powermode 0
15:53:41 SleepDisabled 1 powermode 0
15:53:46 SleepDisabled 1 powermode 0
15:53:51 SleepDisabled 1 powermode 0
15:53:56 SleepDisabled 1 powermode 0
15:54:01 SleepDisabled 1 powermode 0
15:54:06 SleepDisabled 1 powermode 0
15:54:11 SleepDisabled 1 powermode 0
15:54:16 SleepDisabled 1 powermode 0
15:54:21 SleepDisabled 1 powermode 0
15:54:26 SleepDisabled 1 powermode 0
15:54:32 SleepDisabled 1 powermode 0
15:54:37 SleepDisabled 1 powermode 0
15:54:42 SleepDisabled 1 powermode 0
15:54:47 SleepDisabled 1 powermode 0
15:54:52 SleepDisabled 1 powermode 0
15:54:57 SleepDisabled 1 powermode 0
15:55:02 SleepDisabled 1 powermode 0
15:55:07 SleepDisabled 1 powermode 0
15:55:12 SleepDisabled 1 powermode 0
15:55:17 SleepDisabled 1 powermode 0
15:55:22 SleepDisabled 1 powermode 0
15:55:27 SleepDisabled 1 powermode 0
15:55:32 SleepDisabled 1 powermode 0
15:55:37 SleepDisabled 1 powermode 0
15:55:42 SleepDisabled 1 powermode 0
15:55:47 SleepDisabled 1 powermode 0
15:55:52 SleepDisabled 1 powermode 0
15:55:57 SleepDisabled 1 powermode 0
15:56:02 SleepDisabled 1 powermode 0
15:56:07 SleepDisabled 1 powermode 0
15:56:12 SleepDisabled 1 powermode 0
15:56:17 SleepDisabled 1 powermode 0
15:56:22 SleepDisabled 1 powermode 0
15:56:27 SleepDisabled 1 powermode 0
15:56:33 SleepDisabled 1 powermode 0
15:56:38 SleepDisabled 1 powermode 0
15:56:43 SleepDisabled 1 powermode 0
15:56:48 SleepDisabled 0 powermode 0
pmset -g log, display on/off
2026-10-21 15:53:19 -0500 Notification         Display is turned off
2026-10-21 15:56:36 -0500 Notification         Display is turned on
2026-10-21 15:56:49 -0500 Notification         Display is turned off
2026-10-21 16:11:06 -0500 Notification         Display is turned on
2026-10-21 16:14:15 -0500 Notification         Display is turned off
2026-10-21 16:16:47 -0500 Notification         Display is turned on

Not run.

  • An external display going to sleep with this build: this run used the built-in display. The baseline above was on an external display.
  • A locked screen with the display still on: the countdown keeps redrawing there, as before; this change only covers displays asleep.
  • macOS 26.

@salvcy
salvcy marked this pull request as ready for review October 21, 2026 21:21
… sleep

The observer only heard of later changes, and the state started as awake.
An app opened again while the displays slept, by a relaunch over SSH or
after a force quit, resumed its session with the countdown redrawing
until the next display event. The observer now reads the displays once
when it starts, every online one asleep (CGDisplayIsAsleep), and reports
that before the launch reconcile, so a resumed session starts with the
redraw off.
@krishhgg

Copy link
Copy Markdown
Owner

Thank you for this. Your head commit 4a9bdb1 is merged unchanged into #145, together with five other menu bar pull requests that change the same files. When #145 merges, GitHub marks this pull request as merged too, with your commits and your authorship.

Please don't push to this branch from now on. A new commit wouldn't be in #145, so this pull request would stay open instead of showing as merged. If you want to change something, comment here and I'll add it to #145.

#145's description lists the conflicts resolved in its merge commits and the two maintainer fixes after them.

🤖 Generated with Claude Code

krishhgg added a commit that referenced this pull request Oct 24, 2026
The status item runs its own 1 Hz tick for the projected countdown while
a start is in flight. #137 stopped the manager's redraw while every
display sleeps but not this one. The manager's displaysAsleep is now
observable; the status item stops its tick while it is true and redraws
the projection when a display wakes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
krishhgg added a commit that referenced this pull request Oct 27, 2026
Lands #99 at e738536, by 0xRafie.

Conflict with #137 and the maintainer's time change fix, resolved here:
each added an app-lifetime object to InsomniaApp, the display sleep and
time change observers and #99's session command watcher. All three are
kept. The observers start first; the watcher starts after the launch
reconcile, as in #99.
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.

2 participants