diff --git a/Kit/types.swift b/Kit/types.swift index 4d5399d7..38166f1a 100644 --- a/Kit/types.swift +++ b/Kit/types.swift @@ -254,25 +254,27 @@ public var isARM: Bool { } public let notificationLevels: [KeyValue_t] = [ - KeyValue_t(key: "Disabled", value: "Disabled"), - KeyValue_t(key: "10%", value: "10%"), - KeyValue_t(key: "15%", value: "15%"), - KeyValue_t(key: "20%", value: "20%"), - KeyValue_t(key: "25%", value: "25%"), - KeyValue_t(key: "30%", value: "30%"), - KeyValue_t(key: "40%", value: "40%"), - KeyValue_t(key: "50%", value: "50%"), - KeyValue_t(key: "55%", value: "55%"), - KeyValue_t(key: "60%", value: "60%"), - KeyValue_t(key: "65%", value: "65%"), - KeyValue_t(key: "70%", value: "70%"), - KeyValue_t(key: "75%", value: "75%"), - KeyValue_t(key: "80%", value: "80%"), - KeyValue_t(key: "85%", value: "85%"), - KeyValue_t(key: "90%", value: "90%"), - KeyValue_t(key: "95%", value: "95%"), - KeyValue_t(key: "97%", value: "97%"), - KeyValue_t(key: "100%", value: "100%") + KeyValue_t(key: "", value: "Disabled"), + KeyValue_t(key: "0.1", value: "10%"), + KeyValue_t(key: "0.15", value: "15%"), + KeyValue_t(key: "0.2", value: "20%"), + KeyValue_t(key: "0.25", value: "25%"), + KeyValue_t(key: "0.3", value: "30%"), + KeyValue_t(key: "0.35", value: "35%"), + KeyValue_t(key: "0.4", value: "40%"), + KeyValue_t(key: "0.45", value: "45%"), + KeyValue_t(key: "0.5", value: "50%"), + KeyValue_t(key: "0.55", value: "55%"), + KeyValue_t(key: "0.6", value: "60%"), + KeyValue_t(key: "0.65", value: "65%"), + KeyValue_t(key: "0.7", value: "70%"), + KeyValue_t(key: "0.75", value: "75%"), + KeyValue_t(key: "0.8", value: "80%"), + KeyValue_t(key: "0.85", value: "85%"), + KeyValue_t(key: "0.9", value: "90%"), + KeyValue_t(key: "0.95", value: "95%"), + KeyValue_t(key: "0.97", value: "97%"), + KeyValue_t(key: "1.0", value: "100%") ] public struct Scale: KeyValue_p, Equatable { diff --git a/Modules/CPU/main.swift b/Modules/CPU/main.swift index 86544f60..9015c464 100644 --- a/Modules/CPU/main.swift +++ b/Modules/CPU/main.swift @@ -32,10 +32,18 @@ public struct CPU_Limit: Codable { var speed: Int = 0 } +public struct CPU_Frequency: Codable { + var ECores: Int? = nil + var PCores: Int? = nil + var power: Int? = nil + var cores: [Int] = [] +} + public class CPU: Module { private let popupView: Popup private let settingsView: Settings private let portalView: Portal + private let notificationsView: Notifications private var loadReader: LoadReader? = nil private var processReader: ProcessReader? = nil @@ -43,9 +51,7 @@ public class CPU: Module { private var frequencyReader: FrequencyReader? = nil private var limitReader: LimitReader? = nil private var averageReader: AverageReader? = nil - - private var notificationLevelState: Bool = false - private var notificationID: String? = nil + private var powermetricsReader: PowermetricsReader? = nil private var usagePerCoreState: Bool { Store.shared.bool(key: "\(self.config.name)_usagePerCore", defaultValue: false) @@ -56,9 +62,6 @@ public class CPU: Module { private var groupByClustersState: Bool { Store.shared.bool(key: "\(self.config.name)_clustersGroup", defaultValue: false) } - private var notificationLevel: String { - Store.shared.string(key: "\(self.config.name)_notificationLevel", defaultValue: "Disabled") - } private var systemColor: NSColor { let color = Color.secondRed let key = Store.shared.string(key: "\(self.config.name)_systemColor", defaultValue: color.key) @@ -97,11 +100,13 @@ public class CPU: Module { self.settingsView = Settings("CPU") self.popupView = Popup("CPU") self.portalView = Portal("CPU") + self.notificationsView = Notifications(.CPU) super.init( popup: self.popupView, settings: self.settingsView, - portal: self.portalView + portal: self.portalView, + notifications: self.notificationsView ) guard self.available else { return } @@ -109,6 +114,7 @@ public class CPU: Module { self.processReader = ProcessReader(.CPU) self.averageReader = AverageReader(.CPU, popup: true) self.temperatureReader = TemperatureReader(.CPU, popup: true) + self.powermetricsReader = PowermetricsReader(.CPU, popup: true) #if arch(x86_64) self.limitReader = LimitReader(.CPU, popup: true) @@ -189,6 +195,9 @@ public class CPU: Module { if let reader = self.averageReader { self.addReader(reader) } + if let reader = self.powermetricsReader { + self.addReader(reader) + } } private func loadCallback(_ raw: CPU_Load?) { @@ -196,7 +205,7 @@ public class CPU: Module { self.popupView.loadCallback(value) self.portalView.loadCallback(value) - self.checkNotificationLevel(value.totalUsage) + self.notificationsView.loadCallback(value) self.menuBar.widgets.filter{ $0.isActive }.forEach { (w: Widget) in switch w.item { @@ -246,20 +255,4 @@ public class CPU: Module { } } } - - private func checkNotificationLevel(_ value: Double) { - guard self.notificationLevel != "Disabled", let level = Double(self.notificationLevel) else { return } - - if let id = self.notificationID, value < level && self.notificationLevelState { - removeNotification(id) - self.notificationID = nil - self.notificationLevelState = false - } else if value >= level && !self.notificationLevelState { - self.notificationID = showNotification( - title: localizedString("CPU usage threshold"), - subtitle: localizedString("CPU usage is", "\(Int((value)*100))%") - ) - self.notificationLevelState = true - } - } } diff --git a/Modules/CPU/notifications.swift b/Modules/CPU/notifications.swift new file mode 100644 index 00000000..3780b0d1 --- /dev/null +++ b/Modules/CPU/notifications.swift @@ -0,0 +1,145 @@ +// +// notifications.swift +// CPU +// +// Created by Serhiy Mytrovtsiy on 04/12/2023 +// Using Swift 5.0 +// Running on macOS 14.1 +// +// Copyright © 2023 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa +import Kit + +class Notifications: NotificationsWrapper { + private let totalLoadID: String = "totalUsage" + private let systemLoadID: String = "systemUsage" + private let userLoadID: String = "userUsage" + private let eCoresLoadID: String = "eCoresUsage" + private let pCoresLoadID: String = "pCoresUsage" + + private var totalLoadLevel: String = "" + private var systemLoadLevel: String = "" + private var userLoadLevel: String = "" + private var eCoresLoadLevel: String = "" + private var pCoresLoadLevel: String = "" + + public init(_ module: ModuleType) { + super.init(module, [self.totalLoadID, self.systemLoadID, self.userLoadID, self.eCoresLoadID, self.pCoresLoadID]) + + if Store.shared.exist(key: "\(self.module)_notificationLevel") { + let value = Store.shared.string(key: "\(self.module)_notificationLevel", defaultValue: self.totalLoadLevel) + Store.shared.set(key: "\(self.module)_notifications_totalLoad", value: value) + Store.shared.remove("\(self.module)_notificationLevel") + } + + self.totalLoadLevel = Store.shared.string(key: "\(self.module)_notifications_totalLoad", defaultValue: self.totalLoadLevel) + self.systemLoadLevel = Store.shared.string(key: "\(self.module)_notifications_systemLoad", defaultValue: self.systemLoadLevel) + self.userLoadLevel = Store.shared.string(key: "\(self.module)_notifications_userLoad", defaultValue: self.userLoadLevel) + self.eCoresLoadLevel = Store.shared.string(key: "\(self.module)_notifications_eCoresLoad", defaultValue: self.eCoresLoadLevel) + self.pCoresLoadLevel = Store.shared.string(key: "\(self.module)_notifications_pCoresLoad", defaultValue: self.pCoresLoadLevel) + + self.addArrangedSubview(selectSettingsRow( + title: localizedString("Total load"), + action: #selector(self.changeTotalLoad), + items: notificationLevels, + selected: self.totalLoadLevel + )) + + self.addArrangedSubview(selectSettingsRow( + title: localizedString("System load"), + action: #selector(self.changeSystemLoad), + items: notificationLevels, + selected: self.systemLoadLevel + )) + + self.addArrangedSubview(selectSettingsRow( + title: localizedString("User load"), + action: #selector(self.changeUserLoad), + items: notificationLevels, + selected: self.userLoadLevel + )) + + #if arch(arm64) + self.addArrangedSubview(selectSettingsRow( + title: localizedString("Efficiency cores load"), + action: #selector(self.changeECoresLoad), + items: notificationLevels, + selected: self.eCoresLoadLevel + )) + + self.addArrangedSubview(selectSettingsRow( + title: localizedString("Performance cores load"), + action: #selector(self.changePCoresLoad), + items: notificationLevels, + selected: self.pCoresLoadLevel + )) + #endif + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + internal func loadCallback(_ value: CPU_Load) { + let title = localizedString("CPU usage threshold") + + if let threshold = Double(self.totalLoadLevel) { + let subtitle = localizedString("Total usage is", "\(Int((value.totalUsage)*100))%") + self.checkDouble(id: self.totalLoadID, value: value.totalUsage, threshold: threshold, title: title, subtitle: subtitle) + } + + if let threshold = Double(self.systemLoadLevel) { + let subtitle = localizedString("System usage is", "\(Int((value.systemLoad)*100))%") + self.checkDouble(id: self.systemLoadID, value: value.systemLoad, threshold: threshold, title: title, subtitle: subtitle) + } + + if let threshold = Double(self.userLoadLevel) { + let subtitle = localizedString("User usage is", "\(Int((value.systemLoad)*100))%") + self.checkDouble(id: self.userLoadID, value: value.userLoad, threshold: threshold, title: title, subtitle: subtitle) + } + + if let threshold = Double(self.eCoresLoadLevel), let usage = value.usageECores { + let subtitle = localizedString("Efficiency cores usage is", "\(Int((value.systemLoad)*100))%") + self.checkDouble(id: self.eCoresLoadID, value: usage, threshold: threshold, title: title, subtitle: subtitle) + } + + if let threshold = Double(self.pCoresLoadLevel), let usage = value.usagePCores { + let subtitle = localizedString("Performance cores usage is", "\(Int((value.systemLoad)*100))%") + self.checkDouble(id: self.pCoresLoadID, value: usage, threshold: threshold, title: title, subtitle: subtitle) + } + } + + // MARK: - change helpers + + @objc private func changeTotalLoad(_ sender: NSMenuItem) { + guard let key = sender.representedObject as? String else { return } + self.totalLoadLevel = key.isEmpty ? "" : "\(Double(key) ?? 0)" + Store.shared.set(key: "\(self.module)_notifications_totalLoad", value: self.totalLoadLevel) + } + + @objc private func changeSystemLoad(_ sender: NSMenuItem) { + guard let key = sender.representedObject as? String else { return } + self.systemLoadLevel = key.isEmpty ? "" : "\(Double(key) ?? 0)" + Store.shared.set(key: "\(self.module)_notifications_systemLoad", value: self.systemLoadLevel) + } + + @objc private func changeUserLoad(_ sender: NSMenuItem) { + guard let key = sender.representedObject as? String else { return } + self.userLoadLevel = key.isEmpty ? "" : "\(Double(key) ?? 0)" + Store.shared.set(key: "\(self.module)_notifications_userLoad", value: self.userLoadLevel) + } + + @objc private func changeECoresLoad(_ sender: NSMenuItem) { + guard let key = sender.representedObject as? String else { return } + self.eCoresLoadLevel = key.isEmpty ? "" : "\(Double(key) ?? 0)" + Store.shared.set(key: "\(self.module)_notifications_eCoresLoad", value: self.eCoresLoadLevel) + } + + @objc private func changePCoresLoad(_ sender: NSMenuItem) { + guard let key = sender.representedObject as? String else { return } + self.pCoresLoadLevel = key.isEmpty ? "" : "\(Double(key) ?? 0)" + Store.shared.set(key: "\(self.module)_notifications_pCoresLoad", value: self.pCoresLoadLevel) + } +} diff --git a/Modules/CPU/settings.swift b/Modules/CPU/settings.swift index 4e4b33d6..009dd04b 100644 --- a/Modules/CPU/settings.swift +++ b/Modules/CPU/settings.swift @@ -20,7 +20,6 @@ internal class Settings: NSStackView, Settings_v { private var updateIntervalValue: Int = 1 private var updateTopIntervalValue: Int = 1 private var numberOfProcesses: Int = 8 - private var notificationLevel: String = "Disabled" private var clustersGroupState: Bool = false private let title: String @@ -46,7 +45,6 @@ internal class Settings: NSStackView, Settings_v { self.updateIntervalValue = Store.shared.int(key: "\(self.title)_updateInterval", defaultValue: self.updateIntervalValue) self.updateTopIntervalValue = Store.shared.int(key: "\(self.title)_updateTopInterval", defaultValue: self.updateTopIntervalValue) self.numberOfProcesses = Store.shared.int(key: "\(self.title)_processes", defaultValue: self.numberOfProcesses) - self.notificationLevel = Store.shared.string(key: "\(self.title)_notificationLevel", defaultValue: self.notificationLevel) if !self.usagePerCoreState { self.hyperthreadState = false } @@ -153,13 +151,6 @@ internal class Settings: NSStackView, Settings_v { items: NumbersOfProcesses.map{ "\($0)" }, selected: "\(self.numberOfProcesses)" )) - - self.addArrangedSubview(selectSettingsRow( - title: localizedString("Notification level"), - action: #selector(changeNotificationLevel), - items: notificationLevels, - selected: self.notificationLevel == "disabled" ? self.notificationLevel : "\(Int((Double(self.notificationLevel) ?? 0)*100))%" - )) } @objc private func changeUpdateInterval(_ sender: NSMenuItem) { @@ -257,16 +248,6 @@ internal class Settings: NSStackView, Settings_v { self.callback() } - @objc func changeNotificationLevel(_ sender: NSMenuItem) { - guard let key = sender.representedObject as? String else { return } - - if key == "Disabled" { - Store.shared.set(key: "\(self.title)_notificationLevel", value: key) - } else if let value = Double(key.replacingOccurrences(of: "%", with: "")) { - Store.shared.set(key: "\(self.title)_notificationLevel", value: "\(value/100)") - } - } - @objc func toggleClustersGroup(_ sender: NSControl) { var state: NSControl.StateValue? = nil if #available(OSX 10.15, *) { diff --git a/Stats.xcodeproj/project.pbxproj b/Stats.xcodeproj/project.pbxproj index fd74d1c6..4cb9c615 100644 --- a/Stats.xcodeproj/project.pbxproj +++ b/Stats.xcodeproj/project.pbxproj @@ -25,6 +25,12 @@ 5C23BC1029A3B5AE00DBA990 /* portal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C23BC0F29A3B5AE00DBA990 /* portal.swift */; }; 5C5647F82A3F6B100098FFE9 /* Telemetry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5647F72A3F6B100098FFE9 /* Telemetry.swift */; }; 5C8E001029269C7F0027C75A /* protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE493829265055000F2856 /* protocol.swift */; }; + 5CF2210D2B1E7EAF006C583F /* notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF2210C2B1E7EAF006C583F /* notifications.swift */; }; + 5CF221132B1E8078006C583F /* notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF221122B1E8078006C583F /* notifications.swift */; }; + 5CF221152B1F4792006C583F /* notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF221142B1F4792006C583F /* notifications.swift */; }; + 5CF221172B1F4ACB006C583F /* notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF221162B1F4ACB006C583F /* notifications.swift */; }; + 5CF221192B1F8B90006C583F /* notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF221182B1F8B90006C583F /* notifications.swift */; }; + 5CF2211B2B1F8CEF006C583F /* notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF2211A2B1F8CEF006C583F /* notifications.swift */; }; 5CFE492A29264DF1000F2856 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE492929264DF1000F2856 /* main.swift */; }; 5CFE493929265055000F2856 /* protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE493829265055000F2856 /* protocol.swift */; }; 5CFE493D2926513E000F2856 /* eu.exelban.Stats.SMC.Helper in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5CFE492729264DF1000F2856 /* eu.exelban.Stats.SMC.Helper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; @@ -394,6 +400,12 @@ 5C23BC0F29A3B5AE00DBA990 /* portal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = portal.swift; sourceTree = ""; }; 5C5647F72A3F6B100098FFE9 /* Telemetry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Telemetry.swift; sourceTree = ""; }; 5C9F90A02A76B30500D41748 /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/Localizable.strings; sourceTree = ""; }; + 5CF2210C2B1E7EAF006C583F /* notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = notifications.swift; sourceTree = ""; }; + 5CF221122B1E8078006C583F /* notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = notifications.swift; sourceTree = ""; }; + 5CF221142B1F4792006C583F /* notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = notifications.swift; sourceTree = ""; }; + 5CF221162B1F4ACB006C583F /* notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = notifications.swift; sourceTree = ""; }; + 5CF221182B1F8B90006C583F /* notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = notifications.swift; sourceTree = ""; }; + 5CF2211A2B1F8CEF006C583F /* notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = notifications.swift; sourceTree = ""; }; 5CFE492729264DF1000F2856 /* eu.exelban.Stats.SMC.Helper */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = eu.exelban.Stats.SMC.Helper; sourceTree = BUILT_PRODUCTS_DIR; }; 5CFE492929264DF1000F2856 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 5CFE493829265055000F2856 /* protocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = protocol.swift; sourceTree = ""; }; @@ -812,6 +824,7 @@ 9A2847772666AA5000EC1F6D /* reader.swift */, 9A2847752666AA5000EC1F6D /* settings.swift */, 5C23BC0329A014AC00DBA990 /* portal.swift */, + 5CF2210C2B1E7EAF006C583F /* notifications.swift */, ); path = module; sourceTree = ""; @@ -884,6 +897,7 @@ 9AA6425F244B274200416A33 /* popup.swift */, 5C23BC0729A03D1200DBA990 /* portal.swift */, 9A953A1324B9D22D0038EF4B /* settings.swift */, + 5CF221162B1F4ACB006C583F /* notifications.swift */, 9A81C7592449A41400825D92 /* Info.plist */, 9AF9EE192464A7B3005D2270 /* config.plist */, ); @@ -898,6 +912,7 @@ 9A53EBFA24EB041E00648841 /* popup.swift */, 5C23BC0929A0EDA300DBA990 /* portal.swift */, 9A53EBF824EAFA5200648841 /* settings.swift */, + 5CF221142B1F4792006C583F /* notifications.swift */, 9A90E18C24EAD2BB00471E9A /* Info.plist */, 9A90E19724EAD3B000471E9A /* config.plist */, ); @@ -912,6 +927,7 @@ 9A97CEF5253733E400742D8F /* popup.swift */, 5C23BC0129A0102500DBA990 /* portal.swift */, 9A97CEFA253733F300742D8F /* settings.swift */, + 5CF221122B1E8078006C583F /* notifications.swift */, 9A97CECD2537331B00742D8F /* Info.plist */, 9A97CEFF2537340400742D8F /* config.plist */, ); @@ -1001,6 +1017,7 @@ 9A58DE9D24B363D800716A9F /* popup.swift */, 9A58DE9F24B363F300716A9F /* settings.swift */, 9AE29AF7249A53420071B02D /* values.swift */, + 5CF2211A2B1F8CEF006C583F /* notifications.swift */, 9AE29AEC249A50960071B02D /* Info.plist */, 9AE29AF4249A52870071B02D /* config.plist */, 9A3616E82613C3D400D657B6 /* bridge.h */, @@ -1017,6 +1034,7 @@ 9A5AF11A2469CE9B00684737 /* popup.swift */, 5C23BC0F29A3B5AE00DBA990 /* portal.swift */, 9AB7FD7B246B48DB00387FDA /* settings.swift */, + 5CF221182B1F8B90006C583F /* notifications.swift */, 9AF9EE0524648751005D2270 /* Info.plist */, 9AF9EE12246492E8005D2270 /* config.plist */, 5C2229BB29CF66B100F00E69 /* header.h */, @@ -1753,6 +1771,7 @@ 9A2847642666AA2700EC1F6D /* Battery.swift in Sources */, 9A28480B2666AB3000EC1F6D /* Charts.swift in Sources */, 9A2847632666AA2700EC1F6D /* Mini.swift in Sources */, + 5CF2210D2B1E7EAF006C583F /* notifications.swift in Sources */, 9A6EEBBE2685259500897371 /* Logger.swift in Sources */, 9A2847602666AA2700EC1F6D /* NetworkChart.swift in Sources */, 5C23BC0429A014AC00DBA990 /* portal.swift in Sources */, @@ -1791,6 +1810,7 @@ 9AA64260244B274200416A33 /* popup.swift in Sources */, 5C23BC0829A03D1200DBA990 /* portal.swift in Sources */, 9A953A1424B9D22D0038EF4B /* settings.swift in Sources */, + 5CF221172B1F4ACB006C583F /* notifications.swift in Sources */, 9A81C7692449A43600825D92 /* main.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1805,6 +1825,7 @@ 5C23BC0A29A0EDA300DBA990 /* portal.swift in Sources */, 9A53EBFB24EB041E00648841 /* popup.swift in Sources */, 9A53EBF924EAFA5200648841 /* settings.swift in Sources */, + 5CF221152B1F4792006C583F /* notifications.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1818,6 +1839,7 @@ 5C23BC0229A0102500DBA990 /* portal.swift in Sources */, 9A97CEFB253733F300742D8F /* settings.swift in Sources */, 9A97CEE92537338600742D8F /* main.swift in Sources */, + 5CF221132B1E8078006C583F /* notifications.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1854,6 +1876,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5CF2211B2B1F8CEF006C583F /* notifications.swift in Sources */, 9A46C05F266D85F8001A1117 /* smc.swift in Sources */, 9AB6D03926447CAA003215A5 /* reader.m in Sources */, 9AE29AFB249A53DC0071B02D /* readers.swift in Sources */, @@ -1872,6 +1895,7 @@ 9AF9EE1124648ADC005D2270 /* readers.swift in Sources */, 5C23BC1029A3B5AE00DBA990 /* portal.swift in Sources */, 9AB7FD7C246B48DB00387FDA /* settings.swift in Sources */, + 5CF221192B1F8B90006C583F /* notifications.swift in Sources */, 9AF9EE0F2464875F005D2270 /* main.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0;