feat: added notifications for the CPU module

This commit is contained in:
Serhiy Mytrovtsiy
2023-12-12 20:16:27 +01:00
parent 6c46f90d5f
commit fcf4d461ab
5 changed files with 207 additions and 62 deletions

View File

@@ -254,25 +254,27 @@ public var isARM: Bool {
}
public let notificationLevels: [KeyValue_t] = [
KeyValue_t(key: "Disabled", value: "Disabled"),
KeyValue_t(key: "10%", value: "10%"),
KeyValue_t(key: "15%", value: "15%"),
KeyValue_t(key: "20%", value: "20%"),
KeyValue_t(key: "25%", value: "25%"),
KeyValue_t(key: "30%", value: "30%"),
KeyValue_t(key: "40%", value: "40%"),
KeyValue_t(key: "50%", value: "50%"),
KeyValue_t(key: "55%", value: "55%"),
KeyValue_t(key: "60%", value: "60%"),
KeyValue_t(key: "65%", value: "65%"),
KeyValue_t(key: "70%", value: "70%"),
KeyValue_t(key: "75%", value: "75%"),
KeyValue_t(key: "80%", value: "80%"),
KeyValue_t(key: "85%", value: "85%"),
KeyValue_t(key: "90%", value: "90%"),
KeyValue_t(key: "95%", value: "95%"),
KeyValue_t(key: "97%", value: "97%"),
KeyValue_t(key: "100%", value: "100%")
KeyValue_t(key: "", value: "Disabled"),
KeyValue_t(key: "0.1", value: "10%"),
KeyValue_t(key: "0.15", value: "15%"),
KeyValue_t(key: "0.2", value: "20%"),
KeyValue_t(key: "0.25", value: "25%"),
KeyValue_t(key: "0.3", value: "30%"),
KeyValue_t(key: "0.35", value: "35%"),
KeyValue_t(key: "0.4", value: "40%"),
KeyValue_t(key: "0.45", value: "45%"),
KeyValue_t(key: "0.5", value: "50%"),
KeyValue_t(key: "0.55", value: "55%"),
KeyValue_t(key: "0.6", value: "60%"),
KeyValue_t(key: "0.65", value: "65%"),
KeyValue_t(key: "0.7", value: "70%"),
KeyValue_t(key: "0.75", value: "75%"),
KeyValue_t(key: "0.8", value: "80%"),
KeyValue_t(key: "0.85", value: "85%"),
KeyValue_t(key: "0.9", value: "90%"),
KeyValue_t(key: "0.95", value: "95%"),
KeyValue_t(key: "0.97", value: "97%"),
KeyValue_t(key: "1.0", value: "100%")
]
public struct Scale: KeyValue_p, Equatable {

View File

@@ -32,10 +32,18 @@ public struct CPU_Limit: Codable {
var speed: Int = 0
}
public struct CPU_Frequency: Codable {
var ECores: Int? = nil
var PCores: Int? = nil
var power: Int? = nil
var cores: [Int] = []
}
public class CPU: Module {
private let popupView: Popup
private let settingsView: Settings
private let portalView: Portal
private let notificationsView: Notifications
private var loadReader: LoadReader? = nil
private var processReader: ProcessReader? = nil
@@ -43,9 +51,7 @@ public class CPU: Module {
private var frequencyReader: FrequencyReader? = nil
private var limitReader: LimitReader? = nil
private var averageReader: AverageReader? = nil
private var notificationLevelState: Bool = false
private var notificationID: String? = nil
private var powermetricsReader: PowermetricsReader? = nil
private var usagePerCoreState: Bool {
Store.shared.bool(key: "\(self.config.name)_usagePerCore", defaultValue: false)
@@ -56,9 +62,6 @@ public class CPU: Module {
private var groupByClustersState: Bool {
Store.shared.bool(key: "\(self.config.name)_clustersGroup", defaultValue: false)
}
private var notificationLevel: String {
Store.shared.string(key: "\(self.config.name)_notificationLevel", defaultValue: "Disabled")
}
private var systemColor: NSColor {
let color = Color.secondRed
let key = Store.shared.string(key: "\(self.config.name)_systemColor", defaultValue: color.key)
@@ -97,11 +100,13 @@ public class CPU: Module {
self.settingsView = Settings("CPU")
self.popupView = Popup("CPU")
self.portalView = Portal("CPU")
self.notificationsView = Notifications(.CPU)
super.init(
popup: self.popupView,
settings: self.settingsView,
portal: self.portalView
portal: self.portalView,
notifications: self.notificationsView
)
guard self.available else { return }
@@ -109,6 +114,7 @@ public class CPU: Module {
self.processReader = ProcessReader(.CPU)
self.averageReader = AverageReader(.CPU, popup: true)
self.temperatureReader = TemperatureReader(.CPU, popup: true)
self.powermetricsReader = PowermetricsReader(.CPU, popup: true)
#if arch(x86_64)
self.limitReader = LimitReader(.CPU, popup: true)
@@ -189,6 +195,9 @@ public class CPU: Module {
if let reader = self.averageReader {
self.addReader(reader)
}
if let reader = self.powermetricsReader {
self.addReader(reader)
}
}
private func loadCallback(_ raw: CPU_Load?) {
@@ -196,7 +205,7 @@ public class CPU: Module {
self.popupView.loadCallback(value)
self.portalView.loadCallback(value)
self.checkNotificationLevel(value.totalUsage)
self.notificationsView.loadCallback(value)
self.menuBar.widgets.filter{ $0.isActive }.forEach { (w: Widget) in
switch w.item {
@@ -246,20 +255,4 @@ public class CPU: Module {
}
}
}
private func checkNotificationLevel(_ value: Double) {
guard self.notificationLevel != "Disabled", let level = Double(self.notificationLevel) else { return }
if let id = self.notificationID, value < level && self.notificationLevelState {
removeNotification(id)
self.notificationID = nil
self.notificationLevelState = false
} else if value >= level && !self.notificationLevelState {
self.notificationID = showNotification(
title: localizedString("CPU usage threshold"),
subtitle: localizedString("CPU usage is", "\(Int((value)*100))%")
)
self.notificationLevelState = true
}
}
}

View File

@@ -0,0 +1,145 @@
//
// notifications.swift
// CPU
//
// Created by Serhiy Mytrovtsiy on 04/12/2023
// Using Swift 5.0
// Running on macOS 14.1
//
// Copyright © 2023 Serhiy Mytrovtsiy. All rights reserved.
//
import Cocoa
import Kit
class Notifications: NotificationsWrapper {
private let totalLoadID: String = "totalUsage"
private let systemLoadID: String = "systemUsage"
private let userLoadID: String = "userUsage"
private let eCoresLoadID: String = "eCoresUsage"
private let pCoresLoadID: String = "pCoresUsage"
private var totalLoadLevel: String = ""
private var systemLoadLevel: String = ""
private var userLoadLevel: String = ""
private var eCoresLoadLevel: String = ""
private var pCoresLoadLevel: String = ""
public init(_ module: ModuleType) {
super.init(module, [self.totalLoadID, self.systemLoadID, self.userLoadID, self.eCoresLoadID, self.pCoresLoadID])
if Store.shared.exist(key: "\(self.module)_notificationLevel") {
let value = Store.shared.string(key: "\(self.module)_notificationLevel", defaultValue: self.totalLoadLevel)
Store.shared.set(key: "\(self.module)_notifications_totalLoad", value: value)
Store.shared.remove("\(self.module)_notificationLevel")
}
self.totalLoadLevel = Store.shared.string(key: "\(self.module)_notifications_totalLoad", defaultValue: self.totalLoadLevel)
self.systemLoadLevel = Store.shared.string(key: "\(self.module)_notifications_systemLoad", defaultValue: self.systemLoadLevel)
self.userLoadLevel = Store.shared.string(key: "\(self.module)_notifications_userLoad", defaultValue: self.userLoadLevel)
self.eCoresLoadLevel = Store.shared.string(key: "\(self.module)_notifications_eCoresLoad", defaultValue: self.eCoresLoadLevel)
self.pCoresLoadLevel = Store.shared.string(key: "\(self.module)_notifications_pCoresLoad", defaultValue: self.pCoresLoadLevel)
self.addArrangedSubview(selectSettingsRow(
title: localizedString("Total load"),
action: #selector(self.changeTotalLoad),
items: notificationLevels,
selected: self.totalLoadLevel
))
self.addArrangedSubview(selectSettingsRow(
title: localizedString("System load"),
action: #selector(self.changeSystemLoad),
items: notificationLevels,
selected: self.systemLoadLevel
))
self.addArrangedSubview(selectSettingsRow(
title: localizedString("User load"),
action: #selector(self.changeUserLoad),
items: notificationLevels,
selected: self.userLoadLevel
))
#if arch(arm64)
self.addArrangedSubview(selectSettingsRow(
title: localizedString("Efficiency cores load"),
action: #selector(self.changeECoresLoad),
items: notificationLevels,
selected: self.eCoresLoadLevel
))
self.addArrangedSubview(selectSettingsRow(
title: localizedString("Performance cores load"),
action: #selector(self.changePCoresLoad),
items: notificationLevels,
selected: self.pCoresLoadLevel
))
#endif
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
internal func loadCallback(_ value: CPU_Load) {
let title = localizedString("CPU usage threshold")
if let threshold = Double(self.totalLoadLevel) {
let subtitle = localizedString("Total usage is", "\(Int((value.totalUsage)*100))%")
self.checkDouble(id: self.totalLoadID, value: value.totalUsage, threshold: threshold, title: title, subtitle: subtitle)
}
if let threshold = Double(self.systemLoadLevel) {
let subtitle = localizedString("System usage is", "\(Int((value.systemLoad)*100))%")
self.checkDouble(id: self.systemLoadID, value: value.systemLoad, threshold: threshold, title: title, subtitle: subtitle)
}
if let threshold = Double(self.userLoadLevel) {
let subtitle = localizedString("User usage is", "\(Int((value.systemLoad)*100))%")
self.checkDouble(id: self.userLoadID, value: value.userLoad, threshold: threshold, title: title, subtitle: subtitle)
}
if let threshold = Double(self.eCoresLoadLevel), let usage = value.usageECores {
let subtitle = localizedString("Efficiency cores usage is", "\(Int((value.systemLoad)*100))%")
self.checkDouble(id: self.eCoresLoadID, value: usage, threshold: threshold, title: title, subtitle: subtitle)
}
if let threshold = Double(self.pCoresLoadLevel), let usage = value.usagePCores {
let subtitle = localizedString("Performance cores usage is", "\(Int((value.systemLoad)*100))%")
self.checkDouble(id: self.pCoresLoadID, value: usage, threshold: threshold, title: title, subtitle: subtitle)
}
}
// MARK: - change helpers
@objc private func changeTotalLoad(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String else { return }
self.totalLoadLevel = key.isEmpty ? "" : "\(Double(key) ?? 0)"
Store.shared.set(key: "\(self.module)_notifications_totalLoad", value: self.totalLoadLevel)
}
@objc private func changeSystemLoad(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String else { return }
self.systemLoadLevel = key.isEmpty ? "" : "\(Double(key) ?? 0)"
Store.shared.set(key: "\(self.module)_notifications_systemLoad", value: self.systemLoadLevel)
}
@objc private func changeUserLoad(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String else { return }
self.userLoadLevel = key.isEmpty ? "" : "\(Double(key) ?? 0)"
Store.shared.set(key: "\(self.module)_notifications_userLoad", value: self.userLoadLevel)
}
@objc private func changeECoresLoad(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String else { return }
self.eCoresLoadLevel = key.isEmpty ? "" : "\(Double(key) ?? 0)"
Store.shared.set(key: "\(self.module)_notifications_eCoresLoad", value: self.eCoresLoadLevel)
}
@objc private func changePCoresLoad(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String else { return }
self.pCoresLoadLevel = key.isEmpty ? "" : "\(Double(key) ?? 0)"
Store.shared.set(key: "\(self.module)_notifications_pCoresLoad", value: self.pCoresLoadLevel)
}
}

View File

@@ -20,7 +20,6 @@ internal class Settings: NSStackView, Settings_v {
private var updateIntervalValue: Int = 1
private var updateTopIntervalValue: Int = 1
private var numberOfProcesses: Int = 8
private var notificationLevel: String = "Disabled"
private var clustersGroupState: Bool = false
private let title: String
@@ -46,7 +45,6 @@ internal class Settings: NSStackView, Settings_v {
self.updateIntervalValue = Store.shared.int(key: "\(self.title)_updateInterval", defaultValue: self.updateIntervalValue)
self.updateTopIntervalValue = Store.shared.int(key: "\(self.title)_updateTopInterval", defaultValue: self.updateTopIntervalValue)
self.numberOfProcesses = Store.shared.int(key: "\(self.title)_processes", defaultValue: self.numberOfProcesses)
self.notificationLevel = Store.shared.string(key: "\(self.title)_notificationLevel", defaultValue: self.notificationLevel)
if !self.usagePerCoreState {
self.hyperthreadState = false
}
@@ -153,13 +151,6 @@ internal class Settings: NSStackView, Settings_v {
items: NumbersOfProcesses.map{ "\($0)" },
selected: "\(self.numberOfProcesses)"
))
self.addArrangedSubview(selectSettingsRow(
title: localizedString("Notification level"),
action: #selector(changeNotificationLevel),
items: notificationLevels,
selected: self.notificationLevel == "disabled" ? self.notificationLevel : "\(Int((Double(self.notificationLevel) ?? 0)*100))%"
))
}
@objc private func changeUpdateInterval(_ sender: NSMenuItem) {
@@ -257,16 +248,6 @@ internal class Settings: NSStackView, Settings_v {
self.callback()
}
@objc func changeNotificationLevel(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String else { return }
if key == "Disabled" {
Store.shared.set(key: "\(self.title)_notificationLevel", value: key)
} else if let value = Double(key.replacingOccurrences(of: "%", with: "")) {
Store.shared.set(key: "\(self.title)_notificationLevel", value: "\(value/100)")
}
}
@objc func toggleClustersGroup(_ sender: NSControl) {
var state: NSControl.StateValue? = nil
if #available(OSX 10.15, *) {

View File

@@ -25,6 +25,12 @@
5C23BC1029A3B5AE00DBA990 /* portal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C23BC0F29A3B5AE00DBA990 /* portal.swift */; };
5C5647F82A3F6B100098FFE9 /* Telemetry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5647F72A3F6B100098FFE9 /* Telemetry.swift */; };
5C8E001029269C7F0027C75A /* protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE493829265055000F2856 /* protocol.swift */; };
5CF2210D2B1E7EAF006C583F /* notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF2210C2B1E7EAF006C583F /* notifications.swift */; };
5CF221132B1E8078006C583F /* notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF221122B1E8078006C583F /* notifications.swift */; };
5CF221152B1F4792006C583F /* notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF221142B1F4792006C583F /* notifications.swift */; };
5CF221172B1F4ACB006C583F /* notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF221162B1F4ACB006C583F /* notifications.swift */; };
5CF221192B1F8B90006C583F /* notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF221182B1F8B90006C583F /* notifications.swift */; };
5CF2211B2B1F8CEF006C583F /* notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF2211A2B1F8CEF006C583F /* notifications.swift */; };
5CFE492A29264DF1000F2856 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE492929264DF1000F2856 /* main.swift */; };
5CFE493929265055000F2856 /* protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE493829265055000F2856 /* protocol.swift */; };
5CFE493D2926513E000F2856 /* eu.exelban.Stats.SMC.Helper in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5CFE492729264DF1000F2856 /* eu.exelban.Stats.SMC.Helper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
@@ -394,6 +400,12 @@
5C23BC0F29A3B5AE00DBA990 /* portal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = portal.swift; sourceTree = "<group>"; };
5C5647F72A3F6B100098FFE9 /* Telemetry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Telemetry.swift; sourceTree = "<group>"; };
5C9F90A02A76B30500D41748 /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/Localizable.strings; sourceTree = "<group>"; };
5CF2210C2B1E7EAF006C583F /* notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = notifications.swift; sourceTree = "<group>"; };
5CF221122B1E8078006C583F /* notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = notifications.swift; sourceTree = "<group>"; };
5CF221142B1F4792006C583F /* notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = notifications.swift; sourceTree = "<group>"; };
5CF221162B1F4ACB006C583F /* notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = notifications.swift; sourceTree = "<group>"; };
5CF221182B1F8B90006C583F /* notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = notifications.swift; sourceTree = "<group>"; };
5CF2211A2B1F8CEF006C583F /* notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = notifications.swift; sourceTree = "<group>"; };
5CFE492729264DF1000F2856 /* eu.exelban.Stats.SMC.Helper */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = eu.exelban.Stats.SMC.Helper; sourceTree = BUILT_PRODUCTS_DIR; };
5CFE492929264DF1000F2856 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
5CFE493829265055000F2856 /* protocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = protocol.swift; sourceTree = "<group>"; };
@@ -812,6 +824,7 @@
9A2847772666AA5000EC1F6D /* reader.swift */,
9A2847752666AA5000EC1F6D /* settings.swift */,
5C23BC0329A014AC00DBA990 /* portal.swift */,
5CF2210C2B1E7EAF006C583F /* notifications.swift */,
);
path = module;
sourceTree = "<group>";
@@ -884,6 +897,7 @@
9AA6425F244B274200416A33 /* popup.swift */,
5C23BC0729A03D1200DBA990 /* portal.swift */,
9A953A1324B9D22D0038EF4B /* settings.swift */,
5CF221162B1F4ACB006C583F /* notifications.swift */,
9A81C7592449A41400825D92 /* Info.plist */,
9AF9EE192464A7B3005D2270 /* config.plist */,
);
@@ -898,6 +912,7 @@
9A53EBFA24EB041E00648841 /* popup.swift */,
5C23BC0929A0EDA300DBA990 /* portal.swift */,
9A53EBF824EAFA5200648841 /* settings.swift */,
5CF221142B1F4792006C583F /* notifications.swift */,
9A90E18C24EAD2BB00471E9A /* Info.plist */,
9A90E19724EAD3B000471E9A /* config.plist */,
);
@@ -912,6 +927,7 @@
9A97CEF5253733E400742D8F /* popup.swift */,
5C23BC0129A0102500DBA990 /* portal.swift */,
9A97CEFA253733F300742D8F /* settings.swift */,
5CF221122B1E8078006C583F /* notifications.swift */,
9A97CECD2537331B00742D8F /* Info.plist */,
9A97CEFF2537340400742D8F /* config.plist */,
);
@@ -1001,6 +1017,7 @@
9A58DE9D24B363D800716A9F /* popup.swift */,
9A58DE9F24B363F300716A9F /* settings.swift */,
9AE29AF7249A53420071B02D /* values.swift */,
5CF2211A2B1F8CEF006C583F /* notifications.swift */,
9AE29AEC249A50960071B02D /* Info.plist */,
9AE29AF4249A52870071B02D /* config.plist */,
9A3616E82613C3D400D657B6 /* bridge.h */,
@@ -1017,6 +1034,7 @@
9A5AF11A2469CE9B00684737 /* popup.swift */,
5C23BC0F29A3B5AE00DBA990 /* portal.swift */,
9AB7FD7B246B48DB00387FDA /* settings.swift */,
5CF221182B1F8B90006C583F /* notifications.swift */,
9AF9EE0524648751005D2270 /* Info.plist */,
9AF9EE12246492E8005D2270 /* config.plist */,
5C2229BB29CF66B100F00E69 /* header.h */,
@@ -1753,6 +1771,7 @@
9A2847642666AA2700EC1F6D /* Battery.swift in Sources */,
9A28480B2666AB3000EC1F6D /* Charts.swift in Sources */,
9A2847632666AA2700EC1F6D /* Mini.swift in Sources */,
5CF2210D2B1E7EAF006C583F /* notifications.swift in Sources */,
9A6EEBBE2685259500897371 /* Logger.swift in Sources */,
9A2847602666AA2700EC1F6D /* NetworkChart.swift in Sources */,
5C23BC0429A014AC00DBA990 /* portal.swift in Sources */,
@@ -1791,6 +1810,7 @@
9AA64260244B274200416A33 /* popup.swift in Sources */,
5C23BC0829A03D1200DBA990 /* portal.swift in Sources */,
9A953A1424B9D22D0038EF4B /* settings.swift in Sources */,
5CF221172B1F4ACB006C583F /* notifications.swift in Sources */,
9A81C7692449A43600825D92 /* main.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -1805,6 +1825,7 @@
5C23BC0A29A0EDA300DBA990 /* portal.swift in Sources */,
9A53EBFB24EB041E00648841 /* popup.swift in Sources */,
9A53EBF924EAFA5200648841 /* settings.swift in Sources */,
5CF221152B1F4792006C583F /* notifications.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1818,6 +1839,7 @@
5C23BC0229A0102500DBA990 /* portal.swift in Sources */,
9A97CEFB253733F300742D8F /* settings.swift in Sources */,
9A97CEE92537338600742D8F /* main.swift in Sources */,
5CF221132B1E8078006C583F /* notifications.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1854,6 +1876,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5CF2211B2B1F8CEF006C583F /* notifications.swift in Sources */,
9A46C05F266D85F8001A1117 /* smc.swift in Sources */,
9AB6D03926447CAA003215A5 /* reader.m in Sources */,
9AE29AFB249A53DC0071B02D /* readers.swift in Sources */,
@@ -1872,6 +1895,7 @@
9AF9EE1124648ADC005D2270 /* readers.swift in Sources */,
5C23BC1029A3B5AE00DBA990 /* portal.swift in Sources */,
9AB7FD7C246B48DB00387FDA /* settings.swift in Sources */,
5CF221192B1F8B90006C583F /* notifications.swift in Sources */,
9AF9EE0F2464875F005D2270 /* main.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;