Files
james.maesandClaude Opus 4.7 ccb2bb7119 Add About dialog with full credits
⌘-click VisionPOC → "About VisionPOC" (or click the About menu item at
the top of the app menu) opens a dedicated About window styled to match
the HUD: cyan / mint / amber / magenta section headers, monospaced
typography, dark-aqua chrome.

Sections:
- WHAT IT IS — role in the Jarvis program, sibling to MetalPOC + VoicePOC
- DETECTION — Ultralytics YOLOv8x-OIV7 (601 classes), Apple Vision
  framework (text + faces + body/hand pose + animals + tracking + image
  feature print), Core ML + ANE
- RENDERING — Metal 4 + MetalKit, MPSImageSobel, AVFoundation +
  CVMetalTextureCache
- TYPOGRAPHY — Share Tech Mono (Carrois Apostrophe), Orbitron (Matt
  McInerney), both under SIL OFL
- INSPIRATION — Jarvis / Iron Man cinematic HUD
- LICENSE — MIT 2026 James Maes, clickable GitHub link
- BUILT WITH — Claude Opus + Claude Code, XcodeGen, git-lfs

Version string is pulled live from the Info.plist
(CFBundleShortVersionString + CFBundleVersion) so it always reflects
what was built. Window position persists under VPOC.AboutWindowFrame.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 18:12:16 -05:00

551 lines
22 KiB
Swift

