Apple的AppKit
提供了一个可以监控Mac应用的类——Scripting Bridge
,利用它可以监控Safari/Chrome/FireFox/Edge的浏览记录,也可以获得Finder的目录结构。
生成你需要监控的app的桥接头文件
以Safari为例,打开终端,输入以下内容:
sdef 你的Safari全路径 | sdp -fh --basename Safari
之后运行,即可得到Safari.h
,你可以在终端的当前目录下找到这个文件。
获取Safari的Bundle identifier
osascript -e 'id of app "Safari"'
App的Bundle identifier很重要,后续创建观察者需要用到。
创建观察者
获取Safari的Process identifier:
NSString *safariBundleID = @"com.apple.Safari";
pid_t safariPID;
for (NSRunningApplication *currApp in [[NSWorkspace sharedWorkspace] runningApplications])
{
if ([currApp.bundleIdentifier isEqualToString:safariBundleID]) {
safariPID = currApp.processIdentifier;
break;
}
}
利用获取到的Process identifier来创建观察者:
AXObserverRef observer;
if (AXObserverCreate ( pid, (AXObserverCallback)appObserverCallback, &observer) == kAXErrorSuccess) {
AXUIElementRef element = AXUIElementCreateApplication(pid);
AXError returnValue;
if ([appName.lowercaseString containsString:@"safari"]) {
returnValue = AXObserverAddNotification( observer, element,kAXFocusedUIElementChangedNotification, NULL);
} else {
returnValue = AXObserverAddNotification( observer, element,kAXTitleChangedNotification, NULL );
}
if (returnValue != kAXErrorSuccess) {
NSLog(@"Failed to create observer for application");
}
NSLog(@"AXError = %d",returnValue);
CFRunLoopAddSource(CFRunLoopGetMain(),
AXObserverGetRunLoopSource(observer), kCFRunLoopDefaultMode);
CFRelease(element);
[_obDict setObject:(__bridge id _Nonnull)(observer) forKey:@(pid)];
} else {
NSLog(@"Failed to create observer for application");
}
这段代码利用了纯C的函数,需要使用函数指针来作为回调函数,因此你需要额外声明并实现一个回调函数:
void appObserverCallback(AXObserverRef observer, AXUIElementRef element, CFStringRef notification, void * __nullable refcon) {
//自己决定怎样处理回调内容
}
修改相关权限
- 关闭app的SandBox模式
- Targets->Signing & Capabilities->Add Hardened Runtime选中Apple Events
- 在
info.plist
中添加字段Privacy - AppleEvents Sending Usage Description
或者NSAppleEventsUsageDescription
- 在系统的偏好设置->安全性与隐私->隐私->辅助功能中找到自己的工程,然后勾选上。
运行
这时候先打开Safari,然后启动工程,用Safari浏览网页时,回调函数appObserverCallback
就会收到提醒。
More
Scripting Bridge
能做的不仅于此,它可以获取日历,获取Finder的信息,甚至控制音乐播放等。
这里是我的Demo工程,可以在里面看到更多实现细节。