From fff75adfe1ada43a3b5ec9212f6220f192b25790 Mon Sep 17 00:00:00 2001 From: Roopesh Chander Date: Wed, 6 Mar 2019 15:30:42 +0530 Subject: on-demand: macOS: Support SSIDs in on demand activation Signed-off-by: Roopesh Chander --- WireGuard/WireGuard/UI/macOS/View/ControlRow.swift | 61 ++++++++++++++ .../UI/macOS/View/OnDemandWiFiControls.swift | 97 ++++++++++++++++++++++ .../ViewController/TunnelEditViewController.swift | 51 +++++++----- 3 files changed, 187 insertions(+), 22 deletions(-) create mode 100644 WireGuard/WireGuard/UI/macOS/View/ControlRow.swift create mode 100644 WireGuard/WireGuard/UI/macOS/View/OnDemandWiFiControls.swift (limited to 'WireGuard/WireGuard/UI/macOS') diff --git a/WireGuard/WireGuard/UI/macOS/View/ControlRow.swift b/WireGuard/WireGuard/UI/macOS/View/ControlRow.swift new file mode 100644 index 0000000..7759073 --- /dev/null +++ b/WireGuard/WireGuard/UI/macOS/View/ControlRow.swift @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved. + +import Cocoa + +class ControlRow: NSView { + let keyLabel: NSTextField = { + let keyLabel = NSTextField() + keyLabel.isEditable = false + keyLabel.isSelectable = false + keyLabel.isBordered = false + keyLabel.alignment = .right + keyLabel.maximumNumberOfLines = 1 + keyLabel.lineBreakMode = .byTruncatingTail + keyLabel.backgroundColor = .clear + return keyLabel + }() + + var key: String { + get { return keyLabel.stringValue } + set(value) { keyLabel.stringValue = value } + } + + override var intrinsicContentSize: NSSize { + let height = max(keyLabel.intrinsicContentSize.height, controlView.intrinsicContentSize.height) + return NSSize(width: NSView.noIntrinsicMetric, height: height) + } + + let controlView: NSView + + init(controlView: NSView) { + self.controlView = controlView + super.init(frame: CGRect.zero) + + addSubview(keyLabel) + addSubview(controlView) + keyLabel.translatesAutoresizingMaskIntoConstraints = false + controlView.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + keyLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor), + self.leadingAnchor.constraint(equalTo: keyLabel.leadingAnchor), + keyLabel.trailingAnchor.constraint(equalTo: controlView.leadingAnchor, constant: -5) + ]) + + keyLabel.setContentCompressionResistancePriority(.defaultHigh + 2, for: .horizontal) + keyLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal) + + let widthConstraint = keyLabel.widthAnchor.constraint(equalToConstant: 150) + widthConstraint.priority = .defaultHigh + 1 + widthConstraint.isActive = true + } + + required init?(coder decoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func prepareForReuse() { + key = "" + } +} diff --git a/WireGuard/WireGuard/UI/macOS/View/OnDemandWiFiControls.swift b/WireGuard/WireGuard/UI/macOS/View/OnDemandWiFiControls.swift new file mode 100644 index 0000000..bf0e52b --- /dev/null +++ b/WireGuard/WireGuard/UI/macOS/View/OnDemandWiFiControls.swift @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved. + +import Cocoa + +class OnDemandWiFiControls: NSStackView { + + let onDemandWiFiCheckbox: NSButton = { + let checkbox = NSButton() + checkbox.title = tr("tunnelOnDemandWiFi") + checkbox.setButtonType(.switch) + checkbox.state = .off + return checkbox + }() + + static let onDemandSSIDOptions: [ActivateOnDemandViewModel.OnDemandSSIDOption] = [ + .anySSID, .onlySpecificSSIDs, .exceptSpecificSSIDs + ] + + let onDemandSSIDOptionsPopup = NSPopUpButton() + + let onDemandSSIDsField: NSTokenField = { + let tokenField = NSTokenField() + tokenField.tokenizingCharacterSet = CharacterSet([]) + NSLayoutConstraint.activate([ + tokenField.widthAnchor.constraint(greaterThanOrEqualToConstant: 150) + ]) + return tokenField + }() + + override var intrinsicContentSize: NSSize { + let minHeight: CGFloat = 22 + let height = max(minHeight, onDemandWiFiCheckbox.intrinsicContentSize.height, onDemandSSIDOptionsPopup.intrinsicContentSize.height, onDemandSSIDsField.intrinsicContentSize.height) + return NSSize(width: NSView.noIntrinsicMetric, height: height) + } + + var onDemandViewModel: ActivateOnDemandViewModel? { + didSet { updateSSIDControls() } + } + + init() { + super.init(frame: CGRect.zero) + onDemandSSIDOptionsPopup.addItems(withTitles: OnDemandWiFiControls.onDemandSSIDOptions.map { $0.localizedUIString }) + setViews([onDemandWiFiCheckbox, onDemandSSIDOptionsPopup, onDemandSSIDsField], in: .leading) + orientation = .horizontal + + NSLayoutConstraint.activate([ + onDemandWiFiCheckbox.centerYAnchor.constraint(equalTo: centerYAnchor), + onDemandSSIDOptionsPopup.lastBaselineAnchor.constraint(equalTo: onDemandWiFiCheckbox.lastBaselineAnchor), + onDemandSSIDsField.lastBaselineAnchor.constraint(equalTo: onDemandWiFiCheckbox.lastBaselineAnchor) + ]) + + onDemandWiFiCheckbox.target = self + onDemandWiFiCheckbox.action = #selector(wiFiCheckboxToggled) + + onDemandSSIDOptionsPopup.target = self + onDemandSSIDOptionsPopup.action = #selector(ssidOptionsPopupValueChanged) + + updateSSIDControls() + } + + required init?(coder decoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func saveToViewModel() { + guard let onDemandViewModel = onDemandViewModel else { return } + onDemandViewModel.isWiFiInterfaceEnabled = onDemandWiFiCheckbox.state == .on + onDemandViewModel.ssidOption = OnDemandWiFiControls.onDemandSSIDOptions[onDemandSSIDOptionsPopup.indexOfSelectedItem] + onDemandViewModel.selectedSSIDs = (onDemandSSIDsField.objectValue as? [String]) ?? [] + } + + func updateSSIDControls() { + guard let onDemandViewModel = onDemandViewModel else { return } + onDemandWiFiCheckbox.state = onDemandViewModel.isWiFiInterfaceEnabled ? .on : .off + let optionIndex = OnDemandWiFiControls.onDemandSSIDOptions.firstIndex(of: onDemandViewModel.ssidOption) + onDemandSSIDOptionsPopup.selectItem(at: optionIndex ?? 0) + onDemandSSIDsField.objectValue = onDemandViewModel.selectedSSIDs + onDemandSSIDOptionsPopup.isHidden = !onDemandViewModel.isWiFiInterfaceEnabled + onDemandSSIDsField.isHidden = !onDemandViewModel.isWiFiInterfaceEnabled || onDemandViewModel.ssidOption == .anySSID + } + + @objc func wiFiCheckboxToggled() { + onDemandViewModel?.isWiFiInterfaceEnabled = onDemandWiFiCheckbox.state == .on + updateSSIDControls() + } + + @objc func ssidOptionsPopupValueChanged() { + let selectedIndex = onDemandSSIDOptionsPopup.indexOfSelectedItem + onDemandViewModel?.ssidOption = OnDemandWiFiControls.onDemandSSIDOptions[selectedIndex] + onDemandViewModel?.selectedSSIDs = (onDemandSSIDsField.objectValue as? [String]) ?? [] + updateSSIDControls() + if !onDemandSSIDsField.isHidden { + onDemandSSIDsField.becomeFirstResponder() + } + } +} diff --git a/WireGuard/WireGuard/UI/macOS/ViewController/TunnelEditViewController.swift b/WireGuard/WireGuard/UI/macOS/ViewController/TunnelEditViewController.swift index 1c1c054..51b1944 100644 --- a/WireGuard/WireGuard/UI/macOS/ViewController/TunnelEditViewController.swift +++ b/WireGuard/WireGuard/UI/macOS/ViewController/TunnelEditViewController.swift @@ -42,12 +42,16 @@ class TunnelEditViewController: NSViewController { return textView }() - let onDemandRow: PopupRow = { - let popupRow = PopupRow() - popupRow.key = tr("macFieldOnDemand") - return popupRow + let onDemandEthernetCheckbox: NSButton = { + let checkbox = NSButton() + checkbox.title = tr("tunnelOnDemandEthernet") + checkbox.setButtonType(.switch) + checkbox.state = .off + return checkbox }() + let onDemandWiFiControls = OnDemandWiFiControls() + let scrollView: NSScrollView = { let scrollView = NSScrollView() scrollView.hasVerticalScroller = true @@ -89,6 +93,7 @@ class TunnelEditViewController: NSViewController { let tunnelsManager: TunnelsManager let tunnel: TunnelContainer? + var onDemandViewModel: ActivateOnDemandViewModel weak var delegate: TunnelEditViewControllerDelegate? @@ -101,6 +106,7 @@ class TunnelEditViewController: NSViewController { init(tunnelsManager: TunnelsManager, tunnel: TunnelContainer?) { self.tunnelsManager = tunnelsManager self.tunnel = tunnel + self.onDemandViewModel = tunnel != nil ? ActivateOnDemandViewModel(setting: tunnel!.activateOnDemandSetting) : ActivateOnDemandViewModel() super.init(nibName: nil, bundle: nil) } @@ -109,7 +115,6 @@ class TunnelEditViewController: NSViewController { } func populateFields() { - let selectedActivateOnDemandOption: ActivateOnDemandOption if let tunnel = tunnel { // Editing an existing tunnel let tunnelConfiguration = tunnel.tunnelConfiguration! @@ -117,11 +122,6 @@ class TunnelEditViewController: NSViewController { textView.string = tunnelConfiguration.asWgQuickConfig() publicKeyRow.value = tunnelConfiguration.interface.publicKey.base64Key() ?? "" textView.privateKeyString = tunnelConfiguration.interface.privateKey.base64Key() ?? "" - if tunnel.activateOnDemandSetting.isActivateOnDemandEnabled { - selectedActivateOnDemandOption = tunnel.activateOnDemandSetting.activateOnDemandOption - } else { - selectedActivateOnDemandOption = .none - } let singlePeer = tunnelConfiguration.peers.count == 1 ? tunnelConfiguration.peers.first : nil updateExcludePrivateIPsVisibility(singlePeerAllowedIPs: singlePeer?.allowedIPs.map { $0.stringRepresentation }) dnsServersAddedToAllowedIPs = excludePrivateIPsCheckbox.state == .on ? tunnelConfiguration.interface.dns.map { $0.stringRepresentation }.joined(separator: ", ") : nil @@ -132,7 +132,6 @@ class TunnelEditViewController: NSViewController { let bootstrappingText = "[Interface]\nPrivateKey = \(privateKey.base64Key() ?? "")\n" publicKeyRow.value = publicKey.base64Key() ?? "" textView.string = bootstrappingText - selectedActivateOnDemandOption = .none } privateKeyObservationToken = textView.observe(\.privateKeyString) { [weak publicKeyRow] textView, _ in if let privateKeyString = textView.privateKeyString, @@ -150,14 +149,25 @@ class TunnelEditViewController: NSViewController { singlePeerAllowedIPsObservationToken = textView.observe(\.singlePeerAllowedIPs) { [weak self] textView, _ in self?.updateExcludePrivateIPsVisibility(singlePeerAllowedIPs: textView.singlePeerAllowedIPs) } - - onDemandRow.valueOptions = activateOnDemandOptions.map { TunnelViewModel.activateOnDemandOptionText(for: $0) } - onDemandRow.selectedOptionIndex = activateOnDemandOptions.firstIndex(of: selectedActivateOnDemandOption)! } override func loadView() { populateFields() + let onDemandEthernetRow = ControlRow(controlView: onDemandEthernetCheckbox) + onDemandEthernetRow.key = tr("macFieldOnDemand") + onDemandEthernetCheckbox.state = onDemandViewModel.isNonWiFiInterfaceEnabled ? .on : .off + + let onDemandWiFiRow = ControlRow(controlView: onDemandWiFiControls) + onDemandWiFiRow.key = "" + onDemandWiFiControls.onDemandViewModel = onDemandViewModel + + NSLayoutConstraint.activate([ + onDemandEthernetRow.keyLabel.firstBaselineAnchor.constraint(equalTo: onDemandEthernetRow.controlView.firstBaselineAnchor), + onDemandWiFiRow.controlView.centerYAnchor.constraint(equalTo: onDemandWiFiRow.centerYAnchor), + onDemandWiFiRow.trailingAnchor.constraint(equalTo: onDemandWiFiControls.trailingAnchor) + ]) + scrollView.documentView = textView saveButton.target = self @@ -172,7 +182,7 @@ class TunnelEditViewController: NSViewController { let margin: CGFloat = 20 let internalSpacing: CGFloat = 10 - let editorStackView = NSStackView(views: [nameRow, publicKeyRow, onDemandRow, scrollView]) + let editorStackView = NSStackView(views: [nameRow, publicKeyRow, onDemandEthernetRow, onDemandWiFiRow, scrollView]) editorStackView.orientation = .vertical editorStackView.setHuggingPriority(.defaultHigh, for: .horizontal) editorStackView.spacing = internalSpacing @@ -210,13 +220,10 @@ class TunnelEditViewController: NSViewController { ErrorPresenter.showErrorAlert(title: tr("macAlertNameIsEmpty"), message: "", from: self) return } - let onDemandSetting: ActivateOnDemandSetting - let onDemandOption = activateOnDemandOptions[onDemandRow.selectedOptionIndex] - if onDemandOption == .none { - onDemandSetting = ActivateOnDemandSetting.defaultSetting - } else { - onDemandSetting = ActivateOnDemandSetting(isActivateOnDemandEnabled: true, activateOnDemandOption: onDemandOption) - } + + onDemandViewModel.isNonWiFiInterfaceEnabled = onDemandEthernetCheckbox.state == .on + onDemandWiFiControls.saveToViewModel() + let onDemandSetting = ActivateOnDemandSetting(with: onDemandViewModel.toOnDemandOption()) let isTunnelModifiedWithoutChangingName = (tunnel != nil && tunnel!.name == name) guard isTunnelModifiedWithoutChangingName || tunnelsManager.tunnel(named: name) == nil else { -- cgit v1.2.3-59-g8ed1b