created view for Battery module

This commit is contained in:
Serhiy Mytrovtsiy
2019-09-05 15:35:18 +02:00
parent 73efc7c1b3
commit 53138f1cee
13 changed files with 488 additions and 123 deletions

View File

@@ -17,6 +17,6 @@ SPEC CHECKSUMS:
Charts: ec1f57f9340054155691e84d4544a1d239d382c5
LaunchAtLogin: 550b0cbbdaf1b13f87a0fab6a3f8e2fbafe067fe
PODFILE CHECKSUM: dee05cc24d20d667671a5f594bfbb948dcc3d791
PODFILE CHECKSUM: b73e93f3b5879b0f0bf59fcb6d58288fb63aabc0
COCOAPODS: 1.7.1
COCOAPODS: 1.7.5

View File

@@ -10,7 +10,7 @@
628D2DE0AAA753E9F47625B0 /* Pods_Stats.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 56B63995EBD3A1D1EBD3AF38 /* Pods_Stats.framework */; };
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 */; };
9A09C8A222B3D94D0018426F /* BatteryWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A09C8A122B3D94D0018426F /* BatteryWidget.swift */; };
9A1410F9229E721100D29793 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1410F8229E721100D29793 /* AppDelegate.swift */; };
9A141100229E721200D29793 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9A1410FE229E721200D29793 /* Main.storyboard */; };
9A426DB822C2B5EE00C064C4 /* macAppUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A426DB722C2B5EE00C064C4 /* macAppUpdater.swift */; };
@@ -24,6 +24,7 @@
9A59AE56231EE02F007989D6 /* ChartMarker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A59AE55231EE02F007989D6 /* ChartMarker.swift */; };
9A5B1CBF229E78F0008B9D3C /* Observable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A5B1CBE229E78F0008B9D3C /* Observable.swift */; };
9A5B1CC5229E7B40008B9D3C /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A5B1CC4229E7B40008B9D3C /* Extensions.swift */; };
9A606B482321025C00642F51 /* BatteryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A606B472321025C00642F51 /* BatteryView.swift */; };
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 */; };
@@ -67,7 +68,7 @@
56B63995EBD3A1D1EBD3AF38 /* Pods_Stats.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Stats.framework; sourceTree = BUILT_PRODUCTS_DIR; };
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>"; };
9A09C8A122B3D94D0018426F /* BatteryWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryWidget.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>"; };
@@ -84,6 +85,7 @@
9A59AE55231EE02F007989D6 /* ChartMarker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartMarker.swift; sourceTree = "<group>"; };
9A5B1CBE229E78F0008B9D3C /* Observable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Observable.swift; sourceTree = "<group>"; };
9A5B1CC4229E7B40008B9D3C /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
9A606B472321025C00642F51 /* BatteryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryView.swift; sourceTree = "<group>"; };
9A6CFC0022A1C9F5001E782D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
9A74D59622B44498004FE1FA /* Mini.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mini.swift; sourceTree = "<group>"; };
9A79B36922D3BEE600BF1C3A /* Widget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Widget.swift; sourceTree = "<group>"; };
@@ -126,6 +128,7 @@
children = (
9A09C89D22B3A7C90018426F /* Battery.swift */,
9A09C89F22B3A7E20018426F /* BatteryReader.swift */,
9A606B472321025C00642F51 /* BatteryView.swift */,
);
path = Battery;
sourceTree = "<group>";
@@ -214,7 +217,7 @@
children = (
9AF0F31922DA923100026AE6 /* Network */,
9AF0F31822DA922800026AE6 /* Charts */,
9A09C8A122B3D94D0018426F /* BatteryView.swift */,
9A09C8A122B3D94D0018426F /* BatteryWidget.swift */,
9A74D59622B44498004FE1FA /* Mini.swift */,
9A79B36922D3BEE600BF1C3A /* Widget.swift */,
);
@@ -446,7 +449,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
9A09C8A222B3D94D0018426F /* BatteryView.swift in Sources */,
9A09C8A222B3D94D0018426F /* BatteryWidget.swift in Sources */,
9A426DB822C2B5EE00C064C4 /* macAppUpdater.swift in Sources */,
9A79B36E22D3BEF900BF1C3A /* Reader.swift in Sources */,
9A59AE54231ED1AC007989D6 /* CPUView.swift in Sources */,
@@ -470,6 +473,7 @@
9A1410F9229E721100D29793 /* AppDelegate.swift in Sources */,
9AF0F32722DA92DD00026AE6 /* NetworkDotsText.swift in Sources */,
9AF0F32922DA92E800026AE6 /* NetworkArrowsText.swift in Sources */,
9A606B482321025C00642F51 /* BatteryView.swift in Sources */,
9AF0F31F22DA925700026AE6 /* BarChart.swift in Sources */,
9AF0F31B22DA924000026AE6 /* LineChart.swift in Sources */,
9A59AE56231EE02F007989D6 /* ChartMarker.swift in Sources */,

