mirror of
https://github.com/morgan9e/macos-stats
synced 2026-04-14 00:04:15 +09:00
feat: rewrite GPU popup view from scratch. Was added more information about each GPU (#191). Only available chart (temperature and usage) will be visible for GPU.
This commit is contained in:
@@ -23,25 +23,40 @@ public enum GPU_types: GPU_type {
|
||||
}
|
||||
|
||||
public struct GPU_Info {
|
||||
public let model: String
|
||||
public let IOClass: String
|
||||
public let id: String
|
||||
public let type: GPU_type
|
||||
|
||||
public let IOClass: String
|
||||
public var vendor: String? = nil
|
||||
public let model: String
|
||||
|
||||
public var state: Bool = true
|
||||
|
||||
public var utilization: Double = 0
|
||||
public var temperature: Int = 0
|
||||
public var fanSpeed: Int? = nil
|
||||
public var coreClock: Int? = nil
|
||||
public var memoryClock: Int? = nil
|
||||
public var temperature: Double? = nil
|
||||
public var utilization: Double? = nil
|
||||
|
||||
init(type: GPU_type, IOClass: String, vendor: String? = nil, model: String) {
|
||||
self.id = UUID().uuidString
|
||||
self.type = type
|
||||
self.IOClass = IOClass
|
||||
self.vendor = vendor
|
||||
self.model = model
|
||||
}
|
||||
}
|
||||
|
||||
public struct GPUs: value_t {
|
||||
public var list: [GPU_Info] = []
|
||||
|
||||
internal func active() -> [GPU_Info] {
|
||||
return self.list.filter{ $0.state }
|
||||
return self.list.filter{ $0.state && $0.utilization != nil }.sorted{ $0.utilization ?? 0 > $1.utilization ?? 0 }
|
||||
}
|
||||
|
||||
public var widget_value: Double {
|
||||
get {
|
||||
return list.isEmpty ? 0 : list[0].utilization
|
||||
return list.isEmpty ? 0 : (list[0].utilization ?? 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -116,16 +131,19 @@ public class GPU: Module {
|
||||
return
|
||||
}
|
||||
let selectedGPU: GPU_Info = activeGPUs.first{ $0.model == self.selectedGPU } ?? activeGPU
|
||||
guard let utilization = selectedGPU.utilization else {
|
||||
return
|
||||
}
|
||||
|
||||
if let widget = self.widget as? Mini {
|
||||
widget.setValue(selectedGPU.utilization)
|
||||
widget.setValue(utilization)
|
||||
widget.setTitle(self.showType ? "\(selectedGPU.type)GPU" : nil)
|
||||
}
|
||||
if let widget = self.widget as? LineChart {
|
||||
widget.setValue(selectedGPU.utilization)
|
||||
widget.setValue(utilization)
|
||||
}
|
||||
if let widget = self.widget as? BarChart {
|
||||
widget.setValue([selectedGPU.utilization])
|
||||
widget.setValue([utilization])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,14 +13,14 @@ import Cocoa
|
||||
import StatsKit
|
||||
import ModuleKit
|
||||
|
||||
internal class Popup: NSView, Popup_p {
|
||||
private var list: [String: GPUView] = [:]
|
||||
private let gpuViewHeight: CGFloat = 162
|
||||
|
||||
internal class Popup: NSStackView, Popup_p {
|
||||
public var sizeCallback: ((NSSize) -> Void)? = nil
|
||||
|
||||
public init() {
|
||||
super.init(frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: 0))
|
||||
|
||||
self.orientation = .vertical
|
||||
self.spacing = Constants.Popup.margins
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
@@ -28,31 +28,29 @@ internal class Popup: NSView, Popup_p {
|
||||
}
|
||||
|
||||
internal func infoCallback(_ value: GPUs) {
|
||||
if self.list.count != value.list.count {
|
||||
self.subviews.forEach{ $0.removeFromSuperview() }
|
||||
self.list = [:]
|
||||
let views = self.arrangedSubviews.filter{ $0 is GPUView }.map{ $0 as! GPUView }
|
||||
|
||||
if views.count != value.list.count {
|
||||
self.arrangedSubviews.forEach{ $0.removeFromSuperview() }
|
||||
}
|
||||
|
||||
value.list.forEach { (graphics: GPU_Info) in
|
||||
if let gpu = self.list[graphics.model] {
|
||||
gpu.update(graphics)
|
||||
value.list.reversed().forEach { (gpu: GPU_Info) in
|
||||
if let view = views.first(where: { $0.value.id == gpu.id }) {
|
||||
view.update(gpu)
|
||||
} else {
|
||||
let gpu = GPUView(
|
||||
NSRect(
|
||||
x: 0,
|
||||
y: (self.gpuViewHeight + Constants.Popup.margins) * CGFloat(self.list.count),
|
||||
width: self.frame.width,
|
||||
height: self.gpuViewHeight
|
||||
),
|
||||
gpu: graphics
|
||||
)
|
||||
|
||||
self.list[graphics.model] = gpu
|
||||
self.addSubview(gpu)
|
||||
self.addArrangedSubview(GPUView(
|
||||
width: self.frame.width,
|
||||
gpu: gpu,
|
||||
callback: self.recalculateHeight
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
let h: CGFloat = ((self.gpuViewHeight + Constants.Popup.margins) * CGFloat(self.list.count)) - Constants.Popup.margins
|
||||
self.recalculateHeight()
|
||||
}
|
||||
|
||||
private func recalculateHeight() {
|
||||
let h = self.arrangedSubviews.map({ $0.bounds.height + self.spacing }).reduce(0, +) - self.spacing
|
||||
if self.frame.size.height != h {
|
||||
self.setFrameSize(NSSize(width: self.frame.width, height: h))
|
||||
self.sizeCallback?(self.frame.size)
|
||||
@@ -60,46 +58,65 @@ internal class Popup: NSView, Popup_p {
|
||||
}
|
||||
}
|
||||
|
||||
private class GPUView: NSView {
|
||||
private let height: CGFloat = 60
|
||||
private let margin: CGFloat = 4
|
||||
private class GPUView: NSStackView {
|
||||
public var value: GPU_Info
|
||||
private var detailsView: GPUDetails
|
||||
private let circleSize: CGFloat = 50
|
||||
private let chartSize: CGFloat = 60
|
||||
|
||||
private var value: GPU_Info
|
||||
private var stateView: NSView? = nil
|
||||
private var circleRow: NSStackView? = nil
|
||||
private var chartRow: NSStackView? = nil
|
||||
|
||||
private var temperatureChart: LineChartView? = nil
|
||||
private var utilizationChart: LineChartView? = nil
|
||||
private var temperatureCirle: HalfCircleGraphView? = nil
|
||||
private var utilizationCircle: HalfCircleGraphView? = nil
|
||||
|
||||
private var stateView: NSView? = nil
|
||||
public var sizeCallback: (() -> Void)
|
||||
|
||||
public init(_ frame: NSRect, gpu: GPU_Info) {
|
||||
open override var intrinsicContentSize: CGSize {
|
||||
return CGSize(width: self.bounds.width, height: self.bounds.height)
|
||||
}
|
||||
|
||||
public init(width: CGFloat, gpu: GPU_Info, callback: @escaping (() -> Void)) {
|
||||
self.value = gpu
|
||||
self.detailsView = GPUDetails(width: width, value: gpu)
|
||||
self.sizeCallback = callback
|
||||
|
||||
super.init(frame: frame)
|
||||
super.init(frame: NSRect(x: 0, y: 0, width: width, height: 0))
|
||||
|
||||
self.orientation = .vertical
|
||||
self.alignment = .centerX
|
||||
self.distribution = .fillProportionally
|
||||
self.spacing = 0
|
||||
self.wantsLayer = true
|
||||
self.layer?.cornerRadius = 2
|
||||
|
||||
self.initName()
|
||||
self.initTemperature()
|
||||
self.initUtilization()
|
||||
self.addArrangedSubview(self.title())
|
||||
self.addArrangedSubview(self.stats())
|
||||
self.addArrangedSubview(NSView())
|
||||
|
||||
let h = self.arrangedSubviews.map({ $0.bounds.height }).reduce(0, +)
|
||||
self.setFrameSize(NSSize(width: self.frame.width, height: h))
|
||||
self.sizeCallback()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private func initName() {
|
||||
let y: CGFloat = self.frame.height - 23
|
||||
let width: CGFloat = self.value.model.widthOfString(usingFont: NSFont.systemFont(ofSize: 12, weight: .medium)) + 16
|
||||
override func updateLayer() {
|
||||
self.layer?.backgroundColor = isDarkMode ? NSColor(hexString: "#111111", alpha: 0.25).cgColor : NSColor(hexString: "#f5f5f5", alpha: 1).cgColor
|
||||
}
|
||||
|
||||
private func title() -> NSView {
|
||||
let view: NSView = NSView(frame: NSRect(x: 0, y: 0, width: self.frame.width, height: 24))
|
||||
view.heightAnchor.constraint(equalToConstant: view.bounds.height).isActive = true
|
||||
|
||||
let view: NSView = NSView(frame: NSRect(x: (self.frame.width - width)/2, y: y, width: width, height: 20))
|
||||
|
||||
let labelView: NSTextField = TextView(frame: NSRect(x: 0, y: (view.frame.height-15)/2, width: width - 8, height: 15))
|
||||
let width: CGFloat = self.value.model.widthOfString(usingFont: NSFont.systemFont(ofSize: 13, weight: .regular)) + 16
|
||||
let labelView: NSTextField = TextView(frame: NSRect(x: 0, y: (view.frame.height-16)/2, width: width - 8, height: 16))
|
||||
labelView.alignment = .center
|
||||
labelView.textColor = .secondaryLabelColor
|
||||
labelView.font = NSFont.systemFont(ofSize: 12, weight: .medium)
|
||||
labelView.font = NSFont.systemFont(ofSize: 13, weight: .regular)
|
||||
labelView.stringValue = self.value.model
|
||||
|
||||
let stateView: NSView = NSView(frame: NSRect(x: width - 8, y: (view.frame.height-7)/2, width: 6, height: 6))
|
||||
@@ -108,110 +125,264 @@ private class GPUView: NSView {
|
||||
stateView.toolTip = "GPU \(self.value.state ? "enabled" : "disabled")"
|
||||
stateView.layer?.cornerRadius = 4
|
||||
|
||||
let details = LocalizedString("Details").uppercased()
|
||||
let w = details.widthOfString(usingFont: NSFont.systemFont(ofSize: 9, weight: .regular)) + 8
|
||||
let button = NSButtonWithPadding()
|
||||
button.frame = CGRect(x: view.frame.width - w, y: 2, width: w, height: view.frame.height-2)
|
||||
button.verticalPadding = 9
|
||||
button.bezelStyle = .regularSquare
|
||||
button.translatesAutoresizingMaskIntoConstraints = false
|
||||
button.isBordered = false
|
||||
button.action = #selector(self.showDetails)
|
||||
button.target = self
|
||||
button.toolTip = LocalizedString("Details")
|
||||
button.title = details
|
||||
button.font = NSFont.systemFont(ofSize: 9, weight: .regular)
|
||||
|
||||
view.addSubview(labelView)
|
||||
view.addSubview(stateView)
|
||||
|
||||
self.addSubview(view)
|
||||
view.addSubview(button)
|
||||
self.stateView = stateView
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
private func initTemperature() {
|
||||
let view: NSView = NSView(frame: NSRect(
|
||||
x: self.margin,
|
||||
y: self.height + (self.margin*2),
|
||||
width: self.frame.width - (self.margin*2),
|
||||
height: self.height
|
||||
))
|
||||
private func stats() -> NSView {
|
||||
let view: NSView = NSView(frame: NSRect(x: 0, y: 0, width: self.frame.width, height: 0))
|
||||
let container: NSStackView = NSStackView(frame: NSRect(x: 0, y: 0, width: self.frame.width, height: 0))
|
||||
container.orientation = .vertical
|
||||
container.spacing = 0
|
||||
|
||||
let circleWidth: CGFloat = 70
|
||||
let circleSize: CGFloat = 44
|
||||
let circles: NSStackView = NSStackView(frame: NSRect(x: 0, y: 0, width: self.frame.width, height: 0))
|
||||
circles.orientation = .horizontal
|
||||
circles.distribution = .fillEqually
|
||||
circles.alignment = .bottom
|
||||
self.circleRow = circles
|
||||
|
||||
let chartView: NSView = NSView(frame: NSRect(
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: view.frame.width - circleWidth,
|
||||
height: view.frame.height
|
||||
))
|
||||
chartView.wantsLayer = true
|
||||
chartView.layer?.backgroundColor = NSColor.lightGray.withAlphaComponent(0.1).cgColor
|
||||
chartView.layer?.cornerRadius = 3
|
||||
self.temperatureChart = LineChartView(frame: NSRect(x: 0, y: 0, width: chartView.frame.width, height: chartView.frame.height), num: 120)
|
||||
chartView.addSubview(self.temperatureChart!)
|
||||
let charts: NSStackView = NSStackView(frame: NSRect(x: 0, y: 0, width: self.frame.width, height: 0))
|
||||
charts.orientation = .horizontal
|
||||
charts.distribution = .fillEqually
|
||||
self.chartRow = charts
|
||||
|
||||
self.temperatureCirle = HalfCircleGraphView(frame: NSRect(
|
||||
x: (view.frame.width - circleWidth) + (circleWidth - circleSize)/2,
|
||||
y: (view.frame.height - circleSize)/2 - 3,
|
||||
width: circleSize,
|
||||
height: circleSize
|
||||
))
|
||||
self.temperatureCirle!.toolTip = LocalizedString("GPU temperature")
|
||||
self.addStats(id: "temperature", self.value.temperature)
|
||||
self.addStats(id: "utilization", self.value.utilization)
|
||||
|
||||
view.addSubview(chartView)
|
||||
view.addSubview(self.temperatureCirle!)
|
||||
container.addArrangedSubview(circles)
|
||||
container.addArrangedSubview(charts)
|
||||
|
||||
self.temperatureCirle?.setValue(Double(self.value.temperature))
|
||||
self.temperatureCirle?.setText(Temperature(Double(self.value.temperature)))
|
||||
self.temperatureChart?.addValue(Double(self.value.temperature) / 100)
|
||||
view.addSubview(container)
|
||||
|
||||
self.addSubview(view)
|
||||
let h = container.arrangedSubviews.map({ $0.bounds.height }).reduce(0, +)
|
||||
view.setFrameSize(NSSize(width: self.frame.width, height: h))
|
||||
container.setFrameSize(NSSize(width: self.frame.width, height: view.bounds.height))
|
||||
view.heightAnchor.constraint(equalToConstant: view.bounds.height).isActive = true
|
||||
container.heightAnchor.constraint(equalToConstant: view.bounds.height).isActive = true
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
private func initUtilization() {
|
||||
let view: NSView = NSView(frame: NSRect(
|
||||
x: self.margin,
|
||||
y: self.margin,
|
||||
width: self.frame.width - (self.margin*2),
|
||||
height: self.height
|
||||
))
|
||||
private func addStats(id: String, _ val: Double? = nil) {
|
||||
guard let value = val else {
|
||||
return
|
||||
}
|
||||
|
||||
let circleWidth: CGFloat = 70
|
||||
let circleSize: CGFloat = 44
|
||||
var circle: HalfCircleGraphView
|
||||
var chart: LineChartView
|
||||
|
||||
let chartView: NSView = NSView(frame: NSRect(
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: view.frame.width - circleWidth,
|
||||
height: view.frame.height
|
||||
))
|
||||
chartView.wantsLayer = true
|
||||
chartView.layer?.backgroundColor = NSColor.lightGray.withAlphaComponent(0.1).cgColor
|
||||
chartView.layer?.cornerRadius = 3
|
||||
self.utilizationChart = LineChartView(frame: NSRect(x: 0, y: 0, width: chartView.frame.width, height: chartView.frame.height), num: 120)
|
||||
chartView.addSubview(self.utilizationChart!)
|
||||
if let view = self.circleRow?.arrangedSubviews.filter({ $0 is HalfCircleGraphView }).first(where: { ($0 as! HalfCircleGraphView).id == id }) {
|
||||
circle = view as! HalfCircleGraphView
|
||||
} else {
|
||||
circle = HalfCircleGraphView(frame: NSRect(x: 0, y: 0, width: circleSize, height: circleSize))
|
||||
circle.id = id
|
||||
circle.toolTip = "GPU \(id)"
|
||||
if let row = self.circleRow {
|
||||
row.setFrameSize(NSSize(width: row.frame.width, height: self.circleSize + 20))
|
||||
row.edgeInsets = NSEdgeInsets(top: 10, left: 0, bottom: 0, right: 0)
|
||||
row.heightAnchor.constraint(equalToConstant: row.bounds.height).isActive = true
|
||||
row.addArrangedSubview(circle)
|
||||
}
|
||||
}
|
||||
|
||||
self.utilizationCircle = HalfCircleGraphView(frame: NSRect(
|
||||
x: (view.frame.width - circleWidth) + (circleWidth - circleSize)/2,
|
||||
y: (view.frame.height - circleSize)/2 - 3,
|
||||
width: circleSize,
|
||||
height: circleSize
|
||||
))
|
||||
self.utilizationCircle!.toolTip = LocalizedString("GPU utilization")
|
||||
if let view = self.chartRow?.arrangedSubviews.filter({ $0 is LineChartView }).first(where: { ($0 as! LineChartView).id == id }) {
|
||||
chart = view as! LineChartView
|
||||
} else {
|
||||
chart = LineChartView(frame: NSRect(x: 0, y: 0, width: 100, height: self.chartSize), num: 120)
|
||||
chart.wantsLayer = true
|
||||
chart.layer?.backgroundColor = NSColor.lightGray.withAlphaComponent(0.1).cgColor
|
||||
chart.layer?.cornerRadius = 3
|
||||
chart.id = id
|
||||
chart.toolTip = "GPU \(id)"
|
||||
if let row = self.chartRow {
|
||||
row.setFrameSize(NSSize(width: row.frame.width, height: self.chartSize + 20))
|
||||
row.spacing = Constants.Popup.margins
|
||||
row.edgeInsets = NSEdgeInsets(
|
||||
top: Constants.Popup.margins,
|
||||
left: Constants.Popup.margins,
|
||||
bottom: Constants.Popup.margins,
|
||||
right: Constants.Popup.margins
|
||||
)
|
||||
row.heightAnchor.constraint(equalToConstant: row.bounds.height).isActive = true
|
||||
row.addArrangedSubview(chart)
|
||||
}
|
||||
}
|
||||
|
||||
view.addSubview(chartView)
|
||||
view.addSubview(self.utilizationCircle!)
|
||||
|
||||
self.utilizationCircle?.setValue(self.value.utilization)
|
||||
self.utilizationCircle?.setText("\(Int(self.value.utilization*100))%")
|
||||
self.utilizationChart?.addValue(self.value.utilization)
|
||||
|
||||
self.addSubview(view)
|
||||
if id == "temperature" {
|
||||
circle.setValue(value)
|
||||
circle.setText(Temperature(value))
|
||||
|
||||
if self.temperatureChart == nil {
|
||||
self.temperatureChart = chart
|
||||
}
|
||||
} else if id == "utilization" {
|
||||
circle.setValue(value)
|
||||
circle.setText("\(Int(value*100))%")
|
||||
|
||||
if self.utilizationChart == nil {
|
||||
self.utilizationChart = chart
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func update(_ gpu: GPU_Info) {
|
||||
DispatchQueue.main.async(execute: {
|
||||
if (self.window?.isVisible ?? false) {
|
||||
self.stateView?.layer?.backgroundColor = (gpu.state ? NSColor.systemGreen : NSColor.systemRed).cgColor
|
||||
self.stateView?.toolTip = "GPU \(gpu.state ? "enabled" : "disabled")"
|
||||
|
||||
self.temperatureCirle?.setValue(Double(gpu.temperature))
|
||||
self.temperatureCirle?.setText(Temperature(Double(gpu.temperature)))
|
||||
|
||||
self.utilizationCircle?.setValue(gpu.utilization)
|
||||
self.utilizationCircle?.setText("\(Int(gpu.utilization*100))%")
|
||||
}
|
||||
self.detailsView.update(gpu)
|
||||
|
||||
if (self.window?.isVisible ?? false) {
|
||||
self.stateView?.layer?.backgroundColor = (gpu.state ? NSColor.systemGreen : NSColor.systemRed).cgColor
|
||||
self.stateView?.toolTip = "GPU \(gpu.state ? "enabled" : "disabled")"
|
||||
|
||||
self.temperatureChart?.addValue(Double(gpu.temperature) / 100)
|
||||
self.utilizationChart?.addValue(gpu.utilization)
|
||||
})
|
||||
self.addStats(id: "temperature", gpu.temperature)
|
||||
self.addStats(id: "utilization", gpu.utilization)
|
||||
}
|
||||
|
||||
if let value = gpu.temperature {
|
||||
self.temperatureChart?.addValue(value/100)
|
||||
}
|
||||
if let value = gpu.utilization {
|
||||
self.utilizationChart?.addValue(value)
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func showDetails() {
|
||||
if let view = self.arrangedSubviews.first(where: { $0 is GPUDetails }) {
|
||||
view.removeFromSuperview()
|
||||
} else {
|
||||
self.insertArrangedSubview(self.detailsView, at: 1)
|
||||
}
|
||||
|
||||
self.setFrameSize(NSSize(
|
||||
width: self.frame.width,
|
||||
height: self.arrangedSubviews.map({ $0.bounds.height + self.spacing }).reduce(0, +)
|
||||
))
|
||||
self.sizeCallback()
|
||||
}
|
||||
}
|
||||
|
||||
private class GPUDetails: NSView {
|
||||
private var status: NSTextField? = nil
|
||||
private var fanSpeed: NSTextField? = nil
|
||||
private var coreClock: NSTextField? = nil
|
||||
private var memoryClock: NSTextField? = nil
|
||||
private var temperature: NSTextField? = nil
|
||||
private var utilization: NSTextField? = nil
|
||||
|
||||
open override var intrinsicContentSize: CGSize {
|
||||
return CGSize(width: self.bounds.width, height: self.bounds.height)
|
||||
}
|
||||
|
||||
init(width: CGFloat, value: GPU_Info) {
|
||||
super.init(frame: NSRect(x: 0, y: 0, width: width, height: 0))
|
||||
|
||||
let grid: NSGridView = NSGridView(frame: NSRect(
|
||||
x: Constants.Popup.margins,
|
||||
y: Constants.Popup.margins,
|
||||
width: self.frame.width - (Constants.Popup.margins*2),
|
||||
height: 0
|
||||
))
|
||||
grid.yPlacement = .center
|
||||
grid.xPlacement = .leading
|
||||
grid.rowSpacing = 0
|
||||
grid.columnSpacing = 0
|
||||
|
||||
var num: CGFloat = 2
|
||||
|
||||
if let value = value.vendor {
|
||||
grid.addRow(with: keyValueRow("\(LocalizedString("Vendor")):", value))
|
||||
num += 1
|
||||
}
|
||||
|
||||
grid.addRow(with: keyValueRow("\(LocalizedString("Model")):", value.model))
|
||||
|
||||
let state: String = value.state ? LocalizedString("Active") : LocalizedString("Non active")
|
||||
let arr = keyValueRow("\(LocalizedString("Status")):", state)
|
||||
self.status = arr.last
|
||||
grid.addRow(with: arr)
|
||||
|
||||
if let value = value.fanSpeed {
|
||||
let arr = keyValueRow("\(LocalizedString("Fan speed")):", "\(value)%")
|
||||
self.fanSpeed = arr.last
|
||||
grid.addRow(with: arr)
|
||||
num += 1
|
||||
}
|
||||
if let value = value.coreClock {
|
||||
let arr = keyValueRow("\(LocalizedString("Core clock")):", "\(value)MHz")
|
||||
self.coreClock = arr.last
|
||||
grid.addRow(with: arr)
|
||||
num += 1
|
||||
}
|
||||
if let value = value.memoryClock {
|
||||
let arr = keyValueRow("\(LocalizedString("Memory clock")):", "\(value)MHz")
|
||||
self.memoryClock = arr.last
|
||||
grid.addRow(with: arr)
|
||||
num += 1
|
||||
}
|
||||
|
||||
if let value = value.temperature {
|
||||
let arr = keyValueRow("\(LocalizedString("Temperature")):", Temperature(Double(value)))
|
||||
self.temperature = arr.last
|
||||
grid.addRow(with: arr)
|
||||
num += 1
|
||||
}
|
||||
if let value = value.utilization {
|
||||
let arr = keyValueRow("\(LocalizedString("Utilization")):", "\(Int(value*100))%")
|
||||
self.utilization = arr.last
|
||||
grid.addRow(with: arr)
|
||||
num += 1
|
||||
}
|
||||
|
||||
self.setFrameSize(NSSize(width: self.frame.width, height: (16 * num) + Constants.Popup.margins))
|
||||
grid.setFrameSize(NSSize(width: grid.frame.width, height: self.frame.height - Constants.Popup.margins))
|
||||
self.addSubview(grid)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private func keyValueRow(_ key: String, _ value: String) -> [NSTextField] {
|
||||
return [
|
||||
LabelField(frame: NSRect(x: 0, y: 0, width: 0, height: 16), key),
|
||||
ValueField(frame: NSRect(x: 0, y: 0, width: 0, height: 16), value)
|
||||
]
|
||||
}
|
||||
|
||||
public func update(_ gpu: GPU_Info) {
|
||||
self.status?.stringValue = gpu.state ? LocalizedString("Active") : LocalizedString("Non active")
|
||||
|
||||
if let value = gpu.fanSpeed {
|
||||
self.fanSpeed?.stringValue = "\(value)%"
|
||||
}
|
||||
if let value = gpu.coreClock {
|
||||
self.coreClock?.stringValue = "\(value)MHz"
|
||||
}
|
||||
if let value = gpu.memoryClock {
|
||||
self.memoryClock?.stringValue = "\(value)MHz"
|
||||
}
|
||||
|
||||
if let value = gpu.temperature {
|
||||
self.temperature?.stringValue = Temperature(Double(value))
|
||||
}
|
||||
if let value = gpu.utilization {
|
||||
self.utilization?.stringValue = "\(Int(value*100))%"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,17 @@ import ModuleKit
|
||||
import os.log
|
||||
|
||||
public struct device {
|
||||
public let vendor: String?
|
||||
public let model: String
|
||||
public let pci: String
|
||||
public var used: Bool
|
||||
}
|
||||
|
||||
let vendors: [Data: String] = [
|
||||
Data.init([0x86, 0x80, 0x00, 0x00]): "Intel",
|
||||
Data.init([0x02, 0x10, 0x00, 0x00]): "AMD"
|
||||
]
|
||||
|
||||
internal class InfoReader: Reader<GPUs> {
|
||||
internal var smc: UnsafePointer<SMCService>? = nil
|
||||
|
||||
@@ -45,7 +51,17 @@ internal class InfoReader: Reader<GPUs> {
|
||||
}
|
||||
let model = modelName.replacingOccurrences(of: "\0", with: "")
|
||||
|
||||
self.devices.append(device(model: model, pci: pci, used: false))
|
||||
var vendor: String? = nil
|
||||
if let v = vendors.first(where: { $0.key == vendorID }) {
|
||||
vendor = v.value
|
||||
}
|
||||
|
||||
self.devices.append(device(
|
||||
vendor: vendor,
|
||||
model: model,
|
||||
pci: pci,
|
||||
used: false
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,12 +82,14 @@ internal class InfoReader: Reader<GPUs> {
|
||||
return
|
||||
}
|
||||
|
||||
var vendor: String? = nil
|
||||
var model: String = ""
|
||||
let accMatch = (accelerator["IOPCIMatch"] as? String ?? accelerator["IOPCIPrimaryMatch"] as? String ?? "").lowercased()
|
||||
|
||||
for (i, device) in devices.enumerated() {
|
||||
if accMatch.range(of: device.pci) != nil && !device.used {
|
||||
model = device.model
|
||||
vendor = device.vendor
|
||||
devices[i].used = true
|
||||
break
|
||||
}
|
||||
@@ -81,8 +99,11 @@ internal class InfoReader: Reader<GPUs> {
|
||||
var predictModel = ""
|
||||
var type: GPU_types = .unknown
|
||||
|
||||
let utilization = stats["Device Utilization %"] as? Int ?? stats["GPU Activity(%)"] as? Int ?? 0
|
||||
var temperature = stats["Temperature(C)"] as? Int ?? 0
|
||||
let utilization: Int? = stats["Device Utilization %"] as? Int ?? stats["GPU Activity(%)"] as? Int ?? nil
|
||||
var temperature: Int? = stats["Temperature(C)"] as? Int ?? nil
|
||||
let fanSpeed: Int? = stats["Fan Speed(%)"] as? Int ?? nil
|
||||
let coreClock: Int? = stats["Core Clock(MHz)"] as? Int ?? nil
|
||||
let memoryClock: Int? = stats["Memory Clock(MHz)"] as? Int ?? nil
|
||||
|
||||
if ioClass == "nvaccelerator" || ioClass.contains("nvidia") { // nvidia
|
||||
predictModel = "Nvidia Graphics"
|
||||
@@ -91,8 +112,8 @@ internal class InfoReader: Reader<GPUs> {
|
||||
predictModel = "AMD Graphics"
|
||||
type = .discrete
|
||||
|
||||
if temperature == 0 {
|
||||
if let tmp = self.smc?.pointee.getValue("TGDD") {
|
||||
if temperature == nil || temperature == 0 {
|
||||
if let tmp = self.smc?.pointee.getValue("TGDD"), tmp != 128 {
|
||||
temperature = Int(tmp)
|
||||
}
|
||||
}
|
||||
@@ -100,8 +121,8 @@ internal class InfoReader: Reader<GPUs> {
|
||||
predictModel = "Intel Graphics"
|
||||
type = .integrated
|
||||
|
||||
if temperature == 0 {
|
||||
if let tmp = self.smc?.pointee.getValue("TCGC") {
|
||||
if temperature == nil || temperature == 0 {
|
||||
if let tmp = self.smc?.pointee.getValue("TCGC"), tmp != 128 {
|
||||
temperature = Int(tmp)
|
||||
}
|
||||
}
|
||||
@@ -116,9 +137,17 @@ internal class InfoReader: Reader<GPUs> {
|
||||
if model == "" {
|
||||
model = predictModel
|
||||
}
|
||||
if let v = vendor {
|
||||
model = model.removedRegexMatches(pattern: v, replaceWith: "").trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}
|
||||
|
||||
if self.gpus.list.first(where: { $0.model == model }) == nil {
|
||||
self.gpus.list.append(GPU_Info(model: model, IOClass: IOClass, type: type.rawValue))
|
||||
self.gpus.list.append(GPU_Info(
|
||||
type: type.rawValue,
|
||||
IOClass: IOClass,
|
||||
vendor: vendor,
|
||||
model: model
|
||||
))
|
||||
}
|
||||
guard let idx = self.gpus.list.firstIndex(where: { $0.model == model }) else {
|
||||
return
|
||||
@@ -128,8 +157,21 @@ internal class InfoReader: Reader<GPUs> {
|
||||
self.gpus.list[idx].state = state == 0
|
||||
}
|
||||
|
||||
self.gpus.list[idx].utilization = utilization == 0 ? 0 : Double(utilization)/100
|
||||
self.gpus.list[idx].temperature = temperature
|
||||
if let value = utilization {
|
||||
self.gpus.list[idx].utilization = Double(value)/100
|
||||
}
|
||||
if let value = temperature {
|
||||
self.gpus.list[idx].temperature = Double(value)
|
||||
}
|
||||
if let value = fanSpeed {
|
||||
self.gpus.list[idx].fanSpeed = value
|
||||
}
|
||||
if let value = coreClock {
|
||||
self.gpus.list[idx].coreClock = value
|
||||
}
|
||||
if let value = memoryClock {
|
||||
self.gpus.list[idx].memoryClock = value
|
||||
}
|
||||
}
|
||||
|
||||
self.gpus.list.sort{ !$0.state && $1.state }
|
||||
|
||||
@@ -94,23 +94,21 @@ internal class Settings: NSView, Settings_v {
|
||||
}
|
||||
|
||||
private func addGPUSelector(frame: NSRect) {
|
||||
let view: NSView = NSView(frame: frame)
|
||||
let view: NSGridView = NSGridView(frame: frame)
|
||||
view.yPlacement = .center
|
||||
view.wantsLayer = true
|
||||
view.layer?.backgroundColor = NSColor.red.cgColor
|
||||
|
||||
let rowTitle: NSTextField = LabelField(frame: NSRect(
|
||||
x: 0,
|
||||
y: (view.frame.height - 16)/2,
|
||||
width: view.frame.width - 52,
|
||||
height: 17
|
||||
), LocalizedString("GPU to show"))
|
||||
rowTitle.font = NSFont.systemFont(ofSize: 13, weight: .light)
|
||||
rowTitle.textColor = .textColor
|
||||
let title: NSTextField = LabelField(frame: NSRect(x: 0, y: 0, width: 100, height: 17), LocalizedString("GPU to show"))
|
||||
title.font = NSFont.systemFont(ofSize: 13, weight: .light)
|
||||
title.textColor = .textColor
|
||||
|
||||
self.button = NSPopUpButton(frame: NSRect(x: view.frame.width - 200, y: -1, width: 200, height: 30))
|
||||
self.button!.target = self
|
||||
self.button?.action = #selector(self.handleSelection)
|
||||
let button = NSPopUpButton(frame: NSRect(x: view.frame.width - 200, y: -1, width: 200, height: 30))
|
||||
button.target = self
|
||||
button.action = #selector(self.handleSelection)
|
||||
self.button = button
|
||||
|
||||
view.addSubview(rowTitle)
|
||||
view.addSubview(self.button!)
|
||||
view.addRow(with: [title, button])
|
||||
|
||||
self.addSubview(view)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user