diff --git a/ModuleKit/reader.swift b/ModuleKit/reader.swift index b141cb36..996bec2b 100644 --- a/ModuleKit/reader.swift +++ b/ModuleKit/reader.swift @@ -126,7 +126,9 @@ open class Reader: ReaderInternal_p { return } else if self.popup && self.locked { if !self.ready { - self.read() + DispatchQueue.global().async { + self.read() + } } return } @@ -138,8 +140,10 @@ open class Reader: ReaderInternal_p { self.read() }) } - - self.read() + + DispatchQueue.global().async { + self.read() + } self.repeatTask?.start() } diff --git a/Modules/Battery/main.swift b/Modules/Battery/main.swift index a21b1c1d..a65e2c90 100644 --- a/Modules/Battery/main.swift +++ b/Modules/Battery/main.swift @@ -41,6 +41,7 @@ struct Battery_Usage: value_t { public class Battery: Module { private var usageReader: UsageReader? = nil + private var processReader: ProcessReader? = nil private let popupView: Popup = Popup() private var settingsView: Settings @@ -60,6 +61,7 @@ public class Battery: Module { guard self.available else { return } self.usageReader = UsageReader() + self.processReader = ProcessReader() self.usageReader?.readyCallback = { [unowned self] in self.readyHandler() @@ -68,9 +70,18 @@ public class Battery: Module { self.usageCallback(value) } + self.processReader?.callbackHandler = { [unowned self] value in + if let list = value { + self.popupView.processCallback(list) + } + } + 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/Battery/popup.swift b/Modules/Battery/popup.swift index 06bc5540..6b959c5f 100644 --- a/Modules/Battery/popup.swift +++ b/Modules/Battery/popup.swift @@ -14,10 +14,11 @@ 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 let dashboardHeight: CGFloat = 90 + private let detailsHeight: CGFloat = 88 + private let batteryHeight: CGFloat = 66 + private let adapterHeight: CGFloat = 44 + private let processesHeight: CGFloat = 22*5 private var dashboardView: NSView? = nil private var dashboardBatteryView: BatteryView? = nil @@ -38,18 +39,21 @@ internal class Popup: NSView { private var powerField: NSTextField? = nil private var chargingStateField: NSTextField? = nil + private var processes: [ProcessView] = [] + public init() { super.init(frame: NSRect( x: 0, y: 0, width: Constants.Popup.width, - height: dashboardHeight + detailsHeight + batteryHeight + adapterHeight + (Constants.Popup.separatorHeight * 3) + height: dashboardHeight + detailsHeight + batteryHeight + adapterHeight + (Constants.Popup.separatorHeight * 4) + processesHeight )) self.initDashboard() self.initDetails() self.initBattery() self.initAdapter() + self.initProcesses() } required init?(coder: NSCoder) { @@ -127,6 +131,23 @@ internal class Popup: NSView { self.adapterView = 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(ProcessView(0)) + self.processes.append(ProcessView(1)) + self.processes.append(ProcessView(2)) + self.processes.append(ProcessView(3)) + self.processes.append(ProcessView(4)) + + self.processes.forEach{ view.addSubview($0) } + + self.addSubview(view) + } + public func usageCallback(_ value: Battery_Usage) { DispatchQueue.main.async(execute: { self.dashboardBatteryView?.setValue(abs(value.level)) @@ -165,6 +186,19 @@ internal class Popup: NSView { self.chargingStateField?.stringValue = value.isCharging ? "Yes" : "No" }) } + + public func processCallback(_ list: [TopProcess]) { + DispatchQueue.main.async(execute: { + for i in 0.. { private var service: io_connect_t = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleSmartBattery")) @@ -128,3 +130,127 @@ internal class UsageReader: Reader { return nil } } + +public class ProcessReader: Reader<[TopProcess]> { + private var task: Process? = nil + private var initialized: Bool = false + + public override func setup() { + self.popup = true + } + + public override func start() { + if !self.initialized { + DispatchQueue.global().async { + self.read() + } + self.initialized = true + return + } + + DispatchQueue.global().async { + self.task = Process() + let pipe = Pipe() + + self.task?.standardOutput = pipe + self.task?.launchPath = "/usr/bin/top" + self.task?.arguments = ["-o", "power", "-n", "5", "-stats", "pid,command,power"] + + pipe.fileHandleForReading.readabilityHandler = { (fileHandle) -> Void in + let output = String(decoding: fileHandle.availableData, as: UTF8.self) + var processes: [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 + } + + 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 processes.count != 0 { + self.callback(processes) + } + } + + self.task?.launch() + self.task?.waitUntilExit() + } + } + + public override func stop() { + if self.task == nil || !self.task!.isRunning { + return + } + + self.task?.interrupt() + self.task = nil + } + + public override func read() { + let task = Process() + task.launchPath = "/usr/bin/top" + task.arguments = ["-l", "1", "-o", "power", "-n", "5", "-stats", "pid,command,power"] + + let outputPipe = Pipe() + let errorPipe = Pipe() + + task.standardOutput = outputPipe + task.standardError = errorPipe + + do { + try task.run() + } catch let error { + os_log(.error, log: log, "top(): %s", "\(error.localizedDescription)") + 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, _) -> () 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 + } + + 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)) + } + } + + self.callback(processes) + } +} diff --git a/Modules/CPU/readers.swift b/Modules/CPU/readers.swift index 114d8fcc..5b39d293 100644 --- a/Modules/CPU/readers.swift +++ b/Modules/CPU/readers.swift @@ -12,6 +12,7 @@ import Cocoa import StatsKit import ModuleKit +import os.log internal class LoadReader: Reader { public var store: UnsafePointer? = nil @@ -96,7 +97,7 @@ internal class LoadReader: Reader { self.cpuInfo = nil self.numCpuInfo = 0 } else { - print("ERROR host_processor_info(): " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error")) + os_log(.error, log: log, "host_processor_info(): %s", "\((String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))") } let cpuInfo = hostCPULoadInfo() @@ -141,7 +142,7 @@ internal class LoadReader: Reader { } } if result != KERN_SUCCESS { - print("Error - \(#file): \(#function) - kern_result_t = \(result)") + os_log(.error, log: log, "kern_result_t: %s", "\(result)") return nil } @@ -168,7 +169,7 @@ public class ProcessReader: Reader<[TopProcess]> { do { try task.run() } catch let error { - print(error) + os_log(.error, log: log, "error read ps: %s", "\(error.localizedDescription)") return } diff --git a/Modules/Memory/readers.swift b/Modules/Memory/readers.swift index 82950391..23c8f741 100644 --- a/Modules/Memory/readers.swift +++ b/Modules/Memory/readers.swift @@ -12,6 +12,7 @@ import Cocoa import StatsKit import ModuleKit +import os.log internal class UsageReader: Reader { public var totalSize: Double = 0 @@ -32,7 +33,7 @@ internal class UsageReader: Reader { } self.totalSize = 0 - print("Error with host_info(): " + (String(cString: mach_error_string(kerr), encoding: String.Encoding.ascii) ?? "unknown error")) + os_log(.error, log: log, "host_info(): %s", "\((String(cString: mach_error_string(kerr), encoding: String.Encoding.ascii) ?? "unknown error"))") } public override func read() { @@ -74,7 +75,7 @@ internal class UsageReader: Reader { return } - print("Error with host_statistics64(): " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error")) + os_log(.error, log: log, "host_statistics64(): %s", "\((String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))") } } @@ -97,7 +98,7 @@ public class ProcessReader: Reader<[TopProcess]> { do { try task.run() } catch let error { - print(error) + os_log(.error, log: log, "top(): %s", "\(error.localizedDescription)") return }