forked from nikitabobko/AeroSpace
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrefresh.swift
More file actions
172 lines (149 loc) · 6.04 KB
/
refresh.swift
File metadata and controls
172 lines (149 loc) · 6.04 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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
import AppKit
import Common
/// It's one of the most important function of the whole application.
/// The function is called as a feedback response on every user input.
/// The function is idempotent.
@MainActor
func refreshSession<T>(_ event: RefreshSessionEvent, screenIsDefinitelyUnlocked: Bool, startup: Bool = false, body: () -> T) -> T {
check(Thread.current.isMainThread)
// refreshSessionEventForDebug = event
// defer { refreshSessionEventForDebug = nil }
if screenIsDefinitelyUnlocked { resetClosedWindowsCache() }
gc()
gcMonitors()
detectNewAppsAndWindows(startup: startup)
let nativeFocused = getNativeFocusedWindow(startup: startup)
if let nativeFocused { debugWindowsIfRecording(nativeFocused) }
updateFocusCache(nativeFocused)
let focusBefore = focus.windowOrNil
refreshModel()
let result = body()
refreshModel()
let focusAfter = focus.windowOrNil
if startup {
smartLayoutAtStartup()
}
if TrayMenuModel.shared.isEnabled {
if focusBefore != focusAfter {
focusAfter?.nativeFocus() // syncFocusToMacOs
}
updateTrayText()
normalizeLayoutReason(startup: startup)
layoutWorkspaces()
}
return result
}
@MainActor
func refreshAndLayout(_ event: RefreshSessionEvent, screenIsDefinitelyUnlocked: Bool, startup: Bool = false) {
refreshSession(event, screenIsDefinitelyUnlocked: screenIsDefinitelyUnlocked, startup: startup, body: {})
}
@MainActor private var havePendingRefresh = false
@MainActor private var pendingRefreshScreenIsDefinitelyUnlocked = false
@MainActor
func scheduleRefreshAndLayout(_ event: RefreshSessionEvent, screenIsDefinitelyUnlocked: Bool = false) {
if havePendingRefresh {
if screenIsDefinitelyUnlocked {
pendingRefreshScreenIsDefinitelyUnlocked = true
}
return
}
havePendingRefresh = true
pendingRefreshScreenIsDefinitelyUnlocked = screenIsDefinitelyUnlocked
DispatchQueue.main.async { @MainActor in
havePendingRefresh = false
refreshSession(event, screenIsDefinitelyUnlocked: pendingRefreshScreenIsDefinitelyUnlocked, startup: false, body: {})
}
}
@MainActor
func refreshModel() {
gc()
checkOnFocusChangedCallbacks()
normalizeContainers()
}
@MainActor
private func gc() {
// Garbage collect terminated apps and windows before working with all windows
MacApp.garbageCollectTerminatedApps()
gcWindows()
// Garbage collect workspaces after apps, because workspaces contain apps.
Workspace.garbageCollectUnusedWorkspaces()
}
@MainActor
func gcWindows() {
// Second line of defence against lock screen. See the first line of defence: closedWindowsCache
// Second and third lines of defence are technically needed only to avoid potential flickering
if NSWorkspace.shared.frontmostApplication?.bundleIdentifier == lockScreenAppBundleId { return }
let toKill = MacWindow.allWindowsMap.filter { $0.value.axWindow.containingWindowId(signpostEvent: $0.value.app.name) == nil }
// If all windows are "unobservable", it's highly propable that loginwindow might be still active and we are still
// recovering from unlock
if toKill.count == MacWindow.allWindowsMap.count { return }
for window in toKill {
window.value.garbageCollect(skipClosedWindowsCache: false)
}
}
func refreshObs(_ obs: AXObserver, ax: AXUIElement, notif: CFString, data: UnsafeMutableRawPointer?) {
check(Thread.isMainThread)
let notif = notif as String
MainActor.assumeIsolated {
scheduleRefreshAndLayout(.ax(notif), screenIsDefinitelyUnlocked: false)
}
}
enum OptimalHideCorner {
case bottomLeftCorner, bottomRightCorner
}
@MainActor
private func layoutWorkspaces() {
let monitors = monitors
var monitorToOptimalHideCorner: [CGPoint: OptimalHideCorner] = [:]
for monitor in monitors {
let xOff = monitor.width * 0.1
let yOff = monitor.height * 0.1
// brc = bottomRightCorner
let brc1 = monitor.rect.bottomRightCorner + CGPoint(x: 2, y: -yOff)
let brc2 = monitor.rect.bottomRightCorner + CGPoint(x: -xOff, y: 2)
let brc3 = monitor.rect.bottomRightCorner + CGPoint(x: 2, y: 2)
// blc = bottomLeftCorner
let blc1 = monitor.rect.bottomLeftCorner + CGPoint(x: -2, y: -yOff)
let blc2 = monitor.rect.bottomLeftCorner + CGPoint(x: xOff, y: 2)
let blc3 = monitor.rect.bottomLeftCorner + CGPoint(x: -2, y: 2)
let corner: OptimalHideCorner =
monitors.contains(where: { m in m.rect.contains(brc1) || m.rect.contains(brc2) || m.rect.contains(brc3) }) &&
monitors.allSatisfy { m in !m.rect.contains(blc1) && !m.rect.contains(blc2) && !m.rect.contains(blc3) }
? .bottomLeftCorner
: .bottomRightCorner
monitorToOptimalHideCorner[monitor.rect.topLeftCorner] = corner
}
// to reduce flicker, first unhide visible workspaces, then hide invisible ones
for monitor in monitors {
let workspace = monitor.activeWorkspace
workspace.allLeafWindowsRecursive.forEach { ($0 as! MacWindow).unhideFromCorner() } // todo as!
workspace.layoutWorkspace()
}
for workspace in Workspace.all where !workspace.isVisible {
let corner = monitorToOptimalHideCorner[workspace.workspaceMonitor.rect.topLeftCorner] ?? .bottomRightCorner
workspace.allLeafWindowsRecursive.forEach { ($0 as! MacWindow).hideInCorner(corner) } // todo as!
}
}
@MainActor
private func normalizeContainers() {
// Can't do it only for visible workspace because most of the commands support --window-id and --workspace flags
for workspace in Workspace.all {
workspace.normalizeContainers()
}
}
@MainActor
private func detectNewAppsAndWindows(startup: Bool) {
for app in apps {
app.detectNewWindows(startup: startup)
}
}
@MainActor
private func smartLayoutAtStartup() {
let workspace = focus.workspace
let root = workspace.rootTilingContainer
if root.children.count <= 3 {
root.layout = .tiles
} else {
root.layout = .accordion
}
}