Files
wehub-resource-sync 869b84f27c
Build / build (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:29:30 +08:00

89 lines
3.4 KiB
Swift

import AppKit
import SwiftUI
import Combine
/// Captures SwiftUI's `openWindow` action so AppKit surfaces (the menu-bar
/// popover, which lives outside the scene graph and has no working `openWindow`
/// environment) can reopen the main window after it has been closed. The main
/// window records the action on appear; the closure stays valid for the app's
/// lifetime even once the window is gone.
@MainActor
final class WindowOpener {
static let shared = WindowOpener()
var open: ((String) -> Void)?
private init() {}
}
/// AppKit-backed menu-bar system monitor. A SwiftUI `MenuBarExtra` was avoided
/// here: a conditional `.window`-style `MenuBarExtra` fails to type-check, and
/// an unconditional one stalls the XCTest host's run loop. An `NSStatusItem`
/// driving an `NSPopover` (which hosts the existing SwiftUI `MenuBarMonitorView`)
/// gives the same UI with full create/destroy control and no test-host impact.
@MainActor
final class MenuBarController: NSObject, NSPopoverDelegate {
private let statusItem: NSStatusItem
private let popover = NSPopover()
private let monitor = SystemMonitor.shared
private var cancellable: AnyCancellable?
override init() {
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
super.init()
// Persist the user's show/hide choice and ensure the item is requested
// visible (it defaults hidden when restored from a prior autosave state).
statusItem.autosaveName = "PureMacSystemMonitor"
statusItem.isVisible = true
monitor.start()
if let button = statusItem.button {
button.image = NSImage(
systemSymbolName: "gauge.with.dots.needle.67percent",
accessibilityDescription: "System Monitor"
)
button.imagePosition = .imageLeading
button.target = self
button.action = #selector(togglePopover)
updateTitle()
}
popover.behavior = .transient
popover.contentSize = NSSize(width: 248, height: 230)
popover.contentViewController = NSHostingController(rootView: MenuBarMonitorView())
popover.delegate = self
// Refresh the menu-bar CPU readout each time the monitor samples.
cancellable = monitor.$cpuUsage
.receive(on: RunLoop.main)
.sink { [weak self] _ in self?.updateTitle() }
}
/// Remove the status item and release the monitor observer. Called by
/// AppDelegate before dropping the controller so teardown runs on the main
/// actor (a `@MainActor` deinit cannot touch isolated state safely).
func teardown() {
cancellable?.cancel()
cancellable = nil
if popover.isShown { popover.performClose(nil) }
NSStatusBar.system.removeStatusItem(statusItem)
monitor.stop()
}
private func updateTitle() {
guard let button = statusItem.button else { return }
button.title = " \(Int((monitor.cpuUsage * 100).rounded()))%"
}
@objc private func togglePopover() {
guard 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()
}
}
}