diff --git a/Stats.xcodeproj/project.pbxproj b/Stats.xcodeproj/project.pbxproj index 4f062992..e36388ef 100644 --- a/Stats.xcodeproj/project.pbxproj +++ b/Stats.xcodeproj/project.pbxproj @@ -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 = ""; }; - 9A09C89F22B3A7E20018426F /* BatteryReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryReader.swift; sourceTree = ""; }; 9A09C8A122B3D94D0018426F /* BatteryWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryWidget.swift; sourceTree = ""; }; 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 = ""; }; @@ -89,20 +78,20 @@ 9A141102229E721200D29793 /* Stats.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Stats.entitlements; sourceTree = ""; }; 9A2D15CC23C77A7700C4C417 /* Repeat.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Repeat.framework; path = Carthage/Build/Mac/Repeat.framework; sourceTree = ""; }; 9A2D15CF23C77BA300C4C417 /* Repeater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Repeater.swift; sourceTree = ""; }; + 9A2D15D123CCEC7600C4C417 /* Module.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Module.swift; sourceTree = ""; }; + 9A2D15D423CCEFF700C4C417 /* CPU.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPU.swift; sourceTree = ""; }; + 9A2D15D623CCFE1B00C4C417 /* CPULoadReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPULoadReader.swift; sourceTree = ""; }; + 9A2D15D823CD036400C4C417 /* CPUMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPUMenu.swift; sourceTree = ""; }; + 9A2D15DA23CD0B2100C4C417 /* CPUPopup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPUPopup.swift; sourceTree = ""; }; + 9A2D15E023CD133300C4C417 /* CPUUsageReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPUUsageReader.swift; sourceTree = ""; }; + 9A2D15E223CD1E4B00C4C417 /* CPUProcessReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPUProcessReader.swift; sourceTree = ""; }; 9A426DB722C2B5EE00C064C4 /* MacAppUpdater.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MacAppUpdater.swift; sourceTree = ""; }; 9A426DBD22C2BE0000C064C4 /* Updates.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Updates.storyboard; sourceTree = ""; }; - 9A493CDE23202B620064570C /* MemoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryView.swift; sourceTree = ""; }; 9A54EF66232AB81800F7DC20 /* BatteryPercentageWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryPercentageWidget.swift; sourceTree = ""; }; 9A54EF68232AB82700F7DC20 /* BatteryTimeWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryTimeWidget.swift; sourceTree = ""; }; 9A57A18422A1D26D0033E318 /* MenuBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBar.swift; sourceTree = ""; }; - 9A57A19C22A1E3270033E318 /* CPU.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPU.swift; sourceTree = ""; }; - 9A58D1AF22C150C800405315 /* Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = ""; }; - 9A58D1B122C150D700405315 /* NetworkReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkReader.swift; sourceTree = ""; }; - 9A59AE53231ED1AC007989D6 /* CPUView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPUView.swift; sourceTree = ""; }; 9A59AE55231EE02F007989D6 /* ChartMarker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartMarker.swift; sourceTree = ""; }; - 9A5B1CBE229E78F0008B9D3C /* Observable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Observable.swift; sourceTree = ""; }; 9A5B1CC4229E7B40008B9D3C /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; - 9A606B472321025C00642F51 /* BatteryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryView.swift; sourceTree = ""; }; 9A606B492321577400642F51 /* UpdatesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdatesViewController.swift; sourceTree = ""; }; 9A606B4B232157BA00642F51 /* AboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; 9A6698DF2326AAD6001D00E1 /* LaunchAtLogin.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LaunchAtLogin.framework; path = Carthage/Build/Mac/LaunchAtLogin.framework; sourceTree = ""; }; @@ -110,13 +99,6 @@ 9A6CFC0022A1C9F5001E782D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 9A74D59622B44498004FE1FA /* Mini.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mini.swift; sourceTree = ""; }; 9A79B36922D3BEE600BF1C3A /* Widget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Widget.swift; sourceTree = ""; }; - 9A79B36B22D3BEF000BF1C3A /* Module.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Module.swift; sourceTree = ""; }; - 9A79B36D22D3BEF900BF1C3A /* Reader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reader.swift; sourceTree = ""; }; - 9A7B8F5D22A2A57600DEB352 /* CPUReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CPUReader.swift; sourceTree = ""; }; - 9A7B8F6822A2C3A100DEB352 /* Memory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Memory.swift; sourceTree = ""; }; - 9A7B8F6A22A2C3A700DEB352 /* Disk.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Disk.swift; sourceTree = ""; }; - 9A7B8F6C22A2C3D600DEB352 /* MemoryReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryReader.swift; sourceTree = ""; }; - 9A7B8F6E22A2C57000DEB352 /* DiskReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiskReader.swift; sourceTree = ""; }; 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 = ""; }; @@ -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 = ""; - }; 9A1410EC229E721100D29793 = { isa = PBXGroup; children = ( @@ -183,6 +155,19 @@ path = Stats; sourceTree = ""; }; + 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 = ""; + }; 9A54EF65232AB48100F7DC20 /* Battery */ = { isa = PBXGroup; children = ( @@ -193,15 +178,6 @@ path = Battery; sourceTree = ""; }; - 9A58D1AE22C150B800405315 /* Network */ = { - isa = PBXGroup; - children = ( - 9A58D1AF22C150C800405315 /* Network.swift */, - 9A58D1B122C150D700405315 /* NetworkReader.swift */, - ); - path = Network; - sourceTree = ""; - }; 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 = ""; @@ -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 = ""; }; - 9A7B8F5C22A2926500DEB352 /* CPU */ = { - isa = PBXGroup; - children = ( - 9A57A19C22A1E3270033E318 /* CPU.swift */, - 9A7B8F5D22A2A57600DEB352 /* CPUReader.swift */, - 9A59AE53231ED1AC007989D6 /* CPUView.swift */, - ); - path = CPU; - sourceTree = ""; - }; - 9A7B8F6222A2C17000DEB352 /* Memory */ = { - isa = PBXGroup; - children = ( - 9A7B8F6822A2C3A100DEB352 /* Memory.swift */, - 9A7B8F6C22A2C3D600DEB352 /* MemoryReader.swift */, - 9A493CDE23202B620064570C /* MemoryView.swift */, - ); - path = Memory; - sourceTree = ""; - }; - 9A7B8F6322A2C17500DEB352 /* Disk */ = { - isa = PBXGroup; - children = ( - 9A7B8F6A22A2C3A700DEB352 /* Disk.swift */, - 9A7B8F6E22A2C57000DEB352 /* DiskReader.swift */, - ); - path = Disk; - sourceTree = ""; - }; 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; }; diff --git a/Stats/MenuBar.swift b/Stats/MenuBar.swift index 093d29b5..ae229da1 100644 --- a/Stats/MenuBar.swift +++ b/Stats/MenuBar.swift @@ -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 } diff --git a/Stats/Modules/Battery/Battery.swift b/Stats/Modules/Battery/Battery.swift deleted file mode 100644 index bf75e096..00000000 --- a/Stats/Modules/Battery/Battery.swift +++ /dev/null @@ -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 - public let timeView: Observable - - 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) - } -} - diff --git a/Stats/Modules/Battery/BatteryReader.swift b/Stats/Modules/Battery/BatteryReader.swift deleted file mode 100644 index eaf7bcf3..00000000 --- a/Stats/Modules/Battery/BatteryReader.swift +++ /dev/null @@ -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 = 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 { - 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 { - 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) - } -} diff --git a/Stats/Modules/Battery/BatteryView.swift b/Stats/Modules/Battery/BatteryView.swift deleted file mode 100644 index 083ea2f3..00000000 --- a/Stats/Modules/Battery/BatteryView.swift +++ /dev/null @@ -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" - } - } -} diff --git a/Stats/Modules/CPU/CPU.swift b/Stats/Modules/CPU/CPU.swift index 7cb7273e..c6c39de4 100644 --- a/Stats/Modules/CPU/CPU.swift +++ b/Stats/Modules/CPU/CPU.swift @@ -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 - 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) } } diff --git a/Stats/Modules/CPU/CPULoadReader.swift b/Stats/Modules/CPU/CPULoadReader.swift new file mode 100644 index 00000000..658639e8 --- /dev/null +++ b/Stats/Modules/CPU/CPULoadReader.swift @@ -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.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.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.stride/MemoryLayout.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 + } +} diff --git a/Stats/Modules/Memory/Memory.swift b/Stats/Modules/CPU/CPUMenu.swift similarity index 68% rename from Stats/Modules/Memory/Memory.swift rename to Stats/Modules/CPU/CPUMenu.swift index 55a092b3..f81b51ad 100644 --- a/Stats/Modules/Memory/Memory.swift +++ b/Stats/Modules/CPU/CPUMenu.swift @@ -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) } } diff --git a/Stats/Modules/CPU/CPUView.swift b/Stats/Modules/CPU/CPUPopup.swift similarity index 57% rename from Stats/Modules/CPU/CPUView.swift rename to Stats/Modules/CPU/CPUPopup.swift index 61b6690b..ccf0fbae 100644 --- a/Stats/Modules/CPU/CPUView.swift +++ b/Stats/Modules/CPU/CPUPopup.swift @@ -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 diff --git a/Stats/Modules/CPU/CPUProcessReader.swift b/Stats/Modules/CPU/CPUProcessReader.swift new file mode 100644 index 00000000..a225a7d5 --- /dev/null +++ b/Stats/Modules/CPU/CPUProcessReader.swift @@ -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) + }) + } +} diff --git a/Stats/Modules/CPU/CPUReader.swift b/Stats/Modules/CPU/CPUReader.swift deleted file mode 100644 index 64857582..00000000 --- a/Stats/Modules/CPU/CPUReader.swift +++ /dev/null @@ -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 = 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.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.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.stride/MemoryLayout.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 - } -} diff --git a/Stats/Modules/CPU/CPUUsageReader.swift b/Stats/Modules/CPU/CPUUsageReader.swift new file mode 100644 index 00000000..434ea7eb --- /dev/null +++ b/Stats/Modules/CPU/CPUUsageReader.swift @@ -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.stride/MemoryLayout.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 + } +} diff --git a/Stats/Modules/Disk/Disk.swift b/Stats/Modules/Disk/Disk.swift deleted file mode 100644 index b90713a0..00000000 --- a/Stats/Modules/Disk/Disk.swift +++ /dev/null @@ -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) - } -} diff --git a/Stats/Modules/Disk/DiskReader.swift b/Stats/Modules/Disk/DiskReader.swift deleted file mode 100644 index 00bff70a..00000000 --- a/Stats/Modules/Disk/DiskReader.swift +++ /dev/null @@ -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) - } -} diff --git a/Stats/Modules/Memory/MemoryReader.swift b/Stats/Modules/Memory/MemoryReader.swift deleted file mode 100644 index f1fbca0a..00000000 --- a/Stats/Modules/Memory/MemoryReader.swift +++ /dev/null @@ -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 = 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.size / MemoryLayout.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.size / MemoryLayout.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) - } -} diff --git a/Stats/Modules/Memory/MemoryView.swift b/Stats/Modules/Memory/MemoryView.swift deleted file mode 100644 index a7591dba..00000000 --- a/Stats/Modules/Memory/MemoryView.swift +++ /dev/null @@ -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 - } -} diff --git a/Stats/Modules/Module.swift b/Stats/Modules/Module.swift index a0e7636c..fe975a5c 100644 --- a/Stats/Modules/Module.swift +++ b/Stats/Modules/Module.swift @@ -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 } } diff --git a/Stats/Modules/Network/Network.swift b/Stats/Modules/Network/Network.swift deleted file mode 100644 index 07420fa2..00000000 --- a/Stats/Modules/Network/Network.swift +++ /dev/null @@ -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) - } -} diff --git a/Stats/Modules/Network/NetworkReader.swift b/Stats/Modules/Network/NetworkReader.swift deleted file mode 100644 index 7d1b9812..00000000 --- a/Stats/Modules/Network/NetworkReader.swift +++ /dev/null @@ -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? = 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) -> [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? = nil - - if name.hasPrefix("en") { - networkData = unsafeBitCast(pointer.pointee.ifa_data, to: UnsafeMutablePointer.self) - return [Int64(networkData?.pointee.ifi_obytes ?? 0), Int64(networkData?.pointee.ifi_ibytes ?? 0)] // upload, download - } - - return nil - } - - func setInterval(value: Int) {} -} diff --git a/Stats/Modules/Reader.swift b/Stats/Modules/Reader.swift deleted file mode 100644 index 7610464e..00000000 --- a/Stats/Modules/Reader.swift +++ /dev/null @@ -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() {} -} diff --git a/Stats/Views/PopupViewController.swift b/Stats/Views/PopupViewController.swift index d4418d4f..b8006c92 100644 --- a/Stats/Views/PopupViewController.swift +++ b/Stats/Views/PopupViewController.swift @@ -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) } } diff --git a/Stats/Widgets/Battery/BatteryWidget.swift b/Stats/Widgets/Battery/BatteryWidget.swift index 2bf44152..3de28dd4 100644 --- a/Stats/Widgets/Battery/BatteryWidget.swift +++ b/Stats/Widgets/Battery/BatteryWidget.swift @@ -9,7 +9,6 @@ import Cocoa class BatteryWidget: NSView, Widget { - public var activeModule: Observable = Observable(false) public var name: String = "Battery" public var shortName: String = "BAT" public var menus: [NSMenuItem] = [] diff --git a/Stats/Widgets/Charts/BarChart.swift b/Stats/Widgets/Charts/BarChart.swift index f7c7a3bf..86ce5e51 100644 --- a/Stats/Widgets/Charts/BarChart.swift +++ b/Stats/Widgets/Charts/BarChart.swift @@ -9,7 +9,6 @@ import Cocoa class BarChart: NSView, Widget { - var activeModule: Observable = Observable(false) var size: CGFloat = widgetSize.width + 10 let defaults = UserDefaults.standard diff --git a/Stats/Widgets/Charts/LineChart.swift b/Stats/Widgets/Charts/LineChart.swift index e100eacd..acbef40b 100644 --- a/Stats/Widgets/Charts/LineChart.swift +++ b/Stats/Widgets/Charts/LineChart.swift @@ -9,7 +9,6 @@ import Cocoa class Chart: NSView, Widget { - var activeModule: Observable = Observable(false) var size: CGFloat = widgetSize.width + 7 var labelPadding: CGFloat = 10.0 var label: Bool = false diff --git a/Stats/Widgets/Mini.swift b/Stats/Widgets/Mini.swift index 502a9312..fd99312a 100644 --- a/Stats/Widgets/Mini.swift +++ b/Stats/Widgets/Mini.swift @@ -9,7 +9,6 @@ import Cocoa class Mini: NSView, Widget { - var activeModule: Observable = 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 diff --git a/Stats/Widgets/Network/NetworkArrows.swift b/Stats/Widgets/Network/NetworkArrows.swift index 0e0fa7c2..59b2217b 100644 --- a/Stats/Widgets/Network/NetworkArrows.swift +++ b/Stats/Widgets/Network/NetworkArrows.swift @@ -10,12 +10,10 @@ import Cocoa class NetworkArrowsView: NSView, Widget { var menus: [NSMenuItem] = [] - var activeModule: Observable = Observable(false) var size: CGFloat = 8 var name: String = "" var shortName: String = "" - var color: Observable = Observable(false) var download: Int64 { didSet { self.redraw() diff --git a/Stats/Widgets/Network/NetworkArrowsText.swift b/Stats/Widgets/Network/NetworkArrowsText.swift index 56755c61..4e77b705 100644 --- a/Stats/Widgets/Network/NetworkArrowsText.swift +++ b/Stats/Widgets/Network/NetworkArrowsText.swift @@ -10,12 +10,10 @@ import Cocoa class NetworkArrowsTextView: NSView, Widget { var menus: [NSMenuItem] = [] - var activeModule: Observable = Observable(false) var size: CGFloat = widgetSize.width + 24 var name: String = "" var shortName: String = "" - var color: Observable = Observable(false) var download: Int64 { didSet { self.redraw() diff --git a/Stats/Widgets/Network/NetworkDots.swift b/Stats/Widgets/Network/NetworkDots.swift index 56dd3927..f7db246f 100644 --- a/Stats/Widgets/Network/NetworkDots.swift +++ b/Stats/Widgets/Network/NetworkDots.swift @@ -9,13 +9,11 @@ import Cocoa class NetworkDotsView: NSView, Widget { - var activeModule: Observable = Observable(false) var size: CGFloat = 12 var name: String = "" var shortName: String = "" var menus: [NSMenuItem] = [] - var color: Observable = Observable(false) var download: Int64 { didSet { self.redraw() diff --git a/Stats/Widgets/Network/NetworkDotsText.swift b/Stats/Widgets/Network/NetworkDotsText.swift index a994a71c..f2fd60ec 100644 --- a/Stats/Widgets/Network/NetworkDotsText.swift +++ b/Stats/Widgets/Network/NetworkDotsText.swift @@ -10,12 +10,10 @@ import Cocoa class NetworkDotsTextView: NSView, Widget { var menus: [NSMenuItem] = [] - var activeModule: Observable = Observable(false) var size: CGFloat = widgetSize.width + 26 var name: String = "" var shortName: String = "" - var color: Observable = Observable(false) var download: Int64 { didSet { self.redraw() diff --git a/Stats/Widgets/Network/NetworkText.swift b/Stats/Widgets/Network/NetworkText.swift index c2e3cdff..c4eb9f3b 100644 --- a/Stats/Widgets/Network/NetworkText.swift +++ b/Stats/Widgets/Network/NetworkText.swift @@ -10,12 +10,10 @@ import Cocoa class NetworkTextView: NSView, Widget { var menus: [NSMenuItem] = [] - var activeModule: Observable = Observable(false) var size: CGFloat = widgetSize.width + 20 var name: String = "" var shortName: String = "" - var color: Observable = Observable(false) var downloadValue: NSTextField = NSTextField() var uploadValue: NSTextField = NSTextField() diff --git a/Stats/Widgets/Widget.swift b/Stats/Widgets/Widget.swift index 83611912..be5e304c 100644 --- a/Stats/Widgets/Widget.swift +++ b/Stats/Widgets/Widget.swift @@ -11,7 +11,6 @@ import Cocoa protocol Widget { var name: String { get set } var shortName: String { get set } - var activeModule: Observable { get set } var menus: [NSMenuItem] { get } func setValue(data: [Double]) diff --git a/Stats/libs/Extensions.swift b/Stats/libs/Extensions.swift index 8fbf34fa..9889966c 100755 --- a/Stats/libs/Extensions.swift +++ b/Stats/libs/Extensions.swift @@ -246,7 +246,7 @@ extension String { } } -extension URL { +extension URL { func checkFileExist() -> Bool { return FileManager.default.fileExists(atPath: self.path) } diff --git a/Stats/libs/Observable.swift b/Stats/libs/Observable.swift deleted file mode 100755 index 86491ff7..00000000 --- a/Stats/libs/Observable.swift +++ /dev/null @@ -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: ObservableProtocol { - typealias ObserverBlock = (_ newValue: T, _ oldValue: T) -> () - typealias ObserversEntry = (observer: AnyObject, block: ObserverBlock) - private var observers: Array - 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 <<(observable: Observable, value: T) { - observable.value = value -}