initialized battery module

This commit is contained in:
Serhiy Mytrovtsiy
2019-06-14 15:06:45 +02:00
parent e89997ff30
commit a4166729ec
5 changed files with 359 additions and 6 deletions

View File

@@ -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 = "<group>"; };
9A09C89F22B3A7E20018426F /* BatteryReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryReader.swift; sourceTree = "<group>"; };
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 = "<group>"; };
9A1410FF229E721200D29793 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
@@ -91,6 +95,15 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
9A09C89C22B3A7BB0018426F /* Battery */ = {
isa = PBXGroup;
children = (
9A09C89D22B3A7C90018426F /* Battery.swift */,
9A09C89F22B3A7E20018426F /* BatteryReader.swift */,
);
path = Battery;
sourceTree = "<group>";
};
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 */,

View File

@@ -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<Bool> = Observable(true)
@NSApplicationMain

View File

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

View File

@@ -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<Float>!
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
}
}

View File

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