Rename app and repository to Puter
This commit is contained in:
@@ -0,0 +1,396 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
enum TaskSection: String, CaseIterable, Identifiable {
|
||||
case processes = "Processes"
|
||||
case performance = "Performance"
|
||||
case history = "App history"
|
||||
case startup = "Startup apps"
|
||||
case users = "Users"
|
||||
case details = "Details"
|
||||
case services = "Services"
|
||||
case hardware = "Hardware"
|
||||
case settings = "Settings"
|
||||
|
||||
var id: String { rawValue }
|
||||
|
||||
var icon: String {
|
||||
switch self {
|
||||
case .processes: "square.stack.3d.up"
|
||||
case .performance: "waveform.path.ecg"
|
||||
case .history: "clock.arrow.circlepath"
|
||||
case .startup: "gauge.open.with.lines.needle.33percent"
|
||||
case .users: "person.2"
|
||||
case .details: "list.bullet.rectangle"
|
||||
case .services: "gearshape.2"
|
||||
case .hardware: "bolt.horizontal.circle"
|
||||
case .settings: "gearshape"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PowerProfile: Identifiable, Hashable, Sendable {
|
||||
let voltageVolts: Double
|
||||
let currentAmps: Double
|
||||
var id: String { "\(voltageVolts)-\(currentAmps)" }
|
||||
var watts: Double { voltageVolts * currentAmps }
|
||||
}
|
||||
|
||||
struct PowerAdapterSnapshot: Hashable, Sendable {
|
||||
var name = "Power adapter"
|
||||
var manufacturer = "Not reported"
|
||||
var serial = ""
|
||||
var ratedWatts = 0.0
|
||||
var negotiatedVoltage = 0.0
|
||||
var negotiatedCurrent = 0.0
|
||||
var profiles: [PowerProfile] = []
|
||||
|
||||
var negotiatedWatts: Double { negotiatedVoltage * negotiatedCurrent }
|
||||
}
|
||||
|
||||
struct BatterySnapshot: Hashable, Sendable {
|
||||
var isPresent = false
|
||||
var chargePercent = 0.0
|
||||
var isCharging = false
|
||||
var isFullyCharged = false
|
||||
var externalPowerConnected = false
|
||||
var cycleCount = 0
|
||||
var designCycleCount = 0
|
||||
var currentCapacityMAh = 0.0
|
||||
var fullChargeCapacityMAh = 0.0
|
||||
var designCapacityMAh = 0.0
|
||||
var voltageVolts = 0.0
|
||||
var currentAmps = 0.0
|
||||
var temperatureCelsius = 0.0
|
||||
var timeRemainingMinutes: Int?
|
||||
var serial = ""
|
||||
var adapter: PowerAdapterSnapshot?
|
||||
var systemPowerWatts = 0.0
|
||||
|
||||
var healthPercent: Double {
|
||||
guard designCapacityMAh > 0 else { return 0 }
|
||||
return min(100, fullChargeCapacityMAh / designCapacityMAh * 100)
|
||||
}
|
||||
|
||||
var batteryPowerWatts: Double {
|
||||
let watts = abs(voltageVolts * currentAmps)
|
||||
return watts.isFinite && watts <= 500 ? watts : 0
|
||||
}
|
||||
}
|
||||
|
||||
struct ConnectedHardwareDevice: Identifiable, Hashable, Sendable {
|
||||
let id: String
|
||||
let name: String
|
||||
let vendor: String
|
||||
let speed: String
|
||||
let currentAvailableMA: Int?
|
||||
let currentRequiredMA: Int?
|
||||
let depth: Int
|
||||
}
|
||||
|
||||
struct HardwarePortSnapshot: Identifiable, Hashable, Sendable {
|
||||
let id: String
|
||||
let name: String
|
||||
let transport: String
|
||||
let maximumSpeed: String
|
||||
let isConnected: Bool
|
||||
let status: String
|
||||
let devices: [ConnectedHardwareDevice]
|
||||
}
|
||||
|
||||
struct FanSnapshot: Identifiable, Hashable, Sendable {
|
||||
let id: Int
|
||||
let name: String
|
||||
let actualRPM: Double
|
||||
let minimumRPM: Double
|
||||
let maximumRPM: Double
|
||||
let targetRPM: Double
|
||||
let mode: String
|
||||
|
||||
var isAutomatic: Bool { mode.localizedCaseInsensitiveContains("automatic") }
|
||||
}
|
||||
|
||||
struct HardwareSnapshot: Hashable, Sendable {
|
||||
var battery = BatterySnapshot()
|
||||
var ports: [HardwarePortSnapshot] = []
|
||||
var fans: [FanSnapshot] = []
|
||||
var thermalState = "Nominal"
|
||||
var capturedAt = Date.distantPast
|
||||
}
|
||||
|
||||
struct MacMemorySpecification: Hashable, Sendable {
|
||||
let chip: String
|
||||
let cpuCoreCount: Int?
|
||||
let bandwidthGBps: Double
|
||||
let isMaximum: Bool
|
||||
|
||||
var displayValue: String {
|
||||
let bandwidth = bandwidthGBps.rounded() == bandwidthGBps
|
||||
? String(format: "%.0f", bandwidthGBps)
|
||||
: String(format: "%.2f", bandwidthGBps)
|
||||
return "\(isMaximum ? "Up to " : "")\(bandwidth) GB/s bandwidth (Apple specification)"
|
||||
}
|
||||
}
|
||||
|
||||
enum MemoryBandwidthCatalog {
|
||||
// Ordered from most-specific to broadest. Core-count entries distinguish
|
||||
// configurations where Apple ships one chip name with multiple memory buses.
|
||||
static let specifications: [MacMemorySpecification] = [
|
||||
.init(chip: "M5 Max", cpuCoreCount: nil, bandwidthGBps: 614, isMaximum: true),
|
||||
.init(chip: "M5 Pro", cpuCoreCount: nil, bandwidthGBps: 307, isMaximum: true),
|
||||
.init(chip: "M5", cpuCoreCount: nil, bandwidthGBps: 153, isMaximum: false),
|
||||
.init(chip: "M4 Max", cpuCoreCount: 16, bandwidthGBps: 546, isMaximum: false),
|
||||
.init(chip: "M4 Max", cpuCoreCount: 14, bandwidthGBps: 410, isMaximum: false),
|
||||
.init(chip: "M4 Pro", cpuCoreCount: nil, bandwidthGBps: 273, isMaximum: false),
|
||||
.init(chip: "M4", cpuCoreCount: nil, bandwidthGBps: 120, isMaximum: false),
|
||||
.init(chip: "M3 Ultra", cpuCoreCount: nil, bandwidthGBps: 819, isMaximum: false),
|
||||
.init(chip: "M3 Max", cpuCoreCount: 16, bandwidthGBps: 400, isMaximum: false),
|
||||
.init(chip: "M3 Max", cpuCoreCount: 14, bandwidthGBps: 300, isMaximum: false),
|
||||
.init(chip: "M3 Pro", cpuCoreCount: nil, bandwidthGBps: 150, isMaximum: false),
|
||||
.init(chip: "M3", cpuCoreCount: nil, bandwidthGBps: 100, isMaximum: false),
|
||||
.init(chip: "M2 Ultra", cpuCoreCount: nil, bandwidthGBps: 800, isMaximum: false),
|
||||
.init(chip: "M2 Max", cpuCoreCount: nil, bandwidthGBps: 400, isMaximum: false),
|
||||
.init(chip: "M2 Pro", cpuCoreCount: nil, bandwidthGBps: 200, isMaximum: false),
|
||||
.init(chip: "M2", cpuCoreCount: nil, bandwidthGBps: 100, isMaximum: false),
|
||||
.init(chip: "M1 Ultra", cpuCoreCount: nil, bandwidthGBps: 800, isMaximum: false),
|
||||
.init(chip: "M1 Max", cpuCoreCount: nil, bandwidthGBps: 400, isMaximum: false),
|
||||
.init(chip: "M1 Pro", cpuCoreCount: nil, bandwidthGBps: 200, isMaximum: false),
|
||||
.init(chip: "M1", cpuCoreCount: nil, bandwidthGBps: 68.25, isMaximum: false)
|
||||
]
|
||||
|
||||
static func specification(for processor: String, cpuCoreCount: Int = ProcessInfo.processInfo.activeProcessorCount) -> MacMemorySpecification? {
|
||||
let normalized = processor.replacingOccurrences(of: "Apple ", with: "")
|
||||
return specifications.first {
|
||||
normalized == $0.chip && ($0.cpuCoreCount == nil || $0.cpuCoreCount == cpuCoreCount)
|
||||
}
|
||||
}
|
||||
|
||||
static func description(for processor: String, cpuCoreCount: Int = ProcessInfo.processInfo.activeProcessorCount) -> String {
|
||||
specification(for: processor, cpuCoreCount: cpuCoreCount)?.displayValue ?? "Clock not published by macOS"
|
||||
}
|
||||
}
|
||||
|
||||
enum UpdateSpeed: String, CaseIterable, Identifiable {
|
||||
case high = "High"
|
||||
case normal = "Normal"
|
||||
case low = "Low"
|
||||
case paused = "Paused"
|
||||
|
||||
var id: String { rawValue }
|
||||
var interval: Duration {
|
||||
switch self {
|
||||
case .high: .seconds(1)
|
||||
case .normal: .seconds(2)
|
||||
case .low: .seconds(5)
|
||||
case .paused: .seconds(2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ProcessRecord: Identifiable, Hashable, Sendable {
|
||||
let pid: Int32
|
||||
let parentPID: Int32
|
||||
let name: String
|
||||
let executablePath: String
|
||||
let user: String
|
||||
let cpu: Double
|
||||
let memoryPercent: Double
|
||||
let residentBytes: UInt64
|
||||
let state: String
|
||||
let elapsed: String
|
||||
let cpuTime: TimeInterval
|
||||
let threadCount: Int
|
||||
let openFileCount: Int
|
||||
let architecture: String
|
||||
let priority: Int
|
||||
let nice: Int
|
||||
|
||||
var id: Int32 { pid }
|
||||
|
||||
var displayName: String {
|
||||
let cleaned = name.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return cleaned.isEmpty ? "Process \(pid)" : cleaned
|
||||
}
|
||||
}
|
||||
|
||||
struct AppUsageRecord: Identifiable, Codable, Hashable, Sendable {
|
||||
let id: String
|
||||
var name: String
|
||||
var executablePath: String
|
||||
var user: String
|
||||
var cpuSeconds: TimeInterval
|
||||
var networkReceivedBytes: UInt64
|
||||
var networkSentBytes: UInt64
|
||||
var lastSeen: Date
|
||||
|
||||
var networkTotalBytes: UInt64 { networkReceivedBytes + networkSentBytes }
|
||||
}
|
||||
|
||||
struct ProcessActivityRate: Hashable, Sendable {
|
||||
var diskRead = 0.0
|
||||
var diskWrite = 0.0
|
||||
var networkReceive = 0.0
|
||||
var networkSend = 0.0
|
||||
|
||||
var diskTotal: Double { diskRead + diskWrite }
|
||||
var networkTotal: Double { networkReceive + networkSend }
|
||||
}
|
||||
|
||||
enum ServiceDomain: String, CaseIterable, Identifiable, Sendable {
|
||||
case user = "User"
|
||||
case system = "System"
|
||||
|
||||
var id: String { rawValue }
|
||||
var launchctlPrefix: String { self == .system ? "system" : "gui/\(getuid())" }
|
||||
}
|
||||
|
||||
struct ServiceRecord: Identifiable, Hashable, Sendable {
|
||||
let label: String
|
||||
let pid: Int32?
|
||||
let lastExitStatus: Int32
|
||||
let domain: ServiceDomain
|
||||
|
||||
var id: String { "\(domain.rawValue):\(label)" }
|
||||
var isRunning: Bool { pid != nil }
|
||||
var isControllable: Bool { domain == .user }
|
||||
|
||||
var configurationPath: String? {
|
||||
let roots = domain == .system
|
||||
? ["/Library/LaunchDaemons", "/System/Library/LaunchDaemons"]
|
||||
: [NSHomeDirectory() + "/Library/LaunchAgents", "/Library/LaunchAgents", "/System/Library/LaunchAgents"]
|
||||
return roots.map { "\($0)/\(label).plist" }.first { FileManager.default.fileExists(atPath: $0) }
|
||||
}
|
||||
|
||||
var displayName: String {
|
||||
let parts = label.split(separator: ".").map(String.init)
|
||||
let meaningful = parts.filter { part in
|
||||
let isNumber = part.allSatisfy(\.isNumber)
|
||||
let isUUID = UUID(uuidString: part) != nil
|
||||
return !isNumber && !isUUID
|
||||
}
|
||||
guard let last = meaningful.last else { return label }
|
||||
let generic = ["agent", "helper", "service", "xpcservice", "app"]
|
||||
if generic.contains(last.lowercased()), meaningful.count > 1 {
|
||||
return "\(meaningful[meaningful.count - 2]) \(last)"
|
||||
}
|
||||
return last
|
||||
}
|
||||
|
||||
var publisher: String {
|
||||
let parts = label.split(separator: ".").map(String.init)
|
||||
guard let first = parts.first else { return "Unknown" }
|
||||
if label.hasPrefix("com.apple") { return "Apple" }
|
||||
if first == "application", parts.count > 2 {
|
||||
let vendorIndex = ["com", "org", "net", "io"].contains(parts[1].lowercased()) ? 2 : 1
|
||||
let vendor = parts[vendorIndex]
|
||||
return vendor.prefix(1).uppercased() + vendor.dropFirst()
|
||||
}
|
||||
if ["com", "org", "net", "io", "us"].contains(first.lowercased()), parts.count > 1 {
|
||||
return parts[1].prefix(1).uppercased() + parts[1].dropFirst()
|
||||
}
|
||||
return first.prefix(1).uppercased() + first.dropFirst()
|
||||
}
|
||||
}
|
||||
|
||||
struct MetricSample: Identifiable {
|
||||
let id = UUID()
|
||||
let date: Date
|
||||
let value: Double
|
||||
}
|
||||
|
||||
struct VolumeSnapshot: Identifiable, Hashable, Sendable {
|
||||
let id: String
|
||||
let name: String
|
||||
let mountPath: String
|
||||
let fileSystem: String
|
||||
let capacity: UInt64
|
||||
let available: UInt64
|
||||
let isEjectable: Bool
|
||||
|
||||
var used: UInt64 { capacity > available ? capacity - available : 0 }
|
||||
var usedPercent: Double { capacity > 0 ? Double(used) / Double(capacity) * 100 : 0 }
|
||||
}
|
||||
|
||||
struct SystemSnapshot {
|
||||
var cpuPercent = 0.0
|
||||
var memoryUsed: UInt64 = 0
|
||||
var memoryTotal: UInt64 = ProcessInfo.processInfo.physicalMemory
|
||||
var memoryActive: UInt64 = 0
|
||||
var memoryWired: UInt64 = 0
|
||||
var memoryCompressed: UInt64 = 0
|
||||
var memoryCached: UInt64 = 0
|
||||
var swapUsed: UInt64 = 0
|
||||
var swapTotal: UInt64 = 0
|
||||
var memoryType = "Unified"
|
||||
var memorySpeed = "Not reported by macOS"
|
||||
var memoryManufacturer = "Apple unified memory"
|
||||
var diskFree: UInt64 = 0
|
||||
var diskTotal: UInt64 = 0
|
||||
var diskReadRate = 0.0
|
||||
var diskWriteRate = 0.0
|
||||
var diskOperationsPerSecond = 0.0
|
||||
var diskLatencyMilliseconds = 0.0
|
||||
var diskActivePercent = 0.0
|
||||
var gpuPercent = 0.0
|
||||
var gpuRendererPercent = 0.0
|
||||
var gpuTilerPercent = 0.0
|
||||
var gpuMemoryBytes: UInt64 = 0
|
||||
var gpuAllocatedBytes: UInt64 = 0
|
||||
var gpuCoreCount = 0
|
||||
var processCount = 0
|
||||
var threadCount = 0
|
||||
var uptime: TimeInterval = ProcessInfo.processInfo.systemUptime
|
||||
var corePercents: [Double] = []
|
||||
var networkReceiveRate = 0.0
|
||||
var networkSendRate = 0.0
|
||||
var networkInterface = "Network"
|
||||
var removableVolumes: [VolumeSnapshot] = []
|
||||
|
||||
var memoryPercent: Double {
|
||||
guard memoryTotal > 0 else { return 0 }
|
||||
return Double(memoryUsed) / Double(memoryTotal) * 100
|
||||
}
|
||||
|
||||
var diskPercent: Double {
|
||||
guard diskTotal > 0 else { return 0 }
|
||||
return Double(diskTotal - diskFree) / Double(diskTotal) * 100
|
||||
}
|
||||
|
||||
var diskThroughput: Double { diskReadRate + diskWriteRate }
|
||||
}
|
||||
|
||||
@MainActor
|
||||
enum Formatters {
|
||||
static let bytes: ByteCountFormatter = {
|
||||
let formatter = ByteCountFormatter()
|
||||
formatter.countStyle = .memory
|
||||
formatter.allowedUnits = [.useKB, .useMB, .useGB, .useTB]
|
||||
return formatter
|
||||
}()
|
||||
|
||||
static func percent(_ value: Double) -> String {
|
||||
value < 10 ? String(format: "%.1f%%", value) : String(format: "%.0f%%", value)
|
||||
}
|
||||
|
||||
static func uptime(_ interval: TimeInterval) -> String {
|
||||
let total = Int(interval)
|
||||
let days = total / 86_400
|
||||
let hours = (total % 86_400) / 3_600
|
||||
let minutes = (total % 3_600) / 60
|
||||
return days > 0 ? "\(days)d \(hours)h \(minutes)m" : "\(hours)h \(minutes)m"
|
||||
}
|
||||
|
||||
static func rate(_ bytesPerSecond: Double) -> String {
|
||||
"\(bytes.string(fromByteCount: Int64(max(0, bytesPerSecond))))/s"
|
||||
}
|
||||
|
||||
static func duration(_ interval: TimeInterval) -> String {
|
||||
let total = max(0, Int(interval.rounded()))
|
||||
let hours = total / 3_600
|
||||
let minutes = (total % 3_600) / 60
|
||||
let seconds = total % 60
|
||||
return hours > 0
|
||||
? String(format: "%d:%02d:%02d", hours, minutes, seconds)
|
||||
: String(format: "%02d:%02d", minutes, seconds)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user