macOS: Rafactor by introducing a TunnelsTracker
The TunnelTracker is now the central place to track what the current tunnel is, and for keeping track of the tunnel list. Signed-off-by: Roopesh Chander <roop@roopc.net>
This commit is contained in:
parent
796342ddec
commit
dc8f27c5c3
|
@ -55,6 +55,7 @@
|
||||||
6F7774F321774263006A79B3 /* TunnelEditTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774F221774263006A79B3 /* TunnelEditTableViewController.swift */; };
|
6F7774F321774263006A79B3 /* TunnelEditTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774F221774263006A79B3 /* TunnelEditTableViewController.swift */; };
|
||||||
6F7F7E5F21C7D74B00527607 /* TunnelErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7F7E5E21C7D74B00527607 /* TunnelErrors.swift */; };
|
6F7F7E5F21C7D74B00527607 /* TunnelErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7F7E5E21C7D74B00527607 /* TunnelErrors.swift */; };
|
||||||
6F89E17A21EDEB0E00C97BB9 /* StatusItemController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F89E17921EDEB0E00C97BB9 /* StatusItemController.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 */; };
|
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 */; };
|
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 */; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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 */,
|
6FB1BD5F21D2607A00A991BF /* AppDelegate.swift */,
|
||||||
6F89E17921EDEB0E00C97BB9 /* StatusItemController.swift */,
|
6F89E17921EDEB0E00C97BB9 /* StatusItemController.swift */,
|
||||||
6FBA101621D655340051C35F /* StatusMenu.swift */,
|
6FBA101621D655340051C35F /* StatusMenu.swift */,
|
||||||
|
6F89E17B21F090CC00C97BB9 /* TunnelsTracker.swift */,
|
||||||
6FBA104121D6BC210051C35F /* ErrorPresenter.swift */,
|
6FBA104121D6BC210051C35F /* ErrorPresenter.swift */,
|
||||||
6FCD99AE21E0EA1700BA4C82 /* ImportPanelPresenter.swift */,
|
6FCD99AE21E0EA1700BA4C82 /* ImportPanelPresenter.swift */,
|
||||||
6FB1BD6121D2607E00A991BF /* Assets.xcassets */,
|
6FB1BD6121D2607E00A991BF /* Assets.xcassets */,
|
||||||
|
@ -1106,6 +1109,7 @@
|
||||||
6F4DD16C21DA558F00690EAE /* NSTableView+Reuse.swift in Sources */,
|
6F4DD16C21DA558F00690EAE /* NSTableView+Reuse.swift in Sources */,
|
||||||
6FB1BDD821D50F5300A991BF /* WireGuardResult.swift in Sources */,
|
6FB1BDD821D50F5300A991BF /* WireGuardResult.swift in Sources */,
|
||||||
6FB1BDD921D50F5300A991BF /* LocalizationHelper.swift in Sources */,
|
6FB1BDD921D50F5300A991BF /* LocalizationHelper.swift in Sources */,
|
||||||
|
6F89E17C21F090CC00C97BB9 /* TunnelsTracker.swift in Sources */,
|
||||||
6FCD99B121E0EDA900BA4C82 /* TunnelEditViewController.swift in Sources */,
|
6FCD99B121E0EDA900BA4C82 /* TunnelEditViewController.swift in Sources */,
|
||||||
6FB1BDCA21D50F1700A991BF /* x25519.c in Sources */,
|
6FB1BDCA21D50F1700A991BF /* x25519.c in Sources */,
|
||||||
6FB1BDCB21D50F1700A991BF /* Curve25519.swift in Sources */,
|
6FB1BDCB21D50F1700A991BF /* Curve25519.swift in Sources */,
|
||||||
|
|
|
@ -6,8 +6,12 @@ import Cocoa
|
||||||
@NSApplicationMain
|
@NSApplicationMain
|
||||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
|
|
||||||
|
var tunnelsManager: TunnelsManager?
|
||||||
|
var tunnelsTracker: TunnelsTracker?
|
||||||
var statusItemController: StatusItemController?
|
var statusItemController: StatusItemController?
|
||||||
var currentTunnelStatusObserver: AnyObject?
|
|
||||||
|
var manageTunnelsRootVC: ManageTunnelsRootViewController?
|
||||||
|
var manageTunnelsWindowObject: NSWindow?
|
||||||
|
|
||||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||||
Logger.configureGlobal(withFilePath: FileManager.appLogFileURL?.path)
|
Logger.configureGlobal(withFilePath: FileManager.appLogFileURL?.path)
|
||||||
|
@ -20,19 +24,35 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
let tunnelsManager: TunnelsManager = result.value!
|
let tunnelsManager: TunnelsManager = result.value!
|
||||||
let statusItemController = StatusItemController()
|
|
||||||
|
|
||||||
let statusMenu = StatusMenu(tunnelsManager: tunnelsManager)
|
let statusMenu = StatusMenu(tunnelsManager: tunnelsManager)
|
||||||
|
statusMenu.windowDelegate = self
|
||||||
|
|
||||||
|
let statusItemController = StatusItemController()
|
||||||
statusItemController.statusItem.menu = statusMenu
|
statusItemController.statusItem.menu = statusMenu
|
||||||
statusItemController.currentTunnel = statusMenu.currentTunnel
|
|
||||||
self.currentTunnelStatusObserver = statusMenu.observe(\.currentTunnel) { statusMenu, _ in
|
|
||||||
statusItemController.currentTunnel = statusMenu.currentTunnel
|
|
||||||
}
|
|
||||||
self.statusItemController = statusItemController
|
|
||||||
|
|
||||||
tunnelsManager.tunnelsListDelegate = statusMenu
|
let tunnelsTracker = TunnelsTracker(tunnelsManager: tunnelsManager)
|
||||||
tunnelsManager.activationDelegate = statusMenu
|
tunnelsTracker.statusMenu = statusMenu
|
||||||
|
tunnelsTracker.statusItemController = statusItemController
|
||||||
|
|
||||||
|
self.tunnelsManager = tunnelsManager
|
||||||
|
self.tunnelsTracker = tunnelsTracker
|
||||||
|
self.statusItemController = statusItemController
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
class ImportPanelPresenter {
|
class ImportPanelPresenter {
|
||||||
static func presentImportPanel(tunnelsManager: TunnelsManager, sourceVC: NSViewController) {
|
static func presentImportPanel(tunnelsManager: TunnelsManager, sourceVC: NSViewController?) {
|
||||||
guard let window = sourceVC.view.window else { return }
|
guard let window = sourceVC?.view.window else { return }
|
||||||
let openPanel = NSOpenPanel()
|
let openPanel = NSOpenPanel()
|
||||||
openPanel.prompt = tr("macSheetButtonImport")
|
openPanel.prompt = tr("macSheetButtonImport")
|
||||||
openPanel.allowedFileTypes = ["conf", "zip"]
|
openPanel.allowedFileTypes = ["conf", "zip"]
|
||||||
|
|
|
@ -3,27 +3,25 @@
|
||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
|
protocol StatusMenuWindowDelegate: class {
|
||||||
|
func manageTunnelsWindow() -> NSWindow
|
||||||
|
}
|
||||||
|
|
||||||
class StatusMenu: NSMenu {
|
class StatusMenu: NSMenu {
|
||||||
|
|
||||||
let tunnelsManager: TunnelsManager
|
let tunnelsManager: TunnelsManager
|
||||||
var tunnelStatusObservers = [AnyObject]()
|
|
||||||
|
|
||||||
var statusMenuItem: NSMenuItem?
|
var statusMenuItem: NSMenuItem?
|
||||||
var networksMenuItem: NSMenuItem?
|
var networksMenuItem: NSMenuItem?
|
||||||
var firstTunnelMenuItemIndex = 0
|
var firstTunnelMenuItemIndex = 0
|
||||||
var numberOfTunnelMenuItems = 0
|
var numberOfTunnelMenuItems = 0
|
||||||
|
|
||||||
@objc dynamic var currentTunnel: TunnelContainer?
|
var currentTunnel: TunnelContainer? {
|
||||||
|
didSet {
|
||||||
var manageTunnelsRootVC: ManageTunnelsRootViewController?
|
updateStatusMenuItems(with: currentTunnel)
|
||||||
lazy var manageTunnelsWindow: NSWindow = {
|
}
|
||||||
manageTunnelsRootVC = ManageTunnelsRootViewController(tunnelsManager: tunnelsManager)
|
}
|
||||||
let window = NSWindow(contentViewController: manageTunnelsRootVC!)
|
weak var windowDelegate: StatusMenuWindowDelegate?
|
||||||
window.title = tr("macWindowTitleManageTunnels")
|
|
||||||
window.setContentSize(NSSize(width: 800, height: 480))
|
|
||||||
window.setFrameAutosaveName(NSWindow.FrameAutosaveName("ManageTunnelsWindow")) // Auto-save window position and size
|
|
||||||
return window
|
|
||||||
}()
|
|
||||||
|
|
||||||
init(tunnelsManager: TunnelsManager) {
|
init(tunnelsManager: TunnelsManager) {
|
||||||
self.tunnelsManager = tunnelsManager
|
self.tunnelsManager = tunnelsManager
|
||||||
|
@ -31,16 +29,6 @@ class StatusMenu: NSMenu {
|
||||||
|
|
||||||
addStatusMenuItems()
|
addStatusMenuItems()
|
||||||
addItem(NSMenuItem.separator())
|
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
|
firstTunnelMenuItemIndex = numberOfItems
|
||||||
let isAdded = addTunnelMenuItems()
|
let isAdded = addTunnelMenuItems()
|
||||||
|
@ -69,19 +57,21 @@ class StatusMenu: NSMenu {
|
||||||
self.networksMenuItem = networksMenuItem
|
self.networksMenuItem = networksMenuItem
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
|
||||||
//swiftlint:disable:next cyclomatic_complexity
|
//swiftlint:disable:next cyclomatic_complexity
|
||||||
func updateStatusMenuItems(with tunnel: TunnelContainer, ignoreInactive: Bool) -> Bool {
|
func updateStatusMenuItems(with tunnel: TunnelContainer?) {
|
||||||
guard let statusMenuItem = statusMenuItem, let networksMenuItem = networksMenuItem else { return false }
|
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
|
var statusText: String
|
||||||
|
|
||||||
switch tunnel.status {
|
switch tunnel.status {
|
||||||
case .waiting:
|
case .waiting:
|
||||||
return false
|
statusText = tr("tunnelStatusWaiting")
|
||||||
case .inactive:
|
case .inactive:
|
||||||
if ignoreInactive {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
statusText = tr("tunnelStatusInactive")
|
statusText = tr("tunnelStatusInactive")
|
||||||
case .activating:
|
case .activating:
|
||||||
statusText = tr("tunnelStatusActivating")
|
statusText = tr("tunnelStatusActivating")
|
||||||
|
@ -98,7 +88,7 @@ class StatusMenu: NSMenu {
|
||||||
statusMenuItem.title = tr(format: "macStatus (%@)", statusText)
|
statusMenuItem.title = tr(format: "macStatus (%@)", statusText)
|
||||||
|
|
||||||
if tunnel.status == .inactive {
|
if tunnel.status == .inactive {
|
||||||
networksMenuItem.title = tr("macMenuNetworksInactive")
|
networksMenuItem.title = ""
|
||||||
networksMenuItem.isHidden = true
|
networksMenuItem.isHidden = true
|
||||||
} else {
|
} else {
|
||||||
let allowedIPs = tunnel.tunnelConfiguration?.peers.flatMap { $0.allowedIPs }.map { $0.stringRepresentation }.joined(separator: ", ") ?? ""
|
let allowedIPs = tunnel.tunnelConfiguration?.peers.flatMap { $0.allowedIPs }.map { $0.stringRepresentation }.joined(separator: ", ") ?? ""
|
||||||
|
@ -109,7 +99,6 @@ class StatusMenu: NSMenu {
|
||||||
}
|
}
|
||||||
networksMenuItem.isHidden = false
|
networksMenuItem.isHidden = false
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func addTunnelMenuItems() -> Bool {
|
func addTunnelMenuItems() -> Bool {
|
||||||
|
@ -140,24 +129,25 @@ class StatusMenu: NSMenu {
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func tunnelClicked(sender: AnyObject) {
|
@objc func tunnelClicked(sender: AnyObject) {
|
||||||
guard let tunnelMenuItem = sender as? NSMenuItem else { return }
|
guard let tunnelMenuItem = sender as? TunnelMenuItem else { return }
|
||||||
guard let tunnel = tunnelMenuItem.representedObject as? TunnelContainer else { return }
|
|
||||||
if tunnelMenuItem.state == .off {
|
if tunnelMenuItem.state == .off {
|
||||||
tunnelsManager.startActivation(of: tunnel)
|
tunnelsManager.startActivation(of: tunnelMenuItem.tunnel)
|
||||||
} else {
|
} else {
|
||||||
tunnelsManager.startDeactivation(of: tunnel)
|
tunnelsManager.startDeactivation(of: tunnelMenuItem.tunnel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func manageTunnelsClicked() {
|
@objc func manageTunnelsClicked() {
|
||||||
NSApp.activate(ignoringOtherApps: true)
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
|
guard let manageTunnelsWindow = windowDelegate?.manageTunnelsWindow() else { return }
|
||||||
manageTunnelsWindow.makeKeyAndOrderFront(self)
|
manageTunnelsWindow.makeKeyAndOrderFront(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func importTunnelsClicked() {
|
@objc func importTunnelsClicked() {
|
||||||
NSApp.activate(ignoringOtherApps: true)
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
|
guard let manageTunnelsWindow = windowDelegate?.manageTunnelsWindow() else { return }
|
||||||
manageTunnelsWindow.makeKeyAndOrderFront(self)
|
manageTunnelsWindow.makeKeyAndOrderFront(self)
|
||||||
ImportPanelPresenter.presentImportPanel(tunnelsManager: tunnelsManager, sourceVC: manageTunnelsRootVC!)
|
ImportPanelPresenter.presentImportPanel(tunnelsManager: tunnelsManager, sourceVC: manageTunnelsWindow.contentViewController)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func aboutClicked() {
|
@objc func aboutClicked() {
|
||||||
|
@ -179,22 +169,8 @@ class StatusMenu: NSMenu {
|
||||||
|
|
||||||
extension StatusMenu {
|
extension StatusMenu {
|
||||||
func insertTunnelMenuItem(for tunnel: TunnelContainer, at tunnelIndex: Int) {
|
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.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)
|
insertItem(menuItem, at: firstTunnelMenuItemIndex + tunnelIndex)
|
||||||
if numberOfTunnelMenuItems == 0 {
|
if numberOfTunnelMenuItems == 0 {
|
||||||
insertItem(NSMenuItem.separator(), at: firstTunnelMenuItemIndex + tunnelIndex + 1)
|
insertItem(NSMenuItem.separator(), at: firstTunnelMenuItemIndex + tunnelIndex + 1)
|
||||||
|
@ -204,7 +180,6 @@ extension StatusMenu {
|
||||||
|
|
||||||
func removeTunnelMenuItem(at tunnelIndex: Int) {
|
func removeTunnelMenuItem(at tunnelIndex: Int) {
|
||||||
removeItem(at: firstTunnelMenuItemIndex + tunnelIndex)
|
removeItem(at: firstTunnelMenuItemIndex + tunnelIndex)
|
||||||
tunnelStatusObservers.remove(at: tunnelIndex)
|
|
||||||
numberOfTunnelMenuItems -= 1
|
numberOfTunnelMenuItems -= 1
|
||||||
if numberOfTunnelMenuItems == 0 {
|
if numberOfTunnelMenuItems == 0 {
|
||||||
if let firstItem = item(at: firstTunnelMenuItemIndex), firstItem.isSeparatorItem {
|
if let firstItem = item(at: firstTunnelMenuItemIndex), firstItem.isSeparatorItem {
|
||||||
|
@ -214,73 +189,48 @@ extension StatusMenu {
|
||||||
}
|
}
|
||||||
|
|
||||||
func moveTunnelMenuItem(from oldTunnelIndex: Int, to newTunnelIndex: Int) {
|
func moveTunnelMenuItem(from oldTunnelIndex: Int, to newTunnelIndex: Int) {
|
||||||
let oldMenuItem = item(at: firstTunnelMenuItemIndex + oldTunnelIndex)!
|
guard let oldMenuItem = item(at: firstTunnelMenuItemIndex + oldTunnelIndex) as? TunnelMenuItem else { return }
|
||||||
let oldMenuItemTitle = oldMenuItem.title
|
let oldMenuItemTunnel = oldMenuItem.tunnel
|
||||||
let oldMenuItemTunnel = oldMenuItem.representedObject
|
|
||||||
removeItem(at: firstTunnelMenuItemIndex + oldTunnelIndex)
|
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.target = self
|
||||||
menuItem.representedObject = oldMenuItemTunnel
|
|
||||||
insertItem(menuItem, at: firstTunnelMenuItemIndex + newTunnelIndex)
|
insertItem(menuItem, at: firstTunnelMenuItemIndex + newTunnelIndex)
|
||||||
let statusObserver = tunnelStatusObservers.remove(at: oldTunnelIndex)
|
|
||||||
tunnelStatusObservers.insert(statusObserver, at: newTunnelIndex)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateTunnelMenuItem(_ tunnelMenuItem: NSMenuItem) {
|
class TunnelMenuItem: NSMenuItem {
|
||||||
guard let tunnel = tunnelMenuItem.representedObject as? TunnelContainer else { return }
|
|
||||||
tunnelMenuItem.title = tunnel.name
|
var tunnel: TunnelContainer
|
||||||
|
|
||||||
|
private var statusObservationToken: AnyObject?
|
||||||
|
private var nameObservationToken: AnyObject?
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
required init(coder decoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateTitle() {
|
||||||
|
title = tunnel.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateStatus() {
|
||||||
let shouldShowCheckmark = (tunnel.status != .inactive && tunnel.status != .deactivating)
|
let shouldShowCheckmark = (tunnel.status != .inactive && tunnel.status != .deactivating)
|
||||||
tunnelMenuItem.state = shouldShowCheckmark ? .on : .off
|
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
func tunnelMoved(from oldIndex: Int, to newIndex: Int) {
|
|
||||||
moveTunnelMenuItem(from: oldIndex, to: newIndex)
|
|
||||||
manageTunnelsRootVC?.tunnelsListVC?.tunnelMoved(from: oldIndex, to: newIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
func tunnelRemoved(at index: Int) {
|
|
||||||
removeTunnelMenuItem(at: index)
|
|
||||||
manageTunnelsRootVC?.tunnelsListVC?.tunnelRemoved(at: index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func tunnelActivationAttemptSucceeded(tunnel: TunnelContainer) {
|
|
||||||
// Nothing to do
|
|
||||||
}
|
|
||||||
|
|
||||||
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 tunnelActivationSucceeded(tunnel: TunnelContainer) {
|
|
||||||
// Nothing to do
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue