aerospace fork
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116import AppKitimport Common
private struct MonitorInfoImpl { let monitorAppKitNsScreenScreensId: Int let name: String let rect: Rect let visibleRect: Rect let isMain: Bool}
extension MonitorInfoImpl: MonitorInfo { var height: CGFloat { rect.height } var width: CGFloat { rect.width }}
/// Use it instead of NSScreen because it can be mocked in testsprotocol MonitorInfo: 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 } var width: CGFloat { get } var height: CGFloat { get } var isMain: Bool { get }}
final class LazyMonitorInfo: MonitorInfo { private let screen: NSScreen let monitorAppKitNsScreenScreensId: Int let name: String let width: CGFloat let height: CGFloat let isMain: Bool private var _rect: Rect? private var _visibleRect: Rect?
init(monitorAppKitNsScreenScreensId: Int, isMain: Bool, _ 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 self.screen = screen self.isMain = isMain }
var rect: Rect { _rect ?? screen.rect.also { _rect = $0 } }
var visibleRect: Rect { _visibleRect ?? screen.visibleRect.also { _visibleRect = $0 } }}
// 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.extension NSScreen { fileprivate func toMonitorInfo(monitorAppKitNsScreenScreensId: Int) -> MonitorInfo { MonitorInfoImpl( monitorAppKitNsScreenScreensId: monitorAppKitNsScreenScreensId, name: localizedName, rect: rect, visibleRect: visibleRect, isMain: isMainScreen, ) }
fileprivate var isMainScreen: Bool { frame.minX == 0 && frame.minY == 0 }
/// The property is a replacement for Apple's crazy ``frame`` /// /// - For ``MacWindow.topLeftCorner``, (0, 0) is main screen top left corner, and positive y-axis goes down. /// - For ``frame``, (0, 0) is main screen bottom left corner, and positive y-axis goes up (which is crazy). /// /// The property "normalizes" ``frame`` fileprivate var rect: Rect { frame.monitorFrameNormalized() }
/// Same as ``rect`` but for ``visibleFrame`` fileprivate var visibleRect: Rect { visibleFrame.monitorFrameNormalized() }}
private let testMonitorInfoRect = Rect(topLeftX: 0, topLeftY: 0, width: 1920, height: 1080)private let testMonitorInfo = MonitorInfoImpl( monitorAppKitNsScreenScreensId: 1, name: "Test Monitor", rect: testMonitorInfoRect, visibleRect: testMonitorInfoRect, isMain: true,)
var mainMonitorInfo: MonitorInfo { if isUnitTest { return testMonitorInfo } let screens = NSScreen.screens // Fallback: If main screen can't be found (e.g., during display reconfiguration), // return screens.first or testMonitor to avoid crash let screen = screens.withIndex.singleOrNil(where: \.value.isMainScreen) ?? screens.first.map { (0, $0) } guard let screen else { return testMonitorInfo } return LazyMonitorInfo(monitorAppKitNsScreenScreensId: screen.index + 1, isMain: true, screen.value)}
var monitorInfos: [MonitorInfo] { isUnitTest ? [testMonitorInfo] : NSScreen.screens.enumerated().map { $0.element.toMonitorInfo(monitorAppKitNsScreenScreensId: $0.offset + 1) }}
var sortedMonitorInfos: [MonitorInfo] { monitorInfos.sortedBy([\.rect.minX, \.rect.minY])}