Sliding the icons off the screen meant bringing them all back just to
open one menu, which flickered. Draw a frosted window over the other
apps' icons instead: they keep their real positions, so tapping a tile
opens its menu in place with no reveal, and the cover leaves a gap so
the hamburger is never hidden.
💘 Generated with Crush
Assisted-by: Crush:deepseek-v4.1-flash
125 lines
4.6 KiB
Swift
125 lines
4.6 KiB
Swift
import AppKit
|
|
|
|
/// Hides other apps' menu bar icons by drawing a frosted window ("cover") over
|
|
/// them, instead of moving them off-screen. The icons keep their real positions,
|
|
/// so their menus open in the right place with no reveal, and a target item's
|
|
/// real icon can be left visible by cutting a hole in the cover.
|
|
@MainActor
|
|
final class MenuBarCover {
|
|
static let shared = MenuBarCover()
|
|
|
|
/// Just above the status items (level 25) but well below menus (~101).
|
|
private static let windowLevel = NSWindow.Level(rawValue: 26)
|
|
|
|
private var windows: [NSWindow] = []
|
|
|
|
private init() {}
|
|
|
|
func update(enabled: Bool, items: [MenuBarItem], avoid: CGRect? = nil) {
|
|
removeAllWindows()
|
|
guard enabled else { return }
|
|
|
|
let frames = items.compactMap { item -> CGRect? in
|
|
guard Self.shouldHide(item), let frame = item.frame else { return nil }
|
|
return frame
|
|
}
|
|
var runs = Self.runs(from: frames)
|
|
if let avoid {
|
|
runs = Self.subtracting(avoid, from: runs)
|
|
}
|
|
let primaryHeight = NSScreen.screens.first?.frame.height ?? 0
|
|
for run in runs {
|
|
let frame = Self.windowFrame(for: run, primaryHeight: primaryHeight)
|
|
windows.append(makeWindow(frame: frame))
|
|
}
|
|
}
|
|
|
|
/// Which items the cover hides. Everything is covered except the Apple
|
|
/// items the user asked to keep visible.
|
|
nonisolated static func shouldHide(_ item: MenuBarItem) -> Bool {
|
|
let text = item.title.lowercased()
|
|
.replacingOccurrences(of: "\u{2010}", with: "-")
|
|
.replacingOccurrences(of: "\u{2011}", with: "-")
|
|
let keep = ["wi-fi", "wifi", "battery", "clock", "control center", "search", "spotlight"]
|
|
return !keep.contains(where: { text.contains($0) })
|
|
}
|
|
|
|
/// Merge overlapping or near frames (in Accessibility coordinates) into
|
|
/// contiguous runs, so adjacent icons get one cover window rather than many.
|
|
nonisolated static func runs(from frames: [CGRect], gap: CGFloat = 6) -> [CGRect] {
|
|
let sorted = frames.sorted { $0.minX < $1.minX }
|
|
var result: [CGRect] = []
|
|
for frame in sorted {
|
|
if let last = result.last, frame.minX <= last.maxX + gap {
|
|
result[result.count - 1] = last.union(frame)
|
|
} else {
|
|
result.append(frame)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
/// Carve `avoid` out of the runs so the cover never hides our own icon.
|
|
nonisolated static func subtracting(_ avoid: CGRect, from runs: [CGRect]) -> [CGRect] {
|
|
var result: [CGRect] = []
|
|
for run in runs {
|
|
guard run.intersects(avoid) else {
|
|
result.append(run)
|
|
continue
|
|
}
|
|
if avoid.minX > run.minX {
|
|
result.append(CGRect(x: run.minX, y: run.minY, width: avoid.minX - run.minX, height: run.height))
|
|
}
|
|
if avoid.maxX < run.maxX {
|
|
result.append(CGRect(x: avoid.maxX, y: run.minY, width: run.maxX - avoid.maxX, height: run.height))
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
/// Accessibility coordinates use a top-left origin; NSWindow uses a
|
|
/// bottom-left one, so flip Y against the primary screen's height.
|
|
nonisolated static func windowFrame(for axFrame: CGRect, primaryHeight: CGFloat) -> CGRect {
|
|
CGRect(
|
|
x: axFrame.minX,
|
|
y: primaryHeight - axFrame.maxY,
|
|
width: axFrame.width,
|
|
height: axFrame.height
|
|
)
|
|
}
|
|
|
|
private func makeWindow(frame: CGRect) -> NSWindow {
|
|
let window = NSWindow(
|
|
contentRect: frame,
|
|
styleMask: [.borderless],
|
|
backing: .buffered,
|
|
defer: false
|
|
)
|
|
window.level = Self.windowLevel
|
|
window.isOpaque = false
|
|
window.backgroundColor = .clear
|
|
window.hasShadow = false
|
|
// Swallow clicks so the covered icons cannot be clicked through.
|
|
window.ignoresMouseEvents = false
|
|
window.collectionBehavior = [.canJoinAllSpaces, .stationary, .fullScreenAuxiliary, .ignoresCycle]
|
|
window.animationBehavior = .none
|
|
|
|
let effect = NSVisualEffectView(frame: CGRect(origin: .zero, size: frame.size))
|
|
effect.material = .menu
|
|
effect.blendingMode = .behindWindow
|
|
effect.state = .active
|
|
effect.autoresizingMask = [.width, .height]
|
|
window.contentView = effect
|
|
|
|
window.orderFrontRegardless()
|
|
return window
|
|
}
|
|
|
|
private func removeAllWindows() {
|
|
for window in windows {
|
|
window.orderOut(nil)
|
|
}
|
|
windows.removeAll()
|
|
}
|
|
}
|