mirror of
https://github.com/morgan9e/macos-stats
synced 2026-04-14 00:04:15 +09:00
v1.2.2
This commit is contained in:
@@ -49,6 +49,14 @@ You can download latest version [here](https://github.com/exelban/stats/releases
|
||||
|
||||
## What's new
|
||||
|
||||
### v1.2.2
|
||||
- added name of the indicators in the Chart/Chart with value ([#6](https://github.com/exelban/stats/issues/6))
|
||||
- added check for new version on start
|
||||
- removed charts and charts with value to Disk module
|
||||
- now module submenu is disabled if module is disabled
|
||||
- fixed bug when network module stop working after turn on/of
|
||||
- fixed few bugs
|
||||
|
||||
### v1.2.1
|
||||
- added charts and charts with value to Disk module
|
||||
- fixed bug when Chart with value does not shows
|
||||
|
||||
@@ -15,6 +15,9 @@ extension Notification.Name {
|
||||
|
||||
let modules: Observable<[Module]> = Observable([CPU(), Memory(), Disk(), Battery(), Network()])
|
||||
let colors: Observable<Bool> = Observable(true)
|
||||
let labelForChart: Observable<Bool> = Observable(false)
|
||||
|
||||
let updater = macAppUpdater(user: "exelban", repo: "stats")
|
||||
|
||||
@NSApplicationMain
|
||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
@@ -27,7 +30,28 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
updater.check() { result, error in
|
||||
if error != nil && error as! String == "No internet connection" {
|
||||
return
|
||||
}
|
||||
|
||||
guard error == nil, let version: version = result else {
|
||||
print("Error: \(error ?? "check error")")
|
||||
return
|
||||
}
|
||||
|
||||
if version.newest {
|
||||
DispatchQueue.main.async(execute: {
|
||||
let updatesVC: NSWindowController? = NSStoryboard(name: "Updates", bundle: nil).instantiateController(withIdentifier: "UpdatesVC") as? NSWindowController
|
||||
updatesVC?.window?.center()
|
||||
updatesVC?.window?.level = .floating
|
||||
updatesVC!.showWindow(self)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
colors << (defaults.object(forKey: "colors") != nil ? defaults.bool(forKey: "colors") : false)
|
||||
labelForChart << (defaults.object(forKey: "labelForChart") != nil ? defaults.bool(forKey: "labelForChart") : false)
|
||||
_ = MenuBar(menuBarItem, menuBarButton: menuBarButton)
|
||||
|
||||
let launcherAppId = "eu.exelban.StatsLauncher"
|
||||
@@ -92,7 +116,6 @@ class UpdatesVC: NSViewController {
|
||||
@IBOutlet weak var downloadButton: NSButton!
|
||||
@IBOutlet weak var spinner: NSProgressIndicator!
|
||||
|
||||
let updater = macAppUpdater(user: "exelban", repo: "stats")
|
||||
var url: String?
|
||||
|
||||
override func viewDidLoad() {
|
||||
|
||||
@@ -65,6 +65,11 @@ class MenuBar {
|
||||
colorStatus.target = self
|
||||
preferencesMenu.addItem(colorStatus)
|
||||
|
||||
let chartLabels = NSMenuItem(title: "Label in chart", action: #selector(toggleMenu), keyEquivalent: "")
|
||||
chartLabels.state = defaults.bool(forKey: "labelForChart") || defaults.object(forKey: "labelForChart") == nil ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
chartLabels.target = self
|
||||
preferencesMenu.addItem(chartLabels)
|
||||
|
||||
let runAtLogin = NSMenuItem(title: "Run at login", action: #selector(toggleMenu), keyEquivalent: "")
|
||||
runAtLogin.state = defaults.bool(forKey: "runAtLogin") || defaults.object(forKey: "runAtLogin") == nil ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
runAtLogin.target = self
|
||||
@@ -115,6 +120,10 @@ class MenuBar {
|
||||
self.defaults.set(status, forKey: "colors")
|
||||
colors << status
|
||||
return
|
||||
case "Label in chart":
|
||||
self.defaults.set(status, forKey: "labelForChart")
|
||||
labelForChart << status
|
||||
return
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,15 @@ class CPU: Module {
|
||||
self.widgetType = defaults.object(forKey: "\(name)_widget") != nil ? defaults.float(forKey: "\(name)_widget") : Widgets.Mini
|
||||
initMenu()
|
||||
initWidget()
|
||||
|
||||
labelForChart.subscribe(observer: self) { (value, _) in
|
||||
guard let chartView: Chart = self.view as? Chart else {
|
||||
return
|
||||
}
|
||||
self.active << false
|
||||
chartView.toggleLabel(value: value)
|
||||
self.active << true
|
||||
}
|
||||
}
|
||||
|
||||
func initMenu() {
|
||||
|
||||
@@ -29,37 +29,21 @@ class Disk: Module {
|
||||
self.widgetType = defaults.object(forKey: "\(name)_widget") != nil ? defaults.float(forKey: "\(name)_widget") : Widgets.Mini
|
||||
|
||||
self.initMenu()
|
||||
initWidget()
|
||||
|
||||
let widget = Mini(frame: NSMakeRect(0, 0, MODULE_WIDTH, MODULE_HEIGHT))
|
||||
widget.label = self.shortName
|
||||
self.view = widget
|
||||
}
|
||||
|
||||
func initMenu() {
|
||||
menu = NSMenuItem(title: name, action: #selector(toggle), keyEquivalent: "")
|
||||
submenu = NSMenu()
|
||||
|
||||
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
|
||||
|
||||
let mini = NSMenuItem(title: "Mini", action: #selector(toggleWidget), keyEquivalent: "")
|
||||
mini.state = self.widgetType == Widgets.Mini ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
mini.target = self
|
||||
|
||||
let chart = NSMenuItem(title: "Chart", action: #selector(toggleWidget), keyEquivalent: "")
|
||||
chart.state = self.widgetType == Widgets.Chart ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
chart.target = self
|
||||
|
||||
let chartWithValue = NSMenuItem(title: "Chart with value", action: #selector(toggleWidget), keyEquivalent: "")
|
||||
chartWithValue.state = self.widgetType == Widgets.ChartWithValue ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
chartWithValue.target = self
|
||||
|
||||
submenu.addItem(mini)
|
||||
submenu.addItem(chart)
|
||||
submenu.addItem(chartWithValue)
|
||||
|
||||
menu.submenu = submenu
|
||||
menu.isEnabled = true
|
||||
}
|
||||
|
||||
@objc func toggle(_ sender: NSMenuItem) {
|
||||
@@ -75,36 +59,4 @@ class Disk: Module {
|
||||
self.start()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func toggleWidget(_ sender: NSMenuItem) {
|
||||
var widgetCode: Float = 0.0
|
||||
|
||||
switch sender.title {
|
||||
case "Mini":
|
||||
widgetCode = Widgets.Mini
|
||||
case "Chart":
|
||||
widgetCode = Widgets.Chart
|
||||
case "Chart with value":
|
||||
widgetCode = Widgets.ChartWithValue
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if self.widgetType == widgetCode {
|
||||
return
|
||||
}
|
||||
|
||||
for item in self.submenu.items {
|
||||
if item.title == "Mini" || item.title == "Chart" || item.title == "Chart with value" {
|
||||
item.state = NSControl.StateValue.off
|
||||
}
|
||||
}
|
||||
|
||||
sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on
|
||||
self.defaults.set(widgetCode, forKey: "\(name)_widget")
|
||||
self.widgetType = widgetCode
|
||||
self.active << false
|
||||
initWidget()
|
||||
self.active << true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,15 @@ class Memory: Module {
|
||||
self.widgetType = defaults.object(forKey: "\(name)_widget") != nil ? defaults.float(forKey: "\(name)_widget") : Widgets.Mini
|
||||
initMenu()
|
||||
initWidget()
|
||||
|
||||
labelForChart.subscribe(observer: self) { (value, _) in
|
||||
guard let chartView: Chart = self.view as? Chart else {
|
||||
return
|
||||
}
|
||||
self.active << false
|
||||
chartView.toggleLabel(value: value)
|
||||
self.active << true
|
||||
}
|
||||
}
|
||||
|
||||
func initMenu() {
|
||||
@@ -69,8 +78,10 @@ class Memory: Module {
|
||||
self.active << state
|
||||
|
||||
if !state {
|
||||
menu.submenu = nil
|
||||
self.stop()
|
||||
} else {
|
||||
menu.submenu = submenu
|
||||
self.start()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,8 +87,10 @@ class Network: Module {
|
||||
self.active << state
|
||||
|
||||
if !state {
|
||||
menu.submenu = nil
|
||||
self.stop()
|
||||
} else {
|
||||
menu.submenu = submenu
|
||||
self.start()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,33 +33,37 @@ class NetworkReader: Reader {
|
||||
defer {
|
||||
self.pipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
|
||||
}
|
||||
|
||||
|
||||
let output = self.pipe.fileHandleForReading.availableData
|
||||
if output.isEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
let outputString = String(data: output, encoding: String.Encoding.utf8) ?? ""
|
||||
let arr = outputString.condenseWhitespace().split(separator: " ")
|
||||
|
||||
|
||||
if !arr.isEmpty && Int64(arr[0]) != nil {
|
||||
guard let download = Int64(arr[2]), let upload = Int64(arr[5]) else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
guard let value: Double = Double("\(download).\(upload)") else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
self.usage << value
|
||||
}
|
||||
}
|
||||
|
||||
netProcess.launch()
|
||||
do {
|
||||
try netProcess.run()
|
||||
} catch let error {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
func stop() {
|
||||
netProcess.interrupt()
|
||||
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.NSFileHandleDataAvailable, object: nil)
|
||||
}
|
||||
|
||||
func read() {}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.2.1</string>
|
||||
<string>1.2.2</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
import Cocoa
|
||||
|
||||
class Chart: NSView, Widget {
|
||||
var labelPadding: CGFloat = 10.0
|
||||
var labelEnabled: Bool = false
|
||||
var label: String = ""
|
||||
|
||||
var height: CGFloat = 0.0
|
||||
var points: [Double] {
|
||||
didSet {
|
||||
@@ -21,6 +25,11 @@ class Chart: NSView, Widget {
|
||||
super.init(frame: frame)
|
||||
self.wantsLayer = true
|
||||
self.addSubview(NSView())
|
||||
self.labelEnabled = labelForChart.value
|
||||
|
||||
if self.labelEnabled {
|
||||
self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: self.frame.size.width + labelPadding, height: self.frame.size.height)
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder decoder: NSCoder) {
|
||||
@@ -34,12 +43,19 @@ class Chart: NSView, Widget {
|
||||
let gradientColor: NSColor = NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 0.5)
|
||||
|
||||
let context = NSGraphicsContext.current!.cgContext
|
||||
let xOffset: CGFloat = 4.0
|
||||
var xOffset: CGFloat = 4.0
|
||||
if labelEnabled {
|
||||
xOffset = xOffset + labelPadding
|
||||
}
|
||||
let yOffset: CGFloat = 3.0
|
||||
if height == 0 {
|
||||
height = self.frame.size.height - CGFloat((yOffset * 2))
|
||||
}
|
||||
let xRatio = Double(self.frame.size.width - (xOffset * 2)) / (Double(self.points.count) - 1)
|
||||
|
||||
var xRatio = Double(self.frame.size.width - (xOffset * 2)) / (Double(self.points.count) - 1)
|
||||
if labelEnabled {
|
||||
xRatio = Double(self.frame.size.width - (xOffset * 2) + labelPadding) / (Double(self.points.count) - 1)
|
||||
}
|
||||
|
||||
let columnXPoint = { (point: Int) -> CGFloat in
|
||||
return CGFloat((Double(point) * xRatio)) + xOffset
|
||||
@@ -76,6 +92,30 @@ class Chart: NSView, Widget {
|
||||
|
||||
graphPath.lineWidth = 0.5
|
||||
graphPath.stroke()
|
||||
|
||||
if !self.labelEnabled {
|
||||
return
|
||||
}
|
||||
|
||||
let style = NSMutableParagraphStyle()
|
||||
style.alignment = .center
|
||||
let stringAttributes = [
|
||||
NSAttributedString.Key.font: NSFont.systemFont(ofSize: 7.2, weight: .bold),
|
||||
NSAttributedString.Key.foregroundColor: NSColor.labelColor,
|
||||
NSAttributedString.Key.paragraphStyle: style
|
||||
]
|
||||
|
||||
let letterHeight = (self.frame.size.height - (MODULE_MARGIN*2)) / 3
|
||||
let letterWidth: CGFloat = 10.0
|
||||
|
||||
var yMargin = MODULE_MARGIN
|
||||
for char in self.label.reversed() {
|
||||
let rect = CGRect(x: MODULE_MARGIN, y: yMargin, width: letterWidth, height: letterHeight)
|
||||
let str = NSAttributedString.init(string: "\(char)", attributes: stringAttributes)
|
||||
str.draw(with: rect)
|
||||
|
||||
yMargin += letterHeight
|
||||
}
|
||||
}
|
||||
|
||||
func redraw() {
|
||||
@@ -97,6 +137,15 @@ class Chart: NSView, Widget {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func toggleLabel(value: Bool) {
|
||||
labelEnabled = value
|
||||
if value {
|
||||
self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: self.frame.size.width + labelPadding, height: self.frame.size.height)
|
||||
} else {
|
||||
self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: self.frame.size.width - labelPadding, height: self.frame.size.height)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ChartWithValue: Chart {
|
||||
@@ -104,10 +153,12 @@ class ChartWithValue: Chart {
|
||||
|
||||
override init(frame: NSRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.wantsLayer = true
|
||||
|
||||
valueLabel = NSTextField(frame: NSMakeRect(2, MODULE_HEIGHT - 11, self.frame.size.width, 10))
|
||||
if labelEnabled {
|
||||
valueLabel = NSTextField(frame: NSMakeRect(labelPadding + 2, MODULE_HEIGHT - 11, self.frame.size.width, 10))
|
||||
}
|
||||
valueLabel.textColor = NSColor.red
|
||||
valueLabel.isEditable = false
|
||||
valueLabel.isSelectable = false
|
||||
@@ -146,4 +197,15 @@ class ChartWithValue: Chart {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func toggleLabel(value: Bool) {
|
||||
labelEnabled = value
|
||||
if value {
|
||||
valueLabel.frame = NSMakeRect(labelPadding + 2, MODULE_HEIGHT - 11, self.frame.size.width, 10)
|
||||
self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: self.frame.size.width + labelPadding, height: self.frame.size.height)
|
||||
} else {
|
||||
valueLabel.frame = NSMakeRect(2, MODULE_HEIGHT - 11, self.frame.size.width, 10)
|
||||
self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: self.frame.size.width - labelPadding, height: self.frame.size.height)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,16 +141,3 @@ extension NSBezierPath {
|
||||
self.line(to: arrowLine2)
|
||||
}
|
||||
}
|
||||
|
||||
//extension NSView {
|
||||
// var backgroundColor: NSColor? {
|
||||
// get {
|
||||
// guard let color = layer?.backgroundColor else { return nil }
|
||||
// return NSColor(cgColor: color)
|
||||
// }
|
||||
// set {
|
||||
// wantsLayer = true
|
||||
// layer?.backgroundColor = newValue?.cgColor
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
@@ -13,6 +13,7 @@ protocol Module: class {
|
||||
var shortName: String { get }
|
||||
var view: NSView { get set }
|
||||
var menu: NSMenuItem { get }
|
||||
var submenu: NSMenu { get }
|
||||
var active: Observable<Bool> { get }
|
||||
var available: Observable<Bool> { get }
|
||||
var reader: Reader { get }
|
||||
@@ -31,10 +32,14 @@ extension Module {
|
||||
self.view = widget
|
||||
break
|
||||
case Widgets.Chart:
|
||||
self.view = Chart(frame: NSMakeRect(0, 0, MODULE_WIDTH + 7, MODULE_HEIGHT))
|
||||
let widget = Chart(frame: NSMakeRect(0, 0, MODULE_WIDTH + 7, MODULE_HEIGHT))
|
||||
widget.label = self.shortName
|
||||
self.view = widget
|
||||
break
|
||||
case Widgets.ChartWithValue:
|
||||
self.view = ChartWithValue(frame: NSMakeRect(0, 0, MODULE_WIDTH + 7, MODULE_HEIGHT))
|
||||
let widget = ChartWithValue(frame: NSMakeRect(0, 0, MODULE_WIDTH + 7, MODULE_HEIGHT))
|
||||
widget.label = self.shortName
|
||||
self.view = widget
|
||||
break
|
||||
case Widgets.Dots:
|
||||
self.view = NetworkDotsView(frame: NSMakeRect(0, 0, MODULE_WIDTH, MODULE_HEIGHT))
|
||||
|
||||
Reference in New Issue
Block a user