- add Fans popup

- add option to show/hide a label to fans widget
This commit is contained in:
Serhiy Mytrovtsiy
2020-10-22 10:54:43 +02:00
parent e2e575d0e4
commit 2b48cad76a
6 changed files with 269 additions and 11 deletions

View File

@@ -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..<num {
if !self.values.indices.contains(i*2) {
continue
}
var width: CGFloat = 0
var paddingLeft: CGFloat = 0
if self.values.indices.contains((i*2)+1) {
var font: NSFont = NSFont.systemFont(ofSize: 9, weight: .light)
@@ -140,8 +140,14 @@ public class SensorsWidget: Widget {
x += width
totalWidth += width
// add margins between columns
if num != 1 && (i/2) != num {
x += Constants.Widget.margin
totalWidth += Constants.Widget.margin
}
}
totalWidth += Constants.Widget.margin
totalWidth += Constants.Widget.margin // closing space
if abs(self.frame.width - totalWidth) < 2 {
return

View File

@@ -25,6 +25,12 @@ public struct Fan {
return Store.shared.bool(key: "fan_\(self.id)", defaultValue: true)
}
}
var formattedValue: String? {
get {
return (value != nil) ? "\(Int(value!)) RPM": nil
}
}
}
public class Fans: Module {
@@ -33,6 +39,7 @@ public class Fans: Module {
private var fansReader: FansReader
private var settingsView: Settings
private let popupView: Popup = Popup()
public init(_ store: UnsafePointer<Store>, _ smc: UnsafePointer<SMCService>) {
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")))
}
}

179
Modules/Fans/popup.swift Normal file
View File

@@ -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
}
})
}
}

View File

@@ -21,6 +21,8 @@ internal class Settings: NSView, Settings_v {
private let store: UnsafePointer<Store>
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()
}
}

View File

@@ -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),

View File

@@ -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 = "<group>"; };
9A65492224407EA600E30B74 /* store.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = store.swift; sourceTree = "<group>"; };
9A656549253F20EF0096B607 /* settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = settings.swift; sourceTree = "<group>"; };
9A656561253F788A0096B607 /* popup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = popup.swift; sourceTree = "<group>"; };
9A6CFC0022A1C9F5001E782D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
9A7C61B32440DF810032695D /* Mini.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mini.swift; sourceTree = "<group>"; };
9A7D0CB62444C2C800B09070 /* SystemKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemKit.swift; sourceTree = "<group>"; };
@@ -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;