aerospace fork
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189import AppKitimport Common
struct MoveCommand: Command { let args: MoveCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = true
func run(_ env: CmdEnv, _ io: CmdIo) async -> BinaryExitCode { let direction = args.direction.val guard let target = args.resolveTargetOrReportError(env, io) else { return .fail } guard let currentWindow = target.windowOrNil else { return .fail(io.err(noWindowIsFocused)) } if await shouldFailBecauseFullscreen_nonCancellable( window: currentWindow, failIfFullscreen: args.failIfFullscreen, failIfMacosNativeFullscreen: args.failIfMacosNativeFullscreen, ) { return .fail } switch currentWindow.windowParentCases { case .unbound: return .fail case .tilingContainer(let parent): guard let indexOfCurrent = currentWindow.ownIndex else { return .fail(io.err(bugPrompt())) } let indexOfSiblingTarget = indexOfCurrent + direction.focusOffset if parent.orientation == direction.orientation && parent.children.indices.contains(indexOfSiblingTarget) { switch parent.children[indexOfSiblingTarget].tilingTreeNodeCasesOrDie() { case .tilingContainer(let topLevelSiblingTargetContainer): return deepMoveIn(window: currentWindow, into: topLevelSiblingTargetContainer, moveDirection: direction, io) case .window: // "swap windows" let prevBinding = currentWindow.unbindFromParent() currentWindow.bind(to: parent, adaptiveWeight: prevBinding.adaptiveWeight, index: indexOfSiblingTarget) return .succ } } else { return moveOut(tilingWindow: currentWindow, direction: direction, io, args, env) } case .floatingWindowsContainer: // floating window return .fail(io.err("moving floating windows isn't yet supported")) // todo case .macosMinimizedWindowsContainer, .macosFullscreenWindowsContainer, .macosHiddenAppsWindowsContainer: return .fail(io.err(moveOutMacosUnconventionalWindow)) case .macosPopupWindowsContainer: return .fail(io.err(bugPrompt())) // Impossible } }}
@MainActor private func hitWorkspaceBoundaries( _ window: Window, _ workspace: Workspace, _ io: CmdIo, _ args: MoveCmdArgs, _ direction: CardinalDirection, _ env: CmdEnv,) -> BinaryExitCode { switch args.boundaries { case .workspace: switch args.boundariesAction { case .stop: return .succ case .fail: return .fail case .createImplicitContainer: createImplicitContainerAndMoveWindow(window, workspace, direction) return .succ } case .allMonitorsOuterFrame: guard let (monitors, index) = window.nodeMonitor?.findRelativeMonitor(inDirection: direction) else { return .fail(io.err("Should never happen. Can't find the current monitor")) }
if monitors.indices.contains(index) { let moveNodeToMonitorArgs = MoveNodeToMonitorCmdArgs(target: .direction(direction)) .copy(\.windowId, window.windowId) .copy(\.focusFollowsWindow, focus.windowOrNil == window)
return MoveNodeToMonitorCommand(args: moveNodeToMonitorArgs).run(env, io) } else { return hitAllMonitorsOuterFrameBoundaries(window, workspace, args, direction) } }}
@MainActor private func hitAllMonitorsOuterFrameBoundaries( _ window: Window, _ workspace: Workspace, _ args: MoveCmdArgs, _ direction: CardinalDirection,) -> BinaryExitCode { switch args.boundariesAction { case .stop: return .succ case .fail: return .fail case .createImplicitContainer: createImplicitContainerAndMoveWindow(window, workspace, direction) return .succ }}
private let moveOutMacosUnconventionalWindow = "moving macOS fullscreen, minimized windows and windows of hidden apps isn't yet supported. This behavior is subject to change"
@MainActor private func moveOut( tilingWindow window: Window, direction: CardinalDirection, _ io: CmdIo, _ args: MoveCmdArgs, _ env: CmdEnv,) -> BinaryExitCode { let innerMostTilingContainer = window.parents.first(where: { return switch $0.parent?.cases { case .tilingContainer(let parent): parent.orientation == direction.orientation // Stop searching: we have hit the workspace case nil, .workspace: true // Impossible: tilingContainer's parent can only be a workspace or tilingContainer case .floatingWindowsContainer, .macosMinimizedWindowsContainer, .macosFullscreenWindowsContainer, .macosHiddenAppsWindowsContainer, .macosPopupWindowsContainer: true } }) as? TilingContainer guard let innerMostTilingContainer else { return .fail(io.err(bugPrompt())) } // Impossible switch innerMostTilingContainer.tilingContainerParentCases { case .unbound: return .fail case .tilingContainer(let parent): check(parent.orientation == direction.orientation) guard let ownIndex = innerMostTilingContainer.ownIndex else { return .fail(io.err(bugPrompt())) } window.bind(to: parent, adaptiveWeight: WEIGHT_AUTO, index: ownIndex + direction.insertionOffset) return .succ case .workspace(let parent): return hitWorkspaceBoundaries(window, parent, io, args, direction, env) }}
@MainActor private func createImplicitContainerAndMoveWindow( _ window: Window, _ workspace: Workspace, _ direction: CardinalDirection,) { let prevRoot = workspace.rootTilingContainer prevRoot.unbindFromParent() // Force tiles layout _ = TilingContainer(parent: workspace, adaptiveWeight: WEIGHT_AUTO, direction.orientation, .tiles, index: 0) check(prevRoot != workspace.rootTilingContainer) prevRoot.bind(to: workspace.rootTilingContainer, adaptiveWeight: WEIGHT_AUTO, index: 0) window.bind(to: workspace.rootTilingContainer, adaptiveWeight: WEIGHT_AUTO, index: direction.insertionOffset)}
@MainActor private func deepMoveIn(window: Window, into container: TilingContainer, moveDirection: CardinalDirection, _ io: CmdIo) -> BinaryExitCode { let deepTarget = container.tilingTreeNodeCasesOrDie().findDeepMoveInTargetRecursive(moveDirection.orientation) switch deepTarget { case .tilingContainer(let deepTarget): window.bind(to: deepTarget, adaptiveWeight: WEIGHT_AUTO, index: 0) case .window(let deepTarget): guard let parent = deepTarget.parent as? TilingContainer else { return .fail(io.err(bugPrompt())) } guard let deepTargetIndex = deepTarget.ownIndex else { return .fail(io.err(bugPrompt())) } window.bind(to: parent, adaptiveWeight: WEIGHT_AUTO, index: deepTargetIndex + 1) } return .succ}
extension TilingTreeNodeCases { @MainActor fileprivate func findDeepMoveInTargetRecursive(_ orientation: Orientation) -> TilingTreeNodeCases { switch self { case .window: self case .tilingContainer(let container) where container.orientation == orientation: .tilingContainer(container) case .tilingContainer(let container): container.mostRecentChild.orDie("Empty containers must be detached during normalization") .tilingTreeNodeCasesOrDie() .findDeepMoveInTargetRecursive(orientation) } }}
func shouldFailBecauseFullscreen_nonCancellable( window: Window, failIfFullscreen: Bool, failIfMacosNativeFullscreen: Bool,) async -> Bool { if failIfFullscreen && window.isFullscreen { return true } if failIfMacosNativeFullscreen { if true == (try? await window.isMacosFullscreen(.nonCancellable)) { return true } } return false}