diff --git a/Modules/Sensors/main.swift b/Modules/Sensors/main.swift index 74794ade..5f542de1 100644 --- a/Modules/Sensors/main.swift +++ b/Modules/Sensors/main.swift @@ -13,9 +13,10 @@ import Cocoa import Kit public class Sensors: Module { - private var sensorsReader: SensorsReader + private let sensorsReader: SensorsReader private let popupView: Popup - private var settingsView: Settings + private let settingsView: Settings + private let notificationsView: Notifications private var fanValueState: FanValue { FanValue(rawValue: Store.shared.string(key: "\(self.config.name)_fanValue", defaultValue: "percentage")) ?? .percentage @@ -25,14 +26,17 @@ public class Sensors: Module { self.sensorsReader = SensorsReader() self.settingsView = Settings("Sensors", list: self.sensorsReader.list.sensors) self.popupView = Popup() + self.notificationsView = Notifications(.sensors) super.init( popup: self.popupView, - settings: self.settingsView + settings: self.settingsView, + notifications: self.notificationsView ) guard self.available else { return } self.popupView.setup(self.sensorsReader.list.sensors) + self.notificationsView.setup(self.sensorsReader.list.sensors) self.settingsView.callback = { [weak self] in self?.sensorsReader.read() @@ -46,6 +50,7 @@ public class Sensors: Module { DispatchQueue.main.async { self?.popupView.setup(self?.sensorsReader.list.sensors) self?.settingsView.setList(list: self?.sensorsReader.list.sensors ?? []) + self?.notificationsView.setup(self?.sensorsReader.list.sensors) } } } @@ -55,6 +60,7 @@ public class Sensors: Module { DispatchQueue.main.async { self?.popupView.setup(self?.sensorsReader.list.sensors) self?.settingsView.setList(list: self?.sensorsReader.list.sensors ?? []) + self?.notificationsView.setup(self?.sensorsReader.list.sensors) } } } @@ -111,6 +117,7 @@ public class Sensors: Module { } self.popupView.usageCallback(value.sensors) + self.notificationsView.usageCallback(value.sensors) self.menuBar.widgets.filter{ $0.isActive }.forEach { (w: Widget) in switch w.item { diff --git a/Modules/Sensors/notifications.swift b/Modules/Sensors/notifications.swift new file mode 100644 index 00000000..addbf9c6 --- /dev/null +++ b/Modules/Sensors/notifications.swift @@ -0,0 +1,120 @@ +// +// notifications.swift +// Sensors +// +// Created by Serhiy Mytrovtsiy on 05/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 var unknownSensorsState: Bool { + Store.shared.bool(key: "Sensors_unknown", defaultValue: false) + } + + private var temperatureLevels: [KeyValue_t] = [ + KeyValue_t(key: "", value: "Disabled") + ] + private let temperatureList: [String] = ["30", "35", "40", "45", "50", "55", "60", "65", "70", "75", "80", "85", "90", "96", "100", "105", "110"] + + public init(_ module: ModuleType) { + super.init(module) + for p in self.temperatureList { + if let v = Double(p) { + self.temperatureLevels.append(KeyValue_t(key: p, value: temperature(v))) + } + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + internal func setup(_ values: [Sensor_p]? = nil) { + guard var values = values else { return } + values = values.filter({ $0.type == .fan || $0.type == .temperature }) + if !self.unknownSensorsState { + values = values.filter({ $0.group != .unknown }) + } + self.subviews.forEach({ $0.removeFromSuperview() }) + self.initIDs(values.map{$0.key}) + + var types: [SensorType] = [] + values.forEach { (s: Sensor_p) in + if !types.contains(s.type) { + types.append(s.type) + } + } + + types.forEach { (typ: SensorType) in + let filtered = values.filter{ $0.type == typ } + var groups: [SensorGroup] = [] + filtered.forEach { (s: Sensor_p) in + if !groups.contains(s.group) { + groups.append(s.group) + } + } + + let header = NSStackView() + header.heightAnchor.constraint(equalToConstant: Constants.Settings.row).isActive = true + header.spacing = 0 + + let titleField: NSTextField = LabelField(frame: NSRect(x: 0, y: 0, width: 0, height: 0), localizedString(typ.rawValue)) + titleField.font = NSFont.systemFont(ofSize: 13, weight: .medium) + titleField.textColor = .labelColor + + header.addArrangedSubview(titleField) + header.addArrangedSubview(NSView()) + + self.addArrangedSubview(header) + + let container = NSStackView() + container.orientation = .vertical + container.edgeInsets = NSEdgeInsets(top: 0, left: Constants.Settings.margin, bottom: 0, right: Constants.Settings.margin) + container.spacing = 0 + + groups.forEach { (group: SensorGroup) in + filtered.filter{ $0.group == group }.forEach { (s: Sensor_p) in + var items = notificationLevels + if s.type == .temperature { + items = temperatureLevels + } + let row: NSView = selectSettingsRow( + title: localizedString(s.name), + action: #selector(self.changeSensorNotificaion), + items: items, + selected: s.notificationThreshold + ) + row.subviews.filter{ $0 is NSControl }.forEach { (control: NSView) in + control.identifier = NSUserInterfaceItemIdentifier(rawValue: s.key) + } + container.addArrangedSubview(row) + } + } + + self.addArrangedSubview(container) + } + } + + internal func usageCallback(_ values: [Sensor_p]) { + let sensors = values.filter({ !$0.notificationThreshold.isEmpty }) + let title = localizedString("Sensor threshold") + + for s in sensors { + if let threshold = Double(s.notificationThreshold) { + let subtitle = localizedString("\(localizedString(s.name)): \(s.formattedPopupValue)") + self.checkDouble(id: s.key, value: s.value, threshold: threshold, title: title, subtitle: subtitle) + } + } + } + + @objc private func changeSensorNotificaion(_ sender: NSMenuItem) { + guard let id = sender.identifier, let key = sender.representedObject as? String else { return } + Store.shared.set(key: "sensor_\(id.rawValue)_notification", value: key) + } +} diff --git a/Modules/Sensors/values.swift b/Modules/Sensors/values.swift index e5bc3557..dced8a07 100644 --- a/Modules/Sensors/values.swift +++ b/Modules/Sensors/values.swift @@ -36,6 +36,7 @@ public protocol Sensor_p { var value: Double { get set } var state: Bool { get } var popupState: Bool { get } + var notificationThreshold: String { get } var group: SensorGroup { get } var type: SensorType { get } @@ -211,6 +212,9 @@ public struct Sensor: Sensor_p, Codable { public var popupState: Bool { Store.shared.bool(key: "sensor_\(self.key)_popup", defaultValue: true) } + public var notificationThreshold: String { + Store.shared.string(key: "sensor_\(self.key)_notification", defaultValue: "") + } public func copy() -> Sensor { Sensor( @@ -265,6 +269,9 @@ public struct Fan: Sensor_p, Codable { public var popupState: Bool { Store.shared.bool(key: "sensor_\(self.key)_popup", defaultValue: true) } + public var notificationThreshold: String { + Store.shared.string(key: "sensor_\(self.key)_notification", defaultValue: "") + } public var customSpeed: Int? { get {