diff --git a/AeroSpace.xcodeproj/project.pbxproj b/AeroSpace.xcodeproj/project.pbxproj index 748d607a..3b0d96e1 100644 --- a/AeroSpace.xcodeproj/project.pbxproj +++ b/AeroSpace.xcodeproj/project.pbxproj @@ -81,6 +81,7 @@ B19980B36D066FD4947D2F92 /* normalizeContainers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C704028F402C0DE83EDEABB9 /* normalizeContainers.swift */; }; B1F37468B56941F6B22EEEDB /* layoutRecursive.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAC5AF4019D1F5DAB5AF2B56 /* layoutRecursive.swift */; }; B3702BB393A9B03CCAE4C60E /* refresh.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526B113159987FA43EA41120 /* refresh.swift */; }; + B8EB32DC890345B9DD9C166A /* MacosFullscreenWindowsContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09A2832990598D534F4A5225 /* MacosFullscreenWindowsContainer.swift */; }; BA3AD253826B5EA318A6E687 /* MonitorEx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DAEA4B9015FBF8711E532D0 /* MonitorEx.swift */; }; BB3C35BE40958AE6E7B4A9FD /* TreeNodeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD1645D9939F3F896EF21393 /* TreeNodeTest.swift */; }; BC6511DA2ABE84164D90C181 /* ExecAndForgetCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569422C0C4C23EF3E024C8E6 /* ExecAndForgetCommand.swift */; }; @@ -98,6 +99,7 @@ E22ACB36C90695FBAC78226E /* TestWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F1905935B0C61590A96EFEF /* TestWindow.swift */; }; E5682579AEC6B84CF6FCE90D /* TilingContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C848D6E57FDF22AAF0FB45E6 /* TilingContainer.swift */; }; E7F53669DD11FA7C6C3540A0 /* ConfigTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54679FCD1EC1196B27E964D5 /* ConfigTest.swift */; }; + EB171FD9A347F000AC63B573 /* normalizeLayoutReason.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34547605D044E8182B71B0E7 /* normalizeLayoutReason.swift */; }; ED96E36786C941AB3AF780BC /* startAtLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9164C9401F7DDCACE9278DA4 /* startAtLogin.swift */; }; EECC59858691B99A95542D72 /* MacWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9245C6FACF389672EA71173B /* MacWindow.swift */; }; F69159A961FEE54847EC9A8D /* ListMonitorsCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD6BB613EC44C1B32CE4A47E /* ListMonitorsCommand.swift */; }; @@ -122,6 +124,7 @@ 00ECDFE176777828D560A737 /* TilingContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TilingContainer.swift; sourceTree = ""; }; 07ADCBFC2E29A2AD5C1B7984 /* parseCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = parseCommand.swift; sourceTree = ""; }; 09685297933511208058F7CF /* AeroSpace.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AeroSpace.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 09A2832990598D534F4A5225 /* MacosFullscreenWindowsContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacosFullscreenWindowsContainer.swift; sourceTree = ""; }; 0A9DFF8980BB3F90A3793BE9 /* parseOnWindowDetected.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = parseOnWindowDetected.swift; sourceTree = ""; }; 0AEE5470AF418906B180A593 /* mouse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = mouse.swift; sourceTree = ""; }; 0D36D0F8EEB30A7BABC42343 /* LazySequenceProtocolEx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazySequenceProtocolEx.swift; sourceTree = ""; }; @@ -135,6 +138,7 @@ 244190F3707EF2BB3CC7FA5A /* MonitorDescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonitorDescription.swift; sourceTree = ""; }; 25AC44D0E9450867215FCBEC /* MoveNodeToWorkspaceCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoveNodeToWorkspaceCommand.swift; sourceTree = ""; }; 2D96FF3675C63A83DC8B8969 /* SplitCommandTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitCommandTest.swift; sourceTree = ""; }; + 34547605D044E8182B71B0E7 /* normalizeLayoutReason.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = normalizeLayoutReason.swift; sourceTree = ""; }; 3A262B442A94C1964509B691 /* TreeNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TreeNode.swift; sourceTree = ""; }; 3A6EF465EF4129BCB10FE247 /* ExecCommandTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExecCommandTest.swift; sourceTree = ""; }; 3C2E5977331398421A4FC168 /* GlobalObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalObserver.swift; sourceTree = ""; }; @@ -277,6 +281,7 @@ 84AB57B5B8014931BD0F30DB /* AbstractApp.swift */, DAC5AF4019D1F5DAB5AF2B56 /* layoutRecursive.swift */, B7DB782C527ABE0CF31740EB /* MacApp.swift */, + 09A2832990598D534F4A5225 /* MacosFullscreenWindowsContainer.swift */, EB47C43405542756E2EA1B19 /* MacosInvisibleWindowsContainer.swift */, 9245C6FACF389672EA71173B /* MacWindow.swift */, C704028F402C0DE83EDEABB9 /* normalizeContainers.swift */, @@ -375,6 +380,7 @@ D61FE8B343B068F0FFFC2373 /* focused.swift */, 826EA316AB62F913D1A94466 /* FocusSourceOfTruth.swift */, 3C2E5977331398421A4FC168 /* GlobalObserver.swift */, + 34547605D044E8182B71B0E7 /* normalizeLayoutReason.swift */, 526B113159987FA43EA41120 /* refresh.swift */, 796713A1B3AEEBF4D0D180C7 /* server.swift */, 5F5F52E346D024960EAF5938 /* TrayMenuModel.swift */, @@ -629,6 +635,7 @@ 3AFD7EE961B97F38C0914A0C /* ListWorkspacesCommand.swift in Sources */, BD6301B2CFC16FDE4223ACB8 /* MacApp.swift in Sources */, EECC59858691B99A95542D72 /* MacWindow.swift in Sources */, + B8EB32DC890345B9DD9C166A /* MacosFullscreenWindowsContainer.swift in Sources */, 997742E8C68FA29868946181 /* MacosInvisibleWindowsContainer.swift in Sources */, DEE74487CFB70CEC10D9D3D7 /* MacosNativeFullscreenCommand.swift in Sources */, 7C0ACF53062631B667424927 /* MacosNativeMinimizeCommand.swift in Sources */, @@ -664,6 +671,7 @@ 045D4B46ABACA9E8167EF356 /* mouse.swift in Sources */, 78622D72C0CCE8D69B080D29 /* moveWithMouse.swift in Sources */, B19980B36D066FD4947D2F92 /* normalizeContainers.swift in Sources */, + EB171FD9A347F000AC63B573 /* normalizeLayoutReason.swift in Sources */, 4E0BC093AD1FBCCA718A26EC /* parseCommand.swift in Sources */, A0765C31043BCFB0420BF1C9 /* parseConfig.swift in Sources */, 5BA537EABFE48178D6BD2544 /* parseGaps.swift in Sources */, diff --git a/docs/goodness.adoc b/docs/goodness.adoc index d73a94ce..02dc83f4 100644 --- a/docs/goodness.adoc +++ b/docs/goodness.adoc @@ -142,7 +142,7 @@ EOF''' ---- [#disable-hide-app] -== Disable annoying and useless "hide application" +== Disable annoying and useless "hide application" shortcut .~/.aerospace.toml [source,toml] diff --git a/src/command/FocusCommand.swift b/src/command/FocusCommand.swift index fe9ea1be..4a04e49e 100644 --- a/src/command/FocusCommand.swift +++ b/src/command/FocusCommand.swift @@ -104,9 +104,9 @@ private func makeFloatingWindowsSeenAsTiling(workspace: Workspace) -> [FloatingW defer { mruBefore?.markAsMostRecentChild() } - let floatingWindows: [FloatingWindowData] = workspace.floatingAndMacosFullscreenWindows + let floatingWindows: [FloatingWindowData] = workspace.floatingWindows .map { (window: Window) -> FloatingWindowData? in - let center = window.isMacosFullscreen ? workspace.monitor.rect.topLeftCorner : window.getCenter() + let center = window.getCenter() 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.monitor.visibleRectPaddedByOuterGaps).findIn(tree: workspace.rootTilingContainer, virtual: true) else { return nil } @@ -162,7 +162,7 @@ private extension TreeNode { } else { return mostRecentChild?.findFocusTargetRecursive(snappedTo: direction) } - case .macosInvisibleWindowsContainer: + case .macosInvisibleWindowsContainer, .macosFullscreenWindowsContainer: error("Impossible") } } diff --git a/src/command/LayoutCommand.swift b/src/command/LayoutCommand.swift index 3519bc18..91752eb5 100644 --- a/src/command/LayoutCommand.swift +++ b/src/command/LayoutCommand.swift @@ -34,6 +34,9 @@ struct LayoutCommand: Command { case .macosInvisibleWindowsContainer: state.stderr.append("Can't change layout of macOS invisible windows (hidden application or minimized windows). This behavior is subject to change") return false + case .macosFullscreenWindowsContainer(_): + state.stderr.append("Can't change layout of macOS fullscreen windows. This behavior is subject to change") + return false case .tilingContainer: return true // Nothing to do case .workspace(let workspace): @@ -61,7 +64,7 @@ private func changeTilingLayout(_ state: CommandMutableState, targetLayout: Layo parent.layout = targetLayout parent.changeOrientation(targetOrientation) return true - case .workspace, .macosInvisibleWindowsContainer: + case .workspace, .macosInvisibleWindowsContainer, .macosFullscreenWindowsContainer: state.stderr.append("The window is non-tiling") return false } diff --git a/src/command/MoveCommand.swift b/src/command/MoveCommand.swift index 73559953..982a41ba 100644 --- a/src/command/MoveCommand.swift +++ b/src/command/MoveCommand.swift @@ -15,7 +15,7 @@ struct MoveCommand: Command { let indexOfCurrent = currentWindow.ownIndex let indexOfSiblingTarget = indexOfCurrent + direction.focusOffset if parent.orientation == direction.orientation && parent.children.indices.contains(indexOfSiblingTarget) { - switch parent.children[indexOfSiblingTarget].nonRootTreeNodeCasesOrThrow() { + switch parent.children[indexOfSiblingTarget].tilingTreeNodeCasesOrThrow() { case .tilingContainer(let topLevelSiblingTargetContainer): deepMoveIn(window: currentWindow, into: topLevelSiblingTargetContainer, moveDirection: direction) case .window: // "swap windows" @@ -29,19 +29,23 @@ struct MoveCommand: Command { case .workspace: // floating window state.stderr.append("moving floating windows isn't yet supported") // todo return false - case .macosInvisibleWindowsContainer(_): + case .macosInvisibleWindowsContainer: state.stderr.append(moveOutInvisibleWindow) return false + case .macosFullscreenWindowsContainer: + state.stderr.append(moveOutFullscreenWindow) + return false } } } private let moveOutInvisibleWindow = "moving macOS invisible windows (minimized, or windows of hidden apps) isn't yet supported. This behavior is subject to change" +private let moveOutFullscreenWindow = "moving macOS fullscreen windows isn't yet supported. This behavior is subject to change" private func moveOut(_ state: CommandMutableState, window: Window, direction: CardinalDirection) -> Bool { let innerMostChild = window.parents.first(where: { switch $0.parent?.cases { - case .workspace, .macosInvisibleWindowsContainer, nil: + case .workspace, .macosInvisibleWindowsContainer, nil, .macosFullscreenWindowsContainer: return true // Stop searching case .tilingContainer(let parent): return parent.orientation == direction.orientation @@ -67,6 +71,9 @@ private func moveOut(_ state: CommandMutableState, window: Window, direction: Ca case .macosInvisibleWindowsContainer: state.stderr.append(moveOutInvisibleWindow) return false + case .macosFullscreenWindowsContainer: + state.stderr.append(moveOutFullscreenWindow) + return false case .window: error("Window can't contain children nodes") } @@ -81,7 +88,7 @@ private func moveOut(_ state: CommandMutableState, window: Window, direction: Ca } private func deepMoveIn(window: Window, into container: TilingContainer, moveDirection: CardinalDirection) { - let deepTarget = container.nonRootTreeNodeCasesOrThrow().findDeepMoveInTargetRecursive(moveDirection.orientation) + let deepTarget = container.tilingTreeNodeCasesOrThrow().findDeepMoveInTargetRecursive(moveDirection.orientation) switch deepTarget { case .tilingContainer(let deepTarget): window.unbindFromParent() @@ -96,8 +103,8 @@ private func deepMoveIn(window: Window, into container: TilingContainer, moveDir } } -private extension NonRootTreeNodeCases { - func findDeepMoveInTargetRecursive(_ orientation: Orientation) -> NonRootTreeNodeCases { +private extension TilingTreeNodeCases { + func findDeepMoveInTargetRecursive(_ orientation: Orientation) -> TilingTreeNodeCases { switch self { case .window: return self @@ -106,7 +113,7 @@ private extension NonRootTreeNodeCases { return .tilingContainer(container) } else { return (container.mostRecentChild ?? errorT("Empty containers must be detached during normalization")) - .nonRootTreeNodeCasesOrThrow() + .tilingTreeNodeCasesOrThrow() .findDeepMoveInTargetRecursive(orientation) } } diff --git a/src/command/SplitCommand.swift b/src/command/SplitCommand.swift index 99f7ce89..c2f29141 100644 --- a/src/command/SplitCommand.swift +++ b/src/command/SplitCommand.swift @@ -42,7 +42,10 @@ struct SplitCommand: Command { } return true case .macosInvisibleWindowsContainer: - state.stderr.append("Can't split invisible windows (minimized windows or windows of hidden apps)") + state.stderr.append("Can't split invisible windows (minimized windows or windows of hidden apps). This behavior may change in the future") + return false + case .macosFullscreenWindowsContainer(_): + state.stderr.append("Can't split fullscreen windows. This behavior may change in the future") return false } } diff --git a/src/mouse/moveWithMouse.swift b/src/mouse/moveWithMouse.swift index d90b9405..ac09a4b1 100644 --- a/src/mouse/moveWithMouse.swift +++ b/src/mouse/moveWithMouse.swift @@ -17,8 +17,8 @@ private func moveWithMouseIfTheCase(_ window: Window) { // todo cover with tests moveFloatingWindow(window) case .tilingContainer: moveTilingWindow(window) - case .macosInvisibleWindowsContainer: - return // Invisible window can't be moved with mouse + case .macosInvisibleWindowsContainer, .macosFullscreenWindowsContainer: + return // Invisible and fullscreen windows can't be moved with mouse } } @@ -87,7 +87,7 @@ extension CGPoint { case .accordion: target = tree.mostRecentChild } - switch target?.nonRootTreeNodeCasesOrThrow() { + switch target?.tilingTreeNodeCasesOrThrow() { case nil: return nil case .window(let window): diff --git a/src/mouse/resizeWithMouse.swift b/src/mouse/resizeWithMouse.swift index a0f346d2..63730420 100644 --- a/src/mouse/resizeWithMouse.swift +++ b/src/mouse/resizeWithMouse.swift @@ -28,8 +28,8 @@ private func resizeWithMouseIfTheCase(_ window: Window) { // todo cover with tes return } switch window.parent.cases { - case .workspace, .macosInvisibleWindowsContainer: - return // Nothing to do for floating or invisible windows + case .workspace, .macosInvisibleWindowsContainer, .macosFullscreenWindowsContainer: + return // Nothing to do for floating, invisible, or fullscreen windows case .tilingContainer: guard let rect = window.getRect() else { return } guard let lastAppliedLayoutRect = window.lastAppliedLayoutPhysicalRect else { return } diff --git a/src/normalizeLayoutReason.swift b/src/normalizeLayoutReason.swift new file mode 100644 index 00000000..375e9dd8 --- /dev/null +++ b/src/normalizeLayoutReason.swift @@ -0,0 +1,41 @@ +func normalizeLayoutReason() { + for workspace in Workspace.all { + let windows: [Window] = workspace.allLeafWindowsRecursive + _normalizeLayoutReason(workspace: workspace, windows: windows) + } + _normalizeLayoutReason(workspace: Workspace.focused, windows: macosInvisibleWindowsContainer.children.filterIsInstance(of: Window.self)) +} + +func _normalizeLayoutReason(workspace: Workspace, windows: [Window]) { + for window in windows { + let isMacosFullscreen = window.isMacosFullscreen + let isMacosInvisible = !isMacosFullscreen && (window.isMacosMinimized || window.macAppUnsafe.nsApp.isHidden) + switch window.layoutReason { + case .standard: + if isMacosFullscreen { + window.layoutReason = .macos(prevParentKind: window.parent.kind) + window.unbindFromParent() + window.bind(to: workspace.macOsNativeFullscreenWindowsContainer, adaptiveWeight: 1, index: INDEX_BIND_LAST) + } else if isMacosInvisible { + window.layoutReason = .macos(prevParentKind: window.parent.kind) + window.unbindFromParent() + window.bind(to: macosInvisibleWindowsContainer, adaptiveWeight: 1, index: INDEX_BIND_LAST) + } + case .macos(let prevParentKind): + if !isMacosFullscreen && !isMacosInvisible { + window.layoutReason = .standard + window.unbindFromParent() + switch prevParentKind { + case .workspace: + window.bindAsFloatingWindow(to: workspace) + case .tilingContainer: + let data = getBindingDataForNewTilingWindow(workspace) + window.bind(to: data.parent, adaptiveWeight: data.adaptiveWeight, index: data.index) + case .macosInvisibleWindowsContainer, .macosFullscreenWindowsContainer: // wtf case, should never be possible. But If encounter it, let's just re-layout window + let data = getBindingDataForNewWindow(window.asMacWindow().axWindow, workspace, window.macAppUnsafe) + window.bind(to: data.parent, adaptiveWeight: data.adaptiveWeight, index: data.index) + } + } + } + } +} diff --git a/src/refresh.swift b/src/refresh.swift index 9b5cf4b0..96a2cda3 100644 --- a/src/refresh.swift +++ b/src/refresh.swift @@ -73,40 +73,6 @@ private func refreshFocusedWorkspaceBasedOnFocusedWindow() { // todo drop. It sh } } -private func normalizeLayoutReason() { - let workspace = Workspace.focused - let windows: [Window] = workspace.allLeafWindowsRecursive + - macosInvisibleWindowsContainer.children.filterIsInstance(of: Window.self) - for window in windows { - let isMacosFullscreen = window.isMacosFullscreen - let isMacosInvisible = !isMacosFullscreen && (window.isMacosMinimized || window.macAppUnsafe.nsApp.isHidden) - if isMacosFullscreen && !window.layoutReason.isMacos { - window.layoutReason = .macos(prevParentKind: window.parent.kind) - window.unbindFromParent() - window.bindAsFloatingWindow(to: workspace) - } - if isMacosInvisible && !window.layoutReason.isMacos { - window.layoutReason = .macos(prevParentKind: window.parent.kind) - window.unbindFromParent() - window.bind(to: macosInvisibleWindowsContainer, adaptiveWeight: 1, index: INDEX_BIND_LAST) - } - if case .macos(let prevParentKind) = window.layoutReason, !isMacosFullscreen && !isMacosInvisible { - window.layoutReason = .standard - window.unbindFromParent() - switch prevParentKind { - case .workspace: - window.bindAsFloatingWindow(to: workspace) - case .tilingContainer: - let data = getBindingDataForNewTilingWindow(workspace) - window.bind(to: data.parent, adaptiveWeight: data.adaptiveWeight, index: data.index) - case .macosInvisibleWindowsContainer: // wtf case, should never be possible. But If encounter it, let's just re-layout window - let data = getBindingDataForNewWindow(window.asMacWindow().axWindow, workspace, window.macAppUnsafe) - window.bind(to: data.parent, adaptiveWeight: data.adaptiveWeight, index: data.index) - } - } - } -} - private func layoutWorkspaces() { for workspace in Workspace.all { if workspace.isVisible { diff --git a/src/tree/MacosFullscreenWindowsContainer.swift b/src/tree/MacosFullscreenWindowsContainer.swift new file mode 100644 index 00000000..70365485 --- /dev/null +++ b/src/tree/MacosFullscreenWindowsContainer.swift @@ -0,0 +1,8 @@ +import Common + +/// The container for macOS fullscreen windows +class MacosFullscreenWindowsContainer: TreeNode, NonLeafTreeNodeObject { + init(parent: Workspace) { + super.init(parent: parent, adaptiveWeight: 1, index: INDEX_BIND_LAST) + } +} diff --git a/src/tree/TreeNode.swift b/src/tree/TreeNode.swift index 9f5c4cda..f2313bfe 100644 --- a/src/tree/TreeNode.swift +++ b/src/tree/TreeNode.swift @@ -54,6 +54,8 @@ class TreeNode: Equatable { return parent.orientation == targetOrientation ? adaptiveWeight : parent.getWeight(targetOrientation) case .rootTilingContainer: return parent.getWeight(targetOrientation) + case .macosNativeFullscreenStubContainer: + error("Weight doesn't make sense for stub fullscreen container") } } @@ -78,7 +80,7 @@ class TreeNode: Equatable { self.adaptiveWeight = newParent.children.sumOf { $0.getWeight(newParent.orientation) } .div(newParent.children.count) ?? 1 - case .rootTilingContainer, .macosNativeInvisibleWindow: + case .rootTilingContainer, .macosNativeInvisibleWindow, .macosNativeFullscreenStubContainer: self.adaptiveWeight = 1 } } else { diff --git a/src/tree/TreeNodeCases.swift b/src/tree/TreeNodeCases.swift index 6d0bbab9..a9cd8f8b 100644 --- a/src/tree/TreeNodeCases.swift +++ b/src/tree/TreeNodeCases.swift @@ -5,15 +5,17 @@ enum TreeNodeCases { case tilingContainer(TilingContainer) case workspace(Workspace) case macosInvisibleWindowsContainer(MacosInvisibleWindowsContainer) + case macosFullscreenWindowsContainer(MacosFullscreenWindowsContainer) } enum NonLeafTreeNodeCases { case tilingContainer(TilingContainer) case workspace(Workspace) case macosInvisibleWindowsContainer(MacosInvisibleWindowsContainer) + case macosFullscreenWindowsContainer(MacosFullscreenWindowsContainer) } -enum NonRootTreeNodeCases { +enum TilingTreeNodeCases { case window(Window) case tilingContainer(TilingContainer) } @@ -22,6 +24,7 @@ enum NonLeafTreeNodeKind: Equatable { case tilingContainer case workspace case macosInvisibleWindowsContainer + case macosFullscreenWindowsContainer } protocol NonLeafTreeNodeObject: TreeNode {} @@ -36,12 +39,14 @@ extension TreeNode { return .tilingContainer(tilingContainer) } else if let container = self as? MacosInvisibleWindowsContainer { return .macosInvisibleWindowsContainer(container) + } else if let container = self as? MacosFullscreenWindowsContainer { + return .macosFullscreenWindowsContainer(container) } else { error("Unknown tree") } } - func nonRootTreeNodeCasesOrThrow() -> NonRootTreeNodeCases { + func tilingTreeNodeCasesOrThrow() -> TilingTreeNodeCases { if let window = self as? Window { return .window(window) } else if let tilingContainer = self as? TilingContainer { @@ -62,8 +67,10 @@ extension NonLeafTreeNodeObject { return .tilingContainer(tilingContainer) } else if let container = self as? MacosInvisibleWindowsContainer { return .macosInvisibleWindowsContainer(container) + } else if let container = self as? MacosFullscreenWindowsContainer { + return .macosFullscreenWindowsContainer(container) } else { - error("Unknown tree") + error("Unknown tree \(self)") } } @@ -75,6 +82,8 @@ extension NonLeafTreeNodeObject { return .workspace case .macosInvisibleWindowsContainer: return .macosInvisibleWindowsContainer + case .macosFullscreenWindowsContainer: + return .macosFullscreenWindowsContainer } } } @@ -82,6 +91,7 @@ extension NonLeafTreeNodeObject { enum ChildParentRelation: Equatable { case floatingWindow case macosNativeFullscreenWindow + case macosNativeFullscreenStubContainer case macosNativeInvisibleWindow case tiling(parent: TilingContainer) // todo consider splitting it on 'tiles' and 'accordion' case rootTilingContainer @@ -102,8 +112,8 @@ func getChildParentRelationOrNil(child: TreeNode, parent: NonLeafTreeNodeObject) switch (child.nodeCases, parent.cases) { case (.workspace, _): return nil - case (.window(let window), .workspace): - return window.isMacosFullscreen ? .macosNativeFullscreenWindow : .floatingWindow + case (.window, .workspace): + return .floatingWindow case (.window, .macosInvisibleWindowsContainer): return .macosNativeInvisibleWindow case (_, .macosInvisibleWindowsContainer): @@ -115,5 +125,13 @@ func getChildParentRelationOrNil(child: TreeNode, parent: NonLeafTreeNodeObject) return .rootTilingContainer case (.macosInvisibleWindowsContainer, _): return nil + case (.macosFullscreenWindowsContainer, .workspace): + return .macosNativeFullscreenStubContainer + case (.macosFullscreenWindowsContainer, _): + return nil + case (.window, .macosFullscreenWindowsContainer): + return .macosNativeFullscreenWindow + case (_, .macosFullscreenWindowsContainer): + return nil } } diff --git a/src/tree/TreeNodeEx.swift b/src/tree/TreeNodeEx.swift index a5df4ae0..2cbbe307 100644 --- a/src/tree/TreeNodeEx.swift +++ b/src/tree/TreeNodeEx.swift @@ -32,7 +32,7 @@ extension TreeNode { switch parent.cases { case .workspace(let parent): return parent.monitor - case .tilingContainer(let parent): + case .tilingContainer, .macosFullscreenWindowsContainer: return parent.nodeMonitor case .macosInvisibleWindowsContainer: return nil @@ -79,8 +79,8 @@ extension TreeNode { ) -> (parent: TilingContainer, ownIndex: Int)? { let innermostChild = parentsWithSelf.first(where: { (node: TreeNode) -> Bool in switch node.parent?.cases { - case .workspace, nil, .macosInvisibleWindowsContainer: - return true // stop searching + case .workspace, nil, .macosInvisibleWindowsContainer, .macosFullscreenWindowsContainer: + return true // stop searching. We didn't find it, or something went wrong case .tilingContainer(let parent): return (layout == nil || parent.layout == layout) && parent.orientation == direction.orientation && @@ -91,7 +91,7 @@ extension TreeNode { case .tilingContainer(let parent): check(parent.orientation == direction.orientation) return (parent, innermostChild.ownIndexOrNil!) - case .workspace, nil, .macosInvisibleWindowsContainer: + case .workspace, nil, .macosInvisibleWindowsContainer, .macosFullscreenWindowsContainer: return nil } } diff --git a/src/tree/WorkspaceEx.swift b/src/tree/WorkspaceEx.swift index 3f386b1d..08226849 100644 --- a/src/tree/WorkspaceEx.swift +++ b/src/tree/WorkspaceEx.swift @@ -28,10 +28,22 @@ extension Workspace { //: (nativeFocusedWindow?.workspace ?? Workspace.get(byName: focusedWorkspaceName)) } - var floatingAndMacosFullscreenWindows: [Window] { + var floatingWindows: [Window] { children.filterIsInstance(of: Window.self) } + var macOsNativeFullscreenWindowsContainer: MacosFullscreenWindowsContainer { + let containers = children.filterIsInstance(of: MacosFullscreenWindowsContainer.self) + switch containers.count { + case 0: + return MacosFullscreenWindowsContainer(parent: self) + case 1: + return containers.singleOrNil()! + default: + error("Workspace must contain zero or one MacosFullscreenWindowsContainer") + } + } + var forceAssignedMonitor: Monitor? { guard let monitorDescriptions = config.workspaceToMonitorForceAssignment[name] else { return nil } let sortedMonitors = sortedMonitors diff --git a/src/tree/layoutRecursive.swift b/src/tree/layoutRecursive.swift index 89fbd673..e68acb26 100644 --- a/src/tree/layoutRecursive.swift +++ b/src/tree/layoutRecursive.swift @@ -47,8 +47,8 @@ private extension TreeNode { case .accordion: container.layoutAccordion(point, width: width, height: height, virtual: virtual, context) } - case .macosInvisibleWindowsContainer: - return // Nothing to do for invisible windows + case .macosInvisibleWindowsContainer, .macosFullscreenWindowsContainer: + return // Nothing to do for invisible and fullscreen windows } } } diff --git a/test/command/MoveCommandTest.swift b/test/command/MoveCommandTest.swift index dd56c262..e763dcc1 100644 --- a/test/command/MoveCommandTest.swift +++ b/test/command/MoveCommandTest.swift @@ -220,6 +220,8 @@ extension TreeNode { return .workspace(workspace.children.map(\.layoutDescription)) case .macosInvisibleWindowsContainer: return .macosInvisible + case .macosFullscreenWindowsContainer: + return .macosFullscreen } } } @@ -232,4 +234,5 @@ enum LayoutDescription: Equatable { case v_accordion([LayoutDescription]) case window(UInt32) case macosInvisible + case macosFullscreen }