Apps randomly move workspaces and which workspace is show randomly changes #1939
Unanswered
BenRemer
asked this question in
potential-bugs
Replies: 1 comment 1 reply
-
|
Thanks for the report, unfortunately, it doesn't ring a bell, I don't know what could be causing these symptoms. You can try to debug the stealing focus problem with the following logging patch: Detailsdiff --git a/Sources/AppBundle/GlobalObserver.swift b/Sources/AppBundle/GlobalObserver.swift
index 2cbd0e9c..7881f2d4 100644
--- a/Sources/AppBundle/GlobalObserver.swift
+++ b/Sources/AppBundle/GlobalObserver.swift
@@ -32,7 +32,7 @@ enum GlobalObserver {
MacApp.allAppsMap.values.count(where: { $0.nsApp.isHidden }) == 1
{
// Force focus
- _ = w.focusWindow()
+ _ = w.focusWindow(reason: .automaticallyUnhideMacosHiddenApps)
w.nativeFocus()
}
for app in MacApp.allAppsMap.values {
@@ -66,7 +66,7 @@ enum GlobalObserver {
// Detect clicks on desktop of different monitors
case clickedMonitor.activeWorkspace != focus.workspace:
_ = try await runLightSession(.globalObserverLeftMouseUp, token) {
- clickedMonitor.activeWorkspace.focusWorkspace()
+ clickedMonitor.activeWorkspace.focusWorkspace(reason: .leftMouseUp)
}
// Detect close button clicks for unfocused windows. Yes, kAXUIElementDestroyedNotification is that unreliable
// And trigger new window detection that could be delayed due to mouseDown event
diff --git a/Sources/AppBundle/command/impl/FocusBackAndForthCommand.swift b/Sources/AppBundle/command/impl/FocusBackAndForthCommand.swift
index 78e9ab99..4543b41c 100644
--- a/Sources/AppBundle/command/impl/FocusBackAndForthCommand.swift
+++ b/Sources/AppBundle/command/impl/FocusBackAndForthCommand.swift
@@ -7,7 +7,7 @@ struct FocusBackAndForthCommand: Command {
func run(_ env: CmdEnv, _ io: CmdIo) -> Bool {
if let prevFocus {
- return setFocus(to: prevFocus)
+ return setFocus(to: prevFocus, reason: .focusBackAndForth)
} else {
return io.err("Prev window has been closed")
}
diff --git a/Sources/AppBundle/command/impl/FocusCommand.swift b/Sources/AppBundle/command/impl/FocusCommand.swift
index 6d8667b7..28181279 100644
--- a/Sources/AppBundle/command/impl/FocusCommand.swift
+++ b/Sources/AppBundle/command/impl/FocusCommand.swift
@@ -21,19 +21,19 @@ struct FocusCommand: Command {
if let (parent, ownIndex) = window?.closestParent(hasChildrenInDirection: direction, withLayout: nil) {
guard let windowToFocus = parent.children[ownIndex + direction.focusOffset]
.findLeafWindowRecursive(snappedTo: direction.opposite) else { return false }
- return windowToFocus.focusWindow()
+ return windowToFocus.focusWindow(reason: .focusCommand)
} else {
return hitWorkspaceBoundaries(target, io, args, direction)
}
case .windowId(let windowId):
if let windowToFocus = Window.get(byId: windowId) {
- return windowToFocus.focusWindow()
+ return windowToFocus.focusWindow(reason: .focusCommand)
} else {
return 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 windowToFocus.focusWindow()
+ return windowToFocus.focusWindow(reason: .focusCommand)
} else {
return io.err("Can't find window with DFS index \(dfsIndex)")
}
@@ -54,7 +54,7 @@ struct FocusCommand: Command {
case .wrapAroundAllMonitors: return dieT("Must be discarded by args parser")
}
}
- return windows[targetIndex].focusWindow()
+ return windows[targetIndex].focusWindow(reason: .focusCommand)
}
}
}
@@ -80,7 +80,7 @@ struct FocusCommand: Command {
}
if let targetMonitor = monitors.getOrNil(atIndex: index) {
- return targetMonitor.activeWorkspace.focusWorkspace()
+ return targetMonitor.activeWorkspace.focusWorkspace(reason: .focusCommand)
} else {
guard let wrapped = monitors.get(wrappingIndex: index) else { return false }
return hitAllMonitorsOuterFrameBoundaries(target, io, args, direction, wrapped)
@@ -104,7 +104,7 @@ struct FocusCommand: Command {
return wrapAroundTheWorkspace(target, io, direction)
case .wrapAroundAllMonitors:
wrappedMonitor.activeWorkspace.findLeafWindowRecursive(snappedTo: direction.opposite)?.markAsMostRecentChild()
- return wrappedMonitor.activeWorkspace.focusWorkspace()
+ return wrappedMonitor.activeWorkspace.focusWorkspace(reason: .focusCommand)
}
}
@@ -112,7 +112,7 @@ struct FocusCommand: Command {
guard let windowToFocus = target.workspace.findLeafWindowRecursive(snappedTo: direction.opposite) else {
return io.err(noWindowIsFocused)
}
- return windowToFocus.focusWindow()
+ return windowToFocus.focusWindow(reason: .focusCommand)
}
@MainActor private func makeFloatingWindowsSeenAsTiling(workspace: Workspace) async throws -> [FloatingWindowData] {
diff --git a/Sources/AppBundle/command/impl/FocusMonitorCommand.swift b/Sources/AppBundle/command/impl/FocusMonitorCommand.swift
index 7b2e5b44..5741558f 100644
--- a/Sources/AppBundle/command/impl/FocusMonitorCommand.swift
+++ b/Sources/AppBundle/command/impl/FocusMonitorCommand.swift
@@ -8,7 +8,7 @@ struct FocusMonitorCommand: Command {
func run(_ env: CmdEnv, _ io: CmdIo) -> Bool {
guard let target = args.resolveTargetOrReportError(env, io) else { return false }
return switch args.target.val.resolve(target.workspace.workspaceMonitor, wrapAround: args.wrapAround) {
- case .success(let targetMonitor): targetMonitor.activeWorkspace.focusWorkspace()
+ case .success(let targetMonitor): targetMonitor.activeWorkspace.focusWorkspace(reason: .focusMonitor)
case .failure(let msg): io.err(msg)
}
}
diff --git a/Sources/AppBundle/command/impl/MoveNodeToWorkspaceCommand.swift b/Sources/AppBundle/command/impl/MoveNodeToWorkspaceCommand.swift
index 5b2663fe..23f8c3f1 100644
--- a/Sources/AppBundle/command/impl/MoveNodeToWorkspaceCommand.swift
+++ b/Sources/AppBundle/command/impl/MoveNodeToWorkspaceCommand.swift
@@ -38,5 +38,5 @@ func moveWindowToWorkspace(_ window: Window, _ targetWorkspace: Workspace, _ io:
}
let targetContainer: NonLeafTreeNodeObject = window.isFloating ? targetWorkspace : targetWorkspace.rootTilingContainer
window.bind(to: targetContainer, adaptiveWeight: WEIGHT_AUTO, index: index)
- return focusFollowsWindow ? window.focusWindow() : true
+ return focusFollowsWindow ? window.focusWindow(reason: .moveWindowToWorkspace) : true
}
diff --git a/Sources/AppBundle/command/impl/SummonWorkspaceCommand.swift b/Sources/AppBundle/command/impl/SummonWorkspaceCommand.swift
index bf4e7ca6..17600ae7 100644
--- a/Sources/AppBundle/command/impl/SummonWorkspaceCommand.swift
+++ b/Sources/AppBundle/command/impl/SummonWorkspaceCommand.swift
@@ -15,7 +15,7 @@ struct SummonWorkspaceCommand: Command {
return !args.failIfNoop
}
if monitor.setActiveWorkspace(workspace) {
- return workspace.focusWorkspace()
+ return workspace.focusWorkspace(reason: .summonWorkspaceCommand)
} else {
return io.err("Can't move workspace '\(workspace.name)' to monitor '\(monitor.name)'. workspace-to-monitor-force-assignment doesn't allow it")
}
diff --git a/Sources/AppBundle/command/impl/SwapCommand.swift b/Sources/AppBundle/command/impl/SwapCommand.swift
index d265eabf..e7f95da9 100644
--- a/Sources/AppBundle/command/impl/SwapCommand.swift
+++ b/Sources/AppBundle/command/impl/SwapCommand.swift
@@ -49,7 +49,7 @@ struct SwapCommand: Command {
swapWindows(currentWindow, targetWindow)
if args.swapFocus {
- return targetWindow.focusWindow()
+ return targetWindow.focusWindow(reason: .swapCommand)
}
return true
}
diff --git a/Sources/AppBundle/command/impl/WorkspaceBackAndForthCommand.swift b/Sources/AppBundle/command/impl/WorkspaceBackAndForthCommand.swift
index 781398bc..a553f53c 100644
--- a/Sources/AppBundle/command/impl/WorkspaceBackAndForthCommand.swift
+++ b/Sources/AppBundle/command/impl/WorkspaceBackAndForthCommand.swift
@@ -6,6 +6,6 @@ struct WorkspaceBackAndForthCommand: Command {
/*conforms*/ let shouldResetClosedWindowsCache = false
func run(_ env: CmdEnv, _ io: CmdIo) -> Bool {
- return prevFocusedWorkspace?.focusWorkspace() != nil
+ return prevFocusedWorkspace?.focusWorkspace(reason: .workspaceBackAndForth) != nil
}
}
diff --git a/Sources/AppBundle/command/impl/WorkspaceCommand.swift b/Sources/AppBundle/command/impl/WorkspaceCommand.swift
index cae4eb47..414d59c4 100644
--- a/Sources/AppBundle/command/impl/WorkspaceCommand.swift
+++ b/Sources/AppBundle/command/impl/WorkspaceCommand.swift
@@ -33,7 +33,7 @@ struct WorkspaceCommand: Command {
}
return !args.failIfNoop
} else {
- return Workspace.get(byName: workspaceName).focusWorkspace()
+ return Workspace.get(byName: workspaceName).focusWorkspace(reason: .workspaceCommand)
}
}
}
diff --git a/Sources/AppBundle/focus.swift b/Sources/AppBundle/focus.swift
index e4633944..10fd1086 100644
--- a/Sources/AppBundle/focus.swift
+++ b/Sources/AppBundle/focus.swift
@@ -60,7 +60,26 @@ private struct FrozenFocus: AeroAny, Equatable, Sendable {
/// AEROSPACE_WORKSPACE env before accessing the global focus.
@MainActor var focus: LiveFocus { _focus.live }
-@MainActor func setFocus(to newFocus: LiveFocus) -> Bool {
+enum FocusChangeReason {
+ case preserveTheWorkspace
+ case moveWindowToWorkspace
+ case nativeFocus
+ case swapCommand
+ case initAppBundle
+ case summonWorkspaceCommand
+ case focusCommand
+ case focusBackAndForth
+ case automaticallyUnhideMacosHiddenApps
+ case workspaceCommand
+ case menuBarButton
+ case focusMonitor
+ case leftMouseUp
+ case workspaceBackAndForth
+ case test
+}
+
+@MainActor func setFocus(to newFocus: LiveFocus, reason: FocusChangeReason) -> Bool {
+ print("Focused to \(newFocus). Focus change reason: \(reason)")
if _focus == newFocus.frozen { return true }
let oldFocus = focus
// Normalize mruWindow when focus away from a workspace
@@ -75,9 +94,9 @@ private struct FrozenFocus: AeroAny, Equatable, Sendable {
return status
}
extension Window {
- @MainActor func focusWindow() -> Bool {
+ @MainActor func focusWindow(reason: FocusChangeReason) -> Bool {
if let focus = toLiveFocusOrNil() {
- return setFocus(to: focus)
+ return setFocus(to: focus, reason: reason)
} else {
// todo We should also exit-native-hidden/unminimize[/exit-native-fullscreen?] window if we want to fix ID-B6E178F2
// and retry to focus the window. Otherwise, it's not possible to focus minimized/hidden windows
@@ -88,7 +107,7 @@ extension Window {
@MainActor func toLiveFocusOrNil() -> LiveFocus? { visualWorkspace.map { LiveFocus(windowOrNil: self, workspace: $0) } }
}
extension Workspace {
- @MainActor func focusWorkspace() -> Bool { setFocus(to: toLiveFocus()) }
+ @MainActor func focusWorkspace(reason: FocusChangeReason) -> Bool { setFocus(to: toLiveFocus(), reason: reason) }
func toLiveFocus() -> LiveFocus {
// todo unfortunately mostRecentWindowRecursive may recursively reach empty rootTilingContainer
diff --git a/Sources/AppBundle/focusCache.swift b/Sources/AppBundle/focusCache.swift
index d70519e2..e14a076e 100644
--- a/Sources/AppBundle/focusCache.swift
+++ b/Sources/AppBundle/focusCache.swift
@@ -8,7 +8,7 @@
return
}
if nativeFocused?.windowId != lastKnownNativeFocusedWindowId {
- _ = nativeFocused?.focusWindow()
+ _ = nativeFocused?.focusWindow(reason: .nativeFocus)
lastKnownNativeFocusedWindowId = nativeFocused?.windowId
}
nativeFocused?.macAppUnsafe.lastNativeFocusedWindowId = nativeFocused?.windowId
diff --git a/Sources/AppBundle/initAppBundle.swift b/Sources/AppBundle/initAppBundle.swift
index 8f1fc712..b4df9e8c 100644
--- a/Sources/AppBundle/initAppBundle.swift
+++ b/Sources/AppBundle/initAppBundle.swift
@@ -29,7 +29,7 @@ import Foundation
startUnixSocketServer()
GlobalObserver.initObserver()
Workspace.garbageCollectUnusedWorkspaces() // init workspaces
- _ = Workspace.all.first?.focusWorkspace()
+ _ = Workspace.all.first?.focusWorkspace(reason: .initAppBundle)
try await runRefreshSessionBlocking(.startup, layoutWorkspaces: false)
try await runLightSession(.startup, .forceRun) {
smartLayoutAtStartup()
diff --git a/Sources/AppBundle/tree/MacWindow.swift b/Sources/AppBundle/tree/MacWindow.swift
index 503a745e..4e5cfd7b 100644
--- a/Sources/AppBundle/tree/MacWindow.swift
+++ b/Sources/AppBundle/tree/MacWindow.swift
@@ -1,7 +1,7 @@
import AppKit
import Common
-final class MacWindow: Window {
+final class MacWindow: Window, CustomStringConvertible {
let macApp: MacApp
private var prevUnhiddenProportionalPositionInsideWorkspaceRect: CGPoint?
@@ -40,17 +40,13 @@ final class MacWindow: Window {
return window
}
- // var description: String {
- // let description = [
- // ("title", title),
- // ("role", axWindow.get(Ax.roleAttr)),
- // ("subrole", axWindow.get(Ax.subroleAttr)),
- // ("identifier", axWindow.get(Ax.identifierAttr)),
- // ("modal", axWindow.get(Ax.modalAttr).map { String($0) } ?? ""),
- // ("windowId", String(windowId)),
- // ].map { "\($0.0): '\(String(describing: $0.1))'" }.joined(separator: ", ")
- // return "Window(\(description))"
- // }
+ var description: String {
+ let description = [
+ ("windowId", String(windowId)),
+ ("app bundle id", macApp.rawAppBundleId),
+ ].map { "\($0.0): '\(String(describing: $0.1))'" }.joined(separator: ", ")
+ return "Window(\(description))"
+ }
func isWindowHeuristic(_ windowLevel: MacOsWindowLevel?) async throws -> Bool { // todo cache
try await macApp.isWindowHeuristic(windowId, windowLevel)
@@ -89,7 +85,7 @@ final class MacWindow: Window {
switch parent.cases {
case .tilingContainer, .workspace, .macosHiddenAppsWindowsContainer, .macosFullscreenWindowsContainer:
let deadWindowFocus = deadWindowWorkspace.toLiveFocus()
- _ = setFocus(to: deadWindowFocus)
+ _ = setFocus(to: deadWindowFocus, reason: .preserveTheWorkspace)
// Guard against "Apple Reminders popup" bug: https://github.com/nikitabobko/AeroSpace/issues/201
if focus.windowOrNil?.app.pid != app.pid {
// Force focus to fix macOS annoyance with focused apps without windows.
diff --git a/Sources/AppBundle/tree/Workspace.swift b/Sources/AppBundle/tree/Workspace.swift
index e1d425d0..95a5030b 100644
--- a/Sources/AppBundle/tree/Workspace.swift
+++ b/Sources/AppBundle/tree/Workspace.swift
@@ -30,7 +30,7 @@ private func getStubWorkspace(forPoint point: CGPoint) -> Workspace {
.orDie("Can't create empty workspace")
}
-final class Workspace: TreeNode, NonLeafTreeNodeObject, Hashable, Comparable {
+final class Workspace: TreeNode, NonLeafTreeNodeObject, Hashable, Comparable, CustomStringConvertible {
let name: String
nonisolated private let nameLogicalSegments: StringLogicalSegments
/// `assignedMonitorPoint` must be interpreted only when the workspace is invisible
@@ -69,13 +69,9 @@ final class Workspace: TreeNode, NonLeafTreeNodeObject, Hashable, Comparable {
die("It's not possible to change weight of Workspace")
}
- @MainActor
var description: String {
let description = [
("name", name),
- ("isVisible", String(isVisible)),
- ("isEffectivelyEmpty", String(isEffectivelyEmpty)),
- ("doKeepAlive", String(config.persistentWorkspaces.contains(name))),
].map { "\($0.0): '\(String(describing: $0.1))'" }.joined(separator: ", ")
return "Workspace(\(description))"
}
diff --git a/Sources/AppBundle/ui/MenuBar.swift b/Sources/AppBundle/ui/MenuBar.swift
index f8727069..a8b6566a 100644
--- a/Sources/AppBundle/ui/MenuBar.swift
+++ b/Sources/AppBundle/ui/MenuBar.swift
@@ -16,7 +16,7 @@ public func menuBar(viewModel: TrayMenuModel) -> some Scene { // todo should it
ForEach(viewModel.workspaces, id: \.name) { workspace in
Button {
Task {
- try await runLightSession(.menuBarButton, token) { _ = Workspace.get(byName: workspace.name).focusWorkspace() }
+ try await runLightSession(.menuBarButton, token) { _ = Workspace.get(byName: workspace.name).focusWorkspace(reason: .menuBarButton) }
}
} label: {
Toggle(isOn: .constant(workspace.isFocused)) {
diff --git a/Sources/AppBundleTests/assert.swift b/Sources/AppBundleTests/assert.swift
index a694307b..c1052208 100644
--- a/Sources/AppBundleTests/assert.swift
+++ b/Sources/AppBundleTests/assert.swift
@@ -3,6 +3,16 @@
import Common
import XCTest
+extension Window {
+ @MainActor func focusWindow() -> Bool {
+ focusWindow(reason: .test)
+ }
+}
+
+extension Workspace {
+ @MainActor func focusWorkspace() -> Bool { focusWorkspace(reason: .test) }
+}
+
func assertTrue(_ actual: Bool, file: String = #filePath, line: Int = #line) {
assertEquals(actual, true, file: file, line: line)
}As of the second "Windows Moving Across Workspaces Unexpectedly" problem. AeroSpace had this bug before (#1216), but I belive that it's been fixed. Not sure what could be causing it on your side. The symptoms that you are describing are all odd and I don't see people reporing them. I would seek if there any 3rd party application that interferes with AeroSpace |
Beta Was this translation helpful? Give feedback.
1 reply
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Body
Steps to Reproduce
I haven’t found a reliable way to reproduce this. It happens seemingly at random. I can be sitting idle without touching the keyboard or mouse, and after some amount of time, AeroSpace will switch me to a different workspace. This happens both with multiple monitors and when only using the mac screen.
Expected Result
Actual Result
Two related issues occur frequently:
all move into workspace B when I switch to it.
Additional Information
I’m not sure whether this is caused by my configuration or a bug. These two issues happen regularly and make AeroSpace difficult to use at times. Any guidance or troubleshooting suggestions would be appreciated.
Version
Config
Beta Was this translation helpful? Give feedback.
All reactions