From 6983a4ed2137bf1a8377e5d1cf82183e02e617f8 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Sat, 13 Jun 2026 14:08:17 +0800
Subject: [PATCH] Fix multi-page targeting and split view tile QA

---
 C1.source/Sources/Aligner/Infrastructure/Windows/CGWindowAXWindowService.swift |  197 ++++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 178 insertions(+), 19 deletions(-)

diff --git a/C1.source/Sources/Aligner/Infrastructure/Windows/CGWindowAXWindowService.swift b/C1.source/Sources/Aligner/Infrastructure/Windows/CGWindowAXWindowService.swift
index 55b5f44..87b6c01 100644
--- a/C1.source/Sources/Aligner/Infrastructure/Windows/CGWindowAXWindowService.swift
+++ b/C1.source/Sources/Aligner/Infrastructure/Windows/CGWindowAXWindowService.swift
@@ -162,16 +162,12 @@
         let privateActivationOutcome = activateViaPrivateWindowServerAPI(window)
         let didPrivatelyActivate = privateActivationOutcome?.succeeded == true
 
-        let axWindow = AXWindowMetadataReader
-            .metadata(appCategorizer: appCategorizer)
-            .first { metadata in
-                if let windowID = metadata.windowID {
-                    return windowID == window.id
-                }
-
-                return metadata.processIdentifier == window.app.processIdentifier
-                    && metadata.title == window.title
-            }
+        let axWindow = axWindow(
+            for: window,
+            in: AXWindowMetadataReader.metadata(appCategorizer: appCategorizer),
+            operation: "windowActivation",
+            matchAttempt: "initial"
+        )
 
         DevelopmentDiagnostics.log("windowActivation.activate.axLookup", [
             "windowID": window.id,
@@ -273,6 +269,15 @@
         var matchedAXWindow = axWindow(for: window, in: axMetadata)
         var finalAXMetadata = axMetadata
 
+        let shouldPreferFocusedPageClose = matchedAXWindow?.windowID != window.id
+        if shouldPreferFocusedPageClose,
+           let focusedPageCloseResult = closeFocusedPageViaCommandW(
+            window: window,
+            reason: matchedAXWindow == nil ? "axMissing" : "nonDirectAXMatch"
+           ) {
+            return focusedPageCloseResult
+        }
+
         if matchedAXWindow == nil {
             Thread.sleep(forTimeInterval: 0.12)
             finalAXMetadata = AXWindowMetadataReader.metadata(appCategorizer: appCategorizer)
@@ -289,6 +294,14 @@
                 "matchingSizeCandidateCount": matchingAXSizeCandidateCount(for: window, in: finalAXMetadata)
             ])
             return .windowNotFound
+        }
+
+        if matchedAXWindow.windowID != window.id,
+           let focusedPageCloseResult = closeFocusedPageViaCommandW(
+            window: window,
+            reason: "retryNonDirectAXMatch"
+           ) {
+            return focusedPageCloseResult
         }
 
         guard let closeButton = closeButton(for: matchedAXWindow) else {
@@ -364,13 +377,82 @@
         }
     }
 
