What happens
When OLED care dims a display (for example while the screen is locked) and the
app then dies without cleaning up, the panel is left dim. On the next launch
Candela recovers such a display: it notices its own marker, puts the stored
brightness back, and clears the marker. A guard now stops the launch refresh from
reading the panel's own dimmed register back in before that recovery runs.
The same protection is missing on the native leg, which is the path the dim rides
whenever HDR is on and DDC cannot be used. Two places read the display's current
native brightness and persist it as the user's value with no marker check:
BrightnessController.adoptExternal and
BrightnessController.adoptNativeForSurface, in
CandelaKit/Sources/CandelaKit/Brightness/BrightnessController.swift, which
adopt a brightness the app did not set (for example when the menu bar panel
opens and reads the current value).
- The brightness poller restarted by
restartPoller in
Candela/App/AppModel.swift, which starts before the interrupted-dim recovery
runs.
Either one can ease into the leftover dim, save it as the user's brightness, and
leave the recovery with nothing correct to restore. The display then stays dim
and the app believes that is what the person chose.
Expected
While a display carries the interrupted-dim marker, nothing persists a native
read as the user's brightness. The recovery runs first, restores the stored
value, clears the marker, and only then does adoption or polling resume.
Notes
The fix shape suggested is the one already used on the DDC readback path: check
the marker before persisting, in both adoption entry points, and make the
recovery run before the poller is restarted. This was ruled out of scope for the
interrupted-dim work, whose guard was deliberately scoped to the readback path.
Note that the monitor which cannot answer DDC reads hides this class of defect
entirely; it only shows on a panel that answers reads, or on the native leg.
Whether the ordering change alone is enough, or both entry points need the guard
as well, is for the fix to establish; the readback guard's own test shape applies
to both. The recovery and the readback guard land with #90.
Hardware verification
- Enroll a read-answering external monitor in OLED care with HDR on, so the dim
rides the native leg. Note the brightness the app shows. Lock the screen and
confirm the panel visibly dims. Positive control: quit Candela, lock the
screen again, and confirm the panel does not dim.
- While it is dim, kill the app rather than quitting it, so the marker survives.
Confirm the panel is still dim with no app running.
- Relaunch and read the brightness Candela shows for that display. It must be
the value from step 1. Positive control: clear the marker by hand before the
relaunch and confirm the app instead adopts the dim value, which is the
signature of this defect.
- Repeat steps 1 to 3 with HDR off, to confirm the DDC path still recovers.
Positive control: the same run before this change already passed, so a failure
here is a regression from the fix.
What happens
When OLED care dims a display (for example while the screen is locked) and the
app then dies without cleaning up, the panel is left dim. On the next launch
Candela recovers such a display: it notices its own marker, puts the stored
brightness back, and clears the marker. A guard now stops the launch refresh from
reading the panel's own dimmed register back in before that recovery runs.
The same protection is missing on the native leg, which is the path the dim rides
whenever HDR is on and DDC cannot be used. Two places read the display's current
native brightness and persist it as the user's value with no marker check:
BrightnessController.adoptExternalandBrightnessController.adoptNativeForSurface, inCandelaKit/Sources/CandelaKit/Brightness/BrightnessController.swift, whichadopt a brightness the app did not set (for example when the menu bar panel
opens and reads the current value).
restartPollerinCandela/App/AppModel.swift, which starts before the interrupted-dim recoveryruns.
Either one can ease into the leftover dim, save it as the user's brightness, and
leave the recovery with nothing correct to restore. The display then stays dim
and the app believes that is what the person chose.
Expected
While a display carries the interrupted-dim marker, nothing persists a native
read as the user's brightness. The recovery runs first, restores the stored
value, clears the marker, and only then does adoption or polling resume.
Notes
The fix shape suggested is the one already used on the DDC readback path: check
the marker before persisting, in both adoption entry points, and make the
recovery run before the poller is restarted. This was ruled out of scope for the
interrupted-dim work, whose guard was deliberately scoped to the readback path.
Note that the monitor which cannot answer DDC reads hides this class of defect
entirely; it only shows on a panel that answers reads, or on the native leg.
Whether the ordering change alone is enough, or both entry points need the guard
as well, is for the fix to establish; the readback guard's own test shape applies
to both. The recovery and the readback guard land with #90.
Hardware verification
rides the native leg. Note the brightness the app shows. Lock the screen and
confirm the panel visibly dims. Positive control: quit Candela, lock the
screen again, and confirm the panel does not dim.
Confirm the panel is still dim with no app running.
the value from step 1. Positive control: clear the marker by hand before the
relaunch and confirm the app instead adopts the dim value, which is the
signature of this defect.
Positive control: the same run before this change already passed, so a failure
here is a regression from the fix.