mirror of
https://github.com/morgan9e/macos-stats
synced 2026-04-14 00:04:15 +09:00
finally remove observable from the project;
started rewriting modules from zero; CPU mvp;
This commit is contained in:
@@ -7,26 +7,24 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
9A09C89E22B3A7C90018426F /* Battery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A09C89D22B3A7C90018426F /* Battery.swift */; };
|
||||
9A09C8A022B3A7E20018426F /* BatteryReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A09C89F22B3A7E20018426F /* BatteryReader.swift */; };
|
||||
9A09C8A222B3D94D0018426F /* BatteryWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A09C8A122B3D94D0018426F /* BatteryWidget.swift */; };
|
||||
9A1410F9229E721100D29793 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1410F8229E721100D29793 /* AppDelegate.swift */; };
|
||||
9A141100229E721200D29793 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9A1410FE229E721200D29793 /* Main.storyboard */; };
|
||||
9A2D15D023C77BA300C4C417 /* Repeater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15CF23C77BA300C4C417 /* Repeater.swift */; };
|
||||
9A2D15D223CCEC7600C4C417 /* Module.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15D123CCEC7600C4C417 /* Module.swift */; };
|
||||
9A2D15D523CCEFF700C4C417 /* CPU.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15D423CCEFF700C4C417 /* CPU.swift */; };
|
||||
9A2D15D723CCFE1B00C4C417 /* CPULoadReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15D623CCFE1B00C4C417 /* CPULoadReader.swift */; };
|
||||
9A2D15D923CD036400C4C417 /* CPUMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15D823CD036400C4C417 /* CPUMenu.swift */; };
|
||||
9A2D15DB23CD0B2100C4C417 /* CPUPopup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15DA23CD0B2100C4C417 /* CPUPopup.swift */; };
|
||||
9A2D15E123CD133300C4C417 /* CPUUsageReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15E023CD133300C4C417 /* CPUUsageReader.swift */; };
|
||||
9A2D15E323CD1E4B00C4C417 /* CPUProcessReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15E223CD1E4B00C4C417 /* CPUProcessReader.swift */; };
|
||||
9A426DB822C2B5EE00C064C4 /* MacAppUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A426DB722C2B5EE00C064C4 /* MacAppUpdater.swift */; };
|
||||
9A426DBE22C2BE0000C064C4 /* Updates.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9A426DBD22C2BE0000C064C4 /* Updates.storyboard */; };
|
||||
9A493CDF23202B620064570C /* MemoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A493CDE23202B620064570C /* MemoryView.swift */; };
|
||||
9A54EF67232AB81800F7DC20 /* BatteryPercentageWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A54EF66232AB81800F7DC20 /* BatteryPercentageWidget.swift */; };
|
||||
9A54EF69232AB82700F7DC20 /* BatteryTimeWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A54EF68232AB82700F7DC20 /* BatteryTimeWidget.swift */; };
|
||||
9A57A18522A1D26D0033E318 /* MenuBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A57A18422A1D26D0033E318 /* MenuBar.swift */; };
|
||||
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 */; };
|
||||
9A606B482321025C00642F51 /* BatteryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A606B472321025C00642F51 /* BatteryView.swift */; };
|
||||
9A606B4A2321577400642F51 /* UpdatesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A606B492321577400642F51 /* UpdatesViewController.swift */; };
|
||||
9A606B4C232157BA00642F51 /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A606B4B232157BA00642F51 /* AboutViewController.swift */; };
|
||||
9A6698E62326AB16001D00E1 /* Charts.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A6698E32326AAE5001D00E1 /* Charts.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
@@ -34,13 +32,6 @@
|
||||
9A6CFC0122A1C9F5001E782D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9A6CFC0022A1C9F5001E782D /* Assets.xcassets */; };
|
||||
9A74D59722B44498004FE1FA /* Mini.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A74D59622B44498004FE1FA /* Mini.swift */; };
|
||||
9A79B36A22D3BEE600BF1C3A /* Widget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A79B36922D3BEE600BF1C3A /* Widget.swift */; };
|
||||
9A79B36C22D3BEF000BF1C3A /* Module.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A79B36B22D3BEF000BF1C3A /* Module.swift */; };
|
||||
9A79B36E22D3BEF900BF1C3A /* Reader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A79B36D22D3BEF900BF1C3A /* Reader.swift */; };
|
||||
9A7B8F5E22A2A57600DEB352 /* CPUReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7B8F5D22A2A57600DEB352 /* CPUReader.swift */; };
|
||||
9A7B8F6922A2C3A100DEB352 /* Memory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7B8F6822A2C3A100DEB352 /* Memory.swift */; };
|
||||
9A7B8F6B22A2C3A700DEB352 /* Disk.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7B8F6A22A2C3A700DEB352 /* Disk.swift */; };
|
||||
9A7B8F6D22A2C3D600DEB352 /* MemoryReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7B8F6C22A2C3D600DEB352 /* MemoryReader.swift */; };
|
||||
9A7B8F6F22A2C57000DEB352 /* DiskReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7B8F6E22A2C57000DEB352 /* DiskReader.swift */; };
|
||||
9AF0F31B22DA924000026AE6 /* LineChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF0F31A22DA924000026AE6 /* LineChart.swift */; };
|
||||
9AF0F31D22DA925000026AE6 /* LineChartWithValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF0F31C22DA925000026AE6 /* LineChartWithValue.swift */; };
|
||||
9AF0F31F22DA925700026AE6 /* BarChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF0F31E22DA925700026AE6 /* BarChart.swift */; };
|
||||
@@ -79,8 +70,6 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
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 /* BatteryWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryWidget.swift; sourceTree = "<group>"; };
|
||||
9A1410F5229E721100D29793 /* Stats.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Stats.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
9A1410F8229E721100D29793 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
@@ -89,20 +78,20 @@
|
||||
9A141102229E721200D29793 /* Stats.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Stats.entitlements; sourceTree = "<group>"; };
|
||||
9A2D15CC23C77A7700C4C417 /* Repeat.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Repeat.framework; path = Carthage/Build/Mac/Repeat.framework; sourceTree = "<group>"; };
|
||||
9A2D15CF23C77BA300C4C417 /* Repeater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Repeater.swift; sourceTree = "<group>"; };
|
||||
9A2D15D123CCEC7600C4C417 /* Module.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Module.swift; sourceTree = "<group>"; };
|
||||
9A2D15D423CCEFF700C4C417 /* CPU.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPU.swift; sourceTree = "<group>"; };
|
||||
9A2D15D623CCFE1B00C4C417 /* CPULoadReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPULoadReader.swift; sourceTree = "<group>"; };
|
||||
9A2D15D823CD036400C4C417 /* CPUMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPUMenu.swift; sourceTree = "<group>"; };
|
||||
9A2D15DA23CD0B2100C4C417 /* CPUPopup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPUPopup.swift; sourceTree = "<group>"; };
|
||||
9A2D15E023CD133300C4C417 /* CPUUsageReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPUUsageReader.swift; sourceTree = "<group>"; };
|
||||
9A2D15E223CD1E4B00C4C417 /* CPUProcessReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPUProcessReader.swift; sourceTree = "<group>"; };
|
||||
9A426DB722C2B5EE00C064C4 /* MacAppUpdater.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MacAppUpdater.swift; sourceTree = "<group>"; };
|
||||
9A426DBD22C2BE0000C064C4 /* Updates.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Updates.storyboard; sourceTree = "<group>"; };
|
||||
9A493CDE23202B620064570C /* MemoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryView.swift; sourceTree = "<group>"; };
|
||||
9A54EF66232AB81800F7DC20 /* BatteryPercentageWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryPercentageWidget.swift; sourceTree = "<group>"; };
|
||||
9A54EF68232AB82700F7DC20 /* BatteryTimeWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryTimeWidget.swift; sourceTree = "<group>"; };
|
||||
9A57A18422A1D26D0033E318 /* MenuBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBar.swift; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
9A606B472321025C00642F51 /* BatteryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryView.swift; sourceTree = "<group>"; };
|
||||
9A606B492321577400642F51 /* UpdatesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdatesViewController.swift; sourceTree = "<group>"; };
|
||||
9A606B4B232157BA00642F51 /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = "<group>"; };
|
||||
9A6698DF2326AAD6001D00E1 /* LaunchAtLogin.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LaunchAtLogin.framework; path = Carthage/Build/Mac/LaunchAtLogin.framework; sourceTree = "<group>"; };
|
||||
@@ -110,13 +99,6 @@
|
||||
9A6CFC0022A1C9F5001E782D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
9A74D59622B44498004FE1FA /* Mini.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mini.swift; sourceTree = "<group>"; };
|
||||
9A79B36922D3BEE600BF1C3A /* Widget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Widget.swift; sourceTree = "<group>"; };
|
||||
9A79B36B22D3BEF000BF1C3A /* Module.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Module.swift; sourceTree = "<group>"; };
|
||||
9A79B36D22D3BEF900BF1C3A /* Reader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reader.swift; sourceTree = "<group>"; };
|
||||
9A7B8F5D22A2A57600DEB352 /* CPUReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPUReader.swift; sourceTree = "<group>"; };
|
||||
9A7B8F6822A2C3A100DEB352 /* Memory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Memory.swift; sourceTree = "<group>"; };
|
||||
9A7B8F6A22A2C3A700DEB352 /* Disk.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Disk.swift; sourceTree = "<group>"; };
|
||||
9A7B8F6C22A2C3D600DEB352 /* MemoryReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryReader.swift; sourceTree = "<group>"; };
|
||||
9A7B8F6E22A2C57000DEB352 /* DiskReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiskReader.swift; sourceTree = "<group>"; };
|
||||
9A998CD722A199920087ADE7 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
|
||||
9A998CD922A199970087ADE7 /* ServiceManagement.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ServiceManagement.framework; path = System/Library/Frameworks/ServiceManagement.framework; sourceTree = SDKROOT; };
|
||||
9AF0F31A22DA924000026AE6 /* LineChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineChart.swift; sourceTree = "<group>"; };
|
||||
@@ -142,16 +124,6 @@
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
9A09C89C22B3A7BB0018426F /* Battery */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9A09C89D22B3A7C90018426F /* Battery.swift */,
|
||||
9A09C89F22B3A7E20018426F /* BatteryReader.swift */,
|
||||
9A606B472321025C00642F51 /* BatteryView.swift */,
|
||||
);
|
||||
path = Battery;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9A1410EC229E721100D29793 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -183,6 +155,19 @@
|
||||
path = Stats;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9A2D15D323CCEFEC00C4C417 /* CPU */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9A2D15D423CCEFF700C4C417 /* CPU.swift */,
|
||||
9A2D15D623CCFE1B00C4C417 /* CPULoadReader.swift */,
|
||||
9A2D15D823CD036400C4C417 /* CPUMenu.swift */,
|
||||
9A2D15DA23CD0B2100C4C417 /* CPUPopup.swift */,
|
||||
9A2D15E023CD133300C4C417 /* CPUUsageReader.swift */,
|
||||
9A2D15E223CD1E4B00C4C417 /* CPUProcessReader.swift */,
|
||||
);
|
||||
path = CPU;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9A54EF65232AB48100F7DC20 /* Battery */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -193,15 +178,6 @@
|
||||
path = Battery;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9A58D1AE22C150B800405315 /* Network */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9A58D1AF22C150C800405315 /* Network.swift */,
|
||||
9A58D1B122C150D700405315 /* NetworkReader.swift */,
|
||||
);
|
||||
path = Network;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9A5B1CB3229E72A7008B9D3C /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -218,13 +194,8 @@
|
||||
9A5B1CBA229E7892008B9D3C /* Modules */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9A7B8F5C22A2926500DEB352 /* CPU */,
|
||||
9A7B8F6222A2C17000DEB352 /* Memory */,
|
||||
9A7B8F6322A2C17500DEB352 /* Disk */,
|
||||
9A09C89C22B3A7BB0018426F /* Battery */,
|
||||
9A58D1AE22C150B800405315 /* Network */,
|
||||
9A79B36B22D3BEF000BF1C3A /* Module.swift */,
|
||||
9A79B36D22D3BEF900BF1C3A /* Reader.swift */,
|
||||
9A2D15D323CCEFEC00C4C417 /* CPU */,
|
||||
9A2D15D123CCEC7600C4C417 /* Module.swift */,
|
||||
);
|
||||
path = Modules;
|
||||
sourceTree = "<group>";
|
||||
@@ -232,7 +203,6 @@
|
||||
9A5B1CBD229E78D2008B9D3C /* libs */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9A5B1CBE229E78F0008B9D3C /* Observable.swift */,
|
||||
9A5B1CC4229E7B40008B9D3C /* Extensions.swift */,
|
||||
9A426DB722C2B5EE00C064C4 /* MacAppUpdater.swift */,
|
||||
9A59AE55231EE02F007989D6 /* ChartMarker.swift */,
|
||||
@@ -253,35 +223,6 @@
|
||||
path = Widgets;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9A7B8F5C22A2926500DEB352 /* CPU */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9A57A19C22A1E3270033E318 /* CPU.swift */,
|
||||
9A7B8F5D22A2A57600DEB352 /* CPUReader.swift */,
|
||||
9A59AE53231ED1AC007989D6 /* CPUView.swift */,
|
||||
);
|
||||
path = CPU;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9A7B8F6222A2C17000DEB352 /* Memory */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9A7B8F6822A2C3A100DEB352 /* Memory.swift */,
|
||||
9A7B8F6C22A2C3D600DEB352 /* MemoryReader.swift */,
|
||||
9A493CDE23202B620064570C /* MemoryView.swift */,
|
||||
);
|
||||
path = Memory;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9A7B8F6322A2C17500DEB352 /* Disk */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9A7B8F6A22A2C3A700DEB352 /* Disk.swift */,
|
||||
9A7B8F6E22A2C57000DEB352 /* DiskReader.swift */,
|
||||
);
|
||||
path = Disk;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9A998CD622A199920087ADE7 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -452,42 +393,33 @@
|
||||
files = (
|
||||
9A09C8A222B3D94D0018426F /* BatteryWidget.swift in Sources */,
|
||||
9A426DB822C2B5EE00C064C4 /* MacAppUpdater.swift in Sources */,
|
||||
9A79B36E22D3BEF900BF1C3A /* Reader.swift in Sources */,
|
||||
9A606B4C232157BA00642F51 /* AboutViewController.swift in Sources */,
|
||||
9A59AE54231ED1AC007989D6 /* CPUView.swift in Sources */,
|
||||
9A7B8F6F22A2C57000DEB352 /* DiskReader.swift in Sources */,
|
||||
9A7B8F6922A2C3A100DEB352 /* Memory.swift in Sources */,
|
||||
9AF0F32522DA92C400026AE6 /* NetworkText.swift in Sources */,
|
||||
9A7B8F5E22A2A57600DEB352 /* CPUReader.swift in Sources */,
|
||||
9AF0F32322DA92B900026AE6 /* NetworkArrows.swift in Sources */,
|
||||
9A2D15D223CCEC7600C4C417 /* Module.swift in Sources */,
|
||||
9A606B4A2321577400642F51 /* UpdatesViewController.swift in Sources */,
|
||||
9AF0F31D22DA925000026AE6 /* LineChartWithValue.swift in Sources */,
|
||||
9A09C89E22B3A7C90018426F /* Battery.swift in Sources */,
|
||||
9A54EF67232AB81800F7DC20 /* BatteryPercentageWidget.swift in Sources */,
|
||||
9A7B8F6D22A2C3D600DEB352 /* MemoryReader.swift in Sources */,
|
||||
9A79B36C22D3BEF000BF1C3A /* Module.swift in Sources */,
|
||||
9A57A18522A1D26D0033E318 /* MenuBar.swift in Sources */,
|
||||
9A2D15D923CD036400C4C417 /* CPUMenu.swift in Sources */,
|
||||
9AF6F1FE231D732600B8E1E4 /* PopupViewController.swift in Sources */,
|
||||
9A57A19D22A1E3270033E318 /* CPU.swift in Sources */,
|
||||
9A58D1B222C150D700405315 /* NetworkReader.swift in Sources */,
|
||||
9A09C8A022B3A7E20018426F /* BatteryReader.swift in Sources */,
|
||||
9A2D15D023C77BA300C4C417 /* Repeater.swift in Sources */,
|
||||
9A58D1B022C150C800405315 /* Network.swift in Sources */,
|
||||
9A5B1CBF229E78F0008B9D3C /* Observable.swift in Sources */,
|
||||
9A7B8F6B22A2C3A700DEB352 /* Disk.swift in Sources */,
|
||||
9A1410F9229E721100D29793 /* AppDelegate.swift in Sources */,
|
||||
9A2D15DB23CD0B2100C4C417 /* CPUPopup.swift in Sources */,
|
||||
9AF0F32722DA92DD00026AE6 /* NetworkDotsText.swift in Sources */,
|
||||
9AF0F32922DA92E800026AE6 /* NetworkArrowsText.swift in Sources */,
|
||||
9A54EF69232AB82700F7DC20 /* BatteryTimeWidget.swift in Sources */,
|
||||
9A606B482321025C00642F51 /* BatteryView.swift in Sources */,
|
||||
9A2D15D723CCFE1B00C4C417 /* CPULoadReader.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 */,
|
||||
9A2D15E123CD133300C4C417 /* CPUUsageReader.swift in Sources */,
|
||||
9A2D15D523CCEFF700C4C417 /* CPU.swift in Sources */,
|
||||
9A2D15E323CD1E4B00C4C417 /* CPUProcessReader.swift in Sources */,
|
||||
9A79B36A22D3BEE600BF1C3A /* Widget.swift in Sources */,
|
||||
9AF0F32122DA92AD00026AE6 /* NetworkDots.swift in Sources */,
|
||||
9A493CDF23202B620064570C /* MemoryView.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@ import ServiceManagement
|
||||
Class keeps a status bar item and has the main function for updating widgets.
|
||||
*/
|
||||
class MenuBar {
|
||||
public let modules: [Module] = [CPU(), Memory(), Disk(), Battery(), Network()]
|
||||
public let modules: [Module] = [CPU()]
|
||||
|
||||
private let menuBarItem: NSStatusItem
|
||||
private var menuBarButton: NSButton = NSButton()
|
||||
@@ -43,15 +43,10 @@ class MenuBar {
|
||||
var WIDTH: CGFloat = 0
|
||||
for module in self.modules {
|
||||
if module.available {
|
||||
if module.active {
|
||||
module.initWidget()
|
||||
module.initTab()
|
||||
if module.enabled {
|
||||
module.start()
|
||||
}
|
||||
module.initMenu(active: module.active)
|
||||
if module.active {
|
||||
stackView.addArrangedSubview(module.view)
|
||||
WIDTH = WIDTH + module.view.frame.size.width
|
||||
stackView.addArrangedSubview(module.widget.view)
|
||||
WIDTH = WIDTH + module.widget.view.frame.size.width
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,33 +77,30 @@ class MenuBar {
|
||||
let view = self.stackView.subviews.filter{ $0 is Widget && ($0 as! Widget).name == name }
|
||||
if view.isEmpty {
|
||||
// if module is active but not exist in stack, add it to stack (enable module)
|
||||
if module.first!.active {
|
||||
let activeModules = self.modules.filter{ $0.active && $0.available }
|
||||
if module.first!.enabled {
|
||||
let activeModules = self.modules.filter{ $0.enabled && $0.available }
|
||||
let position = activeModules.firstIndex { $0.name == name }
|
||||
|
||||
module.first!.initWidget()
|
||||
if !module.first!.tabInitialized {
|
||||
module.first!.initTab()
|
||||
}
|
||||
|
||||
module.first!.start()
|
||||
|
||||
if position! >= activeModules.count-1 {
|
||||
stackView.addArrangedSubview(module.first!.view)
|
||||
stackView.addArrangedSubview(module.first!.widget.view)
|
||||
} else {
|
||||
stackView.insertArrangedSubview(module.first!.view, at: position!)
|
||||
stackView.insertArrangedSubview(module.first!.widget.view, at: position!)
|
||||
stackView.updateLayer()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// if module not active but exist, remove from stack (disable module), else replace
|
||||
if !module.first!.active {
|
||||
if !module.first!.enabled {
|
||||
view.first!.removeFromSuperview()
|
||||
} else {
|
||||
let newView = module.first!.view
|
||||
let newView = module.first!.widget.view
|
||||
newView.invalidateIntrinsicContentSize()
|
||||
self.stackView.replaceSubview(view.first!, with: newView)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
self.updateWidth()
|
||||
self.popup.reload()
|
||||
}
|
||||
@@ -125,9 +117,9 @@ class MenuBar {
|
||||
if module == nil {
|
||||
return
|
||||
}
|
||||
|
||||
module!.view.invalidateIntrinsicContentSize()
|
||||
self.stackView.replaceSubview(view, with: module!.view)
|
||||
|
||||
module!.widget.view.invalidateIntrinsicContentSize()
|
||||
self.stackView.replaceSubview(view, with: module!.widget.view)
|
||||
self.updateWidth()
|
||||
}
|
||||
}
|
||||
@@ -136,14 +128,16 @@ class MenuBar {
|
||||
Destroy will destroy status bar view.
|
||||
*/
|
||||
public func destroy() {
|
||||
|
||||
for module in self.modules {
|
||||
module.stop()
|
||||
}
|
||||
}
|
||||
|
||||
private func updateWidth() {
|
||||
var WIDTH: CGFloat = 0
|
||||
for module in self.modules {
|
||||
if module.active && module.available {
|
||||
WIDTH = WIDTH + module.view.frame.size.width
|
||||
if module.enabled && module.available {
|
||||
WIDTH = WIDTH + module.widget.view.frame.size.width
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,6 +148,7 @@ class MenuBar {
|
||||
if WIDTH == 0 {
|
||||
self.menuBarButton.image = NSImage(named:NSImage.Name("tray_icon"))
|
||||
self.menuBarItem.length = widgetSize.width
|
||||
self.stackView.frame.size.width = widgetSize.width
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1,216 +0,0 @@
|
||||
//
|
||||
// Battery.swift
|
||||
// Stats
|
||||
//
|
||||
// Created by Serhiy Mytrovtsiy on 14/06/2019.
|
||||
// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class Battery: Module {
|
||||
public let name: String = "Battery"
|
||||
public let shortName: String = "BAT"
|
||||
public var view: NSView = NSView()
|
||||
public var menu: NSMenuItem = NSMenuItem()
|
||||
public var active: Bool = true
|
||||
public var available: Bool = true
|
||||
public var reader: Reader = BatteryReader()
|
||||
public var tabAvailable: Bool = true
|
||||
public var tabInitialized: Bool = false
|
||||
public var tabView: NSTabViewItem = NSTabViewItem()
|
||||
public var updateInterval: Int
|
||||
|
||||
public var widgetType: WidgetType = Widgets.Battery
|
||||
public let percentageView: Observable<Bool>
|
||||
public let timeView: Observable<Bool>
|
||||
|
||||
private let defaults = UserDefaults.standard
|
||||
private var submenu: NSMenu = NSMenu()
|
||||
|
||||
init() {
|
||||
self.available = self.reader.available
|
||||
self.active = defaults.object(forKey: name) != nil ? defaults.bool(forKey: name) : true
|
||||
self.percentageView = Observable(defaults.object(forKey: "\(self.name)_percentage") != nil ? defaults.bool(forKey: "\(self.name)_percentage") : false)
|
||||
self.timeView = Observable(defaults.object(forKey: "\(self.name)_time") != nil ? defaults.bool(forKey: "\(self.name)_time") : false)
|
||||
self.widgetType = defaults.object(forKey: "\(name)_widget") != nil ? defaults.float(forKey: "\(name)_widget") : Widgets.Battery
|
||||
self.updateInterval = defaults.object(forKey: "\(name)_interval") != nil ? defaults.integer(forKey: "\(name)_interval") : 3
|
||||
self.reader.setInterval(value: self.updateInterval)
|
||||
}
|
||||
|
||||
func start() {
|
||||
if !self.reader.value.value.isEmpty {
|
||||
let value = self.reader.value!.value
|
||||
(self.view as! Widget).setValue(data: [abs(value.first!), value.last!])
|
||||
}
|
||||
if let view = self.view as? BatteryWidget {
|
||||
view.setCharging(value: (self.reader as! BatteryReader).usage.value.ACstatus)
|
||||
}
|
||||
|
||||
self.reader.start()
|
||||
self.reader.value.subscribe(observer: self) { (value, _) in
|
||||
if !value.isEmpty {
|
||||
(self.view as! Widget).setValue(data: [abs(value.first!), value.last!])
|
||||
}
|
||||
}
|
||||
(self.reader as! BatteryReader).usage.subscribe(observer: self) { (value, _) in
|
||||
(self.view as! BatteryWidget).setCharging(value: value.ACstatus)
|
||||
}
|
||||
}
|
||||
|
||||
func initMenu(active: Bool) {
|
||||
menu = NSMenuItem(title: name, action: #selector(toggle), keyEquivalent: "")
|
||||
submenu = NSMenu()
|
||||
|
||||
if defaults.object(forKey: name) != nil {
|
||||
menu.state = defaults.bool(forKey: name) ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
} else {
|
||||
menu.state = NSControl.StateValue.on
|
||||
}
|
||||
menu.target = self
|
||||
menu.isEnabled = true
|
||||
|
||||
let percentage = NSMenuItem(title: "Percentage", action: #selector(toggleWidget), keyEquivalent: "")
|
||||
percentage.state = self.widgetType == Widgets.BatteryPercentage ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
percentage.target = self
|
||||
|
||||
let time = NSMenuItem(title: "Time", action: #selector(toggleWidget), keyEquivalent: "")
|
||||
time.state = self.widgetType == Widgets.BatteryTime ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
time.target = self
|
||||
|
||||
submenu.addItem(percentage)
|
||||
submenu.addItem(time)
|
||||
|
||||
submenu.addItem(NSMenuItem.separator())
|
||||
|
||||
if let view = self.view as? Widget {
|
||||
for widgetMenu in view.menus {
|
||||
submenu.addItem(widgetMenu)
|
||||
}
|
||||
}
|
||||
|
||||
submenu.addItem(NSMenuItem.separator())
|
||||
submenu.addItem(generateIntervalMenu())
|
||||
|
||||
if active {
|
||||
menu.submenu = submenu
|
||||
}
|
||||
}
|
||||
|
||||
@objc func toggle(_ sender: NSMenuItem) {
|
||||
let state = sender.state != NSControl.StateValue.on
|
||||
|
||||
sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on
|
||||
self.defaults.set(state, forKey: name)
|
||||
self.active = state
|
||||
menuBar!.reload(name: self.name)
|
||||
|
||||
if !state {
|
||||
menu.submenu = nil
|
||||
self.stop()
|
||||
} else {
|
||||
menu.submenu = submenu
|
||||
self.start()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func toggleWidget(_ sender: NSMenuItem) {
|
||||
var widgetCode: Float = 0.0
|
||||
|
||||
switch sender.title {
|
||||
case "Percentage":
|
||||
widgetCode = Widgets.BatteryPercentage
|
||||
case "Time":
|
||||
widgetCode = Widgets.BatteryTime
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if self.widgetType == widgetCode {
|
||||
widgetCode = Widgets.Battery
|
||||
}
|
||||
|
||||
let state = sender.state == NSControl.StateValue.on
|
||||
for item in self.submenu.items {
|
||||
if item.title == "Percentage" || item.title == "Time" {
|
||||
item.state = NSControl.StateValue.off
|
||||
}
|
||||
}
|
||||
|
||||
sender.state = state ? NSControl.StateValue.off : NSControl.StateValue.on
|
||||
self.defaults.set(widgetCode, forKey: "\(name)_widget")
|
||||
self.widgetType = widgetCode
|
||||
self.initWidget()
|
||||
self.initMenu(active: true)
|
||||
menuBar!.refresh()
|
||||
}
|
||||
|
||||
func generateIntervalMenu() -> NSMenuItem {
|
||||
let updateInterval = NSMenuItem(title: "Update interval", action: nil, keyEquivalent: "")
|
||||
|
||||
let updateIntervals = NSMenu()
|
||||
let updateInterval_1 = NSMenuItem(title: "1s", action: #selector(changeInterval), keyEquivalent: "")
|
||||
updateInterval_1.state = self.updateInterval == 1 ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
updateInterval_1.target = self
|
||||
let updateInterval_2 = NSMenuItem(title: "3s", action: #selector(changeInterval), keyEquivalent: "")
|
||||
updateInterval_2.state = self.updateInterval == 3 ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
updateInterval_2.target = self
|
||||
let updateInterval_3 = NSMenuItem(title: "5s", action: #selector(changeInterval), keyEquivalent: "")
|
||||
updateInterval_3.state = self.updateInterval == 5 ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
updateInterval_3.target = self
|
||||
let updateInterval_4 = NSMenuItem(title: "10s", action: #selector(changeInterval), keyEquivalent: "")
|
||||
updateInterval_4.state = self.updateInterval == 10 ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
updateInterval_4.target = self
|
||||
let updateInterval_5 = NSMenuItem(title: "15s", action: #selector(changeInterval), keyEquivalent: "")
|
||||
updateInterval_5.state = self.updateInterval == 15 ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
updateInterval_5.target = self
|
||||
|
||||
updateIntervals.addItem(updateInterval_1)
|
||||
updateIntervals.addItem(updateInterval_2)
|
||||
updateIntervals.addItem(updateInterval_3)
|
||||
updateIntervals.addItem(updateInterval_4)
|
||||
updateIntervals.addItem(updateInterval_5)
|
||||
|
||||
updateInterval.submenu = updateIntervals
|
||||
|
||||
return updateInterval
|
||||
}
|
||||
|
||||
@objc func changeInterval(_ sender: NSMenuItem) {
|
||||
var interval: Int = self.updateInterval
|
||||
|
||||
switch sender.title {
|
||||
case "1s":
|
||||
interval = 1
|
||||
case "3s":
|
||||
interval = 3
|
||||
case "5s":
|
||||
interval = 5
|
||||
case "10s":
|
||||
interval = 10
|
||||
case "15s":
|
||||
interval = 15
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
if interval == self.updateInterval {
|
||||
return
|
||||
}
|
||||
|
||||
for item in self.submenu.items {
|
||||
if item.title == "Update interval" {
|
||||
for subitem in item.submenu!.items {
|
||||
subitem.state = NSControl.StateValue.off
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sender.state = NSControl.StateValue.on
|
||||
self.updateInterval = interval
|
||||
self.defaults.set(interval, forKey: "\(name)_interval")
|
||||
self.reader.setInterval(value: interval)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,193 +0,0 @@
|
||||
//
|
||||
// BatteryReader.swift
|
||||
// Stats
|
||||
//
|
||||
// Created by Serhiy Mytrovtsiy on 14/06/2019.
|
||||
// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import IOKit.ps
|
||||
|
||||
struct BatteryUsage {
|
||||
var powerSource: String = ""
|
||||
var state: String = ""
|
||||
var isCharged: Bool = false
|
||||
var capacity: Double = 0
|
||||
var cycles: Int = 0
|
||||
var health: Int = 0
|
||||
|
||||
var amperage: Int = 0
|
||||
var voltage: Double = 0
|
||||
var temperature: Double = 0
|
||||
|
||||
var ACwatts: Int = 0
|
||||
var ACstatus: Bool = false
|
||||
|
||||
var timeToEmpty: Int = 0
|
||||
var timeToCharge: Int = 0
|
||||
}
|
||||
|
||||
class BatteryReader: Reader {
|
||||
public var value: Observable<[Double]>!
|
||||
public var usage: Observable<BatteryUsage> = Observable(BatteryUsage())
|
||||
public var availableAdditional: Bool = false
|
||||
public var updateInterval: Int = 0
|
||||
|
||||
private var service: io_connect_t = 0
|
||||
private var internalChecked: Bool = false
|
||||
private var hasInternalBattery: Bool = false
|
||||
private var timer: Repeater?
|
||||
|
||||
public var available: Bool {
|
||||
get {
|
||||
if !self.internalChecked {
|
||||
let snapshot = IOPSCopyPowerSourcesInfo().takeRetainedValue()
|
||||
let sources = IOPSCopyPowerSourcesList(snapshot).takeRetainedValue() as Array
|
||||
self.hasInternalBattery = sources.count > 0
|
||||
self.internalChecked = true
|
||||
}
|
||||
return self.hasInternalBattery
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
self.value = Observable([])
|
||||
self.service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleSmartBattery"))
|
||||
|
||||
if self.available {
|
||||
self.read()
|
||||
}
|
||||
|
||||
self.timer = Repeater.init(interval: .seconds(1), observer: { _ in
|
||||
self.read()
|
||||
})
|
||||
}
|
||||
|
||||
func start() {
|
||||
read()
|
||||
if self.timer != nil && self.timer!.state.isRunning == false {
|
||||
self.timer!.start()
|
||||
}
|
||||
}
|
||||
|
||||
func stop() {
|
||||
self.timer?.pause()
|
||||
IOServiceClose(self.service)
|
||||
IOObjectRelease(self.service)
|
||||
}
|
||||
|
||||
@objc func read() {
|
||||
let psInfo = IOPSCopyPowerSourcesInfo().takeRetainedValue()
|
||||
let psList = IOPSCopyPowerSourcesList(psInfo).takeRetainedValue() as [CFTypeRef]
|
||||
|
||||
for ps in psList {
|
||||
if let list = IOPSGetPowerSourceDescription(psInfo, ps).takeUnretainedValue() as? Dictionary<String, Any> {
|
||||
let powerSource = list[kIOPSPowerSourceStateKey] as? String ?? "AC Power"
|
||||
let state = list[kIOPSBatteryHealthKey] as! String
|
||||
let isCharged = list[kIOPSIsChargedKey] as? Bool ?? false
|
||||
var cap = Float(list[kIOPSCurrentCapacityKey] as! Int) / 100
|
||||
|
||||
let timeToEmpty = Int(list[kIOPSTimeToEmptyKey] as! Int)
|
||||
let timeToCharged = Int(list[kIOPSTimeToFullChargeKey] as! Int)
|
||||
|
||||
let cycles = self.getIntValue("CycleCount" as CFString) ?? 0
|
||||
|
||||
let maxCapacity = self.getIntValue("MaxCapacity" as CFString) ?? 1
|
||||
let designCapacity = self.getIntValue("DesignCapacity" as CFString) ?? 1
|
||||
|
||||
let amperage = self.getIntValue("Amperage" as CFString) ?? 0
|
||||
let voltage = self.getVoltage() ?? 0
|
||||
let temperature = self.getTemperature() ?? 0
|
||||
|
||||
var ACwatts: Int = 0
|
||||
if let ACDetails = IOPSCopyExternalPowerAdapterDetails() {
|
||||
if let ACList = ACDetails.takeUnretainedValue() as? Dictionary<String, Any> {
|
||||
ACwatts = Int(ACList[kIOPSPowerAdapterWattsKey] as! Int)
|
||||
}
|
||||
}
|
||||
let ACstatus = self.getBoolValue("IsCharging" as CFString) ?? false
|
||||
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.usage << BatteryUsage(
|
||||
powerSource: powerSource,
|
||||
state: state,
|
||||
isCharged: isCharged,
|
||||
capacity: Double(cap),
|
||||
cycles: cycles,
|
||||
health: (100 * maxCapacity) / designCapacity,
|
||||
|
||||
amperage: amperage,
|
||||
voltage: voltage,
|
||||
temperature: temperature,
|
||||
|
||||
ACwatts: ACwatts,
|
||||
ACstatus: ACstatus,
|
||||
|
||||
timeToEmpty: timeToEmpty,
|
||||
timeToCharge: timeToCharged
|
||||
)
|
||||
})
|
||||
|
||||
if powerSource == "Battery Power" {
|
||||
cap = 0 - cap
|
||||
}
|
||||
|
||||
var time = 0
|
||||
if timeToEmpty != 0 && timeToCharged == 0 {
|
||||
time = timeToEmpty
|
||||
} else if timeToEmpty == 0 && timeToCharged != 0 {
|
||||
time = timeToCharged
|
||||
}
|
||||
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.value << [Double(cap), Double(time)]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getBoolValue(_ forIdentifier: CFString) -> Bool? {
|
||||
if let value = IORegistryEntryCreateCFProperty(self.service, forIdentifier, kCFAllocatorDefault, 0) {
|
||||
return value.takeRetainedValue() as? Bool
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getIntValue(_ identifier: CFString) -> Int? {
|
||||
if let value = IORegistryEntryCreateCFProperty(self.service, identifier, kCFAllocatorDefault, 0) {
|
||||
return value.takeRetainedValue() as? Int
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDoubleValue(_ identifier: CFString) -> Double? {
|
||||
if let value = IORegistryEntryCreateCFProperty(self.service, identifier, kCFAllocatorDefault, 0) {
|
||||
return value.takeRetainedValue() as? Double
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getVoltage() -> Double? {
|
||||
if let value = self.getDoubleValue("Voltage" as CFString) {
|
||||
return value / 1000.0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getTemperature() -> Double? {
|
||||
if let value = IORegistryEntryCreateCFProperty(self.service, "Temperature" as CFString, kCFAllocatorDefault, 0) {
|
||||
return value.takeRetainedValue() as! Double / 100.0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setInterval(value: Int) {
|
||||
if value == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
self.updateInterval = value
|
||||
self.timer?.reset(.seconds(Double(value)), restart: false)
|
||||
}
|
||||
}
|
||||
@@ -1,260 +0,0 @@
|
||||
//
|
||||
// BatteryView.swift
|
||||
// Stats
|
||||
//
|
||||
// Created by Serhiy Mytrovtsiy on 05/09/2019.
|
||||
// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Cocoa
|
||||
|
||||
extension Battery {
|
||||
|
||||
func initTab() {
|
||||
self.tabView.view?.frame = NSRect(x: 0, y: 0, width: TabWidth, height: 10)
|
||||
|
||||
makeMain()
|
||||
makeOverview()
|
||||
makeBattery()
|
||||
makePowerAdapter()
|
||||
|
||||
self.tabInitialized = true
|
||||
}
|
||||
|
||||
func makeMain() {
|
||||
let stackHeight: CGFloat = 22
|
||||
let vertical: NSStackView = NSStackView(frame: NSRect(x: 0, y: TabHeight - stackHeight*3 - 4, width: TabWidth, height: stackHeight*3))
|
||||
vertical.orientation = .vertical
|
||||
|
||||
let level: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*2, width: TabWidth - 20, height: stackHeight))
|
||||
level.orientation = .horizontal
|
||||
level.distribution = .equalCentering
|
||||
let levelLabel = LabelField(string: "Level")
|
||||
let levelValue = ValueField(string: "0 %")
|
||||
level.addView(levelLabel, in: .center)
|
||||
level.addView(levelValue, in: .center)
|
||||
|
||||
let source: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*1, width: TabWidth - 20, height: stackHeight))
|
||||
source.orientation = .horizontal
|
||||
source.distribution = .equalCentering
|
||||
let sourceLabel = LabelField(string: "Source")
|
||||
let sourceValue = ValueField(string: "AC Power")
|
||||
source.addView(sourceLabel, in: .center)
|
||||
source.addView(sourceValue, in: .center)
|
||||
|
||||
let time: NSStackView = NSStackView(frame: NSRect(x: 10, y: 0, width: TabWidth - 20, height: stackHeight))
|
||||
time.orientation = .horizontal
|
||||
time.distribution = .equalCentering
|
||||
let timeLabel = LabelField(string: "Time to charge")
|
||||
let timeValue = ValueField(string: "Calculating")
|
||||
time.addView(timeLabel, in: .center)
|
||||
time.addView(timeValue, in: .center)
|
||||
|
||||
vertical.addSubview(level)
|
||||
vertical.addSubview(source)
|
||||
vertical.addSubview(time)
|
||||
|
||||
self.tabView.view?.addSubview(vertical)
|
||||
|
||||
(self.reader as! BatteryReader).usage.subscribe(observer: self) { (value, _) in
|
||||
levelValue.stringValue = "\(Int(abs(value.capacity) * 100)) %"
|
||||
sourceValue.stringValue = value.powerSource
|
||||
|
||||
if value.powerSource == "Battery Power" {
|
||||
timeLabel.stringValue = "Time to discharge"
|
||||
if value.timeToEmpty != -1 && value.timeToEmpty != 0 {
|
||||
timeValue.stringValue = Double(value.timeToEmpty*60).printSecondsToHoursMinutesSeconds()
|
||||
}
|
||||
} else {
|
||||
timeLabel.stringValue = "Time to charge"
|
||||
if value.timeToCharge != -1 && value.timeToCharge != 0 {
|
||||
timeValue.stringValue = Double(value.timeToCharge*60).printSecondsToHoursMinutesSeconds()
|
||||
}
|
||||
}
|
||||
|
||||
if value.timeToEmpty == -1 || value.timeToEmpty == -1 {
|
||||
timeValue.stringValue = "Calculating"
|
||||
}
|
||||
|
||||
if value.isCharged {
|
||||
timeValue.stringValue = "Fully charged"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeOverview() {
|
||||
let overviewLabel: NSView = NSView(frame: NSRect(x: 0, y: TabHeight - 102, 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 - 4)
|
||||
overviewText.isEditable = false
|
||||
overviewText.isSelectable = false
|
||||
overviewText.isBezeled = false
|
||||
overviewText.wantsLayer = true
|
||||
overviewText.textColor = .darkGray
|
||||
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: 184, width: TabWidth, height: stackHeight*3))
|
||||
vertical.orientation = .vertical
|
||||
|
||||
let cycles: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*2, width: TabWidth - 20, height: stackHeight))
|
||||
cycles.orientation = .horizontal
|
||||
cycles.distribution = .equalCentering
|
||||
let cyclesLabel = LabelField(string: "Cycles")
|
||||
let cyclesValue = ValueField(string: "0")
|
||||
cycles.addView(cyclesLabel, in: .center)
|
||||
cycles.addView(cyclesValue, in: .center)
|
||||
|
||||
let health: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*1, width: TabWidth - 20, height: stackHeight))
|
||||
health.orientation = .horizontal
|
||||
health.distribution = .equalCentering
|
||||
let healthLabel = LabelField(string: "Health")
|
||||
let healthValue = ValueField(string: "Calculating")
|
||||
health.addView(healthLabel, in: .center)
|
||||
health.addView(healthValue, in: .center)
|
||||
|
||||
let state: NSStackView = NSStackView(frame: NSRect(x: 10, y: 0, width: TabWidth - 20, height: stackHeight))
|
||||
state.orientation = .horizontal
|
||||
state.distribution = .equalCentering
|
||||
let stateLabel = LabelField(string: "State")
|
||||
let stateValue = ValueField(string: "Calculating")
|
||||
state.addView(stateLabel, in: .center)
|
||||
state.addView(stateValue, in: .center)
|
||||
|
||||
vertical.addSubview(cycles)
|
||||
vertical.addSubview(health)
|
||||
vertical.addSubview(state)
|
||||
|
||||
self.tabView.view?.addSubview(vertical)
|
||||
|
||||
(self.reader as! BatteryReader).usage.subscribe(observer: self) { (value, _) in
|
||||
cyclesValue.stringValue = "\(value.cycles)"
|
||||
stateValue.stringValue = value.state
|
||||
healthValue.stringValue = "\(value.health) %"
|
||||
}
|
||||
}
|
||||
|
||||
func makeBattery() {
|
||||
let batteryLabel: NSView = NSView(frame: NSRect(x: 0, y: TabHeight - 202, width: TabWidth, height: 25))
|
||||
|
||||
batteryLabel.wantsLayer = true
|
||||
batteryLabel.layer?.backgroundColor = NSColor(hexString: "#eeeeee", alpha: 0.5).cgColor
|
||||
|
||||
let overviewText: NSTextField = NSTextField(string: "Battery")
|
||||
overviewText.frame = NSRect(x: 0, y: 0, width: TabWidth, height: batteryLabel.frame.size.height - 4)
|
||||
overviewText.isEditable = false
|
||||
overviewText.isSelectable = false
|
||||
overviewText.isBezeled = false
|
||||
overviewText.wantsLayer = true
|
||||
overviewText.textColor = .darkGray
|
||||
overviewText.canDrawSubviewsIntoLayer = true
|
||||
overviewText.alignment = .center
|
||||
overviewText.backgroundColor = NSColor(hexString: "#dddddd", alpha: 0)
|
||||
overviewText.font = NSFont.systemFont(ofSize: 12, weight: .medium)
|
||||
|
||||
batteryLabel.addSubview(overviewText)
|
||||
self.tabView.view?.addSubview(batteryLabel)
|
||||
|
||||
let stackHeight: CGFloat = 22
|
||||
let vertical: NSStackView = NSStackView(frame: NSRect(x: 0, y: TabHeight - 273, width: TabWidth, height: stackHeight*3))
|
||||
vertical.orientation = .vertical
|
||||
|
||||
let amperage: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*2, width: TabWidth - 20, height: stackHeight))
|
||||
amperage.orientation = .horizontal
|
||||
amperage.distribution = .equalCentering
|
||||
let amperageLabel = LabelField(string: "Amperage")
|
||||
let amperageValue = ValueField(string: "0 mA")
|
||||
amperage.addView(amperageLabel, in: .center)
|
||||
amperage.addView(amperageValue, in: .center)
|
||||
|
||||
let voltage: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*1, width: TabWidth - 20, height: stackHeight))
|
||||
voltage.orientation = .horizontal
|
||||
voltage.distribution = .equalCentering
|
||||
let voltageLabel = LabelField(string: "Voltage")
|
||||
let voltageValue = ValueField(string: "0 V")
|
||||
voltage.addView(voltageLabel, in: .center)
|
||||
voltage.addView(voltageValue, in: .center)
|
||||
|
||||
let temperature: NSStackView = NSStackView(frame: NSRect(x: 10, y: 0, width: TabWidth - 20, height: stackHeight))
|
||||
temperature.orientation = .horizontal
|
||||
temperature.distribution = .equalCentering
|
||||
let temperatureLabel = LabelField(string: "Temperature")
|
||||
let temperatureValue = ValueField(string: "0 °C")
|
||||
temperature.addView(temperatureLabel, in: .center)
|
||||
temperature.addView(temperatureValue, in: .center)
|
||||
|
||||
vertical.addSubview(amperage)
|
||||
vertical.addSubview(voltage)
|
||||
vertical.addSubview(temperature)
|
||||
|
||||
self.tabView.view?.addSubview(vertical)
|
||||
(self.reader as! BatteryReader).usage.subscribe(observer: self) { (value, _) in
|
||||
amperageValue.stringValue = "\(abs(value.amperage)) mA"
|
||||
voltageValue.stringValue = "\(value.voltage.roundTo(decimalPlaces: 2)) V"
|
||||
temperatureValue.stringValue = "\(value.temperature) °C"
|
||||
}
|
||||
}
|
||||
|
||||
func makePowerAdapter() {
|
||||
let powerAdapterLabel: NSView = NSView(frame: NSRect(x: 0, y: 52, width: TabWidth, height: 25))
|
||||
|
||||
powerAdapterLabel.wantsLayer = true
|
||||
powerAdapterLabel.layer?.backgroundColor = NSColor(hexString: "#eeeeee", alpha: 0.5).cgColor
|
||||
|
||||
let overviewText: NSTextField = NSTextField(string: "Power adapter")
|
||||
overviewText.frame = NSRect(x: 0, y: 0, width: TabWidth, height: powerAdapterLabel.frame.size.height - 4)
|
||||
overviewText.isEditable = false
|
||||
overviewText.isSelectable = false
|
||||
overviewText.isBezeled = false
|
||||
overviewText.wantsLayer = true
|
||||
overviewText.textColor = .darkGray
|
||||
overviewText.canDrawSubviewsIntoLayer = true
|
||||
overviewText.alignment = .center
|
||||
overviewText.backgroundColor = NSColor(hexString: "#dddddd", alpha: 0)
|
||||
overviewText.font = NSFont.systemFont(ofSize: 12, weight: .medium)
|
||||
|
||||
powerAdapterLabel.addSubview(overviewText)
|
||||
self.tabView.view?.addSubview(powerAdapterLabel)
|
||||
|
||||
let stackHeight: CGFloat = 22
|
||||
let vertical: NSStackView = NSStackView(frame: NSRect(x: 0, y: 4, width: TabWidth, height: stackHeight*2))
|
||||
vertical.orientation = .vertical
|
||||
|
||||
let power: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*1, width: TabWidth - 20, height: stackHeight))
|
||||
power.orientation = .horizontal
|
||||
power.distribution = .equalCentering
|
||||
let powerLabel = LabelField(string: "Power")
|
||||
let powerValue = ValueField(string: "0 W")
|
||||
power.addView(powerLabel, in: .center)
|
||||
power.addView(powerValue, in: .center)
|
||||
|
||||
let charging: NSStackView = NSStackView(frame: NSRect(x: 10, y: 0, width: TabWidth - 20, height: stackHeight))
|
||||
charging.orientation = .horizontal
|
||||
charging.distribution = .equalCentering
|
||||
let chargingLabel = LabelField(string: "Is charging")
|
||||
let chargingValue = ValueField(string: "No")
|
||||
charging.addView(chargingLabel, in: .center)
|
||||
charging.addView(chargingValue, in: .center)
|
||||
|
||||
vertical.addSubview(power)
|
||||
vertical.addSubview(charging)
|
||||
|
||||
self.tabView.view?.addSubview(vertical)
|
||||
|
||||
(self.reader as! BatteryReader).usage.subscribe(observer: self) { (value, _) in
|
||||
powerValue.stringValue = value.powerSource == "Battery Power" ? "Not connected" : "\(value.ACwatts) W"
|
||||
chargingValue.stringValue = value.ACstatus ? "Yes" : "No"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,228 +7,68 @@
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import Charts
|
||||
|
||||
class CPU: Module {
|
||||
public let name: String = "CPU"
|
||||
public let shortName: String = "CPU"
|
||||
public var view: NSView = NSView()
|
||||
public var menu: NSMenuItem = NSMenuItem()
|
||||
public var active: Bool = true
|
||||
public var available: Bool = true
|
||||
public var hyperthreading: Observable<Bool>
|
||||
public var reader: Reader = CPUReader()
|
||||
public var tabView: NSTabViewItem = NSTabViewItem()
|
||||
public var tabAvailable: Bool = true
|
||||
public var tabInitialized: Bool = false
|
||||
public var widgetType: WidgetType
|
||||
public var chart: LineChartView = LineChartView()
|
||||
public var updateInterval: Int
|
||||
public var name: String = "CPU"
|
||||
public var updateInterval: Double = 1
|
||||
|
||||
private let defaults = UserDefaults.standard
|
||||
private var submenu: NSMenu = NSMenu()
|
||||
public var enabled: Bool = true
|
||||
public var available: Bool = true
|
||||
|
||||
public var readers: [Reader] = []
|
||||
public var task: Repeater?
|
||||
|
||||
public var widget: ModuleWidget = ModuleWidget()
|
||||
public var popup: ModulePopup = ModulePopup(true)
|
||||
public var menu: NSMenuItem = NSMenuItem()
|
||||
|
||||
public let defaults = UserDefaults.standard
|
||||
public var submenu: NSMenu = NSMenu()
|
||||
|
||||
var systemValue: NSTextField = NSTextField()
|
||||
var userValue: NSTextField = NSTextField()
|
||||
var idleValue: NSTextField = NSTextField()
|
||||
var processViewList: [NSStackView] = []
|
||||
|
||||
init() {
|
||||
self.active = defaults.object(forKey: name) != nil ? defaults.bool(forKey: name) : true
|
||||
self.hyperthreading = Observable(defaults.object(forKey: "\(name)_hyperthreading") != nil ? defaults.bool(forKey: "\(name)_hyperthreading") : false)
|
||||
self.widgetType = defaults.object(forKey: "\(name)_widget") != nil ? defaults.float(forKey: "\(name)_widget") : Widgets.Mini
|
||||
self.updateInterval = defaults.object(forKey: "\(name)_interval") != nil ? defaults.integer(forKey: "\(name)_interval") : 1
|
||||
self.enabled = defaults.object(forKey: name) != nil ? defaults.bool(forKey: name) : true
|
||||
self.widget.type = defaults.object(forKey: "\(name)_widget") != nil ? defaults.float(forKey: "\(name)_widget") : Widgets.Mini
|
||||
|
||||
self.reader.setInterval(value: self.updateInterval)
|
||||
if self.widgetType == Widgets.BarChart {
|
||||
(self.reader as! CPUReader).perCoreMode = true
|
||||
(self.reader as! CPUReader).hyperthreading = self.hyperthreading.value
|
||||
}
|
||||
|
||||
if !self.available {
|
||||
self.reader.stop()
|
||||
}
|
||||
}
|
||||
|
||||
func initMenu(active: Bool) {
|
||||
menu = NSMenuItem(title: name, action: #selector(toggle), keyEquivalent: "")
|
||||
submenu = NSMenu()
|
||||
|
||||
if defaults.object(forKey: name) != nil {
|
||||
menu.state = defaults.bool(forKey: name) ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
} else {
|
||||
menu.state = NSControl.StateValue.on
|
||||
}
|
||||
menu.target = self
|
||||
|
||||
let mini = NSMenuItem(title: "Mini", action: #selector(toggleWidget), keyEquivalent: "")
|
||||
mini.state = self.widgetType == Widgets.Mini ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
mini.target = self
|
||||
|
||||
let chart = NSMenuItem(title: "Chart", action: #selector(toggleWidget), keyEquivalent: "")
|
||||
chart.state = self.widgetType == Widgets.Chart ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
chart.target = self
|
||||
|
||||
let chartWithValue = NSMenuItem(title: "Chart with value", action: #selector(toggleWidget), keyEquivalent: "")
|
||||
chartWithValue.state = self.widgetType == Widgets.ChartWithValue ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
chartWithValue.target = self
|
||||
|
||||
let barChart = NSMenuItem(title: "Bar chart", action: #selector(toggleWidget), keyEquivalent: "")
|
||||
barChart.state = self.widgetType == Widgets.BarChart ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
barChart.target = self
|
||||
|
||||
let hyperthreading = NSMenuItem(title: "Hyperthreading", action: #selector(toggleHyperthreading), keyEquivalent: "")
|
||||
hyperthreading.state = self.hyperthreading.value ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
hyperthreading.target = self
|
||||
|
||||
submenu.addItem(mini)
|
||||
submenu.addItem(chart)
|
||||
submenu.addItem(chartWithValue)
|
||||
submenu.addItem(barChart)
|
||||
|
||||
submenu.addItem(NSMenuItem.separator())
|
||||
|
||||
if let view = self.view as? Widget {
|
||||
for widgetMenu in view.menus {
|
||||
submenu.addItem(widgetMenu)
|
||||
}
|
||||
}
|
||||
|
||||
if self.widgetType == Widgets.BarChart {
|
||||
submenu.addItem(hyperthreading)
|
||||
}
|
||||
|
||||
submenu.addItem(NSMenuItem.separator())
|
||||
submenu.addItem(generateIntervalMenu())
|
||||
|
||||
if active {
|
||||
menu.submenu = submenu
|
||||
}
|
||||
}
|
||||
|
||||
@objc func toggle(_ sender: NSMenuItem) {
|
||||
let state = sender.state != NSControl.StateValue.on
|
||||
sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on
|
||||
self.defaults.set(state, forKey: name)
|
||||
self.active = state
|
||||
menuBar!.reload(name: self.name)
|
||||
|
||||
if !state {
|
||||
menu.submenu = nil
|
||||
self.stop()
|
||||
} else {
|
||||
menu.submenu = submenu
|
||||
self.start()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func toggleWidget(_ sender: NSMenuItem) {
|
||||
var widgetCode: Float = 0.0
|
||||
|
||||
switch sender.title {
|
||||
case "Mini":
|
||||
widgetCode = Widgets.Mini
|
||||
case "Chart":
|
||||
widgetCode = Widgets.Chart
|
||||
case "Chart with value":
|
||||
widgetCode = Widgets.ChartWithValue
|
||||
case "Bar chart":
|
||||
widgetCode = Widgets.BarChart
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if widgetCode == Widgets.BarChart {
|
||||
(self.reader as! CPUReader).perCoreMode = true
|
||||
} else {
|
||||
(self.reader as! CPUReader).perCoreMode = false
|
||||
}
|
||||
|
||||
if self.widgetType == widgetCode {
|
||||
return
|
||||
}
|
||||
|
||||
for item in self.submenu.items {
|
||||
if item.title == "Mini" || item.title == "Chart" || item.title == "Chart with value" || item.title == "Bar chart" {
|
||||
item.state = NSControl.StateValue.off
|
||||
}
|
||||
}
|
||||
|
||||
sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on
|
||||
self.defaults.set(widgetCode, forKey: "\(name)_widget")
|
||||
self.widgetType = widgetCode
|
||||
self.initWidget()
|
||||
self.initMenu(active: true)
|
||||
menuBar!.reload(name: self.name)
|
||||
}
|
||||
|
||||
@objc func toggleHyperthreading(_ sender: NSMenuItem) {
|
||||
sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on
|
||||
self.defaults.set(sender.state == NSControl.StateValue.on, forKey: "\(name)_hyperthreading")
|
||||
self.hyperthreading << (sender.state == NSControl.StateValue.on)
|
||||
(self.reader as! CPUReader).hyperthreading = sender.state == NSControl.StateValue.on
|
||||
}
|
||||
|
||||
func generateIntervalMenu() -> NSMenuItem {
|
||||
let updateInterval = NSMenuItem(title: "Update interval", action: nil, keyEquivalent: "")
|
||||
self.initMenu()
|
||||
self.initPopup()
|
||||
|
||||
let updateIntervals = NSMenu()
|
||||
let updateInterval_1 = NSMenuItem(title: "1s", action: #selector(changeInterval), keyEquivalent: "")
|
||||
updateInterval_1.state = self.updateInterval == 1 ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
updateInterval_1.target = self
|
||||
let updateInterval_2 = NSMenuItem(title: "3s", action: #selector(changeInterval), keyEquivalent: "")
|
||||
updateInterval_2.state = self.updateInterval == 3 ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
updateInterval_2.target = self
|
||||
let updateInterval_3 = NSMenuItem(title: "5s", action: #selector(changeInterval), keyEquivalent: "")
|
||||
updateInterval_3.state = self.updateInterval == 5 ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
updateInterval_3.target = self
|
||||
let updateInterval_4 = NSMenuItem(title: "10s", action: #selector(changeInterval), keyEquivalent: "")
|
||||
updateInterval_4.state = self.updateInterval == 10 ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
updateInterval_4.target = self
|
||||
let updateInterval_5 = NSMenuItem(title: "15s", action: #selector(changeInterval), keyEquivalent: "")
|
||||
updateInterval_5.state = self.updateInterval == 15 ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
updateInterval_5.target = self
|
||||
readers.append(CPULoadReader(self.name, self.loadUpdater, self.updateChart, true))
|
||||
readers.append(CPUUsageReader(self.usageUpdater))
|
||||
readers.append(CPUProcessReader(self.processesUpdater))
|
||||
|
||||
updateIntervals.addItem(updateInterval_1)
|
||||
updateIntervals.addItem(updateInterval_2)
|
||||
updateIntervals.addItem(updateInterval_3)
|
||||
updateIntervals.addItem(updateInterval_4)
|
||||
updateIntervals.addItem(updateInterval_5)
|
||||
|
||||
updateInterval.submenu = updateIntervals
|
||||
|
||||
return updateInterval
|
||||
}
|
||||
|
||||
@objc func changeInterval(_ sender: NSMenuItem) {
|
||||
var interval: Int = self.updateInterval
|
||||
|
||||
switch sender.title {
|
||||
case "1s":
|
||||
interval = 1
|
||||
case "3s":
|
||||
interval = 3
|
||||
case "5s":
|
||||
interval = 5
|
||||
case "10s":
|
||||
interval = 10
|
||||
case "15s":
|
||||
interval = 15
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
if interval == self.updateInterval {
|
||||
return
|
||||
}
|
||||
|
||||
for item in self.submenu.items {
|
||||
if item.title == "Update interval" {
|
||||
for subitem in item.submenu!.items {
|
||||
subitem.state = NSControl.StateValue.off
|
||||
}
|
||||
self.task = Repeater.init(interval: .seconds(self.updateInterval), observer: { _ in
|
||||
self.readers.forEach { reader in
|
||||
reader.read()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public func start() {
|
||||
if self.task != nil && self.task!.state.isRunning == false {
|
||||
self.task!.start()
|
||||
}
|
||||
}
|
||||
|
||||
public func stop() {
|
||||
if self.task!.state.isRunning {
|
||||
self.task?.pause()
|
||||
}
|
||||
}
|
||||
|
||||
public func restart () {
|
||||
self.stop()
|
||||
self.start()
|
||||
}
|
||||
|
||||
private func loadUpdater(value: [Double]) {
|
||||
if !value.isEmpty && self.widget.view is Widget {
|
||||
(self.widget.view as! Widget).setValue(data: value)
|
||||
}
|
||||
|
||||
sender.state = NSControl.StateValue.on
|
||||
self.updateInterval = interval
|
||||
self.defaults.set(interval, forKey: "\(name)_interval")
|
||||
self.reader.setInterval(value: interval)
|
||||
}
|
||||
}
|
||||
|
||||
142
Stats/Modules/CPU/CPULoadReader.swift
Normal file
142
Stats/Modules/CPU/CPULoadReader.swift
Normal file
@@ -0,0 +1,142 @@
|
||||
//
|
||||
// CPUUsageReader.swift
|
||||
// Stats
|
||||
//
|
||||
// Created by Serhiy Mytrovtsiy on 13/01/2020.
|
||||
// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class CPULoadReader: Reader {
|
||||
public var name: String = "Load"
|
||||
public var enabled: Bool = true
|
||||
public var available: Bool = true
|
||||
public var optional: Bool = false
|
||||
public var initialized: Bool = false
|
||||
public var callback: ([Double]) -> Void = {_ in}
|
||||
public var chartCallback: (Double) -> Void = {_ in}
|
||||
|
||||
public var perCoreMode: Bool = true
|
||||
public var hyperthreading: Bool = false
|
||||
|
||||
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()
|
||||
|
||||
init(_ name: String, _ updater: @escaping ([Double]) -> Void, _ chartUpdater: @escaping (Double) -> Void, _ coreMode: Bool = false) {
|
||||
self.callback = updater
|
||||
self.chartCallback = chartUpdater
|
||||
self.perCoreMode = coreMode
|
||||
self.hyperthreading = UserDefaults.standard.object(forKey: "\(name)_hyperthreading") != nil ? UserDefaults.standard.bool(forKey: "\(name)_hyperthreading") : false
|
||||
|
||||
let mibKeys: [Int32] = [ CTL_HW, HW_NCPU ]
|
||||
|
||||
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)
|
||||
if status != 0 {
|
||||
numCPUs = 1
|
||||
}
|
||||
}
|
||||
|
||||
if self.available {
|
||||
self.read()
|
||||
}
|
||||
}
|
||||
|
||||
public func toggleEnable(_ value: Bool) {
|
||||
self.enabled = value
|
||||
}
|
||||
|
||||
public func read() {
|
||||
if !self.enabled && self.initialized { return }
|
||||
|
||||
var numCPUsU: natural_t = 0
|
||||
let err: kern_return_t = host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &numCPUsU, &cpuInfo, &numCpuInfo);
|
||||
|
||||
if err == KERN_SUCCESS {
|
||||
CPUUsageLock.lock()
|
||||
|
||||
var inUseOnAllCores: Int32 = 0
|
||||
var totalOnAllCores: Int32 = 0
|
||||
var usagePerCore: [Double] = []
|
||||
|
||||
var incrementNumber = 1
|
||||
if !self.hyperthreading && self.perCoreMode {
|
||||
incrementNumber = 2
|
||||
}
|
||||
|
||||
for i in stride(from: 0, to: Int32(numCPUs), by: incrementNumber) {
|
||||
var inUse: Int32
|
||||
var total: Int32
|
||||
if let prevCpuInfo = prevCpuInfo {
|
||||
inUse = cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_USER)]
|
||||
- prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_USER)]
|
||||
+ cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_SYSTEM)]
|
||||
- prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_SYSTEM)]
|
||||
+ cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_NICE)]
|
||||
- prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_NICE)]
|
||||
total = inUse + (cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_IDLE)]
|
||||
- prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_IDLE)])
|
||||
} else {
|
||||
inUse = cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_USER)]
|
||||
+ cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_SYSTEM)]
|
||||
+ cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_NICE)]
|
||||
total = inUse + cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_IDLE)]
|
||||
}
|
||||
|
||||
inUseOnAllCores = inUseOnAllCores + inUse
|
||||
totalOnAllCores = totalOnAllCores + total
|
||||
if total != 0 {
|
||||
usagePerCore.append(Double(inUse) / Double(total))
|
||||
}
|
||||
}
|
||||
|
||||
DispatchQueue.main.async(execute: {
|
||||
if self.perCoreMode {
|
||||
self.callback(usagePerCore)
|
||||
} else {
|
||||
self.callback([(Double(inUseOnAllCores) / Double(totalOnAllCores))])
|
||||
}
|
||||
self.chartCallback(Double(inUseOnAllCores) / Double(totalOnAllCores))
|
||||
})
|
||||
|
||||
CPUUsageLock.unlock()
|
||||
|
||||
if let prevCpuInfo = prevCpuInfo {
|
||||
let prevCpuInfoSize: size_t = MemoryLayout<integer_t>.stride * Int(numPrevCpuInfo)
|
||||
vm_deallocate(mach_task_self_, vm_address_t(bitPattern: prevCpuInfo), vm_size_t(prevCpuInfoSize))
|
||||
}
|
||||
|
||||
prevCpuInfo = cpuInfo
|
||||
numPrevCpuInfo = numCpuInfo
|
||||
|
||||
cpuInfo = nil
|
||||
numCpuInfo = 0
|
||||
} else {
|
||||
print("Error KERN_SUCCESS!")
|
||||
}
|
||||
}
|
||||
|
||||
private 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
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,17 @@
|
||||
//
|
||||
// Memory.swift
|
||||
// CPUMenu.swift
|
||||
// Stats
|
||||
//
|
||||
// Created by Serhiy Mytrovtsiy on 01.06.2019.
|
||||
// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
|
||||
// Created by Serhiy Mytrovtsiy on 13/01/2020.
|
||||
// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import Charts
|
||||
|
||||
class Memory: Module {
|
||||
public let name: String = "Memory"
|
||||
public let shortName: String = "MEM"
|
||||
public var view: NSView = NSView()
|
||||
public var menu: NSMenuItem = NSMenuItem()
|
||||
public var active: Bool = true
|
||||
public var available: Bool = true
|
||||
public var reader: Reader = MemoryReader()
|
||||
public var widgetType: WidgetType
|
||||
public var tabAvailable: Bool = true
|
||||
public var tabInitialized: Bool = false
|
||||
public var tabView: NSTabViewItem = NSTabViewItem()
|
||||
public var chart: LineChartView = LineChartView()
|
||||
public var updateInterval: Int
|
||||
|
||||
private let defaults = UserDefaults.standard
|
||||
private var submenu: NSMenu = NSMenu()
|
||||
|
||||
init() {
|
||||
self.active = defaults.object(forKey: name) != nil ? defaults.bool(forKey: name) : true
|
||||
self.widgetType = defaults.object(forKey: "\(name)_widget") != nil ? defaults.float(forKey: "\(name)_widget") : Widgets.Mini
|
||||
self.updateInterval = defaults.object(forKey: "\(name)_interval") != nil ? defaults.integer(forKey: "\(name)_interval") : 5
|
||||
self.reader.setInterval(value: self.updateInterval)
|
||||
}
|
||||
|
||||
func initMenu(active: Bool) {
|
||||
menu = NSMenuItem(title: name, action: #selector(toggle), keyEquivalent: "")
|
||||
submenu = NSMenu()
|
||||
extension CPU {
|
||||
public func initMenu() {
|
||||
self.menu = NSMenuItem(title: name, action: #selector(toggle), keyEquivalent: "")
|
||||
self.submenu = NSMenu()
|
||||
|
||||
if defaults.object(forKey: name) != nil {
|
||||
menu.state = defaults.bool(forKey: name) ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
@@ -46,21 +21,26 @@ class Memory: Module {
|
||||
menu.target = self
|
||||
|
||||
let mini = NSMenuItem(title: "Mini", action: #selector(toggleWidget), keyEquivalent: "")
|
||||
mini.state = self.widgetType == Widgets.Mini ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
mini.state = self.widget.type == Widgets.Mini ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
mini.target = self
|
||||
|
||||
let chart = NSMenuItem(title: "Chart", action: #selector(toggleWidget), keyEquivalent: "")
|
||||
chart.state = self.widgetType == Widgets.Chart ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
chart.state = self.widget.type == Widgets.Chart ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
chart.target = self
|
||||
|
||||
let chartWithValue = NSMenuItem(title: "Chart with value", action: #selector(toggleWidget), keyEquivalent: "")
|
||||
chartWithValue.state = self.widgetType == Widgets.ChartWithValue ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
chartWithValue.state = self.widget.type == Widgets.ChartWithValue ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
chartWithValue.target = self
|
||||
|
||||
let barChart = NSMenuItem(title: "Bar chart", action: #selector(toggleWidget), keyEquivalent: "")
|
||||
barChart.state = self.widgetType == Widgets.BarChart ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
barChart.state = self.widget.type == Widgets.BarChart ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
barChart.target = self
|
||||
|
||||
let hyperthreading = NSMenuItem(title: "Hyperthreading", action: #selector(toggleHyperthreading), keyEquivalent: "")
|
||||
let hyper = UserDefaults.standard.object(forKey: "\(name)_hyperthreading") != nil ? UserDefaults.standard.bool(forKey: "\(name)_hyperthreading") : false
|
||||
hyperthreading.state = hyper ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
hyperthreading.target = self
|
||||
|
||||
submenu.addItem(mini)
|
||||
submenu.addItem(chart)
|
||||
submenu.addItem(chartWithValue)
|
||||
@@ -68,35 +48,38 @@ class Memory: Module {
|
||||
|
||||
submenu.addItem(NSMenuItem.separator())
|
||||
|
||||
if let view = self.view as? Widget {
|
||||
if let view = self.widget.view as? Widget {
|
||||
for widgetMenu in view.menus {
|
||||
submenu.addItem(widgetMenu)
|
||||
}
|
||||
}
|
||||
|
||||
if self.widget.type == Widgets.BarChart {
|
||||
submenu.addItem(hyperthreading)
|
||||
}
|
||||
|
||||
submenu.addItem(NSMenuItem.separator())
|
||||
submenu.addItem(generateIntervalMenu())
|
||||
|
||||
if active {
|
||||
if self.enabled {
|
||||
menu.submenu = submenu
|
||||
}
|
||||
}
|
||||
|
||||
@objc func toggle(_ sender: NSMenuItem) {
|
||||
let state = sender.state != NSControl.StateValue.on
|
||||
|
||||
sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on
|
||||
self.defaults.set(state, forKey: name)
|
||||
self.active = state
|
||||
self.enabled = state
|
||||
menuBar!.reload(name: self.name)
|
||||
|
||||
if !state {
|
||||
menu.submenu = nil
|
||||
self.stop()
|
||||
} else {
|
||||
menu.submenu = submenu
|
||||
self.start()
|
||||
}
|
||||
|
||||
self.restart()
|
||||
}
|
||||
|
||||
@objc func toggleWidget(_ sender: NSMenuItem) {
|
||||
@@ -115,7 +98,19 @@ class Memory: Module {
|
||||
break
|
||||
}
|
||||
|
||||
if self.widgetType == widgetCode {
|
||||
if widgetCode == Widgets.BarChart {
|
||||
self.readers.forEach { reader in
|
||||
if reader is CPULoadReader {
|
||||
(reader as! CPULoadReader).perCoreMode = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.readers.filter{ $0 is CPULoadReader }.forEach { reader in
|
||||
(reader as! CPULoadReader).perCoreMode = false
|
||||
}
|
||||
}
|
||||
|
||||
if self.widget.type == widgetCode {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -127,13 +122,21 @@ class Memory: Module {
|
||||
|
||||
sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on
|
||||
self.defaults.set(widgetCode, forKey: "\(name)_widget")
|
||||
self.widgetType = widgetCode
|
||||
self.self.widget.type = widgetCode
|
||||
self.initWidget()
|
||||
self.initMenu(active: true)
|
||||
self.initMenu()
|
||||
menuBar!.reload(name: self.name)
|
||||
}
|
||||
|
||||
func generateIntervalMenu() -> NSMenuItem {
|
||||
@objc func toggleHyperthreading(_ sender: NSMenuItem) {
|
||||
sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on
|
||||
self.defaults.set(sender.state == NSControl.StateValue.on, forKey: "\(name)_hyperthreading")
|
||||
self.readers.filter{ $0 is CPULoadReader }.forEach { reader in
|
||||
(reader as! CPULoadReader).hyperthreading = sender.state == NSControl.StateValue.on
|
||||
}
|
||||
}
|
||||
|
||||
private func generateIntervalMenu() -> NSMenuItem {
|
||||
let updateInterval = NSMenuItem(title: "Update interval", action: nil, keyEquivalent: "")
|
||||
|
||||
let updateIntervals = NSMenu()
|
||||
@@ -165,7 +168,7 @@ class Memory: Module {
|
||||
}
|
||||
|
||||
@objc func changeInterval(_ sender: NSMenuItem) {
|
||||
var interval: Int = self.updateInterval
|
||||
var interval: Double = self.updateInterval
|
||||
|
||||
switch sender.title {
|
||||
case "1s":
|
||||
@@ -198,6 +201,6 @@ class Memory: Module {
|
||||
sender.state = NSControl.StateValue.on
|
||||
self.updateInterval = interval
|
||||
self.defaults.set(interval, forKey: "\(name)_interval")
|
||||
self.reader.setInterval(value: interval)
|
||||
self.task?.reset(.seconds(interval), restart: self.task!.state.isRunning)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// CPUView.swift
|
||||
// CPUPopup.swift
|
||||
// Stats
|
||||
//
|
||||
// Created by Serhiy Mytrovtsiy on 03/09/2019.
|
||||
@@ -7,63 +7,54 @@
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import Foundation
|
||||
import Charts
|
||||
|
||||
extension CPU {
|
||||
|
||||
func initTab() {
|
||||
self.tabView.view?.frame = NSRect(x: 0, y: 0, width: TabWidth, height: TabHeight)
|
||||
public func initPopup() {
|
||||
self.popup.view.view?.frame = NSRect(x: 0, y: 0, width: TabWidth, height: TabHeight)
|
||||
|
||||
makeChart()
|
||||
makeOverview()
|
||||
makeProcesses()
|
||||
|
||||
self.tabInitialized = true
|
||||
|
||||
(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() {
|
||||
private 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 - 110, width: TabWidth, height: 102))
|
||||
self.chart.animate(xAxisDuration: 2.0, yAxisDuration: 2.0, easingOption: .easeInCubic)
|
||||
self.chart.backgroundColor = .white
|
||||
self.chart.noDataText = "No \(self.name) usage data"
|
||||
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.popup.chart = LineChartView(frame: CGRect(x: 0, y: TabHeight - 110, width: TabWidth, height: 102))
|
||||
self.popup.chart.animate(xAxisDuration: 2.0, yAxisDuration: 2.0, easingOption: .easeInCubic)
|
||||
self.popup.chart.backgroundColor = .white
|
||||
self.popup.chart.noDataText = "No \(self.name) usage data"
|
||||
self.popup.chart.legend.enabled = false
|
||||
self.popup.chart.scaleXEnabled = false
|
||||
self.popup.chart.scaleYEnabled = false
|
||||
self.popup.chart.pinchZoomEnabled = false
|
||||
self.popup.chart.doubleTapToZoomEnabled = false
|
||||
self.popup.chart.drawBordersEnabled = false
|
||||
|
||||
self.chart.rightAxis.enabled = false
|
||||
self.popup.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.popup.chart.leftAxis.axisMinimum = 0
|
||||
self.popup.chart.leftAxis.axisMaximum = 100
|
||||
self.popup.chart.leftAxis.labelCount = 6
|
||||
self.popup.chart.leftAxis.drawGridLinesEnabled = false
|
||||
self.popup.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.popup.chart.leftAxis.gridColor = NSColor(red:220/255, green:220/255, blue:220/255, alpha:1)
|
||||
self.popup.chart.leftAxis.gridLineWidth = 0.5
|
||||
self.popup.chart.leftAxis.drawGridLinesEnabled = true
|
||||
self.popup.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
|
||||
self.popup.chart.xAxis.drawAxisLineEnabled = false
|
||||
self.popup.chart.xAxis.drawLimitLinesBehindDataEnabled = false
|
||||
self.popup.chart.xAxis.gridLineWidth = 0.5
|
||||
self.popup.chart.xAxis.drawGridLinesEnabled = false
|
||||
self.popup.chart.xAxis.drawLabelsEnabled = false
|
||||
|
||||
let marker = ChartMarker()
|
||||
marker.chartView = self.chart
|
||||
self.chart.marker = marker
|
||||
marker.chartView = self.popup.chart
|
||||
self.popup.chart.marker = marker
|
||||
|
||||
let lineChartEntry = [ChartDataEntry]()
|
||||
let chartDataSet = LineChartDataSet(entries: lineChartEntry, label: "\(self.name) Usage")
|
||||
@@ -78,24 +69,27 @@ extension CPU {
|
||||
data.addDataSet(chartDataSet)
|
||||
data.setDrawValues(false)
|
||||
|
||||
self.chart.data = LineChartData(dataSet: chartDataSet)
|
||||
self.popup.chart.data = LineChartData(dataSet: chartDataSet)
|
||||
|
||||
self.tabView.view?.addSubview(self.chart)
|
||||
self.popup.view.view?.addSubview(self.popup.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)
|
||||
public func updateChart(value: Double) {
|
||||
let v: Double = Double((value * 100).roundTo(decimalPlaces: 2))!
|
||||
|
||||
let index = Double((self.popup.chart.data?.getDataSetByIndex(0)?.entryCount)!)
|
||||
self.popup.chart.data?.addEntry(ChartDataEntry(x: index, y: v), dataSetIndex: 0)
|
||||
|
||||
if index > 120 {
|
||||
self.chart.xAxis.axisMinimum = index - 120
|
||||
self.popup.chart.xAxis.axisMinimum = index - 120
|
||||
}
|
||||
self.chart.xAxis.axisMaximum = index
|
||||
self.chart.notifyDataSetChanged()
|
||||
self.chart.moveViewToX(index)
|
||||
|
||||
self.popup.chart.xAxis.axisMaximum = index
|
||||
self.popup.chart.notifyDataSetChanged()
|
||||
self.popup.chart.moveViewToX(index)
|
||||
}
|
||||
|
||||
func makeOverview() {
|
||||
private func makeOverview() {
|
||||
let overviewLabel: NSView = NSView(frame: NSRect(x: 0, y: TabHeight - 140, width: TabWidth, height: 25))
|
||||
|
||||
overviewLabel.wantsLayer = true
|
||||
@@ -114,7 +108,7 @@ extension CPU {
|
||||
overviewText.font = NSFont.systemFont(ofSize: 12, weight: .medium)
|
||||
|
||||
overviewLabel.addSubview(overviewText)
|
||||
self.tabView.view?.addSubview(overviewLabel)
|
||||
self.popup.view.view?.addSubview(overviewLabel)
|
||||
|
||||
let stackHeight: CGFloat = 22
|
||||
let vertical: NSStackView = NSStackView(frame: NSRect(x: 0, y: 147, width: TabWidth, height: stackHeight*3))
|
||||
@@ -124,40 +118,40 @@ extension CPU {
|
||||
system.orientation = .horizontal
|
||||
system.distribution = .equalCentering
|
||||
let systemLabel = LabelField(string: "System")
|
||||
let systemValue = ValueField(string: "0 %")
|
||||
self.systemValue = ValueField(string: "0 %")
|
||||
system.addView(systemLabel, in: .center)
|
||||
system.addView(systemValue, in: .center)
|
||||
system.addView(self.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 %")
|
||||
self.userValue = ValueField(string: "0 %")
|
||||
user.addView(userLabel, in: .center)
|
||||
user.addView(userValue, in: .center)
|
||||
user.addView(self.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 %")
|
||||
self.idleValue = ValueField(string: "0 %")
|
||||
idle.addView(idleLabel, in: .center)
|
||||
idle.addView(idleValue, in: .center)
|
||||
idle.addView(self.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)) %"
|
||||
}
|
||||
self.popup.view.view?.addSubview(vertical)
|
||||
}
|
||||
|
||||
func makeProcesses() {
|
||||
public func usageUpdater(value: CPUUsage) {
|
||||
self.systemValue.stringValue = "\(value.system.roundTo(decimalPlaces: 2)) %"
|
||||
self.userValue.stringValue = "\(value.user.roundTo(decimalPlaces: 2)) %"
|
||||
self.idleValue.stringValue = "\(value.idle.roundTo(decimalPlaces: 2)) %"
|
||||
}
|
||||
|
||||
private func makeProcesses() {
|
||||
let label: NSView = NSView(frame: NSRect(x: 0, y: 0, width: TabWidth, height: 25))
|
||||
|
||||
label.wantsLayer = true
|
||||
@@ -176,49 +170,49 @@ extension CPU {
|
||||
text.font = NSFont.systemFont(ofSize: 12, weight: .medium)
|
||||
|
||||
label.addSubview(text)
|
||||
self.tabView.view?.addSubview(label)
|
||||
self.popup.view.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] = []
|
||||
self.processViewList = []
|
||||
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)
|
||||
self.processViewList.append(process_1)
|
||||
self.processViewList.append(process_2)
|
||||
self.processViewList.append(process_3)
|
||||
self.processViewList.append(process_4)
|
||||
self.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)
|
||||
self.popup.view.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() {
|
||||
if i < 5 {
|
||||
let processView = processViewList[i]
|
||||
|
||||
(processView.subviews[0] as! NSTextField).stringValue = process.command
|
||||
(processView.subviews[1] as! NSTextField).stringValue = "\(process.usage.roundTo(decimalPlaces: 2)) %"
|
||||
}
|
||||
self.popup.view.view?.addSubview(label)
|
||||
}
|
||||
|
||||
public func processesUpdater(value: [TopProcess]) {
|
||||
for (i, process) in value.enumerated() {
|
||||
if i < 5 {
|
||||
let processView = self.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 {
|
||||
private 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
|
||||
90
Stats/Modules/CPU/CPUProcessReader.swift
Normal file
90
Stats/Modules/CPU/CPUProcessReader.swift
Normal file
@@ -0,0 +1,90 @@
|
||||
//
|
||||
// CPUProcessReader.swift
|
||||
// Stats
|
||||
//
|
||||
// Created by Serhiy Mytrovtsiy on 13/01/2020.
|
||||
// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
struct TopProcess {
|
||||
var pid: Int = 0
|
||||
var command: String = ""
|
||||
var usage: Double = 0
|
||||
}
|
||||
|
||||
class CPUProcessReader: Reader {
|
||||
public var name: String = "Processes"
|
||||
public var enabled: Bool = false
|
||||
public var available: Bool = true
|
||||
public var optional: Bool = true
|
||||
public var initialized: Bool = false
|
||||
public var callback: ([TopProcess]) -> Void = {_ in}
|
||||
|
||||
private var loadPrevious = host_cpu_load_info()
|
||||
|
||||
init(_ updater: @escaping ([TopProcess]) -> Void) {
|
||||
self.callback = updater
|
||||
if self.available {
|
||||
self.read()
|
||||
}
|
||||
}
|
||||
|
||||
public func toggleEnable(_ value: Bool) {
|
||||
self.enabled = value
|
||||
}
|
||||
|
||||
public func read() {
|
||||
if !self.enabled && self.initialized { return }
|
||||
self.initialized = true
|
||||
|
||||
let task = Process()
|
||||
task.launchPath = "/bin/ps"
|
||||
task.arguments = ["-Aceo pid,pcpu,comm", "-r"]
|
||||
|
||||
let outputPipe = Pipe()
|
||||
let errorPipe = Pipe()
|
||||
|
||||
task.standardOutput = outputPipe
|
||||
task.standardError = errorPipe
|
||||
|
||||
do {
|
||||
try task.run()
|
||||
} catch let error {
|
||||
print(error)
|
||||
}
|
||||
|
||||
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 index = 0
|
||||
var processes: [TopProcess] = []
|
||||
output.enumerateLines { (line, stop) -> () in
|
||||
if index != 0 {
|
||||
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
|
||||
let usage = Double(usageString) ?? 0
|
||||
|
||||
processes.append(TopProcess(pid: pid, command: command, usage: usage))
|
||||
}
|
||||
|
||||
if index == 5 { stop = true }
|
||||
index += 1
|
||||
}
|
||||
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.callback(processes)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,263 +0,0 @@
|
||||
//
|
||||
// reader.swift
|
||||
// Stats
|
||||
//
|
||||
// Created by Serhiy Mytrovtsiy on 01.06.2019.
|
||||
// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Cocoa
|
||||
|
||||
struct CPUUsage {
|
||||
var value: Double = 0
|
||||
var system: Double = 0
|
||||
var user: Double = 0
|
||||
var idle: Double = 0
|
||||
}
|
||||
|
||||
struct TopProcess {
|
||||
var pid: Int = 0
|
||||
var command: String = ""
|
||||
var usage: Double = 0
|
||||
}
|
||||
|
||||
class CPUReader: Reader {
|
||||
public var value: Observable<[Double]>!
|
||||
public var usage: Observable<CPUUsage> = Observable(CPUUsage())
|
||||
public var processes: Observable<[TopProcess]> = Observable([TopProcess]())
|
||||
public var available: Bool = true
|
||||
public var availableAdditional: Bool = true
|
||||
public var perCoreMode: Bool = false
|
||||
public var hyperthreading: Bool = false
|
||||
public var updateInterval: Int = 0
|
||||
|
||||
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 timer: Repeater?
|
||||
private var additionalTimer: Repeater?
|
||||
|
||||
init() {
|
||||
let mibKeys: [Int32] = [ CTL_HW, HW_NCPU ]
|
||||
self.value = Observable([])
|
||||
|
||||
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)
|
||||
if status != 0 {
|
||||
numCPUs = 1
|
||||
}
|
||||
}
|
||||
|
||||
if self.available {
|
||||
self.read()
|
||||
}
|
||||
|
||||
self.timer = Repeater.init(interval: .seconds(1), observer: { _ in
|
||||
self.read()
|
||||
})
|
||||
self.additionalTimer = Repeater.init(interval: .seconds(1), observer: { _ in
|
||||
self.readAdditional()
|
||||
})
|
||||
}
|
||||
|
||||
func start() {
|
||||
read()
|
||||
if self.timer != nil && self.timer!.state.isRunning == false {
|
||||
self.timer!.start()
|
||||
}
|
||||
}
|
||||
|
||||
func stop() {
|
||||
self.timer?.pause()
|
||||
}
|
||||
|
||||
func startAdditional() {
|
||||
readAdditional()
|
||||
if self.additionalTimer != nil && self.additionalTimer!.state.isRunning == false {
|
||||
self.additionalTimer!.start()
|
||||
}
|
||||
}
|
||||
|
||||
func stopAdditional() {
|
||||
self.additionalTimer?.pause()
|
||||
}
|
||||
|
||||
@objc func readAdditional() {
|
||||
let task = Process()
|
||||
task.launchPath = "/bin/ps"
|
||||
task.arguments = ["-Aceo pid,pcpu,comm", "-r"]
|
||||
|
||||
let outputPipe = Pipe()
|
||||
let errorPipe = Pipe()
|
||||
|
||||
task.standardOutput = outputPipe
|
||||
task.standardError = errorPipe
|
||||
|
||||
do {
|
||||
try task.run()
|
||||
} catch let error {
|
||||
print(error)
|
||||
}
|
||||
|
||||
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 index = 0
|
||||
var processes: [TopProcess] = []
|
||||
output.enumerateLines { (line, stop) -> () in
|
||||
if index != 0 {
|
||||
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
|
||||
let usage = Double(usageString) ?? 0
|
||||
|
||||
processes.append(TopProcess(pid: pid, command: command, usage: usage))
|
||||
}
|
||||
|
||||
if index == 5 { stop = true }
|
||||
index += 1
|
||||
}
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.processes << processes
|
||||
})
|
||||
}
|
||||
|
||||
@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()
|
||||
|
||||
var inUseOnAllCores: Int32 = 0
|
||||
var totalOnAllCores: Int32 = 0
|
||||
var usagePerCore: [Double] = []
|
||||
|
||||
var incrementNumber = 1
|
||||
if !self.hyperthreading && self.perCoreMode {
|
||||
incrementNumber = 2
|
||||
}
|
||||
|
||||
for i in stride(from: 0, to: Int32(numCPUs), by: incrementNumber) {
|
||||
var inUse: Int32
|
||||
var total: Int32
|
||||
if let prevCpuInfo = prevCpuInfo {
|
||||
inUse = cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_USER)]
|
||||
- prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_USER)]
|
||||
+ cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_SYSTEM)]
|
||||
- prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_SYSTEM)]
|
||||
+ cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_NICE)]
|
||||
- prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_NICE)]
|
||||
total = inUse + (cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_IDLE)]
|
||||
- prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_IDLE)])
|
||||
} else {
|
||||
inUse = cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_USER)]
|
||||
+ cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_SYSTEM)]
|
||||
+ cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_NICE)]
|
||||
total = inUse + cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_IDLE)]
|
||||
}
|
||||
|
||||
inUseOnAllCores = inUseOnAllCores + inUse
|
||||
totalOnAllCores = totalOnAllCores + total
|
||||
if total != 0 {
|
||||
usagePerCore.append(Double(inUse) / Double(total))
|
||||
}
|
||||
}
|
||||
|
||||
DispatchQueue.main.async(execute: {
|
||||
if self.perCoreMode {
|
||||
self.value << usagePerCore
|
||||
} 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()
|
||||
|
||||
if let prevCpuInfo = prevCpuInfo {
|
||||
let prevCpuInfoSize: size_t = MemoryLayout<integer_t>.stride * Int(numPrevCpuInfo)
|
||||
vm_deallocate(mach_task_self_, vm_address_t(bitPattern: prevCpuInfo), vm_size_t(prevCpuInfoSize))
|
||||
}
|
||||
|
||||
prevCpuInfo = cpuInfo
|
||||
numPrevCpuInfo = numCpuInfo
|
||||
|
||||
cpuInfo = nil
|
||||
numCpuInfo = 0
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
|
||||
func setInterval(value: Int) {
|
||||
if value == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
self.updateInterval = value
|
||||
self.timer?.reset(.seconds(Double(value)), restart: false)
|
||||
self.additionalTimer?.reset(.seconds(Double(value)), restart: false)
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
func matches(_ regex: String) -> Bool {
|
||||
return self.range(of: regex, options: .regularExpression, range: nil, locale: nil) != nil
|
||||
}
|
||||
}
|
||||
80
Stats/Modules/CPU/CPUUsageReader.swift
Normal file
80
Stats/Modules/CPU/CPUUsageReader.swift
Normal file
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// CPUUsageReader.swift
|
||||
// Stats
|
||||
//
|
||||
// Created by Serhiy Mytrovtsiy on 13/01/2020.
|
||||
// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
struct CPUUsage {
|
||||
var system: Double = 0
|
||||
var user: Double = 0
|
||||
var idle: Double = 0
|
||||
}
|
||||
|
||||
class CPUUsageReader: Reader {
|
||||
public var name: String = "Usage"
|
||||
public var enabled: Bool = false
|
||||
public var available: Bool = true
|
||||
public var optional: Bool = true
|
||||
public var initialized: Bool = false
|
||||
public var callback: (CPUUsage) -> Void = {_ in}
|
||||
|
||||
private var loadPrevious = host_cpu_load_info()
|
||||
|
||||
init(_ updater: @escaping (CPUUsage) -> Void) {
|
||||
self.callback = updater
|
||||
if self.available {
|
||||
self.read()
|
||||
}
|
||||
}
|
||||
|
||||
public func toggleEnable(_ value: Bool) {
|
||||
self.enabled = value
|
||||
}
|
||||
|
||||
public func read() {
|
||||
if !self.enabled && self.initialized { return }
|
||||
|
||||
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!
|
||||
self.initialized = true
|
||||
|
||||
if !sys.isNaN && !user.isNaN && !idle.isNaN {
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.callback(CPUUsage(system: sys, user: user, idle: idle))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private 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
|
||||
}
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
//
|
||||
// Disk.swift
|
||||
// Stats
|
||||
//
|
||||
// Created by Serhiy Mytrovtsiy on 01.06.2019.
|
||||
// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class Disk: Module {
|
||||
public let name: String = "Disk"
|
||||
public let shortName: String = "SSD"
|
||||
public var view: NSView = NSView()
|
||||
public var menu: NSMenuItem = NSMenuItem()
|
||||
public var widgetType: WidgetType
|
||||
|
||||
public var active: Bool = true
|
||||
public var available: Bool = true
|
||||
public var tabAvailable: Bool = false
|
||||
public var tabInitialized: Bool = false
|
||||
public var tabView: NSTabViewItem = NSTabViewItem()
|
||||
|
||||
public var reader: Reader = DiskReader()
|
||||
public var updateInterval: Int
|
||||
|
||||
private var submenu: NSMenu = NSMenu()
|
||||
private let defaults = UserDefaults.standard
|
||||
|
||||
init() {
|
||||
self.active = defaults.object(forKey: name) != nil ? defaults.bool(forKey: name) : true
|
||||
self.widgetType = defaults.object(forKey: "\(name)_widget") != nil ? defaults.float(forKey: "\(name)_widget") : Widgets.Mini
|
||||
self.updateInterval = defaults.object(forKey: "\(name)_interval") != nil ? defaults.integer(forKey: "\(name)_interval") : 5
|
||||
self.reader.setInterval(value: self.updateInterval)
|
||||
}
|
||||
|
||||
func initTab() {
|
||||
self.tabInitialized = true
|
||||
self.tabView.view?.frame = NSRect(x: 0, y: 0, width: TabWidth, height: TabHeight)
|
||||
}
|
||||
|
||||
func initMenu(active: Bool) {
|
||||
menu = NSMenuItem(title: name, action: #selector(toggle), keyEquivalent: "")
|
||||
submenu = NSMenu()
|
||||
|
||||
if defaults.object(forKey: name) != nil {
|
||||
menu.state = defaults.bool(forKey: name) ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
} else {
|
||||
menu.state = NSControl.StateValue.on
|
||||
}
|
||||
menu.target = self
|
||||
|
||||
let mini = NSMenuItem(title: "Mini", action: #selector(toggleWidget), keyEquivalent: "")
|
||||
mini.state = self.widgetType == Widgets.Mini ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
mini.target = self
|
||||
|
||||
let barChart = NSMenuItem(title: "Bar chart", action: #selector(toggleWidget), keyEquivalent: "")
|
||||
barChart.state = self.widgetType == Widgets.BarChart ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
barChart.target = self
|
||||
|
||||
submenu.addItem(mini)
|
||||
submenu.addItem(barChart)
|
||||
|
||||
submenu.addItem(NSMenuItem.separator())
|
||||
|
||||
if let view = self.view as? Widget {
|
||||
for widgetMenu in view.menus {
|
||||
submenu.addItem(widgetMenu)
|
||||
}
|
||||
}
|
||||
|
||||
submenu.addItem(NSMenuItem.separator())
|
||||
submenu.addItem(generateIntervalMenu())
|
||||
|
||||
if active {
|
||||
menu.submenu = submenu
|
||||
}
|
||||
}
|
||||
|
||||
@objc func toggle(_ sender: NSMenuItem) {
|
||||
let state = sender.state != NSControl.StateValue.on
|
||||
|
||||
sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on
|
||||
self.defaults.set(state, forKey: name)
|
||||
self.active = state
|
||||
menuBar!.reload(name: self.name)
|
||||
|
||||
if !state {
|
||||
self.stop()
|
||||
} else {
|
||||
self.start()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func toggleWidget(_ sender: NSMenuItem) {
|
||||
var widgetCode: Float = 0.0
|
||||
|
||||
switch sender.title {
|
||||
case "Mini":
|
||||
widgetCode = Widgets.Mini
|
||||
case "Bar chart":
|
||||
widgetCode = Widgets.BarChart
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if self.widgetType == widgetCode {
|
||||
return
|
||||
}
|
||||
|
||||
for item in self.submenu.items {
|
||||
if item.title == "Mini" || item.title == "Bar chart" {
|
||||
item.state = NSControl.StateValue.off
|
||||
}
|
||||
}
|
||||
|
||||
sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on
|
||||
self.defaults.set(widgetCode, forKey: "\(name)_widget")
|
||||
self.widgetType = widgetCode
|
||||
self.initWidget()
|
||||
self.initMenu(active: true)
|
||||
menuBar!.reload(name: self.name)
|
||||
}
|
||||
|
||||
func generateIntervalMenu() -> NSMenuItem {
|
||||
let updateInterval = NSMenuItem(title: "Update interval", action: nil, keyEquivalent: "")
|
||||
|
||||
let updateIntervals = NSMenu()
|
||||
let updateInterval_1 = NSMenuItem(title: "1s", action: #selector(changeInterval), keyEquivalent: "")
|
||||
updateInterval_1.state = self.updateInterval == 1 ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
updateInterval_1.target = self
|
||||
let updateInterval_2 = NSMenuItem(title: "3s", action: #selector(changeInterval), keyEquivalent: "")
|
||||
updateInterval_2.state = self.updateInterval == 3 ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
updateInterval_2.target = self
|
||||
let updateInterval_3 = NSMenuItem(title: "5s", action: #selector(changeInterval), keyEquivalent: "")
|
||||
updateInterval_3.state = self.updateInterval == 5 ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
updateInterval_3.target = self
|
||||
let updateInterval_4 = NSMenuItem(title: "10s", action: #selector(changeInterval), keyEquivalent: "")
|
||||
updateInterval_4.state = self.updateInterval == 10 ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
updateInterval_4.target = self
|
||||
let updateInterval_5 = NSMenuItem(title: "15s", action: #selector(changeInterval), keyEquivalent: "")
|
||||
updateInterval_5.state = self.updateInterval == 15 ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
updateInterval_5.target = self
|
||||
|
||||
updateIntervals.addItem(updateInterval_1)
|
||||
updateIntervals.addItem(updateInterval_2)
|
||||
updateIntervals.addItem(updateInterval_3)
|
||||
updateIntervals.addItem(updateInterval_4)
|
||||
updateIntervals.addItem(updateInterval_5)
|
||||
|
||||
updateInterval.submenu = updateIntervals
|
||||
|
||||
return updateInterval
|
||||
}
|
||||
|
||||
@objc func changeInterval(_ sender: NSMenuItem) {
|
||||
var interval: Int = self.updateInterval
|
||||
|
||||
switch sender.title {
|
||||
case "1s":
|
||||
interval = 1
|
||||
case "3s":
|
||||
interval = 3
|
||||
case "5s":
|
||||
interval = 5
|
||||
case "10s":
|
||||
interval = 10
|
||||
case "15s":
|
||||
interval = 15
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
if interval == self.updateInterval {
|
||||
return
|
||||
}
|
||||
|
||||
for item in self.submenu.items {
|
||||
if item.title == "Update interval" {
|
||||
for subitem in item.submenu!.items {
|
||||
subitem.state = NSControl.StateValue.off
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sender.state = NSControl.StateValue.on
|
||||
self.updateInterval = interval
|
||||
self.defaults.set(interval, forKey: "\(name)_interval")
|
||||
self.reader.setInterval(value: interval)
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
//
|
||||
// DiskReader.swift
|
||||
// Stats
|
||||
//
|
||||
// Created by Serhiy Mytrovtsiy on 01.06.2019.
|
||||
// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class DiskReader: Reader {
|
||||
public var value: Observable<[Double]>!
|
||||
public var available: Bool = true
|
||||
public var availableAdditional: Bool = false
|
||||
public var updateInterval: Int = 0
|
||||
|
||||
private var timer: Repeater?
|
||||
|
||||
init() {
|
||||
self.value = Observable([])
|
||||
if self.available {
|
||||
self.read()
|
||||
}
|
||||
|
||||
self.timer = Repeater.init(interval: .seconds(1), observer: { _ in
|
||||
self.read()
|
||||
})
|
||||
}
|
||||
|
||||
func start() {
|
||||
read()
|
||||
if self.timer != nil && self.timer!.state.isRunning == false {
|
||||
self.timer!.start()
|
||||
}
|
||||
}
|
||||
|
||||
func stop() {
|
||||
self.timer?.pause()
|
||||
}
|
||||
|
||||
@objc func read() {
|
||||
let total = totalDiskSpaceInBytes()
|
||||
let free = freeDiskSpaceInBytes()
|
||||
let usedSpace = total - free
|
||||
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.value << [(Double(usedSpace) / Double(total))]
|
||||
})
|
||||
}
|
||||
|
||||
func totalDiskSpaceInBytes() -> Int64 {
|
||||
do {
|
||||
let systemAttributes = try FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String)
|
||||
let space = (systemAttributes[FileAttributeKey.systemSize] as? NSNumber)?.int64Value
|
||||
return space!
|
||||
} catch {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func freeDiskSpaceInBytes() -> Int64 {
|
||||
do {
|
||||
let systemAttributes = try FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String)
|
||||
let freeSpace = (systemAttributes[FileAttributeKey.systemFreeSize] as? NSNumber)?.int64Value
|
||||
return freeSpace!
|
||||
} catch {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func setInterval(value: Int) {
|
||||
if value == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
self.updateInterval = value
|
||||
self.timer?.reset(.seconds(Double(value)), restart: false)
|
||||
}
|
||||
}
|
||||
@@ -1,171 +0,0 @@
|
||||
//
|
||||
// reader.swift
|
||||
// Stats
|
||||
//
|
||||
// Created by Serhiy Mytrovtsiy on 01.06.2019.
|
||||
// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct MemoryUsage {
|
||||
var total: Double = 0
|
||||
var used: Double = 0
|
||||
var free: Double = 0
|
||||
}
|
||||
|
||||
class MemoryReader: Reader {
|
||||
public var value: Observable<[Double]>!
|
||||
public var usage: Observable<MemoryUsage> = Observable(MemoryUsage())
|
||||
public var processes: Observable<[TopProcess]> = Observable([TopProcess]())
|
||||
public var available: Bool = true
|
||||
public var availableAdditional: Bool = true
|
||||
public var totalSize: Float
|
||||
public var updateInterval: Int = 0
|
||||
|
||||
private var timer: Repeater?
|
||||
private var additionalTimer: Repeater?
|
||||
|
||||
init() {
|
||||
self.value = Observable([])
|
||||
var stats = host_basic_info()
|
||||
var count = UInt32(MemoryLayout<host_basic_info_data_t>.size / MemoryLayout<integer_t>.size)
|
||||
|
||||
let kerr: kern_return_t = withUnsafeMutablePointer(to: &stats) {
|
||||
$0.withMemoryRebound(to: integer_t.self, capacity: Int(count)) {
|
||||
host_info(mach_host_self(), HOST_BASIC_INFO, $0, &count)
|
||||
}
|
||||
}
|
||||
|
||||
if kerr == KERN_SUCCESS {
|
||||
self.totalSize = Float(stats.max_mem)
|
||||
}
|
||||
else {
|
||||
self.totalSize = 0
|
||||
print("Error with host_info(): " + (String(cString: mach_error_string(kerr), encoding: String.Encoding.ascii) ?? "unknown error"))
|
||||
}
|
||||
|
||||
if self.available {
|
||||
self.read()
|
||||
}
|
||||
|
||||
self.timer = Repeater.init(interval: .seconds(1), observer: { _ in
|
||||
self.read()
|
||||
})
|
||||
self.additionalTimer = Repeater.init(interval: .seconds(1), observer: { _ in
|
||||
self.readAdditional()
|
||||
})
|
||||
}
|
||||
|
||||
func start() {
|
||||
read()
|
||||
if self.timer != nil && self.timer!.state.isRunning == false {
|
||||
self.timer!.start()
|
||||
}
|
||||
}
|
||||
|
||||
func stop() {
|
||||
self.timer?.pause()
|
||||
}
|
||||
|
||||
func startAdditional() {
|
||||
readAdditional()
|
||||
if self.additionalTimer != nil && self.additionalTimer!.state.isRunning == false {
|
||||
self.additionalTimer!.start()
|
||||
}
|
||||
}
|
||||
|
||||
func stopAdditional() {
|
||||
self.additionalTimer?.pause()
|
||||
}
|
||||
|
||||
@objc func readAdditional() {
|
||||
let task = Process()
|
||||
task.launchPath = "/usr/bin/top"
|
||||
task.arguments = ["-l", "1", "-o", "mem", "-n", "5", "-stats", "pid,command,mem"]
|
||||
|
||||
let outputPipe = Pipe()
|
||||
let errorPipe = Pipe()
|
||||
|
||||
task.standardOutput = outputPipe
|
||||
task.standardError = errorPipe
|
||||
|
||||
do {
|
||||
try task.run()
|
||||
} catch let error {
|
||||
print(error)
|
||||
}
|
||||
|
||||
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, stop) -> () 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))
|
||||
processes.append(process)
|
||||
}
|
||||
}
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.processes << processes
|
||||
})
|
||||
}
|
||||
|
||||
@objc func read() {
|
||||
var stats = vm_statistics64()
|
||||
var count = UInt32(MemoryLayout<vm_statistics64_data_t>.size / MemoryLayout<integer_t>.size)
|
||||
|
||||
let kerr: kern_return_t = withUnsafeMutablePointer(to: &stats) {
|
||||
$0.withMemoryRebound(to: integer_t.self, capacity: 1) {
|
||||
host_statistics64(mach_host_self(), HOST_VM_INFO64, $0, &count)
|
||||
}
|
||||
}
|
||||
|
||||
if kerr == KERN_SUCCESS {
|
||||
let active = Float(stats.active_count) * Float(PAGE_SIZE)
|
||||
// let inactive = Float(stats.inactive_count) * Float(PAGE_SIZE)
|
||||
let wired = Float(stats.wire_count) * Float(PAGE_SIZE)
|
||||
let compressed = Float(stats.compressor_page_count) * Float(PAGE_SIZE)
|
||||
|
||||
let used = active + wired + compressed
|
||||
let free = totalSize - used
|
||||
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.usage << MemoryUsage(total: Double(self.totalSize), used: Double(used), free: Double(free))
|
||||
self.value << [Double((self.totalSize - free) / self.totalSize)]
|
||||
})
|
||||
}
|
||||
else {
|
||||
print("Error with host_statistics64(): " + (String(cString: mach_error_string(kerr), encoding: String.Encoding.ascii) ?? "unknown error"))
|
||||
}
|
||||
}
|
||||
|
||||
func setInterval(value: Int) {
|
||||
if value == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
self.updateInterval = value
|
||||
self.timer?.reset(.seconds(Double(value)), restart: false)
|
||||
self.additionalTimer?.reset(.seconds(Double(value)), restart: false)
|
||||
}
|
||||
}
|
||||
@@ -1,232 +0,0 @@
|
||||
//
|
||||
// MemoryView.swift
|
||||
// Stats
|
||||
//
|
||||
// Created by Serhiy Mytrovtsiy on 04/09/2019.
|
||||
// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import Foundation
|
||||
import Charts
|
||||
|
||||
extension Memory {
|
||||
|
||||
func initTab() {
|
||||
self.tabView.view?.frame = NSRect(x: 0, y: 0, width: TabWidth, height: TabHeight)
|
||||
|
||||
makeChart()
|
||||
makeOverview()
|
||||
makeProcesses()
|
||||
|
||||
self.tabInitialized = true
|
||||
|
||||
(self.reader as! MemoryReader).usage.subscribe(observer: self) { (value, _) in
|
||||
self.updateChart(value: Units(bytes: Int64(value.used)).getReadableTuple().0)
|
||||
}
|
||||
}
|
||||
|
||||
func makeChart() {
|
||||
let reader = self.reader as! MemoryReader
|
||||
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 - 110, width: TabWidth, height: 102))
|
||||
self.chart.animate(xAxisDuration: 2.0, yAxisDuration: 2.0, easingOption: .easeInCubic)
|
||||
self.chart.backgroundColor = .white
|
||||
self.chart.noDataText = "No \(self.name) usage data"
|
||||
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 = Units(bytes: Int64(reader.totalSize)).gigabytes
|
||||
self.chart.leftAxis.labelCount = Units(bytes: Int64(reader.totalSize)).gigabytes > 16 ? 6 : 4
|
||||
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
|
||||
|
||||
let lineChartEntry = [ChartDataEntry]()
|
||||
let chartDataSet = LineChartDataSet(entries: lineChartEntry, label: "\(self.name) 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 - 4)
|
||||
overviewText.isEditable = false
|
||||
overviewText.isSelectable = false
|
||||
overviewText.isBezeled = false
|
||||
overviewText.wantsLayer = true
|
||||
overviewText.textColor = .darkGray
|
||||
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 total: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*2, width: TabWidth - 20, height: stackHeight))
|
||||
total.orientation = .horizontal
|
||||
total.distribution = .equalCentering
|
||||
let totalLabel = LabelField(string: "Total")
|
||||
let totalValue = ValueField(string: "0 GB")
|
||||
total.addView(totalLabel, in: .center)
|
||||
total.addView(totalValue, in: .center)
|
||||
|
||||
let used: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*1, width: TabWidth - 20, height: stackHeight))
|
||||
used.orientation = .horizontal
|
||||
used.distribution = .equalCentering
|
||||
let usedLabel = LabelField(string: "Used")
|
||||
let usedValue = ValueField(string: "0 GB")
|
||||
used.addView(usedLabel, in: .center)
|
||||
used.addView(usedValue, in: .center)
|
||||
|
||||
let free: NSStackView = NSStackView(frame: NSRect(x: 10, y: 0, width: TabWidth - 20, height: stackHeight))
|
||||
free.orientation = .horizontal
|
||||
free.distribution = .equalCentering
|
||||
let freeLabel = LabelField(string: "Free")
|
||||
let freeValue = ValueField(string: "0 GB")
|
||||
free.addView(freeLabel, in: .center)
|
||||
free.addView(freeValue, in: .center)
|
||||
|
||||
vertical.addSubview(total)
|
||||
vertical.addSubview(used)
|
||||
vertical.addSubview(free)
|
||||
|
||||
self.tabView.view?.addSubview(vertical)
|
||||
|
||||
(self.reader as! MemoryReader).usage.subscribe(observer: self) { (value, _) in
|
||||
totalValue.stringValue = Units(bytes: Int64(value.total)).getReadableMemory()
|
||||
usedValue.stringValue = Units(bytes: Int64(value.used)).getReadableMemory()
|
||||
freeValue.stringValue = Units(bytes: Int64(value.free)).getReadableMemory()
|
||||
}
|
||||
}
|
||||
|
||||
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 - 4)
|
||||
text.isEditable = false
|
||||
text.isSelectable = false
|
||||
text.isBezeled = false
|
||||
text.wantsLayer = true
|
||||
text.textColor = .darkGray
|
||||
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! MemoryReader).processes.subscribe(observer: self) { (processes, _) in
|
||||
for (i, process) in processes.enumerated() {
|
||||
if i < 5 {
|
||||
let processView = processViewList[i]
|
||||
|
||||
(processView.subviews[0] as! NSTextField).stringValue = process.command
|
||||
(processView.subviews[1] as! NSTextField).stringValue = Units(bytes: Int64(process.usage)).getReadableMemory()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -7,39 +7,65 @@
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import Charts
|
||||
|
||||
protocol Module: class {
|
||||
var name: String { get }
|
||||
var shortName: String { get }
|
||||
var name: String { get } // module name
|
||||
var updateInterval: Double { get } // module update interval
|
||||
|
||||
var view: NSView { get set }
|
||||
var menu: NSMenuItem { get }
|
||||
var widgetType: WidgetType { get }
|
||||
var enabled: Bool { get } // determine if module is enabled or disabled
|
||||
var available: Bool { get } // determine if module is available on this PC
|
||||
|
||||
var active: Bool { get }
|
||||
var available: Bool { get }
|
||||
var widget: ModuleWidget { get set } // view for widget
|
||||
var menu: NSMenuItem { get } // view for menu
|
||||
var popup: ModulePopup { get } // popup
|
||||
|
||||
var tabView: NSTabViewItem { get }
|
||||
var tabAvailable: Bool { get }
|
||||
var tabInitialized: Bool { get }
|
||||
var readers: [Reader] { get } // list of readers available for module
|
||||
var task: Repeater? { get set } // reader cron task
|
||||
|
||||
var reader: Reader { get }
|
||||
var updateInterval: Int { get }
|
||||
func start() // start module internal processes
|
||||
func stop() // stop module internal processes
|
||||
func restart() // restart module internal processes
|
||||
|
||||
func start()
|
||||
func stop()
|
||||
|
||||
func initMenu(active: Bool)
|
||||
func initTab()
|
||||
func initWidget()
|
||||
}
|
||||
|
||||
extension Module {
|
||||
protocol Reader {
|
||||
var name: String { get } // reader name
|
||||
var enabled: Bool { get set } // determine if reader is enabled or disabled
|
||||
var available: Bool { get } // determine if reader is available on this PC
|
||||
var optional: Bool { get } // say if reader are optional (additional information)
|
||||
var initialized: Bool { get } // to check if first read already done
|
||||
|
||||
func read() // make one read
|
||||
|
||||
func toggleEnable(_ value: Bool) -> Void // enable/disable optional reader
|
||||
}
|
||||
|
||||
struct ModulePopup {
|
||||
var available: Bool = true // say if module have popup view
|
||||
var view: NSTabViewItem = NSTabViewItem() // module popup view
|
||||
var chart: LineChartView = LineChartView() // chart view for popup
|
||||
|
||||
init(_ a: Bool = true) {
|
||||
available = a
|
||||
}
|
||||
}
|
||||
|
||||
struct ModuleWidget {
|
||||
var type: WidgetType = Widgets.Mini // determine a widget typ
|
||||
var view: NSView = NSView() // widget view
|
||||
|
||||
init(_ t: WidgetType = Widgets.Mini) {
|
||||
type = t
|
||||
}
|
||||
}
|
||||
|
||||
extension Module {
|
||||
func initWidget() {
|
||||
var widget: Widget = Mini()
|
||||
|
||||
switch self.widgetType {
|
||||
switch self.widget.type {
|
||||
case Widgets.Mini:
|
||||
widget = Mini()
|
||||
case Widgets.Chart:
|
||||
@@ -69,29 +95,9 @@ extension Module {
|
||||
}
|
||||
|
||||
widget.name = self.name
|
||||
widget.shortName = self.shortName
|
||||
widget.shortName = String(self.name.prefix(3)).uppercased()
|
||||
widget.Init()
|
||||
|
||||
self.view = widget as! NSView
|
||||
}
|
||||
|
||||
func start() {
|
||||
self.reader.start()
|
||||
|
||||
if !self.reader.value.value.isEmpty && self.view is Widget {
|
||||
(self.view as! Widget).setValue(data: self.reader.value.value)
|
||||
}
|
||||
|
||||
self.reader.value.subscribe(observer: self) { (value, _) in
|
||||
if !value.isEmpty && self.view is Widget {
|
||||
(self.view as! Widget).setValue(data: value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func stop() {
|
||||
self.reader.stop()
|
||||
self.reader.stopAdditional()
|
||||
self.reader.value.unsubscribe(observer: self)
|
||||
self.widget.view = widget as! NSView
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
//
|
||||
// Network.swift
|
||||
// Stats
|
||||
//
|
||||
// Created by Serhiy Mytrovtsiy on 24.06.2019.
|
||||
// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class Network: Module {
|
||||
public var name: String = "Network"
|
||||
public var shortName: String = "NET"
|
||||
public var view: NSView = NSView()
|
||||
public var menu: NSMenuItem = NSMenuItem()
|
||||
public var active: Bool = true
|
||||
public var available: Bool = true
|
||||
public var reader: Reader = NetworkReader()
|
||||
public var widgetType: WidgetType = 2.0
|
||||
public var tabAvailable: Bool = false
|
||||
public var tabInitialized: Bool = false
|
||||
public var tabView: NSTabViewItem = NSTabViewItem()
|
||||
public var updateInterval: Int
|
||||
|
||||
private let defaults = UserDefaults.standard
|
||||
private var submenu: NSMenu = NSMenu()
|
||||
|
||||
init() {
|
||||
self.available = self.reader.available
|
||||
self.active = defaults.object(forKey: name) != nil ? defaults.bool(forKey: name) : true
|
||||
self.widgetType = defaults.object(forKey: "\(name)_widget") != nil ? defaults.float(forKey: "\(name)_widget") : Widgets.NetworkDots
|
||||
self.updateInterval = defaults.object(forKey: "\(name)_interval") != nil ? defaults.integer(forKey: "\(name)_interval") : 1
|
||||
self.reader.setInterval(value: self.updateInterval)
|
||||
}
|
||||
|
||||
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)! - 50) / 2
|
||||
text.frame.origin.y = ((self.tabView.view?.frame.size.height)! - 22) / 2
|
||||
|
||||
self.tabView.view?.addSubview(text)
|
||||
|
||||
self.tabInitialized = true
|
||||
}
|
||||
|
||||
func start() {
|
||||
self.reader.start()
|
||||
|
||||
self.reader.value.subscribe(observer: self) { (value, _) in
|
||||
if !value.isEmpty {
|
||||
(self.view as! Widget).setValue(data: value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func initMenu(active: Bool) {
|
||||
menu = NSMenuItem(title: name, action: #selector(toggle), keyEquivalent: "")
|
||||
submenu = NSMenu()
|
||||
|
||||
if defaults.object(forKey: name) != nil {
|
||||
menu.state = defaults.bool(forKey: name) ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
} else {
|
||||
menu.state = NSControl.StateValue.on
|
||||
}
|
||||
menu.target = self
|
||||
|
||||
let dots = NSMenuItem(title: "Dots", action: #selector(toggleWidget), keyEquivalent: "")
|
||||
dots.state = self.widgetType == Widgets.NetworkDots ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
dots.target = self
|
||||
|
||||
let arrows = NSMenuItem(title: "Arrows", action: #selector(toggleWidget), keyEquivalent: "")
|
||||
arrows.state = self.widgetType == Widgets.NetworkArrows ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
arrows.target = self
|
||||
|
||||
let text = NSMenuItem(title: "Text", action: #selector(toggleWidget), keyEquivalent: "")
|
||||
text.state = self.widgetType == Widgets.NetworkText ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
text.target = self
|
||||
|
||||
let dotsWithText = NSMenuItem(title: "Dots with text", action: #selector(toggleWidget), keyEquivalent: "")
|
||||
dotsWithText.state = self.widgetType == Widgets.NetworkDotsWithText ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
dotsWithText.target = self
|
||||
|
||||
let arrowsWithText = NSMenuItem(title: "Arrows with text", action: #selector(toggleWidget), keyEquivalent: "")
|
||||
arrowsWithText.state = self.widgetType == Widgets.NetworkArrowsWithText ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
arrowsWithText.target = self
|
||||
|
||||
let chart = NSMenuItem(title: "Chart", action: #selector(toggleWidget), keyEquivalent: "")
|
||||
chart.state = self.widgetType == Widgets.NetworkChart ? NSControl.StateValue.on : NSControl.StateValue.off
|
||||
chart.target = self
|
||||
|
||||
submenu.addItem(dots)
|
||||
submenu.addItem(arrows)
|
||||
submenu.addItem(text)
|
||||
submenu.addItem(dotsWithText)
|
||||
submenu.addItem(arrowsWithText)
|
||||
|
||||
submenu.addItem(NSMenuItem.separator())
|
||||
|
||||
if let view = self.view as? Widget {
|
||||
for widgetMenu in view.menus {
|
||||
submenu.addItem(widgetMenu)
|
||||
}
|
||||
}
|
||||
|
||||
if active {
|
||||
menu.submenu = submenu
|
||||
}
|
||||
}
|
||||
|
||||
@objc func toggle(_ sender: NSMenuItem) {
|
||||
let state = sender.state != NSControl.StateValue.on
|
||||
|
||||
sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on
|
||||
self.defaults.set(state, forKey: name)
|
||||
self.active = state
|
||||
menuBar!.reload(name: self.name)
|
||||
|
||||
if !state {
|
||||
menu.submenu = nil
|
||||
self.stop()
|
||||
} else {
|
||||
menu.submenu = submenu
|
||||
self.start()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func toggleWidget(_ sender: NSMenuItem) {
|
||||
var widgetCode: Float = 0.0
|
||||
|
||||
switch sender.title {
|
||||
case "Dots":
|
||||
widgetCode = Widgets.NetworkDots
|
||||
case "Arrows":
|
||||
widgetCode = Widgets.NetworkArrows
|
||||
case "Text":
|
||||
widgetCode = Widgets.NetworkText
|
||||
case "Dots with text":
|
||||
widgetCode = Widgets.NetworkDotsWithText
|
||||
case "Arrows with text":
|
||||
widgetCode = Widgets.NetworkArrowsWithText
|
||||
case "Chart":
|
||||
widgetCode = Widgets.NetworkChart
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if self.widgetType == widgetCode {
|
||||
return
|
||||
}
|
||||
|
||||
for item in self.submenu.items {
|
||||
if item.title == "Dots" || item.title == "Arrows" || item.title == "Text" || item.title == "Dots with text" || item.title == "Arrows with text" || item.title == "Chart" {
|
||||
item.state = NSControl.StateValue.off
|
||||
}
|
||||
}
|
||||
|
||||
sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on
|
||||
self.defaults.set(widgetCode, forKey: "\(name)_widget")
|
||||
self.widgetType = widgetCode
|
||||
initWidget()
|
||||
menuBar!.reload(name: self.name)
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
//
|
||||
// NetworkReader.swift
|
||||
// Stats
|
||||
//
|
||||
// Created by Serhiy Mytrovtsiy on 24.06.2019.
|
||||
// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class NetworkReader: Reader {
|
||||
public var value: Observable<[Double]>!
|
||||
public var available: Bool = true
|
||||
public var availableAdditional: Bool = false
|
||||
public var updateInterval: Int = 0
|
||||
|
||||
private var timer: Repeater?
|
||||
private var uploadValue: Int64 = 0
|
||||
private var downloadValue: Int64 = 0
|
||||
|
||||
|
||||
init() {
|
||||
self.value = Observable([])
|
||||
|
||||
if self.available {
|
||||
self.read()
|
||||
}
|
||||
|
||||
self.timer = Repeater.init(interval: .seconds(1), observer: { _ in
|
||||
self.read()
|
||||
})
|
||||
}
|
||||
|
||||
func start() {
|
||||
read()
|
||||
if self.timer != nil && self.timer!.state.isRunning == false {
|
||||
self.timer!.start()
|
||||
}
|
||||
}
|
||||
|
||||
func stop() {
|
||||
self.timer?.pause()
|
||||
}
|
||||
|
||||
func read() {
|
||||
var interfaceAddresses: UnsafeMutablePointer<ifaddrs>? = nil
|
||||
|
||||
var upload: Int64 = 0
|
||||
var download: Int64 = 0
|
||||
guard getifaddrs(&interfaceAddresses) == 0 else { return }
|
||||
|
||||
var pointer = interfaceAddresses
|
||||
while pointer != nil {
|
||||
guard let info = getDataUsageInfo(from: pointer!) else {
|
||||
pointer = pointer!.pointee.ifa_next
|
||||
continue
|
||||
}
|
||||
pointer = pointer!.pointee.ifa_next
|
||||
upload = info[0]
|
||||
download = info[1]
|
||||
}
|
||||
freeifaddrs(interfaceAddresses)
|
||||
|
||||
let lastUpload = self.uploadValue
|
||||
let lastDownload = self.downloadValue
|
||||
if lastUpload != 0 && lastDownload != 0 {
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.value << [Double(download - lastDownload), Double(upload - lastUpload)]
|
||||
})
|
||||
}
|
||||
|
||||
self.uploadValue = upload
|
||||
self.downloadValue = download
|
||||
}
|
||||
|
||||
func getDataUsageInfo(from infoPointer: UnsafeMutablePointer<ifaddrs>) -> [Int64]? {
|
||||
let pointer = infoPointer
|
||||
|
||||
let name: String! = String(cString: infoPointer.pointee.ifa_name)
|
||||
let addr = pointer.pointee.ifa_addr.pointee
|
||||
guard addr.sa_family == UInt8(AF_LINK) else { return nil }
|
||||
var networkData: UnsafeMutablePointer<if_data>? = nil
|
||||
|
||||
if name.hasPrefix("en") {
|
||||
networkData = unsafeBitCast(pointer.pointee.ifa_data, to: UnsafeMutablePointer<if_data>.self)
|
||||
return [Int64(networkData?.pointee.ifi_obytes ?? 0), Int64(networkData?.pointee.ifi_ibytes ?? 0)] // upload, download
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setInterval(value: Int) {}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
//
|
||||
// Reader.swift
|
||||
// Stats
|
||||
//
|
||||
// Created by Serhiy Mytrovtsiy on 08.07.2019.
|
||||
// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol Reader {
|
||||
var value: Observable<[Double]>! { get }
|
||||
|
||||
var available: Bool { get }
|
||||
var availableAdditional: Bool { get }
|
||||
|
||||
func start()
|
||||
func stop()
|
||||
|
||||
func startAdditional()
|
||||
func stopAdditional()
|
||||
|
||||
func setInterval(value: Int)
|
||||
}
|
||||
|
||||
extension Reader {
|
||||
func startAdditional() {}
|
||||
func stopAdditional() {}
|
||||
}
|
||||
@@ -42,8 +42,10 @@ class MainViewController: NSViewController {
|
||||
override func viewWillAppear() {
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
for module in menuBar!.modules {
|
||||
if module.tabAvailable && module.available && module.active && module.reader.availableAdditional {
|
||||
module.reader.startAdditional()
|
||||
if module.popup.available && module.available && module.enabled {
|
||||
module.readers.filter{ $0.optional }.forEach { reader in
|
||||
reader.toggleEnable(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,8 +54,12 @@ class MainViewController: NSViewController {
|
||||
override func viewWillDisappear() {
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
for module in menuBar!.modules {
|
||||
if module.tabAvailable && module.available && module.active && module.reader.availableAdditional {
|
||||
module.reader.stopAdditional()
|
||||
if module.popup.available && module.available && module.enabled {
|
||||
if module.popup.available && module.available && module.enabled {
|
||||
module.readers.filter{ $0.optional }.forEach { reader in
|
||||
reader.toggleEnable(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,16 +79,16 @@ class MainViewController: NSViewController {
|
||||
private func makeHeader() {
|
||||
var list: [String] = []
|
||||
for module in menuBar!.modules {
|
||||
if module.tabAvailable && module.available && module.active {
|
||||
if module.popup.available && module.available && module.enabled {
|
||||
list.append(module.name)
|
||||
|
||||
let tab = module.tabView
|
||||
let tab = module.popup.view
|
||||
tab.label = module.name
|
||||
tab.identifier = module.name
|
||||
tab.view?.wantsLayer = true
|
||||
tab.view?.layer?.backgroundColor = NSColor.white.cgColor
|
||||
|
||||
tabView.addTabViewItem(module.tabView)
|
||||
tabView.addTabViewItem(tab)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
import Cocoa
|
||||
|
||||
class BatteryWidget: NSView, Widget {
|
||||
public var activeModule: Observable<Bool> = Observable(false)
|
||||
public var name: String = "Battery"
|
||||
public var shortName: String = "BAT"
|
||||
public var menus: [NSMenuItem] = []
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
import Cocoa
|
||||
|
||||
class BarChart: NSView, Widget {
|
||||
var activeModule: Observable<Bool> = Observable(false)
|
||||
var size: CGFloat = widgetSize.width + 10
|
||||
let defaults = UserDefaults.standard
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
import Cocoa
|
||||
|
||||
class Chart: NSView, Widget {
|
||||
var activeModule: Observable<Bool> = Observable(false)
|
||||
var size: CGFloat = widgetSize.width + 7
|
||||
var labelPadding: CGFloat = 10.0
|
||||
var label: Bool = false
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
import Cocoa
|
||||
|
||||
class Mini: NSView, Widget {
|
||||
var activeModule: Observable<Bool> = Observable(false)
|
||||
var menus: [NSMenuItem] = []
|
||||
let defaults = UserDefaults.standard
|
||||
|
||||
@@ -38,7 +37,6 @@ class Mini: NSView, Widget {
|
||||
let xOffset: CGFloat = 1.0
|
||||
|
||||
let labelView = NSTextField(frame: NSMakeRect(xOffset, 13, self.frame.size.width, 7))
|
||||
labelView.textColor = NSColor.red
|
||||
labelView.isEditable = false
|
||||
labelView.isSelectable = false
|
||||
labelView.isBezeled = false
|
||||
@@ -52,7 +50,6 @@ class Mini: NSView, Widget {
|
||||
labelView.addSubview(NSView())
|
||||
|
||||
let valueView = NSTextField(frame: NSMakeRect(xOffset, 3, self.frame.size.width, 10))
|
||||
valueView.textColor = NSColor.red
|
||||
valueView.isEditable = false
|
||||
valueView.isSelectable = false
|
||||
valueView.isBezeled = false
|
||||
|
||||
@@ -10,12 +10,10 @@ import Cocoa
|
||||
|
||||
class NetworkArrowsView: NSView, Widget {
|
||||
var menus: [NSMenuItem] = []
|
||||
var activeModule: Observable<Bool> = Observable(false)
|
||||
var size: CGFloat = 8
|
||||
var name: String = ""
|
||||
var shortName: String = ""
|
||||
|
||||
var color: Observable<Bool> = Observable(false)
|
||||
var download: Int64 {
|
||||
didSet {
|
||||
self.redraw()
|
||||
|
||||
@@ -10,12 +10,10 @@ import Cocoa
|
||||
|
||||
class NetworkArrowsTextView: NSView, Widget {
|
||||
var menus: [NSMenuItem] = []
|
||||
var activeModule: Observable<Bool> = Observable(false)
|
||||
var size: CGFloat = widgetSize.width + 24
|
||||
var name: String = ""
|
||||
var shortName: String = ""
|
||||
|
||||
var color: Observable<Bool> = Observable(false)
|
||||
var download: Int64 {
|
||||
didSet {
|
||||
self.redraw()
|
||||
|
||||
@@ -9,13 +9,11 @@
|
||||
import Cocoa
|
||||
|
||||
class NetworkDotsView: NSView, Widget {
|
||||
var activeModule: Observable<Bool> = Observable(false)
|
||||
var size: CGFloat = 12
|
||||
var name: String = ""
|
||||
var shortName: String = ""
|
||||
var menus: [NSMenuItem] = []
|
||||
|
||||
var color: Observable<Bool> = Observable(false)
|
||||
var download: Int64 {
|
||||
didSet {
|
||||
self.redraw()
|
||||
|
||||
@@ -10,12 +10,10 @@ import Cocoa
|
||||
|
||||
class NetworkDotsTextView: NSView, Widget {
|
||||
var menus: [NSMenuItem] = []
|
||||
var activeModule: Observable<Bool> = Observable(false)
|
||||
var size: CGFloat = widgetSize.width + 26
|
||||
var name: String = ""
|
||||
var shortName: String = ""
|
||||
|
||||
var color: Observable<Bool> = Observable(false)
|
||||
var download: Int64 {
|
||||
didSet {
|
||||
self.redraw()
|
||||
|
||||
@@ -10,12 +10,10 @@ import Cocoa
|
||||
|
||||
class NetworkTextView: NSView, Widget {
|
||||
var menus: [NSMenuItem] = []
|
||||
var activeModule: Observable<Bool> = Observable(false)
|
||||
var size: CGFloat = widgetSize.width + 20
|
||||
var name: String = ""
|
||||
var shortName: String = ""
|
||||
|
||||
var color: Observable<Bool> = Observable(false)
|
||||
var downloadValue: NSTextField = NSTextField()
|
||||
var uploadValue: NSTextField = NSTextField()
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import Cocoa
|
||||
protocol Widget {
|
||||
var name: String { get set }
|
||||
var shortName: String { get set }
|
||||
var activeModule: Observable<Bool> { get set }
|
||||
var menus: [NSMenuItem] { get }
|
||||
|
||||
func setValue(data: [Double])
|
||||
|
||||
@@ -246,7 +246,7 @@ extension String {
|
||||
}
|
||||
}
|
||||
|
||||
extension URL {
|
||||
extension URL {
|
||||
func checkFileExist() -> Bool {
|
||||
return FileManager.default.fileExists(atPath: self.path)
|
||||
}
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
//
|
||||
// Observable.swift
|
||||
// Stats
|
||||
//
|
||||
// Created by Serhiy Mytrovtsiy on 29/05/2019.
|
||||
// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol ObservableProtocol {
|
||||
associatedtype T
|
||||
var value: T { get set }
|
||||
func subscribe(observer: AnyObject, block: @escaping (_ newValue: T, _ oldValue: T) -> ())
|
||||
func unsubscribe(observer: AnyObject)
|
||||
}
|
||||
|
||||
public final class Observable<T>: ObservableProtocol {
|
||||
typealias ObserverBlock = (_ newValue: T, _ oldValue: T) -> ()
|
||||
typealias ObserversEntry = (observer: AnyObject, block: ObserverBlock)
|
||||
private var observers: Array<ObserversEntry>
|
||||
private let defaults = UserDefaults.standard
|
||||
private var userDefaultsKey: String = ""
|
||||
|
||||
init(_ value: T) {
|
||||
self.value = value
|
||||
observers = []
|
||||
}
|
||||
|
||||
var value: T {
|
||||
didSet {
|
||||
observers.forEach { (entry: ObserversEntry) in
|
||||
let (_, block) = entry
|
||||
block(value, oldValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func subscribe(observer: AnyObject, block: @escaping ObserverBlock) {
|
||||
let entry: ObserversEntry = (observer: observer, block: block)
|
||||
observers.append(entry)
|
||||
}
|
||||
|
||||
func unsubscribe(observer: AnyObject) {
|
||||
let filtered = observers.filter { entry in
|
||||
let (owner, _) = entry
|
||||
return owner !== observer
|
||||
}
|
||||
|
||||
observers = filtered
|
||||
}
|
||||
}
|
||||
|
||||
func <<<T>(observable: Observable<T>, value: T) {
|
||||
observable.value = value
|
||||
}
|
||||
Reference in New Issue
Block a user