1.0 release candidate: Blinky is ready for qualification. MagSafe LED support is experimental because it relies on a private AppleSMC key.
Blinky is a minimal native macOS menu-bar app that blinks the physical Caps Lock LED, the MagSafe charging LED, or both when macOS creates a notification banner. It uses Swift 6, SwiftUI, Accessibility, IOKit, and Service Management with no third-party runtime dependencies.
Detailed design and development documentation starts at
docs/index.md.
- macOS 15 or newer
- A keyboard exposing a writable Caps Lock LED HID element
- Accessibility permission under System Settings → Privacy & Security → Accessibility
- Input Monitoring permission under System Settings → Privacy & Security → Input Monitoring for direct keyboard HID access
- One-time administrator approval under System Settings → General → Login Items & Extensions for experimental charging LED access
The app has no Dock icon or normal window. App Sandbox is disabled for the Accessibility/HID MVP, and this build is not intended for Mac App Store distribution.
Global shortcuts are available while other applications are active: Control-Option-T runs Test Blink and Control-Option-B hides or restores the menu-bar icon. Hiding lasts only for the current run, so relaunching Blinky always makes the icon visible again.
macOS has no supported public API that sends every application's notifications to a third-party app. Blinky therefore inspects the system Notification Center process through the Accessibility API and watches for newly presented banner elements. Notifications whose banners are suppressed, hidden, or silenced cannot be detected reliably.
This structural Accessibility inspection may break after a macOS update if Apple changes Notification Center's process identity or accessibility hierarchy. Blinky attaches only to the exact known Notification Center bundle identifier, bounds every inspection by time, depth, node count, and text count, and classifies an immutable snapshot away from the UI actor. It reads accepted-banner strings only when the opt-in Morse Code effect requires them. Content is capped at 256 characters during capture, Morse playback is capped at 30 seconds at a complete character boundary, and the message is kept only long enough to compile the blink program. Blinky never logs or persists notification content or sender history, reads Notification Center databases, or monitors Unified Logging.
Per-application filtering is intentionally not included in 1.0 because Notification Center's available Accessibility text does not prove a banner's source identity reliably enough.
Blinky discovers all connected HID keyboards, observes keyboard arrival and removal, and writes only to elements with the LED usage page and Caps Lock LED usage. It does not synthesize keyboard events, press Caps Lock, or change the logical Caps Lock typing state. After an override, it restores the indicator to the current logical Caps Lock state. Unsupported or inaccessible keyboards are reported explicitly.
Every blink sequence owns one UUID session and one cancellable Swift concurrency task. Every output command carries that session identity, so a replaced session cannot write after its successor owns the outputs. A new notification restarts the active sequence. Stopping, cancellation, and application termination restore the Caps Lock indication and the MagSafe mode that existed before blinking. Selecting Both is best-effort: a healthy Caps Lock LED continues if experimental MagSafe output is recovering, and the menu reports degradation.
The Morse Code effect encodes notification banner text with standard dot/dash and letter/word spacing. Speed selects an approximately 250, 150, or 80 millisecond Morse unit. Each compiled message is bounded to 30 seconds at a complete Morse character boundary. Morse has two playback choices: Blink Message plays the bounded message once, while Repeat Every 2s leaves the LED dark for two seconds between complete messages and continues until stopped. With Morse selected, Test Morse Blink… accepts an ephemeral custom message of up to 256 characters and plays it once.
When Charging LED or Both is selected, Charging Color offers Green Flash, Amber Flash, or Mixed Flash. Mixed alternates green and amber on successive ON pulses; OFF steps remain off, and cleanup still restores the original firmware-controlled mode.
Photosensitivity warning: The Strobe effect changes the selected LED state every 80 milliseconds. Rapid flashes can affect people with photosensitive epilepsy or other light sensitivities. Avoid Strobe if you or someone nearby may be sensitive to flashing light.
Charging LED support is experimental. It uses a signed, narrowly scoped launch daemon to write only the private AppleSMC ACLC LED key. The helper records and restores the original LED mode and never changes charging behavior. Blinky negotiates the running helper's signed identity and automatically reloads bundled helper updates. This private key may stop working after macOS or firmware updates.
Blinky is available under the MIT License. Third-party attribution is
listed in docs/THIRD-PARTY-NOTICES.md.
See also the Privacy statement, Security policy,
Changelog, and uninstall guide.
For interactive development, use the safe build-and-run workflow. It quits the running signed process before rebuilding, verifies the finished bundle, and launches one fresh instance:
./Scripts/build-and-run.shLocal development uses the host architecture and xcsift for compact output:
xcodebuild -project Blinky.xcodeproj -target Blinky -configuration Debug \
ARCHS=arm64 ONLY_ACTIVE_ARCH=YES build 2>&1 \
| xcsift -f toon -wxcodebuild -project Blinky.xcodeproj -scheme Blinky -configuration Debug \
-destination 'platform=macOS,arch=arm64' \
ARCHS=arm64 ONLY_ACTIVE_ARCH=YES test 2>&1 \
| xcsift -f toon -wKeep development builds signed with the configured Apple Development identity. TCC permissions such as Accessibility and Input Monitoring do not persist reliably for unsigned or changing ad-hoc app identities, and SMAppService.mainApp may also reject an unsigned build.
- Run Blinky and confirm it appears only in the menu bar.
- With Caps Lock off and then on, choose Test Blink and verify typing state never changes and the LED returns to the matching logical state.
- Grant Accessibility permission when requested.
- Send a notification from another application and verify the configured blink starts.
- Send another notification during the blink and verify the sequence restarts without overlapping.
- Choose Stop Blinking or quit and verify each LED returns to its real pre-blink state.
- Press Control-Option-B twice and verify the menu-bar icon hides and returns; press Control-Option-T and verify Test Blink runs globally.
To post a development notification from Terminal:
./Scripts/send-test-notification.shOptional arguments are message, title, and subtitle, in that order. macOS may ask for notification permission for the process hosting AppleScript notifications.
Experiments/MagSafeLEDProbe.swift is intentionally excluded from all app targets. It is a standalone diagnostic using the same private AppleSMC ACLC LED key. Compile and run its default read-only probe with:
swiftc Experiments/MagSafeLEDProbe.swift -o /tmp/magsafe-led-probe -framework IOKit
/tmp/magsafe-led-probeThe separate blink mode performs privileged, unsupported SMC writes and restores the original ACLC byte afterward.
The modes experiment strobes through green, amber/orange, and off at 0.5-second intervals for five seconds before restoring the original value:
sudo /tmp/magsafe-led-probe modes

