NotchOverlayManager: Handling MacBook Notch-Aware Displays in FluidVoice
NotchOverlayManager is a singleton service in the FluidVoice macOS app that orchestrates overlay UI within the MacBook's physical notch, automatically detecting display capabilities and managing transitions between expanded and compact presentation modes via DynamicNotchKit.
The NotchOverlayManager sits at the heart of FluidVoice's user interface layer, bridging the gap between the audio processing pipeline and the visual feedback system. As implemented in altic-dev/FluidVoice, this singleton class abstracts the complexity of the MacBook's camera housing, turning it into a functional UI element that displays recording status, transcription previews, and command outputs without encroaching on the main workspace.
What Is NotchOverlayManager?
Core Architecture
The NotchOverlayManager follows a strict singleton pattern accessible via NotchOverlayManager.shared. It acts as a centralized coordinator that consumes audio level publishers, monitors the active application, and delegates rendering to DynamicNotchKit, a third-party framework that creates SwiftUI-backed overlays conforming to the physical notch geometry.
Located in [Sources/Fluid/Services/NotchOverlayManager.swift](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/NotchOverlayManager.swift), the class maintains internal state machines for the current presentation mode, tracks generation counters to prevent race conditions during rapid show-hide cycles, and caches the last audio publisher to support seamless re-presentation after temporary dismissal.
Screen Detection and Compact Support
Before presenting any UI, the manager interrogates the display hardware through OverlayScreenResolver.screenForCurrentPointer(). It specifically checks for auxiliary safe areas that indicate a true MacBook notch display:
let supportsCompact = screen.auxiliaryTopLeftArea != nil || screen.auxiliaryTopRightArea != nil
When supportsCompact evaluates to true, the manager can elect a compact presentation style that occupies only the notched portion of the menu bar. On standard external displays lacking these auxiliary areas, it automatically falls back to a full expanded presentation that respects the standard menu bar height.
How NotchOverlayManager Handles MacBook Notch Displays
DynamicNotchKit Integration
The manager instantiates a DynamicNotch object configured with SwiftUI view hierarchies defined in [Sources/Fluid/Views/NotchContentViews.swift](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Views/NotchContentViews.swift). Depending on the computed policy, it injects one of several view configurations:
NotchExpandedView– Full-height overlay for screens without notch support or when user preferences demand maximum visibility.NotchCompactLeadingView/NotchCompactTrailingView– Split views that align to the left and right of the physical camera housing.NotchCompactBottomView– An alternative compact layout for bottom-aligned presentations.
Presentation Policies
The NotchPresentationPolicy structure (constructed via NotchPresentationPolicy.forMode) determines which visual elements appear inside the notch. This policy respects the user's choice in SettingsStore.shared.notchPresentationMode, which can be set to standard or minimal. In minimal mode, the manager suppresses the prompt selector and streaming preview, showing only the essential recording indicator and waveform.
The Show/Hide Lifecycle
Entry points for visibility are strictly controlled through show(audioLevelPublisher:mode:). This method:
- Increments an internal generation counter to invalidate pending async blocks from previous calls.
- Resolves the target screen and selects the appropriate layout strategy.
- Constructs the
DynamicNotchinstance and binds the audio level publisher to the waveform visualization. - Invokes
notch.show()with the configured animation duration.
Dismissal follows a similarly rigorous path through hide(), which cancels active app monitoring, terminates retry timers, and triggers the notch's dismissal animation before nullifying the internal reference.
Command Output Expansion Mode
When the user triggers a command that generates textual output (such as a code explanation or translation), the manager transitions from the standard recording notch to a specialized command view:
NotchOverlayManager.shared.showExpandedCommandOutput()
This method hides the primary notch, instantiates a new DynamicNotch configured with NotchCommandOutputExpandedView, and presents it with a spring animation. The toggleExpandedCommandOutput() convenience method allows the UI to switch between collapsed and expanded states without reconstructing the entire overlay hierarchy.
Bottom Overlay Fallback
If SettingsStore.shared.overlayPosition equals .bottom, the manager bypasses the notch entirely and delegates rendering to BottomOverlayWindowController. This path supports the same audio publisher and mode logic but positions the UI as a floating panel above the Dock rather than inside the menu bar, accommodating users who prefer traditional overlay placements or work with external displays lacking a physical notch.
Key Implementation Details
Audio Publisher Management
The manager stores a reference to the most recent AnyPublisher<Float, Never> via lastAudioPublisher. This caching mechanism enables the show() method to resume visualization immediately when the user invokes a mode change without requiring the audio engine to reinstantiate its publishers.
Asynchronous State Safety
To prevent stale closures from corrupting the UI during rapid context switches, every show operation increments a generation integer. Completion handlers and asynchronous screen resolution blocks capture this generation value and abort execution if the stored activeGeneration has changed, eliminating race conditions between sequential show() and hide() calls.
Code Examples
Configure and display a standard dictation overlay that respects the current screen's notch capabilities:
import Combine
let audioPublisher = audioEngine.levelPublisher.eraseToAnyPublisher()
NotchOverlayManager.shared.show(
audioLevelPublisher: audioPublisher,
mode: .dictation
)
Switch to compact presentation mode for supported displays:
SettingsStore.shared.notchPresentationMode = .minimal
NotchOverlayManager.shared.setMode(.dictation)
Display command output results in an expanded notch view:
NotchOverlayManager.shared.showExpandedCommandOutput()
// Later, dismiss the command view
NotchOverlayManager.shared.hideExpandedCommandOutput()
Handle user interaction when the notch is clicked:
NotchOverlayManager.shared.onNotchClicked = {
if NotchOverlayManager.shared.isShowingCommandOutput {
NotchOverlayManager.shared.hideExpandedCommandOutput()
} else {
NotchOverlayManager.shared.toggleExpandedCommandOutput()
}
}
Update the transcription text in real-time while streaming preview is enabled:
NotchOverlayManager.shared.updateTranscriptionText("Processing your request...")
Source File Reference
| File | Purpose |
|---|---|
| NotchOverlayManager.swift | Singleton coordinator managing DynamicNotchKit integration, lifecycle, and presentation policies. |
| NotchContentViews.swift | SwiftUI view definitions for expanded, compact, and command-output notch states. |
| SettingsStore.swift | User defaults wrapper exposing notchPresentationMode and overlayPosition preferences. |
| BottomOverlayWindowController.swift | Alternative window controller for bottom-screen overlay when notch presentation is disabled. |
| ActiveAppMonitor.swift | Observes frontmost application changes to update the overlay's application icon display. |
Summary
- NotchOverlayManager is a singleton service that abstracts MacBook notch complexity through DynamicNotchKit, providing a unified API for overlay presentation.
- The manager automatically detects notch-compatible displays by checking
auxiliaryTopLeftAreaandauxiliaryTopRightArea, falling back gracefully on external monitors. - It supports both standard and compact presentation modes driven by
NotchPresentationPolicyand user preferences stored in SettingsStore. - The show/hide lifecycle uses generation counters to prevent race conditions during rapid UI transitions.
- For non-notch displays or user preference, the manager can redirect output to a bottom overlay via
BottomOverlayWindowController.
Frequently Asked Questions
What is the purpose of the NotchOverlayManager in FluidVoice?
NotchOverlayManager centralizes all user interface elements that appear in or around the MacBook's camera notch. It handles audio visualization, transcription previews, command output display, and mode indicators, ensuring these elements render correctly regardless of screen type while preventing visual overlap with system menu bar items.
How does NotchOverlayManager detect if a MacBook screen supports compact notch presentation?
The manager queries the NSScreen instance returned by OverlayScreenResolver.screenForCurrentPointer() for the presence of auxiliaryTopLeftArea or auxiliaryTopRightArea. If these properties are non-nil, the screen is identified as having a physical camera housing capable of supporting the compact split-view layout; otherwise, the manager defaults to the full expanded presentation style.
Can NotchOverlayManager display UI at the bottom of the screen instead of the notch?
Yes. When SettingsStore.shared.overlayPosition is set to .bottom, the manager instantiates BottomOverlayWindowController instead of DynamicNotch, positioning a floating panel above the Dock. This behavior maintains feature parity—including audio visualization and command output—while accommodating user preference or hardware configurations where the notch is unavailable.
How does the manager handle audio level visualization?
The manager accepts an AnyPublisher<Float, Never> via show(audioLevelPublisher:mode:) and binds it to the waveform views inside the notch content. It caches the publisher reference in lastAudioPublisher to support seamless re-presentation, allowing the audio engine to maintain continuous emission without recreating publishers between hide and show operations.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →