diff --git a/Cartfile b/Cartfile index 47b6f0ef..59036135 100644 --- a/Cartfile +++ b/Cartfile @@ -1,3 +1,3 @@ -github "sindresorhus/LaunchAtLogin" +github "sindresorhus/LaunchAtLogin" ~> 3.0.0 github "danielgindi/Charts" ~> 3.4.0 -github "ashleymills/Reachability.swift" \ No newline at end of file +github "ashleymills/Reachability.swift" ~> 5.0.0 \ No newline at end of file diff --git a/Makefile b/Makefile index c7c2051b..4b942a43 100644 --- a/Makefile +++ b/Makefile @@ -5,13 +5,14 @@ ITC_USERNAME = $(AC_USERNAME) ITC_PASSWORD = @keychain:AC_PASSWORD ITC_PROVIDER = $(AC_PROVIDER) -RequestUUID = e6c7b954-d9fa-4c74-8927-ba2172c9526e +RequestUUID = 32c80f6c-36ed-4042-9837-b9093c9f4eb9 BUILD_PATH = $(PWD)/build ARCHIVE_PATH = $(BUILD_PATH)/$(APP).xcarchive APP_PATH = "$(BUILD_PATH)/$(APP).app" ZIP_PATH = "$(BUILD_PATH)/$(APP).zip" DMG_PATH = $(PWD)/$(APP).dmg +LOCATION=$(BUILD_PATH)/$(APP).app/Contents/Library/LoginItems/LaunchAtLoginHelper.app all: clean archive notarize sign build diff --git a/README.md b/README.md index bdd54d13..cc512439 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ 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 @@ -23,6 +24,7 @@ You can download latest version [here](https://github.com/exelban/stats/releases | --- | --- | --- | | **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 | @@ -30,7 +32,7 @@ You can download latest version [here](https://github.com/exelban/stats/releases ## Compatibility | macOS | Compatible | | --- | --- | -| 10.15.2 *(Catalina)* | **true** | +| 10.15.3 *(Catalina)* | **true** | | 10.14.6 *(Mojave)* | **true** | | 10.13.6 *(High Sierra)* | **true** | diff --git a/Stats.xcodeproj/project.pbxproj b/Stats.xcodeproj/project.pbxproj index a3f798ea..9b8ccb29 100644 --- a/Stats.xcodeproj/project.pbxproj +++ b/Stats.xcodeproj/project.pbxproj @@ -400,8 +400,8 @@ 9A1410F2229E721100D29793 /* Frameworks */, 9A1410F3229E721100D29793 /* Resources */, 9AB54DAE22A19F96006192E0 /* Copy Files */, - 9A6698D82326A903001D00E1 /* ShellScript */, - 9A493CE1232047F00064570C /* ShellScript */, + 9A6698D82326A903001D00E1 /* Run Script */, + 9A493CE1232047F00064570C /* Run Script */, 9A6698E72326AB16001D00E1 /* Embed Frameworks */, ); buildRules = ( @@ -470,7 +470,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 9A493CE1232047F00064570C /* ShellScript */ = { + 9A493CE1232047F00064570C /* Run Script */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -479,6 +479,7 @@ ); inputPaths = ( ); + name = "Run Script"; outputFileListPaths = ( ); outputPaths = ( @@ -487,7 +488,7 @@ shellPath = /bin/sh; shellScript = "\"${PROJECT_DIR}/Carthage/Build/Mac/LaunchAtLogin.framework/Resources/copy-helper.sh\"\n"; }; - 9A6698D82326A903001D00E1 /* ShellScript */ = { + 9A6698D82326A903001D00E1 /* Run Script */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 8; files = ( @@ -498,6 +499,7 @@ "$(SRCROOT)/Carthage/Build/Mac/LaunchAtLogin.framework", "$(SRCROOT)/Carthage/Build/Mac/Charts.framework", ); + name = "Run Script"; outputFileListPaths = ( ); outputPaths = ( @@ -704,6 +706,7 @@ CODE_SIGN_IDENTITY = "Mac Developer"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = RP2S87B72W; ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = ( @@ -717,7 +720,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.13; - MARKETING_VERSION = 1.5.2; + MARKETING_VERSION = 1.6.0; PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -734,6 +737,7 @@ CODE_SIGN_IDENTITY = "Mac Developer"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = RP2S87B72W; ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = ( @@ -747,7 +751,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.13; - MARKETING_VERSION = 1.5.2; + MARKETING_VERSION = 1.6.0; PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/Stats/AppDelegate.swift b/Stats/AppDelegate.swift index ff51aa9c..6291feca 100755 --- a/Stats/AppDelegate.swift +++ b/Stats/AppDelegate.swift @@ -48,7 +48,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { } func applicationWillTerminate(_ aNotification: Notification) { -// SMCClose() + _ = smc.close() menuBar?.destroy() } diff --git a/Stats/Modules/Sensors/Sensors.swift b/Stats/Modules/Sensors/Sensors.swift index 5e3c2057..4b870199 100644 --- a/Stats/Modules/Sensors/Sensors.swift +++ b/Stats/Modules/Sensors/Sensors.swift @@ -71,8 +71,10 @@ class Sensors: Module { } private func update() { - var value_1: Double = 0 - var value_2: Double = 0 + 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) @@ -80,18 +82,20 @@ class Sensors: Module { if sensor_1 != nil { sensor_1!.update() if sensor_1!.value != nil { - value_1 = sensor_1!.value! + 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 = sensor_2!.value! + 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_2]) + (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/SensorsType.swift b/Stats/Modules/Sensors/SensorsType.swift index 036827d2..47926d88 100644 --- a/Stats/Modules/Sensors/SensorsType.swift +++ b/Stats/Modules/Sensors/SensorsType.swift @@ -19,6 +19,8 @@ enum SensorType: SensorType_t { case Temperature = "Temperature" case Voltage = "Voltage" case Power = "Power" + case Frequency = "Frequency" + case Battery = "Battery" } struct Sensor_t { @@ -27,6 +29,19 @@ struct Sensor_t { 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 @@ -125,7 +140,6 @@ let SensorsDict: [String: Sensor_t] = [ "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), - "VBAT": Sensor_t(name: "Battery", 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), @@ -148,4 +162,22 @@ let SensorsDict: [String: Sensor_t] = [ "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/Info.plist b/Stats/Supporting Files/Info.plist index 6e2a57df..cce2f3ca 100755 --- a/Stats/Supporting Files/Info.plist +++ b/Stats/Supporting Files/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - 1 + $(CURRENT_PROJECT_VERSION) LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/Stats/Widgets/Charts/LineChart.swift b/Stats/Widgets/Charts/LineChart.swift index f579daf2..c84a2612 100644 --- a/Stats/Widgets/Charts/LineChart.swift +++ b/Stats/Widgets/Charts/LineChart.swift @@ -7,6 +7,7 @@ // import Cocoa +import Charts class Chart: NSView, Widget { public var name: String = "LineChart" @@ -172,3 +173,9 @@ class Chart: NSView, Widget { self.redraw() } } + +class ChartsNetworkAxisFormatter: IAxisValueFormatter { + func stringForValue(_ value: Double, axis: AxisBase?) -> String { + return Units(bytes: Int64(value)).getReadableSpeed() + } +} diff --git a/Stats/Widgets/Sensors/SensorsWidget.swift b/Stats/Widgets/Sensors/SensorsWidget.swift index e2d23272..974cc8b6 100644 --- a/Stats/Widgets/Sensors/SensorsWidget.swift +++ b/Stats/Widgets/Sensors/SensorsWidget.swift @@ -82,11 +82,13 @@ class SensorsWidget: NSView, Widget { } func setValue(data: [Double]) { - if self.value != data && data.count == 2 { - self.value = data - - self.topValueView.stringValue = "\(Int(self.value[0]))°" - self.bottomValueView.stringValue = "\(Int(self.value[1]))°" + if self.value != data && data.count == 4 { + self.value = [data[0], data[2]] + let unit_1: String = String(UnicodeScalar(Int(data[1]))!) + let unit_2: String = String(UnicodeScalar(Int(data[3]))!) + + self.topValueView.stringValue = "\(Int(self.value[0]))\(unit_1)" + self.bottomValueView.stringValue = "\(Int(self.value[1]))\(unit_2)" self.topValueView.textColor = self.value[0].temperatureColor(color: self.color) self.bottomValueView.textColor = self.value[1].temperatureColor(color: self.color) diff --git a/Stats/libs/Extensions.swift b/Stats/libs/Extensions.swift index 1c60a171..cc7953f5 100755 --- a/Stats/libs/Extensions.swift +++ b/Stats/libs/Extensions.swift @@ -7,7 +7,123 @@ // import Cocoa -import Charts + +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 { @@ -85,81 +201,6 @@ extension Double { func splitAtDecimal() -> [Int64] { return "\(self)".split(separator: ".").map{Int64($0)!} } -} - -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 Double { func secondsToHoursMinutesSeconds () -> (Int?, Int?, Int?) { let hrs = self / 3600 @@ -192,17 +233,6 @@ extension Double { } } -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!) - } -} - extension NSBezierPath { func addArrow(start: CGPoint, end: CGPoint, pointerLineLength: CGFloat, arrowAngle: CGFloat) { self.move(to: start) @@ -219,7 +249,6 @@ extension NSBezierPath { } extension NSColor { - convenience init(hexString: String, alpha: CGFloat = 1.0) { let hexString: String = hexString.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) let scanner = Scanner(string: hexString) @@ -249,53 +278,13 @@ extension NSColor { } } -extension String { - 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() - } -} - extension URL { func checkFileExist() -> Bool { return FileManager.default.fileExists(atPath: self.path) } } -class ChartsNetworkAxisFormatter: IAxisValueFormatter { - func stringForValue(_ value: Double, axis: AxisBase?) -> String { - return Units(bytes: Int64(value)).getReadableSpeed() - } -} - -public extension FourCharCode { +extension FourCharCode { init(fromString str: String) { precondition(str.count == 4) @@ -340,3 +329,12 @@ extension NSMenuItem { } } } + +extension Character { + func unicodeScalarCodePoint() -> UInt32 { + let characterString = String(self) + let scalars = characterString.unicodeScalars + + return scalars[scalars.startIndex].value + } +} diff --git a/Stats/libs/SMC.swift b/Stats/libs/SMC.swift index 1d93b38b..07803427 100644 --- a/Stats/libs/SMC.swift +++ b/Stats/libs/SMC.swift @@ -14,6 +14,7 @@ enum SMCDataType: String { case SP87 = "sp87" case FLT = "flt " case FPE2 = "fpe2" + case FP2E = "fp2e" } enum SMCKeys: UInt8 { @@ -151,12 +152,8 @@ class SMCService { return Double(value!) } return nil - case SMCDataType.FPE2.rawValue: - // ntohs(*(UInt16*)val.bytes) / 4.0; - print("FPE2") - break default: - print("unsupported data type \(val.dataType)") + print("unsupported data type \(val.dataType) for key: \(key)") return nil } }