Skip to content

Commit 4213dfd

Browse files
committed
Fix "Windows must always have a parent" crashes. Make parent optional
#1369 #1306
1 parent 96c7451 commit 4213dfd

12 files changed

+32
-25
lines changed

Sources/AppBundle/command/impl/LayoutCommand.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ struct LayoutCommand: Command {
3030
case .vertical:
3131
return changeTilingLayout(io, targetLayout: nil, targetOrientation: .v, window: window)
3232
case .tiling:
33-
switch window.parent.cases {
33+
guard let parent = window.parent else { return false }
34+
switch parent.cases {
3435
case .macosPopupWindowsContainer:
3536
return false // Impossible
3637
case .macosMinimizedWindowsContainer, .macosFullscreenWindowsContainer, .macosHiddenAppsWindowsContainer:
@@ -52,7 +53,8 @@ struct LayoutCommand: Command {
5253
}
5354

5455
@MainActor private func changeTilingLayout(_ io: CmdIo, targetLayout: Layout?, targetOrientation: Orientation?, window: Window) -> Bool {
55-
switch window.parent.cases {
56+
guard let parent = window.parent else { return false }
57+
switch parent.cases {
5658
case .tilingContainer(let parent):
5759
let targetOrientation = targetOrientation ?? parent.orientation
5860
let targetLayout = targetLayout ?? parent.layout

Sources/AppBundle/command/impl/MoveCommand.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ struct MoveCommand: Command {
1010
guard let currentWindow = target.windowOrNil else {
1111
return io.err(noWindowIsFocused)
1212
}
13-
switch currentWindow.parent.cases {
13+
guard let parent = currentWindow.parent else { return false }
14+
switch parent.cases {
1415
case .tilingContainer(let parent):
1516
let indexOfCurrent = currentWindow.ownIndex
1617
let indexOfSiblingTarget = indexOfCurrent + direction.focusOffset
@@ -49,11 +50,13 @@ private let moveOutMacosUnconventionalWindow = "moving macOS fullscreen, minimiz
4950
}) as! TilingContainer
5051
let bindTo: TilingContainer
5152
let bindToIndex: Int
52-
switch innerMostChild.parent.nodeCases {
53+
guard let parent = innerMostChild.parent else { return false }
54+
switch parent.nodeCases {
5355
case .tilingContainer(let parent):
5456
check(parent.orientation == direction.orientation)
5557
bindTo = parent
56-
bindToIndex = innerMostChild.ownIndex + direction.insertionOffset
58+
guard let ownIndex = innerMostChild.ownIndex else { return false }
59+
bindToIndex = ownIndex + direction.insertionOffset
5760
case .workspace(let parent): // create implicit container
5861
let prevRoot = parent.rootTilingContainer
5962
prevRoot.unbindFromParent()

Sources/AppBundle/command/impl/SplitCommand.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ struct SplitCommand: Command {
1212
guard let window = target.windowOrNil else {
1313
return io.err(noWindowIsFocused)
1414
}
15-
switch window.parent.cases {
15+
guard let parent = window.parent else { return false }
16+
switch parent.cases {
1617
case .workspace:
1718
// Nothing to do for floating and macOS native fullscreen windows
1819
return io.err("Can't split floating windows")

Sources/AppBundle/mouse/moveWithMouse.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ func movedObs(_ obs: AXObserver, ax: AXUIElement, notif: CFString, data: UnsafeM
2626
@MainActor
2727
private func moveWithMouse(_ window: Window) async throws { // todo cover with tests
2828
resetClosedWindowsCache()
29-
switch window.parent.cases {
29+
guard let parent = window.parent else { return }
30+
switch parent.cases {
3031
case .workspace:
3132
try await moveFloatingWindow(window)
3233
case .tilingContainer:
@@ -40,7 +41,8 @@ private func moveWithMouse(_ window: Window) async throws { // todo cover with t
4041
@MainActor
4142
private func moveFloatingWindow(_ window: Window) async throws {
4243
guard let targetWorkspace = try await window.getCenter()?.monitorApproximation.activeWorkspace else { return }
43-
if targetWorkspace != window.parent {
44+
guard let parent = window.parent else { return }
45+
if targetWorkspace != parent {
4446
window.bindAsFloatingWindow(to: targetWorkspace)
4547
}
4648
}

Sources/AppBundle/mouse/resizeWithMouse.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ private let adaptiveWeightBeforeResizeWithMouseKey = TreeNodeUserDataKey<CGFloat
3939
@MainActor
4040
private func resizeWithMouse(_ window: Window) async throws { // todo cover with tests
4141
resetClosedWindowsCache()
42-
switch window.parent.cases {
42+
guard let parent = window.parent else { return }
43+
switch parent.cases {
4344
case .workspace, .macosMinimizedWindowsContainer, .macosFullscreenWindowsContainer,
4445
.macosPopupWindowsContainer, .macosHiddenAppsWindowsContainer:
4546
return // Nothing to do for floating, or unconventional windows

Sources/AppBundle/normalizeLayoutReason.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,15 @@ private func _normalizeLayoutReason(workspace: Workspace, windows: [Window]) asy
2828
!config.automaticallyUnhideMacosHiddenApps && window.macAppUnsafe.nsApp.isHidden
2929
switch window.layoutReason {
3030
case .standard:
31+
guard let parent = window.parent else { continue }
3132
if isMacosFullscreen {
32-
window.layoutReason = .macos(prevParentKind: window.parent.kind)
33+
window.layoutReason = .macos(prevParentKind: parent.kind)
3334
window.bind(to: workspace.macOsNativeFullscreenWindowsContainer, adaptiveWeight: WEIGHT_DOESNT_MATTER, index: INDEX_BIND_LAST)
3435
} else if isMacosMinimized {
35-
window.layoutReason = .macos(prevParentKind: window.parent.kind)
36+
window.layoutReason = .macos(prevParentKind: parent.kind)
3637
window.bind(to: macosMinimizedWindowsContainer, adaptiveWeight: 1, index: INDEX_BIND_LAST)
3738
} else if isMacosWindowOfHiddenApp {
38-
window.layoutReason = .macos(prevParentKind: window.parent.kind)
39+
window.layoutReason = .macos(prevParentKind: parent.kind)
3940
window.bind(to: workspace.macOsNativeHiddenAppsWindowsContainer, adaptiveWeight: WEIGHT_DOESNT_MATTER, index: INDEX_BIND_LAST)
4041
}
4142
case .macos(let prevParentKind):

Sources/AppBundle/tree/MacWindow.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ final class MacWindow: Window {
152152
func unhideFromCorner() {
153153
guard let prevUnhiddenEmulationPositionRelativeToWorkspaceAssignedRect else { return }
154154
guard let nodeWorkspace else { return } // hiding only makes sense for workspace windows
155+
guard let parent else { return }
155156

156157
switch getChildParentRelation(child: self, parent: parent) {
157158
// Just a small optimization to avoid unnecessary AX calls for non floating windows
@@ -246,7 +247,8 @@ private func unbindAndGetBindingDataForNewTilingWindow(_ workspace: Workspace, w
246247

247248
@MainActor
248249
func tryOnWindowDetected(_ window: Window) async throws {
249-
switch window.parent.cases {
250+
guard let parent = window.parent else { return }
251+
switch parent.cases {
250252
case .tilingContainer, .workspace, .macosMinimizedWindowsContainer,
251253
.macosFullscreenWindowsContainer, .macosHiddenAppsWindowsContainer:
252254
try await onWindowDetected(window)

Sources/AppBundle/tree/TilingContainer.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ class TilingContainer: TreeNode, NonLeafTreeNodeObject { // todo consider renami
55
fileprivate var _orientation: Orientation
66
var orientation: Orientation { _orientation }
77
var layout: Layout
8-
override var parent: NonLeafTreeNodeObject { super.parent ?? dieT("TilingContainers always have parent") }
98

109
@MainActor
1110
init(parent: NonLeafTreeNodeObject, adaptiveWeight: CGFloat, _ orientation: Orientation, _ layout: Layout, index: Int) {
@@ -26,7 +25,7 @@ class TilingContainer: TreeNode, NonLeafTreeNodeObject { // todo consider renami
2625
}
2726

2827
extension TilingContainer {
29-
var ownIndex: Int { parent.children.firstIndex(of: self)! }
28+
var ownIndex: Int? { parent?.children.firstIndex(of: self) }
3029
var isRootContainer: Bool { parent is Workspace }
3130

3231
@MainActor

Sources/AppBundle/tree/TreeNode.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import Common
44
class TreeNode: Equatable {
55
private var _children: [TreeNode] = []
66
var children: [TreeNode] { _children }
7-
fileprivate weak var _parent: NonLeafTreeNodeObject? = nil
8-
var parent: NonLeafTreeNodeObject? { _parent }
7+
fileprivate final weak var _parent: NonLeafTreeNodeObject? = nil
8+
final var parent: NonLeafTreeNodeObject? { _parent }
99
private var adaptiveWeight: CGFloat
1010
private let _mruChildren: MruStack<TreeNode> = MruStack()
1111
// Usages:
@@ -18,7 +18,7 @@ class TreeNode: Equatable {
1818
// - move-mouse command
1919
var lastAppliedLayoutPhysicalRect: Rect? = nil // with real inner gaps
2020
final var unboundStacktrace: String? = nil
21-
var isBound: Bool { unboundStacktrace == nil } // todo drop, once https://github.com/nikitabobko/AeroSpace/issues/1215 is fixed
21+
var isBound: Bool { parent != nil } // todo drop, once https://github.com/nikitabobko/AeroSpace/issues/1215 is fixed
2222

2323
@MainActor
2424
init(parent: NonLeafTreeNodeObject, adaptiveWeight: CGFloat, index: Int) {

Sources/AppBundle/tree/Window.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,6 @@ import Common
44
class Window: TreeNode, Hashable {
55
nonisolated let windowId: UInt32 // todo nonisolated keyword is no longer necessary?
66
let app: any AbstractApp
7-
override var parent: NonLeafTreeNodeObject {
8-
super.parent ?? dieT("Windows must always have a parent. The Window was unbound at:\n\(unboundStacktrace ?? "nil")")
9-
}
10-
var parentOrNilForTests: NonLeafTreeNodeObject? { super.parent }
117
var lastFloatingSize: CGSize?
128
var isFullscreen: Bool = false
139
var noOuterGapsInFullscreen: Bool = false

0 commit comments

Comments
 (0)