diff --git a/Passepartout.xcodeproj/project.pbxproj b/Passepartout.xcodeproj/project.pbxproj index c3e257ab..9e5cbf9f 100644 --- a/Passepartout.xcodeproj/project.pbxproj +++ b/Passepartout.xcodeproj/project.pbxproj @@ -21,6 +21,8 @@ 0E1D72B4213C118500BA1586 /* ConfigurationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1D72B3213C118500BA1586 /* ConfigurationViewController.swift */; }; 0E24273A225950450064A1A3 /* About.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E24273C225950450064A1A3 /* About.storyboard */; }; 0E242742225956AC0064A1A3 /* DonationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E242741225956AC0064A1A3 /* DonationViewController.swift */; }; + 0E294AA125AE2B0A00CB4908 /* Descriptible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E294A8225AE29D100CB4908 /* Descriptible.swift */; }; + 0E294AA225AE2B0B00CB4908 /* Descriptible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E294A8225AE29D100CB4908 /* Descriptible.swift */; }; 0E2AC24522EC3AC10037B4B0 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 0E2AC24422EC3AC10037B4B0 /* Settings.bundle */; }; 0E2B494020FCFF990094784C /* Theme+Titles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2B493F20FCFF990094784C /* Theme+Titles.swift */; }; 0E2EB063236D8E1E0079DB53 /* AppConstants+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2EB062236D8E1E0079DB53 /* AppConstants+App.swift */; }; @@ -367,6 +369,7 @@ 0E23B4A12298559800304C30 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; 0E24273B225950450064A1A3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/About.storyboard; sourceTree = ""; }; 0E242741225956AC0064A1A3 /* DonationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DonationViewController.swift; sourceTree = ""; }; + 0E294A8225AE29D100CB4908 /* Descriptible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Descriptible.swift; sourceTree = ""; }; 0E2AC24422EC3AC10037B4B0 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = ""; }; 0E2B493F20FCFF990094784C /* Theme+Titles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+Titles.swift"; sourceTree = ""; }; 0E2B494120FD16540094784C /* TransientStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransientStore.swift; sourceTree = ""; }; @@ -969,6 +972,7 @@ children = ( 0E57F63A20C83FC5008323CF /* iOS */, 0E569F58259F41690022DFB8 /* macOS */, + 0E294A8225AE29D100CB4908 /* Descriptible.swift */, ); path = App; sourceTree = ""; @@ -1889,6 +1893,7 @@ 0E520320259F58BF00CBAB56 /* AppDelegate.swift in Sources */, 0E520344259F58FE00CBAB56 /* TrustedNetworksAddViewController.swift in Sources */, 0E52033A259F58F500CBAB56 /* AccountViewController.swift in Sources */, + 0E294AA225AE2B0B00CB4908 /* Descriptible.swift in Sources */, 0E520356259F590600CBAB56 /* PreferencesGeneralViewController.swift in Sources */, 0E520348259F58FE00CBAB56 /* MTUViewController.swift in Sources */, 0E52037E259F593B00CBAB56 /* SwiftGen+Segues.swift in Sources */, @@ -1986,6 +1991,7 @@ 0ECC60DE2256B68A0020BEAC /* SwiftGen+Assets.swift in Sources */, 0E242742225956AC0064A1A3 /* DonationViewController.swift in Sources */, 0ED38AEC2141260D0004D387 /* ConfigurationModificationDelegate.swift in Sources */, + 0E294AA125AE2B0A00CB4908 /* Descriptible.swift in Sources */, 0E776642229D0DAE0023FA76 /* Intents.intentdefinition in Sources */, 0ECEE45020E1182E00A6BB43 /* Theme+Cells.swift in Sources */, 0E6268942369AD0600355F75 /* PurchaseTableViewCell.swift in Sources */, diff --git a/Passepartout/App/Descriptible.swift b/Passepartout/App/Descriptible.swift new file mode 100644 index 00000000..ca90a8de --- /dev/null +++ b/Passepartout/App/Descriptible.swift @@ -0,0 +1,131 @@ +// +// Descriptible.swift +// Passepartout +// +// Created by Davide De Rosa on 1/12/21. +// Copyright (c) 2021 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import TunnelKit + +public protocol UIDescriptible { + var uiDescription: String { get } +} + +extension String: UIDescriptible { + public var uiDescription: String { + return description + } +} + +extension OpenVPN.Cipher: UIDescriptible { + public var uiDescription: String { + return description + } +} + +extension OpenVPN.Digest: UIDescriptible { + public var uiDescription: String { + return description + } +} + +extension OpenVPN.CompressionFraming: UIDescriptible { + public var uiDescription: String { + let V = L10n.Core.Configuration.Cells.self + switch self { + case .disabled: + return L10n.Core.Global.Values.disabled + + case .compLZO: + return V.CompressionFraming.Value.lzo + + case .compress: + return V.CompressionFraming.Value.compress + } + } +} + +extension OpenVPN.CompressionAlgorithm: UIDescriptible { + public var uiDescription: String { + let V = L10n.Core.Configuration.Cells.self + switch self { + case .disabled: + return L10n.Core.Global.Values.disabled + + case .LZO: + return V.CompressionAlgorithm.Value.lzo + + case .other: + return V.CompressionAlgorithm.Value.other + } + } +} + +extension OpenVPN.ConfigurationBuilder { + public var uiDescriptionForTLSWrap: String { + let V = L10n.Core.Configuration.Cells.self + if let strategy = tlsWrap?.strategy { + switch strategy { + case .auth: + return V.TlsWrapping.Value.auth + + case .crypt: + return V.TlsWrapping.Value.crypt + } + } else { + return L10n.Core.Global.Values.disabled + } + } + + public var uiDescriptionForKeepAlive: String { + let V = L10n.Core.Configuration.Cells.self + if let keepAlive = keepAliveInterval, keepAlive > 0 { + return V.KeepAlive.Value.seconds(Int(keepAlive)) + } else { + return L10n.Core.Global.Values.disabled + } + } + + public var uiDescriptionForClientCertificate: String { + let V = L10n.Core.Configuration.Cells.Client.Value.self + return (clientCertificate != nil) ? V.enabled : V.disabled + } + + public var uiDescriptionForEKU: String { + let V = L10n.Core.Global.Values.self + return (checksEKU ?? false) ? V.enabled : V.disabled + } + + public var uiDescriptionForRenegotiatesAfter: String { + let V = L10n.Core.Configuration.Cells.self + if let reneg = renegotiatesAfter, reneg > 0 { + return V.RenegotiationSeconds.Value.after(TimeInterval(reneg).localized) + } else { + return L10n.Core.Global.Values.disabled + } + } + + public var uiDescriptionForRandomizeEndpoint: String { + let V = L10n.Core.Global.Values.self + return (randomizeEndpoint ?? false) ? V.enabled : V.disabled + } +} diff --git a/Passepartout/App/iOS/Scenes/ConfigurationViewController.swift b/Passepartout/App/iOS/Scenes/ConfigurationViewController.swift index b8da57ec..fd527b12 100644 --- a/Passepartout/App/iOS/Scenes/ConfigurationViewController.swift +++ b/Passepartout/App/iOS/Scenes/ConfigurationViewController.swift @@ -62,9 +62,6 @@ class ConfigurationViewController: UIViewController, StrongTableHost { model.add(.reset) model.setHeader("", forSection: .reset) } - if !isServerPushed { - model.add(.tls) - } // headers model.setHeader(L10n.Core.Configuration.Sections.Communication.header, forSection: .communication) @@ -105,6 +102,10 @@ class ConfigurationViewController: UIViewController, StrongTableHost { model.set(rows, forSection: .compression) } + if !isServerPushed { + model.add(.tls) + } + rows = [] if let _ = configuration.keepAliveInterval { rows.append(.keepAlive) @@ -122,6 +123,9 @@ class ConfigurationViewController: UIViewController, StrongTableHost { } else { model.add(.communication) model.add(.compression) + if !isServerPushed { + model.add(.tls) + } model.add(.other) model.set([.cipher, .digest], forSection: .communication) model.set([.compressionFraming, .compressionAlgorithm], forSection: .compression) @@ -261,82 +265,60 @@ extension ConfigurationViewController: UITableViewDataSource, UITableViewDelegat } cell.isTappable = isEditable switch row { - case .cipher: - cell.leftText = V.Cipher.caption - cell.rightText = configuration.fallbackCipher.description - - case .digest: - cell.leftText = V.Digest.caption - cell.rightText = configuration.fallbackDigest.description - - case .compressionFraming: - cell.leftText = V.CompressionFraming.caption - cell.rightText = configuration.fallbackCompressionFraming.cellDescription - - case .compressionAlgorithm: - cell.leftText = V.CompressionAlgorithm.caption - if let compressionAlgorithm = configuration.compressionAlgorithm { - cell.rightText = compressionAlgorithm.cellDescription - } else { - cell.rightText = L10n.Core.Global.Values.disabled - } - cell.isTappable = (configuration.compressionFraming != .disabled) - case .resetOriginal: cell.leftText = V.ResetOriginal.caption cell.applyAction(.current) + case .cipher: + cell.leftText = V.Cipher.caption + cell.rightText = configuration.fallbackCipher.uiDescription + + case .digest: + cell.leftText = V.Digest.caption + cell.rightText = configuration.fallbackDigest.uiDescription + + case .compressionFraming: + cell.leftText = V.CompressionFraming.caption + cell.rightText = configuration.fallbackCompressionFraming.uiDescription + + case .compressionAlgorithm: + cell.leftText = V.CompressionAlgorithm.caption + cell.rightText = configuration.fallbackCompressionAlgorithm.uiDescription + cell.isTappable = (configuration.compressionFraming != .disabled) + case .client: cell.leftText = V.Client.caption - cell.rightText = (configuration.clientCertificate != nil) ? V.Client.Value.enabled : V.Client.Value.disabled + cell.rightText = configuration.uiDescriptionForClientCertificate cell.accessoryType = .none cell.isTappable = false case .tlsWrapping: cell.leftText = V.TlsWrapping.caption - if let strategy = configuration.tlsWrap?.strategy { - switch strategy { - case .auth: - cell.rightText = V.TlsWrapping.Value.auth - - case .crypt: - cell.rightText = V.TlsWrapping.Value.crypt - } - } else { - cell.rightText = L10n.Core.Global.Values.disabled - } + cell.rightText = configuration.uiDescriptionForTLSWrap cell.accessoryType = .none cell.isTappable = false case .eku: cell.leftText = V.Eku.caption - cell.rightText = (configuration.checksEKU ?? false) ? L10n.Core.Global.Values.enabled : L10n.Core.Global.Values.disabled + cell.rightText = configuration.uiDescriptionForEKU cell.accessoryType = .none cell.isTappable = false case .keepAlive: cell.leftText = V.KeepAlive.caption - if let keepAlive = configuration.keepAliveInterval, keepAlive > 0 { - cell.rightText = V.KeepAlive.Value.seconds(Int(keepAlive)) - } else { - cell.rightText = L10n.Core.Global.Values.disabled - } + cell.rightText = configuration.uiDescriptionForKeepAlive cell.accessoryType = .none cell.isTappable = false case .renegSeconds: cell.leftText = V.RenegotiationSeconds.caption - if let reneg = configuration.renegotiatesAfter, reneg > 0 { - cell.rightText = V.RenegotiationSeconds.Value.after(TimeInterval(reneg).localized) - } else { - cell.rightText = L10n.Core.Global.Values.disabled - } + cell.rightText = configuration.uiDescriptionForRenegotiatesAfter cell.accessoryType = .none cell.isTappable = false case .randomEndpoint: cell.leftText = V.RandomEndpoint.caption - cell.rightText = (configuration.randomizeEndpoint ?? false) ? L10n.Core.Global.Values.enabled : L10n.Core.Global.Values.disabled + cell.rightText = configuration.uiDescriptionForRandomizeEndpoint cell.accessoryType = .none cell.isTappable = false } @@ -366,7 +348,7 @@ extension ConfigurationViewController: UITableViewDataSource, UITableViewDelegat vc.title = settingCell?.leftText vc.options = options vc.selectedOption = configuration.cipher - vc.descriptionBlock = { $0.description } + vc.descriptionBlock = { $0.uiDescription } vc.selectionBlock = { [weak self] in self?.configuration.cipher = $0 self?.popAndCheckRefresh() @@ -379,7 +361,7 @@ extension ConfigurationViewController: UITableViewDataSource, UITableViewDelegat vc.title = settingCell?.leftText vc.options = OpenVPN.Digest.available vc.selectedOption = configuration.digest - vc.descriptionBlock = { $0.description } + vc.descriptionBlock = { $0.uiDescription } vc.selectionBlock = { [weak self] in self?.configuration.digest = $0 self?.popAndCheckRefresh() @@ -392,7 +374,7 @@ extension ConfigurationViewController: UITableViewDataSource, UITableViewDelegat vc.title = settingCell?.leftText vc.options = OpenVPN.CompressionFraming.available vc.selectedOption = configuration.compressionFraming ?? .disabled - vc.descriptionBlock = { $0.cellDescription } + vc.descriptionBlock = { $0.uiDescription } vc.selectionBlock = { [weak self] in self?.configuration.compressionFraming = $0 if $0 == .disabled { @@ -412,7 +394,7 @@ extension ConfigurationViewController: UITableViewDataSource, UITableViewDelegat vc.title = settingCell?.leftText vc.options = OpenVPN.CompressionAlgorithm.available vc.selectedOption = configuration.compressionAlgorithm ?? .disabled - vc.descriptionBlock = { $0.cellDescription } + vc.descriptionBlock = { $0.uiDescription } vc.selectionBlock = { [weak self] in self?.configuration.compressionAlgorithm = $0 self?.popAndCheckRefresh() @@ -438,37 +420,3 @@ extension ConfigurationViewController: UITableViewDataSource, UITableViewDelegat delegate?.configuration(didUpdate: configuration.build()) } } - -// MARK: - - -private extension OpenVPN.CompressionFraming { - var cellDescription: String { - let V = L10n.Core.Configuration.Cells.self - switch self { - case .disabled: - return L10n.Core.Global.Values.disabled - - case .compLZO: - return V.CompressionFraming.Value.lzo - - case .compress: - return V.CompressionFraming.Value.compress - } - } -} - -private extension OpenVPN.CompressionAlgorithm { - var cellDescription: String { - let V = L10n.Core.Configuration.Cells.self - switch self { - case .disabled: - return L10n.Core.Global.Values.disabled - - case .LZO: - return V.CompressionAlgorithm.Value.lzo - - case .other: - return V.CompressionAlgorithm.Value.other - } - } -} diff --git a/Passepartout/App/macOS/Base.lproj/Service.storyboard b/Passepartout/App/macOS/Base.lproj/Service.storyboard index aeff6219..e8a74ebc 100644 --- a/Passepartout/App/macOS/Base.lproj/Service.storyboard +++ b/Passepartout/App/macOS/Base.lproj/Service.storyboard @@ -11,14 +11,14 @@ - + - + - + @@ -26,7 +26,7 @@ - + @@ -42,353 +42,75 @@ - - - - + + + + - - - - - - - - + + + + + + + + + + + + + + + - - - - - + + + + + + + + + - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - + + - + - - - - + @@ -400,32 +122,9 @@ - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/Passepartout/App/macOS/Global/Macros.swift b/Passepartout/App/macOS/Global/Macros.swift index 5fd8e66c..0f04ae99 100644 --- a/Passepartout/App/macOS/Global/Macros.swift +++ b/Passepartout/App/macOS/Global/Macros.swift @@ -128,6 +128,18 @@ extension NSImage { } } +extension NSMenu { + static func withDescriptibles(_ list: [UIDescriptible]) -> NSMenu { + let menu = NSMenu() + for o in list { + let item = NSMenuItem(title: o.uiDescription, action: nil, keyEquivalent: "") + item.representedObject = o + menu.addItem(item) + } + return menu + } +} + extension String { var asCaption: String { return "\(self):" diff --git a/Passepartout/App/macOS/Scenes/Service/Customization/ConfigurationViewController.swift b/Passepartout/App/macOS/Scenes/Service/Customization/ConfigurationViewController.swift index 61a24edb..7068bfcb 100644 --- a/Passepartout/App/macOS/Scenes/Service/Customization/ConfigurationViewController.swift +++ b/Passepartout/App/macOS/Scenes/Service/Customization/ConfigurationViewController.swift @@ -28,57 +28,17 @@ import PassepartoutCore import TunnelKit class ConfigurationViewController: NSViewController, ProfileCustomization { + private struct Columns { + static let name = NSUserInterfaceItemIdentifier("Name") + + static let value = NSUserInterfaceItemIdentifier("Value") + } + @IBOutlet private weak var labelPresetCaption: NSTextField! @IBOutlet private weak var popupPreset: NSPopUpButton! - @IBOutlet private weak var boxCommunication: NSBox! - - @IBOutlet private weak var labelCipherCaption: NSTextField! - - @IBOutlet private weak var popupCipher: NSPopUpButton! - - @IBOutlet private weak var labelDigestCaption: NSTextField! - - @IBOutlet private weak var popupDigest: NSPopUpButton! - - @IBOutlet private weak var boxCompression: NSBox! - - @IBOutlet private weak var labelCompressionFramingCaption: NSTextField! - - @IBOutlet private weak var popupCompressionFraming: NSPopUpButton! - - @IBOutlet private weak var labelCompressionAlgorithmCaption: NSTextField! - - @IBOutlet private weak var popupCompressionAlgorithm: NSPopUpButton! - - @IBOutlet private weak var boxTLS: NSBox! - - @IBOutlet private weak var labelClientCertificateCaption: NSTextField! - - @IBOutlet private weak var labelClientCertificate: NSTextField! - - @IBOutlet private weak var labelWrappingCaption: NSTextField! - - @IBOutlet private weak var labelWrapping: NSTextField! - - @IBOutlet private weak var labelExtendedVerificationCaption: NSTextField! - - @IBOutlet private weak var labelExtendedVerification: NSTextField! - - @IBOutlet private weak var boxOther: NSBox! - - @IBOutlet private weak var labelKeepAliveCaption: NSTextField! - - @IBOutlet private weak var labelKeepAlive: NSTextField! - - @IBOutlet private weak var labelRenegotiationCaption: NSTextField! - - @IBOutlet private weak var labelRenegotiation: NSTextField! - - @IBOutlet private weak var labelRandomizeEndpointCaption: NSTextField! - - @IBOutlet private weak var labelRandomizeEndpoint: NSTextField! + @IBOutlet private weak var tableConfiguration: NSTableView! private lazy var allPresets: [InfrastructurePreset] = { guard let providerProfile = profile as? ProviderConnectionProfile else { @@ -100,6 +60,21 @@ class ConfigurationViewController: NSViewController, ProfileCustomization { } private var configuration = OpenVPN.ConfigurationBuilder() + + private let rows: [RowType] = [ + .cipher, + .digest, + .compressionFraming, + .compressionAlgorithm, + .client, + .tlsWrapping, + .eku, + .keepAlive, + .renegSeconds, + .randomEndpoint + ] + + private var rowMenus: [RowType: NSMenu] = [:] // MARK: ProfileCustomization @@ -118,119 +93,62 @@ class ConfigurationViewController: NSViewController, ProfileCustomization { override func viewDidLoad() { super.viewDidLoad() - let V = L10n.Core.Configuration.Cells.self - labelPresetCaption.stringValue = L10n.Core.Service.Cells.Provider.Preset.caption.asCaption popupPreset.removeAllItems() if !allPresets.isEmpty { for preset in allPresets { popupPreset.addItem(withTitle: preset.name) } - popupCipher.isEnabled = false - popupDigest.isEnabled = false - popupCompressionFraming.isEnabled = false - popupCompressionAlgorithm.isEnabled = false } else { popupPreset.addItem(withTitle: L10n.Core.Global.Values.default) popupPreset.isEnabled = false } - boxCommunication.title = L10n.Core.Configuration.Sections.Communication.header - boxCompression.title = L10n.Core.Configuration.Sections.Compression.header - boxTLS.title = L10n.Core.Configuration.Sections.Tls.header - boxOther.title = L10n.Core.Configuration.Sections.Other.header - - labelCipherCaption.stringValue = V.Cipher.caption.asCaption - labelDigestCaption.stringValue = V.Digest.caption.asCaption - labelCompressionFramingCaption.stringValue = V.CompressionFraming.caption.asCaption - labelCompressionAlgorithmCaption.stringValue = V.CompressionAlgorithm.caption.asCaption - labelClientCertificateCaption.stringValue = V.Client.caption.asCaption - labelWrappingCaption.stringValue = V.TlsWrapping.caption.asCaption - labelExtendedVerificationCaption.stringValue = V.Eku.caption.asCaption - labelKeepAliveCaption.stringValue = V.KeepAlive.caption.asCaption - labelRenegotiationCaption.stringValue = V.RenegotiationSeconds.caption.asCaption - labelRandomizeEndpointCaption.stringValue = V.RandomEndpoint.caption.asCaption - - popupCipher.removeAllItems() - popupDigest.removeAllItems() - popupCompressionFraming.removeAllItems() - popupCompressionAlgorithm.removeAllItems() - - var cipherOptions: [OpenVPN.Cipher] = configuration.dataCiphers ?? [] - if !cipherOptions.isEmpty { - if let cipher = configuration.cipher, !cipherOptions.contains(cipher) { - cipherOptions.append(cipher) - } - } else { - cipherOptions.append(contentsOf: OpenVPN.Cipher.available) - } - for cipher in cipherOptions { - popupCipher.addItem(withTitle: cipher.rawValue) - } - - for digest in OpenVPN.Digest.available { - popupDigest.addItem(withTitle: digest.rawValue) - } - for framing in OpenVPN.CompressionFraming.available { - popupCompressionFraming.addItem(withTitle: framing.itemDescription) - } - for algorithm in OpenVPN.CompressionAlgorithm.available { - popupCompressionAlgorithm.addItem(withTitle: algorithm.itemDescription) - } - reloadModel() } private func reloadModel() { - let V = L10n.Core.Configuration.Cells.self - if let index = allPresets.firstIndex(where: { $0.id == preset?.id }) { popupPreset.selectItem(at: index) } - if let index = OpenVPN.Cipher.available.firstIndex(of: configuration.fallbackCipher) { - popupCipher.selectItem(at: index) - } - if let index = OpenVPN.Digest.available.firstIndex(of: configuration.fallbackDigest) { - popupDigest.selectItem(at: index) - } - if let index = OpenVPN.CompressionFraming.available.firstIndex(of: configuration.compressionFraming ?? .disabled) { - popupCompressionFraming.selectItem(at: index) - } - if let index = OpenVPN.CompressionAlgorithm.available.firstIndex(of: configuration.compressionAlgorithm ?? .disabled) { - popupCompressionAlgorithm.selectItem(at: index) + var availableCiphers: [OpenVPN.Cipher] + let availableDigests: [OpenVPN.Digest] + let availableCF: [OpenVPN.CompressionFraming] + let availableCA: [OpenVPN.CompressionAlgorithm] + if let _ = profile as? HostConnectionProfile { + availableCiphers = configuration.dataCiphers ?? [] + if !availableCiphers.isEmpty { + if let cipher = configuration.cipher, !availableCiphers.contains(cipher) { + availableCiphers.append(cipher) + } + } else { + availableCiphers.append(contentsOf: OpenVPN.Cipher.available) + } + availableDigests = OpenVPN.Digest.available + availableCF = OpenVPN.CompressionFraming.available + availableCA = OpenVPN.CompressionAlgorithm.available + } else { + availableCiphers = [configuration.fallbackCipher] + availableDigests = [configuration.fallbackDigest] + availableCF = [configuration.fallbackCompressionFraming] + availableCA = [configuration.fallbackCompressionAlgorithm] } - // enforce item constraints - selectCompressionFraming(nil) - selectCompressionAlgorithm(nil) - - labelClientCertificate.stringValue = (configuration.clientCertificate != nil) ? V.Client.Value.enabled : V.Client.Value.disabled - if let strategy = configuration.tlsWrap?.strategy { - switch strategy { - case .auth: - labelWrapping.stringValue = V.TlsWrapping.Value.auth - - case .crypt: - labelWrapping.stringValue = V.TlsWrapping.Value.crypt - } - } else { - labelWrapping.stringValue = L10n.Core.Global.Values.disabled - } - labelExtendedVerification.stringValue = (configuration.checksEKU ?? false) ? L10n.Core.Global.Values.enabled : L10n.Core.Global.Values.disabled - - if let keepAlive = configuration.keepAliveInterval, keepAlive > 0 { - labelKeepAlive.stringValue = V.KeepAlive.Value.seconds(Int(keepAlive)) - } else { - labelKeepAlive.stringValue = L10n.Core.Global.Values.disabled - } - if let reneg = configuration.renegotiatesAfter, reneg > 0 { - labelRenegotiation.stringValue = V.RenegotiationSeconds.Value.after(TimeInterval(reneg).localized) - } else { - labelRenegotiation.stringValue = L10n.Core.Global.Values.disabled - } - labelRandomizeEndpoint.stringValue = (configuration.randomizeEndpoint ?? false) ? L10n.Core.Global.Values.enabled : L10n.Core.Global.Values.disabled + // editable + rowMenus[.cipher] = NSMenu.withDescriptibles(availableCiphers) + rowMenus[.digest] = NSMenu.withDescriptibles(availableDigests) + rowMenus[.compressionFraming] = NSMenu.withDescriptibles(availableCF) + rowMenus[.compressionAlgorithm] = NSMenu.withDescriptibles(availableCA) + + // single-option menus (unselectable) + rowMenus[.client] = NSMenu.withDescriptibles([configuration.uiDescriptionForClientCertificate]) + rowMenus[.tlsWrapping] = NSMenu.withDescriptibles([configuration.uiDescriptionForTLSWrap]) + rowMenus[.eku] = NSMenu.withDescriptibles([configuration.uiDescriptionForEKU]) + rowMenus[.keepAlive] = NSMenu.withDescriptibles([configuration.uiDescriptionForKeepAlive]) + rowMenus[.renegSeconds] = NSMenu.withDescriptibles([configuration.uiDescriptionForRenegotiatesAfter]) + rowMenus[.randomEndpoint] = NSMenu.withDescriptibles([configuration.uiDescriptionForRandomizeEndpoint]) } - + // MARK: Actions @IBAction private func selectPreset(_ sender: Any?) { @@ -238,73 +156,153 @@ class ConfigurationViewController: NSViewController, ProfileCustomization { self.preset = preset reloadModel() delegate?.profileCustomization(self, didUpdatePreset: preset) + tableConfiguration.reloadData() } +} - @IBAction private func selectCipher(_ sender: Any?) { - configuration.cipher = OpenVPN.Cipher.available[popupCipher.indexOfSelectedItem] - delegate?.profileCustomization(self, didUpdateConfiguration: configuration) - } +extension ConfigurationViewController: NSTableViewDataSource, NSTableViewDelegate { + enum RowType: Int { +// case resetOriginal - @IBAction private func selectDigest(_ sender: Any?) { - configuration.digest = OpenVPN.Digest.available[popupDigest.indexOfSelectedItem] - delegate?.profileCustomization(self, didUpdateConfiguration: configuration) + case cipher + + case digest + + case compressionFraming + + case compressionAlgorithm + + case client + + case tlsWrapping + + case eku + + case keepAlive + + case renegSeconds + + case randomEndpoint } - @IBAction private func selectCompressionFraming(_ sender: Any?) { - - // if framing is disabled, disable algorithm - if popupCompressionFraming.indexOfSelectedItem == 0 { - popupCompressionAlgorithm.selectItem(at: 0) - } - - configuration.compressionFraming = OpenVPN.CompressionFraming.available[popupCompressionFraming.indexOfSelectedItem] - configuration.compressionAlgorithm = OpenVPN.CompressionAlgorithm.available[popupCompressionAlgorithm.indexOfSelectedItem] - delegate?.profileCustomization(self, didUpdateConfiguration: configuration) + func numberOfRows(in tableView: NSTableView) -> Int { + return rows.count } - - @IBAction private func selectCompressionAlgorithm(_ sender: Any?) { - - // if framing is disabled and algorithm is not disabled, enable --comp-lzo framing - if popupCompressionFraming.indexOfSelectedItem == 0 && popupCompressionAlgorithm.indexOfSelectedItem != 0 { - popupCompressionFraming.selectItem(at: 1) - } - - configuration.compressionFraming = OpenVPN.CompressionFraming.available[popupCompressionFraming.indexOfSelectedItem] - configuration.compressionAlgorithm = OpenVPN.CompressionAlgorithm.available[popupCompressionAlgorithm.indexOfSelectedItem] - delegate?.profileCustomization(self, didUpdateConfiguration: configuration) - } -} - -// MARK: - - -private extension OpenVPN.CompressionFraming { - var itemDescription: String { + + func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { let V = L10n.Core.Configuration.Cells.self - switch self { - case .disabled: - return L10n.Core.Global.Values.disabled - - case .compLZO: - return V.CompressionFraming.Value.lzo - - case .compress: - return V.CompressionFraming.Value.compress - } - } -} + let rowObject = rows[row] -private extension OpenVPN.CompressionAlgorithm { - var itemDescription: String { - let V = L10n.Core.Configuration.Cells.self - switch self { - case .disabled: - return L10n.Core.Global.Values.disabled + switch tableColumn?.identifier { + case Columns.name: + switch rowObject { + case .cipher: + return V.Cipher.caption + + case .digest: + return V.Digest.caption + + case .compressionFraming: + return V.CompressionFraming.caption + + case .compressionAlgorithm: + return V.CompressionAlgorithm.caption + + case .client: + return V.Client.caption + + case .tlsWrapping: + return V.TlsWrapping.caption + + case .eku: + return V.Eku.caption + + case .keepAlive: + return V.KeepAlive.caption + + case .renegSeconds: + return V.RenegotiationSeconds.caption - case .LZO: - return V.CompressionAlgorithm.Value.lzo + case .randomEndpoint: + return V.RandomEndpoint.caption + } - case .other: - return V.CompressionAlgorithm.Value.other + case Columns.value: + guard let menu = rowMenus[rowObject], let cell = tableColumn?.dataCell(forRow: row) as? NSPopUpButtonCell else { + return nil + } + cell.menu = menu + cell.imageDimsWhenDisabled = false + if menu.numberOfItems > 1 { + cell.arrowPosition = .arrowAtBottom + cell.isEnabled = true + } else { + cell.arrowPosition = .noArrow + cell.isEnabled = false + } + switch rowObject { + case .cipher: + return menu.indexOfItem(withRepresentedObject: configuration.fallbackCipher) + + case .digest: + return menu.indexOfItem(withRepresentedObject: configuration.fallbackDigest) + + case .compressionFraming: + return menu.indexOfItem(withRepresentedObject: configuration.fallbackCompressionFraming) + + case .compressionAlgorithm: + return menu.indexOfItem(withRepresentedObject: configuration.fallbackCompressionAlgorithm) + + default: + return 0 + } + + default: + break + } + return nil + } + + func tableView(_ tableView: NSTableView, setObjectValue object: Any?, for tableColumn: NSTableColumn?, row: Int) { + switch tableColumn?.identifier { + case Columns.value: + let rowObject = rows[row] + guard let menu = rowMenus[rowObject], let optionIndex = object as? Int else { + return + } + let optionObject = menu.item(at: optionIndex)?.representedObject + switch rowObject { + case .cipher: + configuration.cipher = optionObject as? OpenVPN.Cipher + + case .digest: + configuration.digest = optionObject as? OpenVPN.Digest + + case .compressionFraming: + guard let option = optionObject as? OpenVPN.CompressionFraming else { + return + } + configuration.compressionFraming = option + if option == .disabled { + configuration.compressionAlgorithm = .disabled + } + + case .compressionAlgorithm: + guard let option = optionObject as? OpenVPN.CompressionAlgorithm else { + return + } + if configuration.compressionFraming == .disabled && option != .disabled { + configuration.compressionFraming = .compLZO + } + configuration.compressionAlgorithm = option + + default: + break + } + delegate?.profileCustomization(self, didUpdateConfiguration: configuration) + + default: + break } } } diff --git a/Podfile b/Podfile index 5eb46c74..e693b62c 100644 --- a/Podfile +++ b/Podfile @@ -8,7 +8,7 @@ $tunnelkit_specs = ['Protocols/OpenVPN', 'Extra/LZO'] def shared_pods #pod_version $tunnelkit_name, $tunnelkit_specs, '~> 3.1.0' - pod_git $tunnelkit_name, $tunnelkit_specs, 'c15d6f5' + pod_git $tunnelkit_name, $tunnelkit_specs, 'e388842' #pod_path $tunnelkit_name, $tunnelkit_specs, '..' pod 'SSZipArchive' pod 'Kvitto', :git => 'https://github.com/keeshux/Kvitto', :branch => 'enable-macos-spec' diff --git a/Podfile.lock b/Podfile.lock index 2ce3c9d0..3fd636cc 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -52,8 +52,8 @@ DEPENDENCIES: - Kvitto (from `https://github.com/keeshux/Kvitto`, branch `enable-macos-spec`) - MBProgressHUD - SSZipArchive - - TunnelKit/Extra/LZO (from `https://github.com/passepartoutvpn/tunnelkit`, commit `c15d6f5`) - - TunnelKit/Protocols/OpenVPN (from `https://github.com/passepartoutvpn/tunnelkit`, commit `c15d6f5`) + - TunnelKit/Extra/LZO (from `https://github.com/passepartoutvpn/tunnelkit`, commit `e388842`) + - TunnelKit/Protocols/OpenVPN (from `https://github.com/passepartoutvpn/tunnelkit`, commit `e388842`) SPEC REPOS: https://github.com/cocoapods/specs.git: @@ -71,7 +71,7 @@ EXTERNAL SOURCES: :branch: enable-macos-spec :git: https://github.com/keeshux/Kvitto TunnelKit: - :commit: c15d6f5 + :commit: e388842 :git: https://github.com/passepartoutvpn/tunnelkit CHECKOUT OPTIONS: @@ -82,7 +82,7 @@ CHECKOUT OPTIONS: :commit: e263fcd1f40a6a482a0f1e424ba98009c4ad2b96 :git: https://github.com/keeshux/Kvitto TunnelKit: - :commit: c15d6f5 + :commit: e388842 :git: https://github.com/passepartoutvpn/tunnelkit SPEC CHECKSUMS: @@ -95,6 +95,6 @@ SPEC CHECKSUMS: SwiftyBeaver: 2e8acd6fc90c6d0a27055867a290794926d57c02 TunnelKit: 2a6aadea2d772a2760b153aee27d1c334c9ca6db -PODFILE CHECKSUM: 9751a898e23369673b1dfb0c7c7fde9834a55d53 +PODFILE CHECKSUM: d6449ccbaad5d2ca50f6a27a651baaa17ef9db1a COCOAPODS: 1.10.0