+    private func closeFocusedPageViaCommandW(
+        window: AlignerWindow,
+        reason: String
+    ) -> WindowCloseResult? {
+        guard window.identifierSource == .cgWindow,
+              let processIdentifier = window.app.processIdentifier
+        else {
+            DevelopmentDiagnostics.log("windowClose.commandW.skipped", [
+                "windowID": window.id,
+                "reason": reason,
+                "identifierSource": String(describing: window.identifierSource),
+                "hasPID": window.app.processIdentifier != nil
+            ])
+            return nil
+        }
+
+        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)
+        guard postCommandW(to: processIdentifier) else {
+            DevelopmentDiagnostics.log("windowClose.commandW.postFailed", [
+                "windowID": window.id,
+                "pid": processIdentifier,
+                "reason": reason
+            ])
+            return .failed("commandWPostFailed")
+        }
+
+        DevelopmentDiagnostics.log("windowClose.commandW.requested", [
+            "windowID": window.id,
+            "pid": processIdentifier,
+            "reason": reason
+        ])
+        return .requested
+    }
+
+    private func postCommandW(to processIdentifier: Int32) -> Bool {
+        let source = CGEventSource(stateID: .combinedSessionState)
+        guard let keyDown = CGEvent(
+            keyboardEventSource: source,
+            virtualKey: Self.commandWVirtualKeyCode,
+            keyDown: true
+        ),
+              let keyUp = CGEvent(
+                keyboardEventSource: source,
+                virtualKey: Self.commandWVirtualKeyCode,
+                keyDown: false
+              )
+        else {
+            return false
+        }
+
+        keyDown.flags = .maskCommand
+        keyUp.flags = .maskCommand
+        keyDown.postToPid(processIdentifier)
+        keyUp.postToPid(processIdentifier)
+        return true
+    }
+
     private func axWindow(
         for window: AlignerWindow,
         in axMetadata: [AXWindowMetadata],
+        operation: String = "windowClose",
         matchAttempt: String = "initial"
     ) -> AXWindowMetadata? {
         if let directMatch = axMetadata.first(where: { $0.windowID == window.id }) {
-            DevelopmentDiagnostics.log("windowClose.axMatch.directWindowID", [
+            DevelopmentDiagnostics.log("\(operation).axMatch.directWindowID", [
                 "windowID": window.id,
                 "pid": window.app.processIdentifier,
                 "attempt": matchAttempt
@@ -382,12 +464,35 @@
             return nil
         }
 
+        if let geometryFingerprint = axWindowGeometryFingerprint(for: window) {
+            let geometryMatches = axMetadata.filter { metadata in
+                AXWindowGeometryFingerprint(metadata) == geometryFingerprint
+            }
+            if geometryMatches.count == 1 {
+                DevelopmentDiagnostics.log("\(operation).axMatch.geometryFingerprint", [
+                    "windowID": window.id,
+                    "pid": processIdentifier,
+                    "attempt": matchAttempt
+                ])
+                return geometryMatches[0]
+            }
+
+            if geometryMatches.count > 1 {
+                DevelopmentDiagnostics.log("\(operation).axMatch.geometryFingerprintAmbiguous", [
+                    "windowID": window.id,
+                    "pid": processIdentifier,
+                    "candidateCount": geometryMatches.count,
+                    "attempt": matchAttempt
+                ])
+            }
+        }
+
         if let cgFingerprint = cgWindowFingerprint(for: window) {
             let fingerprintMatches = axMetadata.filter { metadata in
                 AXWindowFingerprint(metadata) == cgFingerprint
             }
             if fingerprintMatches.count == 1 {
-                DevelopmentDiagnostics.log("windowClose.axMatch.fingerprint", [
+                DevelopmentDiagnostics.log("\(operation).axMatch.fingerprint", [
                     "windowID": window.id,
                     "pid": processIdentifier,
                     "attempt": matchAttempt
@@ -396,10 +501,11 @@
             }
 
             if fingerprintMatches.count > 1 {
-                DevelopmentDiagnostics.log("windowClose.axMatch.fingerprintAmbiguous", [
+                DevelopmentDiagnostics.log("\(operation).axMatch.fingerprintAmbiguous", [
                     "windowID": window.id,
                     "pid": processIdentifier,
-                    "candidateCount": fingerprintMatches.count
+                    "candidateCount": fingerprintMatches.count,
+                    "attempt": matchAttempt
                 ])
             }
         }
@@ -409,8 +515,8 @@
             metadata.processIdentifier == processIdentifier
                 && normalizedWindowTitle(metadata.title ?? "") == normalizedTitle
         }
-        if titleMatches.count == 1 {
-            DevelopmentDiagnostics.log("windowClose.axMatch.normalizedTitle", [
+        if !normalizedTitle.isEmpty, titleMatches.count == 1 {
+            DevelopmentDiagnostics.log("\(operation).axMatch.normalizedTitle", [
                 "windowID": window.id,
                 "pid": processIdentifier,
                 "attempt": matchAttempt
@@ -424,7 +530,7 @@
             processIdentifier: processIdentifier,
             in: axMetadata
            ) {
-            DevelopmentDiagnostics.log("windowClose.axMatch.finderFallback", [
+            DevelopmentDiagnostics.log("\(operation).axMatch.finderFallback", [
                 "windowID": window.id,
                 "pid": processIdentifier,
                 "attempt": matchAttempt
@@ -433,10 +539,11 @@
         }
 
         if titleMatches.count > 1 {
-            DevelopmentDiagnostics.log("windowClose.axMatch.titleAmbiguous", [
+            DevelopmentDiagnostics.log("\(operation).axMatch.titleAmbiguous", [
                 "windowID": window.id,
                 "pid": processIdentifier,
-                "candidateCount": titleMatches.count
+                "candidateCount": titleMatches.count,
+                "attempt": matchAttempt
             ])
         }
 
@@ -475,6 +582,29 @@
             title: normalizedWindowTitle(title(rawWindow: rawWindow, axWindow: nil)),
             size: bounds(rawWindow[kCGWindowBounds as String]).size
         )
+    }
+
+    private func axWindowGeometryFingerprint(for window: AlignerWindow) -> AXWindowGeometryFingerprint? {
+        guard let processIdentifier = window.app.processIdentifier else { return nil }
+        let frame = cgWindowFrame(for: window) ?? window.frame
+        return AXWindowGeometryFingerprint(
+            processIdentifier: processIdentifier,
+            title: normalizedWindowTitle(window.title),
+            frame: frame
+        )
+    }
+
+    private func cgWindowFrame(for window: AlignerWindow) -> CGRect? {
+        guard let rawWindows = CGWindowListCopyWindowInfo(
+            [.optionIncludingWindow],
+            CGWindowID(window.id)
+        ) as? [[String: Any]],
+              let rawWindow = rawWindows.first
+        else {
+            return nil
+        }
+
+        return bounds(rawWindow[kCGWindowBounds as String])
     }
 
     private func matchingAXTitleCandidateCount(
@@ -984,6 +1114,7 @@
         }
     }
 
+    private static let commandWVirtualKeyCode: CGKeyCode = 13
 }
 
 private struct AXWindowMetadata {
@@ -1053,6 +1184,34 @@
     }
 }
 
+private struct AXWindowGeometryFingerprint: Hashable {
+    let processIdentifier: Int32
+    let title: String
+    let x: Int
+    let y: Int
+    let width: Int
+    let height: Int
+
+    init?(processIdentifier: Int32, title: String, frame: CGRect?) {
+        guard let frame else { return nil }
+
+        self.processIdentifier = processIdentifier
+        self.title = title
+        self.x = Int(frame.origin.x.rounded())
+        self.y = Int(frame.origin.y.rounded())
+        self.width = Int(frame.size.width.rounded())
+        self.height = Int(frame.size.height.rounded())
+    }
+
+    init?(_ metadata: AXWindowMetadata) {
+        self.init(
+            processIdentifier: metadata.processIdentifier,
+            title: normalizedWindowTitle(metadata.title ?? ""),
+            frame: metadata.frame
+        )
+    }
+}
+
 private struct RestorableAXWindow {
     let processIdentifier: Int32
     let element: AXUIElement

--
Gitblit v1.9.3