Compare commits

...

65 Commits

Author SHA1 Message Date
00702ecbec Update Package.swift 2025-07-16 07:16:01 +08:00
2fec12a6e1 App: version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-02-15 14:20:52 +01:00
7b279383d1 App: bump copyright
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-02-15 14:20:35 +01:00
901fe1cf58 App: bump minimum OS versions
This allows us to remove a good deal of legacy cruft.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-02-15 14:20:30 +01:00
ccc7472fd7 WireGuardKitGo: bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-02-14 16:09:14 +01:00
12b095470a WireGuardKit: fix incorrect IP address allocation size
According to [1], the `capacity` parameter is specified as "the number
of instances of T in the re-bound region" and not the total size of the
rebound struct.

Without this patch, there are crashes in the extension with the
following error:

  Fatal error: self must be a properly aligned pointer for types Pointee and T`

Since the subsequent line in the code only reads `sizeof(in_addr)` or
`sizeof(in6_addr)` anyway, change the `capacity` parameter to just be a
count of 1.

[1] https://developer.apple.com/documentation/swift/unsafepointer/withmemoryrebound(to:capacity:_:)

Signed-off-by: John Biggs <john.biggs@proton.ch>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2023-02-08 13:08:40 -03:00
9c07693951 global: apply MIT more consistently
People keep asking.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2022-11-17 01:17:52 +01:00
23618f994f UI: When saving on-demand rules, deactivate if reqd and then save
Signed-off-by: Roopesh Chander <roop@roopc.net>
2021-09-28 00:16:35 +05:30
ba644415c7 UI: When saving on-demand rules on a config, enable on-demand if active
When a user saves on-demand rules on the configuration, set
onDemandEnabled to true if the tunnel is active, and false if it isn't.
Then deactivate the tunnel.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2021-09-24 01:01:10 +05:30
10da5cfdef App: version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-09-23 06:20:28 +02:00
75b6925deb UI: macOS: increase login detector file timeout
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-09-23 06:19:48 +02:00
03a59ff38e Model: migrate iOS 14 keychain references to iOS 15 format
Keychain references used to be bijective, but with the change in format,
Apple tried to be too clever, and references are no longer bijective.
This lead to us deleting keychain entries, which in turn emptied out
people's configs upon upgrading to iOS 15. Disaster!

Fix this by detecting the change in format and saving the new password
reference. We still rely on this being bijective moving forward;
hopefully this bug won't repeat itself. It would be nice to not rely on
that property, but doing so without grinding startup to a halt isn't
obviously done, given how slow the keychain accesses are and how limited
the API is.

Reported-by: Eddie <stunnel@attglobal.net>
Reported-by: Anatoli <me@anatoli.ws>
Reported-by: Alan Graham <alan@meshify.app>
Reported-by: Jacob Wilder <oss@jacobwilder.org>
Reported-by: Miguel Arroz <miguel.arroz@gmail.com>
Reported-by: Reid Rankin <reidrankin@gmail.com>
Reported-by: Fabien <patate.cosmique@pm.me>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-09-23 06:11:19 +02:00
abf506c1fe UI: iOS: remove list pinking when no config
This reverts commit 86afd1a46a.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-09-23 06:08:53 +02:00
dfb685f258 WireGuardApp: restore old keychain consistency behavior
This reverts commit adcbd17ebe.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-09-23 05:40:10 +02:00
f3798d0e11 App: version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-09-22 20:59:19 +02:00
86afd1a46a UI: iOS: disable list rows when no config
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-09-22 20:56:25 +02:00
7171df84fa WireGuardApp: use file to communicate launch-by-login-helper
Apple event params are broken on recent macOS versions.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-09-22 19:22:44 +02:00
d882a486a9 Keychain: remove class constraint when copying
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-09-22 16:51:25 +02:00
adcbd17ebe WireGuardApp: do not delete unverifying profiles ever
The Keychain code is much too fragile, and it's better to err on the
safe side. Instead just log an error when this happens.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-09-22 07:13:48 +02:00
3d8de22b96 WireGuardKitGo: bump wireguard-go version
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-09-22 06:58:14 +02:00
ba4d1e7b21 MacAppStoreUpdateDetector: Detect StoreAEService correctly
In macOS 10.15 and macOS 11, the quit Apple event is sent by:
  com.apple.AppStoreDaemon.StoreAEService

In some earlier macOS release, the quit Apple event was sent by:
  com.apple.CommerceKit.StoreAEService

Signed-off-by: Roopesh Chander <roop@roopc.net>
2021-09-22 06:58:14 +02:00
f5a14b8434 MacAppStoreUpdateDetector: Add pid to the log
Signed-off-by: Roopesh Chander <roop@roopc.net>
2021-09-22 06:58:14 +02:00
b74eb7239a WireGuardKitGo: include new homebrew location in PATH
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-09-22 06:58:14 +02:00
a8226b35d2 build: Fix swiftlint warnings
Signed-off-by: Roopesh Chander <roop@roopc.net>
2021-09-22 06:58:14 +02:00
73c708d902 build: Fix swift warnings
Use 'AnyObject' instead of 'class' to restrict protocol inheritance

Signed-off-by: Roopesh Chander <roop@roopc.net>
2021-09-22 06:58:14 +02:00
3668f3af9f build: Include 'swiftlint' location in the PATH before invoking it
In macOS 11, HomeBrew installs swiftlint under /opt/homebrew, which is not
in the default path that Xcode seems to use. So we include the PATH
to contain:

  - /usr/local/bin:

    Where HomeBrew installs 'swiftlint' in macOS 10.15 and earlier

  - /opt/homebrew/bin:

    Where HomeBrew installs 'swiftlint' in macOS 11

Signed-off-by: Roopesh Chander <roop@roopc.net>
2021-09-22 06:58:14 +02:00
54697a3240 UI: Use 'On-Demand', with hyphen, consistently
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-09-22 06:58:14 +02:00
3428bfbc9e UI: macOS: do on-demand ritual for clicking list item too
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-09-22 02:40:49 +02:00
cfd1b16801 UI: Consider on-demand to be enabled iff the tunnel provider is enabled
Signed-off-by: Roopesh Chander <roop@roopc.net>
2021-08-03 16:35:18 +05:30
ca70fe9ddc UI: When setting on-demand, avoid a second saveToPreferences() call
Signed-off-by: Roopesh Chander <roop@roopc.net>
2021-08-03 16:34:57 +05:30
55c587b443 UI: When saving on-demand rules, don't set isOnDemandEnabled
When adding or modifying a config, when on-demand options are set by a
user, the rules are saved, but isOnDemandEnabled is left unset (and can
be set by the appropriate control in the detail view (switch in iOS /
button in macOS)).

Signed-off-by: Roopesh Chander <roop@roopc.net>
2021-08-02 23:25:53 +05:30
b6831c1aca UI: macOS: Incorporate on-demand-ness in status menu
Signed-off-by: Roopesh Chander <roop@roopc.net>
2021-07-30 13:29:38 +05:30
2ac17da7cb UI: macOS: Tunnel detail: Incorporate on-demand-ness in toggle button
Signed-off-by: Roopesh Chander <roop@roopc.net>
2021-07-30 13:29:33 +05:30
274c4cd092 UI: macOS: Tunnel detail: Incorporate on-demand-ness in the status row
Signed-off-by: Roopesh Chander <roop@roopc.net>
2021-07-30 13:29:28 +05:30
95e1409bfb UI: macOS: Tunnel list: Incorporate on-demand-ness in the status circle
Signed-off-by: Roopesh Chander <roop@roopc.net>
2021-07-30 13:29:25 +05:30
2c2c53b1f8 UI: macOS: Add yellow circle image
Signed-off-by: Roopesh Chander <roop@roopc.net>
2021-07-30 13:29:09 +05:30
9cbfec99df UI: Localizations: Remove alertTunnelActivationFailureOnDemandAddendum
It's not used anymore.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2021-07-28 15:55:17 +05:30
1bd6dcb7e7 UI: Remove addendum on on-demand from error on tunnel activation
Signed-off-by: Roopesh Chander <roop@roopc.net>
2021-07-28 11:52:54 +05:30
c1fe8b0162 UI: When setting on-demand, enable the tunnel if required
Signed-off-by: Roopesh Chander <roop@roopc.net>
2021-07-28 11:52:54 +05:30
64c2fb337d UI: iOS: Tunnels list: Move the "On Demand" label to the right
Having that at the bottom makes it harder for iOS to get
the row height correctly.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2021-07-28 03:35:05 +05:30
147ac02f0d UI: iOS: Show on-demand state in 'Status' if there are on-demand rules
Signed-off-by: Roopesh Chander <roop@roopc.net>
2021-07-28 03:35:05 +05:30
03ef79c0fd UI: When reloading tunnels, preserve '.waiting' state
Signed-off-by: Roopesh Chander <roop@roopc.net>
2021-07-28 03:35:05 +05:30
a261d84fc6 UI: When deactivating for activating another tunnel, disable on-demand
Signed-off-by: Roopesh Chander <roop@roopc.net>
2021-07-28 03:35:05 +05:30
abaf1f1454 UI: Keep on-demand rules even if on-demand is disabled
Signed-off-by: Roopesh Chander <roop@roopc.net>
2021-07-28 03:34:36 +05:30
1e9e21bacf UI: iOS: Tunnel detail: Incorporate on-demand-ness in 'Status'
Signed-off-by: Roopesh Chander <roop@roopc.net>
2021-07-28 03:18:02 +05:30
ac9f7b9f5e UI: iOS: Show "on-demand is active" for tunnels with the active on-demand
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
Signed-off-by: Roopesh Chander <roop@roopc.net>
2021-07-28 03:18:02 +05:30
a115dd3bd9 UI: iOS: Tunnels list: Incorporate on-demand-ness in the switch
Signed-off-by: Roopesh Chander <roop@roopc.net>
2021-07-28 03:18:01 +05:30
df9934a4b8 UI: TunnelsManager: Add setOnDemandEnabled() instance method
Signed-off-by: Roopesh Chander <roop@roopc.net>
2021-07-28 03:18:01 +05:30
40f18de4d2 UI: TunnelsManager: Add TunnelContainer.hasOnDemandRules
Signed-off-by: Roopesh Chander <roop@roopc.net>
2021-07-28 03:18:01 +05:30
13b720442d Global: bump copyright year
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-06-17 16:56:46 +02:00
c1f509d65b Kit: add missing import for WireGuardKitC
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2021-06-17 15:15:41 +02:00
87f0526f09 App: version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-06-16 18:34:54 +02:00
060c027325 Kit: Go: mod bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-06-16 18:03:28 +02:00
23bf3cfccb Kit: Adapter: use more reliable utun detection technique
Rather than hoping that the AF_SYSTEM fd is of type utun, and then
calling "2" on it to get the name -- which could be defined as something
else for a different AF_SYSTEM socket type -- instead simply query the
AF_SYSTEM control socket ID with getpeername. This has one catch, which
is that the ID is dynamically allocated, so we resolve it using the
qualified name. Normally we'd make a new AF_SYSTEM socket for this, but
since that's not allowed in the sandbox, we reuse the AF_SYSTEM socket
that we're checking. At this point in the flow, we know that it's a
proper AF_SYSTEM one, based on the first sockaddr member; we just don't
know that it's a utun variety.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-06-16 17:40:12 +02:00
7f5ad3e503 Kit: Adapter: iterate through all FDs to find UTUN
This is a bit of a kludge, until I find something better. We simply
iterate through all FDs, and call getsockopt on each one until we find
the utun FD. This works, and completes rather quickly (fd is usually 6
or 7). Rather than maintain the old path for older kernels, just use
this for all versions, to get more coverage. Other techniques involve
undocumented APIs; this one has the advantage of using nothing
undocumented.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-06-16 15:56:21 +02:00
820fa55380 SPM: update exclude rules
Fixes missing excluded file warning in Xcode. api-ios.go was renamed to api-apple.go.

Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2021-06-16 15:23:11 +02:00
eb528c766b UI: iOS: asynchronously load from NEHotspotNetwork on iOS 14
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-03-09 09:10:07 -07:00
53235eb38f UI: iOS: clean up visuals in SSID editor
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-03-09 09:10:07 -07:00
b9ff5c2e94 README: account for funky xcode paths
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-03-09 09:10:07 -07:00
b7f69d20b6 Kit: Go: bump to latest API
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-03-09 09:10:04 -07:00
6c4f4109eb UI: iOS: Disable "copy" action on on-demand cells
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2021-01-11 13:09:41 +01:00
7b5b564a6e Kit: netcfg: add explicit IP mask routes
macOS will use the wrong source address unless we add explicit routes
that mention the self-pointing gateway. Actually, it won't add any
implicit routes on its own, so in order to route the masks of the
addresses, we have to add our own routes explicitly.

However, this still doesn't fix the problem while inside of the network
extension, even though it works outside it.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2021-01-01 18:28:14 +01:00
695f868b1f Kit: Go: mod bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-12-23 22:54:47 +01:00
e724c043d9 UI: iOS: Remove duplicate call to addSubview
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-23 16:14:03 +01:00
491301f58b UI: iOS: Fix placeholder label alignment in text fields.
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-23 16:14:03 +01:00
140 changed files with 1621 additions and 576 deletions

View File

@ -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

View File

@ -6,8 +6,8 @@ import PackageDescription
let package = Package(
name: "WireGuardKit",
platforms: [
.macOS(.v10_14),
.iOS(.v12)
.macOS(.v14),
.iOS(.v17)
],
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: ".",

View File

@ -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`.

