Skip to content

Architecture

Christopher Murphy edited this page Feb 22, 2026 · 3 revisions

Architecture Overview

Last Updated: February 2026 Version: 1.0.14

Executive Summary

Aegis is a macOS menu bar application that integrates with Yabai window manager to provide:

  • Visual workspace/space indicators in the menu bar
  • Notch-area HUD for system status (volume, brightness, media, Bluetooth devices, Focus mode)
  • System status display (battery, network, Focus mode, time)
  • Window management via drag-drop and gestures
  • Custom app switcher (Cmd+Tab replacement)
  • Multi-display support with configurable modes

Architecture Principles

  1. Event-driven: EventRouter pub/sub decouples services from UI
  2. Persistent ViewModels: ViewModels survive view rebuilds for smooth animations
  3. Frame-locked animation: CVDisplayLink for vsync-driven progress bars
  4. Window lifecycle: Windows created once at startup, reused throughout session
  5. Configuration-driven: Centralized AegisConfig singleton for all customization

Component Hierarchy

Aegis/
├── App/                    # Application entry point
├── Core/                   # Configuration and services
│   ├── Config/            # Centralized settings
│   ├── Models/            # Core data models
│   └── Services/          # Business logic services
├── Components/            # UI components
│   ├── MenuBar/          # Workspace indicator UI
│   ├── Notch/            # Notch HUD system
│   ├── Systemstatus/     # System monitoring UI
│   └── SettingsPanel/    # Settings interface
├── Helpers/              # Shared utilities
└── AegisYabaiIntegration/ # Shell scripts

Core Components

1. App Layer (App/)

AegisApp.swift

  • SwiftUI app entry point
  • Registers AppDelegate for lifecycle management
  • Provides Settings window

AppDelegate.swift

  • Responsibilities:
    • Initialize all services (Yabai, SystemInfo, Music)
    • Create EventRouter and inject into services
    • Setup MenuBar and Notch components
    • Subscribe to events and route to UI
    • Request automation permissions
    • Check and show setup prompt if needed

2. Core Configuration (Core/Config/)

AegisConfig.swift (~1000 lines)

  • Purpose: Centralized configuration singleton

  • Scope: 100+ @Published properties for every UI/behavior setting

  • Categories:

    • Menu bar layout (height, padding, spacing, corner radii)
    • Space indicators (size, icon display, overflow behavior)
    • Typography (font sizes for labels, values, headers)
    • Animation settings (spring response, damping, durations)
    • System status thresholds (battery levels, WiFi strength)
    • Notch HUD (size, position, progress bar dimensions)
    • Color schemes and opacity values
  • Persistence: UserDefaults for settings recovery across launches

  • Access Pattern: All views use @ObservedObject private var config = AegisConfig.shared


3. Core Services (Core/Services/)

EventRouter.swift

  • Purpose: Pub/sub event bus decoupling services from UI

  • Events:

    • .spaceChanged: Workspace switched
    • .windowsChanged: Window added/removed/moved
    • .volumeChanged: System volume adjusted
    • .brightnessChanged: Display brightness adjusted
    • .musicPlaybackChanged: Music track changed
  • Thread Safety: All handler calls dispatched to main thread

YabaiService.swift

  • Purpose: Interface to Yabai window manager
  • Responsibilities:
    1. Query spaces and windows
    2. Execute commands (focus, move, create, destroy)
    3. Monitor Yabai events via FIFO pipe
    4. Provide window icons from app bundles

SystemInfoService.swift

  • Purpose: Monitor hardware state (volume, brightness, battery)
  • Components:
    • Volume: CoreAudio API for system volume and mute state
    • Brightness: Private API via Objective-C helper
    • Battery: IOKit for battery level, charging state, time remaining

MusicService.swift (now MediaService)

  • Purpose: Integrate with media sources for now-playing display
  • Capabilities:
    • Query current track (title, artist, album) from any media source
    • Fetch album artwork asynchronously
    • Detect playback state (playing/paused)
    • Works with Music.app, Spotify, browsers, video players, etc.