View File

@@ -12,7 +12,7 @@ import LaunchAtLogin
let modules: Observable<[Module]> = Observable([CPU(), Memory(), Disk(), Battery(), Network()])
let updater = macAppUpdater(user: "exelban", repo: "stats")
let menu = NSPopover()
let popover = NSPopover()
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
@@ -26,8 +26,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}
menuBarButton.action = #selector(toggleMenu)
menu.contentViewController = MainViewController.Init()
menu.behavior = NSPopover.Behavior.transient
popover.contentViewController = MainViewController.Init()
popover.behavior = NSPopover.Behavior.transient
_ = MenuBar(menuBarItem, menuBarButton: menuBarButton)
@@ -72,19 +72,19 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}
@objc func toggleMenu(_ sender: Any?) {
if menu.isShown {
menu.performClose(sender)
if popover.isShown {
popover.performClose(sender)
} else {
if let button = self.menuBarItem.button {
NSApplication.shared.activate(ignoringOtherApps: true)
menu.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
menu.becomeFirstResponder()
popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
popover.becomeFirstResponder()
}
}
}
func applicationWillResignActive(_ notification: Notification) {
menu.performClose(self)
popover.performClose(self)
}
}

View File

@@ -28,41 +28,23 @@ class Battery: Module {
self.available = Observable(self.reader.available)
self.active = Observable(defaults.object(forKey: name) != nil ? defaults.bool(forKey: name) : true)
self.percentageView = Observable(defaults.object(forKey: "\(self.name)_percentage") != nil ? defaults.bool(forKey: "\(self.name)_percentage") : false)
self.view = BatteryView(frame: NSMakeRect(0, 0, widgetSize.width, widgetSize.height))
self.view = BatteryWidget(frame: NSMakeRect(0, 0, widgetSize.width, widgetSize.height))
initMenu()
initWidget()
initTab()
}
func initTab() {
self.tabView.view?.frame = NSRect(x: 0, y: 0, width: TabWidth, height: TabHeight)
let text: NSTextField = NSTextField(string: self.name)
text.isEditable = false
text.isSelectable = false
text.isBezeled = false
text.wantsLayer = true
text.textColor = .labelColor
text.canDrawSubviewsIntoLayer = true
text.alignment = .natural
text.font = NSFont.systemFont(ofSize: 13, weight: .regular)
text.frame.origin.x = ((self.tabView.view?.frame.size.width)! - 50) / 2
text.frame.origin.y = ((self.tabView.view?.frame.size.height)! - 22) / 2
self.tabView.view?.addSubview(text)
}
func start() {
if !self.reader.value.value.isEmpty {
let value = self.reader.value!.value
(self.view as! BatteryView).setCharging(value: value.first! > 0)
(self.view as! BatteryWidget).setCharging(value: value.first! > 0)
(self.view as! Widget).setValue(data: [abs(value.first!)])
}
self.reader.start()
self.reader.value.subscribe(observer: self) { (value, _) in
if !value.isEmpty {
(self.view as! BatteryView).setCharging(value: value.first! > 0)
(self.view as! BatteryWidget).setCharging(value: value.first! > 0)
(self.view as! Widget).setValue(data: [abs(value.first!)])
}
}
@@ -70,7 +52,7 @@ class Battery: Module {
func initWidget() {
self.active << false
(self.view as! BatteryView).setPercentage(value: self.percentageView.value)
(self.view as! BatteryWidget).setPercentage(value: self.percentageView.value)
self.active << true
}

View File

@@ -9,10 +9,45 @@
import Foundation
import IOKit.ps
struct BatteryUsage {
var powerSource: String = ""
var state: String = ""
var isCharged: Bool = false
var capacity: Double = 0
var cycles: Int = 0
var health: Int = 0
var amperage: Int = 0
var voltage: Double = 0
var temperature: Double = 0
var ACwatts: Int = 0
var ACstatus: Bool = false
var timeToEmpty: Int = 0
var timeToCharge: Int = 0
}
class BatteryReader: Reader {
var value: Observable<[Double]>!
var available: Bool = false
var updateTimer: Timer!
public var value: Observable<[Double]>!
public var usage: Observable<BatteryUsage> = Observable(BatteryUsage())
public var updateTimer: Timer!
private var service: io_connect_t = 0
private var internalChecked: Bool = false
private var hasInternalBattery: Bool = false
public var available: Bool {
get {
if !self.internalChecked {
let snapshot = IOPSCopyPowerSourcesInfo().takeRetainedValue()
let sources = IOPSCopyPowerSourcesList(snapshot).takeRetainedValue() as Array
self.hasInternalBattery = sources.count > 0
self.internalChecked = true
}
return self.hasInternalBattery
}
}
init() {
self.value = Observable([])
@@ -20,6 +55,7 @@ class BatteryReader: Reader {
}
func start() {
self.service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleSmartBattery"))
if updateTimer != nil {
return
}
@@ -32,25 +68,101 @@ class BatteryReader: Reader {
}
updateTimer.invalidate()
updateTimer = nil
IOServiceClose(self.service)
IOObjectRelease(self.service)
}
@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 powerSourceState = (psDesc[kIOPSPowerSourceStateKey] as? String)
let isCharged = (psDesc[kIOPSIsChargedKey] as? Bool)
var cap: Float = Float(psDesc[kIOPSCurrentCapacityKey] as! Int) / 100
if let list = IOPSGetPowerSourceDescription(psInfo, ps).takeUnretainedValue() as? Dictionary<String, Any> {
let powerSource = list[kIOPSPowerSourceStateKey] as? String ?? "AC Power"
let state = list[kIOPSBatteryHealthKey] as! String
let isCharged = list[kIOPSIsChargedKey] as? Bool ?? false
var cap = Float(list[kIOPSCurrentCapacityKey] as! Int) / 100
let timeToEmpty = Int(list[kIOPSTimeToEmptyKey] as! Int)
let timeToCharged = Int(list[kIOPSTimeToFullChargeKey] as! Int)
let cycles = self.getIntValue("CycleCount" as CFString) ?? 0
if isCharged == nil && powerSourceState! == "Battery Power" {
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<String, Any> {
ACwatts = Int(ACList[kIOPSPowerAdapterWattsKey] as! Int)
}
}
let ACstatus = self.getBoolValue("IsCharging" as CFString) ?? false
self.usage << BatteryUsage(
powerSource: powerSource,
state: state,
isCharged: isCharged,
capacity: Double(cap),
cycles: cycles,
health: (100 * maxCapacity) / designCapacity,
amperage: amperage,
voltage: voltage,
temperature: temperature,
ACwatts: ACwatts,
ACstatus: ACstatus,
timeToEmpty: timeToEmpty,
timeToCharge: timeToCharged
)
if powerSource == "Battery Power" {
cap = 0 - cap
}
self.value << [Double(cap)]
}
}
}
func getBoolValue(_ forIdentifier: CFString) -> Bool? {
if let value = IORegistryEntryCreateCFProperty(self.service, forIdentifier, kCFAllocatorDefault, 0) {
return value.takeRetainedValue() as? Bool
}
return nil
}
func getIntValue(_ identifier: CFString) -> Int? {
if let value = IORegistryEntryCreateCFProperty(self.service, identifier, kCFAllocatorDefault, 0) {
return value.takeRetainedValue() as? Int
}
return nil
}
func getDoubleValue(_ identifier: CFString) -> Double? {
if let value = IORegistryEntryCreateCFProperty(self.service, identifier, kCFAllocatorDefault, 0) {
return value.takeRetainedValue() as? Double
}
return nil
}
func getVoltage() -> Double? {
if let value = self.getDoubleValue("Voltage" as CFString) {
return value / 1000.0
}
return nil
}
func getTemperature() -> Double? {
if let value = IORegistryEntryCreateCFProperty(self.service, "Temperature" as CFString, kCFAllocatorDefault, 0) {
return value.takeRetainedValue() as! Double / 100.0
}
return nil
}
}

View File

@@ -0,0 +1,258 @@
//
// BatteryView.swift
// Stats
//
// Created by Serhiy Mytrovtsiy on 05/09/2019.
// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
//
import Foundation
import Cocoa
extension Battery {
func initTab() {
self.tabView.view?.frame = NSRect(x: 0, y: 0, width: TabWidth, height: 10)
makeMain()
makeOverview()
makeBattery()
makePowerAdapter()
}
func makeMain() {
let stackHeight: CGFloat = 22
let vertical: NSStackView = NSStackView(frame: NSRect(x: 0, y: TabHeight - stackHeight*3 - 4, width: TabWidth, height: stackHeight*3))
vertical.orientation = .vertical
let level: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*2, width: TabWidth - 20, height: stackHeight))
level.orientation = .horizontal
level.distribution = .equalCentering
let levelLabel = LabelField(string: "Level")
let levelValue = ValueField(string: "0%")
level.addView(levelLabel, in: .center)
level.addView(levelValue, in: .center)
let source: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*1, width: TabWidth - 20, height: stackHeight))
source.orientation = .horizontal
source.distribution = .equalCentering
let sourceLabel = LabelField(string: "Source")
let sourceValue = ValueField(string: "AC Power")
source.addView(sourceLabel, in: .center)
source.addView(sourceValue, in: .center)
let time: NSStackView = NSStackView(frame: NSRect(x: 10, y: 0, width: TabWidth - 20, height: stackHeight))
time.orientation = .horizontal
time.distribution = .equalCentering
let timeLabel = LabelField(string: "Time to charge")
let timeValue = ValueField(string: "Calculating")
time.addView(timeLabel, in: .center)
time.addView(timeValue, in: .center)
vertical.addSubview(level)
vertical.addSubview(source)
vertical.addSubview(time)
self.tabView.view?.addSubview(vertical)
(self.reader as! BatteryReader).usage.subscribe(observer: self) { (value, _) in
levelValue.stringValue = "\(Int(value.capacity * 100))%"
sourceValue.stringValue = value.powerSource
if value.powerSource == "Battery Power" {
timeLabel.stringValue = "Time to discharge"
if value.timeToEmpty != -1 && value.timeToEmpty != 0 {
timeValue.stringValue = Double(value.timeToEmpty*60).printSecondsToHoursMinutesSeconds()
}
} else {
timeLabel.stringValue = "Time to charge"
if value.timeToCharge != -1 && value.timeToCharge != 0 {
timeValue.stringValue = Double(value.timeToCharge*60).printSecondsToHoursMinutesSeconds()
}
}
if value.timeToEmpty == -1 || value.timeToEmpty == -1 {
timeValue.stringValue = "Calculating"
}
if value.isCharged {
timeValue.stringValue = "Fully charged"
}
}
}
func makeOverview() {
let overviewLabel: NSView = NSView(frame: NSRect(x: 0, y: TabHeight - 102, width: TabWidth, height: 25))
overviewLabel.wantsLayer = true
overviewLabel.layer?.backgroundColor = NSColor(hexString: "#eeeeee", alpha: 0.5).cgColor
let overviewText: NSTextField = NSTextField(string: "Overview")
overviewText.frame = NSRect(x: 0, y: 0, width: TabWidth, height: overviewLabel.frame.size.height - 4)
overviewText.isEditable = false
overviewText.isSelectable = false
overviewText.isBezeled = false
overviewText.wantsLayer = true
overviewText.textColor = .darkGray
overviewText.canDrawSubviewsIntoLayer = true
overviewText.alignment = .center
overviewText.backgroundColor = NSColor(hexString: "#dddddd", alpha: 0)
overviewText.font = NSFont.systemFont(ofSize: 12, weight: .medium)
overviewLabel.addSubview(overviewText)
self.tabView.view?.addSubview(overviewLabel)
let stackHeight: CGFloat = 22
let vertical: NSStackView = NSStackView(frame: NSRect(x: 0, y: 184, width: TabWidth, height: stackHeight*3))
vertical.orientation = .vertical
let cycles: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*2, width: TabWidth - 20, height: stackHeight))
cycles.orientation = .horizontal
cycles.distribution = .equalCentering
let cyclesLabel = LabelField(string: "Cycles")
let cyclesValue = ValueField(string: "0")
cycles.addView(cyclesLabel, in: .center)
cycles.addView(cyclesValue, in: .center)
let health: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*1, width: TabWidth - 20, height: stackHeight))
health.orientation = .horizontal
health.distribution = .equalCentering
let healthLabel = LabelField(string: "Health")
let healthValue = ValueField(string: "Calculating")
health.addView(healthLabel, in: .center)
health.addView(healthValue, in: .center)
let state: NSStackView = NSStackView(frame: NSRect(x: 10, y: 0, width: TabWidth - 20, height: stackHeight))
state.orientation = .horizontal
state.distribution = .equalCentering
let stateLabel = LabelField(string: "State")
let stateValue = ValueField(string: "Calculating")
state.addView(stateLabel, in: .center)
state.addView(stateValue, in: .center)
vertical.addSubview(cycles)
vertical.addSubview(health)
vertical.addSubview(state)
self.tabView.view?.addSubview(vertical)
(self.reader as! BatteryReader).usage.subscribe(observer: self) { (value, _) in
cyclesValue.stringValue = "\(value.cycles)"
stateValue.stringValue = value.state
healthValue.stringValue = "\(value.health)%"
}
}
func makeBattery() {
let batteryLabel: NSView = NSView(frame: NSRect(x: 0, y: TabHeight - 202, width: TabWidth, height: 25))
batteryLabel.wantsLayer = true
batteryLabel.layer?.backgroundColor = NSColor(hexString: "#eeeeee", alpha: 0.5).cgColor
let overviewText: NSTextField = NSTextField(string: "Battery")
overviewText.frame = NSRect(x: 0, y: 0, width: TabWidth, height: batteryLabel.frame.size.height - 4)
overviewText.isEditable = false
overviewText.isSelectable = false
overviewText.isBezeled = false
overviewText.wantsLayer = true
overviewText.textColor = .darkGray
overviewText.canDrawSubviewsIntoLayer = true
overviewText.alignment = .center
overviewText.backgroundColor = NSColor(hexString: "#dddddd", alpha: 0)
overviewText.font = NSFont.systemFont(ofSize: 12, weight: .medium)
batteryLabel.addSubview(overviewText)
self.tabView.view?.addSubview(batteryLabel)
let stackHeight: CGFloat = 22
let vertical: NSStackView = NSStackView(frame: NSRect(x: 0, y: TabHeight - 273, width: TabWidth, height: stackHeight*3))
vertical.orientation = .vertical
let amperage: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*2, width: TabWidth - 20, height: stackHeight))
amperage.orientation = .horizontal
amperage.distribution = .equalCentering
let amperageLabel = LabelField(string: "Amperage")
let amperageValue = ValueField(string: "0 mA")
amperage.addView(amperageLabel, in: .center)
amperage.addView(amperageValue, in: .center)
let voltage: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*1, width: TabWidth - 20, height: stackHeight))
voltage.orientation = .horizontal
voltage.distribution = .equalCentering
let voltageLabel = LabelField(string: "Voltage")
let voltageValue = ValueField(string: "0 V")
voltage.addView(voltageLabel, in: .center)
voltage.addView(voltageValue, in: .center)
let temperature: NSStackView = NSStackView(frame: NSRect(x: 10, y: 0, width: TabWidth - 20, height: stackHeight))
temperature.orientation = .horizontal
temperature.distribution = .equalCentering
let temperatureLabel = LabelField(string: "Temperature")
let temperatureValue = ValueField(string: "0 °C")
temperature.addView(temperatureLabel, in: .center)
temperature.addView(temperatureValue, in: .center)
vertical.addSubview(amperage)
vertical.addSubview(voltage)
vertical.addSubview(temperature)
self.tabView.view?.addSubview(vertical)
(self.reader as! BatteryReader).usage.subscribe(observer: self) { (value, _) in
amperageValue.stringValue = "\(value.amperage) mA"
voltageValue.stringValue = "\(value.voltage.roundTo(decimalPlaces: 2)) V"
temperatureValue.stringValue = "\(value.temperature) °C"
}
}
func makePowerAdapter() {
let powerAdapterLabel: NSView = NSView(frame: NSRect(x: 0, y: 52, width: TabWidth, height: 25))
powerAdapterLabel.wantsLayer = true
powerAdapterLabel.layer?.backgroundColor = NSColor(hexString: "#eeeeee", alpha: 0.5).cgColor
let overviewText: NSTextField = NSTextField(string: "Power adapter")
overviewText.frame = NSRect(x: 0, y: 0, width: TabWidth, height: powerAdapterLabel.frame.size.height - 4)
overviewText.isEditable = false
overviewText.isSelectable = false
overviewText.isBezeled = false
overviewText.wantsLayer = true
overviewText.textColor = .darkGray
overviewText.canDrawSubviewsIntoLayer = true
overviewText.alignment = .center
overviewText.backgroundColor = NSColor(hexString: "#dddddd", alpha: 0)
overviewText.font = NSFont.systemFont(ofSize: 12, weight: .medium)
powerAdapterLabel.addSubview(overviewText)
self.tabView.view?.addSubview(powerAdapterLabel)
let stackHeight: CGFloat = 22
let vertical: NSStackView = NSStackView(frame: NSRect(x: 0, y: 4, width: TabWidth, height: stackHeight*2))
vertical.orientation = .vertical
let power: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*1, width: TabWidth - 20, height: stackHeight))
power.orientation = .horizontal
power.distribution = .equalCentering
let powerLabel = LabelField(string: "Power")
let powerValue = ValueField(string: "0 W")
power.addView(powerLabel, in: .center)
power.addView(powerValue, in: .center)
let charging: NSStackView = NSStackView(frame: NSRect(x: 10, y: 0, width: TabWidth - 20, height: stackHeight))
charging.orientation = .horizontal
charging.distribution = .equalCentering
let chargingLabel = LabelField(string: "Is charging")
let chargingValue = ValueField(string: "No")
charging.addView(chargingLabel, in: .center)
charging.addView(chargingValue, in: .center)
vertical.addSubview(power)
vertical.addSubview(charging)
self.tabView.view?.addSubview(vertical)
(self.reader as! BatteryReader).usage.subscribe(observer: self) { (value, _) in
powerValue.stringValue = value.powerSource == "Battery Power" ? "Not connected" : "\(value.ACwatts) W"
chargingValue.stringValue = value.ACstatus ? "Yes" : "No"
}
}
}

