2 Commits
Author SHA1 Message Date
TZGyn 4bc6b333dc Put the hamburger at the left of the visible icons
The hamburger was landing among the system icons. Move it to the left
edge of the visible group so it reads as the entry point rather than one
more status item.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4.1-flash
2026-09-27 10:40:27 +08:00
TZGyn abb0b548bf Keep all system icons visible and click Spotlight properly
The spacer boundary cut off Now Playing and the input source, which
should stay like Wi-Fi, so move it left of every Apple item. Spotlight's
status item ignores the accessibility press action, so click it at its
real position instead.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4.1-flash
2026-09-27 10:35:20 +08:00
6 changed files with 65 additions and 18 deletions
+10 -8
View File
@@ -34,14 +34,16 @@ automatically.
What it can and cannot hide: What it can and cannot hide:
- It hides **other third-party apps' icons** (cmux, Kapture, WireGuard, input - It hides **other third-party apps' icons** (cmux, Kapture, moomoo, WireGuard).
source, Now Playing, …) so only your apps' icons leave the bar. - It **keeps Wi-Fi, Now Playing, Spotlight, Clock, Battery and Control Center**
- It **keeps Wi-Fi, Spotlight, Clock, Battery and Control Center**. The spacer's visible, with the **hamburger at the left edge of that group**. The spacer's
boundary is tuned (preferred position 250) to fall just left of Wi-Fi, so the boundary (preferred position 300) falls just left of Now Playing. macOS
Apple items on the right stay visible. macOS reserves that far-right area: a reserves the far-right area, so an item can never be placed to the right of
third-party item can never be placed to the right of it, so those icons cannot the system icons.
be pushed away even in principle. (Bartender and Ice hit the same wall and - The input-source indicator is the one system icon that ends up hidden (it sits
cover the area with their own overlay bar instead.) immediately left of where the hamburger can go). To keep it, set
`defaults write dev.tzgyn.simpletopbar debug.spacerPos -float 350` (and
`debug.hamburgerPos -float 100`), which moves the hamburger one slot right.
- Newly launched apps are placed to the left of the spacer, so they are hidden - Newly launched apps are placed to the left of the spacer, so they are hidden
automatically. automatically.
- Advanced: set `defaults write dev.tzgyn.simpletopbar debug.spacerPos -float <n>` - Advanced: set `defaults write dev.tzgyn.simpletopbar debug.spacerPos -float <n>`
+7 -3
View File
@@ -103,9 +103,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
let name = "SimpleTopBar" let name = "SimpleTopBar"
let positionKey = "NSStatusItem Preferred Position \(name)" let positionKey = "NSStatusItem Preferred Position \(name)"
// Only seed the position on first run so the user's own drag wins and // Only seed the position on first run so the user's own drag wins and
// persists through the autosave name. // persists through the autosave name. Kept just below the hider's
if UserDefaults.standard.object(forKey: positionKey) == nil { // preferred position so the hamburger lands at the left edge of the
UserDefaults.standard.set(100.0, forKey: positionKey) // visible icons. `debug.hamburgerPos` overrides it.
if let override = UserDefaults.standard.object(forKey: "debug.hamburgerPos") as? Double {
UserDefaults.standard.set(override, forKey: positionKey)
} else if UserDefaults.standard.object(forKey: positionKey) == nil {
UserDefaults.standard.set(290.0, forKey: positionKey)
} }
let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
+43 -2
View File
@@ -11,6 +11,8 @@ struct MenuBarItem: Identifiable, Equatable {
let icon: NSImage let icon: NSImage
/// True for Apple's own items (Control Center, Spotlight, …). /// True for Apple's own items (Control Center, Spotlight, …).
let isSystem: Bool let isSystem: Bool
/// Frame in Accessibility coordinates (origin top-left of the main screen).
let frame: CGRect?
let element: AXUIElement let element: AXUIElement
static func == (lhs: MenuBarItem, rhs: MenuBarItem) -> Bool { static func == (lhs: MenuBarItem, rhs: MenuBarItem) -> Bool {
@@ -75,10 +77,11 @@ final class MenuBarItemManager: ObservableObject {
var seenTitles = Set<String>() var seenTitles = Set<String>()
for (index, child) in children.enumerated() { for (index, child) in children.enumerated() {
let position = pointAttribute(child, kAXPositionAttribute)
// Control Center reports every module it owns, including the // Control Center reports every module it owns, including the
// ones not in the menu bar, which are parked off-screen around // ones not in the menu bar, which are parked off-screen around
// y=982. Keep only items actually sitting in the bar. // y=982. Keep only items actually sitting in the bar.
if let position = pointAttribute(child, kAXPositionAttribute), position.y > 60 { if let position, position.y > 60 {
continue continue
} }
@@ -90,6 +93,14 @@ final class MenuBarItemManager: ObservableObject {
// "Control Center"; show each distinct title once per app. // "Control Center"; show each distinct title once per app.
if !seenTitles.insert(title).inserted { continue } if !seenTitles.insert(title).inserted { continue }
let size = sizeAttribute(child, kAXSizeAttribute)
let frame: CGRect?
if let position, let size {
frame = CGRect(origin: position, size: size)
} else {
frame = nil
}
found.append(MenuBarItem( found.append(MenuBarItem(
id: "\(pid)-\(index)", id: "\(pid)-\(index)",
pid: pid, pid: pid,
@@ -97,6 +108,7 @@ final class MenuBarItemManager: ObservableObject {
title: title, title: title,
icon: Self.icon(forTitle: title, app: app), icon: Self.icon(forTitle: title, app: app),
isSystem: Self.isSystemBundle(app.bundleIdentifier), isSystem: Self.isSystemBundle(app.bundleIdentifier),
frame: frame,
element: child element: child
)) ))
} }
@@ -138,12 +150,27 @@ final class MenuBarItemManager: ObservableObject {
} }
private func performPress(_ item: MenuBarItem) -> Bool { private func performPress(_ item: MenuBarItem) -> Bool {
// Spotlight's status item accepts the accessibility press action but
// does nothing with it; it needs a genuine click.
if item.appName.lowercased().contains("spotlight"), let frame = item.frame {
postClick(at: CGPoint(x: frame.midX, y: frame.midY))
return true
}
if AXUIElementPerformAction(item.element, kAXPressAction as CFString) == .success { if AXUIElementPerformAction(item.element, kAXPressAction as CFString) == .success {
return true return true
} }
return AXUIElementPerformAction(item.element, kAXShowMenuAction as CFString) == .success return AXUIElementPerformAction(item.element, kAXShowMenuAction as CFString) == .success
} }
/// Posts a real left click at a point in Accessibility/global coordinates.
private func postClick(at point: CGPoint) {
let source = CGEventSource(stateID: .hidSystemState)
let down = CGEvent(mouseEventSource: source, mouseType: .leftMouseDown, mouseCursorPosition: point, mouseButton: .left)
let up = CGEvent(mouseEventSource: source, mouseType: .leftMouseUp, mouseCursorPosition: point, mouseButton: .left)
down?.post(tap: .cghidEventTap)
up?.post(tap: .cghidEventTap)
}
// MARK: - Classification // MARK: - Classification
/// Apple's own menu bar items (Control Center, Spotlight, the input-source /// Apple's own menu bar items (Control Center, Spotlight, the input-source
@@ -235,6 +262,16 @@ final class MenuBarItemManager: ObservableObject {
guard AXValueGetValue(value as! AXValue, .cgPoint, &point) else { return nil } guard AXValueGetValue(value as! AXValue, .cgPoint, &point) else { return nil }
return point return point
} }
nonisolated static func sizeAttribute(_ element: AXUIElement, _ attribute: String) -> CGSize? {
var value: CFTypeRef?
guard AXUIElementCopyAttributeValue(element, attribute as CFString, &value) == .success,
let value,
CFGetTypeID(value) == AXValueGetTypeID() else { return nil }
var size = CGSize.zero
guard AXValueGetValue(value as! AXValue, .cgSize, &size) else { return nil }
return size
}
} }
/// Best-effort menu bar item hiding. A zero-width spacer status item is created /// Best-effort menu bar item hiding. A zero-width spacer status item is created
@@ -276,10 +313,14 @@ final class MenuBarHider: ObservableObject {
// The boundary between "hidden" and "kept" falls just left of Wi-Fi at // 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 // ~250, so the other apps hide while Apple's Wi-Fi/Clock/Battery/Control
// Center/Spotlight stay visible. `debug.spacerPos` overrides the seed. // Center/Spotlight stay visible. `debug.spacerPos` overrides the seed.
// The boundary sits just left of Now Playing, so every Control Center
// item stays visible while the apps hide. It must stay *above* the
// hamburger's preferred position (see AppDelegate) for the hamburger to
// remain on the visible side. `debug.spacerPos` overrides the seed.
if let override = UserDefaults.standard.object(forKey: "debug.spacerPos") as? Double { if let override = UserDefaults.standard.object(forKey: "debug.spacerPos") as? Double {
UserDefaults.standard.set(override, forKey: key) UserDefaults.standard.set(override, forKey: key)
} else if UserDefaults.standard.object(forKey: key) == nil { } else if UserDefaults.standard.object(forKey: key) == nil {
UserDefaults.standard.set(250.0, forKey: key) UserDefaults.standard.set(300.0, forKey: key)
} }
let item = NSStatusBar.system.statusItem(withLength: 0) let item = NSStatusBar.system.statusItem(withLength: 0)
item.autosaveName = name item.autosaveName = name
+3 -3
View File
@@ -103,9 +103,9 @@ func runSelfTest() {
check(MenuBarItemManager.children(of: systemWide) == nil, "no children on a non-app element") check(MenuBarItemManager.children(of: systemWide) == nil, "no children on a non-app element")
let element = AXUIElementCreateSystemWide() let element = AXUIElementCreateSystemWide()
let first = MenuBarItem(id: "1-0", pid: 1, appName: "A", title: "T", icon: NSImage(), isSystem: false, element: element) let first = MenuBarItem(id: "1-0", pid: 1, appName: "A", title: "T", icon: NSImage(), isSystem: false, frame: nil, element: element)
let same = MenuBarItem(id: "1-0", pid: 1, appName: "A", title: "T", icon: NSImage(), isSystem: false, element: element) let same = MenuBarItem(id: "1-0", pid: 1, appName: "A", title: "T", icon: NSImage(), isSystem: false, frame: nil, element: element)
let different = MenuBarItem(id: "1-1", pid: 1, appName: "A", title: "T", icon: NSImage(), isSystem: false, element: element) let different = MenuBarItem(id: "1-1", pid: 1, appName: "A", title: "T", icon: NSImage(), isSystem: false, frame: nil, element: element)
check(first == same, "menu bar items compare by id and title") check(first == same, "menu bar items compare by id and title")
check(first != different, "different ids are not equal") check(first != different, "different ids are not equal")
+1 -1
View File
@@ -95,7 +95,7 @@ struct TopBarView: View {
.controlSize(.mini) .controlSize(.mini)
.padding(.horizontal, 12) .padding(.horizontal, 12)
.padding(.top, 6) .padding(.top, 6)
.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.") .help("Best-effort: pushes other apps' icons off the menu bar so they only live here. Apple's system items (Wi-Fi, Now Playing, Clock, Battery, Control Center, Spotlight) stay visible; macOS won't let any third-party app move them.")
} }
} }
} }
+1 -1
View File
@@ -2,7 +2,7 @@
set -euo pipefail set -euo pipefail
cd "$(dirname "$0")" cd "$(dirname "$0")"
VERSION="${1:-0.4.4}" VERSION="${1:-0.4.6}"
swift build -c release swift build -c release