Skip to content
BilalAtiquePublic

About

Tiny macOS menu bar app for external monitor brightness over DDC/CI — 11 MB, no dependencies, no Xcode

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Lumen

A tiny macOS menu bar app that controls the brightness of an external monitor over DDC/CI, and the built-in display natively. Written in Swift and AppKit, with no SwiftUI, no dependencies, and no Xcode required to build.

Light Dark
Popover, light Popover, dark

Why

macOS will not let the brightness keys touch an external monitor. Existing tools solve that, but carry more than they need to. Measured on the same machine with vmmap --summary (physical footprint, idle):

Footprint Idle CPU
Lumen 11.4 MB 0.0%
MonitorControl 4.3.3 24.2 MB —

The difference is not the language — only ~3 MB of that is app heap; the rest is the AppKit floor, which any language pays. It comes from what is not linked: no SwiftUI, no auto-updater, and no embedded Swift runtime (the OS copy is used instead of shipping back-deployment dylibs, which would otherwise become private dirty pages).

Features

  • Brightness, contrast and volume for external displays over DDC/CI
  • Dims below the panel's hardware floor. DDC luminance 0 is the minimum backlight, not black, so the bottom of the brightness slider scales the gamma ramp instead of the backlight
  • Brightness for the built-in panel via DisplayServices
  • Every connected display in one popover, each with its own transport
  • F1 / F2 adjust the display under the cursor
  • Handles both brightness key forms: Apple media keys and plain F1/F2
  • Click, drag, or scroll on any slider
  • Menu bar glyph tracks brightness — Lumen consumes the key event, so Apple's HUD never appears and the glyph is the feedback
  • Open at Login, via SMAppService
  • Rebuilds in about two seconds

Requirements

  • Apple Silicon Mac, macOS 13 or later
  • Command Line Tools (xcode-select --install) — full Xcode is not needed
  • The monitor connected over DisplayPort or USB-C. HDMI cannot carry DDC/CI on Apple Silicon.

Build

./scripts/make-signing-identity.sh   # once, see "Signing" below
./build.sh
open Lumen.app

Permissions

The brightness keys need two separate grants. Both are one-time.

  1. Accessibility — System Settings → Privacy & Security → Accessibility
  2. Input Monitoring — System Settings → Privacy & Security → Input Monitoring

Accessibility alone is not enough. Without Input Monitoring, macOS silently strips the keyboard bits from the event tap mask and reports no error, so plain F1/F2 never arrive. Lumen detects this and says so in the popover.

Check the live state any time:

cat /tmp/lumen-state.txt
trusted=true
tapActive=true
grantedMask=0x4c00     # bits 10, 11, 14 = keyDown + keyUp + systemDefined
keyboardEvents=true

A grantedMask of 0x4000 means Input Monitoring is missing.

Grant the app bundle, not the executable. TCC keys these grants to the bundle identity: launching Lumen.app yields 0x4c00, while running Lumen.app/Contents/MacOS/Lumen directly yields 0x4000.

Signing

build.sh signs with a local self-signed certificate, and this is not cosmetic.

An ad-hoc signature's designated requirement is the binary's cdhash:

ad-hoc:       cdhash H"73c96956..."                                     # new every build
certificate:  identifier "local.lumen" and certificate leaf H"c6846..."  # stable

With an ad-hoc signature, every rebuild looks like a different app to macOS and silently revokes both grants. scripts/make-signing-identity.sh creates a certificate-based identity so the requirement is stable and permissions survive rebuilds. The certificate does not need to be in the trust store — it only needs to be in an unlocked keychain that is on the search list.

If no identity is found, build.sh falls back to ad-hoc and warns.

Diagnostics

Tool What it proves
./selftest DDC read/write and both display paths, with a visible pulse
./tapdump The event tap parses brightness keys
./whichdisplay Which display a cursor position resolves to
./keyspy Logs every key and media event to /tmp/lumen-keyspy.log
./dimtest Reads the gamma ramp back to prove software dimming engages

