diff --git a/WireGuard/WireGuard/UI/TunnelViewModel.swift b/WireGuard/WireGuard/UI/TunnelViewModel.swift index 1864cad..8f95039 100644 --- a/WireGuard/WireGuard/UI/TunnelViewModel.swift +++ b/WireGuard/WireGuard/UI/TunnelViewModel.swift @@ -547,6 +547,14 @@ class TunnelViewModel { } } + func asWgQuickConfig() -> String? { + let saveResult = save() + if case .saved(let tunnelConfiguration) = saveResult { + return tunnelConfiguration.asWgQuickConfig() + } + return nil + } + @discardableResult func applyConfiguration(other: TunnelConfiguration) -> Changes { // Replaces current data with data from other TunnelConfiguration, ignoring any changes in peer ordering. diff --git a/WireGuard/WireGuard/UI/macOS/View/ConfTextView.swift b/WireGuard/WireGuard/UI/macOS/View/ConfTextView.swift index bc019f8..2ea8f84 100644 --- a/WireGuard/WireGuard/UI/macOS/View/ConfTextView.swift +++ b/WireGuard/WireGuard/UI/macOS/View/ConfTextView.swift @@ -9,6 +9,7 @@ class ConfTextView: NSTextView { @objc dynamic var hasError: Bool = false @objc dynamic var privateKeyString: String? + @objc dynamic var singlePeerAllowedIPs: [String]? override var string: String { didSet { @@ -52,6 +53,13 @@ class ConfTextView: NSTextView { } } + func setConfText(_ text: String) { + let fullTextRange = NSRange(location: 0, length: (string as NSString).length) + if shouldChangeText(in: fullTextRange, replacementString: text) { + replaceCharacters(in: fullTextRange, with: text) + didChangeText() + } + } } extension ConfTextView: NSTextViewDelegate { @@ -64,6 +72,10 @@ extension ConfTextView: NSTextViewDelegate { if privateKeyString != confTextStorage.privateKeyString { privateKeyString = confTextStorage.privateKeyString } + let updatedSinglePeerAllowedIPs = confTextStorage.hasOnePeer && !confTextStorage.hasError ? confTextStorage.lastOnePeerAllowedIPs : nil + if singlePeerAllowedIPs != updatedSinglePeerAllowedIPs { + singlePeerAllowedIPs = updatedSinglePeerAllowedIPs + } needsDisplay = true } diff --git a/WireGuard/WireGuard/UI/macOS/ViewController/TunnelEditViewController.swift b/WireGuard/WireGuard/UI/macOS/ViewController/TunnelEditViewController.swift index efb3fd7..772ee7f 100644 --- a/WireGuard/WireGuard/UI/macOS/ViewController/TunnelEditViewController.swift +++ b/WireGuard/WireGuard/UI/macOS/ViewController/TunnelEditViewController.swift @@ -56,6 +56,14 @@ class TunnelEditViewController: NSViewController { return scrollView }() + let excludePrivateIPsCheckbox: NSButton = { + let checkbox = NSButton() + checkbox.title = tr("tunnelPeerExcludePrivateIPs") + checkbox.setButtonType(.switch) + checkbox.state = .off + return checkbox + }() + let discardButton: NSButton = { let button = NSButton() button.title = tr("macEditDiscard") @@ -86,6 +94,7 @@ class TunnelEditViewController: NSViewController { var privateKeyObservationToken: AnyObject? var hasErrorObservationToken: AnyObject? + var singlePeerAllowedIPsObservationToken: AnyObject? init(tunnelsManager: TunnelsManager, tunnel: TunnelContainer?) { self.tunnelsManager = tunnelsManager @@ -111,6 +120,8 @@ class TunnelEditViewController: NSViewController { } else { selectedActivateOnDemandOption = .none } + let singlePeer = tunnelConfiguration.peers.count == 1 ? tunnelConfiguration.peers.first : nil + updateExcludePrivateIPsVisibility(singlePeerAllowedIPs: singlePeer?.allowedIPs.map { $0.stringRepresentation }) } else { // Creating a new tunnel let privateKey = Curve25519.generatePrivateKey() @@ -133,6 +144,9 @@ class TunnelEditViewController: NSViewController { hasErrorObservationToken = textView.observe(\.hasError) { [weak saveButton] textView, _ in saveButton?.isEnabled = !textView.hasError } + 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)! @@ -149,6 +163,9 @@ class TunnelEditViewController: NSViewController { discardButton.target = self discardButton.action = #selector(handleDiscardAction) + excludePrivateIPsCheckbox.target = self + excludePrivateIPsCheckbox.action = #selector(excludePrivateIPsCheckboxToggled(sender:)) + let margin: CGFloat = 20 let internalSpacing: CGFloat = 10 @@ -159,6 +176,7 @@ class TunnelEditViewController: NSViewController { let buttonRowStackView = NSStackView() buttonRowStackView.setViews([discardButton, saveButton], in: .trailing) + buttonRowStackView.addView(excludePrivateIPsCheckbox, in: .leading) buttonRowStackView.orientation = .horizontal buttonRowStackView.spacing = internalSpacing @@ -237,4 +255,27 @@ class TunnelEditViewController: NSViewController { delegate?.tunnelEditingCancelled() dismiss(self) } + + func updateExcludePrivateIPsVisibility(singlePeerAllowedIPs: [String]?) { + let shouldAllowExcludePrivateIPsControl: Bool + let excludePrivateIPsValue: Bool + if let singlePeerAllowedIPs = singlePeerAllowedIPs { + (shouldAllowExcludePrivateIPsControl, excludePrivateIPsValue) = TunnelViewModel.PeerData.excludePrivateIPsFieldStates(isSinglePeer: true, allowedIPs: Set(singlePeerAllowedIPs)) + } else { + (shouldAllowExcludePrivateIPsControl, excludePrivateIPsValue) = TunnelViewModel.PeerData.excludePrivateIPsFieldStates(isSinglePeer: false, allowedIPs: Set()) + } + excludePrivateIPsCheckbox.isHidden = !shouldAllowExcludePrivateIPsControl + excludePrivateIPsCheckbox.state = excludePrivateIPsValue ? .on : .off + } + + @objc func excludePrivateIPsCheckboxToggled(sender: AnyObject?) { + guard let excludePrivateIPsCheckbox = sender as? NSButton else { return } + guard let tunnelConfiguration = try? TunnelConfiguration(fromWgQuickConfig: textView.string, called: nameRow.value) else { return } + let isOn = excludePrivateIPsCheckbox.state == .on + let tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnelConfiguration) + tunnelViewModel.peersData.first?.excludePrivateIPsValueChanged(isOn: isOn, dnsServers: tunnelViewModel.interfaceData[.dns]) + if let modifiedConfig = tunnelViewModel.asWgQuickConfig() { + textView.setConfText(modifiedConfig) + } + } }