From a4166729ec6e6691e21f850f262ef03c88ab9009 Mon Sep 17 00:00:00 2001 From: Serhiy Mytrovtsiy Date: Fri, 14 Jun 2019 15:06:45 +0200 Subject: [PATCH 1/3] initialized battery module --- Stats.xcodeproj/project.pbxproj | 16 +++ Stats/AppDelegate.swift | 2 +- Stats/Modules/Battery/Battery.swift | 145 +++++++++++++++++++ Stats/Modules/Battery/BatteryReader.swift | 164 ++++++++++++++++++++++ Stats/libs/Extensions.swift | 38 ++++- 5 files changed, 359 insertions(+), 6 deletions(-) create mode 100644 Stats/Modules/Battery/Battery.swift create mode 100644 Stats/Modules/Battery/BatteryReader.swift diff --git a/Stats.xcodeproj/project.pbxproj b/Stats.xcodeproj/project.pbxproj index f7169d29..77daa7aa 100755 --- a/Stats.xcodeproj/project.pbxproj +++ b/Stats.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 9A09C89E22B3A7C90018426F /* Battery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A09C89D22B3A7C90018426F /* Battery.swift */; }; + 9A09C8A022B3A7E20018426F /* BatteryReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A09C89F22B3A7E20018426F /* BatteryReader.swift */; }; 9A1410F9229E721100D29793 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1410F8229E721100D29793 /* AppDelegate.swift */; }; 9A141100229E721200D29793 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9A1410FE229E721200D29793 /* Main.storyboard */; }; 9A57A18522A1D26D0033E318 /* MenuBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A57A18422A1D26D0033E318 /* MenuBar.swift */; }; @@ -44,6 +46,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 9A09C89D22B3A7C90018426F /* Battery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Battery.swift; sourceTree = ""; }; + 9A09C89F22B3A7E20018426F /* BatteryReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryReader.swift; sourceTree = ""; }; 9A1410F5229E721100D29793 /* Stats.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Stats.app; sourceTree = BUILT_PRODUCTS_DIR; }; 9A1410F8229E721100D29793 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 9A1410FF229E721200D29793 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; @@ -91,6 +95,15 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 9A09C89C22B3A7BB0018426F /* Battery */ = { + isa = PBXGroup; + children = ( + 9A09C89D22B3A7C90018426F /* Battery.swift */, + 9A09C89F22B3A7E20018426F /* BatteryReader.swift */, + ); + path = Battery; + sourceTree = ""; + }; 9A1410EC229E721100D29793 = { isa = PBXGroup; children = ( @@ -136,6 +149,7 @@ 9A5B1CBA229E7892008B9D3C /* Modules */ = { isa = PBXGroup; children = ( + 9A09C89C22B3A7BB0018426F /* Battery */, 9A7B8F5C22A2926500DEB352 /* CPU */, 9A7B8F6222A2C17000DEB352 /* Memory */, 9A7B8F6322A2C17500DEB352 /* Disk */, @@ -311,9 +325,11 @@ 9A7B8F6F22A2C57000DEB352 /* DiskReader.swift in Sources */, 9A7B8F6922A2C3A100DEB352 /* Memory.swift in Sources */, 9A7B8F5E22A2A57600DEB352 /* CPUReader.swift in Sources */, + 9A09C89E22B3A7C90018426F /* Battery.swift in Sources */, 9A7B8F6D22A2C3D600DEB352 /* MemoryReader.swift in Sources */, 9A57A18522A1D26D0033E318 /* MenuBar.swift in Sources */, 9A57A19D22A1E3270033E318 /* CPU.swift in Sources */, + 9A09C8A022B3A7E20018426F /* BatteryReader.swift in Sources */, 9A57A19B22A1E1C50033E318 /* Module.swift in Sources */, 9A5B1CBF229E78F0008B9D3C /* Observable.swift in Sources */, 9A7B8F6B22A2C3A700DEB352 /* Disk.swift in Sources */, diff --git a/Stats/AppDelegate.swift b/Stats/AppDelegate.swift index ab732e83..e7bce5d7 100755 --- a/Stats/AppDelegate.swift +++ b/Stats/AppDelegate.swift @@ -13,7 +13,7 @@ extension Notification.Name { static let killLauncher = Notification.Name("killLauncher") } -let modules: Observable<[Module]> = Observable([CPU(), Memory(), Disk()]) +let modules: Observable<[Module]> = Observable([CPU(), Memory(), Disk(), Battery()]) let colors: Observable = Observable(true) @NSApplicationMain diff --git a/Stats/Modules/Battery/Battery.swift b/Stats/Modules/Battery/Battery.swift new file mode 100644 index 00000000..19923b10 --- /dev/null +++ b/Stats/Modules/Battery/Battery.swift @@ -0,0 +1,145 @@ +// +// Battery.swift +// Stats +// +// Created by Serhiy Mytrovtsiy on 14/06/2019. +// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa + +class BatteryView: NSView { + var value: Float { + didSet { + self.needsDisplay = true + setNeedsDisplay(self.frame) + } + } + var charging: Bool { + didSet { + self.needsDisplay = true + setNeedsDisplay(self.frame) + } + } + + override init(frame: NSRect) { + self.value = 1.0 + self.charging = false + super.init(frame: frame) + self.wantsLayer = true + self.addSubview(NSView()) + } + + required init?(coder decoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func draw(_ dirtyRect: NSRect) { + super.draw(dirtyRect) + + let x: CGFloat = 4.0 + let w: CGFloat = dirtyRect.size.width - (x * 2) + let h: CGFloat = 11.0 + let y: CGFloat = (dirtyRect.size.height - h) / 2 + let r: CGFloat = 1.0 + + let battery = NSBezierPath(roundedRect: NSRect(x: x-1, y: y, width: w-1, height: h), xRadius: r, yRadius: r) + + let bPX: CGFloat = x+w-2 + let bPY: CGFloat = (dirtyRect.size.height / 2) - 2 + let batteryPoint = NSBezierPath(roundedRect: NSRect(x: bPX, y: bPY, width: 2, height: 4), xRadius: r, yRadius: r) + if self.charging { + NSColor.systemGreen.set() + } else { + NSColor.labelColor.set() + } + batteryPoint.lineWidth = 1.1 + batteryPoint.stroke() + batteryPoint.fill() + + let maxWidth = w-4.25 + let inner = NSBezierPath(roundedRect: NSRect(x: x+0.75, y: y+1.5, width: maxWidth*CGFloat(self.value), height: h-3), xRadius: 0.5, yRadius: 0.5) + self.value.batteryColor().set() + inner.lineWidth = 0 + inner.stroke() + inner.close() + inner.fill() + + if self.charging { + NSColor.systemGreen.set() + } else { + NSColor.labelColor.set() + } + battery.lineWidth = 0.8 + battery.stroke() + } + + func changeValue(value: Float) { + if self.value != value { + self.value = value + } + } + + func setCharging(value: Bool) { + if self.charging != value { + self.charging = value + } + } +} + +class Battery: Module { + let name: String = "Battery" + var view: NSView = NSView() + let defaults = UserDefaults.standard + + var active: Observable + var reader: Reader = BatteryReader() + + init() { + self.active = Observable(defaults.object(forKey: name) != nil ? defaults.bool(forKey: name) : true) + self.view = BatteryView(frame: NSMakeRect(0, 0, MODULE_WIDTH, MODULE_HEIGHT)) + } + + func start() { + if !self.reader.usage.value.isNaN { + let value = self.reader.usage!.value + (self.view as! BatteryView).setCharging(value: value > 0) + (self.view as! BatteryView).changeValue(value: abs(value)) + } + + self.reader.start() + self.reader.usage.subscribe(observer: self) { (value, _) in + if !value.isNaN { + (self.view as! BatteryView).setCharging(value: value > 0) + (self.view as! BatteryView).changeValue(value: abs(value)) + } + } + } + + func menu() -> NSMenuItem { + let menu = NSMenuItem(title: name, action: #selector(toggle), keyEquivalent: "") + 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 + menu.isEnabled = true + return menu + } + + @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.active << state + + if !state { + self.stop() + } else { + self.start() + } + } +} + diff --git a/Stats/Modules/Battery/BatteryReader.swift b/Stats/Modules/Battery/BatteryReader.swift new file mode 100644 index 00000000..a2a6eb94 --- /dev/null +++ b/Stats/Modules/Battery/BatteryReader.swift @@ -0,0 +1,164 @@ +// +// BatteryReader.swift +// Stats +// +// Created by Serhiy Mytrovtsiy on 14/06/2019. +// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved. +// + +import Foundation + +class BatteryReader: Reader { + var usage: Observable! + var updateTimer: Timer! + + fileprivate static let IOSERVICE_BATTERY = "AppleSmartBattery" + fileprivate var service: io_service_t = 0 + fileprivate enum Key: String { + case ACPowered = "ExternalConnected" + case Amperage = "Amperage" + /// Current charge + case CurrentCapacity = "CurrentCapacity" + case CycleCount = "CycleCount" + /// Originally DesignCapacity == MaxCapacity + case DesignCapacity = "DesignCapacity" + case DesignCycleCount = "DesignCycleCount9C" + case FullyCharged = "FullyCharged" + case IsCharging = "IsCharging" + /// Current max charge (this degrades over time) + case MaxCapacity = "MaxCapacity" + case Temperature = "Temperature" + /// Time remaining to charge/discharge + case TimeRemaining = "TimeRemaining" + } + + init() { + self.usage = Observable(0) + read() + } + + func start() { + _ = self.open() + if updateTimer != nil { + return + } + updateTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(read), userInfo: nil, repeats: true) + } + + func stop() { + _ = self.close() + if updateTimer == nil { + return + } + updateTimer.invalidate() + updateTimer = nil + } + + @objc func read() { + var cap = charge() + let charging = isCharging() + + if !charging { + cap = 0 - cap + } + + self.usage << Float(cap) + } + + public func open() -> kern_return_t { + if (service != 0) { + #if DEBUG + print("WARNING - \(#file):\(#function) - connection already open") + #endif + return kIOReturnStillOpen + } + + service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceNameMatching("AppleSmartBattery")) + + if (service == 0) { + #if DEBUG + print("ERROR - \(#file):\(#function) - service not found") + #endif + return kIOReturnNotFound + } + + return kIOReturnSuccess + } + + public func close() -> kern_return_t { + let result = IOObjectRelease(service) + service = 0 + + #if DEBUG + if (result != kIOReturnSuccess) { + print("ERROR - \(#file):\(#function) - Failed to close") + } + #endif + + return result + } + + public func maxCapactiy() -> Int { + let prop = IORegistryEntryCreateCFProperty(service, Key.MaxCapacity.rawValue as CFString, kCFAllocatorDefault, 0) + + if prop != nil { + return prop!.takeUnretainedValue() as! Int + } + return 0 + } + + public func currentCapacity() -> Int { + let prop = IORegistryEntryCreateCFProperty(service, Key.CurrentCapacity.rawValue as CFString, kCFAllocatorDefault, 0) + + if prop != nil { + return prop!.takeUnretainedValue() as! Int + } + return 0 + } + + public func isACPowered() -> Bool { + let prop = IORegistryEntryCreateCFProperty(service, Key.ACPowered.rawValue as CFString, kCFAllocatorDefault, 0) + + if prop != nil { + return prop!.takeUnretainedValue() as! Bool + } + return false + } + + public func isCharging() -> Bool { + let prop = IORegistryEntryCreateCFProperty(service, Key.IsCharging.rawValue as CFString, kCFAllocatorDefault, 0) + + if prop != nil { + return prop!.takeUnretainedValue() as! Bool + } + return false + } + + public func isCharged() -> Bool { + let prop = IORegistryEntryCreateCFProperty(service, Key.FullyCharged.rawValue as CFString, kCFAllocatorDefault, 0) + + if prop != nil { + return prop!.takeUnretainedValue() as! Bool + } + return false + } + + public func charge() -> Double { + let ccap = Double(currentCapacity()) + let mcap = Double(maxCapactiy()) + + if ccap != 0 && mcap != 0 { + return ccap / mcap + } + return 0 + } + + public func timeRemaining() -> Int { + let prop = IORegistryEntryCreateCFProperty(service, Key.TimeRemaining.rawValue as CFString, kCFAllocatorDefault, 0) + + if prop != nil { + return prop!.takeUnretainedValue() as! Int + } + return 0 + } +} diff --git a/Stats/libs/Extensions.swift b/Stats/libs/Extensions.swift index 7a7f94b0..9a53f8b2 100755 --- a/Stats/libs/Extensions.swift +++ b/Stats/libs/Extensions.swift @@ -14,18 +14,46 @@ extension Float { return NSString(format: "%.\(decimalPlaces)f" as NSString, self) as String } - func usageColor() -> NSColor { + func usageColor(reversed: Bool = false) -> NSColor { if !colors.value { return NSColor.textColor } + if reversed { + switch self { + case 0.6...0.8: + return NSColor.systemOrange + case 0.8...1: + return NSColor.systemGreen + default: + return NSColor.systemRed + } + } else { + switch self { + case 0.6...0.8: + return NSColor.systemOrange + case 0.8...1: + return NSColor.systemRed + default: + return NSColor.systemGreen + } + } + } + + func batteryColor() -> NSColor { switch self { - case 0.6...0.8: + case 0.2...0.4: + if !colors.value { + return NSColor.controlTextColor + } return NSColor.systemOrange - case 0.8...1: - return NSColor.systemRed - default: + case 0.4...1: + if !colors.value { + return NSColor.controlTextColor + } return NSColor.systemGreen + default: + return NSColor.systemRed } } } From ca5578970f74e6fafa4e7a3d152568aea1b88dfe Mon Sep 17 00:00:00 2001 From: Serhiy Mytrovtsiy Date: Fri, 14 Jun 2019 16:51:16 +0200 Subject: [PATCH 2/3] moved view to file --- Stats.xcodeproj/project.pbxproj | 4 + Stats/Modules/Battery/Battery.swift | 79 -------------- Stats/Modules/Battery/BatteryReader.swift | 120 +++------------------- Stats/Modules/Battery/BatteryView.swift | 88 ++++++++++++++++ 4 files changed, 107 insertions(+), 184 deletions(-) create mode 100644 Stats/Modules/Battery/BatteryView.swift diff --git a/Stats.xcodeproj/project.pbxproj b/Stats.xcodeproj/project.pbxproj index 77daa7aa..7c9af989 100755 --- a/Stats.xcodeproj/project.pbxproj +++ b/Stats.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 9A09C89E22B3A7C90018426F /* Battery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A09C89D22B3A7C90018426F /* Battery.swift */; }; 9A09C8A022B3A7E20018426F /* BatteryReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A09C89F22B3A7E20018426F /* BatteryReader.swift */; }; + 9A09C8A222B3D94D0018426F /* BatteryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A09C8A122B3D94D0018426F /* BatteryView.swift */; }; 9A1410F9229E721100D29793 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1410F8229E721100D29793 /* AppDelegate.swift */; }; 9A141100229E721200D29793 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9A1410FE229E721200D29793 /* Main.storyboard */; }; 9A57A18522A1D26D0033E318 /* MenuBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A57A18422A1D26D0033E318 /* MenuBar.swift */; }; @@ -48,6 +49,7 @@ /* Begin PBXFileReference section */ 9A09C89D22B3A7C90018426F /* Battery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Battery.swift; sourceTree = ""; }; 9A09C89F22B3A7E20018426F /* BatteryReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryReader.swift; sourceTree = ""; }; + 9A09C8A122B3D94D0018426F /* BatteryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryView.swift; sourceTree = ""; }; 9A1410F5229E721100D29793 /* Stats.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Stats.app; sourceTree = BUILT_PRODUCTS_DIR; }; 9A1410F8229E721100D29793 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 9A1410FF229E721200D29793 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; @@ -99,6 +101,7 @@ isa = PBXGroup; children = ( 9A09C89D22B3A7C90018426F /* Battery.swift */, + 9A09C8A122B3D94D0018426F /* BatteryView.swift */, 9A09C89F22B3A7E20018426F /* BatteryReader.swift */, ); path = Battery; @@ -322,6 +325,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 9A09C8A222B3D94D0018426F /* BatteryView.swift in Sources */, 9A7B8F6F22A2C57000DEB352 /* DiskReader.swift in Sources */, 9A7B8F6922A2C3A100DEB352 /* Memory.swift in Sources */, 9A7B8F5E22A2A57600DEB352 /* CPUReader.swift in Sources */, diff --git a/Stats/Modules/Battery/Battery.swift b/Stats/Modules/Battery/Battery.swift index 19923b10..d07ca3c0 100644 --- a/Stats/Modules/Battery/Battery.swift +++ b/Stats/Modules/Battery/Battery.swift @@ -8,85 +8,6 @@ import Cocoa -class BatteryView: NSView { - var value: Float { - didSet { - self.needsDisplay = true - setNeedsDisplay(self.frame) - } - } - var charging: Bool { - didSet { - self.needsDisplay = true - setNeedsDisplay(self.frame) - } - } - - override init(frame: NSRect) { - self.value = 1.0 - self.charging = false - super.init(frame: frame) - self.wantsLayer = true - self.addSubview(NSView()) - } - - required init?(coder decoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func draw(_ dirtyRect: NSRect) { - super.draw(dirtyRect) - - let x: CGFloat = 4.0 - let w: CGFloat = dirtyRect.size.width - (x * 2) - let h: CGFloat = 11.0 - let y: CGFloat = (dirtyRect.size.height - h) / 2 - let r: CGFloat = 1.0 - - let battery = NSBezierPath(roundedRect: NSRect(x: x-1, y: y, width: w-1, height: h), xRadius: r, yRadius: r) - - let bPX: CGFloat = x+w-2 - let bPY: CGFloat = (dirtyRect.size.height / 2) - 2 - let batteryPoint = NSBezierPath(roundedRect: NSRect(x: bPX, y: bPY, width: 2, height: 4), xRadius: r, yRadius: r) - if self.charging { - NSColor.systemGreen.set() - } else { - NSColor.labelColor.set() - } - batteryPoint.lineWidth = 1.1 - batteryPoint.stroke() - batteryPoint.fill() - - let maxWidth = w-4.25 - let inner = NSBezierPath(roundedRect: NSRect(x: x+0.75, y: y+1.5, width: maxWidth*CGFloat(self.value), height: h-3), xRadius: 0.5, yRadius: 0.5) - self.value.batteryColor().set() - inner.lineWidth = 0 - inner.stroke() - inner.close() - inner.fill() - - if self.charging { - NSColor.systemGreen.set() - } else { - NSColor.labelColor.set() - } - battery.lineWidth = 0.8 - battery.stroke() - } - - func changeValue(value: Float) { - if self.value != value { - self.value = value - } - } - - func setCharging(value: Bool) { - if self.charging != value { - self.charging = value - } - } -} - class Battery: Module { let name: String = "Battery" var view: NSView = NSView() diff --git a/Stats/Modules/Battery/BatteryReader.swift b/Stats/Modules/Battery/BatteryReader.swift index a2a6eb94..8e493589 100644 --- a/Stats/Modules/Battery/BatteryReader.swift +++ b/Stats/Modules/Battery/BatteryReader.swift @@ -7,6 +7,7 @@ // import Foundation +import IOKit.ps class BatteryReader: Reader { var usage: Observable! @@ -38,7 +39,6 @@ class BatteryReader: Reader { } func start() { - _ = self.open() if updateTimer != nil { return } @@ -46,7 +46,6 @@ class BatteryReader: Reader { } func stop() { - _ = self.close() if updateTimer == nil { return } @@ -55,110 +54,21 @@ class BatteryReader: Reader { } @objc func read() { - var cap = charge() - let charging = isCharging() + let psInfo = IOPSCopyPowerSourcesInfo().takeRetainedValue() + let psList = IOPSCopyPowerSourcesList(psInfo).takeRetainedValue() as [CFTypeRef] - if !charging { - cap = 0 - cap + for ps in psList { + if let psDesc = IOPSGetPowerSourceDescription(psInfo, ps).takeUnretainedValue() as? [String: Any] { +// let type = psDesc[kIOPSTypeKey] as? String + let isCharging = (psDesc[kIOPSIsChargingKey] as? Bool) + var cap: Float = Float(psDesc[kIOPSCurrentCapacityKey] as! Int) / 100 + + if !isCharging! { + cap = 0 - cap + } + + self.usage << Float(cap) + } } - - self.usage << Float(cap) - } - - public func open() -> kern_return_t { - if (service != 0) { - #if DEBUG - print("WARNING - \(#file):\(#function) - connection already open") - #endif - return kIOReturnStillOpen - } - - service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceNameMatching("AppleSmartBattery")) - - if (service == 0) { - #if DEBUG - print("ERROR - \(#file):\(#function) - service not found") - #endif - return kIOReturnNotFound - } - - return kIOReturnSuccess - } - - public func close() -> kern_return_t { - let result = IOObjectRelease(service) - service = 0 - - #if DEBUG - if (result != kIOReturnSuccess) { - print("ERROR - \(#file):\(#function) - Failed to close") - } - #endif - - return result - } - - public func maxCapactiy() -> Int { - let prop = IORegistryEntryCreateCFProperty(service, Key.MaxCapacity.rawValue as CFString, kCFAllocatorDefault, 0) - - if prop != nil { - return prop!.takeUnretainedValue() as! Int - } - return 0 - } - - public func currentCapacity() -> Int { - let prop = IORegistryEntryCreateCFProperty(service, Key.CurrentCapacity.rawValue as CFString, kCFAllocatorDefault, 0) - - if prop != nil { - return prop!.takeUnretainedValue() as! Int - } - return 0 - } - - public func isACPowered() -> Bool { - let prop = IORegistryEntryCreateCFProperty(service, Key.ACPowered.rawValue as CFString, kCFAllocatorDefault, 0) - - if prop != nil { - return prop!.takeUnretainedValue() as! Bool - } - return false - } - - public func isCharging() -> Bool { - let prop = IORegistryEntryCreateCFProperty(service, Key.IsCharging.rawValue as CFString, kCFAllocatorDefault, 0) - - if prop != nil { - return prop!.takeUnretainedValue() as! Bool - } - return false - } - - public func isCharged() -> Bool { - let prop = IORegistryEntryCreateCFProperty(service, Key.FullyCharged.rawValue as CFString, kCFAllocatorDefault, 0) - - if prop != nil { - return prop!.takeUnretainedValue() as! Bool - } - return false - } - - public func charge() -> Double { - let ccap = Double(currentCapacity()) - let mcap = Double(maxCapactiy()) - - if ccap != 0 && mcap != 0 { - return ccap / mcap - } - return 0 - } - - public func timeRemaining() -> Int { - let prop = IORegistryEntryCreateCFProperty(service, Key.TimeRemaining.rawValue as CFString, kCFAllocatorDefault, 0) - - if prop != nil { - return prop!.takeUnretainedValue() as! Int - } - return 0 } } diff --git a/Stats/Modules/Battery/BatteryView.swift b/Stats/Modules/Battery/BatteryView.swift new file mode 100644 index 00000000..36a011e7 --- /dev/null +++ b/Stats/Modules/Battery/BatteryView.swift @@ -0,0 +1,88 @@ +// +// BatteryView.swift +// Stats +// +// Created by Serhiy Mytrovtsiy on 14/06/2019. +// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa + +class BatteryView: NSView { + var value: Float { + didSet { + self.needsDisplay = true + setNeedsDisplay(self.frame) + } + } + var charging: Bool { + didSet { + self.needsDisplay = true + setNeedsDisplay(self.frame) + } + } + + override init(frame: NSRect) { + self.value = 1.0 + self.charging = false + super.init(frame: frame) + self.wantsLayer = true + self.addSubview(NSView()) + } + + required init?(coder decoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func draw(_ dirtyRect: NSRect) { + super.draw(dirtyRect) + + let x: CGFloat = 4.0 + let w: CGFloat = dirtyRect.size.width - (x * 2) + let h: CGFloat = 11.0 + let y: CGFloat = (dirtyRect.size.height - h) / 2 + let r: CGFloat = 1.0 + + let battery = NSBezierPath(roundedRect: NSRect(x: x-1, y: y, width: w-1, height: h), xRadius: r, yRadius: r) + + let bPX: CGFloat = x+w-2 + let bPY: CGFloat = (dirtyRect.size.height / 2) - 2 + let batteryPoint = NSBezierPath(roundedRect: NSRect(x: bPX, y: bPY, width: 2, height: 4), xRadius: r, yRadius: r) + if self.charging { + NSColor.systemGreen.set() + } else { + NSColor.labelColor.set() + } + batteryPoint.lineWidth = 1.1 + batteryPoint.stroke() + batteryPoint.fill() + + let maxWidth = w-4.25 + let inner = NSBezierPath(roundedRect: NSRect(x: x+0.75, y: y+1.5, width: maxWidth*CGFloat(self.value), height: h-3), xRadius: 0.5, yRadius: 0.5) + self.value.batteryColor().set() + inner.lineWidth = 0 + inner.stroke() + inner.close() + inner.fill() + + if self.charging { + NSColor.systemGreen.set() + } else { + NSColor.labelColor.set() + } + battery.lineWidth = 0.8 + battery.stroke() + } + + func changeValue(value: Float) { + if self.value != value { + self.value = value + } + } + + func setCharging(value: Bool) { + if self.charging != value { + self.charging = value + } + } +} From 1d921b8928ebe64b53c383e2855333dab28c95a6 Mon Sep 17 00:00:00 2001 From: Serhiy Mytrovtsiy Date: Fri, 14 Jun 2019 17:58:41 +0200 Subject: [PATCH 3/3] added available flag to modules; disabled battery module on PC --- Stats/MenuBar.swift | 10 ++++++++-- Stats/Modules/Battery/Battery.swift | 2 ++ Stats/Modules/Battery/BatteryReader.swift | 23 ++--------------------- Stats/Modules/CPU/CPU.swift | 2 ++ Stats/Modules/CPU/CPUReader.swift | 1 + Stats/Modules/Disk/Disk.swift | 2 ++ Stats/Modules/Disk/DiskReader.swift | 1 + Stats/Modules/Memory/Memory.swift | 2 ++ Stats/Modules/Memory/MemoryReader.swift | 1 + Stats/libs/Module.swift | 5 ++++- 10 files changed, 25 insertions(+), 24 deletions(-) diff --git a/Stats/MenuBar.swift b/Stats/MenuBar.swift index a367a5ab..da8bec83 100644 --- a/Stats/MenuBar.swift +++ b/Stats/MenuBar.swift @@ -35,6 +35,10 @@ class MenuBar { module.active.subscribe(observer: self) { (value, _) in self.buildModulesView() } + module.available.subscribe(observer: self) { (value, _) in + self.buildModulesView() + self.menuBarItem.menu = self.buildMenu() + } } } @@ -42,7 +46,9 @@ class MenuBar { let menu = NSMenu() for module in modules.value { - menu.addItem(module.menu()) + if module.available.value { + menu.addItem(module.menu()) + } } menu.addItem(NSMenuItem.separator()) @@ -103,7 +109,7 @@ class MenuBar { WIDTH = 0 for module in modules.value { - if module.active.value { + if module.active.value && module.available.value { module.start() WIDTH = WIDTH + module.view.frame.size.width stack.addView(module.view, in: NSStackView.Gravity.center) diff --git a/Stats/Modules/Battery/Battery.swift b/Stats/Modules/Battery/Battery.swift index d07ca3c0..e5283208 100644 --- a/Stats/Modules/Battery/Battery.swift +++ b/Stats/Modules/Battery/Battery.swift @@ -14,9 +14,11 @@ class Battery: Module { let defaults = UserDefaults.standard var active: Observable + var available: Observable var reader: Reader = BatteryReader() init() { + self.available = Observable(self.reader.available) self.active = Observable(defaults.object(forKey: name) != nil ? defaults.bool(forKey: name) : true) self.view = BatteryView(frame: NSMakeRect(0, 0, MODULE_WIDTH, MODULE_HEIGHT)) } diff --git a/Stats/Modules/Battery/BatteryReader.swift b/Stats/Modules/Battery/BatteryReader.swift index 8e493589..3ba7d013 100644 --- a/Stats/Modules/Battery/BatteryReader.swift +++ b/Stats/Modules/Battery/BatteryReader.swift @@ -11,28 +11,9 @@ import IOKit.ps class BatteryReader: Reader { var usage: Observable! + var available: Bool = false var updateTimer: Timer! - fileprivate static let IOSERVICE_BATTERY = "AppleSmartBattery" - fileprivate var service: io_service_t = 0 - fileprivate enum Key: String { - case ACPowered = "ExternalConnected" - case Amperage = "Amperage" - /// Current charge - case CurrentCapacity = "CurrentCapacity" - case CycleCount = "CycleCount" - /// Originally DesignCapacity == MaxCapacity - case DesignCapacity = "DesignCapacity" - case DesignCycleCount = "DesignCycleCount9C" - case FullyCharged = "FullyCharged" - case IsCharging = "IsCharging" - /// Current max charge (this degrades over time) - case MaxCapacity = "MaxCapacity" - case Temperature = "Temperature" - /// Time remaining to charge/discharge - case TimeRemaining = "TimeRemaining" - } - init() { self.usage = Observable(0) read() @@ -56,10 +37,10 @@ class BatteryReader: Reader { @objc func read() { let psInfo = IOPSCopyPowerSourcesInfo().takeRetainedValue() let psList = IOPSCopyPowerSourcesList(psInfo).takeRetainedValue() as [CFTypeRef] + self.available = psList.count != 0 for ps in psList { if let psDesc = IOPSGetPowerSourceDescription(psInfo, ps).takeUnretainedValue() as? [String: Any] { -// let type = psDesc[kIOPSTypeKey] as? String let isCharging = (psDesc[kIOPSIsChargingKey] as? Bool) var cap: Float = Float(psDesc[kIOPSCurrentCapacityKey] as! Int) / 100 diff --git a/Stats/Modules/CPU/CPU.swift b/Stats/Modules/CPU/CPU.swift index 8bcc2fb2..c8f6fa15 100644 --- a/Stats/Modules/CPU/CPU.swift +++ b/Stats/Modules/CPU/CPU.swift @@ -14,11 +14,13 @@ class CPU: Module { let defaults = UserDefaults.standard var active: Observable + var available: Observable var reader: Reader = CPUReader() @IBOutlet weak var value: NSTextField! init() { + self.available = Observable(true) self.active = Observable(defaults.object(forKey: name) != nil ? defaults.bool(forKey: name) : true) self.view = loadViewFromNib() } diff --git a/Stats/Modules/CPU/CPUReader.swift b/Stats/Modules/CPU/CPUReader.swift index 877231ab..45cf4fb0 100644 --- a/Stats/Modules/CPU/CPUReader.swift +++ b/Stats/Modules/CPU/CPUReader.swift @@ -10,6 +10,7 @@ import Foundation class CPUReader: Reader { var usage: Observable! + var available: Bool = true var cpuInfo: processor_info_array_t! var prevCpuInfo: processor_info_array_t? var numCpuInfo: mach_msg_type_number_t = 0 diff --git a/Stats/Modules/Disk/Disk.swift b/Stats/Modules/Disk/Disk.swift index 8f2d49c4..13b14121 100644 --- a/Stats/Modules/Disk/Disk.swift +++ b/Stats/Modules/Disk/Disk.swift @@ -14,11 +14,13 @@ class Disk: Module { let defaults = UserDefaults.standard var active: Observable + var available: Observable var reader: Reader = DiskReader() @IBOutlet weak var value: NSTextField! init() { + self.available = Observable(true) self.active = Observable(defaults.object(forKey: name) != nil ? defaults.bool(forKey: name) : true) self.view = loadViewFromNib() } diff --git a/Stats/Modules/Disk/DiskReader.swift b/Stats/Modules/Disk/DiskReader.swift index 2fbf0ada..824c0e6b 100644 --- a/Stats/Modules/Disk/DiskReader.swift +++ b/Stats/Modules/Disk/DiskReader.swift @@ -10,6 +10,7 @@ import Foundation class DiskReader: Reader { var usage: Observable! + var available: Bool = true var updateTimer: Timer! init() { diff --git a/Stats/Modules/Memory/Memory.swift b/Stats/Modules/Memory/Memory.swift index 21354ce8..3516c292 100644 --- a/Stats/Modules/Memory/Memory.swift +++ b/Stats/Modules/Memory/Memory.swift @@ -14,11 +14,13 @@ class Memory: Module { let defaults = UserDefaults.standard var active: Observable + var available: Observable var reader: Reader = MemoryReader() @IBOutlet weak var value: NSTextField! init() { + self.available = Observable(true) self.active = Observable(defaults.object(forKey: name) != nil ? defaults.bool(forKey: name) : true) self.view = loadViewFromNib() } diff --git a/Stats/Modules/Memory/MemoryReader.swift b/Stats/Modules/Memory/MemoryReader.swift index 5ba24bfd..14d08c22 100644 --- a/Stats/Modules/Memory/MemoryReader.swift +++ b/Stats/Modules/Memory/MemoryReader.swift @@ -10,6 +10,7 @@ import Foundation class MemoryReader: Reader { var usage: Observable! + var available: Bool = true var updateTimer: Timer! var totalSize: Float diff --git a/Stats/libs/Module.swift b/Stats/libs/Module.swift index 9b646abe..32f1ef65 100644 --- a/Stats/libs/Module.swift +++ b/Stats/libs/Module.swift @@ -11,6 +11,7 @@ import Cocoa protocol Module { var name: String { get } var active: Observable { get } + var available: Observable { get } var reader: Reader { get } var view: NSView { get } @@ -36,7 +37,9 @@ extension Module { protocol Reader { var usage: Observable! { get } + var available: Bool { get } + var updateTimer: Timer! { get set } func start() - func read() func stop() + func read() }