perf(tray): 托盘麦克风设备监听改 OS 原生通知,空闲零唤醒 + 60s 兜底#670
Conversation
issue #470:原 watcher 每 10s 轮询 list_input_devices()(CoreAudio/WASAPI 全枚举)永不 停歇。新增 device_watch 模块用 OS 原生设备变更通知替代:macOS CoreAudio AudioObjectAddPropertyListener(kAudioHardwarePropertyDevices) 专用 CFRunLoop 线程常驻; Windows IMMNotificationClient 经 IMMDeviceEnumerator 注册端点通知。回调复用现有去抖 (microphone_device_signature) + refresh_tray_microphone_menu + emit 收尾。 works 保证:保留一条 60s 全平台慢速兜底轮询无条件运行——原生注册失败/Linux 无原生路径 时退化到它,三平台永远能检测到设备。coreaudio-sys 提升为 macOS 直接依赖(本就在依赖树)。 注:Windows IMMNotificationClient 分支开发机(macOS)无法本地编译,靠 CI 三平台 cargo check + 真机验证;60s 兜底保证即使该分支失效也不会漏检设备。Cargo.lock 未提交(macOS check 会 引入大量与本改动无关的环境性条目),cargo 构建时自愈。
|
@claude 请审查本 PR(#470 性能修复 P0:托盘麦克风轮询→OS 原生通知 + 60s 兜底)。重点核查:(1) macOS CoreAudio listener 的 |
PR Reviewer Guide 🔍(Review updated until commit 4f8ea54)Here are some key observations to aid the review process:
|
|
Claude finished @appergb's task in 5m 20s —— View job PR #670 代码审查审查任务列表:
总体评估整体设计正确,fail-safe 机制到位,unsafe 代码有理有据。 以下按审查重点分项说明,包含 2 个需 CI 验证的潜在问题和若干低危 nitpick。 (1) macOS CoreAudio
|
| 失败点 | 行为 |
|---|---|
CoreAudio AudioObjectAddPropertyListener 返回非零 |
tx.send(false) + return,spawn_native_watcher 返回 false,仅 log::warn |
Windows CoInitializeEx 失败 |
tx.send(false) + return,返回 false |
Windows CoCreateInstance / RegisterEndpointNotificationCallback 失败 |
register_and_run 返回 false,tx.send(false) |
| 线程 spawn 失败 | rx.recv().unwrap_or(false),Err(RecvError) 被 unwrap_or 吞掉 |
| 60s 轮询线程 spawn 失败 | log::warn!,程序不 panic |
全链路无 unwrap() / expect() / panic!,全部退化到仅打 warn 日志。✓
⚠️ 低危:双重 emit(原生通知 + 60s 兜底各自维护独立 last_signature)
make_microphone_change_handler 在其 parking_lot::Mutex 中维护一份 last_signature,60s 轮询线程在 lib.rs:966 维护另一份独立的 last_signature。当两者都在运行时,一次设备变更会:
- 原生通知回调 → 检测到变化 → emit
microphone:devices-changed - 60s 后轮询 → 自己的
last_signature仍是旧值 → 再次 emit
这会导致设备变更后 60s 内有两次 microphone:devices-changed 事件。由于前端处理该事件是刷新菜单(幂等),功能上无影响。但若需严格去重,可在 60s 轮询路径复用 make_microphone_change_handler 的同一个 last_signature 状态(或将 60s 轮询也用同一个 last_signature Mutex 保护)。当前实现可接受。
CI 验证清单(开发机无法本地验证)
| 优先级 | 项目 | 预期结论 |
|---|---|---|
| 必须 | Windows cargo check / 真机运行 |
验证 PROPERTYKEY import 路径是否编译通过;若失败加 "Win32_UI_Shell_PropertiesSystem" feature |
| 必须 | Windows 真机插拔麦克风 | IMMNotificationClient 回调是否触发 + 去抖是否正常 |
| 建议 | macOS 插拔麦克风 | CoreAudio listener 实时触发,60s 无额外刷新 |
| 建议 | Linux smoke test | 60s 轮询唯一路径,日志确认无原生注册 |
结论:代码设计合理,macOS 部分无需改动可放心合并(除 nitpick);Windows 分支需 CI 确认 PROPERTYKEY feature 是否编译通过。两个 nitpick(let _ = &wrapper 和 CLSCTX_ALL)均可 follow-up 修复,不阻塞合并。
|
需要先解决 CI 问题才能合并。 |
|
CI 这边 Windows checks 挂了(macOS / Linux / Android cargo check 都过了),和 PR 描述里「Windows IMMNotificationClient 分支需 CI 验证」对上了。 失败点( 修复方向
macOS CoreAudio 路径和 60s 兜底设计看起来 OK;Windows 编译修好后我再看 CI。Thanks! |
CI Windows checks 失败:windows-rs 的 IMMNotificationClient_Impl / Win32_UI_Shell_PropertiesSystem feature 与 implement 宏的 windows_core 路径在开发机(macOS)无法编译验证,靠 CI 反复试代价大。 简化为:macOS 走 CoreAudio 原生通知(已验证),Windows / Linux 统一返回 false → 走 lib.rs 的 60s 慢速兜底轮询(仍比原 10s 少 6 倍唤醒、保证三平台编译通过)。Windows 原生设备通知 留作后续单独项(需 Windows 开发机验证)。issue #470。
|
Persistent review updated to latest commit 4f8ea54 |
User description
源自 #470 Cloud 性能审查 P0。原 watcher 每 10s 轮询
list_input_devices()(CoreAudio/WASAPI 全枚举)永不停歇。改动:新增
device_watch模块用 OS 原生设备变更通知替代轮询——macOSAudioObjectAddPropertyListener(kAudioHardwarePropertyDevices)专用 CFRunLoop 线程常驻;WindowsIMMNotificationClient经IMMDeviceEnumerator注册端点通知。回调复用现有去抖 +refresh_tray_microphone_menu+ emit。works 保证:保留一条 60s 全平台慢速兜底轮询无条件运行——原生注册失败 / Linux 无原生路径时退化到它,三平台永远能检测到设备。
coreaudio-sys提升为 macOS 直接依赖(本就在依赖树)。验证:macOS⚠️ Windows
cargo check通过。IMMNotificationClient分支开发机(macOS)无法本地编译,请 CI 三平台 cargo check + Windows 真机验证;60s 兜底保证即使该分支失效也不漏检设备。Cargo.lock 未含本次 dep(macOS check 会引入大量无关环境性条目),cargo 构建自愈。PR Type
Enhancement, Bug fix
Description
Replace 10s polling with OS-native device notifications (macOS CoreAudio) for zero idle wake-ups
Fallback 60s slow poll for Windows/Linux (6× lower wake-up rate than original)
Extract common refresh and dedup logic for reusable device-change handling
Diagram Walkthrough
flowchart LR subgraph start_tray_microphone_watcher A["macOS?"] -- yes --> B["spawn_native_watcher (CoreAudio)"] A -- "no (Windows / Linux)" --> C["return false"] end B -- "success?" --> D["native_registered = true"] B -- fail --> D D --> E["60s slow poll fallback unconditionally (1s sleep loop)"] E --> F["Check signature dedup (microphone_device_signature)"] F -- changed --> G["refresh_microphone_on_main (menu + emit)"] F -- unchanged --> EFile Walkthrough
device_watch.rs
Add device_watch module with macOS CoreAudio native listeneropenless-all/app/src-tauri/src/device_watch.rs
CFRunLoop thread
lib.rs
Integrate native watcher, reduce fallback poll to 60s, extract refreshhelperopenless-all/app/src-tauri/src/lib.rs
emit
then 60s poll (1s sleep slices)
Cargo.toml
Add coreaudio-sys dependency for macOS native device watchopenless-all/app/src-tauri/Cargo.toml