Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5eb2d0f661 | ||
|
|
5a4e2fbda2 |
@@ -38,6 +38,10 @@ executable SwiftPM package, no external dependencies (all Apple frameworks).
|
||||
or per-element, so every helper returns `nil` instead of force-unwrapping. The
|
||||
`enumerate()` routine is `nonisolated static` so `--self-test` can run it.
|
||||
- Items are keyed by `pid-index`; `MenuBarItem` equality ignores the AX element.
|
||||
- Control Center is special: its extras menu bar lists **every module it owns**,
|
||||
including ones not in the bar, which are parked off-screen at `y≈982`.
|
||||
`enumerate()` drops items whose AX position `y > 60` and de-duplicates by
|
||||
title per app, otherwise the tray fills with identical "Control Center" tiles.
|
||||
- Reading another app's UI is the only TCC permission needed; there is no Screen
|
||||
Recording or Apple Events usage.
|
||||
|
||||
@@ -115,14 +119,19 @@ no control over this, so the app creates its own `NSStatusItem` and:
|
||||
## Hiding other menu bar items
|
||||
|
||||
`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.
|
||||
Verified with `--diag-hide`:
|
||||
length to `hiddenLength` (10_000) to push the icons on its left off-screen. Its
|
||||
preferred position controls where the hidden/kept boundary lands:
|
||||
|
||||
- All third-party apps' icons go to x < 0 (cmux, Kapture, moomoo, WireGuard,
|
||||
TextInputMenuAgent, Now Playing) and so does Wi-Fi.
|
||||
- **Spotlight, Clock, Battery and Control Center stay**: 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. Do not try to "fix" this; it is a macOS
|
||||
- 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.
|
||||
- **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.
|
||||
|
||||
## Gotchas
|
||||
|
||||
@@ -33,15 +33,19 @@ the app dies, macOS removes the spacer automatically.
|
||||
|
||||
What it can and cannot hide:
|
||||
|
||||
- It hides **other third-party apps' icons** (and can also catch some Apple
|
||||
items such as Wi-Fi).
|
||||
- It **cannot** hide **Spotlight, Clock, Battery, or Control Center**. macOS
|
||||
reserves the far right of the menu bar for those, and a third-party app cannot
|
||||
place its item to their right, so there is nothing to push them with. (This is
|
||||
the same limit Bartender and Ice hit; they cover the area with their own
|
||||
overlay bar instead.)
|
||||
- It hides **other third-party apps' icons** (cmux, Kapture, WireGuard, input
|
||||
source, Now Playing, …) so only your apps' icons leave the bar.
|
||||
- It **keeps Wi-Fi, Spotlight, Clock, Battery and Control Center**. The spacer's
|
||||
boundary is tuned (preferred position 250) to fall just left of Wi-Fi, so the
|
||||
Apple items on the right stay visible. macOS reserves that far-right area: a
|
||||
third-party item can never be placed to the right of it, so those icons cannot
|
||||
be pushed away even in principle. (Bartender and Ice hit the same wall and
|
||||
cover the area with their own overlay bar instead.)
|
||||
- Newly launched apps are placed to the left of the spacer, so they are hidden
|
||||
automatically.
|
||||
- Advanced: set `defaults write dev.tzgyn.simpletopbar debug.spacerPos -float <n>`
|
||||
to move the boundary (larger = keep more icons; > ~400 starts leaking app
|
||||
icons back into the bar).
|
||||
|
||||
## Install with Homebrew
|
||||
|
||||
|
||||
@@ -68,11 +68,23 @@ final class MenuBarItemManager: ObservableObject {
|
||||
guard let bar = extrasMenuBar(of: axApp),
|
||||
let children = children(of: bar) else { continue }
|
||||
|
||||
var seenTitles = Set<String>()
|
||||
for (index, child) in children.enumerated() {
|
||||
// Control Center reports every module it owns, including the
|
||||
// ones not in the menu bar, which are parked off-screen around
|
||||
// y=982. Keep only items actually sitting in the bar.
|
||||
if let position = pointAttribute(child, kAXPositionAttribute), position.y > 60 {
|
||||
continue
|
||||
}
|
||||
|
||||
let title = stringAttribute(child, kAXDescriptionAttribute)
|
||||
?? stringAttribute(child, kAXTitleAttribute)
|
||||
?? app.localizedName
|
||||
?? "Item"
|
||||
// Several Control Center modules share the generic title
|
||||
// "Control Center"; show each distinct title once per app.
|
||||
if !seenTitles.insert(title).inserted { continue }
|
||||
|
||||
found.append(MenuBarItem(
|
||||
id: "\(pid)-\(index)",
|
||||
pid: pid,
|
||||
@@ -167,10 +179,13 @@ final class MenuBarHider: ObservableObject {
|
||||
guard spacer == nil else { return }
|
||||
let name = "SimpleTopBarHider"
|
||||
let key = "NSStatusItem Preferred Position \(name)"
|
||||
// Seed just left of the hamburger so growing the spacer pushes the
|
||||
// third-party icons on its left off-screen while ours stays put.
|
||||
if UserDefaults.standard.object(forKey: key) == nil {
|
||||
UserDefaults.standard.set(150.0, forKey: key)
|
||||
// The boundary between "hidden" and "kept" falls just left of Wi-Fi at
|
||||
// ~250, so the other apps hide while Apple's Wi-Fi/Clock/Battery/Control
|
||||
// Center/Spotlight stay visible. `debug.spacerPos` overrides the seed.
|
||||
if let override = UserDefaults.standard.object(forKey: "debug.spacerPos") as? Double {
|
||||
UserDefaults.standard.set(override, forKey: key)
|
||||
} else if UserDefaults.standard.object(forKey: key) == nil {
|
||||
UserDefaults.standard.set(250.0, forKey: key)
|
||||
}
|
||||
let item = NSStatusBar.system.statusItem(withLength: 0)
|
||||
item.autosaveName = name
|
||||
|
||||
@@ -115,7 +115,7 @@ struct TopBarView: View {
|
||||
.controlSize(.mini)
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.top, 6)
|
||||
.help("Best-effort: pushes other apps' icons off the menu bar so they only live here. Apple's Spotlight, Clock, Battery and Control Center stay pinned on the right and cannot be moved by any third-party app.")
|
||||
.help("Best-effort: pushes other apps' icons off the menu bar so they only live here. Apple's Wi-Fi, Spotlight, Clock, Battery and Control Center stay visible; macOS won't let any third-party app move them.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user