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.
The dated QA logs under docs/ and the probe tools under Tests/ posted real input, moved real menu bar items, or activated the private assessment-mode assertion. Keep only the pure decision/state tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Only drag Ice's hiding boundary within 3 s of an explicit user toggle (Ice button, menu, or hotkey); automatic cache refreshes and launch keep items expanded instead of taking over the pointer. - Wait for the user to stop typing, pointing, and holding modifiers or buttons before a native Command-drag, and give up after 5 s. - Suppress local keyboard and mouse events while the synthetic Command key is down, so keystrokes can't become Command shortcuts. - Don't let Layout drag MenuBarAgent-hosted system items, which crashed MenuBarAgent's input loop in testing. - Restore the 250 ms smart-rehide delay on macOS 14-26, which the macOS 27 work changed without an availability check. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The always-hidden spacer's position is a guessed preferred position and is never aligned. If it lands to the right of Ice, widening it pushes Ice's button into the native overflow with no way to click it. After a spacer is widened, confirm through Ice's own accessibility frames that the button is still on the bar and right of every widened spacer; otherwise withdraw the spacers and show all sections. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Companion to the listener-side fix in the previous commit. The XPCSession client (Ice/MenuBar/MenuBarItems/MenuBarItemServiceConnection.swift) also defaults to .isFromSameTeam() peer requirement, which silently rejects ad-hoc-signed builds the same way the listener did. The refactor introduces MenuBarItemService.ownTeamIdentifier() in the shared layer so both sides query exactly the same predicate. Developer- ID-signed builds keep strict same-team enforcement; ad-hoc community builds skip both requirements (Listener accepts, Client sends).
Describe what works and what doesn't on macOS 27, how hiding works, the hardening added on top of the macOS 27 compatibility work, and how to build an ad-hoc-signed copy with Xcode 27. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Library validation rejects the embedded Sparkle framework, which keeps Sparkle's Team ID, so an ad-hoc build with hardened runtime crashes at launch with "Library missing". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Requiring the pointer to be still for 250 ms made Layout drags time out while the user kept pointing after a drop. Local input is suppressed during the drag, so only held modifiers, pressed buttons and recent typing need to block it. Log which condition blocked on timeout. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
On macOS 27.0 (26A428), system-wide AX hit testing at the narrow hosted boundary returns no element, so every Layout move refused to align the boundary. When the hit test finds no identified element, accept the drag only if Ice's own fresh frame still matches and no other item's frame contains the drag start. A hit on a different identified item still refuses the drag. Also log macOS 27 visibility decisions (once per change) and the reasons alignment fails, so hide problems can be diagnosed from the log. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The spacer length assumed Ice sat at the right edge of the region right of the notch. With many visible items, Ice sits well left of that edge, or even left of the notch, so a 719-point spacer didn't fit, was discarded, and nothing hid. Compute the length from Ice's live frame, the notch, and the end of the application menus: fill the space left of Ice, or, when Ice is right of the notch, exceed the remaining gap there so the spacer moves left of the notch and fills that side. Also permit local mouse events during native drags (only keyboard input is suppressed, matching the state the drags were validated with) and log where a native drag left the item when its order can't be verified. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
On macOS 27.0, hit testing at Ice's boundary returns MenuBarAgent's unidentified host button instead of Ice's own element, so every Layout move refused to align the boundary. Accept an unidentified hit element only when its frame matches the boundary's frame, Ice's own frame still matches, and no other item contains the drag start. Log each condition when refusing, and log when a spacer is withdrawn. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every click started a new overflow change while MenuBarAgent was still animating the previous one, so rapid toggles left hidden items flashing and the bar out of step with Ice's icon. Apply at most one concealment change every 400 ms, always with the latest requested state; the icon still updates on every click. While already concealing, keep the spacer at the length that hid the items instead of recomputing it every five seconds. Ice's button moves once items overflow, and resizing to match made the whole bar reflow. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
getApplicationMenuFrame() finds no menu bar on macOS 27, so the spacer was sized as though the app menus ended at the screen's left edge. The oversized spacer didn't fit beside the menus, and MenuBarAgent moved Ice's button and the visible items across the notch on every toggle, making positions jump and the animation slow. Read the frontmost application's menu frames from its accessibility menu bar, record Ice's position before concealing, and recompute the length when another app becomes frontmost. Shorten the toggle coalescing interval to 300 ms. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
On macOS 27, status items can take the space of the application menus, so a spacer that ended at the app menus let hidden items slide left instead of overflowing. This reverts commit 22a942d. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Clicking Ice's icon with the Ice Bar enabled now shows the hidden items in a bar below the icon, as on earlier versions of macOS. - Keep hidden items concealed in the menu bar while the Ice Bar is in use. Only the Ice Bar opens and closes on click, so the menu bar no longer reflows on every toggle. - Concealed items aren't drawn on macOS 27, so capture their images just before concealing them and whenever they're revealed, and fall back to the owning app's icon when no image exists. - Clicking an item in the Ice Bar reveals the hidden items, clicks the item where MenuBarAgent draws it, and conceals the items again once the menu or window it opened closes. - Place the Ice Bar below Ice's button using its accessibility frame, close it on clicks in other apps with a passive monitor, and stop closing it because the removed hidden-section control item has no frame. - Don't republish the boundary handle on every cache refresh after an automatic hide couldn't align it; wait for a user action instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ce Bar CGDisplayPixelsWide reports points for scaled display modes, so the menu bar strip was captured at 1x (1710 px for a 3420 px display) and glyphs looked soft and heavy when drawn at 2x. Use the display mode's pixel width. Other apps' status item images aren't available through any API, so the Ice Bar still shows crops of the menu bar. Remove the bar's background from them, as Layout does: monochrome glyphs become templates that take the Ice Bar's foreground color, and colored glyphs keep their colors. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Opening the Ice Bar waited for a cache refresh that walks every running app's accessibility tree on macOS 27 and hit its one-second timeout on every open. Concealed items can't be read or captured then anyway, so show the cached items at once and rely on the periodic refresh. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Apps report status item frames with different vertical offsets, so crops taken from those frames drew some glyphs (such as Claude's) higher than others. Trim each extracted glyph to its visible pixels and draw it centered in a slot of uniform height with even horizontal padding. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Settings > General > Ice Bar gets a background opacity slider. It fades the Ice Bar's background, tint, border and shadow; items stay opaque. - When the DebugDumpMacOS27Glyphs default is set, write macOS 27 menu bar captures, per-item crops and processed Ice Bar glyphs, with their frames, to ~/Library/Caches/com.jordanbaird.Ice/GlyphDebug for troubleshooting. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
With a crowded menu bar, MenuBarAgent keeps some items in its own overflow even when Ice reveals everything. Those items aren't drawn anywhere and report stale frames stacked on the overflow («) button, so Ice cropped the button and wallpaper as their Ice Bar images, and clicked the button while believing it clicked the item. The overflow button itself was also listed as a hidden item. - Exclude MenuBarAgent's overflow button from menu bar items, remembering its frame, and mark items whose frames overlap it or another item as not on screen. - Only crop images for items that are on screen; the Ice Bar shows the app icon for the rest. - When a clicked Ice Bar item stays in the system overflow after revealing, open the system overflow instead, where the item can be clicked. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An app's own AXExtrasMenuBar keeps reporting the frame its status item had before macOS moved it. Items that left the system overflow therefore still looked stacked on its button, so Ice treated them as not drawn and never captured or clicked them correctly. MenuBarAgent draws the bar, and its accessibility tree carries the positions it actually used: one window per display, one container per item, each nesting the owning app's element. Take frames from there, and mark an item as not drawn when its container overlaps the overflow button or another container. Also press a concealed item through AXPress or AXShowMenu, which works while macOS isn't drawing it, so clicking an item in the Ice Bar no longer has to reveal the hidden items first. Revealing remains the fallback for items that answer neither action. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The menu bar is translucent, so a crop's background is a piece of wallpaper that changes across the crop and can have an edge cutting through it. A single background color left that wallpaper in the picture as a pale rectangle, and its residual also made plain white glyphs look colored, which kept the wallpaper instead of turning the glyph into a template. Estimate the background at the top and bottom of every column and blend between them, decide color from the glyph's own chroma, and drop anything too faint or not connected to a solid part of the glyph. Concealed items are never drawn, and a full menu bar can leave macOS no room to draw them even when Ice reveals everything, so a picture can be impossible to take for a long time. Save each picture under ~/Library/Caches/com.jordanbaird.Ice/IceBarImages, keyed so it survives the runtime identities that change every launch, and add a photo pass that runs once per launch: it gives the spacer's width back in steps, photographs whatever macOS draws, and restores it. IceBarNoPhotoApps excludes apps whose item changes too often to photograph. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
On macOS 26 and later the bar uses the system glass effect, with two settings that work independently: Darkness picks the shade of the layer over the glass, from white to black, and Transparency picks how much of the glass it covers, from bare glass to solid. Show a saved picture when the live cache has none, and never fall back to a raw crop, which would put a rectangle of wallpaper around the glyph; the app's icon is used instead. A click on Ice's own button reaches MenuBarAgent, so the Ice Bar's outside click monitor closed the bar and the button's action opened it again. Leave clicks on that button to the action. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Multi-line conditions were aligned under their keyword, which the repository's indentation_width rule rejects. Write them the way the rest of the codebase does, with each condition on its own line and the brace below. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The remaining aligned continuations, in the Layout bar, the overlay panel, the control item and the item manager, are written the same way: one condition per line, indented once, with the brace below. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Style the Darkness and Transparency sliders like System Settings: a plain continuous track with a symbol at each end, no percentage, and no step. Dragging one used to catch. Each intermediate value republished the settings object, which rebuilds the whole General pane, including the icon menus and the launch-at-login toggle, and saved a preference. Track the drag in the slider's own state and commit when it ends, and throttle the saves. Two timers also blocked Ice's interface while the settings window was open: the menu bar color sample took a screenshot on the main thread every five seconds, and a complete accessibility walk of every running app ran just as often. Sample the color off the main thread, and skip the walk while the user is in a settings pane that doesn't show the items. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Icon spacing sets how much room sits between the items in the Ice Bar, from none to 24 points, and is saved like the other appearance settings. On macOS 26 and later the settings form had no top padding at all, so the first row of every pane sat against the window's header. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The asset catalog lives under Ice/Resources, so the icon at the top of the README resolved to nothing on the repository page. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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
Makes Ice usable on macOS 27 again: hiding, the Menu Bar Layout editor, and the Ice Bar. Only public APIs are used for hiding. Everything new is gated on
#available(macOS 27.0, *).macOS 27 moved every status item into
MenuBarAgent. There are no per-item windows any more, and an oversized status item (Ice's 10,000-point divider) is discarded instead of pushing items off screen. That's why current builds show "Loading menu bar items" and hide nothing (#954, #965, #970, #991, #992).What's in this branch
This builds on the maintainer's
macos-26branch, notmain. Againstmainthe diff includes all 77macos-26commits, so the macOS 27 work is easiest to review asmacos-26...macos-27(33 commits of our own, plus #980 and #950).MenuBarItemServicework with ad-hoc-signed builds.CGDisplayPixelsWidereturns points; it's now captured at full resolution.DebugDumpMacOS27Glyphsdefault (off unless set) writes captures and frames to the Caches folder.MACOS27.mdcovers the approach, limitations, and how to build an ad-hoc copy.Testing
On a notched MacBook, macOS 27.0 (26A428), Xcode 27.0, Release build signed ad hoc:
NativeMenuBarBoundaryTests,NativeDragVisibilityStateTests,MacOS27DynamicItemStateTests,MenuBarGlyphImageTests) pass.Not yet tested:
Known limitations
ENABLE_HARDENED_RUNTIME=NO), or library validation rejects the embedded Sparkle framework.🤖 Generated with Claude Code