English · Русский · 中文 · فارسی · Español · العربية
A native macOS app for Yeelight LAN lamps and Keychron keyboards — screen-sync ambilight
(multi-display + per-segment addressable strips), music reactivity, scenes, and multi-lamp groups.
Built for the Screen Light Bar Pro, it drives plain RGB strips & bulbs too.
No cloud · no account · everything over your LAN.
Keychron keyboard ambilight — any Keychron QMK/VIA keyboard lights up in the colour of a chosen screen zone, over USB.
Screen-sync ambilight — each lamp samples its own display & region; addressable strips map per-segment, all over a live monitor map.
Menu-bar quick controls — power, brightness, ambient & effects without opening the window.
UI mockups rendered to scale from the app's actual layout & theme.
- Full control — power, front white (brightness + colour temperature), ambient RGB, ready-made scenes.
- Group / "mix" control — select several lamps and drive them together; each lamp speaks its own protocol
dialect (the bar has a separate ambient
bgchannel, strips don't). - Screen-sync ambilight — ScreenCaptureKit samples your screen and streams the colour to the lamp's ambient
channel at ~20 Hz over a UDP session.
- Per-lamp display + region: in a multi-monitor setup, each lamp can sample a different display and a different region of it (top / bottom / left / right / full) — e.g. the bar takes the top of your main screen while an under-desk strip takes the bottom of another.
- Resolution-independent capture (works on 16:9, 4K, portrait, and 32:9 ultrawides alike).
- Live preview: a screen-shaped panel per display showing exactly which region each lamp samples, in its live colour.
- Music reactivity — captures system audio (no microphone), splits it into bass/mid/treble with IIR filters.
- Beat mode pumps brightness on the kick; Spectrum mode maps bass→red / mid→green / treble→blue.
- Keychron keyboard ambilight — drives the RGB matrix of any Keychron QMK/VIA keyboard (over USB) from the same screen-sync or music engine, choosing which screen zone it samples. Model is auto-detected (matched by raw-HID, not a hard-coded PID), colour goes to both the per-key matrix and underglow channels, and nothing is written to EEPROM (no wear). Reads battery % over the cable on firmware that reports it.
- Scenes — save the current lamp colour as a named scene and re-apply it in one tap; ready-made presets plus a custom "My scenes" grid that persists across restarts.
- Quality-of-life — an eyedropper to pick any on-screen colour as the ambient, idle dimming that fades the lamps when you step away and restores the running effect when you're back, launch at login, a scene-cut snap and letterbox (black-bar) skip for the ambilight.
- Gamer-brand themes — Venom (green) · Crimson (red) · Forge (amber) · Solar (orange), each with full light + dark variants that follow the macOS day/night switch. Live in-app theme, appearance (Light / Dark / Auto) and language (7 languages) pickers in Settings.
- Two surfaces — a compact menu-bar panel for quick tweaks and a full resizable borderless window
(
NavigationSplitViewwith a custom chrome) for setup. - Robust on a real network — auto-discovery (SSDP + active subnet scan), reconnect on DHCP IP changes, and serialized control so the lamp never drops a command from concurrent connections.
- macOS 13 (Ventura) or newer, Apple Silicon or Intel.
- Yeelight device(s) with LAN Control enabled (Yeelight app → device → LAN Control).
- Screen Recording permission (System Settings → Privacy & Security) for screen-sync and music modes.
Open YeelightBar.xcodeproj and run the YeelightBar scheme (⌘R). The project is generated from project.yml
with XcodeGen; run xcodegen generate after editing the spec.
swift build
./scripts/bundle.sh # assembles + signs build/YeelightBar.app
open build/YeelightBar.appscripts/setup-signing.sh creates a stable self-signed code-signing identity so the Screen-Recording grant survives
rebuilds (an ad-hoc signature changes every build and would re-trigger the permission prompt).
A small CLI for testing and scripting the protocol:
swift run yeectl discover # SSDP
swift run yeectl auto # SSDP, fall back to active subnet scan
swift run yeectl state <ip>
swift run yeectl on|off <ip>
swift run yeectl bright <ip> <0-100>
swift run yeectl ct <ip> <1700-6500>
swift run yeectl rgb <ip> <hex e.g. FF8800> # ambient / bg channel
swift run yeectl rainbow <ip> [seconds] # UDP 20 Hz streaming testSources/
YeelightKit/ # transport-only library, no UI
Yeelight.swift # TCP 55443 JSON control + UDP 55444 streaming session
Discovery.swift # SSDP multicast discovery
Scan.swift # active subnet scan + manual-IP validation
yeectl/ # CLI
YeelightBarApp/ # SwiftUI app
LampController.swift # @MainActor store: discovery, group control, sync orchestration
ScreenSyncEngine.swift # multi-display capture → per-(display,region) colour → UDP fan-out
MusicSyncEngine.swift # system-audio capture → beat/spectrum → UDP fan-out
FullView.swift / MenuPanelView.swift
The Yeelight LAN protocol (TCP control, the UDP streaming handshake, the bar's quirky main_power/bg_power
channels) is documented in PROTOCOL.md.
This lamp has two independent channels — front white (set_power / main_power) and ambient RGB
(bg_set_power / bg_set_rgb) — so you can run "ambient only". Its power property is unreliable (sticks at on
even when the front is dark); the app reads main_power instead. Plain strips have a single channel and reject the
bar-only dev_toggle, so control is dispatched per device type.
MIT — not affiliated with or endorsed by Yeelight / Xiaomi.