View File

@ -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)

View File

@ -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 {
@ -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
}
}

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.";

View File

@ -1,2 +1,2 @@
VERSION_NAME = 1.0.12
VERSION_ID = 22
VERSION_NAME = 1.0.16
VERSION_ID = 27

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)
}
}
}

View File

@ -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

View File

@ -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
@ -206,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)
@ -214,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 {
@ -231,12 +248,15 @@ 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
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!
// 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
@ -342,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
}
@ -389,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
}
@ -454,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
@ -498,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 {
@ -523,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
@ -544,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()
}
@ -567,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?) {

View File

@ -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)
}
}

View File

@ -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)?)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -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()

View File

@ -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)

View File

@ -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()

View File

@ -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)?)
}

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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)

View File

@ -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 {

View File

@ -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)
}

View File

@ -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")

View File

@ -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()

View File

@ -1,6 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}
}

View File

@ -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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 602 B

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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
}

View File

@ -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>

View File

@ -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>

View File

@ -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;
}

View File

@ -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
}

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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)?)
}
@ -144,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)
}
}
}
@ -291,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
@ -303,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
}
@ -312,12 +328,19 @@ 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
}
}
}

View File

@ -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

View File

@ -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
}
}

View File

@ -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

View File

@ -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

View File

@ -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]?

View File

@ -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

View File

@ -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
}
}

View File

@ -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

View File

@ -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

View File

@ -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")
}

View File

@ -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.
*/

View File

@ -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.
*/

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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")
}
}
}
}

View File

@ -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()
}

View File

@ -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)
}
}
}

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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";

View File

@ -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
@ -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.";

View File

@ -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
@ -368,7 +368,6 @@
"alertTunnelNameEmptyMessage" = "Cannot create tunnel with an empty name";
"alertTunnelNameEmptyTitle" = "No name provided";
"macMenuAddEmptyTunnel" = "Add Empty Tunnel…";
"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.";
"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.";

View File

@ -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
@ -363,7 +363,6 @@
"alertTunnelNameEmptyMessage" = "Cannot create tunnel with an empty name";
"alertInvalidInterfaceMessageMTUInvalid" = "Interfaces 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" = "Peers endpoint must be of the form host:port or [host]:port";
"alertInvalidPeerMessagePublicKeyDuplicated" = "Two or more peers cannot have the same public key";
"alertInvalidPeerMessagePreSharedKeyInvalid" = "Peers preshared key must be a 32-byte key in base64 encoding";

Some files were not shown because too many files have changed in this diff Show More