diff --git a/Stats.xcodeproj/project.pbxproj b/Stats.xcodeproj/project.pbxproj index 18bc7aaf..15991054 100644 --- a/Stats.xcodeproj/project.pbxproj +++ b/Stats.xcodeproj/project.pbxproj @@ -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 = ""; }; 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 = ""; }; + 9AA28DC22437752D00D2B196 /* TemperatureMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemperatureMenu.swift; sourceTree = ""; }; + 9AA28DC42437762C00D2B196 /* TemperatureReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemperatureReader.swift; sourceTree = ""; }; + 9AA28DC9243780C500D2B196 /* Stats.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Stats.h; sourceTree = ""; }; + 9AA28DCD2437884200D2B196 /* SystemKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SystemKit.h; sourceTree = ""; }; + 9AA28DCE2437884200D2B196 /* SystemKit.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SystemKit.c; sourceTree = ""; }; + 9AA28DD0243799E500D2B196 /* TemperatureWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemperatureWidget.swift; sourceTree = ""; }; 9AF0F31A22DA924000026AE6 /* LineChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineChart.swift; sourceTree = ""; }; 9AF0F31C22DA925000026AE6 /* LineChartWithValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineChartWithValue.swift; sourceTree = ""; }; 9AF0F31E22DA925700026AE6 /* BarChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarChart.swift; sourceTree = ""; }; @@ -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 = ""; @@ -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 = ""; }; + 9AA28DBF243774DD00D2B196 /* Temperature */ = { + isa = PBXGroup; + children = ( + 9AA28DC0243774ED00D2B196 /* Temperature.swift */, + 9AA28DC22437752D00D2B196 /* TemperatureMenu.swift */, + 9AA28DC42437762C00D2B196 /* TemperatureReader.swift */, + ); + path = Temperature; + sourceTree = ""; + }; + 9AA28DD224379F8700D2B196 /* Temperature */ = { + isa = PBXGroup; + children = ( + 9AA28DD0243799E500D2B196 /* TemperatureWidget.swift */, + ); + path = Temperature; + sourceTree = ""; + }; 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; diff --git a/Stats/AppDelegate.swift b/Stats/AppDelegate.swift index 27141c51..ccb5d55c 100755 --- a/Stats/AppDelegate.swift +++ b/Stats/AppDelegate.swift @@ -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() } diff --git a/Stats/MenuBar.swift b/Stats/MenuBar.swift index 1016edba..9b5af8a0 100644 --- a/Stats/MenuBar.swift +++ b/Stats/MenuBar.swift @@ -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() diff --git a/Stats/Modules/Battery/Battery.swift b/Stats/Modules/Battery/Battery.swift index 184a0b27..430eebaa 100644 --- a/Stats/Modules/Battery/Battery.swift +++ b/Stats/Modules/Battery/Battery.swift @@ -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) } } diff --git a/Stats/Modules/Battery/BatteryMenu.swift b/Stats/Modules/Battery/BatteryMenu.swift index b24356fc..ff1957e0 100644 --- a/Stats/Modules/Battery/BatteryMenu.swift +++ b/Stats/Modules/Battery/BatteryMenu.swift @@ -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) - } } diff --git a/Stats/Modules/Battery/BatteryPopup.swift b/Stats/Modules/Battery/BatteryPopup.swift index 7caca1a4..3e8bbf08 100644 --- a/Stats/Modules/Battery/BatteryPopup.swift +++ b/Stats/Modules/Battery/BatteryPopup.swift @@ -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" } } diff --git a/Stats/Modules/Battery/BatteryReader.swift b/Stats/Modules/Battery/BatteryReader.swift index ddaeca79..1760410c 100644 --- a/Stats/Modules/Battery/BatteryReader.swift +++ b/Stats/Modules/Battery/BatteryReader.swift @@ -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.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 { 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 { - 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 diff --git a/Stats/Modules/Module.swift b/Stats/Modules/Module.swift index 7f43649d..79cd1e74 100644 --- a/Stats/Modules/Module.swift +++ b/Stats/Modules/Module.swift @@ -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: diff --git a/Stats/Modules/Temperature/Temperature.swift b/Stats/Modules/Temperature/Temperature.swift new file mode 100644 index 00000000..d2304f7b --- /dev/null +++ b/Stats/Modules/Temperature/Temperature.swift @@ -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]) + }) + } + } +} diff --git a/Stats/Modules/Temperature/TemperatureMenu.swift b/Stats/Modules/Temperature/TemperatureMenu.swift new file mode 100644 index 00000000..39d491a2 --- /dev/null +++ b/Stats/Modules/Temperature/TemperatureMenu.swift @@ -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) + } +} diff --git a/Stats/Modules/Temperature/TemperatureReader.swift b/Stats/Modules/Temperature/TemperatureReader.swift new file mode 100644 index 00000000..39a8c928 --- /dev/null +++ b/Stats/Modules/Temperature/TemperatureReader.swift @@ -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 + } +} diff --git a/Stats/Supporting Files/Stats.entitlements b/Stats/Supporting Files/Stats.entitlements index 2eb7e333..0c67376e 100755 --- a/Stats/Supporting Files/Stats.entitlements +++ b/Stats/Supporting Files/Stats.entitlements @@ -1,8 +1,5 @@ - - com.apple.security.application-groups - - + diff --git a/Stats/Widgets/Battery/BatteryPercentageWidget.swift b/Stats/Widgets/Battery/BatteryPercentageWidget.swift index 05cafab7..dce1f30e 100644 --- a/Stats/Widgets/Battery/BatteryPercentageWidget.swift +++ b/Stats/Widgets/Battery/BatteryPercentageWidget.swift @@ -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() } diff --git a/Stats/Widgets/Battery/BatteryTimeWidget.swift b/Stats/Widgets/Battery/BatteryTimeWidget.swift index 6ea4fb6a..a5247a0f 100644 --- a/Stats/Widgets/Battery/BatteryTimeWidget.swift +++ b/Stats/Widgets/Battery/BatteryTimeWidget.swift @@ -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() diff --git a/Stats/Widgets/Charts/BarChart.swift b/Stats/Widgets/Charts/BarChart.swift index 55099f31..a3947d3f 100644 --- a/Stats/Widgets/Charts/BarChart.swift +++ b/Stats/Widgets/Charts/BarChart.swift @@ -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() diff --git a/Stats/Widgets/Charts/LineChart.swift b/Stats/Widgets/Charts/LineChart.swift index a02046c3..f579daf2 100644 --- a/Stats/Widgets/Charts/LineChart.swift +++ b/Stats/Widgets/Charts/LineChart.swift @@ -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 diff --git a/Stats/Widgets/Charts/LineChartWithValue.swift b/Stats/Widgets/Charts/LineChartWithValue.swift index 9cf341fe..3e485fcd 100644 --- a/Stats/Widgets/Charts/LineChartWithValue.swift +++ b/Stats/Widgets/Charts/LineChartWithValue.swift @@ -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) { diff --git a/Stats/Widgets/Mini.swift b/Stats/Widgets/Mini.swift index 0c07d11b..93d24c73 100644 --- a/Stats/Widgets/Mini.swift +++ b/Stats/Widgets/Mini.swift @@ -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 diff --git a/Stats/Widgets/Network/NetworkArrows.swift b/Stats/Widgets/Network/NetworkArrows.swift index fac4822f..e6da08d2 100644 --- a/Stats/Widgets/Network/NetworkArrows.swift +++ b/Stats/Widgets/Network/NetworkArrows.swift @@ -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 diff --git a/Stats/Widgets/Network/NetworkArrowsText.swift b/Stats/Widgets/Network/NetworkArrowsText.swift index 50e6a1f9..5f21406c 100644 --- a/Stats/Widgets/Network/NetworkArrowsText.swift +++ b/Stats/Widgets/Network/NetworkArrowsText.swift @@ -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 diff --git a/Stats/Widgets/Network/NetworkDots.swift b/Stats/Widgets/Network/NetworkDots.swift index ccd0474a..10d5c61b 100644 --- a/Stats/Widgets/Network/NetworkDots.swift +++ b/Stats/Widgets/Network/NetworkDots.swift @@ -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 diff --git a/Stats/Widgets/Network/NetworkDotsText.swift b/Stats/Widgets/Network/NetworkDotsText.swift index 535f5ee1..9982a0af 100644 --- a/Stats/Widgets/Network/NetworkDotsText.swift +++ b/Stats/Widgets/Network/NetworkDotsText.swift @@ -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 diff --git a/Stats/Widgets/Network/NetworkText.swift b/Stats/Widgets/Network/NetworkText.swift index 454ea456..56a4e0b4 100644 --- a/Stats/Widgets/Network/NetworkText.swift +++ b/Stats/Widgets/Network/NetworkText.swift @@ -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 diff --git a/Stats/Widgets/Temperature/TemperatureWidget.swift b/Stats/Widgets/Temperature/TemperatureWidget.swift new file mode 100644 index 00000000..570850a5 --- /dev/null +++ b/Stats/Widgets/Temperature/TemperatureWidget.swift @@ -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() + } +} diff --git a/Stats/Widgets/Widget.swift b/Stats/Widgets/Widget.swift index 27f1968f..5c51461f 100644 --- a/Stats/Widgets/Widget.swift +++ b/Stats/Widgets/Widget.swift @@ -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 diff --git a/Stats/libs/Extensions.swift b/Stats/libs/Extensions.swift index f6f23af5..fa5d75cc 100755 --- a/Stats/libs/Extensions.swift +++ b/Stats/libs/Extensions.swift @@ -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 { + return UnsafeMutablePointer(mutating: (self as NSString).utf8String!) + } } extension NSBezierPath { diff --git a/Stats/libs/Stats.h b/Stats/libs/Stats.h new file mode 100644 index 00000000..4f51e865 --- /dev/null +++ b/Stats/libs/Stats.h @@ -0,0 +1,17 @@ +// +// Stats.h +// Stats +// +// Created by Serhiy Mytrovtsiy on 03/04/2020. +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +#import + +#import "SystemKit.h" + +//! Project version number for SMCKit. +FOUNDATION_EXPORT double SMCKitVersionNumber; + +//! Project version string for SMCKit. +FOUNDATION_EXPORT const unsigned char SMCKitVersionString[]; diff --git a/Stats/libs/SystemKit.c b/Stats/libs/SystemKit.c new file mode 100644 index 00000000..461335b6 --- /dev/null +++ b/Stats/libs/SystemKit.c @@ -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 +#include +#include +#include +#include + +#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; +} diff --git a/Stats/libs/SystemKit.h b/Stats/libs/SystemKit.h new file mode 100644 index 00000000..dfbccb1c --- /dev/null +++ b/Stats/libs/SystemKit.h @@ -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;