From 125e9b94040e95204c6a05cd5089022ba92efbce Mon Sep 17 00:00:00 2001 From: Nikita Bobko Date: Mon, 14 Oct 2024 23:12:43 +0200 Subject: [PATCH] Implement monitor-appkit-nsscreen-screens-id https://github.com/nikitabobko/AeroSpace/issues/336 --- Sources/AppBundle/command/format.swift | 2 ++ Sources/AppBundle/focus.swift | 2 +- Sources/AppBundle/model/Monitor.swift | 43 ++++++++++++++++++------- Sources/AppBundle/model/MonitorEx.swift | 2 ++ docs/aerospace-list-monitors.adoc | 3 +- docs/aerospace-list-windows.adoc | 3 +- docs/aerospace-list-workspaces.adoc | 3 +- 7 files changed, 43 insertions(+), 15 deletions(-) diff --git a/Sources/AppBundle/command/format.swift b/Sources/AppBundle/command/format.swift index 5c597468..b2a0b675 100644 --- a/Sources/AppBundle/command/format.swift +++ b/Sources/AppBundle/command/format.swift @@ -71,6 +71,7 @@ private enum FormatVar: Equatable { enum MonitorFormatVar: String, Equatable { case monitorId = "monitor-id" + case monitorAppKitNsScreenScreensId = "monitor-appkit-nsscreen-screens-id" case monitorName = "monitor-name" } } @@ -141,6 +142,7 @@ extension String { case (.monitor(let m), .monitor(let f)): return switch f { case .monitorId: .success(m.monitorId.map { .int($0 + 1) } ?? .string("NULL-MONITOR-ID")) + case .monitorAppKitNsScreenScreensId: .success(.int(m.monitorAppKitNsScreenScreensId)) case .monitorName: .success(.string(m.name)) } case (.app(let a), .app(let f)): diff --git a/Sources/AppBundle/focus.swift b/Sources/AppBundle/focus.swift index 5f42d8a8..119a429f 100644 --- a/Sources/AppBundle/focus.swift +++ b/Sources/AppBundle/focus.swift @@ -51,7 +51,7 @@ struct FrozenFocus: AeroAny, Equatable { var _focus: FrozenFocus = { // It's fine to call *Inaccurate during startup - let monitor = focusedMonitorInaccurate ?? mainMonitor + let monitor = mainMonitor return FrozenFocus(windowId: nil, workspaceName: monitor.activeWorkspace.name, monitorId: monitor.monitorId ?? 0) }() diff --git a/Sources/AppBundle/model/Monitor.swift b/Sources/AppBundle/model/Monitor.swift index 10887f31..482c956f 100644 --- a/Sources/AppBundle/model/Monitor.swift +++ b/Sources/AppBundle/model/Monitor.swift @@ -2,6 +2,7 @@ import AppKit import Common private struct MonitorImpl { + let monitorAppKitNsScreenScreensId: Int let name: String let rect: Rect let visibleRect: Rect @@ -14,6 +15,8 @@ extension MonitorImpl: Monitor { /// Use it instead of NSScreen because it can be mocked in tests protocol Monitor: AeroAny { + /// The index in NSScreen.screens array. 1-based index + var monitorAppKitNsScreenScreensId: Int { get } var name: String { get } var rect: Rect { get } var visibleRect: Rect { get } @@ -23,13 +26,15 @@ protocol Monitor: AeroAny { class LazyMonitor: Monitor { private let screen: NSScreen + let monitorAppKitNsScreenScreensId: Int let name: String let width: CGFloat let height: CGFloat private var _rect: Rect? private var _visibleRect: Rect? - init(_ screen: NSScreen) { + init(monitorAppKitNsScreenScreensId: Int, _ screen: NSScreen) { + self.monitorAppKitNsScreenScreensId = monitorAppKitNsScreenScreensId self.name = screen.localizedName self.width = screen.frame.width // Don't call rect because it would cause recursion during mainMonitor init self.height = screen.frame.height // Don't call rect because it would cause recursion during mainMonitor init @@ -45,8 +50,19 @@ class LazyMonitor: Monitor { } } +// Note to myself: Don't use NSScreen.main, it's garbage +// 1. The name is misleading, it's supposed to be called "focusedScreen" +// 2. It's inaccurate because NSScreen.main doesn't work correctly from NSWorkspace.didActivateApplicationNotification & +// kAXFocusedWindowChangedNotification callbacks. private extension NSScreen { - var monitor: Monitor { MonitorImpl(name: localizedName, rect: rect, visibleRect: visibleRect) } + func toMonitor(monitorAppKitNsScreenScreensId: Int) -> Monitor { + MonitorImpl( + monitorAppKitNsScreenScreensId: monitorAppKitNsScreenScreensId, + name: localizedName, + rect: rect, + visibleRect: visibleRect + ) + } var isMainScreen: Bool { frame.minX == 0 && frame.minY == 0 @@ -65,19 +81,24 @@ private extension NSScreen { } private let testMonitorRect = Rect(topLeftX: 0, topLeftY: 0, width: 1920, height: 1080) -private let testMonitor = MonitorImpl(name: "Test Monitor", rect: testMonitorRect, visibleRect: testMonitorRect) - -/// It's inaccurate because NSScreen.main doesn't work correctly from NSWorkspace.didActivateApplicationNotification & -/// kAXFocusedWindowChangedNotification callbacks. -var focusedMonitorInaccurate: Monitor? { - isUnitTest ? testMonitor : NSScreen.main?.monitor -} +private let testMonitor = MonitorImpl( + monitorAppKitNsScreenScreensId: 1, + name: "Test Monitor", + rect: testMonitorRect, + visibleRect: testMonitorRect +) var mainMonitor: Monitor { - isUnitTest ? testMonitor : LazyMonitor(NSScreen.screens.singleOrNil(where: \.isMainScreen)!) + if isUnitTest { return testMonitor } + let elem = NSScreen.screens.withIndex.singleOrNil(where: \.value.isMainScreen)! + return LazyMonitor(monitorAppKitNsScreenScreensId: elem.index + 1, elem.value) } -var monitors: [Monitor] { isUnitTest ? [testMonitor] : NSScreen.screens.map(\.monitor) } +var monitors: [Monitor] { + isUnitTest + ? [testMonitor] + : NSScreen.screens.enumerated().map { $0.element.toMonitor(monitorAppKitNsScreenScreensId: $0.offset + 1) } +} var sortedMonitors: [Monitor] { monitors.sorted(using: [SelectorComparator(selector: \.rect.minX), SelectorComparator(selector: \.rect.minY)]) diff --git a/Sources/AppBundle/model/MonitorEx.swift b/Sources/AppBundle/model/MonitorEx.swift index 49a93ccd..bdb0535b 100644 --- a/Sources/AppBundle/model/MonitorEx.swift +++ b/Sources/AppBundle/model/MonitorEx.swift @@ -10,6 +10,8 @@ extension Monitor { ) } + /// todo make 1-based + /// 0-based index var monitorId: Int? { let sorted = sortedMonitors let origin = self.rect.topLeftCorner diff --git a/docs/aerospace-list-monitors.adoc b/docs/aerospace-list-monitors.adoc index 70ca6fce..40e93538 100644 --- a/docs/aerospace-list-monitors.adoc +++ b/docs/aerospace-list-monitors.adoc @@ -53,7 +53,8 @@ If not specified, the default `` is: + The following variables can be used inside ``: -%{monitor-id}:: Number. Sequential number of the belonging monitor +%{monitor-id}:: 1-based Number. Sequential number of the belonging monitor +%{monitor-appkit-nsscreen-screens-id}:: 1-based index of the belonging monitor in `NSScreen.screens` array. Useful for integration with other tools that might be using `NSScreen.screens` ordering (like sketchybar). %{monitor-name}:: String. Name of the belonging monitor %{right-padding}:: A special variable which expands with a minimum number of spaces required to form a right padding in the appropriate column diff --git a/docs/aerospace-list-windows.adoc b/docs/aerospace-list-windows.adoc index 236fd4a7..868566d2 100644 --- a/docs/aerospace-list-windows.adoc +++ b/docs/aerospace-list-windows.adoc @@ -85,7 +85,8 @@ The following variables can be used inside ``: %{workspace}:: String. Name of the belonging workspace -%{monitor-id}:: Number. Sequential number of the belonging monitor +%{monitor-id}:: 1-based Number. Sequential number of the belonging monitor. +%{monitor-appkit-nsscreen-screens-id}:: 1-based index of the belonging monitor in `NSScreen.screens` array. Useful for integration with other tools that might be using `NSScreen.screens` ordering (like sketchybar). %{monitor-name}:: String. Name of the belonging monitor %{right-padding}:: A special variable which expands with a minimum number of spaces required to form a right padding in the appropriate column diff --git a/docs/aerospace-list-workspaces.adoc b/docs/aerospace-list-workspaces.adoc index edbdbfa4..64ce9468 100644 --- a/docs/aerospace-list-workspaces.adoc +++ b/docs/aerospace-list-workspaces.adoc @@ -67,7 +67,8 @@ The following variables can be used inside ``: %{workspace}:: String. Name of the belonging workspace -%{monitor-id}:: Number. Sequential number of the belonging monitor +%{monitor-id}:: 1-based Number. Sequential number of the belonging monitor +%{monitor-appkit-nsscreen-screens-id}:: 1-based Number. Sequential number of the belonging monitor in `NSScreen.screens`. Useful for integration with other tools that might be using `NSScreen.screens` ordering (like sketchybar). %{monitor-name}:: String. Name of the belonging monitor %{right-padding}:: A special variable which expands with a minimum number of spaces required to form a right padding in the appropriate column