React Native开发macOS应用:无法监听应用切换事件该如何解决?
问题描述
我正在使用React Native开发一款macOS应用,希望检测用户切换应用的时机并捕获截图。编写了如下测试代码:
import Foundation import AppKit @objc(ScreenShotModule) public class ScreenShotModule: NSObject, RCTBridgeModule { public static func moduleName() -> String! { return "ScreenShotModule" } var count:Int = 0 override init() { super.init() print("called init") NotificationCenter.default.addObserver( self, selector: #selector(appDidBecomeActive), name: NSWorkspace.didActivateApplicationNotification, object: nil) } @objc func appDidBecomeActive(notification: NSNotification) { print("got notified") let displayId = CGMainDisplayID() guard let image = CGDisplayCreateImage(displayId) else { return } let bitmapRep = NSBitmapImageRep(cgImage: image) guard let imageData = bitmapRep.representation(using: .png, properties: [:]) else { return } let fileManager = FileManager.default let directoryURLs = fileManager.urls(for: .picturesDirectory, in: .userDomainMask) if let documentDirectory = directoryURLs.first { let filePath = documentDirectory.appendingPathComponent("screenshot_\(count).png") do { try imageData.write(to: filePath) count += 1 print("Screenshot saved to: \(filePath)") } catch { print("Error saving screenshot: \(error)") } } } @objc func triggerAppDidBecomeActive() { let dummyNotification = NSNotification(name: NSNotification.Name(""), object: nil) self.appDidBecomeActive(notification: dummyNotification) } @objc func postNotification() { print("posted!") NotificationCenter.default.post(name: NSWorkspace.didActivateApplicationNotification, object: "foo", userInfo: ["key": "Value"]) } @objc public static func requiresMainQueueSetup() -> Bool { return true } }
手动调用triggerAppDidBecomeActive方法或通过postNotification发送模拟通知时,都能正常完成截图操作,但实际运行应用过程中切换其他应用时,无法触发NSWorkspace.didActivateApplicationNotification通知。已尝试开启或关闭应用沙箱,也使用过DistributedNotificationCenter,均无效果。请问该如何检测用户切换应用的事件?
解决方案
1. 切换到NSWorkspace专属通知中心监听
NSWorkspace.didActivateApplicationNotification不属于系统默认的NotificationCenter.default,而是归属NSWorkspace自己的通知中心。修改观察者注册代码:
override init() { super.init() print("called init") // 使用NSWorkspace的通知中心注册观察者 NSWorkspace.shared.notificationCenter.addObserver( self, selector: #selector(appDidBecomeActive), name: NSWorkspace.didActivateApplicationNotification, object: nil) }
2. 沙箱环境下的权限配置
如果开启了应用沙箱:
- 必须在
Info.plist中添加屏幕录制权限说明:<key>NSScreenRecordingUsageDescription</key> <string>需要屏幕录制权限以捕获切换应用时的截图</string> - 访问
Pictures目录需要开启User Selected File或Downloads Folder权限,或者直接将截图存储到应用容器内的目录(比如Application Support),避免权限问题。
3. 备选方案:用CGEventTap监听切换事件
如果通知监听仍不生效,可以通过监听键盘事件(比如Cmd+Tab)来捕获应用切换动作,这种方式需要应用获得辅助功能权限(系统偏好设置 → 安全性与隐私 → 隐私 → 辅助功能,添加你的应用):
override init() { super.init() print("called init") setupEventTap() } private func setupEventTap() { guard let eventTap = CGEvent.tapCreate( tap: .cgSessionEventTap, place: .headInsertEventTap, options: .listenOnly, eventsOfInterest: CGEventMask(1 << CGEventType.keyDown.rawValue), callback: { _, type, event, refcon in guard let refcon = refcon else { return nil } let module = Unmanaged<ScreenShotModule>.fromOpaque(refcon).takeUnretainedValue() // 监听Cmd+Tab组合键 let tabKeyCode = 48 if event.getIntegerValueField(.keyboardEventKeycode) == tabKeyCode, event.flags.contains(.maskCommand) { DispatchQueue.main.async { module.captureScreenshot() } } return event }, userInfo: Unmanaged.passUnretained(self).toOpaque() ) else { print("无法创建事件监听") return } let runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0) CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes) CGEvent.tapEnable(tap: eventTap, enable: true) } // 抽离截图逻辑为独立方法 @objc private func captureScreenshot() { print("开始截图") let displayId = CGMainDisplayID() guard let image = CGDisplayCreateImage(displayId) else { return } let bitmapRep = NSBitmapImageRep(cgImage: image) guard let imageData = bitmapRep.representation(using: .png, properties: [:]) else { return } let fileManager = FileManager.default let directoryURLs = fileManager.urls(for: .picturesDirectory, in: .userDomainMask) if let documentDirectory = directoryURLs.first { let filePath = documentDirectory.appendingPathComponent("screenshot_\(count).png") do { try imageData.write(to: filePath) count += 1 print("Screenshot saved to: \(filePath)") } catch { print("Error saving screenshot: \(error)") } } }
内容的提问来源于stack exchange,提问作者Logister
相关产品推荐
相关产品推荐