import AppKit
import AVFoundation
import CoreText
import Metal
import SwiftUI
import Vision
@main
final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate, @unchecked Sendable {
private var windowController: MainWindowController?
@MainActor private var settingsController: SettingsWindowController?
@MainActor private var logController: LogStreamWindowController?
@MainActor private var historyController: HistoryWindowController?
@MainActor private var statusController: StatusWindowController?
@MainActor private var aboutController: AboutWindowController?
@MainActor private var cameraDevicesSubmenu: NSMenu?
@MainActor private var recorder: Recorder?
@MainActor private var privacyMenuItem: NSMenuItem?
@MainActor private var recordMenuItem: NSMenuItem?
static func main() {
let app = NSApplication.shared
let delegate = AppDelegate()
app.delegate = delegate
app.setActivationPolicy(.regular)
app.run()
}
func applicationDidFinishLaunching(_ notification: Notification) {
LogStream.shared.log("VisionPOC starting up", level: .info, source: .app)
AppDelegate.registerBundledFonts()
let controller = MainWindowController()
windowController = controller
controller.showWindow(nil)
// Wake the history store so it starts subscribing to the event bus
// immediately, before the History window has ever been opened.
_ = HistoryStore.shared
installMainMenu()
NSApp.activate(ignoringOtherApps: true)
LogStream.shared.log("ready — main window shown", level: .info, source: .app)
}
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
true
}
@MainActor
private func installMainMenu() {
let mainMenu = NSMenu()
let appName = ProcessInfo.processInfo.processName
// -----------------------------------------------------------------
// Application menu (VisionPOC): app-level commands only — Settings,
// Hide, Quit. Convention: Mac apps put their config and
// lifecycle here, NOT everything that doesn't fit elsewhere.
// -----------------------------------------------------------------
let appMenuItem = NSMenuItem()
mainMenu.addItem(appMenuItem)
let appMenu = NSMenu()
appMenu.addItem(menuItem("About \(appName)", action: #selector(openAbout(_:)), key: "", mods: []))
appMenu.addItem(.separator())
appMenu.addItem(menuItem("Settings…", action: #selector(openSettings(_:)), key: ",", mods: [.command]))
appMenu.addItem(.separator())
appMenu.addItem(menuItem("Hide \(appName)", action: #selector(NSApplication.hide(_:)), key: "h", mods: [.command], target: NSApp))
let hideOthers = menuItem("Hide Others", action: #selector(NSApplication.hideOtherApplications(_:)), key: "h", mods: [.command, .option], target: NSApp)
appMenu.addItem(hideOthers)
appMenu.addItem(menuItem("Show All", action: #selector(NSApplication.unhideAllApplications(_:)), key: "", mods: [], target: NSApp))
appMenu.addItem(.separator())
appMenu.addItem(menuItem("Quit \(appName)", action: #selector(NSApplication.terminate(_:)), key: "q", mods: [.command], target: NSApp))
appMenuItem.submenu = appMenu
// -----------------------------------------------------------------
// File menu — actions that write something to disk.
// -----------------------------------------------------------------
let fileItem = NSMenuItem()
mainMenu.addItem(fileItem)
let fileMenu = NSMenu(title: "File")
fileMenu.addItem(menuItem("Save Snapshot", action: #selector(saveSnapshot(_:)), key: "s", mods: [.command]))
let record = menuItem("Start Recording", action: #selector(toggleRecording(_:)), key: "r", mods: [.command, .shift])
fileMenu.addItem(record)
recordMenuItem = record
fileItem.submenu = fileMenu
// -----------------------------------------------------------------
// View menu — windows / panels you can open. All of these are
// observability surfaces over the same underlying detector +
// event-bus state.
// -----------------------------------------------------------------
let viewItem = NSMenuItem()
mainMenu.addItem(viewItem)
let viewMenu = NSMenu(title: "View")
viewMenu.addItem(menuItem("Status Panel", action: #selector(openStatus(_:)), key: "i", mods: [.command]))
viewMenu.addItem(menuItem("Detection History", action: #selector(openHistory(_:)), key: "h", mods: [.command, .shift]))
viewMenu.addItem(menuItem("Log Stream", action: #selector(openLogStream(_:)), key: "l", mods: [.command]))
viewItem.submenu = viewMenu
// -----------------------------------------------------------------
// Camera menu — devices listed directly (no nested submenu), then
// Refresh, then Privacy. Privacy lives here because its primary
// effect is gating what the camera-facing detectors do.
// -----------------------------------------------------------------
let cameraItem = NSMenuItem()
mainMenu.addItem(cameraItem)
let cameraMenu = NSMenu(title: "Camera")
cameraMenu.delegate = self
// The dynamic device list is rebuilt by menuNeedsUpdate(_:) each
// time the menu opens. We retain a reference so the rebuild and
// the privacy/refresh items stay distinct.
cameraDevicesSubmenu = cameraMenu
cameraItem.submenu = cameraMenu
// -----------------------------------------------------------------
// Faces menu — enrollment of known people.
// -----------------------------------------------------------------
let facesItem = NSMenuItem()
mainMenu.addItem(facesItem)
let facesMenu = NSMenu(title: "Faces")
facesMenu.addItem(menuItem("Enroll Face…", action: #selector(enrollFace(_:)), key: "e", mods: [.command]))
facesMenu.addItem(menuItem("Forget Face…", action: #selector(forgetFace(_:)), key: "e", mods: [.command, .shift]))
facesItem.submenu = facesMenu
NSApplication.shared.mainMenu = mainMenu
}
/// Small helper to keep the menu-building above readable.
@MainActor
private func menuItem(_ title: String,
action: Selector?,
key: String,
mods: NSEvent.ModifierFlags,
target: AnyObject? = nil) -> NSMenuItem {
let item = NSMenuItem(title: title, action: action, keyEquivalent: key)
item.keyEquivalentModifierMask = mods
item.target = target ?? self
return item
}
// MARK: - Camera menu (dynamic)
@MainActor
@objc func menuNeedsUpdate(_ menu: NSMenu) {
guard menu === cameraDevicesSubmenu else { return }
rebuildCameraDevicesMenu(menu)
}
@MainActor
private func rebuildCameraDevicesMenu(_ menu: NSMenu) {
// Rebuilds the entire Camera menu in place: device list (flat),
// separator, Refresh List, separator, Privacy Mode toggle.
menu.removeAllItems()
let discovery = AVCaptureDevice.DiscoverySession(
deviceTypes: [.builtInWideAngleCamera, .external, .continuityCamera],
mediaType: .video,
position: .unspecified
)
let devices = discovery.devices
if devices.isEmpty {
let empty = NSMenuItem(title: "No cameras found", action: nil, keyEquivalent: "")
empty.isEnabled = false
menu.addItem(empty)
} else {
let activeID = windowController?.activeCameraUniqueID
for device in devices {
let item = NSMenuItem(
title: device.localizedName,
action: #selector(selectCameraDevice(_:)),
keyEquivalent: ""
)
item.target = self
item.representedObject = device
if device.uniqueID == activeID {
item.state = .on
}
menu.addItem(item)
}
}
menu.addItem(.separator())
let refresh = NSMenuItem(
title: "Refresh List",
action: #selector(refreshCameraList(_:)),
keyEquivalent: ""
)
refresh.target = self
menu.addItem(refresh)
menu.addItem(.separator())
let privacyTitle = Theme.Performance.faceRecognitionDisabled
? "Privacy Mode: ON"
: "Privacy Mode: OFF"
let privacy = NSMenuItem(
title: privacyTitle,
action: #selector(togglePrivacyMode(_:)),
keyEquivalent: "p"
)
privacy.keyEquivalentModifierMask = [.command, .shift]
privacy.target = self
menu.addItem(privacy)
privacyMenuItem = privacy
}
@MainActor
@objc private func refreshCameraList(_ sender: Any?) {
guard let menu = cameraDevicesSubmenu else { return }
rebuildCameraDevicesMenu(menu)
}
@MainActor
@objc private func selectCameraDevice(_ sender: Any?) {
guard let item = sender as? NSMenuItem,
let device = item.representedObject as? AVCaptureDevice else { return }
windowController?.switchCamera(to: device)
}
// MARK: - Snapshot
@MainActor
@objc private func saveSnapshot(_ sender: Any?) {
windowController?.saveSnapshotToDesktop()
}
// MARK: - Settings
@MainActor
@objc private func openSettings(_ sender: Any?) {
if settingsController == nil {
// The detector is owned by the main window controller. We pass a
// closure that the Diagnostics section polls at 2 Hz; returning
// nil hides the Diagnostics block (e.g. before the window exists
// or if the detector hasn't started reporting timings yet).
let provider: @MainActor () -> DiagnosticsTimingSnapshot? = { [weak self] in
self?.currentDetectorTiming()
}
settingsController = SettingsWindowController(timingProvider: provider)
}
settingsController?.showWindow(nil)
settingsController?.window?.makeKeyAndOrderFront(nil)
NSApp.activate(ignoringOtherApps: true)
}
/// Reads `detector.lastStageTiming` reflectively via KVC. The detector is
/// owned by Agent A; if they haven't added the property yet, KVC returns
/// nil and the Diagnostics block stays hidden. This keeps the App layer
/// from build-coupling to a Process-layer symbol that may not exist.
@MainActor
private func currentDetectorTiming() -> DiagnosticsTimingSnapshot? {
guard let detector = windowController?.detector else { return nil }
// Use Mirror to look for a `lastStageTiming` member without naming
// the type. If it isn't present, return nil; otherwise project its
// children into our snapshot struct by label.
let mirror = Mirror(reflecting: detector)
guard let raw = mirror.descendant("lastStageTiming") else { return nil }
let inner = Mirror(reflecting: raw)
var snap = DiagnosticsTimingSnapshot.zero
for child in inner.children {
guard let label = child.label else { continue }
let value = (child.value as? Double) ?? Double((child.value as? Float) ?? 0)
switch label {
case "visionBundleMs": snap.visionBundleMs = value
case "featurePrintMs": snap.featurePrintMs = value
case "trackerMs": snap.trackerMs = value
case "totalMs": snap.totalMs = value
default: break
}
}
return snap
}
@MainActor
@objc private func openLogStream(_ sender: Any?) {
if logController == nil {
logController = LogStreamWindowController()
}
logController?.showWindow(nil)
logController?.window?.makeKeyAndOrderFront(nil)
NSApp.activate(ignoringOtherApps: true)
}
@MainActor
@objc private func openHistory(_ sender: Any?) {
if historyController == nil {
historyController = HistoryWindowController()
}
historyController?.showWindow(nil)
historyController?.window?.makeKeyAndOrderFront(nil)
NSApp.activate(ignoringOtherApps: true)
}
@MainActor
@objc private func openAbout(_ sender: Any?) {
if aboutController == nil {
aboutController = AboutWindowController()
}
aboutController?.showWindow(nil)
aboutController?.window?.makeKeyAndOrderFront(nil)
NSApp.activate(ignoringOtherApps: true)
}
@MainActor
@objc private func openStatus(_ sender: Any?) {
if statusController == nil {
statusController = StatusWindowController(detectorAccess: { [weak self] in
self?.buildDetectorSnapshot()
})
}
statusController?.showWindow(nil)
statusController?.window?.makeKeyAndOrderFront(nil)
NSApp.activate(ignoringOtherApps: true)
}
@MainActor
private func buildDetectorSnapshot() -> DetectorSnapshot? {
guard let wc = windowController else { return nil }
let det = wc.detector
let detections = det.currentDetections()
// Partition into face vs. object entries. A detection's label is a
// recognized name when it's an enrolled face — those labels are
// uppercased and don't appear in the COCO/OIV7 class set. Treat
// anything explicitly named "FACE" or matching an enrolled name as
// a face entry; everything else is an object.
let enrolledUpper = Set(det.faceRegistry.enrolledNames().map { $0.uppercased() })
var faces: [DetectorSnapshot.FaceEntry] = []
var objects: [DetectorSnapshot.ObjectEntry] = []
for d in detections {
if d.label == "FACE" || enrolledUpper.contains(d.label) {
faces.append(.init(name: d.label.lowercased(), distance: nil))
} else {
let rgba = ColorHash.colorFor(trackId: d.trackId)
objects.append(.init(
label: d.label,
confidence: d.confidence,
color: Color(red: Double(rgba.x),
green: Double(rgba.y),
blue: Double(rgba.z))
))
}
}
// Sort objects by confidence descending so the most-confident detections lead.
objects.sort { $0.confidence > $1.confidence }
return DetectorSnapshot(
fps: wc.renderer.smoothedFPSForReadout,
lastInferenceMs: det.lastInferenceMs,
detectMode: det.lastDetectMode.rawValue,
objects: objects,
faces: faces
)
}
// MARK: - Privacy Mode
@MainActor
@objc private func togglePrivacyMode(_ sender: Any?) {
let newValue = !Theme.Performance.faceRecognitionDisabled
Theme.Performance.faceRecognitionDisabled = newValue
Theme.Performance.greeterMuted = newValue
TunableSettings.shared.greeterMuted = newValue
privacyMenuItem?.title = newValue ? "Privacy Mode: ON" : "Privacy Mode: OFF"
LogStream.shared.log(
"privacy mode \(newValue ? "enabled" : "disabled")",
level: .info,
source: .app
)
}
// MARK: - Recording
@MainActor
@objc private func toggleRecording(_ sender: Any?) {
if let rec = recorder, rec.isRecording {
stopRecordingFlow(rec)
} else {
startRecordingFlow()
}
}
@MainActor
private func startRecordingFlow() {
guard let renderer = windowController?.renderer,
let device = MTLCreateSystemDefaultDevice(),
let rec = Recorder(device: device) else {
showRecordingResult(url: nil, started: false)
return
}
// Use the renderer's drawable size if available, otherwise the main
// screen. Recorder needs an integer width/height to allocate the
// AVAssetWriterInput at the right resolution.
let size = currentRecordingSize()
let started = rec.startRecording(width: size.width, height: size.height)
guard started else {
recorder = nil
showRecordingResult(url: nil, started: false)
return
}
recorder = rec
renderer.frameTap = { [weak rec] texture, time in
rec?.writeFrame(texture: texture, time: time)
}
recordMenuItem?.title = "Stop Recording"
LogStream.shared.log("recording started (\(size.width)x\(size.height))", level: .info, source: .app)
}
@MainActor
private func stopRecordingFlow(_ rec: Recorder) {
// Drop the frame tap immediately so no more frames queue up while the
// writer is finishing. The recorder owns the in-flight pixel buffer
// until its completion fires.
windowController?.renderer.frameTap = nil
rec.stopRecording { [weak self] url in
Task { @MainActor in
guard let self else { return }
self.recorder = nil
self.recordMenuItem?.title = "Start Recording"
self.showRecordingResult(url: url, started: true)
}
}
}
@MainActor
private func currentRecordingSize() -> (width: Int, height: Int) {
if let view = windowController?.window?.contentView as? NSView {
let scale = view.window?.backingScaleFactor ?? 2.0
let bounds = view.bounds
let w = Int((bounds.width * scale).rounded())
let h = Int((bounds.height * scale).rounded())
if w > 0 && h > 0 { return (w, h) }
}
let frame = NSScreen.main?.frame ?? NSRect(x: 0, y: 0, width: 1920, height: 1080)
return (Int(frame.width), Int(frame.height))
}
@MainActor
private func showRecordingResult(url: URL?, started: Bool) {
let alert = NSAlert()
if let url {
alert.messageText = "Recording saved"
alert.informativeText = "Saved to \(url.path)"
} else if started {
alert.messageText = "Recording failed"
alert.informativeText = "The recorder did not produce a file."
alert.alertStyle = .warning
} else {
alert.messageText = "Recording failed to start"
alert.informativeText = "The recorder could not be initialized."
alert.alertStyle = .warning
}
alert.runModal()
}
// MARK: - Face enrollment
@MainActor
@objc private func enrollFace(_ sender: Any?) {
guard let detector = windowController?.detector else { return }
let alert = NSAlert()
alert.messageText = "Enroll Face"
alert.informativeText = "Aim the camera at the person you want VisionPOC to remember, type their name, and press Start. The next few frames containing a face will be captured."
alert.alertStyle = .informational
alert.addButton(withTitle: "Start")
alert.addButton(withTitle: "Cancel")
let input = NSTextField(frame: NSRect(x: 0, y: 0, width: 260, height: 24))
input.placeholderString = "Name (e.g. James)"
alert.accessoryView = input
alert.window.initialFirstResponder = input
let response = alert.runModal()
guard response == .alertFirstButtonReturn else { return }
let name = input.stringValue.trimmingCharacters(in: .whitespacesAndNewlines)
guard !name.isEmpty else { return }
// Capture inside the detector queue: feed prints straight into the
// registry (which is thread-safe) and only ship the count back to
// MainActor for the confirmation dialog. This avoids sending the
// non-Sendable VNFeaturePrintObservation array across actor isolation.
let registry = detector.faceRegistry
detector.captureFeaturePrints(count: 5) { prints in
registry.enroll(name: name, prints: prints)
let count = prints.count
Task { @MainActor in
AppDelegate.showEnrollmentResult(name: name, count: count)
}
}
}
@MainActor
private static func showEnrollmentResult(name: String, count: Int) {
let alert = NSAlert()
if count == 0 {
alert.messageText = "Enrollment cancelled"
alert.informativeText = "No face was captured."
} else {
alert.messageText = "Enrolled \(name)"
alert.informativeText = "Saved \(count) face print\(count == 1 ? "" : "s"). The HUD will start labeling \(name) on the next detection cycle."
}
alert.runModal()
}
@MainActor
@objc private func forgetFace(_ sender: Any?) {
guard let detector = windowController?.detector else { return }
let names = detector.faceRegistry.enrolledNames()
guard !names.isEmpty else {
let alert = NSAlert()
alert.messageText = "No enrolled faces"
alert.informativeText = "There are no faces in the registry yet. Use Enroll Face… first."
alert.runModal()
return
}
let alert = NSAlert()
alert.messageText = "Forget Face"
alert.informativeText = "Pick a name to remove from the registry. This deletes the stored face prints from disk."
alert.addButton(withTitle: "Forget")
alert.addButton(withTitle: "Cancel")
let popup = NSPopUpButton(frame: NSRect(x: 0, y: 0, width: 260, height: 26))
popup.addItems(withTitles: names)
alert.accessoryView = popup
let resp = alert.runModal()
guard resp == .alertFirstButtonReturn,
let chosen = popup.titleOfSelectedItem else { return }
detector.faceRegistry.forget(name: chosen)
}
private static func registerBundledFonts() {
let names = ["ShareTechMono-Regular", "Orbitron-Bold"]
for name in names {
guard let url = Bundle.main.url(forResource: name, withExtension: "ttf") else { continue }
var error: Unmanaged<CFError>?
if !CTFontManagerRegisterFontsForURL(url as CFURL, .process, &error) {
if let err = error?.takeRetainedValue() {
NSLog("Font registration failed for \(name): \(err)")
}
}
}
}
}