Skip to content

Commit

Permalink
1/n Implement FrozenWorld and closedWindowsCache
Browse files Browse the repository at this point in the history
The cache is used restore windows that were mistakenly garbage collected
because of lock screen

#445
  • Loading branch information
nikitabobko committed Nov 27, 2024
1 parent 4e07455 commit 21c68fb
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 10 deletions.
4 changes: 2 additions & 2 deletions Sources/AppBundle/normalizeLayoutReason.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ private func _normalizeLayoutReason(workspace: Workspace, windows: [Window]) {
case .standard:
if isMacosFullscreen {
window.layoutReason = .macos(prevParentKind: window.parent.kind)
window.bind(to: workspace.macOsNativeFullscreenWindowsContainer, adaptiveWeight: 1, index: INDEX_BIND_LAST)
window.bind(to: workspace.macOsNativeFullscreenWindowsContainer, adaptiveWeight: WEIGHT_DOESNT_MATTER, index: INDEX_BIND_LAST)
} else if isMacosMinimized {
window.layoutReason = .macos(prevParentKind: window.parent.kind)
window.bind(to: macosMinimizedWindowsContainer, adaptiveWeight: 1, index: INDEX_BIND_LAST)
} else if isMacosWindowOfHiddenApp {
window.layoutReason = .macos(prevParentKind: window.parent.kind)
window.bind(to: workspace.macOsNativeHiddenAppsWindowsContainer, adaptiveWeight: 1, index: INDEX_BIND_LAST)
window.bind(to: workspace.macOsNativeHiddenAppsWindowsContainer, adaptiveWeight: WEIGHT_DOESNT_MATTER, index: INDEX_BIND_LAST)
}
case .macos(let prevParentKind):
if !isMacosFullscreen && !isMacosMinimized && !isMacosWindowOfHiddenApp {
Expand Down
9 changes: 5 additions & 4 deletions Sources/AppBundle/tree/TreeNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,10 @@ class TreeNode: Equatable {
self.adaptiveWeight = switch relation {
case .tiling(let newParent):
newParent.children.sumOf { $0.getWeight(newParent.orientation) }.div(newParent.children.count) ?? 1
case .floatingWindow, .macosNativeFullscreenWindow: WEIGHT_FLOATING
case .rootTilingContainer, .macosNativeMinimizedWindow,
.shimContainerRelation, .macosPopupWindow, .macosNativeHiddenAppWindow: 1
case .floatingWindow, .macosNativeFullscreenWindow,
.rootTilingContainer, .macosNativeMinimizedWindow,
.shimContainerRelation, .macosPopupWindow, .macosNativeHiddenAppWindow:
WEIGHT_DOESNT_MATTER
}
} else {
self.adaptiveWeight = adaptiveWeight
Expand Down Expand Up @@ -135,7 +136,7 @@ struct TreeNodeUserDataKey<T> {
let key: String
}

private let WEIGHT_FLOATING = CGFloat(-2)
let WEIGHT_DOESNT_MATTER = CGFloat(-2)
/// Splits containers evenly if tiling.
///
/// Reset weight is bind to workspace (aka "floating windows")
Expand Down
146 changes: 146 additions & 0 deletions Sources/AppBundle/tree/frozen/FrozenTree.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import AppKit
import Common

struct FrozenMonitor {
let topLeftCorner: CGPoint
let visibleWorkspace: String

init(_ monitor: Monitor) {
topLeftCorner = monitor.rect.topLeftCorner
visibleWorkspace = monitor.activeWorkspace.name
}
}

struct FrozenWorkspace {
let name: String
let monitor: FrozenMonitor // todo drop this property, once monitor to workspace assignment migrates to TreeNode
let rootTilingNode: FrozenContainer
let floatingWindows: [FrozenWindow]
let macosUnconventionalWindows: [FrozenWindow]

init(_ workspace: Workspace) {
name = workspace.name
monitor = FrozenMonitor(workspace.workspaceMonitor)
rootTilingNode = FrozenContainer(workspace.rootTilingContainer)
floatingWindows = workspace.floatingWindows.map(FrozenWindow.init)
macosUnconventionalWindows =
workspace.macOsNativeHiddenAppsWindowsContainer.children.map { FrozenWindow($0 as! Window) } +
workspace.macOsNativeFullscreenWindowsContainer.children.map { FrozenWindow($0 as! Window) }
}
}

enum FrozenTreeNode {
case container(FrozenContainer)
case window(FrozenWindow)
}

struct FrozenContainer {
let children: [FrozenTreeNode]
let layout: Layout
let orientation: Orientation
let weight: CGFloat

init(_ container: TilingContainer) {
children = container.children.map {
switch $0.nodeCases {
case .window(let w): .window(FrozenWindow(w))
case .tilingContainer(let c): .container(FrozenContainer(c))
case .workspace,
.macosMinimizedWindowsContainer,
.macosHiddenAppsWindowsContainer,
.macosFullscreenWindowsContainer,
.macosPopupWindowsContainer:
illegalChildParentRelation(child: $0, parent: container)
}
}
layout = container.layout
orientation = container.orientation
weight = getWeightOrNil(container) ?? 1
}
}

struct FrozenWindow {
let id: UInt32
let weight: CGFloat

init(_ window: Window) {
id = window.windowId
weight = getWeightOrNil(window) ?? 1
}
}

func getWeightOrNil(_ node: TreeNode) -> CGFloat? {
((node.parent as? TilingContainer)?.orientation).map { node.getWeight($0) }
}

/// When you lock the screen, all accessibility API becomes unobservable (all attributes become empty, window id
/// becomes nil, etc.) which tricks AeroSpace into thinking that all windows were closed.
/// That's why every time a window dies AeroSpace caches the "entire world" (unless window is already presented in the cache)
/// so that once the screen is unlocked, AeroSpace could restore windows to where they were
private var closedWindowsCache = FrozenWorld(workspaces: [], monitors: [])

func cacheClosedWindowIfNeeded(window: Window) {
if closedWindowsCache.windowIds.contains(window.windowId) {
return // already cached
}
closedWindowsCache = FrozenWorld(
workspaces: Workspace.all.map { FrozenWorkspace($0) },
monitors: monitors.map(FrozenMonitor.init)
)
}

func restoreWindowsFromClosedWindowsCache() {
let monitors = monitors
let topLeftCornerToMonitor = monitors.grouped { $0.rect.topLeftCorner }

for frozenWorkspace in closedWindowsCache.workspaces {
let workspace = Workspace.get(byName: frozenWorkspace.name)
_ = topLeftCornerToMonitor[frozenWorkspace.monitor.topLeftCorner]?
.singleOrNil()?
.setActiveWorkspace(workspace)
for frozenWindow in frozenWorkspace.floatingWindows {
MacWindow.get(byId: frozenWindow.id)?.bindAsFloatingWindow(to: workspace)
}
for frozenWindow in frozenWorkspace.macosUnconventionalWindows { // Will get fixed by normalizations
MacWindow.get(byId: frozenWindow.id)?.bindAsFloatingWindow(to: workspace)
}
let orphans = workspace.rootTilingContainer.allLeafWindowsRecursive
workspace.rootTilingContainer.unbindFromParent()
restoreTreeRecursive(frozenContainer: frozenWorkspace.rootTilingNode, parent: workspace, index: INDEX_BIND_LAST)
for window in (orphans - workspace.rootTilingContainer.allLeafWindowsRecursive) {
window.relayoutWindow(on: workspace, forceTile: true)
}
}

for monitor in closedWindowsCache.monitors {
_ = topLeftCornerToMonitor[monitor.topLeftCorner]?
.singleOrNil()?
.setActiveWorkspace(Workspace.get(byName: monitor.visibleWorkspace))
}
}

private func restoreTreeRecursive(frozenContainer: FrozenContainer, parent: NonLeafTreeNodeObject, index: Int) {
let container = TilingContainer(
parent: parent,
adaptiveWeight: frozenContainer.weight,
frozenContainer.orientation,
frozenContainer.layout,
index: index
)

loop:
for (index, child) in frozenContainer.children.enumerated() {
switch child {
case .window(let w):
// Stop the loop if can't find the window, because otherwise all the subsequent windows will have incorrect index
guard let window = MacWindow.get(byId: w.id) else { break loop }
window.bind(to: container, adaptiveWeight: w.weight, index: index)
case .container(let c):
restoreTreeRecursive(frozenContainer: c, parent: container, index: index)
}
}
}

func resetClosedWindowsCache() {
closedWindowsCache = FrozenWorld(workspaces: [], monitors: [])
}
Empty file.
27 changes: 27 additions & 0 deletions Sources/AppBundle/tree/frozen/FrozenWorld.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
struct FrozenWorld {
let workspaces: [FrozenWorkspace]
let monitors: [FrozenMonitor]
let windowIds: Set<UInt32>

init(workspaces: [FrozenWorkspace], monitors: [FrozenMonitor]) {
self.workspaces = workspaces
self.monitors = monitors
self.windowIds = workspaces.flatMap { collectAllWindowIds(workspace: $0) }.toSet()
}
}

private func collectAllWindowIds(workspace: FrozenWorkspace) -> [UInt32] {
workspace.floatingWindows.map { $0.id } +
workspace.macosUnconventionalWindows.map { $0.id } +
collectAllWindowIdsRecursive(node: .container(workspace.rootTilingNode))
}

private func collectAllWindowIdsRecursive(node: FrozenTreeNode) -> [UInt32] {
switch node {
case .window(let w): [w.id]
case .container(let c):
c.children.reduce(into: [UInt32]()) { partialResult, elem in
partialResult += collectAllWindowIdsRecursive(node: elem)
}
}
}
11 changes: 11 additions & 0 deletions Sources/AppBundle/util/SetEx.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
public extension Set {
internal func toArray() -> [Element] { Array(self) }

@inlinable static func += (lhs: inout Set<Element>, rhs: any Sequence<Element>) {
lhs.formUnion(rhs)
}

@inlinable static func -= (lhs: inout Set<Element>, rhs: any Sequence<Element>) {
lhs.subtract(rhs)
}
}
4 changes: 0 additions & 4 deletions Sources/AppBundle/util/appBundleUtil.swift
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,6 @@ extension CGPoint: Swift.Hashable { // todo migrate to self written Point
}
}

extension Set {
func toArray() -> [Element] { Array(self) }
}

#if DEBUG
let isDebug = true
#else
Expand Down

0 comments on commit 21c68fb

Please sign in to comment.