diff --git a/Kit/module/notifications.swift b/Kit/module/notifications.swift
index 523da673..faee1f38 100644
--- a/Kit/module/notifications.swift
+++ b/Kit/module/notifications.swift
@@ -62,6 +62,18 @@ open class NotificationsWrapper: NSStackView {
}
}
+ public func newNotification(id rid: String, title: String, subtitle: String? = nil) {
+ let id = "Stats_\(self.module)_\(rid)"
+
+ if self.ids[id] != nil {
+ removeNotification(id)
+ self.ids[id] = nil
+ }
+
+ self.showNotification(id: id, title: title, subtitle: subtitle)
+ self.ids[id] = true
+ }
+
public func hideNotification(_ rid: String) {
let id = "Stats_\(self.module)_\(rid)"
if self.ids[id] != nil {
diff --git a/Modules/Net/config.plist b/Modules/Net/config.plist
index a22055d5..45bec3c2 100644
--- a/Modules/Net/config.plist
+++ b/Modules/Net/config.plist
@@ -81,7 +81,7 @@
popup
notifications
-
+
diff --git a/Modules/Net/main.swift b/Modules/Net/main.swift
index 04267108..5794e733 100644
--- a/Modules/Net/main.swift
+++ b/Modules/Net/main.swift
@@ -126,6 +126,7 @@ public class Network: Module {
private let popupView: Popup
private let settingsView: Settings
private let portalView: Portal
+ private let notificationsView: Notifications
private var usageReader: UsageReader? = nil
private var processReader: ProcessReader? = nil
@@ -154,12 +155,14 @@ public class Network: Module {
self.settingsView = Settings(.network)
self.popupView = Popup(.network)
self.portalView = Portal(.network)
+ self.notificationsView = Notifications(.network)
super.init(
moduleType: .network,
popup: self.popupView,
settings: self.settingsView,
- portal: self.portalView
+ portal: self.portalView,
+ notifications: self.notificationsView
)
guard self.available else { return }
@@ -220,6 +223,7 @@ public class Network: Module {
self.popupView.usageCallback(value)
self.portalView.usageCallback(value)
+ self.notificationsView.usageCallback(value)
var upload: Int64 = value.bandwidth.upload
var download: Int64 = value.bandwidth.download
@@ -313,6 +317,7 @@ public class Network: Module {
guard let value = raw, self.enabled else { return }
self.popupView.connectivityCallback(value)
+ self.notificationsView.connectivityCallback(value)
self.menuBar.widgets.filter{ $0.isActive }.forEach { (w: SWidget) in
switch w.item {
diff --git a/Modules/Net/notifications.swift b/Modules/Net/notifications.swift
new file mode 100644
index 00000000..611a0b18
--- /dev/null
+++ b/Modules/Net/notifications.swift
@@ -0,0 +1,141 @@
+//
+// notifications.swift
+// Net
+//
+// Created by Serhiy Mytrovtsiy on 25/01/2025
+// Using Swift 6.0
+// Running on macOS 15.1
+//
+// Copyright © 2025 Serhiy Mytrovtsiy. All rights reserved.
+//
+
+import Cocoa
+import Kit
+
+class Notifications: NotificationsWrapper {
+ private let connectionID: String = "connection"
+ private let interfaceID: String = "interface"
+ private let localID: String = "localIP"
+ private let publicID: String = "publicIP"
+ private let wifiID: String = "wifi"
+
+ private var connectionState: Bool = false
+ private var interfaceState: Bool = false
+ private var localIPState: Bool = false
+ private var publicIPState: Bool = false
+ private var wifiState: Bool = false
+
+ private var connection: Bool?
+ private var interface: String?
+ private var localIP: String?
+ private var publicIP: String?
+ private var wifi: String?
+
+ public init(_ module: ModuleType) {
+ super.init(module, [self.connectionID, self.interfaceID, self.localID, self.publicID, self.wifiID])
+
+ self.connectionState = Store.shared.bool(key: "\(self.module)_notifications_connection_state", defaultValue: self.connectionState)
+ self.interfaceState = Store.shared.bool(key: "\(self.module)_notifications_interface_state", defaultValue: self.interfaceState)
+ self.localIPState = Store.shared.bool(key: "\(self.module)_notifications_localIP_state", defaultValue: self.localIPState)
+ self.publicIPState = Store.shared.bool(key: "\(self.module)_notifications_publicIP_state", defaultValue: self.publicIPState)
+ self.wifiState = Store.shared.bool(key: "\(self.module)_notifications_wifi_state", defaultValue: self.wifiState)
+
+ self.addArrangedSubview(PreferencesSection([
+ PreferencesRow(localizedString("Status"), component: switchView(
+ action: #selector(self.toggleConnectionState),
+ state: self.connectionState
+ )),
+ PreferencesRow(localizedString("Network interface"), component: switchView(
+ action: #selector(self.toggleInterfaceState),
+ state: self.interfaceState
+ )),
+ PreferencesRow(localizedString("Local IP"), component: switchView(
+ action: #selector(self.toggleLocalIPState),
+ state: self.localIPState
+ )),
+ PreferencesRow(localizedString("Public IP"), component: switchView(
+ action: #selector(self.toggleNPublicIPState),
+ state: self.publicIPState
+ )),
+ PreferencesRow(localizedString("WiFi network"), component: switchView(
+ action: #selector(self.toggleWiFiState),
+ state: self.wifiState
+ ))
+ ]))
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ internal func usageCallback(_ value: Network_Usage) {
+ if self.interfaceState {
+ if value.interface?.BSDName != self.interface {
+ self.newNotification(id: self.interfaceID, title: localizedString("Network interface changed"), subtitle: nil)
+ }
+ self.interface = value.interface?.BSDName
+ }
+
+ if self.localIPState {
+ if value.laddr != self.localIP {
+ self.newNotification(id: self.localID, title: localizedString("Local IP changed"), subtitle: nil)
+ }
+ self.localIP = value.laddr
+ }
+
+ if self.publicIPState {
+ if value.raddr.v4 ?? value.raddr.v6 != self.publicIP {
+ self.newNotification(id: self.publicID, title: localizedString("Public IP changed"), subtitle: nil)
+ }
+ self.publicIP = value.raddr.v4 ?? value.raddr.v6
+ }
+
+ if self.wifiState {
+ if value.wifiDetails.ssid != self.wifi {
+ self.newNotification(id: self.wifiID, title: localizedString("WiFi network changed"), subtitle: nil)
+ }
+ self.wifi = value.wifiDetails.ssid
+ }
+ }
+
+ internal func connectivityCallback(_ value: Network_Connectivity) {
+ guard self.connectionState else { return }
+
+ if self.connection == nil {
+ self.connection = value.status
+ return
+ }
+
+ if self.connection != value.status {
+ var title: String
+ if value.status {
+ title = localizedString("Internet connection established")
+ } else {
+ title = localizedString("Internet connection lost")
+ }
+ self.newNotification(id: self.connectionID, title: title, subtitle: nil)
+ }
+ self.connection = value.status
+ }
+
+ @objc private func toggleConnectionState(_ sender: NSControl) {
+ self.interfaceState = controlState(sender)
+ Store.shared.set(key: "\(self.module)_notifications_connection_state", value: self.interfaceState)
+ }
+ @objc private func toggleInterfaceState(_ sender: NSControl) {
+ self.interfaceState = controlState(sender)
+ Store.shared.set(key: "\(self.module)_notifications_interface_state", value: self.interfaceState)
+ }
+ @objc private func toggleLocalIPState(_ sender: NSControl) {
+ self.interfaceState = controlState(sender)
+ Store.shared.set(key: "\(self.module)_notifications_localIP_state", value: self.interfaceState)
+ }
+ @objc private func toggleNPublicIPState(_ sender: NSControl) {
+ self.interfaceState = controlState(sender)
+ Store.shared.set(key: "\(self.module)_notifications_publicIP_state", value: self.interfaceState)
+ }
+ @objc private func toggleWiFiState(_ sender: NSControl) {
+ self.interfaceState = controlState(sender)
+ Store.shared.set(key: "\(self.module)_notifications_wifi_state", value: self.interfaceState)
+ }
+}
diff --git a/Stats.xcodeproj/project.pbxproj b/Stats.xcodeproj/project.pbxproj
index 60632f56..cb7e6272 100644
--- a/Stats.xcodeproj/project.pbxproj
+++ b/Stats.xcodeproj/project.pbxproj
@@ -57,6 +57,7 @@
5C645BFF2C591F6600D8342A /* widget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C645BFE2C591F6600D8342A /* widget.swift */; };
5C645C002C591FFA00D8342A /* Net.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A3E17CC247A94AF00449CD1 /* Net.framework */; };
5C645C012C591FFA00D8342A /* Net.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A3E17CC247A94AF00449CD1 /* Net.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 5C6F55A72D45694400AB58ED /* notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6F55A62D45694400AB58ED /* notifications.swift */; };
5C7C1DF42C29A3A00060387D /* notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7C1DF32C29A3A00060387D /* notifications.swift */; };
5C8E001029269C7F0027C75A /* protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE493829265055000F2856 /* protocol.swift */; };
5CA518382B543FE600EBCCC4 /* portal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA518372B543FE600EBCCC4 /* portal.swift */; };
@@ -539,6 +540,7 @@
5C5647F72A3F6B100098FFE9 /* Telemetry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Telemetry.swift; sourceTree = ""; };
5C621D812B4770D6004ED7AF /* process.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = process.swift; sourceTree = ""; };
5C645BFE2C591F6600D8342A /* widget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = widget.swift; sourceTree = ""; };
+ 5C6F55A62D45694400AB58ED /* notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = notifications.swift; sourceTree = ""; };
5C7C1DF32C29A3A00060387D /* notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = notifications.swift; sourceTree = ""; };
5C9F90A02A76B30500D41748 /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/Localizable.strings; sourceTree = ""; };
5CA518372B543FE600EBCCC4 /* portal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = portal.swift; sourceTree = ""; };
@@ -1075,6 +1077,7 @@
9A3E17E9247B07BF00449CD1 /* popup.swift */,
5C23BC0B29A10BE000DBA990 /* portal.swift */,
9A58DEA324B3647600716A9F /* settings.swift */,
+ 5C6F55A62D45694400AB58ED /* notifications.swift */,
5C645BFE2C591F6600D8342A /* widget.swift */,
9A3E17CF247A94AF00449CD1 /* Info.plist */,
9A3E17DC247A94C300449CD1 /* config.plist */,
@@ -2125,6 +2128,7 @@
5C645BFF2C591F6600D8342A /* widget.swift in Sources */,
9A58DEA424B3647600716A9F /* settings.swift in Sources */,
9A3E17D9247A94B500449CD1 /* main.swift in Sources */,
+ 5C6F55A72D45694400AB58ED /* notifications.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};