Skip to content

Match combined brightness by estimated luminance - #130

Merged
didriksg merged 3 commits into
mainfrom
combined-brightness-luminance
Sep 7, 2026
Merged

Match combined brightness by estimated luminance#130
didriksg merged 3 commits into
mainfrom
combined-brightness-luminance

Conversation

@didriksg

@didriksg didriksg commented Sep 7, 2026

Copy link
Copy Markdown
Owner

Takes over #83 from @alexlee2046, whose commit is the first one here under their own author line. Fixes #83's six review points in the second commit and adds the credit line in the third.

What it does, in the user's terms: the combined brightness slider now moves the built-in panel by estimated luminance instead of by percentage. The built-in's slider curve is nothing like a DDC monitor's, so equal percentages never looked equal; with the panel's rated nits on both sides the slider aims both at the same light output, and the built-in is driven through the linear brightness API so the mapping holds along the whole range. Auto Brightness gets the same conversion in its "follow the built-in" mode. "Keep display offsets" is untouched. A new "Absolute brightness ratio" slider under Show Combined Brightness fine-tunes the pair when the rated numbers are off.

The review points, in order: the lint allowlist for the dlsym'd CoreDisplay name; the linear fallback that mixed domains is gone (a machine without the linear API keeps the old percentage path, gated by BrightnessService.supportsLinearBrightness); the external lookup reuses DDCService's registry walk and DDCServiceMatcher for node pairing, cached for 5 s; the lookup runs off the main actor as part of loadDetails; both Auto Brightness targets clamp to display.maxBrightness so Extra Brightness is not dragged down; the defaults key lost its .v2 and the math lives in Models with its tests in CrispTests.

Verified live on this desk with the slider on: the built-in resolves 600 nits, one external 1156, the other publishes nothing and stays proportional. Dragging the combined slider, externals at 21.8 percent put the built-in at linear 0.462 (73.5 on its own slider), at 29.2 about 0.62, and from about 52 percent up the built-in sits at its ceiling. The Settings slider reads 212 percent for this pair. Auto Brightness follow mode is computed from the same numbers, not driven, since I run with offsets.

One thing to know: the only luminance an EDID publishes is in its HDR static metadata block, so HDR monitors report their HDR peak while SDR panels report nothing. A mini-LED monitor running in SDR is nowhere near its peak, which is why the built-in tops out at half the slider on my desk. The ratio slider is the way back, and its caption now says so. The math is right; the reference number is what the monitor chooses to publish.

alexlee2046 and others added 3 commits September 7, 2026 02:02
Normalize built-in and external displays on an estimated nit scale, use the built-in panel's linear brightness API, and account for Crisp's low-range DDC/gamma blend. Apply the same mapping to absolute auto-brightness, expose a fine-tuning ratio, and add focused math tests and localization.
The six points from the review of #83, on top of alexlee2046's commit as it was.

The dlsym'd CoreDisplay name is on the lint allowlist with the other two. The built-in keeps the proportional mapping when the linear DisplayServices symbols do not resolve: BrightnessService.supportsLinearBrightness gates the linear target in the view, and the two linear setters are no-ops without the API instead of feeding a linear value to the native percent API. The external luminance lookup finds its registry node the way DDC pairing finds its channel: DDCService.displayIdentity(from:) is shared (with its handling of the negative Int bit-patterns real monitors return) and the nodes go through DDCServiceMatcher, so a monitor reads its luminance from the node it pairs on; one walk is cached for the displays of a reconfiguration. The lookup runs from loadDetails off the main actor, and nominalMaxNits is published, since the walk covers the whole service plane. The absolute auto-brightness branch clamps to display.maxBrightness, both the nits path and the old percentage mirror. The defaults key has no .v2, and the math lives in Models.
@didriksg
didriksg merged commit acb8976 into main Sep 7, 2026
1 check passed
@didriksg
didriksg deleted the combined-brightness-luminance branch September 7, 2026 05:50
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