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() } }