Skip to content

FBD 1.8.0

Choose a tag to compare

@github-actions github-actions released this 16 Oct 13:58
· 49 commits to main since this release

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 — with All Modes as the only second level. The Virtual Screens
    page keeps Reconnect 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, or FBD_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
    state ConfigProtectionController already 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 an Apply Display Preset App Intent. The store reuses the resolution-favourite
    shape — one JSON array per display under displayPresets.<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, so mainStatus is
    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 — fbdcli prints a skipped: line, the panel says so in its notice, and Settings names them
    in the row.

    The extraction that made it possible is DisplayStateSnapshot: the private SavedState struct
    ConfigProtectionController wrote 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
    (CGGetLastMouseDelta is 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
    CursorContainmentController refuses 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 IOAVService I2C calls,
    0x84 0x03 / 0x82 0x01 packets, 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;
    --direct inside automation payloads is preserved; help lists enable on 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 bridged NSString (the name) and NSArray (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). fbdcli may show a one-time Keychain prompt.
  • The local API rejects browser requests (non-loopback Host or any Origin), 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.