LUMEN_DEBUG=1 traces tap events to /tmp/lumen.log. For a bundle launch, set it session-wide first, because open does not pass the environment through:

launchctl setenv LUMEN_DEBUG 1     # launchctl unsetenv LUMEN_DEBUG to clear

Render the popover to PNGs without touching the menu bar:

LUMEN_PREVIEW=1 ./Lumen.app/Contents/MacOS/Lumen
# writes /tmp/lumen-ui-light.png and /tmp/lumen-ui-dark.png

How it works

External displays. Apple Silicon replaced IOFramebuffer with IOMobileFramebuffer, which exposes no I²C bus, so the classic IOFBCopyI2CInterfaceForBus route is dead. DDC/CI instead goes through four private IOKit symbols that appear in no public header:

IOAVServiceCreate  IOAVServiceCreateWithService
IOAVServiceReadI2C  IOAVServiceWriteI2C

They are plain C functions, resolved with dlsym rather than linked, so a macOS release that drops them yields a "DDC unavailable" message instead of a launch crash.

Built-in display. DisplayServicesGetBrightness / SetBrightness, so it changes the real backlight rather than dimming with a gamma overlay.

Keys. A CGEventTap on .cgSessionEventTap watching keyDown, keyUp and systemDefined. Apple's internal keyboard sends brightness as NX_SYSDEFINED media keys (subtype 8, key codes 2 and 3); many external keyboards send plain F1 and F2 instead. Modified presses (⌘F1, fn+F1) pass through untouched. The tap re-enables itself after tapDisabledByTimeout, which is otherwise a silent death.

Below the hardware floor. A monitor's DDC luminance 0 is its minimum backlight, which on many panels is still bright. The brightness slider is one continuous 0-100 scale split in two: above 15% it maps to DDC luminance, and below 15% it pins DDC at 0 and scales the display's gamma ramp down to 0.15 via CGSetDisplayTransferByFormula. Measured on a DELL P3223QE:

Slider gammaMax
100% / 50% / 15% 1.000
10% 0.717
0% 0.150

Gamma is chosen over a black overlay window because it does not intercept clicks and does not appear in screenshots. It outlives the process that set it, so Lumen restores it on quit and also clears it at launch, which makes a crash self-healing. Software dimming applies to external displays only — the built-in panel's native minimum is already dark.

Reads are advisory. DDC writes are reliable; reads are not. Lumen validates the get-VCP reply header ([2]==0x02, [3]==0x00, [4]==vcp) and rejects anything malformed, then falls back to the persisted value. Before that check, misaligned frames looked plausible — one panel returned 96, 110 and 100 for an unchanged value, and a bogus 110/128 maximum. Never build UI that trusts a DDC read.

Slider drags coalesce onto one serial queue, latest-value-wins, so a drag cannot queue minutes of stale traffic at ~20 ms of bus time per write.

Limitations

  • One external display. IOAVServiceCreate binds the default external monitor; a second would need a per-display IORegistry walk.
  • Some panels never report brightness. A DELL P3223QE accepts writes but returns no valid brightness or contrast read at any settle time up to 50 ms (volume reads fine). Sliders then start from the last value you set.
  • Gamma dimming reduces colour depth. Below 15% the ramp is compressed, so gradients can band. That is the trade for going darker than the panel allows.
  • No on-screen HUD. Lumen consumes the key event to stop macOS double-handling it, so the menu bar glyph is the only feedback.
  • Private APIs mean this is not App Store eligible, and Apple can break it.

Status

Verified on an M3 Pro with a DELL P3223QE over USB-C: menu bar app, popover, DDC writes, built-in brightness, display hot-plug, and Apple media keys.

The plain-F1/F2 path is implemented and the event tap is confirmed to receive keyboard events, but it has not yet been confirmed against external keyboard hardware. Synthetic key injection is not a valid test here — posted events are rewritten in transit.

Credits

License

MIT — see LICENSE.

About

Tiny macOS menu bar app for external monitor brightness over DDC/CI — 11 MB, no dependencies, no Xcode

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages