Files
wehub-resource-sync 1d1286fadb
Build / Build and test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:18:38 +08:00

852 lines
25 KiB
Swift

import Foundation
import MacToolsPluginKit
enum SystemStatusMetricKind: String, CaseIterable, Codable, Equatable, Hashable, Identifiable, Sendable {
case cpu
case gpu
case memory
case disk
case battery
case network
case topProcesses
var id: String { rawValue }
func title(localization: PluginLocalization = PluginLocalization(bundle: .main)) -> String {
switch self {
case .cpu:
return "CPU"
case .gpu:
return "GPU"
case .memory:
return localization.string("metric.memory", defaultValue: "内存")
case .disk:
return localization.string("metric.disk", defaultValue: "磁盘")
case .battery:
return localization.string("metric.battery", defaultValue: "电量")
case .network:
return localization.string("metric.network", defaultValue: "网络")
case .topProcesses:
return localization.string("metric.topProcesses", defaultValue: "进程")
}
}
var symbolName: String {
switch self {
case .cpu:
return "cpu"
case .gpu:
return "display"
case .memory:
return "memorychip"
case .disk:
return "internaldrive"
case .battery:
return "battery.75percent"
case .network:
return "wifi"
case .topProcesses:
return "list.bullet.rectangle"
}
}
}
struct SystemStatusGridPosition: Equatable, Sendable {
let row: Int
let column: Int
}
enum SystemStatusComponentRow: Equatable, Sendable {
case metrics([SystemStatusMetricKind])
case topProcesses
}
enum SystemStatusComponentLayout {
static let cardCornerRadius = PluginComponentPanelLayoutMetrics.cardCornerRadius
static let cardSpacing: CGFloat = 6
static let cardContentPadding: CGFloat = 8
static let columns = 2
static let metricRows = 3
static let dashboardSectionSpacing: CGFloat = cardSpacing
static let dashboardMetricTileHeight: CGFloat = 99
static let dashboardLowerTileHeight: CGFloat = 96
static let dashboardMetricGridHeight = CGFloat(metricRows) * dashboardMetricTileHeight
+ CGFloat(max(metricRows - 1, 0)) * cardSpacing
static let emptyContentHeight = dashboardLowerTileHeight
static let dashboardContentHeight = contentHeight(for: defaultPanelMetricKinds)
static let defaultPanelMetricKinds: [SystemStatusMetricKind] = [
.cpu,
.gpu,
.network,
.disk,
.memory,
.battery,
.topProcesses
]
static let defaultMenuBarMetricKinds: [SystemStatusMetricKind] = [
.cpu,
.gpu,
.network,
.disk,
.memory,
.battery
]
static func position(for metric: SystemStatusMetricKind) -> SystemStatusGridPosition? {
let metricKinds = defaultPanelMetricKinds.filter { $0 != .topProcesses }
guard let index = metricKinds.firstIndex(of: metric) else {
return nil
}
return SystemStatusGridPosition(
row: index / columns,
column: index % columns
)
}
static func rows(for kinds: [SystemStatusMetricKind]) -> [SystemStatusComponentRow] {
var rows: [SystemStatusComponentRow] = []
var pendingMetrics: [SystemStatusMetricKind] = []
func flushPendingMetrics() {
guard !pendingMetrics.isEmpty else {
return
}
rows.append(.metrics(pendingMetrics))
pendingMetrics.removeAll(keepingCapacity: true)
}
for kind in kinds {
if kind == .topProcesses {
flushPendingMetrics()
rows.append(.topProcesses)
continue
}
pendingMetrics.append(kind)
if pendingMetrics.count == columns {
flushPendingMetrics()
}
}
flushPendingMetrics()
return rows
}
static func contentHeight(for kinds: [SystemStatusMetricKind]) -> CGFloat {
let rows = rows(for: kinds)
guard !rows.isEmpty else {
return emptyContentHeight
}
let rowHeight = rows.reduce(CGFloat(0)) { partialResult, row in
switch row {
case .metrics:
return partialResult + dashboardMetricTileHeight
case .topProcesses:
return partialResult + dashboardLowerTileHeight
}
}
let spacing = CGFloat(max(rows.count - 1, 0)) * dashboardSectionSpacing
return rowHeight + spacing
}
}
struct SystemStatusSnapshot: Equatable, Sendable {
var cpu: SystemStatusCPUSnapshot
var gpu: SystemStatusGPUSnapshot
var memory: SystemStatusMemorySnapshot
var disk: SystemStatusDiskSnapshot
var battery: SystemStatusBatterySnapshot
var network: SystemStatusNetworkSnapshot
var topProcesses: [SystemStatusTopProcess]
var hardware: SystemStatusHardwareSnapshot
var history: [SystemStatusHistoryPoint]
static let empty = SystemStatusSnapshot(
cpu: .empty,
gpu: .empty,
memory: .empty,
disk: .empty,
battery: .empty,
network: .empty,
topProcesses: [],
hardware: .empty,
history: []
)
}
struct SystemStatusFastSample: Equatable, Sendable {
let cpu: SystemStatusCPUSnapshot
let memory: SystemStatusMemorySnapshot
let network: SystemStatusNetworkSnapshot
let disk: SystemStatusDiskSnapshot
}
struct SystemStatusSlowSample: Equatable, Sendable {
let disk: SystemStatusDiskSnapshot
let battery: SystemStatusBatterySnapshot
let gpu: SystemStatusGPUSnapshot
let hardware: SystemStatusHardwareSnapshot
}
struct SystemStatusCPUSnapshot: Equatable, Sendable {
let usage: Double?
let loadAverage1Minute: Double?
let temperatureCelsius: Double?
let systemPowerWatts: Double?
let isCollecting: Bool
static let empty = SystemStatusCPUSnapshot(
usage: nil,
loadAverage1Minute: nil,
temperatureCelsius: nil,
systemPowerWatts: nil,
isCollecting: true
)
}
struct SystemStatusGPUSnapshot: Equatable, Sendable {
let usage: Double?
let name: String?
let temperatureCelsius: Double?
let isAvailable: Bool
let isCollecting: Bool
static let empty = SystemStatusGPUSnapshot(
usage: nil,
name: nil,
temperatureCelsius: nil,
isAvailable: false,
isCollecting: true
)
}
struct SystemStatusMemorySnapshot: Equatable, Sendable {
let usedBytes: UInt64?
let totalBytes: UInt64?
let swapUsedBytes: UInt64?
let swapTotalBytes: UInt64?
var usage: Double? {
guard let usedBytes, let totalBytes, totalBytes > 0 else {
return nil
}
return min(max(Double(usedBytes) / Double(totalBytes), 0), 1)
}
static let empty = SystemStatusMemorySnapshot(
usedBytes: nil,
totalBytes: nil,
swapUsedBytes: nil,
swapTotalBytes: nil
)
}
struct SystemStatusDiskSnapshot: Equatable, Sendable {
let usedBytes: UInt64?
let totalBytes: UInt64?
let readBytesPerSecond: UInt64?
let writeBytesPerSecond: UInt64?
var usage: Double? {
guard let usedBytes, let totalBytes, totalBytes > 0 else {
return nil
}
return min(max(Double(usedBytes) / Double(totalBytes), 0), 1)
}
static let empty = SystemStatusDiskSnapshot(
usedBytes: nil,
totalBytes: nil,
readBytesPerSecond: nil,
writeBytesPerSecond: nil
)
func replacingActivity(from disk: SystemStatusDiskSnapshot) -> SystemStatusDiskSnapshot {
SystemStatusDiskSnapshot(
usedBytes: usedBytes,
totalBytes: totalBytes,
readBytesPerSecond: disk.readBytesPerSecond,
writeBytesPerSecond: disk.writeBytesPerSecond
)
}
func replacingCapacity(from disk: SystemStatusDiskSnapshot) -> SystemStatusDiskSnapshot {
SystemStatusDiskSnapshot(
usedBytes: disk.usedBytes,
totalBytes: disk.totalBytes,
readBytesPerSecond: readBytesPerSecond,
writeBytesPerSecond: writeBytesPerSecond
)
}
}
enum SystemStatusBatteryState: Equatable, Sendable {
case charging
case charged
case unplugged
case acPower
case unavailable
case unknown
func title(localization: PluginLocalization = PluginLocalization(bundle: .main)) -> String {
switch self {
case .charging:
return localization.string("battery.state.charging", defaultValue: "充电中")
case .charged:
return localization.string("battery.state.charged", defaultValue: "已充满")
case .unplugged:
return localization.string("battery.state.unplugged", defaultValue: "使用电池")
case .acPower:
return localization.string("battery.state.acPower", defaultValue: "外接电源")
case .unavailable:
return localization.string("battery.state.unavailable", defaultValue: "无电池")
case .unknown:
return localization.string("battery.state.unknown", defaultValue: "未知")
}
}
}
struct SystemStatusBatterySnapshot: Equatable, Sendable {
let isAvailable: Bool
let level: Double?
let state: SystemStatusBatteryState
let timeRemainingMinutes: Int?
let adapterWatts: Int?
let batteryPowerWatts: Double?
let temperatureCelsius: Double?
let healthPercent: Int?
let cycleCount: Int?
static let empty = SystemStatusBatterySnapshot(
isAvailable: false,
level: nil,
state: .unknown,
timeRemainingMinutes: nil,
adapterWatts: nil,
batteryPowerWatts: nil,
temperatureCelsius: nil,
healthPercent: nil,
cycleCount: nil
)
}
struct SystemStatusNetworkSnapshot: Equatable, Sendable {
let interfaceName: String?
let ipAddress: String?
let publicIPAddress: String?
let downloadBytesPerSecond: UInt64?
let uploadBytesPerSecond: UInt64?
let isConnected: Bool
let isCollecting: Bool
static let empty = SystemStatusNetworkSnapshot(
interfaceName: nil,
ipAddress: nil,
publicIPAddress: nil,
downloadBytesPerSecond: nil,
uploadBytesPerSecond: nil,
isConnected: false,
isCollecting: true
)
func replacingPublicIPAddress(_ publicIPAddress: String?) -> SystemStatusNetworkSnapshot {
SystemStatusNetworkSnapshot(
interfaceName: interfaceName,
ipAddress: ipAddress,
publicIPAddress: publicIPAddress,
downloadBytesPerSecond: downloadBytesPerSecond,
uploadBytesPerSecond: uploadBytesPerSecond,
isConnected: isConnected,
isCollecting: isCollecting
)
}
}
struct SystemStatusTopProcess: Identifiable, Equatable, Sendable {
let pid: Int
let displayName: String
let command: String
let cpuPercent: Double
let memoryPercent: Double
let memoryBytes: UInt64?
var id: Int { pid }
func replacingDisplayName(_ displayName: String) -> SystemStatusTopProcess {
SystemStatusTopProcess(
pid: pid,
displayName: displayName,
command: command,
cpuPercent: cpuPercent,
memoryPercent: memoryPercent,
memoryBytes: memoryBytes
)
}
}
struct SystemStatusHardwareSnapshot: Equatable, Sendable {
let modelName: String?
let chipName: String?
let macOSVersion: String
let uptimeSeconds: TimeInterval?
let totalMemoryBytes: UInt64?
static let empty = SystemStatusHardwareSnapshot(
modelName: nil,
chipName: nil,
macOSVersion: ProcessInfo.processInfo.operatingSystemVersionString,
uptimeSeconds: nil,
totalMemoryBytes: nil
)
func replacingUptime(_ uptimeSeconds: TimeInterval?) -> SystemStatusHardwareSnapshot {
SystemStatusHardwareSnapshot(
modelName: modelName,
chipName: chipName,
macOSVersion: macOSVersion,
uptimeSeconds: uptimeSeconds,
totalMemoryBytes: totalMemoryBytes
)
}
}
struct SystemStatusHistoryPoint: Codable, Equatable, Sendable {
let timestamp: TimeInterval
let cpuUsage: Double?
let gpuUsage: Double?
let memoryUsage: Double?
let diskUsage: Double?
let diskReadBytesPerSecond: UInt64?
let diskWriteBytesPerSecond: UInt64?
let networkDownloadBytesPerSecond: UInt64?
let networkUploadBytesPerSecond: UInt64?
let batteryLevel: Double?
init(timestamp: TimeInterval, snapshot: SystemStatusSnapshot) {
self.timestamp = timestamp
self.cpuUsage = snapshot.cpu.usage
self.gpuUsage = snapshot.gpu.usage
self.memoryUsage = snapshot.memory.usage
self.diskUsage = snapshot.disk.usage
self.diskReadBytesPerSecond = snapshot.disk.readBytesPerSecond
self.diskWriteBytesPerSecond = snapshot.disk.writeBytesPerSecond
self.networkDownloadBytesPerSecond = snapshot.network.downloadBytesPerSecond
self.networkUploadBytesPerSecond = snapshot.network.uploadBytesPerSecond
self.batteryLevel = snapshot.battery.level
}
init(
timestamp: TimeInterval,
cpuUsage: Double? = nil,
gpuUsage: Double? = nil,
memoryUsage: Double? = nil,
diskUsage: Double? = nil,
diskReadBytesPerSecond: UInt64? = nil,
diskWriteBytesPerSecond: UInt64? = nil,
networkDownloadBytesPerSecond: UInt64? = nil,
networkUploadBytesPerSecond: UInt64? = nil,
batteryLevel: Double? = nil
) {
self.timestamp = timestamp
self.cpuUsage = cpuUsage
self.gpuUsage = gpuUsage
self.memoryUsage = memoryUsage
self.diskUsage = diskUsage
self.diskReadBytesPerSecond = diskReadBytesPerSecond
self.diskWriteBytesPerSecond = diskWriteBytesPerSecond
self.networkDownloadBytesPerSecond = networkDownloadBytesPerSecond
self.networkUploadBytesPerSecond = networkUploadBytesPerSecond
self.batteryLevel = batteryLevel
}
}
struct SystemStatusHistoryDocument: Codable, Equatable, Sendable {
let schemaVersion: Int
var samples: [SystemStatusHistoryPoint]
}
struct SystemStatusCPUTicks: Equatable, Sendable {
let user: UInt64
let system: UInt64
let idle: UInt64
let nice: UInt64
}
enum SystemStatusCPUUsageCalculator {
static func usage(current: SystemStatusCPUTicks, previous: SystemStatusCPUTicks) -> Double? {
let user = positiveDelta(current.user, previous.user)
let system = positiveDelta(current.system, previous.system)
let idle = positiveDelta(current.idle, previous.idle)
let nice = positiveDelta(current.nice, previous.nice)
let active = user + system + nice
let total = active + idle
guard total > 0 else {
return nil
}
return min(max(Double(active) / Double(total), 0), 1)
}
private static func positiveDelta(_ current: UInt64, _ previous: UInt64) -> UInt64 {
current >= previous ? current - previous : 0
}
}
enum SystemStatusPowerNormalizer {
static func energyJoules(from value: Double, unit: String) -> Double? {
switch unit {
case "mJ":
return value / 1_000
case "uJ":
return value / 1_000_000
case "nJ":
return value / 1_000_000_000
default:
return nil
}
}
}
enum SystemStatusBatteryPowerNormalizer {
static func telemetryWatts(fromRawMilliwatts rawValue: Any?) -> Double? {
telemetryWatts(fromMilliwatts: signedNumberValue(rawValue))
}
static func telemetryWatts(fromMilliwatts milliwatts: Double?) -> Double? {
guard let milliwatts, milliwatts.isFinite else {
return nil
}
let signedMilliwatts = twosComplementSignedValueIfNeeded(milliwatts)
return validBatteryWatts(signedMilliwatts / 1_000)
}
static func derivedWatts(voltageMillivolts: Double?, amperageMilliamps: Double?) -> Double? {
guard
let voltageMillivolts,
let amperageMilliamps,
voltageMillivolts > 0,
amperageMilliamps != 0
else {
return nil
}
return validBatteryWatts(-(voltageMillivolts * amperageMilliamps) / 1_000_000)
}
private static func signedNumberValue(_ rawValue: Any?) -> Double? {
if let intValue = rawValue as? Int {
return Double(intValue)
}
if let int64Value = rawValue as? Int64 {
return Double(int64Value)
}
if let uint64Value = rawValue as? UInt64 {
return signedDoubleValue(fromUnsigned: uint64Value)
}
if let doubleValue = rawValue as? Double {
return doubleValue
}
if let numberValue = rawValue as? NSNumber {
if numberValue.doubleValue > Double(Int64.max) {
return signedDoubleValue(fromUnsigned: numberValue.uint64Value)
}
return numberValue.doubleValue
}
if let stringValue = rawValue as? String {
if let int64Value = Int64(stringValue) {
return Double(int64Value)
}
if let uint64Value = UInt64(stringValue) {
return signedDoubleValue(fromUnsigned: uint64Value)
}
return Double(stringValue)
}
return nil
}
private static func signedDoubleValue(fromUnsigned value: UInt64) -> Double {
guard value > UInt64(Int64.max) else {
return Double(value)
}
let magnitude = ~value &+ 1
return -Double(magnitude)
}
private static func twosComplementSignedValueIfNeeded(_ value: Double) -> Double {
guard value > Double(Int64.max) else {
return value
}
return value - pow(2, 64)
}
private static func validBatteryWatts(_ watts: Double) -> Double? {
guard watts.isFinite, watts > -200, watts < 200 else {
return nil
}
return watts
}
}
struct SystemStatusPowerCalculator {
static func watts(
current: SystemStatusPowerEnergySample,
previous: SystemStatusPowerEnergySample
) -> Double? {
let elapsedSeconds = current.date.timeIntervalSince(previous.date)
guard elapsedSeconds > 0, current.joules >= previous.joules else {
return nil
}
let watts = (current.joules - previous.joules) / elapsedSeconds
guard watts >= 0, watts < 1_000 else {
return nil
}
return watts
}
}
struct SystemStatusNetworkCounter: Equatable, Sendable {
let key: String
let displayName: String
let receivedBytes: UInt64
let sentBytes: UInt64
let ipAddress: String?
let isUp: Bool
func replacingCounters(from counter: SystemStatusNetworkCounter?) -> SystemStatusNetworkCounter {
guard let counter else {
return self
}
return SystemStatusNetworkCounter(
key: counter.key,
displayName: displayName,
receivedBytes: counter.receivedBytes,
sentBytes: counter.sentBytes,
ipAddress: ipAddress,
isUp: isUp
)
}
}
struct SystemStatusNetworkRate: Equatable, Sendable {
let downloadBytesPerSecond: UInt64
let uploadBytesPerSecond: UInt64
}
enum SystemStatusNetworkRateCalculator {
private static let maximumBytesPerSecond: UInt64 = 2_000_000_000
static func rate(
current: SystemStatusNetworkCounter,
previous: SystemStatusNetworkCounter,
elapsedSeconds: TimeInterval
) -> SystemStatusNetworkRate? {
guard elapsedSeconds > 0 else {
return nil
}
let receivedDelta = positiveDelta(current.receivedBytes, previous.receivedBytes)
let sentDelta = positiveDelta(current.sentBytes, previous.sentBytes)
return SystemStatusNetworkRate(
downloadBytesPerSecond: clampedBytesPerSecond(receivedDelta, elapsedSeconds: elapsedSeconds),
uploadBytesPerSecond: clampedBytesPerSecond(sentDelta, elapsedSeconds: elapsedSeconds)
)
}
private static func positiveDelta(_ current: UInt64, _ previous: UInt64) -> UInt64 {
current >= previous ? current - previous : 0
}
private static func clampedBytesPerSecond(_ delta: UInt64, elapsedSeconds: TimeInterval) -> UInt64 {
let bytesPerSecond = UInt64(Double(delta) / elapsedSeconds)
guard bytesPerSecond <= maximumBytesPerSecond else {
return 0
}
return bytesPerSecond
}
}
struct SystemStatusDiskIOCounter: Equatable, Sendable {
let readBytes: UInt64
let writeBytes: UInt64
}
struct SystemStatusDiskIORate: Equatable, Sendable {
let readBytesPerSecond: UInt64
let writeBytesPerSecond: UInt64
}
enum SystemStatusDiskIORateCalculator {
private static let maximumBytesPerSecond: UInt64 = 10_000_000_000
static func rate(
current: SystemStatusDiskIOCounter,
previous: SystemStatusDiskIOCounter,
elapsedSeconds: TimeInterval
) -> SystemStatusDiskIORate? {
guard elapsedSeconds > 0 else {
return nil
}
let readDelta = positiveDelta(current.readBytes, previous.readBytes)
let writeDelta = positiveDelta(current.writeBytes, previous.writeBytes)
return SystemStatusDiskIORate(
readBytesPerSecond: clampedBytesPerSecond(readDelta, elapsedSeconds: elapsedSeconds),
writeBytesPerSecond: clampedBytesPerSecond(writeDelta, elapsedSeconds: elapsedSeconds)
)
}
private static func positiveDelta(_ current: UInt64, _ previous: UInt64) -> UInt64 {
current >= previous ? current - previous : 0
}
private static func clampedBytesPerSecond(_ delta: UInt64, elapsedSeconds: TimeInterval) -> UInt64 {
let bytesPerSecond = UInt64(Double(delta) / elapsedSeconds)
guard bytesPerSecond <= maximumBytesPerSecond else {
return 0
}
return bytesPerSecond
}
}
enum SystemStatusFormatter {
static func percent(_ value: Double?, fractionDigits: Int = 0) -> String {
guard let value else {
return "—"
}
return numericPercent(value * 100, fractionDigits: fractionDigits)
}
static func wholePercent(_ value: Double?, fractionDigits: Int = 0) -> String {
guard let value else {
return "—"
}
return numericPercent(value, fractionDigits: fractionDigits)
}
static func bytes(_ bytes: UInt64?) -> String {
guard let bytes else {
return "—"
}
return scaledBytes(bytes)
}
static func speed(_ bytesPerSecond: UInt64?) -> String {
guard let bytesPerSecond else {
return "—"
}
return "\(scaledBytes(bytesPerSecond))/s"
}
static func temperature(_ celsius: Double?) -> String {
guard let celsius else {
return "—°C"
}
return "\(format(celsius, fractionDigits: 0))°C"
}
static func power(_ watts: Double?) -> String {
guard let watts else {
return "—W"
}
let fractionDigits = watts < 10 ? 1 : 0
return "\(format(watts, fractionDigits: fractionDigits))W"
}
static func rpm(_ rpm: Double?) -> String {
guard let rpm else {
return "—"
}
return "\(format(rpm, fractionDigits: 0)) RPM"
}
static func timeRemaining(
minutes: Int?,
localization: PluginLocalization = PluginLocalization(bundle: .main)
) -> String {
guard let minutes, minutes >= 0 else {
return localization.string("battery.timeRemaining.estimating", defaultValue: "估算中")
}
if minutes < 60 {
return "\(minutes)m"
}
let hours = minutes / 60
let remainingMinutes = minutes % 60
guard remainingMinutes > 0 else {
return "\(hours)h"
}
return "\(hours)h \(remainingMinutes)m"
}
static func uptime(_ seconds: TimeInterval?) -> String {
guard let seconds, seconds >= 0 else {
return "—"
}
let totalHours = Int(seconds / 3_600)
let days = totalHours / 24
let hours = totalHours % 24
if days > 0 {
return "\(days)d \(hours)h"
}
return "\(max(totalHours, 0))h"
}
private static func numericPercent(_ value: Double, fractionDigits: Int) -> String {
let clampedFractionDigits = max(fractionDigits, 0)
return "\(format(value, fractionDigits: clampedFractionDigits))%"
}
private static func scaledBytes(_ bytes: UInt64) -> String {
let units = ["B", "KB", "MB", "GB", "TB"]
var value = Double(bytes)
var unitIndex = 0
while value >= 1024, unitIndex < units.count - 1 {
value /= 1024
unitIndex += 1
}
let fractionDigits = unitIndex == 0 || value >= 100 ? 0 : 1
return "\(format(value, fractionDigits: fractionDigits)) \(units[unitIndex])"
}
private static func format(_ value: Double, fractionDigits: Int) -> String {
if fractionDigits == 0 {
return String(Int(value.rounded()))
}
return String(format: "%.\(fractionDigits)f", value)
}
}