View File

@@ -46,7 +46,7 @@ class CPUReader: Reader {
self.value = Observable([])
self.topProcess.launchPath = "/usr/bin/top"
self.topProcess.arguments = ["-s", "1", "-o", "cpu", "-n", "5", "-stats", "pid,command,cpu"]
self.topProcess.arguments = ["-s", "3", "-o", "cpu", "-n", "5", "-stats", "pid,command,cpu"]
self.topProcess.standardOutput = pipe
mibKeys.withUnsafeBufferPointer() { mib in

View File

@@ -121,24 +121,24 @@ extension CPU {
let system: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*2, width: TabWidth - 20, height: stackHeight))
system.orientation = .horizontal
system.distribution = .equalCentering
let systemLabel = labelField(string: "System")
let systemValue = valueField(string: "0 %")
let systemLabel = LabelField(string: "System")
let systemValue = ValueField(string: "0 %")
system.addView(systemLabel, in: .center)
system.addView(systemValue, in: .center)
let user: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*1, width: TabWidth - 20, height: stackHeight))
user.orientation = .horizontal
user.distribution = .equalCentering
let userLabel = labelField(string: "User")
let userValue = valueField(string: "0 %")
let userLabel = LabelField(string: "User")
let userValue = ValueField(string: "0 %")
user.addView(userLabel, in: .center)
user.addView(userValue, in: .center)
let idle: NSStackView = NSStackView(frame: NSRect(x: 10, y: 0, width: TabWidth - 20, height: stackHeight))
idle.orientation = .horizontal
idle.distribution = .equalCentering
let idleLabel = labelField(string: "Idle")
let idleValue = valueField(string: "0 %")
let idleLabel = LabelField(string: "Idle")
let idleValue = ValueField(string: "0 %")
idle.addView(idleLabel, in: .center)
idle.addView(idleValue, in: .center)
@@ -220,39 +220,11 @@ extension CPU {
let view: NSStackView = NSStackView(frame: NSRect(x: 10, y: CGFloat(num)*height, width: TabWidth - 20, height: height))
view.orientation = .horizontal
view.distribution = .equalCentering
let viewLabel = labelField(string: label)
let viewValue = valueField(string: value)
let viewLabel = LabelField(string: label)
let viewValue = ValueField(string: value)
view.addView(viewLabel, in: .center)
view.addView(viewValue, in: .center)
return view
}
func labelField(string: String) -> NSTextField {
let label: NSTextField = NSTextField(string: string)
label.isEditable = false
label.isSelectable = false
label.isBezeled = false
label.textColor = .black
label.alignment = .center
label.font = NSFont.systemFont(ofSize: 12, weight: .regular)
label.backgroundColor = NSColor(hexString: "#dddddd", alpha: 0)
return label
}
func valueField(string: String) -> NSTextField {
let label: NSTextField = NSTextField(string: string)
label.isEditable = false
label.isSelectable = false
label.isBezeled = false
label.textColor = .black
label.alignment = .center
label.font = NSFont.systemFont(ofSize: 13, weight: .regular)
label.backgroundColor = NSColor(hexString: "#dddddd", alpha: 0)
return label
}
}

