Fix IceBar click flakiness on macOS 26 (notched + Control Center reparenting)#911
Open
tducp015 wants to merge 74 commits into
Open
Fix IceBar click flakiness on macOS 26 (notched + Control Center reparenting)#911tducp015 wants to merge 74 commits into
tducp015 wants to merge 74 commits into
Conversation
Misc additional changes and cleanup
`RunLoopLocalEventMonitor` seems to prevent certain buttons from receiving events in macOS 26 Developer Beta 1. This might be a bug in the beta, or `RunLoopLocalEventMonitor` itself. For now, let's just move to a better mouse check implementation that doesn't use continuous event monitoring. Note the `FIXME` (line 1057). The previous implementation had this problem too, but it was never documented.
A big part of this is hopefully a temporary measure. We need an accurate identifier for every item, and the old way just isn't cutting it right now. Items are all owned by the Control Center in macOS 26, and try as I might, I couldn't find a great way to get the _actual_ host apps for the items. Must dig deeper into the mines... Oh yeah, there's also a bunch of random stuff here too that I don't really want to explain. Just know that it probably all fixed something.
This is what I get for trusting Xcode to update my build settings
- Refactor screen capture - Rework menu bar item getters - Update immovable/non-hideable info lists - Remove OSLog wrapper - Minor migration rework - Remove old entitlements file
Should also fix a crash when accessing `ControlItem.windowNumber`.
- Rework app lifecycle - Rework how windows are initialized - Update documentation comments - Refactoring and cleanup
This should fix some performance issues that occur during mouse tracking operations (e.g. highlighting a button on hover).
We also store the status item and layout constraint in a separate storage class. Not sure I like this, but the idea is to convey the tightly coupled relationship between the constraint and status item, and to ensure that they are initialized (and deinitialized) at the same time.
Relying on the default behavior seemed to work fine, but is now broken in the macOS 26 Developer Beta. Probably better to handle it explicitly, even if it is just a beta bug.
- Documentation changes
- Misc event handling improvements - Fix temporarily shown item interface check - Fix broken on screen item check - Additional minor refactoring
On macOS 26, menu bar items whose owning app does not expose its extras menu bar via Accessibility can never be resolved to a source PID. The previous behavior set shouldClearCachedItemWindowIDs on every nil sourcePID, creating a feedback loop: every cache refresh invalidated itself, triggering another full re-cache with a fresh XPC roundtrip plus an O(running apps) AX enumeration for each unresolvable item. Under this thrash, IceBarPanel.show() would race with the background re-cache, and toggle()'s isHidden check against controlItem.state could end up calling hide() on a bar that had just been shown, manifesting as clicks failing to open the IceBar about half the time. Fixes: - SourcePIDCache now records failed lookups with a 30s TTL, skipping the expensive AX scan on retry. - MenuBarItemManager no longer invalidates the item window ID cache when sourcePID is nil; items with stable UUID-based tags stay in the cache until a running-apps change gives them another chance. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…enting
In macOS 26, Control Center re-parents Ice's control item windows, so
their kCGWindowOwnerPID is Control Center, not Ice. The XPC source-PID
lookup then fails for our own items (Ice as an LSUIElement app does not
expose its NSStatusItems via AXExtrasMenuBar), and they fall through to
a synthetic UUID namespace.
The window title is still set correctly ("Ice.ControlItem.Hidden" etc.)
but the namespace mismatch means the resulting tag no longer equals
MenuBarItemTag.hiddenControlItem. ControlItemPair.init can't find the
hidden control item, the entire menu bar item cache is cleared, and the
rebuild loops indefinitely - the IceBar never leaves "Loading menu bar
items...".
Recognise our own control items by their unique title prefix
("Ice.ControlItem.") and force the namespace to .ice regardless of what
the source-PID lookup returned. As a related fallback, items whose owner
is *not* Control Center (i.e. not reparented) now get their owner's
bundle identifier instead of a UUID - useful for any well-behaved status
item app whose AXExtrasMenuBar lookup happens to fail.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
NSScreen.getApplicationMenuFrame() hit-tested AX at the single corner
pixel (displayBounds.origin) and gave up if the result wasn't role
.menuBar. That single point is unreliable in three configurations:
- Notched Apple silicon Macs: (0, 0) is outside the screen's
rounded-corner mask, so the AX hit-test returns nil.
- Menu bar accessories that paint near the corner (e.g. NotchNook)
can intercept the hit-test and return their own AX element.
- macOS 26 Liquid Glass menu bar: the topmost edge can be classified
as the underlying window in some foreground apps.
When the lookup returned nil, MenuBarItemManager.temporarilyShow
silently aborted with "No application menu frame, so not showing ...",
so clicking a hidden item from the IceBar appeared to do nothing.
Probe a few inset points along the leftmost portion of the menu bar
(where the Apple/app menus live, well clear of the notch and any
centred accessories) and walk the AX parent chain when a hit lands
inside a menu bar item. The original corner-pixel probe is kept as the
last fallback, to preserve the previous behavior on machines where it
worked.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author
|
Hello, thank you for your work. I have like a million items in my menu bar, and thankfully I have this haha. I've noticed this issue for a while, but didn't have the time to check over it. Hopefully this helps someone else as well! |
|
大佬你编译一版然后发布吧,ice 原作者应该已经放弃维护这个项目了。 |
Author
Yes, there are a few more bugs there are to fix. I'll probably attach a build here later if anybody needs when I'm free. It seems features such as hover to open, and the gradient bar is broken. Hover crashes the program for me, and the gradient is rendered in the middle of my screen. |
pls read #867 (comment) |
|
emindeniz99
pushed a commit
to emindeniz99/Ice
that referenced
this pull request
Apr 18, 2026
Ice's macos-26 branch left several critical issues that prevent the app from working correctly on Tahoe, especially 26.3+ / 26.4+. This change folds in the fixes from the three community PRs that address them (jordanbaird#903, jordanbaird#911, jordanbaird#922) and the cache-thrash guard from jordanbaird#874. Changes: * `Bridging.getActiveMenuBarDisplayID()` falls back to `CGMainDisplayID()` when `CGSCopyActiveMenuBarDisplayIdentifier` returns nil, which is now the case on macOS 26.4.1. Without the fallback, the item cache's `displayID` stayed `nil` and the layout preview rendered as "Unable to display menu bar items" even though items existed. * `MenuBarItemTag.Namespace` on macOS 26 recognizes Ice's own control items by window title ("Ice.ControlItem.*") and falls back to the owning application's identifiers when the owner isn't Control Center. This stops the UUID-namespace feedback loop that left the layout stuck on "Loading menu bar items..." after Control Center re-parented Ice's status items. * `SourcePIDCache` caches failed AX lookups for 30 seconds, and the item manager no longer invalidates the window-ID cache when `sourcePID` is nil. Those two together stopped the thrash where each failed lookup triggered another full scan, racing with `IceBarPanel.show()` and causing clicks to drop roughly half the time on notched Macs. * `AXHelpers.menuBarElement(nearDisplayOrigin:)` probes several inset points along the leftmost menu bar region instead of hit-testing the exact display corner. Single-point probing fails on notched displays (outside the rounded-corner mask), next to menu bar accessories such as NotchNook, and on Tahoe's translucent menu bar. `getApplicationMenuFrame()` and `hasValidMenuBar(in:for:)` both use the new helper. * `MenuBarItemImageCache.compositeCapture` falls back to `item.bounds` when `CGSGetScreenRectForWindow` fails and tolerates a one-pixel discrepancy in the composite width. This keeps items that Control Center has re-parented from being dropped entirely. * XPC `.isFromSameTeam()` requirement is only applied when the current process actually has a team identifier. Ad-hoc signed builds (the default when no signing team is configured) do not, and the old requirement refused every peer, leaving the `MenuBarItemService` unusable. A new `CodeSignInfo` helper inspects the process's code signature. * The settings detail pane is keyed by the current navigation identifier on macOS 26 so that `NavigationSplitView` reliably updates on the first sidebar click.
emindeniz99
pushed a commit
to emindeniz99/Ice
that referenced
this pull request
Apr 23, 2026
I could only read the descriptions of the community PRs before; after fetching the actual .patch files I realised my implementation had real gaps. Fill them in: * AXHelpers.menuBarElement walks the AX parent chain up to 4 hops when a probe point lands on a menu-bar item instead of the menu bar itself. Without this, hits on "File"/"Edit"/etc. were dropped as not-a-menu-bar. (PR jordanbaird#911) * MenuBarItem / MenuBarItemTag / Namespace accept an optional titleOverride. On some macOS 26 builds Control Center strips the titles off reparented status item windows entirely, so the title prefix check I added earlier doesn't match anything. The caller now frame-matches against live NSStatusItem windows and passes the correct "Ice.ControlItem.*" back in. MenuBarItemManager.cacheItemsRegardless builds that map by converting the ControlItem window frames from Cocoa to CG screen coordinates and looking them up in the current CGWindowList. Items without a precomputed identifier fall through to the existing title-prefix / owner-bundle / UUID chain. (PR jordanbaird#903) * Permission now carries a list of settingsURLs and a mayRequireRelaunch flag. ScreenRecordingPermission gets three URLs — the new macOS 26 PrivacySecurity extension URL with and without the ?Privacy_ScreenCapture anchor, plus the legacy com.apple.preference.security URL. openSettingsPane launches System Settings first (so the URL isn't ignored when it's cold), then walks the URL list via NSWorkspace, and finally shells out to /usr/bin/open. (PR jordanbaird#928) * AppState.relaunch reopens the current bundle via NSWorkspace.openApplication and terminates the current process. PermissionsView, AdvancedSettingsPane, and the Menu Bar Layout pane expose a "Relaunch Ice" button and explanatory copy when a permission has mayRequireRelaunch == true. (PR jordanbaird#928)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
On macOS 26 (Tahoe), the IceBar exhibits "click flakiness": clicking the Ice control item often fails to open the bar, and clicking a hidden item inside the bar often does nothing. On notched MacBooks with menu bar accessories (e.g. NotchNook), the bar can also fail to populate at all, sitting forever on "Loading menu bar items…".
This PR fixes three independent root causes that all surface as the same symptom. They are split into three commits so each can be reviewed, reverted, or backported on its own.
Environment in which this was reproduced and verified
main1. SourcePID cache thrash (commit 1)
MenuBarItemManager.uncheckedCacheItemssetshouldClearCachedItemWindowIDs = trueon every menu bar item whosesourcePIDwas nil. On macOS 26 there is always at least one such item (any app whose extras menu bar isn't exposed via Accessibility), so the cache invalidation became a feedback loop:getMenuBarItemsgetMenuBarItemsdoes an XPC roundtrip + anO(running apps)AX enumeration for every previously-failed itemUnder that thrash,
IceBarPanel.show()raced with the background re-cache, andMenuBarSection.toggle()'sisHiddencheck againstcontrolItem.statecould end up callinghide()on a bar that had just been shown — clicks failing to open the bar about half the time.Fix:
SourcePIDCacherecords failed lookups with a 30s TTL, skipping the AX scan on retry. The TTL entry is cleared whenever the running apps list changes, so newly-launched apps still get a chance to match.MenuBarItemManagerno longer invalidates the item window ID cache on a nil sourcePID. Items with stable UUID-based tags stay cached until a running-apps change gives them another chance to resolve.2. Control-item recognition after Control Center reparenting (commit 2)
In macOS 26, Control Center re-parents virtually every menu bar item window, including Ice's own control items.
kCGWindowOwnerPIDbecomes Control Center's PID for all of them, so Ice's control items end up flowing through the same source-PID lookup as everything else.When that lookup fails (which it always does for Ice, because Ice as an LSUIElement app does not expose its NSStatusItems via
AXExtrasMenuBar),MenuBarItemTag.Namespace.init(uncheckedItemWindow:sourcePID:)falls through to a synthetic.uuid(...)namespace. The title is still set correctly (Ice.ControlItem.Hiddenetc.), but the namespace mismatch means the resulting tag is no longer equal toMenuBarItemTag.hiddenControlItem.ControlItemPair.init(items:)then can't find the hidden control item,cacheItemsRegardlessclears the entire menu bar item cache, the next refresh produces the same items with the same wrong tags, and the cache rebuild loops indefinitely. The IceBar never leaves "Loading menu bar items…".Fix: in the macOS 26 namespace initializer, recognise Ice's own control items by their window title prefix (
Ice.ControlItem.) and force the namespace to.iceregardless of what the source-PID lookup returned. As a related fallback, items whose owner is not Control Center (i.e. not reparented) now get their owner's bundle identifier instead of a UUID — useful for any well-behaved status item app whose AX extras menu bar lookup happens to fail.I confirmed via diagnostic logging on macOS 26.3.1 that the failing items look like:
```
tag=66387C2B-...:Ice.ControlItem.Hidden ownerPID=15219 (Control Center) sourcePID=nil
```
before the fix, and:
```
Missing sourcePID for <com.jordanbaird.Ice:Ice.ControlItem.Visible (windowID: ...)>
```
(harmless "missing sourcePID" log, but the tag now matches) after the fix. The cache rebuild loop terminates after the very first attempt.
3.
getApplicationMenuFrame()AX corner-pixel hit-test (commit 3)NSScreen.getApplicationMenuFrame()didAXHelpers.element(at: displayBounds.origin)— a single AX hit-test at the very top-left pixel of the display — and gave up if the result wasn't role.menuBar. That single point is unreliable in three configurations that all apply to the affected machine:(0, 0)falls outside the screen's rounded-corner mask, so the AX hit-test returns nil.When the function returned nil,
MenuBarItemManager.temporarilyShowsilently aborted with"No application menu frame, so not showing …", so clicking a hidden item from the IceBar appeared to do nothing.Fix: moved the probe into a new
AXHelpers.menuBarElement(nearDisplayOrigin:)that hit-tests a few points along the leftmost portion of the menu bar (where the Apple/app menus live, well clear of the notch and any centred accessories) and walks up the AX parent chain when a hit lands inside a specific menu bar item. The probe falls back to the original(0, 0)as the last attempt to preserve the previous behavior on machines where it worked.Test plan
Known limitations / follow-ups
The first cache attempt at startup still races against status item registration and emits one "Missing control item for hidden section" warning, but it's now self-recovering on the next attempt instead of looping. A follow-up could retry that first attempt after a short delay; left out of this PR to keep the diff focused.
Researched and authored with help from Claude (Anthropic). Each commit carries a `Co-Authored-By` trailer for transparency.