diff --git a/ModuleKit/Widgets/Speed.swift b/ModuleKit/Widgets/Speed.swift index 251a88a6..46ca0c0c 100644 --- a/ModuleKit/Widgets/Speed.swift +++ b/ModuleKit/Widgets/Speed.swift @@ -26,6 +26,8 @@ public class SpeedWidget: Widget { private var state: Bool = false private var valueState: Bool = true + private var symbols: [String] = ["U", "D"] + private var uploadField: NSTextField? = nil private var downloadField: NSTextField? = nil @@ -38,6 +40,14 @@ public class SpeedWidget: Widget { public init(preview: Bool, title: String, config: NSDictionary?, store: UnsafePointer?) { let widgetTitle: String = title self.store = store + if config != nil { + if let symbols = config!["Symbols"] as? [String] { + self.symbols = symbols + } + if let iconName = config!["Icon"] as? String, let icon = speed_icon_t(rawValue: iconName) { + self.icon = icon + } + } 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 = .speed @@ -172,23 +182,27 @@ public class SpeedWidget: Widget { 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.textColor, - 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) + if self.symbols.count > 1 { + 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.textColor, + NSAttributedString.Key.paragraphStyle: NSMutableParagraphStyle() + ] + let rect = CGRect(x: Constants.Widget.margin, y: 1, width: 8, height: rowHeight) + let str = NSAttributedString.init(string: self.symbols[1], 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.textColor, - 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) + if self.symbols.count > 0 { + let uploadAttributes = [ + NSAttributedString.Key.font: NSFont.systemFont(ofSize: 9, weight: .regular), + NSAttributedString.Key.foregroundColor: uploadValue >= 1_024 ? NSColor.red : NSColor.textColor, + NSAttributedString.Key.paragraphStyle: NSMutableParagraphStyle() + ] + let rect = CGRect(x: Constants.Widget.margin, y: rowHeight+1, width: 8, height: rowHeight) + let str = NSAttributedString.init(string: self.symbols[0], attributes: uploadAttributes) + str.draw(with: rect) + } } public override func settings(superview: NSView) { @@ -255,11 +269,11 @@ public class SpeedWidget: Widget { var updated: Bool = false if self.downloadValue != download { - self.downloadValue = download + self.downloadValue = abs(download) updated = true } if self.uploadValue != upload { - self.uploadValue = upload + self.uploadValue = abs(upload) updated = true } diff --git a/Modules/Battery/readers.swift b/Modules/Battery/readers.swift index 626f2bb1..47a0ab44 100644 --- a/Modules/Battery/readers.swift +++ b/Modules/Battery/readers.swift @@ -234,11 +234,11 @@ public class ProcessReader: Reader<[TopProcess]> { output.enumerateLines { (line, _) -> () in if line.matches("^\\d* +.+ \\d*.?\\d*$") { var str = line.trimmingCharacters(in: .whitespaces) - + let pidString = str.findAndCrop(pattern: "^\\d+") let usageString = str.findAndCrop(pattern: " +[0-9]+.*[0-9]*$") let command = str.trimmingCharacters(in: .whitespaces) - + let pid = Int(pidString) ?? 0 guard let usage = Double(usageString.filter("01234567890.".contains)) else { return diff --git a/Modules/Disk/Bridge.h b/Modules/Disk/Bridge.h new file mode 100644 index 00000000..d04b8b2c --- /dev/null +++ b/Modules/Disk/Bridge.h @@ -0,0 +1,17 @@ +// +// Bridge.h +// Disk +// +// Created by Serhiy Mytrovtsiy on 07/08/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +#ifndef Bridge_h +#define Bridge_h + +#include + +#endif /* Bridge_h */ diff --git a/Modules/Disk/config.plist b/Modules/Disk/config.plist index 914aa157..e95fff93 100644 --- a/Modules/Disk/config.plist +++ b/Modules/Disk/config.plist @@ -68,6 +68,20 @@ Order 2 + speed + + Default + + Order + 3 + Icon + Character + Symbols + + W + R + + diff --git a/Modules/Disk/main.swift b/Modules/Disk/main.swift index 61b9803f..54a7e576 100644 --- a/Modules/Disk/main.swift +++ b/Modules/Disk/main.swift @@ -63,9 +63,15 @@ struct DiskList: value_t { } } +public struct IO { + var read: Int = 0 + var write: Int = 0 +} + public class Disk: Module { private let popupView: Popup = Popup() private var capacityReader: CapacityReader? = nil + private var ioReader: IOReader? = nil private var settingsView: Settings private var selectedDisk: String = "" @@ -83,12 +89,17 @@ public class Disk: Module { self.capacityReader?.store = store self.selectedDisk = store.pointee.string(key: "\(self.config.name)_disk", defaultValue: self.selectedDisk) + self.ioReader = IOReader() + self.capacityReader?.readyCallback = { [unowned self] in self.readyHandler() } self.capacityReader?.callbackHandler = { [unowned self] value in self.capacityCallback(value: value) } + self.ioReader?.callbackHandler = { [unowned self] value in + self.ioCallback(value: value) + } self.settingsView.selectedDiskHandler = { [unowned self] value in self.selectedDisk = value @@ -104,6 +115,9 @@ public class Disk: Module { if let reader = self.capacityReader { self.addReader(reader) } + if let reader = self.ioReader { + self.addReader(reader) + } } private func capacityCallback(value: DiskList?) { @@ -137,4 +151,14 @@ public class Disk: Module { widget.setValue((free, usedSpace)) } } + + private func ioCallback(value: IO?) { + if value == nil { + return + } + + if let widget = self.widget as? SpeedWidget { + widget.setValue(upload: Int64(value!.write), download: Int64(value!.read)) + } + } } diff --git a/Modules/Disk/readers.swift b/Modules/Disk/readers.swift index c7e9b3e1..f884dd69 100644 --- a/Modules/Disk/readers.swift +++ b/Modules/Disk/readers.swift @@ -12,6 +12,8 @@ import Cocoa import ModuleKit import StatsKit +import IOKit +import Darwin internal class CapacityReader: Reader { private var disks: DiskList = DiskList() @@ -124,3 +126,46 @@ internal class CapacityReader: Reader { } } } + +// https://gist.github.com/kainjow/0e7650cc797a52261e0f4ba851477c2f +internal class IOReader: Reader { + public var stats: IO = IO() + + public override func read() { + let initialNumPids = proc_listallpids(nil, 0) + let buffer = UnsafeMutablePointer.allocate(capacity: Int(initialNumPids)) + defer { + buffer.deallocate() + } + + let bufferLength = initialNumPids * Int32(MemoryLayout.size) + let numPids = proc_listallpids(buffer, bufferLength) + + var read: Int = 0 + var write: Int = 0 + for i in 0.. Order 0 + Symbols + + U + D + diff --git a/Stats.xcodeproj/project.pbxproj b/Stats.xcodeproj/project.pbxproj index c2d9d08a..11fb5e20 100644 --- a/Stats.xcodeproj/project.pbxproj +++ b/Stats.xcodeproj/project.pbxproj @@ -70,6 +70,7 @@ 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 */; }; + 9AA95A6424DD7C6300801B69 /* Bridge.h in Headers */ = {isa = PBXBuildFile; fileRef = 9AA95A6324DD7C6300801B69 /* Bridge.h */; }; 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 */; }; @@ -451,6 +452,7 @@ 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 = ""; }; + 9AA95A6324DD7C6300801B69 /* Bridge.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Bridge.h; 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 = ""; }; @@ -808,6 +810,7 @@ 9AB7FD7B246B48DB00387FDA /* settings.swift */, 9AF9EE0524648751005D2270 /* Info.plist */, 9AF9EE12246492E8005D2270 /* config.plist */, + 9AA95A6324DD7C6300801B69 /* Bridge.h */, ); path = Disk; sourceTree = ""; @@ -869,6 +872,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 9AA95A6424DD7C6300801B69 /* Bridge.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2214,6 +2218,7 @@ PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.Disk; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = Modules/Disk/Bridge.h; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; @@ -2246,6 +2251,7 @@ PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.Disk; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = Modules/Disk/Bridge.h; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; diff --git a/StatsKit/SMC.swift b/StatsKit/SMC.swift index 99539ca4..cb0433a3 100644 --- a/StatsKit/SMC.swift +++ b/StatsKit/SMC.swift @@ -103,21 +103,21 @@ public class SMCService { var result: kern_return_t var iterator: io_iterator_t = 0 let device: io_object_t - + let matchingDictionary: CFMutableDictionary = IOServiceMatching("AppleSMC") result = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDictionary, &iterator) if (result != kIOReturnSuccess) { print("Error IOServiceGetMatchingServices(): " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error")) 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 } - + result = IOServiceOpen(device, mach_task_self_, 0, &conn) IOObjectRelease(device) if (result != kIOReturnSuccess) { diff --git a/StatsKit/SystemKit.swift b/StatsKit/SystemKit.swift index fae9dcb7..014562b9 100644 --- a/StatsKit/SystemKit.swift +++ b/StatsKit/SystemKit.swift @@ -180,6 +180,8 @@ public class SystemKit { } } } + + IOObjectRelease(device) } } diff --git a/StatsKit/extensions.swift b/StatsKit/extensions.swift index 8633e185..ab02ac3a 100644 --- a/StatsKit/extensions.swift +++ b/StatsKit/extensions.swift @@ -156,6 +156,16 @@ extension String: LocalizedError { public func matches(_ regex: String) -> Bool { return self.range(of: regex, options: .regularExpression, range: nil, locale: nil) != nil } + + public func removedRegexMatches(pattern: String, replaceWith: String = "") -> String { + do { + let regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpression.Options.caseInsensitive) + let range = NSMakeRange(0, self.count) + return regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: replaceWith) + } catch { + return self + } + } } public extension Int {