Skip to content

fix(tray): vendor and patch tray-icon to fix menu popup on macOS fullscreen Spaces#2745

Closed
TuYv wants to merge 1 commit into
farion1231:mainfrom
TuYv:worktree-patch-tray-icon-fullscreen-bug
Closed

fix(tray): vendor and patch tray-icon to fix menu popup on macOS fullscreen Spaces#2745
TuYv wants to merge 1 commit into
farion1231:mainfrom
TuYv:worktree-patch-tray-icon-fullscreen-bug

Conversation

@TuYv

@TuYv TuYv commented May 12, 2026

Copy link
Copy Markdown
Contributor

Summary / 概述

Fixes the tray menu silently failing to pop up on macOS when the click happens on a secondary display while a full-screen Space is active there (主屏永远正常,副屏桌面 Space 正常,仅副屏全屏 Space 下点击托盘没反应)。

The root cause is in the upstream tray-icon crate: it triggers the menu via button.performClick(None), a synthesised click without an NSEvent context. AppKit's status-item popup logic resolves the active screen/Space via [NSApp currentEvent] and silently no-ops when that context resolves to the wrong NSWindow (which is exactly what happens when a full-screen app on a secondary display is the front-most window).

Other tray apps (Bartender, Hidden Bar 等纯菜单栏 App) are unaffected because they don't go through this code path — cc-switch hits it because it is a regular Dock-visible app with both a main window and a tray icon, and tray-icon always installs its custom TrayTarget subview that overrides mouseDown:.

This commit vendors tray-icon 0.21.3 (the version pulled by tauri = "2.8.2") under src-tauri/vendor/tray-icon and replaces performClick(None) with NSStatusItem.popUpStatusItemMenu directly. A [patch.crates-io] entry in src-tauri/Cargo.toml redirects the transitive dependency.

The same fix is being upstreamed in tauri-apps/tray-icon#318 (related upstream issue: #251). Once that PR is merged, released, and Tauri picks it up, this entire vendor/ directory and the [patch.crates-io] section can be removed.

Reproduction (before this PR)

  1. Connect an external display
  2. Open any app full-screen on the secondary display (e.g. browser in fullscreen mode)
  3. Click the cc-switch tray icon on that display's menu bar
  4. ❌ Menu does not appear (click is registered — the icon highlights — but no popup)

Repeat on the primary display, or on the secondary display's desktop Space → ✅ menu pops up normally.

Affected versions

Every release from tray-icon@0.7.5 (2023-08-03) through HEAD (0.24.0). Regression was introduced in tauri-apps/tray-icon#69 (refactor(macos): rewrite impl to fix missing click issues) — the rewrite moved click handling to a custom subview that intercepts mouseDown:, breaking the native button → menu path, and used performClick(None) as a workaround.

Related Issue / 关联 Issue

Upstream: tauri-apps/tray-icon#251 (OPEN), tauri-apps/tray-icon#318 (proposed fix).

No existing cc-switch issue.

Screenshots / 截图

Not applicable — the bug is "no UI appears", and the fix is "the existing UI now appears". The change is invisible on the primary display and on secondary-display desktop Spaces; it is only visible (= the menu now works) on a full-screen Space hosted by a secondary display.

Checklist / 检查清单

  • pnpm typecheck — N/A, no TypeScript changes
  • pnpm format:check — N/A, no TypeScript / formatted-file changes
  • cargo clippy -p cc-switch --no-deps -- -D warnings passes on cc-switch's own code (the 16 warnings emitted from the vendored tray-icon crate are upstream-pre-existing unnecessary unsafe block warnings — not introduced by this patch)
  • No user-facing text changed → i18n untouched

How the patch is structured

  • src-tauri/vendor/tray-icon/ — full source of tray-icon@0.21.3 taken from the cargo registry, minus the registry-internal files (.cargo-ok, .cargo-checksum.json, .cargo_vcs_info.json, Cargo.toml.orig, Cargo.lock). Dual-licensed MIT OR Apache-2.0; both license files are preserved.
  • src-tauri/vendor/tray-icon/src/platform_impl/macos/mod.rs — the only file modified inside the vendored crate. on_tray_click switches from performClick(None) to NSStatusItem.popUpStatusItemMenu(&menu). The Retained<NSMenu> is cloned before the modal popup so the RefCell::borrow() is released — necessary because popUpStatusItemMenu: is modal and a menu item action can re-enter TrayIcon::set_menu, which would borrow_mut() the same RefCell and panic.
  • src-tauri/Cargo.toml — adds [patch.crates-io] entry pointing at vendor/tray-icon.
  • src-tauri/Cargo.lock — auto-updated by cargo to reflect the path source.

Removal path

When the upstream PR (tauri-apps/tray-icon#318) is merged, released, and Tauri upgrades to use the fixed tray-icon, this entire patch can be removed in one commit:

rm -rf src-tauri/vendor/tray-icon
# Remove the [patch.crates-io] section from src-tauri/Cargo.toml
cargo update --package tray-icon

No other source changes will be required — the patch is purely additive.

Apply a local fix for tauri-apps/tray-icon#251: on macOS, clicking the tray
icon on a secondary display while a full-screen Space is active on that
display did nothing — the menu silently failed to appear. Other tray apps
(Bartender, Hidden Bar, etc.) are unaffected because they are pure menu-bar
apps; cc-switch hits the bug because it is a regular app with both a Dock
icon and a tray icon.

Root cause is in the tray-icon crate (every release from 0.7.5 through 0.24
inclusive): the macOS implementation triggers the menu via
`button.performClick(None)`, which synthesises a click without an NSEvent.
AppKit's status-item popup logic resolves the active screen/Space via
`[NSApp currentEvent]` and silently no-ops when the synthesised click
points to the wrong context.

We vendor tray-icon 0.21.3 (the version pulled by tauri 2.8.2) under
src-tauri/vendor/tray-icon and patch `on_tray_click` to drive the popup
through `NSStatusItem.popUpStatusItemMenu` directly. A `[patch.crates-io]`
entry in Cargo.toml redirects the dependency to the local copy.

The same fix is being upstreamed in tauri-apps/tray-icon#318. Once that
PR is merged, released, and Tauri picks it up, this entire vendor
directory and the `[patch.crates-io]` section can be removed.
@TuYv

TuYv commented May 12, 2026

Copy link
Copy Markdown
Contributor Author

Closing for now — reconsidering the vendor-vs-git-fork approach before re-opening. Upstream fix is being tracked at tauri-apps/tray-icon#318.

@TuYv TuYv closed this May 12, 2026
@TuYv TuYv deleted the worktree-patch-tray-icon-fullscreen-bug branch May 12, 2026 10:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant