aerospace fork
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197import AppKitimport Common
enum EffectiveLeaf { case window(Window) case emptyWorkspace(Workspace)}extension LiveFocus { var asLeaf: EffectiveLeaf { if let windowOrNil { .window(windowOrNil) } else { .emptyWorkspace(workspace) } }}
/// This object should be only passed around but never memorized/// Alternative name: ResolvedFocusstruct LiveFocus: AeroAny, Equatable { let windowOrNil: Window? var workspace: Workspace
@MainActor fileprivate var frozen: FrozenFocus { return FrozenFocus( windowId: windowOrNil?.windowId, workspaceName: workspace.name, monitorId_oneBased: workspace.workspaceMonitor.monitorId_oneBased ?? 0, ) }}
/// "old", "captured", "frozen in time" Focus/// It's safe to keep a hard reference to this object./// Unlike in LiveFocus, information inside FrozenFocus isn't guaranteed to be self-consistent./// window - workspace - monitor relation could change since the moment object was createdprivate struct FrozenFocus: AeroAny, Equatable, Sendable { let windowId: UInt32? let workspaceName: String // monitorId is not part of the focus. We keep it here only for 'on-focused-monitor-changed' to work let monitorId_oneBased: Int
@MainActor var live: LiveFocus { // Important: don't access focus.monitorId here. monitorId is not part of the focus. Always prefer workspace let window: Window? = windowId.flatMap { Window.get(byId: $0) } let workspace = Workspace.get(byName: workspaceName)
let workspaceFocus = workspace.toLiveFocus() let windowFocus = window?.toLiveFocusOrNil() ?? workspaceFocus
return workspaceFocus.workspace != windowFocus.workspace ? workspaceFocus // If window and workspace become separated prefer workspace : windowFocus }}
@MainActor private var _focus: FrozenFocus = { let monitor = mainMonitorInfo return FrozenFocus(windowId: nil, workspaceName: monitor.activeWorkspace.name, monitorId_oneBased: monitor.monitorId_oneBased ?? 0)}()
/// Global focus./// Commands must be cautious about accessing this property directly. There are legitimate cases./// But, in general, commands must firstly check --window-id, --workspace, DEEWM_WINDOW_ID env and/// DEEWM_WORKSPACE env before accessing the global focus.@MainActor var focus: LiveFocus { _focus.live }
@MainActor func setFocus(to newFocus: LiveFocus) -> Bool { if _focus == newFocus.frozen { return true } let oldFocus = focus // Normalize mruWindow when focus away from a workspace if oldFocus.workspace != newFocus.workspace { oldFocus.windowOrNil?.markAsMostRecentChild() }
_focus = newFocus.frozen let status = newFocus.workspace.workspaceMonitor.setActiveWorkspace(newFocus.workspace)
newFocus.windowOrNil?.markAsMostRecentChild() return status}extension Window { @MainActor func focusWindow() -> Bool { if let focus = toLiveFocusOrNil() { return setFocus(to: focus) } 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 return false } }
@MainActor func toLiveFocusOrNil() -> LiveFocus? { visualWorkspace.map { LiveFocus(windowOrNil: self, workspace: $0) } }}extension Workspace { @MainActor func focusWorkspace() -> Bool { setFocus(to: toLiveFocus()) }
func toLiveFocus() -> LiveFocus { // todo unfortunately mostRecentWindowRecursive may recursively reach empty rootTilingContainer // while floating or macos unconventional windows might be presented if let wd = mostRecentWindowRecursive ?? anyLeafWindowRecursive { LiveFocus(windowOrNil: wd, workspace: self) } else { LiveFocus(windowOrNil: nil, workspace: self) // emptyWorkspace } }}
@MainActor private var _lastKnownFocus: FrozenFocus = _focus
// Used by workspace-back-and-forth@MainActor var _prevFocusedWorkspaceName: String? = nil { didSet { prevFocusedWorkspaceDate = .now }}@MainActor var prevFocusedWorkspaceDate: Date = .distantPast@MainActor var prevFocusedWorkspace: Workspace? { _prevFocusedWorkspaceName.map { Workspace.get(byName: $0) } }
// Used by focus-back-and-forth@MainActor private var _prevFocus: FrozenFocus? = nil@MainActor var prevFocus: LiveFocus? { _prevFocus?.live.takeIf { $0 != focus } }
@MainActor private var onFocusChangedRecursionGuard = false// Should be called in refreshSession@MainActor func checkOnFocusChangedCallbacks_nonCancellable() async { if refreshSessionEvent?.isStartup == true { return } let focus = focus let frozenFocus = focus.frozen var hasFocusChanged = false var hasFocusedWorkspaceChanged = false var hasFocusedMonitorChanged = false if frozenFocus != _lastKnownFocus { _prevFocus = _lastKnownFocus hasFocusChanged = true } if frozenFocus.workspaceName != _lastKnownFocus.workspaceName { _prevFocusedWorkspaceName = _lastKnownFocus.workspaceName hasFocusedWorkspaceChanged = true } if frozenFocus.monitorId_oneBased != _lastKnownFocus.monitorId_oneBased { hasFocusedMonitorChanged = true } _lastKnownFocus = frozenFocus
if onFocusChangedRecursionGuard { return } onFocusChangedRecursionGuard = true defer { onFocusChangedRecursionGuard = false } if hasFocusChanged { _ = await onFocusChanged(.defaultEnv, CmdIoImpl.emptyStdinIgnoringOut, focus) } if let _prevFocusedWorkspaceName, hasFocusedWorkspaceChanged { onWorkspaceChanged(_prevFocusedWorkspaceName, frozenFocus.workspaceName, focus) } if hasFocusedMonitorChanged { _ = await onFocusedMonitorChanged(.defaultEnv, CmdIoImpl.emptyStdinIgnoringOut, focus) }}
@MainActor func onFocusedMonitorChanged(_ env: CmdEnv, _ io: CmdIo, _ focus: LiveFocus) async -> Int32ExitCode { broadcastEvent(.focusedMonitorChanged( workspace: focus.workspace.name, monitorId_oneBased: focus.workspace.workspaceMonitor.monitorId_oneBased ?? 0, )) return await config.onFocusedMonitorChanged.run(env.withFocus(focus), io)}
@MainActor func onFocusChanged(_ env: CmdEnv, _ io: CmdIo, _ focus: LiveFocus) async -> Int32ExitCode { broadcastEvent(.focusChanged( windowId: focus.windowOrNil?.windowId, workspace: focus.workspace.name, )) return await config.onFocusChanged.run(env.withFocus(focus), io)}
@MainActor private func onWorkspaceChanged(_ oldWorkspace: String, _ newWorkspace: String, _ focus: LiveFocus) { broadcastEvent(.workspaceChanged( workspace: newWorkspace, prevWorkspace: oldWorkspace, )) if let exec = config.execOnWorkspaceChange.first { let process = Process() process.executableURL = URL(filePath: exec) process.arguments = Array(config.execOnWorkspaceChange.dropFirst()) var environment = config.execConfig.envVariables environment[DEEWM_FOCUSED_WORKSPACE] = newWorkspace environment[DEEWM_PREV_WORKSPACE] = oldWorkspace switch focus.asLeaf { case .emptyWorkspace(let w): environment[DEEWM_WORKSPACE] = w.name environment[DEEWM_WINDOW_ID] = nil case .window(let w): environment[DEEWM_WORKSPACE] = nil environment[DEEWM_WINDOW_ID] = w.windowId.description } process.environment = environment _ = Result { try process.run() } }}