From 4d6f759d3b052f6301eeb120b7119b4a1f48234c Mon Sep 17 00:00:00 2001 From: Serhiy Mytrovtsiy Date: Sun, 7 Jun 2020 12:22:32 +0200 Subject: [PATCH] v2.0.0 (#34) * v2.0.0 * rewritten application from scratch * new Settings * new custom popup view * moved to own implementation of chart * added more option to configure a widget * now each module has own widget in the menu bar * a lot of new features... --- .github/FUNDING.yml | 3 - .gitignore | 4 +- CODE_OF_CONDUCT.md | 76 - Cartfile | 1 - Makefile | 10 +- ModuleKit/Constants.swift | 43 + .../Assets.xcassets/Contents.json | 6 + .../settings.imageset/Contents.json | 26 + .../baseline_settings_black_24pt_1x.png | Bin 0 -> 330 bytes .../baseline_settings_black_24pt_2x.png | Bin 0 -> 551 bytes .../baseline_settings_black_24pt_3x.png | Bin 0 -> 772 bytes ModuleKit/Supporting Files/Info.plist | 24 + ModuleKit/Widgets/BarChart.swift | 240 ++ ModuleKit/Widgets/Battery.swift | 190 ++ ModuleKit/Widgets/LineChart.swift | 255 +++ ModuleKit/Widgets/Mini.swift | 168 ++ ModuleKit/Widgets/Network.swift | 251 +++ ModuleKit/module.swift | 287 +++ ModuleKit/popup.swift | 220 ++ ModuleKit/reader.swift | 129 ++ ModuleKit/settings.swift | 268 +++ ModuleKit/widget.swift | 97 + Modules/Battery/Info.plist | 24 + Modules/Battery/config.plist | 36 + Modules/Battery/main.swift | 86 + Modules/Battery/popup.swift | 229 ++ Modules/Battery/readers.swift | 135 ++ Modules/CPU/Info.plist | 24 + Modules/CPU/config.plist | 38 + Modules/CPU/main.swift | 80 + Modules/CPU/popup.swift | 169 ++ Modules/CPU/readers.swift | 152 ++ Modules/CPU/settings.swift | 71 + Modules/Disk/Info.plist | 24 + Modules/Disk/config.plist | 51 + Modules/Disk/main.swift | 123 ++ Modules/Disk/popup.swift | 176 ++ .../Disk/readers.swift | 90 +- Modules/Disk/settings.swift | 80 + Modules/Memory/Info.plist | 24 + Modules/Memory/config.plist | 50 + Modules/Memory/main.swift | 71 + Modules/Memory/popup.swift | 119 + Modules/Memory/readers.swift | 72 + Modules/Net/Info.plist | 24 + Modules/Net/config.plist | 18 + Modules/Net/main.swift | 86 + Modules/Net/popup.swift | 195 ++ Modules/Net/readers.swift | 214 ++ README.md | 39 +- Stats.xcodeproj/project.pbxproj | 1923 ++++++++++++++--- .../xcshareddata/xcschemes/Stats.xcscheme | 27 +- Stats/AppDelegate.swift | 172 +- Stats/MenuBar.swift | 155 -- Stats/Modules/Battery/Battery.swift | 95 - Stats/Modules/Battery/BatteryMenu.swift | 94 - Stats/Modules/Battery/BatteryPopup.swift | 257 --- Stats/Modules/Battery/BatteryReader.swift | 189 -- Stats/Modules/CPU/CPU.swift | 80 - Stats/Modules/CPU/CPULoadReader.swift | 144 -- Stats/Modules/CPU/CPUMenu.swift | 206 -- Stats/Modules/CPU/CPUPopup.swift | 236 -- Stats/Modules/CPU/CPUProcessReader.swift | 93 - Stats/Modules/CPU/CPUUsageReader.swift | 82 - Stats/Modules/Disk/Disk.swift | 90 - Stats/Modules/Disk/DiskMenu.swift | 201 -- Stats/Modules/Module.swift | 115 - Stats/Modules/Network/Network.swift | 85 - .../Network/NetworkInterfaceReader.swift | 277 --- Stats/Modules/Network/NetworkMenu.swift | 118 - Stats/Modules/Network/NetworkPopup.swift | 284 --- Stats/Modules/Network/NetworkReader.swift | 99 - Stats/Modules/RAM/RAM.swift | 82 - Stats/Modules/RAM/RAMMenu.swift | 176 -- Stats/Modules/RAM/RAMPopup.swift | 237 -- Stats/Modules/RAM/RAMProcessReader.swift | 87 - Stats/Modules/RAM/RAMUsageReader.swift | 90 - Stats/Modules/Sensors/Sensors.swift | 102 - Stats/Modules/Sensors/SensorsMenu.swift | 153 -- Stats/Modules/Sensors/SensorsType.swift | 183 -- Stats/Supporting Files/About.storyboard | 183 -- .../Contents.json | 12 +- .../baseline_apps_white_24pt_1x.png | Bin 0 -> 90 bytes .../baseline_apps_white_24pt_2x.png | Bin 0 -> 93 bytes .../baseline_apps_white_24pt_3x.png | Bin 0 -> 98 bytes .../bug.imageset/Contents.json | 26 + .../baseline_bug_report_white_24pt_1x.png | Bin 0 -> 200 bytes .../baseline_bug_report_white_24pt_2x.png | Bin 0 -> 317 bytes .../baseline_bug_report_white_24pt_3x.png | Bin 0 -> 445 bytes .../chart.imageset/Contents.json | 26 + ...ne_insert_chart_outlined_white_24pt_1x.png | Bin 0 -> 162 bytes ...ne_insert_chart_outlined_white_24pt_2x.png | Bin 0 -> 215 bytes ...ne_insert_chart_outlined_white_24pt_3x.png | Bin 0 -> 305 bytes .../Assets.xcassets/devices/Contents.json | 6 + .../devices/imac.imageset/Contents.json | 21 + .../devices/imac.imageset/imac.png | Bin 0 -> 49207 bytes .../devices/imacPro.imageset/Contents.json | 21 + .../devices/imacPro.imageset/imacPro.png | Bin 0 -> 49735 bytes .../devices/macMini.imageset/Contents.json | 21 + .../devices/macMini.imageset/macMini.png | Bin 0 -> 43344 bytes .../devices/macbookAir.imageset/Contents.json | 21 + .../macbookAir.imageset/macbookAir.png | Bin 0 -> 55680 bytes .../devices/macbookPro.imageset/Contents.json | 21 + .../macbookPro.imageset/macbookPro.png | Bin 0 -> 56144 bytes .../Assets.xcassets/icons/Contents.json | 6 - .../baseline_build_black_18pt_1x.png | Bin 216 -> 0 bytes .../baseline_build_black_18pt_2x.png | Bin 303 -> 0 bytes .../baseline_build_black_18pt_3x.png | Bin 444 -> 0 bytes .../power.imageset/Contents.json | 26 + ...eline_power_settings_new_white_24pt_1x.png | Bin 0 -> 301 bytes ...eline_power_settings_new_white_24pt_2x.png | Bin 0 -> 548 bytes ...eline_power_settings_new_white_24pt_3x.png | Bin 0 -> 747 bytes .../Base.lproj/Main.storyboard | 71 - Stats/Supporting Files/Info.plist | 4 +- Stats/Supporting Files/Updates.storyboard | 287 --- .../Supporting Files}/background.png | Bin .../Supporting Files}/cover.psd | Bin Stats/Supporting Files/main.swift | 15 + Stats/Views/AboutViewController.swift | 35 - Stats/Views/AppSettings.swift | 281 +++ Stats/Views/PopupViewController.swift | 288 --- Stats/Views/Settings.swift | 349 +++ Stats/Views/Update.swift | 179 ++ Stats/Views/UpdatesViewController.swift | 79 - .../Battery/BatteryPercentageWidget.swift | 67 - Stats/Widgets/Battery/BatteryTimeWidget.swift | 69 - Stats/Widgets/Battery/BatteryWidget.swift | 132 -- Stats/Widgets/Charts/BarChart.swift | 146 -- Stats/Widgets/Charts/LineChart.swift | 181 -- Stats/Widgets/Charts/LineChartWithValue.swift | 120 - Stats/Widgets/Mini.swift | 106 - Stats/Widgets/Network/NetworkArrows.swift | 93 - Stats/Widgets/Network/NetworkArrowsText.swift | 127 -- Stats/Widgets/Network/NetworkDots.swift | 79 - Stats/Widgets/Network/NetworkDotsText.swift | 118 - Stats/Widgets/Network/NetworkText.swift | 81 - Stats/Widgets/Sensors/SensorsWidget.swift | 169 -- Stats/Widgets/Widget.swift | 55 - Stats/libs/ChartMarker.swift | 47 - Stats/libs/Extensions.swift | 340 --- StatsKit/Charts.swift | 99 + StatsKit/Info.plist | 24 + {Stats/libs => StatsKit}/SMC.swift | 36 +- StatsKit/StatsKit.h | 15 + StatsKit/SystemKit.swift | 383 ++++ StatsKit/extensions.swift | 525 +++++ .../launchAtLogin.swift | 11 +- StatsKit/store.swift | 48 + .../updater.swift | 199 +- resources/logo.png | Bin 26246 -> 0 bytes resources/logo.psd | Bin 1247004 -> 0 bytes resources/tray_icon.psd | Bin 24948 -> 0 bytes 152 files changed, 8546 insertions(+), 7632 deletions(-) delete mode 100644 .github/FUNDING.yml delete mode 100644 CODE_OF_CONDUCT.md create mode 100644 ModuleKit/Constants.swift create mode 100644 ModuleKit/Supporting Files/Assets.xcassets/Contents.json create mode 100644 ModuleKit/Supporting Files/Assets.xcassets/settings.imageset/Contents.json create mode 100644 ModuleKit/Supporting Files/Assets.xcassets/settings.imageset/baseline_settings_black_24pt_1x.png create mode 100644 ModuleKit/Supporting Files/Assets.xcassets/settings.imageset/baseline_settings_black_24pt_2x.png create mode 100644 ModuleKit/Supporting Files/Assets.xcassets/settings.imageset/baseline_settings_black_24pt_3x.png create mode 100644 ModuleKit/Supporting Files/Info.plist create mode 100644 ModuleKit/Widgets/BarChart.swift create mode 100644 ModuleKit/Widgets/Battery.swift create mode 100644 ModuleKit/Widgets/LineChart.swift create mode 100644 ModuleKit/Widgets/Mini.swift create mode 100644 ModuleKit/Widgets/Network.swift create mode 100644 ModuleKit/module.swift create mode 100644 ModuleKit/popup.swift create mode 100644 ModuleKit/reader.swift create mode 100644 ModuleKit/settings.swift create mode 100644 ModuleKit/widget.swift create mode 100644 Modules/Battery/Info.plist create mode 100644 Modules/Battery/config.plist create mode 100644 Modules/Battery/main.swift create mode 100644 Modules/Battery/popup.swift create mode 100644 Modules/Battery/readers.swift create mode 100644 Modules/CPU/Info.plist create mode 100644 Modules/CPU/config.plist create mode 100644 Modules/CPU/main.swift create mode 100644 Modules/CPU/popup.swift create mode 100644 Modules/CPU/readers.swift create mode 100644 Modules/CPU/settings.swift create mode 100644 Modules/Disk/Info.plist create mode 100644 Modules/Disk/config.plist create mode 100644 Modules/Disk/main.swift create mode 100644 Modules/Disk/popup.swift rename Stats/Modules/Disk/DiskCapacityReader.swift => Modules/Disk/readers.swift (69%) create mode 100644 Modules/Disk/settings.swift create mode 100644 Modules/Memory/Info.plist create mode 100644 Modules/Memory/config.plist create mode 100644 Modules/Memory/main.swift create mode 100644 Modules/Memory/popup.swift create mode 100644 Modules/Memory/readers.swift create mode 100644 Modules/Net/Info.plist create mode 100644 Modules/Net/config.plist create mode 100644 Modules/Net/main.swift create mode 100644 Modules/Net/popup.swift create mode 100644 Modules/Net/readers.swift delete mode 100644 Stats/MenuBar.swift delete mode 100644 Stats/Modules/Battery/Battery.swift delete mode 100644 Stats/Modules/Battery/BatteryMenu.swift delete mode 100644 Stats/Modules/Battery/BatteryPopup.swift delete mode 100644 Stats/Modules/Battery/BatteryReader.swift delete mode 100644 Stats/Modules/CPU/CPU.swift delete mode 100644 Stats/Modules/CPU/CPULoadReader.swift delete mode 100644 Stats/Modules/CPU/CPUMenu.swift delete mode 100644 Stats/Modules/CPU/CPUPopup.swift delete mode 100644 Stats/Modules/CPU/CPUProcessReader.swift delete mode 100644 Stats/Modules/CPU/CPUUsageReader.swift delete mode 100644 Stats/Modules/Disk/Disk.swift delete mode 100644 Stats/Modules/Disk/DiskMenu.swift delete mode 100644 Stats/Modules/Module.swift delete mode 100644 Stats/Modules/Network/Network.swift delete mode 100644 Stats/Modules/Network/NetworkInterfaceReader.swift delete mode 100644 Stats/Modules/Network/NetworkMenu.swift delete mode 100644 Stats/Modules/Network/NetworkPopup.swift delete mode 100644 Stats/Modules/Network/NetworkReader.swift delete mode 100644 Stats/Modules/RAM/RAM.swift delete mode 100644 Stats/Modules/RAM/RAMMenu.swift delete mode 100644 Stats/Modules/RAM/RAMPopup.swift delete mode 100644 Stats/Modules/RAM/RAMProcessReader.swift delete mode 100644 Stats/Modules/RAM/RAMUsageReader.swift delete mode 100644 Stats/Modules/Sensors/Sensors.swift delete mode 100644 Stats/Modules/Sensors/SensorsMenu.swift delete mode 100644 Stats/Modules/Sensors/SensorsType.swift delete mode 100644 Stats/Supporting Files/About.storyboard rename Stats/Supporting Files/Assets.xcassets/{icons/baseline_build_black_18pt.imageset => apps.imageset}/Contents.json (58%) create mode 100644 Stats/Supporting Files/Assets.xcassets/apps.imageset/baseline_apps_white_24pt_1x.png create mode 100644 Stats/Supporting Files/Assets.xcassets/apps.imageset/baseline_apps_white_24pt_2x.png create mode 100644 Stats/Supporting Files/Assets.xcassets/apps.imageset/baseline_apps_white_24pt_3x.png create mode 100644 Stats/Supporting Files/Assets.xcassets/bug.imageset/Contents.json create mode 100644 Stats/Supporting Files/Assets.xcassets/bug.imageset/baseline_bug_report_white_24pt_1x.png create mode 100644 Stats/Supporting Files/Assets.xcassets/bug.imageset/baseline_bug_report_white_24pt_2x.png create mode 100644 Stats/Supporting Files/Assets.xcassets/bug.imageset/baseline_bug_report_white_24pt_3x.png create mode 100644 Stats/Supporting Files/Assets.xcassets/chart.imageset/Contents.json create mode 100644 Stats/Supporting Files/Assets.xcassets/chart.imageset/baseline_insert_chart_outlined_white_24pt_1x.png create mode 100644 Stats/Supporting Files/Assets.xcassets/chart.imageset/baseline_insert_chart_outlined_white_24pt_2x.png create mode 100644 Stats/Supporting Files/Assets.xcassets/chart.imageset/baseline_insert_chart_outlined_white_24pt_3x.png create mode 100644 Stats/Supporting Files/Assets.xcassets/devices/Contents.json create mode 100644 Stats/Supporting Files/Assets.xcassets/devices/imac.imageset/Contents.json create mode 100644 Stats/Supporting Files/Assets.xcassets/devices/imac.imageset/imac.png create mode 100644 Stats/Supporting Files/Assets.xcassets/devices/imacPro.imageset/Contents.json create mode 100644 Stats/Supporting Files/Assets.xcassets/devices/imacPro.imageset/imacPro.png create mode 100644 Stats/Supporting Files/Assets.xcassets/devices/macMini.imageset/Contents.json create mode 100644 Stats/Supporting Files/Assets.xcassets/devices/macMini.imageset/macMini.png create mode 100644 Stats/Supporting Files/Assets.xcassets/devices/macbookAir.imageset/Contents.json create mode 100644 Stats/Supporting Files/Assets.xcassets/devices/macbookAir.imageset/macbookAir.png create mode 100644 Stats/Supporting Files/Assets.xcassets/devices/macbookPro.imageset/Contents.json create mode 100644 Stats/Supporting Files/Assets.xcassets/devices/macbookPro.imageset/macbookPro.png delete mode 100644 Stats/Supporting Files/Assets.xcassets/icons/Contents.json delete mode 100644 Stats/Supporting Files/Assets.xcassets/icons/baseline_build_black_18pt.imageset/baseline_build_black_18pt_1x.png delete mode 100644 Stats/Supporting Files/Assets.xcassets/icons/baseline_build_black_18pt.imageset/baseline_build_black_18pt_2x.png delete mode 100644 Stats/Supporting Files/Assets.xcassets/icons/baseline_build_black_18pt.imageset/baseline_build_black_18pt_3x.png create mode 100644 Stats/Supporting Files/Assets.xcassets/power.imageset/Contents.json create mode 100644 Stats/Supporting Files/Assets.xcassets/power.imageset/baseline_power_settings_new_white_24pt_1x.png create mode 100644 Stats/Supporting Files/Assets.xcassets/power.imageset/baseline_power_settings_new_white_24pt_2x.png create mode 100644 Stats/Supporting Files/Assets.xcassets/power.imageset/baseline_power_settings_new_white_24pt_3x.png delete mode 100755 Stats/Supporting Files/Base.lproj/Main.storyboard delete mode 100644 Stats/Supporting Files/Updates.storyboard rename {resources => Stats/Supporting Files}/background.png (100%) rename {resources => Stats/Supporting Files}/cover.psd (100%) create mode 100644 Stats/Supporting Files/main.swift delete mode 100644 Stats/Views/AboutViewController.swift create mode 100644 Stats/Views/AppSettings.swift delete mode 100644 Stats/Views/PopupViewController.swift create mode 100644 Stats/Views/Settings.swift create mode 100644 Stats/Views/Update.swift delete mode 100644 Stats/Views/UpdatesViewController.swift delete mode 100644 Stats/Widgets/Battery/BatteryPercentageWidget.swift delete mode 100644 Stats/Widgets/Battery/BatteryTimeWidget.swift delete mode 100644 Stats/Widgets/Battery/BatteryWidget.swift delete mode 100644 Stats/Widgets/Charts/BarChart.swift delete mode 100644 Stats/Widgets/Charts/LineChart.swift delete mode 100644 Stats/Widgets/Charts/LineChartWithValue.swift delete mode 100644 Stats/Widgets/Mini.swift delete mode 100644 Stats/Widgets/Network/NetworkArrows.swift delete mode 100644 Stats/Widgets/Network/NetworkArrowsText.swift delete mode 100644 Stats/Widgets/Network/NetworkDots.swift delete mode 100644 Stats/Widgets/Network/NetworkDotsText.swift delete mode 100644 Stats/Widgets/Network/NetworkText.swift delete mode 100644 Stats/Widgets/Sensors/SensorsWidget.swift delete mode 100644 Stats/Widgets/Widget.swift delete mode 100644 Stats/libs/ChartMarker.swift delete mode 100755 Stats/libs/Extensions.swift create mode 100644 StatsKit/Charts.swift create mode 100644 StatsKit/Info.plist rename {Stats/libs => StatsKit}/SMC.swift (93%) create mode 100644 StatsKit/StatsKit.h create mode 100644 StatsKit/SystemKit.swift create mode 100644 StatsKit/extensions.swift rename Stats/libs/LaunchAtLogin.swift => StatsKit/launchAtLogin.swift (81%) create mode 100644 StatsKit/store.swift rename Stats/libs/MacAppUpdater.swift => StatsKit/updater.swift (71%) delete mode 100644 resources/logo.png delete mode 100644 resources/logo.psd delete mode 100644 resources/tray_icon.psd diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index e22feee6..00000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,3 +0,0 @@ -# These are supported funding model platforms - -github: exelban diff --git a/.gitignore b/.gitignore index a286af46..43daec06 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,6 @@ xcuserdata Stats.dmg Stats.app -create-dmg \ No newline at end of file +create-dmg + +Cartfile.resolved \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index d74d9c2c..00000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,76 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and expression, -level of experience, education, socio-economic status, nationality, personal -appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at mitrovtsiy@ukr.net. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see -https://www.contributor-covenant.org/faq diff --git a/Cartfile b/Cartfile index 23908c29..003e0fe0 100644 --- a/Cartfile +++ b/Cartfile @@ -1,3 +1,2 @@ -github "danielgindi/Charts" ~> 3.4.0 github "ashleymills/Reachability.swift" ~> 5.0.0 github "malcommac/Repeat" ~> 0.6.0 \ No newline at end of file diff --git a/Makefile b/Makefile index 9f6bf502..4d75ae00 100644 --- a/Makefile +++ b/Makefile @@ -59,7 +59,7 @@ build: sign ./create-dmg/create-dmg \ --volname $(APP) \ - --background "./resources/background.png" \ + --background "./Stats/Supporting Files/background.png" \ --window-pos 200 120 \ --window-size 500 320 \ --icon-size 80 \ @@ -89,4 +89,10 @@ history: .PHONY: dep dep: - carthage update --platform macOS \ No newline at end of file + carthage update --platform macOS + +.PHONY: zip +zip: + cd ../ + zip -r archive.zip ./ + open $(PWD) \ No newline at end of file diff --git a/ModuleKit/Constants.swift b/ModuleKit/Constants.swift new file mode 100644 index 00000000..5bd24214 --- /dev/null +++ b/ModuleKit/Constants.swift @@ -0,0 +1,43 @@ +// +// Constants.swift +// ModuleKit +// +// Created by Serhiy Mytrovtsiy on 15/04/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa + +public struct Popup_c_s { + public let width: CGFloat = 264 + public let height: CGFloat = 300 + public let margins: CGFloat = 8 + public let headerHeight: CGFloat = 42 + public let separatorHeight: CGFloat = 30 +} + +public struct Settings_c_s { + public let width: CGFloat = 539 + public let height: CGFloat = 479 + public let margin: CGFloat = 10 +} + +public struct Widget_c_s { + public let width: CGFloat = 32 + public var height: CGFloat { + get { + let systemHeight = NSApplication.shared.mainMenu?.menuBarHeight + return (systemHeight == 0 ? 22 : systemHeight) ?? 22 + } + } + public let margin: CGFloat = 2 +} + +public struct Constants { + public static let Popup: Popup_c_s = Popup_c_s() + public static let Settings: Settings_c_s = Settings_c_s() + public static let Widget: Widget_c_s = Widget_c_s() +} diff --git a/ModuleKit/Supporting Files/Assets.xcassets/Contents.json b/ModuleKit/Supporting Files/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/ModuleKit/Supporting Files/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ModuleKit/Supporting Files/Assets.xcassets/settings.imageset/Contents.json b/ModuleKit/Supporting Files/Assets.xcassets/settings.imageset/Contents.json new file mode 100644 index 00000000..211deede --- /dev/null +++ b/ModuleKit/Supporting Files/Assets.xcassets/settings.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "baseline_settings_black_24pt_1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "baseline_settings_black_24pt_2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "baseline_settings_black_24pt_3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/ModuleKit/Supporting Files/Assets.xcassets/settings.imageset/baseline_settings_black_24pt_1x.png b/ModuleKit/Supporting Files/Assets.xcassets/settings.imageset/baseline_settings_black_24pt_1x.png new file mode 100644 index 0000000000000000000000000000000000000000..afdee093478f2176474f052e27151d1bd634d5d7 GIT binary patch literal 330 zcmV-Q0k!^#P)1!Jh9DRUD28AFz%T&707DQA5EL*3K>-24Fbu^2MKM4CLlF!^F$}@C z7x25eOxpl`d4Qjj%S9*C6-r}@(RzZ62aMJWWK=k)43EJI=I9`!MT&?Ll$c?#GjtGo ztkFZ3c_Rq#h~ZX3N4k%kF7X37T2!d9g&d!_z@JilLlzc@%_1y;`6*sc@LIFFnXbbFlM!o4Ds82!bLgiXs?>!%zgF2#R1Bf}#k5A}9hFil7(@2!fy(U?>6zf?x=S0D?mi z1c!eg07t*Od%u%>Mm)n}%+o55%z4fE+p~r3n8ahX_UMK@MwN z!mC^Cp$u~rZ3$(Ft;1ZybNQ#GMXbox;9=Rqo2rK z13Sn=j3rihgFLKYKlK5)f~h5RVl$Y#f=+0U3wSk!khZ{I$kQBa2x((@caA-T7!!O= z9XrdwuhC6C?I{irwy2|&nz(WNU>!3kr(YSb**!FH)P_(_T?AM6vwvQ1K{+G{wgk$s pL9qRSGE5L`HFO->sH&Qg&ewt{ zs=@jC5Jeq0-vpv)0Owmk6iwlL8;GJ6oGuU15X0#z5KSR6Uk-h&FhUtgU1AB*G)DtT zRWZRUZg7HwHH3VyKpQ#iSHTiu*k7DuzdX8FK|bi=Ae|wGd}50cD!7AK6r+bKCfFs6 zs{rdgLypuvtX6{@xeBZlUq}tV1L1H(Cz|?;es;8K!GOQo7Ch1eIXAM%E{2{LSCg+gYEWTS!V> zm~NQmMY@iS{w7hYH%Lkms<2*wzT&wjgsG~EW4MO3@_2zLdd$cLhzI0gv@-rdG+mn7 zplVtogprzvAqH$wfoctdW7*&W+D#z_Z54k>*N7noy+XSs#DYhJNUDUH#W84ChgcG0 zhAwI-p^Q3iFt_~dGygsQa|0_kIvnwiQy6UkaYP%&JHE^Z9%}Q~d{?jrptxMn6UX0000 + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSHumanReadableCopyright + Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. + + diff --git a/ModuleKit/Widgets/BarChart.swift b/ModuleKit/Widgets/BarChart.swift new file mode 100644 index 00000000..718d39e2 --- /dev/null +++ b/ModuleKit/Widgets/BarChart.swift @@ -0,0 +1,240 @@ +// +// BarChart.swift +// ModuleKit +// +// Created by Serhiy Mytrovtsiy on 26/04/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import StatsKit + +public class BarChart: Widget { + private var labelState: Bool = true + private var boxState: Bool = true + private var colorState: Bool = false + + private let store: UnsafePointer? + private var value: [Double] = [] + + public init(preview: Bool, title: String, config: NSDictionary?, store: UnsafePointer?) { + var widgetTitle: String = title + self.store = store + if config != nil { + var configuration = config! + if let titleFromConfig = config!["Title"] as? String { + widgetTitle = titleFromConfig + } + + if preview { + if let previewConfig = config!["Preview"] as? NSDictionary { + configuration = previewConfig + if let value = configuration["Value"] as? String { + self.value = value.split(separator: ",").map{ (Double($0) ?? 0) } + } + } + } + + if let label = configuration["Label"] as? Bool { + self.labelState = label + } + if let box = configuration["Box"] as? Bool { + self.boxState = box + } + if let color = configuration["Color"] as? Bool { + self.colorState = color + } + } + super.init(frame: CGRect(x: 0, y: Constants.Widget.margin, width: Constants.Widget.width, height: Constants.Widget.height - (2*Constants.Widget.margin))) + self.preview = preview + self.title = widgetTitle + self.type = .barChart + self.canDrawConcurrently = true + + if self.store != nil && !preview { + self.boxState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_box", defaultValue: self.boxState) + self.labelState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_label", defaultValue: self.labelState) + self.colorState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_color", defaultValue: self.colorState) + } + + if preview { + if self.value.count == 0 { + self.value = [0.72, 0.38] + } + self.setFrameSize(NSSize(width: 36, height: self.frame.size.height)) + self.invalidateIntrinsicContentSize() + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func draw(_ dirtyRect: NSRect) { + super.draw(dirtyRect) + + let ctx = NSGraphicsContext.current!.cgContext + ctx.saveGState() + + var width: CGFloat = 0 + var x: CGFloat = Constants.Widget.margin + var chartPadding: CGFloat = 0 + + if self.labelState { + let style = NSMutableParagraphStyle() + style.alignment = .center + let stringAttributes = [ + NSAttributedString.Key.font: NSFont.systemFont(ofSize: 7, weight: .regular), + NSAttributedString.Key.foregroundColor: NSColor.labelColor, + NSAttributedString.Key.paragraphStyle: style + ] + + let letterHeight = self.frame.height / 3 + let letterWidth: CGFloat = 6.0 + + var yMargin: CGFloat = 0 + for char in String(self.title.prefix(3)).uppercased().reversed() { + let rect = CGRect(x: x, y: yMargin, width: letterWidth, height: letterHeight) + let str = NSAttributedString.init(string: "\(char)", attributes: stringAttributes) + str.draw(with: rect) + yMargin += letterHeight + } + width = width + letterWidth + (Constants.Widget.margin*2) + x = letterWidth + (Constants.Widget.margin*3) + } + + switch self.value.count { + case 0, 1: + width += 14 + break + case 2: + width += 26 + break + case 3...4: // 3,4 + width += 32 + break + case 5...8: // 5,6,7,8 + width += 42 + break + case 9...12: // 9..12 + width += 52 + break + case 13...16: // 13..16 + width += 78 + break + case 17...32: // 17..32 + width += 86 + break + default: // > 32 + width += 120 + break + } + + let box = NSBezierPath(roundedRect: NSRect(x: x, y: 0, width: width - x - Constants.Widget.margin, height: self.frame.size.height), xRadius: 2, yRadius: 2) + if self.boxState { + NSColor.black.set() + box.stroke() + box.fill() + chartPadding = 1 + } + + let widthForBarChart = box.bounds.width - chartPadding + let partitionMargin: CGFloat = 0.5 + let partitionsMargin: CGFloat = (CGFloat(self.value.count - 1)) * partitionMargin / CGFloat(self.value.count - 1) + let partitionWidth: CGFloat = (widthForBarChart / CGFloat(self.value.count)) - CGFloat(partitionsMargin.isNaN ? 0 : partitionsMargin) + let maxPartitionHeight: CGFloat = box.bounds.height - (chartPadding*2) + + x += partitionMargin + for i in 0..? + + private var percentage: Double = 1 + private var time: Int = 0 + private var charging: Bool = false + + public init(preview: Bool, title: String, config: NSDictionary?, store: UnsafePointer?) { + let widgetTitle: String = title + self.store = store + super.init(frame: CGRect(x: 0, y: Constants.Widget.margin, width: 30, height: Constants.Widget.height - (2*Constants.Widget.margin))) + self.title = widgetTitle + self.type = .battery + self.preview = preview + self.canDrawConcurrently = true + + if self.store != nil { + self.additional = battery_additional_t(rawValue: store!.pointee.string(key: "\(self.title)_\(self.type.rawValue)_additional", defaultValue: self.additional.rawValue)) ?? self.additional + self.iconState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_icon", defaultValue: self.iconState) + self.colorState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_color", defaultValue: self.colorState) + } + + if self.preview { + self.percentage = 0.72 + self.additional = .none + self.iconState = true + self.colorState = false + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func draw(_ dirtyRect: NSRect) { + super.draw(dirtyRect) + + var width: CGFloat = 30 + var x: CGFloat = Constants.Widget.margin + + let stringAttributes = [ + NSAttributedString.Key.font: NSFont.systemFont(ofSize: 12, weight: .regular), + NSAttributedString.Key.foregroundColor: isDarkMode ? NSColor.white : NSColor.textColor, + NSAttributedString.Key.paragraphStyle: NSMutableParagraphStyle() + ] + + if self.additional == .percentage { + let string = "\(Int((self.percentage.rounded(toPlaces: 2)) * 100))%" + let stringWidth = string.widthOfString(usingFont: .systemFont(ofSize: 12, weight: .regular)) + let rect = CGRect(x: x, y: (Constants.Widget.height-12)/2, width: stringWidth, height: 12) + let str = NSAttributedString.init(string: string, attributes: stringAttributes) + str.draw(with: rect) + + width += stringWidth + Constants.Widget.margin + x += stringWidth + Constants.Widget.margin + } else if self.additional == .time { + let string = Double(self.time*60).printSecondsToHoursMinutesSeconds() + let stringWidth = string.widthOfString(usingFont: .systemFont(ofSize: 12, weight: .regular)) + let rect = CGRect(x: x, y: (Constants.Widget.height-12)/2, width: stringWidth, height: 12) + let str = NSAttributedString.init(string: string, attributes: stringAttributes) + str.draw(with: rect) + + width += stringWidth + Constants.Widget.margin + x += stringWidth + Constants.Widget.margin + } + + let w: CGFloat = 30 - (Constants.Widget.margin*2) - 4 + let h: CGFloat = 11 + let y: CGFloat = (dirtyRect.size.height - h) / 2 + let batteryFrame = NSBezierPath(roundedRect: NSRect(x: x+1, y: y, width: w, height: h), xRadius: 1, yRadius: 1) + + if self.charging { + NSColor.systemGreen.set() + } else { + NSColor.black.set() + } + + let bPX: CGFloat = x+w+1 + let bPY: CGFloat = (dirtyRect.size.height / 2) - 2 + let batteryPoint = NSBezierPath(roundedRect: NSRect(x: bPX, y: bPY, width: 2, height: 4), xRadius: 1, yRadius: 1) + batteryPoint.lineWidth = 1.1 + batteryPoint.stroke() + batteryPoint.fill() + + batteryFrame.lineWidth = 1 + batteryFrame.stroke() + + let maxWidth = w - 3 + let inner = NSBezierPath(roundedRect: NSRect(x: x+2.5, y: y+1.5, width: maxWidth * CGFloat(self.percentage), height: h-3), xRadius: 0.5, yRadius: 0.5) + self.percentage.batteryColor(color: self.colorState).set() + inner.lineWidth = 0 + inner.stroke() + inner.close() + inner.fill() + + self.setWidth(width) + } + + public func setValue(percentage: Double, isCharging: Bool, time: Int) { + var updated: Bool = false + + if self.percentage != percentage { + self.percentage = abs(percentage) + updated = true + } + if self.charging != isCharging { + self.charging = isCharging + updated = true + } + if self.time != time { + self.time = time + updated = true + } + + if updated { + DispatchQueue.main.async(execute: { + self.display() + }) + } + } + + public override func settings(superview: NSView) { + let rowHeight: CGFloat = 30 + let height: CGFloat = ((rowHeight + Constants.Settings.margin) * 2) + Constants.Settings.margin + superview.setFrameSize(NSSize(width: superview.frame.width, height: height)) + + let view: NSView = NSView(frame: NSRect(x: Constants.Settings.margin, y: Constants.Settings.margin, width: superview.frame.width - (Constants.Settings.margin*2), height: superview.frame.height - (Constants.Settings.margin*2))) + + view.addSubview(SelectTitleRow( + frame: NSRect(x: 0, y: rowHeight + Constants.Settings.margin, width: view.frame.width, height: rowHeight), + title: "Additional information", + action: #selector(toggleAdditional), + items: battery_additional_t.allCases.map{ return $0.rawValue }, + selected: self.additional.rawValue + )) + + view.addSubview(ToggleTitleRow( + frame: NSRect(x: 0, y: (rowHeight + Constants.Settings.margin) * 0, width: view.frame.width, height: rowHeight), + title: "Colorize", + action: #selector(toggleColor), + state: self.colorState + )) + + superview.addSubview(view) + } + + @objc private func toggleAdditional(_ sender: NSMenuItem) { + let newValue: battery_additional_t = battery_additional_t(rawValue: sender.title) ?? .none + self.additional = newValue + self.store?.pointee.set(key: "\(self.title)_\(self.type.rawValue)_additional", value: self.additional.rawValue) + self.display() + } + + @objc private func toggleColor(_ sender: NSControl) { + var state: NSControl.StateValue? = nil + if #available(OSX 10.15, *) { + state = sender is NSSwitch ? (sender as! NSSwitch).state: nil + } else { + state = sender is NSButton ? (sender as! NSButton).state: nil + } + self.colorState = state! == .on ? true : false + self.store?.pointee.set(key: "\(self.title)_\(self.type.rawValue)_color", value: self.colorState) + self.display() + } +} diff --git a/ModuleKit/Widgets/LineChart.swift b/ModuleKit/Widgets/LineChart.swift new file mode 100644 index 00000000..8070f085 --- /dev/null +++ b/ModuleKit/Widgets/LineChart.swift @@ -0,0 +1,255 @@ +// +// Chart.swift +// ModuleKit +// +// Created by Serhiy Mytrovtsiy on 18/04/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import StatsKit + +public class LineChart: Widget { + private var labelState: Bool = true + private var boxState: Bool = true + private var valueState: Bool = false + private var colorState: Bool = false + + private let store: UnsafePointer? + private var chart: LineChartView + private var value: Double = 0 + + public init(preview: Bool, title: String, config: NSDictionary?, store: UnsafePointer?) { + var widgetTitle: String = title + self.store = store + if config != nil { + if let titleFromConfig = config!["Title"] as? String { + widgetTitle = titleFromConfig + } + if let label = config!["Label"] as? Bool { + self.labelState = label + } + if let box = config!["Box"] as? Bool { + self.boxState = box + } + if let value = config!["Value"] as? Bool { + self.valueState = value + } + if let color = config!["Color"] as? Bool { + self.colorState = color + } + } + self.chart = LineChartView(frame: NSRect(x: 0, y: 0, width: Constants.Widget.width, height: Constants.Widget.height - (2*Constants.Widget.margin)), num: 60) + super.init(frame: CGRect(x: 0, y: Constants.Widget.margin, width: Constants.Widget.width, height: Constants.Widget.height - (2*Constants.Widget.margin))) + self.preview = preview + self.title = widgetTitle + self.type = .lineChart + self.canDrawConcurrently = true + + if self.store != nil && !preview { + self.boxState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_box", defaultValue: self.boxState) + self.valueState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_value", defaultValue: self.valueState) + self.labelState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_label", defaultValue: self.labelState) + self.colorState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_color", defaultValue: self.colorState) + } + + if self.labelState { + self.setFrameSize(NSSize(width: Constants.Widget.width + 6 + (Constants.Widget.margin*2), height: self.frame.size.height)) + } + + if preview { + var list: [Double] = [] + for _ in 0..<16 { + list.append(Double(CGFloat(Float(arc4random()) / Float(UINT32_MAX)))) + } + self.chart.points = list + self.value = 0.38 + } + } + + public override func draw(_ dirtyRect: NSRect) { + super.draw(dirtyRect) + + let ctx = NSGraphicsContext.current!.cgContext + ctx.saveGState() + + var width = Constants.Widget.width + var x: CGFloat = Constants.Widget.margin + var chartPadding: CGFloat = 0 + + if self.labelState { + let style = NSMutableParagraphStyle() + style.alignment = .center + let stringAttributes = [ + NSAttributedString.Key.font: NSFont.systemFont(ofSize: 7, weight: .regular), + NSAttributedString.Key.foregroundColor: NSColor.labelColor, + NSAttributedString.Key.paragraphStyle: style + ] + + let letterHeight = self.frame.height / 3 + let letterWidth: CGFloat = 6.0 + + var yMargin: CGFloat = 0 + for char in String(self.title.prefix(3)).uppercased().reversed() { + let rect = CGRect(x: x, y: yMargin, width: letterWidth, height: letterHeight) + let str = NSAttributedString.init(string: "\(char)", attributes: stringAttributes) + str.draw(with: rect) + yMargin += letterHeight + } + width = width + letterWidth + (Constants.Widget.margin*2) + x = letterWidth + (Constants.Widget.margin*3) + } + + var boxHeight: CGFloat = self.frame.size.height + var boxRadius: CGFloat = 2 + let boxWidth: CGFloat = Constants.Widget.width - (Constants.Widget.margin*2) + + if self.valueState { + let style = NSMutableParagraphStyle() + style.alignment = .right + + var color = isDarkMode ? NSColor.white : NSColor.black + if self.colorState { + color = self.value.textUsageColor(color: self.colorState) + } + + let stringAttributes = [ + NSAttributedString.Key.font: NSFont.systemFont(ofSize: 8, weight: .regular), + NSAttributedString.Key.foregroundColor: color, + NSAttributedString.Key.paragraphStyle: style + ] + + let rect = CGRect(x: x, y: boxHeight-7, width: boxWidth - chartPadding, height: 7) + let str = NSAttributedString.init(string: "\(Int((value.rounded(toPlaces: 2)) * 100))%", attributes: stringAttributes) + str.draw(with: rect) + + boxHeight = 9 + boxRadius = 1 + } + + let box = NSBezierPath(roundedRect: NSRect(x: x, y: 0, width: boxWidth, height: boxHeight), xRadius: boxRadius, yRadius: boxRadius) + if self.boxState { + NSColor.black.set() + box.stroke() + box.fill() + self.chart.transparent = false + chartPadding = 1 + } else { + self.chart.transparent = true + } + + chart.setFrameSize(NSSize(width: box.bounds.width - chartPadding, height: box.bounds.height - (chartPadding*2))) + chart.draw(NSRect(x: box.bounds.origin.x + 1, y: chartPadding, width: chart.frame.width, height: chart.frame.height)) + + ctx.restoreGState() + self.setWidth(width) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func settings(superview: NSView) { + let rowHeight: CGFloat = 30 + let height: CGFloat = ((rowHeight + Constants.Settings.margin) * 4) + Constants.Settings.margin + superview.setFrameSize(NSSize(width: superview.frame.width, height: height)) + + let view: NSView = NSView(frame: NSRect(x: Constants.Settings.margin, y: Constants.Settings.margin, width: superview.frame.width - (Constants.Settings.margin*2), height: superview.frame.height - (Constants.Settings.margin*2))) + + view.addSubview(ToggleTitleRow( + frame: NSRect(x: 0, y: (rowHeight + Constants.Settings.margin) * 3, width: view.frame.width, height: rowHeight), + title: "Label", + action: #selector(toggleLabel), + state: self.labelState + )) + + view.addSubview(ToggleTitleRow( + frame: NSRect(x: 0, y: (rowHeight + Constants.Settings.margin) * 2, width: view.frame.width, height: rowHeight), + title: "Box", + action: #selector(toggleBox), + state: self.boxState + )) + + view.addSubview(ToggleTitleRow( + frame: NSRect(x: 0, y: (rowHeight + Constants.Settings.margin) * 1, width: view.frame.width, height: rowHeight), + title: "Value", + action: #selector(toggleValue), + state: self.valueState + )) + + view.addSubview(ToggleTitleRow( + frame: NSRect(x: 0, y: (rowHeight + Constants.Settings.margin) * 0, width: view.frame.width, height: rowHeight), + title: "Colorize", + action: #selector(toggleColor), + state: self.colorState + )) + + superview.addSubview(view) + } + + public override func setValues(_ values: [value_t]) { + let historyValues = values.map{ $0.widget_value }.suffix(60) + let end = self.chart.points!.count + self.chart.points!.replaceSubrange(end-historyValues.count...end-1, with: historyValues) + self.display() + } + + public func setValue(_ value: Double) { + self.value = value + DispatchQueue.main.async(execute: { + self.chart.addValue(value) + self.display() + }) + } + + @objc private func toggleLabel(_ sender: NSControl) { + var state: NSControl.StateValue? = nil + if #available(OSX 10.15, *) { + state = sender is NSSwitch ? (sender as! NSSwitch).state: nil + } else { + state = sender is NSButton ? (sender as! NSButton).state: nil + } + self.labelState = state! == .on ? true : false + self.store?.pointee.set(key: "\(self.title)_\(self.type.rawValue)_label", value: self.labelState) + self.display() + } + + @objc private func toggleBox(_ sender: NSControl) { + var state: NSControl.StateValue? = nil + if #available(OSX 10.15, *) { + state = sender is NSSwitch ? (sender as! NSSwitch).state: nil + } else { + state = sender is NSButton ? (sender as! NSButton).state: nil + } + self.boxState = state! == .on ? true : false + self.store?.pointee.set(key: "\(self.title)_\(self.type.rawValue)_box", value: self.boxState) + self.display() + } + + @objc private func toggleValue(_ sender: NSControl) { + var state: NSControl.StateValue? = nil + if #available(OSX 10.15, *) { + state = sender is NSSwitch ? (sender as! NSSwitch).state: nil + } else { + state = sender is NSButton ? (sender as! NSButton).state: nil + } + self.valueState = state! == .on ? true : false + self.store?.pointee.set(key: "\(self.title)_\(self.type.rawValue)_value", value: self.valueState) + self.display() + } + + @objc private func toggleColor(_ sender: NSControl) { + var state: NSControl.StateValue? = nil + if #available(OSX 10.15, *) { + state = sender is NSSwitch ? (sender as! NSSwitch).state: nil + } else { + state = sender is NSButton ? (sender as! NSButton).state: nil + } + self.colorState = state! == .on ? true : false + self.store?.pointee.set(key: "\(self.title)_\(self.type.rawValue)_color", value: self.colorState) + self.display() + } +} diff --git a/ModuleKit/Widgets/Mini.swift b/ModuleKit/Widgets/Mini.swift new file mode 100644 index 00000000..d8b72535 --- /dev/null +++ b/ModuleKit/Widgets/Mini.swift @@ -0,0 +1,168 @@ +// +// Mini.swift +// ModuleKit +// +// Created by Serhiy Mytrovtsiy on 10/04/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import StatsKit + +public class Mini: Widget { + private var valueView: NSTextField = NSTextField() + private var labelView: NSTextField = NSTextField() + + public var colorState: Bool = false + public var labelState: Bool = true + + private let onlyValueWidth: CGFloat = 38 + private var value: Double = 0 + private let store: UnsafePointer? + + public init(preview: Bool, title: String, config: NSDictionary?, store: UnsafePointer?) { + var widgetTitle: String = title + self.store = store + if config != nil { + var configuration = config! + + if preview { + if let previewConfig = config!["Preview"] as? NSDictionary { + configuration = previewConfig + if let value = configuration["Value"] as? String { + self.value = Double(value) ?? 0.38 + } else { + self.value = 0.38 + } + } else { + self.value = 0.38 + } + } + + if let titleFromConfig = configuration["Title"] as? String { + widgetTitle = titleFromConfig + } + if let label = configuration["Label"] as? Bool { + self.labelState = label + } + if let color = configuration["Color"] as? Bool { + self.colorState = color + } + } + super.init(frame: CGRect(x: 0, y: Constants.Widget.margin, width: Constants.Widget.width, height: Constants.Widget.height - (2*Constants.Widget.margin))) + self.title = widgetTitle + self.type = .mini + self.preview = preview + self.canDrawConcurrently = true + + if self.store != nil { + self.colorState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_color", defaultValue: self.colorState) + self.labelState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_label", defaultValue: self.labelState) + } + } + + public override func draw(_ dirtyRect: NSRect) { + super.draw(dirtyRect) + + var width: CGFloat = onlyValueWidth + let x: CGFloat = Constants.Widget.margin + var valueSize: CGFloat = 13 + var y: CGFloat = (Constants.Widget.height-valueSize)/2 + let style = NSMutableParagraphStyle() + style.alignment = .center + + if self.labelState { + let stringAttributes = [ + NSAttributedString.Key.font: NSFont.systemFont(ofSize: 7, weight: .light), + NSAttributedString.Key.foregroundColor: isDarkMode ? NSColor.white : NSColor.textColor, + NSAttributedString.Key.paragraphStyle: NSMutableParagraphStyle() + ] + let rect = CGRect(x: x, y: 12, width: 20, height: 7) + let str = NSAttributedString.init(string: self.title, attributes: stringAttributes) + str.draw(with: rect) + + y = 1 + valueSize = 11 + width = Constants.Widget.width + style.alignment = .left + } + + let stringAttributes = [ + NSAttributedString.Key.font: NSFont.systemFont(ofSize: valueSize, weight: .regular), + NSAttributedString.Key.foregroundColor: isDarkMode ? NSColor.white : NSColor.textColor, + NSAttributedString.Key.paragraphStyle: style + ] + let rect = CGRect(x: x, y: y, width: width - (Constants.Widget.margin*2), height: valueSize) + let str = NSAttributedString.init(string: "\(Int(self.value.rounded(toPlaces: 2) * 100))%", attributes: stringAttributes) + str.draw(with: rect) + + self.setWidth(width) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func settings(superview: NSView) { + let height: CGFloat = 60 + (Constants.Settings.margin*3) + let rowHeight: CGFloat = 30 + superview.setFrameSize(NSSize(width: superview.frame.width, height: height)) + + let view: NSView = NSView(frame: NSRect(x: Constants.Settings.margin, y: Constants.Settings.margin, width: superview.frame.width - (Constants.Settings.margin*2), height: superview.frame.height - (Constants.Settings.margin*2))) + + view.addSubview(ToggleTitleRow( + frame: NSRect(x: 0, y: rowHeight + Constants.Settings.margin, width: view.frame.width, height: rowHeight), + title: "Label", + action: #selector(toggleLabel), + state: self.labelState + )) + + view.addSubview(ToggleTitleRow( + frame: NSRect(x: 0, y: 0, width: view.frame.width, height: rowHeight), + title: "Colorize", + action: #selector(toggleColor), + state: self.colorState + )) + + superview.addSubview(view) + } + + @objc private func toggleColor(_ sender: NSControl) { + var state: NSControl.StateValue? = nil + if #available(OSX 10.15, *) { + state = sender is NSSwitch ? (sender as! NSSwitch).state: nil + } else { + state = sender is NSButton ? (sender as! NSButton).state: nil + } + self.colorState = state! == .on ? true : false + self.valueView.textColor = value.textUsageColor(color: self.colorState) + self.store?.pointee.set(key: "\(self.title)_\(self.type.rawValue)_color", value: self.colorState) + self.display() + } + + @objc private func toggleLabel(_ sender: NSControl) { + var state: NSControl.StateValue? = nil + if #available(OSX 10.15, *) { + state = sender is NSSwitch ? (sender as! NSSwitch).state: nil + } else { + state = sender is NSButton ? (sender as! NSButton).state: nil + } + self.labelState = state! == .on ? true : false + self.store?.pointee.set(key: "\(self.title)_\(self.type.rawValue)_label", value: self.labelState) + self.display() + } + + public func setValue(_ value: Double, sufix: String) { + if value == self.value { + return + } + + self.value = value + DispatchQueue.main.async(execute: { + self.display() + }) + } +} diff --git a/ModuleKit/Widgets/Network.swift b/ModuleKit/Widgets/Network.swift new file mode 100644 index 00000000..8ab36bf5 --- /dev/null +++ b/ModuleKit/Widgets/Network.swift @@ -0,0 +1,251 @@ +// +// Network.swift +// ModuleKit +// +// Created by Serhiy Mytrovtsiy on 24/05/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import StatsKit + +public enum network_icon_t: String { + case no = "" + case dot = "Dots" + case arrow = "Arrows" + case char = "Character" +} +extension network_icon_t: CaseIterable {} + +public class NetworkWidget: Widget { + private var icon: network_icon_t = .dot + private var valueState: Bool = true + + private var uploadField: NSTextField? = nil + private var downloadField: NSTextField? = nil + + private var uploadValue: Int64 = 0 + private var downloadValue: Int64 = 0 + + private let store: UnsafePointer? + private var width: CGFloat = 52 + + public init(preview: Bool, title: String, config: NSDictionary?, store: UnsafePointer?) { + let widgetTitle: String = title + self.store = store + super.init(frame: CGRect(x: 0, y: Constants.Widget.margin, width: width, height: Constants.Widget.height - (2*Constants.Widget.margin))) + self.title = widgetTitle + self.type = .network + self.preview = preview + self.canDrawConcurrently = true + + if self.store != nil { + self.valueState = store!.pointee.bool(key: "\(self.title)_\(self.type.rawValue)_value", defaultValue: self.valueState) + self.icon = network_icon_t(rawValue: store!.pointee.string(key: "\(self.title)_\(self.type.rawValue)_icon", defaultValue: self.icon.rawValue)) ?? self.icon + } + + if preview { + self.downloadValue = 8947141 + self.uploadValue = 478678 + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func draw(_ dirtyRect: NSRect) { +// guard let ctx = NSGraphicsContext.current?.cgContext else { return } + super.draw(dirtyRect) + + var width: CGFloat = 10 + var x: CGFloat = 10 + + switch self.icon { + case .dot: + self.drawDots(dirtyRect) + case .arrow: + self.drawArrows(dirtyRect) + case .char: + self.drawChars(dirtyRect) + default: + x = 0 + width = 0 + break + } + + if self.valueState { + let rowWidth: CGFloat = 42 + let rowHeight: CGFloat = self.frame.height / 2 + let style = NSMutableParagraphStyle() + style.alignment = .right + let stringAttributes = [ + NSAttributedString.Key.font: NSFont.systemFont(ofSize: 9, weight: .light), + NSAttributedString.Key.foregroundColor: NSColor.textColor, + NSAttributedString.Key.paragraphStyle: style + ] + + var rect = CGRect(x: Constants.Widget.margin + x, y: 1, width: rowWidth - (Constants.Widget.margin*2), height: rowHeight) + let download = NSAttributedString.init(string: Units(bytes: self.downloadValue).getReadableSpeed(), attributes: stringAttributes) + download.draw(with: rect) + + rect = CGRect(x: Constants.Widget.margin + x, y: rect.height+1, width: rowWidth - (Constants.Widget.margin*2), height: rowHeight) + let upload = NSAttributedString.init(string: Units(bytes: self.uploadValue).getReadableSpeed(), attributes: stringAttributes) + upload.draw(with: rect) + + width += rowWidth + } + + if width == 0 { + width = 1 + } + self.setWidth(width) + } + + private func drawDots(_ dirtyRect: NSRect) { + let rowHeight: CGFloat = self.frame.height / 2 + let size: CGFloat = 6 + let y: CGFloat = (rowHeight-size)/2 + + var downloadCircle = NSBezierPath() + downloadCircle = NSBezierPath(ovalIn: CGRect(x: Constants.Widget.margin, y: y-0.2, width: size, height: size)) + if self.downloadValue >= 1_024 { + NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 0.8).setFill() + } else { + NSColor.labelColor.setFill() + } + downloadCircle.fill() + + var uploadCircle = NSBezierPath() + uploadCircle = NSBezierPath(ovalIn: CGRect(x: Constants.Widget.margin, y: 10.5, width: size, height: size)) + if self.uploadValue >= 1_024 { + NSColor.red.setFill() + } else { + NSColor.labelColor.setFill() + } + uploadCircle.fill() + } + + private func drawArrows(_ dirtyRect: NSRect) { + let arrowAngle = CGFloat(Double.pi / 5) + let pointerLineLength: CGFloat = 3.5 + let workingHeight: CGFloat = (self.frame.size.height - (Constants.Widget.margin * 2)) + let height: CGFloat = ((workingHeight - Constants.Widget.margin) / 2) + + let downloadArrow = NSBezierPath() + let downloadStart = CGPoint(x: Constants.Widget.margin + (pointerLineLength/2), y: height + Constants.Widget.margin) + let downloadEnd = CGPoint(x: Constants.Widget.margin + (pointerLineLength/2), y: Constants.Widget.margin) + downloadArrow.addArrow(start: downloadStart, end: downloadEnd, pointerLineLength: pointerLineLength, arrowAngle: arrowAngle) + + if self.downloadValue >= 1_024 { + NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 0.8).set() + } else { + NSColor.labelColor.set() + } + downloadArrow.lineWidth = 1 + downloadArrow.stroke() + downloadArrow.close() + + let uploadArrow = NSBezierPath() + let uploadStart = CGPoint(x: Constants.Widget.margin + (pointerLineLength/2), y: height + (Constants.Widget.margin * 2)) + let uploadEnd = CGPoint(x: Constants.Widget.margin + (pointerLineLength/2), y: (Constants.Widget.margin * 2) + (height * 2)) + uploadArrow.addArrow(start: uploadStart, end: uploadEnd, pointerLineLength: pointerLineLength, arrowAngle: arrowAngle) + + if self.uploadValue >= 1_024 { + NSColor.red.set() + } else { + NSColor.labelColor.set() + } + uploadArrow.lineWidth = 1 + uploadArrow.stroke() + uploadArrow.close() + } + + private func drawChars(_ dirtyRect: NSRect) { + let rowHeight: CGFloat = self.frame.height / 2 + + let downloadAttributes = [ + NSAttributedString.Key.font: NSFont.systemFont(ofSize: 9, weight: .regular), + NSAttributedString.Key.foregroundColor: downloadValue >= 1_024 ? NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 0.8) : NSColor.labelColor, + NSAttributedString.Key.paragraphStyle: NSMutableParagraphStyle() + ] + var rect = CGRect(x: Constants.Widget.margin, y: 1, width: 8, height: rowHeight) + var str = NSAttributedString.init(string: "D", attributes: downloadAttributes) + str.draw(with: rect) + + let uploadAttributes = [ + NSAttributedString.Key.font: NSFont.systemFont(ofSize: 9, weight: .regular), + NSAttributedString.Key.foregroundColor: uploadValue >= 1_024 ? NSColor.red : NSColor.labelColor, + NSAttributedString.Key.paragraphStyle: NSMutableParagraphStyle() + ] + rect = CGRect(x: Constants.Widget.margin, y: rect.height+1, width: 8, height: rowHeight) + str = NSAttributedString.init(string: "U", attributes: uploadAttributes) + str.draw(with: rect) + } + + public override func settings(superview: NSView) { + let height: CGFloat = 60 + (Constants.Settings.margin*3) + let rowHeight: CGFloat = 30 + superview.setFrameSize(NSSize(width: superview.frame.width, height: height)) + + let view: NSView = NSView(frame: NSRect(x: Constants.Settings.margin, y: Constants.Settings.margin, width: superview.frame.width - (Constants.Settings.margin*2), height: superview.frame.height - (Constants.Settings.margin*2))) + + view.addSubview(SelectTitleRow( + frame: NSRect(x: 0, y: rowHeight + Constants.Settings.margin, width: view.frame.width, height: rowHeight), + title: "Pictogram", + action: #selector(toggleIcon), + items: network_icon_t.allCases.map{ return $0.rawValue }, + selected: self.icon.rawValue + )) + + view.addSubview(ToggleTitleRow( + frame: NSRect(x: 0, y: 0, width: view.frame.width, height: rowHeight), + title: "Value", + action: #selector(toggleValue), + state: self.valueState + )) + + superview.addSubview(view) + } + + @objc private func toggleValue(_ sender: NSControl) { + var state: NSControl.StateValue? = nil + if #available(OSX 10.15, *) { + state = sender is NSSwitch ? (sender as! NSSwitch).state: nil + } else { + state = sender is NSButton ? (sender as! NSButton).state: nil + } + self.valueState = state! == .on ? true : false + self.store?.pointee.set(key: "\(self.title)_\(self.type.rawValue)_value", value: self.valueState) + self.display() + } + + @objc private func toggleIcon(_ sender: NSMenuItem) { + let newIcon: network_icon_t = network_icon_t(rawValue: sender.title) ?? .no + self.icon = newIcon + self.store?.pointee.set(key: "\(self.title)_\(self.type.rawValue)_icon", value: self.icon.rawValue) + self.display() + } + + public func setValue(upload: Int64, download: Int64) { + var updated: Bool = false + + if self.downloadValue != download { + self.downloadValue = download + updated = true + } + if self.uploadValue != upload { + self.uploadValue = upload + updated = true + } + + if updated { + DispatchQueue.main.async(execute: { + self.display() + }) + } + } +} diff --git a/ModuleKit/module.swift b/ModuleKit/module.swift new file mode 100644 index 00000000..85114264 --- /dev/null +++ b/ModuleKit/module.swift @@ -0,0 +1,287 @@ +// +// module.swift +// ModuleKit +// +// Created by Serhiy Mytrovtsiy on 09/04/2020. +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import os.log +import StatsKit + +public protocol Module_p { + var available: Bool { get } + var enabled: Bool { get } + + var widget: Widget_p? { get } + var settings: Settings_p? { get } + + func load() + func terminate() +} + +public struct module_c { + public var name: String = "" + public var icon: NSImage? = nil + + var defaultState: Bool = false + var defaultWidget: widget_t = .unknown + var availableWidgets: [widget_t] = [] + + var widgetsConfig: NSDictionary = NSDictionary() + + init(in path: String) { + let dict: NSDictionary = NSDictionary(contentsOfFile: path)! + + if let name = dict["Name"] as? String { + self.name = name + } + if let state = dict["State"] as? Bool { + self.defaultState = state + } + + if let widgetsDict = dict["Widgets"] as? NSDictionary { + self.widgetsConfig = widgetsDict + for widgetName in widgetsDict.allKeys { + if let widget = widget_t(rawValue: widgetName as! String) { + self.availableWidgets.append(widget) + + let widgetDict = widgetsDict[widgetName as! String] as! NSDictionary + if widgetDict["Default"] as! Bool { + self.defaultWidget = widget + } + } + } + } + } +} + +open class Module: Module_p { + public var config: module_c + + public var available: Bool = false + public var enabled: Bool = false + + public var widget: Widget_p? = nil + public var settings: Settings_p? = nil + + private var settingsView: Settings_v? = nil + private var popup: NSWindow = NSWindow() + + private let log: OSLog + private var store: UnsafePointer? = nil + private var readers: [Reader_p] = [] + private var menuBarItem: NSStatusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength) + private var activeWidget: widget_t { + get { + let widgetStr = self.store?.pointee.string(key: "\(self.config.name)_widget", defaultValue: self.config.defaultWidget.rawValue) + return widget_t.allCases.first{ $0.rawValue == widgetStr } ?? widget_t.unknown + } + set {} + } + private var ready: Bool = false + private var widgetLoaded: Bool = false + + public init(store: UnsafePointer?, popup: NSView?, settings: Settings_v?) { + self.config = module_c(in: Bundle(for: type(of: self)).path(forResource: "config", ofType: "plist")!) + + self.log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: self.config.name) + self.store = store + self.settingsView = settings + self.available = self.isAvailable() + self.enabled = self.store?.pointee.bool(key: "\(self.config.name)_state", defaultValue: self.config.defaultState) ?? false + self.menuBarItem.isVisible = self.enabled + self.menuBarItem.autosaveName = self.config.name + + NotificationCenter.default.addObserver(self, selector: #selector(listenForWidgetSwitch), name: .switchWidget, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(listenForMouseDownInSettings), name: .clickInSettings, object: nil) + + if self.config.widgetsConfig.count != 0 { + self.setWidget() + } else { + os_log(.debug, log: log, "Module started without widget") + } + + self.settings = Settings(config: &self.config, enabled: self.enabled, activeWidget: self.widget, moduleSettings: { [weak self] (_ superview: NSView) in + if self != nil && self?.settingsView != nil { + self!.settingsView!.load(rect: superview.frame, widget: self!.activeWidget) + superview.setFrameSize(NSSize(width: superview.frame.width, height: self!.settingsView!.frame.height)) + superview.addSubview(self!.settingsView!) + } + }) + self.settings?.toggleCallback = { [weak self] in + self?.toggleEnabled() + } + + self.popup = PopupWindow(title: self.config.name, view: popup) + } + + // load function which call when app start + public func load() { + if self.enabled && self.widget != nil && self.ready { + DispatchQueue.main.async { + self.menuBarItem.button?.target = self + self.menuBarItem.button?.action = #selector(self.togglePopup) + self.menuBarItem.button?.sendAction(on: [.leftMouseDown, .rightMouseDown]) + + self.menuBarItem.length = self.widget!.frame.width + self.menuBarItem.button?.addSubview(self.widget!) + self.widgetLoaded = true + } + } + } + + // terminate function which call before app termination + public func terminate() { + self.willTerminate() + self.readers.forEach{ + $0.stop() + $0.terminate() + } + NSStatusBar.system.removeStatusItem(self.menuBarItem) + os_log(.debug, log: log, "Module terminated") + } + + // function to call before module terminate + open func willTerminate() {} + + // set module state to enabled + public func enable() { + self.enabled = true + self.store?.pointee.set(key: "\(self.config.name)_state", value: true) + self.readers.forEach{ $0.start() } + self.menuBarItem.isVisible = true + if self.menuBarItem.length < 0 { + self.load() + } + os_log(.debug, log: log, "Module enabled") + } + + // set module state to disabled + public func disable() { + self.enabled = false + self.store?.pointee.set(key: "\(self.config.name)_state", value: false) + self.readers.forEach{ $0.pause() } + self.menuBarItem.isVisible = false + self.popup.setIsVisible(false) + os_log(.debug, log: log, "Module disabled") + } + + // toggle module state + private func toggleEnabled() { + if self.enabled { + self.disable() + } else { + self.enable() + } + } + + // add reader to module. If module is enabled will fire a read function and start a reader + public func addReader(_ reader: Reader_p) { + if self.enabled { + reader.start() + } + self.readers.append(reader) + + os_log(.debug, log: log, "Successfully add reader %s", "\(reader.self)") + } + + // handler for reader, calls when main reader is ready, and return first value + public func readyHandler() { + os_log(.debug, log: log, "Reader report readiness") + self.ready = true + if !self.widgetLoaded { + self.load() + } + } + + // change menu item width + public func widgetWidthHandler(_ width: CGFloat) { + os_log(.debug, log: log, "Widget %s change width to %.2f", "\(type(of: self.widget!))", width) + self.menuBarItem.length = width + } + + // determine if module is available (can be overrided in module) + open func isAvailable() -> Bool { return true } + + // load and setup widget + private func setWidget() { + self.widget = LoadWidget(self.activeWidget, preview: false, title: self.config.name, config: self.config.widgetsConfig, store: self.store) + if self.widget == nil { + self.enabled = false + os_log(.error, log: log, "widget with type %s not found", "\(self.activeWidget)") + return + } + os_log(.debug, log: log, "Successfully initialize widget: %s", "\(String(describing: self.widget!))") + + self.widget?.widthHandler = { [weak self] value in + self?.widgetWidthHandler(value) + } + + self.readers.forEach{ $0.read() } + if let mainReader = self.readers.first(where: { !$0.optional }) { + self.widget?.setValues(mainReader.getHistory()) + } + + if self.ready { + self.menuBarItem.length = self.widget!.frame.width + self.menuBarItem.button?.subviews.forEach{ $0.removeFromSuperview() } + self.menuBarItem.button?.addSubview(self.widget!) + } + + self.settings?.setActiveWidget(self.widget) + } + + @objc private func togglePopup(_ sender: Any?) { + let openedWindows = NSApplication.shared.windows.filter{ $0 is NSPanel } + openedWindows.forEach{ $0.setIsVisible(false) } + + if self.popup.occlusionState.rawValue == 8192 { + NSApplication.shared.activate(ignoringOtherApps: true) + + let buttonOrigin = self.menuBarItem.button?.window?.frame.origin + let buttonCenter = (self.menuBarItem.button?.window?.frame.width)! / 2 + let windowCenter = self.popup.frame.width / 2 + + self.popup.contentView?.invalidateIntrinsicContentSize() + var x = buttonOrigin!.x - windowCenter + buttonCenter + let y = buttonOrigin!.y - self.popup.contentView!.intrinsicContentSize.height - 3 + + if let screen = NSScreen.main { + let width = screen.frame.size.width + + if x + self.popup.frame.width > width { + x = width - self.popup.frame.width + } + } + if buttonOrigin!.x - self.popup.frame.width < 0 { + x = 0 + } + + self.popup.setFrameOrigin(NSPoint(x: x, y: y)) + self.popup.setIsVisible(true) + } + } + + @objc private func listenForWidgetSwitch(_ notification: Notification) { + if let moduleName = notification.userInfo?["module"] as? String { + if let widgetName = notification.userInfo?["widget"] as? String { + if moduleName == self.config.name { + if let widgetType = widget_t.allCases.first(where: { $0.rawValue == widgetName }) { + self.activeWidget = widgetType + self.store?.pointee.set(key: "\(self.config.name)_widget", value: widgetType.rawValue) + self.setWidget() + os_log(.debug, log: log, "Widget is changed to: %s", "\(widgetName)") + } + } + } + } + } + + @objc private func listenForMouseDownInSettings(_ notification: Notification) { + if self.popup.isVisible { + self.popup.setIsVisible(false) + } + } +} diff --git a/ModuleKit/popup.swift b/ModuleKit/popup.swift new file mode 100644 index 00000000..3c1c2714 --- /dev/null +++ b/ModuleKit/popup.swift @@ -0,0 +1,220 @@ +// +// popup.swift +// ModuleKit +// +// Created by Serhiy Mytrovtsiy on 11/04/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import StatsKit + +internal class PopupWindow: NSPanel, NSWindowDelegate { + private let viewController: PopupViewController = PopupViewController() + + init(title: String, view: NSView?) { + self.viewController.setup(title: title, view: view) + + super.init( + contentRect: NSMakeRect(0, 0, self.viewController.view.frame.width, self.viewController.view.frame.height), + styleMask: [], + backing: .buffered, + defer: true + ) + + self.contentViewController = viewController + self.backingType = .buffered + self.isFloatingPanel = true + self.becomesKeyOnlyIfNeeded = true + self.styleMask = .borderless + self.animationBehavior = .default + self.collectionBehavior = .transient + self.backgroundColor = .clear + self.hasShadow = true + self.setIsVisible(false) + } +} + +internal class PopupViewController: NSViewController { + private var popup: PopupView + + public init() { + self.popup = PopupView(frame: NSRect(x: 0, y: 0, width: Constants.Popup.width + (Constants.Popup.margins * 2), height: Constants.Popup.height+Constants.Popup.headerHeight)) + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + self.view = self.popup + } + + override func viewDidLoad() { + super.viewDidLoad() + } + + override func viewWillAppear() { + self.popup.appear() + } + + override func viewWillDisappear() { + self.popup.disappear() + } + + public func setup(title: String, view: NSView?) { + self.title = title + self.popup.headerView?.titleView?.stringValue = title + self.popup.setView(view) + } +} + +internal class PopupView: NSView { + public var headerView: HeaderView? = nil + private var mainView: NSView? = nil + + override var intrinsicContentSize: CGSize { + var h: CGFloat = self.mainView?.subviews.first?.frame.height ?? 0 + if h != 0 { + h += Constants.Popup.margins*2 + } + return CGSize(width: self.frame.size.width, height: h + Constants.Popup.headerHeight) + } + + override init(frame: NSRect) { + super.init(frame: CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.width, height: frame.height)) + self.wantsLayer = true + self.canDrawConcurrently = true + self.layer!.cornerRadius = 3 + + self.headerView = HeaderView(frame: NSRect(x: 0, y: frame.height - Constants.Popup.headerHeight, width: frame.width, height: Constants.Popup.headerHeight)) + + let mainView: NSView = NSView(frame: NSRect(x: Constants.Popup.margins, y: Constants.Popup.margins, width: frame.width - (Constants.Popup.margins*2), height: 0)) + + self.addSubview(self.headerView!) + self.addSubview(mainView) + + self.mainView = mainView + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func updateLayer() { + if self.mainView!.subviews.count != 0 { + if self.mainView?.frame.height != self.mainView!.subviews.first!.frame.size.height { + self.setHeight(self.mainView!.subviews.first!.frame.size) + } + } + self.layer!.backgroundColor = self.isDarkMode ? NSColor.windowBackgroundColor.cgColor : NSColor.white.cgColor + } + + public func setView(_ view: NSView?) { + if view == nil { + self.setFrameSize(NSSize(width: Constants.Popup.width+(Constants.Popup.margins*2), height: Constants.Popup.headerHeight)) + self.headerView?.setFrameOrigin(NSPoint(x: 0, y: 0)) + return + } + + self.mainView?.addSubview(view!) + self.setHeight(view!.frame.size) + } + + private func setHeight(_ size: CGSize) { + DispatchQueue.main.async(execute: { + self.mainView?.setFrameSize(NSSize(width: self.mainView!.frame.width, height: size.height)) + self.setFrameSize(NSSize(width: size.width + (Constants.Popup.margins*2), height: size.height + Constants.Popup.headerHeight + Constants.Popup.margins*2)) + self.headerView?.setFrameOrigin(NSPoint(x: 0, y: self.frame.height - Constants.Popup.headerHeight)) + + var frame = self.window?.frame + frame?.size = self.frame.size + self.window?.setFrame(frame!, display: true) + }) + } + + internal func appear() { + self.display() + self.mainView?.subviews.first{ !($0 is HeaderView) }?.display() + } + internal func disappear() {} +} + +internal class HeaderView: NSView { + public var titleView: NSTextField? = nil + + private var settingsButton: NSButton? + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override init(frame: NSRect) { + super.init(frame: CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.width, height: frame.height)) + + let titleView = NSTextField(frame: NSMakeRect(frame.width/4, (frame.height - 18)/2, frame.width/2, 18)) + titleView.isEditable = false + titleView.isSelectable = false + titleView.isBezeled = false + titleView.wantsLayer = true + titleView.textColor = .labelColor + titleView.backgroundColor = .clear + titleView.canDrawSubviewsIntoLayer = true + titleView.alignment = .center + titleView.font = NSFont.systemFont(ofSize: 16, weight: .medium) + titleView.stringValue = "" + + self.titleView = titleView + self.addSubview(titleView) + + let button = NSButtonWithPadding() + button.frame = CGRect(x: frame.width - 38, y: 2, width: 30, height: 30) + button.verticalPadding = 14 + button.horizontalPadding = 14 + button.bezelStyle = .regularSquare + button.translatesAutoresizingMaskIntoConstraints = false + button.imageScaling = .scaleNone + button.image = Bundle(for: type(of: self)).image(forResource: "settings")! + button.contentTintColor = .lightGray + button.isBordered = false + button.action = #selector(openMenu) + button.target = self + + let trackingArea = NSTrackingArea(rect: button.frame, options: [NSTrackingArea.Options.activeAlways, NSTrackingArea.Options.mouseEnteredAndExited, NSTrackingArea.Options.activeInActiveApp], owner: self, userInfo: nil) + self.addTrackingArea(trackingArea) + + self.addSubview(button) + + self.settingsButton = button + } + + override func draw(_ dirtyRect: NSRect) { + super.draw(dirtyRect) + + NSColor.gridColor.set() + let line = NSBezierPath() + line.move(to: NSMakePoint(0, 0)) + line.line(to: NSMakePoint(self.frame.width, 0)) + line.lineWidth = 1 + line.stroke() + } + + override func mouseEntered(with: NSEvent) { + self.settingsButton!.contentTintColor = .gray + NSCursor.pointingHand.set() + } + + override func mouseExited(with: NSEvent) { + self.settingsButton!.contentTintColor = .lightGray + NSCursor.arrow.set() + } + + @objc func openMenu(_ sender: Any) { + self.window?.setIsVisible(false) + NotificationCenter.default.post(name: .toggleSettings, object: nil, userInfo: ["module": self.titleView?.stringValue ?? ""]) + } +} diff --git a/ModuleKit/reader.swift b/ModuleKit/reader.swift new file mode 100644 index 00000000..6fdd8d25 --- /dev/null +++ b/ModuleKit/reader.swift @@ -0,0 +1,129 @@ +// +// reader.swift +// ModuleKit +// +// Created by Serhiy Mytrovtsiy on 10/04/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import Repeat +import os.log + +public protocol value_t { + var widget_value: Double { get } +} + +public protocol Reader_p { + var optional: Bool { get } + + func setup() -> Void + func read() -> Void + func terminate() -> Void + + func getValue() -> T + func getHistory() -> [value_t] + + func start() -> Void + func pause() -> Void + func stop() -> Void +} + +public protocol ReaderInternal_p { + associatedtype T + + var value: T? { get } + func read() -> Void +} + +open class Reader: ReaderInternal_p { + public let log: OSLog + public var value: T? + public var interval: Int = 1000 + public var optional: Bool = false + + public var readyCallback: () -> Void = {} + public var callbackHandler: (T?) -> Void = {_ in } + + private var repeatTask: Repeater? + private var nilCallbackCounter: Int = 0 + private var ready: Bool = false + + private var history: [T]? = [] + + public init() { + self.log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "\(T.self)") + + self.setup() + + self.repeatTask = Repeater.init(interval: .milliseconds(self.interval), observer: { _ in + self.read() + }) + + os_log(.debug, log: self.log, "Successfully initialize reader") + } + + public func callback(_ value: T?) { + if !self.optional && !self.ready { + if self.value == nil && value != nil { + self.readyCallback() + } else if self.value == nil && value == nil { + if self.nilCallbackCounter > 5 { + os_log(.error, log: self.log, "Callback receive nil value more than 5 times. Please check this reader!") + self.stop() + return + } else { + os_log(.debug, log: self.log, "Restarting initial read") + self.nilCallbackCounter += 1 + self.read() + return + } + } else if self.nilCallbackCounter != 0 && value != nil { + self.nilCallbackCounter = 0 + } + } + + self.value = value + if !self.ready { + self.ready = true + os_log(.debug, log: self.log, "Reader is ready") + } + if value != nil { + if self.history?.count ?? 0 >= 300 { + self.history!.remove(at: 0) + } + self.history?.append(value!) + self.callbackHandler(value!) + } + } + + open func read() {} + open func setup() {} + open func terminate() {} + + open func start() { + self.read() + self.repeatTask!.start() + } + + open func pause() { + self.repeatTask!.pause() + } + + open func stop() { + self.repeatTask!.removeAllObservers(thenStop: true) + } +} + +extension Reader: Reader_p { + public func getValue() -> T { + return self.value as! T + } + + public func getHistory() -> [T] { + return self.history as! [T] + } +} diff --git a/ModuleKit/settings.swift b/ModuleKit/settings.swift new file mode 100644 index 00000000..650ae013 --- /dev/null +++ b/ModuleKit/settings.swift @@ -0,0 +1,268 @@ +// +// settings.swift +// ModuleKit +// +// Created by Serhiy Mytrovtsiy on 13/04/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa + +public protocol Settings_p: NSView { + var toggleCallback: () -> () { get set } + func setActiveWidget(_ widget: Widget_p?) +} + +public protocol Settings_v: NSView { + func load(rect: NSRect, widget: widget_t) +} + +open class Settings: NSView, Settings_p { + public var toggleCallback: () -> () = {} + + private let headerHeight: CGFloat = 42 + private var widgetSelectorHeight: CGFloat = Constants.Widget.height + (Constants.Settings.margin*2) + + private var widgetSelectorView: NSView? = nil + private var widgetSettingsView: NSView? = nil + private var moduleSettingsView: NSView? = nil + + private var config: UnsafePointer + private var activeWidget: Widget_p? + + private var moduleSettings: (_ superview: NSView) -> () + + init(config: UnsafePointer, enabled: Bool, activeWidget: Widget_p?, moduleSettings: @escaping (_ superview: NSView) -> ()) { + self.config = config + self.activeWidget = activeWidget + self.moduleSettings = moduleSettings + super.init(frame: NSRect(x: 0, y: 0, width: Constants.Settings.width, height: Constants.Settings.height)) + self.wantsLayer = true + self.layer?.backgroundColor = NSColor.windowBackgroundColor.cgColor + + addHeader(state: enabled) + addWidgetSelector() + addWidgetSettings() + addModuleSettings() + } + + private func addModuleSettings() { + let y: CGFloat = self.frame.height - headerHeight - widgetSelectorHeight - (self.widgetSettingsView?.frame.height ?? 0) + let view: NSView = NSView(frame: NSRect(x: Constants.Settings.margin, y: y - (Constants.Settings.margin*3), width: self.frame.width - (Constants.Settings.margin*2), height: 0)) + view.wantsLayer = true + view.layer?.backgroundColor = .white + view.layer!.cornerRadius = 3 + + self.appearance = NSAppearance(named: .aqua) + + self.moduleSettings(view) + + if view.frame.height != 0 { + view.setFrameOrigin(NSPoint(x: view.frame.origin.x, y: view.frame.origin.y - view.frame.height)) + self.addSubview(view) + self.moduleSettingsView = view + } + } + + private func addWidgetSettings() { + if self.activeWidget == nil { + return + } + + let view: NSView = NSView(frame: NSRect(x: Constants.Settings.margin, y: self.frame.height - headerHeight - widgetSelectorHeight - (Constants.Settings.margin*2), width: self.frame.width - (Constants.Settings.margin*2), height: 0)) + view.wantsLayer = true + view.layer?.backgroundColor = .white + view.layer!.cornerRadius = 3 + + self.activeWidget?.settings(superview: view) + + if view.frame.height != 0 { + view.setFrameOrigin(NSPoint(x: view.frame.origin.x, y: view.frame.origin.y - view.frame.height)) + self.addSubview(view) + self.widgetSettingsView = view + } + } + + private func addWidgetSelector() { + if self.config.pointee.availableWidgets.count == 0 { + self.widgetSelectorHeight = 0 + return + } + + let view: NSView = NSView(frame: NSRect(x: Constants.Settings.margin, y: self.frame.height - self.headerHeight - self.widgetSelectorHeight - Constants.Settings.margin, width: self.frame.width - (Constants.Settings.margin*2), height: self.widgetSelectorHeight)) + view.wantsLayer = true + view.layer?.backgroundColor = .white + view.layer!.cornerRadius = 3 + + var x: CGFloat = Constants.Settings.margin + for i in 0...self.config.pointee.availableWidgets.count - 1 { + let widgetType = self.config.pointee.availableWidgets[i] + if let widget = LoadWidget(widgetType, preview: true, title: self.config.pointee.name, config: self.config.pointee.widgetsConfig, store: nil) { + let preview = WidgetPreview( + frame: NSRect(x: x, y: Constants.Settings.margin, width: widget.frame.width, height: self.widgetSelectorHeight - (Constants.Settings.margin*2)), + title: self.config.pointee.name, + widget: widget, + state: self.activeWidget?.type == widgetType + ) + preview.widthCallback = { [weak self] in + self?.recalculateWidgetSelectorOptionsWidth() + } + view.addSubview(preview) + x += widget.frame.width + Constants.Settings.margin + } + } + + self.addSubview(view) + self.widgetSelectorView = view + } + + private func recalculateWidgetSelectorOptionsWidth() { + var x: CGFloat = Constants.Settings.margin + self.widgetSelectorView?.subviews.forEach({ (v: NSView) in + v.setFrameOrigin(NSPoint(x: x, y: v.frame.origin.y)) + x += v.frame.width + Constants.Settings.margin + }) + } + + private func addHeader(state: Bool) { + let view: NSView = NSView(frame: NSRect(x: 0, y: self.frame.height - self.headerHeight, width: self.frame.width, height: self.headerHeight)) + view.wantsLayer = true + + let titleView = NSTextField(frame: NSRect(x: Constants.Settings.margin, y: (view.frame.height-20)/2, width: self.frame.width - 65, height: 20)) + titleView.isEditable = false + titleView.isSelectable = false + titleView.isBezeled = false + titleView.wantsLayer = true + titleView.textColor = .black + titleView.backgroundColor = .clear + titleView.canDrawSubviewsIntoLayer = true + titleView.alignment = .natural + titleView.font = NSFont.systemFont(ofSize: 18, weight: .light) + titleView.stringValue = self.config.pointee.name + + var toggle: NSControl = NSControl() + if #available(OSX 10.15, *) { + let switchButton = NSSwitch(frame: NSRect(x: self.frame.width-55, y: 0, width: 50, height: view.frame.height)) + switchButton.state = state ? .on : .off + switchButton.action = #selector(self.toggleEnable) + switchButton.target = self + + toggle = switchButton + } else { + let button: NSButton = NSButton(frame: NSRect(x: self.frame.width-55, y: 0, width: 30, height: view.frame.height)) + button.setButtonType(.switch) + button.state = state ? .on : .off + button.title = "" + button.action = #selector(self.toggleEnable) + button.isBordered = false + button.isTransparent = true + + toggle = button + } + + let line: NSView = NSView(frame: NSRect(x: 0, y: 0, width: view.frame.width, height: 1)) + line.wantsLayer = true + line.layer?.backgroundColor = NSColor(hexString: "#d1d1d1").cgColor + + view.addSubview(titleView) + view.addSubview(toggle) + view.addSubview(line) + + self.addSubview(view) + } + + @objc func toggleEnable(_ sender: Any) { + self.toggleCallback() + } + + public func setActiveWidget(_ widget: Widget_p?) { + self.activeWidget = widget + + self.subviews.filter{ $0 == self.widgetSettingsView || $0 == self.moduleSettingsView }.forEach{ $0.removeFromSuperview() } + self.addWidgetSettings() + self.addModuleSettings() + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +class WidgetPreview: NSView { + private let type: widget_t + private var state: Bool + private let title: String + + public var widthCallback: () -> Void = {} + + public init(frame: NSRect, title: String, widget: Widget_p, state: Bool) { + self.type = widget.type + self.state = state + self.title = title + super.init(frame: frame) + + NotificationCenter.default.addObserver(self, selector: #selector(maybeActivate), name: .switchWidget, object: nil) + + self.wantsLayer = true + self.layer?.cornerRadius = 2 + self.layer?.borderColor = self.state ? NSColor.systemBlue.cgColor : NSColor(hexString: "#dddddd").cgColor + self.layer?.borderWidth = 1 + + widget.widthHandler = { [weak self] value in + self?.removeTrackingArea((self?.trackingAreas.first)!) + + let rect = NSRect(x: 0, y: 0, width: value, height: self!.frame.height) + let trackingArea = NSTrackingArea(rect: rect, options: [NSTrackingArea.Options.activeAlways, NSTrackingArea.Options.mouseEnteredAndExited, NSTrackingArea.Options.activeInActiveApp], owner: self, userInfo: ["menu": self!.type]) + self?.addTrackingArea(trackingArea) + + DispatchQueue.main.async(execute: { + self?.setFrameSize(NSSize(width: value, height: self?.frame.height ?? Constants.Widget.height)) + self?.widthCallback() + }) + } + self.addSubview(widget) + + let rect = NSRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height) + let trackingArea = NSTrackingArea(rect: rect, options: [NSTrackingArea.Options.activeAlways, NSTrackingArea.Options.mouseEnteredAndExited, NSTrackingArea.Options.activeInActiveApp], owner: self, userInfo: ["menu": self.type]) + self.addTrackingArea(trackingArea) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func mouseEntered(with: NSEvent) { + self.layer?.borderColor = NSColor.systemBlue.cgColor + NSCursor.pointingHand.set() + } + + override func mouseExited(with: NSEvent) { + self.layer?.borderColor = self.state ? NSColor.systemBlue.cgColor : NSColor.tertiaryLabelColor.cgColor + NSCursor.arrow.set() + } + + override func mouseDown(with: NSEvent) { + if !self.state { + NotificationCenter.default.post(name: .switchWidget, object: nil, userInfo: ["module": self.title, "widget": self.type.rawValue]) + } + } + + @objc private func maybeActivate(_ notification: Notification) { + if let moduleName = notification.userInfo?["module"] as? String { + if moduleName == self.title { + if let widgetName = notification.userInfo?["widget"] as? String { + if widgetName == self.type.rawValue { + self.layer?.borderColor = NSColor.systemBlue.cgColor + self.state = true + } else { + self.layer?.borderColor = NSColor.tertiaryLabelColor.cgColor + self.state = false + } + } + } + } + } +} diff --git a/ModuleKit/widget.swift b/ModuleKit/widget.swift new file mode 100644 index 00000000..0d8b5a46 --- /dev/null +++ b/ModuleKit/widget.swift @@ -0,0 +1,97 @@ +// +// widget.swift +// ModuleKit +// +// Created by Serhiy Mytrovtsiy on 10/04/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import StatsKit + +public enum widget_t: String { + case unknown = "" + case mini = "mini" + case lineChart = "line_chart" + case barChart = "bar_chart" + case network = "network" + case battery = "battery" +} +extension widget_t: CaseIterable {} + +public protocol Widget_p: NSView { + var title: String { get } + var preview: Bool { get } + var type: widget_t { get } + var widthHandler: ((CGFloat) -> Void)? { get set } + + func setValues(_ values: [value_t]) + func settings(superview: NSView) +} + +open class Widget: NSView, Widget_p { + public var widthHandler: ((CGFloat) -> Void)? = nil + public var title: String = "" + public var preview: Bool = false + public var type: widget_t = .unknown + + private var widthHandlerRetry: Int8 = 0 + + open override var intrinsicContentSize: CGSize { + return CGSize(width: self.frame.size.width, height: self.frame.size.height) + } + + public func setWidth(_ width: CGFloat) { + if self.frame.width == width || self.widthHandlerRetry >= 3 { + return + } + + if self.widthHandler == nil { + DispatchQueue.main.asyncAfter(deadline: .now() + .microseconds(10)) { + self.setWidth(width) + self.widthHandlerRetry += 1 + } + return + } + + DispatchQueue.main.async { + self.setFrameSize(NSSize(width: width, height: self.frame.size.height)) + self.invalidateIntrinsicContentSize() + self.display() + } + + self.widthHandler!(width) + } + + open func settings(superview: NSView) {} + open func setValues(_ values: [value_t]) {} +} + +func LoadWidget(_ type: widget_t, preview: Bool, title: String, config: NSDictionary?, store: UnsafePointer?) -> Widget_p? { + var widget: Widget_p? = nil + let widgetConfig: NSDictionary? = config?[type.rawValue] as? NSDictionary + + switch type { + case .mini: + widget = Mini(preview: preview, title: title, config: widgetConfig, store: store) + break + case .lineChart: + widget = LineChart(preview: preview, title: title, config: widgetConfig, store: store) + break + case .barChart: + widget = BarChart(preview: preview, title: title, config: widgetConfig, store: store) + break + case .network: + widget = NetworkWidget(preview: preview, title: title, config: widgetConfig, store: store) + break + case .battery: + widget = BatterykWidget(preview: preview, title: title, config: widgetConfig, store: store) + break + default: break + } + + return widget +} diff --git a/Modules/Battery/Info.plist b/Modules/Battery/Info.plist new file mode 100644 index 00000000..20202aa7 --- /dev/null +++ b/Modules/Battery/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSHumanReadableCopyright + Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. + + diff --git a/Modules/Battery/config.plist b/Modules/Battery/config.plist new file mode 100644 index 00000000..35e67ae5 --- /dev/null +++ b/Modules/Battery/config.plist @@ -0,0 +1,36 @@ + + + + + Name + Battery + State + + Widgets + + mini + + Default + + Label + + Title + BAT + Preview + + Label + + Title + BAT + Value + 0.72 + + + battery + + Default + + + + + diff --git a/Modules/Battery/main.swift b/Modules/Battery/main.swift new file mode 100644 index 00000000..41ac1b34 --- /dev/null +++ b/Modules/Battery/main.swift @@ -0,0 +1,86 @@ +// +// main.swift +// Battery +// +// Created by Serhiy Mytrovtsiy on 06/06/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import StatsKit +import ModuleKit +import IOKit.ps + +struct Usage: value_t { + var powerSource: String = "" + var state: String = "" + var isCharged: Bool = false + var level: 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 = true + + var timeToEmpty: Int = 0 + var timeToCharge: Int = 0 + + public var widget_value: Double { + get { + return self.level + } + } +} + +public class Battery: Module { + private var usageReader: UsageReader = UsageReader() + private let popupView: Popup = Popup() + + public init(_ store: UnsafePointer?) { + super.init( + store: store, + popup: self.popupView, + settings: nil + ) + + self.usageReader.readyCallback = { [unowned self] in + self.readyHandler() + } + self.usageReader.callbackHandler = { [unowned self] value in + self.usageCallback(value) + } + + self.addReader(self.usageReader) + } + + public override func isAvailable() -> Bool { + let snapshot = IOPSCopyPowerSourcesInfo().takeRetainedValue() + let sources = IOPSCopyPowerSourcesList(snapshot).takeRetainedValue() as Array + return sources.count > 0 + } + + private func usageCallback(_ value: Usage?) { + if value == nil { + return + } + + self.popupView.usageCallback(value!) + if let widget = self.widget as? Mini { + widget.setValue(value!.level, sufix: "%") + } + if let widget = self.widget as? BatterykWidget { + widget.setValue( + percentage: value?.level ?? 0, + isCharging: false, + time: (value?.timeToEmpty == 0 && value?.timeToCharge != 0 ? value?.timeToCharge : value?.timeToEmpty) ?? 0 + ) + } + } +} diff --git a/Modules/Battery/popup.swift b/Modules/Battery/popup.swift new file mode 100644 index 00000000..3b3ac99f --- /dev/null +++ b/Modules/Battery/popup.swift @@ -0,0 +1,229 @@ +// +// popup.swift +// Battery +// +// Created by Serhiy Mytrovtsiy on 06/06/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import ModuleKit +import StatsKit + +internal class Popup: NSView { + let dashboardHeight: CGFloat = 90 + let detailsHeight: CGFloat = 88 + let batteryHeight: CGFloat = 66 + let adapterHeight: CGFloat = 44 + + private var dashboardView: NSView? = nil + private var dashboardBatteryView: BatteryView? = nil + private var detailsView: NSView? = nil + private var batteryView: NSView? = nil + private var adapterView: NSView? = nil + + private var levelField: NSTextField? = nil + private var sourceField: NSTextField? = nil + private var timeLabelField: NSTextField? = nil + private var timeField: NSTextField? = nil + private var healthField: NSTextField? = nil + + private var amperageField: NSTextField? = nil + private var voltageField: NSTextField? = nil + private var temperatureField: NSTextField? = nil + + private var powerField: NSTextField? = nil + private var chargingStateField: NSTextField? = nil + + private var initialized: Bool = false + + public init() { + super.init(frame: NSRect( + x: 0, + y: 0, + width: Constants.Popup.width, + height: dashboardHeight + detailsHeight + batteryHeight + adapterHeight + (Constants.Popup.separatorHeight * 3) + )) + + self.initDashboard() + self.initDetails() + self.initBattery() + self.initAdapter() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func initDashboard() { + let view: NSView = NSView(frame: NSRect(x: 0, y: self.frame.height - self.dashboardHeight, width: self.frame.width, height: self.dashboardHeight)) + + let batteryView: BatteryView = BatteryView(frame: NSRect(x: Constants.Popup.margins, y: Constants.Popup.margins, width: view.frame.width - (Constants.Popup.margins*2), height: view.frame.height - (Constants.Popup.margins*2))) + view.addSubview(batteryView) + + self.addSubview(view) + self.dashboardView = view + self.dashboardBatteryView = batteryView + } + + private func initDetails() { + let y: CGFloat = self.dashboardView!.frame.origin.y - Constants.Popup.separatorHeight + let separator = SeparatorView("Details", origin: NSPoint(x: 0, y: y), width: self.frame.width) + self.addSubview(separator) + + let view: NSView = NSView(frame: NSRect(x: 0, y: separator.frame.origin.y - self.detailsHeight, width: self.frame.width, height: self.detailsHeight)) + + self.levelField = PopupRow(view, n: 3, title: "Level:", value: "") + self.sourceField = PopupRow(view, n: 2, title: "Source:", value: "") + let t = self.labelValue(view, n: 1, title: "Time:", value: "") + self.timeLabelField = t.0 + self.timeField = t.1 + self.healthField = PopupRow(view, n: 0, title: "Health:", value: "") + + self.addSubview(view) + self.detailsView = view + } + + private func labelValue(_ view: NSView, n: CGFloat, title: String, value: String) -> (NSTextField, NSTextField) { + let rowView: NSView = NSView(frame: NSRect(x: 0, y: 22*n, width: view.frame.width, height: 22)) + + let labelView: LabelField = LabelField(frame: NSRect(x: 0, y: (22-15)/2, width: view.frame.width/2, height: 15), title) + let valueView: ValueField = ValueField(frame: NSRect(x: view.frame.width/2, y: (22-16)/2, width: view.frame.width/2, height: 16), value) + + rowView.addSubview(labelView) + rowView.addSubview(valueView) + view.addSubview(rowView) + + return (labelView, valueView) + } + + private func initBattery() { + let y: CGFloat = self.detailsView!.frame.origin.y - Constants.Popup.separatorHeight + let separator = SeparatorView("Battery", origin: NSPoint(x: 0, y: y), width: self.frame.width) + self.addSubview(separator) + + let view: NSView = NSView(frame: NSRect(x: 0, y: separator.frame.origin.y - self.batteryHeight, width: self.frame.width, height: self.batteryHeight)) + + self.amperageField = PopupRow(view, n: 2, title: "Amperage:", value: "") + self.voltageField = PopupRow(view, n: 1, title: "Voltage:", value: "") + self.temperatureField = PopupRow(view, n: 0, title: "Temperatrure:", value: "") + + self.addSubview(view) + self.batteryView = view + } + + private func initAdapter() { + let y: CGFloat = self.batteryView!.frame.origin.y - Constants.Popup.separatorHeight + let separator = SeparatorView("Power adapter", origin: NSPoint(x: 0, y: y), width: self.frame.width) + self.addSubview(separator) + + let view: NSView = NSView(frame: NSRect(x: 0, y: separator.frame.origin.y - self.adapterHeight, width: self.frame.width, height: self.adapterHeight)) + + self.powerField = PopupRow(view, n: 1, title: "Power:", value: "") + self.chargingStateField = PopupRow(view, n: 0, title: "Is charging:", value: "") + + self.addSubview(view) + self.adapterView = view + } + + public func usageCallback(_ value: Usage) { + DispatchQueue.main.async(execute: { + if !self.window!.isVisible && self.initialized { + return + } + + self.dashboardBatteryView?.setValue(abs(value.level)) + + self.levelField?.stringValue = "\(Int(abs(value.level) * 100)) %" + self.sourceField?.stringValue = value.powerSource + self.timeField?.stringValue = "" + + if value.powerSource == "Battery Power" { + self.timeLabelField?.stringValue = "Time to discharge:" + if value.timeToEmpty != -1 && value.timeToEmpty != 0 { + self.timeField?.stringValue = Double(value.timeToEmpty*60).printSecondsToHoursMinutesSeconds() + } + } else { + self.timeLabelField?.stringValue = "Time to charge:" + if value.timeToCharge != -1 && value.timeToCharge != 0 { + self.timeField?.stringValue = Double(value.timeToCharge*60).printSecondsToHoursMinutesSeconds() + } + } + + if value.timeToEmpty == -1 || value.timeToEmpty == -1 { + self.timeField?.stringValue = "Calculating" + } + + if value.isCharged { + self.timeField?.stringValue = "Fully charged" + } + + self.healthField?.stringValue = "\(value.health) % (\(value.state))" + + self.amperageField?.stringValue = "\(abs(value.amperage)) mA" + self.voltageField?.stringValue = "\(value.voltage.roundTo(decimalPlaces: 2)) V" + self.temperatureField?.stringValue = "\(value.temperature) °C" + + self.powerField?.stringValue = value.powerSource == "Battery Power" ? "Not connected" : "\(value.ACwatts) W" + self.chargingStateField?.stringValue = value.level > 0 ? "Yes" : "No" + + self.initialized = true + }) + } +} + +private class BatteryView: NSView { + private var percentage: Double = 0 + + public override init(frame: NSRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func draw(_ dirtyRect: NSRect) { + super.draw(dirtyRect) + + let w: CGFloat = 130 + let h: CGFloat = 50 + let x: CGFloat = (dirtyRect.width - w)/2 + let y: CGFloat = (dirtyRect.size.height - h) / 2 + let radius: CGFloat = 3 + let batteryFrame = NSBezierPath(roundedRect: NSRect(x: x+1, y: y, width: w, height: h), xRadius: radius, yRadius: radius) + NSColor.black.set() + + let bPX: CGFloat = x+w+1 + let bPY: CGFloat = (dirtyRect.size.height / 2) - 4 + let batteryPoint = NSBezierPath(roundedRect: NSRect(x: bPX, y: bPY, width: 4, height: 8), xRadius: radius, yRadius: radius) + batteryPoint.lineWidth = 1.1 + batteryPoint.stroke() + batteryPoint.fill() + + batteryFrame.lineWidth = 1 + batteryFrame.stroke() + + let maxWidth = w-2 + let inner = NSBezierPath(roundedRect: NSRect(x: x+2, y: y+1, width: maxWidth * CGFloat(self.percentage), height: h-2), xRadius: radius, yRadius: radius) + self.percentage.batteryColor(color: true).set() + inner.lineWidth = 0 + inner.stroke() + inner.close() + inner.fill() + } + + public func setValue(_ value: Double) { + if self.percentage == value { + return + } + + self.percentage = value + DispatchQueue.main.async(execute: { + self.display() + }) + } +} diff --git a/Modules/Battery/readers.swift b/Modules/Battery/readers.swift new file mode 100644 index 00000000..70422f94 --- /dev/null +++ b/Modules/Battery/readers.swift @@ -0,0 +1,135 @@ +// +// readers.swift +// Battery +// +// Created by Serhiy Mytrovtsiy on 06/06/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import ModuleKit + +internal class UsageReader: Reader { + private var service: io_connect_t = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleSmartBattery")) + + private var source: CFRunLoopSource? + private var loop: CFRunLoop? + + private var usage: Usage = Usage() + + public override func start() { + let context = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()) + + self.source = IOPSNotificationCreateRunLoopSource({ (context) in + guard let ctx = context else { + return + } + + let watcher = Unmanaged.fromOpaque(ctx).takeUnretainedValue() + watcher.read() + }, context).takeRetainedValue() + + self.loop = RunLoop.current.getCFRunLoop() + CFRunLoopAddSource(self.loop, source, .defaultMode) + + self.read() + } + + public override func stop() { + guard let runLoop = loop, let source = source else { + return + } + + CFRunLoopRemoveSource(runLoop, source, .defaultMode) + } + + public override func read() { + let psInfo = IOPSCopyPowerSourcesInfo().takeRetainedValue() + let psList = IOPSCopyPowerSourcesList(psInfo).takeRetainedValue() as [CFTypeRef] + + if psList.count == 0 { + return + } + + for ps in psList { + if let list = IOPSGetPowerSourceDescription(psInfo, ps).takeUnretainedValue() as? Dictionary { + self.usage.powerSource = list[kIOPSPowerSourceStateKey] as? String ?? "AC Power" + self.usage.state = list[kIOPSBatteryHealthKey] as! String + self.usage.isCharged = list[kIOPSIsChargedKey] as? Bool ?? false + var cap = Double(list[kIOPSCurrentCapacityKey] as! Int) / 100 + + self.usage.timeToEmpty = Int(list[kIOPSTimeToEmptyKey] as! Int) + self.usage.timeToCharge = Int(list[kIOPSTimeToFullChargeKey] as! Int) + + self.usage.cycles = self.getIntValue("CycleCount" as CFString) ?? 0 + + let maxCapacity = self.getIntValue("MaxCapacity" as CFString) ?? 1 + let designCapacity = self.getIntValue("DesignCapacity" as CFString) ?? 1 + self.usage.health = (100 * maxCapacity) / designCapacity + + self.usage.amperage = self.getIntValue("Amperage" as CFString) ?? 0 + self.usage.voltage = self.getVoltage() ?? 0 + self.usage.temperature = self.getTemperature() ?? 0 + + var ACwatts: Int = 0 + if let ACDetails = IOPSCopyExternalPowerAdapterDetails() { + if let ACList = ACDetails.takeUnretainedValue() as? Dictionary { + guard let watts = ACList[kIOPSPowerAdapterWattsKey] else { + return + } + ACwatts = Int(watts as! Int) + } + } + self.usage.ACwatts = ACwatts + self.usage.ACstatus = self.getBoolValue("IsCharging" as CFString) ?? false + + if self.usage.powerSource == "Battery Power" { + cap = 0 - cap + } + self.usage.level = cap + + DispatchQueue.main.async(execute: { + self.callback(self.usage) + }) + } + } + } + + private func getBoolValue(_ forIdentifier: CFString) -> Bool? { + if let value = IORegistryEntryCreateCFProperty(self.service, forIdentifier, kCFAllocatorDefault, 0) { + return value.takeRetainedValue() as? Bool + } + return nil + } + + private func getIntValue(_ identifier: CFString) -> Int? { + if let value = IORegistryEntryCreateCFProperty(self.service, identifier, kCFAllocatorDefault, 0) { + return value.takeRetainedValue() as? Int + } + return nil + } + + private func getDoubleValue(_ identifier: CFString) -> Double? { + if let value = IORegistryEntryCreateCFProperty(self.service, identifier, kCFAllocatorDefault, 0) { + return value.takeRetainedValue() as? Double + } + return nil + } + + private func getVoltage() -> Double? { + if let value = self.getDoubleValue("Voltage" as CFString) { + return value / 1000.0 + } + return nil + } + + private func getTemperature() -> Double? { + if let value = IORegistryEntryCreateCFProperty(self.service, "Temperature" as CFString, kCFAllocatorDefault, 0) { + return value.takeRetainedValue() as! Double / 100.0 + } + return nil + } +} diff --git a/Modules/CPU/Info.plist b/Modules/CPU/Info.plist new file mode 100644 index 00000000..20202aa7 --- /dev/null +++ b/Modules/CPU/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSHumanReadableCopyright + Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. + + diff --git a/Modules/CPU/config.plist b/Modules/CPU/config.plist new file mode 100644 index 00000000..d74eb35b --- /dev/null +++ b/Modules/CPU/config.plist @@ -0,0 +1,38 @@ + + + + + Name + CPU + State + + Widgets + + mini + + Default + + Preview + + Value + 0.08 + + + line_chart + + Default + + + bar_chart + + Default + + Preview + + Value + 0.36,0.28 + + + + + diff --git a/Modules/CPU/main.swift b/Modules/CPU/main.swift new file mode 100644 index 00000000..c0a852ab --- /dev/null +++ b/Modules/CPU/main.swift @@ -0,0 +1,80 @@ +// +// main.swift +// CPU +// +// Created by Serhiy Mytrovtsiy on 09/04/2020. +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import ModuleKit +import StatsKit + +public struct Load: value_t { + var totalUsage: Double = 0 + var usagePerCore: [Double] = [] + + var systemLoad: Double = 0 + var userLoad: Double = 0 + var idleLoad: Double = 0 + + public var widget_value: Double { + get { + return self.totalUsage + } + } +} + +public struct TopProcess { + var pid: Int = 0 + var command: String = "" + var usage: Double = 0 +} + +public class CPU: Module { + private let popupView: Popup = Popup() + private var settingsView: Settings + + private var loadReader: LoadReader = LoadReader() + private let smc: UnsafePointer? + + public init(_ store: UnsafePointer, _ smc: UnsafePointer) { + self.smc = smc + self.settingsView = Settings("CPU", store: store) + self.loadReader.store = store + + super.init( + store: store, + popup: self.popupView, + settings: self.settingsView + ) + + self.loadReader.readyCallback = { [unowned self] in + self.readyHandler() + } + self.loadReader.callbackHandler = { [unowned self] value in + self.loadCallback(value) + } + + self.addReader(self.loadReader) + } + + private func loadCallback(_ value: Load?) { + if value == nil { + return + } + + let temperature = self.smc?.pointee.getValue("TC0F") ?? self.smc?.pointee.getValue("TC0P") ?? self.smc?.pointee.getValue("TC0H") + self.popupView.loadCallback(value!, tempValue: temperature) + + if let widget = self.widget as? Mini { + widget.setValue(value!.totalUsage, sufix: "%") + } + if let widget = self.widget as? LineChart { + widget.setValue(value!.totalUsage) + } + if let widget = self.widget as? BarChart { + widget.setValue(value!.usagePerCore) + } + } +} diff --git a/Modules/CPU/popup.swift b/Modules/CPU/popup.swift new file mode 100644 index 00000000..5335344f --- /dev/null +++ b/Modules/CPU/popup.swift @@ -0,0 +1,169 @@ +// +// popup.swift +// CPU +// +// Created by Serhiy Mytrovtsiy on 15/04/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import ModuleKit +import StatsKit + +internal class Popup: NSView { + private let dashboardHeight: CGFloat = 90 + private let detailsHeight: CGFloat = 66 // -26 + + private var loadField: NSTextField? = nil + private var temperatureField: NSTextField? = nil + + private var systemField: NSTextField? = nil + private var userField: NSTextField? = nil + private var idleField: NSTextField? = nil + + public var chart: LineChartView? = nil + private var ready: Bool = false + + public init() { + super.init(frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: dashboardHeight + Constants.Popup.separatorHeight + detailsHeight)) + + initDashboard() + initDetails() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func updateLayer() { + self.chart?.display() + } + + private func initDashboard() { + let rightWidth: CGFloat = 110 + let view: NSView = NSView(frame: NSRect(x: 0, y: self.frame.height - self.dashboardHeight, width: self.frame.width, height: self.dashboardHeight)) + + let leftPanel = NSView(frame: NSRect(x: 0, y: 0, width: view.frame.width - rightWidth - Constants.Popup.margins, height: view.frame.height)) + + self.chart = LineChartView(frame: NSRect(x: 4, y: 3, width: leftPanel.frame.width, height: leftPanel.frame.height), num: 120) + leftPanel.addSubview(self.chart!) + + let rightPanel: NSView = NSView(frame: NSRect(x: view.frame.width - rightWidth, y: 0, width: rightWidth, height: view.frame.height)) + self.loadField = addFirstRow(mView: rightPanel, y: ((rightPanel.frame.height - 16)/2)+9, title: "Load:", value: "") + self.temperatureField = addFirstRow(mView: rightPanel, y: ((rightPanel.frame.height - 16)/2)-9, title: "Temperature:", value: "") + + view.addSubview(leftPanel) + view.addSubview(rightPanel) + self.addSubview(view) + } + + private func initDetails() { + let y: CGFloat = self.frame.height - self.dashboardHeight - Constants.Popup.separatorHeight + let separator = SeparatorView("Details", origin: NSPoint(x: 0, y: y), width: self.frame.width) + self.addSubview(separator) + + let view: NSView = NSView(frame: NSRect(x: 0, y: separator.frame.origin.y - self.detailsHeight, width: self.frame.width, height: self.detailsHeight)) + + self.systemField = PopupRow(view, n: 2, title: "System:", value: "") + self.userField = PopupRow(view, n: 1, title: "User:", value: "") + self.idleField = PopupRow(view, n: 0, title: "Idle:", value: "") + + self.addSubview(view) + } + + private func addFirstRow(mView: NSView, y: CGFloat, title: String, value: String) -> NSTextField { + let rowView: NSView = NSView(frame: NSRect(x: 0, y: y, width: mView.frame.width, height: 16)) + + let labelWidth = title.widthOfString(usingFont: .systemFont(ofSize: 10, weight: .light)) + 4 + let labelView: NSTextField = TextView(frame: NSRect(x: 0, y: 1.5, width: labelWidth, height: 13)) + labelView.stringValue = title + labelView.alignment = .natural + labelView.font = NSFont.systemFont(ofSize: 10, weight: .light) + + let valueView: NSTextField = TextView(frame: NSRect(x: labelWidth, y: 1, width: mView.frame.width - labelWidth, height: 14)) + valueView.stringValue = value + valueView.alignment = .right + valueView.font = NSFont.systemFont(ofSize: 11, weight: .medium) + + rowView.addSubview(labelView) + rowView.addSubview(valueView) + mView.addSubview(rowView) + + return valueView + } + + public func loadCallback(_ value: Load, tempValue: Double?) { + var temperature: String = "Unknown" + + DispatchQueue.main.async(execute: { + if self.window!.isVisible || !self.ready { + if tempValue != nil { + let formatter = MeasurementFormatter() + let measurement = Measurement(value: tempValue!.rounded(toPlaces: 0), unit: UnitTemperature.celsius) + temperature = formatter.string(from: measurement) + } + + self.temperatureField?.stringValue = temperature + + self.systemField?.stringValue = "\(Int(value.systemLoad.rounded(toPlaces: 2) * 100)) %" + self.userField?.stringValue = "\(Int(value.userLoad.rounded(toPlaces: 2) * 100)) %" + self.idleField?.stringValue = "\(Int(value.idleLoad.rounded(toPlaces: 2) * 100)) %" + + let v = Int(value.totalUsage.rounded(toPlaces: 2) * 100) + self.loadField?.stringValue = "\(v) %" + self.ready = true + } + + self.chart?.addValue(value.totalUsage) + }) + } +} + +private class ProcessView: NSView { + public var width: CGFloat { + get { return 0 } + set { + self.setFrameSize(NSSize(width: newValue, height: self.frame.height)) + } + } + + public var label: String { + get { return "" } + set { + self.labelView?.stringValue = newValue + } + } + public var value: String { + get { return "" } + set { + self.valueView?.stringValue = newValue + } + } + + private var labelView: LabelField? = nil + private var valueView: ValueField? = nil + + init(_ n: CGFloat) { + super.init(frame: NSRect(x: 0, y: n*22, width: Constants.Popup.width, height: 16)) + + let rowView: NSView = NSView(frame: NSRect(x: Constants.Popup.margins, y: 0, width: self.frame.width - (Constants.Popup.margins*2), height: 16)) + + let labelView: LabelField = LabelField(frame: NSRect(x: 0, y: 0.5, width: 50, height: 15), "") + let valueView: ValueField = ValueField(frame: NSRect(x: 50, y: 0, width: rowView.frame.width - 50, height: 16), "") + + rowView.addSubview(labelView) + rowView.addSubview(valueView) + + self.labelView = labelView + self.valueView = valueView + + self.addSubview(rowView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Modules/CPU/readers.swift b/Modules/CPU/readers.swift new file mode 100644 index 00000000..58a8bdb0 --- /dev/null +++ b/Modules/CPU/readers.swift @@ -0,0 +1,152 @@ +// +// readers.swift +// CPU +// +// Created by Serhiy Mytrovtsiy on 10/04/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import StatsKit +import ModuleKit + +internal class LoadReader: Reader { + public var store: UnsafePointer? = nil + + 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 previousInfo = host_cpu_load_info() + + private var response: Load = Load() + private var numCPUsU: natural_t = 0 + private var usagePerCore: [Double] = [] + + public override func setup() { + self.interval = 1500 + + [CTL_HW, HW_NCPU].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 { + self.numCPUs = 1 + } + } + } + + public override func read() { + let result: kern_return_t = host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &self.numCPUsU, &self.cpuInfo, &self.numCpuInfo) + if result == KERN_SUCCESS { + self.CPUUsageLock.lock() + self.usagePerCore = [] + + for i in 0 ..< Int32(numCPUs) { + var inUse: Int32 + var total: Int32 + if let prevCpuInfo = self.prevCpuInfo { + inUse = self.cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_USER)] + - prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_USER)] + + self.cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_SYSTEM)] + - prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_SYSTEM)] + + self.cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_NICE)] + - prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_NICE)] + total = inUse + (self.cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_IDLE)] + - prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_IDLE)]) + } else { + inUse = self.cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_USER)] + + self.cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_SYSTEM)] + + self.cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_NICE)] + total = inUse + self.cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_IDLE)] + } + + if total != 0 { + self.usagePerCore.append(Double(inUse) / Double(total)) + } + } + self.CPUUsageLock.unlock() + + if self.store?.pointee.bool(key: "CPU_hyperhreading", defaultValue: false) ?? false { + self.response.usagePerCore = self.usagePerCore + } else { + var i = 0 + var a = 0 + + self.response.usagePerCore = [] + while i < Int(self.usagePerCore.count/2) { + a = i*2 + if self.usagePerCore.indices.contains(a) && self.usagePerCore.indices.contains(a+1) { + self.response.usagePerCore.append((Double(self.usagePerCore[a]) + Double(self.usagePerCore[a+1])) / 2) + } + i += 1 + } + } + + if let prevCpuInfo = self.prevCpuInfo { + let prevCpuInfoSize: size_t = MemoryLayout.stride * Int(self.numPrevCpuInfo) + vm_deallocate(mach_task_self_, vm_address_t(bitPattern: prevCpuInfo), vm_size_t(prevCpuInfoSize)) + } + + self.prevCpuInfo = self.cpuInfo + self.numPrevCpuInfo = self.numCpuInfo + + self.cpuInfo = nil + self.numCpuInfo = 0 + } else { + print("ERROR host_processor_info(): " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error")) + } + + let cpuInfo = hostCPULoadInfo() + if cpuInfo == nil { + self.callback(nil) + return + } + + let userDiff = Double(cpuInfo!.cpu_ticks.0 - self.previousInfo.cpu_ticks.0) + let sysDiff = Double(cpuInfo!.cpu_ticks.1 - self.previousInfo.cpu_ticks.1) + let idleDiff = Double(cpuInfo!.cpu_ticks.2 - self.previousInfo.cpu_ticks.2) + let niceDiff = Double(cpuInfo!.cpu_ticks.3 - self.previousInfo.cpu_ticks.3) + let totalTicks = sysDiff + userDiff + niceDiff + idleDiff + + let system = sysDiff / totalTicks + let user = userDiff / totalTicks + let idle = idleDiff / totalTicks + + if !system.isNaN { + self.response.systemLoad = system + } + if !user.isNaN { + self.response.userLoad = user + } + if !idle.isNaN { + self.response.idleLoad = idle + } + self.previousInfo = cpuInfo! + self.response.totalUsage = self.response.systemLoad + self.response.userLoad + + self.callback(self.response) + } + + 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: kern_return_t = 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/Modules/CPU/settings.swift b/Modules/CPU/settings.swift new file mode 100644 index 00000000..d4c0d0ab --- /dev/null +++ b/Modules/CPU/settings.swift @@ -0,0 +1,71 @@ +// +// Settings.swift +// CPU +// +// Created by Serhiy Mytrovtsiy on 18/04/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import StatsKit +import ModuleKit + +internal class Settings: NSView, Settings_v { + private var hyperthreadState: Bool = false + + private let title: String + private let store: UnsafePointer? + + public init(_ title: String, store: UnsafePointer?) { + self.title = title + self.store = store + super.init(frame: CGRect(x: Constants.Settings.margin, y: Constants.Settings.margin, width: 0, height: 0)) + self.wantsLayer = true + self.canDrawConcurrently = true + + if self.store != nil { + self.hyperthreadState = store!.pointee.bool(key: "\(self.title)_hyperhreading", defaultValue: self.hyperthreadState) + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func load(rect: NSRect, widget: widget_t) { + self.subviews.forEach{ $0.removeFromSuperview() } + + let rowHeight: CGFloat = 30 + var height: CGFloat = 0 + + if widget == .barChart { + self.addSubview(ToggleTitleRow( + frame: NSRect(x: 0, y: (rowHeight + Constants.Settings.margin) * 0, width: rect.width - (Constants.Settings.margin*2), height: rowHeight), + title: "Show hyper-threading cores", + action: #selector(toggleMultithreading), + state: self.hyperthreadState + )) + height += rowHeight + } + + if height != 0 { + height += (Constants.Settings.margin*2) + } + self.setFrameSize(NSSize(width: rect.width - (Constants.Settings.margin*2), height: height)) + } + + @objc func toggleMultithreading(_ sender: NSControl) { + var state: NSControl.StateValue? = nil + if #available(OSX 10.15, *) { + state = sender is NSSwitch ? (sender as! NSSwitch).state: nil + } else { + state = sender is NSButton ? (sender as! NSButton).state: nil + } + + self.hyperthreadState = state! == .on ? true : false + self.store?.pointee.set(key: "\(self.title)_hyperhreading", value: self.hyperthreadState) + } +} diff --git a/Modules/Disk/Info.plist b/Modules/Disk/Info.plist new file mode 100644 index 00000000..20202aa7 --- /dev/null +++ b/Modules/Disk/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSHumanReadableCopyright + Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. + + diff --git a/Modules/Disk/config.plist b/Modules/Disk/config.plist new file mode 100644 index 00000000..f356641c --- /dev/null +++ b/Modules/Disk/config.plist @@ -0,0 +1,51 @@ + + + + + Name + Disk + State + + Widgets + + mini + + Default + + Title + SSD + Preview + + Title + SSD + Value + 0.36 + + + bar_chart + + Default + + Title + SSD + Label + + Box + + Color + + Preview + + Label + + Box + + Color + + Value + 0.36 + + + + + diff --git a/Modules/Disk/main.swift b/Modules/Disk/main.swift new file mode 100644 index 00000000..30bdb50e --- /dev/null +++ b/Modules/Disk/main.swift @@ -0,0 +1,123 @@ +// +// main.swift +// Disk +// +// Created by Serhiy Mytrovtsiy on 07/05/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import StatsKit +import ModuleKit + +struct diskInfo { + var name: String = "" + var model: String = "" + var path: URL? + var connection: String = "" + var fileSystem: String = "" + + var totalSize: Int64 = 0 + var freeSize: Int64 = 0 + + var mediaBSDName: String = "" + var root: Bool = false +} + +struct DiskList: value_t { + var list: [diskInfo] = [] + + public var widget_value: Double { + get { + return 0 + } + } + + func getDiskByBSDName(_ name: String) -> diskInfo? { + if let idx = self.list.firstIndex(where: { $0.mediaBSDName == name }) { + return self.list[idx] + } + + return nil + } + + func getDiskByName(_ name: String) -> diskInfo? { + if let idx = self.list.firstIndex(where: { $0.name == name }) { + return self.list[idx] + } + + return nil + } + + func getRootDisk() -> diskInfo? { + if let idx = self.list.firstIndex(where: { $0.root }) { + return self.list[idx] + } + + return nil + } +} + +public class Disk: Module { + private let popupView: Popup = Popup() + private var capacityReader: CapacityReader = CapacityReader() + private var settingsView: Settings + private var selectedDisk: String = "" + + public init(_ store: UnsafePointer?) { + self.settingsView = Settings("Disk", store: store!) + + super.init( + store: store, + popup: self.popupView, + settings: self.settingsView + ) + self.selectedDisk = store!.pointee.string(key: "\(self.config.name)_disk", defaultValue: self.selectedDisk) + + self.capacityReader.readyCallback = { [unowned self] in + self.readyHandler() + } + self.capacityReader.callbackHandler = { [unowned self] value in + self.capacityCallback(value: value) + } + + self.settingsView.selectedDiskHandler = { [unowned self] value in + self.selectedDisk = value + self.capacityReader.read() + } + + self.addReader(self.capacityReader) + } + + private func capacityCallback(value: DiskList?) { + if value == nil { + return + } + self.popupView.usageCallback(value!) + self.settingsView.setList(value!) + + var d: diskInfo? = value!.getDiskByName(self.selectedDisk) + if d == nil { + d = value!.getRootDisk() + } + + if d == nil { + return + } + + let total = d!.totalSize + let free = d!.freeSize + let usedSpace = total - free + let percentage = Double(usedSpace) / Double(total) + + if let widget = self.widget as? Mini { + widget.setValue(percentage, sufix: "%") + } + if let widget = self.widget as? BarChart { + widget.setValue([percentage]) + } + } +} diff --git a/Modules/Disk/popup.swift b/Modules/Disk/popup.swift new file mode 100644 index 00000000..06419b59 --- /dev/null +++ b/Modules/Disk/popup.swift @@ -0,0 +1,176 @@ +// +// popup.swift +// Disk +// +// Created by Serhiy Mytrovtsiy on 11/05/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import ModuleKit +import StatsKit + +internal class Popup: NSView { + let diskFullHeight: CGFloat = 60 + var list: [String: DiskView] = [:] + + public init() { + super.init(frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: 0)) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + internal func usageCallback(_ value: DiskList) { + value.list.reversed().forEach { (d: diskInfo) in + if self.list[d.name] == nil { + DispatchQueue.main.async(execute: { + self.list[d.name] = DiskView( + NSRect(x: 0, y: (self.diskFullHeight + Constants.Popup.margins) * CGFloat(self.list.count), width: self.frame.width, height: self.diskFullHeight), + name: d.name, + size: d.totalSize, + free: d.freeSize, + path: d.path + ) + self.addSubview(self.list[d.name]!) + + self.setFrameSize(NSSize(width: self.frame.width, height: ((self.diskFullHeight + Constants.Popup.margins) * CGFloat(self.list.count)) - Constants.Popup.margins)) + }) + } else { + self.list[d.name]?.update(free: d.freeSize) + } + } + } +} + +internal class DiskView: NSView { + public let name: String + public let size: Int64 + private let uri: URL? + + private let nameHeight: CGFloat = 20 + private let legendHeight: CGFloat = 12 + private let barHeight: CGFloat = 10 + + private var legendField: NSTextField? = nil + private var percentageField: NSTextField? = nil + private var usedBarSpace: NSView? = nil + + private var mainView: NSView + + private var initialized: Bool = false + + public init(_ frame: NSRect, name: String, size: Int64, free: Int64, path: URL?) { + self.mainView = NSView(frame: NSRect(x: 5, y: 5, width: frame.width - 10, height: frame.height - 10)) + self.name = name + self.size = size + self.uri = path + super.init(frame: frame) + + self.wantsLayer = true + self.layer?.cornerRadius = 2 + + self.addName() + self.addHorizontalBar(free: free) + self.addLegend(free: free) + + self.addSubview(self.mainView) + + let rect: CGRect = CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height) + let trackingArea = NSTrackingArea(rect: rect, options: [NSTrackingArea.Options.activeAlways, NSTrackingArea.Options.mouseEnteredAndExited, NSTrackingArea.Options.activeInActiveApp], owner: self, userInfo: nil) + self.addTrackingArea(trackingArea) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func updateLayer() { + self.layer?.backgroundColor = isDarkMode ? NSColor(hexString: "#111111", alpha: 0.25).cgColor : NSColor(hexString: "#f5f5f5", alpha: 1).cgColor + } + + private func addName() { + let nameWidth = self.name.widthOfString(usingFont: .systemFont(ofSize: 13, weight: .light)) + 4 + let view: NSView = NSView(frame: NSRect(x: 0, y: self.mainView.frame.height - nameHeight, width: nameWidth, height: nameHeight)) + + let nameField: NSTextField = TextView(frame: NSRect(x: 0, y: 0, width: nameWidth, height: view.frame.height)) + nameField.stringValue = self.name + + view.addSubview(nameField) + self.mainView.addSubview(view) + } + + private func addLegend(free: Int64) { + let view: NSView = NSView(frame: NSRect(x: 0, y: 2, width: self.mainView.frame.width, height: self.legendHeight)) + + self.legendField = TextView(frame: NSRect(x: 0, y: 0, width: view.frame.width - 40, height: view.frame.height)) + self.legendField?.font = NSFont.systemFont(ofSize: 11, weight: .light) + self.legendField?.stringValue = "Used \(Units(bytes: (self.size - free)).getReadableMemory()) from \(Units(bytes: self.size).getReadableMemory())" + + self.percentageField = TextView(frame: NSRect(x: view.frame.width - 40, y: 0, width: 40, height: view.frame.height)) + self.percentageField?.font = NSFont.systemFont(ofSize: 11, weight: .regular) + self.percentageField?.alignment = .right + self.percentageField?.stringValue = "\(Int8((Double(self.size - free) / Double(self.size)) * 100))%" + + view.addSubview(self.legendField!) + view.addSubview(self.percentageField!) + self.mainView.addSubview(view) + } + + private func addHorizontalBar(free: Int64) { + let view: NSView = NSView(frame: NSRect(x: 1, y: self.mainView.frame.height - self.nameHeight - 11, width: self.mainView.frame.width - 2, height: self.barHeight)) + view.wantsLayer = true + view.layer?.backgroundColor = NSColor.white.cgColor + view.layer?.borderColor = NSColor.secondaryLabelColor.cgColor + view.layer?.borderWidth = 0.25 + view.layer?.cornerRadius = 3 + + let percentage = CGFloat(self.size - free) / CGFloat(self.size) + let width: CGFloat = (view.frame.width * percentage) / 1 + self.usedBarSpace = NSView(frame: NSRect(x: 0, y: 0, width: width, height: view.frame.height)) + self.usedBarSpace?.wantsLayer = true + self.usedBarSpace?.layer?.backgroundColor = NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 1).cgColor + + view.addSubview(self.usedBarSpace!) + self.mainView.addSubview(view) + } + + public func update(free: Int64) { + DispatchQueue.main.async(execute: { + if !self.window!.isVisible && self.initialized { + return + } + + if self.legendField != nil { + self.legendField?.stringValue = "Used \(Units(bytes: (self.size - free)).getReadableMemory()) from \(Units(bytes: self.size).getReadableMemory())" + self.percentageField?.stringValue = "\(Int8((Double(self.size - free) / Double(self.size)) * 100))%" + } + + if self.usedBarSpace != nil { + let percentage = CGFloat(self.size - free) / CGFloat(self.size) + let width: CGFloat = ((self.mainView.frame.width - 2) * percentage) / 1 + self.usedBarSpace?.setFrameSize(NSSize(width: width, height: self.usedBarSpace!.frame.height)) + } + + self.initialized = true + }) + } + + override func mouseEntered(with: NSEvent) { + NSCursor.pointingHand.set() + } + + override func mouseExited(with: NSEvent) { + NSCursor.arrow.set() + } + + override func mouseDown(with: NSEvent) { + if let uri = self.uri { + NSWorkspace.shared.openFile(uri.absoluteString, withApplication: "Finder") + } + } +} diff --git a/Stats/Modules/Disk/DiskCapacityReader.swift b/Modules/Disk/readers.swift similarity index 69% rename from Stats/Modules/Disk/DiskCapacityReader.swift rename to Modules/Disk/readers.swift index b5a821f3..7a77fb6d 100644 --- a/Stats/Modules/Disk/DiskCapacityReader.swift +++ b/Modules/Disk/readers.swift @@ -1,81 +1,25 @@ // -// DiskCapacityReader.swift -// Stats +// readers.swift +// Disk +// +// Created by Serhiy Mytrovtsiy on 07/05/2020. +// Using Swift 5.0. +// Running on macOS 10.15. // -// Created by Serhiy Mytrovtsiy on 14/01/2020. // Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. // import Cocoa +import ModuleKit -struct diskInfo { - var ID: String = ""; +internal class CapacityReader: Reader { + private var disks: DiskList = DiskList() - var name: String = ""; - var model: String = ""; - var path: URL?; - var connection: String = ""; - var fileSystem: String = ""; - - var totalSize: Int64 = 0; - var freeSize: Int64 = 0; - - var mediaBSDName: String = ""; - var root: Bool = false; -} - -struct disksList { - var list: [diskInfo] = [] - - func getDiskByBSDName(_ name: String) -> diskInfo? { - if let idx = self.list.firstIndex(where: { $0.mediaBSDName == name }) { - return self.list[idx] - } - - return nil + public override func setup() { + self.interval = 10000 } - func getDiskByName(_ name: String) -> diskInfo? { - if let idx = self.list.firstIndex(where: { $0.name == name }) { - return self.list[idx] - } - - return nil - } - - func getRootDisk() -> diskInfo? { - if let idx = self.list.firstIndex(where: { $0.root }) { - return self.list[idx] - } - - return nil - } -} - -class DiskCapacityReader: Reader { - public var name: String = "Capacity" - public var enabled: Bool = true - public var available: Bool = true - public var optional: Bool = false - public var initialized: Bool = false - public var callback: (disksList) -> Void = {_ in} - - private var disks: disksList = disksList() - - init(_ updater: @escaping (disksList) -> Void) { - self.callback = updater - - if self.available { - DispatchQueue.global(qos: .default).async { - self.read() - } - } - } - - public func read() { - if !self.enabled && self.initialized { return } - self.initialized = true - + public override func read() { let keys: [URLResourceKey] = [.volumeNameKey] let paths = FileManager.default.mountedVolumeURLs(includingResourceValuesForKeys: keys)! if let session = DASessionCreate(kCFAllocatorDefault) { @@ -103,13 +47,7 @@ class DiskCapacityReader: Reader { } } - DispatchQueue.main.async(execute: { - self.callback(self.disks) - }) - } - - public func toggleEnable(_ value: Bool) { - self.enabled = value + self.callback(self.disks) } private func getDisk(_ disk: DADisk) -> diskInfo? { @@ -159,7 +97,7 @@ class DiskCapacityReader: Reader { } } } - + if d.path != nil { d.freeSize = freeDiskSpaceInBytes(d.path!.absoluteString) } diff --git a/Modules/Disk/settings.swift b/Modules/Disk/settings.swift new file mode 100644 index 00000000..3bd7ee57 --- /dev/null +++ b/Modules/Disk/settings.swift @@ -0,0 +1,80 @@ +// +// settings.swift +// Disk +// +// Created by Serhiy Mytrovtsiy on 12/05/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import StatsKit +import ModuleKit + +internal class Settings: NSView, Settings_v { + public var selectedDiskHandler: (String) -> Void = {_ in } + + private let title: String + private let store: UnsafePointer + private var selectedDisk: String + private var button: NSPopUpButton? + + public init(_ title: String, store: UnsafePointer) { + self.title = title + self.store = store + self.selectedDisk = store.pointee.string(key: "\(self.title)_disk", defaultValue: "") + super.init(frame: CGRect(x: Constants.Settings.margin, y: Constants.Settings.margin, width: 0, height: 0)) + self.wantsLayer = true + self.canDrawConcurrently = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func load(rect: NSRect, widget: widget_t) { + self.subviews.forEach{ $0.removeFromSuperview() } + + self.addDiskSelector(rect: rect) + + self.setFrameSize(NSSize(width: rect.width - (Constants.Settings.margin*2), height: 30 + (Constants.Settings.margin*2))) + } + + private func addDiskSelector(rect: NSRect) { + let view: NSView = NSView(frame: NSRect(x: 0, y: 0, width: rect.width, height: 30)) + + let rowTitle: NSTextField = LabelField(frame: NSRect(x: 0, y: (view.frame.height - 16)/2, width: view.frame.width - 52, height: 17), "Disk to show") + rowTitle.font = NSFont.systemFont(ofSize: 13, weight: .light) + rowTitle.textColor = .labelColor + + self.button = NSPopUpButton(frame: NSRect(x: view.frame.width - 164, y: 0, width: 140, height: 30)) + self.button!.target = self + self.button?.action = #selector(self.handleSelection) + + view.addSubview(rowTitle) + view.addSubview(self.button!) + + self.addSubview(view) + } + + internal func setList(_ list: DiskList) { + let disks = list.list.map{ $0.name } + DispatchQueue.main.async(execute: { + if disks != self.button?.itemTitles { + self.button?.addItems(withTitles: disks) + if self.selectedDisk != "" { + self.button?.selectItem(withTitle: self.selectedDisk) + } + } + }) + } + + @objc func handleSelection(_ sender: NSPopUpButton) { + guard let item = sender.selectedItem else { return } + self.selectedDisk = item.title + self.store.pointee.set(key: "\(self.title)_disk", value: item.title) + self.selectedDiskHandler(item.title) + } +} diff --git a/Modules/Memory/Info.plist b/Modules/Memory/Info.plist new file mode 100644 index 00000000..20202aa7 --- /dev/null +++ b/Modules/Memory/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSHumanReadableCopyright + Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. + + diff --git a/Modules/Memory/config.plist b/Modules/Memory/config.plist new file mode 100644 index 00000000..1e275e1c --- /dev/null +++ b/Modules/Memory/config.plist @@ -0,0 +1,50 @@ + + + + + Name + RAM + State + + Widgets + + mini + + Default + + Preview + + Value + 0.58 + + + line_chart + + Default + + + bar_chart + + Default + + Label + + Box + + Color + + Preview + + Label + + Box + + Color + + Value + 0.48 + + + + + diff --git a/Modules/Memory/main.swift b/Modules/Memory/main.swift new file mode 100644 index 00000000..e0ad4f4b --- /dev/null +++ b/Modules/Memory/main.swift @@ -0,0 +1,71 @@ +// +// main.swift +// Memory +// +// Created by Serhiy Mytrovtsiy on 12/04/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import StatsKit +import ModuleKit + +public struct Usage: value_t { + var active: Double? + var inactive: Double? + var wired: Double? + var compressed: Double? + + var usage: Double? + var total: Double? + var used: Double? + var free: Double? + + public var widget_value: Double { + get { + return self.usage ?? 0 + } + } +} + +public class Memory: Module { + private let popupView: Popup = Popup() + private var usageReader: UsageReader = UsageReader() + + public init(_ store: UnsafePointer?) { + super.init( + store: store, + popup: self.popupView, + settings: nil + ) + + self.usageReader.readyCallback = { [unowned self] in + self.readyHandler() + } + self.usageReader.callbackHandler = { [unowned self] value in + self.loadCallback(value: value) + } + + self.addReader(self.usageReader) + } + + private func loadCallback(value: Usage?) { + if value == nil { + return + } + + self.popupView.loadCallback(value!) + if let widget = self.widget as? Mini { + widget.setValue(value!.usage ?? 0, sufix: "%") + } + if let widget = self.widget as? LineChart { + widget.setValue(value!.usage ?? 0) + } + if let widget = self.widget as? BarChart { + widget.setValue([value!.usage ?? 0]) + } + } +} diff --git a/Modules/Memory/popup.swift b/Modules/Memory/popup.swift new file mode 100644 index 00000000..4f30bc28 --- /dev/null +++ b/Modules/Memory/popup.swift @@ -0,0 +1,119 @@ +// +// popup.swift +// Memory +// +// Created by Serhiy Mytrovtsiy on 18/04/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import ModuleKit +import StatsKit + +internal class Popup: NSView { + private let dashboardHeight: CGFloat = 90 + private let detailsHeight: CGFloat = 66 + + private var totalField: NSTextField? = nil + private var usedField: NSTextField? = nil + private var freeField: NSTextField? = nil + + private var activeField: NSTextField? = nil + private var inactiveField: NSTextField? = nil + private var wiredField: NSTextField? = nil + private var compressedField: NSTextField? = nil + + private var chart: LineChartView? = nil + private var initialized: Bool = false + + public init() { + super.init(frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: dashboardHeight + Constants.Popup.separatorHeight + detailsHeight)) + + initFirstView() + initDetails() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func updateLayer() { + self.chart?.display() + } + + private func initFirstView() { + let rightWidth: CGFloat = 116 + let view: NSView = NSView(frame: NSRect(x: 0, y: self.frame.height - self.dashboardHeight, width: self.frame.width, height: self.dashboardHeight)) + + let leftPanel = NSView(frame: NSRect(x: 0, y: 0, width: view.frame.width - rightWidth - Constants.Popup.margins, height: view.frame.height)) + + self.chart = LineChartView(frame: NSRect(x: 4, y: 3, width: leftPanel.frame.width, height: leftPanel.frame.height), num: 120) + leftPanel.addSubview(self.chart!) + + let rightPanel: NSView = NSView(frame: NSRect(x: view.frame.width - rightWidth, y: 0, width: rightWidth, height: view.frame.height)) + self.activeField = addFirstRow(mView: rightPanel, y: ((rightPanel.frame.height - 16)/2)+29, title: "Active:", value: "") + self.inactiveField = addFirstRow(mView: rightPanel, y: (rightPanel.frame.height - 16)/2+10, title: "Inactive:", value: "") + self.wiredField = addFirstRow(mView: rightPanel, y: ((rightPanel.frame.height - 16)/2)-10, title: "Wired:", value: "") + self.compressedField = addFirstRow(mView: rightPanel, y: ((rightPanel.frame.height - 16)/2)-29, title: "Compressed:", value: "") + + view.addSubview(leftPanel) + view.addSubview(rightPanel) + self.addSubview(view) + } + + private func initDetails() { + let y: CGFloat = self.frame.height - self.dashboardHeight - Constants.Popup.separatorHeight + let separator = SeparatorView("Details", origin: NSPoint(x: 0, y: y), width: self.frame.width) + self.addSubview(separator) + + let view: NSView = NSView(frame: NSRect(x: 0, y: separator.frame.origin.y - self.detailsHeight, width: self.frame.width, height: self.detailsHeight)) + + self.totalField = PopupRow(view, n: 2, title: "Total:", value: "") + self.usedField = PopupRow(view, n: 1, title: "Used:", value: "") + self.freeField = PopupRow(view, n: 0, title: "Free:", value: "") + + self.addSubview(view) + } + + private func addFirstRow(mView: NSView, y: CGFloat, title: String, value: String) -> NSTextField { + let rowView: NSView = NSView(frame: NSRect(x: 0, y: y, width: mView.frame.width, height: 16)) + + let labelWidth = title.widthOfString(usingFont: .systemFont(ofSize: 10, weight: .light)) + 4 + let labelView: NSTextField = TextView(frame: NSRect(x: 0, y: 1.5, width: labelWidth, height: 13)) + labelView.stringValue = title + labelView.alignment = .natural + labelView.font = NSFont.systemFont(ofSize: 10, weight: .light) + + let valueView: NSTextField = TextView(frame: NSRect(x: labelWidth, y: 1, width: mView.frame.width - labelWidth, height: 14)) + valueView.stringValue = value + valueView.alignment = .right + valueView.font = NSFont.systemFont(ofSize: 11, weight: .medium) + + rowView.addSubview(labelView) + rowView.addSubview(valueView) + mView.addSubview(rowView) + + return valueView + } + + public func loadCallback(_ value: Usage) { + DispatchQueue.main.async(execute: { + if self.window!.isVisible || !self.initialized { + self.activeField?.stringValue = Units(bytes: Int64(value.active!)).getReadableMemory() + self.inactiveField?.stringValue = Units(bytes: Int64(value.inactive!)).getReadableMemory() + self.wiredField?.stringValue = Units(bytes: Int64(value.wired!)).getReadableMemory() + self.compressedField?.stringValue = Units(bytes: Int64(value.compressed!)).getReadableMemory() + + self.totalField?.stringValue = Units(bytes: Int64(value.total!)).getReadableMemory() + self.usedField?.stringValue = Units(bytes: Int64(value.used!)).getReadableMemory() + self.freeField?.stringValue = Units(bytes: Int64(value.free!)).getReadableMemory() + self.initialized = true + } + + self.chart?.addValue(value.usage!) + }) + } +} diff --git a/Modules/Memory/readers.swift b/Modules/Memory/readers.swift new file mode 100644 index 00000000..fc768320 --- /dev/null +++ b/Modules/Memory/readers.swift @@ -0,0 +1,72 @@ +// +// readers.swift +// Memory +// +// Created by Serhiy Mytrovtsiy on 12/04/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import ModuleKit + +internal class UsageReader: Reader { + public var totalSize: Double = 0 + + public override func setup() { + 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 = Double(stats.max_mem) + return + } + + self.totalSize = 0 + print("Error with host_info(): " + (String(cString: mach_error_string(kerr), encoding: String.Encoding.ascii) ?? "unknown error")) + } + + public override func read() { + var stats = vm_statistics64() + var count = UInt32(MemoryLayout.size / MemoryLayout.size) + + let result: 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 result == KERN_SUCCESS { + let active = Double(stats.active_count) * Double(PAGE_SIZE) + let inactive = Double(stats.inactive_count) * Double(PAGE_SIZE) + let wired = Double(stats.wire_count) * Double(PAGE_SIZE) + let compressed = Double(stats.compressor_page_count) * Double(PAGE_SIZE) + + let used = active + wired + compressed + let free = self.totalSize - used + + self.callback(Usage( + active: active, + inactive: inactive, + wired: wired, + compressed: compressed, + + usage: Double((self.totalSize - free) / self.totalSize), + total: Double(self.totalSize), + used: Double(used), + free: Double(free)) + ) + return + } + + print("Error with host_statistics64(): " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error")) + } +} diff --git a/Modules/Net/Info.plist b/Modules/Net/Info.plist new file mode 100644 index 00000000..20202aa7 --- /dev/null +++ b/Modules/Net/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSHumanReadableCopyright + Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. + + diff --git a/Modules/Net/config.plist b/Modules/Net/config.plist new file mode 100644 index 00000000..53f09bd2 --- /dev/null +++ b/Modules/Net/config.plist @@ -0,0 +1,18 @@ + + + + + Name + Network + State + + Widgets + + network + + Default + + + + + diff --git a/Modules/Net/main.swift b/Modules/Net/main.swift new file mode 100644 index 00000000..e275e6e4 --- /dev/null +++ b/Modules/Net/main.swift @@ -0,0 +1,86 @@ +// +// main.swift +// Net +// +// Created by Serhiy Mytrovtsiy on 24/05/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import StatsKit +import ModuleKit + +public enum Network_t: String { + case wifi + case ethernet +} + +public struct Usage: value_t { + var active: Bool = false + + var download: Int64 = 0 + var upload: Int64 = 0 + + var laddr: String? = nil // local ip + var paddr: String? = nil // remote ip + var iaddr: String? = nil // mac adress + + var connectionType: Network_t? = nil + + var countryCode: String? = nil + var networkName: String? = nil + + mutating func reset() { + self.active = false + + self.download = 0 + self.upload = 0 + + self.laddr = nil + self.paddr = nil + self.iaddr = nil + + self.connectionType = nil + + self.countryCode = nil + self.networkName = nil + } + + public var widget_value: Double = 0 +} + +public class Network: Module { + private var usageReader: UsageReader = UsageReader() + private let popupView: Popup = Popup() + + public init(_ store: UnsafePointer?) { + super.init( + store: store, + popup: self.popupView, + settings: nil + ) + + self.usageReader.readyCallback = { [unowned self] in + self.readyHandler() + } + self.usageReader.callbackHandler = { [unowned self] value in + self.usageCallback(value) + } + + self.addReader(self.usageReader) + } + + private func usageCallback(_ value: Usage?) { + if value == nil { + return + } + + self.popupView.usageCallback(value!) + if let widget = self.widget as? NetworkWidget { + widget.setValue(upload: value!.upload, download: value!.download) + } + } +} diff --git a/Modules/Net/popup.swift b/Modules/Net/popup.swift new file mode 100644 index 00000000..7f6410a9 --- /dev/null +++ b/Modules/Net/popup.swift @@ -0,0 +1,195 @@ +// +// popup.swift +// Net +// +// Created by Serhiy Mytrovtsiy on 24/05/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import ModuleKit +import StatsKit + +internal class Popup: NSView { + let dashboardHeight: CGFloat = 90 + let detailsHeight: CGFloat = 88 + + private var dashboardView: NSView? = nil + + private var uploadView: NSView? = nil + private var uploadValue: Int64 = 0 + private var uploadValueField: NSTextField? = nil + private var uploadUnitField: NSTextField? = nil + + private var downloadView: NSView? = nil + private var downloadValue: Int64 = 0 + private var downloadValueField: NSTextField? = nil + private var downloadUnitField: NSTextField? = nil + + private var publicIPField: NSTextField? = nil + private var localIPField: NSTextField? = nil + private var networkTypeField: NSTextField? = nil + private var macAdressField: NSTextField? = nil + + private var initialized: Bool = false + + public init() { + super.init(frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: dashboardHeight + Constants.Popup.separatorHeight + detailsHeight)) + + initDashboard() + initDetails() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func initDashboard() { + let view: NSView = NSView(frame: NSRect(x: 0, y: self.frame.height - self.dashboardHeight, width: self.frame.width, height: self.dashboardHeight)) + + let leftPart: NSView = NSView(frame: NSRect(x: 0, y: 0, width: view.frame.width / 2, height: view.frame.height)) + let uploadFields = self.topValueView(leftPart, title: "Upload") + self.uploadView = uploadFields.0 + self.uploadValueField = uploadFields.1 + self.uploadUnitField = uploadFields.2 + + let rightPart: NSView = NSView(frame: NSRect(x: view.frame.width / 2, y: 0, width: view.frame.width / 2, height: view.frame.height)) + let downloadFields = self.topValueView(rightPart, title: "Download") + self.downloadView = downloadFields.0 + self.downloadValueField = downloadFields.1 + self.downloadUnitField = downloadFields.2 + + view.addSubview(leftPart) + view.addSubview(rightPart) + self.addSubview(view) + self.dashboardView = view + } + + private func topValueView(_ view: NSView, title: String) -> (NSView, NSTextField, NSTextField) { + let topHeight: CGFloat = 30 + let titleHeight: CGFloat = 15 + + let valueWidth = "0".widthOfString(usingFont: .systemFont(ofSize: 26, weight: .light)) + 5 + let unitWidth = "KB/s".widthOfString(usingFont: .systemFont(ofSize: 13, weight: .light)) + 5 + let topPartWidth = valueWidth + unitWidth + + let topPart: NSView = NSView(frame: NSRect(x: (view.frame.width-topPartWidth)/2, y: (view.frame.height - topHeight - titleHeight)/2 + titleHeight, width: topPartWidth, height: topHeight)) + + let valueField = LabelField(frame: NSRect(x: 0, y: 0, width: valueWidth, height: 30), "0") + valueField.font = NSFont.systemFont(ofSize: 26, weight: .light) + valueField.textColor = .labelColor + valueField.alignment = .right + + let unitField = LabelField(frame: NSRect(x: valueField.frame.width, y: 4, width: unitWidth, height: 15), "KB/s") + unitField.font = NSFont.systemFont(ofSize: 13, weight: .light) + unitField.textColor = .labelColor + unitField.alignment = .left + + let titleField = LabelField(frame: NSRect(x: 0, y: topPart.frame.origin.y - titleHeight, width: view.frame.width, height: titleHeight), title) + titleField.alignment = .center + + topPart.addSubview(valueField) + topPart.addSubview(unitField) + view.addSubview(topPart) + view.addSubview(titleField) + + return (topPart, valueField, unitField) + } + + private func setUploadDownloadFields() { + let upload = Units(bytes: self.uploadValue).getReadableTuple() + let download = Units(bytes: self.downloadValue).getReadableTuple() + + var valueWidth = "\(upload.0)".widthOfString(usingFont: .systemFont(ofSize: 26, weight: .light)) + 5 + var unitWidth = upload.1.widthOfString(usingFont: .systemFont(ofSize: 13, weight: .light)) + 5 + var topPartWidth = valueWidth + unitWidth + + self.uploadView?.setFrameSize(NSSize(width: topPartWidth, height: self.uploadView!.frame.height)) + self.uploadView?.setFrameOrigin(NSPoint(x: ((self.frame.width/2)-topPartWidth)/2, y: self.uploadView!.frame.origin.y)) + + self.uploadValueField?.setFrameSize(NSSize(width: valueWidth, height: self.uploadValueField!.frame.height)) + self.uploadValueField?.stringValue = "\(upload.0)" + self.uploadUnitField?.setFrameSize(NSSize(width: unitWidth, height: self.uploadUnitField!.frame.height)) + self.uploadUnitField?.setFrameOrigin(NSPoint(x: self.uploadValueField!.frame.width, y: self.uploadUnitField!.frame.origin.y)) + self.uploadUnitField?.stringValue = upload.1 + + valueWidth = "\(download.0)".widthOfString(usingFont: .systemFont(ofSize: 26, weight: .light)) + 5 + unitWidth = download.1.widthOfString(usingFont: .systemFont(ofSize: 13, weight: .light)) + 5 + topPartWidth = valueWidth + unitWidth + + self.downloadView?.setFrameSize(NSSize(width: topPartWidth, height: self.downloadView!.frame.height)) + self.downloadView?.setFrameOrigin(NSPoint(x: ((self.frame.width/2)-topPartWidth)/2, y: self.downloadView!.frame.origin.y)) + + self.downloadValueField?.setFrameSize(NSSize(width: valueWidth, height: self.downloadValueField!.frame.height)) + self.downloadValueField?.stringValue = "\(download.0)" + self.downloadUnitField?.setFrameSize(NSSize(width: unitWidth, height: self.downloadUnitField!.frame.height)) + self.downloadUnitField?.setFrameOrigin(NSPoint(x: self.downloadValueField!.frame.width, y: self.downloadUnitField!.frame.origin.y)) + self.downloadUnitField?.stringValue = download.1 + } + + private func initDetails() { + let y: CGFloat = self.dashboardView!.frame.origin.y - Constants.Popup.separatorHeight + let separator = SeparatorView("Details", origin: NSPoint(x: 0, y: y), width: self.frame.width) + self.addSubview(separator) + + let view: NSView = NSView(frame: NSRect(x: 0, y: separator.frame.origin.y - self.detailsHeight, width: self.frame.width, height: self.detailsHeight)) + + self.publicIPField = PopupRow(view, n: 3, title: "Public IP:", value: "") + self.localIPField = PopupRow(view, n: 2, title: "Local IP:", value: "") + self.networkTypeField = PopupRow(view, n: 1, title: "Network:", value: "") + self.macAdressField = PopupRow(view, n: 0, title: "Physical address:", value: "") + + self.addSubview(view) + } + + public func usageCallback(_ value: Usage) { + DispatchQueue.main.async(execute: { + if !self.window!.isVisible && self.initialized { + return + } + + self.uploadValue = value.upload + self.downloadValue = value.download + self.setUploadDownloadFields() + + if !value.active { + self.publicIPField?.stringValue = "No connection" + self.localIPField?.stringValue = "No connection" + self.networkTypeField?.stringValue = "No connection" + self.macAdressField?.stringValue = "No connection" + return + } + + if var publicIP = value.paddr, self.publicIPField?.stringValue != publicIP { + if value.countryCode != nil { + publicIP = "\(publicIP) (\(value.countryCode!))" + } + self.publicIPField?.stringValue = publicIP + } + if value.laddr != nil && self.localIPField?.stringValue != value.laddr { + self.localIPField?.stringValue = value.laddr! + } + if value.iaddr != nil && self.macAdressField?.stringValue != value.iaddr { + self.macAdressField?.stringValue = value.iaddr! + } + + if value.connectionType != nil { + var networkType = "" + if value.connectionType == .wifi { + networkType = "\(value.networkName!) (WiFi)" + } else if value.connectionType == .ethernet { + networkType = "Ethernet" + } + + if self.networkTypeField?.stringValue != networkType { + self.networkTypeField?.stringValue = networkType + } + } + + self.initialized = true + }) + } +} diff --git a/Modules/Net/readers.swift b/Modules/Net/readers.swift new file mode 100644 index 00000000..07ec825c --- /dev/null +++ b/Modules/Net/readers.swift @@ -0,0 +1,214 @@ +// +// readers.swift +// Net +// +// Created by Serhiy Mytrovtsiy on 24/05/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import ModuleKit +import SystemConfiguration +import Reachability +import os.log +import CoreWLAN + +internal class UsageReader: Reader { + private var reachability: Reachability? = nil + private var usage: Usage = Usage() + + private var interfaceID: String? = nil + + public override func setup() { + do { + self.reachability = try Reachability() + try self.reachability!.startNotifier() + } catch let error { + os_log(.error, log: log, "initialize Reachability error %s", "\(error)") + } + + self.reachability!.whenReachable = { _ in + self.readInformation() + self.start() + } + self.reachability!.whenUnreachable = { _ in + self.usage.reset() + self.callback(self.usage) + self.stop() + } + } + + public override func read() { + guard self.reachability?.connection != .unavailable else { + if self.usage.active { + self.usage.reset() + self.callback(self.usage) + } + return + } + + var interfaceAddresses: UnsafeMutablePointer? = nil + var upload: Int64 = 0 + var download: Int64 = 0 + guard getifaddrs(&interfaceAddresses) == 0 else { return } + + var pointer = interfaceAddresses + while pointer != nil { + defer { pointer = pointer?.pointee.ifa_next } + + if String(cString: pointer!.pointee.ifa_name) != self.interfaceID { + continue + } + + if let info = getBytesInfo(pointer!) { + upload += info.upload + download += info.download + } + + if let ip = getLocalIP(pointer!), self.usage.laddr != ip { + self.usage.laddr = ip + } + } + freeifaddrs(interfaceAddresses) + + if self.usage.upload != 0 && self.usage.download != 0 { + self.usage.upload = upload - self.usage.upload + self.usage.download = download - self.usage.download + } + + self.callback(self.usage) + self.usage.upload = upload + self.usage.download = download + } + + private func readInformation() { + guard self.reachability != nil && self.reachability!.connection != .unavailable else { return } + + if let global = SCDynamicStoreCopyValue(nil, "State:/Network/Global/IPv4" as CFString) { + self.interfaceID = global["PrimaryInterface"] as? String + } + + self.usage.active = true + DispatchQueue.global(qos: .background).async { + self.usage.paddr = self.getPublicIP() + } + + if self.reachability!.connection == .wifi && CWWiFiClient.shared().interface() != nil { + self.usage.connectionType = .wifi + self.usage.networkName = CWWiFiClient.shared().interface()!.ssid() + self.usage.countryCode = CWWiFiClient.shared().interface()!.countryCode() + self.usage.iaddr = CWWiFiClient.shared().interface()!.hardwareAddress() + } else { + self.usage.connectionType = .ethernet + self.usage.iaddr = getMacAddress() + } + } + + private func getDataUsageInfo(from infoPointer: UnsafeMutablePointer) -> (upload: Int64, download: Int64)? { + let pointer = infoPointer + + let addr = pointer.pointee.ifa_addr.pointee + guard addr.sa_family == UInt8(AF_LINK) else { return nil } + var networkData: UnsafeMutablePointer? = nil + + networkData = unsafeBitCast(pointer.pointee.ifa_data, to: UnsafeMutablePointer.self) + return (upload: Int64(networkData?.pointee.ifi_obytes ?? 0), download: Int64(networkData?.pointee.ifi_ibytes ?? 0)) + } + + private func getBytesInfo(_ pointer: UnsafeMutablePointer) -> (upload: Int64, download: Int64)? { + let addr = pointer.pointee.ifa_addr.pointee + + guard addr.sa_family == UInt8(AF_LINK) else { + return nil + } + + let data: UnsafeMutablePointer? = unsafeBitCast(pointer.pointee.ifa_data, to: UnsafeMutablePointer.self) + return (upload: Int64(data?.pointee.ifi_obytes ?? 0), download: Int64(data?.pointee.ifi_ibytes ?? 0)) + } + + private func getLocalIP(_ pointer: UnsafeMutablePointer) -> String? { + var addr = pointer.pointee.ifa_addr.pointee + + guard addr.sa_family == UInt8(AF_INET) else { + return nil + } + + var ip = [CChar](repeating: 0, count: Int(NI_MAXHOST)) + getnameinfo(&addr, socklen_t(addr.sa_len), &ip, socklen_t(ip.count), nil, socklen_t(0), NI_NUMERICHOST) + + return String(cString: ip) + } + + private func getPublicIP() -> String? { + let url = URL(string: "https://api.ipify.org") + var address: String? = nil + + do { + if let url = url { + address = try String(contentsOf: url) + if address!.contains("<") { + address = nil + } + } + } catch let error { + os_log(.error, log: log, "get public ip %s", "\(error)") + } + + return address + } + + // https://stackoverflow.com/questions/31835418/how-to-get-mac-address-from-os-x-with-swift + private func getMacAddress() -> String? { + var macAddressAsString : String? + if let intfIterator = findEthernetInterfaces() { + if let macAddress = getMACAddress(intfIterator) { + macAddressAsString = macAddress.map( { String(format:"%02x", $0) } ).joined(separator: ":") + } + IOObjectRelease(intfIterator) + } + return macAddressAsString + } + + private func findEthernetInterfaces() -> io_iterator_t? { + let matchingDictUM = IOServiceMatching("IOEthernetInterface"); + if matchingDictUM == nil { + return nil + } + + let matchingDict = matchingDictUM! as NSMutableDictionary + matchingDict["IOPropertyMatch"] = [ "IOPrimaryInterface" : true] + + var matchingServices : io_iterator_t = 0 + if IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &matchingServices) != KERN_SUCCESS { + return nil + } + + return matchingServices + } + + private func getMACAddress(_ intfIterator : io_iterator_t) -> [UInt8]? { + var macAddress : [UInt8]? + var intfService = IOIteratorNext(intfIterator) + + while intfService != 0 { + var controllerService : io_object_t = 0 + if IORegistryEntryGetParentEntry(intfService, kIOServicePlane, &controllerService) == KERN_SUCCESS { + let dataUM = IORegistryEntryCreateCFProperty(controllerService, "IOMACAddress" as CFString, kCFAllocatorDefault, 0) + if dataUM != nil { + let data = (dataUM!.takeRetainedValue() as! CFData) as Data + macAddress = [0, 0, 0, 0, 0, 0] + data.copyBytes(to: &macAddress!, count: macAddress!.count) + } + IOObjectRelease(controllerService) + } + + IOObjectRelease(intfService) + intfService = IOIteratorNext(intfIterator) + } + + return macAddress + } +} diff --git a/README.md b/README.md index 131802d9..35f75874 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,39 @@ # Stats -Simple macOS system monitor in your menu bar +

[![Stats](https://serhiy.s3.eu-central-1.amazonaws.com/Github_repo/stats/cover%3Fv1.6.0.png)](https://github.com/exelban/stats/releases) +Simple macOS system monitor in your menu bar + +## Installation +You can download latest version [here](https://github.com/exelban/stats/releases). + +## Requirements + +Stats is currently supported on macOS 10.14 (Mojave) and higher. + ## Features Stats is a application which allows you to monitor your macOS system. - CPU Usage - Memory Usage - - Sensors (Temperature/Voltage/Power) - Disk utilization - Battery level - Network usage - - Black theme compatible -## Installation -You can download latest version [here](https://github.com/exelban/stats/releases). +## Developing -## Modules +Pull requests and impovment proposals are welcomed. -| Name | Available widgets | Description | -| --- | --- | --- | -| **CPU** | Percentage / Chart / Chart with value / Chart Bar | Shows CPU usage | -| **Memory** | Percentage / Chart / Chart with value / Chart Bar | Shows RAM usage | -| **Sensors** | Text | Shows data from internal sensors | -| **Disk** | Percentage / Chart Bar | Shows disk utilization | -| **Battery** | Graphic / Percentage | Shows battery level and charging status | -| **Newtork** | Dots / Upload/Download traffic | Shows network activity | +If you want to run the project locally you need to have [carthage](https://github.com/Carthage/Carthage#installing-carthage) installed. -## Compatibility -| macOS | Compatible | -| --- | --- | -| 10.15.3 *(Catalina)* | **true** | -| 10.14.6 *(Mojave)* | **true** | -| 10.13.6 *(High Sierra)* | **true** | +```bash +git clone https://github.com/exelban/stats +cd stats +make dep +open ./Stats.xcodeproj +``` ## License [MIT License](https://github.com/exelban/stats/blob/master/LICENSE) diff --git a/Stats.xcodeproj/project.pbxproj b/Stats.xcodeproj/project.pbxproj index 40f8964f..938a1c01 100644 --- a/Stats.xcodeproj/project.pbxproj +++ b/Stats.xcodeproj/project.pbxproj @@ -7,80 +7,290 @@ objects = { /* Begin PBXBuildFile section */ - 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 */; }; - 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 */; }; - 9A2D15E623CE291600C4C417 /* RAM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15E523CE291600C4C417 /* RAM.swift */; }; - 9A2D15E823CE29A400C4C417 /* RAMMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15E723CE29A400C4C417 /* RAMMenu.swift */; }; - 9A2D15EA23CE2C1100C4C417 /* RAMPopup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15E923CE2C1100C4C417 /* RAMPopup.swift */; }; - 9A2D15EE23CE2EE200C4C417 /* RAMUsageReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15ED23CE2EE200C4C417 /* RAMUsageReader.swift */; }; - 9A2D15F023CE32D500C4C417 /* RAMProcessReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15EF23CE32D500C4C417 /* RAMProcessReader.swift */; }; - 9A2D15F323CE391300C4C417 /* Disk.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15F223CE391300C4C417 /* Disk.swift */; }; - 9A2D15F523CE393A00C4C417 /* DiskMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15F423CE393A00C4C417 /* DiskMenu.swift */; }; - 9A2D15F723CE3A1200C4C417 /* DiskCapacityReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15F623CE3A1200C4C417 /* DiskCapacityReader.swift */; }; - 9A2D15FA23CE3BE600C4C417 /* Battery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15F923CE3BE600C4C417 /* Battery.swift */; }; - 9A2D15FC23CE3C1A00C4C417 /* BatteryMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15FB23CE3C1A00C4C417 /* BatteryMenu.swift */; }; - 9A2D15FE23CE3DE300C4C417 /* BatteryReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15FD23CE3DE300C4C417 /* BatteryReader.swift */; }; - 9A2D160023CE40DE00C4C417 /* BatteryPopup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D15FF23CE40DE00C4C417 /* BatteryPopup.swift */; }; - 9A2D160323CE445900C4C417 /* Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D160223CE445900C4C417 /* Network.swift */; }; - 9A2D160523CE451B00C4C417 /* NetworkMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D160423CE451B00C4C417 /* NetworkMenu.swift */; }; - 9A2D160723CE462400C4C417 /* NetworkReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2D160623CE462400C4C417 /* NetworkReader.swift */; }; - 9A3434F1243E19E6006B19F9 /* LaunchAtLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A3434F0243E19E6006B19F9 /* LaunchAtLogin.swift */; }; + 9A0C82DE24460F7200FAE3D4 /* StatsKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A0C82DC24460F7200FAE3D4 /* StatsKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9A0C82E124460F7200FAE3D4 /* StatsKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */; }; + 9A0C82E224460F7200FAE3D4 /* StatsKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9A0C82E624460F9A00FAE3D4 /* extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A654920244074B500E30B74 /* extensions.swift */; }; + 9A0C82E724460F9C00FAE3D4 /* updater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A0C82D124460DFF00FAE3D4 /* updater.swift */; }; + 9A0C82E824460F9E00FAE3D4 /* launchAtLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A0C82D324460E4400FAE3D4 /* launchAtLogin.swift */; }; + 9A0C82E924460F9F00FAE3D4 /* store.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A65492224407EA600E30B74 /* store.swift */; }; + 9A0C82EA24460FB100FAE3D4 /* StatsKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */; }; + 9A0C82EB24460FB100FAE3D4 /* StatsKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9A0C82EE2446124800FAE3D4 /* SystemKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7D0CB62444C2C800B09070 /* SystemKit.swift */; }; + 9A1A7ABA24561F0B00A84F7A /* BarChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1A7AB924561F0B00A84F7A /* BarChart.swift */; }; + 9A313BF7247EF01800DB5101 /* Reachability.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A5349CD23D8832E00C23824 /* Reachability.framework */; }; + 9A313BF8247EF01800DB5101 /* Reachability.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A5349CD23D8832E00C23824 /* Reachability.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 9A34353B243E278D006B19F9 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A34353A243E278D006B19F9 /* main.swift */; }; 9A34353C243E27E8006B19F9 /* LaunchAtLogin.app in Copy Files */ = {isa = PBXBuildFile; fileRef = 9A343527243E26A0006B19F9 /* LaunchAtLogin.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 9A426DB822C2B5EE00C064C4 /* MacAppUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A426DB722C2B5EE00C064C4 /* MacAppUpdater.swift */; }; - 9A426DBE22C2BE0000C064C4 /* Updates.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9A426DBD22C2BE0000C064C4 /* Updates.storyboard */; }; - 9A5349C723D8535900C23824 /* NetworkPopup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A5349C623D8535900C23824 /* NetworkPopup.swift */; }; - 9A5349C923D8642A00C23824 /* NetworkInterfaceReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A5349C823D8642A00C23824 /* NetworkInterfaceReader.swift */; }; - 9A5349CE23D8832E00C23824 /* Reachability.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A5349CD23D8832E00C23824 /* Reachability.framework */; }; - 9A5349CF23D8832E00C23824 /* Reachability.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A5349CD23D8832E00C23824 /* Reachability.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 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 */; }; - 9A59AE56231EE02F007989D6 /* ChartMarker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A59AE55231EE02F007989D6 /* ChartMarker.swift */; }; - 9A5B1CC5229E7B40008B9D3C /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A5B1CC4229E7B40008B9D3C /* Extensions.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, ); }; }; + 9A3E17BD247A8F5700449CD1 /* StatsKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */; }; + 9A3E17BE247A8F5700449CD1 /* StatsKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9A3E17C1247A8F5E00449CD1 /* StatsKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */; }; + 9A3E17C2247A8F5E00449CD1 /* StatsKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9A3E17D3247A94AF00449CD1 /* Net.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A3E17CC247A94AF00449CD1 /* Net.framework */; }; + 9A3E17D4247A94AF00449CD1 /* Net.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A3E17CC247A94AF00449CD1 /* Net.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9A3E17D9247A94B500449CD1 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A3E17D8247A94B500449CD1 /* main.swift */; }; + 9A3E17DB247A94BC00449CD1 /* readers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A3E17DA247A94BC00449CD1 /* readers.swift */; }; + 9A3E17DE247A94DC00449CD1 /* ModuleKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEADD243FB13500668CB0 /* ModuleKit.framework */; }; + 9A3E17DF247A94DC00449CD1 /* ModuleKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEADD243FB13500668CB0 /* ModuleKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9A3E17E2247A94DC00449CD1 /* StatsKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */; }; + 9A3E17E3247A94DC00449CD1 /* StatsKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9A3E17E8247AA8E100449CD1 /* Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A3E17E7247AA8E100449CD1 /* Network.swift */; }; + 9A3E17EA247B07BF00449CD1 /* popup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A3E17E9247B07BF00449CD1 /* popup.swift */; }; + 9A5AF11B2469CE9B00684737 /* popup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A5AF11A2469CE9B00684737 /* popup.swift */; }; + 9A6549292440A57200E30B74 /* Repeat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A6549282440A57200E30B74 /* Repeat.framework */; }; + 9A65492A2440A57200E30B74 /* Repeat.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A6549282440A57200E30B74 /* Repeat.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 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 */; }; - 9A7DE036245982460084BD7A /* Repeat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A7DE035245982460084BD7A /* Repeat.framework */; }; - 9A7DE037245982460084BD7A /* Repeat.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A7DE035245982460084BD7A /* Repeat.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 9AA28DC1243774ED00D2B196 /* Sensors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA28DC0243774ED00D2B196 /* Sensors.swift */; }; - 9AA28DC32437752D00D2B196 /* SensorsMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA28DC22437752D00D2B196 /* SensorsMenu.swift */; }; - 9AA28DD1243799E500D2B196 /* SensorsWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA28DD0243799E500D2B196 /* SensorsWidget.swift */; }; - 9AA28DD6243A8A3D00D2B196 /* SMC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA28DD5243A8A3D00D2B196 /* SMC.swift */; }; - 9AA28DDB243B4AF500D2B196 /* SensorsType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA28DDA243B4AF500D2B196 /* SensorsType.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 */; }; - 9AF0F32122DA92AD00026AE6 /* NetworkDots.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF0F32022DA92AD00026AE6 /* NetworkDots.swift */; }; - 9AF0F32322DA92B900026AE6 /* NetworkArrows.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF0F32222DA92B900026AE6 /* NetworkArrows.swift */; }; - 9AF0F32522DA92C400026AE6 /* NetworkText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF0F32422DA92C400026AE6 /* NetworkText.swift */; }; - 9AF0F32722DA92DD00026AE6 /* NetworkDotsText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF0F32622DA92DD00026AE6 /* NetworkDotsText.swift */; }; - 9AF0F32922DA92E800026AE6 /* NetworkArrowsText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF0F32822DA92E800026AE6 /* NetworkArrowsText.swift */; }; - 9AF6F1FE231D732600B8E1E4 /* PopupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF6F1FD231D732600B8E1E4 /* PopupViewController.swift */; }; - 9AFFCB3B22B3FD0500B0E6D8 /* About.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9AFFCB3A22B3FD0500B0E6D8 /* About.storyboard */; }; + 9A7C61B42440DF810032695D /* Mini.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7C61B32440DF810032695D /* Mini.swift */; }; + 9A81C74D24499C7000825D92 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A81C74B24499C7000825D92 /* AppSettings.swift */; }; + 9A81C74E24499C7000825D92 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A81C74C24499C7000825D92 /* Settings.swift */; }; + 9A81C75024499D6600825D92 /* settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A81C74F24499D6600825D92 /* settings.swift */; }; + 9A81C75D2449A41400825D92 /* Memory.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A81C7562449A41400825D92 /* Memory.framework */; }; + 9A81C75E2449A41400825D92 /* Memory.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A81C7562449A41400825D92 /* Memory.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9A81C7622449A41E00825D92 /* ModuleKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEADD243FB13500668CB0 /* ModuleKit.framework */; }; + 9A81C7632449A41E00825D92 /* ModuleKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEADD243FB13500668CB0 /* ModuleKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9A81C7692449A43600825D92 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A81C7672449A43600825D92 /* main.swift */; }; + 9A81C76A2449A43600825D92 /* readers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A81C7682449A43600825D92 /* readers.swift */; }; + 9A81C76B2449AE9400825D92 /* StatsKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */; }; + 9A81C76C2449AE9400825D92 /* StatsKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9A81C7702449B8D500825D92 /* Charts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A81C76F2449B8D500825D92 /* Charts.swift */; }; + 9A944D55244920690058F32A /* reader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A944D54244920690058F32A /* reader.swift */; }; + 9A944D59244920FE0058F32A /* readers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A944D58244920FE0058F32A /* readers.swift */; }; + 9A944D5B244925720058F32A /* widget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A944D5A244925720058F32A /* widget.swift */; }; + 9A944D5D24492A8B0058F32A /* popup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A944D5C24492A8B0058F32A /* popup.swift */; }; + 9A944D5F24492AA60058F32A /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A944D5E24492AA60058F32A /* Constants.swift */; }; + 9A944D6124492B6D0058F32A /* popup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A944D6024492B6D0058F32A /* popup.swift */; }; + 9A9D728A24471FAE005CF997 /* SMC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A9D728924471FAE005CF997 /* SMC.swift */; }; + 9A9EA9452476D34500E3B883 /* Update.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A9EA9442476D34500E3B883 /* Update.swift */; }; + 9AA4A00A2443656D00ECCF07 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9AA4A0092443656D00ECCF07 /* Assets.xcassets */; }; + 9AA64260244B274200416A33 /* popup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA6425F244B274200416A33 /* popup.swift */; }; + 9AA64262244B57C800416A33 /* settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA64261244B57C800416A33 /* settings.swift */; }; + 9AA64264244B94F300416A33 /* LineChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA64263244B94F300416A33 /* LineChart.swift */; }; + 9AABEAE4243FB13500668CB0 /* ModuleKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEADD243FB13500668CB0 /* ModuleKit.framework */; }; + 9AABEAE5243FB13500668CB0 /* ModuleKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEADD243FB13500668CB0 /* ModuleKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9AABEAEA243FB15E00668CB0 /* module.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AABEAE9243FB15E00668CB0 /* module.swift */; }; + 9AABEB6B243FCE8A00668CB0 /* CPU.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEB64243FCE8A00668CB0 /* CPU.framework */; }; + 9AABEB6C243FCE8A00668CB0 /* CPU.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEB64243FCE8A00668CB0 /* CPU.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9AABEB71243FCE9400668CB0 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AABEB70243FCE9400668CB0 /* main.swift */; }; + 9AABEB72243FCEEF00668CB0 /* ModuleKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEADD243FB13500668CB0 /* ModuleKit.framework */; }; + 9AABEB73243FCEEF00668CB0 /* ModuleKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEADD243FB13500668CB0 /* ModuleKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9AABEB7A243FD26200668CB0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AABEB79243FD26200668CB0 /* AppDelegate.swift */; }; + 9AABEB7E243FDEF100668CB0 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AABEB7D243FDEF100668CB0 /* main.swift */; }; + 9AB14B77248CEF3500DC6731 /* config.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9AF9EE192464A7B3005D2270 /* config.plist */; }; + 9AB14B78248CEF3B00DC6731 /* config.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9AF9EE12246492E8005D2270 /* config.plist */; }; + 9AB14B79248CEF4100DC6731 /* config.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9A3E17DC247A94C300449CD1 /* config.plist */; }; + 9AB14B7A248CEF4900DC6731 /* config.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9ABFF904248BEC0B00C9041A /* config.plist */; }; + 9AB14B7B248CF00F00DC6731 /* config.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9AF9EE1B2464A7BA005D2270 /* config.plist */; }; + 9AB7FD7C246B48DB00387FDA /* settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AB7FD7B246B48DB00387FDA /* settings.swift */; }; + 9ABFF8FD248BEBCB00C9041A /* Battery.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9ABFF8F6248BEBCB00C9041A /* Battery.framework */; }; + 9ABFF8FE248BEBCB00C9041A /* Battery.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9ABFF8F6248BEBCB00C9041A /* Battery.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9ABFF903248BEBD700C9041A /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ABFF902248BEBD700C9041A /* main.swift */; }; + 9ABFF906248BEC2600C9041A /* ModuleKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEADD243FB13500668CB0 /* ModuleKit.framework */; }; + 9ABFF907248BEC2600C9041A /* ModuleKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEADD243FB13500668CB0 /* ModuleKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9ABFF90B248BEC2900C9041A /* StatsKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */; }; + 9ABFF90C248BEC2900C9041A /* StatsKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9ABFF910248BEE7200C9041A /* readers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ABFF90F248BEE7200C9041A /* readers.swift */; }; + 9ABFF912248BF39500C9041A /* Battery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ABFF911248BF39500C9041A /* Battery.swift */; }; + 9ABFF914248C30A800C9041A /* popup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ABFF913248C30A800C9041A /* popup.swift */; }; + 9AF9EE0924648751005D2270 /* Disk.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AF9EE0224648751005D2270 /* Disk.framework */; }; + 9AF9EE0A24648751005D2270 /* Disk.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9AF9EE0224648751005D2270 /* Disk.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9AF9EE0F2464875F005D2270 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF9EE0E2464875F005D2270 /* main.swift */; }; + 9AF9EE1124648ADC005D2270 /* readers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF9EE1024648ADC005D2270 /* readers.swift */; }; + 9AF9EE1424649BAD005D2270 /* ModuleKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEADD243FB13500668CB0 /* ModuleKit.framework */; }; + 9AF9EE1524649BAD005D2270 /* ModuleKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9AABEADD243FB13500668CB0 /* ModuleKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 9A0C82DF24460F7200FAE3D4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9A1410ED229E721100D29793 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9A0C82D924460F7200FAE3D4; + remoteInfo = StatsKit; + }; + 9A0C82EC24460FB100FAE3D4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9A1410ED229E721100D29793 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9A0C82D924460F7200FAE3D4; + remoteInfo = StatsKit; + }; + 9A3E17BF247A8F5700449CD1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9A1410ED229E721100D29793 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9A0C82D924460F7200FAE3D4; + remoteInfo = StatsKit; + }; + 9A3E17C3247A8F5E00449CD1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9A1410ED229E721100D29793 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9A0C82D924460F7200FAE3D4; + remoteInfo = StatsKit; + }; + 9A3E17D1247A94AF00449CD1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9A1410ED229E721100D29793 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9A3E17CB247A94AF00449CD1; + remoteInfo = Net; + }; + 9A3E17E0247A94DC00449CD1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9A1410ED229E721100D29793 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9AABEADC243FB13500668CB0; + remoteInfo = ModuleKit; + }; + 9A3E17E4247A94DC00449CD1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9A1410ED229E721100D29793 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9A0C82D924460F7200FAE3D4; + remoteInfo = StatsKit; + }; + 9A81C75B2449A41400825D92 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9A1410ED229E721100D29793 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9A81C7552449A41400825D92; + remoteInfo = Memory; + }; + 9A81C7642449A41E00825D92 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9A1410ED229E721100D29793 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9AABEADC243FB13500668CB0; + remoteInfo = ModuleKit; + }; + 9A81C76D2449AE9400825D92 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9A1410ED229E721100D29793 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9A0C82D924460F7200FAE3D4; + remoteInfo = StatsKit; + }; + 9AABEAE2243FB13500668CB0 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9A1410ED229E721100D29793 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9AABEADC243FB13500668CB0; + remoteInfo = ModuleKit; + }; + 9AABEB69243FCE8A00668CB0 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9A1410ED229E721100D29793 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9AABEB63243FCE8A00668CB0; + remoteInfo = CPU; + }; + 9AABEB74243FCEEF00668CB0 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9A1410ED229E721100D29793 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9AABEADC243FB13500668CB0; + remoteInfo = ModuleKit; + }; + 9ABFF8FB248BEBCB00C9041A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9A1410ED229E721100D29793 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9ABFF8F5248BEBCB00C9041A; + remoteInfo = Battery; + }; + 9ABFF908248BEC2600C9041A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9A1410ED229E721100D29793 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9AABEADC243FB13500668CB0; + remoteInfo = ModuleKit; + }; + 9ABFF90D248BEC2900C9041A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9A1410ED229E721100D29793 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9A0C82D924460F7200FAE3D4; + remoteInfo = StatsKit; + }; + 9AF9EE0724648751005D2270 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9A1410ED229E721100D29793 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9AF9EE0124648751005D2270; + remoteInfo = Disk; + }; + 9AF9EE1624649BAD005D2270 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9A1410ED229E721100D29793 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9AABEADC243FB13500668CB0; + remoteInfo = ModuleKit; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ + 9A3E17E6247A94DC00449CD1 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 9A3E17DF247A94DC00449CD1 /* ModuleKit.framework in Embed Frameworks */, + 9A313BF8247EF01800DB5101 /* Reachability.framework in Embed Frameworks */, + 9A3E17E3247A94DC00449CD1 /* StatsKit.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + 9A65492B2440A57200E30B74 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 9A65492A2440A57200E30B74 /* Repeat.framework in Embed Frameworks */, + 9A0C82EB24460FB100FAE3D4 /* StatsKit.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; 9A6698E72326AB16001D00E1 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( - 9A5349CF23D8832E00C23824 /* Reachability.framework in Embed Frameworks */, - 9A7DE037245982460084BD7A /* Repeat.framework in Embed Frameworks */, - 9A6698E62326AB16001D00E1 /* Charts.framework in Embed Frameworks */, + 9AF9EE0A24648751005D2270 /* Disk.framework in Embed Frameworks */, + 9A81C75E2449A41400825D92 /* Memory.framework in Embed Frameworks */, + 9ABFF8FE248BEBCB00C9041A /* Battery.framework in Embed Frameworks */, + 9AABEB6C243FCE8A00668CB0 /* CPU.framework in Embed Frameworks */, + 9AABEAE5243FB13500668CB0 /* ModuleKit.framework in Embed Frameworks */, + 9A3E17D4247A94AF00449CD1 /* Net.framework in Embed Frameworks */, + 9A0C82E224460F7200FAE3D4 /* StatsKit.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + 9A81C7662449A41E00825D92 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 9A81C7632449A41E00825D92 /* ModuleKit.framework in Embed Frameworks */, + 9A81C76C2449AE9400825D92 /* StatsKit.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + 9AABEB76243FCEEF00668CB0 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 12; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 9AABEB73243FCEEF00668CB0 /* ModuleKit.framework in Embed Frameworks */, + 9A3E17BE247A8F5700449CD1 /* StatsKit.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -96,84 +306,127 @@ name = "Copy Files"; runOnlyForDeploymentPostprocessing = 0; }; + 9ABFF90A248BEC2600C9041A /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 9ABFF907248BEC2600C9041A /* ModuleKit.framework in Embed Frameworks */, + 9ABFF90C248BEC2900C9041A /* StatsKit.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + 9AF9EE1824649BAD005D2270 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 9AF9EE1524649BAD005D2270 /* ModuleKit.framework in Embed Frameworks */, + 9A3E17C2247A8F5E00449CD1 /* StatsKit.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 9A09C8A122B3D94D0018426F /* BatteryWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryWidget.swift; sourceTree = ""; }; + 9A0C82D124460DFF00FAE3D4 /* updater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = updater.swift; sourceTree = ""; }; + 9A0C82D324460E4400FAE3D4 /* launchAtLogin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = launchAtLogin.swift; sourceTree = ""; }; + 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StatsKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9A0C82DC24460F7200FAE3D4 /* StatsKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StatsKit.h; sourceTree = ""; }; + 9A0C82DD24460F7200FAE3D4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; 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 = ""; }; - 9A1410FF229E721200D29793 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 9A141101229E721200D29793 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; 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 = ""; }; - 9A2D15E523CE291600C4C417 /* RAM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RAM.swift; sourceTree = ""; }; - 9A2D15E723CE29A400C4C417 /* RAMMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RAMMenu.swift; sourceTree = ""; }; - 9A2D15E923CE2C1100C4C417 /* RAMPopup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RAMPopup.swift; sourceTree = ""; }; - 9A2D15ED23CE2EE200C4C417 /* RAMUsageReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RAMUsageReader.swift; sourceTree = ""; }; - 9A2D15EF23CE32D500C4C417 /* RAMProcessReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RAMProcessReader.swift; sourceTree = ""; }; - 9A2D15F223CE391300C4C417 /* Disk.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Disk.swift; sourceTree = ""; }; - 9A2D15F423CE393A00C4C417 /* DiskMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiskMenu.swift; sourceTree = ""; }; - 9A2D15F623CE3A1200C4C417 /* DiskCapacityReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiskCapacityReader.swift; sourceTree = ""; }; - 9A2D15F923CE3BE600C4C417 /* Battery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Battery.swift; sourceTree = ""; }; - 9A2D15FB23CE3C1A00C4C417 /* BatteryMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryMenu.swift; sourceTree = ""; }; - 9A2D15FD23CE3DE300C4C417 /* BatteryReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryReader.swift; sourceTree = ""; }; - 9A2D15FF23CE40DE00C4C417 /* BatteryPopup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryPopup.swift; sourceTree = ""; }; - 9A2D160223CE445900C4C417 /* Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = ""; }; - 9A2D160423CE451B00C4C417 /* NetworkMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMenu.swift; sourceTree = ""; }; - 9A2D160623CE462400C4C417 /* NetworkReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkReader.swift; sourceTree = ""; }; - 9A3434F0243E19E6006B19F9 /* LaunchAtLogin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchAtLogin.swift; sourceTree = ""; }; + 9A1A7AB924561F0B00A84F7A /* BarChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarChart.swift; sourceTree = ""; }; + 9A1CC5AC24615E5C0023F4E8 /* IntelPowerGadget.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IntelPowerGadget.framework; path = StatsKit/IntelPowerGadget/IntelPowerGadget.framework; sourceTree = ""; }; 9A343527243E26A0006B19F9 /* LaunchAtLogin.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LaunchAtLogin.app; sourceTree = BUILT_PRODUCTS_DIR; }; 9A343535243E26A0006B19F9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9A343536243E26A0006B19F9 /* LaunchAtLogin.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = LaunchAtLogin.entitlements; sourceTree = ""; }; 9A34353A243E278D006B19F9 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.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 = ""; }; - 9A5349C623D8535900C23824 /* NetworkPopup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkPopup.swift; sourceTree = ""; }; - 9A5349C823D8642A00C23824 /* NetworkInterfaceReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkInterfaceReader.swift; sourceTree = ""; }; + 9A3E17CC247A94AF00449CD1 /* Net.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Net.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9A3E17CF247A94AF00449CD1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9A3E17D8247A94B500449CD1 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; + 9A3E17DA247A94BC00449CD1 /* readers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = readers.swift; sourceTree = ""; }; + 9A3E17DC247A94C300449CD1 /* config.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = config.plist; sourceTree = ""; }; + 9A3E17E7247AA8E100449CD1 /* Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = ""; }; + 9A3E17E9247B07BF00449CD1 /* popup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = popup.swift; sourceTree = ""; }; 9A5349CD23D8832E00C23824 /* Reachability.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Reachability.framework; path = Carthage/Build/Mac/Reachability.framework; 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 = ""; }; - 9A59AE55231EE02F007989D6 /* ChartMarker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartMarker.swift; sourceTree = ""; }; - 9A5B1CC4229E7B40008B9D3C /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.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 = ""; }; - 9A6698E32326AAE5001D00E1 /* Charts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Charts.framework; path = Carthage/Build/Mac/Charts.framework; sourceTree = ""; }; + 9A5AF11A2469CE9B00684737 /* popup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = popup.swift; sourceTree = ""; }; + 9A654920244074B500E30B74 /* extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = extensions.swift; sourceTree = ""; }; + 9A65492224407EA600E30B74 /* store.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = store.swift; sourceTree = ""; }; + 9A6549282440A57200E30B74 /* Repeat.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Repeat.framework; path = Carthage/Build/Mac/Repeat.framework; sourceTree = ""; }; 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 = ""; }; - 9A7DE035245982460084BD7A /* Repeat.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Repeat.framework; path = Carthage/Build/Mac/Repeat.framework; sourceTree = ""; }; + 9A7C61B32440DF810032695D /* Mini.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mini.swift; sourceTree = ""; }; + 9A7D0CB62444C2C800B09070 /* SystemKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemKit.swift; sourceTree = ""; }; + 9A81C74B24499C7000825D92 /* AppSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = ""; }; + 9A81C74C24499C7000825D92 /* Settings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; + 9A81C74F24499D6600825D92 /* settings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = settings.swift; sourceTree = ""; }; + 9A81C7562449A41400825D92 /* Memory.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Memory.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9A81C7592449A41400825D92 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9A81C7672449A43600825D92 /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; + 9A81C7682449A43600825D92 /* readers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = readers.swift; sourceTree = ""; }; + 9A81C76F2449B8D500825D92 /* Charts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Charts.swift; sourceTree = ""; }; + 9A944D54244920690058F32A /* reader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = reader.swift; sourceTree = ""; }; + 9A944D58244920FE0058F32A /* readers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = readers.swift; sourceTree = ""; }; + 9A944D5A244925720058F32A /* widget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = widget.swift; sourceTree = ""; }; + 9A944D5C24492A8B0058F32A /* popup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = popup.swift; sourceTree = ""; }; + 9A944D5E24492AA60058F32A /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; + 9A944D6024492B6D0058F32A /* popup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = popup.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; }; - 9AA28DC0243774ED00D2B196 /* Sensors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sensors.swift; sourceTree = ""; }; - 9AA28DC22437752D00D2B196 /* SensorsMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SensorsMenu.swift; sourceTree = ""; }; - 9AA28DD0243799E500D2B196 /* SensorsWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SensorsWidget.swift; sourceTree = ""; }; - 9AA28DD5243A8A3D00D2B196 /* SMC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMC.swift; sourceTree = ""; }; - 9AA28DDA243B4AF500D2B196 /* SensorsType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SensorsType.swift; sourceTree = ""; }; - 9AF0F31A22DA924000026AE6 /* LineChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineChart.swift; sourceTree = ""; }; - 9AF0F31C22DA925000026AE6 /* LineChartWithValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineChartWithValue.swift; sourceTree = ""; }; - 9AF0F31E22DA925700026AE6 /* BarChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarChart.swift; sourceTree = ""; }; - 9AF0F32022DA92AD00026AE6 /* NetworkDots.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkDots.swift; sourceTree = ""; }; - 9AF0F32222DA92B900026AE6 /* NetworkArrows.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkArrows.swift; sourceTree = ""; }; - 9AF0F32422DA92C400026AE6 /* NetworkText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkText.swift; sourceTree = ""; }; - 9AF0F32622DA92DD00026AE6 /* NetworkDotsText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkDotsText.swift; sourceTree = ""; }; - 9AF0F32822DA92E800026AE6 /* NetworkArrowsText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkArrowsText.swift; sourceTree = ""; }; - 9AF6F1FD231D732600B8E1E4 /* PopupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupViewController.swift; sourceTree = ""; }; - 9AFFCB3A22B3FD0500B0E6D8 /* About.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = About.storyboard; sourceTree = ""; }; + 9A9D728924471FAE005CF997 /* SMC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMC.swift; sourceTree = ""; }; + 9A9EA9442476D34500E3B883 /* Update.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Update.swift; sourceTree = ""; }; + 9AA4A0092443656D00ECCF07 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 9AA6425F244B274200416A33 /* popup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = popup.swift; sourceTree = ""; }; + 9AA64261244B57C800416A33 /* settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = settings.swift; sourceTree = ""; }; + 9AA64263244B94F300416A33 /* LineChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineChart.swift; sourceTree = ""; }; + 9AABEADD243FB13500668CB0 /* ModuleKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ModuleKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9AABEAE0243FB13500668CB0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9AABEAE9243FB15E00668CB0 /* module.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = module.swift; sourceTree = ""; }; + 9AABEB64243FCE8A00668CB0 /* CPU.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CPU.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9AABEB67243FCE8A00668CB0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9AABEB70243FCE9400668CB0 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; + 9AABEB79243FD26200668CB0 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 9AABEB7D243FDEF100668CB0 /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; + 9AB7FD7B246B48DB00387FDA /* settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = settings.swift; sourceTree = ""; }; + 9ABFF8F6248BEBCB00C9041A /* Battery.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Battery.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9ABFF8F9248BEBCB00C9041A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9ABFF902248BEBD700C9041A /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; + 9ABFF904248BEC0B00C9041A /* config.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = config.plist; sourceTree = ""; }; + 9ABFF90F248BEE7200C9041A /* readers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = readers.swift; sourceTree = ""; }; + 9ABFF911248BF39500C9041A /* Battery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Battery.swift; sourceTree = ""; }; + 9ABFF913248C30A800C9041A /* popup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = popup.swift; sourceTree = ""; }; + 9AF9EE0224648751005D2270 /* Disk.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Disk.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9AF9EE0524648751005D2270 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9AF9EE0E2464875F005D2270 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; + 9AF9EE1024648ADC005D2270 /* readers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = readers.swift; sourceTree = ""; }; + 9AF9EE12246492E8005D2270 /* config.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = config.plist; sourceTree = ""; }; + 9AF9EE192464A7B3005D2270 /* config.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = config.plist; sourceTree = ""; }; + 9AF9EE1B2464A7BA005D2270 /* config.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = config.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 9A0C82D724460F7200FAE3D4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 9A1410F2229E721100D29793 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9A5349CE23D8832E00C23824 /* Reachability.framework in Frameworks */, - 9A7DE036245982460084BD7A /* Repeat.framework in Frameworks */, + 9AF9EE0924648751005D2270 /* Disk.framework in Frameworks */, + 9AABEAE4243FB13500668CB0 /* ModuleKit.framework in Frameworks */, + 9ABFF8FD248BEBCB00C9041A /* Battery.framework in Frameworks */, + 9A81C75D2449A41400825D92 /* Memory.framework in Frameworks */, + 9A0C82E124460F7200FAE3D4 /* StatsKit.framework in Frameworks */, + 9A3E17D3247A94AF00449CD1 /* Net.framework in Frameworks */, + 9AABEB6B243FCE8A00668CB0 /* CPU.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -184,14 +437,88 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 9A3E17C9247A94AF00449CD1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9A3E17DE247A94DC00449CD1 /* ModuleKit.framework in Frameworks */, + 9A313BF7247EF01800DB5101 /* Reachability.framework in Frameworks */, + 9A3E17E2247A94DC00449CD1 /* StatsKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9A81C7532449A41400825D92 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9A81C7622449A41E00825D92 /* ModuleKit.framework in Frameworks */, + 9A81C76B2449AE9400825D92 /* StatsKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9AABEADA243FB13500668CB0 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9A6549292440A57200E30B74 /* Repeat.framework in Frameworks */, + 9A0C82EA24460FB100FAE3D4 /* StatsKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9AABEB61243FCE8A00668CB0 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9AABEB72243FCEEF00668CB0 /* ModuleKit.framework in Frameworks */, + 9A3E17BD247A8F5700449CD1 /* StatsKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9ABFF8F3248BEBCB00C9041A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9ABFF906248BEC2600C9041A /* ModuleKit.framework in Frameworks */, + 9ABFF90B248BEC2900C9041A /* StatsKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9AF9EDFF24648751005D2270 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9AF9EE1424649BAD005D2270 /* ModuleKit.framework in Frameworks */, + 9A3E17C1247A8F5E00449CD1 /* StatsKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 9A0C82DB24460F7200FAE3D4 /* StatsKit */ = { + isa = PBXGroup; + children = ( + 9A7D0CB62444C2C800B09070 /* SystemKit.swift */, + 9A0C82D124460DFF00FAE3D4 /* updater.swift */, + 9A65492224407EA600E30B74 /* store.swift */, + 9A0C82D324460E4400FAE3D4 /* launchAtLogin.swift */, + 9A654920244074B500E30B74 /* extensions.swift */, + 9A0C82DC24460F7200FAE3D4 /* StatsKit.h */, + 9A0C82DD24460F7200FAE3D4 /* Info.plist */, + 9A9D728924471FAE005CF997 /* SMC.swift */, + 9A81C76F2449B8D500825D92 /* Charts.swift */, + ); + path = StatsKit; + sourceTree = ""; + }; 9A1410EC229E721100D29793 = { isa = PBXGroup; children = ( 9A1410F7229E721100D29793 /* Stats */, + 9A0C82DB24460F7200FAE3D4 /* StatsKit */, 9A343528243E26A0006B19F9 /* LaunchAtLogin */, + 9AABEADE243FB13500668CB0 /* ModuleKit */, + 9AB14B75248CEEC600DC6731 /* Modules */, 9A1410F6229E721100D29793 /* Products */, 9A998CD622A199920087ADE7 /* Frameworks */, ); @@ -202,6 +529,13 @@ children = ( 9A1410F5229E721100D29793 /* Stats.app */, 9A343527243E26A0006B19F9 /* LaunchAtLogin.app */, + 9AABEADD243FB13500668CB0 /* ModuleKit.framework */, + 9AABEB64243FCE8A00668CB0 /* CPU.framework */, + 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */, + 9A81C7562449A41400825D92 /* Memory.framework */, + 9AF9EE0224648751005D2270 /* Disk.framework */, + 9A3E17CC247A94AF00449CD1 /* Net.framework */, + 9ABFF8F6248BEBCB00C9041A /* Battery.framework */, ); name = Products; sourceTree = ""; @@ -209,75 +543,13 @@ 9A1410F7229E721100D29793 /* Stats */ = { isa = PBXGroup; children = ( - 9AF6F1FC231D72EC00B8E1E4 /* Views */, - 9A74D59522B440D4004FE1FA /* Widgets */, + 9A81C74A24499C4B00825D92 /* Views */, 9A5B1CB3229E72A7008B9D3C /* Supporting Files */, - 9A5B1CBA229E7892008B9D3C /* Modules */, - 9A5B1CBD229E78D2008B9D3C /* libs */, - 9A1410F8229E721100D29793 /* AppDelegate.swift */, - 9A57A18422A1D26D0033E318 /* MenuBar.swift */, + 9AABEB79243FD26200668CB0 /* AppDelegate.swift */, ); path = Stats; sourceTree = ""; }; - 9A2D15D323CCEFEC00C4C417 /* CPU */ = { - isa = PBXGroup; - children = ( - 9A2D15D423CCEFF700C4C417 /* CPU.swift */, - 9A2D15D823CD036400C4C417 /* CPUMenu.swift */, - 9A2D15DA23CD0B2100C4C417 /* CPUPopup.swift */, - 9A2D15D623CCFE1B00C4C417 /* CPULoadReader.swift */, - 9A2D15E023CD133300C4C417 /* CPUUsageReader.swift */, - 9A2D15E223CD1E4B00C4C417 /* CPUProcessReader.swift */, - ); - path = CPU; - sourceTree = ""; - }; - 9A2D15E423CE290400C4C417 /* RAM */ = { - isa = PBXGroup; - children = ( - 9A2D15E523CE291600C4C417 /* RAM.swift */, - 9A2D15E723CE29A400C4C417 /* RAMMenu.swift */, - 9A2D15E923CE2C1100C4C417 /* RAMPopup.swift */, - 9A2D15ED23CE2EE200C4C417 /* RAMUsageReader.swift */, - 9A2D15EF23CE32D500C4C417 /* RAMProcessReader.swift */, - ); - path = RAM; - sourceTree = ""; - }; - 9A2D15F123CE390500C4C417 /* Disk */ = { - isa = PBXGroup; - children = ( - 9A2D15F223CE391300C4C417 /* Disk.swift */, - 9A2D15F423CE393A00C4C417 /* DiskMenu.swift */, - 9A2D15F623CE3A1200C4C417 /* DiskCapacityReader.swift */, - ); - path = Disk; - sourceTree = ""; - }; - 9A2D15F823CE3BDA00C4C417 /* Battery */ = { - isa = PBXGroup; - children = ( - 9A2D15F923CE3BE600C4C417 /* Battery.swift */, - 9A2D15FB23CE3C1A00C4C417 /* BatteryMenu.swift */, - 9A2D15FF23CE40DE00C4C417 /* BatteryPopup.swift */, - 9A2D15FD23CE3DE300C4C417 /* BatteryReader.swift */, - ); - path = Battery; - sourceTree = ""; - }; - 9A2D160123CE444D00C4C417 /* Network */ = { - isa = PBXGroup; - children = ( - 9A2D160223CE445900C4C417 /* Network.swift */, - 9A2D160423CE451B00C4C417 /* NetworkMenu.swift */, - 9A5349C623D8535900C23824 /* NetworkPopup.swift */, - 9A2D160623CE462400C4C417 /* NetworkReader.swift */, - 9A5349C823D8642A00C23824 /* NetworkInterfaceReader.swift */, - ); - path = Network; - sourceTree = ""; - }; 9A343528243E26A0006B19F9 /* LaunchAtLogin */ = { isa = PBXGroup; children = ( @@ -288,132 +560,222 @@ path = LaunchAtLogin; sourceTree = ""; }; - 9A54EF65232AB48100F7DC20 /* Battery */ = { + 9A3E17CD247A94AF00449CD1 /* Net */ = { isa = PBXGroup; children = ( - 9A09C8A122B3D94D0018426F /* BatteryWidget.swift */, - 9A54EF66232AB81800F7DC20 /* BatteryPercentageWidget.swift */, - 9A54EF68232AB82700F7DC20 /* BatteryTimeWidget.swift */, + 9A3E17D8247A94B500449CD1 /* main.swift */, + 9A3E17DA247A94BC00449CD1 /* readers.swift */, + 9A3E17E9247B07BF00449CD1 /* popup.swift */, + 9A3E17CF247A94AF00449CD1 /* Info.plist */, + 9A3E17DC247A94C300449CD1 /* config.plist */, ); - path = Battery; + path = Net; sourceTree = ""; }; 9A5B1CB3229E72A7008B9D3C /* Supporting Files */ = { isa = PBXGroup; children = ( 9A6CFC0022A1C9F5001E782D /* Assets.xcassets */, - 9A1410FE229E721200D29793 /* Main.storyboard */, - 9AFFCB3A22B3FD0500B0E6D8 /* About.storyboard */, - 9A426DBD22C2BE0000C064C4 /* Updates.storyboard */, + 9AABEB7D243FDEF100668CB0 /* main.swift */, 9A141101229E721200D29793 /* Info.plist */, ); path = "Supporting Files"; sourceTree = ""; }; - 9A5B1CBA229E7892008B9D3C /* Modules */ = { + 9A7C61B22440DF770032695D /* Widgets */ = { isa = PBXGroup; children = ( - 9A2D15D323CCEFEC00C4C417 /* CPU */, - 9A2D15E423CE290400C4C417 /* RAM */, - 9A2D15F123CE390500C4C417 /* Disk */, - 9A2D15F823CE3BDA00C4C417 /* Battery */, - 9A2D160123CE444D00C4C417 /* Network */, - 9AA28DBF243774DD00D2B196 /* Sensors */, - 9A2D15D123CCEC7600C4C417 /* Module.swift */, - ); - path = Modules; - sourceTree = ""; - }; - 9A5B1CBD229E78D2008B9D3C /* libs */ = { - isa = PBXGroup; - children = ( - 9AA28DD5243A8A3D00D2B196 /* SMC.swift */, - 9A5B1CC4229E7B40008B9D3C /* Extensions.swift */, - 9A426DB722C2B5EE00C064C4 /* MacAppUpdater.swift */, - 9A59AE55231EE02F007989D6 /* ChartMarker.swift */, - 9A3434F0243E19E6006B19F9 /* LaunchAtLogin.swift */, - ); - path = libs; - sourceTree = ""; - }; - 9A74D59522B440D4004FE1FA /* Widgets */ = { - isa = PBXGroup; - children = ( - 9A54EF65232AB48100F7DC20 /* Battery */, - 9AF0F31922DA923100026AE6 /* Network */, - 9AF0F31822DA922800026AE6 /* Charts */, - 9AA28DD224379F8700D2B196 /* Sensors */, - 9A74D59622B44498004FE1FA /* Mini.swift */, - 9A79B36922D3BEE600BF1C3A /* Widget.swift */, + 9A7C61B32440DF810032695D /* Mini.swift */, + 9AA64263244B94F300416A33 /* LineChart.swift */, + 9A1A7AB924561F0B00A84F7A /* BarChart.swift */, + 9A3E17E7247AA8E100449CD1 /* Network.swift */, + 9ABFF911248BF39500C9041A /* Battery.swift */, ); path = Widgets; sourceTree = ""; }; + 9A81C74A24499C4B00825D92 /* Views */ = { + isa = PBXGroup; + children = ( + 9A81C74B24499C7000825D92 /* AppSettings.swift */, + 9A81C74C24499C7000825D92 /* Settings.swift */, + 9A9EA9442476D34500E3B883 /* Update.swift */, + ); + path = Views; + sourceTree = ""; + }; + 9A81C7572449A41400825D92 /* Memory */ = { + isa = PBXGroup; + children = ( + 9A81C7672449A43600825D92 /* main.swift */, + 9A81C7682449A43600825D92 /* readers.swift */, + 9AA6425F244B274200416A33 /* popup.swift */, + 9A81C7592449A41400825D92 /* Info.plist */, + 9AF9EE192464A7B3005D2270 /* config.plist */, + ); + path = Memory; + sourceTree = ""; + }; 9A998CD622A199920087ADE7 /* Frameworks */ = { isa = PBXGroup; children = ( - 9A7DE035245982460084BD7A /* Repeat.framework */, + 9A1CC5AC24615E5C0023F4E8 /* IntelPowerGadget.framework */, + 9A6549282440A57200E30B74 /* Repeat.framework */, 9A5349CD23D8832E00C23824 /* Reachability.framework */, - 9A6698E32326AAE5001D00E1 /* Charts.framework */, 9A998CD922A199970087ADE7 /* ServiceManagement.framework */, 9A998CD722A199920087ADE7 /* Cocoa.framework */, ); name = Frameworks; sourceTree = ""; }; - 9AA28DBF243774DD00D2B196 /* Sensors */ = { + 9AA0E9BD244269C400825127 /* Supporting Files */ = { isa = PBXGroup; children = ( - 9AA28DC0243774ED00D2B196 /* Sensors.swift */, - 9AA28DC22437752D00D2B196 /* SensorsMenu.swift */, - 9AA28DDA243B4AF500D2B196 /* SensorsType.swift */, + 9AA4A0092443656D00ECCF07 /* Assets.xcassets */, + 9AABEAE0243FB13500668CB0 /* Info.plist */, ); - path = Sensors; + path = "Supporting Files"; sourceTree = ""; }; - 9AA28DD224379F8700D2B196 /* Sensors */ = { + 9AABEADE243FB13500668CB0 /* ModuleKit */ = { isa = PBXGroup; children = ( - 9AA28DD0243799E500D2B196 /* SensorsWidget.swift */, + 9A7C61B22440DF770032695D /* Widgets */, + 9AA0E9BD244269C400825127 /* Supporting Files */, + 9AABEAE9243FB15E00668CB0 /* module.swift */, + 9A944D54244920690058F32A /* reader.swift */, + 9A944D5A244925720058F32A /* widget.swift */, + 9A944D5C24492A8B0058F32A /* popup.swift */, + 9A81C74F24499D6600825D92 /* settings.swift */, + 9A944D5E24492AA60058F32A /* Constants.swift */, ); - path = Sensors; + path = ModuleKit; sourceTree = ""; }; - 9AF0F31822DA922800026AE6 /* Charts */ = { + 9AABEB65243FCE8A00668CB0 /* CPU */ = { isa = PBXGroup; children = ( - 9AF0F31A22DA924000026AE6 /* LineChart.swift */, - 9AF0F31C22DA925000026AE6 /* LineChartWithValue.swift */, - 9AF0F31E22DA925700026AE6 /* BarChart.swift */, + 9AABEB70243FCE9400668CB0 /* main.swift */, + 9A944D58244920FE0058F32A /* readers.swift */, + 9A944D6024492B6D0058F32A /* popup.swift */, + 9AA64261244B57C800416A33 /* settings.swift */, + 9AABEB67243FCE8A00668CB0 /* Info.plist */, + 9AF9EE1B2464A7BA005D2270 /* config.plist */, ); - path = Charts; + path = CPU; sourceTree = ""; }; - 9AF0F31922DA923100026AE6 /* Network */ = { + 9AB14B75248CEEC600DC6731 /* Modules */ = { isa = PBXGroup; children = ( - 9AF0F32022DA92AD00026AE6 /* NetworkDots.swift */, - 9AF0F32222DA92B900026AE6 /* NetworkArrows.swift */, - 9AF0F32422DA92C400026AE6 /* NetworkText.swift */, - 9AF0F32622DA92DD00026AE6 /* NetworkDotsText.swift */, - 9AF0F32822DA92E800026AE6 /* NetworkArrowsText.swift */, + 9AABEB65243FCE8A00668CB0 /* CPU */, + 9A81C7572449A41400825D92 /* Memory */, + 9AF9EE0324648751005D2270 /* Disk */, + 9A3E17CD247A94AF00449CD1 /* Net */, + 9ABFF8F7248BEBCB00C9041A /* Battery */, ); - path = Network; + path = Modules; sourceTree = ""; }; - 9AF6F1FC231D72EC00B8E1E4 /* Views */ = { + 9ABFF8F7248BEBCB00C9041A /* Battery */ = { isa = PBXGroup; children = ( - 9AF6F1FD231D732600B8E1E4 /* PopupViewController.swift */, - 9A606B492321577400642F51 /* UpdatesViewController.swift */, - 9A606B4B232157BA00642F51 /* AboutViewController.swift */, + 9ABFF902248BEBD700C9041A /* main.swift */, + 9ABFF90F248BEE7200C9041A /* readers.swift */, + 9ABFF913248C30A800C9041A /* popup.swift */, + 9ABFF8F9248BEBCB00C9041A /* Info.plist */, + 9ABFF904248BEC0B00C9041A /* config.plist */, ); - path = Views; + path = Battery; + sourceTree = ""; + }; + 9AF9EE0324648751005D2270 /* Disk */ = { + isa = PBXGroup; + children = ( + 9AF9EE0E2464875F005D2270 /* main.swift */, + 9AF9EE1024648ADC005D2270 /* readers.swift */, + 9A5AF11A2469CE9B00684737 /* popup.swift */, + 9AB7FD7B246B48DB00387FDA /* settings.swift */, + 9AF9EE0524648751005D2270 /* Info.plist */, + 9AF9EE12246492E8005D2270 /* config.plist */, + ); + path = Disk; sourceTree = ""; }; /* End PBXGroup section */ +/* Begin PBXHeadersBuildPhase section */ + 9A0C82D524460F7200FAE3D4 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 9A0C82DE24460F7200FAE3D4 /* StatsKit.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9A3E17C7247A94AF00449CD1 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9A81C7512449A41400825D92 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9AABEAD8243FB13500668CB0 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9AABEB5F243FCE8A00668CB0 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9ABFF8F1248BEBCB00C9041A /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9AF9EDFD24648751005D2270 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + /* Begin PBXNativeTarget section */ + 9A0C82D924460F7200FAE3D4 /* StatsKit */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9A0C82E324460F7200FAE3D4 /* Build configuration list for PBXNativeTarget "StatsKit" */; + buildPhases = ( + 9A0C82D524460F7200FAE3D4 /* Headers */, + 9A0C82D624460F7200FAE3D4 /* Sources */, + 9A0C82D724460F7200FAE3D4 /* Frameworks */, + 9A0C82D824460F7200FAE3D4 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = StatsKit; + productName = StatsKit; + productReference = 9A0C82DA24460F7200FAE3D4 /* StatsKit.framework */; + productType = "com.apple.product-type.framework"; + }; 9A1410F4229E721100D29793 /* Stats */ = { isa = PBXNativeTarget; buildConfigurationList = 9A141105229E721200D29793 /* Build configuration list for PBXNativeTarget "Stats" */; @@ -422,12 +784,18 @@ 9A1410F2229E721100D29793 /* Frameworks */, 9A1410F3229E721100D29793 /* Resources */, 9AB54DAE22A19F96006192E0 /* Copy Files */, - 9A6698D82326A903001D00E1 /* Run Script */, 9A6698E72326AB16001D00E1 /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( + 9AABEAE3243FB13500668CB0 /* PBXTargetDependency */, + 9AABEB6A243FCE8A00668CB0 /* PBXTargetDependency */, + 9A0C82E024460F7200FAE3D4 /* PBXTargetDependency */, + 9A81C75C2449A41400825D92 /* PBXTargetDependency */, + 9AF9EE0824648751005D2270 /* PBXTargetDependency */, + 9A3E17D2247A94AF00449CD1 /* PBXTargetDependency */, + 9ABFF8FC248BEBCB00C9041A /* PBXTargetDependency */, ); name = Stats; productName = "Mini Stats"; @@ -451,16 +819,148 @@ productReference = 9A343527243E26A0006B19F9 /* LaunchAtLogin.app */; productType = "com.apple.product-type.application"; }; + 9A3E17CB247A94AF00449CD1 /* Net */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9A3E17D5247A94AF00449CD1 /* Build configuration list for PBXNativeTarget "Net" */; + buildPhases = ( + 9A3E17C7247A94AF00449CD1 /* Headers */, + 9A3E17C8247A94AF00449CD1 /* Sources */, + 9A3E17C9247A94AF00449CD1 /* Frameworks */, + 9A3E17CA247A94AF00449CD1 /* Resources */, + 9A3E17E6247A94DC00449CD1 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 9A3E17E1247A94DC00449CD1 /* PBXTargetDependency */, + 9A3E17E5247A94DC00449CD1 /* PBXTargetDependency */, + ); + name = Net; + productName = Net; + productReference = 9A3E17CC247A94AF00449CD1 /* Net.framework */; + productType = "com.apple.product-type.framework"; + }; + 9A81C7552449A41400825D92 /* Memory */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9A81C75F2449A41400825D92 /* Build configuration list for PBXNativeTarget "Memory" */; + buildPhases = ( + 9A81C7512449A41400825D92 /* Headers */, + 9A81C7522449A41400825D92 /* Sources */, + 9A81C7532449A41400825D92 /* Frameworks */, + 9A81C7542449A41400825D92 /* Resources */, + 9A81C7662449A41E00825D92 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 9A81C7652449A41E00825D92 /* PBXTargetDependency */, + 9A81C76E2449AE9400825D92 /* PBXTargetDependency */, + ); + name = Memory; + productName = Memory; + productReference = 9A81C7562449A41400825D92 /* Memory.framework */; + productType = "com.apple.product-type.framework"; + }; + 9AABEADC243FB13500668CB0 /* ModuleKit */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9AABEAE8243FB13500668CB0 /* Build configuration list for PBXNativeTarget "ModuleKit" */; + buildPhases = ( + 9AABEAD8243FB13500668CB0 /* Headers */, + 9AABEAD9243FB13500668CB0 /* Sources */, + 9AABEADA243FB13500668CB0 /* Frameworks */, + 9AABEADB243FB13500668CB0 /* Resources */, + 9A65492B2440A57200E30B74 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 9A0C82ED24460FB100FAE3D4 /* PBXTargetDependency */, + ); + name = ModuleKit; + productName = ModuleKit; + productReference = 9AABEADD243FB13500668CB0 /* ModuleKit.framework */; + productType = "com.apple.product-type.framework"; + }; + 9AABEB63243FCE8A00668CB0 /* CPU */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9AABEB6D243FCE8A00668CB0 /* Build configuration list for PBXNativeTarget "CPU" */; + buildPhases = ( + 9AABEB5F243FCE8A00668CB0 /* Headers */, + 9AABEB60243FCE8A00668CB0 /* Sources */, + 9AABEB61243FCE8A00668CB0 /* Frameworks */, + 9AABEB62243FCE8A00668CB0 /* Resources */, + 9AABEB76243FCEEF00668CB0 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 9AABEB75243FCEEF00668CB0 /* PBXTargetDependency */, + 9A3E17C0247A8F5700449CD1 /* PBXTargetDependency */, + ); + name = CPU; + productName = CPU; + productReference = 9AABEB64243FCE8A00668CB0 /* CPU.framework */; + productType = "com.apple.product-type.framework"; + }; + 9ABFF8F5248BEBCB00C9041A /* Battery */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9ABFF901248BEBCB00C9041A /* Build configuration list for PBXNativeTarget "Battery" */; + buildPhases = ( + 9ABFF8F1248BEBCB00C9041A /* Headers */, + 9ABFF8F2248BEBCB00C9041A /* Sources */, + 9ABFF8F3248BEBCB00C9041A /* Frameworks */, + 9ABFF8F4248BEBCB00C9041A /* Resources */, + 9ABFF90A248BEC2600C9041A /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 9ABFF909248BEC2600C9041A /* PBXTargetDependency */, + 9ABFF90E248BEC2900C9041A /* PBXTargetDependency */, + ); + name = Battery; + productName = Battery; + productReference = 9ABFF8F6248BEBCB00C9041A /* Battery.framework */; + productType = "com.apple.product-type.framework"; + }; + 9AF9EE0124648751005D2270 /* Disk */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9AF9EE0D24648751005D2270 /* Build configuration list for PBXNativeTarget "Disk" */; + buildPhases = ( + 9AF9EDFD24648751005D2270 /* Headers */, + 9AF9EDFE24648751005D2270 /* Sources */, + 9AF9EDFF24648751005D2270 /* Frameworks */, + 9AF9EE0024648751005D2270 /* Resources */, + 9AF9EE1824649BAD005D2270 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 9AF9EE1724649BAD005D2270 /* PBXTargetDependency */, + 9A3E17C4247A8F5E00449CD1 /* PBXTargetDependency */, + ); + name = Disk; + productName = Disk; + productReference = 9AF9EE0224648751005D2270 /* Disk.framework */; + productType = "com.apple.product-type.framework"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 9A1410ED229E721100D29793 /* Project object */ = { isa = PBXProject; attributes = { + KnownAssetTags = ( + New, + ); LastSwiftUpdateCheck = 1140; - LastUpgradeCheck = 1140; + LastUpgradeCheck = 1150; ORGANIZATIONNAME = "Serhiy Mytrovtsiy"; TargetAttributes = { + 9A0C82D924460F7200FAE3D4 = { + CreatedOnToolsVersion = 11.4; + LastSwiftMigration = 1140; + }; 9A1410F4229E721100D29793 = { CreatedOnToolsVersion = 10.2.1; LastSwiftMigration = 1030; @@ -476,6 +976,30 @@ 9A343526243E26A0006B19F9 = { CreatedOnToolsVersion = 11.4; }; + 9A3E17CB247A94AF00449CD1 = { + CreatedOnToolsVersion = 11.5; + LastSwiftMigration = 1150; + }; + 9A81C7552449A41400825D92 = { + CreatedOnToolsVersion = 11.4.1; + LastSwiftMigration = 1140; + }; + 9AABEADC243FB13500668CB0 = { + CreatedOnToolsVersion = 11.4; + LastSwiftMigration = 1140; + }; + 9AABEB63243FCE8A00668CB0 = { + CreatedOnToolsVersion = 11.4; + LastSwiftMigration = 1140; + }; + 9ABFF8F5248BEBCB00C9041A = { + CreatedOnToolsVersion = 11.5; + LastSwiftMigration = 1150; + }; + 9AF9EE0124648751005D2270 = { + CreatedOnToolsVersion = 11.4.1; + LastSwiftMigration = 1140; + }; }; }; buildConfigurationList = 9A1410F0229E721100D29793 /* Build configuration list for PBXProject "Stats" */; @@ -493,19 +1017,30 @@ targets = ( 9A1410F4229E721100D29793 /* Stats */, 9A343526243E26A0006B19F9 /* LaunchAtLogin */, + 9AABEADC243FB13500668CB0 /* ModuleKit */, + 9AABEB63243FCE8A00668CB0 /* CPU */, + 9A0C82D924460F7200FAE3D4 /* StatsKit */, + 9A81C7552449A41400825D92 /* Memory */, + 9AF9EE0124648751005D2270 /* Disk */, + 9A3E17CB247A94AF00449CD1 /* Net */, + 9ABFF8F5248BEBCB00C9041A /* Battery */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 9A0C82D824460F7200FAE3D4 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 9A1410F3229E721100D29793 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 9A6CFC0122A1C9F5001E782D /* Assets.xcassets in Resources */, - 9AFFCB3B22B3FD0500B0E6D8 /* About.storyboard in Resources */, - 9A141100229E721200D29793 /* Main.storyboard in Resources */, - 9A426DBE22C2BE0000C064C4 /* Updates.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -516,86 +1051,80 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 9A3E17CA247A94AF00449CD1 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9AB14B79248CEF4100DC6731 /* config.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9A81C7542449A41400825D92 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9AB14B77248CEF3500DC6731 /* config.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9AABEADB243FB13500668CB0 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9AA4A00A2443656D00ECCF07 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9AABEB62243FCE8A00668CB0 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9AB14B7B248CF00F00DC6731 /* config.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9ABFF8F4248BEBCB00C9041A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9AB14B7A248CEF4900DC6731 /* config.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9AF9EE0024648751005D2270 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9AB14B78248CEF3B00DC6731 /* config.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ -/* Begin PBXShellScriptBuildPhase section */ - 9A6698D82326A903001D00E1 /* Run Script */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 8; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "$(SRCROOT)/Carthage/Build/Mac/Charts.framework", - ); - name = "Run Script"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 1; - shellPath = /bin/sh; - shellScript = "/usr/local/bin/carthage copy-frameworks\n"; - }; -/* End PBXShellScriptBuildPhase section */ - /* Begin PBXSourcesBuildPhase section */ + 9A0C82D624460F7200FAE3D4 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9A81C7702449B8D500825D92 /* Charts.swift in Sources */, + 9A0C82E624460F9A00FAE3D4 /* extensions.swift in Sources */, + 9A0C82E724460F9C00FAE3D4 /* updater.swift in Sources */, + 9A0C82EE2446124800FAE3D4 /* SystemKit.swift in Sources */, + 9A9D728A24471FAE005CF997 /* SMC.swift in Sources */, + 9A0C82E824460F9E00FAE3D4 /* launchAtLogin.swift in Sources */, + 9A0C82E924460F9F00FAE3D4 /* store.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 9A1410F1229E721100D29793 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 9A2D15E623CE291600C4C417 /* RAM.swift in Sources */, - 9A09C8A222B3D94D0018426F /* BatteryWidget.swift in Sources */, - 9A5349C723D8535900C23824 /* NetworkPopup.swift in Sources */, - 9AA28DC32437752D00D2B196 /* SensorsMenu.swift in Sources */, - 9A426DB822C2B5EE00C064C4 /* MacAppUpdater.swift in Sources */, - 9A606B4C232157BA00642F51 /* AboutViewController.swift in Sources */, - 9AA28DC1243774ED00D2B196 /* Sensors.swift in Sources */, - 9AF0F32522DA92C400026AE6 /* NetworkText.swift in Sources */, - 9AF0F32322DA92B900026AE6 /* NetworkArrows.swift in Sources */, - 9A2D15D223CCEC7600C4C417 /* Module.swift in Sources */, - 9A2D160023CE40DE00C4C417 /* BatteryPopup.swift in Sources */, - 9A2D15FC23CE3C1A00C4C417 /* BatteryMenu.swift in Sources */, - 9A606B4A2321577400642F51 /* UpdatesViewController.swift in Sources */, - 9AF0F31D22DA925000026AE6 /* LineChartWithValue.swift in Sources */, - 9A2D160723CE462400C4C417 /* NetworkReader.swift in Sources */, - 9AA28DD6243A8A3D00D2B196 /* SMC.swift in Sources */, - 9AA28DD1243799E500D2B196 /* SensorsWidget.swift in Sources */, - 9A2D15FA23CE3BE600C4C417 /* Battery.swift in Sources */, - 9A54EF67232AB81800F7DC20 /* BatteryPercentageWidget.swift in Sources */, - 9A57A18522A1D26D0033E318 /* MenuBar.swift in Sources */, - 9A2D15D923CD036400C4C417 /* CPUMenu.swift in Sources */, - 9AF6F1FE231D732600B8E1E4 /* PopupViewController.swift in Sources */, - 9A1410F9229E721100D29793 /* AppDelegate.swift in Sources */, - 9A2D15E823CE29A400C4C417 /* RAMMenu.swift in Sources */, - 9A2D15DB23CD0B2100C4C417 /* CPUPopup.swift in Sources */, - 9A2D160523CE451B00C4C417 /* NetworkMenu.swift in Sources */, - 9AF0F32722DA92DD00026AE6 /* NetworkDotsText.swift in Sources */, - 9AF0F32922DA92E800026AE6 /* NetworkArrowsText.swift in Sources */, - 9A54EF69232AB82700F7DC20 /* BatteryTimeWidget.swift in Sources */, - 9A2D15D723CCFE1B00C4C417 /* CPULoadReader.swift in Sources */, - 9A3434F1243E19E6006B19F9 /* LaunchAtLogin.swift in Sources */, - 9A2D15F323CE391300C4C417 /* Disk.swift in Sources */, - 9A2D15F723CE3A1200C4C417 /* DiskCapacityReader.swift in Sources */, - 9A2D160323CE445900C4C417 /* Network.swift in Sources */, - 9A2D15F023CE32D500C4C417 /* RAMProcessReader.swift in Sources */, - 9AF0F31F22DA925700026AE6 /* BarChart.swift in Sources */, - 9A2D15EE23CE2EE200C4C417 /* RAMUsageReader.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 */, - 9AA28DDB243B4AF500D2B196 /* SensorsType.swift in Sources */, - 9A2D15D523CCEFF700C4C417 /* CPU.swift in Sources */, - 9A2D15E323CD1E4B00C4C417 /* CPUProcessReader.swift in Sources */, - 9A2D15EA23CE2C1100C4C417 /* RAMPopup.swift in Sources */, - 9A2D15FE23CE3DE300C4C417 /* BatteryReader.swift in Sources */, - 9A2D15F523CE393A00C4C417 /* DiskMenu.swift in Sources */, - 9A79B36A22D3BEE600BF1C3A /* Widget.swift in Sources */, - 9AF0F32122DA92AD00026AE6 /* NetworkDots.swift in Sources */, - 9A5349C923D8642A00C23824 /* NetworkInterfaceReader.swift in Sources */, + 9AABEB7E243FDEF100668CB0 /* main.swift in Sources */, + 9AABEB7A243FD26200668CB0 /* AppDelegate.swift in Sources */, + 9A9EA9452476D34500E3B883 /* Update.swift in Sources */, + 9A81C74E24499C7000825D92 /* Settings.swift in Sources */, + 9A81C74D24499C7000825D92 /* AppSettings.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -607,20 +1136,249 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 9A3E17C8247A94AF00449CD1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9A3E17DB247A94BC00449CD1 /* readers.swift in Sources */, + 9A3E17EA247B07BF00449CD1 /* popup.swift in Sources */, + 9A3E17D9247A94B500449CD1 /* main.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9A81C7522449A41400825D92 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9A81C76A2449A43600825D92 /* readers.swift in Sources */, + 9AA64260244B274200416A33 /* popup.swift in Sources */, + 9A81C7692449A43600825D92 /* main.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9AABEAD9243FB13500668CB0 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9A3E17E8247AA8E100449CD1 /* Network.swift in Sources */, + 9AA64264244B94F300416A33 /* LineChart.swift in Sources */, + 9A1A7ABA24561F0B00A84F7A /* BarChart.swift in Sources */, + 9A944D55244920690058F32A /* reader.swift in Sources */, + 9A7C61B42440DF810032695D /* Mini.swift in Sources */, + 9A944D5D24492A8B0058F32A /* popup.swift in Sources */, + 9ABFF912248BF39500C9041A /* Battery.swift in Sources */, + 9AABEAEA243FB15E00668CB0 /* module.swift in Sources */, + 9A944D5B244925720058F32A /* widget.swift in Sources */, + 9A81C75024499D6600825D92 /* settings.swift in Sources */, + 9A944D5F24492AA60058F32A /* Constants.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9AABEB60243FCE8A00668CB0 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9A944D59244920FE0058F32A /* readers.swift in Sources */, + 9A944D6124492B6D0058F32A /* popup.swift in Sources */, + 9AABEB71243FCE9400668CB0 /* main.swift in Sources */, + 9AA64262244B57C800416A33 /* settings.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9ABFF8F2248BEBCB00C9041A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9ABFF910248BEE7200C9041A /* readers.swift in Sources */, + 9ABFF914248C30A800C9041A /* popup.swift in Sources */, + 9ABFF903248BEBD700C9041A /* main.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9AF9EDFE24648751005D2270 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9A5AF11B2469CE9B00684737 /* popup.swift in Sources */, + 9AF9EE1124648ADC005D2270 /* readers.swift in Sources */, + 9AB7FD7C246B48DB00387FDA /* settings.swift in Sources */, + 9AF9EE0F2464875F005D2270 /* main.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ -/* Begin PBXVariantGroup section */ - 9A1410FE229E721200D29793 /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 9A1410FF229E721200D29793 /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; +/* Begin PBXTargetDependency section */ + 9A0C82E024460F7200FAE3D4 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9A0C82D924460F7200FAE3D4 /* StatsKit */; + targetProxy = 9A0C82DF24460F7200FAE3D4 /* PBXContainerItemProxy */; }; -/* End PBXVariantGroup section */ + 9A0C82ED24460FB100FAE3D4 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9A0C82D924460F7200FAE3D4 /* StatsKit */; + targetProxy = 9A0C82EC24460FB100FAE3D4 /* PBXContainerItemProxy */; + }; + 9A3E17C0247A8F5700449CD1 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9A0C82D924460F7200FAE3D4 /* StatsKit */; + targetProxy = 9A3E17BF247A8F5700449CD1 /* PBXContainerItemProxy */; + }; + 9A3E17C4247A8F5E00449CD1 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9A0C82D924460F7200FAE3D4 /* StatsKit */; + targetProxy = 9A3E17C3247A8F5E00449CD1 /* PBXContainerItemProxy */; + }; + 9A3E17D2247A94AF00449CD1 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9A3E17CB247A94AF00449CD1 /* Net */; + targetProxy = 9A3E17D1247A94AF00449CD1 /* PBXContainerItemProxy */; + }; + 9A3E17E1247A94DC00449CD1 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9AABEADC243FB13500668CB0 /* ModuleKit */; + targetProxy = 9A3E17E0247A94DC00449CD1 /* PBXContainerItemProxy */; + }; + 9A3E17E5247A94DC00449CD1 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9A0C82D924460F7200FAE3D4 /* StatsKit */; + targetProxy = 9A3E17E4247A94DC00449CD1 /* PBXContainerItemProxy */; + }; + 9A81C75C2449A41400825D92 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9A81C7552449A41400825D92 /* Memory */; + targetProxy = 9A81C75B2449A41400825D92 /* PBXContainerItemProxy */; + }; + 9A81C7652449A41E00825D92 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9AABEADC243FB13500668CB0 /* ModuleKit */; + targetProxy = 9A81C7642449A41E00825D92 /* PBXContainerItemProxy */; + }; + 9A81C76E2449AE9400825D92 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9A0C82D924460F7200FAE3D4 /* StatsKit */; + targetProxy = 9A81C76D2449AE9400825D92 /* PBXContainerItemProxy */; + }; + 9AABEAE3243FB13500668CB0 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9AABEADC243FB13500668CB0 /* ModuleKit */; + targetProxy = 9AABEAE2243FB13500668CB0 /* PBXContainerItemProxy */; + }; + 9AABEB6A243FCE8A00668CB0 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9AABEB63243FCE8A00668CB0 /* CPU */; + targetProxy = 9AABEB69243FCE8A00668CB0 /* PBXContainerItemProxy */; + }; + 9AABEB75243FCEEF00668CB0 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9AABEADC243FB13500668CB0 /* ModuleKit */; + targetProxy = 9AABEB74243FCEEF00668CB0 /* PBXContainerItemProxy */; + }; + 9ABFF8FC248BEBCB00C9041A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9ABFF8F5248BEBCB00C9041A /* Battery */; + targetProxy = 9ABFF8FB248BEBCB00C9041A /* PBXContainerItemProxy */; + }; + 9ABFF909248BEC2600C9041A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9AABEADC243FB13500668CB0 /* ModuleKit */; + targetProxy = 9ABFF908248BEC2600C9041A /* PBXContainerItemProxy */; + }; + 9ABFF90E248BEC2900C9041A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9A0C82D924460F7200FAE3D4 /* StatsKit */; + targetProxy = 9ABFF90D248BEC2900C9041A /* PBXContainerItemProxy */; + }; + 9AF9EE0824648751005D2270 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9AF9EE0124648751005D2270 /* Disk */; + targetProxy = 9AF9EE0724648751005D2270 /* PBXContainerItemProxy */; + }; + 9AF9EE1724649BAD005D2270 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9AABEADC243FB13500668CB0 /* ModuleKit */; + targetProxy = 9AF9EE1624649BAD005D2270 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ + 9A0C82E424460F7200FAE3D4 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = RP2S87B72W; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/Mac", + "$(LOCAL_LIBRARY_DIR)/Frameworks", + ); + INFOPLIST_FILE = StatsKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.StatsKit; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 9A0C82E524460F7200FAE3D4 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = NO; + CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = RP2S87B72W; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/Mac", + "$(LOCAL_LIBRARY_DIR)/Frameworks", + ); + INFOPLIST_FILE = StatsKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.StatsKit; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; 9A141103229E721200D29793 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -672,7 +1430,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -727,7 +1485,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; @@ -739,6 +1497,7 @@ 9A141106229E721200D29793 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = ""; CODE_SIGN_IDENTITY = "Mac Developer"; @@ -750,15 +1509,15 @@ FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", - "$(PROJECT_DIR)/Carthage/Build/Mac", + "$(LOCAL_LIBRARY_DIR)/Frameworks", ); INFOPLIST_FILE = "$(SRCROOT)/Stats/Supporting Files/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; - MARKETING_VERSION = 1.6.5; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MARKETING_VERSION = 2.0.0; PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -770,6 +1529,7 @@ 9A141107229E721200D29793 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = ""; CODE_SIGN_IDENTITY = "Mac Developer"; @@ -781,15 +1541,15 @@ FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", - "$(PROJECT_DIR)/Carthage/Build/Mac", + "$(LOCAL_LIBRARY_DIR)/Frameworks", ); INFOPLIST_FILE = "$(SRCROOT)/Stats/Supporting Files/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; - MARKETING_VERSION = 1.6.5; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MARKETING_VERSION = 2.0.0; PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -803,6 +1563,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = LaunchAtLogin/LaunchAtLogin.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_ASSET_PATHS = ""; @@ -814,7 +1575,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 10.14; PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.LaunchAtLogin; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -827,6 +1588,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = LaunchAtLogin/LaunchAtLogin.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_ASSET_PATHS = ""; @@ -838,7 +1600,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 10.14; PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.LaunchAtLogin; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -846,9 +1608,418 @@ }; name = Release; }; + 9A3E17D6247A94AF00449CD1 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = RP2S87B72W; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/Mac", + ); + INFOPLIST_FILE = Modules/Net/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.Net; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 9A3E17D7247A94AF00449CD1 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = RP2S87B72W; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/Mac", + ); + INFOPLIST_FILE = Modules/Net/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.Net; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 9A81C7602449A41400825D92 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = RP2S87B72W; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = Modules/Memory/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.Memory; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 9A81C7612449A41400825D92 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = RP2S87B72W; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = Modules/Memory/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.Memory; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 9AABEAE6243FB13500668CB0 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = RP2S87B72W; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/Mac", + ); + INFOPLIST_FILE = "ModuleKit/Supporting Files/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.ModuleKit; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 9AABEAE7243FB13500668CB0 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = RP2S87B72W; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/Mac", + ); + INFOPLIST_FILE = "ModuleKit/Supporting Files/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.ModuleKit; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 9AABEB6E243FCE8A00668CB0 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = RP2S87B72W; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_SEARCH_PATHS = ( + "$(PROJECT_DIR)/Carthage/Build/Mac", + "$(inherited)", + "$(LOCAL_LIBRARY_DIR)/Frameworks", + ); + INFOPLIST_FILE = Modules/CPU/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.CPU; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + REEXPORTED_LIBRARY_PATHS = ""; + SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 9AABEB6F243FCE8A00668CB0 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = RP2S87B72W; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_SEARCH_PATHS = ( + "$(PROJECT_DIR)/Carthage/Build/Mac", + "$(inherited)", + "$(LOCAL_LIBRARY_DIR)/Frameworks", + ); + INFOPLIST_FILE = Modules/CPU/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.CPU; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + REEXPORTED_LIBRARY_PATHS = ""; + SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 9ABFF8FF248BEBCB00C9041A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = RP2S87B72W; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = Modules/Battery/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.Battery; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 9ABFF900248BEBCB00C9041A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = RP2S87B72W; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = Modules/Battery/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.Battery; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 9AF9EE0B24648751005D2270 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_ASSET_PATHS = ""; + DEVELOPMENT_TEAM = RP2S87B72W; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = Modules/Disk/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.Disk; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 9AF9EE0C24648751005D2270 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_ASSET_PATHS = ""; + DEVELOPMENT_TEAM = RP2S87B72W; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = Modules/Disk/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + PRODUCT_BUNDLE_IDENTIFIER = eu.exelban.Stats.Disk; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 9A0C82E324460F7200FAE3D4 /* Build configuration list for PBXNativeTarget "StatsKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9A0C82E424460F7200FAE3D4 /* Debug */, + 9A0C82E524460F7200FAE3D4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 9A1410F0229E721100D29793 /* Build configuration list for PBXProject "Stats" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -876,6 +2047,60 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 9A3E17D5247A94AF00449CD1 /* Build configuration list for PBXNativeTarget "Net" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9A3E17D6247A94AF00449CD1 /* Debug */, + 9A3E17D7247A94AF00449CD1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9A81C75F2449A41400825D92 /* Build configuration list for PBXNativeTarget "Memory" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9A81C7602449A41400825D92 /* Debug */, + 9A81C7612449A41400825D92 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9AABEAE8243FB13500668CB0 /* Build configuration list for PBXNativeTarget "ModuleKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9AABEAE6243FB13500668CB0 /* Debug */, + 9AABEAE7243FB13500668CB0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9AABEB6D243FCE8A00668CB0 /* Build configuration list for PBXNativeTarget "CPU" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9AABEB6E243FCE8A00668CB0 /* Debug */, + 9AABEB6F243FCE8A00668CB0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9ABFF901248BEBCB00C9041A /* Build configuration list for PBXNativeTarget "Battery" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9ABFF8FF248BEBCB00C9041A /* Debug */, + 9ABFF900248BEBCB00C9041A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9AF9EE0D24648751005D2270 /* Build configuration list for PBXNativeTarget "Disk" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9AF9EE0B24648751005D2270 /* Debug */, + 9AF9EE0C24648751005D2270 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 9A1410ED229E721100D29793 /* Project object */; diff --git a/Stats.xcodeproj/xcshareddata/xcschemes/Stats.xcscheme b/Stats.xcodeproj/xcshareddata/xcschemes/Stats.xcscheme index d70d86ea..d75206f5 100644 --- a/Stats.xcodeproj/xcshareddata/xcschemes/Stats.xcscheme +++ b/Stats.xcodeproj/xcshareddata/xcschemes/Stats.xcscheme @@ -1,6 +1,6 @@ + allowLocationSimulation = "NO"> + + + + + + + + + + = activeModules.count-1 { - stackView.addArrangedSubview(module.first!.widget.view) - } else { - 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!.enabled { - view.first!.removeFromSuperview() - } else { - let newView = module.first!.widget.view - newView.invalidateIntrinsicContentSize() - self.stackView.replaceSubview(view.first!, with: newView) - } - } - - self.updateWidth() - self.popup.reload() - } - - /* - Refresh wigets views if size of view was changed. - For enabling/disabling widgets, please use reload(). - */ - public func refresh() { - self.stackView.subviews.forEach { view in - if !(view is Widget) { return } - - let module = self.modules.first { $0.name == (view as! Widget).name } - if module == nil { - return - } - - module!.widget.view.invalidateIntrinsicContentSize() - self.stackView.replaceSubview(view, with: module!.widget.view) - self.updateWidth() - } - } - - /* - 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.enabled && module.available { - WIDTH = WIDTH + module.widget.view.frame.size.width - } - } - - if self.stackView.subviews.count == 0 || WIDTH == 0 { - self.menuBarButton.image = NSImage(named:NSImage.Name("tray_icon")) - self.menuBarItem.length = widgetSize.width - self.stackView.frame.size.width = widgetSize.width - } else { - self.menuBarButton.image = nil - self.stackView.frame.size.width = WIDTH - self.menuBarItem.length = WIDTH - } - } -} diff --git a/Stats/Modules/Battery/Battery.swift b/Stats/Modules/Battery/Battery.swift deleted file mode 100644 index ab78a960..00000000 --- a/Stats/Modules/Battery/Battery.swift +++ /dev/null @@ -1,95 +0,0 @@ -// -// Battery.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 14/01/2020. -// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa -import IOKit.ps -import Repeat - -class Battery: Module { - public var name: String = "Battery" - - public var enabled: Bool = true - public var available: Bool { - get { - let snapshot = IOPSCopyPowerSourcesInfo().takeRetainedValue() - let sources = IOPSCopyPowerSourcesList(snapshot).takeRetainedValue() as Array - return sources.count > 0 - } - } - - public var readers: [Reader] = [] - public var task: Repeater? - - public var widget: ModuleWidget = ModuleWidget() - public var popup: ModulePopup = ModulePopup(true) - public var menu: NSMenuItem = NSMenuItem() - - internal let defaults = UserDefaults.standard - internal var submenu: NSMenu = NSMenu() - - internal var cyclesValue: NSTextField = NSTextField() - internal var stateValue: NSTextField = NSTextField() - internal var healthValue: NSTextField = NSTextField() - internal var amperageValue: NSTextField = NSTextField() - internal var voltageValue: NSTextField = NSTextField() - internal var temperatureValue: NSTextField = NSTextField() - internal var powerValue: NSTextField = NSTextField() - internal var chargingValue: NSTextField = NSTextField() - internal var levelValue: NSTextField = NSTextField() - internal var sourceValue: NSTextField = NSTextField() - internal var timeLabel: NSTextField = NSTextField() - internal var timeValue: NSTextField = NSTextField() - - init() { - if !self.available { return } - - 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.Battery - - self.initWidget() - self.initMenu() - self.initPopup() - - readers.append(BatteryReader(self.usageUpdater)) - } - - public func start() { - (readers[0] as! BatteryReader).start() - } - - public func stop() { - if readers.count > 0 { - (readers[0] as! BatteryReader).stop() - } - } - - public func restart() { - self.stop() - self.start() - } - - private func usageUpdater(value: BatteryUsage) { - self.popupUpdater(value: value) - - var time = value.timeToEmpty - if time == 0 && value.timeToCharge != 0 { - time = value.timeToCharge - } - - if self.widget.view is Widget { - (self.widget.view as! Widget).setValue(data: [abs(value.level), Double(time)]) - - if self.widget.view is BatteryWidget && value.level != 100 { - (self.widget.view as! BatteryWidget).setCharging(value: value.level > 0) - } else if self.widget.view is BatteryWidget && value.level == 100 { - (self.widget.view as! BatteryWidget).setCharging(value: false) - } - } - } -} - diff --git a/Stats/Modules/Battery/BatteryMenu.swift b/Stats/Modules/Battery/BatteryMenu.swift deleted file mode 100644 index ff1957e0..00000000 --- a/Stats/Modules/Battery/BatteryMenu.swift +++ /dev/null @@ -1,94 +0,0 @@ -// -// BatteryMenu.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 14/01/2020. -// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa - -extension Battery { - public func initMenu() { - 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.widget.type == Widgets.BatteryPercentage ? NSControl.StateValue.on : NSControl.StateValue.off - percentage.target = self - - let time = NSMenuItem(title: "Time", action: #selector(toggleWidget), keyEquivalent: "") - time.state = self.widget.type == 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.widget.view as? Widget { - for widgetMenu in view.menus { - submenu.addItem(widgetMenu) - } - } - - 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.enabled = state - menuBar!.reload(name: self.name) - - if !state { - menu.submenu = nil - } else { - menu.submenu = submenu - } - - self.restart() - } - - @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.widget.type == 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.widget.type = widgetCode - self.initWidget() - self.initMenu() - menuBar!.reload(name: self.name) - } -} diff --git a/Stats/Modules/Battery/BatteryPopup.swift b/Stats/Modules/Battery/BatteryPopup.swift deleted file mode 100644 index 3e8bbf08..00000000 --- a/Stats/Modules/Battery/BatteryPopup.swift +++ /dev/null @@ -1,257 +0,0 @@ -// -// BatteryPopup.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 14/01/2020. -// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa - -extension Battery { - public func initPopup() { - self.popup.view.view?.frame = NSRect(x: 0, y: 0, width: TabWidth, height: TabHeight) - - self.makeMain() - self.makeOverview() - self.makeBattery() - self.makePowerAdapter() - } - - private 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: 11, y: stackHeight*2, width: TabWidth - 19, height: stackHeight)) - level.orientation = .horizontal - level.distribution = .equalCentering - let levelLabel = LabelField(string: "Level") - self.levelValue = ValueField(string: "0 %") - level.addView(levelLabel, in: .center) - level.addView(self.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") - self.sourceValue = ValueField(string: "AC Power") - source.addView(sourceLabel, in: .center) - source.addView(self.sourceValue, in: .center) - - let time: NSStackView = NSStackView(frame: NSRect(x: 10, y: 0, width: TabWidth - 20, height: stackHeight)) - time.orientation = .horizontal - time.distribution = .equalCentering - self.timeLabel = LabelField(string: "Time to charge") - self.timeValue = ValueField(string: "Calculating") - time.addView(self.timeLabel, in: .center) - time.addView(self.timeValue, in: .center) - - vertical.addSubview(level) - vertical.addSubview(source) - vertical.addSubview(time) - - self.popup.view.view?.addSubview(vertical) - } - - private 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.popup.view.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") - self.cyclesValue = ValueField(string: "0") - cycles.addView(cyclesLabel, in: .center) - cycles.addView(self.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") - self.healthValue = ValueField(string: "Calculating") - health.addView(healthLabel, in: .center) - health.addView(self.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") - self.stateValue = ValueField(string: "Calculating") - state.addView(stateLabel, in: .center) - state.addView(self.stateValue, in: .center) - - vertical.addSubview(cycles) - vertical.addSubview(health) - vertical.addSubview(state) - - self.popup.view.view?.addSubview(vertical) - } - - private 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.popup.view.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") - self.amperageValue = ValueField(string: "0 mA") - amperage.addView(amperageLabel, in: .center) - amperage.addView(self.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") - self.voltageValue = ValueField(string: "0 V") - voltage.addView(voltageLabel, in: .center) - voltage.addView(self.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") - self.temperatureValue = ValueField(string: "0 °C") - temperature.addView(temperatureLabel, in: .center) - temperature.addView(self.temperatureValue, in: .center) - - vertical.addSubview(amperage) - vertical.addSubview(voltage) - vertical.addSubview(temperature) - - self.popup.view.view?.addSubview(vertical) - } - - private 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.popup.view.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") - self.powerValue = ValueField(string: "0 W") - power.addView(powerLabel, in: .center) - power.addView(self.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") - self.chargingValue = ValueField(string: "No") - charging.addView(chargingLabel, in: .center) - charging.addView(self.chargingValue, in: .center) - - vertical.addSubview(power) - vertical.addSubview(charging) - - self.popup.view.view?.addSubview(vertical) - } - - public func popupUpdater(value: BatteryUsage) { - if !self.popup.active && self.popup.initialized { return } - self.popup.initialized = true - - // makeMain - self.levelValue.stringValue = "\(Int(abs(value.level) * 100)) %" - self.sourceValue.stringValue = value.powerSource - if value.powerSource == "Battery Power" { - self.timeLabel.stringValue = "Time to discharge" - if value.timeToEmpty != -1 && value.timeToEmpty != 0 { - self.timeValue.stringValue = Double(value.timeToEmpty*60).printSecondsToHoursMinutesSeconds() - } - } else { - self.timeLabel.stringValue = "Time to charge" - if value.timeToCharge != -1 && value.timeToCharge != 0 { - self.timeValue.stringValue = Double(value.timeToCharge*60).printSecondsToHoursMinutesSeconds() - } - } - - if value.timeToEmpty == -1 || value.timeToEmpty == -1 { - self.timeValue.stringValue = "Calculating" - } - - if value.isCharged { - self.timeValue.stringValue = "Fully charged" - } - - // makeOverview - self.cyclesValue.stringValue = "\(value.cycles)" - self.stateValue.stringValue = value.state - self.healthValue.stringValue = "\(value.health) %" - - // makeBattery - self.amperageValue.stringValue = "\(abs(value.amperage)) mA" - self.voltageValue.stringValue = "\(value.voltage.roundTo(decimalPlaces: 2)) V" - self.temperatureValue.stringValue = "\(value.temperature) °C" - - // makePowerAdapter - self.powerValue.stringValue = value.powerSource == "Battery Power" ? "Not connected" : "\(value.ACwatts) W" - self.chargingValue.stringValue = value.level > 0 ? "Yes" : "No" - } -} diff --git a/Stats/Modules/Battery/BatteryReader.swift b/Stats/Modules/Battery/BatteryReader.swift deleted file mode 100644 index 1760410c..00000000 --- a/Stats/Modules/Battery/BatteryReader.swift +++ /dev/null @@ -1,189 +0,0 @@ -// -// BatteryReader.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 14/01/2020. -// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa -import IOKit.ps - -struct BatteryUsage { - var powerSource: String = "" - var state: String = "" - var isCharged: Bool = false - var level: 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 name: String = "Battery" - public var enabled: Bool = true - 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 - } - } - public var optional: Bool = false - public var initialized: Bool = false - public var callback: (BatteryUsage) -> Void = {_ in} - - private var service: io_connect_t = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleSmartBattery")) - private var internalChecked: Bool = false - private var hasInternalBattery: Bool = false - - private var source: CFRunLoopSource? - private var loop: CFRunLoop? - - init(_ updater: @escaping (BatteryUsage) -> Void) { - self.callback = updater - self.read() - } - - public func start() { - let context = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()) - - source = IOPSNotificationCreateRunLoopSource({ (context) in - guard let ctx = context else { - return - } - - let watcher = Unmanaged.fromOpaque(ctx).takeUnretainedValue() - watcher.read() - }, context).takeRetainedValue() - - loop = RunLoop.current.getCFRunLoop() - CFRunLoopAddSource(loop, source, .defaultMode) - } - - public func stop() { - guard let runLoop = loop, let source = source else { - return - } - - CFRunLoopRemoveSource(runLoop, source, .defaultMode) - } - - public func read() { - if !self.enabled && self.initialized { return } - self.initialized = true - - 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 { - guard let watts = ACList[kIOPSPowerAdapterWattsKey] else { - return - } - ACwatts = Int(watts as! Int) - } - } - let ACstatus = self.getBoolValue("IsCharging" as CFString) ?? false - - if powerSource == "Battery Power" { - cap = 0 - cap - } - - DispatchQueue.main.async(execute: { - let usage = BatteryUsage( - powerSource: powerSource, - state: state, - isCharged: isCharged, - level: Double(cap), - cycles: cycles, - health: (100 * maxCapacity) / designCapacity, - - amperage: amperage, - voltage: voltage, - temperature: temperature, - - ACwatts: ACwatts, - ACstatus: ACstatus, - - timeToEmpty: timeToEmpty, - timeToCharge: timeToCharged - ) - self.callback(usage) - }) - } - } - } - - public func toggleEnable(_ value: Bool) { - self.enabled = value - } - - private func getBoolValue(_ forIdentifier: CFString) -> Bool? { - if let value = IORegistryEntryCreateCFProperty(self.service, forIdentifier, kCFAllocatorDefault, 0) { - return value.takeRetainedValue() as? Bool - } - return nil - } - - private func getIntValue(_ identifier: CFString) -> Int? { - if let value = IORegistryEntryCreateCFProperty(self.service, identifier, kCFAllocatorDefault, 0) { - return value.takeRetainedValue() as? Int - } - return nil - } - - private func getDoubleValue(_ identifier: CFString) -> Double? { - if let value = IORegistryEntryCreateCFProperty(self.service, identifier, kCFAllocatorDefault, 0) { - return value.takeRetainedValue() as? Double - } - return nil - } - - private func getVoltage() -> Double? { - if let value = self.getDoubleValue("Voltage" as CFString) { - return value / 1000.0 - } - return nil - } - - private func getTemperature() -> Double? { - if let value = IORegistryEntryCreateCFProperty(self.service, "Temperature" as CFString, kCFAllocatorDefault, 0) { - return value.takeRetainedValue() as! Double / 100.0 - } - return nil - } -} diff --git a/Stats/Modules/CPU/CPU.swift b/Stats/Modules/CPU/CPU.swift deleted file mode 100644 index 0f07af19..00000000 --- a/Stats/Modules/CPU/CPU.swift +++ /dev/null @@ -1,80 +0,0 @@ -// -// CPU.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 01.06.2019. -// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa -import Charts -import Repeat - -class CPU: Module { - public var name: String = "CPU" - public var updateInterval: Double = 1 - - 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() - - internal let defaults = UserDefaults.standard - internal var submenu: NSMenu = NSMenu() - - internal var systemValue: NSTextField = NSTextField() - internal var userValue: NSTextField = NSTextField() - internal var idleValue: NSTextField = NSTextField() - internal var processViewList: [NSStackView] = [] - internal var chart: LineChartView = LineChartView() - - init() { - if !self.available { return } - - self.enabled = defaults.object(forKey: name) != nil ? defaults.bool(forKey: name) : true - self.updateInterval = defaults.object(forKey: "\(name)_interval") != nil ? defaults.double(forKey: "\(name)_interval") : self.updateInterval - self.widget.type = defaults.object(forKey: "\(name)_widget") != nil ? defaults.float(forKey: "\(name)_widget") : Widgets.Mini - - self.initWidget() - self.initMenu() - self.initPopup() - - readers.append(CPULoadReader(self.name, self.loadUpdater, self.chartUpdater, true)) - readers.append(CPUUsageReader(self.usageUpdater)) - readers.append(CPUProcessReader(self.processesUpdater)) - - 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) - } - } -} diff --git a/Stats/Modules/CPU/CPULoadReader.swift b/Stats/Modules/CPU/CPULoadReader.swift deleted file mode 100644 index 2277a886..00000000 --- a/Stats/Modules/CPU/CPULoadReader.swift +++ /dev/null @@ -1,144 +0,0 @@ -// -// 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 { - DispatchQueue.global(qos: .default).async { - 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/CPU/CPUMenu.swift b/Stats/Modules/CPU/CPUMenu.swift deleted file mode 100644 index f81b51ad..00000000 --- a/Stats/Modules/CPU/CPUMenu.swift +++ /dev/null @@ -1,206 +0,0 @@ -// -// CPUMenu.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 13/01/2020. -// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa - -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 - } else { - menu.state = NSControl.StateValue.on - } - menu.target = self - - let mini = NSMenuItem(title: "Mini", action: #selector(toggleWidget), keyEquivalent: "") - 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.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.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.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) - submenu.addItem(barChart) - - submenu.addItem(NSMenuItem.separator()) - - 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 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.enabled = state - menuBar!.reload(name: self.name) - - if !state { - menu.submenu = nil - } else { - menu.submenu = submenu - } - - self.restart() - } - - @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.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 - } - - 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.self.widget.type = widgetCode - self.initWidget() - self.initMenu() - 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.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() - 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: Double = 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.task?.reset(.seconds(interval), restart: self.task!.state.isRunning) - } -} diff --git a/Stats/Modules/CPU/CPUPopup.swift b/Stats/Modules/CPU/CPUPopup.swift deleted file mode 100644 index 03970fcd..00000000 --- a/Stats/Modules/CPU/CPUPopup.swift +++ /dev/null @@ -1,236 +0,0 @@ -// -// CPUPopup.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 03/09/2019. -// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa -import Charts - -extension CPU { - public func initPopup() { - self.popup.view.view?.frame = NSRect(x: 0, y: 0, width: TabWidth, height: TabHeight) - - makeChart() - makeOverview() - makeProcesses() - } - - 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.chart.autoScaleMinMaxEnabled = true - - self.chart.rightAxis.enabled = false - - self.chart.leftAxis.axisMinimum = 0 - self.chart.leftAxis.axisMaximum = 100 - self.chart.leftAxis.labelCount = 6 - self.chart.leftAxis.drawGridLinesEnabled = false - self.chart.leftAxis.drawAxisLineEnabled = false - - self.chart.leftAxis.gridColor = NSColor(red:220/255, green:220/255, blue:220/255, alpha:1) - self.chart.leftAxis.gridLineWidth = 0.5 - self.chart.leftAxis.drawGridLinesEnabled = true - self.chart.leftAxis.labelTextColor = NSColor(red:150/255, green:150/255, blue:150/255, alpha:1) - - self.chart.xAxis.drawAxisLineEnabled = false - self.chart.xAxis.drawLimitLinesBehindDataEnabled = false - self.chart.xAxis.gridLineWidth = 0.5 - self.chart.xAxis.drawGridLinesEnabled = false - self.chart.xAxis.drawLabelsEnabled = false - - let marker = ChartMarker() - marker.chartView = self.chart - self.chart.marker = marker - - var lineChartEntry = [ChartDataEntry]() - lineChartEntry.append(ChartDataEntry(x: 0, y: 0)) - 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.popup.view.view?.addSubview(self.chart) - } - - public func chartUpdater(value: Double) { - if self.chart.data == nil { return } - - let v: Double = Double((value * 100).roundTo(decimalPlaces: 2))! - - let index = Double((self.chart.data?.getDataSetByIndex(0)?.entryCount)!) - self.chart.data?.addEntry(ChartDataEntry(x: index, y: v), dataSetIndex: 0) - - if index > 120 { - self.chart.xAxis.axisMinimum = index - 120 - } - self.chart.xAxis.axisMaximum = index - - if self.popup.active { - self.chart.notifyDataSetChanged() - self.chart.moveViewToX(index) - } - } - - private 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.popup.view.view?.addSubview(overviewLabel) - - let stackHeight: CGFloat = 22 - let vertical: NSStackView = NSStackView(frame: NSRect(x: 0, y: 147, width: TabWidth, height: stackHeight*3)) - vertical.orientation = .vertical - - let system: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*2, width: TabWidth - 20, height: stackHeight)) - system.orientation = .horizontal - system.distribution = .equalCentering - let systemLabel = LabelField(string: "System") - self.systemValue = ValueField(string: "0 %") - system.addView(systemLabel, 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") - self.userValue = ValueField(string: "0 %") - user.addView(userLabel, 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") - self.idleValue = ValueField(string: "0 %") - idle.addView(idleLabel, in: .center) - idle.addView(self.idleValue, in: .center) - - vertical.addSubview(system) - vertical.addSubview(user) - vertical.addSubview(idle) - - self.popup.view.view?.addSubview(vertical) - } - - public func usageUpdater(value: CPUUsage) { - if !self.popup.active && self.popup.initialized { return } - - 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 - 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.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 - - 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: "") - - 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.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.popup.view.view?.addSubview(label) - } - - public func processesUpdater(value: [TopProcess]) { - if self.processViewList.isEmpty || !self.popup.active && self.popup.initialized { return } - self.popup.initialized = true - - 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)) %" - } - } - } - - 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 - 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/CPU/CPUProcessReader.swift b/Stats/Modules/CPU/CPUProcessReader.swift deleted file mode 100644 index 6d9dca6e..00000000 --- a/Stats/Modules/CPU/CPUProcessReader.swift +++ /dev/null @@ -1,93 +0,0 @@ -// -// 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 = "Process" - 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 { - DispatchQueue.global(qos: .default).async { - 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) - return - } - - 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,.]+ ") - let command = str.trimmingCharacters(in: .whitespaces) - - let pid = Int(pidString) ?? 0 - let usage = Double(usageString.replacingOccurrences(of: ",", with: ".")) ?? 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/CPUUsageReader.swift b/Stats/Modules/CPU/CPUUsageReader.swift deleted file mode 100644 index dc396e79..00000000 --- a/Stats/Modules/CPU/CPUUsageReader.swift +++ /dev/null @@ -1,82 +0,0 @@ -// -// 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 { - DispatchQueue.global(qos: .default).async { - 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 d78fbf65..00000000 --- a/Stats/Modules/Disk/Disk.swift +++ /dev/null @@ -1,90 +0,0 @@ -// -// Disk.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 14/01/2020. -// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa -import Repeat - -class Disk: Module { - public var name: String = "SSD" - public var updateInterval: Double = 5 - - 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(false) - public var menu: NSMenuItem = NSMenuItem() - - internal let defaults = UserDefaults.standard - internal var submenu: NSMenu = NSMenu() - internal var selectedDisk: String = "" - internal var disks: disksList = disksList() - - init() { - if !self.available { return } - - self.enabled = defaults.object(forKey: name) != nil ? defaults.bool(forKey: name) : true - self.updateInterval = defaults.object(forKey: "\(name)_interval") != nil ? defaults.double(forKey: "\(name)_interval") : self.updateInterval - self.widget.type = defaults.object(forKey: "\(name)_widget") != nil ? defaults.float(forKey: "\(name)_widget") : Widgets.Mini - self.selectedDisk = defaults.object(forKey: "\(name)_disk") != nil ? defaults.string(forKey: "\(name)_disk")! : self.selectedDisk - - self.initWidget() - self.initMenu() - - readers.append(DiskCapacityReader(self.usageUpdater)) - - 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 usageUpdater(disks: disksList) { - if self.disks.list.count != disks.list.count && disks.list.count != 0 { - self.disks = disks - self.initMenu() - } - - if self.widget.view is Widget { - var d: diskInfo? = disks.getDiskByBSDName(self.selectedDisk) - if d == nil { - d = disks.getRootDisk() - } - - if d != nil { - let total = d!.totalSize - let free = d!.freeSize - let usedSpace = total - free - let percentage = Double(usedSpace) / Double(total) - - (self.widget.view as! Widget).setValue(data: [percentage]) - } - } - } -} diff --git a/Stats/Modules/Disk/DiskMenu.swift b/Stats/Modules/Disk/DiskMenu.swift deleted file mode 100644 index 42ac543d..00000000 --- a/Stats/Modules/Disk/DiskMenu.swift +++ /dev/null @@ -1,201 +0,0 @@ -// -// DiskMenu.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 14/01/2020. -// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa - -extension Disk { - public func initMenu() { - 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 - - if self.disks.list.count > 1 { - self.disks.list.forEach { (d: diskInfo) in - let disk = NSMenuItem(title: d.name, action: #selector(toggleDisk), keyEquivalent: "") - if self.selectedDisk == "" && d.root { - disk.state = NSControl.StateValue.on - } else { - disk.state = self.selectedDisk == d.mediaBSDName ? NSControl.StateValue.on : NSControl.StateValue.off - } - disk.target = self - - submenu.addItem(disk) - } - } - - let mini = NSMenuItem(title: "Mini", action: #selector(toggleWidget), keyEquivalent: "") - mini.state = self.widget.type == Widgets.Mini ? NSControl.StateValue.on : NSControl.StateValue.off - mini.target = self - - let barChart = NSMenuItem(title: "Bar chart", action: #selector(toggleWidget), keyEquivalent: "") - barChart.state = self.widget.type == Widgets.BarChart ? NSControl.StateValue.on : NSControl.StateValue.off - barChart.target = self - - submenu.addItem(NSMenuItem.separator()) - - submenu.addItem(mini) - submenu.addItem(barChart) - - submenu.addItem(NSMenuItem.separator()) - - if let view = self.widget.view as? Widget { - for widgetMenu in view.menus { - submenu.addItem(widgetMenu) - } - } - - submenu.addItem(NSMenuItem.separator()) - submenu.addItem(generateIntervalMenu()) - - 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.enabled = state - menuBar!.reload(name: self.name) - - if !state { - menu.submenu = nil - } else { - menu.submenu = submenu - } - - self.restart() - } - - @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.widget.type == 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.widget.type = widgetCode - self.initWidget() - self.initMenu() - menuBar!.reload(name: self.name) - } - - private 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: Double = 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.task?.reset(.seconds(interval), restart: self.task!.state.isRunning) - } - - @objc func toggleDisk(_ sender: NSMenuItem) { - let name: String = sender.title - let d: diskInfo? = self.disks.getDiskByName(name) - if d == nil { - return - } - - if d!.mediaBSDName == self.selectedDisk { - return - } - - for item in self.submenu.items { - if self.disks.getDiskByName(item.title) != nil { - item.state = NSControl.StateValue.off - } - } - - sender.state = NSControl.StateValue.on - self.selectedDisk = d!.mediaBSDName - self.defaults.set(d!.mediaBSDName, forKey: "\(name)_disk") - menuBar!.reload(name: self.name) - } -} diff --git a/Stats/Modules/Module.swift b/Stats/Modules/Module.swift deleted file mode 100644 index c5f139f9..00000000 --- a/Stats/Modules/Module.swift +++ /dev/null @@ -1,115 +0,0 @@ -// -// Module.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 08.07.2019. -// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa -import Charts -import Repeat - -protocol Module: class { - var name: String { get } // module name - - var enabled: Bool { get } // determine if module is enabled or disabled - var available: Bool { get } // determine if module is available on this PC - - var widget: ModuleWidget { get set } // view for widget - var menu: NSMenuItem { get } // view for menu - var popup: ModulePopup { get set } // popup - - var readers: [Reader] { get } // list of readers available for module - var task: Repeater? { get set } // reader cron task - - func start() // start module internal processes - func stop() // stop module internal processes - func restart() // restart module internal processes - - func initWidget() -} - -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 active: Bool = false // indicate that popup is opened and selected this view - var initialized: Bool = false // allows to set some value when on first load - - init(_ a: Bool = true) { - available = a - } - - mutating func setActive(_ state: Bool) { - if self.active != state { - self.active = state - } - } -} - -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.widget.type { - case Widgets.Mini: - widget = Mini() - case Widgets.Sensors: - widget = SensorsWidget() - case Widgets.Chart: - widget = Chart() - case Widgets.ChartWithValue: - widget = ChartWithValue() - case Widgets.NetworkDots: - widget = NetworkDotsView() - case Widgets.NetworkArrows: - widget = NetworkArrowsView() - case Widgets.NetworkText: - widget = NetworkTextView() - case Widgets.NetworkDotsWithText: - widget = NetworkDotsTextView() - case Widgets.NetworkArrowsWithText: - widget = NetworkArrowsTextView() - case Widgets.BarChart: - widget = BarChart() - case Widgets.Battery: - widget = BatteryWidget() - case Widgets.BatteryPercentage: - widget = BatteryPercentageWidget() - case Widgets.BatteryTime: - widget = BatteryTimeWidget() - default: - widget = Mini() - } - - widget.name = self.name - widget.start() - - self.readers.forEach { reader in - reader.read() - } - - 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 79231022..00000000 --- a/Stats/Modules/Network/Network.swift +++ /dev/null @@ -1,85 +0,0 @@ -// -// Network.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 14/01/2020. -// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa -import Charts -import Repeat - -class Network: Module { - public var name: String = "Network" - public var updateInterval: Double = 1 - - 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() - - internal let defaults = UserDefaults.standard - internal var submenu: NSMenu = NSMenu() - internal var chart: LineChartView = LineChartView() - - internal var publicIPValue: NSTextField = NSTextField() - internal var localIPValue: NSTextField = NSTextField() - internal var networkValue: NSTextField = NSTextField() - internal var physicalValue: NSTextField = NSTextField() - internal var downloadValue: NSTextField = NSTextField() - internal var uploadValue: NSTextField = NSTextField() - internal var totalDownloadValue: NSTextField = NSTextField() - internal var totalUploadValue: NSTextField = NSTextField() - - init() { - if !self.available { return } - - 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.NetworkDots - - self.initWidget() - self.initMenu() - self.initPopup() - - readers.append(NetworkReader(self.usageUpdater)) - readers.append(NetworkInterfaceReader(self.overviewUpdater)) - - 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 usageUpdater(value: NetworkUsage) { - self.dataUpdater(value: value) - self.chartUpdater(value: value) - - if self.widget.view is Widget { - (self.widget.view as! Widget).setValue(data: [Double(value.download), Double(value.upload)]) - } - } -} diff --git a/Stats/Modules/Network/NetworkInterfaceReader.swift b/Stats/Modules/Network/NetworkInterfaceReader.swift deleted file mode 100644 index a142d301..00000000 --- a/Stats/Modules/Network/NetworkInterfaceReader.swift +++ /dev/null @@ -1,277 +0,0 @@ -// -// NetworkInterfaceReader.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 22/01/2020. -// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa -import CoreWLAN -import SystemConfiguration -import Reachability - -struct NetworkInterface { - var active: Bool - - var localIP: String? - var publicIP: String? - var countryCode: String? - - var networkType: String? - var macAddress: String? - var wifiName: String? - - var force: Bool = false - - init( - active: Bool = false, - localIP: String? = nil, - publicIP: String? = nil, - countryCode: String? = nil, - networkType: String? = nil, - macAddress: String? = nil, - wifiName: String? = nil, - force: Bool = false - ) { - self.active = active - - self.localIP = localIP - self.publicIP = publicIP - self.countryCode = countryCode - - self.networkType = networkType - self.macAddress = macAddress - self.wifiName = wifiName - - self.force = force - } -} - -class NetworkInterfaceReader: Reader { - public var name: String = "Interface" - public var enabled: Bool = false - public var available: Bool = true - public var optional: Bool = true - public var initialized: Bool = false - public var callback: (NetworkInterface) -> Void = {_ in} - - private var uploadValue: Int64 = 0 - private var downloadValue: Int64 = 0 - - private var publicIP: String? = nil - private var reachability: Reachability? = nil - private var forceRead: Bool = false - - private var repeatCounter: Int8 = 0 - - init(_ updater: @escaping (NetworkInterface) -> Void) { - do { - self.reachability = try Reachability() - } catch let error { - print("initialize Reachability \(error)") - } - self.callback = updater - - if self.available { - DispatchQueue.global(qos: .default).async { - self.read() - } - } - - if self.reachability != nil { - self.reachability!.whenReachable = { reachability in - self.repeatCounter = 0 - self.forceRead = true - self.read() - } - self.reachability!.whenUnreachable = { _ in - self.forceRead = true - self.read() - } - - do { - try self.reachability!.startNotifier() - } catch { - print("Unable to start notifier") - } - } - } - - public func read() { - if (!self.enabled && self.initialized && !self.forceRead) || self.reachability == nil { return } - self.initialized = true - - var result = NetworkInterface(active: false) - result.force = self.forceRead - if self.forceRead { - self.forceRead = false - } - - if self.reachability!.connection != .unavailable && isConnectedToNetwork() { - if self.publicIP == nil { - DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 3, execute: { - if self.repeatCounter < 5 { - self.publicIP = self.getPublicIP() - self.forceRead = true - self.read() - self.repeatCounter += 1 - } else { - self.publicIP = "Unknown" - } - }) - } - - result.active = true - - if self.reachability!.connection == .wifi && CWWiFiClient.shared().interface() != nil { - result.networkType = "Wi-Fi" - result.wifiName = CWWiFiClient.shared().interface()!.ssid() - result.countryCode = CWWiFiClient.shared().interface()!.countryCode() - result.macAddress = CWWiFiClient.shared().interface()!.hardwareAddress() - } else { - result.networkType = "Ethernet" - result.macAddress = getMacAddress() - } - - result.localIP = getLocalIP() - result.publicIP = publicIP - } else { - self.publicIP = nil - } - - DispatchQueue.main.async(execute: { - self.callback(result) - }) - } - - private func isWIFIActive() -> Bool { - guard let interfaceNames = CWWiFiClient.interfaceNames() else { - return false - } - - for interfaceName in interfaceNames { - let interface = CWWiFiClient.shared().interface(withName: interfaceName) - - if interface?.ssid() != nil { - return true - } - } - return false - } - - // https://stackoverflow.com/questions/31835418/how-to-get-mac-address-from-os-x-with-swift - private func getMacAddress() -> String? { - var macAddressAsString : String? - if let intfIterator = FindEthernetInterfaces() { - if let macAddress = GetMACAddress(intfIterator) { - macAddressAsString = macAddress.map( { String(format:"%02x", $0) } ).joined(separator: ":") - } - IOObjectRelease(intfIterator) - } - return macAddressAsString - } - - private func FindEthernetInterfaces() -> io_iterator_t? { - let matchingDictUM = IOServiceMatching("IOEthernetInterface"); - if matchingDictUM == nil { - return nil - } - - let matchingDict = matchingDictUM! as NSMutableDictionary - matchingDict["IOPropertyMatch"] = [ "IOPrimaryInterface" : true] - - var matchingServices : io_iterator_t = 0 - if IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &matchingServices) != KERN_SUCCESS { - return nil - } - - return matchingServices - } - - private func GetMACAddress(_ intfIterator : io_iterator_t) -> [UInt8]? { - var macAddress : [UInt8]? - var intfService = IOIteratorNext(intfIterator) - - while intfService != 0 { - var controllerService : io_object_t = 0 - if IORegistryEntryGetParentEntry(intfService, kIOServicePlane, &controllerService) == KERN_SUCCESS { - let dataUM = IORegistryEntryCreateCFProperty(controllerService, "IOMACAddress" as CFString, kCFAllocatorDefault, 0) - if dataUM != nil { - let data = (dataUM!.takeRetainedValue() as! CFData) as Data - macAddress = [0, 0, 0, 0, 0, 0] - data.copyBytes(to: &macAddress!, count: macAddress!.count) - } - IOObjectRelease(controllerService) - } - - IOObjectRelease(intfService) - intfService = IOIteratorNext(intfIterator) - } - - return macAddress - } - - private func getPublicIP() -> String? { - let url = URL(string: "https://api.ipify.org") - var address: String? = nil - - do { - if let url = url { - address = try String(contentsOf: url) - if address!.contains("<") { - address = nil - } - } - } catch let error { - print("get public ip \(error)") - } - - return address - } - - private func getLocalIP() -> String { - var address: String = "" - - // Get list of all interfaces on the local machine: - var ifaddr : UnsafeMutablePointer? - guard getifaddrs(&ifaddr) == 0 else { return "" } - guard let firstAddr = ifaddr else { return "" } - - // For each interface ... - for ifptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) { - let interface = ifptr.pointee - - // Check for IPv4 or IPv6 interface: - let addrFamily = interface.ifa_addr.pointee.sa_family - if addrFamily == UInt8(AF_INET) || addrFamily == UInt8(AF_INET6) { - - // Check interface name: - let name = String(cString: interface.ifa_name) - if name == "en0" { - - // Convert interface address to a human readable string: - var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST)) - getnameinfo(interface.ifa_addr, socklen_t(interface.ifa_addr.pointee.sa_len), - &hostname, socklen_t(hostname.count), - nil, socklen_t(0), NI_NUMERICHOST) - address = String(cString: hostname) - } else if name == "en1" { - // Convert interface address to a human readable string: - var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST)) - getnameinfo(interface.ifa_addr, socklen_t(interface.ifa_addr.pointee.sa_len), - &hostname, socklen_t(hostname.count), - nil, socklen_t(1), NI_NUMERICHOST) - address = String(cString: hostname) - } - } - } - freeifaddrs(ifaddr) - - return address - } - - public func toggleEnable(_ value: Bool) { - self.enabled = value - } -} diff --git a/Stats/Modules/Network/NetworkMenu.swift b/Stats/Modules/Network/NetworkMenu.swift deleted file mode 100644 index 29b6134f..00000000 --- a/Stats/Modules/Network/NetworkMenu.swift +++ /dev/null @@ -1,118 +0,0 @@ -// -// NetworkMenu.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 14/01/2020. -// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa - -extension Network { - public func initMenu() { - 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.widget.type == Widgets.NetworkDots ? NSControl.StateValue.on : NSControl.StateValue.off - dots.target = self - - let arrows = NSMenuItem(title: "Arrows", action: #selector(toggleWidget), keyEquivalent: "") - arrows.state = self.widget.type == Widgets.NetworkArrows ? NSControl.StateValue.on : NSControl.StateValue.off - arrows.target = self - - let text = NSMenuItem(title: "Text", action: #selector(toggleWidget), keyEquivalent: "") - text.state = self.widget.type == 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.widget.type == 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.widget.type == Widgets.NetworkArrowsWithText ? NSControl.StateValue.on : NSControl.StateValue.off - arrowsWithText.target = self - - let chart = NSMenuItem(title: "Chart", action: #selector(toggleWidget), keyEquivalent: "") - chart.state = self.widget.type == 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.widget.view as? Widget { - for widgetMenu in view.menus { - submenu.addItem(widgetMenu) - } - } - - 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.enabled = state - menuBar!.reload(name: self.name) - - if !state { - menu.submenu = nil - } else { - menu.submenu = submenu - } - - self.restart() - } - - @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.widget.type == 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.widget.type = widgetCode - initWidget() - menuBar!.reload(name: self.name) - } -} diff --git a/Stats/Modules/Network/NetworkPopup.swift b/Stats/Modules/Network/NetworkPopup.swift deleted file mode 100644 index 44ffd5a6..00000000 --- a/Stats/Modules/Network/NetworkPopup.swift +++ /dev/null @@ -1,284 +0,0 @@ -// -// NetworkPopup.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 22/01/2020. -// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa -import Charts - -extension Network { - public func initPopup() { - self.popup.view.view?.frame = NSRect(x: 0, y: 0, width: TabWidth, height: TabHeight) - - makeChart() - makeOverview() - makeDataOverview() - } - - private func makeChart() { - let downloadLineColor: NSColor = NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 1.0) - let downloadGradientColor: NSColor = NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 0.5) - - let uploadLineColor: NSColor = NSColor(red: (1), green: (0), blue: (0), alpha: 1.0) - let uploadGradientColor: NSColor = NSColor(red: (1), green: (0), blue: (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.autoScaleMinMaxEnabled = true - - self.chart.rightAxis.enabled = false - - self.chart.leftAxis.valueFormatter = ChartsNetworkAxisFormatter() - self.chart.leftAxis.axisMinimum = 0 - 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 = ChartNetworkMarker() - marker.chartView = self.chart - self.chart.marker = marker - - var downloadLineChartEntry = [ChartDataEntry]() - downloadLineChartEntry.append(ChartDataEntry(x: 0, y: 0)) - let download = LineChartDataSet(entries: downloadLineChartEntry, label: "Download") - download.drawCirclesEnabled = false - download.mode = .cubicBezier - download.cubicIntensity = 0.1 - download.colors = [downloadLineColor] - download.fillColor = downloadGradientColor - download.drawFilledEnabled = true - - var uploadLineChartEntry = [ChartDataEntry]() - uploadLineChartEntry.append(ChartDataEntry(x: 0, y: 0)) - let upload = LineChartDataSet(entries: uploadLineChartEntry, label: "Upload") - upload.drawCirclesEnabled = false - upload.mode = .cubicBezier - upload.cubicIntensity = 0.1 - upload.colors = [uploadLineColor] - upload.fillColor = uploadGradientColor - upload.drawFilledEnabled = true - - let data = LineChartData() - data.addDataSet(download) - data.addDataSet(upload) - data.setDrawValues(false) - - self.chart.data = data - self.popup.view.view?.addSubview(self.chart) - } - - public func chartUpdater(value: NetworkUsage) { - if self.chart.data == nil { return } - - let index = Double((self.chart.data?.getDataSetByIndex(0)?.entryCount)!) - self.chart.data?.addEntry(ChartDataEntry(x: index, y: Double(value.download)), dataSetIndex: 0) - self.chart.data?.addEntry(ChartDataEntry(x: index, y: Double(value.upload)), dataSetIndex: 1) - - if index > 120 { - self.chart.xAxis.axisMinimum = index - 120 - } - self.chart.xAxis.axisMaximum = index - - if self.popup.active { - self.chart.notifyDataSetChanged() - self.chart.moveViewToX(index) - } - } - - private 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.popup.view.view?.addSubview(overviewLabel) - - let stackHeight: CGFloat = 22 - let vertical: NSStackView = NSStackView(frame: NSRect(x: 0, y: 125, width: TabWidth, height: stackHeight*4)) - vertical.orientation = .vertical - - let publicIP: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*3, width: TabWidth - 20, height: stackHeight)) - publicIP.orientation = .horizontal - publicIP.distribution = .equalCentering - let publicIPLabel = LabelField(string: "Public IP") - self.publicIPValue = ValueField(string: "No connection") - publicIP.addView(publicIPLabel, in: .center) - publicIP.addView(self.publicIPValue, in: .center) - - let localIP: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*2, width: TabWidth - 20, height: stackHeight)) - localIP.orientation = .horizontal - localIP.distribution = .equalCentering - let localIPLabel = LabelField(string: "Local IP") - self.localIPValue = ValueField(string: "No connection") - localIP.addView(localIPLabel, in: .center) - localIP.addView(self.localIPValue, in: .center) - - let network: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*1, width: TabWidth - 20, height: stackHeight)) - network.orientation = .horizontal - network.distribution = .equalCentering - let networkLabel = LabelField(string: "Network") - self.networkValue = ValueField(string: "No connection") - network.addView(networkLabel, in: .center) - network.addView(self.networkValue, in: .center) - - let physical: NSStackView = NSStackView(frame: NSRect(x: 10, y: 0, width: TabWidth - 20, height: stackHeight)) - physical.orientation = .horizontal - physical.distribution = .equalCentering - let physicalLabel = LabelField(string: "Physical address") - self.physicalValue = ValueField(string: "No connection") - physical.addView(physicalLabel, in: .center) - physical.addView(self.physicalValue, in: .center) - - vertical.addSubview(publicIP) - vertical.addSubview(localIP) - vertical.addSubview(network) - vertical.addSubview(physical) - - self.popup.view.view?.addSubview(vertical) - } - - public func overviewUpdater(value: NetworkInterface) { - if !self.popup.active && self.popup.initialized && !value.force { return } - self.popup.initialized = true - - if !value.active { - self.clearOverview() - return - } - - if let publicIP = value.publicIP { -// if value.countryCode != nil { -// publicIP = "\(publicIP) (\(value.countryCode!))" -// } - self.publicIPValue.stringValue = publicIP - } - if let localIP = value.localIP { - self.localIPValue.stringValue = localIP - } - if var networkType = value.networkType { - if value.wifiName != nil { - networkType = "\(value.wifiName!) (\(networkType))" - } - self.networkValue.stringValue = networkType - } - if let macAddress = value.macAddress { - self.physicalValue.stringValue = macAddress.uppercased() - } - } - - private func clearOverview() { - self.publicIPValue.stringValue = "No connection" - self.localIPValue.stringValue = "No connection" - self.networkValue.stringValue = "No connection" - self.physicalValue.stringValue = "No connection" - } - - private func makeDataOverview() { - let label: NSView = NSView(frame: NSRect(x: 0, y: 95, width: TabWidth, height: 25)) - - label.wantsLayer = true - label.layer?.backgroundColor = NSColor(hexString: "#eeeeee", alpha: 0.5).cgColor - - let text: NSTextField = NSTextField(string: "Data overview") - 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.popup.view.view?.addSubview(label) - - let stackHeight: CGFloat = 22 - let vertical: NSStackView = NSStackView(frame: NSRect(x: 0, y: 4, width: TabWidth, height: stackHeight*4)) - vertical.orientation = .vertical - - let upload: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*3, width: TabWidth - 20, height: stackHeight)) - upload.orientation = .horizontal - upload.distribution = .equalCentering - let uploadLabel = LabelField(string: "Upload") - self.uploadValue = ValueField(string: "0 KB/s") - upload.addView(uploadLabel, in: .center) - upload.addView(self.uploadValue, in: .center) - - let download: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*2, width: TabWidth - 20, height: stackHeight)) - download.orientation = .horizontal - download.distribution = .equalCentering - let downloadLabel = LabelField(string: "Download") - self.downloadValue = ValueField(string: "0 KB/s") - download.addView(downloadLabel, in: .center) - download.addView(self.downloadValue, in: .center) - - let totalUpload: NSStackView = NSStackView(frame: NSRect(x: 10, y: stackHeight*1, width: TabWidth - 20, height: stackHeight)) - totalUpload.orientation = .horizontal - totalUpload.distribution = .equalCentering - let totalUploadLabel = LabelField(string: "Total upload") - self.totalUploadValue = ValueField(string: "0 KB") - totalUpload.addView(totalUploadLabel, in: .center) - totalUpload.addView(self.totalUploadValue, in: .center) - - let totalDownload: NSStackView = NSStackView(frame: NSRect(x: 10, y: 0, width: TabWidth - 20, height: stackHeight)) - totalDownload.orientation = .horizontal - totalDownload.distribution = .equalCentering - let totalDownloadLabel = LabelField(string: "Total download") - self.totalDownloadValue = ValueField(string: "0 KB") - totalDownload.addView(totalDownloadLabel, in: .center) - totalDownload.addView(self.totalDownloadValue, in: .center) - - vertical.addSubview(upload) - vertical.addSubview(download) - vertical.addSubview(totalUpload) - vertical.addSubview(totalDownload) - - self.popup.view.view?.addSubview(vertical) - } - - public func dataUpdater(value: NetworkUsage) { - if !self.popup.active && self.popup.initialized { return } - - self.downloadValue.stringValue = Units(bytes: value.download).getReadableSpeed() - self.uploadValue.stringValue = Units(bytes: value.upload).getReadableSpeed() - - self.totalDownloadValue.stringValue = Units(bytes: value.totalDownload).getReadableMemory() - self.totalUploadValue.stringValue = Units(bytes: value.totalUpload).getReadableMemory() - } -} diff --git a/Stats/Modules/Network/NetworkReader.swift b/Stats/Modules/Network/NetworkReader.swift deleted file mode 100644 index b65497fa..00000000 --- a/Stats/Modules/Network/NetworkReader.swift +++ /dev/null @@ -1,99 +0,0 @@ -// -// NetworkReader.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 14/01/2020. -// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa - -struct NetworkUsage { - var download: Int64 = 0 - var upload: Int64 = 0 - - var totalDownload: Int64 = 0 - var totalUpload: Int64 = 0 -} - -class NetworkReader: Reader { - public var name: String = "Network" - public var enabled: Bool = true - public var available: Bool = true - public var optional: Bool = false - public var initialized: Bool = false - public var callback: (NetworkUsage) -> Void = {_ in} - - private var uploadValue: Int64 = 0 - private var downloadValue: Int64 = 0 - - init(_ updater: @escaping (NetworkUsage) -> Void) { - self.callback = updater - - if self.available { - DispatchQueue.global(qos: .default).async { - self.read() - } - } - } - - public func read() { - if !self.enabled && self.initialized { return } - self.initialized = true - - 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.callback(NetworkUsage( - download: download - lastDownload, - upload: upload - lastUpload, - totalDownload: download, - totalUpload: upload - )) - }) - } - - self.uploadValue = upload - self.downloadValue = download - } - - public func toggleEnable(_ value: Bool) { - self.enabled = value - } - - private 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 - } -} diff --git a/Stats/Modules/RAM/RAM.swift b/Stats/Modules/RAM/RAM.swift deleted file mode 100644 index 889c87ae..00000000 --- a/Stats/Modules/RAM/RAM.swift +++ /dev/null @@ -1,82 +0,0 @@ -// -// RAM.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 14/01/2020. -// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa -import Charts -import Repeat - -class RAM: Module { - public var name: String = "RAM" - public var updateInterval: Double = 1 - - 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() - - internal let defaults = UserDefaults.standard - internal var submenu: NSMenu = NSMenu() - - internal var totalValue: NSTextField = NSTextField() - internal var usedValue: NSTextField = NSTextField() - internal var freeValue: NSTextField = NSTextField() - internal var processViewList: [NSStackView] = [] - internal var chart: LineChartView = LineChartView() - - init() { - if !self.available { return } - - self.enabled = defaults.object(forKey: name) != nil ? defaults.bool(forKey: name) : true - self.updateInterval = defaults.object(forKey: "\(name)_interval") != nil ? defaults.double(forKey: "\(name)_interval") : self.updateInterval - self.widget.type = defaults.object(forKey: "\(name)_widget") != nil ? defaults.float(forKey: "\(name)_widget") : Widgets.Mini - - readers.append(RAMUsageReader(self.usageUpdater)) - readers.append(RAMProcessReader(self.processesUpdater)) - - self.initWidget() - self.initMenu() - self.initPopup() - - 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 usageUpdater(value: RAMUsage) { - self.chartUpdater(value: value) - self.overviewUpdater(value: value) - - if self.widget.view is Widget { - (self.widget.view as! Widget).setValue(data: [value.usage]) - } - } -} diff --git a/Stats/Modules/RAM/RAMMenu.swift b/Stats/Modules/RAM/RAMMenu.swift deleted file mode 100644 index 6e94c02f..00000000 --- a/Stats/Modules/RAM/RAMMenu.swift +++ /dev/null @@ -1,176 +0,0 @@ -// -// RAMMenu.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 14/01/2020. -// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa - -extension RAM { - public func initMenu() { - 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.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.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.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.widget.type == Widgets.BarChart ? NSControl.StateValue.on : NSControl.StateValue.off - barChart.target = self - - submenu.addItem(mini) - submenu.addItem(chart) - submenu.addItem(chartWithValue) - submenu.addItem(barChart) - - submenu.addItem(NSMenuItem.separator()) - - if let view = self.widget.view as? Widget { - for widgetMenu in view.menus { - submenu.addItem(widgetMenu) - } - } - - submenu.addItem(NSMenuItem.separator()) - submenu.addItem(generateIntervalMenu()) - - 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.enabled = state - menuBar!.reload(name: self.name) - - if !state { - menu.submenu = nil - } else { - menu.submenu = submenu - } - - self.restart() - } - - @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 self.widget.type == 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.widget.type = widgetCode - self.initWidget() - self.initMenu() - 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: Double = 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.task?.reset(.seconds(interval), restart: self.task!.state.isRunning) - } -} diff --git a/Stats/Modules/RAM/RAMPopup.swift b/Stats/Modules/RAM/RAMPopup.swift deleted file mode 100644 index 70841765..00000000 --- a/Stats/Modules/RAM/RAMPopup.swift +++ /dev/null @@ -1,237 +0,0 @@ -// -// RAMPopup.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 14/01/2020. -// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa -import Charts - -extension RAM { - public func initPopup() { - self.popup.view.view?.frame = NSRect(x: 0, y: 0, width: TabWidth, height: TabHeight) - - makeChart() - makeOverview() - makeProcesses() - } - - 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.chart.autoScaleMinMaxEnabled = true - - self.chart.rightAxis.enabled = false - - let v = self.readers.filter{ $0 is RAMUsageReader }.first as! RAMUsageReader - self.chart.leftAxis.axisMinimum = 0 - self.chart.leftAxis.axisMaximum = Units(bytes: Int64(v.totalSize)).gigabytes - self.chart.leftAxis.labelCount = Units(bytes: Int64(v.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 - - var lineChartEntry = [ChartDataEntry]() - lineChartEntry.append(ChartDataEntry(x: 0, y: 0)) - 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.popup.view.view?.addSubview(self.chart) - } - - public func chartUpdater(value: RAMUsage) { - if self.chart.data == nil { return } - - let index = Double((self.chart.data?.getDataSetByIndex(0)?.entryCount)!) - let usage = Units(bytes: Int64(value.used)).getReadableTuple().0 - self.chart.data?.addEntry(ChartDataEntry(x: index, y: usage), dataSetIndex: 0) - - if index > 120 { - self.chart.xAxis.axisMinimum = index - 120 - } - self.chart.xAxis.axisMaximum = index - - if self.popup.active { - self.chart.notifyDataSetChanged() - self.chart.moveViewToX(index) - } - } - - private 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.popup.view.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") - self.totalValue = ValueField(string: "0 GB") - total.addView(totalLabel, in: .center) - total.addView(self.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") - self.usedValue = ValueField(string: "0 GB") - used.addView(usedLabel, in: .center) - used.addView(self.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") - self.freeValue = ValueField(string: "0 GB") - free.addView(freeLabel, in: .center) - free.addView(self.freeValue, in: .center) - - vertical.addSubview(total) - vertical.addSubview(used) - vertical.addSubview(free) - - self.popup.view.view?.addSubview(vertical) - } - - public func overviewUpdater(value: RAMUsage) { - if !self.popup.active && self.popup.initialized { return } - - self.totalValue.stringValue = Units(bytes: Int64(value.total)).getReadableMemory() - self.usedValue.stringValue = Units(bytes: Int64(value.used)).getReadableMemory() - self.freeValue.stringValue = Units(bytes: Int64(value.free)).getReadableMemory() - } - - private 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.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 - - 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: "") - - 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.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.popup.view.view?.addSubview(label) - } - - public func processesUpdater(value: [TopProcess]) { - if self.processViewList.isEmpty || !self.popup.active && self.popup.initialized { return } - self.popup.initialized = true - - 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 = Units(bytes: Int64(process.usage)).getReadableMemory() - } - } - } - - 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 - 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/RAM/RAMProcessReader.swift b/Stats/Modules/RAM/RAMProcessReader.swift deleted file mode 100644 index ee518fdb..00000000 --- a/Stats/Modules/RAM/RAMProcessReader.swift +++ /dev/null @@ -1,87 +0,0 @@ -// -// RAMProcessReader.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 14/01/2020. -// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa - -class RAMProcessReader: Reader { - public var name: String = "Process" - 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} - - init(_ updater: @escaping ([TopProcess]) -> Void) { - self.callback = updater - - if self.available { - DispatchQueue.global(qos: .default).async { - self.read() - } - } - } - - func read() { - if !self.enabled && self.initialized { return } - self.initialized = true - - 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) - return - } - - 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.callback(processes) - }) - } - - func toggleEnable(_ value: Bool) { - self.enabled = value - } -} diff --git a/Stats/Modules/RAM/RAMUsageReader.swift b/Stats/Modules/RAM/RAMUsageReader.swift deleted file mode 100644 index ed3a5e61..00000000 --- a/Stats/Modules/RAM/RAMUsageReader.swift +++ /dev/null @@ -1,90 +0,0 @@ -// -// RAMUsageReader.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 14/01/2020. -// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa - -struct RAMUsage { - var usage: Double = 0 - var total: Double = 0 - var used: Double = 0 - var free: Double = 0 -} - -class RAMUsageReader: Reader { - public var name: String = "Usage" - public var enabled: Bool = true - public var available: Bool = true - public var optional: Bool = false - public var initialized: Bool = false - public var callback: (RAMUsage) -> Void = {_ in} - - public var totalSize: Float = 0 - public var usage: RAMUsage = RAMUsage() - - init(_ updater: @escaping (RAMUsage) -> Void) { - self.callback = updater - - 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 { - DispatchQueue.global(qos: .default).async { - self.read() - } - } - } - - func read() { - if !self.enabled && self.initialized { return } - self.initialized = true - - 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 = RAMUsage(usage: Double((self.totalSize - free) / self.totalSize), total: Double(self.totalSize), used: Double(used), free: Double(free)) - self.callback(self.usage) - }) - } else { - print("Error with host_statistics64(): " + (String(cString: mach_error_string(kerr), encoding: String.Encoding.ascii) ?? "unknown error")) - } - } - - func toggleEnable(_ value: Bool) { - self.enabled = value - } -} diff --git a/Stats/Modules/Sensors/Sensors.swift b/Stats/Modules/Sensors/Sensors.swift deleted file mode 100644 index 2ae61e08..00000000 --- a/Stats/Modules/Sensors/Sensors.swift +++ /dev/null @@ -1,102 +0,0 @@ -// -// Sensors.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 03/04/2020. -// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa -import Repeat - -class Sensors: Module { - public var name: String = "Sensors" - public var updateInterval: Double = 1 - - public var enabled: Bool = true - public var available: Bool = true - - public var widget: ModuleWidget = ModuleWidget() - public var popup: ModulePopup = ModulePopup(false) - public var menu: NSMenuItem = NSMenuItem() - - public var readers: [Reader] = [] - public var task: Repeater? - - internal let defaults = UserDefaults.standard - internal var submenu: NSMenu = NSMenu() - - internal var value_1: String = "TC0P" - internal var value_2: String = "TG0D" - internal var once: Int = 0 - - internal var sensors: Sensors_t = Sensors_t() - - init() { - if !self.available { return } - - 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.Sensors - self.value_1 = (defaults.object(forKey: "\(name)_value_1") != nil ? defaults.string(forKey: "\(name)_value_1")! : value_1) - self.value_2 = (defaults.object(forKey: "\(name)_value_2") != nil ? defaults.string(forKey: "\(name)_value_2")! : value_2) - - self.initWidget() - self.initMenu() - - if self.enabled { - self.update() - } - - self.task = Repeater.init(interval: .seconds(self.updateInterval), observer: { _ in - if self.enabled { - self.update() - } - }) - } - - 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 update() { - var value_1_unit: Double = 0 - var value_1_value: Double = 0 - var value_2_unit: Double = 0 - var value_2_value: Double = 0 - - var sensor_1: Sensor_t? = self.sensors.find(byKey: self.value_1) - var sensor_2: Sensor_t? = self.sensors.find(byKey: self.value_2) - - if sensor_1 != nil { - sensor_1!.update() - if sensor_1!.value != nil { - value_1_value = sensor_1!.value! - value_1_unit = Double(sensor_1!.unit[0].unicodeScalarCodePoint()) - } - } - if sensor_2 != nil { - sensor_2!.update() - if sensor_2!.value != nil { - value_2_value = sensor_2!.value! - value_2_unit = Double(sensor_2!.unit[0].unicodeScalarCodePoint()) - } - } - - DispatchQueue.main.async(execute: { - (self.widget.view as! Widget).setValue(data: [value_1_value, value_1_unit, value_2_value, value_2_unit]) - }) - } -} diff --git a/Stats/Modules/Sensors/SensorsMenu.swift b/Stats/Modules/Sensors/SensorsMenu.swift deleted file mode 100644 index d7910247..00000000 --- a/Stats/Modules/Sensors/SensorsMenu.swift +++ /dev/null @@ -1,153 +0,0 @@ -// -// SensorsMenu.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 03/04/2020. -// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa - -extension Sensors { - public func initMenu() { - 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 sensor_1: NSMenuItem = NSMenuItem(title: "Sensor #1", action: nil, keyEquivalent: "") - sensor_1.target = self - sensor_1.submenu = NSMenu() - addSensorsMennu(sensor_1.submenu!, value: self.value_1, action: #selector(toggleValue1)) - - let sensor_2: NSMenuItem = NSMenuItem(title: "Sensor #2", action: nil, keyEquivalent: "") - sensor_2.target = self - sensor_2.submenu = NSMenu() - addSensorsMennu(sensor_2.submenu!, value: self.value_2, action: #selector(toggleValue2)) - - submenu.addItem(sensor_1) - submenu.addItem(sensor_2) - - submenu.addItem(NSMenuItem.separator()) - - if let view = self.widget.view as? Widget { - for widgetMenu in view.menus { - submenu.addItem(widgetMenu) - } - } - - if self.enabled { - menu.submenu = submenu - } - } - - private func addSensorsMennu(_ menu: NSMenu, value: String, action: Selector?) { - var sensorsMenu: NSMenuItem? = generateSensorsMenu(type: SensorType.Temperature, value: value, action: action) - if sensorsMenu != nil { - menu.addItem(sensorsMenu!) - } - sensorsMenu = generateSensorsMenu(type: SensorType.Voltage, value: value, action: action) - if sensorsMenu != nil { - menu.addItem(sensorsMenu!) - } - sensorsMenu = generateSensorsMenu(type: SensorType.Power, value: value, action: action) - if sensorsMenu != nil { - menu.addItem(sensorsMenu!) - } - } - - private func generateSensorsMenu(type: SensorType, value: String, action: Selector?) -> NSMenuItem? { - let list: [Sensor_t] = self.sensors.list.filter{ $0.type == type.rawValue } - if list.isEmpty { - return nil - } - - let mainItem: NSMenuItem = NSMenuItem(title: type.rawValue, action: nil, keyEquivalent: "") - mainItem.target = self - mainItem.submenu = NSMenu() - - var groups: [SensorGroup_t] = [] - list.forEach { (s: Sensor_t) in - if !groups.contains(s.group) { - groups.append(s.group) - } - } - groups.sort() - - groups.forEach { (g: SensorGroup_t) in - mainItem.submenu!.addItem(NSMenuItem(title: g, action: nil, keyEquivalent: "")) - - list.filter{ $0.group == g }.forEach { (s: Sensor_t) in - let menuPoint: NSMenuItem = NSMenuItem(title: s.name, action: action, keyEquivalent: "") - menuPoint.state = s.key == value ? NSControl.StateValue.on : NSControl.StateValue.off - menuPoint.target = self - menuPoint.extraString = s.key - - mainItem.submenu!.addItem(menuPoint) - } - - mainItem.submenu!.addItem(NSMenuItem.separator()) - } - - return mainItem - } - - @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.enabled = state - menuBar!.reload(name: self.name) - - if !state { - menu.submenu = nil - } else { - menu.submenu = submenu - } - - self.restart() - } - - @objc func toggleValue1(_ sender: NSMenuItem) { - let val: String = sender.extraString - if self.value_1 == val { - return - } - - let state = sender.state == NSControl.StateValue.on - for item in self.submenu.items { - item.state = NSControl.StateValue.off - } - - sender.state = state ? NSControl.StateValue.off : NSControl.StateValue.on - self.defaults.set(val, forKey: "\(name)_value_1") - self.value_1 = val - self.initWidget() - self.initMenu() - menuBar!.reload(name: self.name) - } - - @objc func toggleValue2(_ sender: NSMenuItem) { - let val: String = sender.extraString - if self.value_2 == val { - return - } - - let state = sender.state == NSControl.StateValue.on - for item in self.submenu.items { - item.state = NSControl.StateValue.off - } - - sender.state = state ? NSControl.StateValue.off : NSControl.StateValue.on - self.defaults.set(val, forKey: "\(name)_value_2") - self.value_2 = val - self.initWidget() - self.initMenu() - menuBar!.reload(name: self.name) - } -} diff --git a/Stats/Modules/Sensors/SensorsType.swift b/Stats/Modules/Sensors/SensorsType.swift deleted file mode 100644 index 47926d88..00000000 --- a/Stats/Modules/Sensors/SensorsType.swift +++ /dev/null @@ -1,183 +0,0 @@ -// -// SensorsType.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 06/04/2020. -// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. -// - -typealias SensorGroup_t = String -enum SensorGroup: SensorGroup_t { - case CPU = "CPU" - case GPU = "GPU" - case System = "Systems" - case Sensor = "Sensors" -} - -typealias SensorType_t = String -enum SensorType: SensorType_t { - case Temperature = "Temperature" - case Voltage = "Voltage" - case Power = "Power" - case Frequency = "Frequency" - case Battery = "Battery" -} - -struct Sensor_t { - var name: String - var key: String = "" - - var group: SensorGroup_t - var type: SensorType_t - var unit: String { - get { - switch self.type{ - case SensorType.Temperature.rawValue: - return "°" - case SensorType.Voltage.rawValue: - return "V" - case SensorType.Power.rawValue: - return "W" - default: return "" - } - } - } - - var value: Double? = nil - - public mutating func update() { - self.value = smc.getValue(self.key) - } -} - -struct Sensors_t { - var list: [Sensor_t] = [] - - init() { - var available: [String] = smc.getAllKeys() - var sensor: Sensor_t? = nil - - available = available.filter({ (key: String) -> Bool in - switch key.prefix(1) { - case "T", "V", "P": return SensorsDict[key] != nil - default: return false - } - }) - - available.forEach { (key: String) in - sensor = SensorsDict[key] - if sensor != nil { - sensor!.value = smc.getValue(key) - if sensor!.value != nil { - sensor!.key = key - self.list.append(sensor!) - } - } - } - } - - public func find(byKey key: String) -> Sensor_t? { - return self.list.first{ $0.key == key} - } -} - -// List of keys: https://github.com/acidanthera/VirtualSMC/blob/master/Docs/SMCSensorKeys.txt -let SensorsDict: [String: Sensor_t] = [ - /// Temperature - "TA0P": Sensor_t(name: "Ambient 1", group: SensorGroup.Sensor.rawValue, type: SensorType.Temperature.rawValue), - "TA1P": Sensor_t(name: "Ambient 2", group: SensorGroup.Sensor.rawValue, type: SensorType.Temperature.rawValue), - "Th0H": Sensor_t(name: "Heatpipe 1", group: SensorGroup.Sensor.rawValue, type: SensorType.Temperature.rawValue), - "Th1H": Sensor_t(name: "Heatpipe 2", group: SensorGroup.Sensor.rawValue, type: SensorType.Temperature.rawValue), - "Th2H": Sensor_t(name: "Heatpipe 3", group: SensorGroup.Sensor.rawValue, type: SensorType.Temperature.rawValue), - "Th3H": Sensor_t(name: "Heatpipe 4", group: SensorGroup.Sensor.rawValue, type: SensorType.Temperature.rawValue), - "TZ0C": Sensor_t(name: "Termal zone 1", group: SensorGroup.Sensor.rawValue, type: SensorType.Temperature.rawValue), - "TZ1C": Sensor_t(name: "Termal zone 2", group: SensorGroup.Sensor.rawValue, type: SensorType.Temperature.rawValue), - - "TC0F": Sensor_t(name: "CPU die", group: SensorGroup.CPU.rawValue, type: SensorType.Temperature.rawValue), - "TC0H": Sensor_t(name: "CPU heatsink", group: SensorGroup.CPU.rawValue, type: SensorType.Temperature.rawValue), - "TC0P": Sensor_t(name: "CPU proximity", group: SensorGroup.CPU.rawValue, type: SensorType.Temperature.rawValue), - "TC1C": Sensor_t(name: "CPU core 1", group: SensorGroup.CPU.rawValue, type: SensorType.Temperature.rawValue), - "TC2C": Sensor_t(name: "CPU core 2", group: SensorGroup.CPU.rawValue, type: SensorType.Temperature.rawValue), - "TC3C": Sensor_t(name: "CPU core 3", group: SensorGroup.CPU.rawValue, type: SensorType.Temperature.rawValue), - "TC4C": Sensor_t(name: "CPU core 4", group: SensorGroup.CPU.rawValue, type: SensorType.Temperature.rawValue), - "TC5C": Sensor_t(name: "CPU core 5", group: SensorGroup.CPU.rawValue, type: SensorType.Temperature.rawValue), - "TC6C": Sensor_t(name: "CPU core 6", group: SensorGroup.CPU.rawValue, type: SensorType.Temperature.rawValue), - "TC7C": Sensor_t(name: "CPU core 7", group: SensorGroup.CPU.rawValue, type: SensorType.Temperature.rawValue), - "TC8C": Sensor_t(name: "CPU core 8", group: SensorGroup.CPU.rawValue, type: SensorType.Temperature.rawValue), - - "TCGC": Sensor_t(name: "GPU Intel Graphics", group: SensorGroup.GPU.rawValue, type: SensorType.Temperature.rawValue), - "TG0D": Sensor_t(name: "GPU die", group: SensorGroup.GPU.rawValue, type: SensorType.Temperature.rawValue), - "TG0H": Sensor_t(name: "GPU heatsink", group: SensorGroup.GPU.rawValue, type: SensorType.Temperature.rawValue), - "TG0P": Sensor_t(name: "GPU proximity", group: SensorGroup.GPU.rawValue, type: SensorType.Temperature.rawValue), - - "Tm0P": Sensor_t(name: "Mainboard", group: SensorGroup.System.rawValue, type: SensorType.Temperature.rawValue), - "Tp0P": Sensor_t(name: "Powerboard", group: SensorGroup.System.rawValue, type: SensorType.Temperature.rawValue), - "TB1T": Sensor_t(name: "Battery", group: SensorGroup.System.rawValue, type: SensorType.Temperature.rawValue), - "TW0P": Sensor_t(name: "Airport", group: SensorGroup.System.rawValue, type: SensorType.Temperature.rawValue), - "TL0P": Sensor_t(name: "Display", group: SensorGroup.System.rawValue, type: SensorType.Temperature.rawValue), - "TI0P": Sensor_t(name: "Thunderbold 1", group: SensorGroup.System.rawValue, type: SensorType.Temperature.rawValue), - "TI1P": Sensor_t(name: "Thunderbold 2", group: SensorGroup.System.rawValue, type: SensorType.Temperature.rawValue), - "TI2P": Sensor_t(name: "Thunderbold 3", group: SensorGroup.System.rawValue, type: SensorType.Temperature.rawValue), - "TI3P": Sensor_t(name: "Thunderbold 4", group: SensorGroup.System.rawValue, type: SensorType.Temperature.rawValue), - - "TN0D": Sensor_t(name: "Northbridge die", group: SensorGroup.System.rawValue, type: SensorType.Temperature.rawValue), - "TN0H": Sensor_t(name: "Northbridge heatsink", group: SensorGroup.System.rawValue, type: SensorType.Temperature.rawValue), - "TN0P": Sensor_t(name: "Northbridge proximity", group: SensorGroup.System.rawValue, type: SensorType.Temperature.rawValue), - - /// Voltage - "VCAC": Sensor_t(name: "CPU IA", group: SensorGroup.CPU.rawValue, type: SensorType.Voltage.rawValue), - "VCSC": Sensor_t(name: "CPU System Agent", group: SensorGroup.CPU.rawValue, type: SensorType.Voltage.rawValue), - "VC0C": Sensor_t(name: "CPU Core 1", group: SensorGroup.CPU.rawValue, type: SensorType.Voltage.rawValue), - "VC1C": Sensor_t(name: "CPU Core 2", group: SensorGroup.CPU.rawValue, type: SensorType.Voltage.rawValue), - "VC2C": Sensor_t(name: "CPU Core 3", group: SensorGroup.CPU.rawValue, type: SensorType.Voltage.rawValue), - "VC3C": Sensor_t(name: "CPU Core 4", group: SensorGroup.CPU.rawValue, type: SensorType.Voltage.rawValue), - "VC4C": Sensor_t(name: "CPU Core 5", group: SensorGroup.CPU.rawValue, type: SensorType.Voltage.rawValue), - "VC5C": Sensor_t(name: "CPU Core 6", group: SensorGroup.CPU.rawValue, type: SensorType.Voltage.rawValue), - "VC6C": Sensor_t(name: "CPU Core 7", group: SensorGroup.CPU.rawValue, type: SensorType.Voltage.rawValue), - "VC7C": Sensor_t(name: "CPU Core 8", group: SensorGroup.CPU.rawValue, type: SensorType.Voltage.rawValue), - - "VCTC": Sensor_t(name: "GPU Intel Graphics", group: SensorGroup.GPU.rawValue, type: SensorType.Voltage.rawValue), - "VG0C": Sensor_t(name: "GPU", group: SensorGroup.GPU.rawValue, type: SensorType.Voltage.rawValue), - - "VM0R": Sensor_t(name: "Memory", group: SensorGroup.System.rawValue, type: SensorType.Voltage.rawValue), - "Vb0R": Sensor_t(name: "CMOS", group: SensorGroup.System.rawValue, type: SensorType.Voltage.rawValue), - - "VD0R": Sensor_t(name: "DC In", group: SensorGroup.Sensor.rawValue, type: SensorType.Voltage.rawValue), - "VP0R": Sensor_t(name: "12V rail", group: SensorGroup.Sensor.rawValue, type: SensorType.Voltage.rawValue), - "Vp0C": Sensor_t(name: "12V vcc", group: SensorGroup.Sensor.rawValue, type: SensorType.Voltage.rawValue), - "VV2S": Sensor_t(name: "3V", group: SensorGroup.Sensor.rawValue, type: SensorType.Voltage.rawValue), - "VR3R": Sensor_t(name: "3.3V", group: SensorGroup.Sensor.rawValue, type: SensorType.Voltage.rawValue), - "VV1S": Sensor_t(name: "5V", group: SensorGroup.Sensor.rawValue, type: SensorType.Voltage.rawValue), - "VV9S": Sensor_t(name: "12V", group: SensorGroup.Sensor.rawValue, type: SensorType.Voltage.rawValue), - "VeES": Sensor_t(name: "PCI 12V", group: SensorGroup.Sensor.rawValue, type: SensorType.Voltage.rawValue), - - /// Power - "PCPC": Sensor_t(name: "CPU Package", group: SensorGroup.CPU.rawValue, type: SensorType.Power.rawValue), - "PCPT": Sensor_t(name: "CPU Package total", group: SensorGroup.CPU.rawValue, type: SensorType.Power.rawValue), - "PC0R": Sensor_t(name: "CPU Computing high side", group: SensorGroup.CPU.rawValue, type: SensorType.Power.rawValue), - - "PCPG": Sensor_t(name: "GPU Intel Graphics", group: SensorGroup.GPU.rawValue, type: SensorType.Power.rawValue), - "PG0R": Sensor_t(name: "GPU", group: SensorGroup.GPU.rawValue, type: SensorType.Power.rawValue), - - "PPBR": Sensor_t(name: "Battery", group: SensorGroup.Sensor.rawValue, type: SensorType.Power.rawValue), - "PDTR": Sensor_t(name: "DC In", group: SensorGroup.Sensor.rawValue, type: SensorType.Power.rawValue), - "PSTR": Sensor_t(name: "System total", group: SensorGroup.Sensor.rawValue, type: SensorType.Power.rawValue), - - /// Frequency - "FRC0": Sensor_t(name: "CPU 1", group: SensorGroup.CPU.rawValue, type: SensorType.Frequency.rawValue), - "FRC1": Sensor_t(name: "CPU 2", group: SensorGroup.CPU.rawValue, type: SensorType.Frequency.rawValue), - "FRC2": Sensor_t(name: "CPU 3", group: SensorGroup.CPU.rawValue, type: SensorType.Frequency.rawValue), - "FRC3": Sensor_t(name: "CPU 4", group: SensorGroup.CPU.rawValue, type: SensorType.Frequency.rawValue), - "FRC4": Sensor_t(name: "CPU 5", group: SensorGroup.CPU.rawValue, type: SensorType.Frequency.rawValue), - "FRC5": Sensor_t(name: "CPU 6", group: SensorGroup.CPU.rawValue, type: SensorType.Frequency.rawValue), - "FRC6": Sensor_t(name: "CPU 7", group: SensorGroup.CPU.rawValue, type: SensorType.Frequency.rawValue), - "FRC7": Sensor_t(name: "CPU 8", group: SensorGroup.CPU.rawValue, type: SensorType.Frequency.rawValue), - - "CG0C": Sensor_t(name: "GPU", group: SensorGroup.GPU.rawValue, type: SensorType.Frequency.rawValue), - "CG0S": Sensor_t(name: "GPU shader", group: SensorGroup.GPU.rawValue, type: SensorType.Frequency.rawValue), - "CG0M": Sensor_t(name: "GPU memory", group: SensorGroup.GPU.rawValue, type: SensorType.Frequency.rawValue), - - /// Battery - "B0AV": Sensor_t(name: "Voltage", group: SensorGroup.Sensor.rawValue, type: SensorType.Battery.rawValue), - "B0AC": Sensor_t(name: "Amperage", group: SensorGroup.Sensor.rawValue, type: SensorType.Battery.rawValue), -] diff --git a/Stats/Supporting Files/About.storyboard b/Stats/Supporting Files/About.storyboard deleted file mode 100644 index 077412f3..00000000 --- a/Stats/Supporting Files/About.storyboard +++ /dev/null @@ -1,183 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Stats/Supporting Files/Assets.xcassets/icons/baseline_build_black_18pt.imageset/Contents.json b/Stats/Supporting Files/Assets.xcassets/apps.imageset/Contents.json similarity index 58% rename from Stats/Supporting Files/Assets.xcassets/icons/baseline_build_black_18pt.imageset/Contents.json rename to Stats/Supporting Files/Assets.xcassets/apps.imageset/Contents.json index f9f14e7c..fcec68e6 100644 --- a/Stats/Supporting Files/Assets.xcassets/icons/baseline_build_black_18pt.imageset/Contents.json +++ b/Stats/Supporting Files/Assets.xcassets/apps.imageset/Contents.json @@ -1,26 +1,26 @@ { "images" : [ { + "filename" : "baseline_apps_white_24pt_1x.png", "idiom" : "universal", - "filename" : "baseline_build_black_18pt_1x.png", "scale" : "1x" }, { + "filename" : "baseline_apps_white_24pt_2x.png", "idiom" : "universal", - "filename" : "baseline_build_black_18pt_2x.png", "scale" : "2x" }, { + "filename" : "baseline_apps_white_24pt_3x.png", "idiom" : "universal", - "filename" : "baseline_build_black_18pt_3x.png", "scale" : "3x" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 }, "properties" : { "template-rendering-intent" : "template" } -} \ No newline at end of file +} diff --git a/Stats/Supporting Files/Assets.xcassets/apps.imageset/baseline_apps_white_24pt_1x.png b/Stats/Supporting Files/Assets.xcassets/apps.imageset/baseline_apps_white_24pt_1x.png new file mode 100644 index 0000000000000000000000000000000000000000..a5644e8d47b09ab1112cc73a43f93b414a5dec9a GIT binary patch literal 90 zcmeAS@N?(olHy`uVBq!ia0vp^5+KY7Bp6QcFoXgrrjj7PUo>r|a7SsCtZTy@ua)wD?<&7Q7)F6*2UngEx+9ZUcK literal 0 HcmV?d00001 diff --git a/Stats/Supporting Files/Assets.xcassets/bug.imageset/Contents.json b/Stats/Supporting Files/Assets.xcassets/bug.imageset/Contents.json new file mode 100644 index 00000000..65e2296b --- /dev/null +++ b/Stats/Supporting Files/Assets.xcassets/bug.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "baseline_bug_report_white_24pt_1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "baseline_bug_report_white_24pt_2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "baseline_bug_report_white_24pt_3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Stats/Supporting Files/Assets.xcassets/bug.imageset/baseline_bug_report_white_24pt_1x.png b/Stats/Supporting Files/Assets.xcassets/bug.imageset/baseline_bug_report_white_24pt_1x.png new file mode 100644 index 0000000000000000000000000000000000000000..953932bff469afa7bb46a5a269118baef50e0df3 GIT binary patch literal 200 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gj{hlt4Ar*{ouQ0MPIf^tqbd1np zIwQC%mi>iCBBSB~rb7&3i5p)CJmgLY?5*NBd%WS``?JYG!cDS$FXrD#RZW`w6R`ds8R>bidJ#bxsg3s+b#_2>8%xHq)$ literal 0 HcmV?d00001 diff --git a/Stats/Supporting Files/Assets.xcassets/bug.imageset/baseline_bug_report_white_24pt_2x.png b/Stats/Supporting Files/Assets.xcassets/bug.imageset/baseline_bug_report_white_24pt_2x.png new file mode 100644 index 0000000000000000000000000000000000000000..e687d60179158709448c57eb159b7300885194a2 GIT binary patch literal 317 zcmV-D0mA-?P)3~!_~3(>z-K-acm!f%0D4d2Z1AmaJe4G z;D3D5?`@Myb159I5CV>Hi$%(WC2m@e?*Ic9nd3*mrd2=(kQ4D?JZSPIu!hefGRKct zrUEr0I=rAqk7sll;~PZedmrB#R@lq8!@T|5FEijgwSY_07;%<<5@r}oXN%)hgT^92 zp$M27b|nw=wWm}YR1D}3J;1*Xh*Ky6A|Oto2-v3DFtq=bh*Ky6BA^K77)|FDC(ZAK z3S6WXuu*Q^{&tR0z5%QB8(@xm-*<;us;MC48jsimV88>eaGF90;m>#j0nfX_@`>U3 P00000NkvXXu0mjfUO<5T literal 0 HcmV?d00001 diff --git a/Stats/Supporting Files/Assets.xcassets/bug.imageset/baseline_bug_report_white_24pt_3x.png b/Stats/Supporting Files/Assets.xcassets/bug.imageset/baseline_bug_report_white_24pt_3x.png new file mode 100644 index 0000000000000000000000000000000000000000..30f89bf74c97d0092245ea6388284e62c4e4f18d GIT binary patch literal 445 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@Zgyv2U~KVpaSW+oe0z&ACEAhU_(Nv_ z5mu4I20R7_0(ux3x)|9QB-l6@7I1J$G%+?MXmlw_a7oBDB}p)}Dg5A)6X`GVytnj! zKl_I{^5x~nr!MXQfdiuJyj^D`G@cIb*vx!J`&cB?zsH|M9({Qx`dFglO3Ivjd)($M zQ+{}F1FPE9M_YUGT?OBfr#o~w{`uP`G@Wg#T9IP=^51cjd1f^5Rv4}mj%alM@21z2#tQwBS1M zL;kSB@)}10mPkKG0XD~d5AG=PG#ixjG%0X=l5Js+-zU2FcHZ}IwYNX-mESLZ>I@Jx z7#6U|Ir#ox)u;k7v}xRRdNvB zzuN12>Hcm9CdCO3Oq@sN3o&r2fLLHcBqBngkwwS@N?0+y6@#&%q9;C!Llx{|Tt4q? z@BOt(4%wf~`xtr~X6>;(b^p(g%!bHJOuXU>GoXy85}Sb4q9e0E{JZh5!Hn literal 0 HcmV?d00001 diff --git a/Stats/Supporting Files/Assets.xcassets/devices/Contents.json b/Stats/Supporting Files/Assets.xcassets/devices/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/Stats/Supporting Files/Assets.xcassets/devices/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Stats/Supporting Files/Assets.xcassets/devices/imac.imageset/Contents.json b/Stats/Supporting Files/Assets.xcassets/devices/imac.imageset/Contents.json new file mode 100644 index 00000000..ee9e9071 --- /dev/null +++ b/Stats/Supporting Files/Assets.xcassets/devices/imac.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "imac.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Stats/Supporting Files/Assets.xcassets/devices/imac.imageset/imac.png b/Stats/Supporting Files/Assets.xcassets/devices/imac.imageset/imac.png new file mode 100644 index 0000000000000000000000000000000000000000..2d65b9e42cffae2e498e3050918a4031786fc8d9 GIT binary patch literal 49207 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

mr_3=*eWiv~4U5lkrHU3mw~?kEFc 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 Hu0mjfT*IDv literal 0 HcmV?d00001 diff --git a/Stats/Supporting Files/Assets.xcassets/devices/imacPro.imageset/Contents.json b/Stats/Supporting Files/Assets.xcassets/devices/imacPro.imageset/Contents.json new file mode 100644 index 00000000..b4c088f1 --- /dev/null +++ b/Stats/Supporting Files/Assets.xcassets/devices/imacPro.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "imacPro.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Stats/Supporting Files/Assets.xcassets/devices/imacPro.imageset/imacPro.png b/Stats/Supporting Files/Assets.xcassets/devices/imacPro.imageset/imacPro.png new file mode 100644 index 0000000000000000000000000000000000000000..5d909f238ee219ef58b32103ba9b262da5ebbd95 GIT binary patch literal 49735 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≈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 literal 0 HcmV?d00001 diff --git a/Stats/Supporting Files/Assets.xcassets/devices/macbookAir.imageset/Contents.json b/Stats/Supporting Files/Assets.xcassets/devices/macbookAir.imageset/Contents.json new file mode 100644 index 00000000..d7e925f6 --- /dev/null +++ b/Stats/Supporting Files/Assets.xcassets/devices/macbookAir.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "macbookAir.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Stats/Supporting Files/Assets.xcassets/devices/macbookAir.imageset/macbookAir.png b/Stats/Supporting Files/Assets.xcassets/devices/macbookAir.imageset/macbookAir.png new file mode 100644 index 0000000000000000000000000000000000000000..7a51f7816323a49b971586b32132020ce287be92 GIT binary patch literal 55680 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 literal 0 HcmV?d00001 diff --git a/Stats/Supporting Files/Assets.xcassets/icons/Contents.json b/Stats/Supporting Files/Assets.xcassets/icons/Contents.json deleted file mode 100644 index da4a164c..00000000 --- a/Stats/Supporting Files/Assets.xcassets/icons/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Stats/Supporting Files/Assets.xcassets/icons/baseline_build_black_18pt.imageset/baseline_build_black_18pt_1x.png b/Stats/Supporting Files/Assets.xcassets/icons/baseline_build_black_18pt.imageset/baseline_build_black_18pt_1x.png deleted file mode 100644 index 792a9c06150a1654585144e5bcfa2263db236f50..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 216 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+0wn(&ce?|m=6kw0hEy<4J=4g>lqlf(uvy3{ zKR|%x#sUWJ4k5uH76*=;0R8O@EEk=>Finj8f2;FgLduKX49B0!E5~t|mOo=D(qd8E z_^7?}^t~^IY&9nWV+8NYsXMPQxvYMdHN7XJ?DCO$d(Ud81qF}a!`2(wZZK6q(*u5h_ Pr!siD`njxgN@xNAStw5- diff --git a/Stats/Supporting Files/Assets.xcassets/icons/baseline_build_black_18pt.imageset/baseline_build_black_18pt_2x.png b/Stats/Supporting Files/Assets.xcassets/icons/baseline_build_black_18pt.imageset/baseline_build_black_18pt_2x.png deleted file mode 100644 index a1e7f71e7771dde9ee0d9ba52fc0aaa6646b7147..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 303 zcmV+~0nq-5P)yaxAF{Wc9r8Vn(I2 z^$%J>*2jw(W&YY8vSD8MPi31iFWe#P;N>5c%`q=#TSHd$@8G~9^THysC{B5i>jqsv z8oHqZSv;5Sq6`?_MYZtcE~=6pyQmUmF*LIL_2dCFv~!6$b!krNO2U)_)Q!8Sih!bW z+(iw5JE$viz;)`1B6N~ELo;aVjC@=!l}?gzf3IOZiHvLK^sxW{002ovPDHLkV1lO? BhAsdA diff --git a/Stats/Supporting Files/Assets.xcassets/icons/baseline_build_black_18pt.imageset/baseline_build_black_18pt_3x.png b/Stats/Supporting Files/Assets.xcassets/icons/baseline_build_black_18pt.imageset/baseline_build_black_18pt_3x.png deleted file mode 100644 index 34e6be7afa827c733fff673938bb22878776996c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 444 zcmV;t0YmII?+vL`DMbZBfe0lJ>8I`0|MAX!P zwmOlZyz9Ehy41cu0Dd?Zml7>-hRXM<{g zoNz^lPcBElPwtUcpWG3FPi_Wf9fU@1$9TCKR3%6nxiTX+kD>(3o&rE7deS@hlr62%@o2k&g~-xE+mPV#}v+OB9UCoLJ8wI zH-|)U6{HZ$I&rQKiCET3Jrm$u$OEF-Hma6X*flLEYA3JcnozWWsCJ>@Vx)a?&18LY z1H7oY2Au2SR>_@exN_p;{BcmnGWW>0-LcKNmb2H+7_-EQGeH{wja*u}I*OK^JNeVx znNO}BJ6G+KJO1ZfrB5!c=Sq*uSh*^SR;`s=2Y=i?_~Hr-f*-EHD1Z+xub`A%o?!qF moPttwuS!bsL))d7lIa6`({}_$U~0000DF}mnf-nq&Aj~HS7xD>$AXo?%!#DWkPH^_@Rx`mq zeCB@l@0`Y0)j<9~02l%@ZvZd_`rgHRK!%Kq0)Pa$03A=j2{`~wM?ecmk!{i9W8@CB zOI%SGcp?KUV23QqJ~FUDmR7(G=~oCCA?Hfp&=Jr@ZodkMkh>L-AyK8?k<=%p0&D}vCK!mK00000NkvXXu0mjfbg*;t literal 0 HcmV?d00001 diff --git a/Stats/Supporting Files/Assets.xcassets/power.imageset/baseline_power_settings_new_white_24pt_2x.png b/Stats/Supporting Files/Assets.xcassets/power.imageset/baseline_power_settings_new_white_24pt_2x.png new file mode 100644 index 0000000000000000000000000000000000000000..860a2348805d86b3bf1de4b970d05804315b5b3e GIT binary patch literal 548 zcmV+<0^9wGP)FbpAthY&(|cnBec5C(t{wqFeA zbFlmF9n1ar0ln{i|JU8UdsnzjB9TbsTMz?;AO;A~R2iT03c$}Ev6xr$6-@C=-*

zO+1re$#?TbT%t)|8$dK~^65Lk2qge`VK}ky!AVrgL-tU3m;b}5)ZT`mc^sfU1Xj$%Cb_p}+2AD1%qDe1cfLAmbx&)A`Pa|LeIhhle0FqL`0CN8Ag6}4k0j~dhfEt?Y zT>>1?q#7{59h$6M0%V#=EKyG4sY{VWvy4iV%jnppNZ;$mO7|k`xD=^dwzri4&EHz? zTXrqBR=*MTJAj#{SkJPts^{2z8zm3g4;nchQ!M3lXs|<*g^$8rlzilHXs|#@)7K3q zwh&nIQ2Pq!D0%aJpp~H{#k!(y6SpWyyd7|f{mB6(CmdqskDIl3l-LgqJcr28#u4Y( z!v((6DecRq;|%aD z?KJ9c9-pP%Z#vMCv{wuR2ildly~#kIq+LaSAA|>Kzi%?oH)&1$K)y=*yvabVwBz`J zoJ+ghWT1t#?f8KlNxLoc&NzOc9chm>P!04pY)e~gGSE`mD1IP^(*7$ki|xk` zZi2=cU@mRi#RbRm#sLGJ$=mJfjyy?ggT;A=rL?xMW1mUEa`xt#U=pNt$rnvRvRydEkd!FD$f1WT7|4RZ|<=rGUp>bh?x$d>` z-;J-|yES;Sd5<_PJ`i`fW*K9Kg`YRaUj_D5A;zgGzTpZjmH=4b4*%kBd - - - - - - - - - - - -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Stats/Supporting Files/Info.plist b/Stats/Supporting Files/Info.plist index 67ae3df6..f82c114b 100755 --- a/Stats/Supporting Files/Info.plist +++ b/Stats/Supporting Files/Info.plist @@ -18,6 +18,8 @@ $(MARKETING_VERSION) CFBundleVersion $(CURRENT_PROJECT_VERSION) + Description + Simple macOS system monitor in your menu bar LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion @@ -26,8 +28,6 @@ NSHumanReadableCopyright Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. - NSMainStoryboardFile - Main NSPrincipalClass NSApplication diff --git a/Stats/Supporting Files/Updates.storyboard b/Stats/Supporting Files/Updates.storyboard deleted file mode 100644 index 15a7697b..00000000 --- a/Stats/Supporting Files/Updates.storyboard +++ /dev/null @@ -1,287 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/resources/background.png b/Stats/Supporting Files/background.png similarity index 100% rename from resources/background.png rename to Stats/Supporting Files/background.png diff --git a/resources/cover.psd b/Stats/Supporting Files/cover.psd similarity index 100% rename from resources/cover.psd rename to Stats/Supporting Files/cover.psd diff --git a/Stats/Supporting Files/main.swift b/Stats/Supporting Files/main.swift new file mode 100644 index 00000000..60fe67e5 --- /dev/null +++ b/Stats/Supporting Files/main.swift @@ -0,0 +1,15 @@ +// +// main.swift +// Stats +// +// Created by Serhiy Mytrovtsiy on 10/04/2020. +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa + +private let app = NSApplication.shared +private let delegate = AppDelegate() + +app.delegate = delegate +app.run() diff --git a/Stats/Views/AboutViewController.swift b/Stats/Views/AboutViewController.swift deleted file mode 100644 index 52c1320a..00000000 --- a/Stats/Views/AboutViewController.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// AboutViewController.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 05/09/2019. -// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa -import Foundation - -class AboutVC: NSViewController { - @IBOutlet weak var versionLabel: NSTextField! - - override func viewDidLoad() { - super.viewDidLoad() - self.view.wantsLayer = true - } - - @IBAction func openLink(_ sender: Any) { - NSWorkspace.shared.open(URL(string: "https://github.com/exelban/stats")!) - } - - @IBAction func exit(_ sender: Any) { - self.view.window?.close() - } - - override func awakeFromNib() { - if self.view.layer != nil { - self.view.window?.backgroundColor = .windowBackgroundColor - let versionNumber = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String - versionLabel.stringValue = "Version \(versionNumber)" - } - } -} diff --git a/Stats/Views/AppSettings.swift b/Stats/Views/AppSettings.swift new file mode 100644 index 00000000..2f8ff9c2 --- /dev/null +++ b/Stats/Views/AppSettings.swift @@ -0,0 +1,281 @@ +// +// AppSettings.swift +// Stats +// +// Created by Serhiy Mytrovtsiy on 15/04/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import StatsKit + +class ApplicationSettings: NSView { + private let width: CGFloat = 540 + private let height: CGFloat = 480 + private let deviceInfoHeight: CGFloat = 300 + + init() { + super.init(frame: NSRect(x: 0, y: 0, width: width, height: height)) + self.wantsLayer = true + self.layer?.backgroundColor = .clear + + self.addDeviceInfo() + self.addSettings() + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func addSettings() { + let view: NSView = NSView(frame: NSRect(x: 0, y: 1, width: self.width-1, height: self.height - self.deviceInfoHeight)) + let rowHeight: CGFloat = 40 + let rowHorizontalPadding: CGFloat = 16 + + let leftPanel: NSView = NSView(frame: NSRect(x: 0, y: 0, width: view.frame.width/2, height: view.frame.height)) + leftPanel.wantsLayer = true + + var processorInfo = "" + if systemKit.device.info?.cpu?.name != "" { + processorInfo += "\(systemKit.device.info?.cpu?.name ?? "Unknown")\n" + } + processorInfo += "\(systemKit.device.info?.cpu?.physicalCores ?? 0) cores (\(systemKit.device.info?.cpu?.logicalCores ?? 0) threads)" + leftPanel.addSubview(makeInfoRow( + frame: NSRect(x: rowHorizontalPadding, y: rowHeight*3, width: leftPanel.frame.width - (rowHorizontalPadding*1.5), height: rowHeight), + title: "Processor", + value: processorInfo + )) + + let sizeFormatter = ByteCountFormatter() + sizeFormatter.allowedUnits = [.useGB] + sizeFormatter.countStyle = .memory + leftPanel.addSubview(makeInfoRow( + frame: NSRect(x: rowHorizontalPadding, y: rowHeight*2, width: leftPanel.frame.width - (rowHorizontalPadding*1.5), height: rowHeight), + title: "Memory", + value: "\(sizeFormatter.string(fromByteCount: Int64(systemKit.device.info?.ram?.total ?? 0)))" + )) + + let gpus = systemKit.device.info?.gpu + var gpu: String = "Unknown" + if gpus != nil { + if gpus?.count == 1 { + gpu = gpus![0].name + } else { + gpu = "" + gpus!.forEach{ gpu += "\($0.name)\n" } + } + } + leftPanel.addSubview(makeInfoRow( + frame: NSRect(x: rowHorizontalPadding, y: rowHeight*1, width: leftPanel.frame.width - (rowHorizontalPadding*1.5), height: rowHeight), + title: "GPU", + value: gpu + )) + + leftPanel.addSubview(makeInfoRow( + frame: NSRect(x: rowHorizontalPadding, y: 0, width: leftPanel.frame.width - (rowHorizontalPadding*1.5), height: rowHeight), + title: "Disk", + value: "\(systemKit.device.info?.disk?.model ?? systemKit.device.info?.disk?.name ?? "Unknown")" + )) + + let rightPanel: NSView = NSView(frame: NSRect(x: self.width/2, y: 0, width: view.frame.width/2, height: view.frame.height)) + + rightPanel.addSubview(makeSettingRow( + frame: NSRect(x: rowHorizontalPadding*0.5, y: rowHeight*2, width: rightPanel.frame.width - (rowHorizontalPadding*1.5), height: rowHeight), + title: "Check for updates on start", + action: #selector(self.toggleUpdates), + state: store.bool(key: "checkUpdatesOnLogin", defaultValue: true) + )) + + rightPanel.addSubview(makeSettingRow( + frame: NSRect(x: rowHorizontalPadding*0.5, y: rowHeight*1, width: rightPanel.frame.width - (rowHorizontalPadding*1.5), height: rowHeight), + title: "Show icon in dock", + action: #selector(self.toggleDock), + state: store.bool(key: "dockIcon", defaultValue: false) + )) + + rightPanel.addSubview(makeSettingRow( + frame: NSRect(x: rowHorizontalPadding*0.5, y: 0, width: rightPanel.frame.width - (rowHorizontalPadding*1.5), height: rowHeight), + title: "Start at login", + action: #selector(self.toggleLaunchAtLogin), + state: LaunchAtLogin.isEnabled + )) + + view.addSubview(leftPanel) + view.addSubview(rightPanel) + self.addSubview(view) + } + + private func makeInfoRow(frame: NSRect, title: String, value: String) -> NSView { + let row: NSView = NSView(frame: frame) + let titleWidth = title.widthOfString(usingFont: .systemFont(ofSize: 13, weight: .light)) + 10 + + let rowTitle: NSTextField = TextView(frame: NSRect(x: 0, y: (row.frame.height - 16)/2, width: titleWidth, height: 17)) + rowTitle.font = NSFont.systemFont(ofSize: 13, weight: .light) + rowTitle.textColor = .secondaryLabelColor + rowTitle.stringValue = title + + let rowValue: NSTextField = TextView(frame: NSRect(x: titleWidth, y: (row.frame.height - 16)/2, width: row.frame.width - titleWidth, height: 17)) + rowValue.font = NSFont.systemFont(ofSize: 13, weight: .light) + rowValue.alignment = .right + rowValue.stringValue = value + rowValue.isSelectable = true + + if value.contains("\n") { + rowValue.frame = NSRect(x: titleWidth, y: 0, width: rowValue.frame.width, height: row.frame.height) + } + + row.addSubview(rowTitle) + row.addSubview(rowValue) + + return row + } + + private func makeSettingRow(frame: NSRect, title: String, action: Selector, state: Bool) -> NSView { + let row: NSView = NSView(frame: frame) + let state: NSControl.StateValue = state ? .on : .off + + let rowTitle: NSTextField = TextView(frame: NSRect(x: 0, y: (row.frame.height - 16)/2, width: row.frame.width - 52, height: 17)) + rowTitle.font = NSFont.systemFont(ofSize: 13, weight: .light) + rowTitle.textColor = .secondaryLabelColor + rowTitle.stringValue = title + + var toggle: NSControl = NSControl() + if #available(OSX 10.15, *) { + let switchButton = NSSwitch(frame: NSRect(x: row.frame.width - 50, y: 0, width: 50, height: row.frame.height)) + switchButton.state = state + switchButton.action = action + switchButton.target = self + + toggle = switchButton + } else { + let button: NSButton = NSButton(frame: NSRect(x: row.frame.width - 30, y: 0, width: 30, height: row.frame.height)) + button.setButtonType(.switch) + button.state = state + button.title = "" + button.action = action + button.isBordered = false + button.isTransparent = true + + toggle = button + } + + row.addSubview(toggle) + row.addSubview(rowTitle) + + return row + } + + private func addDeviceInfo() { + let view: NSView = NSView(frame: NSRect(x: 0, y: self.height - self.deviceInfoHeight, width: self.width, height: self.deviceInfoHeight)) + let leftPanel: NSView = NSView(frame: NSRect(x: 0, y: 0, width: self.width/2, height: self.deviceInfoHeight)) + + let deviceImageView: NSImageView = NSImageView(image: systemKit.device.model.icon) + deviceImageView.frame = NSRect(x: (leftPanel.frame.width - 160)/2, y: ((self.deviceInfoHeight - 120)/2) + 22, width: 160, height: 120) + + let deviceNameField: NSTextField = TextView(frame: NSRect(x: 0, y: 72, width: leftPanel.frame.width, height: 20)) + deviceNameField.alignment = .center + deviceNameField.font = NSFont.systemFont(ofSize: 14, weight: .regular) + deviceNameField.stringValue = systemKit.device.model.name + deviceNameField.isSelectable = true + + let osField: NSTextField = TextView(frame: NSRect(x: 0, y: 52, width: leftPanel.frame.width, height: 18)) + osField.alignment = .center + osField.font = NSFont.systemFont(ofSize: 12, weight: .regular) + osField.stringValue = "macOS \(systemKit.device.os?.name ?? "Unknown") (\(systemKit.device.os?.version.getFullVersion() ?? ""))" + osField.isSelectable = true + + leftPanel.addSubview(deviceImageView) + leftPanel.addSubview(deviceNameField) + leftPanel.addSubview(osField) + + let rightPanel: NSView = NSView(frame: NSRect(x: self.width/2, y: 0, width: self.width/2, height: self.deviceInfoHeight)) + + let iconView: NSImageView = NSImageView(frame: NSRect(x: (leftPanel.frame.width - 100)/2, y: ((self.deviceInfoHeight - 100)/2) + 32, width: 100, height: 100)) + iconView.image = NSImage(named: NSImage.Name("AppIcon"))! + + let infoView: NSView = NSView(frame: NSRect(x: 0, y: 54, width: self.width/2, height: 42)) + + let statsName: NSTextField = TextView(frame: NSRect(x: 0, y: 20, width: leftPanel.frame.width, height: 22)) + statsName.alignment = .center + statsName.font = NSFont.systemFont(ofSize: 20, weight: .regular) + statsName.stringValue = "Stats" + statsName.isSelectable = true + + let statsVersion: NSTextField = TextView(frame: NSRect(x: 0, y: 0, width: leftPanel.frame.width, height: 16)) + statsVersion.alignment = .center + statsVersion.font = NSFont.systemFont(ofSize: 12, weight: .regular) + let versionNumber = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String + statsVersion.stringValue = "Version \(versionNumber)" + statsVersion.isSelectable = true + + infoView.addSubview(statsName) + infoView.addSubview(statsVersion) + + let button: NSButton = NSButton(frame: NSRect(x: (rightPanel.frame.width - 160)/2, y: 20, width: 160, height: 28)) + button.title = "Check for updates" + button.bezelStyle = .rounded + button.target = self + button.action = #selector(checkNewVersion) + + rightPanel.addSubview(iconView) + rightPanel.addSubview(infoView) + rightPanel.addSubview(button) + + view.addSubview(leftPanel) + view.addSubview(rightPanel) + + self.addSubview(view) + } + + @objc func checkNewVersion(_ sender: NSObject) { + NotificationCenter.default.post(name: .checkForUpdates, object: nil, userInfo: nil) + } + + @objc func toggleUpdates(_ sender: NSObject) { + var state: NSControl.StateValue? = nil + if #available(OSX 10.15, *) { + state = sender is NSSwitch ? (sender as! NSSwitch).state: nil + } else { + state = sender is NSButton ? (sender as! NSButton).state: nil + } + + if state != nil { + store.set(key: "checkUpdatesOnLogin", value: state! == NSControl.StateValue.on) + } + } + + @objc func toggleDock(_ sender: NSControl) { + var state: NSControl.StateValue? = nil + if #available(OSX 10.15, *) { + state = sender is NSSwitch ? (sender as! NSSwitch).state: nil + } else { + state = sender is NSButton ? (sender as! NSButton).state: nil + } + + if state != nil { + store.set(key: "dockIcon", value: state! == NSControl.StateValue.on) + } + let dockIconStatus = state == NSControl.StateValue.on ? NSApplication.ActivationPolicy.regular : NSApplication.ActivationPolicy.accessory + NSApp.setActivationPolicy(dockIconStatus) + if state == .off { + NSApplication.shared.activate(ignoringOtherApps: true) + } + } + + @objc func toggleLaunchAtLogin(_ sender: NSControl) { + var state: NSControl.StateValue? = nil + if #available(OSX 10.15, *) { + state = sender is NSSwitch ? (sender as! NSSwitch).state: nil + } else { + state = sender is NSButton ? (sender as! NSButton).state: nil + } + + LaunchAtLogin.isEnabled = state! == NSControl.StateValue.on + if !store.exist(key: "runAtLoginInitialized") { + store.set(key: "runAtLoginInitialized", value: true) + } + } +} diff --git a/Stats/Views/PopupViewController.swift b/Stats/Views/PopupViewController.swift deleted file mode 100644 index 53063686..00000000 --- a/Stats/Views/PopupViewController.swift +++ /dev/null @@ -1,288 +0,0 @@ -// -// MainViewController.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 02/09/2019. -// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa -import ServiceManagement - -public let TabWidth: CGFloat = 300 -public let TabHeight: CGFloat = 356 - -class MainViewController: NSViewController { - let defaults = UserDefaults.standard - - @IBOutlet weak var tabView: NSTabView! - @IBOutlet weak var topStackView: NSStackView! - - var segmentsControl: NSSegmentedControl! - var settingsButton: NSButton! - - static func Init() -> MainViewController { - let storyboard = NSStoryboard.init(name: "Main", bundle: nil) - let identifier = NSStoryboard.SceneIdentifier("MainViewController") - - guard let viewcontroller = storyboard.instantiateController(withIdentifier: identifier) as? MainViewController else { - fatalError("Why cant i find MainViewController? - Check Main.storyboard") - } - - return viewcontroller - } - - override func viewDidLoad() { - super.viewDidLoad() - - makeHeader() - } - - override func viewWillAppear() { - if self.segmentsControl == nil || self.segmentsControl.selectedSegment == -1 { return } - menuBar?.modules[self.segmentsControl.selectedSegment].popup.setActive(true) - - DispatchQueue.global(qos: .background).async { - for module in menuBar!.modules { - if module.popup.available && module.available && module.enabled { - module.readers.filter{ $0.optional }.forEach { reader in - reader.toggleEnable(true) - } - } - } - } - } - - override func viewWillDisappear() { - if self.segmentsControl == nil || self.segmentsControl.selectedSegment == -1 { return } - menuBar?.modules[self.segmentsControl.selectedSegment].popup.setActive(false) - - DispatchQueue.global(qos: .background).async { - for module in menuBar!.modules { - 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) - } - } - } - } - } - } - - public func reload() { - for tab in self.tabView.tabViewItems { - self.tabView.removeTabViewItem(tab) - } - for view in self.topStackView.subviews { - view.removeFromSuperview() - } - self.segmentsControl = NSSegmentedControl(labels: [], trackingMode: .selectOne, target: self, action: #selector(self.switchTabs)) - self.makeHeader() - } - - private func makeHeader() { - var list: [String] = [] - for module in menuBar!.modules { - if module.popup.available && module.available && module.enabled { - list.append(module.name) - - 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(tab) - } - } - - let button = NSButton(frame: NSRect(x: 0, y: 0, width: 26, height: 20)) - button.title = "" - button.image = NSImage(named: NSImage.Name("NSActionTemplate")) - button.imagePosition = .imageOnly - button.bezelStyle = .texturedSquare - button.setButtonType(.momentaryPushIn) - button.action = #selector(showSettings) - - button.widthAnchor.constraint(equalToConstant: 26).isActive = true - button.heightAnchor.constraint(equalToConstant: 21).isActive = true - - if list.count > 0 { - self.segmentsControl = NSSegmentedControl(labels: list, trackingMode: NSSegmentedControl.SwitchTracking.selectOne, target: self, action: #selector(switchTabs)) - self.segmentsControl.setSelected(true, forSegment: 0) - self.segmentsControl.segmentDistribution = .fillEqually - - self.topStackView.addView(self.segmentsControl, in: NSStackView.Gravity.center) - } else { - self.topStackView.addView(NSView(frame: NSRect(x: 0, y: 0, width: 0, height: 0)), in: NSStackView.Gravity.center) - tabView.addTabViewItem(generateEmptyTabView()) - } - self.topStackView.addView(button, in: NSStackView.Gravity.center) - } - - @objc func switchTabs(_ sender: NSSegmentedControl) { - if let selectedLabel = self.segmentsControl.label(forSegment: sender.selectedSegment) { - let tabNumber = self.tabView.indexOfTabViewItem(withIdentifier: selectedLabel) - - menuBar?.modules.forEach({ module in - if module.name == selectedLabel && !module.popup.active { - module.popup.setActive(true) - } else if module.popup.active { - module.popup.setActive(false) - } - }) - - self.tabView.selectTabViewItem(at: tabNumber) - } - } - - @IBAction func showSettings(_ sender: NSButton) { - let settings = buildSettings() - let p = NSPoint(x: NSEvent.mouseLocation.x + 3, y: NSEvent.mouseLocation.y - 3) - settings.popUp(positioning: settings.item(at: 0), at:p , in: nil) - } - - private func buildSettings() -> NSMenu { - let menu = NSMenu() - - for module in menuBar!.modules { - if module.available { - menu.addItem(module.menu) - } - } - - menu.addItem(NSMenuItem.separator()) - - let openActivityMonitorMenu = NSMenuItem(title: "Open Activity Monitor", action: #selector(openActivityMonitor), keyEquivalent: "") - openActivityMonitorMenu.target = self - - let checkForUpdates = NSMenuItem(title: "Check for updates on start", action: #selector(toggleMenu), keyEquivalent: "") - checkForUpdates.state = defaults.bool(forKey: "checkUpdatesOnLogin") || defaults.object(forKey: "checkUpdatesOnLogin") == nil ? NSControl.StateValue.on : NSControl.StateValue.off - checkForUpdates.target = self - - let runAtLogin = NSMenuItem(title: "Start at login", action: #selector(toggleMenu), keyEquivalent: "") - runAtLogin.state = LaunchAtLogin.isEnabled ? NSControl.StateValue.on : NSControl.StateValue.off - runAtLogin.target = self - - let dockIcon = NSMenuItem(title: "Show icon in dock", action: #selector(toggleMenu), keyEquivalent: "") - dockIcon.state = defaults.bool(forKey: "dockIcon") ? NSControl.StateValue.on : NSControl.StateValue.off - dockIcon.target = self - - let updateMenu = NSMenuItem(title: "Check for updates", action: #selector(checkUpdate), keyEquivalent: "") - updateMenu.target = self - - let aboutMenu = NSMenuItem(title: "About Stats", action: #selector(openAbout), keyEquivalent: "") - aboutMenu.target = self - - menu.addItem(checkForUpdates) - menu.addItem(runAtLogin) - menu.addItem(dockIcon) - - menu.addItem(NSMenuItem.separator()) - - menu.addItem(openActivityMonitorMenu) - menu.addItem(updateMenu) - menu.addItem(aboutMenu) - menu.addItem(NSMenuItem(title: "Quit Stats", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "")) - - return menu - } - - @objc func openActivityMonitor(_ sender: NSMenuItem) { - NSWorkspace.shared.launchApplication( - withBundleIdentifier: "com.apple.ActivityMonitor", - options: [.default], - additionalEventParamDescriptor: nil, - launchIdentifier: nil) - } - - @objc func checkUpdate(_ sender : NSMenuItem) { - let updatesVC: NSWindowController? = NSStoryboard(name: "Updates", bundle: nil).instantiateController(withIdentifier: "UpdatesVC") as? NSWindowController - updatesVC?.window?.center() - updatesVC?.window?.level = .floating - updatesVC!.showWindow(self) - } - - @objc func openAbout(_ sender : NSMenuItem) { - let aboutVC: NSWindowController? = NSStoryboard(name: "About", bundle: nil).instantiateController(withIdentifier: "AboutVC") as? NSWindowController - aboutVC?.window?.center() - aboutVC?.window?.level = .floating - aboutVC!.showWindow(self) - } - - @objc func toggleMenu(_ sender : NSMenuItem) { - let status = sender.state != NSControl.StateValue.on - sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on - - switch sender.title { - case "Start at login": - LaunchAtLogin.isEnabled = status - if self.defaults.object(forKey: "runAtLoginInitialized") == nil { - self.defaults.set(true, forKey: "runAtLoginInitialized") - } - case "Check for updates on start": - self.defaults.set(status, forKey: "checkUpdatesOnLogin") - case "Show icon in dock": - self.defaults.set(status, forKey: "dockIcon") - let iconStatus = status ? NSApplication.ActivationPolicy.regular : NSApplication.ActivationPolicy.accessory - NSApp.setActivationPolicy(iconStatus) - return - default: break - } - } -} - - -func LabelField(string: String) -> NSTextField { - let label: NSTextField = NSTextField(string: string) - - label.isEditable = false - label.isSelectable = false - label.isBezeled = false - label.textColor = .darkGray - label.alignment = .center - label.font = NSFont.systemFont(ofSize: 12, weight: .regular) - label.backgroundColor = NSColor(hexString: "#dddddd", alpha: 0) - - return label -} - -func ValueField(string: String) -> NSTextField { - let label: NSTextField = NSTextField(string: string) - - label.isEditable = false - label.isSelectable = false - label.isBezeled = false - label.textColor = .black - label.alignment = .center - label.font = NSFont.systemFont(ofSize: 13, weight: .regular) - label.backgroundColor = NSColor(hexString: "#dddddd", alpha: 0) - - return label -} - -func generateEmptyTabView() -> NSTabViewItem { - let emptyTabView = NSTabViewItem() - emptyTabView.view?.frame = NSRect(x: 0, y: 0, width: TabWidth, height: TabHeight) - emptyTabView.label = "empty" - emptyTabView.identifier = "empty" - emptyTabView.view?.wantsLayer = true - emptyTabView.view?.layer?.backgroundColor = NSColor.white.cgColor - - let text: NSTextField = NSTextField(string: "No dashboard available") - text.isEditable = false - text.isSelectable = false - text.isBezeled = false - text.wantsLayer = true - text.textColor = .labelColor - text.canDrawSubviewsIntoLayer = true - text.alignment = .center - text.font = NSFont.systemFont(ofSize: 13, weight: .regular) - text.frame = NSRect(x: 0, y: 0, width: TabWidth, height: 22) - text.frame.origin.y = ((emptyTabView.view?.frame.size.height)! - 22) / 2 - - emptyTabView.view?.addSubview(text) - - return emptyTabView -} diff --git a/Stats/Views/Settings.swift b/Stats/Views/Settings.swift new file mode 100644 index 00000000..41696614 --- /dev/null +++ b/Stats/Views/Settings.swift @@ -0,0 +1,349 @@ +// +// Settings.swift +// Stats +// +// Created by Serhiy Mytrovtsiy on 12/04/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import ModuleKit +import StatsKit + +class SettingsWindow: NSWindow, NSWindowDelegate { + private let viewController: SettingsViewController = SettingsViewController() + + init() { + let w = NSScreen.main!.frame.width + let h = NSScreen.main!.frame.height + super.init( + contentRect: NSMakeRect(w - self.viewController.view.frame.width, h - self.viewController.view.frame.height, self.viewController.view.frame.width, self.viewController.view.frame.height), + styleMask: [.closable, .titled, .miniaturizable], + backing: .buffered, + defer: true + ) + + self.contentViewController = self.viewController + self.animationBehavior = .default + self.collectionBehavior = .transient + self.titlebarAppearsTransparent = true + self.appearance = NSAppearance(named: .darkAqua) + self.center() + self.setIsVisible(false) + + let windowController = NSWindowController() + windowController.window = self + windowController.loadWindow() + } + + public func setModules() { + self.viewController.setModules(&modules) + if modules.filter({ $0.enabled != false && $0.available != false }).count == 0 { + self.setIsVisible(true) + } + } + + public func openMenu(_ title: String) { + self.viewController.openMenu(title) + } + + override func mouseUp(with: NSEvent) { + NotificationCenter.default.post(name: .clickInSettings, object: nil, userInfo: nil) + } +} + +private class SettingsViewController: NSViewController { + private var settings: SettingsView + + public init() { + self.settings = SettingsView(frame: NSRect(x: 0, y: 0, width: 720, height: 480)) + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + self.view = self.settings + } + + override func viewDidLoad() { + super.viewDidLoad() + } + + public func setModules(_ list: UnsafeMutablePointer<[Module]>) { + self.settings.setModules(list) + } + + public func openMenu(_ title: String) { + self.settings.openMenu(title) + } +} + +private class SettingsView: NSView { + private var modules: UnsafeMutablePointer<[Module]>? + private let navigationWidth: CGFloat = 180 + private let buttonHeight: CGFloat = 45 + + private var navigationView: NSScrollView? = nil + private var buttonsView: NSView? = nil + private var mainView: NSView? = nil + + private var applicationSettings: NSView = ApplicationSettings() + + override init(frame: NSRect) { + super.init(frame: CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.width, height: frame.height)) + self.wantsLayer = true + + NotificationCenter.default.addObserver(self, selector: #selector(menuCallback), name: .openSettingsView, object: nil) + + let navigationView: NSScrollView = NSScrollView(frame: NSRect(x: 0, y: buttonHeight, width: navigationWidth, height: frame.height - buttonHeight)) + navigationView.wantsLayer = true + navigationView.drawsBackground = false + + navigationView.addSubview(MenuView(n: 0, icon: NSImage(named: NSImage.Name("apps"))!, title: "Stats")) + + let buttonsView: NSView = NSView(frame: NSRect(x: 0, y: 0, width: navigationWidth, height: buttonHeight)) + buttonsView.wantsLayer = true + + buttonsView.addSubview(self.makeButton(4, title: "Open Activity Monitor", image: "chart", action: #selector(openActivityMonitor))) + buttonsView.addSubview(self.makeButton(3, title: "Report a bug", image: "bug", action: #selector(reportBug))) + buttonsView.addSubview(self.makeButton(1, title: "Close application", image: "power", action: #selector(closeApp))) + + let mainView: NSView = NSView(frame: NSRect(x: navigationWidth, y: 1, width: frame.width - navigationWidth-1, height: frame.height-1)) + mainView.wantsLayer = true + mainView.layer?.cornerRadius = 3 + mainView.layer?.maskedCorners = [.layerMaxXMinYCorner] + + self.addSubview(navigationView) + self.addSubview(buttonsView) + self.addSubview(mainView) + + self.navigationView = navigationView + self.mainView = mainView + self.buttonsView = buttonsView + + self.openMenu("Stats") + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func draw(_ dirtyRect: NSRect) { + super.draw(dirtyRect) + + NSColor.gridColor.set() + var line = NSBezierPath() + line.move(to: NSMakePoint(0, self.buttonHeight)) + line.line(to: NSMakePoint(self.navigationWidth, self.buttonHeight)) + line.lineWidth = 1 + line.stroke() + + line = NSBezierPath() + line.move(to: NSMakePoint(self.navigationWidth, 0)) + line.line(to: NSMakePoint(self.navigationWidth, self.frame.height)) + line.lineWidth = 1 + line.stroke() + } + + public func openMenu(_ title: String) { + self.navigationView?.subviews.forEach({ (m: NSView) in + if let menu = m as? MenuView { + if menu.title == title { + menu.activate() + } + } + }) + } + + public func setModules(_ list: UnsafeMutablePointer<[Module]>) { + list.pointee.forEach { (m: Module) in + if !m.available { return } + let n: Int = (self.navigationView?.subviews.count ?? 2)!-1 + let menu: NSView = MenuView(n: n, icon: m.config.icon, title: m.config.name) + self.navigationView?.addSubview(menu) + } + self.modules = list +// self.openMenu("CPU") + } + + @objc private func menuCallback(_ notification: Notification) { + if let title = notification.userInfo?["module"] as? String { + var view: NSView = self.applicationSettings + + let detectedModule = self.modules?.pointee.first{ $0.config.name == title } + if detectedModule != nil { + if let v = detectedModule?.settings { + view = v + } + } + + self.mainView?.subviews.forEach{ $0.removeFromSuperview() } + self.mainView?.addSubview(view) + + self.navigationView?.subviews.forEach({ (m: NSView) in + if let menu = m as? MenuView { + if menu.active { + menu.reset() + } + } + }) + } + } + + private func makeButton(_ n: Int, title: String, image: String, action: Selector) -> NSButton { + let button = NSButtonWithPadding() + button.frame = CGRect(x: Int(self.navigationWidth) - (45*n), y: 0, width: 44, height: 44) + button.verticalPadding = 20 + button.horizontalPadding = 20 + button.title = title + button.bezelStyle = .regularSquare + button.translatesAutoresizingMaskIntoConstraints = false + button.imageScaling = .scaleNone + button.image = Bundle(for: type(of: self)).image(forResource: image)! + button.contentTintColor = .lightGray + button.isBordered = false + button.action = action + button.target = self + button.focusRingType = .none + + let rect = NSRect(x: Int(self.navigationWidth) - (45*n), y: 0, width: 44, height: 44) + let trackingArea = NSTrackingArea(rect: rect, options: [NSTrackingArea.Options.activeAlways, NSTrackingArea.Options.mouseEnteredAndExited, NSTrackingArea.Options.activeInActiveApp], owner: self, userInfo: ["button": title]) + self.addTrackingArea(trackingArea) + + return button + } + + override func mouseEntered(with: NSEvent) { + if let userData = with.trackingArea?.userInfo as? [String : AnyObject] { + if let title = userData["button"] as? String { + let b = self.buttonsView?.subviews.first{ $0 is NSButton && ($0 as! NSButton).title == title } + if b != nil && b is NSButton { + (b as! NSButton).contentTintColor = .labelColor + (b as! NSButton).layer?.backgroundColor = .init(gray: 0.1, alpha: 0.5) + NSCursor.pointingHand.set() + } + } + } + } + + override func mouseExited(with: NSEvent) { + if let userData = with.trackingArea?.userInfo as? [String : AnyObject] { + if let title = userData["button"] as? String { + let b = self.buttonsView?.subviews.first{ $0 is NSButton && ($0 as! NSButton).title == title } + if b != nil && b is NSButton { + (b as! NSButton).contentTintColor = .lightGray + (b as! NSButton).layer?.backgroundColor = .clear + NSCursor.arrow.set() + } + } + } + } + + @objc public func openActivityMonitor(_ sender: Any) { + NSWorkspace.shared.launchApplication( + withBundleIdentifier: "com.apple.ActivityMonitor", + options: [.default], + additionalEventParamDescriptor: nil, + launchIdentifier: nil + ) + self.window?.setIsVisible(false) + } + + @objc public func reportBug(_ sender: Any) { + NSWorkspace.shared.open(URL(string: "https://github.com/exelban/stats/issues/new")!) + } + + @objc public func aboutApp(_ sender: Any) { + print("about app") + } + + @objc public func closeApp(_ sender: Any) { + NSApp.terminate(sender) + } +} + +private class MenuView: NSView { + private let height: CGFloat = 40 + private let width: CGFloat = 180 + + private var imageView: NSImageView? = nil + private var titleView: NSTextField? = nil + + public let title: String + public var active: Bool = false + + init(n: Int, icon: NSImage?, title: String) { + self.title = title + super.init(frame: NSRect(x: 0, y: self.height*CGFloat(n), width: width, height: self.height)) + self.wantsLayer = true + self.layer?.backgroundColor = .clear + + let rect = NSRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height) + let trackingArea = NSTrackingArea(rect: rect, options: [NSTrackingArea.Options.activeAlways, NSTrackingArea.Options.mouseEnteredAndExited, NSTrackingArea.Options.activeInActiveApp], owner: self, userInfo: ["menu": title]) + self.addTrackingArea(trackingArea) + + let imageView = NSImageView() + if icon != nil { + imageView.image = icon! + } + imageView.frame = NSRect(x: 8, y: (self.height - 18)/2, width: 18, height: 18) + imageView.wantsLayer = true + imageView.contentTintColor = .secondaryLabelColor + + let titleView = TextView(frame: NSMakeRect(34, (self.height - 16)/2, 100, 17)) + titleView.alignment = .natural + titleView.textColor = .secondaryLabelColor + titleView.font = NSFont.systemFont(ofSize: 14, weight: .regular) + titleView.stringValue = title + + self.addSubview(imageView) + self.addSubview(titleView) + + self.imageView = imageView + self.titleView = titleView + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func mouseEntered(with: NSEvent) { + self.titleView?.textColor = .labelColor + self.imageView?.contentTintColor = .labelColor + self.layer?.backgroundColor = .init(gray: 0.1, alpha: 0.5) + NSCursor.pointingHand.set() + } + + override func mouseExited(with: NSEvent) { + if !self.active { + self.reset() + } + NSCursor.arrow.set() + } + + override func mouseDown(with: NSEvent) { + self.activate() + } + + public func reset() { + self.titleView?.textColor = .secondaryLabelColor + self.imageView?.contentTintColor = .secondaryLabelColor + self.layer?.backgroundColor = .clear + self.active = false + } + + public func activate() { + NotificationCenter.default.post(name: .openSettingsView, object: nil, userInfo: ["module": self.title]) + + self.titleView?.textColor = .labelColor + self.imageView?.contentTintColor = .labelColor + self.layer?.backgroundColor = .init(gray: 0.1, alpha: 0.5) + self.active = true + } +} diff --git a/Stats/Views/Update.swift b/Stats/Views/Update.swift new file mode 100644 index 00000000..3c90fa7b --- /dev/null +++ b/Stats/Views/Update.swift @@ -0,0 +1,179 @@ +// +// Update.swift +// Stats +// +// Created by Serhiy Mytrovtsiy on 21/05/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import StatsKit +import os.log + +class UpdateWindow: NSWindow, NSWindowDelegate { + private let viewController: UpdateViewController = UpdateViewController() + + init() { + let w = NSScreen.main!.frame.width + let h = NSScreen.main!.frame.height + super.init( + contentRect: NSMakeRect(w - self.viewController.view.frame.width, h - self.viewController.view.frame.height, self.viewController.view.frame.width, self.viewController.view.frame.height), + styleMask: [.closable, .titled], + backing: .buffered, + defer: true + ) + + self.contentViewController = self.viewController + self.animationBehavior = .default + self.collectionBehavior = .transient + self.titlebarAppearsTransparent = true + self.appearance = NSAppearance(named: .darkAqua) + self.center() + self.setIsVisible(false) + + let windowController = NSWindowController() + windowController.window = self + windowController.loadWindow() + } + + public func open(_ v: version) { + if !self.isVisible { + self.setIsVisible(true) + self.makeKeyAndOrderFront(nil) + } + self.viewController.open(v) + } +} + +private class UpdateViewController: NSViewController { + private var update: UpdateView + + public init() { + self.update = UpdateView(frame: NSRect(x: 0, y: 0, width: 280, height: 150)) + super.init(nibName: nil, bundle: nil) + self.view = self.update + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func open(_ v: version) { + self.update.setVersions(v) + } +} + +private class UpdateView: NSView { + private let progressBar: NSProgressIndicator = NSProgressIndicator() + private var version: version? = nil + private var informationView: NSView? = nil + private var noNew: NSView? = nil + private var currentVersion: NSTextField? = nil + private var latestVersion: NSTextField? = nil + + override init(frame: NSRect) { + super.init(frame: CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.width, height: frame.height)) + self.wantsLayer = true + + self.addProgressBar() + self.addInformation() + self.addNoNew() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func addProgressBar() { + self.progressBar.isDisplayedWhenStopped = false + self.progressBar.frame = NSRect(x: (self.frame.width - 22)/2, y: (self.frame.height - 22)/2, width: 22, height: 22) + self.progressBar.style = .spinning + + self.addSubview(self.progressBar) + } + + private func addInformation() { + let view: NSView = NSView(frame: NSRect(x: 10, y: 10, width: self.frame.width - 20, height: self.frame.height - 20)) + + let title: NSTextField = TextView(frame: NSRect(x: 0, y: view.frame.height - 18, width: view.frame.width, height: 18)) + title.font = NSFont.systemFont(ofSize: 14, weight: .bold) + title.alignment = .center + title.stringValue = "New version available" + + let currentVersion: NSTextField = TextView(frame: NSRect(x: 0, y: title.frame.origin.y - 40, width: view.frame.width, height: 16)) + currentVersion.stringValue = "Current version: 0.0.0" + + let latestVersion: NSTextField = TextView(frame: NSRect(x: 0, y: currentVersion.frame.origin.y - 22, width: view.frame.width, height: 16)) + latestVersion.stringValue = "Latest version: 0.0.0" + + let button: NSButton = NSButton(frame: NSRect(x: 0, y: 0, width: view.frame.width, height: 26)) + button.title = "Download" + button.bezelStyle = .rounded + button.action = #selector(self.download) + button.target = self + + view.addSubview(title) + view.addSubview(currentVersion) + view.addSubview(latestVersion) + view.addSubview(button) + view.isHidden = true + self.addSubview(view) + self.informationView = view + self.currentVersion = currentVersion + self.latestVersion = latestVersion + } + + private func addNoNew() { + let view: NSView = NSView(frame: NSRect(x: 10, y: 10, width: self.frame.width - 20, height: self.frame.height - 20)) + + let title: NSTextField = TextView(frame: NSRect(x: 0, y: ((view.frame.height - 18)/2)+20, width: view.frame.width, height: 18)) + title.font = NSFont.systemFont(ofSize: 14, weight: .regular) + title.alignment = .center + title.stringValue = "The latest version of Stats installed" + + let button: NSButton = NSButton(frame: NSRect(x: 0, y: 0, width: view.frame.width, height: 26)) + button.title = "Close" + button.bezelStyle = .rounded + button.action = #selector(self.close) + button.target = self + + view.addSubview(button) + view.addSubview(title) + self.addSubview(view) + self.noNew = view + } + + public func setVersions(_ v: version) { + self.progressBar.stopAnimation(self) + self.noNew?.isHidden = true + self.informationView?.isHidden = true + + if v.newest { + self.informationView?.isHidden = false + self.version = v + + currentVersion?.stringValue = "Current version: \(v.current)" + latestVersion?.stringValue = "Latest version: \(v.latest)" + return + } + + self.noNew?.isHidden = false + } + + @objc func close(_ sender: Any) { + self.window?.setIsVisible(false) + } + + @objc func download(_ sender: Any) { + guard let urlString = self.version?.url, let url = URL(string: urlString) else { + return + } + os_log(.debug, log: log, "start downloading new version of app from: %s", "\(url.absoluteString)") + updater.download(url) + self.progressBar.startAnimation(self) + self.informationView?.isHidden = true + } +} diff --git a/Stats/Views/UpdatesViewController.swift b/Stats/Views/UpdatesViewController.swift deleted file mode 100644 index 033ddee4..00000000 --- a/Stats/Views/UpdatesViewController.swift +++ /dev/null @@ -1,79 +0,0 @@ -// -// UpdatesViewController.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 05/09/2019. -// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa -import Foundation - -class UpdatesVC: NSViewController { - @IBOutlet weak var mainView: NSStackView! - @IBOutlet weak var spinnerView: NSView! - @IBOutlet weak var noInternetView: NSView! - @IBOutlet weak var mainTextLabel: NSTextFieldCell! - @IBOutlet weak var currentVersionLabel: NSTextField! - @IBOutlet weak var latestVersionLabel: NSTextField! - @IBOutlet weak var downloadButton: NSButton! - @IBOutlet weak var spinner: NSProgressIndicator! - - var url: String? - - override func viewDidLoad() { - super.viewDidLoad() - self.view.wantsLayer = true - - self.spinner.startAnimation(self) - - updater.check() { result, error in - if error != nil && error as! String == "No internet connection" { - DispatchQueue.main.async(execute: { - self.spinnerView.isHidden = true - self.noInternetView.isHidden = false - }) - return - } - - guard error == nil, let version: version = result else { - print("Error: \(error ?? "check error")") - return - } - - DispatchQueue.main.async(execute: { - self.spinner.stopAnimation(self) - self.spinnerView.isHidden = true - self.mainView.isHidden = false - self.currentVersionLabel.stringValue = version.current - self.latestVersionLabel.stringValue = version.latest - self.url = version.url - - if !version.newest { - self.mainTextLabel.stringValue = "No new version available" - self.downloadButton.isEnabled = false - } - }) - } - } - - override func awakeFromNib() { - if self.view.layer != nil { - self.view.window?.backgroundColor = .windowBackgroundColor - } - } - - @IBAction func download(_ sender: Any) { - guard let urlString = self.url, let url = URL(string: urlString) else { - return - } - updater.download(url) - self.spinner.startAnimation(self) - self.spinnerView.isHidden = false - self.mainView.isHidden = true - } - - @IBAction func exit(_ sender: Any) { - self.view.window?.close() - } -} diff --git a/Stats/Widgets/Battery/BatteryPercentageWidget.swift b/Stats/Widgets/Battery/BatteryPercentageWidget.swift deleted file mode 100644 index dce1f30e..00000000 --- a/Stats/Widgets/Battery/BatteryPercentageWidget.swift +++ /dev/null @@ -1,67 +0,0 @@ -// -// BatteryPercentageWidget.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 12/09/2019. -// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa - -class BatteryPercentageWidget: BatteryWidget { - private var percentageValue: NSTextField = NSTextField() - - private let percentageLowWidth: CGFloat = 23 - private let percentageWidth: CGFloat = 30 - private let percentageFullWidth: CGFloat = 36 - - override init(frame: NSRect) { - super.init(frame: CGRect(x: 0, y: 0, width: widgetSize.width, height: widgetSize.height)) - self.name = "BatteryPercentage" - self.drawPercentage() - self.update() - } - - required init?(coder decoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func drawPercentage() { - self.percentageValue = NSTextField(frame: NSMakeRect(0, 0, percentageWidth, self.frame.size.height - 2)) - percentageValue.isEditable = false - percentageValue.isSelectable = false - percentageValue.isBezeled = false - percentageValue.wantsLayer = true - percentageValue.textColor = .labelColor - percentageValue.backgroundColor = .controlColor - percentageValue.canDrawSubviewsIntoLayer = true - percentageValue.alignment = .right - percentageValue.font = NSFont.systemFont(ofSize: 12, weight: .regular) - percentageValue.stringValue = "\(Int(self.value * 100))%" - if self.value == 0 { - percentageValue.isHidden = true - } - - self.addSubview(percentageValue) - } - - override func update() { - if self.value == 0 { return } - - if self.percentageValue.isHidden { - self.percentageValue.isHidden = false - } - self.percentageValue.stringValue = "\(Int(self.value * 100))%" - - if self.value == 1 && self.size != self.batterySize + percentageFullWidth { - self.changeWidth(width: 0) - self.percentageValue.frame.size.width = 0 - } else if self.value < 0.1 && self.size != self.batterySize + percentageLowWidth { - self.changeWidth(width: percentageLowWidth) - self.percentageValue.frame.size.width = percentageLowWidth - } else if self.value >= 0.1 && self.value != 1 && self.size != self.batterySize + percentageWidth { - self.changeWidth(width: percentageWidth) - self.percentageValue.frame.size.width = percentageWidth - } - } -} diff --git a/Stats/Widgets/Battery/BatteryTimeWidget.swift b/Stats/Widgets/Battery/BatteryTimeWidget.swift deleted file mode 100644 index a5247a0f..00000000 --- a/Stats/Widgets/Battery/BatteryTimeWidget.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// BatteryTimeWidget.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 12/09/2019. -// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa - -class BatteryTimeWidget: BatteryWidget { - private var timeValue: NSTextField = NSTextField() - private let timeWidth: CGFloat = 62 - private let timeHourWidth: CGFloat = 42 - - override init(frame: NSRect) { - super.init(frame: CGRect(x: 0, y: 0, width: widgetSize.width, height: widgetSize.height)) - self.name = "BatteryTime" - self.drawTime() - self.changeWidth(width: self.timeWidth) - self.update() - } - - required init?(coder decoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func drawTime() { - self.timeValue = NSTextField(frame: NSMakeRect(0, 0, timeWidth, self.frame.size.height - 4)) - timeValue.isEditable = false - timeValue.isSelectable = false - timeValue.isBezeled = false - timeValue.wantsLayer = true - timeValue.textColor = .labelColor - timeValue.backgroundColor = .controlColor - timeValue.canDrawSubviewsIntoLayer = true - timeValue.alignment = .right - timeValue.font = NSFont.systemFont(ofSize: 11, weight: .regular) - timeValue.stringValue = (self.time*60).printSecondsToHoursMinutesSeconds() - if self.time <= 0 { - timeValue.isHidden = true - } - - self.addSubview(timeValue) - } - - override func update() { - if self.value == 0 { return } - - if self.time > 0 { - if self.timeValue.isHidden { - self.timeValue.isHidden = false - } - self.timeValue.stringValue = (self.time*60).printSecondsToHoursMinutesSeconds() - } - - if self.time <= 0 { - self.changeWidth(width: 0) - self.timeValue.frame.size.width = 0 - self.timeValue.isHidden = true - } else if self.time <= 59 { - self.changeWidth(width: timeHourWidth) - self.timeValue.frame.size.width = timeHourWidth - } else if self.time > 59 { - self.changeWidth(width: timeWidth) - self.timeValue.frame.size.width = timeWidth - } - } -} diff --git a/Stats/Widgets/Battery/BatteryWidget.swift b/Stats/Widgets/Battery/BatteryWidget.swift deleted file mode 100644 index 22d833a8..00000000 --- a/Stats/Widgets/Battery/BatteryWidget.swift +++ /dev/null @@ -1,132 +0,0 @@ -// -// BatteryView.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 14/06/2019. -// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa - -class BatteryWidget: NSView, Widget { - public var name: String = "Battery" - public var menus: [NSMenuItem] = [] - public var size: CGFloat = 30 - public var batterySize: CGFloat = 30 - - private let defaults = UserDefaults.standard - private var color: Bool = false - - public var value: Double = 0 - public var time: Double = 0 - public var charging: Bool = false - - override var intrinsicContentSize: CGSize { - return CGSize(width: self.frame.size.width, height: self.frame.size.height) - } - - override init(frame: NSRect) { - self.value = 0.0 - self.time = 0.0 - self.charging = false - super.init(frame: CGRect(x: 0, y: 0, width: self.size, height: widgetSize.height)) - self.wantsLayer = true - } - - required init?(coder decoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func start() { - self.color = defaults.object(forKey: "\(name)_color") != nil ? defaults.bool(forKey: "\(name)_color") : false - self.initMenu() - self.redraw() - } - - func initMenu() {} - func update() { - self.changeWidth(width: 0) - } - - override func draw(_ dirtyRect: NSRect) { - super.draw(dirtyRect) - - var x: CGFloat = 4.0 - let w: CGFloat = self.batterySize - (x * 2) - let h: CGFloat = 11.0 - let y: CGFloat = (dirtyRect.size.height - h) / 2 - let r: CGFloat = 1.0 - if dirtyRect.size.width != batterySize { - x += self.size - batterySize - } - - let battery = NSBezierPath(roundedRect: NSRect(x: x-1, y: y, width: w-1, height: h), xRadius: r, yRadius: r) - - let bPX: CGFloat = x+w-2 - let bPY: CGFloat = (dirtyRect.size.height / 2) - 2 - let batteryPoint = NSBezierPath(roundedRect: NSRect(x: bPX, y: bPY, width: 2, height: 4), xRadius: r, yRadius: r) - if self.charging { - NSColor.systemGreen.set() - } else { - NSColor.labelColor.set() - } - batteryPoint.lineWidth = 1.1 - batteryPoint.stroke() - batteryPoint.fill() - - let maxWidth = w-4 - let inner = NSBezierPath(roundedRect: NSRect(x: x+0.5, y: y+1.5, width: maxWidth*CGFloat(self.value), height: h-3), xRadius: 0.5, yRadius: 0.5) - self.value.batteryColor(color: self.color).set() - inner.lineWidth = 0 - inner.stroke() - inner.close() - inner.fill() - - if self.charging { - NSColor.systemGreen.set() - } else { - NSColor.labelColor.set() - } - battery.lineWidth = 0.8 - battery.stroke() - } - - func redraw() { - self.display() - } - - func setValue(data: [Double]) { - let value: Double = data.first! - let time: Double = data.last! - var changed: Bool = false - - if self.value != value { - self.value = value - changed = true - } - if self.time != time { - self.time = time - changed = true - } - - if changed { - self.redraw() - self.update() - } - } - - func setCharging(value: Bool) { - if self.charging != value { - self.charging = value - self.redraw() - } - } - - func changeWidth(width: CGFloat) { - self.size = batterySize + width - self.frame.size.width = self.size - if menuBar != nil { - menuBar!.refresh() - } - } -} diff --git a/Stats/Widgets/Charts/BarChart.swift b/Stats/Widgets/Charts/BarChart.swift deleted file mode 100644 index a3947d3f..00000000 --- a/Stats/Widgets/Charts/BarChart.swift +++ /dev/null @@ -1,146 +0,0 @@ -// -// BarChart.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 09.07.2019. -// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa - -class BarChart: NSView, Widget { - public var name: String = "BarChart" - public var menus: [NSMenuItem] = [] - - private var size: CGFloat = widgetSize.width + 10 - private var labelPadding: CGFloat = 12.0 - private var label: Bool = false - private let defaults = UserDefaults.standard - - private var partitions: [Double] = [] - - override var intrinsicContentSize: CGSize { - return CGSize(width: self.frame.size.width, height: self.frame.size.height) - } - - override init(frame: NSRect) { - self.label = defaults.object(forKey: "\(name)_label") != nil ? defaults.bool(forKey: "\(name)_label") : true - self.partitions = Array(repeating: 0.0, count: 1) - super.init(frame: CGRect(x: 0, y: 0, width: 0, height: widgetSize.height)) - self.wantsLayer = true - self.addSubview(NSView()) - } - - required init?(coder decoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func start() { - self.label = defaults.object(forKey: "\(name)_label") != nil ? defaults.bool(forKey: "\(name)_label") : true - self.initPreferences() - } - - func initPreferences() { - let label = NSMenuItem(title: "Label", action: #selector(toggleLabel), keyEquivalent: "") - label.state = self.label ? NSControl.StateValue.on : NSControl.StateValue.off - label.target = self - - self.menus.append(label) - } - - @objc func toggleLabel(_ sender: NSMenuItem) { - sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on - self.defaults.set(sender.state == NSControl.StateValue.on, forKey: "\(self.name)_label") - self.label = (sender.state == NSControl.StateValue.on) - } - - override func draw(_ dirtyRect: NSRect) { - super.draw(dirtyRect) - - let gradientColor: NSColor = NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 0.8) - let width = self.frame.size.width - (widgetSize.margin * 2) - let height = self.frame.size.height - (widgetSize.margin * 2) - - var x = widgetSize.margin - if label { - x = x + labelPadding - } - - let partitionMargin: CGFloat = 0.5 - let partitionsWidth: CGFloat = width - (partitionMargin * 2) - x - var partitionWidth: CGFloat = partitionsWidth - if partitions.count > 1 { - partitionWidth = (partitionsWidth - (partitionMargin * (CGFloat(partitions.count) - 1))) / CGFloat(partitions.count) - } - - for i in 0.. Double in - return v.rounded(toPlaces: 2) - } - if self.partitions != values { - self.partitions = values - self.redraw() - } - } - - func redraw() { - var width: CGFloat = widgetSize.width + 10 - if self.partitions.count == 1 { - width = 18 - } - if self.partitions.count == 2 { - width = 28 - } - if self.label { - width += labelPadding - } - - if self.frame.size.width != width { - self.setFrameSize(NSSize(width: width, height: self.frame.size.height)) - if menuBar != nil { - menuBar!.refresh() - } - } - - self.display() - } -} diff --git a/Stats/Widgets/Charts/LineChart.swift b/Stats/Widgets/Charts/LineChart.swift deleted file mode 100644 index c84a2612..00000000 --- a/Stats/Widgets/Charts/LineChart.swift +++ /dev/null @@ -1,181 +0,0 @@ -// -// LineChart.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 14.07.2019. -// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa -import Charts - -class Chart: NSView, Widget { - public var name: String = "LineChart" - public var menus: [NSMenuItem] = [] - - internal let defaults = UserDefaults.standard - internal var size: CGFloat = widgetSize.width + 7 - internal var labelPadding: CGFloat = 10.0 - internal var label: Bool = false - - internal var height: CGFloat = 0.0 - internal var points: [Double] = [] - - override var intrinsicContentSize: CGSize { - return CGSize(width: self.frame.size.width, height: self.frame.size.height) - } - - override init(frame: NSRect) { - self.points = Array(repeating: 0.0, count: 50) - super.init(frame: CGRect(x: 0, y: 0, width: self.size, height: widgetSize.height)) - self.wantsLayer = true - self.addSubview(NSView()) - } - - required init?(coder decoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func start() { - self.label = defaults.object(forKey: "\(name)_label") != nil ? defaults.bool(forKey: "\(name)_label") : true - self.initMenu() - - if self.label { - self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: self.frame.size.width + labelPadding, height: self.frame.size.height) - } - } - - func initMenu() { - let label = NSMenuItem(title: "Label", action: #selector(toggleLabel), keyEquivalent: "") - label.state = self.label ? NSControl.StateValue.on : NSControl.StateValue.off - label.target = self - - self.menus.append(label) - } - - @objc func toggleLabel(_ sender: NSMenuItem) { - sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on - self.defaults.set(sender.state == NSControl.StateValue.on, forKey: "\(self.name)_label") - self.label = (sender.state == NSControl.StateValue.on) - - var width = self.size - if self.label { - width = width + labelPadding - } - - self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: width, height: self.frame.size.height) - self.redraw() - menuBar!.refresh() - } - - override func draw(_ dirtyRect: NSRect) { - super.draw(dirtyRect) - - 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) - - let context = NSGraphicsContext.current!.cgContext - var xOffset: CGFloat = 4.0 - if label { - xOffset = xOffset + labelPadding - } - let yOffset: CGFloat = 3.0 - if height == 0 { - height = self.frame.size.height - CGFloat((yOffset * 2)) - } - - var xRatio = Double(self.frame.size.width - (xOffset * 2)) / (Double(self.points.count) - 1) - if label { - xRatio = Double(self.frame.size.width - (xOffset * 2) + labelPadding) / (Double(self.points.count) - 1) - } - - let columnXPoint = { (point: Int) -> CGFloat in - return CGFloat((Double(point) * xRatio)) + xOffset - } - let columnYPoint = { (point: Int) -> CGFloat in - return CGFloat((CGFloat(truncating: self.points[point] as NSNumber) * self.height)) + yOffset - } - - let graphPath = NSBezierPath() - let x: CGFloat = columnXPoint(0) - let y: CGFloat = columnYPoint(0) - graphPath.move(to: CGPoint(x: x, y: y)) - - for i in 1.. String { - return Units(bytes: Int64(value)).getReadableSpeed() - } -} diff --git a/Stats/Widgets/Charts/LineChartWithValue.swift b/Stats/Widgets/Charts/LineChartWithValue.swift deleted file mode 100644 index 3e485fcd..00000000 --- a/Stats/Widgets/Charts/LineChartWithValue.swift +++ /dev/null @@ -1,120 +0,0 @@ -// -// LineChartWithValue.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 14.07.2019. -// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa - -class ChartWithValue: Chart { - private var valueLabel: NSTextField = NSTextField() - private var color: Bool = false - - override var intrinsicContentSize: CGSize { - return CGSize(width: self.frame.size.width, height: self.frame.size.height) - } - - override init(frame: NSRect) { - super.init(frame: CGRect(x: 0, y: 0, width: widgetSize.width + 7, height: widgetSize.height)) - self.wantsLayer = true - self.name = "LineChartWithValue" - } - - required init?(coder decoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func start() { - self.label = defaults.object(forKey: "\(name)_label") != nil ? defaults.bool(forKey: "\(name)_label") : true - self.color = defaults.object(forKey: "\(name)_color") != nil ? defaults.bool(forKey: "\(name)_color") : false - self.initMenu() - - if self.label { - self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: self.frame.size.width + labelPadding, height: self.frame.size.height) - } - self.drawValue() - } - - override func initMenu() { - let label = NSMenuItem(title: "Label", action: #selector(toggleLabel), keyEquivalent: "") - label.state = self.label ? NSControl.StateValue.on : NSControl.StateValue.off - label.target = self - - let color = NSMenuItem(title: "Color", action: #selector(toggleColor), keyEquivalent: "") - color.state = self.color ? NSControl.StateValue.on : NSControl.StateValue.off - color.target = self - - self.menus.append(label) - self.menus.append(color) - } - - override func setValue(data: [Double]) { - let value: Double = data.first! - - self.valueLabel.stringValue = "\(Int(Float(value.roundTo(decimalPlaces: 2))! * 100))%" - self.valueLabel.textColor = value.usageColor(color: self.color) - - if self.points.count < 50 { - self.points.append(value) - return - } - - for (i, _) in self.points.enumerated() { - if i+1 < self.points.count { - self.points[i] = self.points[i+1] - } else { - self.points[i] = value - } - } - - self.redraw() - } - - func drawValue () { - for subview in self.subviews { - subview.removeFromSuperview() - } - - valueLabel = NSTextField(frame: NSMakeRect(2, widgetSize.height - 11, self.frame.size.width, 10)) - if label { - valueLabel = NSTextField(frame: NSMakeRect(labelPadding + 2, widgetSize.height - 11, self.frame.size.width, 10)) - } - valueLabel.textColor = NSColor.red - valueLabel.isEditable = false - valueLabel.isSelectable = false - valueLabel.isBezeled = false - valueLabel.wantsLayer = true - valueLabel.textColor = .labelColor - valueLabel.backgroundColor = .controlColor - valueLabel.canDrawSubviewsIntoLayer = true - valueLabel.alignment = .natural - valueLabel.font = NSFont.systemFont(ofSize: 8, weight: .light) - valueLabel.stringValue = "" - valueLabel.addSubview(NSView()) - - self.height = 7.0 - self.addSubview(valueLabel) - } - - @objc override func toggleLabel(_ sender: NSMenuItem) { - sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on - self.defaults.set(sender.state == NSControl.StateValue.on, forKey: "\(self.name)_label") - self.label = (sender.state == NSControl.StateValue.on) - - var width = self.size - if self.label { - width = width + labelPadding - } - self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: width, height: self.frame.size.height) - self.drawValue() - menuBar!.refresh() - } - - @objc func toggleColor(_ 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)_color") - self.color = sender.state == NSControl.StateValue.on - } -} diff --git a/Stats/Widgets/Mini.swift b/Stats/Widgets/Mini.swift deleted file mode 100644 index 99ead121..00000000 --- a/Stats/Widgets/Mini.swift +++ /dev/null @@ -1,106 +0,0 @@ -// -// Mini.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 14.06.2019. -// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa - -class Mini: NSView, Widget { - public var name: String = "Mini" - public var menus: [NSMenuItem] = [] - - private var value: Double = 0 - private var size: CGFloat = widgetSize.width - private var valueView: NSTextField = NSTextField() - private var labelView: NSTextField = NSTextField() - private let defaults = UserDefaults.standard - - private var color: Bool = false - - override var intrinsicContentSize: CGSize { - return CGSize(width: self.frame.size.width, height: self.frame.size.height) - } - - override init(frame: NSRect) { - super.init(frame: CGRect(x: 0, y: 0, width: self.size, height: widgetSize.height)) - self.wantsLayer = true - - let xOffset: CGFloat = 1.0 - - let labelView = NSTextField(frame: NSMakeRect(xOffset, 13, self.frame.size.width, 9)) - labelView.isEditable = false - labelView.isSelectable = false - labelView.isBezeled = false - labelView.wantsLayer = true - labelView.textColor = .labelColor - labelView.backgroundColor = .controlColor - labelView.canDrawSubviewsIntoLayer = true - labelView.alignment = .natural - labelView.font = NSFont.systemFont(ofSize: 8, weight: .light) - labelView.stringValue = String(self.name.prefix(3)).uppercased() - labelView.addSubview(NSView()) - - let valueView = NSTextField(frame: NSMakeRect(xOffset, 3, self.frame.size.width, 10)) - valueView.isEditable = false - valueView.isSelectable = false - valueView.isBezeled = false - valueView.wantsLayer = true - valueView.textColor = .labelColor - valueView.backgroundColor = .controlColor - valueView.canDrawSubviewsIntoLayer = true - valueView.alignment = .natural - valueView.font = NSFont.systemFont(ofSize: 10, weight: .regular) - valueView.stringValue = "" - valueView.addSubview(NSView()) - - self.labelView = labelView - self.valueView = valueView - - self.addSubview(self.labelView) - self.addSubview(self.valueView) - } - - required init?(coder decoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func start() { - self.color = defaults.object(forKey: "\(name)_color") != nil ? defaults.bool(forKey: "\(name)_color") : false - self.labelView.stringValue = String(self.name.prefix(3)).uppercased() - self.initMenu() - self.redraw() - } - - func initMenu() { - let color = NSMenuItem(title: "Color", action: #selector(toggleColor), keyEquivalent: "") - color.state = self.color ? NSControl.StateValue.on : NSControl.StateValue.off - color.target = self - - self.menus.append(color) - } - - func redraw() { - self.valueView.textColor = self.value.usageColor(color: self.color) - self.display() - } - - func setValue(data: [Double]) { - let value: Double = data.first! - if self.value != value && !value.isNaN { - self.value = value - - self.valueView.stringValue = "\(Int(Float(value.roundTo(decimalPlaces: 2))! * 100))%" - self.valueView.textColor = value.usageColor(color: self.color) - } - } - - @objc func toggleColor(_ 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)_color") - self.color = sender.state == NSControl.StateValue.on - self.redraw() - } -} diff --git a/Stats/Widgets/Network/NetworkArrows.swift b/Stats/Widgets/Network/NetworkArrows.swift deleted file mode 100644 index e6da08d2..00000000 --- a/Stats/Widgets/Network/NetworkArrows.swift +++ /dev/null @@ -1,93 +0,0 @@ -// -// NetworkArrows.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 14.07.2019. -// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa - -class NetworkArrowsView: NSView, Widget { - public var menus: [NSMenuItem] = [] - public var size: CGFloat = 8 - public var name: String = "NetworkArrows" - - private var download: Int64 = 0 - private var upload: Int64 = 0 - - override var intrinsicContentSize: CGSize { - return CGSize(width: self.frame.size.width, height: self.frame.size.height) - } - - override init(frame: NSRect) { - self.download = 0 - self.upload = 0 - super.init(frame: CGRect(x: 0, y: 0, width: self.size, height: widgetSize.height)) - self.wantsLayer = true - self.addSubview(NSView()) - } - - required init?(coder decoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func start() {} - - override func draw(_ dirtyRect: NSRect) { - super.draw(dirtyRect) - - let arrowAngle = CGFloat(Double.pi / 5) - let pointerLineLength: CGFloat = 3.5 - let workingHeight: CGFloat = (self.frame.size.height - (widgetSize.margin * 2)) - let height: CGFloat = ((workingHeight - widgetSize.margin) / 2) - - let downloadArrow = NSBezierPath() - let downloadStart = CGPoint(x: widgetSize.margin + (pointerLineLength/2), y: height + widgetSize.margin) - let downloadEnd = CGPoint(x: widgetSize.margin + (pointerLineLength/2), y: widgetSize.margin) - - downloadArrow.addArrow(start: downloadStart, end: downloadEnd, pointerLineLength: pointerLineLength, arrowAngle: arrowAngle) - - if self.download >= 1_024 { - NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 0.8).set() - } else { - NSColor.labelColor.set() - } - downloadArrow.lineWidth = 1 - downloadArrow.stroke() - downloadArrow.close() - - let uploadArrow = NSBezierPath() - let uploadStart = CGPoint(x: widgetSize.margin + (pointerLineLength/2), y: height + (widgetSize.margin * 2)) - let uploadEnd = CGPoint(x: widgetSize.margin + (pointerLineLength/2), y: (widgetSize.margin * 2) + (height * 2)) - - uploadArrow.addArrow(start: uploadStart, end: uploadEnd, pointerLineLength: pointerLineLength, arrowAngle: arrowAngle) - - if self.upload >= 1_024 { - NSColor.red.set() - } else { - NSColor.labelColor.set() - } - uploadArrow.lineWidth = 1 - uploadArrow.stroke() - uploadArrow.close() - } - - func redraw() { - self.display() - } - - func setValue(data: [Double]) { - let download: Int64 = Int64(data[0]) - let upload: Int64 = Int64(data[1]) - - if self.download != download { - self.download = download - } - if self.upload != upload { - self.upload = upload - } - - self.redraw() - } -} diff --git a/Stats/Widgets/Network/NetworkArrowsText.swift b/Stats/Widgets/Network/NetworkArrowsText.swift deleted file mode 100644 index 5f21406c..00000000 --- a/Stats/Widgets/Network/NetworkArrowsText.swift +++ /dev/null @@ -1,127 +0,0 @@ -// -// NetworkArrowsText.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 14.07.2019. -// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa - -class NetworkArrowsTextView: NSView, Widget { - public var menus: [NSMenuItem] = [] - public var size: CGFloat = widgetSize.width + 24 - public var name: String = "NetworkArrowsText" - - private var download: Int64 = 0 - private var upload: Int64 = 0 - - var downloadValue: NSTextField = NSTextField() - var uploadValue: NSTextField = NSTextField() - - override var intrinsicContentSize: CGSize { - return CGSize(width: self.frame.size.width, height: self.frame.size.height) - } - - override init(frame: NSRect) { - self.download = 0 - self.upload = 0 - super.init(frame: CGRect(x: 0, y: 0, width: self.size, height: widgetSize.height)) - self.wantsLayer = true - self.valueView() - } - - required init?(coder decoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func start() {} - - override func draw(_ dirtyRect: NSRect) { - super.draw(dirtyRect) - - let arrowAngle = CGFloat(Double.pi / 5) - let pointerLineLength: CGFloat = 3.5 - let workingHeight: CGFloat = (self.frame.size.height - (widgetSize.margin * 2)) - let height: CGFloat = ((workingHeight - widgetSize.margin) / 2) - - let downloadArrow = NSBezierPath() - let downloadStart = CGPoint(x: widgetSize.margin + (pointerLineLength/2), y: height + widgetSize.margin) - let downloadEnd = CGPoint(x: widgetSize.margin + (pointerLineLength/2), y: widgetSize.margin) - - downloadArrow.addArrow(start: downloadStart, end: downloadEnd, pointerLineLength: pointerLineLength, arrowAngle: arrowAngle) - - if self.download >= 1_024 { - NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 0.8).set() - } else { - NSColor.labelColor.set() - } - downloadArrow.lineWidth = 1 - downloadArrow.stroke() - downloadArrow.close() - - let uploadArrow = NSBezierPath() - let uploadStart = CGPoint(x: widgetSize.margin + (pointerLineLength/2), y: height + (widgetSize.margin * 2)) - let uploadEnd = CGPoint(x: widgetSize.margin + (pointerLineLength/2), y: (widgetSize.margin * 2) + (height * 2)) - - uploadArrow.addArrow(start: uploadStart, end: uploadEnd, pointerLineLength: pointerLineLength, arrowAngle: arrowAngle) - - if self.upload >= 1_024 { - NSColor.red.set() - } else { - NSColor.labelColor.set() - } - uploadArrow.lineWidth = 1 - uploadArrow.stroke() - uploadArrow.close() - } - - func redraw() { - self.display() - } - - func setValue(data: [Double]) { - let download: Int64 = Int64(data[0]) - let upload: Int64 = Int64(data[1]) - - if self.download != download { - self.download = download - downloadValue.stringValue = Units(bytes: self.download).getReadableSpeed() - } - if self.upload != upload { - self.upload = upload - uploadValue.stringValue = Units(bytes: self.upload).getReadableSpeed() - } - - self.redraw() - } - - func valueView() { - downloadValue = NSTextField(frame: NSMakeRect(widgetSize.margin, widgetSize.margin + 1, self.frame.size.width - widgetSize.margin, 9)) - downloadValue.isEditable = false - downloadValue.isSelectable = false - downloadValue.isBezeled = false - downloadValue.wantsLayer = true - downloadValue.textColor = .labelColor - downloadValue.backgroundColor = .controlColor - downloadValue.canDrawSubviewsIntoLayer = true - downloadValue.alignment = .right - downloadValue.font = NSFont.systemFont(ofSize: 9, weight: .light) - downloadValue.stringValue = "0 KB/s" - - uploadValue = NSTextField(frame: NSMakeRect(widgetSize.margin, self.frame.size.height - 10, self.frame.size.width - widgetSize.margin, 9)) - uploadValue.isEditable = false - uploadValue.isSelectable = false - uploadValue.isBezeled = false - uploadValue.wantsLayer = true - uploadValue.textColor = .labelColor - uploadValue.backgroundColor = .controlColor - uploadValue.canDrawSubviewsIntoLayer = true - uploadValue.alignment = .right - uploadValue.font = NSFont.systemFont(ofSize: 9, weight: .light) - uploadValue.stringValue = "0 KB/s" - - self.addSubview(downloadValue) - self.addSubview(uploadValue) - } -} diff --git a/Stats/Widgets/Network/NetworkDots.swift b/Stats/Widgets/Network/NetworkDots.swift deleted file mode 100644 index 10d5c61b..00000000 --- a/Stats/Widgets/Network/NetworkDots.swift +++ /dev/null @@ -1,79 +0,0 @@ -// -// NetworkDots.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 14.07.2019. -// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa - -class NetworkDotsView: NSView, Widget { - public var size: CGFloat = 12 - public var name: String = "NetworkDots" - public var menus: [NSMenuItem] = [] - - private var download: Int64 = 0 - private var upload: Int64 = 0 - - override var intrinsicContentSize: CGSize { - return CGSize(width: self.frame.size.width, height: self.frame.size.height) - } - - override init(frame: NSRect) { - self.download = 0 - self.upload = 0 - super.init(frame: CGRect(x: 0, y: 0, width: self.size, height: widgetSize.height)) - self.wantsLayer = true - self.addSubview(NSView()) - } - - required init?(coder decoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func start() {} - - override func draw(_ dirtyRect: NSRect) { - super.draw(dirtyRect) - - let workingHeight: CGFloat = (self.frame.size.height - (widgetSize.margin * 2)) - let height: CGFloat = ((workingHeight - widgetSize.margin) / 2) - 1 - - var uploadCircle = NSBezierPath() - uploadCircle = NSBezierPath(ovalIn: CGRect(x: widgetSize.margin, y: height + (widgetSize.margin * 2) + 1, width: height, height: height)) - if self.upload >= 1_024 { - NSColor.red.setFill() - } else { - NSColor.labelColor.setFill() - } - uploadCircle.fill() - - var downloadCircle = NSBezierPath() - downloadCircle = NSBezierPath(ovalIn: CGRect(x: widgetSize.margin, y: widgetSize.margin, width: height, height: height)) - if self.download >= 1_024 { - NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 0.8).setFill() - } else { - NSColor.labelColor.setFill() - } - downloadCircle.fill() - } - - func redraw() { - self.display() - } - - func setValue(data: [Double]) { - let download: Int64 = Int64(data[0]) - let upload: Int64 = Int64(data[1]) - - if self.download != download { - self.download = download - } - if self.upload != upload { - self.upload = upload - } - - self.redraw() - } -} diff --git a/Stats/Widgets/Network/NetworkDotsText.swift b/Stats/Widgets/Network/NetworkDotsText.swift deleted file mode 100644 index 9982a0af..00000000 --- a/Stats/Widgets/Network/NetworkDotsText.swift +++ /dev/null @@ -1,118 +0,0 @@ -// -// NetworkDotsText.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 14.07.2019. -// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa - -class NetworkDotsTextView: NSView, Widget { - public var menus: [NSMenuItem] = [] - public var size: CGFloat = widgetSize.width + 26 - public var name: String = "NetworkDotsText" - - private var download: Int64 = 0 - private var upload: Int64 = 0 - - var downloadValue: NSTextField = NSTextField() - var uploadValue: NSTextField = NSTextField() - - override var intrinsicContentSize: CGSize { - return CGSize(width: self.frame.size.width, height: self.frame.size.height) - } - - override init(frame: NSRect) { - self.download = 0 - self.upload = 0 - super.init(frame: CGRect(x: 0, y: 0, width: self.size, height: widgetSize.height)) - self.wantsLayer = true - self.valueView() - } - - required init?(coder decoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func start() {} - - override func draw(_ dirtyRect: NSRect) { - super.draw(dirtyRect) - - let workingHeight: CGFloat = (self.frame.size.height - (widgetSize.margin * 2)) - let height: CGFloat = ((workingHeight - widgetSize.margin) / 2) - 1 - - var uploadCircle = NSBezierPath() - uploadCircle = NSBezierPath(ovalIn: CGRect(x: widgetSize.margin, y: height + (widgetSize.margin * 2) + 1, width: height, height: height)) - if self.upload >= 1_024 { - NSColor.red.setFill() - } else { - NSColor.labelColor.setFill() - } - uploadCircle.fill() - - var downloadCircle = NSBezierPath() - downloadCircle = NSBezierPath(ovalIn: CGRect(x: widgetSize.margin, y: widgetSize.margin, width: height, height: height)) - if self.download >= 1_024 { - NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 0.8).setFill() - } else { - NSColor.labelColor.setFill() - } - downloadCircle.fill() - } - - func redraw() { - self.display() - } - - func setValue(data: [Double]) { - let download: Int64 = Int64(data[0]) - let upload: Int64 = Int64(data[1]) - var changed: Bool = false - - if self.download != download { - self.download = download - downloadValue.stringValue = Units(bytes: self.download).getReadableSpeed() - changed = true - } - if self.upload != upload { - self.upload = upload - uploadValue.stringValue = Units(bytes: self.upload).getReadableSpeed() - changed = true - } - - if changed { - self.redraw() - } - } - - func valueView() { - downloadValue = NSTextField(frame: NSMakeRect(widgetSize.margin, widgetSize.margin + 1, self.frame.size.width - widgetSize.margin, 9)) - downloadValue.isEditable = false - downloadValue.isSelectable = false - downloadValue.isBezeled = false - downloadValue.wantsLayer = true - downloadValue.textColor = .labelColor - downloadValue.backgroundColor = .controlColor - downloadValue.canDrawSubviewsIntoLayer = true - downloadValue.alignment = .right - downloadValue.font = NSFont.systemFont(ofSize: 9, weight: .light) - downloadValue.stringValue = Units(bytes: self.download).getReadableSpeed() - - uploadValue = NSTextField(frame: NSMakeRect(widgetSize.margin, self.frame.size.height - 10, self.frame.size.width - widgetSize.margin, 9)) - uploadValue.isEditable = false - uploadValue.isSelectable = false - uploadValue.isBezeled = false - uploadValue.wantsLayer = true - uploadValue.textColor = .labelColor - uploadValue.backgroundColor = .controlColor - uploadValue.canDrawSubviewsIntoLayer = true - uploadValue.alignment = .right - uploadValue.font = NSFont.systemFont(ofSize: 9, weight: .light) - uploadValue.stringValue = Units(bytes: self.upload).getReadableSpeed() - - self.addSubview(downloadValue) - self.addSubview(uploadValue) - } -} diff --git a/Stats/Widgets/Network/NetworkText.swift b/Stats/Widgets/Network/NetworkText.swift deleted file mode 100644 index 56a4e0b4..00000000 --- a/Stats/Widgets/Network/NetworkText.swift +++ /dev/null @@ -1,81 +0,0 @@ -// -// NetworkText.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 14.07.2019. -// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa - -class NetworkTextView: NSView, Widget { - public var menus: [NSMenuItem] = [] - public var size: CGFloat = widgetSize.width + 20 - public var name: String = "NetworkText" - - private var downloadValue: NSTextField = NSTextField() - private var uploadValue: NSTextField = NSTextField() - - override var intrinsicContentSize: CGSize { - return CGSize(width: self.frame.size.width, height: self.frame.size.height) - } - - override init(frame: NSRect) { - super.init(frame: CGRect(x: 0, y: 0, width: self.size, height: widgetSize.height)) - self.wantsLayer = true - self.valueView() - } - - required init?(coder decoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func start() {} - - override func draw(_ dirtyRect: NSRect) { - super.draw(dirtyRect) - } - - func redraw() { - self.display() - } - - func setValue(data: [Double]) { - let download: Int64 = Int64(data[0]) - let upload: Int64 = Int64(data[1]) - - downloadValue.stringValue = Units(bytes: download).getReadableSpeed() - uploadValue.stringValue = Units(bytes: upload).getReadableSpeed() - - self.redraw() - } - - func valueView() { - downloadValue = NSTextField(frame: NSMakeRect(widgetSize.margin, widgetSize.margin + 1, self.frame.size.width - widgetSize.margin, 9)) - downloadValue.isEditable = false - downloadValue.isSelectable = false - downloadValue.isBezeled = false - downloadValue.wantsLayer = true - downloadValue.textColor = .labelColor - downloadValue.backgroundColor = .controlColor - downloadValue.canDrawSubviewsIntoLayer = true - downloadValue.alignment = .right - downloadValue.font = NSFont.systemFont(ofSize: 9, weight: .light) - downloadValue.stringValue = "0 KB/s" - - uploadValue = NSTextField(frame: NSMakeRect(widgetSize.margin, self.frame.size.height - 10, self.frame.size.width - widgetSize.margin, 9)) - uploadValue.isEditable = false - uploadValue.isSelectable = false - uploadValue.isBezeled = false - uploadValue.wantsLayer = true - uploadValue.textColor = .labelColor - uploadValue.backgroundColor = .controlColor - uploadValue.canDrawSubviewsIntoLayer = true - uploadValue.alignment = .right - uploadValue.font = NSFont.systemFont(ofSize: 9, weight: .light) - uploadValue.stringValue = "0 KB/s" - - self.addSubview(downloadValue) - self.addSubview(uploadValue) - } -} diff --git a/Stats/Widgets/Sensors/SensorsWidget.swift b/Stats/Widgets/Sensors/SensorsWidget.swift deleted file mode 100644 index 9fc6bc48..00000000 --- a/Stats/Widgets/Sensors/SensorsWidget.swift +++ /dev/null @@ -1,169 +0,0 @@ -// -// Sensors.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 03/04/2020. -// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa -import Foundation - -class SensorsWidget: NSView, Widget { - public var name: String = "Sensors" - public var menus: [NSMenuItem] = [] - - private var value: [Double] = [] - private var size: CGFloat = 24 - private var smallSize: CGFloat = 24 - private var bigSize: CGFloat = 36 - private var topValueView: NSTextField = NSTextField() - private var bottomValueView: NSTextField = NSTextField() - private let defaults = UserDefaults.standard - - private var color: Bool = true - - override var intrinsicContentSize: CGSize { - return CGSize(width: self.frame.size.width, height: self.frame.size.height) - } - - required init?(coder decoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override init(frame: NSRect) { - super.init(frame: CGRect(x: 0, y: 0, width: self.size, height: widgetSize.height)) - self.wantsLayer = true - - let xOffset: CGFloat = 1.0 - - let topValueView = NSTextField(frame: NSMakeRect(xOffset, 11, self.frame.size.width, 10)) - topValueView.isEditable = false - topValueView.isSelectable = false - topValueView.isBezeled = false - topValueView.wantsLayer = true - topValueView.textColor = .labelColor - topValueView.backgroundColor = .controlColor - topValueView.canDrawSubviewsIntoLayer = true - topValueView.alignment = .natural - topValueView.font = NSFont.systemFont(ofSize: 9, weight: .light) - topValueView.stringValue = "" - topValueView.addSubview(NSView()) - - let bottomValueView = NSTextField(frame: NSMakeRect(xOffset, 2, self.frame.size.width, 10)) - bottomValueView.isEditable = false - bottomValueView.isSelectable = false - bottomValueView.isBezeled = false - bottomValueView.wantsLayer = true - bottomValueView.textColor = .labelColor - bottomValueView.backgroundColor = .controlColor - bottomValueView.canDrawSubviewsIntoLayer = true - bottomValueView.alignment = .natural - bottomValueView.font = NSFont.systemFont(ofSize: 9, weight: .light) - bottomValueView.stringValue = "" - bottomValueView.addSubview(NSView()) - - self.topValueView = topValueView - self.bottomValueView = bottomValueView - - self.addSubview(self.topValueView) - self.addSubview(self.bottomValueView) - } - - func start() { - self.color = defaults.object(forKey: "\(name)_color") != nil ? defaults.bool(forKey: "\(name)_color") : true - self.initMenu() - self.redraw() - } - - func redraw() { - if self.value.count == 2 { - self.topValueView.textColor = self.value[0].temperatureColor(color: self.color) - self.bottomValueView.textColor = self.value[1].temperatureColor(color: self.color) - } - self.display() - } - - func setValue(data: [Double]) { - if self.value != data && data.count == 4 { - var width = self.smallSize - let unit_1: String = String(UnicodeScalar(Int(data[1]))!) - let unit_2: String = String(UnicodeScalar(Int(data[3]))!) - self.value = [data[0], data[2]] - - if data[1] == 176 { - self.topValueView.stringValue = "\(Int(self.value[0]))\(unit_1)" - } else { - width = bigSize - switch value[0] { - case 0..<10: - self.topValueView.stringValue = "\(self.value[0].rounded(toPlaces: 2))\(unit_1)" - break - case 10..<100: - self.topValueView.stringValue = "\(self.value[0].rounded(toPlaces: 1))\(unit_1)" - break - default: - self.topValueView.stringValue = "\(Int(self.value[0]))\(unit_1)" - break - } - } - - if data[3] == 176 { - self.bottomValueView.stringValue = "\(Int(self.value[1]))\(unit_2)" - } else { - width = self.bigSize - switch value[1] { - case 0..<10: - self.bottomValueView.stringValue = "\(self.value[1].rounded(toPlaces: 2))\(unit_2)" - break - case 10..<100: - self.bottomValueView.stringValue = "\(self.value[1].rounded(toPlaces: 1))\(unit_2)" - break - default: - self.bottomValueView.stringValue = "\(Int(self.value[1]))\(unit_2)" - break - } - } - - if self.size != width { - setWidth(width) - } - - self.topValueView.textColor = self.value[0].temperatureColor(color: self.color) - self.bottomValueView.textColor = self.value[1].temperatureColor(color: self.color) - } - } - - func initMenu() { - let color = NSMenuItem(title: "Color", action: #selector(toggleColor), keyEquivalent: "") - color.state = self.color ? NSControl.StateValue.on : NSControl.StateValue.off - color.target = self - - self.menus.append(color) - } - - @objc func toggleColor(_ 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)_color") - self.color = sender.state == NSControl.StateValue.on - - if self.value.count == 2 { - self.topValueView.textColor = self.value[0].temperatureColor(color: self.color) - self.bottomValueView.textColor = self.value[1].temperatureColor(color: self.color) - } - - self.redraw() - } - - private func setWidth(_ width: CGFloat) { - self.size = width - - self.topValueView.frame.size.width = width - self.bottomValueView.frame.size.width = width - - self.frame.size.width = self.size - if menuBar != nil { - menuBar!.refresh() - } - } -} diff --git a/Stats/Widgets/Widget.swift b/Stats/Widgets/Widget.swift deleted file mode 100644 index 8a88dfef..00000000 --- a/Stats/Widgets/Widget.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// Widget.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 08.07.2019. -// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa - -protocol Widget { - var name: String { get set } // module name - var menus: [NSMenuItem] { get } // module settings - - var intrinsicContentSize: CGSize { get } - - func start() - func redraw() - - func setValue(data: [Double]) // pass value to widget -} - -typealias WidgetType = Float -struct Widgets { - static let Mini: WidgetType = 0.0 - static let Sensors: WidgetType = 0.1 - - static let Chart: WidgetType = 1.0 - static let ChartWithValue: WidgetType = 1.1 - - static let NetworkDots: WidgetType = 2.0 - static let NetworkArrows: WidgetType = 2.1 - static let NetworkText: WidgetType = 2.2 - static let NetworkDotsWithText: WidgetType = 2.3 - static let NetworkArrowsWithText: WidgetType = 2.4 - static let NetworkChart: WidgetType = 2.5 - - static let BarChart: WidgetType = 3.0 - - static let Battery: WidgetType = 4.0 - static let BatteryPercentage: WidgetType = 4.1 - static let BatteryTime: WidgetType = 4.2 -} - -struct WidgetSize { - let width: CGFloat = 32 - var height: CGFloat { - get { - let systemHeight = NSApplication.shared.mainMenu?.menuBarHeight - return (systemHeight == 0 ? 22 : systemHeight)! - } - } - let margin: CGFloat = 2 -} -let widgetSize = WidgetSize() diff --git a/Stats/libs/ChartMarker.swift b/Stats/libs/ChartMarker.swift deleted file mode 100644 index c990eca4..00000000 --- a/Stats/libs/ChartMarker.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// ChartMarker.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 03/09/2019. -// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa -import Charts - -class ChartMarker: MarkerView { - var text = "" - - override func refreshContent(entry: ChartDataEntry, highlight: Highlight) { - super.refreshContent(entry: entry, highlight: highlight) - text = String(entry.y) - } - - override func draw(context: CGContext, point: CGPoint) { - super.draw(context: context, point: point) - - var drawAttributes = [NSAttributedString.Key : Any]() - drawAttributes[.font] = NSFont.systemFont(ofSize: 13) - drawAttributes[.foregroundColor] = NSColor.white - drawAttributes[.backgroundColor] = NSColor.darkGray - - self.bounds.size = ("\(text)" as NSString).size(withAttributes: drawAttributes) - self.offset = CGPoint(x: 0, y: self.bounds.size.height) - - let offset = self.offsetForDrawing(atPoint: point) - drawText(text: "\(text)" as NSString, rect: CGRect(origin: CGPoint(x: point.x + offset.x, y: point.y + offset.y), size: self.bounds.size), withAttributes: drawAttributes) - } - - func drawText(text: NSString, rect: CGRect, withAttributes attributes: [NSAttributedString.Key : Any]? = nil) { - let size = text.size(withAttributes: attributes) - let centeredRect = CGRect(x: rect.origin.x + (rect.size.width - size.width) / 2.0, y: rect.origin.y + (rect.size.height - size.height) / 2.0, width: size.width, height: size.height) - text.draw(in: centeredRect, withAttributes: attributes) - } -} - -class ChartNetworkMarker: ChartMarker { - override func refreshContent(entry: ChartDataEntry, highlight: Highlight) { - super.refreshContent(entry: entry, highlight: highlight) - text = Units(bytes: Int64(entry.y)).getReadableSpeed() - } -} diff --git a/Stats/libs/Extensions.swift b/Stats/libs/Extensions.swift deleted file mode 100755 index cc7953f5..00000000 --- a/Stats/libs/Extensions.swift +++ /dev/null @@ -1,340 +0,0 @@ -// -// Extensions.swift -// Stats -// -// Created by Serhiy Mytrovtsiy on 29/05/2019. -// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved. -// - -import Cocoa - -public enum Unit : Float { - case byte = 1 - case kilobyte = 1024 - case megabyte = 1048576 - case gigabyte = 1073741824 -} - -public struct Units { - public let bytes: Int64 - - public init(bytes: Int64) { - self.bytes = bytes - } - - public var kilobytes: Double { - return Double(bytes) / 1_024 - } - public var megabytes: Double { - return kilobytes / 1_024 - } - public var gigabytes: Double { - return megabytes / 1_024 - } - - public func getReadableTuple() -> (Double, String) { - switch bytes { - case 0..<1_024: - return (0, "KB/s") - case 1_024..<(1_024 * 1_024): - return (Double(String(format: "%.2f", kilobytes))!, "KB/s") - case 1_024..<(1_024 * 1_024 * 1_024): - return (Double(String(format: "%.2f", megabytes))!, "MB/s") - case (1_024 * 1_024 * 1_024)...Int64.max: - return (Double(String(format: "%.2f", gigabytes))!, "GB/s") - default: - return (Double(String(format: "%.2f", kilobytes))!, "KB/s") - } - } - - public func getReadableSpeed() -> String { - switch bytes { - case 0..<1_024: - return "0 KB/s" - case 1_024..<(1_024 * 1_024): - return String(format: "%.0f KB/s", kilobytes) - case 1_024..<(1_024 * 1_024 * 100): - return String(format: "%.1f MB/s", megabytes) - case (1_024 * 1_024 * 100)..<(1_024 * 1_024 * 1_024): - return String(format: "%.0f MB/s", megabytes) - case (1_024 * 1_024 * 1_024)...Int64.max: - return String(format: "%.1f GB/s", gigabytes) - default: - return String(format: "%.0f KB/s", kilobytes) - } - } - - public func getReadableMemory() -> String { - switch bytes { - case 0..<1_024: - return "0 KB/s" - case 1_024..<(1_024 * 1_024): - return String(format: "%.0f KB", kilobytes) - case 1_024..<(1_024 * 1_024 * 1_024): - return String(format: "%.0f MB", megabytes) - case (1_024 * 1_024 * 1_024)...Int64.max: - return String(format: "%.2f GB", gigabytes) - default: - return String(format: "%.0f KB", kilobytes) - } - } -} - -extension String { - func condenseWhitespace() -> String { - let components = self.components(separatedBy: .whitespacesAndNewlines) - return components.filter { !$0.isEmpty }.joined(separator: " ") - } - - var UTF8CString: UnsafeMutablePointer { - return UnsafeMutablePointer(mutating: (self as NSString).utf8String!) - } - - mutating func findAndCrop(pattern: String) -> String { - let regex = try! NSRegularExpression(pattern: pattern) - let stringRange = NSRange(location: 0, length: self.utf16.count) - var line = self - - if let searchRange = regex.firstMatch(in: self, options: [], range: stringRange) { - let start = self.index(self.startIndex, offsetBy: searchRange.range.lowerBound) - let end = self.index(self.startIndex, offsetBy: searchRange.range.upperBound) - let value = String(self[start.. Bool { - return self.range(of: regex, options: .regularExpression, range: nil, locale: nil) != nil - } - - func toUpperCase() -> String { - return prefix(1).capitalized + dropFirst() - } - func toLowwerCase() -> String { - return prefix(1).lowercased() + dropFirst() - } - - subscript(offset: Int) -> Character { self[index(startIndex, offsetBy: offset)] } -} - -extension Double { - func roundTo(decimalPlaces: Int) -> String { - return NSString(format: "%.\(decimalPlaces)f" as NSString, self) as String - } - - func rounded(toPlaces places:Int) -> Double { - let divisor = pow(10.0, Double(places)) - return (self * divisor).rounded() / divisor - } - - func usageColor(reversed: Bool = false, color: Bool = false) -> NSColor { - if !color { - return NSColor.textColor - } - - if reversed { - switch self { - case 0.6...0.8: - return NSColor.systemOrange - case 0.8...1: - return NSColor.systemGreen - default: - return NSColor.systemRed - } - } else { - switch self { - case 0.6...0.8: - return NSColor.systemOrange - case 0.8...1: - return NSColor.systemRed - default: - return NSColor.systemGreen - } - } - } - - func batteryColor(color: Bool = false) -> NSColor { - switch self { - case 0.2...0.4: - if !color { - return NSColor.controlTextColor - } - return NSColor.systemOrange - case 0.4...1: - if self == 1 { - return NSColor.controlTextColor - } - if !color { - return NSColor.controlTextColor - } - return NSColor.systemGreen - default: - return NSColor.systemRed - } - } - - func temperatureColor(color: Bool = false) -> NSColor { - switch self { - case 0...70: - return NSColor.controlTextColor - case 70...90: - if !color { - return NSColor.controlTextColor - } - return NSColor.systemOrange - default: - if !color { - return NSColor.controlTextColor - } - return NSColor.systemRed - } - } - - func splitAtDecimal() -> [Int64] { - return "\(self)".split(separator: ".").map{Int64($0)!} - } - - func secondsToHoursMinutesSeconds () -> (Int?, Int?, Int?) { - let hrs = self / 3600 - let mins = (self.truncatingRemainder(dividingBy: 3600)) / 60 - let seconds = (self.truncatingRemainder(dividingBy:3600)).truncatingRemainder(dividingBy:60) - return (Int(hrs) > 0 ? Int(hrs) : nil , Int(mins) > 0 ? Int(mins) : nil, Int(seconds) > 0 ? Int(seconds) : nil) - } - - func printSecondsToHoursMinutesSeconds () -> String { - let time = self.secondsToHoursMinutesSeconds() - - switch time { - case (nil, let x? , let y?): - return "\(x) min \(y) sec" - case (nil, let x?, nil): - return "\(x) min" - case (let x?, nil, nil): - return "\(x) h" - case (nil, nil, let x?): - return "\(x) sec" - case (let x?, nil, let z?): - return "\(x) h \(z) sec" - case (let x?, let y?, nil): - return "\(x) h \(y) min" - case (let x?, let y?, let z?): - return "\(x) h \(y) min \(z) sec" - default: - return "n/a" - } - } -} - -extension NSBezierPath { - func addArrow(start: CGPoint, end: CGPoint, pointerLineLength: CGFloat, arrowAngle: CGFloat) { - self.move(to: start) - self.line(to: end) - - let startEndAngle = atan((end.y - start.y) / (end.x - start.x)) + ((end.x - start.x) < 0 ? CGFloat(Double.pi) : 0) - let arrowLine1 = CGPoint(x: end.x + pointerLineLength * cos(CGFloat(Double.pi) - startEndAngle + arrowAngle), y: end.y - pointerLineLength * sin(CGFloat(Double.pi) - startEndAngle + arrowAngle)) - let arrowLine2 = CGPoint(x: end.x + pointerLineLength * cos(CGFloat(Double.pi) - startEndAngle - arrowAngle), y: end.y - pointerLineLength * sin(CGFloat(Double.pi) - startEndAngle - arrowAngle)) - - self.line(to: arrowLine1) - self.move(to: end) - self.line(to: arrowLine2) - } -} - -extension NSColor { - convenience init(hexString: String, alpha: CGFloat = 1.0) { - let hexString: String = hexString.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) - let scanner = Scanner(string: hexString) - if (hexString.hasPrefix("#")) { - scanner.scanLocation = 1 - } - var color: UInt32 = 0 - scanner.scanHexInt32(&color) - let mask = 0x000000FF - let r = Int(color >> 16) & mask - let g = Int(color >> 8) & mask - let b = Int(color) & mask - let red = CGFloat(r) / 255.0 - let green = CGFloat(g) / 255.0 - let blue = CGFloat(b) / 255.0 - self.init(red:red, green:green, blue:blue, alpha:alpha) - } - - func toHexString() -> String { - var r:CGFloat = 0 - var g:CGFloat = 0 - var b:CGFloat = 0 - var a:CGFloat = 0 - getRed(&r, green: &g, blue: &b, alpha: &a) - let rgb:Int = (Int)(r*255)<<16 | (Int)(g*255)<<8 | (Int)(b*255)<<0 - return String(format:"#%06x", rgb) - } -} - -extension URL { - func checkFileExist() -> Bool { - return FileManager.default.fileExists(atPath: self.path) - } -} - -extension FourCharCode { - init(fromString str: String) { - precondition(str.count == 4) - - self = str.utf8.reduce(0) { sum, character in - return sum << 8 | UInt32(character) - } - } - - func toString() -> String { - return String(describing: UnicodeScalar(self >> 24 & 0xff)!) + - String(describing: UnicodeScalar(self >> 16 & 0xff)!) + - String(describing: UnicodeScalar(self >> 8 & 0xff)!) + - String(describing: UnicodeScalar(self & 0xff)!) - } -} - -extension UInt32 { - init(bytes: (UInt8, UInt8, UInt8, UInt8)) { - self = UInt32(bytes.0) << 24 | UInt32(bytes.1) << 16 | UInt32(bytes.2) << 8 | UInt32(bytes.3) - } -} - -extension FloatingPoint { - init?(_ bytes: [UInt8]) { - self = bytes.withUnsafeBytes { - return $0.load(fromByteOffset: 0, as: Self.self) - } - } -} - -extension NSMenuItem { - private static var _extraString = [String:String]() - - var extraString: String { - get { - let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self)) - return NSMenuItem._extraString[tmpAddress] ?? "" - } - set(newValue) { - let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self)) - NSMenuItem._extraString[tmpAddress] = newValue - } - } -} - -extension Character { - func unicodeScalarCodePoint() -> UInt32 { - let characterString = String(self) - let scalars = characterString.unicodeScalars - - return scalars[scalars.startIndex].value - } -} diff --git a/StatsKit/Charts.swift b/StatsKit/Charts.swift new file mode 100644 index 00000000..6b294b7b --- /dev/null +++ b/StatsKit/Charts.swift @@ -0,0 +1,99 @@ +// +// Chart.swift +// StatsKit +// +// Created by Serhiy Mytrovtsiy on 17/04/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa + +public enum chart_t: Int { + case line = 0 + case bar = 1 + + init?(value: Int) { + self.init(rawValue: value) + } +} + +public class LineChartView: NSView { + public var points: [Double]? = nil + public var transparent: Bool = true + + public init(frame: NSRect, num: Int) { + self.points = Array(repeating: 0, count: num) + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func draw(_ dirtyRect: NSRect) { + super.draw(dirtyRect) + + if self.points?.count == 0 { + return + } + + var lineColor: NSColor = NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 1.0) + var gradientColor: NSColor = NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 0.5) + if !self.transparent { + lineColor = NSColor(hexString: "#5c91f4") + gradientColor = NSColor(hexString: "#5c91f4") + } + + let context = NSGraphicsContext.current!.cgContext + context.setShouldAntialias(true) + let height: CGFloat = self.frame.size.height - self.frame.origin.y - 0.5 + let xRatio: CGFloat = self.frame.size.width / CGFloat(self.points!.count) + + let columnXPoint = { (point: Int) -> CGFloat in + return (CGFloat(point) * xRatio) + dirtyRect.origin.x + } + let columnYPoint = { (point: Int) -> CGFloat in + return CGFloat((CGFloat(truncating: self.points![point] as NSNumber) * height)) + dirtyRect.origin.y + 0.5 + } + + let linePath = NSBezierPath() + let x: CGFloat = columnXPoint(0) + let y: CGFloat = columnYPoint(0) + linePath.move(to: CGPoint(x: x, y: y)) + + for i in 1.. + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSHumanReadableCopyright + Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. + + diff --git a/Stats/libs/SMC.swift b/StatsKit/SMC.swift similarity index 93% rename from Stats/libs/SMC.swift rename to StatsKit/SMC.swift index 07803427..63538fd1 100644 --- a/Stats/libs/SMC.swift +++ b/StatsKit/SMC.swift @@ -1,8 +1,11 @@ // // SMC.swift -// Stats +// StatsKit // // Created by Serhiy Mytrovtsiy on 05/04/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// // Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. // @@ -85,14 +88,10 @@ struct SMCVal_t { } } -class SMCService { +public class SMCService { private var conn: io_connect_t = 0; - init() { - - } - - public func open() -> kern_return_t { + public init() { var result: kern_return_t var iterator: io_iterator_t = 0 let device: io_object_t @@ -101,24 +100,22 @@ class SMCService { result = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDictionary, &iterator) if (result != kIOReturnSuccess) { print("Error IOServiceGetMatchingServices(): " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error")) - return result + return } device = IOIteratorNext(iterator) IOObjectRelease(iterator) if (device == 0) { print("Error IOIteratorNext(): " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error")) - return kIOReturnError + return } result = IOServiceOpen(device, mach_task_self_, 0, &conn) IOObjectRelease(device) if (result != kIOReturnSuccess) { print("Error IOServiceOpen(): " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error")) - return result + return } - - return kIOReturnSuccess } public func close() -> kern_return_t{ @@ -208,7 +205,7 @@ class SMCService { public func getAllKeys() -> [String] { var list: [String] = [] - let keysNum: Double? = smc.getValue("#KEY") + let keysNum: Double? = self.getValue("#KEY") if keysNum == nil { print("ERROR no keys count found") return list @@ -236,16 +233,3 @@ class SMCService { return list } } - -//int64_t GetCPUFrequency() { -// int mib[2]; -// unsigned int freq; -// size_t len; -// -// mib[0] = CTL_HW; -// mib[1] = HW_CPU_FREQ; -// len = sizeof(freq); -// sysctl(mib, 2, &freq, &len, NULL, 0); -// -// return freq; -//} diff --git a/StatsKit/StatsKit.h b/StatsKit/StatsKit.h new file mode 100644 index 00000000..acae1b22 --- /dev/null +++ b/StatsKit/StatsKit.h @@ -0,0 +1,15 @@ +// +// StatsKit.h +// StatsKit +// +// Created by Serhiy Mytrovtsiy on 14/04/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +#import + +FOUNDATION_EXPORT double StatsKitVersionNumber; +FOUNDATION_EXPORT const unsigned char StatsKitVersionString[]; diff --git a/StatsKit/SystemKit.swift b/StatsKit/SystemKit.swift new file mode 100644 index 00000000..db623e14 --- /dev/null +++ b/StatsKit/SystemKit.swift @@ -0,0 +1,383 @@ +// +// SystemKit.swift +// Stats +// +// Created by Serhiy Mytrovtsiy on 13/04/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import os.log + +public enum deviceType: Int { + case unknown = -1 + case macMini = 1 + case macPro = 2 + case imac = 3 + case imacpro = 4 + case macbook = 5 + case macbookAir = 6 + case macbookPro = 7 +} + +public struct model_s { + public let name: String + public let year: Int + public let type: deviceType + public var icon: NSImage = NSImage(named: NSImage.Name("imacPro"))! +} + +public struct os_s { + public let name: String + public let version: OperatingSystemVersion + public let build: String +} + +public struct cpu_s { + public let physicalCores: Int8 + public let logicalCores: Int8 + public let name: String +} + +public struct ram_s { + public var active: Double + public var inactive: Double + public var wired: Double + public var compressed: Double + public var total: Double + public var used: Double +} + +public struct gpu_s { + public let name: String +} + +public struct disk_s { + public let name: String + public let model: String + public let size: Int64 +} + +public struct info_s { + public var cpu: cpu_s? = nil + public var ram: ram_s? = nil + public var gpu: [gpu_s]? = nil + public var disk: disk_s? = nil +} + +public struct device_s { + public var model: model_s = model_s(name: "Unknown", year: 2020, type: .unknown) + public var os: os_s? = nil + public var info: info_s? = info_s() +} + +public class SystemKit { + public var device: device_s = device_s() + private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "SystemKit") + + public init() { + if let modelName = self.modelName() { + if let modelInfo = deviceDict[modelName] { + self.device.model = modelInfo + self.device.model.icon = self.getIcon(type: self.device.model.type) + } else { + os_log(.error, log: self.log, "unknown device %s", modelName) + } + } + + let procInfo = ProcessInfo() + let systemVersion = procInfo.operatingSystemVersion + let build = procInfo.operatingSystemVersionString.split(separator: "(")[1].replacingOccurrences(of: "Build ", with: "").replacingOccurrences(of: ")", with: "") + + self.device.os = os_s(name: osDict[systemVersion.minorVersion] ?? "Unknown", version: systemVersion, build: build) + + self.device.info?.cpu = self.getCPUInfo() + self.device.info?.ram = self.getRamInfo() + self.device.info?.gpu = self.getGPUInfo() + self.device.info?.disk = self.getDiskInfo() + } + + public func modelName() -> String? { + var mib = [CTL_HW, HW_MODEL] + var size = MemoryLayout.size + + let pointer = UnsafeMutablePointer.allocate(capacity: 1) + defer { + pointer.deallocate() + } + let result = sysctl(&mib, u_int(mib.count), pointer, &size, nil, 0) + + if result == KERN_SUCCESS { + return String(cString: UnsafeRawPointer(pointer).assumingMemoryBound(to: CChar.self)) + } + + os_log(.error, log: self.log, "error call sysctl(): %v", (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error")) + return nil + } + + private func getCPUInfo() -> cpu_s? { + var sizeOfName = 0 + sysctlbyname("machdep.cpu.brand_string", nil, &sizeOfName, nil, 0) + var nameCharts = [CChar](repeating: 0, count: sizeOfName) + sysctlbyname("machdep.cpu.brand_string", &nameCharts, &sizeOfName, nil, 0) + var name = String(cString: nameCharts) + if name != "" { + name = name.replacingOccurrences(of: "(TM)", with: "") + name = name.replacingOccurrences(of: "(R)", with: "") + name = name.replacingOccurrences(of: "CPU", with: "") + name = name.replacingOccurrences(of: " @ ", with: "") + } + print(name) + + var size = UInt32(MemoryLayout.size / MemoryLayout.size) + let hostInfo = host_basic_info_t.allocate(capacity: 1) + defer { + hostInfo.deallocate() + } + + let result = hostInfo.withMemoryRebound(to: integer_t.self, capacity: Int(size)) { + host_info(mach_host_self(), HOST_BASIC_INFO, $0, &size) + } + + if result == KERN_SUCCESS { + let data = hostInfo.move() + return cpu_s(physicalCores: Int8(data.physical_cpu), logicalCores: Int8(data.logical_cpu), name: name) + } + + os_log(.error, log: self.log, "hostInfo.withMemoryRebound(): %v", (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error")) + return nil + } + + private func getGPUInfo() -> [gpu_s]? { + var gpu: [gpu_s] = [] + var iterator: io_iterator_t = 0 + var device: io_object_t = 1 + + let result = IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching("IOPCIDevice"), &iterator) + if result == kIOReturnSuccess { + + while device != 0 { + device = IOIteratorNext(iterator) + var serviceDictionary: Unmanaged? + + if (IORegistryEntryCreateCFProperties(device, &serviceDictionary, kCFAllocatorDefault, 0) != kIOReturnSuccess) { + IOObjectRelease(device) + continue + } + + if let props = serviceDictionary { + let dict = props.takeRetainedValue() as NSDictionary + + if let d = dict.object(forKey: "IOName") as? String { + if d == "display" { + let model = dict.object(forKey: "model") as! Data + let modelName = String(data: model, encoding: .ascii)!.replacingOccurrences(of: "\0", with: "") + gpu.append(gpu_s(name: modelName)) + } + } + } + } + } + + return gpu + } + + private func getDiskInfo() -> disk_s? { + var disk: DADisk? = nil + + let keys: [URLResourceKey] = [.volumeNameKey] + let paths = FileManager.default.mountedVolumeURLs(includingResourceValuesForKeys: keys)! + if let session = DASessionCreate(kCFAllocatorDefault) { + for url in paths { + if url.pathComponents.count == 1 { + disk = DADiskCreateFromVolumePath(kCFAllocatorDefault, session, url as CFURL) + } + } + } + + if disk == nil { + os_log(.error, log: self.log, "empty disk after fetching list") + return nil + } + + if let diskDescription = DADiskCopyDescription(disk!) { + if let dict = diskDescription as? [String: AnyObject] { + if let removable = dict[kDADiskDescriptionMediaRemovableKey as String] { + if removable as! Bool { + return nil + } + } + + var name: String = "" + var model: String = "" + var size: Int64 = 0 + + if let mediaName = dict[kDADiskDescriptionMediaNameKey as String] { + name = mediaName as! String + } + if let deviceModel = dict[kDADiskDescriptionDeviceModelKey as String] { + model = (deviceModel as! String).trimmingCharacters(in: .whitespacesAndNewlines) + } + if let mediaSize = dict[kDADiskDescriptionMediaSizeKey as String] { + size = Int64(truncating: mediaSize as! NSNumber) + } + + return disk_s(name: name, model: model, size: size) + } + } + + return nil + } + + public func getRamInfo() -> ram_s? { + var vmStats = host_basic_info() + var count = UInt32(MemoryLayout.size / MemoryLayout.size) + var totalSize: Double = 0 + + var result: kern_return_t = withUnsafeMutablePointer(to: &vmStats) { + $0.withMemoryRebound(to: integer_t.self, capacity: Int(count)) { + host_info(mach_host_self(), HOST_BASIC_INFO, $0, &count) + } + } + + if result == KERN_SUCCESS { + totalSize = Double(vmStats.max_mem) + } else { + os_log(.error, log: self.log, "host_basic_info(): %v", (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error")) + return nil + } + + var pageSize: vm_size_t = 0 + result = withUnsafeMutablePointer(to: &pageSize) { (size) -> kern_return_t in + host_page_size(mach_host_self(), size) + } + + var stats = vm_statistics64() + count = UInt32(MemoryLayout.size / MemoryLayout.size) + + result = withUnsafeMutablePointer(to: &stats) { + $0.withMemoryRebound(to: integer_t.self, capacity: 1) { + host_statistics64(mach_host_self(), HOST_VM_INFO64, $0, &count) + } + } + + if result == KERN_SUCCESS { + let active = Double(stats.active_count) * Double(PAGE_SIZE) + let inactive = Double(stats.inactive_count) * Double(PAGE_SIZE) + let wired = Double(stats.wire_count) * Double(PAGE_SIZE) + let compressed = Double(stats.compressor_page_count) * Double(PAGE_SIZE) + + return ram_s( + active: active, + inactive: inactive, + wired: wired, + compressed: compressed, + total: totalSize, + used: active + wired + compressed + ) + } + + os_log(.error, log: self.log, "host_statistics64(): %v", (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error")) + return nil + } + + private func getIcon(type: deviceType) -> NSImage { + var icon: NSImage = NSImage() + + switch type { + case .macMini: + icon = NSImage(named: NSImage.Name("macMini"))! + break + case .imacpro: + icon = NSImage(named: NSImage.Name("imacPro"))! + break + case .imac: + icon = NSImage(named: NSImage.Name("imac"))! + break + case .macbook, .macbookAir: + icon = NSImage(named: NSImage.Name("macbookAir"))! + break + case .macbookPro: + icon = NSImage(named: NSImage.Name("macbookPro"))! + break + default: + icon = NSImage(named: NSImage.Name("imacPro"))! + break + } + + return icon + } +} + +let deviceDict: [String: model_s] = [ + // Mac Mini + "MacMini6,1": model_s(name: "Mac mini (Late 2012)", year: 2012, type: .macMini), + "Macmini6,2": model_s(name: "Mac mini (Late 2012)", year: 2012, type: .macMini), + "Macmini7,1": model_s(name: "Mac mini (Late 2014)", year: 2012, type: .macMini), + "Macmini8,1": model_s(name: "Mac mini (Late 2018)", year: 2012, type: .macMini), + + // Mac Pro + "MacPro6,1": model_s(name: "Mac Pro (Late 2013)", year: 2012, type: .macPro), + "MacPro7,1": model_s(name: "Mac Pro (2019)", year: 2012, type: .macPro), + + // iMac + "iMac13,2": model_s(name: "iMac 27-Inch (Late 2012)", year: 2012, type: .imac), + "iMac14,2": model_s(name: "iMac 27-Inch (Late 2013)", year: 2012, type: .imac), + "iMac15,1": model_s(name: "iMac 27-Inch (5K, Late 2014)", year: 2012, type: .imac), + "iMac17,1": model_s(name: "iMac 27-Inch (5K, Late 2015)", year: 2012, type: .imac), + "iMac18,3": model_s(name: "iMac 27-Inch (5K, Mid 2017)", year: 2012, type: .imac), + "iMac19,1": model_s(name: "iMac 27-Inch (5K, 2019)", year: 2012, type: .imac), + + // iMac Pro + "iMacPro1,1": model_s(name: "iMac Pro (5K, Late 2017)", year: 2017, type: .imacpro), + + // MacBook + "MacBook8,1": model_s(name: "MacBook (Early 2015)", year: 2015, type: .macbook), + "MacBook9,1": model_s(name: "MacBook (Early 2016)", year: 2016, type: .macbook), + "MacBook10,1": model_s(name: "MacBook (Early 2017)", year: 2017, type: .macbook), + + // MacBook Air + "MacBookAir5,1": model_s(name: "MacBook Air 11\" (Mid 2012)", year: 2012, type: .macbookAir), + "MacBookAir5,2": model_s(name: "MacBook Air 13\" (Mid 2012)", year: 2012, type: .macbookAir), + "MacBookAir6,1": model_s(name: "MacBook Air 11\" (Early 2014)", year: 2014, type: .macbookAir), + "MacBookAir6,2": model_s(name: "MacBook Air 13\" (Early 2014)", year: 2014, type: .macbookAir), + "MacBookAir7,1": model_s(name: "MacBook Air 11\" (Early 2015)", year: 2015, type: .macbookAir), + "MacBookAir7,2": model_s(name: "MacBook Air 13\" (Early 2015)", year: 2015, type: .macbookAir), + "MacBookAir8,1": model_s(name: "MacBook Air 13\" (2018)", year: 2018, type: .macbookAir), + "MacBookAir8,2": model_s(name: "MacBook Air 13\" (2019)", year: 2019, type: .macbookAir), + "MacBookAir9,1": model_s(name: "MacBook Air 13\" (2020)", year: 2020, type: .macbookAir), + + // MacBook Pro + "MacBookPro9,1": model_s(name: "MacBook Pro 15\" (Mid 2012)", year: 2012, type: .macbookPro), + "MacBookPro9,2": model_s(name: "MacBook Pro 13\" (Mid 2012)", year: 2012, type: .macbookPro), + "MacBookPro10,1": model_s(name: "MacBook Pro 15\" (Retina, Mid 2012)", year: 2012, type: .macbookPro), + "MacBookPro10,2": model_s(name: "MacBook Pro 13\" (Retina, Late 2012)", year: 2012, type: .macbookPro), + "MacBookPro11,1": model_s(name: "MacBook Pro 13\" (Retina, Mid 2014)", year: 2014, type: .macbookPro), + "MacBookPro11,2": model_s(name: "MacBook Pro 15\" (Retina, Mid 2014)", year: 2014, type: .macbookPro), + "MacBookPro11,3": model_s(name: "MacBook Pro 15\" (Retina, Mid 2014)", year: 2014, type: .macbookPro), + "MacBookPro11,4": model_s(name: "MacBook Pro 15\" (Retina, Mid 2015)", year: 2015, type: .macbookPro), + "MacBookPro11,5": model_s(name: "MacBook Pro 15\" (Retina, Mid 2015)", year: 2015, type: .macbookPro), + "MacBookPro12,1": model_s(name: "MacBook Pro 13\" (Mid 2015)", year: 2015, type: .macbookPro), + "MacBookPro13,1": model_s(name: "MacBook Pro 13\" (Late 2016)", year: 2016, type: .macbookPro), + "MacBookPro13,2": model_s(name: "MacBook Pro 13\" (Late 2016)", year: 2016, type: .macbookPro), + "MacBookPro13,3": model_s(name: "MacBook Pro 15\" (Late 2016)", year: 2016, type: .macbookPro), + "MacBookPro14,1": model_s(name: "MacBook Pro 13\" (Mid 2017)", year: 2017, type: .macbookPro), + "MacBookPro14,2": model_s(name: "MacBook Pro 13\" (Mid 2017)", year: 2017, type: .macbookPro), + "MacBookPro14,3": model_s(name: "MacBook Pro 15\" (Mid 2017)", year: 2017, type: .macbookPro), + "MacBookPro15,1": model_s(name: "MacBook Pro 15\" (Mid 2018)", year: 2018, type: .macbookPro), + "MacBookPro15,2": model_s(name: "MacBook Pro 13\" (Mid 2019)", year: 2019, type: .macbookPro), + "MacBookPro15,3": model_s(name: "MacBook Pro 15\" (Mid 2019)", year: 2019, type: .macbookPro), + "MacBookPro15,4": model_s(name: "MacBook Pro 13\" (Mid 2019)", year: 2019, type: .macbookPro), + "MacBookPro16,1": model_s(name: "MacBook Pro 16\" (Late 2019)", year: 2019, type: .macbookPro), + "MacBookPro16,2": model_s(name: "MacBook Pro 13\" (Mid 2020)", year: 2019, type: .macbookPro), + "MacBookPro16,3": model_s(name: "MacBook Pro 13\" (Mid 2020)", year: 2020, type: .macbookPro), +] + +let osDict: [Int: String] = [ + 13: "High Sierra", + 14: "Mojave", + 15: "Catalina", +] diff --git a/StatsKit/extensions.swift b/StatsKit/extensions.swift new file mode 100644 index 00000000..99567cbc --- /dev/null +++ b/StatsKit/extensions.swift @@ -0,0 +1,525 @@ +// +// extensions.swift +// StatsKit +// +// Created by Serhiy Mytrovtsiy on 10/04/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa + +public enum Unit : Float { + case byte = 1 + case kilobyte = 1024 + case megabyte = 1048576 + case gigabyte = 1073741824 +} + +public struct Units { + public let bytes: Int64 + + public init(bytes: Int64) { + self.bytes = bytes + } + + public var kilobytes: Double { + return Double(bytes) / 1_024 + } + public var megabytes: Double { + return kilobytes / 1_024 + } + public var gigabytes: Double { + return megabytes / 1_024 + } + + public func getReadableTuple() -> (String, String) { + switch bytes { + case 0..<1_024: + return ("0", "KB/s") + case 1_024..<(1_024 * 1_024): + return (String(format: "%.0f", kilobytes), "KB/s") + case 1_024..<(1_024 * 1_024 * 100): + return (String(format: "%.1f", megabytes), "MB/s") + case (1_024 * 1_024 * 100)..<(1_024 * 1_024 * 1_024): + return (String(format: "%.0f", megabytes), "MB/s") + case (1_024 * 1_024 * 1_024)...Int64.max: + return (String(format: "%.1f", gigabytes), "GB/s") + default: + return (String(format: "%.0f", kilobytes), "KB/s") + } + } + + public func getReadableSpeed() -> String { + switch bytes { + case 0..<1_024: + return "0 KB/s" + case 1_024..<(1_024 * 1_024): + return String(format: "%.0f KB/s", kilobytes) + case 1_024..<(1_024 * 1_024 * 100): + return String(format: "%.1f MB/s", megabytes) + case (1_024 * 1_024 * 100)..<(1_024 * 1_024 * 1_024): + return String(format: "%.0f MB/s", megabytes) + case (1_024 * 1_024 * 1_024)...Int64.max: + return String(format: "%.1f GB/s", gigabytes) + default: + return String(format: "%.0f KB/s", kilobytes) + } + } + + public func getReadableMemory() -> String { + switch bytes { + case 0..<1_024: + return "0 KB" + case 1_024..<(1_024 * 1_024): + return String(format: "%.0f KB", kilobytes) + case 1_024..<(1_024 * 1_024 * 1_024): + return String(format: "%.0f MB", megabytes) + case (1_024 * 1_024 * 1_024)...Int64.max: + return String(format: "%.2f GB", gigabytes) + default: + return String(format: "%.0f KB", kilobytes) + } + } +} + +extension String: LocalizedError { + public var errorDescription: String? { return self } + + public func widthOfString(usingFont font: NSFont) -> CGFloat { + let fontAttributes = [NSAttributedString.Key.font: font] + let size = self.size(withAttributes: fontAttributes) + return size.width + } + + public func heightOfString(usingFont font: NSFont) -> CGFloat { + let fontAttributes = [NSAttributedString.Key.font: font] + let size = self.size(withAttributes: fontAttributes) + return size.height + } + + public func sizeOfString(usingFont font: NSFont) -> CGSize { + let fontAttributes = [NSAttributedString.Key.font: font] + return self.size(withAttributes: fontAttributes) + } + + public func condenseWhitespace() -> String { + let components = self.components(separatedBy: .whitespacesAndNewlines) + return components.filter { !$0.isEmpty }.joined(separator: " ") + } + + public mutating func findAndCrop(pattern: String) -> String { + let regex = try! NSRegularExpression(pattern: pattern) + let stringRange = NSRange(location: 0, length: self.utf16.count) + var line = self + + if let searchRange = regex.firstMatch(in: self, options: [], range: stringRange) { + let start = self.index(self.startIndex, offsetBy: searchRange.range.lowerBound) + let end = self.index(self.startIndex, offsetBy: searchRange.range.upperBound) + let value = String(self[start.. String { + return NSString(format: "%.\(decimalPlaces)f" as NSString, self) as String + } + + func rounded(toPlaces places:Int) -> Double { + let divisor = pow(10.0, Double(places)) + return (self * divisor).rounded() / divisor + } + + func usageColor(reversed: Bool = false, color: NSColor = NSColor(hexString: "#5c91f4")) -> NSColor { + var firstColor = color + if UserDefaults.standard.object(forKey: "color") != nil { + firstColor = NSColor(hexString: UserDefaults.standard.string(forKey: "color")!) + } + + let secondColor: NSColor = NSColor.systemOrange + let thirdColor: NSColor = NSColor.systemRed + + if reversed { + switch self { + case 0.6...0.8: + return secondColor + case 0.8...1: + return firstColor + default: + return thirdColor + } + } else { + switch self { + case 0.6...0.8: + return secondColor + case 0.8...1: + return thirdColor + default: + return firstColor + } + } + } + + func textUsageColor(color: Bool) -> NSColor { + if !color { + return NSColor.textColor + } + return usageColor(color: NSColor.textColor) + } + + func batteryColor(color: Bool = false) -> NSColor { + switch self { + case 0.2...0.4: + if !color { + return NSColor.black + } + return NSColor.systemOrange + case 0.4...1: + if self == 1 { + return NSColor.black + } + if !color { + return NSColor.black + } + return NSColor.systemGreen + default: + return NSColor.systemRed + } + } + + func secondsToHoursMinutesSeconds () -> (Int?, Int?, Int?) { + let hrs = self / 3600 + let mins = (self.truncatingRemainder(dividingBy: 3600)) / 60 + let seconds = (self.truncatingRemainder(dividingBy:3600)).truncatingRemainder(dividingBy:60) + return (Int(hrs) > 0 ? Int(hrs) : nil , Int(mins) > 0 ? Int(mins) : nil, Int(seconds) > 0 ? Int(seconds) : nil) + } + + func printSecondsToHoursMinutesSeconds () -> String { + let time = self.secondsToHoursMinutesSeconds() + + switch time { + case (nil, let x? , let y?): + return "\(x)min \(y)sec" + case (nil, let x?, nil): + return "\(x)min" + case (let x?, nil, nil): + return "\(x)h" + case (nil, nil, let x?): + return "\(x)sec" + case (let x?, nil, let z?): + return "\(x)h \(z)sec" + case (let x?, let y?, nil): + return "\(x)h \(y)min" + case (let x?, let y?, let z?): + return "\(x)h \(y)min \(z)sec" + default: + return "n/a" + } + } +} + +public extension NSView { + var isDarkMode: Bool { + if #available(OSX 10.14, *) { + switch effectiveAppearance.name { + case .darkAqua, .vibrantDark, .accessibilityHighContrastDarkAqua, .accessibilityHighContrastVibrantDark: + return true + default: + return false + } + } else { + switch effectiveAppearance.name { + case .vibrantDark: + return true + default: + return false + } + } + } + + func ToggleTitleRow(frame: NSRect, title: String, action: Selector, state: Bool) -> NSView { + let row: NSView = NSView(frame: frame) + let state: NSControl.StateValue = state ? .on : .off + + let rowTitle: NSTextField = LabelField(frame: NSRect(x: 0, y: (row.frame.height - 16)/2, width: row.frame.width - 52, height: 17), title) + rowTitle.font = NSFont.systemFont(ofSize: 13, weight: .light) + rowTitle.textColor = .labelColor + + var toggle: NSControl = NSControl() + if #available(OSX 10.15, *) { + let switchButton = NSSwitch(frame: NSRect(x: row.frame.width - 50, y: 0, width: 50, height: row.frame.height)) + switchButton.state = state + switchButton.action = action + switchButton.target = self + + toggle = switchButton + } else { + let button: NSButton = NSButton(frame: NSRect(x: row.frame.width - 30, y: 0, width: 30, height: row.frame.height)) + button.setButtonType(.switch) + button.state = state + button.title = "" + button.action = action + button.isBordered = false + button.isTransparent = true + + toggle = button + } + + row.addSubview(toggle) + row.addSubview(rowTitle) + + return row + } + + func SelectTitleRow(frame: NSRect, title: String, action: Selector, items: [String], selected: String) -> NSView { + let row: NSView = NSView(frame: frame) + + let rowTitle: NSTextField = LabelField(frame: NSRect(x: 0, y: (row.frame.height - 16)/2, width: row.frame.width - 52, height: 17), title) + rowTitle.font = NSFont.systemFont(ofSize: 13, weight: .light) + rowTitle.textColor = .labelColor + + let select: NSPopUpButton = NSPopUpButton(frame: NSRect(x: row.frame.width - 50, y: 0, width: 50, height: row.frame.height)) + select.target = self + select.action = action + select.addItems(withTitles: items) + select.selectItem(withTitle: selected) + select.sizeToFit() + + rowTitle.setFrameSize(NSSize(width: row.frame.width - select.frame.width, height: rowTitle.frame.height)) + select.setFrameOrigin(NSPoint(x: row.frame.width - select.frame.width, y: 0)) + + row.addSubview(select) + row.addSubview(rowTitle) + + return row + } +} + +public extension Notification.Name { + static let toggleSettings = Notification.Name("toggleSettings") + static let toggleModule = Notification.Name("toggleModule") + static let openSettingsView = Notification.Name("openSettingsView") + static let switchWidget = Notification.Name("switchWidget") + static let checkForUpdates = Notification.Name("checkForUpdates") + static let clickInSettings = Notification.Name("clickInSettings") +} + +public class NSButtonWithPadding: NSButton { + public var horizontalPadding: CGFloat = 0 + public var verticalPadding: CGFloat = 0 + + public override var intrinsicContentSize: NSSize { + var size = super.intrinsicContentSize + size.width += self.horizontalPadding + size.height += self.verticalPadding + return size; + } +} + +public class TextView: NSTextField { + public override init(frame: NSRect) { + super.init(frame: frame) + + self.isEditable = false + self.isSelectable = false + self.isBezeled = false + self.wantsLayer = true + self.textColor = .labelColor + self.backgroundColor = .clear + self.canDrawSubviewsIntoLayer = true + self.alignment = .natural + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +public extension OperatingSystemVersion { + func getFullVersion(separator: String = ".") -> String { + return "\(majorVersion)\(separator)\(minorVersion)\(separator)\(patchVersion)" + } +} + +extension URL { + func checkFileExist() -> Bool { + return FileManager.default.fileExists(atPath: self.path) + } +} + +extension UInt32 { + init(bytes: (UInt8, UInt8, UInt8, UInt8)) { + self = UInt32(bytes.0) << 24 | UInt32(bytes.1) << 16 | UInt32(bytes.2) << 8 | UInt32(bytes.3) + } +} + +extension FourCharCode { + init(fromString str: String) { + precondition(str.count == 4) + + self = str.utf8.reduce(0) { sum, character in + return sum << 8 | UInt32(character) + } + } + + func toString() -> String { + return String(describing: UnicodeScalar(self >> 24 & 0xff)!) + + String(describing: UnicodeScalar(self >> 16 & 0xff)!) + + String(describing: UnicodeScalar(self >> 8 & 0xff)!) + + String(describing: UnicodeScalar(self & 0xff)!) + } +} + +public extension NSColor { + convenience init(hexString: String, alpha: CGFloat = 1.0) { + let hexString: String = hexString.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + let scanner = Scanner(string: hexString) + if (hexString.hasPrefix("#")) { + scanner.scanLocation = 1 + } + var color: UInt32 = 0 + scanner.scanHexInt32(&color) + let mask = 0x000000FF + let r = Int(color >> 16) & mask + let g = Int(color >> 8) & mask + let b = Int(color) & mask + let red = CGFloat(r) / 255.0 + let green = CGFloat(g) / 255.0 + let blue = CGFloat(b) / 255.0 + self.init(red:red, green:green, blue:blue, alpha:alpha) + } + + func toHexString() -> String { + var r:CGFloat = 0 + var g:CGFloat = 0 + var b:CGFloat = 0 + var a:CGFloat = 0 + getRed(&r, green: &g, blue: &b, alpha: &a) + let rgb:Int = (Int)(r*255)<<16 | (Int)(g*255)<<8 | (Int)(b*255)<<0 + return String(format:"#%06x", rgb) + } +} + +public class LabelField: NSTextField { + public init(frame: NSRect, _ label: String) { + super.init(frame: frame) + + self.isEditable = false + self.isSelectable = false + self.isBezeled = false + self.wantsLayer = true + self.backgroundColor = .clear + self.canDrawSubviewsIntoLayer = true + + self.stringValue = label + self.textColor = .secondaryLabelColor + self.alignment = .natural + self.font = NSFont.systemFont(ofSize: 12, weight: .regular) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +public class ValueField: NSTextField { + public init(frame: NSRect, _ value: String) { + super.init(frame: frame) + + self.isEditable = false + self.isSelectable = false + self.isBezeled = false + self.wantsLayer = true + self.backgroundColor = .clear + self.canDrawSubviewsIntoLayer = true + + self.stringValue = value + self.textColor = .textColor + self.alignment = .right + self.font = NSFont.systemFont(ofSize: 13, weight: .regular) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +public extension NSBezierPath { + func addArrow(start: CGPoint, end: CGPoint, pointerLineLength: CGFloat, arrowAngle: CGFloat) { + self.move(to: start) + self.line(to: end) + + let startEndAngle = atan((end.y - start.y) / (end.x - start.x)) + ((end.x - start.x) < 0 ? CGFloat(Double.pi) : 0) + let arrowLine1 = CGPoint(x: end.x + pointerLineLength * cos(CGFloat(Double.pi) - startEndAngle + arrowAngle), y: end.y - pointerLineLength * sin(CGFloat(Double.pi) - startEndAngle + arrowAngle)) + let arrowLine2 = CGPoint(x: end.x + pointerLineLength * cos(CGFloat(Double.pi) - startEndAngle - arrowAngle), y: end.y - pointerLineLength * sin(CGFloat(Double.pi) - startEndAngle - arrowAngle)) + + self.line(to: arrowLine1) + self.move(to: end) + self.line(to: arrowLine2) + } +} + +public func SeparatorView(_ title: String, origin: NSPoint, width: CGFloat) -> NSView { + let view: NSView = NSView(frame: NSRect(x: origin.x, y: origin.y, width: width, height: 30)) + + let labelView: NSTextField = TextView(frame: NSRect(x: 0, y: (view.frame.height-15)/2, width: view.frame.width, height: 15)) + labelView.stringValue = title + labelView.alignment = .center + labelView.textColor = .secondaryLabelColor + labelView.font = NSFont.systemFont(ofSize: 12, weight: .medium) + labelView.stringValue = title + + view.addSubview(labelView) + return view +} + +public func PopupRow(_ view: NSView, n: CGFloat, title: String, value: String) -> NSTextField { + let rowView: NSView = NSView(frame: NSRect(x: 0, y: 22*n, width: view.frame.width, height: 22)) + + let labelWidth = title.widthOfString(usingFont: .systemFont(ofSize: 13, weight: .regular)) + 5 + let labelView: LabelField = LabelField(frame: NSRect(x: 0, y: (22-15)/2, width: labelWidth, height: 15), title) + let valueView: ValueField = ValueField(frame: NSRect(x: labelWidth, y: (22-16)/2, width: rowView.frame.width - labelWidth, height: 16), value) + + rowView.addSubview(labelView) + rowView.addSubview(valueView) + view.addSubview(rowView) + + return valueView +} diff --git a/Stats/libs/LaunchAtLogin.swift b/StatsKit/launchAtLogin.swift similarity index 81% rename from Stats/libs/LaunchAtLogin.swift rename to StatsKit/launchAtLogin.swift index d9b95f5f..2bd457e4 100644 --- a/Stats/libs/LaunchAtLogin.swift +++ b/StatsKit/launchAtLogin.swift @@ -1,12 +1,15 @@ // -// LaunchAtLogin.swift -// Stats +// launchAtLogin.swift +// StatsKit +// +// Created by Serhiy Mytrovtsiy on 14/04/2020. +// Using Swift 5.0. +// Running on macOS 10.15. // -// Created by Serhiy Mytrovtsiy on 08/04/2020. // Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. // -import Foundation +import Cocoa import ServiceManagement public struct LaunchAtLogin { diff --git a/StatsKit/store.swift b/StatsKit/store.swift new file mode 100644 index 00000000..866751fd --- /dev/null +++ b/StatsKit/store.swift @@ -0,0 +1,48 @@ +// +// store.swift +// StatsKit +// +// Created by Serhiy Mytrovtsiy on 10/04/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa + +public class Store { + private let defaults = UserDefaults.standard + + public init() {} + + public func exist(key: String) -> Bool { + return self.defaults.object(forKey: key) == nil ? false : true + } + + public func bool(key: String, defaultValue value: Bool) -> Bool { + return !self.exist(key: key) ? value : defaults.bool(forKey: key) + } + + public func string(key: String, defaultValue value: String) -> String { + return (!self.exist(key: key) ? value : defaults.string(forKey: key))! + } + + public func int(key: String, defaultValue value: Int) -> Int { + return (!self.exist(key: key) ? value : defaults.integer(forKey: key)) + } + + public func set(key: String, value: Bool) { + self.defaults.set(value, forKey: key) + } + + public func set(key: String, value: String) { + self.defaults.set(value, forKey: key) + } + + public func reset() { + self.defaults.dictionaryRepresentation().keys.forEach { key in + self.defaults.removeObject(forKey: key) + } + } +} diff --git a/Stats/libs/MacAppUpdater.swift b/StatsKit/updater.swift similarity index 71% rename from Stats/libs/MacAppUpdater.swift rename to StatsKit/updater.swift index 0314f4c8..a80fcaa4 100644 --- a/Stats/libs/MacAppUpdater.swift +++ b/StatsKit/updater.swift @@ -1,98 +1,47 @@ // -// macAppUpdater.swift -// Stats +// updater.swift +// StatsKit // -// Created by Serhiy Mytrovtsiy on 25.06.2019. -// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved. +// Created by Serhiy Mytrovtsiy on 14/04/2020. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. // -import Foundation +import Cocoa import SystemConfiguration -extension String: Error {} - -struct version { - let current: String - let latest: String - let newest: Bool - let url: String +public struct version { + public let current: String + public let latest: String + public let newest: Bool + public let url: String } -struct Version { +public struct Version { var major: Int = 0 var minor: Int = 0 var patch: Int = 0 } public class macAppUpdater { - let user: String - let repo: String + private let user: String + private let repo: String - let appName: String = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as! String - let currentVersion: String = "v\(Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String)" + private let appName: String = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as! String + private let currentVersion: String = "v\(Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String)" - var url: String { + private var url: String { return "https://api.github.com/repos/\(user)/\(repo)/releases/latest" } - init(user: String, repo: String) { + public init(user: String, repo: String) { self.user = user self.repo = repo } - func fetchLastVersion(completionHandler: @escaping (_ result: [String]?, _ error: Error?) -> Void) { - let task = URLSession.shared.dataTask(with: URL(string: self.url)!) { data, response, error in - guard let data = data, error == nil else { return } - - do { - let jsonResponse = try JSONSerialization.jsonObject(with: data, options: []) - guard let jsonArray = jsonResponse as? [String: Any] else { - completionHandler(nil, "parse json") - return - } - let lastVersion = jsonArray["tag_name"] as? String - - guard let assets = jsonArray["assets"] as? [[String: Any]] else { - completionHandler(nil, "parse assets") - return - } - if let asset = assets.first(where: {$0["name"] as! String == "\(self.appName).dmg"}) { - let downloadURL = asset["browser_download_url"] as? String - completionHandler([lastVersion!, downloadURL!], nil) - } - } catch let parsingError { - completionHandler(nil, parsingError) - } - } - task.resume() - } - - func checkIfNewer(currentVersion: String, latestVersion: String) -> Bool { - let currentNumber = currentVersion.replacingOccurrences(of: "v", with: "") - let latestNumber = latestVersion.replacingOccurrences(of: "v", with: "") - - let currentArray = currentNumber.condenseWhitespace().split(separator: ".") - let latestArray = latestNumber.condenseWhitespace().split(separator: ".") - - let current = Version(major: Int(currentArray[0]) ?? 0, minor: Int(currentArray[1]) ?? 0, patch: Int(currentArray[2]) ?? 0) - let latest = Version(major: Int(latestArray[0]) ?? 0, minor: Int(latestArray[1]) ?? 0, patch: Int(latestArray[2]) ?? 0) - - if latest.major > current.major { - return true - } - - if latest.minor > current.minor && latest.major >= current.major { - return true - } - - if latest.patch > current.patch && latest.minor >= current.minor && latest.major >= current.major { - return true - } - - return false - } - - func check(completionHandler: @escaping (_ result: version?, _ error: Error?) -> Void) { + public func check(completionHandler: @escaping (_ result: version?, _ error: Error?) -> Void) { if !isConnectedToNetwork() { completionHandler(nil, "No internet connection") return @@ -108,16 +57,68 @@ public class macAppUpdater { completionHandler(nil, "wrong results") return } - + let downloadURL: String = result![1] let lastVersion: String = result![0] let newVersion: Bool = self.checkIfNewer(currentVersion: self.currentVersion, latestVersion: lastVersion) - + completionHandler(version(current: self.currentVersion, latest: lastVersion, newest: newVersion, url: downloadURL), nil) } } - func download(_ url: URL) { + private func fetchLastVersion(completionHandler: @escaping (_ result: [String]?, _ error: Error?) -> Void) { + let task = URLSession.shared.dataTask(with: URL(string: self.url)!) { data, response, error in + guard let data = data, error == nil else { return } + + do { + let jsonResponse = try JSONSerialization.jsonObject(with: data, options: []) + guard let jsonArray = jsonResponse as? [String: Any] else { + completionHandler(nil, "parse json") + return + } + let lastVersion = jsonArray["tag_name"] as? String + + guard let assets = jsonArray["assets"] as? [[String: Any]] else { + completionHandler(nil, "parse assets") + return + } + if let asset = assets.first(where: {$0["name"] as! String == "\(self.appName).dmg"}) { + let downloadURL = asset["browser_download_url"] as? String + completionHandler([lastVersion!, downloadURL!], nil) + } + } catch let parsingError { + completionHandler(nil, parsingError) + } + } + task.resume() + } + + private func checkIfNewer(currentVersion: String, latestVersion: String) -> Bool { + let currentNumber = currentVersion.replacingOccurrences(of: "v", with: "") + let latestNumber = latestVersion.replacingOccurrences(of: "v", with: "") + + let currentArray = currentNumber.condenseWhitespace().split(separator: ".") + let latestArray = latestNumber.condenseWhitespace().split(separator: ".") + + let current = Version(major: Int(currentArray[0]) ?? 0, minor: Int(currentArray[1]) ?? 0, patch: Int(currentArray[2]) ?? 0) + let latest = Version(major: Int(latestArray[0]) ?? 0, minor: Int(latestArray[1]) ?? 0, patch: Int(latestArray[2]) ?? 0) + + if latest.major > current.major { + return true + } + + if latest.minor > current.minor && latest.major >= current.major { + return true + } + + if latest.patch > current.patch && latest.minor >= current.minor && latest.major >= current.major { + return true + } + + return false + } + + public func download(_ url: URL) { let downloadTask = URLSession.shared.downloadTask(with: url) { urlOrNil, responseOrNil, errorOrNil in // check for and handle errors: @@ -155,7 +156,7 @@ public class macAppUpdater { if fileName.hasSuffix(fileExt) { fileNameWithotSuffix = String(fileName.prefix(fileName.count - (fileExt.count+1))) } - + while toPath.checkFileExist() { counter += 1 newFileName = "\(fileNameWithotSuffix!)-\(counter).\(fileExt)" @@ -174,36 +175,36 @@ public class macAppUpdater { let task = Process() task.launchPath = "/usr/bin/hdiutil" task.arguments = ["attach", url] - + let pipe = Pipe() task.standardOutput = pipe task.launch() task.waitUntilExit() - + exit(0) } -} -// https://stackoverflow.com/questions/30743408/check-for-internet-connection-with-swift -func isConnectedToNetwork() -> Bool { - var zeroAddress = sockaddr_in(sin_len: 0, sin_family: 0, sin_port: 0, sin_addr: in_addr(s_addr: 0), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0)) - zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress)) - zeroAddress.sin_family = sa_family_t(AF_INET) - - let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) { - $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {zeroSockAddress in - SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress) + // https://stackoverflow.com/questions/30743408/check-for-internet-connection-with-swift + private func isConnectedToNetwork() -> Bool { + var zeroAddress = sockaddr_in(sin_len: 0, sin_family: 0, sin_port: 0, sin_addr: in_addr(s_addr: 0), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0)) + zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress)) + zeroAddress.sin_family = sa_family_t(AF_INET) + + let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) { + $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {zeroSockAddress in + SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress) + } } + + var flags: SCNetworkReachabilityFlags = SCNetworkReachabilityFlags(rawValue: 0) + if SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) == false { + return false + } + + let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0 + let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0 + let ret = (isReachable && !needsConnection) + + return ret } - - var flags: SCNetworkReachabilityFlags = SCNetworkReachabilityFlags(rawValue: 0) - if SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) == false { - return false - } - - let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0 - let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0 - let ret = (isReachable && !needsConnection) - - return ret } diff --git a/resources/logo.png b/resources/logo.png deleted file mode 100644 index d2ec060cf0c95d7945365925c681c227864bab8e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26246 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 diff --git a/resources/logo.psd b/resources/logo.psd deleted file mode 100644 index 4bde4b51b033ac44c55584a92c9a93b27285c5a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1247004 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

HtoDnWXX9+VxV9w0qP59&LJ9-wFtm7rQFRf6;& zJt#X!JwSSp9@KXbJwVYQDnYeUss!mldQf(ddVusGJ*e*>dVr!qRDx=yR0-09^q}k@ z^#JKXdQjg%^Z-SJs07tYsS>0I=|R~+>H*S&^q{_j=mClbQ3q7qaqrAm+DOG~>AU!BMNIgJ$kRH@`5IsQA zASyw%QmO>$L3&Vjka~dhAU&w>AbNnJK~#cjrBn&hgY=;6AoT$0L3&W%LG%DcgQx`6 zN~sd02kAlCLFxh0gY=-jgXjT@22lyBl~N^057L9OgVY102kAk52hjr*4WbfME2T=1 z9;63l2dM{057LAB4x$Gr8bl?iR!Ws1JxCAA4pI-09;65L9YhaMG>A%2t&}Q3dXOHJ z9i$#0JxCAgJBS{jXb_d4S}9e6^dLPbJ4ii1dXOH}cMv^5(I6^8wNk1C=|Or>c9432 z^dLQ`?;v`BqCr%GYNb>O(u4G%>>%|3=|Or>-$C>MMT4jW)k>)nqzCCi*+J?7(u4G% zzJuriiUv^$s+CeDNDtD3vV+tEqzCCieFxD46b+&hR4b)QkRGH5We2GTNDtD3`VOK8 zC>lg1s8&jqAU#MA$_`QwkRGH5^&LbHP&9~2P_2|IL3)rLlpUlVAU#MA>N|)YplA@4 zpjs(ag7hFgC_6|!KzfiK)OQd)K+zy7LA6q<1nEI~P(`P~SoH07ZkS1l3BZ5~K&|LD@m-0n&r?puU6X0g47u396M+B}fm_ zgR+Cv1Ehzk9>Aam{gMKt04YEUkOHItDL@L40;B*bKnjooq`?0M1*AvqhBJG`U+KJt zR3XxX^q^}*q6a7%L?!5&h*SyEgY=;6AoT$0L3&W%LG%DcgQx`6N~sd02kAlCLFxh0 zgY=-jgXjT@22lyBl~N^057L9OgVY102kAk52hjr*4WbfME2T=19;63l2dM{057LAB z4x$Gr8bl?iR!Ws1JxCAA4pI-09;65L9YhaMG>A%2t&}Q3dXOHJ9i$#0JxCAgJBS{j zXb_d4S}9e6^dLPbJ4ii1dXOH}cMv^5(I6^8wNk1C=|Or>c9432^dLQ`?;v`BqCr%G zYNb>O(u4G%>>%|3=|Or>-$C>MMT4jW)k>)nqzCCi*+J?7(u4G%zJuriiUv^$s+CeD zNDtD3vV+tEqzCCieFxD46b+&hR4b)QkRGH5We2GTNDtD3`VOK8C>lg1s8&jqAU#MA z$_`QwkRGH5^&LbHP&9~2P_2|IL3)rLlpUlVAU#MA>N|)YplA@4pjs(ag7hFgC_6|! zKzfiK)OQd)K+zy7LA6q<1nEI~P(` zP~SoH07ZkS1l3BZ5~K&|LD@m-0n&r?puU6X0g47u396M+B}fm_gR+Cv1EdG(L460& z0~8IS5>zXtN{}9;2W1DT2S^XngZd7l2PhguC8$HtoDnWXX9+VxV9w0qP59&LJ9-wFtm7rQFRf6;&Jt#X!JwSSp9@KXbJwVYQDnYeU zss!mldQf(ddVusGJ*e*>dVr!qRDx=yR0-09^q}k@^#JKXdQjg%^Z-SJs07tYsS>0I z=|R~+>H*S&^q{_j=mClbQ3q7qaqrAm+DOG~>AU!BMNIgJ$kRH@`5IsQAASyw%QmO>$L3&Vjka~dh_|NqK z1~urH6d(mi0aAbzAO%PPQh*d71xNu>fD|AF{x>KfJ+3#L`z!uRXFj9~kshQ6T}2W- zK+zy7L03(rN{}9;2W1DT2S^XngZd7l2PhguC8$HtoDnWXX9+VxV9w0qP59&LJ9-wFtm7rQFRf6;&Jt#X!JwSSp9@KXbJwVYQDnYeU zss!mldQf(ddVusGJ*e*>dVr!qRDx=yR0-09^q}k@^#JKXdQjg%^Z-SJs07tYsS>0I z=|R~+>H*S&^q{_j=mClbQ3q7qaqrAm+DOG~>AU!BMNIgJ$kRH@`5IsQAASyw%QmO>$L3&Vjka~dhAU&w> zAbNnJK~#cjrBn&hgY=;6AoT$0L3&W%LG%DcgQx`6N~sd02kAlCLFxh0gY=-jgXjT@ z22lyBl~N^057L9OgVY102kAk52hjr*4WbfME2T=19;63l2dM{057LAB4x$Gr8bl?i zR!Ws1JxCAA4pI-09;65L9YhaMG>A%2t&}Q3dXOHJ9i$#0JxCAgJBS{jXb_d4S}9e6 z^dLPbJ4ii1dXOH}cMv^5(I6^8wNk1C=|Or>c9432^dLQ`?;v`BqCr%GYNb>O(u4G% z>>%|3=|Or>-$C>MMT4jW)k>)nqzCCi*+J?7(u4G%zJuriiUv^$s+CeDNDtD3vV+tE zqzCCieFxD46b+&hR4b)QkRGH5We2GTNDtD3`VOK8C>lg1s8&jqAU#MA$_`QwkRGH5 z^&LbHP&9~2P_2|IL3)rLlpUlVAU#MA>N|)YplA@4pjs(ag7hFgC_6|!KzfiK)OQd) zK+zy7LA6q<1nEI~P(`P~SoH07ZkS z1l3BZ5~K&|LD@m-0n&r?puU6X0g47u396M+B}fm_gR+Cv1Ej})t_Luv0o|Y_G>0bO z3@-TFxahYbG=Tch(B!v%(KLP@KW6+bK3;s-NXQS>gL*}+i9_Nv!+1TYi%cE-H4N1+ zT!No#{4)+oryANa%;kqa9pZU$YVl!l+NbY^c1&9{a>n`mRxAD10{SHdNC8rS6d(mi z0aAbzAO%PPQsDot0!qgz{Cwk)jhdq$;}MbJxXN_A_|aDJVZLo~3B%B*WBe5H&p2e5 zB2G6wZZyv2m*9WJcYeq?EPupWd~We0-r~bN!;c$|qzz-zqw4y2q}{OSxSKx;cfq6a zCU{ithDYZ$rovQMjDID)KEpXz!LF2(o0tFiKbCJja{lVWr}`AgzQir&rsD6XuLn8E z)~DD#dwBKyk*&-B81LWH&B-pzx6@>mJ%0=B~n4#c*4n?NSOuFIc?6#3CruGJGnh zjbG+z6DjanptT#gBV(ZqpUi2~xAeANF7UaawH>l2Yn}}MhSPr7=w-P|;0r)&*<(xQ zY#Ba@)26QLY7r&y9B7&>NuDW#zvMK@OPZ`hdKy4QHYR4e3>?R4V#ZY5i0};1wB4Nj zIoFCle#U9CH@Dr4>@3jO1>Vtr*5e}mnA5}t+HFO8HfU-rNXwU{A99+s1vR!KJr^`> zb`|8x()T${!LBwtkzN2Ajn{=NSvr=}T=3HDMtTuw%!b8g$kMlQyN8+WMLGgB<~~n8 z%F?$u%~K!q{YWoG+V5$aEPVrMKl6h~N8)x5e)L|JzQ$=D57rz;dKs{8w~}SyD;&Gk z?Fh2VfwesIR+jyjV`p0)Lv|%-s%_T4k%cdD8vW*K$B|wQ8rvC}uVm>9oF;RI?MbAg zK*I*UOq8Y1ahimI>@?DAfi*n&QWieLu~QAtAiEwkrRL?u%fhEQP2N1)b4YJM?ViWU z!Y4WQy!&}%Hvy}>ZNXmW%cGWZ?to ziwLWmNbdqx=SHk7ypLly>fAzhH?U#pcVyYU97`V-gX~^lHc>IM>~4-l+1x>PAF!G? zZppH{ICisUEVBE7^+~%a%SLl7tKfp8#F3GZ+ zIkrIa6xrjztaehy}WjAmvwt5`0r-0Rs zKPSts=U9B*7s#Fl*7V(3S#}-A-ZhIy_6)H0AI`|KYdQ9z{Yzxe0_&P}T9%FCSXS2r zWX}QXnR7~(UBj`Qo{7kw2i7+c%79Dhib{S&=Y_Yq;YG)vO2lcwYTb5mbxy(Kj*;vdu`*+E*^D#$ivIG{UMa!b| zIGE0pcQL2yl~AVdla1^>tQDy{Wt*LgdsZt4+51?7Ztjp}=U`p2$rV_L+AfRE=3p&P zJ^=9ECQJUoflnTi53#nUE1@jCPCl}a(1|y;$~HR-x7D&hU?F0QEIN~eB|P~U<6N(V zGQFoB*(botCH*bi?C+d5sXPcQ?AR=e&fqjVdGaa1Pn%@P=^Pu1;4=*IR7Eni3Qxkp zjj~Nn`atGvjtf3Spu>!XpI7z&%vMs zNW}emsf@Chwu#8X%+(65=5a7H5t4va%Th*JR`n!gVbdywR&zPnlmxGUHPb7jOyB$! zvT$alLaR9(oOuPW(E}?K$ZQTgU&9;BsyCHUc2n~PSs1ijq17z>1m3_~VCIjNQTEvU zEwbSEmqM$V9QeJ3ci8GvWt2TNdxtFeE>mbV19Rm&NCsy1L>XmI%#x9Xp^*x$rgJbf z8B);MvC1fmWhuyl*PjZle&nEsKweNmo=<`I0Bx5lwE2O9Ht%_Iw+eFidq@T5RG^Hq z0;g1DA!3Qb`qMawNQE?Db{~~d_R&5KSqO|!Xf>6CK!LoYf{aar4*+fWb5HzVNw4oY zX#0UDH>)5we}Ip`DmET`f}WFcfxiEB1&Q3?kk0vV%%jQI%Z08JJuw3*C7({!GU zR6$0jLk2L5WMz~kTVx;$Jr*de{~bD7Ah)O>w`4#j`XoacWf^vv$ik5M3hRH%!4QGm zqk`O*30b)QY-N;Xm(D^K2Fz1f{~Hbl3giwItY-GW6uEP3XWBwP& zNEKvcHsoMNi&I9~OO}HyxXe*l|0@n$1ahJZa-u+(iH2z&-@8dae7RW&=$iV{ny9#oqK<-jO z?iR?~D#+V<;4njFD5Kg;2FOB{847#74ndj_`C737TLm@RTnDmW+PTvc#x$n{de^+ryof*X$9I2GIk zws@C+Hmso?Om2fS0k z;jtN{s^IX*L^bzOMSIwPglP(PevZLG6n~IIN{B~}qy#BpdLDs1L7pH_lyqi|qJyG? zqJyG?dfU{GHs%4U3RD%0ZZKp=@&tK;JV8AL>M2l9!I%fADo|B0x`BEMq8msFQo=MY z$P?rV@B}fU=xFAoEC&&{e^`I#_C^{%QC_3nfLd*lC1Sw&f5y=ze3Gzfq zRg$8EqJyG?qJyqc&@~EU9-yi~Rl(>6I?p7!fs`O6Oyhz)L7pH_l+=T!=%DDJ=%DDJ zBMLDOkP@VXX+|VZkSE9!B~?j^4vG$n4vG%CMnTsojCp{n0#ya08|XZf=mt`PlrW78 z@&tK;JW)~)nxccEgQA0?gN`V~JU~j25~dlEJVBlyPn1+8DLN=RC^{%Q=o$rGqcG+H zstQyUjBcRwOrje|2~xr|F31z)3GzfqJ!py!iVlhniVixW5c2>jK}wirMDhfAf;>@D zm89sP=%DDJ=%8y9bdAE82dFAgRWQ1N&NGQ_ASFl%)3_i{kSE9!CH0^wIw(3QIw(5m zh(gQ*qy#Bpni0tp@D51OKbqJyG?qJxeo#5_PskP@aDkvu`3AWxK3 zB`G>6Iw(3QI_Me&U86AO0jdgA6^w46^Gu=}NC{HHG%m;!G?d4fDqQkDFFKROtsPv=V7J;U@kj%5OaR25w6f55#{(OxpP7pH=Y zL+-8$?jCYCRB$(uJF9{_i`)?v+)?DVs^GREw_F9c9J$#lxY@{!SHVp{PN#w!j$AJl zTyNxDRd8;|IjP{Bkh4|6*#ZZUF+;)7FE~vG@Z^0JG}VW_a@*`R9XN3 zz12xr3}%NfH9$7@KsF!& z5+D$ku!gK8A^V;td(!p$-deh=yQ*&SzVkf)=l48+`;m0l{nWX)s;jHcJ?GqWZh;)h zNZP4>o<;vdW+v_{E}Mn?Ym7Rd38q+OflTl8Np(x$GBq=Aq<)+pK6z{n48 zf&7q>v`hU0i~cKc{9PId9=oB|C<(1+CT8RW7vXzfz=?e)Z%gzJF_!EWgRf zCvSlqa+5d+xv(klG;99N!93PRL)aQSw65my!}flGa!VZ5ByV2}nA` zxz+-05vg-X2`MIIn>9+d7lWj~Z(L^~wN<2zzb__5gv_u;$?PJK)c?kM3#n})E$&)G z3h@nCqvWGPkdz+XU?H_#q{nC>xk2c_){Pdd6I$J%8>E1cdDbYIUjUN&U*BXQRTOEy ze*wuS-b??f#l=sB$to_AzXn|MRFh) z9KEk*D;Jz5(wh5nNe&?&S%M!!U^XE;ws9o}iNa1Ik+gg9b}l+yv=#SYY!;y-ouOQI z27V)>oM1BvSz?8fW$hA4JDv~YN}Y*cen-Y;5Zdo%IG3FTedeJyurLpNYK4*stsv70 z*%-kUI$IQl3W%gz;PZJ%baR|tK_xs%Jz!5EvTy}v-uLU%Ky+~-^nmt^0AuiZ{^^aO!7f@m(gOr!<(RfA0>w0~I)mt8K>vH?|KPZRo7Z7i2v zA=0|1D#4z@_s|r_WmjS*GQ0w83V!F!@mzKlzO#|#U{4bIcFRF7yIQ0zZ?__o7bb}iWVO28%)I<6&w%dW#1#ubB2BJ{21L@v7?$M#kcSlEz9 zH6G!z8*t7>6@opA^ILb6%WlMXG5iMDBZNL#ag57u5^2Sg1z-~iedtCKm)$JV8xQA$ zO@O{}Iho6D5$ToR=Yc&8d&jZkTs8zx`#acU>=2pgkv+-|!YaAoB$wSL z(gn60u&}*<(Q=B*hC+*&m<2YL(3h%CbJ;MFR=t!7HU>|L^i(bzj(b_Z46xDo9Y>wv zvJoPU3b+RL01<>B=Q%FCLlm6Tg>=wSg#M}aJQv=H`~RP=fZa#vgBQ}c>@M6*9=r^8 zFYZzsE^yi1BHhsW64>2@eo}vl%kIVV^3w}ocj2s^zszO#;W^wd4eU-LbX@Gb%7ypi zOf2bm9&{w`Cnf1z_<%@DMx6y4L1_0K*SKsnuC?xGz=jd}QEeudjl~)M=rq_++=CBf zaoISL9=Pum*lmzomBVEZ;#&LYB-pKl-m@c@%N`Qx&U;RP-9qRad3jtmL8N(aB!k^d z1poQ11zh-uD74P^PXfJ>&_PFTaN(mOJu>Jh*bPLWe=aKG!bze~^k;em^g4`VW= zJ}wFkGyM}luOULOZO$?-eG=EjwqA!o!&mn7(Q+=Ff_u=@@nBcrUYu6RWl!T5@6dNC1vC*(M|bebsSeC8VkdJ*VP zuGDksi=uGl6W_g{7hv3PooM9Jmqp>kTf%P8bBXZ6uJ#r#oh}OPyITS4Pf5yb48(f`-|JaP9>x#{qGCY zIQFwVQMmBmJwpIbfs;#kd`aaD4t+xuDwjOINut|{t;3t44YN6Tv1qH=G}3Q_#J3Pz zm%l_c&ExQ8qOES*n;q6ld?T@S9Uoc0ki%Dsw)(I!omNTwO=9ajYGuJ<4qqeM3Rb?+ zdAY>b5+6GFABoM&IXm1GeVUK_GMFxr_NXL2{v#J%aIWU;(IomfF9whFUnuQSN__5o zbHVA>^_)H0M4#5w1#jLtSK6bH_}uowPdf`YbN28R3wQqX!fi999j+7KyI-Fik<}h* zy2o1!bAOh%_9neCy51g)x<{bWBO9H2 zoBGaZTc9G%`*_uT^k>4|!hdJSD9xnmTfDbu_q3A-q$6fW?cVO*L;Fr1le)$HugbB% zZc6^7{g?H}+IwRR{|Ep6AN*ybkhg0|RgOGD&vyCylDDyMPHlC|k+-jQTIz&gA@W|J9B~m0`S1?!~js zy@%Rr^;g~By{CJN`<*Jqy~XpLw*6M#d-tZ@$5XQWN#CyDVzl3_Z)4ZSfBx!4ykGyh zOeW#hkK+33fNLEB7*X*Ou>QtW9p$(&|?2U)IihX<4K9azEM@&n?wJrLsYOCj8m1BS97VSGWYW071)>+T;OZ#5Chr8XA zP`AshS(}?OerLX)I!>egjPxT}gVWP0D<8G0jj5d6sxq=R#y+{XsHFT?ZIj#Of0>@A z{mk@DMr;4Nx489FPs!{|xZCvam{u&mRQ*`q!t`dQQLAlk?OJ}=7+7KQU zpXf}rWn^dPw^oscCXqC>R@w5ivooBj-4o-Z!Z)m%Gh@VEk!dG z(TWcx0k_@v=+H3}ehgZ?ap$4btg@ySF;)y{D$7bev~%O)pdTlU8T#mbw_P*Ye?Rf} zyZ6yo#(z6)<*s9wi<-*BFwwuM=<=~$tEPQB{*{6E`W2b%nLs-A?ElJ#KP=dqc%?d9 zTq|~}zLL0g!4Dt4(!Xbo$v!Ehlh1w6zVq$8ZO3w&E{coAPE9$-w$1zYooDa!X))O+ zjdbk%@Q8^sLXryFPKmR{4s8WVAu}e9c$jpa>YAfzPsugX!RLXO{yH=4bU8EgcNEJ{ zht2%!OAq+WF~qNv4t@{4`NPV%tQO`;X%;)QWW}xg;jM={1RL_jq=U^q;=h*0=CmYb(0{)>u)}&o+5vj-2eYCw+nA@f zQ1oldikkJ|;Eo}NbTjeu={NSb$n-Ymjn4(09{JnYem>!bw3GODe(H-=ryE(I#Wm5l z@$~91p6a~IfSm?E!b`UAw_Iss5j2-X-?l4TzJKYCC*)Jp4aoV#r`NdIab+zMnIig>#myeu>#za27`BNQC+#e%m&hd1r)cM-7jHXeK&~LZ z^r4SdoNuj_&_vO<_56yD9-=1=*fqrGjuBI$t1BclUi7Jsp7O^#QVqzB#77wT)#hu( z5_v%MxwiSMfkK)A8A5zIJU2P2ra&V1ias?_KR?&uvH=-Ne7cU9ezY-HB6o^DjYp@C z=z7h7j3ho?{y67UTc$*Yi#~0q=KQfswgI_YCR?vc%N!S$8DcZ6Z zy@-2=H33-8!z8 z2v067T&iFNqENW-$-O%2CK3AlmJzH_*F_=Yx4!#zR3j1Y{rl+!3Y9Gir~iI$jE;f< zI^g5O?WzGfQ?#`o{v;q?$HA~Iyd2^(Y^RI1hLD$q!#WP;^1f5^RC76e1$k5Zsn?PE z7Z&&c`uTCy0)Igij(;9-%uCdCVxuo?Zcy#`=h5Nj7wB;>3T}dXCuOE4rG z)Er{-eREHn+xKuGtOB7j4c1BWRu%2X|tZudlh?iF-xi+SgqQyr>X3i5Awl zJ(RoA=fd8FUeq?Ydv>?GeV#j+lj+4`FDeYKulG~k9@hv_NPXY8%!`U3!tIl8WVlX$ zm?+$sbbEytwSx$OOK-Y8$lFEX=8`~Wf7hPZT|^iX(SF4xYb(YTK7=WlT52!cpCc15 zx~R>fkT{Y$yr?KR1wXpv_Q-D(g-ahHOx;r!0s^}HaKjxCupYOJAG$CfgSN*QB6Oct z<&H5}D+*Q9dNg}caYVR((M@-R!)hGkqWfFDsDrpF)-|WNM!yn{wRL@%KSbN(VIm9; zZ+Ay*FmFr5VB$q3;(YCKy5mKb!ZEvdh^HikwLBZ64Qrt>&rb8`3jsAl4{HNz5Tbc_ z=yVST!91@X)duq*ko4%QGd&mt61|*s&~;$JA_8NRUY_m2Ac$$`F{XOUH;7=U;PlwA zxgHDxs9s3W22ddwIpKx*9t?uFo;#!s-a>Hoq30HQFj#2%M2t3Y%pua4C)hLG{b;^G zg#J67+Ca2AoWouHmv~T@aP@9!-=i6J4X(_P2bXzJSMdF>Zr!D!s^H#V-D`yhm5ys^ zVbcx`RUryZ3%jrKpfYfW`B$wrq_GUYX4+=4?ubtI~ref*c+1ECDFo>`n7_AMQ%@=9(z|9^^0TJ$7*RBoN zg*Ui;-F-}%aF4tY#~0L~4J(F&xgp4Zn+H`)1fQ?U*K5ud9>e8d`Gk5<2>pKj@>&g) ziSYf`!#o&-r#~Fcrb6mk2?o1tVkGu-!{QK$^8c|n7 zq56BjT^>|5g6cBad`j)_l1MY(W(td25*BR?NLZp_F5o(Ul&LFj#zBNTmbEU{KnP7} zU3O=b2URbfC++eVggf9o9q^#Am}uxZ?Yf^cB0V=W+JnJ@tNTK=Yoid75r!F_c1$fe z1CtzcHDfv@3XVyBOzClt9Lw=uzN%dhcT%KRUq0x;v=iDZe5OXwagl~&mZ+5jg~)HR zV}=GwMsJh-m@?$XzNluJ zi~6-C;=BDqNY*gRh;7hG5r`W)jF8enOtVtutiaX1u$5_5Zs-ucw}oApW~EXq2_0R) zbSgI#k08=FW%G}$?5!sB-pCdY1_4o#_cF~(m9rM3o>;@QF1MfniRL=mW05!+}AE?7=<zLBYIZU#Yj8}GQHVG!;$rnt(3*@5K~ zW0-cQ9zVjzdWKbaFbE3_@5yvKl|eY#*ishGvJ;*!3^)oq9Rl|22(;~Cj-C||SD&>MqVb3CARqRbAG%EUKJ&O-? zL(r<~zoO+DDxT20_OpOPH-tdw{dY4RR2?^hTHh>V>ZhAQV0GDBl^O;CwYRNi%BPz_ zQ1_bKsx=G(bRSD+A(n0iLE`C;F&$Oy>j*~ueG`k>bVEz76p9X z$dqKa9ITyb{9J>i;jluEc_1+6gb;sbb(XJcEQ9EOmFdxLDN}G%{inGIEUNgp{)iVL zi1i-_xsc0-UUCUeymfrQxVv4_(slv1TvE?qN2={UaTlJ%5Dv|zY{0bLSg0F0vlg$mZ5atKY zS+-R!suXwpy=-crh01Wp3s~)9pzj7=4fkTOQ2*+KYg|k@p~EWKebhHd+JMG^`xR^G4 zW5FxEn5!aPY}?^tpqqEv$)-74IafrwtJ6*wgA^`3PAvCgE<+pc!5mHUctk?&(o9Gz zhs}n1?{+Z)dAxY3mzaxKPw@C27eZgDV^bxq5KNaGukKX{*hJqa&1|BiMbfYk=!1O< z0!P>PPF1m2x${_*_dWAplgrIm74qzmZ*g_(in(_5Xc34$sIbE&Pq(F!vY z3p}FQbxbOjp>&Q>m|6Hm9BI=rm~R~oU>-F#p4re5&$jBAQ+OEM6{j$Buw*D*cC%^6 zjIjIk-^VM=Tr3R9Z`R4dj4Z$3L4}z|=;LKgI_3m^TaO=7nE8ahSlg&$Fl(zF%6xll z%nPtYqM<>@B;!u^MuNgD#4of}cKB(>oP?!9?ys49eiJkLrM-(QQkbl+b zkRu}b^{7HD!J_DevcFK~Xo6N7~uYi!91vkbqO4b?gZGruji;|jAJ z%+@L$gT*5eekT+Liy(Z%Ds>DNqwMX#{Fv+vVqWOCOLkmp$BemQOy?AZ!NQ9U`^t54 zumC2}|CGXD?${}&OvhmUm>h6gVb@pxpcA;v=j2UL??PnEcJ)zxFiga@Ji}cK$=M-iG)R8lVIwlIbYWMRBgZXEV^EY%1 z=Ah?$FuyGu^CmE9vZGcz9xRng>wZCDHlwd|`8qj!(AS+86$UfZ+t23d7|c@7+vpiFpCX1k)xA?ne54cs|vFn-$in^ zj)}x~>YuJKnCW&-$kH*G?Iw0+K4>-`%yj)?Wv8=tJecjq`ei6gIGDW|Iyqq?-RGC7 zFqjS7BChEe%!ngwSqg*Mux)d?j=_w0i;a1@*?4x~H@H@IZEMGag@$Wsj*B6SuIR*U z70Du%zxK|9{%_^yUmDGaOwPt+XO4F(p1 zx~HlY2G)VVvSb|t3qcuDu)bh{=#eQ$aA=1ED@10GCWSePYwOTKot(L_ zo%=N_46GD1BwokBQV~K~ObF{Mg^+*7=@6ue_*Erf$9Ugqgy70)Riwa<@ocjkOQIby zY#GhZw<%02JMtJ^S2Nj>w<`>69RU}jbqwqsmjYOH3L7zO9k%`YMN+VLL?c0@%8-To zbz)%dSfmo?2>CopM@$px7m5hD&4xD{63xTqPGBtwESE!GvpI9z2u(|XsFj}_;yGy}cDSyZ z7_uzXKun`ZR>(vaA)jnFAR0vS={iY9Hg1ef#t3bCa=paB^3(mg8ik}C3#>nR-8V=K zEI+oGtp;7e`V(v0s4(QW5Ca5Rz^2LsY(pcQ#t3ze+$2eXZRmkYHP%Tx8rX-b9@s1~ z`LNWTF87j9BUZkqSdLuQWi4MY35YkdN&{HLy(EU6DxsvO|(l$#NDMBlKp!oe~2h zSjY3r4LX7$?0m;v3PTnzGeB;LWU)-ZOg74Cj36u1Zb=HvWOtV?H4p<+S=rrtBnDPnBwSuHDFIW7MSA>&}fAr zU(Pq^DGR>>nSimbU%fFx>-)t>Qedp}J*~$4X?udfF4ZqqVMx$igPt-(5+oBa=RMtK zjL^2H<0L6C=iQbu$3P5BdKtIHD-2mY+W<)y$x@kUg)`o1j8G?>@=U#eQLtP7ECVqx z4CZ$`q%dUDOatVKNH)m?@-M%oMlWhl%w>^`IV{P5(Xe}|F+$6FCMXPvm~PO|C6PqP z1Tu`j6J$VK6v;b@k_>_LEHx(3%@GNqBti|M)Q1keT6*ViSTE-;pdM8(2Vs_7LRvXOZ-b z5z`5Il|>`yi8CU3?KlWnHT|!fAvDkbgv5{~?S@iPaiK8+cF*V9%@ErD+(|I7dv?6a zq8{{vf$j5Z#}tVn(^?IYQzDth2v|lRZZ<<`^CPFgz%nW%v)BoJPq2Ag~Lg%Lt)?oE{#64_)Z<%CFfGGZPQl)3|6wW5xTIWwSPY1MyhlZ{g%QE9fH=(%>KuLn%wpVZuhtogIf5&u!$pZ9Khzo` ziI~eW0+!(4Im{61xc`#GkgYX_QW8WG&Is6wpLZG~heh(rWgyH{e2xXI=#Lua;;VjF zB!+y+;#Ks>Aq||j;dMQyNiD$$HBvm%f9<@&- zZ(Iift2sSwhR}51Jc%J6vQQ#@KYK;;H%7p&-YL%vp=Dk3C5HUMf{pZ2_CWPv1T61& z)tVu+{=Nc02%e2Bph-X4og#U) z5Cm-Z^sE^|&(b1^A%D&>l(Iu4;~4?#zHgQpf@I6Z5<|YuHk1;H^Uny_|8FZXLuiSA ziNug!Szwj^h$3L!Vgx({-D}MdTHB*kV#p#Ed8L;U4vRY@;6u2-$qb=Q_m@cwS)E}h zB}^o%838XtV5=EITOTZ!7_yZGcr=#6eTyFeCO6@&^_trys`#kvw-(CZbJ=XhuX4^5i)~ z8S6#zWW7x6GbQ#h0^XWICOIhKv2hM=kQfrq!u9lHU57_IBjC^J({6^)_CAdgL)Ni? zKE0H+Fi|lA9-sT0%@Eprf0M+JMX82T*5FRS2>67$-!wyLefMUGAyZEqN?DDk0wdrx z>QZfn(CV%&5<`Aqp@sS*S_QKOBj8u^yJ3dVLcdmtAs?~eL%kIEmOf%cJns3I%@7m^ zXplBe5c!X$Gmh)71jdljfzDx?(!4_14i zQ)0+s7AC1}l;1Cd@re=eZ*_B+AvQ-hA~EDg7D%a=f|W-3Z3fV4|DzztH%Ck)Q1P3iEH^RDFe3!<8a)Ptyrx~4HAN#2PLS6mB8Ef69Ao~w zKJece9|~4_IUh^{Ll!5PAWj@_5=+qRUuTZcTK{A)<_6Jg!uX0A5Nk1rmF(ZW~2lp*DM`*F{DKO+uEQ;6Ynr;%w zpClrVL&Tk83Cr#6<_LB6J`IM_@;s(Z$+C(X%NX<60wg% z>`P^7+wW~PM`-Ks&VV65A28{sUL-%CVSZPdX^zlLdKL_Mkwq;Vo!gtZDM`d;4zcAd zOI{z)V2;p+fOBA&_tv-{IKjSJa9zBxkk zg)~+S;FY~5{nSExl!z@H0v00pk-OHLBecHj1u*2(JtqCY75Aw`Byfm?3#=f*{T>TE zwYOSCgty;+5e%8M+oT^j^d?Ef1rBlHB8oJSfh3BDu|>p8^w%dDhDhiY?#R(H4phP=7Mq#rmI-;{{$9Af(wR%YY2 zo8}0;dD~SmXyz5iF^`a(huC1pGrh5 zhlou_*%EqZi#bAD?!3k=Z2~9grxFnzZZZ-O(brfhl{;F@5%iM*hJ4CG{EZH|1pP=v zG>3@JKp_}9{N+_Tn&``E4 z!sz>fTX?8M1ak<)K0-zohsa`u8pwbUlU@o$(w~)mAU|`6pC#fXhd7yws!H^(c5{Tb z-*p`f`D}~HNb*JUnMCa75WBClI!~P%%n{nqiPeH4BR8A$0|)m=iCE4dmPaX#V*x7) zMoY{QT1sz#A%i!V^aE%8V2PN_Atp=2VGfaSgOxJ#Z#G9z7?qVfBcs-vj3gU2Z;4pS zA(l!+28YNjBt?YS*O~N!kO4a@=ElNmjDNu(CQ3vkhlngxZ!8NH4T<_K-*$V%;zVQWlAk^$X9BBpbQ84_`lL!^|TNFeRn zW{%LduBEJy;E2^GBe{lOxkSw75OX9Vg+rv2veJfK+sqM+gq1%eBUhP>BpoA>h`AhM zu0*78h}1Gxy0L4UIf9X}@{VN0N|TXX70C#Rn86`tNW@VNakQM3rtH{gj?l)Atb8ST zZiUH6uE63d5kGT?pCw{HhuB}iN^c5C3}*ZT#Co+FftBYZgO-~h2uv9y5npqNuO(s& zhu9(!SsWs#l9egFdzr~N5ZZFLRLJyg4)L}`%;pfYCE^%|NUlN&R_fPij?hLwsZ8sT zr6yxVkj)T@_?AO_D-l~c#5Re@lZo1ixTl)9O7GvSj!>SOGGM%ICGPg_4a8r zM`)9eR3yCb0+XXb)Y!uk@eYR=D-lyT#1x5$;1D|{BAr8|*Rvw&o!ZP1+SW-bxjtyV z$7n0ZeS(nJGPl4w7rv5!2Z#BCP#BxB#%nO zdmQ3DiTI5}{3a0|si3B4dPFZ_5|PG{aiNhQP}bIHj?gAs6ByEau12=DAH@6hmWV%c zh(Aiiw;bYIiCDrRmPtf3hlrDi91d~4i6Erc4Wn{WC=n|;#7c>X;}G!@kC+KN=)Ex3cvHaT8IS=uGyWe)MO zM10O6zL1Dn9AcJ4gmH*)iAd!TsS;7aAu3u(DzOIm9axF`h$=mxymU#CH-gjYCY6h}9fojYRC^ z5IZH}D2F&I5tli{Wr--_5XBPF#37oT1j|7>2bp|x$QykdVZ?nL;y#HO$RP$v#4rvq zOd`f`h%pi|fkS*I5kGK?Hc7-T4uSAOoYTV`;;=-datJv2 zAtRGRz_A3Pj6=Zm2BMimK*6NMZ<@*Faa5$fXz#{p4?ue_PJ1u3Kg4N&2)TNBNWWCJ&m(Jq9h0k@*2-N zaDCrOdoE{xc=kvhryU+S%k~P+{#ZOqYB=p!^7{XDdp#j!s>$g;f}7L--&!fD<$Rm4 zKK(!aZL+Jak~4mGwf?8S2CHA&fBI{-x?B9GcO$F2oz>ma>TYXwx3;?5TRknTo;Frb zE32oS)zi}IX>0YgwtCuIX$w}`hLyHrrR`X0OIF$zUt6=*_ijmNwAMS%aI{KUceU1k zt#xQ?J=|KCx1JAJ&nZ6R9IZSr;mvLSBeit>^L9wg78;g0)S;+OBag=bXy65uSaegOgdcENnC&tnEBJTT#L- z+M9SbtA&ybYr7ZEcJ`4Z!`l94ZNuZ)0eiAutgVT#z*^WOK^!wTNfOzvX>A*|wy#>- zXvcEKF;=p#THA^(?ah3vbvvxqu;W|UzCk3Lo4$!`Kex8QTifxieF4_~ghx0dm3ABKv4~exBKqj4wDcWM2`_-}H(k!_JW*`K+uxU-0zt z{AW3mhb`XReMV#-9M6xpN|Isi3*`9|-;`wVe3JbcVd2^A*vve+7XDEXvF7ekBKugE zaR}K@Ywhc`_80Sf&VkI=YwfviB*eo14I<3k{jK=Wt^M%UzI%Q#x=Xk0`N zCz%KA62$a^;Kc>cxy9IGi}++jQfG5woO5q6PTD%w+A?0-BI?^#$>Ps>vEv7BG5(wv z!~Q&zVI2vNn0#J5{jpn&$>+uO|B=bCSRK%vam2YaOgtOknK> zZ{5XdX=TP7^JN!gu0)LF5YqY&jKpFU3D&cum?H#hQ9#V#5b}DKmpFv9CdPVY4c7Qr ztj_^qy~f9KosjivBdlt&ShI9X>z;UPr+OE$HC&ne^;aNxYq*w4Yq;Lv$dJ~7@mG_z zU^N-mwI!G@I3rk-2Euw>8*gpgV@w96r4}pfu#(SWo!+gi^|M|vh?R+n=8Fo+v0Gfb zXtDkg>o6_WP=c^thiSQ%(|ScHR-W?KmF~HT<^ZlR?=FmlLcYD)|R(kcR=nf ztohAB2Ai!WK548hme&niu05u<@@*zUkl327V8%*mi*?W--Y^$JMzOWjw1Z>2Nk2uX zuz?lXS(r(iBP8tB)`nZGFz=ehR%i7`h=tYt2O35>ALXpj!ePK3!&kU0T)5kP#(A*9?2))^X*31UyANk7O0fjkc& zEOSLzXPiK03trxeP|MsGZ!y9;g9b8n@bYkMxV2m!R4CkKa#BzvfMo=+&MN|9khu_+ zn*r5*b@NC*+A_W;;kgo;A1P&qPe6h|PgA6qxdrkU5h8pCb zxs@C?R4CYIA_N5`SSFnFmicr*^f4F0a`GVa4==CJaVhi97f~hyQF8-XXBa{zqb>VQ zq#%<~h?L1_D2I@89q}_JWg%mdb>1apZ~C28_9i&tc{!k>ZY_@#^|i`Ig`8HiVoc78 zoYM-Kv3fgWO%Rk^VVSW`5yH!_6>piN>m5e$Gkul#ow|j5VYiZX4B5>ra+iUy%zwtu zqILsW)J|CDV!M@$ZpWb?95Olm$Q&0YWsZB1L%evLWw5izPX~gRA!}b>Zq~j&Nh~{{MeaWkmiYtmOFd$8+5|BZNeC?y8S-->)*u(+5ghMPlTKJR z#al?om`48cVEl!^XlwW|d(!hLE7sh8-bK?Q}VlE$v>PYKWi; zVnYwcL=#TV++aDYE;sXTFUB0e-0F;BM{AJ-UC!{!%>vw)G5ZN8mvDrfZI}+W%19iA zC+#`Iu^{ttl#=<_JU8-FjM#@e)_Fr2$iN({WMF>Il#p^X@5P#vG(#C1k^f)G1U<}@ z7{-V_h-JB8C}WdIqQ6By z0=M~eLn&&G_6UBa_W>-AdpP0bi{B<^l&23`Wt|TrWKD+QSi(fIM$T@}&4AyFF`>8< zWEx6Q^XMZRe-BPJ{+=w?{&r%O(|$FjgJ3Hm z<8ut9sI?BTo<`tQOc077C4^UZ;k7~#TVNr%ZrBfMH^j@;9ccfyJVOK(n%f64W;5Z` zkwDfnsT#%Ib@_%;Pz$=@e#T%qoOw-)8FJMN+9|)lPztI}%at)UqSTxjBG=d02n%uQ z4MQoYZ+%Uw(y@V8>tpnFjCMUCy9y13pkBy9xuOW|!>Ky*C}Y;)$}KXKvLCe@SnZN^ zunSce8zQLuU3(W})}rFF8A2vyO9btZU1BH&HO6x~GG-00rpwqY5UcTAC^Zxkqtu15 zs4g>z)s9&Ot7(~`6tzapD(Dbam2OrNt9m&r@jaVY-MJ`L-C1E>yk|LXQI&?nJ*ZRw z!qT`NRaJ%v3e!7!UBy8!!=%Oxk*g6cLogJlrV$dsT9rn6^)iqpxG&cjjwL~qwe_NbLeS4Aq&kAxjq=b%Giyr zEWQ3b%UWe~3EA0XI22^Kkm{Yyh3&??YFh2@SWUD!xa^t@rKq*n=HR?oRdAaPeZagD zTuc8`=xG+=)RPNh_2g#3i=WmlGJxbj;h$5c2wQ5GV_{Q8W~SOjDbZWx~90V2M;Xa2luh;MgR{m`Vt@dLi0- zl5%bV#3@KjY7CHZN72O;ET*$4!iYkSj%j~L^czN}FZMM)-lp@TGmWPfYwLGWtGXt zJEWcvIAvD7c}t{1Sl56?0@uN2wWON{M@VuIj$~1hx&#I_ld9G`!z39 z6mx29H$NUK$tl8v(YOq^TB*Ab&Pjf???Mz)O1G#Pj&PJmO>2)ox>Xtrf=xa#E>BJ- zgh)g_9GII75=p{Qw|X5IY>kBN7LHwqc%xQ zF4o!`mwzWg8x_L3&U_9ETIFps7%}SA=l9qk$wAP9Y2Eu~rS5$eT;AIa#Gru5gmsb> z1QRR|H6Y+rTDevss9#c;0RpE|vY*VL6u>=&;RZ}IjHCCik>tRM|7>%F0n>tN-hWsv zF>uj;Zj6wLt0V%>^|g@(LQphk-AaX^e#tuwkT#JX_mdeo#_uZFX~49LwBYU)lAO!9 zbJp)NV4NbYe`2}BTq5+(&fSI#8NW5yqS{lkML0zTe}hQOC7l*_ajIA??R z>m&q;0_H?2L2c0objWnk7Hd-(I93BLMeCRuBE1seROFyUPheS$j+qHlMqs!Qvljpu-h1t)(hjTgxx977F)ddi;Hn6|q9fqQ2+na4l&-z1L$--@9aWh~SiC~gb*~lV|Iu%CE%*UzMV4 zYw=wiEzvPBXrJzOMq$?AJ3UmYV`6Z}2uM{J7_B?UlOdPI_ zE~gX*24}xr6*>kb-s3u_C=3kFz7dr=<{+-s4kr}`_GH`EDjfq`a-`o0g<;m>jnz8l zut>Msjw{SknD$oJ=$HhNuC*mA42;2aQLT#?jwjYtb<&a1;GLUSVJyz3XhNjzJ>o zb9col3{0Qhj<)F-m_3hni&YqyK0EJk*Dm~#-jtn*^BM4ItnsEdI$?#^TDy_jr~9_zl{ z#lRZZVGCPZqwOn4q+2>{b1^rGK>zLB4n5}Hk zGFTR|*tWsNV0qvdEumgaF)r1K>s(AZp)Xa2c`+sUHeOomVqn91FrCHmYx_cofBJ)K zTudp(9K|B+wM>~v4|H4YVoHc0Ompt=g33iY+qTLD6~Qs!*y+VoLMa@-!o|SA)IW=b zi)%YWta$;Z=JI4~3R~}VM=T2m*D}?3bltVg#T4Kq&e`L|pyKA54oh7Oj6;8}XW`LW zIdxD#zg+BMU>q8JorOPZ83$tDpAB{~x%jCbW}(Yk<|f8-?;;li(~r;M16~ZwKFj?U zx|l3NKWb#*z*;$txDY4IcQL4%I3$n718W(WZ1RT8b1^X6bdQbmVqmg~>oM2GTqA;Q zs`H>1)QX?RG}{~(b(PSO)hxJG+Zzln)gx!Q7#Lk1ICt2Kfx+dReWr_n(WT@11TO{# zmkk|fxR{HCeq4XVixI^7kAqwY>?QrLvWQRZaA-#VX)Xfx691569t3P9;iT(S7jll! ziA^k=(k%p*j;6oC4x(OQXYnk}VX=~K1eT7RCy0iDwWFI93F&5F>DbxLsbOHr__~or zAi5b?F&h8YreR>o7?jRJ5Zw%{7}o~1YM2xp)n*nw=w@J}*zDh;VPJ#!*ukRu+zjjw zj*pu(3~Uhn($0D?us@{rYt%5wMCiE0$%5tFf)?VkSkkdULmh+GQkLccEy67Ejhh+< z0lfFbv3NMQAXpFL?{R3DBe+BV(0s`QS|SR~KL~Xi3ig4=ud!G&waXir_?27G zYEfwT8Z&$MRfL)QL#bIF5N7RX9xBx^(M0f_*_Pu0trLZ|nZ6|&3iI-3GIKp3%*ivK zDbg@eM7V9aGtUFsi0^IrZG{>Nv+h^&Sx}OD;Fxjezfz!KFzdd33yUXmGnjF2xjkRQ z>>_kbDT@ViGa(`^9dlj7?1bktf<^kcnXMv?=$WfwFdrUQ#)5R*4CcaRL1Fps?h^AIhAIqb$eu4ov{QD4ui^{6$N!Xuc0u%?0>w`1H#<$c>l8+W-XrM zU)8ek3AdyeQK)45xNGq zvXBHfbVw9hgS(#8P|Jwzu~VW4ggM`-$Bt{5r5N{@)mk};qEP*%kgQ=~%)Xm!nX2jQ z2rlcpk7s}hX)0HcW?rm>8Sl-mXiQqt@fKMA@uD`Hj7ai%wRI#4)b7&@vVGS&Za0T zlr0M7U-^Z4P=!S37Sy0!nV*ZNL{PVF9#jDldaZ9~6A)Ea9!9>tSBM7%Wp!{2n}Mhh zW*;$wH+wKpL|;G4W)~`hnZ?=HH+nER_@#``+OC0$L?LUuu)%}M!twpBgiRh)Q6-{K z@;ASA9#jSq{C}-uQv;PM6NS28{nvO<>9|@JHM6;aN>$+Aw5a{1qAi{t>PNvzbJ=Wn#xM#o;4+@U6A#qH#S0SkN zaYKSV7-)hoBrvsIWuUSrys*%NNyYE#2vgft1}gi}q4PZ$Xn8LsF_m0pppGZKJlBIc ziMzpZrje^mEAGy(&h}uS!M&c$lx>xPqMiKuOb-SgqG1V%8b*Z0ZrF4W2D;hvF-((I z<zkPx zEK6D`3N7pVwR=&}sd@$1GbLB1pik8Y_hMSGR#prVdInW9HCLwAib73L&t@;`0G=z8 z3f;>}){8>nq^?ZM)yjg#b-l?qf>>tGuWP+%F@@ndYdaHo_M7^1gPa_sQ6P(bm52Wt!*3t-~*QX`MSh zI~sS6rG0X|s5Q`$zPjWNosJcSOJ8+inxD4Ml|&dC)>7%}@*u9Tu%S$^(^AXvg!uIa zQ{3c%9TtTfzuv<%J1w=8*nIvJ+g|6wj$r(;fAYQT#X(i*J+p)`+^s> zkl1|3#ygqTC1b}$n=^i_FVoAkz0SjxF}sxMT{3o36iR0YGM!6H&4IMH_O~&uO2$r$ zwzmCm`!c;t%gx01`Ohq-SIO8J+~NOuFVm^C6tkKR3u#cH^P;UGWEf>ym6n24G=P47 ze3~Naf+!sSJb>v;T4)Mt(7#N41qq)8FqOxXYb3(&zCWYJ?cou8=KJ5p=%|}S=sP7-ji`h9FLO%2 z{W_|a2tyVXEme-QKop7=4PnZPXOGpyW_x)(DnrX>wn((qtbfJE6c$geg4jBa+2INg zD-~^xJH~Wm%7`adiX;6bwpESGst|3hai0W)>$oCfyYsWy7Byz7TC}yqeg<=>x4Z&y z6QWzxkf%Cu(G%`us)T31*NN@+Pxd#d!ASL@t!e)!cWlye*~HfMz3@6UP^d|?)rG&; zb-j+u!10b+U#`aRw2HR!^`km6MZmM)t2lqdf^*aW9H(f@2_9x!spBpZ+oS(TX_rG^ zV8Uq^QvUJiQXO`l*zW%8-g-GQ1vZ+7y??!jJ;6OYJwt4rN3Y12V@hU;w)_>NJF|!s zZ!U${2248ADo1k6!&h-+(f}3*;>{h$m-g<45;??Tp=c}F@NNK$;qc~;5g$7E=Y(cC z{9%dc(|q{n!IZ^dc#{dl=Kto>OgVO8xoFE=@}@tFeDLPti7jyAjw(53V6|wg+A%SZ z#UyxhF~rCA#N_x!*_ppy^l6Bn{Dh6g3wV=J#HZs+Gg1^c`)1LnEoH__9hoQJo7_!& zx{jKAs!eurZv%O1?x?PL24o~7=cTsE&gXEDsq;p4W&Uk%nW4m|+sK(oO|n~fr|8p^ zG;?G(=6UueLom+ge~GS@9kzQ#pW5hOp6|qb!rtUY9Q)vJwq(k#)B~bV=9X^;+nBf3 zn_NSDx{sV1S0g(x<3*pExTzz%Gasopxq|o#4}ZGqe4FeyOcZ_F&ae9PVS)KCz1hXY z$A9SL-NmxwFG=(%-aUD!|1kq{KJn@G?(Dcq+1-{R`c%Zteiw5@z06s}uh-~bcjw7Y zuCt;~-tJ#VV}7S6;pe*JCEE|%uC%df)g=t{%C;X~x`R2nya&p{*L^!b{nhHzxKB+H zuZg~mr&oXVbZ6#Z(zDIP&*$N>QzJ9l*n}om^lQtAoI3VlALjni(+=X-@v#qQM`gEf z16?TkwP#1o{>x(>Lk#Ik&;#F{5`MlZ5_Gxf*K|I7%DV$Qtv95LNe5fti0_uh<>3A> zMXVD$wCBbx`)))a?juHHxlTHCc=)X!SH)+yG8Md8?9iGWzv`#A9_her`1-M5BOQF~ zFMmEOJhkEo_>N-5nTXk+zijtmhH*WfMmpa1$R8)p+?I5s{ggNxqq&i^ZRVGMe1zP_ ztj_u!Qb;G?UVj+(-Gb1h>&+L%#bT%C>q(&tz8m+4UcRzPSHDjp=}hk$@Y-K~TDUdw zN=>%7R_s)BC2{M*pZ@aNfV-HUY1Gkv;@{zS17Cap+o09Eldcpumx*Dbe{=Dbq}{87 zzI*?*fxqh@TM+f#g^~dO`v(sD(`P?TU$SY}q13FhrWP?)3}`OPN#d6ojPm7T8#@u@GkKGHkZ=DmlO*S30ZcT1J~kY$-|Ra^fj zf0-;#+4A0j(s8<5JUQ=A@-ef0*oz(agVHy_WNBr%Kf5K!?drem`?$52R+ju;-A~)% zxlQfGY^y5A?610?w;Z-z-N)*kw%sl7fB(+i@Bd83|8~FsSuf_aop`^@+5azE{CY%uRJ2{mjC3vJYsJNo?Eq_*b%6-{+-$) zA2(y!eO28m^V)yebB5i28R7m{=GATbPx6zU@#=HR^RK%_{x0Qbo4S|U>Xy&m$;Ty2 zl%=^jd8<4^H=%tee<$iUqir#IuhO1vYy|TEp4-(=>UdQBN8RrE*Rz#rY)q=WRr{}d zFZUkoo%<6b)Gh9<@)jmnZCCe^8Mof({;b{Rsh={?P~M@)lJ~ zW>VBGsy;1i*Rr0?bZ}YsR<-d#l6L->q^UDqoxPrf4*x84`IpHA13kYNxp#_viai+r zd;goL*|yT&;)r#mXc%p4sJ+Y)=D23ky5FAdSnDVsA` zuYK_C)a9Xv)5>ZbXB|-nd(Ogzbkfl=FJAUyf{3CA&mJyzJu9k~v_+{Dd6UyOJp4m&j4{T>ur6kpnYOu#fd%mN>;hP=5_8;S) zjl>S+cHd{m|6^@@_8CW-0Uu(|anw0{uOD3f)0lzVz!%zm2afq^)xqoM9G4CFPcLpp2f64AU;GKVMJy&zdk!OHM*)Kc%s!nbC`OSx= z)^m2>hu{2p)2XVB2m825+s6Dl%sx6_a045LfqrjO}!U25Hhk&KxhSDNW?8qnwMSq`6)n4q^G%#vCo z>^={^9TZcNg}a@6pLc)%7jbv?sfZ7H^TBk9-46E6p!kaGxGI+F=&OvbNnY^oLzg6a zi{0m;cNZkr+;FtuUg<461GMej%1;KIm*5R{+kj72o-1}VI@oo_o;U7sWaB!w&;%b=Q@0<6KA{;m?8;YY4-_yV|rqBHSXt|baVk|Th@jN z{gWklsomCp!iKCmM-^_a-ja*#*B!Q+#Gu!XNbDlJ?X_u%H4Tn($95fC%Gm266Cagt zKJ)FiN52TU-s~vB4cA+41!K=Fd^0d!k~_z43w(3onKnnEV~38dw&yu)RdK&O7cH?f z>^{%^5?AHOckI@owYZtsib5w2kZ-lq>~z3H8d{j|*sDWtGBjuX`+fFEQm5E`?0w!} zmy_?( zT@l(~7lzF^osV0$_sJ6N1r8x4XsG(^bJ&F!gHj3{>cyh|ooXj|2n(mDJ-FLt5b zf&XkZ2fle^iNfaFZI66&pr+7q-ir;k*E;HPrha*1k%H#hg(rSVDRf-$BA42$@K6wP zf?t`hkXd%&<=~t`JVHE=YPr3_(dw{OM1B-FS3xr{){i18iX2zH*p*QG+8sjDPmj)0 z(5rUg(VvovBvnxR^Jb4pnu?ZRWf zT`YE#d65xJYVGdvy>GhEV~{$2cWnutXPzV20anPJ|4gk5O|%Qo%+D=xRC$rR7@7E0 zpK2F**e>+>DzQY;yR~1`UPhK}c#ZWXpVfG~@Y?#a5{JV}W)vgSrwn9AAd|6nVc>7+ zC60P8@&F?beHK{aBBSj>;Ae+Q@Lcpfq8NLrLnz<)Dm&M*%qY9?>c;X?2Rq5yamFz+ zCulJH{$z45zNes^QV09ywB$iXp8BS5o{QX#WB=wd zzS=T}=tU+nvTW^8cJ<0+gk2cAwyezI+4oV#<;{L9!^MSRl(X|H9HL{Iv`xL0B{A~y zFLAdfTh#OHxmT;x`q&A*K8*z0L7awBefVKw!5QqT1uQyE#g;l*<cWJR=Kc4Pv)VnOtob2F)sHm1bvbn|hWmFfwmifA>6e zrCsPhHLu-K?O5U=^AaPoe|`9bOXhOB&%?iFJ8;Wh=0RRzWX3=GCA-L_cA?+T8Rd=@ z9$Y%((!cM^9xd`H7h?uTzfUiBtnvUe7`xde$_hpY?d6dES7Di@OI5v8ad5p~a z^`Te|*@SEEU${CqdyoZ;EDY)&ts(L7?jKZGA!$(VBPwKM>AZpaHRMg)73P&zIM~$1 zO%^kGJ9y6X)LZYZ_x^FKZq@t8`^QQ` z2$K>O;iyMYj93Tu~2qXjo8Tan& z1Z41jziV~x?!9|RKhJ&Y*1c7?C^78)$?vy%_3G7Y{nl@IVQYe*8QPYeBx;##>(v^kBM zuMA7)?!@XHEK|~lU8ys6I5gvF#w?N&nVmJoL6)G6b0T$ydg@jGOcHmooI^iydt`|` zj(#ktHPkGkxXD;_M)BOfpQ`32)yQKU7w78a!qaNyR4h6hjg-3a?J3R@c@)ps__{i| z@U%$U;bMlK_qduumB_aW; zn6{%Fnh%JSwqu}K&D~1kOqgJPhURYHJHR5LcZ24`@*~B~#sc=Xp_dID>EQMWePCIg zT)fNR^ z&lk9VR|cnLI~4E2yK(T9dgFvc-a?EBZMoKYgoE25^v=uc4E0h~;ucZdnSOxmTaqvI zerM|CLfE>FTwwP!Ol~I-lK6|<##IVEm>gyvDZjhlu3$mV)jZtNahN$&FBZzOO(3H|Sv>y0u8 zw~E}V2d!n9lDbjo53Z;;E;_h19E4mJIAW_+vRWJTTiKTt&6Tp?2 zy&+MpL>0G5N}S!nS~IJbV&&IiHY8q??N(%-l=!-}3|HVXp}!7^)ede8xp^JKa77M)-?g}r@l9x|;m)~P2 z*xW*)zjwL8w6809J1B2s=gI_b0Um&j^#*T%C2uF?T_40Hwk>a-&IAp<(;?W?E#n4Fe?300*IAn`lyBmqsc+;rNooHI5cgEvxMI3WP{u@SQ7V< zo7>*n87g|F(A&>xFgVyPu83Ske`~X;xF0cID;mtR1=TbA$!+U$HbHNW(7SBK>e?ai z05;w!_1S5BYV8J2$6S+LA2ImcTAB}0V#y0!*4yAztTjp+V7aro!{ipUKb62u!Idn) z+~?qqK(k);UP%HsN$BrZMGVcs6;oc}qn!3^y%UB0XdyUxUZSq}7_THcr8VbK8=Qb0 z(q!xtxJ$9FOs*(wx07JZl>!+YVIICqkoBR`}a zNf0;&efWK(S?*;l&8Mij>{Y&uY|W#D{%Y9`ypd>nP}g#Xnzwg8n85vjalQSfc?zVs zQmJ`#+JOY{duSdVxg~czmc+C2n)~p@YHJ>eIi&oy(PWH@Yd%MAN0-6`?ps{*jyrN+ zWXZcg&C$u}+?Ch@Vc`c z#VieEL2jW z6VkZ!bqd60DQ$c-YRrg}H6Znx_qdjJf=!r$YogfFSrY50`RsFCn>)D}mNe+W*>SF( z+^z?>n{slELVsXa1lu`FUIV$=8S9fcL+BZ^X%M%#2-{ab#Ig-rQX@hiQXj>p&*Gxw zDtfO;lGh;g-W5?}ew=Hh-r~nBv$Q3zUg(b%M~sDWE=F#1$5l!4>V)2La|9bhOK%go z>99ImlGIh#3O#Ll1O|AEYnInBXn7J>jaL({BR9_7Ab0tX+`c*c4_mm`E=RF>wB+3+ zSJI8UHz#)u&rwNKo}Q|a`ZKxwPD_%wD!hvGqu63v@^14wa^)V+Deo%o$KnXKm=<>j z@=~HBQx_)*yn-#lNQ^9+-<9UO)Li@i!X)sr8d*G{RopM+DqgY9HLUBY5c(^)bF1T= zMy}+s`APE1apz7%4W1Avd7yS>%}b)NW$d&wYOFOWTHy^W8#Cu7aTkT2xjbsDGdZ3a zHyZlXv^hx}ww9?Aqw=(vT4}3qGW5vjvy!;;LLVB5VoR%9_l>yCEl$k!q>kL8I^|)T+Hrdn8)VhKA4D@ZOe@-Ohw9``3BApVD7MFn z<0--&L(fT@n#7&t^AR<+nHrcWIgQB2laes(WIv7= zM!t#hKtit_z%8t^amUcfwFdUuO4vA1T9x+Nge0sOuW8thcbM3C3M+rXerqU^M}_`; zxnb-ysfie!DW#9&Ux_-8V4qtWHFlfWBw($Iy9yVF{czXfDE8v2ZIe0J_h;D$rIyq~ znCkaOu@hI^6mmN|-f_vpzPaO$sIkxFrjq-q?QIu#KH7nxc!(2 zRz|U3S9+(DTaacS`&#Y8?mBHj)YxxwIplJ1-jLicg?NB401lYkkKnX%_L;LKZm-bB zMWs&yTQ`H$NOflJys>+PHZqExziQh|-mvJ1m`f-2-@`GO4x8L8$qliO#Vw_~@Nxln z#N=ol&?6rkE_pkJ{t-qU4~p5kIgf*3b{}3EKCCJSsSO1PW%y(W!fzaQnH;iK@ zH;-K1+YK&x*wGKDGhjzh^5#qKt$G)?UFdIts;J6q5l~iJu}EHBv<>U zavyNE5BvT%@%)@Lxkcn^-mqO3tp3{qn^uhhi-VH4m|S(AYM0(T?3JqxgJ<~En=9q@ zx$fd%9q0pj^aLTdM7Hk@+p)sZy9s*HzEYE0O0Mor<=Ei74p8eXF ztO$PsM}GwBT`sw|uDG}js-MrB+zQDJsC03#Ht@b(h;w+gMBY)Z6wdao6Z+ec#yGbU zlb#;=u)-y8EyqtH?z^DtB?PN&GX~2y2MqR!eT^B^S3EFDr2Tg4eqS*P(r> zd^eoUTP3tF4cJT+wU*R(Y=d^h?8xDj$SuJfy2Fs3K5XA+a>e)Cj!PB|J4weP1LvnoULLu#U6l)qvwg6W z+<(-7ea7N&U(37kak3;SdJ*0k=NjYO7T(vZ&+zd~n>9kDDL z_LLW|Ho}gh^ll?p*N@YOMJT=Vu^6pu#EGrqwv&sz&*>dhw?9n9%1 zk%Qf35XK!RI&uZ*7cHB!Wg<35XxWCrX_M3rQZqUCCQ`G7Hq(GjNKrf4$1BskFgAP2VPR6-UUJnv`MOM$2TM=S>PG3UMS1(kMgGFqZz4Bc=zqbz zxM9jGWbX}Do=8r$(}Xq{XRRhxL@KAyC2Xod;&|!vMV&>|xh~BM)6>>9#<~6E_TqRm zxoxl+wcXnYYn8gz1LR8X=MpFJ8YbhpC~1VPN^u9tm3QNkDUpM1iQ}D%BDU`kxw@CQ z1WV*#Tk3^z%taQt!{nkL?s9PxFwbIq{^H=YZ+5z{@k0CBrjAfrZmJiir{*@o@};yM zCATeOhf5snR2+GVDkfEu#ieWFHNZ-hRT6`JY=XRoW8^M(<3c!*8-qT%+=NB;M9Cc| zSNGa>7dM*EcC+-OW1o=I25)m=qlET}O`RZ>!$p6h(jPEy-@pc7vea2Zotx9VFg8KpNbV%L;*MKf;=ac-R{S$IC{rbOid=a&?p+eqe#iTNOM3V58ctzzq1V5<*~P&s z_GIVAGZ(sZzFWSB4j+XY9O- zP2i7`J4^1|0~=l9z80W*9emju`*((v(~y~4ac}V-3Wt^qS*Rp)_71U z;+g80iCtv7K3wfV{UWpv8(|fquuG(-uJWMnVp?j1O^8%E8ns zN<(6@+9;+Gv~H#3>SXKYd0-KtjcSAw2BptCvy*DO%Bu`5FnOx+hDIVe~F@a$K zZ;5l-sL3AKWei}8xnsp)zQCP0hR22J$9%7r$_ zqV7=IM!2T9AHqvQ+ZcnLkizZ~yWDw#C#HA49D||I;}kMbT=06Ys(AJ*YEy55{2TED*Gr!*+cs%Nx_PF2!Kxv^edZ?>wZP z7TPVY_?ee|`5v=c)6NKv%V!=j#->W%5Cc`=w( z3k10o`hN;g;?FiU{o#;Qz)&ptskbhEp3wAR7!hWIn^Xz!9KibV=z4z;K&119{tDMSAgRO zJSS%)#VdHi57WEj3dl{TwDHO@C|M_tDaOZOo+ib6(B2Qz+v5tzs;k7m5l8a+Uyt{^+-Y5 zmHYiL{r)RWlA{x*_L*|hN|v@mXrIMk=q^AcE~Fhx_rvt`;wH(>qO?8{&mJidTAvuq z;RRwAq-h%(JgI!lxJ{CpO`Y{m_+k1J^-VCE7vNwAI&+lUTJjZahlwKwlQ}6!tIYDl z^eoH`%wD!aF)+d@IH;>ZZQm%K&ZQClew_-5C7l?W6 zk9>Lox^-?5+O{T1&8N=j^M08A9M%e#A&s1ttK0{Zb>`u{t*8Z*R`akQraxTM1e1ON zj+<}=lkAWjXA3uDzZru;pA@8BzRwTS?}c%T+#=b#$^|mHZJUHPEC#bbDM&lj#t+lm zoNAKXVoG~Yxn3qq`$=f;#c()KAQn^FfmAeynzl{3 zg(mCVAnz5aCDa-1>4)hr;$B@hIc+8VCY}ANeqN9Dj-r-QTEi25nEnLANg<7#Ht({h zv~@z8qo`$+R@21~)4Sl#*FYLMZ8Ci|-ECVdw8@HEPH9#5`(gV1RZTduD8TU?q>ZLW zr(4<@ygL-Ng3>DQ^~3azcuMOajhyz)B~NLqVdz!VN=m!b&JWYuU2MYfM}Z)xeR0uK z8jjOGSJWy>JD2H)>6zyclL=|$w86>=I(dZP*ln<)R#RGOh99P9;2jfzG;-RY3!ZJm z@!TLqt)a9t8Ge|aai$4}E(JI=gtUJtNABdd;TZ2nds;rJZi=hw15fnj0aFoc8fqPiaeq_D@Bvr?k=xKTP&p4ARJHpO$(`TO#iQ zsST8NA=3}j+hDcTWOCXUXFR3l$`L|pBc)x;^uzQ_jJIZ!(?-yP*ZnLk7TO3!{X}UM z?fo#leMJ)vj0yxfZR9CWX^U{otEf$scCC{irr(FH#7#&er%gHODQzKEsfyZ6X?4Hx z!}Q5< zU+N_|4MWxz%GyCEWw)$(LMv#pseZmxKSgb2`;VmhVS4Ie_*p?GIqk2&efgq=SR_rMahp|HywVl!`+WBF6Ta2P#AdQ^%J$50_R+%+!+nR%1JVj8ZS!GIX)}ekxk*}i!1VQH z`jXkf))%MxL3(O2^uZEDPWzV%RZ5;EXJD38)J{sP$n?YX%!(V>w@Mt~4>KXOzc$O4%BrEL0~ED{Aq8$x2&Kfn<$z-T z;LH4;%t6X2-svd^P5{`c9#qU1zRVYjL91&z`9XRowE7_27oaV7hmSU0pxK8M^m~5@ z?LGtt2lV)fR6i<3q+(NfSYlc>joR*R8jqL>En86>GM)V~y>mk|q#a@N*E6EROB#-B z*EPYGC#kmrsJ|!*IxnXCV|sd7Go&4*&hguQbl!m(rU{n8qcDBy(Z~HU{c*e=j*4PR z+qKO{+HIljqR~%Mg90cleM#|_cl5{fj=1T?;uv)<+v=keN6E{Y;3q1nz5x_oN~ECk zJp3-aZvlc)(#|)7J5HSww)kK;n4Um0qojHSP(2idSJ=KhA6YkrR@e+#C#Z2~0EH#S z2~cQ%!vp@9{s2bW2~k368#nvtydkuWP4ID*)CU372a1Bus&@XE-ma<{(oRz6;!Qp} zaa_KbhD}NJ4WMwJNI_>=nm?weVRWB_KMQnD`^iUVlhCHp{3)py1E?1j1)Zl-{jiwO zPBlZ=X{sEz(MKhyaZRwLO6utV>M2EGXcTYok%cpStdUNOGt~H90QDX1t&-{i`ONjdP0X7C@m_NkQYmH9i^}gm$nQ)Ol+BHh}t;R$@u@ z2%s>jkb=e&t9>-q3+;F_s0-9MK7hjHe?d__1E`*g!sBs1)gRMSFEmSOXxWded|C$Q ziXTapv1P9ZP_NT2UB+pkvb8^^x2|k9Ic?EOADy+BdPrTQ&Vd2cK-#!3QfF-ke@yRy zbUBLC@>clhggZwbsY}#3G=Rdieo0Xe2T%_y3O##xxsOJ;h8${^vOtXvphlA_XS;d^ zP`KyipwJg(Y5tg=hI_6!ZQ(MXmR-k5J*f)W{{a;GzXB9=)_3&B^p5q-Ca2{u_0b8J zmF=W1Q)hMng?Z(&qMiz%Fk6#C&z3In(O4z4(q@aA9YD<_Rmpbs51{(B1GZtRl@H)6*)NO-@_7*r#Q1-dRfO zDs}!Nfcgi|AS4CnIq$U_WId!P+@&Lne5gvH9d4GgKuryxrjn{+yZ#(N{fVa;RqTs~ z4*rKw+M$1%;MHv-~kV3)@Gx)s?4Lb!fkKX0}he;Ei^s*`k&OP|M=f=K<8`r0UtO7Xv74 zN$NqNUuzQba}9Si@&dvls+&zt+d0eel@o)f9Oi!|O`8xvO^8$d1E~I_8rc5M0aRz6 zyfv_2OK1AD>zwRYlUf--t!%brjR>Gd#Hm*Us8>lv*sqcH{+Qk#Pf|p3TJa2@_QMwt zK+TO) zg9E6+ajItk)svLL_BUkuV|pgun1Hk0P1B@&Ai5r0aQnxZ{v@lotxrwjU_@m z+iX!=0w}DtOj$n$P}tv_)Q|w`(>V3V0P2sVZtxnr22gm1-vEVcteWg|jqnD>ST(7_ z0ID!faq61@>gzZ)Fo1d|PCXMqJwxgyd!l^+)t(*$H`x;xCi(QlaiPJ)ZBjb| zs2y=?UH~;OPGtvB*>UQf017=}w(IEt>S(l))Jq?zGTawdGP4MZl zVrc_6sqFz2Y%eCYFo43k$fQOGPwQ8KJKUEJSQlYBZqK*Yn$Kur1019tzQ{$2V3QxC5 zO$nf;#HkSh)bKd<_WzM!wbEZk5)R_DO8bIM5nX*;{P%Gk8P5_k?r@jxMzK>Hw0x0a+%yzvK zKn;vjuLe-B#i`!~P*26F&H+^CIMpVA$|Us*pS9{SKKBt3IyH!%Qq=hX3g-YObvS^+ ze$Axv1E~BswI+bVY-q}wA3)8IQ&R#cJh!H-kpa}mI5jkY8XBkm7C>QUH8u7Rps-sr zsn-H1j2x4CA%J=zPIV8Uy2q)`0TjlUsj+PUh4BR!8IG@p(LUqrpwO`H&=hqgfU1mB zr2!O9U`$!X0Tiq@Cbd6+DvDD(1E`&GYGVMkF;1-rpfG=#?OGH-Es9e!11RjKOh#%Q+wl>iEsq!1K#`6l&rfGoTfOzNrsLh8`~Sr5lGKKx%uWd+E}ifil`Kz00o zJ=Gzg^_Vf$eH`U8UmoCWDrd|8pWr&y?hh?gziR*A9y{TlkN?UiGTa>SUpec9o9BXN zR(sA2qU`xM+*}^+y%6pl6YhN#?%fyeJsR$v9Pa%dZmke*-4SjL6K*}UB4B2<*H+=y zY2ns<;nt7g)}rCowc*ytEv~oauCK+lyQISH1H$bo!tGDO?RCQKo5JnE!tLk6?G3~2 zGsEp!!|i{=?WM!*%Y*j#I5#kNeJ$DxXhqUmv^_{FoE;;aO(dLsC7i8haDe5+w)=#$ zA%(L?g|lsivy+9hxrMUdg|P-oi*1XxMM*vFZ(7uBJ7qYVXE^(5I9qHuyKXoeaX5Q( zINNmyJ9rqIx;&|9(LOJ!aJK$%=LX@ zqkt19`;05xd0Du#w{YipL1%&|ty8ryCyeq;w#D;GNrgN6{K)@MQ$vrLr@-OPWW$~B zhC3?`I(NRP+JzC;;u*F)&2RBMTv9=2>u<-;*2A6Chdc8R=RXj_7XdxdqF;mb@MzIT zLQ>)UEyDS31o?q1j{9yD26$x@#;K|Rs!CDli6CE>F7$Pg{yZ)E%}DCg0KXaA2Pep% z2j^v`KTnWvP+pum8=qHv8Z`YcJmtRVlVm*QShLB3STA7FZKO$+dnvi-7xe7v^A zWrgq;!!@?(dqxjCtwldJNrm%?n;qcoX8YIe4WM>g{&jkF7|%akWeu$0oARuF2p^mN@WsT0_v}=y=d%sL;xAftqO9pZw!F zH5vAKRXza$lnf!2)ZqXM?{YJ=^`VwSThXq61W+04BDRMA3?2QSsXsJ8yZ7|iGvCXIsb~mB{=8cFK3?A8G4C=(~iBur0j z^wEe^O=c1%44tt7)L5o!Iz^4A0w@^4Lu8u5p2Vc4{N&RvWUZP)Dr^p{lbnb9Z1RzY zyjy+ZdAC~3&Q*e|)T`QV_7R5CSZ!gZ44*j+Eo(6gS=gLr;WDHh=Y86n=W_wbwziMi z*1~3PL;J^X@sWiraG1xAht4S%HbWhBF5T**6WQ*TGLK!@+<7t+-@mr`NJG9pGZP#$@h_loQZ#l=S*A|K*`LChp96} zo<=5z43qV-r4*9KnH*B)tPDs;`AR%zWlM=I53>0!=E#&8Ia|!Oi3@BYYp2W*`ukm; z9X~;6GW(}wa($UxW(eIcTb;GrQxLM2s(hp&Q=7JsCiR%X+@><;>T7#E6(TdMnR8WA zt9&UGMP~liLOEpov%z*Tv%!YVEsF~Xk*PLZ-djoe=g`ep*>Pn)-rp5^_6D-{{?3uL zcZM%DOHq5+8zC|Uhs`^T-UyM!I9yKTaQT&GM&}lDHHXjptk-ou=-IU>THLu#WquBq zlbTeR4AMkjXntM(})&jDYOoOdlK1Ri1Oiv&)>h%F!3svW|LUvoU9w zna^9uqmI0oR?3FYOD8;4BC~%lm9sxw{(n+|)dV7{PQYgN?Sc|dopx;k zQbCmty4I;02Ae3Y#kvWk^lSH>@>Gg~@m3Xva5WuBg{cs+k!>4s+Eb}rZ-P`{^@=i8 zmtq5@`Pahu%&wTBUuaY6sdR~mSJseqItKe;td?R`^9ZW?(Hiwl$jKTaAD#77YS$1U zrJqVa=ZP&7@ropCd8HruVOD(;QmAio-c#Cg5wDgKuJ#J4FqK?Zvu!Qbg&`HD%FHTC zYq4exsW26AWIdhFFM4(e>giyFSXFoQi!E2@k+pulzT_zlwSJH-)2axhS9UD-#MYoR z`DMk0t3yOeuga?Ml(rVLeU++Lw2b{W$q&;e*_D*SR9sq0X|peTDn;tuplVQ$ zo7JF}P+E(%rAX<~7p{0J-GE}esJ+5URk@VbV%@87HMAB}=l+0-Tt%uPSGf9KizqEb zwXhaykD;;~YmY6Yw9EHi_w0|&LcbSpW>xVFZ>))am{k{T0i}hgvNj)F%Qf3b>CxwE zJlnQK=+EKJ#tLxrC~cb`X4R8xp*G!LRBgJs)On)iN_Vnu-WPSAP1_c)n>UBjf~xT0 zDOJ__(7V&?J*90I+B8+uucZoskJqaTfwR~j>l-|!<;Uv{hO1pTlUGn2Pzmvfs)QJ> zuHpF#)W%jpz$ z*1uS0JCrL@MKy6tTP(dfjnZl#Z}wEWSIAPNs^BN2wOAHZmKYsy!&8`DVsr|1hA3Dn z%b$+9=_$=Fe>z!SLCeKdC$mgdi=|n^m3f^=ogs?G$`Z8GZhKw$HWcO0~~y7GYV#14yi@m^U9c!?jTEm5U_W$ocNJ!K(R zPhUsrVc!D9ZBHpGOsV71XncsO$>B;ck7EC~RL%JZ>~2vR`r#HTLlX;AXj+xGZmDdw zRRsGXpCZ@~T~Xz&!W6c-n(JN7 zUYo0m-Va9-08stLdL$l0!s320lvXRiBS}gx>om>-D-(LBGK>3#(pGq5+KQMg`ZgR% zpFj;9?;-4>d`IO=ouu^3ohNu;mtYn{!Fa{pp|p+On6@!yY?sn*lNvP9Lm1M34~iMv z;*@^9+awRH0&lYG7I%wn+nYYwgS(8)=-!yIRm%IBT1RM%eRSLal zgT>vX){^#9J-91EZ(kBK@}#^Q)H=@_(WIKG^K#ee9$1yo zAHtKoNlI%Xm!Fp7!CgZ=q^&XICzFHL@4PWByV0bOoE7i0SAO)6b{!j>x|p$1N<$J_ zJPG&B@ZhRdk8F^f0Zv=vjcM~^#(I-N+di4;Aq@T=?>3rLlsapEH_HQqC&!~#W5zlu zEkf>S=4=nH4k@?y#f-Hk2d%TbF>PX_Ni_&i1Lk-LgImR$(U`GD3ah8m%7^EAa1H8q zt(IIJxt(eAJh%vMSANV`WpYqE${W+ZYc#1^N{hTY-$Pnd=&wd%#!4xzhFn?a1sC~g$J5jD-O#0 z1U&fUM!g?%kr-G9!AbERzm&WxX2+&7rHcHYH01g z7pBd*gQL$JNu4G&WRpwSw?Z3o8)Y!3OX?Iap|1C47dBGpz3XnHl;<>)L)*%`<+-@; z@P;nG73WU!8V+~d;^MxCY4PySC^I@$$}1ta0oDAHAHW|l)Hd8i(b6fBJHe}%=!I#M zZlLVyWJw(-_0cw$uu&+kjYNsYq&TJ5y|&$jjTZWAbvAd5ZL8>(@8ZVrT5&LCOqBA9 z$(3{}aB*XCK!J6EW=xRWQF7ZdcDT54xUbt9jq#E?LMk_Prw7HELBr=<6dneqWxFX< z?8J-w7YC(BKiuUKHbLkgMs4m8mDcs#?c#8{-?O$6IVP3XgXGG)?QwCF*d>j~J*v0^ z*6M3rYUJe7FosZC%3olJ{LCyv*q4KZzk zFm1dMrwRqA!9^}%sHZy^m0?X;`=~SWm;Ekmy3qd;X+#XC(z%yhb18g@pUGYoxM+EEvW)7rEp1_I=iydCVlnO>MS(?Eckq6$c57rTVb!T2;#B|}m9 zY+Lk`V=ip2&_BlbY%sa)?t)9$3RL1W zV4+vkT2eohxu}&w`@uk+I7O`?_3cF$g>$rT4Y<53YBj0hms}Li$A%lQs4Hp}sn7X4 zi7VVSLi^l+Gq|Ew^4>>3Wdjm1oO=z%7-ow(W3Q0hN4%UwZk^CS0>|rD9Qro$cXm!9 zw_fOfj~H=oIa;Sj-e*@QayW&09~^s~@|H>N9p1V`ZlloO0mqw7ZYjCC{u~O4+)r2u z*BS6jR@@TF_2bY=->RoB6XncM=&y~8OXk=urT!F6zQAUB^} zf&~A zTp1{+-IDg6Xop``e)!cQ&aCO5rtO+-wKHs4lqVUQD$0EGf92JcT6IM1B|bu z@P$;=3{s=`#!962;8~0se05RdkEF)&{g+7X#d94+EpSEUfYPU?^0k|Y6=E)#8ioI% z;--_EpT<{xB3FdDX~7sEnO zWx&v=gpH-J+Wr&c-z)YQo*X=3T;WpX7|diTM&x7G?r;h_j)@EdmOe>U_(of)R@Ok#~BXq5#nE<++q*JzZW4D9{kJR%%bDVab0F`^ zVv$0pR@i+z?TEr6W>Ffs6OYYL;>v~o*zqV#Vixxcxr&z=n(CAXo9;{KmDO?XF1ecb z7%1xGVAFlCCJJMhCGQTnl<3G*Mo~J!D*jT%jL}8$^xEtgyzs4{#X8%*nyx*>O_@ zMkR}DBvV_L?@&%c*Y6d5GiTnF|uaF$?Mddm&G6z zCkSI;R$c`58B6mua;KkR0E&}?G4Pqw5m;y}u8Lgs2V0Z4TUcULM_{9|xGUtQq%$kwGr*LvG;N71eflbBYE|WXkOWj8MweSmOEcD>yI9EZgcF2w- zc`4$P+6c@lmbgpAa@+1oz%cK$)fPrz1+lP;#Ex`hK!M#lnAEx*jKB(FQDvm62GGCX zreIR*f7OT@H8A6Z^S+66Y-KUjInX@90l&^LKk^k;J0||?zm&bjL~uK zEV=SNMF|`XVSUPPV?rJk=SrzLGCJ)*0{A@!%jn20Oz}U&$upt>FC^(_Zfk~#tMm4o zcx8Ma=T1>`*{g>V1dhVAQFa5bs*!Q-q|`hronF|sz%e2v{f7uLxf$Qa$r5Vb`7r&k zZ4id3hj+q9*!U*S9jE5X0mTU%3{wLtVf`_(XIo0UliWm_JsNtv^vc-emy=ZeW4 zd6F*5HV13clSd*jh>eJIN2s^v6S@`K9IQ#7)PR#8sj+#O@>WYvV4H)L=)Tnruy*ky z#T_D7`U;(QZ4OqVS4taT#j?2r?7@cb(&$NRgVS;EzH5lUK4p{pDKWn*on&ngwwu@1~gUzPHyaw2mY;G^P)31~zaIo3Da=HOF zC7as=E+sOd&7}lzj!0=UKGFcIl1=WS#6wTh=aV0*zhP;4`d|aBMK-sS5^KLoqi3ZJ z&PU&TRf}L52f0JkV{|=QnE=9u^5~9wSb}Vc`P5wX5xpjDfv}-`RMh|*kIikTymb#$ zC2+8fJg}}FHXfVXN_m&wx|YDfHuBb`2H0+FZVTnj?@*nS2?yxlQbu=S3}}#NK|g7YGkjM68o@QZHe&I?E{It0oD_+R!W?AFYl-gt`jNu&Vxkdd#fa_ zp~MUQ>9J;murl<&P!CIqEpZjO6%TUI*&M6~53Z<(6~yLNlDqtO4q}^w_2BQ9>tO}4 zx#i?GKf;H?=3o_gWOF^N7B;sGT*|f2)A%&n;1-O-&#%?PZef#4D6#O_I|(56`_C5E z!yaLCx!~?;nB11`P`1SFBBkxbhI&{eY;qBGpME2O!$$s%)AcYk*w{i*hgeVT!4VE| z*q=YJw9Yu`z~)n0<)E}|2eu2>F{rX0E@Za2dAN7jGVOilYX`U+i-o;)upijm94u7s zV*Z-e;adl|7o%nxu7i`fH5zCgz^3WKz>yAcpU?+hsDov|=IDQk%}`#q?;RX=*4;MO zVb^bS^uWA{*muv@pu$=Eny!B_h31P6$n?z_;u z*TGE^cMZ%V`Oi*raM;8?n_r7vxGfK{k_PTyWJcB$2UsFfvSvo=3@&!8HsbUJ10vafdplD~*rEW-P6lWKw3p!O}U#Xj4~-66N;32r_u4ja!N>uRxq zc5=6OXgKv&9I7EY*w9QJ;{^rr*i*hk=L+keE(!EwxLe4PSn7b z(`T6j#OAF}Q4O}CPOgUB))!VdxEi6qu(bx8ODA`onxiYbtKU|0Pn}5Vz9L$S?WL2f zBH1wep*0SX4I<^C*$uVWI6BFz(4A59TJjN*PzsHwr$(MeX4tQysk`zh&(>fo=OizPY9r;`kT%=or;5A{nM2N1V>9O@&yhSoG&A2p-o+d=^n5k8 zb5639y0s z@~9+7-lyhIk~t#fzL6ET3~rp9G7poin$TIz$RuZol+F{XZps~zlRQYWX4Y?y$loV9 zOQigERt>U$G#47X2T)4<_uj3>ONk-TIUcIv@bDl`~{d(-jZW`Yu zkbA{-BW2HkH1)P5xe)Kg0lTrb#xBMwb2rJ7L780ItNvV!J3gqS4#q?Ey5zjc?{<>B zG_sR=^YgPMBBj&FOEq$b;*?oH@_J4;wU!{cOr&(nxqjVHk}Qea$wk)o=AvEc#+vUB zYa`d?-oq(ztGH&Q6n)tGQk=wEuJwmS*KjtTC^L`b`Rq<=MaJ*e;$CK-zb1DTPMMoX zR?mL2GA?sH`h9lwHJnu^%G?ODRdnlHX=;hdFMkrP(%#w{y(aezPO0lf6;?RI9HBd# zF~1HwU4;|oM5${@UZ43pwPNOXScCm;=5-j3lgL#h^WSY%IBs@iB$%ptV&54km0<+|I+&JkCPRwuF_iVHo0Nk03;6&yY$s-Fp=dc#`% zt=8OJ^SfiBRqMCbHeAI?U?Tb>^&1z)KmN5zo)9UIkH2t5E|s0ir(rnX#X#9Ps3UC( zlyXY6>Nsde^r~D0JJ~6izHjle`livILADe_s&8)16}b|2vXig`xq<0+{1Y^$5XA!I ziSZXIahR6a)bZGU$BdN1p(r?^$TE@gz|g`<9DgN}Jd!YsR`nY{XgkqF%SEfq4>s0U z$_1=bKTi@Gj8@kc^+}_Ni-IcA);^1_Rm!EQljSJ^UbSUYUYus4STwvarR=g?oH|L` z_3`+h9PJ;PDB^)l{N;fUWZFi}IadUQl#^s-ziI;9Rr+2wLb&F)5F_ngOqIWQ;UmSnD948w5 zr@g!zE*i<6pYeJ+&Hw!J9+Cd~jPvCN*B*&zglOgBcRKKVVz_|$ufsd~;>vQlXiG%v z#U-OvUEYv~c{DOyz_$1Bki0shT&~&@(He2lXmxPRi)lRN87{uZ?SFC1!P`cqT#+TR z*T|O5f4d`3qIh1_s^i=9%WfJ~Fy$w=_6pgC{1H#>knA|o>ZuX=j2wmeJ(;Z#Wk##g zS#P!HQ685Et=hje3rBW!at)Q(2BefT?y1ZF`h_%}OY+TsPulZe=hxNajXu{U`MkJb zv?^cvkB4}8$^+I`o&T}4yc+wWSuVCzoHtq>p8BT_JOCapW{XxG{xtRQRlKXY@JVdN zDX{l6>>Ay>%?Sz57x%R3J$hHeW$DnDOdls*otOP$x^iS$3|s39*?H9$rHfrMeN>z? z?y1_C-3tNaY{)XuJ+n7fos%n;#C9AKXN`NVZTkA9*2;rpmAI$%OJ8rgc2;_zB}*7=xYfeXe&2!1(i7rW9e)4W!ov+G zq#sN&z8tN&Yy3KTa?K9|9!up&Diyy@eQdxFYfeUwOP`bE-^fM3-!OhvwQKe#f5_s3 zzfAlp>kpsI-c?mBeL#}=1sLN|<5y){rv2;Xj@+J9ieGhn`CrqvlpT>i8Oi({%p*0% zug-4C`Sg!nmV>Vrzv}YGPjj}MJtTc3l6k%*uNc3sD$M=nooCu}_Y@VsZvV_X-{cln z9h6=P$-glbtJyN+*XQ%+We<3|Z61E(rucQ+rw3%u%RgTv{REPKV*>0rr;UFr+r4DW zKVN+$Z6|)^-_jm=^`E12cbDzM6z{&vJS;CZ{=NR>_Ju$Ev(Mw1`^9(S-!dQX^Uog^ zZa=Bj?~$kF$!d5qU1$}`7{ z@;CkOD;CTeKRSE(pg{u%_IbV6^Uptf_qW~=b!KOdY^#< z2MrpYJ$n4C1uHh?7ac!S*-&I$zGU-xi4?W2AYK;QqEi95W`EwsiNm8!%(fFs;e% zZ*#F6Tw+>3-QUtax7oDBxxcmg!1_khwg0L8FONTOX+5;xYQNDsYY%E!^3Q!5|72gvJz#h-%gX) zX@8T*CI3rZGk=GFc-`spPEeLphW&3_g8JV2oA)E!AEzw!v-NwEFtkz=f8Lb7VZAY`bJ$hST3CHzt&%Cl`ovmK ztJSqyXLAj%*51}?{64ul&yp*3uGRVHL9F3_gSC7Q?JbXW{{Pbcr`ij+|F%t3zy#Dgt z=Ua9A?Qb8xb)S}TLueVd?rZh%Z-3kE?(_f0%dfw|E$;}m<=vcLbfUB};=kpUJM-%E zM+(+*Xa4v8uRq_db6dGHZ`--s^RM^+J9p-53yz$x_TQOH+d!o3(AK3n-wzoG+d!tY z4P?SLFmTBCIZL-5DvS8p227hoRq^)RDI@;Y_o)Zdq)j62!KeEEZN!w^?ZwyqY!apo zRjBjbj(MuJ(4c1I_ZzR_R*N7doFrgbxg}qS;4&Qcb;iyT8`R1^G^1>f--N*k!_v& zcA&#LHSpWqA|LCNY5zLEWyX-#e`DIee)IZ}8C%YK+rLcv+QqFogI<>I6|k?}`|_aa zTQ7Rs*G&7}wLP;3_m11|dJmqx`osTY4O4#FSfZQdDu_eyf|cGv5);! zT4zgFj7YG~j#yFZZJjkOzmpOyzmxndztXCFdg;&?TF0%*tzQ_r)W@o-(Si|2KonPZ^4Of=-ysuw&SlVCyI?u<_u59?5vR`u8@L$T_ z_*qHUe=PSoE0L!#^#$KJPhnp8x}e_s6h@x!oSU6+zBBur_xX-GH!8?> zoEv>r;B#&y&!a9b80a{UdS?O5xL)T`@|^3?*q)AauAXBLd7pEc$7e$yi65UmGSuhz zOdiTz$sOo8lpCn$dLGKjqrMBX`n0x>`dasydBN+bPo5x_jPHdA7G*EQ31V8W@g-g- zi1J+W(8%9e)?b`U{%+(UuX9Ox)_LHY?vAt0?%&`9b33fsZ z;pzZK1GlS#bf2hStN$s@;XaYpZ%w`1eZq9fcu)==4k5T?Jm}?;A^kkyJL2;5sCD~! znBF5061~QHuk-L4Q+{YOlKjwSxctzRXWMsKiJooWd3d%dZ@Ixq z-g2LKc*{u-ynSycd*JPJd*CUrz70uUeH%Qy`bD(Qv+Wn> z-QJwiKeTj6n*{&RHlLOz`-e)0)6*YiDF1Oj?r=EG&_6nz>~JbwSkHXidPiLKc+pC< z{`gF?3#;^sEgh7RR0h#D&E4_EmxxIHkIhX9cD?NfQ zeUX{u5uEusMrWc&u=Fyn%x<6LW!yfyGTF;mdM;lZbuYb#)RZ_LE=DQ$j=GlYxvadQ zC%L?#C%L?#m5245E^8bz@tW^4$K_#ddTl?RQve&y0r{ZtrFbwBU- zFJ0d6ejfFcT^{wyOMj`$OMi*WOFyjNf7m#J@R14Oqa9@Y#HJqHh+0F4@e`Yp<0n)+ z#v97nn~&poR3FseaK&S&2#;Z|2#;Y&5gu05$ZS{C$eg685#KnJg2Xr!71xq-_We}l z6|Ne2j1dgy5+hrzSefzddCAsg9~CL>$0x%2TM2uy~Qo#_Y2z9k4L>xUS_Q}QF#FJ_EdJCOjwkGkFDq!=|B^;Yz}y4Bw|I^VNO!~rHbh6L)Ug&H6l70f2rK<)&292 z5oJW@HTlbmR8gm|b7^c#MARu`M5`T~jAEVV5yd($F^W}2#g-v6lT#j|V*8aPM#ah~ z+Z9=Cw5=CWww(~q$;-3+on+MR<@c^6aERJ{?{Z?)u8acS*qPx8wmd`ucixy717fKT2+hx4`g7%zPY!31A?H44*%ggxts`p9~ zIKD(S3IU=E}^W(tNbDLKT!qcbX5C zG-T+G1YyHPa?+5nIY`3hGzps{C2Y=>usK}9=7b5KWhQLiny`6r!sgWpo2Msi-k&J* z?}W%B6gDqWK%S$`&OAo}d6T9)^CpGO!=&;${c%~+fJSKWYYBOs!sdwzn|Dg)vC8$# zW3||s$0~egESYPo_WgxPS8_Qy*OtoN^$KHu?P+7UK<=(r5^{IRTwo=hxxh|1bAe$T zcxM*dX=fIr6&`uYR(a(q%S4_xD^Ho3_iSp~oTSzvL_HO`e;8N9g@Tnw?Q<(e#<~{d zQ5)*Wqh{r08$er$^J%j}2ccJ{-7REm!~QYos;j9o>1!)%Geez>b6Go#G( zxZfw!<9<8Sqs$z+v!iF`$c{**x6d4)VKY&N&1@+%WiIf^l)1pplqoZB=J;gZMB+s& z^Jcg_o-!xsNEOlKykp1-I?~PwI+GbDqa(Z$jOxR&X0bAi&XU}aM%TUAg`{%etbC<1 z`)TB3!^I){DU(q$_>Gf*%%&RoFzVv4QU4HJvB}Boto84xFfQk1AUo?j^>%hvneDai z?FN@T?C1v|d+c#jp3ELw_g1}&L-yFW!12_Z?OVX>t^0GGi`ypjKRdF~%IvnaZz}fz zXZx`4e-k-6Pnz;nw%j-DfHG?=Z-Gs##?F?jvhVh(cInMa%)TqL`PO)6^X&t9RyJRm z9k}+5Yc6@4pcn14vIDDZ!*43b2IqAkUl6khpEa*TW-qS$(^Z!| zhv2O2&#P4S=W-W^3{L+<_Gc@b^lHfsz2xFn<7EYoU+{Wmp6f4Fl%%tH$aDRro#$HS z%>IsTux`I%ku!UwoikhJ-yX{=N~Dl~d#s&*Tjuiqk^PuREf?B!JC}E0X7B}eW^k2h z+;k^#UI6SLX{+o^<0^A`TMmarwaDU?vB}O{E;FGQ`eZ`iVP`@Q%&cByXI9VSI63Q+ zsr{&(sXZ|B`#C%FyUHa03?I+L>qyEZuQJoW!sOG59PBACIx^F%O!xhi(~PtK<|k&l zSDEwQ=X8`P4|bRS7@wS&IG$7{{=vsw9PBQGFzz_fkqejCKU|G~o$TY4J{1R6U=3*M z3|qkd;kkCD}{JFFCZ*O3cb0U~TgiE#BPiqLynP9fTm zcw?suZHis%BB*jk+B&;(M%YRmVJmxttrVgvkG!)@!e60T&6DgcvJ;8Kb_n^}$mFtVV`WuJmFsN`!Elwld0 zFa>I4xtqb;TxwD7<`cV&jVzs$!{peBL)VpPt{~;$UBU&aVFyR7~oa^ zEcJHOx76DatJGWA(s8O3-P=qKo;bpXiyK&)#!Jyvv2`sLwhLP(FKpSpux0$J*}4{s z0){Oy7`9|#Es`gMDS=ogTQ|?6iz7lCWffEmTb?m&xkp(&^6fbuT9I(QA8za#xr&Ie zaek#J@#t6uD{-ku+cLL#@-V5xia1`_QWeU4a+U{|P$<)&*1ijv!o}tt=d@-R23Ngz2y(PTsP`9W_?e-PEFi{e~M_0q%D zUZ?H-E=s|5WyXAaMo>SO;6NWGU=Kj2;##T;AGXRoITWpbB-=wN@)SROtqJ*x3Iq-L zy79T?;O=bTF`<1PgWFYstj41k#=YPZAw8`y2Gc94CPQn`o*m(#5vKQN5xGKa3}=K% z&K0)x^HHHqiorTtAjWb&IMdGh!l@AE`F3Ywu+5s(o9I&K_v+^(LVHs|;~*+!86BeB z)M245L+T`m8i%&Gy7HhOraw^GB)Rcy{NTHu(hkXbu(zbq> z-nO(!a#JboZ9Dy@Q`$bfXk#e+R)8>JPBUWwn?-4TBA(I;gw`hpb9e#L3uBJipvpcZ zUqQam);CFNHg(oN;fLu@)HlIsUVwug=*&@WYsu2K!^9DT$($6VRc85NdKTsea&swd z7*o)?`(vBXzKEeP0x3uG#68MQ)MoUF8Cq+_p_Z8x}+IT~d&Cs*N9}w>i}$ zxy6+Bo}KB~*}|WM_FfEbnFV4or5#B1!}QesO_Ix{v_4G!>y)E@-YCcJHAu^aG;JF_ zBHh#md9O$6W%0>m5ZcrL=}8{4o6qhLb`XIc?r$PigCf zHb+s*D6OW8AEtM~ov(p3a@u72YP#FDR%nwIwVcwb?)Ss=`>UEzkfi{}bC5Qg9-VG! zD5pAFQ7b5|@?Jko?}(?g4${bJ-(2#Ph7zt{D{3XBU25lt>Fq8y;rOFKkkh`n=qU}y zX`d@<6{Vfa^uzSbbI3gmY2>uQ^fYx}DURI+D{3{Rm1g*1dIsJx5lADa4Z7ed4aakX z6t#xZ&Sdyudd8V1l;bJDp&_LG>%6Bl9OL~xx=OX{THJVR|~A=0->(r+rLMS$DrJ71}=)wVu*SGyE{wZ!t(Cr+sRN$vI^%k#~XA z21>h->4)iUu-a-eIqi!xo^8vOBZSmOO1qfphv}IZZ_OsBji3jwvsLQn#VE$AsGlgU zqP-ucx36eIxu^ofzTExDv#2lJ^V1e2WHD#kVZ~hOAlnXwE03? ztEerU$6xlPUV_swWNo3W9duH5%bF*&f+m~l=S%fd)K<3tNU9&EryfRqQ|Kh8{q>lq z&bdPStD?41T4}l;rl(_OxC?3Iw9kq?rOm;ih@!SrT17iQOmB-(^b4es)4r#VwYxuN z3+;PFx)zUAU(Ah`cR~Yoc1pjs+6p624+b`?WDAdOg~J|thj*#y`3mV z0coQTdiKSSc=IW07o|0H@x$~k4L4zb*d@qmD-L){%MscN2i41$>ZPdNl(lESr>yBh z+w(JG@plVJ4fLf3DryhgUz+ZRVbtD&&1Mgd)S+{Dk*CgSD86n{7#Y{@^~3aguici? z_EOrcLQiQp2A^{q)}*~So(J`$FZHCN_L0iB!+V`0cM4{NJFs)@Ly-tj1AVE1iYf%9 zmu2{2dPdn@*wzXW-w$b{_jiYRS0J^Y<*S3ge@DXR@6 z9!B~rQh)TNUQ^V5UcvEHUo7=F43hf=F>M&51>7oefIrNH(Ei#iUn&cwX7-B%6t#pQ z1#VFYrNqAFfMWjO%lw|qLCPuK=_vRLmE?%omD5t7|&>L3$^&`XK6iKwIt( zA8ooovkxig_x=#teMlUJB>hCHAC)3fv8g;PF)f>3C+=<<&!gCEMRCY<_Q&+j4b6~t zgw0={?;{OIw(FW;%ahbw0n}d<1)Ueu{V_egtQpddQs?;XK05Ef4ATV5;8B=9_2}dN znEp6k4@X5YrS010Bki`(cG2i3sX+l0mcFET%RBmGdPm&!ViaJ3&ShJDbmAy^Srf{b zNUCoDg_ja3=sXX<3-23x3*K<&o53BY&Iwz5u%CrCfo4WY^$4JPC`6osKtyun9SBj(R$$T~xf-vvNCM&5&1E@z81&xL4d^ASo@Gli-sWCf% z!bE#kQI7>sk0}Zo_pkNQ2;YPK&5(7D8ov&pVAnXOsBQridX*G39$e$2u|a4Dn?aqY z#%}|tZ)qi#RF41(lL{$lJh9qGW4+LhH-oxBjpG9-O#T-X)iZ$Vsi^;py*Cf8qT2ht zyMyOEPu;q2-S>~T>aDte+&?-A5Fk;aA{_JxIHDq`M^HiWCMFIFDxfg0T;1|HBmrKBNaSCVY#L9pct2L#l zjmZKvM?lRXs)qXYR{`}dSu<*AESeI7F*Om3GR`UcR|NFzDi+TcHB~@OCF%zCY@mP| z*ox#AH>hWI9fL8oBbL~lQ*xFE^sI`_Wd@Zhpi)WJP3l>H0X2Z^EH|lVH{*jbwf)UD zgHv`b3+P#;rtGq)NdjsTQMGK$1Qf=s78H8ckPwWi2@P!qr{pgU=ox&la5lL#Wx9Zx zPE;NB>}>({HrapbsAr+XU`$O!>IKdzN0tQi4E|hL{}|L90fjrD4#kkDM4h-433#jE5HC8~4B`Qqg+D|~?xVHFrU`*|R zIf`@2k@*4rFJ&`Mx-B6QxuCn-YRCH33gdopDN0MnM1J7g=Oc zD+JUEi~3eTeQi;%3#iwKYNq~&6N51|5%XO${q7H;Eolr?d|m>LVk59gEva|4bNo}>pXDpNpVU2Mo2BcO1V8Pq@l z^*T|lbd<>gsw>%>TXB?1#hifNpVJg5nha{UfZADeFAEqMJ*LjOD!r@K&4vL2mytWF#7epfO?*&-)JN{3#iWI?eH6o z#Fd!=BXLSouC$rdK>>BpqSgqg)fP2PKuxo#F9g&miy9=L23pkP0_t(1?qDR;#`s`N zje{oP4&#*bGXh4enCZX`YOjET?!};12q>(J3~Gvi!gI}_z7S9tF@qW`paxr19|6^e zsJnE9B?zbl@<+K#S6C$(2kBqrZ@A~<3Nxre0fkw|pf(98tOE>cfq=sNYfuvf6y_#_ z`dmPLZc%RuD9qA^tfvIjQ$*dPQB4$3iR71ak4Cj-x@SE32qRLVrp=^I38+&RwOc^p z$!)Z_T0r6MHmGy~m2Oev1yqVfeJ-Frv#7xW3Rkqzuf75bbEZK(BA^~2>ORdBO>x1P z8i%#{ea0yj(*n*B;zY1+GN@w$>X=3C7Em}xhOBG>m2FWO0xH9zeil$aThv$qg*}_m zuMq-jq(u!9P;Xe&GXm;4i|Qtzx>;050hK`1?{u%#P7OGZh|sBPgU=eHE(s{u0Su~0 zKw-aTP`Lss*P=EFD9nb2tYrdfnMI`wDBQP(tVsfDl0}UZP~$A>GXaH})o5{;fWmIg zpxzKrxN;1tpMdITQN0CJZ;R?Cpm2Q|Ep`%6xW3>bL)TZ+lz{8&n5JOep>XP&fU35r zQUL`Ej3KL7KtXF`P)7t*p+)5jsC(Y)CZM26lAy55H>l?YvhY+esOSEL)Dr?(J*^gd{tKyOfvjY!#Uufh^uIinDCj+A zOnx3G2h5j8X*OlE<^LgQw`GrqA~mkc|K8V*-2L&dyd&l2fPdwzBR9{9W>$O76jAp4 zD>s+RJs0GjF>=oo5ea%(=h^`qQc zRBl}>w?>Y--e&u`sB3pd$?XH=_7rma6S=*P+`dU}4<@&tliM50?K9=}taAHbxxKX9 zzFf4&huy%~^+nYekQPaas(WCRoE}3?CnBd`k<-|WLHz7+L9P0XO|i!usGQ^E;)Odob64{jwiAS zo-?g#G8RT=lZ|SxWR#rkb5yWVQ^ANCR$w`stekyU&Q>h4J74Dg!W9%{yb6rW*GIAz;DL(!4diMz+Ptf^N4(daxAJ$&|(>E+)7uq2aCW3y6LQe1qZ(t!aO4RTb3anA&LSdxBYUF}+(Eq3*KNO6lh{A`` ztso<*P$ap)CiE<7m=pzYMGv`Rlqh^F-wfYU>xIE!^_)Y`1_sBBDJ-;%g5H#7j#Ht795*MV|IfDS5k?CNY_g4 z3W%V|DOS*=RA{AKpeB0uZg4!O!oohUvpYaQu@F*56$vOj%Z=qD@ga@Jkd9Oo7UZD6|;vrV?SwaQymbf}YVcQ3&<>6hh5{wljkxs1+=W zJw153F;X&$1dZ{W!wx~vZn zhT0xxG6)*~V2sB9GD~NntP)Ug?J|;HT$4$9fi{P129ibE^NGP%?uuY!BgylmS5JVxjijfZmgE31$ zvD_*wPs@1$h0Dyy`OoYR(q0OjS{i5dx?2S2tatYF??G0$t%s6<0uC$8i`3qS##P@Oo zq#g#Kl}@4(N{6qfyyF;Vn%UOXs!qke(iUS$V`OFDL(~4aAk571PLxc>_$c}3 zKee;et3|P=efx%@;bt~_mO(#iZhS`N1qrkB>?3FYD`$M0L}vd1JZHaL{(qu`)dWJk zPQXqY+vR6{+q7#F5GAT~(4&FZFxWxTqSj3yO1<&WdEZ7?YE~5nxtb0{$yA8gPJJ6+ z;@hZQZ-OXc^@<8!mtq@93$BInwOuhoE$>+B+vsY|s;t54bc_kYs9K6y%|lf6qdn@I z5XWkWj4JbO)UF{ylzKk?qA#{qvnrBorXw8{gqihCh(djnOTN<9X;!rqx!Nm4$y9RL zNPUZ17ltUADl;2MTGW~~M9EaZVfA#rx$HY2sHcM~#H_lbUXEU!ht>L-aK%>|YW*Nv zrdbh4txl@+#Wrbb(pApM)gdBEtx2x(m9`nPeGRWyw3fzgRuHDlvMVXcR9sp^(iUCy zZ4~vJ7THyvMAe}B7}cOwleDO{rHE3){jT{ox(&s6QG10dRb`R1sCBR8YG|#ZHjfA@ zauxE5TypijR+2P{YGF}okD;;~)gD_x(yl&u(|0^}YU+b{GV_XOcw)^A!pyp8%SoC< zm9=HyqStIAN)5kQ=i9ekn))K1Y*YbmDM{NCgqii^qSU5)kJqMKLT#RjUg?h2&HJ{& zw`+T>x_OI9ny3mN?owW@52HJ;(O24DO_|GU`bDb{*r$Eoxa%R$_Gc zEni`FiP3awQ=(ugD}Ormwy!k1{ON3V1ksDB&ZaU|QA@MRm3f^>ZAui4WhH3m-t|2K zl%Pf3UMfL5gQN)x+CGm53@>~uS0I;_+x_ysZ>x5>-RWqx-L*$FU#v*8iubZoz^j8W zWi>Aa%xVw6<0}ifdWJYk4+j<~?sT40GNq2Epz{({lc(UEM=!xVnZ`d_HRoTjyG3Q_ zo>3}86ZX5V=EP3$$p&=>n>iHFr3%t=Wvk61*pCGi!G7!-FM=&sQk$3Yj#i%ADE3|T zNI{YEykuc{ z;%N3u^si^HCYH9K=vW?ZB+OPz{2^rtFM6MXBmkg>PV>3ckg#}Y3reezB%;)cuG4+6 z3Qg@=VRFBdv}}J&$!=jq-%^nD3Dn3mA7Pi-Gb)$0NtAlE+YBG<3e;jK7|*$TByGDt zrfhG~_cCdBi5flAM;Ow7k8aWTSd@CR*DN2b3Qw||CU=MWb|`+f4|f%t(L*izZYJ+H zYIU+drc7?pcUhDg>Ywf-ty)w2H<{dRYV~ZFIX>Jql(;_IqUSJqx2V;n{urJ4od(s6 zLdSD`gdx@cd(8&bMr~g0G0z99(bUIqC+}d=T8Ybz%kbfDpdQlh7X4R)L#sdeV@hhX zK_NLSo@cMk_mOrJ8=Qs~eLIteB(%5_9$MhT)$$S9#yB0EvdJG)mbK_x4GMkxa-okf z_4PHBzJ1J(u`!P5g9iWLyJr`Eg5qxDZZPZi~Lb;Lz-3e@ywQ*`Vr4 zT4>NRA8BDt9TaNO*E4B##8q@#?!)Q0r7?|UGp?4ny>TmixMuWjZ;QUp;Ltm`^`&8o za=Z4y5nxKQLEU7%8?w>|Ythsp=-pZ-?FMm`C=KBi*Q%-AD_it6jH@9oKR(NcYs1Z- z-=eQJIP`9&KSn2(Q`bpa7#?WeR$(X~hBKSRq+KKKdhazp+-*(mjmvHoa^B} z`wd#^q85Fn!J&6q{+N>0Y*1I}vKzb3M;MIAUp4D1n5-&lvtdBC4|Z2m2jF&H&bUhA z${t?t!`;)=hs#>@Wd_$wlgS1j?mnIWW`nyzy-WAUl=NnODU)`YsE;@L2t!`6kDK)+ zjH;l%)%M-wgT-ilYg_ci28OPcByaY?)a3Kc233wG)ttC39tscdI3=f9U&Mr6qMpt0 zrP7-9OvYTKeuY2U>JjyWrhbU4VK0c@{m<|4tB0G2nJfQ345ag!ybE*yi~TTV@jaM6GZ78GgOO3zoE?N947B9 zaobSMFY*Tb1(({k+bCL^&bTvl6f^xWW!5c}U7gLS(?pHh;}JF)#kG+rQJ-Z|YQr0Q zJ=hdYeWStVPEp^gdgXe!sdTI`OzAV3ykg?ccFpr}(_o;$x{>rhXc>x#QGmL;nLF z4%Yqt_07mJ$y+@}TxG9=9&Q#5Ni%Yfa_%T`XCFS~;bvo|IopgZvYb0Y+@Vf~JzP3w z%R|k`;mf%~;^5B9e&ULfiagvxy2Y9iL&v#%;`Ven;o&m*Wv5%5uB&U~PI@?4YvWey2$19Q z_R;7q^uv^eI)cJDl}A)+u}9cqT%S6sWN<2%`WF83lm}a)sbApwY%;jL#DzXO?crcw zeIMtd(ctzF*YM6654VhN?Pgd$c)Poat9|vXhg*&X?)7F=o#EUr;;uh;&cm(X=d8}) za)_(!b>73lMoH(a*5WY6r9DbK+$!7`Cv=P3iP2MwAHLw>vT$w-bXXX8|8@{}IH}ab z!D84cPevd@SIk%0ttk_E)4)(j) zWjZweoZAXc$?(IJxjJ0?IkknT>E#|_*{H;+LqpH0%|!iD;i1-R$}c+V#Bpj9Q9oYx zP_U!@sKe!*QyYm&x#FQ%X6$UnjiSSGb6YfZ6gWD5&S7jrpVQ#DxviS|c}Taob?BWM`k02=&A|%wF*q7^ zlDC#|Bk0t*x$T-d0vw%e;?@w?FpMq*H}@-6!VNn7k~z1UaYO0Sa&tR0btpKx7)f3h zarJ}gQgw5%3Js~(;R4LLRm9Z}pa9@X^M&(onD0NOOJ$Bt#0p^l9VfY_%ZXR*V;^jV9QN;muuh918kJe#+7)-#JRFqv-HXFIEv|^02`;9zj|V$}4QFXsr?nbk zX>|H4Uy5w>q^7=fMc4BUYC0&TMj1q@Y3MuV?_y{OYIGvPVY!kvRV zj8?eRrfb{Z2IE?`W}>b~rXI49ywmNOz6XyFOt_1a}$Zw>AvT8Rt^yLf)TDMGBp*LhsvkUlueYbDwDfK+|@TIOwY+ddphW97^)|eYa_04GzHB$IcQHu zH-@2lGPzbdj(KqucIM=u@r;`rg6hfST8P`3M4@6%4jRv-9U&-{Os<)@;wLG*%gI5R z`DAejY9*6Hh6kLvftw;YC^HA*%#p^C?(-BB*`wnaOEFSTc~EAK#c?dPIF#+v)kPgB z7|F>&{n%kq2&y1cUL$e)AEDqNCkOT8Bl|lg8-k9;VECA?Q?0?kaI*1NdpQ9}B-@#=;2Bwzw+d>c{SjkQbwUSs#L0#T0ji zu&hq|T^Qz_PRfc9v>+ySnXnVRD4@XZ9aL()j)kBFF{uioYKD`4zfD1>X#+NijNN7W>YJI7j{98X@@w!o=cO#Ck)WOCDgw8*p6a(++p!?r;vrh4YXM_8X| zai^)}>fyyM4vMMa)zJRvsTOyNDdz@ zD8$BF+zD#8?n`njwmE2%zN`bs-s0D05y{)gJb`TvTB3(GHbL7(Z*lH8aiy=3^RCT7 zOY~Z46SP=1ca%o3>8CjIq_x3$ICnoag`l6Z$s;5&w+A`N+8}g0J#w3%pR&0^;;M#S zbaBw_46AB_=E>#`6Sp+6+{Hm>len}AIwhMsL|n;h6)p}so7YO3pi{EBgWzI9Gdf;z zfs3`6j%lGLXq9YoKS@0PJo$XmoBTI4Ezciog0{%!@=0R-4{_vKX@ko!Hb2xOSjIu_ z(;9JgJyGofp+k9MUn4X@w!~a&xn>l3P1*vXLm5@m1Ramf?In3z9;tD0(2YE@r4c$F zo7+wDuDpN4#X&dn{*@-^ZftHB$y=6K>*ApENL<#4%E&gCLtN>b^cKCze?#Z-=7mP+ zWNdB+jZAn^BKhsvV73;Mm>F(@w#6oYWfI?_PTSymO?|7h5!x1;+h#}%xxkH(xCj!t zn<>Atw=#)?X;^KE@YEd)iF5+YJ76=Dxb#6fqc*rji+OMci z!?5y3Xi98}8;Hw(lrB1(gZALj>_%uoY;HYqS3jqV*yf-;`21=kv>-OOj<}tV(@kM> z&`DETKo_Sbl1NQVbHy7J07@v0bn&#lJ7p9i! z5o{jG9Bi+!?`)XfnQlko619sU?@^`Hsi}1m;Ni{)iP=^%PW1sEh?h&`?DcXFR95$X$Z>h%y+R5FamYWtN z)2`A4FQShNni{Y*c9OqAV$8MgT}8>uwU`dyU#rLF*h$`^w$Bb*>=21f<*>8$*z`KN zR^oDBSnA-gU8HfOHMe;N;n+wmhq8LoUeN4gl@`-8D^!o|tdrDi0 zQX4^C0xhQ7jGEhQN8}`r5m}e{=M(JvL}qF+ zf6lB!7E$bpoa7NAn^r$tY>`-HKAQy>Ioj(u$pRw7TmQnJStN6*7W0>_*pJ=Te{zwB zw3~X&!QpZIX-VV?JQs%_z}gzS7^lnwM4la;Kudc*o~v-iN1ttg@{m6+Y2KvI@`=1M zsVjf-(|fD6n68tq)Uh3kQ)V8KH#2(iwFHrCwU}NRH*e}(k|}X7aiPrvY0=Kx#+vW- z&7qrY@8OiVTf3ph6n@(NibY~A*Z$MO8?cSLW#$lhDYYwKk@O8EbcCzW1zVFas4T&Rt2C=2Mq=sbGU1KX@Cp!yEkXx8;)1D$_3ZYnl zJe5{n4P%oJ{7I$6b*olcT;@mDcXq&&XF zAn}lYJf)yYH+5&`)RTh3tygib3da9=xq<3hyC=pMgs-xNqEl)L$}U$zYHBai2~$t; z@a~mbSjm=(Zt^!GkN)^HX|0L8t;Ib3xACJMg4Nx=}K5M z=udljm!dTjduhR2@udEz4_g*`m#j zHfmS&b`3dWdy;7+MT2gy=h&PEy^^ik+-RM4S#Ni2>dSFt2-BI(ma*XE?Y9M2tDRzcU=FEyw3GIOzuq)%!W z^#^LUrw%{>Idx<$=>DnOYc8@Ci@P7kwKDyI8#^Yv(w=*8Y|tKP|H_0NH_Dg?TBPJc zqHpF-9-PRXDz<13Bo3aOd$WvrdPUNQv{L==DqEvd)(X*q5UDD|Cfsk8_qI^n@D`4b_UOvqIN63{!1b)OV4P3Nc_v!D~g)V zFh7_`d>wjoU;ktH+@@cKKN(9`QmOXG*e8eovgus-H1j!${6-eW{g(cRn*EEud_9>K z{1w_Cl3)LF(f*oZ<^vMRFUK_=*8fnkYwkZ@O``2dwf2XkSN}10SH%hDlM%@;#ynD| z|DkMG##e84UkAQc`$P9PzslHEcAWW0MDp~Myr%!LrXXwLh`wED_Y~It*ro4?iCG0T z$Cy__)-gs{BieLUdxKF|n?I-Qu68a4O`!6f@ zo>LkRGHZEcGh~@A(Et5%!KRGVPY3qybQ-aU|K8>4H$Iz?v8mwlZM~4$rX#;a_TpUq zzg#cgot2*QAA_HLG`3t@sQs7NN1q-1A1UcsyNjWeEn-&V$Zt~g&<_2-HkBXCS(EY8 zS0e^K+pXhGEUy1+$8OIK9P!mp8EbNml{a0}Pcv(+_uIF%jrxDPd9f&O)3WqQ<31bq z=1aXEP54dQsr|QvM|-{W=CIGkO-f(3DX-|_%?lVl^U36VzNBcnwo?CNQ}u<@g}FQa z@9gE7X;V^DMvopja`0ONUV7<;`+rs*yZexW@LHt{{(tQLpa0(%UV3T3TZ2cA96dTE zbxK<1^6VYCg{Lo6Hx=sTn2)_@A_a-PSLi8tJev=3cL%O(Yw_t#I7It>_p-JbpK>#n zcK|MEJMk&E_;^1p*7o63ZXNM{TBsH3lMG9O_tQM>ls?1IrF%c!h8cT-p-uLFnuX=y zYD4?!{gm``I}J^o_fvBW%uf_m`R~gA@cDUPd0P3i@+YO6@~D!`ejZZjCs}zAKMyHM zj-M{Y`l*TI_e{}%Yp1q>aBER9}ou5X&Z%gHEa9Kg`nxFnN|C21YZ2s*ycAWMnZZ7gK ze$4b8`k~{FXJ>+BIc3;?+Y%vOTlHRS~TZ+|4nlY&8*H@hD)|N zQv&~%^RJHVdnW1ppZCc5&i~A7-s4CK?suJk(Gi%m_m%kxKW<`aUNzt4yz@6bXK4N< z!v4*9^F8lh{K-zd`J8h9Z-2tiC4aV=-!eb7<q-b0&kzT)S^JTuNu z{9c>1`!nhR|L1<+{LAc*x&ATVcmMDHlvZpcmA~u!ZGX%D2EDTXBEo#fewV*Pa?Q`p z?{LOmH`;$Y-*c}|X=O^^_x{-L+ACf2RTq4VuZ8(L<|>(_nD3bDX}-GV>ug$s^R>6R z8h?nb&Xd_no!08~^C;Hvf5KY+Y2|&Nb^iZQ{yX0bcz@d}P119Tb`|0siFh}YtDP3? zgv7fj@eWM9dlT>c#JfiEj+1S9L)E2c3UhbPizxC?PcFTFMFbK042&O33bHh5>wbOWJ^$8a&n+vZ5=|D_%%lX`ulSd7H%22mFHF(tImAU7G)GdZq=kmcdQ%Ak=cs$eU z#6SMVsHtlXUiR1O7@DJsyrrol`gS%nN1gkQNL`v&;jcNewNpQibZDnW{+Lx5pq(=G zUzc_*82i?r4E@)i-Wt1L*Cl`bm!V&~ygOs`tIWLu`n3mN9X)UNWq$Rc_yJ+?oKbGS^ri%lNRnrLpclGUHf)#+qrv>vE^O z?a+q5Jteo!UmMOf?Ku-(b!ggOosbitY3Dlp9jUK4boj5N?(o&&GwVRzuGIbx>p=h1 zU3LD}0d9FXICW5hWqC*#H1%MB<$>8WPR#wQ!=~}qxhMQ>8r)X0?r%LTTS^|v+rD7?q}*_7R0k@0}#Q&^@Xq?#tle2>t{h^cFE(Do-wt*uuDEO>A0U=lG!?s zPVDWlb@rYJ3+7&E+ubIV-?b`c6*bkpx)$?fYGIYferi}{kN)t)5~HKA%0BVKQLk0j zu=E!H)Z0`N!_wRPr(&=#A`h^Ez)zG7U`Gg_*kS(J2!0dPCNII zRX%oZ!+zeh{nO44`+4V2w>Nq1=Z00jFv2Qd=(WmQmi}iPmj0*xEd8ecz`CI>|AC=v zef$T=FX5gZbHM4A5R)|ez%`Fog6Zn;l*82ljs{*=2j)J}xLJKS&fz{0H*`~@*L}ip z$#|4qJah@cCF4;)mkj3T0pAgipGUpd&%^K@Np^dWeCOjmLLMpY>Q@g$BrILM)PVvR z+6S)o@k!x+F5f4+{9Ka1_wjRK-ZP<%Z$)^|ytOgp^`0?&a(aZU4!|d;ho4Ul^Yh93 zD8kR@qdc#l5BD8f^qk9g=($WE-y!agloH{O^u3Qi(suIQ*)z=LEpD%|fm?jM#<(Ba zf(Spf1s*>%?%DQJvfH!mCm+u??kzVa!dvc3A8$G4fp_?WNDsWjUJpF()weCet8bf+ zSD)c2_3vM|KdWV3oG-AEgjtPRnR z=DB=h@`L0(#HYkGxERGeIQd4T=Q8((p5^g|p5^g|<{s9IyKi#H#ACkuVvmQl;kDf- z!fU(FdXLw(tRJ|nUpezsKQH5{9_0Q0y~q1K$fJI?$D^Km>96s4>96*9>C5{4%f=DN zM<&QeJFxhP9Z%Cn)Vzc!equ*t`~;83c$+(W)9sjs>VxXro_Gu%;qjd(!sEM$2oE!A zWRWLoWN}2)NMIaFo;wbO$F;!=$k&+An;j{YwgTvo)5T7IBy!!Ji`%F!@bctXT%3JOzkw;ArSFFou;B? z?v2jLz=$KX?2I@vBA6;X5lj{C2qq&YDl{*N`;GH1QcTpmh?poN%BtW+?l#UrL|MI9 z;Eu9lQCru>c%rt(TysZlu^_N@O1JTjBSsKdw{`9yFcwmFVJIN`lpv&RC}IrHv$y!T z7%^&{H5Uj!>dvzxV$@jFTj5KlV>y>gQE!Frs5c%*_g%avjxPSYh&Vdg$UNCdKNdN- zA$sH>i$ts#-r_oBpGd^bEVgmkL*xo>529aeBa4T2{ z91(xXBRU6@YrEYvB04GllDpgU^A{W=N)eqm*q1rUqfXzVrLip$QKu9mTI=9g6zfu- zDAuL!C{`8~TY=0>PI-um9a`azie*u@*~!#tyIn-tc11iV9iHj$#G-buess;nA!_%d ztL~^>76rV$8-*v>@(=~wZM!=PnBt1no1-b?K`H$9mpR#AbI{ z?2gTrjrC@+<9oW7x!OhSc=z4z*l`w(-k9nWjh@=*jz(wk?PvQ@5R5&Bh;Q$A)*au@ z;^mijp^3%j5HH_lxjSB-#oyO_bk@Zo{{F)nclYrqS07Gq zf7%7kKorPq>=T?B0$9d@%Av(B5E%!CR=P6|unY#d-8~r$x+71rGlK!oiSSF@370_R zM2P!2A}4}umIc|Y4f1&)WHU?1W~$)1FJ}AXzL`ad=b7X#!Z|2A(q|!TVT%fX2`c^JsfI-=3e&dRdAHhj?Jk%t}qaAc1?ZOCKUV;kOY^l-=?`#w0bdQ<veB~aw)O9D_W@`Bu!Dxy2!+W@rQtEOhp7 zi>7{njN>N9vOkADt@6l2_UBK*nc1H=@a)f(9u66t{*LU=W;W@Kj2m~w!)?UF3LJev z$IJ3uf6t>No!vv8>+kJ6*DPoDPt*tV^m7(DvnSa(vswP_X>>$x3i-FE+4;9wF7Np? zj&5q5rp&W*c?&axFSj#;^GxH0JBjlEp#O;5U}qZVnaexT<=}1>S-d*zurrslOy~sx znb7yynb3uq)eG&+>N#|slm%pJKWS%b7iNCHXlH)sndJM@?dd*_h)nW4GyQ9neA>-H zpYpOJGd<6AKa@MoILB|9JJUVSoc}RRM{ar0y9~qiNfQ%YPdpR>N!=w`*O9DrdxPu`6fDR^pJY>>*nzgjXJU<$&if zpj#S%iVgRTF)m{BV)RNT#3>W=J>rm=Yl2%3KX7D1%DAW%>b7_pK_OSqex^M;+MDNF2TRgI`E1qeVrIM_0RYCm^DwkDamdlc?y~PT7eY(-pFUn7%d}lz-a^w9m6u>h}jIoks7XwR{T{KIU@e*jSWcxIGm!=W31R5)- zHg25{hJtEi&4Oyx=w0;UY^>DV$-q)?C(KfBvZdpADY_3RIk@`@Ptk5+X=;_CtD)XS zEo>)SCQr8Po@^PvTIyZYqJXj`24zbY)+2d>ObNsW*1M%XLmbkS$!0-C+479CStkGxD8up+h!Tk=Ag&u02?u0okQ zwK`^@4~DXvsLy8>=Mf6th zBt25x4OZpMENY4o$qOugy2mVj%8RM~iE^n&imP!JQ@us44zsg6*f2`74l_%$3d_7M zHOsup7LBD+wQA^X^_VdjOQ-tu>yk#* zZ1Zt5lLlL+N;GjuksBUtI#D}^4qE5I;Z`Jz$wO|e-@z$MC;PNorYTEXPz@$eGb&=2 zKj@FC4;dA)p>aw?HPf;J`00BO28gPo19ZdnQrM$_zbF0a!R(!JaH&m|9GlTqsUX-^F}KxMo)@- zRsY6xaCf$ON>jdRf!kFctHz@iSYGgMNR2CKf$Ej0R$XaQUKsDwB2@1$AaaGUsWceP3zESv~SokT!$5e>g4(Q{xV_K*>uS z)#zcV$Gx3{gIr$>8F4eI)p_KC<;9L-ZQKG?Fj0_pDKQ9ByIg8z+$@szp&eAl&^KG+GQ+BjKT}%|(tnU?sslDo38JAAdrrFU6&Nd5B^0@_WY7);CNDW>bNux_P5HV7itapQ2ZOXz@j;jxU);*LOp-P@`^# zK*rhfE=}3f%BW@3X85HbOnnh+g{zQ8oRY=e2P4NL2hVLzEhlMpJ%cc{XI(3@1?C|c zGmc;uCC_#Ciof0ojYJEQiV+2AS04(()CZy5B5oxcUG4%I*|!~<@?8s(?-B)R=Q{>r zYRB`fj9W$0KC&|%JA3%6rhL=_x6C|k6-hf98-%H`M_L(|MbZXS@?WPM{(3vRc5gsh z7NjYA$RpBAZDZ$(sMXYFxPK6)zKnBq)8Lf#QfX>3TebC zORxG$+oCCpIklFg)pZZT)b2R*b&y7!GMjugy?xuPDYH4Xj-=H*9E7P4*R-M_OCHQ~ zkT!)pI=#|RPIU^WvPoL?gF%>@guAo>(uh+gUh$QN60Q?CwVtG1=^TWqoiDe-{E?>- zr+jXZyVRjqCsf{G9v_lZ4cEB?x1Zl)6 zqsx7z!8|vbQ=3TIg$_ZO+TlVg%JJmEXb5Tlxa2DhX1srJYBQ78J_u9Wm$bq(lZSNW zB<-TFw6$34a%u}nD`_8usqwg*n<0%jgwvx2c4nY_jw-!hvPWj3X zlXJ>k&CUW*+elh@LJ+2Q#A>V6;FNDK`1UP}T_HqmCux@xf-p4!*IS#xDdWk5*V!xn zdKHSXa_U!-R@Eg4Q@d2PqFhuS^1I_mC-DG+$T@W-%)Ff1LDFt?4Z_riu$8zCX~Ze% z=Y0FN0;^O`?IdXpe+t6XKQ*)>QDq(ydqUa@@-6lr>2gh3&Z!)d7JfPiQ=i6cc?Z&n zQ#O+avRB$NP1($;T{Mrs8c4kYr(ww2MY8shld@OVQccNgwW*QjCPliBr-Lw@cj!DI zEsvz_Bo~9i94YBwKG)9dKq(*Y;AoagAJ_y5Adj~q3gD|P1&6GmlHs_-F zx=G>6xba{RrapM%E|YeMq-7TPN`o1E@m*+>4#7MR>e)c*Sxy}$D%TG0bzZsYm=W$l z&vh6@B0!A{q(*Y80F+wMAqZ1DRNRMdtpM@;kT&IzZ~tfG+At}MMpKUDc@1FfVW8)@V_%4 z^uInikV;0WnIqa!61AE_3cR8aN{M~TQO>*`$oz$vV%==u zX=I8`gPouKY-%?*$SlkJvP=<*o#zJPjIFsAmwLa+)&S+;z+V-6RZ6bHw z_Eu!~XVfPG>Jv_(&6>`^nA*9f4bsk0o2zyNwArR9t4OiQs38Ih=ZPq^SrHeEsd2cv z&!LVE+MN4qK$}QQKZn$xjCxr>z04`Jc|JA>YtfYRZ4g#MO-|n)&?KnotUjb6 z9H($;6mJWV)r|SG4YDp!i$4jdpGe=zsNMpq7pKr-;nsi_bxkR3gRD|&@dp7lo^-N| zdO|=w!6~#@uqB|yFuVLqwK8fkRX|~)E#uUa0_sUlp~WMc16qXd!I3t|x=1Zf5Kz!- zT;xh{5y z+P=EY;FOi?1KO;|)I-!|YICH38c90t%hYClVlbvAB3%yWl$`8sVAz6jlY1x_*a2Kn~h1qn3~ksW^hXGnt(Rpva*+`tJG$yfWo|Tl~d0ND9qMG zVPs2J2eeqDDWz>DwMalMB&wSFHB3MaBa1*a8;itXOzncP;GB||70@#{(&SmxL;*FC zsB6@-egdi=Sr@KR&#K~rF*UBL&ES+Zs{(p18C0r(N+nr0sb~EK)Bv)x+@zk} zj1R`t_BY!MPT93Apl6kuvdf|-38+a#)v_@YP#CjXQ0Q4hLNKN#G_)ClpxQFYX_w*}POWdEt7o`n*FF*OmX7dWRJSrX7Q_;X?XV^DJh6z+gJ6lX%u zh6yOlQ}v+Gvv6`SrY2+i$T_8WaX`;1*m~EXG6htoCF>Ib^$Af8)c-#Ts2*g!YC!*$ z3yT8!Rjw%)+DvM#fLd!&-w3F0h-##My)2-xC20hOajkPz4o`7(vK%g`wQUBcD>RSQzwMD%ypk5=Ynff12493(%%y-RB^I?wKy9+9 z$pUJ!MGY5FxO?F^PFGnk0fk*H+{WoBYvO`2H5Q5=&M61x1{@_kNe@_5rhvk_*pM|w zK;bGgsDT3Nb)s77D3b+LSF$&^;wY7hIRU*trzuc08Psk8wcDcR38=XiHCjN8wy0MH z)T>0b(J>|os3fv)<40F6rUxA3SxqTxGpSty3TrJx)_ehl{k=hr6;NMU)SCk8O`>km zG4>Epc!u8sg=4Il9dL~B2FA5&Pz3_2z@oAQRF*|e6i^c^YNUV~VNrbrR9~WQ(@1m? zP+iDl;5Lm!`K*AEIISsAaU0Y=0kzMfmI|n)7L_WXQY~tPfWk-^{d!(NJx|ncG!mT! zRA=&b_>D&5%FKX~IHf69+Dz)8fI4VVYXsD4i<%~&rdiY%0&0{+4H8fTE$VRr^*B*? zFcNBGd@!cQL6dNYamx7_0V7t-bl?WHS3p7cVo)mt6xKxsHAO(-xn@vb2q=u0K@ApA zgDt9$fa*ikUAn>&1XKd~quiw{tdfj_^e^%^-1BjT8C0Qw!mMLZn*TjF z)G3SFEuiq^Hd&FG zpJs}txL{0;!`l2l#J!>!1Z-ZQ?Tw( zICV`xRa;c4fPw|akX0<8ptUimBLb?>qVfe)zC~>pP}?mkTR>s{GWxYrK&`Z>g#rq@ zX+u_;fJ(EdDFSM$MWqTTtnrN&zZX#7Thtf+b^U?-uomfcnIuh6||S7By5r z4YjB@1Qc|PM(34yGhR*OCVg;cUYRvd2S_8dv3i?`ucy{`gnkk#cjuzjD@*o99F` zt379mD0}{uo6F^%3v$mGx#yMKvrq0hD)&s5d%nx9739_(a%&j5^-#88X0_K=a_cm? zHJ{x2QEn|Nx2~02BS&3tv;ADuwL7Ea_5pHx3c3A>++Ig+-z2vOliSb9?G5GjnR0tp zx&5!)URrKnF52V6ZeZ;CqUsAsi=;%=Jupg6k0GZMk<+ip>1xIZG$*#+M^1+%r;n1; zZOQ4$M=YmrmeXBJ=)q-l z>daCRRX@)tIbFY;-9XL;A!pB!vyI5vStM*WGWH*`t0_@!NsN-SON|m(oNODHoV`rW z_9kb?6WIjMnN~F!3nR11MzvQmO3wBy`0To&VN9{7Xc#?)vtkhctrJ)V3eG{g`Dq($PZ+d<-1WJ z@X9E#s2TxP!zqk}$k(Mi`MNNFo~V8^jQUF8H)H$Yi2QkAFEjjkM7}{e7F8x_v5ZqV z#;86^%r8si|MZIGB_;BuLjC~5duy)1OUm}k68U)TvSdm4i{Tif`ks-8of6fLjZtzw zaf<}rZnl5jApv#3^siHEWjz0Ilurjc2rA5vkh~?8j|CL0T!ydYOaTQy5QEAQP_POb zRHc9_;S_q`J*pEYIjbt~24e~oNrtQHBmwocMQspJD=g}qfU?|P;e;C1iI$v&mDhtY zrN8C4`?Y|ANycy$UM8THSkzGgg#d(q#)%oHDXL2}xpXU0-K!ZT=c+B`_ATQ?&Ya*y zF6jd;$8|aPb~#u13&Gy)6wW|y_ZZixF8maNphOJ^V3b^FL7pIJzzS0k2R+0aK@aG^ zD1-umYK=U!BWgGX3jmP|?T`o)LBB*HCwPQ6u#g!gYWNBT)+lnJFj8SPazQ%if7Fm4 z3Pw^y;X~mA6uy;jhHt6$!eFp^&Y@=mgJZ@N7FtF@ zZ%WiKHAYE=yom$o;*9`0^h^|vhd|$(EKpGSAvhXOfpPzz2@{kHI;1fRj!mR7yTO7h zsl-8~YbAFDM9}0ED`-+Gv{Eim6Fqx3IG$5sVV~F89U!1s2q~kA1Qed-Mri9}(L-C& zufGW>7A8xfxzR&zCr*M8{nSfeq z2J@@sv4Ws~O7uWM|7bI63V|!sX4G^9j5-$FBqh+0Pm_Rtjm-|2F_4&nrCgvi57E;% z@MI8O)&~efZ4WaU1dV?%M&o~(rL$002`IRB8A&g$$t1l%n?p7PbPb6%SPG4DI?lxc zYQCAYL%kBWF+dn9r^LeeW26ch7>p?hPc*0u0magePznAN|gU&tSWU9CY)5l}3DOc@P-v4Db?p^?|-=`8^* zB1aA~=@`_H0%|-_rPSh40R?ST^xQq_<6#R+*&5I>qzg)+6h9ZJ$*4(%Sc0Nn+X7@E z*%5}-NMkffKusd5gbk~JI!TF>SQ4h^wgq)gVwXelI5Q*ua_vr>?b@--`GWwgYW z$Ef>Jb7ZoNoKdrF;sB#$?PM83|FYkA;AdzG%l^rjtUxA4S^JjB2)g>0(Qv+XM^o-WP_E>EsFz?$W$wr z_m)w?IdpS*c3hT^_n89U(LmPTXB=637X(t7oH|IOA(1IqHt#S-Ln4c@Tux-U{K_n& zbJSeT@_C=thHl4vhZaSPyEX94&vH4biIT~1EtfA_Hv6_rCU2RH;JI{V2(pV)`dBMh zdBzFfA#>&`$5`A*KIw}s!kl4bK97<|9eFX$Z0l-Or($1ei?O6Jva;`?X@6W0X6AS& zN+x4`l>GCb+F9z=qS({EeM8Z3Gn+ljpdU3iKBMx2gxPuak+c7mGrmnCv;P2|vtKU% zKT*PJ0wG=}U?+|3^0U5e+O-LY5>-0r(ZFjM>>z1T>n0GT-gxM|Z=)+Us|tf$O$VZ6 zDnx9jzKt*OZPczeL6oq1MFp=*v5lk!*TVSPu9%^gcP#a7bhTzx)?jry#spzhEyb+n zA*%Y(9`#L#V>LuZmH9Sm*AO8}Js*G37h9`Y6-hSJk&X(&%=#uop}xr_Uuo+!t6GX& z?G>VAD!FW=zD2DILzGOFnGGZ@YRwv=WGdjWdOF`+_8kz^)4>&DR^3rAN3YJqYW+;O z;wufcevmEGtO%r5Csq1ln>01)D(B?t5D}%;Bv<)L+l<-1hSw`vOXD^x2vcU+m6T*E zF0CPHi>~@Miuz59>?%*9YEXTQYEY|5TGZN7M5*C^*L)k@hGM*^y+W0$vPfFgx>s^F zv{q4@M+6nQ3VB5?x%yr!Nt#5puqd_1P}z-YkF6kSS0B9TJ03eV^+7zDdBrn4v1SHg zW?i)9Bu%2q+A?s_Yqk-khF`4n?b|L*eGyMKssOi?r0og9%zAQBYSX>PYtt>EHqS(_ zbjRxEecRyMwLMndyu~C z^Do%lqB3;PD3zfJ`(0OaV*Tq;tFb7cdkp7Xu57hg1pBdoBG`{z9tkM+{YVur_AOUBT&@hcS=791KvDCqMp1LQ66tc~){Wxrmy!aCwk6dlXMjfB~1i9e();YIILkOTnK&}lx2$B?jiXbVcKktCwj zimuarunJA>T48d(leBDqOv!FxMc-18^a<3+G#_D?*)uAawMmqEwc89I>YPV zdn9eUKc;MN(f2ZGcZnK3(?=N6e~)g__gIvAv)3#itO`%En(RW#t8tR|!BduCf`!|`~ZEE#wmpMM%HI%qM+oI<%dAF$5rT!S5 z`JD#Uj6%nAeS{&^|9i~_)kbYz?J>^>tI^cQa3}9z(prhjjmz-iZlE60?iT%5gF~x7 z`D03Ivq2#_E1qYs&G(UZ6C0d{7JWODh9tDO6CPUN!`1Q;*~T~>oU+LuQvLZEb`$RkaGKQi@w?5&}ybXrp#nartpeeYg-#S8j{G!QjyBWPeQg zso9|FNm^*oG9PJSO&t_!(bqF+b;MP4TkgZ@xTP_TWHYXoxV>>Je7I)xZf}de&fw5H zxb>xBigLU5!4Y6evq9Zty&JO92W!#PA?V#&ChZ1sl_(A271ye%-78!4HH@nvEY^5XrNN9QNU&PN!G$X_+sRg@59~G)Q8Jj^koLuOq0n5AMQS#|7L@`LcL4($CUJDeJPW6nW&F9`Upc_ zv5%YeC5)<|zSZ{KHr=SzpA2 zU80`N@TJn4^-RWGq<)1z+UgPYgQk9nt79Rf%2@AT+~%QDwU`%bv0hkUVCdJyhqrsM z30k{{FX|RoO8w98@T-TLh?y(@J`AMunY;^h0E_)FW$`_jJ~J3qLe$tD9$`Of%GkRo zgE@~;=jjj{2JZA=lQeZ;!(Ei}oNI9CTV<~t5BC$E(3N*A?i?LMQPM6C_cK(BMZclU z=o}{REOFaV%`fr>`~{cVw%aILn$Ea0bQClFFlE*)lwF<8sMAD^+T#&68O61cC{dqf zQEI~*dp+0`O?{)m=1x)Hs(R&mxT$okFihz)nY?1+&UVf7aMNI*z`8)uXE5$0aeF%K z^KjE~UiUQXX^c8SR90-h4@EPBg7;}rSOiQ-^-`$Vi3j=b4oVGwy5A#ghNgZRwz=cf zXhZ)49uC(1{`JksG09s!MqFjDgC1@c4M{U{k86?nKgu&-@uMuf1*=}?;ZVM>~AQ3V>PF@+vssHZyym0=B8 zhpElbdq+IjJWYKs)QlKT-sU0VYWpAca2Xi8+Ga#va_%5;mA#I6IM|tK>{<=(0C5+4 z9QSYwG_^-rGwe28-hSeWl8QXsLb}D85ktqheB$j;qJ^7hf_E%d{bg*t-5IF(0KYOzPyVqBj(s$_5~m--g|@{|W#qN!it`fM_|y~Kq+ zI_=?LUwt3vqS4^?5ZCa|84tINZtZ4RK6tykiK~6}tcP2U1@84`RGs16F5<2~ch1AD z;ODH);Btto>~-G5!A42vtk&W%#-%+i_~ zcQ~ok!@*+MDNjf61?PSxZcjp)hg*Yd;8z{hs5rNsxGixPJshljaqD$x{5iLcxUASq z9uD@q*kw92{+!ziPRa1Yl({-w`#H6RsOjY%VcDp}sY650sm(cnws z6Hz~2_E4~+{iws`ol_f$O1a{pU>{4-p;7152BN;9@3>dEn>6Jc9nRpKT2JRb{1tV; zjlu3U2G=n4m}cy3#*L!GadTTVbrd){e$HWRL!Z;&xVf#G`gusVxOM2A8v2-q+RecV z^)Waab&|K1aUY~t1s*D#DO1vmFAR>BQB{E|7hnsGzv(sFY&iR^Vcxy!*ed3xV%JK+PJx62p!1_ zqsBewW)oMKOiLv%?2x@dF8o97(B&>eeOzU&b zp2D4jJB(Ji)Z|pmWHEZ^3##4W6m}XD87^R2FcU`Fd+5@`sk0-vGn$%;3v;i*k^cUk zu4cqhb_b`tb2zc{!q5(Kd8C`atE<@^DffYsI}i7mbzx}zI7eFdJ5XUHQuY8RSAr9^ zHw?WW=Sb`Ro30-3Mq&C+?gG>y$HUO)agH?Vx8Y>chr;2VT&bq^xe$h~j&r0@zl9w` z{{<0T85V+7Vd&~ON7{0710F`<-A;KIv6!h1Lkq__(w38D>1ztNc5;_A^{Y@AS~$*; zh8vHd^tiWGYhVY!!OnuKU8! zh?$f^+?gkrMR1jx`sC>_RAMIgJ8@O7QfR7E9(1~|U{p3*+|1;b1aTysl}me zpRO+IK*2~(4(i7ai$YKZnerNm+y4j!2RS*YA0OEtf|keR8i*@z7&AsRTG!{Fojb%IVkWR&J97QVsclBD;vO1qy1R;9Wxe2aJI!&5m!HUUxd6E?aTTQ z)GDU9D}-fr+V8?J?{rdDgrEg6vCD*==tThqcJH84>vb#yEr>}~5LGjr{QGSRDz#zP z^{_tO!pecgglEMcc5xH62jXYqA*1WlEb<}=+}E943+^5l5(!nOrY)nej*2_ci4{-Z^nrIz!1k{`AWLNV1d zA3nnRM2kC3EmsdOc5zTl4X=jwM^Cl5QzS1lfgF@=dC(>$WQOsu|H0yli97KuxhUHl zv`NpN2tgq>-r`PByLDfZTd~bSoAl-X$KHFuS5f7E|7YeVp$G;GwpI7>5)l;??5n7N zh$4s;U1eX?#;s_Z!OOR);V~I+OI8vw~KZO?htXw zZ&C8D&cRFcR&oivSUPu*u3*W?Fv_IW!5R4OMwXPqKc$lgNMhWdC`nca;oJFBTnYSB zI+s9PR=-p~4!)g!Stam1>D+$e7Pd?G zg9|ZO#z5}$7US-EHro$`59QgN#qb2_662`m+;1ssQWppx%D1^C@bT!}c9OT@v0Oh6 zzLCc^6vM}(b6ZJXX5UMG9DE~vGfUvR(YY-oZ&AB^KMp>Rc8iL!GP2Ia5|{iQ?V_FH zFMJ;Foh^n>M(3jG%9PD(M|pcXxXLTHn_E@_Z;MWD<`UnhM(f~euk?O$F}y80w^5Z? z>Ibfc#Ceb?(oDsj9mOT~q06dEM5b;ZNTe@7`vt7$5*I#9->435@X8M_ghY{hDxe~Gq>$<27UH{u$+WNgPy@IJ%EG=P_z!e$J`|OD z^k3844L5-MaMw)7c~B>=-3{a&fYFrRe}nHOM ztbU!N49qI@4rA{hV*n3&<@?7L7Qyn@$(b<2VCE$E8*2bzGxtj_f=#b;(_vmA_!QG= zoPmQaOf}OZSUZzxFt1>D7L9+9exTZq^r%;UaC}h_taY870>cRkU-IV@4InJt&r$O} z12@@QUygnx?v=>~4o2)NafPtLb$J*oS&r|oboL`t4d5xS{K%ZrBBcB1B%&|4A>^EY z-AgvA*2XhDZOrn@&qS6M!kjgdPJwBD03J6TKZtoQ|G+==nPI zI+Og!qnsSkUi6xDiBE_gnaJ5*`H6|SRoo&nl8HnX%zf?% zKc2|BUirDX1(-z?Hj$A$KxE0XZpVBiw9IaiNRcC3$4JH#Sr+xScxI8zg($ zW9_Sm%ozJrsgK;@m7f}$k&76uf9)$kdP>&*xfLn2Lg;RI}<}b%D za;@7~`$CYOg2>*jgrP}d4xy#JBMUBaz1zsn_vVy)P7Zm}C-ew9`5`BB5PbBneh!36 zCF`ZWt;x8ivg2M)>%QwtE+P`>kItg{%hM-x8LE;ey>gcc={a0gHda0zw{tyilwChO zL_P(Qa>na<=!czU7r7E_WT&G0uA$TVOBnexh)u>V^_R$k9Igo)*~w5rD$(6ec!8WL zghBy%VM2N~f@%IuodEN@qFj#u2@6gLGQ%rB_EUT|!e9O*g(S+$J;j^;{=g&^J@570 z|MyMB*<67&)=!ayl5)?bC4Iuk#YKX$(bhgoE@gAo)W}kl0FT;?slBGFD3pd*r)FH> z%BhhgzdnBcr+w3bmaO(wpSMh_Mi%`*Wujko8@nsjdqG*&Fj%An6L+X%D z?U8mAuwOR(a*F!w;V43ZOrE6{P3HgkG)4u})|mZt7kW zPMk+XgFfo&UAXr$v1zm4zn9$qR2byF_x;&v=gUd=@JCAt&0hLhJBm+)d*S|T_nEXb z`#e|L{Lx}>X1S**_MgvCXe8VV-`+F-j4diZ&$Tvxw7`3=+>`iwuP}=8gkzkBC#=`+ ziM8d~TqE;mFAwK>F=#0`3BCuhfdJueN4qmiR< zzX!5e-i&fj^4z{{DU?U*fv0WXxd_=6aUIIP4VY4{Jg6vc=&NBAm!v0uP*~5QaYe;= zqAxH>PV=Uhd(JQa?C;XmRc!DEwd*q`E-!^=dIi$^yvgN3r3YsAe=?W1W3xBtiT*PW zlqT~Gtw4IWH>o`6Oyo~J?=9r?Rh_CgY#Pj8_XYs$@ZbxeA zNpEnwxBs>HaLGxY2NQ^|L~H8HL&{FC8~wNEThX1A>q-xTRq;b&wqtajof++PT9jeY8-eZjUI|F zZ&s3?7`uGt$R9s@r`r>4@}OKdYx6|6cRu@ZlRHN@zdA+-s{=v@%yX2G2YPoAMe!jy?$T+G-B$abvq8H=AFgm)1FMm=S#S^+`FVa zq$K<7iG;Z57ONuWP8d5R{D&X<_wV!m>pgqEQvV!#vbH_L@LJXu?@!i0ck3%Xd%pgD zpZ@)S_#u4A*a>qZRz=4poH(0Zl2D$G{@DCYgkxgw#pU66JZlee{{~$2u0UxX5~8h+ zJ?CAIQW3^7Ux2gT7?g^LkGb@icPC0kbi`bm;7uqWp+*wSr8~UG%O|S7baUxO^w_gi zZ?d^G66)YG)%$5KCI4KE>WMR#YS%z3VG_H8eZ)Gkr`cn8bzl#(_IN$SUv1fg2nEus zt@U;Dp zO^Uw9f6G{YE!%;$8ISRoEIygb&3mklyY_u_Z~XhOyIJ7t-pT7IoC63l)7|ZPU8IQS^ZIWu{{P*hR_FLK!f}q~a ztM+BoYm45D%0#)5*7h3Tsod#hWUaPEJ;oCMJ;qPsxkh{RE&WJsOFKe**7sUJn}{}& z`nIveyjLsrf7dvA7bV6mYPI&Ct~%8$UdC6=6w-}ODZe(Eoou7qA@-Xg77 zyfpn->><6GG-|C(*RI9ZeQLR;i|hKi*wXa&2cf|Wou0J&hoR>`#vX^x--&&|?g(e$ zYy$oV{@r2NH=TueZn(|c?$IvIyY=q#={Ez0j+!xVdDNC2^@*P3w6yazMeItY!miX5 zdCsS$CD$k3ykkq$@_92x4IA*yr+s>N>+)zj?`=1D5)73X#YeKx>+XE0)0=(2A2MTQXsd6m^lj1pRzmzub3_56uLzVG{Hr-$ym?ySx68(FiEJ05!R{jUd4U$gsEPGz35 zOlek`b87dR8H2xm|HX&z2+6TIGLAL5rR|F!eE0j}9jA-al-Wv?;?p}8|Nh+vFSfm< z#O9a;)+Ff8r{Dg1%#z*dl_!*mN|Va;-Al%N{r1y$236Y}bBr}^`at*o!y@)%)gDkr zDvfHh_CyTp-~9pBbdc}r8!yRO)+p$n7ydSU*}?psO1RQk$v?Pk_}^Z*Cuo!|e9)nBge6_Y zLW0`#{%vJa?R?M~pp#bq*1Jv6a!b0N1vh>2vl$1k&IEi`3BG!8=4VefU1PxxX3~u> zc)ne9x^@cClS**y=|$hZaATAOI)a7V+3T0p8HzFi=wT%&WA!h+?u@Y@$FiWi-x(R3 zSIx-;B`7a;WbeDTTaXi3&~?ubSe;YB$vsL?&gucrU$@(WoW_Es`#+j?w5F7!JCxv> zqtiaRUy8S2XR)9gyN7QsF5u`EC8&6F_$xOiT9ETuko@o$3(sEU$I&)GN)?EXcKjtU1HUB}z~Yk(m}`6bowHd1!pi2~I8q89%gh<7^8u8t4A# zfY>WXI5}4dx)M9!QTdVuxrKS|>A$k@AO~kEp2C&=?g4zz+fGLBMvn;{JHtGl=f|h5;aIp*uXxhNKaV~nkV|HsMm=Wd zuT;4j#gjj~N8?#0E{%DbzZ+S$Oyw#SPg&%<&F7l9bIfzY$5E9_RIWntR7QP#!vYhR z#XL8DvZ;EJ%9Sae>P?^A7-8aanCGUx(be-+t{7Z&-yfRbeE-9X?E8l3e%ET4p`gNI8GEL=j6i>;V7d&fCTq%AQ z+x|)D786&;JaX5FDcxupsK5QqXZGOXZDuA2HEzbw z0n~haPcLt(&+%a!6;J)9?ows|w~)!rzdW1e!`3VE*)N-C1yB)8ZZ)nX(?_jEo#R?% z2T)6x{QTOw3?H>xk?Ynze=&es#^m5WiK#wng(4^R3C;_kRxtU70T+^e)G|fBFyMxQ z0BRMJ!zN!&@=;3_`SPT&q5x_Qlb={ociKlSM!S|jK?aS+1LVsL6_4zy1mT>#&PSPeM z)NR*x15>9+ySfecW8#_Ab?Gx{mZ?4xp~xq4FTWEkXajoXuNy>!Y z@@$;nkSk=!XxBYMk*^GCw#1LR$mF0e^5+^TJcjeX2#WNha+&na-#ke;cyKk1A%; z8!2Q@Y4x2{q?9*EVbLX(GO1nMBm;9C_xVGluINlTlW&|-GtmGYqc+_Xond# z9pTq$lOJ^jzr7>B8=yl9#w%|2V=9?+XXF?Ib5N0%!NN1PsTx;cK>26`bU=~I2ZWH) zqqn>Uy}_B01}I*U&UD@C$J8ezyA>&^%T5C_k+EB)h4q6C%r5-W7v4&GlO|^}ekq?7lOCjfZ9DMYekSiVFjJVP zeY^rhhqfzHe0$QYG&$37caN(ftxAWs;d>j`oHQ$qn$DzNS)^0x&=v(F?S=bCD0?%R zbjQkSKPE^Mi*C%seJH z9#nP3588<5)1bzrn`x5fGwGQmQp68IyWn%J*ZIE7GOj$p&Txrh z)Y=Svsp+KpOg{)ZQ~ji-q&;fXMkf6!nG{F8wz&!>e-e&EanEjs-Z-Qt-4B|h$hAX) za}AQBnRMSlQZ98#vlQvzeU}UjG%D%yiWENxT2;jtQoe!O!lYX_p7n#KE7Hc>NC(y0 zy_HF?<&pZS*EUU&@?I}AFx#1Q-AqzGb!Lhp&AP7Gz{D}>k)%_8%w*jAkC2Y4)wL6? z{%25OlNH+enYqB<*HWkGx}k%y_iA$7KVv7r)sF^+)}n zaroXQG`?b>pugRB#@8L5C{FssG``eQytkrdpNnczgC0Lg;LXob1QEOlhG3ol{qyp>AF!X&b z>J1F^!1l@e{22IZQra^EbA(BsUL_@2mor3>uYPJkj^VT-cKac4=0vb&m5Pst_>fafYCI<{0I64`xs8YUnA1$U=Mbsd#)_bB zAHHX}k2!-Iqi|aQQ>#ctJx2JLvv@2Fsv~{eD5wUv#vl)Q$Hi$SGwG%nQpk-El=7IH zNBfu*COw`R6Tnm|Qs(1hd`v2nKDtUuyHO4l_p5&$>toWH6f~Zcb|VADeQNMH9|JY? z=It8;n9F!j-9FyOoMV_EoD_Sb94PnMPm>p4oEKEpKUa`mZ)D08sq(XlJ_f3)XH@I9R@GO61s*lM*UtYdCfGJd@EALP9 zF}e842G^5qU=#%VpnimBhL5_09)CTV21Y0kJznTc9|L_i?9B22<`VALduREWd?s}+ zB;&y-Cs)DB8?${(0e(Bxkpav_MXLU6j*lr~l4tyq047_J#(U=Z80f>Ho5{K`)&&b= zbLe~@Q^KTHry>HFEa>vB7Wf$G%pLQ|0x`;g6_Vd^p^qtJQlCmPK#WW#zWTo`@-dgu zNB>;EAOM0Zu>NOhv5%@?Qlmxl0-$t7TGVKXkAYTw<4!VBjJ2h~I=*qKkGaaEdz0q` zFsXQM-W%y-pm{%2NLGwd4lJ3%XO{VxDki;sbyfhAj2~HV3Id56znV$^s3S|qCX z!Xz)Zgp6{?8fvxH$H?r_i<1MyoKU3PN7wlf>BTZKm5f4QE|qs%uM+Ty_F+|IC>fDs zid6NN4Jv{_*SGbWVy|&W6{-GPigi(I$!Gez2yPjr99E=p(k7J|#-!lI^*#eZlLK37 zaqwo98P24pQFSIJQIVpWMyt#S{33SNni$wyyFw|VM(r619r19Di8+9W!L6|>Gm1%n zOcG%>W1AD8mHcsw%8X`GoAXsBIk3pix7n&PW0>@4UZsiIkKfj#+f-&Olb$cVYGPou zm3E@o9<}**CcS#)iiz2ap6=B+l^KU$XpIQ?8QZ)^k!t$vP?_;e`g{FllN=a!^?%=~ zG7}j4xy*#@RM;=ORALg7q;Vo%Xsl@mdM9a*%1j0`v&1AO4$lG4UX_`GU(CE>69da{ zk!PREOa-&J$i!?@q!l6iRc0FQo@Iq5W~(BtZ$xoSx`MC^L)M7ErLoPh4WpYTsLTxX zOdIk|a$*%}N3#Pe1KY7lbe@TU{kS*upvug`ox1gsiHTOE{h<^srS{B*ah5NXRkC*{Dc|ZaShe^Pr9#$~G}k&{c0fsxq)Y zTOPe&Vqk+FZAo!kYV!gx$3&pk*q-%@bnNEiDzgxE9XW53vkrCLbV6ldq26#f)5O3^ zJ#@oKm5IQ)C!RAgYjEx@C^}5-ffXBiK!k&h?SUnGAoR4#z={pspKg)^OEy0AjLIy< zcd<9k#H_@3+AK+BVCgoEOEodDc6T(T7&Ns9mTpLl2s#_v18X-XBw1yagIS+!lCw;a zHiV?846I?#inAsL7V!#Cs>;9`_AE>?F|ddic_`9N?OBE2;A|1vHnwMpBF&c4eGD6a z+9YPNf?0M|Vl`vor%XhI!Ukoi#2Uu_aneLAQrNfWRAMcY`qf(@QvKgDRR%WqpKC>U z-Pl^#-L-!{uQIT?dsZDcRRz1ds%MtUY=8zWbSEP#07gQz+%5KR~6EjbdN* z*uvPrcoQ*AVFM{Uf!YGgzfZjtBGvaPRGDo|>QW_g7L4tkitnmRk;=e3@L0({Q&sQ~ zls#FjGVl(B7mMJ2G!`K{nAyQgas!TlYt!-OP zaz?{<9#W+;@KQ(-TTBc*6%i8Ugiu`xjQuOtgp7plpb_w6^r^Q(aOc#hQsBpUx=Q4d z7+VZqM%A;mDw9ZO9&M^>IGuT&%D~qVdVI5qfxqKqC}pQmi{b0=Y!vw@#=79|*z93G zhK<{35(9U~c#Sy1*r!n@Vz458riwt=tV@+8QPo8>1m2Rce39~EYzRCi1uSeLZ!5ed zp=Z{csv(a_=p;_STjGgYXFlyv$+~qKXW(-Q*|W-mf!}3s$aIxqv$W(LV^uW@ncdnLaG(ke+HLv7B1E*YecGgdM0P!wYlxWatkr=++^QAi!<=rgzV8Wgp7@W=Voun zY?WbCA}z#RRoFB^q%!u&QVZgW!u~pk%Sc1VSZR$&m56D&*IOe}eUAlP3VcKN6l%Fn#@4_;RCLcm z&YXv*?$APus!CyeHCd!GY|a7;qy*o#AhH1iOR6Wb1jfUg)J3C4rBebge+rQ3KaJFQZC~noZFSwh*bGVBxiE* z!%mxRQBl4krG+k28Mbnk1(K(*Rf4$0*cUS`h)W9lVmX%q2iC*LT{SjTY{Q9lwfzdt zz=72)X@&&@CstCkl`6v)PPafVDr})33K;u9ODr>1m!q%`S8*AIFrP15BhuwIt2qNF zSmUEpEh>T|>}caPD#IpDu|O^;Y@#6GChJ*mjY#!9*K#RvlihY{vV|D9%JObo#~HZE zq}WLo3|wWg(t4F)-%hkZG8OinAmB)Q1TGchMG@O@rd2(hcndKZ3Y#s6%Z&9JXF;SZtk*^^1FpE+@~jb@^(M~16(?=f684O3fje%Kv{_}? z=VL8uO2w~05OCJDsjx<*iZ;<)3Y>Mp2eq6(V@+__C5FVP3>z}qqNZep4G{#~c~8|^ zBU0^Cv0Mt=dDkV6vJeB8Uh;KYREAB|5(|xuNmAHkLDV1^UvG^_^$5z7dI6_ki}NEa zYJy|%e2Z-=!xjv;Ku#-cfgtJ_dtJ+3G`8iG!rt7@Wx#28^CfFU%DW{_W!Q?L7WJG| z*a|@?jJ-X?f;geDw|8(EGP~uHHGyh&aztV)v?NMnTaTj_0kF3RS=4b1b?kxw_#AIx zdDeuKf6H!xtT5_G+V)XYLFMpCzU}9+)PN(XV-J8KNC3HLLr52I+$)fIl#pON1+jft zVe^R?%2+qbMlchH6!y|S5b$a?J7Lqa*Q)<5#@lG+q+9)i-?#24@kWoBGq>}4rU@c+cRaBVs_$=X>@`!?7LD+ zWCv_nBH#)BL%AIymEU!eGi-5*rIa{@EhhrL;%Dovk?jh5@e~kp6(6C574uocU3@0w zG-uf7l&@k&wxPL1z?pngfgOV8p5Y9eRcI+?E1F9L+|7^GStDB%);S3Xyv@>n%8N0# z1s>;Pp0k``A5mJ28HvTOgb4VfufJ%ANCh_~bB2x8bEb{5V&IV=0v_wOHFk(p+b)GO zY&)gj7>^g*(F)s6#4J1-^sH+mw;7#kDhPPA8>QMIQhK8_&ai)2cf6Yv_5%^{dEZoI zhe)Njq;rOirz9U^E5!Cjh0P!WUh#XY?GT)E24~o6N&_-WiBi}WBIYsn`~^#5gTh`t z2LfJm>7X4VB?V`4hJ8S(MCN+dE9~z?z^~pU(+-jHu0PKi_7f!=nWe0Q>O%xP@3)rP zAyUPiS)5@rGA*U7RoGl2;G2K2)((;C9=gC8w)vc;lr;*AAtD0LMoMThw|2F{x@Cia z?_N4=he(H|9L})!(k-Q|QrP=Mz`GxuYKKUv!52BhzD%=}vJ%&y2>Ac6%e6zK+-A9) zVZTsfmHCWTz`I2RA_O;=+96VD%S)VL<0i8vw<$#{;^%*<^?G^10oAa>Xi zJBdK7qsvK48HjguDG|hOTVgj6YZ-g-gr$syc-)l=Vy`W+mxy)vg;AoL`CJy@7gi>S z-L}MTBGxn3>6oRA`FM_%3u3D+v7Lwwj6Fkna^`l-Q&`8#g4k?JY$hU#vB!^C%9yLL z$14P}!Is!S1fn(V>+KM!zQYyHu;rAlXKw2pJlcssJg0S?9U|4WzRDRkhZ6eCQf9+N zMFb)~cU9RTQq^6RoMGb=Ev3vtPe24>LN{NwL!^qEt2o039ki4(6Hf&q5H)IEY==n2 z*H?3feMhN<<};cBw*?W1D}`LJL!|7G8qTnfDEZJV1+k@%h}eQY|CAjfosw!f!+OM9 zNd_a%_>D5RR}9_gD%+4D3Sz9JU3> z$k~C;fRZ)M&6tSJk(aNvEnmBXA-on=XA9J^u$@5I1WNWaS1|$CYbQhWty!@hAQd;; z1%iFG(?)`%e6@@1#x0g?N65-qX*Uq|l5t>GxIrF*ATMzu+JT7PP4T z-@6A4n;2(<)Z={jFhuy8mDwXwX|ugx*yr1Aq}0J`2FI56``AHZp4Q^xQ{8 zqyrJTkD`dTSJ@*{)$RMiu%EZu)PrEn&-)qTj6qrUh?Er+4~D(D#YRds9A2D=b|9kT z5r>rSsk29AYuL3_4Jldun1eQh|WQd3c<=P`sZg3(P_WEWUDF{Bj&WQ~U#D+wMsOlXx z_J~yTheKf4kDF}jsZiLDhbZnUrPw1X8z zu7n-|L(yC7dJqKrj1zkuh`mSHQO54wU?ZbUVXco6G1P$=dXymqET6YWr1SDIFzm(k zHuaQ3d*s9-2Lc`<#F4MBut%hd>yLwBe_dx&4?=N&%1J>HqgMi)uP8@e2j-OyB8GCq*jf^6NwLeM3C&~zwXgGXc9;+WI!k3jTlEAP}R@l^o@a11Q5#vC_Bryc=Z>qLO zr0Sc_f?{UR)RPQ`{gqPutxh=?^>AXd1F<=oA(7yQ zYI{Vgz99t+`y|q)9)wyy;Y5@J5tYJH@msI7N2E&3uz+-gcbD4KgMjV3oLJ^SM5Zz% zEHo;!N2D_N|I!%ixx}WPY=!mU#2g1=P8vhfgD1}(k@7t0U|1&_7Gd@MAS~R86B8T= zh}@gpbV9f+BJ!?$&c)SieO!Ejh2S zewSkJjOElGpVn-h~Ah{>Etb|6x+Sq@{~ zIX1N*Wx$(51k!4(f59O7b7G|fu`-7t@kTDRN2EgeA{h4AY@2#g6!sV=1~?D{II+cn z*m98}y{GXNdqld@I2R1-JjEfi<}oBZU0-XD zpe6ZWSoi5REjgpG?wlCrKn&x=E(c;)J`z2p##ikT>1yKwFs$P=o0cG9q$4MObRd4@ z#6|~VV*x{QmW+wPtbc$QuT~-#f?@5a+8{_wY0rr-9f&VEvB-f~#EDb~BE66mF?QP& zn|2_z<+dUs-f$q^;KWD=Vk9SaI}m$|7}Cr_uG%Bg)sSK^?1{-XZAFsJ6P)eE+X$KN@ZYm+-We1`=Cw_4te&Ixv1F?}4c@9Kg2`gpno{2Us zMas}Ur9`~rK)lO|(GJ9DPHc4`ww5xa?MX%Uh*XSu2+A01MLMUI-ih3#R-EYVK=kIs za0g-}C$=~cTR2hRKopd*a$ME%HZ4Vxlov~j;5Xwyyvd1h2O^vkYaNJnoJe;dGRhf} z7(>U|v=o_GEiM!ByaVw(C;rQU_?i>59f-M{NOT|$U1mt847zHMNR>eqVAy@X+nf!u z#vb6r+YUr;PJ}xU;hb3EK&<9Ok^_-c!I1XZq}CphYMWdE!`hFvIU8iJJ;I3(9EcA% zG0K4$#fc~fB8n5K4n*1&hV;|Mwf2Zq*W@Y~_RtucvpJ}+hdA+P2jb71_|1X%jT3%T zL4_M?L@moWam+!+@v97pvYxBsx87D?K5FpkQ3bqtA^6bgX`=OsWzw<47+Q%%{4=Q>|LDrlLPT5PQ2|vyv>Pk9Efi? zF~)%y!-+@-B9ark9EjbVNOvI8YgrvMxGH-@s*>u!u$IGYt{Jj{TXLeE1JRBXT^)!Q zIq{(b@gXO^cOZV?#P1Ho@0?ibKrH3NHV0xmCz2e9Bu*4N5XE(@p0SXjHrI5I!b0ka zxZQ!cgA*Mbhz^|S>Oj26iT53d_c`&k1Mv+f20IXgIWg0Ln8k_J4#a9s>~bJ>apIH% zaf%Z;4#Y)HR5}oq^$f#7nhvq~<}h#cb&QBR9f&(Q@vsBYo)euNh|ZjN(}8%C6MY?s zew_Huf%u*igB^&$oS5i9OytCT2Vwyy);JJIFT^$7?m%qkM4|(MAU|ZJI1mUdfyi?p z;Ccg58elvv1Mo7-cTA_b$og+qBL%I|h4zZ>QEJCxs#@<$!YA4U1I4&~3HyoW=1 z50t;-Q2q|eKXxep1m&MQlz)Npfez&ZQT|Vd@}E#X+@T!fhS(T~@-Zl%>QFuv<#Qa$ zkz>ymJCq~Co2_;zUxV@}hjPr(!FD*5??Cx(hjOHXxt1SuI38IN?D)S~p5bsjB6}>; zp&Su8*YW~~<1u)al{l1R$m{>;a`;NvAe-)GCpxG9*GeT;>hNvC`}9Bh+oZd#(4qZw zxBk1o2De|^zx!);>n;A>-pH-DbL%bLdRw>N+O4;Dds?_XZQPz#ZcjV6r={D|*6nHS z_Oy4?7TmNAH*LjD+i}yD+_WucZOvWZyC$8{UGF^Ppj8Up)m{H}*P-3@aCcqa-5zka zDf&5_t*}d+Y@4^Z9`0@*x!YLocAAT=2P@shUIcN`!J-s4r<2`!`WkItcl+7h26wmP z-F*S>{secQgu7qk4u@+hd?QZ&l}7dC)pGICfN=NoIQfd=uF>D*R)#2oJ1_PZGp#dzT~=eoc4ZsJnmF-ACKop&h-sf7RVr?CNiJ z_FC7$YYjiXi|-r6UVGO!6aI5|AH2IC-#r%K9#44Cp`{`w;S|?sRzqjuk<0Kp$lx(6_sAC_eNOSSbRJ>bVjn(ZA_nIa z$D6@rxW@vW;)$YsBGQ;@=>a*@wT?7`l5L-aVG@ln?MQMe5zN3Xmg_W}kS#kedL)J-@*{N5VbN zqJu*_9;J4;XMDgXi%v`-shBKO2Sf5j;W<{e&38;4qW9*q=K>5`3u%psSY z9uRZvg|KfANvTDj+Iz3EVk$ocX*5Xc)t4xCf& zS>ZM2uZ?iXUlTcS?s;^sxp?jweaJ0z$_w0fjk$&I+9wV&k)P<4F1e>5mN;amF1^MaSl2vS_iS9`1@Ey>*k#D+1>uwzJn9;A zi(T@Qkx8BAkmHo_6z(MA~6 z3gVj$L#a|A=vzE@LP`xR1kh z9n0xHA`~M}oyL`}ySA~ZE+brTJ4YjFUB=0Ra35>yG+q~T0;ao;`32!VZrF9~vE<2L zViVZL#MU%|86%}##zBL4)m{keNn@#{M&(Ow>dCm&M>xarAc( zxX++*iZQ2oIObj3Tpp5~y~gIEV37ct5#*%nydogl+Y6z&Nlq~4GQSCkfop9VqRo-w zKGVu^Je$_pNWqLQn6CvyUk8HE`Qkov3}&c_SZ`AgW~jmZGay{&u#x1f4K_lspaji? zbJTS{9T2VUh0vTlM-WShvZ)3$|Lo&4|9s{^@VSB9XBaxn*rJU#^uu_QYvOla-Vkzvp4<0ZT2Pv;hpAyin_LWq@-ZC*{Cq5)re@Dt0LyK!pvB$>SJsW zEV)86V;x}3X@0FOu5)y~O@#AIU%4R%u3^5gYngQnvzxihT?WE+{xj!U)GlBawf(Mh zv0clIZu_7gY_qxim^p44pE>S%2jcmCG=rVX{B$6k=D7Qu=D2g7DX$DO!_s+%)lI@&cNK!mf&+Vug92^W0o@JWBz}2Cg{$# zL}wz_A(!R2rHlm%)w(!Fl8&M#wZRPr~gvu9S$o4G^ct0moWDCG|RSV>kGK7QV>FG z5G-cw{d7wy+FAz~Pa`|5m>}msN`%w83om7ZSOgErIm>#mcEc92x`WiVHq#Qp3e9!x ziCM@T){(%hXM8n^+se*cO2JytSMDMP!{O}Lv=}B!FnNE#flCA1toay+U?N{A7!B^dx=Dv8( zRCG~=md)L&t^kCgaV?9AEDnz1! zmLa}I9<8*OS#OCP!eY41_oFpKVYk~M?B#tRuq@zw#Zm}nnp&_|5cUfP=JJID2Rkf2 z*n1CT3}Vc2^+J^2!txy#Kn%ykq}Bj?XBVmn$6z{_MHsP=W4_Hoj``|Bj*9(ikiE$_ z5Ka>}xU48yj|J`WT-K$mgTu=j(N?#tWo|2EI<2#rb;CB^TCCUkqV0N}FK*>bHEuQQ zB|WuOKQTFv>)N46>$P8l^iQ$8rb@W4t(Pff!(voI3Q2TX4;3k>uIs8^!MecKORuuO zZ{{^2^;6p9vc@Y0n7XbNi*!l56=z@H$ZINRY_{{NwdJ_JXKmKLJ=-jbOTlD(kGid) zTMEa8^)kEKO1w_%`EHTKYP}e?dV8MTy1=)uRjpSq<1#RLuI<{`=`93v(P544sz)QaoE$tDt(W1}R_e}1aMF3T?`$lll;pBz zIMPubvR!-pp~bu{NH+PzdU zPm#@Sm!D=maz(VRKcRYD5cyUGHo zRiu3(f4GsvIoQ6P6ckVur(&5mu)%2zfqN6=PyX;DkEYstm%Fed=RP%t!=8`_!mR0%N~g zAuPPwM@Uk{7L$}wiWDJfOgsiC?bvE!Mk~@TOu8tp4g!fSQ?{9yF^ZJZvQlOC(V1^I zF=Oe>uc{1!d{0)znV8>k<{hu73_^H)5W<@PE!{XB3Y9^CZlD#y23}T)UFaWn znuLtUg1O~BLh`KJWkMz>(mIdEAi&l%ez%F4h~=J}mZ@@f(6!lPVz4M%O3PA}!SZfl zd3#OFWV$vbDuWQ$lb83Im?`+xK3S|X2w-)o+HYd8TwT?RMJltEu3x-~nMT*IP-PIH z8fb;Efdwjo(9-w>lMsZJCg%GH+*=1s2*OGclExr#6cT&T#LQHr*pNI`PBdNPL=%IM zP<+!%Dzh2SmE(s@%xpzE-ZEEZHZkeW%)=&T4xT)BUR0Tl^gSFgF>~pA$Wa+gO7vLC zQ4=!{Q=&hftuh<%JzP0vV&=o`+2w-DtY=cMs^cbR0luqVSt_#*{c!CG6SEMGiTBT| z%vvUWT7S~SEW-EsX{O2`-0__i!oEAF5(s7tI%N`qP{-g5l|ZOs!f6wMK*of0A0bJz z&zO)UiZn;kn3eE&MI@P+rMN3RX{sEAB0MY3nwUsMTIoqundP{1HYA&vWs0;RBt>PG zAx$hM#l$Sf-PI^rWe_B2x+B%ZtWcyKP0y+fLIBP8rJ0zOc%rG&l73pB;X_bkyXbRfsXY($%HI-)Xgdx!45Xky^xzK>{l7`7SAs>G20bsv1gykOonT3W{HW3Q>59Ry($A| zur$8Z#OzR{3DO>wnE>6P%!FXltzUPk#CRrsRe#xpz&%_4)lQXx1NJX96((knBGvq5 zhswYK`|8yzCI-&etG(h>2JY2PrB_YNKBO>r+O9H~%5h%edN|ym4WNC#jaWt1Gnd{7BMOV z*Jsm>btWcJkv2AsRv9=pLzdK=7&tYTgltxsq0A$VQ%uZZMH(kdoQ!15Yeq{OHRVsVnSO;`IE_ys)^X-O3$ zb4HOSc~c{}*B5u|G zb9_ublU^uX7Qp1<+jwENkAV;C-XzN7H`av||D=0o`It*+a};IR8<{*s+SFpEkI7}S zJh*;U0Fk(A6=z%V56L?xDoq~^)XmA z@rg{z2R1Tr*c>Ib8}2=00WmzY|GI;<}8yvgX*^iKsESj4EBujQD>OcqnMIg zjkUqyQru&NkAc(Wo+H}>7&u&xc!&EKI9(dgjSFDlaGBS5n2$NZq>n3h1~9Tx@$nEJ z0)I){GnDaZY>t%NcCe3tzoc2jZa)IPlI86BK|bUNlloUuI;AcIo{q}D!w;g}V2AN6 zO{ZK*9f7AK{V`@>;O%I^Ga+>bo{rTm>J1D$8DCzd3`CuQ7vt*RYYhxM8SRrO1yN_< z#W>r(#=s=ttQJ!Cpw7TYv9MXSfq@U=<8sRC(;4_5%0I3&Fz`XNId<5Of&bxHo2v$9 zFOwTjs;6W*UC=n(7LyuZF;Kgqwd5W1gT}*3eD$({K?3jXv6K&|3xfAx%kAX`W+!^+ z@2XDvL6a1@>N~m2K*2xo=vm4&(0|Wm+v)QNp82AQeH!C(Uu>D`pr#vuS z4($H?*9#5I7DRbAo%Lg;qp!QIz`(?!)n8Un`jswdrXpYY5|+Jw7r}DBKQYx0g4KTL z{!0dCGn0dd*QWbHa}>FDcyO+Pf?fWnlng%zHhIdQaturqldqdvpXmq9$M-h%x@-dl ztNX?Cl$4}592WQa7qbiutnM2YQ9hB*z~WwX!+8U&8nU=P2OM@c$516w%noiqajd-#?Wl!K!)u!UFLl4@XJ>%Nnp@5jK- z&3`x9z`)kMWo3aM13P!+EoTi3?AYG9l(eGP1sgWE_Zb5NJN8D{Aw~u^?ED)~8yMKA zFK3qeF|bcFyPq^L3z^(}avddt=#{NklJF^T+|k4}#5i z;E{a>W-{9Sd9hK>4n;2hT;6M7;LN^_EgEE~YbS2&+jbj}2~1`$BpK(cgguw^!cIR# zVRFcimxmeT>{aB;KZcMeU3-E|py~1MUOC(V!CG5+_jW%9@%V0MM;aJdVrRQ;^<%10 z+raYC2Iznym;V^D#gBsKefNqn1_*Z6io0X{7--dG>#pJu^){z0C$;w2_=iVLRM?qQb5KR_{2EqD>?y%60fg<|yVX`hX2A0L)m*@L2 z>G-9*pSsil^cwiW1yQo8%>(DCP#{XcCa6@gUL@u)svnq5DYb{k9sQH zkJ--T4lC;R`MXsLPrY?YjPIdQ$3Q}?f1%0Yw z!ri0=8)Zc^`IaHYq~;20wj!4dxuq(A+JxuIfNXtO$y`Ow9&kNrxkg#gm_quVI_8rF zZRu3skct3mJ#@WZyH5D1g^IkZmqa?OQPvtJ|7lensh^@jY_*4J~RiV}JTvE)0G%N>{JG|BL0BRib1oz%jPgqFExm(DFEiFU26x8D zOQd%REMAc>jSM55%SerawAVM*l2#?KgNmni;~T-GcNw|i_&)!YN_v&R4xxwt*Bzu& z87cCbc8<8BK}QwOm59y~X;nrFUeQqL(|v0FZGZ~?9td%3T8F5}qVmzhD zStHtzLgY`?F^}9e;)<4Em_$`}l}Rb`=c<_}wBPnRE%h%2UqM{IP*QpPxvNb6!?%aD zygfXE4}JTGXcKjr$@hh)Xc={I|D}Yt*=V9lnf%1~oXP5WW+`&c_$NqN@vpI%c|2X` zVr6J?Wpfly$=nw`q_FsN1P>Z&&zlQQDZUBa1u5>un)Wfdr%n%GZ5 zmz%g8=DDd~OtqFXRjhcbWBS1z8YnLdT;I*rTFO%yxXpcUB2~h_-gC@z!zUXnwPd6U z#Z$TQlN%S9xHRUu{?E(Hv_zpw#Z$KY&)3g2amhH}o^$iH9G)7*lRvj-V^ReC>pg?( z*Lgy^mVi^Qc+w|y_DnZ%Cz$7g0rfJ~Y{c9c7-|{f2D_*r)jTg=S5D*2*q=LTCb**krK!yFi*PyJ8MKH#~6GSI|sC*Jdi+cAHKAA z=jDnNi*br4ciy|9l*19o?PftzhacmrMEb)dC8%oqj~yh+!3ZSdn5WrmlT$?Q!c@hR zGU>Hul=%?IZDF3U{;P^a&cIB?Q?#mo8091ca?vcv^Vq;GS4A*?t`c-*%fQDxlrIoS zMzNsAFAPgi!|V%{pxT6CFEpk|ejvG)1zq2B^nqFt;$8ys!04XWXIhXei5!zyD}v9< zK_-src|FCq17$|CpcXxb@2M1F;nhk|<(}a^T2SOUkc>b(pZ#fbsR-DvSAt46|MYAV ziU|jj^KtGSzFL$bLaCdSpp->lb?{KMHjtdff^P0HD7HidFt;c{C9#8g+)OdjKyn%j zmLK@*jH9(8Zn#4Uu01;AuMfx+zYJt2vY=+22Cls*0)Kmypo?nzB2eBFJ@E3CdjiOE1`WW)g9(8(;8zx8!s!nN=sz z(9=u4d*Mb3as@V&($|BVKJ~@SgXpKim9t9l)q^v?c&aG{n9OVy3kiCl_n?)@wPes_ zC?U1UD+l#{Ac(?0X1bh(G=AiRkx^-NOF(BUA$4g{BR_nkafBsZ2>Ri7!kl>}AHKQmXIY~lZ`V&pEKe-h3BIvXaA?KIPrG`9$T4olkFmzr zJ@`uh;Y;>hs5_vHL~AbWSu*_dR~}^7k=NOLKmuzLeD^c&eDizco^w?vl!;1{s&jiH zfB)v4XYLLbE?x66J6Kcc)^;y__`|rxJ5HCRDYKO(C8u{R9{0nCFSWas^h~RYHnL`o z{_yZieZC$tbM2nf7pwA=WlFQEi>LRjojK&2J}*7|hepDKXs#}jg*LnE;m&XM`(fy$ z1#7k?rsh>vD=|uFRbFc1wlxbT4gI0tTb&=is~PE7Ry9v&A;CBN@xDhpcW>Ui@0SC9 z8Zmmt%;n2tx9q7;tWRS(wMDE1$*LuIzVPrP~0me}RXXU-Ts;->*$_U+xh^P~6u z@rGdGDl#`tt57Rp^zW%I-CuqElkdLMw!6Lh?U&eYhUG&rjD!Ds1cO4_G`#*tU4b{* zbL<)RG<%A_o?zPRF=qAZz&fx;cnSS|#P4PNY~1#z#HS9dJtWb8y|g_(YoD|&ZJ+oo zKKcKpBdApWuW$M9)sCaR;uW|hKAZmviQ4hTcTG~@K3%eraNBOn{{7c~vo~2!)(x*0 zSZCG+uTB_u_9^?2z0cleZ?V_d>v;9zFYWzh_7dyCUNO90W-sH@OL)=N%UmAG?8dr6 zZdbf$`(^c=_PvM~m1_TqF}|oB)EfOorCo5OkVpFkuM=WVCmcy69p4!=y?4c5+N;Uz z#Gc1XFjPV>qa1x7ZHc1=Lx1U5`b)=%<3!o>_-)W$@uD(8XnO)bnLq2KzV$ro=qHy- zp2Le;K=m2x)Q{3A;)oVU=;iunlK(8q>AzO4^Fsd9V6-!%)(Gx7-ZEoNT3y<^CdcYs zv|qGO$P{fNsU%bT`z+t4`l;Wc);`1kQn@%z``ZcU@C-i_+Cy8KM0%(56EA&B+oQeH zar#TELEl4LVy|eQM$mEg|BbA1n~o7jX?y7$pT$|yc|Hx9+L>y#`X3=$L&uOD`i|&7 zT?28?=xq{P`d?A6{#i((>!P(tw9D?V&WR(%XYE-3ed3c|Mq4CTlW}eD;wa;h`W~&7 zx>Uhwgt2wTZLM5C-v2naPyWZ5Y3n`qd#S|j^$fn|bj9WWBB3lhlT1ednUHdbz$&Yp-4wxJN5-dlz?;CRwAky|iB|Gw&0n)Mmlx zf31Jhg4h32n?;TOf3>}$U()*+tq&G7^eVzXi+Fs+kXrGX6 zwQXc+n_6RhX}z<)&z~_q2lQ=iOM8aVIfy4D_06JA+C$}{2Jvj7zfa?gsqfYH8=qs^ zlUN+5eWIr-wNI-p@LjZB-|N3c=OdoQ^xcasy6W`&(DVXLf+!WwL87(ixu{)~(qF;T zUvahc662PpduViEnJ(X061XjNBz>>{XYr}C@4Q5vnqEck^k3{1XCul;o~G+*vhB3H zE;yc!73G?)Cp5m!TqCA3@mXk~x+Goyq%G1+g$Ak%4f53hq z3&hP%u_`xOJjQK})%IDxi@oN*bhLJaZr2H!v_J5F;2y19lOkkkHlzMYaKic&b!wKV zm8Ghc8Q+Z-uW_3g`bIiFo8*(uPXD!Ig;pv` zNe^!DUuk-}X0sS=C!y(+wr~C4$Qrka7e{D&g`L>ozY>;{ZdKEk_M&e>+t%K-J;pL^ zpSZ$;v@SOi{@c!<0!z)Gt@cn$^tNedaINqE89$R*AO6qCz1Fh`eLRztX%|4gM=DE5^1seQU zdLOCvXMuaPe$V_)r1la=i%-UFt=#>+!GA?@DoszIclu14R73t1T?I|Ew9+wYpU^oQ z{8!rbA*)v3rcdNoGVY;mu~+jr2|}~=tly2Saa&l?Mu`pnD{+OzRVOcp_F4CMnD>aQ z9!QI;EB0zf8;{k?_2d0%w@?1ZnQ7}i_Is(s?e(AWGd1|H^y{qO2gbXj!G9(0ZgEW- z{8tVBE2D?1!GEP`n?nB-`BDw}SMD~8Mi?y~<91-VR_6aMKAHd0(HbeX%%5nF_Fvx; zd;IrnvKstX|F^vk4fQu%f&a`EXz*X@eI)gEdLO56Yx~Ua#Aq*ZwD@G)*2>-A{}cYJ z1{n=kpy3KMT!Drw&~ODBu0X>TXt)9mSD@hvG+crIlPlnEshA0)#beyoNNu0>yVz^~ zOGj%*h%NIc+N1r~x5OU*{hF)>|J8rSckut96ypr(_S-b;nKmt`nCH^^Zv)qGUFZrl z+y@Q!LBoAu-)A=bJ{o=>4Zn|u--mVu8v28V{-B{h(0&bOOQYfU(eV3d_uAP2Mzs!_G@T(UN-ax4gEnwf6&k$Xjh=2KWOL=8u|n6*U+FJH1r1z{lWjA{XvD& zyyD!UElZ}2{O4C6z1^$J^YtBAn|a=F?*#le|IP5SHuW8vKi8$#+aG=P&ymxX#2z|V zv5~JJsokk{N~7HP<>P<&u-k)UEhGq;hrEp*?DpXg9L? zKh*Zvz21=fU+zD8{i#Z^7@;_HhSDf+=d`b0xYujha8YfXH~8KczM8f(FTv1cT2k=9 z%+LQfdvE#`)z$6$uT_{l=iG7bIB)J>u&WSJKt)4rV#H`nFdEaMi6*AeXiQ9JqDEs9 z4fdj9FE-eV4JtM$*q~qo_J}ATih?3YTUGWQ_dBa7+`wF`7&ykgzu&me3muG_>oe!v zYp-^$W_Nn10TYd89lwn}aqoGa3Jdsj@< z_G+IyJBGI^(opLSpMVj2?_V=fJF9)}ZvDo;P(!UTd;)*o_t5?6L-(6YsP=idZ$!&$ z8fvBC(|Tm`qau^7z4&DB-@#Wj&~n2P{C8%tiP=|eDO(Wx9ICe5S@l+yhFM@(dak-tWnxmQEd>!lnHpxEVF`*TaG01Q)s_=O zZ5bLS%COjmp15OTj^lIA`#wCQVP+YY5BEL2Z(`D`El;-eJf&e~;(4|_d1zuz;*;l_ zx}MN5(+x}4O^+X&7<}CKactK#4Ko!??Bk~<<}99Pd$(g6X0p*Nbo*2H=O*1H?p(Dz z(Cdf>i7+g^%xD+is1i zC9YS?TVL(e5>7*Yb!$~EQ2-mQcW4QRAzN=9Iyj6dJO}mH1l1Q7wp>6dh%mUvbz6Php75*P8#6AsoA)zxyR?*c86Wk}zh$+d)~dbjU0Orq9GfBvQ80KYa5%S@_M>0_Oo7OsN6>sg}q5`3Z=p)Mg-GkEPb6kEphsZLOHG zcz| zA*NQ#hXbB?Ax@!lzL!=*OskepzIp70IBCdlo}||jV#*^gzzIW4@uZ(#E!IEul0MxK z>rT|@pMh8B=N*uK$4=G|Gpps*)(^axNW@&odp7cwq#kSjC(kB^WOHPgc zXbjJTy#uoC?c7>Id{^SdLZTtQ^Q2!|Ee;lYNx#<+`!Cn%Usf&ecJU6#E_d^42{GlC z7Ylm~G1-%Td9}>(^$tkiOIK=RtQJ2NdaA#7_I!4$#=?ebu`kbyg)N5IXG;HJBhr85 zNgrD+(l2|_zu6EcifjBq`fnPM{u@vFjn(p+%{w4%*Gg(EY^)Z4=X&vI6Z%6>`c2jH zPJnkn1~_ilSlCo8R^@oHu+b2!OzGchMEahd^qZ^2iEJb$%{uD46)3VKCBVx!#wHZszv5SFZ$OT;(U3HKS)2m5$VT! z(r>MnkAl4eGWbzNjfJhKH(6dhT8DaLNfsUOZZje$AAAd?V72_oUx}_V+dKfb96pQRC5$YH=aMi$|*ralw>+K_k*H zFs1*b5$Qkiq~D40Q48;Y^t33tE>gyelzW46dJi%`CvS++J z!3y*Uru0i1k$#CO{kM%sKg^WAQzO!M^rTOy7Nw`X_`lo`rT1!{7wHc-BK=`g`h|^1 zzrd9K`$nYy&XoR*Mx^iNNxugzm#=p~+V0=4dE!0QBI}fw99m|GEK~aUMx@_vN1UYI4{b#HZ%pamZbbUGJ?ZzNC2i#$kS(7*sL7$d)#BDkFFCZ-5Jjf+X^lvqW=g-Q z5$QLY($8r``Z=cbqZ^TalqvmZjY$8QDSeMdr0;G@->MPmTY1taR*SnQyySnhA?`k` zd0$9>tr6+3nbMzWMEcXF^ofl~zt@z0LnG2}Fr{DIi1Z6h>8Ca#{Zv!>v5iPS)|7rk zBhrsBr61IY^q-s3hc_a9xG8<_Mx=kol)h&p(!Xg+|5_u`zh+7w+=%qSru3~Fk-qf{ zqz`C>{+7@6r+cYgON@V~rIp%$=YxCg@89|G?|d+?0{^=_{Qsjoq#OUs)+Xfjw|e&< zFl@xAKO<((i&-0&urK*px=+@nOP3$t6lD*bqU`ZapUamnWj#y(pUL|Y;?~B@n-lTp zs1d^kyxhO{Td#+-u}!VhUFcqZj`82EI=&q~WaPwI(d*(5q+cv3Pjo(V{&#u7#q!N2(962QX?T)P$H(Fzn@n3$gzCGak@sTm{DH-|Y8=beE|5cu!krE#hIsW?rVXykF zYqYK^<3HQ?9Pq=0g&UJIik3OAIRCRKBYES(2|oSn`!weX=YQDqQe)?g{=7%a!;MrC zZTyFCmrs6+ic2eWPH-M}{;M!8F6y^Wy7<=Z+|_SumGLjX9$!z0NxE415E_!4|Ej#0 z6fD-#=mT@fBwhPq-?tb8g@GWWzSAp`p4(5+v^hrZe67zw9@2FQg|Y#;siu6&W#c)Tm*@ zKKt~8XT5zwUw^&x<2J(Yfg$`JxAE!x`s<<3djHQ4KK*Riuu-EXMnp!fSR40pVsiS0 z+?)0|=gans2~j`v!tUwDwRuU#%f8)*&DfS{&v)iH|GV_c*+V`}Zep>u!Hg z`<;#r`(I6q&pG3K==_iC={uq$ehBa0YOaxK{6}DqPk)>oy(9hlF=w-!_-S8te9%a5 z<@*51X4*aKb4n|8*&O{mhXAy8BNvP8k2;-+jQynd_4;Ri1D*D@d9`m!lOG=uF5vm|J$!0)*~UxO z9zU%(Rdv?+a{i&!5ktdT(WwR$l~!RxBUT^EKkIy{>ePy#dRWbS(A4_!jAm^=pSiav z!})S?TI`fzVF3=P+iAQU5H@T|YvE=3JxC= zbEKr$S?O$6c6j#Zf#%(+YHj7vHfUa&J=^)RBRhWPH(hOX>|m+!vaRbkGvl)z70yy; zGke;+L4oEyxoUlOx4xT|NJQM0@N)qUJCqZ{JOJMPUkIWvntYN-g&IncGqa; z-*5J@s$Azw6(?5x-nZphXq!V-{r6QTDjqlsq3zi0e*U}gvu6XHTH8as)!q?FWtXXv zt{eMq3)=NR!+5F1yJOd7*q=J{k#k8Ay=~_G;A(A844*Eex81nxY?d20F+A`nbYTa7 zVEDwi+{;d%8{0;AG4Ebi>uF{Ld>NgUM{jNZ;E+SECutTkc>dwCJf}}q^cMl{n6K*A5LbA7oHDHQn&(IWf8FiSq^?E;;hEK=+pRJIJNa zB9qJVojz5`6MLpN!lzk=kN=<*IS-u0&SoV^lRjwf-iBU<$NOMXQVBHWtQh2fx)GY9 z44+pX+9{U%BRJKk1c%hr3&XA6z8L}`uV%JXWyX0KkH}}G+kO8 zjzNR>GQQOCX)$PZVJR9YpRi0 zpMotvcXYR!d4^BNpSKhgIDLw@d>d#!(xlR}%J6CPb3*w|XSK6g<)O)aZ0=3SIjD$z zCLO9Qbo!Jh{M^QTqDrM{t>M%5mpvdvo+ak%0QWB7EFA6nIwlWlD);=-)_j~xrDlWS z6Ff4p5(TpAP{dm`{b`2b^VUCys%}6{;>ck0Su&NHO@>d<&xu4_TRSYs(}|wJuS3^f z!>?6|KL?plvZ?go99)aR+sZ(kp552x+3$K1<)QEF)A+S)+u#;ujd<_dLG)j9>!#DE zB5B-fo(-hO4WHM>B~{?pTWdbCRW?G;4#U!C(NzFNvBLsALzp8d48vk?-gH{7F6v`% zgqGcgrQ3`wXt}y#K=Z1aOg?P*G#{`Wx-D5V-ni2UEqe{itAC|IOIFl-w(=S+2l1fq zMM1YE?XOqwH$uxkdQ%7OMNZ4Mgh|GnO^oUT;=y?|oibbRsp%aPhZaxvJDlw@zTsP_n zIr2{z&o-@i!_ws+I3kaX546t8}um^c4=r@h!|$zSxYXG#=n zSl(TfUykvbkA2tI&8K_Sa@w%G9+hA0^eKrQX!C63z?o0mzzro;7*t!XMZG@LtCn-{ z<(njX394?wh*qAR1vmuSYD5A?Eoe^dN#8V|?N!ftyo%ZI#*#j+y=P^4rD175F8wZA zcT4{4E|gdL_k&i=Hh3?5v0ROM)3Y>(L!v#Ru0C)&;K|;>HfsO$S}qxukg0iipt3E4 zY@YZx`Ul&f&1H`SqjaOKFTpyM@ZagG#4@Ts%hX{p@&F}3J= z9eIW&V9XiU<8(h#lXijO(>>}cf)$ptV*;sn(W|(M(ivTR8x8c1FKj6_iRT&8Hb}-- zLP_z`Zgj9mujD$){bzd|NPZ#WmE@Yl7!Me)L|lMs$DYrvG@jQhDKspBV<~xg?8nvv zwMw2*or*0d-D3mktdw5MP1O2Dw~+e$U%^SWN*)`Qwtt9yQ8EX_xx!zKGg>&{vwci_pV+u@<*N<QWV5<8BCM&{$!+tHzM zUUgKV_1H=l4-Id=wN^(Zo^kjg{A!PTmj;e{Kb#m!4@xGBF>h_I)q!VxYXy0iJjhDt zEcH6>8Wz8w&LMU7gw`8tbs(=>|DBCr&;8^_GZwv$`zUD>kgJvhgKZmXbrj>E^xy;d z_3DH`I)$p&@euDZ@@ARSQo6F|+FBhbeLYu};@6vz?P#u}*MU;mY4L5Oz8Zm3fbO(W z2I@iEh^zSZ_To-SUUlGbZ?`oSNPS|2-^yAY*QxBCz^@f+y3u?^?*|SQ_lmQl(XsCg zUtX&NEkpQD*RNZ99rUWh^6pLtG~8Ymy0lgUny1iZxACiE*Sj?P(s}WZAwNij!`G(; zFR4|Ki>#P-z04^SKS=SyPcnSM4?3L{7zB;+UsS6h+pzeL&Vq)6;dI(t=fM<14mkW2 za}LY#Z)^){HC#lo7;+pM4iBK2nNGtrLw=h27(oY%WA{gMYc*sVmXCHLb7bnLbg*5g zVTK{YQyx0+IxSTj-=0&eAp`yY#wut?=|>Z5orXw5ew=(C-KwQLrt7R)4QJ7p$CN`u z^2evVXqaut4-ed>{=Il!$jn*|7!8EXD~5&xuB!udK13O^Pofhu982NUHq&c0oJ3V?!@^(Xj|MbY&IN=8kO(3QN{u27)?5|zO!Df z{himb(vSmBB5B?jKbjo4i$IjIk5uF#lTHq#kejY#tj2N4z0h#|PyYwCNu!KJ)bsHu z7;eN9K2@anu+kF>MOkhWwd4)A%> zMj4o&wVqjm-^*qNP$*KT1LJYu-wP@2{9uZydNN}tO3ZxHUHChW9(jJ}pJk&VKg&SU zs+Aq8YLiA8n6`9SSykzj8J`u`{>*FGY{(vap~12Bjf&c&QN}h*sJ1$wVQ-JywLkM3 z;^6NxGPb`DO{hJO5r;-#Ke@In`1#Mg1{m}Ey9gT62Rt_xWx%xo>CjO0x8HNucn#YP zIXDYRFMVgF;DE=6O&D-|cM*!S2Gbl}_cnGI@~vcODE!;+d8S7hcu9VL<0Z-Dx9wgu zV7}RQSrxjEvIVX%w8sgQmDUT&@H=wC=|x9^t4tuReN*S>WiZM>neDt0Z5fpbx@<`I zBzw?@oFb2VJ+BE-2I@^OJfA!j{=Cfdd6{U)j$2%L_Pj1eAq?)yVHtvKk%0Edr5?;^7B6IM^AblNs(pGo24iN%@~SWl~bPo^syH&a9Fc*9G+rt zkLPV;6r%60(k|n$JfnXKL=;?4fhzR8eU8G$98@@jCGiXtQ_#JK!-njiN!jq1F9jGq z#Q}3X-@hmuGW*y5%4Y)lh*vf^>?jN|!E}?5N5ewo!b-c{DIFWcE1oWeSAm|&HVVd2 z7${uo(J&u{;Sjkt%I4|Scpe9$!;T^0KUZ)Jcd5D(YNRsX0<$N@&H#JwVYM)kcLXP z#@IYVC0++E2I!ee&k#ycdCxOUH)LqiBaGH$>YI_;JUE9AF0ukIJ%8A7lY*&+9G3sY zc@Mrt`p?#Cz^dJr`}(As4rV95&&& zdMQL~!O!d3ddBU%d0eb9B%jO-qLm*{4&WXpV-UQO`9l_Jt>f;G_HppOYC)^#6C%=5 zZCF|@Kp~fL?L8Yzcn5Ib$gt~3m)`sNtc(;qMTJbq@7ITUCOFsOOJL8R z;0d)VB`yy<^DkZl?qP9xkoa0+9R-gJ`C6jOgJs%0z+ExqfxPh(EnYo$0Cze4bi-wT zg_ggDS3o(i?UmqBC+Jnq8@T90)3L!Qs`iN`%pgRkQEbwR7NJZ|6_ zaM^+J&YCu~X7>C6?x34qjVXt`Jis#-=W`Hq{Fc$KgahO2Xuvg419M4rbW2Y}fY(rD z$oI1-;Y!zH?QB0Q3J>a`>f`k$Of+g zw=lhtMn1#@J`eto9t4X5W5|cJH)6FpS&B|zOey*B>GKd2X}~2?pOO!yzgstHH6YB1;>zz#4xiNfQ9Ukv@_znD?`Hp9E7tcG8gMXJJVM56B zpd>xUO+$txAP37QSmU)%fV;{<5~1MssOOPUQh-uE>Nb8)3fZCM?;xir2ak7KRp$v% z%R-Oi_kuy5pdjFHR>v;chS6j4!VWX?3$h7-^ zM89kil}@KD`+cAG37(<8#KBTlpG9>P;O415XlLZM)}C-W??E-5fYPlESXhTgcNwsj z^oAYO+P4^^@Bp%S`20FJu9*6^nA{)bSrXvPV@G0uh=rB3F7xW(7%Ox^3ngO%4r{Dq zsiNn3DrO6t&#i-Fu+n@1il#i@GeyJCH!MGw(%`3H(40CvhD(F6fk>AAoT|YW;u5Mg z&k(Sa$sL}rVbMFpZBreR;r<%`qZ)n@9`eLf3fI|Z*k;vn03kcubUQpa+2xonJ#lTSlq5Xw%b2-Xk7jjdJ4Z^{otI&IOg`l zOPx*;D_TvggQps@QR8J4QGSh}Mm&M5?mk6dp1w*n`4V21(^8&!s6GzYQzKi{aHV*Dt?X6AR{TEQ4dGP-&; zb#Oewe;*doQ;xOQ;~^PKIaB!IbxX~+@flq#I!ux9)4ESxl#k0{Xa2pn6d(re1- zN|8#l0%T3s40^d!tv56pkk2h*@tlc46f89>z`F@T{gkmSZfbbE=eCECY6*4()#bP1 z-7bOTa9hv*94-g1YKWayRm*j(D z4Ud{Kl*(yVsCj!BwF0P}F3-w_d3Gsr{-dV;We1K+Es^- zwX4v0fY$nTdV_@@Qs~+I1Z!(*M-~bvKhFOY z^!3F^l^T4#9*+*LZ3-kM1MC|6=?{1w3iQ{jlhDWf}x)7=C}< zo`Vvc{S#=ZPa@UnkeqsaGzOBVAvyWVLydieX**{k)xjW&xSQ=G>K=sAmCWq?NW){e z+Wj(8#kHV#yP3xrxkVf#m%BgK@E9-mEW}GNiKqO0jyg|MLy`s~wWEFP~|6%s56=AyxShiq)HWOp*qd z-^C8R5r&4xjAxw7%l;Yl__-Kg;K?kGaT@%*A*Lda3S5i(X6sSzd==BwO$X+B)_EFM+3en-WCJ3ug0?D7T02V~s;@^`ogZZy(e2=2SZOxE2@8 z{7ls7Jr8JJv#SM7?Rg$yX+QKFuyMkb#+}Xd{|rdBKBRe7V#~!!J{PrPH~6)f<<<2c z%M-S>k1(^7$pI9b;O$>V>zW8Y+L~66pN1E+6^E#T?pSV}AT8 zhM4n&CZY?{VVC(t3uO$C}1i2Nq^ftSGL<UjVB5JnwC=P;?;=j5`Uu28YPbqX%Ke)%8D-!G~l11AtgS& zHHB6AhewPyC=F@KKc|(Ly7r$M@^dtoGA)SWx;%di{86MS`)pqW{s|U|sUbfWv|h<0 z#vDKeuXI3C1O71<5^#=2rUp=4nzvtqN|Q|X2P8J&A7LOx_LEyuT%6}`qiMkEsVx6& zZv*}z{HNzn^4qB7OTq6#nu^b99jI>pJwQl`@_)A<#r1jnWoTNq68}X)1OC1tzaW0A zjTRPoz8w4p;=kVAfWKE$eqwD~lzau6rq#r|){*M^e-{A-vcK9&OC!Af3PT`bB<(|Y zHZXq&x2>b@iN$KV(hxI=9iQ(j6jfv;I%>)CE{Y{TG-0c`gdcKbqn-Tcd;tU$ zN>yk9(!^RhTw2#cRuJkgjhuCq>W(=`v$N)4oY_3phHa4Lc68SUjz_I(dj@F^v~<@h zGf%Z~FJxze-L;Y9QL9@XLz=W8cda(_XboB(hU{2NcWc1$XjS|YktU;^yHzpsXpLO0 zagv|AHR5=*YSvirmpi&!H8YRau-!SxVy*7hkmtn$Bq<1WH>zeFjd903yx2u*`iUVT zK;H^;H|b^?T>-sV5#eshIos$FMo_&d{m|W`m~r$(@7|*NG{W7Ja6EdX{uM}5G05Gc znR)btZdreKPsZ`+5&Mu0`*-dh)y$(K9&CrKxUah><#>w`T0kz2S=FG*V-zro9<3nM z-H&s)Y?np(;~q+wZC`iiYF0b<2%;g2fk;b%QYV(XhcRaOnjx0sl^ny(6MUV+7|6C0 zDYn`;SlZ$s+i7!;oEn-2y`08h2mIXQFSBV3+x)P{Su9t<7`;Wf27hOQ+=D+eO@l_% zL=oX0^EEV$vEvYo9WnOI>*yYPnsJO+JLb{b_})F{Y{*YW+`RM|jeHBbyGOoe9^>V1 z^fpWTx`(_C`6*PQpVGLyw6A;IZRRmv?_276Lw+i%W7ZQI_uId6kNeF$<_lj@ zfy-)lT=C*C?)N+J7`zzco_3h=97Bx3i%a%%&lVb5$E?E|`v|iZ5$&GznCWaoECd~E zbL9Zj&_wOF-=8d#x%n5kY;ShTDoU!X5*OYwLC^|4%gK2MHVx*4{&-O zGsdiT?ir(*$4s+b7Wj&fPAYiJ7()xN8t5ztrNz{`;}XnsUDdcC)IHH``0$vp2Av@T z@4IKqX5*M+zejD*xghs6w;_$$vMu4BQ#iy3T1%~KJhP?=+-q}BvN>EVLamr=HtPNx zp@B(|ELM?o3^B^}QaRMsYluwy~L2yKKG z-osJYU{7eo%p;@{OfRf3G(*87v=Ve4DQ<*%LNjI_A)U}0ke&~6hh!SspYP6dc<2cY znR$eiTzx}=EmL8Cz9FKJwPQSiDKmZA5ThZRt6~>mB!{M7M0=uQX806FMDX14ojaJ- z(DQ`|>rgY2Xyb8d+Y4!FpDcyvix4iPW-tTigw2NCf1gCYWCu-_Ss^29gFIEAs?B|JTn(=fDG%JxcYgF_SSCJLtJONcRoMwn|$oJ9N3d_;h z|Ait8^UQ-E$F=%!tKx$k1-%r()GJ7l>c`{KF9p)8A-M`3!C+Uu<+Z=dm`4!19le!Q zzspKqEJK3Q-gg-MC|3JWD8vdCjXRKk0qq#>iKLq|4iR`)f&3*;VS5F}LFG_C&%xME zHN-rqF7I~dW}uR(|pfl!(3%j43P4CzV#0wpijAVFR`&-#_w`ayS*6Khm7?)?85bjACg^)oYl zz}>Bhp9>YXvG~=KGIm@klk528(pac2ed~sTUXM5p+IJD_S^G2F#^;n`L1zZuRMKK2 z5}a$v)3W7RsNSri@x6)Bpl@~atXrCGCt(zbDS;SMq_7=}s#uQjrQ;jVvZ)zQM2y1$ zY56<16#PcCz?WcPo@dq8jN>y*RM##A6)S164GB`%uP5!rz6>-{V!MjQ*Gql}UC_a^ z;%v6P2YY%j{uRHMC~R*=Rl;^4X&>lW@HXQKh_2ZoEepG?;J4tNXTm_FXT9Bw<5NtL za4j>yz0BRPU&Oa&HXy-48&6ByL8#uKqT>wl1L*t^&ovfi+q=;8=Y#&SOyT)fEP>so zCuhHvv=f~gJ^AhKiH>p^6N}p*@2)HTs!4op`=AL5~SF8+SN#iXcdip4qs4- zyxG~aQN#S`@i@a#2&v->_x^~6`HmDJ*Wvp&o_!)_9Lfig@`4ce{)~qB4$L~x{EHQy zZ7OCQUs<9CGRbOJJUa5`ZzMR^lBcELaj2f4qVZzhsepH7?3a)5QoG!Ow4%2|VIAe7 z6&2Vqo-I0N-%+BzgKL?soJv}(1RQ^vdFLq1>mXaLVo@8ug7Vz9JS+n+dKO>ZQCLP* z48d$i+CTE_ZZbQLYTSo%r6A<4f=5eWr3uS)&*myKjy5C$uI;hjQ_^A#5*)Jew6tO7 zfD{TEZCLkGcz2_dXJ?q%I2un^aV_nBU%{gRZk`4MgD)}Jj&C0ghHL4#IFg@d;dcpw+X#b}}RWu&;>&9kG?Y<(^I zRBApiw|=7V9iMbu5C2A;XI|PGObH-cuVN3nUupu5(wU9A|He0as3Z@0sxX|4(dBix zb|b{ImD8*Pqq~qBkQTImrsTy|=pXLcM{1_=b)Ih_+p1zQxEu<3oM#8CnZ|mf zeC0}o&(tjC2J);7#JNFHDB4ei>4rTC<{?`_I|g{R9Gjh9jH8F4!YZtqo0+5~ZdN5C;*gv9pTyDT*1@CH2hNdxoW0F4f5n_R2 zDbJ>1UmIFNvDLFd-t72dyvD~ANVI=Ag|&-W*9DNKZiA(fmVaW zuc&CZ@>rfekBJU7vSU2wp6dGj3>`I;JNnbl4NG?>MrKs6kK0tZv>k!`d<%+_bG(=U zvAx%R_Q{{tyv+!nMwq@s34C9&kEOAWNd>8-(>&Jp1w(9v_)4IPmH}6wJdUTa!0-{} z+qX%q?Z@uUX#lNEG<<9+m{e>g*X?B-*pR$dkusZ&Z)zXYX9#&gWDt`1!N zj-kBhM~hJnP3K?+a};n=0&DudA(G(PY1i$p94&sLOshR1S=aP^3}-3RT#IlGX{_S~ zV!$AxJm)j(()X~k0Of_akBjp>*If{=1gL0Nw=S0RG}a({Qj_-mF7~W^TK5SLd(s~%m5At*#iZLk<+sWF#gZfGDb11F6Hne>OyCp9|ylMwaI}H&7 z<=HK0-It@qUl13yQ_-?r5tRSK(^z;8rZ)ADc-FSV5Pv{_ZfN@|wk$z>-d9CS z-wY^^;b|6=RXS znT5*?aR92<@GLg8ylKbY2S;xeE#GlKZhxCL!!*qE8?K&0#BO43m%2sYG#^lb#ak!L5Z>lC#34&obaRJ3f3IlB0sr?H2rIkhW) zd2U^(+ZvZ(&mWXuY`&hgeGBUt5MOGcqGgLqP(F^QvHhymHHe3AV4u6#5W}JTnrl01 z!}kU8G>A`HRkXC8fbway?W-YOf;NrH>45dDZ9EvYE5)u&XBu*0$+F>dquIiMNJOq;ZKK7+I8P+;ESlR7QIu}BR>3leI@gcm zSj(WP&6~WEwTz8$lORpzS#&ZV!}9f3D%f59!YP#1@R>m;L-(ICtY!3$;~_o7v*=m- z!*ZU#3YPwu)QbMwSj(VOARV)UwTwO;Lw1qEvo!;PTmO<|)%S&0y7`2Xs^dQgPF~kS3oZVanOJ8dLKitV$2E7;3U!z&e7;XOo z>0X}2P`wW<=lZE&_j~TMi?s}TH+28Lgtd%029>bgJbMoVb!tnl`m1Q!;tI7Ty>_#< zF~Nz0_CFW1wsYL|DvoDqYDI16HGdT?{jWi8@0P&Y#>8$7Y)@Ro+QufwiIA@0*$3#i zu*Xmq2B>HmPzZTNr#-A~%t#kQ`_zT3ZA^xzLb{k|F&7TL4c#SeRkUnV0(oihUe>mI zCN35(U~OM?%_+q!o_&P*YLV-?RJ1!^iUJc^+lW9QE+f{>XKiO0VlAW-c=j=xD_2?S zp`v9ES6TE+Vr?UIf|;onIJ}O{_bsuXP0i!{H56@*S zXBy%Vq+cjm>Ujn!S<;-$vri529^5)>Rl(AF2K0OTS+7AO$}4_|V!b|(O%jl9v9HEIZJA8xq22T@NtP{HEz5wPzmS+o-(kz;X=q8rXp zJaUSo(^$hN3~>}*J3@~zUQa@qrDZHJObHL8H^s4&@urHO(HdH=uZniJBE6M8PILqT?P_9aXUGh>9f=IT|Nj zy4VR9+ap-hM-8zZd1mjD&iFkY$KxLo{D~6o55MPd>JRk(sYDR~7K0V!Zm2D5 zt3qX4%BS5NI}2;<)S6S=i<^8Oc4t&ohtrJ9vs|GJ`H`BXoSMX;I6!30!X}doRwXR0 z7vNZy^$cTqwrl!qi7*s2_8~>Ubhv`u@BlZQgs5Q|LKQKVqvv3+Gv(&4XA0BFhS*7O zBj_yS_Z)XP;%g2375sJ`VXEKoflYg}B}8NcVdYfcXh+yj0`DA+SFeEgxpaFQX?AV0&g3k(qk zz2h`2O{T&wFg}BwhX<-^uZCrNs?qZ}dSOjwCeSWet_@7YhD)fwiZ2^B9FAZI6?L88 zYvAu8|3tyAf#Tv;YFM^Hj9aYX=tWpw#uPzppnb!f-+OVq0P2fcTx9%Su9&v-83+DD!(Y$y)c~aRCvA15b2Qbj?lmn*5wy7_V0t;tP1yLjzMMH%P&v(H0jf6<%*Q z1ZG2G4adfyt0{mmN&`p1>jK<>+pw#NaSeJ!Uk%(BUWY5#B~X0Ds)nT%yS~L@1$_~+ zr4@>2-e!oUkYD6jZ2PB{?Nbf=Ddau#nI{HIMek;FH7uLogyl&J`WR&E%N1U4b@h+p z7{}rW?JG2h8m57V!8Q9U*BHNXOyXAv%LCQ4yVCutpwUfV<+`4#Q_k6<%*K#A(QvD_DftMWlv}gglpH*SO19 zUkyvO;;jPadBIYHoNrUZ(nhr+LO~ydY~5|e6K}@K9pr~N7Uze8sIM5NfvJ2|1{E@X zuf?WEmrzaDs8`VIU^=-(;Wv)Mxt@z-aYWim5snBAOj&vzH&8ch!Pa5>I=u@GOl?Ik z1q;O@w^-rzMng~;icqlhL@^qc%5E~p(y?x=p^Cv8crc#0!u1))`jhtc=xOLp1*&No zKn3B;n@oOzrgrtvEyXj(x&ozQse(m-MNHPPlOfOI*bRnw9q*x!hQ&(0c)f`68SH30 zaiL8OOIsl0 zJ$b^gR5s62UUFXJJ<`NDg0h5R_d&Ku!(uih_HiuEj=EZVSK(0@rs7xTPsOjkWz?8{ z5LUVFB;aWoNYmy|6!dJ!QmM{xFzR|L@3KSbL=LJSE*+X*TCU;EPEie zGWDMsZQJLRAuZIjY(X99AO(%ciAbWBk$Yye%hWT^Q?SP&i_oxChK_SA&NsWBxvvI( z&-KjIA=H0n^h1LnyzQ%|WpkR54^q(cA=^j2828NRL#h1ESFrTVQ#I^V$kRC%S1Y*R zvj%p*XX?D`KQl%f^qwnR_l)pvOMhzCKUdJ0%8L{lZ*VZiNv^W3U=M?xqhU$+VUCTf zsn4Hj;LoV|`qK!l{xf4-_@S#1sA+eWeyE@^))psdRLMOv#G68kqXmTduE7)PH7-;lm(w1gmKo?11_(8j3UYSjaMH2EaWt<{uf5k5#a- zkgd?LDRpW9C5j|=>zE!Z)tFG0sYaySnVE02dR>M-Owx45nV*W+} zlRg@lDol};;*j;r2cn-{A*^uS_{Kl7^s9h+R}_<>DZfro?39BMx1}Ctn8I=tWUDmn zD#+(5SQ?>>(XeA6-=|<{uF_Y-_JzEVV-ew|(pBZJhNVA^uV1G~b^Y7|O?CVX#mBka zLTvsFo4S4xPql8ir295kdIfet09}IVd+h*QLwZEGDO2t2{^&A zxDUyfdc{x;9E$8L_oWr5`niRrsy84k#;593*(I&TP~SsA4~6Uqt&(whjU~DxkPlO^ zlpiZJ>~hFwD%h=%jnS}FkK+|AMdiaa?8lI2a_kaMK?T*<=oeggR!j!nd@i`tKl8p9o_{cwFIvVNJyjtGnc zMDYbhPGds`wH{p`FmDMgq7foZ!=^z#NWqSWELOwDLOx!>Mng7P!_xeBnS$L5*)R=D zEn<>_rJl#NafN$J$SZ0eayb_FulZ5S6QD61K=r}zA(PYCZqozs)l9`(!d9OvkoQoq zUqNkX}$*x2Gx`O=_vZEUIDCD0iSZc#J zY1oaBk5aHvkj>Vx6dR6Gu(ay`tA-s3d7Of!5mP@68xDE8f~ArCYZ~@7$O}0ZJN@Vd zR{E-8X{!X?>M`?%K~p`vl%aUTbKSd+-c+!NV~T?s_8{b+E7;MHt<$h;As?+^=?zC} zSQ^JgDOf5wziL=|!y6SWMOIzgIirwY^~X%es{xRwD%d>8-q6^^s8{51>;dd@r-&Ux!`Q*v32?!=2L8IA02^qj4tG+p??aZZVW~fSU%^s~7O!FBAs?z>|AcIr zhNV%4^`#3GERA@6(y%{4zD2=Ouk)#fr4dhxf~9`AyN2x!c{a!5-f?RM zgn=435T&ES`jjah*jn8VaPe71>A=SBV#wPm*tZ}%uVK$a{+5EJ-eJFnrP;#(1^Xjp z8#OG|@gEf|_5TYs>_W)@QLrSNs9`6NZUws;vY#|8J=10ddjPTl8kX9fg9!x zV~0!yOM7_RYuNUX7jZ038u-z$!ls6$jRq!u&zMpPIs~%(Gm27)BNj9-eO1Bsg6x!r zJq39$1?wu68rD@R73}xW8>eAuZ1KHnv%9r8#8yArao8g?w? zs}!uO{noIbA z^3xKs($k9ajKfN0IB?X;VCn)`Om@W;4SN-AM+F-O_Kb!-12#;-(%M9_hD`?BPr(iW z8?Rweb}T~_ERDd{XxKGie^s!q7Fffg1-DF8uvCwxYS^h@BNZ&w&v6=d9N1_DyAJG+ z8Wv*?%X$S%b#SnT9Sk;J!P4lwzlKE}v>a5h>0tY4*gjy>6)deEcGIxkz-B4f0l}OgGYMS*FLXdKXC zJo+=x^%`_N&|i3T4AA8obUD!9c{BoOlm?9g8o{HqF)~tv&H_4xN1;_jXi#`1X<}PH zq>3_3Q>wo;=-)sW@F=oDjM1RTM!B3v(a?)uH0Uot*YM~@pxa--=@ z+W(z4_yszR|2w_h3v|og#u1vqfHzb#1bKmh&kGE-0z=Ce7|_1JF!}`s?=LWA zc!7z^3rvq*U{dxo+NLH>1z%tS`U2D57np3nz!bj+vZ;xIg%=2Cyg(48si7N0UMujd zO^O(SEdblpNY#r($WFj+QzLUP5)q^pwW*QF7m0wrNaQsVJ~uVu`y!F%UtzbY5pIMZ zyhhf);i8x*lP?m{4};yNt|WL{!MK9Rt7-+K$tx#VMJ$KUODE5q76# z!*Y{$K7plaXH$1vZ2{ZVonADfYU&QN7unh7YQHrby{P>k#_bYAUcC=lQ+M)p0^7tL zgfFsp5xZ0KV7W=VCc&nHZR&1Kn&gknXb{|WhkLQpb0p-dU7}HtHFbCDXs}J)75h2Z zrtZdl6Rd6*@I}Z~yMPozGy?!FtHdH$V0hP zQ;j*>)&YZj{%jjg5uS$TCY`DSOD*2-nnQKJL#{f_Hv=-=DZ*&TRi_AtL#8{uNTo-0 zdhu<@E^BmswH=yA5npPhHOh8*@o9 z#=}YR<&djRdk=-|n1($9x$2a8ciL6Z)bs7qw;ZZ_WGmcWg1hvo{b0}5<0b+cF@2|j z2Sd)D>IX}!SMxM1<>%Tv%zFa+2V|~Wpgrrv=8Y3@Iv#S>Ee7F`6=>{I&!xIOp*1#R zbjVR;7H*(+UE9!xa~hXi(BkhGYM8FH61h(1fx+&8Y^sK(3{hQFK^eJC!&10hbwLJ& zhc9YaT9sB^%z+^yF1esRP~2O>&DZKHL2&&F4dg#o!=f+bHyliEfu)A&4-Gp8a@8d$ zG#Fo_VOK$}x_pHu)F(CU3CLBKzF-j0^y_9`g7J) zf^F)hLS2ww-mf!yo%0%(KNY~T?jj{uBT>aXF<9!3n|guMG_Xy*IO;R7IeCiev|N8- zRd7=;%EGl`w0zj43%bB=0_*K!u+5cBK7*y<_@OI`=Ut({knCNQjxxWi3K+MO`<6nx z;wm)n*Rb_*sptI0#dE39+@uTOz|IBh?c%w46-<7;;DvYIF52rr<%hG2YYAy+>+OQS zoN~rzu(4p9da>YGuuZ+tFbprS$yX`9$Tg0*x{}%t-35{hA>UKR^AjRiXgtnPwR3fs?7un1zxa_!Zq zH2xjL(~Z2AwKYfMD%vz?ZqkLYV5wJa>P55&Md+`<^}S8c%w0v>nCo{j@Io=-Xk0mb z9Gd@8-@fa*(j<+$_!lfqRlQv}JYT`mGdKCF zj5{#`_Dd9=CVvXR`)3HgCiU!R2yhuY`L%*BROhChsB;vEVLyNXmda*RKfKVG4?es@ z?L__eM4y_t4#T=nP5gr==07!otJrAjfYrM#(J#d{my{R?SlW=~c z7hDZ_N&%B&HJ>xNYNf81gK?+2>mxs^&zaE2H=6Vz6%u)-8h0&AMD$+_!Hqz0za%YVminR^17p@Gxk zHTwlUu)o0T9u#)v-V;KvuI~+AW}evd&A=%dcp|*!zZtmH5PRX(NexWXE%s}LV99IE zR}$rG4#0}fB)UHupwJ?!?lYX{AZI_r36{Kmix${8ud&ERZKLAzpY9Kj&ep)Q zsbI4o9>o&dPIz@m15?Y&e)1ITaCoKr)+)_B6yI8Pf8}){t!8qMj1^pjbQE8C#ihvD zl-h_d%xzNU)2H`eyzs*|?Myqx7bJp&Rr__}#>BgS)>N zvt4i6rz(U_DRF)uGGs^<{Ej`t_zfDPUQ2`;cGbLao|RSvSS%N;3d@Hv-p+*M7p)wQ zd)g-5cVA${xsA&+K17a9qARBAzpGh>?0`p-u}Z_VvLdTGa4^o)@21>r%V$>#K1p4M z>~h_nRNpYz!EowN6)OIObTHk4(U6qQX;Wy163u{9*P3(eOn0&UXd?T5Qw{MEtVW$= ztj;uKSE?=Ar$obqYk3C1p1>?M`Xzf`RF3oZ}kQQ18I zq0{2bv_`OokD^qar@Zpx@aYIDKXg{(`X3eT>U!s-GhR6^Ut#ZO}s=IiDf4qJl*vvTO*?O5eQ4fMvelWY+X?+%J!|OJ4Ei z_^F1N`q=ptn=@3j7zW!@k28KxMe9=Wgy3W)Og(5t2uDxB9P)|t33kIzVf{`s#G9}j zcZ{(NHr&N7Q?e_-hI8y>L!5mIyRxM9RMv31AzRbb`0P=}GUzc+osXSjf(jNBV0jGB z%7Ef$fMu@lG}iP9^fQRD$>IQxpJa&WXU<1>S1Q`|uA+}Heor!d-f%bxKB|O|!m^_q zM@JZzM5ps1Vhz(-zfT(CJv?<{Dq|V!Ko=XQWaGdNRIt`8*hLYV!5ThAMG$UhSvmY4 zLymLeO-8C_9LIC>)96rSlf73&&8MIUDDf(#XQ zD9*4(vWCx~38e1A9?Ibp5V54pRIy?pq&p5VhCxycEKx}&LK@DK(l`l^96e^S9-lSj z8`Ke<=d19LzsA0@4M{+YXkj8oBK9A(M@*7h?)hEfs9^^Ia}pRZ{}?4dmW z)DYXD{Rb5-MnJlKKWiH-YPvjQoy(feMCm*O`aPb0f+-cGgH*7fK>wkAtYxrYkW|SY zg7ixji%J9W`}0_@FSs8n!^+c-4e>MJ9ZHz0+%HM2*N@TrUUgND`K;+IL-wF|neWH* zj}T*n_O&Wntc7$!B5V5*Rv4%atLU_VwSCc$9jWmw3gr2RhL{EI3stmO2*XS?RfryAr?dXR240zLb_xRYx{vAJJE9$1}tK2XQOSWx>?bg z=kFuH1ML%4w3rC#ngrJNeM5Gm_j%2KF>5;q{RWN3>~HY=J&cN={ZADw{(yArZr1ia zL-s;LE3dX#!rIP7%pYqY($S0O?_zcY?PFB57z1g-F4i{KK4=$m{i0dZm+|S19MFAv z`VPj=kd9QruE1*2PS!G5Ocv!O-=(bOJT!)vp!-9fb{gU!SysWWAkM)ZtYxtMU^%<_ zGS>1HJaaa5_v2{?PF+L#n+kS~5mMq=%U}_tkvV?LS<6?^=umDC;AuO;9gvPv!GgjW zsoPo0V99c>-wM`pJ}l>wWuC5bKl2zB>?&a?+gQtB5jd53{xPiOYp7Uxu-u=gE3s)7 z((x+TRj&?hWi5ln&`4fuwUV`b-H@%WL3cP$SGfDUNh;VCWJ!)=Erb0SmTv~FVl5XK zGUx^@f6UY6n8iaHse)aj-=r<9<#I#5OKnMU@M_j{A=-DuxTT{n&zHFeqVrX>m=Di( zY-Vkj8L}s}p=BM`u(oetGL7(qw7+P;aw ztVHdBVDZrHe5tOo#Gm8(7;A2T%nXuA*s$ zN?r@(X}E^5vb@wHmbHxsYeDV*xb>{>sZ@(pSF__&r{K29+aQ7@$^miD=F)|iM5S_+_?R8Pscl(S<|S$?@;}8q|5O=kGUv$O+ayR%Tfaye@o!=T_Ii2qR0)alFOS9to0L4j&n)NTiB z+ldB~lt(OMZDa7~YIb5&v{(-1+5SA8XNaK?+rxIUw(l4+%nsYbma?`lyT-KguyvP$ z7KZ^Jj8?$1XBEW5c^KpTVJ^>jIv4w}Aif;1n>CFYN-G*3E?>gh#snoWAL1w#O-p#P zFqo%v4Dl1h<(OG_cy4$R5@Nd@65u=IKn=o&;&{wV$>9)R1pe6^fY4+D445`FUtRZacu@2=CdB zK%Uux=MmoPPuVtFh12{^I{H&3XgJWzz?w0{MePr=#u3+SPo*h#j)Io2P^-65MY~=~ z(JMTC&Jd&VMn6wxO-o}SgrjG(h7rrP9fRLFtq$>UOAPXmr96!gw5!3K&%>^lZSOUU zwS2}9i=euy*I~vmc1!dU^sX00DqtCiS*c93r7-k7JX9K_R|0wZH1;?lOU5hM@lF{2 zdM0c5l&jB1wNGX2&NnQbOK4#veue^;9d5y|BUOmd#UTDZyi)al@rrLNOOK3eSkT1J z5RveF#4*M;cASlPLQ8+MrYczJp9#+k+aG7>#i&??v}P$Y{dqpkkRMVXvX#fhR$8Z( z_7A79){h%vDLfyV#@NQr#G#L9op$MD1uFy4!^rDx(-}G%yCSaB`n1dq5{TOL2+ z?qF8)uviVv&SC$sma*{y6M5+vaEdX!+>isPf!;b%!J;Gp&J8@xz$-8YqTaqxwCC{@ zjKGj#t9V$ff^66X)-bl!Y=v~t8OAI&&=0!jloZ0^dJSynarvCz_ACRhL>Oiwl{+uCK2x%?eF3uiMo0!j^7u%ZB+bz zcL|iod@nFO4qf?DU+Ng}SYdykyD^!`(_$ukU;0SN%2rv>Ztt7L*j{hQzUVJyRx1^s zWXSi>49cv4iwwU3<2sl2_a7?kCt@2R{9Db_uKF?Ofs&Pe>43k!#F)nU&96{J(*0EQ zUPE>-h37XqW;6IkbX`>KOS{vSWv-I#F~sjMkZj9g@J;9!_rtYu_ms4>?m>cSJS`%i zdXH5_C!j7+O|gHN%NWPu=sxIFWkJYYh4tO;%*kzYnZdWvOM`2J? z8GIX7Lyp6>33eqdtw~6*f~RpknN#kws_1xnXP}4VGq!Qy>2q{Kcx6=z<4ExZrMTJo z8pA`mtGGJ8s8sOVu!RBsrP!`B_zo1;WcaqULP<;iQ%EqAr^Pg=KJBlf@nX7D6KL;O zz!=9%?MF?>?e66Y>nImL!9adUA%pKizUITVpUV_9Zf4j3`k?IwL+{3u9|Rp+s-$H= zCKBM_e8V^e>JbK@GBZF$qbl}7Tvyr$-eho8&~=~^ z^?Qke-iWOupi_KrF*Mpg-xSdC#Y$QRT|$C+Jnbs(*{xM{EW*kN;7G^dV#au)AqP`w zuIPPBVSR&ZRw8e8D`9Z-I^Ax;wJ}8sdOc37;K|Rlyv@-25TZE)dh<;sEd%MD$MUp@ zrFR~vqS0Z$T?%?^DPtRF0qBL6zI{Vs8z-HAL4u0+${6|pdfN)99$BcMF;qeGD=)Vz zXXt|nf?fu_ra(zc|C7LzZ50fTZS_=D(k~TwUc0*tj-Ax)@*qw1Q}MW7FXSpxr2KF8-ZUzz zqw5zxr$LkFUC+Co`{7;d|Gj8N0RQ=jR2)#-SsYMt z02CAlWKa-s0L1|jWKw3CnxXrMv-JH{pFZ8E>2tc9d%xWOTDX?l>(u`3y=&gJt7@0) za|t{>4vj&Itr}J?BOmMCDYQ!*l7{0uN^VM-3>s_g@Gf-K7ZS2@qsN-X8r_UZ2=&^T~A9p%oZD&_ES<9R;-MV)`Q1RlOeeZd!bKai2X4HR|H zIfr5&B7ukJa}bPYw%)%`2Cw2ZR9tqIfa4&l>t%iB?#sY&`)NrRF4Q<#juX^bjFMY= zPX>xBM0=4QX^DIQ7dcY&@6kxDb+({6q@l94Oc?cYN0@0|MA$I21dNV%Wdv_Vk>97e zn!Mct{SZq`jC^dN_Z=B@G_4Z&E4W&Jy18I39J>o=H7cW#&Xhx-P3v%f7XinL0s25bfk7h0#J8(;Z&T-9?ksU?LhWb!h z%@1@F&m3M(*dH{=dGX+zGWd10Zr)M%Z|h{b0cnoMA;ZaZ0IK!BA%kBFigxOK1`XXi zHOJ$qpuXnYr(T!A;}%62Zc*TDL*)p%b8C*rp6>`)L#VlHGI-pDh~(h4#~4dDoz3A- z=m<-DQ?3jecM=Y8;C~s1?|Opi!Mn=<|F$*}(5Bb;9a?O-QV-Ix?(6#=@EzmgmG@=6N zm<%gu3>AQvkDy1d%=3;zTZ8W*E#65U{~C=l12lz$<1O$wm*t&@v<%mDS^0G58;y>= z>78JK$Jr{6D@eP6U303NWt)v4FJVKYN3oaP6D{zq(Cs49?hd{vi^q|H5#^vcr#xwa z#~~+WGSVtWq{`x-^A&_LqK72S%MS-Eo#KG6^}isC@1pI4R*@83U$Ko7&7N@Thk z=&eZ>!f~#2D`cyupOZy%7>wJ2-pFqWh-Y}5husKt`wwSj(Kwm2NCWh`(-yLEj&~i< znjceS(Hg>rYk`h9V}Ztjp$MS0;e3)*twTV>Gwho3{Kbmn*z{jWiQtuz!Xj(Yl>IV3 zYho}8WGPiDnlBJ3G>R4G5UX9x7poYJ%2-U%d-=+cMzc;wXvzG-*QpqemhcP8lZ|td z$D>6lX>Zjm;#-s$kCsU5gN;}6O-V|=(4sZ-`4%n4qbJO#_|t}-Acdzr>H=umL--yg z#-k^Opf#}$ z=Qe0Gi}=}4jK?ghZ$Gzv_*qa2kJ;3>fa3MH^~^^*1^Jmap8}jFmm91xnGe20;lJ~? zfLJ{HJ4N3ad{`D;V?b+{=JXgdV7e@2(Dg9V^Y(|7?y!J-Z-GYpCvO3ngv03ilcEa- z^Olbk8phY4JPa(Wc+1TMhP}6nqbA#JU*2SsLQ}gh7wEOT)o4QFzVDiDYAxpb@YbRf z8b+sA4$zx;o7IHIP34X6sHvJakT+GO&@g#DE(5)lx0p@nb(kUEQbYNMH*Y9Qp-A=s0Z-Qf);I#XV{+xz4Z>US*Veq@90w1U3mm4Ph28?A#Uekp_wimxpkiwT? z#7O}@SsxBS+~Tmra!r0kPg`Vo?2&CWSSz_C1AozjU#gkpV~OW+@g-ejU2x-9NmBB$ zR8uAZf5nSmDw^6tCNSI)6Kc?=Ht?UMuy9JOpq;8|T49gg3SY>yCMd35Vekk@!Qm1K>jbhduB+ z2^*=;SdidKl9GQ1c3mCHE=F0>1HT(Jx)&*R69q?>6utnvUlpKAa(mPRzXv_)6jCZj z3r;jC{B7I~cnGR2FWwVrD!;vGw^>MeI8yKdO5x#49C{m6cl^qF$d7^V^$t=Bh6+AN zDSSR=DvVid*{9__@bKM!T85OIfr1NE3ZIAGNym-4$rU~D`?1=iC7bFY_+O>))CZgh zDs@O@5BveF2wRboX!IRR;o*aJ+6^krpO1Us4-$5n1}VFp1mCq39uD*fEvR-Gy#J>1 z$4MF|6;iaT1joA+J{Rr39aPEg)ji~6$HVO;QaXOpT4}%7eF$y_RjzMM5Bw3>0JLVB zgN0oSDf!s^m{1L>hofqH;NuW#xLL*s_tr zXJfLz0V?&v`W|@flr2;v<>nA!!$=CBg^L`RHx$|^r>8v_oFEFPC|WmXJ%kM}DGry> zPh&WaJ?6^qHkxWM9&PI=Qeu?CcAFGFldvmkJIVHG>Y+daY9Jda+Lgjqp%gxYu%R@c z#s1Ab@QGM+(y~>D3fr7g_;j2#tOr&7bbg=Mr2k0_!1|Q0gk4rCe40T@M>xMvZNi_z z+O0#cnXt(#g};dT?J7!Xud=O&{>doWJxICgD{N3p;ZxDh<3W|~-QEL#8rD=AQsUi& z{c$P$1yu6}TFr%{I(jg`v4~NHv~g^75;o|iIGjTtYy*S3pw1o)QZPHvHry5_oK29z zL!;mtn5c!OC<$B=HqXotyrErvRu0u*bvgwVOE^O|Q zXFc#2aMmvuDH-0vxe_Tn%2~M=R2Ad9df-tv<13I7qZCfHNa2$V?L@usc@I3=hQ1GX z7*F&_;nB`!pq(nT+mtVRFi0l~rHWQ={fr7LC5V>pUqtii`>F>6^e$hVaAu4Ag~L!% z8lb0nr-G_9@O2M7dUs$cQZ5b@&SFX76QE`^RkQWSH$50!!g8w>47TBMJHBg4F*t!m zT{Eb5Ilt?H#{lQN3n}U!g;Q`+_~SS#T>`4?!QDOZbb!r9N+mYydxds~^I9_oH;KY|J2n~E1FphBT`3N*RDQuZAgoYwhOaYY0PARY z4)AS@g~Q2Gcvwo45NZl5q}t#pGd!%b;9B6zCkY3mrSP!Sd^28Qg{II%>yP4_%KtWP z*cx!i7%ZIXmf{ejZ~t%TA~Or&oy0W=1J^GLEkhfPm7#nCgta^S>07$St(vyUi~%g{ zX;skR>Fk?U@UXT&DtLz#wnDSXd9xXVd#HttG!F$I3wHse@~{h*L|iwAwZPSpTg(_> zjWY78mSc~)3l|Zj@VJTbYb%E_pkeM-GdNaAa~mMO^;h8nhZG#koqz`ra?qV_qMB-; z2x=X~_u_!7Rw~S1<`j?^%AL4=VpI{kx1mBx~749QSm%s}w zB=3N(bSiaEnebI8L0U6+MyFbp|C}h?V=z`JG~3*$bIrt|hA7-r=syZ=jQa(vJf2}O zUQ1-Gp`id7DsSP(q~gQmsQJ!@hBm+s;we1ACMv{ifu+QAVGHkyXe zjW!b=F6klX@coEqvQ>PWp}o}1q3&%Hz8TBe8py8;Ib{{EMj2j&oH#G)o;TrJ(T?Kq z{i5GVtN2!w;PO@oDxX1j%1jKvct#mYP`%tK(JBXMIwl{4+E_2T!(rmkP841TwP1Z~ zj1Zzhx-B;mg^Nlp*vxOdK@OA(rY;{N*vL z92$sXzyV5Ft`EIq;(-0IV5)=mz<{GxIn;r$f)XAXc+O0RXGAgZ2xVOne8egT^qWEP zsIw;_=gk;&p^ZGj_xM4Ft>UXO^VEPYX=th$9%IGOBz&)#8*3Ghv13Rw=<0u=+d!rw zz*zN5J-#OoIcOCRhjwT!=+Zw-H^XDB{4gEgYeV;2#bfNG@)ga0DKpI&yv2C%SF>J& zeO5V?LxVcd<>7v>I0x@wG3McW-GaSV@o-BIISmaucY4sB6jKrCh{9ugCperQvd1b1 z%z&ZQpey~B9&9i%P>^p+@wqy5msL3Y%09ex zv;_Bf(Txd#!#XhD)7ee!rJ|`@t#Y`JnQ3!7ba)baQ^X+(1^eVJc4QRoo87lqI7Fi-Hc(sK)`RIdFZ01|KpG-tN1(= z_+B(vMf=tP)Xgt&fay12D?E_hsG_}I>#XvCvvhnO&BKC15eNAB7hpid21nlbHCFN0 zQI1QWfa7=4*n!TN-N`AQE>%Wa#bY(-7Y{r9?*2s<)hy!s|>Ij zol!(3{pn2I?ScY(utN9~dMS!#7{5x-Ojn4)bxjL$P#8@8?E(Y%+P^96Tk3e9U=sT;%^4>%o1CPUxikSZgkieM!jQjD+3BdH(9 z8vY_2Yxh8zJ&wAqg-pN=FfJQk?*&muk~MrPYHV3Obk)Rg8S!%?Cb8IPu*s9)wx zomtj+oHrDiLcMcfgOOH6j$?Y>p#~2WF?GmU;{gX>AT(Cgt<-yu1vTPPl>gMx{E6O1 zl5P)h-i4NfL&+j1>i-ouB*F|^R00m=q13BsO^4GMh1NBLG(VVnfCUcli3R6_aq~L; z)hlZpl8C}DP6G~?CaOgo;MbdY2^=(Wex}dTxqvWQa|&(pC)A58=m6C|Nz&4C&|hM* zrb7bXb`+hl!>AWl;E+NT!(uzZp%_<%gwN7C&_Z8^LRYA_`)ZA}Dsm3>yiJYob<0GR zB+Y}?{bowrw8@(~Qw25PWcJ?FM0sx(wH0X|v<=eIRMbZfFtuY9xrmWAvL4?xaiTUW z&4c!Y^ma~B{g<1mE2#+Fp>BUww__F%^&4pp=!@Up28W8JqRy-$wDT{m0EgS(ntHA@ z2aE^GA4|ZZa=F`EQCVDu63Z*`z2rx@g@rPb=7C|!bp;ewJYL~WJ(EItW~0rnc#QAW zD_l*(m^6=lMB%xWouI9W7$D42DuNkpz)G6G&ch>1jLJ!K*hv)r2^gE;DXd-X zxz?x!yo8>sYQRI2_{l>XJhl_X*mGDqyhCeV<0Z`aDw2=ceN8QRXwQwM8|i!wrS(w3 zT?k(<+)KFk)2%Chu40wcv0qIL`)$b?9Tc?>I~q*XQDFhtRA_ z&~fHoQ$vSuO(w5&Ilz#f2Jbv7t~lI%w~<3B+IDy`IAl*VncdPHRwMFKx*E+g%12nr zs0h4XK2bHmr+;TU{eqt1vQ`rj@ct>rC2K&f zIU97q$m0o7e0^5`_4GK?;z>Fe5#$|FjE-wZM)JcD`A*OSz9*Nke0=SQ8*N&_$?w6V}Hy|LEa&Fu_B+Vv| z7wCPa?fcD{{HqbYbjVT-eGfO8*fBrEI|r^-n3j z7A|%*c?INna4;i&$N_cBfzJfLkBX>?;v#sycH*c&P;a_ z9EmFO3^9zRpXmgb@>K&(E-N`MC_m-=+!omS|M&>rOcjCm)90Tee9fJ&Ot6`oMxvM) z-wugw@m~vGSrv|`E543z!`JrsiN|g7K#Ll-xegK^{Np2dmQ@5T*yryD_*%Dln8|@D zmlw2b_uucfa1QGDubl)3xr%h7M81w!gUh|&-H+JRqz2?OFK9siFmjmS=2sC1GA!~T zcxx`qbTT=FLyu)*}-5iFfj-;SHHjCsD zX0r85!GwFf-fx#pF2%&bd0vJ_pP$jf1|BS;(JI6;(xPND z=DF;!$>jl2j8auMaf-YZLxg?ELl~r1;K~JdEvr?d=wNAmfWA*0TtYLo$WTY>j~)*U z)N#bY{m0Y}dW*?Hn;Gh2+VzqvUCi+Q+iIFPhvwtK%3=eX<0zNl<5*C_9;n&oZ`!?+ z<8m9?grsVarK(+%9vQg6dY`zfstv9T2Tf{-%Lbcy%0mZ=Drtlnx_PXyw|^4D^yZQl z>dYvK8fMx{mDA(~n*OwSHJIFw@_%g5BpI2Ey5C4Ef11-8n|i>A^bWuF6cbm(hr%fh ztXe*d$a@MKBAoFy?P$xX0k6VnRRtt!^HzN-@K_q!4{tr#KHIXjE;KFu`_$sSOc3v;xKkq zHf7$C@jFg$*=BPTY!;_=)NxF?;@})lI;=$y41k_<4wlz}P1<5#>Zh@#&0*p&=(p6i z1}u^s+q2h9)W>LzAZ5f+Iblt9J2CN(u-o#RlO`0Fq_bwrR# z;^6mdVr?zw(3G^$>#-dgVbpj3F0QVX4w83wf9J(t9jFHP?{?p<;T-DXzH_I`Pg^;| z9HgAKx3nE&wqs{*bdVd};fx^l#L+D%I=2&?O7~7vQm>>fPCJOh=WB0bdkhZ8){MwW z&UB+O0@kIY^Tf!ER&dhXT>Ck7u-f8;N#5^|^SBJ7<{X<+BfoH_=2HaBLr3Syk*Q79 zXpQIp@T03iTbwo%2ag{PR=nhzILGF+HD9_=lQ4pG5=WOW*Q7O9;bhUlA3VfIYdq|jr(0&W`Mg5cni!d>uHgDlX;`t#Ub9O}T_sL9*$o!1$= zxV2GkJM%TATz_pz%Eq~)oM^w@NBTRBn!7QjMBC1_f?xj5@4Qag#jlw-4xFvZZ7bpW zHRZ-G`P!E{)DDq;zF#ki&Bege!8x|&s%8(Q`}nr=+JKV#YF%2>L#|&{=AJ*McvF}3 zaWvs6f9%Pu!XYkn(X@46xzkmFZEiKF)p0B0iyv_PDl%i1%^2Pa&?(Yy_>5&SnH7~7 z%p8m3SBz6q!`?QxO5*4{J36DWkn7)+7r%CXpeOaOo+JG|1Lv=e&uc1y)#lik5k1?N zA7!og%Lr0R`YFf!7F(dX$Mt`leI#=3=N|OjW(Mi+@%h}yBiWDdp)nT3{x-%eD1zLG z5doKtqi0a$*{TBWy{c;on}3`3p)!J8CGROeoc3FE{55=cs5%=N#CUGAE4z1zLKpI2Ks&IijM)}JlE z3nTgc>b#Vg$grSKJhnme_dUl<|9MSJN?!F1&awRL`X8`&Vjs_Iq@T}>h{Qt8L(ByK z)mV5bepkd#!2v^^4v;70znq2!1pgGVEB;bpQ!YxgFfn3=k6`@R(l(p)^Y~)fp*(dN z_mum0=aYhr#F+KV<_87%;m&3Y`L~;2K+ybU>thl#3Z8UkasAYJhn9We(Qc>SsAlIe zq0y&{HC0?0_n(%^yH`@;c15lDIb_BJ|Do>brL$q)iI~-G!xwEqpxMu>Y*T_hpn~x{u(_aqnr$ zGWP#H%O8dUkF~|&FnP~yOvtL(%txKCz#)Tsuk%r6?COxu-0s`SA%?u~{PE0iRZ@N( z802v8)!j*!nrQnY@VVE3=jM!?61v-&gK?YwI{$m@$dGk!rZfmHvb*=P2g}h z;cSs@US;Gzo*x8GoA>+bEiuPaF6Wojwx#1dz<=6nOY$$L9FN(u`uBO$0zdE+PMO%| zm{0!4Wzfh^Crq0ax_EiyI@ONYLrF;)nYrEh-H({6w+##pj@O9)tGXX`=l^$ZW=2xd zq1YX&b&<;#hxVT}ZNjG`2f3KG7U%>aKlJkIi2v=QwOvJ~Nw<%-ukDCSpsziMOQUZr z`G2!LFrI=_TwGh?-!X_X^-33^%fa7t9W&4l`hou!^uBiR_Z9K~&<^?!Mf3r7(0?YP zpI`_5coBV&9rQsW`q_5S&l1ri0G+sYEPtcEeiYF!w}XDUh(5{=`X~|oZae6AiRe$* zL4QI-pKb?znuz|c9rSlZ^woCISBvPo?4a)w=+kt}0O7utWliy4@SetimxlfY`Z0FU zj~3B?YX|+eBKjqE(1(lYx7tC!RYad)2YtMVKF<#Nn1~36H3^fM&d3Mmx6VY$7gMOokKE)3DGa~w8JLro|^y~}gJ2r+CTx(Rm z5D=+4=2P1M=9A}!5(52?cF->p(eJW@ey4~&#}4`{BKlT4=vxK)3p!@RT^j{-kTAo$ z4EY6ps2%j7BKqBS(C-w{=h;Dj!$i+McNVsr<@x}<`r0KR&g+=TwgJrKXNDYuevKXU zks|s`JLoe_^lXRH+{s~?51f)zb_j@bI%b@0029a=asc|(cF;$N=(Fvh&oa@oU9Q5t zXG`7rFSz^cirdG$rFB-v%&-k$W@t_HJMEy~DWZR92mM16J=^_(aNIyH2RQ3GqFX?u z=$OB31DHQGh8%!C%MSW16FvK4K#`4(8HcwmUkHdZI%c750JEUepb_ZL+CiUUqGvfb z;cmTL8R34t8z&%6WA?BOU_v?!8i7994*FyhJ^O5caJoZIBSZ`s@Jv7?>zFXx04A*6 zpb_XZ?V!&z(X+38h4=?@8X*RP?`r{(gmz>b!2H!_&5n<{;(bNhfVaX76-ZI^2&clpbk%sfJo3WD{TXqmCXi?5_D{}QV1g=CkHnf zl+^+v9zD%AfSK21kO2BTJLvNS`V%^4s?=LYV&(8c&D2H%f6!;zL7!=&XWs>wZ#IZo z^S^P;BcNMA9M>_YYy+62rv{CV>)5xWgy1liy!kH(1T^ZcfHI@R%bnLtF zH8$Ed!T^nbCm@dKn9H^SOlGY?!eJfrb*&8nX9@Fljgg+c?_|D>WtsDH2w`?#KpfIB zKidW{KUEtf9MZA%?)5fQI*;fU^#USR$859>V4|uF5@L01*N6ri5)cAu7$+bOqTXx+ zn4~8L2?up-_cY-son`s(Ul1fIs9QiB(6J?wFDi-Axc%l_{MaC4zm8ekXd@GNPHs)5 zk)G}PsL2K(4fpf91jIfabICS|2wE({*BNnbQhfYgTa=h{1=3)aK3QG z2+--+6lXqWvK#^ds+`Yd8xh?)_Ph(dnIRl%v&12yhRgXZBjTM7?^f)#ia-bt*Nn?X z#9JL+p`dq2;YdJ^Am!OV|M#OU+`*Pty zs|W+KJvAlW$m0 zaT0mcyVFKYn~uHc&7c3aFU85L z)lAqwo}V-#nsw~S0sL8VOYZ!aRu~D12}VQ{%6Gi@*sUcF@u$XD#v2ihI>pPCPBc7} zyjr+*=(O_X2_vQfCY2w5GTo9t|D^*VMlK&SBI?4=LIhrTUyjxbOkUfOR&ROl34Yn(Hz3VDsV9c#Mw88Kx#ws^Yupt_|L z1m>Gwyw`{*)hW8q4!>+w3hojQKij>>h$(?)8$874>Mh0K4ztIGu3bjVL!F}GXQv#i zauiNKH|#JX9_ZM+)2>nBAR6m z=oef8@IUiTC&Jv(DPEuWnBHWUlM;nX_a|Pz6Jc)a6mJqn(fjjqm<_m;82{#t2$K)Y zNo<13V-PJV;N+WEBFs(XCu1s&6(A>PGf_;&!KiS>A)Z6PVL0VY^>bOL$omOiTY1bL-0A)) zuT6x>)G5>l0>w9odg>N~R&YSwD#E1c6h$k2#kZGwVsOjUcV$tN2$QOFc$FFEA-+1* z6Lg3;3|yS``l$$YPUr9}d5)X->Q+w_ZjpNZocXFwggT>h;8GS25Z@c?iNdAP0byw` zszs@MCp!?kDdadE9B5l7eQJBqu@MY!WS z$F8hZfrb~^dkW&wDa6s~^N6gj5)tl*&aopia_Zag{_}0X{ zr`=g*_%7Xlcj4Z^(Opr?f1LHzq)$h6`!LSbtm~-&|D}pB&fPx${qd)hzMA#p@~B-$ zakHT7f*D7;f}mYZ?%y0MX<|$topdSol}lf_n5Hu6vgqqaedW@#1p3B}o+ZNflD=~3 zD;HVh(5dBPy&79LEm`LHy-qj2SN$-jR$?>LEm^F^ainqp>G`}-81^i zrLSB}CD1p2n94#tdi0e`U%Ai{=xYak?Vzt6^x0QJ3G|Hzed9sjcp&r!vBlK4u8{5- zedW?uE~XObn?FotAs#*Y%B8PdXbJSSgT8jq*ADvZE1?AX#)H1`pl>`7dV|RVSx z_l&-B=_?mg3G~e$rm_%^9)0D~S1z;!`r1KXJLqc%efE`50)68_-+0hB9tgcbY%%q% zE2MiyU%A**F7!rw^_|S)yVoxdn{Bw!?q>ByyPM%g`|Pmg>vtc|xKrKZM*BP6drkC; z{j7ii)~?vUHy~gZy<*>VS#%#Bm-}B9pLN(6YV&e`=*F|fFU6Pp`G^8xqn&L;6mT9L z)+3?-Vi-KX6|>+2TQLk4#N2u=j$xpWnXu*yn=un6_lTK5V>D#14peHa#7sbp24&#t zY_k{*2|9=3UA96(IJnN(Ra_=2QW}3E>rb2UC;rSbi$8%_7thYxjCJAJBi04tYIG&f z!#jfV!60Z{jbF{inCc@sa%MpPd}=n+Tt(MJ#ZMEu@y&sZFm(Sz&>Q8Z3GF7-MH zQt#s|(P&Q5c0_ZE7DaQ?M~xa`GiuZbanvZprg}8PW^Ae%k4&+tXauaac0|BhD~f=H zI9r!LvKeRVBT<|!#PVucXfu}A!WL63FT@K=_O%@^EZGzA=Mf5UC9F0Xc!DcMF38GkZ zh>KE;brhZX~roI(ZpFI)V2O1-`s}Qnwyd6>D z#~Y)m7Xm=r^@>5e@QMN6NmyvdI|&N}dc4DsY{xqcNy0k}dtj*c?xly`z9oUT zDo*$2t%?|!=f1W9tWS&Z8it+kZ7dS#@t#LkZ{G9Rk9Sh+c`@W8adiTm;@82=B>z%|v;T1!?!&qd;JB$y6cNly7&SSE! zw=YTd{;kU1y@h!it*&>kZT9{>&)&TkdIlQx?nP3(d3w%{H&4$BFPPf-rs_h0K1IjA z@U$&J^lEGG--Wg7-Pyl1h8f{3#@`QZKRhdX*%oi}_VVpqrnhhN_Ui55bFh!xY(M5J zdR-Xr9ryD6V!Pf)Ud0*8w72gspT}yXcP~8S&FNmgA+1K-r z9rRm8^h@lZ4;RsYYX|+eBKk3Q(2o|;53qxNfIy$FW4dnHDE}D3biFdv7wD_)psyCu z-?fAOj)*?p4*E0^{RunhPl)LE*g?NbL?2}beUylPxgGS&Mf72I(1(fWXWKzPOGF=J z2Yrx;eu5qJ<3;oVcF=z&qW{ni`VU3)zIM?2is%R0K|k>Sg5J{({vIa&`8ImjUeZTP zPfqof1HbpzR}OvUAl?P;YY+ebXb<`1f0Tp#J{~(^dT?l1_}`IRw#Ob#O3lpf&UYw& z^r+%Z1EYS)G3qxB4i%3c6?f}zHb=c>@ zKmHXRn{u_Vsx5(Q;r`uLRd_WeHu|q0g9C@T9&wX|&;SH%6Rt@LW*&cEhQ8R496Czox6@px~{ ztR-9Hb1OCLxm(=(n#$bxtxIN&@yM~0MGSf0`J)+ssFL#PxCkzXd#^4pN%hB!kDSdX zclp{nOx|-HGbbW8qom^%IAn0|b(CbpMud!UyKkoo@#H<_$Fu(2mr<(Gg2OrPJxyuG zzCUMutbAlAhh);b|M=sPZw4{0FA3xNW{|_kk3aVBp77sOW_}wQ z8XmqXGFo-;Xj1?5?EHu24VombzosNr_0u?6~BbcoZ#>0kxpKd|9JTMPna3<%ZjL7ac8gGd)#^vQi@J*3?1#d_zp)r8`xcEUuqA?@;(;k9Iq` zUnBi|W~_`a)I8)mx&LY^%!=Q&>gR994pkl?Pso2MhmQT`=T*DnvkF^sxqh0$_?0t! z%#T0FS5ZD59sFT^O4(iR759Ek{@IwwpMpjY*hY%T`(9&a{=7EkY<|rR&ao_I{SPBv z+Nteb;y5yNTSo0Ij_G*sQTFjoi-SkIuP3+2`|hKI7jHVA{ix#>=UAJuEp(*$OY8D# z77@n*L6Ip{1>Aep*Aq7{p8lb81i4DybN+Dp;?0TIs|z@Xs+7ne2AZ4B)XTFgCHdKMG`GEuItp+re0fF-)k6fv`$Mx$hIQYvLC-K|h^86}^qtC2OX-^Be z{!MxD>p}xPwP1IS^!E%5T^FCZB9LG5}rmmUlCO*e0uUj*5^qRFLM_t18YrY=4G|0CK+zyd`zClZ4uQfMu9h{>& zXUi-v@o80gZf)Q;dwZUyl) zmreG#2X0$wS6_DcR{Kk?4%}{RndW{TpM?W-^4wmbRwqPes4HkEJ+^$38+~JUBkAWh zY5B22%{#6NC6^I7K`A~DF3;^ParkJ->G}%Ju{>#2kk@ta!U=w_pjAob6`Vu;=_MbD zPp`}KaU`DK#}rr6TH6ugcSWBQN57CAIqFKzp*ZF{Pw~sia%}KcvD3In_Hz4U&apIc z#YC4&dOq)nqszn(<#i($9PT(JGFsM#UGzEEs-K4|+))hlj=# zXr7>_AD!P3dpxZr_IE{ zefGh!ms}I)*qpIuiVJ@Xy%X7+vL>S$oXQT)cE4i>ClzrRy7+8s7pLYNo6;h`bmk9^ ztMO*+myu~r)tp1?*~LTe+reoEa<}F>ruI(cj=o9DKLp>uHhUsH`WA*pFNf< zr#-|$8MNn7JNFFD`TFK*?)+2t^|bMCzTOE=kM;z?Xt3SJ_YnuT*+=SHF+e%o+w;8_ zy&xAss&SX;`#tx-r|!sXckyG#avB{3V!^4VChiUAP$7UT&z=EddR$`z=g^+9;=|{LeBC4tAFjw~$JfTV8Ojbj z_?#t*iMyWw@H9TuOE6)sp)rKUH#BgHC%Y$V?BH^NC_dR(3@%R&2D=DnD07H|OYi~k zRupgi^qC!8(uiW%KY8F%tolMJ9AeBuM!!&jw<7PKVK409l1XbSOY@Xd+==>3FeWpI z;mk%!8OECGwsEqFbhmv@pVKHpN>!gX_s zlE`7}Z0dn#?lR|`rioKj?VcnoiQo=Z?ehdK6IIvp=n0fO${3$oGid4&;enouv##jYBJ`zY_ZAb z0jgpPv{B@*7$R(4A0mn&EAqQAx+|)-d_>F2di`KjQ--8L7sV6RXTsJTo|GS>dh(Lf zLX)%*B_02)b9qD*eo>XkkoweYrQrX@{GgnDQvC)-qM|a&Z%Gqp62beVN`-EQXvCgCh?f%<~T97}C9GYz)hCM)rMm_{d=OmQ(V!_^+KV8<}*|J{8|ac`x@mVv|b)dj0N3P(S#`H^a!}4N>^~^MGb& z_b9p+u&zlXQ3RdUv_g}FAg44VlUL9rC;<)wh30I~ahqIPh{7c-A2N$p;*KIOQAM5; zh40EDe9aGYNwCR7O%#J7A43z(nVC-Kj7*+ElbL6rq2h7mAZlh=&rds1`0r~0byf6; zGe#yFq8Jffg|96!{z*1@bYk>KrV_6#bV)Yy=s+G9UcpyQ(j;m)TGxYvDLp%zN{k(Q z(#QjO9D9&zmp$8w-m|pMql+k9f4L9p(ujcxMjj~Zfh$Y#_5Lrebj4zw#|yNy2$ZVg za)|PTkw+6zC_^se>yrpCdJEM$k5{P2=ms^XXgw5g)W`#^FW^uszBWV;rfVJRJkTmX z*wX^)Cy_o!j6Bc}d?KIVYs;PwGHmjAhyEMa4(eNBu7`{~YG~WLg|F>#pV0M+bv<-M zF)m4iK}T~g=zx(2Muwns`mf33vTX8DOgg6phnD^R`-~hgJo)c$!B_41Np$sPO^elp zostfPYu5YjH8LniQLL{~b8PyQt2Xd!h(k~o$F;)g6svzc+37+hpVbYrt=N6F6MfhYql&gNm>d(>bhg6m?O9HW)eF z#$Xmz2M*~I=_=3~hwbP`DOxS}4h>?pr)e7?9~0iH8dS12CE$S#9y`(c;x(OEASoXI z?XlLV#!aH|`1>($oMH)F=CknS^<@nlpXk>{E4tp_9>~3r4 z*wV#}>nbA)7@Mw(OTnUTw{NKp7W0>U4JiicqeV{C zIUwke2!mr$2^bfL&^5ZX+Bi$tvDx5Ix6;*A=qiFra$Si^VzWQj*x-QmrqBLPOdsl< zdN;J71lr2LooakXNpLoJr0UxQXf=r=O>Iy`&}K&_U@W6;qRR%4iIY-b~|xieUUeE#rH0 zq^R>t>u?D(=>w3W$V|hMieMN+Q|si|2Q%N;&?1YlBa@Jev@xc!Q3db2zO}PS*q*VT z3wRXtxj+{(jn66^%t42PTM~JoJ_TznDu=Msi)bDg&fr4~~wdn0g-)l$gANV&TcnP^venr3|!FbYpq`!nj& z=-lnK&MJ?5!v0W+Ok{j|SCk3ZDW7I^!JWZY&JipG>0I0R!o z91>tp4-h__gA?d|M_4}~q7BAA@gd4M778ABUh-guKSts*eJ$LGQ71?w13!R(dh@DTHV8FYBM zRGBNv1@>5!OP_*q**Dv*=AneJgN~sTPx6<>`ipe1m6sA-K&xx66kP?RlOGdI>MzLv1w z$3a&$*R;;3_%6&(Pw@Si_hGB}Iv5odTG%^rgXo@_DFe?iO&`IMgRKY_*5cA7h&8@P zjGpkD6f+K5H12ZBI>tlr2uO2yO4u)pDd8dgX01sFxMaKzQP#y@3WtlNIlx!pvbht4 z<-y5jI&>g=R0qvw7s0b3&7m2iXCBod*30Dlp_;&1# zK~WBBOx{$7)@4qKRylxhXe#RLh*zRnCRz#Wbp+p2{RA(ObROCWJEs;3RL!P4@}^=$ z-eylJ5yKd=p`PXj?oyK=5=)%kL!Y_#()uUwF(6kNRC$kMBj} zg~MIacr8kBWg7@n{Y*|tnvDj+`lX@-+ajD&tmc3NVLBMJgqu90lmS|KcniK~_?@+i ze}($@PIb6T`t?q;4oHPcA{yr4+5@Q!S5i7Ut6j1Q#*UB86M`+Y#bnBTYt^9 z0^fxoRB_#K*sm7C1X*Mc1n<)U7opOT)8r=Tq zh7}HbplAJC?j?r7X`9R#+#&2V95!JaW()gEQso~5G0~bgFk;y(zl~-L3NX6Br>f9K zD}}S2QXH_T^38Zf-F=#9Z3WQci4;rNC2hbTb}h0Z{uqpM zxO*KczMxBDG5!i+CszQUJM^Iy{Bd;Q5>RCi?(TulChXvBq*RVCwt_zald>69yPU;G z1!y0H6To>FQq(^Rp%Ro094^Njr1~K%Ko)M@sXx~E01ApNxfK(1np{LhVHMc3LcSfuL9HUAV z&0PI-A&7|-2YBnkJ3ysgNZmtX1{cw|)JW+Fue6eb)7UZI13fFoQ}>h@p9&+1wt+p$ zCsy!q;CZKkDt8EVr;70xu;9o=N}9J23P(!+6vB=w1XZjnb+L)@=Lzc?iu4^QYkJ_3vYeJo-P9T@`ky0gSR17ZqJCJhd}tO#Ik$z?TEW9{>=Z?-sc;na zREim(Xhs#%%8hcWx55GC>=uuFrh8Losh9!k#vA>UjdyFXf=503TmjWyoCq?34-$3-ZCksI@gb%1f0M8-37}HXr9N-5eAqW3 zv~4H22nP_Q@UV3}QbASsMMV!h?5i*67?kSq!ixT|wY;x@s(dtcnTzGa!W&(NlpJs2 zqJ)(ETNtTtgX)f7Sr2>+49Po4xjpoi75Q+}jw}LImN)gdi{-=X?hWgTEgJdS3LfU_ z=n7CJxlw<+7!MoSEeRZJRDeCXLt>CfZ2QH*%!9vsN5NHZV(ES0i=q=dGDum+PVg0#2X*it9%Aynx-R6kLi4ZyQkASEB_Nq7CUJ#3qM!U^rR8Ip?m}DV zMg8W27{3WU`r=Evt~u{Um-fb*eG}61D=@mI10Um**8{&E6|?sZ-D1i0rp^&*d?j*u=^4jt zrRQtJ*)`g(yI_t`XVL3g}*bxb1^zgydEhjA;PQJl+0PmPj4`+#WkAN-Jfs{MG zhh@=qP@M{IpGWsn%+U%mw;f3ZzDH!?^;k6Crtmp*E5#hHgI|~8Zw-o*#luDFjg!A@ z`}dbE@ZA_lDL#K7b*oBe9zI&{JkYewqB}9>^4}457KTeU&zt&nrSXlxUk8o)+e{1m zTWk{3f&7N|aalZk%$~I2wFYNc;NM^)0rzOwTuhchcr%c3&5>pY{izCS~S8#6qf|L4;|xi6BsHCC%1C zUNI>&vPbKdSov|=CH3Gel zZx&*78)5bW-8i1^DoU3+T9tn-XyTou;H=Y8q}BTKtx-xkTJ?xB(3~?|j1!Bez2P*{ z%17|MK?;vv<#QJ_mp%AiCC1a7Xeb%#SK!JVqn^Xq@57k492>jA~Btz*mgmM>R1Xqv7EDNQ-ykM?+~mvlAq> z{`{aShGQ@uQHhG(EsuUnm`I=-1Nk9cjK)+zE0&4mM{+6Im=VHgzi6Gx&nRLzW}-=r zv_FOMGl>))Gt%^S(6rCyXEZS$Ga;WgouA31@R$)NQW=^>{ERBbV(9^QQgC)~F)bh7ln~3V;Y+o6FmFLvVqu7QFlcqm0p5%e zgR2R109A4Wktd}49Bd%vIZz}krNFE*32En)yyavm8t4Pm{<`eS+h1bQFxy;l#+f-F z2Zr?)sW<-adh^Dg7)^~v^h73-xA`nZ!*-kt+Yz>Bbb-UT{*DlWSkd=p|G zg|s2k@|b>KpCL;)TpRv%&v6yjS8$Dp@o=g5(h95dFO1tgpBqw@MKbbt5JrWfT`D-H#OOPOS%S1}a@d`)Q&G(2mZ7iYx;~P{Glv21m?ro%#qfL>njI*bI63rQeUoKY z2o6;-IFB$ZQ0_}gWhBSo{1=)m#7kuSCef?aKBY_sy${aR$51@iRT|E^UPbB?zj7Ho zoWc5Xs~plLAs$Zb!L(L#T`Oeq%zhBGPIx8(zmDBLGz#XR9QpuZegj&&Q1GOS3l1K5 zeS`e1QbzVc*ggSpl`Njw2!htYP6_R?dlE=XE?N$aO^jKzw}uIOFk<0X;P3%!?d)n9 z+1Nt)2qO`*Ti5{-qq8xo0^RUYjSM;tETUO0Sv(U5g37_d{*_pI z7T@HUI63rD!u$$!`ttkO^DBTB?21;2Qolr)K(jh_hnmSWi%umZ6pV3t0W zksXh&*ar8dcAl_pDu$=S$Du`P+xSKqd;&(`M-Z@G*tHeI)A+vj$h%1v&76krS0!Ih zO2fGf3{uP)IdmdnmH}NmT-b3I%f5&+J+Ob7Wz90OPogW~I1sD(M%eHc!&Bj1(;!tH z*dl{Jg*q>SfM{XAT@1fKm}sb0KaGA~KyzLr(O1sGX$92^+}mZ* zObiIFDy7l-L4t{qL*tyoceF(|j1&%Ph_jx8I~;XL)qcmHkFYFvQ25nA_j$rO5iuOh zXH(|2e*F0iOZZu=Ixze*2ZduQV)$u(fXr~x$YdR5vl3aq?;(w5U2lSQgd7?bGrk?# zsU=@NLZyDxh}8B8S{dnR7XxU6T`C;W5$lc?wFs&e4d7(a%prhNDkSP0jrkbTj>uur z8-4)uavy0h>j~?b`Qe$2WOPM8taez4ez~@Dbr$n^-ocjU!0gEQiI+@dX_Qian*#s5Dw9U%Zl$ zor%__r}$Dhl_!>t36tkO;I%BC4in5K;Y^?yeM~>bF#F`NmvFa$_T!t*(rDKC22!>9 z>w!{kTv!VOD?rimiEyG(Ed40vR62ZC416o2`(?r$hwkAIBq}WqO9G@Fm&0c9m70pH zbYh|W?<0hvEqU%c8Nu1GE^DA#y`ONDQ_KU_onJjtYln2p;+bUdUnra-6{8OmW&zTY z<*+a==OaByIKe8$9wJNsXA0*TsW#&tFu3YF$hoy2aX)No7dxK0JGh`J+Th1g{ha>~74Hk|Z ziuF5A;2F@Hs zk$itkqPXzr$oJH##T=8y-XKgcu<^nvak18TFe9Ei)R=G9NXo^jCr=b7TjMK@MzeoS z{c+`647zvqV;efnY@ja-7W8vNygSnA8FI+AVgLe_; z{5$HsZU1b&q+~c(l)2EnafCFS9p2cjKeeGh&TrYmZ^MNP%@<#jDD$0!SxjBm%+d{# zvI{UXQfnr|NgD0og!9hud^Zl;D1&EC1K#|ha4kcebKK?_*$lrslOhd_T$qxEu-aHo}Z!Iw5+u zG#FmQK$^VciUe3im>uA(3EV6x7#>U|of69iNQ2n{Wk|iD+#-XfD^O;ZaDm4><1Cql z#3j;DxL$`eKVVP}gZYT=? zPEVHvza~r?Ei_lUEh2?xmLj>$Uk=Us!abSz~* zg3A@j<=Ff-Zz%j82;DfR6Mm93x)jS4I*H5^t{Iy1K|j{Y-Q8bL=dDc^%?$ zNoiirH^j%Da%lau&K!_NV-GTb4rxE0m&|t;VL~BWGnVdNT54W_hnta{<0OY>opQiD zOB!8?W=xp}o|BaQ4E>YV=X|>JYAO3MPD_>``S4juG)I_NFfVnZ`@T|W<{uN<3Fb{h=$5sm>?b&~gyaX#a%k4M5X@tx(bzKg??7_! z8A;h1n1Eoe8A~_YEoD~`CI#RNrzOESH*_AUnbKIC69@w9j?r>pb~IJ&dwNK~QgRJ? z7-nWBCRtK64jt_U^CA~|xIqfd{DkBNUpX{iAuFWOI9uUPM~;7zBxU2s(%)cS>vdRC zb}eDnA-UK^4$Zne1oO?(=sH}kZ$k2%Qxd(gvg`ECn8`jV304#4GFZn+ zV{xG6GY!r@XeY>_+3{MWYd)ukVJyqMUO!VX@rjbMTlJO!lOc_5;G3a(q#T$X3C}PS zd{k2MQ?!-aNZyekDY^wtZr(-f`W)YI>2g#w?fmow6KT4xl7tBZF zW}Nv0^AeZilCm4IkAdVzZgObW?Gc!-kVfP9)qrXwFFYZc?7IB=$vE`( zK^twSljYFtB-&2fK1h(kGlw8M`KY99v_yCTm_yRoR*a^d;N3Y|4$Y3%r~A{xewO)0 zFMyrL9Ep>ZjUGfbOOVFmuwel0pX%XqXm&WBXJR!0CnaU0_tUX_=MhQS7&&$#^{g}& z$95;uGHn_xhh_&iAzeF(9R_V15NY%$9_r4j|uY!Qm;y5 zVf;}`rOZVR%(|2UJ?*TdWS}om$@?TF!)jfO)GTT2Gr~-PVE2e|9Gn2{||%g zsKnqFl|-XrG$t{@1*3^eM58f@iEB)PLB$0lE~vPm;({nDs3@SI;sWl7pdbi>Ajmq5 z`@+26Yi15}7`o34Q1x5tB~?kN>Hb_-pWgfG?yHY6HG@75+4DCuHDirIHS9Q>eTIoT z^(FV+c(mwRNPWrB<4oOH;KV`tADfuE^Gxk3j?L22iu%xEHy$mz6(d~tQ6f_}7Ix9l zy?7&2Hx4;2MrbsfeSvWcXAH$sPaZ9LmLj~i_X(zMtVlOOdT0bwHx|R82;IbHu@?4u z4B3_4c(mBH65&-oCz-lUD{&!W15RuVo(vTiu>VA1J1=+93v zQwxv)M+>t*CA0JM(DK8Qx|@o!@DuZkPunA^rC?Ox>_dE<|V~ zn>Ap6k?Mb`2agursclwvNoDGWS(IA-ihr28Z%V=ngf3yT8p)*yq96P4V9|$?R^*b# zR4q!gAbaa-rsf-_JekEquRw6aKptys8^RSfOhcxS6vkA0T@sQI8bH?zT273NjbEKY?mUmJ<&~i$l21?<_+xaF`5)-&Qe`j4cM5Aw;rBTuJbx9<0=j$BJ%L zJIX!JG4z67NP8P$E18O~nkK&sStzbEIOWi;N_|frEcT@J#%-r`hGyX7Xj4XrTftO~ z{U91i$8lIV9|8M0mqkAzB(YfBqxcB-C|>D>3(J{`FH6D&q`JZHJR{XhP-kfyO9(t#?*|%$+R~7`XVFMU=JGEL0ona*n=z+vrM z9>ax4Bi(CQ>`GXhir}|cpY6nhMW<}2F6w-lp?akxe2+l=pMNuxjZ+MNLi6_=G#O}( z4-XZ6=xyj^7K__8{m^d<`xi6S;^0LLL^t%m!btKe<93GdH5dH~DGq0mxRc|IxxA1S z%+z{L5@@U_?tGP@H4M8ykoyN0g#lQY%ps3MY;`vtDt4oMI?iHOV~?GBbFzD}7T;%0 z8CBo&8Y9W8jiwCoD?Up(wS9`;=-cSZ6qwm={iI6KgQ*=CPd=UIENGh z)d3uK-wSv+@#Vv!FEzv%7QF^%ohdhuHF8v^Ny1So4euL_bgwa)BPOZ96OnG?6(;MB z4{SOF7d!J|(HYY^A)Tc;95We64u8u@cB-k;e{_?PYOw!6bHm4cSo|1k8sQ%njeAqt zdz7p<9Mw1}M;~@io5j$*)@Y&;=BU6li3;Sf)XJ;9`LL-lV^}l}YIs#4-3MNC(tQdC z6Cl3I>lP#3>rAGQaVjv)I)CA?X%Jk9>r(7iiL;GbtTBZ&7QG&ee(L(xzT(g+lCT!y zb-lA0>0WPYIRjPTfk<}Y%3rHWsq<-?+udZGg? zdK0#gU>FiMJ>#VNgd}W)_{Sc180p?*++h+vQ-ME2x_{uX2@rh8c+Q)ZV~LV*2Pr$s zf;VHmjy)h@%~MXY0k47dnl5)4$=+=04u+_}gOTiCI4tHtLe>+G;^W3?g^b@#)UrIHHrBr7$a8iBL)P`*KtyPL=vzX5~5jb z6o#4-1cs}?;Yf7}Jm9t*YNCvjUSWg^JOZf>;INw^xX_6Yi%vN8Eo|b@xd_`*%lYQR zlCTBgxhxjP{;6jhtHO>&_@E-j7lWm$SJs&ii=E4$c?pNUh_JmioKzn&j*r4c7K-+c+%D>_V6d8;0dCtUv=K4jCAkD zp+{pNpEir<(0iadt&)>&T!%A$7mLLeX(zI9gs8xjr4QhNx@8Zx2glc`EL33XD~57d z2o~}yIH}$*2~>wdI4pfplnP6AH;u*8wQlU83X@gf$@t=0<1>!di}t-3X{e;U__XLr z72&%wMt*^&e)Y^F&Ns&x4W+^s4hsW|uvCRzitrp3yH67Op&W**u-M5L`js=%40bNQ zxKzuBMQtfG&*jjE5f)2ftQk6_$0GbNi^Vl|?L!3qtOEaxq&+ky&AOzJm-J1zN6yvU zL(UThOLg-Gi+!?v5IJdt>F{D5mOH?MBtlS~FVA zCt9b2ur6n!-CP)(-FRg!_vsST7_MW@JlQ<7V}janFCbd0DP9t zi+UOBZ${rXo-)LhPm8WJfKK4hu$&0V)HAZa8T~Si%hF3EOWz!-!iFL|lf}Zd zf~lNUU{g8M;BEcQm~Buw*Bbqdu#+viQMW#xLt`l~oTK>$3&uRjShqRsS+HwVSdx8~ z#m1T2^EWE+H&lJyXohC}&6pRCG*$vWZOYP-92#?N;WEuCS>KE~Ev+G!a9CP@Zd75Z z4@+dR@L8aJguuxvFpaN|jIT{te>3Lr{s?UF;nSi|1H}8&RGdLCKv)*709fCQ^+y)M z7jW1Zgl$)0w^(8hkC29y%6q8OD5~@1&gc{@@SpL`d+Ls z^AJ9n!&2XOK!t@-P*}@h&mnA)3cCp5=`0rhI9#Yh{!9h_jM{_?Ep4s87^`tpAfFap z$jbj2ho*F$qqRIMzhFl=gYZQhc0a<_s<4!wu^g7>Zj)75D#sia3#&;tDt{H0>W>fE zKCE*KEV>sVpDZq{?}fF8>c@Bv3qzuCNQFIw@Z}tq`j5pbEX;jEB8MfD$|w~UvmBv> z#lkMumwHT3J}i3DWTUT+kz1g#3KO!(s>aGKSoEm(n8smgRNbw@?nd}}4tok=3shL@ z)lRY4qgcO@!DP4!Obw>oiR_Tp^#j(={s^o!dVFJlvglF^@kT2qgQontOtw=N47)9j zFhLy6;RxHQ!tO-)S`JGylzA%bJcOrkSX!%$P+>D(p6dui~(W5H?SRrFI<8VaX~Vpu&EQ@N5=)0<&@&1bkHB zZpcoZ8*NHj=QVaSF+&xKXe*SJTiA%Dt?VY-MoweDdk7>Kc*o5teS=I9fR=eEH(+NF6uve zsVMG6{fF_HNb5R{6A_pP2o<+EIgJAu)O+-Q!6+rLuto?ODr^SACvezB2#Zl+V-UWG z!$u-(sR~Q$->n?>B*KDJSn3gzIV_Dl#)B)YQbJx)|B%mO;eXAAdLB;|#h%n2TwXGA z8pmx00=}QkDJ2~BDMa`{4m%NHXH{6Ta!ll~ixIXkL9q`hsUb0`w>2e!-ga59~G8t!{Hp3cKv@FQ7X_N{Z zfbdKXOEdWoRoD*^Udm!|(vJ$T&Y2I3+B(o4uNfr_n%d!=EKUipHJv*8gu}v)DWs~f zsR$p>Vdo-jj|#gR;d41Gm2j8}OY^vJ4ofxXR~42@ct3|F%c}7>XE^f9dd`Hr3PgB1 zhb=(Z$0~X;>lF%E>}i~Gr=HeL1$LvJ*7*&igmHqkH{g<+Ez)cH0vw>FHr$)Teu1z| z6_&=sFE}jqXz?m+Ji@1P*gp`qRfVNl%O4ze1HzW7ur!u#;IK5~`ALQS3E>AhER8y2 zRalzwoa3-G4i8Xa2O#_|i-q5Dr&|07h`2!#L5VJU2}3L8waIqU(1 z{iMRuHyz-xrx6yY!cxDJ%3-q+=C8ui+##F8(iz_FDr|Rzm$O*hG;pD5g_aMCTAG-+ zG%{)>XkUaCUFXzFT(O{aX%7xN6k%6Y*sBO1%3+PQQiV0vN)9^%a^qB3np@1^u(WoF zRAD0#{s)H*L)Z!xb_K%2IP4CDEl^<>Abcl>HTK^sY!KLZ4oiLIC>3@z!q0NpEQEcc z!cwo4#bK%5_^PnRddFdFz`Cli7=(oy7K8#;MP^tW=E)M;;QREr7*hS143r z?}P2dVg12gS7EP%_2;m(H<6~orhy&BVW)tNS7A|iG*dV%&A_5n*l4i7a#&*ztiqxP z*DU6+)Q&<`*if)x9G2SWLKSu)*hmh$2ka~r7IO~GUJgrbaIy+J8Eiapy5D6*eD&03{yc@16{^OAyo)bL6Itv7Pi(VRn%cxQvIcZ{snXc8$~t<^Hfk| zqqvQYqN5jnQ9*wJ8qG%c1D&CQVy-6cXQS~zr>LM9+QfJ^iaIJxP(e}0#Y8rW!BiNn zf?_Zh)7U5)l`v8T9SQVY+fZzW@X%|}Iz&Y)W?>>tmKe1OlMZ3<|8g`BTKlSKMQ1DK zvlEPdR_OD;94&=b9~G@WNHFV&G}`MUpx!E|H&E88-Txn=UMksy8G*<;40%?1uD^n+ zLcOK+e^(()U{<3J=Kt(`Gn%^ERvkJToZJ*J7gp(99=5$p``^k+PD;K z#{#q+%ieY@w%f79ABb#fqhX;P=8Se2QQB&{f#tOp-`XaN5!e#2ZM9Uj$3k`)dfRH5 zYmY^cdepXBBHLpDZI9(O3~6qw#kW0{=3k+=trl*WA8cCIzh_x7Q77AD(f5bmw(ca@ z+QBG<<(0RC(dL~K>>}16&28GT0b2yNtvf|oVB5Mw)t;R(YJ*c%cBs^LoMwTX&dyfNkr}Hij}g_S9WjdyqKk z(dONJuyl%G2!Au*c)FQ)ir_R@Tc;P&Sf>b}w>_sOBB0mSX^o8>y^F!N^^^#mC`0Hbg*qb ztx1dg*;y@&yC!olPI}HpIPa8bIKtX`I(07Cww{U|54NqRaX$g8b_zHb;k;A8_MJcW zWSwor33J-zXw%7Ou$f?Oow&ZjIyDV84(x3Hp=o0Y^GrOzrj})gIpM#UTj0r;_G-2=`~*B5dD% zP27y7UQ6w!D9xvsH$}nH#)R4p)!z`#yP>)cVVhJgnQlTj?`G_e2vfU}OT8)YMlP*s zZQTSO#=05XzB|ac?@enXm%m>h^g0|jo7oBS$df;77vNyw*KJ_2WyY=uyKr^NvRN%=7XWr@uOS@O=R9MQ- z-A@?h1on4?8NEQu`^3tF6G(JC!g*c{0uWZBqL)T4p7(?LVkCR#OKy7r}(3O>F zIJuzB-*5RaxwI1U^^6Y&n}D!T6_zrD=cs}*@~{d^=5n5c3^EVrs<5;x&2!9wDI%O) z&>1LJDZ%qK-z5m#uh2yPYd$QDK={K3Ms9(nj_G$5b{@icPEcqv9<9ReL^#j+3N5Iw zsIZq2&U5;LNkH4(&9ukWO#+_SM7nG_**UFa zv0!aE20Kv4$Y-!L9Y0gZ`QGj74#_@4?WlIS&w=5c+_?(UIj+!byI~t{6wgXG9M7dg za+@5$fn5vMmgBi~wT%2~r^7p2j`n&|{bA{aTSA)J+H&xhSHnm%*ch;Fbu735Y+D@~ z`lA5b>`JjcZXDsdlKKxd2a*v8KT*xdFR)7x)>g-rm|wtG>;q1lfIoGk7t*iTnoaI^ z+vctlE=p+|(w3vpbF8zmVAp_Eb5KfV`!yUEMoh6r)io;3exvvkceL^vz)^a2Udw4xYhM-EOO6=`l!Dn_XI)+Vi(6 zzPP`oEM>aWh68b$VYkVVH`oxc*HskLnu6y*oE(?;Xq)5m_P9)kGj=i!w8>d}yWPaw za!@}EdD5Tq#Cm%G&e&=0rsf!*#uKJvd^;TSr-9wdx-kOwJ5-)FKLud>8G=b9p7{&` zoUv27wv$73*0K|Ajtnu(2N1wg-E8Z_3-&zt@G12Z*5yP`O+0{NwWlWj#uu}nnt&^I zTEVAM&&c{>wMQ=6`@BYbp7ww{btr%h;yoNg56idl!5UL}KjF zw6ri~DTaq}r$W%Yr?2SM`2apF(0hZNN4HEbokXg@FsyN&`@&j28i9pQWN=}poBz@n z;O~&E&v`5iHuS>Tw`54L&TM*HtrGdMUIku;l(FAdgUKZh@EsNSD#GK)WMi!vEWIY@ zuj70%3~MT{$(hAS;_M3rQB;h1dbwvfHx-l~)qQ4;uh;o=G%4D;Z3NdDb_>I3G;O z_945kbuq(rbrK}odKxmBEbI)HlI=&k2`t6f2B9i4o6VY19jXF{8dJ@pv4KLHsA|t} z-b6U_8BVa2>cO6G*#!3raYr5k55vAw`~m3*zyw%pbi(LqzY`Hc?0vQ*;$tjb&LqP749*|t~U z;XsqRIMo;EKcdl!`2s!YkC5(gI$F(2H;%xEBmASA2)FJ_;G8iXnv0z2ygv(0!>&mY zG@rl0rsqKNL_RD`MED#wYkHjis0xg(9firF|1p-dL%lEEQevh1j3oNjBi%9A8R-U% zS+6F94;x!v0Gkz^U|`YQcH(G0i}`jo5}xbC!r`ZF$#atfBUZBEJYytsYzeuTvM#ID zlGqcUB*t(R)6R-m-;)L7PW^Gp&2H@MI)^8ztC3ws?@88*!A?e^{@|g)9|)aHJ}_F6 zVrM!On#D!4kf`F$EOwQt+J3c|S-$C#@D;R%UtwrnC5ip1wZw2y%BP0@EOw=F8YEWx z{LNH*9uJOFKBZh{=mm?jq@w;$9xMEbBu{0dV&|fldW}BYDTJx`0&3MQ$}1NZz5+(& zmwE%-|L|zz(7Ps+k?Iwa*jM+8;KN)Pc2H5*mqjnfWS3yEzSk0F!Y`U?t&o(#&<%RZ zD}5tOD?C^TLzYcpv!ZjsYrtZW%TlK5OYkp`zDq20X7Qnt5c*pG7Kbxk%Y)b{VS9ek2aQ7GdyRYgoZd_Z3O_ z9ABN3&d>~YoRN*=vTRArKrEaU3im%}eS1H0Dd9vumC@Fl?Z#>pGg^E1O&Ce=GYrKb$p)6hC6f>uz$QiM3R2WCa5Xcih0VW$jSbRw^Jrl= zLW7f-x?f?3f%>r8-W!;@b0x7Cb)My3Z2qMrtcLUm9xX&5^q-SV-7nGhaL!Du>>9z; zeMb_zQroP4pUuCJgiVkh%A({uyRPkp2gc7Jf(Qq2o;5&m?guI$H6* z>t?3zeAxW42O>5MW%Ez5I)e0hJX)BC(8Oa*-C&2KU&wcfWU9W0r#JFIk6_bJFn>nq zY#wYhtR^32Y6gqNqIk!73sZ9eI>S4VJ(5l9B_WkG^I)S9CpCeo8SE%%zT0^#Q*$A{ z`7UISV$%({b&b&9c(8Gfa4w#y87z!6VxG%3rsn(TbSSq2*|ZMk4usC(!2+2x(vL7T zgC))RF58)!i=a85G_&b?(>KrK!NwYP?l4m`SQt*l0=Fop=3+Fg0%#u1rt5HM7NLuH zu(4g8ImFZq7E>d!*kcD%^8-orD2D6+HeG8P^Oo>nqmd;oj;R^!*U((%y_2cAL=wFp zLi5*bx(2Iwgog28YmX|-4C&thWSC%4QBIIlCU4rqj#)r95|G#iAQ}-iu(zyu!lSh+TPt0{;)8&#d1Hliw>}Tpm zfp#f{@ELoUx>26mD+pf4qlI-4enrcs%S=U5-6xi*8x^@vHH44b&D8x6yUGZS;lV-- zLQ|qStf_rAe0qSX8twN}YQH*vHeV_kTi|IPZLFtzb}@CM-|?k-I_V%&HM%_5+QsVL zY`R1emLd3d=Qt)^i^^~t!q@C%>PC0#b`QaS@MvRQDe+>{4%mSiBU}5MYvUn&|^GUIEK(u+c~WG z5sAixfMw}7PbYLk*z|p4k0xe0$1~{$%OH%-IxaXlq2|e9!-P3Sn&ayE|kbn zEtbEZz|^frhe^UiwlZ~N@@MRJqIk5h4Z`obvFQRyn2KPX|52vyCz9x|gYKX$Ox^d4 zb6WAN(=iS$oCTa3$$`Z|^$4EMhB40%GNz18=i?j}g70}AXR5{u#e=4Y+cqUn&wjC4K16-l5#ke z$dnUI-B`^|eukC6MyBdJ#yNwS?|zaAV=3)UnHCqpp~d&h5WJd4(=<=4?8ByWVb7*a z^Gjmth6%x#X(1b!x?w@kUWM)?9!-Occ*~Vd!}jr21L$eVOx-ZMOhu*zu4k%-%?6V^ zvCu1p3BSRdqL4Ce9fuZsRw8&4kEZ#l__z<7&NiM&5OqUOF?GL{#39s#Le?^M!$#Km z7NlR&o@U}O?`h8?JlmDc!@M_|vTZI8r}dlIFq#@c%ZXkV_KXo+-aVD69Cpp_RGVVf zaA@%Z>h<>XXk($2f54`1O2S-}==e0IYEc@8z`6f06~mURy@+(@d7NRx?wI5uOW8C` z(8dmPJsUO_wr=QZrsnICuo0r`hn{69#%YP60+sc~Fb*twVO1(-XwNa|b@-?Zgcf?S z>1#OSge+OaVHfG4c+x7S;;Y6n8_hnQp?AHc=~GD?Bk?Oau-Nkv(v|FVoy;gG!N;X&8CIfjkJ|CZv|8L732x56XK&D7Z|!DB(X<1?SYBuS~ia@v_ZAV zmt$;PI7Yj7V%?zSOzoMH5Qdb`yvWdv6K6Bu(AMATP!21)Wh3RK-7hidO=wu9v}Y-1 zyRrEUNgPRI$RRc^9HM<%Q8#iKQ~M=J*n*T#&0y%p$;7FzXrFe=QVuJ6VuTSNbjxJW zkvJ9cfcB@wd@nYCQ4*->#;|E2<|S=zf3t+C`vUH8fc@?=L-Q8nZ5r&!?+xLwVmHhe z#6s^Y3>wF7y&_qxV_w_^@OqkXB+osFNv3=A2zlMM?y5f&88RE%RahY&j9IzufE&`)@#7s-Uh z8Z~g7$C&1#ZZ{b44%85ogQm1Q2ZzUG8p`9?un_l5uL+VkY?>+6K{pwScbf9zP$QQW zyO$uVANpo7Xx#ht#ga=b>CWSEXlx47oyex)c?dJUw;bI#D7P2kb8ay-N5gv^!uG!5 zvSJskG{wh$*$jF&CS{KiTF{lppEA`z@uKr>29HZu&NP-b1it3zPcd~StJt)#3TfZ+ zipz=~IgqX!k;BlvR}x2HycBahczm)XevWQX%<;@+@cS^YGfMyBB}ab}ju9e#yV$g` zeXM!GWkr`vz?1GURO9~UL^P317ao055(iWvPr)$+F=bG?FrbQg@T)I_Zj$M?1o%I(t>qdT69WAgzan^?#c9GiW85H zr!oUQrHG*$7oNsr5JHjFbCe^-w-lqSPcegsaAS3C__mJ2AI1>|r0<;e0RvA!bxlLs zw$yTI(d{ZCtYXu`a)`d>#-mX%1E>qsjVfU%N1=|QF68lm8jg0K0107$%rA6;M zh_H@L8>{=>E<8F0W@Q*~#D>Wg4CP6ZIGJj5?XX82?fZi(XXbF>G3hp)&X4(HO9YRDoVl#n6qr092q=LmqN; z)U%b*~FjSCT=%lkdxXx}M`6B`z6qx`<1Q+9cq2eRK@`98A+_31YIA$44PXzA;7vgHK1!VcIYT#&G%rBd+`AkWcky)5!k9$7czHV0l02Vw}H{f40$X0+J|Tbs>gMJ<+cV(sX+3>pV+MvsJlNdZKpItF|79aX$arkgk3N@!)7&s21x|}wC z+)WOS+fQ?!;zEs6%YK54#mKpNH#jJ+5N$?yNK5h-OymeLzDL7a>->Z2a1w>xAj~8y zcW|?mXG`Kt)CBa7*Exz;Bg=2nSB+TLg1(IYkC;L*!bozh;D71X$o z)?7Ds;O2oo>yp9I9ZGpaHdIkJkv8#~?3mm)?jS)UPy_D-AgI4Wo?`Pz{eI6Q7q%)~7UoNXxRMLW06dF=W2!fXg7 zcb>!JE<}hPR(tfZwCUU&zfV0ZwWf3qjXMe3^pJlVhwrRmI(T;)@V0Y&SbG$Ob3jX* z{LMAv?3{K#!j58>g2Gzv8sS=kyKs@@vuI%(;EV2OIdCwQI(--&PH3(fha%8Tpv2E` zS&d{Yl!m!GThKU=@hkNcC4OmK92;6A9wUx!`mPo{4wFox3Rg8EmCHApyZ_qpbeN@i z>TwXwI5gA-p62q{3^ev-RbTFI!Q(L5a4b4dVNP**9DAo6dNg=X3moT%{7NWaQ@Awt zP&a`t@!Q*i#>uB%_drJ`tI+5wfamw3qgTzp9f!8wzk#qACqDlQl`uzS4{x%@Lz0U?j*kYw}x!vRaDXz(R|$GQAdsvlHD zEZkVIkA@%P(l{kDx*GJFxE9KBu5=A_SC2l*rS<5HYeBCxZ3!qxc$|k_3A%P-0++_g zoF8cW}C8ANuw}k6TR6~7;IW}+C@3@ zlc`LVIBLRA$WJlaiO-`(X{oj9elXQ2C6Ag&juX%5$k!Df#xr)=| zyv`bf`TN%h|J5`XP^uSyCHmU?JGt}&Gu<%ntW}?ZsmnY8O%FZ2Y5u`VJ7z#Wml3Do zchd~Wq8yX1--*8ZzG>#eqA~gEnt_gGv1#UJfid4(tfwj46(7@-jYZSEFCFw!)2z`# z+cr34bWL5Z22FOM<7JmhI18yT$s+Y+UY01CMsJE#Ix!p&$0kU}55 ztn^31_IeE=S3i(#kSsnEBNh5AG4JDiD;~DnkMj_6wu@{6W$_tkoz!oXj<{#VZ^del zayHRjw!gA?vH`~;R`uipD}EbRgjEQMZL%G+c-WwwHX@ep_d+XvyClxnA!MVIY`bRh zFrWuFAXbFg`ftg9oTPCIM@YkB+3?Qd(^3Cx5$lj!v6X)8c(@)wNZpSOD)kq;4}q14 zmG1Mvir6b-c|?6+ng*u31fx7XBdCZgs|#$(y_^{shLibI7I5+8V3$A41MBnb!!>5aw-u;Oce;jB1(h-vEEuSl4@yKWGX2iPp=~F8nW%KDhghXoPQ!Ok$&RkEb zr$4jeQ8$cjxUT6$4~s`V8-sePX;`a$VI?3*(rCk}a37Ajr;>xH>3-StojxzE1fX^K z;Doc7?I$0GVx<5r%{vjX?)ty7;?cVO?;_+x7x^p}i{FoAhEX!BX1ul%a0<(UeZ`cJ zX7~lnKpz25?;#&{W#LgMpvb3~-)kDyJ1=b};F6?qUWZKvahr>LP?seEGnJok4hSoh zvpttJ6M)%K&$HlbX3K|@Sv+P+pTX4>R!Aj*q0M;A&H_uo=YJ+2jArqesre+o#0pKL z3o{EHN_)6uz%uAA)Et427$tuP=9DA&{qFEoRrQTnMo_%`k+ej& z=*O~tWIle6?;_ujV(Wi{ffni1)GybPjlm)yA2oA%J-(oNVTx)BUZbPWhNiS$WaO|2 zxQC5{H2i+sKUtN3g@dvC5U+GH8CfiR0XmUV{JzgKNtJ&gi9c5&-YzdP5Lx*9SjO$b z@0CBx_Yv7S@Ei-tYlwGuB$=lyd=YYxO6J;tL{R}MR%alg?HYkng*2C7dfUh(C(5& zf(~hjqBP`uu}4)xnWXX9MiKLku6HaFupbskX=vEyv0GI_DI(sZh`YKRZI*^7lBUZp z5`8alm#PG`o31e^v-^{eH52d@b>u#NkLkKol`qD~^8oSUK1giFqp$cN4!=K`5~a$c z@92IA@gDv}+d!5qKwtIKL;QZI`*u|x2JN5{#7p`xsTq&H^1~$jUJ|rbl}F!6`Zbk{ zv?LKB9_f4u2Faib@Bi`L_=wO3IfJXY}E`BZ!icrO2SMIi_9#*UmXfs5{u@TZ; zh{ex)x@}O!G5mj%kMx(#(b9&7%wsb{JEsil^T)4K<+IUTw&)P?qCahE$UF|u_+QlH zAB}E{*IHExnEi~*rX1YlMH>^cgk=!CslJisOW7mWs7knrk!f`;(r`cMl0rf#GWPx( z?8skR_}_ z%ED@1V%n>zn6F)>Dg%bvp7BUSzR|C=EFlan8CL{x3M#(m3RNCc`N@Ugll*9dK<59U zZ+J>m|H8?ks(c1Ad^0Mnrgn`7nfYZ2nEH9Ffd#S=MYP##nW_vJOFzw^Z0a%TW$tF#|JCnIxPQWIt5PnCxG}&XCer1nLXC#fwlE?UhtU$82%K~6) zAD30HcYM6Wh1SoepV<5%7wq0y)7Jt-~Xaqs9fSgW)L+QFgo@*g!JZ2E^3My zE@34-IS0QU>O*!MHU0z)Yc~)xZ77+o<&S_FU}zeCy%9i$BsD$}B{u&d(yEK;LxyKr z!fr{^Ck9nP^Kia18MD-695ZK`rg}q{vZlD<(q0VD>#HFHSxg2wH5oAQ`6I=e(!Y$> zV>uZy$jU!U>H8FKBeClPjCVo#kdXU>6WM=d39*=l{g4X@`9WmWR7=BQ^g_!j5j8WA zEWok^*u(-eA-Hmx@#>YDgg8m#8?A$cQ^Ttj5@6RGehLzF(Y}_S*UBO1 zO>x7eqbTRK)%d-1zM_!WGN|lVQ{0MG-egRblK~^M_o@n#y;@OM*fOXNQf_JJ#ENC{H}{u)LFI+Y6(fTdQI^nS4&e0mtYQcT~u6)Q9#jd*b>kdzqtYl_vR@Y zbGSr}f8ISvxblsq<+3H9KhVy|g@}R$u5T21aT+35&}xe&HqnEOgYyULS}a0#ZquA!whm?eE?nIEtvVAk<9j%U%d zXRWV1?FpAKP4Zn^0SReeTjnTi37Gc;;7|q3b=BMZt!~N+nCkj%uZD!P0dx#Q?JHJd zC{KS0bDgGs{~&n+87^f>nn5x3kdQw1vJDB#(LtVnO_SJ$V_(UW*l-C`-mi`|K*IUJ z3>y-bp^c@#)nll*Fa>8jnxThuh^T?A8#8>b zO&O?BJyw@OFOR9119fVP_x~Bz5ZD5P#yENlWZUlml}~H zU&mBK%8g&$cG*nH14JKtT!-klLwd+&{&2}b>Jf4qvUSJDI#~?DyriJ9`)#Q}^!kKJ zozIy|7<(F>Cy5F55M8n5Ba2;|Cj`BY*ZiB6XdmGppEt{Zi>i;q@9QDr=6o-UX`Lq` zPtpX$)gc`X=jM3J8%CQX2k$v&8yfZKqUz!THrdRQI~d897gkcnj?>B;Ut7?8$Hi2? z)?*4+xZF3wrj#7X!TI}So$-4H$QyW=5e*23FQW$JZ1VS=*V~kGOVacYFRsvQG8T51 z_aS$nlUj%?7udBd4)0H$rFsW_Q*!7Wl+u6?s!N>Ve#;y?T5@okkyuB!m>e2Xf;wAv zy?D8c5#Dd@g9^Pv<=#MTj#Q`t^$sv4X=*>q|_@kVb*h~i|k=}F8mt)`x{!l)pfmO?g!5QV4L(CDpEj!x0 zWWcI0Ap9OQHe@XBC%dsYCOPz7oYC+|?^qB%fUF9(3ON6HJvZNWf>fYxZX@+Cpi ze730wQp)yC_0pTAoR=KDrtT|)l%h?a>FkhlgmO3f5yaFcFX$uRC%+^)^jVNxi(el_ z2O3|jww0PVG`cBAsvqhduCD$}`^uagGzslztFPkM>LXL!XtixiPAtmlm*jd7MbY1T zzctIbj$glzF2b+%$-j7$LBW=seUgLEugP@}^^WyviwB#FV$k%M2h zSf3si;7U83!O}y?(KR3}y&jV8ZXTs2tE4SS>m`RFOD|!23>Vw`gsgw7#-Nkx+vEEU4IWnjRZDP?) zLPy`A$jiF>dWYiO)4XYgX6x&)UE(x!iI`SfsCT>*vv8O*Ek$U_;^;hVVay%;TAR9L zs1xn5*b)^iIZB?Bw&WD(9jni-o$O1CEBxj=Is9yO0sIC;PV%IEBHLo#NDh8;kCffh zJLbg&2YAtyk)_C^fZ({id+6$p%=M!!Guv`rqG=5eNvXc4@1(oAd;W0a{fm`SCzs*# zci+^#)fXY5DIvqPwCb@f?WyGGHfGHwUB13kZRU=-BiwEPS|fFG8!>lBCNA2Sq9`t{ z8RJGP7u(Ww_@HmsXXx_uo$9ae`TZ-e+kiGmoxHyKeb03q>cH5j%UJ)d*Acs<)sVCe znYz3BcN%izS5E2gMD2Eq^o~>iDJ$c18*24cke9jPTd#w5$*YtcyG#gAugTSSsz{HT z`?U`l)OJXne7>F=m5z?5PVZQg9zLN9?c>}2)-vSWSIbUT+}3w0O4;<=m)>NO-is>y zX)#X^Z4DNC?O<3@LX%x=U$f_`%GJ# z{P40~sCzdzb051maVA7gT%M4B z9h2nuiZkLPLuLkimc>K*eFmQTdqiG4E9OP#un369Ou-Ns1p zUyrg*#Y6=E7&zzyr)|=G>A##l7!>$pa74_htcrBxW>#$Qm~Qgq$5v|7q)zUi&)<`M?^Rad&0`+OPnN5sMO{MxnpZ2iA#^V1JT{1NnJpU(2ZI$H?|mvq!F29KNd z_s0EcS&z2r3-$kcl$ExBf%4ALfaF*A``^ zC+t}L$INm5JzeFqMYd(-N&oTe?LX@KU;q9ma&P?U%-oWiB%BBMPi;wV=IQvok^lVt z>-VGldwa^KOl(Wcl>Vo4*FGN)8#O*?_JWXQ;p?Mz#Kk42q&H?Z-Vuu4lnLYc^S{$mlH=lbM6C~B7P4S=&^zNt4g0uH*Upx$1sWjuU9TxQ%Kvu6wOvG(LAN7r zN8M58Ku3L0=0-3?@fX+;e}N)? zs2%Y`74bLP5kEo^f1e%k_bK8h*%AMwBK~zd;$Kt5FSa9ou_FFcJK{f;5j`)`p z@gLd||Dh$m_{K%vuj3WKR$R?}BNHbK!XVp#FzC6t#1Q{`JK}$@h`-8?_$w9h$;5$+Aaw_pPIiH@q_G$AEb!C(T?~V6!A0ch=0)%Uwq~)Z#VPW z09}23Dig;H!U)@dFye{%ixGc`9q~gH@l))GpJItG)@hp?Ia+=PPDyI(Wa6kn7-}03 z{PpH9K>WY$h##zopJqq=R7-sEsf&E?xnhzrl|9 z8x-+x+Y$e^CBE3$TRv{U`vRPG?bRp~@dja$Z9w>4XZ`}jPqia{swKYo+#}mY!;Hq; zmd|D4h(VZc8xW?|n^T1N33kMfx5O9quJYY_ULN6oy{ld(4rBDN4G5F#%qc?rLw3YJ zWQi|6@sLk<@KOYi0Ul3e;*dd@X&Vq`)|yj<_$hY8PqD-oU-`)X4|pkp4+NiAG7*P* zWE&6`)tFO+_*r(uzbVH*Xb}9H+hAKJ3$IiRsW!_;{H=Dx-)f03R=da^BU*|!{X!0E zs%7GULHN-&ApBTmP7&f~+7UlPjvtFTPM(d1gEvJ6JvPfn{GE2h-)V_2HsByP?^{j3 z!%>H)PA2vnguiS9!e5o<6zw;N#ah{o2u}}gGH8osA_gtZHXwXoVb*~78Fs|akmK(& z2qRf<9Vrus7iva6GRsH&6g%RlSmKLs2Q}YpP>MGFjcXo*8f9XyK{#j|5aPCMVjh|` z?1lzE`REo;1Mcbgy_Sh+gAi{U5aLVC8lny2+fN_ZsM~M@^yyof*kuq-+XjS`60?S# z2I1=x8wwI6;p+!Y@x_}?&9|{ye)&0GJj0Jz)4a- zqfBfwh`G!cm6SAYzd7d=nssb72umN?_y{~Fx1^vczWB6ng$>{&?&m#~i7f`0MAASy6bvB4Jw9wNzW*zG>9@++kKW;b07hil@Z$rZw^hz&eVx2)O@YLBLaLLx= zUbb1sT7wX48xZ#2GRF@$2){PiP;gEXe$8r%FFqUE{9HrJ%8Gl#{?BA$jX}KM<%tay z@EqlR|E5{TKL#84-cEXpbIhM8DxfbVe|D)3rv*!8CP;+skGnNpqx+`gXp zMkc}xVgp{EvMieV=>;mY1Z<==(C8;)`#-e{Ca0S0wTKH!=}w5TA{d9kB9Lz(>`W z&oaz9mLX^To1g1x$z4N@s=H(+mKwxIy&G+$3O)#WKe}ipmKYrJx=S{Q8}L1G_kx)S zF=%dh+W_FF%In5?Gw?SG&$VU3mA}9>gX_g~bNt0vNDD5?15(PUroV5YtW(dKfnbA} z)OnraKvm25@Lkn8iFSbK-(Z8raNOB+Ft}w@(=TvW;e7l|6JRii@y;fn$vgrMs+^Ce zH4%*l@mOcNnIRu)YstZ*M(1OxO~hLR-mTcEO2CDOOY-R^;*9~XP|zI`z7Dvpay@;j ziFl2&F#Eo4nI6+Ga5Um|F{O!kWe~G`B2{(3wTVwwaue~=fcGnu$M0HdB3D-TlA4GY zn4k^Xs;UDHSOz^j*+e`yh;_K&r}pJ=`4UifqKSBB5MNB+u1dhc)wCChO~g|yC_+q! ztXqD8=@+=A33+q8iO?Iwy`6We>Ue-!vgc?M(O?iS;DVsqcfj>dw+jhPgboAWK+}nB zUaCsb*%rh%5%uUsCMgeVw&Wg4;-rQnO+=kR^ES+hZjSLQRyj^0uX=m9iK#J&C%jGP zzgx;T{qhJy*@?I&qS_!9`74j4w&dWd%D?Df6H$dO^f$T`$4^$JB>wj7Koe1E5D$2m z&XTv3Zu+GPlZ4p)O+*Fq_fzGu+m;;sPkmYt(?mQnXkPr~MDC&Z$-=Eer@vn8YhubU zrSdhMOm8XQ^h+IFjGW%nL_9QzwbPWxi(7JV7&fhTcN0-!5aYb*QYAlG@F3+Kx2uVG zfZ+p<(bNbyM*Hs3&L*M=I&gVWjev`&ZgEjf#C?NUIgPHI@>BH;jwmX(HxUH}@zjUP zL*FgG9B!aKJhionxM$EjUE-Xqs^k^?b}V_irHRQih&iK`2i03@fn&bWIh&h^y9P~T zLeJBxT5y-RXF}tqCMFjtTj8!eSKm?%?l8NrcpA~f+%{;+W;vZz)uVBmRkpr~xMdKp zk20aD8nPa1#T`P`cmED-_&2@IPe;Zvt+hhd5$>5v}DH&@Z?G;CJM$LBU)z zXkP8>M>pAdT0(K@e&4IN3g(JI^Ll@Ox`a ze$b#`&KNZMorB2D5HD@;=P+m|jPjQ9Ic?Bn{0OTppV@>v-9Kj3D3}z3rh1#d@&=K0 z+9FX4wpCXtn3D!g_Fq2A+e_9AZkhW0m0h7=5)BS7Q)ap=uTEJ*J0yoLvr}J{E2yIe zhbM<7xhk)2SyQ-0>NP9nWvPNXVsOyMPxnyX8?&ZxY1Cup$>+riD$d~W?DWFk%1dk3 z)PBjK_rg=p3KdkW!Lc!C!x$H9=i|y!i<2B(Mz7Cl%vW%G4USJ!7yFxEWVcpiqT?k; zry;?qPjeOAE`wuTO2`PKcOj(?<=@9~G3c|9l)76AZaeg4ZVu{27af#wm47Emj=h35 zXFk2I;5HlHeSCJ~4}MzZ6;SKs;=sPv?}v?NA7?7a^#;xJoWl+)rw(zm++wvRFW`jw zkf|#V=R7~JK*J4S0=%@!B^`K*ZCFej#f6&n%bo2*uYf#RZI@T5JmeG+f9r?prb$N=np#j1GzOQXG|UI3U`a#F5C7RGxf64YYcg(NwMjjCZ(Bo8hF8ZbI_(2oP%$aUU10UPOMFDJF(5Y?ZB(g z>zMvFy!yaHkJ77;(RdqLOzNyCCvG}GU4_Hg>@PM^c;Q1V62!p|0bc|D$P z)0fxu#};2+@E3N-$F{$)Ll%EwW7{WW z=Y1BRknlVC)(*duZ!LZ&?elFpUE$jj{xj=G+4P?|O5s1V^*%bu4`^?jen5LG{D8vO z=&jcI8iha8OLq7(y=3uc3ZJQIwtc3$rCEHYw%#u*e7`2z;rlhw;`f}^72b=gu8HjaLE|l2zP06 zBMeW)`F43K&XYYAx857F-5!@4WRJ`6>ija$4zJE*n!GwMhg;d!d5(r}>ScELrd}p{ znuhP|y>|G%-Xr_IZoPMHc<{b&mj~}c*@HKE2_I;Om+*m2Ucy(S8??t0`J}h9Cvx~Y zU)>sC=kTU}!Y*&>CuDExYcTZLebN+hN@nQCO=YJ`C=eN`U|6g)^yfBbv*9!xw@(TkIZ~$%BD+aal zD+YKcVY(gfButaz;~j=WcD%z7C%?n62@}=Uz4Xx9wdAIH zUsf5_^sjKkTmLE+-rb68&AVIs(0*F?vRCWh4zufRvajTq!|?W5rX6peU6$WI zYwg==t$)3(weQ2l8pJ1dy+GF>zd+aeH|~VNcD#w#^oAbZ=(}vk8-1Db8-0jB%Z~W7 z=^pYp$FM#}3y@7ok>r}g5*?#uatO4(YXWH?O zccvo#W;^0YrXM||ZwlN&Y_bA;n5WvIkZI*$`$c5?1-PCh#zZ5{QZjfYwU=>MiGCm9r5QV z;(udD{BIQT2ig&TfFiz!9q~Qn_(=xg>17-FA1Mh>Uz*Dc@r&(P{bc* zNBmC}@dw!v{}V<05ABHmp(4JI9r1k>@w?a&ze_vfd)gu2-6B8JM(f%vb;PBUQyuwW z+WYIshmL$u?gDqzhyOe3L#FgU+OEF+1`it@7&LRvA0hv&i`pHRn3C3*>5y~h&b`-V zLiG#1Q2n~h;ohA)IgOeBotBaqw>xUxnvg%{%nS;AXY{ba{d~J>Lz@j3YK5OC{l}x% zkbo((7q5=o6Ms50x1=UX|4RR#nv&ei)A4&ES1+DDC16M|k4<(;GL-lyxpXDs0h=K>r>td+e4~Fa4|gpuiu4Blf3dm2cG->i<=qm9{@3 z_{YFO-4EL><(2d==U!h&CZA@S1=7T%G zw027Gx(u8Y9F?3~_YxA4_3zf@CPxKN9_V`0&LqT0?`r#v|9wmHU0nkt9M!+8yPLe_ z_woI-ckGmKNP5TBf7*(JS8J*u;Gq7UnyUv_O!IebzE#2hhGXD|&J0h?(Us{l_5Y(Q z%fFqOmK3*pdw5vL;yH7Ig1#C1W#cdhzkdDtyzVNvypRN!*Iga@^y}x>IPAZ_9Q#dB z(40AoL&Czh?~Z#XDJ}DMewi*#|Bfy72ykv>oVpQ^$e zXA+_#LKprtdF(JhU-u;GmGmEXU%z2vC;zlCG$K0T%#FgT6VQ@a`hV++uAPdFT(KZ1V34ode(ACFZ#UmT0YM8^M8=-FR-{YScdE|VF=vE( zt)0@(OP#un`70(%cUxbt|F4Ry)R>6Hv&IenK)X%4Fa4MHgTdowEslsu&3c@!@1)C$ z`D;wK=EooSMU;t02PZC%&%3UFsekW5W`d)-$+1t++T@bUdZF&!J864Y%?=#kwp_X_z2`O{aQ3RbX?N-_>m5sy*9P@z z{?a->nc0$~M?gq?(N+Ds#TQ~%&mR4ubFg$yde`~G(X&^_UMRk*cPNSv2@sHS%b9w< zxVus(r-46hzgmAo|4z}FT_IEbJ?K^gs)~pI)R0|gif-sT)nDEI(?BQX+u(e81(IX8 z@vBajXX)Rm$cR}Mfh)c%TKPF&`o(Zk1y{5U2B-X@AfTy zr^1x23&!@Yg1CcHr`}^1Y)vUF&_CBZ-rhIgU%SJuuPu`tT}Or_KDw>%RD3FO&X--v zfbEq!b@_76=2OKbdcEH9QR0%3uF7+q{Ipd{j$Y&cKla}ID~hZ8_pj6DbN`U)1_S{W zm1um77>$W0#v~?cq6taFXiR*bB<4}nOd3=iP;o?@!2t&_ID-QM4mgX5BB02if{fj* z#yR)Bt1M`meGXZ$*1g}gzSkdItlst7d!JKh+-ImonKf74|5J5hedO?v5@_3?|4+#9 z$o0poD&6(&S86gBeIDewTUD;D7TUs><~R%7|I1a7y5Q@+4*D4CYW;sX`hLA2wZK*9 zu6DoT%vl;9aJjZ;dWSw!c2yL)UnxwUIXvhD zbYTa7(D0ebg+=ZT6}zVN@!Y*G*Yk=V_+@;4F}<~A5uwM7l)Mreu`IKu*xeyN{)<4* zFDJ{?;9JG^!BOH!-3|9E1>2_&@jYzld9J_WJ7oIyYxuS9NYr3^w(+ZabT&u-pWtzu zO5E;Sc=6}rzv+3vfF9|u^c)|5&Uq6xebcyL&w~syr{?J$2F}cAc;>D^;xb}B3o^H~ z-$yQe7L!p^;_lFpF>~PQf8f(Xy@P-Fy6cbJmF`!n(r10>Yi>htK=MAEm0kr+*Vl#n zpZ^D%V)YI^Che|%;I47MQh6}y10Qqacn!W8`$5#f%3JOZ)w?J4xbzP+Eko*Noj}*# zfYi}9Y0VG8S3(m`Qq`2YJ2+3y8tD1iV_81M=^Y%yag~^o<(^Wy68N9t=SdPvBpTM zy3YOyVr1go+wKl^>C^x9@MXGA=^g$xJ-rUU-c9_(QU4G0?AC2VR^9|~Cuw|;HH0~i z!Z1GR_HDQA=E@5q0H1f_>Ni&-M0MsZ`}U}S`O;ASAWZa zmi*ZF9o8nsqe$rcvCwVH`R&!e{(+W5^rntF@3?K}qu;a!lj*wc&FJ%YP(zRY82scP zXd$zoCe=X8+1dT>yv*JMNapOb&{C81spI)S&~i+-y%AFgC8wjptkLsce9ArQ^c{2} zG5tMHgvv@xHa?X;`8YZ9N1t0S9ZA(~eg1$W;`n4OcqSZC7nYNH2m8lcYwo&jxpVtj zQ;8HLW^V3XBxdW!_NEV3%iPGp%Za)Q6$Oj%^>owT4&CIaIs z@0dN#O1G_K<$KnYC`q@yx3Z)bdmwW{U3ZhJkpq|)7?DrvMM|D`MVzPV%c z)~W^!s%^JoUtiFwmP_#E>vU%os_y=YovfV&I0V{h;(m-;(40EczxG|!s-DYu6^r1F z?ez5S*3$I`-PV2j=?7@tZ6%BPP+qCu4_Y-x#6kFCyBYhYwKRuM$`6RW`N-{pCkG=O z)c&cpT+?l#(Zxtm&CYO#75_&6;0WJQ^Av+bTXA&gidMB;*KHr~sD~@ZCkLN;X)(?y zg3sNxZd?71kEumhds3|10wXUN$?1Rm<+IE64*g?qB3NO&7#T#pi(17^l+O6dduX6{ zf8oe_`S4O*IKoA0J(N_g?neiE)JjTG?ms)=!sAz>dStwO7~=uGN7NOlb{+WKPUCsC zl5*V^G>smwP5Rh=q*2KWs#8g|qsyi>ELD7Lwkma>JN2W z$nSah_4GvFZLR94(QRF#Zom`gpP$(53dPTkfkrUbvP&u*299b!+!#uSXOPA9 zZ|!K*fi%9gj=akVx6?UGwT=h6&F}k5c)DOl=WUHTkk_4mFTk&tzW1XUi(1EDC}}g0 ztF|K%j;)P4Ds`J9;s}1dIU|Tpp{jK}#(RvpUE{V@Zy30#Q3p!jzzx;-^>$1*n(L@_ zpj5sVcMnhBjOv=ur~~z&Yt&8rdN1y^^j38|NBvE#!_#Lb`fX^`QA%a+41TRk>__tz zwI6@$w!z6xG&;^b!`C+IK+7It+p${Z}?>D9~;GQ}UtV z=?YRB4#2i%+wkBIsaq?fYm+qlP@) z_R&6Mj>!I$4z{Z_%-6;6tjF#LZd=2)w-+~R$VLCZtpOUcM$p7sr6EQaA7}i9Zq-)1 zzVE_D4HwauudjuMjE~Q?qG6FPh8=l8{d?uo&;^YeFd7J5S_utDjH?4wKE&!`NSYfn z99wyGmwAmE&Z04kE{BG+Av6nAX;_MSl8$SL(pat>{>e z(wF3Hzy!&55|H-%U(O5kbb^NF6 zP*E9;&E4w!#2CkKS_!3NvfqowXJhpY{63jdQZmi2xbb&>Xu2L1;oE#XTQtS*S>v;@ z`Vr`uLOSxlwcl*~o!7BJ7o*SO(c(xynjDx#AXYy_DvFUwXGc@WO;s`ybTQ~4G?f18 z|ETfNSUnB({1+4qad1#+X@H(UQmVKYZeAlC{m>Z<8hy<<@D^b5Q?c=sEj>3h)VoFQXO)dV^BQ*OV!%OYaHYIa*Z63xz6%qo z6c;oc9B{AkXI?`x{9QxF4iBLTwUvxyGy;dowKXA4Kl2)3%!^6}`kZ|xKuuzc8#0mrvjp*TN+=IE-ov0E2!Wk5su?|x009;@Re z`TdTUBr@K1wxR*^&8}-2(0$Y_H^R`C6DTX4m)GETMikaOhm;HH`otE1iwM*76L;Z0?p&&xDj^xA3USyNq%MHt-3VUdir z5_L{eWsbrGk=|QVdl0Lm{lHts?^RJAm7n+FFnZF~8rT&`m;?h84(#4Nj+9xVCCNtS*ID zfu71S8OBf;C|qi3ScbxIj9i=SuzEF~KZ(}%BpxjK*51_J#_H&v?cWx=(6x$_gJvmn zAqOqx$_6B&E;g{K*N+8b3yiJ1Pa%zll|gfqI!^22t70S~{f!GAiNHAJjr0cyXNcl4 z)(C-5#aUgvx(j^a*G*%eSREs!v4wwOlYrRusx?UAc?^9VQ|?k476zR4@L&N(5)11d z(@^Pdq{A92@j5Q(Vqi9H4`w~9Pqtdj>SYohTn1jjCs)iScp@_T>tsy6$1`K2arekTW`UZ{=_w1&OBhAZg*ufvBsk&bMSRA7z85qSrSua91;Ov6=OblHqt+!>nb zfzQ;nP;DoE-rUt1xAW$&;gy$=nQ&V9v2tLBZqvizl_(jzLTUZFF1jbf`-bJ6nkGb~ zbEn^Jnw61)=ctgU@q6ibYx2YM#VK=`Gdx(M%!3ls15xokEnYP_@JJWmR~Yu!DfwG? z1(XB3dNj?kNy}fl=z+WyrDNAC`7&JuZ^3gVW1Hsr#6Lj)bQ8aC4&JEb%Q2`Za$&ra z*oD^2ni6my-E;z`9HMBHH5cb|5Oe&1c;ms*sm(OFP`C?8b$oyo5#Tl4(ZvV(^xy_# zv9`$vgk*v?km~#otj$Hd2E;0S7dGI*!ci&Be5gnA$Oq>_Un{b~Yp6o&nL|FT4{8ej zkS7Qh1+6C^a^6T%=43TGfyip|;nSuN6lthI=S@CTPqlAXYQTd%_T!x;1Z{7Yh-zH~ zCE)k{p;nZLPeZLP#+1QX$Mo7@x`B?Tzulkeg8^w8Z({yuVwa`05Yjm=VkS{8N+zn6tuK}S9Xh-Uh) za-rpKSm4tX;Uy0cf$~{^_`c1x%HN89@;XrS-#7d( z+>xH~&HOLYMbE?d{qi6y4$bq=bTPFC8C4(USk+7cqF;`vdbeBDOg*Gb!3)%vWLU}{ zva*?i0!+cs&WK%|t#CT;!QV&$J+~=vMKd1VW#A^#JLss=zD=Ku1c=HB%bMZ&y7;D& z+@EYM32^4IBQa1*f|Z;;OPk>sEA&ANC6WR&71ps-G4L`KvlYHen&B9%_%27$6qg5P zDfnf&?T2a_{FH?+ZpLG{6pjrm1xonxL89-QrSQlTLZepWnlKSDD&VnH(n=XEgxn@z;+aBF>u z%l~+oiE%!~AXXd=o!?BsIkYZ_soGpI4r^~Grva0yko3nC?sLXC<~7rRkRO!8hxEpS zE1ZTkD5!;xXeeJcD!Q2h4E;yZTPPf29pvKpwHSQV{Do+t$m}_%8J~l&(=l2X5h7xt@F^FaRe6} zOI>ijb~o zFTD5+U3ANUr1+hD1-}(T$|^jy!pC!~0M$VR0en_KQuCd4^#YgvZB$5+eo_4$MHM^~ za99`LRwHLzv5o?T2iq|y-3vPfgD9%$so)Tr)&fZO2VYn4h;)Ua5koEUzJWTO>J}uI z!>qjkTncugrdN=gOB@uP@pJ%zQAaE#u41Hhi3z6x(Ynd?c&cUsg@-&996;f!fuw$N zu_6V#uwonsKa1X>@RTRNAA=++196U<3LXKx;DdN7yE}zbJ^6i@aAZSrFxa{Zhx0#G z7kzW_)OtS(v3c@)b>X)jlH9)5rVfrr_-|M_rE;3ni-%+y$%mB+9yMhgmDBt%&+TE<3ZQlxp4E)Eb}4cGqo)4m1fS8B zLgt?Kw;^XTAaVYFPhtPa%Unt_;>VtR5=!7!au#(V!b|Fh87SK_JckWw!m9bDHeNUBCT7535FO+-Xd?DF^A_DKGs zd-dN5N!>)3f=6fjJ5|Az4hnpGrXRh#eJLca-`xrx!BYDQJmvi5h8Mq17t^V1t!%s> zl1u+7UHERpQ#E5L=Iv=84aVSfsld)mhcum5#OGJ~yy5}f@rXsOTQsq>-Nz4&+xB+o-~w#Q?IeS~Ry=i#ZN z!4z@#w2!EJFh*A*ulExLkKtc&1JjTc!lOZYU|5U+ay!>t%o{D!|_2Mx~ zcdUY>{9WsARxbSr@eZ%VOu!X&#fx9AizpW)mBXJa>|fNymo<3G`4h$JJ^f#%i=Uj3 z)O`6u!DGfTu>nujj-^<=Cyz-|MC}9Yz?-Nmc+7aF8(xmg_2QRce1VkNT+W>FYu-Ml>AqAt4;YIJp8NvT=mU>vUbD9&P3?IeVQD|~9I$c5c*e|T`hPwo zDUWGhl@<^uHqdY#Rd2vqX zHB$N}-cUB`jB6&XXn5jS)tG^KAC_iLccH)%Z@mO}N}nQr8m(t|@>tWD=EA}ZiWSAR zc>ER>FyPLgvb^v=bTNsl{JD_zQo0l!{yCy2W_rvkS^{W#j3 z<>~tjjHT)!IUll7N|(cRGG6yJt)(=lZTi?ch|Yy1Nbw3x8qX3whL%#A<9{QrOZ=HG ziBcXR(qQcT6?I=8X~DlhLrVPV&J+<|v z@F(z0&1Z*N@XxSFObz+T;4M-fG3G!jc-5oQTkub@kbrYEB0G@c(!BjDRGJL3KPs&S z{{#amvY!z^adDo%ho%9mr=s?=gDv>S@SoD3?zc_KSA*Y+XX-wub)e??_Xr^=%Kv?S z6xZkN*Pv-hA^wZ~E%?85@dfcI4q8~?`C9N>i667C1^@76`AKr@l=5|Gni7aN){&a~ z{{R66vY%k5r4inKovtBbB%I^+v@m~P7hj|9X>oG8Ue^{7J#lvn8X^6OM90v=4sW|b z*M0--nv~jt#v0U*=)<+&cS&g{1`{r{ORnjEw!k5qZpi*?%D7<;OiUmLUz%v@ZD#0j z>~~7m1k*8&M)K(0YIDuxdD6&~XwY?En3=-S$l4L88QLN zHtd3|u$NgII3BgC>jgY>B*3gyo;=mYgOFVaF>52oqgDr;#4|a;X07(*(HaC~LUuC1 zYz;Ubt%_e7p2_WIwkn=HS|g)1PWLlgBaTO_W={fN)XQwuJbAQ+-7Z0vWH(zwp4XP+ zk+LweQT4>p822j1i(M(FpXpi@=(~f=Cf$=pS3oaTi!xhs&Nh04iBvDDhnYQ!Cyt)z zy}MMOCYn77$D>CYS%+up!p$DdlSfZz%0`+!8ONhX96~mn-cR zc*3`IZ7p8ON!&ca*Ex)V9DDFYii3lNBN?(i4s+zx(lqF`H2yl`XO6!-O=H;Rhds{P zS{aPdTa+>QyAW&+{yb?KG@>SIQRbMhrD=>E$71Y=v1f5FbL{DfW5n94nBK;B=9sf3 zKO1rL>K8QfE$eTNd_8%Lm;2G%tR89(d0X;xs6;=fad-7lbKLF8W4u1J+IZvUfV(9h zjp~^HjK=-Wugr13Cy)8US5)BgyPcA}IE?%KjywggMViwNPyD*BMdHO}_?feXmew)r zuqQpitVN4ACq15Yfv&9pon$xXDjcmvKc*?u9tTGoB}$tsqcLNe=z2^uwvz$ojLp+H zW_kf9>CNGqI=;wa#`Ymj&tt}z-_4vcdh(cQcFPA}_t9Aij~QcF8CC<`Wnr|K+I(Ds zd9G27%fifwX3NB5!Ww*m418eDmOYJQj{QEhL6?HfX>Lmzvt`Hrhi=WKO{BHd=En10 zHh~8n<|LcLwUww9vptQPeLK|O&sQQ`NX=j_&Ix-OHvc}0 ze9eWlxW`oqkD#7kHl8R8vx17ATF$(zE7^Y6q`bBfc>M?~rs;{F#z3eW(n~r>+QXsJ$kNsaLvM|pi{FH9ERrld_3B4M@)EjU-+mFYEUlyb{LklH5 zg26_<6?cEYm`4!18@-imzalBGt-%A;@7`zd6Iks-q0rXJ=(W1`BWTwIE0XS+afrYh z1@gyYiS2b52h~FTQWs-8Ti2FCb?uNF5_&yqb0MBccksAyq(fR5a#O-%8NiW-CkjHW zWe88_v3wCyU}!Iq^4fenP(7%D@gA!ugDB@>WHc5rKBw9`(OQG?G=2;LAEqpHZ zrzOhhO}h32=(-QA^)pZUh}o@aKa@*sWASSsW$g57Cf5(^+BB%Heyc)4Z$X>}?Yov_ zt^Il0KBQ|&p!0%mOKEKz9=H_1(<0y^RPT_{ThV@lzT3}QxAe4~j!__{1X|=BiR~m* z#ae_fU0++vrk;2jVjM0=Yu~vm;kTg$z6JwJtyNo3{GeIat_4?0X>AvL&tkuxbP)S8 z&`4>2%INL7HWhSPPiw{5)Aj-E>B0C{n_4BYy#rMV+ku30w6);viSI{r%?WAEpnDR2 zC*FA;48&OL?Vk8PU5kNhd4c9Kcgucpr>OD6JWs(A*gZ-)`>mus=+r3X_xjgL_+6;y=P2c+OkLTHpcPy@=U*qKwRk*` z<=|B5XVOz~hNB!(*B9pgh?e<|Cqhf%``6Y!5lbfN+qX>HN*^c^MYTez0j$t|U|4S-XNn0JoCycx0t8H?KR6_git z*p8D6ZB0|B~=%fPHgdAmSR6?Mb>8 z0oP9B;z)j;nWk$C0Vi1d+B|Ke+em^hC;2GodZ8_5^y;n~gR`TPN1W8a$#JI7CIN< ziF^I59gUvWH=$3Z=Ci2tGl}m~CdZVs=16hiU#o%%rPaBJ_ar|I>&7LH1iB0;lEuM^HQ zxxG%O@u)VAuUy&inVO}-Af6RLI5()3Yu#tUbjzLu^NouSWDsA z<1d}Ib~}5b_&7=e^t#9W!J5U?i}Dhm(&b^%1(zJz+$CM3B)GN-S@Pu>CcE&_kuNEz zrETWfle#tvY?8H0+|z4(Fe8ZqYTD>otX=GS3Pg5_(h#0Tv42AGxWd5M65eP{#^o@6 zW0F4Z31WfTYM#x(zBaUkT8gzn-qZ0oyvC;#NOTUH!&*j+!2dElujs+!q6hZnWADXf z|7ZzMBS~$dwY%Tb_)5&@C;+R)@GQa`1JE~Wd#%F-p707?+xwiNw{>sMWi2DP;>d>Q zcYE=;=y~^rv1`NF&fl_xpG624o=-Z?JUp~9g_-D)oV*x+gEgL8^kw)WV8sp z0p-a&jRl5}DBr$GXKg<GSln=&1Duw|!cTPVAZ3~#EsJop771}}n2Z)- z2xe$eJdK5!5X=s>MF&~ik1=OAGL4pWTH3ybkSoN6Sp05YP|P(DS~#Z;KbfZsFioLO z8RAU4j28AxC_l>6#VE$4e9#`&_I=b(dY|KH<+Y{l8`v#* z72=J%S=y~@>!G}$Bdz;#wDudsce=@F(d`bD|H0E(cn+a9_2*R9woBK3hVshpv}E1V z_DyVAg7~tJj21q*P`;k0v1A@r5Amo!S=&ww0ioRa7A>~7v|XZWset$GVqxqX+6(C+ zp2a?a;ZR-QUj~c*q%Yrttw##&F4#fGsGFtdvf%1hsU5>-`l@O2F$=a^PNC?WS zdnK^8%Z*~JW$a+#8eKaA)rmZd4J~gvvG>9Cu8bD%xFC1FO`Bm_=6QwDQ)o%sS=-gd z$UsZy+1q9@tm!R-MQ=ogwNaZ`%Xd&#&O*Fw8*BO=g5Jopv-ZsrTKg8_iY_u*bjBQA z`;Movhsl@PmEWve7n--mRoL?fTu(t1F9RuQP9c8rWcn!*@^E9?!b-D%dgsp74 zD|KxGl;1M8qqcls5YL79tX)P6`xz*oOWVF$(p6~FsGN@4!rDf{s9mWvHl1-awQ1Us z%`AM!ET?fvtYvI^d##qr=};Lh-lK9_``R`MuWf+slufK{lte@Uv<*C4jn=dQx*Ph- zXwl!0htX!gmgz<@9cvkVI)?087SFyM5ENqfUGtXA98}W| zJn9-ToZ!+78UYGz@>&+gNMMGVU<``7M_VMgP4AiMDx#=&X zMaLV|mJHs<+QtMY8QOo1V{KnI>s2z(($tFD&|Cg8TKM0B+}UqGYa0{0MA)9WlC_OZ zjx!-mgyO*-I5w+P3>GwQ~<3x6vn0+XPKR^Y{wI5`o_(%s@58N&b{Q<}7eK#%nDrVoqP*I_V_B~+ zW0M4=yLk2m##U4}3w>m?@WE{}!WnjiwTFtFH-q0?2#Zq9pSOs$eM#5mK{|_PZMt6xRNo7c!6JlQEAh){k zlWA`F@kz#OusaQQpp+d5b|=T;%t&b6Gg@rTn9UkKg{}tgsxFjHjd)k)XpA~&u~i!{ zh3Q>IbL?E~CM2AX4^ zOWZzc7W#gd7_Y%Df@Rkma#*~9IgPf6qj7G^QABP%lbFU%IZ9ZOBbTwg#0b%7lNB%x zqDDzrs^vAoa@c&CNgR!h8bQ_Yd&g6W--od=0qU!RE;D{FHA5kz6)=rDf0VEpP<#W& zrTAKjy^RjcF|`biUWQ3OHGNB-Na#blwgl>(ujMg*FEeY-00le%en&~z3ov}@vBYq? zuH`^}fn%5J+92qiu3%|0HRuZCGuWj_P(yb)EV@&TUdqucUS{Tg+6BwCfoa%q3H3Me zWy6-k5$vF%uJbzu{2k<~1K&X6*Cka%{h@U4v(PICvH2>sSNQ7C(@<4R|rM*LE&o++Jli2X8Cj zx8U}V5*FhiE%&~}@IGT$p`~+dJfd**fafaUx$wF^ zxP(QctzD^?c>SlYVK$^Ca_o9^HDwUSD&SanU4|QQTXr?;jX|$AQ~?i#*TW_3Dk#2T zm&3x2UEf-qguV*d>N-i9cj?+{$ggrNw*6Df_NjvX6!L*3Op3u$(Yx&{hlTHLSe_-J zPeQh(R^oMv(LZV@ITlA~d(a?iyaFB%*PK0WF@EEi#7_{`2FYo&()}c%(M@UTH4?v* zv9b;MZjQzA4u6`SOjp3u;aZJa-yYE%gCh}?K1VuOC{_n$f6W1m7!A{ zi}TG!nujXj_l-1Dhv1!N^h4ng-t&>u!k1>`;SzcoWQV91il*;ci2}@~?Rb$+vjL`B?(u^@J%^_z=Sek#X zRIt>B?dMqBS>U(}VT1yv{`Ic$wF&PuV+=nCLRW~K79lREA4EfOhMoplF3kYAG-Lje z3;8q&n*`Z91-lONBneCNsqYl*caRrwEaqCF=!afYu)QD;r74qldch(qwd*ubMnXjM=y;l+(hG zqWtekX!0wI=JH&AA&zhi@?RwEHprGJSjx}s5|+kp5ek;xV?M_sYSNY7zk;Rm6GE{M z@7w~5=0z){hzplqM0==wd@f-TNYqjkYzpLaB`mccGZietecFBrOCgmJ3KpXrt&C$4 z7wbbUW}qAvfi&3Y<79FRG-hF1E=AS2+(JZ;T90oeEcL3J6znF*mr2;ekWEvt)T$lk z*gcrPQNUz~0;URc$4+rb@A84@XI}{Gj2qwhBn!Vfs5hdR3{ClUhGM52jJPfJFykeb zV#I<+(K;r0_0;Q>;lL(D_DvEERe9fA^TOq{tWpZ30nZ!#|joB6zw|4VihA8x#bFy z!y<@!^3Jpx;hkHcF&NYeXbptREv&zgXTv2dr6^Uwrb7OUgiU~Kfr6zsktktl1!Sy( zr4n$4V{spn5A}*+3OEeeS?fb9PTskNrK&d|ti-44WZ5O`l~6xGLXU&&IIWU#d5tBy zVDB*=e}u)!N1F1gvJ!Wzm+ieB_zL_U^OW5jj z1M&e9_AAIT6)Z(LzLKyrAxluO)Mw6=u!k*pN**Cp(ykeyJlCm{b+ z!crT)UBPaHe6oa%g=~?6rPy$+gr!ygpA_sQ$de^3jhIF#*x`_$masIE|CfUO7v$v} zi=BS-0_%O`uyE9a?(~#-!=R}iUdxrd;U(s-qc$Tln3O^{EK zu=Ivw6fBM7VkIn<5sYRS!ssn_{b!P1B)OTtn=++V@=hrEDe zaqqaj4#FS>9E8$QXMfI=4s5ON2DtK~q;z0ocO~TAB9SGgr&Jdyn>B~{8tGZ1KB(UI}h?03A+KZ zX$p24I1_zEh=}cV!i^;BbL&4qz+e^X@0((KhUI05t!qVDAhJwui zJ3_*a1)Hj1QFd%&B`l4=5*2JB*qU{QZ3@MWqm(9S^bDA3zLgL$+! z&_V@T2(&kk(zGsDfua!=19%C4O5`QfM&G~#d3%YJrApIE39G^ zCTOt4lqL*1w733`qt{`zkHRV%TT#e6jCNM*{eK)Sht&{;)etz$ZIMQOy$dv0fd&KR zcJ2QE5Dijf6GjAr+YFhh|K&c5qC%~u_x}qZ)7WD4|Cs-8=bI7gYFlyme-+NE3K$D3 z8kdT;H);QO+TeC{8vl2Cxps8Rt&JlTgMrpi#So+&1D|#bx7sntYsb*C9Ru2S45QmI zcyGs)p&b*Ic1(}jF)8~2ZBrYkg6)`qwqx4cj>&dAruYMpO>GP;v?H9+jvz`~LpO-L z)*-EJiWq?{1KZX}ReK_2XJEIjk-7Fn1gS-BYb3Hg5zzKTUSr^MTO+>hi8TKNyKRkd zBmB^6Wc_O{iitAWo{0V+*lp`dLTf7+HxPN1tzfiyh(Dz_xXzC>Ly7 zSE$;vGDdZ9oMMGawIW9=fsqP!8sxH-K&nS=U4i@sY+F}85yflsitDFf4=d8ETIn4O zwyi77uYzss$~L;Pf3T+Bkz0erPLDRP=7XhO1aHe1^NrojvRwp6z_zw~A%ojR0K4tk zHL(JATidO%Qet-|*tYHxp`9G9?N0e#V%I2@ihNe>ib2G+&AV~Bfo*LUPuur%##>yE3PVB5OWi)K`9-C@?Aooz<@t=Q;A?f-agml*Qu1IXIClkYXK zZQMcFp1q6MomvdbZQ3;nHXCePcWcrle^PFX;I0|&#ZJ#jkjr+7#zNNC-KkT+wslwR z=V05q8~07Hs$IZWA(!m}{%8A-1G&Ae*kMkq9Btb94E8kG)^=Q<<#tVjO$IwjzG>Qc z!?K;;^oCp8VQ$=U>b>{{yOY7Tb!R*E4z2B|A0tIzo`ENlk*~#CYGXyy4aSfVV zJH1fAon8QY2(0SV#Mh9^PED+btgWXtsQ+*4DUq+hwstyYj3m7jU1{x9%s`&~hv#wp zIT|OQs2^_ANg%N4VB2~EX${!6o_wOYgX+Z9R>);1t|;)9qd2^j1G(&^S6|2~6>KHs zVce;ye{#01CkFZa**2UaJP*xnI#majTD+->Lv>Rjm!0OD51Hx|VLarrQ-l*BQ=ML< z(jz;)_%>uk3ZIK0AHYAZJhYgQeB0r3#kvbJKn1J%RlhGUFC#Yn|A0;{=>egELnV0S|ntzaobWEWLXM($Fu6fT!t zkU`<$s|uD@rDYd$V2FrIE@%%F_m*(;wfssDT)#pC`H$tWHWcy-7n56HsbTtA!A3$Z zy99*>7WuTz2UT1_5oqZl*m~-R#CEHsM!mm+TyIGI_dbw;mqUgPqoGFVn!q+~P_4NQu`Qg__e3!LVHZR^ERpMkwzEU8Xw z)fZNUwDqDaTq{P)hi$r`3+#5Vtz8VZqn^oUurwS$c0-chb?OVr-b3lA@w+L3aXYzB zHMC2vLTmjRwjqXk&Tm{imkrHrx&RLB60ogZJh!xt$**=^c-PuRdp)WAaCUJmAq{O? zyWsD7E#ouTB(QD0Sa2HHwq9sB2rsbBS1GpV8b@4RN$rQ~0?8GSAE;sS3+yb&+In#% z#uvCNwp3ClaG$zy3+YF^W%K&owq4hWD@th@vbBpsv$(ynU>Ac`T~JD4`^6F#K}=Dr zyc(6pzu`Rn53gnI%+a`tHV2y9bRjHQ>Q&o%5iLRy>ML-4?opb#t7!kp^}85&p%`&A zt{grE&40*m-!-l@$>A>k1xr)a)-D`gCSfVfZNBRG<$IQ2luS3SNv>JO)zXya7zG>y zIeWn~SSs6Xy;%AaIMwDWt$Gu!IE}E|bdfjMD6kh4hG|YAyFi>S zE`POc7nir^%5+@DPN9J|UDn?2YvNnGp#ELtNngqn@8tnp#!h25)y4SKpRgC>|APzp zGr+FlPK<#45{0MDp8{z8GX!HuJ^L8~T*gj*t)mOoxoIcr90g+74jUTKrTl;A=wBvFM1Iy_~Yfpni@6x%^j6SvYk)AztFFC_vbE2pXq~Skj z8F!)^-{g{f9_Sx_nkbaBy+PMbBDc57O>0{qXFrMr7A>Q=)8@}HQTZB8^Cd3Pn5)r; zpx&a3&AoGMy{=t_R~r=UYRK6SP=Q@V&4hy{rkr8iFmAsATK4HH`s(~}Ijqt51|=We zGQV^ZuYeI)lYH(AbNNIFZ`di|!uu`%(iq?`;nw>y76BXj!r2!TNbt^V`nFmX@?)6- zo&(SLZ>u5Xk_`Bo0zLzjdd6!1)V&3`j+wXPk6S7#M4O}E&u6@n$N z6<IKh(FZ_xr9=QS4DsBM&d{?q*M=pqHY zhzd6Q;ZZEH?SWU<6fm`{>?cpbPJmacZ>`eIL-MUv^DD0_Xf=~dGFEUA(vf`S6_+Ao zQ|dqX!t733#Pu#CpvSunZ`2xD99stwmo0f#g)6%W>*G0l96zn&HPugaVaZC>23W&H|R<5yN%xlH@`~0op$TdO(0u?0RB z@_v^f_ijsYIb${~m-x_re-6&Ts!0hfAHT@clcD)5IjnsJ`DC6oKTf|#0i$Wh%jD=q z#+&ZY5K5<%IKPkSBD4X1CtYCt28~g#ElLg>HE%f23OfQUwkvjt;#gzBETBwVjNRmjB8m5&M(a@8Fai)GBKbI1 zaeI=tVXzT!>Q@=6{R(LW-GR}P6uz`6G*^n|!l_%n9J|0Qwja%8-*2|AeFUqqXBn#t zbkUb;OOh0&d~)^W*!jjVNYsS_sDejGjzLFLEpzj6JKj5fO7#itps=jo!4^9jMbq%h*3!r6zT=VGu+ zuxRKti*@{@S!%Vk9L6^2u}|DD5L%JJS`4ymEYAv`;-`Q`iQjD2^eNmgkG4zP@ZtDq zU5kF|evZu}*}T_#AdcRp)5d@M&Firm6A86O3ihkkl?fI~aA~c^he2$7B+|IXi_#e8M z?#7#pk-Ay187(&HoGIl|RX-+84 zwa2iAFQ5se?!pEeQY32QnJrSk&l_j&pmrc{uI%V3QH{l^Zmmcf2OQYm{3 z(l2E!DhjEVkRor&eKnI?FYcSr7%^wAJbW{pQ87@X;hA7tm%AR44`*e;>Yt( z5MzV(O)^^B1nG=4*7g&uFi;y-_u6vS_ElZ)15YIo>wS~~WLPl#VAYF8jwfz`% z5BtnSRhJd4?Q6Q|LUpsI8_z$|wK!;xmeE=?q^l0Fwjb%@HA+``;7ZnZ0orz|n{~Z; z{x1Z0pnax{)@DMQxSzHCmoECz`@H2J$J)M*euGA1&Nq1eAx1^e{;Q1Eeugw^9=8rWH;Tp{I4=_7|_DC76MMAoNFKZj@5VQ-0e(|j7B7Az|I_RN1 zeIMgzNGHi)Be0sjhqVkAlSOgOXEkfN7>(gI=pM$?Ze2S{mSwOJ#5uZ~wG4IyEEo8$ zVJ+W4nhT(N1W&tg>KfAj%3x!Rkd?|>28$q#xbC->wR{te4(0YJo^~SK0qJBJtWh{4 z`%l&~Sh8H`w~n=30?UPDnWr1fG)Kx{ql9JcVl9J3;8Ya*uV*daLd7bE<&ivHk4>|X z{vv~odUY&?wG0+RBXO(K2G(+^E;`+U?%_OLXZCrsWUvur$w+1`gZ&tmZwGH=Etlyc zxB`|x=IL6@;vtQZ!N%w}eJ5+VRu}J4TT&U4z?v>c`;Hj5a1G`88gn4JOh#+V;MwjS ztnC_I45T)+re`8+y8@GGgdc?SEuOE|wQbP8UPf!{A>F>6wOx&|6@`}?`fp-w-^O6p zkcaX7Jzd)f?I|)^OM!IbHrDn%^jp}IDO^J~v$pT(VhFvH+TJ{0W%ijzWVCh!w&N&p z<{hD$p;zgmTP4J!wy>t}n$^CtJ5N{YG*%X8>|0s75~IS?P(F7nYa8M~sz4KDG_6pH zTR}Vx*DzKV*E%M#wvn)o)c#N3!rH!zM*1qmzsYC{>xrv=Jbg#kzJs`;^ETEtUTEiX zDF1FVYa8#=aTemGGFn>-aTMU&zI{)4Ll;FjdHp+AhJVLP+<@U~Mm?hu2A1(T`MPK)^-% zUk)dnZ}ap`qeTWm2`Yg&Rz}kjo+uCD z>Fc`oJ;b$`S#d=TGqbQ-D36R|ZDV3)KLK&NjHa=axai<%Oeu%Z8yS0mwT;f24&h( z2`ze7K^!NeX?!Z~_2%h3V^4x`4nEANCd6Rwd|30e;HauRncBj&mv{*um9@Oe>lhMXYxzmHE zFX`G8ywT4ySkpou4dIkUtYO4*9Vg-U^-jllH~@n@WGPQ01Z^~!%Xrv$+0MZWS<4r6 zZ6#DU49;W>W4FX$jo$Uj7zr$bFe?=~jx2^=ibUl=dLxLZ&ts1hvg8*D`->Zf$1GqC zpELSwRQqhk?lRrhyNVV@Qs+xx(ep0+O1B?p=;e9`dpfP?h{~QkkL939_&tfIwMj2% zDJgOuYx^wn1knj`_tg`O?G?Is^$x9piEIbYV+n0w9r9%_k868r^-k0eoXc82t!pvx zeBw#QHg=p%d`3%u3!^2h@Xv$i<=sy)bQ~&HIjvcWJb#|g(Zw+8LsEELOQCgI;T$%H zwSG$1R>SjgIgD-WOdR)w)@fJImarlaJ&Y*rdYYl*u`8mK)~7{b5YL~~HLAKvJgp@? zrp4{gX0f(U;0y=YFV8TRR~x5kuqMAHO2Ue+7%zw$!Dks7+irsqdbhc6b>;EnW(SkN z!&(9~yT|{*TE@l;Oyq@Y)H%lRT3w8y20CS?ghfdJTo`nof!ARSM7@2v)}6<*Fakq{ zZRBBXBV^-eu!ga%CI!;)3yfK8pbvlO78JtbdJSynF?=rRdXa%QpoHK(*vh+0aNL+o zU3oGOYsnAYw(+`z%`m+le~B@?(aejK7gAPqFGE&Wgyu3d&i#gB$|cIW%Xn-W8wL*fGbjpWz=g@q{_7+_XMSm&sJIVNTUA&KG zP~-<*W%#Wa*BRPBcr3A>hHZrKFM+3x`my+tlofub0gt)Hn8x|duTVu!`^o5oy69gG z&ntQrF!(lfT~zI>`_q zKk5S26z8x)#yAc~4?(9Y%0eGVtnV{3r?5*AgYTr52G=6)OX$5g@CJD*4{M2#CAg)m z=yU}SOetn8C*!aQ(tRaRM(@$ZyOgq;H*YXF&S>?afl%GME{XNsSf7P~Oa3<*d>2+j zPQkSqPAM(y>3CorPvd$rw>V^%(W&&#K#wh9Y~#Sw=jep+${Hlb@x&MO#O>a<7#_-v z;_CXMUc&Ff76$m2d4Cy=7t^1bK<9`u#yDQ; z2x>y^^{lr3OcD;N{hfeJb;7q zE#nlZM;L&Lyg(U^syG;NUEv&ko54|yM^nmc2j7!e$C+4aWXgKpVQ}0)*ON-r)G7(R z4O>S*XZhS^XtaMmS)fxZrL+jXh6k4Nv{Bp(I?L!Jgq0D%5w3_z#(0`8BB(Uiy?a+; zeXB7m5qJAlF*tgies|$oCS4eE*r1Ot>pzi$|hCYJcwhpQ%l}l(0RnYv3qHeVeeH1~^ zBG8FtQd;<*1)kxkV{mM%r>Y{(1;}^|(fZtmffYQitpI)J^-_uTjk*YTf&R9hF^)0m zx1e2L+>+2Z2}@P6dQbyHV?aEJUdh4|DJ>jnzzag044#G1G-`rKcgXnlc%smF!o~2} z$T@Tzcr7<2_A!qbQVR!hVOz^q2Gf^V&~-y@Na(d@nQ={UGc>k;e?!?isaQg^HU5*(+WraZub8hgujf`Y}!xyXwW6i0|wK_1nTyo?YzB*yn> zxVg?E6o-R&*)DA&MY(4*^E^)%6HyY-I$n?%UWhEeM5!9Ov;})0!p=$X1-kec`wMa0BflH1Y&7SuqJg6KRX5_Sgr85zqnMaD z89=Kt&G~B>OPmDnns`RSaN@j_jE}@FS zMJu<>d93;N!e|I@?zn`Ks!nLIa*r__+&t)1fD@}of1Zy z6PjDbrU*0>c;m;UtW7swifhWo7Bn_w{6y_USy+Y?$AZ?-dw9kEh8hRDENn{)8atnQ6@iXVSI}rGfEV_ntyj&{j!j$LE<%=Mm-A0h zDAVywZnvZsJoaS;=RlV3mnOAOE8i$|;#Baq7CiP=1ssFyBv#GIx0YpghW-d+Xw)ci z#D9AWz8bzAfb2rI15zGa26`3ZnLUmjEqH8la-=|3)N8+#e`vlSY@UDEU};YT zUlz7c%0Hm;L9d9zjTd`&$X+RpT_PiEKrc>iVI2EP7sGbV$URcpjmEeR^n7zkz_W+P ze%Se->%Q78rLi;TTPNr_yIR=BKHfQ?o!_NOX{RnGyFkzQvjvR}Lo-0TCYvJet;4{K z`&czK^?N#wZPUL%GQ)I=W0ABNQa{y9nkS45i6p0D%nadZWGjY4!ZpdvR!{}(;InFH6 zo;+&8IO2D`tO*=XHEJK8sf#dclqZjx7=h9x_W7ALk>gRLJ5p;=7jD*QPaZ8nIPvKn z&6a@U(V_%o;F;P{W{cv5`Ii%Hb^{!b-lX$s*r*$4 z_C}sOda-eof2TW}y%5KvH|u&9&p5v|dqYniy{J)s&UQ6>L5@dn8gh?W5r*67T;ohSS!b1dL#Ui?J#`EDDfbm>dl6`ASPW?<+NsiEqjr8mbPobMO` zg`B}NuAj{jP?K>Cx_&15TsL#%!_gRgb;&`)GSeKnHNhC~&2-a{?QDoSWaDTW_ho~g zZH^k7Xq@+*^@2u=r@NY?MUKXxGbj`E0&~pTMB}9L{O2@O&FO3oRXG|%-hd;Z7n>vI zCVCEfh-WlVJ{fEdlsOs$?trxWZn4sC4)~jJOcU%YpBno#g3W;!rrj3l0ddkogSnD#3ity7=2Wf8 zKBjm92OiNJYhOommc-e|RL!vs{IMW&s@TM1h8cwOCYa*w@-wG+9FM7@V>9^EA?8fA ziO2jlBi#ali}TFz5G8 z_+muE3n5u$H>cMejT!ztJW}?q8CPh65srAb6lH3j`4h(?C^4NXRpp3WuiA=O$cRcf z?wbBGBEaDYiA-z&UK?SCRGMrf6cd4VwnM{2E0n_V2+4ejkrA4jJTLRh&`ncQQ-j-F z`&&UYjz5dD0ax(U(I7Jt*_3~)P;QSxa;3Kw1LSzb5__D*Q|Cjk zd)bdi?Dae(=XzK%NRB^^o(gT2xc)|=7mwKO8`mMp>}-WVIX(xqliH2)p+#Q&8qD@6 zXZHtK@mG$g81QyHRTEL{#jnMTuo{x>jj>~nM-1A&5>GjQzTw5M)5TOLBrEM!?3&{d zK%e2lQ!8FZ|C{n3J8A5(kho@Afp?D2M)|M9Qz`zpyzFDeqvH-p>i_LhlwYhqjH<#@ z*&(G~d;-P*^k%9?S*sSDeJp;wdkarp>09Q-C*snXJV%n@|JEsqWT-5yv0JK@RjAHdnO$j+cV1y3HO< z?`C&^wcy2RID&c_?{0m_?^m0M%PL&2e#o2WI*Ej z-C8Q-_;g*2r*!6rReAB-G3TVbtr>4Eb8>td_8L~;sfv;2I&qW#JJ0|dl8>xaR*paT z(n|egbDg@0--)?fz2TX)$jk8u(7$CNmsUAyz5Gu>&c;EK6=E$=bNqgk^CUc#7F_4W z@4{&6AS6j0t@Uw^--p*ck6v?b-+C_v*cQ<@mkP&xyR|^iY1o50SPKQ^!yCL5q@s7A zGF&^++MB@f*dQ^Co~obb^x`3zMmbnMtW;5lVWn>_(z)Kx?Zxj!30x0JZeMF(2WKD2 z491-rYgY@$C%-HwH4`3s z@hBTc9jT((|Qu!YHfG|VJ>^Y~I z0*sD&W`eJsWNl97c#M?Z!&Ot5A(f4aZpLGDHmVGK;d|D`XpYB7EhPOhW@t8NjIkBJ zDgVz>!FEDJdN*rVH>V-qDF07sl6e%@J9)lA3~)n_qGY&YFf)`FK#X?7?>wV9ZpnxR z%@kmSJ)#62+zCIW;4#|nbM852*f!?^pM}j7T*OhiW(4fZJmiH^VVQ8hab&tADT#aBw)LI|DDn$U1*o zL~VKld2nkTe$OA6rAYn~T?^Eb@YuzHG|g+mF)!K_AZdSp9EIg9>2gPuvmX$zMY_I!W9(KuR~|t@mL|R!jYv6u-x`>vIdsQ zBA)|)VIp}R&lL=#@Kh6Df*hnbbH4w6rTvGx%|9M(mCd=NBL&x*G?ePLjJoA@eBXG>wfJYuJkuR$IjfStr33ePw3 z)hI`a`29fW4kcfW9Gq4SLxrEvN?DTvDE{O+aQa*iCwy2> zKIDI}S*hW+ZtJv`9xODj-f7Z+^{`RogKKT4O-c>rc(8~bOz6C)nGg4MTjvB)T{J2| zsR8w-OA_AM&4|6t6g)r~xryJCx@=VPx6t#H;<@A=` zEm3N~=x10SEe#m@bz1zt+dK29Dz5C`pHuhZ)Y$%BI=^&&@8#E7s{|Ft zXm`}aM2*@;#VLtH)Fc{byG^p%q)pULB8pQ~;zdPq07nD_1qW16QE>oA20>6p0YPNA zmn$Fw_q<)XRdtU-)v2}m{qffNoz=^Qd#us50gI*H ziP9Jrq)--Moqqu&MEHRt(R-CHe+B&b!y_;dKT2AySGuqTF2;DPU3m0^hp#RVPlJ6! zL1^36C!OL0M4``an^k&z?7KqO06dLONt0Uom#4Jcu5@5KJR$rG`X!`IQIEEKXSyH? zomZ8?2vVj=>+OmGSZn_*#cDT~t?Dd2KRXY%YyqC<2OO<}^F35rFjow~BiOLxaP;T^ z>G@xeKMQx2yUkaD=?K1fbq0!|moH}|PlwHEO>oM-=XI9|AfI!P_tkj}lIq%Gah zQ(8<_x&e#KJvWp}w*9DBY1xJpgOWFeBHwPVIJStVAk+F5!ExJuDrQ@@AzcY)D(yo4 zk6m1sl0}5&&@MmbSHfL@x^7yw0N3K@H^9P!AE=AjB1#wk4-de?jn7?IZp#)RA9yUx z02A3uyEIb1I0v0rnhnP@7Q(Wy;-i*rKw9d&3_2|2Ec239CKZ3iL(DGAf#dnhI=j+X z%Qkk3Lics0m3C9^k6x6Ci(ul%Trg3(uDdIhwrnC0LSTC(j5_~kX@ybgMS>`F{W%Xz zRBrF%O6M(`*al~HuNAZfKHZeNltqL)TDRrW_^f*eWT>5M*}_&)7#s$<304XpuI#>A z?FFn7c3)8dHf-S^+-2CU(Q#VFk>R#=7PQEn9$D$3%ENOPcoh_f@7n77?aNef>+p zLi|M69HnIoFz=ZJFIB*DU3t(zKh+hmR5viF94uU%B)x{AH;VOeD^IA0LC!DAT3zw%QGAyitYb>G34!4Rc zbKu<$C9E>w!kKfq9QyFNYs%ZQA7COq8CK`v#HBCx+NN5_gxFr34i@4kyQbYOTUZH| zm(G+!Sg!4@Jjz%^SoP|?wgC7u6D~2v;3Ep-VqmyJ*$#M*UKv6*#OjVRI9eSuUV4X1 z?@ai>>Yp&`hz-=XW0Q8PHgZK_;<4Y39v$U+cxrhRK4Oz74BcM|14;S{D&HynfVHPi zUnRlOs{KPiTwM8t1!1m37qfY_{#4^WHFA9J0dnBoYhbzzb z77?{vup~51TiYAX)XRExCY9M!h zoASgiWd2MQr7VLx)X$l4H09gQu2lgo8+MpR%)AKdvfY1G)_p8uxhVW~cNrYLIJ4t1 z=4h;<@KKZv)bR^CDQj02u?o)2f_OM;iyD8FX#s~AFKY=%bqCV}YEF@aQr%=P_(9YspH>3i!`sUP2kn^?&ZI ztY%t7SpDq%a~d2?nCB7398HNRj0vrT7Arz0Dyy;Iz)!gLjsK+vA4Tj{VF-`Qd9PT)v-$mN(d83YW94 zZvfLux@hv88*BS*JVTWq@L)zX)B?4Ps=>3db31KeT1gd!L6-bdyO6l-FUs$bdmu?I zgDn^Et7X1rkd!R-6Lhj@>NG#D8U|Dqv#@Kbtllr0ycWh(NxPU#)p7GXxqkK1@-E!r z2m2S4+D(r`rg@~xWPsA4E8m^Bft4F;hAHnI#frkPH8;SDE&e;$Qq1nlX~^u$uNT`*X_m1a zmA9{;gB{0O(u(cE_2vER3|N7ut|1%J!Aj1qsh(E(^jw6iYU-{Wu#&!EsPv4+^2;$% z7`r_WtP~%b*TXJbxh$G`%sW&JR`RxwwHdGyC3$y$37DyjT{=M7Pkv1_4Okjm2}euz zPy6kQ)vVq`!bLYHs=UZ+v5R_23kobdheT7K?_#Tp>}G5HiVtNoDWd7a74eess@U(~?Jc&M z17POc(^Z9bbYBRX)m=(!J|ZN*?z4jK7lN77-}aH#X;{74E1KR~6jMG6{_GPnnMOb*Pf zgq&@*CTyJ4Mf!!)M=TP}T_$Zzu!5z`z{wucswB(O7SS|f^)>i;3>F-p#QKi)lzwRR zf!U?mbBu576R>2vwt9rLz{;`&H~GH*JOP_w%I)UT7~k=p()7s(riW(FalSF7`4EjK z{@GW08f01W6HQ%b20eIYFSVN=pI-G*CuvIPBU(jsr;k>hew+ty76r}hO1~Pfb>4n~ z(DS`uZL~u~mc?(F)jmErlYTp>f74qdy7gReAm47!x0`L( zcg*i6{fH&)BsBM(zvH?s*KW!`u%M^(gvJ^j{9Mv;)G8cbnPWHK3tcwaQ+gDU9$Czu zqnCx=gQJz_SB>f@{ldaB zY%`6bY4G<^#SiS}tVo|pJ)|uotHDQ;d?K?RK&p%Sez5eD8QV@hTv}s%8XcptwuC^P1$tKcHiz+nY8Eoab9--+9bMp zjr)F25^S_Dgi~>C(-bf1$%Spr1_PS0CDE2;ceCC;_^$~)?gF$`bn7wUUk7i)OC4}; zv?Xqt(Id)`wF=4F)+Ae|y;XHa^!ho2I!f2=PO(+TL37qeXH-|(pMbrjtuuN=7_#?R zH20crNvO!MyOkyce?PI0w4k;}bn7$m```pfJXLmcMS^8|FX?xD_E?L-a}(B_F1>4a z%ZuCo&yRabOVS|_!XN*0dt4s8#08h==`|C)q^AP5wE{S+qn3rHr`p{f#O+uzrT-Iv zB1E_TQ=jNak<)ua!)`a{p#bRYKVH>-9;C+1(Zo9`c6Hxqt4 zF=q3!=+=9RPk4&$F5C&)m86^v4e!b{*dyKJ9CtfY7rkr3v88lyILZ zy_LxitGDr@Ti5YR_9T{P*&o^6t+_X2!*{G(GH=rGzOZMrOmy$kcle}vOV;fOkG+{| zJ#Tj_Pu#O)eAh~Y{f5)*Ic)yMqv^IhdzQUJS@!J<(fb3|F8gNol+lCV_Btaz7dv>p zJ$Uq#+21T%8?Zn6!tLy`Q}$N2^rIW+5A#%}q^vQV5L(C&kY%H<&*H`N=TDnDe)Q;3j)ACO<6EdZe5|WG{C_{k!2cLEdi40I)8@}#yx7Ng zqb2A-WUJHhNoiTd)=22Yy%_&*yuFk;I;#^$pc5k(Y>2!G%K|?32z#rFn~@t9jPz0- zHCTKo#PHxRe%cwE3AcM6`*C|~TUP9@pS~Cj69p&BvanZd-DTM96~S@$to2|a*52BB zFD`iH?7wzNHfUjo*v50<)Ww#_q(U%o(cZf7`Vs%dQwMq~uXD1x0PiMtYyH>h-&?{H za%}7D*X?a=ISJvGAEy7c+eL$Yus}?-?KI%y`9B3jB;2p`x2M_LR^CsD2>5CK#{)Vk zFV?X-Vi9f9b@Ab6->lpg7N1hG%bsIzTapqVwr%A%pMChY@?IC)%pZa`1 zYJR4iN8o!J-JZ$U7wCr>&<~~bpBvDBPU*ilpkGYsHyO}xqV!<~^r4hK(SZIMr7tp| zFLLQ|V`t^}I;|713%2GqD#R&*hO+@QyiPu2pr30%KbO*PFrZ&g>7xzkqbPm40e!kl zkLx^dF;WU_YxJm7h!}!~umSWzt?VJtFEpV4JEadWpx;XAFB;Hap!81+=$|P1lLWnU zo8h1!5%sT;#|!%T2K4hO{Wb&ot&~2|fc~mWk83@ZpPRL=0cq=NjY6CtXdD|r8$K~Zr*q25!n0O8Fn8#XIjSyn!*OqlxmlLs{#F1N`KdY{;o@p9q%fy8)%IH-gSM) zp%Bpo{m2H;zijddK!4tV{=7?%>$;^eF3kP#ZOb}^h$853Yyd5=$~^-8F$4N&mmb@@ zD0}O*d<6UJyVw=tDBL~R0GeGT_XzYy4Cs%z^!RBv*(2o^zk3gSdK%cDWBM2J#;u>u8ZoyY7 zMwH9;LBGp@ewRy+%R4I-Bbu9b9+3*vlq0(^EtOjUeWC$xk|ge4*x(64^fJ;|%EITzcFz{KX#|Xw%NW z!#0oM4uuFID1r^3$VYOILI`dgq!fo~ZrgbT3IPpjREUEFg|Pt?RxG!0kl=X0!k$kowsbA_&eK8$gk{atlEOJ0>fy z(lz&w^9U44n&eQ3-2`W}_(dg}hTU(T={a&6y9ioc!Waa6PHt7Us>e0&mok9Uus^Rx zA$AgUmJOh@59Ahh5cKyKe`cjS@BBM_ns5GnRgcShmoa(-g_wGmDnuY;88(0-vg8)F z6ZGX1Mg#Dn!!I*cJuc~0&H!L1Wv>#2*hWw!8$c2FLElv~8n`5)?^0Aft{wH_dkxKl74{8}tW}6j1n2g8%4h;A zNA=82mfQH1;25RgW8IDeshOwE(-XXErLt zMuMy1>yuhq=W!^GIkftg+{OlibJS9tS{xLd>Xm&{Ce{;twR1hA4cM37`RWas_=TVY zYycg&F6-Ap?o(cY)#}o9$bCtw9yiWyVD#vQi03vc#9D%DKT!&>YBd2hsy?nwl-pPX zo*nt(dp*s$TM$uI*JNTf!6olH7`=iT1n-tym5Eivl=T;pLEM4b6PZ_J!j}kldNKg0 zQ`O_nWf@orC$CHMj?+h=G((rG39^0#JV>L?^b1n7)cN;RIM?Sd$$$^RXF6@BUZ`rW zhuT%0&PcxiNq>BZKu&r(Ukq+8bsm9o6`m(Ar~n~2+S6GxS&M)IRi4M=Rl-5=iB8ha z4CSS^<{VVi=yc+|N;DDpZpAiT0!nyvjyWu?R@ASTMOeJh^;~VCDV_U0Ng^+BsqgBER zX=E1tvSxFxNW`&_!jV3H6X zrV^##UvK)g+vXh9pYqNQRf!TJJX_vTDi5XIE!cI~arv`DDpL$os=m%Q)0^9O9;t#7 zBj*mPL=nN23+UI2n{!YwY(eD#m3T;SWKU^RrFOTVf>h7QeJW7^w+|?crbj?wv@c8c zsze^N0h<@~2q+QNJ2F@$atVICK-zMu-K$zCMDaLCC9(-V`yTz$ck_`$8L0Qp?ox>d zM5tNi8LQjKbEw;~s%EFkWD%VHDgB~)^H!iR->2z;Dv?P9$FctBbX$SF#Ql#swyR7A z^z4_e^n3Nqn}I#dU4N+wP?@_#DE_A7McsCUj^7k-QHfN7Z%@9Y13=ZLN$9q}3M9jg zWR$eOTkE92zJrlxiA4qOK!%8tzlhd+1kw@M0x&qLiBQZfB0N7dP}<3^)yi7fbbsi1 z6UE#hLPOXfY5%+y^9yWB3~gwjm?U5h!;hfa3{*=Rez>8YVy?k>Vn2~;1!%S7Ckmg$ zHq}wgWg;Za^GMNQEU?3OZqieVxkQA56+P)4F)w=$>MHbHQBX}W7l>frJ6tL=q}4a5 z=P-OPEXupi&p9F_ehsU(+RS#?)BSZ~1;xY>p?vp9dI!Udqh*OZ_ht}L{Z0y>FJSKUFfY_FH^9Kw8uAb z^@S7_MNIbSzjdScjlE34rqOO+ovzELs7PX}J-6&#dehp=RG4UbciGw69Eu7jW=Hzg zDV<*~K2D#tNYUK+(=F+a`xF;K%r)m%jFi8~{&GW3I$AV$9N}}mCWGSk5pz|X@3`Np z3(;-Re?JKugU0&CRi#o~5VV&RIR71KqXVs_|9(a^zcW8DspdAt1(Mcf7q|U=um`;b z^yTit3;P~}|Gw>FSrSEVAwpgHQPcW)BfMO@SYIZuz#Hl#=B+=PUU!*7EyO|EI36X2 zZ(97>u&x_5JA6xQ({fZsn{zM!)Q_Y3!nyceH}8kFfnjH^+`OObs6@}C3M(zYUXw@AGe>3ny!$t=oCynD`@_8P?|QhO{Oxym z<%~3;*8gYF%;iC`88sUT z^_1RiE>mbAYW@0%AHuR434mJNk0)-8Sz$kncs+?CgOIu1``<+tN*}7w0?!b0QRH{; zx4TVo`;hsSu^X>9NTuSnxRb=(aKmrxE2$J0g3P}g^=oRAO^1sn=BCtNN4aNETo^LF zwIse?s@1I392A?Vk6+R|i=rZtsh#(>LaFwz78OQJh1ZNK?dx*)pcT}rK6m=R2 zf8Janl^fN9b`zmu^Pfv8CJqU&{UcQ>*Qv$qAVTUtUVB0@=a4WkgsbC(I*Lg`f_X-|R6tRS*#P62VXmi`8%S`QnIn}%)M9=i zLhekr28y`_%2G(p9uDR{feWFg!$UMk6_jX+_)3q1o>;sFwVx1m9*$)}Ax zhdN0c+;^%>Hh4H+DzT@HK%u_;F}qYE7va|QIN)#ddYJ^n&PMd%tdJ(T4 zP>EuMy-TFxciKc16jSp)s1hYeXl-#E(M2jDzWm!9QkhbOyWEk=-)Xl3MfC1;2~~;5 z2+w#bmA}&_poren8DT2%1mSkUQu#Y=0*dGbw>zv7m`6Hr7i*)u{VDiHp} zCKbQaCQ1mleG;h>RgfW8Izn`jN8qiM!cmp6BHZD)RQ^u86)2*2qC=EQ*bwfVxnGwk zBsjBkv`SPXJhW0On5W$al-;WwdQ2tk2+w}LSC@dYd(Y<_SBV;g1wUyzr`-mW-SZPp zszfcqZ6c(CdD;Y&-HT`wqY`xp|F28Cb%`8;FTHX~C7vPtX1Y{jPrD5$)R+F|X_cr) z*sD+~u%}HtAh^)$j7mI5_%G!* zG+reHG_g^t_@~{(T>vIrPyzf={bpT#8Vuqi>4`x4vjU+}s9vfNsNDdR8LaodBm*lE z9#Yrx1BhZF1^hZBLDu^sp`Wv2o$}AkVJJ&z?RQxQRv}ZjGG{$OZQ>5WW!*8 z6go7XiWD2SAq3yJDidoUg`_&`4=VrOyjQmfPVI0_Ce|X{`dlNUjhk?DZkH$%>ku|Y zJL?r{w{ZjN4V#iw0{!xw(ZqH5Vu7On1)+J);)dD{B*9m{uFLkOxh1Gs+34Vl=0 za3||CMwb%d>~+2=6C1&^na=8o+MT)v$+p8Snec-&c%qik##Kn@!flm6D{2_P75Gw? zqW=|cOAa=G9iQBhTi67*KC82yqxL8+!?-&o%Y+4PugP{srxM_H*)ByTP_VQ7qjnRQ z2->6Q{So@o#%SOoK{Hcj`@sthVr(M!yVhhCh9oDsa0mjh!u1X;PDn_5;3EHga zw?a~OumSAwPM2E>&^cj36BQ2L~h{_LgTU-0DPrxETz9{Kz~Kihaxoa z0mD9gbMF08xj&$fGN6xA^kE2lG_e8Pt8FE zTZlkt(LF|w;45{DD1E8{eX635MCi}Xa?9H148BqKmkPN*pbs#h4^Z?+5N-vZSZaQ) z&`02-b``BE`cOrG6r!$%4d9ygRdSDxBJ}xPMvvedc%M`H zXaoA=iarW@1fP7OJ<|N|2jI&dzqiUgibCkqGzI|Q%$q{#BMj&x6n!+pZJ)9MT+`Mj z_b3|fYtHi9+HnNm*z>0J0S5E|ivAen-vTy(i*U8vqhkp5Nn!X1zT(%1(l0fjUrOmO z7|>r(^v4nY-7_|T>)q}02#&+*L^5Lp@RdMsO8=_?{U%DEVnCm&=uaU0N)sEvO@DYQ zkKhE{1Me_K0AC>-N9i{h&~K#lHw@@+DEgD|oZ7$!a6`Kqc?2inF6%50uN~L$mBWFQ zez5`lVoD!rK!1eN7aGtPEBY9O{&z`csg(8rcADYFj6d_!L5K-DG$Oz8Tq@(oZ#@pGN6d8PKn$^hXWoqbPlv0ezaH zKMk3*h7I6a{7m-HX@uUm!SE1#^YXtb{U8JSL6m-`0sSmWzubU+Ii=riKp#Zu;|%EI zD1D{@eHNv!F`%ze^k)!qzs~SKd_&Z|UOr!-_b{OMp!Dw;(7#LRKQf^In9|QNpr1?W zzc-*?Lh08T(66KPfd=$}ls?RWK8(_zF`$p7^a%#^36%bh0ev#1&oH3BN9hX;=nE)) zxdD9zrMDW;TmKEc-GKdCmwo4FT(s}-frM+N-^jQhoDXK)58My*BRY=<9uGVoXbw0Ui&uH+X*F@xbGO_5hCu z+8aDS@Oa?yKzo441MLl-A9y_Qc%VJNrCNKzoDd2ObYR9%v8nc%Z$( z^8=3u9uKq!cs$VF;Q4{a1CIyV13VsRZ}9xUw0Ui&uH+X*F@xbGO_5hCu+8aDS z@Oa?yKzo441MLl-A9y_Qc%VJNrCNKzoDd2ObYR9%v8nc%Z$(^8=3u z9uKq!cs$VF;Q4{a1CIyV13VsRZ}9xUUwZ zeKMs_FrZJM^k)p{V<~-@0ev{74>X_;r1a|y=+{yD9}MW1Q2IFr^m8fwM+WpCQ~Gxd z=-;LE9tQLtl-}Kd-d)k3MyTcn`T~8X0eu#wk29c;qx8EC=z}Qzas&G1 zlzx^0{cK7<*ns{6O8=$-{hKa5Hr3x`oPb>f*P9gL6hi520HxQ-XAJaF2J}&sew6|J zYDz!VfPNaKf6IWrw@Z)PHr`@*Xg9%4Z51L0q5EtAJ*bsE1p06T`ooldi2?mmO8=n& z{YXmxM+5plD*BTM72jq!D2Sj(HS%~tf5d?P2&MnQfPN{ZA8bHB#HGjW8tyO#5KM4` zyF#2m=mr}=H=fEP2K`0@dOu1(&VYWLOOIb^N@k2;55(vn6yi8SDQp0x+T{^|{#OHf z3#A`rK=19+WB2+L#t8O89&%TRV+dVf1L#7vJOa=!HK1Qg>H8Sa_i^blE=*;NU?0K7 zSRtYj3Sa|hn@t`8=)Dc-yxM@eP2Z%fl%r_Mu!d&lvXa=2mK-g`b921 zeyu)}(Lg9%`>!d)VT2B`0d(kz+#}ErG@u`(=)+--lf~#z7(wY}vVG7mG@$>=rN&M_;* zL4;!uI~5SqgV(Ci|)g#*w+c0QwpD1x)y6k_ccY8$e?p$}Q|g=<-8G1IGxuT%hW4Uq>MWI8LymuR`oW zD1;55L-}$Gdk}uJu87gX34-h1RES`NRCVD7-9FKVnz!m2`>4)LIlBiV*_Y< zuG~Tp!uZl7Mhh_nCt!uxjc~7q5(aS!=FM;A$ZhOGXagHS8?#kCcF!+mv~Zf>hwcio z6QMC|0F8Mdx3B}D!;cvaoFVAQeN~TNuPkE#u>@ECZ-oeiEW-xSj4Zi@?Fhv@VKfj& zP)w$($8T868NgYBt#2sAHiV|L0W{;D+(H0Ckrj*v&Vi3IR6TyRq>=%ghgkhjh1iO) zx1)+d#KRRjI$dsK3qqUO0NQ$2)#EnFRz?dKAS<;|h|LIhd1_-27hz`Tl_t00kI+mu zfM%u2dJ95(su>MjB4|&Fs>kh8>&tF zNd|ln{^DYu1jQsXGfn^i3T_q)q%Q17M!~waVqf~ z;kV0o=o0q{E`K{#CF&9OD%z<_JRrEJ>lu}J1{1XOUAjaz!5MF!R*5=&Q`bcrH@PjrY48(GQ}Q+(=R`>j1t)NF9SLDsTrf#I)?II>c&X&YmV9in)b^ zR&(+ab(pn8$eYuuiDGUb!EILVbsgpxBIM3?YoM4UV7|<_sl#j_LdKVF^%Qdr#xwrf zZ5_ss2-n67brf?M2?LKdrRXpgA~Zz}d`dBwkkE0HElr2nOa$Aej@1-%0SUkVDm`6? z*-C`;uV7K$b$-qvVbCF1wbhG?P|Rs0{CRUl zjt;Y%2o;bJDJ}?^e>ZUP zk>YwC?hG*(A6Y!`ceg1n5Vd}N#1DtFn}`k=Pu!Za5C1UY^(2bif`qocKQpa9npU?8 z*p?^tS4gY6w4)rM;g<6mUwZZ>Ac;@n;vzfIUHR)TVO>O4cpye|M|2IDW0$+pL zyLTEee!-G;f#I=NZr;yzRH6oHTGw(n@(5~hRJPB%fAdOgc;LDv3&s!V zbg60m_oB|f(G1tM>_zSu?ibgstYu&D>xld1We@PY@v=vpR|)qE_lx8K9tS)QcpUif zdAsZZo)36F;Q4^(1H}V8AMkv@^MP_T&?^l5IjZF=gZqX1#pMBh|8V(&&K}$^+%J*` UcpUIJ;Bmm`SBeMz=jDU{3zSIEGynhq diff --git a/resources/tray_icon.psd b/resources/tray_icon.psd deleted file mode 100644 index 1c903de307b0f5adadaf41841cafe547a89ec082..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24948 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