Skip to content

Commit

Permalink
feat: added notifications for the CPU module
Browse files Browse the repository at this point in the history
  • Loading branch information
exelban committed Dec 12, 2023
1 parent 6c46f90 commit fcf4d46
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 62 deletions.
40 changes: 21 additions & 19 deletions Kit/types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
41 changes: 17 additions & 24 deletions Modules/CPU/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,26 @@ 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
private var temperatureReader: TemperatureReader? = nil
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)
Expand All @@ -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)
Expand Down Expand Up @@ -97,18 +100,21 @@ 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 }

self.loadReader = LoadReader(.CPU)
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)
Expand Down Expand Up @@ -189,14 +195,17 @@ 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?) {
guard let value = raw, self.enabled else { return }

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 {
Expand Down Expand Up @@ -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
}
}
}
145 changes: 145 additions & 0 deletions Modules/CPU/notifications.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
19 changes: 0 additions & 19 deletions Modules/CPU/settings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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, *) {
Expand Down
Loading

0 comments on commit fcf4d46

Please sign in to comment.