aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--WireGuard/WireGuard.xcodeproj/project.pbxproj4
-rw-r--r--WireGuard/WireGuard/UI/macOS/AppDelegate.swift36
-rw-r--r--WireGuard/WireGuard/UI/macOS/ImportPanelPresenter.swift4
-rw-r--r--WireGuard/WireGuard/UI/macOS/StatusMenu.swift158
-rw-r--r--WireGuard/WireGuard/UI/macOS/TunnelsTracker.swift126
5 files changed, 214 insertions, 114 deletions
diff --git a/WireGuard/WireGuard.xcodeproj/project.pbxproj b/WireGuard/WireGuard.xcodeproj/project.pbxproj
index b6b1c32..ae18c66 100644
--- a/WireGuard/WireGuard.xcodeproj/project.pbxproj
+++ b/WireGuard/WireGuard.xcodeproj/project.pbxproj
@@ -55,6 +55,7 @@
6F7774F321774263006A79B3 /* TunnelEditTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774F221774263006A79B3 /* TunnelEditTableViewController.swift */; };
6F7F7E5F21C7D74B00527607 /* TunnelErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7F7E5E21C7D74B00527607 /* TunnelErrors.swift */; };
6F89E17A21EDEB0E00C97BB9 /* StatusItemController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F89E17921EDEB0E00C97BB9 /* StatusItemController.swift */; };
+ 6F89E17C21F090CC00C97BB9 /* TunnelsTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F89E17B21F090CC00C97BB9 /* TunnelsTracker.swift */; };
6F919EC3218A2AE90023B400 /* ErrorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F919EC2218A2AE90023B400 /* ErrorPresenter.swift */; };
6F919ED9218C65C50023B400 /* wireguard_doc_logo_22x29.png in Resources */ = {isa = PBXBuildFile; fileRef = 6F919ED5218C65C50023B400 /* wireguard_doc_logo_22x29.png */; };
6F919EDA218C65C50023B400 /* wireguard_doc_logo_44x58.png in Resources */ = {isa = PBXBuildFile; fileRef = 6F919ED6218C65C50023B400 /* wireguard_doc_logo_44x58.png */; };
@@ -265,6 +266,7 @@
6F7774F221774263006A79B3 /* TunnelEditTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelEditTableViewController.swift; sourceTree = "<group>"; };
6F7F7E5E21C7D74B00527607 /* TunnelErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelErrors.swift; sourceTree = "<group>"; };
6F89E17921EDEB0E00C97BB9 /* StatusItemController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusItemController.swift; sourceTree = "<group>"; };
+ 6F89E17B21F090CC00C97BB9 /* TunnelsTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelsTracker.swift; sourceTree = "<group>"; };
6F919EC2218A2AE90023B400 /* ErrorPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorPresenter.swift; sourceTree = "<group>"; };
6F919ED5218C65C50023B400 /* wireguard_doc_logo_22x29.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = wireguard_doc_logo_22x29.png; sourceTree = "<group>"; };
6F919ED6218C65C50023B400 /* wireguard_doc_logo_44x58.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = wireguard_doc_logo_44x58.png; sourceTree = "<group>"; };
@@ -516,6 +518,7 @@
6FB1BD5F21D2607A00A991BF /* AppDelegate.swift */,
6F89E17921EDEB0E00C97BB9 /* StatusItemController.swift */,
6FBA101621D655340051C35F /* StatusMenu.swift */,
+ 6F89E17B21F090CC00C97BB9 /* TunnelsTracker.swift */,
6FBA104121D6BC210051C35F /* ErrorPresenter.swift */,
6FCD99AE21E0EA1700BA4C82 /* ImportPanelPresenter.swift */,
6FB1BD6121D2607E00A991BF /* Assets.xcassets */,
@@ -1106,6 +1109,7 @@
6F4DD16C21DA558F00690EAE /* NSTableView+Reuse.swift in Sources */,
6FB1BDD821D50F5300A991BF /* WireGuardResult.swift in Sources */,
6FB1BDD921D50F5300A991BF /* LocalizationHelper.swift in Sources */,
+ 6F89E17C21F090CC00C97BB9 /* TunnelsTracker.swift in Sources */,
6FCD99B121E0EDA900BA4C82 /* TunnelEditViewController.swift in Sources */,
6FB1BDCA21D50F1700A991BF /* x25519.c in Sources */,
6FB1BDCB21D50F1700A991BF /* Curve25519.swift in Sources */,
diff --git a/WireGuard/WireGuard/UI/macOS/AppDelegate.swift b/WireGuard/WireGuard/UI/macOS/AppDelegate.swift
index 3f08987..95c1aa6 100644
--- a/WireGuard/WireGuard/UI/macOS/AppDelegate.swift
+++ b/WireGuard/WireGuard/UI/macOS/AppDelegate.swift
@@ -6,8 +6,12 @@ import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
+ var tunnelsManager: TunnelsManager?
+ var tunnelsTracker: TunnelsTracker?
var statusItemController: StatusItemController?
- var currentTunnelStatusObserver: AnyObject?
+
+ var manageTunnelsRootVC: ManageTunnelsRootViewController?
+ var manageTunnelsWindowObject: NSWindow?
func applicationDidFinishLaunching(_ aNotification: Notification) {
Logger.configureGlobal(withFilePath: FileManager.appLogFileURL?.path)
@@ -20,19 +24,35 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}
let tunnelsManager: TunnelsManager = result.value!
- let statusItemController = StatusItemController()
let statusMenu = StatusMenu(tunnelsManager: tunnelsManager)
+ statusMenu.windowDelegate = self
+ let statusItemController = StatusItemController()
statusItemController.statusItem.menu = statusMenu
- statusItemController.currentTunnel = statusMenu.currentTunnel
- self.currentTunnelStatusObserver = statusMenu.observe(\.currentTunnel) { statusMenu, _ in
- statusItemController.currentTunnel = statusMenu.currentTunnel
- }
+
+ let tunnelsTracker = TunnelsTracker(tunnelsManager: tunnelsManager)
+ tunnelsTracker.statusMenu = statusMenu
+ tunnelsTracker.statusItemController = statusItemController
+
+ self.tunnelsManager = tunnelsManager
+ self.tunnelsTracker = tunnelsTracker
self.statusItemController = statusItemController
+ }
+ }
+}
- tunnelsManager.tunnelsListDelegate = statusMenu
- tunnelsManager.activationDelegate = statusMenu
+extension AppDelegate: StatusMenuWindowDelegate {
+ func manageTunnelsWindow() -> NSWindow {
+ if manageTunnelsWindowObject == nil {
+ manageTunnelsRootVC = ManageTunnelsRootViewController(tunnelsManager: tunnelsManager!)
+ let window = NSWindow(contentViewController: manageTunnelsRootVC!)
+ window.title = tr("macWindowTitleManageTunnels")
+ window.setContentSize(NSSize(width: 800, height: 480))
+ window.setFrameAutosaveName(NSWindow.FrameAutosaveName("ManageTunnelsWindow")) // Auto-save window position and size
+ manageTunnelsWindowObject = window
+ tunnelsTracker?.manageTunnelsRootVC = manageTunnelsRootVC
}
+ return manageTunnelsWindowObject!
}
}
diff --git a/WireGuard/WireGuard/UI/macOS/ImportPanelPresenter.swift b/WireGuard/WireGuard/UI/macOS/ImportPanelPresenter.swift
index 03b1be7..95fc46a 100644
--- a/WireGuard/WireGuard/UI/macOS/ImportPanelPresenter.swift
+++ b/WireGuard/WireGuard/UI/macOS/ImportPanelPresenter.swift
@@ -4,8 +4,8 @@
import Cocoa
class ImportPanelPresenter {
- static func presentImportPanel(tunnelsManager: TunnelsManager, sourceVC: NSViewController) {
- guard let window = sourceVC.view.window else { return }
+ static func presentImportPanel(tunnelsManager: TunnelsManager, sourceVC: NSViewController?) {
+ guard let window = sourceVC?.view.window else { return }
let openPanel = NSOpenPanel()
openPanel.prompt = tr("macSheetButtonImport")
openPanel.allowedFileTypes = ["conf", "zip"]
diff --git a/WireGuard/WireGuard/UI/macOS/StatusMenu.swift b/WireGuard/WireGuard/UI/macOS/StatusMenu.swift
index 634ea9f..2ae3013 100644
--- a/WireGuard/WireGuard/UI/macOS/StatusMenu.swift
+++ b/WireGuard/WireGuard/UI/macOS/StatusMenu.swift
@@ -3,27 +3,25 @@
import Cocoa
+protocol StatusMenuWindowDelegate: class {
+ func manageTunnelsWindow() -> NSWindow
+}
+
class StatusMenu: NSMenu {
let tunnelsManager: TunnelsManager
- var tunnelStatusObservers = [AnyObject]()
var statusMenuItem: NSMenuItem?
var networksMenuItem: NSMenuItem?
var firstTunnelMenuItemIndex = 0
var numberOfTunnelMenuItems = 0
- @objc dynamic var currentTunnel: TunnelContainer?
-
- var manageTunnelsRootVC: ManageTunnelsRootViewController?
- lazy var manageTunnelsWindow: NSWindow = {
- manageTunnelsRootVC = ManageTunnelsRootViewController(tunnelsManager: tunnelsManager)
- let window = NSWindow(contentViewController: manageTunnelsRootVC!)
- window.title = tr("macWindowTitleManageTunnels")
- window.setContentSize(NSSize(width: 800, height: 480))
- window.setFrameAutosaveName(NSWindow.FrameAutosaveName("ManageTunnelsWindow")) // Auto-save window position and size
- return window
- }()
+ var currentTunnel: TunnelContainer? {
+ didSet {
+ updateStatusMenuItems(with: currentTunnel)
+ }
+ }
+ weak var windowDelegate: StatusMenuWindowDelegate?
init(tunnelsManager: TunnelsManager) {
self.tunnelsManager = tunnelsManager
@@ -31,16 +29,6 @@ class StatusMenu: NSMenu {
addStatusMenuItems()
addItem(NSMenuItem.separator())
- for index in 0 ..< tunnelsManager.numberOfTunnels() {
- let tunnel = tunnelsManager.tunnel(at: index)
- if tunnel.status != .inactive {
- currentTunnel = tunnel
- }
- let isUpdated = updateStatusMenuItems(with: tunnel, ignoreInactive: true)
- if isUpdated {
- break
- }
- }
firstTunnelMenuItemIndex = numberOfItems
let isAdded = addTunnelMenuItems()
@@ -69,19 +57,21 @@ class StatusMenu: NSMenu {
self.networksMenuItem = networksMenuItem
}
- @discardableResult
//swiftlint:disable:next cyclomatic_complexity
- func updateStatusMenuItems(with tunnel: TunnelContainer, ignoreInactive: Bool) -> Bool {
- guard let statusMenuItem = statusMenuItem, let networksMenuItem = networksMenuItem else { return false }
+ func updateStatusMenuItems(with tunnel: TunnelContainer?) {
+ guard let statusMenuItem = statusMenuItem, let networksMenuItem = networksMenuItem else { return }
+ guard let tunnel = tunnel else {
+ statusMenuItem.title = tr(format: "macStatus (%@)", tr("tunnelStatusInactive"))
+ networksMenuItem.title = ""
+ networksMenuItem.isHidden = true
+ return
+ }
var statusText: String
switch tunnel.status {
case .waiting:
- return false
+ statusText = tr("tunnelStatusWaiting")
case .inactive:
- if ignoreInactive {
- return false
- }
statusText = tr("tunnelStatusInactive")
case .activating:
statusText = tr("tunnelStatusActivating")
@@ -98,7 +88,7 @@ class StatusMenu: NSMenu {
statusMenuItem.title = tr(format: "macStatus (%@)", statusText)
if tunnel.status == .inactive {
- networksMenuItem.title = tr("macMenuNetworksInactive")
+ networksMenuItem.title = ""
networksMenuItem.isHidden = true
} else {
let allowedIPs = tunnel.tunnelConfiguration?.peers.flatMap { $0.allowedIPs }.map { $0.stringRepresentation }.joined(separator: ", ") ?? ""
@@ -109,7 +99,6 @@ class StatusMenu: NSMenu {
}
networksMenuItem.isHidden = false
}
- return true
}
func addTunnelMenuItems() -> Bool {
@@ -140,24 +129,25 @@ class StatusMenu: NSMenu {
}
@objc func tunnelClicked(sender: AnyObject) {
- guard let tunnelMenuItem = sender as? NSMenuItem else { return }
- guard let tunnel = tunnelMenuItem.representedObject as? TunnelContainer else { return }
+ guard let tunnelMenuItem = sender as? TunnelMenuItem else { return }
if tunnelMenuItem.state == .off {
- tunnelsManager.startActivation(of: tunnel)
+ tunnelsManager.startActivation(of: tunnelMenuItem.tunnel)
} else {
- tunnelsManager.startDeactivation(of: tunnel)
+ tunnelsManager.startDeactivation(of: tunnelMenuItem.tunnel)
}
}
@objc func manageTunnelsClicked() {
NSApp.activate(ignoringOtherApps: true)
+ guard let manageTunnelsWindow = windowDelegate?.manageTunnelsWindow() else { return }
manageTunnelsWindow.makeKeyAndOrderFront(self)
}
@objc func importTunnelsClicked() {
NSApp.activate(ignoringOtherApps: true)
+ guard let manageTunnelsWindow = windowDelegate?.manageTunnelsWindow() else { return }
manageTunnelsWindow.makeKeyAndOrderFront(self)
- ImportPanelPresenter.presentImportPanel(tunnelsManager: tunnelsManager, sourceVC: manageTunnelsRootVC!)
+ ImportPanelPresenter.presentImportPanel(tunnelsManager: tunnelsManager, sourceVC: manageTunnelsWindow.contentViewController)
}
@objc func aboutClicked() {
@@ -179,22 +169,8 @@ class StatusMenu: NSMenu {
extension StatusMenu {
func insertTunnelMenuItem(for tunnel: TunnelContainer, at tunnelIndex: Int) {
- let menuItem = NSMenuItem(title: tunnel.name, action: #selector(tunnelClicked(sender:)), keyEquivalent: "")
+ let menuItem = TunnelMenuItem(tunnel: tunnel, action: #selector(tunnelClicked(sender:)))
menuItem.target = self
- menuItem.representedObject = tunnel
- updateTunnelMenuItem(menuItem)
- let statusObservationToken = tunnel.observe(\.status) { [weak self] tunnel, _ in
- updateTunnelMenuItem(menuItem)
- if tunnel.status == .deactivating || tunnel.status == .inactive {
- if self?.currentTunnel == tunnel {
- self?.currentTunnel = self?.tunnelsManager.waitingTunnel()
- }
- } else {
- self?.currentTunnel = tunnel
- }
- self?.updateStatusMenuItems(with: tunnel, ignoreInactive: false)
- }
- tunnelStatusObservers.insert(statusObservationToken, at: tunnelIndex)
insertItem(menuItem, at: firstTunnelMenuItemIndex + tunnelIndex)
if numberOfTunnelMenuItems == 0 {
insertItem(NSMenuItem.separator(), at: firstTunnelMenuItemIndex + tunnelIndex + 1)
@@ -204,7 +180,6 @@ extension StatusMenu {
func removeTunnelMenuItem(at tunnelIndex: Int) {
removeItem(at: firstTunnelMenuItemIndex + tunnelIndex)
- tunnelStatusObservers.remove(at: tunnelIndex)
numberOfTunnelMenuItems -= 1
if numberOfTunnelMenuItems == 0 {
if let firstItem = item(at: firstTunnelMenuItemIndex), firstItem.isSeparatorItem {
@@ -214,73 +189,48 @@ extension StatusMenu {
}
func moveTunnelMenuItem(from oldTunnelIndex: Int, to newTunnelIndex: Int) {
- let oldMenuItem = item(at: firstTunnelMenuItemIndex + oldTunnelIndex)!
- let oldMenuItemTitle = oldMenuItem.title
- let oldMenuItemTunnel = oldMenuItem.representedObject
+ guard let oldMenuItem = item(at: firstTunnelMenuItemIndex + oldTunnelIndex) as? TunnelMenuItem else { return }
+ let oldMenuItemTunnel = oldMenuItem.tunnel
removeItem(at: firstTunnelMenuItemIndex + oldTunnelIndex)
- let menuItem = NSMenuItem(title: oldMenuItemTitle, action: #selector(tunnelClicked(sender:)), keyEquivalent: "")
+ let menuItem = TunnelMenuItem(tunnel: oldMenuItemTunnel, action: #selector(tunnelClicked(sender:)))
menuItem.target = self
- menuItem.representedObject = oldMenuItemTunnel
insertItem(menuItem, at: firstTunnelMenuItemIndex + newTunnelIndex)
- let statusObserver = tunnelStatusObservers.remove(at: oldTunnelIndex)
- tunnelStatusObservers.insert(statusObserver, at: newTunnelIndex)
- }
-}
-
-private func updateTunnelMenuItem(_ tunnelMenuItem: NSMenuItem) {
- guard let tunnel = tunnelMenuItem.representedObject as? TunnelContainer else { return }
- tunnelMenuItem.title = tunnel.name
- let shouldShowCheckmark = (tunnel.status != .inactive && tunnel.status != .deactivating)
- tunnelMenuItem.state = shouldShowCheckmark ? .on : .off
-}
-extension StatusMenu: TunnelsManagerListDelegate {
- func tunnelAdded(at index: Int) {
- let tunnel = tunnelsManager.tunnel(at: index)
- insertTunnelMenuItem(for: tunnel, at: index)
- manageTunnelsRootVC?.tunnelsListVC?.tunnelAdded(at: index)
}
+}
- func tunnelModified(at index: Int) {
- if let tunnelMenuItem = item(at: firstTunnelMenuItemIndex + index) {
- updateTunnelMenuItem(tunnelMenuItem)
- }
- manageTunnelsRootVC?.tunnelsListVC?.tunnelModified(at: index)
- }
+class TunnelMenuItem: NSMenuItem {
- func tunnelMoved(from oldIndex: Int, to newIndex: Int) {
- moveTunnelMenuItem(from: oldIndex, to: newIndex)
- manageTunnelsRootVC?.tunnelsListVC?.tunnelMoved(from: oldIndex, to: newIndex)
- }
+ var tunnel: TunnelContainer
- func tunnelRemoved(at index: Int) {
- removeTunnelMenuItem(at: index)
- manageTunnelsRootVC?.tunnelsListVC?.tunnelRemoved(at: index)
- }
-}
+ private var statusObservationToken: AnyObject?
+ private var nameObservationToken: AnyObject?
-extension StatusMenu: TunnelsManagerActivationDelegate {
- func tunnelActivationAttemptFailed(tunnel: TunnelContainer, error: TunnelsManagerActivationAttemptError) {
- if let manageTunnelsRootVC = manageTunnelsRootVC, manageTunnelsWindow.isVisible {
- ErrorPresenter.showErrorAlert(error: error, from: manageTunnelsRootVC)
- } else {
- ErrorPresenter.showErrorAlert(error: error, from: nil)
+ init(tunnel: TunnelContainer, action selector: Selector?) {
+ self.tunnel = tunnel
+ super.init(title: tunnel.name, action: selector, keyEquivalent: "")
+ updateStatus()
+ let statusObservationToken = tunnel.observe(\.status) { [weak self] _, _ in
+ self?.updateStatus()
+ }
+ updateTitle()
+ let nameObservationToken = tunnel.observe(\TunnelContainer.name) { [weak self] _, _ in
+ self?.updateTitle()
}
+ self.statusObservationToken = statusObservationToken
+ self.nameObservationToken = nameObservationToken
}
- func tunnelActivationAttemptSucceeded(tunnel: TunnelContainer) {
- // Nothing to do
+ required init(coder decoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
}
- func tunnelActivationFailed(tunnel: TunnelContainer, error: TunnelsManagerActivationError) {
- if let manageTunnelsRootVC = manageTunnelsRootVC, manageTunnelsWindow.isVisible {
- ErrorPresenter.showErrorAlert(error: error, from: manageTunnelsRootVC)
- } else {
- ErrorPresenter.showErrorAlert(error: error, from: nil)
- }
+ func updateTitle() {
+ title = tunnel.name
}
- func tunnelActivationSucceeded(tunnel: TunnelContainer) {
- // Nothing to do
+ func updateStatus() {
+ let shouldShowCheckmark = (tunnel.status != .inactive && tunnel.status != .deactivating)
+ state = shouldShowCheckmark ? .on : .off
}
}
diff --git a/WireGuard/WireGuard/UI/macOS/TunnelsTracker.swift b/WireGuard/WireGuard/UI/macOS/TunnelsTracker.swift
new file mode 100644
index 0000000..781fa2e
--- /dev/null
+++ b/WireGuard/WireGuard/UI/macOS/TunnelsTracker.swift
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
+
+import Cocoa
+
+// Keeps track of tunnels and informs the following objects of changes in tunnels:
+// - Status menu
+// - Status item controller
+// - Tunnels list view controller in the Manage Tunnels window
+
+class TunnelsTracker {
+
+ weak var statusMenu: StatusMenu?
+ weak var statusItemController: StatusItemController?
+ weak var manageTunnelsRootVC: ManageTunnelsRootViewController?
+
+ private var tunnelsManager: TunnelsManager
+ private var tunnelStatusObservers = [AnyObject]()
+ private var currentTunnel: TunnelContainer? {
+ didSet {
+ statusMenu?.currentTunnel = currentTunnel
+ statusItemController?.currentTunnel = currentTunnel
+ }
+ }
+
+ init(tunnelsManager: TunnelsManager) {
+ self.tunnelsManager = tunnelsManager
+
+ if let waitingTunnel = tunnelsManager.waitingTunnel() {
+ currentTunnel = waitingTunnel
+ } else {
+ for index in 0 ..< tunnelsManager.numberOfTunnels() {
+ let tunnel = tunnelsManager.tunnel(at: index)
+ if tunnel.status != .inactive {
+ currentTunnel = tunnel
+ break
+ }
+ }
+ }
+
+ for index in 0 ..< tunnelsManager.numberOfTunnels() {
+ let tunnel = tunnelsManager.tunnel(at: index)
+ let statusObservationToken = observeStatus(of: tunnel)
+ tunnelStatusObservers.insert(statusObservationToken, at: index)
+ }
+
+ tunnelsManager.tunnelsListDelegate = self
+ tunnelsManager.activationDelegate = self
+ }
+
+ func observeStatus(of tunnel: TunnelContainer) -> AnyObject {
+ return tunnel.observe(\.status) { [weak self] tunnel, _ in
+ guard let self = self else { return }
+ if tunnel.status == .deactivating || tunnel.status == .inactive {
+ if self.currentTunnel == tunnel {
+ if let waitingTunnel = self.tunnelsManager.waitingTunnel() {
+ self.currentTunnel = waitingTunnel
+ } else if tunnel.status == .inactive {
+ self.currentTunnel = nil
+ }
+ }
+ } else {
+ self.currentTunnel = tunnel
+ }
+ }
+ }
+}
+
+extension TunnelsTracker: TunnelsManagerListDelegate {
+ func tunnelAdded(at index: Int) {
+ let tunnel = tunnelsManager.tunnel(at: index)
+ if tunnel.status != .deactivating && tunnel.status != .inactive {
+ self.currentTunnel = tunnel
+ }
+ let statusObservationToken = observeStatus(of: tunnel)
+ tunnelStatusObservers.insert(statusObservationToken, at: index)
+
+ statusMenu?.insertTunnelMenuItem(for: tunnel, at: index)
+ manageTunnelsRootVC?.tunnelsListVC?.tunnelAdded(at: index)
+ }
+
+ func tunnelModified(at index: Int) {
+ manageTunnelsRootVC?.tunnelsListVC?.tunnelModified(at: index)
+ }
+
+ func tunnelMoved(from oldIndex: Int, to newIndex: Int) {
+ let statusObserver = tunnelStatusObservers.remove(at: oldIndex)
+ tunnelStatusObservers.insert(statusObserver, at: newIndex)
+
+ statusMenu?.moveTunnelMenuItem(from: oldIndex, to: newIndex)
+ manageTunnelsRootVC?.tunnelsListVC?.tunnelMoved(from: oldIndex, to: newIndex)
+ }
+
+ func tunnelRemoved(at index: Int) {
+ tunnelStatusObservers.remove(at: index)
+
+ statusMenu?.removeTunnelMenuItem(at: index)
+ manageTunnelsRootVC?.tunnelsListVC?.tunnelRemoved(at: index)
+ }
+}
+
+extension TunnelsTracker: TunnelsManagerActivationDelegate {
+ func tunnelActivationAttemptFailed(tunnel: TunnelContainer, error: TunnelsManagerActivationAttemptError) {
+ if let manageTunnelsRootVC = manageTunnelsRootVC, manageTunnelsRootVC.view.window?.isVisible ?? false {
+ ErrorPresenter.showErrorAlert(error: error, from: manageTunnelsRootVC)
+ } else {
+ ErrorPresenter.showErrorAlert(error: error, from: nil)
+ }
+ }
+
+ func tunnelActivationAttemptSucceeded(tunnel: TunnelContainer) {
+ // Nothing to do
+ }
+
+ func tunnelActivationFailed(tunnel: TunnelContainer, error: TunnelsManagerActivationError) {
+ if let manageTunnelsRootVC = manageTunnelsRootVC, manageTunnelsRootVC.view.window?.isVisible ?? false {
+ ErrorPresenter.showErrorAlert(error: error, from: manageTunnelsRootVC)
+ } else {
+ ErrorPresenter.showErrorAlert(error: error, from: nil)
+ }
+ }
+
+ func tunnelActivationSucceeded(tunnel: TunnelContainer) {
+ // Nothing to do
+ }
+}