From bedb145ee1413482763707fe73787b622cb52cbf Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Sat, 13 Jun 2026 16:08:40 +0800
Subject: [PATCH] Fix Finder tab space activation

---
 C1.source/Sources/AlignerCore/Windows/QuickSwitchViewModel.swift |  217 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 211 insertions(+), 6 deletions(-)

diff --git a/C1.source/Sources/AlignerCore/Windows/QuickSwitchViewModel.swift b/C1.source/Sources/AlignerCore/Windows/QuickSwitchViewModel.swift
index 1686294..18bc70a 100644
--- a/C1.source/Sources/AlignerCore/Windows/QuickSwitchViewModel.swift
+++ b/C1.source/Sources/AlignerCore/Windows/QuickSwitchViewModel.swift
@@ -1,3 +1,4 @@
+import CoreGraphics
 import Foundation
 
 public struct QuickSwitchViewModel: Equatable, Sendable {
@@ -5,17 +6,20 @@
     public let appShelf: [QuickSwitchAppShelfItemViewModel]
     public let waterfallColumns: [QuickSwitchWaterfallColumnViewModel]
     public let initialSelection: QuickSwitchSelection?
+    public let lockedSpaceID: UInt64?
 
     public init(
         displays: [QuickSwitchDisplayViewModel],
         appShelf: [QuickSwitchAppShelfItemViewModel],
         waterfallColumns: [QuickSwitchWaterfallColumnViewModel],
-        initialSelection: QuickSwitchSelection?
+        initialSelection: QuickSwitchSelection?,
+        lockedSpaceID: UInt64? = nil
     ) {
         self.displays = displays
         self.appShelf = appShelf
         self.waterfallColumns = waterfallColumns
         self.initialSelection = initialSelection
+        self.lockedSpaceID = lockedSpaceID
     }
 
     public var spaceCount: Int {
@@ -59,6 +63,7 @@
     public let windowCount: Int
     public let appCount: Int
     public let appNames: [String]
+    public let splitViewAppNames: [String]
 
     public init(
         id: UInt64,
@@ -69,7 +74,8 @@
         isCurrent: Bool = false,
         windowCount: Int,
         appCount: Int,
-        appNames: [String] = []
+        appNames: [String] = [],
+        splitViewAppNames: [String] = []
     ) {
         self.id = id
         self.displayUUID = displayUUID
@@ -80,6 +86,7 @@
         self.windowCount = windowCount
         self.appCount = appCount
         self.appNames = appNames
+        self.splitViewAppNames = splitViewAppNames
     }
 }
 
@@ -163,12 +170,14 @@
         let windowCountsBySpace = countWindowsBySpace(windowItems)
         let appCountsBySpace = countAppsBySpace(snapshot.appGroups)
         let appNamesBySpace = appNamesBySpace(snapshot.appGroups)
+        let splitViewAppNamesBySpace = splitViewAppNamesBySpace(snapshot.appGroups)
         let displays = displayViewModels(
             from: snapshot.displays,
             currentSpaceIDs: currentSpaceIDs,
             windowCountsBySpace: windowCountsBySpace,
             appCountsBySpace: appCountsBySpace,
-            appNamesBySpace: appNamesBySpace
+            appNamesBySpace: appNamesBySpace,
+            splitViewAppNamesBySpace: splitViewAppNamesBySpace
         )
         let appShelf = appShelfViewModels(from: snapshot.appGroups)
 
@@ -197,7 +206,8 @@
             displays: displays,
             appShelf: appShelf,
             waterfallColumns: columns,
-            initialSelection: QuickSwitchFocusPolicy.initialSelection(in: snapshot)
+            initialSelection: QuickSwitchFocusPolicy.initialSelection(in: snapshot),
+            lockedSpaceID: nil
         )
     }
 
@@ -206,7 +216,8 @@
         currentSpaceIDs: Set<UInt64>,
         windowCountsBySpace: [UInt64: Int],
         appCountsBySpace: [UInt64: Int],
