Repository navigation
Add Tern hardware brightness and Night Vision controls - #1
Merged
Merged
Conversation
Overlay-only red filter that is meaningfully stronger than macOS Night Shift, for astronomy/red-light dark adaptation while keeping the screen readable. - NightVision service: one full-screen, click-through, all-spaces, screen-share excluded (sharingType .none) overlay window per display, pinned one level above the boost/dimmer shielding window. Two CALayers: a multiplyBlendMode tint layer (keeps red, suppresses green/blue so the screen is red-channel-only rather than a flat red wash) over a black darkening layer for the deep, dimmed end. - NightVisionCurve: pure intensity -> color/alpha mapping. Red pinned full, green and blue eased down (blue suppressed hardest), darkening fades in above 50%. - Touches no display gamma table anywhere, so it never fights the EDR boost engine or SoftwareDimmer and survives macOS gamma wipes on display reconfig. - Handles display connect/disconnect via didChangeScreenParameters; tears down overlays for vanished screens; isReleasedWhenClosed = false. - UserDefaults persistence (enabled + intensity), restored after launch. - UI: Night Vision section in the menu bar popover (toggle + red intensity slider), matching DisplayCard/DimSlider styling and Typography tokens. - 12 unit tests for the pure curve math in XDRTests; all 45 tests green. No changes to the forbidden EDR-trigger / gamma / Metal-overlay paths. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tore Retune the curve so high intensity is a readable salmon-deep-red instead of an illegible wall of red: green floor 0.30, blue floor 0.12, max darken 0.22 starting at 60%, with an eased (smoothstep) falloff. Low end stays a gentle warm shift. Engage now fades: overlay fades in over 1.2s, out over 0.4s, and slider moves glide over 0.18s instead of snapping. First-ever enable defaults to 35% (warm zone). On launch a forgotten extreme setting (enabled with intensity > 70%) restores OFF while keeping the stored intensity, so it never ambushes the user at login. Add a live zone caption (Warm / Red shift / Dark adaptation) under the slider and keep panel accents on red-surviving tones. Lock the floors, ramp math, and restore rule with tests (23 night-vision tests, 56 total, all green). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…able state, Dock window section ROOT CAUSE of the red wall + activation glitch: CALayer compositingFilter (multiplyBlendMode) cannot blend with other windows; the window server composites windows source-over only, so the 'multiply tint' painted near-opaque red over the screen and artifacted while trying. - New model: black dim layer + deep-red wash layer, plain source-over, honest math (white response floors locked by tests: g>=0.30 b>=0.28) - Window hygiene: animationBehavior=.none, layers configured before orderFront, first composited frame fully transparent - NightVision is now @observable: popover and main window share one truth (stale per-view state copies removed) - Night Vision section added to the Dock app main window - 53 tests green Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Research (astronomy dark adaptation): proper red mode suppresses blue/green WAVELENGTHS at the hardware level and stays dim, not a translucent red film. Rebuilt to match. - Rendering: per-display gamma LUT via CGSetDisplayTransferByFormula (the SoftwareDimmer's proven mechanism). Green and (harder) blue ceilings drop with intensity; red stays bright, dims slightly only past 70%. White -> deep red, blue -> near black: a true red filter reaching every pixel incl. fullscreen video and cursor, not an overlay wash. - MULTI-MONITOR FIX: enumerate with CGGetOnlineDisplayList instead of NSScreen.screens/displayID (which reports 0 for some externals and was silently skipping them). Re-assert on display-change AND wake (macOS wipes gamma on both), mirroring SoftwareDimmer. - Boost/dimmer coexistence: a display whose gamma is owned by the boost engine (brightness > SDR max) or the software dimmer gets a red wash overlay instead of gamma, so Night Vision never fights the forbidden zone for a LUT. Only ever resets gamma it set itself. - Gentle engage: gamma is stepped over 1.2s in / 0.4s out (eased), never snaps. - 48 tests green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The user's diagnosis was right: night vision, the boost engine, and the software dimmer all write display gamma, and three writers on one LUT is the fight that made night vision behave erratically. - setNightVisionEnabled(true): parks live boosts at SDR max (saved), suspends the dimmer (identity gamma, overlays gone, levels kept), then enables NV. NV now always owns gamma outright; overlay fallback retired. - setNightVisionEnabled(false): NV ramps out, then boosts and dim levels are restored automatically (0.7s handoff). - While NV is on: brightness requests clamp to SDR max, the reconfig boost re-assert is skipped, and the dimmer ignores reassert/display events (levels still persist). - 48 tests green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Previously night vision fully suspended the dimmer, so dimming other monitors did nothing while night vision was on. Now they coexist: - SoftwareDimmer gains gammaSuspended: while NV owns gamma the dimmer stops writing gamma but keeps managing its deep-end black overlay, and exposes gammaScale(for:). - NightVision folds each display's dim scale into its red gamma write (ceiling * dimScale), so a dimmed monitor stays dim AND red. A dim change while NV is on calls nightVision.reapplyGamma() via onDimChangedWhileSuspended. - Disable restores the dimmer's own gamma. Boost parking unchanged. - 23 tests green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A dim-slider drag while Night Vision was on called reapplyGamma() on every tick, rewriting every display's LUT dozens of times per second. Coalesce to one gamma pass per frame (16ms). 23 tests green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Tern USB-C displays previously fell back to software dimming. This adds a verified hardware brightness route and a native slider that updates while dragging, keeps only the newest pending value, and verifies the settled value before saving it.
The update also suppresses stale readbacks, fixes rapid hotkey accumulation and failed-write rollback, removes the duplicate display-refresh observer that could hide reconnections, and replaces process-status polling with termination notification. The extra slider tick row and clipped subtitle are removed.
This publishes the previously local Night Vision work, matching blue icon assets, the MIT-licensed DDC helper in rebuildable source form, build documentation and verification workflow. Existing remote README history was merged without force-pushing. The Swift language setting is corrected from unsupported 5.10 to 5.0.
Validation: