Skip to content

Commit

Permalink
Rewrite a bunch of things around workspaces follow up
Browse files Browse the repository at this point in the history
  • Loading branch information
nikitabobko committed Oct 15, 2023
1 parent c9208a5 commit ef4347e
Show file tree
Hide file tree
Showing 23 changed files with 97 additions and 92 deletions.
12 changes: 4 additions & 8 deletions AeroSpace.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
059308740527CC10D1A79DDD /* AeroApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 996BDAB685567E430DF7D962 /* AeroApp.swift */; };
0A90EEEAC020DD3A56736014 /* FocusCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = E761155C73F06E2CF5E292A4 /* FocusCommand.swift */; };
0A9DAD847B5340FBE3D9CF88 /* FlattenWorkspaceTreeCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3C5B6719EF0BC14D7CF868C /* FlattenWorkspaceTreeCommand.swift */; };
115F5CA4BEB80B645E66D198 /* NSScreenEx.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF3BB3DD434C75536217CB88 /* NSScreenEx.swift */; };
1311398A83B998908773C54D /* FocusCommandTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0EAADE8D2FB5D05FA5456B0 /* FocusCommandTest.swift */; };
1C46EBB55D401C0D1AFD50F0 /* CollectionEx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51CE37C1B8D858C81A396F40 /* CollectionEx.swift */; };
1D408CDF1A489E527327EB15 /* CompositeCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = D82CD9670B7A6050073E0F76 /* CompositeCommand.swift */; };
Expand All @@ -36,13 +35,13 @@
70A82A4A9DFC89286C4F7696 /* TilingContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00ECDFE176777828D560A737 /* TilingContainer.swift */; };
77FA83225024151CD556E1ED /* CloseAllWindowsButCurrentCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99853C505D93E41F6531C324 /* CloseAllWindowsButCurrentCommand.swift */; };
78EE0CEF814ABDBA67941B84 /* Rect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28B788A95DD3C267878E05B5 /* Rect.swift */; };
7ED8C2A66DD6F903796F090C /* TestApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2431BEAFFF1AC12D3001317A /* TestApp.swift */; };
852F88894A3B9FC385563665 /* HotKey in Frameworks */ = {isa = PBXBuildFile; productRef = 42BC1E757EF69233C2262FF4 /* HotKey */; };
89064BDDB5E9D17BEDE52E8C /* MoveContainerToWorkspaceCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD62043D7F5113A8BA635FDF /* MoveContainerToWorkspaceCommand.swift */; };
920FDF8498DCCB62149D1719 /* Monitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6507EBAA795220FD0C05384 /* Monitor.swift */; };
93D44EA41776738B4758C28D /* util.swift in Sources */ = {isa = PBXBuildFile; fileRef = 976540EBEACF846D598CD6E1 /* util.swift */; };
991943D50DF9EDBF321A66F1 /* SelectorComparator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1316FFED5D1044CB693EA45 /* SelectorComparator.swift */; };
9A138A729245BD2723148583 /* focused.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61FE8B343B068F0FFFC2373 /* focused.swift */; };
9C6B3FEFCC73DA17B2D6246F /* TestApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B239D50676DD5C78936C0B /* TestApp.swift */; };
9D34BD7DE311254BF52F5EA2 /* testUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77C03164B8BFD1E59779C6E /* testUtil.swift */; };
A0765C31043BCFB0420BF1C9 /* parseConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67DBAF4ECF8A0B931FC34EAD /* parseConfig.swift */; };
A2CBF9674964F9083BB198D2 /* ArrayEx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 883D7F7F87FBE7D0BDE4E87F /* ArrayEx.swift */; };
Expand Down Expand Up @@ -92,8 +91,8 @@
1A2B673C67B00DBFCC27FFE7 /* LayoutCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutCommand.swift; sourceTree = "<group>"; };
1C0D40CBD65704BA9595C2FA /* keysMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = keysMap.swift; sourceTree = "<group>"; };
1E81623E8954701269A22322 /* AeroSpaceApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AeroSpaceApp.swift; sourceTree = "<group>"; };
2431BEAFFF1AC12D3001317A /* TestApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestApp.swift; sourceTree = "<group>"; };
28B788A95DD3C267878E05B5 /* Rect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Rect.swift; sourceTree = "<group>"; };
34B239D50676DD5C78936C0B /* TestApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestApp.swift; sourceTree = "<group>"; };
381743262EB4D8AB365235C8 /* Writer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Writer.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>"; };
Expand Down Expand Up @@ -133,7 +132,6 @@
A9EDFD4A9F45182CA6E0BD7B /* OptionalEx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionalEx.swift; sourceTree = "<group>"; };
AAE5DCAEC5EE619CE33859E7 /* SequenceEx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SequenceEx.swift; sourceTree = "<group>"; };
AD1645D9939F3F896EF21393 /* TreeNodeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TreeNodeTest.swift; sourceTree = "<group>"; };
AF3BB3DD434C75536217CB88 /* NSScreenEx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSScreenEx.swift; sourceTree = "<group>"; };
B1316FFED5D1044CB693EA45 /* SelectorComparator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectorComparator.swift; sourceTree = "<group>"; };
B7DB782C527ABE0CF31740EB /* MacApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacApp.swift; sourceTree = "<group>"; };
BEF353340822CD20E9DAB3EC /* AeroSpace.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AeroSpace.entitlements; sourceTree = "<group>"; };
Expand Down Expand Up @@ -198,6 +196,7 @@
DAC5AF4019D1F5DAB5AF2B56 /* layoutRecursive.swift */,
B7DB782C527ABE0CF31740EB /* MacApp.swift */,
9245C6FACF389672EA71173B /* MacWindow.swift */,
34B239D50676DD5C78936C0B /* TestApp.swift */,
C848D6E57FDF22AAF0FB45E6 /* TilingContainer.swift */,
3A262B442A94C1964509B691 /* TreeNode.swift */,
9F6B8A501483ACBB62560101 /* TreeNodeEx.swift */,
Expand Down Expand Up @@ -303,7 +302,6 @@
BAC4E4D413715557A1BBDCC8 /* tree */ = {
isa = PBXGroup;
children = (
2431BEAFFF1AC12D3001317A /* TestApp.swift */,
6F1905935B0C61590A96EFEF /* TestWindow.swift */,
00ECDFE176777828D560A737 /* TilingContainer.swift */,
AD1645D9939F3F896EF21393 /* TreeNodeTest.swift */,
Expand All @@ -323,7 +321,6 @@
54B4C778D2594EB23C295741 /* Copyable.swift */,
F6507EBAA795220FD0C05384 /* Monitor.swift */,
954A434EE57D76F5A9D4140D /* MruStack.swift */,
AF3BB3DD434C75536217CB88 /* NSScreenEx.swift */,
A9EDFD4A9F45182CA6E0BD7B /* OptionalEx.swift */,
28B788A95DD3C267878E05B5 /* Rect.swift */,
B1316FFED5D1044CB693EA45 /* SelectorComparator.swift */,
Expand Down Expand Up @@ -433,7 +430,6 @@
1311398A83B998908773C54D /* FocusCommandTest.swift in Sources */,
A55F31B0CC357B37108B8F54 /* MoveContainerToWorkspaceCommandTest.swift in Sources */,
B1E527CF4941A4E9B8D9C3D0 /* MoveThroughCommandTest.swift in Sources */,
7ED8C2A66DD6F903796F090C /* TestApp.swift in Sources */,
E22ACB36C90695FBAC78226E /* TestWindow.swift in Sources */,
70A82A4A9DFC89286C4F7696 /* TilingContainer.swift in Sources */,
BB3C35BE40958AE6E7B4A9FD /* TreeNodeTest.swift in Sources */,
Expand Down Expand Up @@ -470,13 +466,13 @@
89064BDDB5E9D17BEDE52E8C /* MoveContainerToWorkspaceCommand.swift in Sources */,
EDFDE707B4DC5E500B1709B1 /* MoveThroughCommand.swift in Sources */,
635733FDDF37E44364372B74 /* MruStack.swift in Sources */,
115F5CA4BEB80B645E66D198 /* NSScreenEx.swift in Sources */,
C0A88261ECF505FC5648FC0A /* OptionalEx.swift in Sources */,
78EE0CEF814ABDBA67941B84 /* Rect.swift in Sources */,
FC35D6D0A678CC802972C6FE /* ReloadConfigCommand.swift in Sources */,
D24D02B1FD87424B908986AF /* ResizeCommand.swift in Sources */,
991943D50DF9EDBF321A66F1 /* SelectorComparator.swift in Sources */,
AE76A183D0454E4C8ADCE380 /* SequenceEx.swift in Sources */,
9C6B3FEFCC73DA17B2D6246F /* TestApp.swift in Sources */,
E5682579AEC6B84CF6FCE90D /* TilingContainer.swift in Sources */,
56E72B24303F5F337B31B776 /* TrayMenuModel.swift in Sources */,
F892B5DCB4F731B3E173FF4C /* TreeNode.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion src/AeroSpaceApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ struct AeroSpaceApp: App {
}
Button("Quit \(Bundle.appName)") {
for app in apps { // Make all windows fullscreen before Quit
for window in app.macApp?.windows ?? [] {
for window in app.windows {
let rect = window.workspace.monitor.visibleRect
window.setSize(CGSize(width: rect.width, height: rect.height))
window.setTopLeftCorner(rect.topLeftCorner)
Expand Down
4 changes: 2 additions & 2 deletions src/TrayMenuModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ class TrayMenuModel: ObservableObject {
}

func updateTrayText() {
TrayMenuModel.shared.trayText = NSScreen.screens
TrayMenuModel.shared.trayText = monitors
.sorted(using: [SelectorComparator(selector: \.rect.minX), SelectorComparator(selector: \.rect.minY)])
.map { $0.monitor.getActiveWorkspace().name }
.map(\.activeWorkspace.name)
.joined(separator: "")
}
4 changes: 2 additions & 2 deletions src/command/WorkspaceCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ struct WorkspaceCommand : Command {
// The switching itself will be done by refreshWorkspaces and layoutWorkspaces later in refresh
} else { // switch to empty workspace
precondition(workspace.isEffectivelyEmpty)
// It's the only place in the app where I allow myself to use NSScreen.main.
// This function isn't invoked from callbacks that's why .main should be fine
// It's fine to call Unsafe from here because commands are not invoked from callbacks,
// the callbacks are triggered by user
if let focusedMonitor = focusedMonitorOrNilIfDesktop ?? focusedMonitorUnsafe {
focusedMonitor.setActiveWorkspace(workspace)
}
Expand Down
2 changes: 1 addition & 1 deletion src/config/Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ struct Config {
var accordionPadding: Int

let modes: [String: Mode]
var workspaceNames: [String]
var preservedWorkspaceNames: [String]
}

enum FocusWrapping: String { // todo think about mental model
Expand Down
2 changes: 1 addition & 1 deletion src/config/parseConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ func parseConfig(_ rawToml: String) -> ParsedTomlWriter<Config> {
accordionPadding: value12 ?? defaultConfig.accordionPadding,

modes: modesOrDefault,
workspaceNames: modesOrDefault.values.lazy
preservedWorkspaceNames: modesOrDefault.values.lazy
.flatMap { (mode: Mode) -> [HotkeyBinding] in mode.bindings }
.map { (binding: HotkeyBinding) -> Command in binding.command }
.map { (command: Command) -> Command in (command as? CompositeCommand)?.subCommands.singleOrNil() ?? command }
Expand Down
27 changes: 3 additions & 24 deletions src/focused.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,36 +24,15 @@ var focusedApp: AeroApp? {
}
}

/// Motivation:
/// 1. NSScreen.main is a misleading name.
/// 2. NSScreen.main doesn't work correctly from NSWorkspace.didActivateApplicationNotification &
/// kAXFocusedWindowChangedNotification callbacks.
///
/// Returns `nil` if the desktop is selected (which is when the app is active but doesn't show any window)
var focusedMonitorOrNilIfDesktop: Monitor? {
let window = focusedWindow as! MacWindow? // todo
return window?.getCenter()?.monitorApproximation ?? monitors.singleOrNil()
//NSWorkspace.activeApp?.macApp?.axFocusedWindow?
// .get(Ax.topLeftCornerAttr)?.monitorApproximation
// ?? NSScreen.screens.singleOrNil()
}

/// It's unsafe because NSScreen.main doesn't work correctly from NSWorkspace.didActivateApplicationNotification &
/// kAXFocusedWindowChangedNotification callbacks.
var focusedMonitorUnsafe: Monitor? {
NSScreen.main?.monitor
}

var monitors: [Monitor] { NSScreen.screens.map(\.monitor) }

var focusedWindow: Window? { focusedApp?.focusedWindow }

var focusedWindowOrEffectivelyFocused: Window? {
focusedWindow ?? Workspace.focused.mostRecentWindow ?? Workspace.focused.anyLeafWindowRecursive
}

private var _focusedWorkspaceName: String = focusedMonitorUnsafe?.getActiveWorkspace().name
?? mainMonitor.getActiveWorkspace().name
// It's fine to call this Unsafe during startup
private var _focusedWorkspaceName: String = focusedMonitorUnsafe?.activeWorkspace.name
?? mainMonitor.activeWorkspace.name
var focusedWorkspaceName: String {
get { _focusedWorkspaceName }
set {
Expand Down
6 changes: 3 additions & 3 deletions src/refresh.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ private func refreshWorkspaces() {
//debug("refreshWorkspaces: not empty")
let focusedWorkspace: Workspace
if focusedWindow.isFloating && !focusedWindow.isHiddenViaEmulation { // todo maybe drop once move with mouse is supported
focusedWorkspace = focusedWindow.getCenter()?.monitorApproximation.getActiveWorkspace()
focusedWorkspace = focusedWindow.getCenter()?.monitorApproximation.activeWorkspace
?? focusedWindow.workspace
focusedWindow.bindAsFloatingWindowTo(workspace: focusedWorkspace)
} else {
Expand Down Expand Up @@ -67,7 +67,7 @@ private func normalizeContainers() {

private func layoutWindows(firstStart: Bool) {
for monitor in monitors {
let workspace = monitor.getActiveWorkspace()
let workspace = monitor.activeWorkspace
if workspace.isEffectivelyEmpty { continue }
let rect = monitor.visibleRect
workspace.layoutRecursive(rect.topLeftCorner, width: rect.width, height: rect.height, firstStart: firstStart)
Expand All @@ -76,6 +76,6 @@ private func layoutWindows(firstStart: Bool) {

private func detectNewWindowsAndAttachThemToWorkspaces() {
for app in apps {
let _ = app.macApp?.windows
let _ = app.windows
}
}
1 change: 1 addition & 0 deletions src/tree/AeroApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ class AeroApp: Hashable {
}

var focusedWindow: Window? { error("Not implemented") }
var windows: [Window] { error("Not implemented") }
}
2 changes: 1 addition & 1 deletion src/tree/MacApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ final class MacApp: AeroApp {
axApp.get(Ax.focusedWindowAttr)
}

var windows: [MacWindow] {
override var windows: [Window] {
(axApp.get(Ax.windowsAttr) ?? []).compactMap({ MacWindow.get(app: self, axWindow: $0) })
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/tree/MacWindow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ final class MacWindow: Window, CustomStringConvertible {
if let existing = allWindowsMap[id] {
return existing
} else {
let workspace: Workspace = (axWindow.center?.monitorApproximation ?? mainMonitor).getActiveWorkspace()
let workspace: Workspace = (axWindow.center?.monitorApproximation ?? mainMonitor).activeWorkspace
let parent: NonLeafTreeNode
let index: Int
if shouldFloat(axWindow) {
Expand Down
10 changes: 4 additions & 6 deletions test/tree/TestApp.swift → src/tree/TestApp.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
@testable import AeroSpace_Debug

final class TestApp: AeroApp {
static var shared = TestApp(id: 0)

private override init(id: Int32) {
super.init(id: id)
}

var _windows: [TestWindow] = []
var windows: [TestWindow] {
var _windows: [Window] = []
override var windows: [Window] {
get { _windows }
set {
if let focusedWindow {
Expand All @@ -18,8 +16,8 @@ final class TestApp: AeroApp {
}
}

private var _focusedWindow: TestWindow? = nil
override var focusedWindow: TestWindow? {
private var _focusedWindow: Window? = nil
override var focusedWindow: Window? {
get { _focusedWindow }
set {
if let window = newValue {
Expand Down
21 changes: 16 additions & 5 deletions src/tree/Workspace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ var allMonitorsRectsUnion: Rect {
monitors.map(\.rect).union()
}

class Workspace: TreeNode, NonLeafTreeNode, Hashable, Identifiable {
class Workspace: TreeNode, NonLeafTreeNode, Hashable, Identifiable, CustomStringConvertible {
let name: String
var id: String { name } // satisfy Identifiable
/// This variable must be interpreted only when the workspace is invisible
Expand Down Expand Up @@ -59,8 +59,17 @@ class Workspace: TreeNode, NonLeafTreeNode, Hashable, Identifiable {
error("It's not possible to change weight of Workspace")
}

var description: String {
let description = [
("name", name),
("isVisible", String(isVisible)),
("isEffectivelyEmpty", String(isEffectivelyEmpty)),
].map { "\($0.0): '\(String(describing: $0.1))'" }.joined(separator: ", ")
return "Workspace(\(description))"
}

static func garbageCollectUnusedWorkspaces() {
let preservedNames = config.workspaceNames.toSet()
let preservedNames = config.preservedWorkspaceNames.toSet()
for name in preservedNames {
_ = get(byName: name) // Make sure that all preserved workspaces are "cached"
}
Expand Down Expand Up @@ -105,17 +114,19 @@ extension Workspace {
}

extension Monitor {
func getActiveWorkspace() -> Workspace {
var activeWorkspace: Workspace {
if let existing = screenPointToVisibleWorkspace[rect.topLeftCorner] {
return existing
}
// What if monitor configuration changed? (frame.origin is changed)
rearrangeWorkspacesOnMonitors()
// Normally, recursion should happen only once more because we must take the value from the cache
// (Unless, monitor configuration data race happens)
return getActiveWorkspace()
return self.activeWorkspace
}

// It can't be converted to property because stupid Swift requires Monitor to be `var`
// if you want to assign to calculated property
func setActiveWorkspace(_ workspace: Workspace) {
rect.topLeftCorner.setActiveWorkspace(workspace)
}
Expand All @@ -140,7 +151,7 @@ private extension CGPoint {
private func rearrangeWorkspacesOnMonitors() {
var oldVisibleScreens: Set<CGPoint> = screenPointToVisibleWorkspace.keys.toSet()

let newScreens = NSScreen.screens.map(\.rect.topLeftCorner)
let newScreens = monitors.map(\.rect.topLeftCorner)
var newScreenToOldScreenMapping: [CGPoint:CGPoint] = [:]
var preservedOldScreens: [CGPoint] = []
for newScreen in newScreens {
Expand Down
Loading

0 comments on commit ef4347e

Please sign in to comment.