import Foundation
|
import AlignerCore
|
|
protocol QuickSwitchSnapshotLoading: Sendable {
|
func snapshot() throws -> QuickSwitchSnapshotLoad
|
}
|
|
struct QuickSwitchSnapshotLoad: Sendable {
|
let snapshot: QuickSwitchSnapshot
|
let currentSpaceIDs: Set<UInt64>
|
}
|
|
enum FixtureQuickSwitchMode: Sendable {
|
case layout
|
case candidateFiltering
|
case activation
|
}
|
|
final class LiveQuickSwitchSnapshotLoader: QuickSwitchSnapshotLoading {
|
func snapshot() throws -> QuickSwitchSnapshotLoad {
|
let spaceEnumerator = try SkyLightSpaceEnumerator()
|
let displays = try spaceEnumerator.displays()
|
let currentSpaceIDs = try spaceEnumerator.currentSpaceIDs()
|
let fullscreenSpaceIDs = Set(
|
displays
|
.flatMap(\.spaces)
|
.filter { $0.type == .fullscreen }
|
.map(\.id)
|
)
|
let windowService = CGWindowAXWindowService(
|
spaceIDsByWindowIDProvider: spaceEnumerator.spaceIDsByWindowID(windowIDs:),
|
fullscreenSpaceIDs: fullscreenSpaceIDs
|
)
|
let windows = try windowService.allWindows()
|
|
return QuickSwitchSnapshotLoad(
|
snapshot: QuickSwitchSnapshotBuilder.snapshot(displays: displays, windows: windows),
|
currentSpaceIDs: currentSpaceIDs
|
)
|
}
|
}
|
|
final class DelayedQuickSwitchSnapshotLoader: QuickSwitchSnapshotLoading {
|
private let base: any QuickSwitchSnapshotLoading
|
private let delay: TimeInterval
|
|
init(base: any QuickSwitchSnapshotLoading, delay: TimeInterval) {
|
self.base = base
|
self.delay = delay
|
}
|
|
func snapshot() throws -> QuickSwitchSnapshotLoad {
|
if delay > 0 {
|
Thread.sleep(forTimeInterval: delay)
|
}
|
|
return try base.snapshot()
|
}
|
}
|
|
final class FixtureQuickSwitchSnapshotLoader: QuickSwitchSnapshotLoading {
|
private let appCount: Int
|
private let windowsPerApp: Int
|
private let mode: FixtureQuickSwitchMode
|
|
init(appCount: Int, windowsPerApp: Int = 1, mode: FixtureQuickSwitchMode = .layout) {
|
self.appCount = max(1, appCount)
|
self.windowsPerApp = max(1, windowsPerApp)
|
self.mode = mode
|
}
|
|
func snapshot() throws -> QuickSwitchSnapshotLoad {
|
if mode == .candidateFiltering {
|
return candidateFilteringSnapshot()
|
}
|
if mode == .activation {
|
return activationSnapshot()
|
}
|
|
return layoutSnapshot()
|
}
|
|
private func layoutSnapshot() -> QuickSwitchSnapshotLoad {
|
let displayUUID = "fixture-display-a"
|
let displays = [
|
AlignerDisplay(
|
uuid: displayUUID,
|
physical: true,
|
spaces: [
|
AlignerSpace(id: 1, type: .user, displayUUID: displayUUID, index: 1),
|
AlignerSpace(id: 2, type: .fullscreen, displayUUID: displayUUID, index: 2),
|
AlignerSpace(id: 3, type: .user, displayUUID: displayUUID, index: 3)
|
]
|
)
|
]
|
let windows = (0..<appCount).flatMap { index in
|
let app = AlignerApp(
|
bundleIdentifier: "com.example.fixture.\(String(format: "%03d", index))",
|
name: "Fixture App \(String(format: "%03d", index))",
|
category: .generic,
|
processIdentifier: Int32(20_000 + index)
|
)
|
|
return (0..<windowsPerApp).map { windowIndex in
|
let spaceSeed = index + windowIndex
|
let spaceID = spaceSeed % 5 == 0 ? UInt64(2) : UInt64(spaceSeed % 2 == 0 ? 1 : 3)
|
return AlignerWindow(
|
id: UInt32(10_000 + index * 100 + windowIndex),
|
app: app,
|
title: "Fixture Window \(String(format: "%03d", index))-\(String(format: "%02d", windowIndex))",
|
isFullscreen: spaceID == 2,
|
spaceIDs: [spaceID]
|
)
|
}
|
}
|
|
return QuickSwitchSnapshotLoad(
|
snapshot: QuickSwitchSnapshotBuilder.snapshot(displays: displays, windows: windows),
|
currentSpaceIDs: [2]
|
)
|
}
|
|
private func candidateFilteringSnapshot() -> QuickSwitchSnapshotLoad {
|
let displayUUID = "fixture-display-a"
|
let displays = [
|
AlignerDisplay(
|
uuid: displayUUID,
|
physical: true,
|
spaces: [
|
AlignerSpace(id: 1, type: .user, displayUUID: displayUUID, index: 1),
|
AlignerSpace(id: 2, type: .fullscreen, displayUUID: displayUUID, index: 2)
|
]
|
)
|
]
|
|
let app = AlignerApp(
|
bundleIdentifier: "com.example.candidate.standard",
|
name: "Candidate Standard",
|
category: .documentNotes,
|
processIdentifier: 30_001
|
)
|
let hiddenApp = AlignerApp(
|
bundleIdentifier: "com.example.candidate.hidden",
|
name: "Candidate Hidden",
|
category: .generic,
|
isHidden: true,
|
processIdentifier: 30_002
|
)
|
let systemApp = AlignerApp(
|
bundleIdentifier: "com.apple.SecurityAgent",
|
name: "SecurityAgent",
|
category: .generic,
|
processIdentifier: 30_003
|
)
|
let finderApp = AlignerApp(
|
bundleIdentifier: "com.apple.finder",
|
name: "Finder",
|
category: .finder,
|
processIdentifier: 30_004
|
)
|
|
let records = [
|
WindowEnumerationRecord(
|
id: 20_001,
|
app: app,
|
title: "Standard Candidate",
|
size: CGSize(width: 900, height: 600),
|
subrole: .standard,
|
hasAXBacking: true,
|
spaceIDs: [1]
|
),
|
WindowEnumerationRecord(
|
id: 20_002,
|
app: app,
|
title: "Minimized Candidate",
|
identifierSource: .syntheticAX,
|
size: .zero,
|
subrole: .standard,
|
isMinimized: true,
|
spaceIDs: [1]
|
),
|
WindowEnumerationRecord(
|
id: 20_003,
|
app: app,
|
title: "Fullscreen Candidate",
|
size: .zero,
|
subrole: .standard,
|
isFullscreen: true,
|
spaceIDs: [2]
|
),
|
WindowEnumerationRecord(
|
id: 20_004,
|
app: app,
|
title: "Sheet Candidate",
|
size: CGSize(width: 520, height: 320),
|
subrole: .sheet,
|
hasAXBacking: true,
|
spaceIDs: [1]
|
),
|
WindowEnumerationRecord(
|
id: 20_005,
|
app: app,
|
title: "Dialog Candidate",
|
size: CGSize(width: 560, height: 360),
|
subrole: .dialog,
|
hasAXBacking: true,
|
spaceIDs: [1]
|
),
|
WindowEnumerationRecord(
|
id: 20_006,
|
app: app,
|
title: "",
|
size: CGSize(width: 840, height: 540),
|
subrole: .standard,
|
hasAXBacking: true,
|
spaceIDs: [1]
|
),
|
WindowEnumerationRecord(
|
id: 20_007,
|
app: app,
|
activationPolicy: .accessory,
|
title: "Accessory Rejected",
|
size: CGSize(width: 900, height: 600),
|
subrole: .standard,
|
spaceIDs: [1]
|
),
|
WindowEnumerationRecord(
|
id: 20_008,
|
app: hiddenApp,
|
title: "Hidden Rejected",
|
size: CGSize(width: 900, height: 600),
|
subrole: .standard,
|
spaceIDs: [1]
|
),
|
WindowEnumerationRecord(
|
id: 20_009,
|
app: app,
|
title: "Tiny Rejected",
|
size: CGSize(width: 80, height: 40),
|
subrole: .standard,
|
spaceIDs: [1]
|
),
|
WindowEnumerationRecord(
|
id: 20_010,
|
app: app,
|
title: "Ghost Rejected",
|
size: CGSize(width: 900, height: 600),
|
subrole: .standard,
|
isGhost: true,
|
spaceIDs: [1]
|
),
|
WindowEnumerationRecord(
|
id: 20_011,
|
app: app,
|
title: "Popover Rejected",
|
size: CGSize(width: 900, height: 600),
|
subrole: .unknown("AXPopover"),
|
spaceIDs: [1]
|
),
|
WindowEnumerationRecord(
|
id: 20_012,
|
app: finderApp,
|
title: "Desktop",
|
size: CGSize(width: 1710, height: 1112),
|
subrole: .standard,
|
isDesktopElement: true,
|
spaceIDs: [1]
|
),
|
WindowEnumerationRecord(
|
id: 20_013,
|
app: systemApp,
|
title: "Security Confirmation",
|
size: CGSize(width: 620, height: 420),
|
subrole: .dialog,
|
isSystemCritical: true,
|
spaceIDs: [1]
|
),
|
WindowEnumerationRecord(
|
id: 20_014,
|
app: app,
|
title: "Noninteractive Rejected",
|
size: CGSize(width: 900, height: 600),
|
subrole: .standard,
|
isInteractable: false,
|
spaceIDs: [1]
|
),
|
WindowEnumerationRecord(
|
id: 20_015,
|
app: app,
|
title: "CG-only Offscreen Rejected",
|
size: CGSize(width: 900, height: 600),
|
subrole: .standard,
|
hasAXBacking: false,
|
isOnscreen: false,
|
spaceIDs: [1]
|
)
|
]
|
let windows = records.compactMap(WindowEnumerationPolicy.window)
|
|
return QuickSwitchSnapshotLoad(
|
snapshot: QuickSwitchSnapshotBuilder.snapshot(displays: displays, windows: windows),
|
currentSpaceIDs: [1]
|
)
|
}
|
|
private func activationSnapshot() -> QuickSwitchSnapshotLoad {
|
let displayUUID = "fixture-display-a"
|
let displays = [
|
AlignerDisplay(
|
uuid: displayUUID,
|
physical: true,
|
spaces: [
|
AlignerSpace(id: 1, type: .user, displayUUID: displayUUID, index: 1),
|
AlignerSpace(id: 2, type: .fullscreen, displayUUID: displayUUID, index: 2)
|
]
|
)
|
]
|
|
let normalApp = AlignerApp(
|
bundleIdentifier: "com.example.activation.000",
|
name: "Activation App 000",
|
category: .generic,
|
processIdentifier: 40_000
|
)
|
let minimizedApp = AlignerApp(
|
bundleIdentifier: "com.example.activation.001",
|
name: "Activation App 001",
|
category: .generic,
|
processIdentifier: 40_001
|
)
|
let windows = [
|
AlignerWindow(
|
id: 40_100,
|
app: normalApp,
|
title: "Normal Activation Window",
|
isMinimized: false,
|
spaceIDs: [1]
|
),
|
AlignerWindow(
|
id: 40_110,
|
app: normalApp,
|
title: "App Activated Only Window",
|
isMinimized: false,
|
spaceIDs: [1]
|
),
|
AlignerWindow(
|
id: 40_200,
|
app: minimizedApp,
|
title: "Minimized Activation Window",
|
identifierSource: .syntheticAX,
|
isMinimized: true,
|
spaceIDs: [1]
|
)
|
]
|
|
return QuickSwitchSnapshotLoad(
|
snapshot: QuickSwitchSnapshotBuilder.snapshot(displays: displays, windows: windows),
|
currentSpaceIDs: [1]
|
)
|
}
|
}
|