diff --git a/AGENTS.md b/AGENTS.md index e9f0f18..76fc73c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -119,7 +119,8 @@ 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. 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: - Seed **250** (default) puts the boundary just left of Wi-Fi, so every @@ -129,6 +130,9 @@ preferred position controls where the hidden/kept boundary lands: - 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. +- **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 diff --git a/README.md b/README.md index 3e83c45..31f07a6 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,9 @@ still works. macOS has no public API to move another app's status item. The **Hide items from the menu bar** toggle uses the spacer technique: a zero-width status item is created at launch whose length is expanded to push the other icons off-screen so -they only live in this panel. It is off by default and always undone on quit; if -the app dies, macOS removes the spacer automatically. +they only live in this panel. It is **on by default** (the choice is +remembered), always undone on quit, and if the app dies macOS removes the spacer +automatically. What it can and cannot hide: diff --git a/Sources/SimpleTopBar/App.swift b/Sources/SimpleTopBar/App.swift index 0a5a7c9..4cdc96c 100644 --- a/Sources/SimpleTopBar/App.swift +++ b/Sources/SimpleTopBar/App.swift @@ -43,8 +43,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate { AppListModel.shared.start() MenuBarItemManager.shared.start() - MenuBarHider.shared.prepare() + // Create the hamburger before the hider so it is placed in the visible + // part of the bar; the hider then contracts the space around it. setupStatusItem() + MenuBarHider.shared.prepare() if CommandLine.arguments.contains("--diag") { DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) { diff --git a/Sources/SimpleTopBar/MenuBarItems.swift b/Sources/SimpleTopBar/MenuBarItems.swift index f2965f1..f3ab437 100644 --- a/Sources/SimpleTopBar/MenuBarItems.swift +++ b/Sources/SimpleTopBar/MenuBarItems.swift @@ -151,10 +151,10 @@ final class MenuBarItemManager: ObservableObject { } /// Best-effort menu bar item hiding. A zero-width spacer status item is created -/// at launch; expanding its length to a huge value pushes the items to its left +/// at launch; expanding its length to a huge value pushes the other apps' icons /// off the visible menu bar, so they can only be reached through SimpleTopBar's -/// panel. Reliable only when SimpleTopBar starts at login (its spacer then sits -/// to the right of later-launched apps' items). Always restored on quit. +/// panel. On by default; the choice is persisted. Always restored on quit, and +/// a crash cleans up automatically because status items die with the process. @MainActor final class MenuBarHider: ObservableObject { static let shared = MenuBarHider() @@ -162,17 +162,24 @@ final class MenuBarHider: ObservableObject { /// Width that pushes the neighbouring items off the visible menu bar. static let hiddenLength: CGFloat = 10_000 + private static let defaultsKey = "hideMenuBarItems" + @Published private(set) var isHiding = false - @Published var enabled = false { + @Published var enabled: Bool { didSet { guard enabled != oldValue else { return } + UserDefaults.standard.set(enabled, forKey: Self.defaultsKey) apply(enabled) } } private var spacer: NSStatusItem? - private init() {} + private init() { + // Hide by default: the menu bar then shows only SimpleTopBar's hamburger + // plus the Apple items macOS will not let us move. + enabled = UserDefaults.standard.object(forKey: Self.defaultsKey) as? Bool ?? true + } /// Create the spacer early (at launch) so it lands as far right as possible. func prepare() { @@ -191,19 +198,19 @@ final class MenuBarHider: ObservableObject { item.autosaveName = name item.button?.isHidden = true spacer = item + apply(enabled) } private func apply(_ hiding: Bool) { - prepare() spacer?.length = hiding ? Self.hiddenLength : 0 isHiding = hiding } - /// Put the menu bar back the way we found it. Called on quit. + /// Put the menu bar back the way we found it. Called on quit. Leaves the + /// user's preference intact. func restore() { spacer?.length = 0 isHiding = false - enabled = false } }