merged battery module

This commit is contained in:
Serhiy Mytrovtsiy
2019-06-15 13:04:42 +02:00
16 changed files with 317 additions and 13 deletions

View File

@@ -7,6 +7,9 @@
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 */; };
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 */; };
@@ -44,6 +47,9 @@
/* 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>"; };
9A09C8A122B3D94D0018426F /* BatteryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryView.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 +97,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 = (
@@ -138,6 +153,7 @@
9A5B1CBA229E7892008B9D3C /* Modules */ = {
isa = PBXGroup;
children = (
9A09C89C22B3A7BB0018426F /* Battery */,
9A7B8F5C22A2926500DEB352 /* CPU */,
9A7B8F6222A2C17000DEB352 /* Memory */,
9A7B8F6322A2C17500DEB352 /* Disk */,
@@ -158,6 +174,7 @@
9A74D59522B440D4004FE1FA /* Widgets */ = {
isa = PBXGroup;
children = (
9A09C8A122B3D94D0018426F /* BatteryView.swift */,
9A74D59322B4315C004FE1FA /* Chart.swift */,
9A74D59622B44498004FE1FA /* Mini.swift */,
);
@@ -314,13 +331,16 @@
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 */,
9A74D59422B4315C004FE1FA /* Chart.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

@@ -37,6 +37,11 @@ class MenuBar {
self.menuBarItem.menu?.removeAllItems()
self.menuBarItem.menu = self.buildMenu()
}
module.available.subscribe(observer: self) { (value, _) in
self.buildModulesView()
self.menuBarItem.menu?.removeAllItems()
self.menuBarItem.menu = self.buildMenu()
}
}
}
@@ -44,8 +49,10 @@ class MenuBar {
let menu = NSMenu()
for module in modules.value {
if module.available.value {
menu.addItem(module.menu)
}
}
menu.addItem(NSMenuItem.separator())
@@ -115,7 +122,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)

View File

@@ -0,0 +1,72 @@
//
// Battery.swift
// Stats
//
// Created by Serhiy Mytrovtsiy on 14/06/2019.
// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
//
import Cocoa
class Battery: Module {
let name: String = "Battery"
let shortName: String = ""
var view: NSView = NSView()
var menu: NSMenuItem = NSMenuItem()
var submenu: NSMenu = NSMenu()
var active: Observable<Bool>
var available: Observable<Bool>
var reader: Reader = BatteryReader()
let defaults = UserDefaults.standard
var widgetType: WidgetType = Widgets.Mini
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))
initMenu()
}
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! Widget).value(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! Widget).value(value: abs(value))
}
}
}
func initMenu() {
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
}
@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,55 @@
//
// BatteryReader.swift
// Stats
//
// Created by Serhiy Mytrovtsiy on 14/06/2019.
// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
//
import Foundation
import IOKit.ps
class BatteryReader: Reader {
var usage: Observable<Float>!
var available: Bool = false
var updateTimer: Timer!
init() {
self.usage = Observable(0)
read()
}
func start() {
if updateTimer != nil {
return
}
updateTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(read), userInfo: nil, repeats: true)
}
func stop() {
if updateTimer == nil {
return
}
updateTimer.invalidate()
updateTimer = nil
}
@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 isCharging = (psDesc[kIOPSIsChargingKey] as? Bool)
var cap: Float = Float(psDesc[kIOPSCurrentCapacityKey] as! Int) / 100
if !isCharging! {
cap = 0 - cap
}
self.usage << Float(cap)
}
}
}
}

View File

@@ -15,12 +15,14 @@ class CPU: Module {
var menu: NSMenuItem = NSMenuItem()
var submenu: NSMenu = NSMenu()
var active: Observable<Bool>
var available: Observable<Bool>
var reader: Reader = CPUReader()
let defaults = UserDefaults.standard
var widgetType: WidgetType
init() {
self.available = Observable(true)
self.active = Observable(defaults.object(forKey: name) != nil ? defaults.bool(forKey: name) : true)
self.widgetType = defaults.object(forKey: "\(name)_widget") != nil ? defaults.float(forKey: "\(name)_widget") : Widgets.Mini
initMenu()

View File

@@ -10,6 +10,7 @@ import Foundation
class CPUReader: Reader {
var usage: Observable<Float>!
var available: Bool = true
var cpuInfo: processor_info_array_t!
var prevCpuInfo: processor_info_array_t?
var numCpuInfo: mach_msg_type_number_t = 0

View File

@@ -17,11 +17,13 @@ class Disk: Module {
var widgetType: WidgetType
var active: Observable<Bool>
var available: Observable<Bool>
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.widgetType = defaults.object(forKey: "\(name)_widget") != nil ? defaults.float(forKey: "\(name)_widget") : Widgets.Mini

View File

@@ -10,6 +10,7 @@ import Foundation
class DiskReader: Reader {
var usage: Observable<Float>!
var available: Bool = true
var updateTimer: Timer!
init() {

View File

@@ -15,6 +15,7 @@ class Memory: Module {
var menu: NSMenuItem = NSMenuItem()
var submenu: NSMenu = NSMenu()
var active: Observable<Bool>
var available: Observable<Bool>
var reader: Reader = MemoryReader()
var widgetType: WidgetType
@@ -23,6 +24,7 @@ class Memory: Module {
@IBOutlet weak var value: NSTextField!
init() {
self.available = Observable(true)
self.active = Observable(defaults.object(forKey: name) != nil ? defaults.bool(forKey: name) : true)
self.widgetType = defaults.object(forKey: "\(name)_widget") != nil ? defaults.float(forKey: "\(name)_widget") : Widgets.Mini
initMenu()

View File

@@ -10,6 +10,7 @@ import Foundation
class MemoryReader: Reader {
var usage: Observable<Float>!
var available: Bool = true
var updateTimer: Timer!
var totalSize: Float

View File

@@ -0,0 +1,91 @@
//
// BatteryView.swift
// Stats
//
// Created by Serhiy Mytrovtsiy on 14/06/2019.
// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
//
import Cocoa
class BatteryView: NSView, Widget {
var value: Float {
didSet {
self.redraw()
}
}
var charging: Bool {
didSet {
self.redraw()
}
}
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 redraw() {
self.needsDisplay = true
setNeedsDisplay(self.frame)
}
func value(value: Float) {
if self.value != value {
self.value = value
}
}
func setCharging(value: Bool) {
if self.charging != value {
self.charging = value
}
}
}

View File

@@ -12,8 +12,7 @@ class Chart: NSView, Widget {
var height: CGFloat = 0.0
var points: [Float] {
didSet {
self.needsDisplay = true
setNeedsDisplay(self.frame)
self.redraw()
}
}
@@ -79,6 +78,11 @@ class Chart: NSView, Widget {
graphPath.stroke()
}
func redraw() {
self.needsDisplay = true
setNeedsDisplay(self.frame)
}
func value(value: Float) {
if self.points.count < 50 {
self.points.append(value)

View File

@@ -65,6 +65,12 @@ class Mini: NSView, Widget {
fatalError("init(coder:) has not been implemented")
}
func redraw() {
self.valueView.textColor = Float(self.value).usageColor()
self.needsDisplay = true
setNeedsDisplay(self.frame)
}
func value(value: Float) {
if self.value != value {
self.value = value

View File

@@ -14,11 +14,21 @@ 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
@@ -28,6 +38,24 @@ extension Float {
return NSColor.systemGreen
}
}
}
func batteryColor() -> NSColor {
switch self {
case 0.2...0.4:
if !colors.value {
return NSColor.controlTextColor
}
return NSColor.systemOrange
case 0.4...1:
if !colors.value {
return NSColor.controlTextColor
}
return NSColor.systemGreen
default:
return NSColor.systemRed
}
}
}
public enum Unit : Float {

View File

@@ -14,6 +14,7 @@ protocol Module: class {
var view: NSView { get set }
var menu: NSMenuItem { get }
var active: Observable<Bool> { get }
var available: Observable<Bool> { get }
var reader: Reader { get }
var widgetType: WidgetType { get }
@@ -53,7 +54,7 @@ extension Module {
}
self.reader.start()
self.reader.usage.subscribe(observer: self as AnyObject) { (value, _) in
self.reader.usage.subscribe(observer: self) { (value, _) in
if !value.isNaN {
guard let widget = self.view as? Widget else {
return
@@ -61,23 +62,34 @@ extension Module {
widget.value(value: value)
}
}
colors.subscribe(observer: self) { (value, _) in
guard let widget = self.view as? Widget else {
return
}
widget.redraw()
}
}
func stop() {
self.reader.stop()
self.reader.usage.unsubscribe(observer: self as AnyObject)
self.reader.usage.unsubscribe(observer: self)
colors.unsubscribe(observer: self)
}
}
protocol Reader {
var usage: Observable<Float>! { get }
var available: Bool { get }
var updateTimer: Timer! { get set }
func start()
func read()
func stop()
func read()
}
protocol Widget {
func value(value: Float)
func redraw()
}
typealias WidgetType = Float