1import AppKit2import SwiftUI34@MainActor5class MenuController: NSObject {6 static private(set) weak var shared: MenuController?78 let statusItem: NSStatusItem9 let popover: NSPopover10 var eventMonitor: EventMonitor?1112 init(statusItem: NSStatusItem, popover: NSPopover? = nil) {13 self.statusItem = statusItem14 self.popover = popover ?? NSPopover()15 super.init()1617 if let button = statusItem.button {18 button.action = #selector(togglePopover(_:))19 button.target = self20 }2122 23 self.popover.contentSize = NSSize(width: 450, height: 640)24 self.popover.behavior = .transient25 self.popover.contentViewController = NSHostingController(rootView: MainView())2627 eventMonitor = EventMonitor(mask: [.leftMouseDown, .rightMouseDown]) { [weak self] event in28 if let self = self, self.popover.isShown {29 self.closePopover(sender: event)30 }31 }3233 MenuController.shared = self34 }3536 37 38 39 func beginModalPresentation() {40 eventMonitor?.stop()41 popover.behavior = .applicationDefined42 }4344 func endModalPresentation() {45 popover.behavior = .transient46 if popover.isShown {47 eventMonitor?.start()48 }49 }5051 @objc func togglePopover(_ sender: AnyObject?) {52 if popover.isShown {53 closePopover(sender: sender)54 } else {55 showPopover(sender: sender)56 }57 }5859 func showPopover(sender: AnyObject?) {60 if let button = statusItem.button {61 popover.show(relativeTo: button.bounds, of: button, preferredEdge: .minY)62 eventMonitor?.start()63 }64 }6566 func closePopover(sender: AnyObject?) {67 popover.performClose(sender)68 eventMonitor?.stop()69 }70}7172class EventMonitor {73 private var monitor: Any?74 private let mask: NSEvent.EventTypeMask75 let handler: (NSEvent?) -> Void76 private let addMonitor: (NSEvent.EventTypeMask, @escaping (NSEvent?) -> Void) -> Any?77 private let removeMonitor: (Any) -> Void7879 init(80 mask: NSEvent.EventTypeMask,81 handler: @escaping (NSEvent?) -> Void,82 addMonitor: @escaping (NSEvent.EventTypeMask, @escaping (NSEvent?) -> Void) -> Any? =83 NSEvent.addGlobalMonitorForEvents,84 removeMonitor: @escaping (Any) -> Void = NSEvent.removeMonitor85 ) {86 self.mask = mask87 self.handler = handler88 self.addMonitor = addMonitor89 self.removeMonitor = removeMonitor90 }9192 deinit {93 stop()94 }9596 func start() {97 98 99 100 101 102 if monitor != nil {103 stop()104 }105 monitor = addMonitor(mask, handler)106 }107108 func stop() {109 if let monitor = monitor {110 removeMonitor(monitor)111 self.monitor = nil112 }113 }114}