diff --git a/ModuleKit/module.swift b/ModuleKit/module.swift index 2032f8bc..fb15343f 100644 --- a/ModuleKit/module.swift +++ b/ModuleKit/module.swift @@ -107,6 +107,7 @@ open class Module: Module_p { NotificationCenter.default.addObserver(self, selector: #selector(listenForMouseDownInSettings), name: .clickInSettings, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(listenForModuleToggle), name: .toggleModule, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(listenChangingPopupSize), name: .updatePopupSize, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(listenResignActive), name: NSApplication.willResignActiveNotification, object: nil) if self.config.widgetsConfig.count != 0 { self.setWidget() @@ -358,4 +359,8 @@ open class Module: Module_p { } } } + + @objc private func listenResignActive(_ notification: Notification) { + self.visibilityCallback(false) + } } diff --git a/ModuleKit/popup.swift b/ModuleKit/popup.swift index b86d0dd0..4f6957ab 100644 --- a/ModuleKit/popup.swift +++ b/ModuleKit/popup.swift @@ -12,7 +12,7 @@ import Cocoa import StatsKit -internal class PopupWindow: NSPanel, NSWindowDelegate { +internal class PopupWindow: NSPanel { private let viewController: PopupViewController = PopupViewController() init(title: String, view: NSView?, visibilityCallback: @escaping (_ state: Bool) -> Void) { @@ -62,11 +62,15 @@ internal class PopupViewController: NSViewController { } override func viewWillAppear() { + super.viewWillAppear() + self.popup.appear() self.visibilityCallback(true) } override func viewWillDisappear() { + super.viewWillDisappear() + self.popup.disappear() self.visibilityCallback(false) } diff --git a/Modules/CPU/popup.swift b/Modules/CPU/popup.swift index f37898eb..3fd5b75f 100644 --- a/Modules/CPU/popup.swift +++ b/Modules/CPU/popup.swift @@ -154,7 +154,7 @@ internal class Popup: NSView { let process = list[i] let index = list.count-i-1 if self.processes.indices.contains(index) { - self.processes[index].label = process.command + self.processes[index].label = process.name != nil ? process.name! : process.command self.processes[index].value = "\(process.usage)%" } } diff --git a/Modules/CPU/readers.swift b/Modules/CPU/readers.swift index 08046a1c..114d8fcc 100644 --- a/Modules/CPU/readers.swift +++ b/Modules/CPU/readers.swift @@ -193,7 +193,12 @@ public class ProcessReader: Reader<[TopProcess]> { let pid = Int(pidString) ?? 0 let usage = Double(usageString.replacingOccurrences(of: ",", with: ".")) ?? 0 - processes.append(TopProcess(pid: pid, command: command, usage: usage)) + var name: String? = nil + if let app = NSRunningApplication(processIdentifier: pid_t(pid) ) { + name = app.localizedName ?? nil + } + + processes.append(TopProcess(pid: pid, command: command, name: name, usage: usage)) } if index == 5 { stop = true } diff --git a/Modules/Memory/popup.swift b/Modules/Memory/popup.swift index d7082d64..7544495d 100644 --- a/Modules/Memory/popup.swift +++ b/Modules/Memory/popup.swift @@ -144,7 +144,7 @@ internal class Popup: NSView { let process = list[i] let index = list.count-i-1 if self.processes.indices.contains(index) { - self.processes[index].label = process.command + self.processes[index].label = process.name != nil ? process.name! : process.command self.processes[index].value = Units(bytes: Int64(process.usage)).getReadableMemory() } } diff --git a/Modules/Memory/readers.swift b/Modules/Memory/readers.swift index 93e64d3b..82950391 100644 --- a/Modules/Memory/readers.swift +++ b/Modules/Memory/readers.swift @@ -111,22 +111,28 @@ public class ProcessReader: Reader<[TopProcess]> { } var processes: [TopProcess] = [] - output.enumerateLines { (line, stop) -> () in + output.enumerateLines { (line, _) -> () 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)) + + var name: String? = nil + if let app = NSRunningApplication(processIdentifier: pid_t(pid) ) { + name = app.localizedName ?? nil + } + + let process = TopProcess(pid: pid, command: command, name: name, usage: usage * Double(1024 * 1024)) processes.append(process) } } diff --git a/Modules/Net/main.swift b/Modules/Net/main.swift index 5bd1d4f6..6c2995f1 100644 --- a/Modules/Net/main.swift +++ b/Modules/Net/main.swift @@ -57,8 +57,17 @@ public struct Network_Usage: value_t { public var widget_value: Double = 0 } +public struct Network_Process { + var time: Date = Date() + var name: String = "" + var pid: String = "" + var download: Int = 0 + var upload: Int = 0 +} + public class Network: Module { - private var usageReader: UsageReader? + private var usageReader: UsageReader? = nil + private var processReader: ProcessReader? = nil private let popupView: Popup = Popup() private var settingsView: Settings @@ -75,6 +84,8 @@ public class Network: Module { self.usageReader = UsageReader() self.usageReader?.store = store + self.processReader = ProcessReader() + self.usageReader?.readyCallback = { [unowned self] in self.readyHandler() } @@ -82,6 +93,12 @@ public class Network: Module { self.usageCallback(value) } + self.processReader?.callbackHandler = { [unowned self] value in + if let list = value { + self.popupView.processCallback(list) + } + } + self.settingsView.callback = { [unowned self] in self.usageReader?.getDetails() self.usageReader?.read() @@ -90,6 +107,9 @@ public class Network: Module { if let reader = self.usageReader { self.addReader(reader) } + if let reader = self.processReader { + self.addReader(reader) + } } public override func isAvailable() -> Bool { diff --git a/Modules/Net/popup.swift b/Modules/Net/popup.swift index a1816da7..8d33d4bc 100644 --- a/Modules/Net/popup.swift +++ b/Modules/Net/popup.swift @@ -14,8 +14,9 @@ import ModuleKit import StatsKit internal class Popup: NSView { - let dashboardHeight: CGFloat = 90 - let detailsHeight: CGFloat = 110 + private let dashboardHeight: CGFloat = 90 + private let detailsHeight: CGFloat = 110 + private let processesHeight: CGFloat = 22*5 private var dashboardView: NSView? = nil @@ -37,11 +38,14 @@ internal class Popup: NSView { private var initialized: Bool = false + private var processes: [NetworkProcessView] = [] + public init() { - super.init(frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: dashboardHeight + Constants.Popup.separatorHeight + detailsHeight)) + super.init(frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: dashboardHeight + (Constants.Popup.separatorHeight*2) + detailsHeight + processesHeight)) initDashboard() initDetails() + initProcesses() } required init?(coder: NSCoder) { @@ -157,6 +161,23 @@ internal class Popup: NSView { self.addSubview(view) } + private func initProcesses() { + let separator = SeparatorView("Top processes", origin: NSPoint(x: 0, y: self.processesHeight), width: self.frame.width) + self.addSubview(separator) + + let view: NSView = NSView(frame: NSRect(x: 0, y: 0, width: self.frame.width, height: self.processesHeight)) + + self.processes.append(NetworkProcessView(0)) + self.processes.append(NetworkProcessView(1)) + self.processes.append(NetworkProcessView(2)) + self.processes.append(NetworkProcessView(3)) + self.processes.append(NetworkProcessView(4)) + + self.processes.forEach{ view.addSubview($0) } + + self.addSubview(view) + } + public func usageCallback(_ value: Network_Usage) { DispatchQueue.main.async(execute: { if !(self.window?.isVisible ?? false) && self.initialized { @@ -199,6 +220,20 @@ internal class Popup: NSView { self.initialized = true }) } + + public func processCallback(_ list: [Network_Process]) { + DispatchQueue.main.async(execute: { + for i in 0.. { return (upload: Int64(data?.pointee.ifi_obytes ?? 0), download: Int64(data?.pointee.ifi_ibytes ?? 0)) } } + +public class ProcessReader: Reader<[Network_Process]> { + private var previous: [Network_Process] = [] + + public override func setup() { + self.popup = true + } + + public override func read() { + let task = Process() + task.launchPath = "/usr/bin/nettop" + task.arguments = ["-P", "-L", "1", "-k", "time,interface,state,rx_dupe,rx_ooo,re-tx,rtt_avg,rcvsize,tx_win,tc_class,tc_mgt,cc_algo,P,C,R,W,arch"] + + 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 list: [Network_Process] = [] + var firstLine = false + output.enumerateLines { (line, _) -> () in + if !firstLine { + firstLine = true + return + } + + let parsedLine = line.split(separator: ",") + guard parsedLine.count >= 3 else { + return + } + + var process = Network_Process() + process.time = Date() + + let nameArray = parsedLine[0].split(separator: ".") + if let pid = nameArray.last { + process.pid = String(pid) + } + if let app = NSRunningApplication(processIdentifier: pid_t(process.pid) ?? 0) { + process.name = app.localizedName ?? nameArray.dropLast().joined(separator: ".") + } else { + process.name = nameArray.dropLast().joined(separator: ".") + } + + if let download = Int(parsedLine[1]) { + process.download = download + } + if let upload = Int(parsedLine[2]) { + process.upload = upload + } + + list.append(process) + } + + var processes: [Network_Process] = [] + if self.previous.count == 0 { + self.previous = list + processes = list + } else { + self.previous.forEach { (pp: Network_Process) in + if let i = list.firstIndex(where: { $0.pid == pp.pid }) { + let p = list[i] + + let download = p.download - pp.download + let upload = p.upload - pp.upload + let time = download == 0 && upload == 0 ? pp.time : Date() + list[i].time = time + + processes.append(Network_Process(time: time, name: p.name, pid: p.pid, download: download, upload: upload)) + } + } + self.previous = list + } + + processes.sort { + if $0.download != $1.download { + return $0.download < $1.download + } else if $0.upload < $1.upload { + return $0.upload < $1.upload + } else { + return $0.time < $1.time + } + } + + self.callback(processes.suffix(5).reversed()) + } +} diff --git a/StatsKit/extensions.swift b/StatsKit/extensions.swift index 497eaa38..0384b2a3 100644 --- a/StatsKit/extensions.swift +++ b/StatsKit/extensions.swift @@ -822,11 +822,13 @@ public func showNotification(title: String, subtitle: String, id: String = UUID( public struct TopProcess { public var pid: Int public var command: String + public var name: String? public var usage: Double - public init(pid: Int, command: String, usage: Double) { + public init(pid: Int, command: String, name: String?, usage: Double) { self.pid = pid self.command = command + self.name = name self.usage = usage } }