on-demand: macOS: Support SSIDs in on demand activation
Signed-off-by: Roopesh Chander <roop@roopc.net>
This commit is contained in:
committed by
Jason A. Donenfeld
parent
01604dd8d1
commit
fff75adfe1
@@ -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 = ""
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user