Skip to content

fix(commands): wrap tray refresh in run_on_main_thread to avoid macOS UI freeze#380

Merged
appergb merged 1 commit into
betafrom
fix/commands-tray-main-thread
May 9, 2026
Merged

fix(commands): wrap tray refresh in run_on_main_thread to avoid macOS UI freeze#380
appergb merged 1 commit into
betafrom
fix/commands-tray-main-thread

Conversation

@appergb
Copy link
Copy Markdown
Collaborator

@appergb appergb commented May 9, 2026

User description

Summary

set_settingsset_default_polish_mode 是同步 Tauri commands,跑在 IPC handler 线程。它们直接调 refresh_tray_microphone_menu,里面 tray.set_menu(...) 改 macOS NSStatusItem 必须主线程;从 IPC 线程直调会触发 macOS dispatch queue 死锁,导致用户改任何偏好开关(包括 ASR 配置、划词追问历史保存、默认风格切换)后整个 UI 永久卡死、所有按键无响应。

修复:把两处 tray 刷新都包到 app.run_on_main_thread(move || ...) dispatch 到主线程,IPC 线程立即返回不阻塞。这跟 lib.rs:558start_tray_microphone_watcher 已经在用的 pattern 完全一致(参考 issue #169)。

Root cause

PR #306(feat: microphone device selection)第一次在 set_settings 加这个调用,没意识到主线程隔离;set_default_polish_mode 后续也有同样模式。两处都修。

Defensive sweep

整个项目里 tray.set_menu() 只在 refresh_tray_microphone_menu(lib.rs:518)一处出现,所有 5 个调用点已确认安全:

位置 状态
lib.rs:207 tray hover event tray 事件本身在主线程 ✓
lib.rs:559 麦克风设备 watcher 已包 run_on_main_thread ✓
lib.rs:605 tray 菜单事件 主线程 ✓
commands.rs:160 set_settings 本提交修复
commands.rs:944 set_default_polish_mode 本提交修复

其它 sync IPC commands(set_dictation/qa/translation/switch_style/open_app_hotkey)通过 coord.refresh_*_hotkey() 间接刷新,那些函数内部已包了 run_on_main_thread;不碰 AppKit 的 commands 安全。Rust 后端无更多隐患。

Test plan

  • 反复 toggle 划词追问"历史保存"开关,UI 不应卡死
  • 切换默认风格(Style 页 / 托盘菜单),UI 不应卡死
  • 改 ASR 麦克风设备,托盘菜单同步更新且 UI 不卡
  • cargo check 通过(已验证)

PR Type

Bug fix


Description

  • Wrap tray refresh in run_on_main_thread in set_settings

  • Same fix in set_default_polish_mode

  • Prevent UI freeze from IPC threads calling NSStatusItem


Diagram Walkthrough

flowchart LR
  A["IPC thread (set_settings)"] -- "run_on_main_thread" --> B["Main thread"]
  B -- "refresh_tray_microphone_menu" --> C["Update NSStatusItem menu"]
Loading

File Walkthrough

Relevant files
Bug fix
commands.rs
Dispatch tray menu refresh to main thread in settings commands

openless-all/app/src-tauri/src/commands.rs

  • Wrapped refresh_tray_microphone_menu call in set_settings with
    app.run_on_main_thread to dispatch to main thread.
  • Similarly wrapped in set_default_polish_mode.
  • Removed direct use of tray_microphones state parameter; now accessed
    via app.state inside the closure.
+24/-7   

… UI freeze

set_settings 和 set_default_polish_mode 是同步 Tauri commands,跑在 IPC handler
线程。它们直接调 refresh_tray_microphone_menu,里面 tray.set_menu(...) 改 macOS
NSStatusItem 必须在主线程上做。从 IPC 线程直调会触发 macOS dispatch queue
死锁,导致用户改任何偏好开关后整个 UI 永久卡死、所有按键无响应。

修复:把两处 tray 刷新都包到 app.run_on_main_thread(move || ...) dispatch 到主线程,
IPC 线程立即返回不阻塞。这跟 lib.rs:558 里 start_tray_microphone_watcher 已经
在用的 pattern 完全一致(issue #169 stop_qa_hotkey_listener 同样用法)。

防御性扫描结论:整个项目里 tray.set_menu() 只在 refresh_tray_microphone_menu
(lib.rs:518)一处出现,所有 5 个调用点已确认安全:
  - lib.rs:207 tray hover event:tray 事件本身就在主线程
  - lib.rs:559 麦克风设备 watcher:已有 run_on_main_thread
  - lib.rs:605 tray 菜单事件:本身在主线程
  - commands.rs:160 set_settings:本提交修复
  - commands.rs:944 set_default_polish_mode:本提交修复

其它 sync IPC commands(set_dictation/qa/translation/switch_style/open_app_hotkey)
通过 coord.refresh_*_hotkey() 间接刷新,那些函数内部已包了 run_on_main_thread;
不碰 AppKit 的 commands 安全。
@chatgpt-codex-connector
Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 9, 2026

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis ❌

306 - Not compliant

Non-compliant requirements:

  • Add microphone device enumeration, selection, and persistence through IPC/state.
  • Add a preferred microphone picker in the settings UI.
  • Fall back to the system default microphone when the preferred one is unavailable, then automatically switch back when it returns.
  • Update zh-CN / zh-TW / en / ja / ko localization strings.
  • Refresh the tray microphone menu automatically on device changes, without adding a manual refresh action.
  • Fix the settings-page microphone preview monitor cleanup / stop-start race.
  • Move preview recorder start/stop onto a blocking thread to avoid UI stalls during slow device init/release.
  • Stop the settings preview recorder before dictation / QA recording starts to avoid concurrent use of the same input device.

Requires further human verification:

  • Verify the microphone selection and fallback behavior on Windows, macOS, and Linux.
  • Verify the tray menu and preview behavior in the running application.

169 - Not compliant

Non-compliant requirements:

  • Ensure QaHotkeyMonitor teardown on macOS happens on the main thread.
  • Prevent the exit-time Carbon RemoveEventHotKey / DisposeEventHotKeyRef crash on macOS.

Requires further human verification:

  • Confirm the exit-path crash is gone on macOS through runtime testing.
⏱️ Estimated effort to review: 1 🔵⚪⚪⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ No major issues detected

@appergb appergb merged commit 14bfa5f into beta May 9, 2026
4 checks passed
@appergb appergb deleted the fix/commands-tray-main-thread branch May 9, 2026 08:29
appergb pushed a commit that referenced this pull request May 9, 2026
Beta-6 包含的合并:
- #379 fix(recorder): watchdog 在 sleep 醒来后重检 stop_flag,消除停采误报
- #380 fix(commands): wrap tray refresh in run_on_main_thread 修主线程死锁
- #381 feat(ui): consolidate footer/nav, sliding indicator, hover cues, top-right saved toast
- #378 拆分 coordinator 子状态机模块(间接合入)

Tag: v1.2.24-6-beta-tauri 推到 main 后触发 release-tauri.yml Beta 流水线。
appergb pushed a commit that referenced this pull request May 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant