aerospace fork
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217import AppKitimport Common
struct FocusCommand: Command { let args: FocusCmdArgs /*conforms*/ let shouldResetClosedWindowsCache = false
func run(_ env: CmdEnv, _ io: CmdIo) async -> BinaryExitCode { guard let target = args.resolveTargetOrReportError(env, io) else { return .fail } if let window = target.windowOrNil, await shouldFailBecauseFullscreen_nonCancellable( window: window, failIfFullscreen: args.failIfFullscreen, failIfMacosNativeFullscreen: args.failIfMacosNativeFullscreen, ) { return .fail } // todo bug: floating windows break mru let floatingWindows = args.floatingAsTiling ? await 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] .findLeafWindowRecursive(snappedTo: direction.opposite) else { return .fail(io.err(bugPrompt())) } return .from(bool: windowToFocus.focusWindow()) } else { return hitWorkspaceBoundaries(target, io, args, direction) } case .windowId(let windowId): if let windowToFocus = Window.get(byId: windowId) { return .from(bool: windowToFocus.focusWindow()) } else { return .fail(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 .from(bool: windowToFocus.focusWindow()) } else { return .fail(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 .fail } var targetIndex = switch nextPrev { case .dfsNext: currentIndex + 1 case .dfsPrev: currentIndex - 1 } if !(0 ..< windows.count).contains(targetIndex) { switch args.boundariesAction { case .stop: return .succ case .fail: return .fail case .wrapAroundTheWorkspace: targetIndex = (targetIndex + windows.count) % windows.count case .wrapAroundAllMonitors: return .fail(io.err(bugPrompt("Must be discarded by args parser"))) } } return .from(bool: windows[targetIndex].focusWindow()) } }}
@MainActor private func hitWorkspaceBoundaries( _ target: LiveFocus, _ io: CmdIo, _ args: FocusCmdArgs, _ direction: CardinalDirection,) -> BinaryExitCode { switch args.boundaries { case .workspace: return switch args.boundariesAction { case .stop: .succ case .fail: .fail case .wrapAroundTheWorkspace: wrapAroundTheWorkspace(target, io, direction) case .wrapAroundAllMonitors: .fail(io.err("Must be discarded by args parser")) } case .allMonitorsOuterFrame: let currentMonitor = target.workspace.workspaceMonitor guard let (monitors, index) = currentMonitor.findRelativeMonitor(inDirection: direction) else { return .fail(io.err(bugPrompt("Should never happen. Can't find the current monitor"))) }
if let targetMonitor = monitors.getOrNil(atIndex: index) { return .from(bool: targetMonitor.activeWorkspace.focusWorkspace()) } else { guard let wrapped = monitors.get(wrappingIndex: index) else { return .fail(io.err(bugPrompt("\(index) \(monitors)"))) } return hitAllMonitorsOuterFrameBoundaries(target, io, args, direction, wrapped) } }}
@MainActor private func hitAllMonitorsOuterFrameBoundaries( _ target: LiveFocus, _ io: CmdIo, _ args: FocusCmdArgs, _ direction: CardinalDirection, _ wrappedMonitor: MonitorInfo,) -> BinaryExitCode { switch args.boundariesAction { case .stop: return .succ case .fail: return .fail case .wrapAroundTheWorkspace: return wrapAroundTheWorkspace(target, io, direction) case .wrapAroundAllMonitors: wrappedMonitor.activeWorkspace.findLeafWindowRecursive(snappedTo: direction.opposite)?.markAsMostRecentChild() return .from(bool: wrappedMonitor.activeWorkspace.focusWorkspace()) }}
@MainActor private func wrapAroundTheWorkspace(_ target: LiveFocus, _ io: CmdIo, _ direction: CardinalDirection) -> BinaryExitCode { guard let windowToFocus = target.workspace.findLeafWindowRecursive(snappedTo: direction.opposite) else { return .fail(io.err(noWindowIsFocused)) } return .from(bool: windowToFocus.focusWindow())}
@MainActor private func makeFloatingWindowsSeenAsTiling(workspace: Workspace) async -> [FloatingWindowData] { let mruBefore = workspace.mostRecentWindowRecursive defer { mruBefore?.markAsMostRecentChild() } var _floatingWindows: [FloatingWindowData] = [] for window in workspace.floatingWindows { // 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 = try? await window.getCenter(.nonCancellable) else { continue }
let tilingParent: TilingContainer let index: Int if let target = center.coerce(in: workspace.workspaceMonitor.visibleRectPaddedByOuterGaps)? .findWindowRecursively(in: workspace.rootTilingContainer, virtual: true, fullscreenCoversAll: false) { guard let targetCenter = try? await target.getCenter(.nonCancellable) else { continue } guard let _tilingParent = target.parent as? TilingContainer else { continue } tilingParent = _tilingParent index = switch tilingParent.layout { case .tiles, .masterStack: center.getProjection(tilingParent.orientation) >= targetCenter.getProjection(tilingParent.orientation) ? target.ownIndex.orDie() + 1 : target.ownIndex.orDie() case .accordion: center.getProjection(tilingParent.orientation) >= targetCenter.getProjection(tilingParent.orientation) ? tilingParent.children.count : 0 } } else { index = 0 tilingParent = workspace.rootTilingContainer }
let data = window.unbindFromParent() let floatingWindowData = FloatingWindowData( window: window, center: center, tilingParent: tilingParent, adaptiveWeight: data.adaptiveWeight, index: index, ) _floatingWindows.append(floatingWindowData) } let floatingWindows: [FloatingWindowData] = _floatingWindows.sortedBy { $0.center.getProjection($0.tilingParent.orientation) }.reversed()
for floating in floatingWindows { // Make floating windows be seen as tiling floating.window.bind(to: floating.tilingParent, 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.floatingWindowsContainer, adaptiveWeight: floating.adaptiveWeight, index: INDEX_BIND_LAST) }}
private struct FloatingWindowData { let window: Window let center: CGPoint
let tilingParent: TilingContainer let adaptiveWeight: CGFloat let index: Int}
extension TreeNode { @MainActor func findLeafWindowRecursive(snappedTo direction: CardinalDirection) -> Window? { switch nodeCases { case .workspace(let workspace): return workspace.rootTilingContainer.findLeafWindowRecursive(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)? .findLeafWindowRecursive(snappedTo: direction) } else { return mostRecentChild?.findLeafWindowRecursive(snappedTo: direction) } case .macosMinimizedWindowsContainer, .macosFullscreenWindowsContainer, .macosPopupWindowsContainer, .macosHiddenAppsWindowsContainer, .floatingWindowsContainer: die("Impossible") } }}