mirror of
https://github.com/morgan9e/macos-stats
synced 2026-04-14 00:04:15 +09:00
initialized battery module
This commit is contained in:
@@ -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 */,
|
||||
|
||||
@@ -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
|
||||
|
||||
145
Stats/Modules/Battery/Battery.swift
Normal file
145
Stats/Modules/Battery/Battery.swift
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
164
Stats/Modules/Battery/BatteryReader.swift
Normal file
164
Stats/Modules/Battery/BatteryReader.swift
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user