-        appNamesBySpace: [UInt64: [String]]
+        appNamesBySpace: [UInt64: [String]],
+        splitViewAppNamesBySpace: [UInt64: [String]]
     ) -> [QuickSwitchDisplayViewModel] {
         let sortedDisplays = displays
             .sorted { $0.uuid.localizedStandardCompare($1.uuid) == .orderedAscending }
@@ -228,7 +239,8 @@
                             isCurrent: currentSpaceIDs.contains(space.id),
                             windowCount: windowCountsBySpace[space.id] ?? 0,
                             appCount: appCountsBySpace[space.id] ?? 0,
-                            appNames: appNamesBySpace[space.id] ?? []
+                            appNames: appNamesBySpace[space.id] ?? [],
+                            splitViewAppNames: splitViewAppNamesBySpace[space.id] ?? []
                         )
                     }
 
@@ -305,6 +317,54 @@
         return namesBySpace
     }
 
+    private static func splitViewAppNamesBySpace(_ appGroups: [QuickSwitchAppGroup]) -> [UInt64: [String]] {
+        struct Candidate {
+            let appName: String
+            let windowID: UInt32
+            let frame: CGRect
+        }
+
+        var candidatesBySpace: [UInt64: [Candidate]] = [:]
+
+        for group in appGroups {
+            for item in group.windows {
+                guard item.window.isFullscreen,
+                      let primarySpaceID = item.primarySpaceID,
+                      let frame = item.window.frame,
+                      frame.width > 0,
+                      frame.height > 0
+                else {
+                    continue
+                }
+
+                candidatesBySpace[primarySpaceID, default: []].append(
+                    Candidate(
+                        appName: group.app.name,
+                        windowID: item.window.id,
+                        frame: frame
+                    )
+                )
+            }
+        }
+
+        return candidatesBySpace.compactMapValues { candidates in
+            guard candidates.count >= 2 else { return nil }
+
+            return candidates
+                .sorted { lhs, rhs in
+                    if lhs.frame.midX != rhs.frame.midX {
+                        return lhs.frame.midX < rhs.frame.midX
+                    }
+                    if lhs.frame.midY != rhs.frame.midY {
+                        return lhs.frame.midY < rhs.frame.midY
+                    }
+                    return lhs.windowID < rhs.windowID
+                }
+                .prefix(2)
+                .map(\.appName)
+        }
+    }
+
     private static func orderedUnique<T: Hashable>(_ values: [T]) -> [T] {
         var seen = Set<T>()
         var result: [T] = []
@@ -339,3 +399,148 @@
         return "\(displayLabel(for: displayIndex))\(space.index)"
     }
 }
