diff --git a/src/command/CloseAllWindowsButCurrentCommand.swift b/src/command/CloseAllWindowsButCurrentCommand.swift index c2d6cb97..9a5ff21c 100644 --- a/src/command/CloseAllWindowsButCurrentCommand.swift +++ b/src/command/CloseAllWindowsButCurrentCommand.swift @@ -10,7 +10,11 @@ struct CloseAllWindowsButCurrentCommand: Command { return false } var result = true - for window in focused.workspace.allLeafWindowsRecursive { + guard let workspace = focused.workspace else { + state.stderr.append("Focused window '\(focused.title)'") + return false + } + for window in workspace.allLeafWindowsRecursive { if window != focused { state.subject = .window(window) result = CloseCommand(args: args.closeArgs).run(state) && result diff --git a/src/command/Command.swift b/src/command/Command.swift index 90ebad2b..0a02f6fd 100644 --- a/src/command/Command.swift +++ b/src/command/Command.swift @@ -83,7 +83,7 @@ extension CommandSubject { var workspace: Workspace { switch self { case .window(let window): - return window.workspace + return window.nodeMonitor?.activeWorkspace ?? Workspace.focused case .emptyWorkspace(let workspaceName): return Workspace.get(byName: workspaceName) } diff --git a/src/command/FocusCommand.swift b/src/command/FocusCommand.swift index dd9f8372..fe9ea1be 100644 --- a/src/command/FocusCommand.swift +++ b/src/command/FocusCommand.swift @@ -109,7 +109,7 @@ private func makeFloatingWindowsSeenAsTiling(workspace: Workspace) -> [FloatingW let center = window.isMacosFullscreen ? workspace.monitor.rect.topLeftCorner : 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: window.workspace.monitor.visibleRectPaddedByOuterGaps).findIn(tree: workspace.rootTilingContainer, virtual: true) else { return nil } + guard let target = center.coerceIn(rect: workspace.monitor.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) diff --git a/src/command/LayoutCommand.swift b/src/command/LayoutCommand.swift index cad9a0cd..3519bc18 100644 --- a/src/command/LayoutCommand.swift +++ b/src/command/LayoutCommand.swift @@ -36,21 +36,19 @@ struct LayoutCommand: Command { return false case .tilingContainer: return true // Nothing to do - case .workspace: + case .workspace(let workspace): window.lastFloatingSize = window.getSize() ?? window.lastFloatingSize - let data = getBindingDataForNewTilingWindow(window.unbindFromParent().parent.workspace) + window.unbindFromParent() + let data = getBindingDataForNewTilingWindow(workspace) window.bind(to: data.parent, adaptiveWeight: data.adaptiveWeight, index: data.index) return true } case .floating: - let workspace = window.unbindFromParent().parent.workspace + let workspace = state.subject.workspace // Capture workspace before unbind ID-1A4CF7C5 + window.unbindFromParent() // ID-1A4CF7C5 window.bindAsFloatingWindow(to: workspace) guard let topLeftCorner = window.getTopLeftCorner() else { return false } - let offset = CGPoint( - x: abs(topLeftCorner.x - workspace.monitor.rect.topLeftX).takeIf { $0 < 30 } ?? 0, - y: abs(topLeftCorner.y - workspace.monitor.rect.topLeftY).takeIf { $0 < 30 } ?? 0 - ) - return window.setFrame(topLeftCorner + offset, window.lastFloatingSize) + return window.setFrame(topLeftCorner, window.lastFloatingSize) } } } diff --git a/src/command/MacosNativeFullscreenCommand.swift b/src/command/MacosNativeFullscreenCommand.swift index e320906d..99bed3f4 100644 --- a/src/command/MacosNativeFullscreenCommand.swift +++ b/src/command/MacosNativeFullscreenCommand.swift @@ -1,6 +1,11 @@ import Common -struct MacosNativeFullscreenCommand: Command { +/// Problem B6E178F2: It's not first-class citizen command in AeroSpace model, since it interacts with macOS API directly. +/// Consecutive macos-native-fullscreen commands may not works as expected (because macOS may report correct state with a +/// delay), or may flicker +/// +/// The same applies to macos-native-minimize command +struct MacosNativeFullscreenCommand: Command { // todo only allow as the latest command in sequence let args: MacosNativeFullscreenCmdArgs func _run(_ state: CommandMutableState, stdin: String) -> Bool { @@ -12,6 +17,7 @@ struct MacosNativeFullscreenCommand: Command { let axWindow = window.asMacWindow().axWindow let success = axWindow.set(Ax.isFullscreenAttr, !window.isMacosFullscreen) if !success { state.stderr.append("Failed") } + // todo attach or detach to appropriate parent return success } } diff --git a/src/command/MacosNativeMinimizeCommand.swift b/src/command/MacosNativeMinimizeCommand.swift index 7076c3e4..07b9ec94 100644 --- a/src/command/MacosNativeMinimizeCommand.swift +++ b/src/command/MacosNativeMinimizeCommand.swift @@ -1,5 +1,6 @@ import Common +/// See: MacosNativeFullscreenCommand. Problem B6E178F2 struct MacosNativeMinimizeCommand: Command { let args: MacosNativeMinimizeCmdArgs diff --git a/src/command/MoveCommand.swift b/src/command/MoveCommand.swift index bb1dbda0..73559953 100644 --- a/src/command/MoveCommand.swift +++ b/src/command/MoveCommand.swift @@ -57,7 +57,7 @@ private func moveOut(_ state: CommandMutableState, window: Window, direction: Ca case .workspace(let parent): // create implicit container let prevRoot = parent.rootTilingContainer prevRoot.unbindFromParent() - // Force list layout + // Force tiles layout _ = TilingContainer(parent: parent, adaptiveWeight: WEIGHT_AUTO, direction.orientation, .tiles, index: 0) check(prevRoot != parent.rootTilingContainer) prevRoot.bind(to: parent.rootTilingContainer, adaptiveWeight: WEIGHT_AUTO, index: 0) diff --git a/src/command/MoveNodeToWorkspaceCommand.swift b/src/command/MoveNodeToWorkspaceCommand.swift index 1c782e65..d5e1d007 100644 --- a/src/command/MoveNodeToWorkspaceCommand.swift +++ b/src/command/MoveNodeToWorkspaceCommand.swift @@ -8,21 +8,21 @@ struct MoveNodeToWorkspaceCommand: Command { state.stderr.append(noWindowIsFocused) return false } - let preserveWorkspace = focused.workspace + let prevWorkspace = focused.workspace ?? Workspace.focused let targetWorkspace: Workspace switch args.target { case .relative(let relative): - guard let workspace = getNextPrevWorkspace(current: state.subject.workspace, relative: relative, stdin: stdin) else { return false } + guard let workspace = getNextPrevWorkspace(current: prevWorkspace, relative: relative, stdin: stdin) else { return false } targetWorkspace = workspace case .direct(let direct): targetWorkspace = Workspace.get(byName: direct.name.raw) } - if preserveWorkspace == targetWorkspace { + if prevWorkspace == targetWorkspace { return true } let targetContainer: NonLeafTreeNodeObject = focused.isFloating ? targetWorkspace : targetWorkspace.rootTilingContainer focused.unbindFromParent() focused.bind(to: targetContainer, adaptiveWeight: WEIGHT_AUTO, index: INDEX_BIND_LAST) - return WorkspaceCommand.run(state, preserveWorkspace.name) + return WorkspaceCommand.run(state, prevWorkspace.name) } } diff --git a/src/command/MoveWorkspaceToMonitorCommand.swift b/src/command/MoveWorkspaceToMonitorCommand.swift index 98ab3aac..d1a16931 100644 --- a/src/command/MoveWorkspaceToMonitorCommand.swift +++ b/src/command/MoveWorkspaceToMonitorCommand.swift @@ -5,7 +5,7 @@ struct MoveWorkspaceToMonitorCommand: Command { func _run(_ state: CommandMutableState, stdin: String) -> Bool { check(Thread.current.isMainThread) - let focusedWorkspace = state.subject.workspace + let focusedWorkspace = state.subject.workspace ?? Workspace.focused let prevMonitor = focusedWorkspace.monitor let sortedMonitors = sortedMonitors guard let index = sortedMonitors.firstIndex(where: { $0.rect.topLeftCorner == prevMonitor.rect.topLeftCorner }) else { return false } diff --git a/src/command/query/DebugWindowsCommand.swift b/src/command/query/DebugWindowsCommand.swift index 46878de7..2fbda641 100644 --- a/src/command/query/DebugWindowsCommand.swift +++ b/src/command/query/DebugWindowsCommand.swift @@ -81,7 +81,8 @@ func debugWindowsIfRecording(_ window: Window) { var result: [String] = [] result.append("\(windowPrefix) windowId: \(window.windowId)") - result.append("\(windowPrefix) workspace: \(window.workspace.name)") + result.append("\(windowPrefix) workspace: \(window.workspace?.name ?? "nil")") + result.append("\(windowPrefix) treeNodeParent: \(window.parent)") result.append("\(windowPrefix) recognizedAsDialog: \(shouldFloat(window.axWindow, app))") result.append(dumpAx(window.axWindow, windowPrefix, .window)) diff --git a/src/command/query/ListWindowsCommand.swift b/src/command/query/ListWindowsCommand.swift index 3ac7d0b5..4b248820 100644 --- a/src/command/query/ListWindowsCommand.swift +++ b/src/command/query/ListWindowsCommand.swift @@ -51,7 +51,7 @@ struct ListWindowsCommand: Command { } state.stdout += windows .map { window in - [String(window.windowId), window.app.name ?? "NULL-APP-NAME", window.title ?? "NULL-TITLE"] + [String(window.windowId), window.app.name ?? "NULL-APP-NAME", window.title] } .toPaddingTable() return true diff --git a/src/config/parseConfig.swift b/src/config/parseConfig.swift index 54418e18..b3c544dd 100644 --- a/src/config/parseConfig.swift +++ b/src/config/parseConfig.swift @@ -151,6 +151,7 @@ private extension ParsedCmd where T == any Command { } } +// todo Problem B6E178F2. Make macos-native* commands to be the last commands in the sequence func parseCommandOrCommands(_ raw: TOMLValueConvertible) -> Parsed<[any Command]> { if let rawString = raw.string { return parseCommand(rawString).toEither().map { [$0] } diff --git a/src/mouse/moveWithMouse.swift b/src/mouse/moveWithMouse.swift index df448123..d90b9405 100644 --- a/src/mouse/moveWithMouse.swift +++ b/src/mouse/moveWithMouse.swift @@ -35,7 +35,7 @@ private func moveTilingWindow(_ window: Window) { window.lastAppliedLayoutPhysicalRect = nil let mouseLocation = mouseLocation let targetWorkspace = mouseLocation.monitorApproximation.activeWorkspace - let swapTarget = mouseLocation.findIn(tree: targetWorkspace.workspace.rootTilingContainer, virtual: false)?.takeIf({ $0 != window }) + let swapTarget = mouseLocation.findIn(tree: targetWorkspace.rootTilingContainer, virtual: false)?.takeIf({ $0 != window }) if targetWorkspace != window.workspace { // Move window to a different monitor let index: Int if let swapTarget, let parent = swapTarget.parent as? TilingContainer, let targetRect = swapTarget.lastAppliedLayoutPhysicalRect { diff --git a/src/refresh.swift b/src/refresh.swift index 588443b1..9b5cf4b0 100644 --- a/src/refresh.swift +++ b/src/refresh.swift @@ -65,8 +65,9 @@ func takeFocusFromMacOs(_ nativeFocused: Window?, startup: Bool) { // alternativ } private func refreshFocusedWorkspaceBasedOnFocusedWindow() { // todo drop. It should no longer be necessary - if let focusedWindow = focusedWindow { - let focusedWorkspace: Workspace = focusedWindow.workspace + if let focusedWindow = focusedWindow, let monitor = focusedWindow.nodeMonitor { + // todo it's rather refresh focused monitor + let focusedWorkspace: Workspace = monitor.activeWorkspace check(focusedWorkspace.monitor.setActiveWorkspace(focusedWorkspace)) focusedWorkspaceName = focusedWorkspace.name } @@ -74,7 +75,9 @@ private func refreshFocusedWorkspaceBasedOnFocusedWindow() { // todo drop. It sh private func normalizeLayoutReason() { let workspace = Workspace.focused - for window in workspace.allLeafWindowsRecursive { + 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 { @@ -87,7 +90,7 @@ private func normalizeLayoutReason() { window.unbindFromParent() window.bind(to: macosInvisibleWindowsContainer, adaptiveWeight: 1, index: INDEX_BIND_LAST) } - if case .macos(let prevParentKind) = window.layoutReason, !isMacosFullscreen { + if case .macos(let prevParentKind) = window.layoutReason, !isMacosFullscreen && !isMacosInvisible { window.layoutReason = .standard window.unbindFromParent() switch prevParentKind { diff --git a/src/tree/MacWindow.swift b/src/tree/MacWindow.swift index 89703230..572575ae 100644 --- a/src/tree/MacWindow.swift +++ b/src/tree/MacWindow.swift @@ -63,14 +63,14 @@ final class MacWindow: Window, CustomStringConvertible { if MacWindow.allWindowsMap.removeValue(forKey: windowId) == nil { return } - let workspace = unbindFromParent().parent.workspace.name + let workspace = unbindFromParent().parent.workspace?.name for obs in axObservers { AXObserverRemoveNotification(obs.obs, obs.ax, obs.notif) } axObservers = [] // todo the if is an approximation to filter out cases when window just closed itself (or was killed remotely) // we might want to track the time of the latest workspace switch to make the approximation more accurate - if workspace == previousFocusedWorkspaceName || workspace == focusedWorkspaceName { + if let workspace, workspace == previousFocusedWorkspaceName || workspace == focusedWorkspaceName { refreshSession(forceFocus: true) { _ = WorkspaceCommand.run(.focused, workspace) } @@ -83,7 +83,7 @@ final class MacWindow: Window, CustomStringConvertible { return true } - override var title: String? { axWindow.get(Ax.titleAttr) } + override var title: String { axWindow.get(Ax.titleAttr) ?? "" } override var isMacosFullscreen: Bool { axWindow.get(Ax.isFullscreenAttr) == true } override var isMacosMinimized: Bool { axWindow.get(Ax.minimizedAttr) == true } @@ -105,16 +105,18 @@ final class MacWindow: Window, CustomStringConvertible { if !isHiddenViaEmulation { debug("hideViaEmulation: Hide \(self)") guard let topLeftCorner = getTopLeftCorner() else { return } + guard let workspace else { return } // hiding only makes sense for workspace windows prevUnhiddenEmulationPositionRelativeToWorkspaceAssignedRect = topLeftCorner - workspace.monitor.rect.topLeftCorner } - setTopLeftCorner(allMonitorsRectsUnion.bottomRightCorner) + _ = setTopLeftCorner(allMonitorsRectsUnion.bottomRightCorner) } func unhideViaEmulation() { guard let prevUnhiddenEmulationPositionRelativeToWorkspaceAssignedRect else { return } + guard let workspace else { return } // hiding only makes sense for workspace windows - setTopLeftCorner(workspace.monitor.rect.topLeftCorner + prevUnhiddenEmulationPositionRelativeToWorkspaceAssignedRect) + _ = setTopLeftCorner(workspace.monitor.rect.topLeftCorner + prevUnhiddenEmulationPositionRelativeToWorkspaceAssignedRect) self.prevUnhiddenEmulationPositionRelativeToWorkspaceAssignedRect = nil } @@ -277,7 +279,7 @@ extension WindowDetectedCallback { if let startupMatcher = matcher.duringAeroSpaceStartup, startupMatcher != startup { return false } - if let regex = matcher.windowTitleRegexSubstring, !(window.title ?? "").contains(regex) { + if let regex = matcher.windowTitleRegexSubstring, !(window.title).contains(regex) { return false } if let appId = matcher.appId, appId != window.app.id { diff --git a/src/tree/TreeNodeEx.swift b/src/tree/TreeNodeEx.swift index 21753bf2..a5df4ae0 100644 --- a/src/tree/TreeNodeEx.swift +++ b/src/tree/TreeNodeEx.swift @@ -23,8 +23,20 @@ extension TreeNode { var parents: [NonLeafTreeNodeObject] { parent.flatMap { [$0] + $0.parents } ?? [] } var parentsWithSelf: [TreeNode] { parent.flatMap { [self] + $0.parentsWithSelf } ?? [self] } - var workspace: Workspace { - self as? Workspace ?? parent?.workspace ?? errorT("Unknown type \(Self.self)") + var workspace: Workspace? { + self as? Workspace ?? parent?.workspace + } + + var nodeMonitor: Monitor? { + guard let parent else { return nil } + switch parent.cases { + case .workspace(let parent): + return parent.monitor + case .tilingContainer(let parent): + return parent.nodeMonitor + case .macosInvisibleWindowsContainer: + return nil + } } var mostRecentWindow: Window? { diff --git a/src/tree/Window.swift b/src/tree/Window.swift index 37adf152..d32f61e4 100644 --- a/src/tree/Window.swift +++ b/src/tree/Window.swift @@ -26,9 +26,9 @@ class Window: TreeNode, Hashable { func getTopLeftCorner() -> CGPoint? { error("Not implemented") } func getSize() -> CGSize? { error("Not implemented") } - var title: String? { error("Not implemented") } + var title: String { error("Not implemented") } var isMacosFullscreen: Bool { false } - var isMacosMinimized: Bool { false } + var isMacosMinimized: Bool { false } // todo replace with enum MacOsWindowNativeState { normal, fullscreen, invisible } var isHiddenViaEmulation: Bool { error("Not implemented") } func setSize(_ size: CGSize) -> Bool { error("Not implemented") } @@ -59,10 +59,14 @@ extension Window { var ownIndex: Int { ownIndexOrNil! } - func focus() { // todo rename: focusWindowAndWorkspace + func focus() -> Bool { // todo rename: focusWindowAndWorkspace markAsMostRecentChild() // todo bug make the workspace active first... - focusedWorkspaceName = workspace.name + if let workspace = workspace ?? nodeMonitor?.activeWorkspace { // todo change focusedWorkspaceName to focused monitor + focusedWorkspaceName = workspace.name + return nodeMonitor?.setActiveWorkspace(workspace) ?? true + } // else if We should exit-native-fullscreen/unminimize window if we want to fix B6E178F2 + return true } func setFrame(_ topLeft: CGPoint?, _ size: CGSize?) -> Bool { diff --git a/src/tree/Workspace.swift b/src/tree/Workspace.swift index 10fea056..208f2dbf 100644 --- a/src/tree/Workspace.swift +++ b/src/tree/Workspace.swift @@ -93,7 +93,7 @@ class Workspace: TreeNode, NonLeafTreeNodeObject, Hashable, Identifiable, Custom extension Workspace { var isVisible: Bool { visibleWorkspaceToScreenPoint.keys.contains(self) } - var monitor: Monitor { + var monitor: Monitor { // todo rename to workspaceMonitor (to distinguish from nodeMonitor) forceAssignedMonitor ?? visibleWorkspaceToScreenPoint[self]?.monitorApproximation ?? assignedMonitorPoint?.monitorApproximation diff --git a/src/tree/WorkspaceEx.swift b/src/tree/WorkspaceEx.swift index 60f721d2..3f386b1d 100644 --- a/src/tree/WorkspaceEx.swift +++ b/src/tree/WorkspaceEx.swift @@ -29,7 +29,7 @@ extension Workspace { } var floatingAndMacosFullscreenWindows: [Window] { - workspace.children.filterIsInstance(of: Window.self) + children.filterIsInstance(of: Window.self) } var forceAssignedMonitor: Monitor? { @@ -39,13 +39,4 @@ extension Workspace { .compactMap { $0.resolveMonitor(sortedMonitors: sortedMonitors) } .first } - - func layoutWorkspace() { - if isEffectivelyEmpty { return } - let rect = monitor.visibleRectPaddedByOuterGaps - // If monitors are aligned vertically and the monitor below has smaller width, then macOS may not allow the - // window on the upper monitor to take full width. rect.height - 1 resolves this problem - // But I also faced this problem in mointors horizontal configuration. ¯\_(ツ)_/¯ - layoutRecursive(rect.topLeftCorner, width: rect.width, height: rect.height - 1, virtual: rect) - } } diff --git a/src/tree/layoutRecursive.swift b/src/tree/layoutRecursive.swift index 3525b1a1..89fbd673 100644 --- a/src/tree/layoutRecursive.swift +++ b/src/tree/layoutRecursive.swift @@ -1,5 +1,16 @@ -extension TreeNode { - func layoutRecursive(_ point: CGPoint, width: CGFloat, height: CGFloat, virtual: Rect) { +extension Workspace { + func layoutWorkspace() { + if isEffectivelyEmpty { return } + let rect = monitor.visibleRectPaddedByOuterGaps + // If monitors are aligned vertically and the monitor below has smaller width, then macOS may not allow the + // window on the upper monitor to take full width. rect.height - 1 resolves this problem + // But I also faced this problem in mointors horizontal configuration. ¯\_(ツ)_/¯ + layoutRecursive(rect.topLeftCorner, width: rect.width, height: rect.height - 1, virtual: rect, LayoutContext(workspace: self)) + } +} + +private extension TreeNode { + func layoutRecursive(_ point: CGPoint, width: CGFloat, height: CGFloat, virtual: Rect, _ context: LayoutContext) { var point = point if let orientation = (self as? TilingContainer)?.orientation, orientation == (parent as? TilingContainer)?.orientation { point = orientation == .h @@ -11,20 +22,20 @@ extension TreeNode { case .workspace(let workspace): lastAppliedLayoutPhysicalRect = physicalRect lastAppliedLayoutVirtualRect = virtual - workspace.rootTilingContainer.layoutRecursive(point, width: width, height: height, virtual: virtual) + workspace.rootTilingContainer.layoutRecursive(point, width: width, height: height, virtual: virtual, context) for window in workspace.children.filterIsInstance(of: Window.self) { - window.layoutFloatingWindow() + window.layoutFloatingWindow(context) } case .window(let window): if window.windowId != currentlyManipulatedWithMouseWindowId { lastAppliedLayoutVirtualRect = virtual - if window.isFullscreen && window == workspace.rootTilingContainer.mostRecentWindow { + if window.isFullscreen && window == context.workspace.rootTilingContainer.mostRecentWindow { lastAppliedLayoutPhysicalRect = nil - window.layoutFullscreen() + window.layoutFullscreen(context) } else { lastAppliedLayoutPhysicalRect = physicalRect window.isFullscreen = false - window.setFrame(point, CGSize(width: width, height: height)) + _ = window.setFrame(point, CGSize(width: width, height: height)) } } case .tilingContainer(let container): @@ -32,9 +43,9 @@ extension TreeNode { lastAppliedLayoutVirtualRect = virtual switch container.layout { case .tiles: - container.layoutTiles(point, width: width, height: height, virtual: virtual) + container.layoutTiles(point, width: width, height: height, virtual: virtual, context) case .accordion: - container.layoutAccordion(point, width: width, height: height, virtual: virtual) + container.layoutAccordion(point, width: width, height: height, virtual: virtual, context) } case .macosInvisibleWindowsContainer: return // Nothing to do for invisible windows @@ -42,34 +53,39 @@ extension TreeNode { } } +private struct LayoutContext { + let workspace: Workspace +} + private extension Window { - func layoutFloatingWindow() { + func layoutFloatingWindow(_ context: LayoutContext) { + let workspace = context.workspace let currentMonitor = getCenter()?.monitorApproximation if let currentMonitor, let windowTopLeftCorner = getTopLeftCorner(), workspace != currentMonitor.activeWorkspace { let xProportion = (windowTopLeftCorner.x - currentMonitor.visibleRect.topLeftX) / currentMonitor.visibleRect.width let yProportion = (windowTopLeftCorner.y - currentMonitor.visibleRect.topLeftY) / currentMonitor.visibleRect.height let moveTo = workspace.monitor - setTopLeftCorner(CGPoint( + _ = setTopLeftCorner(CGPoint( x: moveTo.visibleRect.topLeftX + xProportion * moveTo.visibleRect.width, y: moveTo.visibleRect.topLeftY + yProportion * moveTo.visibleRect.height )) } if isFullscreen { - layoutFullscreen() + layoutFullscreen(context) isFullscreen = false } } - func layoutFullscreen() { - let monitorRect = workspace.monitor.visibleRectPaddedByOuterGaps - setFrame(monitorRect.topLeftCorner, CGSize(width: monitorRect.width, height: monitorRect.height)) + func layoutFullscreen(_ context: LayoutContext) { + let monitorRect = context.workspace.monitor.visibleRectPaddedByOuterGaps + _ = setFrame(monitorRect.topLeftCorner, CGSize(width: monitorRect.width, height: monitorRect.height)) } } private extension TilingContainer { - func layoutTiles(_ point: CGPoint, width: CGFloat, height: CGFloat, virtual: Rect) { - let gaps = ResolvedGaps(gaps: config.gaps, monitor: workspace.monitor) + func layoutTiles(_ point: CGPoint, width: CGFloat, height: CGFloat, virtual: Rect, _ context: LayoutContext) { + let gaps = ResolvedGaps(gaps: config.gaps, monitor: context.workspace.monitor) var point = point var virtualPoint = virtual.topLeftCorner @@ -95,14 +111,15 @@ private extension TilingContainer { topLeftY: virtualPoint.y, width: orientation == .h ? child.hWeight : width, height: orientation == .v ? child.vWeight : height - ) + ), + context ) virtualPoint = orientation == .h ? virtualPoint.addingXOffset(child.hWeight) : virtualPoint.addingYOffset(child.vWeight) point = orientation == .h ? point.addingXOffset(child.hWeight) : point.addingYOffset(child.vWeight) } } - func layoutAccordion(_ point: CGPoint, width: CGFloat, height: CGFloat, virtual: Rect) { + func layoutAccordion(_ point: CGPoint, width: CGFloat, height: CGFloat, virtual: Rect, _ context: LayoutContext) { guard let mruIndex: Int = mostRecentChild?.ownIndexOrNil else { return } for (index, child) in children.enumerated() { let lPadding: CGFloat @@ -133,14 +150,16 @@ private extension TilingContainer { point + CGPoint(x: lPadding, y: 0), width: width - rPadding - lPadding, height: height, - virtual: virtual + virtual: virtual, + context ) case .v: child.layoutRecursive( point + CGPoint(x: 0, y: lPadding), width: width, height: height - lPadding - rPadding, - virtual: virtual + virtual: virtual, + context ) } } diff --git a/test/command/MoveCommandTest.swift b/test/command/MoveCommandTest.swift index 815784c0..dd56c262 100644 --- a/test/command/MoveCommandTest.swift +++ b/test/command/MoveCommandTest.swift @@ -216,7 +216,7 @@ extension TreeNode { ? .h_accordion(container.children.map(\.layoutDescription)) : .v_accordion(container.children.map(\.layoutDescription)) } - case .workspace: + case .workspace(let workspace): return .workspace(workspace.children.map(\.layoutDescription)) case .macosInvisibleWindowsContainer: return .macosInvisible diff --git a/test/testUtil.swift b/test/testUtil.swift index 858ad6ea..05ed45a1 100644 --- a/test/testUtil.swift +++ b/test/testUtil.swift @@ -36,6 +36,7 @@ func setUpWorkspacesForTests() { Workspace.garbageCollectUnusedWorkspaces() check(Workspace.focused.isEffectivelyEmpty) check(Workspace.focused === Workspace.all.singleOrNil(), Workspace.all.map(\.description).joined(separator: ", ")) + check(mainMonitor.setActiveWorkspace(Workspace.focused)) TestApp.shared.focusedWindow = nil TestApp.shared.windows = [] diff --git a/test/tree/TestWindow.swift b/test/tree/TestWindow.swift index 263e222a..6467a1cb 100644 --- a/test/tree/TestWindow.swift +++ b/test/tree/TestWindow.swift @@ -19,7 +19,7 @@ final class TestWindow: Window, CustomStringConvertible { return true } - override var title: String? { description } + override var title: String { description } override func getRect() -> Rect? { // todo change to not Optional _rect