Compare commits
78 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 41206b5193 | |||
| b5acacda9d | |||
| ae74cb807d | |||
| 2fec12a6e1 | |||
| 7b279383d1 | |||
| 901fe1cf58 | |||
| ccc7472fd7 | |||
| 12b095470a | |||
| 9c07693951 | |||
| 23618f994f | |||
| ba644415c7 | |||
| 10da5cfdef | |||
| 75b6925deb | |||
| 03a59ff38e | |||
| abf506c1fe | |||
| dfb685f258 | |||
| f3798d0e11 | |||
| 86afd1a46a | |||
| 7171df84fa | |||
| d882a486a9 | |||
| adcbd17ebe | |||
| 3d8de22b96 | |||
| ba4d1e7b21 | |||
| f5a14b8434 | |||
| b74eb7239a | |||
| a8226b35d2 | |||
| 73c708d902 | |||
| 3668f3af9f | |||
| 54697a3240 | |||
| 3428bfbc9e | |||
| cfd1b16801 | |||
| ca70fe9ddc | |||
| 55c587b443 | |||
| b6831c1aca | |||
| 2ac17da7cb | |||
| 274c4cd092 | |||
| 95e1409bfb | |||
| 2c2c53b1f8 | |||
| 9cbfec99df | |||
| 1bd6dcb7e7 | |||
| c1fe8b0162 | |||
| 64c2fb337d | |||
| 147ac02f0d | |||
| 03ef79c0fd | |||
| a261d84fc6 | |||
| abaf1f1454 | |||
| 1e9e21bacf | |||
| ac9f7b9f5e | |||
| a115dd3bd9 | |||
| df9934a4b8 | |||
| 40f18de4d2 | |||
| 13b720442d | |||
| c1f509d65b | |||
| 87f0526f09 | |||
| 060c027325 | |||
| 23bf3cfccb | |||
| 7f5ad3e503 | |||
| 820fa55380 | |||
| eb528c766b | |||
| 53235eb38f | |||
| b9ff5c2e94 | |||
| b7f69d20b6 | |||
| 6c4f4109eb | |||
| 7b5b564a6e | |||
| 695f868b1f | |||
| e724c043d9 | |||
| 491301f58b | |||
| c4f79beb8d | |||
| a613fec2ff | |||
| e54a5d9a13 | |||
| 6d57c8b6f9 | |||
| b67acaccff | |||
| d8568b0e31 | |||
| 373bb2ae99 | |||
| 631286e2d1 | |||
| 74cd7041dc | |||
| 21d920c8b0 | |||
| 44c4df1cd5 |
@@ -1,4 +1,4 @@
|
||||
Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
// swift-tools-version:5.3
|
||||
// swift-tools-version:5.9
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
@@ -6,8 +6,8 @@ import PackageDescription
|
||||
let package = Package(
|
||||
name: "WireGuardKit",
|
||||
platforms: [
|
||||
.macOS(.v10_14),
|
||||
.iOS(.v12)
|
||||
.macOS(.v12),
|
||||
.iOS(.v15)
|
||||
],
|
||||
products: [
|
||||
.library(name: "WireGuardKit", targets: ["WireGuardKit"])
|
||||
@@ -30,7 +30,7 @@ let package = Package(
|
||||
"goruntime-boottime-over-monotonic.diff",
|
||||
"go.mod",
|
||||
"go.sum",
|
||||
"api-ios.go",
|
||||
"api-apple.go",
|
||||
"Makefile"
|
||||
],
|
||||
publicHeadersPath: ".",
|
||||
|
||||
@@ -18,7 +18,7 @@ $ cp Sources/WireGuardApp/Config/Developer.xcconfig.template Sources/WireGuardAp
|
||||
$ vim Sources/WireGuardApp/Config/Developer.xcconfig
|
||||
```
|
||||
|
||||
- Install swiftlint and go 1.15:
|
||||
- Install swiftlint and go 1.19:
|
||||
|
||||
```
|
||||
$ brew install swiftlint go
|
||||
@@ -54,7 +54,7 @@ $ open WireGuard.xcodeproj
|
||||
the "External Build Tool Configuration":
|
||||
|
||||
```
|
||||
$BUILD_DIR/../../SourcePackages/checkouts/wireguard-apple/Sources/WireGuardKitGo
|
||||
${BUILD_DIR%Build/*}SourcePackages/checkouts/wireguard-apple/Sources/WireGuardKitGo
|
||||
```
|
||||
|
||||
- Switch to "Build Settings" and find `SDKROOT`.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
@@ -35,6 +35,10 @@ extension FileManager {
|
||||
return sharedFolderURL?.appendingPathComponent("last-error.txt")
|
||||
}
|
||||
|
||||
static var loginHelperTimestampURL: URL? {
|
||||
return sharedFolderURL?.appendingPathComponent("login-helper-timestamp.bin")
|
||||
}
|
||||
|
||||
static func deleteFile(at url: URL) -> Bool {
|
||||
do {
|
||||
try FileManager.default.removeItem(at: url)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
import Security
|
||||
@@ -7,8 +7,7 @@ import Security
|
||||
class Keychain {
|
||||
static func openReference(called ref: Data) -> String? {
|
||||
var result: CFTypeRef?
|
||||
let ret = SecItemCopyMatching([kSecClass: kSecClassGenericPassword,
|
||||
kSecValuePersistentRef: ref,
|
||||
let ret = SecItemCopyMatching([kSecValuePersistentRef: ref,
|
||||
kSecReturnData: true] as CFDictionary,
|
||||
&result)
|
||||
if ret != errSecSuccess || result == nil {
|
||||
@@ -44,7 +43,7 @@ class Keychain {
|
||||
items[kSecAttrSynchronizable] = false
|
||||
items[kSecAttrAccessible] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
|
||||
|
||||
guard let extensionPath = Bundle.main.builtInPlugInsURL?.appendingPathComponent("WireGuardNetworkExtension.appex").path else {
|
||||
guard let extensionPath = Bundle.main.builtInPlugInsURL?.appendingPathComponent("WireGuardNetworkExtension.appex", isDirectory: true).path else {
|
||||
wg_log(.error, staticMessage: "Unable to determine app extension path")
|
||||
return nil
|
||||
}
|
||||
@@ -109,8 +108,7 @@ class Keychain {
|
||||
}
|
||||
|
||||
static func verifyReference(called ref: Data) -> Bool {
|
||||
return SecItemCopyMatching([kSecClass: kSecClassGenericPassword,
|
||||
kSecValuePersistentRef: ref] as CFDictionary,
|
||||
return SecItemCopyMatching([kSecValuePersistentRef: ref] as CFDictionary,
|
||||
nil) != errSecItemNotFound
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef RINGLOGGER_H
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import NetworkExtension
|
||||
|
||||
@@ -72,7 +72,7 @@ extension NETunnelProviderProtocol {
|
||||
#error("Unimplemented")
|
||||
#endif
|
||||
guard passwordReference == nil else { return true }
|
||||
wg_log(.debug, message: "Migrating tunnel configuration '\(name)'")
|
||||
wg_log(.info, message: "Migrating tunnel configuration '\(name)'")
|
||||
passwordReference = Keychain.makeReference(containing: oldConfig, called: name)
|
||||
return true
|
||||
}
|
||||
@@ -81,6 +81,25 @@ extension NETunnelProviderProtocol {
|
||||
providerConfiguration = ["UID": getuid()]
|
||||
return true
|
||||
}
|
||||
#elseif os(iOS)
|
||||
/* Update the stored reference from the old iOS 14 one to the canonical iOS 15 one.
|
||||
* The iOS 14 ones are 96 bits, while the iOS 15 ones are 160 bits. We do this so
|
||||
* that we can have fast set exclusion in deleteReferences safely. */
|
||||
if passwordReference != nil && passwordReference!.count == 12 {
|
||||
var result: CFTypeRef?
|
||||
let ret = SecItemCopyMatching([kSecValuePersistentRef: passwordReference!,
|
||||
kSecReturnPersistentRef: true] as CFDictionary,
|
||||
&result)
|
||||
if ret != errSecSuccess || result == nil {
|
||||
return false
|
||||
}
|
||||
guard let newReference = result as? Data else { return false }
|
||||
if !newReference.elementsEqual(passwordReference!) {
|
||||
wg_log(.info, message: "Migrating iOS 14-style keychain reference to iOS 15-style keychain reference for '\(name)'")
|
||||
passwordReference = newReference
|
||||
return true
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
/// This source file contains bits of code from:
|
||||
/// https://oleb.net/blog/2018/01/notificationcenter-removeobserver/
|
||||
|
||||
/// Wraps the observer token received from
|
||||
/// `NotificationCenter.addObserver(forName:object:queue:using:)`
|
||||
/// and unregisters it in deinit.
|
||||
final class NotificationToken {
|
||||
let notificationCenter: NotificationCenter
|
||||
let token: Any
|
||||
|
||||
init(notificationCenter: NotificationCenter = .default, token: Any) {
|
||||
self.notificationCenter = notificationCenter
|
||||
self.token = token
|
||||
}
|
||||
|
||||
deinit {
|
||||
notificationCenter.removeObserver(token)
|
||||
}
|
||||
}
|
||||
|
||||
extension NotificationCenter {
|
||||
/// Convenience wrapper for addObserver(forName:object:queue:using:)
|
||||
/// that returns our custom `NotificationToken`.
|
||||
func observe(name: NSNotification.Name?, object obj: Any?, queue: OperationQueue?, using block: @escaping (Notification) -> Void) -> NotificationToken {
|
||||
let token = addObserver(forName: name, object: obj, queue: queue, using: block)
|
||||
return NotificationToken(notificationCenter: self, token: token)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
// iOS permission prompts
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
// Generic alert action names
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"tunnelsListSelectAllButtonTitle" = "Select All";
|
||||
"tunnelsListDeleteButtonTitle" = "Delete";
|
||||
"tunnelsListSelectedTitle (%d)" = "%d selected";
|
||||
"tunnelListCaptionOnDemand" = "On-Demand";
|
||||
|
||||
// Tunnels list menu
|
||||
|
||||
@@ -55,6 +56,11 @@
|
||||
"tunnelStatusRestarting" = "Restarting";
|
||||
"tunnelStatusWaiting" = "Waiting";
|
||||
|
||||
"tunnelStatusAddendumOnDemand" = " (On-Demand)";
|
||||
"tunnelStatusOnDemandDisabled" = "On-Demand Disabled";
|
||||
"tunnelStatusAddendumOnDemandEnabled" = ", On-Demand Enabled";
|
||||
"tunnelStatusAddendumOnDemandDisabled" = ", On-Demand Disabled";
|
||||
|
||||
"macToggleStatusButtonActivate" = "Activate";
|
||||
"macToggleStatusButtonActivating" = "Activating…";
|
||||
"macToggleStatusButtonDeactivate" = "Deactivate";
|
||||
@@ -62,6 +68,9 @@
|
||||
"macToggleStatusButtonReasserting" = "Reactivating…";
|
||||
"macToggleStatusButtonRestarting" = "Restarting…";
|
||||
"macToggleStatusButtonWaiting" = "Waiting…";
|
||||
"macToggleStatusButtonEnableOnDemand" = "Enable On-Demand";
|
||||
"macToggleStatusButtonDisableOnDemand" = "Disable On-Demand";
|
||||
"macToggleStatusButtonDisableOnDemandDeactivate" = "Disable On-Demand and Deactivate";
|
||||
|
||||
"tunnelSectionTitleInterface" = "Interface";
|
||||
|
||||
@@ -109,8 +118,9 @@
|
||||
"tunnelOnDemandSectionTitleAddSSIDs" = "Add SSIDs";
|
||||
"tunnelOnDemandAddMessageAddConnectedSSID (%@)" = "Add connected: %@";
|
||||
"tunnelOnDemandAddMessageAddNewSSID" = "Add new";
|
||||
"tunnelOnDemandSSIDTextFieldPlaceholder" = "SSID";
|
||||
|
||||
"tunnelOnDemandKey" = "On demand";
|
||||
"tunnelOnDemandKey" = "On-demand";
|
||||
"tunnelOnDemandOptionOff" = "Off";
|
||||
"tunnelOnDemandOptionWiFiOnly" = "Wi-Fi only";
|
||||
"tunnelOnDemandOptionWiFiOrCellular" = "Wi-Fi or cellular";
|
||||
@@ -255,8 +265,6 @@
|
||||
"alertTunnelActivationFileDescriptorFailureMessage" = "Unable to determine TUN device file descriptor.";
|
||||
"alertTunnelActivationSetNetworkSettingsMessage" = "Unable to apply network settings to tunnel object.";
|
||||
|
||||
"alertTunnelActivationFailureOnDemandAddendum" = " This tunnel has Activate On Demand enabled, so this tunnel might be re-activated automatically by the OS. You may turn off Activate On Demand in this app by editing the tunnel configuration.";
|
||||
|
||||
"alertTunnelDNSFailureTitle" = "DNS resolution failure";
|
||||
"alertTunnelDNSFailureMessage" = "One or more endpoint domains could not be resolved.";
|
||||
|
||||
@@ -297,6 +305,7 @@
|
||||
"macMenuNetworksNone" = "Networks: None";
|
||||
|
||||
"macMenuTitle" = "WireGuard";
|
||||
"macTunnelsMenuTitle" = "Tunnels";
|
||||
"macMenuManageTunnels" = "Manage Tunnels";
|
||||
"macMenuImportTunnels" = "Import Tunnel(s) from File…";
|
||||
"macMenuAddEmptyTunnel" = "Add Empty Tunnel…";
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
VERSION_NAME = 1.0.11
|
||||
VERSION_ID = 21
|
||||
VERSION_NAME = 1.0.16
|
||||
VERSION_ID = 27
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import NetworkExtension
|
||||
|
||||
@@ -42,11 +42,11 @@ extension ActivateOnDemandOption {
|
||||
}
|
||||
}
|
||||
tunnelProviderManager.onDemandRules = rules
|
||||
tunnelProviderManager.isOnDemandEnabled = self != .off
|
||||
tunnelProviderManager.isOnDemandEnabled = (rules != nil) && tunnelProviderManager.isOnDemandEnabled
|
||||
}
|
||||
|
||||
init(from tunnelProviderManager: NETunnelProviderManager) {
|
||||
if tunnelProviderManager.isOnDemandEnabled, let onDemandRules = tunnelProviderManager.onDemandRules {
|
||||
if let onDemandRules = tunnelProviderManager.onDemandRules {
|
||||
self = ActivateOnDemandOption.create(from: onDemandRules)
|
||||
} else {
|
||||
self = .off
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import NetworkExtension
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import NetworkExtension
|
||||
|
||||
@@ -56,10 +56,10 @@ enum TunnelsManagerActivationError: WireGuardAppError {
|
||||
|
||||
var alertText: AlertText {
|
||||
switch self {
|
||||
case .activationFailed(let wasOnDemandEnabled):
|
||||
return (tr("alertTunnelActivationFailureTitle"), tr("alertTunnelActivationFailureMessage") + (wasOnDemandEnabled ? tr("alertTunnelActivationFailureOnDemandAddendum") : ""))
|
||||
case .activationFailedWithExtensionError(let title, let message, let wasOnDemandEnabled):
|
||||
return (title, message + (wasOnDemandEnabled ? tr("alertTunnelActivationFailureOnDemandAddendum") : ""))
|
||||
case .activationFailed:
|
||||
return (tr("alertTunnelActivationFailureTitle"), tr("alertTunnelActivationFailureMessage"))
|
||||
case .activationFailedWithExtensionError(let title, let message, _):
|
||||
return (title, message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
import NetworkExtension
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
import NetworkExtension
|
||||
import os.log
|
||||
|
||||
protocol TunnelsManagerListDelegate: class {
|
||||
protocol TunnelsManagerListDelegate: AnyObject {
|
||||
func tunnelAdded(at index: Int)
|
||||
func tunnelModified(at index: Int)
|
||||
func tunnelMoved(from oldIndex: Int, to newIndex: Int)
|
||||
func tunnelRemoved(at index: Int, tunnel: TunnelContainer)
|
||||
}
|
||||
|
||||
protocol TunnelsManagerActivationDelegate: class {
|
||||
protocol TunnelsManagerActivationDelegate: AnyObject {
|
||||
func tunnelActivationAttemptFailed(tunnel: TunnelContainer, error: TunnelsManagerActivationAttemptError) // startTunnel wasn't called or failed
|
||||
func tunnelActivationAttemptSucceeded(tunnel: TunnelContainer) // startTunnel succeeded
|
||||
func tunnelActivationFailed(tunnel: TunnelContainer, error: TunnelsManagerActivationError) // status didn't change to connected
|
||||
@@ -23,9 +23,9 @@ class TunnelsManager {
|
||||
private var tunnels: [TunnelContainer]
|
||||
weak var tunnelsListDelegate: TunnelsManagerListDelegate?
|
||||
weak var activationDelegate: TunnelsManagerActivationDelegate?
|
||||
private var statusObservationToken: AnyObject?
|
||||
private var waiteeObservationToken: AnyObject?
|
||||
private var configurationsObservationToken: AnyObject?
|
||||
private var statusObservationToken: NotificationToken?
|
||||
private var waiteeObservationToken: NSKeyValueObservation?
|
||||
private var configurationsObservationToken: NotificationToken?
|
||||
|
||||
init(tunnelProviders: [NETunnelProviderManager]) {
|
||||
tunnels = tunnelProviders.map { TunnelContainer(tunnel: $0) }.sorted { TunnelsManager.tunnelNameIsLessThan($0.name, $1.name) }
|
||||
@@ -138,10 +138,10 @@ class TunnelsManager {
|
||||
let activeTunnel = tunnels.first { $0.status == .active || $0.status == .activating }
|
||||
|
||||
tunnelProviderManager.saveToPreferences { [weak self] error in
|
||||
guard error == nil else {
|
||||
wg_log(.error, message: "Add: Saving configuration failed: \(error!)")
|
||||
if let error = error {
|
||||
wg_log(.error, message: "Add: Saving configuration failed: \(error)")
|
||||
(tunnelProviderManager.protocolConfiguration as? NETunnelProviderProtocol)?.destroyConfigurationReference()
|
||||
completionHandler(.failure(TunnelsManagerError.systemErrorOnAddTunnel(systemError: error!)))
|
||||
completionHandler(.failure(TunnelsManagerError.systemErrorOnAddTunnel(systemError: error)))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -169,7 +169,20 @@ class TunnelsManager {
|
||||
}
|
||||
|
||||
func addMultiple(tunnelConfigurations: [TunnelConfiguration], completionHandler: @escaping (UInt, TunnelsManagerError?) -> Void) {
|
||||
addMultiple(tunnelConfigurations: ArraySlice(tunnelConfigurations), numberSuccessful: 0, lastError: nil, completionHandler: completionHandler)
|
||||
// Temporarily pause observation of changes to VPN configurations to prevent the feedback
|
||||
// loop that causes `reload()` to be called on each newly added tunnel, which significantly
|
||||
// impacts performance.
|
||||
configurationsObservationToken = nil
|
||||
|
||||
self.addMultiple(tunnelConfigurations: ArraySlice(tunnelConfigurations), numberSuccessful: 0, lastError: nil) { [weak self] numSucceeded, error in
|
||||
completionHandler(numSucceeded, error)
|
||||
|
||||
// Restart observation of changes to VPN configrations.
|
||||
self?.startObservingTunnelConfigurations()
|
||||
|
||||
// Force reload all configurations to make sure that all tunnels are up to date.
|
||||
self?.reload()
|
||||
}
|
||||
}
|
||||
|
||||
private func addMultiple(tunnelConfigurations: ArraySlice<TunnelConfiguration>, numberSuccessful: UInt, lastError: TunnelsManagerError?, completionHandler: @escaping (UInt, TunnelsManagerError?) -> Void) {
|
||||
@@ -193,7 +206,10 @@ class TunnelsManager {
|
||||
}
|
||||
}
|
||||
|
||||
func modify(tunnel: TunnelContainer, tunnelConfiguration: TunnelConfiguration, onDemandOption: ActivateOnDemandOption, completionHandler: @escaping (TunnelsManagerError?) -> Void) {
|
||||
func modify(tunnel: TunnelContainer, tunnelConfiguration: TunnelConfiguration,
|
||||
onDemandOption: ActivateOnDemandOption,
|
||||
shouldEnsureOnDemandEnabled: Bool = false,
|
||||
completionHandler: @escaping (TunnelsManagerError?) -> Void) {
|
||||
let tunnelName = tunnelConfiguration.name ?? ""
|
||||
if tunnelName.isEmpty {
|
||||
completionHandler(TunnelsManagerError.tunnelNameEmpty)
|
||||
@@ -201,6 +217,20 @@ class TunnelsManager {
|
||||
}
|
||||
|
||||
let tunnelProviderManager = tunnel.tunnelProvider
|
||||
|
||||
let isIntroducingOnDemandRules = (tunnelProviderManager.onDemandRules ?? []).isEmpty && onDemandOption != .off
|
||||
if isIntroducingOnDemandRules && tunnel.status != .inactive && tunnel.status != .deactivating {
|
||||
tunnel.onDeactivated = { [weak self] in
|
||||
self?.modify(tunnel: tunnel, tunnelConfiguration: tunnelConfiguration,
|
||||
onDemandOption: onDemandOption, shouldEnsureOnDemandEnabled: true,
|
||||
completionHandler: completionHandler)
|
||||
}
|
||||
self.startDeactivation(of: tunnel)
|
||||
return
|
||||
} else {
|
||||
tunnel.onDeactivated = nil
|
||||
}
|
||||
|
||||
let oldName = tunnelProviderManager.localizedDescription ?? ""
|
||||
let isNameChanged = tunnelName != oldName
|
||||
if isNameChanged {
|
||||
@@ -218,14 +248,17 @@ class TunnelsManager {
|
||||
}
|
||||
tunnelProviderManager.isEnabled = true
|
||||
|
||||
let isActivatingOnDemand = !tunnelProviderManager.isOnDemandEnabled && onDemandOption != .off
|
||||
let isActivatingOnDemand = !tunnelProviderManager.isOnDemandEnabled && shouldEnsureOnDemandEnabled
|
||||
onDemandOption.apply(on: tunnelProviderManager)
|
||||
if shouldEnsureOnDemandEnabled {
|
||||
tunnelProviderManager.isOnDemandEnabled = true
|
||||
}
|
||||
|
||||
tunnelProviderManager.saveToPreferences { [weak self] error in
|
||||
guard error == nil else {
|
||||
//TODO: the passwordReference for the old one has already been removed at this point and we can't easily roll back!
|
||||
wg_log(.error, message: "Modify: Saving configuration failed: \(error!)")
|
||||
completionHandler(TunnelsManagerError.systemErrorOnModifyTunnel(systemError: error!))
|
||||
if let error = error {
|
||||
// TODO: the passwordReference for the old one has already been removed at this point and we can't easily roll back!
|
||||
wg_log(.error, message: "Modify: Saving configuration failed: \(error)")
|
||||
completionHandler(TunnelsManagerError.systemErrorOnModifyTunnel(systemError: error))
|
||||
return
|
||||
}
|
||||
guard let self = self else { return }
|
||||
@@ -253,12 +286,12 @@ class TunnelsManager {
|
||||
// Without this, the tunnel stopes getting updates on the tunnel status from iOS.
|
||||
tunnelProviderManager.loadFromPreferences { error in
|
||||
tunnel.isActivateOnDemandEnabled = tunnelProviderManager.isOnDemandEnabled
|
||||
guard error == nil else {
|
||||
wg_log(.error, message: "Modify: Re-loading after saving configuration failed: \(error!)")
|
||||
completionHandler(TunnelsManagerError.systemErrorOnModifyTunnel(systemError: error!))
|
||||
return
|
||||
if let error = error {
|
||||
wg_log(.error, message: "Modify: Re-loading after saving configuration failed: \(error)")
|
||||
completionHandler(TunnelsManagerError.systemErrorOnModifyTunnel(systemError: error))
|
||||
} else {
|
||||
completionHandler(nil)
|
||||
}
|
||||
completionHandler(nil)
|
||||
}
|
||||
} else {
|
||||
completionHandler(nil)
|
||||
@@ -278,9 +311,9 @@ class TunnelsManager {
|
||||
#error("Unimplemented")
|
||||
#endif
|
||||
tunnelProviderManager.removeFromPreferences { [weak self] error in
|
||||
guard error == nil else {
|
||||
wg_log(.error, message: "Remove: Saving configuration failed: \(error!)")
|
||||
completionHandler(TunnelsManagerError.systemErrorOnRemoveTunnel(systemError: error!))
|
||||
if let error = error {
|
||||
wg_log(.error, message: "Remove: Saving configuration failed: \(error)")
|
||||
completionHandler(TunnelsManagerError.systemErrorOnRemoveTunnel(systemError: error))
|
||||
return
|
||||
}
|
||||
if let self = self, let index = self.tunnels.firstIndex(of: tunnel) {
|
||||
@@ -296,7 +329,20 @@ class TunnelsManager {
|
||||
}
|
||||
|
||||
func removeMultiple(tunnels: [TunnelContainer], completionHandler: @escaping (TunnelsManagerError?) -> Void) {
|
||||
removeMultiple(tunnels: ArraySlice(tunnels), completionHandler: completionHandler)
|
||||
// Temporarily pause observation of changes to VPN configurations to prevent the feedback
|
||||
// loop that causes `reload()` to be called for each removed tunnel, which significantly
|
||||
// impacts performance.
|
||||
configurationsObservationToken = nil
|
||||
|
||||
removeMultiple(tunnels: ArraySlice(tunnels)) { [weak self] error in
|
||||
completionHandler(error)
|
||||
|
||||
// Restart observation of changes to VPN configrations.
|
||||
self?.startObservingTunnelConfigurations()
|
||||
|
||||
// Force reload all configurations to make sure that all tunnels are up to date.
|
||||
self?.reload()
|
||||
}
|
||||
}
|
||||
|
||||
private func removeMultiple(tunnels: ArraySlice<TunnelContainer>, completionHandler: @escaping (TunnelsManagerError?) -> Void) {
|
||||
@@ -316,6 +362,41 @@ class TunnelsManager {
|
||||
}
|
||||
}
|
||||
|
||||
func setOnDemandEnabled(_ isOnDemandEnabled: Bool, on tunnel: TunnelContainer, completionHandler: @escaping (TunnelsManagerError?) -> Void) {
|
||||
let tunnelProviderManager = tunnel.tunnelProvider
|
||||
let isCurrentlyEnabled = (tunnelProviderManager.isOnDemandEnabled && tunnelProviderManager.isEnabled)
|
||||
guard isCurrentlyEnabled != isOnDemandEnabled else {
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
let isActivatingOnDemand = !tunnelProviderManager.isOnDemandEnabled && isOnDemandEnabled
|
||||
tunnelProviderManager.isOnDemandEnabled = isOnDemandEnabled
|
||||
tunnelProviderManager.isEnabled = true
|
||||
tunnelProviderManager.saveToPreferences { error in
|
||||
if let error = error {
|
||||
wg_log(.error, message: "Modify On-Demand: Saving configuration failed: \(error)")
|
||||
completionHandler(TunnelsManagerError.systemErrorOnModifyTunnel(systemError: error))
|
||||
return
|
||||
}
|
||||
if isActivatingOnDemand {
|
||||
// If we're enabling on-demand, we want to make sure the tunnel is enabled.
|
||||
// If not enabled, the OS will not turn the tunnel on/off based on our rules.
|
||||
tunnelProviderManager.loadFromPreferences { error in
|
||||
// isActivateOnDemandEnabled will get changed in reload(), but no harm in setting it here too
|
||||
tunnel.isActivateOnDemandEnabled = tunnelProviderManager.isOnDemandEnabled
|
||||
if let error = error {
|
||||
wg_log(.error, message: "Modify On-Demand: Re-loading after saving configuration failed: \(error)")
|
||||
completionHandler(TunnelsManagerError.systemErrorOnModifyTunnel(systemError: error))
|
||||
return
|
||||
}
|
||||
completionHandler(nil)
|
||||
}
|
||||
} else {
|
||||
completionHandler(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func numberOfTunnels() -> Int {
|
||||
return tunnels.count
|
||||
}
|
||||
@@ -363,7 +444,17 @@ class TunnelsManager {
|
||||
tunnel.status = .waiting
|
||||
activateWaitingTunnelOnDeactivation(of: tunnelInOperation)
|
||||
if tunnelInOperation.status != .deactivating {
|
||||
startDeactivation(of: tunnelInOperation)
|
||||
if tunnelInOperation.isActivateOnDemandEnabled {
|
||||
setOnDemandEnabled(false, on: tunnelInOperation) { [weak self] error in
|
||||
guard error == nil else {
|
||||
wg_log(.error, message: "Unable to activate tunnel '\(tunnel.name)' because on-demand could not be disabled on active tunnel '\(tunnel.name)'")
|
||||
return
|
||||
}
|
||||
self?.startDeactivation(of: tunnelInOperation)
|
||||
}
|
||||
} else {
|
||||
startDeactivation(of: tunnelInOperation)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -406,7 +497,7 @@ class TunnelsManager {
|
||||
}
|
||||
|
||||
private func startObservingTunnelStatuses() {
|
||||
statusObservationToken = NotificationCenter.default.addObserver(forName: .NEVPNStatusDidChange, object: nil, queue: OperationQueue.main) { [weak self] statusChangeNotification in
|
||||
statusObservationToken = NotificationCenter.default.observe(name: .NEVPNStatusDidChange, object: nil, queue: OperationQueue.main) { [weak self] statusChangeNotification in
|
||||
guard let self = self,
|
||||
let session = statusChangeNotification.object as? NETunnelProviderSession,
|
||||
let tunnelProvider = session.manager as? NETunnelProviderManager,
|
||||
@@ -428,6 +519,11 @@ class TunnelsManager {
|
||||
}
|
||||
}
|
||||
|
||||
if session.status == .disconnected {
|
||||
tunnel.onDeactivated?()
|
||||
tunnel.onDeactivated = nil
|
||||
}
|
||||
|
||||
if tunnel.status == .restarting && session.status == .disconnected {
|
||||
tunnel.startActivation(activationDelegate: self.activationDelegate)
|
||||
return
|
||||
@@ -438,7 +534,7 @@ class TunnelsManager {
|
||||
}
|
||||
|
||||
func startObservingTunnelConfigurations() {
|
||||
configurationsObservationToken = NotificationCenter.default.addObserver(forName: .NEVPNConfigurationChange, object: nil, queue: OperationQueue.main) { [weak self] _ in
|
||||
configurationsObservationToken = NotificationCenter.default.observe(name: .NEVPNConfigurationChange, object: nil, queue: OperationQueue.main) { [weak self] _ in
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
// We schedule reload() in a subsequent runloop to ensure that the completion handler of loadAllFromPreferences
|
||||
// (reload() calls loadAllFromPreferences) is called after the completion handler of the saveToPreferences or
|
||||
@@ -472,6 +568,7 @@ class TunnelContainer: NSObject {
|
||||
@objc dynamic var status: TunnelStatus
|
||||
|
||||
@objc dynamic var isActivateOnDemandEnabled: Bool
|
||||
@objc dynamic var hasOnDemandRules: Bool
|
||||
|
||||
var isAttemptingActivation = false {
|
||||
didSet {
|
||||
@@ -497,8 +594,14 @@ class TunnelContainer: NSObject {
|
||||
var activationAttemptId: String?
|
||||
var activationTimer: Timer?
|
||||
var deactivationTimer: Timer?
|
||||
var onDeactivated: (() -> Void)?
|
||||
|
||||
fileprivate var tunnelProvider: NETunnelProviderManager
|
||||
fileprivate var tunnelProvider: NETunnelProviderManager {
|
||||
didSet {
|
||||
isActivateOnDemandEnabled = tunnelProvider.isOnDemandEnabled && tunnelProvider.isEnabled
|
||||
hasOnDemandRules = !(tunnelProvider.onDemandRules ?? []).isEmpty
|
||||
}
|
||||
}
|
||||
|
||||
var tunnelConfiguration: TunnelConfiguration? {
|
||||
return tunnelProvider.tunnelConfiguration
|
||||
@@ -518,7 +621,8 @@ class TunnelContainer: NSObject {
|
||||
name = tunnel.localizedDescription ?? "Unnamed"
|
||||
let status = TunnelStatus(from: tunnel.connection.status)
|
||||
self.status = status
|
||||
isActivateOnDemandEnabled = tunnel.isOnDemandEnabled
|
||||
isActivateOnDemandEnabled = tunnel.isOnDemandEnabled && tunnel.isEnabled
|
||||
hasOnDemandRules = !(tunnel.onDemandRules ?? []).isEmpty
|
||||
tunnelProvider = tunnel
|
||||
super.init()
|
||||
}
|
||||
@@ -541,11 +645,10 @@ class TunnelContainer: NSObject {
|
||||
}
|
||||
|
||||
func refreshStatus() {
|
||||
if status == .restarting {
|
||||
if (status == .restarting) || (status == .waiting && tunnelProvider.connection.status == .disconnected) {
|
||||
return
|
||||
}
|
||||
status = TunnelStatus(from: tunnelProvider.connection.status)
|
||||
isActivateOnDemandEnabled = tunnelProvider.isOnDemandEnabled
|
||||
}
|
||||
|
||||
fileprivate func startActivation(recursionCount: UInt = 0, lastError: Error? = nil, activationDelegate: TunnelsManagerActivationDelegate?) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
@@ -51,20 +51,18 @@ class ActivateOnDemandViewModel {
|
||||
extension ActivateOnDemandViewModel {
|
||||
convenience init(tunnel: TunnelContainer) {
|
||||
self.init()
|
||||
if tunnel.isActivateOnDemandEnabled {
|
||||
switch tunnel.onDemandOption {
|
||||
case .off:
|
||||
break
|
||||
case .wiFiInterfaceOnly(let onDemandSSIDOption):
|
||||
isWiFiInterfaceEnabled = true
|
||||
(ssidOption, selectedSSIDs) = ssidViewModel(from: onDemandSSIDOption)
|
||||
case .nonWiFiInterfaceOnly:
|
||||
isNonWiFiInterfaceEnabled = true
|
||||
case .anyInterface(let onDemandSSIDOption):
|
||||
isWiFiInterfaceEnabled = true
|
||||
isNonWiFiInterfaceEnabled = true
|
||||
(ssidOption, selectedSSIDs) = ssidViewModel(from: onDemandSSIDOption)
|
||||
}
|
||||
switch tunnel.onDemandOption {
|
||||
case .off:
|
||||
break
|
||||
case .wiFiInterfaceOnly(let onDemandSSIDOption):
|
||||
isWiFiInterfaceEnabled = true
|
||||
(ssidOption, selectedSSIDs) = ssidViewModel(from: onDemandSSIDOption)
|
||||
case .nonWiFiInterfaceOnly:
|
||||
isNonWiFiInterfaceEnabled = true
|
||||
case .anyInterface(let onDemandSSIDOption):
|
||||
isWiFiInterfaceEnabled = true
|
||||
isNonWiFiInterfaceEnabled = true
|
||||
(ssidOption, selectedSSIDs) = ssidViewModel(from: onDemandSSIDOption)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
protocol ErrorPresenterProtocol {
|
||||
static func showErrorAlert(title: String, message: String, from sourceVC: AnyObject?, onPresented: (() -> Void)?, onDismissal: (() -> Void)?)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
import LocalAuthentication
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
@@ -398,12 +398,13 @@ class TunnelViewModel {
|
||||
|
||||
static let ipv4DefaultRouteString = "0.0.0.0/0"
|
||||
static let ipv4DefaultRouteModRFC1918String = [ // Set of all non-private IPv4 IPs
|
||||
"0.0.0.0/5", "8.0.0.0/7", "11.0.0.0/8", "12.0.0.0/6", "16.0.0.0/4", "32.0.0.0/3",
|
||||
"64.0.0.0/2", "128.0.0.0/3", "160.0.0.0/5", "168.0.0.0/6", "172.0.0.0/12",
|
||||
"172.32.0.0/11", "172.64.0.0/10", "172.128.0.0/9", "173.0.0.0/8", "174.0.0.0/7",
|
||||
"176.0.0.0/4", "192.0.0.0/9", "192.128.0.0/11", "192.160.0.0/13", "192.169.0.0/16",
|
||||
"192.170.0.0/15", "192.172.0.0/14", "192.176.0.0/12", "192.192.0.0/10",
|
||||
"193.0.0.0/8", "194.0.0.0/7", "196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4"
|
||||
"1.0.0.0/8", "2.0.0.0/8", "3.0.0.0/8", "4.0.0.0/6", "8.0.0.0/7", "11.0.0.0/8",
|
||||
"12.0.0.0/6", "16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/3",
|
||||
"160.0.0.0/5", "168.0.0.0/6", "172.0.0.0/12", "172.32.0.0/11", "172.64.0.0/10",
|
||||
"172.128.0.0/9", "173.0.0.0/8", "174.0.0.0/7", "176.0.0.0/4", "192.0.0.0/9",
|
||||
"192.128.0.0/11", "192.160.0.0/13", "192.169.0.0/16", "192.170.0.0/15",
|
||||
"192.172.0.0/14", "192.176.0.0/12", "192.192.0.0/10", "193.0.0.0/8",
|
||||
"194.0.0.0/7", "196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4"
|
||||
]
|
||||
|
||||
static func excludePrivateIPsFieldStates(isSinglePeer: Bool, allowedIPs: Set<String>) -> (shouldAllowExcludePrivateIPsControl: Bool, excludePrivateIPsValue: Bool) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import UIKit
|
||||
import os.log
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import UIKit
|
||||
import os.log
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
@@ -15,7 +15,7 @@ extension UITableView {
|
||||
}
|
||||
|
||||
func dequeueReusableCell<T: UITableViewCell>(for indexPath: IndexPath) -> T {
|
||||
//swiftlint:disable:next force_cast
|
||||
// swiftlint:disable:next force_cast
|
||||
return dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as! T
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
@@ -9,6 +9,11 @@ class EditableTextCell: UITableViewCell {
|
||||
set(value) { valueTextField.text = value }
|
||||
}
|
||||
|
||||
var placeholder: String? {
|
||||
get { return valueTextField.placeholder }
|
||||
set(value) { valueTextField.placeholder = value }
|
||||
}
|
||||
|
||||
let valueTextField: UITextField = {
|
||||
let valueTextField = UITextField()
|
||||
valueTextField.textAlignment = .left
|
||||
@@ -29,12 +34,13 @@ class EditableTextCell: UITableViewCell {
|
||||
valueTextField.delegate = self
|
||||
contentView.addSubview(valueTextField)
|
||||
valueTextField.translatesAutoresizingMaskIntoConstraints = false
|
||||
let bottomAnchorConstraint = contentView.layoutMarginsGuide.bottomAnchor.constraint(equalToSystemSpacingBelow: valueTextField.bottomAnchor, multiplier: 1)
|
||||
// Reduce the bottom margin by 0.5pt to maintain the default cell height (44pt)
|
||||
let bottomAnchorConstraint = contentView.layoutMarginsGuide.bottomAnchor.constraint(equalTo: valueTextField.bottomAnchor, constant: -0.5)
|
||||
bottomAnchorConstraint.priority = .defaultLow
|
||||
NSLayoutConstraint.activate([
|
||||
valueTextField.leadingAnchor.constraint(equalToSystemSpacingAfter: contentView.layoutMarginsGuide.leadingAnchor, multiplier: 1),
|
||||
contentView.layoutMarginsGuide.trailingAnchor.constraint(equalToSystemSpacingAfter: valueTextField.trailingAnchor, multiplier: 1),
|
||||
valueTextField.topAnchor.constraint(equalToSystemSpacingBelow: contentView.layoutMarginsGuide.topAnchor, multiplier: 1),
|
||||
valueTextField.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
|
||||
contentView.layoutMarginsGuide.trailingAnchor.constraint(equalTo: valueTextField.trailingAnchor),
|
||||
contentView.layoutMarginsGuide.topAnchor.constraint(equalTo: valueTextField.topAnchor),
|
||||
bottomAnchorConstraint
|
||||
])
|
||||
}
|
||||
@@ -50,6 +56,7 @@ class EditableTextCell: UITableViewCell {
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
message = ""
|
||||
placeholder = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
@@ -9,11 +9,7 @@ class KeyValueCell: UITableViewCell {
|
||||
let keyLabel = UILabel()
|
||||
keyLabel.font = UIFont.preferredFont(forTextStyle: .body)
|
||||
keyLabel.adjustsFontForContentSizeCategory = true
|
||||
if #available(iOS 13.0, *) {
|
||||
keyLabel.textColor = .label
|
||||
} else {
|
||||
keyLabel.textColor = .black
|
||||
}
|
||||
keyLabel.textColor = .label
|
||||
keyLabel.textAlignment = .left
|
||||
return keyLabel
|
||||
}()
|
||||
@@ -27,7 +23,7 @@ class KeyValueCell: UITableViewCell {
|
||||
}()
|
||||
|
||||
let valueTextField: UITextField = {
|
||||
let valueTextField = UITextField()
|
||||
let valueTextField = KeyValueCellTextField()
|
||||
valueTextField.textAlignment = .right
|
||||
valueTextField.isEnabled = false
|
||||
valueTextField.font = UIFont.preferredFont(forTextStyle: .body)
|
||||
@@ -35,11 +31,7 @@ class KeyValueCell: UITableViewCell {
|
||||
valueTextField.autocapitalizationType = .none
|
||||
valueTextField.autocorrectionType = .no
|
||||
valueTextField.spellCheckingType = .no
|
||||
if #available(iOS 13.0, *) {
|
||||
valueTextField.textColor = .secondaryLabel
|
||||
} else {
|
||||
valueTextField.textColor = .gray
|
||||
}
|
||||
valueTextField.textColor = .secondaryLabel
|
||||
return valueTextField
|
||||
}()
|
||||
|
||||
@@ -64,18 +56,10 @@ class KeyValueCell: UITableViewCell {
|
||||
|
||||
var isValueValid = true {
|
||||
didSet {
|
||||
if #available(iOS 13.0, *) {
|
||||
if isValueValid {
|
||||
keyLabel.textColor = .label
|
||||
} else {
|
||||
keyLabel.textColor = .systemRed
|
||||
}
|
||||
if isValueValid {
|
||||
keyLabel.textColor = .label
|
||||
} else {
|
||||
if isValueValid {
|
||||
keyLabel.textColor = .black
|
||||
} else {
|
||||
keyLabel.textColor = .red
|
||||
}
|
||||
keyLabel.textColor = .systemRed
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,8 +99,6 @@ class KeyValueCell: UITableViewCell {
|
||||
expandToFitValueLabelConstraint.priority = .defaultLow + 1
|
||||
expandToFitValueLabelConstraint.isActive = true
|
||||
|
||||
contentView.addSubview(valueLabelScrollView)
|
||||
|
||||
contentView.addSubview(valueLabelScrollView)
|
||||
valueLabelScrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
@@ -234,3 +216,10 @@ extension KeyValueCell: UITextFieldDelegate {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class KeyValueCellTextField: UITextField {
|
||||
override func placeholderRect(forBounds bounds: CGRect) -> CGRect {
|
||||
// UIKit renders the placeholder label 0.5pt higher
|
||||
return super.placeholderRect(forBounds: bounds).integral.offsetBy(dx: 0, dy: -0.5)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
@@ -16,17 +16,15 @@ class SwitchCell: UITableViewCell {
|
||||
get { return switchView.isEnabled }
|
||||
set(value) {
|
||||
switchView.isEnabled = value
|
||||
if #available(iOS 13.0, *) {
|
||||
textLabel?.textColor = value ? .label : .secondaryLabel
|
||||
} else {
|
||||
textLabel?.textColor = value ? .black : .gray
|
||||
}
|
||||
textLabel?.textColor = value ? .label : .secondaryLabel
|
||||
}
|
||||
}
|
||||
|
||||
var onSwitchToggled: ((Bool) -> Void)?
|
||||
|
||||
var observationToken: AnyObject?
|
||||
var statusObservationToken: AnyObject?
|
||||
var isOnDemandEnabledObservationToken: AnyObject?
|
||||
var hasOnDemandRulesObservationToken: AnyObject?
|
||||
|
||||
let switchView = UISwitch()
|
||||
|
||||
@@ -51,6 +49,8 @@ class SwitchCell: UITableViewCell {
|
||||
isEnabled = true
|
||||
message = ""
|
||||
isOn = false
|
||||
observationToken = nil
|
||||
statusObservationToken = nil
|
||||
isOnDemandEnabledObservationToken = nil
|
||||
hasOnDemandRulesObservationToken = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
@@ -28,11 +28,7 @@ class TextCell: UITableViewCell {
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
message = ""
|
||||
if #available(iOS 13.0, *) {
|
||||
setTextColor(.label)
|
||||
} else {
|
||||
setTextColor(.black)
|
||||
}
|
||||
setTextColor(.label)
|
||||
setTextAlignment(.left)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
@@ -30,11 +30,7 @@ class TunnelEditEditableKeyValueCell: TunnelEditKeyValueCell {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
copyableGesture = false
|
||||
if #available(iOS 13.0, *) {
|
||||
valueTextField.textColor = .label
|
||||
} else {
|
||||
valueTextField.textColor = .black
|
||||
}
|
||||
valueTextField.textColor = .label
|
||||
valueTextField.isEnabled = true
|
||||
valueLabelScrollView.isScrollEnabled = false
|
||||
valueTextField.widthAnchor.constraint(equalTo: valueLabelScrollView.widthAnchor).isActive = true
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
@@ -12,9 +12,16 @@ class TunnelListCell: UITableViewCell {
|
||||
self?.nameLabel.text = tunnel.name
|
||||
}
|
||||
// Bind to the tunnel's status
|
||||
update(from: tunnel?.status, animated: false)
|
||||
update(from: tunnel, animated: false)
|
||||
statusObservationToken = tunnel?.observe(\.status) { [weak self] tunnel, _ in
|
||||
self?.update(from: tunnel.status, animated: true)
|
||||
self?.update(from: tunnel, animated: true)
|
||||
}
|
||||
// Bind to tunnel's on-demand settings
|
||||
isOnDemandEnabledObservationToken = tunnel?.observe(\.isActivateOnDemandEnabled) { [weak self] tunnel, _ in
|
||||
self?.update(from: tunnel, animated: true)
|
||||
}
|
||||
hasOnDemandRulesObservationToken = tunnel?.observe(\.hasOnDemandRules) { [weak self] tunnel, _ in
|
||||
self?.update(from: tunnel, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,33 +35,45 @@ class TunnelListCell: UITableViewCell {
|
||||
return nameLabel
|
||||
}()
|
||||
|
||||
let onDemandLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.text = ""
|
||||
label.font = UIFont.preferredFont(forTextStyle: .caption2)
|
||||
label.adjustsFontForContentSizeCategory = true
|
||||
label.numberOfLines = 1
|
||||
label.textColor = .secondaryLabel
|
||||
return label
|
||||
}()
|
||||
|
||||
let busyIndicator: UIActivityIndicatorView = {
|
||||
let busyIndicator: UIActivityIndicatorView
|
||||
if #available(iOS 13.0, *) {
|
||||
busyIndicator = UIActivityIndicatorView(style: .medium)
|
||||
} else {
|
||||
busyIndicator = UIActivityIndicatorView(style: .gray)
|
||||
}
|
||||
busyIndicator = UIActivityIndicatorView(style: .medium)
|
||||
busyIndicator.hidesWhenStopped = true
|
||||
return busyIndicator
|
||||
}()
|
||||
|
||||
let statusSwitch = UISwitch()
|
||||
|
||||
private var statusObservationToken: NSKeyValueObservation?
|
||||
private var nameObservationToken: NSKeyValueObservation?
|
||||
private var statusObservationToken: NSKeyValueObservation?
|
||||
private var isOnDemandEnabledObservationToken: NSKeyValueObservation?
|
||||
private var hasOnDemandRulesObservationToken: NSKeyValueObservation?
|
||||
|
||||
private var subTitleLabelBottomConstraint: NSLayoutConstraint?
|
||||
private var nameLabelBottomConstraint: NSLayoutConstraint?
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
accessoryType = .disclosureIndicator
|
||||
|
||||
for subview in [statusSwitch, busyIndicator, nameLabel] {
|
||||
for subview in [statusSwitch, busyIndicator, onDemandLabel, nameLabel] {
|
||||
subview.translatesAutoresizingMaskIntoConstraints = false
|
||||
contentView.addSubview(subview)
|
||||
}
|
||||
|
||||
nameLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
||||
onDemandLabel.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
|
||||
|
||||
let nameLabelBottomConstraint =
|
||||
contentView.layoutMarginsGuide.bottomAnchor.constraint(equalToSystemSpacingBelow: nameLabel.bottomAnchor, multiplier: 1)
|
||||
@@ -64,13 +83,18 @@ class TunnelListCell: UITableViewCell {
|
||||
statusSwitch.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
|
||||
statusSwitch.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
|
||||
statusSwitch.leadingAnchor.constraint(equalToSystemSpacingAfter: busyIndicator.trailingAnchor, multiplier: 1),
|
||||
statusSwitch.leadingAnchor.constraint(equalToSystemSpacingAfter: onDemandLabel.trailingAnchor, multiplier: 1),
|
||||
|
||||
nameLabel.topAnchor.constraint(equalToSystemSpacingBelow: contentView.layoutMarginsGuide.topAnchor, multiplier: 1),
|
||||
nameLabelBottomConstraint,
|
||||
nameLabel.leadingAnchor.constraint(equalToSystemSpacingAfter: contentView.layoutMarginsGuide.leadingAnchor, multiplier: 1),
|
||||
nameLabel.trailingAnchor.constraint(lessThanOrEqualTo: statusSwitch.leadingAnchor),
|
||||
nameLabelBottomConstraint,
|
||||
|
||||
onDemandLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
|
||||
onDemandLabel.leadingAnchor.constraint(equalToSystemSpacingAfter: nameLabel.trailingAnchor, multiplier: 1),
|
||||
|
||||
busyIndicator.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
|
||||
busyIndicator.leadingAnchor.constraint(equalToSystemSpacingAfter: nameLabel.trailingAnchor, multiplier: 1)
|
||||
busyIndicator.leadingAnchor.constraint(greaterThanOrEqualToSystemSpacingAfter: nameLabel.trailingAnchor, multiplier: 1)
|
||||
])
|
||||
|
||||
statusSwitch.addTarget(self, action: #selector(switchToggled), for: .valueChanged)
|
||||
@@ -94,21 +118,43 @@ class TunnelListCell: UITableViewCell {
|
||||
onSwitchToggled?(statusSwitch.isOn)
|
||||
}
|
||||
|
||||
private func update(from status: TunnelStatus?, animated: Bool) {
|
||||
guard let status = status else {
|
||||
private func update(from tunnel: TunnelContainer?, animated: Bool) {
|
||||
guard let tunnel = tunnel else {
|
||||
reset(animated: animated)
|
||||
return
|
||||
}
|
||||
statusSwitch.setOn(!(status == .deactivating || status == .inactive), animated: animated)
|
||||
statusSwitch.isUserInteractionEnabled = (status == .inactive || status == .active)
|
||||
if status == .inactive || status == .active {
|
||||
busyIndicator.stopAnimating()
|
||||
let status = tunnel.status
|
||||
let isOnDemandEngaged = tunnel.isActivateOnDemandEnabled
|
||||
|
||||
let shouldSwitchBeOn = ((status != .deactivating && status != .inactive) || isOnDemandEngaged)
|
||||
statusSwitch.setOn(shouldSwitchBeOn, animated: true)
|
||||
|
||||
if isOnDemandEngaged && !(status == .activating || status == .active) {
|
||||
statusSwitch.onTintColor = UIColor.systemYellow
|
||||
} else {
|
||||
busyIndicator.startAnimating()
|
||||
statusSwitch.onTintColor = UIColor.systemGreen
|
||||
}
|
||||
|
||||
statusSwitch.isUserInteractionEnabled = (status == .inactive || status == .active)
|
||||
|
||||
if tunnel.hasOnDemandRules {
|
||||
onDemandLabel.text = isOnDemandEngaged ? tr("tunnelListCaptionOnDemand") : ""
|
||||
busyIndicator.stopAnimating()
|
||||
statusSwitch.isUserInteractionEnabled = true
|
||||
} else {
|
||||
onDemandLabel.text = ""
|
||||
if status == .inactive || status == .active {
|
||||
busyIndicator.stopAnimating()
|
||||
} else {
|
||||
busyIndicator.startAnimating()
|
||||
}
|
||||
statusSwitch.isUserInteractionEnabled = (status == .inactive || status == .active)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private func reset(animated: Bool) {
|
||||
statusSwitch.thumbTintColor = nil
|
||||
statusSwitch.setOn(false, animated: animated)
|
||||
statusSwitch.isUserInteractionEnabled = false
|
||||
busyIndicator.stopAnimating()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
@@ -15,15 +15,9 @@ class LogViewController: UIViewController {
|
||||
}()
|
||||
|
||||
let busyIndicator: UIActivityIndicatorView = {
|
||||
if #available(iOS 13.0, *) {
|
||||
let busyIndicator = UIActivityIndicatorView(style: .medium)
|
||||
busyIndicator.hidesWhenStopped = true
|
||||
return busyIndicator
|
||||
} else {
|
||||
let busyIndicator = UIActivityIndicatorView(style: .gray)
|
||||
busyIndicator.hidesWhenStopped = true
|
||||
return busyIndicator
|
||||
}
|
||||
let busyIndicator = UIActivityIndicatorView(style: .medium)
|
||||
busyIndicator.hidesWhenStopped = true
|
||||
return busyIndicator
|
||||
}()
|
||||
|
||||
let paragraphStyle: NSParagraphStyle = {
|
||||
@@ -41,12 +35,7 @@ class LogViewController: UIViewController {
|
||||
|
||||
override func loadView() {
|
||||
view = UIView()
|
||||
if #available(iOS 13.0, *) {
|
||||
view.backgroundColor = .systemBackground
|
||||
} else {
|
||||
view.backgroundColor = .white
|
||||
}
|
||||
|
||||
view.backgroundColor = .systemBackground
|
||||
view.addSubview(textView)
|
||||
textView.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
@@ -92,15 +81,8 @@ class LogViewController: UIViewController {
|
||||
let bodyFont = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body)
|
||||
let captionFont = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.caption1)
|
||||
for logEntry in fetchedLogEntries {
|
||||
var bgColor: UIColor
|
||||
var fgColor: UIColor
|
||||
if #available(iOS 13.0, *) {
|
||||
bgColor = self.isNextLineHighlighted ? .systemGray3 : .systemBackground
|
||||
fgColor = .label
|
||||
} else {
|
||||
bgColor = self.isNextLineHighlighted ? UIColor(white: 0.88, alpha: 1.0) : UIColor.white
|
||||
fgColor = .black
|
||||
}
|
||||
let bgColor: UIColor = self.isNextLineHighlighted ? .systemGray3 : .systemBackground
|
||||
let fgColor: UIColor = .label
|
||||
let timestampText = NSAttributedString(string: logEntry.timestamp + "\n", attributes: [.font: captionFont, .backgroundColor: bgColor, .foregroundColor: fgColor, .paragraphStyle: self.paragraphStyle])
|
||||
let messageText = NSAttributedString(string: logEntry.message + "\n", attributes: [.font: bodyFont, .backgroundColor: bgColor, .foregroundColor: fgColor, .paragraphStyle: self.paragraphStyle])
|
||||
richText.append(timestampText)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
@@ -11,11 +11,7 @@ class MainViewController: UISplitViewController {
|
||||
|
||||
init() {
|
||||
let detailVC = UIViewController()
|
||||
if #available(iOS 13.0, *) {
|
||||
detailVC.view.backgroundColor = .systemBackground
|
||||
} else {
|
||||
detailVC.view.backgroundColor = .white
|
||||
}
|
||||
detailVC.view.backgroundColor = .systemBackground
|
||||
let detailNC = UINavigationController(rootViewController: detailVC)
|
||||
|
||||
let masterVC = TunnelsListTableViewController()
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import AVFoundation
|
||||
import UIKit
|
||||
|
||||
protocol QRScanViewControllerDelegate: class {
|
||||
protocol QRScanViewControllerDelegate: AnyObject {
|
||||
func addScannedQRCode(tunnelConfiguration: TunnelConfiguration, qrScanViewController: QRScanViewController, completionHandler: (() -> Void)?)
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
+34
-30
@@ -1,10 +1,11 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import UIKit
|
||||
import SystemConfiguration.CaptiveNetwork
|
||||
import NetworkExtension
|
||||
|
||||
protocol SSIDOptionEditTableViewControllerDelegate: class {
|
||||
protocol SSIDOptionEditTableViewControllerDelegate: AnyObject {
|
||||
func ssidOptionSaved(option: ActivateOnDemandViewModel.OnDemandSSIDOption, ssids: [String])
|
||||
}
|
||||
|
||||
@@ -39,9 +40,16 @@ class SSIDOptionEditTableViewController: UITableViewController {
|
||||
selectedOption = option
|
||||
selectedSSIDs = ssids
|
||||
super.init(style: .grouped)
|
||||
connectedSSID = getConnectedSSID()
|
||||
loadSections()
|
||||
loadAddSSIDRows()
|
||||
addSSIDRows.removeAll()
|
||||
addSSIDRows.append(.addNewSSID)
|
||||
|
||||
getConnectedSSID { [weak self] ssid in
|
||||
guard let self = self else { return }
|
||||
self.connectedSSID = ssid
|
||||
self.updateCurrentSSIDEntry()
|
||||
self.updateTableViewAddSSIDRows()
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
@@ -60,6 +68,7 @@ class SSIDOptionEditTableViewController: UITableViewController {
|
||||
tableView.register(TextCell.self)
|
||||
tableView.isEditing = true
|
||||
tableView.allowsSelectionDuringEditing = true
|
||||
tableView.keyboardDismissMode = .onDrag
|
||||
}
|
||||
|
||||
func loadSections() {
|
||||
@@ -71,14 +80,14 @@ class SSIDOptionEditTableViewController: UITableViewController {
|
||||
}
|
||||
}
|
||||
|
||||
func loadAddSSIDRows() {
|
||||
addSSIDRows.removeAll()
|
||||
if let connectedSSID = connectedSSID {
|
||||
if !selectedSSIDs.contains(connectedSSID) {
|
||||
addSSIDRows.append(.addConnectedSSID(connectedSSID: connectedSSID))
|
||||
func updateCurrentSSIDEntry() {
|
||||
if let connectedSSID = connectedSSID, !selectedSSIDs.contains(connectedSSID) {
|
||||
if let first = addSSIDRows.first, case .addNewSSID = first {
|
||||
addSSIDRows.insert(.addConnectedSSID(connectedSSID: connectedSSID), at: 0)
|
||||
}
|
||||
} else if let first = addSSIDRows.first, case .addConnectedSSID = first {
|
||||
addSSIDRows.removeFirst()
|
||||
}
|
||||
addSSIDRows.append(.addNewSSID)
|
||||
}
|
||||
|
||||
func updateTableViewAddSSIDRows() {
|
||||
@@ -176,11 +185,7 @@ extension SSIDOptionEditTableViewController {
|
||||
private func noSSIDsCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell: TextCell = tableView.dequeueReusableCell(for: indexPath)
|
||||
cell.message = tr("tunnelOnDemandNoSSIDs")
|
||||
if #available(iOS 13.0, *) {
|
||||
cell.setTextColor(.secondaryLabel)
|
||||
} else {
|
||||
cell.setTextColor(.gray)
|
||||
}
|
||||
cell.setTextColor(.secondaryLabel)
|
||||
cell.setTextAlignment(.center)
|
||||
return cell
|
||||
}
|
||||
@@ -188,12 +193,13 @@ extension SSIDOptionEditTableViewController {
|
||||
private func selectedSSIDCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell: EditableTextCell = tableView.dequeueReusableCell(for: indexPath)
|
||||
cell.message = selectedSSIDs[indexPath.row]
|
||||
cell.placeholder = tr("tunnelOnDemandSSIDTextFieldPlaceholder")
|
||||
cell.isEditing = true
|
||||
cell.onValueBeingEdited = { [weak self, weak cell] text in
|
||||
guard let self = self, let cell = cell else { return }
|
||||
if let row = self.tableView.indexPath(for: cell)?.row {
|
||||
self.selectedSSIDs[row] = text
|
||||
self.loadAddSSIDRows()
|
||||
self.updateCurrentSSIDEntry()
|
||||
self.updateTableViewAddSSIDRows()
|
||||
}
|
||||
}
|
||||
@@ -224,7 +230,7 @@ extension SSIDOptionEditTableViewController {
|
||||
} else {
|
||||
tableView.reloadRows(at: [indexPath], with: .automatic)
|
||||
}
|
||||
loadAddSSIDRows()
|
||||
updateCurrentSSIDEntry()
|
||||
updateTableViewAddSSIDRows()
|
||||
case .addSSIDs:
|
||||
assert(editingStyle == .insert)
|
||||
@@ -244,7 +250,7 @@ extension SSIDOptionEditTableViewController {
|
||||
} else {
|
||||
tableView.insertRows(at: [indexPath], with: .automatic)
|
||||
}
|
||||
loadAddSSIDRows()
|
||||
updateCurrentSSIDEntry()
|
||||
updateTableViewAddSSIDRows()
|
||||
if newSSID.isEmpty {
|
||||
if let selectedSSIDCell = tableView.cellForRow(at: indexPath) as? EditableTextCell {
|
||||
@@ -253,6 +259,16 @@ extension SSIDOptionEditTableViewController {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func getConnectedSSID(completionHandler: @escaping (String?) -> Void) {
|
||||
#if targetEnvironment(simulator)
|
||||
completionHandler("Simulator Wi-Fi")
|
||||
#else
|
||||
NEHotspotNetwork.fetchCurrent { hotspotNetwork in
|
||||
completionHandler(hotspotNetwork?.ssid)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
extension SSIDOptionEditTableViewController {
|
||||
@@ -289,15 +305,3 @@ extension SSIDOptionEditTableViewController {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func getConnectedSSID() -> String? {
|
||||
guard let supportedInterfaces = CNCopySupportedInterfaces() as? [CFString] else { return nil }
|
||||
for interface in supportedInterfaces {
|
||||
if let networkInfo = CNCopyCurrentNetworkInfo(interface) {
|
||||
if let ssid = (networkInfo as NSDictionary)[kCNNetworkInfoKeySSID as String] as? String {
|
||||
return !ssid.isEmpty ? ssid : nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import UIKit
|
||||
import os.log
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
@@ -324,8 +324,22 @@ extension TunnelDetailTableViewController {
|
||||
private func statusCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell: SwitchCell = tableView.dequeueReusableCell(for: indexPath)
|
||||
|
||||
let statusUpdate: (SwitchCell, TunnelStatus) -> Void = { cell, status in
|
||||
let text: String
|
||||
func update(cell: SwitchCell?, with tunnel: TunnelContainer) {
|
||||
guard let cell = cell else { return }
|
||||
|
||||
let status = tunnel.status
|
||||
let isOnDemandEngaged = tunnel.isActivateOnDemandEnabled
|
||||
|
||||
let isSwitchOn = (status == .activating || status == .active || isOnDemandEngaged)
|
||||
cell.switchView.setOn(isSwitchOn, animated: true)
|
||||
|
||||
if isOnDemandEngaged && !(status == .activating || status == .active) {
|
||||
cell.switchView.onTintColor = UIColor.systemYellow
|
||||
} else {
|
||||
cell.switchView.onTintColor = UIColor.systemGreen
|
||||
}
|
||||
|
||||
var text: String
|
||||
switch status {
|
||||
case .inactive:
|
||||
text = tr("tunnelStatusInactive")
|
||||
@@ -342,24 +356,49 @@ extension TunnelDetailTableViewController {
|
||||
case .waiting:
|
||||
text = tr("tunnelStatusWaiting")
|
||||
}
|
||||
|
||||
if tunnel.hasOnDemandRules {
|
||||
text += isOnDemandEngaged ? tr("tunnelStatusAddendumOnDemand") : ""
|
||||
cell.switchView.isUserInteractionEnabled = true
|
||||
cell.isEnabled = true
|
||||
} else {
|
||||
cell.switchView.isUserInteractionEnabled = (status == .inactive || status == .active)
|
||||
cell.isEnabled = (status == .inactive || status == .active)
|
||||
}
|
||||
|
||||
if tunnel.hasOnDemandRules && !isOnDemandEngaged && status == .inactive {
|
||||
text = tr("tunnelStatusOnDemandDisabled")
|
||||
}
|
||||
|
||||
cell.textLabel?.text = text
|
||||
cell.switchView.isOn = !(status == .deactivating || status == .inactive)
|
||||
cell.switchView.isUserInteractionEnabled = (status == .inactive || status == .active)
|
||||
cell.isEnabled = status == .active || status == .inactive
|
||||
}
|
||||
|
||||
statusUpdate(cell, tunnel.status)
|
||||
cell.observationToken = tunnel.observe(\.status) { [weak cell] tunnel, _ in
|
||||
guard let cell = cell else { return }
|
||||
statusUpdate(cell, tunnel.status)
|
||||
update(cell: cell, with: tunnel)
|
||||
cell.statusObservationToken = tunnel.observe(\.status) { [weak cell] tunnel, _ in
|
||||
update(cell: cell, with: tunnel)
|
||||
}
|
||||
cell.isOnDemandEnabledObservationToken = tunnel.observe(\.isActivateOnDemandEnabled) { [weak cell] tunnel, _ in
|
||||
update(cell: cell, with: tunnel)
|
||||
}
|
||||
cell.hasOnDemandRulesObservationToken = tunnel.observe(\.hasOnDemandRules) { [weak cell] tunnel, _ in
|
||||
update(cell: cell, with: tunnel)
|
||||
}
|
||||
|
||||
cell.onSwitchToggled = { [weak self] isOn in
|
||||
guard let self = self else { return }
|
||||
if isOn {
|
||||
self.tunnelsManager.startActivation(of: self.tunnel)
|
||||
|
||||
if self.tunnel.hasOnDemandRules {
|
||||
self.tunnelsManager.setOnDemandEnabled(isOn, on: self.tunnel) { error in
|
||||
if error == nil && !isOn {
|
||||
self.tunnelsManager.startDeactivation(of: self.tunnel)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.tunnelsManager.startDeactivation(of: self.tunnel)
|
||||
if isOn {
|
||||
self.tunnelsManager.startActivation(of: self.tunnel)
|
||||
} else {
|
||||
self.tunnelsManager.startDeactivation(of: self.tunnel)
|
||||
}
|
||||
}
|
||||
}
|
||||
return cell
|
||||
@@ -395,6 +434,7 @@ extension TunnelDetailTableViewController {
|
||||
let cell: KeyValueCell = tableView.dequeueReusableCell(for: indexPath)
|
||||
cell.key = field.localizedUIString
|
||||
cell.value = onDemandViewModel.localizedInterfaceDescription
|
||||
cell.copyableGesture = false
|
||||
return cell
|
||||
} else {
|
||||
assert(field == .ssid)
|
||||
@@ -402,6 +442,7 @@ extension TunnelDetailTableViewController {
|
||||
let cell: KeyValueCell = tableView.dequeueReusableCell(for: indexPath)
|
||||
cell.key = field.localizedUIString
|
||||
cell.value = onDemandViewModel.ssidOption.localizedUIString
|
||||
cell.copyableGesture = false
|
||||
return cell
|
||||
} else {
|
||||
let cell: ChevronCell = tableView.dequeueReusableCell(for: indexPath)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import UIKit
|
||||
|
||||
protocol TunnelEditTableViewControllerDelegate: class {
|
||||
protocol TunnelEditTableViewControllerDelegate: AnyObject {
|
||||
func tunnelSaved(tunnel: TunnelContainer)
|
||||
func tunnelEditingCancelled()
|
||||
}
|
||||
@@ -318,7 +318,7 @@ extension TunnelEditTableViewController {
|
||||
let removedSectionIndices = self.deletePeer(peer: peerData)
|
||||
let shouldShowExcludePrivateIPs = (self.tunnelViewModel.peersData.count == 1 && self.tunnelViewModel.peersData[0].shouldAllowExcludePrivateIPsControl)
|
||||
|
||||
//swiftlint:disable:next trailing_closure
|
||||
// swiftlint:disable:next trailing_closure
|
||||
tableView.performBatchUpdates({
|
||||
self.tableView.deleteSections(removedSectionIndices, with: .fade)
|
||||
if shouldShowExcludePrivateIPs {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import UIKit
|
||||
import MobileCoreServices
|
||||
@@ -33,11 +33,7 @@ class TunnelsListTableViewController: UIViewController {
|
||||
|
||||
let busyIndicator: UIActivityIndicatorView = {
|
||||
let busyIndicator: UIActivityIndicatorView
|
||||
if #available(iOS 13.0, *) {
|
||||
busyIndicator = UIActivityIndicatorView(style: .medium)
|
||||
} else {
|
||||
busyIndicator = UIActivityIndicatorView(style: .gray)
|
||||
}
|
||||
busyIndicator = UIActivityIndicatorView(style: .medium)
|
||||
busyIndicator.hidesWhenStopped = true
|
||||
return busyIndicator
|
||||
}()
|
||||
@@ -51,11 +47,7 @@ class TunnelsListTableViewController: UIViewController {
|
||||
|
||||
override func loadView() {
|
||||
view = UIView()
|
||||
if #available(iOS 13.0, *) {
|
||||
view.backgroundColor = .systemBackground
|
||||
} else {
|
||||
view.backgroundColor = .white
|
||||
}
|
||||
view.backgroundColor = .systemBackground
|
||||
|
||||
tableView.dataSource = self
|
||||
tableView.delegate = self
|
||||
@@ -317,10 +309,18 @@ extension TunnelsListTableViewController: UITableViewDataSource {
|
||||
cell.tunnel = tunnel
|
||||
cell.onSwitchToggled = { [weak self] isOn in
|
||||
guard let self = self, let tunnelsManager = self.tunnelsManager else { return }
|
||||
if isOn {
|
||||
tunnelsManager.startActivation(of: tunnel)
|
||||
if tunnel.hasOnDemandRules {
|
||||
tunnelsManager.setOnDemandEnabled(isOn, on: tunnel) { error in
|
||||
if error == nil && !isOn {
|
||||
tunnelsManager.startDeactivation(of: tunnel)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tunnelsManager.startDeactivation(of: tunnel)
|
||||
if isOn {
|
||||
tunnelsManager.startActivation(of: tunnel)
|
||||
} else {
|
||||
tunnelsManager.startDeactivation(of: tunnel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -398,11 +398,7 @@ extension TunnelsListTableViewController: TunnelsManagerListDelegate {
|
||||
(splitViewController.viewControllers[0] as? UINavigationController)?.popToRootViewController(animated: false)
|
||||
} else {
|
||||
let detailVC = UIViewController()
|
||||
if #available(iOS 13.0, *) {
|
||||
detailVC.view.backgroundColor = .systemBackground
|
||||
} else {
|
||||
detailVC.view.backgroundColor = .white
|
||||
}
|
||||
detailVC.view.backgroundColor = .systemBackground
|
||||
let detailNC = UINavigationController(rootViewController: detailVC)
|
||||
splitViewController.showDetailViewController(detailNC, sender: self)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
import ServiceManagement
|
||||
@@ -65,19 +65,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows: Bool) -> Bool {
|
||||
if let appleEvent = NSAppleEventManager.shared().currentAppleEvent {
|
||||
if LaunchedAtLoginDetector.isReopenedByLoginItemHelper(reopenAppleEvent: appleEvent) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if hasVisibleWindows {
|
||||
return true
|
||||
}
|
||||
showManageTunnelsWindow(completion: nil)
|
||||
return false
|
||||
}
|
||||
|
||||
@objc func confirmAndQuit() {
|
||||
let alert = NSAlert()
|
||||
alert.messageText = tr("macConfirmAndQuitAlertMessage")
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
|
||||
class Application: NSApplication {
|
||||
|
||||
private var appDelegate: AppDelegate? //swiftlint:disable:this weak_delegate
|
||||
private var appDelegate: AppDelegate? // swiftlint:disable:this weak_delegate
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "StatusCircleYellow@1x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "StatusCircleYellow@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 364 B |
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 602 B |
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<key>LSUIElement</key>
|
||||
<true/>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.</string>
|
||||
<string>Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>WireGuard.Application</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
|
||||
@@ -1,28 +1,19 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
|
||||
class LaunchedAtLoginDetector {
|
||||
static let launchCode = "LaunchedByWireGuardLoginItemHelper"
|
||||
|
||||
static func isLaunchedAtLogin(openAppleEvent: NSAppleEventDescriptor) -> Bool {
|
||||
guard isOpenEvent(openAppleEvent) else { return false }
|
||||
guard let propData = openAppleEvent.paramDescriptor(forKeyword: keyAEPropData) else { return false }
|
||||
return propData.stringValue == launchCode
|
||||
}
|
||||
|
||||
static func isReopenedByLoginItemHelper(reopenAppleEvent: NSAppleEventDescriptor) -> Bool {
|
||||
guard isReopenEvent(reopenAppleEvent) else { return false }
|
||||
guard let propData = reopenAppleEvent.paramDescriptor(forKeyword: keyAEPropData) else { return false }
|
||||
return propData.stringValue == launchCode
|
||||
let now = clock_gettime_nsec_np(CLOCK_UPTIME_RAW)
|
||||
guard openAppleEvent.eventClass == kCoreEventClass && openAppleEvent.eventID == kAEOpenApplication else { return false }
|
||||
guard let url = FileManager.loginHelperTimestampURL else { return false }
|
||||
guard let data = try? Data(contentsOf: url) else { return false }
|
||||
_ = FileManager.deleteFile(at: url)
|
||||
guard data.count == 8 else { return false }
|
||||
let then = data.withUnsafeBytes { ptr in
|
||||
ptr.load(as: UInt64.self)
|
||||
}
|
||||
return now - then <= 20000000000
|
||||
}
|
||||
}
|
||||
|
||||
private func isOpenEvent(_ event: NSAppleEventDescriptor) -> Bool {
|
||||
return event.eventClass == kCoreEventClass && event.eventID == kAEOpenApplication
|
||||
}
|
||||
|
||||
private func isReopenEvent(_ event: NSAppleEventDescriptor) -> Bool {
|
||||
return event.eventClass == kCoreEventClass && event.eventID == kAEReopenApplication
|
||||
}
|
||||
|
||||
@@ -25,12 +25,14 @@
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.</string>
|
||||
<string>Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>LSBackgroundOnly</key>
|
||||
<true/>
|
||||
<key>com.wireguard.macos.app_id</key>
|
||||
<string>$(APP_ID_MACOS)</string>
|
||||
<key>com.wireguard.macos.app_group_id</key>
|
||||
<string>$(DEVELOPMENT_TEAM).group.$(APP_ID_MACOS)</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -4,5 +4,9 @@
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>$(DEVELOPMENT_TEAM).group.$(APP_ID_MACOS)</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,17 +1,32 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
NSString *appIdInfoDictionaryKey = @"com.wireguard.macos.app_id";
|
||||
NSString *appId = [NSBundle.mainBundle objectForInfoDictionaryKey:appIdInfoDictionaryKey];
|
||||
NSString *appId = [NSBundle.mainBundle objectForInfoDictionaryKey:@"com.wireguard.macos.app_id"];
|
||||
NSString *appGroupId = [NSBundle.mainBundle objectForInfoDictionaryKey:@"com.wireguard.macos.app_group_id"];
|
||||
if (!appId || !appGroupId)
|
||||
return 1;
|
||||
NSURL *containerUrl = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:appGroupId];
|
||||
if (!containerUrl)
|
||||
return 2;
|
||||
uint64_t now = clock_gettime_nsec_np(CLOCK_UPTIME_RAW);
|
||||
if (![[NSData dataWithBytes:&now length:sizeof(now)] writeToURL:[containerUrl URLByAppendingPathComponent:@"login-helper-timestamp.bin"] atomically:YES])
|
||||
return 3;
|
||||
|
||||
NSString *launchCode = @"LaunchedByWireGuardLoginItemHelper";
|
||||
NSAppleEventDescriptor *paramDescriptor = [NSAppleEventDescriptor descriptorWithString:launchCode];
|
||||
|
||||
[NSWorkspace.sharedWorkspace launchAppWithBundleIdentifier:appId options:NSWorkspaceLaunchWithoutActivation
|
||||
additionalEventParamDescriptor:paramDescriptor launchIdentifier:NULL];
|
||||
NSCondition *condition = [[NSCondition alloc] init];
|
||||
NSURL *appURL = [NSWorkspace.sharedWorkspace URLForApplicationWithBundleIdentifier:appId];
|
||||
if (!appURL)
|
||||
return 4;
|
||||
NSWorkspaceOpenConfiguration *openConfiguration = [NSWorkspaceOpenConfiguration configuration];
|
||||
openConfiguration.activates = NO;
|
||||
openConfiguration.addsToRecentItems = NO;
|
||||
openConfiguration.hides = YES;
|
||||
[NSWorkspace.sharedWorkspace openApplicationAtURL:appURL configuration:openConfiguration completionHandler:^(NSRunningApplication * _Nullable app, NSError * _Nullable error) {
|
||||
[condition signal];
|
||||
}];
|
||||
[condition wait];
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
|
||||
@@ -8,11 +8,12 @@ class MacAppStoreUpdateDetector {
|
||||
guard isQuitEvent(quitAppleEvent) else { return false }
|
||||
guard let senderPIDDescriptor = quitAppleEvent.attributeDescriptor(forKeyword: keySenderPIDAttr) else { return false }
|
||||
let pid = senderPIDDescriptor.int32Value
|
||||
wg_log(.debug, message: "aevt/quit Apple event received from pid: \(pid)")
|
||||
guard let executablePath = getExecutablePath(from: pid) else { return false }
|
||||
wg_log(.debug, message: "aevt/quit Apple event received from: \(executablePath)")
|
||||
wg_log(.debug, message: "aevt/quit Apple event received from executable: \(executablePath)")
|
||||
if executablePath.hasPrefix("/System/Library/") {
|
||||
let executableName = URL(fileURLWithPath: executablePath, isDirectory: false).lastPathComponent
|
||||
return executableName == "com.apple.CommerceKit.StoreAEService"
|
||||
return executableName.hasPrefix("com.apple.") && executableName.hasSuffix(".StoreAEService")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import AppKit
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
|
||||
@@ -7,7 +7,7 @@ extension NSTableView {
|
||||
func dequeueReusableCell<T: NSView>() -> T {
|
||||
let identifier = NSUserInterfaceItemIdentifier(NSStringFromClass(T.self))
|
||||
if let cellView = makeView(withIdentifier: identifier, owner: self) {
|
||||
//swiftlint:disable:next force_cast
|
||||
// swiftlint:disable:next force_cast
|
||||
return cellView as! T
|
||||
}
|
||||
let cellView = T()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
|
||||
protocol StatusMenuWindowDelegate: class {
|
||||
protocol StatusMenuWindowDelegate: AnyObject {
|
||||
func showManageTunnelsWindow(completion: ((NSWindow?) -> Void)?)
|
||||
}
|
||||
|
||||
@@ -14,8 +14,14 @@ class StatusMenu: NSMenu {
|
||||
var statusMenuItem: NSMenuItem?
|
||||
var networksMenuItem: NSMenuItem?
|
||||
var deactivateMenuItem: NSMenuItem?
|
||||
var firstTunnelMenuItemIndex = 0
|
||||
var numberOfTunnelMenuItems = 0
|
||||
|
||||
private let tunnelsBreakdownMenu = NSMenu()
|
||||
private let tunnelsMenuItem = NSMenuItem(title: tr("macTunnelsMenuTitle"), action: nil, keyEquivalent: "")
|
||||
private let tunnelsMenuSeparatorItem = NSMenuItem.separator()
|
||||
|
||||
private var firstTunnelMenuItemIndex = 0
|
||||
private var numberOfTunnelMenuItems = 0
|
||||
private var tunnelsPresentationStyle = StatusMenuTunnelsPresentationStyle.inline
|
||||
|
||||
var currentTunnel: TunnelContainer? {
|
||||
didSet {
|
||||
@@ -26,16 +32,20 @@ class StatusMenu: NSMenu {
|
||||
|
||||
init(tunnelsManager: TunnelsManager) {
|
||||
self.tunnelsManager = tunnelsManager
|
||||
|
||||
super.init(title: tr("macMenuTitle"))
|
||||
|
||||
addStatusMenuItems()
|
||||
addItem(NSMenuItem.separator())
|
||||
|
||||
tunnelsMenuItem.submenu = tunnelsBreakdownMenu
|
||||
addItem(tunnelsMenuItem)
|
||||
|
||||
firstTunnelMenuItemIndex = numberOfItems
|
||||
let isAdded = addTunnelMenuItems()
|
||||
if isAdded {
|
||||
addItem(NSMenuItem.separator())
|
||||
}
|
||||
populateInitialTunnelMenuItems()
|
||||
|
||||
addItem(tunnelsMenuSeparatorItem)
|
||||
|
||||
addTunnelManagementItems()
|
||||
addItem(NSMenuItem.separator())
|
||||
addApplicationItems()
|
||||
@@ -108,15 +118,6 @@ class StatusMenu: NSMenu {
|
||||
deactivateMenuItem.isHidden = tunnel.status != .active
|
||||
}
|
||||
|
||||
func addTunnelMenuItems() -> Bool {
|
||||
let numberOfTunnels = tunnelsManager.numberOfTunnels()
|
||||
for index in 0 ..< tunnelsManager.numberOfTunnels() {
|
||||
let tunnel = tunnelsManager.tunnel(at: index)
|
||||
insertTunnelMenuItem(for: tunnel, at: numberOfTunnelMenuItems)
|
||||
}
|
||||
return numberOfTunnels > 0
|
||||
}
|
||||
|
||||
func addTunnelManagementItems() {
|
||||
let manageItem = NSMenuItem(title: tr("macMenuManageTunnels"), action: #selector(manageTunnelsClicked), keyEquivalent: "")
|
||||
manageItem.target = self
|
||||
@@ -143,10 +144,20 @@ class StatusMenu: NSMenu {
|
||||
|
||||
@objc func tunnelClicked(sender: AnyObject) {
|
||||
guard let tunnelMenuItem = sender as? TunnelMenuItem else { return }
|
||||
if tunnelMenuItem.state == .off {
|
||||
tunnelsManager.startActivation(of: tunnelMenuItem.tunnel)
|
||||
let tunnel = tunnelMenuItem.tunnel
|
||||
if tunnel.hasOnDemandRules {
|
||||
let turnOn = !tunnel.isActivateOnDemandEnabled
|
||||
tunnelsManager.setOnDemandEnabled(turnOn, on: tunnel) { error in
|
||||
if error == nil && !turnOn {
|
||||
self.tunnelsManager.startDeactivation(of: tunnel)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tunnelsManager.startDeactivation(of: tunnelMenuItem.tunnel)
|
||||
if tunnel.status == .inactive {
|
||||
tunnelsManager.startActivation(of: tunnel)
|
||||
} else if tunnel.status == .active {
|
||||
tunnelsManager.startDeactivation(of: tunnel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,34 +177,121 @@ class StatusMenu: NSMenu {
|
||||
|
||||
extension StatusMenu {
|
||||
func insertTunnelMenuItem(for tunnel: TunnelContainer, at tunnelIndex: Int) {
|
||||
let menuItem = TunnelMenuItem(tunnel: tunnel, action: #selector(tunnelClicked(sender:)))
|
||||
menuItem.target = self
|
||||
menuItem.isHidden = !tunnel.isTunnelAvailableToUser
|
||||
insertItem(menuItem, at: firstTunnelMenuItemIndex + tunnelIndex)
|
||||
if numberOfTunnelMenuItems == 0 {
|
||||
insertItem(NSMenuItem.separator(), at: firstTunnelMenuItemIndex + tunnelIndex + 1)
|
||||
let nextNumberOfTunnels = numberOfTunnelMenuItems + 1
|
||||
|
||||
guard !reparentTunnelMenuItems(nextNumberOfTunnels: nextNumberOfTunnels) else {
|
||||
return
|
||||
}
|
||||
numberOfTunnelMenuItems += 1
|
||||
|
||||
let menuItem = makeTunnelItem(tunnel: tunnel)
|
||||
switch tunnelsPresentationStyle {
|
||||
case .submenu:
|
||||
tunnelsBreakdownMenu.insertItem(menuItem, at: tunnelIndex)
|
||||
case .inline:
|
||||
insertItem(menuItem, at: firstTunnelMenuItemIndex + tunnelIndex)
|
||||
}
|
||||
|
||||
numberOfTunnelMenuItems = nextNumberOfTunnels
|
||||
updateTunnelsMenuItemVisibility()
|
||||
}
|
||||
|
||||
func removeTunnelMenuItem(at tunnelIndex: Int) {
|
||||
removeItem(at: firstTunnelMenuItemIndex + tunnelIndex)
|
||||
numberOfTunnelMenuItems -= 1
|
||||
if numberOfTunnelMenuItems == 0 {
|
||||
if let firstItem = item(at: firstTunnelMenuItemIndex), firstItem.isSeparatorItem {
|
||||
removeItem(at: firstTunnelMenuItemIndex)
|
||||
}
|
||||
let nextNumberOfTunnels = numberOfTunnelMenuItems - 1
|
||||
|
||||
guard !reparentTunnelMenuItems(nextNumberOfTunnels: nextNumberOfTunnels) else {
|
||||
return
|
||||
}
|
||||
|
||||
switch tunnelsPresentationStyle {
|
||||
case .submenu:
|
||||
tunnelsBreakdownMenu.removeItem(at: tunnelIndex)
|
||||
case .inline:
|
||||
removeItem(at: firstTunnelMenuItemIndex + tunnelIndex)
|
||||
}
|
||||
|
||||
numberOfTunnelMenuItems = nextNumberOfTunnels
|
||||
updateTunnelsMenuItemVisibility()
|
||||
}
|
||||
|
||||
func moveTunnelMenuItem(from oldTunnelIndex: Int, to newTunnelIndex: Int) {
|
||||
guard let oldMenuItem = item(at: firstTunnelMenuItemIndex + oldTunnelIndex) as? TunnelMenuItem else { return }
|
||||
let oldMenuItemTunnel = oldMenuItem.tunnel
|
||||
removeItem(at: firstTunnelMenuItemIndex + oldTunnelIndex)
|
||||
let menuItem = TunnelMenuItem(tunnel: oldMenuItemTunnel, action: #selector(tunnelClicked(sender:)))
|
||||
menuItem.target = self
|
||||
insertItem(menuItem, at: firstTunnelMenuItemIndex + newTunnelIndex)
|
||||
let tunnel = tunnelsManager.tunnel(at: newTunnelIndex)
|
||||
let menuItem = makeTunnelItem(tunnel: tunnel)
|
||||
|
||||
switch tunnelsPresentationStyle {
|
||||
case .submenu:
|
||||
tunnelsBreakdownMenu.removeItem(at: oldTunnelIndex)
|
||||
tunnelsBreakdownMenu.insertItem(menuItem, at: newTunnelIndex)
|
||||
case .inline:
|
||||
removeItem(at: firstTunnelMenuItemIndex + oldTunnelIndex)
|
||||
insertItem(menuItem, at: firstTunnelMenuItemIndex + newTunnelIndex)
|
||||
}
|
||||
}
|
||||
|
||||
private func makeTunnelItem(tunnel: TunnelContainer) -> TunnelMenuItem {
|
||||
let menuItem = TunnelMenuItem(tunnel: tunnel, action: #selector(tunnelClicked(sender:)))
|
||||
menuItem.target = self
|
||||
menuItem.isHidden = !tunnel.isTunnelAvailableToUser
|
||||
return menuItem
|
||||
}
|
||||
|
||||
private func populateInitialTunnelMenuItems() {
|
||||
let numberOfTunnels = tunnelsManager.numberOfTunnels()
|
||||
let initialStyle = tunnelsPresentationStyle.preferredPresentationStyle(numberOfTunnels: numberOfTunnels)
|
||||
|
||||
tunnelsPresentationStyle = initialStyle
|
||||
switch initialStyle {
|
||||
case .inline:
|
||||
numberOfTunnelMenuItems = addTunnelMenuItems(into: self, at: firstTunnelMenuItemIndex)
|
||||
case .submenu:
|
||||
numberOfTunnelMenuItems = addTunnelMenuItems(into: tunnelsBreakdownMenu, at: 0)
|
||||
}
|
||||
|
||||
updateTunnelsMenuItemVisibility()
|
||||
}
|
||||
|
||||
private func reparentTunnelMenuItems(nextNumberOfTunnels: Int) -> Bool {
|
||||
let nextStyle = tunnelsPresentationStyle.preferredPresentationStyle(numberOfTunnels: nextNumberOfTunnels)
|
||||
|
||||
switch (tunnelsPresentationStyle, nextStyle) {
|
||||
case (.inline, .submenu):
|
||||
tunnelsPresentationStyle = nextStyle
|
||||
for index in (0..<numberOfTunnelMenuItems).reversed() {
|
||||
removeItem(at: firstTunnelMenuItemIndex + index)
|
||||
}
|
||||
numberOfTunnelMenuItems = addTunnelMenuItems(into: tunnelsBreakdownMenu, at: 0)
|
||||
updateTunnelsMenuItemVisibility()
|
||||
return true
|
||||
|
||||
case (.submenu, .inline):
|
||||
tunnelsPresentationStyle = nextStyle
|
||||
tunnelsBreakdownMenu.removeAllItems()
|
||||
numberOfTunnelMenuItems = addTunnelMenuItems(into: self, at: firstTunnelMenuItemIndex)
|
||||
updateTunnelsMenuItemVisibility()
|
||||
return true
|
||||
|
||||
case (.submenu, .submenu), (.inline, .inline):
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private func addTunnelMenuItems(into menu: NSMenu, at startIndex: Int) -> Int {
|
||||
let numberOfTunnels = tunnelsManager.numberOfTunnels()
|
||||
for tunnelIndex in 0..<numberOfTunnels {
|
||||
let tunnel = tunnelsManager.tunnel(at: tunnelIndex)
|
||||
let menuItem = makeTunnelItem(tunnel: tunnel)
|
||||
menu.insertItem(menuItem, at: startIndex + tunnelIndex)
|
||||
}
|
||||
return numberOfTunnels
|
||||
}
|
||||
|
||||
private func updateTunnelsMenuItemVisibility() {
|
||||
switch tunnelsPresentationStyle {
|
||||
case .inline:
|
||||
tunnelsMenuItem.isHidden = true
|
||||
case .submenu:
|
||||
tunnelsMenuItem.isHidden = false
|
||||
}
|
||||
tunnelsMenuSeparatorItem.isHidden = numberOfTunnelMenuItems == 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,6 +301,7 @@ class TunnelMenuItem: NSMenuItem {
|
||||
|
||||
private var statusObservationToken: AnyObject?
|
||||
private var nameObservationToken: AnyObject?
|
||||
private var isOnDemandEnabledObservationToken: AnyObject?
|
||||
|
||||
init(tunnel: TunnelContainer, action selector: Selector?) {
|
||||
self.tunnel = tunnel
|
||||
@@ -215,7 +314,12 @@ class TunnelMenuItem: NSMenuItem {
|
||||
let nameObservationToken = tunnel.observe(\TunnelContainer.name) { [weak self] _, _ in
|
||||
self?.updateTitle()
|
||||
}
|
||||
let isOnDemandEnabledObservationToken = tunnel.observe(\.isActivateOnDemandEnabled) { [weak self] _, _ in
|
||||
self?.updateTitle()
|
||||
self?.updateStatus()
|
||||
}
|
||||
self.statusObservationToken = statusObservationToken
|
||||
self.isOnDemandEnabledObservationToken = isOnDemandEnabledObservationToken
|
||||
self.nameObservationToken = nameObservationToken
|
||||
}
|
||||
|
||||
@@ -224,11 +328,35 @@ class TunnelMenuItem: NSMenuItem {
|
||||
}
|
||||
|
||||
func updateTitle() {
|
||||
title = tunnel.name
|
||||
if tunnel.isActivateOnDemandEnabled {
|
||||
title = tunnel.name + " (On-Demand)"
|
||||
} else {
|
||||
title = tunnel.name
|
||||
}
|
||||
}
|
||||
|
||||
func updateStatus() {
|
||||
let shouldShowCheckmark = (tunnel.status != .inactive && tunnel.status != .deactivating)
|
||||
state = shouldShowCheckmark ? .on : .off
|
||||
if tunnel.isActivateOnDemandEnabled {
|
||||
state = (tunnel.status == .inactive || tunnel.status == .deactivating) ? .mixed : .on
|
||||
} else {
|
||||
state = (tunnel.status == .inactive || tunnel.status == .deactivating) ? .off : .on
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum StatusMenuTunnelsPresentationStyle {
|
||||
case inline
|
||||
case submenu
|
||||
|
||||
func preferredPresentationStyle(numberOfTunnels: Int) -> StatusMenuTunnelsPresentationStyle {
|
||||
let maxInlineTunnels = 10
|
||||
|
||||
if case .inline = self, numberOfTunnels > maxInlineTunnels {
|
||||
return .submenu
|
||||
} else if case .submenu = self, numberOfTunnels <= maxInlineTunnels {
|
||||
return .inline
|
||||
} else {
|
||||
return self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
|
||||
@@ -28,7 +28,9 @@ class ButtonRow: NSView {
|
||||
}
|
||||
|
||||
var onButtonClicked: (() -> Void)?
|
||||
var observationToken: AnyObject?
|
||||
var statusObservationToken: AnyObject?
|
||||
var isOnDemandEnabledObservationToken: AnyObject?
|
||||
var hasOnDemandRulesObservationToken: AnyObject?
|
||||
|
||||
override var intrinsicContentSize: NSSize {
|
||||
return NSSize(width: NSView.noIntrinsicMetric, height: button.intrinsicContentSize.height)
|
||||
@@ -62,6 +64,8 @@ class ButtonRow: NSView {
|
||||
buttonTitle = ""
|
||||
buttonToolTip = ""
|
||||
onButtonClicked = nil
|
||||
observationToken = nil
|
||||
statusObservationToken = nil
|
||||
isOnDemandEnabledObservationToken = nil
|
||||
hasOnDemandRulesObservationToken = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
|
||||
@@ -16,7 +16,7 @@ class ConfTextStorage: NSTextStorage {
|
||||
private(set) var hasError = false
|
||||
private(set) var privateKeyString: String?
|
||||
|
||||
private(set) var hasOnePeer: Bool = false
|
||||
private(set) var hasOnePeer = false
|
||||
private(set) var lastOnePeerAllowedIPs = [String]()
|
||||
private(set) var lastOnePeerDNSServers = [String]()
|
||||
private(set) var lastOnePeerHasPublicKey = false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
|
||||
@@ -7,7 +7,7 @@ class ConfTextView: NSTextView {
|
||||
|
||||
private let confTextStorage = ConfTextStorage()
|
||||
|
||||
@objc dynamic var hasError: Bool = false
|
||||
@objc dynamic var hasError = false
|
||||
@objc dynamic var privateKeyString: String?
|
||||
@objc dynamic var singlePeerAllowedIPs: [String]?
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
|
||||
@@ -49,7 +49,9 @@ class EditableKeyValueRow: NSView {
|
||||
set(value) { valueImageView?.image = value }
|
||||
}
|
||||
|
||||
var observationToken: AnyObject?
|
||||
var statusObservationToken: AnyObject?
|
||||
var isOnDemandEnabledObservationToken: AnyObject?
|
||||
var hasOnDemandRulesObservationToken: AnyObject?
|
||||
|
||||
override var intrinsicContentSize: NSSize {
|
||||
let height = max(keyLabel.intrinsicContentSize.height, valueLabel.intrinsicContentSize.height)
|
||||
@@ -108,7 +110,9 @@ class EditableKeyValueRow: NSView {
|
||||
key = ""
|
||||
value = ""
|
||||
isKeyInBold = false
|
||||
observationToken = nil
|
||||
statusObservationToken = nil
|
||||
isOnDemandEnabledObservationToken = nil
|
||||
hasOnDemandRulesObservationToken = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
import CoreWLAN
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
|
||||
@@ -12,9 +12,12 @@ class TunnelListRow: NSView {
|
||||
self?.nameLabel.stringValue = tunnel.name
|
||||
}
|
||||
// Bind to the tunnel's status
|
||||
statusImageView.image = TunnelListRow.image(for: tunnel?.status)
|
||||
statusImageView.image = TunnelListRow.image(for: tunnel)
|
||||
statusObservationToken = tunnel?.observe(\TunnelContainer.status) { [weak self] tunnel, _ in
|
||||
self?.statusImageView.image = TunnelListRow.image(for: tunnel.status)
|
||||
self?.statusImageView.image = TunnelListRow.image(for: tunnel)
|
||||
}
|
||||
isOnDemandEnabledObservationToken = tunnel?.observe(\TunnelContainer.isActivateOnDemandEnabled) { [weak self] tunnel, _ in
|
||||
self?.statusImageView.image = TunnelListRow.image(for: tunnel)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,6 +36,7 @@ class TunnelListRow: NSView {
|
||||
|
||||
private var statusObservationToken: AnyObject?
|
||||
private var nameObservationToken: AnyObject?
|
||||
private var isOnDemandEnabledObservationToken: AnyObject?
|
||||
|
||||
init() {
|
||||
super.init(frame: CGRect.zero)
|
||||
@@ -56,15 +60,19 @@ class TunnelListRow: NSView {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
static func image(for status: TunnelStatus?) -> NSImage? {
|
||||
guard let status = status else { return nil }
|
||||
switch status {
|
||||
static func image(for tunnel: TunnelContainer?) -> NSImage? {
|
||||
guard let tunnel = tunnel else { return nil }
|
||||
switch tunnel.status {
|
||||
case .active, .restarting, .reasserting:
|
||||
return NSImage(named: NSImage.statusAvailableName)
|
||||
case .activating, .waiting, .deactivating:
|
||||
return NSImage(named: NSImage.statusPartiallyAvailableName)
|
||||
case .inactive:
|
||||
return NSImage(named: NSImage.statusNoneName)
|
||||
if tunnel.isActivateOnDemandEnabled {
|
||||
return NSImage(named: NSImage.Name.statusOnDemandEnabled)
|
||||
} else {
|
||||
return NSImage(named: NSImage.statusNoneName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,3 +81,7 @@ class TunnelListRow: NSView {
|
||||
statusImageView.image = nil
|
||||
}
|
||||
}
|
||||
|
||||
extension NSImage.Name {
|
||||
static let statusOnDemandEnabled = NSImage.Name("StatusCircleYellow")
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
/*
|
||||
* Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/* SPDX-License-Identifier: MIT */
|
||||
/*
|
||||
* Copyright (C) 2015-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
|
||||
@@ -18,6 +18,9 @@ class LogViewController: NSViewController {
|
||||
}
|
||||
}
|
||||
|
||||
private var boundsChangedNotificationToken: NotificationToken?
|
||||
private var frameChangedNotificationToken: NotificationToken?
|
||||
|
||||
let scrollView: NSScrollView = {
|
||||
let scrollView = NSScrollView()
|
||||
scrollView.hasVerticalScroller = true
|
||||
@@ -104,13 +107,13 @@ class LogViewController: NSViewController {
|
||||
clipView.documentView = tableView
|
||||
scrollView.contentView = clipView
|
||||
|
||||
_ = NotificationCenter.default.addObserver(forName: NSView.boundsDidChangeNotification, object: clipView, queue: OperationQueue.main) { [weak self] _ in
|
||||
boundsChangedNotificationToken = NotificationCenter.default.observe(name: NSView.boundsDidChangeNotification, object: clipView, queue: OperationQueue.main) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
let lastVisibleRowIndex = self.tableView.row(at: NSPoint(x: 0, y: self.scrollView.contentView.documentVisibleRect.maxY - 1))
|
||||
self.isInScrolledToEndMode = lastVisibleRowIndex < 0 || lastVisibleRowIndex == self.logEntries.count - 1
|
||||
}
|
||||
|
||||
_ = NotificationCenter.default.addObserver(forName: NSView.frameDidChangeNotification, object: tableView, queue: OperationQueue.main) { [weak self] _ in
|
||||
frameChangedNotificationToken = NotificationCenter.default.observe(name: NSView.frameDidChangeNotification, object: tableView, queue: OperationQueue.main) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
if self.isInScrolledToEndMode {
|
||||
DispatchQueue.main.async {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
|
||||
|
||||
+86
-43
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
|
||||
@@ -216,10 +216,19 @@ class TunnelDetailTableViewController: NSViewController {
|
||||
}
|
||||
|
||||
@objc func handleToggleActiveStatusAction() {
|
||||
if tunnel.status == .inactive {
|
||||
tunnelsManager.startActivation(of: tunnel)
|
||||
} else if tunnel.status == .active {
|
||||
tunnelsManager.startDeactivation(of: tunnel)
|
||||
if tunnel.hasOnDemandRules {
|
||||
let turnOn = !tunnel.isActivateOnDemandEnabled
|
||||
tunnelsManager.setOnDemandEnabled(turnOn, on: tunnel) { error in
|
||||
if error == nil && !turnOn {
|
||||
self.tunnelsManager.startDeactivation(of: self.tunnel)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if tunnel.status == .inactive {
|
||||
tunnelsManager.startActivation(of: tunnel)
|
||||
} else if tunnel.status == .active {
|
||||
tunnelsManager.startDeactivation(of: tunnel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,79 +431,113 @@ extension TunnelDetailTableViewController: NSTableViewDelegate {
|
||||
func statusCell() -> NSView {
|
||||
let cell: KeyValueImageRow = tableView.dequeueReusableCell()
|
||||
cell.key = tr(format: "macFieldKey (%@)", tr("tunnelInterfaceStatus"))
|
||||
cell.value = TunnelDetailTableViewController.localizedStatusDescription(forStatus: tunnel.status)
|
||||
cell.valueImage = TunnelDetailTableViewController.image(forStatus: tunnel.status)
|
||||
cell.observationToken = tunnel.observe(\.status) { [weak cell] tunnel, _ in
|
||||
cell.value = TunnelDetailTableViewController.localizedStatusDescription(for: tunnel)
|
||||
cell.valueImage = TunnelDetailTableViewController.image(for: tunnel)
|
||||
let changeHandler: (TunnelContainer, Any) -> Void = { [weak cell] tunnel, _ in
|
||||
guard let cell = cell else { return }
|
||||
cell.value = TunnelDetailTableViewController.localizedStatusDescription(forStatus: tunnel.status)
|
||||
cell.valueImage = TunnelDetailTableViewController.image(forStatus: tunnel.status)
|
||||
cell.value = TunnelDetailTableViewController.localizedStatusDescription(for: tunnel)
|
||||
cell.valueImage = TunnelDetailTableViewController.image(for: tunnel)
|
||||
}
|
||||
cell.statusObservationToken = tunnel.observe(\.status, changeHandler: changeHandler)
|
||||
cell.isOnDemandEnabledObservationToken = tunnel.observe(\.isActivateOnDemandEnabled, changeHandler: changeHandler)
|
||||
cell.hasOnDemandRulesObservationToken = tunnel.observe(\.hasOnDemandRules, changeHandler: changeHandler)
|
||||
return cell
|
||||
}
|
||||
|
||||
func toggleStatusCell() -> NSView {
|
||||
let cell: ButtonRow = tableView.dequeueReusableCell()
|
||||
cell.buttonTitle = TunnelDetailTableViewController.localizedToggleStatusActionText(forStatus: tunnel.status)
|
||||
cell.isButtonEnabled = (tunnel.status == .active || tunnel.status == .inactive)
|
||||
cell.buttonTitle = TunnelDetailTableViewController.localizedToggleStatusActionText(for: tunnel)
|
||||
cell.isButtonEnabled = (tunnel.hasOnDemandRules || tunnel.status == .active || tunnel.status == .inactive)
|
||||
cell.buttonToolTip = tr("macToolTipToggleStatus")
|
||||
cell.onButtonClicked = { [weak self] in
|
||||
self?.handleToggleActiveStatusAction()
|
||||
}
|
||||
cell.observationToken = tunnel.observe(\.status) { [weak cell] tunnel, _ in
|
||||
let changeHandler: (TunnelContainer, Any) -> Void = { [weak cell] tunnel, _ in
|
||||
guard let cell = cell else { return }
|
||||
cell.buttonTitle = TunnelDetailTableViewController.localizedToggleStatusActionText(forStatus: tunnel.status)
|
||||
cell.isButtonEnabled = (tunnel.status == .active || tunnel.status == .inactive)
|
||||
cell.buttonTitle = TunnelDetailTableViewController.localizedToggleStatusActionText(for: tunnel)
|
||||
cell.isButtonEnabled = (tunnel.hasOnDemandRules || tunnel.status == .active || tunnel.status == .inactive)
|
||||
}
|
||||
cell.statusObservationToken = tunnel.observe(\.status, changeHandler: changeHandler)
|
||||
cell.isOnDemandEnabledObservationToken = tunnel.observe(\.isActivateOnDemandEnabled, changeHandler: changeHandler)
|
||||
cell.hasOnDemandRulesObservationToken = tunnel.observe(\.hasOnDemandRules, changeHandler: changeHandler)
|
||||
return cell
|
||||
}
|
||||
|
||||
private static func localizedStatusDescription(forStatus status: TunnelStatus) -> String {
|
||||
private static func localizedStatusDescription(for tunnel: TunnelContainer) -> String {
|
||||
let status = tunnel.status
|
||||
let isOnDemandEngaged = tunnel.isActivateOnDemandEnabled
|
||||
|
||||
var text: String
|
||||
switch status {
|
||||
case .inactive:
|
||||
return tr("tunnelStatusInactive")
|
||||
text = tr("tunnelStatusInactive")
|
||||
case .activating:
|
||||
return tr("tunnelStatusActivating")
|
||||
text = tr("tunnelStatusActivating")
|
||||
case .active:
|
||||
return tr("tunnelStatusActive")
|
||||
text = tr("tunnelStatusActive")
|
||||
case .deactivating:
|
||||
return tr("tunnelStatusDeactivating")
|
||||
text = tr("tunnelStatusDeactivating")
|
||||
case .reasserting:
|
||||
return tr("tunnelStatusReasserting")
|
||||
text = tr("tunnelStatusReasserting")
|
||||
case .restarting:
|
||||
return tr("tunnelStatusRestarting")
|
||||
text = tr("tunnelStatusRestarting")
|
||||
case .waiting:
|
||||
return tr("tunnelStatusWaiting")
|
||||
text = tr("tunnelStatusWaiting")
|
||||
}
|
||||
|
||||
if tunnel.hasOnDemandRules {
|
||||
text += isOnDemandEngaged ?
|
||||
tr("tunnelStatusAddendumOnDemandEnabled") : tr("tunnelStatusAddendumOnDemandDisabled")
|
||||
}
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
private static func image(forStatus status: TunnelStatus?) -> NSImage? {
|
||||
guard let status = status else { return nil }
|
||||
switch status {
|
||||
private static func image(for tunnel: TunnelContainer?) -> NSImage? {
|
||||
guard let tunnel = tunnel else { return nil }
|
||||
switch tunnel.status {
|
||||
case .active, .restarting, .reasserting:
|
||||
return NSImage(named: NSImage.statusAvailableName)
|
||||
case .activating, .waiting, .deactivating:
|
||||
return NSImage(named: NSImage.statusPartiallyAvailableName)
|
||||
case .inactive:
|
||||
return NSImage(named: NSImage.statusNoneName)
|
||||
if tunnel.isActivateOnDemandEnabled {
|
||||
return NSImage(named: NSImage.Name.statusOnDemandEnabled)
|
||||
} else {
|
||||
return NSImage(named: NSImage.statusNoneName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func localizedToggleStatusActionText(forStatus status: TunnelStatus) -> String {
|
||||
switch status {
|
||||
case .waiting:
|
||||
return tr("macToggleStatusButtonWaiting")
|
||||
case .inactive:
|
||||
return tr("macToggleStatusButtonActivate")
|
||||
case .activating:
|
||||
return tr("macToggleStatusButtonActivating")
|
||||
case .active:
|
||||
return tr("macToggleStatusButtonDeactivate")
|
||||
case .deactivating:
|
||||
return tr("macToggleStatusButtonDeactivating")
|
||||
case .reasserting:
|
||||
return tr("macToggleStatusButtonReasserting")
|
||||
case .restarting:
|
||||
return tr("macToggleStatusButtonRestarting")
|
||||
private static func localizedToggleStatusActionText(for tunnel: TunnelContainer) -> String {
|
||||
if tunnel.hasOnDemandRules {
|
||||
let turnOn = !tunnel.isActivateOnDemandEnabled
|
||||
if turnOn {
|
||||
return tr("macToggleStatusButtonEnableOnDemand")
|
||||
} else {
|
||||
if tunnel.status == .active {
|
||||
return tr("macToggleStatusButtonDisableOnDemandDeactivate")
|
||||
} else {
|
||||
return tr("macToggleStatusButtonDisableOnDemand")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch tunnel.status {
|
||||
case .waiting:
|
||||
return tr("macToggleStatusButtonWaiting")
|
||||
case .inactive:
|
||||
return tr("macToggleStatusButtonActivate")
|
||||
case .activating:
|
||||
return tr("macToggleStatusButtonActivating")
|
||||
case .active:
|
||||
return tr("macToggleStatusButtonDeactivate")
|
||||
case .deactivating:
|
||||
return tr("macToggleStatusButtonDeactivating")
|
||||
case .reasserting:
|
||||
return tr("macToggleStatusButtonReasserting")
|
||||
case .restarting:
|
||||
return tr("macToggleStatusButtonRestarting")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
|
||||
protocol TunnelEditViewControllerDelegate: class {
|
||||
protocol TunnelEditViewControllerDelegate: AnyObject {
|
||||
func tunnelSaved(tunnel: TunnelContainer)
|
||||
func tunnelEditingCancelled()
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
|
||||
protocol TunnelsListTableViewControllerDelegate: class {
|
||||
protocol TunnelsListTableViewControllerDelegate: AnyObject {
|
||||
func tunnelsSelected(tunnelIndices: [Int])
|
||||
func tunnelsListEmpty()
|
||||
}
|
||||
@@ -232,10 +232,19 @@ class TunnelsListTableViewController: NSViewController {
|
||||
let tunnelIndex = tableView.clickedRow
|
||||
guard tunnelIndex >= 0 && tunnelIndex < tunnelsManager.numberOfTunnels() else { return }
|
||||
let tunnel = tunnelsManager.tunnel(at: tunnelIndex)
|
||||
if tunnel.status == .inactive {
|
||||
tunnelsManager.startActivation(of: tunnel)
|
||||
} else if tunnel.status == .active {
|
||||
tunnelsManager.startDeactivation(of: tunnel)
|
||||
if tunnel.hasOnDemandRules {
|
||||
let turnOn = !tunnel.isActivateOnDemandEnabled
|
||||
tunnelsManager.setOnDemandEnabled(turnOn, on: tunnel) { error in
|
||||
if error == nil && !turnOn {
|
||||
self.tunnelsManager.startDeactivation(of: tunnel)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if tunnel.status == .inactive {
|
||||
tunnelsManager.startActivation(of: tunnel)
|
||||
} else if tunnel.status == .active {
|
||||
tunnelsManager.startDeactivation(of: tunnel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
protocol WireGuardAppError: Error {
|
||||
typealias AlertText = (title: String, message: String)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
enum WireGuardResult<T> {
|
||||
case success(_ value: T)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
@@ -148,7 +148,6 @@
|
||||
"alertInvalidInterfaceTitle" = "Invalid interface";
|
||||
"tunnelSectionTitleStatus" = "Status";
|
||||
"macDeleteTunnelConfirmationAlertButtonTitleDelete" = "Delete";
|
||||
"alertTunnelActivationFailureOnDemandAddendum" = " This tunnel has Activate On Demand enabled, so this tunnel might be re-activated automatically by the OS. You may turn off Activate On Demand in this app by editing the tunnel configuration.";
|
||||
"alertTunnelActivationFailureTitle" = "Activation failure";
|
||||
"macLogButtonTitleClose" = "Close";
|
||||
"tunnelOnDemandSSIDViewTitle" = "SSIDs";
|
||||
@@ -235,6 +234,7 @@
|
||||
"macMenuFile" = "File";
|
||||
"tunnelStatusActivating" = "Activating";
|
||||
"macToolTipToggleStatus" = "Toggle status (⌘T)";
|
||||
"macTunnelsMenuTitle" = "Tunnels";
|
||||
"alertTunnelActivationSystemErrorTitle" = "Activation failure";
|
||||
"alertInvalidInterfaceMessagePrivateKeyRequired" = "Interface’s private key is required";
|
||||
"alertNoTunnelsToExportTitle" = "Nothing to export";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
// Generic alert action names
|
||||
|
||||
|
||||
@@ -255,8 +255,6 @@
|
||||
"alertTunnelActivationFileDescriptorFailureMessage" = "TUN Dateideskriptor kann nicht ermittelt werden.";
|
||||
"alertTunnelActivationSetNetworkSettingsMessage" = "Netzwerkeinstellungen können nicht auf Tunnelobjekt angewendet werden.";
|
||||
|
||||
"alertTunnelActivationFailureOnDemandAddendum" = " Dieser Tunnel hat 'Activate on Demand' aktiviert, so dass dieser Tunnel vom Betriebssystem automatisch aktiviert werden kann. Du kannst 'Activate on Demand' in dieser App deaktivieren, indem du die Tunnelkonfiguration änderst.";
|
||||
|
||||
"alertTunnelDNSFailureTitle" = "DNS-Auflösungsfehler";
|
||||
"alertTunnelDNSFailureMessage" = "Eine oder mehrere Endpunkt-Domains konnten nicht aufgelöst werden.";
|
||||
|
||||
@@ -443,3 +441,4 @@
|
||||
// Donation
|
||||
|
||||
"donateLink" = "♥ Spende an das WireGuard Projekt";
|
||||
"macTunnelsMenuTitle" = "Tunnels";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
// Generic alert action names
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
"tunnelPeerPublicKey" = "Clave pública";
|
||||
"tunnelPeerPreSharedKey" = "Clave precompartida";
|
||||
"tunnelPeerEndpoint" = "Punto final";
|
||||
"tunnelPeerPersistentKeepalive" = "Mantenimiento persistente";
|
||||
"tunnelPeerPersistentKeepalive" = "Keepalive persistente";
|
||||
"tunnelPeerAllowedIPs" = "IPs permitidas";
|
||||
"tunnelPeerRxBytes" = "Datos recibidos";
|
||||
"tunnelPeerTxBytes" = "Datos enviados";
|
||||
@@ -177,142 +177,218 @@
|
||||
"alertInvalidPeerMessagePreSharedKeyInvalid" = "La clave precompartida del par debe ser de 32 bytes en codificación base64";
|
||||
"alertInvalidPeerMessageAllowedIPsInvalid" = "Las IPs permitidas del par deben ser una lista de direcciones IP separadas por comas, opcionalmente en notación CIDR";
|
||||
"alertInvalidPeerMessageEndpointInvalid" = "El punto final del par debe ser de la forma ‘host:port’ o ‘[host]:port’";
|
||||
"alertInvalidPeerMessagePersistentKeepaliveInvalid" = "El keepalive persistente del par debe estar entre 0 y 65535, o no especificado";
|
||||
"alertInvalidPeerMessagePersistentKeepaliveInvalid" = "El keepalive persistente del peer debe estar entre 0 y 65535, o no especificado";
|
||||
"alertInvalidPeerMessagePublicKeyDuplicated" = "Dos o mas pares no pueden tener la misma llave pùblica";
|
||||
|
||||
// Scanning QR code UI
|
||||
|
||||
"scanQRCodeViewTitle" = "Escanear código QR";
|
||||
"scanQRCodeTipText" = "Consejo: generar con `qrencode -t ansiutf8 tunnel.conf`";
|
||||
|
||||
// Scanning QR code alerts
|
||||
|
||||
"alertScanQRCodeCameraUnsupportedTitle" = "Cámara no soportada";
|
||||
"alertScanQRCodeCameraUnsupportedMessage" = "Este dispositivo no es capaz de escanear códigos QR";
|
||||
|
||||
"alertScanQRCodeInvalidQRCodeTitle" = "Código QR inválido";
|
||||
"alertScanQRCodeInvalidQRCodeMessage" = "El código QR escaneado no es una configuración válida de WireGuard";
|
||||
|
||||
"alertScanQRCodeUnreadableQRCodeTitle" = "Código inválido";
|
||||
"alertScanQRCodeUnreadableQRCodeMessage" = "El código escaneado no pudo ser leído";
|
||||
|
||||
"alertScanQRCodeNamePromptTitle" = "Por favor, nombra el túnel escaneado";
|
||||
|
||||
// Settings UI
|
||||
|
||||
"settingsViewTitle" = "Configuración";
|
||||
|
||||
"settingsSectionTitleAbout" = "Acerca de";
|
||||
"settingsVersionKeyWireGuardForIOS" = "WireGuard para iOS";
|
||||
|
||||
"settingsSectionTitleExportConfigurations" = "Exportar configuraciones";
|
||||
"settingsExportZipButtonTitle" = "Exportar archivo zip";
|
||||
|
||||
"settingsSectionTitleTunnelLog" = "Registro";
|
||||
"settingsViewLogButtonTitle" = "Ver registro";
|
||||
|
||||
// Log view
|
||||
|
||||
"logViewTitle" = "Registro";
|
||||
|
||||
// Log alerts
|
||||
|
||||
"alertUnableToRemovePreviousLogTitle" = "Exportación de registros fallida";
|
||||
"alertUnableToRemovePreviousLogMessage" = "El registro preexistente no ha podido ser borrado";
|
||||
|
||||
"alertUnableToWriteLogTitle" = "Exportación de registros fallida";
|
||||
"alertUnableToWriteLogMessage" = "No se pudo escribir en el archivo de registros";
|
||||
|
||||
// Zip import / export error alerts
|
||||
|
||||
"alertCantOpenInputZipFileTitle" = "No se pudo leer el archivo zip";
|
||||
"alertCantOpenInputZipFileMessage" = "El archivo zip no pudo ser leído.";
|
||||
|
||||
"alertCantOpenOutputZipFileForWritingTitle" = "No se pudo crear el archivo zip";
|
||||
"alertCantOpenOutputZipFileForWritingMessage" = "No se pudo abrir el archivo zip para escribir.";
|
||||
|
||||
"alertBadArchiveTitle" = "No se pudo leer el archivo zip";
|
||||
"alertBadArchiveMessage" = "Archivo zip erróneo o corrupto.";
|
||||
|
||||
"alertNoTunnelsToExportTitle" = "Nada para exportar";
|
||||
"settingsSectionTitleAbout" = "About";
|
||||
"macMenuDeleteSelected" = "Delete Selected";
|
||||
"alertSystemErrorMessageTunnelConfigurationInvalid" = "The configuration is invalid.";
|
||||
"alertNoTunnelsToExportMessage" = "No hay túneles para exportar";
|
||||
|
||||
"alertNoTunnelsInImportedZipArchiveTitle" = "No hay túneles en el archivo zip";
|
||||
|
||||
// Tunnel management error alerts
|
||||
|
||||
"alertTunnelActivationFailureTitle" = "Fallo en la activación";
|
||||
"alertTunnelActivationFailureMessage" = "El túnel no pudo ser activado. Por favor, asegúrese de estar conectado a Internet.";
|
||||
"alertTunnelActivationSavedConfigFailureMessage" = "No se ha podido recuperar la información del túnel de la configuración guardada.";
|
||||
|
||||
"alertTunnelDNSFailureTitle" = "Fallo en resolución DNS";
|
||||
"alertSystemErrorOnAddTunnelTitle" = "No se pudo crear el túnel";
|
||||
"alertSystemErrorOnModifyTunnelTitle" = "No se pudo modificar el túnel";
|
||||
"alertSystemErrorOnRemoveTunnelTitle" = "No se pudo eliminar el túnel";
|
||||
|
||||
/* The alert message for this alert shall include
|
||||
one of the alertSystemErrorMessage* listed further down */
|
||||
"alertTunnelActivationSystemErrorTitle" = "Fallo en la activación";
|
||||
"alertTunnelActivationSystemErrorMessage (%@)" = "No se pudo activar el túnel. %@";
|
||||
|
||||
/* alertSystemErrorMessage* messages */
|
||||
"alertSystemErrorMessageTunnelConfigurationInvalid" = "La configuración es inválida.";
|
||||
"alertSystemErrorMessageTunnelConfigurationDisabled" = "La configuración está desactivada.";
|
||||
"alertSystemErrorMessageTunnelConnectionFailed" = "La conexión ha fallado.";
|
||||
"alertSystemErrorMessageTunnelConfigurationUnknown" = "Error desconocido de sistema.";
|
||||
|
||||
"macMenuTitle" = "WireGuard";
|
||||
"macMenuManageTunnels" = "Gestionar túneles";
|
||||
"macMenuImportTunnels" = "Importar túnel(es) desde archivo";
|
||||
"macMenuViewLog" = "Ver registro";
|
||||
"macMenuAbout" = "Acerca de WireGuard";
|
||||
"macMenuQuit" = "Salir de WireGuard";
|
||||
|
||||
"macMenuHideApp" = "Ocultar WireGuard";
|
||||
"macMenuShowAllApps" = "Mostrar todo";
|
||||
|
||||
"macMenuFile" = "Archivo";
|
||||
"macMenuCloseWindow" = "Cerrar Ventana";
|
||||
|
||||
"macMenuEdit" = "Editar";
|
||||
"macMenuCut" = "Cortar";
|
||||
"macMenuCopy" = "Copiar";
|
||||
"macMenuPaste" = "Pegar";
|
||||
"macMenuSelectAll" = "Seleccionar todo";
|
||||
|
||||
"macMenuTunnel" = "Túnel";
|
||||
"macMenuToggleStatus" = "Cambiar estado";
|
||||
"macMenuEditTunnel" = "Editar…";
|
||||
"macMenuDeleteSelected" = "Eliminar elementos seleccionados";
|
||||
|
||||
"macMenuWindow" = "Ventana";
|
||||
"macMenuMinimize" = "Minimizar";
|
||||
|
||||
// Mac manage tunnels window
|
||||
|
||||
"macWindowTitleManageTunnels" = "Gestionar Túneles WireGuard";
|
||||
|
||||
"macDeleteTunnelConfirmationAlertMessage (%@)" = "¿Estás seguro que deseas eliminar \"%@\"?";
|
||||
"macDeleteMultipleTunnelsConfirmationAlertMessage (%d)" = "¿Está seguro que desea eliminar %d túneles?";
|
||||
"macDeleteTunnelConfirmationAlertInfo" = "No puedes deshacer esta acción.";
|
||||
"macDeleteTunnelConfirmationAlertButtonTitleDelete" = "Eliminar";
|
||||
"macDeleteTunnelConfirmationAlertButtonTitleCancel" = "Cancelar";
|
||||
"macDeleteTunnelConfirmationAlertButtonTitleDeleting" = "Eliminando…";
|
||||
|
||||
"macButtonImportTunnels" = "Importar túnel(es) desde archivo";
|
||||
"macSheetButtonImport" = "Importar";
|
||||
|
||||
"macNameFieldExportLog" = "Guardar registro en:";
|
||||
"macSheetButtonExportLog" = "Guardar";
|
||||
|
||||
"macNameFieldExportZip" = "Exportar túneles a:";
|
||||
"macSheetButtonExportZip" = "Guardar";
|
||||
|
||||
"macButtonDeleteTunnels (%d)" = "Eliminar %d túneles";
|
||||
|
||||
"macButtonEdit" = "Editar";
|
||||
"macFieldOnDemand" = "Bajo demanda:";
|
||||
"macFieldOnDemandSSIDs" = "SSIDs:";
|
||||
|
||||
// Mac status display
|
||||
|
||||
"macStatus (%@)" = "Estado: %@";
|
||||
|
||||
// Mac editing config
|
||||
|
||||
"macEditDiscard" = "Descartar";
|
||||
"macEditSave" = "Guardar";
|
||||
"macAlertDNSInvalid (%@)" = "El DNS ‘%@’ no es válido.";
|
||||
|
||||
"macAlertPublicKeyInvalid" = "La Clave pública no es válida";
|
||||
"macAlertPreSharedKeyInvalid" = "La clave compartida no es válida";
|
||||
"macAlertEndpointInvalid (%@)" = "Endpoint ‘%@’ no es válido";
|
||||
"macAlertPersistentKeepliveInvalid (%@)" = "El valor keepalive persistente '%@' no es válido";
|
||||
"macAlertInfoUnrecognizedPeerKey" = "Las claves válidas son: ‘PublicKey’, ‘PresharedKey’, ‘AllowedIPs’, ‘Endpoint’ y ‘PersistentKeepalive’";
|
||||
"macConfirmAndQuitAlertQuitWireGuard" = "Salir de WireGuard";
|
||||
"macConfirmAndQuitAlertCloseWindow" = "Cerrar Gestor de túneles";
|
||||
|
||||
// Mac tooltip
|
||||
|
||||
"macToolTipEditTunnel" = "Editar túnel (⌘E)";
|
||||
"macToolTipToggleStatus" = "Cambiar estado (⌘T)";
|
||||
|
||||
// Mac log view
|
||||
|
||||
"macLogColumnTitleTime" = "Tiempo";
|
||||
"macLogColumnTitleLogMessage" = "Mensaje de registro";
|
||||
"macLogButtonTitleClose" = "Cerrar";
|
||||
"macLogButtonTitleSave" = "Guardar…";
|
||||
|
||||
// Mac unusable tunnel view
|
||||
|
||||
"macUnusableTunnelMessage" = "La configuración de este túnel no se encuentra en el llavero.";
|
||||
"macUnusableTunnelButtonTitleDeleteTunnel" = "Eliminar túnel";
|
||||
|
||||
// Mac App Store updating alert
|
||||
|
||||
"macAppStoreUpdatingAlertMessage" = "App Store desea actualizar WireGuard";
|
||||
|
||||
// Donation
|
||||
|
||||
"donateLink" = "♥ Donar al Proyecto WireGuard";
|
||||
"macAlertNoInterface" = "Configuration must have an ‘Interface’ section.";
|
||||
"macNameFieldExportZip" = "Export tunnels to:";
|
||||
"alertSystemErrorMessageTunnelConfigurationUnknown" = "Unknown system error.";
|
||||
"macMenuCut" = "Cut";
|
||||
"macEditDiscard" = "Discard";
|
||||
"alertScanQRCodeCameraUnsupportedMessage" = "This device is not able to scan QR codes";
|
||||
"macSheetButtonExportZip" = "Save";
|
||||
"macWindowTitleManageTunnels" = "Manage WireGuard Tunnels";
|
||||
"macConfirmAndQuitAlertInfo" = "If you close the tunnels manager, WireGuard will continue to be available from the menu bar icon.";
|
||||
"macUnusableTunnelInfo" = "In case this tunnel was created by another user, only that user can view, edit, or activate this tunnel.";
|
||||
"alertTunnelActivationErrorTunnelIsNotInactiveMessage" = "The tunnel is already active or in the process of being activated";
|
||||
"alertTunnelActivationSetNetworkSettingsMessage" = "Unable to apply network settings to tunnel object.";
|
||||
"macMenuExportTunnels" = "Export Tunnels to Zip…";
|
||||
"macMenuShowAllApps" = "Show All";
|
||||
"alertCantOpenInputConfFileTitle" = "Unable to import from file";
|
||||
"alertScanQRCodeInvalidQRCodeMessage" = "The scanned QR code is not a valid WireGuard configuration";
|
||||
"macMenuHideApp" = "Hide WireGuard";
|
||||
"settingsViewTitle" = "Settings";
|
||||
"macDeleteTunnelConfirmationAlertInfo" = "You cannot undo this action.";
|
||||
"macDeleteTunnelConfirmationAlertButtonTitleDeleting" = "Deleting…";
|
||||
"settingsViewLogButtonTitle" = "View log";
|
||||
"alertSystemErrorMessageTunnelConnectionFailed" = "The connection failed.";
|
||||
"macButtonEdit" = "Edit";
|
||||
"macAlertPublicKeyInvalid" = "Public key is invalid";
|
||||
"macNameFieldExportLog" = "Save log to:";
|
||||
"alertSystemErrorOnAddTunnelTitle" = "Unable to create tunnel";
|
||||
"macConfirmAndQuitAlertMessage" = "Do you want to close the tunnels manager or quit WireGuard entirely?";
|
||||
"alertTunnelActivationSavedConfigFailureMessage" = "Unable to retrieve tunnel information from the saved configuration.";
|
||||
"macAlertInfoUnrecognizedInterfaceKey" = "Valid keys are: ‘PrivateKey’, ‘ListenPort’, ‘Address’, ‘DNS’ and ‘MTU’.";
|
||||
"macLogColumnTitleTime" = "Time";
|
||||
"alertTunnelNameEmptyMessage" = "Cannot create tunnel with an empty name";
|
||||
"alertTunnelNameEmptyTitle" = "No name provided";
|
||||
"alertUnableToWriteLogMessage" = "Unable to write logs to file";
|
||||
"macMenuQuit" = "Quit WireGuard";
|
||||
"macMenuAddEmptyTunnel" = "Add Empty Tunnel…";
|
||||
"macDeleteTunnelConfirmationAlertButtonTitleDelete" = "Delete";
|
||||
"alertTunnelActivationFailureOnDemandAddendum" = " This tunnel has Activate On Demand enabled, so this tunnel might be re-activated automatically by the OS. You may turn off Activate On Demand in this app by editing the tunnel configuration.";
|
||||
"alertTunnelActivationFailureTitle" = "Activation failure";
|
||||
"macLogButtonTitleClose" = "Close";
|
||||
"settingsExportZipButtonTitle" = "Export zip archive";
|
||||
"macViewPrivateData" = "view tunnel private keys";
|
||||
"alertTunnelActivationErrorTunnelIsNotInactiveTitle" = "Activation in progress";
|
||||
"macAppExitingWithActiveTunnelInfo" = "The tunnel will remain active after exiting. You may disable it by reopening this application or through the Network panel in System Preferences.";
|
||||
"macMenuEdit" = "Edit";
|
||||
"donateLink" = "♥ Donate to the WireGuard Project";
|
||||
"alertScanQRCodeCameraUnsupportedTitle" = "Camera Unsupported";
|
||||
"macMenuWindow" = "Window";
|
||||
"alertTunnelActivationFailureMessage" = "The tunnel could not be activated. Please ensure that you are connected to the Internet.";
|
||||
"macMenuHideOtherApps" = "Hide Others";
|
||||
"alertCantOpenInputZipFileMessage" = "The zip archive could not be read.";
|
||||
"macAlertPrivateKeyInvalid" = "Private key is invalid.";
|
||||
"macDeleteTunnelConfirmationAlertButtonTitleCancel" = "Cancel";
|
||||
"alertSystemErrorMessageTunnelConfigurationDisabled" = "The configuration is disabled.";
|
||||
"alertUnableToWriteLogTitle" = "Log export failed";
|
||||
"macMenuNetworksNone" = "Networks: None";
|
||||
"alertCantOpenOutputZipFileForWritingMessage" = "Could not open zip file for writing.";
|
||||
"macMenuSelectAll" = "Select All";
|
||||
"logViewTitle" = "Log";
|
||||
"macConfirmAndQuitAlertQuitWireGuard" = "Quit WireGuard";
|
||||
"alertSystemErrorOnRemoveTunnelTitle" = "Unable to remove tunnel";
|
||||
"macFieldOnDemand" = "On-Demand:";
|
||||
"macMenuCloseWindow" = "Close Window";
|
||||
"macSheetButtonExportLog" = "Save";
|
||||
"alertSystemErrorOnModifyTunnelTitle" = "Unable to modify tunnel";
|
||||
"alertSystemErrorMessageTunnelConfigurationReadWriteFailed" = "Reading or writing the configuration failed.";
|
||||
"macMenuEditTunnel" = "Edit…";
|
||||
"settingsSectionTitleTunnelLog" = "Log";
|
||||
"macMenuManageTunnels" = "Manage Tunnels";
|
||||
"macButtonImportTunnels" = "Import tunnel(s) from file";
|
||||
"macAppExitingWithActiveTunnelMessage" = "WireGuard is exiting with an active tunnel";
|
||||
"alertSystemErrorMessageTunnelConfigurationStale" = "The configuration is stale.";
|
||||
"alertTunnelDNSFailureMessage" = "One or more endpoint domains could not be resolved.";
|
||||
"alertNoTunnelsInImportedZipArchiveTitle" = "No tunnels in zip archive";
|
||||
"alertTunnelDNSFailureTitle" = "DNS resolution failure";
|
||||
"macLogButtonTitleSave" = "Save…";
|
||||
"macMenuToggleStatus" = "Toggle Status";
|
||||
"macMenuMinimize" = "Minimize";
|
||||
"alertCantOpenInputZipFileTitle" = "Unable to read zip archive";
|
||||
"alertScanQRCodeUnreadableQRCodeMessage" = "The scanned code could not be read";
|
||||
"alertScanQRCodeUnreadableQRCodeTitle" = "Invalid Code";
|
||||
"alertSystemErrorOnListingTunnelsTitle" = "Unable to list tunnels";
|
||||
"settingsVersionKeyWireGuardForIOS" = "WireGuard for iOS";
|
||||
"macMenuPaste" = "Paste";
|
||||
"macAlertMultipleInterfaces" = "Configuration must have only one ‘Interface’ section.";
|
||||
"macAppStoreUpdatingAlertMessage" = "App Store would like to update WireGuard";
|
||||
"macUnusableTunnelMessage" = "The configuration for this tunnel cannot be found in the keychain.";
|
||||
"macToolTipEditTunnel" = "Edit tunnel (⌘E)";
|
||||
"macMenuZoom" = "Zoom";
|
||||
"alertBadArchiveTitle" = "Unable to read zip archive";
|
||||
"macExportPrivateData" = "export tunnel private keys";
|
||||
"alertTunnelAlreadyExistsWithThatNameTitle" = "Name already exists";
|
||||
"iosViewPrivateData" = "Authenticate to view tunnel private keys.";
|
||||
"macAlertPreSharedKeyInvalid" = "Preshared key is invalid";
|
||||
"macEditSave" = "Save";
|
||||
"macConfirmAndQuitAlertCloseWindow" = "Close Tunnels Manager";
|
||||
"macMenuFile" = "File";
|
||||
"macToolTipToggleStatus" = "Toggle status (⌘T)";
|
||||
"alertTunnelActivationSystemErrorTitle" = "Activation failure";
|
||||
"scanQRCodeTipText" = "Tip: Generate with `qrencode -t ansiutf8 < tunnel.conf`";
|
||||
"alertNoTunnelsToExportMessage" = "There are no tunnels to export";
|
||||
"macMenuImportTunnels" = "Import Tunnel(s) from File…";
|
||||
"alertScanQRCodeInvalidQRCodeTitle" = "Invalid QR Code";
|
||||
"macMenuViewLog" = "View Log";
|
||||
"macAlertInfoUnrecognizedPeerKey" = "Valid keys are: ‘PublicKey’, ‘PresharedKey’, ‘AllowedIPs’, ‘Endpoint’ and ‘PersistentKeepalive’";
|
||||
"macUnusableTunnelButtonTitleDeleteTunnel" = "Delete tunnel";
|
||||
"macMenuTunnel" = "Tunnel";
|
||||
"macMenuCopy" = "Copy";
|
||||
"macTunnelsMenuTitle" = "Tunnels";
|
||||
"alertTunnelAlreadyExistsWithThatNameMessage" = "A tunnel with that name already exists";
|
||||
"macLogColumnTitleLogMessage" = "Log message";
|
||||
"iosExportPrivateData" = "Authenticate to export tunnel private keys.";
|
||||
"macMenuAbout" = "About WireGuard";
|
||||
"macSheetButtonImport" = "Import";
|
||||
"alertScanQRCodeNamePromptTitle" = "Please name the scanned tunnel";
|
||||
"alertUnableToRemovePreviousLogMessage" = "The pre-existing log could not be cleared";
|
||||
"alertTunnelActivationBackendFailureMessage" = "Unable to turn on Go backend library.";
|
||||
"settingsSectionTitleExportConfigurations" = "Export configurations";
|
||||
"alertBadArchiveMessage" = "Bad or corrupt zip archive.";
|
||||
"settingsVersionKeyWireGuardGoBackend" = "WireGuard Go Backend";
|
||||
"macFieldOnDemandSSIDs" = "SSIDs:";
|
||||
"alertCantOpenOutputZipFileForWritingTitle" = "Unable to create zip archive";
|
||||
"alertNoTunnelsInImportedZipArchiveMessage" = "No .conf tunnel files were found inside the zip archive.";
|
||||
"alertTunnelActivationFileDescriptorFailureMessage" = "Unable to determine TUN device file descriptor.";
|
||||
"macAlertNameIsEmpty" = "Name is required";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
|
||||
// Generic alert action names
|
||||
|
||||
|
||||
@@ -363,7 +363,6 @@
|
||||
"alertTunnelNameEmptyMessage" = "Cannot create tunnel with an empty name";
|
||||
"alertInvalidInterfaceMessageMTUInvalid" = "Interface’s MTU must be between 576 and 65535, or unspecified";
|
||||
"alertUnableToWriteLogMessage" = "Unable to write logs to file";
|
||||
"alertTunnelActivationFailureOnDemandAddendum" = " This tunnel has Activate On Demand enabled, so this tunnel might be re-activated automatically by the OS. You may turn off Activate On Demand in this app by editing the tunnel configuration.";
|
||||
"alertInvalidPeerMessageEndpointInvalid" = "Peer’s endpoint must be of the form ‘host:port’ or ‘[host]:port’";
|
||||
"alertInvalidPeerMessagePublicKeyDuplicated" = "Two or more peers cannot have the same public key";
|
||||
"alertInvalidPeerMessagePreSharedKeyInvalid" = "Peer’s preshared key must be a 32-byte key in base64 encoding";
|
||||
@@ -389,6 +388,7 @@
|
||||
"macUnusableTunnelMessage" = "The configuration for this tunnel cannot be found in the keychain.";
|
||||
"iosViewPrivateData" = "Authenticate to view tunnel private keys.";
|
||||
"macToolTipToggleStatus" = "Toggle status (⌘T)";
|
||||
"macTunnelsMenuTitle" = "Tunnels";
|
||||
"scanQRCodeTipText" = "Tip: Generate with `qrencode -t ansiutf8 < tunnel.conf`";
|
||||
"macAlertInfoUnrecognizedPeerKey" = "Valid keys are: ‘PublicKey’, ‘PresharedKey’, ‘AllowedIPs’, ‘Endpoint’ and ‘PersistentKeepalive’";
|
||||
"alertInvalidPeerMessageAllowedIPsInvalid" = "Peer’s allowed IPs must be a list of comma-separated IP addresses, optionally in CIDR notation";
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user