Skip to content

Sync XDR state after wake and restore native brightness keys below 100% - #6

Merged
shay2000 merged 3 commits into
mainfrom
fix/xdr-wake-state-and-native-brightness-keys
Sep 14, 2026
Merged

shay2000 merged 3 commits into
mainfrom
fix/xdr-wake-state-and-native-brightness-keys

Conversation

@shay2000

Copy link
Copy Markdown
Owner

Problem 1: stale XDR state after sleep/wake

Sometimes, after the Mac sleeps with XDR extended brightness on, waking leaves the boost disabled (fine — the panel is back at SDR) but the app still reports the panel as boosted: the slider stays at 150% and the stored preference says 1.5.

Three contributing causes, all fixed here:

  • Resume raced the refresh loop. On a plain sleep/wake (no display reconfiguration), soberNow() restarted the one-second refresh loop before resuming the boost. The loop's first pass read the un-boosted panel and dragged the stored >1.0 brightness back toward the SDR maximum before the boost had been re-applied — so the boost silently never came back. The resume now runs first.
  • The refresh loop stayed dead on some machines. soberNow() only restarted the loop in the arm64 branch; the loop is what keeps sliders and stored values in step with panels, so it now restarts on every architecture (updateArm64AVServices() is internally a no-op where it does not apply).
  • A failed resume left a lie in place. The resume can still fail (the EDR window cannot be re-created, or macOS has withdrawn the extended range). Ten seconds after waking, each XDR-enabled display now checks whether a boost is actually running; where it is not, the stored value, the live slider and the menus snap to what the panel really shows (AppleDisplay.reconcileXDRStateAfterWake()). XDR stays enabled, so pushing past 100% starts the boost again.

Problem 2: the Mac's own brightness UI vanished while the app runs

With only the built-in display attached, the app kept the brightness keys away from macOS at every brightness (so the XDR opt-in stayed reachable from the keys), and the native brightness OSD was suppressed for Apple displays everywhere (macOS 27's OSDManager leaves the overlay permanently drawn). The result: pressing F1/F2 changed the panel with no OSD, no slider — no feedback at all.

  • The keys are now held back from macOS only while an XDR-capable panel sits at the top of the standard range or is boosting — the moments the next press up needs this app. Below 100% macOS gets the keys back, and with them its own brightness overlay and slider feedback. The one-second refresh loop watches the panel and retakes the keys as it reaches 100% again (including when macOS moved it there with the native keys), so the XDR opt-in and the extended range stay reachable from the keyboard. MediaKeyTapManager.shouldHoldBrightnessKeysForXDR() holds the decision.
  • The native OSD is shown again for Apple displays everywhere except macOS 27, where the permanent-overlay bug remains and the menu-bar sun, the XDR dialog and the live slider remain the feedback.

Tests

Added to XDRBrightnessTests (no hardware touched — dummy and stub displays):

  • Wake reconciliation snaps a stale 150% record back to the panel's real brightness; leaves it alone when XDR is off or the value is inside the standard range.
  • Brightness-key engagement: held only at the top of the standard range (with XDR on or off — the opt-in must stay reachable), released below it, with external displays attached, during sleep/reconfiguration, and for non-XDR panels.

Two wake-related XDR fixes:

- After a plain sleep/wake the XDR boost is now resumed before the
  refresh loop restarts (the loop's first pass used to overwrite the
  stored >1.0 brightness with the un-boosted panel reading, silently
  dropping the setting), the loop itself now restarts on every
  architecture, and a delayed reconciliation snaps the stored value,
  slider and menus back to what the panel really shows whenever the
  boost fails to come back at all.

- The brightness keys are only held back from macOS while the built-in
  XDR panel is at the top of the standard range or boosting; below that
  macOS gets the keys back, so its native brightness OSD and sliders
  return. The one-second refresh loop retakes the keys as the panel
  reaches 100% again, keeping the XDR opt-in reachable from the keys.
  The native OSD is also shown again for Apple displays everywhere
  except macOS 27, where the OSDManager overlay stays permanently
  drawn.
@greptile-apps

greptile-apps Bot commented Sep 14, 2026 •

Copy link
Copy Markdown

Greptile Summary

This PR synchronizes stored XDR brightness with the panel after wake and restores native brightness-key handling below 100%.

  • Resumes XDR before restarting the display refresh loop.
  • Reconciles stale extended-brightness state after an unsuccessful resume.
  • Dynamically intercepts brightness keys only when an XDR-capable panel reaches 100% or is boosting.
  • Restores native brightness OSD behavior except on macOS 27.
  • Adds coverage for wake reconciliation and brightness-key engagement.

Confidence Score: 4/5

The PR is not yet safe to merge because the delayed wake callback can still reconcile state during a newer wake cycle.

The previous wake-cycle finding remains unresolved: the delayed callback still checks only whether sleepID and reconfigureID are currently zero, so an earlier callback can run after a subsequent cycle has reset both IDs and overwrite extended brightness while the newer resume is settling. The previous test-isolation finding also remains outstanding because the reconciliation test still does not set and restore disableSmoothBrightness or otherwise isolate its write from process-wide preferences.

Files Needing Attention: MonitorControl/Support/AppDelegate.swift, MonitorControlTests/XDRBrightnessTests.swift

Important Files Changed

Filename Overview
MonitorControl/Support/AppDelegate.swift Reorders wake recovery and schedules delayed XDR reconciliation; the previously reported stale-callback race remains outstanding.
MonitorControl/Model/AppleDisplay.swift Adds failed-resume reconciliation and enables native brightness OSD on supported macOS versions.
MonitorControl/Support/MediaKeyTapManager.swift Dynamically returns brightness keys to macOS below the XDR boundary and retakes them at 100%.
MonitorControlTests/XDRBrightnessTests.swift Adds XDR wake and media-key tests; the previously reported dependence on process-wide smooth-brightness preferences remains outstanding.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Mac wakes] --> B{Display reconfiguration needed?}
  B -->|Yes| C[configure displays and resume XDR]
  B -->|No| D[update AVServices]
  D --> E[resume XDR]
  E --> F[restart refresh loop]
  C --> G[Schedule reconciliation after 10 seconds]
  F --> G
  G --> H{Same wake remains settled?}
  H -->|No| I[Skip reconciliation]
  H -->|Yes| J{Stored brightness above 100% and boost inactive?}
  J -->|No| K[Keep current state]
  J -->|Yes| L[Read panel brightness]
  L --> M[Correct preference and slider]
