import AppKit
|
import Foundation
|
import AlignerCore
|
|
enum Round1SnapshotDump {
|
static func run(prettyPrinted: Bool) throws {
|
let spaceEnumerator = try SkyLightSpaceEnumerator()
|
let displays = try spaceEnumerator.displays()
|
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()
|
let snapshot = QuickSwitchSnapshotBuilder.snapshot(displays: displays, windows: windows)
|
let json = try JSONSerialization.data(
|
withJSONObject: dictionary(
|
displays: displays,
|
windows: windows,
|
snapshot: snapshot
|
),
|
options: prettyPrinted ? [.prettyPrinted, .sortedKeys] : [.sortedKeys]
|
)
|
|
FileHandle.standardOutput.write(json)
|
FileHandle.standardOutput.write(Data("\n".utf8))
|
}
|
|
private static func dictionary(
|
displays: [AlignerDisplay],
|
windows: [AlignerWindow],
|
snapshot: QuickSwitchSnapshot
|
) -> [String: Any] {
|
[
|
"generatedAt": ISO8601DateFormatter().string(from: Date()),
|
"displayCount": displays.count,
|
"spaceCount": displays.flatMap(\.spaces).count,
|
"candidateWindowCount": windows.count,
|
"snapshotAppCount": snapshot.appGroups.count,
|
"snapshotWindowCount": snapshot.appGroups.flatMap(\.windows).count,
|
"displays": displays.map(displayDictionary),
|
"windows": windows.map(windowDictionary),
|
"appGroups": snapshot.appGroups.map(appGroupDictionary)
|
]
|
}
|
|
private static func displayDictionary(_ display: AlignerDisplay) -> [String: Any] {
|
[
|
"uuid": display.uuid,
|
"physical": display.physical,
|
"spaces": display.spaces.map(spaceDictionary)
|
]
|
}
|
|
private static func spaceDictionary(_ space: AlignerSpace) -> [String: Any] {
|
[
|
"id": space.id,
|
"type": string(from: space.type),
|
"displayUUID": space.displayUUID,
|
"index": space.index
|
]
|
}
|
|
private static func appGroupDictionary(_ group: QuickSwitchAppGroup) -> [String: Any] {
|
[
|
"app": appDictionary(group.app),
|
"windows": group.windows.map { item in
|
[
|
"primarySpaceID": item.primarySpaceID as Any,
|
"window": windowDictionary(item.window)
|
]
|
}
|
]
|
}
|
|
private static func windowDictionary(_ window: AlignerWindow) -> [String: Any] {
|
var result = [
|
"id": window.id,
|
"identifierSource": string(from: window.identifierSource),
|
"isSyntheticWindowID": MinimizedWindowFallbackPolicy.isSyntheticWindow(window),
|
"titleHash": DevelopmentDiagnostics.stableFingerprint(window.title),
|
"titleLength": window.title.count,
|
"titleIsEmpty": window.title.isEmpty,
|
"isMinimized": window.isMinimized,
|
"isFullscreen": window.isFullscreen,
|
"isGhost": window.isGhost,
|
"spaceIDs": window.spaceIDs,
|
"app": appDictionary(window.app)
|
] as [String: Any]
|
if DevelopmentDiagnostics.includesSensitiveFields {
|
result["title"] = window.title
|
}
|
return result
|
}
|
|
private static func appDictionary(_ app: AlignerApp) -> [String: Any] {
|
let pathSummary = DevelopmentDiagnostics.pathSummary(app.executablePath)
|
var result = [
|
"bundleIdentifier": app.bundleIdentifier,
|
"name": app.name,
|
"category": app.category.rawValue,
|
"isHidden": app.isHidden,
|
"executablePathPresent": pathSummary["present"] == "true",
|
"executablePathKind": pathSummary["locationKind"] ?? "missing",
|
"executablePathBasename": pathSummary["basename"] ?? "",
|
"executablePathHash": pathSummary["fingerprint"] ?? DevelopmentDiagnostics.stableFingerprint(""),
|
"processIdentifier": app.processIdentifier as Any
|
] as [String: Any]
|
if DevelopmentDiagnostics.includesSensitiveFields {
|
result["executablePath"] = app.executablePath as Any
|
}
|
return result
|
}
|
|
private static func string(from type: AlignerSpaceType) -> String {
|
switch type {
|
case .user:
|
return "user"
|
case .fullscreen:
|
return "fullscreen"
|
case .system:
|
return "system"
|
case .unknown(let rawValue):
|
return "unknown(\(rawValue))"
|
}
|
}
|
|
private static func string(from source: AlignerWindow.IdentifierSource) -> String {
|
switch source {
|
case .cgWindow:
|
return "cgWindow"
|
case .syntheticAX:
|
return "syntheticAX"
|
}
|
}
|
}
|
|
struct Round1SnapshotDumpOptions {
|
let enabled: Bool
|
let prettyPrinted: Bool
|
|
static func parse(arguments: [String]) -> Round1SnapshotDumpOptions {
|
Round1SnapshotDumpOptions(
|
enabled: arguments.contains("--round01-dump-window-snapshot"),
|
prettyPrinted: arguments.contains("--round01-dump-window-snapshot-pretty")
|
)
|
}
|
}
|