Hiding other apps' icons worked, but Apple pins Spotlight, Clock, Battery
and Control Center to the right of the menu bar, so a third-party item can
never be placed after them to push them away. Document the real limit so
the behaviour is not mistaken for a bug.
💘 Generated with Crush
Assisted-by: Crush:deepseek-v4.1-flash
145 lines
4.9 KiB
Swift
145 lines
4.9 KiB
Swift
import SwiftUI
|
|
import AppKit
|
|
import ServiceManagement
|
|
|
|
@main
|
|
struct SimpleTopBarApp: App {
|
|
@NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
|
|
|
|
var body: some Scene {
|
|
// The app is a menu bar extra, so it has no real windows.
|
|
Settings { EmptyView() }
|
|
}
|
|
}
|
|
|
|
/// Owns the menu bar status item directly (instead of `MenuBarExtra`) so the
|
|
/// item can be given an `autosaveName` and a persisted preferred position. On a
|
|
/// full menu bar, a brand new item lands under the notch and is invisible;
|
|
/// writing `NSStatusItem Preferred Position <name>` into defaults puts it in a
|
|
/// visible slot, and the autosave name lets the user's own drag persist.
|
|
final class AppDelegate: NSObject, NSApplicationDelegate {
|
|
private var statusItem: NSStatusItem?
|
|
private var popover: NSPopover?
|
|
|
|
func applicationDidFinishLaunching(_ notification: Notification) {
|
|
if CommandLine.arguments.contains("--self-test") {
|
|
runSelfTest()
|
|
return
|
|
}
|
|
|
|
if CommandLine.arguments.contains("--dump-items") {
|
|
dumpMenuBarItems()
|
|
return
|
|
}
|
|
|
|
// Single instance: stale copies keep a menu bar icon around after an
|
|
// update and swallow clicks meant for the new instance.
|
|
let myPid = ProcessInfo.processInfo.processIdentifier
|
|
let others = NSRunningApplication.runningApplications(withBundleIdentifier: "dev.tzgyn.simpletopbar")
|
|
.filter { $0.processIdentifier != myPid }
|
|
for other in others {
|
|
other.forceTerminate()
|
|
}
|
|
|
|
AppListModel.shared.start()
|
|
MenuBarItemManager.shared.start()
|
|
MenuBarHider.shared.prepare()
|
|
setupStatusItem()
|
|
|
|
if CommandLine.arguments.contains("--diag") {
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) {
|
|
dumpMenuBarItems()
|
|
}
|
|
return
|
|
}
|
|
|
|
if CommandLine.arguments.contains("--diag-hide") {
|
|
MenuBarHider.shared.enabled = true
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
|
|
dumpMenuBarItems()
|
|
}
|
|
return
|
|
}
|
|
|
|
// The tray is empty without Accessibility permission, so surface the
|
|
// system prompt once at launch instead of waiting for the user to open
|
|
// the panel and find the button.
|
|
if !AccessibilityPermission.isGranted,
|
|
!UserDefaults.standard.bool(forKey: "didRequestAccessibility") {
|
|
UserDefaults.standard.set(true, forKey: "didRequestAccessibility")
|
|
AccessibilityPermission.request()
|
|
}
|
|
}
|
|
|
|
func applicationWillTerminate(_ notification: Notification) {
|
|
// Never leave the menu bar resized behind us.
|
|
MenuBarHider.shared.restore()
|
|
}
|
|
|
|
private func setupStatusItem() {
|
|
let name = "SimpleTopBar"
|
|
let positionKey = "NSStatusItem Preferred Position \(name)"
|
|
// Only seed the position on first run so the user's own drag wins and
|
|
// persists through the autosave name.
|
|
if UserDefaults.standard.object(forKey: positionKey) == nil {
|
|
UserDefaults.standard.set(100.0, forKey: positionKey)
|
|
}
|
|
|
|
let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
|
item.autosaveName = name
|
|
|
|
if let button = item.button {
|
|
let image = NSImage(systemSymbolName: "line.3.horizontal", accessibilityDescription: name)
|
|
image?.isTemplate = true
|
|
button.image = image
|
|
button.target = self
|
|
button.action = #selector(togglePopover)
|
|
button.setAccessibilityLabel("SimpleTopBar")
|
|
}
|
|
statusItem = item
|
|
|
|
let popover = NSPopover()
|
|
popover.behavior = .transient
|
|
popover.animates = false
|
|
popover.contentViewController = NSHostingController(
|
|
rootView: TopBarView(
|
|
model: AppListModel.shared,
|
|
menuBar: MenuBarItemManager.shared,
|
|
hider: MenuBarHider.shared
|
|
)
|
|
)
|
|
self.popover = popover
|
|
}
|
|
|
|
@objc private func togglePopover() {
|
|
guard let popover, let button = statusItem?.button else { return }
|
|
if popover.isShown {
|
|
popover.performClose(nil)
|
|
} else {
|
|
NSApp.activate(ignoringOtherApps: true)
|
|
popover.show(relativeTo: button.bounds, of: button, preferredEdge: .minY)
|
|
popover.contentViewController?.view.window?.makeKey()
|
|
}
|
|
}
|
|
}
|
|
|
|
enum LoginItem {
|
|
static var isEnabled: Bool {
|
|
SMAppService.mainApp.status == .enabled
|
|
}
|
|
|
|
static func toggle() -> Bool {
|
|
let service = SMAppService.mainApp
|
|
do {
|
|
if service.status == .enabled {
|
|
try service.unregister()
|
|
} else {
|
|
try service.register()
|
|
}
|
|
return true
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
}
|