Repository navigation
FBD 1.8.0
UNSIGNED BUILD — not signed with a Developer ID and not notarized.
Gatekeeper will block it; see the README Install section.
[1.8.0] - 2026-10-16
Changed
- Redesigned the menu-bar panel (Control Center style, Liquid Glass on macOS 26+); Settings now
opens in its own window. The panel is a borderless popover sized to its content (340 pt wide,
at most 85% of the screen tall, then it scrolls). The front page has each display's brightness
(and DDC volume) slider, quick toggles for Dark Mode, Night Shift, True Tone and Keep Awake,
presets, and rows for virtual screens and display groups. Every other per-display control the old
panel had is on that display's page, one click away — resolution, refresh rate, XDR / HDR, colour
profile, EDID, filters, monitor controls and display actions, plus what the old ⋯ menu held: HiDPI,
Auto Brightness, resolution favourites, presets (apply and save), mirroring, Picture in Picture,
streaming and Show in Settings — withAll Modesas the only second level. The Virtual Screens
page keepsReconnect All. Selections are
flat checkmark lists, and every label is plain language, localised in English, Simplified and
Traditional Chinese. On macOS 26+ the backdrop, toggles, page header, small buttons and slider
thumb use Liquid Glass, and the HUD defaults to glass (a saved choice wins). Earlier macOS
versions, orFBD_DISABLE_GLASS=1, get the same UI with materials. CI and release builds now
require Xcode 26+.
Added
-
Named display presets: a display's whole configuration saved under a name, with an icon.
FBD could recall a resolution (config favourite) and restore a set of protected properties on
reconnect, but it could not name and re-apply a display's whole configuration — which is what
BetterDisplay's presets and the Crisp review's feature list both call for. A preset records the
stateConfigProtectionControlleralready knew how to capture — mode, refresh rate, brightness,
the XDR preset, HDR mode, rotation and the colour profile — under a name the user chooses and one
of a closed set of twelve SF Symbols, closed for the reason the menu-bar icon's set is: a symbol
name that fails to resolve would draw a blank row. Three surfaces, plus Shortcuts:
fbdcli config preset <id> list|save|apply|delete, a per-display Display presets section in
Settings → Displays (icon, name, the properties it carries, apply and delete), a quick apply menu
in the panel, and anApply Display PresetApp Intent. The store reuses the resolution-favourite
shape — one JSON array per display underdisplayPresets.<identity>, the key dropped when the
last preset goes — refuses a name already in use on that display, and applies through the same
path reconnect-restore uses.Two choices are deliberate. A preset does not carry main-display status by default: applying
one must not silently move the menu bar out from under another display, somainStatusis
restored only when it is asked for explicitly. And apply reports what it could not restore: a
preset can name properties a display cannot accept (brightness on a panel with no brightness path,
the XDR preset on a display that is not XDR-capable), and counting those as applied would be a
lie —fbdcliprints askipped:line, the panel says so in its notice, and Settings names them
in the row.The extraction that made it possible is
DisplayStateSnapshot: the privateSavedStatestruct
ConfigProtectionControllerwrote and read, made public. The stored blob is byte-identical, so
a configuration saved by an older FBD still restores. -
The pointer can cross between displays whose edges do not line up. macOS stops the pointer at an
edge no other display covers, so a short display beside a tall one — or one lowered out of line —
traps it in the gap between them.Settings.edgeCrossingEnabled(Settings → Displays, or
fbdcli layout edge-crossing [on|off]) carries it over instead: FBD watches pointer movement and,
when a push reaches an edge a neighbour's facing edge continues, lands the pointer just inside that
neighbour at the height it was pushed at.Three things were measured rather than assumed, and each changed the design. The pointer cannot be
polled for its direction: macOS clamps it to the displays' bounds, so a pointer pinned at an edge
reports zero movement between samples — the push has to come from the event's own hardware delta
(CGGetLastMouseDeltais gone from the Swift SDK). Warping needs no permission, and neither does
a mouse-moved monitor — only key events need Accessibility, which is the trade
CursorContainmentControllerrefuses for an event tap and this feature does not have to make. And
a warp holds hardware input back for a quarter of a second by default, which reads as the pointer
sticking, so the suppression interval is zeroed before each warp.It fails closed. A neighbouring display that does not actually meet the edge means nothing happens —
a deliberate gap is respected, and the geometry refuses to move a pointer macOS would have crossed by
itself. It is inert with one display, and if the monitor never fires it simply does nothing: it can
never fight the user for the pointer. Off by default, like everything else in FBD that moves it.
Fixed
- DDC/CI now uses the framing Apple Silicon monitors expect (5-argument
IOAVServiceI2C calls,
0x84 0x03/0x82 0x01packets, XOR checksum), matching the reference implementation. Brightness,
contrast, volume, input and mute over DDC are rewritten accordingly; not yet verified on hardware (#7). - Mute media key no longer only unmutes; DDC slider drags send the latest value instead of queueing
every step; the DDC service cache is refreshed on display changes; EDID product code and serial
are read little-endian. - XDR upscaling above the hardware ceiling falls back to the software boost when the native preset
write is refused (macOS 27); ending a boost no longer removes the dim or rounded-corner overlays. - A built-in display disabled by FBD, and a lock-dimmed brightness, are restored after a crash or quit.
fbdcli: a bad token or timeout is an error instead of silently driving the hardware directly;
--directinside automation payloads is preserved;helplistsenableon its own line; zsh
completion covers every command.- Creating a virtual screen no longer crashes FBD. The SkyLight calls that build a virtual
display were handed pointers to a bridgedNSString(the name) andNSArray(the optional modes)
that were temporaries: Swift released them as soon as the pointer was taken, while SkyLight kept
using it, so creating a screen from the app crashed. Both objects are now kept alive for the whole
call.
Security
- The HTTP API token and LG TV pairing key moved from preferences to the Keychain (migrated on first
run).fbdclimay show a one-time Keychain prompt. - The local API rejects browser requests (non-loopback
Hostor anyOrigin), sends no CORS headers,
caps concurrent connections and no longer falls back to a guessable token. FBD_DISPLAY_NAME(from the monitor's EDID) is sanitised before it reaches automation scripts.