Skip to content

Commit

Permalink
6/n Use special "fullscreen windows" container for macOS native fulls…
Browse files Browse the repository at this point in the history
…creen windows

nikitabobko#18

Motivation: More typesafe and accurate tree structure
  • Loading branch information
nikitabobko committed Mar 9, 2024
1 parent 8a8b482 commit 7b10b89
Show file tree
Hide file tree
Showing 17 changed files with 136 additions and 65 deletions.
8 changes: 8 additions & 0 deletions AeroSpace.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand All @@ -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 */; };
Expand All @@ -122,6 +124,7 @@
00ECDFE176777828D560A737 /* TilingContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TilingContainer.swift; sourceTree = "<group>"; };
07ADCBFC2E29A2AD5C1B7984 /* parseCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = parseCommand.swift; sourceTree = "<group>"; };
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 = "<group>"; };
0A9DFF8980BB3F90A3793BE9 /* parseOnWindowDetected.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = parseOnWindowDetected.swift; sourceTree = "<group>"; };
0AEE5470AF418906B180A593 /* mouse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = mouse.swift; sourceTree = "<group>"; };
0D36D0F8EEB30A7BABC42343 /* LazySequenceProtocolEx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazySequenceProtocolEx.swift; sourceTree = "<group>"; };
Expand All @@ -135,6 +138,7 @@
244190F3707EF2BB3CC7FA5A /* MonitorDescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonitorDescription.swift; sourceTree = "<group>"; };
25AC44D0E9450867215FCBEC /* MoveNodeToWorkspaceCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoveNodeToWorkspaceCommand.swift; sourceTree = "<group>"; };
2D96FF3675C63A83DC8B8969 /* SplitCommandTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitCommandTest.swift; sourceTree = "<group>"; };
34547605D044E8182B71B0E7 /* normalizeLayoutReason.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = normalizeLayoutReason.swift; sourceTree = "<group>"; };
3A262B442A94C1964509B691 /* TreeNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TreeNode.swift; sourceTree = "<group>"; };
3A6EF465EF4129BCB10FE247 /* ExecCommandTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExecCommandTest.swift; sourceTree = "<group>"; };
3C2E5977331398421A4FC168 /* GlobalObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalObserver.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down
2 changes: 1 addition & 1 deletion docs/goodness.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
6 changes: 3 additions & 3 deletions src/command/FocusCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down Expand Up @@ -162,7 +162,7 @@ private extension TreeNode {
} else {
return mostRecentChild?.findFocusTargetRecursive(snappedTo: direction)
}
case .macosInvisibleWindowsContainer:
case .macosInvisibleWindowsContainer, .macosFullscreenWindowsContainer:
error("Impossible")
}
}
Expand Down
5 changes: 4 additions & 1 deletion src/command/LayoutCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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
}
Expand Down
21 changes: 14 additions & 7 deletions src/command/MoveCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand All @@ -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")
}
Expand All @@ -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()
Expand All @@ -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
Expand All @@ -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)
}
}
Expand Down
5 changes: 4 additions & 1 deletion src/command/SplitCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/mouse/moveWithMouse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down Expand Up @@ -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):
Expand Down
4 changes: 2 additions & 2 deletions src/mouse/resizeWithMouse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
41 changes: 41 additions & 0 deletions src/normalizeLayoutReason.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}
}
34 changes: 0 additions & 34 deletions src/refresh.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
8 changes: 8 additions & 0 deletions src/tree/MacosFullscreenWindowsContainer.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
Loading

0 comments on commit 7b10b89

Please sign in to comment.