Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Resource/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -222,3 +222,5 @@
"widget.battery.description" = "Display battery information";
"widget.network.title" = "Network Widget";
"widget.network.description" = "Display network information";

"cpu.cores" = "CPU Cores";
2 changes: 2 additions & 0 deletions Resource/zh-Hans.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -223,3 +223,5 @@
"widget.battery.description" = "显示电池相关信息";
"widget.network.title" = "网络小组件";
"widget.network.description" = "显示网络相关信息";

"cpu.cores" = "CPU 核心";
30 changes: 17 additions & 13 deletions eul.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
6C1FA40E24AA162800CA7F71 /* Refreshable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C1FA40D24AA162800CA7F71 /* Refreshable.swift */; };
6C1FA41024AA1D4400CA7F71 /* FanStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C1FA40F24AA1D4400CA7F71 /* FanStore.swift */; };
6C1FA41224AA1DC100CA7F71 /* MemoryStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C1FA41124AA1DC100CA7F71 /* MemoryStore.swift */; };
6C1FA4A124A712BB00CA7F71 /* AppleSiliconSensors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C1FA4A024A712BB00CA7F71 /* AppleSiliconSensors.swift */; };
6C2688F52556762B00FB7306 /* SharedLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6C2688EE2556762B00FB7306 /* SharedLibrary.framework */; };
6C2688F62556762B00FB7306 /* SharedLibrary.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6C2688EE2556762B00FB7306 /* SharedLibrary.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
6C2688FE2556763700FB7306 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CAEED5324A5DE4700C39597 /* Color.swift */; };
Expand Down Expand Up @@ -377,6 +378,7 @@
6C1FA40D24AA162800CA7F71 /* Refreshable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Refreshable.swift; sourceTree = "<group>"; };
6C1FA40F24AA1D4400CA7F71 /* FanStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FanStore.swift; sourceTree = "<group>"; };
6C1FA41124AA1DC100CA7F71 /* MemoryStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryStore.swift; sourceTree = "<group>"; };
6C1FA4A024A712BB00CA7F71 /* AppleSiliconSensors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleSiliconSensors.swift; sourceTree = "<group>"; };
6C2688EE2556762B00FB7306 /* SharedLibrary.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SharedLibrary.framework; sourceTree = BUILT_PRODUCTS_DIR; };
6C2688F12556762B00FB7306 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
6C2F1647255C1EAD0062F76F /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/Localizable.strings; sourceTree = "<group>"; };
Expand Down Expand Up @@ -821,6 +823,7 @@
6C7DB6CE24E02F0600133B06 /* Shell.swift */,
6CAEED6424A62DF800C39597 /* SMC.swift */,
6C1FA3FF24A712AA00CA7F71 /* SmcControl.swift */,
6C1FA4A024A712BB00CA7F71 /* AppleSiliconSensors.swift */,
6C831368253DCF1F00914BB0 /* Print.swift */,
6CFAB80525BC526E002F5F48 /* GPU.swift */,
6CFAB80825BC55C6002F5F48 /* IOHelper.swift */,
Expand Down Expand Up @@ -1345,7 +1348,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 = "exit 0\n";
};
6CC0798D250CEE96000D7DAC /* Copy LaunchAtLogin helper */ = {
isa = PBXShellScriptBuildPhase;
Expand Down Expand Up @@ -1461,6 +1464,7 @@
6CC1EBC02576A0E200BC05CA /* Array.swift in Sources */,
6C1FA3F824A70DF300CA7F71 /* CpuView.swift in Sources */,
6C1FA40024A712AA00CA7F71 /* SmcControl.swift in Sources */,
6C1FA4A124A712BB00CA7F71 /* AppleSiliconSensors.swift in Sources */,
6CC1EBBD25769E2400BC05CA /* FanTextComponent.swift in Sources */,
6C33CECD25177CE300345977 /* CpuMenuBlockView.swift in Sources */,
6CF28D0D251758EE00EBE9CB /* StatusBarView.swift in Sources */,
Expand Down Expand Up @@ -1661,7 +1665,7 @@
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 7;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = M8G2RFZVFV;
DEVELOPMENT_TEAM = 6BPGJ4H7NN;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
Expand Down Expand Up @@ -1692,7 +1696,7 @@
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 7;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = M8G2RFZVFV;
DEVELOPMENT_TEAM = 6BPGJ4H7NN;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
Expand Down Expand Up @@ -1723,7 +1727,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 52;
DEVELOPMENT_TEAM = M8G2RFZVFV;
DEVELOPMENT_TEAM = 6BPGJ4H7NN;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = MemoryWidget/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
Expand All @@ -1749,7 +1753,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 52;
DEVELOPMENT_TEAM = M8G2RFZVFV;
DEVELOPMENT_TEAM = 6BPGJ4H7NN;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = MemoryWidget/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
Expand All @@ -1775,7 +1779,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 52;
DEVELOPMENT_TEAM = M8G2RFZVFV;
DEVELOPMENT_TEAM = 6BPGJ4H7NN;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = NetworkWidget/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
Expand All @@ -1801,7 +1805,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 52;
DEVELOPMENT_TEAM = M8G2RFZVFV;
DEVELOPMENT_TEAM = 6BPGJ4H7NN;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = NetworkWidget/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
Expand Down Expand Up @@ -1947,7 +1951,7 @@
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 52;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = M8G2RFZVFV;
DEVELOPMENT_TEAM = 6BPGJ4H7NN;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
Expand Down Expand Up @@ -1978,7 +1982,7 @@
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 52;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = M8G2RFZVFV;
DEVELOPMENT_TEAM = 6BPGJ4H7NN;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
Expand All @@ -2005,7 +2009,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 52;
DEVELOPMENT_TEAM = M8G2RFZVFV;
DEVELOPMENT_TEAM = 6BPGJ4H7NN;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = BatteryWidget/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
Expand All @@ -2031,7 +2035,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 52;
DEVELOPMENT_TEAM = M8G2RFZVFV;
DEVELOPMENT_TEAM = 6BPGJ4H7NN;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = BatteryWidget/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
Expand All @@ -2057,7 +2061,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 52;
DEVELOPMENT_TEAM = M8G2RFZVFV;
DEVELOPMENT_TEAM = 6BPGJ4H7NN;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = CpuWidget/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
Expand All @@ -2083,7 +2087,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 52;
DEVELOPMENT_TEAM = M8G2RFZVFV;
DEVELOPMENT_TEAM = 6BPGJ4H7NN;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = CpuWidget/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
Expand Down
14 changes: 14 additions & 0 deletions eul/Schema/SystemProfiler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,29 @@ struct DisplayDevice: Codable {
var deviceType: String?
var model: String?
var vendor: String?
var cores: String? // GPU cores for Apple Silicon

enum CodingKeys: String, CodingKey {
case deviceId = "spdisplays_device-id"
case deviceType = "sppci_device_type"
case model = "sppci_model"
case vendor = "spdisplays_vendor"
case cores = "sppci_cores"
}

var isGPU: Bool {
deviceType == "spdisplays_gpu"
}

// Generate a default device ID for Apple Silicon GPUs
var resolvedDeviceId: String? {
if let deviceId = deviceId {
return deviceId
}
// For Apple Silicon, generate ID from model name
if let model = model {
return "apple-silicon-\(model.replacingOccurrences(of: " ", with: "-").lowercased())"
}
return nil
}
}
115 changes: 115 additions & 0 deletions eul/Store/CpuStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ class CpuStore: ObservableObject, Refreshable {
@Published var upTime: (days: Int, hrs: Int, mins: Int, secs: Int)?
@Published var thermalLevel: System.ThermalLevel = .Unknown
@Published var usageHistory: [Double] = []

// Per-core data
@Published var coreUsages: [Double] = []
@Published var coreTemps: [Double] = []
@Published var coreLabels: [String] = [] // e.g., "E0", "P0", "P1"

// Previous CPU tick values for calculating per-core usage
private var prevCoreTicks: [[Int]] = []

// P-core and E-core counts
private var pCoreCount = 0
private var eCoreCount = 0

var loadAverage1MinString: String {
formatDouble(loadAverage?[safe: 0])
Expand Down Expand Up @@ -59,20 +71,123 @@ class CpuStore: ObservableObject, Refreshable {
logicalCores = System.logicalCores()
upTime = System.uptime()
thermalLevel = System.thermalLevel()

// Get P-core and E-core counts (Apple Silicon only)
#if arch(arm64)
pCoreCount = getSysctlInt("hw.perflevel0.physicalcpu")
eCoreCount = getSysctlInt("hw.perflevel1.physicalcpu")
#endif
}

private func getSysctlInt(_ name: String) -> Int {
var value: Int = 0
var size = MemoryLayout<Int>.size
sysctlbyname(name, &value, &size, nil, 0)
return value
}

private func getUsage() {
let usage = Info.system.usageCPU()
usageCPU = usage
loadAverage = System.loadAverage()
usageHistory = (usageHistory + [usage.system + usage.user]).suffix(LineChart.defaultMaxPointCount)
coreUsages = getPerCoreUsage()
}

private func getPerCoreUsage() -> [Double] {
var cpuInfo: processor_info_array_t?
var numCpuInfo: mach_msg_type_number_t = 0
var numCPUsU: natural_t = 0

let err = host_processor_info(
mach_host_self(),
PROCESSOR_CPU_LOAD_INFO,
&numCPUsU,
&cpuInfo,
&numCpuInfo
)

guard err == KERN_SUCCESS, let cpuInfo = cpuInfo else {
return []
}

var currentTicks: [[Int]] = []
var usages: [Double] = []
var labels: [String] = []

let totalCores = Int(numCPUsU)

for i in 0..<totalCores {
let offset = Int32(CPU_STATE_MAX) * Int32(i)

let user = Int(cpuInfo[Int(offset + Int32(CPU_STATE_USER))])
let system = Int(cpuInfo[Int(offset + Int32(CPU_STATE_SYSTEM))])
let idle = Int(cpuInfo[Int(offset + Int32(CPU_STATE_IDLE))])
let nice = Int(cpuInfo[Int(offset + Int32(CPU_STATE_NICE))])

currentTicks.append([user, system, idle, nice])

// Generate core label (E-cores first, then P-cores on Apple Silicon)
#if arch(arm64)
if i < eCoreCount {
labels.append("E\(i)")
} else {
labels.append("P\(i - eCoreCount)")
}
#else
labels.append("C\(i)")
#endif

// Calculate usage from delta if we have previous data
if i < prevCoreTicks.count {
let prev = prevCoreTicks[i]
let dUser = user - prev[0]
let dSystem = system - prev[1]
let dIdle = idle - prev[2]
let dNice = nice - prev[3]

let dTotal = dUser + dSystem + dIdle + dNice
if dTotal > 0 {
let usage = Double(dUser + dSystem + dNice) / Double(dTotal) * 100
usages.append(usage)
} else {
usages.append(0)
}
} else {
// First call, no previous data - show 0
usages.append(0)
}
}

// Save current ticks and labels for next calculation
prevCoreTicks = currentTicks
coreLabels = labels

let size = vm_size_t(numCpuInfo) * vm_size_t(MemoryLayout<integer_t>.stride)
vm_deallocate(mach_task_self_, vm_address_t(bitPattern: cpuInfo), size)

return usages
}

private func getTemp() {
temp = (SmcControl.shared.cpuDieTemperature ?? 0) > 0
? SmcControl.shared.cpuDieTemperature
: SmcControl.shared.cpuProximityTemperature

#if arch(arm64)
coreTemps = getPerCoreTemps()
#endif
}

#if arch(arm64)
private func getPerCoreTemps() -> [Double] {
guard let sensors = AppleSiliconSensors.shared?.getAllTemperatures() else {
return []
}
let tdieSensors = sensors.filter { $0.name.hasPrefix("PMU tdie") || $0.name.hasPrefix("PMU2 tdie") }
return tdieSensors.map { $0.temperature }
}
#endif

@objc func refresh() {
getInfo()
Expand Down
22 changes: 20 additions & 2 deletions eul/Store/GpuStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ class GpuStore: ObservableObject, Refreshable {
@Published var usageHistory: [Double] = []

var usageAverage: Double? {
#if arch(arm64)
if let stat = gpuStatistics.first {
return Double(stat.usagePercentage)
}
#endif

let stats = gpus.compactMap { getStatustic(for: $0) }
guard stats.count > 0 else {
return nil
Expand All @@ -36,6 +42,13 @@ class GpuStore: ObservableObject, Refreshable {
}

var temperatureAverage: Double? {
#if arch(arm64)
if let temp = gpuStatistics.first?.temperature {
return temp
}
return AppleSiliconSensors.shared?.gpuTemperature
#endif

let temps = gpus.compactMap { getStatustic(for: $0)?.temperature }
guard temps.count > 0 else {
return nil
Expand All @@ -44,15 +57,20 @@ class GpuStore: ObservableObject, Refreshable {
}

func getStatustic(for gpu: GPU) -> GPU.Statistic? {
gpuStatistics.first {
#if arch(arm64)
if gpu.deviceId.hasPrefix("apple-silicon-") {
return gpuStatistics.first
}
#endif

return gpuStatistics.first {
$0.pciMatch.lowercased().contains(gpu.deviceId.deletingPrefix("0x"))
}
}

init() {
gpus = GPU.getGPUs() ?? []
initObserver(for: .StoreShouldRefresh)
// refresh immediately to prevent "N/A"
activeCancellable = Publishers
.CombineLatest(componentsStore.$activeComponents, menuComponentsStore.$activeComponents)
.sink { _ in
Expand Down
Loading