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/Aligner/Infrastructure/Windows/CGWindowAXWindowService.swift |  342 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 331 insertions(+), 11 deletions(-)

diff --git a/C1.source/Sources/Aligner/Infrastructure/Windows/CGWindowAXWindowService.swift b/C1.source/Sources/Aligner/Infrastructure/Windows/CGWindowAXWindowService.swift
index 87b6c01..b1b1cd3 100644
--- a/C1.source/Sources/Aligner/Infrastructure/Windows/CGWindowAXWindowService.swift
+++ b/C1.source/Sources/Aligner/Infrastructure/Windows/CGWindowAXWindowService.swift
@@ -162,9 +162,10 @@
         let privateActivationOutcome = activateViaPrivateWindowServerAPI(window)
         let didPrivatelyActivate = privateActivationOutcome?.succeeded == true
 
+        let currentAXMetadata = AXWindowMetadataReader.metadata(appCategorizer: appCategorizer)
         let axWindow = axWindow(
             for: window,
-            in: AXWindowMetadataReader.metadata(appCategorizer: appCategorizer),
+            in: currentAXMetadata,
             operation: "windowActivation",
             matchAttempt: "initial"
         )
@@ -188,6 +189,20 @@
             "spaceFocusError": spaceFocusOutcome?.error
         ])
         guard let axWindow else {
+            if let finderTabActivated = activateFinderTabPageIfNeeded(
+                window: window,
+                axMetadata: currentAXMetadata,
+                operation: "windowActivation",
+                reason: "axMissing"
+            ) {
+                DevelopmentDiagnostics.log("windowActivation.activate.finderTabResult", [
+                    "windowID": window.id,
+                    "pid": window.app.processIdentifier,
+                    "activated": finderTabActivated
+                ])
+                return finderTabActivated ? .activated : .activationFailed
+            }
+
             let activated = activateApplication(for: window)
             DevelopmentDiagnostics.log("windowActivation.activate.axMissing", [
                 "windowID": window.id,
@@ -393,15 +408,31 @@
             return nil
         }
 
-        guard let activationOutcome = activateViaPrivateWindowServerAPI(window),
-              activationOutcome.succeeded
-        else {
-            DevelopmentDiagnostics.log("windowClose.commandW.activationFailed", [
-                "windowID": window.id,
-                "pid": processIdentifier,
-                "reason": reason
-            ])
-            return nil
+        if let finderTabActivated = activateFinderTabPageIfNeeded(
+            window: window,
+            axMetadata: AXWindowMetadataReader.metadata(appCategorizer: appCategorizer),
+            operation: "windowClose",
+            reason: reason
+        ) {
+            guard finderTabActivated else {
+                DevelopmentDiagnostics.log("windowClose.commandW.finderTabActivationFailed", [
+                    "windowID": window.id,
+                    "pid": processIdentifier,
+                    "reason": reason
+                ])
+                return .failed("finderTabActivationFailed")
+            }
+        } else {
+            guard let activationOutcome = activateViaPrivateWindowServerAPI(window),
+                  activationOutcome.succeeded
+            else {
+                DevelopmentDiagnostics.log("windowClose.commandW.activationFailed", [
+                    "windowID": window.id,
+                    "pid": processIdentifier,
+                    "reason": reason
+                ])
+                return nil
+            }
         }
 
         Thread.sleep(forTimeInterval: 0.08)
@@ -564,6 +595,271 @@
                 && metadata.subrole == .standard
         }
         return finderCandidates.count == 1 ? finderCandidates[0] : nil
+    }
+
+    private func activateFinderTabPageIfNeeded(
+        window: AlignerWindow,
+        axMetadata: [AXWindowMetadata],
+        operation: String,
+        reason: String
+    ) -> Bool? {
+        guard isFinderCGOnlyPageCandidate(window),
+              let processIdentifier = window.app.processIdentifier
+        else {
+            return nil
+        }
+
+        guard let host = finderTabHostAXWindow(for: window, in: axMetadata) else {
+            DevelopmentDiagnostics.log("\(operation).finderTab.hostMissing", [
+                "windowID": window.id,
+                "pid": processIdentifier,
+                "reason": reason,
+                "spaceIDs": window.spaceIDs
+            ])
+            return false
+        }
+
+        let hostPrivateActivationSucceeded: Bool
+        if let hostWindowID = host.windowID,
+           let privateActivationBridge {
+            hostPrivateActivationSucceeded = privateActivationBridge.activate(
+                processIdentifier: processIdentifier,
+                windowID: hostWindowID
+            ).succeeded
+        } else {
+            hostPrivateActivationSucceeded = false
+        }
+
+        let applicationActivated = activateApplication(processIdentifier: processIdentifier)
+        let focusBeforeResult = focusWindowResult(host.element, processIdentifier: processIdentifier)
+        let raiseBeforeResult = raiseWindowResult(host.element)
+        let selectedTab = selectFinderTab(window, in: host)
+        Thread.sleep(forTimeInterval: 0.05)
+        let focusAfterResult = focusWindowResult(host.element, processIdentifier: processIdentifier)
+        let raiseAfterResult = raiseWindowResult(host.element)
+
+        let didFocusOrRaise = focusBeforeResult.success
+            || raiseBeforeResult.success
+            || focusAfterResult.success
+            || raiseAfterResult.success
+        let activated = selectedTab
+            && (hostPrivateActivationSucceeded || applicationActivated || didFocusOrRaise)
+
+        DevelopmentDiagnostics.log("\(operation).finderTab.result", [
+            "windowID": window.id,
+            "pid": processIdentifier,
+            "reason": reason,
+            "hostWindowID": host.windowID,
+            "hostTitleHash": DevelopmentDiagnostics.stableFingerprint(host.title),
+            "hostTitleLength": host.title?.count,
+            "hostPrivateActivated": hostPrivateActivationSucceeded,
+            "applicationActivated": applicationActivated,
+            "selectedTab": selectedTab,
+            "focusBeforeAXError": String(describing: focusBeforeResult.error),
+            "raiseBeforeAXError": String(describing: raiseBeforeResult.error),
+            "focusAfterAXError": String(describing: focusAfterResult.error),
+            "raiseAfterAXError": String(describing: raiseAfterResult.error),
+            "activated": activated
+        ])
+
+        return activated
+    }
+
+    private func isFinderCGOnlyPageCandidate(_ window: AlignerWindow) -> Bool {
+        window.identifierSource == .cgWindow
+            && window.app.bundleIdentifier == "com.apple.finder"
+            && !window.title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
+    }
+
+    private func finderTabHostAXWindow(
+        for window: AlignerWindow,
+        in axMetadata: [AXWindowMetadata]
+    ) -> AXWindowMetadata? {
+        guard let processIdentifier = window.app.processIdentifier else { return nil }
+        let candidates = axMetadata.filter { metadata in
+            metadata.processIdentifier == processIdentifier
+                && metadata.subrole == .standard
+                && metadata.frame?.isEmpty == false
+        }
+        guard !candidates.isEmpty else { return nil }
+
+        let targetSpaceIDs = Set(window.spaceIDs)
+        let candidateIDs = candidates.compactMap(\.windowID)
+        let candidateSpacesByID = targetSpaceIDs.isEmpty ? [:] : spaceIDsByWindowIDProvider(candidateIDs)
+        let spaceCompatibleCandidates = targetSpaceIDs.isEmpty
+            ? candidates
+            : candidates.filter { metadata in
+                guard let windowID = metadata.windowID else { return false }
+                return !Set(candidateSpacesByID[windowID] ?? []).isDisjoint(with: targetSpaceIDs)
+            }
+        let hostCandidates = spaceCompatibleCandidates.isEmpty ? candidates : spaceCompatibleCandidates
+        let targetFrame = cgWindowFrame(for: window) ?? window.frame
+        let scoredHosts = hostCandidates.compactMap { metadata -> (metadata: AXWindowMetadata, score: CGFloat)? in
+            guard let score = frameOverlapScore(targetFrame, metadata.frame),
+                  score >= 0.85
+            else {
+                return nil
+            }
+
+            return (metadata, score)
+        }
+        .sorted { lhs, rhs in
+            if lhs.score != rhs.score {
+                return lhs.score > rhs.score
+            }
+            return (lhs.metadata.windowID ?? UInt32.max) < (rhs.metadata.windowID ?? UInt32.max)
+        }
+
+        if let best = scoredHosts.first {
+            if scoredHosts.count == 1 {
+                return best.metadata
+            }
+
+            let secondBest = scoredHosts[1]
+            if best.score - secondBest.score >= 0.05 {
+                return best.metadata
+            }
+        }
+
+        return hostCandidates.count == 1 ? hostCandidates[0] : nil
+    }
+
+    private func selectFinderTab(
+        _ window: AlignerWindow,
+        in host: AXWindowMetadata
+    ) -> Bool {
+        let normalizedTargetTitle = normalizedWindowTitle(window.title)
+        guard !normalizedTargetTitle.isEmpty else { return false }
+
+        if normalizedWindowTitle(host.title ?? "") == normalizedTargetTitle {
+            return true
+        }
+
+        guard let tabButton = finderTabButton(
+            matchingNormalizedTitle: normalizedTargetTitle,
+            in: host.element
+        ) else {
+            DevelopmentDiagnostics.log("windowActivation.finderTab.tabButtonMissing", [
+                "windowID": window.id,
+                "hostWindowID": host.windowID,
+                "targetTitleHash": DevelopmentDiagnostics.stableFingerprint(window.title),
+                "targetTitleLength": window.title.count
+            ])
+            return false
+        }
+
+        let tabButtonRole = axStringAttribute(kAXRoleAttribute as String, for: tabButton)
+        let tabButtonSubrole = axStringAttribute(kAXSubroleAttribute as String, for: tabButton)
+        let pressResult = AXUIElementPerformAction(tabButton, kAXPressAction as CFString)
+        DevelopmentDiagnostics.log("windowActivation.finderTab.pressTab", [
+            "windowID": window.id,
+            "hostWindowID": host.windowID,
+            "tabButtonRole": tabButtonRole,
+            "tabButtonSubrole": tabButtonSubrole,
+            "axError": String(describing: pressResult)
+        ])
+        guard pressResult == .success else { return false }
+
+        for _ in 0..<10 {
+            Thread.sleep(forTimeInterval: 0.04)
+            let selectedTitle = normalizedWindowTitle(
+                axStringAttribute(kAXTitleAttribute as String, for: host.element) ?? ""
+            )
+            if selectedTitle == normalizedTargetTitle {
+                return true
+            }
+        }
+
+        let currentHostTitle = axStringAttribute(kAXTitleAttribute as String, for: host.element) ?? ""
+        DevelopmentDiagnostics.log("windowActivation.finderTab.selectionVerificationFailed", [
+            "windowID": window.id,
+            "hostWindowID": host.windowID,
+            "targetTitleHash": DevelopmentDiagnostics.stableFingerprint(window.title),
+            "targetTitleLength": window.title.count,
+            "currentHostTitleHash": DevelopmentDiagnostics.stableFingerprint(currentHostTitle),
+            "currentHostTitleLength": currentHostTitle.count
+        ])
+        return false
+    }
+
+    private func finderTabButton(
+        matchingNormalizedTitle targetTitle: String,
+        in root: AXUIElement
+    ) -> AXUIElement? {
+        var visitedCount = 0
+
+        func search(_ element: AXUIElement, depth: Int, insideTabGroup: Bool) -> AXUIElement? {
+            visitedCount += 1
+            guard visitedCount <= 240, depth <= 9 else { return nil }
+
+            let role = axStringAttribute(kAXRoleAttribute as String, for: element)
+            let subrole = axStringAttribute(kAXSubroleAttribute as String, for: element)
+            let title = axStringAttribute(kAXTitleAttribute as String, for: element)
+                ?? axStringAttribute(kAXDescriptionAttribute as String, for: element)
+                ?? axStringAttribute(kAXValueAttribute as String, for: element)
+            let isTabGroup = role == "AXTabGroup"
+            let isTabControl = subrole == "AXTabButton"
+                || (insideTabGroup && (role == kAXRadioButtonRole as String || role == kAXButtonRole as String))
+
+            if normalizedWindowTitle(title ?? "") == targetTitle,
+               isTabControl {
+                return element
+            }
+
+            guard isTabGroup || insideTabGroup || depth < 7 else { return nil }
+            for child in axChildren(of: element) {
+                if let match = search(child, depth: depth + 1, insideTabGroup: insideTabGroup || isTabGroup) {
+                    return match
+                }
+            }
+
+            return nil
+        }
+
+        return search(root, depth: 0, insideTabGroup: false)
+    }
+
+    private func axChildren(of element: AXUIElement) -> [AXUIElement] {
+        var value: CFTypeRef?
+        guard AXUIElementCopyAttributeValue(element, kAXChildrenAttribute as CFString, &value) == .success,
+              let children = value as? [AXUIElement]
+        else {
+            return []
+        }
+
+        return children
+    }
+
+    private func axStringAttribute(_ attribute: String, for element: AXUIElement) -> String? {
+        var value: CFTypeRef?
+        guard AXUIElementCopyAttributeValue(element, attribute as CFString, &value) == .success,
+              let value
+        else {
+            return nil
+        }
+
+        return value as? String
+    }
+
+    private func frameOverlapScore(_ lhs: CGRect?, _ rhs: CGRect?) -> CGFloat? {
+        guard let lhs,
+              let rhs,
+              !lhs.isEmpty,
+              !rhs.isEmpty
+        else {
+            return nil
+        }
+
+        let intersection = lhs.intersection(rhs)
+        guard !intersection.isNull,
+              !intersection.isEmpty
+        else {
+            return nil
+        }
+
+        let denominator = min(lhs.width * lhs.height, rhs.width * rhs.height)
+        guard denominator > 0 else { return nil }
+        return (intersection.width * intersection.height) / denominator
     }
 
     private func cgWindowFingerprint(for window: AlignerWindow) -> AXWindowFingerprint? {
@@ -895,7 +1191,31 @@
         }
 
         restorableAXWindowsByID = nextRestorableAXWindowsByID
-        return cgRecords + axOnlyRecords
+        let records = cgRecords + axOnlyRecords
+        let attributedRecords = FinderTabSpaceAttributionPolicy.attributedRecords(records)
+        DevelopmentDiagnostics.log("windowEnumeration.finderTabSpaceAttribution", [
+            "finderNoSpaceCandidateCount": records.filter { record in
+                record.app.bundleIdentifier == "com.apple.finder"
+                    && record.identifierSource == .cgWindow
+                    && record.spaceIDs.isEmpty
+                    && !record.title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
+                    && record.frame?.isEmpty == false
+            }.count,
+            "finderFullscreenHostCount": records.filter { record in
+                record.app.bundleIdentifier == "com.apple.finder"
+                    && record.isFullscreen
+                    && !record.spaceIDs.isEmpty
+                    && record.subrole == .standard
+                    && record.frame?.isEmpty == false
+            }.count,
+            "attributedCount": zip(records, attributedRecords).filter { before, after in
+                before.spaceIDs.isEmpty && !after.spaceIDs.isEmpty
+            }.count,
+            "attributedWindowIDs": zip(records, attributedRecords).compactMap { before, after in
+                before.spaceIDs.isEmpty && !after.spaceIDs.isEmpty ? after.id : nil
+            }
+        ])
+        return attributedRecords
     }
 
     private func app(

--
Gitblit v1.9.3