mirror of
https://github.com/morgan9e/macos-stats
synced 2026-04-14 00:04:15 +09:00
created view for Memory module
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
9A141100229E721200D29793 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9A1410FE229E721200D29793 /* Main.storyboard */; };
|
||||
9A426DB822C2B5EE00C064C4 /* macAppUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A426DB722C2B5EE00C064C4 /* macAppUpdater.swift */; };
|
||||
9A426DBE22C2BE0000C064C4 /* Updates.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9A426DBD22C2BE0000C064C4 /* Updates.storyboard */; };
|
||||
9A493CDF23202B620064570C /* MemoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A493CDE23202B620064570C /* MemoryView.swift */; };
|
||||
9A57A18522A1D26D0033E318 /* MenuBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A57A18422A1D26D0033E318 /* MenuBar.swift */; };
|
||||
9A57A19D22A1E3270033E318 /* CPU.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A57A19C22A1E3270033E318 /* CPU.swift */; };
|
||||
9A58D1B022C150C800405315 /* Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A58D1AF22C150C800405315 /* Network.swift */; };
|
||||
@@ -80,6 +81,7 @@
|
||||
9A141102229E721200D29793 /* Stats.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Stats.entitlements; sourceTree = "<group>"; };
|
||||
9A426DB722C2B5EE00C064C4 /* macAppUpdater.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = macAppUpdater.swift; sourceTree = "<group>"; };
|
||||
9A426DBD22C2BE0000C064C4 /* Updates.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Updates.storyboard; sourceTree = "<group>"; };
|
||||
9A493CDE23202B620064570C /* MemoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryView.swift; sourceTree = "<group>"; };
|
||||
9A57A18422A1D26D0033E318 /* MenuBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBar.swift; sourceTree = "<group>"; };
|
||||
9A57A19C22A1E3270033E318 /* CPU.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPU.swift; sourceTree = "<group>"; };
|
||||
9A58D1AF22C150C800405315 /* Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = "<group>"; };
|
||||
@@ -256,6 +258,7 @@
|
||||
children = (
|
||||
9A7B8F6822A2C3A100DEB352 /* Memory.swift */,
|
||||
9A7B8F6C22A2C3D600DEB352 /* MemoryReader.swift */,
|
||||
9A493CDE23202B620064570C /* MemoryView.swift */,
|
||||
);
|
||||
path = Memory;
|
||||
sourceTree = "<group>";
|
||||
@@ -549,6 +552,7 @@
|
||||
9A5B1CC5229E7B40008B9D3C /* Extensions.swift in Sources */,
|
||||
9A79B36A22D3BEE600BF1C3A /* Widget.swift in Sources */,
|
||||
9AF0F32122DA92AD00026AE6 /* NetworkDots.swift in Sources */,
|
||||
9A493CDF23202B620064570C /* MemoryView.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@ struct CPUUsage {
|
||||
var idle: Double = 0
|
||||
}
|
||||
|
||||
struct CPUProcess {
|
||||
struct TopProcess {
|
||||
var pid: Int = 0
|
||||
var command: String = ""
|
||||
var usage: Double = 0
|
||||
@@ -24,7 +24,7 @@ struct CPUProcess {
|
||||
class CPUReader: Reader {
|
||||
public var value: Observable<[Double]>!
|
||||
public var usage: Observable<CPUUsage> = Observable(CPUUsage())
|
||||
public var processes: Observable<[CPUProcess]> = Observable([CPUProcess]())
|
||||
public var processes: Observable<[TopProcess]> = Observable([TopProcess]())
|
||||
public var available: Bool = true
|
||||
public var updateTimer: Timer!
|
||||
public var perCoreMode: Bool = false
|
||||
@@ -81,14 +81,14 @@ class CPUReader: Reader {
|
||||
}
|
||||
|
||||
let outputString = String(data: output, encoding: String.Encoding.utf8) ?? ""
|
||||
var processes: [CPUProcess] = []
|
||||
var processes: [TopProcess] = []
|
||||
outputString.enumerateLines { (line, stop) -> () in
|
||||
if line.matches("^\\d+ + .+ +\\d+.\\d *$") {
|
||||
let arr = line.condenseWhitespace().split(separator: " ")
|
||||
let pid = Int(arr[0]) ?? 0
|
||||
let command = String(arr[1])
|
||||
let usage = Double(arr[2]) ?? 0
|
||||
let process = CPUProcess(pid: pid, command: command, usage: usage)
|
||||
let process = TopProcess(pid: pid, command: command, usage: usage)
|
||||
processes.append(process)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ extension CPU {
|
||||
self.chart = LineChartView(frame: CGRect(x: 0, y: TabHeight - 110, width: TabWidth, height: 102))
|
||||
self.chart.animate(xAxisDuration: 2.0, yAxisDuration: 2.0, easingOption: .easeInCubic)
|
||||
self.chart.backgroundColor = .white
|
||||
self.chart.noDataText = "No data about CPU usage"
|
||||
self.chart.noDataText = "No \(self.name) usage data"
|
||||
self.chart.legend.enabled = false
|
||||
self.chart.scaleXEnabled = false
|
||||
self.chart.scaleYEnabled = false
|
||||
@@ -63,12 +63,8 @@ extension CPU {
|
||||
marker.chartView = self.chart
|
||||
self.chart.marker = marker
|
||||
|
||||
var lineChartEntry = [ChartDataEntry]()
|
||||
|
||||
lineChartEntry.append(ChartDataEntry(x: 0, y: 50))
|
||||
lineChartEntry.append(ChartDataEntry(x: 1, y: 25))
|
||||
|
||||
let chartDataSet = LineChartDataSet(entries: lineChartEntry, label: "CPU Usage")
|
||||
let lineChartEntry = [ChartDataEntry]()
|
||||
let chartDataSet = LineChartDataSet(entries: lineChartEntry, label: "\(self.name) Usage")
|
||||
chartDataSet.drawCirclesEnabled = false
|
||||
chartDataSet.mode = .cubicBezier
|
||||
chartDataSet.cubicIntensity = 0.1
|
||||
@@ -210,10 +206,12 @@ extension CPU {
|
||||
|
||||
(self.reader as! CPUReader).processes.subscribe(observer: self) { (processes, _) in
|
||||
for (i, process) in processes.enumerated() {
|
||||
let processView = processViewList[i]
|
||||
|
||||
(processView.subviews[0] as! NSTextField).stringValue = process.command
|
||||
(processView.subviews[1] as! NSTextField).stringValue = "\(process.usage.roundTo(decimalPlaces: 2)) %"
|
||||
if i < 5 {
|
||||
let processView = processViewList[i]
|
||||
|
||||
(processView.subviews[0] as! NSTextField).stringValue = process.command
|
||||
(processView.subviews[1] as! NSTextField).stringValue = "\(process.usage.roundTo(decimalPlaces: 2)) %"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,23 +7,23 @@
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import Charts
|
||||
|
||||
class Memory: Module {
|
||||
let name: String = "Memory"
|
||||
let shortName: String = "MEM"
|
||||
var view: NSView = NSView()
|
||||
var menu: NSMenuItem = NSMenuItem()
|
||||
var submenu: NSMenu = NSMenu()
|
||||
var active: Observable<Bool>
|
||||
var available: Observable<Bool>
|
||||
var reader: Reader = MemoryReader()
|
||||
var widgetType: WidgetType
|
||||
var viewAvailable: Bool = true
|
||||
var tabView: NSTabViewItem = NSTabViewItem()
|
||||
public let name: String = "Memory"
|
||||
public let shortName: String = "MEM"
|
||||
public var view: NSView = NSView()
|
||||
public var menu: NSMenuItem = NSMenuItem()
|
||||
public var active: Observable<Bool>
|
||||
public var available: Observable<Bool>
|
||||
public var reader: Reader = MemoryReader()
|
||||
public var widgetType: WidgetType
|
||||
public var viewAvailable: Bool = true
|
||||
public var tabView: NSTabViewItem = NSTabViewItem()
|
||||
public var chart: LineChartView = LineChartView()
|
||||
|
||||
let defaults = UserDefaults.standard
|
||||
|
||||
@IBOutlet weak var value: NSTextField!
|
||||
private let defaults = UserDefaults.standard
|
||||
private var submenu: NSMenu = NSMenu()
|
||||
|
||||
init() {
|
||||
self.available = Observable(true)
|
||||
@@ -34,24 +34,6 @@ class Memory: Module {
|
||||
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 initMenu() {
|
||||
menu = NSMenuItem(title: name, action: #selector(toggle), keyEquivalent: "")
|
||||
submenu = NSMenu()
|
||||
|
||||
@@ -8,17 +8,32 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct MemoryUsage {
|
||||
var total: Double = 0
|
||||
var used: Double = 0
|
||||
var free: Double = 0
|
||||
}
|
||||
|
||||
class MemoryReader: Reader {
|
||||
var value: Observable<[Double]>!
|
||||
var available: Bool = true
|
||||
var updateTimer: Timer!
|
||||
var totalSize: Float
|
||||
public var value: Observable<[Double]>!
|
||||
public var usage: Observable<MemoryUsage> = Observable(MemoryUsage())
|
||||
public var processes: Observable<[TopProcess]> = Observable([TopProcess]())
|
||||
public var available: Bool = true
|
||||
public var updateTimer: Timer!
|
||||
public var totalSize: Float
|
||||
|
||||
private var topProcess: Process = Process()
|
||||
private var pipe: Pipe = Pipe()
|
||||
|
||||
init() {
|
||||
self.value = Observable([])
|
||||
var stats = host_basic_info()
|
||||
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.standardOutput = pipe
|
||||
|
||||
let kerr: kern_return_t = withUnsafeMutablePointer(to: &stats) {
|
||||
$0.withMemoryRebound(to: integer_t.self, capacity: Int(count)) {
|
||||
host_info(mach_host_self(), HOST_BASIC_INFO, $0, &count)
|
||||
@@ -41,6 +56,43 @@ class MemoryReader: Reader {
|
||||
return
|
||||
}
|
||||
updateTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(read), userInfo: nil, repeats: true)
|
||||
|
||||
if topProcess.isRunning {
|
||||
return
|
||||
}
|
||||
self.pipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
|
||||
|
||||
NotificationCenter.default.addObserver(forName: NSNotification.Name.NSFileHandleDataAvailable, object: self.pipe.fileHandleForReading , queue: nil) { _ -> Void in
|
||||
defer {
|
||||
self.pipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
|
||||
}
|
||||
|
||||
let output = self.pipe.fileHandleForReading.availableData
|
||||
if output.isEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
let outputString = String(data: output, encoding: String.Encoding.utf8) ?? ""
|
||||
var processes: [TopProcess] = []
|
||||
outputString.enumerateLines { (line, stop) -> () in
|
||||
if line.matches("^\\d+ + .+ +\\d+.\\d[M\\+\\-]+ *$") {
|
||||
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)
|
||||
processes.append(process)
|
||||
}
|
||||
}
|
||||
|
||||
self.processes << processes
|
||||
}
|
||||
|
||||
do {
|
||||
try topProcess.run()
|
||||
} catch let error {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
func stop() {
|
||||
@@ -49,6 +101,7 @@ class MemoryReader: Reader {
|
||||
}
|
||||
updateTimer.invalidate()
|
||||
updateTimer = nil
|
||||
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.NSFileHandleDataAvailable, object: nil)
|
||||
}
|
||||
|
||||
@objc func read() {
|
||||
@@ -63,11 +116,14 @@ class MemoryReader: Reader {
|
||||
|
||||
if kerr == KERN_SUCCESS {
|
||||
let active = Float(stats.active_count) * Float(PAGE_SIZE)
|
||||
// let inactive = Float(stats.inactive_count) * Float(PAGE_SIZE)
|
||||
// let inactive = Float(stats.inactive_count) * Float(PAGE_SIZE)
|
||||
let wired = Float(stats.wire_count) * Float(PAGE_SIZE)
|
||||
let compressed = Float(stats.compressor_page_count) * Float(PAGE_SIZE)
|
||||
|
||||
let free = totalSize - (active + wired + compressed)
|
||||
let used = active + wired + compressed
|
||||
let free = totalSize - used
|
||||
|
||||
self.usage << MemoryUsage(total: Double(totalSize), used: Double(used), free: Double(free))
|
||||
self.value << [Double((totalSize - free) / totalSize)]
|
||||
}
|
||||
else {
|
||||
|
||||
258
Stats/Modules/Memory/MemoryView.swift
Normal file
258
Stats/Modules/Memory/MemoryView.swift
Normal file
@@ -0,0 +1,258 @@
|
||||
//
|
||||
// MemoryView.swift
|
||||
// Stats
|
||||
//
|
||||
// Created by Serhiy Mytrovtsiy on 04/09/2019.
|
||||
// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import Foundation
|
||||
import Charts
|
||||
|
||||
extension Memory {
|
||||
|
||||
func initTab() {
|
||||
self.tabView.view?.frame = NSRect(x: 0, y: 0, width: TabWidth, height: TabHeight)
|
||||
|
||||
makeChart()
|
||||
makeOverview()
|
||||
makeProcesses()
|
||||
|
||||
(self.reader as! MemoryReader).usage.subscribe(observer: self) { (value, _) in
|
||||
self.updateChart(value: Units(bytes: Int64(value.used)).getReadableTuple().0)
|
||||
}
|
||||
}
|
||||
|
||||
func makeChart() {
|
||||
let reader = self.reader as! MemoryReader
|
||||
let lineColor: NSColor = NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 1.0)
|
||||
let gradientColor: NSColor = NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 0.5)
|
||||
|
||||
self.chart = LineChartView(frame: CGRect(x: 0, y: TabHeight - 110, width: TabWidth, height: 102))
|
||||
self.chart.animate(xAxisDuration: 2.0, yAxisDuration: 2.0, easingOption: .easeInCubic)
|
||||
self.chart.backgroundColor = .white
|
||||
self.chart.noDataText = "No \(self.name) usage data"
|
||||
self.chart.legend.enabled = false
|
||||
self.chart.scaleXEnabled = false
|
||||
self.chart.scaleYEnabled = false
|
||||
self.chart.pinchZoomEnabled = false
|
||||
self.chart.doubleTapToZoomEnabled = false
|
||||
self.chart.drawBordersEnabled = false
|
||||
|
||||
self.chart.rightAxis.enabled = false
|
||||
|
||||
self.chart.leftAxis.axisMinimum = 0
|
||||
self.chart.leftAxis.axisMaximum = Units(bytes: Int64(reader.totalSize)).gigabytes
|
||||
self.chart.leftAxis.labelCount = Units(bytes: Int64(reader.totalSize)).gigabytes > 16 ? 6 : 4
|
||||
self.chart.leftAxis.drawGridLinesEnabled = false
|
||||
self.chart.leftAxis.drawAxisLineEnabled = false
|
||||
|
||||
self.chart.leftAxis.gridColor = NSColor(red:220/255, green:220/255, blue:220/255, alpha:1)
|
||||
self.chart.leftAxis.gridLineWidth = 0.5
|
||||
self.chart.leftAxis.drawGridLinesEnabled = true
|
||||
self.chart.leftAxis.labelTextColor = NSColor(red:150/255, green:150/255, blue:150/255, alpha:1)
|
||||
|
||||
self.chart.xAxis.drawAxisLineEnabled = false
|
||||
self.chart.xAxis.drawLimitLinesBehindDataEnabled = false
|
||||
self.chart.xAxis.gridLineWidth = 0.5
|
||||
self.chart.xAxis.drawGridLinesEnabled = false
|
||||
self.chart.xAxis.drawLabelsEnabled = false
|
||||
|
||||
let marker = ChartMarker()
|
||||
marker.chartView = self.chart
|
||||
self.chart.marker = marker
|
||||
|
||||
let lineChartEntry = [ChartDataEntry]()
|
||||
let chartDataSet = LineChartDataSet(entries: lineChartEntry, label: "\(self.name) Usage")
|
||||
chartDataSet.drawCirclesEnabled = false
|
||||
chartDataSet.mode = .cubicBezier
|
||||
chartDataSet.cubicIntensity = 0.1
|
||||
chartDataSet.colors = [lineColor]
|
||||
chartDataSet.fillColor = gradientColor
|
||||
chartDataSet.drawFilledEnabled = true
|
||||
|
||||
let data = LineChartData()
|
||||
data.addDataSet(chartDataSet)
|
||||
data.setDrawValues(false)
|
||||
|
||||
self.chart.data = LineChartData(dataSet: chartDataSet)
|
||||
|
||||
self.tabView.view?.addSubview(self.chart)
|
||||
}
|
||||
|
||||
func updateChart(value: Double) {
|
||||
let index = Double((self.chart.data?.getDataSetByIndex(0)?.entryCount)!)
|
||||
self.chart.data?.addEntry(ChartDataEntry(x: index, y: value), dataSetIndex: 0)
|
||||
|
||||
if index > 120 {
|
||||
self.chart.xAxis.axisMinimum = index - 120
|
||||
}
|
||||
self.chart.xAxis.axisMaximum = index
|
||||
self.chart.notifyDataSetChanged()
|
||||
self.chart.moveViewToX(index)
|
||||
}
|
||||
|
||||
func makeOverview() {
|
||||
let overviewLabel: NSView = NSView(frame: NSRect(x: 0, y: TabHeight - 140, 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: 147, width: TabWidth, height: stackHeight*3))
|
||||
vertical.orientation = .vertical
|
||||
|
||||
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")
|
||||
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")
|
||||
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")
|
||||
free.addView(freeLabel, in: .center)
|
||||
free.addView(freeValue, in: .center)
|
||||
|
||||
vertical.addSubview(total)
|
||||
vertical.addSubview(used)
|
||||
vertical.addSubview(free)
|
||||
|
||||
self.tabView.view?.addSubview(vertical)
|
||||
|
||||
(self.reader as! MemoryReader).usage.subscribe(observer: self) { (value, _) in
|
||||
totalValue.stringValue = Units(bytes: Int64(value.total)).getReadableUnit()
|
||||
usedValue.stringValue = Units(bytes: Int64(value.used)).getReadableUnit()
|
||||
freeValue.stringValue = Units(bytes: Int64(value.free)).getReadableUnit()
|
||||
}
|
||||
}
|
||||
|
||||
func makeProcesses() {
|
||||
let label: NSView = NSView(frame: NSRect(x: 0, y: 0, width: TabWidth, height: 25))
|
||||
|
||||
label.wantsLayer = true
|
||||
label.layer?.backgroundColor = NSColor(hexString: "#eeeeee", alpha: 0.5).cgColor
|
||||
|
||||
let text: NSTextField = NSTextField(string: "Top Processes")
|
||||
text.frame = NSRect(x: 0, y: 0, width: TabWidth, height: label.frame.size.height - 4)
|
||||
text.isEditable = false
|
||||
text.isSelectable = false
|
||||
text.isBezeled = false
|
||||
text.wantsLayer = true
|
||||
text.textColor = .darkGray
|
||||
text.canDrawSubviewsIntoLayer = true
|
||||
text.alignment = .center
|
||||
text.backgroundColor = NSColor(hexString: "#dddddd", alpha: 0)
|
||||
text.font = NSFont.systemFont(ofSize: 12, weight: .medium)
|
||||
|
||||
label.addSubview(text)
|
||||
self.tabView.view?.addSubview(label)
|
||||
|
||||
let stackHeight: CGFloat = 22
|
||||
let vertical: NSStackView = NSStackView(frame: NSRect(x: 0, y: 4, width: TabWidth, height: stackHeight*5))
|
||||
vertical.orientation = .vertical
|
||||
vertical.distribution = .fill
|
||||
|
||||
var processViewList: [NSStackView] = []
|
||||
let process_1 = makeProcessView(num: 4, height: stackHeight, label: "", value: "")
|
||||
let process_2 = makeProcessView(num: 3, height: stackHeight, label: "", value: "")
|
||||
let process_3 = makeProcessView(num: 2, height: stackHeight, label: "", value: "")
|
||||
let process_4 = makeProcessView(num: 1, height: stackHeight, label: "", value: "")
|
||||
let process_5 = makeProcessView(num: 0, height: stackHeight, label: "", value: "")
|
||||
|
||||
processViewList.append(process_1)
|
||||
processViewList.append(process_2)
|
||||
processViewList.append(process_3)
|
||||
processViewList.append(process_4)
|
||||
processViewList.append(process_5)
|
||||
|
||||
vertical.addSubview(process_1)
|
||||
vertical.addSubview(process_2)
|
||||
vertical.addSubview(process_3)
|
||||
vertical.addSubview(process_4)
|
||||
vertical.addSubview(process_5)
|
||||
self.tabView.view?.addSubview(vertical)
|
||||
|
||||
label.frame = NSRect(x: 0, y: vertical.frame.origin.y + vertical.frame.size.height + 2, width: TabWidth, height: 25)
|
||||
self.tabView.view?.addSubview(label)
|
||||
|
||||
(self.reader as! MemoryReader).processes.subscribe(observer: self) { (processes, _) in
|
||||
for (i, process) in processes.enumerated() {
|
||||
if i < 5 {
|
||||
let processView = processViewList[i]
|
||||
|
||||
(processView.subviews[0] as! NSTextField).stringValue = process.command
|
||||
(processView.subviews[1] as! NSTextField).stringValue = Units(bytes: Int64(process.usage)).getReadableUnit()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeProcessView(num: Int, height: CGFloat, label: String, value: String) -> NSStackView {
|
||||
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)
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.2.11</string>
|
||||
<string>1.3.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
|
||||
@@ -94,11 +94,11 @@ class NetworkArrowsTextView: NSView, Widget {
|
||||
|
||||
if self.download != download {
|
||||
self.download = download
|
||||
downloadValue.stringValue = Units(bytes: self.download).getReadableUnit()
|
||||
downloadValue.stringValue = "\(Units(bytes: self.download).getReadableUnit())/s"
|
||||
}
|
||||
if self.upload != upload {
|
||||
self.upload = upload
|
||||
uploadValue.stringValue = Units(bytes: self.upload).getReadableUnit()
|
||||
uploadValue.stringValue = "\(Units(bytes: self.upload).getReadableUnit())/s"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -80,11 +80,11 @@ class NetworkDotsTextView: NSView, Widget {
|
||||
|
||||
if self.download != download {
|
||||
self.download = download
|
||||
downloadValue.stringValue = Units(bytes: self.download).getReadableUnit()
|
||||
downloadValue.stringValue = "\(Units(bytes: self.download).getReadableUnit())/s"
|
||||
}
|
||||
if self.upload != upload {
|
||||
self.upload = upload
|
||||
uploadValue.stringValue = Units(bytes: self.upload).getReadableUnit()
|
||||
uploadValue.stringValue = "\(Units(bytes: self.upload).getReadableUnit())/s"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,8 +44,8 @@ class NetworkTextView: NSView, Widget {
|
||||
let download: Int64 = Int64(data[0])
|
||||
let upload: Int64 = Int64(data[1])
|
||||
|
||||
downloadValue.stringValue = Units(bytes: download).getReadableUnit()
|
||||
uploadValue.stringValue = Units(bytes: upload).getReadableUnit()
|
||||
downloadValue.stringValue = "\(Units(bytes: download).getReadableUnit())/s"
|
||||
uploadValue.stringValue = "\(Units(bytes: upload).getReadableUnit())/s"
|
||||
}
|
||||
|
||||
func valueView() {
|
||||
|
||||
@@ -107,15 +107,15 @@ public struct Units {
|
||||
public func getReadableUnit() -> String {
|
||||
switch bytes {
|
||||
case 0..<1_024:
|
||||
return "0 KB/s"
|
||||
return "0 KB"
|
||||
case 1_024..<(1_024 * 1_024):
|
||||
return String(format: "%.0f KB/s", kilobytes)
|
||||
return String(format: "%.0f KB", kilobytes)
|
||||
case 1_024..<(1_024 * 1_024 * 1_024):
|
||||
return String(format: "%.2f MB/s", megabytes)
|
||||
return String(format: "%.2f MB", megabytes)
|
||||
case (1_024 * 1_024 * 1_024)...Int64.max:
|
||||
return String(format: "%.2f GB/s", gigabytes)
|
||||
return String(format: "%.2f GB", gigabytes)
|
||||
default:
|
||||
return String(format: "%.0f KB/s", kilobytes)
|
||||
return String(format: "%.0f KB", kilobytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user