尚未收集仓库信息。
尚未检测到更新。GitHub 发布版本后会显示在这里。
Free, open-source Hue Sync alternative for Mac: sync Philips Hue lights with your screen (ambilight) or music, from the menu bar.
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗A menu bar app that syncs Philips Hue lights with your screen, like an ambilight, or with the music your Mac plays: strips behind the monitor follow its edges or ripple with the beat, and lamps around it pick up the mood.
Status: bridge discovery and pairing, screen sync, music sync and manual light control over the Hue Entertainment streaming API.
A note on how this was made: Blask Sync is fully vibecoded. All the code was written by an AI (Claude Code) from my ideas and feedback. To be honest, I wouldn't have the time to write all of it myself, and without AI this app simply wouldn't exist.
swift test # unit tests
scripts/bundle.sh # builds and signs build/BlaskSync Dev.app
open "build/BlaskSync Dev.app"
Local builds are Blask Sync Dev, so they can be installed next to a release and told apart: bundle ID
local.blasksync.BlaskSync.dev, an orange DEV badge on the app icon, an orange menu bar icon, and no
self-updating (so a release never replaces them). They keep their own settings and permissions, but share the
bridge pairing in ~/Library/Application Support/BlaskSync/bridge.json. VARIANT=release scripts/bundle.sh
builds the published app, build/BlaskSync.app, as the release workflow does.
Every pull request runs .github/workflows/checks.yml: swift format lint (style in .swift-format), a build
with warnings as errors, the unit tests, and scripts/check-strings.sh (String Catalogs in sync with the code and
fully translated). To run them locally:
swift format -i -r -p Sources Tests scripts Package.swift # format in place
swift format lint --strict -r -p Sources Tests scripts Package.swift
swift build -Xswiftc -warnings-as-errors
swift test
scripts/check-strings.sh
The app is translated into Polish, German, Spanish and French and follows the language set in System Settings →
General → Language & Region (per app, too). The text lives in String Catalogs: Resources/Localizable.xcstrings
for the app and Resources/InfoPlist.xcstrings for the permission prompts; scripts/bundle.sh compiles them
into the app. After changing UI text, run scripts/update-strings.sh to add new strings to the catalog, then
translate them (Xcode's String Catalog editor shows what's missing). Text built in code rather than passed to a
SwiftUI view needs String(localized:) to be picked up. To try a language without changing the system:
open "build/BlaskSync Dev.app" --args -AppleLanguages '(pl)'
Download BlaskSync-vX.Y.Z.zip from Releases, unzip it
and move BlaskSync.app to Applications. Release builds are signed with a self-signed certificate, not
notarized, so macOS blocks the first launch; clear the download quarantine once:
xattr -dr com.apple.quarantine /Applications/BlaskSync.app
(or try opening it, then click Open Anyway in System Settings → Privacy & Security). Every release is signed with the same certificate, so Screen Recording carries over when updating.
From 0.2.0 on, the app updates itself with Sparkle: it checks once a day (or
via Check Now in Settings → General), shows the release notes, then
installs and relaunches. The feed is the appcast.xml attached to the latest GitHub release, and each
update's zip must carry an EdDSA signature matching SUPublicEDKey in Info.plist. Updating needs the app
in Applications with the quarantine cleared; macOS runs a quarantined app from a read-only copy.
Push a version tag; the Release workflow runs the tests, builds a universal
(Apple Silicon + Intel) app stamped with that version, and publishes it as a GitHub release with its update feed (scripts/make-appcast.sh). The release notes,
shown on the release page and in the app's update window, are the tag message after its first line
(scripts/release-notes.sh; without one, the commits since the previous tag). It uses three repository secrets: the code signing certificate in
SIGNING_P12 (base64 .p12) and SIGNING_P12_PASSWORD, and the Sparkle update signing key in
SPARKLE_PRIVATE_KEY (from generate_keys --account blask-sync -x):
# notes.md: "Blask Sync 0.2.0", a blank line, then Markdown (## headings, - bullets, **bold**, `code`)
git tag -a --cleanup=whitespace v0.2.0 -F notes.md && git push origin v0.2.0
macOS ties the Screen Recording permission to the app's code signature. scripts/bundle.sh signs with SIGN_IDENTITY if set, else the first
"Apple Development" certificate, else ad hoc.
If security find-identity -v -p codesigning lists no valid identity even though Xcode created a
certificate, install Apple's WWDR G3 intermediate.
While Settings or Manual Control is open, Blask Sync shows in the Dock and ⌘-Tab (so a window that went behind others can be brought back); with them closed it's menu-bar-only again. The sync session belongs to the app, not to a window, so sync keeps running with all windows closed.
Capture. ScreenCaptureKit streams the chosen display at 160 px wide and up to 60 fps
(SyncCore/ScreenCapture.swift). Blask Sync's own windows are left out, so the editor doesn't sample
itself. Frames only arrive when the picture changes.
Protected video. While a display is being captured, macOS hides DRM-protected video on the screen itself, so the Apple TV app wouldn't play movies at all. With Pause for Apple TV app (on by default), capture stops while the TV app is in front and the lights ease to a soft warm glow; it resumes when you switch away. The TV app is also left out of the capture filter. Lights can't follow protected movies. DRM video in a browser (e.g. Netflix in Safari) has the same limitation.
Mapping. The canvas shows the monitor (with the live preview) on the wall, lit by your lights: each one is a glowing marker that casts its color onto the wall. Drag a light to move it; hover to see the screen area it follows, select it to resize that area. With Identify while moving (on by default), the real lights take the colors shown on the canvas (one distinct hue per light), with the light you move or select bright and the others dim; if sync is off, the lights are taken over just for this, without capturing the screen, and released when you leave the tab.
Defaults project positions from the entertainment area: strip segments onto the nearest screen edge, and lamps relative to the strip's extent, so a lamp outside the strip lands beside the screen. Layouts are saved per area; Reset Layout restores the defaults.
Colors. ColorExtractor averages each region. ColorAdjustment then applies the saturation boost
(averages drift toward gray; this restores vividness, keeping hue and brightness) and the minimum
brightness (dark scenes glow faintly instead of switching off).
Smoothing. ScreenSyncEngine eases toward the latest colors at 50 Hz through two exponential stages,
an ease-in/ease-out curve rather than a jolt. Intensity sets the average delay: subtle 0.6 s,
moderate 0.3 s, high 0.18 s, extreme 0.1 s. Even Extreme covers the usual gap between captured frames, and
easing continues while the screen is static.
The first time, macOS asks for Screen Recording permission, and the app has to be relaunched after you allow it.
Capture. A Core Audio process tap (SyncCore/SystemAudioTap.swift, macOS 14.2+) records everything the
Mac plays, without a virtual audio driver and without capturing the display, so it can run while the
Apple TV app plays a movie. macOS asks for System Audio Recording permission; if it's denied, the tap
delivers silence and the meters stay still.
Analysis. AudioAnalyzer runs a 1024-sample FFT (Accelerate) every ~11 ms: loudness and bass
(20-150 Hz) / mid (150-2000 Hz) / high (2-10 kHz) levels, each auto-gained against its own recent peak,
and beats when bass energy jumps 1.45× over its 0.5 s average (at least 0.25 s apart).
Lights. MusicVisualizer colors from a palette (Vivid, Sunset, Ocean, Party) in one of two styles:
Intensity sets how lively Flow moves, or how hard Pulse hits. Silence settles to a soft glow.
_hue._tcp over mDNS, with discovery.meethue.com as a fallback. You can also enter an IP.{"devicetype": "blask-sync#<host>", "generateclientkey": true} to /api every 2 s for
30 s while you press the bridge's link button.username (the CLIP v2 application key) and clientkey (the DTLS PSK for streaming) go into
~/Library/Application Support/BlaskSync/bridge.json, readable only by your user. A file rather than the
Keychain, so builds signed differently (local and release) share the pairing without password prompts.The bridge's certificate is issued by Signify's private CA. The app accepts it only when its common name equals the bridge ID. If the bridge gets a new IP from DHCP, the app finds it again by ID.
Right after launch, macOS can briefly block local network access ("Local network prohibited", reported as error -1009 "offline"). Loading areas retries with backoff before reporting an error.
ChannelDescription classifies channels from the bridge's own data: a light whose entertainment service
has more than one segment is a gradient strip, and each channel member's index is its segment. Everything
else is a lamp, with its archetype (table_shade, hue_play, …) picking the icon.
PUT /clip/v2/resource/entertainment_configuration/{id} with {"action": "start"}, then
opens a DTLS 1.2 PSK session (TLS_PSK_WITH_AES_128_GCM_SHA256) to UDP port 2100. The PSK identity is the
application key and the key is the hex-decoded client key.HueKit/EntertainmentStream.swift).
Nothing is sent until there are real colors, so the lights keep their state instead of flashing a
placeholder. Capture starts while the stream connects.{"action": "stop"} first, with a
2 s cap. The lights then return to their previous state. Without it, the bridge ends the session after
about 10 s without packets.Diagnostics go to the unified log under the local.blasksync subsystem (zsh has a log builtin, so use the
full path):
/usr/bin/log show --last 10m --style compact --predicate 'subsystem == "local.blasksync"'
| Category | Every 5 s / on event |
|---|---|
stream | packets sent to the bridge: rate and largest gap (expect 50/s, no gaps over 50 ms) |
screen-sync | smoothing ticks and captured frames |
capture | ScreenCaptureKit buffers by status (complete / idle / …) and nearly black frames |
bridge | area loading retries |
audio | audio tap start and peak level (permanently "silent" while music plays = no permission) |
defaults write local.blasksync.BlaskSync sync.allowAppNap -bool YES (delete the key to restore).| Path | What |
|---|---|
Sources/HueKit | Hue protocol: discovery, pairing, CLIP v2, credential storage, strip/lamp classification, DTLS streaming |
Sources/SyncCore | Screen and audio capture, screen mapping, color extraction, smoothing, audio analysis and the music visualizer. No UI code |
Sources/BlaskSync | The SwiftUI menu bar app, Settings and Manual Control windows |
Tests/ | Unit tests for HueKit and SyncCore |
Resources/ | Info.plist (LSUIElement, local network usage, Bonjour service) and AppIcon.icns |
scripts/ | bundle.sh (build, bundle, sign), make-appcast.sh (update feed), make-icon.swift and make-icns.sh (app icon) |
scripts/make-icon.swift draws the icon (a monitor with a spectrum halo and a sparkle) with CoreGraphics.
scripts/make-icns.sh renders it at every macOS size into Resources/AppIcon.icns (and the DEV-badged
Resources/AppIcon-Dev.icns for local builds), which bundle.sh copies into the app, and writes
docs/icon.png for this README. From the same render, scripts/make-web-images.swift makes the website's
link preview (docs/social.png, 1200×630) and home screen icon (docs/apple-touch-icon.png). The menu bar icon is drawn in code in Sources/BlaskSync/MenuBarIcon.swift.