Skip to content

Commit d4280dd

Browse files
committed
fix(dock-engine): improve window activation to target the correct window
Old logic matched only by X coordinate with 3px tolerance, which fails for multi-window apps like IntelliJ IDEA where windows have distinct titles but may share similar X positions. New two-pass matching strategy: - Pass 1: exact AX title match against the CGWindowName of the target window (covers IDEA project windows, Chrome tabs, etc.) - Pass 2: position match using both X and Y within 8pt tolerance as fallback for untitled or same-title windows Also resolve the owning PID from CGWindowInfo so that multi-process apps activate the correct process instance.
1 parent ab03e63 commit d4280dd

1 file changed

Lines changed: 38 additions & 9 deletions

File tree

DockMasterPro/Services/WindowPreviewService.swift

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -82,27 +82,56 @@ final class WindowPreviewService {
8282

8383
// MARK: - Activation
8484

85-
/// 激活应用并尝试将指定窗口置前(需在主线程调用)
85+
/// 激活应用并将指定窗口置前(需在主线程调用)
86+
///
87+
/// 匹配策略(优先级递减):
88+
/// 1. 窗口标题完全匹配(适用 IDEA、Chrome 等多窗口标题各异的应用)
89+
/// 2. 位置匹配:X 和 Y 坐标均在 8pt 以内(兜底,标题为空时使用)
8690
@MainActor
8791
func activateWindow(windowID: CGWindowID, bundleIdentifier: String) {
88-
guard let app = NSRunningApplication
89-
.runningApplications(withBundleIdentifier: bundleIdentifier).first
92+
let apps = NSRunningApplication.runningApplications(withBundleIdentifier: bundleIdentifier)
93+
guard !apps.isEmpty else { return }
94+
95+
// 获取目标窗口的 CGInfo(标题 + 位置)
96+
guard let list = CGWindowListCopyWindowInfo([.optionIncludingWindow], windowID)
97+
as? [[CFString: Any]],
98+
let cgInfo = list.first
9099
else { return }
91-
app.activate(options: .activateIgnoringOtherApps)
92100

93-
// 通过 AX 将与 windowID 位置匹配的窗口 raise 到最前
94-
let axApp = AXUIElementCreateApplication(app.processIdentifier)
101+
let cgTitle = cgInfo[kCGWindowName as CFString] as? String ?? ""
102+
let targetFrame = cgFrame(for: windowID)
103+
104+
// 找到拥有该窗口的进程(多进程应用兼容)
105+
let targetPID = cgInfo[kCGWindowOwnerPID as CFString] as? Int32
106+
let targetApp = apps.first(where: { $0.processIdentifier == targetPID }) ?? apps[0]
107+
108+
targetApp.activate(options: .activateIgnoringOtherApps)
109+
110+
let axApp = AXUIElementCreateApplication(targetApp.processIdentifier)
95111
var windowsRef: CFTypeRef?
96112
guard AXUIElementCopyAttributeValue(axApp, kAXWindowsAttribute as CFString, &windowsRef) == .success,
97113
let axWindows = windowsRef as? [AXUIElement]
98114
else { return }
99115

100-
let targetFrame = cgFrame(for: windowID)
116+
// Pass 1:标题精确匹配(多窗口应用首选)
117+
if !cgTitle.isEmpty {
118+
for axWin in axWindows {
119+
var titleRef: CFTypeRef?
120+
if AXUIElementCopyAttributeValue(axWin, kAXTitleAttribute as CFString, &titleRef) == .success,
121+
let axTitle = titleRef as? String, axTitle == cgTitle {
122+
AXUIElementPerformAction(axWin, kAXRaiseAction as CFString)
123+
return
124+
}
125+
}
126+
}
127+
128+
// Pass 2:位置匹配(X 和 Y 均在 8pt 以内)
101129
for axWin in axWindows {
102130
if let pos = axPoint(axWin, kAXPositionAttribute as CFString),
103-
abs(pos.x - targetFrame.minX) < 3 {
131+
abs(pos.x - targetFrame.minX) < 8,
132+
abs(pos.y - targetFrame.minY) < 8 {
104133
AXUIElementPerformAction(axWin, kAXRaiseAction as CFString)
105-
break
134+
return
106135
}
107136
}
108137
}

0 commit comments

Comments
 (0)