BluetoothDeviceService.swift

  • Purpose: Monitor Bluetooth device connections/disconnections
  • Capabilities:
    • Detect device connect/disconnect events via IOBluetooth
    • Identify device types (AirPods, keyboards, mice, etc.)
    • Fetch battery levels via system_profiler

AppSwitcherService.swift

  • Purpose: Custom Cmd+Tab window switcher
  • Capabilities:
    • Intercept Cmd+Tab via CGEvent tap
    • Display windows organized by space
    • Multiple input methods: keyboard, mouse hover, two-finger scroll
    • Type-to-filter search

4. MenuBar Component (Components/MenuBar/)

Purpose: Display Yabai workspaces as interactive indicators

Architecture Pattern

MenuBarController (Facade)
    ↓
MenuBarCoordinator (Orchestrator)
    ↓ creates
MenuBarWindowController (Window management)
MenuBarViewModel (State)
MenuBarInteractionMonitor (Native menu bar tracking)
    ↓ renders
SpaceIndicatorView (UI)

Key Files

  • DisplayMenuBarManager.swift: Manages menu bars across multiple displays
  • MenuBarCoordinator.swift: Central orchestrator for a single display
  • MenuBarViewModel.swift: State management with @Published properties
  • SpaceIndicatorView.swift: Main UI view for workspace display
  • SpaceDropController.swift: Drag-drop delegate for moving windows

Multi-Display Architecture

DisplayMenuBarManager (Multi-display orchestrator)
    ↓ creates one per display
MenuBarCoordinator (Per-display orchestrator)
    ↓ with display-specific
MenuBarViewModel (spaceFilterMode: .display(index))
    ↓ renders filtered
SpaceIndicatorView (Only spaces for this display)

Display detection:

  • NSApplication.didChangeScreenParametersNotification for high-level changes
  • CGDisplayRegisterReconfigurationCallback for instant, granular detection
  • Supports add/remove/move/setMain display events

Multi-monitor modes:

  • Auto: Single display = primary only, multiple = per monitor
  • Primary Only: Menu bar only on main display
  • Per Monitor: Each display shows only its own spaces
  • All Show All: Every display shows all spaces

5. Notch HUD Component (Components/Notch/)

Purpose: Display system notifications at notch location

Architecture Pattern

NotchHUDController (Window + visibility management)
    ↓ owns
OverlayHUDViewModel (Volume/brightness state)
    ↓ contains
ProgressBarAnimator (Frame-locked interpolation)
    ↓ renders
MinimalHUDWrapper (UI)

NotchHUDController
    ↓ also owns
MediaHUDViewModel → MediaHUDView (Now Playing)
DeviceHUDViewModel → DeviceHUDView (Bluetooth devices)
FocusHUDViewModel → FocusHUDView (Focus mode)

ProgressBarAnimator.swift

  • Frame-locked interpolation for smooth progress bar
  • Uses CVDisplayLink for vsync-driven callbacks (60Hz)
  • Exponential smoothing: displayed += (target - displayed) * 0.35

MediaHUDView.swift

  • Displays album art and either visualizer or track info
  • Marquee scrolling for long text
  • Works with all media sources via MediaRemote framework

DeviceHUDView.swift

  • Shows Bluetooth device connections/disconnections
  • Battery ring indicator
  • Supports AirPods, headphones, keyboards, mice, etc.

FocusHUDView.swift

  • Shows Focus mode changes
  • Displays mode name and icon from user's configuration

6. System Status Component (Components/Systemstatus/)

Purpose: Display system information in menu bar

Architecture

SystemStatusMonitor (Aggregates all status)
    ↓
BatteryStatusMonitor (Battery specific)
NetworkStatus (Network model)
FocusStatusMonitor (Focus mode detection)
    ↓
SystemStatusView (Container)
    ↓
Individual icon views (Battery, Network, Focus, Clock, Date)

