Skip to content

Add brightness presets and a monitorcontrol:// URL scheme - #1892

Open
Erik5000 wants to merge 3 commits into
MonitorControl:mainfrom
Erik5000:feature/brightness-presets-and-url-scheme
Open

Add brightness presets and a monitorcontrol:// URL scheme#1892
Erik5000 wants to merge 3 commits into
MonitorControl:mainfrom
Erik5000:feature/brightness-presets-and-url-scheme

Conversation

@Erik5000

@Erik5000 Erik5000 commented Aug 12, 2026

Copy link
Copy Markdown

What this adds

Two related things, in three commits that can be taken separately:

  1. Four brightness presets. Each has a percentage you set and a shortcut
    you record. A preset sets every display to that level.
  2. A monitorcontrol:// URL scheme, so a script or a launcher can do the
    same thing.
monitorcontrol://brightness/set?value=10&display=all
monitorcontrol://brightness/change?delta=-10

Both go through one small shared file, BrightnessActions.swift, so the two
entry points cannot drift apart.

Why

The most common request I could find in this repo is a key that dims every
screen when you leave the desk, and a key that puts them back: #1851, #1674,
#727, #795, #1224. Related asks for outside control: #682, #1515, #1620, #1858.

I understand the answer has been that MonitorControl stays simple, and that
BetterDisplay covers this. I still think this specific shape is small enough to
fit: it adds no new window, no new tab, and nothing changes for a user who
never records a preset shortcut.

Please close this if you disagree. I will keep using my fork and I will not
argue the point.

Design notes

  • Display.setBrightness already existed and already picks DDC, the Apple
    protocol, gamma, or the shade layer. Nothing in that path is touched.
  • The new action repeats the three side effects of stepBrightness: the OSD,
    the menu slider, and brightnessSyncSourceValue. The last one matters,
    because AppDelegate.job() compares against it and a stale value makes the
    sync push the difference to every other display.
  • A relative URL change does its arithmetic per display inside the app. A
    caller therefore never has to read the brightness first. Many monitors accept
    writes but return noise on reads.
  • Presets stay active whichever way the brightness keys are set. Most users
    keep the media keys, and a preset must still work for them.
  • The settings rows are built in code, not in the storyboard, to keep the new
    text in Localizable.strings only instead of 19 Main.strings files.
  • Any app can open a URL, so disableExternalControl switches the scheme off
    and a 1% floor stops a URL from making a screen fully black.
  • No change to the deployment target. App Intents would have needed macOS 13.

Testing

Built and run on macOS 26.5.2, Apple silicon, with 4 external displays
(3x LG UltraFine, 1x N27p) in clamshell mode. Two of those monitors accept DDC
writes but return noise on reads, which made them a useful test.

Checked: absolute set, relative change, all four displays reaching the target,
the OSD, the menu sliders, the brightness sync not fighting the change, the 1%
floor, the off switch, and malformed URLs (value=abc, value=999, unknown
action) being ignored without a crash.

The diff carries no SwiftFormat churn and no CFBundleVersion bumps.

Disclosure

This was written with AI assistance (Claude). You said on 2026-05-09 that you
do not review AI generated PRs, and asked to be told. So: this is one. I am not
asking you to make an exception. If the AI contribution / fork recommendation
label is the right home for it, that is completely fine, and the fork is at
https://github.com/Erik5000/MonitorControl for anyone who wants the feature.

Every line was read, built, and tested on real hardware before opening this.

Erik5000 and others added 2 commits August 12, 2026 08:00
MonitorControl can set an absolute brightness from the menu slider only.
The slider path also skips the OSD and the brightness sync bookkeeping,
so it is not reusable.

Add BrightnessActions with two entry points, setLevel and changeLevel.
Both perform the same side effects as a brightness key press: they show
the OSD, move the menu slider, and update brightnessSyncSourceValue.

The last one matters. AppDelegate.job() compares the live brightness of
an Apple display against that value. A stale value makes the sync push
the difference to every other display.

The relative entry point does its arithmetic per display. An outside
caller therefore never needs to read the current level first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
MonitorControl has no way for another app to change the brightness. The
only absolute path is the menu slider, which needs a mouse.

Add a URL scheme on top of the shared action layer:

  monitorcontrol://brightness/set?value=10
  monitorcontrol://brightness/set?value=10&display=all
  monitorcontrol://brightness/change?delta=-10

Values are percentages. The app does the arithmetic for a relative
change, so a caller never has to read the current level. This matters:
many monitors accept DDC writes but return noise on reads.

Any app can open a URL, so there are two limits. The
disableExternalControl preference switches the scheme off. A 1% floor
stops a URL from making a screen fully black.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Erik5000
Erik5000 force-pushed the feature/brightness-presets-and-url-scheme branch 3 times, most recently from c910a72 to cbc6d5a Compare August 12, 2026 07:19
MonitorControl can step the brightness up and down, but it cannot jump
to a level. Users ask for this often, most recently in MonitorControl#1851 and MonitorControl#1674:
a key that dims every screen when they leave the desk, and a key that
puts the screens back.

Add four presets. Each one has a percentage the user sets and a
shortcut the user records. A preset always changes every display,
because a scene that reached only the display under the pointer would
be of little use.

The presets stay active whichever way the brightness keys are set. Most
users keep the media keys, and a preset must still work for them.

The rows are built in code, not in the storyboard. Every storyboard
label needs an entry in 19 Main.strings files keyed by object id. In
code the new text lives in Localizable.strings only.

The percentage box saves while it is typed. An NSTextField sends its
action on Return only, so a level that was typed and left uncommitted
would never reach the settings.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Erik5000
Erik5000 force-pushed the feature/brightness-presets-and-url-scheme branch from cbc6d5a to dc7ee4c Compare August 12, 2026 07:23
@waydabber waydabber added the AI contribution / fork recommendation Unplanned PR (typically AI) with improvements which may be of interest to the community label Aug 12, 2026
@waydabber

Copy link
Copy Markdown
Member

No worries. I'll not close this but not merge it either, just keep it open with a label for visibility if one needs this specific feature and would like to get to the fork. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI contribution / fork recommendation Unplanned PR (typically AI) with improvements which may be of interest to the community

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants