diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
deleted file mode 100644
index e22feee6..00000000
--- a/.github/FUNDING.yml
+++ /dev/null
@@ -1,3 +0,0 @@
-# These are supported funding model platforms
-
-github: exelban
diff --git a/.gitignore b/.gitignore
index a286af46..43daec06 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,4 +7,6 @@ xcuserdata
Stats.dmg
Stats.app
-create-dmg
\ No newline at end of file
+create-dmg
+
+Cartfile.resolved
\ No newline at end of file
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
deleted file mode 100644
index d74d9c2c..00000000
--- a/CODE_OF_CONDUCT.md
+++ /dev/null
@@ -1,76 +0,0 @@
-# Contributor Covenant Code of Conduct
-
-## Our Pledge
-
-In the interest of fostering an open and welcoming environment, we as
-contributors and maintainers pledge to making participation in our project and
-our community a harassment-free experience for everyone, regardless of age, body
-size, disability, ethnicity, sex characteristics, gender identity and expression,
-level of experience, education, socio-economic status, nationality, personal
-appearance, race, religion, or sexual identity and orientation.
-
-## Our Standards
-
-Examples of behavior that contributes to creating a positive environment
-include:
-
-* Using welcoming and inclusive language
-* Being respectful of differing viewpoints and experiences
-* Gracefully accepting constructive criticism
-* Focusing on what is best for the community
-* Showing empathy towards other community members
-
-Examples of unacceptable behavior by participants include:
-
-* The use of sexualized language or imagery and unwelcome sexual attention or
- advances
-* Trolling, insulting/derogatory comments, and personal or political attacks
-* Public or private harassment
-* Publishing others' private information, such as a physical or electronic
- address, without explicit permission
-* Other conduct which could reasonably be considered inappropriate in a
- professional setting
-
-## Our Responsibilities
-
-Project maintainers are responsible for clarifying the standards of acceptable
-behavior and are expected to take appropriate and fair corrective action in
-response to any instances of unacceptable behavior.
-
-Project maintainers have the right and responsibility to remove, edit, or
-reject comments, commits, code, wiki edits, issues, and other contributions
-that are not aligned to this Code of Conduct, or to ban temporarily or
-permanently any contributor for other behaviors that they deem inappropriate,
-threatening, offensive, or harmful.
-
-## Scope
-
-This Code of Conduct applies both within project spaces and in public spaces
-when an individual is representing the project or its community. Examples of
-representing a project or community include using an official project e-mail
-address, posting via an official social media account, or acting as an appointed
-representative at an online or offline event. Representation of a project may be
-further defined and clarified by project maintainers.
-
-## Enforcement
-
-Instances of abusive, harassing, or otherwise unacceptable behavior may be
-reported by contacting the project team at mitrovtsiy@ukr.net. All
-complaints will be reviewed and investigated and will result in a response that
-is deemed necessary and appropriate to the circumstances. The project team is
-obligated to maintain confidentiality with regard to the reporter of an incident.
-Further details of specific enforcement policies may be posted separately.
-
-Project maintainers who do not follow or enforce the Code of Conduct in good
-faith may face temporary or permanent repercussions as determined by other
-members of the project's leadership.
-
-## Attribution
-
-This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
-available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
-
-[homepage]: https://www.contributor-covenant.org
-
-For answers to common questions about this code of conduct, see
-https://www.contributor-covenant.org/faq
diff --git a/Cartfile b/Cartfile
index 23908c29..003e0fe0 100644
--- a/Cartfile
+++ b/Cartfile
@@ -1,3 +1,2 @@
-github "danielgindi/Charts" ~> 3.4.0
github "ashleymills/Reachability.swift" ~> 5.0.0
github "malcommac/Repeat" ~> 0.6.0
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 9f6bf502..4d75ae00 100644
--- a/Makefile
+++ b/Makefile
@@ -59,7 +59,7 @@ build: sign
./create-dmg/create-dmg \
--volname $(APP) \
- --background "./resources/background.png" \
+ --background "./Stats/Supporting Files/background.png" \
--window-pos 200 120 \
--window-size 500 320 \
--icon-size 80 \
@@ -89,4 +89,10 @@ history:
.PHONY: dep
dep:
- carthage update --platform macOS
\ No newline at end of file
+ carthage update --platform macOS
+
+.PHONY: zip
+zip:
+ cd ../
+ zip -r archive.zip ./
+ open $(PWD)
\ No newline at end of file
diff --git a/ModuleKit/Constants.swift b/ModuleKit/Constants.swift
new file mode 100644
index 00000000..5bd24214
--- /dev/null
+++ b/ModuleKit/Constants.swift
@@ -0,0 +1,43 @@
+//
+// Constants.swift
+// ModuleKit
+//
+// Created by Serhiy Mytrovtsiy on 15/04/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+
+public struct Popup_c_s {
+ public let width: CGFloat = 264
+ public let height: CGFloat = 300
+ public let margins: CGFloat = 8
+ public let headerHeight: CGFloat = 42
+ public let separatorHeight: CGFloat = 30
+}
+
+public struct Settings_c_s {
+ public let width: CGFloat = 539
+ public let height: CGFloat = 479
+ public let margin: CGFloat = 10
+}
+
+public struct Widget_c_s {
+ public let width: CGFloat = 32
+ public var height: CGFloat {
+ get {
+ let systemHeight = NSApplication.shared.mainMenu?.menuBarHeight
+ return (systemHeight == 0 ? 22 : systemHeight) ?? 22
+ }
+ }
+ public let margin: CGFloat = 2
+}
+
+public struct Constants {
+ public static let Popup: Popup_c_s = Popup_c_s()
+ public static let Settings: Settings_c_s = Settings_c_s()
+ public static let Widget: Widget_c_s = Widget_c_s()
+}
diff --git a/ModuleKit/Supporting Files/Assets.xcassets/Contents.json b/ModuleKit/Supporting Files/Assets.xcassets/Contents.json
new file mode 100644
index 00000000..73c00596
--- /dev/null
+++ b/ModuleKit/Supporting Files/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ModuleKit/Supporting Files/Assets.xcassets/settings.imageset/Contents.json b/ModuleKit/Supporting Files/Assets.xcassets/settings.imageset/Contents.json
new file mode 100644
index 00000000..211deede
--- /dev/null
+++ b/ModuleKit/Supporting Files/Assets.xcassets/settings.imageset/Contents.json
@@ -0,0 +1,26 @@
+{
+ "images" : [
+ {
+ "filename" : "baseline_settings_black_24pt_1x.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "baseline_settings_black_24pt_2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "baseline_settings_black_24pt_3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/ModuleKit/Supporting Files/Assets.xcassets/settings.imageset/baseline_settings_black_24pt_1x.png b/ModuleKit/Supporting Files/Assets.xcassets/settings.imageset/baseline_settings_black_24pt_1x.png
new file mode 100644
index 00000000..afdee093
Binary files /dev/null and b/ModuleKit/Supporting Files/Assets.xcassets/settings.imageset/baseline_settings_black_24pt_1x.png differ
diff --git a/ModuleKit/Supporting Files/Assets.xcassets/settings.imageset/baseline_settings_black_24pt_2x.png b/ModuleKit/Supporting Files/Assets.xcassets/settings.imageset/baseline_settings_black_24pt_2x.png
new file mode 100644
index 00000000..e860fbe5
Binary files /dev/null and b/ModuleKit/Supporting Files/Assets.xcassets/settings.imageset/baseline_settings_black_24pt_2x.png differ
diff --git a/ModuleKit/Supporting Files/Assets.xcassets/settings.imageset/baseline_settings_black_24pt_3x.png b/ModuleKit/Supporting Files/Assets.xcassets/settings.imageset/baseline_settings_black_24pt_3x.png
new file mode 100644
index 00000000..bfb26bad
Binary files /dev/null and b/ModuleKit/Supporting Files/Assets.xcassets/settings.imageset/baseline_settings_black_24pt_3x.png differ
diff --git a/ModuleKit/Supporting Files/Info.plist b/ModuleKit/Supporting Files/Info.plist
new file mode 100644
index 00000000..20202aa7
--- /dev/null
+++ b/ModuleKit/Supporting Files/Info.plist
@@ -0,0 +1,24 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+ NSHumanReadableCopyright
+ Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+
+
diff --git a/ModuleKit/Widgets/BarChart.swift b/ModuleKit/Widgets/BarChart.swift
new file mode 100644
index 00000000..718d39e2
--- /dev/null
+++ b/ModuleKit/Widgets/BarChart.swift
@@ -0,0 +1,240 @@
+//
+// BarChart.swift
+// ModuleKit
+//
+// Created by Serhiy Mytrovtsiy on 26/04/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+import StatsKit
+
+public class BarChart: Widget {
+ private var labelState: Bool = true
+ private var boxState: Bool = true
+ private var colorState: Bool = false
+
+ private let store: UnsafePointer?
+ private var value: [Double] = []
+
+ public init(preview: Bool, title: String, config: NSDictionary?, store: UnsafePointer?) {
+ var widgetTitle: String = title
+ self.store = store
+ if config != nil {
+ var configuration = config!
+ if let titleFromConfig = config!["Title"] as? String {
+ widgetTitle = titleFromConfig
+ }
+
+ if preview {
+ if let previewConfig = config!["Preview"] as? NSDictionary {
+ configuration = previewConfig
+ if let value = configuration["Value"] as? String {
+ self.value = value.split(separator: ",").map{ (Double($0) ?? 0) }
+ }
+ }
+ }
+
+ if let label = configuration["Label"] as? Bool {
+ self.labelState = label
+ }
+ if let box = configuration["Box"] as? Bool {
+ self.boxState = box
+ }
+ if let color = configuration["Color"] as? Bool {
+ self.colorState = color
+ }
+ }
+ super.init(frame: CGRect(x: 0, y: Constants.Widget.margin, width: Constants.Widget.width, height: Constants.Widget.height - (2*Constants.Widget.margin)))
+ self.preview = preview
+ self.title = widgetTitle
+ self.type = .barChart
+ self.canDrawConcurrently = true
+
+ if self.store != nil && !preview {
+ self.boxState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_box", defaultValue: self.boxState)
+ self.labelState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_label", defaultValue: self.labelState)
+ self.colorState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_color", defaultValue: self.colorState)
+ }
+
+ if preview {
+ if self.value.count == 0 {
+ self.value = [0.72, 0.38]
+ }
+ self.setFrameSize(NSSize(width: 36, height: self.frame.size.height))
+ self.invalidateIntrinsicContentSize()
+ }
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ public override func draw(_ dirtyRect: NSRect) {
+ super.draw(dirtyRect)
+
+ let ctx = NSGraphicsContext.current!.cgContext
+ ctx.saveGState()
+
+ var width: CGFloat = 0
+ var x: CGFloat = Constants.Widget.margin
+ var chartPadding: CGFloat = 0
+
+ if self.labelState {
+ let style = NSMutableParagraphStyle()
+ style.alignment = .center
+ let stringAttributes = [
+ NSAttributedString.Key.font: NSFont.systemFont(ofSize: 7, weight: .regular),
+ NSAttributedString.Key.foregroundColor: NSColor.labelColor,
+ NSAttributedString.Key.paragraphStyle: style
+ ]
+
+ let letterHeight = self.frame.height / 3
+ let letterWidth: CGFloat = 6.0
+
+ var yMargin: CGFloat = 0
+ for char in String(self.title.prefix(3)).uppercased().reversed() {
+ let rect = CGRect(x: x, y: yMargin, width: letterWidth, height: letterHeight)
+ let str = NSAttributedString.init(string: "\(char)", attributes: stringAttributes)
+ str.draw(with: rect)
+ yMargin += letterHeight
+ }
+ width = width + letterWidth + (Constants.Widget.margin*2)
+ x = letterWidth + (Constants.Widget.margin*3)
+ }
+
+ switch self.value.count {
+ case 0, 1:
+ width += 14
+ break
+ case 2:
+ width += 26
+ break
+ case 3...4: // 3,4
+ width += 32
+ break
+ case 5...8: // 5,6,7,8
+ width += 42
+ break
+ case 9...12: // 9..12
+ width += 52
+ break
+ case 13...16: // 13..16
+ width += 78
+ break
+ case 17...32: // 17..32
+ width += 86
+ break
+ default: // > 32
+ width += 120
+ break
+ }
+
+ let box = NSBezierPath(roundedRect: NSRect(x: x, y: 0, width: width - x - Constants.Widget.margin, height: self.frame.size.height), xRadius: 2, yRadius: 2)
+ if self.boxState {
+ NSColor.black.set()
+ box.stroke()
+ box.fill()
+ chartPadding = 1
+ }
+
+ let widthForBarChart = box.bounds.width - chartPadding
+ let partitionMargin: CGFloat = 0.5
+ let partitionsMargin: CGFloat = (CGFloat(self.value.count - 1)) * partitionMargin / CGFloat(self.value.count - 1)
+ let partitionWidth: CGFloat = (widthForBarChart / CGFloat(self.value.count)) - CGFloat(partitionsMargin.isNaN ? 0 : partitionsMargin)
+ let maxPartitionHeight: CGFloat = box.bounds.height - (chartPadding*2)
+
+ x += partitionMargin
+ for i in 0..?
+
+ private var percentage: Double = 1
+ private var time: Int = 0
+ private var charging: Bool = false
+
+ public init(preview: Bool, title: String, config: NSDictionary?, store: UnsafePointer?) {
+ let widgetTitle: String = title
+ self.store = store
+ super.init(frame: CGRect(x: 0, y: Constants.Widget.margin, width: 30, height: Constants.Widget.height - (2*Constants.Widget.margin)))
+ self.title = widgetTitle
+ self.type = .battery
+ self.preview = preview
+ self.canDrawConcurrently = true
+
+ if self.store != nil {
+ self.additional = battery_additional_t(rawValue: store!.pointee.string(key: "\(self.title)_\(self.type.rawValue)_additional", defaultValue: self.additional.rawValue)) ?? self.additional
+ self.iconState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_icon", defaultValue: self.iconState)
+ self.colorState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_color", defaultValue: self.colorState)
+ }
+
+ if self.preview {
+ self.percentage = 0.72
+ self.additional = .none
+ self.iconState = true
+ self.colorState = false
+ }
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ public override func draw(_ dirtyRect: NSRect) {
+ super.draw(dirtyRect)
+
+ var width: CGFloat = 30
+ var x: CGFloat = Constants.Widget.margin
+
+ let stringAttributes = [
+ NSAttributedString.Key.font: NSFont.systemFont(ofSize: 12, weight: .regular),
+ NSAttributedString.Key.foregroundColor: isDarkMode ? NSColor.white : NSColor.textColor,
+ NSAttributedString.Key.paragraphStyle: NSMutableParagraphStyle()
+ ]
+
+ if self.additional == .percentage {
+ let string = "\(Int((self.percentage.rounded(toPlaces: 2)) * 100))%"
+ let stringWidth = string.widthOfString(usingFont: .systemFont(ofSize: 12, weight: .regular))
+ let rect = CGRect(x: x, y: (Constants.Widget.height-12)/2, width: stringWidth, height: 12)
+ let str = NSAttributedString.init(string: string, attributes: stringAttributes)
+ str.draw(with: rect)
+
+ width += stringWidth + Constants.Widget.margin
+ x += stringWidth + Constants.Widget.margin
+ } else if self.additional == .time {
+ let string = Double(self.time*60).printSecondsToHoursMinutesSeconds()
+ let stringWidth = string.widthOfString(usingFont: .systemFont(ofSize: 12, weight: .regular))
+ let rect = CGRect(x: x, y: (Constants.Widget.height-12)/2, width: stringWidth, height: 12)
+ let str = NSAttributedString.init(string: string, attributes: stringAttributes)
+ str.draw(with: rect)
+
+ width += stringWidth + Constants.Widget.margin
+ x += stringWidth + Constants.Widget.margin
+ }
+
+ let w: CGFloat = 30 - (Constants.Widget.margin*2) - 4
+ let h: CGFloat = 11
+ let y: CGFloat = (dirtyRect.size.height - h) / 2
+ let batteryFrame = NSBezierPath(roundedRect: NSRect(x: x+1, y: y, width: w, height: h), xRadius: 1, yRadius: 1)
+
+ if self.charging {
+ NSColor.systemGreen.set()
+ } else {
+ NSColor.black.set()
+ }
+
+ let bPX: CGFloat = x+w+1
+ let bPY: CGFloat = (dirtyRect.size.height / 2) - 2
+ let batteryPoint = NSBezierPath(roundedRect: NSRect(x: bPX, y: bPY, width: 2, height: 4), xRadius: 1, yRadius: 1)
+ batteryPoint.lineWidth = 1.1
+ batteryPoint.stroke()
+ batteryPoint.fill()
+
+ batteryFrame.lineWidth = 1
+ batteryFrame.stroke()
+
+ let maxWidth = w - 3
+ let inner = NSBezierPath(roundedRect: NSRect(x: x+2.5, y: y+1.5, width: maxWidth * CGFloat(self.percentage), height: h-3), xRadius: 0.5, yRadius: 0.5)
+ self.percentage.batteryColor(color: self.colorState).set()
+ inner.lineWidth = 0
+ inner.stroke()
+ inner.close()
+ inner.fill()
+
+ self.setWidth(width)
+ }
+
+ public func setValue(percentage: Double, isCharging: Bool, time: Int) {
+ var updated: Bool = false
+
+ if self.percentage != percentage {
+ self.percentage = abs(percentage)
+ updated = true
+ }
+ if self.charging != isCharging {
+ self.charging = isCharging
+ updated = true
+ }
+ if self.time != time {
+ self.time = time
+ updated = true
+ }
+
+ if updated {
+ DispatchQueue.main.async(execute: {
+ self.display()
+ })
+ }
+ }
+
+ public override func settings(superview: NSView) {
+ let rowHeight: CGFloat = 30
+ let height: CGFloat = ((rowHeight + Constants.Settings.margin) * 2) + Constants.Settings.margin
+ superview.setFrameSize(NSSize(width: superview.frame.width, height: height))
+
+ let view: NSView = NSView(frame: NSRect(x: Constants.Settings.margin, y: Constants.Settings.margin, width: superview.frame.width - (Constants.Settings.margin*2), height: superview.frame.height - (Constants.Settings.margin*2)))
+
+ view.addSubview(SelectTitleRow(
+ frame: NSRect(x: 0, y: rowHeight + Constants.Settings.margin, width: view.frame.width, height: rowHeight),
+ title: "Additional information",
+ action: #selector(toggleAdditional),
+ items: battery_additional_t.allCases.map{ return $0.rawValue },
+ selected: self.additional.rawValue
+ ))
+
+ view.addSubview(ToggleTitleRow(
+ frame: NSRect(x: 0, y: (rowHeight + Constants.Settings.margin) * 0, width: view.frame.width, height: rowHeight),
+ title: "Colorize",
+ action: #selector(toggleColor),
+ state: self.colorState
+ ))
+
+ superview.addSubview(view)
+ }
+
+ @objc private func toggleAdditional(_ sender: NSMenuItem) {
+ let newValue: battery_additional_t = battery_additional_t(rawValue: sender.title) ?? .none
+ self.additional = newValue
+ self.store?.pointee.set(key: "\(self.title)_\(self.type.rawValue)_additional", value: self.additional.rawValue)
+ self.display()
+ }
+
+ @objc private func toggleColor(_ sender: NSControl) {
+ var state: NSControl.StateValue? = nil
+ if #available(OSX 10.15, *) {
+ state = sender is NSSwitch ? (sender as! NSSwitch).state: nil
+ } else {
+ state = sender is NSButton ? (sender as! NSButton).state: nil
+ }
+ self.colorState = state! == .on ? true : false
+ self.store?.pointee.set(key: "\(self.title)_\(self.type.rawValue)_color", value: self.colorState)
+ self.display()
+ }
+}
diff --git a/ModuleKit/Widgets/LineChart.swift b/ModuleKit/Widgets/LineChart.swift
new file mode 100644
index 00000000..8070f085
--- /dev/null
+++ b/ModuleKit/Widgets/LineChart.swift
@@ -0,0 +1,255 @@
+//
+// Chart.swift
+// ModuleKit
+//
+// Created by Serhiy Mytrovtsiy on 18/04/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+import StatsKit
+
+public class LineChart: Widget {
+ private var labelState: Bool = true
+ private var boxState: Bool = true
+ private var valueState: Bool = false
+ private var colorState: Bool = false
+
+ private let store: UnsafePointer?
+ private var chart: LineChartView
+ private var value: Double = 0
+
+ public init(preview: Bool, title: String, config: NSDictionary?, store: UnsafePointer?) {
+ var widgetTitle: String = title
+ self.store = store
+ if config != nil {
+ if let titleFromConfig = config!["Title"] as? String {
+ widgetTitle = titleFromConfig
+ }
+ if let label = config!["Label"] as? Bool {
+ self.labelState = label
+ }
+ if let box = config!["Box"] as? Bool {
+ self.boxState = box
+ }
+ if let value = config!["Value"] as? Bool {
+ self.valueState = value
+ }
+ if let color = config!["Color"] as? Bool {
+ self.colorState = color
+ }
+ }
+ self.chart = LineChartView(frame: NSRect(x: 0, y: 0, width: Constants.Widget.width, height: Constants.Widget.height - (2*Constants.Widget.margin)), num: 60)
+ super.init(frame: CGRect(x: 0, y: Constants.Widget.margin, width: Constants.Widget.width, height: Constants.Widget.height - (2*Constants.Widget.margin)))
+ self.preview = preview
+ self.title = widgetTitle
+ self.type = .lineChart
+ self.canDrawConcurrently = true
+
+ if self.store != nil && !preview {
+ self.boxState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_box", defaultValue: self.boxState)
+ self.valueState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_value", defaultValue: self.valueState)
+ self.labelState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_label", defaultValue: self.labelState)
+ self.colorState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_color", defaultValue: self.colorState)
+ }
+
+ if self.labelState {
+ self.setFrameSize(NSSize(width: Constants.Widget.width + 6 + (Constants.Widget.margin*2), height: self.frame.size.height))
+ }
+
+ if preview {
+ var list: [Double] = []
+ for _ in 0..<16 {
+ list.append(Double(CGFloat(Float(arc4random()) / Float(UINT32_MAX))))
+ }
+ self.chart.points = list
+ self.value = 0.38
+ }
+ }
+
+ public override func draw(_ dirtyRect: NSRect) {
+ super.draw(dirtyRect)
+
+ let ctx = NSGraphicsContext.current!.cgContext
+ ctx.saveGState()
+
+ var width = Constants.Widget.width
+ var x: CGFloat = Constants.Widget.margin
+ var chartPadding: CGFloat = 0
+
+ if self.labelState {
+ let style = NSMutableParagraphStyle()
+ style.alignment = .center
+ let stringAttributes = [
+ NSAttributedString.Key.font: NSFont.systemFont(ofSize: 7, weight: .regular),
+ NSAttributedString.Key.foregroundColor: NSColor.labelColor,
+ NSAttributedString.Key.paragraphStyle: style
+ ]
+
+ let letterHeight = self.frame.height / 3
+ let letterWidth: CGFloat = 6.0
+
+ var yMargin: CGFloat = 0
+ for char in String(self.title.prefix(3)).uppercased().reversed() {
+ let rect = CGRect(x: x, y: yMargin, width: letterWidth, height: letterHeight)
+ let str = NSAttributedString.init(string: "\(char)", attributes: stringAttributes)
+ str.draw(with: rect)
+ yMargin += letterHeight
+ }
+ width = width + letterWidth + (Constants.Widget.margin*2)
+ x = letterWidth + (Constants.Widget.margin*3)
+ }
+
+ var boxHeight: CGFloat = self.frame.size.height
+ var boxRadius: CGFloat = 2
+ let boxWidth: CGFloat = Constants.Widget.width - (Constants.Widget.margin*2)
+
+ if self.valueState {
+ let style = NSMutableParagraphStyle()
+ style.alignment = .right
+
+ var color = isDarkMode ? NSColor.white : NSColor.black
+ if self.colorState {
+ color = self.value.textUsageColor(color: self.colorState)
+ }
+
+ let stringAttributes = [
+ NSAttributedString.Key.font: NSFont.systemFont(ofSize: 8, weight: .regular),
+ NSAttributedString.Key.foregroundColor: color,
+ NSAttributedString.Key.paragraphStyle: style
+ ]
+
+ let rect = CGRect(x: x, y: boxHeight-7, width: boxWidth - chartPadding, height: 7)
+ let str = NSAttributedString.init(string: "\(Int((value.rounded(toPlaces: 2)) * 100))%", attributes: stringAttributes)
+ str.draw(with: rect)
+
+ boxHeight = 9
+ boxRadius = 1
+ }
+
+ let box = NSBezierPath(roundedRect: NSRect(x: x, y: 0, width: boxWidth, height: boxHeight), xRadius: boxRadius, yRadius: boxRadius)
+ if self.boxState {
+ NSColor.black.set()
+ box.stroke()
+ box.fill()
+ self.chart.transparent = false
+ chartPadding = 1
+ } else {
+ self.chart.transparent = true
+ }
+
+ chart.setFrameSize(NSSize(width: box.bounds.width - chartPadding, height: box.bounds.height - (chartPadding*2)))
+ chart.draw(NSRect(x: box.bounds.origin.x + 1, y: chartPadding, width: chart.frame.width, height: chart.frame.height))
+
+ ctx.restoreGState()
+ self.setWidth(width)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ public override func settings(superview: NSView) {
+ let rowHeight: CGFloat = 30
+ let height: CGFloat = ((rowHeight + Constants.Settings.margin) * 4) + Constants.Settings.margin
+ superview.setFrameSize(NSSize(width: superview.frame.width, height: height))
+
+ let view: NSView = NSView(frame: NSRect(x: Constants.Settings.margin, y: Constants.Settings.margin, width: superview.frame.width - (Constants.Settings.margin*2), height: superview.frame.height - (Constants.Settings.margin*2)))
+
+ view.addSubview(ToggleTitleRow(
+ frame: NSRect(x: 0, y: (rowHeight + Constants.Settings.margin) * 3, width: view.frame.width, height: rowHeight),
+ title: "Label",
+ action: #selector(toggleLabel),
+ state: self.labelState
+ ))
+
+ view.addSubview(ToggleTitleRow(
+ frame: NSRect(x: 0, y: (rowHeight + Constants.Settings.margin) * 2, width: view.frame.width, height: rowHeight),
+ title: "Box",
+ action: #selector(toggleBox),
+ state: self.boxState
+ ))
+
+ view.addSubview(ToggleTitleRow(
+ frame: NSRect(x: 0, y: (rowHeight + Constants.Settings.margin) * 1, width: view.frame.width, height: rowHeight),
+ title: "Value",
+ action: #selector(toggleValue),
+ state: self.valueState
+ ))
+
+ view.addSubview(ToggleTitleRow(
+ frame: NSRect(x: 0, y: (rowHeight + Constants.Settings.margin) * 0, width: view.frame.width, height: rowHeight),
+ title: "Colorize",
+ action: #selector(toggleColor),
+ state: self.colorState
+ ))
+
+ superview.addSubview(view)
+ }
+
+ public override func setValues(_ values: [value_t]) {
+ let historyValues = values.map{ $0.widget_value }.suffix(60)
+ let end = self.chart.points!.count
+ self.chart.points!.replaceSubrange(end-historyValues.count...end-1, with: historyValues)
+ self.display()
+ }
+
+ public func setValue(_ value: Double) {
+ self.value = value
+ DispatchQueue.main.async(execute: {
+ self.chart.addValue(value)
+ self.display()
+ })
+ }
+
+ @objc private func toggleLabel(_ sender: NSControl) {
+ var state: NSControl.StateValue? = nil
+ if #available(OSX 10.15, *) {
+ state = sender is NSSwitch ? (sender as! NSSwitch).state: nil
+ } else {
+ state = sender is NSButton ? (sender as! NSButton).state: nil
+ }
+ self.labelState = state! == .on ? true : false
+ self.store?.pointee.set(key: "\(self.title)_\(self.type.rawValue)_label", value: self.labelState)
+ self.display()
+ }
+
+ @objc private func toggleBox(_ sender: NSControl) {
+ var state: NSControl.StateValue? = nil
+ if #available(OSX 10.15, *) {
+ state = sender is NSSwitch ? (sender as! NSSwitch).state: nil
+ } else {
+ state = sender is NSButton ? (sender as! NSButton).state: nil
+ }
+ self.boxState = state! == .on ? true : false
+ self.store?.pointee.set(key: "\(self.title)_\(self.type.rawValue)_box", value: self.boxState)
+ self.display()
+ }
+
+ @objc private func toggleValue(_ sender: NSControl) {
+ var state: NSControl.StateValue? = nil
+ if #available(OSX 10.15, *) {
+ state = sender is NSSwitch ? (sender as! NSSwitch).state: nil
+ } else {
+ state = sender is NSButton ? (sender as! NSButton).state: nil
+ }
+ self.valueState = state! == .on ? true : false
+ self.store?.pointee.set(key: "\(self.title)_\(self.type.rawValue)_value", value: self.valueState)
+ self.display()
+ }
+
+ @objc private func toggleColor(_ sender: NSControl) {
+ var state: NSControl.StateValue? = nil
+ if #available(OSX 10.15, *) {
+ state = sender is NSSwitch ? (sender as! NSSwitch).state: nil
+ } else {
+ state = sender is NSButton ? (sender as! NSButton).state: nil
+ }
+ self.colorState = state! == .on ? true : false
+ self.store?.pointee.set(key: "\(self.title)_\(self.type.rawValue)_color", value: self.colorState)
+ self.display()
+ }
+}
diff --git a/ModuleKit/Widgets/Mini.swift b/ModuleKit/Widgets/Mini.swift
new file mode 100644
index 00000000..d8b72535
--- /dev/null
+++ b/ModuleKit/Widgets/Mini.swift
@@ -0,0 +1,168 @@
+//
+// Mini.swift
+// ModuleKit
+//
+// Created by Serhiy Mytrovtsiy on 10/04/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+import StatsKit
+
+public class Mini: Widget {
+ private var valueView: NSTextField = NSTextField()
+ private var labelView: NSTextField = NSTextField()
+
+ public var colorState: Bool = false
+ public var labelState: Bool = true
+
+ private let onlyValueWidth: CGFloat = 38
+ private var value: Double = 0
+ private let store: UnsafePointer?
+
+ public init(preview: Bool, title: String, config: NSDictionary?, store: UnsafePointer?) {
+ var widgetTitle: String = title
+ self.store = store
+ if config != nil {
+ var configuration = config!
+
+ if preview {
+ if let previewConfig = config!["Preview"] as? NSDictionary {
+ configuration = previewConfig
+ if let value = configuration["Value"] as? String {
+ self.value = Double(value) ?? 0.38
+ } else {
+ self.value = 0.38
+ }
+ } else {
+ self.value = 0.38
+ }
+ }
+
+ if let titleFromConfig = configuration["Title"] as? String {
+ widgetTitle = titleFromConfig
+ }
+ if let label = configuration["Label"] as? Bool {
+ self.labelState = label
+ }
+ if let color = configuration["Color"] as? Bool {
+ self.colorState = color
+ }
+ }
+ super.init(frame: CGRect(x: 0, y: Constants.Widget.margin, width: Constants.Widget.width, height: Constants.Widget.height - (2*Constants.Widget.margin)))
+ self.title = widgetTitle
+ self.type = .mini
+ self.preview = preview
+ self.canDrawConcurrently = true
+
+ if self.store != nil {
+ self.colorState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_color", defaultValue: self.colorState)
+ self.labelState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_label", defaultValue: self.labelState)
+ }
+ }
+
+ public override func draw(_ dirtyRect: NSRect) {
+ super.draw(dirtyRect)
+
+ var width: CGFloat = onlyValueWidth
+ let x: CGFloat = Constants.Widget.margin
+ var valueSize: CGFloat = 13
+ var y: CGFloat = (Constants.Widget.height-valueSize)/2
+ let style = NSMutableParagraphStyle()
+ style.alignment = .center
+
+ if self.labelState {
+ let stringAttributes = [
+ NSAttributedString.Key.font: NSFont.systemFont(ofSize: 7, weight: .light),
+ NSAttributedString.Key.foregroundColor: isDarkMode ? NSColor.white : NSColor.textColor,
+ NSAttributedString.Key.paragraphStyle: NSMutableParagraphStyle()
+ ]
+ let rect = CGRect(x: x, y: 12, width: 20, height: 7)
+ let str = NSAttributedString.init(string: self.title, attributes: stringAttributes)
+ str.draw(with: rect)
+
+ y = 1
+ valueSize = 11
+ width = Constants.Widget.width
+ style.alignment = .left
+ }
+
+ let stringAttributes = [
+ NSAttributedString.Key.font: NSFont.systemFont(ofSize: valueSize, weight: .regular),
+ NSAttributedString.Key.foregroundColor: isDarkMode ? NSColor.white : NSColor.textColor,
+ NSAttributedString.Key.paragraphStyle: style
+ ]
+ let rect = CGRect(x: x, y: y, width: width - (Constants.Widget.margin*2), height: valueSize)
+ let str = NSAttributedString.init(string: "\(Int(self.value.rounded(toPlaces: 2) * 100))%", attributes: stringAttributes)
+ str.draw(with: rect)
+
+ self.setWidth(width)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ public override func settings(superview: NSView) {
+ let height: CGFloat = 60 + (Constants.Settings.margin*3)
+ let rowHeight: CGFloat = 30
+ superview.setFrameSize(NSSize(width: superview.frame.width, height: height))
+
+ let view: NSView = NSView(frame: NSRect(x: Constants.Settings.margin, y: Constants.Settings.margin, width: superview.frame.width - (Constants.Settings.margin*2), height: superview.frame.height - (Constants.Settings.margin*2)))
+
+ view.addSubview(ToggleTitleRow(
+ frame: NSRect(x: 0, y: rowHeight + Constants.Settings.margin, width: view.frame.width, height: rowHeight),
+ title: "Label",
+ action: #selector(toggleLabel),
+ state: self.labelState
+ ))
+
+ view.addSubview(ToggleTitleRow(
+ frame: NSRect(x: 0, y: 0, width: view.frame.width, height: rowHeight),
+ title: "Colorize",
+ action: #selector(toggleColor),
+ state: self.colorState
+ ))
+
+ superview.addSubview(view)
+ }
+
+ @objc private func toggleColor(_ sender: NSControl) {
+ var state: NSControl.StateValue? = nil
+ if #available(OSX 10.15, *) {
+ state = sender is NSSwitch ? (sender as! NSSwitch).state: nil
+ } else {
+ state = sender is NSButton ? (sender as! NSButton).state: nil
+ }
+ self.colorState = state! == .on ? true : false
+ self.valueView.textColor = value.textUsageColor(color: self.colorState)
+ self.store?.pointee.set(key: "\(self.title)_\(self.type.rawValue)_color", value: self.colorState)
+ self.display()
+ }
+
+ @objc private func toggleLabel(_ sender: NSControl) {
+ var state: NSControl.StateValue? = nil
+ if #available(OSX 10.15, *) {
+ state = sender is NSSwitch ? (sender as! NSSwitch).state: nil
+ } else {
+ state = sender is NSButton ? (sender as! NSButton).state: nil
+ }
+ self.labelState = state! == .on ? true : false
+ self.store?.pointee.set(key: "\(self.title)_\(self.type.rawValue)_label", value: self.labelState)
+ self.display()
+ }
+
+ public func setValue(_ value: Double, sufix: String) {
+ if value == self.value {
+ return
+ }
+
+ self.value = value
+ DispatchQueue.main.async(execute: {
+ self.display()
+ })
+ }
+}
diff --git a/ModuleKit/Widgets/Network.swift b/ModuleKit/Widgets/Network.swift
new file mode 100644
index 00000000..8ab36bf5
--- /dev/null
+++ b/ModuleKit/Widgets/Network.swift
@@ -0,0 +1,251 @@
+//
+// Network.swift
+// ModuleKit
+//
+// Created by Serhiy Mytrovtsiy on 24/05/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+import StatsKit
+
+public enum network_icon_t: String {
+ case no = ""
+ case dot = "Dots"
+ case arrow = "Arrows"
+ case char = "Character"
+}
+extension network_icon_t: CaseIterable {}
+
+public class NetworkWidget: Widget {
+ private var icon: network_icon_t = .dot
+ private var valueState: Bool = true
+
+ private var uploadField: NSTextField? = nil
+ private var downloadField: NSTextField? = nil
+
+ private var uploadValue: Int64 = 0
+ private var downloadValue: Int64 = 0
+
+ private let store: UnsafePointer?
+ private var width: CGFloat = 52
+
+ public init(preview: Bool, title: String, config: NSDictionary?, store: UnsafePointer?) {
+ let widgetTitle: String = title
+ self.store = store
+ super.init(frame: CGRect(x: 0, y: Constants.Widget.margin, width: width, height: Constants.Widget.height - (2*Constants.Widget.margin)))
+ self.title = widgetTitle
+ self.type = .network
+ self.preview = preview
+ self.canDrawConcurrently = true
+
+ if self.store != nil {
+ self.valueState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_value", defaultValue: self.valueState)
+ self.icon = network_icon_t(rawValue: store!.pointee.string(key: "\(self.title)_\(self.type.rawValue)_icon", defaultValue: self.icon.rawValue)) ?? self.icon
+ }
+
+ if preview {
+ self.downloadValue = 8947141
+ self.uploadValue = 478678
+ }
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ public override func draw(_ dirtyRect: NSRect) {
+// guard let ctx = NSGraphicsContext.current?.cgContext else { return }
+ super.draw(dirtyRect)
+
+ var width: CGFloat = 10
+ var x: CGFloat = 10
+
+ switch self.icon {
+ case .dot:
+ self.drawDots(dirtyRect)
+ case .arrow:
+ self.drawArrows(dirtyRect)
+ case .char:
+ self.drawChars(dirtyRect)
+ default:
+ x = 0
+ width = 0
+ break
+ }
+
+ if self.valueState {
+ let rowWidth: CGFloat = 42
+ let rowHeight: CGFloat = self.frame.height / 2
+ let style = NSMutableParagraphStyle()
+ style.alignment = .right
+ let stringAttributes = [
+ NSAttributedString.Key.font: NSFont.systemFont(ofSize: 9, weight: .light),
+ NSAttributedString.Key.foregroundColor: NSColor.textColor,
+ NSAttributedString.Key.paragraphStyle: style
+ ]
+
+ var rect = CGRect(x: Constants.Widget.margin + x, y: 1, width: rowWidth - (Constants.Widget.margin*2), height: rowHeight)
+ let download = NSAttributedString.init(string: Units(bytes: self.downloadValue).getReadableSpeed(), attributes: stringAttributes)
+ download.draw(with: rect)
+
+ rect = CGRect(x: Constants.Widget.margin + x, y: rect.height+1, width: rowWidth - (Constants.Widget.margin*2), height: rowHeight)
+ let upload = NSAttributedString.init(string: Units(bytes: self.uploadValue).getReadableSpeed(), attributes: stringAttributes)
+ upload.draw(with: rect)
+
+ width += rowWidth
+ }
+
+ if width == 0 {
+ width = 1
+ }
+ self.setWidth(width)
+ }
+
+ private func drawDots(_ dirtyRect: NSRect) {
+ let rowHeight: CGFloat = self.frame.height / 2
+ let size: CGFloat = 6
+ let y: CGFloat = (rowHeight-size)/2
+
+ var downloadCircle = NSBezierPath()
+ downloadCircle = NSBezierPath(ovalIn: CGRect(x: Constants.Widget.margin, y: y-0.2, width: size, height: size))
+ if self.downloadValue >= 1_024 {
+ NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 0.8).setFill()
+ } else {
+ NSColor.labelColor.setFill()
+ }
+ downloadCircle.fill()
+
+ var uploadCircle = NSBezierPath()
+ uploadCircle = NSBezierPath(ovalIn: CGRect(x: Constants.Widget.margin, y: 10.5, width: size, height: size))
+ if self.uploadValue >= 1_024 {
+ NSColor.red.setFill()
+ } else {
+ NSColor.labelColor.setFill()
+ }
+ uploadCircle.fill()
+ }
+
+ private func drawArrows(_ dirtyRect: NSRect) {
+ let arrowAngle = CGFloat(Double.pi / 5)
+ let pointerLineLength: CGFloat = 3.5
+ let workingHeight: CGFloat = (self.frame.size.height - (Constants.Widget.margin * 2))
+ let height: CGFloat = ((workingHeight - Constants.Widget.margin) / 2)
+
+ let downloadArrow = NSBezierPath()
+ let downloadStart = CGPoint(x: Constants.Widget.margin + (pointerLineLength/2), y: height + Constants.Widget.margin)
+ let downloadEnd = CGPoint(x: Constants.Widget.margin + (pointerLineLength/2), y: Constants.Widget.margin)
+ downloadArrow.addArrow(start: downloadStart, end: downloadEnd, pointerLineLength: pointerLineLength, arrowAngle: arrowAngle)
+
+ if self.downloadValue >= 1_024 {
+ NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 0.8).set()
+ } else {
+ NSColor.labelColor.set()
+ }
+ downloadArrow.lineWidth = 1
+ downloadArrow.stroke()
+ downloadArrow.close()
+
+ let uploadArrow = NSBezierPath()
+ let uploadStart = CGPoint(x: Constants.Widget.margin + (pointerLineLength/2), y: height + (Constants.Widget.margin * 2))
+ let uploadEnd = CGPoint(x: Constants.Widget.margin + (pointerLineLength/2), y: (Constants.Widget.margin * 2) + (height * 2))
+ uploadArrow.addArrow(start: uploadStart, end: uploadEnd, pointerLineLength: pointerLineLength, arrowAngle: arrowAngle)
+
+ if self.uploadValue >= 1_024 {
+ NSColor.red.set()
+ } else {
+ NSColor.labelColor.set()
+ }
+ uploadArrow.lineWidth = 1
+ uploadArrow.stroke()
+ uploadArrow.close()
+ }
+
+ private func drawChars(_ dirtyRect: NSRect) {
+ let rowHeight: CGFloat = self.frame.height / 2
+
+ let downloadAttributes = [
+ NSAttributedString.Key.font: NSFont.systemFont(ofSize: 9, weight: .regular),
+ NSAttributedString.Key.foregroundColor: downloadValue >= 1_024 ? NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 0.8) : NSColor.labelColor,
+ NSAttributedString.Key.paragraphStyle: NSMutableParagraphStyle()
+ ]
+ var rect = CGRect(x: Constants.Widget.margin, y: 1, width: 8, height: rowHeight)
+ var str = NSAttributedString.init(string: "D", attributes: downloadAttributes)
+ str.draw(with: rect)
+
+ let uploadAttributes = [
+ NSAttributedString.Key.font: NSFont.systemFont(ofSize: 9, weight: .regular),
+ NSAttributedString.Key.foregroundColor: uploadValue >= 1_024 ? NSColor.red : NSColor.labelColor,
+ NSAttributedString.Key.paragraphStyle: NSMutableParagraphStyle()
+ ]
+ rect = CGRect(x: Constants.Widget.margin, y: rect.height+1, width: 8, height: rowHeight)
+ str = NSAttributedString.init(string: "U", attributes: uploadAttributes)
+ str.draw(with: rect)
+ }
+
+ public override func settings(superview: NSView) {
+ let height: CGFloat = 60 + (Constants.Settings.margin*3)
+ let rowHeight: CGFloat = 30
+ superview.setFrameSize(NSSize(width: superview.frame.width, height: height))
+
+ let view: NSView = NSView(frame: NSRect(x: Constants.Settings.margin, y: Constants.Settings.margin, width: superview.frame.width - (Constants.Settings.margin*2), height: superview.frame.height - (Constants.Settings.margin*2)))
+
+ view.addSubview(SelectTitleRow(
+ frame: NSRect(x: 0, y: rowHeight + Constants.Settings.margin, width: view.frame.width, height: rowHeight),
+ title: "Pictogram",
+ action: #selector(toggleIcon),
+ items: network_icon_t.allCases.map{ return $0.rawValue },
+ selected: self.icon.rawValue
+ ))
+
+ view.addSubview(ToggleTitleRow(
+ frame: NSRect(x: 0, y: 0, width: view.frame.width, height: rowHeight),
+ title: "Value",
+ action: #selector(toggleValue),
+ state: self.valueState
+ ))
+
+ superview.addSubview(view)
+ }
+
+ @objc private func toggleValue(_ sender: NSControl) {
+ var state: NSControl.StateValue? = nil
+ if #available(OSX 10.15, *) {
+ state = sender is NSSwitch ? (sender as! NSSwitch).state: nil
+ } else {
+ state = sender is NSButton ? (sender as! NSButton).state: nil
+ }
+ self.valueState = state! == .on ? true : false
+ self.store?.pointee.set(key: "\(self.title)_\(self.type.rawValue)_value", value: self.valueState)
+ self.display()
+ }
+
+ @objc private func toggleIcon(_ sender: NSMenuItem) {
+ let newIcon: network_icon_t = network_icon_t(rawValue: sender.title) ?? .no
+ self.icon = newIcon
+ self.store?.pointee.set(key: "\(self.title)_\(self.type.rawValue)_icon", value: self.icon.rawValue)
+ self.display()
+ }
+
+ public func setValue(upload: Int64, download: Int64) {
+ var updated: Bool = false
+
+ if self.downloadValue != download {
+ self.downloadValue = download
+ updated = true
+ }
+ if self.uploadValue != upload {
+ self.uploadValue = upload
+ updated = true
+ }
+
+ if updated {
+ DispatchQueue.main.async(execute: {
+ self.display()
+ })
+ }
+ }
+}
diff --git a/ModuleKit/module.swift b/ModuleKit/module.swift
new file mode 100644
index 00000000..85114264
--- /dev/null
+++ b/ModuleKit/module.swift
@@ -0,0 +1,287 @@
+//
+// module.swift
+// ModuleKit
+//
+// Created by Serhiy Mytrovtsiy on 09/04/2020.
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+import os.log
+import StatsKit
+
+public protocol Module_p {
+ var available: Bool { get }
+ var enabled: Bool { get }
+
+ var widget: Widget_p? { get }
+ var settings: Settings_p? { get }
+
+ func load()
+ func terminate()
+}
+
+public struct module_c {
+ public var name: String = ""
+ public var icon: NSImage? = nil
+
+ var defaultState: Bool = false
+ var defaultWidget: widget_t = .unknown
+ var availableWidgets: [widget_t] = []
+
+ var widgetsConfig: NSDictionary = NSDictionary()
+
+ init(in path: String) {
+ let dict: NSDictionary = NSDictionary(contentsOfFile: path)!
+
+ if let name = dict["Name"] as? String {
+ self.name = name
+ }
+ if let state = dict["State"] as? Bool {
+ self.defaultState = state
+ }
+
+ if let widgetsDict = dict["Widgets"] as? NSDictionary {
+ self.widgetsConfig = widgetsDict
+ for widgetName in widgetsDict.allKeys {
+ if let widget = widget_t(rawValue: widgetName as! String) {
+ self.availableWidgets.append(widget)
+
+ let widgetDict = widgetsDict[widgetName as! String] as! NSDictionary
+ if widgetDict["Default"] as! Bool {
+ self.defaultWidget = widget
+ }
+ }
+ }
+ }
+ }
+}
+
+open class Module: Module_p {
+ public var config: module_c
+
+ public var available: Bool = false
+ public var enabled: Bool = false
+
+ public var widget: Widget_p? = nil
+ public var settings: Settings_p? = nil
+
+ private var settingsView: Settings_v? = nil
+ private var popup: NSWindow = NSWindow()
+
+ private let log: OSLog
+ private var store: UnsafePointer? = nil
+ private var readers: [Reader_p] = []
+ private var menuBarItem: NSStatusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
+ private var activeWidget: widget_t {
+ get {
+ let widgetStr = self.store?.pointee.string(key: "\(self.config.name)_widget", defaultValue: self.config.defaultWidget.rawValue)
+ return widget_t.allCases.first{ $0.rawValue == widgetStr } ?? widget_t.unknown
+ }
+ set {}
+ }
+ private var ready: Bool = false
+ private var widgetLoaded: Bool = false
+
+ public init(store: UnsafePointer?, popup: NSView?, settings: Settings_v?) {
+ self.config = module_c(in: Bundle(for: type(of: self)).path(forResource: "config", ofType: "plist")!)
+
+ self.log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: self.config.name)
+ self.store = store
+ self.settingsView = settings
+ self.available = self.isAvailable()
+ self.enabled = self.store?.pointee.bool(key: "\(self.config.name)_state", defaultValue: self.config.defaultState) ?? false
+ self.menuBarItem.isVisible = self.enabled
+ self.menuBarItem.autosaveName = self.config.name
+
+ NotificationCenter.default.addObserver(self, selector: #selector(listenForWidgetSwitch), name: .switchWidget, object: nil)
+ NotificationCenter.default.addObserver(self, selector: #selector(listenForMouseDownInSettings), name: .clickInSettings, object: nil)
+
+ if self.config.widgetsConfig.count != 0 {
+ self.setWidget()
+ } else {
+ os_log(.debug, log: log, "Module started without widget")
+ }
+
+ self.settings = Settings(config: &self.config, enabled: self.enabled, activeWidget: self.widget, moduleSettings: { [weak self] (_ superview: NSView) in
+ if self != nil && self?.settingsView != nil {
+ self!.settingsView!.load(rect: superview.frame, widget: self!.activeWidget)
+ superview.setFrameSize(NSSize(width: superview.frame.width, height: self!.settingsView!.frame.height))
+ superview.addSubview(self!.settingsView!)
+ }
+ })
+ self.settings?.toggleCallback = { [weak self] in
+ self?.toggleEnabled()
+ }
+
+ self.popup = PopupWindow(title: self.config.name, view: popup)
+ }
+
+ // load function which call when app start
+ public func load() {
+ if self.enabled && self.widget != nil && self.ready {
+ DispatchQueue.main.async {
+ self.menuBarItem.button?.target = self
+ self.menuBarItem.button?.action = #selector(self.togglePopup)
+ self.menuBarItem.button?.sendAction(on: [.leftMouseDown, .rightMouseDown])
+
+ self.menuBarItem.length = self.widget!.frame.width
+ self.menuBarItem.button?.addSubview(self.widget!)
+ self.widgetLoaded = true
+ }
+ }
+ }
+
+ // terminate function which call before app termination
+ public func terminate() {
+ self.willTerminate()
+ self.readers.forEach{
+ $0.stop()
+ $0.terminate()
+ }
+ NSStatusBar.system.removeStatusItem(self.menuBarItem)
+ os_log(.debug, log: log, "Module terminated")
+ }
+
+ // function to call before module terminate
+ open func willTerminate() {}
+
+ // set module state to enabled
+ public func enable() {
+ self.enabled = true
+ self.store?.pointee.set(key: "\(self.config.name)_state", value: true)
+ self.readers.forEach{ $0.start() }
+ self.menuBarItem.isVisible = true
+ if self.menuBarItem.length < 0 {
+ self.load()
+ }
+ os_log(.debug, log: log, "Module enabled")
+ }
+
+ // set module state to disabled
+ public func disable() {
+ self.enabled = false
+ self.store?.pointee.set(key: "\(self.config.name)_state", value: false)
+ self.readers.forEach{ $0.pause() }
+ self.menuBarItem.isVisible = false
+ self.popup.setIsVisible(false)
+ os_log(.debug, log: log, "Module disabled")
+ }
+
+ // toggle module state
+ private func toggleEnabled() {
+ if self.enabled {
+ self.disable()
+ } else {
+ self.enable()
+ }
+ }
+
+ // add reader to module. If module is enabled will fire a read function and start a reader
+ public func addReader(_ reader: Reader_p) {
+ if self.enabled {
+ reader.start()
+ }
+ self.readers.append(reader)
+
+ os_log(.debug, log: log, "Successfully add reader %s", "\(reader.self)")
+ }
+
+ // handler for reader, calls when main reader is ready, and return first value
+ public func readyHandler() {
+ os_log(.debug, log: log, "Reader report readiness")
+ self.ready = true
+ if !self.widgetLoaded {
+ self.load()
+ }
+ }
+
+ // change menu item width
+ public func widgetWidthHandler(_ width: CGFloat) {
+ os_log(.debug, log: log, "Widget %s change width to %.2f", "\(type(of: self.widget!))", width)
+ self.menuBarItem.length = width
+ }
+
+ // determine if module is available (can be overrided in module)
+ open func isAvailable() -> Bool { return true }
+
+ // load and setup widget
+ private func setWidget() {
+ self.widget = LoadWidget(self.activeWidget, preview: false, title: self.config.name, config: self.config.widgetsConfig, store: self.store)
+ if self.widget == nil {
+ self.enabled = false
+ os_log(.error, log: log, "widget with type %s not found", "\(self.activeWidget)")
+ return
+ }
+ os_log(.debug, log: log, "Successfully initialize widget: %s", "\(String(describing: self.widget!))")
+
+ self.widget?.widthHandler = { [weak self] value in
+ self?.widgetWidthHandler(value)
+ }
+
+ self.readers.forEach{ $0.read() }
+ if let mainReader = self.readers.first(where: { !$0.optional }) {
+ self.widget?.setValues(mainReader.getHistory())
+ }
+
+ if self.ready {
+ self.menuBarItem.length = self.widget!.frame.width
+ self.menuBarItem.button?.subviews.forEach{ $0.removeFromSuperview() }
+ self.menuBarItem.button?.addSubview(self.widget!)
+ }
+
+ self.settings?.setActiveWidget(self.widget)
+ }
+
+ @objc private func togglePopup(_ sender: Any?) {
+ let openedWindows = NSApplication.shared.windows.filter{ $0 is NSPanel }
+ openedWindows.forEach{ $0.setIsVisible(false) }
+
+ if self.popup.occlusionState.rawValue == 8192 {
+ NSApplication.shared.activate(ignoringOtherApps: true)
+
+ let buttonOrigin = self.menuBarItem.button?.window?.frame.origin
+ let buttonCenter = (self.menuBarItem.button?.window?.frame.width)! / 2
+ let windowCenter = self.popup.frame.width / 2
+
+ self.popup.contentView?.invalidateIntrinsicContentSize()
+ var x = buttonOrigin!.x - windowCenter + buttonCenter
+ let y = buttonOrigin!.y - self.popup.contentView!.intrinsicContentSize.height - 3
+
+ if let screen = NSScreen.main {
+ let width = screen.frame.size.width
+
+ if x + self.popup.frame.width > width {
+ x = width - self.popup.frame.width
+ }
+ }
+ if buttonOrigin!.x - self.popup.frame.width < 0 {
+ x = 0
+ }
+
+ self.popup.setFrameOrigin(NSPoint(x: x, y: y))
+ self.popup.setIsVisible(true)
+ }
+ }
+
+ @objc private func listenForWidgetSwitch(_ notification: Notification) {
+ if let moduleName = notification.userInfo?["module"] as? String {
+ if let widgetName = notification.userInfo?["widget"] as? String {
+ if moduleName == self.config.name {
+ if let widgetType = widget_t.allCases.first(where: { $0.rawValue == widgetName }) {
+ self.activeWidget = widgetType
+ self.store?.pointee.set(key: "\(self.config.name)_widget", value: widgetType.rawValue)
+ self.setWidget()
+ os_log(.debug, log: log, "Widget is changed to: %s", "\(widgetName)")
+ }
+ }
+ }
+ }
+ }
+
+ @objc private func listenForMouseDownInSettings(_ notification: Notification) {
+ if self.popup.isVisible {
+ self.popup.setIsVisible(false)
+ }
+ }
+}
diff --git a/ModuleKit/popup.swift b/ModuleKit/popup.swift
new file mode 100644
index 00000000..3c1c2714
--- /dev/null
+++ b/ModuleKit/popup.swift
@@ -0,0 +1,220 @@
+//
+// popup.swift
+// ModuleKit
+//
+// Created by Serhiy Mytrovtsiy on 11/04/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+import StatsKit
+
+internal class PopupWindow: NSPanel, NSWindowDelegate {
+ private let viewController: PopupViewController = PopupViewController()
+
+ init(title: String, view: NSView?) {
+ self.viewController.setup(title: title, view: view)
+
+ super.init(
+ contentRect: NSMakeRect(0, 0, self.viewController.view.frame.width, self.viewController.view.frame.height),
+ styleMask: [],
+ backing: .buffered,
+ defer: true
+ )
+
+ self.contentViewController = viewController
+ self.backingType = .buffered
+ self.isFloatingPanel = true
+ self.becomesKeyOnlyIfNeeded = true
+ self.styleMask = .borderless
+ self.animationBehavior = .default
+ self.collectionBehavior = .transient
+ self.backgroundColor = .clear
+ self.hasShadow = true
+ self.setIsVisible(false)
+ }
+}
+
+internal class PopupViewController: NSViewController {
+ private var popup: PopupView
+
+ public init() {
+ self.popup = PopupView(frame: NSRect(x: 0, y: 0, width: Constants.Popup.width + (Constants.Popup.margins * 2), height: Constants.Popup.height+Constants.Popup.headerHeight))
+ super.init(nibName: nil, bundle: nil)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func loadView() {
+ self.view = self.popup
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ }
+
+ override func viewWillAppear() {
+ self.popup.appear()
+ }
+
+ override func viewWillDisappear() {
+ self.popup.disappear()
+ }
+
+ public func setup(title: String, view: NSView?) {
+ self.title = title
+ self.popup.headerView?.titleView?.stringValue = title
+ self.popup.setView(view)
+ }
+}
+
+internal class PopupView: NSView {
+ public var headerView: HeaderView? = nil
+ private var mainView: NSView? = nil
+
+ override var intrinsicContentSize: CGSize {
+ var h: CGFloat = self.mainView?.subviews.first?.frame.height ?? 0
+ if h != 0 {
+ h += Constants.Popup.margins*2
+ }
+ return CGSize(width: self.frame.size.width, height: h + Constants.Popup.headerHeight)
+ }
+
+ override init(frame: NSRect) {
+ super.init(frame: CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.width, height: frame.height))
+ self.wantsLayer = true
+ self.canDrawConcurrently = true
+ self.layer!.cornerRadius = 3
+
+ self.headerView = HeaderView(frame: NSRect(x: 0, y: frame.height - Constants.Popup.headerHeight, width: frame.width, height: Constants.Popup.headerHeight))
+
+ let mainView: NSView = NSView(frame: NSRect(x: Constants.Popup.margins, y: Constants.Popup.margins, width: frame.width - (Constants.Popup.margins*2), height: 0))
+
+ self.addSubview(self.headerView!)
+ self.addSubview(mainView)
+
+ self.mainView = mainView
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func updateLayer() {
+ if self.mainView!.subviews.count != 0 {
+ if self.mainView?.frame.height != self.mainView!.subviews.first!.frame.size.height {
+ self.setHeight(self.mainView!.subviews.first!.frame.size)
+ }
+ }
+ self.layer!.backgroundColor = self.isDarkMode ? NSColor.windowBackgroundColor.cgColor : NSColor.white.cgColor
+ }
+
+ public func setView(_ view: NSView?) {
+ if view == nil {
+ self.setFrameSize(NSSize(width: Constants.Popup.width+(Constants.Popup.margins*2), height: Constants.Popup.headerHeight))
+ self.headerView?.setFrameOrigin(NSPoint(x: 0, y: 0))
+ return
+ }
+
+ self.mainView?.addSubview(view!)
+ self.setHeight(view!.frame.size)
+ }
+
+ private func setHeight(_ size: CGSize) {
+ DispatchQueue.main.async(execute: {
+ self.mainView?.setFrameSize(NSSize(width: self.mainView!.frame.width, height: size.height))
+ self.setFrameSize(NSSize(width: size.width + (Constants.Popup.margins*2), height: size.height + Constants.Popup.headerHeight + Constants.Popup.margins*2))
+ self.headerView?.setFrameOrigin(NSPoint(x: 0, y: self.frame.height - Constants.Popup.headerHeight))
+
+ var frame = self.window?.frame
+ frame?.size = self.frame.size
+ self.window?.setFrame(frame!, display: true)
+ })
+ }
+
+ internal func appear() {
+ self.display()
+ self.mainView?.subviews.first{ !($0 is HeaderView) }?.display()
+ }
+ internal func disappear() {}
+}
+
+internal class HeaderView: NSView {
+ public var titleView: NSTextField? = nil
+
+ private var settingsButton: NSButton?
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override init(frame: NSRect) {
+ super.init(frame: CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.width, height: frame.height))
+
+ let titleView = NSTextField(frame: NSMakeRect(frame.width/4, (frame.height - 18)/2, frame.width/2, 18))
+ titleView.isEditable = false
+ titleView.isSelectable = false
+ titleView.isBezeled = false
+ titleView.wantsLayer = true
+ titleView.textColor = .labelColor
+ titleView.backgroundColor = .clear
+ titleView.canDrawSubviewsIntoLayer = true
+ titleView.alignment = .center
+ titleView.font = NSFont.systemFont(ofSize: 16, weight: .medium)
+ titleView.stringValue = ""
+
+ self.titleView = titleView
+ self.addSubview(titleView)
+
+ let button = NSButtonWithPadding()
+ button.frame = CGRect(x: frame.width - 38, y: 2, width: 30, height: 30)
+ button.verticalPadding = 14
+ button.horizontalPadding = 14
+ button.bezelStyle = .regularSquare
+ button.translatesAutoresizingMaskIntoConstraints = false
+ button.imageScaling = .scaleNone
+ button.image = Bundle(for: type(of: self)).image(forResource: "settings")!
+ button.contentTintColor = .lightGray
+ button.isBordered = false
+ button.action = #selector(openMenu)
+ button.target = self
+
+ let trackingArea = NSTrackingArea(rect: button.frame, options: [NSTrackingArea.Options.activeAlways, NSTrackingArea.Options.mouseEnteredAndExited, NSTrackingArea.Options.activeInActiveApp], owner: self, userInfo: nil)
+ self.addTrackingArea(trackingArea)
+
+ self.addSubview(button)
+
+ self.settingsButton = button
+ }
+
+ override func draw(_ dirtyRect: NSRect) {
+ super.draw(dirtyRect)
+
+ NSColor.gridColor.set()
+ let line = NSBezierPath()
+ line.move(to: NSMakePoint(0, 0))
+ line.line(to: NSMakePoint(self.frame.width, 0))
+ line.lineWidth = 1
+ line.stroke()
+ }
+
+ override func mouseEntered(with: NSEvent) {
+ self.settingsButton!.contentTintColor = .gray
+ NSCursor.pointingHand.set()
+ }
+
+ override func mouseExited(with: NSEvent) {
+ self.settingsButton!.contentTintColor = .lightGray
+ NSCursor.arrow.set()
+ }
+
+ @objc func openMenu(_ sender: Any) {
+ self.window?.setIsVisible(false)
+ NotificationCenter.default.post(name: .toggleSettings, object: nil, userInfo: ["module": self.titleView?.stringValue ?? ""])
+ }
+}
diff --git a/ModuleKit/reader.swift b/ModuleKit/reader.swift
new file mode 100644
index 00000000..6fdd8d25
--- /dev/null
+++ b/ModuleKit/reader.swift
@@ -0,0 +1,129 @@
+//
+// reader.swift
+// ModuleKit
+//
+// Created by Serhiy Mytrovtsiy on 10/04/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+import Repeat
+import os.log
+
+public protocol value_t {
+ var widget_value: Double { get }
+}
+
+public protocol Reader_p {
+ var optional: Bool { get }
+
+ func setup() -> Void
+ func read() -> Void
+ func terminate() -> Void
+
+ func getValue() -> T
+ func getHistory() -> [value_t]
+
+ func start() -> Void
+ func pause() -> Void
+ func stop() -> Void
+}
+
+public protocol ReaderInternal_p {
+ associatedtype T
+
+ var value: T? { get }
+ func read() -> Void
+}
+
+open class Reader: ReaderInternal_p {
+ public let log: OSLog
+ public var value: T?
+ public var interval: Int = 1000
+ public var optional: Bool = false
+
+ public var readyCallback: () -> Void = {}
+ public var callbackHandler: (T?) -> Void = {_ in }
+
+ private var repeatTask: Repeater?
+ private var nilCallbackCounter: Int = 0
+ private var ready: Bool = false
+
+ private var history: [T]? = []
+
+ public init() {
+ self.log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "\(T.self)")
+
+ self.setup()
+
+ self.repeatTask = Repeater.init(interval: .milliseconds(self.interval), observer: { _ in
+ self.read()
+ })
+
+ os_log(.debug, log: self.log, "Successfully initialize reader")
+ }
+
+ public func callback(_ value: T?) {
+ if !self.optional && !self.ready {
+ if self.value == nil && value != nil {
+ self.readyCallback()
+ } else if self.value == nil && value == nil {
+ if self.nilCallbackCounter > 5 {
+ os_log(.error, log: self.log, "Callback receive nil value more than 5 times. Please check this reader!")
+ self.stop()
+ return
+ } else {
+ os_log(.debug, log: self.log, "Restarting initial read")
+ self.nilCallbackCounter += 1
+ self.read()
+ return
+ }
+ } else if self.nilCallbackCounter != 0 && value != nil {
+ self.nilCallbackCounter = 0
+ }
+ }
+
+ self.value = value
+ if !self.ready {
+ self.ready = true
+ os_log(.debug, log: self.log, "Reader is ready")
+ }
+ if value != nil {
+ if self.history?.count ?? 0 >= 300 {
+ self.history!.remove(at: 0)
+ }
+ self.history?.append(value!)
+ self.callbackHandler(value!)
+ }
+ }
+
+ open func read() {}
+ open func setup() {}
+ open func terminate() {}
+
+ open func start() {
+ self.read()
+ self.repeatTask!.start()
+ }
+
+ open func pause() {
+ self.repeatTask!.pause()
+ }
+
+ open func stop() {
+ self.repeatTask!.removeAllObservers(thenStop: true)
+ }
+}
+
+extension Reader: Reader_p {
+ public func getValue() -> T {
+ return self.value as! T
+ }
+
+ public func getHistory() -> [T] {
+ return self.history as! [T]
+ }
+}
diff --git a/ModuleKit/settings.swift b/ModuleKit/settings.swift
new file mode 100644
index 00000000..650ae013
--- /dev/null
+++ b/ModuleKit/settings.swift
@@ -0,0 +1,268 @@
+//
+// settings.swift
+// ModuleKit
+//
+// Created by Serhiy Mytrovtsiy on 13/04/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+
+public protocol Settings_p: NSView {
+ var toggleCallback: () -> () { get set }
+ func setActiveWidget(_ widget: Widget_p?)
+}
+
+public protocol Settings_v: NSView {
+ func load(rect: NSRect, widget: widget_t)
+}
+
+open class Settings: NSView, Settings_p {
+ public var toggleCallback: () -> () = {}
+
+ private let headerHeight: CGFloat = 42
+ private var widgetSelectorHeight: CGFloat = Constants.Widget.height + (Constants.Settings.margin*2)
+
+ private var widgetSelectorView: NSView? = nil
+ private var widgetSettingsView: NSView? = nil
+ private var moduleSettingsView: NSView? = nil
+
+ private var config: UnsafePointer
+ private var activeWidget: Widget_p?
+
+ private var moduleSettings: (_ superview: NSView) -> ()
+
+ init(config: UnsafePointer, enabled: Bool, activeWidget: Widget_p?, moduleSettings: @escaping (_ superview: NSView) -> ()) {
+ self.config = config
+ self.activeWidget = activeWidget
+ self.moduleSettings = moduleSettings
+ super.init(frame: NSRect(x: 0, y: 0, width: Constants.Settings.width, height: Constants.Settings.height))
+ self.wantsLayer = true
+ self.layer?.backgroundColor = NSColor.windowBackgroundColor.cgColor
+
+ addHeader(state: enabled)
+ addWidgetSelector()
+ addWidgetSettings()
+ addModuleSettings()
+ }
+
+ private func addModuleSettings() {
+ let y: CGFloat = self.frame.height - headerHeight - widgetSelectorHeight - (self.widgetSettingsView?.frame.height ?? 0)
+ let view: NSView = NSView(frame: NSRect(x: Constants.Settings.margin, y: y - (Constants.Settings.margin*3), width: self.frame.width - (Constants.Settings.margin*2), height: 0))
+ view.wantsLayer = true
+ view.layer?.backgroundColor = .white
+ view.layer!.cornerRadius = 3
+
+ self.appearance = NSAppearance(named: .aqua)
+
+ self.moduleSettings(view)
+
+ if view.frame.height != 0 {
+ view.setFrameOrigin(NSPoint(x: view.frame.origin.x, y: view.frame.origin.y - view.frame.height))
+ self.addSubview(view)
+ self.moduleSettingsView = view
+ }
+ }
+
+ private func addWidgetSettings() {
+ if self.activeWidget == nil {
+ return
+ }
+
+ let view: NSView = NSView(frame: NSRect(x: Constants.Settings.margin, y: self.frame.height - headerHeight - widgetSelectorHeight - (Constants.Settings.margin*2), width: self.frame.width - (Constants.Settings.margin*2), height: 0))
+ view.wantsLayer = true
+ view.layer?.backgroundColor = .white
+ view.layer!.cornerRadius = 3
+
+ self.activeWidget?.settings(superview: view)
+
+ if view.frame.height != 0 {
+ view.setFrameOrigin(NSPoint(x: view.frame.origin.x, y: view.frame.origin.y - view.frame.height))
+ self.addSubview(view)
+ self.widgetSettingsView = view
+ }
+ }
+
+ private func addWidgetSelector() {
+ if self.config.pointee.availableWidgets.count == 0 {
+ self.widgetSelectorHeight = 0
+ return
+ }
+
+ let view: NSView = NSView(frame: NSRect(x: Constants.Settings.margin, y: self.frame.height - self.headerHeight - self.widgetSelectorHeight - Constants.Settings.margin, width: self.frame.width - (Constants.Settings.margin*2), height: self.widgetSelectorHeight))
+ view.wantsLayer = true
+ view.layer?.backgroundColor = .white
+ view.layer!.cornerRadius = 3
+
+ var x: CGFloat = Constants.Settings.margin
+ for i in 0...self.config.pointee.availableWidgets.count - 1 {
+ let widgetType = self.config.pointee.availableWidgets[i]
+ if let widget = LoadWidget(widgetType, preview: true, title: self.config.pointee.name, config: self.config.pointee.widgetsConfig, store: nil) {
+ let preview = WidgetPreview(
+ frame: NSRect(x: x, y: Constants.Settings.margin, width: widget.frame.width, height: self.widgetSelectorHeight - (Constants.Settings.margin*2)),
+ title: self.config.pointee.name,
+ widget: widget,
+ state: self.activeWidget?.type == widgetType
+ )
+ preview.widthCallback = { [weak self] in
+ self?.recalculateWidgetSelectorOptionsWidth()
+ }
+ view.addSubview(preview)
+ x += widget.frame.width + Constants.Settings.margin
+ }
+ }
+
+ self.addSubview(view)
+ self.widgetSelectorView = view
+ }
+
+ private func recalculateWidgetSelectorOptionsWidth() {
+ var x: CGFloat = Constants.Settings.margin
+ self.widgetSelectorView?.subviews.forEach({ (v: NSView) in
+ v.setFrameOrigin(NSPoint(x: x, y: v.frame.origin.y))
+ x += v.frame.width + Constants.Settings.margin
+ })
+ }
+
+ private func addHeader(state: Bool) {
+ let view: NSView = NSView(frame: NSRect(x: 0, y: self.frame.height - self.headerHeight, width: self.frame.width, height: self.headerHeight))
+ view.wantsLayer = true
+
+ let titleView = NSTextField(frame: NSRect(x: Constants.Settings.margin, y: (view.frame.height-20)/2, width: self.frame.width - 65, height: 20))
+ titleView.isEditable = false
+ titleView.isSelectable = false
+ titleView.isBezeled = false
+ titleView.wantsLayer = true
+ titleView.textColor = .black
+ titleView.backgroundColor = .clear
+ titleView.canDrawSubviewsIntoLayer = true
+ titleView.alignment = .natural
+ titleView.font = NSFont.systemFont(ofSize: 18, weight: .light)
+ titleView.stringValue = self.config.pointee.name
+
+ var toggle: NSControl = NSControl()
+ if #available(OSX 10.15, *) {
+ let switchButton = NSSwitch(frame: NSRect(x: self.frame.width-55, y: 0, width: 50, height: view.frame.height))
+ switchButton.state = state ? .on : .off
+ switchButton.action = #selector(self.toggleEnable)
+ switchButton.target = self
+
+ toggle = switchButton
+ } else {
+ let button: NSButton = NSButton(frame: NSRect(x: self.frame.width-55, y: 0, width: 30, height: view.frame.height))
+ button.setButtonType(.switch)
+ button.state = state ? .on : .off
+ button.title = ""
+ button.action = #selector(self.toggleEnable)
+ button.isBordered = false
+ button.isTransparent = true
+
+ toggle = button
+ }
+
+ let line: NSView = NSView(frame: NSRect(x: 0, y: 0, width: view.frame.width, height: 1))
+ line.wantsLayer = true
+ line.layer?.backgroundColor = NSColor(hexString: "#d1d1d1").cgColor
+
+ view.addSubview(titleView)
+ view.addSubview(toggle)
+ view.addSubview(line)
+
+ self.addSubview(view)
+ }
+
+ @objc func toggleEnable(_ sender: Any) {
+ self.toggleCallback()
+ }
+
+ public func setActiveWidget(_ widget: Widget_p?) {
+ self.activeWidget = widget
+
+ self.subviews.filter{ $0 == self.widgetSettingsView || $0 == self.moduleSettingsView }.forEach{ $0.removeFromSuperview() }
+ self.addWidgetSettings()
+ self.addModuleSettings()
+ }
+
+ required public init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+}
+
+class WidgetPreview: NSView {
+ private let type: widget_t
+ private var state: Bool
+ private let title: String
+
+ public var widthCallback: () -> Void = {}
+
+ public init(frame: NSRect, title: String, widget: Widget_p, state: Bool) {
+ self.type = widget.type
+ self.state = state
+ self.title = title
+ super.init(frame: frame)
+
+ NotificationCenter.default.addObserver(self, selector: #selector(maybeActivate), name: .switchWidget, object: nil)
+
+ self.wantsLayer = true
+ self.layer?.cornerRadius = 2
+ self.layer?.borderColor = self.state ? NSColor.systemBlue.cgColor : NSColor(hexString: "#dddddd").cgColor
+ self.layer?.borderWidth = 1
+
+ widget.widthHandler = { [weak self] value in
+ self?.removeTrackingArea((self?.trackingAreas.first)!)
+
+ let rect = NSRect(x: 0, y: 0, width: value, height: self!.frame.height)
+ let trackingArea = NSTrackingArea(rect: rect, options: [NSTrackingArea.Options.activeAlways, NSTrackingArea.Options.mouseEnteredAndExited, NSTrackingArea.Options.activeInActiveApp], owner: self, userInfo: ["menu": self!.type])
+ self?.addTrackingArea(trackingArea)
+
+ DispatchQueue.main.async(execute: {
+ self?.setFrameSize(NSSize(width: value, height: self?.frame.height ?? Constants.Widget.height))
+ self?.widthCallback()
+ })
+ }
+ self.addSubview(widget)
+
+ let rect = NSRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height)
+ let trackingArea = NSTrackingArea(rect: rect, options: [NSTrackingArea.Options.activeAlways, NSTrackingArea.Options.mouseEnteredAndExited, NSTrackingArea.Options.activeInActiveApp], owner: self, userInfo: ["menu": self.type])
+ self.addTrackingArea(trackingArea)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func mouseEntered(with: NSEvent) {
+ self.layer?.borderColor = NSColor.systemBlue.cgColor
+ NSCursor.pointingHand.set()
+ }
+
+ override func mouseExited(with: NSEvent) {
+ self.layer?.borderColor = self.state ? NSColor.systemBlue.cgColor : NSColor.tertiaryLabelColor.cgColor
+ NSCursor.arrow.set()
+ }
+
+ override func mouseDown(with: NSEvent) {
+ if !self.state {
+ NotificationCenter.default.post(name: .switchWidget, object: nil, userInfo: ["module": self.title, "widget": self.type.rawValue])
+ }
+ }
+
+ @objc private func maybeActivate(_ notification: Notification) {
+ if let moduleName = notification.userInfo?["module"] as? String {
+ if moduleName == self.title {
+ if let widgetName = notification.userInfo?["widget"] as? String {
+ if widgetName == self.type.rawValue {
+ self.layer?.borderColor = NSColor.systemBlue.cgColor
+ self.state = true
+ } else {
+ self.layer?.borderColor = NSColor.tertiaryLabelColor.cgColor
+ self.state = false
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/ModuleKit/widget.swift b/ModuleKit/widget.swift
new file mode 100644
index 00000000..0d8b5a46
--- /dev/null
+++ b/ModuleKit/widget.swift
@@ -0,0 +1,97 @@
+//
+// widget.swift
+// ModuleKit
+//
+// Created by Serhiy Mytrovtsiy on 10/04/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+import StatsKit
+
+public enum widget_t: String {
+ case unknown = ""
+ case mini = "mini"
+ case lineChart = "line_chart"
+ case barChart = "bar_chart"
+ case network = "network"
+ case battery = "battery"
+}
+extension widget_t: CaseIterable {}
+
+public protocol Widget_p: NSView {
+ var title: String { get }
+ var preview: Bool { get }
+ var type: widget_t { get }
+ var widthHandler: ((CGFloat) -> Void)? { get set }
+
+ func setValues(_ values: [value_t])
+ func settings(superview: NSView)
+}
+
+open class Widget: NSView, Widget_p {
+ public var widthHandler: ((CGFloat) -> Void)? = nil
+ public var title: String = ""
+ public var preview: Bool = false
+ public var type: widget_t = .unknown
+
+ private var widthHandlerRetry: Int8 = 0
+
+ open override var intrinsicContentSize: CGSize {
+ return CGSize(width: self.frame.size.width, height: self.frame.size.height)
+ }
+
+ public func setWidth(_ width: CGFloat) {
+ if self.frame.width == width || self.widthHandlerRetry >= 3 {
+ return
+ }
+
+ if self.widthHandler == nil {
+ DispatchQueue.main.asyncAfter(deadline: .now() + .microseconds(10)) {
+ self.setWidth(width)
+ self.widthHandlerRetry += 1
+ }
+ return
+ }
+
+ DispatchQueue.main.async {
+ self.setFrameSize(NSSize(width: width, height: self.frame.size.height))
+ self.invalidateIntrinsicContentSize()
+ self.display()
+ }
+
+ self.widthHandler!(width)
+ }
+
+ open func settings(superview: NSView) {}
+ open func setValues(_ values: [value_t]) {}
+}
+
+func LoadWidget(_ type: widget_t, preview: Bool, title: String, config: NSDictionary?, store: UnsafePointer?) -> Widget_p? {
+ var widget: Widget_p? = nil
+ let widgetConfig: NSDictionary? = config?[type.rawValue] as? NSDictionary
+
+ switch type {
+ case .mini:
+ widget = Mini(preview: preview, title: title, config: widgetConfig, store: store)
+ break
+ case .lineChart:
+ widget = LineChart(preview: preview, title: title, config: widgetConfig, store: store)
+ break
+ case .barChart:
+ widget = BarChart(preview: preview, title: title, config: widgetConfig, store: store)
+ break
+ case .network:
+ widget = NetworkWidget(preview: preview, title: title, config: widgetConfig, store: store)
+ break
+ case .battery:
+ widget = BatterykWidget(preview: preview, title: title, config: widgetConfig, store: store)
+ break
+ default: break
+ }
+
+ return widget
+}
diff --git a/Modules/Battery/Info.plist b/Modules/Battery/Info.plist
new file mode 100644
index 00000000..20202aa7
--- /dev/null
+++ b/Modules/Battery/Info.plist
@@ -0,0 +1,24 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+ NSHumanReadableCopyright
+ Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+
+
diff --git a/Modules/Battery/config.plist b/Modules/Battery/config.plist
new file mode 100644
index 00000000..35e67ae5
--- /dev/null
+++ b/Modules/Battery/config.plist
@@ -0,0 +1,36 @@
+
+
+
+
+ Name
+ Battery
+ State
+
+ Widgets
+
+ mini
+
+ Default
+
+ Label
+
+ Title
+ BAT
+ Preview
+
+ Label
+
+ Title
+ BAT
+ Value
+ 0.72
+
+
+ battery
+
+ Default
+
+
+
+
+
diff --git a/Modules/Battery/main.swift b/Modules/Battery/main.swift
new file mode 100644
index 00000000..41ac1b34
--- /dev/null
+++ b/Modules/Battery/main.swift
@@ -0,0 +1,86 @@
+//
+// main.swift
+// Battery
+//
+// Created by Serhiy Mytrovtsiy on 06/06/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+import StatsKit
+import ModuleKit
+import IOKit.ps
+
+struct Usage: value_t {
+ var powerSource: String = ""
+ var state: String = ""
+ var isCharged: Bool = false
+ var level: 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 = true
+
+ var timeToEmpty: Int = 0
+ var timeToCharge: Int = 0
+
+ public var widget_value: Double {
+ get {
+ return self.level
+ }
+ }
+}
+
+public class Battery: Module {
+ private var usageReader: UsageReader = UsageReader()
+ private let popupView: Popup = Popup()
+
+ public init(_ store: UnsafePointer?) {
+ super.init(
+ store: store,
+ popup: self.popupView,
+ settings: nil
+ )
+
+ self.usageReader.readyCallback = { [unowned self] in
+ self.readyHandler()
+ }
+ self.usageReader.callbackHandler = { [unowned self] value in
+ self.usageCallback(value)
+ }
+
+ self.addReader(self.usageReader)
+ }
+
+ public override func isAvailable() -> Bool {
+ let snapshot = IOPSCopyPowerSourcesInfo().takeRetainedValue()
+ let sources = IOPSCopyPowerSourcesList(snapshot).takeRetainedValue() as Array
+ return sources.count > 0
+ }
+
+ private func usageCallback(_ value: Usage?) {
+ if value == nil {
+ return
+ }
+
+ self.popupView.usageCallback(value!)
+ if let widget = self.widget as? Mini {
+ widget.setValue(value!.level, sufix: "%")
+ }
+ if let widget = self.widget as? BatterykWidget {
+ widget.setValue(
+ percentage: value?.level ?? 0,
+ isCharging: false,
+ time: (value?.timeToEmpty == 0 && value?.timeToCharge != 0 ? value?.timeToCharge : value?.timeToEmpty) ?? 0
+ )
+ }
+ }
+}
diff --git a/Modules/Battery/popup.swift b/Modules/Battery/popup.swift
new file mode 100644
index 00000000..3b3ac99f
--- /dev/null
+++ b/Modules/Battery/popup.swift
@@ -0,0 +1,229 @@
+//
+// popup.swift
+// Battery
+//
+// Created by Serhiy Mytrovtsiy on 06/06/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+import ModuleKit
+import StatsKit
+
+internal class Popup: NSView {
+ let dashboardHeight: CGFloat = 90
+ let detailsHeight: CGFloat = 88
+ let batteryHeight: CGFloat = 66
+ let adapterHeight: CGFloat = 44
+
+ private var dashboardView: NSView? = nil
+ private var dashboardBatteryView: BatteryView? = nil
+ private var detailsView: NSView? = nil
+ private var batteryView: NSView? = nil
+ private var adapterView: NSView? = nil
+
+ private var levelField: NSTextField? = nil
+ private var sourceField: NSTextField? = nil
+ private var timeLabelField: NSTextField? = nil
+ private var timeField: NSTextField? = nil
+ private var healthField: NSTextField? = nil
+
+ private var amperageField: NSTextField? = nil
+ private var voltageField: NSTextField? = nil
+ private var temperatureField: NSTextField? = nil
+
+ private var powerField: NSTextField? = nil
+ private var chargingStateField: NSTextField? = nil
+
+ private var initialized: Bool = false
+
+ public init() {
+ super.init(frame: NSRect(
+ x: 0,
+ y: 0,
+ width: Constants.Popup.width,
+ height: dashboardHeight + detailsHeight + batteryHeight + adapterHeight + (Constants.Popup.separatorHeight * 3)
+ ))
+
+ self.initDashboard()
+ self.initDetails()
+ self.initBattery()
+ self.initAdapter()
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ private func initDashboard() {
+ let view: NSView = NSView(frame: NSRect(x: 0, y: self.frame.height - self.dashboardHeight, width: self.frame.width, height: self.dashboardHeight))
+
+ let batteryView: BatteryView = BatteryView(frame: NSRect(x: Constants.Popup.margins, y: Constants.Popup.margins, width: view.frame.width - (Constants.Popup.margins*2), height: view.frame.height - (Constants.Popup.margins*2)))
+ view.addSubview(batteryView)
+
+ self.addSubview(view)
+ self.dashboardView = view
+ self.dashboardBatteryView = batteryView
+ }
+
+ private func initDetails() {
+ let y: CGFloat = self.dashboardView!.frame.origin.y - Constants.Popup.separatorHeight
+ let separator = SeparatorView("Details", origin: NSPoint(x: 0, y: y), width: self.frame.width)
+ self.addSubview(separator)
+
+ let view: NSView = NSView(frame: NSRect(x: 0, y: separator.frame.origin.y - self.detailsHeight, width: self.frame.width, height: self.detailsHeight))
+
+ self.levelField = PopupRow(view, n: 3, title: "Level:", value: "")
+ self.sourceField = PopupRow(view, n: 2, title: "Source:", value: "")
+ let t = self.labelValue(view, n: 1, title: "Time:", value: "")
+ self.timeLabelField = t.0
+ self.timeField = t.1
+ self.healthField = PopupRow(view, n: 0, title: "Health:", value: "")
+
+ self.addSubview(view)
+ self.detailsView = view
+ }
+
+ private func labelValue(_ view: NSView, n: CGFloat, title: String, value: String) -> (NSTextField, NSTextField) {
+ let rowView: NSView = NSView(frame: NSRect(x: 0, y: 22*n, width: view.frame.width, height: 22))
+
+ let labelView: LabelField = LabelField(frame: NSRect(x: 0, y: (22-15)/2, width: view.frame.width/2, height: 15), title)
+ let valueView: ValueField = ValueField(frame: NSRect(x: view.frame.width/2, y: (22-16)/2, width: view.frame.width/2, height: 16), value)
+
+ rowView.addSubview(labelView)
+ rowView.addSubview(valueView)
+ view.addSubview(rowView)
+
+ return (labelView, valueView)
+ }
+
+ private func initBattery() {
+ let y: CGFloat = self.detailsView!.frame.origin.y - Constants.Popup.separatorHeight
+ let separator = SeparatorView("Battery", origin: NSPoint(x: 0, y: y), width: self.frame.width)
+ self.addSubview(separator)
+
+ let view: NSView = NSView(frame: NSRect(x: 0, y: separator.frame.origin.y - self.batteryHeight, width: self.frame.width, height: self.batteryHeight))
+
+ self.amperageField = PopupRow(view, n: 2, title: "Amperage:", value: "")
+ self.voltageField = PopupRow(view, n: 1, title: "Voltage:", value: "")
+ self.temperatureField = PopupRow(view, n: 0, title: "Temperatrure:", value: "")
+
+ self.addSubview(view)
+ self.batteryView = view
+ }
+
+ private func initAdapter() {
+ let y: CGFloat = self.batteryView!.frame.origin.y - Constants.Popup.separatorHeight
+ let separator = SeparatorView("Power adapter", origin: NSPoint(x: 0, y: y), width: self.frame.width)
+ self.addSubview(separator)
+
+ let view: NSView = NSView(frame: NSRect(x: 0, y: separator.frame.origin.y - self.adapterHeight, width: self.frame.width, height: self.adapterHeight))
+
+ self.powerField = PopupRow(view, n: 1, title: "Power:", value: "")
+ self.chargingStateField = PopupRow(view, n: 0, title: "Is charging:", value: "")
+
+ self.addSubview(view)
+ self.adapterView = view
+ }
+
+ public func usageCallback(_ value: Usage) {
+ DispatchQueue.main.async(execute: {
+ if !self.window!.isVisible && self.initialized {
+ return
+ }
+
+ self.dashboardBatteryView?.setValue(abs(value.level))
+
+ self.levelField?.stringValue = "\(Int(abs(value.level) * 100)) %"
+ self.sourceField?.stringValue = value.powerSource
+ self.timeField?.stringValue = ""
+
+ if value.powerSource == "Battery Power" {
+ self.timeLabelField?.stringValue = "Time to discharge:"
+ if value.timeToEmpty != -1 && value.timeToEmpty != 0 {
+ self.timeField?.stringValue = Double(value.timeToEmpty*60).printSecondsToHoursMinutesSeconds()
+ }
+ } else {
+ self.timeLabelField?.stringValue = "Time to charge:"
+ if value.timeToCharge != -1 && value.timeToCharge != 0 {
+ self.timeField?.stringValue = Double(value.timeToCharge*60).printSecondsToHoursMinutesSeconds()
+ }
+ }
+
+ if value.timeToEmpty == -1 || value.timeToEmpty == -1 {
+ self.timeField?.stringValue = "Calculating"
+ }
+
+ if value.isCharged {
+ self.timeField?.stringValue = "Fully charged"
+ }
+
+ self.healthField?.stringValue = "\(value.health) % (\(value.state))"
+
+ self.amperageField?.stringValue = "\(abs(value.amperage)) mA"
+ self.voltageField?.stringValue = "\(value.voltage.roundTo(decimalPlaces: 2)) V"
+ self.temperatureField?.stringValue = "\(value.temperature) °C"
+
+ self.powerField?.stringValue = value.powerSource == "Battery Power" ? "Not connected" : "\(value.ACwatts) W"
+ self.chargingStateField?.stringValue = value.level > 0 ? "Yes" : "No"
+
+ self.initialized = true
+ })
+ }
+}
+
+private class BatteryView: NSView {
+ private var percentage: Double = 0
+
+ public override init(frame: NSRect) {
+ super.init(frame: frame)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ public override func draw(_ dirtyRect: NSRect) {
+ super.draw(dirtyRect)
+
+ let w: CGFloat = 130
+ let h: CGFloat = 50
+ let x: CGFloat = (dirtyRect.width - w)/2
+ let y: CGFloat = (dirtyRect.size.height - h) / 2
+ let radius: CGFloat = 3
+ let batteryFrame = NSBezierPath(roundedRect: NSRect(x: x+1, y: y, width: w, height: h), xRadius: radius, yRadius: radius)
+ NSColor.black.set()
+
+ let bPX: CGFloat = x+w+1
+ let bPY: CGFloat = (dirtyRect.size.height / 2) - 4
+ let batteryPoint = NSBezierPath(roundedRect: NSRect(x: bPX, y: bPY, width: 4, height: 8), xRadius: radius, yRadius: radius)
+ batteryPoint.lineWidth = 1.1
+ batteryPoint.stroke()
+ batteryPoint.fill()
+
+ batteryFrame.lineWidth = 1
+ batteryFrame.stroke()
+
+ let maxWidth = w-2
+ let inner = NSBezierPath(roundedRect: NSRect(x: x+2, y: y+1, width: maxWidth * CGFloat(self.percentage), height: h-2), xRadius: radius, yRadius: radius)
+ self.percentage.batteryColor(color: true).set()
+ inner.lineWidth = 0
+ inner.stroke()
+ inner.close()
+ inner.fill()
+ }
+
+ public func setValue(_ value: Double) {
+ if self.percentage == value {
+ return
+ }
+
+ self.percentage = value
+ DispatchQueue.main.async(execute: {
+ self.display()
+ })
+ }
+}
diff --git a/Modules/Battery/readers.swift b/Modules/Battery/readers.swift
new file mode 100644
index 00000000..70422f94
--- /dev/null
+++ b/Modules/Battery/readers.swift
@@ -0,0 +1,135 @@
+//
+// readers.swift
+// Battery
+//
+// Created by Serhiy Mytrovtsiy on 06/06/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+import ModuleKit
+
+internal class UsageReader: Reader {
+ private var service: io_connect_t = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleSmartBattery"))
+
+ private var source: CFRunLoopSource?
+ private var loop: CFRunLoop?
+
+ private var usage: Usage = Usage()
+
+ public override func start() {
+ let context = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
+
+ self.source = IOPSNotificationCreateRunLoopSource({ (context) in
+ guard let ctx = context else {
+ return
+ }
+
+ let watcher = Unmanaged.fromOpaque(ctx).takeUnretainedValue()
+ watcher.read()
+ }, context).takeRetainedValue()
+
+ self.loop = RunLoop.current.getCFRunLoop()
+ CFRunLoopAddSource(self.loop, source, .defaultMode)
+
+ self.read()
+ }
+
+ public override func stop() {
+ guard let runLoop = loop, let source = source else {
+ return
+ }
+
+ CFRunLoopRemoveSource(runLoop, source, .defaultMode)
+ }
+
+ public override func read() {
+ let psInfo = IOPSCopyPowerSourcesInfo().takeRetainedValue()
+ let psList = IOPSCopyPowerSourcesList(psInfo).takeRetainedValue() as [CFTypeRef]
+
+ if psList.count == 0 {
+ return
+ }
+
+ for ps in psList {
+ if let list = IOPSGetPowerSourceDescription(psInfo, ps).takeUnretainedValue() as? Dictionary {
+ self.usage.powerSource = list[kIOPSPowerSourceStateKey] as? String ?? "AC Power"
+ self.usage.state = list[kIOPSBatteryHealthKey] as! String
+ self.usage.isCharged = list[kIOPSIsChargedKey] as? Bool ?? false
+ var cap = Double(list[kIOPSCurrentCapacityKey] as! Int) / 100
+
+ self.usage.timeToEmpty = Int(list[kIOPSTimeToEmptyKey] as! Int)
+ self.usage.timeToCharge = Int(list[kIOPSTimeToFullChargeKey] as! Int)
+
+ self.usage.cycles = self.getIntValue("CycleCount" as CFString) ?? 0
+
+ let maxCapacity = self.getIntValue("MaxCapacity" as CFString) ?? 1
+ let designCapacity = self.getIntValue("DesignCapacity" as CFString) ?? 1
+ self.usage.health = (100 * maxCapacity) / designCapacity
+
+ self.usage.amperage = self.getIntValue("Amperage" as CFString) ?? 0
+ self.usage.voltage = self.getVoltage() ?? 0
+ self.usage.temperature = self.getTemperature() ?? 0
+
+ var ACwatts: Int = 0
+ if let ACDetails = IOPSCopyExternalPowerAdapterDetails() {
+ if let ACList = ACDetails.takeUnretainedValue() as? Dictionary {
+ guard let watts = ACList[kIOPSPowerAdapterWattsKey] else {
+ return
+ }
+ ACwatts = Int(watts as! Int)
+ }
+ }
+ self.usage.ACwatts = ACwatts
+ self.usage.ACstatus = self.getBoolValue("IsCharging" as CFString) ?? false
+
+ if self.usage.powerSource == "Battery Power" {
+ cap = 0 - cap
+ }
+ self.usage.level = cap
+
+ DispatchQueue.main.async(execute: {
+ self.callback(self.usage)
+ })
+ }
+ }
+ }
+
+ private func getBoolValue(_ forIdentifier: CFString) -> Bool? {
+ if let value = IORegistryEntryCreateCFProperty(self.service, forIdentifier, kCFAllocatorDefault, 0) {
+ return value.takeRetainedValue() as? Bool
+ }
+ return nil
+ }
+
+ private func getIntValue(_ identifier: CFString) -> Int? {
+ if let value = IORegistryEntryCreateCFProperty(self.service, identifier, kCFAllocatorDefault, 0) {
+ return value.takeRetainedValue() as? Int
+ }
+ return nil
+ }
+
+ private func getDoubleValue(_ identifier: CFString) -> Double? {
+ if let value = IORegistryEntryCreateCFProperty(self.service, identifier, kCFAllocatorDefault, 0) {
+ return value.takeRetainedValue() as? Double
+ }
+ return nil
+ }
+
+ private func getVoltage() -> Double? {
+ if let value = self.getDoubleValue("Voltage" as CFString) {
+ return value / 1000.0
+ }
+ return nil
+ }
+
+ private func getTemperature() -> Double? {
+ if let value = IORegistryEntryCreateCFProperty(self.service, "Temperature" as CFString, kCFAllocatorDefault, 0) {
+ return value.takeRetainedValue() as! Double / 100.0
+ }
+ return nil
+ }
+}
diff --git a/Modules/CPU/Info.plist b/Modules/CPU/Info.plist
new file mode 100644
index 00000000..20202aa7
--- /dev/null
+++ b/Modules/CPU/Info.plist
@@ -0,0 +1,24 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+ NSHumanReadableCopyright
+ Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+
+
diff --git a/Modules/CPU/config.plist b/Modules/CPU/config.plist
new file mode 100644
index 00000000..d74eb35b
--- /dev/null
+++ b/Modules/CPU/config.plist
@@ -0,0 +1,38 @@
+
+
+
+
+ Name
+ CPU
+ State
+
+ Widgets
+
+ mini
+
+ Default
+
+ Preview
+
+ Value
+ 0.08
+
+
+ line_chart
+
+ Default
+
+
+ bar_chart
+
+ Default
+
+ Preview
+
+ Value
+ 0.36,0.28
+
+
+
+
+
diff --git a/Modules/CPU/main.swift b/Modules/CPU/main.swift
new file mode 100644
index 00000000..c0a852ab
--- /dev/null
+++ b/Modules/CPU/main.swift
@@ -0,0 +1,80 @@
+//
+// main.swift
+// CPU
+//
+// Created by Serhiy Mytrovtsiy on 09/04/2020.
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+import ModuleKit
+import StatsKit
+
+public struct Load: value_t {
+ var totalUsage: Double = 0
+ var usagePerCore: [Double] = []
+
+ var systemLoad: Double = 0
+ var userLoad: Double = 0
+ var idleLoad: Double = 0
+
+ public var widget_value: Double {
+ get {
+ return self.totalUsage
+ }
+ }
+}
+
+public struct TopProcess {
+ var pid: Int = 0
+ var command: String = ""
+ var usage: Double = 0
+}
+
+public class CPU: Module {
+ private let popupView: Popup = Popup()
+ private var settingsView: Settings
+
+ private var loadReader: LoadReader = LoadReader()
+ private let smc: UnsafePointer?
+
+ public init(_ store: UnsafePointer, _ smc: UnsafePointer) {
+ self.smc = smc
+ self.settingsView = Settings("CPU", store: store)
+ self.loadReader.store = store
+
+ super.init(
+ store: store,
+ popup: self.popupView,
+ settings: self.settingsView
+ )
+
+ self.loadReader.readyCallback = { [unowned self] in
+ self.readyHandler()
+ }
+ self.loadReader.callbackHandler = { [unowned self] value in
+ self.loadCallback(value)
+ }
+
+ self.addReader(self.loadReader)
+ }
+
+ private func loadCallback(_ value: Load?) {
+ if value == nil {
+ return
+ }
+
+ let temperature = self.smc?.pointee.getValue("TC0F") ?? self.smc?.pointee.getValue("TC0P") ?? self.smc?.pointee.getValue("TC0H")
+ self.popupView.loadCallback(value!, tempValue: temperature)
+
+ if let widget = self.widget as? Mini {
+ widget.setValue(value!.totalUsage, sufix: "%")
+ }
+ if let widget = self.widget as? LineChart {
+ widget.setValue(value!.totalUsage)
+ }
+ if let widget = self.widget as? BarChart {
+ widget.setValue(value!.usagePerCore)
+ }
+ }
+}
diff --git a/Modules/CPU/popup.swift b/Modules/CPU/popup.swift
new file mode 100644
index 00000000..5335344f
--- /dev/null
+++ b/Modules/CPU/popup.swift
@@ -0,0 +1,169 @@
+//
+// popup.swift
+// CPU
+//
+// Created by Serhiy Mytrovtsiy on 15/04/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+import ModuleKit
+import StatsKit
+
+internal class Popup: NSView {
+ private let dashboardHeight: CGFloat = 90
+ private let detailsHeight: CGFloat = 66 // -26
+
+ private var loadField: NSTextField? = nil
+ private var temperatureField: NSTextField? = nil
+
+ private var systemField: NSTextField? = nil
+ private var userField: NSTextField? = nil
+ private var idleField: NSTextField? = nil
+
+ public var chart: LineChartView? = nil
+ private var ready: Bool = false
+
+ public init() {
+ super.init(frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: dashboardHeight + Constants.Popup.separatorHeight + detailsHeight))
+
+ initDashboard()
+ initDetails()
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ public override func updateLayer() {
+ self.chart?.display()
+ }
+
+ private func initDashboard() {
+ let rightWidth: CGFloat = 110
+ let view: NSView = NSView(frame: NSRect(x: 0, y: self.frame.height - self.dashboardHeight, width: self.frame.width, height: self.dashboardHeight))
+
+ let leftPanel = NSView(frame: NSRect(x: 0, y: 0, width: view.frame.width - rightWidth - Constants.Popup.margins, height: view.frame.height))
+
+ self.chart = LineChartView(frame: NSRect(x: 4, y: 3, width: leftPanel.frame.width, height: leftPanel.frame.height), num: 120)
+ leftPanel.addSubview(self.chart!)
+
+ let rightPanel: NSView = NSView(frame: NSRect(x: view.frame.width - rightWidth, y: 0, width: rightWidth, height: view.frame.height))
+ self.loadField = addFirstRow(mView: rightPanel, y: ((rightPanel.frame.height - 16)/2)+9, title: "Load:", value: "")
+ self.temperatureField = addFirstRow(mView: rightPanel, y: ((rightPanel.frame.height - 16)/2)-9, title: "Temperature:", value: "")
+
+ view.addSubview(leftPanel)
+ view.addSubview(rightPanel)
+ self.addSubview(view)
+ }
+
+ private func initDetails() {
+ let y: CGFloat = self.frame.height - self.dashboardHeight - Constants.Popup.separatorHeight
+ let separator = SeparatorView("Details", origin: NSPoint(x: 0, y: y), width: self.frame.width)
+ self.addSubview(separator)
+
+ let view: NSView = NSView(frame: NSRect(x: 0, y: separator.frame.origin.y - self.detailsHeight, width: self.frame.width, height: self.detailsHeight))
+
+ self.systemField = PopupRow(view, n: 2, title: "System:", value: "")
+ self.userField = PopupRow(view, n: 1, title: "User:", value: "")
+ self.idleField = PopupRow(view, n: 0, title: "Idle:", value: "")
+
+ self.addSubview(view)
+ }
+
+ private func addFirstRow(mView: NSView, y: CGFloat, title: String, value: String) -> NSTextField {
+ let rowView: NSView = NSView(frame: NSRect(x: 0, y: y, width: mView.frame.width, height: 16))
+
+ let labelWidth = title.widthOfString(usingFont: .systemFont(ofSize: 10, weight: .light)) + 4
+ let labelView: NSTextField = TextView(frame: NSRect(x: 0, y: 1.5, width: labelWidth, height: 13))
+ labelView.stringValue = title
+ labelView.alignment = .natural
+ labelView.font = NSFont.systemFont(ofSize: 10, weight: .light)
+
+ let valueView: NSTextField = TextView(frame: NSRect(x: labelWidth, y: 1, width: mView.frame.width - labelWidth, height: 14))
+ valueView.stringValue = value
+ valueView.alignment = .right
+ valueView.font = NSFont.systemFont(ofSize: 11, weight: .medium)
+
+ rowView.addSubview(labelView)
+ rowView.addSubview(valueView)
+ mView.addSubview(rowView)
+
+ return valueView
+ }
+
+ public func loadCallback(_ value: Load, tempValue: Double?) {
+ var temperature: String = "Unknown"
+
+ DispatchQueue.main.async(execute: {
+ if self.window!.isVisible || !self.ready {
+ if tempValue != nil {
+ let formatter = MeasurementFormatter()
+ let measurement = Measurement(value: tempValue!.rounded(toPlaces: 0), unit: UnitTemperature.celsius)
+ temperature = formatter.string(from: measurement)
+ }
+
+ self.temperatureField?.stringValue = temperature
+
+ self.systemField?.stringValue = "\(Int(value.systemLoad.rounded(toPlaces: 2) * 100)) %"
+ self.userField?.stringValue = "\(Int(value.userLoad.rounded(toPlaces: 2) * 100)) %"
+ self.idleField?.stringValue = "\(Int(value.idleLoad.rounded(toPlaces: 2) * 100)) %"
+
+ let v = Int(value.totalUsage.rounded(toPlaces: 2) * 100)
+ self.loadField?.stringValue = "\(v) %"
+ self.ready = true
+ }
+
+ self.chart?.addValue(value.totalUsage)
+ })
+ }
+}
+
+private class ProcessView: NSView {
+ public var width: CGFloat {
+ get { return 0 }
+ set {
+ self.setFrameSize(NSSize(width: newValue, height: self.frame.height))
+ }
+ }
+
+ public var label: String {
+ get { return "" }
+ set {
+ self.labelView?.stringValue = newValue
+ }
+ }
+ public var value: String {
+ get { return "" }
+ set {
+ self.valueView?.stringValue = newValue
+ }
+ }
+
+ private var labelView: LabelField? = nil
+ private var valueView: ValueField? = nil
+
+ init(_ n: CGFloat) {
+ super.init(frame: NSRect(x: 0, y: n*22, width: Constants.Popup.width, height: 16))
+
+ let rowView: NSView = NSView(frame: NSRect(x: Constants.Popup.margins, y: 0, width: self.frame.width - (Constants.Popup.margins*2), height: 16))
+
+ let labelView: LabelField = LabelField(frame: NSRect(x: 0, y: 0.5, width: 50, height: 15), "")
+ let valueView: ValueField = ValueField(frame: NSRect(x: 50, y: 0, width: rowView.frame.width - 50, height: 16), "")
+
+ rowView.addSubview(labelView)
+ rowView.addSubview(valueView)
+
+ self.labelView = labelView
+ self.valueView = valueView
+
+ self.addSubview(rowView)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+}
diff --git a/Modules/CPU/readers.swift b/Modules/CPU/readers.swift
new file mode 100644
index 00000000..58a8bdb0
--- /dev/null
+++ b/Modules/CPU/readers.swift
@@ -0,0 +1,152 @@
+//
+// readers.swift
+// CPU
+//
+// Created by Serhiy Mytrovtsiy on 10/04/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+import StatsKit
+import ModuleKit
+
+internal class LoadReader: Reader {
+ public var store: UnsafePointer? = nil
+
+ private var cpuInfo: processor_info_array_t!
+ private var prevCpuInfo: processor_info_array_t?
+ private var numCpuInfo: mach_msg_type_number_t = 0
+ private var numPrevCpuInfo: mach_msg_type_number_t = 0
+ private var numCPUs: uint = 0
+ private let CPUUsageLock: NSLock = NSLock()
+ private var previousInfo = host_cpu_load_info()
+
+ private var response: Load = Load()
+ private var numCPUsU: natural_t = 0
+ private var usagePerCore: [Double] = []
+
+ public override func setup() {
+ self.interval = 1500
+
+ [CTL_HW, HW_NCPU].withUnsafeBufferPointer() { mib in
+ var sizeOfNumCPUs: size_t = MemoryLayout.size
+ let status = sysctl(processor_info_array_t(mutating: mib.baseAddress), 2, &numCPUs, &sizeOfNumCPUs, nil, 0)
+ if status != 0 {
+ self.numCPUs = 1
+ }
+ }
+ }
+
+ public override func read() {
+ let result: kern_return_t = host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &self.numCPUsU, &self.cpuInfo, &self.numCpuInfo)
+ if result == KERN_SUCCESS {
+ self.CPUUsageLock.lock()
+ self.usagePerCore = []
+
+ for i in 0 ..< Int32(numCPUs) {
+ var inUse: Int32
+ var total: Int32
+ if let prevCpuInfo = self.prevCpuInfo {
+ inUse = self.cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_USER)]
+ - prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_USER)]
+ + self.cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_SYSTEM)]
+ - prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_SYSTEM)]
+ + self.cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_NICE)]
+ - prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_NICE)]
+ total = inUse + (self.cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_IDLE)]
+ - prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_IDLE)])
+ } else {
+ inUse = self.cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_USER)]
+ + self.cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_SYSTEM)]
+ + self.cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_NICE)]
+ total = inUse + self.cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_IDLE)]
+ }
+
+ if total != 0 {
+ self.usagePerCore.append(Double(inUse) / Double(total))
+ }
+ }
+ self.CPUUsageLock.unlock()
+
+ if self.store?.pointee.bool(key: "CPU_hyperhreading", defaultValue: false) ?? false {
+ self.response.usagePerCore = self.usagePerCore
+ } else {
+ var i = 0
+ var a = 0
+
+ self.response.usagePerCore = []
+ while i < Int(self.usagePerCore.count/2) {
+ a = i*2
+ if self.usagePerCore.indices.contains(a) && self.usagePerCore.indices.contains(a+1) {
+ self.response.usagePerCore.append((Double(self.usagePerCore[a]) + Double(self.usagePerCore[a+1])) / 2)
+ }
+ i += 1
+ }
+ }
+
+ if let prevCpuInfo = self.prevCpuInfo {
+ let prevCpuInfoSize: size_t = MemoryLayout.stride * Int(self.numPrevCpuInfo)
+ vm_deallocate(mach_task_self_, vm_address_t(bitPattern: prevCpuInfo), vm_size_t(prevCpuInfoSize))
+ }
+
+ self.prevCpuInfo = self.cpuInfo
+ self.numPrevCpuInfo = self.numCpuInfo
+
+ self.cpuInfo = nil
+ self.numCpuInfo = 0
+ } else {
+ print("ERROR host_processor_info(): " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
+ }
+
+ let cpuInfo = hostCPULoadInfo()
+ if cpuInfo == nil {
+ self.callback(nil)
+ return
+ }
+
+ let userDiff = Double(cpuInfo!.cpu_ticks.0 - self.previousInfo.cpu_ticks.0)
+ let sysDiff = Double(cpuInfo!.cpu_ticks.1 - self.previousInfo.cpu_ticks.1)
+ let idleDiff = Double(cpuInfo!.cpu_ticks.2 - self.previousInfo.cpu_ticks.2)
+ let niceDiff = Double(cpuInfo!.cpu_ticks.3 - self.previousInfo.cpu_ticks.3)
+ let totalTicks = sysDiff + userDiff + niceDiff + idleDiff
+
+ let system = sysDiff / totalTicks
+ let user = userDiff / totalTicks
+ let idle = idleDiff / totalTicks
+
+ if !system.isNaN {
+ self.response.systemLoad = system
+ }
+ if !user.isNaN {
+ self.response.userLoad = user
+ }
+ if !idle.isNaN {
+ self.response.idleLoad = idle
+ }
+ self.previousInfo = cpuInfo!
+ self.response.totalUsage = self.response.systemLoad + self.response.userLoad
+
+ self.callback(self.response)
+ }
+
+ private func hostCPULoadInfo() -> host_cpu_load_info? {
+ let HOST_CPU_LOAD_INFO_COUNT = MemoryLayout.stride/MemoryLayout.stride
+ var size = mach_msg_type_number_t(HOST_CPU_LOAD_INFO_COUNT)
+ var cpuLoadInfo = host_cpu_load_info()
+
+ let result: kern_return_t = withUnsafeMutablePointer(to: &cpuLoadInfo) {
+ $0.withMemoryRebound(to: integer_t.self, capacity: HOST_CPU_LOAD_INFO_COUNT) {
+ host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, $0, &size)
+ }
+ }
+ if result != KERN_SUCCESS {
+ print("Error - \(#file): \(#function) - kern_result_t = \(result)")
+ return nil
+ }
+
+ return cpuLoadInfo
+ }
+}
diff --git a/Modules/CPU/settings.swift b/Modules/CPU/settings.swift
new file mode 100644
index 00000000..d4c0d0ab
--- /dev/null
+++ b/Modules/CPU/settings.swift
@@ -0,0 +1,71 @@
+//
+// Settings.swift
+// CPU
+//
+// Created by Serhiy Mytrovtsiy on 18/04/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+import StatsKit
+import ModuleKit
+
+internal class Settings: NSView, Settings_v {
+ private var hyperthreadState: Bool = false
+
+ private let title: String
+ private let store: UnsafePointer?
+
+ public init(_ title: String, store: UnsafePointer?) {
+ self.title = title
+ self.store = store
+ super.init(frame: CGRect(x: Constants.Settings.margin, y: Constants.Settings.margin, width: 0, height: 0))
+ self.wantsLayer = true
+ self.canDrawConcurrently = true
+
+ if self.store != nil {
+ self.hyperthreadState = store!.pointee.bool(key: "\(self.title)_hyperhreading", defaultValue: self.hyperthreadState)
+ }
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ public func load(rect: NSRect, widget: widget_t) {
+ self.subviews.forEach{ $0.removeFromSuperview() }
+
+ let rowHeight: CGFloat = 30
+ var height: CGFloat = 0
+
+ if widget == .barChart {
+ self.addSubview(ToggleTitleRow(
+ frame: NSRect(x: 0, y: (rowHeight + Constants.Settings.margin) * 0, width: rect.width - (Constants.Settings.margin*2), height: rowHeight),
+ title: "Show hyper-threading cores",
+ action: #selector(toggleMultithreading),
+ state: self.hyperthreadState
+ ))
+ height += rowHeight
+ }
+
+ if height != 0 {
+ height += (Constants.Settings.margin*2)
+ }
+ self.setFrameSize(NSSize(width: rect.width - (Constants.Settings.margin*2), height: height))
+ }
+
+ @objc func toggleMultithreading(_ sender: NSControl) {
+ var state: NSControl.StateValue? = nil
+ if #available(OSX 10.15, *) {
+ state = sender is NSSwitch ? (sender as! NSSwitch).state: nil
+ } else {
+ state = sender is NSButton ? (sender as! NSButton).state: nil
+ }
+
+ self.hyperthreadState = state! == .on ? true : false
+ self.store?.pointee.set(key: "\(self.title)_hyperhreading", value: self.hyperthreadState)
+ }
+}
diff --git a/Modules/Disk/Info.plist b/Modules/Disk/Info.plist
new file mode 100644
index 00000000..20202aa7
--- /dev/null
+++ b/Modules/Disk/Info.plist
@@ -0,0 +1,24 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+ NSHumanReadableCopyright
+ Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+
+
diff --git a/Modules/Disk/config.plist b/Modules/Disk/config.plist
new file mode 100644
index 00000000..f356641c
--- /dev/null
+++ b/Modules/Disk/config.plist
@@ -0,0 +1,51 @@
+
+
+
+
+ Name
+ Disk
+ State
+
+ Widgets
+
+ mini
+
+ Default
+
+ Title
+ SSD
+ Preview
+
+ Title
+ SSD
+ Value
+ 0.36
+
+
+ bar_chart
+
+ Default
+
+ Title
+ SSD
+ Label
+
+ Box
+
+ Color
+
+ Preview
+
+ Label
+
+ Box
+
+ Color
+
+ Value
+ 0.36
+
+
+
+
+
diff --git a/Modules/Disk/main.swift b/Modules/Disk/main.swift
new file mode 100644
index 00000000..30bdb50e
--- /dev/null
+++ b/Modules/Disk/main.swift
@@ -0,0 +1,123 @@
+//
+// main.swift
+// Disk
+//
+// Created by Serhiy Mytrovtsiy on 07/05/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+import StatsKit
+import ModuleKit
+
+struct diskInfo {
+ var name: String = ""
+ var model: String = ""
+ var path: URL?
+ var connection: String = ""
+ var fileSystem: String = ""
+
+ var totalSize: Int64 = 0
+ var freeSize: Int64 = 0
+
+ var mediaBSDName: String = ""
+ var root: Bool = false
+}
+
+struct DiskList: value_t {
+ var list: [diskInfo] = []
+
+ public var widget_value: Double {
+ get {
+ return 0
+ }
+ }
+
+ func getDiskByBSDName(_ name: String) -> diskInfo? {
+ if let idx = self.list.firstIndex(where: { $0.mediaBSDName == name }) {
+ return self.list[idx]
+ }
+
+ return nil
+ }
+
+ func getDiskByName(_ name: String) -> diskInfo? {
+ if let idx = self.list.firstIndex(where: { $0.name == name }) {
+ return self.list[idx]
+ }
+
+ return nil
+ }
+
+ func getRootDisk() -> diskInfo? {
+ if let idx = self.list.firstIndex(where: { $0.root }) {
+ return self.list[idx]
+ }
+
+ return nil
+ }
+}
+
+public class Disk: Module {
+ private let popupView: Popup = Popup()
+ private var capacityReader: CapacityReader = CapacityReader()
+ private var settingsView: Settings
+ private var selectedDisk: String = ""
+
+ public init(_ store: UnsafePointer?) {
+ self.settingsView = Settings("Disk", store: store!)
+
+ super.init(
+ store: store,
+ popup: self.popupView,
+ settings: self.settingsView
+ )
+ self.selectedDisk = store!.pointee.string(key: "\(self.config.name)_disk", defaultValue: self.selectedDisk)
+
+ self.capacityReader.readyCallback = { [unowned self] in
+ self.readyHandler()
+ }
+ self.capacityReader.callbackHandler = { [unowned self] value in
+ self.capacityCallback(value: value)
+ }
+
+ self.settingsView.selectedDiskHandler = { [unowned self] value in
+ self.selectedDisk = value
+ self.capacityReader.read()
+ }
+
+ self.addReader(self.capacityReader)
+ }
+
+ private func capacityCallback(value: DiskList?) {
+ if value == nil {
+ return
+ }
+ self.popupView.usageCallback(value!)
+ self.settingsView.setList(value!)
+
+ var d: diskInfo? = value!.getDiskByName(self.selectedDisk)
+ if d == nil {
+ d = value!.getRootDisk()
+ }
+
+ if d == nil {
+ return
+ }
+
+ let total = d!.totalSize
+ let free = d!.freeSize
+ let usedSpace = total - free
+ let percentage = Double(usedSpace) / Double(total)
+
+ if let widget = self.widget as? Mini {
+ widget.setValue(percentage, sufix: "%")
+ }
+ if let widget = self.widget as? BarChart {
+ widget.setValue([percentage])
+ }
+ }
+}
diff --git a/Modules/Disk/popup.swift b/Modules/Disk/popup.swift
new file mode 100644
index 00000000..06419b59
--- /dev/null
+++ b/Modules/Disk/popup.swift
@@ -0,0 +1,176 @@
+//
+// popup.swift
+// Disk
+//
+// Created by Serhiy Mytrovtsiy on 11/05/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+import ModuleKit
+import StatsKit
+
+internal class Popup: NSView {
+ let diskFullHeight: CGFloat = 60
+ var list: [String: DiskView] = [:]
+
+ public init() {
+ super.init(frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: 0))
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ internal func usageCallback(_ value: DiskList) {
+ value.list.reversed().forEach { (d: diskInfo) in
+ if self.list[d.name] == nil {
+ DispatchQueue.main.async(execute: {
+ self.list[d.name] = DiskView(
+ NSRect(x: 0, y: (self.diskFullHeight + Constants.Popup.margins) * CGFloat(self.list.count), width: self.frame.width, height: self.diskFullHeight),
+ name: d.name,
+ size: d.totalSize,
+ free: d.freeSize,
+ path: d.path
+ )
+ self.addSubview(self.list[d.name]!)
+
+ self.setFrameSize(NSSize(width: self.frame.width, height: ((self.diskFullHeight + Constants.Popup.margins) * CGFloat(self.list.count)) - Constants.Popup.margins))
+ })
+ } else {
+ self.list[d.name]?.update(free: d.freeSize)
+ }
+ }
+ }
+}
+
+internal class DiskView: NSView {
+ public let name: String
+ public let size: Int64
+ private let uri: URL?
+
+ private let nameHeight: CGFloat = 20
+ private let legendHeight: CGFloat = 12
+ private let barHeight: CGFloat = 10
+
+ private var legendField: NSTextField? = nil
+ private var percentageField: NSTextField? = nil
+ private var usedBarSpace: NSView? = nil
+
+ private var mainView: NSView
+
+ private var initialized: Bool = false
+
+ public init(_ frame: NSRect, name: String, size: Int64, free: Int64, path: URL?) {
+ self.mainView = NSView(frame: NSRect(x: 5, y: 5, width: frame.width - 10, height: frame.height - 10))
+ self.name = name
+ self.size = size
+ self.uri = path
+ super.init(frame: frame)
+
+ self.wantsLayer = true
+ self.layer?.cornerRadius = 2
+
+ self.addName()
+ self.addHorizontalBar(free: free)
+ self.addLegend(free: free)
+
+ self.addSubview(self.mainView)
+
+ let rect: CGRect = CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height)
+ let trackingArea = NSTrackingArea(rect: rect, options: [NSTrackingArea.Options.activeAlways, NSTrackingArea.Options.mouseEnteredAndExited, NSTrackingArea.Options.activeInActiveApp], owner: self, userInfo: nil)
+ self.addTrackingArea(trackingArea)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func updateLayer() {
+ self.layer?.backgroundColor = isDarkMode ? NSColor(hexString: "#111111", alpha: 0.25).cgColor : NSColor(hexString: "#f5f5f5", alpha: 1).cgColor
+ }
+
+ private func addName() {
+ let nameWidth = self.name.widthOfString(usingFont: .systemFont(ofSize: 13, weight: .light)) + 4
+ let view: NSView = NSView(frame: NSRect(x: 0, y: self.mainView.frame.height - nameHeight, width: nameWidth, height: nameHeight))
+
+ let nameField: NSTextField = TextView(frame: NSRect(x: 0, y: 0, width: nameWidth, height: view.frame.height))
+ nameField.stringValue = self.name
+
+ view.addSubview(nameField)
+ self.mainView.addSubview(view)
+ }
+
+ private func addLegend(free: Int64) {
+ let view: NSView = NSView(frame: NSRect(x: 0, y: 2, width: self.mainView.frame.width, height: self.legendHeight))
+
+ self.legendField = TextView(frame: NSRect(x: 0, y: 0, width: view.frame.width - 40, height: view.frame.height))
+ self.legendField?.font = NSFont.systemFont(ofSize: 11, weight: .light)
+ self.legendField?.stringValue = "Used \(Units(bytes: (self.size - free)).getReadableMemory()) from \(Units(bytes: self.size).getReadableMemory())"
+
+ self.percentageField = TextView(frame: NSRect(x: view.frame.width - 40, y: 0, width: 40, height: view.frame.height))
+ self.percentageField?.font = NSFont.systemFont(ofSize: 11, weight: .regular)
+ self.percentageField?.alignment = .right
+ self.percentageField?.stringValue = "\(Int8((Double(self.size - free) / Double(self.size)) * 100))%"
+
+ view.addSubview(self.legendField!)
+ view.addSubview(self.percentageField!)
+ self.mainView.addSubview(view)
+ }
+
+ private func addHorizontalBar(free: Int64) {
+ let view: NSView = NSView(frame: NSRect(x: 1, y: self.mainView.frame.height - self.nameHeight - 11, width: self.mainView.frame.width - 2, height: self.barHeight))
+ view.wantsLayer = true
+ view.layer?.backgroundColor = NSColor.white.cgColor
+ view.layer?.borderColor = NSColor.secondaryLabelColor.cgColor
+ view.layer?.borderWidth = 0.25
+ view.layer?.cornerRadius = 3
+
+ let percentage = CGFloat(self.size - free) / CGFloat(self.size)
+ let width: CGFloat = (view.frame.width * percentage) / 1
+ self.usedBarSpace = NSView(frame: NSRect(x: 0, y: 0, width: width, height: view.frame.height))
+ self.usedBarSpace?.wantsLayer = true
+ self.usedBarSpace?.layer?.backgroundColor = NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 1).cgColor
+
+ view.addSubview(self.usedBarSpace!)
+ self.mainView.addSubview(view)
+ }
+
+ public func update(free: Int64) {
+ DispatchQueue.main.async(execute: {
+ if !self.window!.isVisible && self.initialized {
+ return
+ }
+
+ if self.legendField != nil {
+ self.legendField?.stringValue = "Used \(Units(bytes: (self.size - free)).getReadableMemory()) from \(Units(bytes: self.size).getReadableMemory())"
+ self.percentageField?.stringValue = "\(Int8((Double(self.size - free) / Double(self.size)) * 100))%"
+ }
+
+ if self.usedBarSpace != nil {
+ let percentage = CGFloat(self.size - free) / CGFloat(self.size)
+ let width: CGFloat = ((self.mainView.frame.width - 2) * percentage) / 1
+ self.usedBarSpace?.setFrameSize(NSSize(width: width, height: self.usedBarSpace!.frame.height))
+ }
+
+ self.initialized = true
+ })
+ }
+
+ override func mouseEntered(with: NSEvent) {
+ NSCursor.pointingHand.set()
+ }
+
+ override func mouseExited(with: NSEvent) {
+ NSCursor.arrow.set()
+ }
+
+ override func mouseDown(with: NSEvent) {
+ if let uri = self.uri {
+ NSWorkspace.shared.openFile(uri.absoluteString, withApplication: "Finder")
+ }
+ }
+}
diff --git a/Stats/Modules/Disk/DiskCapacityReader.swift b/Modules/Disk/readers.swift
similarity index 69%
rename from Stats/Modules/Disk/DiskCapacityReader.swift
rename to Modules/Disk/readers.swift
index b5a821f3..7a77fb6d 100644
--- a/Stats/Modules/Disk/DiskCapacityReader.swift
+++ b/Modules/Disk/readers.swift
@@ -1,81 +1,25 @@
//
-// DiskCapacityReader.swift
-// Stats
+// readers.swift
+// Disk
+//
+// Created by Serhiy Mytrovtsiy on 07/05/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
//
-// Created by Serhiy Mytrovtsiy on 14/01/2020.
// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
//
import Cocoa
+import ModuleKit
-struct diskInfo {
- var ID: String = "";
+internal class CapacityReader: Reader {
+ private var disks: DiskList = DiskList()
- var name: String = "";
- var model: String = "";
- var path: URL?;
- var connection: String = "";
- var fileSystem: String = "";
-
- var totalSize: Int64 = 0;
- var freeSize: Int64 = 0;
-
- var mediaBSDName: String = "";
- var root: Bool = false;
-}
-
-struct disksList {
- var list: [diskInfo] = []
-
- func getDiskByBSDName(_ name: String) -> diskInfo? {
- if let idx = self.list.firstIndex(where: { $0.mediaBSDName == name }) {
- return self.list[idx]
- }
-
- return nil
+ public override func setup() {
+ self.interval = 10000
}
- func getDiskByName(_ name: String) -> diskInfo? {
- if let idx = self.list.firstIndex(where: { $0.name == name }) {
- return self.list[idx]
- }
-
- return nil
- }
-
- func getRootDisk() -> diskInfo? {
- if let idx = self.list.firstIndex(where: { $0.root }) {
- return self.list[idx]
- }
-
- return nil
- }
-}
-
-class DiskCapacityReader: Reader {
- public var name: String = "Capacity"
- public var enabled: Bool = true
- public var available: Bool = true
- public var optional: Bool = false
- public var initialized: Bool = false
- public var callback: (disksList) -> Void = {_ in}
-
- private var disks: disksList = disksList()
-
- init(_ updater: @escaping (disksList) -> Void) {
- self.callback = updater
-
- if self.available {
- DispatchQueue.global(qos: .default).async {
- self.read()
- }
- }
- }
-
- public func read() {
- if !self.enabled && self.initialized { return }
- self.initialized = true
-
+ public override func read() {
let keys: [URLResourceKey] = [.volumeNameKey]
let paths = FileManager.default.mountedVolumeURLs(includingResourceValuesForKeys: keys)!
if let session = DASessionCreate(kCFAllocatorDefault) {
@@ -103,13 +47,7 @@ class DiskCapacityReader: Reader {
}
}
- DispatchQueue.main.async(execute: {
- self.callback(self.disks)
- })
- }
-
- public func toggleEnable(_ value: Bool) {
- self.enabled = value
+ self.callback(self.disks)
}
private func getDisk(_ disk: DADisk) -> diskInfo? {
@@ -159,7 +97,7 @@ class DiskCapacityReader: Reader {
}
}
}
-
+
if d.path != nil {
d.freeSize = freeDiskSpaceInBytes(d.path!.absoluteString)
}
diff --git a/Modules/Disk/settings.swift b/Modules/Disk/settings.swift
new file mode 100644
index 00000000..3bd7ee57
--- /dev/null
+++ b/Modules/Disk/settings.swift
@@ -0,0 +1,80 @@
+//
+// settings.swift
+// Disk
+//
+// Created by Serhiy Mytrovtsiy on 12/05/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+import StatsKit
+import ModuleKit
+
+internal class Settings: NSView, Settings_v {
+ public var selectedDiskHandler: (String) -> Void = {_ in }
+
+ private let title: String
+ private let store: UnsafePointer
+ private var selectedDisk: String
+ private var button: NSPopUpButton?
+
+ public init(_ title: String, store: UnsafePointer) {
+ self.title = title
+ self.store = store
+ self.selectedDisk = store.pointee.string(key: "\(self.title)_disk", defaultValue: "")
+ super.init(frame: CGRect(x: Constants.Settings.margin, y: Constants.Settings.margin, width: 0, height: 0))
+ self.wantsLayer = true
+ self.canDrawConcurrently = true
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ public func load(rect: NSRect, widget: widget_t) {
+ self.subviews.forEach{ $0.removeFromSuperview() }
+
+ self.addDiskSelector(rect: rect)
+
+ self.setFrameSize(NSSize(width: rect.width - (Constants.Settings.margin*2), height: 30 + (Constants.Settings.margin*2)))
+ }
+
+ private func addDiskSelector(rect: NSRect) {
+ let view: NSView = NSView(frame: NSRect(x: 0, y: 0, width: rect.width, height: 30))
+
+ let rowTitle: NSTextField = LabelField(frame: NSRect(x: 0, y: (view.frame.height - 16)/2, width: view.frame.width - 52, height: 17), "Disk to show")
+ rowTitle.font = NSFont.systemFont(ofSize: 13, weight: .light)
+ rowTitle.textColor = .labelColor
+
+ self.button = NSPopUpButton(frame: NSRect(x: view.frame.width - 164, y: 0, width: 140, height: 30))
+ self.button!.target = self
+ self.button?.action = #selector(self.handleSelection)
+
+ view.addSubview(rowTitle)
+ view.addSubview(self.button!)
+
+ self.addSubview(view)
+ }
+
+ internal func setList(_ list: DiskList) {
+ let disks = list.list.map{ $0.name }
+ DispatchQueue.main.async(execute: {
+ if disks != self.button?.itemTitles {
+ self.button?.addItems(withTitles: disks)
+ if self.selectedDisk != "" {
+ self.button?.selectItem(withTitle: self.selectedDisk)
+ }
+ }
+ })
+ }
+
+ @objc func handleSelection(_ sender: NSPopUpButton) {
+ guard let item = sender.selectedItem else { return }
+ self.selectedDisk = item.title
+ self.store.pointee.set(key: "\(self.title)_disk", value: item.title)
+ self.selectedDiskHandler(item.title)
+ }
+}
diff --git a/Modules/Memory/Info.plist b/Modules/Memory/Info.plist
new file mode 100644
index 00000000..20202aa7
--- /dev/null
+++ b/Modules/Memory/Info.plist
@@ -0,0 +1,24 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+ NSHumanReadableCopyright
+ Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+
+
diff --git a/Modules/Memory/config.plist b/Modules/Memory/config.plist
new file mode 100644
index 00000000..1e275e1c
--- /dev/null
+++ b/Modules/Memory/config.plist
@@ -0,0 +1,50 @@
+
+
+
+
+ Name
+ RAM
+ State
+
+ Widgets
+
+ mini
+
+ Default
+
+ Preview
+
+ Value
+ 0.58
+
+
+ line_chart
+
+ Default
+
+
+ bar_chart
+
+ Default
+
+ Label
+
+ Box
+
+ Color
+
+ Preview
+
+ Label
+
+ Box
+
+ Color
+
+ Value
+ 0.48
+
+
+
+
+
diff --git a/Modules/Memory/main.swift b/Modules/Memory/main.swift
new file mode 100644
index 00000000..e0ad4f4b
--- /dev/null
+++ b/Modules/Memory/main.swift
@@ -0,0 +1,71 @@
+//
+// main.swift
+// Memory
+//
+// Created by Serhiy Mytrovtsiy on 12/04/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+import StatsKit
+import ModuleKit
+
+public struct Usage: value_t {
+ var active: Double?
+ var inactive: Double?
+ var wired: Double?
+ var compressed: Double?
+
+ var usage: Double?
+ var total: Double?
+ var used: Double?
+ var free: Double?
+
+ public var widget_value: Double {
+ get {
+ return self.usage ?? 0
+ }
+ }
+}
+
+public class Memory: Module {
+ private let popupView: Popup = Popup()
+ private var usageReader: UsageReader = UsageReader()
+
+ public init(_ store: UnsafePointer?) {
+ super.init(
+ store: store,
+ popup: self.popupView,
+ settings: nil
+ )
+
+ self.usageReader.readyCallback = { [unowned self] in
+ self.readyHandler()
+ }
+ self.usageReader.callbackHandler = { [unowned self] value in
+ self.loadCallback(value: value)
+ }
+
+ self.addReader(self.usageReader)
+ }
+
+ private func loadCallback(value: Usage?) {
+ if value == nil {
+ return
+ }
+
+ self.popupView.loadCallback(value!)
+ if let widget = self.widget as? Mini {
+ widget.setValue(value!.usage ?? 0, sufix: "%")
+ }
+ if let widget = self.widget as? LineChart {
+ widget.setValue(value!.usage ?? 0)
+ }
+ if let widget = self.widget as? BarChart {
+ widget.setValue([value!.usage ?? 0])
+ }
+ }
+}
diff --git a/Modules/Memory/popup.swift b/Modules/Memory/popup.swift
new file mode 100644
index 00000000..4f30bc28
--- /dev/null
+++ b/Modules/Memory/popup.swift
@@ -0,0 +1,119 @@
+//
+// popup.swift
+// Memory
+//
+// Created by Serhiy Mytrovtsiy on 18/04/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+import ModuleKit
+import StatsKit
+
+internal class Popup: NSView {
+ private let dashboardHeight: CGFloat = 90
+ private let detailsHeight: CGFloat = 66
+
+ private var totalField: NSTextField? = nil
+ private var usedField: NSTextField? = nil
+ private var freeField: NSTextField? = nil
+
+ private var activeField: NSTextField? = nil
+ private var inactiveField: NSTextField? = nil
+ private var wiredField: NSTextField? = nil
+ private var compressedField: NSTextField? = nil
+
+ private var chart: LineChartView? = nil
+ private var initialized: Bool = false
+
+ public init() {
+ super.init(frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: dashboardHeight + Constants.Popup.separatorHeight + detailsHeight))
+
+ initFirstView()
+ initDetails()
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ public override func updateLayer() {
+ self.chart?.display()
+ }
+
+ private func initFirstView() {
+ let rightWidth: CGFloat = 116
+ let view: NSView = NSView(frame: NSRect(x: 0, y: self.frame.height - self.dashboardHeight, width: self.frame.width, height: self.dashboardHeight))
+
+ let leftPanel = NSView(frame: NSRect(x: 0, y: 0, width: view.frame.width - rightWidth - Constants.Popup.margins, height: view.frame.height))
+
+ self.chart = LineChartView(frame: NSRect(x: 4, y: 3, width: leftPanel.frame.width, height: leftPanel.frame.height), num: 120)
+ leftPanel.addSubview(self.chart!)
+
+ let rightPanel: NSView = NSView(frame: NSRect(x: view.frame.width - rightWidth, y: 0, width: rightWidth, height: view.frame.height))
+ self.activeField = addFirstRow(mView: rightPanel, y: ((rightPanel.frame.height - 16)/2)+29, title: "Active:", value: "")
+ self.inactiveField = addFirstRow(mView: rightPanel, y: (rightPanel.frame.height - 16)/2+10, title: "Inactive:", value: "")
+ self.wiredField = addFirstRow(mView: rightPanel, y: ((rightPanel.frame.height - 16)/2)-10, title: "Wired:", value: "")
+ self.compressedField = addFirstRow(mView: rightPanel, y: ((rightPanel.frame.height - 16)/2)-29, title: "Compressed:", value: "")
+
+ view.addSubview(leftPanel)
+ view.addSubview(rightPanel)
+ self.addSubview(view)
+ }
+
+ private func initDetails() {
+ let y: CGFloat = self.frame.height - self.dashboardHeight - Constants.Popup.separatorHeight
+ let separator = SeparatorView("Details", origin: NSPoint(x: 0, y: y), width: self.frame.width)
+ self.addSubview(separator)
+
+ let view: NSView = NSView(frame: NSRect(x: 0, y: separator.frame.origin.y - self.detailsHeight, width: self.frame.width, height: self.detailsHeight))
+
+ self.totalField = PopupRow(view, n: 2, title: "Total:", value: "")
+ self.usedField = PopupRow(view, n: 1, title: "Used:", value: "")
+ self.freeField = PopupRow(view, n: 0, title: "Free:", value: "")
+
+ self.addSubview(view)
+ }
+
+ private func addFirstRow(mView: NSView, y: CGFloat, title: String, value: String) -> NSTextField {
+ let rowView: NSView = NSView(frame: NSRect(x: 0, y: y, width: mView.frame.width, height: 16))
+
+ let labelWidth = title.widthOfString(usingFont: .systemFont(ofSize: 10, weight: .light)) + 4
+ let labelView: NSTextField = TextView(frame: NSRect(x: 0, y: 1.5, width: labelWidth, height: 13))
+ labelView.stringValue = title
+ labelView.alignment = .natural
+ labelView.font = NSFont.systemFont(ofSize: 10, weight: .light)
+
+ let valueView: NSTextField = TextView(frame: NSRect(x: labelWidth, y: 1, width: mView.frame.width - labelWidth, height: 14))
+ valueView.stringValue = value
+ valueView.alignment = .right
+ valueView.font = NSFont.systemFont(ofSize: 11, weight: .medium)
+
+ rowView.addSubview(labelView)
+ rowView.addSubview(valueView)
+ mView.addSubview(rowView)
+
+ return valueView
+ }
+
+ public func loadCallback(_ value: Usage) {
+ DispatchQueue.main.async(execute: {
+ if self.window!.isVisible || !self.initialized {
+ self.activeField?.stringValue = Units(bytes: Int64(value.active!)).getReadableMemory()
+ self.inactiveField?.stringValue = Units(bytes: Int64(value.inactive!)).getReadableMemory()
+ self.wiredField?.stringValue = Units(bytes: Int64(value.wired!)).getReadableMemory()
+ self.compressedField?.stringValue = Units(bytes: Int64(value.compressed!)).getReadableMemory()
+
+ self.totalField?.stringValue = Units(bytes: Int64(value.total!)).getReadableMemory()
+ self.usedField?.stringValue = Units(bytes: Int64(value.used!)).getReadableMemory()
+ self.freeField?.stringValue = Units(bytes: Int64(value.free!)).getReadableMemory()
+ self.initialized = true
+ }
+
+ self.chart?.addValue(value.usage!)
+ })
+ }
+}
diff --git a/Modules/Memory/readers.swift b/Modules/Memory/readers.swift
new file mode 100644
index 00000000..fc768320
--- /dev/null
+++ b/Modules/Memory/readers.swift
@@ -0,0 +1,72 @@
+//
+// readers.swift
+// Memory
+//
+// Created by Serhiy Mytrovtsiy on 12/04/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+import ModuleKit
+
+internal class UsageReader: Reader {
+ public var totalSize: Double = 0
+
+ public override func setup() {
+ var stats = host_basic_info()
+ var count = UInt32(MemoryLayout.size / MemoryLayout.size)
+
+ 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)
+ }
+ }
+
+ if kerr == KERN_SUCCESS {
+ self.totalSize = Double(stats.max_mem)
+ return
+ }
+
+ self.totalSize = 0
+ print("Error with host_info(): " + (String(cString: mach_error_string(kerr), encoding: String.Encoding.ascii) ?? "unknown error"))
+ }
+
+ public override func read() {
+ var stats = vm_statistics64()
+ var count = UInt32(MemoryLayout.size / MemoryLayout.size)
+
+ let result: kern_return_t = withUnsafeMutablePointer(to: &stats) {
+ $0.withMemoryRebound(to: integer_t.self, capacity: 1) {
+ host_statistics64(mach_host_self(), HOST_VM_INFO64, $0, &count)
+ }
+ }
+
+ if result == KERN_SUCCESS {
+ let active = Double(stats.active_count) * Double(PAGE_SIZE)
+ let inactive = Double(stats.inactive_count) * Double(PAGE_SIZE)
+ let wired = Double(stats.wire_count) * Double(PAGE_SIZE)
+ let compressed = Double(stats.compressor_page_count) * Double(PAGE_SIZE)
+
+ let used = active + wired + compressed
+ let free = self.totalSize - used
+
+ self.callback(Usage(
+ active: active,
+ inactive: inactive,
+ wired: wired,
+ compressed: compressed,
+
+ usage: Double((self.totalSize - free) / self.totalSize),
+ total: Double(self.totalSize),
+ used: Double(used),
+ free: Double(free))
+ )
+ return
+ }
+
+ print("Error with host_statistics64(): " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
+ }
+}
diff --git a/Modules/Net/Info.plist b/Modules/Net/Info.plist
new file mode 100644
index 00000000..20202aa7
--- /dev/null
+++ b/Modules/Net/Info.plist
@@ -0,0 +1,24 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+ NSHumanReadableCopyright
+ Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+
+
diff --git a/Modules/Net/config.plist b/Modules/Net/config.plist
new file mode 100644
index 00000000..53f09bd2
--- /dev/null
+++ b/Modules/Net/config.plist
@@ -0,0 +1,18 @@
+
+
+
+
+ Name
+ Network
+ State
+
+ Widgets
+
+ network
+
+ Default
+
+
+
+
+
diff --git a/Modules/Net/main.swift b/Modules/Net/main.swift
new file mode 100644
index 00000000..e275e6e4
--- /dev/null
+++ b/Modules/Net/main.swift
@@ -0,0 +1,86 @@
+//
+// main.swift
+// Net
+//
+// Created by Serhiy Mytrovtsiy on 24/05/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+import StatsKit
+import ModuleKit
+
+public enum Network_t: String {
+ case wifi
+ case ethernet
+}
+
+public struct Usage: value_t {
+ var active: Bool = false
+
+ var download: Int64 = 0
+ var upload: Int64 = 0
+
+ var laddr: String? = nil // local ip
+ var paddr: String? = nil // remote ip
+ var iaddr: String? = nil // mac adress
+
+ var connectionType: Network_t? = nil
+
+ var countryCode: String? = nil
+ var networkName: String? = nil
+
+ mutating func reset() {
+ self.active = false
+
+ self.download = 0
+ self.upload = 0
+
+ self.laddr = nil
+ self.paddr = nil
+ self.iaddr = nil
+
+ self.connectionType = nil
+
+ self.countryCode = nil
+ self.networkName = nil
+ }
+
+ public var widget_value: Double = 0
+}
+
+public class Network: Module {
+ private var usageReader: UsageReader = UsageReader()
+ private let popupView: Popup = Popup()
+
+ public init(_ store: UnsafePointer?) {
+ super.init(
+ store: store,
+ popup: self.popupView,
+ settings: nil
+ )
+
+ self.usageReader.readyCallback = { [unowned self] in
+ self.readyHandler()
+ }
+ self.usageReader.callbackHandler = { [unowned self] value in
+ self.usageCallback(value)
+ }
+
+ self.addReader(self.usageReader)
+ }
+
+ private func usageCallback(_ value: Usage?) {
+ if value == nil {
+ return
+ }
+
+ self.popupView.usageCallback(value!)
+ if let widget = self.widget as? NetworkWidget {
+ widget.setValue(upload: value!.upload, download: value!.download)
+ }
+ }
+}
diff --git a/Modules/Net/popup.swift b/Modules/Net/popup.swift
new file mode 100644
index 00000000..7f6410a9
--- /dev/null
+++ b/Modules/Net/popup.swift
@@ -0,0 +1,195 @@
+//
+// popup.swift
+// Net
+//
+// Created by Serhiy Mytrovtsiy on 24/05/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+import ModuleKit
+import StatsKit
+
+internal class Popup: NSView {
+ let dashboardHeight: CGFloat = 90
+ let detailsHeight: CGFloat = 88
+
+ private var dashboardView: NSView? = nil
+
+ private var uploadView: NSView? = nil
+ private var uploadValue: Int64 = 0
+ private var uploadValueField: NSTextField? = nil
+ private var uploadUnitField: NSTextField? = nil
+
+ private var downloadView: NSView? = nil
+ private var downloadValue: Int64 = 0
+ private var downloadValueField: NSTextField? = nil
+ private var downloadUnitField: NSTextField? = nil
+
+ private var publicIPField: NSTextField? = nil
+ private var localIPField: NSTextField? = nil
+ private var networkTypeField: NSTextField? = nil
+ private var macAdressField: NSTextField? = nil
+
+ private var initialized: Bool = false
+
+ public init() {
+ super.init(frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: dashboardHeight + Constants.Popup.separatorHeight + detailsHeight))
+
+ initDashboard()
+ initDetails()
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ private func initDashboard() {
+ let view: NSView = NSView(frame: NSRect(x: 0, y: self.frame.height - self.dashboardHeight, width: self.frame.width, height: self.dashboardHeight))
+
+ let leftPart: NSView = NSView(frame: NSRect(x: 0, y: 0, width: view.frame.width / 2, height: view.frame.height))
+ let uploadFields = self.topValueView(leftPart, title: "Upload")
+ self.uploadView = uploadFields.0
+ self.uploadValueField = uploadFields.1
+ self.uploadUnitField = uploadFields.2
+
+ let rightPart: NSView = NSView(frame: NSRect(x: view.frame.width / 2, y: 0, width: view.frame.width / 2, height: view.frame.height))
+ let downloadFields = self.topValueView(rightPart, title: "Download")
+ self.downloadView = downloadFields.0
+ self.downloadValueField = downloadFields.1
+ self.downloadUnitField = downloadFields.2
+
+ view.addSubview(leftPart)
+ view.addSubview(rightPart)
+ self.addSubview(view)
+ self.dashboardView = view
+ }
+
+ private func topValueView(_ view: NSView, title: String) -> (NSView, NSTextField, NSTextField) {
+ let topHeight: CGFloat = 30
+ let titleHeight: CGFloat = 15
+
+ let valueWidth = "0".widthOfString(usingFont: .systemFont(ofSize: 26, weight: .light)) + 5
+ let unitWidth = "KB/s".widthOfString(usingFont: .systemFont(ofSize: 13, weight: .light)) + 5
+ let topPartWidth = valueWidth + unitWidth
+
+ let topPart: NSView = NSView(frame: NSRect(x: (view.frame.width-topPartWidth)/2, y: (view.frame.height - topHeight - titleHeight)/2 + titleHeight, width: topPartWidth, height: topHeight))
+
+ let valueField = LabelField(frame: NSRect(x: 0, y: 0, width: valueWidth, height: 30), "0")
+ valueField.font = NSFont.systemFont(ofSize: 26, weight: .light)
+ valueField.textColor = .labelColor
+ valueField.alignment = .right
+
+ let unitField = LabelField(frame: NSRect(x: valueField.frame.width, y: 4, width: unitWidth, height: 15), "KB/s")
+ unitField.font = NSFont.systemFont(ofSize: 13, weight: .light)
+ unitField.textColor = .labelColor
+ unitField.alignment = .left
+
+ let titleField = LabelField(frame: NSRect(x: 0, y: topPart.frame.origin.y - titleHeight, width: view.frame.width, height: titleHeight), title)
+ titleField.alignment = .center
+
+ topPart.addSubview(valueField)
+ topPart.addSubview(unitField)
+ view.addSubview(topPart)
+ view.addSubview(titleField)
+
+ return (topPart, valueField, unitField)
+ }
+
+ private func setUploadDownloadFields() {
+ let upload = Units(bytes: self.uploadValue).getReadableTuple()
+ let download = Units(bytes: self.downloadValue).getReadableTuple()
+
+ var valueWidth = "\(upload.0)".widthOfString(usingFont: .systemFont(ofSize: 26, weight: .light)) + 5
+ var unitWidth = upload.1.widthOfString(usingFont: .systemFont(ofSize: 13, weight: .light)) + 5
+ var topPartWidth = valueWidth + unitWidth
+
+ self.uploadView?.setFrameSize(NSSize(width: topPartWidth, height: self.uploadView!.frame.height))
+ self.uploadView?.setFrameOrigin(NSPoint(x: ((self.frame.width/2)-topPartWidth)/2, y: self.uploadView!.frame.origin.y))
+
+ self.uploadValueField?.setFrameSize(NSSize(width: valueWidth, height: self.uploadValueField!.frame.height))
+ self.uploadValueField?.stringValue = "\(upload.0)"
+ self.uploadUnitField?.setFrameSize(NSSize(width: unitWidth, height: self.uploadUnitField!.frame.height))
+ self.uploadUnitField?.setFrameOrigin(NSPoint(x: self.uploadValueField!.frame.width, y: self.uploadUnitField!.frame.origin.y))
+ self.uploadUnitField?.stringValue = upload.1
+
+ valueWidth = "\(download.0)".widthOfString(usingFont: .systemFont(ofSize: 26, weight: .light)) + 5
+ unitWidth = download.1.widthOfString(usingFont: .systemFont(ofSize: 13, weight: .light)) + 5
+ topPartWidth = valueWidth + unitWidth
+
+ self.downloadView?.setFrameSize(NSSize(width: topPartWidth, height: self.downloadView!.frame.height))
+ self.downloadView?.setFrameOrigin(NSPoint(x: ((self.frame.width/2)-topPartWidth)/2, y: self.downloadView!.frame.origin.y))
+
+ self.downloadValueField?.setFrameSize(NSSize(width: valueWidth, height: self.downloadValueField!.frame.height))
+ self.downloadValueField?.stringValue = "\(download.0)"
+ self.downloadUnitField?.setFrameSize(NSSize(width: unitWidth, height: self.downloadUnitField!.frame.height))
+ self.downloadUnitField?.setFrameOrigin(NSPoint(x: self.downloadValueField!.frame.width, y: self.downloadUnitField!.frame.origin.y))
+ self.downloadUnitField?.stringValue = download.1
+ }
+
+ private func initDetails() {
+ let y: CGFloat = self.dashboardView!.frame.origin.y - Constants.Popup.separatorHeight
+ let separator = SeparatorView("Details", origin: NSPoint(x: 0, y: y), width: self.frame.width)
+ self.addSubview(separator)
+
+ let view: NSView = NSView(frame: NSRect(x: 0, y: separator.frame.origin.y - self.detailsHeight, width: self.frame.width, height: self.detailsHeight))
+
+ self.publicIPField = PopupRow(view, n: 3, title: "Public IP:", value: "")
+ self.localIPField = PopupRow(view, n: 2, title: "Local IP:", value: "")
+ self.networkTypeField = PopupRow(view, n: 1, title: "Network:", value: "")
+ self.macAdressField = PopupRow(view, n: 0, title: "Physical address:", value: "")
+
+ self.addSubview(view)
+ }
+
+ public func usageCallback(_ value: Usage) {
+ DispatchQueue.main.async(execute: {
+ if !self.window!.isVisible && self.initialized {
+ return
+ }
+
+ self.uploadValue = value.upload
+ self.downloadValue = value.download
+ self.setUploadDownloadFields()
+
+ if !value.active {
+ self.publicIPField?.stringValue = "No connection"
+ self.localIPField?.stringValue = "No connection"
+ self.networkTypeField?.stringValue = "No connection"
+ self.macAdressField?.stringValue = "No connection"
+ return
+ }
+
+ if var publicIP = value.paddr, self.publicIPField?.stringValue != publicIP {
+ if value.countryCode != nil {
+ publicIP = "\(publicIP) (\(value.countryCode!))"
+ }
+ self.publicIPField?.stringValue = publicIP
+ }
+ if value.laddr != nil && self.localIPField?.stringValue != value.laddr {
+ self.localIPField?.stringValue = value.laddr!
+ }
+ if value.iaddr != nil && self.macAdressField?.stringValue != value.iaddr {
+ self.macAdressField?.stringValue = value.iaddr!
+ }
+
+ if value.connectionType != nil {
+ var networkType = ""
+ if value.connectionType == .wifi {
+ networkType = "\(value.networkName!) (WiFi)"
+ } else if value.connectionType == .ethernet {
+ networkType = "Ethernet"
+ }
+
+ if self.networkTypeField?.stringValue != networkType {
+ self.networkTypeField?.stringValue = networkType
+ }
+ }
+
+ self.initialized = true
+ })
+ }
+}
diff --git a/Modules/Net/readers.swift b/Modules/Net/readers.swift
new file mode 100644
index 00000000..07ec825c
--- /dev/null
+++ b/Modules/Net/readers.swift
@@ -0,0 +1,214 @@
+//
+// readers.swift
+// Net
+//
+// Created by Serhiy Mytrovtsiy on 24/05/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+import ModuleKit
+import SystemConfiguration
+import Reachability
+import os.log
+import CoreWLAN
+
+internal class UsageReader: Reader {
+ private var reachability: Reachability? = nil
+ private var usage: Usage = Usage()
+
+ private var interfaceID: String? = nil
+
+ public override func setup() {
+ do {
+ self.reachability = try Reachability()
+ try self.reachability!.startNotifier()
+ } catch let error {
+ os_log(.error, log: log, "initialize Reachability error %s", "\(error)")
+ }
+
+ self.reachability!.whenReachable = { _ in
+ self.readInformation()
+ self.start()
+ }
+ self.reachability!.whenUnreachable = { _ in
+ self.usage.reset()
+ self.callback(self.usage)
+ self.stop()
+ }
+ }
+
+ public override func read() {
+ guard self.reachability?.connection != .unavailable else {
+ if self.usage.active {
+ self.usage.reset()
+ self.callback(self.usage)
+ }
+ return
+ }
+
+ var interfaceAddresses: UnsafeMutablePointer? = nil
+ var upload: Int64 = 0
+ var download: Int64 = 0
+ guard getifaddrs(&interfaceAddresses) == 0 else { return }
+
+ var pointer = interfaceAddresses
+ while pointer != nil {
+ defer { pointer = pointer?.pointee.ifa_next }
+
+ if String(cString: pointer!.pointee.ifa_name) != self.interfaceID {
+ continue
+ }
+
+ if let info = getBytesInfo(pointer!) {
+ upload += info.upload
+ download += info.download
+ }
+
+ if let ip = getLocalIP(pointer!), self.usage.laddr != ip {
+ self.usage.laddr = ip
+ }
+ }
+ freeifaddrs(interfaceAddresses)
+
+ if self.usage.upload != 0 && self.usage.download != 0 {
+ self.usage.upload = upload - self.usage.upload
+ self.usage.download = download - self.usage.download
+ }
+
+ self.callback(self.usage)
+ self.usage.upload = upload
+ self.usage.download = download
+ }
+
+ private func readInformation() {
+ guard self.reachability != nil && self.reachability!.connection != .unavailable else { return }
+
+ if let global = SCDynamicStoreCopyValue(nil, "State:/Network/Global/IPv4" as CFString) {
+ self.interfaceID = global["PrimaryInterface"] as? String
+ }
+
+ self.usage.active = true
+ DispatchQueue.global(qos: .background).async {
+ self.usage.paddr = self.getPublicIP()
+ }
+
+ if self.reachability!.connection == .wifi && CWWiFiClient.shared().interface() != nil {
+ self.usage.connectionType = .wifi
+ self.usage.networkName = CWWiFiClient.shared().interface()!.ssid()
+ self.usage.countryCode = CWWiFiClient.shared().interface()!.countryCode()
+ self.usage.iaddr = CWWiFiClient.shared().interface()!.hardwareAddress()
+ } else {
+ self.usage.connectionType = .ethernet
+ self.usage.iaddr = getMacAddress()
+ }
+ }
+
+ private func getDataUsageInfo(from infoPointer: UnsafeMutablePointer) -> (upload: Int64, download: Int64)? {
+ let pointer = infoPointer
+
+ let addr = pointer.pointee.ifa_addr.pointee
+ guard addr.sa_family == UInt8(AF_LINK) else { return nil }
+ var networkData: UnsafeMutablePointer? = nil
+
+ networkData = unsafeBitCast(pointer.pointee.ifa_data, to: UnsafeMutablePointer.self)
+ return (upload: Int64(networkData?.pointee.ifi_obytes ?? 0), download: Int64(networkData?.pointee.ifi_ibytes ?? 0))
+ }
+
+ private func getBytesInfo(_ pointer: UnsafeMutablePointer) -> (upload: Int64, download: Int64)? {
+ let addr = pointer.pointee.ifa_addr.pointee
+
+ guard addr.sa_family == UInt8(AF_LINK) else {
+ return nil
+ }
+
+ let data: UnsafeMutablePointer? = unsafeBitCast(pointer.pointee.ifa_data, to: UnsafeMutablePointer.self)
+ return (upload: Int64(data?.pointee.ifi_obytes ?? 0), download: Int64(data?.pointee.ifi_ibytes ?? 0))
+ }
+
+ private func getLocalIP(_ pointer: UnsafeMutablePointer) -> String? {
+ var addr = pointer.pointee.ifa_addr.pointee
+
+ guard addr.sa_family == UInt8(AF_INET) else {
+ return nil
+ }
+
+ var ip = [CChar](repeating: 0, count: Int(NI_MAXHOST))
+ getnameinfo(&addr, socklen_t(addr.sa_len), &ip, socklen_t(ip.count), nil, socklen_t(0), NI_NUMERICHOST)
+
+ return String(cString: ip)
+ }
+
+ private func getPublicIP() -> String? {
+ let url = URL(string: "https://api.ipify.org")
+ var address: String? = nil
+
+ do {
+ if let url = url {
+ address = try String(contentsOf: url)
+ if address!.contains("<") {
+ address = nil
+ }
+ }
+ } catch let error {
+ os_log(.error, log: log, "get public ip %s", "\(error)")
+ }
+
+ return address
+ }
+
+ // https://stackoverflow.com/questions/31835418/how-to-get-mac-address-from-os-x-with-swift
+ private func getMacAddress() -> String? {
+ var macAddressAsString : String?
+ if let intfIterator = findEthernetInterfaces() {
+ if let macAddress = getMACAddress(intfIterator) {
+ macAddressAsString = macAddress.map( { String(format:"%02x", $0) } ).joined(separator: ":")
+ }
+ IOObjectRelease(intfIterator)
+ }
+ return macAddressAsString
+ }
+
+ private func findEthernetInterfaces() -> io_iterator_t? {
+ let matchingDictUM = IOServiceMatching("IOEthernetInterface");
+ if matchingDictUM == nil {
+ return nil
+ }
+
+ let matchingDict = matchingDictUM! as NSMutableDictionary
+ matchingDict["IOPropertyMatch"] = [ "IOPrimaryInterface" : true]
+
+ var matchingServices : io_iterator_t = 0
+ if IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &matchingServices) != KERN_SUCCESS {
+ return nil
+ }
+
+ return matchingServices
+ }
+
+ private func getMACAddress(_ intfIterator : io_iterator_t) -> [UInt8]? {
+ var macAddress : [UInt8]?
+ var intfService = IOIteratorNext(intfIterator)
+
+ while intfService != 0 {
+ var controllerService : io_object_t = 0
+ if IORegistryEntryGetParentEntry(intfService, kIOServicePlane, &controllerService) == KERN_SUCCESS {
+ let dataUM = IORegistryEntryCreateCFProperty(controllerService, "IOMACAddress" as CFString, kCFAllocatorDefault, 0)
+ if dataUM != nil {
+ let data = (dataUM!.takeRetainedValue() as! CFData) as Data
+ macAddress = [0, 0, 0, 0, 0, 0]
+ data.copyBytes(to: &macAddress!, count: macAddress!.count)
+ }
+ IOObjectRelease(controllerService)
+ }
+
+ IOObjectRelease(intfService)
+ intfService = IOIteratorNext(intfIterator)
+ }
+
+ return macAddress
+ }
+}
diff --git a/README.md b/README.md
index 131802d9..35f75874 100644
--- a/README.md
+++ b/README.md
@@ -1,40 +1,39 @@
# Stats
-Simple macOS system monitor in your menu bar
+
[](https://github.com/exelban/stats/releases)
+Simple macOS system monitor in your menu bar
+
+## Installation
+You can download latest version [here](https://github.com/exelban/stats/releases).
+
+## Requirements
+
+Stats is currently supported on macOS 10.14 (Mojave) and higher.
+
## Features
Stats is a application which allows you to monitor your macOS system.
- CPU Usage
- Memory Usage
- - Sensors (Temperature/Voltage/Power)
- Disk utilization
- Battery level
- Network usage
- - Black theme compatible
-## Installation
-You can download latest version [here](https://github.com/exelban/stats/releases).
+## Developing
-## Modules
+Pull requests and impovment proposals are welcomed.
-| Name | Available widgets | Description |
-| --- | --- | --- |
-| **CPU** | Percentage / Chart / Chart with value / Chart Bar | Shows CPU usage |
-| **Memory** | Percentage / Chart / Chart with value / Chart Bar | Shows RAM usage |
-| **Sensors** | Text | Shows data from internal sensors |
-| **Disk** | Percentage / Chart Bar | Shows disk utilization |
-| **Battery** | Graphic / Percentage | Shows battery level and charging status |
-| **Newtork** | Dots / Upload/Download traffic | Shows network activity |
+If you want to run the project locally you need to have [carthage](https://github.com/Carthage/Carthage#installing-carthage) installed.
-## Compatibility
-| macOS | Compatible |
-| --- | --- |
-| 10.15.3 *(Catalina)* | **true** |
-| 10.14.6 *(Mojave)* | **true** |
-| 10.13.6 *(High Sierra)* | **true** |
+```bash
+git clone https://github.com/exelban/stats
+cd stats
+make dep
+open ./Stats.xcodeproj
+```
## License
[MIT License](https://github.com/exelban/stats/blob/master/LICENSE)
diff --git a/Stats.xcodeproj/project.pbxproj b/Stats.xcodeproj/project.pbxproj
index 40f8964f..938a1c01 100644
--- a/Stats.xcodeproj/project.pbxproj
+++ b/Stats.xcodeproj/project.pbxproj
@@ -7,80 +7,290 @@
objects = {
/* Begin PBXBuildFile section */
- 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 */; };
- 9A2D15D223CCEC7600C4C417 /* Module.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15D123CCEC7600C4C417 /* Module.swift */; };
- 9A2D15D523CCEFF700C4C417 /* CPU.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15D423CCEFF700C4C417 /* CPU.swift */; };
- 9A2D15D723CCFE1B00C4C417 /* CPULoadReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15D623CCFE1B00C4C417 /* CPULoadReader.swift */; };
- 9A2D15D923CD036400C4C417 /* CPUMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15D823CD036400C4C417 /* CPUMenu.swift */; };
- 9A2D15DB23CD0B2100C4C417 /* CPUPopup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15DA23CD0B2100C4C417 /* CPUPopup.swift */; };
- 9A2D15E123CD133300C4C417 /* CPUUsageReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15E023CD133300C4C417 /* CPUUsageReader.swift */; };
- 9A2D15E323CD1E4B00C4C417 /* CPUProcessReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15E223CD1E4B00C4C417 /* CPUProcessReader.swift */; };
- 9A2D15E623CE291600C4C417 /* RAM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15E523CE291600C4C417 /* RAM.swift */; };
- 9A2D15E823CE29A400C4C417 /* RAMMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15E723CE29A400C4C417 /* RAMMenu.swift */; };
- 9A2D15EA23CE2C1100C4C417 /* RAMPopup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15E923CE2C1100C4C417 /* RAMPopup.swift */; };
- 9A2D15EE23CE2EE200C4C417 /* RAMUsageReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15ED23CE2EE200C4C417 /* RAMUsageReader.swift */; };
- 9A2D15F023CE32D500C4C417 /* RAMProcessReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15EF23CE32D500C4C417 /* RAMProcessReader.swift */; };
- 9A2D15F323CE391300C4C417 /* Disk.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15F223CE391300C4C417 /* Disk.swift */; };
- 9A2D15F523CE393A00C4C417 /* DiskMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15F423CE393A00C4C417 /* DiskMenu.swift */; };
- 9A2D15F723CE3A1200C4C417 /* DiskCapacityReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15F623CE3A1200C4C417 /* DiskCapacityReader.swift */; };
- 9A2D15FA23CE3BE600C4C417 /* Battery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15F923CE3BE600C4C417 /* Battery.swift */; };
- 9A2D15FC23CE3C1A00C4C417 /* BatteryMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15FB23CE3C1A00C4C417 /* BatteryMenu.swift */; };
- 9A2D15FE23CE3DE300C4C417 /* BatteryReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15FD23CE3DE300C4C417 /* BatteryReader.swift */; };
- 9A2D160023CE40DE00C4C417 /* BatteryPopup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15FF23CE40DE00C4C417 /* BatteryPopup.swift */; };
- 9A2D160323CE445900C4C417 /* Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D160223CE445900C4C417 /* Network.swift */; };
- 9A2D160523CE451B00C4C417 /* NetworkMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D160423CE451B00C4C417 /* NetworkMenu.swift */; };
- 9A2D160723CE462400C4C417 /* NetworkReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D160623CE462400C4C417 /* NetworkReader.swift */; };
- 9A3434F1243E19E6006B19F9 /* LaunchAtLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A3434F0243E19E6006B19F9 /* LaunchAtLogin.swift */; };
+ 9A0C82DE24460F7200FAE3D4 /* StatsKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A0C82DC24460F7200FAE3D4 /* StatsKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 9A0C82E124460F7200FAE3D4 /* StatsKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */; };
+ 9A0C82E224460F7200FAE3D4 /* StatsKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 9A0C82E624460F9A00FAE3D4 /* extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A654920244074B500E30B74 /* extensions.swift */; };
+ 9A0C82E724460F9C00FAE3D4 /* updater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A0C82D124460DFF00FAE3D4 /* updater.swift */; };
+ 9A0C82E824460F9E00FAE3D4 /* launchAtLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A0C82D324460E4400FAE3D4 /* launchAtLogin.swift */; };
+ 9A0C82E924460F9F00FAE3D4 /* store.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A65492224407EA600E30B74 /* store.swift */; };
+ 9A0C82EA24460FB100FAE3D4 /* StatsKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */; };
+ 9A0C82EB24460FB100FAE3D4 /* StatsKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 9A0C82EE2446124800FAE3D4 /* SystemKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7D0CB62444C2C800B09070 /* SystemKit.swift */; };
+ 9A1A7ABA24561F0B00A84F7A /* BarChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1A7AB924561F0B00A84F7A /* BarChart.swift */; };
+ 9A313BF7247EF01800DB5101 /* Reachability.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A5349CD23D8832E00C23824 /* Reachability.framework */; };
+ 9A313BF8247EF01800DB5101 /* Reachability.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A5349CD23D8832E00C23824 /* Reachability.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
9A34353B243E278D006B19F9 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A34353A243E278D006B19F9 /* main.swift */; };
9A34353C243E27E8006B19F9 /* LaunchAtLogin.app in Copy Files */ = {isa = PBXBuildFile; fileRef = 9A343527243E26A0006B19F9 /* LaunchAtLogin.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
- 9A426DB822C2B5EE00C064C4 /* MacAppUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A426DB722C2B5EE00C064C4 /* MacAppUpdater.swift */; };
- 9A426DBE22C2BE0000C064C4 /* Updates.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9A426DBD22C2BE0000C064C4 /* Updates.storyboard */; };
- 9A5349C723D8535900C23824 /* NetworkPopup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A5349C623D8535900C23824 /* NetworkPopup.swift */; };
- 9A5349C923D8642A00C23824 /* NetworkInterfaceReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A5349C823D8642A00C23824 /* NetworkInterfaceReader.swift */; };
- 9A5349CE23D8832E00C23824 /* Reachability.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A5349CD23D8832E00C23824 /* Reachability.framework */; };
- 9A5349CF23D8832E00C23824 /* Reachability.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A5349CD23D8832E00C23824 /* Reachability.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
- 9A54EF67232AB81800F7DC20 /* BatteryPercentageWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A54EF66232AB81800F7DC20 /* BatteryPercentageWidget.swift */; };
- 9A54EF69232AB82700F7DC20 /* BatteryTimeWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A54EF68232AB82700F7DC20 /* BatteryTimeWidget.swift */; };
- 9A57A18522A1D26D0033E318 /* MenuBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A57A18422A1D26D0033E318 /* MenuBar.swift */; };
- 9A59AE56231EE02F007989D6 /* ChartMarker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A59AE55231EE02F007989D6 /* ChartMarker.swift */; };
- 9A5B1CC5229E7B40008B9D3C /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A5B1CC4229E7B40008B9D3C /* Extensions.swift */; };
- 9A606B4A2321577400642F51 /* UpdatesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A606B492321577400642F51 /* UpdatesViewController.swift */; };
- 9A606B4C232157BA00642F51 /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A606B4B232157BA00642F51 /* AboutViewController.swift */; };
- 9A6698E62326AB16001D00E1 /* Charts.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A6698E32326AAE5001D00E1 /* Charts.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 9A3E17BD247A8F5700449CD1 /* StatsKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */; };
+ 9A3E17BE247A8F5700449CD1 /* StatsKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 9A3E17C1247A8F5E00449CD1 /* StatsKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */; };
+ 9A3E17C2247A8F5E00449CD1 /* StatsKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 9A3E17D3247A94AF00449CD1 /* Net.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A3E17CC247A94AF00449CD1 /* Net.framework */; };
+ 9A3E17D4247A94AF00449CD1 /* Net.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A3E17CC247A94AF00449CD1 /* Net.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 9A3E17D9247A94B500449CD1 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A3E17D8247A94B500449CD1 /* main.swift */; };
+ 9A3E17DB247A94BC00449CD1 /* readers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A3E17DA247A94BC00449CD1 /* readers.swift */; };
+ 9A3E17DE247A94DC00449CD1 /* ModuleKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEADD243FB13500668CB0 /* ModuleKit.framework */; };
+ 9A3E17DF247A94DC00449CD1 /* ModuleKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEADD243FB13500668CB0 /* ModuleKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 9A3E17E2247A94DC00449CD1 /* StatsKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */; };
+ 9A3E17E3247A94DC00449CD1 /* StatsKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 9A3E17E8247AA8E100449CD1 /* Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A3E17E7247AA8E100449CD1 /* Network.swift */; };
+ 9A3E17EA247B07BF00449CD1 /* popup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A3E17E9247B07BF00449CD1 /* popup.swift */; };
+ 9A5AF11B2469CE9B00684737 /* popup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A5AF11A2469CE9B00684737 /* popup.swift */; };
+ 9A6549292440A57200E30B74 /* Repeat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A6549282440A57200E30B74 /* Repeat.framework */; };
+ 9A65492A2440A57200E30B74 /* Repeat.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A6549282440A57200E30B74 /* Repeat.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
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 */; };
- 9A7DE036245982460084BD7A /* Repeat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A7DE035245982460084BD7A /* Repeat.framework */; };
- 9A7DE037245982460084BD7A /* Repeat.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A7DE035245982460084BD7A /* Repeat.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
- 9AA28DC1243774ED00D2B196 /* Sensors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA28DC0243774ED00D2B196 /* Sensors.swift */; };
- 9AA28DC32437752D00D2B196 /* SensorsMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA28DC22437752D00D2B196 /* SensorsMenu.swift */; };
- 9AA28DD1243799E500D2B196 /* SensorsWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA28DD0243799E500D2B196 /* SensorsWidget.swift */; };
- 9AA28DD6243A8A3D00D2B196 /* SMC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA28DD5243A8A3D00D2B196 /* SMC.swift */; };
- 9AA28DDB243B4AF500D2B196 /* SensorsType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA28DDA243B4AF500D2B196 /* SensorsType.swift */; };
- 9AF0F31B22DA924000026AE6 /* LineChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF0F31A22DA924000026AE6 /* LineChart.swift */; };
- 9AF0F31D22DA925000026AE6 /* LineChartWithValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF0F31C22DA925000026AE6 /* LineChartWithValue.swift */; };
- 9AF0F31F22DA925700026AE6 /* BarChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF0F31E22DA925700026AE6 /* BarChart.swift */; };
- 9AF0F32122DA92AD00026AE6 /* NetworkDots.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF0F32022DA92AD00026AE6 /* NetworkDots.swift */; };
- 9AF0F32322DA92B900026AE6 /* NetworkArrows.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF0F32222DA92B900026AE6 /* NetworkArrows.swift */; };
- 9AF0F32522DA92C400026AE6 /* NetworkText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF0F32422DA92C400026AE6 /* NetworkText.swift */; };
- 9AF0F32722DA92DD00026AE6 /* NetworkDotsText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF0F32622DA92DD00026AE6 /* NetworkDotsText.swift */; };
- 9AF0F32922DA92E800026AE6 /* NetworkArrowsText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF0F32822DA92E800026AE6 /* NetworkArrowsText.swift */; };
- 9AF6F1FE231D732600B8E1E4 /* PopupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF6F1FD231D732600B8E1E4 /* PopupViewController.swift */; };
- 9AFFCB3B22B3FD0500B0E6D8 /* About.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9AFFCB3A22B3FD0500B0E6D8 /* About.storyboard */; };
+ 9A7C61B42440DF810032695D /* Mini.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7C61B32440DF810032695D /* Mini.swift */; };
+ 9A81C74D24499C7000825D92 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A81C74B24499C7000825D92 /* AppSettings.swift */; };
+ 9A81C74E24499C7000825D92 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A81C74C24499C7000825D92 /* Settings.swift */; };
+ 9A81C75024499D6600825D92 /* settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A81C74F24499D6600825D92 /* settings.swift */; };
+ 9A81C75D2449A41400825D92 /* Memory.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A81C7562449A41400825D92 /* Memory.framework */; };
+ 9A81C75E2449A41400825D92 /* Memory.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A81C7562449A41400825D92 /* Memory.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 9A81C7622449A41E00825D92 /* ModuleKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEADD243FB13500668CB0 /* ModuleKit.framework */; };
+ 9A81C7632449A41E00825D92 /* ModuleKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEADD243FB13500668CB0 /* ModuleKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 9A81C7692449A43600825D92 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A81C7672449A43600825D92 /* main.swift */; };
+ 9A81C76A2449A43600825D92 /* readers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A81C7682449A43600825D92 /* readers.swift */; };
+ 9A81C76B2449AE9400825D92 /* StatsKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */; };
+ 9A81C76C2449AE9400825D92 /* StatsKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 9A81C7702449B8D500825D92 /* Charts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A81C76F2449B8D500825D92 /* Charts.swift */; };
+ 9A944D55244920690058F32A /* reader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A944D54244920690058F32A /* reader.swift */; };
+ 9A944D59244920FE0058F32A /* readers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A944D58244920FE0058F32A /* readers.swift */; };
+ 9A944D5B244925720058F32A /* widget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A944D5A244925720058F32A /* widget.swift */; };
+ 9A944D5D24492A8B0058F32A /* popup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A944D5C24492A8B0058F32A /* popup.swift */; };
+ 9A944D5F24492AA60058F32A /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A944D5E24492AA60058F32A /* Constants.swift */; };
+ 9A944D6124492B6D0058F32A /* popup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A944D6024492B6D0058F32A /* popup.swift */; };
+ 9A9D728A24471FAE005CF997 /* SMC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A9D728924471FAE005CF997 /* SMC.swift */; };
+ 9A9EA9452476D34500E3B883 /* Update.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A9EA9442476D34500E3B883 /* Update.swift */; };
+ 9AA4A00A2443656D00ECCF07 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9AA4A0092443656D00ECCF07 /* Assets.xcassets */; };
+ 9AA64260244B274200416A33 /* popup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA6425F244B274200416A33 /* popup.swift */; };
+ 9AA64262244B57C800416A33 /* settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA64261244B57C800416A33 /* settings.swift */; };
+ 9AA64264244B94F300416A33 /* LineChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA64263244B94F300416A33 /* LineChart.swift */; };
+ 9AABEAE4243FB13500668CB0 /* ModuleKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEADD243FB13500668CB0 /* ModuleKit.framework */; };
+ 9AABEAE5243FB13500668CB0 /* ModuleKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEADD243FB13500668CB0 /* ModuleKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 9AABEAEA243FB15E00668CB0 /* module.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AABEAE9243FB15E00668CB0 /* module.swift */; };
+ 9AABEB6B243FCE8A00668CB0 /* CPU.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEB64243FCE8A00668CB0 /* CPU.framework */; };
+ 9AABEB6C243FCE8A00668CB0 /* CPU.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEB64243FCE8A00668CB0 /* CPU.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 9AABEB71243FCE9400668CB0 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AABEB70243FCE9400668CB0 /* main.swift */; };
+ 9AABEB72243FCEEF00668CB0 /* ModuleKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEADD243FB13500668CB0 /* ModuleKit.framework */; };
+ 9AABEB73243FCEEF00668CB0 /* ModuleKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEADD243FB13500668CB0 /* ModuleKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 9AABEB7A243FD26200668CB0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AABEB79243FD26200668CB0 /* AppDelegate.swift */; };
+ 9AABEB7E243FDEF100668CB0 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AABEB7D243FDEF100668CB0 /* main.swift */; };
+ 9AB14B77248CEF3500DC6731 /* config.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9AF9EE192464A7B3005D2270 /* config.plist */; };
+ 9AB14B78248CEF3B00DC6731 /* config.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9AF9EE12246492E8005D2270 /* config.plist */; };
+ 9AB14B79248CEF4100DC6731 /* config.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9A3E17DC247A94C300449CD1 /* config.plist */; };
+ 9AB14B7A248CEF4900DC6731 /* config.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9ABFF904248BEC0B00C9041A /* config.plist */; };
+ 9AB14B7B248CF00F00DC6731 /* config.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9AF9EE1B2464A7BA005D2270 /* config.plist */; };
+ 9AB7FD7C246B48DB00387FDA /* settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AB7FD7B246B48DB00387FDA /* settings.swift */; };
+ 9ABFF8FD248BEBCB00C9041A /* Battery.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9ABFF8F6248BEBCB00C9041A /* Battery.framework */; };
+ 9ABFF8FE248BEBCB00C9041A /* Battery.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9ABFF8F6248BEBCB00C9041A /* Battery.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 9ABFF903248BEBD700C9041A /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ABFF902248BEBD700C9041A /* main.swift */; };
+ 9ABFF906248BEC2600C9041A /* ModuleKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEADD243FB13500668CB0 /* ModuleKit.framework */; };
+ 9ABFF907248BEC2600C9041A /* ModuleKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEADD243FB13500668CB0 /* ModuleKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 9ABFF90B248BEC2900C9041A /* StatsKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */; };
+ 9ABFF90C248BEC2900C9041A /* StatsKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 9ABFF910248BEE7200C9041A /* readers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ABFF90F248BEE7200C9041A /* readers.swift */; };
+ 9ABFF912248BF39500C9041A /* Battery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ABFF911248BF39500C9041A /* Battery.swift */; };
+ 9ABFF914248C30A800C9041A /* popup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ABFF913248C30A800C9041A /* popup.swift */; };
+ 9AF9EE0924648751005D2270 /* Disk.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AF9EE0224648751005D2270 /* Disk.framework */; };
+ 9AF9EE0A24648751005D2270 /* Disk.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9AF9EE0224648751005D2270 /* Disk.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 9AF9EE0F2464875F005D2270 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF9EE0E2464875F005D2270 /* main.swift */; };
+ 9AF9EE1124648ADC005D2270 /* readers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF9EE1024648ADC005D2270 /* readers.swift */; };
+ 9AF9EE1424649BAD005D2270 /* ModuleKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEADD243FB13500668CB0 /* ModuleKit.framework */; };
+ 9AF9EE1524649BAD005D2270 /* ModuleKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEADD243FB13500668CB0 /* ModuleKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
/* End PBXBuildFile section */
+/* Begin PBXContainerItemProxy section */
+ 9A0C82DF24460F7200FAE3D4 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 9A1410ED229E721100D29793 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 9A0C82D924460F7200FAE3D4;
+ remoteInfo = StatsKit;
+ };
+ 9A0C82EC24460FB100FAE3D4 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 9A1410ED229E721100D29793 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 9A0C82D924460F7200FAE3D4;
+ remoteInfo = StatsKit;
+ };
+ 9A3E17BF247A8F5700449CD1 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 9A1410ED229E721100D29793 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 9A0C82D924460F7200FAE3D4;
+ remoteInfo = StatsKit;
+ };
+ 9A3E17C3247A8F5E00449CD1 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 9A1410ED229E721100D29793 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 9A0C82D924460F7200FAE3D4;
+ remoteInfo = StatsKit;
+ };
+ 9A3E17D1247A94AF00449CD1 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 9A1410ED229E721100D29793 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 9A3E17CB247A94AF00449CD1;
+ remoteInfo = Net;
+ };
+ 9A3E17E0247A94DC00449CD1 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 9A1410ED229E721100D29793 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 9AABEADC243FB13500668CB0;
+ remoteInfo = ModuleKit;
+ };
+ 9A3E17E4247A94DC00449CD1 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 9A1410ED229E721100D29793 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 9A0C82D924460F7200FAE3D4;
+ remoteInfo = StatsKit;
+ };
+ 9A81C75B2449A41400825D92 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 9A1410ED229E721100D29793 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 9A81C7552449A41400825D92;
+ remoteInfo = Memory;
+ };
+ 9A81C7642449A41E00825D92 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 9A1410ED229E721100D29793 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 9AABEADC243FB13500668CB0;
+ remoteInfo = ModuleKit;
+ };
+ 9A81C76D2449AE9400825D92 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 9A1410ED229E721100D29793 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 9A0C82D924460F7200FAE3D4;
+ remoteInfo = StatsKit;
+ };
+ 9AABEAE2243FB13500668CB0 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 9A1410ED229E721100D29793 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 9AABEADC243FB13500668CB0;
+ remoteInfo = ModuleKit;
+ };
+ 9AABEB69243FCE8A00668CB0 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 9A1410ED229E721100D29793 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 9AABEB63243FCE8A00668CB0;
+ remoteInfo = CPU;
+ };
+ 9AABEB74243FCEEF00668CB0 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 9A1410ED229E721100D29793 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 9AABEADC243FB13500668CB0;
+ remoteInfo = ModuleKit;
+ };
+ 9ABFF8FB248BEBCB00C9041A /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 9A1410ED229E721100D29793 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 9ABFF8F5248BEBCB00C9041A;
+ remoteInfo = Battery;
+ };
+ 9ABFF908248BEC2600C9041A /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 9A1410ED229E721100D29793 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 9AABEADC243FB13500668CB0;
+ remoteInfo = ModuleKit;
+ };
+ 9ABFF90D248BEC2900C9041A /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 9A1410ED229E721100D29793 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 9A0C82D924460F7200FAE3D4;
+ remoteInfo = StatsKit;
+ };
+ 9AF9EE0724648751005D2270 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 9A1410ED229E721100D29793 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 9AF9EE0124648751005D2270;
+ remoteInfo = Disk;
+ };
+ 9AF9EE1624649BAD005D2270 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 9A1410ED229E721100D29793 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 9AABEADC243FB13500668CB0;
+ remoteInfo = ModuleKit;
+ };
+/* End PBXContainerItemProxy section */
+
/* Begin PBXCopyFilesBuildPhase section */
+ 9A3E17E6247A94DC00449CD1 /* Embed Frameworks */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ 9A3E17DF247A94DC00449CD1 /* ModuleKit.framework in Embed Frameworks */,
+ 9A313BF8247EF01800DB5101 /* Reachability.framework in Embed Frameworks */,
+ 9A3E17E3247A94DC00449CD1 /* StatsKit.framework in Embed Frameworks */,
+ );
+ name = "Embed Frameworks";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 9A65492B2440A57200E30B74 /* Embed Frameworks */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ 9A65492A2440A57200E30B74 /* Repeat.framework in Embed Frameworks */,
+ 9A0C82EB24460FB100FAE3D4 /* StatsKit.framework in Embed Frameworks */,
+ );
+ name = "Embed Frameworks";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
9A6698E72326AB16001D00E1 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
- 9A5349CF23D8832E00C23824 /* Reachability.framework in Embed Frameworks */,
- 9A7DE037245982460084BD7A /* Repeat.framework in Embed Frameworks */,
- 9A6698E62326AB16001D00E1 /* Charts.framework in Embed Frameworks */,
+ 9AF9EE0A24648751005D2270 /* Disk.framework in Embed Frameworks */,
+ 9A81C75E2449A41400825D92 /* Memory.framework in Embed Frameworks */,
+ 9ABFF8FE248BEBCB00C9041A /* Battery.framework in Embed Frameworks */,
+ 9AABEB6C243FCE8A00668CB0 /* CPU.framework in Embed Frameworks */,
+ 9AABEAE5243FB13500668CB0 /* ModuleKit.framework in Embed Frameworks */,
+ 9A3E17D4247A94AF00449CD1 /* Net.framework in Embed Frameworks */,
+ 9A0C82E224460F7200FAE3D4 /* StatsKit.framework in Embed Frameworks */,
+ );
+ name = "Embed Frameworks";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 9A81C7662449A41E00825D92 /* Embed Frameworks */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ 9A81C7632449A41E00825D92 /* ModuleKit.framework in Embed Frameworks */,
+ 9A81C76C2449AE9400825D92 /* StatsKit.framework in Embed Frameworks */,
+ );
+ name = "Embed Frameworks";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 9AABEB76243FCEEF00668CB0 /* Embed Frameworks */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 12;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ 9AABEB73243FCEEF00668CB0 /* ModuleKit.framework in Embed Frameworks */,
+ 9A3E17BE247A8F5700449CD1 /* StatsKit.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
@@ -96,84 +306,127 @@
name = "Copy Files";
runOnlyForDeploymentPostprocessing = 0;
};
+ 9ABFF90A248BEC2600C9041A /* Embed Frameworks */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ 9ABFF907248BEC2600C9041A /* ModuleKit.framework in Embed Frameworks */,
+ 9ABFF90C248BEC2900C9041A /* StatsKit.framework in Embed Frameworks */,
+ );
+ name = "Embed Frameworks";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 9AF9EE1824649BAD005D2270 /* Embed Frameworks */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ 9AF9EE1524649BAD005D2270 /* ModuleKit.framework in Embed Frameworks */,
+ 9A3E17C2247A8F5E00449CD1 /* StatsKit.framework in Embed Frameworks */,
+ );
+ name = "Embed Frameworks";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
- 9A09C8A122B3D94D0018426F /* BatteryWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryWidget.swift; sourceTree = ""; };
+ 9A0C82D124460DFF00FAE3D4 /* updater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = updater.swift; sourceTree = ""; };
+ 9A0C82D324460E4400FAE3D4 /* launchAtLogin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = launchAtLogin.swift; sourceTree = ""; };
+ 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StatsKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 9A0C82DC24460F7200FAE3D4 /* StatsKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StatsKit.h; sourceTree = ""; };
+ 9A0C82DD24460F7200FAE3D4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
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 = ""; };
- 9A1410FF229E721200D29793 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
9A141101229E721200D29793 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- 9A2D15D123CCEC7600C4C417 /* Module.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Module.swift; sourceTree = ""; };
- 9A2D15D423CCEFF700C4C417 /* CPU.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPU.swift; sourceTree = ""; };
- 9A2D15D623CCFE1B00C4C417 /* CPULoadReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPULoadReader.swift; sourceTree = ""; };
- 9A2D15D823CD036400C4C417 /* CPUMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPUMenu.swift; sourceTree = ""; };
- 9A2D15DA23CD0B2100C4C417 /* CPUPopup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPUPopup.swift; sourceTree = ""; };
- 9A2D15E023CD133300C4C417 /* CPUUsageReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPUUsageReader.swift; sourceTree = ""; };
- 9A2D15E223CD1E4B00C4C417 /* CPUProcessReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPUProcessReader.swift; sourceTree = ""; };
- 9A2D15E523CE291600C4C417 /* RAM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RAM.swift; sourceTree = ""; };
- 9A2D15E723CE29A400C4C417 /* RAMMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RAMMenu.swift; sourceTree = ""; };
- 9A2D15E923CE2C1100C4C417 /* RAMPopup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RAMPopup.swift; sourceTree = ""; };
- 9A2D15ED23CE2EE200C4C417 /* RAMUsageReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RAMUsageReader.swift; sourceTree = ""; };
- 9A2D15EF23CE32D500C4C417 /* RAMProcessReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RAMProcessReader.swift; sourceTree = ""; };
- 9A2D15F223CE391300C4C417 /* Disk.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Disk.swift; sourceTree = ""; };
- 9A2D15F423CE393A00C4C417 /* DiskMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiskMenu.swift; sourceTree = ""; };
- 9A2D15F623CE3A1200C4C417 /* DiskCapacityReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiskCapacityReader.swift; sourceTree = ""; };
- 9A2D15F923CE3BE600C4C417 /* Battery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Battery.swift; sourceTree = ""; };
- 9A2D15FB23CE3C1A00C4C417 /* BatteryMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryMenu.swift; sourceTree = ""; };
- 9A2D15FD23CE3DE300C4C417 /* BatteryReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryReader.swift; sourceTree = ""; };
- 9A2D15FF23CE40DE00C4C417 /* BatteryPopup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryPopup.swift; sourceTree = ""; };
- 9A2D160223CE445900C4C417 /* Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = ""; };
- 9A2D160423CE451B00C4C417 /* NetworkMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMenu.swift; sourceTree = ""; };
- 9A2D160623CE462400C4C417 /* NetworkReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkReader.swift; sourceTree = ""; };
- 9A3434F0243E19E6006B19F9 /* LaunchAtLogin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchAtLogin.swift; sourceTree = ""; };
+ 9A1A7AB924561F0B00A84F7A /* BarChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarChart.swift; sourceTree = ""; };
+ 9A1CC5AC24615E5C0023F4E8 /* IntelPowerGadget.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IntelPowerGadget.framework; path = StatsKit/IntelPowerGadget/IntelPowerGadget.framework; sourceTree = ""; };
9A343527243E26A0006B19F9 /* LaunchAtLogin.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LaunchAtLogin.app; sourceTree = BUILT_PRODUCTS_DIR; };
9A343535243E26A0006B19F9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
9A343536243E26A0006B19F9 /* LaunchAtLogin.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = LaunchAtLogin.entitlements; sourceTree = ""; };
9A34353A243E278D006B19F9 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; };
- 9A426DB722C2B5EE00C064C4 /* MacAppUpdater.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MacAppUpdater.swift; sourceTree = ""; };
- 9A426DBD22C2BE0000C064C4 /* Updates.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Updates.storyboard; sourceTree = ""; };
- 9A5349C623D8535900C23824 /* NetworkPopup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkPopup.swift; sourceTree = ""; };
- 9A5349C823D8642A00C23824 /* NetworkInterfaceReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkInterfaceReader.swift; sourceTree = ""; };
+ 9A3E17CC247A94AF00449CD1 /* Net.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Net.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 9A3E17CF247A94AF00449CD1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 9A3E17D8247A94B500449CD1 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; };
+ 9A3E17DA247A94BC00449CD1 /* readers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = readers.swift; sourceTree = ""; };
+ 9A3E17DC247A94C300449CD1 /* config.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = config.plist; sourceTree = ""; };
+ 9A3E17E7247AA8E100449CD1 /* Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = ""; };
+ 9A3E17E9247B07BF00449CD1 /* popup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = popup.swift; sourceTree = ""; };
9A5349CD23D8832E00C23824 /* Reachability.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Reachability.framework; path = Carthage/Build/Mac/Reachability.framework; sourceTree = ""; };
- 9A54EF66232AB81800F7DC20 /* BatteryPercentageWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryPercentageWidget.swift; sourceTree = ""; };
- 9A54EF68232AB82700F7DC20 /* BatteryTimeWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryTimeWidget.swift; sourceTree = ""; };
- 9A57A18422A1D26D0033E318 /* MenuBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBar.swift; sourceTree = ""; };
- 9A59AE55231EE02F007989D6 /* ChartMarker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartMarker.swift; sourceTree = ""; };
- 9A5B1CC4229E7B40008B9D3C /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; };
- 9A606B492321577400642F51 /* UpdatesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdatesViewController.swift; sourceTree = ""; };
- 9A606B4B232157BA00642F51 /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; };
- 9A6698E32326AAE5001D00E1 /* Charts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Charts.framework; path = Carthage/Build/Mac/Charts.framework; sourceTree = ""; };
+ 9A5AF11A2469CE9B00684737 /* popup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = popup.swift; sourceTree = ""; };
+ 9A654920244074B500E30B74 /* extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = extensions.swift; sourceTree = ""; };
+ 9A65492224407EA600E30B74 /* store.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = store.swift; sourceTree = ""; };
+ 9A6549282440A57200E30B74 /* Repeat.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Repeat.framework; path = Carthage/Build/Mac/Repeat.framework; sourceTree = ""; };
9A6CFC0022A1C9F5001E782D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
- 9A74D59622B44498004FE1FA /* Mini.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mini.swift; sourceTree = ""; };
- 9A79B36922D3BEE600BF1C3A /* Widget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Widget.swift; sourceTree = ""; };
- 9A7DE035245982460084BD7A /* Repeat.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Repeat.framework; path = Carthage/Build/Mac/Repeat.framework; sourceTree = ""; };
+ 9A7C61B32440DF810032695D /* Mini.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mini.swift; sourceTree = ""; };
+ 9A7D0CB62444C2C800B09070 /* SystemKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemKit.swift; sourceTree = ""; };
+ 9A81C74B24499C7000825D92 /* AppSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = ""; };
+ 9A81C74C24499C7000825D92 /* Settings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; };
+ 9A81C74F24499D6600825D92 /* settings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = settings.swift; sourceTree = ""; };
+ 9A81C7562449A41400825D92 /* Memory.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Memory.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 9A81C7592449A41400825D92 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 9A81C7672449A43600825D92 /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; };
+ 9A81C7682449A43600825D92 /* readers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = readers.swift; sourceTree = ""; };
+ 9A81C76F2449B8D500825D92 /* Charts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Charts.swift; sourceTree = ""; };
+ 9A944D54244920690058F32A /* reader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = reader.swift; sourceTree = ""; };
+ 9A944D58244920FE0058F32A /* readers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = readers.swift; sourceTree = ""; };
+ 9A944D5A244925720058F32A /* widget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = widget.swift; sourceTree = ""; };
+ 9A944D5C24492A8B0058F32A /* popup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = popup.swift; sourceTree = ""; };
+ 9A944D5E24492AA60058F32A /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; };
+ 9A944D6024492B6D0058F32A /* popup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = popup.swift; sourceTree = ""; };
9A998CD722A199920087ADE7 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
9A998CD922A199970087ADE7 /* ServiceManagement.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ServiceManagement.framework; path = System/Library/Frameworks/ServiceManagement.framework; sourceTree = SDKROOT; };
- 9AA28DC0243774ED00D2B196 /* Sensors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sensors.swift; sourceTree = ""; };
- 9AA28DC22437752D00D2B196 /* SensorsMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SensorsMenu.swift; sourceTree = ""; };
- 9AA28DD0243799E500D2B196 /* SensorsWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SensorsWidget.swift; sourceTree = ""; };
- 9AA28DD5243A8A3D00D2B196 /* SMC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMC.swift; sourceTree = ""; };
- 9AA28DDA243B4AF500D2B196 /* SensorsType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SensorsType.swift; sourceTree = ""; };
- 9AF0F31A22DA924000026AE6 /* LineChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineChart.swift; sourceTree = ""; };
- 9AF0F31C22DA925000026AE6 /* LineChartWithValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineChartWithValue.swift; sourceTree = ""; };
- 9AF0F31E22DA925700026AE6 /* BarChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarChart.swift; sourceTree = ""; };
- 9AF0F32022DA92AD00026AE6 /* NetworkDots.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkDots.swift; sourceTree = ""; };
- 9AF0F32222DA92B900026AE6 /* NetworkArrows.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkArrows.swift; sourceTree = ""; };
- 9AF0F32422DA92C400026AE6 /* NetworkText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkText.swift; sourceTree = ""; };
- 9AF0F32622DA92DD00026AE6 /* NetworkDotsText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkDotsText.swift; sourceTree = ""; };
- 9AF0F32822DA92E800026AE6 /* NetworkArrowsText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkArrowsText.swift; sourceTree = ""; };
- 9AF6F1FD231D732600B8E1E4 /* PopupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupViewController.swift; sourceTree = ""; };
- 9AFFCB3A22B3FD0500B0E6D8 /* About.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = About.storyboard; sourceTree = ""; };
+ 9A9D728924471FAE005CF997 /* SMC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMC.swift; sourceTree = ""; };
+ 9A9EA9442476D34500E3B883 /* Update.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Update.swift; sourceTree = ""; };
+ 9AA4A0092443656D00ECCF07 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 9AA6425F244B274200416A33 /* popup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = popup.swift; sourceTree = ""; };
+ 9AA64261244B57C800416A33 /* settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = settings.swift; sourceTree = ""; };
+ 9AA64263244B94F300416A33 /* LineChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineChart.swift; sourceTree = ""; };
+ 9AABEADD243FB13500668CB0 /* ModuleKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ModuleKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 9AABEAE0243FB13500668CB0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 9AABEAE9243FB15E00668CB0 /* module.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = module.swift; sourceTree = ""; };
+ 9AABEB64243FCE8A00668CB0 /* CPU.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CPU.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 9AABEB67243FCE8A00668CB0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 9AABEB70243FCE9400668CB0 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; };
+ 9AABEB79243FD26200668CB0 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ 9AABEB7D243FDEF100668CB0 /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; };
+ 9AB7FD7B246B48DB00387FDA /* settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = settings.swift; sourceTree = ""; };
+ 9ABFF8F6248BEBCB00C9041A /* Battery.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Battery.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 9ABFF8F9248BEBCB00C9041A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 9ABFF902248BEBD700C9041A /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; };
+ 9ABFF904248BEC0B00C9041A /* config.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = config.plist; sourceTree = ""; };
+ 9ABFF90F248BEE7200C9041A /* readers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = readers.swift; sourceTree = ""; };
+ 9ABFF911248BF39500C9041A /* Battery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Battery.swift; sourceTree = ""; };
+ 9ABFF913248C30A800C9041A /* popup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = popup.swift; sourceTree = ""; };
+ 9AF9EE0224648751005D2270 /* Disk.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Disk.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 9AF9EE0524648751005D2270 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 9AF9EE0E2464875F005D2270 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; };
+ 9AF9EE1024648ADC005D2270 /* readers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = readers.swift; sourceTree = ""; };
+ 9AF9EE12246492E8005D2270 /* config.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = config.plist; sourceTree = ""; };
+ 9AF9EE192464A7B3005D2270 /* config.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = config.plist; sourceTree = ""; };
+ 9AF9EE1B2464A7BA005D2270 /* config.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = config.plist; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
+ 9A0C82D724460F7200FAE3D4 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
9A1410F2229E721100D29793 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 9A5349CE23D8832E00C23824 /* Reachability.framework in Frameworks */,
- 9A7DE036245982460084BD7A /* Repeat.framework in Frameworks */,
+ 9AF9EE0924648751005D2270 /* Disk.framework in Frameworks */,
+ 9AABEAE4243FB13500668CB0 /* ModuleKit.framework in Frameworks */,
+ 9ABFF8FD248BEBCB00C9041A /* Battery.framework in Frameworks */,
+ 9A81C75D2449A41400825D92 /* Memory.framework in Frameworks */,
+ 9A0C82E124460F7200FAE3D4 /* StatsKit.framework in Frameworks */,
+ 9A3E17D3247A94AF00449CD1 /* Net.framework in Frameworks */,
+ 9AABEB6B243FCE8A00668CB0 /* CPU.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -184,14 +437,88 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 9A3E17C9247A94AF00449CD1 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9A3E17DE247A94DC00449CD1 /* ModuleKit.framework in Frameworks */,
+ 9A313BF7247EF01800DB5101 /* Reachability.framework in Frameworks */,
+ 9A3E17E2247A94DC00449CD1 /* StatsKit.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 9A81C7532449A41400825D92 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9A81C7622449A41E00825D92 /* ModuleKit.framework in Frameworks */,
+ 9A81C76B2449AE9400825D92 /* StatsKit.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 9AABEADA243FB13500668CB0 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9A6549292440A57200E30B74 /* Repeat.framework in Frameworks */,
+ 9A0C82EA24460FB100FAE3D4 /* StatsKit.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 9AABEB61243FCE8A00668CB0 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9AABEB72243FCEEF00668CB0 /* ModuleKit.framework in Frameworks */,
+ 9A3E17BD247A8F5700449CD1 /* StatsKit.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 9ABFF8F3248BEBCB00C9041A /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9ABFF906248BEC2600C9041A /* ModuleKit.framework in Frameworks */,
+ 9ABFF90B248BEC2900C9041A /* StatsKit.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 9AF9EDFF24648751005D2270 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9AF9EE1424649BAD005D2270 /* ModuleKit.framework in Frameworks */,
+ 9A3E17C1247A8F5E00449CD1 /* StatsKit.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 9A0C82DB24460F7200FAE3D4 /* StatsKit */ = {
+ isa = PBXGroup;
+ children = (
+ 9A7D0CB62444C2C800B09070 /* SystemKit.swift */,
+ 9A0C82D124460DFF00FAE3D4 /* updater.swift */,
+ 9A65492224407EA600E30B74 /* store.swift */,
+ 9A0C82D324460E4400FAE3D4 /* launchAtLogin.swift */,
+ 9A654920244074B500E30B74 /* extensions.swift */,
+ 9A0C82DC24460F7200FAE3D4 /* StatsKit.h */,
+ 9A0C82DD24460F7200FAE3D4 /* Info.plist */,
+ 9A9D728924471FAE005CF997 /* SMC.swift */,
+ 9A81C76F2449B8D500825D92 /* Charts.swift */,
+ );
+ path = StatsKit;
+ sourceTree = "";
+ };
9A1410EC229E721100D29793 = {
isa = PBXGroup;
children = (
9A1410F7229E721100D29793 /* Stats */,
+ 9A0C82DB24460F7200FAE3D4 /* StatsKit */,
9A343528243E26A0006B19F9 /* LaunchAtLogin */,
+ 9AABEADE243FB13500668CB0 /* ModuleKit */,
+ 9AB14B75248CEEC600DC6731 /* Modules */,
9A1410F6229E721100D29793 /* Products */,
9A998CD622A199920087ADE7 /* Frameworks */,
);
@@ -202,6 +529,13 @@
children = (
9A1410F5229E721100D29793 /* Stats.app */,
9A343527243E26A0006B19F9 /* LaunchAtLogin.app */,
+ 9AABEADD243FB13500668CB0 /* ModuleKit.framework */,
+ 9AABEB64243FCE8A00668CB0 /* CPU.framework */,
+ 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */,
+ 9A81C7562449A41400825D92 /* Memory.framework */,
+ 9AF9EE0224648751005D2270 /* Disk.framework */,
+ 9A3E17CC247A94AF00449CD1 /* Net.framework */,
+ 9ABFF8F6248BEBCB00C9041A /* Battery.framework */,
);
name = Products;
sourceTree = "";
@@ -209,75 +543,13 @@
9A1410F7229E721100D29793 /* Stats */ = {
isa = PBXGroup;
children = (
- 9AF6F1FC231D72EC00B8E1E4 /* Views */,
- 9A74D59522B440D4004FE1FA /* Widgets */,
+ 9A81C74A24499C4B00825D92 /* Views */,
9A5B1CB3229E72A7008B9D3C /* Supporting Files */,
- 9A5B1CBA229E7892008B9D3C /* Modules */,
- 9A5B1CBD229E78D2008B9D3C /* libs */,
- 9A1410F8229E721100D29793 /* AppDelegate.swift */,
- 9A57A18422A1D26D0033E318 /* MenuBar.swift */,
+ 9AABEB79243FD26200668CB0 /* AppDelegate.swift */,
);
path = Stats;
sourceTree = "";
};
- 9A2D15D323CCEFEC00C4C417 /* CPU */ = {
- isa = PBXGroup;
- children = (
- 9A2D15D423CCEFF700C4C417 /* CPU.swift */,
- 9A2D15D823CD036400C4C417 /* CPUMenu.swift */,
- 9A2D15DA23CD0B2100C4C417 /* CPUPopup.swift */,
- 9A2D15D623CCFE1B00C4C417 /* CPULoadReader.swift */,
- 9A2D15E023CD133300C4C417 /* CPUUsageReader.swift */,
- 9A2D15E223CD1E4B00C4C417 /* CPUProcessReader.swift */,
- );
- path = CPU;
- sourceTree = "";
- };
- 9A2D15E423CE290400C4C417 /* RAM */ = {
- isa = PBXGroup;
- children = (
- 9A2D15E523CE291600C4C417 /* RAM.swift */,
- 9A2D15E723CE29A400C4C417 /* RAMMenu.swift */,
- 9A2D15E923CE2C1100C4C417 /* RAMPopup.swift */,
- 9A2D15ED23CE2EE200C4C417 /* RAMUsageReader.swift */,
- 9A2D15EF23CE32D500C4C417 /* RAMProcessReader.swift */,
- );
- path = RAM;
- sourceTree = "";
- };
- 9A2D15F123CE390500C4C417 /* Disk */ = {
- isa = PBXGroup;
- children = (
- 9A2D15F223CE391300C4C417 /* Disk.swift */,
- 9A2D15F423CE393A00C4C417 /* DiskMenu.swift */,
- 9A2D15F623CE3A1200C4C417 /* DiskCapacityReader.swift */,
- );
- path = Disk;
- sourceTree = "";
- };
- 9A2D15F823CE3BDA00C4C417 /* Battery */ = {
- isa = PBXGroup;
- children = (
- 9A2D15F923CE3BE600C4C417 /* Battery.swift */,
- 9A2D15FB23CE3C1A00C4C417 /* BatteryMenu.swift */,
- 9A2D15FF23CE40DE00C4C417 /* BatteryPopup.swift */,
- 9A2D15FD23CE3DE300C4C417 /* BatteryReader.swift */,
- );
- path = Battery;
- sourceTree = "";
- };
- 9A2D160123CE444D00C4C417 /* Network */ = {
- isa = PBXGroup;
- children = (
- 9A2D160223CE445900C4C417 /* Network.swift */,
- 9A2D160423CE451B00C4C417 /* NetworkMenu.swift */,
- 9A5349C623D8535900C23824 /* NetworkPopup.swift */,
- 9A2D160623CE462400C4C417 /* NetworkReader.swift */,
- 9A5349C823D8642A00C23824 /* NetworkInterfaceReader.swift */,
- );
- path = Network;
- sourceTree = "";
- };
9A343528243E26A0006B19F9 /* LaunchAtLogin */ = {
isa = PBXGroup;
children = (
@@ -288,132 +560,222 @@
path = LaunchAtLogin;
sourceTree = "";
};
- 9A54EF65232AB48100F7DC20 /* Battery */ = {
+ 9A3E17CD247A94AF00449CD1 /* Net */ = {
isa = PBXGroup;
children = (
- 9A09C8A122B3D94D0018426F /* BatteryWidget.swift */,
- 9A54EF66232AB81800F7DC20 /* BatteryPercentageWidget.swift */,
- 9A54EF68232AB82700F7DC20 /* BatteryTimeWidget.swift */,
+ 9A3E17D8247A94B500449CD1 /* main.swift */,
+ 9A3E17DA247A94BC00449CD1 /* readers.swift */,
+ 9A3E17E9247B07BF00449CD1 /* popup.swift */,
+ 9A3E17CF247A94AF00449CD1 /* Info.plist */,
+ 9A3E17DC247A94C300449CD1 /* config.plist */,
);
- path = Battery;
+ path = Net;
sourceTree = "";
};
9A5B1CB3229E72A7008B9D3C /* Supporting Files */ = {
isa = PBXGroup;
children = (
9A6CFC0022A1C9F5001E782D /* Assets.xcassets */,
- 9A1410FE229E721200D29793 /* Main.storyboard */,
- 9AFFCB3A22B3FD0500B0E6D8 /* About.storyboard */,
- 9A426DBD22C2BE0000C064C4 /* Updates.storyboard */,
+ 9AABEB7D243FDEF100668CB0 /* main.swift */,
9A141101229E721200D29793 /* Info.plist */,
);
path = "Supporting Files";
sourceTree = "";
};
- 9A5B1CBA229E7892008B9D3C /* Modules */ = {
+ 9A7C61B22440DF770032695D /* Widgets */ = {
isa = PBXGroup;
children = (
- 9A2D15D323CCEFEC00C4C417 /* CPU */,
- 9A2D15E423CE290400C4C417 /* RAM */,
- 9A2D15F123CE390500C4C417 /* Disk */,
- 9A2D15F823CE3BDA00C4C417 /* Battery */,
- 9A2D160123CE444D00C4C417 /* Network */,
- 9AA28DBF243774DD00D2B196 /* Sensors */,
- 9A2D15D123CCEC7600C4C417 /* Module.swift */,
- );
- path = Modules;
- sourceTree = "";
- };
- 9A5B1CBD229E78D2008B9D3C /* libs */ = {
- isa = PBXGroup;
- children = (
- 9AA28DD5243A8A3D00D2B196 /* SMC.swift */,
- 9A5B1CC4229E7B40008B9D3C /* Extensions.swift */,
- 9A426DB722C2B5EE00C064C4 /* MacAppUpdater.swift */,
- 9A59AE55231EE02F007989D6 /* ChartMarker.swift */,
- 9A3434F0243E19E6006B19F9 /* LaunchAtLogin.swift */,
- );
- path = libs;
- sourceTree = "";
- };
- 9A74D59522B440D4004FE1FA /* Widgets */ = {
- isa = PBXGroup;
- children = (
- 9A54EF65232AB48100F7DC20 /* Battery */,
- 9AF0F31922DA923100026AE6 /* Network */,
- 9AF0F31822DA922800026AE6 /* Charts */,
- 9AA28DD224379F8700D2B196 /* Sensors */,
- 9A74D59622B44498004FE1FA /* Mini.swift */,
- 9A79B36922D3BEE600BF1C3A /* Widget.swift */,
+ 9A7C61B32440DF810032695D /* Mini.swift */,
+ 9AA64263244B94F300416A33 /* LineChart.swift */,
+ 9A1A7AB924561F0B00A84F7A /* BarChart.swift */,
+ 9A3E17E7247AA8E100449CD1 /* Network.swift */,
+ 9ABFF911248BF39500C9041A /* Battery.swift */,
);
path = Widgets;
sourceTree = "";
};
+ 9A81C74A24499C4B00825D92 /* Views */ = {
+ isa = PBXGroup;
+ children = (
+ 9A81C74B24499C7000825D92 /* AppSettings.swift */,
+ 9A81C74C24499C7000825D92 /* Settings.swift */,
+ 9A9EA9442476D34500E3B883 /* Update.swift */,
+ );
+ path = Views;
+ sourceTree = "";
+ };
+ 9A81C7572449A41400825D92 /* Memory */ = {
+ isa = PBXGroup;
+ children = (
+ 9A81C7672449A43600825D92 /* main.swift */,
+ 9A81C7682449A43600825D92 /* readers.swift */,
+ 9AA6425F244B274200416A33 /* popup.swift */,
+ 9A81C7592449A41400825D92 /* Info.plist */,
+ 9AF9EE192464A7B3005D2270 /* config.plist */,
+ );
+ path = Memory;
+ sourceTree = "";
+ };
9A998CD622A199920087ADE7 /* Frameworks */ = {
isa = PBXGroup;
children = (
- 9A7DE035245982460084BD7A /* Repeat.framework */,
+ 9A1CC5AC24615E5C0023F4E8 /* IntelPowerGadget.framework */,
+ 9A6549282440A57200E30B74 /* Repeat.framework */,
9A5349CD23D8832E00C23824 /* Reachability.framework */,
- 9A6698E32326AAE5001D00E1 /* Charts.framework */,
9A998CD922A199970087ADE7 /* ServiceManagement.framework */,
9A998CD722A199920087ADE7 /* Cocoa.framework */,
);
name = Frameworks;
sourceTree = "";
};
- 9AA28DBF243774DD00D2B196 /* Sensors */ = {
+ 9AA0E9BD244269C400825127 /* Supporting Files */ = {
isa = PBXGroup;
children = (
- 9AA28DC0243774ED00D2B196 /* Sensors.swift */,
- 9AA28DC22437752D00D2B196 /* SensorsMenu.swift */,
- 9AA28DDA243B4AF500D2B196 /* SensorsType.swift */,
+ 9AA4A0092443656D00ECCF07 /* Assets.xcassets */,
+ 9AABEAE0243FB13500668CB0 /* Info.plist */,
);
- path = Sensors;
+ path = "Supporting Files";
sourceTree = "";
};
- 9AA28DD224379F8700D2B196 /* Sensors */ = {
+ 9AABEADE243FB13500668CB0 /* ModuleKit */ = {
isa = PBXGroup;
children = (
- 9AA28DD0243799E500D2B196 /* SensorsWidget.swift */,
+ 9A7C61B22440DF770032695D /* Widgets */,
+ 9AA0E9BD244269C400825127 /* Supporting Files */,
+ 9AABEAE9243FB15E00668CB0 /* module.swift */,
+ 9A944D54244920690058F32A /* reader.swift */,
+ 9A944D5A244925720058F32A /* widget.swift */,
+ 9A944D5C24492A8B0058F32A /* popup.swift */,
+ 9A81C74F24499D6600825D92 /* settings.swift */,
+ 9A944D5E24492AA60058F32A /* Constants.swift */,
);
- path = Sensors;
+ path = ModuleKit;
sourceTree = "";
};
- 9AF0F31822DA922800026AE6 /* Charts */ = {
+ 9AABEB65243FCE8A00668CB0 /* CPU */ = {
isa = PBXGroup;
children = (
- 9AF0F31A22DA924000026AE6 /* LineChart.swift */,
- 9AF0F31C22DA925000026AE6 /* LineChartWithValue.swift */,
- 9AF0F31E22DA925700026AE6 /* BarChart.swift */,
+ 9AABEB70243FCE9400668CB0 /* main.swift */,
+ 9A944D58244920FE0058F32A /* readers.swift */,
+ 9A944D6024492B6D0058F32A /* popup.swift */,
+ 9AA64261244B57C800416A33 /* settings.swift */,
+ 9AABEB67243FCE8A00668CB0 /* Info.plist */,
+ 9AF9EE1B2464A7BA005D2270 /* config.plist */,
);
- path = Charts;
+ path = CPU;
sourceTree = "";
};
- 9AF0F31922DA923100026AE6 /* Network */ = {
+ 9AB14B75248CEEC600DC6731 /* Modules */ = {
isa = PBXGroup;
children = (
- 9AF0F32022DA92AD00026AE6 /* NetworkDots.swift */,
- 9AF0F32222DA92B900026AE6 /* NetworkArrows.swift */,
- 9AF0F32422DA92C400026AE6 /* NetworkText.swift */,
- 9AF0F32622DA92DD00026AE6 /* NetworkDotsText.swift */,
- 9AF0F32822DA92E800026AE6 /* NetworkArrowsText.swift */,
+ 9AABEB65243FCE8A00668CB0 /* CPU */,
+ 9A81C7572449A41400825D92 /* Memory */,
+ 9AF9EE0324648751005D2270 /* Disk */,
+ 9A3E17CD247A94AF00449CD1 /* Net */,
+ 9ABFF8F7248BEBCB00C9041A /* Battery */,
);
- path = Network;
+ path = Modules;
sourceTree = "";
};
- 9AF6F1FC231D72EC00B8E1E4 /* Views */ = {
+ 9ABFF8F7248BEBCB00C9041A /* Battery */ = {
isa = PBXGroup;
children = (
- 9AF6F1FD231D732600B8E1E4 /* PopupViewController.swift */,
- 9A606B492321577400642F51 /* UpdatesViewController.swift */,
- 9A606B4B232157BA00642F51 /* AboutViewController.swift */,
+ 9ABFF902248BEBD700C9041A /* main.swift */,
+ 9ABFF90F248BEE7200C9041A /* readers.swift */,
+ 9ABFF913248C30A800C9041A /* popup.swift */,
+ 9ABFF8F9248BEBCB00C9041A /* Info.plist */,
+ 9ABFF904248BEC0B00C9041A /* config.plist */,
);
- path = Views;
+ path = Battery;
+ sourceTree = "";
+ };
+ 9AF9EE0324648751005D2270 /* Disk */ = {
+ isa = PBXGroup;
+ children = (
+ 9AF9EE0E2464875F005D2270 /* main.swift */,
+ 9AF9EE1024648ADC005D2270 /* readers.swift */,
+ 9A5AF11A2469CE9B00684737 /* popup.swift */,
+ 9AB7FD7B246B48DB00387FDA /* settings.swift */,
+ 9AF9EE0524648751005D2270 /* Info.plist */,
+ 9AF9EE12246492E8005D2270 /* config.plist */,
+ );
+ path = Disk;
sourceTree = "";
};
/* End PBXGroup section */
+/* Begin PBXHeadersBuildPhase section */
+ 9A0C82D524460F7200FAE3D4 /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9A0C82DE24460F7200FAE3D4 /* StatsKit.h in Headers */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 9A3E17C7247A94AF00449CD1 /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 9A81C7512449A41400825D92 /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 9AABEAD8243FB13500668CB0 /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 9AABEB5F243FCE8A00668CB0 /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 9ABFF8F1248BEBCB00C9041A /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 9AF9EDFD24648751005D2270 /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXHeadersBuildPhase section */
+
/* Begin PBXNativeTarget section */
+ 9A0C82D924460F7200FAE3D4 /* StatsKit */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 9A0C82E324460F7200FAE3D4 /* Build configuration list for PBXNativeTarget "StatsKit" */;
+ buildPhases = (
+ 9A0C82D524460F7200FAE3D4 /* Headers */,
+ 9A0C82D624460F7200FAE3D4 /* Sources */,
+ 9A0C82D724460F7200FAE3D4 /* Frameworks */,
+ 9A0C82D824460F7200FAE3D4 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = StatsKit;
+ productName = StatsKit;
+ productReference = 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */;
+ productType = "com.apple.product-type.framework";
+ };
9A1410F4229E721100D29793 /* Stats */ = {
isa = PBXNativeTarget;
buildConfigurationList = 9A141105229E721200D29793 /* Build configuration list for PBXNativeTarget "Stats" */;
@@ -422,12 +784,18 @@
9A1410F2229E721100D29793 /* Frameworks */,
9A1410F3229E721100D29793 /* Resources */,
9AB54DAE22A19F96006192E0 /* Copy Files */,
- 9A6698D82326A903001D00E1 /* Run Script */,
9A6698E72326AB16001D00E1 /* Embed Frameworks */,
);
buildRules = (
);
dependencies = (
+ 9AABEAE3243FB13500668CB0 /* PBXTargetDependency */,
+ 9AABEB6A243FCE8A00668CB0 /* PBXTargetDependency */,
+ 9A0C82E024460F7200FAE3D4 /* PBXTargetDependency */,
+ 9A81C75C2449A41400825D92 /* PBXTargetDependency */,
+ 9AF9EE0824648751005D2270 /* PBXTargetDependency */,
+ 9A3E17D2247A94AF00449CD1 /* PBXTargetDependency */,
+ 9ABFF8FC248BEBCB00C9041A /* PBXTargetDependency */,
);
name = Stats;
productName = "Mini Stats";
@@ -451,16 +819,148 @@
productReference = 9A343527243E26A0006B19F9 /* LaunchAtLogin.app */;
productType = "com.apple.product-type.application";
};
+ 9A3E17CB247A94AF00449CD1 /* Net */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 9A3E17D5247A94AF00449CD1 /* Build configuration list for PBXNativeTarget "Net" */;
+ buildPhases = (
+ 9A3E17C7247A94AF00449CD1 /* Headers */,
+ 9A3E17C8247A94AF00449CD1 /* Sources */,
+ 9A3E17C9247A94AF00449CD1 /* Frameworks */,
+ 9A3E17CA247A94AF00449CD1 /* Resources */,
+ 9A3E17E6247A94DC00449CD1 /* Embed Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 9A3E17E1247A94DC00449CD1 /* PBXTargetDependency */,
+ 9A3E17E5247A94DC00449CD1 /* PBXTargetDependency */,
+ );
+ name = Net;
+ productName = Net;
+ productReference = 9A3E17CC247A94AF00449CD1 /* Net.framework */;
+ productType = "com.apple.product-type.framework";
+ };
+ 9A81C7552449A41400825D92 /* Memory */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 9A81C75F2449A41400825D92 /* Build configuration list for PBXNativeTarget "Memory" */;
+ buildPhases = (
+ 9A81C7512449A41400825D92 /* Headers */,
+ 9A81C7522449A41400825D92 /* Sources */,
+ 9A81C7532449A41400825D92 /* Frameworks */,
+ 9A81C7542449A41400825D92 /* Resources */,
+ 9A81C7662449A41E00825D92 /* Embed Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 9A81C7652449A41E00825D92 /* PBXTargetDependency */,
+ 9A81C76E2449AE9400825D92 /* PBXTargetDependency */,
+ );
+ name = Memory;
+ productName = Memory;
+ productReference = 9A81C7562449A41400825D92 /* Memory.framework */;
+ productType = "com.apple.product-type.framework";
+ };
+ 9AABEADC243FB13500668CB0 /* ModuleKit */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 9AABEAE8243FB13500668CB0 /* Build configuration list for PBXNativeTarget "ModuleKit" */;
+ buildPhases = (
+ 9AABEAD8243FB13500668CB0 /* Headers */,
+ 9AABEAD9243FB13500668CB0 /* Sources */,
+ 9AABEADA243FB13500668CB0 /* Frameworks */,
+ 9AABEADB243FB13500668CB0 /* Resources */,
+ 9A65492B2440A57200E30B74 /* Embed Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 9A0C82ED24460FB100FAE3D4 /* PBXTargetDependency */,
+ );
+ name = ModuleKit;
+ productName = ModuleKit;
+ productReference = 9AABEADD243FB13500668CB0 /* ModuleKit.framework */;
+ productType = "com.apple.product-type.framework";
+ };
+ 9AABEB63243FCE8A00668CB0 /* CPU */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 9AABEB6D243FCE8A00668CB0 /* Build configuration list for PBXNativeTarget "CPU" */;
+ buildPhases = (
+ 9AABEB5F243FCE8A00668CB0 /* Headers */,
+ 9AABEB60243FCE8A00668CB0 /* Sources */,
+ 9AABEB61243FCE8A00668CB0 /* Frameworks */,
+ 9AABEB62243FCE8A00668CB0 /* Resources */,
+ 9AABEB76243FCEEF00668CB0 /* Embed Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 9AABEB75243FCEEF00668CB0 /* PBXTargetDependency */,
+ 9A3E17C0247A8F5700449CD1 /* PBXTargetDependency */,
+ );
+ name = CPU;
+ productName = CPU;
+ productReference = 9AABEB64243FCE8A00668CB0 /* CPU.framework */;
+ productType = "com.apple.product-type.framework";
+ };
+ 9ABFF8F5248BEBCB00C9041A /* Battery */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 9ABFF901248BEBCB00C9041A /* Build configuration list for PBXNativeTarget "Battery" */;
+ buildPhases = (
+ 9ABFF8F1248BEBCB00C9041A /* Headers */,
+ 9ABFF8F2248BEBCB00C9041A /* Sources */,
+ 9ABFF8F3248BEBCB00C9041A /* Frameworks */,
+ 9ABFF8F4248BEBCB00C9041A /* Resources */,
+ 9ABFF90A248BEC2600C9041A /* Embed Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 9ABFF909248BEC2600C9041A /* PBXTargetDependency */,
+ 9ABFF90E248BEC2900C9041A /* PBXTargetDependency */,
+ );
+ name = Battery;
+ productName = Battery;
+ productReference = 9ABFF8F6248BEBCB00C9041A /* Battery.framework */;
+ productType = "com.apple.product-type.framework";
+ };
+ 9AF9EE0124648751005D2270 /* Disk */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 9AF9EE0D24648751005D2270 /* Build configuration list for PBXNativeTarget "Disk" */;
+ buildPhases = (
+ 9AF9EDFD24648751005D2270 /* Headers */,
+ 9AF9EDFE24648751005D2270 /* Sources */,
+ 9AF9EDFF24648751005D2270 /* Frameworks */,
+ 9AF9EE0024648751005D2270 /* Resources */,
+ 9AF9EE1824649BAD005D2270 /* Embed Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 9AF9EE1724649BAD005D2270 /* PBXTargetDependency */,
+ 9A3E17C4247A8F5E00449CD1 /* PBXTargetDependency */,
+ );
+ name = Disk;
+ productName = Disk;
+ productReference = 9AF9EE0224648751005D2270 /* Disk.framework */;
+ productType = "com.apple.product-type.framework";
+ };
/* End PBXNativeTarget section */
/* Begin PBXProject section */
9A1410ED229E721100D29793 /* Project object */ = {
isa = PBXProject;
attributes = {
+ KnownAssetTags = (
+ New,
+ );
LastSwiftUpdateCheck = 1140;
- LastUpgradeCheck = 1140;
+ LastUpgradeCheck = 1150;
ORGANIZATIONNAME = "Serhiy Mytrovtsiy";
TargetAttributes = {
+ 9A0C82D924460F7200FAE3D4 = {
+ CreatedOnToolsVersion = 11.4;
+ LastSwiftMigration = 1140;
+ };
9A1410F4229E721100D29793 = {
CreatedOnToolsVersion = 10.2.1;
LastSwiftMigration = 1030;
@@ -476,6 +976,30 @@
9A343526243E26A0006B19F9 = {
CreatedOnToolsVersion = 11.4;
};
+ 9A3E17CB247A94AF00449CD1 = {
+ CreatedOnToolsVersion = 11.5;
+ LastSwiftMigration = 1150;
+ };
+ 9A81C7552449A41400825D92 = {
+ CreatedOnToolsVersion = 11.4.1;
+ LastSwiftMigration = 1140;
+ };
+ 9AABEADC243FB13500668CB0 = {
+ CreatedOnToolsVersion = 11.4;
+ LastSwiftMigration = 1140;
+ };
+ 9AABEB63243FCE8A00668CB0 = {
+ CreatedOnToolsVersion = 11.4;
+ LastSwiftMigration = 1140;
+ };
+ 9ABFF8F5248BEBCB00C9041A = {
+ CreatedOnToolsVersion = 11.5;
+ LastSwiftMigration = 1150;
+ };
+ 9AF9EE0124648751005D2270 = {
+ CreatedOnToolsVersion = 11.4.1;
+ LastSwiftMigration = 1140;
+ };
};
};
buildConfigurationList = 9A1410F0229E721100D29793 /* Build configuration list for PBXProject "Stats" */;
@@ -493,19 +1017,30 @@
targets = (
9A1410F4229E721100D29793 /* Stats */,
9A343526243E26A0006B19F9 /* LaunchAtLogin */,
+ 9AABEADC243FB13500668CB0 /* ModuleKit */,
+ 9AABEB63243FCE8A00668CB0 /* CPU */,
+ 9A0C82D924460F7200FAE3D4 /* StatsKit */,
+ 9A81C7552449A41400825D92 /* Memory */,
+ 9AF9EE0124648751005D2270 /* Disk */,
+ 9A3E17CB247A94AF00449CD1 /* Net */,
+ 9ABFF8F5248BEBCB00C9041A /* Battery */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
+ 9A0C82D824460F7200FAE3D4 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
9A1410F3229E721100D29793 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
9A6CFC0122A1C9F5001E782D /* Assets.xcassets in Resources */,
- 9AFFCB3B22B3FD0500B0E6D8 /* About.storyboard in Resources */,
- 9A141100229E721200D29793 /* Main.storyboard in Resources */,
- 9A426DBE22C2BE0000C064C4 /* Updates.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -516,86 +1051,80 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 9A3E17CA247A94AF00449CD1 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9AB14B79248CEF4100DC6731 /* config.plist in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 9A81C7542449A41400825D92 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9AB14B77248CEF3500DC6731 /* config.plist in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 9AABEADB243FB13500668CB0 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9AA4A00A2443656D00ECCF07 /* Assets.xcassets in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 9AABEB62243FCE8A00668CB0 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9AB14B7B248CF00F00DC6731 /* config.plist in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 9ABFF8F4248BEBCB00C9041A /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9AB14B7A248CEF4900DC6731 /* config.plist in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 9AF9EE0024648751005D2270 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9AB14B78248CEF3B00DC6731 /* config.plist in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXResourcesBuildPhase section */
-/* Begin PBXShellScriptBuildPhase section */
- 9A6698D82326A903001D00E1 /* Run Script */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 8;
- files = (
- );
- inputFileListPaths = (
- );
- inputPaths = (
- "$(SRCROOT)/Carthage/Build/Mac/Charts.framework",
- );
- name = "Run Script";
- outputFileListPaths = (
- );
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 1;
- shellPath = /bin/sh;
- shellScript = "/usr/local/bin/carthage copy-frameworks\n";
- };
-/* End PBXShellScriptBuildPhase section */
-
/* Begin PBXSourcesBuildPhase section */
+ 9A0C82D624460F7200FAE3D4 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9A81C7702449B8D500825D92 /* Charts.swift in Sources */,
+ 9A0C82E624460F9A00FAE3D4 /* extensions.swift in Sources */,
+ 9A0C82E724460F9C00FAE3D4 /* updater.swift in Sources */,
+ 9A0C82EE2446124800FAE3D4 /* SystemKit.swift in Sources */,
+ 9A9D728A24471FAE005CF997 /* SMC.swift in Sources */,
+ 9A0C82E824460F9E00FAE3D4 /* launchAtLogin.swift in Sources */,
+ 9A0C82E924460F9F00FAE3D4 /* store.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
9A1410F1229E721100D29793 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- 9A2D15E623CE291600C4C417 /* RAM.swift in Sources */,
- 9A09C8A222B3D94D0018426F /* BatteryWidget.swift in Sources */,
- 9A5349C723D8535900C23824 /* NetworkPopup.swift in Sources */,
- 9AA28DC32437752D00D2B196 /* SensorsMenu.swift in Sources */,
- 9A426DB822C2B5EE00C064C4 /* MacAppUpdater.swift in Sources */,
- 9A606B4C232157BA00642F51 /* AboutViewController.swift in Sources */,
- 9AA28DC1243774ED00D2B196 /* Sensors.swift in Sources */,
- 9AF0F32522DA92C400026AE6 /* NetworkText.swift in Sources */,
- 9AF0F32322DA92B900026AE6 /* NetworkArrows.swift in Sources */,
- 9A2D15D223CCEC7600C4C417 /* Module.swift in Sources */,
- 9A2D160023CE40DE00C4C417 /* BatteryPopup.swift in Sources */,
- 9A2D15FC23CE3C1A00C4C417 /* BatteryMenu.swift in Sources */,
- 9A606B4A2321577400642F51 /* UpdatesViewController.swift in Sources */,
- 9AF0F31D22DA925000026AE6 /* LineChartWithValue.swift in Sources */,
- 9A2D160723CE462400C4C417 /* NetworkReader.swift in Sources */,
- 9AA28DD6243A8A3D00D2B196 /* SMC.swift in Sources */,
- 9AA28DD1243799E500D2B196 /* SensorsWidget.swift in Sources */,
- 9A2D15FA23CE3BE600C4C417 /* Battery.swift in Sources */,
- 9A54EF67232AB81800F7DC20 /* BatteryPercentageWidget.swift in Sources */,
- 9A57A18522A1D26D0033E318 /* MenuBar.swift in Sources */,
- 9A2D15D923CD036400C4C417 /* CPUMenu.swift in Sources */,
- 9AF6F1FE231D732600B8E1E4 /* PopupViewController.swift in Sources */,
- 9A1410F9229E721100D29793 /* AppDelegate.swift in Sources */,
- 9A2D15E823CE29A400C4C417 /* RAMMenu.swift in Sources */,
- 9A2D15DB23CD0B2100C4C417 /* CPUPopup.swift in Sources */,
- 9A2D160523CE451B00C4C417 /* NetworkMenu.swift in Sources */,
- 9AF0F32722DA92DD00026AE6 /* NetworkDotsText.swift in Sources */,
- 9AF0F32922DA92E800026AE6 /* NetworkArrowsText.swift in Sources */,
- 9A54EF69232AB82700F7DC20 /* BatteryTimeWidget.swift in Sources */,
- 9A2D15D723CCFE1B00C4C417 /* CPULoadReader.swift in Sources */,
- 9A3434F1243E19E6006B19F9 /* LaunchAtLogin.swift in Sources */,
- 9A2D15F323CE391300C4C417 /* Disk.swift in Sources */,
- 9A2D15F723CE3A1200C4C417 /* DiskCapacityReader.swift in Sources */,
- 9A2D160323CE445900C4C417 /* Network.swift in Sources */,
- 9A2D15F023CE32D500C4C417 /* RAMProcessReader.swift in Sources */,
- 9AF0F31F22DA925700026AE6 /* BarChart.swift in Sources */,
- 9A2D15EE23CE2EE200C4C417 /* RAMUsageReader.swift in Sources */,
- 9AF0F31B22DA924000026AE6 /* LineChart.swift in Sources */,
- 9A59AE56231EE02F007989D6 /* ChartMarker.swift in Sources */,
- 9A74D59722B44498004FE1FA /* Mini.swift in Sources */,
- 9A5B1CC5229E7B40008B9D3C /* Extensions.swift in Sources */,
- 9A2D15E123CD133300C4C417 /* CPUUsageReader.swift in Sources */,
- 9AA28DDB243B4AF500D2B196 /* SensorsType.swift in Sources */,
- 9A2D15D523CCEFF700C4C417 /* CPU.swift in Sources */,
- 9A2D15E323CD1E4B00C4C417 /* CPUProcessReader.swift in Sources */,
- 9A2D15EA23CE2C1100C4C417 /* RAMPopup.swift in Sources */,
- 9A2D15FE23CE3DE300C4C417 /* BatteryReader.swift in Sources */,
- 9A2D15F523CE393A00C4C417 /* DiskMenu.swift in Sources */,
- 9A79B36A22D3BEE600BF1C3A /* Widget.swift in Sources */,
- 9AF0F32122DA92AD00026AE6 /* NetworkDots.swift in Sources */,
- 9A5349C923D8642A00C23824 /* NetworkInterfaceReader.swift in Sources */,
+ 9AABEB7E243FDEF100668CB0 /* main.swift in Sources */,
+ 9AABEB7A243FD26200668CB0 /* AppDelegate.swift in Sources */,
+ 9A9EA9452476D34500E3B883 /* Update.swift in Sources */,
+ 9A81C74E24499C7000825D92 /* Settings.swift in Sources */,
+ 9A81C74D24499C7000825D92 /* AppSettings.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -607,20 +1136,249 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 9A3E17C8247A94AF00449CD1 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9A3E17DB247A94BC00449CD1 /* readers.swift in Sources */,
+ 9A3E17EA247B07BF00449CD1 /* popup.swift in Sources */,
+ 9A3E17D9247A94B500449CD1 /* main.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 9A81C7522449A41400825D92 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9A81C76A2449A43600825D92 /* readers.swift in Sources */,
+ 9AA64260244B274200416A33 /* popup.swift in Sources */,
+ 9A81C7692449A43600825D92 /* main.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 9AABEAD9243FB13500668CB0 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9A3E17E8247AA8E100449CD1 /* Network.swift in Sources */,
+ 9AA64264244B94F300416A33 /* LineChart.swift in Sources */,
+ 9A1A7ABA24561F0B00A84F7A /* BarChart.swift in Sources */,
+ 9A944D55244920690058F32A /* reader.swift in Sources */,
+ 9A7C61B42440DF810032695D /* Mini.swift in Sources */,
+ 9A944D5D24492A8B0058F32A /* popup.swift in Sources */,
+ 9ABFF912248BF39500C9041A /* Battery.swift in Sources */,
+ 9AABEAEA243FB15E00668CB0 /* module.swift in Sources */,
+ 9A944D5B244925720058F32A /* widget.swift in Sources */,
+ 9A81C75024499D6600825D92 /* settings.swift in Sources */,
+ 9A944D5F24492AA60058F32A /* Constants.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 9AABEB60243FCE8A00668CB0 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9A944D59244920FE0058F32A /* readers.swift in Sources */,
+ 9A944D6124492B6D0058F32A /* popup.swift in Sources */,
+ 9AABEB71243FCE9400668CB0 /* main.swift in Sources */,
+ 9AA64262244B57C800416A33 /* settings.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 9ABFF8F2248BEBCB00C9041A /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9ABFF910248BEE7200C9041A /* readers.swift in Sources */,
+ 9ABFF914248C30A800C9041A /* popup.swift in Sources */,
+ 9ABFF903248BEBD700C9041A /* main.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 9AF9EDFE24648751005D2270 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9A5AF11B2469CE9B00684737 /* popup.swift in Sources */,
+ 9AF9EE1124648ADC005D2270 /* readers.swift in Sources */,
+ 9AB7FD7C246B48DB00387FDA /* settings.swift in Sources */,
+ 9AF9EE0F2464875F005D2270 /* main.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXSourcesBuildPhase section */
-/* Begin PBXVariantGroup section */
- 9A1410FE229E721200D29793 /* Main.storyboard */ = {
- isa = PBXVariantGroup;
- children = (
- 9A1410FF229E721200D29793 /* Base */,
- );
- name = Main.storyboard;
- sourceTree = "";
+/* Begin PBXTargetDependency section */
+ 9A0C82E024460F7200FAE3D4 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 9A0C82D924460F7200FAE3D4 /* StatsKit */;
+ targetProxy = 9A0C82DF24460F7200FAE3D4 /* PBXContainerItemProxy */;
};
-/* End PBXVariantGroup section */
+ 9A0C82ED24460FB100FAE3D4 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 9A0C82D924460F7200FAE3D4 /* StatsKit */;
+ targetProxy = 9A0C82EC24460FB100FAE3D4 /* PBXContainerItemProxy */;
+ };
+ 9A3E17C0247A8F5700449CD1 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 9A0C82D924460F7200FAE3D4 /* StatsKit */;
+ targetProxy = 9A3E17BF247A8F5700449CD1 /* PBXContainerItemProxy */;
+ };
+ 9A3E17C4247A8F5E00449CD1 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 9A0C82D924460F7200FAE3D4 /* StatsKit */;
+ targetProxy = 9A3E17C3247A8F5E00449CD1 /* PBXContainerItemProxy */;
+ };
+ 9A3E17D2247A94AF00449CD1 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 9A3E17CB247A94AF00449CD1 /* Net */;
+ targetProxy = 9A3E17D1247A94AF00449CD1 /* PBXContainerItemProxy */;
+ };
+ 9A3E17E1247A94DC00449CD1 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 9AABEADC243FB13500668CB0 /* ModuleKit */;
+ targetProxy = 9A3E17E0247A94DC00449CD1 /* PBXContainerItemProxy */;
+ };
+ 9A3E17E5247A94DC00449CD1 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 9A0C82D924460F7200FAE3D4 /* StatsKit */;
+ targetProxy = 9A3E17E4247A94DC00449CD1 /* PBXContainerItemProxy */;
+ };
+ 9A81C75C2449A41400825D92 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 9A81C7552449A41400825D92 /* Memory */;
+ targetProxy = 9A81C75B2449A41400825D92 /* PBXContainerItemProxy */;
+ };
+ 9A81C7652449A41E00825D92 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 9AABEADC243FB13500668CB0 /* ModuleKit */;
+ targetProxy = 9A81C7642449A41E00825D92 /* PBXContainerItemProxy */;
+ };
+ 9A81C76E2449AE9400825D92 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 9A0C82D924460F7200FAE3D4 /* StatsKit */;
+ targetProxy = 9A81C76D2449AE9400825D92 /* PBXContainerItemProxy */;
+ };
+ 9AABEAE3243FB13500668CB0 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 9AABEADC243FB13500668CB0 /* ModuleKit */;
+ targetProxy = 9AABEAE2243FB13500668CB0 /* PBXContainerItemProxy */;
+ };
+ 9AABEB6A243FCE8A00668CB0 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 9AABEB63243FCE8A00668CB0 /* CPU */;
+ targetProxy = 9AABEB69243FCE8A00668CB0 /* PBXContainerItemProxy */;
+ };
+ 9AABEB75243FCEEF00668CB0 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 9AABEADC243FB13500668CB0 /* ModuleKit */;
+ targetProxy = 9AABEB74243FCEEF00668CB0 /* PBXContainerItemProxy */;
+ };
+ 9ABFF8FC248BEBCB00C9041A /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 9ABFF8F5248BEBCB00C9041A /* Battery */;
+ targetProxy = 9ABFF8FB248BEBCB00C9041A /* PBXContainerItemProxy */;
+ };
+ 9ABFF909248BEC2600C9041A /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 9AABEADC243FB13500668CB0 /* ModuleKit */;
+ targetProxy = 9ABFF908248BEC2600C9041A /* PBXContainerItemProxy */;
+ };
+ 9ABFF90E248BEC2900C9041A /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 9A0C82D924460F7200FAE3D4 /* StatsKit */;
+ targetProxy = 9ABFF90D248BEC2900C9041A /* PBXContainerItemProxy */;
+ };
+ 9AF9EE0824648751005D2270 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 9AF9EE0124648751005D2270 /* Disk */;
+ targetProxy = 9AF9EE0724648751005D2270 /* PBXContainerItemProxy */;
+ };
+ 9AF9EE1724649BAD005D2270 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 9AABEADC243FB13500668CB0 /* ModuleKit */;
+ targetProxy = 9AF9EE1624649BAD005D2270 /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
+ 9A0C82E424460F7200FAE3D4 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ APPLICATION_EXTENSION_API_ONLY = NO;
+ CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES;
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ CURRENT_PROJECT_VERSION = 1;
+ DEFINES_MODULE = YES;
+ DEVELOPMENT_TEAM = RP2S87B72W;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/Carthage/Build/Mac",
+ "$(LOCAL_LIBRARY_DIR)/Frameworks",
+ );
+ INFOPLIST_FILE = StatsKit/Info.plist;
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
+ PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.StatsKit;
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ SKIP_INSTALL = YES;
+ SWIFT_OBJC_BRIDGING_HEADER = "";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Debug;
+ };
+ 9A0C82E524460F7200FAE3D4 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ APPLICATION_EXTENSION_API_ONLY = NO;
+ CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES;
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ CURRENT_PROJECT_VERSION = 1;
+ DEFINES_MODULE = YES;
+ DEVELOPMENT_TEAM = RP2S87B72W;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/Carthage/Build/Mac",
+ "$(LOCAL_LIBRARY_DIR)/Frameworks",
+ );
+ INFOPLIST_FILE = StatsKit/Info.plist;
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
+ PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.StatsKit;
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ SKIP_INSTALL = YES;
+ SWIFT_OBJC_BRIDGING_HEADER = "";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Release;
+ };
9A141103229E721200D29793 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -672,7 +1430,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- MACOSX_DEPLOYMENT_TARGET = 10.13;
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
@@ -727,7 +1485,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- MACOSX_DEPLOYMENT_TARGET = 10.13;
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
@@ -739,6 +1497,7 @@
9A141106229E721200D29793 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "";
CODE_SIGN_IDENTITY = "Mac Developer";
@@ -750,15 +1509,15 @@
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
- "$(PROJECT_DIR)/Carthage/Build/Mac",
+ "$(LOCAL_LIBRARY_DIR)/Frameworks",
);
INFOPLIST_FILE = "$(SRCROOT)/Stats/Supporting Files/Info.plist";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
- MACOSX_DEPLOYMENT_TARGET = 10.13;
- MARKETING_VERSION = 1.6.5;
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
+ MARKETING_VERSION = 2.0.0;
PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -770,6 +1529,7 @@
9A141107229E721200D29793 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "";
CODE_SIGN_IDENTITY = "Mac Developer";
@@ -781,15 +1541,15 @@
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
- "$(PROJECT_DIR)/Carthage/Build/Mac",
+ "$(LOCAL_LIBRARY_DIR)/Frameworks",
);
INFOPLIST_FILE = "$(SRCROOT)/Stats/Supporting Files/Info.plist";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
- MACOSX_DEPLOYMENT_TARGET = 10.13;
- MARKETING_VERSION = 1.6.5;
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
+ MARKETING_VERSION = 2.0.0;
PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -803,6 +1563,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = LaunchAtLogin/LaunchAtLogin.entitlements;
+ CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_ASSET_PATHS = "";
@@ -814,7 +1575,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
- MACOSX_DEPLOYMENT_TARGET = 10.15;
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.LaunchAtLogin;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@@ -827,6 +1588,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = LaunchAtLogin/LaunchAtLogin.entitlements;
+ CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_ASSET_PATHS = "";
@@ -838,7 +1600,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
- MACOSX_DEPLOYMENT_TARGET = 10.15;
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.LaunchAtLogin;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@@ -846,9 +1608,418 @@
};
name = Release;
};
+ 9A3E17D6247A94AF00449CD1 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_IDENTITY = "Mac Developer";
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ CURRENT_PROJECT_VERSION = 1;
+ DEFINES_MODULE = YES;
+ DEVELOPMENT_TEAM = RP2S87B72W;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/Carthage/Build/Mac",
+ );
+ INFOPLIST_FILE = Modules/Net/Info.plist;
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
+ PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.Net;
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ SKIP_INSTALL = YES;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Debug;
+ };
+ 9A3E17D7247A94AF00449CD1 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_IDENTITY = "Mac Developer";
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ CURRENT_PROJECT_VERSION = 1;
+ DEFINES_MODULE = YES;
+ DEVELOPMENT_TEAM = RP2S87B72W;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/Carthage/Build/Mac",
+ );
+ INFOPLIST_FILE = Modules/Net/Info.plist;
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
+ PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.Net;
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ SKIP_INSTALL = YES;
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Release;
+ };
+ 9A81C7602449A41400825D92 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_IDENTITY = "";
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ CURRENT_PROJECT_VERSION = 1;
+ DEFINES_MODULE = YES;
+ DEVELOPMENT_TEAM = RP2S87B72W;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ INFOPLIST_FILE = Modules/Memory/Info.plist;
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
+ PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.Memory;
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ SKIP_INSTALL = YES;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Debug;
+ };
+ 9A81C7612449A41400825D92 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_IDENTITY = "";
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ CURRENT_PROJECT_VERSION = 1;
+ DEFINES_MODULE = YES;
+ DEVELOPMENT_TEAM = RP2S87B72W;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ INFOPLIST_FILE = Modules/Memory/Info.plist;
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
+ PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.Memory;
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ SKIP_INSTALL = YES;
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Release;
+ };
+ 9AABEAE6243FB13500668CB0 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ CURRENT_PROJECT_VERSION = 1;
+ DEFINES_MODULE = YES;
+ DEVELOPMENT_TEAM = RP2S87B72W;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/Carthage/Build/Mac",
+ );
+ INFOPLIST_FILE = "ModuleKit/Supporting Files/Info.plist";
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
+ PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.ModuleKit;
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ SKIP_INSTALL = YES;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Debug;
+ };
+ 9AABEAE7243FB13500668CB0 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ CURRENT_PROJECT_VERSION = 1;
+ DEFINES_MODULE = YES;
+ DEVELOPMENT_TEAM = RP2S87B72W;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/Carthage/Build/Mac",
+ );
+ INFOPLIST_FILE = "ModuleKit/Supporting Files/Info.plist";
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
+ PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.ModuleKit;
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ SKIP_INSTALL = YES;
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Release;
+ };
+ 9AABEB6E243FCE8A00668CB0 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_IDENTITY = "";
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ CURRENT_PROJECT_VERSION = 1;
+ DEFINES_MODULE = YES;
+ DEVELOPMENT_TEAM = RP2S87B72W;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(PROJECT_DIR)/Carthage/Build/Mac",
+ "$(inherited)",
+ "$(LOCAL_LIBRARY_DIR)/Frameworks",
+ );
+ INFOPLIST_FILE = Modules/CPU/Info.plist;
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
+ PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.CPU;
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ REEXPORTED_LIBRARY_PATHS = "";
+ SKIP_INSTALL = YES;
+ SWIFT_OBJC_BRIDGING_HEADER = "";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Debug;
+ };
+ 9AABEB6F243FCE8A00668CB0 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_IDENTITY = "";
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ CURRENT_PROJECT_VERSION = 1;
+ DEFINES_MODULE = YES;
+ DEVELOPMENT_TEAM = RP2S87B72W;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(PROJECT_DIR)/Carthage/Build/Mac",
+ "$(inherited)",
+ "$(LOCAL_LIBRARY_DIR)/Frameworks",
+ );
+ INFOPLIST_FILE = Modules/CPU/Info.plist;
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
+ PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.CPU;
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ REEXPORTED_LIBRARY_PATHS = "";
+ SKIP_INSTALL = YES;
+ SWIFT_OBJC_BRIDGING_HEADER = "";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Release;
+ };
+ 9ABFF8FF248BEBCB00C9041A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_IDENTITY = "Mac Developer";
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ CURRENT_PROJECT_VERSION = 1;
+ DEFINES_MODULE = YES;
+ DEVELOPMENT_TEAM = RP2S87B72W;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ INFOPLIST_FILE = Modules/Battery/Info.plist;
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
+ PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.Battery;
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ SKIP_INSTALL = YES;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Debug;
+ };
+ 9ABFF900248BEBCB00C9041A /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_IDENTITY = "Mac Developer";
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ CURRENT_PROJECT_VERSION = 1;
+ DEFINES_MODULE = YES;
+ DEVELOPMENT_TEAM = RP2S87B72W;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ INFOPLIST_FILE = Modules/Battery/Info.plist;
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
+ PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.Battery;
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ SKIP_INSTALL = YES;
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Release;
+ };
+ 9AF9EE0B24648751005D2270 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_IDENTITY = "";
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ CURRENT_PROJECT_VERSION = 1;
+ DEFINES_MODULE = YES;
+ DEVELOPMENT_ASSET_PATHS = "";
+ DEVELOPMENT_TEAM = RP2S87B72W;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ INFOPLIST_FILE = Modules/Disk/Info.plist;
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
+ PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.Disk;
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ SKIP_INSTALL = YES;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Debug;
+ };
+ 9AF9EE0C24648751005D2270 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_IDENTITY = "";
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ CURRENT_PROJECT_VERSION = 1;
+ DEFINES_MODULE = YES;
+ DEVELOPMENT_ASSET_PATHS = "";
+ DEVELOPMENT_TEAM = RP2S87B72W;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ INFOPLIST_FILE = Modules/Disk/Info.plist;
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
+ PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.Disk;
+ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+ SKIP_INSTALL = YES;
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Release;
+ };
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
+ 9A0C82E324460F7200FAE3D4 /* Build configuration list for PBXNativeTarget "StatsKit" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 9A0C82E424460F7200FAE3D4 /* Debug */,
+ 9A0C82E524460F7200FAE3D4 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
9A1410F0229E721100D29793 /* Build configuration list for PBXProject "Stats" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@@ -876,6 +2047,60 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ 9A3E17D5247A94AF00449CD1 /* Build configuration list for PBXNativeTarget "Net" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 9A3E17D6247A94AF00449CD1 /* Debug */,
+ 9A3E17D7247A94AF00449CD1 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 9A81C75F2449A41400825D92 /* Build configuration list for PBXNativeTarget "Memory" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 9A81C7602449A41400825D92 /* Debug */,
+ 9A81C7612449A41400825D92 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 9AABEAE8243FB13500668CB0 /* Build configuration list for PBXNativeTarget "ModuleKit" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 9AABEAE6243FB13500668CB0 /* Debug */,
+ 9AABEAE7243FB13500668CB0 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 9AABEB6D243FCE8A00668CB0 /* Build configuration list for PBXNativeTarget "CPU" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 9AABEB6E243FCE8A00668CB0 /* Debug */,
+ 9AABEB6F243FCE8A00668CB0 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 9ABFF901248BEBCB00C9041A /* Build configuration list for PBXNativeTarget "Battery" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 9ABFF8FF248BEBCB00C9041A /* Debug */,
+ 9ABFF900248BEBCB00C9041A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 9AF9EE0D24648751005D2270 /* Build configuration list for PBXNativeTarget "Disk" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 9AF9EE0B24648751005D2270 /* Debug */,
+ 9AF9EE0C24648751005D2270 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
/* End XCConfigurationList section */
};
rootObject = 9A1410ED229E721100D29793 /* Project object */;
diff --git a/Stats.xcodeproj/xcshareddata/xcschemes/Stats.xcscheme b/Stats.xcodeproj/xcshareddata/xcschemes/Stats.xcscheme
index d70d86ea..d75206f5 100644
--- a/Stats.xcodeproj/xcshareddata/xcschemes/Stats.xcscheme
+++ b/Stats.xcodeproj/xcshareddata/xcschemes/Stats.xcscheme
@@ -1,6 +1,6 @@
+ allowLocationSimulation = "NO">
+
+
+
+
+
+
+
+
+
+
= activeModules.count-1 {
- stackView.addArrangedSubview(module.first!.widget.view)
- } else {
- stackView.insertArrangedSubview(module.first!.widget.view, at: position!)
- stackView.updateLayer()
- }
- }
- } else {
- // if module not active but exist, remove from stack (disable module), else replace
- if !module.first!.enabled {
- view.first!.removeFromSuperview()
- } else {
- let newView = module.first!.widget.view
- newView.invalidateIntrinsicContentSize()
- self.stackView.replaceSubview(view.first!, with: newView)
- }
- }
-
- self.updateWidth()
- self.popup.reload()
- }
-
- /*
- Refresh wigets views if size of view was changed.
- For enabling/disabling widgets, please use reload().
- */
- public func refresh() {
- self.stackView.subviews.forEach { view in
- if !(view is Widget) { return }
-
- let module = self.modules.first { $0.name == (view as! Widget).name }
- if module == nil {
- return
- }
-
- module!.widget.view.invalidateIntrinsicContentSize()
- self.stackView.replaceSubview(view, with: module!.widget.view)
- self.updateWidth()
- }
- }
-
- /*
- Destroy will destroy status bar view.
- */
- public func destroy() {
- for module in self.modules {
- module.stop()
- }
- }
-
- private func updateWidth() {
- var WIDTH: CGFloat = 0
- for module in self.modules {
- if module.enabled && module.available {
- WIDTH = WIDTH + module.widget.view.frame.size.width
- }
- }
-
- if self.stackView.subviews.count == 0 || WIDTH == 0 {
- self.menuBarButton.image = NSImage(named:NSImage.Name("tray_icon"))
- self.menuBarItem.length = widgetSize.width
- self.stackView.frame.size.width = widgetSize.width
- } else {
- self.menuBarButton.image = nil
- self.stackView.frame.size.width = WIDTH
- self.menuBarItem.length = WIDTH
- }
- }
-}
diff --git a/Stats/Modules/Battery/Battery.swift b/Stats/Modules/Battery/Battery.swift
deleted file mode 100644
index ab78a960..00000000
--- a/Stats/Modules/Battery/Battery.swift
+++ /dev/null
@@ -1,95 +0,0 @@
-//
-// Battery.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 14/01/2020.
-// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-import IOKit.ps
-import Repeat
-
-class Battery: Module {
- public var name: String = "Battery"
-
- public var enabled: Bool = true
- public var available: Bool {
- get {
- let snapshot = IOPSCopyPowerSourcesInfo().takeRetainedValue()
- let sources = IOPSCopyPowerSourcesList(snapshot).takeRetainedValue() as Array
- return sources.count > 0
- }
- }
-
- public var readers: [Reader] = []
- public var task: Repeater?
-
- public var widget: ModuleWidget = ModuleWidget()
- public var popup: ModulePopup = ModulePopup(true)
- public var menu: NSMenuItem = NSMenuItem()
-
- internal let defaults = UserDefaults.standard
- internal var submenu: NSMenu = NSMenu()
-
- internal var cyclesValue: NSTextField = NSTextField()
- internal var stateValue: NSTextField = NSTextField()
- internal var healthValue: NSTextField = NSTextField()
- internal var amperageValue: NSTextField = NSTextField()
- internal var voltageValue: NSTextField = NSTextField()
- internal var temperatureValue: NSTextField = NSTextField()
- internal var powerValue: NSTextField = NSTextField()
- internal var chargingValue: NSTextField = NSTextField()
- internal var levelValue: NSTextField = NSTextField()
- internal var sourceValue: NSTextField = NSTextField()
- internal var timeLabel: NSTextField = NSTextField()
- internal var timeValue: NSTextField = NSTextField()
-
- init() {
- if !self.available { return }
-
- self.enabled = defaults.object(forKey: name) != nil ? defaults.bool(forKey: name) : true
- self.widget.type = defaults.object(forKey: "\(name)_widget") != nil ? defaults.float(forKey: "\(name)_widget") : Widgets.Battery
-
- self.initWidget()
- self.initMenu()
- self.initPopup()
-
- readers.append(BatteryReader(self.usageUpdater))
- }
-
- public func start() {
- (readers[0] as! BatteryReader).start()
- }
-
- public func stop() {
- if readers.count > 0 {
- (readers[0] as! BatteryReader).stop()
- }
- }
-
- public func restart() {
- self.stop()
- self.start()
- }
-
- private func usageUpdater(value: BatteryUsage) {
- self.popupUpdater(value: value)
-
- var time = value.timeToEmpty
- if time == 0 && value.timeToCharge != 0 {
- time = value.timeToCharge
- }
-
- if self.widget.view is Widget {
- (self.widget.view as! Widget).setValue(data: [abs(value.level), Double(time)])
-
- if self.widget.view is BatteryWidget && value.level != 100 {
- (self.widget.view as! BatteryWidget).setCharging(value: value.level > 0)
- } else if self.widget.view is BatteryWidget && value.level == 100 {
- (self.widget.view as! BatteryWidget).setCharging(value: false)
- }
- }
- }
-}
-
diff --git a/Stats/Modules/Battery/BatteryMenu.swift b/Stats/Modules/Battery/BatteryMenu.swift
deleted file mode 100644
index ff1957e0..00000000
--- a/Stats/Modules/Battery/BatteryMenu.swift
+++ /dev/null
@@ -1,94 +0,0 @@
-//
-// BatteryMenu.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 14/01/2020.
-// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-
-extension Battery {
- public 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
- menu.isEnabled = true
-
- let percentage = NSMenuItem(title: "Percentage", action: #selector(toggleWidget), keyEquivalent: "")
- percentage.state = self.widget.type == Widgets.BatteryPercentage ? NSControl.StateValue.on : NSControl.StateValue.off
- percentage.target = self
-
- let time = NSMenuItem(title: "Time", action: #selector(toggleWidget), keyEquivalent: "")
- time.state = self.widget.type == Widgets.BatteryTime ? NSControl.StateValue.on : NSControl.StateValue.off
- time.target = self
-
- submenu.addItem(percentage)
- submenu.addItem(time)
-
- submenu.addItem(NSMenuItem.separator())
-
- if let view = self.widget.view as? Widget {
- for widgetMenu in view.menus {
- submenu.addItem(widgetMenu)
- }
- }
-
- if self.enabled {
- menu.submenu = submenu
- }
- }
-
- @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.enabled = state
- menuBar!.reload(name: self.name)
-
- if !state {
- menu.submenu = nil
- } else {
- menu.submenu = submenu
- }
-
- self.restart()
- }
-
- @objc func toggleWidget(_ sender: NSMenuItem) {
- var widgetCode: Float = 0.0
-
- switch sender.title {
- case "Percentage":
- widgetCode = Widgets.BatteryPercentage
- case "Time":
- widgetCode = Widgets.BatteryTime
- default:
- break
- }
-
- if self.widget.type == widgetCode {
- widgetCode = Widgets.Battery
- }
-
- let state = sender.state == NSControl.StateValue.on
- for item in self.submenu.items {
- if item.title == "Percentage" || item.title == "Time" {
- item.state = NSControl.StateValue.off
- }
- }
-
- sender.state = state ? NSControl.StateValue.off : NSControl.StateValue.on
- self.defaults.set(widgetCode, forKey: "\(name)_widget")
- self.widget.type = widgetCode
- self.initWidget()
- self.initMenu()
- menuBar!.reload(name: self.name)
- }
-}
diff --git a/Stats/Modules/Battery/BatteryPopup.swift b/Stats/Modules/Battery/BatteryPopup.swift
deleted file mode 100644
index 3e8bbf08..00000000
--- a/Stats/Modules/Battery/BatteryPopup.swift
+++ /dev/null
@@ -1,257 +0,0 @@
-//
-// BatteryPopup.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 14/01/2020.
-// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-
-extension Battery {
- public func initPopup() {
- self.popup.view.view?.frame = NSRect(x: 0, y: 0, width: TabWidth, height: TabHeight)
-
- self.makeMain()
- self.makeOverview()
- self.makeBattery()
- self.makePowerAdapter()
- }
-
- private 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: 11, y: stackHeight*2, width: TabWidth - 19, height: stackHeight))
- level.orientation = .horizontal
- level.distribution = .equalCentering
- let levelLabel = LabelField(string: "Level")
- self.levelValue = ValueField(string: "0 %")
- level.addView(levelLabel, in: .center)
- level.addView(self.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")
- self.sourceValue = ValueField(string: "AC Power")
- source.addView(sourceLabel, in: .center)
- source.addView(self.sourceValue, in: .center)
-
- let time: NSStackView = NSStackView(frame: NSRect(x: 10, y: 0, width: TabWidth - 20, height: stackHeight))
- time.orientation = .horizontal
- time.distribution = .equalCentering
- self.timeLabel = LabelField(string: "Time to charge")
- self.timeValue = ValueField(string: "Calculating")
- time.addView(self.timeLabel, in: .center)
- time.addView(self.timeValue, in: .center)
-
- vertical.addSubview(level)
- vertical.addSubview(source)
- vertical.addSubview(time)
-
- self.popup.view.view?.addSubview(vertical)
- }
-
- private 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.popup.view.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")
- self.cyclesValue = ValueField(string: "0")
- cycles.addView(cyclesLabel, in: .center)
- cycles.addView(self.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")
- self.healthValue = ValueField(string: "Calculating")
- health.addView(healthLabel, in: .center)
- health.addView(self.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")
- self.stateValue = ValueField(string: "Calculating")
- state.addView(stateLabel, in: .center)
- state.addView(self.stateValue, in: .center)
-
- vertical.addSubview(cycles)
- vertical.addSubview(health)
- vertical.addSubview(state)
-
- self.popup.view.view?.addSubview(vertical)
- }
-
- private 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.popup.view.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")
- self.amperageValue = ValueField(string: "0 mA")
- amperage.addView(amperageLabel, in: .center)
- amperage.addView(self.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")
- self.voltageValue = ValueField(string: "0 V")
- voltage.addView(voltageLabel, in: .center)
- voltage.addView(self.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")
- self.temperatureValue = ValueField(string: "0 °C")
- temperature.addView(temperatureLabel, in: .center)
- temperature.addView(self.temperatureValue, in: .center)
-
- vertical.addSubview(amperage)
- vertical.addSubview(voltage)
- vertical.addSubview(temperature)
-
- self.popup.view.view?.addSubview(vertical)
- }
-
- private 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.popup.view.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")
- self.powerValue = ValueField(string: "0 W")
- power.addView(powerLabel, in: .center)
- power.addView(self.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")
- self.chargingValue = ValueField(string: "No")
- charging.addView(chargingLabel, in: .center)
- charging.addView(self.chargingValue, in: .center)
-
- vertical.addSubview(power)
- vertical.addSubview(charging)
-
- self.popup.view.view?.addSubview(vertical)
- }
-
- public func popupUpdater(value: BatteryUsage) {
- if !self.popup.active && self.popup.initialized { return }
- self.popup.initialized = true
-
- // makeMain
- self.levelValue.stringValue = "\(Int(abs(value.level) * 100)) %"
- self.sourceValue.stringValue = value.powerSource
- if value.powerSource == "Battery Power" {
- self.timeLabel.stringValue = "Time to discharge"
- if value.timeToEmpty != -1 && value.timeToEmpty != 0 {
- self.timeValue.stringValue = Double(value.timeToEmpty*60).printSecondsToHoursMinutesSeconds()
- }
- } else {
- self.timeLabel.stringValue = "Time to charge"
- if value.timeToCharge != -1 && value.timeToCharge != 0 {
- self.timeValue.stringValue = Double(value.timeToCharge*60).printSecondsToHoursMinutesSeconds()
- }
- }
-
- if value.timeToEmpty == -1 || value.timeToEmpty == -1 {
- self.timeValue.stringValue = "Calculating"
- }
-
- if value.isCharged {
- self.timeValue.stringValue = "Fully charged"
- }
-
- // makeOverview
- self.cyclesValue.stringValue = "\(value.cycles)"
- self.stateValue.stringValue = value.state
- self.healthValue.stringValue = "\(value.health) %"
-
- // makeBattery
- self.amperageValue.stringValue = "\(abs(value.amperage)) mA"
- self.voltageValue.stringValue = "\(value.voltage.roundTo(decimalPlaces: 2)) V"
- self.temperatureValue.stringValue = "\(value.temperature) °C"
-
- // makePowerAdapter
- self.powerValue.stringValue = value.powerSource == "Battery Power" ? "Not connected" : "\(value.ACwatts) W"
- self.chargingValue.stringValue = value.level > 0 ? "Yes" : "No"
- }
-}
diff --git a/Stats/Modules/Battery/BatteryReader.swift b/Stats/Modules/Battery/BatteryReader.swift
deleted file mode 100644
index 1760410c..00000000
--- a/Stats/Modules/Battery/BatteryReader.swift
+++ /dev/null
@@ -1,189 +0,0 @@
-//
-// BatteryReader.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 14/01/2020.
-// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-import IOKit.ps
-
-struct BatteryUsage {
- var powerSource: String = ""
- var state: String = ""
- var isCharged: Bool = false
- var level: 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 {
- public var name: String = "Battery"
- public var enabled: Bool = true
- 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
- }
- }
- public var optional: Bool = false
- public var initialized: Bool = false
- public var callback: (BatteryUsage) -> Void = {_ in}
-
- private var service: io_connect_t = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleSmartBattery"))
- private var internalChecked: Bool = false
- private var hasInternalBattery: Bool = false
-
- private var source: CFRunLoopSource?
- private var loop: CFRunLoop?
-
- init(_ updater: @escaping (BatteryUsage) -> Void) {
- self.callback = updater
- self.read()
- }
-
- public func start() {
- let context = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
-
- source = IOPSNotificationCreateRunLoopSource({ (context) in
- guard let ctx = context else {
- return
- }
-
- let watcher = Unmanaged.fromOpaque(ctx).takeUnretainedValue()
- watcher.read()
- }, context).takeRetainedValue()
-
- loop = RunLoop.current.getCFRunLoop()
- CFRunLoopAddSource(loop, source, .defaultMode)
- }
-
- public func stop() {
- guard let runLoop = loop, let source = source else {
- return
- }
-
- CFRunLoopRemoveSource(runLoop, source, .defaultMode)
- }
-
- public func read() {
- if !self.enabled && self.initialized { return }
- self.initialized = true
-
- let psInfo = IOPSCopyPowerSourcesInfo().takeRetainedValue()
- let psList = IOPSCopyPowerSourcesList(psInfo).takeRetainedValue() as [CFTypeRef]
-
- for ps in psList {
- if let list = IOPSGetPowerSourceDescription(psInfo, ps).takeUnretainedValue() as? Dictionary {
- 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
-
- 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 {
- guard let watts = ACList[kIOPSPowerAdapterWattsKey] else {
- return
- }
- ACwatts = Int(watts as! Int)
- }
- }
- let ACstatus = self.getBoolValue("IsCharging" as CFString) ?? false
-
- if powerSource == "Battery Power" {
- cap = 0 - cap
- }
-
- DispatchQueue.main.async(execute: {
- let usage = BatteryUsage(
- powerSource: powerSource,
- state: state,
- isCharged: isCharged,
- level: Double(cap),
- cycles: cycles,
- health: (100 * maxCapacity) / designCapacity,
-
- amperage: amperage,
- voltage: voltage,
- temperature: temperature,
-
- ACwatts: ACwatts,
- ACstatus: ACstatus,
-
- timeToEmpty: timeToEmpty,
- timeToCharge: timeToCharged
- )
- self.callback(usage)
- })
- }
- }
- }
-
- public func toggleEnable(_ value: Bool) {
- self.enabled = value
- }
-
- private func getBoolValue(_ forIdentifier: CFString) -> Bool? {
- if let value = IORegistryEntryCreateCFProperty(self.service, forIdentifier, kCFAllocatorDefault, 0) {
- return value.takeRetainedValue() as? Bool
- }
- return nil
- }
-
- private func getIntValue(_ identifier: CFString) -> Int? {
- if let value = IORegistryEntryCreateCFProperty(self.service, identifier, kCFAllocatorDefault, 0) {
- return value.takeRetainedValue() as? Int
- }
- return nil
- }
-
- private func getDoubleValue(_ identifier: CFString) -> Double? {
- if let value = IORegistryEntryCreateCFProperty(self.service, identifier, kCFAllocatorDefault, 0) {
- return value.takeRetainedValue() as? Double
- }
- return nil
- }
-
- private func getVoltage() -> Double? {
- if let value = self.getDoubleValue("Voltage" as CFString) {
- return value / 1000.0
- }
- return nil
- }
-
- private func getTemperature() -> Double? {
- if let value = IORegistryEntryCreateCFProperty(self.service, "Temperature" as CFString, kCFAllocatorDefault, 0) {
- return value.takeRetainedValue() as! Double / 100.0
- }
- return nil
- }
-}
diff --git a/Stats/Modules/CPU/CPU.swift b/Stats/Modules/CPU/CPU.swift
deleted file mode 100644
index 0f07af19..00000000
--- a/Stats/Modules/CPU/CPU.swift
+++ /dev/null
@@ -1,80 +0,0 @@
-//
-// CPU.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 01.06.2019.
-// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-import Charts
-import Repeat
-
-class CPU: Module {
- public var name: String = "CPU"
- public var updateInterval: Double = 1
-
- public var enabled: Bool = true
- public var available: Bool = true
-
- public var readers: [Reader] = []
- public var task: Repeater?
-
- public var widget: ModuleWidget = ModuleWidget()
- public var popup: ModulePopup = ModulePopup(true)
- public var menu: NSMenuItem = NSMenuItem()
-
- internal let defaults = UserDefaults.standard
- internal var submenu: NSMenu = NSMenu()
-
- internal var systemValue: NSTextField = NSTextField()
- internal var userValue: NSTextField = NSTextField()
- internal var idleValue: NSTextField = NSTextField()
- internal var processViewList: [NSStackView] = []
- internal var chart: LineChartView = LineChartView()
-
- init() {
- if !self.available { return }
-
- self.enabled = defaults.object(forKey: name) != nil ? defaults.bool(forKey: name) : true
- self.updateInterval = defaults.object(forKey: "\(name)_interval") != nil ? defaults.double(forKey: "\(name)_interval") : self.updateInterval
- self.widget.type = defaults.object(forKey: "\(name)_widget") != nil ? defaults.float(forKey: "\(name)_widget") : Widgets.Mini
-
- self.initWidget()
- self.initMenu()
- self.initPopup()
-
- readers.append(CPULoadReader(self.name, self.loadUpdater, self.chartUpdater, true))
- readers.append(CPUUsageReader(self.usageUpdater))
- readers.append(CPUProcessReader(self.processesUpdater))
-
- self.task = Repeater.init(interval: .seconds(self.updateInterval), observer: { _ in
- self.readers.forEach { reader in
- reader.read()
- }
- })
- }
-
- public func start() {
- if self.task != nil && self.task!.state.isRunning == false {
- self.task!.start()
- }
- }
-
- public func stop() {
- if self.task!.state.isRunning {
- self.task?.pause()
- }
- }
-
- public func restart () {
- self.stop()
- self.start()
- }
-
- private func loadUpdater(value: [Double]) {
- if !value.isEmpty && self.widget.view is Widget {
- (self.widget.view as! Widget).setValue(data: value)
- }
- }
-}
diff --git a/Stats/Modules/CPU/CPULoadReader.swift b/Stats/Modules/CPU/CPULoadReader.swift
deleted file mode 100644
index 2277a886..00000000
--- a/Stats/Modules/CPU/CPULoadReader.swift
+++ /dev/null
@@ -1,144 +0,0 @@
-//
-// CPUUsageReader.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 13/01/2020.
-// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-
-class CPULoadReader: Reader {
- public var name: String = "Load"
- public var enabled: Bool = true
- public var available: Bool = true
- public var optional: Bool = false
- public var initialized: Bool = false
- public var callback: ([Double]) -> Void = {_ in}
- public var chartCallback: (Double) -> Void = {_ in}
-
- public var perCoreMode: Bool = true
- public var hyperthreading: Bool = false
-
- private var cpuInfo: processor_info_array_t!
- private var prevCpuInfo: processor_info_array_t?
- private var numCpuInfo: mach_msg_type_number_t = 0
- private var numPrevCpuInfo: mach_msg_type_number_t = 0
- private var numCPUs: uint = 0
- private let CPUUsageLock: NSLock = NSLock()
- private var loadPrevious = host_cpu_load_info()
-
- init(_ name: String, _ updater: @escaping ([Double]) -> Void, _ chartUpdater: @escaping (Double) -> Void, _ coreMode: Bool = false) {
- self.callback = updater
- self.chartCallback = chartUpdater
- self.perCoreMode = coreMode
- self.hyperthreading = UserDefaults.standard.object(forKey: "\(name)_hyperthreading") != nil ? UserDefaults.standard.bool(forKey: "\(name)_hyperthreading") : false
-
- let mibKeys: [Int32] = [ CTL_HW, HW_NCPU ]
-
- mibKeys.withUnsafeBufferPointer() { mib in
- var sizeOfNumCPUs: size_t = MemoryLayout.size
- let status = sysctl(processor_info_array_t(mutating: mib.baseAddress), 2, &numCPUs, &sizeOfNumCPUs, nil, 0)
- if status != 0 {
- numCPUs = 1
- }
- }
-
- if self.available {
- DispatchQueue.global(qos: .default).async {
- self.read()
- }
- }
- }
-
- public func toggleEnable(_ value: Bool) {
- self.enabled = value
- }
-
- public func read() {
- if !self.enabled && self.initialized { return }
-
- var numCPUsU: natural_t = 0
- let err: kern_return_t = host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &numCPUsU, &cpuInfo, &numCpuInfo);
-
- if err == KERN_SUCCESS {
- CPUUsageLock.lock()
-
- var inUseOnAllCores: Int32 = 0
- var totalOnAllCores: Int32 = 0
- var usagePerCore: [Double] = []
-
- var incrementNumber = 1
- if !self.hyperthreading && self.perCoreMode {
- incrementNumber = 2
- }
-
- for i in stride(from: 0, to: Int32(numCPUs), by: incrementNumber) {
- var inUse: Int32
- var total: Int32
- if let prevCpuInfo = prevCpuInfo {
- inUse = cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_USER)]
- - prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_USER)]
- + cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_SYSTEM)]
- - prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_SYSTEM)]
- + cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_NICE)]
- - prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_NICE)]
- total = inUse + (cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_IDLE)]
- - prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_IDLE)])
- } else {
- inUse = cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_USER)]
- + cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_SYSTEM)]
- + cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_NICE)]
- total = inUse + cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_IDLE)]
- }
-
- inUseOnAllCores = inUseOnAllCores + inUse
- totalOnAllCores = totalOnAllCores + total
- if total != 0 {
- usagePerCore.append(Double(inUse) / Double(total))
- }
- }
-
- DispatchQueue.main.async(execute: {
- if self.perCoreMode {
- self.callback(usagePerCore)
- } else {
- self.callback([(Double(inUseOnAllCores) / Double(totalOnAllCores))])
- }
- self.chartCallback(Double(inUseOnAllCores) / Double(totalOnAllCores))
- })
-
- CPUUsageLock.unlock()
-
- if let prevCpuInfo = prevCpuInfo {
- let prevCpuInfoSize: size_t = MemoryLayout.stride * Int(numPrevCpuInfo)
- vm_deallocate(mach_task_self_, vm_address_t(bitPattern: prevCpuInfo), vm_size_t(prevCpuInfoSize))
- }
-
- prevCpuInfo = cpuInfo
- numPrevCpuInfo = numCpuInfo
-
- cpuInfo = nil
- numCpuInfo = 0
- } else {
- print("Error KERN_SUCCESS!")
- }
- }
-
- private func hostCPULoadInfo() -> host_cpu_load_info? {
- let HOST_CPU_LOAD_INFO_COUNT = MemoryLayout.stride/MemoryLayout.stride
- var size = mach_msg_type_number_t(HOST_CPU_LOAD_INFO_COUNT)
- var cpuLoadInfo = host_cpu_load_info()
-
- let result = withUnsafeMutablePointer(to: &cpuLoadInfo) {
- $0.withMemoryRebound(to: integer_t.self, capacity: HOST_CPU_LOAD_INFO_COUNT) {
- host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, $0, &size)
- }
- }
- if result != KERN_SUCCESS {
- print("Error - \(#file): \(#function) - kern_result_t = \(result)")
- return nil
- }
- return cpuLoadInfo
- }
-}
diff --git a/Stats/Modules/CPU/CPUMenu.swift b/Stats/Modules/CPU/CPUMenu.swift
deleted file mode 100644
index f81b51ad..00000000
--- a/Stats/Modules/CPU/CPUMenu.swift
+++ /dev/null
@@ -1,206 +0,0 @@
-//
-// CPUMenu.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 13/01/2020.
-// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-
-extension CPU {
- public func initMenu() {
- self.menu = NSMenuItem(title: name, action: #selector(toggle), keyEquivalent: "")
- self.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.widget.type == Widgets.Mini ? NSControl.StateValue.on : NSControl.StateValue.off
- mini.target = self
-
- let chart = NSMenuItem(title: "Chart", action: #selector(toggleWidget), keyEquivalent: "")
- chart.state = self.widget.type == 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.widget.type == Widgets.ChartWithValue ? NSControl.StateValue.on : NSControl.StateValue.off
- chartWithValue.target = self
-
- let barChart = NSMenuItem(title: "Bar chart", action: #selector(toggleWidget), keyEquivalent: "")
- barChart.state = self.widget.type == Widgets.BarChart ? NSControl.StateValue.on : NSControl.StateValue.off
- barChart.target = self
-
- let hyperthreading = NSMenuItem(title: "Hyperthreading", action: #selector(toggleHyperthreading), keyEquivalent: "")
- let hyper = UserDefaults.standard.object(forKey: "\(name)_hyperthreading") != nil ? UserDefaults.standard.bool(forKey: "\(name)_hyperthreading") : false
- hyperthreading.state = hyper ? NSControl.StateValue.on : NSControl.StateValue.off
- hyperthreading.target = self
-
- submenu.addItem(mini)
- submenu.addItem(chart)
- submenu.addItem(chartWithValue)
- submenu.addItem(barChart)
-
- submenu.addItem(NSMenuItem.separator())
-
- if let view = self.widget.view as? Widget {
- for widgetMenu in view.menus {
- submenu.addItem(widgetMenu)
- }
- }
-
- if self.widget.type == Widgets.BarChart {
- submenu.addItem(hyperthreading)
- }
-
- submenu.addItem(NSMenuItem.separator())
- submenu.addItem(generateIntervalMenu())
-
- if self.enabled {
- menu.submenu = submenu
- }
- }
-
- @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.enabled = state
- menuBar!.reload(name: self.name)
-
- if !state {
- menu.submenu = nil
- } else {
- menu.submenu = submenu
- }
-
- self.restart()
- }
-
- @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
- case "Bar chart":
- widgetCode = Widgets.BarChart
- default:
- break
- }
-
- if widgetCode == Widgets.BarChart {
- self.readers.forEach { reader in
- if reader is CPULoadReader {
- (reader as! CPULoadReader).perCoreMode = true
- }
- }
- } else {
- self.readers.filter{ $0 is CPULoadReader }.forEach { reader in
- (reader as! CPULoadReader).perCoreMode = false
- }
- }
-
- if self.widget.type == widgetCode {
- return
- }
-
- for item in self.submenu.items {
- if item.title == "Mini" || item.title == "Chart" || item.title == "Chart with value" || item.title == "Bar chart" {
- 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.self.widget.type = widgetCode
- self.initWidget()
- self.initMenu()
- menuBar!.reload(name: self.name)
- }
-
- @objc func toggleHyperthreading(_ sender: NSMenuItem) {
- sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on
- self.defaults.set(sender.state == NSControl.StateValue.on, forKey: "\(name)_hyperthreading")
- self.readers.filter{ $0 is CPULoadReader }.forEach { reader in
- (reader as! CPULoadReader).hyperthreading = sender.state == NSControl.StateValue.on
- }
- }
-
- private func generateIntervalMenu() -> NSMenuItem {
- let updateInterval = NSMenuItem(title: "Update interval", action: nil, keyEquivalent: "")
-
- let updateIntervals = NSMenu()
- let updateInterval_1 = NSMenuItem(title: "1s", action: #selector(changeInterval), keyEquivalent: "")
- updateInterval_1.state = self.updateInterval == 1 ? NSControl.StateValue.on : NSControl.StateValue.off
- updateInterval_1.target = self
- let updateInterval_2 = NSMenuItem(title: "3s", action: #selector(changeInterval), keyEquivalent: "")
- updateInterval_2.state = self.updateInterval == 3 ? NSControl.StateValue.on : NSControl.StateValue.off
- updateInterval_2.target = self
- let updateInterval_3 = NSMenuItem(title: "5s", action: #selector(changeInterval), keyEquivalent: "")
- updateInterval_3.state = self.updateInterval == 5 ? NSControl.StateValue.on : NSControl.StateValue.off
- updateInterval_3.target = self
- let updateInterval_4 = NSMenuItem(title: "10s", action: #selector(changeInterval), keyEquivalent: "")
- updateInterval_4.state = self.updateInterval == 10 ? NSControl.StateValue.on : NSControl.StateValue.off
- updateInterval_4.target = self
- let updateInterval_5 = NSMenuItem(title: "15s", action: #selector(changeInterval), keyEquivalent: "")
- updateInterval_5.state = self.updateInterval == 15 ? NSControl.StateValue.on : NSControl.StateValue.off
- updateInterval_5.target = self
-
- updateIntervals.addItem(updateInterval_1)
- updateIntervals.addItem(updateInterval_2)
- updateIntervals.addItem(updateInterval_3)
- updateIntervals.addItem(updateInterval_4)
- updateIntervals.addItem(updateInterval_5)
-
- updateInterval.submenu = updateIntervals
-
- return updateInterval
- }
-
- @objc func changeInterval(_ sender: NSMenuItem) {
- var interval: Double = self.updateInterval
-
- switch sender.title {
- case "1s":
- interval = 1
- case "3s":
- interval = 3
- case "5s":
- interval = 5
- case "10s":
- interval = 10
- case "15s":
- interval = 15
- default:
- break
- }
-
-
- if interval == self.updateInterval {
- return
- }
-
- for item in self.submenu.items {
- if item.title == "Update interval" {
- for subitem in item.submenu!.items {
- subitem.state = NSControl.StateValue.off
- }
- }
- }
-
- sender.state = NSControl.StateValue.on
- self.updateInterval = interval
- self.defaults.set(interval, forKey: "\(name)_interval")
- self.task?.reset(.seconds(interval), restart: self.task!.state.isRunning)
- }
-}
diff --git a/Stats/Modules/CPU/CPUPopup.swift b/Stats/Modules/CPU/CPUPopup.swift
deleted file mode 100644
index 03970fcd..00000000
--- a/Stats/Modules/CPU/CPUPopup.swift
+++ /dev/null
@@ -1,236 +0,0 @@
-//
-// CPUPopup.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 03/09/2019.
-// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-import Charts
-
-extension CPU {
- public func initPopup() {
- self.popup.view.view?.frame = NSRect(x: 0, y: 0, width: TabWidth, height: TabHeight)
-
- makeChart()
- makeOverview()
- makeProcesses()
- }
-
- private func makeChart() {
- 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.autoScaleMinMaxEnabled = true
-
- self.chart.rightAxis.enabled = false
-
- self.chart.leftAxis.axisMinimum = 0
- self.chart.leftAxis.axisMaximum = 100
- self.chart.leftAxis.labelCount = 6
- 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
-
- var lineChartEntry = [ChartDataEntry]()
- lineChartEntry.append(ChartDataEntry(x: 0, y: 0))
- 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.popup.view.view?.addSubview(self.chart)
- }
-
- public func chartUpdater(value: Double) {
- if self.chart.data == nil { return }
-
- let v: Double = Double((value * 100).roundTo(decimalPlaces: 2))!
-
- let index = Double((self.chart.data?.getDataSetByIndex(0)?.entryCount)!)
- self.chart.data?.addEntry(ChartDataEntry(x: index, y: v), dataSetIndex: 0)
-
- if index > 120 {
- self.chart.xAxis.axisMinimum = index - 120
- }
- self.chart.xAxis.axisMaximum = index
-
- if self.popup.active {
- self.chart.notifyDataSetChanged()
- self.chart.moveViewToX(index)
- }
- }
-
- private 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.popup.view.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 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")
- self.systemValue = ValueField(string: "0 %")
- system.addView(systemLabel, in: .center)
- system.addView(self.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")
- self.userValue = ValueField(string: "0 %")
- user.addView(userLabel, in: .center)
- user.addView(self.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")
- self.idleValue = ValueField(string: "0 %")
- idle.addView(idleLabel, in: .center)
- idle.addView(self.idleValue, in: .center)
-
- vertical.addSubview(system)
- vertical.addSubview(user)
- vertical.addSubview(idle)
-
- self.popup.view.view?.addSubview(vertical)
- }
-
- public func usageUpdater(value: CPUUsage) {
- if !self.popup.active && self.popup.initialized { return }
-
- self.systemValue.stringValue = "\(value.system.roundTo(decimalPlaces: 2)) %"
- self.userValue.stringValue = "\(value.user.roundTo(decimalPlaces: 2)) %"
- self.idleValue.stringValue = "\(value.idle.roundTo(decimalPlaces: 2)) %"
- }
-
- private 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.popup.view.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
-
- self.processViewList = []
- 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: "")
-
- self.processViewList.append(process_1)
- self.processViewList.append(process_2)
- self.processViewList.append(process_3)
- self.processViewList.append(process_4)
- self.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.popup.view.view?.addSubview(vertical)
-
- label.frame = NSRect(x: 0, y: vertical.frame.origin.y + vertical.frame.size.height + 2, width: TabWidth, height: 25)
- self.popup.view.view?.addSubview(label)
- }
-
- public func processesUpdater(value: [TopProcess]) {
- if self.processViewList.isEmpty || !self.popup.active && self.popup.initialized { return }
- self.popup.initialized = true
-
- for (i, process) in value.enumerated() {
- if i < 5 {
- let processView = self.processViewList[i]
-
- (processView.subviews[0] as! NSTextField).stringValue = process.command
- (processView.subviews[1] as! NSTextField).stringValue = "\(process.usage.roundTo(decimalPlaces: 2)) %"
- }
- }
- }
-
- private 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
- }
-}
diff --git a/Stats/Modules/CPU/CPUProcessReader.swift b/Stats/Modules/CPU/CPUProcessReader.swift
deleted file mode 100644
index 6d9dca6e..00000000
--- a/Stats/Modules/CPU/CPUProcessReader.swift
+++ /dev/null
@@ -1,93 +0,0 @@
-//
-// CPUProcessReader.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 13/01/2020.
-// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-
-struct TopProcess {
- var pid: Int = 0
- var command: String = ""
- var usage: Double = 0
-}
-
-class CPUProcessReader: Reader {
- public var name: String = "Process"
- public var enabled: Bool = false
- public var available: Bool = true
- public var optional: Bool = true
- public var initialized: Bool = false
- public var callback: ([TopProcess]) -> Void = {_ in}
-
- private var loadPrevious = host_cpu_load_info()
-
- init(_ updater: @escaping ([TopProcess]) -> Void) {
- self.callback = updater
- if self.available {
- DispatchQueue.global(qos: .default).async {
- self.read()
- }
- }
- }
-
- public func toggleEnable(_ value: Bool) {
- self.enabled = value
- }
-
- public func read() {
- if !self.enabled && self.initialized { return }
- self.initialized = true
-
- let task = Process()
- task.launchPath = "/bin/ps"
- task.arguments = ["-Aceo pid,pcpu,comm", "-r"]
-
- let outputPipe = Pipe()
- let errorPipe = Pipe()
-
- task.standardOutput = outputPipe
- task.standardError = errorPipe
-
- do {
- try task.run()
- } catch let error {
- print(error)
- return
- }
-
- let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile()
- let errorData = errorPipe.fileHandleForReading.readDataToEndOfFile()
- let output = String(decoding: outputData, as: UTF8.self)
- _ = String(decoding: errorData, as: UTF8.self)
-
- if output.isEmpty {
- return
- }
-
- var index = 0
- var processes: [TopProcess] = []
- output.enumerateLines { (line, stop) -> () in
- if index != 0 {
- var str = line.trimmingCharacters(in: .whitespaces)
- let pidString = str.findAndCrop(pattern: "^\\d+")
- let usageString = str.findAndCrop(pattern: "^[0-9,.]+ ")
- let command = str.trimmingCharacters(in: .whitespaces)
-
- let pid = Int(pidString) ?? 0
- let usage = Double(usageString.replacingOccurrences(of: ",", with: ".")) ?? 0
-
- processes.append(TopProcess(pid: pid, command: command, usage: usage))
- }
-
- if index == 5 { stop = true }
- index += 1
- }
-
- DispatchQueue.main.async(execute: {
- self.callback(processes)
- })
- }
-}
diff --git a/Stats/Modules/CPU/CPUUsageReader.swift b/Stats/Modules/CPU/CPUUsageReader.swift
deleted file mode 100644
index dc396e79..00000000
--- a/Stats/Modules/CPU/CPUUsageReader.swift
+++ /dev/null
@@ -1,82 +0,0 @@
-//
-// CPUUsageReader.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 13/01/2020.
-// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-
-struct CPUUsage {
- var system: Double = 0
- var user: Double = 0
- var idle: Double = 0
-}
-
-class CPUUsageReader: Reader {
- public var name: String = "Usage"
- public var enabled: Bool = false
- public var available: Bool = true
- public var optional: Bool = true
- public var initialized: Bool = false
- public var callback: (CPUUsage) -> Void = {_ in}
-
- private var loadPrevious = host_cpu_load_info()
-
- init(_ updater: @escaping (CPUUsage) -> Void) {
- self.callback = updater
- if self.available {
- DispatchQueue.global(qos: .default).async {
- self.read()
- }
- }
- }
-
- public func toggleEnable(_ value: Bool) {
- self.enabled = value
- }
-
- public func read() {
- if !self.enabled && self.initialized { return }
-
- let load = hostCPULoadInfo()
- let userDiff = Double(load!.cpu_ticks.0 - loadPrevious.cpu_ticks.0)
- let sysDiff = Double(load!.cpu_ticks.1 - loadPrevious.cpu_ticks.1)
- let idleDiff = Double(load!.cpu_ticks.2 - loadPrevious.cpu_ticks.2)
- let niceDiff = Double(load!.cpu_ticks.3 - loadPrevious.cpu_ticks.3)
-
- let totalTicks = sysDiff + userDiff + niceDiff + idleDiff
-
- let sys = sysDiff / totalTicks * 100.0
- let user = userDiff / totalTicks * 100.0
- let idle = idleDiff / totalTicks * 100.0
-
- self.loadPrevious = load!
- self.initialized = true
-
- if !sys.isNaN && !user.isNaN && !idle.isNaN {
- DispatchQueue.main.async(execute: {
- self.callback(CPUUsage(system: sys, user: user, idle: idle))
- })
- }
- }
-
- private func hostCPULoadInfo() -> host_cpu_load_info? {
- let HOST_CPU_LOAD_INFO_COUNT = MemoryLayout.stride/MemoryLayout.stride
- var size = mach_msg_type_number_t(HOST_CPU_LOAD_INFO_COUNT)
- var cpuLoadInfo = host_cpu_load_info()
-
- let result = withUnsafeMutablePointer(to: &cpuLoadInfo) {
- $0.withMemoryRebound(to: integer_t.self, capacity: HOST_CPU_LOAD_INFO_COUNT) {
- host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, $0, &size)
- }
- }
- if result != KERN_SUCCESS {
- print("Error - \(#file): \(#function) - kern_result_t = \(result)")
- return nil
- }
-
- return cpuLoadInfo
- }
-}
diff --git a/Stats/Modules/Disk/Disk.swift b/Stats/Modules/Disk/Disk.swift
deleted file mode 100644
index d78fbf65..00000000
--- a/Stats/Modules/Disk/Disk.swift
+++ /dev/null
@@ -1,90 +0,0 @@
-//
-// Disk.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 14/01/2020.
-// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-import Repeat
-
-class Disk: Module {
- public var name: String = "SSD"
- public var updateInterval: Double = 5
-
- public var enabled: Bool = true
- public var available: Bool = true
-
- public var readers: [Reader] = []
- public var task: Repeater?
-
- public var widget: ModuleWidget = ModuleWidget()
- public var popup: ModulePopup = ModulePopup(false)
- public var menu: NSMenuItem = NSMenuItem()
-
- internal let defaults = UserDefaults.standard
- internal var submenu: NSMenu = NSMenu()
- internal var selectedDisk: String = ""
- internal var disks: disksList = disksList()
-
- init() {
- if !self.available { return }
-
- self.enabled = defaults.object(forKey: name) != nil ? defaults.bool(forKey: name) : true
- self.updateInterval = defaults.object(forKey: "\(name)_interval") != nil ? defaults.double(forKey: "\(name)_interval") : self.updateInterval
- self.widget.type = defaults.object(forKey: "\(name)_widget") != nil ? defaults.float(forKey: "\(name)_widget") : Widgets.Mini
- self.selectedDisk = defaults.object(forKey: "\(name)_disk") != nil ? defaults.string(forKey: "\(name)_disk")! : self.selectedDisk
-
- self.initWidget()
- self.initMenu()
-
- readers.append(DiskCapacityReader(self.usageUpdater))
-
- self.task = Repeater.init(interval: .seconds(self.updateInterval), observer: { _ in
- self.readers.forEach { reader in
- reader.read()
- }
- })
- }
-
- public func start() {
- if self.task != nil && self.task!.state.isRunning == false {
- self.task!.start()
- }
- }
-
- public func stop() {
- if self.task!.state.isRunning {
- self.task?.pause()
- }
- }
-
- public func restart() {
- self.stop()
- self.start()
- }
-
- private func usageUpdater(disks: disksList) {
- if self.disks.list.count != disks.list.count && disks.list.count != 0 {
- self.disks = disks
- self.initMenu()
- }
-
- if self.widget.view is Widget {
- var d: diskInfo? = disks.getDiskByBSDName(self.selectedDisk)
- if d == nil {
- d = disks.getRootDisk()
- }
-
- if d != nil {
- let total = d!.totalSize
- let free = d!.freeSize
- let usedSpace = total - free
- let percentage = Double(usedSpace) / Double(total)
-
- (self.widget.view as! Widget).setValue(data: [percentage])
- }
- }
- }
-}
diff --git a/Stats/Modules/Disk/DiskMenu.swift b/Stats/Modules/Disk/DiskMenu.swift
deleted file mode 100644
index 42ac543d..00000000
--- a/Stats/Modules/Disk/DiskMenu.swift
+++ /dev/null
@@ -1,201 +0,0 @@
-//
-// DiskMenu.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 14/01/2020.
-// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-
-extension Disk {
- public 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
-
- if self.disks.list.count > 1 {
- self.disks.list.forEach { (d: diskInfo) in
- let disk = NSMenuItem(title: d.name, action: #selector(toggleDisk), keyEquivalent: "")
- if self.selectedDisk == "" && d.root {
- disk.state = NSControl.StateValue.on
- } else {
- disk.state = self.selectedDisk == d.mediaBSDName ? NSControl.StateValue.on : NSControl.StateValue.off
- }
- disk.target = self
-
- submenu.addItem(disk)
- }
- }
-
- let mini = NSMenuItem(title: "Mini", action: #selector(toggleWidget), keyEquivalent: "")
- mini.state = self.widget.type == Widgets.Mini ? NSControl.StateValue.on : NSControl.StateValue.off
- mini.target = self
-
- let barChart = NSMenuItem(title: "Bar chart", action: #selector(toggleWidget), keyEquivalent: "")
- barChart.state = self.widget.type == Widgets.BarChart ? NSControl.StateValue.on : NSControl.StateValue.off
- barChart.target = self
-
- submenu.addItem(NSMenuItem.separator())
-
- submenu.addItem(mini)
- submenu.addItem(barChart)
-
- submenu.addItem(NSMenuItem.separator())
-
- if let view = self.widget.view as? Widget {
- for widgetMenu in view.menus {
- submenu.addItem(widgetMenu)
- }
- }
-
- submenu.addItem(NSMenuItem.separator())
- submenu.addItem(generateIntervalMenu())
-
- if self.enabled {
- menu.submenu = submenu
- }
- }
-
- @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.enabled = state
- menuBar!.reload(name: self.name)
-
- if !state {
- menu.submenu = nil
- } else {
- menu.submenu = submenu
- }
-
- self.restart()
- }
-
- @objc func toggleWidget(_ sender: NSMenuItem) {
- var widgetCode: Float = 0.0
-
- switch sender.title {
- case "Mini":
- widgetCode = Widgets.Mini
- case "Bar chart":
- widgetCode = Widgets.BarChart
- default:
- break
- }
-
- if self.widget.type == widgetCode {
- return
- }
-
- for item in self.submenu.items {
- if item.title == "Mini" || item.title == "Bar chart" {
- 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.widget.type = widgetCode
- self.initWidget()
- self.initMenu()
- menuBar!.reload(name: self.name)
- }
-
- private func generateIntervalMenu() -> NSMenuItem {
- let updateInterval = NSMenuItem(title: "Update interval", action: nil, keyEquivalent: "")
-
- let updateIntervals = NSMenu()
- let updateInterval_1 = NSMenuItem(title: "1s", action: #selector(changeInterval), keyEquivalent: "")
- updateInterval_1.state = self.updateInterval == 1 ? NSControl.StateValue.on : NSControl.StateValue.off
- updateInterval_1.target = self
- let updateInterval_2 = NSMenuItem(title: "3s", action: #selector(changeInterval), keyEquivalent: "")
- updateInterval_2.state = self.updateInterval == 3 ? NSControl.StateValue.on : NSControl.StateValue.off
- updateInterval_2.target = self
- let updateInterval_3 = NSMenuItem(title: "5s", action: #selector(changeInterval), keyEquivalent: "")
- updateInterval_3.state = self.updateInterval == 5 ? NSControl.StateValue.on : NSControl.StateValue.off
- updateInterval_3.target = self
- let updateInterval_4 = NSMenuItem(title: "10s", action: #selector(changeInterval), keyEquivalent: "")
- updateInterval_4.state = self.updateInterval == 10 ? NSControl.StateValue.on : NSControl.StateValue.off
- updateInterval_4.target = self
- let updateInterval_5 = NSMenuItem(title: "15s", action: #selector(changeInterval), keyEquivalent: "")
- updateInterval_5.state = self.updateInterval == 15 ? NSControl.StateValue.on : NSControl.StateValue.off
- updateInterval_5.target = self
-
- updateIntervals.addItem(updateInterval_1)
- updateIntervals.addItem(updateInterval_2)
- updateIntervals.addItem(updateInterval_3)
- updateIntervals.addItem(updateInterval_4)
- updateIntervals.addItem(updateInterval_5)
-
- updateInterval.submenu = updateIntervals
-
- return updateInterval
- }
-
- @objc func changeInterval(_ sender: NSMenuItem) {
- var interval: Double = self.updateInterval
-
- switch sender.title {
- case "1s":
- interval = 1
- case "3s":
- interval = 3
- case "5s":
- interval = 5
- case "10s":
- interval = 10
- case "15s":
- interval = 15
- default:
- break
- }
-
- if interval == self.updateInterval {
- return
- }
-
- for item in self.submenu.items {
- if item.title == "Update interval" {
- for subitem in item.submenu!.items {
- subitem.state = NSControl.StateValue.off
- }
- }
- }
-
- sender.state = NSControl.StateValue.on
- self.updateInterval = interval
- self.defaults.set(interval, forKey: "\(name)_interval")
- self.task?.reset(.seconds(interval), restart: self.task!.state.isRunning)
- }
-
- @objc func toggleDisk(_ sender: NSMenuItem) {
- let name: String = sender.title
- let d: diskInfo? = self.disks.getDiskByName(name)
- if d == nil {
- return
- }
-
- if d!.mediaBSDName == self.selectedDisk {
- return
- }
-
- for item in self.submenu.items {
- if self.disks.getDiskByName(item.title) != nil {
- item.state = NSControl.StateValue.off
- }
- }
-
- sender.state = NSControl.StateValue.on
- self.selectedDisk = d!.mediaBSDName
- self.defaults.set(d!.mediaBSDName, forKey: "\(name)_disk")
- menuBar!.reload(name: self.name)
- }
-}
diff --git a/Stats/Modules/Module.swift b/Stats/Modules/Module.swift
deleted file mode 100644
index c5f139f9..00000000
--- a/Stats/Modules/Module.swift
+++ /dev/null
@@ -1,115 +0,0 @@
-//
-// Module.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 08.07.2019.
-// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-import Charts
-import Repeat
-
-protocol Module: class {
- var name: String { get } // module name
-
- var enabled: Bool { get } // determine if module is enabled or disabled
- var available: Bool { get } // determine if module is available on this PC
-
- var widget: ModuleWidget { get set } // view for widget
- var menu: NSMenuItem { get } // view for menu
- var popup: ModulePopup { get set } // popup
-
- var readers: [Reader] { get } // list of readers available for module
- var task: Repeater? { get set } // reader cron task
-
- func start() // start module internal processes
- func stop() // stop module internal processes
- func restart() // restart module internal processes
-
- func initWidget()
-}
-
-protocol Reader {
- var name: String { get } // reader name
- var enabled: Bool { get set } // determine if reader is enabled or disabled
- var available: Bool { get } // determine if reader is available on this PC
- var optional: Bool { get } // say if reader are optional (additional information)
- var initialized: Bool { get } // to check if first read already done
-
- func read() // make one read
-
- func toggleEnable(_ value: Bool) -> Void // enable/disable optional reader
-}
-
-struct ModulePopup {
- var available: Bool = true // say if module have popup view
- var view: NSTabViewItem = NSTabViewItem() // module popup view
- var active: Bool = false // indicate that popup is opened and selected this view
- var initialized: Bool = false // allows to set some value when on first load
-
- init(_ a: Bool = true) {
- available = a
- }
-
- mutating func setActive(_ state: Bool) {
- if self.active != state {
- self.active = state
- }
- }
-}
-
-struct ModuleWidget {
- var type: WidgetType = Widgets.Mini // determine a widget typ
- var view: NSView = NSView() // widget view
-
- init(_ t: WidgetType = Widgets.Mini) {
- type = t
- }
-}
-
-extension Module {
- func initWidget() {
- var widget: Widget = Mini()
-
- switch self.widget.type {
- case Widgets.Mini:
- widget = Mini()
- case Widgets.Sensors:
- widget = SensorsWidget()
- case Widgets.Chart:
- widget = Chart()
- case Widgets.ChartWithValue:
- widget = ChartWithValue()
- case Widgets.NetworkDots:
- widget = NetworkDotsView()
- case Widgets.NetworkArrows:
- widget = NetworkArrowsView()
- case Widgets.NetworkText:
- widget = NetworkTextView()
- case Widgets.NetworkDotsWithText:
- widget = NetworkDotsTextView()
- case Widgets.NetworkArrowsWithText:
- widget = NetworkArrowsTextView()
- case Widgets.BarChart:
- widget = BarChart()
- case Widgets.Battery:
- widget = BatteryWidget()
- case Widgets.BatteryPercentage:
- widget = BatteryPercentageWidget()
- case Widgets.BatteryTime:
- widget = BatteryTimeWidget()
- default:
- widget = Mini()
- }
-
- widget.name = self.name
- widget.start()
-
- self.readers.forEach { reader in
- reader.read()
- }
-
- self.widget.view = widget as! NSView
- }
-}
diff --git a/Stats/Modules/Network/Network.swift b/Stats/Modules/Network/Network.swift
deleted file mode 100644
index 79231022..00000000
--- a/Stats/Modules/Network/Network.swift
+++ /dev/null
@@ -1,85 +0,0 @@
-//
-// Network.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 14/01/2020.
-// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-import Charts
-import Repeat
-
-class Network: Module {
- public var name: String = "Network"
- public var updateInterval: Double = 1
-
- public var enabled: Bool = true
- public var available: Bool = true
-
- public var readers: [Reader] = []
- public var task: Repeater?
-
- public var widget: ModuleWidget = ModuleWidget()
- public var popup: ModulePopup = ModulePopup(true)
- public var menu: NSMenuItem = NSMenuItem()
-
- internal let defaults = UserDefaults.standard
- internal var submenu: NSMenu = NSMenu()
- internal var chart: LineChartView = LineChartView()
-
- internal var publicIPValue: NSTextField = NSTextField()
- internal var localIPValue: NSTextField = NSTextField()
- internal var networkValue: NSTextField = NSTextField()
- internal var physicalValue: NSTextField = NSTextField()
- internal var downloadValue: NSTextField = NSTextField()
- internal var uploadValue: NSTextField = NSTextField()
- internal var totalDownloadValue: NSTextField = NSTextField()
- internal var totalUploadValue: NSTextField = NSTextField()
-
- init() {
- if !self.available { return }
-
- self.enabled = defaults.object(forKey: name) != nil ? defaults.bool(forKey: name) : true
- self.widget.type = defaults.object(forKey: "\(name)_widget") != nil ? defaults.float(forKey: "\(name)_widget") : Widgets.NetworkDots
-
- self.initWidget()
- self.initMenu()
- self.initPopup()
-
- readers.append(NetworkReader(self.usageUpdater))
- readers.append(NetworkInterfaceReader(self.overviewUpdater))
-
- self.task = Repeater.init(interval: .seconds(self.updateInterval), observer: { _ in
- self.readers.forEach { reader in
- reader.read()
- }
- })
- }
-
- public func start() {
- if self.task != nil && self.task!.state.isRunning == false {
- self.task!.start()
- }
- }
-
- public func stop() {
- if self.task!.state.isRunning {
- self.task?.pause()
- }
- }
-
- public func restart() {
- self.stop()
- self.start()
- }
-
- private func usageUpdater(value: NetworkUsage) {
- self.dataUpdater(value: value)
- self.chartUpdater(value: value)
-
- if self.widget.view is Widget {
- (self.widget.view as! Widget).setValue(data: [Double(value.download), Double(value.upload)])
- }
- }
-}
diff --git a/Stats/Modules/Network/NetworkInterfaceReader.swift b/Stats/Modules/Network/NetworkInterfaceReader.swift
deleted file mode 100644
index a142d301..00000000
--- a/Stats/Modules/Network/NetworkInterfaceReader.swift
+++ /dev/null
@@ -1,277 +0,0 @@
-//
-// NetworkInterfaceReader.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 22/01/2020.
-// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-import CoreWLAN
-import SystemConfiguration
-import Reachability
-
-struct NetworkInterface {
- var active: Bool
-
- var localIP: String?
- var publicIP: String?
- var countryCode: String?
-
- var networkType: String?
- var macAddress: String?
- var wifiName: String?
-
- var force: Bool = false
-
- init(
- active: Bool = false,
- localIP: String? = nil,
- publicIP: String? = nil,
- countryCode: String? = nil,
- networkType: String? = nil,
- macAddress: String? = nil,
- wifiName: String? = nil,
- force: Bool = false
- ) {
- self.active = active
-
- self.localIP = localIP
- self.publicIP = publicIP
- self.countryCode = countryCode
-
- self.networkType = networkType
- self.macAddress = macAddress
- self.wifiName = wifiName
-
- self.force = force
- }
-}
-
-class NetworkInterfaceReader: Reader {
- public var name: String = "Interface"
- public var enabled: Bool = false
- public var available: Bool = true
- public var optional: Bool = true
- public var initialized: Bool = false
- public var callback: (NetworkInterface) -> Void = {_ in}
-
- private var uploadValue: Int64 = 0
- private var downloadValue: Int64 = 0
-
- private var publicIP: String? = nil
- private var reachability: Reachability? = nil
- private var forceRead: Bool = false
-
- private var repeatCounter: Int8 = 0
-
- init(_ updater: @escaping (NetworkInterface) -> Void) {
- do {
- self.reachability = try Reachability()
- } catch let error {
- print("initialize Reachability \(error)")
- }
- self.callback = updater
-
- if self.available {
- DispatchQueue.global(qos: .default).async {
- self.read()
- }
- }
-
- if self.reachability != nil {
- self.reachability!.whenReachable = { reachability in
- self.repeatCounter = 0
- self.forceRead = true
- self.read()
- }
- self.reachability!.whenUnreachable = { _ in
- self.forceRead = true
- self.read()
- }
-
- do {
- try self.reachability!.startNotifier()
- } catch {
- print("Unable to start notifier")
- }
- }
- }
-
- public func read() {
- if (!self.enabled && self.initialized && !self.forceRead) || self.reachability == nil { return }
- self.initialized = true
-
- var result = NetworkInterface(active: false)
- result.force = self.forceRead
- if self.forceRead {
- self.forceRead = false
- }
-
- if self.reachability!.connection != .unavailable && isConnectedToNetwork() {
- if self.publicIP == nil {
- DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 3, execute: {
- if self.repeatCounter < 5 {
- self.publicIP = self.getPublicIP()
- self.forceRead = true
- self.read()
- self.repeatCounter += 1
- } else {
- self.publicIP = "Unknown"
- }
- })
- }
-
- result.active = true
-
- if self.reachability!.connection == .wifi && CWWiFiClient.shared().interface() != nil {
- result.networkType = "Wi-Fi"
- result.wifiName = CWWiFiClient.shared().interface()!.ssid()
- result.countryCode = CWWiFiClient.shared().interface()!.countryCode()
- result.macAddress = CWWiFiClient.shared().interface()!.hardwareAddress()
- } else {
- result.networkType = "Ethernet"
- result.macAddress = getMacAddress()
- }
-
- result.localIP = getLocalIP()
- result.publicIP = publicIP
- } else {
- self.publicIP = nil
- }
-
- DispatchQueue.main.async(execute: {
- self.callback(result)
- })
- }
-
- private func isWIFIActive() -> Bool {
- guard let interfaceNames = CWWiFiClient.interfaceNames() else {
- return false
- }
-
- for interfaceName in interfaceNames {
- let interface = CWWiFiClient.shared().interface(withName: interfaceName)
-
- if interface?.ssid() != nil {
- return true
- }
- }
- return false
- }
-
- // https://stackoverflow.com/questions/31835418/how-to-get-mac-address-from-os-x-with-swift
- private func getMacAddress() -> String? {
- var macAddressAsString : String?
- if let intfIterator = FindEthernetInterfaces() {
- if let macAddress = GetMACAddress(intfIterator) {
- macAddressAsString = macAddress.map( { String(format:"%02x", $0) } ).joined(separator: ":")
- }
- IOObjectRelease(intfIterator)
- }
- return macAddressAsString
- }
-
- private func FindEthernetInterfaces() -> io_iterator_t? {
- let matchingDictUM = IOServiceMatching("IOEthernetInterface");
- if matchingDictUM == nil {
- return nil
- }
-
- let matchingDict = matchingDictUM! as NSMutableDictionary
- matchingDict["IOPropertyMatch"] = [ "IOPrimaryInterface" : true]
-
- var matchingServices : io_iterator_t = 0
- if IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &matchingServices) != KERN_SUCCESS {
- return nil
- }
-
- return matchingServices
- }
-
- private func GetMACAddress(_ intfIterator : io_iterator_t) -> [UInt8]? {
- var macAddress : [UInt8]?
- var intfService = IOIteratorNext(intfIterator)
-
- while intfService != 0 {
- var controllerService : io_object_t = 0
- if IORegistryEntryGetParentEntry(intfService, kIOServicePlane, &controllerService) == KERN_SUCCESS {
- let dataUM = IORegistryEntryCreateCFProperty(controllerService, "IOMACAddress" as CFString, kCFAllocatorDefault, 0)
- if dataUM != nil {
- let data = (dataUM!.takeRetainedValue() as! CFData) as Data
- macAddress = [0, 0, 0, 0, 0, 0]
- data.copyBytes(to: &macAddress!, count: macAddress!.count)
- }
- IOObjectRelease(controllerService)
- }
-
- IOObjectRelease(intfService)
- intfService = IOIteratorNext(intfIterator)
- }
-
- return macAddress
- }
-
- private func getPublicIP() -> String? {
- let url = URL(string: "https://api.ipify.org")
- var address: String? = nil
-
- do {
- if let url = url {
- address = try String(contentsOf: url)
- if address!.contains("<") {
- address = nil
- }
- }
- } catch let error {
- print("get public ip \(error)")
- }
-
- return address
- }
-
- private func getLocalIP() -> String {
- var address: String = ""
-
- // Get list of all interfaces on the local machine:
- var ifaddr : UnsafeMutablePointer?
- guard getifaddrs(&ifaddr) == 0 else { return "" }
- guard let firstAddr = ifaddr else { return "" }
-
- // For each interface ...
- for ifptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) {
- let interface = ifptr.pointee
-
- // Check for IPv4 or IPv6 interface:
- let addrFamily = interface.ifa_addr.pointee.sa_family
- if addrFamily == UInt8(AF_INET) || addrFamily == UInt8(AF_INET6) {
-
- // Check interface name:
- let name = String(cString: interface.ifa_name)
- if name == "en0" {
-
- // Convert interface address to a human readable string:
- var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
- getnameinfo(interface.ifa_addr, socklen_t(interface.ifa_addr.pointee.sa_len),
- &hostname, socklen_t(hostname.count),
- nil, socklen_t(0), NI_NUMERICHOST)
- address = String(cString: hostname)
- } else if name == "en1" {
- // Convert interface address to a human readable string:
- var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
- getnameinfo(interface.ifa_addr, socklen_t(interface.ifa_addr.pointee.sa_len),
- &hostname, socklen_t(hostname.count),
- nil, socklen_t(1), NI_NUMERICHOST)
- address = String(cString: hostname)
- }
- }
- }
- freeifaddrs(ifaddr)
-
- return address
- }
-
- public func toggleEnable(_ value: Bool) {
- self.enabled = value
- }
-}
diff --git a/Stats/Modules/Network/NetworkMenu.swift b/Stats/Modules/Network/NetworkMenu.swift
deleted file mode 100644
index 29b6134f..00000000
--- a/Stats/Modules/Network/NetworkMenu.swift
+++ /dev/null
@@ -1,118 +0,0 @@
-//
-// NetworkMenu.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 14/01/2020.
-// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-
-extension Network {
- public 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 dots = NSMenuItem(title: "Dots", action: #selector(toggleWidget), keyEquivalent: "")
- dots.state = self.widget.type == Widgets.NetworkDots ? NSControl.StateValue.on : NSControl.StateValue.off
- dots.target = self
-
- let arrows = NSMenuItem(title: "Arrows", action: #selector(toggleWidget), keyEquivalent: "")
- arrows.state = self.widget.type == Widgets.NetworkArrows ? NSControl.StateValue.on : NSControl.StateValue.off
- arrows.target = self
-
- let text = NSMenuItem(title: "Text", action: #selector(toggleWidget), keyEquivalent: "")
- text.state = self.widget.type == Widgets.NetworkText ? NSControl.StateValue.on : NSControl.StateValue.off
- text.target = self
-
- let dotsWithText = NSMenuItem(title: "Dots with text", action: #selector(toggleWidget), keyEquivalent: "")
- dotsWithText.state = self.widget.type == Widgets.NetworkDotsWithText ? NSControl.StateValue.on : NSControl.StateValue.off
- dotsWithText.target = self
-
- let arrowsWithText = NSMenuItem(title: "Arrows with text", action: #selector(toggleWidget), keyEquivalent: "")
- arrowsWithText.state = self.widget.type == Widgets.NetworkArrowsWithText ? NSControl.StateValue.on : NSControl.StateValue.off
- arrowsWithText.target = self
-
- let chart = NSMenuItem(title: "Chart", action: #selector(toggleWidget), keyEquivalent: "")
- chart.state = self.widget.type == Widgets.NetworkChart ? NSControl.StateValue.on : NSControl.StateValue.off
- chart.target = self
-
- submenu.addItem(dots)
- submenu.addItem(arrows)
- submenu.addItem(text)
- submenu.addItem(dotsWithText)
- submenu.addItem(arrowsWithText)
-
- submenu.addItem(NSMenuItem.separator())
-
- if let view = self.widget.view as? Widget {
- for widgetMenu in view.menus {
- submenu.addItem(widgetMenu)
- }
- }
-
- if self.enabled {
- menu.submenu = submenu
- }
- }
-
- @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.enabled = state
- menuBar!.reload(name: self.name)
-
- if !state {
- menu.submenu = nil
- } else {
- menu.submenu = submenu
- }
-
- self.restart()
- }
-
- @objc func toggleWidget(_ sender: NSMenuItem) {
- var widgetCode: Float = 0.0
-
- switch sender.title {
- case "Dots":
- widgetCode = Widgets.NetworkDots
- case "Arrows":
- widgetCode = Widgets.NetworkArrows
- case "Text":
- widgetCode = Widgets.NetworkText
- case "Dots with text":
- widgetCode = Widgets.NetworkDotsWithText
- case "Arrows with text":
- widgetCode = Widgets.NetworkArrowsWithText
- case "Chart":
- widgetCode = Widgets.NetworkChart
- default:
- break
- }
-
- if self.widget.type == widgetCode {
- return
- }
-
- for item in self.submenu.items {
- if item.title == "Dots" || item.title == "Arrows" || item.title == "Text" || item.title == "Dots with text" || item.title == "Arrows with text" || item.title == "Chart" {
- 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.widget.type = widgetCode
- initWidget()
- menuBar!.reload(name: self.name)
- }
-}
diff --git a/Stats/Modules/Network/NetworkPopup.swift b/Stats/Modules/Network/NetworkPopup.swift
deleted file mode 100644
index 44ffd5a6..00000000
--- a/Stats/Modules/Network/NetworkPopup.swift
+++ /dev/null
@@ -1,284 +0,0 @@
-//
-// NetworkPopup.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 22/01/2020.
-// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-import Charts
-
-extension Network {
- public func initPopup() {
- self.popup.view.view?.frame = NSRect(x: 0, y: 0, width: TabWidth, height: TabHeight)
-
- makeChart()
- makeOverview()
- makeDataOverview()
- }
-
- private func makeChart() {
- let downloadLineColor: NSColor = NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 1.0)
- let downloadGradientColor: NSColor = NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 0.5)
-
- let uploadLineColor: NSColor = NSColor(red: (1), green: (0), blue: (0), alpha: 1.0)
- let uploadGradientColor: NSColor = NSColor(red: (1), green: (0), blue: (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.autoScaleMinMaxEnabled = true
-
- self.chart.rightAxis.enabled = false
-
- self.chart.leftAxis.valueFormatter = ChartsNetworkAxisFormatter()
- self.chart.leftAxis.axisMinimum = 0
- 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 = ChartNetworkMarker()
- marker.chartView = self.chart
- self.chart.marker = marker
-
- var downloadLineChartEntry = [ChartDataEntry]()
- downloadLineChartEntry.append(ChartDataEntry(x: 0, y: 0))
- let download = LineChartDataSet(entries: downloadLineChartEntry, label: "Download")
- download.drawCirclesEnabled = false
- download.mode = .cubicBezier
- download.cubicIntensity = 0.1
- download.colors = [downloadLineColor]
- download.fillColor = downloadGradientColor
- download.drawFilledEnabled = true
-
- var uploadLineChartEntry = [ChartDataEntry]()
- uploadLineChartEntry.append(ChartDataEntry(x: 0, y: 0))
- let upload = LineChartDataSet(entries: uploadLineChartEntry, label: "Upload")
- upload.drawCirclesEnabled = false
- upload.mode = .cubicBezier
- upload.cubicIntensity = 0.1
- upload.colors = [uploadLineColor]
- upload.fillColor = uploadGradientColor
- upload.drawFilledEnabled = true
-
- let data = LineChartData()
- data.addDataSet(download)
- data.addDataSet(upload)
- data.setDrawValues(false)
-
- self.chart.data = data
- self.popup.view.view?.addSubview(self.chart)
- }
-
- public func chartUpdater(value: NetworkUsage) {
- if self.chart.data == nil { return }
-
- let index = Double((self.chart.data?.getDataSetByIndex(0)?.entryCount)!)
- self.chart.data?.addEntry(ChartDataEntry(x: index, y: Double(value.download)), dataSetIndex: 0)
- self.chart.data?.addEntry(ChartDataEntry(x: index, y: Double(value.upload)), dataSetIndex: 1)
-
- if index > 120 {
- self.chart.xAxis.axisMinimum = index - 120
- }
- self.chart.xAxis.axisMaximum = index
-
- if self.popup.active {
- self.chart.notifyDataSetChanged()
- self.chart.moveViewToX(index)
- }
- }
-
- private 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.popup.view.view?.addSubview(overviewLabel)
-
- let stackHeight: CGFloat = 22
- let vertical: NSStackView = NSStackView(frame: NSRect(x: 0, y: 125, width: TabWidth, height: stackHeight*4))
- vertical.orientation = .vertical
-
- let publicIP: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*3, width: TabWidth - 20, height: stackHeight))
- publicIP.orientation = .horizontal
- publicIP.distribution = .equalCentering
- let publicIPLabel = LabelField(string: "Public IP")
- self.publicIPValue = ValueField(string: "No connection")
- publicIP.addView(publicIPLabel, in: .center)
- publicIP.addView(self.publicIPValue, in: .center)
-
- let localIP: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*2, width: TabWidth - 20, height: stackHeight))
- localIP.orientation = .horizontal
- localIP.distribution = .equalCentering
- let localIPLabel = LabelField(string: "Local IP")
- self.localIPValue = ValueField(string: "No connection")
- localIP.addView(localIPLabel, in: .center)
- localIP.addView(self.localIPValue, in: .center)
-
- let network: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*1, width: TabWidth - 20, height: stackHeight))
- network.orientation = .horizontal
- network.distribution = .equalCentering
- let networkLabel = LabelField(string: "Network")
- self.networkValue = ValueField(string: "No connection")
- network.addView(networkLabel, in: .center)
- network.addView(self.networkValue, in: .center)
-
- let physical: NSStackView = NSStackView(frame: NSRect(x: 10, y: 0, width: TabWidth - 20, height: stackHeight))
- physical.orientation = .horizontal
- physical.distribution = .equalCentering
- let physicalLabel = LabelField(string: "Physical address")
- self.physicalValue = ValueField(string: "No connection")
- physical.addView(physicalLabel, in: .center)
- physical.addView(self.physicalValue, in: .center)
-
- vertical.addSubview(publicIP)
- vertical.addSubview(localIP)
- vertical.addSubview(network)
- vertical.addSubview(physical)
-
- self.popup.view.view?.addSubview(vertical)
- }
-
- public func overviewUpdater(value: NetworkInterface) {
- if !self.popup.active && self.popup.initialized && !value.force { return }
- self.popup.initialized = true
-
- if !value.active {
- self.clearOverview()
- return
- }
-
- if let publicIP = value.publicIP {
-// if value.countryCode != nil {
-// publicIP = "\(publicIP) (\(value.countryCode!))"
-// }
- self.publicIPValue.stringValue = publicIP
- }
- if let localIP = value.localIP {
- self.localIPValue.stringValue = localIP
- }
- if var networkType = value.networkType {
- if value.wifiName != nil {
- networkType = "\(value.wifiName!) (\(networkType))"
- }
- self.networkValue.stringValue = networkType
- }
- if let macAddress = value.macAddress {
- self.physicalValue.stringValue = macAddress.uppercased()
- }
- }
-
- private func clearOverview() {
- self.publicIPValue.stringValue = "No connection"
- self.localIPValue.stringValue = "No connection"
- self.networkValue.stringValue = "No connection"
- self.physicalValue.stringValue = "No connection"
- }
-
- private func makeDataOverview() {
- let label: NSView = NSView(frame: NSRect(x: 0, y: 95, width: TabWidth, height: 25))
-
- label.wantsLayer = true
- label.layer?.backgroundColor = NSColor(hexString: "#eeeeee", alpha: 0.5).cgColor
-
- let text: NSTextField = NSTextField(string: "Data overview")
- 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.popup.view.view?.addSubview(label)
-
- let stackHeight: CGFloat = 22
- let vertical: NSStackView = NSStackView(frame: NSRect(x: 0, y: 4, width: TabWidth, height: stackHeight*4))
- vertical.orientation = .vertical
-
- let upload: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*3, width: TabWidth - 20, height: stackHeight))
- upload.orientation = .horizontal
- upload.distribution = .equalCentering
- let uploadLabel = LabelField(string: "Upload")
- self.uploadValue = ValueField(string: "0 KB/s")
- upload.addView(uploadLabel, in: .center)
- upload.addView(self.uploadValue, in: .center)
-
- let download: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*2, width: TabWidth - 20, height: stackHeight))
- download.orientation = .horizontal
- download.distribution = .equalCentering
- let downloadLabel = LabelField(string: "Download")
- self.downloadValue = ValueField(string: "0 KB/s")
- download.addView(downloadLabel, in: .center)
- download.addView(self.downloadValue, in: .center)
-
- let totalUpload: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*1, width: TabWidth - 20, height: stackHeight))
- totalUpload.orientation = .horizontal
- totalUpload.distribution = .equalCentering
- let totalUploadLabel = LabelField(string: "Total upload")
- self.totalUploadValue = ValueField(string: "0 KB")
- totalUpload.addView(totalUploadLabel, in: .center)
- totalUpload.addView(self.totalUploadValue, in: .center)
-
- let totalDownload: NSStackView = NSStackView(frame: NSRect(x: 10, y: 0, width: TabWidth - 20, height: stackHeight))
- totalDownload.orientation = .horizontal
- totalDownload.distribution = .equalCentering
- let totalDownloadLabel = LabelField(string: "Total download")
- self.totalDownloadValue = ValueField(string: "0 KB")
- totalDownload.addView(totalDownloadLabel, in: .center)
- totalDownload.addView(self.totalDownloadValue, in: .center)
-
- vertical.addSubview(upload)
- vertical.addSubview(download)
- vertical.addSubview(totalUpload)
- vertical.addSubview(totalDownload)
-
- self.popup.view.view?.addSubview(vertical)
- }
-
- public func dataUpdater(value: NetworkUsage) {
- if !self.popup.active && self.popup.initialized { return }
-
- self.downloadValue.stringValue = Units(bytes: value.download).getReadableSpeed()
- self.uploadValue.stringValue = Units(bytes: value.upload).getReadableSpeed()
-
- self.totalDownloadValue.stringValue = Units(bytes: value.totalDownload).getReadableMemory()
- self.totalUploadValue.stringValue = Units(bytes: value.totalUpload).getReadableMemory()
- }
-}
diff --git a/Stats/Modules/Network/NetworkReader.swift b/Stats/Modules/Network/NetworkReader.swift
deleted file mode 100644
index b65497fa..00000000
--- a/Stats/Modules/Network/NetworkReader.swift
+++ /dev/null
@@ -1,99 +0,0 @@
-//
-// NetworkReader.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 14/01/2020.
-// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-
-struct NetworkUsage {
- var download: Int64 = 0
- var upload: Int64 = 0
-
- var totalDownload: Int64 = 0
- var totalUpload: Int64 = 0
-}
-
-class NetworkReader: Reader {
- public var name: String = "Network"
- public var enabled: Bool = true
- public var available: Bool = true
- public var optional: Bool = false
- public var initialized: Bool = false
- public var callback: (NetworkUsage) -> Void = {_ in}
-
- private var uploadValue: Int64 = 0
- private var downloadValue: Int64 = 0
-
- init(_ updater: @escaping (NetworkUsage) -> Void) {
- self.callback = updater
-
- if self.available {
- DispatchQueue.global(qos: .default).async {
- self.read()
- }
- }
- }
-
- public func read() {
- if !self.enabled && self.initialized { return }
- self.initialized = true
-
- var interfaceAddresses: UnsafeMutablePointer? = nil
-
- var upload: Int64 = 0
- var download: Int64 = 0
- guard getifaddrs(&interfaceAddresses) == 0 else { return }
-
- var pointer = interfaceAddresses
- while pointer != nil {
- guard let info = getDataUsageInfo(from: pointer!) else {
- pointer = pointer!.pointee.ifa_next
- continue
- }
- pointer = pointer!.pointee.ifa_next
- upload += info[0]
- download += info[1]
- }
- freeifaddrs(interfaceAddresses)
-
- let lastUpload = self.uploadValue
- let lastDownload = self.downloadValue
-
- if lastUpload != 0 && lastDownload != 0 {
- DispatchQueue.main.async(execute: {
- self.callback(NetworkUsage(
- download: download - lastDownload,
- upload: upload - lastUpload,
- totalDownload: download,
- totalUpload: upload
- ))
- })
- }
-
- self.uploadValue = upload
- self.downloadValue = download
- }
-
- public func toggleEnable(_ value: Bool) {
- self.enabled = value
- }
-
- private func getDataUsageInfo(from infoPointer: UnsafeMutablePointer) -> [Int64]? {
- let pointer = infoPointer
-
- let name: String! = String(cString: infoPointer.pointee.ifa_name)
- let addr = pointer.pointee.ifa_addr.pointee
- guard addr.sa_family == UInt8(AF_LINK) else { return nil }
- var networkData: UnsafeMutablePointer? = nil
-
- if name.hasPrefix("en") {
- networkData = unsafeBitCast(pointer.pointee.ifa_data, to: UnsafeMutablePointer.self)
- return [Int64(networkData?.pointee.ifi_obytes ?? 0), Int64(networkData?.pointee.ifi_ibytes ?? 0)] // upload, download
- }
-
- return nil
- }
-}
diff --git a/Stats/Modules/RAM/RAM.swift b/Stats/Modules/RAM/RAM.swift
deleted file mode 100644
index 889c87ae..00000000
--- a/Stats/Modules/RAM/RAM.swift
+++ /dev/null
@@ -1,82 +0,0 @@
-//
-// RAM.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 14/01/2020.
-// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-import Charts
-import Repeat
-
-class RAM: Module {
- public var name: String = "RAM"
- public var updateInterval: Double = 1
-
- public var enabled: Bool = true
- public var available: Bool = true
-
- public var readers: [Reader] = []
- public var task: Repeater?
-
- public var widget: ModuleWidget = ModuleWidget()
- public var popup: ModulePopup = ModulePopup(true)
- public var menu: NSMenuItem = NSMenuItem()
-
- internal let defaults = UserDefaults.standard
- internal var submenu: NSMenu = NSMenu()
-
- internal var totalValue: NSTextField = NSTextField()
- internal var usedValue: NSTextField = NSTextField()
- internal var freeValue: NSTextField = NSTextField()
- internal var processViewList: [NSStackView] = []
- internal var chart: LineChartView = LineChartView()
-
- init() {
- if !self.available { return }
-
- self.enabled = defaults.object(forKey: name) != nil ? defaults.bool(forKey: name) : true
- self.updateInterval = defaults.object(forKey: "\(name)_interval") != nil ? defaults.double(forKey: "\(name)_interval") : self.updateInterval
- self.widget.type = defaults.object(forKey: "\(name)_widget") != nil ? defaults.float(forKey: "\(name)_widget") : Widgets.Mini
-
- readers.append(RAMUsageReader(self.usageUpdater))
- readers.append(RAMProcessReader(self.processesUpdater))
-
- self.initWidget()
- self.initMenu()
- self.initPopup()
-
- self.task = Repeater.init(interval: .seconds(self.updateInterval), observer: { _ in
- self.readers.forEach { reader in
- reader.read()
- }
- })
- }
-
- public func start() {
- if self.task != nil && self.task!.state.isRunning == false {
- self.task!.start()
- }
- }
-
- public func stop() {
- if self.task!.state.isRunning {
- self.task?.pause()
- }
- }
-
- public func restart() {
- self.stop()
- self.start()
- }
-
- private func usageUpdater(value: RAMUsage) {
- self.chartUpdater(value: value)
- self.overviewUpdater(value: value)
-
- if self.widget.view is Widget {
- (self.widget.view as! Widget).setValue(data: [value.usage])
- }
- }
-}
diff --git a/Stats/Modules/RAM/RAMMenu.swift b/Stats/Modules/RAM/RAMMenu.swift
deleted file mode 100644
index 6e94c02f..00000000
--- a/Stats/Modules/RAM/RAMMenu.swift
+++ /dev/null
@@ -1,176 +0,0 @@
-//
-// RAMMenu.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 14/01/2020.
-// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-
-extension RAM {
- public 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.widget.type == Widgets.Mini ? NSControl.StateValue.on : NSControl.StateValue.off
- mini.target = self
-
- let chart = NSMenuItem(title: "Chart", action: #selector(toggleWidget), keyEquivalent: "")
- chart.state = self.widget.type == 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.widget.type == Widgets.ChartWithValue ? NSControl.StateValue.on : NSControl.StateValue.off
- chartWithValue.target = self
-
- let barChart = NSMenuItem(title: "Bar chart", action: #selector(toggleWidget), keyEquivalent: "")
- barChart.state = self.widget.type == Widgets.BarChart ? NSControl.StateValue.on : NSControl.StateValue.off
- barChart.target = self
-
- submenu.addItem(mini)
- submenu.addItem(chart)
- submenu.addItem(chartWithValue)
- submenu.addItem(barChart)
-
- submenu.addItem(NSMenuItem.separator())
-
- if let view = self.widget.view as? Widget {
- for widgetMenu in view.menus {
- submenu.addItem(widgetMenu)
- }
- }
-
- submenu.addItem(NSMenuItem.separator())
- submenu.addItem(generateIntervalMenu())
-
- if self.enabled {
- menu.submenu = submenu
- }
- }
-
- @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.enabled = state
- menuBar!.reload(name: self.name)
-
- if !state {
- menu.submenu = nil
- } else {
- menu.submenu = submenu
- }
-
- self.restart()
- }
-
- @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
- case "Bar chart":
- widgetCode = Widgets.BarChart
- default:
- break
- }
-
- if self.widget.type == widgetCode {
- return
- }
-
- for item in self.submenu.items {
- if item.title == "Mini" || item.title == "Chart" || item.title == "Chart with value" || item.title == "Bar chart" {
- 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.widget.type = widgetCode
- self.initWidget()
- self.initMenu()
- menuBar!.reload(name: self.name)
- }
-
- func generateIntervalMenu() -> NSMenuItem {
- let updateInterval = NSMenuItem(title: "Update interval", action: nil, keyEquivalent: "")
-
- let updateIntervals = NSMenu()
- let updateInterval_1 = NSMenuItem(title: "1s", action: #selector(changeInterval), keyEquivalent: "")
- updateInterval_1.state = self.updateInterval == 1 ? NSControl.StateValue.on : NSControl.StateValue.off
- updateInterval_1.target = self
- let updateInterval_2 = NSMenuItem(title: "3s", action: #selector(changeInterval), keyEquivalent: "")
- updateInterval_2.state = self.updateInterval == 3 ? NSControl.StateValue.on : NSControl.StateValue.off
- updateInterval_2.target = self
- let updateInterval_3 = NSMenuItem(title: "5s", action: #selector(changeInterval), keyEquivalent: "")
- updateInterval_3.state = self.updateInterval == 5 ? NSControl.StateValue.on : NSControl.StateValue.off
- updateInterval_3.target = self
- let updateInterval_4 = NSMenuItem(title: "10s", action: #selector(changeInterval), keyEquivalent: "")
- updateInterval_4.state = self.updateInterval == 10 ? NSControl.StateValue.on : NSControl.StateValue.off
- updateInterval_4.target = self
- let updateInterval_5 = NSMenuItem(title: "15s", action: #selector(changeInterval), keyEquivalent: "")
- updateInterval_5.state = self.updateInterval == 15 ? NSControl.StateValue.on : NSControl.StateValue.off
- updateInterval_5.target = self
-
- updateIntervals.addItem(updateInterval_1)
- updateIntervals.addItem(updateInterval_2)
- updateIntervals.addItem(updateInterval_3)
- updateIntervals.addItem(updateInterval_4)
- updateIntervals.addItem(updateInterval_5)
-
- updateInterval.submenu = updateIntervals
-
- return updateInterval
- }
-
- @objc func changeInterval(_ sender: NSMenuItem) {
- var interval: Double = self.updateInterval
-
- switch sender.title {
- case "1s":
- interval = 1
- case "3s":
- interval = 3
- case "5s":
- interval = 5
- case "10s":
- interval = 10
- case "15s":
- interval = 15
- default:
- break
- }
-
- if interval == self.updateInterval {
- return
- }
-
- for item in self.submenu.items {
- if item.title == "Update interval" {
- for subitem in item.submenu!.items {
- subitem.state = NSControl.StateValue.off
- }
- }
- }
-
- sender.state = NSControl.StateValue.on
- self.updateInterval = interval
- self.defaults.set(interval, forKey: "\(name)_interval")
- self.task?.reset(.seconds(interval), restart: self.task!.state.isRunning)
- }
-}
diff --git a/Stats/Modules/RAM/RAMPopup.swift b/Stats/Modules/RAM/RAMPopup.swift
deleted file mode 100644
index 70841765..00000000
--- a/Stats/Modules/RAM/RAMPopup.swift
+++ /dev/null
@@ -1,237 +0,0 @@
-//
-// RAMPopup.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 14/01/2020.
-// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-import Charts
-
-extension RAM {
- public func initPopup() {
- self.popup.view.view?.frame = NSRect(x: 0, y: 0, width: TabWidth, height: TabHeight)
-
- makeChart()
- makeOverview()
- makeProcesses()
- }
-
- private func makeChart() {
- 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.autoScaleMinMaxEnabled = true
-
- self.chart.rightAxis.enabled = false
-
- let v = self.readers.filter{ $0 is RAMUsageReader }.first as! RAMUsageReader
- self.chart.leftAxis.axisMinimum = 0
- self.chart.leftAxis.axisMaximum = Units(bytes: Int64(v.totalSize)).gigabytes
- self.chart.leftAxis.labelCount = Units(bytes: Int64(v.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
-
- var lineChartEntry = [ChartDataEntry]()
- lineChartEntry.append(ChartDataEntry(x: 0, y: 0))
- 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.popup.view.view?.addSubview(self.chart)
- }
-
- public func chartUpdater(value: RAMUsage) {
- if self.chart.data == nil { return }
-
- let index = Double((self.chart.data?.getDataSetByIndex(0)?.entryCount)!)
- let usage = Units(bytes: Int64(value.used)).getReadableTuple().0
- self.chart.data?.addEntry(ChartDataEntry(x: index, y: usage), dataSetIndex: 0)
-
- if index > 120 {
- self.chart.xAxis.axisMinimum = index - 120
- }
- self.chart.xAxis.axisMaximum = index
-
- if self.popup.active {
- self.chart.notifyDataSetChanged()
- self.chart.moveViewToX(index)
- }
- }
-
- private 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.popup.view.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")
- self.totalValue = ValueField(string: "0 GB")
- total.addView(totalLabel, in: .center)
- total.addView(self.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")
- self.usedValue = ValueField(string: "0 GB")
- used.addView(usedLabel, in: .center)
- used.addView(self.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")
- self.freeValue = ValueField(string: "0 GB")
- free.addView(freeLabel, in: .center)
- free.addView(self.freeValue, in: .center)
-
- vertical.addSubview(total)
- vertical.addSubview(used)
- vertical.addSubview(free)
-
- self.popup.view.view?.addSubview(vertical)
- }
-
- public func overviewUpdater(value: RAMUsage) {
- if !self.popup.active && self.popup.initialized { return }
-
- self.totalValue.stringValue = Units(bytes: Int64(value.total)).getReadableMemory()
- self.usedValue.stringValue = Units(bytes: Int64(value.used)).getReadableMemory()
- self.freeValue.stringValue = Units(bytes: Int64(value.free)).getReadableMemory()
- }
-
- private 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.popup.view.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
-
- self.processViewList = []
- 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: "")
-
- self.processViewList.append(process_1)
- self.processViewList.append(process_2)
- self.processViewList.append(process_3)
- self.processViewList.append(process_4)
- self.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.popup.view.view?.addSubview(vertical)
-
- label.frame = NSRect(x: 0, y: vertical.frame.origin.y + vertical.frame.size.height + 2, width: TabWidth, height: 25)
- self.popup.view.view?.addSubview(label)
- }
-
- public func processesUpdater(value: [TopProcess]) {
- if self.processViewList.isEmpty || !self.popup.active && self.popup.initialized { return }
- self.popup.initialized = true
-
- for (i, process) in value.enumerated() {
- if i < 5 {
- let processView = self.processViewList[i]
-
- (processView.subviews[0] as! NSTextField).stringValue = process.command
- (processView.subviews[1] as! NSTextField).stringValue = Units(bytes: Int64(process.usage)).getReadableMemory()
- }
- }
- }
-
- private 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
- }
-}
diff --git a/Stats/Modules/RAM/RAMProcessReader.swift b/Stats/Modules/RAM/RAMProcessReader.swift
deleted file mode 100644
index ee518fdb..00000000
--- a/Stats/Modules/RAM/RAMProcessReader.swift
+++ /dev/null
@@ -1,87 +0,0 @@
-//
-// RAMProcessReader.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 14/01/2020.
-// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-
-class RAMProcessReader: Reader {
- public var name: String = "Process"
- public var enabled: Bool = false
- public var available: Bool = true
- public var optional: Bool = true
- public var initialized: Bool = false
- public var callback: ([TopProcess]) -> Void = {_ in}
-
- init(_ updater: @escaping ([TopProcess]) -> Void) {
- self.callback = updater
-
- if self.available {
- DispatchQueue.global(qos: .default).async {
- self.read()
- }
- }
- }
-
- func read() {
- if !self.enabled && self.initialized { return }
- self.initialized = true
-
- let task = Process()
- task.launchPath = "/usr/bin/top"
- task.arguments = ["-l", "1", "-o", "mem", "-n", "5", "-stats", "pid,command,mem"]
-
- let outputPipe = Pipe()
- let errorPipe = Pipe()
-
- task.standardOutput = outputPipe
- task.standardError = errorPipe
-
- do {
- try task.run()
- } catch let error {
- print(error)
- return
- }
-
- let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile()
- let errorData = errorPipe.fileHandleForReading.readDataToEndOfFile()
- let output = String(decoding: outputData, as: UTF8.self)
- _ = String(decoding: errorData, as: UTF8.self)
-
- if output.isEmpty {
- return
- }
-
- var processes: [TopProcess] = []
- output.enumerateLines { (line, stop) -> () in
- if line.matches("^\\d+ + .+ +\\d+.\\d[M\\+\\-]+ *$") {
- var str = line.trimmingCharacters(in: .whitespaces)
- let pidString = str.findAndCrop(pattern: "^\\d+")
- let usageString = str.findAndCrop(pattern: " [0-9]+M(\\+|\\-)*$")
- var command = str.trimmingCharacters(in: .whitespaces)
-
- if let regex = try? NSRegularExpression(pattern: " (\\+|\\-)*$", options: .caseInsensitive) {
- command = regex.stringByReplacingMatches(in: command, options: [], range: NSRange(location: 0, length: command.count), withTemplate: "")
- }
-
- let pid = Int(pidString) ?? 0
- guard let usage = Double(usageString.filter("01234567890.".contains)) else {
- return
- }
- let process = TopProcess(pid: pid, command: command, usage: usage * Double(1024 * 1024))
- processes.append(process)
- }
- }
- DispatchQueue.main.async(execute: {
- self.callback(processes)
- })
- }
-
- func toggleEnable(_ value: Bool) {
- self.enabled = value
- }
-}
diff --git a/Stats/Modules/RAM/RAMUsageReader.swift b/Stats/Modules/RAM/RAMUsageReader.swift
deleted file mode 100644
index ed3a5e61..00000000
--- a/Stats/Modules/RAM/RAMUsageReader.swift
+++ /dev/null
@@ -1,90 +0,0 @@
-//
-// RAMUsageReader.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 14/01/2020.
-// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-
-struct RAMUsage {
- var usage: Double = 0
- var total: Double = 0
- var used: Double = 0
- var free: Double = 0
-}
-
-class RAMUsageReader: Reader {
- public var name: String = "Usage"
- public var enabled: Bool = true
- public var available: Bool = true
- public var optional: Bool = false
- public var initialized: Bool = false
- public var callback: (RAMUsage) -> Void = {_ in}
-
- public var totalSize: Float = 0
- public var usage: RAMUsage = RAMUsage()
-
- init(_ updater: @escaping (RAMUsage) -> Void) {
- self.callback = updater
-
- var stats = host_basic_info()
- var count = UInt32(MemoryLayout.size / MemoryLayout.size)
-
- 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)
- }
- }
-
- if kerr == KERN_SUCCESS {
- self.totalSize = Float(stats.max_mem)
- }
- else {
- self.totalSize = 0
- print("Error with host_info(): " + (String(cString: mach_error_string(kerr), encoding: String.Encoding.ascii) ?? "unknown error"))
- }
-
- if self.available {
- DispatchQueue.global(qos: .default).async {
- self.read()
- }
- }
- }
-
- func read() {
- if !self.enabled && self.initialized { return }
- self.initialized = true
-
- var stats = vm_statistics64()
- var count = UInt32(MemoryLayout.size / MemoryLayout.size)
-
- let kerr: kern_return_t = withUnsafeMutablePointer(to: &stats) {
- $0.withMemoryRebound(to: integer_t.self, capacity: 1) {
- host_statistics64(mach_host_self(), HOST_VM_INFO64, $0, &count)
- }
- }
-
- if kerr == KERN_SUCCESS {
- let active = Float(stats.active_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 used = active + wired + compressed
- let free = totalSize - used
-
- DispatchQueue.main.async(execute: {
- self.usage = RAMUsage(usage: Double((self.totalSize - free) / self.totalSize), total: Double(self.totalSize), used: Double(used), free: Double(free))
- self.callback(self.usage)
- })
- } else {
- print("Error with host_statistics64(): " + (String(cString: mach_error_string(kerr), encoding: String.Encoding.ascii) ?? "unknown error"))
- }
- }
-
- func toggleEnable(_ value: Bool) {
- self.enabled = value
- }
-}
diff --git a/Stats/Modules/Sensors/Sensors.swift b/Stats/Modules/Sensors/Sensors.swift
deleted file mode 100644
index 2ae61e08..00000000
--- a/Stats/Modules/Sensors/Sensors.swift
+++ /dev/null
@@ -1,102 +0,0 @@
-//
-// Sensors.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 03/04/2020.
-// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-import Repeat
-
-class Sensors: Module {
- public var name: String = "Sensors"
- public var updateInterval: Double = 1
-
- public var enabled: Bool = true
- public var available: Bool = true
-
- public var widget: ModuleWidget = ModuleWidget()
- public var popup: ModulePopup = ModulePopup(false)
- public var menu: NSMenuItem = NSMenuItem()
-
- public var readers: [Reader] = []
- public var task: Repeater?
-
- internal let defaults = UserDefaults.standard
- internal var submenu: NSMenu = NSMenu()
-
- internal var value_1: String = "TC0P"
- internal var value_2: String = "TG0D"
- internal var once: Int = 0
-
- internal var sensors: Sensors_t = Sensors_t()
-
- init() {
- if !self.available { return }
-
- self.enabled = defaults.object(forKey: name) != nil ? defaults.bool(forKey: name) : true
- self.widget.type = defaults.object(forKey: "\(name)_widget") != nil ? defaults.float(forKey: "\(name)_widget") : Widgets.Sensors
- self.value_1 = (defaults.object(forKey: "\(name)_value_1") != nil ? defaults.string(forKey: "\(name)_value_1")! : value_1)
- self.value_2 = (defaults.object(forKey: "\(name)_value_2") != nil ? defaults.string(forKey: "\(name)_value_2")! : value_2)
-
- self.initWidget()
- self.initMenu()
-
- if self.enabled {
- self.update()
- }
-
- self.task = Repeater.init(interval: .seconds(self.updateInterval), observer: { _ in
- if self.enabled {
- self.update()
- }
- })
- }
-
- public func start() {
- if self.task != nil && self.task!.state.isRunning == false {
- self.task!.start()
- }
- }
-
- public func stop() {
- if self.task!.state.isRunning {
- self.task?.pause()
- }
- }
-
- public func restart() {
- self.stop()
- self.start()
- }
-
- private func update() {
- var value_1_unit: Double = 0
- var value_1_value: Double = 0
- var value_2_unit: Double = 0
- var value_2_value: Double = 0
-
- var sensor_1: Sensor_t? = self.sensors.find(byKey: self.value_1)
- var sensor_2: Sensor_t? = self.sensors.find(byKey: self.value_2)
-
- if sensor_1 != nil {
- sensor_1!.update()
- if sensor_1!.value != nil {
- value_1_value = sensor_1!.value!
- value_1_unit = Double(sensor_1!.unit[0].unicodeScalarCodePoint())
- }
- }
- if sensor_2 != nil {
- sensor_2!.update()
- if sensor_2!.value != nil {
- value_2_value = sensor_2!.value!
- value_2_unit = Double(sensor_2!.unit[0].unicodeScalarCodePoint())
- }
- }
-
- DispatchQueue.main.async(execute: {
- (self.widget.view as! Widget).setValue(data: [value_1_value, value_1_unit, value_2_value, value_2_unit])
- })
- }
-}
diff --git a/Stats/Modules/Sensors/SensorsMenu.swift b/Stats/Modules/Sensors/SensorsMenu.swift
deleted file mode 100644
index d7910247..00000000
--- a/Stats/Modules/Sensors/SensorsMenu.swift
+++ /dev/null
@@ -1,153 +0,0 @@
-//
-// SensorsMenu.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 03/04/2020.
-// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-
-extension Sensors {
- public 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 sensor_1: NSMenuItem = NSMenuItem(title: "Sensor #1", action: nil, keyEquivalent: "")
- sensor_1.target = self
- sensor_1.submenu = NSMenu()
- addSensorsMennu(sensor_1.submenu!, value: self.value_1, action: #selector(toggleValue1))
-
- let sensor_2: NSMenuItem = NSMenuItem(title: "Sensor #2", action: nil, keyEquivalent: "")
- sensor_2.target = self
- sensor_2.submenu = NSMenu()
- addSensorsMennu(sensor_2.submenu!, value: self.value_2, action: #selector(toggleValue2))
-
- submenu.addItem(sensor_1)
- submenu.addItem(sensor_2)
-
- submenu.addItem(NSMenuItem.separator())
-
- if let view = self.widget.view as? Widget {
- for widgetMenu in view.menus {
- submenu.addItem(widgetMenu)
- }
- }
-
- if self.enabled {
- menu.submenu = submenu
- }
- }
-
- private func addSensorsMennu(_ menu: NSMenu, value: String, action: Selector?) {
- var sensorsMenu: NSMenuItem? = generateSensorsMenu(type: SensorType.Temperature, value: value, action: action)
- if sensorsMenu != nil {
- menu.addItem(sensorsMenu!)
- }
- sensorsMenu = generateSensorsMenu(type: SensorType.Voltage, value: value, action: action)
- if sensorsMenu != nil {
- menu.addItem(sensorsMenu!)
- }
- sensorsMenu = generateSensorsMenu(type: SensorType.Power, value: value, action: action)
- if sensorsMenu != nil {
- menu.addItem(sensorsMenu!)
- }
- }
-
- private func generateSensorsMenu(type: SensorType, value: String, action: Selector?) -> NSMenuItem? {
- let list: [Sensor_t] = self.sensors.list.filter{ $0.type == type.rawValue }
- if list.isEmpty {
- return nil
- }
-
- let mainItem: NSMenuItem = NSMenuItem(title: type.rawValue, action: nil, keyEquivalent: "")
- mainItem.target = self
- mainItem.submenu = NSMenu()
-
- var groups: [SensorGroup_t] = []
- list.forEach { (s: Sensor_t) in
- if !groups.contains(s.group) {
- groups.append(s.group)
- }
- }
- groups.sort()
-
- groups.forEach { (g: SensorGroup_t) in
- mainItem.submenu!.addItem(NSMenuItem(title: g, action: nil, keyEquivalent: ""))
-
- list.filter{ $0.group == g }.forEach { (s: Sensor_t) in
- let menuPoint: NSMenuItem = NSMenuItem(title: s.name, action: action, keyEquivalent: "")
- menuPoint.state = s.key == value ? NSControl.StateValue.on : NSControl.StateValue.off
- menuPoint.target = self
- menuPoint.extraString = s.key
-
- mainItem.submenu!.addItem(menuPoint)
- }
-
- mainItem.submenu!.addItem(NSMenuItem.separator())
- }
-
- return mainItem
- }
-
- @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.enabled = state
- menuBar!.reload(name: self.name)
-
- if !state {
- menu.submenu = nil
- } else {
- menu.submenu = submenu
- }
-
- self.restart()
- }
-
- @objc func toggleValue1(_ sender: NSMenuItem) {
- let val: String = sender.extraString
- if self.value_1 == val {
- return
- }
-
- let state = sender.state == NSControl.StateValue.on
- for item in self.submenu.items {
- item.state = NSControl.StateValue.off
- }
-
- sender.state = state ? NSControl.StateValue.off : NSControl.StateValue.on
- self.defaults.set(val, forKey: "\(name)_value_1")
- self.value_1 = val
- self.initWidget()
- self.initMenu()
- menuBar!.reload(name: self.name)
- }
-
- @objc func toggleValue2(_ sender: NSMenuItem) {
- let val: String = sender.extraString
- if self.value_2 == val {
- return
- }
-
- let state = sender.state == NSControl.StateValue.on
- for item in self.submenu.items {
- item.state = NSControl.StateValue.off
- }
-
- sender.state = state ? NSControl.StateValue.off : NSControl.StateValue.on
- self.defaults.set(val, forKey: "\(name)_value_2")
- self.value_2 = val
- self.initWidget()
- self.initMenu()
- menuBar!.reload(name: self.name)
- }
-}
diff --git a/Stats/Modules/Sensors/SensorsType.swift b/Stats/Modules/Sensors/SensorsType.swift
deleted file mode 100644
index 47926d88..00000000
--- a/Stats/Modules/Sensors/SensorsType.swift
+++ /dev/null
@@ -1,183 +0,0 @@
-//
-// SensorsType.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 06/04/2020.
-// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-typealias SensorGroup_t = String
-enum SensorGroup: SensorGroup_t {
- case CPU = "CPU"
- case GPU = "GPU"
- case System = "Systems"
- case Sensor = "Sensors"
-}
-
-typealias SensorType_t = String
-enum SensorType: SensorType_t {
- case Temperature = "Temperature"
- case Voltage = "Voltage"
- case Power = "Power"
- case Frequency = "Frequency"
- case Battery = "Battery"
-}
-
-struct Sensor_t {
- var name: String
- var key: String = ""
-
- var group: SensorGroup_t
- var type: SensorType_t
- var unit: String {
- get {
- switch self.type{
- case SensorType.Temperature.rawValue:
- return "°"
- case SensorType.Voltage.rawValue:
- return "V"
- case SensorType.Power.rawValue:
- return "W"
- default: return ""
- }
- }
- }
-
- var value: Double? = nil
-
- public mutating func update() {
- self.value = smc.getValue(self.key)
- }
-}
-
-struct Sensors_t {
- var list: [Sensor_t] = []
-
- init() {
- var available: [String] = smc.getAllKeys()
- var sensor: Sensor_t? = nil
-
- available = available.filter({ (key: String) -> Bool in
- switch key.prefix(1) {
- case "T", "V", "P": return SensorsDict[key] != nil
- default: return false
- }
- })
-
- available.forEach { (key: String) in
- sensor = SensorsDict[key]
- if sensor != nil {
- sensor!.value = smc.getValue(key)
- if sensor!.value != nil {
- sensor!.key = key
- self.list.append(sensor!)
- }
- }
- }
- }
-
- public func find(byKey key: String) -> Sensor_t? {
- return self.list.first{ $0.key == key}
- }
-}
-
-// List of keys: https://github.com/acidanthera/VirtualSMC/blob/master/Docs/SMCSensorKeys.txt
-let SensorsDict: [String: Sensor_t] = [
- /// Temperature
- "TA0P": Sensor_t(name: "Ambient 1", group: SensorGroup.Sensor.rawValue, type: SensorType.Temperature.rawValue),
- "TA1P": Sensor_t(name: "Ambient 2", group: SensorGroup.Sensor.rawValue, type: SensorType.Temperature.rawValue),
- "Th0H": Sensor_t(name: "Heatpipe 1", group: SensorGroup.Sensor.rawValue, type: SensorType.Temperature.rawValue),
- "Th1H": Sensor_t(name: "Heatpipe 2", group: SensorGroup.Sensor.rawValue, type: SensorType.Temperature.rawValue),
- "Th2H": Sensor_t(name: "Heatpipe 3", group: SensorGroup.Sensor.rawValue, type: SensorType.Temperature.rawValue),
- "Th3H": Sensor_t(name: "Heatpipe 4", group: SensorGroup.Sensor.rawValue, type: SensorType.Temperature.rawValue),
- "TZ0C": Sensor_t(name: "Termal zone 1", group: SensorGroup.Sensor.rawValue, type: SensorType.Temperature.rawValue),
- "TZ1C": Sensor_t(name: "Termal zone 2", group: SensorGroup.Sensor.rawValue, type: SensorType.Temperature.rawValue),
-
- "TC0F": Sensor_t(name: "CPU die", group: SensorGroup.CPU.rawValue, type: SensorType.Temperature.rawValue),
- "TC0H": Sensor_t(name: "CPU heatsink", group: SensorGroup.CPU.rawValue, type: SensorType.Temperature.rawValue),
- "TC0P": Sensor_t(name: "CPU proximity", group: SensorGroup.CPU.rawValue, type: SensorType.Temperature.rawValue),
- "TC1C": Sensor_t(name: "CPU core 1", group: SensorGroup.CPU.rawValue, type: SensorType.Temperature.rawValue),
- "TC2C": Sensor_t(name: "CPU core 2", group: SensorGroup.CPU.rawValue, type: SensorType.Temperature.rawValue),
- "TC3C": Sensor_t(name: "CPU core 3", group: SensorGroup.CPU.rawValue, type: SensorType.Temperature.rawValue),
- "TC4C": Sensor_t(name: "CPU core 4", group: SensorGroup.CPU.rawValue, type: SensorType.Temperature.rawValue),
- "TC5C": Sensor_t(name: "CPU core 5", group: SensorGroup.CPU.rawValue, type: SensorType.Temperature.rawValue),
- "TC6C": Sensor_t(name: "CPU core 6", group: SensorGroup.CPU.rawValue, type: SensorType.Temperature.rawValue),
- "TC7C": Sensor_t(name: "CPU core 7", group: SensorGroup.CPU.rawValue, type: SensorType.Temperature.rawValue),
- "TC8C": Sensor_t(name: "CPU core 8", group: SensorGroup.CPU.rawValue, type: SensorType.Temperature.rawValue),
-
- "TCGC": Sensor_t(name: "GPU Intel Graphics", group: SensorGroup.GPU.rawValue, type: SensorType.Temperature.rawValue),
- "TG0D": Sensor_t(name: "GPU die", group: SensorGroup.GPU.rawValue, type: SensorType.Temperature.rawValue),
- "TG0H": Sensor_t(name: "GPU heatsink", group: SensorGroup.GPU.rawValue, type: SensorType.Temperature.rawValue),
- "TG0P": Sensor_t(name: "GPU proximity", group: SensorGroup.GPU.rawValue, type: SensorType.Temperature.rawValue),
-
- "Tm0P": Sensor_t(name: "Mainboard", group: SensorGroup.System.rawValue, type: SensorType.Temperature.rawValue),
- "Tp0P": Sensor_t(name: "Powerboard", group: SensorGroup.System.rawValue, type: SensorType.Temperature.rawValue),
- "TB1T": Sensor_t(name: "Battery", group: SensorGroup.System.rawValue, type: SensorType.Temperature.rawValue),
- "TW0P": Sensor_t(name: "Airport", group: SensorGroup.System.rawValue, type: SensorType.Temperature.rawValue),
- "TL0P": Sensor_t(name: "Display", group: SensorGroup.System.rawValue, type: SensorType.Temperature.rawValue),
- "TI0P": Sensor_t(name: "Thunderbold 1", group: SensorGroup.System.rawValue, type: SensorType.Temperature.rawValue),
- "TI1P": Sensor_t(name: "Thunderbold 2", group: SensorGroup.System.rawValue, type: SensorType.Temperature.rawValue),
- "TI2P": Sensor_t(name: "Thunderbold 3", group: SensorGroup.System.rawValue, type: SensorType.Temperature.rawValue),
- "TI3P": Sensor_t(name: "Thunderbold 4", group: SensorGroup.System.rawValue, type: SensorType.Temperature.rawValue),
-
- "TN0D": Sensor_t(name: "Northbridge die", group: SensorGroup.System.rawValue, type: SensorType.Temperature.rawValue),
- "TN0H": Sensor_t(name: "Northbridge heatsink", group: SensorGroup.System.rawValue, type: SensorType.Temperature.rawValue),
- "TN0P": Sensor_t(name: "Northbridge proximity", group: SensorGroup.System.rawValue, type: SensorType.Temperature.rawValue),
-
- /// Voltage
- "VCAC": Sensor_t(name: "CPU IA", group: SensorGroup.CPU.rawValue, type: SensorType.Voltage.rawValue),
- "VCSC": Sensor_t(name: "CPU System Agent", group: SensorGroup.CPU.rawValue, type: SensorType.Voltage.rawValue),
- "VC0C": Sensor_t(name: "CPU Core 1", group: SensorGroup.CPU.rawValue, type: SensorType.Voltage.rawValue),
- "VC1C": Sensor_t(name: "CPU Core 2", group: SensorGroup.CPU.rawValue, type: SensorType.Voltage.rawValue),
- "VC2C": Sensor_t(name: "CPU Core 3", group: SensorGroup.CPU.rawValue, type: SensorType.Voltage.rawValue),
- "VC3C": Sensor_t(name: "CPU Core 4", group: SensorGroup.CPU.rawValue, type: SensorType.Voltage.rawValue),
- "VC4C": Sensor_t(name: "CPU Core 5", group: SensorGroup.CPU.rawValue, type: SensorType.Voltage.rawValue),
- "VC5C": Sensor_t(name: "CPU Core 6", group: SensorGroup.CPU.rawValue, type: SensorType.Voltage.rawValue),
- "VC6C": Sensor_t(name: "CPU Core 7", group: SensorGroup.CPU.rawValue, type: SensorType.Voltage.rawValue),
- "VC7C": Sensor_t(name: "CPU Core 8", group: SensorGroup.CPU.rawValue, type: SensorType.Voltage.rawValue),
-
- "VCTC": Sensor_t(name: "GPU Intel Graphics", group: SensorGroup.GPU.rawValue, type: SensorType.Voltage.rawValue),
- "VG0C": Sensor_t(name: "GPU", group: SensorGroup.GPU.rawValue, type: SensorType.Voltage.rawValue),
-
- "VM0R": Sensor_t(name: "Memory", group: SensorGroup.System.rawValue, type: SensorType.Voltage.rawValue),
- "Vb0R": Sensor_t(name: "CMOS", group: SensorGroup.System.rawValue, type: SensorType.Voltage.rawValue),
-
- "VD0R": Sensor_t(name: "DC In", group: SensorGroup.Sensor.rawValue, type: SensorType.Voltage.rawValue),
- "VP0R": Sensor_t(name: "12V rail", group: SensorGroup.Sensor.rawValue, type: SensorType.Voltage.rawValue),
- "Vp0C": Sensor_t(name: "12V vcc", group: SensorGroup.Sensor.rawValue, type: SensorType.Voltage.rawValue),
- "VV2S": Sensor_t(name: "3V", group: SensorGroup.Sensor.rawValue, type: SensorType.Voltage.rawValue),
- "VR3R": Sensor_t(name: "3.3V", group: SensorGroup.Sensor.rawValue, type: SensorType.Voltage.rawValue),
- "VV1S": Sensor_t(name: "5V", group: SensorGroup.Sensor.rawValue, type: SensorType.Voltage.rawValue),
- "VV9S": Sensor_t(name: "12V", group: SensorGroup.Sensor.rawValue, type: SensorType.Voltage.rawValue),
- "VeES": Sensor_t(name: "PCI 12V", group: SensorGroup.Sensor.rawValue, type: SensorType.Voltage.rawValue),
-
- /// Power
- "PCPC": Sensor_t(name: "CPU Package", group: SensorGroup.CPU.rawValue, type: SensorType.Power.rawValue),
- "PCPT": Sensor_t(name: "CPU Package total", group: SensorGroup.CPU.rawValue, type: SensorType.Power.rawValue),
- "PC0R": Sensor_t(name: "CPU Computing high side", group: SensorGroup.CPU.rawValue, type: SensorType.Power.rawValue),
-
- "PCPG": Sensor_t(name: "GPU Intel Graphics", group: SensorGroup.GPU.rawValue, type: SensorType.Power.rawValue),
- "PG0R": Sensor_t(name: "GPU", group: SensorGroup.GPU.rawValue, type: SensorType.Power.rawValue),
-
- "PPBR": Sensor_t(name: "Battery", group: SensorGroup.Sensor.rawValue, type: SensorType.Power.rawValue),
- "PDTR": Sensor_t(name: "DC In", group: SensorGroup.Sensor.rawValue, type: SensorType.Power.rawValue),
- "PSTR": Sensor_t(name: "System total", group: SensorGroup.Sensor.rawValue, type: SensorType.Power.rawValue),
-
- /// Frequency
- "FRC0": Sensor_t(name: "CPU 1", group: SensorGroup.CPU.rawValue, type: SensorType.Frequency.rawValue),
- "FRC1": Sensor_t(name: "CPU 2", group: SensorGroup.CPU.rawValue, type: SensorType.Frequency.rawValue),
- "FRC2": Sensor_t(name: "CPU 3", group: SensorGroup.CPU.rawValue, type: SensorType.Frequency.rawValue),
- "FRC3": Sensor_t(name: "CPU 4", group: SensorGroup.CPU.rawValue, type: SensorType.Frequency.rawValue),
- "FRC4": Sensor_t(name: "CPU 5", group: SensorGroup.CPU.rawValue, type: SensorType.Frequency.rawValue),
- "FRC5": Sensor_t(name: "CPU 6", group: SensorGroup.CPU.rawValue, type: SensorType.Frequency.rawValue),
- "FRC6": Sensor_t(name: "CPU 7", group: SensorGroup.CPU.rawValue, type: SensorType.Frequency.rawValue),
- "FRC7": Sensor_t(name: "CPU 8", group: SensorGroup.CPU.rawValue, type: SensorType.Frequency.rawValue),
-
- "CG0C": Sensor_t(name: "GPU", group: SensorGroup.GPU.rawValue, type: SensorType.Frequency.rawValue),
- "CG0S": Sensor_t(name: "GPU shader", group: SensorGroup.GPU.rawValue, type: SensorType.Frequency.rawValue),
- "CG0M": Sensor_t(name: "GPU memory", group: SensorGroup.GPU.rawValue, type: SensorType.Frequency.rawValue),
-
- /// Battery
- "B0AV": Sensor_t(name: "Voltage", group: SensorGroup.Sensor.rawValue, type: SensorType.Battery.rawValue),
- "B0AC": Sensor_t(name: "Amperage", group: SensorGroup.Sensor.rawValue, type: SensorType.Battery.rawValue),
-]
diff --git a/Stats/Supporting Files/About.storyboard b/Stats/Supporting Files/About.storyboard
deleted file mode 100644
index 077412f3..00000000
--- a/Stats/Supporting Files/About.storyboard
+++ /dev/null
@@ -1,183 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Stats/Supporting Files/Assets.xcassets/icons/baseline_build_black_18pt.imageset/Contents.json b/Stats/Supporting Files/Assets.xcassets/apps.imageset/Contents.json
similarity index 58%
rename from Stats/Supporting Files/Assets.xcassets/icons/baseline_build_black_18pt.imageset/Contents.json
rename to Stats/Supporting Files/Assets.xcassets/apps.imageset/Contents.json
index f9f14e7c..fcec68e6 100644
--- a/Stats/Supporting Files/Assets.xcassets/icons/baseline_build_black_18pt.imageset/Contents.json
+++ b/Stats/Supporting Files/Assets.xcassets/apps.imageset/Contents.json
@@ -1,26 +1,26 @@
{
"images" : [
{
+ "filename" : "baseline_apps_white_24pt_1x.png",
"idiom" : "universal",
- "filename" : "baseline_build_black_18pt_1x.png",
"scale" : "1x"
},
{
+ "filename" : "baseline_apps_white_24pt_2x.png",
"idiom" : "universal",
- "filename" : "baseline_build_black_18pt_2x.png",
"scale" : "2x"
},
{
+ "filename" : "baseline_apps_white_24pt_3x.png",
"idiom" : "universal",
- "filename" : "baseline_build_black_18pt_3x.png",
"scale" : "3x"
}
],
"info" : {
- "version" : 1,
- "author" : "xcode"
+ "author" : "xcode",
+ "version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
-}
\ No newline at end of file
+}
diff --git a/Stats/Supporting Files/Assets.xcassets/apps.imageset/baseline_apps_white_24pt_1x.png b/Stats/Supporting Files/Assets.xcassets/apps.imageset/baseline_apps_white_24pt_1x.png
new file mode 100644
index 00000000..a5644e8d
Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/apps.imageset/baseline_apps_white_24pt_1x.png differ
diff --git a/Stats/Supporting Files/Assets.xcassets/apps.imageset/baseline_apps_white_24pt_2x.png b/Stats/Supporting Files/Assets.xcassets/apps.imageset/baseline_apps_white_24pt_2x.png
new file mode 100644
index 00000000..95e1f49c
Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/apps.imageset/baseline_apps_white_24pt_2x.png differ
diff --git a/Stats/Supporting Files/Assets.xcassets/apps.imageset/baseline_apps_white_24pt_3x.png b/Stats/Supporting Files/Assets.xcassets/apps.imageset/baseline_apps_white_24pt_3x.png
new file mode 100644
index 00000000..1ab7303e
Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/apps.imageset/baseline_apps_white_24pt_3x.png differ
diff --git a/Stats/Supporting Files/Assets.xcassets/bug.imageset/Contents.json b/Stats/Supporting Files/Assets.xcassets/bug.imageset/Contents.json
new file mode 100644
index 00000000..65e2296b
--- /dev/null
+++ b/Stats/Supporting Files/Assets.xcassets/bug.imageset/Contents.json
@@ -0,0 +1,26 @@
+{
+ "images" : [
+ {
+ "filename" : "baseline_bug_report_white_24pt_1x.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "baseline_bug_report_white_24pt_2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "baseline_bug_report_white_24pt_3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/Stats/Supporting Files/Assets.xcassets/bug.imageset/baseline_bug_report_white_24pt_1x.png b/Stats/Supporting Files/Assets.xcassets/bug.imageset/baseline_bug_report_white_24pt_1x.png
new file mode 100644
index 00000000..953932bf
Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/bug.imageset/baseline_bug_report_white_24pt_1x.png differ
diff --git a/Stats/Supporting Files/Assets.xcassets/bug.imageset/baseline_bug_report_white_24pt_2x.png b/Stats/Supporting Files/Assets.xcassets/bug.imageset/baseline_bug_report_white_24pt_2x.png
new file mode 100644
index 00000000..e687d601
Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/bug.imageset/baseline_bug_report_white_24pt_2x.png differ
diff --git a/Stats/Supporting Files/Assets.xcassets/bug.imageset/baseline_bug_report_white_24pt_3x.png b/Stats/Supporting Files/Assets.xcassets/bug.imageset/baseline_bug_report_white_24pt_3x.png
new file mode 100644
index 00000000..30f89bf7
Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/bug.imageset/baseline_bug_report_white_24pt_3x.png differ
diff --git a/Stats/Supporting Files/Assets.xcassets/chart.imageset/Contents.json b/Stats/Supporting Files/Assets.xcassets/chart.imageset/Contents.json
new file mode 100644
index 00000000..37de9b8e
--- /dev/null
+++ b/Stats/Supporting Files/Assets.xcassets/chart.imageset/Contents.json
@@ -0,0 +1,26 @@
+{
+ "images" : [
+ {
+ "filename" : "baseline_insert_chart_outlined_white_24pt_1x.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "baseline_insert_chart_outlined_white_24pt_2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "baseline_insert_chart_outlined_white_24pt_3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/Stats/Supporting Files/Assets.xcassets/chart.imageset/baseline_insert_chart_outlined_white_24pt_1x.png b/Stats/Supporting Files/Assets.xcassets/chart.imageset/baseline_insert_chart_outlined_white_24pt_1x.png
new file mode 100644
index 00000000..b98cd270
Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/chart.imageset/baseline_insert_chart_outlined_white_24pt_1x.png differ
diff --git a/Stats/Supporting Files/Assets.xcassets/chart.imageset/baseline_insert_chart_outlined_white_24pt_2x.png b/Stats/Supporting Files/Assets.xcassets/chart.imageset/baseline_insert_chart_outlined_white_24pt_2x.png
new file mode 100644
index 00000000..726a5b53
Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/chart.imageset/baseline_insert_chart_outlined_white_24pt_2x.png differ
diff --git a/Stats/Supporting Files/Assets.xcassets/chart.imageset/baseline_insert_chart_outlined_white_24pt_3x.png b/Stats/Supporting Files/Assets.xcassets/chart.imageset/baseline_insert_chart_outlined_white_24pt_3x.png
new file mode 100644
index 00000000..a90d466b
Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/chart.imageset/baseline_insert_chart_outlined_white_24pt_3x.png differ
diff --git a/Stats/Supporting Files/Assets.xcassets/devices/Contents.json b/Stats/Supporting Files/Assets.xcassets/devices/Contents.json
new file mode 100644
index 00000000..73c00596
--- /dev/null
+++ b/Stats/Supporting Files/Assets.xcassets/devices/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Stats/Supporting Files/Assets.xcassets/devices/imac.imageset/Contents.json b/Stats/Supporting Files/Assets.xcassets/devices/imac.imageset/Contents.json
new file mode 100644
index 00000000..ee9e9071
--- /dev/null
+++ b/Stats/Supporting Files/Assets.xcassets/devices/imac.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "imac.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Stats/Supporting Files/Assets.xcassets/devices/imac.imageset/imac.png b/Stats/Supporting Files/Assets.xcassets/devices/imac.imageset/imac.png
new file mode 100644
index 00000000..2d65b9e4
Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/devices/imac.imageset/imac.png differ
diff --git a/Stats/Supporting Files/Assets.xcassets/devices/imacPro.imageset/Contents.json b/Stats/Supporting Files/Assets.xcassets/devices/imacPro.imageset/Contents.json
new file mode 100644
index 00000000..b4c088f1
--- /dev/null
+++ b/Stats/Supporting Files/Assets.xcassets/devices/imacPro.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "imacPro.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Stats/Supporting Files/Assets.xcassets/devices/imacPro.imageset/imacPro.png b/Stats/Supporting Files/Assets.xcassets/devices/imacPro.imageset/imacPro.png
new file mode 100644
index 00000000..5d909f23
Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/devices/imacPro.imageset/imacPro.png differ
diff --git a/Stats/Supporting Files/Assets.xcassets/devices/macMini.imageset/Contents.json b/Stats/Supporting Files/Assets.xcassets/devices/macMini.imageset/Contents.json
new file mode 100644
index 00000000..4e854e62
--- /dev/null
+++ b/Stats/Supporting Files/Assets.xcassets/devices/macMini.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "macMini.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Stats/Supporting Files/Assets.xcassets/devices/macMini.imageset/macMini.png b/Stats/Supporting Files/Assets.xcassets/devices/macMini.imageset/macMini.png
new file mode 100644
index 00000000..7b9bec12
Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/devices/macMini.imageset/macMini.png differ
diff --git a/Stats/Supporting Files/Assets.xcassets/devices/macbookAir.imageset/Contents.json b/Stats/Supporting Files/Assets.xcassets/devices/macbookAir.imageset/Contents.json
new file mode 100644
index 00000000..d7e925f6
--- /dev/null
+++ b/Stats/Supporting Files/Assets.xcassets/devices/macbookAir.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "macbookAir.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Stats/Supporting Files/Assets.xcassets/devices/macbookAir.imageset/macbookAir.png b/Stats/Supporting Files/Assets.xcassets/devices/macbookAir.imageset/macbookAir.png
new file mode 100644
index 00000000..7a51f781
Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/devices/macbookAir.imageset/macbookAir.png differ
diff --git a/Stats/Supporting Files/Assets.xcassets/devices/macbookPro.imageset/Contents.json b/Stats/Supporting Files/Assets.xcassets/devices/macbookPro.imageset/Contents.json
new file mode 100644
index 00000000..cf9b1176
--- /dev/null
+++ b/Stats/Supporting Files/Assets.xcassets/devices/macbookPro.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "macbookPro.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Stats/Supporting Files/Assets.xcassets/devices/macbookPro.imageset/macbookPro.png b/Stats/Supporting Files/Assets.xcassets/devices/macbookPro.imageset/macbookPro.png
new file mode 100644
index 00000000..5401f2c9
Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/devices/macbookPro.imageset/macbookPro.png differ
diff --git a/Stats/Supporting Files/Assets.xcassets/icons/Contents.json b/Stats/Supporting Files/Assets.xcassets/icons/Contents.json
deleted file mode 100644
index da4a164c..00000000
--- a/Stats/Supporting Files/Assets.xcassets/icons/Contents.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
\ No newline at end of file
diff --git a/Stats/Supporting Files/Assets.xcassets/icons/baseline_build_black_18pt.imageset/baseline_build_black_18pt_1x.png b/Stats/Supporting Files/Assets.xcassets/icons/baseline_build_black_18pt.imageset/baseline_build_black_18pt_1x.png
deleted file mode 100644
index 792a9c06..00000000
Binary files a/Stats/Supporting Files/Assets.xcassets/icons/baseline_build_black_18pt.imageset/baseline_build_black_18pt_1x.png and /dev/null differ
diff --git a/Stats/Supporting Files/Assets.xcassets/icons/baseline_build_black_18pt.imageset/baseline_build_black_18pt_2x.png b/Stats/Supporting Files/Assets.xcassets/icons/baseline_build_black_18pt.imageset/baseline_build_black_18pt_2x.png
deleted file mode 100644
index a1e7f71e..00000000
Binary files a/Stats/Supporting Files/Assets.xcassets/icons/baseline_build_black_18pt.imageset/baseline_build_black_18pt_2x.png and /dev/null differ
diff --git a/Stats/Supporting Files/Assets.xcassets/icons/baseline_build_black_18pt.imageset/baseline_build_black_18pt_3x.png b/Stats/Supporting Files/Assets.xcassets/icons/baseline_build_black_18pt.imageset/baseline_build_black_18pt_3x.png
deleted file mode 100644
index 34e6be7a..00000000
Binary files a/Stats/Supporting Files/Assets.xcassets/icons/baseline_build_black_18pt.imageset/baseline_build_black_18pt_3x.png and /dev/null differ
diff --git a/Stats/Supporting Files/Assets.xcassets/power.imageset/Contents.json b/Stats/Supporting Files/Assets.xcassets/power.imageset/Contents.json
new file mode 100644
index 00000000..cbc0a06d
--- /dev/null
+++ b/Stats/Supporting Files/Assets.xcassets/power.imageset/Contents.json
@@ -0,0 +1,26 @@
+{
+ "images" : [
+ {
+ "filename" : "baseline_power_settings_new_white_24pt_1x.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "baseline_power_settings_new_white_24pt_2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "baseline_power_settings_new_white_24pt_3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/Stats/Supporting Files/Assets.xcassets/power.imageset/baseline_power_settings_new_white_24pt_1x.png b/Stats/Supporting Files/Assets.xcassets/power.imageset/baseline_power_settings_new_white_24pt_1x.png
new file mode 100644
index 00000000..66cd1ea5
Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/power.imageset/baseline_power_settings_new_white_24pt_1x.png differ
diff --git a/Stats/Supporting Files/Assets.xcassets/power.imageset/baseline_power_settings_new_white_24pt_2x.png b/Stats/Supporting Files/Assets.xcassets/power.imageset/baseline_power_settings_new_white_24pt_2x.png
new file mode 100644
index 00000000..860a2348
Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/power.imageset/baseline_power_settings_new_white_24pt_2x.png differ
diff --git a/Stats/Supporting Files/Assets.xcassets/power.imageset/baseline_power_settings_new_white_24pt_3x.png b/Stats/Supporting Files/Assets.xcassets/power.imageset/baseline_power_settings_new_white_24pt_3x.png
new file mode 100644
index 00000000..cb19fc04
Binary files /dev/null and b/Stats/Supporting Files/Assets.xcassets/power.imageset/baseline_power_settings_new_white_24pt_3x.png differ
diff --git a/Stats/Supporting Files/Base.lproj/Main.storyboard b/Stats/Supporting Files/Base.lproj/Main.storyboard
deleted file mode 100755
index 8c35fef6..00000000
--- a/Stats/Supporting Files/Base.lproj/Main.storyboard
+++ /dev/null
@@ -1,71 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Stats/Supporting Files/Info.plist b/Stats/Supporting Files/Info.plist
index 67ae3df6..f82c114b 100755
--- a/Stats/Supporting Files/Info.plist
+++ b/Stats/Supporting Files/Info.plist
@@ -18,6 +18,8 @@
$(MARKETING_VERSION)
CFBundleVersion
$(CURRENT_PROJECT_VERSION)
+ Description
+ Simple macOS system monitor in your menu bar
LSApplicationCategoryType
public.app-category.utilities
LSMinimumSystemVersion
@@ -26,8 +28,6 @@
NSHumanReadableCopyright
Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
- NSMainStoryboardFile
- Main
NSPrincipalClass
NSApplication
diff --git a/Stats/Supporting Files/Updates.storyboard b/Stats/Supporting Files/Updates.storyboard
deleted file mode 100644
index 15a7697b..00000000
--- a/Stats/Supporting Files/Updates.storyboard
+++ /dev/null
@@ -1,287 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/resources/background.png b/Stats/Supporting Files/background.png
similarity index 100%
rename from resources/background.png
rename to Stats/Supporting Files/background.png
diff --git a/resources/cover.psd b/Stats/Supporting Files/cover.psd
similarity index 100%
rename from resources/cover.psd
rename to Stats/Supporting Files/cover.psd
diff --git a/Stats/Supporting Files/main.swift b/Stats/Supporting Files/main.swift
new file mode 100644
index 00000000..60fe67e5
--- /dev/null
+++ b/Stats/Supporting Files/main.swift
@@ -0,0 +1,15 @@
+//
+// main.swift
+// Stats
+//
+// Created by Serhiy Mytrovtsiy on 10/04/2020.
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+
+private let app = NSApplication.shared
+private let delegate = AppDelegate()
+
+app.delegate = delegate
+app.run()
diff --git a/Stats/Views/AboutViewController.swift b/Stats/Views/AboutViewController.swift
deleted file mode 100644
index 52c1320a..00000000
--- a/Stats/Views/AboutViewController.swift
+++ /dev/null
@@ -1,35 +0,0 @@
-//
-// AboutViewController.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 05/09/2019.
-// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-import Foundation
-
-class AboutVC: NSViewController {
- @IBOutlet weak var versionLabel: NSTextField!
-
- override func viewDidLoad() {
- super.viewDidLoad()
- self.view.wantsLayer = true
- }
-
- @IBAction func openLink(_ sender: Any) {
- NSWorkspace.shared.open(URL(string: "https://github.com/exelban/stats")!)
- }
-
- @IBAction func exit(_ sender: Any) {
- self.view.window?.close()
- }
-
- override func awakeFromNib() {
- if self.view.layer != nil {
- self.view.window?.backgroundColor = .windowBackgroundColor
- let versionNumber = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String
- versionLabel.stringValue = "Version \(versionNumber)"
- }
- }
-}
diff --git a/Stats/Views/AppSettings.swift b/Stats/Views/AppSettings.swift
new file mode 100644
index 00000000..2f8ff9c2
--- /dev/null
+++ b/Stats/Views/AppSettings.swift
@@ -0,0 +1,281 @@
+//
+// AppSettings.swift
+// Stats
+//
+// Created by Serhiy Mytrovtsiy on 15/04/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+import StatsKit
+
+class ApplicationSettings: NSView {
+ private let width: CGFloat = 540
+ private let height: CGFloat = 480
+ private let deviceInfoHeight: CGFloat = 300
+
+ init() {
+ super.init(frame: NSRect(x: 0, y: 0, width: width, height: height))
+ self.wantsLayer = true
+ self.layer?.backgroundColor = .clear
+
+ self.addDeviceInfo()
+ self.addSettings()
+ }
+
+ required public init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ private func addSettings() {
+ let view: NSView = NSView(frame: NSRect(x: 0, y: 1, width: self.width-1, height: self.height - self.deviceInfoHeight))
+ let rowHeight: CGFloat = 40
+ let rowHorizontalPadding: CGFloat = 16
+
+ let leftPanel: NSView = NSView(frame: NSRect(x: 0, y: 0, width: view.frame.width/2, height: view.frame.height))
+ leftPanel.wantsLayer = true
+
+ var processorInfo = ""
+ if systemKit.device.info?.cpu?.name != "" {
+ processorInfo += "\(systemKit.device.info?.cpu?.name ?? "Unknown")\n"
+ }
+ processorInfo += "\(systemKit.device.info?.cpu?.physicalCores ?? 0) cores (\(systemKit.device.info?.cpu?.logicalCores ?? 0) threads)"
+ leftPanel.addSubview(makeInfoRow(
+ frame: NSRect(x: rowHorizontalPadding, y: rowHeight*3, width: leftPanel.frame.width - (rowHorizontalPadding*1.5), height: rowHeight),
+ title: "Processor",
+ value: processorInfo
+ ))
+
+ let sizeFormatter = ByteCountFormatter()
+ sizeFormatter.allowedUnits = [.useGB]
+ sizeFormatter.countStyle = .memory
+ leftPanel.addSubview(makeInfoRow(
+ frame: NSRect(x: rowHorizontalPadding, y: rowHeight*2, width: leftPanel.frame.width - (rowHorizontalPadding*1.5), height: rowHeight),
+ title: "Memory",
+ value: "\(sizeFormatter.string(fromByteCount: Int64(systemKit.device.info?.ram?.total ?? 0)))"
+ ))
+
+ let gpus = systemKit.device.info?.gpu
+ var gpu: String = "Unknown"
+ if gpus != nil {
+ if gpus?.count == 1 {
+ gpu = gpus![0].name
+ } else {
+ gpu = ""
+ gpus!.forEach{ gpu += "\($0.name)\n" }
+ }
+ }
+ leftPanel.addSubview(makeInfoRow(
+ frame: NSRect(x: rowHorizontalPadding, y: rowHeight*1, width: leftPanel.frame.width - (rowHorizontalPadding*1.5), height: rowHeight),
+ title: "GPU",
+ value: gpu
+ ))
+
+ leftPanel.addSubview(makeInfoRow(
+ frame: NSRect(x: rowHorizontalPadding, y: 0, width: leftPanel.frame.width - (rowHorizontalPadding*1.5), height: rowHeight),
+ title: "Disk",
+ value: "\(systemKit.device.info?.disk?.model ?? systemKit.device.info?.disk?.name ?? "Unknown")"
+ ))
+
+ let rightPanel: NSView = NSView(frame: NSRect(x: self.width/2, y: 0, width: view.frame.width/2, height: view.frame.height))
+
+ rightPanel.addSubview(makeSettingRow(
+ frame: NSRect(x: rowHorizontalPadding*0.5, y: rowHeight*2, width: rightPanel.frame.width - (rowHorizontalPadding*1.5), height: rowHeight),
+ title: "Check for updates on start",
+ action: #selector(self.toggleUpdates),
+ state: store.bool(key: "checkUpdatesOnLogin", defaultValue: true)
+ ))
+
+ rightPanel.addSubview(makeSettingRow(
+ frame: NSRect(x: rowHorizontalPadding*0.5, y: rowHeight*1, width: rightPanel.frame.width - (rowHorizontalPadding*1.5), height: rowHeight),
+ title: "Show icon in dock",
+ action: #selector(self.toggleDock),
+ state: store.bool(key: "dockIcon", defaultValue: false)
+ ))
+
+ rightPanel.addSubview(makeSettingRow(
+ frame: NSRect(x: rowHorizontalPadding*0.5, y: 0, width: rightPanel.frame.width - (rowHorizontalPadding*1.5), height: rowHeight),
+ title: "Start at login",
+ action: #selector(self.toggleLaunchAtLogin),
+ state: LaunchAtLogin.isEnabled
+ ))
+
+ view.addSubview(leftPanel)
+ view.addSubview(rightPanel)
+ self.addSubview(view)
+ }
+
+ private func makeInfoRow(frame: NSRect, title: String, value: String) -> NSView {
+ let row: NSView = NSView(frame: frame)
+ let titleWidth = title.widthOfString(usingFont: .systemFont(ofSize: 13, weight: .light)) + 10
+
+ let rowTitle: NSTextField = TextView(frame: NSRect(x: 0, y: (row.frame.height - 16)/2, width: titleWidth, height: 17))
+ rowTitle.font = NSFont.systemFont(ofSize: 13, weight: .light)
+ rowTitle.textColor = .secondaryLabelColor
+ rowTitle.stringValue = title
+
+ let rowValue: NSTextField = TextView(frame: NSRect(x: titleWidth, y: (row.frame.height - 16)/2, width: row.frame.width - titleWidth, height: 17))
+ rowValue.font = NSFont.systemFont(ofSize: 13, weight: .light)
+ rowValue.alignment = .right
+ rowValue.stringValue = value
+ rowValue.isSelectable = true
+
+ if value.contains("\n") {
+ rowValue.frame = NSRect(x: titleWidth, y: 0, width: rowValue.frame.width, height: row.frame.height)
+ }
+
+ row.addSubview(rowTitle)
+ row.addSubview(rowValue)
+
+ return row
+ }
+
+ private func makeSettingRow(frame: NSRect, title: String, action: Selector, state: Bool) -> NSView {
+ let row: NSView = NSView(frame: frame)
+ let state: NSControl.StateValue = state ? .on : .off
+
+ let rowTitle: NSTextField = TextView(frame: NSRect(x: 0, y: (row.frame.height - 16)/2, width: row.frame.width - 52, height: 17))
+ rowTitle.font = NSFont.systemFont(ofSize: 13, weight: .light)
+ rowTitle.textColor = .secondaryLabelColor
+ rowTitle.stringValue = title
+
+ var toggle: NSControl = NSControl()
+ if #available(OSX 10.15, *) {
+ let switchButton = NSSwitch(frame: NSRect(x: row.frame.width - 50, y: 0, width: 50, height: row.frame.height))
+ switchButton.state = state
+ switchButton.action = action
+ switchButton.target = self
+
+ toggle = switchButton
+ } else {
+ let button: NSButton = NSButton(frame: NSRect(x: row.frame.width - 30, y: 0, width: 30, height: row.frame.height))
+ button.setButtonType(.switch)
+ button.state = state
+ button.title = ""
+ button.action = action
+ button.isBordered = false
+ button.isTransparent = true
+
+ toggle = button
+ }
+
+ row.addSubview(toggle)
+ row.addSubview(rowTitle)
+
+ return row
+ }
+
+ private func addDeviceInfo() {
+ let view: NSView = NSView(frame: NSRect(x: 0, y: self.height - self.deviceInfoHeight, width: self.width, height: self.deviceInfoHeight))
+ let leftPanel: NSView = NSView(frame: NSRect(x: 0, y: 0, width: self.width/2, height: self.deviceInfoHeight))
+
+ let deviceImageView: NSImageView = NSImageView(image: systemKit.device.model.icon)
+ deviceImageView.frame = NSRect(x: (leftPanel.frame.width - 160)/2, y: ((self.deviceInfoHeight - 120)/2) + 22, width: 160, height: 120)
+
+ let deviceNameField: NSTextField = TextView(frame: NSRect(x: 0, y: 72, width: leftPanel.frame.width, height: 20))
+ deviceNameField.alignment = .center
+ deviceNameField.font = NSFont.systemFont(ofSize: 14, weight: .regular)
+ deviceNameField.stringValue = systemKit.device.model.name
+ deviceNameField.isSelectable = true
+
+ let osField: NSTextField = TextView(frame: NSRect(x: 0, y: 52, width: leftPanel.frame.width, height: 18))
+ osField.alignment = .center
+ osField.font = NSFont.systemFont(ofSize: 12, weight: .regular)
+ osField.stringValue = "macOS \(systemKit.device.os?.name ?? "Unknown") (\(systemKit.device.os?.version.getFullVersion() ?? ""))"
+ osField.isSelectable = true
+
+ leftPanel.addSubview(deviceImageView)
+ leftPanel.addSubview(deviceNameField)
+ leftPanel.addSubview(osField)
+
+ let rightPanel: NSView = NSView(frame: NSRect(x: self.width/2, y: 0, width: self.width/2, height: self.deviceInfoHeight))
+
+ let iconView: NSImageView = NSImageView(frame: NSRect(x: (leftPanel.frame.width - 100)/2, y: ((self.deviceInfoHeight - 100)/2) + 32, width: 100, height: 100))
+ iconView.image = NSImage(named: NSImage.Name("AppIcon"))!
+
+ let infoView: NSView = NSView(frame: NSRect(x: 0, y: 54, width: self.width/2, height: 42))
+
+ let statsName: NSTextField = TextView(frame: NSRect(x: 0, y: 20, width: leftPanel.frame.width, height: 22))
+ statsName.alignment = .center
+ statsName.font = NSFont.systemFont(ofSize: 20, weight: .regular)
+ statsName.stringValue = "Stats"
+ statsName.isSelectable = true
+
+ let statsVersion: NSTextField = TextView(frame: NSRect(x: 0, y: 0, width: leftPanel.frame.width, height: 16))
+ statsVersion.alignment = .center
+ statsVersion.font = NSFont.systemFont(ofSize: 12, weight: .regular)
+ let versionNumber = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String
+ statsVersion.stringValue = "Version \(versionNumber)"
+ statsVersion.isSelectable = true
+
+ infoView.addSubview(statsName)
+ infoView.addSubview(statsVersion)
+
+ let button: NSButton = NSButton(frame: NSRect(x: (rightPanel.frame.width - 160)/2, y: 20, width: 160, height: 28))
+ button.title = "Check for updates"
+ button.bezelStyle = .rounded
+ button.target = self
+ button.action = #selector(checkNewVersion)
+
+ rightPanel.addSubview(iconView)
+ rightPanel.addSubview(infoView)
+ rightPanel.addSubview(button)
+
+ view.addSubview(leftPanel)
+ view.addSubview(rightPanel)
+
+ self.addSubview(view)
+ }
+
+ @objc func checkNewVersion(_ sender: NSObject) {
+ NotificationCenter.default.post(name: .checkForUpdates, object: nil, userInfo: nil)
+ }
+
+ @objc func toggleUpdates(_ sender: NSObject) {
+ var state: NSControl.StateValue? = nil
+ if #available(OSX 10.15, *) {
+ state = sender is NSSwitch ? (sender as! NSSwitch).state: nil
+ } else {
+ state = sender is NSButton ? (sender as! NSButton).state: nil
+ }
+
+ if state != nil {
+ store.set(key: "checkUpdatesOnLogin", value: state! == NSControl.StateValue.on)
+ }
+ }
+
+ @objc func toggleDock(_ sender: NSControl) {
+ var state: NSControl.StateValue? = nil
+ if #available(OSX 10.15, *) {
+ state = sender is NSSwitch ? (sender as! NSSwitch).state: nil
+ } else {
+ state = sender is NSButton ? (sender as! NSButton).state: nil
+ }
+
+ if state != nil {
+ store.set(key: "dockIcon", value: state! == NSControl.StateValue.on)
+ }
+ let dockIconStatus = state == NSControl.StateValue.on ? NSApplication.ActivationPolicy.regular : NSApplication.ActivationPolicy.accessory
+ NSApp.setActivationPolicy(dockIconStatus)
+ if state == .off {
+ NSApplication.shared.activate(ignoringOtherApps: true)
+ }
+ }
+
+ @objc func toggleLaunchAtLogin(_ sender: NSControl) {
+ var state: NSControl.StateValue? = nil
+ if #available(OSX 10.15, *) {
+ state = sender is NSSwitch ? (sender as! NSSwitch).state: nil
+ } else {
+ state = sender is NSButton ? (sender as! NSButton).state: nil
+ }
+
+ LaunchAtLogin.isEnabled = state! == NSControl.StateValue.on
+ if !store.exist(key: "runAtLoginInitialized") {
+ store.set(key: "runAtLoginInitialized", value: true)
+ }
+ }
+}
diff --git a/Stats/Views/PopupViewController.swift b/Stats/Views/PopupViewController.swift
deleted file mode 100644
index 53063686..00000000
--- a/Stats/Views/PopupViewController.swift
+++ /dev/null
@@ -1,288 +0,0 @@
-//
-// MainViewController.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 02/09/2019.
-// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-import ServiceManagement
-
-public let TabWidth: CGFloat = 300
-public let TabHeight: CGFloat = 356
-
-class MainViewController: NSViewController {
- let defaults = UserDefaults.standard
-
- @IBOutlet weak var tabView: NSTabView!
- @IBOutlet weak var topStackView: NSStackView!
-
- var segmentsControl: NSSegmentedControl!
- var settingsButton: NSButton!
-
- static func Init() -> MainViewController {
- let storyboard = NSStoryboard.init(name: "Main", bundle: nil)
- let identifier = NSStoryboard.SceneIdentifier("MainViewController")
-
- guard let viewcontroller = storyboard.instantiateController(withIdentifier: identifier) as? MainViewController else {
- fatalError("Why cant i find MainViewController? - Check Main.storyboard")
- }
-
- return viewcontroller
- }
-
- override func viewDidLoad() {
- super.viewDidLoad()
-
- makeHeader()
- }
-
- override func viewWillAppear() {
- if self.segmentsControl == nil || self.segmentsControl.selectedSegment == -1 { return }
- menuBar?.modules[self.segmentsControl.selectedSegment].popup.setActive(true)
-
- DispatchQueue.global(qos: .background).async {
- for module in menuBar!.modules {
- if module.popup.available && module.available && module.enabled {
- module.readers.filter{ $0.optional }.forEach { reader in
- reader.toggleEnable(true)
- }
- }
- }
- }
- }
-
- override func viewWillDisappear() {
- if self.segmentsControl == nil || self.segmentsControl.selectedSegment == -1 { return }
- menuBar?.modules[self.segmentsControl.selectedSegment].popup.setActive(false)
-
- DispatchQueue.global(qos: .background).async {
- for module in menuBar!.modules {
- if module.popup.available && module.available && module.enabled {
- if module.popup.available && module.available && module.enabled {
- module.readers.filter{ $0.optional }.forEach { reader in
- reader.toggleEnable(false)
- }
- }
- }
- }
- }
- }
-
- public func reload() {
- for tab in self.tabView.tabViewItems {
- self.tabView.removeTabViewItem(tab)
- }
- for view in self.topStackView.subviews {
- view.removeFromSuperview()
- }
- self.segmentsControl = NSSegmentedControl(labels: [], trackingMode: .selectOne, target: self, action: #selector(self.switchTabs))
- self.makeHeader()
- }
-
- private func makeHeader() {
- var list: [String] = []
- for module in menuBar!.modules {
- if module.popup.available && module.available && module.enabled {
- list.append(module.name)
-
- let tab = module.popup.view
- tab.label = module.name
- tab.identifier = module.name
- tab.view?.wantsLayer = true
- tab.view?.layer?.backgroundColor = NSColor.white.cgColor
-
- tabView.addTabViewItem(tab)
- }
- }
-
- let button = NSButton(frame: NSRect(x: 0, y: 0, width: 26, height: 20))
- button.title = ""
- button.image = NSImage(named: NSImage.Name("NSActionTemplate"))
- button.imagePosition = .imageOnly
- button.bezelStyle = .texturedSquare
- button.setButtonType(.momentaryPushIn)
- button.action = #selector(showSettings)
-
- button.widthAnchor.constraint(equalToConstant: 26).isActive = true
- button.heightAnchor.constraint(equalToConstant: 21).isActive = true
-
- if list.count > 0 {
- self.segmentsControl = NSSegmentedControl(labels: list, trackingMode: NSSegmentedControl.SwitchTracking.selectOne, target: self, action: #selector(switchTabs))
- self.segmentsControl.setSelected(true, forSegment: 0)
- self.segmentsControl.segmentDistribution = .fillEqually
-
- self.topStackView.addView(self.segmentsControl, in: NSStackView.Gravity.center)
- } else {
- self.topStackView.addView(NSView(frame: NSRect(x: 0, y: 0, width: 0, height: 0)), in: NSStackView.Gravity.center)
- tabView.addTabViewItem(generateEmptyTabView())
- }
- self.topStackView.addView(button, in: NSStackView.Gravity.center)
- }
-
- @objc func switchTabs(_ sender: NSSegmentedControl) {
- if let selectedLabel = self.segmentsControl.label(forSegment: sender.selectedSegment) {
- let tabNumber = self.tabView.indexOfTabViewItem(withIdentifier: selectedLabel)
-
- menuBar?.modules.forEach({ module in
- if module.name == selectedLabel && !module.popup.active {
- module.popup.setActive(true)
- } else if module.popup.active {
- module.popup.setActive(false)
- }
- })
-
- self.tabView.selectTabViewItem(at: tabNumber)
- }
- }
-
- @IBAction func showSettings(_ sender: NSButton) {
- let settings = buildSettings()
- let p = NSPoint(x: NSEvent.mouseLocation.x + 3, y: NSEvent.mouseLocation.y - 3)
- settings.popUp(positioning: settings.item(at: 0), at:p , in: nil)
- }
-
- private func buildSettings() -> NSMenu {
- let menu = NSMenu()
-
- for module in menuBar!.modules {
- if module.available {
- menu.addItem(module.menu)
- }
- }
-
- menu.addItem(NSMenuItem.separator())
-
- let openActivityMonitorMenu = NSMenuItem(title: "Open Activity Monitor", action: #selector(openActivityMonitor), keyEquivalent: "")
- openActivityMonitorMenu.target = self
-
- let checkForUpdates = NSMenuItem(title: "Check for updates on start", action: #selector(toggleMenu), keyEquivalent: "")
- checkForUpdates.state = defaults.bool(forKey: "checkUpdatesOnLogin") || defaults.object(forKey: "checkUpdatesOnLogin") == nil ? NSControl.StateValue.on : NSControl.StateValue.off
- checkForUpdates.target = self
-
- let runAtLogin = NSMenuItem(title: "Start at login", action: #selector(toggleMenu), keyEquivalent: "")
- runAtLogin.state = LaunchAtLogin.isEnabled ? NSControl.StateValue.on : NSControl.StateValue.off
- runAtLogin.target = self
-
- let dockIcon = NSMenuItem(title: "Show icon in dock", action: #selector(toggleMenu), keyEquivalent: "")
- dockIcon.state = defaults.bool(forKey: "dockIcon") ? NSControl.StateValue.on : NSControl.StateValue.off
- dockIcon.target = self
-
- let updateMenu = NSMenuItem(title: "Check for updates", action: #selector(checkUpdate), keyEquivalent: "")
- updateMenu.target = self
-
- let aboutMenu = NSMenuItem(title: "About Stats", action: #selector(openAbout), keyEquivalent: "")
- aboutMenu.target = self
-
- menu.addItem(checkForUpdates)
- menu.addItem(runAtLogin)
- menu.addItem(dockIcon)
-
- menu.addItem(NSMenuItem.separator())
-
- menu.addItem(openActivityMonitorMenu)
- menu.addItem(updateMenu)
- menu.addItem(aboutMenu)
- menu.addItem(NSMenuItem(title: "Quit Stats", action: #selector(NSApplication.terminate(_:)), keyEquivalent: ""))
-
- return menu
- }
-
- @objc func openActivityMonitor(_ sender: NSMenuItem) {
- NSWorkspace.shared.launchApplication(
- withBundleIdentifier: "com.apple.ActivityMonitor",
- options: [.default],
- additionalEventParamDescriptor: nil,
- launchIdentifier: nil)
- }
-
- @objc func checkUpdate(_ sender : NSMenuItem) {
- let updatesVC: NSWindowController? = NSStoryboard(name: "Updates", bundle: nil).instantiateController(withIdentifier: "UpdatesVC") as? NSWindowController
- updatesVC?.window?.center()
- updatesVC?.window?.level = .floating
- updatesVC!.showWindow(self)
- }
-
- @objc func openAbout(_ sender : NSMenuItem) {
- let aboutVC: NSWindowController? = NSStoryboard(name: "About", bundle: nil).instantiateController(withIdentifier: "AboutVC") as? NSWindowController
- aboutVC?.window?.center()
- aboutVC?.window?.level = .floating
- aboutVC!.showWindow(self)
- }
-
- @objc func toggleMenu(_ sender : NSMenuItem) {
- let status = sender.state != NSControl.StateValue.on
- sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on
-
- switch sender.title {
- case "Start at login":
- LaunchAtLogin.isEnabled = status
- if self.defaults.object(forKey: "runAtLoginInitialized") == nil {
- self.defaults.set(true, forKey: "runAtLoginInitialized")
- }
- case "Check for updates on start":
- self.defaults.set(status, forKey: "checkUpdatesOnLogin")
- case "Show icon in dock":
- self.defaults.set(status, forKey: "dockIcon")
- let iconStatus = status ? NSApplication.ActivationPolicy.regular : NSApplication.ActivationPolicy.accessory
- NSApp.setActivationPolicy(iconStatus)
- return
- default: break
- }
- }
-}
-
-
-func LabelField(string: String) -> NSTextField {
- let label: NSTextField = NSTextField(string: string)
-
- label.isEditable = false
- label.isSelectable = false
- label.isBezeled = false
- label.textColor = .darkGray
- 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
-}
-
-func generateEmptyTabView() -> NSTabViewItem {
- let emptyTabView = NSTabViewItem()
- emptyTabView.view?.frame = NSRect(x: 0, y: 0, width: TabWidth, height: TabHeight)
- emptyTabView.label = "empty"
- emptyTabView.identifier = "empty"
- emptyTabView.view?.wantsLayer = true
- emptyTabView.view?.layer?.backgroundColor = NSColor.white.cgColor
-
- let text: NSTextField = NSTextField(string: "No dashboard available")
- text.isEditable = false
- text.isSelectable = false
- text.isBezeled = false
- text.wantsLayer = true
- text.textColor = .labelColor
- text.canDrawSubviewsIntoLayer = true
- text.alignment = .center
- text.font = NSFont.systemFont(ofSize: 13, weight: .regular)
- text.frame = NSRect(x: 0, y: 0, width: TabWidth, height: 22)
- text.frame.origin.y = ((emptyTabView.view?.frame.size.height)! - 22) / 2
-
- emptyTabView.view?.addSubview(text)
-
- return emptyTabView
-}
diff --git a/Stats/Views/Settings.swift b/Stats/Views/Settings.swift
new file mode 100644
index 00000000..41696614
--- /dev/null
+++ b/Stats/Views/Settings.swift
@@ -0,0 +1,349 @@
+//
+// Settings.swift
+// Stats
+//
+// Created by Serhiy Mytrovtsiy on 12/04/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+import ModuleKit
+import StatsKit
+
+class SettingsWindow: NSWindow, NSWindowDelegate {
+ private let viewController: SettingsViewController = SettingsViewController()
+
+ init() {
+ let w = NSScreen.main!.frame.width
+ let h = NSScreen.main!.frame.height
+ super.init(
+ contentRect: NSMakeRect(w - self.viewController.view.frame.width, h - self.viewController.view.frame.height, self.viewController.view.frame.width, self.viewController.view.frame.height),
+ styleMask: [.closable, .titled, .miniaturizable],
+ backing: .buffered,
+ defer: true
+ )
+
+ self.contentViewController = self.viewController
+ self.animationBehavior = .default
+ self.collectionBehavior = .transient
+ self.titlebarAppearsTransparent = true
+ self.appearance = NSAppearance(named: .darkAqua)
+ self.center()
+ self.setIsVisible(false)
+
+ let windowController = NSWindowController()
+ windowController.window = self
+ windowController.loadWindow()
+ }
+
+ public func setModules() {
+ self.viewController.setModules(&modules)
+ if modules.filter({ $0.enabled != false && $0.available != false }).count == 0 {
+ self.setIsVisible(true)
+ }
+ }
+
+ public func openMenu(_ title: String) {
+ self.viewController.openMenu(title)
+ }
+
+ override func mouseUp(with: NSEvent) {
+ NotificationCenter.default.post(name: .clickInSettings, object: nil, userInfo: nil)
+ }
+}
+
+private class SettingsViewController: NSViewController {
+ private var settings: SettingsView
+
+ public init() {
+ self.settings = SettingsView(frame: NSRect(x: 0, y: 0, width: 720, height: 480))
+ super.init(nibName: nil, bundle: nil)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func loadView() {
+ self.view = self.settings
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ }
+
+ public func setModules(_ list: UnsafeMutablePointer<[Module]>) {
+ self.settings.setModules(list)
+ }
+
+ public func openMenu(_ title: String) {
+ self.settings.openMenu(title)
+ }
+}
+
+private class SettingsView: NSView {
+ private var modules: UnsafeMutablePointer<[Module]>?
+ private let navigationWidth: CGFloat = 180
+ private let buttonHeight: CGFloat = 45
+
+ private var navigationView: NSScrollView? = nil
+ private var buttonsView: NSView? = nil
+ private var mainView: NSView? = nil
+
+ private var applicationSettings: NSView = ApplicationSettings()
+
+ override init(frame: NSRect) {
+ super.init(frame: CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.width, height: frame.height))
+ self.wantsLayer = true
+
+ NotificationCenter.default.addObserver(self, selector: #selector(menuCallback), name: .openSettingsView, object: nil)
+
+ let navigationView: NSScrollView = NSScrollView(frame: NSRect(x: 0, y: buttonHeight, width: navigationWidth, height: frame.height - buttonHeight))
+ navigationView.wantsLayer = true
+ navigationView.drawsBackground = false
+
+ navigationView.addSubview(MenuView(n: 0, icon: NSImage(named: NSImage.Name("apps"))!, title: "Stats"))
+
+ let buttonsView: NSView = NSView(frame: NSRect(x: 0, y: 0, width: navigationWidth, height: buttonHeight))
+ buttonsView.wantsLayer = true
+
+ buttonsView.addSubview(self.makeButton(4, title: "Open Activity Monitor", image: "chart", action: #selector(openActivityMonitor)))
+ buttonsView.addSubview(self.makeButton(3, title: "Report a bug", image: "bug", action: #selector(reportBug)))
+ buttonsView.addSubview(self.makeButton(1, title: "Close application", image: "power", action: #selector(closeApp)))
+
+ let mainView: NSView = NSView(frame: NSRect(x: navigationWidth, y: 1, width: frame.width - navigationWidth-1, height: frame.height-1))
+ mainView.wantsLayer = true
+ mainView.layer?.cornerRadius = 3
+ mainView.layer?.maskedCorners = [.layerMaxXMinYCorner]
+
+ self.addSubview(navigationView)
+ self.addSubview(buttonsView)
+ self.addSubview(mainView)
+
+ self.navigationView = navigationView
+ self.mainView = mainView
+ self.buttonsView = buttonsView
+
+ self.openMenu("Stats")
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func draw(_ dirtyRect: NSRect) {
+ super.draw(dirtyRect)
+
+ NSColor.gridColor.set()
+ var line = NSBezierPath()
+ line.move(to: NSMakePoint(0, self.buttonHeight))
+ line.line(to: NSMakePoint(self.navigationWidth, self.buttonHeight))
+ line.lineWidth = 1
+ line.stroke()
+
+ line = NSBezierPath()
+ line.move(to: NSMakePoint(self.navigationWidth, 0))
+ line.line(to: NSMakePoint(self.navigationWidth, self.frame.height))
+ line.lineWidth = 1
+ line.stroke()
+ }
+
+ public func openMenu(_ title: String) {
+ self.navigationView?.subviews.forEach({ (m: NSView) in
+ if let menu = m as? MenuView {
+ if menu.title == title {
+ menu.activate()
+ }
+ }
+ })
+ }
+
+ public func setModules(_ list: UnsafeMutablePointer<[Module]>) {
+ list.pointee.forEach { (m: Module) in
+ if !m.available { return }
+ let n: Int = (self.navigationView?.subviews.count ?? 2)!-1
+ let menu: NSView = MenuView(n: n, icon: m.config.icon, title: m.config.name)
+ self.navigationView?.addSubview(menu)
+ }
+ self.modules = list
+// self.openMenu("CPU")
+ }
+
+ @objc private func menuCallback(_ notification: Notification) {
+ if let title = notification.userInfo?["module"] as? String {
+ var view: NSView = self.applicationSettings
+
+ let detectedModule = self.modules?.pointee.first{ $0.config.name == title }
+ if detectedModule != nil {
+ if let v = detectedModule?.settings {
+ view = v
+ }
+ }
+
+ self.mainView?.subviews.forEach{ $0.removeFromSuperview() }
+ self.mainView?.addSubview(view)
+
+ self.navigationView?.subviews.forEach({ (m: NSView) in
+ if let menu = m as? MenuView {
+ if menu.active {
+ menu.reset()
+ }
+ }
+ })
+ }
+ }
+
+ private func makeButton(_ n: Int, title: String, image: String, action: Selector) -> NSButton {
+ let button = NSButtonWithPadding()
+ button.frame = CGRect(x: Int(self.navigationWidth) - (45*n), y: 0, width: 44, height: 44)
+ button.verticalPadding = 20
+ button.horizontalPadding = 20
+ button.title = title
+ button.bezelStyle = .regularSquare
+ button.translatesAutoresizingMaskIntoConstraints = false
+ button.imageScaling = .scaleNone
+ button.image = Bundle(for: type(of: self)).image(forResource: image)!
+ button.contentTintColor = .lightGray
+ button.isBordered = false
+ button.action = action
+ button.target = self
+ button.focusRingType = .none
+
+ let rect = NSRect(x: Int(self.navigationWidth) - (45*n), y: 0, width: 44, height: 44)
+ let trackingArea = NSTrackingArea(rect: rect, options: [NSTrackingArea.Options.activeAlways, NSTrackingArea.Options.mouseEnteredAndExited, NSTrackingArea.Options.activeInActiveApp], owner: self, userInfo: ["button": title])
+ self.addTrackingArea(trackingArea)
+
+ return button
+ }
+
+ override func mouseEntered(with: NSEvent) {
+ if let userData = with.trackingArea?.userInfo as? [String : AnyObject] {
+ if let title = userData["button"] as? String {
+ let b = self.buttonsView?.subviews.first{ $0 is NSButton && ($0 as! NSButton).title == title }
+ if b != nil && b is NSButton {
+ (b as! NSButton).contentTintColor = .labelColor
+ (b as! NSButton).layer?.backgroundColor = .init(gray: 0.1, alpha: 0.5)
+ NSCursor.pointingHand.set()
+ }
+ }
+ }
+ }
+
+ override func mouseExited(with: NSEvent) {
+ if let userData = with.trackingArea?.userInfo as? [String : AnyObject] {
+ if let title = userData["button"] as? String {
+ let b = self.buttonsView?.subviews.first{ $0 is NSButton && ($0 as! NSButton).title == title }
+ if b != nil && b is NSButton {
+ (b as! NSButton).contentTintColor = .lightGray
+ (b as! NSButton).layer?.backgroundColor = .clear
+ NSCursor.arrow.set()
+ }
+ }
+ }
+ }
+
+ @objc public func openActivityMonitor(_ sender: Any) {
+ NSWorkspace.shared.launchApplication(
+ withBundleIdentifier: "com.apple.ActivityMonitor",
+ options: [.default],
+ additionalEventParamDescriptor: nil,
+ launchIdentifier: nil
+ )
+ self.window?.setIsVisible(false)
+ }
+
+ @objc public func reportBug(_ sender: Any) {
+ NSWorkspace.shared.open(URL(string: "https://github.com/exelban/stats/issues/new")!)
+ }
+
+ @objc public func aboutApp(_ sender: Any) {
+ print("about app")
+ }
+
+ @objc public func closeApp(_ sender: Any) {
+ NSApp.terminate(sender)
+ }
+}
+
+private class MenuView: NSView {
+ private let height: CGFloat = 40
+ private let width: CGFloat = 180
+
+ private var imageView: NSImageView? = nil
+ private var titleView: NSTextField? = nil
+
+ public let title: String
+ public var active: Bool = false
+
+ init(n: Int, icon: NSImage?, title: String) {
+ self.title = title
+ super.init(frame: NSRect(x: 0, y: self.height*CGFloat(n), width: width, height: self.height))
+ self.wantsLayer = true
+ self.layer?.backgroundColor = .clear
+
+ let rect = NSRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height)
+ let trackingArea = NSTrackingArea(rect: rect, options: [NSTrackingArea.Options.activeAlways, NSTrackingArea.Options.mouseEnteredAndExited, NSTrackingArea.Options.activeInActiveApp], owner: self, userInfo: ["menu": title])
+ self.addTrackingArea(trackingArea)
+
+ let imageView = NSImageView()
+ if icon != nil {
+ imageView.image = icon!
+ }
+ imageView.frame = NSRect(x: 8, y: (self.height - 18)/2, width: 18, height: 18)
+ imageView.wantsLayer = true
+ imageView.contentTintColor = .secondaryLabelColor
+
+ let titleView = TextView(frame: NSMakeRect(34, (self.height - 16)/2, 100, 17))
+ titleView.alignment = .natural
+ titleView.textColor = .secondaryLabelColor
+ titleView.font = NSFont.systemFont(ofSize: 14, weight: .regular)
+ titleView.stringValue = title
+
+ self.addSubview(imageView)
+ self.addSubview(titleView)
+
+ self.imageView = imageView
+ self.titleView = titleView
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ override func mouseEntered(with: NSEvent) {
+ self.titleView?.textColor = .labelColor
+ self.imageView?.contentTintColor = .labelColor
+ self.layer?.backgroundColor = .init(gray: 0.1, alpha: 0.5)
+ NSCursor.pointingHand.set()
+ }
+
+ override func mouseExited(with: NSEvent) {
+ if !self.active {
+ self.reset()
+ }
+ NSCursor.arrow.set()
+ }
+
+ override func mouseDown(with: NSEvent) {
+ self.activate()
+ }
+
+ public func reset() {
+ self.titleView?.textColor = .secondaryLabelColor
+ self.imageView?.contentTintColor = .secondaryLabelColor
+ self.layer?.backgroundColor = .clear
+ self.active = false
+ }
+
+ public func activate() {
+ NotificationCenter.default.post(name: .openSettingsView, object: nil, userInfo: ["module": self.title])
+
+ self.titleView?.textColor = .labelColor
+ self.imageView?.contentTintColor = .labelColor
+ self.layer?.backgroundColor = .init(gray: 0.1, alpha: 0.5)
+ self.active = true
+ }
+}
diff --git a/Stats/Views/Update.swift b/Stats/Views/Update.swift
new file mode 100644
index 00000000..3c90fa7b
--- /dev/null
+++ b/Stats/Views/Update.swift
@@ -0,0 +1,179 @@
+//
+// Update.swift
+// Stats
+//
+// Created by Serhiy Mytrovtsiy on 21/05/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+import StatsKit
+import os.log
+
+class UpdateWindow: NSWindow, NSWindowDelegate {
+ private let viewController: UpdateViewController = UpdateViewController()
+
+ init() {
+ let w = NSScreen.main!.frame.width
+ let h = NSScreen.main!.frame.height
+ super.init(
+ contentRect: NSMakeRect(w - self.viewController.view.frame.width, h - self.viewController.view.frame.height, self.viewController.view.frame.width, self.viewController.view.frame.height),
+ styleMask: [.closable, .titled],
+ backing: .buffered,
+ defer: true
+ )
+
+ self.contentViewController = self.viewController
+ self.animationBehavior = .default
+ self.collectionBehavior = .transient
+ self.titlebarAppearsTransparent = true
+ self.appearance = NSAppearance(named: .darkAqua)
+ self.center()
+ self.setIsVisible(false)
+
+ let windowController = NSWindowController()
+ windowController.window = self
+ windowController.loadWindow()
+ }
+
+ public func open(_ v: version) {
+ if !self.isVisible {
+ self.setIsVisible(true)
+ self.makeKeyAndOrderFront(nil)
+ }
+ self.viewController.open(v)
+ }
+}
+
+private class UpdateViewController: NSViewController {
+ private var update: UpdateView
+
+ public init() {
+ self.update = UpdateView(frame: NSRect(x: 0, y: 0, width: 280, height: 150))
+ super.init(nibName: nil, bundle: nil)
+ self.view = self.update
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ public func open(_ v: version) {
+ self.update.setVersions(v)
+ }
+}
+
+private class UpdateView: NSView {
+ private let progressBar: NSProgressIndicator = NSProgressIndicator()
+ private var version: version? = nil
+ private var informationView: NSView? = nil
+ private var noNew: NSView? = nil
+ private var currentVersion: NSTextField? = nil
+ private var latestVersion: NSTextField? = nil
+
+ override init(frame: NSRect) {
+ super.init(frame: CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.width, height: frame.height))
+ self.wantsLayer = true
+
+ self.addProgressBar()
+ self.addInformation()
+ self.addNoNew()
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ private func addProgressBar() {
+ self.progressBar.isDisplayedWhenStopped = false
+ self.progressBar.frame = NSRect(x: (self.frame.width - 22)/2, y: (self.frame.height - 22)/2, width: 22, height: 22)
+ self.progressBar.style = .spinning
+
+ self.addSubview(self.progressBar)
+ }
+
+ private func addInformation() {
+ let view: NSView = NSView(frame: NSRect(x: 10, y: 10, width: self.frame.width - 20, height: self.frame.height - 20))
+
+ let title: NSTextField = TextView(frame: NSRect(x: 0, y: view.frame.height - 18, width: view.frame.width, height: 18))
+ title.font = NSFont.systemFont(ofSize: 14, weight: .bold)
+ title.alignment = .center
+ title.stringValue = "New version available"
+
+ let currentVersion: NSTextField = TextView(frame: NSRect(x: 0, y: title.frame.origin.y - 40, width: view.frame.width, height: 16))
+ currentVersion.stringValue = "Current version: 0.0.0"
+
+ let latestVersion: NSTextField = TextView(frame: NSRect(x: 0, y: currentVersion.frame.origin.y - 22, width: view.frame.width, height: 16))
+ latestVersion.stringValue = "Latest version: 0.0.0"
+
+ let button: NSButton = NSButton(frame: NSRect(x: 0, y: 0, width: view.frame.width, height: 26))
+ button.title = "Download"
+ button.bezelStyle = .rounded
+ button.action = #selector(self.download)
+ button.target = self
+
+ view.addSubview(title)
+ view.addSubview(currentVersion)
+ view.addSubview(latestVersion)
+ view.addSubview(button)
+ view.isHidden = true
+ self.addSubview(view)
+ self.informationView = view
+ self.currentVersion = currentVersion
+ self.latestVersion = latestVersion
+ }
+
+ private func addNoNew() {
+ let view: NSView = NSView(frame: NSRect(x: 10, y: 10, width: self.frame.width - 20, height: self.frame.height - 20))
+
+ let title: NSTextField = TextView(frame: NSRect(x: 0, y: ((view.frame.height - 18)/2)+20, width: view.frame.width, height: 18))
+ title.font = NSFont.systemFont(ofSize: 14, weight: .regular)
+ title.alignment = .center
+ title.stringValue = "The latest version of Stats installed"
+
+ let button: NSButton = NSButton(frame: NSRect(x: 0, y: 0, width: view.frame.width, height: 26))
+ button.title = "Close"
+ button.bezelStyle = .rounded
+ button.action = #selector(self.close)
+ button.target = self
+
+ view.addSubview(button)
+ view.addSubview(title)
+ self.addSubview(view)
+ self.noNew = view
+ }
+
+ public func setVersions(_ v: version) {
+ self.progressBar.stopAnimation(self)
+ self.noNew?.isHidden = true
+ self.informationView?.isHidden = true
+
+ if v.newest {
+ self.informationView?.isHidden = false
+ self.version = v
+
+ currentVersion?.stringValue = "Current version: \(v.current)"
+ latestVersion?.stringValue = "Latest version: \(v.latest)"
+ return
+ }
+
+ self.noNew?.isHidden = false
+ }
+
+ @objc func close(_ sender: Any) {
+ self.window?.setIsVisible(false)
+ }
+
+ @objc func download(_ sender: Any) {
+ guard let urlString = self.version?.url, let url = URL(string: urlString) else {
+ return
+ }
+ os_log(.debug, log: log, "start downloading new version of app from: %s", "\(url.absoluteString)")
+ updater.download(url)
+ self.progressBar.startAnimation(self)
+ self.informationView?.isHidden = true
+ }
+}
diff --git a/Stats/Views/UpdatesViewController.swift b/Stats/Views/UpdatesViewController.swift
deleted file mode 100644
index 033ddee4..00000000
--- a/Stats/Views/UpdatesViewController.swift
+++ /dev/null
@@ -1,79 +0,0 @@
-//
-// UpdatesViewController.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 05/09/2019.
-// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-import Foundation
-
-class UpdatesVC: NSViewController {
- @IBOutlet weak var mainView: NSStackView!
- @IBOutlet weak var spinnerView: NSView!
- @IBOutlet weak var noInternetView: NSView!
- @IBOutlet weak var mainTextLabel: NSTextFieldCell!
- @IBOutlet weak var currentVersionLabel: NSTextField!
- @IBOutlet weak var latestVersionLabel: NSTextField!
- @IBOutlet weak var downloadButton: NSButton!
- @IBOutlet weak var spinner: NSProgressIndicator!
-
- var url: String?
-
- override func viewDidLoad() {
- super.viewDidLoad()
- self.view.wantsLayer = true
-
- self.spinner.startAnimation(self)
-
- updater.check() { result, error in
- if error != nil && error as! String == "No internet connection" {
- DispatchQueue.main.async(execute: {
- self.spinnerView.isHidden = true
- self.noInternetView.isHidden = false
- })
- return
- }
-
- guard error == nil, let version: version = result else {
- print("Error: \(error ?? "check error")")
- return
- }
-
- DispatchQueue.main.async(execute: {
- self.spinner.stopAnimation(self)
- self.spinnerView.isHidden = true
- self.mainView.isHidden = false
- self.currentVersionLabel.stringValue = version.current
- self.latestVersionLabel.stringValue = version.latest
- self.url = version.url
-
- if !version.newest {
- self.mainTextLabel.stringValue = "No new version available"
- self.downloadButton.isEnabled = false
- }
- })
- }
- }
-
- override func awakeFromNib() {
- if self.view.layer != nil {
- self.view.window?.backgroundColor = .windowBackgroundColor
- }
- }
-
- @IBAction func download(_ sender: Any) {
- guard let urlString = self.url, let url = URL(string: urlString) else {
- return
- }
- updater.download(url)
- self.spinner.startAnimation(self)
- self.spinnerView.isHidden = false
- self.mainView.isHidden = true
- }
-
- @IBAction func exit(_ sender: Any) {
- self.view.window?.close()
- }
-}
diff --git a/Stats/Widgets/Battery/BatteryPercentageWidget.swift b/Stats/Widgets/Battery/BatteryPercentageWidget.swift
deleted file mode 100644
index dce1f30e..00000000
--- a/Stats/Widgets/Battery/BatteryPercentageWidget.swift
+++ /dev/null
@@ -1,67 +0,0 @@
-//
-// BatteryPercentageWidget.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 12/09/2019.
-// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-
-class BatteryPercentageWidget: BatteryWidget {
- private var percentageValue: NSTextField = NSTextField()
-
- private let percentageLowWidth: CGFloat = 23
- private let percentageWidth: CGFloat = 30
- private let percentageFullWidth: CGFloat = 36
-
- override init(frame: NSRect) {
- super.init(frame: CGRect(x: 0, y: 0, width: widgetSize.width, height: widgetSize.height))
- self.name = "BatteryPercentage"
- self.drawPercentage()
- self.update()
- }
-
- required init?(coder decoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- private func drawPercentage() {
- self.percentageValue = NSTextField(frame: NSMakeRect(0, 0, percentageWidth, self.frame.size.height - 2))
- percentageValue.isEditable = false
- percentageValue.isSelectable = false
- percentageValue.isBezeled = false
- percentageValue.wantsLayer = true
- percentageValue.textColor = .labelColor
- percentageValue.backgroundColor = .controlColor
- percentageValue.canDrawSubviewsIntoLayer = true
- percentageValue.alignment = .right
- percentageValue.font = NSFont.systemFont(ofSize: 12, weight: .regular)
- percentageValue.stringValue = "\(Int(self.value * 100))%"
- if self.value == 0 {
- percentageValue.isHidden = true
- }
-
- self.addSubview(percentageValue)
- }
-
- override func update() {
- if self.value == 0 { return }
-
- if self.percentageValue.isHidden {
- self.percentageValue.isHidden = false
- }
- self.percentageValue.stringValue = "\(Int(self.value * 100))%"
-
- if self.value == 1 && self.size != self.batterySize + percentageFullWidth {
- self.changeWidth(width: 0)
- self.percentageValue.frame.size.width = 0
- } else if self.value < 0.1 && self.size != self.batterySize + percentageLowWidth {
- self.changeWidth(width: percentageLowWidth)
- self.percentageValue.frame.size.width = percentageLowWidth
- } else if self.value >= 0.1 && self.value != 1 && self.size != self.batterySize + percentageWidth {
- self.changeWidth(width: percentageWidth)
- self.percentageValue.frame.size.width = percentageWidth
- }
- }
-}
diff --git a/Stats/Widgets/Battery/BatteryTimeWidget.swift b/Stats/Widgets/Battery/BatteryTimeWidget.swift
deleted file mode 100644
index a5247a0f..00000000
--- a/Stats/Widgets/Battery/BatteryTimeWidget.swift
+++ /dev/null
@@ -1,69 +0,0 @@
-//
-// BatteryTimeWidget.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 12/09/2019.
-// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-
-class BatteryTimeWidget: BatteryWidget {
- private var timeValue: NSTextField = NSTextField()
- private let timeWidth: CGFloat = 62
- private let timeHourWidth: CGFloat = 42
-
- override init(frame: NSRect) {
- super.init(frame: CGRect(x: 0, y: 0, width: widgetSize.width, height: widgetSize.height))
- self.name = "BatteryTime"
- self.drawTime()
- self.changeWidth(width: self.timeWidth)
- self.update()
- }
-
- required init?(coder decoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- private func drawTime() {
- self.timeValue = NSTextField(frame: NSMakeRect(0, 0, timeWidth, self.frame.size.height - 4))
- timeValue.isEditable = false
- timeValue.isSelectable = false
- timeValue.isBezeled = false
- timeValue.wantsLayer = true
- timeValue.textColor = .labelColor
- timeValue.backgroundColor = .controlColor
- timeValue.canDrawSubviewsIntoLayer = true
- timeValue.alignment = .right
- timeValue.font = NSFont.systemFont(ofSize: 11, weight: .regular)
- timeValue.stringValue = (self.time*60).printSecondsToHoursMinutesSeconds()
- if self.time <= 0 {
- timeValue.isHidden = true
- }
-
- self.addSubview(timeValue)
- }
-
- override func update() {
- if self.value == 0 { return }
-
- if self.time > 0 {
- if self.timeValue.isHidden {
- self.timeValue.isHidden = false
- }
- self.timeValue.stringValue = (self.time*60).printSecondsToHoursMinutesSeconds()
- }
-
- if self.time <= 0 {
- self.changeWidth(width: 0)
- self.timeValue.frame.size.width = 0
- self.timeValue.isHidden = true
- } else if self.time <= 59 {
- self.changeWidth(width: timeHourWidth)
- self.timeValue.frame.size.width = timeHourWidth
- } else if self.time > 59 {
- self.changeWidth(width: timeWidth)
- self.timeValue.frame.size.width = timeWidth
- }
- }
-}
diff --git a/Stats/Widgets/Battery/BatteryWidget.swift b/Stats/Widgets/Battery/BatteryWidget.swift
deleted file mode 100644
index 22d833a8..00000000
--- a/Stats/Widgets/Battery/BatteryWidget.swift
+++ /dev/null
@@ -1,132 +0,0 @@
-//
-// BatteryView.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 14/06/2019.
-// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-
-class BatteryWidget: NSView, Widget {
- public var name: String = "Battery"
- public var menus: [NSMenuItem] = []
- public var size: CGFloat = 30
- public var batterySize: CGFloat = 30
-
- private let defaults = UserDefaults.standard
- private var color: Bool = false
-
- public var value: Double = 0
- public var time: Double = 0
- public var charging: Bool = false
-
- override var intrinsicContentSize: CGSize {
- return CGSize(width: self.frame.size.width, height: self.frame.size.height)
- }
-
- override init(frame: NSRect) {
- self.value = 0.0
- self.time = 0.0
- self.charging = false
- super.init(frame: CGRect(x: 0, y: 0, width: self.size, height: widgetSize.height))
- self.wantsLayer = true
- }
-
- required init?(coder decoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- func start() {
- self.color = defaults.object(forKey: "\(name)_color") != nil ? defaults.bool(forKey: "\(name)_color") : false
- self.initMenu()
- self.redraw()
- }
-
- func initMenu() {}
- func update() {
- self.changeWidth(width: 0)
- }
-
- override func draw(_ dirtyRect: NSRect) {
- super.draw(dirtyRect)
-
- var x: CGFloat = 4.0
- let w: CGFloat = self.batterySize - (x * 2)
- let h: CGFloat = 11.0
- let y: CGFloat = (dirtyRect.size.height - h) / 2
- let r: CGFloat = 1.0
- if dirtyRect.size.width != batterySize {
- x += self.size - batterySize
- }
-
- 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
- let inner = NSBezierPath(roundedRect: NSRect(x: x+0.5, y: y+1.5, width: maxWidth*CGFloat(self.value), height: h-3), xRadius: 0.5, yRadius: 0.5)
- self.value.batteryColor(color: self.color).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.display()
- }
-
- func setValue(data: [Double]) {
- let value: Double = data.first!
- let time: Double = data.last!
- var changed: Bool = false
-
- if self.value != value {
- self.value = value
- changed = true
- }
- if self.time != time {
- self.time = time
- changed = true
- }
-
- if changed {
- self.redraw()
- self.update()
- }
- }
-
- func setCharging(value: Bool) {
- if self.charging != value {
- self.charging = value
- self.redraw()
- }
- }
-
- func changeWidth(width: CGFloat) {
- self.size = batterySize + width
- self.frame.size.width = self.size
- if menuBar != nil {
- menuBar!.refresh()
- }
- }
-}
diff --git a/Stats/Widgets/Charts/BarChart.swift b/Stats/Widgets/Charts/BarChart.swift
deleted file mode 100644
index a3947d3f..00000000
--- a/Stats/Widgets/Charts/BarChart.swift
+++ /dev/null
@@ -1,146 +0,0 @@
-//
-// BarChart.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 09.07.2019.
-// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-
-class BarChart: NSView, Widget {
- public var name: String = "BarChart"
- public var menus: [NSMenuItem] = []
-
- private var size: CGFloat = widgetSize.width + 10
- private var labelPadding: CGFloat = 12.0
- private var label: Bool = false
- private let defaults = UserDefaults.standard
-
- private var partitions: [Double] = []
-
- override var intrinsicContentSize: CGSize {
- return CGSize(width: self.frame.size.width, height: self.frame.size.height)
- }
-
- override init(frame: NSRect) {
- self.label = defaults.object(forKey: "\(name)_label") != nil ? defaults.bool(forKey: "\(name)_label") : true
- self.partitions = Array(repeating: 0.0, count: 1)
- super.init(frame: CGRect(x: 0, y: 0, width: 0, height: widgetSize.height))
- self.wantsLayer = true
- self.addSubview(NSView())
- }
-
- required init?(coder decoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- func start() {
- self.label = defaults.object(forKey: "\(name)_label") != nil ? defaults.bool(forKey: "\(name)_label") : true
- self.initPreferences()
- }
-
- func initPreferences() {
- let label = NSMenuItem(title: "Label", action: #selector(toggleLabel), keyEquivalent: "")
- label.state = self.label ? NSControl.StateValue.on : NSControl.StateValue.off
- label.target = self
-
- self.menus.append(label)
- }
-
- @objc func toggleLabel(_ sender: NSMenuItem) {
- sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on
- self.defaults.set(sender.state == NSControl.StateValue.on, forKey: "\(self.name)_label")
- self.label = (sender.state == NSControl.StateValue.on)
- }
-
- override func draw(_ dirtyRect: NSRect) {
- super.draw(dirtyRect)
-
- let gradientColor: NSColor = NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 0.8)
- let width = self.frame.size.width - (widgetSize.margin * 2)
- let height = self.frame.size.height - (widgetSize.margin * 2)
-
- var x = widgetSize.margin
- if label {
- x = x + labelPadding
- }
-
- let partitionMargin: CGFloat = 0.5
- let partitionsWidth: CGFloat = width - (partitionMargin * 2) - x
- var partitionWidth: CGFloat = partitionsWidth
- if partitions.count > 1 {
- partitionWidth = (partitionsWidth - (partitionMargin * (CGFloat(partitions.count) - 1))) / CGFloat(partitions.count)
- }
-
- for i in 0.. Double in
- return v.rounded(toPlaces: 2)
- }
- if self.partitions != values {
- self.partitions = values
- self.redraw()
- }
- }
-
- func redraw() {
- var width: CGFloat = widgetSize.width + 10
- if self.partitions.count == 1 {
- width = 18
- }
- if self.partitions.count == 2 {
- width = 28
- }
- if self.label {
- width += labelPadding
- }
-
- if self.frame.size.width != width {
- self.setFrameSize(NSSize(width: width, height: self.frame.size.height))
- if menuBar != nil {
- menuBar!.refresh()
- }
- }
-
- self.display()
- }
-}
diff --git a/Stats/Widgets/Charts/LineChart.swift b/Stats/Widgets/Charts/LineChart.swift
deleted file mode 100644
index c84a2612..00000000
--- a/Stats/Widgets/Charts/LineChart.swift
+++ /dev/null
@@ -1,181 +0,0 @@
-//
-// LineChart.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 14.07.2019.
-// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-import Charts
-
-class Chart: NSView, Widget {
- public var name: String = "LineChart"
- public var menus: [NSMenuItem] = []
-
- internal let defaults = UserDefaults.standard
- internal var size: CGFloat = widgetSize.width + 7
- internal var labelPadding: CGFloat = 10.0
- internal var label: Bool = false
-
- internal var height: CGFloat = 0.0
- internal var points: [Double] = []
-
- override var intrinsicContentSize: CGSize {
- return CGSize(width: self.frame.size.width, height: self.frame.size.height)
- }
-
- override init(frame: NSRect) {
- self.points = Array(repeating: 0.0, count: 50)
- super.init(frame: CGRect(x: 0, y: 0, width: self.size, height: widgetSize.height))
- self.wantsLayer = true
- self.addSubview(NSView())
- }
-
- required init?(coder decoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- func start() {
- self.label = defaults.object(forKey: "\(name)_label") != nil ? defaults.bool(forKey: "\(name)_label") : true
- self.initMenu()
-
- if self.label {
- self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: self.frame.size.width + labelPadding, height: self.frame.size.height)
- }
- }
-
- func initMenu() {
- let label = NSMenuItem(title: "Label", action: #selector(toggleLabel), keyEquivalent: "")
- label.state = self.label ? NSControl.StateValue.on : NSControl.StateValue.off
- label.target = self
-
- self.menus.append(label)
- }
-
- @objc func toggleLabel(_ sender: NSMenuItem) {
- sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on
- self.defaults.set(sender.state == NSControl.StateValue.on, forKey: "\(self.name)_label")
- self.label = (sender.state == NSControl.StateValue.on)
-
- var width = self.size
- if self.label {
- width = width + labelPadding
- }
-
- self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: width, height: self.frame.size.height)
- self.redraw()
- menuBar!.refresh()
- }
-
- override func draw(_ dirtyRect: NSRect) {
- super.draw(dirtyRect)
-
- 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)
-
- let context = NSGraphicsContext.current!.cgContext
- var xOffset: CGFloat = 4.0
- if label {
- xOffset = xOffset + labelPadding
- }
- let yOffset: CGFloat = 3.0
- if height == 0 {
- height = self.frame.size.height - CGFloat((yOffset * 2))
- }
-
- var xRatio = Double(self.frame.size.width - (xOffset * 2)) / (Double(self.points.count) - 1)
- if label {
- 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
- }
- let columnYPoint = { (point: Int) -> CGFloat in
- return CGFloat((CGFloat(truncating: self.points[point] as NSNumber) * self.height)) + yOffset
- }
-
- let graphPath = NSBezierPath()
- let x: CGFloat = columnXPoint(0)
- let y: CGFloat = columnYPoint(0)
- graphPath.move(to: CGPoint(x: x, y: y))
-
- for i in 1.. String {
- return Units(bytes: Int64(value)).getReadableSpeed()
- }
-}
diff --git a/Stats/Widgets/Charts/LineChartWithValue.swift b/Stats/Widgets/Charts/LineChartWithValue.swift
deleted file mode 100644
index 3e485fcd..00000000
--- a/Stats/Widgets/Charts/LineChartWithValue.swift
+++ /dev/null
@@ -1,120 +0,0 @@
-//
-// LineChartWithValue.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 14.07.2019.
-// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-
-class ChartWithValue: Chart {
- private var valueLabel: NSTextField = NSTextField()
- private var color: Bool = false
-
- override var intrinsicContentSize: CGSize {
- return CGSize(width: self.frame.size.width, height: self.frame.size.height)
- }
-
- override init(frame: NSRect) {
- super.init(frame: CGRect(x: 0, y: 0, width: widgetSize.width + 7, height: widgetSize.height))
- self.wantsLayer = true
- self.name = "LineChartWithValue"
- }
-
- required init?(coder decoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- override func start() {
- self.label = defaults.object(forKey: "\(name)_label") != nil ? defaults.bool(forKey: "\(name)_label") : true
- self.color = defaults.object(forKey: "\(name)_color") != nil ? defaults.bool(forKey: "\(name)_color") : false
- self.initMenu()
-
- if self.label {
- self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: self.frame.size.width + labelPadding, height: self.frame.size.height)
- }
- self.drawValue()
- }
-
- override func initMenu() {
- let label = NSMenuItem(title: "Label", action: #selector(toggleLabel), keyEquivalent: "")
- label.state = self.label ? NSControl.StateValue.on : NSControl.StateValue.off
- label.target = self
-
- let color = NSMenuItem(title: "Color", action: #selector(toggleColor), keyEquivalent: "")
- color.state = self.color ? NSControl.StateValue.on : NSControl.StateValue.off
- color.target = self
-
- self.menus.append(label)
- self.menus.append(color)
- }
-
- override func setValue(data: [Double]) {
- let value: Double = data.first!
-
- self.valueLabel.stringValue = "\(Int(Float(value.roundTo(decimalPlaces: 2))! * 100))%"
- self.valueLabel.textColor = value.usageColor(color: self.color)
-
- if self.points.count < 50 {
- self.points.append(value)
- return
- }
-
- for (i, _) in self.points.enumerated() {
- if i+1 < self.points.count {
- self.points[i] = self.points[i+1]
- } else {
- self.points[i] = value
- }
- }
-
- self.redraw()
- }
-
- func drawValue () {
- for subview in self.subviews {
- subview.removeFromSuperview()
- }
-
- valueLabel = NSTextField(frame: NSMakeRect(2, widgetSize.height - 11, self.frame.size.width, 10))
- if label {
- valueLabel = NSTextField(frame: NSMakeRect(labelPadding + 2, widgetSize.height - 11, self.frame.size.width, 10))
- }
- valueLabel.textColor = NSColor.red
- valueLabel.isEditable = false
- valueLabel.isSelectable = false
- valueLabel.isBezeled = false
- valueLabel.wantsLayer = true
- valueLabel.textColor = .labelColor
- valueLabel.backgroundColor = .controlColor
- valueLabel.canDrawSubviewsIntoLayer = true
- valueLabel.alignment = .natural
- valueLabel.font = NSFont.systemFont(ofSize: 8, weight: .light)
- valueLabel.stringValue = ""
- valueLabel.addSubview(NSView())
-
- self.height = 7.0
- self.addSubview(valueLabel)
- }
-
- @objc override func toggleLabel(_ sender: NSMenuItem) {
- sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on
- self.defaults.set(sender.state == NSControl.StateValue.on, forKey: "\(self.name)_label")
- self.label = (sender.state == NSControl.StateValue.on)
-
- var width = self.size
- if self.label {
- width = width + labelPadding
- }
- self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: width, height: self.frame.size.height)
- self.drawValue()
- menuBar!.refresh()
- }
-
- @objc func toggleColor(_ sender: NSMenuItem) {
- sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on
- self.defaults.set(sender.state == NSControl.StateValue.on, forKey: "\(name)_color")
- self.color = sender.state == NSControl.StateValue.on
- }
-}
diff --git a/Stats/Widgets/Mini.swift b/Stats/Widgets/Mini.swift
deleted file mode 100644
index 99ead121..00000000
--- a/Stats/Widgets/Mini.swift
+++ /dev/null
@@ -1,106 +0,0 @@
-//
-// Mini.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 14.06.2019.
-// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-
-class Mini: NSView, Widget {
- public var name: String = "Mini"
- public var menus: [NSMenuItem] = []
-
- private var value: Double = 0
- private var size: CGFloat = widgetSize.width
- private var valueView: NSTextField = NSTextField()
- private var labelView: NSTextField = NSTextField()
- private let defaults = UserDefaults.standard
-
- private var color: Bool = false
-
- override var intrinsicContentSize: CGSize {
- return CGSize(width: self.frame.size.width, height: self.frame.size.height)
- }
-
- override init(frame: NSRect) {
- super.init(frame: CGRect(x: 0, y: 0, width: self.size, height: widgetSize.height))
- self.wantsLayer = true
-
- let xOffset: CGFloat = 1.0
-
- let labelView = NSTextField(frame: NSMakeRect(xOffset, 13, self.frame.size.width, 9))
- labelView.isEditable = false
- labelView.isSelectable = false
- labelView.isBezeled = false
- labelView.wantsLayer = true
- labelView.textColor = .labelColor
- labelView.backgroundColor = .controlColor
- labelView.canDrawSubviewsIntoLayer = true
- labelView.alignment = .natural
- labelView.font = NSFont.systemFont(ofSize: 8, weight: .light)
- labelView.stringValue = String(self.name.prefix(3)).uppercased()
- labelView.addSubview(NSView())
-
- let valueView = NSTextField(frame: NSMakeRect(xOffset, 3, self.frame.size.width, 10))
- valueView.isEditable = false
- valueView.isSelectable = false
- valueView.isBezeled = false
- valueView.wantsLayer = true
- valueView.textColor = .labelColor
- valueView.backgroundColor = .controlColor
- valueView.canDrawSubviewsIntoLayer = true
- valueView.alignment = .natural
- valueView.font = NSFont.systemFont(ofSize: 10, weight: .regular)
- valueView.stringValue = ""
- valueView.addSubview(NSView())
-
- self.labelView = labelView
- self.valueView = valueView
-
- self.addSubview(self.labelView)
- self.addSubview(self.valueView)
- }
-
- required init?(coder decoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- func start() {
- self.color = defaults.object(forKey: "\(name)_color") != nil ? defaults.bool(forKey: "\(name)_color") : false
- self.labelView.stringValue = String(self.name.prefix(3)).uppercased()
- self.initMenu()
- self.redraw()
- }
-
- func initMenu() {
- let color = NSMenuItem(title: "Color", action: #selector(toggleColor), keyEquivalent: "")
- color.state = self.color ? NSControl.StateValue.on : NSControl.StateValue.off
- color.target = self
-
- self.menus.append(color)
- }
-
- func redraw() {
- self.valueView.textColor = self.value.usageColor(color: self.color)
- self.display()
- }
-
- func setValue(data: [Double]) {
- let value: Double = data.first!
- if self.value != value && !value.isNaN {
- self.value = value
-
- self.valueView.stringValue = "\(Int(Float(value.roundTo(decimalPlaces: 2))! * 100))%"
- self.valueView.textColor = value.usageColor(color: self.color)
- }
- }
-
- @objc func toggleColor(_ sender: NSMenuItem) {
- sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on
- self.defaults.set(sender.state == NSControl.StateValue.on, forKey: "\(name)_color")
- self.color = sender.state == NSControl.StateValue.on
- self.redraw()
- }
-}
diff --git a/Stats/Widgets/Network/NetworkArrows.swift b/Stats/Widgets/Network/NetworkArrows.swift
deleted file mode 100644
index e6da08d2..00000000
--- a/Stats/Widgets/Network/NetworkArrows.swift
+++ /dev/null
@@ -1,93 +0,0 @@
-//
-// NetworkArrows.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 14.07.2019.
-// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-
-class NetworkArrowsView: NSView, Widget {
- public var menus: [NSMenuItem] = []
- public var size: CGFloat = 8
- public var name: String = "NetworkArrows"
-
- private var download: Int64 = 0
- private var upload: Int64 = 0
-
- override var intrinsicContentSize: CGSize {
- return CGSize(width: self.frame.size.width, height: self.frame.size.height)
- }
-
- override init(frame: NSRect) {
- self.download = 0
- self.upload = 0
- super.init(frame: CGRect(x: 0, y: 0, width: self.size, height: widgetSize.height))
- self.wantsLayer = true
- self.addSubview(NSView())
- }
-
- required init?(coder decoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- func start() {}
-
- override func draw(_ dirtyRect: NSRect) {
- super.draw(dirtyRect)
-
- let arrowAngle = CGFloat(Double.pi / 5)
- let pointerLineLength: CGFloat = 3.5
- let workingHeight: CGFloat = (self.frame.size.height - (widgetSize.margin * 2))
- let height: CGFloat = ((workingHeight - widgetSize.margin) / 2)
-
- let downloadArrow = NSBezierPath()
- let downloadStart = CGPoint(x: widgetSize.margin + (pointerLineLength/2), y: height + widgetSize.margin)
- let downloadEnd = CGPoint(x: widgetSize.margin + (pointerLineLength/2), y: widgetSize.margin)
-
- downloadArrow.addArrow(start: downloadStart, end: downloadEnd, pointerLineLength: pointerLineLength, arrowAngle: arrowAngle)
-
- if self.download >= 1_024 {
- NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 0.8).set()
- } else {
- NSColor.labelColor.set()
- }
- downloadArrow.lineWidth = 1
- downloadArrow.stroke()
- downloadArrow.close()
-
- let uploadArrow = NSBezierPath()
- let uploadStart = CGPoint(x: widgetSize.margin + (pointerLineLength/2), y: height + (widgetSize.margin * 2))
- let uploadEnd = CGPoint(x: widgetSize.margin + (pointerLineLength/2), y: (widgetSize.margin * 2) + (height * 2))
-
- uploadArrow.addArrow(start: uploadStart, end: uploadEnd, pointerLineLength: pointerLineLength, arrowAngle: arrowAngle)
-
- if self.upload >= 1_024 {
- NSColor.red.set()
- } else {
- NSColor.labelColor.set()
- }
- uploadArrow.lineWidth = 1
- uploadArrow.stroke()
- uploadArrow.close()
- }
-
- func redraw() {
- self.display()
- }
-
- func setValue(data: [Double]) {
- let download: Int64 = Int64(data[0])
- let upload: Int64 = Int64(data[1])
-
- if self.download != download {
- self.download = download
- }
- if self.upload != upload {
- self.upload = upload
- }
-
- self.redraw()
- }
-}
diff --git a/Stats/Widgets/Network/NetworkArrowsText.swift b/Stats/Widgets/Network/NetworkArrowsText.swift
deleted file mode 100644
index 5f21406c..00000000
--- a/Stats/Widgets/Network/NetworkArrowsText.swift
+++ /dev/null
@@ -1,127 +0,0 @@
-//
-// NetworkArrowsText.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 14.07.2019.
-// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-
-class NetworkArrowsTextView: NSView, Widget {
- public var menus: [NSMenuItem] = []
- public var size: CGFloat = widgetSize.width + 24
- public var name: String = "NetworkArrowsText"
-
- private var download: Int64 = 0
- private var upload: Int64 = 0
-
- var downloadValue: NSTextField = NSTextField()
- var uploadValue: NSTextField = NSTextField()
-
- override var intrinsicContentSize: CGSize {
- return CGSize(width: self.frame.size.width, height: self.frame.size.height)
- }
-
- override init(frame: NSRect) {
- self.download = 0
- self.upload = 0
- super.init(frame: CGRect(x: 0, y: 0, width: self.size, height: widgetSize.height))
- self.wantsLayer = true
- self.valueView()
- }
-
- required init?(coder decoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- func start() {}
-
- override func draw(_ dirtyRect: NSRect) {
- super.draw(dirtyRect)
-
- let arrowAngle = CGFloat(Double.pi / 5)
- let pointerLineLength: CGFloat = 3.5
- let workingHeight: CGFloat = (self.frame.size.height - (widgetSize.margin * 2))
- let height: CGFloat = ((workingHeight - widgetSize.margin) / 2)
-
- let downloadArrow = NSBezierPath()
- let downloadStart = CGPoint(x: widgetSize.margin + (pointerLineLength/2), y: height + widgetSize.margin)
- let downloadEnd = CGPoint(x: widgetSize.margin + (pointerLineLength/2), y: widgetSize.margin)
-
- downloadArrow.addArrow(start: downloadStart, end: downloadEnd, pointerLineLength: pointerLineLength, arrowAngle: arrowAngle)
-
- if self.download >= 1_024 {
- NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 0.8).set()
- } else {
- NSColor.labelColor.set()
- }
- downloadArrow.lineWidth = 1
- downloadArrow.stroke()
- downloadArrow.close()
-
- let uploadArrow = NSBezierPath()
- let uploadStart = CGPoint(x: widgetSize.margin + (pointerLineLength/2), y: height + (widgetSize.margin * 2))
- let uploadEnd = CGPoint(x: widgetSize.margin + (pointerLineLength/2), y: (widgetSize.margin * 2) + (height * 2))
-
- uploadArrow.addArrow(start: uploadStart, end: uploadEnd, pointerLineLength: pointerLineLength, arrowAngle: arrowAngle)
-
- if self.upload >= 1_024 {
- NSColor.red.set()
- } else {
- NSColor.labelColor.set()
- }
- uploadArrow.lineWidth = 1
- uploadArrow.stroke()
- uploadArrow.close()
- }
-
- func redraw() {
- self.display()
- }
-
- func setValue(data: [Double]) {
- let download: Int64 = Int64(data[0])
- let upload: Int64 = Int64(data[1])
-
- if self.download != download {
- self.download = download
- downloadValue.stringValue = Units(bytes: self.download).getReadableSpeed()
- }
- if self.upload != upload {
- self.upload = upload
- uploadValue.stringValue = Units(bytes: self.upload).getReadableSpeed()
- }
-
- self.redraw()
- }
-
- func valueView() {
- downloadValue = NSTextField(frame: NSMakeRect(widgetSize.margin, widgetSize.margin + 1, self.frame.size.width - widgetSize.margin, 9))
- downloadValue.isEditable = false
- downloadValue.isSelectable = false
- downloadValue.isBezeled = false
- downloadValue.wantsLayer = true
- downloadValue.textColor = .labelColor
- downloadValue.backgroundColor = .controlColor
- downloadValue.canDrawSubviewsIntoLayer = true
- downloadValue.alignment = .right
- downloadValue.font = NSFont.systemFont(ofSize: 9, weight: .light)
- downloadValue.stringValue = "0 KB/s"
-
- uploadValue = NSTextField(frame: NSMakeRect(widgetSize.margin, self.frame.size.height - 10, self.frame.size.width - widgetSize.margin, 9))
- uploadValue.isEditable = false
- uploadValue.isSelectable = false
- uploadValue.isBezeled = false
- uploadValue.wantsLayer = true
- uploadValue.textColor = .labelColor
- uploadValue.backgroundColor = .controlColor
- uploadValue.canDrawSubviewsIntoLayer = true
- uploadValue.alignment = .right
- uploadValue.font = NSFont.systemFont(ofSize: 9, weight: .light)
- uploadValue.stringValue = "0 KB/s"
-
- self.addSubview(downloadValue)
- self.addSubview(uploadValue)
- }
-}
diff --git a/Stats/Widgets/Network/NetworkDots.swift b/Stats/Widgets/Network/NetworkDots.swift
deleted file mode 100644
index 10d5c61b..00000000
--- a/Stats/Widgets/Network/NetworkDots.swift
+++ /dev/null
@@ -1,79 +0,0 @@
-//
-// NetworkDots.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 14.07.2019.
-// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-
-class NetworkDotsView: NSView, Widget {
- public var size: CGFloat = 12
- public var name: String = "NetworkDots"
- public var menus: [NSMenuItem] = []
-
- private var download: Int64 = 0
- private var upload: Int64 = 0
-
- override var intrinsicContentSize: CGSize {
- return CGSize(width: self.frame.size.width, height: self.frame.size.height)
- }
-
- override init(frame: NSRect) {
- self.download = 0
- self.upload = 0
- super.init(frame: CGRect(x: 0, y: 0, width: self.size, height: widgetSize.height))
- self.wantsLayer = true
- self.addSubview(NSView())
- }
-
- required init?(coder decoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- func start() {}
-
- override func draw(_ dirtyRect: NSRect) {
- super.draw(dirtyRect)
-
- let workingHeight: CGFloat = (self.frame.size.height - (widgetSize.margin * 2))
- let height: CGFloat = ((workingHeight - widgetSize.margin) / 2) - 1
-
- var uploadCircle = NSBezierPath()
- uploadCircle = NSBezierPath(ovalIn: CGRect(x: widgetSize.margin, y: height + (widgetSize.margin * 2) + 1, width: height, height: height))
- if self.upload >= 1_024 {
- NSColor.red.setFill()
- } else {
- NSColor.labelColor.setFill()
- }
- uploadCircle.fill()
-
- var downloadCircle = NSBezierPath()
- downloadCircle = NSBezierPath(ovalIn: CGRect(x: widgetSize.margin, y: widgetSize.margin, width: height, height: height))
- if self.download >= 1_024 {
- NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 0.8).setFill()
- } else {
- NSColor.labelColor.setFill()
- }
- downloadCircle.fill()
- }
-
- func redraw() {
- self.display()
- }
-
- func setValue(data: [Double]) {
- let download: Int64 = Int64(data[0])
- let upload: Int64 = Int64(data[1])
-
- if self.download != download {
- self.download = download
- }
- if self.upload != upload {
- self.upload = upload
- }
-
- self.redraw()
- }
-}
diff --git a/Stats/Widgets/Network/NetworkDotsText.swift b/Stats/Widgets/Network/NetworkDotsText.swift
deleted file mode 100644
index 9982a0af..00000000
--- a/Stats/Widgets/Network/NetworkDotsText.swift
+++ /dev/null
@@ -1,118 +0,0 @@
-//
-// NetworkDotsText.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 14.07.2019.
-// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-
-class NetworkDotsTextView: NSView, Widget {
- public var menus: [NSMenuItem] = []
- public var size: CGFloat = widgetSize.width + 26
- public var name: String = "NetworkDotsText"
-
- private var download: Int64 = 0
- private var upload: Int64 = 0
-
- var downloadValue: NSTextField = NSTextField()
- var uploadValue: NSTextField = NSTextField()
-
- override var intrinsicContentSize: CGSize {
- return CGSize(width: self.frame.size.width, height: self.frame.size.height)
- }
-
- override init(frame: NSRect) {
- self.download = 0
- self.upload = 0
- super.init(frame: CGRect(x: 0, y: 0, width: self.size, height: widgetSize.height))
- self.wantsLayer = true
- self.valueView()
- }
-
- required init?(coder decoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- func start() {}
-
- override func draw(_ dirtyRect: NSRect) {
- super.draw(dirtyRect)
-
- let workingHeight: CGFloat = (self.frame.size.height - (widgetSize.margin * 2))
- let height: CGFloat = ((workingHeight - widgetSize.margin) / 2) - 1
-
- var uploadCircle = NSBezierPath()
- uploadCircle = NSBezierPath(ovalIn: CGRect(x: widgetSize.margin, y: height + (widgetSize.margin * 2) + 1, width: height, height: height))
- if self.upload >= 1_024 {
- NSColor.red.setFill()
- } else {
- NSColor.labelColor.setFill()
- }
- uploadCircle.fill()
-
- var downloadCircle = NSBezierPath()
- downloadCircle = NSBezierPath(ovalIn: CGRect(x: widgetSize.margin, y: widgetSize.margin, width: height, height: height))
- if self.download >= 1_024 {
- NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 0.8).setFill()
- } else {
- NSColor.labelColor.setFill()
- }
- downloadCircle.fill()
- }
-
- func redraw() {
- self.display()
- }
-
- func setValue(data: [Double]) {
- let download: Int64 = Int64(data[0])
- let upload: Int64 = Int64(data[1])
- var changed: Bool = false
-
- if self.download != download {
- self.download = download
- downloadValue.stringValue = Units(bytes: self.download).getReadableSpeed()
- changed = true
- }
- if self.upload != upload {
- self.upload = upload
- uploadValue.stringValue = Units(bytes: self.upload).getReadableSpeed()
- changed = true
- }
-
- if changed {
- self.redraw()
- }
- }
-
- func valueView() {
- downloadValue = NSTextField(frame: NSMakeRect(widgetSize.margin, widgetSize.margin + 1, self.frame.size.width - widgetSize.margin, 9))
- downloadValue.isEditable = false
- downloadValue.isSelectable = false
- downloadValue.isBezeled = false
- downloadValue.wantsLayer = true
- downloadValue.textColor = .labelColor
- downloadValue.backgroundColor = .controlColor
- downloadValue.canDrawSubviewsIntoLayer = true
- downloadValue.alignment = .right
- downloadValue.font = NSFont.systemFont(ofSize: 9, weight: .light)
- downloadValue.stringValue = Units(bytes: self.download).getReadableSpeed()
-
- uploadValue = NSTextField(frame: NSMakeRect(widgetSize.margin, self.frame.size.height - 10, self.frame.size.width - widgetSize.margin, 9))
- uploadValue.isEditable = false
- uploadValue.isSelectable = false
- uploadValue.isBezeled = false
- uploadValue.wantsLayer = true
- uploadValue.textColor = .labelColor
- uploadValue.backgroundColor = .controlColor
- uploadValue.canDrawSubviewsIntoLayer = true
- uploadValue.alignment = .right
- uploadValue.font = NSFont.systemFont(ofSize: 9, weight: .light)
- uploadValue.stringValue = Units(bytes: self.upload).getReadableSpeed()
-
- self.addSubview(downloadValue)
- self.addSubview(uploadValue)
- }
-}
diff --git a/Stats/Widgets/Network/NetworkText.swift b/Stats/Widgets/Network/NetworkText.swift
deleted file mode 100644
index 56a4e0b4..00000000
--- a/Stats/Widgets/Network/NetworkText.swift
+++ /dev/null
@@ -1,81 +0,0 @@
-//
-// NetworkText.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 14.07.2019.
-// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-
-class NetworkTextView: NSView, Widget {
- public var menus: [NSMenuItem] = []
- public var size: CGFloat = widgetSize.width + 20
- public var name: String = "NetworkText"
-
- private var downloadValue: NSTextField = NSTextField()
- private var uploadValue: NSTextField = NSTextField()
-
- override var intrinsicContentSize: CGSize {
- return CGSize(width: self.frame.size.width, height: self.frame.size.height)
- }
-
- override init(frame: NSRect) {
- super.init(frame: CGRect(x: 0, y: 0, width: self.size, height: widgetSize.height))
- self.wantsLayer = true
- self.valueView()
- }
-
- required init?(coder decoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- func start() {}
-
- override func draw(_ dirtyRect: NSRect) {
- super.draw(dirtyRect)
- }
-
- func redraw() {
- self.display()
- }
-
- func setValue(data: [Double]) {
- let download: Int64 = Int64(data[0])
- let upload: Int64 = Int64(data[1])
-
- downloadValue.stringValue = Units(bytes: download).getReadableSpeed()
- uploadValue.stringValue = Units(bytes: upload).getReadableSpeed()
-
- self.redraw()
- }
-
- func valueView() {
- downloadValue = NSTextField(frame: NSMakeRect(widgetSize.margin, widgetSize.margin + 1, self.frame.size.width - widgetSize.margin, 9))
- downloadValue.isEditable = false
- downloadValue.isSelectable = false
- downloadValue.isBezeled = false
- downloadValue.wantsLayer = true
- downloadValue.textColor = .labelColor
- downloadValue.backgroundColor = .controlColor
- downloadValue.canDrawSubviewsIntoLayer = true
- downloadValue.alignment = .right
- downloadValue.font = NSFont.systemFont(ofSize: 9, weight: .light)
- downloadValue.stringValue = "0 KB/s"
-
- uploadValue = NSTextField(frame: NSMakeRect(widgetSize.margin, self.frame.size.height - 10, self.frame.size.width - widgetSize.margin, 9))
- uploadValue.isEditable = false
- uploadValue.isSelectable = false
- uploadValue.isBezeled = false
- uploadValue.wantsLayer = true
- uploadValue.textColor = .labelColor
- uploadValue.backgroundColor = .controlColor
- uploadValue.canDrawSubviewsIntoLayer = true
- uploadValue.alignment = .right
- uploadValue.font = NSFont.systemFont(ofSize: 9, weight: .light)
- uploadValue.stringValue = "0 KB/s"
-
- self.addSubview(downloadValue)
- self.addSubview(uploadValue)
- }
-}
diff --git a/Stats/Widgets/Sensors/SensorsWidget.swift b/Stats/Widgets/Sensors/SensorsWidget.swift
deleted file mode 100644
index 9fc6bc48..00000000
--- a/Stats/Widgets/Sensors/SensorsWidget.swift
+++ /dev/null
@@ -1,169 +0,0 @@
-//
-// Sensors.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 03/04/2020.
-// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-import Foundation
-
-class SensorsWidget: NSView, Widget {
- public var name: String = "Sensors"
- public var menus: [NSMenuItem] = []
-
- private var value: [Double] = []
- private var size: CGFloat = 24
- private var smallSize: CGFloat = 24
- private var bigSize: CGFloat = 36
- private var topValueView: NSTextField = NSTextField()
- private var bottomValueView: NSTextField = NSTextField()
- private let defaults = UserDefaults.standard
-
- private var color: Bool = true
-
- override var intrinsicContentSize: CGSize {
- return CGSize(width: self.frame.size.width, height: self.frame.size.height)
- }
-
- required init?(coder decoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- override init(frame: NSRect) {
- super.init(frame: CGRect(x: 0, y: 0, width: self.size, height: widgetSize.height))
- self.wantsLayer = true
-
- let xOffset: CGFloat = 1.0
-
- let topValueView = NSTextField(frame: NSMakeRect(xOffset, 11, self.frame.size.width, 10))
- topValueView.isEditable = false
- topValueView.isSelectable = false
- topValueView.isBezeled = false
- topValueView.wantsLayer = true
- topValueView.textColor = .labelColor
- topValueView.backgroundColor = .controlColor
- topValueView.canDrawSubviewsIntoLayer = true
- topValueView.alignment = .natural
- topValueView.font = NSFont.systemFont(ofSize: 9, weight: .light)
- topValueView.stringValue = ""
- topValueView.addSubview(NSView())
-
- let bottomValueView = NSTextField(frame: NSMakeRect(xOffset, 2, self.frame.size.width, 10))
- bottomValueView.isEditable = false
- bottomValueView.isSelectable = false
- bottomValueView.isBezeled = false
- bottomValueView.wantsLayer = true
- bottomValueView.textColor = .labelColor
- bottomValueView.backgroundColor = .controlColor
- bottomValueView.canDrawSubviewsIntoLayer = true
- bottomValueView.alignment = .natural
- bottomValueView.font = NSFont.systemFont(ofSize: 9, weight: .light)
- bottomValueView.stringValue = ""
- bottomValueView.addSubview(NSView())
-
- self.topValueView = topValueView
- self.bottomValueView = bottomValueView
-
- self.addSubview(self.topValueView)
- self.addSubview(self.bottomValueView)
- }
-
- func start() {
- self.color = defaults.object(forKey: "\(name)_color") != nil ? defaults.bool(forKey: "\(name)_color") : true
- self.initMenu()
- self.redraw()
- }
-
- func redraw() {
- if self.value.count == 2 {
- self.topValueView.textColor = self.value[0].temperatureColor(color: self.color)
- self.bottomValueView.textColor = self.value[1].temperatureColor(color: self.color)
- }
- self.display()
- }
-
- func setValue(data: [Double]) {
- if self.value != data && data.count == 4 {
- var width = self.smallSize
- let unit_1: String = String(UnicodeScalar(Int(data[1]))!)
- let unit_2: String = String(UnicodeScalar(Int(data[3]))!)
- self.value = [data[0], data[2]]
-
- if data[1] == 176 {
- self.topValueView.stringValue = "\(Int(self.value[0]))\(unit_1)"
- } else {
- width = bigSize
- switch value[0] {
- case 0..<10:
- self.topValueView.stringValue = "\(self.value[0].rounded(toPlaces: 2))\(unit_1)"
- break
- case 10..<100:
- self.topValueView.stringValue = "\(self.value[0].rounded(toPlaces: 1))\(unit_1)"
- break
- default:
- self.topValueView.stringValue = "\(Int(self.value[0]))\(unit_1)"
- break
- }
- }
-
- if data[3] == 176 {
- self.bottomValueView.stringValue = "\(Int(self.value[1]))\(unit_2)"
- } else {
- width = self.bigSize
- switch value[1] {
- case 0..<10:
- self.bottomValueView.stringValue = "\(self.value[1].rounded(toPlaces: 2))\(unit_2)"
- break
- case 10..<100:
- self.bottomValueView.stringValue = "\(self.value[1].rounded(toPlaces: 1))\(unit_2)"
- break
- default:
- self.bottomValueView.stringValue = "\(Int(self.value[1]))\(unit_2)"
- break
- }
- }
-
- if self.size != width {
- setWidth(width)
- }
-
- self.topValueView.textColor = self.value[0].temperatureColor(color: self.color)
- self.bottomValueView.textColor = self.value[1].temperatureColor(color: self.color)
- }
- }
-
- func initMenu() {
- let color = NSMenuItem(title: "Color", action: #selector(toggleColor), keyEquivalent: "")
- color.state = self.color ? NSControl.StateValue.on : NSControl.StateValue.off
- color.target = self
-
- self.menus.append(color)
- }
-
- @objc func toggleColor(_ sender: NSMenuItem) {
- sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on
- self.defaults.set(sender.state == NSControl.StateValue.on, forKey: "\(name)_color")
- self.color = sender.state == NSControl.StateValue.on
-
- if self.value.count == 2 {
- self.topValueView.textColor = self.value[0].temperatureColor(color: self.color)
- self.bottomValueView.textColor = self.value[1].temperatureColor(color: self.color)
- }
-
- self.redraw()
- }
-
- private func setWidth(_ width: CGFloat) {
- self.size = width
-
- self.topValueView.frame.size.width = width
- self.bottomValueView.frame.size.width = width
-
- self.frame.size.width = self.size
- if menuBar != nil {
- menuBar!.refresh()
- }
- }
-}
diff --git a/Stats/Widgets/Widget.swift b/Stats/Widgets/Widget.swift
deleted file mode 100644
index 8a88dfef..00000000
--- a/Stats/Widgets/Widget.swift
+++ /dev/null
@@ -1,55 +0,0 @@
-//
-// Widget.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 08.07.2019.
-// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-
-protocol Widget {
- var name: String { get set } // module name
- var menus: [NSMenuItem] { get } // module settings
-
- var intrinsicContentSize: CGSize { get }
-
- func start()
- func redraw()
-
- func setValue(data: [Double]) // pass value to widget
-}
-
-typealias WidgetType = Float
-struct Widgets {
- static let Mini: WidgetType = 0.0
- static let Sensors: WidgetType = 0.1
-
- static let Chart: WidgetType = 1.0
- static let ChartWithValue: WidgetType = 1.1
-
- static let NetworkDots: WidgetType = 2.0
- static let NetworkArrows: WidgetType = 2.1
- static let NetworkText: WidgetType = 2.2
- static let NetworkDotsWithText: WidgetType = 2.3
- static let NetworkArrowsWithText: WidgetType = 2.4
- static let NetworkChart: WidgetType = 2.5
-
- static let BarChart: WidgetType = 3.0
-
- static let Battery: WidgetType = 4.0
- static let BatteryPercentage: WidgetType = 4.1
- static let BatteryTime: WidgetType = 4.2
-}
-
-struct WidgetSize {
- let width: CGFloat = 32
- var height: CGFloat {
- get {
- let systemHeight = NSApplication.shared.mainMenu?.menuBarHeight
- return (systemHeight == 0 ? 22 : systemHeight)!
- }
- }
- let margin: CGFloat = 2
-}
-let widgetSize = WidgetSize()
diff --git a/Stats/libs/ChartMarker.swift b/Stats/libs/ChartMarker.swift
deleted file mode 100644
index c990eca4..00000000
--- a/Stats/libs/ChartMarker.swift
+++ /dev/null
@@ -1,47 +0,0 @@
-//
-// ChartMarker.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 03/09/2019.
-// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-import Charts
-
-class ChartMarker: MarkerView {
- var text = ""
-
- override func refreshContent(entry: ChartDataEntry, highlight: Highlight) {
- super.refreshContent(entry: entry, highlight: highlight)
- text = String(entry.y)
- }
-
- override func draw(context: CGContext, point: CGPoint) {
- super.draw(context: context, point: point)
-
- var drawAttributes = [NSAttributedString.Key : Any]()
- drawAttributes[.font] = NSFont.systemFont(ofSize: 13)
- drawAttributes[.foregroundColor] = NSColor.white
- drawAttributes[.backgroundColor] = NSColor.darkGray
-
- self.bounds.size = ("\(text)" as NSString).size(withAttributes: drawAttributes)
- self.offset = CGPoint(x: 0, y: self.bounds.size.height)
-
- let offset = self.offsetForDrawing(atPoint: point)
- drawText(text: "\(text)" as NSString, rect: CGRect(origin: CGPoint(x: point.x + offset.x, y: point.y + offset.y), size: self.bounds.size), withAttributes: drawAttributes)
- }
-
- func drawText(text: NSString, rect: CGRect, withAttributes attributes: [NSAttributedString.Key : Any]? = nil) {
- let size = text.size(withAttributes: attributes)
- let centeredRect = CGRect(x: rect.origin.x + (rect.size.width - size.width) / 2.0, y: rect.origin.y + (rect.size.height - size.height) / 2.0, width: size.width, height: size.height)
- text.draw(in: centeredRect, withAttributes: attributes)
- }
-}
-
-class ChartNetworkMarker: ChartMarker {
- override func refreshContent(entry: ChartDataEntry, highlight: Highlight) {
- super.refreshContent(entry: entry, highlight: highlight)
- text = Units(bytes: Int64(entry.y)).getReadableSpeed()
- }
-}
diff --git a/Stats/libs/Extensions.swift b/Stats/libs/Extensions.swift
deleted file mode 100755
index cc7953f5..00000000
--- a/Stats/libs/Extensions.swift
+++ /dev/null
@@ -1,340 +0,0 @@
-//
-// Extensions.swift
-// Stats
-//
-// Created by Serhiy Mytrovtsiy on 29/05/2019.
-// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
-//
-
-import Cocoa
-
-public enum Unit : Float {
- case byte = 1
- case kilobyte = 1024
- case megabyte = 1048576
- case gigabyte = 1073741824
-}
-
-public struct Units {
- public let bytes: Int64
-
- public init(bytes: Int64) {
- self.bytes = bytes
- }
-
- public var kilobytes: Double {
- return Double(bytes) / 1_024
- }
- public var megabytes: Double {
- return kilobytes / 1_024
- }
- public var gigabytes: Double {
- return megabytes / 1_024
- }
-
- public func getReadableTuple() -> (Double, String) {
- switch bytes {
- case 0..<1_024:
- return (0, "KB/s")
- case 1_024..<(1_024 * 1_024):
- return (Double(String(format: "%.2f", kilobytes))!, "KB/s")
- case 1_024..<(1_024 * 1_024 * 1_024):
- return (Double(String(format: "%.2f", megabytes))!, "MB/s")
- case (1_024 * 1_024 * 1_024)...Int64.max:
- return (Double(String(format: "%.2f", gigabytes))!, "GB/s")
- default:
- return (Double(String(format: "%.2f", kilobytes))!, "KB/s")
- }
- }
-
- public func getReadableSpeed() -> String {
- switch bytes {
- case 0..<1_024:
- return "0 KB/s"
- case 1_024..<(1_024 * 1_024):
- return String(format: "%.0f KB/s", kilobytes)
- case 1_024..<(1_024 * 1_024 * 100):
- return String(format: "%.1f MB/s", megabytes)
- case (1_024 * 1_024 * 100)..<(1_024 * 1_024 * 1_024):
- return String(format: "%.0f MB/s", megabytes)
- case (1_024 * 1_024 * 1_024)...Int64.max:
- return String(format: "%.1f GB/s", gigabytes)
- default:
- return String(format: "%.0f KB/s", kilobytes)
- }
- }
-
- public func getReadableMemory() -> String {
- switch bytes {
- case 0..<1_024:
- return "0 KB/s"
- case 1_024..<(1_024 * 1_024):
- return String(format: "%.0f KB", kilobytes)
- case 1_024..<(1_024 * 1_024 * 1_024):
- return String(format: "%.0f MB", megabytes)
- case (1_024 * 1_024 * 1_024)...Int64.max:
- return String(format: "%.2f GB", gigabytes)
- default:
- return String(format: "%.0f KB", kilobytes)
- }
- }
-}
-
-extension String {
- func condenseWhitespace() -> String {
- let components = self.components(separatedBy: .whitespacesAndNewlines)
- return components.filter { !$0.isEmpty }.joined(separator: " ")
- }
-
- var UTF8CString: UnsafeMutablePointer {
- return UnsafeMutablePointer(mutating: (self as NSString).utf8String!)
- }
-
- mutating func findAndCrop(pattern: String) -> String {
- let regex = try! NSRegularExpression(pattern: pattern)
- let stringRange = NSRange(location: 0, length: self.utf16.count)
- var line = self
-
- if let searchRange = regex.firstMatch(in: self, options: [], range: stringRange) {
- let start = self.index(self.startIndex, offsetBy: searchRange.range.lowerBound)
- let end = self.index(self.startIndex, offsetBy: searchRange.range.upperBound)
- let value = String(self[start.. Bool {
- return self.range(of: regex, options: .regularExpression, range: nil, locale: nil) != nil
- }
-
- func toUpperCase() -> String {
- return prefix(1).capitalized + dropFirst()
- }
- func toLowwerCase() -> String {
- return prefix(1).lowercased() + dropFirst()
- }
-
- subscript(offset: Int) -> Character { self[index(startIndex, offsetBy: offset)] }
-}
-
-extension Double {
- func roundTo(decimalPlaces: Int) -> String {
- return NSString(format: "%.\(decimalPlaces)f" as NSString, self) as String
- }
-
- func rounded(toPlaces places:Int) -> Double {
- let divisor = pow(10.0, Double(places))
- return (self * divisor).rounded() / divisor
- }
-
- func usageColor(reversed: Bool = false, color: Bool = false) -> NSColor {
- if !color {
- 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(color: Bool = false) -> NSColor {
- switch self {
- case 0.2...0.4:
- if !color {
- return NSColor.controlTextColor
- }
- return NSColor.systemOrange
- case 0.4...1:
- if self == 1 {
- return NSColor.controlTextColor
- }
- if !color {
- return NSColor.controlTextColor
- }
- return NSColor.systemGreen
- default:
- return NSColor.systemRed
- }
- }
-
- func temperatureColor(color: Bool = false) -> NSColor {
- switch self {
- case 0...70:
- return NSColor.controlTextColor
- case 70...90:
- if !color {
- return NSColor.controlTextColor
- }
- return NSColor.systemOrange
- default:
- if !color {
- return NSColor.controlTextColor
- }
- return NSColor.systemRed
- }
- }
-
- func splitAtDecimal() -> [Int64] {
- return "\(self)".split(separator: ".").map{Int64($0)!}
- }
-
- 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) h"
- case (nil, nil, let x?):
- return "\(x) sec"
- case (let x?, nil, let z?):
- return "\(x) h \(z) sec"
- case (let x?, let y?, nil):
- return "\(x) h \(y) min"
- case (let x?, let y?, let z?):
- return "\(x) h \(y) min \(z) sec"
- default:
- return "n/a"
- }
- }
-}
-
-extension NSBezierPath {
- func addArrow(start: CGPoint, end: CGPoint, pointerLineLength: CGFloat, arrowAngle: CGFloat) {
- self.move(to: start)
- self.line(to: end)
-
- let startEndAngle = atan((end.y - start.y) / (end.x - start.x)) + ((end.x - start.x) < 0 ? CGFloat(Double.pi) : 0)
- let arrowLine1 = CGPoint(x: end.x + pointerLineLength * cos(CGFloat(Double.pi) - startEndAngle + arrowAngle), y: end.y - pointerLineLength * sin(CGFloat(Double.pi) - startEndAngle + arrowAngle))
- let arrowLine2 = CGPoint(x: end.x + pointerLineLength * cos(CGFloat(Double.pi) - startEndAngle - arrowAngle), y: end.y - pointerLineLength * sin(CGFloat(Double.pi) - startEndAngle - arrowAngle))
-
- self.line(to: arrowLine1)
- self.move(to: end)
- self.line(to: arrowLine2)
- }
-}
-
-extension NSColor {
- convenience init(hexString: String, alpha: CGFloat = 1.0) {
- let hexString: String = hexString.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
- let scanner = Scanner(string: hexString)
- if (hexString.hasPrefix("#")) {
- scanner.scanLocation = 1
- }
- var color: UInt32 = 0
- scanner.scanHexInt32(&color)
- let mask = 0x000000FF
- let r = Int(color >> 16) & mask
- let g = Int(color >> 8) & mask
- let b = Int(color) & mask
- let red = CGFloat(r) / 255.0
- let green = CGFloat(g) / 255.0
- let blue = CGFloat(b) / 255.0
- self.init(red:red, green:green, blue:blue, alpha:alpha)
- }
-
- func toHexString() -> String {
- var r:CGFloat = 0
- var g:CGFloat = 0
- var b:CGFloat = 0
- var a:CGFloat = 0
- getRed(&r, green: &g, blue: &b, alpha: &a)
- let rgb:Int = (Int)(r*255)<<16 | (Int)(g*255)<<8 | (Int)(b*255)<<0
- return String(format:"#%06x", rgb)
- }
-}
-
-extension URL {
- func checkFileExist() -> Bool {
- return FileManager.default.fileExists(atPath: self.path)
- }
-}
-
-extension FourCharCode {
- init(fromString str: String) {
- precondition(str.count == 4)
-
- self = str.utf8.reduce(0) { sum, character in
- return sum << 8 | UInt32(character)
- }
- }
-
- func toString() -> String {
- return String(describing: UnicodeScalar(self >> 24 & 0xff)!) +
- String(describing: UnicodeScalar(self >> 16 & 0xff)!) +
- String(describing: UnicodeScalar(self >> 8 & 0xff)!) +
- String(describing: UnicodeScalar(self & 0xff)!)
- }
-}
-
-extension UInt32 {
- init(bytes: (UInt8, UInt8, UInt8, UInt8)) {
- self = UInt32(bytes.0) << 24 | UInt32(bytes.1) << 16 | UInt32(bytes.2) << 8 | UInt32(bytes.3)
- }
-}
-
-extension FloatingPoint {
- init?(_ bytes: [UInt8]) {
- self = bytes.withUnsafeBytes {
- return $0.load(fromByteOffset: 0, as: Self.self)
- }
- }
-}
-
-extension NSMenuItem {
- private static var _extraString = [String:String]()
-
- var extraString: String {
- get {
- let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self))
- return NSMenuItem._extraString[tmpAddress] ?? ""
- }
- set(newValue) {
- let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self))
- NSMenuItem._extraString[tmpAddress] = newValue
- }
- }
-}
-
-extension Character {
- func unicodeScalarCodePoint() -> UInt32 {
- let characterString = String(self)
- let scalars = characterString.unicodeScalars
-
- return scalars[scalars.startIndex].value
- }
-}
diff --git a/StatsKit/Charts.swift b/StatsKit/Charts.swift
new file mode 100644
index 00000000..6b294b7b
--- /dev/null
+++ b/StatsKit/Charts.swift
@@ -0,0 +1,99 @@
+//
+// Chart.swift
+// StatsKit
+//
+// Created by Serhiy Mytrovtsiy on 17/04/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+
+public enum chart_t: Int {
+ case line = 0
+ case bar = 1
+
+ init?(value: Int) {
+ self.init(rawValue: value)
+ }
+}
+
+public class LineChartView: NSView {
+ public var points: [Double]? = nil
+ public var transparent: Bool = true
+
+ public init(frame: NSRect, num: Int) {
+ self.points = Array(repeating: 0, count: num)
+ super.init(frame: frame)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ public override func draw(_ dirtyRect: NSRect) {
+ super.draw(dirtyRect)
+
+ if self.points?.count == 0 {
+ return
+ }
+
+ var lineColor: NSColor = NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 1.0)
+ var gradientColor: NSColor = NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 0.5)
+ if !self.transparent {
+ lineColor = NSColor(hexString: "#5c91f4")
+ gradientColor = NSColor(hexString: "#5c91f4")
+ }
+
+ let context = NSGraphicsContext.current!.cgContext
+ context.setShouldAntialias(true)
+ let height: CGFloat = self.frame.size.height - self.frame.origin.y - 0.5
+ let xRatio: CGFloat = self.frame.size.width / CGFloat(self.points!.count)
+
+ let columnXPoint = { (point: Int) -> CGFloat in
+ return (CGFloat(point) * xRatio) + dirtyRect.origin.x
+ }
+ let columnYPoint = { (point: Int) -> CGFloat in
+ return CGFloat((CGFloat(truncating: self.points![point] as NSNumber) * height)) + dirtyRect.origin.y + 0.5
+ }
+
+ let linePath = NSBezierPath()
+ let x: CGFloat = columnXPoint(0)
+ let y: CGFloat = columnYPoint(0)
+ linePath.move(to: CGPoint(x: x, y: y))
+
+ for i in 1..
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+ NSHumanReadableCopyright
+ Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+
+
diff --git a/Stats/libs/SMC.swift b/StatsKit/SMC.swift
similarity index 93%
rename from Stats/libs/SMC.swift
rename to StatsKit/SMC.swift
index 07803427..63538fd1 100644
--- a/Stats/libs/SMC.swift
+++ b/StatsKit/SMC.swift
@@ -1,8 +1,11 @@
//
// SMC.swift
-// Stats
+// StatsKit
//
// Created by Serhiy Mytrovtsiy on 05/04/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
//
@@ -85,14 +88,10 @@ struct SMCVal_t {
}
}
-class SMCService {
+public class SMCService {
private var conn: io_connect_t = 0;
- init() {
-
- }
-
- public func open() -> kern_return_t {
+ public init() {
var result: kern_return_t
var iterator: io_iterator_t = 0
let device: io_object_t
@@ -101,24 +100,22 @@ class SMCService {
result = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDictionary, &iterator)
if (result != kIOReturnSuccess) {
print("Error IOServiceGetMatchingServices(): " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
- return result
+ return
}
device = IOIteratorNext(iterator)
IOObjectRelease(iterator)
if (device == 0) {
print("Error IOIteratorNext(): " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
- return kIOReturnError
+ return
}
result = IOServiceOpen(device, mach_task_self_, 0, &conn)
IOObjectRelease(device)
if (result != kIOReturnSuccess) {
print("Error IOServiceOpen(): " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
- return result
+ return
}
-
- return kIOReturnSuccess
}
public func close() -> kern_return_t{
@@ -208,7 +205,7 @@ class SMCService {
public func getAllKeys() -> [String] {
var list: [String] = []
- let keysNum: Double? = smc.getValue("#KEY")
+ let keysNum: Double? = self.getValue("#KEY")
if keysNum == nil {
print("ERROR no keys count found")
return list
@@ -236,16 +233,3 @@ class SMCService {
return list
}
}
-
-//int64_t GetCPUFrequency() {
-// int mib[2];
-// unsigned int freq;
-// size_t len;
-//
-// mib[0] = CTL_HW;
-// mib[1] = HW_CPU_FREQ;
-// len = sizeof(freq);
-// sysctl(mib, 2, &freq, &len, NULL, 0);
-//
-// return freq;
-//}
diff --git a/StatsKit/StatsKit.h b/StatsKit/StatsKit.h
new file mode 100644
index 00000000..acae1b22
--- /dev/null
+++ b/StatsKit/StatsKit.h
@@ -0,0 +1,15 @@
+//
+// StatsKit.h
+// StatsKit
+//
+// Created by Serhiy Mytrovtsiy on 14/04/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+#import
+
+FOUNDATION_EXPORT double StatsKitVersionNumber;
+FOUNDATION_EXPORT const unsigned char StatsKitVersionString[];
diff --git a/StatsKit/SystemKit.swift b/StatsKit/SystemKit.swift
new file mode 100644
index 00000000..db623e14
--- /dev/null
+++ b/StatsKit/SystemKit.swift
@@ -0,0 +1,383 @@
+//
+// SystemKit.swift
+// Stats
+//
+// Created by Serhiy Mytrovtsiy on 13/04/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+import os.log
+
+public enum deviceType: Int {
+ case unknown = -1
+ case macMini = 1
+ case macPro = 2
+ case imac = 3
+ case imacpro = 4
+ case macbook = 5
+ case macbookAir = 6
+ case macbookPro = 7
+}
+
+public struct model_s {
+ public let name: String
+ public let year: Int
+ public let type: deviceType
+ public var icon: NSImage = NSImage(named: NSImage.Name("imacPro"))!
+}
+
+public struct os_s {
+ public let name: String
+ public let version: OperatingSystemVersion
+ public let build: String
+}
+
+public struct cpu_s {
+ public let physicalCores: Int8
+ public let logicalCores: Int8
+ public let name: String
+}
+
+public struct ram_s {
+ public var active: Double
+ public var inactive: Double
+ public var wired: Double
+ public var compressed: Double
+ public var total: Double
+ public var used: Double
+}
+
+public struct gpu_s {
+ public let name: String
+}
+
+public struct disk_s {
+ public let name: String
+ public let model: String
+ public let size: Int64
+}
+
+public struct info_s {
+ public var cpu: cpu_s? = nil
+ public var ram: ram_s? = nil
+ public var gpu: [gpu_s]? = nil
+ public var disk: disk_s? = nil
+}
+
+public struct device_s {
+ public var model: model_s = model_s(name: "Unknown", year: 2020, type: .unknown)
+ public var os: os_s? = nil
+ public var info: info_s? = info_s()
+}
+
+public class SystemKit {
+ public var device: device_s = device_s()
+ private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "SystemKit")
+
+ public init() {
+ if let modelName = self.modelName() {
+ if let modelInfo = deviceDict[modelName] {
+ self.device.model = modelInfo
+ self.device.model.icon = self.getIcon(type: self.device.model.type)
+ } else {
+ os_log(.error, log: self.log, "unknown device %s", modelName)
+ }
+ }
+
+ let procInfo = ProcessInfo()
+ let systemVersion = procInfo.operatingSystemVersion
+ let build = procInfo.operatingSystemVersionString.split(separator: "(")[1].replacingOccurrences(of: "Build ", with: "").replacingOccurrences(of: ")", with: "")
+
+ self.device.os = os_s(name: osDict[systemVersion.minorVersion] ?? "Unknown", version: systemVersion, build: build)
+
+ self.device.info?.cpu = self.getCPUInfo()
+ self.device.info?.ram = self.getRamInfo()
+ self.device.info?.gpu = self.getGPUInfo()
+ self.device.info?.disk = self.getDiskInfo()
+ }
+
+ public func modelName() -> String? {
+ var mib = [CTL_HW, HW_MODEL]
+ var size = MemoryLayout.size
+
+ let pointer = UnsafeMutablePointer.allocate(capacity: 1)
+ defer {
+ pointer.deallocate()
+ }
+ let result = sysctl(&mib, u_int(mib.count), pointer, &size, nil, 0)
+
+ if result == KERN_SUCCESS {
+ return String(cString: UnsafeRawPointer(pointer).assumingMemoryBound(to: CChar.self))
+ }
+
+ os_log(.error, log: self.log, "error call sysctl(): %v", (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
+ return nil
+ }
+
+ private func getCPUInfo() -> cpu_s? {
+ var sizeOfName = 0
+ sysctlbyname("machdep.cpu.brand_string", nil, &sizeOfName, nil, 0)
+ var nameCharts = [CChar](repeating: 0, count: sizeOfName)
+ sysctlbyname("machdep.cpu.brand_string", &nameCharts, &sizeOfName, nil, 0)
+ var name = String(cString: nameCharts)
+ if name != "" {
+ name = name.replacingOccurrences(of: "(TM)", with: "")
+ name = name.replacingOccurrences(of: "(R)", with: "")
+ name = name.replacingOccurrences(of: "CPU", with: "")
+ name = name.replacingOccurrences(of: " @ ", with: "")
+ }
+ print(name)
+
+ var size = UInt32(MemoryLayout.size / MemoryLayout.size)
+ let hostInfo = host_basic_info_t.allocate(capacity: 1)
+ defer {
+ hostInfo.deallocate()
+ }
+
+ let result = hostInfo.withMemoryRebound(to: integer_t.self, capacity: Int(size)) {
+ host_info(mach_host_self(), HOST_BASIC_INFO, $0, &size)
+ }
+
+ if result == KERN_SUCCESS {
+ let data = hostInfo.move()
+ return cpu_s(physicalCores: Int8(data.physical_cpu), logicalCores: Int8(data.logical_cpu), name: name)
+ }
+
+ os_log(.error, log: self.log, "hostInfo.withMemoryRebound(): %v", (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
+ return nil
+ }
+
+ private func getGPUInfo() -> [gpu_s]? {
+ var gpu: [gpu_s] = []
+ var iterator: io_iterator_t = 0
+ var device: io_object_t = 1
+
+ let result = IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching("IOPCIDevice"), &iterator)
+ if result == kIOReturnSuccess {
+
+ while device != 0 {
+ device = IOIteratorNext(iterator)
+ var serviceDictionary: Unmanaged?
+
+ if (IORegistryEntryCreateCFProperties(device, &serviceDictionary, kCFAllocatorDefault, 0) != kIOReturnSuccess) {
+ IOObjectRelease(device)
+ continue
+ }
+
+ if let props = serviceDictionary {
+ let dict = props.takeRetainedValue() as NSDictionary
+
+ if let d = dict.object(forKey: "IOName") as? String {
+ if d == "display" {
+ let model = dict.object(forKey: "model") as! Data
+ let modelName = String(data: model, encoding: .ascii)!.replacingOccurrences(of: "\0", with: "")
+ gpu.append(gpu_s(name: modelName))
+ }
+ }
+ }
+ }
+ }
+
+ return gpu
+ }
+
+ private func getDiskInfo() -> disk_s? {
+ var disk: DADisk? = nil
+
+ let keys: [URLResourceKey] = [.volumeNameKey]
+ let paths = FileManager.default.mountedVolumeURLs(includingResourceValuesForKeys: keys)!
+ if let session = DASessionCreate(kCFAllocatorDefault) {
+ for url in paths {
+ if url.pathComponents.count == 1 {
+ disk = DADiskCreateFromVolumePath(kCFAllocatorDefault, session, url as CFURL)
+ }
+ }
+ }
+
+ if disk == nil {
+ os_log(.error, log: self.log, "empty disk after fetching list")
+ return nil
+ }
+
+ if let diskDescription = DADiskCopyDescription(disk!) {
+ if let dict = diskDescription as? [String: AnyObject] {
+ if let removable = dict[kDADiskDescriptionMediaRemovableKey as String] {
+ if removable as! Bool {
+ return nil
+ }
+ }
+
+ var name: String = ""
+ var model: String = ""
+ var size: Int64 = 0
+
+ if let mediaName = dict[kDADiskDescriptionMediaNameKey as String] {
+ name = mediaName as! String
+ }
+ if let deviceModel = dict[kDADiskDescriptionDeviceModelKey as String] {
+ model = (deviceModel as! String).trimmingCharacters(in: .whitespacesAndNewlines)
+ }
+ if let mediaSize = dict[kDADiskDescriptionMediaSizeKey as String] {
+ size = Int64(truncating: mediaSize as! NSNumber)
+ }
+
+ return disk_s(name: name, model: model, size: size)
+ }
+ }
+
+ return nil
+ }
+
+ public func getRamInfo() -> ram_s? {
+ var vmStats = host_basic_info()
+ var count = UInt32(MemoryLayout.size / MemoryLayout.size)
+ var totalSize: Double = 0
+
+ var result: kern_return_t = withUnsafeMutablePointer(to: &vmStats) {
+ $0.withMemoryRebound(to: integer_t.self, capacity: Int(count)) {
+ host_info(mach_host_self(), HOST_BASIC_INFO, $0, &count)
+ }
+ }
+
+ if result == KERN_SUCCESS {
+ totalSize = Double(vmStats.max_mem)
+ } else {
+ os_log(.error, log: self.log, "host_basic_info(): %v", (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
+ return nil
+ }
+
+ var pageSize: vm_size_t = 0
+ result = withUnsafeMutablePointer(to: &pageSize) { (size) -> kern_return_t in
+ host_page_size(mach_host_self(), size)
+ }
+
+ var stats = vm_statistics64()
+ count = UInt32(MemoryLayout.size / MemoryLayout.size)
+
+ result = withUnsafeMutablePointer(to: &stats) {
+ $0.withMemoryRebound(to: integer_t.self, capacity: 1) {
+ host_statistics64(mach_host_self(), HOST_VM_INFO64, $0, &count)
+ }
+ }
+
+ if result == KERN_SUCCESS {
+ let active = Double(stats.active_count) * Double(PAGE_SIZE)
+ let inactive = Double(stats.inactive_count) * Double(PAGE_SIZE)
+ let wired = Double(stats.wire_count) * Double(PAGE_SIZE)
+ let compressed = Double(stats.compressor_page_count) * Double(PAGE_SIZE)
+
+ return ram_s(
+ active: active,
+ inactive: inactive,
+ wired: wired,
+ compressed: compressed,
+ total: totalSize,
+ used: active + wired + compressed
+ )
+ }
+
+ os_log(.error, log: self.log, "host_statistics64(): %v", (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
+ return nil
+ }
+
+ private func getIcon(type: deviceType) -> NSImage {
+ var icon: NSImage = NSImage()
+
+ switch type {
+ case .macMini:
+ icon = NSImage(named: NSImage.Name("macMini"))!
+ break
+ case .imacpro:
+ icon = NSImage(named: NSImage.Name("imacPro"))!
+ break
+ case .imac:
+ icon = NSImage(named: NSImage.Name("imac"))!
+ break
+ case .macbook, .macbookAir:
+ icon = NSImage(named: NSImage.Name("macbookAir"))!
+ break
+ case .macbookPro:
+ icon = NSImage(named: NSImage.Name("macbookPro"))!
+ break
+ default:
+ icon = NSImage(named: NSImage.Name("imacPro"))!
+ break
+ }
+
+ return icon
+ }
+}
+
+let deviceDict: [String: model_s] = [
+ // Mac Mini
+ "MacMini6,1": model_s(name: "Mac mini (Late 2012)", year: 2012, type: .macMini),
+ "Macmini6,2": model_s(name: "Mac mini (Late 2012)", year: 2012, type: .macMini),
+ "Macmini7,1": model_s(name: "Mac mini (Late 2014)", year: 2012, type: .macMini),
+ "Macmini8,1": model_s(name: "Mac mini (Late 2018)", year: 2012, type: .macMini),
+
+ // Mac Pro
+ "MacPro6,1": model_s(name: "Mac Pro (Late 2013)", year: 2012, type: .macPro),
+ "MacPro7,1": model_s(name: "Mac Pro (2019)", year: 2012, type: .macPro),
+
+ // iMac
+ "iMac13,2": model_s(name: "iMac 27-Inch (Late 2012)", year: 2012, type: .imac),
+ "iMac14,2": model_s(name: "iMac 27-Inch (Late 2013)", year: 2012, type: .imac),
+ "iMac15,1": model_s(name: "iMac 27-Inch (5K, Late 2014)", year: 2012, type: .imac),
+ "iMac17,1": model_s(name: "iMac 27-Inch (5K, Late 2015)", year: 2012, type: .imac),
+ "iMac18,3": model_s(name: "iMac 27-Inch (5K, Mid 2017)", year: 2012, type: .imac),
+ "iMac19,1": model_s(name: "iMac 27-Inch (5K, 2019)", year: 2012, type: .imac),
+
+ // iMac Pro
+ "iMacPro1,1": model_s(name: "iMac Pro (5K, Late 2017)", year: 2017, type: .imacpro),
+
+ // MacBook
+ "MacBook8,1": model_s(name: "MacBook (Early 2015)", year: 2015, type: .macbook),
+ "MacBook9,1": model_s(name: "MacBook (Early 2016)", year: 2016, type: .macbook),
+ "MacBook10,1": model_s(name: "MacBook (Early 2017)", year: 2017, type: .macbook),
+
+ // MacBook Air
+ "MacBookAir5,1": model_s(name: "MacBook Air 11\" (Mid 2012)", year: 2012, type: .macbookAir),
+ "MacBookAir5,2": model_s(name: "MacBook Air 13\" (Mid 2012)", year: 2012, type: .macbookAir),
+ "MacBookAir6,1": model_s(name: "MacBook Air 11\" (Early 2014)", year: 2014, type: .macbookAir),
+ "MacBookAir6,2": model_s(name: "MacBook Air 13\" (Early 2014)", year: 2014, type: .macbookAir),
+ "MacBookAir7,1": model_s(name: "MacBook Air 11\" (Early 2015)", year: 2015, type: .macbookAir),
+ "MacBookAir7,2": model_s(name: "MacBook Air 13\" (Early 2015)", year: 2015, type: .macbookAir),
+ "MacBookAir8,1": model_s(name: "MacBook Air 13\" (2018)", year: 2018, type: .macbookAir),
+ "MacBookAir8,2": model_s(name: "MacBook Air 13\" (2019)", year: 2019, type: .macbookAir),
+ "MacBookAir9,1": model_s(name: "MacBook Air 13\" (2020)", year: 2020, type: .macbookAir),
+
+ // MacBook Pro
+ "MacBookPro9,1": model_s(name: "MacBook Pro 15\" (Mid 2012)", year: 2012, type: .macbookPro),
+ "MacBookPro9,2": model_s(name: "MacBook Pro 13\" (Mid 2012)", year: 2012, type: .macbookPro),
+ "MacBookPro10,1": model_s(name: "MacBook Pro 15\" (Retina, Mid 2012)", year: 2012, type: .macbookPro),
+ "MacBookPro10,2": model_s(name: "MacBook Pro 13\" (Retina, Late 2012)", year: 2012, type: .macbookPro),
+ "MacBookPro11,1": model_s(name: "MacBook Pro 13\" (Retina, Mid 2014)", year: 2014, type: .macbookPro),
+ "MacBookPro11,2": model_s(name: "MacBook Pro 15\" (Retina, Mid 2014)", year: 2014, type: .macbookPro),
+ "MacBookPro11,3": model_s(name: "MacBook Pro 15\" (Retina, Mid 2014)", year: 2014, type: .macbookPro),
+ "MacBookPro11,4": model_s(name: "MacBook Pro 15\" (Retina, Mid 2015)", year: 2015, type: .macbookPro),
+ "MacBookPro11,5": model_s(name: "MacBook Pro 15\" (Retina, Mid 2015)", year: 2015, type: .macbookPro),
+ "MacBookPro12,1": model_s(name: "MacBook Pro 13\" (Mid 2015)", year: 2015, type: .macbookPro),
+ "MacBookPro13,1": model_s(name: "MacBook Pro 13\" (Late 2016)", year: 2016, type: .macbookPro),
+ "MacBookPro13,2": model_s(name: "MacBook Pro 13\" (Late 2016)", year: 2016, type: .macbookPro),
+ "MacBookPro13,3": model_s(name: "MacBook Pro 15\" (Late 2016)", year: 2016, type: .macbookPro),
+ "MacBookPro14,1": model_s(name: "MacBook Pro 13\" (Mid 2017)", year: 2017, type: .macbookPro),
+ "MacBookPro14,2": model_s(name: "MacBook Pro 13\" (Mid 2017)", year: 2017, type: .macbookPro),
+ "MacBookPro14,3": model_s(name: "MacBook Pro 15\" (Mid 2017)", year: 2017, type: .macbookPro),
+ "MacBookPro15,1": model_s(name: "MacBook Pro 15\" (Mid 2018)", year: 2018, type: .macbookPro),
+ "MacBookPro15,2": model_s(name: "MacBook Pro 13\" (Mid 2019)", year: 2019, type: .macbookPro),
+ "MacBookPro15,3": model_s(name: "MacBook Pro 15\" (Mid 2019)", year: 2019, type: .macbookPro),
+ "MacBookPro15,4": model_s(name: "MacBook Pro 13\" (Mid 2019)", year: 2019, type: .macbookPro),
+ "MacBookPro16,1": model_s(name: "MacBook Pro 16\" (Late 2019)", year: 2019, type: .macbookPro),
+ "MacBookPro16,2": model_s(name: "MacBook Pro 13\" (Mid 2020)", year: 2019, type: .macbookPro),
+ "MacBookPro16,3": model_s(name: "MacBook Pro 13\" (Mid 2020)", year: 2020, type: .macbookPro),
+]
+
+let osDict: [Int: String] = [
+ 13: "High Sierra",
+ 14: "Mojave",
+ 15: "Catalina",
+]
diff --git a/StatsKit/extensions.swift b/StatsKit/extensions.swift
new file mode 100644
index 00000000..99567cbc
--- /dev/null
+++ b/StatsKit/extensions.swift
@@ -0,0 +1,525 @@
+//
+// extensions.swift
+// StatsKit
+//
+// Created by Serhiy Mytrovtsiy on 10/04/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+
+public enum Unit : Float {
+ case byte = 1
+ case kilobyte = 1024
+ case megabyte = 1048576
+ case gigabyte = 1073741824
+}
+
+public struct Units {
+ public let bytes: Int64
+
+ public init(bytes: Int64) {
+ self.bytes = bytes
+ }
+
+ public var kilobytes: Double {
+ return Double(bytes) / 1_024
+ }
+ public var megabytes: Double {
+ return kilobytes / 1_024
+ }
+ public var gigabytes: Double {
+ return megabytes / 1_024
+ }
+
+ public func getReadableTuple() -> (String, String) {
+ switch bytes {
+ case 0..<1_024:
+ return ("0", "KB/s")
+ case 1_024..<(1_024 * 1_024):
+ return (String(format: "%.0f", kilobytes), "KB/s")
+ case 1_024..<(1_024 * 1_024 * 100):
+ return (String(format: "%.1f", megabytes), "MB/s")
+ case (1_024 * 1_024 * 100)..<(1_024 * 1_024 * 1_024):
+ return (String(format: "%.0f", megabytes), "MB/s")
+ case (1_024 * 1_024 * 1_024)...Int64.max:
+ return (String(format: "%.1f", gigabytes), "GB/s")
+ default:
+ return (String(format: "%.0f", kilobytes), "KB/s")
+ }
+ }
+
+ public func getReadableSpeed() -> String {
+ switch bytes {
+ case 0..<1_024:
+ return "0 KB/s"
+ case 1_024..<(1_024 * 1_024):
+ return String(format: "%.0f KB/s", kilobytes)
+ case 1_024..<(1_024 * 1_024 * 100):
+ return String(format: "%.1f MB/s", megabytes)
+ case (1_024 * 1_024 * 100)..<(1_024 * 1_024 * 1_024):
+ return String(format: "%.0f MB/s", megabytes)
+ case (1_024 * 1_024 * 1_024)...Int64.max:
+ return String(format: "%.1f GB/s", gigabytes)
+ default:
+ return String(format: "%.0f KB/s", kilobytes)
+ }
+ }
+
+ public func getReadableMemory() -> String {
+ switch bytes {
+ case 0..<1_024:
+ return "0 KB"
+ case 1_024..<(1_024 * 1_024):
+ return String(format: "%.0f KB", kilobytes)
+ case 1_024..<(1_024 * 1_024 * 1_024):
+ return String(format: "%.0f MB", megabytes)
+ case (1_024 * 1_024 * 1_024)...Int64.max:
+ return String(format: "%.2f GB", gigabytes)
+ default:
+ return String(format: "%.0f KB", kilobytes)
+ }
+ }
+}
+
+extension String: LocalizedError {
+ public var errorDescription: String? { return self }
+
+ public func widthOfString(usingFont font: NSFont) -> CGFloat {
+ let fontAttributes = [NSAttributedString.Key.font: font]
+ let size = self.size(withAttributes: fontAttributes)
+ return size.width
+ }
+
+ public func heightOfString(usingFont font: NSFont) -> CGFloat {
+ let fontAttributes = [NSAttributedString.Key.font: font]
+ let size = self.size(withAttributes: fontAttributes)
+ return size.height
+ }
+
+ public func sizeOfString(usingFont font: NSFont) -> CGSize {
+ let fontAttributes = [NSAttributedString.Key.font: font]
+ return self.size(withAttributes: fontAttributes)
+ }
+
+ public func condenseWhitespace() -> String {
+ let components = self.components(separatedBy: .whitespacesAndNewlines)
+ return components.filter { !$0.isEmpty }.joined(separator: " ")
+ }
+
+ public mutating func findAndCrop(pattern: String) -> String {
+ let regex = try! NSRegularExpression(pattern: pattern)
+ let stringRange = NSRange(location: 0, length: self.utf16.count)
+ var line = self
+
+ if let searchRange = regex.firstMatch(in: self, options: [], range: stringRange) {
+ let start = self.index(self.startIndex, offsetBy: searchRange.range.lowerBound)
+ let end = self.index(self.startIndex, offsetBy: searchRange.range.upperBound)
+ let value = String(self[start.. String {
+ return NSString(format: "%.\(decimalPlaces)f" as NSString, self) as String
+ }
+
+ func rounded(toPlaces places:Int) -> Double {
+ let divisor = pow(10.0, Double(places))
+ return (self * divisor).rounded() / divisor
+ }
+
+ func usageColor(reversed: Bool = false, color: NSColor = NSColor(hexString: "#5c91f4")) -> NSColor {
+ var firstColor = color
+ if UserDefaults.standard.object(forKey: "color") != nil {
+ firstColor = NSColor(hexString: UserDefaults.standard.string(forKey: "color")!)
+ }
+
+ let secondColor: NSColor = NSColor.systemOrange
+ let thirdColor: NSColor = NSColor.systemRed
+
+ if reversed {
+ switch self {
+ case 0.6...0.8:
+ return secondColor
+ case 0.8...1:
+ return firstColor
+ default:
+ return thirdColor
+ }
+ } else {
+ switch self {
+ case 0.6...0.8:
+ return secondColor
+ case 0.8...1:
+ return thirdColor
+ default:
+ return firstColor
+ }
+ }
+ }
+
+ func textUsageColor(color: Bool) -> NSColor {
+ if !color {
+ return NSColor.textColor
+ }
+ return usageColor(color: NSColor.textColor)
+ }
+
+ func batteryColor(color: Bool = false) -> NSColor {
+ switch self {
+ case 0.2...0.4:
+ if !color {
+ return NSColor.black
+ }
+ return NSColor.systemOrange
+ case 0.4...1:
+ if self == 1 {
+ return NSColor.black
+ }
+ if !color {
+ return NSColor.black
+ }
+ return NSColor.systemGreen
+ default:
+ return NSColor.systemRed
+ }
+ }
+
+ 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)h"
+ case (nil, nil, let x?):
+ return "\(x)sec"
+ case (let x?, nil, let z?):
+ return "\(x)h \(z)sec"
+ case (let x?, let y?, nil):
+ return "\(x)h \(y)min"
+ case (let x?, let y?, let z?):
+ return "\(x)h \(y)min \(z)sec"
+ default:
+ return "n/a"
+ }
+ }
+}
+
+public extension NSView {
+ var isDarkMode: Bool {
+ if #available(OSX 10.14, *) {
+ switch effectiveAppearance.name {
+ case .darkAqua, .vibrantDark, .accessibilityHighContrastDarkAqua, .accessibilityHighContrastVibrantDark:
+ return true
+ default:
+ return false
+ }
+ } else {
+ switch effectiveAppearance.name {
+ case .vibrantDark:
+ return true
+ default:
+ return false
+ }
+ }
+ }
+
+ func ToggleTitleRow(frame: NSRect, title: String, action: Selector, state: Bool) -> NSView {
+ let row: NSView = NSView(frame: frame)
+ let state: NSControl.StateValue = state ? .on : .off
+
+ let rowTitle: NSTextField = LabelField(frame: NSRect(x: 0, y: (row.frame.height - 16)/2, width: row.frame.width - 52, height: 17), title)
+ rowTitle.font = NSFont.systemFont(ofSize: 13, weight: .light)
+ rowTitle.textColor = .labelColor
+
+ var toggle: NSControl = NSControl()
+ if #available(OSX 10.15, *) {
+ let switchButton = NSSwitch(frame: NSRect(x: row.frame.width - 50, y: 0, width: 50, height: row.frame.height))
+ switchButton.state = state
+ switchButton.action = action
+ switchButton.target = self
+
+ toggle = switchButton
+ } else {
+ let button: NSButton = NSButton(frame: NSRect(x: row.frame.width - 30, y: 0, width: 30, height: row.frame.height))
+ button.setButtonType(.switch)
+ button.state = state
+ button.title = ""
+ button.action = action
+ button.isBordered = false
+ button.isTransparent = true
+
+ toggle = button
+ }
+
+ row.addSubview(toggle)
+ row.addSubview(rowTitle)
+
+ return row
+ }
+
+ func SelectTitleRow(frame: NSRect, title: String, action: Selector, items: [String], selected: String) -> NSView {
+ let row: NSView = NSView(frame: frame)
+
+ let rowTitle: NSTextField = LabelField(frame: NSRect(x: 0, y: (row.frame.height - 16)/2, width: row.frame.width - 52, height: 17), title)
+ rowTitle.font = NSFont.systemFont(ofSize: 13, weight: .light)
+ rowTitle.textColor = .labelColor
+
+ let select: NSPopUpButton = NSPopUpButton(frame: NSRect(x: row.frame.width - 50, y: 0, width: 50, height: row.frame.height))
+ select.target = self
+ select.action = action
+ select.addItems(withTitles: items)
+ select.selectItem(withTitle: selected)
+ select.sizeToFit()
+
+ rowTitle.setFrameSize(NSSize(width: row.frame.width - select.frame.width, height: rowTitle.frame.height))
+ select.setFrameOrigin(NSPoint(x: row.frame.width - select.frame.width, y: 0))
+
+ row.addSubview(select)
+ row.addSubview(rowTitle)
+
+ return row
+ }
+}
+
+public extension Notification.Name {
+ static let toggleSettings = Notification.Name("toggleSettings")
+ static let toggleModule = Notification.Name("toggleModule")
+ static let openSettingsView = Notification.Name("openSettingsView")
+ static let switchWidget = Notification.Name("switchWidget")
+ static let checkForUpdates = Notification.Name("checkForUpdates")
+ static let clickInSettings = Notification.Name("clickInSettings")
+}
+
+public class NSButtonWithPadding: NSButton {
+ public var horizontalPadding: CGFloat = 0
+ public var verticalPadding: CGFloat = 0
+
+ public override var intrinsicContentSize: NSSize {
+ var size = super.intrinsicContentSize
+ size.width += self.horizontalPadding
+ size.height += self.verticalPadding
+ return size;
+ }
+}
+
+public class TextView: NSTextField {
+ public override init(frame: NSRect) {
+ super.init(frame: frame)
+
+ self.isEditable = false
+ self.isSelectable = false
+ self.isBezeled = false
+ self.wantsLayer = true
+ self.textColor = .labelColor
+ self.backgroundColor = .clear
+ self.canDrawSubviewsIntoLayer = true
+ self.alignment = .natural
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+}
+
+public extension OperatingSystemVersion {
+ func getFullVersion(separator: String = ".") -> String {
+ return "\(majorVersion)\(separator)\(minorVersion)\(separator)\(patchVersion)"
+ }
+}
+
+extension URL {
+ func checkFileExist() -> Bool {
+ return FileManager.default.fileExists(atPath: self.path)
+ }
+}
+
+extension UInt32 {
+ init(bytes: (UInt8, UInt8, UInt8, UInt8)) {
+ self = UInt32(bytes.0) << 24 | UInt32(bytes.1) << 16 | UInt32(bytes.2) << 8 | UInt32(bytes.3)
+ }
+}
+
+extension FourCharCode {
+ init(fromString str: String) {
+ precondition(str.count == 4)
+
+ self = str.utf8.reduce(0) { sum, character in
+ return sum << 8 | UInt32(character)
+ }
+ }
+
+ func toString() -> String {
+ return String(describing: UnicodeScalar(self >> 24 & 0xff)!) +
+ String(describing: UnicodeScalar(self >> 16 & 0xff)!) +
+ String(describing: UnicodeScalar(self >> 8 & 0xff)!) +
+ String(describing: UnicodeScalar(self & 0xff)!)
+ }
+}
+
+public extension NSColor {
+ convenience init(hexString: String, alpha: CGFloat = 1.0) {
+ let hexString: String = hexString.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
+ let scanner = Scanner(string: hexString)
+ if (hexString.hasPrefix("#")) {
+ scanner.scanLocation = 1
+ }
+ var color: UInt32 = 0
+ scanner.scanHexInt32(&color)
+ let mask = 0x000000FF
+ let r = Int(color >> 16) & mask
+ let g = Int(color >> 8) & mask
+ let b = Int(color) & mask
+ let red = CGFloat(r) / 255.0
+ let green = CGFloat(g) / 255.0
+ let blue = CGFloat(b) / 255.0
+ self.init(red:red, green:green, blue:blue, alpha:alpha)
+ }
+
+ func toHexString() -> String {
+ var r:CGFloat = 0
+ var g:CGFloat = 0
+ var b:CGFloat = 0
+ var a:CGFloat = 0
+ getRed(&r, green: &g, blue: &b, alpha: &a)
+ let rgb:Int = (Int)(r*255)<<16 | (Int)(g*255)<<8 | (Int)(b*255)<<0
+ return String(format:"#%06x", rgb)
+ }
+}
+
+public class LabelField: NSTextField {
+ public init(frame: NSRect, _ label: String) {
+ super.init(frame: frame)
+
+ self.isEditable = false
+ self.isSelectable = false
+ self.isBezeled = false
+ self.wantsLayer = true
+ self.backgroundColor = .clear
+ self.canDrawSubviewsIntoLayer = true
+
+ self.stringValue = label
+ self.textColor = .secondaryLabelColor
+ self.alignment = .natural
+ self.font = NSFont.systemFont(ofSize: 12, weight: .regular)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+}
+
+public class ValueField: NSTextField {
+ public init(frame: NSRect, _ value: String) {
+ super.init(frame: frame)
+
+ self.isEditable = false
+ self.isSelectable = false
+ self.isBezeled = false
+ self.wantsLayer = true
+ self.backgroundColor = .clear
+ self.canDrawSubviewsIntoLayer = true
+
+ self.stringValue = value
+ self.textColor = .textColor
+ self.alignment = .right
+ self.font = NSFont.systemFont(ofSize: 13, weight: .regular)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+}
+
+public extension NSBezierPath {
+ func addArrow(start: CGPoint, end: CGPoint, pointerLineLength: CGFloat, arrowAngle: CGFloat) {
+ self.move(to: start)
+ self.line(to: end)
+
+ let startEndAngle = atan((end.y - start.y) / (end.x - start.x)) + ((end.x - start.x) < 0 ? CGFloat(Double.pi) : 0)
+ let arrowLine1 = CGPoint(x: end.x + pointerLineLength * cos(CGFloat(Double.pi) - startEndAngle + arrowAngle), y: end.y - pointerLineLength * sin(CGFloat(Double.pi) - startEndAngle + arrowAngle))
+ let arrowLine2 = CGPoint(x: end.x + pointerLineLength * cos(CGFloat(Double.pi) - startEndAngle - arrowAngle), y: end.y - pointerLineLength * sin(CGFloat(Double.pi) - startEndAngle - arrowAngle))
+
+ self.line(to: arrowLine1)
+ self.move(to: end)
+ self.line(to: arrowLine2)
+ }
+}
+
+public func SeparatorView(_ title: String, origin: NSPoint, width: CGFloat) -> NSView {
+ let view: NSView = NSView(frame: NSRect(x: origin.x, y: origin.y, width: width, height: 30))
+
+ let labelView: NSTextField = TextView(frame: NSRect(x: 0, y: (view.frame.height-15)/2, width: view.frame.width, height: 15))
+ labelView.stringValue = title
+ labelView.alignment = .center
+ labelView.textColor = .secondaryLabelColor
+ labelView.font = NSFont.systemFont(ofSize: 12, weight: .medium)
+ labelView.stringValue = title
+
+ view.addSubview(labelView)
+ return view
+}
+
+public func PopupRow(_ view: NSView, n: CGFloat, title: String, value: String) -> NSTextField {
+ let rowView: NSView = NSView(frame: NSRect(x: 0, y: 22*n, width: view.frame.width, height: 22))
+
+ let labelWidth = title.widthOfString(usingFont: .systemFont(ofSize: 13, weight: .regular)) + 5
+ let labelView: LabelField = LabelField(frame: NSRect(x: 0, y: (22-15)/2, width: labelWidth, height: 15), title)
+ let valueView: ValueField = ValueField(frame: NSRect(x: labelWidth, y: (22-16)/2, width: rowView.frame.width - labelWidth, height: 16), value)
+
+ rowView.addSubview(labelView)
+ rowView.addSubview(valueView)
+ view.addSubview(rowView)
+
+ return valueView
+}
diff --git a/Stats/libs/LaunchAtLogin.swift b/StatsKit/launchAtLogin.swift
similarity index 81%
rename from Stats/libs/LaunchAtLogin.swift
rename to StatsKit/launchAtLogin.swift
index d9b95f5f..2bd457e4 100644
--- a/Stats/libs/LaunchAtLogin.swift
+++ b/StatsKit/launchAtLogin.swift
@@ -1,12 +1,15 @@
//
-// LaunchAtLogin.swift
-// Stats
+// launchAtLogin.swift
+// StatsKit
+//
+// Created by Serhiy Mytrovtsiy on 14/04/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
//
-// Created by Serhiy Mytrovtsiy on 08/04/2020.
// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
//
-import Foundation
+import Cocoa
import ServiceManagement
public struct LaunchAtLogin {
diff --git a/StatsKit/store.swift b/StatsKit/store.swift
new file mode 100644
index 00000000..866751fd
--- /dev/null
+++ b/StatsKit/store.swift
@@ -0,0 +1,48 @@
+//
+// store.swift
+// StatsKit
+//
+// Created by Serhiy Mytrovtsiy on 10/04/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+
+public class Store {
+ private let defaults = UserDefaults.standard
+
+ public init() {}
+
+ public func exist(key: String) -> Bool {
+ return self.defaults.object(forKey: key) == nil ? false : true
+ }
+
+ public func bool(key: String, defaultValue value: Bool) -> Bool {
+ return !self.exist(key: key) ? value : defaults.bool(forKey: key)
+ }
+
+ public func string(key: String, defaultValue value: String) -> String {
+ return (!self.exist(key: key) ? value : defaults.string(forKey: key))!
+ }
+
+ public func int(key: String, defaultValue value: Int) -> Int {
+ return (!self.exist(key: key) ? value : defaults.integer(forKey: key))
+ }
+
+ public func set(key: String, value: Bool) {
+ self.defaults.set(value, forKey: key)
+ }
+
+ public func set(key: String, value: String) {
+ self.defaults.set(value, forKey: key)
+ }
+
+ public func reset() {
+ self.defaults.dictionaryRepresentation().keys.forEach { key in
+ self.defaults.removeObject(forKey: key)
+ }
+ }
+}
diff --git a/Stats/libs/MacAppUpdater.swift b/StatsKit/updater.swift
similarity index 71%
rename from Stats/libs/MacAppUpdater.swift
rename to StatsKit/updater.swift
index 0314f4c8..a80fcaa4 100644
--- a/Stats/libs/MacAppUpdater.swift
+++ b/StatsKit/updater.swift
@@ -1,98 +1,47 @@
//
-// macAppUpdater.swift
-// Stats
+// updater.swift
+// StatsKit
//
-// Created by Serhiy Mytrovtsiy on 25.06.2019.
-// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
+// Created by Serhiy Mytrovtsiy on 14/04/2020.
+// Using Swift 5.0.
+// Running on macOS 10.15.
+//
+// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
//
-import Foundation
+import Cocoa
import SystemConfiguration
-extension String: Error {}
-
-struct version {
- let current: String
- let latest: String
- let newest: Bool
- let url: String
+public struct version {
+ public let current: String
+ public let latest: String
+ public let newest: Bool
+ public let url: String
}
-struct Version {
+public struct Version {
var major: Int = 0
var minor: Int = 0
var patch: Int = 0
}
public class macAppUpdater {
- let user: String
- let repo: String
+ private let user: String
+ private let repo: String
- let appName: String = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as! String
- let currentVersion: String = "v\(Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String)"
+ private let appName: String = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as! String
+ private let currentVersion: String = "v\(Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String)"
- var url: String {
+ private var url: String {
return "https://api.github.com/repos/\(user)/\(repo)/releases/latest"
}
- init(user: String, repo: String) {
+ public init(user: String, repo: String) {
self.user = user
self.repo = repo
}
- func fetchLastVersion(completionHandler: @escaping (_ result: [String]?, _ error: Error?) -> Void) {
- let task = URLSession.shared.dataTask(with: URL(string: self.url)!) { data, response, error in
- guard let data = data, error == nil else { return }
-
- do {
- let jsonResponse = try JSONSerialization.jsonObject(with: data, options: [])
- guard let jsonArray = jsonResponse as? [String: Any] else {
- completionHandler(nil, "parse json")
- return
- }
- let lastVersion = jsonArray["tag_name"] as? String
-
- guard let assets = jsonArray["assets"] as? [[String: Any]] else {
- completionHandler(nil, "parse assets")
- return
- }
- if let asset = assets.first(where: {$0["name"] as! String == "\(self.appName).dmg"}) {
- let downloadURL = asset["browser_download_url"] as? String
- completionHandler([lastVersion!, downloadURL!], nil)
- }
- } catch let parsingError {
- completionHandler(nil, parsingError)
- }
- }
- task.resume()
- }
-
- func checkIfNewer(currentVersion: String, latestVersion: String) -> Bool {
- let currentNumber = currentVersion.replacingOccurrences(of: "v", with: "")
- let latestNumber = latestVersion.replacingOccurrences(of: "v", with: "")
-
- let currentArray = currentNumber.condenseWhitespace().split(separator: ".")
- let latestArray = latestNumber.condenseWhitespace().split(separator: ".")
-
- let current = Version(major: Int(currentArray[0]) ?? 0, minor: Int(currentArray[1]) ?? 0, patch: Int(currentArray[2]) ?? 0)
- let latest = Version(major: Int(latestArray[0]) ?? 0, minor: Int(latestArray[1]) ?? 0, patch: Int(latestArray[2]) ?? 0)
-
- if latest.major > current.major {
- return true
- }
-
- if latest.minor > current.minor && latest.major >= current.major {
- return true
- }
-
- if latest.patch > current.patch && latest.minor >= current.minor && latest.major >= current.major {
- return true
- }
-
- return false
- }
-
- func check(completionHandler: @escaping (_ result: version?, _ error: Error?) -> Void) {
+ public func check(completionHandler: @escaping (_ result: version?, _ error: Error?) -> Void) {
if !isConnectedToNetwork() {
completionHandler(nil, "No internet connection")
return
@@ -108,16 +57,68 @@ public class macAppUpdater {
completionHandler(nil, "wrong results")
return
}
-
+
let downloadURL: String = result![1]
let lastVersion: String = result![0]
let newVersion: Bool = self.checkIfNewer(currentVersion: self.currentVersion, latestVersion: lastVersion)
-
+
completionHandler(version(current: self.currentVersion, latest: lastVersion, newest: newVersion, url: downloadURL), nil)
}
}
- func download(_ url: URL) {
+ private func fetchLastVersion(completionHandler: @escaping (_ result: [String]?, _ error: Error?) -> Void) {
+ let task = URLSession.shared.dataTask(with: URL(string: self.url)!) { data, response, error in
+ guard let data = data, error == nil else { return }
+
+ do {
+ let jsonResponse = try JSONSerialization.jsonObject(with: data, options: [])
+ guard let jsonArray = jsonResponse as? [String: Any] else {
+ completionHandler(nil, "parse json")
+ return
+ }
+ let lastVersion = jsonArray["tag_name"] as? String
+
+ guard let assets = jsonArray["assets"] as? [[String: Any]] else {
+ completionHandler(nil, "parse assets")
+ return
+ }
+ if let asset = assets.first(where: {$0["name"] as! String == "\(self.appName).dmg"}) {
+ let downloadURL = asset["browser_download_url"] as? String
+ completionHandler([lastVersion!, downloadURL!], nil)
+ }
+ } catch let parsingError {
+ completionHandler(nil, parsingError)
+ }
+ }
+ task.resume()
+ }
+
+ private func checkIfNewer(currentVersion: String, latestVersion: String) -> Bool {
+ let currentNumber = currentVersion.replacingOccurrences(of: "v", with: "")
+ let latestNumber = latestVersion.replacingOccurrences(of: "v", with: "")
+
+ let currentArray = currentNumber.condenseWhitespace().split(separator: ".")
+ let latestArray = latestNumber.condenseWhitespace().split(separator: ".")
+
+ let current = Version(major: Int(currentArray[0]) ?? 0, minor: Int(currentArray[1]) ?? 0, patch: Int(currentArray[2]) ?? 0)
+ let latest = Version(major: Int(latestArray[0]) ?? 0, minor: Int(latestArray[1]) ?? 0, patch: Int(latestArray[2]) ?? 0)
+
+ if latest.major > current.major {
+ return true
+ }
+
+ if latest.minor > current.minor && latest.major >= current.major {
+ return true
+ }
+
+ if latest.patch > current.patch && latest.minor >= current.minor && latest.major >= current.major {
+ return true
+ }
+
+ return false
+ }
+
+ public func download(_ url: URL) {
let downloadTask = URLSession.shared.downloadTask(with: url) {
urlOrNil, responseOrNil, errorOrNil in
// check for and handle errors:
@@ -155,7 +156,7 @@ public class macAppUpdater {
if fileName.hasSuffix(fileExt) {
fileNameWithotSuffix = String(fileName.prefix(fileName.count - (fileExt.count+1)))
}
-
+
while toPath.checkFileExist() {
counter += 1
newFileName = "\(fileNameWithotSuffix!)-\(counter).\(fileExt)"
@@ -174,36 +175,36 @@ public class macAppUpdater {
let task = Process()
task.launchPath = "/usr/bin/hdiutil"
task.arguments = ["attach", url]
-
+
let pipe = Pipe()
task.standardOutput = pipe
task.launch()
task.waitUntilExit()
-
+
exit(0)
}
-}
-// https://stackoverflow.com/questions/30743408/check-for-internet-connection-with-swift
-func isConnectedToNetwork() -> Bool {
- var zeroAddress = sockaddr_in(sin_len: 0, sin_family: 0, sin_port: 0, sin_addr: in_addr(s_addr: 0), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
- zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress))
- zeroAddress.sin_family = sa_family_t(AF_INET)
-
- let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) {
- $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {zeroSockAddress in
- SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress)
+ // https://stackoverflow.com/questions/30743408/check-for-internet-connection-with-swift
+ private func isConnectedToNetwork() -> Bool {
+ var zeroAddress = sockaddr_in(sin_len: 0, sin_family: 0, sin_port: 0, sin_addr: in_addr(s_addr: 0), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
+ zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress))
+ zeroAddress.sin_family = sa_family_t(AF_INET)
+
+ let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) {
+ $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {zeroSockAddress in
+ SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress)
+ }
}
+
+ var flags: SCNetworkReachabilityFlags = SCNetworkReachabilityFlags(rawValue: 0)
+ if SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) == false {
+ return false
+ }
+
+ let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0
+ let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0
+ let ret = (isReachable && !needsConnection)
+
+ return ret
}
-
- var flags: SCNetworkReachabilityFlags = SCNetworkReachabilityFlags(rawValue: 0)
- if SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) == false {
- return false
- }
-
- let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0
- let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0
- let ret = (isReachable && !needsConnection)
-
- return ret
}
diff --git a/resources/logo.png b/resources/logo.png
deleted file mode 100644
index d2ec060c..00000000
Binary files a/resources/logo.png and /dev/null differ
diff --git a/resources/logo.psd b/resources/logo.psd
deleted file mode 100644
index 4bde4b51..00000000
Binary files a/resources/logo.psd and /dev/null differ
diff --git a/resources/tray_icon.psd b/resources/tray_icon.psd
deleted file mode 100644
index 1c903de3..00000000
Binary files a/resources/tray_icon.psd and /dev/null differ