mirror of
https://github.com/morgan9e/macos-stats
synced 2026-04-14 00:04:15 +09:00
- add temperature module (CPU and GPU temperature);
- fix value margin in network widget view; - add missing widgets name; - improve battery module updates;
This commit is contained in:
@@ -51,6 +51,11 @@
|
||||
9A6CFC0122A1C9F5001E782D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9A6CFC0022A1C9F5001E782D /* Assets.xcassets */; };
|
||||
9A74D59722B44498004FE1FA /* Mini.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A74D59622B44498004FE1FA /* Mini.swift */; };
|
||||
9A79B36A22D3BEE600BF1C3A /* Widget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A79B36922D3BEE600BF1C3A /* Widget.swift */; };
|
||||
9AA28DC1243774ED00D2B196 /* Temperature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA28DC0243774ED00D2B196 /* Temperature.swift */; };
|
||||
9AA28DC32437752D00D2B196 /* TemperatureMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA28DC22437752D00D2B196 /* TemperatureMenu.swift */; };
|
||||
9AA28DC52437762C00D2B196 /* TemperatureReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA28DC42437762C00D2B196 /* TemperatureReader.swift */; };
|
||||
9AA28DCF2437884200D2B196 /* SystemKit.c in Sources */ = {isa = PBXBuildFile; fileRef = 9AA28DCE2437884200D2B196 /* SystemKit.c */; };
|
||||
9AA28DD1243799E500D2B196 /* TemperatureWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA28DD0243799E500D2B196 /* TemperatureWidget.swift */; };
|
||||
9AF0F31B22DA924000026AE6 /* LineChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF0F31A22DA924000026AE6 /* LineChart.swift */; };
|
||||
9AF0F31D22DA925000026AE6 /* LineChartWithValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF0F31C22DA925000026AE6 /* LineChartWithValue.swift */; };
|
||||
9AF0F31F22DA925700026AE6 /* BarChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF0F31E22DA925700026AE6 /* BarChart.swift */; };
|
||||
@@ -139,6 +144,13 @@
|
||||
9A79B36922D3BEE600BF1C3A /* Widget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Widget.swift; sourceTree = "<group>"; };
|
||||
9A998CD722A199920087ADE7 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
|
||||
9A998CD922A199970087ADE7 /* ServiceManagement.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ServiceManagement.framework; path = System/Library/Frameworks/ServiceManagement.framework; sourceTree = SDKROOT; };
|
||||
9AA28DC0243774ED00D2B196 /* Temperature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Temperature.swift; sourceTree = "<group>"; };
|
||||
9AA28DC22437752D00D2B196 /* TemperatureMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemperatureMenu.swift; sourceTree = "<group>"; };
|
||||
9AA28DC42437762C00D2B196 /* TemperatureReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemperatureReader.swift; sourceTree = "<group>"; };
|
||||
9AA28DC9243780C500D2B196 /* Stats.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Stats.h; sourceTree = "<group>"; };
|
||||
9AA28DCD2437884200D2B196 /* SystemKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SystemKit.h; sourceTree = "<group>"; };
|
||||
9AA28DCE2437884200D2B196 /* SystemKit.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SystemKit.c; sourceTree = "<group>"; };
|
||||
9AA28DD0243799E500D2B196 /* TemperatureWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemperatureWidget.swift; sourceTree = "<group>"; };
|
||||
9AF0F31A22DA924000026AE6 /* LineChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineChart.swift; sourceTree = "<group>"; };
|
||||
9AF0F31C22DA925000026AE6 /* LineChartWithValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineChartWithValue.swift; sourceTree = "<group>"; };
|
||||
9AF0F31E22DA925700026AE6 /* BarChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarChart.swift; sourceTree = "<group>"; };
|
||||
@@ -283,6 +295,7 @@
|
||||
9A2D15F123CE390500C4C417 /* Disk */,
|
||||
9A2D15F823CE3BDA00C4C417 /* Battery */,
|
||||
9A2D160123CE444D00C4C417 /* Network */,
|
||||
9AA28DBF243774DD00D2B196 /* Temperature */,
|
||||
9A2D15D123CCEC7600C4C417 /* Module.swift */,
|
||||
);
|
||||
path = Modules;
|
||||
@@ -295,6 +308,9 @@
|
||||
9A426DB722C2B5EE00C064C4 /* MacAppUpdater.swift */,
|
||||
9A59AE55231EE02F007989D6 /* ChartMarker.swift */,
|
||||
9A2D15CF23C77BA300C4C417 /* Repeater.swift */,
|
||||
9AA28DC9243780C500D2B196 /* Stats.h */,
|
||||
9AA28DCD2437884200D2B196 /* SystemKit.h */,
|
||||
9AA28DCE2437884200D2B196 /* SystemKit.c */,
|
||||
);
|
||||
path = libs;
|
||||
sourceTree = "<group>";
|
||||
@@ -305,6 +321,7 @@
|
||||
9A54EF65232AB48100F7DC20 /* Battery */,
|
||||
9AF0F31922DA923100026AE6 /* Network */,
|
||||
9AF0F31822DA922800026AE6 /* Charts */,
|
||||
9AA28DD224379F8700D2B196 /* Temperature */,
|
||||
9A74D59622B44498004FE1FA /* Mini.swift */,
|
||||
9A79B36922D3BEE600BF1C3A /* Widget.swift */,
|
||||
);
|
||||
@@ -324,6 +341,24 @@
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9AA28DBF243774DD00D2B196 /* Temperature */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9AA28DC0243774ED00D2B196 /* Temperature.swift */,
|
||||
9AA28DC22437752D00D2B196 /* TemperatureMenu.swift */,
|
||||
9AA28DC42437762C00D2B196 /* TemperatureReader.swift */,
|
||||
);
|
||||
path = Temperature;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9AA28DD224379F8700D2B196 /* Temperature */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9AA28DD0243799E500D2B196 /* TemperatureWidget.swift */,
|
||||
);
|
||||
path = Temperature;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9AF0F31822DA922800026AE6 /* Charts */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -483,8 +518,10 @@
|
||||
9A2D15E623CE291600C4C417 /* RAM.swift in Sources */,
|
||||
9A09C8A222B3D94D0018426F /* BatteryWidget.swift in Sources */,
|
||||
9A5349C723D8535900C23824 /* NetworkPopup.swift in Sources */,
|
||||
9AA28DC32437752D00D2B196 /* TemperatureMenu.swift in Sources */,
|
||||
9A426DB822C2B5EE00C064C4 /* MacAppUpdater.swift in Sources */,
|
||||
9A606B4C232157BA00642F51 /* AboutViewController.swift in Sources */,
|
||||
9AA28DC1243774ED00D2B196 /* Temperature.swift in Sources */,
|
||||
9AF0F32522DA92C400026AE6 /* NetworkText.swift in Sources */,
|
||||
9AF0F32322DA92B900026AE6 /* NetworkArrows.swift in Sources */,
|
||||
9A2D15D223CCEC7600C4C417 /* Module.swift in Sources */,
|
||||
@@ -493,8 +530,11 @@
|
||||
9A606B4A2321577400642F51 /* UpdatesViewController.swift in Sources */,
|
||||
9AF0F31D22DA925000026AE6 /* LineChartWithValue.swift in Sources */,
|
||||
9A2D160723CE462400C4C417 /* NetworkReader.swift in Sources */,
|
||||
9AA28DD1243799E500D2B196 /* TemperatureWidget.swift in Sources */,
|
||||
9A2D15FA23CE3BE600C4C417 /* Battery.swift in Sources */,
|
||||
9A54EF67232AB81800F7DC20 /* BatteryPercentageWidget.swift in Sources */,
|
||||
9AA28DCF2437884200D2B196 /* SystemKit.c in Sources */,
|
||||
9AA28DC52437762C00D2B196 /* TemperatureReader.swift in Sources */,
|
||||
9A57A18522A1D26D0033E318 /* MenuBar.swift in Sources */,
|
||||
9A2D15D923CD036400C4C417 /* CPUMenu.swift in Sources */,
|
||||
9AF6F1FE231D732600B8E1E4 /* PopupViewController.swift in Sources */,
|
||||
@@ -683,6 +723,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/$(PROJECT_NAME)/libs/$(SWIFT_MODULE_NAME).h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
@@ -712,6 +753,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/$(PROJECT_NAME)/libs/$(SWIFT_MODULE_NAME).h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
|
||||
@@ -20,6 +20,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
private let popover = NSPopover()
|
||||
|
||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||
SMCOpen();
|
||||
|
||||
guard let menuBarButton = self.menuBarItem.button else {
|
||||
NSApp.terminate(nil)
|
||||
return
|
||||
@@ -40,6 +42,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ aNotification: Notification) {
|
||||
SMCClose()
|
||||
menuBar?.destroy()
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import ServiceManagement
|
||||
Class keeps a status bar item and has the main function for updating widgets.
|
||||
*/
|
||||
class MenuBar {
|
||||
public let modules: [Module] = [CPU(), RAM(), Disk(), Battery(), Network()]
|
||||
public let modules: [Module] = [CPU(), RAM(), Temperature(), Disk(), Battery(), Network()]
|
||||
|
||||
private let menuBarItem: NSStatusItem
|
||||
private var menuBarButton: NSButton = NSButton()
|
||||
|
||||
@@ -56,24 +56,14 @@ class Battery: Module {
|
||||
self.initPopup()
|
||||
|
||||
readers.append(BatteryReader(self.usageUpdater))
|
||||
|
||||
self.task = Repeater.init(interval: .seconds(self.updateInterval), observer: { _ in
|
||||
self.readers.forEach { reader in
|
||||
reader.read()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public func start() {
|
||||
if self.task != nil && self.task!.state.isRunning == false {
|
||||
self.task!.start()
|
||||
}
|
||||
(readers[0] as! BatteryReader).start()
|
||||
}
|
||||
|
||||
public func stop() {
|
||||
if self.task != nil && self.task!.state.isRunning {
|
||||
self.task?.pause()
|
||||
}
|
||||
(readers[0] as! BatteryReader).stop()
|
||||
}
|
||||
|
||||
public func restart() {
|
||||
@@ -90,11 +80,11 @@ class Battery: Module {
|
||||
}
|
||||
|
||||
if self.widget.view is Widget {
|
||||
(self.widget.view as! Widget).setValue(data: [abs(value.capacity), Double(time)])
|
||||
(self.widget.view as! Widget).setValue(data: [abs(value.level), Double(time)])
|
||||
|
||||
if self.widget.view is BatteryWidget && value.capacity != 100 {
|
||||
(self.widget.view as! BatteryWidget).setCharging(value: value.capacity > 0)
|
||||
} else if self.widget.view is BatteryWidget && value.capacity == 100 {
|
||||
if self.widget.view is BatteryWidget && value.level != 100 {
|
||||
(self.widget.view as! BatteryWidget).setCharging(value: value.level > 0)
|
||||
} else if self.widget.view is BatteryWidget && value.level == 100 {
|
||||
(self.widget.view as! BatteryWidget).setCharging(value: false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,9 +40,6 @@ extension Battery {
|
||||
}
|
||||
}
|
||||
|
||||
submenu.addItem(NSMenuItem.separator())
|
||||
submenu.addItem(generateIntervalMenu())
|
||||
|
||||
if self.enabled {
|
||||
menu.submenu = submenu
|
||||
}
|
||||
@@ -94,71 +91,4 @@ extension Battery {
|
||||
self.initMenu()
|
||||
menuBar!.reload(name: self.name)
|
||||
}
|
||||
|
||||
private func generateIntervalMenu() -> NSMenuItem {
|
||||
let updateInterval = NSMenuItem(title: "Update interval", action: nil, keyEquivalent: "")
|
||||
|
||||
let updateIntervals = NSMenu()
|
||||
let updateInterval_1 = NSMenuItem(title: "1s", action: #selector(changeInterval), keyEquivalent: "")
|
||||
updateInterval_1.state = self.updateInterval == 1 ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
updateInterval_1.target = self
|
||||
let updateInterval_2 = NSMenuItem(title: "3s", action: #selector(changeInterval), keyEquivalent: "")
|
||||
updateInterval_2.state = self.updateInterval == 3 ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
updateInterval_2.target = self
|
||||
let updateInterval_3 = NSMenuItem(title: "5s", action: #selector(changeInterval), keyEquivalent: "")
|
||||
updateInterval_3.state = self.updateInterval == 5 ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
updateInterval_3.target = self
|
||||
let updateInterval_4 = NSMenuItem(title: "10s", action: #selector(changeInterval), keyEquivalent: "")
|
||||
updateInterval_4.state = self.updateInterval == 10 ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
updateInterval_4.target = self
|
||||
let updateInterval_5 = NSMenuItem(title: "15s", action: #selector(changeInterval), keyEquivalent: "")
|
||||
updateInterval_5.state = self.updateInterval == 15 ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
updateInterval_5.target = self
|
||||
|
||||
updateIntervals.addItem(updateInterval_1)
|
||||
updateIntervals.addItem(updateInterval_2)
|
||||
updateIntervals.addItem(updateInterval_3)
|
||||
updateIntervals.addItem(updateInterval_4)
|
||||
updateIntervals.addItem(updateInterval_5)
|
||||
|
||||
updateInterval.submenu = updateIntervals
|
||||
|
||||
return updateInterval
|
||||
}
|
||||
|
||||
@objc func changeInterval(_ sender: NSMenuItem) {
|
||||
var interval: Double = self.updateInterval
|
||||
|
||||
switch sender.title {
|
||||
case "1s":
|
||||
interval = 1
|
||||
case "3s":
|
||||
interval = 3
|
||||
case "5s":
|
||||
interval = 5
|
||||
case "10s":
|
||||
interval = 10
|
||||
case "15s":
|
||||
interval = 15
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if interval == self.updateInterval {
|
||||
return
|
||||
}
|
||||
|
||||
for item in self.submenu.items {
|
||||
if item.title == "Update interval" {
|
||||
for subitem in item.submenu!.items {
|
||||
subitem.state = NSControl.StateValue.off
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sender.state = NSControl.StateValue.on
|
||||
self.updateInterval = interval
|
||||
self.defaults.set(interval, forKey: "\(name)_interval")
|
||||
self.task?.reset(.seconds(interval), restart: self.task!.state.isRunning)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +218,7 @@ extension Battery {
|
||||
self.popup.initialized = true
|
||||
|
||||
// makeMain
|
||||
self.levelValue.stringValue = "\(Int(abs(value.capacity) * 100)) %"
|
||||
self.levelValue.stringValue = "\(Int(abs(value.level) * 100)) %"
|
||||
self.sourceValue.stringValue = value.powerSource
|
||||
if value.powerSource == "Battery Power" {
|
||||
self.timeLabel.stringValue = "Time to discharge"
|
||||
@@ -252,6 +252,6 @@ extension Battery {
|
||||
|
||||
// makePowerAdapter
|
||||
self.powerValue.stringValue = value.powerSource == "Battery Power" ? "Not connected" : "\(value.ACwatts) W"
|
||||
self.chargingValue.stringValue = value.capacity > 0 ? "Yes" : "No"
|
||||
self.chargingValue.stringValue = value.level > 0 ? "Yes" : "No"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ struct BatteryUsage {
|
||||
var powerSource: String = ""
|
||||
var state: String = ""
|
||||
var isCharged: Bool = false
|
||||
var capacity: Double = 0
|
||||
var level: Double = 0
|
||||
var cycles: Int = 0
|
||||
var health: Int = 0
|
||||
|
||||
@@ -46,29 +46,49 @@ class BatteryReader: Reader {
|
||||
public var initialized: Bool = false
|
||||
public var callback: (BatteryUsage) -> Void = {_ in}
|
||||
|
||||
private var service: io_connect_t = 0
|
||||
private var service: io_connect_t = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleSmartBattery"))
|
||||
private var internalChecked: Bool = false
|
||||
private var hasInternalBattery: Bool = false
|
||||
|
||||
private var source: CFRunLoopSource?
|
||||
private var loop: CFRunLoop?
|
||||
|
||||
init(_ updater: @escaping (BatteryUsage) -> Void) {
|
||||
self.callback = updater
|
||||
|
||||
self.service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleSmartBattery"))
|
||||
|
||||
if self.available {
|
||||
DispatchQueue.global(qos: .default).async {
|
||||
self.read()
|
||||
}
|
||||
}
|
||||
self.read()
|
||||
}
|
||||
|
||||
|
||||
public func start() {
|
||||
let context = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
|
||||
|
||||
source = IOPSNotificationCreateRunLoopSource({ (context) in
|
||||
guard let ctx = context else {
|
||||
return
|
||||
}
|
||||
|
||||
let watcher = Unmanaged<BatteryReader>.fromOpaque(ctx).takeUnretainedValue()
|
||||
watcher.read()
|
||||
}, context).takeRetainedValue()
|
||||
|
||||
loop = RunLoop.current.getCFRunLoop()
|
||||
CFRunLoopAddSource(loop, source, .defaultMode)
|
||||
}
|
||||
|
||||
public func stop() {
|
||||
guard let runLoop = loop, let source = source else {
|
||||
return
|
||||
}
|
||||
|
||||
CFRunLoopRemoveSource(runLoop, source, .defaultMode)
|
||||
}
|
||||
|
||||
public func read() {
|
||||
if !self.enabled && self.initialized { return }
|
||||
self.initialized = true
|
||||
|
||||
|
||||
let psInfo = IOPSCopyPowerSourcesInfo().takeRetainedValue()
|
||||
let psList = IOPSCopyPowerSourcesList(psInfo).takeRetainedValue() as [CFTypeRef]
|
||||
|
||||
|
||||
for ps in psList {
|
||||
if let list = IOPSGetPowerSourceDescription(psInfo, ps).takeUnretainedValue() as? Dictionary<String, Any> {
|
||||
let powerSource = list[kIOPSPowerSourceStateKey] as? String ?? "AC Power"
|
||||
@@ -80,42 +100,45 @@ class BatteryReader: Reader {
|
||||
let timeToCharged = Int(list[kIOPSTimeToFullChargeKey] as! Int)
|
||||
|
||||
let cycles = self.getIntValue("CycleCount" as CFString) ?? 0
|
||||
|
||||
|
||||
let maxCapacity = self.getIntValue("MaxCapacity" as CFString) ?? 1
|
||||
let designCapacity = self.getIntValue("DesignCapacity" as CFString) ?? 1
|
||||
|
||||
|
||||
let amperage = self.getIntValue("Amperage" as CFString) ?? 0
|
||||
let voltage = self.getVoltage() ?? 0
|
||||
let temperature = self.getTemperature() ?? 0
|
||||
|
||||
|
||||
var ACwatts: Int = 0
|
||||
if let ACDetails = IOPSCopyExternalPowerAdapterDetails() {
|
||||
if let ACList = ACDetails.takeUnretainedValue() as? Dictionary<String, Any> {
|
||||
ACwatts = Int(ACList[kIOPSPowerAdapterWattsKey] as! Int)
|
||||
guard let watts = ACList[kIOPSPowerAdapterWattsKey] else {
|
||||
return
|
||||
}
|
||||
ACwatts = Int(watts as! Int)
|
||||
}
|
||||
}
|
||||
let ACstatus = self.getBoolValue("IsCharging" as CFString) ?? false
|
||||
|
||||
|
||||
if powerSource == "Battery Power" {
|
||||
cap = 0 - cap
|
||||
}
|
||||
|
||||
|
||||
DispatchQueue.main.async(execute: {
|
||||
let usage = BatteryUsage(
|
||||
powerSource: powerSource,
|
||||
state: state,
|
||||
isCharged: isCharged,
|
||||
capacity: Double(cap),
|
||||
level: Double(cap),
|
||||
cycles: cycles,
|
||||
health: (100 * maxCapacity) / designCapacity,
|
||||
|
||||
|
||||
amperage: amperage,
|
||||
voltage: voltage,
|
||||
temperature: temperature,
|
||||
|
||||
|
||||
ACwatts: ACwatts,
|
||||
ACstatus: ACstatus,
|
||||
|
||||
|
||||
timeToEmpty: timeToEmpty,
|
||||
timeToCharge: timeToCharged
|
||||
)
|
||||
@@ -135,28 +158,28 @@ class BatteryReader: Reader {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
private func getIntValue(_ identifier: CFString) -> Int? {
|
||||
if let value = IORegistryEntryCreateCFProperty(self.service, identifier, kCFAllocatorDefault, 0) {
|
||||
return value.takeRetainedValue() as? Int
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
private func getDoubleValue(_ identifier: CFString) -> Double? {
|
||||
if let value = IORegistryEntryCreateCFProperty(self.service, identifier, kCFAllocatorDefault, 0) {
|
||||
return value.takeRetainedValue() as? Double
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
private func getVoltage() -> Double? {
|
||||
if let value = self.getDoubleValue("Voltage" as CFString) {
|
||||
return value / 1000.0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
private func getTemperature() -> Double? {
|
||||
if let value = IORegistryEntryCreateCFProperty(self.service, "Temperature" as CFString, kCFAllocatorDefault, 0) {
|
||||
return value.takeRetainedValue() as! Double / 100.0
|
||||
|
||||
@@ -75,6 +75,8 @@ extension Module {
|
||||
switch self.widget.type {
|
||||
case Widgets.Mini:
|
||||
widget = Mini()
|
||||
case Widgets.Temperature:
|
||||
widget = TemperatureWidget()
|
||||
case Widgets.Chart:
|
||||
widget = Chart()
|
||||
case Widgets.ChartWithValue:
|
||||
|
||||
78
Stats/Modules/Temperature/Temperature.swift
Normal file
78
Stats/Modules/Temperature/Temperature.swift
Normal file
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// Temperature.swift
|
||||
// Stats
|
||||
//
|
||||
// Created by Serhiy Mytrovtsiy on 03/04/2020.
|
||||
// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class Temperature: Module {
|
||||
public var name: String = "Temperature"
|
||||
public var updateInterval: Double = 1
|
||||
|
||||
public var enabled: Bool = true
|
||||
public var available: Bool = true
|
||||
|
||||
public var widget: ModuleWidget = ModuleWidget()
|
||||
public var popup: ModulePopup = ModulePopup(false)
|
||||
public var menu: NSMenuItem = NSMenuItem()
|
||||
|
||||
public var readers: [Reader] = []
|
||||
public var task: Repeater?
|
||||
|
||||
internal let defaults = UserDefaults.standard
|
||||
internal var submenu: NSMenu = NSMenu()
|
||||
|
||||
internal var cpu: String = SMC_TEMP_CPU_0_PROXIMITY
|
||||
internal var gpu: String = SMC_TEMP_GPU_0_PROXIMITY
|
||||
|
||||
init() {
|
||||
if !self.available { return }
|
||||
|
||||
self.enabled = defaults.object(forKey: name) != nil ? defaults.bool(forKey: name) : true
|
||||
self.widget.type = defaults.object(forKey: "\(name)_widget") != nil ? defaults.float(forKey: "\(name)_widget") : Widgets.Temperature
|
||||
self.cpu = (defaults.object(forKey: "\(name)_cpu") != nil ? defaults.string(forKey: "\(name)_cpu") : cpu)!
|
||||
self.gpu = (defaults.object(forKey: "\(name)_gpu") != nil ? defaults.string(forKey: "\(name)_gpu") : gpu)!
|
||||
|
||||
self.initWidget()
|
||||
self.initMenu()
|
||||
|
||||
readers.append(TemperatureReader(self.usageUpdater))
|
||||
|
||||
self.task = Repeater.init(interval: .seconds(self.updateInterval), observer: { _ in
|
||||
self.readers.forEach { reader in
|
||||
reader.read()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func start() {
|
||||
if self.task != nil && self.task!.state.isRunning == false {
|
||||
self.task!.start()
|
||||
}
|
||||
}
|
||||
|
||||
func stop() {
|
||||
if self.task!.state.isRunning {
|
||||
self.task?.pause()
|
||||
}
|
||||
}
|
||||
|
||||
func restart() {
|
||||
self.stop()
|
||||
self.start()
|
||||
}
|
||||
|
||||
private func usageUpdater(value: TemperatureValue) {
|
||||
if self.widget.view is Widget {
|
||||
DispatchQueue.main.async(execute: {
|
||||
let cpu: Double = self.cpu == SMC_TEMP_CPU_0_DIE ? value.CPUDie : value.CPUProximity
|
||||
let gpu: Double = self.gpu == SMC_TEMP_GPU_0_DIODE ? value.GPUDie : value.GPUProximity
|
||||
|
||||
(self.widget.view as! Widget).setValue(data: [cpu, gpu])
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
127
Stats/Modules/Temperature/TemperatureMenu.swift
Normal file
127
Stats/Modules/Temperature/TemperatureMenu.swift
Normal file
@@ -0,0 +1,127 @@
|
||||
//
|
||||
// TemperatureMenu.swift
|
||||
// Stats
|
||||
//
|
||||
// Created by Serhiy Mytrovtsiy on 03/04/2020.
|
||||
// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
extension Temperature {
|
||||
public func initMenu() {
|
||||
menu = NSMenuItem(title: name, action: #selector(toggle), keyEquivalent: "")
|
||||
submenu = NSMenu()
|
||||
|
||||
if defaults.object(forKey: name) != nil {
|
||||
menu.state = defaults.bool(forKey: name) ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
} else {
|
||||
menu.state = NSControl.StateValue.on
|
||||
}
|
||||
menu.target = self
|
||||
|
||||
let cpuDie: NSMenuItem = NSMenuItem(title: "CPU Die", action: #selector(toggleCPU), keyEquivalent: "")
|
||||
cpuDie.state = self.cpu == SMC_TEMP_CPU_0_DIE ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
cpuDie.target = self
|
||||
|
||||
let cpuProximity: NSMenuItem = NSMenuItem(title: "CPU Proximity", action: #selector(toggleCPU), keyEquivalent: "")
|
||||
cpuProximity.state = self.cpu == SMC_TEMP_CPU_0_PROXIMITY ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
cpuProximity.target = self
|
||||
|
||||
let gpuDie: NSMenuItem = NSMenuItem(title: "GPU Die", action: #selector(toggleGPU), keyEquivalent: "")
|
||||
gpuDie.state = self.gpu == SMC_TEMP_GPU_0_DIODE ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
gpuDie.target = self
|
||||
|
||||
let gpuProximity: NSMenuItem = NSMenuItem(title: "GPU Proximity", action: #selector(toggleGPU), keyEquivalent: "")
|
||||
gpuProximity.state = self.gpu == SMC_TEMP_GPU_0_PROXIMITY ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
gpuProximity.target = self
|
||||
|
||||
submenu.addItem(cpuProximity)
|
||||
submenu.addItem(cpuDie)
|
||||
submenu.addItem(NSMenuItem.separator())
|
||||
submenu.addItem(gpuProximity)
|
||||
submenu.addItem(gpuDie)
|
||||
|
||||
submenu.addItem(NSMenuItem.separator())
|
||||
|
||||
if let view = self.widget.view as? Widget {
|
||||
for widgetMenu in view.menus {
|
||||
submenu.addItem(widgetMenu)
|
||||
}
|
||||
}
|
||||
|
||||
if self.enabled {
|
||||
menu.submenu = submenu
|
||||
}
|
||||
}
|
||||
|
||||
@objc func toggle(_ sender: NSMenuItem) {
|
||||
let state = sender.state != NSControl.StateValue.on
|
||||
sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on
|
||||
self.defaults.set(state, forKey: name)
|
||||
self.enabled = state
|
||||
menuBar!.reload(name: self.name)
|
||||
|
||||
if !state {
|
||||
menu.submenu = nil
|
||||
} else {
|
||||
menu.submenu = submenu
|
||||
}
|
||||
|
||||
self.restart()
|
||||
}
|
||||
|
||||
@objc func toggleCPU(_ sender: NSMenuItem) {
|
||||
var cpu: String = sender.title
|
||||
|
||||
switch cpu {
|
||||
case "CPU Die":
|
||||
cpu = SMC_TEMP_CPU_0_DIE
|
||||
case "CPU Proximity":
|
||||
cpu = SMC_TEMP_CPU_0_PROXIMITY
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
let state = sender.state == NSControl.StateValue.on
|
||||
for item in self.submenu.items {
|
||||
if item.title == "CPU Die" || item.title == "CPU Proximity" {
|
||||
item.state = NSControl.StateValue.off
|
||||
}
|
||||
}
|
||||
|
||||
sender.state = state ? NSControl.StateValue.off : NSControl.StateValue.on
|
||||
self.defaults.set(cpu, forKey: "\(name)_cpu")
|
||||
self.cpu = cpu
|
||||
self.initWidget()
|
||||
self.initMenu()
|
||||
menuBar!.reload(name: self.name)
|
||||
}
|
||||
|
||||
@objc func toggleGPU(_ sender: NSMenuItem) {
|
||||
var gpu: String = sender.title
|
||||
|
||||
switch gpu {
|
||||
case "GPU Die":
|
||||
gpu = SMC_TEMP_GPU_0_DIODE
|
||||
case "GPU Proximity":
|
||||
gpu = SMC_TEMP_GPU_0_PROXIMITY
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
let state = sender.state == NSControl.StateValue.on
|
||||
for item in self.submenu.items {
|
||||
if item.title == "GPU Die" || item.title == "GPU Proximity" {
|
||||
item.state = NSControl.StateValue.off
|
||||
}
|
||||
}
|
||||
|
||||
sender.state = state ? NSControl.StateValue.off : NSControl.StateValue.on
|
||||
self.defaults.set(gpu, forKey: "\(name)_gpu")
|
||||
self.gpu = gpu
|
||||
self.initWidget()
|
||||
self.initMenu()
|
||||
menuBar!.reload(name: self.name)
|
||||
}
|
||||
}
|
||||
55
Stats/Modules/Temperature/TemperatureReader.swift
Normal file
55
Stats/Modules/Temperature/TemperatureReader.swift
Normal file
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// TemperatureReader.swift
|
||||
// Stats
|
||||
//
|
||||
// Created by Serhiy Mytrovtsiy on 03/04/2020.
|
||||
// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
|
||||
//
|
||||
|
||||
import IOKit
|
||||
import Foundation
|
||||
|
||||
struct TemperatureValue {
|
||||
var CPUDie: Double = 0
|
||||
var CPUProximity: Double = 0
|
||||
var GPUDie: Double = 0
|
||||
var GPUProximity: Double = 0
|
||||
}
|
||||
|
||||
class TemperatureReader: Reader {
|
||||
public var name: String = "Temperature"
|
||||
public var enabled: Bool = true
|
||||
public var available: Bool = true
|
||||
public var optional: Bool = false
|
||||
public var initialized: Bool = false
|
||||
|
||||
public var callback: (TemperatureValue) -> Void = {_ in}
|
||||
|
||||
init(_ updater: @escaping (TemperatureValue) -> Void) {
|
||||
self.callback = updater
|
||||
|
||||
if self.available {
|
||||
DispatchQueue.global(qos: .default).async {
|
||||
self.read()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func read() {
|
||||
if !self.enabled && self.initialized { return }
|
||||
self.initialized = true
|
||||
|
||||
let temp = TemperatureValue(
|
||||
CPUDie: GetTemperature(SMC_TEMP_CPU_0_DIE.UTF8CString),
|
||||
CPUProximity: GetTemperature(SMC_TEMP_CPU_0_PROXIMITY.UTF8CString),
|
||||
GPUDie: GetTemperature(SMC_TEMP_GPU_0_DIODE.UTF8CString),
|
||||
GPUProximity: GetTemperature(SMC_TEMP_GPU_0_PROXIMITY.UTF8CString)
|
||||
)
|
||||
|
||||
self.callback(temp)
|
||||
}
|
||||
|
||||
func toggleEnable(_ value: Bool) {
|
||||
self.enabled = value
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array/>
|
||||
</dict>
|
||||
<dict/>
|
||||
</plist>
|
||||
|
||||
@@ -17,6 +17,7 @@ class BatteryPercentageWidget: BatteryWidget {
|
||||
|
||||
override init(frame: NSRect) {
|
||||
super.init(frame: CGRect(x: 0, y: 0, width: widgetSize.width, height: widgetSize.height))
|
||||
self.name = "BatteryPercentage"
|
||||
self.drawPercentage()
|
||||
self.update()
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ class BatteryTimeWidget: BatteryWidget {
|
||||
|
||||
override init(frame: NSRect) {
|
||||
super.init(frame: CGRect(x: 0, y: 0, width: widgetSize.width, height: widgetSize.height))
|
||||
self.name = "BatteryTime"
|
||||
self.drawTime()
|
||||
self.changeWidth(width: self.timeWidth)
|
||||
self.update()
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import Cocoa
|
||||
|
||||
class BarChart: NSView, Widget {
|
||||
public var name: String = ""
|
||||
public var name: String = "BarChart"
|
||||
public var menus: [NSMenuItem] = []
|
||||
|
||||
private var size: CGFloat = widgetSize.width + 10
|
||||
@@ -136,7 +136,9 @@ class BarChart: NSView, Widget {
|
||||
|
||||
if self.frame.size.width != width {
|
||||
self.setFrameSize(NSSize(width: width, height: self.frame.size.height))
|
||||
menuBar!.refresh()
|
||||
if menuBar != nil {
|
||||
menuBar!.refresh()
|
||||
}
|
||||
}
|
||||
|
||||
self.display()
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import Cocoa
|
||||
|
||||
class Chart: NSView, Widget {
|
||||
public var name: String = ""
|
||||
public var name: String = "LineChart"
|
||||
public var menus: [NSMenuItem] = []
|
||||
|
||||
internal let defaults = UserDefaults.standard
|
||||
|
||||
@@ -19,6 +19,7 @@ class ChartWithValue: Chart {
|
||||
override init(frame: NSRect) {
|
||||
super.init(frame: CGRect(x: 0, y: 0, width: widgetSize.width + 7, height: widgetSize.height))
|
||||
self.wantsLayer = true
|
||||
self.name = "LineChartWithValue"
|
||||
}
|
||||
|
||||
required init?(coder decoder: NSCoder) {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import Cocoa
|
||||
|
||||
class Mini: NSView, Widget {
|
||||
public var name: String = ""
|
||||
public var name: String = "Mini"
|
||||
public var menus: [NSMenuItem] = []
|
||||
|
||||
private var value: Double = 0
|
||||
@@ -26,7 +26,6 @@ class Mini: NSView, Widget {
|
||||
|
||||
override init(frame: NSRect) {
|
||||
super.init(frame: CGRect(x: 0, y: 0, width: self.size, height: widgetSize.height))
|
||||
|
||||
self.wantsLayer = true
|
||||
|
||||
let xOffset: CGFloat = 1.0
|
||||
|
||||
@@ -11,7 +11,7 @@ import Cocoa
|
||||
class NetworkArrowsView: NSView, Widget {
|
||||
public var menus: [NSMenuItem] = []
|
||||
public var size: CGFloat = 8
|
||||
public var name: String = ""
|
||||
public var name: String = "NetworkArrows"
|
||||
|
||||
private var download: Int64 = 0
|
||||
private var upload: Int64 = 0
|
||||
|
||||
@@ -11,7 +11,7 @@ import Cocoa
|
||||
class NetworkArrowsTextView: NSView, Widget {
|
||||
public var menus: [NSMenuItem] = []
|
||||
public var size: CGFloat = widgetSize.width + 24
|
||||
public var name: String = ""
|
||||
public var name: String = "NetworkArrowsText"
|
||||
|
||||
private var download: Int64 = 0
|
||||
private var upload: Int64 = 0
|
||||
@@ -97,7 +97,7 @@ class NetworkArrowsTextView: NSView, Widget {
|
||||
}
|
||||
|
||||
func valueView() {
|
||||
downloadValue = NSTextField(frame: NSMakeRect(widgetSize.margin, widgetSize.margin, self.frame.size.width - widgetSize.margin, 9))
|
||||
downloadValue = NSTextField(frame: NSMakeRect(widgetSize.margin, widgetSize.margin + 1, self.frame.size.width - widgetSize.margin, 9))
|
||||
downloadValue.isEditable = false
|
||||
downloadValue.isSelectable = false
|
||||
downloadValue.isBezeled = false
|
||||
|
||||
@@ -10,7 +10,7 @@ import Cocoa
|
||||
|
||||
class NetworkDotsView: NSView, Widget {
|
||||
public var size: CGFloat = 12
|
||||
public var name: String = ""
|
||||
public var name: String = "NetworkDots"
|
||||
public var menus: [NSMenuItem] = []
|
||||
|
||||
private var download: Int64 = 0
|
||||
|
||||
@@ -11,7 +11,7 @@ import Cocoa
|
||||
class NetworkDotsTextView: NSView, Widget {
|
||||
public var menus: [NSMenuItem] = []
|
||||
public var size: CGFloat = widgetSize.width + 26
|
||||
public var name: String = ""
|
||||
public var name: String = "NetworkDotsText"
|
||||
|
||||
private var download: Int64 = 0
|
||||
private var upload: Int64 = 0
|
||||
@@ -88,7 +88,7 @@ class NetworkDotsTextView: NSView, Widget {
|
||||
}
|
||||
|
||||
func valueView() {
|
||||
downloadValue = NSTextField(frame: NSMakeRect(widgetSize.margin, widgetSize.margin, self.frame.size.width - widgetSize.margin, 9))
|
||||
downloadValue = NSTextField(frame: NSMakeRect(widgetSize.margin, widgetSize.margin + 1, self.frame.size.width - widgetSize.margin, 9))
|
||||
downloadValue.isEditable = false
|
||||
downloadValue.isSelectable = false
|
||||
downloadValue.isBezeled = false
|
||||
|
||||
@@ -11,7 +11,7 @@ import Cocoa
|
||||
class NetworkTextView: NSView, Widget {
|
||||
public var menus: [NSMenuItem] = []
|
||||
public var size: CGFloat = widgetSize.width + 20
|
||||
public var name: String = ""
|
||||
public var name: String = "NetworkText"
|
||||
|
||||
private var downloadValue: NSTextField = NSTextField()
|
||||
private var uploadValue: NSTextField = NSTextField()
|
||||
@@ -51,7 +51,7 @@ class NetworkTextView: NSView, Widget {
|
||||
}
|
||||
|
||||
func valueView() {
|
||||
downloadValue = NSTextField(frame: NSMakeRect(widgetSize.margin, widgetSize.margin, self.frame.size.width - widgetSize.margin, 9))
|
||||
downloadValue = NSTextField(frame: NSMakeRect(widgetSize.margin, widgetSize.margin + 1, self.frame.size.width - widgetSize.margin, 9))
|
||||
downloadValue.isEditable = false
|
||||
downloadValue.isSelectable = false
|
||||
downloadValue.isBezeled = false
|
||||
|
||||
116
Stats/Widgets/Temperature/TemperatureWidget.swift
Normal file
116
Stats/Widgets/Temperature/TemperatureWidget.swift
Normal file
@@ -0,0 +1,116 @@
|
||||
//
|
||||
// DoubleRow.swift
|
||||
// Stats
|
||||
//
|
||||
// Created by Serhiy Mytrovtsiy on 03/04/2020.
|
||||
// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class TemperatureWidget: NSView, Widget {
|
||||
public var name: String = "Temperature"
|
||||
public var menus: [NSMenuItem] = []
|
||||
|
||||
private var value: [Double] = []
|
||||
private var size: CGFloat = 24
|
||||
private var topValueView: NSTextField = NSTextField()
|
||||
private var bottomValueView: NSTextField = NSTextField()
|
||||
private let defaults = UserDefaults.standard
|
||||
|
||||
private var color: Bool = true
|
||||
|
||||
override var intrinsicContentSize: CGSize {
|
||||
return CGSize(width: self.frame.size.width, height: self.frame.size.height)
|
||||
}
|
||||
|
||||
required init?(coder decoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override init(frame: NSRect) {
|
||||
super.init(frame: CGRect(x: 0, y: 0, width: self.size, height: widgetSize.height))
|
||||
self.wantsLayer = true
|
||||
|
||||
let xOffset: CGFloat = 1.0
|
||||
|
||||
let topValueView = NSTextField(frame: NSMakeRect(xOffset, 11, self.frame.size.width, 10))
|
||||
topValueView.isEditable = false
|
||||
topValueView.isSelectable = false
|
||||
topValueView.isBezeled = false
|
||||
topValueView.wantsLayer = true
|
||||
topValueView.textColor = .labelColor
|
||||
topValueView.backgroundColor = .controlColor
|
||||
topValueView.canDrawSubviewsIntoLayer = true
|
||||
topValueView.alignment = .natural
|
||||
topValueView.font = NSFont.systemFont(ofSize: 9, weight: .light)
|
||||
topValueView.stringValue = ""
|
||||
topValueView.addSubview(NSView())
|
||||
|
||||
let bottomValueView = NSTextField(frame: NSMakeRect(xOffset, 2, self.frame.size.width, 10))
|
||||
bottomValueView.isEditable = false
|
||||
bottomValueView.isSelectable = false
|
||||
bottomValueView.isBezeled = false
|
||||
bottomValueView.wantsLayer = true
|
||||
bottomValueView.textColor = .labelColor
|
||||
bottomValueView.backgroundColor = .controlColor
|
||||
bottomValueView.canDrawSubviewsIntoLayer = true
|
||||
bottomValueView.alignment = .natural
|
||||
bottomValueView.font = NSFont.systemFont(ofSize: 9, weight: .light)
|
||||
bottomValueView.stringValue = ""
|
||||
bottomValueView.addSubview(NSView())
|
||||
|
||||
self.topValueView = topValueView
|
||||
self.bottomValueView = bottomValueView
|
||||
|
||||
self.addSubview(self.topValueView)
|
||||
self.addSubview(self.bottomValueView)
|
||||
}
|
||||
|
||||
func start() {
|
||||
self.color = defaults.object(forKey: "\(name)_color") != nil ? defaults.bool(forKey: "\(name)_color") : true
|
||||
self.initMenu()
|
||||
self.redraw()
|
||||
}
|
||||
|
||||
func redraw() {
|
||||
if self.value.count == 2 {
|
||||
self.topValueView.textColor = self.value[0].temperatureColor(color: self.color)
|
||||
self.bottomValueView.textColor = self.value[1].temperatureColor(color: self.color)
|
||||
}
|
||||
self.display()
|
||||
}
|
||||
|
||||
func setValue(data: [Double]) {
|
||||
if self.value != data && data.count == 2 {
|
||||
self.value = data
|
||||
|
||||
self.topValueView.stringValue = "\(Int(self.value[0]))°"
|
||||
self.bottomValueView.stringValue = "\(Int(self.value[1]))°"
|
||||
|
||||
self.topValueView.textColor = self.value[0].temperatureColor(color: self.color)
|
||||
self.bottomValueView.textColor = self.value[1].temperatureColor(color: self.color)
|
||||
}
|
||||
}
|
||||
|
||||
func initMenu() {
|
||||
let color = NSMenuItem(title: "Color", action: #selector(toggleColor), keyEquivalent: "")
|
||||
color.state = self.color ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
color.target = self
|
||||
|
||||
self.menus.append(color)
|
||||
}
|
||||
|
||||
@objc func toggleColor(_ sender: NSMenuItem) {
|
||||
sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on
|
||||
self.defaults.set(sender.state == NSControl.StateValue.on, forKey: "\(name)_color")
|
||||
self.color = sender.state == NSControl.StateValue.on
|
||||
|
||||
if self.value.count == 2 {
|
||||
self.topValueView.textColor = self.value[0].temperatureColor(color: self.color)
|
||||
self.bottomValueView.textColor = self.value[1].temperatureColor(color: self.color)
|
||||
}
|
||||
|
||||
self.redraw()
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,8 @@ protocol Widget {
|
||||
typealias WidgetType = Float
|
||||
struct Widgets {
|
||||
static let Mini: WidgetType = 0.0
|
||||
static let Temperature: WidgetType = 0.1
|
||||
|
||||
static let Chart: WidgetType = 1.0
|
||||
static let ChartWithValue: WidgetType = 1.1
|
||||
|
||||
|
||||
@@ -65,6 +65,23 @@ extension Double {
|
||||
}
|
||||
}
|
||||
|
||||
func temperatureColor(color: Bool = false) -> NSColor {
|
||||
switch self {
|
||||
case 0...70:
|
||||
return NSColor.controlTextColor
|
||||
case 70...90:
|
||||
if !color {
|
||||
return NSColor.controlTextColor
|
||||
}
|
||||
return NSColor.systemOrange
|
||||
default:
|
||||
if !color {
|
||||
return NSColor.controlTextColor
|
||||
}
|
||||
return NSColor.systemRed
|
||||
}
|
||||
}
|
||||
|
||||
func splitAtDecimal() -> [Int64] {
|
||||
return "\(self)".split(separator: ".").map{Int64($0)!}
|
||||
}
|
||||
@@ -180,6 +197,10 @@ extension String {
|
||||
let components = self.components(separatedBy: .whitespacesAndNewlines)
|
||||
return components.filter { !$0.isEmpty }.joined(separator: " ")
|
||||
}
|
||||
|
||||
var UTF8CString: UnsafeMutablePointer<Int8> {
|
||||
return UnsafeMutablePointer(mutating: (self as NSString).utf8String!)
|
||||
}
|
||||
}
|
||||
|
||||
extension NSBezierPath {
|
||||
|
||||
17
Stats/libs/Stats.h
Normal file
17
Stats/libs/Stats.h
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// Stats.h
|
||||
// Stats
|
||||
//
|
||||
// Created by Serhiy Mytrovtsiy on 03/04/2020.
|
||||
// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import "SystemKit.h"
|
||||
|
||||
//! Project version number for SMCKit.
|
||||
FOUNDATION_EXPORT double SMCKitVersionNumber;
|
||||
|
||||
//! Project version string for SMCKit.
|
||||
FOUNDATION_EXPORT const unsigned char SMCKitVersionString[];
|
||||
277
Stats/libs/SystemKit.c
Normal file
277
Stats/libs/SystemKit.c
Normal file
@@ -0,0 +1,277 @@
|
||||
//
|
||||
// SystemKit.c
|
||||
// Stats
|
||||
//
|
||||
// SMC code borrowed from https://github.com/lavoiesl/osx-cpu-temp.
|
||||
//
|
||||
// Created by Serhiy Mytrovtsiy on 03/04/2020.
|
||||
// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
|
||||
//
|
||||
|
||||
#include <IOKit/ps/IOPowerSources.h>
|
||||
#include <IOKit/ps/IOPSKeys.h>
|
||||
#include <IOKit/IOKitLib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "SystemKit.h"
|
||||
|
||||
static io_connect_t conn;
|
||||
|
||||
UInt32 _strtoul(char* str, int size, int base) {
|
||||
UInt32 total = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < size; i++) {
|
||||
if (base == 16)
|
||||
total += str[i] << (size - 1 - i) * 8;
|
||||
else
|
||||
total += (unsigned char)(str[i] << (size - 1 - i) * 8);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
void _ultostr(char* str, UInt32 val) {
|
||||
str[0] = '\0';
|
||||
sprintf(str, "%c%c%c%c",
|
||||
(unsigned int)val >> 24,
|
||||
(unsigned int)val >> 16,
|
||||
(unsigned int)val >> 8,
|
||||
(unsigned int)val);
|
||||
}
|
||||
|
||||
void t(void * refcon,
|
||||
io_service_t service,
|
||||
uint32_t messageType,
|
||||
void * messageArgument) {
|
||||
printf("Hmmmm");
|
||||
}
|
||||
|
||||
kern_return_t SMCOpen(void) {
|
||||
kern_return_t result;
|
||||
io_iterator_t iterator;
|
||||
io_object_t device;
|
||||
|
||||
CFMutableDictionaryRef matchingDictionary = IOServiceMatching("AppleSMC");
|
||||
result = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDictionary, &iterator);
|
||||
if (result != kIOReturnSuccess) {
|
||||
printf("Error: IOServiceGetMatchingServices() = %08x\n", result);
|
||||
return 1;
|
||||
}
|
||||
|
||||
device = IOIteratorNext(iterator);
|
||||
IOObjectRelease(iterator);
|
||||
if (device == 0) {
|
||||
printf("Error: no SMC found\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
result = IOServiceOpen(device, mach_task_self(), 0, &conn);
|
||||
IOObjectRelease(device);
|
||||
if (result != kIOReturnSuccess) {
|
||||
printf("Error: IOServiceOpen() = %08x\n", result);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
kern_return_t SMCClose(void) {
|
||||
return IOServiceClose(conn);
|
||||
}
|
||||
|
||||
kern_return_t SMCCall(int index, SMCKeyData_t* inputStructure, SMCKeyData_t* outputStructure) {
|
||||
size_t structureInputSize;
|
||||
size_t structureOutputSize;
|
||||
|
||||
structureInputSize = sizeof(SMCKeyData_t);
|
||||
structureOutputSize = sizeof(SMCKeyData_t);
|
||||
|
||||
#if MAC_OS_X_VERSION_10_5
|
||||
return IOConnectCallStructMethod(conn, index,
|
||||
// inputStructure
|
||||
inputStructure, structureInputSize,
|
||||
// ouputStructure
|
||||
outputStructure, &structureOutputSize);
|
||||
#else
|
||||
return IOConnectMethodStructureIStructureO(conn, index,
|
||||
structureInputSize, /* structureInputSize */
|
||||
&structureOutputSize, /* structureOutputSize */
|
||||
inputStructure, /* inputStructure */
|
||||
outputStructure); /* ouputStructure */
|
||||
#endif
|
||||
}
|
||||
|
||||
kern_return_t SMCReadKey(UInt32Char_t key, SMCVal_t* val) {
|
||||
kern_return_t result;
|
||||
SMCKeyData_t inputStructure;
|
||||
SMCKeyData_t outputStructure;
|
||||
|
||||
memset(&inputStructure, 0, sizeof(SMCKeyData_t));
|
||||
memset(&outputStructure, 0, sizeof(SMCKeyData_t));
|
||||
memset(val, 0, sizeof(SMCVal_t));
|
||||
|
||||
inputStructure.key = _strtoul(key, 4, 16);
|
||||
inputStructure.data8 = SMC_CMD_READ_KEYINFO;
|
||||
|
||||
result = SMCCall(KERNEL_INDEX_SMC, &inputStructure, &outputStructure);
|
||||
if (result != kIOReturnSuccess)
|
||||
return result;
|
||||
|
||||
val->dataSize = outputStructure.keyInfo.dataSize;
|
||||
_ultostr(val->dataType, outputStructure.keyInfo.dataType);
|
||||
inputStructure.keyInfo.dataSize = val->dataSize;
|
||||
inputStructure.data8 = SMC_CMD_READ_BYTES;
|
||||
|
||||
result = SMCCall(KERNEL_INDEX_SMC, &inputStructure, &outputStructure);
|
||||
if (result != kIOReturnSuccess)
|
||||
return result;
|
||||
|
||||
memcpy(val->bytes, outputStructure.bytes, sizeof(outputStructure.bytes));
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
double GetTemperature(char* key) {
|
||||
SMCVal_t val;
|
||||
kern_return_t result;
|
||||
|
||||
result = SMCReadKey(key, &val);
|
||||
if (result == kIOReturnSuccess) {
|
||||
if (val.dataSize > 0) {
|
||||
if (strcmp(val.dataType, DATATYPE_SP78) == 0) {
|
||||
int intValue = val.bytes[0] * 256 + (unsigned char)val.bytes[1];
|
||||
return intValue / 256.0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
printf("Error: SMCReadKey() = %08x\n", result);
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
float GetFanRPM(char* key) {
|
||||
SMCVal_t val;
|
||||
kern_return_t result;
|
||||
|
||||
result = SMCReadKey(key, &val);
|
||||
if (result == kIOReturnSuccess) {
|
||||
if (val.dataSize > 0) {
|
||||
if (strcmp(val.dataType, DATATYPE_FPE2) == 0) {
|
||||
return ntohs(*(UInt16*)val.bytes) / 4.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1.f;
|
||||
}
|
||||
|
||||
PowerManagmentInformation *GetPowerInfo() {
|
||||
PowerManagmentInformation *info = malloc(sizeof(PowerManagmentInformation));
|
||||
|
||||
CFTypeRef power_sources = IOPSCopyPowerSourcesInfo();
|
||||
CFTypeRef external_adapter = IOPSCopyExternalPowerAdapterDetails();
|
||||
|
||||
/* Get information aboud external adapter */
|
||||
if (external_adapter != NULL) {
|
||||
CFNumberRef watts = CFDictionaryGetValue(external_adapter, CFSTR(kIOPSPowerAdapterWattsKey));
|
||||
if (watts) {
|
||||
CFNumberGetValue(watts, kCFNumberDoubleType, &info->ACWatts);
|
||||
CFRelease(watts);
|
||||
}
|
||||
|
||||
CFRelease(external_adapter);
|
||||
}
|
||||
|
||||
if(power_sources == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
CFArrayRef list = IOPSCopyPowerSourcesList(power_sources);
|
||||
CFDictionaryRef battery = NULL;
|
||||
|
||||
if(list == NULL) {
|
||||
CFRelease(power_sources);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Get the battery */
|
||||
for(int i = 0; i < CFArrayGetCount(list) && battery == NULL; ++i) {
|
||||
CFDictionaryRef candidate = IOPSGetPowerSourceDescription(power_sources, CFArrayGetValueAtIndex(list, i));
|
||||
CFStringRef type;
|
||||
|
||||
if(candidate != NULL) {
|
||||
type = (CFStringRef) CFDictionaryGetValue(candidate, CFSTR(kIOPSTransportTypeKey));
|
||||
|
||||
if(kCFCompareEqualTo == CFStringCompare(type, CFSTR(kIOPSInternalType), 0)) {
|
||||
CFRetain(candidate);
|
||||
battery = candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(battery != NULL) {
|
||||
CFStringRef power_state = CFDictionaryGetValue(battery, CFSTR(kIOPSPowerSourceStateKey));
|
||||
|
||||
CFNumberRef max_capacity = CFDictionaryGetValue(battery, CFSTR(kIOPSMaxCapacityKey));
|
||||
CFNumberRef design_capacity = CFDictionaryGetValue(battery, CFSTR(kIOPSDesignCapacityKey));
|
||||
CFNumberRef current_capacity = CFDictionaryGetValue(battery, CFSTR(kIOPSCurrentCapacityKey));
|
||||
|
||||
CFNumberRef amperage = CFDictionaryGetValue(battery, CFSTR(kIOPSCurrentKey));
|
||||
CFNumberRef voltage = CFDictionaryGetValue(battery, CFSTR(kIOPSVoltageKey));
|
||||
CFNumberRef temperature = CFDictionaryGetValue(battery, CFSTR(kIOPSTemperatureKey));
|
||||
|
||||
/* Determine the AC state */
|
||||
info->isCharging = kCFCompareEqualTo == CFStringCompare(power_state, CFSTR(kIOPSACPowerValue), 0);
|
||||
|
||||
/* Get capacity */
|
||||
if (max_capacity) {
|
||||
CFNumberGetValue(max_capacity, kCFNumberDoubleType, &info->maxCapacity);
|
||||
CFRelease(max_capacity);
|
||||
}
|
||||
if (design_capacity) {
|
||||
CFNumberGetValue(design_capacity, kCFNumberDoubleType, &info->designCapacity);
|
||||
CFRelease(design_capacity);
|
||||
}
|
||||
if (current_capacity) {
|
||||
CFNumberGetValue(current_capacity, kCFNumberDoubleType, &info->currentCapacity);
|
||||
CFRelease(current_capacity);
|
||||
}
|
||||
|
||||
/* Determine the level */
|
||||
if (info->currentCapacity && info->maxCapacity) {
|
||||
info->level = (info->currentCapacity * 100.0) / info->maxCapacity;
|
||||
}
|
||||
|
||||
/* Get the parameters */
|
||||
if (amperage) {
|
||||
CFNumberGetValue(amperage, kCFNumberDoubleType, &info->amperage);
|
||||
CFRelease(amperage);
|
||||
}
|
||||
if (voltage) {
|
||||
printf("2\n");
|
||||
CFNumberGetValue(voltage, kCFNumberDoubleType, &info->voltage);
|
||||
CFRelease(voltage);
|
||||
}
|
||||
if (temperature) {
|
||||
printf("3\n");
|
||||
CFNumberGetValue(temperature, kCFNumberDoubleType, &info->voltage);
|
||||
CFRelease(temperature);
|
||||
}
|
||||
|
||||
// printf("%f\n", info->amperage);
|
||||
// printf("%f\n", test);
|
||||
// printf("%f\n", info->temperature);
|
||||
// printf("\n%hhu\n", info->isCharging);
|
||||
|
||||
// printf("%u", info->level);
|
||||
|
||||
CFRelease(battery);
|
||||
}
|
||||
|
||||
CFRelease(list);
|
||||
CFRelease(power_sources);
|
||||
|
||||
free(info);
|
||||
return info;
|
||||
}
|
||||
142
Stats/libs/SystemKit.h
Normal file
142
Stats/libs/SystemKit.h
Normal file
@@ -0,0 +1,142 @@
|
||||
//
|
||||
// SystemKit.h
|
||||
// Stats
|
||||
//
|
||||
// Created by Serhiy Mytrovtsiy on 03/04/2020.
|
||||
// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
|
||||
//
|
||||
|
||||
#define SMC_TEMP_AMBIENT_AIR_0 "TA0P"
|
||||
#define SMC_TEMP_AMBIENT_AIR_1 "TA1P"
|
||||
|
||||
#define SMC_TEMP_CPU_0_DIE "TC0F"
|
||||
#define SMC_TEMP_CPU_0_DIODE "TC0D"
|
||||
#define SMC_TEMP_CPU_0_HEATSINK "TC0H"
|
||||
#define SMC_TEMP_CPU_0_PROXIMITY "TC0P"
|
||||
|
||||
#define SMC_TEMP_ENCLOSURE_BASE_0 "TB0T"
|
||||
#define SMC_TEMP_ENCLOSURE_BASE_1 "TB1T"
|
||||
#define SMC_TEMP_ENCLOSURE_BASE_2 "TB2T"
|
||||
#define SMC_TEMP_ENCLOSURE_BASE_3 "TB3T"
|
||||
|
||||
#define SMC_TEMP_GPU_0_DIODE "TG0D"
|
||||
#define SMC_TEMP_GPU_0_HEATSINK "TG0H"
|
||||
#define SMC_TEMP_GPU_0_PROXIMITY "TG0P"
|
||||
|
||||
#define SMC_TEMP_HDD_PROXIMITY "TH0P"
|
||||
#define SMC_TEMP_LCD_PROXIMITY "TL0P"
|
||||
#define SMC_TEMP_MISC_PROXIMITY "Tm0P"
|
||||
#define SMC_TEMP_ODD_PROXIMITY "TO0P"
|
||||
|
||||
#define SMC_TEMP_HEATSINK_0 "Th0H"
|
||||
#define SMC_TEMP_HEATSINK_1 "Th1H"
|
||||
#define SMC_TEMP_HEATSINK_2 "Th2H"
|
||||
|
||||
#define SMC_TEMP_MEM_SLOT_0 "TM0S"
|
||||
#define SMC_TEMP_MEM_SLOTS_PROXIMITY "TM0P"
|
||||
|
||||
#define SMC_TEMP_NORTHBRIDGE "TN0H"
|
||||
#define SMC_TEMP_NORTHBRIDGE_DIODE "TN0D"
|
||||
#define SMC_TEMP_NORTHBRIDGE_PROXIMITY "TN0P"
|
||||
|
||||
#define SMC_TEMP_PALM_REST "Ts0P"
|
||||
#define PWR_TEMP_SUPPLY_PROXIMITY "Tp0P"
|
||||
|
||||
#define SMC_TEMP_THUNDERBOLT_0 "TI0P"
|
||||
#define SMC_TEMP_THUNDERBOLT_1 "TI1P"
|
||||
|
||||
#define SMC_FAN0_RPM "F0Ac"
|
||||
|
||||
typedef enum {
|
||||
Off_line,
|
||||
AC_Power,
|
||||
Battery_Power
|
||||
} PowerSource;
|
||||
|
||||
typedef struct {
|
||||
unsigned int level;
|
||||
Boolean isCharging;
|
||||
|
||||
double amperage;
|
||||
double voltage;
|
||||
double temperature;
|
||||
|
||||
double cycles;
|
||||
|
||||
double currentCapacity;
|
||||
double maxCapacity;
|
||||
double designCapacity;
|
||||
|
||||
double timeToEmpty;
|
||||
double timeToFull;
|
||||
|
||||
PowerSource powerSource;
|
||||
double ACWatts;
|
||||
} PowerManagmentInformation;
|
||||
|
||||
kern_return_t SMCOpen(void);
|
||||
kern_return_t SMCClose(void);
|
||||
|
||||
double GetTemperature(char* key);
|
||||
float GetFanRPM(char* key);
|
||||
//PowerManagmentInformation *GetPowerInfo(void);
|
||||
|
||||
// INTERNAL
|
||||
#define KERNEL_INDEX_SMC 2
|
||||
|
||||
#define SMC_CMD_READ_BYTES 5
|
||||
#define SMC_CMD_WRITE_BYTES 6
|
||||
#define SMC_CMD_READ_INDEX 8
|
||||
#define SMC_CMD_READ_KEYINFO 9
|
||||
#define SMC_CMD_READ_PLIMIT 11
|
||||
#define SMC_CMD_READ_VERS 12
|
||||
|
||||
#define DATATYPE_FPE2 "fpe2"
|
||||
#define DATATYPE_UINT8 "ui8 "
|
||||
#define DATATYPE_UINT16 "ui16"
|
||||
#define DATATYPE_UINT32 "ui32"
|
||||
#define DATATYPE_SP78 "sp78"
|
||||
|
||||
typedef char UInt32Char_t[5];
|
||||
typedef char SMCBytes_t[32];
|
||||
|
||||
typedef struct {
|
||||
UInt32Char_t key;
|
||||
UInt32 dataSize;
|
||||
UInt32Char_t dataType;
|
||||
SMCBytes_t bytes;
|
||||
} SMCVal_t;
|
||||
|
||||
typedef struct {
|
||||
char major;
|
||||
char minor;
|
||||
char build;
|
||||
char reserved[1];
|
||||
UInt16 release;
|
||||
} SMCKeyData_vers_t;
|
||||
|
||||
typedef struct {
|
||||
UInt16 version;
|
||||
UInt16 length;
|
||||
UInt32 cpuPLimit;
|
||||
UInt32 gpuPLimit;
|
||||
UInt32 memPLimit;
|
||||
} SMCKeyData_pLimitData_t;
|
||||
|
||||
typedef struct {
|
||||
UInt32 dataSize;
|
||||
UInt32 dataType;
|
||||
char dataAttributes;
|
||||
} SMCKeyData_keyInfo_t;
|
||||
|
||||
typedef struct {
|
||||
UInt32 key;
|
||||
SMCKeyData_vers_t vers;
|
||||
SMCKeyData_pLimitData_t pLimitData;
|
||||
SMCKeyData_keyInfo_t keyInfo;
|
||||
char result;
|
||||
char status;
|
||||
char data8;
|
||||
UInt32 data32;
|
||||
SMCBytes_t bytes;
|
||||
} SMCKeyData_t;
|
||||
Reference in New Issue
Block a user