| | |
| | | private typealias CGSMainConnectionIDFunction = @convention(c) () -> UInt32 |
| | | private typealias CGSCopyManagedDisplaySpacesFunction = @convention(c) (UInt32) -> Unmanaged<CFArray>? |
| | | private typealias CGSCopySpacesForWindowsFunction = @convention(c) (UInt32, UInt32, CFArray) -> Unmanaged<CFArray>? |
| | | private typealias CGSCopyWindowsWithOptionsAndTagsFunction = @convention(c) ( |
| | | UInt32, |
| | | Int, |
| | | CFArray, |
| | | Int, |
| | | UnsafeMutablePointer<Int>, |
| | | UnsafeMutablePointer<Int> |
| | | ) -> Unmanaged<CFArray>? |
| | | |
| | | private let handle: UnsafeMutableRawPointer? |
| | | private let mainConnectionID: CGSMainConnectionIDFunction |
| | | private let copyManagedDisplaySpaces: CGSCopyManagedDisplaySpacesFunction |
| | | private let copySpacesForWindows: CGSCopySpacesForWindowsFunction? |
| | | private let copyWindowsWithOptionsAndTags: CGSCopyWindowsWithOptionsAndTagsFunction? |
| | | |
| | | init() throws { |
| | | guard let handle = dlopen("/System/Library/PrivateFrameworks/SkyLight.framework/SkyLight", RTLD_LAZY) else { |
| | |
| | | self.mainConnectionID = try Self.symbol("CGSMainConnectionID", in: handle) |
| | | self.copyManagedDisplaySpaces = try Self.symbol("CGSCopyManagedDisplaySpaces", in: handle) |
| | | self.copySpacesForWindows = try? Self.symbol("CGSCopySpacesForWindows", in: handle) |
| | | self.copyWindowsWithOptionsAndTags = try? Self.symbol("CGSCopyWindowsWithOptionsAndTags", in: handle) |
| | | } |
| | | |
| | | deinit { |
| | |
| | | } |
| | | |
| | | func spaceIDsByWindowID(windowIDs: [UInt32]) -> [UInt32: [UInt64]] { |
| | | let directSpaceIDsByWindowID = directSpaceIDsByWindowID(windowIDs: windowIDs) |
| | | guard let orderedSpaceIDs = try? allManagedSpaceIDs(), |
| | | !orderedSpaceIDs.isEmpty |
| | | else { |
| | | return directSpaceIDsByWindowID |
| | | } |
| | | |
| | | let windowIDsBySpaceID = windowIDsBySpaceID(spaceIDs: orderedSpaceIDs) |
| | | guard !windowIDsBySpaceID.isEmpty else { |
| | | return directSpaceIDsByWindowID |
| | | } |
| | | |
| | | return WindowSpaceMappingPolicy.mergedSpaceIDsByWindowID( |
| | | requestedWindowIDs: windowIDs, |
| | | directSpaceIDsByWindowID: directSpaceIDsByWindowID, |
| | | windowIDsBySpaceID: windowIDsBySpaceID, |
| | | orderedSpaceIDs: orderedSpaceIDs |
| | | ) |
| | | } |
| | | |
| | | private func directSpaceIDsByWindowID(windowIDs: [UInt32]) -> [UInt32: [UInt64]] { |
| | | guard let copySpacesForWindows else { return [:] } |
| | | |
| | | let connectionID = mainConnectionID() |
| | |
| | | |
| | | return result |
| | | } |
| | | |
| | | private func allManagedSpaceIDs() throws -> [UInt64] { |
| | | try managedDisplaySpaceRecords() |
| | | .flatMap { displayRecord -> [UInt64] in |
| | | let spaceRecords = displayRecord["Spaces"] as? [[String: Any]] ?? [] |
| | | return spaceRecords.compactMap { spaceRecord in |
| | | uint64Value(spaceRecord["id64"] ?? spaceRecord["id"]) |
| | | } |
| | | } |
| | | } |
| | | |
| | | private func windowIDsBySpaceID(spaceIDs: [UInt64]) -> [UInt64: [UInt32]] { |
| | | guard let copyWindowsWithOptionsAndTags else { return [:] } |
| | | |
| | | let connectionID = mainConnectionID() |
| | | let options = Self.copyWindowsOptionIncludeInvisible |
| | | var result: [UInt64: [UInt32]] = [:] |
| | | |
| | | for spaceID in spaceIDs { |
| | | let spaceIDArray = [NSNumber(value: spaceID)] as CFArray |
| | | var setTags = 0 |
| | | var clearTags = 0 |
| | | guard let unmanagedWindows = copyWindowsWithOptionsAndTags( |
| | | connectionID, |
| | | 0, |
| | | spaceIDArray, |
| | | options, |
| | | &setTags, |
| | | &clearTags |
| | | ) else { |
| | | continue |
| | | } |
| | | |
| | | let windowIDs = (unmanagedWindows.takeRetainedValue() as? [Any] ?? []) |
| | | .compactMap { item -> UInt32? in |
| | | uint32Value(item) |
| | | } |
| | | |
| | | if !windowIDs.isEmpty { |
| | | result[spaceID] = windowIDs |
| | | } |
| | | } |
| | | |
| | | return result |
| | | } |
| | | |
| | | private static let copyWindowsOptionIncludeInvisible = (1 << 0) | (1 << 1) | (1 << 2) |
| | | |
| | | private static func symbol<T>(_ name: String, in handle: UnsafeMutableRawPointer) throws -> T { |
| | | guard let rawSymbol = dlsym(handle, name) else { |
| | |
| | | return nil |
| | | } |
| | | } |
| | | |
| | | private func uint32Value(_ value: Any?) -> UInt32? { |
| | | switch value { |
| | | case let number as NSNumber: |
| | | return number.uint32Value |
| | | case let uint32 as UInt32: |
| | | return uint32 |
| | | case let uint as UInt where uint <= UInt(UInt32.max): |
| | | return UInt32(uint) |
| | | case let int as Int where int >= 0 && int <= Int(UInt32.max): |
| | | return UInt32(int) |
| | | default: |
| | | return nil |
| | | } |
| | | } |