forked from nikitabobko/AeroSpace
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDebugWindowsCommand.swift
More file actions
115 lines (103 loc) · 4.65 KB
/
DebugWindowsCommand.swift
File metadata and controls
115 lines (103 loc) · 4.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import AppKit
import Common
import OrderedCollections
private let disclaimer =
"""
!!! DISCLAIMER !!!
!!! 'debug-windows' command is not stable API. Please don't rely on the command existence and output format !!!
!!! The only intended use case is to report bugs about incorrect windows handling !!!
"""
@MainActor private var debugWindowsState: DebugWindowsState = .notRecording
@MainActor private var debugWindowsLog: OrderedDictionary<UInt32, String> = [:]
private let debugWindowsLimit = 5
enum DebugWindowsState {
case recording
case notRecording
case recordingAborted
}
struct DebugWindowsCommand: Command {
let args: DebugWindowsCmdArgs
func run(_ env: CmdEnv, _ io: CmdIo) async throws -> Bool {
if let windowId = args.windowId {
guard let window = Window.get(byId: windowId) else {
return io.err("Can't find window with the specified window-id: \(windowId)")
}
io.out(try await dumpWindowDebugInfo(window) + "\n")
io.out(disclaimer)
return true
}
switch debugWindowsState {
case .recording:
debugWindowsState = .notRecording
io.out(debugWindowsLog.values.joined(separator: "\n-----\n"))
io.out("\n" + disclaimer + "\n")
io.out("Debug session finished" + "\n")
debugWindowsLog = [:]
return true
case .notRecording:
debugWindowsState = .recording
debugWindowsLog = [:]
io.out(
"""
Debug windows session has started
1. Focus the problematic window
2. Run 'aerospace debug-windows' once again to finish the session and get the results
"""
)
// Make sure that the Terminal window that started the recording is recorded first
guard let target = args.resolveTargetOrReportError(env, io) else { return false }
if let window = target.windowOrNil {
try await debugWindowsIfRecording(window)
}
return true
case .recordingAborted:
io.out(
"""
Recording of the previous session was aborted after \(debugWindowsLimit) windows has been focused
Run the command one more time to start new debug session
"""
)
debugWindowsState = .notRecording
debugWindowsLog = [:]
return false
}
}
}
@MainActor
private func dumpWindowDebugInfo(_ window: Window) async throws -> String {
let window = window as! MacWindow
var result: [String: Json] = try await window.dumpAxInfo()
result["Aero.axWindowId"] = .fromOrDie(window.windowId)
result["Aero.workspace"] = .string(window.nodeWorkspace?.name ?? "nil")
result["Aero.treeNodeParent"] = .fromOrDie(String(describing: window.parent))
result["Aero.isWindowHeuristic"] = .fromOrDie(try await window.isWindowHeuristic())
result["Aero.isDialogHeuristic"] = .fromOrDie(try await window.isDialogHeuristic())
let appInfoDic = window.macApp.nsApp.bundleURL.flatMap { Bundle.init(url: $0) }?.infoDictionary ?? [:]
result["Aero.App.appBundleId"] = .fromOrDie(window.app.bundleId ?? "NULL-APP-BUNDLE-ID")
result["Aero.App.versionShort"] = .fromOrDie(appInfoDic["CFBundleShortVersionString"])
result["Aero.App.version"] = .fromOrDie(appInfoDic["CFBundleVersion"])
result["Aero.App.nsApp.activationPolicy"] = .string(window.macApp.nsApp.activationPolicy.prettyDescription)
result["Aero.AXApp"] = .dict(try await window.macApp.dumpAppAxInfo())
// todo add app bundle version to debug log
var matchingCallbacks: [Json] = []
for callback in config.onWindowDetected where try await callback.matches(window) {
matchingCallbacks.append(callback.debugInfo)
}
result["Aero.on-window-detected"] = .array(matchingCallbacks)
return (JSONEncoder.aeroSpaceDefault.encodeToString(result) ?? "nil").prefixLines(with: "\(window.app.bundleId ?? "nil-bundle-id") ||| ")
}
@MainActor
func debugWindowsIfRecording(_ window: Window) async throws {
switch debugWindowsState {
case .recording: break
case .notRecording, .recordingAborted: return
}
if debugWindowsLog.count > debugWindowsLimit {
debugWindowsState = .recordingAborted
debugWindowsLog = [:]
}
if debugWindowsLog.keys.contains(window.windowId) {
return
}
debugWindowsLog[window.windowId] = try await dumpWindowDebugInfo(window)
}