Nuke trailing spaces
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
parent
1fecd8eb6c
commit
7b9d4cb9e3
|
@ -7,17 +7,17 @@ import NetworkExtension
|
||||||
|
|
||||||
protocol LegacyModel: Decodable {
|
protocol LegacyModel: Decodable {
|
||||||
associatedtype Model
|
associatedtype Model
|
||||||
|
|
||||||
var migrated: Model { get }
|
var migrated: Model { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LegacyDNSServer: LegacyModel {
|
struct LegacyDNSServer: LegacyModel {
|
||||||
let address: IPAddress
|
let address: IPAddress
|
||||||
|
|
||||||
var migrated: DNSServer {
|
var migrated: DNSServer {
|
||||||
return DNSServer(address: address)
|
return DNSServer(address: address)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(from decoder: Decoder) throws {
|
init(from decoder: Decoder) throws {
|
||||||
let container = try decoder.singleValueContainer()
|
let container = try decoder.singleValueContainer()
|
||||||
var data = try container.decode(Data.self)
|
var data = try container.decode(Data.self)
|
||||||
|
@ -33,7 +33,7 @@ struct LegacyDNSServer: LegacyModel {
|
||||||
}
|
}
|
||||||
address = ipAddress
|
address = ipAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DecodingError: Error {
|
enum DecodingError: Error {
|
||||||
case invalidData
|
case invalidData
|
||||||
}
|
}
|
||||||
|
@ -48,11 +48,11 @@ extension Array where Element == LegacyDNSServer {
|
||||||
struct LegacyEndpoint: LegacyModel {
|
struct LegacyEndpoint: LegacyModel {
|
||||||
let host: Network.NWEndpoint.Host
|
let host: Network.NWEndpoint.Host
|
||||||
let port: Network.NWEndpoint.Port
|
let port: Network.NWEndpoint.Port
|
||||||
|
|
||||||
var migrated: Endpoint {
|
var migrated: Endpoint {
|
||||||
return Endpoint(host: host, port: port)
|
return Endpoint(host: host, port: port)
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
let container = try decoder.singleValueContainer()
|
let container = try decoder.singleValueContainer()
|
||||||
let endpointString = try container.decode(String.self)
|
let endpointString = try container.decode(String.self)
|
||||||
|
@ -81,7 +81,7 @@ struct LegacyEndpoint: LegacyModel {
|
||||||
host = NWEndpoint.Host(hostString)
|
host = NWEndpoint.Host(hostString)
|
||||||
port = endpointPort
|
port = endpointPort
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DecodingError: Error {
|
enum DecodingError: Error {
|
||||||
case invalidData
|
case invalidData
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,7 @@ struct LegacyInterfaceConfiguration: LegacyModel {
|
||||||
let listenPort: UInt16?
|
let listenPort: UInt16?
|
||||||
let mtu: UInt16?
|
let mtu: UInt16?
|
||||||
let dns: [LegacyDNSServer]
|
let dns: [LegacyDNSServer]
|
||||||
|
|
||||||
var migrated: InterfaceConfiguration {
|
var migrated: InterfaceConfiguration {
|
||||||
var interface = InterfaceConfiguration(name: name, privateKey: privateKey)
|
var interface = InterfaceConfiguration(name: name, privateKey: privateKey)
|
||||||
interface.addresses = addresses.migrated
|
interface.addresses = addresses.migrated
|
||||||
|
@ -108,11 +108,11 @@ struct LegacyInterfaceConfiguration: LegacyModel {
|
||||||
struct LegacyIPAddressRange: LegacyModel {
|
struct LegacyIPAddressRange: LegacyModel {
|
||||||
let address: IPAddress
|
let address: IPAddress
|
||||||
let networkPrefixLength: UInt8
|
let networkPrefixLength: UInt8
|
||||||
|
|
||||||
var migrated: IPAddressRange {
|
var migrated: IPAddressRange {
|
||||||
return IPAddressRange(address: address, networkPrefixLength: networkPrefixLength)
|
return IPAddressRange(address: address, networkPrefixLength: networkPrefixLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
let container = try decoder.singleValueContainer()
|
let container = try decoder.singleValueContainer()
|
||||||
var data = try container.decode(Data.self)
|
var data = try container.decode(Data.self)
|
||||||
|
@ -127,7 +127,7 @@ struct LegacyIPAddressRange: LegacyModel {
|
||||||
guard let ipAddress = ipAddressFromData else { throw DecodingError.invalidData }
|
guard let ipAddress = ipAddressFromData else { throw DecodingError.invalidData }
|
||||||
address = ipAddress
|
address = ipAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DecodingError: Error {
|
enum DecodingError: Error {
|
||||||
case invalidData
|
case invalidData
|
||||||
}
|
}
|
||||||
|
@ -145,7 +145,7 @@ struct LegacyPeerConfiguration: LegacyModel {
|
||||||
let allowedIPs: [LegacyIPAddressRange]
|
let allowedIPs: [LegacyIPAddressRange]
|
||||||
let endpoint: LegacyEndpoint?
|
let endpoint: LegacyEndpoint?
|
||||||
let persistentKeepAlive: UInt16?
|
let persistentKeepAlive: UInt16?
|
||||||
|
|
||||||
var migrated: PeerConfiguration {
|
var migrated: PeerConfiguration {
|
||||||
var configuration = PeerConfiguration(publicKey: publicKey)
|
var configuration = PeerConfiguration(publicKey: publicKey)
|
||||||
configuration.preSharedKey = preSharedKey
|
configuration.preSharedKey = preSharedKey
|
||||||
|
@ -165,14 +165,14 @@ extension Array where Element == LegacyPeerConfiguration {
|
||||||
final class LegacyTunnelConfiguration: LegacyModel {
|
final class LegacyTunnelConfiguration: LegacyModel {
|
||||||
let interface: LegacyInterfaceConfiguration
|
let interface: LegacyInterfaceConfiguration
|
||||||
let peers: [LegacyPeerConfiguration]
|
let peers: [LegacyPeerConfiguration]
|
||||||
|
|
||||||
var migrated: TunnelConfiguration {
|
var migrated: TunnelConfiguration {
|
||||||
return TunnelConfiguration(interface: interface.migrated, peers: peers.migrated)
|
return TunnelConfiguration(interface: interface.migrated, peers: peers.migrated)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NETunnelProviderProtocol {
|
extension NETunnelProviderProtocol {
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func migrateConfigurationIfNeeded() -> Bool {
|
func migrateConfigurationIfNeeded() -> Bool {
|
||||||
guard let configurationVersion = providerConfiguration?["tunnelConfigurationVersion"] as? Int else { return false }
|
guard let configurationVersion = providerConfiguration?["tunnelConfigurationVersion"] as? Int else { return false }
|
||||||
|
@ -183,11 +183,11 @@ extension NETunnelProviderProtocol {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private func migrateFromConfigurationV1() {
|
private func migrateFromConfigurationV1() {
|
||||||
guard let serializedTunnelConfiguration = providerConfiguration?["tunnelConfiguration"] as? Data else { return }
|
guard let serializedTunnelConfiguration = providerConfiguration?["tunnelConfiguration"] as? Data else { return }
|
||||||
guard let configuration = try? JSONDecoder().decode(LegacyTunnelConfiguration.self, from: serializedTunnelConfiguration) else { return }
|
guard let configuration = try? JSONDecoder().decode(LegacyTunnelConfiguration.self, from: serializedTunnelConfiguration) else { return }
|
||||||
providerConfiguration = [Keys.wgQuickConfig.rawValue: configuration.migrated.asWgQuickConfig()]
|
providerConfiguration = [Keys.wgQuickConfig.rawValue: configuration.migrated.asWgQuickConfig()]
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ public class Logger {
|
||||||
enum LoggerError: Error {
|
enum LoggerError: Error {
|
||||||
case openFailure
|
case openFailure
|
||||||
}
|
}
|
||||||
|
|
||||||
static var global: Logger?
|
static var global: Logger?
|
||||||
|
|
||||||
var log: OpaquePointer
|
var log: OpaquePointer
|
||||||
|
|
|
@ -6,7 +6,7 @@ import Network
|
||||||
|
|
||||||
struct DNSServer {
|
struct DNSServer {
|
||||||
let address: IPAddress
|
let address: IPAddress
|
||||||
|
|
||||||
init(address: IPAddress) {
|
init(address: IPAddress) {
|
||||||
self.address = address
|
self.address = address
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ extension DNSServer {
|
||||||
var stringRepresentation: String {
|
var stringRepresentation: String {
|
||||||
return "\(address)"
|
return "\(address)"
|
||||||
}
|
}
|
||||||
|
|
||||||
init?(from addressString: String) {
|
init?(from addressString: String) {
|
||||||
if let addr = IPv4Address(addressString) {
|
if let addr = IPv4Address(addressString) {
|
||||||
address = addr
|
address = addr
|
||||||
|
|
|
@ -7,7 +7,7 @@ import Network
|
||||||
struct Endpoint {
|
struct Endpoint {
|
||||||
let host: NWEndpoint.Host
|
let host: NWEndpoint.Host
|
||||||
let port: NWEndpoint.Port
|
let port: NWEndpoint.Port
|
||||||
|
|
||||||
init(host: NWEndpoint.Host, port: NWEndpoint.Port) {
|
init(host: NWEndpoint.Host, port: NWEndpoint.Port) {
|
||||||
self.host = host
|
self.host = host
|
||||||
self.port = port
|
self.port = port
|
||||||
|
@ -25,7 +25,7 @@ extension Endpoint {
|
||||||
return "[\(address)]:\(port)"
|
return "[\(address)]:\(port)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init?(from string: String) {
|
init?(from string: String) {
|
||||||
// Separation of host and port is based on 'parse_endpoint' function in
|
// Separation of host and port is based on 'parse_endpoint' function in
|
||||||
// https://git.zx2c4.com/WireGuard/tree/src/tools/config.c
|
// https://git.zx2c4.com/WireGuard/tree/src/tools/config.c
|
||||||
|
|
|
@ -7,7 +7,7 @@ import Network
|
||||||
struct IPAddressRange {
|
struct IPAddressRange {
|
||||||
let address: IPAddress
|
let address: IPAddress
|
||||||
var networkPrefixLength: UInt8
|
var networkPrefixLength: UInt8
|
||||||
|
|
||||||
init(address: IPAddress, networkPrefixLength: UInt8) {
|
init(address: IPAddress, networkPrefixLength: UInt8) {
|
||||||
self.address = address
|
self.address = address
|
||||||
self.networkPrefixLength = networkPrefixLength
|
self.networkPrefixLength = networkPrefixLength
|
||||||
|
@ -18,13 +18,13 @@ extension IPAddressRange {
|
||||||
var stringRepresentation: String {
|
var stringRepresentation: String {
|
||||||
return "\(address)/\(networkPrefixLength)"
|
return "\(address)/\(networkPrefixLength)"
|
||||||
}
|
}
|
||||||
|
|
||||||
init?(from string: String) {
|
init?(from string: String) {
|
||||||
guard let parsed = IPAddressRange.parseAddressString(string) else { return nil }
|
guard let parsed = IPAddressRange.parseAddressString(string) else { return nil }
|
||||||
address = parsed.0
|
address = parsed.0
|
||||||
networkPrefixLength = parsed.1
|
networkPrefixLength = parsed.1
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func parseAddressString(_ string: String) -> (IPAddress, UInt8)? {
|
private static func parseAddressString(_ string: String) -> (IPAddress, UInt8)? {
|
||||||
let endOfIPAddress = string.lastIndex(of: "/") ?? string.endIndex
|
let endOfIPAddress = string.lastIndex(of: "/") ?? string.endIndex
|
||||||
let addressString = String(string[string.startIndex ..< endOfIPAddress])
|
let addressString = String(string[string.startIndex ..< endOfIPAddress])
|
||||||
|
@ -36,7 +36,7 @@ extension IPAddressRange {
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
let maxNetworkPrefixLength: UInt8 = address is IPv4Address ? 32 : 128
|
let maxNetworkPrefixLength: UInt8 = address is IPv4Address ? 32 : 128
|
||||||
var networkPrefixLength: UInt8
|
var networkPrefixLength: UInt8
|
||||||
if endOfIPAddress < string.endIndex { // "/" was located
|
if endOfIPAddress < string.endIndex { // "/" was located
|
||||||
|
@ -48,7 +48,7 @@ extension IPAddressRange {
|
||||||
} else {
|
} else {
|
||||||
networkPrefixLength = maxNetworkPrefixLength
|
networkPrefixLength = maxNetworkPrefixLength
|
||||||
}
|
}
|
||||||
|
|
||||||
return (address, networkPrefixLength)
|
return (address, networkPrefixLength)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ struct InterfaceConfiguration {
|
||||||
var listenPort: UInt16?
|
var listenPort: UInt16?
|
||||||
var mtu: UInt16?
|
var mtu: UInt16?
|
||||||
var dns = [DNSServer]()
|
var dns = [DNSServer]()
|
||||||
|
|
||||||
init(name: String?, privateKey: Data) {
|
init(name: String?, privateKey: Data) {
|
||||||
self.name = name
|
self.name = name
|
||||||
self.privateKey = privateKey
|
self.privateKey = privateKey
|
||||||
|
|
|
@ -17,7 +17,7 @@ struct PeerConfiguration {
|
||||||
var allowedIPs = [IPAddressRange]()
|
var allowedIPs = [IPAddressRange]()
|
||||||
var endpoint: Endpoint?
|
var endpoint: Endpoint?
|
||||||
var persistentKeepAlive: UInt16?
|
var persistentKeepAlive: UInt16?
|
||||||
|
|
||||||
init(publicKey: Data) {
|
init(publicKey: Data) {
|
||||||
self.publicKey = publicKey
|
self.publicKey = publicKey
|
||||||
if publicKey.count != TunnelConfiguration.keyLength {
|
if publicKey.count != TunnelConfiguration.keyLength {
|
||||||
|
|
|
@ -6,18 +6,18 @@ import NetworkExtension
|
||||||
private var tunnelNameKey: Void?
|
private var tunnelNameKey: Void?
|
||||||
|
|
||||||
extension NETunnelProviderProtocol {
|
extension NETunnelProviderProtocol {
|
||||||
|
|
||||||
enum Keys: String {
|
enum Keys: String {
|
||||||
case wgQuickConfig = "WgQuickConfig"
|
case wgQuickConfig = "WgQuickConfig"
|
||||||
}
|
}
|
||||||
|
|
||||||
convenience init?(tunnelConfiguration: TunnelConfiguration) {
|
convenience init?(tunnelConfiguration: TunnelConfiguration) {
|
||||||
self.init()
|
self.init()
|
||||||
|
|
||||||
let appId = Bundle.main.bundleIdentifier!
|
let appId = Bundle.main.bundleIdentifier!
|
||||||
providerBundleIdentifier = "\(appId).network-extension"
|
providerBundleIdentifier = "\(appId).network-extension"
|
||||||
providerConfiguration = [Keys.wgQuickConfig.rawValue: tunnelConfiguration.asWgQuickConfig()]
|
providerConfiguration = [Keys.wgQuickConfig.rawValue: tunnelConfiguration.asWgQuickConfig()]
|
||||||
|
|
||||||
let endpoints = tunnelConfiguration.peers.compactMap { $0.endpoint }
|
let endpoints = tunnelConfiguration.peers.compactMap { $0.endpoint }
|
||||||
if endpoints.count == 1 {
|
if endpoints.count == 1 {
|
||||||
serverAddress = endpoints[0].stringRepresentation
|
serverAddress = endpoints[0].stringRepresentation
|
||||||
|
@ -26,14 +26,14 @@ extension NETunnelProviderProtocol {
|
||||||
} else {
|
} else {
|
||||||
serverAddress = "Multiple endpoints"
|
serverAddress = "Multiple endpoints"
|
||||||
}
|
}
|
||||||
|
|
||||||
username = tunnelConfiguration.interface.name
|
username = tunnelConfiguration.interface.name
|
||||||
}
|
}
|
||||||
|
|
||||||
func tunnelConfiguration(name: String?) -> TunnelConfiguration? {
|
func tunnelConfiguration(name: String?) -> TunnelConfiguration? {
|
||||||
migrateConfigurationIfNeeded()
|
migrateConfigurationIfNeeded()
|
||||||
guard let serializedConfig = providerConfiguration?[Keys.wgQuickConfig.rawValue] as? String else { return nil }
|
guard let serializedConfig = providerConfiguration?[Keys.wgQuickConfig.rawValue] as? String else { return nil }
|
||||||
return try? TunnelConfiguration(serializedConfig, name: name)
|
return try? TunnelConfiguration(serializedConfig, name: name)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension String {
|
extension String {
|
||||||
|
|
||||||
func splitToArray(separator: Character = ",", trimmingCharacters: CharacterSet? = nil) -> [String] {
|
func splitToArray(separator: Character = ",", trimmingCharacters: CharacterSet? = nil) -> [String] {
|
||||||
return split(separator: separator)
|
return split(separator: separator)
|
||||||
.map {
|
.map {
|
||||||
|
@ -15,11 +15,11 @@ extension String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Optional where Wrapped == String {
|
extension Optional where Wrapped == String {
|
||||||
|
|
||||||
func splitToArray(separator: Character = ",", trimmingCharacters: CharacterSet? = nil) -> [String] {
|
func splitToArray(separator: Character = ",", trimmingCharacters: CharacterSet? = nil) -> [String] {
|
||||||
switch self {
|
switch self {
|
||||||
case .none:
|
case .none:
|
||||||
|
@ -28,5 +28,5 @@ extension Optional where Wrapped == String {
|
||||||
return wrapped.splitToArray(separator: separator, trimmingCharacters: trimmingCharacters)
|
return wrapped.splitToArray(separator: separator, trimmingCharacters: trimmingCharacters)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,13 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension TunnelConfiguration {
|
extension TunnelConfiguration {
|
||||||
|
|
||||||
enum ParserState {
|
enum ParserState {
|
||||||
case inInterfaceSection
|
case inInterfaceSection
|
||||||
case inPeerSection
|
case inPeerSection
|
||||||
case notInASection
|
case notInASection
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ParseError: Error {
|
enum ParseError: Error {
|
||||||
case invalidLine(_ line: String.SubSequence)
|
case invalidLine(_ line: String.SubSequence)
|
||||||
case noInterface
|
case noInterface
|
||||||
|
@ -19,17 +19,17 @@ extension TunnelConfiguration {
|
||||||
case multiplePeersWithSamePublicKey
|
case multiplePeersWithSamePublicKey
|
||||||
case invalidPeer
|
case invalidPeer
|
||||||
}
|
}
|
||||||
|
|
||||||
//swiftlint:disable:next cyclomatic_complexity function_body_length
|
//swiftlint:disable:next cyclomatic_complexity function_body_length
|
||||||
convenience init(_ wgQuickConfig: String, name: String?) throws {
|
convenience init(_ wgQuickConfig: String, name: String?) throws {
|
||||||
var interfaceConfiguration: InterfaceConfiguration?
|
var interfaceConfiguration: InterfaceConfiguration?
|
||||||
var peerConfigurations = [PeerConfiguration]()
|
var peerConfigurations = [PeerConfiguration]()
|
||||||
|
|
||||||
let lines = wgQuickConfig.split(separator: "\n")
|
let lines = wgQuickConfig.split(separator: "\n")
|
||||||
|
|
||||||
var parserState = ParserState.notInASection
|
var parserState = ParserState.notInASection
|
||||||
var attributes = [String: String]()
|
var attributes = [String: String]()
|
||||||
|
|
||||||
for (lineIndex, line) in lines.enumerated() {
|
for (lineIndex, line) in lines.enumerated() {
|
||||||
var trimmedLine: String
|
var trimmedLine: String
|
||||||
if let commentRange = line.range(of: "#") {
|
if let commentRange = line.range(of: "#") {
|
||||||
|
@ -37,12 +37,12 @@ extension TunnelConfiguration {
|
||||||
} else {
|
} else {
|
||||||
trimmedLine = String(line)
|
trimmedLine = String(line)
|
||||||
}
|
}
|
||||||
|
|
||||||
trimmedLine = trimmedLine.trimmingCharacters(in: .whitespaces)
|
trimmedLine = trimmedLine.trimmingCharacters(in: .whitespaces)
|
||||||
|
|
||||||
guard !trimmedLine.isEmpty else { continue }
|
guard !trimmedLine.isEmpty else { continue }
|
||||||
let lowercasedLine = line.lowercased()
|
let lowercasedLine = line.lowercased()
|
||||||
|
|
||||||
if let equalsIndex = line.firstIndex(of: "=") {
|
if let equalsIndex = line.firstIndex(of: "=") {
|
||||||
// Line contains an attribute
|
// Line contains an attribute
|
||||||
let key = line[..<equalsIndex].trimmingCharacters(in: .whitespaces).lowercased()
|
let key = line[..<equalsIndex].trimmingCharacters(in: .whitespaces).lowercased()
|
||||||
|
@ -56,9 +56,9 @@ extension TunnelConfiguration {
|
||||||
} else if lowercasedLine != "[interface]" && lowercasedLine != "[peer]" {
|
} else if lowercasedLine != "[interface]" && lowercasedLine != "[peer]" {
|
||||||
throw ParseError.invalidLine(line)
|
throw ParseError.invalidLine(line)
|
||||||
}
|
}
|
||||||
|
|
||||||
let isLastLine = lineIndex == lines.count - 1
|
let isLastLine = lineIndex == lines.count - 1
|
||||||
|
|
||||||
if isLastLine || lowercasedLine == "[interface]" || lowercasedLine == "[peer]" {
|
if isLastLine || lowercasedLine == "[interface]" || lowercasedLine == "[peer]" {
|
||||||
// Previous section has ended; process the attributes collected so far
|
// Previous section has ended; process the attributes collected so far
|
||||||
if parserState == .inInterfaceSection {
|
if parserState == .inInterfaceSection {
|
||||||
|
@ -70,7 +70,7 @@ extension TunnelConfiguration {
|
||||||
peerConfigurations.append(peer)
|
peerConfigurations.append(peer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if lowercasedLine == "[interface]" {
|
if lowercasedLine == "[interface]" {
|
||||||
parserState = .inInterfaceSection
|
parserState = .inInterfaceSection
|
||||||
attributes.removeAll()
|
attributes.removeAll()
|
||||||
|
@ -79,20 +79,20 @@ extension TunnelConfiguration {
|
||||||
attributes.removeAll()
|
attributes.removeAll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let peerPublicKeysArray = peerConfigurations.map { $0.publicKey }
|
let peerPublicKeysArray = peerConfigurations.map { $0.publicKey }
|
||||||
let peerPublicKeysSet = Set<Data>(peerPublicKeysArray)
|
let peerPublicKeysSet = Set<Data>(peerPublicKeysArray)
|
||||||
if peerPublicKeysArray.count != peerPublicKeysSet.count {
|
if peerPublicKeysArray.count != peerPublicKeysSet.count {
|
||||||
throw ParseError.multiplePeersWithSamePublicKey
|
throw ParseError.multiplePeersWithSamePublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
if let interfaceConfiguration = interfaceConfiguration {
|
if let interfaceConfiguration = interfaceConfiguration {
|
||||||
self.init(interface: interfaceConfiguration, peers: peerConfigurations)
|
self.init(interface: interfaceConfiguration, peers: peerConfigurations)
|
||||||
} else {
|
} else {
|
||||||
throw ParseError.noInterface
|
throw ParseError.noInterface
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func asWgQuickConfig() -> String {
|
func asWgQuickConfig() -> String {
|
||||||
var output = "[Interface]\n"
|
var output = "[Interface]\n"
|
||||||
output.append("PrivateKey = \(interface.privateKey.base64EncodedString())\n")
|
output.append("PrivateKey = \(interface.privateKey.base64EncodedString())\n")
|
||||||
|
@ -110,7 +110,7 @@ extension TunnelConfiguration {
|
||||||
if let mtu = interface.mtu {
|
if let mtu = interface.mtu {
|
||||||
output.append("MTU = \(mtu)\n")
|
output.append("MTU = \(mtu)\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
for peer in peers {
|
for peer in peers {
|
||||||
output.append("\n[Peer]\n")
|
output.append("\n[Peer]\n")
|
||||||
output.append("PublicKey = \(peer.publicKey.base64EncodedString())\n")
|
output.append("PublicKey = \(peer.publicKey.base64EncodedString())\n")
|
||||||
|
@ -128,10 +128,10 @@ extension TunnelConfiguration {
|
||||||
output.append("PersistentKeepalive = \(persistentKeepAlive)\n")
|
output.append("PersistentKeepalive = \(persistentKeepAlive)\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
//swiftlint:disable:next cyclomatic_complexity
|
//swiftlint:disable:next cyclomatic_complexity
|
||||||
private static func collate(interfaceAttributes attributes: [String: String], name: String?) -> InterfaceConfiguration? {
|
private static func collate(interfaceAttributes attributes: [String: String], name: String?) -> InterfaceConfiguration? {
|
||||||
// required wg fields
|
// required wg fields
|
||||||
|
@ -166,7 +166,7 @@ extension TunnelConfiguration {
|
||||||
}
|
}
|
||||||
return interface
|
return interface
|
||||||
}
|
}
|
||||||
|
|
||||||
//swiftlint:disable:next cyclomatic_complexity
|
//swiftlint:disable:next cyclomatic_complexity
|
||||||
private static func collate(peerAttributes attributes: [String: String]) -> PeerConfiguration? {
|
private static func collate(peerAttributes attributes: [String: String]) -> PeerConfiguration? {
|
||||||
// required wg fields
|
// required wg fields
|
||||||
|
@ -196,5 +196,5 @@ extension TunnelConfiguration {
|
||||||
}
|
}
|
||||||
return peer
|
return peer
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ enum TunnelsManagerActivationAttemptError: WireGuardAppError {
|
||||||
enum TunnelsManagerActivationError: WireGuardAppError {
|
enum TunnelsManagerActivationError: WireGuardAppError {
|
||||||
case activationFailed(wasOnDemandEnabled: Bool)
|
case activationFailed(wasOnDemandEnabled: Bool)
|
||||||
case activationFailedWithExtensionError(title: String, message: String, wasOnDemandEnabled: Bool)
|
case activationFailedWithExtensionError(title: String, message: String, wasOnDemandEnabled: Bool)
|
||||||
|
|
||||||
var alertText: AlertText {
|
var alertText: AlertText {
|
||||||
switch self {
|
switch self {
|
||||||
case .activationFailed(let wasOnDemandEnabled):
|
case .activationFailed(let wasOnDemandEnabled):
|
||||||
|
|
|
@ -12,7 +12,7 @@ import NetworkExtension
|
||||||
case reasserting // Not a possible state at present
|
case reasserting // Not a possible state at present
|
||||||
case restarting // Restarting tunnel (done after saving modifications to an active tunnel)
|
case restarting // Restarting tunnel (done after saving modifications to an active tunnel)
|
||||||
case waiting // Waiting for another tunnel to be brought down
|
case waiting // Waiting for another tunnel to be brought down
|
||||||
|
|
||||||
init(from systemStatus: NEVPNStatus) {
|
init(from systemStatus: NEVPNStatus) {
|
||||||
switch systemStatus {
|
switch systemStatus {
|
||||||
case .connected:
|
case .connected:
|
||||||
|
|
|
@ -41,7 +41,7 @@ class TunnelsManager {
|
||||||
completionHandler(.failure(TunnelsManagerError.systemErrorOnListingTunnels(systemError: error)))
|
completionHandler(.failure(TunnelsManagerError.systemErrorOnListingTunnels(systemError: error)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let tunnelManagers = managers ?? []
|
let tunnelManagers = managers ?? []
|
||||||
tunnelManagers.forEach { tunnelManager in
|
tunnelManagers.forEach { tunnelManager in
|
||||||
if (tunnelManager.protocolConfiguration as? NETunnelProviderProtocol)?.migrateConfigurationIfNeeded() == true {
|
if (tunnelManager.protocolConfiguration as? NETunnelProviderProtocol)?.migrateConfigurationIfNeeded() == true {
|
||||||
|
@ -78,9 +78,9 @@ class TunnelsManager {
|
||||||
completionHandler(.failure(TunnelsManagerError.systemErrorOnAddTunnel(systemError: error!)))
|
completionHandler(.failure(TunnelsManagerError.systemErrorOnAddTunnel(systemError: error!)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
|
||||||
let tunnel = TunnelContainer(tunnel: tunnelProviderManager)
|
let tunnel = TunnelContainer(tunnel: tunnelProviderManager)
|
||||||
self.tunnels.append(tunnel)
|
self.tunnels.append(tunnel)
|
||||||
self.tunnels.sort { $0.name < $1.name }
|
self.tunnels.sort { $0.name < $1.name }
|
||||||
|
@ -126,7 +126,7 @@ class TunnelsManager {
|
||||||
tunnelProviderManager.protocolConfiguration = NETunnelProviderProtocol(tunnelConfiguration: tunnelConfiguration)
|
tunnelProviderManager.protocolConfiguration = NETunnelProviderProtocol(tunnelConfiguration: tunnelConfiguration)
|
||||||
tunnelProviderManager.localizedDescription = (tunnelConfiguration).interface.name
|
tunnelProviderManager.localizedDescription = (tunnelConfiguration).interface.name
|
||||||
tunnelProviderManager.isEnabled = true
|
tunnelProviderManager.isEnabled = true
|
||||||
|
|
||||||
let isActivatingOnDemand = !tunnelProviderManager.isOnDemandEnabled && activateOnDemandSetting.isActivateOnDemandEnabled
|
let isActivatingOnDemand = !tunnelProviderManager.isOnDemandEnabled && activateOnDemandSetting.isActivateOnDemandEnabled
|
||||||
activateOnDemandSetting.apply(on: tunnelProviderManager)
|
activateOnDemandSetting.apply(on: tunnelProviderManager)
|
||||||
|
|
||||||
|
@ -137,7 +137,7 @@ class TunnelsManager {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
|
||||||
if isNameChanged {
|
if isNameChanged {
|
||||||
let oldIndex = self.tunnels.firstIndex(of: tunnel)!
|
let oldIndex = self.tunnels.firstIndex(of: tunnel)!
|
||||||
self.tunnels.sort { $0.name < $1.name }
|
self.tunnels.sort { $0.name < $1.name }
|
||||||
|
@ -351,11 +351,11 @@ class TunnelContainer: NSObject {
|
||||||
var tunnelConfiguration: TunnelConfiguration? {
|
var tunnelConfiguration: TunnelConfiguration? {
|
||||||
return (tunnelProvider.protocolConfiguration as? NETunnelProviderProtocol)?.tunnelConfiguration(name: tunnelProvider.localizedDescription)
|
return (tunnelProvider.protocolConfiguration as? NETunnelProviderProtocol)?.tunnelConfiguration(name: tunnelProvider.localizedDescription)
|
||||||
}
|
}
|
||||||
|
|
||||||
var activateOnDemandSetting: ActivateOnDemandSetting {
|
var activateOnDemandSetting: ActivateOnDemandSetting {
|
||||||
return ActivateOnDemandSetting(from: tunnelProvider)
|
return ActivateOnDemandSetting(from: tunnelProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(tunnel: NETunnelProviderManager) {
|
init(tunnel: NETunnelProviderManager) {
|
||||||
name = tunnel.localizedDescription ?? "Unnamed"
|
name = tunnel.localizedDescription ?? "Unnamed"
|
||||||
let status = TunnelStatus(from: tunnel.connection.status)
|
let status = TunnelStatus(from: tunnel.connection.status)
|
||||||
|
|
|
@ -10,42 +10,42 @@ class BorderedTextButton: UIView {
|
||||||
button.titleLabel?.adjustsFontForContentSizeCategory = true
|
button.titleLabel?.adjustsFontForContentSizeCategory = true
|
||||||
return button
|
return button
|
||||||
}()
|
}()
|
||||||
|
|
||||||
override var intrinsicContentSize: CGSize {
|
override var intrinsicContentSize: CGSize {
|
||||||
let buttonSize = button.intrinsicContentSize
|
let buttonSize = button.intrinsicContentSize
|
||||||
return CGSize(width: buttonSize.width + 32, height: buttonSize.height + 16)
|
return CGSize(width: buttonSize.width + 32, height: buttonSize.height + 16)
|
||||||
}
|
}
|
||||||
|
|
||||||
var title: String {
|
var title: String {
|
||||||
get { return button.title(for: .normal) ?? "" }
|
get { return button.title(for: .normal) ?? "" }
|
||||||
set(value) { button.setTitle(value, for: .normal) }
|
set(value) { button.setTitle(value, for: .normal) }
|
||||||
}
|
}
|
||||||
|
|
||||||
var onTapped: (() -> Void)?
|
var onTapped: (() -> Void)?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
super.init(frame: CGRect.zero)
|
super.init(frame: CGRect.zero)
|
||||||
|
|
||||||
layer.borderWidth = 1
|
layer.borderWidth = 1
|
||||||
layer.cornerRadius = 5
|
layer.cornerRadius = 5
|
||||||
layer.borderColor = button.tintColor.cgColor
|
layer.borderColor = button.tintColor.cgColor
|
||||||
|
|
||||||
addSubview(button)
|
addSubview(button)
|
||||||
button.translatesAutoresizingMaskIntoConstraints = false
|
button.translatesAutoresizingMaskIntoConstraints = false
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
button.centerXAnchor.constraint(equalTo: centerXAnchor),
|
button.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||||
button.centerYAnchor.constraint(equalTo: centerYAnchor)
|
button.centerYAnchor.constraint(equalTo: centerYAnchor)
|
||||||
])
|
])
|
||||||
|
|
||||||
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
|
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func buttonTapped() {
|
@objc func buttonTapped() {
|
||||||
onTapped?()
|
onTapped?()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,20 +13,20 @@ class ButtonCell: UITableViewCell {
|
||||||
set(value) { button.tintColor = value ? .red : buttonStandardTintColor }
|
set(value) { button.tintColor = value ? .red : buttonStandardTintColor }
|
||||||
}
|
}
|
||||||
var onTapped: (() -> Void)?
|
var onTapped: (() -> Void)?
|
||||||
|
|
||||||
let button: UIButton = {
|
let button: UIButton = {
|
||||||
let button = UIButton(type: .system)
|
let button = UIButton(type: .system)
|
||||||
button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body)
|
button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body)
|
||||||
button.titleLabel?.adjustsFontForContentSizeCategory = true
|
button.titleLabel?.adjustsFontForContentSizeCategory = true
|
||||||
return button
|
return button
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var buttonStandardTintColor: UIColor
|
var buttonStandardTintColor: UIColor
|
||||||
|
|
||||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
buttonStandardTintColor = button.tintColor
|
buttonStandardTintColor = button.tintColor
|
||||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||||
|
|
||||||
contentView.addSubview(button)
|
contentView.addSubview(button)
|
||||||
button.translatesAutoresizingMaskIntoConstraints = false
|
button.translatesAutoresizingMaskIntoConstraints = false
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
|
@ -34,18 +34,18 @@ class ButtonCell: UITableViewCell {
|
||||||
contentView.layoutMarginsGuide.bottomAnchor.constraint(equalTo: button.bottomAnchor),
|
contentView.layoutMarginsGuide.bottomAnchor.constraint(equalTo: button.bottomAnchor),
|
||||||
button.centerXAnchor.constraint(equalTo: contentView.centerXAnchor)
|
button.centerXAnchor.constraint(equalTo: contentView.centerXAnchor)
|
||||||
])
|
])
|
||||||
|
|
||||||
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
|
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func buttonTapped() {
|
@objc func buttonTapped() {
|
||||||
onTapped?()
|
onTapped?()
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
override func prepareForReuse() {
|
override func prepareForReuse() {
|
||||||
super.prepareForReuse()
|
super.prepareForReuse()
|
||||||
buttonText = ""
|
buttonText = ""
|
||||||
|
|
|
@ -13,16 +13,16 @@ class CheckmarkCell: UITableViewCell {
|
||||||
accessoryType = isChecked ? .checkmark : .none
|
accessoryType = isChecked ? .checkmark : .none
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
isChecked = false
|
isChecked = false
|
||||||
super.init(style: .default, reuseIdentifier: reuseIdentifier)
|
super.init(style: .default, reuseIdentifier: reuseIdentifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
override func prepareForReuse() {
|
override func prepareForReuse() {
|
||||||
super.prepareForReuse()
|
super.prepareForReuse()
|
||||||
message = ""
|
message = ""
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class KeyValueCell: UITableViewCell {
|
class KeyValueCell: UITableViewCell {
|
||||||
|
|
||||||
let keyLabel: UILabel = {
|
let keyLabel: UILabel = {
|
||||||
let keyLabel = UILabel()
|
let keyLabel = UILabel()
|
||||||
keyLabel.font = UIFont.preferredFont(forTextStyle: .body)
|
keyLabel.font = UIFont.preferredFont(forTextStyle: .body)
|
||||||
|
@ -13,7 +13,7 @@ class KeyValueCell: UITableViewCell {
|
||||||
keyLabel.textAlignment = .left
|
keyLabel.textAlignment = .left
|
||||||
return keyLabel
|
return keyLabel
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let valueLabelScrollView: UIScrollView = {
|
let valueLabelScrollView: UIScrollView = {
|
||||||
let scrollView = UIScrollView(frame: .zero)
|
let scrollView = UIScrollView(frame: .zero)
|
||||||
scrollView.isDirectionalLockEnabled = true
|
scrollView.isDirectionalLockEnabled = true
|
||||||
|
@ -21,7 +21,7 @@ class KeyValueCell: UITableViewCell {
|
||||||
scrollView.showsVerticalScrollIndicator = false
|
scrollView.showsVerticalScrollIndicator = false
|
||||||
return scrollView
|
return scrollView
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let valueTextField: UITextField = {
|
let valueTextField: UITextField = {
|
||||||
let valueTextField = UITextField()
|
let valueTextField = UITextField()
|
||||||
valueTextField.textAlignment = .right
|
valueTextField.textAlignment = .right
|
||||||
|
@ -34,7 +34,7 @@ class KeyValueCell: UITableViewCell {
|
||||||
valueTextField.textColor = .gray
|
valueTextField.textColor = .gray
|
||||||
return valueTextField
|
return valueTextField
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var copyableGesture = true
|
var copyableGesture = true
|
||||||
|
|
||||||
var key: String {
|
var key: String {
|
||||||
|
@ -53,7 +53,7 @@ class KeyValueCell: UITableViewCell {
|
||||||
get { return valueTextField.keyboardType }
|
get { return valueTextField.keyboardType }
|
||||||
set(value) { valueTextField.keyboardType = value }
|
set(value) { valueTextField.keyboardType = value }
|
||||||
}
|
}
|
||||||
|
|
||||||
var isValueValid = true {
|
var isValueValid = true {
|
||||||
didSet {
|
didSet {
|
||||||
if isValueValid {
|
if isValueValid {
|
||||||
|
@ -63,26 +63,26 @@ class KeyValueCell: UITableViewCell {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var isStackedHorizontally = false
|
var isStackedHorizontally = false
|
||||||
var isStackedVertically = false
|
var isStackedVertically = false
|
||||||
var contentSizeBasedConstraints = [NSLayoutConstraint]()
|
var contentSizeBasedConstraints = [NSLayoutConstraint]()
|
||||||
|
|
||||||
var onValueChanged: ((String) -> Void)?
|
var onValueChanged: ((String) -> Void)?
|
||||||
var onValueBeingEdited: ((String) -> Void)?
|
var onValueBeingEdited: ((String) -> Void)?
|
||||||
|
|
||||||
private var textFieldValueOnBeginEditing: String = ""
|
private var textFieldValueOnBeginEditing: String = ""
|
||||||
|
|
||||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||||
|
|
||||||
contentView.addSubview(keyLabel)
|
contentView.addSubview(keyLabel)
|
||||||
keyLabel.translatesAutoresizingMaskIntoConstraints = false
|
keyLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
keyLabel.leftAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leftAnchor),
|
keyLabel.leftAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leftAnchor),
|
||||||
keyLabel.topAnchor.constraint(equalToSystemSpacingBelow: contentView.layoutMarginsGuide.topAnchor, multiplier: 0.5)
|
keyLabel.topAnchor.constraint(equalToSystemSpacingBelow: contentView.layoutMarginsGuide.topAnchor, multiplier: 0.5)
|
||||||
])
|
])
|
||||||
|
|
||||||
valueTextField.delegate = self
|
valueTextField.delegate = self
|
||||||
valueLabelScrollView.addSubview(valueTextField)
|
valueLabelScrollView.addSubview(valueTextField)
|
||||||
valueTextField.translatesAutoresizingMaskIntoConstraints = false
|
valueTextField.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
@ -96,31 +96,31 @@ class KeyValueCell: UITableViewCell {
|
||||||
let expandToFitValueLabelConstraint = NSLayoutConstraint(item: valueTextField, attribute: .width, relatedBy: .equal, toItem: valueLabelScrollView, attribute: .width, multiplier: 1, constant: 0)
|
let expandToFitValueLabelConstraint = NSLayoutConstraint(item: valueTextField, attribute: .width, relatedBy: .equal, toItem: valueLabelScrollView, attribute: .width, multiplier: 1, constant: 0)
|
||||||
expandToFitValueLabelConstraint.priority = .defaultLow + 1
|
expandToFitValueLabelConstraint.priority = .defaultLow + 1
|
||||||
expandToFitValueLabelConstraint.isActive = true
|
expandToFitValueLabelConstraint.isActive = true
|
||||||
|
|
||||||
contentView.addSubview(valueLabelScrollView)
|
contentView.addSubview(valueLabelScrollView)
|
||||||
|
|
||||||
contentView.addSubview(valueLabelScrollView)
|
contentView.addSubview(valueLabelScrollView)
|
||||||
valueLabelScrollView.translatesAutoresizingMaskIntoConstraints = false
|
valueLabelScrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
valueLabelScrollView.rightAnchor.constraint(equalTo: contentView.layoutMarginsGuide.rightAnchor),
|
valueLabelScrollView.rightAnchor.constraint(equalTo: contentView.layoutMarginsGuide.rightAnchor),
|
||||||
contentView.layoutMarginsGuide.bottomAnchor.constraint(equalToSystemSpacingBelow: valueLabelScrollView.bottomAnchor, multiplier: 0.5)
|
contentView.layoutMarginsGuide.bottomAnchor.constraint(equalToSystemSpacingBelow: valueLabelScrollView.bottomAnchor, multiplier: 0.5)
|
||||||
])
|
])
|
||||||
|
|
||||||
keyLabel.setContentCompressionResistancePriority(.defaultHigh + 1, for: .horizontal)
|
keyLabel.setContentCompressionResistancePriority(.defaultHigh + 1, for: .horizontal)
|
||||||
keyLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
|
keyLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
|
||||||
valueLabelScrollView.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
valueLabelScrollView.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
||||||
|
|
||||||
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(_:)))
|
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(_:)))
|
||||||
addGestureRecognizer(gestureRecognizer)
|
addGestureRecognizer(gestureRecognizer)
|
||||||
isUserInteractionEnabled = true
|
isUserInteractionEnabled = true
|
||||||
|
|
||||||
configureForContentSize()
|
configureForContentSize()
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func configureForContentSize() {
|
func configureForContentSize() {
|
||||||
var constraints = [NSLayoutConstraint]()
|
var constraints = [NSLayoutConstraint]()
|
||||||
if traitCollection.preferredContentSizeCategory.isAccessibilityCategory {
|
if traitCollection.preferredContentSizeCategory.isAccessibilityCategory {
|
||||||
|
@ -152,13 +152,13 @@ class KeyValueCell: UITableViewCell {
|
||||||
contentSizeBasedConstraints = constraints
|
contentSizeBasedConstraints = constraints
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func handleTapGesture(_ recognizer: UIGestureRecognizer) {
|
@objc func handleTapGesture(_ recognizer: UIGestureRecognizer) {
|
||||||
if !copyableGesture {
|
if !copyableGesture {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard recognizer.state == .recognized else { return }
|
guard recognizer.state == .recognized else { return }
|
||||||
|
|
||||||
if let recognizerView = recognizer.view,
|
if let recognizerView = recognizer.view,
|
||||||
let recognizerSuperView = recognizerView.superview, recognizerView.becomeFirstResponder() {
|
let recognizerSuperView = recognizerView.superview, recognizerView.becomeFirstResponder() {
|
||||||
let menuController = UIMenuController.shared
|
let menuController = UIMenuController.shared
|
||||||
|
@ -166,19 +166,19 @@ class KeyValueCell: UITableViewCell {
|
||||||
menuController.setMenuVisible(true, animated: true)
|
menuController.setMenuVisible(true, animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override var canBecomeFirstResponder: Bool {
|
override var canBecomeFirstResponder: Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
|
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
|
||||||
return (action == #selector(UIResponderStandardEditActions.copy(_:)))
|
return (action == #selector(UIResponderStandardEditActions.copy(_:)))
|
||||||
}
|
}
|
||||||
|
|
||||||
override func copy(_ sender: Any?) {
|
override func copy(_ sender: Any?) {
|
||||||
UIPasteboard.general.string = valueTextField.text
|
UIPasteboard.general.string = valueTextField.text
|
||||||
}
|
}
|
||||||
|
|
||||||
override func prepareForReuse() {
|
override func prepareForReuse() {
|
||||||
super.prepareForReuse()
|
super.prepareForReuse()
|
||||||
copyableGesture = true
|
copyableGesture = true
|
||||||
|
@ -194,18 +194,18 @@ class KeyValueCell: UITableViewCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension KeyValueCell: UITextFieldDelegate {
|
extension KeyValueCell: UITextFieldDelegate {
|
||||||
|
|
||||||
func textFieldDidBeginEditing(_ textField: UITextField) {
|
func textFieldDidBeginEditing(_ textField: UITextField) {
|
||||||
textFieldValueOnBeginEditing = textField.text ?? ""
|
textFieldValueOnBeginEditing = textField.text ?? ""
|
||||||
isValueValid = true
|
isValueValid = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func textFieldDidEndEditing(_ textField: UITextField) {
|
func textFieldDidEndEditing(_ textField: UITextField) {
|
||||||
let isModified = textField.text ?? "" != textFieldValueOnBeginEditing
|
let isModified = textField.text ?? "" != textFieldValueOnBeginEditing
|
||||||
guard isModified else { return }
|
guard isModified else { return }
|
||||||
onValueChanged?(textField.text ?? "")
|
onValueChanged?(textField.text ?? "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||||
if let onValueBeingEdited = onValueBeingEdited {
|
if let onValueBeingEdited = onValueBeingEdited {
|
||||||
let modifiedText = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string)
|
let modifiedText = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string)
|
||||||
|
@ -213,5 +213,5 @@ extension KeyValueCell: UITextFieldDelegate {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,14 +19,14 @@ class SwitchCell: UITableViewCell {
|
||||||
textLabel?.textColor = value ? .black : .gray
|
textLabel?.textColor = value ? .black : .gray
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var onSwitchToggled: ((Bool) -> Void)?
|
var onSwitchToggled: ((Bool) -> Void)?
|
||||||
|
|
||||||
let switchView = UISwitch()
|
let switchView = UISwitch()
|
||||||
|
|
||||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
super.init(style: .default, reuseIdentifier: reuseIdentifier)
|
super.init(style: .default, reuseIdentifier: reuseIdentifier)
|
||||||
|
|
||||||
accessoryView = switchView
|
accessoryView = switchView
|
||||||
switchView.addTarget(self, action: #selector(switchToggled), for: .valueChanged)
|
switchView.addTarget(self, action: #selector(switchToggled), for: .valueChanged)
|
||||||
}
|
}
|
||||||
|
@ -34,11 +34,11 @@ class SwitchCell: UITableViewCell {
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func switchToggled() {
|
@objc func switchToggled() {
|
||||||
onSwitchToggled?(switchView.isOn)
|
onSwitchToggled?(switchView.isOn)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func prepareForReuse() {
|
override func prepareForReuse() {
|
||||||
super.prepareForReuse()
|
super.prepareForReuse()
|
||||||
isEnabled = true
|
isEnabled = true
|
||||||
|
|
|
@ -4,40 +4,40 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class TunnelEditKeyValueCell: KeyValueCell {
|
class TunnelEditKeyValueCell: KeyValueCell {
|
||||||
|
|
||||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||||
|
|
||||||
keyLabel.textAlignment = .right
|
keyLabel.textAlignment = .right
|
||||||
valueTextField.textAlignment = .left
|
valueTextField.textAlignment = .left
|
||||||
|
|
||||||
let widthRatioConstraint = NSLayoutConstraint(item: keyLabel, attribute: .width, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 0.4, constant: 0)
|
let widthRatioConstraint = NSLayoutConstraint(item: keyLabel, attribute: .width, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 0.4, constant: 0)
|
||||||
// In case the key doesn't fit into 0.4 * width,
|
// In case the key doesn't fit into 0.4 * width,
|
||||||
// set a CR priority > the 0.4-constraint's priority.
|
// set a CR priority > the 0.4-constraint's priority.
|
||||||
widthRatioConstraint.priority = .defaultHigh + 1
|
widthRatioConstraint.priority = .defaultHigh + 1
|
||||||
widthRatioConstraint.isActive = true
|
widthRatioConstraint.isActive = true
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class TunnelEditEditableKeyValueCell: TunnelEditKeyValueCell {
|
class TunnelEditEditableKeyValueCell: TunnelEditKeyValueCell {
|
||||||
|
|
||||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||||
|
|
||||||
copyableGesture = false
|
copyableGesture = false
|
||||||
valueTextField.textColor = .black
|
valueTextField.textColor = .black
|
||||||
valueTextField.isEnabled = true
|
valueTextField.isEnabled = true
|
||||||
valueLabelScrollView.isScrollEnabled = false
|
valueLabelScrollView.isScrollEnabled = false
|
||||||
valueTextField.widthAnchor.constraint(equalTo: valueLabelScrollView.widthAnchor).isActive = true
|
valueTextField.widthAnchor.constraint(equalTo: valueLabelScrollView.widthAnchor).isActive = true
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ class TunnelListCell: UITableViewCell {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var onSwitchToggled: ((Bool) -> Void)?
|
var onSwitchToggled: ((Bool) -> Void)?
|
||||||
|
|
||||||
let nameLabel: UILabel = {
|
let nameLabel: UILabel = {
|
||||||
let nameLabel = UILabel()
|
let nameLabel = UILabel()
|
||||||
nameLabel.font = UIFont.preferredFont(forTextStyle: .body)
|
nameLabel.font = UIFont.preferredFont(forTextStyle: .body)
|
||||||
|
@ -27,35 +27,35 @@ class TunnelListCell: UITableViewCell {
|
||||||
nameLabel.numberOfLines = 0
|
nameLabel.numberOfLines = 0
|
||||||
return nameLabel
|
return nameLabel
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let busyIndicator: UIActivityIndicatorView = {
|
let busyIndicator: UIActivityIndicatorView = {
|
||||||
let busyIndicator = UIActivityIndicatorView(style: .gray)
|
let busyIndicator = UIActivityIndicatorView(style: .gray)
|
||||||
busyIndicator.hidesWhenStopped = true
|
busyIndicator.hidesWhenStopped = true
|
||||||
return busyIndicator
|
return busyIndicator
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let statusSwitch = UISwitch()
|
let statusSwitch = UISwitch()
|
||||||
|
|
||||||
private var statusObservationToken: AnyObject?
|
private var statusObservationToken: AnyObject?
|
||||||
private var nameObservationToken: AnyObject?
|
private var nameObservationToken: AnyObject?
|
||||||
|
|
||||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||||
|
|
||||||
contentView.addSubview(statusSwitch)
|
contentView.addSubview(statusSwitch)
|
||||||
statusSwitch.translatesAutoresizingMaskIntoConstraints = false
|
statusSwitch.translatesAutoresizingMaskIntoConstraints = false
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
statusSwitch.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
|
statusSwitch.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
|
||||||
contentView.rightAnchor.constraint(equalTo: statusSwitch.rightAnchor)
|
contentView.rightAnchor.constraint(equalTo: statusSwitch.rightAnchor)
|
||||||
])
|
])
|
||||||
|
|
||||||
contentView.addSubview(busyIndicator)
|
contentView.addSubview(busyIndicator)
|
||||||
busyIndicator.translatesAutoresizingMaskIntoConstraints = false
|
busyIndicator.translatesAutoresizingMaskIntoConstraints = false
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
busyIndicator.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
|
busyIndicator.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
|
||||||
statusSwitch.leftAnchor.constraint(equalToSystemSpacingAfter: busyIndicator.rightAnchor, multiplier: 1)
|
statusSwitch.leftAnchor.constraint(equalToSystemSpacingAfter: busyIndicator.rightAnchor, multiplier: 1)
|
||||||
])
|
])
|
||||||
|
|
||||||
contentView.addSubview(nameLabel)
|
contentView.addSubview(nameLabel)
|
||||||
nameLabel.translatesAutoresizingMaskIntoConstraints = false
|
nameLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||||
nameLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
nameLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
||||||
|
@ -67,16 +67,16 @@ class TunnelListCell: UITableViewCell {
|
||||||
busyIndicator.leftAnchor.constraint(equalToSystemSpacingAfter: nameLabel.rightAnchor, multiplier: 1),
|
busyIndicator.leftAnchor.constraint(equalToSystemSpacingAfter: nameLabel.rightAnchor, multiplier: 1),
|
||||||
bottomAnchorConstraint
|
bottomAnchorConstraint
|
||||||
])
|
])
|
||||||
|
|
||||||
accessoryType = .disclosureIndicator
|
accessoryType = .disclosureIndicator
|
||||||
|
|
||||||
statusSwitch.addTarget(self, action: #selector(switchToggled), for: .valueChanged)
|
statusSwitch.addTarget(self, action: #selector(switchToggled), for: .valueChanged)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func switchToggled() {
|
@objc func switchToggled() {
|
||||||
onSwitchToggled?(statusSwitch.isOn)
|
onSwitchToggled?(statusSwitch.isOn)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func update(from status: TunnelStatus?) {
|
private func update(from status: TunnelStatus?) {
|
||||||
guard let status = status else {
|
guard let status = status else {
|
||||||
reset()
|
reset()
|
||||||
|
@ -93,17 +93,17 @@ class TunnelListCell: UITableViewCell {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
private func reset() {
|
private func reset() {
|
||||||
statusSwitch.isOn = false
|
statusSwitch.isOn = false
|
||||||
statusSwitch.isUserInteractionEnabled = false
|
statusSwitch.isUserInteractionEnabled = false
|
||||||
busyIndicator.stopAnimating()
|
busyIndicator.stopAnimating()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func prepareForReuse() {
|
override func prepareForReuse() {
|
||||||
super.prepareForReuse()
|
super.prepareForReuse()
|
||||||
reset()
|
reset()
|
||||||
|
|
|
@ -58,12 +58,12 @@ class SettingsTableViewController: UITableViewController {
|
||||||
override func viewDidLayoutSubviews() {
|
override func viewDidLayoutSubviews() {
|
||||||
super.viewDidLayoutSubviews()
|
super.viewDidLayoutSubviews()
|
||||||
guard let logo = tableView.tableFooterView else { return }
|
guard let logo = tableView.tableFooterView else { return }
|
||||||
|
|
||||||
let bottomPadding = max(tableView.layoutMargins.bottom, 10)
|
let bottomPadding = max(tableView.layoutMargins.bottom, 10)
|
||||||
let fullHeight = max(tableView.contentSize.height, tableView.bounds.size.height - tableView.layoutMargins.top - bottomPadding)
|
let fullHeight = max(tableView.contentSize.height, tableView.bounds.size.height - tableView.layoutMargins.top - bottomPadding)
|
||||||
|
|
||||||
let imageAspectRatio = logo.intrinsicContentSize.width / logo.intrinsicContentSize.height
|
let imageAspectRatio = logo.intrinsicContentSize.width / logo.intrinsicContentSize.height
|
||||||
|
|
||||||
var height = tableView.estimatedRowHeight * 1.5
|
var height = tableView.estimatedRowHeight * 1.5
|
||||||
var width = height * imageAspectRatio
|
var width = height * imageAspectRatio
|
||||||
let maxWidth = view.bounds.size.width - max(tableView.layoutMargins.left + tableView.layoutMargins.right, 20)
|
let maxWidth = view.bounds.size.width - max(tableView.layoutMargins.left + tableView.layoutMargins.right, 20)
|
||||||
|
@ -71,11 +71,11 @@ class SettingsTableViewController: UITableViewController {
|
||||||
width = maxWidth
|
width = maxWidth
|
||||||
height = width / imageAspectRatio
|
height = width / imageAspectRatio
|
||||||
}
|
}
|
||||||
|
|
||||||
let needsReload = height != logo.frame.height
|
let needsReload = height != logo.frame.height
|
||||||
|
|
||||||
logo.frame = CGRect(x: (view.bounds.size.width - width) / 2, y: fullHeight - height, width: width, height: height)
|
logo.frame = CGRect(x: (view.bounds.size.width - width) / 2, y: fullHeight - height, width: width, height: height)
|
||||||
|
|
||||||
if needsReload {
|
if needsReload {
|
||||||
tableView.tableFooterView = logo
|
tableView.tableFooterView = logo
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ class TunnelDetailTableViewController: UITableViewController {
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
onDemandStatusObservationToken = nil
|
onDemandStatusObservationToken = nil
|
||||||
statusObservationToken = nil
|
statusObservationToken = nil
|
||||||
|
@ -166,7 +166,7 @@ extension TunnelDetailTableViewController {
|
||||||
|
|
||||||
private func statusCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
private func statusCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
||||||
let cell: SwitchCell = tableView.dequeueReusableCell(for: indexPath)
|
let cell: SwitchCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
|
|
||||||
let statusUpdate: (SwitchCell, TunnelStatus) -> Void = { cell, status in
|
let statusUpdate: (SwitchCell, TunnelStatus) -> Void = { cell, status in
|
||||||
let text: String
|
let text: String
|
||||||
switch status {
|
switch status {
|
||||||
|
@ -192,13 +192,13 @@ extension TunnelDetailTableViewController {
|
||||||
}
|
}
|
||||||
cell.isEnabled = status == .active || status == .inactive
|
cell.isEnabled = status == .active || status == .inactive
|
||||||
}
|
}
|
||||||
|
|
||||||
statusUpdate(cell, tunnel.status)
|
statusUpdate(cell, tunnel.status)
|
||||||
statusObservationToken = tunnel.observe(\.status) { [weak cell] tunnel, _ in
|
statusObservationToken = tunnel.observe(\.status) { [weak cell] tunnel, _ in
|
||||||
guard let cell = cell else { return }
|
guard let cell = cell else { return }
|
||||||
statusUpdate(cell, tunnel.status)
|
statusUpdate(cell, tunnel.status)
|
||||||
}
|
}
|
||||||
|
|
||||||
cell.onSwitchToggled = { [weak self] isOn in
|
cell.onSwitchToggled = { [weak self] isOn in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
if isOn {
|
if isOn {
|
||||||
|
|
|
@ -17,27 +17,27 @@ class TunnelsListTableViewController: UIViewController {
|
||||||
tableView.register(TunnelListCell.self)
|
tableView.register(TunnelListCell.self)
|
||||||
return tableView
|
return tableView
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let centeredAddButton: BorderedTextButton = {
|
let centeredAddButton: BorderedTextButton = {
|
||||||
let button = BorderedTextButton()
|
let button = BorderedTextButton()
|
||||||
button.title = tr("tunnelsListCenteredAddTunnelButtonTitle")
|
button.title = tr("tunnelsListCenteredAddTunnelButtonTitle")
|
||||||
button.isHidden = true
|
button.isHidden = true
|
||||||
return button
|
return button
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let busyIndicator: UIActivityIndicatorView = {
|
let busyIndicator: UIActivityIndicatorView = {
|
||||||
let busyIndicator = UIActivityIndicatorView(style: .gray)
|
let busyIndicator = UIActivityIndicatorView(style: .gray)
|
||||||
busyIndicator.hidesWhenStopped = true
|
busyIndicator.hidesWhenStopped = true
|
||||||
return busyIndicator
|
return busyIndicator
|
||||||
}()
|
}()
|
||||||
|
|
||||||
override func loadView() {
|
override func loadView() {
|
||||||
view = UIView()
|
view = UIView()
|
||||||
view.backgroundColor = .white
|
view.backgroundColor = .white
|
||||||
|
|
||||||
tableView.dataSource = self
|
tableView.dataSource = self
|
||||||
tableView.delegate = self
|
tableView.delegate = self
|
||||||
|
|
||||||
view.addSubview(tableView)
|
view.addSubview(tableView)
|
||||||
tableView.translatesAutoresizingMaskIntoConstraints = false
|
tableView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
|
@ -53,22 +53,22 @@ class TunnelsListTableViewController: UIViewController {
|
||||||
busyIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
busyIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
||||||
busyIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor)
|
busyIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor)
|
||||||
])
|
])
|
||||||
|
|
||||||
view.addSubview(centeredAddButton)
|
view.addSubview(centeredAddButton)
|
||||||
centeredAddButton.translatesAutoresizingMaskIntoConstraints = false
|
centeredAddButton.translatesAutoresizingMaskIntoConstraints = false
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
centeredAddButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
centeredAddButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
||||||
centeredAddButton.centerYAnchor.constraint(equalTo: view.centerYAnchor)
|
centeredAddButton.centerYAnchor.constraint(equalTo: view.centerYAnchor)
|
||||||
])
|
])
|
||||||
|
|
||||||
centeredAddButton.onTapped = { [weak self] in
|
centeredAddButton.onTapped = { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
self.addButtonTapped(sender: self.centeredAddButton)
|
self.addButtonTapped(sender: self.centeredAddButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
busyIndicator.startAnimating()
|
busyIndicator.startAnimating()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ class TunnelsListTableViewController: UIViewController {
|
||||||
func setTunnelsManager(tunnelsManager: TunnelsManager) {
|
func setTunnelsManager(tunnelsManager: TunnelsManager) {
|
||||||
self.tunnelsManager = tunnelsManager
|
self.tunnelsManager = tunnelsManager
|
||||||
tunnelsManager.tunnelsListDelegate = self
|
tunnelsManager.tunnelsListDelegate = self
|
||||||
|
|
||||||
busyIndicator.stopAnimating()
|
busyIndicator.stopAnimating()
|
||||||
tableView.reloadData()
|
tableView.reloadData()
|
||||||
centeredAddButton.isHidden = tunnelsManager.numberOfTunnels() > 0
|
centeredAddButton.isHidden = tunnelsManager.numberOfTunnels() > 0
|
||||||
|
@ -96,7 +96,7 @@ class TunnelsListTableViewController: UIViewController {
|
||||||
|
|
||||||
@objc func addButtonTapped(sender: AnyObject) {
|
@objc func addButtonTapped(sender: AnyObject) {
|
||||||
guard tunnelsManager != nil else { return }
|
guard tunnelsManager != nil else { return }
|
||||||
|
|
||||||
let alert = UIAlertController(title: "", message: tr("addTunnelMenuHeader"), preferredStyle: .actionSheet)
|
let alert = UIAlertController(title: "", message: tr("addTunnelMenuHeader"), preferredStyle: .actionSheet)
|
||||||
let importFileAction = UIAlertAction(title: tr("addTunnelMenuImportFile"), style: .default) { [weak self] _ in
|
let importFileAction = UIAlertAction(title: tr("addTunnelMenuImportFile"), style: .default) { [weak self] _ in
|
||||||
self?.presentViewControllerForFileImport()
|
self?.presentViewControllerForFileImport()
|
||||||
|
@ -129,7 +129,7 @@ class TunnelsListTableViewController: UIViewController {
|
||||||
|
|
||||||
@objc func settingsButtonTapped(sender: UIBarButtonItem) {
|
@objc func settingsButtonTapped(sender: UIBarButtonItem) {
|
||||||
guard tunnelsManager != nil else { return }
|
guard tunnelsManager != nil else { return }
|
||||||
|
|
||||||
let settingsVC = SettingsTableViewController(tunnelsManager: tunnelsManager)
|
let settingsVC = SettingsTableViewController(tunnelsManager: tunnelsManager)
|
||||||
let settingsNC = UINavigationController(rootViewController: settingsVC)
|
let settingsNC = UINavigationController(rootViewController: settingsVC)
|
||||||
settingsNC.modalPresentationStyle = .formSheet
|
settingsNC.modalPresentationStyle = .formSheet
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
|
|
||||||
protocol WireGuardAppError: Error {
|
protocol WireGuardAppError: Error {
|
||||||
typealias AlertText = (title: String, message: String)
|
typealias AlertText = (title: String, message: String)
|
||||||
|
|
||||||
var alertText: AlertText { get }
|
var alertText: AlertText { get }
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,7 @@ class DNSResolver {
|
||||||
extension DNSResolver {
|
extension DNSResolver {
|
||||||
// Based on DNS resolution code by Jason Donenfeld <jason@zx2c4.com>
|
// Based on DNS resolution code by Jason Donenfeld <jason@zx2c4.com>
|
||||||
// in parse_endpoint() in src/tools/config.c in the WireGuard codebase
|
// in parse_endpoint() in src/tools/config.c in the WireGuard codebase
|
||||||
|
|
||||||
//swiftlint:disable:next cyclomatic_complexity
|
//swiftlint:disable:next cyclomatic_complexity
|
||||||
private static func resolveSync(endpoint: Endpoint) -> Endpoint? {
|
private static func resolveSync(endpoint: Endpoint) -> Endpoint? {
|
||||||
switch endpoint.host {
|
switch endpoint.host {
|
||||||
|
|
|
@ -14,7 +14,7 @@ enum PacketTunnelProviderError: Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
class PacketTunnelProvider: NEPacketTunnelProvider {
|
class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
|
|
||||||
private var wgHandle: Int32?
|
private var wgHandle: Int32?
|
||||||
private var networkMonitor: NWPathMonitor?
|
private var networkMonitor: NWPathMonitor?
|
||||||
private var lastFirstInterface: NWInterface?
|
private var lastFirstInterface: NWInterface?
|
||||||
|
|
Loading…
Reference in New Issue