+
+public enum QuickSwitchSpaceFilterPolicy {
+    public static func projectedViewModel(
+        from viewModel: QuickSwitchViewModel,
+        lockedSpaceID: UInt64?,
+        preferredSelection: QuickSwitchSelection? = nil
+    ) -> QuickSwitchViewModel {
+        guard let lockedSpaceID else {
+            return replacingInitialSelection(
+                in: viewModel,
+                lockedSpaceID: nil,
+                preferredSelection: preferredSelection
+            )
+        }
+
+        var nextGlobalIndex = 0
+        let columns = viewModel.waterfallColumns.compactMap { column -> QuickSwitchWaterfallColumnViewModel? in
+            let windows = column.windows.filter { $0.primarySpaceID == lockedSpaceID }
+            guard !windows.isEmpty else { return nil }
+
+            let reindexedWindows = windows.enumerated().map { windowIndex, card in
+                defer { nextGlobalIndex += 1 }
+                return QuickSwitchWindowCardViewModel(
+                    appGroupIndex: column.appGroupIndex,
+                    windowIndex: windowIndex,
+                    globalIndex: nextGlobalIndex,
+                    window: card.window,
+                    primarySpaceID: card.primarySpaceID,
+                    primarySpaceLabel: card.primarySpaceLabel
+                )
+            }
+
+            return QuickSwitchWaterfallColumnViewModel(
+                appGroupIndex: column.appGroupIndex,
+                app: column.app,
+                windows: reindexedWindows
+            )
+        }
+
+        let columnsByAppGroupIndex = Dictionary(uniqueKeysWithValues: columns.map { ($0.appGroupIndex, $0) })
+        let appShelf = viewModel.appShelf.compactMap { item -> QuickSwitchAppShelfItemViewModel? in
+            guard let column = columnsByAppGroupIndex[item.appGroupIndex] else { return nil }
+            return QuickSwitchAppShelfItemViewModel(
+                appGroupIndex: item.appGroupIndex,
+                app: item.app,
+                windowCount: column.windows.count,
+                primarySpaceIDs: orderedPrimarySpaceIDs(in: column.windows),
+                hasMinimizedWindows: column.windows.contains { $0.window.isMinimized },
+                hasFullscreenWindows: column.windows.contains { $0.window.isFullscreen }
+            )
+        }
+
+        return QuickSwitchViewModel(
+            displays: viewModel.displays,
+            appShelf: appShelf,
+            waterfallColumns: columns,
+            initialSelection: initialSelection(in: columns, preferredSelection: preferredSelection),
+            lockedSpaceID: lockedSpaceID
+        )
+    }
+
+    public static func singleFullscreenSelection(
+        inSpaceID spaceID: UInt64,
+        viewModel: QuickSwitchViewModel
+    ) -> QuickSwitchSelection? {
+        guard viewModel.displays
+            .flatMap(\.spaces)
+            .contains(where: { $0.id == spaceID && $0.type == .fullscreen })
+        else {
+            return nil
+        }
+
+        let cards = viewModel.waterfallColumns
+            .flatMap(\.windows)
+            .filter { $0.primarySpaceID == spaceID }
+
+        guard cards.count == 1,
+              let card = cards.first,
+              card.window.isFullscreen
+        else {
+            return nil
+        }
+
+        return QuickSwitchSelection(
+            appGroupIndex: card.appGroupIndex,
+            windowIndex: card.windowIndex,
+            windowID: card.window.id
+        )
+    }
+
+    private static func replacingInitialSelection(
+        in viewModel: QuickSwitchViewModel,
+        lockedSpaceID: UInt64?,
+        preferredSelection: QuickSwitchSelection?
+    ) -> QuickSwitchViewModel {
+        QuickSwitchViewModel(
+            displays: viewModel.displays,
+            appShelf: viewModel.appShelf,
+            waterfallColumns: viewModel.waterfallColumns,
+            initialSelection: initialSelection(in: viewModel.waterfallColumns, preferredSelection: preferredSelection)
+                ?? viewModel.initialSelection,
+            lockedSpaceID: lockedSpaceID
+        )
+    }
+
+    private static func initialSelection(
+        in columns: [QuickSwitchWaterfallColumnViewModel],
+        preferredSelection: QuickSwitchSelection?
+    ) -> QuickSwitchSelection? {
+        if let preferredSelection,
+           let matchingCard = columns
+            .flatMap(\.windows)
+            .first(where: { $0.window.id == preferredSelection.windowID }) {
+            return QuickSwitchSelection(
+                appGroupIndex: matchingCard.appGroupIndex,
+                windowIndex: matchingCard.windowIndex,
+                windowID: matchingCard.window.id
+            )
+        }
+
+        guard let firstCard = columns.first?.windows.first else { return nil }
+        return QuickSwitchSelection(
+            appGroupIndex: firstCard.appGroupIndex,
+            windowIndex: firstCard.windowIndex,
+            windowID: firstCard.window.id
+        )
+    }
+
+    private static func orderedPrimarySpaceIDs(in cards: [QuickSwitchWindowCardViewModel]) -> [UInt64] {
+        var seen = Set<UInt64>()
+        var result: [UInt64] = []
+
+        for card in cards {
+            guard let spaceID = card.primarySpaceID,
+                  !seen.contains(spaceID)
+            else {
+                continue
+            }
+            seen.insert(spaceID)
+            result.append(spaceID)
+        }
+
+        return result
+    }
+}

--
Gitblit v1.9.3