Fix menu bar item identification and navigation on macOS Tahoe#903
Open
tabossert wants to merge 5 commits into
Open
Fix menu bar item identification and navigation on macOS Tahoe#903tabossert wants to merge 5 commits into
tabossert wants to merge 5 commits into
Conversation
On macOS Tahoe (26), all menu bar items appear owned by Control Center and have nil window titles, breaking Ice's control item identification. Additionally, the XPC service fails to connect with ad-hoc code signing because isFromSameTeam() requires a valid team ID. This commit bypasses the XPC service and instead identifies control items by matching NSStatusItem window frames against CGWindowList entries. NSWindow.windowNumber returns 64-bit values on Tahoe that exceed CGWindowID's UInt32 range, so direct ID matching isn't possible. Frame-based matching (x-position and width) reliably maps between the two ID spaces. Key changes: - Disable XPC service startup (blocked by ad-hoc signing) - Remove isFromSameTeam() peer requirement from XPC session - Add titleOverride to MenuBarItem/MenuBarItemTag init for Tahoe - Match control items by frame position instead of PID or window ID - Pass control item map through getMenuBarItems for correct tagging Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add .id() modifier keyed to the navigation identifier to force SwiftUI to re-render the detail pane when the sidebar selection changes. Without this, NavigationSplitView on macOS Tahoe sometimes fails to update the detail view on single click while double click always works. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Author
|
Tested on Fresh upgrade to MacOS Tahoe 26.3 |
|
Good pr, thank you |
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)
5 tasks
On macOS Tahoe (26), `XPCListener(service:, requirement: .isFromSameTeam())` silently drops every incoming check-in when the service is ad-hoc signed (no team identifier to compare against). The listener returns successfully from construction, so the existing try/catch fallback to an unrestricted listener never triggers — clients see `XPCRichError code=1` on every sendSync, and the service logs `Dropping check-in message due to code signing requirement` / `Bogus check-in attempt. Ignoring.` Gate the same-team requirement behind a runtime team-ID check via the new shared `currentProcessTeamID()` helper (using SecCodeCopySigningInformation): properly signed builds keep the requirement; ad-hoc builds fall through to the unrestricted listener so the service actually serves requests. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The cancellation handler captured `[weak self]` and set `self.session = nil` to mark the session for recreation. That assignment ran on the session's target queue, outside the storage lock that protects every other access to `self.session`. When a request was in flight on one thread and the session was being cancelled on another, the unlocked nil-assignment released the XPCSession concurrently with `sendSync`'s `_os_object_retain`, tripping `libdispatch: API MISUSE: Resurrection of an object` and crashing the app. Move the responsibility for nilling out the session into `send`'s catch block, which already runs under `storage.withLock`. The cancellation handler now just logs. Subsequent `getOrCreateSession` calls see the stale reference, `sendSync` fails, and the catch block clears it for the next attempt — all lock-protected. Also gate `setPeerRequirement(.isFromSameTeam())` on `currentProcessTeamID()` matching the listener side, so the client doesn't reject responses from an ad-hoc-signed service. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The .id() fix on the detail pane only helps when the List's selection binding actually propagates. On macOS Tahoe, NavigationSplitView's List selection sometimes updates the highlight on single click without writing to the bound value (a second click is required to commit it), so the detail pane and navigation title stay stale while the sidebar visually shows the new selection. Attach a `simultaneousGesture(TapGesture)` to each sidebar item that writes the identifier into navigationState directly, alongside the List's own selection handling. The click always commits the selection in one shot, and the existing .id() then forces the detail pane to re-render. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
pdurlej
added a commit
to pdurlej/Ice
that referenced
this pull request
May 24, 2026
Upstream PR jordanbaird#903 introduced MenuBarItemService.xpc with this line: listener = try XPCListener(service: name, requirement: .isFromSameTeam()) { ... } `.isFromSameTeam()` requires the listener and every peer to be signed with the same Apple Team Identifier. That works for builds Jordan produces from his Developer ID Application certificate. It does NOT work for any ad-hoc-signed build (TeamIdentifier = empty), because empty-vs-empty is never treated as a match — the listener rejects every check-in attempt with "Bogus check-in attempt. Ignoring." and "Dropping check-in message due to code signing requirement". The visible symptom is the Menu Bar Layout settings pane spinning forever on "Loading menu bar items…" — XPC never returns, so Ice never gets the cached item snapshots. This is the same class of bug reported in upstream issues jordanbaird#744 (46 reactions) and jordanbaird#891 (30 react- ions), and it bites every community fork that ships without an Apple Developer Program account — which is roughly every community fork. This commit reads the running process's actual Team Identifier via SecCodeCopySigningInformation; if it's nil (ad-hoc / unsigned), we fall through to the no-requirement activation path. If it's set (properly Developer-ID-signed build), behaviour is unchanged — the strict same-team requirement still applies. For our fire fork: - 0.11.13-fire.0..fire.1 (ad-hoc, this branch's defaults) → fixed. - 0.11.13-fire.2+ once signed with our Developer ID → unchanged, still uses .isFromSameTeam() because we'll have a team ID. Also bumps MARKETING_VERSION 0.11.13-fire.1 → 0.11.13-fire.2 and CURRENT_PROJECT_VERSION 1123 → 1124 so Sparkle in installed fire.1 recognizes this as a newer build and offers the update.
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.
Description
On macOS Tahoe, all menu bar items appear owned by Control Center with nil window titles. This breaks Ice's ability to identify its own control items, causing the menu bar item cache to never populate ("Loading menu bar items..." indefinitely). Additionally,
NavigationSplitViewin the settings window sometimes fails to update the detail pane on single click.Changes
titleOverrideparameter toMenuBarItemandMenuBarItemTaginit so control items can have their identifier restored as the title when the system returns nil.controlItemMapparameter togetMenuBarItemsto pass the window ID → identifier mapping through the enumeration pipeline. Items in the map get Ice's PID assourcePID; all others still resolve via XPC.getMenuBarItems(windows:controlItemMap:)overload to accept pre-fetched windows, avoiding a duplicategetMenuBarItemWindowscall incacheItemsRegardless..id()modifier toNavigationSplitViewdetail pane, keyed to the navigation identifier, to force re-rendering on selection change.