Swift开发MacOS菜单栏测量应用 虚拟按键触发后焦点丢失问题
问题根因
- 虚拟按键投递时未显式绑定原前台应用的进程标识,当事件投递间隔过短、或菜单栏应用在蓝牙回调过程中被系统临时激活时,后续按键事件会错投到自身应用上,最终打断目标电子表格的内部焦点状态
- 用
usleep做同步延迟是典型的主线程阻塞实现,macOS在主线程阻塞期间会自动调整窗口激活层级,很容易把焦点从原本选中的Excel/Numbers单元格切到应用空白画布区,触发浮动文本框创建逻辑 - 快捷键构造缺少完整的修饰键时序:现有代码只给V键的down/up事件挂了Command修饰符标记,完全没有投递Command键本身的按下/抬起事件,系统会偶发识别为普通V键输入,进一步打乱焦点状态
具体修复步骤
1. 提前锁定目标前台进程
在收到蓝牙测量数据、准备执行粘贴流程的第一时间,抓取当前前台应用的运行实例,后续所有模拟按键事件都绑定到这个进程,避免事件错投:
// 进入测量处理流程时先固定目标应用 guard let targetApp = NSWorkspace.shared.frontmostApplication else { return }
修正粘贴函数的事件序列,按照真实键盘的物理输入顺序投递事件(Command按下 -> V按下 -> V抬起 -> Command抬起),同时给每个事件绑定目标进程:
func paste(to targetApp: NSRunningApplication) { // 按下Command键 guard let cmdDown = CGEvent(keyboardEventSource: nil, virtualKey: 0x37, keyDown: true) else { return } cmdDown.setTargetProcess(targetApp) // 按下V键,携带Command修饰标记 guard let vDown = CGEvent(keyboardEventSource: nil, virtualKey: 0x09, keyDown: true) else { return } vDown.flags = .maskCommand vDown.setTargetProcess(targetApp) // 抬起V键 guard let vUp = CGEvent(keyboardEventSource: nil, virtualKey: 0x09, keyDown: false) else { return } vUp.flags = .maskCommand vUp.setTargetProcess(targetApp) // 抬起Command键 guard let cmdUp = CGEvent(keyboardEventSource: nil, virtualKey: 0x37, keyDown: false) else { return } cmdUp.setTargetProcess(targetApp) // 按物理输入时序投递 cmdDown.post(tap: .cghidEventTap) vDown.post(tap: .cghidEventTap) vUp.post(tap: .cghidEventTap) cmdUp.post(tap: .cghidEventTap) }
2. 替换阻塞式延迟
删除所有usleep调用,改用GCD异步派发延迟给前台应用足够时间处理粘贴逻辑、更新内部焦点状态:
func pressEnterKey(to targetApp: NSRunningApplication) { // 等粘贴事件被目标应用完全处理后再投递回车 DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { guard let enterDown = CGEvent(keyboardEventSource: nil, virtualKey: 0x24, keyDown: true) else { return } enterDown.setTargetProcess(targetApp) guard let enterUp = CGEvent(keyboardEventSource: nil, virtualKey: 0x24, keyDown: false) else { return } enterUp.setTargetProcess(targetApp) enterDown.post(tap: .cghidEventTap) enterUp.post(tap: .cghidEventTap) } }
Big Sur上这个延迟值设为0.2-0.3秒即可,过长会影响连续测量的响应速度,过短会导致粘贴操作还没完成就触发回车,打乱单元格选中状态
3. 修正应用激活策略
确认Info.plist中配置了LSUIElement = true,确保菜单栏应用永远不会抢占前台焦点:
<key>LSUIElement</key> <true/>
4. 补全Big Sur版本的权限要求
除了现有的辅助功能权限,还需要给应用授权「输入监控」权限,否则跨进程的CGEvent投递会出现偶发的事件丢失、焦点错乱问题:
- 打开系统设置 -> 安全性与隐私 -> 隐私 -> 输入监控,将应用加入授权列表并勾选
- 授权完成后必须重启应用才能生效
针对性优化
- 全局初始化一次复用的
CGEventSource,不要每次构造按键事件都新建源,减少系统事件路由的额外延迟 - 针对Excel、Numbers这类电子表格应用,在投递回车事件前做一次轻量校验:如果检测到当前目标应用的焦点不在可编辑的单元格输入态,就跳过本次回车投递,避免在空白画布上生成覆盖式文本框
内容的提问来源于stack exchange,提问作者RK69
相关产品推荐
相关产品推荐