FocusStatusMonitor.swift

  • Watches ~/Library/DoNotDisturb/DB/ for Focus mode changes
  • Parses Assertions.json to detect active Focus mode
  • Reads ModeConfigurations.json for mode names and SF Symbols

Data Flow Diagrams

Volume/Brightness HUD Flow

User presses volume key
    ↓
SystemInfoService detects change (CoreAudio listener)
    ↓
EventRouter.publish(.volumeChanged, level: 0.75)
    ↓
AppDelegate subscription fires
    ↓
NotchHUDController.showVolume(level: 0.75)
    ↓
overlayViewModel.progressAnimator.setTarget(0.75)
    ↓
CVDisplayLink ticks at 60Hz (vsync)
    ↓
ProgressBarAnimator interpolates displayed value
    ↓
SwiftUI re-renders progress bar

Space Change Flow

User switches space (Yabai command)
    ↓
Yabai writes event to FIFO pipe
    ↓
YabaiService reads pipe, parses event
    ↓
EventRouter.publish(.spaceChanged)
    ↓
MenuBarCoordinator subscription fires
    ↓
MenuBarCoordinator.updateSpaces()
    ↓
SpaceIndicatorView re-renders with new active state

Performance Optimizations

1. Frame-Locked Animation

  • CVDisplayLink provides vsync-driven interpolation independent of main thread
  • Coalescing with updatePending flag prevents SwiftUI re-render storms
  • Bypass ViewModel updates during rapid input

2. Window Lifecycle Management

  • Windows created once at startup, reused throughout session
  • isReleasedWhenClosed = false prevents deallocation
  • Force layout pass before first show prevents jank

3. Event-Driven Architecture

  • FIFO pipe provides real-time Yabai events
  • EventRouter decouples services from UI
  • Fallback polling every 10s if pipe unavailable

4. Persistent ViewModels

  • ViewModels owned by controllers, survive view rebuilds
  • ProgressBarAnimator instance persists in ViewModel
  • State survives SwiftUI view invalidation

External Integrations

Yabai Window Manager

  • Communication: Shell commands via Process
  • Event Monitoring: FIFO pipe at ~/.config/aegis/yabai.pipe
  • Setup: User configures yabairc to write events to pipe

Media Sources (via MediaRemote)

  • Communication: MediaRemote framework via mediaremote-adapter
  • Sources: Music.app, Spotify, Safari, Chrome, Firefox, video players, etc.
  • Queries: Current track, playback state, bundle identifier
  • Artwork: Fetched asynchronously, cached per-track

System APIs

  • CoreAudio: Volume level and mute state
  • CoreGraphics: Display reconfiguration callbacks for monitor detection
  • IOKit: Battery status
  • IOBluetooth: Device connection/disconnection notifications
  • Private API: Brightness monitoring
  • AppKit: Window management, screen geometry
  • DispatchSource: File system monitoring for Focus mode changes

File Count by Component

Component Files Total Lines (approx)
App 2 200
Core/Config 2 1,800
Core/Services 6 1,600
MenuBar 13 2,500
Notch 12 1,200
Systemstatus 12 900
SettingsPanel 3 1,500
Total ~50 ~9,700

Quick Reference: Key Files

File Purpose
AppDelegate.swift App initialization, service setup, event routing
AegisConfig.swift Centralized configuration singleton
AegisConfigFile.swift JSON config file support with hot-reload
EventRouter.swift Pub/sub event bus
YabaiService.swift Yabai WM integration
BluetoothDeviceService.swift Bluetooth device monitoring
AppSwitcherService.swift Custom Cmd+Tab window switcher
DisplayMenuBarManager.swift Multi-display menu bar management
MenuBarCoordinator.swift Per-display menu bar orchestration
SpaceIndicatorView.swift Workspace UI display
NotchHUDController.swift Notch HUD window management
ProgressBarAnimator.swift Frame-locked interpolation
MediaHUDView.swift Now Playing UI
DeviceHUDView.swift Bluetooth device HUD
FocusStatusMonitor.swift Focus mode detection

Clone this wiki locally