From f9024012fbbff9211fb54e1bf2cf6a1b022a2d37 Mon Sep 17 00:00:00 2001 From: shay2000 Date: Mon, 14 Sep 2026 00:56:43 +0000 Subject: [PATCH 1/3] Sync XDR state after wake and hand brightness keys back below 100% 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. --- MonitorControl/Model/AppleDisplay.swift | 49 +++++++++-- MonitorControl/Model/Display.swift | 12 +-- MonitorControl/Support/AppDelegate.swift | 39 +++++++-- .../Support/MediaKeyTapManager.swift | 75 ++++++++++++++--- MonitorControlTests/XDRBrightnessTests.swift | 83 +++++++++++++++++++ 5 files changed, 230 insertions(+), 28 deletions(-) diff --git a/MonitorControl/Model/AppleDisplay.swift b/MonitorControl/Model/AppleDisplay.swift index af44545..dadea69 100644 --- a/MonitorControl/Model/AppleDisplay.swift +++ b/MonitorControl/Model/AppleDisplay.swift @@ -42,11 +42,17 @@ class AppleDisplay: Display { override var brightnessMaxValue: Float { self.effectiveBrightnessMax } - /// The built-in panel never raises macOS's native brightness OSD through `OSDManager`: - /// see the override of `showsBrightnessOSD` on `Display`. The menu-bar sun going yellow - /// when a boost is active, the XDR opt-in dialog at 100 %, and the live slider in the - /// menu are the visible feedback instead. - override var showsBrightnessOSD: Bool { false } + /// The built-in panel raises macOS's native brightness OSD through `OSDManager` only on + /// systems where that overlay still behaves. On macOS 27 it leaves the OSD permanently + /// drawn: `showImage:…:msecUntilFade:` neither honours the fade timer nor accepts a + /// later update that should replace it, so once raised it stays up while the app is + /// running. Everywhere else the OSD is shown, so pressing the brightness keys gives the + /// same visual feedback macOS itself would; the menu-bar sun going yellow when a boost + /// is active, the XDR opt-in dialog at 100 %, and the live slider in the menu are the + /// visible feedback instead where it is not. + override var showsBrightnessOSD: Bool { + ProcessInfo.processInfo.operatingSystemVersion.majorVersion < 27 + } /// True when the panel can do extended brightness but the user has not switched it on. var canOfferXDR: Bool { @@ -189,6 +195,39 @@ class AppleDisplay: Display { self.applyBrightnessToPanel(self.getBrightness()) } + /// Brings the app's record of brightness back in line with the panel after a wake that + /// killed the XDR boost. + /// + /// Sleep tears the boost down, and the resume a few seconds after waking can still + /// fail: the EDR window cannot be re-created, or macOS has withdrawn the extended + /// range. The stored preference then keeps claiming the panel is at, say, 150% while it + /// is really at the SDR maximum, and the slider follows the preference. Rather than + /// leave that lie in place — with the extended range still enabled and one drag away — + /// fall back to the standard range at whatever brightness the panel is actually + /// showing. XDR stays enabled, so pushing past 100% starts the boost again. + func reconcileXDRStateAfterWake() { + guard self.isXDREnabled else { + return + } + guard self.getBrightness() > 1.005, !self.isXDRBoosting else { + return + } + var actual = self.getAppleBrightness() + if !(0.001 ... 1.0).contains(actual) { + // A failed read leaves 0 behind, and while the boost ran the SDR side was pinned at + // the maximum, so fall back to that rather than trusting a dark-screen reading. + actual = 1.0 + } + os_log("XDR boost did not survive the wake on display %{public}@; snapping brightness back to %{public}@.", type: .info, String(self.identifier), String(actual)) + _ = self.setBrightness(actual) + if let sliderHandler = self.sliderHandler[.brightness] { + sliderHandler.setValue(actual, displayID: self.identifier) + } + DispatchQueue.main.async { + app.updateMenusAndKeys() + } + } + override func stepBrightness(isUp: Bool, isSmallIncrement: Bool) { super.stepBrightness(isUp: isUp, isSmallIncrement: isSmallIncrement) // Only act when the user is pushing up against the ceiling: that is when the XDR diff --git a/MonitorControl/Model/Display.swift b/MonitorControl/Model/Display.swift index 9d441c9..772acd7 100644 --- a/MonitorControl/Model/Display.swift +++ b/MonitorControl/Model/Display.swift @@ -37,12 +37,12 @@ class Display: Equatable { /// Whether `stepBrightness` drives the native macOS brightness OSD for this display. /// /// Defaults to `true` so external (DDC/HDMI) displays, which have no other feedback, - /// keep showing the macOS chiclet HUD. Apple displays override this to `false` because - /// on macOS 27 the private `OSDManager` we route through leaves the OSD permanently - /// drawn: `showImage:…:msecUntilFade:` neither honours the fade timer nor accepts a - /// later update that should replace it, so once raised it stays up while the app is - /// running. The XDR opt-in dialog and the menu-bar sun are the visible feedback on - /// the built-in panel instead. + /// keep showing the macOS chiclet HUD. Apple displays override this to report `false` + /// on macOS 27 only, where the private `OSDManager` we route through leaves the OSD + /// permanently drawn: `showImage:…:msecUntilFade:` neither honours the fade timer nor + /// accepts a later update that should replace it, so once raised it stays up while the + /// app is running. There the XDR opt-in dialog and the menu-bar sun are the visible + /// feedback on the built-in panel instead. var showsBrightnessOSD: Bool { true } func prefExists(key: PrefKey? = nil, for command: Command? = nil) -> Bool { diff --git a/MonitorControl/Support/AppDelegate.swift b/MonitorControl/Support/AppDelegate.swift index d31b1cc..f3acd4a 100644 --- a/MonitorControl/Support/AppDelegate.swift +++ b/MonitorControl/Support/AppDelegate.swift @@ -289,18 +289,35 @@ class AppDelegate: NSObject, NSApplicationDelegate { if self.reconfigureID != 0 { let dispatchedReconfigureID = self.reconfigureID os_log("Displays need reconfig after sober with reconfigureID %{public}@", type: .info, String(dispatchedReconfigureID)) + // `configure()` resumes the XDR boost itself, before it restarts the refresh loop. self.configure(dispatchedReconfigureID: dispatchedReconfigureID) - } else if Arm64DDC.isArm64 { + } else { os_log("Displays don't need reconfig after sober but might need AVServices update", type: .info) DisplayManager.shared.updateArm64AVServices() + // The boost was dropped on the way to sleep; a plain sleep/wake never reaches + // `configure()`. Resume before restarting the refresh loop: the loop's first pass + // would otherwise read the un-boosted panel and drag the stored brightness back + // down to the SDR maximum before the boost has been re-applied, silently + // cancelling the XDR setting. The loop itself must also come back on every + // architecture — it is what keeps sliders and stored values in step with the + // panels — and `updateArm64AVServices()` is a no-op where it does not apply. + self.resumeXDRForAllDisplays() self.job(start: true) } self.startupActionWriteRepeatAfterSober() self.updateMediaKeyTap() - // The boost was dropped on the way to sleep. A wake that also reconfigured the - // displays picks it up through `configure()`, but a plain sleep/wake never gets there, - // so the panel would be left at its SDR maximum with the slider still reading 150%. - self.resumeXDRForAllDisplays() + // The resume above can still fail — the EDR window cannot be re-created, or macOS + // has withdrawn the extended range — while the stored preference keeps claiming the + // panel sits above 100%. Ten seconds in (the resume ran three seconds after waking + // and the boost ramps in within a couple more), check whether the boost is actually + // running and, where it is not, snap the app's state back to what the panel really + // shows instead of leaving the slider reading a value the panel is not delivering. + DispatchQueue.main.asyncAfter(deadline: .now() + 10.0) { [weak self] in + guard let self = self, self.sleepID == 0, self.reconfigureID == 0 else { + return + } + self.reconcileXDRStateForAllDisplays() + } } } @@ -314,6 +331,13 @@ class AppDelegate: NSObject, NSApplicationDelegate { } } + /// Brings every display's XDR state back in line with what its panel is really showing. + private func reconcileXDRStateForAllDisplays() { + for display in DisplayManager.shared.displays { + (display as? AppleDisplay)?.reconcileXDRStateAfterWake() + } + } + private func startupActionWriteRepeatAfterSober(dispatchedCounter: Int = 0) { let counter = dispatchedCounter == 0 ? 10 : dispatchedCounter self.startupActionWriteCounter = dispatchedCounter == 0 ? counter : self.startupActionWriteCounter @@ -357,6 +381,11 @@ class AppDelegate: NSObject, NSApplicationDelegate { } } let nextRefresh = refreshedSomething ? 0.1 : 1.0 + // The brightness keys are handed back to macOS while the built-in panel sits below + // the top of the standard range. This is what takes them back once the panel is at + // 100% again — including when macOS itself moved it there with the native keys. + // Cheap by design: the tap is only rebuilt when the verdict actually changes. + self.mediaKeyTap.refreshBrightnessKeyEngagement() DispatchQueue.main.asyncAfter(deadline: .now() + nextRefresh) { self.job() } diff --git a/MonitorControl/Support/MediaKeyTapManager.swift b/MonitorControl/Support/MediaKeyTapManager.swift index c70fa83..58ca456 100644 --- a/MonitorControl/Support/MediaKeyTapManager.swift +++ b/MonitorControl/Support/MediaKeyTapManager.swift @@ -11,6 +11,10 @@ class MediaKeyTapManager: MediaKeyTapDelegate { var keyRepeatTimers: [MediaKey: Timer] = [:] /// Guards the "Accessibility is missing" alert so it is shown at most once per session. private var didReportMissingAccessibility = false + /// The last verdict on holding the brightness keys back from macOS for the XDR range. + /// Kept in step by `updateMediaKeyTap()` so `refreshBrightnessKeyEngagement()` can tell + /// whether the verdict has actually changed. + private var holdingBrightnessKeysForXDR = false func handle(mediaKey: MediaKey, event: KeyEvent?, modifiers: NSEvent.ModifierFlags?) { let isPressed = event?.keyPressed ?? true @@ -163,18 +167,17 @@ class MediaKeyTapManager: MediaKeyTapDelegate { // Disengage brightness keys on sleep so MacBook native screen can be controlled meanwhile let isTransient = app.sleepID != 0 || app.reconfigureID != 0 let disengageBrightness = !hasExternalDisplay || isTransient - if disengageBrightness, !prefs.bool(forKey: PrefKey.useFineScaleBrightness.rawValue) { - // Keep them anyway when the built-in panel can go past 100%. With no external display - // attached macOS consumes the brightness keys itself, so `stepBrightness` never runs - // and the XDR opt-in is unreachable except by opening the menu and dragging the - // slider to 100% — which defeats the point of having the keys. Only while awake and - // settled: during sleep and display reconfiguration the panel belongs to macOS. - let keepForXDR = !hasExternalDisplay && !isTransient - && DisplayManager.shared.displays.contains { ($0 as? AppleDisplay)?.isXDRCapable == true } - if !keepForXDR { - let keysToDelete: [MediaKey] = [.brightnessUp, .brightnessDown] - keys.removeAll { keysToDelete.contains($0) } - } + // While the built-in XDR panel is at the top of the standard range, or boosting, the + // brightness keys must be kept from macOS: the next press up crosses into extended + // brightness — or offers to — which macOS will never do on its own. Below that the + // keys are handed back, and with them macOS's own brightness overlay and slider + // feedback, which this app cannot reproduce (the private OSD manager leaves the + // overlay permanently drawn on macOS 27). + let holdBrightnessKeysForXDR = MediaKeyTapManager.shouldHoldBrightnessKeysForXDR(displays: DisplayManager.shared.displays, hasExternalDisplay: hasExternalDisplay, isTransient: isTransient) + self.holdingBrightnessKeysForXDR = holdBrightnessKeysForXDR + if disengageBrightness, !prefs.bool(forKey: PrefKey.useFineScaleBrightness.rawValue), !holdBrightnessKeysForXDR { + let keysToDelete: [MediaKey] = [.brightnessUp, .brightnessDown] + keys.removeAll { keysToDelete.contains($0) } } // Remove volume related keys if audio device is controllable if let defaultAudioDevice = app.coreAudio.defaultOutputDevice { @@ -208,6 +211,54 @@ class MediaKeyTapManager: MediaKeyTapDelegate { } } + /// Whether the brightness keys must be held back from macOS even though it could handle + /// them itself. + /// + /// With no external display attached, macOS drives the built-in panel's brightness + /// perfectly well and shows its own brightness overlay — feedback this app cannot + /// reproduce, since the private OSD manager leaves that overlay permanently drawn on + /// macOS 27. So while the panel sits below the top of the standard range the keys are + /// given back to the system and the native brightness UI returns. + /// + /// They are taken back only while an XDR-capable panel is at 100% — where the next press + /// up has to cross into extended brightness, or offer to, and macOS has nothing to + /// offer — or while a boost is actually running, so the extended range can also be + /// stepped back down from the keys. Dummy displays are excluded: they exist to stand in + /// for hardware and never carry a boost. + static func shouldHoldBrightnessKeysForXDR(displays: [Display], hasExternalDisplay: Bool, isTransient: Bool) -> Bool { + guard !hasExternalDisplay, !isTransient else { + return false + } + return displays.contains { display in + guard let appleDisplay = display as? AppleDisplay, appleDisplay.isXDRCapable, !appleDisplay.isDummy else { + return false + } + return appleDisplay.isXDRBoosting || appleDisplay.getAppleBrightness() >= 0.999 + } + } + + /// Re-checks, from the one-second refresh loop, whether the brightness keys must be + /// held for the XDR range. + /// + /// The verdict changes as the built-in panel crosses the top of the standard range — + /// including when macOS moves it there with the native keys while the app is not + /// listening — so this is what takes the keys back. Rebuilding the event tap churns the + /// session, so it only happens when the verdict actually changed. + func refreshBrightnessKeyEngagement() { + var hasExternalDisplay = false + for display in DisplayManager.shared.getAllDisplays() where !display.isBuiltIn() { + hasExternalDisplay = true + } + let hold = MediaKeyTapManager.shouldHoldBrightnessKeysForXDR( + displays: DisplayManager.shared.displays, + hasExternalDisplay: hasExternalDisplay, + isTransient: app.sleepID != 0 || app.reconfigureID != 0 + ) + if hold != self.holdingBrightnessKeysForXDR { + self.updateMediaKeyTap() + } + } + /// The name of the row that holds the Accessibility app list, which Apple renamed. /// /// On macOS 27 the row — and the window it opens — is called "Device Control and Data diff --git a/MonitorControlTests/XDRBrightnessTests.swift b/MonitorControlTests/XDRBrightnessTests.swift index bfc87d4..7760c7e 100644 --- a/MonitorControlTests/XDRBrightnessTests.swift +++ b/MonitorControlTests/XDRBrightnessTests.swift @@ -1,5 +1,6 @@ // Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others +import Cocoa import XCTest @testable import LumaControl @@ -14,6 +15,17 @@ private class StubbedBrightnessAppleDisplay: AppleDisplay { } } +// The brightness-key engagement decision reads the panel's live brightness, which dummies +// always report as the SDR maximum (and are excluded from the decision anyway), so these +// tests drive it through a non-dummy stub instead. +private class StubbedPanelAppleDisplay: AppleDisplay { + var stubbedPanelBrightness: Float = 1 + + override func getAppleBrightness() -> Float { + self.stubbedPanelBrightness + } +} + private final class RecordingBrightnessDisplay: Display { var writes: [(value: Float, isMainThread: Bool)] = [] var onWrite: ((Float) -> Void)? @@ -269,4 +281,75 @@ final class XDRBrightnessTests: XCTestCase { wait(for: [settled], timeout: 1.0) XCTAssertTrue(display.writes.isEmpty) } + + // MARK: - XDR state after a wake + + func testReconcileXDRAfterWakeSnapsStaleExtendedBrightnessToPanel() { + self.appleDisplay.savePref(true, key: .xdrEnabled) + self.appleDisplay.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. + self.appleDisplay.reconcileXDRStateAfterWake() + + XCTAssertEqual(self.appleDisplay.getBrightness(), 1.0, accuracy: 0.001) + } + + func testReconcileXDRAfterWakeLeavesBrightnessAloneWhenXDRIsOff() { + self.appleDisplay.savePref(false, key: .xdrEnabled) + self.appleDisplay.savePref(Float(1.5), for: .brightness) + + self.appleDisplay.reconcileXDRStateAfterWake() + + XCTAssertEqual(self.appleDisplay.getBrightness(), 1.5, accuracy: 0.001) + } + + func testReconcileXDRAfterWakeLeavesBrightnessAloneBelowExtendedRange() { + self.appleDisplay.savePref(true, key: .xdrEnabled) + self.appleDisplay.savePref(Float(0.8), for: .brightness) + + self.appleDisplay.reconcileXDRStateAfterWake() + + XCTAssertEqual(self.appleDisplay.getBrightness(), 0.8, accuracy: 0.001) + } + + // MARK: - Brightness key engagement + + private func makePanelDisplay(_ identifier: CGDirectDisplayID, name: String, modelNumber: UInt32, panelBrightness: Float, xdrCapable: Bool) -> StubbedPanelAppleDisplay { + let display = StubbedPanelAppleDisplay(identifier, name: name, vendorNumber: 1552, modelNumber: modelNumber, serialNumber: 1, isVirtual: false, isDummy: false) + display.isXDRCapable = xdrCapable + display.stubbedPanelBrightness = panelBrightness + return display + } + + func testBrightnessKeysAreHeldOnlyAtTheTopOfTheStandardRange() { + let below = self.makePanelDisplay(10, name: "XDR Panel Below", modelNumber: 2_001, panelBrightness: 0.5, xdrCapable: true) + let atMaximum = self.makePanelDisplay(11, name: "XDR Panel At Max", modelNumber: 2_002, panelBrightness: 1.0, xdrCapable: true) + + // Below 100% macOS gets the keys back — and with them its own brightness feedback. + XCTAssertFalse(MediaKeyTapManager.shouldHoldBrightnessKeysForXDR(displays: [below], hasExternalDisplay: false, isTransient: false)) + // At 100% the next press up crosses into XDR territory, so the app takes them back. + XCTAssertTrue(MediaKeyTapManager.shouldHoldBrightnessKeysForXDR(displays: [atMaximum], hasExternalDisplay: false, isTransient: false)) + } + + func testBrightnessKeysAreHeldAtMaximumEvenWithXDROff() { + // The opt-in prompt must stay reachable from the keys, so a capable panel that has + // not enabled XDR still holds the keys while it sits at the standard maximum. + let atMaximum = self.makePanelDisplay(12, name: "XDR Panel Opt In", modelNumber: 2_003, panelBrightness: 1.0, xdrCapable: true) + + XCTAssertTrue(MediaKeyTapManager.shouldHoldBrightnessKeysForXDR(displays: [atMaximum], hasExternalDisplay: false, isTransient: false)) + } + + func testBrightnessKeysAreReleasedWithExternalDisplaysOrDuringSleep() { + let atMaximum = self.makePanelDisplay(13, name: "XDR Panel External", modelNumber: 2_004, panelBrightness: 1.0, xdrCapable: true) + + XCTAssertFalse(MediaKeyTapManager.shouldHoldBrightnessKeysForXDR(displays: [atMaximum], hasExternalDisplay: true, isTransient: false)) + XCTAssertFalse(MediaKeyTapManager.shouldHoldBrightnessKeysForXDR(displays: [atMaximum], hasExternalDisplay: false, isTransient: true)) + } + + func testBrightnessKeysAreReleasedForNonXDRPanels() { + let plain = self.makePanelDisplay(14, name: "Plain Panel", modelNumber: 2_005, panelBrightness: 1.0, xdrCapable: false) + + XCTAssertFalse(MediaKeyTapManager.shouldHoldBrightnessKeysForXDR(displays: [plain], hasExternalDisplay: false, isTransient: false)) + } } From 1776f08c699afc14fc226ad49d7ab1253f2e2c92 Mon Sep 17 00:00:00 2001 From: shay2000 Date: Mon, 14 Sep 2026 01:01:46 +0000 Subject: [PATCH 2/3] Drive wake reconciliation tests through a pref-reading stub 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. --- MonitorControlTests/XDRBrightnessTests.swift | 51 +++++++++++++++----- 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/MonitorControlTests/XDRBrightnessTests.swift b/MonitorControlTests/XDRBrightnessTests.swift index 7760c7e..516dac7 100644 --- a/MonitorControlTests/XDRBrightnessTests.swift +++ b/MonitorControlTests/XDRBrightnessTests.swift @@ -26,6 +26,15 @@ private class StubbedPanelAppleDisplay: AppleDisplay { } } +// reconcileXDRStateAfterWake() decides from the stored brightness, which dummies always +// report as 1, so these tests drive the stored value through a stub that reads the +// preference — the same read the real non-dummy path performs. +private class PrefReadingBrightnessAppleDisplay: AppleDisplay { + override func getBrightness() -> Float { + self.prefExists(for: .brightness) ? self.readPrefAsFloat(for: .brightness) : 1 + } +} + private final class RecordingBrightnessDisplay: Display { var writes: [(value: Float, isMainThread: Bool)] = [] var onWrite: ((Float) -> Void)? @@ -285,32 +294,50 @@ final class XDRBrightnessTests: XCTestCase { // MARK: - XDR state after a wake func testReconcileXDRAfterWakeSnapsStaleExtendedBrightnessToPanel() { - self.appleDisplay.savePref(true, key: .xdrEnabled) - self.appleDisplay.savePref(Float(1.5), for: .brightness) + let display = PrefReadingBrightnessAppleDisplay(20, name: "XDR Wake Display", vendorNumber: 1552, modelNumber: 3_001, serialNumber: 1, isVirtual: false, isDummy: true) + defer { + display.removePref(key: .xdrEnabled) + display.removePref(key: .value, for: .brightness) + } + display.isXDRCapable = true + display.savePref(true, key: .xdrEnabled) + 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. - self.appleDisplay.reconcileXDRStateAfterWake() + display.reconcileXDRStateAfterWake() - XCTAssertEqual(self.appleDisplay.getBrightness(), 1.0, accuracy: 0.001) + XCTAssertEqual(display.getBrightness(), 1.0, accuracy: 0.001) } func testReconcileXDRAfterWakeLeavesBrightnessAloneWhenXDRIsOff() { - self.appleDisplay.savePref(false, key: .xdrEnabled) - self.appleDisplay.savePref(Float(1.5), for: .brightness) + let display = PrefReadingBrightnessAppleDisplay(21, name: "XDR Wake Display 2", vendorNumber: 1552, modelNumber: 3_002, serialNumber: 2, isVirtual: false, isDummy: true) + defer { + display.removePref(key: .xdrEnabled) + display.removePref(key: .value, for: .brightness) + } + display.isXDRCapable = true + display.savePref(false, key: .xdrEnabled) + display.savePref(Float(1.5), for: .brightness) - self.appleDisplay.reconcileXDRStateAfterWake() + display.reconcileXDRStateAfterWake() - XCTAssertEqual(self.appleDisplay.getBrightness(), 1.5, accuracy: 0.001) + XCTAssertEqual(display.getBrightness(), 1.5, accuracy: 0.001) } func testReconcileXDRAfterWakeLeavesBrightnessAloneBelowExtendedRange() { - self.appleDisplay.savePref(true, key: .xdrEnabled) - self.appleDisplay.savePref(Float(0.8), for: .brightness) + let display = PrefReadingBrightnessAppleDisplay(22, name: "XDR Wake Display 3", vendorNumber: 1552, modelNumber: 3_003, serialNumber: 3, isVirtual: false, isDummy: true) + defer { + display.removePref(key: .xdrEnabled) + display.removePref(key: .value, for: .brightness) + } + display.isXDRCapable = true + display.savePref(true, key: .xdrEnabled) + display.savePref(Float(0.8), for: .brightness) - self.appleDisplay.reconcileXDRStateAfterWake() + display.reconcileXDRStateAfterWake() - XCTAssertEqual(self.appleDisplay.getBrightness(), 0.8, accuracy: 0.001) + XCTAssertEqual(display.getBrightness(), 0.8, accuracy: 0.001) } // MARK: - Brightness key engagement From 8ef8f4376f68e9ef074f1eff25f839297766f72e Mon Sep 17 00:00:00 2001 From: shay2000 Date: Mon, 14 Sep 2026 01:06:28 +0000 Subject: [PATCH 3/3] Drop the menu rebuild from wake reconciliation 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. --- MonitorControl/Model/AppleDisplay.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MonitorControl/Model/AppleDisplay.swift b/MonitorControl/Model/AppleDisplay.swift index dadea69..5bd25aa 100644 --- a/MonitorControl/Model/AppleDisplay.swift +++ b/MonitorControl/Model/AppleDisplay.swift @@ -223,9 +223,9 @@ class AppleDisplay: Display { if let sliderHandler = self.sliderHandler[.brightness] { sliderHandler.setValue(actual, displayID: self.identifier) } - DispatchQueue.main.async { - app.updateMenusAndKeys() - } + // No menu rebuild is needed here, unlike on a disable: XDR stays enabled, so the menu + // items and the slider's extended range are unchanged, the live slider was just updated + // directly, and the menu-bar icon follows the engine's own boost notifications. } override func stepBrightness(isUp: Bool, isSmallIncrement: Bool) {