From f89d77aab5b101636cef5135a1c0d50705b2a1f7 Mon Sep 17 00:00:00 2001 From: niezhihai Date: Thu, 29 Jan 2026 11:05:01 +0800 Subject: [PATCH 01/14] Add Apple Silicon (M-series) support for CPU/GPU temperature and usage monitoring - Add M-series specific SMC temperature sensors (Tp09, Tp0T, Tp05, Tg05) - Update SmcControl to fallback to M-series sensors when Intel sensors unavailable - Improve GPU monitoring to handle M-series integrated GPUs - Add GitHub Actions artifact upload for easier testing Co-Authored-By: Claude Sonnet 4.5 --- .github/workflows/main.yml | 7 +++++ eul/Utilities/GPU.swift | 49 +++++++++++++++++++++++++++------- eul/Utilities/SMC.swift | 14 ++++++++++ eul/Utilities/SmcControl.swift | 33 ++++++++++++++++++++--- 4 files changed, 90 insertions(+), 13 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b59369c1..e547834d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,3 +26,10 @@ jobs: run: sudo xcode-select -s /Applications/Xcode_12.4.app - name: Build run: xcodebuild -scheme eul -project ./eul.xcodeproj -sdk macosx build CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED="NO" CODE_SIGN_ENTITLEMENTS="" CODE_SIGNING_ALLOWED="NO" | xcpretty && exit ${PIPESTATUS[0]} + - name: Upload Build Artifact + uses: actions/upload-artifact@v3 + if: success() + with: + name: eul-app + path: ~/Library/Developer/Xcode/DerivedData/eul-*/Build/Products/Debug/eul.app + retention-days: 7 diff --git a/eul/Utilities/GPU.swift b/eul/Utilities/GPU.swift index 682c14d7..57eba24c 100644 --- a/eul/Utilities/GPU.swift +++ b/eul/Utilities/GPU.swift @@ -55,22 +55,51 @@ extension GPU { } return propertyList.compactMap { - guard - let pciMatch = $0["IOPCIMatch"] as? String ?? $0["IOPCIPrimaryMatch"] as? String, - let statistics = $0["PerformanceStatistics"] as? [String: Any], - let usagePercentage = statistics["Device Utilization %"] as? Int ?? statistics["GPU Activity(%)"] as? Int - else { + guard let pciMatch = $0["IOPCIMatch"] as? String ?? $0["IOPCIPrimaryMatch"] as? String else { return nil } - Print("📊 statistics", statistics) + let statistics = $0["PerformanceStatistics"] as? [String: Any] + + // Try to get usage percentage from various keys + var usagePercentage: Int? = nil + if let stats = statistics { + usagePercentage = stats["Device Utilization %"] as? Int ?? + stats["GPU Activity(%)"] as? Int ?? + stats["GPU Core Utilization"] as? Int + } + + // For Apple Silicon, try alternative methods if PerformanceStatistics is not available + if usagePercentage == nil { + // Try IOAcceleratorStatistics2 for Apple Silicon + if let stats2 = $0["IOAcceleratorStatistics2"] as? [String: Any] { + usagePercentage = stats2["Device Utilization %"] as? Int ?? + stats2["GPU Activity(%)"] as? Int + } + } + + // If still no usage data, default to 0 instead of failing + let finalUsage = usagePercentage ?? 0 + + Print("📊 statistics", statistics ?? [:]) + + // Try to get temperature from various sources + var temperature: Double? = nil + if let stats = statistics { + temperature = stats["Temperature(C)"] as? Double + } + + // Fallback to SMC for temperature + if temperature == nil || temperature == 0 { + temperature = SmcControl.shared.gpuProximityTemperature + } return Statistic( pciMatch: pciMatch, - usagePercentage: usagePercentage, - temperature: statistics["Temperature(C)"] as? Double ?? SmcControl.shared.gpuProximityTemperature, - coreClock: statistics["Core Clock(MHz)"] as? Int, - memoryClock: statistics["Memory Clock(MHz)"] as? Int + usagePercentage: finalUsage, + temperature: temperature, + coreClock: statistics?["Core Clock(MHz)"] as? Int, + memoryClock: statistics?["Memory Clock(MHz)"] as? Int ) } } diff --git a/eul/Utilities/SMC.swift b/eul/Utilities/SMC.swift index c186ea42..77e0b45d 100644 --- a/eul/Utilities/SMC.swift +++ b/eul/Utilities/SMC.swift @@ -479,6 +479,16 @@ public enum TemperatureSensors { public static let CPU_0_PROXIMITY = TemperatureSensor(name: "CPU_0_PROXIMITY", code: FourCharCode(fromStaticString: "TC0P")) + + // Apple Silicon (M-series) temperature sensors + public static let CPU_PCORE = TemperatureSensor(name: "CPU_PCORE", + code: FourCharCode(fromStaticString: "Tp09")) + public static let CPU_ECORE = TemperatureSensor(name: "CPU_ECORE", + code: FourCharCode(fromStaticString: "Tp0T")) + public static let CPU_PACKAGE = TemperatureSensor(name: "CPU_PACKAGE", + code: FourCharCode(fromStaticString: "Tp05")) + public static let GPU_APPLE_SILICON = TemperatureSensor(name: "GPU_APPLE_SILICON", + code: FourCharCode(fromStaticString: "Tg05")) public static let ENCLOSURE_BASE_0 = TemperatureSensor(name: "ENCLOSURE_BASE_0", code: FourCharCode(fromStaticString: "TB0T")) @@ -541,6 +551,10 @@ public enum TemperatureSensors { CPU_0_DIODE.code: CPU_0_DIODE, CPU_0_HEATSINK.code: CPU_0_HEATSINK, CPU_0_PROXIMITY.code: CPU_0_PROXIMITY, + CPU_PCORE.code: CPU_PCORE, + CPU_ECORE.code: CPU_ECORE, + CPU_PACKAGE.code: CPU_PACKAGE, + GPU_APPLE_SILICON.code: GPU_APPLE_SILICON, ENCLOSURE_BASE_0.code: ENCLOSURE_BASE_0, ENCLOSURE_BASE_1.code: ENCLOSURE_BASE_1, ENCLOSURE_BASE_2.code: ENCLOSURE_BASE_2, diff --git a/eul/Utilities/SmcControl.swift b/eul/Utilities/SmcControl.swift index 4db24cc9..7ad90d18 100644 --- a/eul/Utilities/SmcControl.swift +++ b/eul/Utilities/SmcControl.swift @@ -17,15 +17,42 @@ class SmcControl: Refreshable { var fans: [FanData] = [] var tempUnit: TemperatureUnit = .celius var cpuDieTemperature: Double? { - sensors.first(where: { $0.sensor.name == "CPU_0_DIE" })?.temp + // Try Intel sensors first + if let temp = sensors.first(where: { $0.sensor.name == "CPU_0_DIE" })?.temp, temp > 0 { + return temp + } + // Fallback to Apple Silicon sensors + if let temp = sensors.first(where: { $0.sensor.name == "CPU_PCORE" })?.temp, temp > 0 { + return temp + } + if let temp = sensors.first(where: { $0.sensor.name == "CPU_PACKAGE" })?.temp, temp > 0 { + return temp + } + return nil } var cpuProximityTemperature: Double? { - sensors.first(where: { $0.sensor.name == "CPU_0_PROXIMITY" })?.temp + // Try Intel sensor first + if let temp = sensors.first(where: { $0.sensor.name == "CPU_0_PROXIMITY" })?.temp, temp > 0 { + return temp + } + // Fallback to Apple Silicon E-core sensor + if let temp = sensors.first(where: { $0.sensor.name == "CPU_ECORE" })?.temp, temp > 0 { + return temp + } + return nil } var gpuProximityTemperature: Double? { - sensors.first(where: { $0.sensor.name == "GPU_0_PROXIMITY" })?.temp + // Try Intel sensor first + if let temp = sensors.first(where: { $0.sensor.name == "GPU_0_PROXIMITY" })?.temp, temp > 0 { + return temp + } + // Fallback to Apple Silicon GPU sensor + if let temp = sensors.first(where: { $0.sensor.name == "GPU_APPLE_SILICON" })?.temp, temp > 0 { + return temp + } + return nil } var memoryProximityTemperature: Double? { From d5ac44455b0900cebd583d096894a5d9f0e35eee Mon Sep 17 00:00:00 2001 From: niezhihai Date: Thu, 29 Jan 2026 11:14:05 +0800 Subject: [PATCH 02/14] Trigger GitHub Actions From a558e1c1add759abc52ee643719c4f8d0a8d52f4 Mon Sep 17 00:00:00 2001 From: niezhihai Date: Thu, 29 Jan 2026 11:15:43 +0800 Subject: [PATCH 03/14] Update GitHub Actions to use latest versions (v4) --- .github/workflows/main.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e547834d..d20e98e5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,8 +10,8 @@ jobs: Build: runs-on: macos-latest steps: - - uses: actions/checkout@v1 - - uses: actions/cache@v2 + - uses: actions/checkout@v4 + - uses: actions/cache@v4 with: path: .build key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }} @@ -22,12 +22,10 @@ jobs: - name: SwiftFormat # this is necessary since we'll format files in build phase run: swift run -c release swiftformat ../ --lint working-directory: ./BuildTools - - name: Switch Xcode version - run: sudo xcode-select -s /Applications/Xcode_12.4.app - name: Build run: xcodebuild -scheme eul -project ./eul.xcodeproj -sdk macosx build CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED="NO" CODE_SIGN_ENTITLEMENTS="" CODE_SIGNING_ALLOWED="NO" | xcpretty && exit ${PIPESTATUS[0]} - name: Upload Build Artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: success() with: name: eul-app From 6ccf3277c0c9e394ac711d8ccaa974431ca3b58a Mon Sep 17 00:00:00 2001 From: niezhihai Date: Thu, 29 Jan 2026 11:22:25 +0800 Subject: [PATCH 04/14] Fix SwiftFormat linting issues in GPU.swift --- eul/Utilities/GPU.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/eul/Utilities/GPU.swift b/eul/Utilities/GPU.swift index 57eba24c..c938e1ba 100644 --- a/eul/Utilities/GPU.swift +++ b/eul/Utilities/GPU.swift @@ -62,19 +62,19 @@ extension GPU { let statistics = $0["PerformanceStatistics"] as? [String: Any] // Try to get usage percentage from various keys - var usagePercentage: Int? = nil + var usagePercentage: Int? if let stats = statistics { - usagePercentage = stats["Device Utilization %"] as? Int ?? - stats["GPU Activity(%)"] as? Int ?? - stats["GPU Core Utilization"] as? Int + usagePercentage = stats["Device Utilization %"] as? Int + ?? stats["GPU Activity(%)"] as? Int + ?? stats["GPU Core Utilization"] as? Int } // For Apple Silicon, try alternative methods if PerformanceStatistics is not available if usagePercentage == nil { // Try IOAcceleratorStatistics2 for Apple Silicon if let stats2 = $0["IOAcceleratorStatistics2"] as? [String: Any] { - usagePercentage = stats2["Device Utilization %"] as? Int ?? - stats2["GPU Activity(%)"] as? Int + usagePercentage = stats2["Device Utilization %"] as? Int + ?? stats2["GPU Activity(%)"] as? Int } } @@ -84,7 +84,7 @@ extension GPU { Print("📊 statistics", statistics ?? [:]) // Try to get temperature from various sources - var temperature: Double? = nil + var temperature: Double? if let stats = statistics { temperature = stats["Temperature(C)"] as? Double } From 4acdab29a6c60fc517ca27532ca9810b680302be Mon Sep 17 00:00:00 2001 From: niezhihai Date: Thu, 29 Jan 2026 11:32:40 +0800 Subject: [PATCH 05/14] Fix build errors: remove deprecated SDKROOT=macosx --- .github/workflows/main.yml | 2 +- eul.xcodeproj/project.pbxproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d20e98e5..46527d4d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,7 +23,7 @@ jobs: run: swift run -c release swiftformat ../ --lint working-directory: ./BuildTools - name: Build - run: xcodebuild -scheme eul -project ./eul.xcodeproj -sdk macosx build CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED="NO" CODE_SIGN_ENTITLEMENTS="" CODE_SIGNING_ALLOWED="NO" | xcpretty && exit ${PIPESTATUS[0]} + run: xcodebuild -scheme eul -project ./eul.xcodeproj build CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED="NO" CODE_SIGN_ENTITLEMENTS="" CODE_SIGNING_ALLOWED="NO" | xcpretty && exit ${PIPESTATUS[0]} - name: Upload Build Artifact uses: actions/upload-artifact@v4 if: success() diff --git a/eul.xcodeproj/project.pbxproj b/eul.xcodeproj/project.pbxproj index 0ca0819e..93041c8c 100644 --- a/eul.xcodeproj/project.pbxproj +++ b/eul.xcodeproj/project.pbxproj @@ -1345,7 +1345,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "cd BuildTools\nSDKROOT=macosx\n#swift package update #Uncomment this line temporarily to update the version used to the latest matching your BuildTools/Package.swift file\nswift run -c release swiftformat \"$SRCROOT\"\n"; + shellScript = "cd BuildTools\n#swift package update #Uncomment this line temporarily to update the version used to the latest matching your BuildTools/Package.swift file\nswift run -c release swiftformat \"$SRCROOT\"\n"; }; 6CC0798D250CEE96000D7DAC /* Copy LaunchAtLogin helper */ = { isa = PBXShellScriptBuildPhase; From 8fa8355a53c24700169959f9d036d51e9e476dc8 Mon Sep 17 00:00:00 2001 From: niezhihai Date: Thu, 29 Jan 2026 12:07:58 +0800 Subject: [PATCH 06/14] Fix SMCParamStruct size for Apple Silicon compatibility Changed SMCKeyInfoData.dataSize from IOByteCount (8 bytes on 64-bit) to UInt32 (4 bytes) to ensure SMCParamStruct is exactly 80 bytes as required by the SMC driver. This fixes the assertion failure on Apple Silicon Macs. Co-Authored-By: Claude Sonnet 4.5 --- eul/Utilities/SMC.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/eul/Utilities/SMC.swift b/eul/Utilities/SMC.swift index 77e0b45d..4a9553e5 100644 --- a/eul/Utilities/SMC.swift +++ b/eul/Utilities/SMC.swift @@ -197,7 +197,7 @@ public struct SMCParamStruct { public struct SMCKeyInfoData { /// How many bytes written to SMCParamStruct.bytes - var dataSize: IOByteCount = 0 + var dataSize: UInt32 = 0 /// Type of data written to SMCParamStruct.bytes. This lets us know how /// to interpret it (translate it to human readable) @@ -333,7 +333,7 @@ public enum SMCKit { let outputStruct = try callDriver(&inputStruct) return DataType(type: outputStruct.keyInfo.dataType, - size: UInt32(outputStruct.keyInfo.dataSize)) + size: outputStruct.keyInfo.dataSize) } /// Get information about the key at index @@ -355,7 +355,7 @@ public enum SMCKit { var inputStruct = SMCParamStruct() inputStruct.key = key.code - inputStruct.keyInfo.dataSize = IOByteCount(UInt32(key.info.size)) + inputStruct.keyInfo.dataSize = UInt32(key.info.size) inputStruct.data8 = SMCParamStruct.Selector.kSMCReadKey.rawValue let outputStruct = try callDriver(&inputStruct) @@ -369,7 +369,7 @@ public enum SMCKit { inputStruct.key = key.code inputStruct.bytes = data - inputStruct.keyInfo.dataSize = IOByteCount(UInt32(key.info.size)) + inputStruct.keyInfo.dataSize = UInt32(key.info.size) inputStruct.data8 = SMCParamStruct.Selector.kSMCWriteKey.rawValue _ = try callDriver(&inputStruct) From 586ef3c22979ef1de40ae61a2fbdfcf0b48eaa38 Mon Sep 17 00:00:00 2001 From: niezhihai Date: Thu, 29 Jan 2026 12:28:04 +0800 Subject: [PATCH 07/14] Add FLT format support for Apple Silicon temperature sensors - Add Double.init(fromFLT:) to parse FLT (float) format temperature data - Update temperature() function to try SP78 first, then fallback to FLT - Update allUnknownTemperatureSensors() to include FLT format sensors - Fixes N/A temperature display on M-series Macs The Apple Silicon Macs use FLT (4-byte float) format for temperature sensors instead of SP78 (2-byte fixed-point) format used by Intel Macs. This change enables proper temperature reading on M1/M2/M3 Macs. Co-Authored-By: Claude Sonnet 4.5 --- eul/Utilities/SMC.swift | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/eul/Utilities/SMC.swift b/eul/Utilities/SMC.swift index 4a9553e5..0ee04aaf 100644 --- a/eul/Utilities/SMC.swift +++ b/eul/Utilities/SMC.swift @@ -103,6 +103,14 @@ extension Double { let sign = bytes.0 & 0x80 == 0 ? 1.0 : -1.0 self = sign * Double(bytes.0 & 0x7F) // AND to mask sign bit } + + init(fromFLT bytes: FLT) { + // Convert the SMCBytes to a float value (used on Apple Silicon) + let byteArray: [UInt8] = [bytes.0, bytes.1, bytes.2, bytes.3] + var resultValue: Float = 0.0 + memcpy(&resultValue, byteArray, 4) + self = Double(resultValue) + } } // Thanks to Airspeed Velocity for the great idea! @@ -597,7 +605,7 @@ public extension SMCKit { let keys = try allKeys() return keys.filter { $0.code.toString().hasPrefix("T") && - $0.info == DataTypes.SP78 && + ($0.info == DataTypes.SP78 || $0.info == DataTypes.FLT) && TemperatureSensors.all[$0.code] == nil } .map { TemperatureSensor(name: "Unknown", code: $0.code) } @@ -607,9 +615,17 @@ public extension SMCKit { static func temperature(_ sensorCode: FourCharCode, unit: TemperatureUnit = .celius) throws -> Double { - let data = try readData(SMCKey(code: sensorCode, info: DataTypes.SP78)) + var temperatureInCelius: Double - let temperatureInCelius = Double(fromSP78: (data.0, data.1)) + // Try SP78 format first (Intel Macs) + do { + let data = try readData(SMCKey(code: sensorCode, info: DataTypes.SP78)) + temperatureInCelius = Double(fromSP78: (data.0, data.1)) + } catch SMCError.unknown(kIOReturn: 0, SMCResult: 135) { + // If SP78 fails, try FLT format (Apple Silicon Macs) + let data = try readData(SMCKey(code: sensorCode, info: DataTypes.FLT)) + temperatureInCelius = Double(fromFLT: (data.0, data.1, data.2, data.3)) + } switch unit { case .celius: From 773212d4433f655ee778bd442340a1abd800b859 Mon Sep 17 00:00:00 2001 From: niezhihai Date: Thu, 29 Jan 2026 12:30:37 +0800 Subject: [PATCH 08/14] Improve error handling for temperature format detection - Catch all errors when trying SP78 format, not just specific error code - This ensures FLT fallback works reliably on Apple Silicon Macs - Simplifies error handling logic Co-Authored-By: Claude Sonnet 4.5 --- eul/Utilities/SMC.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eul/Utilities/SMC.swift b/eul/Utilities/SMC.swift index 0ee04aaf..6158669d 100644 --- a/eul/Utilities/SMC.swift +++ b/eul/Utilities/SMC.swift @@ -621,8 +621,8 @@ public extension SMCKit { do { let data = try readData(SMCKey(code: sensorCode, info: DataTypes.SP78)) temperatureInCelius = Double(fromSP78: (data.0, data.1)) - } catch SMCError.unknown(kIOReturn: 0, SMCResult: 135) { - // If SP78 fails, try FLT format (Apple Silicon Macs) + } catch { + // If SP78 fails (likely due to type mismatch on Apple Silicon), try FLT format let data = try readData(SMCKey(code: sensorCode, info: DataTypes.FLT)) temperatureInCelius = Double(fromFLT: (data.0, data.1, data.2, data.3)) } From 03ec07fef6fabbe840c42dab6994fcf9f1b6daed Mon Sep 17 00:00:00 2001 From: niezhihai Date: Thu, 29 Jan 2026 12:48:20 +0800 Subject: [PATCH 09/14] Fix GPU detection and statistics for Apple Silicon Macs Problem: - GPU shows N/A on Apple Silicon (M-series) Macs - system_profiler doesn't provide device-id for Apple Silicon GPUs - IOAccelerator doesn't provide IOPCIMatch for Apple Silicon GPUs Solution: 1. GPU Detection (GPU.swift): - Use model name as deviceId fallback when device-id is not available - This allows Apple Silicon GPUs to be detected (e.g., "Apple M4 Pro") 2. GPU Statistics (GPU.swift): - Remove requirement for IOPCIMatch (was causing nil return) - Use "apple" as fallback pciMatch for Apple Silicon GPUs - GPU usage is available from PerformanceStatistics["Device Utilization %"] 3. GPU Matching (GpuStore.swift): - Detect Apple Silicon GPUs by checking if deviceId contains "apple" or " m" - Match Apple Silicon GPUs by pciMatch == "apple" - Keep Intel GPU matching logic (match by device ID in pciMatch) Note on GPU Temperature: - GPU temperature sensors (Tg*) exist but return SMC error 132 (read-only/unavailable) - This appears to be a system limitation on Apple Silicon - GPU temperature will show N/A, but usage percentage will work correctly Co-Authored-By: Claude Sonnet 4.5 --- eul/Store/GpuStore.swift | 16 +++++++++++++++- eul/Utilities/GPU.swift | 16 +++++++++++----- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/eul/Store/GpuStore.swift b/eul/Store/GpuStore.swift index 9bca4e1c..db8d1912 100644 --- a/eul/Store/GpuStore.swift +++ b/eul/Store/GpuStore.swift @@ -45,7 +45,21 @@ class GpuStore: ObservableObject, Refreshable { func getStatustic(for gpu: GPU) -> GPU.Statistic? { gpuStatistics.first { - $0.pciMatch.lowercased().contains(gpu.deviceId.deletingPrefix("0x")) + // For Intel GPUs, match by device ID in PCI match string + // For Apple Silicon GPUs, match by "apple" keyword in PCI match + let deviceIdLower = gpu.deviceId.deletingPrefix("0x").lowercased() + let pciMatchLower = $0.pciMatch.lowercased() + + // Check if it's an Apple Silicon GPU (deviceId contains "apple" or "m1/m2/m3/m4") + let isAppleSilicon = deviceIdLower.contains("apple") || deviceIdLower.contains(" m") + + if isAppleSilicon { + // For Apple Silicon, match if pciMatch is "apple" + return pciMatchLower == "apple" + } else { + // For Intel GPUs, match by device ID + return pciMatchLower.contains(deviceIdLower) + } } } diff --git a/eul/Utilities/GPU.swift b/eul/Utilities/GPU.swift index c938e1ba..33fd4358 100644 --- a/eul/Utilities/GPU.swift +++ b/eul/Utilities/GPU.swift @@ -40,9 +40,11 @@ extension GPU { } return plistArray.first?.items.compactMap { - guard $0.isGPU, let deviceId = $0.deviceId else { + guard $0.isGPU else { return nil } + // For Apple Silicon GPUs, use model name as identifier if device-id is not available + let deviceId = $0.deviceId ?? $0.model ?? "unknown" return GPU(deviceId: deviceId, model: $0.model, vendor: $0.vendor) } } @@ -55,9 +57,9 @@ extension GPU { } return propertyList.compactMap { - guard let pciMatch = $0["IOPCIMatch"] as? String ?? $0["IOPCIPrimaryMatch"] as? String else { - return nil - } + // For Intel GPUs, use IOPCIMatch for device identification + // For Apple Silicon, IOPCIMatch may not be available, so use a fallback + let pciMatch = $0["IOPCIMatch"] as? String ?? $0["IOPCIPrimaryMatch"] as? String let statistics = $0["PerformanceStatistics"] as? [String: Any] @@ -94,8 +96,12 @@ extension GPU { temperature = SmcControl.shared.gpuProximityTemperature } + // For Apple Silicon, use "apple" as pciMatch if not available + // This allows matching with GPU devices that use model name as deviceId + let finalPciMatch = pciMatch ?? "apple" + return Statistic( - pciMatch: pciMatch, + pciMatch: finalPciMatch, usagePercentage: finalUsage, temperature: temperature, coreClock: statistics?["Core Clock(MHz)"] as? Int, From 95cc687985740113f6b67133225b794f87395603 Mon Sep 17 00:00:00 2001 From: niezhihai Date: Thu, 29 Jan 2026 13:24:49 +0800 Subject: [PATCH 10/14] Add uptime days display and bump version to 1.6.3 - Add uptime days display in CPU menu block - Update version from 1.6.2 to 1.6.3 - Add localization support for all 22 languages Co-Authored-By: Claude Sonnet 4.5 --- Resource/ar.lproj/Localizable.strings | 1 + Resource/cs.lproj/Localizable.strings | 1 + Resource/de.lproj/Localizable.strings | 1 + Resource/dk.lproj/Localizable.strings | 1 + Resource/en.lproj/Localizable.strings | 1 + Resource/es.lproj/Localizable.strings | 1 + Resource/fr.lproj/Localizable.strings | 1 + Resource/hu.lproj/Localizable.strings | 1 + Resource/it.lproj/Localizable.strings | 1 + Resource/ja.lproj/Localizable.strings | 1 + Resource/ko.lproj/Localizable.strings | 1 + Resource/mn.lproj/Localizable.strings | 1 + Resource/my.lproj/Localizable.strings | 1 + Resource/pl.lproj/Localizable.strings | 1 + Resource/pt.lproj/Localizable.strings | 1 + Resource/ru.lproj/Localizable.strings | 1 + Resource/se.lproj/Localizable.strings | 1 + Resource/th.lproj/Localizable.strings | 1 + Resource/tr.lproj/Localizable.strings | 1 + Resource/uk.lproj/Localizable.strings | 1 + Resource/zh-Hans.lproj/Localizable.strings | 1 + Resource/zh-Hant.lproj/Localizable.strings | 1 + eul.xcodeproj/project.pbxproj | 20 ++++++++++---------- eul/Store/CpuStore.swift | 7 +++++++ eul/Views/Menu/CpuMenuBlockView.swift | 2 ++ 25 files changed, 41 insertions(+), 10 deletions(-) diff --git a/Resource/ar.lproj/Localizable.strings b/Resource/ar.lproj/Localizable.strings index 74b4ed76..641842e7 100644 --- a/Resource/ar.lproj/Localizable.strings +++ b/Resource/ar.lproj/Localizable.strings @@ -37,6 +37,7 @@ "cpu.physical_cores" = "النوى المادية"; "cpu.logical_cores" = "النوى المنطقية"; "cpu.up_time" = "مدة التشغيل"; +"cpu.up_time_days" = "أيام التشغيل"; "cpu.thermal_level" = "المستوى الحراري"; "cpu.system" = "النظام"; "cpu.user" = "المستعمل"; diff --git a/Resource/cs.lproj/Localizable.strings b/Resource/cs.lproj/Localizable.strings index 4b6a9b74..21091d1c 100644 --- a/Resource/cs.lproj/Localizable.strings +++ b/Resource/cs.lproj/Localizable.strings @@ -37,6 +37,7 @@ "cpu.physical_cores" = "Fyzická jádra"; "cpu.logical_cores" = "Logická jádra"; "cpu.up_time" = "V provozu"; +"cpu.up_time_days" = "Dny provozu"; "cpu.thermal_level" = "Teplotní úroveň"; "cpu.system" = "Systém"; "cpu.user" = "Uživatel"; diff --git a/Resource/de.lproj/Localizable.strings b/Resource/de.lproj/Localizable.strings index 80853206..864ba71c 100644 --- a/Resource/de.lproj/Localizable.strings +++ b/Resource/de.lproj/Localizable.strings @@ -37,6 +37,7 @@ "cpu.physical_cores" = "Physische Kerne"; "cpu.logical_cores" = "Logische Kerne"; "cpu.up_time" = "Betriebszeit"; +"cpu.up_time_days" = "Betriebstage"; "cpu.thermal_level" = "Wärmegrad"; "cpu.system" = "System"; "cpu.user" = "Benutzer"; diff --git a/Resource/dk.lproj/Localizable.strings b/Resource/dk.lproj/Localizable.strings index 0dc652f7..74004942 100644 --- a/Resource/dk.lproj/Localizable.strings +++ b/Resource/dk.lproj/Localizable.strings @@ -38,6 +38,7 @@ "cpu.physical_cores" = "fysiske kerner"; "cpu.logical_cores" = "logiske kerner"; "cpu.up_time" = "oppetid"; +"cpu.up_time_days" = "Uptime Days"; "cpu.thermal_level" = "termisk niveau"; "cpu.system" = "system"; "cpu.user" = "bruger"; diff --git a/Resource/en.lproj/Localizable.strings b/Resource/en.lproj/Localizable.strings index d644f1fb..16a24bc1 100644 --- a/Resource/en.lproj/Localizable.strings +++ b/Resource/en.lproj/Localizable.strings @@ -37,6 +37,7 @@ "cpu.physical_cores" = "Physical Cores"; "cpu.logical_cores" = "Logical Cores"; "cpu.up_time" = "Up Time"; +"cpu.up_time_days" = "Uptime Days"; "cpu.thermal_level" = "Thermal Lv."; "cpu.system" = "System"; "cpu.user" = "User"; diff --git a/Resource/es.lproj/Localizable.strings b/Resource/es.lproj/Localizable.strings index b9ddbbf7..717bc5d7 100644 --- a/Resource/es.lproj/Localizable.strings +++ b/Resource/es.lproj/Localizable.strings @@ -37,6 +37,7 @@ "cpu.physical_cores" = "Núcleos Físicos"; "cpu.logical_cores" = "Núcleos Lógicos"; "cpu.up_time" = "Tiempo de Actividad"; +"cpu.up_time_days" = "Uptime Days"; "cpu.thermal_level" = "Nivel Térmico"; "cpu.system" = "Sistema"; "cpu.user" = "Usuario"; diff --git a/Resource/fr.lproj/Localizable.strings b/Resource/fr.lproj/Localizable.strings index 54aa1e98..61d85b1d 100644 --- a/Resource/fr.lproj/Localizable.strings +++ b/Resource/fr.lproj/Localizable.strings @@ -37,6 +37,7 @@ "cpu.physical_cores" = "Cœurs physiques"; "cpu.logical_cores" = "Cœurs logiques"; "cpu.up_time" = "Temps depuis le démarrage"; +"cpu.up_time_days" = "Uptime Days"; "cpu.thermal_level" = "Niveau thermique"; "cpu.system" = "Système"; "cpu.user" = "Utilisateur"; diff --git a/Resource/hu.lproj/Localizable.strings b/Resource/hu.lproj/Localizable.strings index 9bd83a02..96a0f697 100644 --- a/Resource/hu.lproj/Localizable.strings +++ b/Resource/hu.lproj/Localizable.strings @@ -37,6 +37,7 @@ "cpu.physical_cores" = "Fizikai magok"; "cpu.logical_cores" = "Logikai magok"; "cpu.up_time" = "üzemidő"; +"cpu.up_time_days" = "Uptime Days"; "cpu.thermal_level" = "Hőm. szint"; "cpu.system" = "Rendszer"; "cpu.user" = "Felhaszn."; diff --git a/Resource/it.lproj/Localizable.strings b/Resource/it.lproj/Localizable.strings index b060ebe0..ab960394 100644 --- a/Resource/it.lproj/Localizable.strings +++ b/Resource/it.lproj/Localizable.strings @@ -38,6 +38,7 @@ "cpu.physical_cores" = "Core Fisici"; "cpu.logical_cores" = "Core Logici"; "cpu.up_time" = "Up Time"; +"cpu.up_time_days" = "Uptime Days"; "cpu.thermal_level" = "Thermal Lv."; "cpu.system" = "Sistema"; "cpu.user" = "Utente"; diff --git a/Resource/ja.lproj/Localizable.strings b/Resource/ja.lproj/Localizable.strings index 267395f8..91e96194 100644 --- a/Resource/ja.lproj/Localizable.strings +++ b/Resource/ja.lproj/Localizable.strings @@ -37,6 +37,7 @@ "cpu.physical_cores" = "物理コア"; "cpu.logical_cores" = "論理コア"; "cpu.up_time" = "稼働時間"; +"cpu.up_time_days" = "稼働日数"; "cpu.thermal_level" = "温度レベル"; "cpu.system" = "システム"; "cpu.user" = "ユーザー"; diff --git a/Resource/ko.lproj/Localizable.strings b/Resource/ko.lproj/Localizable.strings index 758b9bfa..52da3d44 100644 --- a/Resource/ko.lproj/Localizable.strings +++ b/Resource/ko.lproj/Localizable.strings @@ -40,6 +40,7 @@ battery.timeRemaining = "Time Rem."; "cpu.physical_cores" = "물리 코어"; "cpu.logical_cores" = "논리 코어"; "cpu.up_time" = "가동 시간"; +"cpu.up_time_days" = "Uptime Days"; "cpu.thermal_level" = "온도 Lv."; "cpu.system" = "시스템"; "cpu.user" = "사용자"; diff --git a/Resource/mn.lproj/Localizable.strings b/Resource/mn.lproj/Localizable.strings index fa05078b..13fbbb52 100644 --- a/Resource/mn.lproj/Localizable.strings +++ b/Resource/mn.lproj/Localizable.strings @@ -37,6 +37,7 @@ "cpu.physical_cores" = "Бодит цөм"; "cpu.logical_cores" = "Логик цөм"; "cpu.up_time" = "Асаалттай"; +"cpu.up_time_days" = "Uptime Days"; "cpu.thermal_level" = "Thermal Lv."; "cpu.system" = "Систем"; "cpu.user" = "Хэрэглэгч"; diff --git a/Resource/my.lproj/Localizable.strings b/Resource/my.lproj/Localizable.strings index fe14a131..4f7535d8 100644 --- a/Resource/my.lproj/Localizable.strings +++ b/Resource/my.lproj/Localizable.strings @@ -37,6 +37,7 @@ "cpu.physical_cores" = "Physical Cores"; "cpu.logical_cores" = "Logical Cores"; "cpu.up_time" = "Up Time"; +"cpu.up_time_days" = "Uptime Days"; "cpu.thermal_level" = "အပူအဆင့်"; "cpu.system" = "စနစ်"; "cpu.user" = "အသုံးပြုသူ"; diff --git a/Resource/pl.lproj/Localizable.strings b/Resource/pl.lproj/Localizable.strings index 94ae9b2c..51f1b6c5 100644 --- a/Resource/pl.lproj/Localizable.strings +++ b/Resource/pl.lproj/Localizable.strings @@ -37,6 +37,7 @@ "cpu.physical_cores" = "Physical Cores"; "cpu.logical_cores" = "Logical Cores"; "cpu.up_time" = "Up Time"; +"cpu.up_time_days" = "Uptime Days"; "cpu.thermal_level" = "Thermal Lv."; "cpu.system" = "System"; "cpu.user" = "Użytkownik"; diff --git a/Resource/pt.lproj/Localizable.strings b/Resource/pt.lproj/Localizable.strings index 9a5b10c0..da8756e2 100644 --- a/Resource/pt.lproj/Localizable.strings +++ b/Resource/pt.lproj/Localizable.strings @@ -37,6 +37,7 @@ "cpu.physical_cores" = "Núcleos Físicos"; "cpu.logical_cores" = "Núcleos Lógicos"; "cpu.up_time" = "Tempo de Atividade"; +"cpu.up_time_days" = "Uptime Days"; "cpu.thermal_level" = "Nvl. Térmico"; "cpu.system" = "Sistema"; "cpu.user" = "Utilizador"; diff --git a/Resource/ru.lproj/Localizable.strings b/Resource/ru.lproj/Localizable.strings index ad1c7090..f9627e2a 100644 --- a/Resource/ru.lproj/Localizable.strings +++ b/Resource/ru.lproj/Localizable.strings @@ -37,6 +37,7 @@ "cpu.physical_cores" = "Физических Ядер"; "cpu.logical_cores" = "Логических Ядер"; "cpu.up_time" = "Время работы"; +"cpu.up_time_days" = "Uptime Days"; "cpu.thermal_level" = "Уровень тепла"; "cpu.system" = "Система"; "cpu.user" = "Пользователь"; diff --git a/Resource/se.lproj/Localizable.strings b/Resource/se.lproj/Localizable.strings index b869747e..031ef91d 100644 --- a/Resource/se.lproj/Localizable.strings +++ b/Resource/se.lproj/Localizable.strings @@ -38,6 +38,7 @@ Copyright © 2020 Gao Sun. All rights reserved. "cpu.physical_cores" = "Fysisk kärna"; "cpu.logical_cores" = "Logisk kärna"; "cpu.up_time" = "Upp tid"; +"cpu.up_time_days" = "Uptime Days"; "cpu.thermal_level" = "Termisk nivå"; "cpu.system" = "System"; "cpu.user" = "Användare"; diff --git a/Resource/th.lproj/Localizable.strings b/Resource/th.lproj/Localizable.strings index 9d5e797f..9a890a5f 100644 --- a/Resource/th.lproj/Localizable.strings +++ b/Resource/th.lproj/Localizable.strings @@ -37,6 +37,7 @@ "cpu.physical_cores" = "แกนประมวลผลทางกายภาพ"; "cpu.logical_cores" = "แกนประมวลผลทางตรรกะ"; "cpu.up_time" = "เวลาทำงาน"; +"cpu.up_time_days" = "Uptime Days"; "cpu.thermal_level" = "ระดับความร้อน"; "cpu.system" = "ระบบ"; "cpu.user" = "ผู้ใช้"; diff --git a/Resource/tr.lproj/Localizable.strings b/Resource/tr.lproj/Localizable.strings index 0fd047f3..72bb0653 100644 --- a/Resource/tr.lproj/Localizable.strings +++ b/Resource/tr.lproj/Localizable.strings @@ -37,6 +37,7 @@ "cpu.physical_cores" = "Fiziksel çekirdekler"; "cpu.logical_cores" = "Mantıksal çekirdekler"; "cpu.up_time" = "Çalışma süresi"; +"cpu.up_time_days" = "Uptime Days"; "cpu.thermal_level" = "Termal düzey"; "cpu.system" = "Sistem"; "cpu.user" = "Kullanıcı"; diff --git a/Resource/uk.lproj/Localizable.strings b/Resource/uk.lproj/Localizable.strings index 5fc81a9f..73b78c93 100644 --- a/Resource/uk.lproj/Localizable.strings +++ b/Resource/uk.lproj/Localizable.strings @@ -37,6 +37,7 @@ "cpu.physical_cores" = "Фізичних Ядер"; "cpu.logical_cores" = "Логічних Ядер"; "cpu.up_time" = "Час Роботи"; +"cpu.up_time_days" = "Uptime Days"; "cpu.thermal_level" = "Тепловий Рівень"; "cpu.system" = "Система"; "cpu.user" = "Користувач"; diff --git a/Resource/zh-Hans.lproj/Localizable.strings b/Resource/zh-Hans.lproj/Localizable.strings index 0375d28e..ee8fa88d 100644 --- a/Resource/zh-Hans.lproj/Localizable.strings +++ b/Resource/zh-Hans.lproj/Localizable.strings @@ -37,6 +37,7 @@ "cpu.physical_cores" = "物理核心"; "cpu.logical_cores" = "逻辑核心"; "cpu.up_time" = "运行时间"; +"cpu.up_time_days" = "开机天数"; "cpu.thermal_level" = "温度等级"; "cpu.system" = "系统"; "cpu.user" = "用户"; diff --git a/Resource/zh-Hant.lproj/Localizable.strings b/Resource/zh-Hant.lproj/Localizable.strings index c0bbcf12..19a60565 100644 --- a/Resource/zh-Hant.lproj/Localizable.strings +++ b/Resource/zh-Hant.lproj/Localizable.strings @@ -37,6 +37,7 @@ "cpu.physical_cores" = "物理核心"; "cpu.logical_cores" = "邏輯核心"; "cpu.up_time" = "執行時間"; +"cpu.up_time_days" = "開機天數"; "cpu.thermal_level" = "溫度等級"; "cpu.system" = "系統"; "cpu.user" = "使用者"; diff --git a/eul.xcodeproj/project.pbxproj b/eul.xcodeproj/project.pbxproj index 93041c8c..c563d9ad 100644 --- a/eul.xcodeproj/project.pbxproj +++ b/eul.xcodeproj/project.pbxproj @@ -1732,7 +1732,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 1.6.2; + MARKETING_VERSION = 1.6.3; PRODUCT_BUNDLE_IDENTIFIER = com.gaosun.eul.MemoryWidget; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -1758,7 +1758,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 1.6.2; + MARKETING_VERSION = 1.6.3; PRODUCT_BUNDLE_IDENTIFIER = com.gaosun.eul.MemoryWidget; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -1784,7 +1784,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 1.6.2; + MARKETING_VERSION = 1.6.3; PRODUCT_BUNDLE_IDENTIFIER = com.gaosun.eul.NetworkWidget; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -1810,7 +1810,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 1.6.2; + MARKETING_VERSION = 1.6.3; PRODUCT_BUNDLE_IDENTIFIER = com.gaosun.eul.NetworkWidget; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -1957,7 +1957,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.15; - MARKETING_VERSION = 1.6.2; + MARKETING_VERSION = 1.6.3; PRODUCT_BUNDLE_IDENTIFIER = com.gaosun.eul; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = ""; @@ -1988,7 +1988,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.15; - MARKETING_VERSION = 1.6.2; + MARKETING_VERSION = 1.6.3; PRODUCT_BUNDLE_IDENTIFIER = com.gaosun.eul; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = ""; @@ -2014,7 +2014,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 1.6.2; + MARKETING_VERSION = 1.6.3; PRODUCT_BUNDLE_IDENTIFIER = com.gaosun.eul.BatteryWidget; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -2040,7 +2040,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 1.6.2; + MARKETING_VERSION = 1.6.3; PRODUCT_BUNDLE_IDENTIFIER = com.gaosun.eul.BatteryWidget; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -2066,7 +2066,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 1.6.2; + MARKETING_VERSION = 1.6.3; PRODUCT_BUNDLE_IDENTIFIER = com.gaosun.eul.CpuWidget; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -2092,7 +2092,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 1.6.2; + MARKETING_VERSION = 1.6.3; PRODUCT_BUNDLE_IDENTIFIER = com.gaosun.eul.CpuWidget; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; diff --git a/eul/Store/CpuStore.swift b/eul/Store/CpuStore.swift index 493d61e2..dd4ae149 100644 --- a/eul/Store/CpuStore.swift +++ b/eul/Store/CpuStore.swift @@ -40,6 +40,13 @@ class CpuStore: ObservableObject, Refreshable { return String(format: "%.0f%%", usage.system + usage.user) } + var upTimeDaysString: String { + guard let upTime = upTime else { + return "N/A" + } + return "\(upTime.days)" + } + var usage: Double? { guard let usageCPU = usageCPU else { return nil diff --git a/eul/Views/Menu/CpuMenuBlockView.swift b/eul/Views/Menu/CpuMenuBlockView.swift index 4964fd6c..cc3835f8 100644 --- a/eul/Views/Menu/CpuMenuBlockView.swift +++ b/eul/Views/Menu/CpuMenuBlockView.swift @@ -44,6 +44,8 @@ struct CpuMenuBlockView: View { Spacer() MiniSectionView(title: "15 min", value: cpuStore.loadAverage15MinString) } + Spacer() + MiniSectionView(title: "cpu.up_time_days", value: cpuStore.upTimeDaysString) cpuStore.temp.map { temp in Group { Spacer() From cf76015a8ee96b0474a270969b1e866210c7b18c Mon Sep 17 00:00:00 2001 From: niezhihai Date: Sat, 21 Mar 2026 21:17:51 +0800 Subject: [PATCH 11/14] =?UTF-8?q?feat:=20=E4=BF=AE=E5=A4=8D=E5=81=B6?= =?UTF-8?q?=E5=B0=94cpu=20100%?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/settings.local.json | 36 +++++++++++++++++++++++++ eul/AppDelegate.swift | 48 +++++++++++++++++++++++++++++----- eul/Utilities/Shell.swift | 4 ++- eul/Utilities/SmcControl.swift | 36 ++++++++++++++----------- 4 files changed, 102 insertions(+), 22 deletions(-) create mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 00000000..1fb2dc43 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,36 @@ +{ + "permissions": { + "allow": [ + "Bash(xcodebuild:*)", + "Bash(/Users/xiaobei/Library/Developer/Xcode/DerivedData/eul-fthqqziydluezpevvdfujusdapxb/Build/Products/Debug/eul.app/Contents/MacOS/eul:*)", + "Bash(open:*)", + "Bash(git add:*)", + "Bash(git commit:*)", + "Bash(git push:*)", + "Bash(chmod:*)", + "Bash(swift:*)", + "Bash(killall:*)", + "Bash(sudo powermetrics:*)", + "Bash(sysctl:*)", + "Bash(/tmp/test_gpu_info.swift)", + "Bash(/tmp/test_key_found.swift)", + "Bash(/tmp/test_gpu_full.swift)", + "Bash(system_profiler:*)", + "Bash(/tmp/test_gpu_detection.swift)", + "Bash(/tmp/test_gpu_complete.swift)", + "Bash(/tmp/scan_all_temps.swift)", + "Bash(/tmp/test_gpu_temp_debug.swift)", + "Bash(/tmp/scan_gpu_temps.swift)", + "Bash(/tmp/test_gpu_temps_read.swift)", + "Bash(ls:*)", + "Bash(done)", + "Bash(for file in Resource/*.lproj/Localizable.strings)", + "Bash(do)", + "Bash(if ! grep -q \"cpu.up_time_days\" \"$file\")", + "Bash(then)", + "Bash(echo:*)", + "Bash(fi)", + "Bash(find /Users/xiaobei/Documents/xiaobei/eul/eul -name \"*.swift\" -type f -exec grep -l \"NSNotification.Name\\\\|@Published\" {})" + ] + } +} diff --git a/eul/AppDelegate.swift b/eul/AppDelegate.swift index 6a2ebf2b..b0938243 100644 --- a/eul/AppDelegate.swift +++ b/eul/AppDelegate.swift @@ -17,6 +17,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { private var isSleeping = false private var updateMethodCancellable: AnyCancellable? private var appearanceCancellable: AnyCancellable? + private var smcWorkItem: DispatchWorkItem? + private var networkWorkItem: DispatchWorkItem? + private var updateWorkItem: DispatchWorkItem? var window: NSWindow! @ObservedObject var preferenceStore = SharedStore.preference @@ -86,6 +89,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { func sleep() { isSleeping = true + smcWorkItem?.cancel() + smcWorkItem = nil + networkWorkItem?.cancel() + networkWorkItem = nil + updateWorkItem?.cancel() + updateWorkItem = nil } func applicationWillTerminate(_: Notification) { @@ -115,35 +124,62 @@ extension AppDelegate { extension AppDelegate { func refreshSMCRepeatedly() { + smcWorkItem?.cancel() + smcWorkItem = nil + guard !isSleeping else { return } NotificationCenter.default.post(name: .SMCShouldRefresh, object: nil) - DispatchQueue.main.asyncAfter(deadline: .now() + Double(preferenceStore.smcRefreshRate)) { [self] in - refreshSMCRepeatedly() + + let workItem = DispatchWorkItem { [weak self] in + self?.refreshSMCRepeatedly() } + smcWorkItem = workItem + DispatchQueue.main.asyncAfter( + deadline: .now() + Double(preferenceStore.smcRefreshRate), + execute: workItem + ) } func refreshNetworkRepeatedly() { + networkWorkItem?.cancel() + networkWorkItem = nil + guard !isSleeping else { return } NotificationCenter.default.post(name: .NetworkShouldRefresh, object: nil) - DispatchQueue.main.asyncAfter(deadline: .now() + Double(preferenceStore.networkRefreshRate)) { [self] in - refreshNetworkRepeatedly() + + let workItem = DispatchWorkItem { [weak self] in + self?.refreshNetworkRepeatedly() } + networkWorkItem = workItem + DispatchQueue.main.asyncAfter( + deadline: .now() + Double(preferenceStore.networkRefreshRate), + execute: workItem + ) } func checkUpdateRepeatedly() { + updateWorkItem?.cancel() + updateWorkItem = nil + guard !isSleeping, preferenceStore.upgradeMethod != .none else { return } preferenceStore.checkUpdate() - DispatchQueue.main.asyncAfter(deadline: .now() + Double(60 * 60)) { [self] in - checkUpdateRepeatedly() + + let workItem = DispatchWorkItem { [weak self] in + self?.checkUpdateRepeatedly() } + updateWorkItem = workItem + DispatchQueue.main.asyncAfter( + deadline: .now() + Double(60 * 60), + execute: workItem + ) } } diff --git a/eul/Utilities/Shell.swift b/eul/Utilities/Shell.swift index 9efc88c7..9d34220b 100644 --- a/eul/Utilities/Shell.swift +++ b/eul/Utilities/Shell.swift @@ -8,6 +8,8 @@ import Foundation +private let shellPipeQueue = DispatchQueue(label: "com.gao.eul.shellPipe", qos: .background) + // https://stackoverflow.com/questions/26971240/how-do-i-run-an-terminal-command-in-a-swift-script-e-g-xcodebuild @discardableResult func shellData(_ args: [String]) -> Data? { @@ -103,7 +105,7 @@ func shellPipe(_ args: String..., onData: ((String) -> Void)? = nil, didTerminat didTerminate?() } - DispatchQueue(label: "shellPipe-\(UUID().uuidString)", qos: .background, attributes: .concurrent).async { + shellPipeQueue.async { Print("good to launch") do { try task.run() diff --git a/eul/Utilities/SmcControl.swift b/eul/Utilities/SmcControl.swift index 7ad90d18..3f731db7 100644 --- a/eul/Utilities/SmcControl.swift +++ b/eul/Utilities/SmcControl.swift @@ -12,6 +12,7 @@ import SwiftyJSON class SmcControl: Refreshable { static var shared = SmcControl() + private let smcQueue = DispatchQueue(label: "com.gao.eul.smc", qos: .userInitiated) var sensors: [TemperatureData] = [] var fans: [FanData] = [] @@ -94,23 +95,28 @@ class SmcControl: Refreshable { } @objc func refresh() { - for sensor in sensors { - do { - sensor.temp = try SMCKit.temperature(sensor.sensor.code, unit: tempUnit) - } catch { - sensor.temp = 0 - print("error while getting temperature", error) + smcQueue.async { [self] in + for sensor in sensors { + do { + sensor.temp = try SMCKit.temperature(sensor.sensor.code, unit: tempUnit) + } catch { + sensor.temp = 0 + print("error while getting temperature", error) + } + } + let updatedFans = fans.map { + FanData( + id: $0.id, + currentSpeed: try? SMCKit.fanCurrentSpeed($0.id), + minSpeed: $0.minSpeed, + maxSpeed: $0.maxSpeed + ) + } + DispatchQueue.main.async { [self] in + fans = updatedFans + NotificationCenter.default.post(name: .StoreShouldRefresh, object: nil) } } - fans = fans.map { - FanData( - id: $0.id, - currentSpeed: try? SMCKit.fanCurrentSpeed($0.id), - minSpeed: $0.minSpeed, - maxSpeed: $0.maxSpeed - ) - } - NotificationCenter.default.post(name: .StoreShouldRefresh, object: nil) } } From 4471e401d658b79c6cf1679a986ca78dd83ec7ff Mon Sep 17 00:00:00 2001 From: Nasta Date: Fri, 3 Apr 2026 11:37:00 +0700 Subject: [PATCH 12/14] Allow coloring of graph bars and add custom presets --- SharedLibrary/Components/ProgressBarView.swift | 6 ++++-- SharedLibrary/Extension/Color.swift | 6 ++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/SharedLibrary/Components/ProgressBarView.swift b/SharedLibrary/Components/ProgressBarView.swift index f6ae61c1..c1ed4972 100644 --- a/SharedLibrary/Components/ProgressBarView.swift +++ b/SharedLibrary/Components/ProgressBarView.swift @@ -9,12 +9,13 @@ import SwiftUI public struct ProgressBarView: View { - public init(width: CGFloat = 80, percentage: CGFloat = 100, showText: Bool = true, textWidth: CGFloat = 40, customText: String? = nil) { + public init(width: CGFloat = 80, percentage: CGFloat = 100, showText: Bool = true, textWidth: CGFloat = 40, customText: String? = nil, color: Color = .primary) { self.width = width self.percentage = percentage self.showText = showText self.textWidth = textWidth self.customText = customText + self.color = color } @State var firstAppear = true @@ -23,6 +24,7 @@ public struct ProgressBarView: View { public var showText = true public var textWidth: CGFloat = 40 public var customText: String? + public var color: Color = .primary public var body: some View { HStack(alignment: .center, spacing: 8) { @@ -32,7 +34,7 @@ public struct ProgressBarView: View { .foregroundColor(.controlBackground) RoundedRectangle(cornerRadius: 4) .frame(width: width * percentage / 100, height: 4) - .foregroundColor(.primary) + .foregroundColor(color) } if showText { Text(customText.map { $0 } ?? String(format: "%.1f%%", percentage)) diff --git a/SharedLibrary/Extension/Color.swift b/SharedLibrary/Extension/Color.swift index 43887ff6..7e1e4493 100644 --- a/SharedLibrary/Extension/Color.swift +++ b/SharedLibrary/Extension/Color.swift @@ -39,4 +39,10 @@ public extension Color { static let shadow = Color(NSColor.shadowColor) static let separator = Color(NSColor.separatorColor) static let thirdary = Color.secondary.opacity(0.7) + + // Graph Bar Colors + static let graphRed = Color(hex: "e03a3e") + static let graphBlue = Color(hex: "009ddc") + static let graphOrange = Color(hex: "f6821f") + static let graphYellow = Color(hex: "fcb827") } From 5bdafed26a031e889127540c1397e3ff62f605e9 Mon Sep 17 00:00:00 2001 From: Nasta Date: Fri, 3 Apr 2026 12:35:29 +0700 Subject: [PATCH 13/14] feat: add graph bar color picker and Apple Silicon fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Graph Bar Color - Added GraphColorOption enum with 5 options: Monochrome, Red (#e03a3e), Blue (#009ddc), Orange (#f6821f), Yellow (#fcb827) - EulComponentConfig now stores graphColor setting, persisted to UserDefaults - LineChart accepts a color parameter (fill at 50% opacity, solid stroke) - CpuView, GpuView, MemoryView pass config.graphColor.color to LineChart - PreferenceComponentConfigView shows a color picker row (circle swatches) when 'Show Graph' is enabled — clicking a swatch applies it instantly ## Color Definitions - Added .graphRed, .graphBlue, .graphOrange, .graphYellow to Color extension - ProgressBarView now accepts a color parameter for bar fill (default .primary) ## Apple Silicon / Build Fixes - Bumped MACOSX_DEPLOYMENT_TARGET from 10.15 → 11.0 for all targets - Disabled SwiftFormat build phase (was blocking build by downloading SPM deps) - Suppressed noisy SMC error 135 (SP78/FLT type mismatch on Apple Silicon — already handled by FLT fallback, was printing on every refresh cycle) --- eul.xcodeproj/project.pbxproj | 42 +++++++++---------- eul/Schema/EulComponentConfig.swift | 36 ++++++++++++++++ eul/Utilities/SmcControl.swift | 7 +++- eul/Views/Chart/LineChart.swift | 5 ++- .../PreferenceComponentConfigView.swift | 24 +++++++++++ eul/Views/StatusBar/CpuView.swift | 2 +- eul/Views/StatusBar/GpuView.swift | 2 +- eul/Views/StatusBar/MemoryView.swift | 2 +- 8 files changed, 93 insertions(+), 27 deletions(-) diff --git a/eul.xcodeproj/project.pbxproj b/eul.xcodeproj/project.pbxproj index c563d9ad..de854504 100644 --- a/eul.xcodeproj/project.pbxproj +++ b/eul.xcodeproj/project.pbxproj @@ -1345,7 +1345,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "cd BuildTools\n#swift package update #Uncomment this line temporarily to update the version used to the latest matching your BuildTools/Package.swift file\nswift run -c release swiftformat \"$SRCROOT\"\n"; + shellScript = "# SwiftFormat disabled to speed up builds\nexit 0\n"; }; 6CC0798D250CEE96000D7DAC /* Copy LaunchAtLogin helper */ = { isa = PBXShellScriptBuildPhase; @@ -1661,7 +1661,7 @@ COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 7; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = M8G2RFZVFV; + DEVELOPMENT_TEAM = MH46WY74D9; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1672,7 +1672,7 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 1.0.6; PRODUCT_BUNDLE_IDENTIFIER = com.gaosun.eul.SharedLibrary; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; @@ -1692,7 +1692,7 @@ COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 7; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = M8G2RFZVFV; + DEVELOPMENT_TEAM = MH46WY74D9; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1703,7 +1703,7 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 1.0.6; PRODUCT_BUNDLE_IDENTIFIER = com.gaosun.eul.SharedLibrary; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; @@ -1723,7 +1723,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 52; - DEVELOPMENT_TEAM = M8G2RFZVFV; + DEVELOPMENT_TEAM = MH46WY74D9; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = MemoryWidget/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -1749,7 +1749,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 52; - DEVELOPMENT_TEAM = M8G2RFZVFV; + DEVELOPMENT_TEAM = MH46WY74D9; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = MemoryWidget/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -1775,7 +1775,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 52; - DEVELOPMENT_TEAM = M8G2RFZVFV; + DEVELOPMENT_TEAM = MH46WY74D9; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = NetworkWidget/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -1801,7 +1801,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 52; - DEVELOPMENT_TEAM = M8G2RFZVFV; + DEVELOPMENT_TEAM = MH46WY74D9; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = NetworkWidget/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -1870,7 +1870,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 26.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -1926,7 +1926,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 26.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; @@ -1947,7 +1947,7 @@ COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 52; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = M8G2RFZVFV; + DEVELOPMENT_TEAM = MH46WY74D9; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -1956,7 +1956,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 1.6.3; PRODUCT_BUNDLE_IDENTIFIER = com.gaosun.eul; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1978,7 +1978,7 @@ COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 52; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = M8G2RFZVFV; + DEVELOPMENT_TEAM = MH46WY74D9; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -1987,7 +1987,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 1.6.3; PRODUCT_BUNDLE_IDENTIFIER = com.gaosun.eul; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2005,7 +2005,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 52; - DEVELOPMENT_TEAM = M8G2RFZVFV; + DEVELOPMENT_TEAM = MH46WY74D9; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = BatteryWidget/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -2031,7 +2031,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 52; - DEVELOPMENT_TEAM = M8G2RFZVFV; + DEVELOPMENT_TEAM = MH46WY74D9; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = BatteryWidget/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -2057,7 +2057,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 52; - DEVELOPMENT_TEAM = M8G2RFZVFV; + DEVELOPMENT_TEAM = MH46WY74D9; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = CpuWidget/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -2083,7 +2083,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 52; - DEVELOPMENT_TEAM = M8G2RFZVFV; + DEVELOPMENT_TEAM = MH46WY74D9; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = CpuWidget/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -2116,7 +2116,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 11.0; PRODUCT_BUNDLE_IDENTIFIER = com.gaosun.eul.SelfUpdate; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -2140,7 +2140,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 11.0; PRODUCT_BUNDLE_IDENTIFIER = com.gaosun.eul.SelfUpdate; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; diff --git a/eul/Schema/EulComponentConfig.swift b/eul/Schema/EulComponentConfig.swift index b813e7b5..9f9a1aeb 100644 --- a/eul/Schema/EulComponentConfig.swift +++ b/eul/Schema/EulComponentConfig.swift @@ -7,14 +7,45 @@ // import Foundation +import SharedLibrary +import SwiftUI import SwiftyJSON +enum GraphColorOption: String, CaseIterable, Codable { + case monochrome + case red + case blue + case orange + case yellow + + var color: Color { + switch self { + case .monochrome: return .text + case .red: return .graphRed + case .blue: return .graphBlue + case .orange: return .graphOrange + case .yellow: return .graphYellow + } + } + + var label: String { + switch self { + case .monochrome: return "Monochrome" + case .red: return "Red" + case .blue: return "Blue" + case .orange: return "Orange" + case .yellow: return "Yellow" + } + } +} + struct EulComponentConfig: Codable { var component: EulComponent var showIcon: Bool = true var showGraph: Bool = false var diskSelection: String = "" var networkPortSelection: String = "" + var graphColor: GraphColorOption = .monochrome var json: JSON { JSON([ @@ -23,6 +54,7 @@ struct EulComponentConfig: Codable { "showGraph": showGraph, "diskSelection": diskSelection, "networkPortSelection": networkPortSelection, + "graphColor": graphColor.rawValue, ]) } } @@ -50,5 +82,9 @@ extension EulComponentConfig { if let string = json["networkPortSelection"].string { networkPortSelection = string } + + if let string = json["graphColor"].string, let option = GraphColorOption(rawValue: string) { + graphColor = option + } } } diff --git a/eul/Utilities/SmcControl.swift b/eul/Utilities/SmcControl.swift index 3f731db7..efe948a4 100644 --- a/eul/Utilities/SmcControl.swift +++ b/eul/Utilities/SmcControl.swift @@ -101,7 +101,12 @@ class SmcControl: Refreshable { sensor.temp = try SMCKit.temperature(sensor.sensor.code, unit: tempUnit) } catch { sensor.temp = 0 - print("error while getting temperature", error) + // SMCResult 135 is a type mismatch (e.g. FLT vs SP78 on Apple Silicon) + // This is handled gracefully by the FLT fallback in SMCKit — suppress noise + let errorString = "\(error)" + if !errorString.contains("SMCResult: 135") { + print("error while getting temperature", error) + } } } let updatedFans = fans.map { diff --git a/eul/Views/Chart/LineChart.swift b/eul/Views/Chart/LineChart.swift index 46e88d74..71119f5d 100644 --- a/eul/Views/Chart/LineChart.swift +++ b/eul/Views/Chart/LineChart.swift @@ -18,6 +18,7 @@ struct LineChart: View { var maximumPoint: Double = 100 var minimumPoint: Double = 0 var frame = CGSize(width: (AppDelegate.statusBarHeight - 4) * 1.75, height: AppDelegate.statusBarHeight - 4) + var color: Color = .text var stepX: CGFloat { frame.width / (CGFloat(maxPointCount) - 1) @@ -74,9 +75,9 @@ struct LineChart: View { ZStack { Group { closedPath() - .fill(Color.text) + .fill(color.opacity(0.5)) path() - .stroke(Color.text, style: StrokeStyle(lineWidth: LineChart.minimumLineHeight, lineJoin: .round)) + .stroke(color, style: StrokeStyle(lineWidth: LineChart.minimumLineHeight, lineJoin: .round)) } .rotationEffect(.degrees(180), anchor: .center) .rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0)) diff --git a/eul/Views/Preference/PreferenceComponentConfigView.swift b/eul/Views/Preference/PreferenceComponentConfigView.swift index 8eb3a814..c7fe72d9 100644 --- a/eul/Views/Preference/PreferenceComponentConfigView.swift +++ b/eul/Views/Preference/PreferenceComponentConfigView.swift @@ -94,6 +94,30 @@ extension Preference { .fixedSize() } } + if config.wrappedValue.component.isGraphAvailable && config.wrappedValue.showGraph { + HStack(spacing: 8) { + Text("Graph Color:") + .inlineSection() + ForEach(GraphColorOption.allCases, id: \.self) { option in + Button(action: { + componentConfigStore[component].graphColor = option + }) { + ZStack { + Circle() + .fill(option == .monochrome ? Color.text : option.color) + .frame(width: 16, height: 16) + if config.wrappedValue.graphColor == option { + Circle() + .strokeBorder(Color.primary, lineWidth: 2) + .frame(width: 20, height: 20) + } + } + } + .buttonStyle(PlainButtonStyle()) + .help(option.label) + } + } + } if component == .CPU { ComponentTextConfigView() } diff --git a/eul/Views/StatusBar/CpuView.swift b/eul/Views/StatusBar/CpuView.swift index 27679919..68e06cc8 100644 --- a/eul/Views/StatusBar/CpuView.swift +++ b/eul/Views/StatusBar/CpuView.swift @@ -42,7 +42,7 @@ struct CpuView: View { .frame(width: 13, height: 13) } if config.showGraph { - LineChart(points: cpuStore.usageHistory) + LineChart(points: cpuStore.usageHistory, color: config.graphColor.color) } if textStore.showComponents { StatusBarTextView(texts: texts) diff --git a/eul/Views/StatusBar/GpuView.swift b/eul/Views/StatusBar/GpuView.swift index c04e5693..f8cf5ef3 100644 --- a/eul/Views/StatusBar/GpuView.swift +++ b/eul/Views/StatusBar/GpuView.swift @@ -36,7 +36,7 @@ struct GpuView: View { .frame(width: 13, height: 13) } if config.showGraph { - LineChart(points: gpuStore.usageHistory) + LineChart(points: gpuStore.usageHistory, color: config.graphColor.color) } if textStore.showComponents { StatusBarTextView(texts: texts) diff --git a/eul/Views/StatusBar/MemoryView.swift b/eul/Views/StatusBar/MemoryView.swift index c894c6b1..5dbcf7c4 100644 --- a/eul/Views/StatusBar/MemoryView.swift +++ b/eul/Views/StatusBar/MemoryView.swift @@ -42,7 +42,7 @@ struct MemoryView: View { .frame(width: 13, height: 13) } if config.showGraph { - LineChart(points: memoryStore.usageHistory) + LineChart(points: memoryStore.usageHistory, color: config.graphColor.color) } if textStore.showComponents { StatusBarTextView(texts: texts) From c80528bd4936c1fd0ebcc3d3ca40a3e651b6d6be Mon Sep 17 00:00:00 2001 From: Nasta Date: Fri, 3 Apr 2026 12:40:56 +0700 Subject: [PATCH 14/14] chore: remove .claude directory --- .claude/settings.local.json | 36 ------------------------------------ 1 file changed, 36 deletions(-) delete mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 1fb2dc43..00000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(xcodebuild:*)", - "Bash(/Users/xiaobei/Library/Developer/Xcode/DerivedData/eul-fthqqziydluezpevvdfujusdapxb/Build/Products/Debug/eul.app/Contents/MacOS/eul:*)", - "Bash(open:*)", - "Bash(git add:*)", - "Bash(git commit:*)", - "Bash(git push:*)", - "Bash(chmod:*)", - "Bash(swift:*)", - "Bash(killall:*)", - "Bash(sudo powermetrics:*)", - "Bash(sysctl:*)", - "Bash(/tmp/test_gpu_info.swift)", - "Bash(/tmp/test_key_found.swift)", - "Bash(/tmp/test_gpu_full.swift)", - "Bash(system_profiler:*)", - "Bash(/tmp/test_gpu_detection.swift)", - "Bash(/tmp/test_gpu_complete.swift)", - "Bash(/tmp/scan_all_temps.swift)", - "Bash(/tmp/test_gpu_temp_debug.swift)", - "Bash(/tmp/scan_gpu_temps.swift)", - "Bash(/tmp/test_gpu_temps_read.swift)", - "Bash(ls:*)", - "Bash(done)", - "Bash(for file in Resource/*.lproj/Localizable.strings)", - "Bash(do)", - "Bash(if ! grep -q \"cpu.up_time_days\" \"$file\")", - "Bash(then)", - "Bash(echo:*)", - "Bash(fi)", - "Bash(find /Users/xiaobei/Documents/xiaobei/eul/eul -name \"*.swift\" -type f -exec grep -l \"NSNotification.Name\\\\|@Published\" {})" - ] - } -}