diff --git a/ModuleKit/Widgets/Sensors.swift b/ModuleKit/Widgets/Sensors.swift index 083fbbd0..f9c6891a 100644 --- a/ModuleKit/Widgets/Sensors.swift +++ b/ModuleKit/Widgets/Sensors.swift @@ -67,15 +67,15 @@ public class SensorsWidget: Widget { let num: Int = Int(round(Double(self.values.count) / 2)) let rowHeight: CGFloat = self.frame.height / 2 - var totalWidth: CGFloat = Constants.Widget.margin + var totalWidth: CGFloat = Constants.Widget.margin // opening space var x: CGFloat = Constants.Widget.margin - var paddingLeft: CGFloat = 0 for i in 0.., _ smc: UnsafePointer) { self.store = store @@ -42,11 +49,14 @@ public class Fans: Module { super.init( store: store, - popup: nil, + popup: self.popupView, settings: self.settingsView ) guard self.available else { return } + self.checkIfNoSensorsEnabled() + self.popupView.setup(self.fansReader.list) + self.settingsView.callback = { [unowned self] in self.checkIfNoSensorsEnabled() self.fansReader.read() @@ -80,10 +90,14 @@ public class Fans: Module { return } + self.popupView.usageCallback(value!) + + let label: Bool = store.pointee.bool(key: "Fans_label", defaultValue: false) var list: [SensorValue_t] = [] value!.forEach { (f: Fan) in - if let value = f.value, f.state { - list.append(SensorValue_t("\(f.name.prefix(1).uppercased()): \(Int(value)) RPM", icon: Bundle(identifier: "eu.exelban.Stats.ModuleKit")?.image(forResource: "fan"))) + if let value = f.formattedValue, f.state { + let str = label ? "\(f.name.prefix(1).uppercased()): \(value)" : value + list.append(SensorValue_t(str, icon: Bundle(identifier: "eu.exelban.Stats.ModuleKit")?.image(forResource: "fan"))) } } diff --git a/Modules/Fans/popup.swift b/Modules/Fans/popup.swift new file mode 100644 index 00000000..92ddbfba --- /dev/null +++ b/Modules/Fans/popup.swift @@ -0,0 +1,179 @@ +// +// settings.swift +// Fans +// +// Created by Serhiy Mytrovtsiy on 21/10/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import ModuleKit +import StatsKit + +internal class Popup: NSView { + private var list: [Int: FanView] = [:] + + public init() { + super.init(frame: NSRect( x: 0, y: 0, width: Constants.Popup.width, height: 0)) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + internal func setup(_ values: [Fan]?) { + guard values != nil else { + return + } + + self.subviews.forEach { (v: NSView) in + v.removeFromSuperview() + } + + let fanViewHeight: CGFloat = 40 + let view: NSView = NSView(frame: NSRect( + x: 0, + y: 0, + width: self.frame.width, + height: ((fanViewHeight+Constants.Popup.margins)*CGFloat(values!.count))-Constants.Popup.margins + )) + var i: CGFloat = 0 + + values!.reversed().forEach { (f: Fan) in + let fanView = FanView( + NSRect( + x: 0, + y: (fanViewHeight + Constants.Popup.margins) * i, + width: self.frame.width, + height: fanViewHeight + ), + fan: f + ) + self.list[f.id] = fanView + view.addSubview(fanView) + i += 1 + } + self.addSubview(view) + + self.setFrameSize(NSSize(width: self.frame.width, height: view.frame.height)) + } + + internal func usageCallback(_ values: [Fan]) { + values.forEach { (f: Fan) in + if self.list[f.id] != nil { + DispatchQueue.main.async(execute: { + if f.value != nil && (self.window?.isVisible ?? false) { + self.list[f.id]?.update(f) + } + }) + } + } + } +} + +internal class FanView: NSView { + private let fan: Fan + private var mainView: NSView + + private var valueField: NSTextField? = nil + private var percentageField: NSTextField? = nil + + private var ready: Bool = false + + public init(_ frame: NSRect, fan: Fan) { + self.fan = fan + self.mainView = NSView(frame: NSRect(x: 5, y: 5, width: frame.width - 10, height: frame.height - 10)) + super.init(frame: frame) + + self.wantsLayer = true + self.layer?.cornerRadius = 2 + + self.addFirstRow() + self.addSecondRow() + + self.addSubview(self.mainView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func updateLayer() { + self.layer?.backgroundColor = isDarkMode ? NSColor(hexString: "#111111", alpha: 0.25).cgColor : NSColor(hexString: "#f5f5f5", alpha: 1).cgColor + } + + private func addFirstRow() { + let row: NSView = NSView(frame: NSRect(x: 0, y: 14, width: self.mainView.frame.width, height: 16)) + + let value = self.fan.formattedValue ?? "0 RPM" + let valueWidth: CGFloat = 80 + + let nameField: NSTextField = TextView(frame: NSRect( + x: 0, + y: 0, + width: self.mainView.frame.width - valueWidth, + height: row.frame.height + )) + nameField.stringValue = self.fan.name + nameField.cell?.truncatesLastVisibleLine = true + + let valueField: NSTextField = TextView(frame: NSRect( + x: self.mainView.frame.width - valueWidth, + y: 0, + width: valueWidth, + height: row.frame.height + )) + valueField.font = NSFont.systemFont(ofSize: 13, weight: .regular) + valueField.stringValue = value + valueField.alignment = .right + + row.addSubview(nameField) + row.addSubview(valueField) + + self.mainView.addSubview(row) + self.valueField = valueField + } + + private func addSecondRow() { + let row: NSView = NSView(frame: NSRect(x: 0, y: 0, width: self.mainView.frame.width, height: 14)) + + let value = (self.fan.value ?? 0) + let percentage = "\((100*Int(value)) / self.fan.maxSpeed)%" + let percentageWidth: CGFloat = 40 + + let percentageField: NSTextField = TextView(frame: NSRect( + x: self.mainView.frame.width - percentageWidth, + y: 0, + width: percentageWidth, + height: row.frame.height + )) + percentageField.font = NSFont.systemFont(ofSize: 11, weight: .light) + percentageField.textColor = .secondaryLabelColor + percentageField.stringValue = percentage + percentageField.alignment = .right + + row.addSubview(percentageField) + self.mainView.addSubview(row) + self.percentageField = percentageField + } + + public func update(_ value: Fan) { + DispatchQueue.main.async(execute: { + if (self.window?.isVisible ?? false) || !self.ready { + + if let view = self.valueField, let value = value.formattedValue { + view.stringValue = value + } + + if let view = self.percentageField, let value = value.value { + view.stringValue = "\((100*Int(value)) / self.fan.maxSpeed)%" + } + + self.ready = true + } + }) + } +} diff --git a/Modules/Fans/settings.swift b/Modules/Fans/settings.swift index 8e93d590..53e01a5a 100644 --- a/Modules/Fans/settings.swift +++ b/Modules/Fans/settings.swift @@ -21,6 +21,8 @@ internal class Settings: NSView, Settings_v { private let store: UnsafePointer private var button: NSPopUpButton? private let list: UnsafeMutablePointer<[Fan]> + private var labelState: Bool = false + public var callback: (() -> Void) = {} public var setInterval: ((_ value: Double) -> Void) = {_ in } @@ -40,6 +42,7 @@ internal class Settings: NSView, Settings_v { self.canDrawConcurrently = true self.updateIntervalValue = store.pointee.string(key: "\(self.title)_updateInterval", defaultValue: self.updateIntervalValue) + self.labelState = store.pointee.bool(key: "\(self.title)_label", defaultValue: self.labelState) } required init?(coder: NSCoder) { @@ -53,7 +56,7 @@ internal class Settings: NSView, Settings_v { self.subviews.forEach{ $0.removeFromSuperview() } let rowHeight: CGFloat = 30 - let settingsHeight: CGFloat = rowHeight + Constants.Settings.margin + let settingsHeight: CGFloat = rowHeight*2 + Constants.Settings.margin let sensorsListHeight: CGFloat = (rowHeight+Constants.Settings.margin) * CGFloat(self.list.pointee.count) + ((rowHeight+Constants.Settings.margin)) let height: CGFloat = settingsHeight + sensorsListHeight let x: CGFloat = height < 360 ? 0 : Constants.Settings.margin @@ -72,7 +75,14 @@ internal class Settings: NSView, Settings_v { selected: "\(self.updateIntervalValue) sec" )) - let rowTitleView: NSView = NSView(frame: NSRect(x: 0, y: height - (rowHeight*2) - Constants.Settings.margin, width: view.frame.width, height: rowHeight)) + self.addSubview(ToggleTitleRow( + frame: NSRect(x: Constants.Settings.margin, y: height - rowHeight*2 - Constants.Settings.margin, width: view.frame.width, height: rowHeight), + title: LocalizedString("Label"), + action: #selector(toggleLabelState), + state: self.labelState + )) + + let rowTitleView: NSView = NSView(frame: NSRect(x: 0, y: height - (rowHeight*3) - Constants.Settings.margin*2, width: view.frame.width, height: rowHeight)) let rowTitle: NSTextField = LabelField(frame: NSRect(x: 0, y: (rowHeight-19)/2, width: view.frame.width, height: 19), "Fans") rowTitle.font = NSFont.systemFont(ofSize: 14, weight: .regular) rowTitle.textColor = .secondaryLabelColor @@ -122,4 +132,17 @@ internal class Settings: NSView, Settings_v { self.setInterval(value) } } + + @objc func toggleLabelState(_ sender: NSControl) { + var state: NSControl.StateValue? = nil + if #available(OSX 10.15, *) { + state = sender is NSSwitch ? (sender as! NSSwitch).state: nil + } else { + state = sender is NSButton ? (sender as! NSButton).state: nil + } + + self.labelState = state! == .on ? true : false + self.store.pointee.set(key: "\(self.title)_label", value: self.labelState) + self.callback() + } } diff --git a/Modules/Sensors/values.swift b/Modules/Sensors/values.swift index c517369d..f90b6a40 100644 --- a/Modules/Sensors/values.swift +++ b/Modules/Sensors/values.swift @@ -97,7 +97,7 @@ let SensorsList: [Sensor_t] = [ Sensor_t(key: "TC0E", name: "CPU 1", group: SensorGroup.CPU.rawValue, type: SensorType.Temperature.rawValue), Sensor_t(key: "TC0F", name: "CPU 2", group: SensorGroup.CPU.rawValue, type: SensorType.Temperature.rawValue), - Sensor_t(key: "TC0D", name: "CPU die", group: SensorGroup.CPU.rawValue, type: SensorType.Temperature.rawValue), + Sensor_t(key: "TC0D", name: "CPU diode", group: SensorGroup.CPU.rawValue, type: SensorType.Temperature.rawValue), Sensor_t(key: "TC0C", name: "CPU core", group: SensorGroup.CPU.rawValue, type: SensorType.Temperature.rawValue), Sensor_t(key: "TC0H", name: "CPU heatsink", group: SensorGroup.CPU.rawValue, type: SensorType.Temperature.rawValue), Sensor_t(key: "TC0P", name: "CPU proximity", group: SensorGroup.CPU.rawValue, type: SensorType.Temperature.rawValue), @@ -121,7 +121,7 @@ let SensorsList: [Sensor_t] = [ Sensor_t(key: "TC15c", name: "CPU core 16", group: SensorGroup.CPU.rawValue, type: SensorType.Temperature.rawValue), Sensor_t(key: "TCGC", name: "GPU Intel Graphics", group: SensorGroup.GPU.rawValue, type: SensorType.Temperature.rawValue), - Sensor_t(key: "TG0D", name: "GPU die", group: SensorGroup.GPU.rawValue, type: SensorType.Temperature.rawValue), + Sensor_t(key: "TG0D", name: "GPU diode", group: SensorGroup.GPU.rawValue, type: SensorType.Temperature.rawValue), Sensor_t(key: "TG0H", name: "GPU heatsink", group: SensorGroup.GPU.rawValue, type: SensorType.Temperature.rawValue), Sensor_t(key: "TG0P", name: "GPU proximity", group: SensorGroup.GPU.rawValue, type: SensorType.Temperature.rawValue), @@ -135,7 +135,7 @@ let SensorsList: [Sensor_t] = [ Sensor_t(key: "TI2P", name: "Thunderbold 3", group: SensorGroup.System.rawValue, type: SensorType.Temperature.rawValue), Sensor_t(key: "TI3P", name: "Thunderbold 4", group: SensorGroup.System.rawValue, type: SensorType.Temperature.rawValue), - Sensor_t(key: "TN0D", name: "Northbridge die", group: SensorGroup.System.rawValue, type: SensorType.Temperature.rawValue), + Sensor_t(key: "TN0D", name: "Northbridge diode", group: SensorGroup.System.rawValue, type: SensorType.Temperature.rawValue), Sensor_t(key: "TN0H", name: "Northbridge heatsink", group: SensorGroup.System.rawValue, type: SensorType.Temperature.rawValue), Sensor_t(key: "TN0P", name: "Northbridge proximity", group: SensorGroup.System.rawValue, type: SensorType.Temperature.rawValue), diff --git a/Stats.xcodeproj/project.pbxproj b/Stats.xcodeproj/project.pbxproj index 34e60861..31473447 100644 --- a/Stats.xcodeproj/project.pbxproj +++ b/Stats.xcodeproj/project.pbxproj @@ -39,6 +39,7 @@ 9A58DEA424B3647600716A9F /* settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A58DEA324B3647600716A9F /* settings.swift */; }; 9A5AF11B2469CE9B00684737 /* popup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A5AF11A2469CE9B00684737 /* popup.swift */; }; 9A65654A253F20EF0096B607 /* settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A656549253F20EF0096B607 /* settings.swift */; }; + 9A656562253F788A0096B607 /* popup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A656561253F788A0096B607 /* popup.swift */; }; 9A6CFC0122A1C9F5001E782D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9A6CFC0022A1C9F5001E782D /* Assets.xcassets */; }; 9A7C61B42440DF810032695D /* Mini.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7C61B32440DF810032695D /* Mini.swift */; }; 9A81C74D24499C7000825D92 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A81C74B24499C7000825D92 /* AppSettings.swift */; }; @@ -95,6 +96,8 @@ 9AB14B78248CEF3B00DC6731 /* config.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9AF9EE12246492E8005D2270 /* config.plist */; }; 9AB14B79248CEF4100DC6731 /* config.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9A3E17DC247A94C300449CD1 /* config.plist */; }; 9AB14B7A248CEF4900DC6731 /* config.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9ABFF904248BEC0B00C9041A /* config.plist */; }; + 9AB1572E25407F7B00671260 /* ModuleKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEADD243FB13500668CB0 /* ModuleKit.framework */; }; + 9AB1573D25407F7E00671260 /* StatsKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */; }; 9AB7FD7C246B48DB00387FDA /* settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AB7FD7B246B48DB00387FDA /* settings.swift */; }; 9ABFF8FD248BEBCB00C9041A /* Battery.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9ABFF8F6248BEBCB00C9041A /* Battery.framework */; }; 9ABFF8FE248BEBCB00C9041A /* Battery.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9ABFF8F6248BEBCB00C9041A /* Battery.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -244,6 +247,20 @@ remoteGlobalIDString = 9AABEADC243FB13500668CB0; remoteInfo = ModuleKit; }; + 9AB1573025407F7B00671260 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9A1410ED229E721100D29793 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9AABEADC243FB13500668CB0; + remoteInfo = ModuleKit; + }; + 9AB1573F25407F7E00671260 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9A1410ED229E721100D29793 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9A0C82D924460F7200FAE3D4; + remoteInfo = StatsKit; + }; 9ABFF8FB248BEBCB00C9041A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 9A1410ED229E721100D29793 /* Project object */; @@ -382,6 +399,7 @@ 9A654920244074B500E30B74 /* extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = extensions.swift; sourceTree = ""; }; 9A65492224407EA600E30B74 /* store.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = store.swift; sourceTree = ""; }; 9A656549253F20EF0096B607 /* settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = settings.swift; sourceTree = ""; }; + 9A656561253F788A0096B607 /* popup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = popup.swift; sourceTree = ""; }; 9A6CFC0022A1C9F5001E782D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 9A7C61B32440DF810032695D /* Mini.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mini.swift; sourceTree = ""; }; 9A7D0CB62444C2C800B09070 /* SystemKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemKit.swift; sourceTree = ""; }; @@ -516,6 +534,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 9AB1572E25407F7B00671260 /* ModuleKit.framework in Frameworks */, + 9AB1573D25407F7E00671260 /* StatsKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -715,6 +735,7 @@ 9A8DE5E3253DF4E2006A748F /* main.swift */, 9A8DE608253DF740006A748F /* readers.swift */, 9A656549253F20EF0096B607 /* settings.swift */, + 9A656561253F788A0096B607 /* popup.swift */, 9A8DE58A253DEFA9006A748F /* Info.plist */, 9A8DE5FB253DF658006A748F /* config.plist */, ); @@ -1039,6 +1060,8 @@ buildRules = ( ); dependencies = ( + 9AB1573125407F7B00671260 /* PBXTargetDependency */, + 9AB1574025407F7E00671260 /* PBXTargetDependency */, ); name = Fans; productName = Fans; @@ -1440,6 +1463,7 @@ files = ( 9A8DE609253DF740006A748F /* readers.swift in Sources */, 9A8DE5E4253DF4E2006A748F /* main.swift in Sources */, + 9A656562253F788A0096B607 /* popup.swift in Sources */, 9A65654A253F20EF0096B607 /* settings.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1609,6 +1633,16 @@ target = 9AABEADC243FB13500668CB0 /* ModuleKit */; targetProxy = 9AABEAE2243FB13500668CB0 /* PBXContainerItemProxy */; }; + 9AB1573125407F7B00671260 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9AABEADC243FB13500668CB0 /* ModuleKit */; + targetProxy = 9AB1573025407F7B00671260 /* PBXContainerItemProxy */; + }; + 9AB1574025407F7E00671260 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9A0C82D924460F7200FAE3D4 /* StatsKit */; + targetProxy = 9AB1573F25407F7E00671260 /* PBXContainerItemProxy */; + }; 9ABFF8FC248BEBCB00C9041A /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 9ABFF8F5248BEBCB00C9041A /* Battery */; @@ -2310,6 +2344,7 @@ 9AABEAE6243FB13500668CB0 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; @@ -2341,6 +2376,7 @@ 9AABEAE7243FB13500668CB0 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic;