| C1.source/Resources/Aligner-Info.plist | ●●●●● patch | view | raw | blame | history | |
| C1.source/Sources/Aligner/Infrastructure/PrivateAPI/SkyLightSpaceEnumerator.swift | ●●●●● patch | view | raw | blame | history | |
| C1.source/Sources/Aligner/QuickSwitchRootView.swift | ●●●●● patch | view | raw | blame | history | |
| C1.source/Sources/AlignerCore/Spaces/WindowSpaceMappingPolicy.swift | ●●●●● patch | view | raw | blame | history | |
| C1.source/Tests/AlignerCoreTests/AlignerCoreTests.swift | ●●●●● patch | view | raw | blame | history |
C1.source/Resources/Aligner-Info.plist
@@ -17,9 +17,9 @@ <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key> <string>0.0.50</string> <string>0.0.51</string> <key>CFBundleVersion</key> <string>20260610.1156</string> <string>20260610.1637</string> <key>LSMinimumSystemVersion</key> <string>26.0</string> <key>NSHighResolutionCapable</key> C1.source/Sources/Aligner/Infrastructure/PrivateAPI/SkyLightSpaceEnumerator.swift
@@ -25,11 +25,20 @@ 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 { @@ -40,6 +49,7 @@ 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 { @@ -115,6 +125,27 @@ } 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() @@ -138,6 +169,53 @@ 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 { @@ -217,3 +295,18 @@ 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 } } C1.source/Sources/Aligner/QuickSwitchRootView.swift
@@ -722,7 +722,8 @@ "waterfallMaxScrollOffset": Double(waterfallMaxScrollOffset), "waterfallAlignmentMinScrollOffset": Double(waterfallAlignmentScrollRange.lowerBound), "waterfallAlignmentMaxScrollOffset": Double(waterfallAlignmentScrollRange.upperBound), "waterfallScrollable": waterfallAlignmentScrollable, "waterfallScrollable": waterfallMaxScrollOffset > 0, "waterfallAlignmentScrollable": waterfallAlignmentScrollable, "waterfallColumnCount": waterfallColumns.count, "waterfallColumnNames": waterfallColumns.map(\.column.app.name), "waterfallClipsToBounds": waterfallLayer.masksToBounds, C1.source/Sources/AlignerCore/Spaces/WindowSpaceMappingPolicy.swift
New file @@ -0,0 +1,54 @@ import Foundation public enum WindowSpaceMappingPolicy { public static func mergedSpaceIDsByWindowID( requestedWindowIDs: [UInt32], directSpaceIDsByWindowID: [UInt32: [UInt64]], windowIDsBySpaceID: [UInt64: [UInt32]], orderedSpaceIDs: [UInt64] ) -> [UInt32: [UInt64]] { let requestedWindowIDSet = Set(requestedWindowIDs) var result: [UInt32: [UInt64]] = [:] for windowID in requestedWindowIDs { let directSpaceIDs = orderedUnique(directSpaceIDsByWindowID[windowID] ?? []) if !directSpaceIDs.isEmpty { result[windowID] = directSpaceIDs } } for spaceID in normalizedSpaceOrder( orderedSpaceIDs: orderedSpaceIDs, windowIDsBySpaceID: windowIDsBySpaceID ) { for windowID in windowIDsBySpaceID[spaceID] ?? [] { guard requestedWindowIDSet.contains(windowID), result[windowID] == nil else { continue } result[windowID, default: []].append(spaceID) } } return result.mapValues(orderedUnique) } private static func normalizedSpaceOrder( orderedSpaceIDs: [UInt64], windowIDsBySpaceID: [UInt64: [UInt32]] ) -> [UInt64] { let orderedUniqueSpaceIDs = orderedUnique(orderedSpaceIDs) if !orderedUniqueSpaceIDs.isEmpty { return orderedUniqueSpaceIDs } return windowIDsBySpaceID.keys.sorted() } private static func orderedUnique<T: Hashable>(_ values: [T]) -> [T] { var seen = Set<T>() return values.filter { seen.insert($0).inserted } } } C1.source/Tests/AlignerCoreTests/AlignerCoreTests.swift
@@ -1174,6 +1174,50 @@ XCTAssertEqual(snapshot.appGroups.single?.windows.map(\.primarySpaceID), [10, 11, 12, 12, nil]) } func testWindowSpaceMappingFillsUnresolvedWindowsFromSpaceInverseMap() { let mapping = WindowSpaceMappingPolicy.mergedSpaceIDsByWindowID( requestedWindowIDs: [10, 20, 30], directSpaceIDsByWindowID: [10: [100]], windowIDsBySpaceID: [ 100: [10, 20], 101: [30] ], orderedSpaceIDs: [100, 101] ) XCTAssertEqual(mapping[10], [100]) XCTAssertEqual(mapping[20], [100]) XCTAssertEqual(mapping[30], [101]) } func testWindowSpaceMappingDoesNotOverwriteDirectSpaceAssignments() { let mapping = WindowSpaceMappingPolicy.mergedSpaceIDsByWindowID( requestedWindowIDs: [10], directSpaceIDsByWindowID: [10: [101]], windowIDsBySpaceID: [ 100: [10] ], orderedSpaceIDs: [100, 101] ) XCTAssertEqual(mapping[10], [101]) } func testWindowSpaceMappingLeavesAmbiguousNoSpaceWindowsUnassigned() { let mapping = WindowSpaceMappingPolicy.mergedSpaceIDsByWindowID( requestedWindowIDs: [10, 20], directSpaceIDsByWindowID: [:], windowIDsBySpaceID: [ 100: [10, 99] ], orderedSpaceIDs: [100] ) XCTAssertEqual(mapping[10], [100]) XCTAssertNil(mapping[20]) XCTAssertNil(mapping[99]) } func testQuickSwitchSnapshotKeepsMinimizedAndFullscreenWindows() { let app = makeApp() let snapshot = QuickSwitchSnapshotBuilder.snapshot(