Mac获取活动窗口PID、窗口ID、进程名及窗口名的统一方案问询
统一获取Mac活动窗口四项属性的解决方案
问题背景
开发Go应用时需定期获取Mac活动窗口的PID、进程名、窗口ID(WindowNumber)、窗口名,涉及Mac生态的FFI调用。现有两种实现均存在信息缺失:
- AppleScript:多数场景无法获取WindowNumber
- Objective-C(基于CoreGraphics):多数场景无法获取WindowName
需要一种能同时获取全部四项属性的统一方案,而非拼接两种现有方法。
现有实现问题
AppleScript实现(缺失WindowNumber)
global activeApp, activePID, activeName, windowName set windowName to "" tell application "System Events" set activeApp to first application process whose frontmost is true set activePID to unix id of activeApp set activeName to name of activeApp tell process activeName try tell (1st window whose value of attribute "AXMain" is true) set windowName to value of attribute "AXTitle" end tell end try end tell end tell return ("{\"pid\":" & activePID & ",\"name\":\"" & activeName & "\",\"window\":\"" & windowName & "\"}" as text)
Objective-C实现(缺失WindowName)
#include <Cocoa/Cocoa.h> #include <CoreGraphics/CGWindow.h> struct details { int wid; int pid; const char* name; const char* window; }; int activeWindow(struct details *d) { if (d == NULL) { return 0; } NSArray *windows = (NSArray *)CGWindowListCopyWindowInfo(kCGWindowListExcludeDesktopElements|kCGWindowListOptionOnScreenOnly,kCGNullWindowID); for(NSDictionary *window in windows){ int WindowLayer = [[window objectForKey:(NSString *)kCGWindowLayer] intValue]; if (WindowLayer == 0) { d->wid = [[window objectForKey:(NSString *)kCGWindowNumber] intValue]; d->pid = [[window objectForKey:(NSString *)kCGWindowOwnerPID] intValue]; d->name = [[window objectForKey:(NSString *)kCGWindowOwnerName] UTF8String]; d->window = [[window objectForKey:(NSString *)kCGWindowName] UTF8String]; return 1; } } return 0; }
统一解决方案(Objective-C结合Accessibility API)
通过Accessibility API(AXUIElement)可一次性获取全部四项属性,无需拼接两种方法:
#include <Cocoa/Cocoa.h> #include <ApplicationServices/ApplicationServices.h> struct WindowDetails { pid_t pid; const char* processName; CGWindowID windowID; const char* windowName; }; // 释放字符串资源的辅助函数 void freeWindowDetails(struct WindowDetails *details) { if (details->processName) free((void*)details->processName); if (details->windowName) free((void*)details->windowName); memset(details, 0, sizeof(struct WindowDetails)); } int getActiveWindowDetails(struct WindowDetails *details) { if (!details) return 0; memset(details, 0, sizeof(struct WindowDetails)); // 获取前台应用的AXUIElement实例 AXUIElementRef appElement = AXUIElementCreateApplication(AXUIElementGetFocusedApplication()); if (!appElement) return 0; // 获取应用PID pid_t pid; AXError pidErr = AXUIElementGetAttributeValue(appElement, kAXPIDAttribute, (CFTypeRef *)&pid); if (pidErr != kAXErrorSuccess) { CFRelease(appElement); return 0; } details->pid = pid; // 获取应用名称 CFStringRef processNameRef; AXError nameErr = AXUIElementGetAttributeValue(appElement, kAXTitleAttribute, (CFTypeRef *)&processNameRef); if (nameErr == kAXErrorSuccess) { details->processName = strdup(CFStringGetCStringPtr(processNameRef, kCFStringEncodingUTF8)); CFRelease(processNameRef); } // 获取主窗口元素 AXUIElementRef mainWindow; AXError windowErr = AXUIElementGetAttributeValue(appElement, kAXMainWindowAttribute, (CFTypeRef *)&mainWindow); if (windowErr != kAXErrorSuccess) { CFRelease(appElement); return 0; } // 获取窗口ID(WindowNumber) CGWindowID windowID; AXError widErr = AXUIElementGetAttributeValue(mainWindow, kAXWindowNumberAttribute, (CFTypeRef *)&windowID); if (widErr == kAXErrorSuccess) { details->windowID = windowID; } // 获取窗口名称 CFStringRef windowNameRef; AXError winNameErr = AXUIElementGetAttributeValue(mainWindow, kAXTitleAttribute, (CFTypeRef *)&windowNameRef); if (winNameErr == kAXErrorSuccess) { details->windowName = strdup(CFStringGetCStringPtr(windowNameRef, kCFStringEncodingUTF8)); CFRelease(windowNameRef); } // 释放资源 CFRelease(mainWindow); CFRelease(appElement); // 验证核心属性是否获取成功 return (details->pid != 0 && details->windowID != 0) ? 1 : 0; }
方案说明
- 权限要求:需在应用的
Info.plist中添加NSAccessibilityUsageDescription权限说明,否则系统会拒绝Accessibility API访问 - 属性获取逻辑:
- 通过
kAXPIDAttribute获取进程PID - 通过
kAXTitleAttribute分别获取进程名与窗口名 - 通过
kAXWindowNumberAttribute获取窗口ID(对应AppleScript中的WindowNumber)
- 通过
- 资源管理:提供
freeWindowDetails函数释放C侧分配的字符串内存,避免泄漏 - Go调用适配:可通过
cgo直接调用该代码,编译时需链接ApplicationServices框架,同时注意Go与C字符串的转换逻辑
内容的提问来源于stack exchange,提问作者kortschak
相关产品推荐
相关产品推荐

