aboutsummaryrefslogtreecommitdiffstats
path: root/WireGuard/WireGuard/UI/macOS/ViewController/TunnelsListTableViewController.swift
diff options
context:
space:
mode:
Diffstat (limited to 'WireGuard/WireGuard/UI/macOS/ViewController/TunnelsListTableViewController.swift')
-rw-r--r--WireGuard/WireGuard/UI/macOS/ViewController/TunnelsListTableViewController.swift354
1 files changed, 0 insertions, 354 deletions
diff --git a/WireGuard/WireGuard/UI/macOS/ViewController/TunnelsListTableViewController.swift b/WireGuard/WireGuard/UI/macOS/ViewController/TunnelsListTableViewController.swift
deleted file mode 100644
index 0771582..0000000
--- a/WireGuard/WireGuard/UI/macOS/ViewController/TunnelsListTableViewController.swift
+++ /dev/null
@@ -1,354 +0,0 @@
-// SPDX-License-Identifier: MIT
-// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
-
-import Cocoa
-
-protocol TunnelsListTableViewControllerDelegate: class {
- func tunnelsSelected(tunnelIndices: [Int])
- func tunnelsListEmpty()
-}
-
-class TunnelsListTableViewController: NSViewController {
-
- let tunnelsManager: TunnelsManager
- weak var delegate: TunnelsListTableViewControllerDelegate?
- var isRemovingTunnelsFromWithinTheApp = false
-
- let tableView: NSTableView = {
- let tableView = NSTableView()
- tableView.addTableColumn(NSTableColumn(identifier: NSUserInterfaceItemIdentifier("TunnelsList")))
- tableView.headerView = nil
- tableView.rowSizeStyle = .medium
- tableView.allowsMultipleSelection = true
- return tableView
- }()
-
- let addButton: NSPopUpButton = {
- let imageItem = NSMenuItem(title: "", action: nil, keyEquivalent: "")
- imageItem.image = NSImage(named: NSImage.addTemplateName)!
-
- let menu = NSMenu()
- menu.addItem(imageItem)
- menu.addItem(withTitle: tr("macMenuAddEmptyTunnel"), action: #selector(handleAddEmptyTunnelAction), keyEquivalent: "n")
- menu.addItem(withTitle: tr("macMenuImportTunnels"), action: #selector(handleImportTunnelAction), keyEquivalent: "o")
- menu.autoenablesItems = false
-
- let button = NSPopUpButton(frame: NSRect.zero, pullsDown: true)
- button.menu = menu
- button.bezelStyle = .smallSquare
- (button.cell as? NSPopUpButtonCell)?.arrowPosition = .arrowAtBottom
- return button
- }()
-
- let removeButton: NSButton = {
- let image = NSImage(named: NSImage.removeTemplateName)!
- let button = NSButton(image: image, target: self, action: #selector(handleRemoveTunnelAction))
- button.bezelStyle = .smallSquare
- button.imagePosition = .imageOnly
- return button
- }()
-
- let actionButton: NSPopUpButton = {
- let imageItem = NSMenuItem(title: "", action: nil, keyEquivalent: "")
- imageItem.image = NSImage(named: NSImage.actionTemplateName)!
-
- let menu = NSMenu()
- menu.addItem(imageItem)
- menu.addItem(withTitle: tr("macMenuViewLog"), action: #selector(handleViewLogAction), keyEquivalent: "")
- menu.addItem(withTitle: tr("macMenuExportTunnels"), action: #selector(handleExportTunnelsAction), keyEquivalent: "")
- menu.autoenablesItems = false
-
- let button = NSPopUpButton(frame: NSRect.zero, pullsDown: true)
- button.menu = menu
- button.bezelStyle = .smallSquare
- (button.cell as? NSPopUpButtonCell)?.arrowPosition = .arrowAtBottom
- return button
- }()
-
- init(tunnelsManager: TunnelsManager) {
- self.tunnelsManager = tunnelsManager
- super.init(nibName: nil, bundle: nil)
- }
-
- required init?(coder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- override func loadView() {
- tableView.dataSource = self
- tableView.delegate = self
-
- tableView.doubleAction = #selector(listDoubleClicked(sender:))
-
- let isSelected = selectTunnelInOperation() || selectTunnel(at: 0)
- if !isSelected {
- delegate?.tunnelsListEmpty()
- }
- tableView.allowsEmptySelection = false
-
- let scrollView = NSScrollView()
- scrollView.hasVerticalScroller = true
- scrollView.autohidesScrollers = true
- scrollView.borderType = .bezelBorder
-
- let clipView = NSClipView()
- clipView.documentView = tableView
- scrollView.contentView = clipView
-
- let buttonBar = NSStackView(views: [addButton, removeButton, actionButton])
- buttonBar.orientation = .horizontal
- buttonBar.spacing = -1
-
- NSLayoutConstraint.activate([
- removeButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 26),
- removeButton.topAnchor.constraint(equalTo: buttonBar.topAnchor),
- removeButton.bottomAnchor.constraint(equalTo: buttonBar.bottomAnchor)
- ])
-
- let fillerButton = FillerButton()
-
- let containerView = NSView()
- containerView.addSubview(scrollView)
- containerView.addSubview(buttonBar)
- containerView.addSubview(fillerButton)
- scrollView.translatesAutoresizingMaskIntoConstraints = false
- buttonBar.translatesAutoresizingMaskIntoConstraints = false
- fillerButton.translatesAutoresizingMaskIntoConstraints = false
-
- NSLayoutConstraint.activate([
- containerView.topAnchor.constraint(equalTo: scrollView.topAnchor),
- containerView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
- containerView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
- scrollView.bottomAnchor.constraint(equalTo: buttonBar.topAnchor, constant: 1),
- containerView.leadingAnchor.constraint(equalTo: buttonBar.leadingAnchor),
- containerView.bottomAnchor.constraint(equalTo: buttonBar.bottomAnchor),
- scrollView.bottomAnchor.constraint(equalTo: fillerButton.topAnchor, constant: 1),
- containerView.bottomAnchor.constraint(equalTo: fillerButton.bottomAnchor),
- buttonBar.trailingAnchor.constraint(equalTo: fillerButton.leadingAnchor, constant: 1),
- fillerButton.trailingAnchor.constraint(equalTo: containerView.trailingAnchor)
- ])
-
- NSLayoutConstraint.activate([
- containerView.widthAnchor.constraint(equalToConstant: 180),
- containerView.heightAnchor.constraint(greaterThanOrEqualToConstant: 120)
- ])
-
- addButton.menu?.items.forEach { $0.target = self }
- actionButton.menu?.items.forEach { $0.target = self }
-
- view = containerView
- }
-
- override func viewWillAppear() {
- selectTunnelInOperation()
- }
-
- @discardableResult
- func selectTunnelInOperation() -> Bool {
- if let currentTunnel = tunnelsManager.tunnelInOperation(), let indexToSelect = tunnelsManager.index(of: currentTunnel) {
- return selectTunnel(at: indexToSelect)
- }
- return false
- }
-
- @objc func handleAddEmptyTunnelAction() {
- let tunnelEditVC = TunnelEditViewController(tunnelsManager: tunnelsManager, tunnel: nil)
- tunnelEditVC.delegate = self
- presentAsSheet(tunnelEditVC)
- }
-
- @objc func handleImportTunnelAction() {
- ImportPanelPresenter.presentImportPanel(tunnelsManager: tunnelsManager, sourceVC: self)
- }
-
- @objc func handleRemoveTunnelAction() {
- guard let window = view.window else { return }
-
- let selectedTunnelIndices = tableView.selectedRowIndexes.sorted().filter { $0 >= 0 && $0 < tunnelsManager.numberOfTunnels() }
- guard !selectedTunnelIndices.isEmpty else { return }
- var nextSelection = selectedTunnelIndices.last! + 1
- if nextSelection >= tunnelsManager.numberOfTunnels() {
- nextSelection = max(selectedTunnelIndices.first! - 1, 0)
- }
-
- let alert = DeleteTunnelsConfirmationAlert()
- if selectedTunnelIndices.count == 1 {
- let firstSelectedTunnel = tunnelsManager.tunnel(at: selectedTunnelIndices.first!)
- alert.messageText = tr(format: "macDeleteTunnelConfirmationAlertMessage (%@)", firstSelectedTunnel.name)
- } else {
- alert.messageText = tr(format: "macDeleteMultipleTunnelsConfirmationAlertMessage (%d)", selectedTunnelIndices.count)
- }
- alert.informativeText = tr("macDeleteTunnelConfirmationAlertInfo")
- alert.onDeleteClicked = { [weak self] completion in
- guard let self = self else { return }
- self.selectTunnel(at: nextSelection)
- let selectedTunnels = selectedTunnelIndices.map { self.tunnelsManager.tunnel(at: $0) }
- self.isRemovingTunnelsFromWithinTheApp = true
- self.tunnelsManager.removeMultiple(tunnels: selectedTunnels) { [weak self] error in
- guard let self = self else { return }
- self.isRemovingTunnelsFromWithinTheApp = false
- defer { completion() }
- if let error = error {
- ErrorPresenter.showErrorAlert(error: error, from: self)
- return
- }
- }
- }
- alert.beginSheetModal(for: window)
- }
-
- @objc func handleViewLogAction() {
- let logVC = LogViewController()
- self.presentAsSheet(logVC)
- }
-
- @objc func handleExportTunnelsAction() {
- PrivateDataConfirmation.confirmAccess(to: tr("macExportPrivateData")) { [weak self] in
- guard let self = self else { return }
- guard let window = self.view.window else { return }
- let savePanel = NSSavePanel()
- savePanel.allowedFileTypes = ["zip"]
- savePanel.prompt = tr("macSheetButtonExportZip")
- savePanel.nameFieldLabel = tr("macNameFieldExportZip")
- savePanel.nameFieldStringValue = "wireguard-export.zip"
- let tunnelsManager = self.tunnelsManager
- savePanel.beginSheetModal(for: window) { [weak tunnelsManager] response in
- guard let tunnelsManager = tunnelsManager else { return }
- guard response == .OK else { return }
- guard let destinationURL = savePanel.url else { return }
- let count = tunnelsManager.numberOfTunnels()
- let tunnelConfigurations = (0 ..< count).compactMap { tunnelsManager.tunnel(at: $0).tunnelConfiguration }
- ZipExporter.exportConfigFiles(tunnelConfigurations: tunnelConfigurations, to: destinationURL) { [weak self] error in
- if let error = error {
- ErrorPresenter.showErrorAlert(error: error, from: self)
- return
- }
- }
- }
- }
- }
-
- @objc func listDoubleClicked(sender: AnyObject) {
- let tunnelIndex = tableView.clickedRow
- guard tunnelIndex >= 0 && tunnelIndex < tunnelsManager.numberOfTunnels() else { return }
- let tunnel = tunnelsManager.tunnel(at: tunnelIndex)
- if tunnel.status == .inactive {
- tunnelsManager.startActivation(of: tunnel)
- } else if tunnel.status == .active {
- tunnelsManager.startDeactivation(of: tunnel)
- }
- }
-
- @discardableResult
- private func selectTunnel(at index: Int) -> Bool {
- if index < tunnelsManager.numberOfTunnels() {
- tableView.scrollRowToVisible(index)
- tableView.selectRowIndexes(IndexSet(integer: index), byExtendingSelection: false)
- return true
- }
- return false
- }
-}
-
-extension TunnelsListTableViewController: TunnelEditViewControllerDelegate {
- func tunnelSaved(tunnel: TunnelContainer) {
- if let tunnelIndex = tunnelsManager.index(of: tunnel), tunnelIndex >= 0 {
- self.selectTunnel(at: tunnelIndex)
- }
- }
-
- func tunnelEditingCancelled() {
- // Nothing to do
- }
-}
-
-extension TunnelsListTableViewController {
- func tunnelAdded(at index: Int) {
- tableView.insertRows(at: IndexSet(integer: index), withAnimation: .slideLeft)
- if tunnelsManager.numberOfTunnels() == 1 {
- selectTunnel(at: 0)
- }
- if !NSApp.isActive {
- // macOS's VPN prompt might have caused us to lose focus
- NSApp.activate(ignoringOtherApps: true)
- }
- }
-
- func tunnelModified(at index: Int) {
- tableView.reloadData(forRowIndexes: IndexSet(integer: index), columnIndexes: IndexSet(integer: 0))
- }
-
- func tunnelMoved(from oldIndex: Int, to newIndex: Int) {
- tableView.moveRow(at: oldIndex, to: newIndex)
- }
-
- func tunnelRemoved(at index: Int) {
- let selectedIndices = tableView.selectedRowIndexes
- let isSingleSelectedTunnelBeingRemoved = selectedIndices.contains(index) && selectedIndices.count == 1
- tableView.removeRows(at: IndexSet(integer: index), withAnimation: .slideLeft)
- if tunnelsManager.numberOfTunnels() == 0 {
- delegate?.tunnelsListEmpty()
- } else if !isRemovingTunnelsFromWithinTheApp && isSingleSelectedTunnelBeingRemoved {
- let newSelection = min(index, tunnelsManager.numberOfTunnels() - 1)
- tableView.selectRowIndexes(IndexSet(integer: newSelection), byExtendingSelection: false)
- }
- }
-}
-
-extension TunnelsListTableViewController: NSTableViewDataSource {
- func numberOfRows(in tableView: NSTableView) -> Int {
- return tunnelsManager.numberOfTunnels()
- }
-}
-
-extension TunnelsListTableViewController: NSTableViewDelegate {
- func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
- let cell: TunnelListRow = tableView.dequeueReusableCell()
- cell.tunnel = tunnelsManager.tunnel(at: row)
- return cell
- }
-
- func tableViewSelectionDidChange(_ notification: Notification) {
- let selectedTunnelIndices = tableView.selectedRowIndexes.sorted()
- if !selectedTunnelIndices.isEmpty {
- delegate?.tunnelsSelected(tunnelIndices: tableView.selectedRowIndexes.sorted())
- }
- }
-}
-
-extension TunnelsListTableViewController {
- override func keyDown(with event: NSEvent) {
- if event.specialKey == .delete {
- handleRemoveTunnelAction()
- }
- }
-}
-
-extension TunnelsListTableViewController: NSMenuItemValidation {
- func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
- if menuItem.action == #selector(TunnelsListTableViewController.handleRemoveTunnelAction) {
- return !tableView.selectedRowIndexes.isEmpty
- }
- return true
- }
-}
-
-class FillerButton: NSButton {
- override var intrinsicContentSize: NSSize {
- return NSSize(width: NSView.noIntrinsicMetric, height: NSView.noIntrinsicMetric)
- }
-
- init() {
- super.init(frame: CGRect.zero)
- title = ""
- bezelStyle = .smallSquare
- }
-
- required init?(coder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- override func mouseDown(with event: NSEvent) {
- // Eat mouseDown event, so that the button looks enabled but is unresponsive
- }
-}