Hide menu bar icons with a cover instead of moving them off screen
Sliding the icons off the screen meant bringing them all back just to
open one menu, which flickered. Draw a frosted window over the other
apps' icons instead: they keep their real positions, so tapping a tile
opens its menu in place with no reveal, and the cover leaves a gap so
the hamburger is never hidden.
💘 Generated with Crush
Assisted-by: Crush:deepseek-v4.1-flash
This commit is contained in:
@@ -120,38 +120,32 @@ no control over this, so the app creates its own `NSStatusItem` and:
|
||||
`/tmp/simpletopbar-items.txt`; use it to verify the icon is not under the notch
|
||||
(x should be ~1200, not ~765).
|
||||
|
||||
## Hiding other menu bar items
|
||||
## Hiding other menu bar items (cover)
|
||||
|
||||
`MenuBarHider` creates a zero-width spacer `NSStatusItem` at launch and grows its
|
||||
length to `hiddenLength` (10_000) to push the icons on its left off-screen. It is
|
||||
**on by default** (`hideMenuBarItems` defaults to true, then persisted). Its
|
||||
preferred position controls where the hidden/kept boundary lands:
|
||||
`MenuBarHider` drives `MenuBarCover`, which hides other apps' icons by drawing
|
||||
our own frosted window ("cover") over them instead of moving them off-screen.
|
||||
Because the icons keep their real positions, `press` opens their menus in place
|
||||
with no reveal. Verified with `--diag-cover` (cover windows at level 26) and
|
||||
`--diag-press` (the WireGuard menu opens at a positive x, not off-screen).
|
||||
|
||||
- Seed **250** (default) puts the boundary just left of Wi-Fi, so every
|
||||
third-party app icon goes to x < 0 (cmux, Kapture, moomoo, WireGuard,
|
||||
TextInputMenuAgent, Now Playing) while **Wi-Fi (x≈1173), Spotlight, Clock,
|
||||
Battery, Control Center and our hamburger stay visible**.
|
||||
- Verified with `--diag-hide` across positions 100–400. Below ~200 Wi-Fi gets
|
||||
hidden too; above ~300 app icons (Now Playing, TextInput) leak back.
|
||||
- `debug.spacerPos` (Double) overrides the seed for tuning without rebuilding.
|
||||
- Pressing a tray tile calls `MenuBarItemManager.press`. A hidden item sits
|
||||
off-screen, and its menu opens off-screen too (verified with `--diag-press`:
|
||||
the menu window lands at negative x). So press briefly `suspend()`s the hider,
|
||||
waits ~0.2s for the layout to settle, performs the accessibility action
|
||||
(`kAXPressAction`, then `kAXShowMenuAction`), and `resume()`s ~0.1s later. The
|
||||
menu is its own window anchored at the item's on-screen spot, so it survives
|
||||
re-hiding.
|
||||
- **Cannot reveal a single icon.** The spacer hides a contiguous block to its
|
||||
left, so a press can only reveal the whole block. Isolating one item would
|
||||
need either per-item ordering control (not available) or covering the icons
|
||||
with an overlay window instead of moving them off-screen.
|
||||
- **Order matters**: `AppDelegate` must call `setupStatusItem()` *before*
|
||||
`MenuBarHider.shared.prepare()`. If the hider contracts the bar first, the
|
||||
hamburger is created into the collapsed space and lands off-screen.
|
||||
- **Wi-Fi/Spotlight/Clock/Battery/Control Center cannot be hidden**: macOS pins
|
||||
them to the far right and a third-party item can never be inserted to their
|
||||
right, so the spacer has nothing to push them with. This is a macOS
|
||||
constraint, not a bug.
|
||||
- `MenuBarCover.shouldHide` covers everything except the Apple keep-list
|
||||
(Wi-Fi, Battery, Clock, Control Center, Spotlight/Search).
|
||||
- `runs(from:)` merges adjacent item frames into contiguous cover windows;
|
||||
`subtracting(_:from:)` carves out the hamburger so it is never covered
|
||||
(`MenuBarItemManager.refresh` passes `avoid:` = our own item's frame).
|
||||
- Frames come from AX (`kAXPositionAttribute` + `kAXSizeAttribute`) with a
|
||||
**top-left origin**; `windowFrame(for:primaryHeight:)` flips Y for NSWindow.
|
||||
- The cover window level is 26 (just above status items at 25, below menus at
|
||||
~101), so opened menus draw on top of it.
|
||||
- `ignoresMouseEvents = false` makes the covered strip swallow clicks so the
|
||||
hidden icons cannot be clicked through.
|
||||
|
||||
### Legacy: off-screen spacer
|
||||
|
||||
An earlier approach used a zero-width spacer `NSStatusItem` grown to width
|
||||
10_000 to push icons off the left edge. It is no longer used because it required
|
||||
a visible reveal to open a menu. Findings if it is ever revived: preferred
|
||||
position ~250 kept Wi-Fi while hiding apps, and `debug.spacerPos` overrode it.
|
||||
|
||||
## Gotchas
|
||||
|
||||
@@ -162,7 +156,7 @@ preferred position controls where the hidden/kept boundary lands:
|
||||
- Reading `activationPolicy` is reliable but must happen on the main thread;
|
||||
keep all `NSWorkspace` access inside the model.
|
||||
- The menu bar tray needs Accessibility permission; do not add Screen Recording
|
||||
or Apple Events usage. `MenuBarHider` must always be restored on quit, but
|
||||
because a status item dies with its process, a crash cleans up on its own.
|
||||
or Apple Events usage. `MenuBarHider` is restored on quit, and because the
|
||||
cover windows belong to the process, a crash removes them automatically.
|
||||
- `.gitignore` covers `.build/`, `SimpleTopBar.app/`, `dist/` — build artifacts
|
||||
should never be committed.
|
||||
|
||||
Reference in New Issue
Block a user