View File

@@ -31,7 +31,7 @@ class MemoryReader: Reader {
var count = UInt32(MemoryLayout<host_basic_info_data_t>.size / MemoryLayout<integer_t>.size)
self.topProcess.launchPath = "/usr/bin/top"
self.topProcess.arguments = ["-s", "1", "-o", "mem", "-n", "5", "-stats", "pid,command,mem"]
self.topProcess.arguments = ["-s", "3", "-o", "mem", "-n", "5", "-stats", "pid,command,mem"]
self.topProcess.standardOutput = pipe
let kerr: kern_return_t = withUnsafeMutablePointer(to: &stats) {
@@ -79,8 +79,10 @@ class MemoryReader: Reader {
let arr = line.condenseWhitespace().split(separator: " ")
let pid = Int(arr[0]) ?? 0
let command = String(arr[1])
let usage = Double(arr[2].filter("01234567890.".contains))! * Double(1024 * 1024)
let process = TopProcess(pid: pid, command: command, usage: usage)
guard let usage = Double(arr[2].filter("01234567890.".contains)) else {
return
}
let process = TopProcess(pid: pid, command: command, usage: usage * Double(1024 * 1024))
processes.append(process)
}
}

View File

@@ -121,24 +121,24 @@ extension Memory {
let total: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*2, width: TabWidth - 20, height: stackHeight))
total.orientation = .horizontal
total.distribution = .equalCentering
let totalLabel = labelField(string: "Total")
let totalValue = valueField(string: "0 GB")
let totalLabel = LabelField(string: "Total")
let totalValue = ValueField(string: "0 GB")
total.addView(totalLabel, in: .center)
total.addView(totalValue, in: .center)
let used: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*1, width: TabWidth - 20, height: stackHeight))
used.orientation = .horizontal
used.distribution = .equalCentering
let usedLabel = labelField(string: "Used")
let usedValue = valueField(string: "0 GB")
let usedLabel = LabelField(string: "Used")
let usedValue = ValueField(string: "0 GB")
used.addView(usedLabel, in: .center)
used.addView(usedValue, in: .center)
let free: NSStackView = NSStackView(frame: NSRect(x: 10, y: 0, width: TabWidth - 20, height: stackHeight))
free.orientation = .horizontal
free.distribution = .equalCentering
let freeLabel = labelField(string: "Free")
let freeValue = valueField(string: "0 GB")
let freeLabel = LabelField(string: "Free")
let freeValue = ValueField(string: "0 GB")
free.addView(freeLabel, in: .center)
free.addView(freeValue, in: .center)
@@ -220,39 +220,11 @@ extension Memory {
let view: NSStackView = NSStackView(frame: NSRect(x: 10, y: CGFloat(num)*height, width: TabWidth - 20, height: height))
view.orientation = .horizontal
view.distribution = .equalCentering
let viewLabel = labelField(string: label)
let viewValue = valueField(string: value)
let viewLabel = LabelField(string: label)
let viewValue = ValueField(string: value)
view.addView(viewLabel, in: .center)
view.addView(viewValue, in: .center)
return view
}
func labelField(string: String) -> NSTextField {
let label: NSTextField = NSTextField(string: string)
label.isEditable = false
label.isSelectable = false
label.isBezeled = false
label.textColor = .black
label.alignment = .center
label.font = NSFont.systemFont(ofSize: 12, weight: .regular)
label.backgroundColor = NSColor(hexString: "#dddddd", alpha: 0)
return label
}
func valueField(string: String) -> NSTextField {
let label: NSTextField = NSTextField(string: string)
label.isEditable = false
label.isSelectable = false
label.isBezeled = false
label.textColor = .black
label.alignment = .center
label.font = NSFont.systemFont(ofSize: 13, weight: .regular)
label.backgroundColor = NSColor(hexString: "#dddddd", alpha: 0)
return label
}
}

