Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
abb0b548bf |
@@ -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
|
||||||
@@ -279,7 +316,7 @@ final class MenuBarHider: ObservableObject {
|
|||||||
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(350.0, forKey: key)
|
||||||
}
|
}
|
||||||
let item = NSStatusBar.system.statusItem(withLength: 0)
|
let item = NSStatusBar.system.statusItem(withLength: 0)
|
||||||
item.autosaveName = name
|
item.autosaveName = name
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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.5}"
|
||||||
|
|
||||||
swift build -c release
|
swift build -c release
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user