created view for CPU module

This commit is contained in:
Serhiy Mytrovtsiy
2019-09-04 00:38:56 +02:00
parent 5a0f8ce854
commit f2571989ac
15 changed files with 630 additions and 67 deletions

13
Podfile Normal file
View File

@@ -0,0 +1,13 @@
target 'Stats' do
use_frameworks!
pod 'Charts'
end
target 'StatsLauncher' do
use_frameworks!
# Pods for StatsLauncher
end

18
Podfile.lock Normal file
View File

@@ -0,0 +1,18 @@
PODS:
- Charts (3.3.0):
- Charts/Core (= 3.3.0)
- Charts/Core (3.3.0)
DEPENDENCIES:
- Charts
SPEC REPOS:
https://github.com/cocoapods/specs.git:
- Charts
SPEC CHECKSUMS:
Charts: ec1f57f9340054155691e84d4544a1d239d382c5
PODFILE CHECKSUM: 1935eab6769b7093597e74f1286aedc1ea7c755a
COCOAPODS: 1.7.1

107
Stats.xcodeproj/project.pbxproj Executable file → Normal file
View File

@@ -3,10 +3,12 @@
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objectVersion = 51;
objects = {
/* Begin PBXBuildFile section */
556DD3DCB38374D039BECE89 /* Pods_StatsLauncher.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B76EECB31DFC37E1EA558662 /* Pods_StatsLauncher.framework */; };
628D2DE0AAA753E9F47625B0 /* Pods_Stats.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 56B63995EBD3A1D1EBD3AF38 /* Pods_Stats.framework */; };
9A09C89E22B3A7C90018426F /* Battery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A09C89D22B3A7C90018426F /* Battery.swift */; };
9A09C8A022B3A7E20018426F /* BatteryReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A09C89F22B3A7E20018426F /* BatteryReader.swift */; };
9A09C8A222B3D94D0018426F /* BatteryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A09C8A122B3D94D0018426F /* BatteryView.swift */; };
@@ -18,6 +20,8 @@
9A57A19D22A1E3270033E318 /* CPU.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A57A19C22A1E3270033E318 /* CPU.swift */; };
9A58D1B022C150C800405315 /* Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A58D1AF22C150C800405315 /* Network.swift */; };
9A58D1B222C150D700405315 /* NetworkReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A58D1B122C150D700405315 /* NetworkReader.swift */; };
9A59AE54231ED1AC007989D6 /* CPUView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A59AE53231ED1AC007989D6 /* CPUView.swift */; };
9A59AE56231EE02F007989D6 /* ChartMarker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A59AE55231EE02F007989D6 /* ChartMarker.swift */; };
9A5B1CBF229E78F0008B9D3C /* Observable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A5B1CBE229E78F0008B9D3C /* Observable.swift */; };
9A5B1CC5229E7B40008B9D3C /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A5B1CC4229E7B40008B9D3C /* Extensions.swift */; };
9A6CFC0122A1C9F5001E782D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9A6CFC0022A1C9F5001E782D /* Assets.xcassets */; };
@@ -61,6 +65,11 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
0BAA6D5F418C0F6999BD18BD /* Pods-StatsLauncher.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StatsLauncher.release.xcconfig"; path = "Target Support Files/Pods-StatsLauncher/Pods-StatsLauncher.release.xcconfig"; sourceTree = "<group>"; };
1A1A00B8A6C7702FA7C3FD9A /* Pods-Stats.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Stats.release.xcconfig"; path = "Target Support Files/Pods-Stats/Pods-Stats.release.xcconfig"; sourceTree = "<group>"; };
2650ED499D83382C9C938E76 /* Pods-StatsLauncher.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StatsLauncher.debug.xcconfig"; path = "Target Support Files/Pods-StatsLauncher/Pods-StatsLauncher.debug.xcconfig"; sourceTree = "<group>"; };
38B7BF640C895BAFBF1A44BE /* Pods-Stats.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Stats.debug.xcconfig"; path = "Target Support Files/Pods-Stats/Pods-Stats.debug.xcconfig"; sourceTree = "<group>"; };
56B63995EBD3A1D1EBD3AF38 /* Pods_Stats.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Stats.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9A09C89D22B3A7C90018426F /* Battery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Battery.swift; sourceTree = "<group>"; };
9A09C89F22B3A7E20018426F /* BatteryReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryReader.swift; sourceTree = "<group>"; };
9A09C8A122B3D94D0018426F /* BatteryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryView.swift; sourceTree = "<group>"; };
@@ -75,6 +84,8 @@
9A57A19C22A1E3270033E318 /* CPU.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPU.swift; sourceTree = "<group>"; };
9A58D1AF22C150C800405315 /* Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = "<group>"; };
9A58D1B122C150D700405315 /* NetworkReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkReader.swift; sourceTree = "<group>"; };
9A59AE53231ED1AC007989D6 /* CPUView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPUView.swift; sourceTree = "<group>"; };
9A59AE55231EE02F007989D6 /* ChartMarker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartMarker.swift; sourceTree = "<group>"; };
9A5B1CBE229E78F0008B9D3C /* Observable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Observable.swift; sourceTree = "<group>"; };
9A5B1CC4229E7B40008B9D3C /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
9A6CFC0022A1C9F5001E782D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@@ -105,6 +116,7 @@
9AFA402A22AE49A200FE90BC /* StatsLauncher.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = StatsLauncher.entitlements; sourceTree = "<group>"; };
9AFA402E22AE49AE00FE90BC /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
9AFFCB3A22B3FD0500B0E6D8 /* About.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = About.storyboard; sourceTree = "<group>"; };
B76EECB31DFC37E1EA558662 /* Pods_StatsLauncher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_StatsLauncher.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -112,6 +124,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
628D2DE0AAA753E9F47625B0 /* Pods_Stats.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -119,6 +132,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
556DD3DCB38374D039BECE89 /* Pods_StatsLauncher.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -141,6 +155,7 @@
9AFA401F22AE49A100FE90BC /* StatsLauncher */,
9A1410F6229E721100D29793 /* Products */,
9A998CD622A199920087ADE7 /* Frameworks */,
A159F8578E30ECA1F2F6028E /* Pods */,
);
sourceTree = "<group>";
};
@@ -209,6 +224,7 @@
9A5B1CBE229E78F0008B9D3C /* Observable.swift */,
9A5B1CC4229E7B40008B9D3C /* Extensions.swift */,
9A426DB722C2B5EE00C064C4 /* macAppUpdater.swift */,
9A59AE55231EE02F007989D6 /* ChartMarker.swift */,
);
path = libs;
sourceTree = "<group>";
@@ -230,6 +246,7 @@
children = (
9A57A19C22A1E3270033E318 /* CPU.swift */,
9A7B8F5D22A2A57600DEB352 /* CPUReader.swift */,
9A59AE53231ED1AC007989D6 /* CPUView.swift */,
);
path = CPU;
sourceTree = "<group>";
@@ -257,6 +274,8 @@
children = (
9A998CD922A199970087ADE7 /* ServiceManagement.framework */,
9A998CD722A199920087ADE7 /* Cocoa.framework */,
56B63995EBD3A1D1EBD3AF38 /* Pods_Stats.framework */,
B76EECB31DFC37E1EA558662 /* Pods_StatsLauncher.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@@ -303,6 +322,17 @@
path = StatsLauncher;
sourceTree = "<group>";
};
A159F8578E30ECA1F2F6028E /* Pods */ = {
isa = PBXGroup;
children = (
38B7BF640C895BAFBF1A44BE /* Pods-Stats.debug.xcconfig */,
1A1A00B8A6C7702FA7C3FD9A /* Pods-Stats.release.xcconfig */,
2650ED499D83382C9C938E76 /* Pods-StatsLauncher.debug.xcconfig */,
0BAA6D5F418C0F6999BD18BD /* Pods-StatsLauncher.release.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -310,10 +340,12 @@
isa = PBXNativeTarget;
buildConfigurationList = 9A141105229E721200D29793 /* Build configuration list for PBXNativeTarget "Stats" */;
buildPhases = (
813D45DAD934E69BA5C6A78F /* [CP] Check Pods Manifest.lock */,
9A1410F1229E721100D29793 /* Sources */,
9A1410F2229E721100D29793 /* Frameworks */,
9A1410F3229E721100D29793 /* Resources */,
9AB54DAE22A19F96006192E0 /* Copy Files */,
EDB5CB3173CB4B8BADA1278D /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -328,6 +360,7 @@
isa = PBXNativeTarget;
buildConfigurationList = 9AFA402B22AE49A200FE90BC /* Build configuration list for PBXNativeTarget "StatsLauncher" */;
buildPhases = (
3AB22E5D98D11E0384E0FF21 /* [CP] Check Pods Manifest.lock */,
9AFA401A22AE49A100FE90BC /* Sources */,
9AFA401B22AE49A100FE90BC /* Frameworks */,
9AFA401C22AE49A100FE90BC /* Resources */,
@@ -359,7 +392,7 @@
enabled = 0;
};
com.apple.Sandbox = {
enabled = 1;
enabled = 0;
};
};
};
@@ -416,6 +449,70 @@
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3AB22E5D98D11E0384E0FF21 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-StatsLauncher-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
813D45DAD934E69BA5C6A78F /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Stats-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
EDB5CB3173CB4B8BADA1278D /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Stats/Pods-Stats-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Stats/Pods-Stats-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Stats/Pods-Stats-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
9A1410F1229E721100D29793 /* Sources */ = {
isa = PBXSourcesBuildPhase;
@@ -424,6 +521,7 @@
9A09C8A222B3D94D0018426F /* BatteryView.swift in Sources */,
9A426DB822C2B5EE00C064C4 /* macAppUpdater.swift in Sources */,
9A79B36E22D3BEF900BF1C3A /* Reader.swift in Sources */,
9A59AE54231ED1AC007989D6 /* CPUView.swift in Sources */,
9A7B8F6F22A2C57000DEB352 /* DiskReader.swift in Sources */,
9A7B8F6922A2C3A100DEB352 /* Memory.swift in Sources */,
9AF0F32522DA92C400026AE6 /* NetworkText.swift in Sources */,
@@ -446,6 +544,7 @@
9AF0F32922DA92E800026AE6 /* NetworkArrowsText.swift in Sources */,
9AF0F31F22DA925700026AE6 /* BarChart.swift in Sources */,
9AF0F31B22DA924000026AE6 /* LineChart.swift in Sources */,
9A59AE56231EE02F007989D6 /* ChartMarker.swift in Sources */,
9A74D59722B44498004FE1FA /* Mini.swift in Sources */,
9A5B1CC5229E7B40008B9D3C /* Extensions.swift in Sources */,
9A79B36A22D3BEE600BF1C3A /* Widget.swift in Sources */,
@@ -600,6 +699,7 @@
};
9A141106229E721200D29793 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 38B7BF640C895BAFBF1A44BE /* Pods-Stats.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "Stats/Supporting Files/Stats.entitlements";
@@ -627,6 +727,7 @@
};
9A141107229E721200D29793 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 1A1A00B8A6C7702FA7C3FD9A /* Pods-Stats.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "Stats/Supporting Files/Stats.entitlements";
@@ -654,6 +755,7 @@
};
9AFA402C22AE49A200FE90BC /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 2650ED499D83382C9C938E76 /* Pods-StatsLauncher.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = StatsLauncher/StatsLauncher.entitlements;
@@ -676,6 +778,7 @@
};
9AFA402D22AE49A200FE90BC /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 0BAA6D5F418C0F6999BD18BD /* Pods-StatsLauncher.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = StatsLauncher/StatsLauncher.entitlements;

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1100"
LastUpgradeVersion = "1030"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -27,6 +27,8 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
@@ -36,8 +38,8 @@
ReferencedContainer = "container:Stats.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@@ -59,6 +61,8 @@
ReferencedContainer = "container:Stats.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Stats.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -7,23 +7,25 @@
//
import Cocoa
import Charts
class CPU: Module {
let name: String = "CPU"
let shortName: String = "CPU"
var view: NSView = NSView()
var menu: NSMenuItem = NSMenuItem()
var submenu: NSMenu = NSMenu()
var active: Observable<Bool>
var available: Observable<Bool>
var hyperthreading: Observable<Bool>
var reader: Reader = CPUReader()
var tabView: NSTabViewItem = NSTabViewItem()
public let name: String = "CPU"
public let shortName: String = "CPU"
public var view: NSView = NSView()
public var menu: NSMenuItem = NSMenuItem()
public var active: Observable<Bool>
public var available: Observable<Bool>
public var hyperthreading: Observable<Bool>
public var reader: Reader = CPUReader()
public var tabView: NSTabViewItem = NSTabViewItem()
public var viewAvailable: Bool = true
public var widgetType: WidgetType
var viewAvailable: Bool = true
public var chart: LineChartView = LineChartView()
let defaults = UserDefaults.standard
var widgetType: WidgetType
private let defaults = UserDefaults.standard
private var submenu: NSMenu = NSMenu()
init() {
self.available = Observable(true)
@@ -42,24 +44,6 @@ class CPU: Module {
initTab()
}
func initTab() {
self.tabView.view?.frame = NSRect(x: 0, y: 0, width: TabWidth, height: TabHeight)
let text: NSTextField = NSTextField(string: self.name)
text.isEditable = false
text.isSelectable = false
text.isBezeled = false
text.wantsLayer = true
text.textColor = .labelColor
text.canDrawSubviewsIntoLayer = true
text.alignment = .natural
text.font = NSFont.systemFont(ofSize: 13, weight: .regular)
text.frame.origin.x = ((self.tabView.view?.frame.size.width)! - 30) / 2
text.frame.origin.y = ((self.tabView.view?.frame.size.height)! - 22) / 2
self.tabView.view?.addSubview(text)
}
func initMenu() {
menu = NSMenuItem(title: name, action: #selector(toggle), keyEquivalent: "")
submenu = NSMenu()

View File

@@ -8,23 +8,47 @@
import Foundation
struct CPUUsage {
var value: Double = 0
var system: Double = 0
var user: Double = 0
var idle: Double = 0
}
struct CPUProcess {
var pid: Int = 0
var command: String = ""
var usage: Double = 0
}
class CPUReader: Reader {
var value: Observable<[Double]>!
var available: Bool = true
var cpuInfo: processor_info_array_t!
var prevCpuInfo: processor_info_array_t?
var numCpuInfo: mach_msg_type_number_t = 0
var numPrevCpuInfo: mach_msg_type_number_t = 0
var numCPUs: uint = 0
var updateTimer: Timer!
let CPUUsageLock: NSLock = NSLock()
public var value: Observable<[Double]>!
public var usage: Observable<CPUUsage> = Observable(CPUUsage())
public var processes: Observable<[CPUProcess]> = Observable([CPUProcess]())
public var available: Bool = true
public var updateTimer: Timer!
public var perCoreMode: Bool = false
public var hyperthreading: Bool = true
var perCoreMode: Bool = false
var hyperthreading: Bool = true
private var cpuInfo: processor_info_array_t!
private var prevCpuInfo: processor_info_array_t?
private var numCpuInfo: mach_msg_type_number_t = 0
private var numPrevCpuInfo: mach_msg_type_number_t = 0
private var numCPUs: uint = 0
private let CPUUsageLock: NSLock = NSLock()
private var loadPrevious = host_cpu_load_info()
private var topProcess: Process = Process()
private var pipe: Pipe = Pipe()
init() {
let mibKeys: [Int32] = [ CTL_HW, HW_NCPU ]
self.value = Observable([])
self.topProcess.launchPath = "/usr/bin/top"
self.topProcess.arguments = ["-s", "1", "-o", "cpu", "-n", "5", "-stats", "pid,command,cpu"]
self.topProcess.standardOutput = pipe
mibKeys.withUnsafeBufferPointer() { mib in
var sizeOfNumCPUs: size_t = MemoryLayout<uint>.size
let status = sysctl(processor_info_array_t(mutating: mib.baseAddress), 2, &numCPUs, &sizeOfNumCPUs, nil, 0)
@@ -40,6 +64,43 @@ class CPUReader: Reader {
return
}
updateTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(read), userInfo: nil, repeats: true)
if topProcess.isRunning {
return
}
self.pipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
NotificationCenter.default.addObserver(forName: NSNotification.Name.NSFileHandleDataAvailable, object: self.pipe.fileHandleForReading , queue: nil) { _ -> Void in
defer {
self.pipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
}
let output = self.pipe.fileHandleForReading.availableData
if output.isEmpty {
return
}
let outputString = String(data: output, encoding: String.Encoding.utf8) ?? ""
var processes: [CPUProcess] = []
outputString.enumerateLines { (line, stop) -> () in
if line.matches("^\\d+ + .+ +\\d+.\\d *$") {
let arr = line.condenseWhitespace().split(separator: " ")
let pid = Int(arr[0]) ?? 0
let command = String(arr[1])
let usage = Double(arr[2]) ?? 0
let process = CPUProcess(pid: pid, command: command, usage: usage)
processes.append(process)
}
}
self.processes << processes
}
do {
try topProcess.run()
} catch let error {
print(error)
}
}
func stop() {
@@ -48,11 +109,14 @@ class CPUReader: Reader {
}
updateTimer.invalidate()
updateTimer = nil
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.NSFileHandleDataAvailable, object: nil)
}
@objc func read() {
var numCPUsU: natural_t = 0
let err: kern_return_t = host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &numCPUsU, &cpuInfo, &numCpuInfo);
let usage = getUsage()
if err == KERN_SUCCESS {
CPUUsageLock.lock()
@@ -65,7 +129,7 @@ class CPUReader: Reader {
incrementNumber = 2
}
for i in stride(from: 0, to: Int32(numCPUs), by: incrementNumber){
for i in stride(from: 0, to: Int32(numCPUs), by: incrementNumber) {
var inUse: Int32
var total: Int32
if let prevCpuInfo = prevCpuInfo {
@@ -96,6 +160,9 @@ class CPUReader: Reader {
} else {
self.value << [(Double(inUseOnAllCores) / Double(totalOnAllCores))]
}
if !usage.system.isNaN && !usage.user.isNaN && !usage.idle.isNaN {
self.usage << CPUUsage(value: Double(inUseOnAllCores) / Double(totalOnAllCores), system: usage.system, user: usage.user, idle: usage.idle)
}
CPUUsageLock.unlock()
@@ -113,4 +180,46 @@ class CPUReader: Reader {
print("Error KERN_SUCCESS!")
}
}
func hostCPULoadInfo() -> host_cpu_load_info? {
let HOST_CPU_LOAD_INFO_COUNT = MemoryLayout<host_cpu_load_info>.stride/MemoryLayout<integer_t>.stride
var size = mach_msg_type_number_t(HOST_CPU_LOAD_INFO_COUNT)
var cpuLoadInfo = host_cpu_load_info()
let result = withUnsafeMutablePointer(to: &cpuLoadInfo) {
$0.withMemoryRebound(to: integer_t.self, capacity: HOST_CPU_LOAD_INFO_COUNT) {
host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, $0, &size)
}
}
if result != KERN_SUCCESS {
print("Error - \(#file): \(#function) - kern_result_t = \(result)")
return nil
}
return cpuLoadInfo
}
public func getUsage() -> (system: Double, user: Double, idle : Double) {
let load = hostCPULoadInfo()
let userDiff = Double(load!.cpu_ticks.0 - loadPrevious.cpu_ticks.0)
let sysDiff = Double(load!.cpu_ticks.1 - loadPrevious.cpu_ticks.1)
let idleDiff = Double(load!.cpu_ticks.2 - loadPrevious.cpu_ticks.2)
let niceDiff = Double(load!.cpu_ticks.3 - loadPrevious.cpu_ticks.3)
let totalTicks = sysDiff + userDiff + niceDiff + idleDiff
let sys = sysDiff / totalTicks * 100.0
let user = userDiff / totalTicks * 100.0
let idle = idleDiff / totalTicks * 100.0
self.loadPrevious = load!
return (sys, user, idle)
}
}
extension String {
func matches(_ regex: String) -> Bool {
return self.range(of: regex, options: .regularExpression, range: nil, locale: nil) != nil
}
}

View File

@@ -0,0 +1,258 @@
//
// CPUView.swift
// Stats
//
// Created by Serhiy Mytrovtsiy on 03/09/2019.
// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
//
import Cocoa
import Foundation
import Charts
extension CPU {
func initTab() {
self.tabView.view?.frame = NSRect(x: 0, y: 0, width: TabWidth, height: TabHeight)
makeChart()
makeOverview()
makeProcesses()
(self.reader as! CPUReader).usage.subscribe(observer: self) { (value, _) in
let v: Double = Double((value.value * 100).roundTo(decimalPlaces: 2))!
self.updateChart(value: v)
}
}
func makeChart() {
let lineColor: NSColor = NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 1.0)
let gradientColor: NSColor = NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 0.5)
self.chart = LineChartView(frame: CGRect(x: 0, y: TabHeight - 108, width: TabWidth, height: 100))
self.chart.animate(xAxisDuration: 2.0, yAxisDuration: 2.0, easingOption: .easeInCubic)
self.chart.backgroundColor = .white
self.chart.noDataText = "No data about CPU usage"
self.chart.legend.enabled = false
self.chart.scaleXEnabled = false
self.chart.scaleYEnabled = false
self.chart.pinchZoomEnabled = false
self.chart.doubleTapToZoomEnabled = false
self.chart.drawBordersEnabled = false
self.chart.rightAxis.enabled = false
self.chart.leftAxis.axisMinimum = 0
self.chart.leftAxis.axisMaximum = 100
self.chart.leftAxis.labelCount = 6
self.chart.leftAxis.drawGridLinesEnabled = false
self.chart.leftAxis.drawAxisLineEnabled = false
self.chart.leftAxis.gridColor = NSColor(red:220/255, green:220/255, blue:220/255, alpha:1)
self.chart.leftAxis.gridLineWidth = 0.5
self.chart.leftAxis.drawGridLinesEnabled = true
self.chart.leftAxis.labelTextColor = NSColor(red:150/255, green:150/255, blue:150/255, alpha:1)
self.chart.xAxis.drawAxisLineEnabled = false
self.chart.xAxis.drawLimitLinesBehindDataEnabled = false
self.chart.xAxis.gridLineWidth = 0.5
self.chart.xAxis.drawGridLinesEnabled = false
self.chart.xAxis.drawLabelsEnabled = false
let marker = ChartMarker()
marker.chartView = self.chart
self.chart.marker = marker
var lineChartEntry = [ChartDataEntry]()
lineChartEntry.append(ChartDataEntry(x: 0, y: 50))
lineChartEntry.append(ChartDataEntry(x: 1, y: 25))
let chartDataSet = LineChartDataSet(entries: lineChartEntry, label: "CPU Usage")
chartDataSet.drawCirclesEnabled = false
chartDataSet.mode = .cubicBezier
chartDataSet.cubicIntensity = 0.1
chartDataSet.colors = [lineColor]
chartDataSet.fillColor = gradientColor
chartDataSet.drawFilledEnabled = true
let data = LineChartData()
data.addDataSet(chartDataSet)
data.setDrawValues(false)
self.chart.data = LineChartData(dataSet: chartDataSet)
self.tabView.view?.addSubview(self.chart)
}
func updateChart(value: Double) {
let index = Double((self.chart.data?.getDataSetByIndex(0)?.entryCount)!)
self.chart.data?.addEntry(ChartDataEntry(x: index, y: value), dataSetIndex: 0)
if index > 120 {
self.chart.xAxis.axisMinimum = index - 120
}
self.chart.xAxis.axisMaximum = index
self.chart.notifyDataSetChanged()
self.chart.moveViewToX(index)
}
func makeOverview() {
let overviewLabel: NSView = NSView(frame: NSRect(x: 0, y: TabHeight - 140, width: TabWidth, height: 25))
overviewLabel.wantsLayer = true
overviewLabel.layer?.backgroundColor = NSColor(hexString: "#eeeeee", alpha: 0.5).cgColor
let overviewText: NSTextField = NSTextField(string: "Overview")
overviewText.frame = NSRect(x: 0, y: 0, width: TabWidth, height: overviewLabel.frame.size.height - 5)
overviewText.isEditable = false
overviewText.isSelectable = false
overviewText.isBezeled = false
overviewText.wantsLayer = true
overviewText.textColor = .labelColor
overviewText.canDrawSubviewsIntoLayer = true
overviewText.alignment = .center
overviewText.backgroundColor = NSColor(hexString: "#dddddd", alpha: 0)
overviewText.font = NSFont.systemFont(ofSize: 12, weight: .medium)
overviewLabel.addSubview(overviewText)
self.tabView.view?.addSubview(overviewLabel)
let stackHeight: CGFloat = 22
let vertical: NSStackView = NSStackView(frame: NSRect(x: 0, y: 147, width: TabWidth, height: stackHeight*3))
vertical.orientation = .vertical
let system: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*2, width: TabWidth - 20, height: stackHeight))
system.orientation = .horizontal
system.distribution = .equalCentering
let systemLabel = labelField(string: "System")
let systemValue = valueField(string: "0 %")
system.addView(systemLabel, in: .center)
system.addView(systemValue, in: .center)
let user: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*1, width: TabWidth - 20, height: stackHeight))
user.orientation = .horizontal
user.distribution = .equalCentering
let userLabel = labelField(string: "User")
let userValue = valueField(string: "0 %")
user.addView(userLabel, in: .center)
user.addView(userValue, in: .center)
let idle: NSStackView = NSStackView(frame: NSRect(x: 10, y: 0, width: TabWidth - 20, height: stackHeight))
idle.orientation = .horizontal
idle.distribution = .equalCentering
let idleLabel = labelField(string: "Idle")
let idleValue = valueField(string: "0 %")
idle.addView(idleLabel, in: .center)
idle.addView(idleValue, in: .center)
vertical.addSubview(system)
vertical.addSubview(user)
vertical.addSubview(idle)
self.tabView.view?.addSubview(vertical)
(self.reader as! CPUReader).usage.subscribe(observer: self) { (value, _) in
systemValue.stringValue = "\(value.system.roundTo(decimalPlaces: 2)) %"
userValue.stringValue = "\(value.user.roundTo(decimalPlaces: 2)) %"
idleValue.stringValue = "\(value.idle.roundTo(decimalPlaces: 2)) %"
}
}
func makeProcesses() {
let label: NSView = NSView(frame: NSRect(x: 0, y: 0, width: TabWidth, height: 25))
label.wantsLayer = true
label.layer?.backgroundColor = NSColor(hexString: "#eeeeee", alpha: 0.5).cgColor
let text: NSTextField = NSTextField(string: "Top Processes")
text.frame = NSRect(x: 0, y: 0, width: TabWidth, height: label.frame.size.height - 5)
text.isEditable = false
text.isSelectable = false
text.isBezeled = false
text.wantsLayer = true
text.textColor = .labelColor
text.canDrawSubviewsIntoLayer = true
text.alignment = .center
text.backgroundColor = NSColor(hexString: "#dddddd", alpha: 0)
text.font = NSFont.systemFont(ofSize: 12, weight: .medium)
label.addSubview(text)
self.tabView.view?.addSubview(label)
let stackHeight: CGFloat = 22
let vertical: NSStackView = NSStackView(frame: NSRect(x: 0, y: 4, width: TabWidth, height: stackHeight*5))
vertical.orientation = .vertical
vertical.distribution = .fill
var processViewList: [NSStackView] = []
let process_1 = makeProcessView(num: 4, height: stackHeight, label: "", value: "")
let process_2 = makeProcessView(num: 3, height: stackHeight, label: "", value: "")
let process_3 = makeProcessView(num: 2, height: stackHeight, label: "", value: "")
let process_4 = makeProcessView(num: 1, height: stackHeight, label: "", value: "")
let process_5 = makeProcessView(num: 0, height: stackHeight, label: "", value: "")
processViewList.append(process_1)
processViewList.append(process_2)
processViewList.append(process_3)
processViewList.append(process_4)
processViewList.append(process_5)
vertical.addSubview(process_1)
vertical.addSubview(process_2)
vertical.addSubview(process_3)
vertical.addSubview(process_4)
vertical.addSubview(process_5)
self.tabView.view?.addSubview(vertical)
label.frame = NSRect(x: 0, y: vertical.frame.origin.y + vertical.frame.size.height + 2, width: TabWidth, height: 25)
self.tabView.view?.addSubview(label)
(self.reader as! CPUReader).processes.subscribe(observer: self) { (processes, _) in
for (i, process) in processes.enumerated() {
let processView = processViewList[i]
(processView.subviews[0] as! NSTextField).stringValue = process.command
(processView.subviews[1] as! NSTextField).stringValue = "\(process.usage.roundTo(decimalPlaces: 2)) %"
}
}
}
func makeProcessView(num: Int, height: CGFloat, label: String, value: String) -> NSStackView {
let view: NSStackView = NSStackView(frame: NSRect(x: 10, y: CGFloat(num)*height, width: TabWidth - 20, height: height))
view.orientation = .horizontal
view.distribution = .equalCentering
let viewLabel = labelField(string: label)
let viewValue = valueField(string: value)
view.addView(viewLabel, in: .center)
view.addView(viewValue, in: .center)
return view
}
func labelField(string: String) -> NSTextField {
let label: NSTextField = NSTextField(string: string)
label.isEditable = false
label.isSelectable = false
label.isBezeled = false
label.textColor = .labelColor
label.alignment = .center
label.font = NSFont.systemFont(ofSize: 12, weight: .regular)
return label
}
func valueField(string: String) -> NSTextField {
let label: NSTextField = NSTextField(string: string)
label.isEditable = false
label.isSelectable = false
label.isBezeled = false
label.textColor = .black
label.alignment = .center
label.font = NSFont.systemFont(ofSize: 13, weight: .regular)
return label
}
}

View File

@@ -18,7 +18,7 @@ class Network: Module {
var available: Observable<Bool>
var reader: Reader = NetworkReader()
var widgetType: WidgetType = 2.0
var viewAvailable: Bool = true
var viewAvailable: Bool = false
var tabView: NSTabViewItem = NSTabViewItem()
let defaults = UserDefaults.standard

View File

@@ -2,13 +2,7 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>

View File

@@ -9,8 +9,8 @@
import Cocoa
import ServiceManagement
let TabWidth: CGFloat = 300
let TabHeight: CGFloat = 356
public let TabWidth: CGFloat = 300
public let TabHeight: CGFloat = 356
class MainViewController: NSViewController {
let defaults = UserDefaults.standard

View File

@@ -0,0 +1,41 @@
//
// ChartMarker.swift
// Stats
//
// Created by Serhiy Mytrovtsiy on 03/09/2019.
// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
//
import Cocoa
import Foundation
import Charts
class ChartMarker: MarkerView {
var text = ""
override func refreshContent(entry: ChartDataEntry, highlight: Highlight) {
super.refreshContent(entry: entry, highlight: highlight)
text = String(entry.y)
}
override func draw(context: CGContext, point: CGPoint) {
super.draw(context: context, point: point)
var drawAttributes = [NSAttributedString.Key : Any]()
drawAttributes[.font] = NSFont.systemFont(ofSize: 13)
drawAttributes[.foregroundColor] = NSColor.white
drawAttributes[.backgroundColor] = NSColor.darkGray
self.bounds.size = (" \(text) " as NSString).size(withAttributes: drawAttributes)
self.offset = CGPoint(x: 0, y: self.bounds.size.height)
let offset = self.offsetForDrawing(atPoint: point)
drawText(text: " \(text) " as NSString, rect: CGRect(origin: CGPoint(x: point.x + offset.x, y: point.y + offset.y), size: self.bounds.size), withAttributes: drawAttributes)
}
func drawText(text: NSString, rect: CGRect, withAttributes attributes: [NSAttributedString.Key : Any]? = nil) {
let size = text.size(withAttributes: attributes)
let centeredRect = CGRect(x: rect.origin.x + (rect.size.width - size.width) / 2.0, y: rect.origin.y + (rect.size.height - size.height) / 2.0, width: size.width, height: size.height)
text.draw(in: centeredRect, withAttributes: attributes)
}
}

View File

@@ -141,3 +141,34 @@ extension NSBezierPath {
self.line(to: arrowLine2)
}
}
extension NSColor {
convenience init(hexString: String, alpha: CGFloat = 1.0) {
let hexString: String = hexString.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
let scanner = Scanner(string: hexString)
if (hexString.hasPrefix("#")) {
scanner.scanLocation = 1
}
var color: UInt32 = 0
scanner.scanHexInt32(&color)
let mask = 0x000000FF
let r = Int(color >> 16) & mask
let g = Int(color >> 8) & mask
let b = Int(color) & mask
let red = CGFloat(r) / 255.0
let green = CGFloat(g) / 255.0
let blue = CGFloat(b) / 255.0
self.init(red:red, green:green, blue:blue, alpha:alpha)
}
func toHexString() -> String {
var r:CGFloat = 0
var g:CGFloat = 0
var b:CGFloat = 0
var a:CGFloat = 0
getRed(&r, green: &g, blue: &b, alpha: &a)
let rgb:Int = (Int)(r*255)<<16 | (Int)(g*255)<<8 | (Int)(b*255)<<0
return String(format:"#%06x", rgb)
}
}

View File

@@ -13,7 +13,6 @@ protocol ObservableProtocol {
var value: T { get set }
func subscribe(observer: AnyObject, block: @escaping (_ newValue: T, _ oldValue: T) -> ())
func unsubscribe(observer: AnyObject)
func userDefaults(key: String)
}
public final class Observable<T>: ObservableProtocol {
@@ -34,7 +33,6 @@ public final class Observable<T>: ObservableProtocol {
let (_, block) = entry
block(value, oldValue)
}
updateUserDefaults()
}
}
@@ -51,14 +49,6 @@ public final class Observable<T>: ObservableProtocol {
observers = filtered
}
func userDefaults(key: String) {
self.userDefaultsKey = key
}
func updateUserDefaults() {
self.defaults.set(self.value, forKey: self.userDefaultsKey)
}
}
func <<<T>(observable: Observable<T>, value: T) {