native first
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
.build/
|
||||
build/
|
||||
@@ -0,0 +1,34 @@
|
||||
// swift-tools-version: 5.9
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "MastermindPOC",
|
||||
platforms: [
|
||||
.macOS(.v14),
|
||||
],
|
||||
products: [
|
||||
.executable(name: "MastermindPOC", targets: ["MastermindPOC"]),
|
||||
.library(name: "MastermindPOCCore", targets: ["MastermindPOCCore"]),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "MastermindPOCCore"),
|
||||
.executableTarget(
|
||||
name: "MastermindPOC",
|
||||
dependencies: ["MastermindPOCCore"],
|
||||
linkerSettings: [
|
||||
.linkedFramework("AppKit"),
|
||||
.linkedFramework("AVFoundation"),
|
||||
.linkedFramework("CoreGraphics"),
|
||||
.linkedFramework("CoreMedia"),
|
||||
.linkedFramework("CoreVideo"),
|
||||
.linkedFramework("ScreenCaptureKit"),
|
||||
.linkedFramework("SwiftUI"),
|
||||
]
|
||||
),
|
||||
.testTarget(
|
||||
name: "MastermindPOCCoreTests",
|
||||
dependencies: ["MastermindPOCCore"]
|
||||
),
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,183 @@
|
||||
import AppKit
|
||||
import AVFoundation
|
||||
import CoreGraphics
|
||||
import MastermindPOCCore
|
||||
|
||||
final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
private let settingsStore = OverlaySettingsStore()
|
||||
private lazy var overlayController = OverlayWindowController(
|
||||
initialSettings: settingsStore.settings,
|
||||
actions: OverlayActions(
|
||||
openSettings: { [weak self] in self?.showSettings() },
|
||||
hideOverlay: { [weak self] in self?.hideOverlay() },
|
||||
quitApp: { NSApplication.shared.terminate(nil) }
|
||||
)
|
||||
)
|
||||
private lazy var settingsWindowController = SettingsWindowController(settingsStore: settingsStore)
|
||||
private var stats = AudioPipelineStats()
|
||||
private var screenFrameCount = 0
|
||||
|
||||
private lazy var captureCoordinator = CaptureCoordinator(
|
||||
onScreenFrame: { [weak self] frameCount in
|
||||
Task { @MainActor in
|
||||
self?.recordScreenFrame(frameCount)
|
||||
}
|
||||
},
|
||||
onSystemAudioFrame: { [weak self] frame in
|
||||
Task { @MainActor in
|
||||
self?.recordAudioFrame(frame)
|
||||
}
|
||||
},
|
||||
onError: { [weak self] message in
|
||||
Task { @MainActor in
|
||||
self?.setStatus(.error(message))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
private lazy var microphoneEngine = MicrophoneCaptureEngine(
|
||||
onFrame: { [weak self] frame in
|
||||
Task { @MainActor in
|
||||
self?.recordAudioFrame(frame)
|
||||
}
|
||||
},
|
||||
onError: { [weak self] message in
|
||||
Task { @MainActor in
|
||||
self?.setStatus(.error(message))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
private lazy var menuBarController = MenuBarController(
|
||||
actions: MenuBarController.Actions(
|
||||
showOverlay: { [weak self] in self?.showOverlay() },
|
||||
hideOverlay: { [weak self] in self?.hideOverlay() },
|
||||
openSettings: { [weak self] in self?.showSettings() },
|
||||
toggleClickThrough: { [weak self] in self?.toggleClickThrough() },
|
||||
startScreenAndSystemAudio: { [weak self] in self?.startScreenAndSystemAudio() },
|
||||
startMicrophone: { [weak self] in self?.startMicrophone() },
|
||||
pauseAllCapture: { [weak self] in self?.pauseAllCapture() },
|
||||
quit: { NSApplication.shared.terminate(nil) }
|
||||
)
|
||||
)
|
||||
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
settingsStore.onChange = { [weak self] settings in
|
||||
self?.overlayController.viewModel.backgroundOpacity = settings.backgroundOpacity
|
||||
}
|
||||
|
||||
overlayController.show()
|
||||
setStatus(.idle)
|
||||
refreshCounters()
|
||||
}
|
||||
|
||||
private func showOverlay() {
|
||||
overlayController.show()
|
||||
menuBarController.setOverlayVisible(true)
|
||||
}
|
||||
|
||||
private func hideOverlay() {
|
||||
overlayController.hide()
|
||||
menuBarController.setOverlayVisible(false)
|
||||
}
|
||||
|
||||
private func showSettings() {
|
||||
settingsWindowController.show()
|
||||
}
|
||||
|
||||
private func toggleClickThrough() {
|
||||
let enabled = overlayController.toggleClickThrough()
|
||||
overlayController.viewModel.clickThroughEnabled = enabled
|
||||
menuBarController.setClickThrough(enabled)
|
||||
}
|
||||
|
||||
private func startScreenAndSystemAudio() {
|
||||
Task {
|
||||
do {
|
||||
try await captureCoordinator.start(excludingWindowIDs: overlayController.excludedWindowIDs)
|
||||
setStatus(.screenContext)
|
||||
} catch {
|
||||
setStatus(.error(error.localizedDescription))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func startMicrophone() {
|
||||
Task {
|
||||
do {
|
||||
try await requestMicrophoneAccess()
|
||||
try microphoneEngine.start()
|
||||
setStatus(.listening)
|
||||
} catch {
|
||||
setStatus(.error(error.localizedDescription))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func pauseAllCapture() {
|
||||
Task {
|
||||
await captureCoordinator.stop()
|
||||
microphoneEngine.stop()
|
||||
setStatus(.paused)
|
||||
}
|
||||
}
|
||||
|
||||
private func recordScreenFrame(_ frameCount: Int) {
|
||||
screenFrameCount = frameCount
|
||||
overlayController.viewModel.screenFrames = frameCount
|
||||
setStatus(.screenContext)
|
||||
}
|
||||
|
||||
private func recordAudioFrame(_ frame: PCMFrame) {
|
||||
stats.recordFrame(source: frame.source, byteCount: frame.pcmS16LE.count)
|
||||
refreshCounters()
|
||||
|
||||
switch frame.source {
|
||||
case .microphone:
|
||||
setStatus(.listening)
|
||||
case .system:
|
||||
setStatus(.systemAudio)
|
||||
}
|
||||
}
|
||||
|
||||
private func refreshCounters() {
|
||||
overlayController.viewModel.microphoneFrames = stats.microphoneFrames
|
||||
overlayController.viewModel.microphoneBytes = stats.microphoneBytes
|
||||
overlayController.viewModel.systemFrames = stats.systemFrames
|
||||
overlayController.viewModel.systemBytes = stats.systemBytes
|
||||
}
|
||||
|
||||
private func setStatus(_ status: TrustStatus) {
|
||||
overlayController.viewModel.apply(status: status)
|
||||
menuBarController.setStatus(status)
|
||||
}
|
||||
|
||||
private func requestMicrophoneAccess() async throws {
|
||||
switch AVCaptureDevice.authorizationStatus(for: .audio) {
|
||||
case .authorized:
|
||||
return
|
||||
case .notDetermined:
|
||||
let granted = await AVCaptureDevice.requestAccess(for: .audio)
|
||||
if granted {
|
||||
return
|
||||
}
|
||||
throw POCError("Microphone permission was denied")
|
||||
case .denied, .restricted:
|
||||
throw POCError("Microphone permission is not available")
|
||||
@unknown default:
|
||||
throw POCError("Unknown microphone permission state")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct POCError: LocalizedError {
|
||||
let message: String
|
||||
|
||||
init(_ message: String) {
|
||||
self.message = message
|
||||
}
|
||||
|
||||
var errorDescription: String? {
|
||||
message
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import CoreGraphics
|
||||
import CoreMedia
|
||||
import CoreVideo
|
||||
import Foundation
|
||||
import MastermindPOCCore
|
||||
import ScreenCaptureKit
|
||||
|
||||
final class CaptureCoordinator: NSObject, SCStreamOutput, SCStreamDelegate {
|
||||
private let outputQueue = DispatchQueue(label: "app.mastermind.poc.screencapture")
|
||||
private let onScreenFrame: (Int) -> Void
|
||||
private let onSystemAudioFrame: (PCMFrame) -> Void
|
||||
private let onError: (String) -> Void
|
||||
|
||||
private var stream: SCStream?
|
||||
private var screenFrameCount = 0
|
||||
|
||||
init(
|
||||
onScreenFrame: @escaping (Int) -> Void,
|
||||
onSystemAudioFrame: @escaping (PCMFrame) -> Void,
|
||||
onError: @escaping (String) -> Void
|
||||
) {
|
||||
self.onScreenFrame = onScreenFrame
|
||||
self.onSystemAudioFrame = onSystemAudioFrame
|
||||
self.onError = onError
|
||||
super.init()
|
||||
}
|
||||
|
||||
func start(excludingWindowIDs: Set<CGWindowID>) async throws {
|
||||
await stop()
|
||||
|
||||
let content = try await SCShareableContent.current
|
||||
|
||||
guard let display = content.displays.first(where: { $0.displayID == CGMainDisplayID() }) ?? content.displays.first else {
|
||||
throw POCError("No capturable display found")
|
||||
}
|
||||
|
||||
let currentPID = ProcessInfo.processInfo.processIdentifier
|
||||
let excludedWindows = content.windows.filter { window in
|
||||
excludingWindowIDs.contains(window.windowID) || window.owningApplication?.processID == currentPID
|
||||
}
|
||||
|
||||
let filter = SCContentFilter(display: display, excludingWindows: excludedWindows)
|
||||
let configuration = SCStreamConfiguration()
|
||||
configuration.width = max(1, display.width)
|
||||
configuration.height = max(1, display.height)
|
||||
configuration.minimumFrameInterval = CMTime(value: 1, timescale: 2)
|
||||
configuration.pixelFormat = kCVPixelFormatType_32BGRA
|
||||
configuration.queueDepth = 3
|
||||
configuration.capturesAudio = true
|
||||
configuration.sampleRate = 16_000
|
||||
configuration.channelCount = 1
|
||||
configuration.excludesCurrentProcessAudio = true
|
||||
|
||||
let stream = SCStream(filter: filter, configuration: configuration, delegate: self)
|
||||
try stream.addStreamOutput(self, type: .screen, sampleHandlerQueue: outputQueue)
|
||||
try stream.addStreamOutput(self, type: .audio, sampleHandlerQueue: outputQueue)
|
||||
try await stream.startCapture()
|
||||
|
||||
screenFrameCount = 0
|
||||
self.stream = stream
|
||||
}
|
||||
|
||||
func stop() async {
|
||||
guard let stream else {
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
try await stream.stopCapture()
|
||||
} catch {
|
||||
onError("Screen capture stop failed: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
self.stream = nil
|
||||
}
|
||||
|
||||
func stream(_ stream: SCStream, didOutputSampleBuffer sampleBuffer: CMSampleBuffer, of type: SCStreamOutputType) {
|
||||
guard CMSampleBufferIsValid(sampleBuffer) else {
|
||||
return
|
||||
}
|
||||
|
||||
switch type {
|
||||
case .screen:
|
||||
screenFrameCount += 1
|
||||
onScreenFrame(screenFrameCount)
|
||||
case .audio:
|
||||
if let frame = SampleBufferPCMExtractor.extractFrame(from: sampleBuffer, source: .system) {
|
||||
onSystemAudioFrame(frame)
|
||||
}
|
||||
case .microphone:
|
||||
return
|
||||
@unknown default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func stream(_ stream: SCStream, didStopWithError error: Error) {
|
||||
onError("Screen capture stopped: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import AppKit
|
||||
import MastermindPOCCore
|
||||
|
||||
final class MenuBarController: NSObject {
|
||||
struct Actions {
|
||||
let showOverlay: () -> Void
|
||||
let hideOverlay: () -> Void
|
||||
let openSettings: () -> Void
|
||||
let toggleClickThrough: () -> Void
|
||||
let startScreenAndSystemAudio: () -> Void
|
||||
let startMicrophone: () -> Void
|
||||
let pauseAllCapture: () -> Void
|
||||
let quit: () -> Void
|
||||
}
|
||||
|
||||
private let actions: Actions
|
||||
private let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
||||
private let statusMenuItem = NSMenuItem(title: "Mastermind: Idle", action: nil, keyEquivalent: "")
|
||||
private let showItem = NSMenuItem(title: "Show Assistant", action: #selector(showOverlay), keyEquivalent: "")
|
||||
private let hideItem = NSMenuItem(title: "Hide Assistant", action: #selector(hideOverlay), keyEquivalent: "")
|
||||
private let clickThroughItem = NSMenuItem(title: "Enable Click-Through", action: #selector(toggleClickThrough), keyEquivalent: "")
|
||||
|
||||
init(actions: Actions) {
|
||||
self.actions = actions
|
||||
super.init()
|
||||
configureMenu()
|
||||
setStatus(.idle)
|
||||
}
|
||||
|
||||
func setStatus(_ status: TrustStatus) {
|
||||
let title = status.menuBarTitle
|
||||
statusMenuItem.title = title
|
||||
statusItem.button?.title = title
|
||||
}
|
||||
|
||||
func setOverlayVisible(_ visible: Bool) {
|
||||
showItem.isEnabled = !visible
|
||||
hideItem.isEnabled = visible
|
||||
}
|
||||
|
||||
func setClickThrough(_ enabled: Bool) {
|
||||
clickThroughItem.title = enabled ? "Disable Click-Through" : "Enable Click-Through"
|
||||
clickThroughItem.state = enabled ? .on : .off
|
||||
}
|
||||
|
||||
private func configureMenu() {
|
||||
statusItem.button?.title = "Mastermind: Idle"
|
||||
statusItem.button?.toolTip = "Mastermind AI helper status"
|
||||
|
||||
let menu = NSMenu()
|
||||
statusMenuItem.isEnabled = false
|
||||
menu.addItem(statusMenuItem)
|
||||
menu.addItem(.separator())
|
||||
|
||||
showItem.target = self
|
||||
hideItem.target = self
|
||||
clickThroughItem.target = self
|
||||
menu.addItem(showItem)
|
||||
menu.addItem(hideItem)
|
||||
menu.addItem(clickThroughItem)
|
||||
menu.addItem(.separator())
|
||||
|
||||
let settingsItem = NSMenuItem(title: "Settings...", action: #selector(openSettings), keyEquivalent: ",")
|
||||
settingsItem.target = self
|
||||
menu.addItem(settingsItem)
|
||||
menu.addItem(.separator())
|
||||
|
||||
let screenItem = NSMenuItem(title: "Start Screen + System Audio", action: #selector(startScreenAndSystemAudio), keyEquivalent: "")
|
||||
screenItem.target = self
|
||||
menu.addItem(screenItem)
|
||||
|
||||
let microphoneItem = NSMenuItem(title: "Start Microphone", action: #selector(startMicrophone), keyEquivalent: "")
|
||||
microphoneItem.target = self
|
||||
menu.addItem(microphoneItem)
|
||||
|
||||
let pauseItem = NSMenuItem(title: "Pause All Capture", action: #selector(pauseAllCapture), keyEquivalent: "")
|
||||
pauseItem.target = self
|
||||
menu.addItem(pauseItem)
|
||||
menu.addItem(.separator())
|
||||
|
||||
let quitItem = NSMenuItem(title: "Quit Mastermind POC", action: #selector(quit), keyEquivalent: "q")
|
||||
quitItem.target = self
|
||||
menu.addItem(quitItem)
|
||||
|
||||
statusItem.menu = menu
|
||||
setOverlayVisible(true)
|
||||
setClickThrough(false)
|
||||
}
|
||||
|
||||
@objc private func showOverlay() {
|
||||
actions.showOverlay()
|
||||
}
|
||||
|
||||
@objc private func hideOverlay() {
|
||||
actions.hideOverlay()
|
||||
}
|
||||
|
||||
@objc private func openSettings() {
|
||||
actions.openSettings()
|
||||
}
|
||||
|
||||
@objc private func toggleClickThrough() {
|
||||
actions.toggleClickThrough()
|
||||
}
|
||||
|
||||
@objc private func startScreenAndSystemAudio() {
|
||||
actions.startScreenAndSystemAudio()
|
||||
}
|
||||
|
||||
@objc private func startMicrophone() {
|
||||
actions.startMicrophone()
|
||||
}
|
||||
|
||||
@objc private func pauseAllCapture() {
|
||||
actions.pauseAllCapture()
|
||||
}
|
||||
|
||||
@objc private func quit() {
|
||||
actions.quit()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import AVFoundation
|
||||
import Foundation
|
||||
import MastermindPOCCore
|
||||
|
||||
final class MicrophoneCaptureEngine {
|
||||
private let engine = AVAudioEngine()
|
||||
private let onFrame: (PCMFrame) -> Void
|
||||
private let onError: (String) -> Void
|
||||
private var isRunning = false
|
||||
|
||||
init(onFrame: @escaping (PCMFrame) -> Void, onError: @escaping (String) -> Void) {
|
||||
self.onFrame = onFrame
|
||||
self.onError = onError
|
||||
}
|
||||
|
||||
func start() throws {
|
||||
guard !isRunning else {
|
||||
return
|
||||
}
|
||||
|
||||
let inputNode = engine.inputNode
|
||||
let format = inputNode.outputFormat(forBus: 0)
|
||||
|
||||
inputNode.removeTap(onBus: 0)
|
||||
inputNode.installTap(onBus: 0, bufferSize: 1024, format: format) { [weak self] buffer, _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
if let frame = MicrophonePCMConverter.extractFrame(from: buffer) {
|
||||
self.onFrame(frame)
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
try engine.start()
|
||||
isRunning = true
|
||||
} catch {
|
||||
inputNode.removeTap(onBus: 0)
|
||||
onError("Microphone start failed: \(error.localizedDescription)")
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
func stop() {
|
||||
guard isRunning else {
|
||||
return
|
||||
}
|
||||
|
||||
engine.inputNode.removeTap(onBus: 0)
|
||||
engine.stop()
|
||||
isRunning = false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import AVFoundation
|
||||
import Foundation
|
||||
import MastermindPOCCore
|
||||
|
||||
enum MicrophonePCMConverter {
|
||||
static func extractFrame(from buffer: AVAudioPCMBuffer) -> PCMFrame? {
|
||||
let frameCount = Int(buffer.frameLength)
|
||||
guard frameCount > 0 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let channelCount = max(1, Int(buffer.format.channelCount))
|
||||
let sourceRate = Int(buffer.format.sampleRate.rounded())
|
||||
let samples: [Float]
|
||||
|
||||
if let floatData = buffer.floatChannelData {
|
||||
samples = mixFloatChannels(floatData, channelCount: channelCount, frameCount: frameCount)
|
||||
} else if let int16Data = buffer.int16ChannelData {
|
||||
samples = mixInt16Channels(int16Data, channelCount: channelCount, frameCount: frameCount)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let mono16k = PCM16LE.resampleLinear(samples: samples, sourceRate: sourceRate, targetRate: 16_000)
|
||||
let data = PCM16LE.encode(samples: mono16k)
|
||||
|
||||
return PCMFrame(source: .microphone, sampleRate: 16_000, channels: 1, pcmS16LE: data)
|
||||
}
|
||||
|
||||
private static func mixFloatChannels(_ channelData: UnsafePointer<UnsafeMutablePointer<Float>>, channelCount: Int, frameCount: Int) -> [Float] {
|
||||
var mono = [Float](repeating: 0, count: frameCount)
|
||||
let divisor = Float(channelCount)
|
||||
|
||||
for channel in 0..<channelCount {
|
||||
let channelPointer = channelData[channel]
|
||||
for frame in 0..<frameCount {
|
||||
mono[frame] += channelPointer[frame] / divisor
|
||||
}
|
||||
}
|
||||
|
||||
return mono
|
||||
}
|
||||
|
||||
private static func mixInt16Channels(_ channelData: UnsafePointer<UnsafeMutablePointer<Int16>>, channelCount: Int, frameCount: Int) -> [Float] {
|
||||
var mono = [Float](repeating: 0, count: frameCount)
|
||||
let divisor = Float(channelCount)
|
||||
|
||||
for channel in 0..<channelCount {
|
||||
let channelPointer = channelData[channel]
|
||||
for frame in 0..<frameCount {
|
||||
mono[frame] += (Float(channelPointer[frame]) / Float(Int16.max)) / divisor
|
||||
}
|
||||
}
|
||||
|
||||
return mono
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import Foundation
|
||||
import MastermindPOCCore
|
||||
|
||||
final class OverlaySettingsStore: ObservableObject {
|
||||
static let backgroundOpacityKey = "overlay.backgroundOpacity"
|
||||
|
||||
@Published private(set) var settings: OverlaySettings
|
||||
|
||||
var onChange: ((OverlaySettings) -> Void)?
|
||||
|
||||
private let defaults: UserDefaults
|
||||
|
||||
init(defaults: UserDefaults = .standard) {
|
||||
self.defaults = defaults
|
||||
|
||||
if defaults.object(forKey: Self.backgroundOpacityKey) == nil {
|
||||
settings = .default
|
||||
} else {
|
||||
settings = OverlaySettings(backgroundOpacity: defaults.double(forKey: Self.backgroundOpacityKey))
|
||||
}
|
||||
}
|
||||
|
||||
var backgroundOpacity: Double {
|
||||
settings.backgroundOpacity
|
||||
}
|
||||
|
||||
func updateBackgroundOpacity(_ opacity: Double) {
|
||||
let nextSettings = OverlaySettings(backgroundOpacity: opacity)
|
||||
settings = nextSettings
|
||||
defaults.set(nextSettings.backgroundOpacity, forKey: Self.backgroundOpacityKey)
|
||||
onChange?(nextSettings)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
import MastermindPOCCore
|
||||
import SwiftUI
|
||||
|
||||
final class OverlayViewModel: ObservableObject {
|
||||
@Published var statusTitle = "Mastermind: Idle"
|
||||
@Published var statusDetail = "Native macOS companion proof"
|
||||
@Published var clickThroughEnabled = false
|
||||
@Published var backgroundOpacity = OverlaySettings.default.backgroundOpacity
|
||||
@Published var screenFrames = 0
|
||||
@Published var microphoneFrames = 0
|
||||
@Published var microphoneBytes = 0
|
||||
@Published var systemFrames = 0
|
||||
@Published var systemBytes = 0
|
||||
|
||||
func apply(status: TrustStatus) {
|
||||
statusTitle = status.menuBarTitle
|
||||
|
||||
switch status {
|
||||
case .idle:
|
||||
statusDetail = "Ready. Capture is off."
|
||||
case .listening:
|
||||
statusDetail = "Microphone capture is active."
|
||||
case .screenContext:
|
||||
statusDetail = "Screen context proof is active."
|
||||
case .systemAudio:
|
||||
statusDetail = "System audio proof is active."
|
||||
case .agentWorking:
|
||||
statusDetail = "Agent work placeholder."
|
||||
case .paused:
|
||||
statusDetail = "All capture paused."
|
||||
case .permissionNeeded(let message):
|
||||
statusDetail = message
|
||||
case .error(let message):
|
||||
statusDetail = message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct OverlayView: View {
|
||||
@ObservedObject var viewModel: OverlayViewModel
|
||||
let actions: OverlayActions
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
HudTitleBar(viewModel: viewModel, actions: actions)
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(viewModel.statusTitle)
|
||||
.font(.system(.title3, design: .rounded, weight: .semibold))
|
||||
.foregroundStyle(.white)
|
||||
Text(viewModel.statusDetail)
|
||||
.font(.callout)
|
||||
.foregroundStyle(.white.opacity(0.78))
|
||||
.lineLimit(2)
|
||||
}
|
||||
|
||||
Divider()
|
||||
.overlay(.white.opacity(0.2))
|
||||
|
||||
HStack(spacing: 14) {
|
||||
CounterView(label: "Screen", value: viewModel.screenFrames)
|
||||
CounterView(label: "Mic", value: viewModel.microphoneFrames)
|
||||
CounterView(label: "System", value: viewModel.systemFrames)
|
||||
}
|
||||
|
||||
Text("PCM bytes mic \(viewModel.microphoneBytes) | system \(viewModel.systemBytes)")
|
||||
.font(.caption2.monospacedDigit())
|
||||
.foregroundStyle(.white.opacity(0.62))
|
||||
}
|
||||
.padding(18)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 18, style: .continuous)
|
||||
.fill(.black.opacity(viewModel.backgroundOpacity))
|
||||
.stroke(.white.opacity(0.16), lineWidth: 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private struct HudTitleBar: View {
|
||||
@ObservedObject var viewModel: OverlayViewModel
|
||||
let actions: OverlayActions
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 10) {
|
||||
ZStack(alignment: .leading) {
|
||||
WindowDragRegion()
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: "sparkles")
|
||||
.font(.caption.weight(.semibold))
|
||||
Text("Mastermind")
|
||||
.font(.headline)
|
||||
Text(viewModel.clickThroughEnabled ? "Click-through" : "Interactive")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.white.opacity(0.66))
|
||||
}
|
||||
.foregroundStyle(.white)
|
||||
.allowsHitTesting(false)
|
||||
}
|
||||
.frame(height: 28)
|
||||
|
||||
HStack(spacing: 6) {
|
||||
HudIconButton(systemName: "gearshape", help: "Settings", action: actions.openSettings)
|
||||
HudIconButton(systemName: "eye.slash", help: "Hide assistant", action: actions.hideOverlay)
|
||||
HudIconButton(systemName: "xmark", help: "Quit Mastermind POC", role: .destructive, action: actions.quitApp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct HudIconButton: View {
|
||||
let systemName: String
|
||||
let help: String
|
||||
var role: ButtonRole?
|
||||
let action: () -> Void
|
||||
|
||||
var body: some View {
|
||||
Button(role: role, action: action) {
|
||||
Image(systemName: systemName)
|
||||
.font(.caption.weight(.semibold))
|
||||
.frame(width: 24, height: 24)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.foregroundStyle(.white.opacity(0.82))
|
||||
.background(.white.opacity(0.08), in: RoundedRectangle(cornerRadius: 6, style: .continuous))
|
||||
.help(help)
|
||||
}
|
||||
}
|
||||
|
||||
private struct WindowDragRegion: NSViewRepresentable {
|
||||
func makeNSView(context: Context) -> DragHandleView {
|
||||
DragHandleView()
|
||||
}
|
||||
|
||||
func updateNSView(_ nsView: DragHandleView, context: Context) {}
|
||||
}
|
||||
|
||||
private final class DragHandleView: NSView {
|
||||
override var mouseDownCanMoveWindow: Bool {
|
||||
true
|
||||
}
|
||||
|
||||
override func mouseDown(with event: NSEvent) {
|
||||
window?.performDrag(with: event)
|
||||
}
|
||||
}
|
||||
|
||||
private struct CounterView: View {
|
||||
let label: String
|
||||
let value: Int
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 3) {
|
||||
Text(label)
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.white.opacity(0.58))
|
||||
Text("\(value)")
|
||||
.font(.caption.monospacedDigit().weight(.semibold))
|
||||
.foregroundStyle(.white)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import AppKit
|
||||
import CoreGraphics
|
||||
import MastermindPOCCore
|
||||
import SwiftUI
|
||||
|
||||
final class OverlayWindowController {
|
||||
let viewModel = OverlayViewModel()
|
||||
|
||||
private let panel: NSPanel
|
||||
private var clickThroughEnabled = false
|
||||
|
||||
init(initialSettings: OverlaySettings, actions: OverlayActions) {
|
||||
let screenFrame = NSScreen.main?.visibleFrame ?? NSRect(x: 0, y: 0, width: 1280, height: 800)
|
||||
let size = NSSize(width: 440, height: 210)
|
||||
let origin = NSPoint(
|
||||
x: screenFrame.maxX - size.width - 28,
|
||||
y: screenFrame.maxY - size.height - 28
|
||||
)
|
||||
|
||||
panel = NSPanel(
|
||||
contentRect: NSRect(origin: origin, size: size),
|
||||
styleMask: [.borderless, .nonactivatingPanel],
|
||||
backing: .buffered,
|
||||
defer: false
|
||||
)
|
||||
|
||||
panel.isReleasedWhenClosed = false
|
||||
panel.isOpaque = false
|
||||
panel.backgroundColor = .clear
|
||||
panel.hasShadow = true
|
||||
panel.level = .floating
|
||||
panel.isMovableByWindowBackground = true
|
||||
panel.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary, .stationary, .ignoresCycle]
|
||||
panel.sharingType = .none
|
||||
panel.title = "Mastermind POC Overlay"
|
||||
viewModel.backgroundOpacity = initialSettings.backgroundOpacity
|
||||
panel.contentView = NSHostingView(rootView: OverlayView(viewModel: viewModel, actions: actions))
|
||||
}
|
||||
|
||||
var excludedWindowIDs: Set<CGWindowID> {
|
||||
[CGWindowID(panel.windowNumber)]
|
||||
}
|
||||
|
||||
func show() {
|
||||
panel.orderFrontRegardless()
|
||||
}
|
||||
|
||||
func hide() {
|
||||
panel.orderOut(nil)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func toggleClickThrough() -> Bool {
|
||||
clickThroughEnabled.toggle()
|
||||
panel.ignoresMouseEvents = clickThroughEnabled
|
||||
return clickThroughEnabled
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import AudioToolbox
|
||||
import CoreMedia
|
||||
import Foundation
|
||||
import MastermindPOCCore
|
||||
|
||||
enum SampleBufferPCMExtractor {
|
||||
static func extractFrame(from sampleBuffer: CMSampleBuffer, source: AudioSource) -> PCMFrame? {
|
||||
guard let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer),
|
||||
let streamDescription = CMAudioFormatDescriptionGetStreamBasicDescription(formatDescription)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let asbd = streamDescription.pointee
|
||||
guard asbd.mFormatID == kAudioFormatLinearPCM else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var bufferListSize = 0
|
||||
var blockBuffer: CMBlockBuffer?
|
||||
var status = CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(
|
||||
sampleBuffer,
|
||||
bufferListSizeNeededOut: &bufferListSize,
|
||||
bufferListOut: nil,
|
||||
bufferListSize: 0,
|
||||
blockBufferAllocator: kCFAllocatorDefault,
|
||||
blockBufferMemoryAllocator: kCFAllocatorDefault,
|
||||
flags: kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment,
|
||||
blockBufferOut: &blockBuffer
|
||||
)
|
||||
|
||||
guard status == noErr, bufferListSize > 0 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let bufferListPointer = UnsafeMutableRawPointer.allocate(byteCount: bufferListSize, alignment: MemoryLayout<AudioBufferList>.alignment)
|
||||
defer {
|
||||
bufferListPointer.deallocate()
|
||||
}
|
||||
|
||||
let audioBufferList = bufferListPointer.bindMemory(to: AudioBufferList.self, capacity: 1)
|
||||
status = CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(
|
||||
sampleBuffer,
|
||||
bufferListSizeNeededOut: nil,
|
||||
bufferListOut: audioBufferList,
|
||||
bufferListSize: bufferListSize,
|
||||
blockBufferAllocator: kCFAllocatorDefault,
|
||||
blockBufferMemoryAllocator: kCFAllocatorDefault,
|
||||
flags: kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment,
|
||||
blockBufferOut: &blockBuffer
|
||||
)
|
||||
|
||||
guard status == noErr else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let buffers = UnsafeMutableAudioBufferListPointer(audioBufferList)
|
||||
guard let firstBuffer = buffers.first,
|
||||
let mData = firstBuffer.mData
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let sourceRate = Int(asbd.mSampleRate.rounded())
|
||||
let byteCount = Int(firstBuffer.mDataByteSize)
|
||||
let rawPointer = UnsafeRawPointer(mData)
|
||||
|
||||
let samples: [Float]
|
||||
if asbd.mBitsPerChannel == 32, asbd.mFormatFlags & kAudioFormatFlagIsFloat != 0 {
|
||||
let sampleCount = byteCount / MemoryLayout<Float>.size
|
||||
let pointer = rawPointer.bindMemory(to: Float.self, capacity: sampleCount)
|
||||
samples = (0..<sampleCount).map { pointer[$0] }
|
||||
} else if asbd.mBitsPerChannel == 16, asbd.mFormatFlags & kAudioFormatFlagIsSignedInteger != 0 {
|
||||
let sampleCount = byteCount / MemoryLayout<Int16>.size
|
||||
let pointer = rawPointer.bindMemory(to: Int16.self, capacity: sampleCount)
|
||||
samples = (0..<sampleCount).map { index in
|
||||
Float(Int16(littleEndian: pointer[index])) / Float(Int16.max)
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let mono16k = PCM16LE.resampleLinear(samples: samples, sourceRate: sourceRate, targetRate: 16_000)
|
||||
let data = PCM16LE.encode(samples: mono16k)
|
||||
|
||||
return PCMFrame(source: source, sampleRate: 16_000, channels: 1, pcmS16LE: data)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import AppKit
|
||||
import MastermindPOCCore
|
||||
import SwiftUI
|
||||
|
||||
final class SettingsWindowController {
|
||||
private let panel: NSPanel
|
||||
|
||||
init(settingsStore: OverlaySettingsStore) {
|
||||
panel = NSPanel(
|
||||
contentRect: NSRect(x: 0, y: 0, width: 360, height: 160),
|
||||
styleMask: [.titled, .closable, .utilityWindow],
|
||||
backing: .buffered,
|
||||
defer: false
|
||||
)
|
||||
|
||||
panel.isReleasedWhenClosed = false
|
||||
panel.hidesOnDeactivate = false
|
||||
panel.title = "Mastermind Settings"
|
||||
panel.level = .floating
|
||||
panel.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
|
||||
panel.contentView = NSHostingView(rootView: SettingsView(settingsStore: settingsStore))
|
||||
}
|
||||
|
||||
func show() {
|
||||
panel.center()
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
panel.makeKeyAndOrderFront(nil)
|
||||
}
|
||||
}
|
||||
|
||||
private struct SettingsView: View {
|
||||
@ObservedObject var settingsStore: OverlaySettingsStore
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text("Window")
|
||||
.font(.headline)
|
||||
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack {
|
||||
Text("Opacity")
|
||||
Spacer()
|
||||
Text("\(Int(settingsStore.backgroundOpacity * 100))%")
|
||||
.font(.caption.monospacedDigit())
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
|
||||
Slider(
|
||||
value: Binding(
|
||||
get: { settingsStore.backgroundOpacity },
|
||||
set: { settingsStore.updateBackgroundOpacity($0) }
|
||||
),
|
||||
in: OverlaySettings.minimumOpacity...OverlaySettings.maximumOpacity
|
||||
)
|
||||
}
|
||||
|
||||
Text("Changes apply to the HUD background only.")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.padding(20)
|
||||
.frame(width: 360, height: 160)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import AppKit
|
||||
|
||||
let app = NSApplication.shared
|
||||
let delegate = AppDelegate()
|
||||
|
||||
app.delegate = delegate
|
||||
app.setActivationPolicy(.accessory)
|
||||
app.run()
|
||||
@@ -0,0 +1,24 @@
|
||||
public struct AudioPipelineStats: Equatable {
|
||||
public private(set) var microphoneFrames: Int
|
||||
public private(set) var microphoneBytes: Int
|
||||
public private(set) var systemFrames: Int
|
||||
public private(set) var systemBytes: Int
|
||||
|
||||
public init(microphoneFrames: Int = 0, microphoneBytes: Int = 0, systemFrames: Int = 0, systemBytes: Int = 0) {
|
||||
self.microphoneFrames = microphoneFrames
|
||||
self.microphoneBytes = microphoneBytes
|
||||
self.systemFrames = systemFrames
|
||||
self.systemBytes = systemBytes
|
||||
}
|
||||
|
||||
public mutating func recordFrame(source: AudioSource, byteCount: Int) {
|
||||
switch source {
|
||||
case .microphone:
|
||||
microphoneFrames += 1
|
||||
microphoneBytes += byteCount
|
||||
case .system:
|
||||
systemFrames += 1
|
||||
systemBytes += byteCount
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
public struct OverlayActions {
|
||||
public let openSettings: () -> Void
|
||||
public let hideOverlay: () -> Void
|
||||
public let quitApp: () -> Void
|
||||
|
||||
public init(openSettings: @escaping () -> Void, hideOverlay: @escaping () -> Void, quitApp: @escaping () -> Void) {
|
||||
self.openSettings = openSettings
|
||||
self.hideOverlay = hideOverlay
|
||||
self.quitApp = quitApp
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
public struct OverlaySettings: Equatable {
|
||||
public static let minimumOpacity = 0.35
|
||||
public static let maximumOpacity = 0.95
|
||||
public static let `default` = OverlaySettings(backgroundOpacity: 0.72)
|
||||
|
||||
public let backgroundOpacity: Double
|
||||
|
||||
public init(backgroundOpacity: Double) {
|
||||
self.backgroundOpacity = Self.clamp(backgroundOpacity)
|
||||
}
|
||||
|
||||
public static func clamp(_ opacity: Double) -> Double {
|
||||
min(maximumOpacity, max(minimumOpacity, opacity))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import Foundation
|
||||
|
||||
public enum PCM16LE {
|
||||
public static func encode(samples: [Float]) -> Data {
|
||||
var data = Data()
|
||||
data.reserveCapacity(samples.count * MemoryLayout<Int16>.size)
|
||||
|
||||
for sample in samples {
|
||||
let clamped = max(-1.0, min(1.0, sample))
|
||||
let scaled: Int16
|
||||
|
||||
if clamped >= 1.0 {
|
||||
scaled = Int16.max
|
||||
} else if clamped <= -1.0 {
|
||||
scaled = Int16.min
|
||||
} else {
|
||||
scaled = Int16((clamped * Float(Int16.max)).rounded())
|
||||
}
|
||||
|
||||
var littleEndian = scaled.littleEndian
|
||||
withUnsafeBytes(of: &littleEndian) { bytes in
|
||||
data.append(contentsOf: bytes)
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
public static func resampleLinear(samples: [Float], sourceRate: Int, targetRate: Int = 16_000) -> [Float] {
|
||||
guard sourceRate > 0, targetRate > 0, !samples.isEmpty else {
|
||||
return []
|
||||
}
|
||||
|
||||
guard sourceRate != targetRate else {
|
||||
return samples
|
||||
}
|
||||
|
||||
let ratio = Double(sourceRate) / Double(targetRate)
|
||||
let outputCount = max(1, Int(Double(samples.count) / ratio))
|
||||
|
||||
return (0..<outputCount).map { index in
|
||||
let sourcePosition = Double(index) * ratio
|
||||
let lowerIndex = Int(sourcePosition)
|
||||
let upperIndex = min(lowerIndex + 1, samples.count - 1)
|
||||
let fraction = Float(sourcePosition - Double(lowerIndex))
|
||||
let lower = samples[min(lowerIndex, samples.count - 1)]
|
||||
let upper = samples[upperIndex]
|
||||
return lower + ((upper - lower) * fraction)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import Foundation
|
||||
|
||||
public enum AudioSource: String, Equatable {
|
||||
case microphone
|
||||
case system
|
||||
}
|
||||
|
||||
public struct PCMFrame: Equatable {
|
||||
public let source: AudioSource
|
||||
public let sampleRate: Int
|
||||
public let channels: Int
|
||||
public let pcmS16LE: Data
|
||||
public let timestamp: Date
|
||||
|
||||
public init(source: AudioSource, sampleRate: Int, channels: Int, pcmS16LE: Data, timestamp: Date = Date()) {
|
||||
self.source = source
|
||||
self.sampleRate = sampleRate
|
||||
self.channels = channels
|
||||
self.pcmS16LE = pcmS16LE
|
||||
self.timestamp = timestamp
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
public enum TrustStatus: Equatable {
|
||||
case idle
|
||||
case listening
|
||||
case screenContext
|
||||
case systemAudio
|
||||
case agentWorking
|
||||
case paused
|
||||
case permissionNeeded(String)
|
||||
case error(String)
|
||||
|
||||
public var menuBarTitle: String {
|
||||
switch self {
|
||||
case .idle:
|
||||
return "Mastermind: Idle"
|
||||
case .listening:
|
||||
return "Mastermind: Listening"
|
||||
case .screenContext:
|
||||
return "Mastermind: Screen"
|
||||
case .systemAudio:
|
||||
return "Mastermind: System Audio"
|
||||
case .agentWorking:
|
||||
return "Mastermind: Working"
|
||||
case .paused:
|
||||
return "Mastermind: Paused"
|
||||
case .permissionNeeded:
|
||||
return "Mastermind: Permission"
|
||||
case .error:
|
||||
return "Mastermind: Error"
|
||||
}
|
||||
}
|
||||
|
||||
public var isCapturing: Bool {
|
||||
switch self {
|
||||
case .listening, .screenContext, .systemAudio:
|
||||
return true
|
||||
case .idle, .agentWorking, .paused, .permissionNeeded, .error:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import XCTest
|
||||
@testable import MastermindPOCCore
|
||||
|
||||
final class AudioModelTests: XCTestCase {
|
||||
func testPCMFrameStoresSidecarCompatibleAudioMetadata() {
|
||||
let data = Data([0x00, 0x00, 0xff, 0x7f])
|
||||
let timestamp = Date(timeIntervalSince1970: 42)
|
||||
|
||||
let frame = PCMFrame(
|
||||
source: .microphone,
|
||||
sampleRate: 16_000,
|
||||
channels: 1,
|
||||
pcmS16LE: data,
|
||||
timestamp: timestamp
|
||||
)
|
||||
|
||||
XCTAssertEqual(frame.source, .microphone)
|
||||
XCTAssertEqual(frame.sampleRate, 16_000)
|
||||
XCTAssertEqual(frame.channels, 1)
|
||||
XCTAssertEqual(frame.pcmS16LE, data)
|
||||
XCTAssertEqual(frame.timestamp, timestamp)
|
||||
}
|
||||
|
||||
func testPCM16LEClampsAndEncodesLittleEndianSamples() {
|
||||
let encoded = PCM16LE.encode(samples: [-2.0, -1.0, 0.0, 0.5, 2.0])
|
||||
|
||||
XCTAssertEqual(
|
||||
Array(encoded),
|
||||
[
|
||||
0x00, 0x80,
|
||||
0x00, 0x80,
|
||||
0x00, 0x00,
|
||||
0x00, 0x40,
|
||||
0xff, 0x7f,
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func testAudioStatsCountMicrophoneAndSystemFramesSeparately() {
|
||||
var stats = AudioPipelineStats()
|
||||
|
||||
stats.recordFrame(source: .microphone, byteCount: 320)
|
||||
stats.recordFrame(source: .system, byteCount: 640)
|
||||
stats.recordFrame(source: .microphone, byteCount: 160)
|
||||
|
||||
XCTAssertEqual(stats.microphoneFrames, 2)
|
||||
XCTAssertEqual(stats.microphoneBytes, 480)
|
||||
XCTAssertEqual(stats.systemFrames, 1)
|
||||
XCTAssertEqual(stats.systemBytes, 640)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import XCTest
|
||||
@testable import MastermindPOCCore
|
||||
|
||||
final class OverlayActionsTests: XCTestCase {
|
||||
func testOverlayActionsInvokeInjectedCallbacks() {
|
||||
var openedSettings = false
|
||||
var hidOverlay = false
|
||||
var quitApp = false
|
||||
|
||||
let actions = OverlayActions(
|
||||
openSettings: { openedSettings = true },
|
||||
hideOverlay: { hidOverlay = true },
|
||||
quitApp: { quitApp = true }
|
||||
)
|
||||
|
||||
actions.openSettings()
|
||||
actions.hideOverlay()
|
||||
actions.quitApp()
|
||||
|
||||
XCTAssertTrue(openedSettings)
|
||||
XCTAssertTrue(hidOverlay)
|
||||
XCTAssertTrue(quitApp)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import XCTest
|
||||
@testable import MastermindPOCCore
|
||||
|
||||
final class OverlaySettingsTests: XCTestCase {
|
||||
func testDefaultOpacityIsReadableHudDefault() {
|
||||
XCTAssertEqual(OverlaySettings.default.backgroundOpacity, 0.72, accuracy: 0.0001)
|
||||
}
|
||||
|
||||
func testOpacityBelowMinimumClampsToMinimum() {
|
||||
let settings = OverlaySettings(backgroundOpacity: 0.1)
|
||||
|
||||
XCTAssertEqual(settings.backgroundOpacity, 0.35, accuracy: 0.0001)
|
||||
}
|
||||
|
||||
func testOpacityAboveMaximumClampsToMaximum() {
|
||||
let settings = OverlaySettings(backgroundOpacity: 1.0)
|
||||
|
||||
XCTAssertEqual(settings.backgroundOpacity, 0.95, accuracy: 0.0001)
|
||||
}
|
||||
|
||||
func testValidOpacityStaysUnchanged() {
|
||||
let settings = OverlaySettings(backgroundOpacity: 0.64)
|
||||
|
||||
XCTAssertEqual(settings.backgroundOpacity, 0.64, accuracy: 0.0001)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import XCTest
|
||||
@testable import MastermindPOCCore
|
||||
|
||||
final class TrustStatusTests: XCTestCase {
|
||||
func testMenuBarTitlesDescribeVisibleAssistantState() {
|
||||
XCTAssertEqual(TrustStatus.idle.menuBarTitle, "Mastermind: Idle")
|
||||
XCTAssertEqual(TrustStatus.listening.menuBarTitle, "Mastermind: Listening")
|
||||
XCTAssertEqual(TrustStatus.screenContext.menuBarTitle, "Mastermind: Screen")
|
||||
XCTAssertEqual(TrustStatus.systemAudio.menuBarTitle, "Mastermind: System Audio")
|
||||
XCTAssertEqual(TrustStatus.paused.menuBarTitle, "Mastermind: Paused")
|
||||
XCTAssertEqual(TrustStatus.error("No permission").menuBarTitle, "Mastermind: Error")
|
||||
}
|
||||
|
||||
func testCaptureStatesIdentifyActiveCapture() {
|
||||
XCTAssertFalse(TrustStatus.idle.isCapturing)
|
||||
XCTAssertFalse(TrustStatus.paused.isCapturing)
|
||||
XCTAssertTrue(TrustStatus.listening.isCapturing)
|
||||
XCTAssertTrue(TrustStatus.screenContext.isCapturing)
|
||||
XCTAssertTrue(TrustStatus.systemAudio.isCapturing)
|
||||
}
|
||||
}
|
||||
Executable
+58
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
APP_NAME="MastermindPOC"
|
||||
BUILD_DIR="$ROOT_DIR/build"
|
||||
APP_DIR="$BUILD_DIR/$APP_NAME.app"
|
||||
CONTENTS_DIR="$APP_DIR/Contents"
|
||||
MACOS_DIR="$CONTENTS_DIR/MacOS"
|
||||
|
||||
export CLANG_MODULE_CACHE_PATH="$ROOT_DIR/.build/module-cache"
|
||||
|
||||
cd "$ROOT_DIR"
|
||||
swift build --product "$APP_NAME"
|
||||
|
||||
rm -rf "$APP_DIR"
|
||||
mkdir -p "$MACOS_DIR"
|
||||
|
||||
cp "$ROOT_DIR/.build/debug/$APP_NAME" "$MACOS_DIR/$APP_NAME"
|
||||
chmod +x "$MACOS_DIR/$APP_NAME"
|
||||
|
||||
cat > "$CONTENTS_DIR/Info.plist" <<'PLIST'
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Mastermind POC</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>MastermindPOC</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>app.mastermind.poc</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Mastermind POC</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>14.0</string>
|
||||
<key>LSUIElement</key>
|
||||
<true/>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Mastermind POC uses microphone audio only when you start microphone capture.</string>
|
||||
</dict>
|
||||
</plist>
|
||||
PLIST
|
||||
|
||||
printf 'APPL????' > "$CONTENTS_DIR/PkgInfo"
|
||||
|
||||
echo "Built $APP_DIR"
|
||||
Reference in New Issue
Block a user