finally remove observable from the project;

started rewriting modules from zero;
CPU mvp;
This commit is contained in:
Serhiy Mytrovtsiy
2020-01-13 23:11:54 +01:00
parent 185419aeaf
commit f00ae071bb
33 changed files with 612 additions and 2498 deletions

View File

@@ -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;
};

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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"
}
}
}

View File

@@ -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)
}
}

View 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
}
}

View File

@@ -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)
}
}

View File

@@ -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

View 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)
})
}
}

View File

@@ -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
}
}

View 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
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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)
}
}

View File

@@ -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) {}
}

View File

@@ -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() {}
}

View File

@@ -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)
}
}

View File

@@ -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] = []

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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])

View File

@@ -246,7 +246,7 @@ extension String {
}
}
extension URL {
extension URL {
func checkFileExist() -> Bool {
return FileManager.default.fileExists(atPath: self.path)
}

View File

@@ -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
}