View File

@@ -57,6 +57,7 @@ class MainViewController: NSViewController {
self.segmentsControl = NSSegmentedControl(labels: items, trackingMode: NSSegmentedControl.SwitchTracking.selectOne, target: self, action: #selector(switchTabs))
self.segmentsControl.setSelected(true, forSegment: 0)
// self.tabView.selectTabViewItem(at: 2)
self.segmentsControl.segmentDistribution = .fillEqually
let button = NSButton(frame: NSRect(x: 0, y: 0, width: 26, height: 20))
@@ -164,3 +165,32 @@ class MainViewController: NSViewController {
}
}
}
func LabelField(string: String) -> NSTextField {
let label: NSTextField = NSTextField(string: string)
label.isEditable = false
label.isSelectable = false
label.isBezeled = false
label.textColor = .black
label.alignment = .center
label.font = NSFont.systemFont(ofSize: 12, weight: .regular)
label.backgroundColor = NSColor(hexString: "#dddddd", alpha: 0)
return label
}
func ValueField(string: String) -> NSTextField {
let label: NSTextField = NSTextField(string: string)
label.isEditable = false
label.isSelectable = false
label.isBezeled = false
label.textColor = .black
label.alignment = .center
label.font = NSFont.systemFont(ofSize: 13, weight: .regular)
label.backgroundColor = NSColor(hexString: "#dddddd", alpha: 0)
return label
}

