Apps randomly move workspaces and which workspace is show randomly changes #1939
Replies: 7 comments 5 replies
-
|
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.
-
|
im facing the same issue. |
Beta Was this translation helpful? Give feedback.
-
|
Anyone here also have it randomly toggle fullscreen? Perhaps the issues are related? |
Beta Was this translation helpful? Give feedback.
-
|
@nikitabobko i can help you debug this if you let me know what would be useful in a bug report and how to do so. i'm facing this constantly now (not sure why it has become more frequent) and the product is unusable now as it moving me to random screens every few minutes - it jumped twice while writing this comment lol I pretty much exclusively use the workspace keys |
Beta Was this translation helpful? Give feedback.
-
|
This is becoming a regular issue for me as well. So far, I have found that switching the active desktop to my external monitor and then back to my built-in does usually fix it, but not always. I also do not know what triggers it. I first noticed this when attempting to take notes in Emacs on video calls happening in Teams or Google Meet (in Zen or Chrome). That made me think it was some sort of thing connected to audio/video focus, but now it's sometimes throwing me to empty workspaces. It's very frustrating when it decides to do this. |
Beta Was this translation helpful? Give feedback.
-
|
@nikitabobko bump. is there any way we can help you debug? |
Beta Was this translation helpful? Give feedback.
-
|
I'm also facing this issue, randomly change workspace and also move window over other workspace after update to lastest version. |
Beta Was this translation helpful? Give feedback.
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