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 +

[![Stats](https://serhiy.s3.eu-central-1.amazonaws.com/Github_repo/stats/cover%3Fv1.6.0.png)](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