forked from nikitabobko/AeroSpace
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFocusCommand.swift
More file actions
184 lines (171 loc) · 8.11 KB
/
FocusCommand.swift
File metadata and controls
184 lines (171 loc) · 8.11 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
173
174
175
176
177
178
179
180
181
182
183
184
import AppKit
import Common
struct FocusCommand: Command {
let args: FocusCmdArgs
func run(_ env: CmdEnv, _ io: CmdIo) -> Bool {
guard let target = args.resolveTargetOrReportError(env, io) else { return false }
// todo bug: floating windows break mru
let floatingWindows = args.floatingAsTiling ? makeFloatingWindowsSeenAsTiling(workspace: target.workspace) : []
defer {
if args.floatingAsTiling {
restoreFloatingWindows(floatingWindows: floatingWindows, workspace: target.workspace)
}
}
switch args.target {
case .direction(let direction):
let window = target.windowOrNil
if let (parent, ownIndex) = window?.closestParent(hasChildrenInDirection: direction, withLayout: nil) {
guard let windowToFocus = parent.children[ownIndex + direction.focusOffset]
.findFocusTargetRecursive(snappedTo: direction.opposite) else { return false }
return windowToFocus.focusWindow()
} else {
return hitWorkspaceBoundaries(target, io, args, direction)
}
case .windowId(let windowId):
if let windowToFocus = Window.get(byId: windowId) {
return windowToFocus.focusWindow()
} else {
return io.err("Can't find window with ID \(windowId)")
}
case .dfsIndex(let dfsIndex):
if let windowToFocus = target.workspace.rootTilingContainer.allLeafWindowsRecursive.getOrNil(atIndex: Int(dfsIndex)) {
return windowToFocus.focusWindow()
} else {
return io.err("Can't find window with DFS index \(dfsIndex)")
}
case .dfsRelative(let nextPrev):
let windows = target.workspace.rootTilingContainer.allLeafWindowsRecursive
guard let currentIndex = windows.firstIndex(where: { $0 == target.windowOrNil }) else {
return false
}
var targetIndex = switch nextPrev {
case .next: currentIndex + 1
case .prev: currentIndex - 1
}
if targetIndex < 0 || targetIndex >= windows.count {
switch args.boundariesAction {
case .stop: return true
case .fail: return false
case .wrapAroundTheWorkspace: targetIndex = (targetIndex + windows.count) % windows.count
case .wrapAroundAllMonitors: return errorT("Must be discarded by args parser")
}
}
return windows[targetIndex].focusWindow()
}
}
}
@MainActor private func hitWorkspaceBoundaries(
_ target: LiveFocus,
_ io: CmdIo,
_ args: FocusCmdArgs,
_ direction: CardinalDirection
) -> Bool {
switch args.boundaries {
case .workspace:
return switch args.boundariesAction {
case .stop: true
case .fail: false
case .wrapAroundTheWorkspace: wrapAroundTheWorkspace(target, io, direction)
case .wrapAroundAllMonitors: errorT("Must be discarded by args parser")
}
case .allMonitorsUnionFrame:
let currentMonitor = target.workspace.workspaceMonitor
guard let (monitors, index) = currentMonitor.findRelativeMonitor(inDirection: direction) else {
return io.err("Can't find monitor in direction \(direction)")
}
if let targetMonitor = monitors.getOrNil(atIndex: index) {
return targetMonitor.activeWorkspace.focusWorkspace()
} else {
guard let wrapped = monitors.get(wrappingIndex: index) else { return false }
return hitAllMonitorsOuterFrameBoundaries(target, io, args, direction, wrapped)
}
}
}
@MainActor private func hitAllMonitorsOuterFrameBoundaries(
_ target: LiveFocus,
_ io: CmdIo,
_ args: FocusCmdArgs,
_ direction: CardinalDirection,
_ wrappedMonitor: Monitor
) -> Bool {
switch args.boundariesAction {
case .stop:
return true
case .fail:
return false
case .wrapAroundTheWorkspace:
return wrapAroundTheWorkspace(target, io, direction)
case .wrapAroundAllMonitors:
wrappedMonitor.activeWorkspace.findFocusTargetRecursive(snappedTo: direction.opposite)?.markAsMostRecentChild()
return wrappedMonitor.activeWorkspace.focusWorkspace()
}
}
@MainActor private func wrapAroundTheWorkspace(_ target: LiveFocus, _ io: CmdIo, _ direction: CardinalDirection) -> Bool {
guard let windowToFocus = target.workspace.findFocusTargetRecursive(snappedTo: direction.opposite) else {
return io.err(noWindowIsFocused)
}
return windowToFocus.focusWindow()
}
@MainActor private func makeFloatingWindowsSeenAsTiling(workspace: Workspace) -> [FloatingWindowData] {
let mruBefore = workspace.mostRecentWindowRecursive
defer {
mruBefore?.markAsMostRecentChild()
}
let floatingWindows: [FloatingWindowData] = workspace.floatingWindows
.map { (window: Window) -> FloatingWindowData? in
let center = window.getCenter() // todo bug: we shouldn't access ax api here. What if the window was moved but it wasn't committed to ax yet?
guard let center else { return nil }
// todo bug: what if there are no tiling windows on the workspace?
guard let target = center.coerceIn(rect: workspace.workspaceMonitor.visibleRectPaddedByOuterGaps).findIn(tree: workspace.rootTilingContainer, virtual: true) else { return nil }
guard let targetCenter = target.getCenter() else { return nil }
guard let tilingParent = target.parent as? TilingContainer else { return nil }
let index = center.getProjection(tilingParent.orientation) >= targetCenter.getProjection(tilingParent.orientation)
? target.ownIndex + 1
: target.ownIndex
let data = window.unbindFromParent()
return FloatingWindowData(window: window, center: center, parent: tilingParent, adaptiveWeight: data.adaptiveWeight, index: index)
}
.filterNotNil()
.sortedBy { $0.center.getProjection($0.parent.orientation) }
.reversed()
for floating in floatingWindows { // Make floating windows be seen as tiling
floating.window.bind(to: floating.parent, adaptiveWeight: 1, index: floating.index)
}
return floatingWindows
}
@MainActor private func restoreFloatingWindows(floatingWindows: [FloatingWindowData], workspace: Workspace) {
let mruBefore = workspace.mostRecentWindowRecursive
defer {
mruBefore?.markAsMostRecentChild()
}
for floating in floatingWindows {
floating.window.bind(to: workspace, adaptiveWeight: floating.adaptiveWeight, index: INDEX_BIND_LAST)
}
}
private struct FloatingWindowData {
let window: Window
let center: CGPoint
let parent: TilingContainer
let adaptiveWeight: CGFloat
let index: Int
}
extension TreeNode {
func findFocusTargetRecursive(snappedTo direction: CardinalDirection) -> Window? {
switch nodeCases {
case .workspace(let workspace):
return workspace.rootTilingContainer.findFocusTargetRecursive(snappedTo: direction)
case .window(let window):
return window
case .tilingContainer(let container):
if direction.orientation == container.orientation {
return (direction.isPositive ? container.children.last : container.children.first)?
.findFocusTargetRecursive(snappedTo: direction)
} else {
return mostRecentChild?.findFocusTargetRecursive(snappedTo: direction)
}
case .macosMinimizedWindowsContainer, .macosFullscreenWindowsContainer,
.macosPopupWindowsContainer, .macosHiddenAppsWindowsContainer:
error("Impossible")
}
}
}