Loading

Reviews (3): Last reviewed commit: "Drop the menu rebuild from wake reconcil..." | Re-trigger Greptile

Comment on lines +315 to +320
DispatchQueue.main.asyncAfter(deadline: .now() + 10.0) { [weak self] in
guard let self = self, self.sleepID == 0, self.reconfigureID == 0 else {
return
}
self.reconcileXDRStateForAllDisplays()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Stale wake callback reconciles

If another sleep/wake cycle completes during this ten-second delay, both lifecycle IDs can return to zero, so the callback from the earlier wake passes this guard while the newer XDR resume is still settling. Because the boost may not yet be reported as active, reconciliation can replace the valid stored extended brightness with the panel's SDR value. Tie the callback to the wake cycle that scheduled it or cancel it when another sleep begins.

Prompt To Fix With AI
This is a comment left during a code review.
Path: MonitorControl/Support/AppDelegate.swift
Line: 315-320

Comment:
**Stale wake callback reconciles**

If another sleep/wake cycle completes during this ten-second delay, both lifecycle IDs can return to zero, so the callback from the earlier wake passes this guard while the newer XDR resume is still settling. Because the boost may not yet be reported as active, reconciliation can replace the valid stored extended brightness with the panel's SDR value. Tie the callback to the wake cycle that scheduled it or cancel it when another sleep begins.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Codex

The dummy AppleDisplay.getBrightness() short-circuits to 1, so the
reconciliation assertions were reading the constant instead of the
stored value: the leave-alone cases failed outright and the snap case
passed vacuously. The stub reads the preference, the same read the real
non-dummy path performs.
display.savePref(Float(1.5), for: .brightness)

// The engine never resumed after the wake, so the panel is really at its SDR maximum.
// The app must stop claiming 150% and say what the panel shows.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Test Depends on Global Preference

If the process-wide disableSmoothBrightness preference is true, setBrightness takes the direct-write path, which rejects writes to this dummy display. The stored brightness therefore remains at 1.5 and this test fails because of external user-default state rather than the behavior under test. Set and restore the smooth-brightness preference in the test, or use a stub that records the reconciliation write independently of global preferences.

Prompt To Fix With AI
This is a comment left during a code review.
Path: MonitorControlTests/XDRBrightnessTests.swift
Line: 307

Comment:
**Test Depends on Global Preference**

If the process-wide `disableSmoothBrightness` preference is true, `setBrightness` takes the direct-write path, which rejects writes to this dummy display. The stored brightness therefore remains at 1.5 and this test fails because of external user-default state rather than the behavior under test. Set and restore the smooth-brightness preference in the test, or use a stub that records the reconciliation write independently of global preferences.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Codex

The test host never builds the menu (applicationDidFinishLaunching
returns before setMenu() under XCTest), so the deferred
app.updateMenusAndKeys() force-unwrapped a nil menu and crashed the
next test. The rebuild was not doing anything anyway: reconciliation
leaves xdrEnabled alone, so the menu items and the slider's extended
range are unchanged, the live slider is updated directly, and the
menu-bar icon follows the engine's boost notifications.
@shay2000
shay2000 merged commit b718725 into main Sep 14, 2026
3 checks passed
@shay2000
shay2000 deleted the fix/xdr-wake-state-and-native-brightness-keys branch September 14, 2026 02:21
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