View File

@@ -8,7 +8,7 @@
import Cocoa
class BatteryView: NSView, Widget {
class BatteryWidget: NSView, Widget {
var activeModule: Observable<Bool> = Observable(false)
var size: CGFloat = widgetSize.width
var name: String = ""

View File

@@ -120,6 +120,39 @@ public struct Units {
}
}
extension Double {
func secondsToHoursMinutesSeconds () -> (Int?, Int?, Int?) {
let hrs = self / 3600
let mins = (self.truncatingRemainder(dividingBy: 3600)) / 60
let seconds = (self.truncatingRemainder(dividingBy:3600)).truncatingRemainder(dividingBy:60)
return (Int(hrs) > 0 ? Int(hrs) : nil , Int(mins) > 0 ? Int(mins) : nil, Int(seconds) > 0 ? Int(seconds) : nil)
}
func printSecondsToHoursMinutesSeconds () -> String {
let time = self.secondsToHoursMinutesSeconds()
switch time {
case (nil, let x? , let y?):
return "\(x) min \(y) sec"
case (nil, let x?, nil):
return "\(x) min"
case (let x?, nil, nil):
return "\(x) hr"
case (nil, nil, let x?):
return "\(x) sec"
case (let x?, nil, let z?):
return "\(x) hr \(z) sec"
case (let x?, let y?, nil):
return "\(x) hr \(y) min"
case (let x?, let y?, let z?):
return "\(x) hr \(y) min \(z) sec"
default:
return "n/a"
}
}
}
extension String {
func condenseWhitespace() -> String {
let components = self.components(separatedBy: .whitespacesAndNewlines)