Compare commits
142 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 571349bb3d | |||
| 98ebd55208 | |||
| 83d0d34411 | |||
| 8c7c4b6792 | |||
| 5b34f49166 | |||
| cef3957875 | |||
| d9e88c51bd | |||
| db876647d6 | |||
| 90eb45e287 | |||
| 9f3d86723a | |||
| 11063d0f88 | |||
| 557ee4390b | |||
| 9ce42d152d | |||
| 4c1b2e1258 | |||
| 3bd611aa7c | |||
| adbe0b065e | |||
| 6015661beb | |||
| 6e6a6b88fb | |||
| 9690365dd4 | |||
| 0299c3929e | |||
| 0043233872 | |||
| a74dd24578 | |||
| dd9506ecee | |||
| 0c2eb003a0 | |||
| f83f159f97 | |||
| 6175de0438 | |||
| bd61be52e6 | |||
| 909f88be70 | |||
| b7c3bd0d8c | |||
| 4237ab4a6f | |||
| 0fcaf6debb | |||
| dbd5ea1ff0 | |||
| 9afe230c10 | |||
| 4bdfbb518e | |||
| fbe101eabb | |||
| cda3170970 | |||
| a5e7c3906b | |||
| b21fdfed67 | |||
| 5f8843e247 | |||
| 7a3f65fd2f | |||
| dca0fb29f6 | |||
| af9c800af8 | |||
| a9b925c69b | |||
| f93f9d62f4 | |||
| fc163fc9ff | |||
| adc5a7cac2 | |||
| 0dd22ca45a | |||
| bca9fead5e | |||
| 51822f722a | |||
| 121d223229 | |||
| 439fb6bbac | |||
| 9c8231dcf7 | |||
| 0440c4a33a | |||
| 01be43aa7a | |||
| e29c6900e5 | |||
| a334c25aff | |||
| f56b2ad968 | |||
| 503ac6c8a2 | |||
| 5f30e021ef | |||
| d748382fce | |||
| 63299a2752 | |||
| b7f8f74b56 | |||
| 8e5a9215de | |||
| 64925cab89 | |||
| 062b4d4b16 | |||
| d9bdc61fb9 | |||
| 0ae8d25134 | |||
| 574d8433b3 | |||
| bd339e2876 | |||
| fff75adfe1 | |||
| 01604dd8d1 | |||
| bdeb89a9e5 | |||
| 36dc252512 | |||
| 5941bf181c | |||
| 7a450089c0 | |||
| 5d757982ba | |||
| 3767a12983 | |||
| 9795b0609a | |||
| 0f98312d15 | |||
| f81275812c | |||
| b2b5e0e379 | |||
| a6f80135ef | |||
| e23c221aff | |||
| 50bc994762 | |||
| 3e05da4486 | |||
| c750f28c67 | |||
| f6c70500a7 | |||
| 663923864c | |||
| 9250780ffc | |||
| 9bc17034dd | |||
| db6f0729c6 | |||
| 4503c11b0c | |||
| fe4f8b666d | |||
| 90c0f7e92e | |||
| 3afcee04be | |||
| 202e7a4890 | |||
| d7b16ffb1f | |||
| b1dabf5a00 | |||
| a389bd93cb | |||
| b2a2110d8c | |||
| 5ed28907ec | |||
| ab6d714070 | |||
| d3df8734c2 | |||
| ea5996abe0 | |||
| ce405f856e | |||
| 98a967acc8 | |||
| b01d09dfb5 | |||
| 7a580e8941 | |||
| 39fb52a2e3 | |||
| 69a064d954 | |||
| eb684ef711 | |||
| b0eff424f9 | |||
| c195760b15 | |||
| ba3f0db92c | |||
| 5031a7db4c | |||
| a355232e09 | |||
| 6f7214ff38 | |||
| 4c88f477a2 | |||
| 2fb9d6af71 | |||
| 38ac66071c | |||
| 910fdfc321 | |||
| c38a88988b | |||
| cf51344444 | |||
| fda36b571d | |||
| 1be3224d7a | |||
| ca088e9ddc | |||
| fcca2d4fec | |||
| 58181a4d40 | |||
| cf816ede13 | |||
| fbac282bdc | |||
| 61f5e017c8 | |||
| 4547e01283 | |||
| 5792db22a6 | |||
| 9d5aa1d8fa | |||
| 6331b81b5d | |||
| 77f929789c | |||
| b5b72b309f | |||
| 966fa7909b | |||
| 115059f2bb | |||
| e53c2d4d17 | |||
| 0a3a5ee900 | |||
| 7720307fc9 |
@@ -1,3 +0,0 @@
|
|||||||
[submodule "wireguard-go"]
|
|
||||||
path = wireguard-go
|
|
||||||
url = https://git.zx2c4.com/wireguard-go
|
|
||||||
+140
@@ -0,0 +1,140 @@
|
|||||||
|
# Installing WireGuard tunnels using Configuration Profiles
|
||||||
|
|
||||||
|
WireGuard configurations can be installed using Configuration Profiles
|
||||||
|
through .mobileconfig files.
|
||||||
|
|
||||||
|
### Top-level payload entries
|
||||||
|
|
||||||
|
A .mobileconfig file is a plist file in XML format. The top-level XML item is a top-level payload dictionary (dict). This payload dictionary should contain the following keys:
|
||||||
|
|
||||||
|
- `PayloadDisplayName` (string): The name of the configuration profile, visible when installing the profile
|
||||||
|
|
||||||
|
- `PayloadType` (string): Should be `Configuration`
|
||||||
|
|
||||||
|
- `PayloadVersion` (integer): Should be `1`
|
||||||
|
|
||||||
|
- `PayloadIdentifier` (string): A reverse-DNS style unique identifier for the profile file.
|
||||||
|
If you install another .mobileconfig file with the same identifier, the new one
|
||||||
|
overwrites the old one.
|
||||||
|
|
||||||
|
- `PayloadUUID` (string): A randomly generated UUID for this payload
|
||||||
|
|
||||||
|
- `PayloadContent` (array): Should contain an array of payload dictionaries.
|
||||||
|
Each of these payload dictionaries can represent a WireGuard tunnel
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
Here's an example .mobileconfig with the above fields filled in:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PayloadDisplayName</key>
|
||||||
|
<string>WireGuard Demo Configuration Profile</string>
|
||||||
|
<key>PayloadType</key>
|
||||||
|
<string>Configuration</string>
|
||||||
|
<key>PayloadVersion</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>PayloadIdentifier</key>
|
||||||
|
<string>com.your-org.wireguard.FCC9BF80-C540-44C1-B243-521FDD1B2905</string>
|
||||||
|
<key>PayloadUUID</key>
|
||||||
|
<string>F346AAF4-53A2-4FA1-ACA3-EEE74DBED029</string>
|
||||||
|
<key>PayloadContent</key>
|
||||||
|
<array>
|
||||||
|
<!-- An array of WireGuard configuration payload dictionaries -->
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
```
|
||||||
|
|
||||||
|
### WireGuard payload entries
|
||||||
|
|
||||||
|
Each WireGuard configuration payload dictionary should contain the following
|
||||||
|
keys:
|
||||||
|
|
||||||
|
- `PayloadDisplayName` (string): Should be `VPN`
|
||||||
|
|
||||||
|
- `PayloadType` (string): Should be `com.apple.vpn.managed`
|
||||||
|
|
||||||
|
- `PayloadVersion` (integer): Should be `1`
|
||||||
|
|
||||||
|
- `PayloadIdentifier` (string): A reverse-DNS style unique identifier for the WireGuard configuration profile.
|
||||||
|
|
||||||
|
- `PayloadUUID` (string): A randomly generated UUID for this payload
|
||||||
|
|
||||||
|
- `UserDefinedName` (string): The name of the WireGuard tunnel.
|
||||||
|
This name shall be used to represent the tunnel in the WireGuard app, and in the System UI for VPNs (Settings > VPN on iOS, System Preferences > Network on macOS).
|
||||||
|
|
||||||
|
- `VPNType` (string): Should be `VPN`
|
||||||
|
|
||||||
|
- `VPNSubType` (string): Should be set as the bundle identifier of the WireGuard app.
|
||||||
|
|
||||||
|
- iOS: `com.wireguard.ios`
|
||||||
|
- macOS: `com.wireguard.macos`
|
||||||
|
|
||||||
|
- `VendorConfig` (dict): Should be a dictionary with the following key:
|
||||||
|
|
||||||
|
- `WgQuickConfig` (string): Should be a WireGuard configuration in [wg-quick(8)] / [wg(8)] format.
|
||||||
|
The keys 'FwMark', 'Table', 'PreUp', 'PostUp', 'PreDown', 'PostDown' and 'SaveConfig' are not supported.
|
||||||
|
|
||||||
|
- `VPN` (dict): Should be a dictionary with the following keys:
|
||||||
|
|
||||||
|
- `RemoteAddress` (string): A non-empty string.
|
||||||
|
This string is displayed as the server name in the System UI for
|
||||||
|
VPNs (Settings > VPN on iOS, System Preferences > Network on macOS).
|
||||||
|
|
||||||
|
- `AuthenticationMethod` (string): Should be `Password`
|
||||||
|
|
||||||
|
Here's an example WireGuard configuration payload dictionary:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<!-- A WireGuard configuration payload dictionary -->
|
||||||
|
<dict>
|
||||||
|
<key>PayloadDisplayName</key>
|
||||||
|
<string>VPN</string>
|
||||||
|
<key>PayloadType</key>
|
||||||
|
<string>com.apple.vpn.managed</string>
|
||||||
|
<key>PayloadVersion</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>PayloadIdentifier</key>
|
||||||
|
<string>com.your-org.wireguard.demo-profile-1.demo-tunnel</string>
|
||||||
|
<key>PayloadUUID</key>
|
||||||
|
<string>44CDFE9F-4DC7-472A-956F-61C68055117C</string>
|
||||||
|
<key>UserDefinedName</key>
|
||||||
|
<string>Demo from MobileConfig file</string>
|
||||||
|
<key>VPNType</key>
|
||||||
|
<string>VPN</string>
|
||||||
|
<key>VPNSubType</key>
|
||||||
|
<string>com.wireguard.ios</string>
|
||||||
|
<key>VendorConfig</key>
|
||||||
|
<dict>
|
||||||
|
<key>WgQuickConfig</key>
|
||||||
|
<string>
|
||||||
|
[Interface]
|
||||||
|
PrivateKey = mInDaw06K0NgfULRObHJjkWD3ahUC8XC1tVjIf6W+Vo=
|
||||||
|
Address = 10.10.1.0/24
|
||||||
|
DNS = 1.1.1.1, 1.0.0.1
|
||||||
|
|
||||||
|
[Peer]
|
||||||
|
PublicKey = JRI8Xc0zKP9kXk8qP84NdUQA04h6DLfFbwJn4g+/PFs=
|
||||||
|
Endpoint = demo.wireguard.com:12912
|
||||||
|
AllowedIPs = 0.0.0.0/0
|
||||||
|
</string>
|
||||||
|
</dict>
|
||||||
|
<key>VPN</key>
|
||||||
|
<dict>
|
||||||
|
<key>RemoteAddress</key>
|
||||||
|
<string>demo.wireguard.com:12912</string>
|
||||||
|
<key>AuthenticationMethod</key>
|
||||||
|
<string>Password</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Caveats
|
||||||
|
|
||||||
|
Configurations added via .mobileconfig will not be migrated into keychain until the WireGuard application is opened once.
|
||||||
|
|
||||||
|
[wg-quick(8)]: https://git.zx2c4.com/WireGuard/about/src/tools/man/wg-quick.8
|
||||||
|
[wg(8)]: https://git.zx2c4.com/WireGuard/about/src/tools/man/wg.8
|
||||||
@@ -1,21 +1,14 @@
|
|||||||
# [WireGuard](https://www.wireguard.com/) for iOS
|
# [WireGuard](https://www.wireguard.com/) for iOS and macOS
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
- Clone this repo:
|
- Clone this repo recursively:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ git clone https://git.zx2c4.com/wireguard-ios
|
$ git clone --recursive https://git.zx2c4.com/wireguard-ios
|
||||||
$ cd wireguard-ios
|
$ cd wireguard-ios
|
||||||
```
|
```
|
||||||
|
|
||||||
- Init and update submodule:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ git submodule init
|
|
||||||
$ git submodule update
|
|
||||||
```
|
|
||||||
|
|
||||||
- Rename and populate developer team ID file:
|
- Rename and populate developer team ID file:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -23,19 +16,19 @@ $ cp WireGuard/WireGuard/Config/Developer.xcconfig.template WireGuard/WireGuard/
|
|||||||
$ vim WireGuard/WireGuard/Config/Developer.xcconfig
|
$ vim WireGuard/WireGuard/Config/Developer.xcconfig
|
||||||
```
|
```
|
||||||
|
|
||||||
- Install swiftlint:
|
- Install swiftlint and go:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ brew install swiftlint
|
$ brew install swiftlint go
|
||||||
```
|
```
|
||||||
|
|
||||||
- Open project in XCode:
|
- Open project in Xcode:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ open ./WireGuard/WireGuard.xcodeproj
|
$ open ./WireGuard/WireGuard.xcodeproj
|
||||||
```
|
```
|
||||||
|
|
||||||
- Flip switches, press buttons, and make whirling noises until XCode builds it.
|
- Flip switches, press buttons, and make whirling noises until Xcode builds it.
|
||||||
|
|
||||||
## MIT License
|
## MIT License
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,6 @@ opt_in_rules:
|
|||||||
- unneeded_parentheses_in_closure_argument
|
- unneeded_parentheses_in_closure_argument
|
||||||
- unused_import
|
- unused_import
|
||||||
- trailing_closure
|
- trailing_closure
|
||||||
variable_name:
|
identifier_name:
|
||||||
min_length:
|
min_length:
|
||||||
warning: 0
|
warning: 0
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ extension FileManager {
|
|||||||
return sharedFolderURL
|
return sharedFolderURL
|
||||||
}
|
}
|
||||||
|
|
||||||
static var networkExtensionLogFileURL: URL? {
|
static var logFileURL: URL? {
|
||||||
return sharedFolderURL?.appendingPathComponent("tunnel-log.bin")
|
return sharedFolderURL?.appendingPathComponent("tunnel-log.bin")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,14 +35,6 @@ extension FileManager {
|
|||||||
return sharedFolderURL?.appendingPathComponent("last-error.txt")
|
return sharedFolderURL?.appendingPathComponent("last-error.txt")
|
||||||
}
|
}
|
||||||
|
|
||||||
static var appLogFileURL: URL? {
|
|
||||||
guard let documentDirURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first else {
|
|
||||||
wg_log(.error, message: "Cannot obtain app documents folder URL")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return documentDirURL.appendingPathComponent("app-log.bin")
|
|
||||||
}
|
|
||||||
|
|
||||||
static func deleteFile(at url: URL) -> Bool {
|
static func deleteFile(at url: URL) -> Bool {
|
||||||
do {
|
do {
|
||||||
try FileManager.default.removeItem(at: url)
|
try FileManager.default.removeItem(at: url)
|
||||||
|
|||||||
@@ -12,10 +12,12 @@ public class Logger {
|
|||||||
static var global: Logger?
|
static var global: Logger?
|
||||||
|
|
||||||
var log: OpaquePointer
|
var log: OpaquePointer
|
||||||
|
var tag: String
|
||||||
|
|
||||||
init(withFilePath filePath: String) throws {
|
init(tagged tag: String, withFilePath filePath: String) throws {
|
||||||
guard let log = open_log(filePath) else { throw LoggerError.openFailure }
|
guard let log = open_log(filePath) else { throw LoggerError.openFailure }
|
||||||
self.log = log
|
self.log = log
|
||||||
|
self.tag = tag
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
@@ -23,17 +25,14 @@ public class Logger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func log(message: String) {
|
func log(message: String) {
|
||||||
write_msg_to_log(log, message.trimmingCharacters(in: .newlines))
|
write_msg_to_log(log, tag, message.trimmingCharacters(in: .newlines))
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeLog(called ourTag: String, mergedWith otherLogFile: String, called otherTag: String, to targetFile: String) -> Bool {
|
func writeLog(to targetFile: String) -> Bool {
|
||||||
guard let other = open_log(otherLogFile) else { return false }
|
return write_log_to_file(targetFile, self.log) == 0
|
||||||
let ret = write_logs_to_file(targetFile, log, ourTag, other, otherTag)
|
|
||||||
close_log(other)
|
|
||||||
return ret == 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static func configureGlobal(withFilePath filePath: String?) {
|
static func configureGlobal(tagged tag: String, withFilePath filePath: String?) {
|
||||||
if Logger.global != nil {
|
if Logger.global != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -41,7 +40,7 @@ public class Logger {
|
|||||||
os_log("Unable to determine log destination path. Log will not be saved to file.", log: OSLog.default, type: .error)
|
os_log("Unable to determine log destination path. Log will not be saved to file.", log: OSLog.default, type: .error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let logger = try? Logger(withFilePath: filePath) else {
|
guard let logger = try? Logger(tagged: tag, withFilePath: filePath) else {
|
||||||
os_log("Unable to open log file for writing. Log will not be saved to file.", log: OSLog.default, type: .error)
|
os_log("Unable to open log file for writing. Log will not be saved to file.", log: OSLog.default, type: .error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -52,7 +51,6 @@ public class Logger {
|
|||||||
}
|
}
|
||||||
let goBackendVersion = WIREGUARD_GO_VERSION
|
let goBackendVersion = WIREGUARD_GO_VERSION
|
||||||
Logger.global?.log(message: "App version: \(appVersion); Go backend version: \(goBackendVersion)")
|
Logger.global?.log(message: "App version: \(appVersion); Go backend version: \(goBackendVersion)")
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdatomic.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
@@ -19,100 +21,124 @@
|
|||||||
|
|
||||||
enum {
|
enum {
|
||||||
MAX_LOG_LINE_LENGTH = 512,
|
MAX_LOG_LINE_LENGTH = 512,
|
||||||
MAX_LINES = 1024,
|
MAX_LINES = 2048,
|
||||||
MAGIC = 0xbeefbabeU
|
MAGIC = 0xabadbeefU
|
||||||
};
|
};
|
||||||
|
|
||||||
struct log_line {
|
struct log_line {
|
||||||
struct timeval tv;
|
atomic_uint_fast64_t time_ns;
|
||||||
char line[MAX_LOG_LINE_LENGTH];
|
char line[MAX_LOG_LINE_LENGTH];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct log {
|
struct log {
|
||||||
struct { uint32_t first, len; } header;
|
atomic_uint_fast32_t next_index;
|
||||||
struct log_line lines[MAX_LINES];
|
struct log_line lines[MAX_LINES];
|
||||||
uint32_t magic;
|
uint32_t magic;
|
||||||
};
|
};
|
||||||
|
|
||||||
void write_msg_to_log(struct log *log, const char *msg)
|
void write_msg_to_log(struct log *log, const char *tag, const char *msg)
|
||||||
{
|
{
|
||||||
struct log_line *line = &log->lines[(log->header.first + log->header.len) % MAX_LINES];
|
uint32_t index;
|
||||||
|
struct log_line *line;
|
||||||
|
struct timespec ts;
|
||||||
|
|
||||||
if (log->header.len == MAX_LINES)
|
// Race: This isn't synchronized with the fetch_add below, so items might be slightly out of order.
|
||||||
log->header.first = (log->header.first + 1) % MAX_LINES;
|
clock_gettime(CLOCK_REALTIME, &ts);
|
||||||
else
|
|
||||||
++log->header.len;
|
|
||||||
|
|
||||||
gettimeofday(&line->tv, NULL);
|
// Race: More than MAX_LINES writers and this will clash.
|
||||||
strncpy(line->line, msg, MAX_LOG_LINE_LENGTH - 1);
|
index = atomic_fetch_add(&log->next_index, 1);
|
||||||
line->line[MAX_LOG_LINE_LENGTH - 1] = '\0';
|
line = &log->lines[index % MAX_LINES];
|
||||||
|
|
||||||
msync(&log->header, sizeof(log->header), MS_ASYNC);
|
// Race: Before this line executes, we'll display old data after new data.
|
||||||
|
atomic_store(&line->time_ns, 0);
|
||||||
|
memset(line->line, 0, MAX_LOG_LINE_LENGTH);
|
||||||
|
|
||||||
|
snprintf(line->line, MAX_LOG_LINE_LENGTH, "[%s] %s", tag, msg);
|
||||||
|
atomic_store(&line->time_ns, ts.tv_sec * 1000000000ULL + ts.tv_nsec);
|
||||||
|
|
||||||
|
msync(&log->next_index, sizeof(log->next_index), MS_ASYNC);
|
||||||
msync(line, sizeof(*line), MS_ASYNC);
|
msync(line, sizeof(*line), MS_ASYNC);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool first_before_second(const struct log_line *line1, const struct log_line *line2)
|
int write_log_to_file(const char *file_name, const struct log *input_log)
|
||||||
{
|
{
|
||||||
if (line1->tv.tv_sec <= line2->tv.tv_sec)
|
struct log *log;
|
||||||
return true;
|
uint32_t l, i;
|
||||||
if (line1->tv.tv_sec == line2->tv.tv_sec)
|
|
||||||
return line1->tv.tv_usec <= line2->tv.tv_usec;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int write_logs_to_file(const char *file_name, const struct log *log1, const char *tag1, const struct log *log2, const char *tag2)
|
|
||||||
{
|
|
||||||
uint32_t i1, i2, len1 = log1->header.len, len2 = log2->header.len;
|
|
||||||
FILE *file;
|
FILE *file;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if (len1 > MAX_LINES)
|
log = malloc(sizeof(*log));
|
||||||
len1 = MAX_LINES;
|
if (!log)
|
||||||
if (len2 > MAX_LINES)
|
return -errno;
|
||||||
len2 = MAX_LINES;
|
memcpy(log, input_log, sizeof(*log));
|
||||||
|
|
||||||
file = fopen(file_name, "w");
|
file = fopen(file_name, "w");
|
||||||
if (!file)
|
if (!file) {
|
||||||
|
free(log);
|
||||||
return -errno;
|
return -errno;
|
||||||
|
}
|
||||||
|
|
||||||
for (i1 = 0, i2 = 0;;) {
|
for (l = 0, i = log->next_index; l < MAX_LINES; ++l, ++i) {
|
||||||
|
const struct log_line *line = &log->lines[i % MAX_LINES];
|
||||||
|
time_t seconds = line->time_ns / 1000000000ULL;
|
||||||
|
uint32_t useconds = (line->time_ns % 1000000000ULL) / 1000ULL;
|
||||||
struct tm tm;
|
struct tm tm;
|
||||||
char buf[MAX_LOG_LINE_LENGTH];
|
|
||||||
const struct log_line *line1 = &log1->lines[(log1->header.first + i1) % MAX_LINES];
|
|
||||||
const struct log_line *line2 = &log2->lines[(log2->header.first + i2) % MAX_LINES];
|
|
||||||
const struct log_line *line;
|
|
||||||
const char *tag;
|
|
||||||
|
|
||||||
if (i1 < len1 && (i2 >= len2 || first_before_second(line1, line2))) {
|
if (!line->time_ns)
|
||||||
line = line1;
|
continue;
|
||||||
tag = tag1;
|
|
||||||
++i1;
|
|
||||||
} else if (i2 < len2 && (i1 >= len1 || first_before_second(line2, line1))) {
|
|
||||||
line = line2;
|
|
||||||
tag = tag2;
|
|
||||||
++i2;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(buf, line->line, MAX_LOG_LINE_LENGTH);
|
if (!localtime_r(&seconds, &tm))
|
||||||
buf[MAX_LOG_LINE_LENGTH - 1] = '\0';
|
|
||||||
if (!localtime_r(&line->tv.tv_sec, &tm))
|
|
||||||
goto err;
|
goto err;
|
||||||
if (fprintf(file, "%04d-%02d-%02d %02d:%02d:%02d.%06d: [%s] %s\n",
|
|
||||||
|
if (fprintf(file, "%04d-%02d-%02d %02d:%02d:%02d.%06d: %s\n",
|
||||||
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
|
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
|
||||||
tm.tm_hour, tm.tm_min, tm.tm_sec, line->tv.tv_usec,
|
tm.tm_hour, tm.tm_min, tm.tm_sec, useconds,
|
||||||
tag, buf) < 0)
|
line->line) < 0)
|
||||||
goto err;
|
goto err;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
errno = 0;
|
errno = 0;
|
||||||
|
|
||||||
err:
|
err:
|
||||||
ret = -errno;
|
ret = -errno;
|
||||||
fclose(file);
|
fclose(file);
|
||||||
|
free(log);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t view_lines_from_cursor(const struct log *input_log, uint32_t cursor, void *ctx, void(*cb)(const char *, uint64_t, void *))
|
||||||
|
{
|
||||||
|
struct log *log;
|
||||||
|
uint32_t l, i = cursor;
|
||||||
|
|
||||||
|
log = malloc(sizeof(*log));
|
||||||
|
if (!log)
|
||||||
|
return cursor;
|
||||||
|
memcpy(log, input_log, sizeof(*log));
|
||||||
|
|
||||||
|
if (i == -1)
|
||||||
|
i = log->next_index;
|
||||||
|
|
||||||
|
for (l = 0; l < MAX_LINES; ++l, ++i) {
|
||||||
|
const struct log_line *line = &log->lines[i % MAX_LINES];
|
||||||
|
|
||||||
|
if (cursor != -1 && i % MAX_LINES == log->next_index % MAX_LINES)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (!line->time_ns) {
|
||||||
|
if (cursor == -1)
|
||||||
|
continue;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cb(line->line, line->time_ns, ctx);
|
||||||
|
cursor = (i + 1) % MAX_LINES;
|
||||||
|
}
|
||||||
|
free(log);
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
struct log *open_log(const char *file_name)
|
struct log *open_log(const char *file_name)
|
||||||
{
|
{
|
||||||
int fd;
|
int fd;
|
||||||
|
|||||||
@@ -6,9 +6,12 @@
|
|||||||
#ifndef RINGLOGGER_H
|
#ifndef RINGLOGGER_H
|
||||||
#define RINGLOGGER_H
|
#define RINGLOGGER_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
struct log;
|
struct log;
|
||||||
void write_msg_to_log(struct log *log, const char *msg);
|
void write_msg_to_log(struct log *log, const char *tag, const char *msg);
|
||||||
int write_logs_to_file(const char *file_name, const struct log *log1, const char *tag1, const struct log *log2, const char *tag2);
|
int write_log_to_file(const char *file_name, const struct log *input_log);
|
||||||
|
uint32_t view_lines_from_cursor(const struct log *input_log, uint32_t cursor, void *ctx, void(*)(const char *, uint64_t, void *));
|
||||||
struct log *open_log(const char *file_name);
|
struct log *open_log(const char *file_name);
|
||||||
void close_log(struct log *log);
|
void close_log(struct log *log);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
#include "ringlogger.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
|
static void forkwrite(void)
|
||||||
|
{
|
||||||
|
struct log *log = open_log("/tmp/test_log");
|
||||||
|
char c[512];
|
||||||
|
int i, base;
|
||||||
|
bool in_fork = !fork();
|
||||||
|
|
||||||
|
base = 10000 * in_fork;
|
||||||
|
for (i = 0; i < 1024; ++i) {
|
||||||
|
snprintf(c, 512, "bla bla bla %d", base + i);
|
||||||
|
write_msg_to_log(log, "HMM", c);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (in_fork)
|
||||||
|
_exit(0);
|
||||||
|
wait(NULL);
|
||||||
|
|
||||||
|
write_log_to_file("/dev/stdout", log);
|
||||||
|
close_log(log);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void writetext(const char *text)
|
||||||
|
{
|
||||||
|
struct log *log = open_log("/tmp/test_log");
|
||||||
|
write_msg_to_log(log, "TXT", text);
|
||||||
|
close_log(log);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void show_line(const char *line, uint64_t time_ns)
|
||||||
|
{
|
||||||
|
printf("%" PRIu64 ": %s\n", time_ns, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void follow(void)
|
||||||
|
{
|
||||||
|
uint32_t cursor = -1;
|
||||||
|
struct log *log = open_log("/tmp/test_log");
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
cursor = view_lines_from_cursor(log, cursor, show_line);
|
||||||
|
usleep(1000 * 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
if (!strcmp(argv[1], "fork"))
|
||||||
|
forkwrite();
|
||||||
|
else if (!strcmp(argv[1], "write"))
|
||||||
|
writetext(argv[2]);
|
||||||
|
else if (!strcmp(argv[1], "follow"))
|
||||||
|
follow();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -13,8 +13,8 @@ extension Data {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var out = Data(repeating: 0, count: Int(WG_KEY_LEN_HEX))
|
var out = Data(repeating: 0, count: Int(WG_KEY_LEN_HEX))
|
||||||
out.withUnsafeMutableBytes { outBytes in
|
out.withUnsafeMutableInt8Bytes { outBytes in
|
||||||
self.withUnsafeBytes { inBytes in
|
self.withUnsafeUInt8Bytes { inBytes in
|
||||||
key_to_hex(outBytes, inBytes)
|
key_to_hex(outBytes, inBytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,7 @@ extension Data {
|
|||||||
init?(hexKey hexString: String) {
|
init?(hexKey hexString: String) {
|
||||||
self.init(repeating: 0, count: Int(WG_KEY_LEN))
|
self.init(repeating: 0, count: Int(WG_KEY_LEN))
|
||||||
|
|
||||||
if !self.withUnsafeMutableBytes { key_from_hex($0, hexString) } {
|
if !self.withUnsafeMutableUInt8Bytes { key_from_hex($0, hexString) } {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -35,8 +35,8 @@ extension Data {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var out = Data(repeating: 0, count: Int(WG_KEY_LEN_BASE64))
|
var out = Data(repeating: 0, count: Int(WG_KEY_LEN_BASE64))
|
||||||
out.withUnsafeMutableBytes { outBytes in
|
out.withUnsafeMutableInt8Bytes { outBytes in
|
||||||
self.withUnsafeBytes { inBytes in
|
self.withUnsafeUInt8Bytes { inBytes in
|
||||||
key_to_base64(outBytes, inBytes)
|
key_to_base64(outBytes, inBytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,8 +47,34 @@ extension Data {
|
|||||||
init?(base64Key base64String: String) {
|
init?(base64Key base64String: String) {
|
||||||
self.init(repeating: 0, count: Int(WG_KEY_LEN))
|
self.init(repeating: 0, count: Int(WG_KEY_LEN))
|
||||||
|
|
||||||
if !self.withUnsafeMutableBytes { key_from_base64($0, base64String) } {
|
if !self.withUnsafeMutableUInt8Bytes { key_from_base64($0, base64String) } {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Data {
|
||||||
|
func withUnsafeUInt8Bytes<R>(_ body: (UnsafePointer<UInt8>) -> R) -> R {
|
||||||
|
assert(!isEmpty)
|
||||||
|
return self.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) -> R in
|
||||||
|
let bytes = ptr.bindMemory(to: UInt8.self)
|
||||||
|
return body(bytes.baseAddress!) // might crash if self.count == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func withUnsafeMutableUInt8Bytes<R>(_ body: (UnsafeMutablePointer<UInt8>) -> R) -> R {
|
||||||
|
assert(!isEmpty)
|
||||||
|
return self.withUnsafeMutableBytes { (ptr: UnsafeMutableRawBufferPointer) -> R in
|
||||||
|
let bytes = ptr.bindMemory(to: UInt8.self)
|
||||||
|
return body(bytes.baseAddress!) // might crash if self.count == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func withUnsafeMutableInt8Bytes<R>(_ body: (UnsafeMutablePointer<Int8>) -> R) -> R {
|
||||||
|
assert(!isEmpty)
|
||||||
|
return self.withUnsafeMutableBytes { (ptr: UnsafeMutableRawBufferPointer) -> R in
|
||||||
|
let bytes = ptr.bindMemory(to: Int8.self)
|
||||||
|
return body(bytes.baseAddress!) // might crash if self.count == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ extension Endpoint {
|
|||||||
return "\(address):\(port)"
|
return "\(address):\(port)"
|
||||||
case .ipv6(let address):
|
case .ipv6(let address):
|
||||||
return "[\(address)]:\(port)"
|
return "[\(address)]:\(port)"
|
||||||
|
@unknown default:
|
||||||
|
fatalError()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,6 +80,8 @@ extension Endpoint {
|
|||||||
return true
|
return true
|
||||||
case .ipv6:
|
case .ipv6:
|
||||||
return true
|
return true
|
||||||
|
@unknown default:
|
||||||
|
fatalError()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,6 +93,8 @@ extension Endpoint {
|
|||||||
return nil
|
return nil
|
||||||
case .ipv6:
|
case .ipv6:
|
||||||
return nil
|
return nil
|
||||||
|
@unknown default:
|
||||||
|
fatalError()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,17 +34,14 @@ extension NETunnelProviderProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func asTunnelConfiguration(called name: String? = nil) -> TunnelConfiguration? {
|
func asTunnelConfiguration(called name: String? = nil) -> TunnelConfiguration? {
|
||||||
migrateConfigurationIfNeeded(called: name ?? "unknown")
|
if let passwordReference = passwordReference,
|
||||||
//TODO: in the case where migrateConfigurationIfNeeded is called by the network extension,
|
let config = Keychain.openReference(called: passwordReference) {
|
||||||
// before the app has started, and when there is, in fact, configuration that needs to be
|
return try? TunnelConfiguration(fromWgQuickConfig: config, called: name)
|
||||||
// put into the keychain, this will generate one new keychain item every time it is started,
|
}
|
||||||
// until finally the app is open. Would it be possible to call saveToPreferences here? Or is
|
if let oldConfig = providerConfiguration?["WgQuickConfig"] as? String {
|
||||||
// that generally not available to network extensions? In which case, what should our
|
return try? TunnelConfiguration(fromWgQuickConfig: oldConfig, called: name)
|
||||||
// behavior be?
|
}
|
||||||
|
return nil
|
||||||
guard let passwordReference = passwordReference else { return nil }
|
|
||||||
guard let config = Keychain.openReference(called: passwordReference) else { return nil }
|
|
||||||
return try? TunnelConfiguration(fromWgQuickConfig: config, called: name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func destroyConfigurationReference() {
|
func destroyConfigurationReference() {
|
||||||
@@ -52,9 +49,9 @@ extension NETunnelProviderProtocol {
|
|||||||
Keychain.deleteReference(called: ref)
|
Keychain.deleteReference(called: ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifyConfigurationReference() -> Data? {
|
func verifyConfigurationReference() -> Bool {
|
||||||
guard let ref = passwordReference else { return nil }
|
guard let ref = passwordReference else { return false }
|
||||||
return Keychain.verifyReference(called: ref) ? ref : nil
|
return Keychain.verifyReference(called: ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
@@ -66,6 +63,7 @@ extension NETunnelProviderProtocol {
|
|||||||
guard let oldConfig = providerConfiguration?["WgQuickConfig"] as? String else { return false }
|
guard let oldConfig = providerConfiguration?["WgQuickConfig"] as? String else { return false }
|
||||||
providerConfiguration = nil
|
providerConfiguration = nil
|
||||||
guard passwordReference == nil else { return true }
|
guard passwordReference == nil else { return true }
|
||||||
|
wg_log(.debug, message: "Migrating tunnel configuration '\(name)'")
|
||||||
passwordReference = Keychain.makeReference(containing: oldConfig, called: name)
|
passwordReference = Keychain.makeReference(containing: oldConfig, called: name)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,15 +52,15 @@ extension TunnelConfiguration {
|
|||||||
trimmedLine = String(line)
|
trimmedLine = String(line)
|
||||||
}
|
}
|
||||||
|
|
||||||
trimmedLine = trimmedLine.trimmingCharacters(in: .whitespaces)
|
trimmedLine = trimmedLine.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
let lowercasedLine = trimmedLine.lowercased()
|
let lowercasedLine = trimmedLine.lowercased()
|
||||||
|
|
||||||
if !trimmedLine.isEmpty {
|
if !trimmedLine.isEmpty {
|
||||||
if let equalsIndex = trimmedLine.firstIndex(of: "=") {
|
if let equalsIndex = trimmedLine.firstIndex(of: "=") {
|
||||||
// Line contains an attribute
|
// Line contains an attribute
|
||||||
let keyWithCase = trimmedLine[..<equalsIndex].trimmingCharacters(in: .whitespaces)
|
let keyWithCase = trimmedLine[..<equalsIndex].trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
let key = keyWithCase.lowercased()
|
let key = keyWithCase.lowercased()
|
||||||
let value = trimmedLine[trimmedLine.index(equalsIndex, offsetBy: 1)...].trimmingCharacters(in: .whitespaces)
|
let value = trimmedLine[trimmedLine.index(equalsIndex, offsetBy: 1)...].trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
let keysWithMultipleEntriesAllowed: Set<String> = ["address", "allowedips", "dns"]
|
let keysWithMultipleEntriesAllowed: Set<String> = ["address", "allowedips", "dns"]
|
||||||
if let presentValue = attributes[key] {
|
if let presentValue = attributes[key] {
|
||||||
if keysWithMultipleEntriesAllowed.contains(key) {
|
if keysWithMultipleEntriesAllowed.contains(key) {
|
||||||
@@ -182,7 +182,7 @@ extension TunnelConfiguration {
|
|||||||
}
|
}
|
||||||
if let addressesString = attributes["address"] {
|
if let addressesString = attributes["address"] {
|
||||||
var addresses = [IPAddressRange]()
|
var addresses = [IPAddressRange]()
|
||||||
for addressString in addressesString.splitToArray(trimmingCharacters: .whitespaces) {
|
for addressString in addressesString.splitToArray(trimmingCharacters: .whitespacesAndNewlines) {
|
||||||
guard let address = IPAddressRange(from: addressString) else {
|
guard let address = IPAddressRange(from: addressString) else {
|
||||||
throw ParseError.interfaceHasInvalidAddress(addressString)
|
throw ParseError.interfaceHasInvalidAddress(addressString)
|
||||||
}
|
}
|
||||||
@@ -192,7 +192,7 @@ extension TunnelConfiguration {
|
|||||||
}
|
}
|
||||||
if let dnsString = attributes["dns"] {
|
if let dnsString = attributes["dns"] {
|
||||||
var dnsServers = [DNSServer]()
|
var dnsServers = [DNSServer]()
|
||||||
for dnsServerString in dnsString.splitToArray(trimmingCharacters: .whitespaces) {
|
for dnsServerString in dnsString.splitToArray(trimmingCharacters: .whitespacesAndNewlines) {
|
||||||
guard let dnsServer = DNSServer(from: dnsServerString) else {
|
guard let dnsServer = DNSServer(from: dnsServerString) else {
|
||||||
throw ParseError.interfaceHasInvalidDNS(dnsServerString)
|
throw ParseError.interfaceHasInvalidDNS(dnsServerString)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,12 +41,17 @@
|
|||||||
6B62E45F220A6FA900EF34A6 /* PrivateDataConfirmation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B62E45E220A6FA900EF34A6 /* PrivateDataConfirmation.swift */; };
|
6B62E45F220A6FA900EF34A6 /* PrivateDataConfirmation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B62E45E220A6FA900EF34A6 /* PrivateDataConfirmation.swift */; };
|
||||||
6B62E460220A6FA900EF34A6 /* PrivateDataConfirmation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B62E45E220A6FA900EF34A6 /* PrivateDataConfirmation.swift */; };
|
6B62E460220A6FA900EF34A6 /* PrivateDataConfirmation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B62E45E220A6FA900EF34A6 /* PrivateDataConfirmation.swift */; };
|
||||||
6B653B86220DE2960050E69C /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6FF4AC462120B9E0002C96EB /* NetworkExtension.framework */; };
|
6B653B86220DE2960050E69C /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6FF4AC462120B9E0002C96EB /* NetworkExtension.framework */; };
|
||||||
|
6B6956362211DA80001B618A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B6956352211DA80001B618A /* main.m */; };
|
||||||
6B707D8421F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */; };
|
6B707D8421F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */; };
|
||||||
6B707D8621F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */; };
|
6B707D8621F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */; };
|
||||||
6BD5C97B220D1AE200784E08 /* key.c in Sources */ = {isa = PBXBuildFile; fileRef = 6BD5C979220D1AE100784E08 /* key.c */; };
|
6BD5C97B220D1AE200784E08 /* key.c in Sources */ = {isa = PBXBuildFile; fileRef = 6BD5C979220D1AE100784E08 /* key.c */; };
|
||||||
6BD5C97C220D1AE200784E08 /* key.c in Sources */ = {isa = PBXBuildFile; fileRef = 6BD5C979220D1AE100784E08 /* key.c */; };
|
6BD5C97C220D1AE200784E08 /* key.c in Sources */ = {isa = PBXBuildFile; fileRef = 6BD5C979220D1AE100784E08 /* key.c */; };
|
||||||
6BD5C97D220D1AE200784E08 /* key.c in Sources */ = {isa = PBXBuildFile; fileRef = 6BD5C979220D1AE100784E08 /* key.c */; };
|
6BD5C97D220D1AE200784E08 /* key.c in Sources */ = {isa = PBXBuildFile; fileRef = 6BD5C979220D1AE100784E08 /* key.c */; };
|
||||||
6BD5C97E220D1AE200784E08 /* key.c in Sources */ = {isa = PBXBuildFile; fileRef = 6BD5C979220D1AE100784E08 /* key.c */; };
|
6BD5C97E220D1AE200784E08 /* key.c in Sources */ = {isa = PBXBuildFile; fileRef = 6BD5C979220D1AE100784E08 /* key.c */; };
|
||||||
|
6F0F44C9222D55BB00B0FF04 /* TextCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F0F44C8222D55BB00B0FF04 /* TextCell.swift */; };
|
||||||
|
6F0F44CB222D55FD00B0FF04 /* EditableTextCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F0F44CA222D55FD00B0FF04 /* EditableTextCell.swift */; };
|
||||||
|
6F1075642258AE9800D78929 /* DeleteTunnelsConfirmationAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F1075632258AE9800D78929 /* DeleteTunnelsConfirmationAlert.swift */; };
|
||||||
|
6F19D30422402B8700A126F2 /* ConfirmationAlertPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F19D30322402B8700A126F2 /* ConfirmationAlertPresenter.swift */; };
|
||||||
6F4DD16B21DA558800690EAE /* TunnelListRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4DD16A21DA558800690EAE /* TunnelListRow.swift */; };
|
6F4DD16B21DA558800690EAE /* TunnelListRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4DD16A21DA558800690EAE /* TunnelListRow.swift */; };
|
||||||
6F4DD16C21DA558F00690EAE /* NSTableView+Reuse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4DD16721DA552B00690EAE /* NSTableView+Reuse.swift */; };
|
6F4DD16C21DA558F00690EAE /* NSTableView+Reuse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4DD16721DA552B00690EAE /* NSTableView+Reuse.swift */; };
|
||||||
6F4DD16E21DBEA0700690EAE /* ManageTunnelsRootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4DD16D21DBEA0700690EAE /* ManageTunnelsRootViewController.swift */; };
|
6F4DD16E21DBEA0700690EAE /* ManageTunnelsRootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4DD16D21DBEA0700690EAE /* ManageTunnelsRootViewController.swift */; };
|
||||||
@@ -54,6 +59,7 @@
|
|||||||
6F5A2B4821AFF49A0081EDD8 /* FileManager+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5A2B4421AFDE020081EDD8 /* FileManager+Extension.swift */; };
|
6F5A2B4821AFF49A0081EDD8 /* FileManager+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5A2B4421AFDE020081EDD8 /* FileManager+Extension.swift */; };
|
||||||
6F5D0C1D218352EF000F85AD /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5D0C1C218352EF000F85AD /* PacketTunnelProvider.swift */; };
|
6F5D0C1D218352EF000F85AD /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5D0C1C218352EF000F85AD /* PacketTunnelProvider.swift */; };
|
||||||
6F5D0C22218352EF000F85AD /* WireGuardNetworkExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6F5D0C1A218352EF000F85AD /* WireGuardNetworkExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
6F5D0C22218352EF000F85AD /* WireGuardNetworkExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6F5D0C1A218352EF000F85AD /* WireGuardNetworkExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
|
6F5EA59B223E58A8002B380A /* ButtonRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5EA59A223E58A8002B380A /* ButtonRow.swift */; };
|
||||||
6F613D9B21DE33B8004B217A /* KeyValueRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F613D9A21DE33B8004B217A /* KeyValueRow.swift */; };
|
6F613D9B21DE33B8004B217A /* KeyValueRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F613D9A21DE33B8004B217A /* KeyValueRow.swift */; };
|
||||||
6F61F1E921B932F700483816 /* WireGuardAppError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F61F1E821B932F700483816 /* WireGuardAppError.swift */; };
|
6F61F1E921B932F700483816 /* WireGuardAppError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F61F1E821B932F700483816 /* WireGuardAppError.swift */; };
|
||||||
6F61F1EB21B937EF00483816 /* WireGuardResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F61F1EA21B937EF00483816 /* WireGuardResult.swift */; };
|
6F61F1EB21B937EF00483816 /* WireGuardResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F61F1EA21B937EF00483816 /* WireGuardResult.swift */; };
|
||||||
@@ -63,6 +69,9 @@
|
|||||||
6F6899A62180447E0012E523 /* x25519.c in Sources */ = {isa = PBXBuildFile; fileRef = 6F6899A52180447E0012E523 /* x25519.c */; };
|
6F6899A62180447E0012E523 /* x25519.c in Sources */ = {isa = PBXBuildFile; fileRef = 6F6899A52180447E0012E523 /* x25519.c */; };
|
||||||
6F6899A8218044FC0012E523 /* Curve25519.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F6899A7218044FC0012E523 /* Curve25519.swift */; };
|
6F6899A8218044FC0012E523 /* Curve25519.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F6899A7218044FC0012E523 /* Curve25519.swift */; };
|
||||||
6F693A562179E556008551C1 /* Endpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F693A552179E556008551C1 /* Endpoint.swift */; };
|
6F693A562179E556008551C1 /* Endpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F693A552179E556008551C1 /* Endpoint.swift */; };
|
||||||
|
6F70E20E221058E1008BDFB4 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6F70E20C221058DF008BDFB4 /* InfoPlist.strings */; };
|
||||||
|
6F70E20F221058E1008BDFB4 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6F70E20C221058DF008BDFB4 /* InfoPlist.strings */; };
|
||||||
|
6F70E23D22109E15008BDFB4 /* WireGuardLoginItemHelper.app in Embed Login Item Helper */ = {isa = PBXBuildFile; fileRef = 6F70E22922106A2D008BDFB4 /* WireGuardLoginItemHelper.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
6F7774E1217181B1006A79B3 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774DF217181B1006A79B3 /* MainViewController.swift */; };
|
6F7774E1217181B1006A79B3 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774DF217181B1006A79B3 /* MainViewController.swift */; };
|
||||||
6F7774E2217181B1006A79B3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E0217181B1006A79B3 /* AppDelegate.swift */; };
|
6F7774E2217181B1006A79B3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E0217181B1006A79B3 /* AppDelegate.swift */; };
|
||||||
6F7774E421718281006A79B3 /* TunnelsListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E321718281006A79B3 /* TunnelsListTableViewController.swift */; };
|
6F7774E421718281006A79B3 /* TunnelsListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E321718281006A79B3 /* TunnelsListTableViewController.swift */; };
|
||||||
@@ -73,13 +82,21 @@
|
|||||||
6F7F7E5F21C7D74B00527607 /* TunnelErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7F7E5E21C7D74B00527607 /* TunnelErrors.swift */; };
|
6F7F7E5F21C7D74B00527607 /* TunnelErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7F7E5E21C7D74B00527607 /* TunnelErrors.swift */; };
|
||||||
6F89E17A21EDEB0E00C97BB9 /* StatusItemController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F89E17921EDEB0E00C97BB9 /* StatusItemController.swift */; };
|
6F89E17A21EDEB0E00C97BB9 /* StatusItemController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F89E17921EDEB0E00C97BB9 /* StatusItemController.swift */; };
|
||||||
6F89E17C21F090CC00C97BB9 /* TunnelsTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F89E17B21F090CC00C97BB9 /* TunnelsTracker.swift */; };
|
6F89E17C21F090CC00C97BB9 /* TunnelsTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F89E17B21F090CC00C97BB9 /* TunnelsTracker.swift */; };
|
||||||
|
6F8F0D7122258153000E8335 /* ActivateOnDemandViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F8F0D7022258153000E8335 /* ActivateOnDemandViewModel.swift */; };
|
||||||
|
6F8F0D7222258153000E8335 /* ActivateOnDemandViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F8F0D7022258153000E8335 /* ActivateOnDemandViewModel.swift */; };
|
||||||
|
6F8F0D7422267AD2000E8335 /* ChevronCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F8F0D7322267AD2000E8335 /* ChevronCell.swift */; };
|
||||||
|
6F8F0D7722267C57000E8335 /* SSIDOptionEditTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F8F0D7622267C57000E8335 /* SSIDOptionEditTableViewController.swift */; };
|
||||||
|
6F907C9C224663A2003CED21 /* LogViewHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F907C9B224663A2003CED21 /* LogViewHelper.swift */; };
|
||||||
|
6F907C9D224663A2003CED21 /* LogViewHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F907C9B224663A2003CED21 /* LogViewHelper.swift */; };
|
||||||
6F919EC3218A2AE90023B400 /* ErrorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F919EC2218A2AE90023B400 /* ErrorPresenter.swift */; };
|
6F919EC3218A2AE90023B400 /* ErrorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F919EC2218A2AE90023B400 /* ErrorPresenter.swift */; };
|
||||||
6F919ED9218C65C50023B400 /* wireguard_doc_logo_22x29.png in Resources */ = {isa = PBXBuildFile; fileRef = 6F919ED5218C65C50023B400 /* wireguard_doc_logo_22x29.png */; };
|
6F919ED9218C65C50023B400 /* wireguard_doc_logo_22x29.png in Resources */ = {isa = PBXBuildFile; fileRef = 6F919ED5218C65C50023B400 /* wireguard_doc_logo_22x29.png */; };
|
||||||
6F919EDA218C65C50023B400 /* wireguard_doc_logo_44x58.png in Resources */ = {isa = PBXBuildFile; fileRef = 6F919ED6218C65C50023B400 /* wireguard_doc_logo_44x58.png */; };
|
6F919EDA218C65C50023B400 /* wireguard_doc_logo_44x58.png in Resources */ = {isa = PBXBuildFile; fileRef = 6F919ED6218C65C50023B400 /* wireguard_doc_logo_44x58.png */; };
|
||||||
6F919EDB218C65C50023B400 /* wireguard_doc_logo_64x64.png in Resources */ = {isa = PBXBuildFile; fileRef = 6F919ED7218C65C50023B400 /* wireguard_doc_logo_64x64.png */; };
|
6F919EDB218C65C50023B400 /* wireguard_doc_logo_64x64.png in Resources */ = {isa = PBXBuildFile; fileRef = 6F919ED7218C65C50023B400 /* wireguard_doc_logo_64x64.png */; };
|
||||||
6F919EDC218C65C50023B400 /* wireguard_doc_logo_320x320.png in Resources */ = {isa = PBXBuildFile; fileRef = 6F919ED8218C65C50023B400 /* wireguard_doc_logo_320x320.png */; };
|
6F919EDC218C65C50023B400 /* wireguard_doc_logo_320x320.png in Resources */ = {isa = PBXBuildFile; fileRef = 6F919ED8218C65C50023B400 /* wireguard_doc_logo_320x320.png */; };
|
||||||
6F9B582921E8D6D100544D02 /* PopupRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F9B582721E8CD4300544D02 /* PopupRow.swift */; };
|
6F9B8A8E223398610041B9C4 /* SSIDOptionDetailTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F9B8A8D223398610041B9C4 /* SSIDOptionDetailTableViewController.swift */; };
|
||||||
|
6FADE96C2254B8C300B838A4 /* UnusableTunnelDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FADE96A2254A6C200B838A4 /* UnusableTunnelDetailViewController.swift */; };
|
||||||
6FB1017921C57DE600766195 /* MockTunnels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FB1017821C57DE600766195 /* MockTunnels.swift */; };
|
6FB1017921C57DE600766195 /* MockTunnels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FB1017821C57DE600766195 /* MockTunnels.swift */; };
|
||||||
|
6FB17946222FD5960018AE71 /* OnDemandWiFiControls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FB17945222FD5960018AE71 /* OnDemandWiFiControls.swift */; };
|
||||||
6FB1BD6021D2607A00A991BF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FB1BD5F21D2607A00A991BF /* AppDelegate.swift */; };
|
6FB1BD6021D2607A00A991BF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FB1BD5F21D2607A00A991BF /* AppDelegate.swift */; };
|
||||||
6FB1BD6221D2607E00A991BF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6FB1BD6121D2607E00A991BF /* Assets.xcassets */; };
|
6FB1BD6221D2607E00A991BF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6FB1BD6121D2607E00A991BF /* Assets.xcassets */; };
|
||||||
6FB1BD9921D4BFE700A991BF /* WireGuardNetworkExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6FB1BD9121D4BFE600A991BF /* WireGuardNetworkExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
6FB1BD9921D4BFE700A991BF /* WireGuardNetworkExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6FB1BD9121D4BFE600A991BF /* WireGuardNetworkExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
@@ -117,7 +134,7 @@
|
|||||||
6FB1BDCA21D50F1700A991BF /* x25519.c in Sources */ = {isa = PBXBuildFile; fileRef = 6F6899A52180447E0012E523 /* x25519.c */; };
|
6FB1BDCA21D50F1700A991BF /* x25519.c in Sources */ = {isa = PBXBuildFile; fileRef = 6F6899A52180447E0012E523 /* x25519.c */; };
|
||||||
6FB1BDCB21D50F1700A991BF /* Curve25519.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F6899A7218044FC0012E523 /* Curve25519.swift */; };
|
6FB1BDCB21D50F1700A991BF /* Curve25519.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F6899A7218044FC0012E523 /* Curve25519.swift */; };
|
||||||
6FB1BDCC21D50F5300A991BF /* TunnelsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774EE21722D97006A79B3 /* TunnelsManager.swift */; };
|
6FB1BDCC21D50F5300A991BF /* TunnelsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774EE21722D97006A79B3 /* TunnelsManager.swift */; };
|
||||||
6FB1BDCD21D50F5300A991BF /* ActivateOnDemandSetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5DA32197085D0001E2F7 /* ActivateOnDemandSetting.swift */; };
|
6FB1BDCD21D50F5300A991BF /* ActivateOnDemandOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5DA32197085D0001E2F7 /* ActivateOnDemandOption.swift */; };
|
||||||
6FB1BDCE21D50F5300A991BF /* TunnelStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F4541A821C451D100994C13 /* TunnelStatus.swift */; };
|
6FB1BDCE21D50F5300A991BF /* TunnelStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F4541A821C451D100994C13 /* TunnelStatus.swift */; };
|
||||||
6FB1BDD021D50F5300A991BF /* TunnelErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7F7E5E21C7D74B00527607 /* TunnelErrors.swift */; };
|
6FB1BDD021D50F5300A991BF /* TunnelErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7F7E5E21C7D74B00527607 /* TunnelErrors.swift */; };
|
||||||
6FB1BDD121D50F5300A991BF /* ZipImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE254FA219C10800028284D /* ZipImporter.swift */; };
|
6FB1BDD121D50F5300A991BF /* ZipImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE254FA219C10800028284D /* ZipImporter.swift */; };
|
||||||
@@ -138,11 +155,14 @@
|
|||||||
6FBA104021D6B7040051C35F /* ErrorPresenterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FBA103A21D6B4280051C35F /* ErrorPresenterProtocol.swift */; };
|
6FBA104021D6B7040051C35F /* ErrorPresenterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FBA103A21D6B4280051C35F /* ErrorPresenterProtocol.swift */; };
|
||||||
6FBA104321D6BC250051C35F /* ErrorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FBA104121D6BC210051C35F /* ErrorPresenter.swift */; };
|
6FBA104321D6BC250051C35F /* ErrorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FBA104121D6BC210051C35F /* ErrorPresenter.swift */; };
|
||||||
6FBA104621D7EBFA0051C35F /* TunnelsListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FBA104521D7EBFA0051C35F /* TunnelsListTableViewController.swift */; };
|
6FBA104621D7EBFA0051C35F /* TunnelsListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FBA104521D7EBFA0051C35F /* TunnelsListTableViewController.swift */; };
|
||||||
6FCD99AA21E0E14700BA4C82 /* NoTunnelsDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FCD99A821E0E0C700BA4C82 /* NoTunnelsDetailViewController.swift */; };
|
6FCD99AA21E0E14700BA4C82 /* ButtonedDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FCD99A821E0E0C700BA4C82 /* ButtonedDetailViewController.swift */; };
|
||||||
6FCD99AF21E0EA1700BA4C82 /* ImportPanelPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FCD99AE21E0EA1700BA4C82 /* ImportPanelPresenter.swift */; };
|
6FCD99AF21E0EA1700BA4C82 /* ImportPanelPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FCD99AE21E0EA1700BA4C82 /* ImportPanelPresenter.swift */; };
|
||||||
6FCD99B121E0EDA900BA4C82 /* TunnelEditViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FCD99B021E0EDA900BA4C82 /* TunnelEditViewController.swift */; };
|
6FCD99B121E0EDA900BA4C82 /* TunnelEditViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FCD99B021E0EDA900BA4C82 /* TunnelEditViewController.swift */; };
|
||||||
6FDB3C3B21DCF47400A0C0BF /* TunnelDetailTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDB3C3A21DCF47400A0C0BF /* TunnelDetailTableViewController.swift */; };
|
6FDB3C3B21DCF47400A0C0BF /* TunnelDetailTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDB3C3A21DCF47400A0C0BF /* TunnelDetailTableViewController.swift */; };
|
||||||
6FDB3C3C21DCF6BB00A0C0BF /* TunnelViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F628C3C217F09E9003482A3 /* TunnelViewModel.swift */; };
|
6FDB3C3C21DCF6BB00A0C0BF /* TunnelViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F628C3C217F09E9003482A3 /* TunnelViewModel.swift */; };
|
||||||
|
6FDB6D13224A15BF00EE4BC3 /* LogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDB6D12224A15BE00EE4BC3 /* LogViewController.swift */; };
|
||||||
|
6FDB6D15224CB2CE00EE4BC3 /* LogViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDB6D14224CB2CE00EE4BC3 /* LogViewCell.swift */; };
|
||||||
|
6FDB6D18224CC05A00EE4BC3 /* LogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDB6D16224CC04E00EE4BC3 /* LogViewController.swift */; };
|
||||||
6FDEF7E421846C1A00D8FBF6 /* libwg-go.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6FDEF7E321846C1A00D8FBF6 /* libwg-go.a */; };
|
6FDEF7E421846C1A00D8FBF6 /* libwg-go.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6FDEF7E321846C1A00D8FBF6 /* libwg-go.a */; };
|
||||||
6FDEF7E62185EFB200D8FBF6 /* QRScanViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDEF7E52185EFAF00D8FBF6 /* QRScanViewController.swift */; };
|
6FDEF7E62185EFB200D8FBF6 /* QRScanViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDEF7E52185EFAF00D8FBF6 /* QRScanViewController.swift */; };
|
||||||
6FDEF7FB21863B6100D8FBF6 /* unzip.c in Sources */ = {isa = PBXBuildFile; fileRef = 6FDEF7F621863B6100D8FBF6 /* unzip.c */; };
|
6FDEF7FB21863B6100D8FBF6 /* unzip.c in Sources */ = {isa = PBXBuildFile; fileRef = 6FDEF7F621863B6100D8FBF6 /* unzip.c */; };
|
||||||
@@ -170,7 +190,7 @@
|
|||||||
6FFA5D952194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5D942194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift */; };
|
6FFA5D952194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5D942194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift */; };
|
||||||
6FFA5D96219446380001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5D942194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift */; };
|
6FFA5D96219446380001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5D942194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift */; };
|
||||||
6FFA5DA021958ECC0001E2F7 /* ErrorNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5D9F21958ECC0001E2F7 /* ErrorNotifier.swift */; };
|
6FFA5DA021958ECC0001E2F7 /* ErrorNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5D9F21958ECC0001E2F7 /* ErrorNotifier.swift */; };
|
||||||
6FFA5DA42197085D0001E2F7 /* ActivateOnDemandSetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5DA32197085D0001E2F7 /* ActivateOnDemandSetting.swift */; };
|
6FFA5DA42197085D0001E2F7 /* ActivateOnDemandOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5DA32197085D0001E2F7 /* ActivateOnDemandOption.swift */; };
|
||||||
6FFACD2021E4D8D500E9A2A5 /* ParseError+WireGuardAppError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFACD1E21E4D89600E9A2A5 /* ParseError+WireGuardAppError.swift */; };
|
6FFACD2021E4D8D500E9A2A5 /* ParseError+WireGuardAppError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFACD1E21E4D89600E9A2A5 /* ParseError+WireGuardAppError.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
@@ -182,6 +202,13 @@
|
|||||||
remoteGlobalIDString = 6F5D0C19218352EF000F85AD;
|
remoteGlobalIDString = 6F5D0C19218352EF000F85AD;
|
||||||
remoteInfo = WireGuardNetworkExtension;
|
remoteInfo = WireGuardNetworkExtension;
|
||||||
};
|
};
|
||||||
|
6F70E23A22109DD3008BDFB4 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 6FF4AC0C211EC46F002C96EB /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 6F70E22822106A2D008BDFB4;
|
||||||
|
remoteInfo = WireGuardmacOSLoginItemHelper;
|
||||||
|
};
|
||||||
6FB1BD9721D4BFE700A991BF /* PBXContainerItemProxy */ = {
|
6FB1BD9721D4BFE700A991BF /* PBXContainerItemProxy */ = {
|
||||||
isa = PBXContainerItemProxy;
|
isa = PBXContainerItemProxy;
|
||||||
containerPortal = 6FF4AC0C211EC46F002C96EB /* Project object */;
|
containerPortal = 6FF4AC0C211EC46F002C96EB /* Project object */;
|
||||||
@@ -217,6 +244,17 @@
|
|||||||
name = "Embed App Extensions";
|
name = "Embed App Extensions";
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
6F70E23C22109DE5008BDFB4 /* Embed Login Item Helper */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
dstPath = Contents/Library/LoginItems;
|
||||||
|
dstSubfolderSpec = 1;
|
||||||
|
files = (
|
||||||
|
6F70E23D22109E15008BDFB4 /* WireGuardLoginItemHelper.app in Embed Login Item Helper */,
|
||||||
|
);
|
||||||
|
name = "Embed Login Item Helper";
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
6FB1BD9D21D4BFE700A991BF /* Embed App Extensions */ = {
|
6FB1BD9D21D4BFE700A991BF /* Embed App Extensions */ = {
|
||||||
isa = PBXCopyFilesBuildPhase;
|
isa = PBXCopyFilesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@@ -252,9 +290,14 @@
|
|||||||
6B586C52220CBA6D00427C51 /* Data+KeyEncoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+KeyEncoding.swift"; sourceTree = "<group>"; };
|
6B586C52220CBA6D00427C51 /* Data+KeyEncoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+KeyEncoding.swift"; sourceTree = "<group>"; };
|
||||||
6B5C5E26220A48D30024272E /* Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = "<group>"; };
|
6B5C5E26220A48D30024272E /* Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = "<group>"; };
|
||||||
6B62E45E220A6FA900EF34A6 /* PrivateDataConfirmation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateDataConfirmation.swift; sourceTree = "<group>"; };
|
6B62E45E220A6FA900EF34A6 /* PrivateDataConfirmation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateDataConfirmation.swift; sourceTree = "<group>"; };
|
||||||
|
6B6956352211DA80001B618A /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||||
6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelConfiguration+UapiConfig.swift"; sourceTree = "<group>"; };
|
6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelConfiguration+UapiConfig.swift"; sourceTree = "<group>"; };
|
||||||
6BD5C979220D1AE100784E08 /* key.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = key.c; sourceTree = "<group>"; };
|
6BD5C979220D1AE100784E08 /* key.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = key.c; sourceTree = "<group>"; };
|
||||||
6BD5C97A220D1AE200784E08 /* key.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = key.h; sourceTree = "<group>"; };
|
6BD5C97A220D1AE200784E08 /* key.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = key.h; sourceTree = "<group>"; };
|
||||||
|
6F0F44C8222D55BB00B0FF04 /* TextCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextCell.swift; sourceTree = "<group>"; };
|
||||||
|
6F0F44CA222D55FD00B0FF04 /* EditableTextCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditableTextCell.swift; sourceTree = "<group>"; };
|
||||||
|
6F1075632258AE9800D78929 /* DeleteTunnelsConfirmationAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteTunnelsConfirmationAlert.swift; sourceTree = "<group>"; };
|
||||||
|
6F19D30322402B8700A126F2 /* ConfirmationAlertPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationAlertPresenter.swift; sourceTree = "<group>"; };
|
||||||
6F4DD16721DA552B00690EAE /* NSTableView+Reuse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTableView+Reuse.swift"; sourceTree = "<group>"; };
|
6F4DD16721DA552B00690EAE /* NSTableView+Reuse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTableView+Reuse.swift"; sourceTree = "<group>"; };
|
||||||
6F4DD16A21DA558800690EAE /* TunnelListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelListRow.swift; sourceTree = "<group>"; };
|
6F4DD16A21DA558800690EAE /* TunnelListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelListRow.swift; sourceTree = "<group>"; };
|
||||||
6F4DD16D21DBEA0700690EAE /* ManageTunnelsRootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManageTunnelsRootViewController.swift; sourceTree = "<group>"; };
|
6F4DD16D21DBEA0700690EAE /* ManageTunnelsRootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManageTunnelsRootViewController.swift; sourceTree = "<group>"; };
|
||||||
@@ -266,6 +309,7 @@
|
|||||||
6F5D0C1F218352EF000F85AD /* WireGuardNetworkExtension_iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WireGuardNetworkExtension_iOS.entitlements; sourceTree = "<group>"; };
|
6F5D0C1F218352EF000F85AD /* WireGuardNetworkExtension_iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WireGuardNetworkExtension_iOS.entitlements; sourceTree = "<group>"; };
|
||||||
6F5D0C3421839E37000F85AD /* WireGuardNetworkExtension-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "WireGuardNetworkExtension-Bridging-Header.h"; sourceTree = "<group>"; };
|
6F5D0C3421839E37000F85AD /* WireGuardNetworkExtension-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "WireGuardNetworkExtension-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
6F5D0C472183C6A3000F85AD /* PacketTunnelSettingsGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelSettingsGenerator.swift; sourceTree = "<group>"; };
|
6F5D0C472183C6A3000F85AD /* PacketTunnelSettingsGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelSettingsGenerator.swift; sourceTree = "<group>"; };
|
||||||
|
6F5EA59A223E58A8002B380A /* ButtonRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonRow.swift; sourceTree = "<group>"; };
|
||||||
6F613D9A21DE33B8004B217A /* KeyValueRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyValueRow.swift; sourceTree = "<group>"; };
|
6F613D9A21DE33B8004B217A /* KeyValueRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyValueRow.swift; sourceTree = "<group>"; };
|
||||||
6F61F1E821B932F700483816 /* WireGuardAppError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireGuardAppError.swift; sourceTree = "<group>"; };
|
6F61F1E821B932F700483816 /* WireGuardAppError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireGuardAppError.swift; sourceTree = "<group>"; };
|
||||||
6F61F1EA21B937EF00483816 /* WireGuardResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireGuardResult.swift; sourceTree = "<group>"; };
|
6F61F1EA21B937EF00483816 /* WireGuardResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireGuardResult.swift; sourceTree = "<group>"; };
|
||||||
@@ -277,6 +321,10 @@
|
|||||||
6F6899A52180447E0012E523 /* x25519.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = x25519.c; sourceTree = "<group>"; };
|
6F6899A52180447E0012E523 /* x25519.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = x25519.c; sourceTree = "<group>"; };
|
||||||
6F6899A7218044FC0012E523 /* Curve25519.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Curve25519.swift; sourceTree = "<group>"; };
|
6F6899A7218044FC0012E523 /* Curve25519.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Curve25519.swift; sourceTree = "<group>"; };
|
||||||
6F693A552179E556008551C1 /* Endpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Endpoint.swift; sourceTree = "<group>"; };
|
6F693A552179E556008551C1 /* Endpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Endpoint.swift; sourceTree = "<group>"; };
|
||||||
|
6F70E20D221058DF008BDFB4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = WireGuard/Base.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||||
|
6F70E22922106A2D008BDFB4 /* WireGuardLoginItemHelper.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WireGuardLoginItemHelper.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
6F70E23222106A31008BDFB4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
6F70E23922109BEF008BDFB4 /* LoginItemHelper.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = LoginItemHelper.entitlements; sourceTree = "<group>"; };
|
||||||
6F7774DF217181B1006A79B3 /* MainViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = "<group>"; };
|
6F7774DF217181B1006A79B3 /* MainViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = "<group>"; };
|
||||||
6F7774E0217181B1006A79B3 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
6F7774E0217181B1006A79B3 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
6F7774E321718281006A79B3 /* TunnelsListTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelsListTableViewController.swift; sourceTree = "<group>"; };
|
6F7774E321718281006A79B3 /* TunnelsListTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelsListTableViewController.swift; sourceTree = "<group>"; };
|
||||||
@@ -287,13 +335,19 @@
|
|||||||
6F7F7E5E21C7D74B00527607 /* TunnelErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelErrors.swift; sourceTree = "<group>"; };
|
6F7F7E5E21C7D74B00527607 /* TunnelErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelErrors.swift; sourceTree = "<group>"; };
|
||||||
6F89E17921EDEB0E00C97BB9 /* StatusItemController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusItemController.swift; sourceTree = "<group>"; };
|
6F89E17921EDEB0E00C97BB9 /* StatusItemController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusItemController.swift; sourceTree = "<group>"; };
|
||||||
6F89E17B21F090CC00C97BB9 /* TunnelsTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelsTracker.swift; sourceTree = "<group>"; };
|
6F89E17B21F090CC00C97BB9 /* TunnelsTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelsTracker.swift; sourceTree = "<group>"; };
|
||||||
|
6F8F0D7022258153000E8335 /* ActivateOnDemandViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivateOnDemandViewModel.swift; sourceTree = "<group>"; };
|
||||||
|
6F8F0D7322267AD2000E8335 /* ChevronCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChevronCell.swift; sourceTree = "<group>"; };
|
||||||
|
6F8F0D7622267C57000E8335 /* SSIDOptionEditTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSIDOptionEditTableViewController.swift; sourceTree = "<group>"; };
|
||||||
|
6F907C9B224663A2003CED21 /* LogViewHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewHelper.swift; sourceTree = "<group>"; };
|
||||||
6F919EC2218A2AE90023B400 /* ErrorPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorPresenter.swift; sourceTree = "<group>"; };
|
6F919EC2218A2AE90023B400 /* ErrorPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorPresenter.swift; sourceTree = "<group>"; };
|
||||||
6F919ED5218C65C50023B400 /* wireguard_doc_logo_22x29.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = wireguard_doc_logo_22x29.png; sourceTree = "<group>"; };
|
6F919ED5218C65C50023B400 /* wireguard_doc_logo_22x29.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = wireguard_doc_logo_22x29.png; sourceTree = "<group>"; };
|
||||||
6F919ED6218C65C50023B400 /* wireguard_doc_logo_44x58.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = wireguard_doc_logo_44x58.png; sourceTree = "<group>"; };
|
6F919ED6218C65C50023B400 /* wireguard_doc_logo_44x58.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = wireguard_doc_logo_44x58.png; sourceTree = "<group>"; };
|
||||||
6F919ED7218C65C50023B400 /* wireguard_doc_logo_64x64.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = wireguard_doc_logo_64x64.png; sourceTree = "<group>"; };
|
6F919ED7218C65C50023B400 /* wireguard_doc_logo_64x64.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = wireguard_doc_logo_64x64.png; sourceTree = "<group>"; };
|
||||||
6F919ED8218C65C50023B400 /* wireguard_doc_logo_320x320.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = wireguard_doc_logo_320x320.png; sourceTree = "<group>"; };
|
6F919ED8218C65C50023B400 /* wireguard_doc_logo_320x320.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = wireguard_doc_logo_320x320.png; sourceTree = "<group>"; };
|
||||||
6F9B582721E8CD4300544D02 /* PopupRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupRow.swift; sourceTree = "<group>"; };
|
6F9B8A8D223398610041B9C4 /* SSIDOptionDetailTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSIDOptionDetailTableViewController.swift; sourceTree = "<group>"; };
|
||||||
|
6FADE96A2254A6C200B838A4 /* UnusableTunnelDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnusableTunnelDetailViewController.swift; sourceTree = "<group>"; };
|
||||||
6FB1017821C57DE600766195 /* MockTunnels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTunnels.swift; sourceTree = "<group>"; };
|
6FB1017821C57DE600766195 /* MockTunnels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTunnels.swift; sourceTree = "<group>"; };
|
||||||
|
6FB17945222FD5960018AE71 /* OnDemandWiFiControls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnDemandWiFiControls.swift; sourceTree = "<group>"; };
|
||||||
6FB1BD5D21D2607A00A991BF /* WireGuard.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WireGuard.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
6FB1BD5D21D2607A00A991BF /* WireGuard.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WireGuard.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
6FB1BD5F21D2607A00A991BF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
6FB1BD5F21D2607A00A991BF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
6FB1BD6121D2607E00A991BF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
6FB1BD6121D2607E00A991BF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
@@ -309,10 +363,13 @@
|
|||||||
6FBA103D21D6B6D70051C35F /* TunnelImporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelImporter.swift; sourceTree = "<group>"; };
|
6FBA103D21D6B6D70051C35F /* TunnelImporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelImporter.swift; sourceTree = "<group>"; };
|
||||||
6FBA104121D6BC210051C35F /* ErrorPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorPresenter.swift; sourceTree = "<group>"; };
|
6FBA104121D6BC210051C35F /* ErrorPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorPresenter.swift; sourceTree = "<group>"; };
|
||||||
6FBA104521D7EBFA0051C35F /* TunnelsListTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelsListTableViewController.swift; sourceTree = "<group>"; };
|
6FBA104521D7EBFA0051C35F /* TunnelsListTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelsListTableViewController.swift; sourceTree = "<group>"; };
|
||||||
6FCD99A821E0E0C700BA4C82 /* NoTunnelsDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoTunnelsDetailViewController.swift; sourceTree = "<group>"; };
|
6FCD99A821E0E0C700BA4C82 /* ButtonedDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonedDetailViewController.swift; sourceTree = "<group>"; };
|
||||||
6FCD99AE21E0EA1700BA4C82 /* ImportPanelPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportPanelPresenter.swift; sourceTree = "<group>"; };
|
6FCD99AE21E0EA1700BA4C82 /* ImportPanelPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportPanelPresenter.swift; sourceTree = "<group>"; };
|
||||||
6FCD99B021E0EDA900BA4C82 /* TunnelEditViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelEditViewController.swift; sourceTree = "<group>"; };
|
6FCD99B021E0EDA900BA4C82 /* TunnelEditViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelEditViewController.swift; sourceTree = "<group>"; };
|
||||||
6FDB3C3A21DCF47400A0C0BF /* TunnelDetailTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelDetailTableViewController.swift; sourceTree = "<group>"; };
|
6FDB3C3A21DCF47400A0C0BF /* TunnelDetailTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelDetailTableViewController.swift; sourceTree = "<group>"; };
|
||||||
|
6FDB6D12224A15BE00EE4BC3 /* LogViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogViewController.swift; sourceTree = "<group>"; };
|
||||||
|
6FDB6D14224CB2CE00EE4BC3 /* LogViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewCell.swift; sourceTree = "<group>"; };
|
||||||
|
6FDB6D16224CC04E00EE4BC3 /* LogViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewController.swift; sourceTree = "<group>"; };
|
||||||
6FDEF7E321846C1A00D8FBF6 /* libwg-go.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = "libwg-go.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
6FDEF7E321846C1A00D8FBF6 /* libwg-go.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = "libwg-go.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
6FDEF7E52185EFAF00D8FBF6 /* QRScanViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRScanViewController.swift; sourceTree = "<group>"; };
|
6FDEF7E52185EFAF00D8FBF6 /* QRScanViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRScanViewController.swift; sourceTree = "<group>"; };
|
||||||
6FDEF7F621863B6100D8FBF6 /* unzip.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = unzip.c; sourceTree = "<group>"; };
|
6FDEF7F621863B6100D8FBF6 /* unzip.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = unzip.c; sourceTree = "<group>"; };
|
||||||
@@ -340,7 +397,7 @@
|
|||||||
6FF4AC482120B9E0002C96EB /* WireGuard.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WireGuard.entitlements; sourceTree = "<group>"; };
|
6FF4AC482120B9E0002C96EB /* WireGuard.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WireGuard.entitlements; sourceTree = "<group>"; };
|
||||||
6FFA5D942194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NETunnelProviderProtocol+Extension.swift"; sourceTree = "<group>"; };
|
6FFA5D942194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NETunnelProviderProtocol+Extension.swift"; sourceTree = "<group>"; };
|
||||||
6FFA5D9F21958ECC0001E2F7 /* ErrorNotifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorNotifier.swift; sourceTree = "<group>"; };
|
6FFA5D9F21958ECC0001E2F7 /* ErrorNotifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorNotifier.swift; sourceTree = "<group>"; };
|
||||||
6FFA5DA32197085D0001E2F7 /* ActivateOnDemandSetting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivateOnDemandSetting.swift; sourceTree = "<group>"; };
|
6FFA5DA32197085D0001E2F7 /* ActivateOnDemandOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivateOnDemandOption.swift; sourceTree = "<group>"; };
|
||||||
6FFACD1E21E4D89600E9A2A5 /* ParseError+WireGuardAppError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParseError+WireGuardAppError.swift"; sourceTree = "<group>"; };
|
6FFACD1E21E4D89600E9A2A5 /* ParseError+WireGuardAppError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParseError+WireGuardAppError.swift"; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
@@ -392,6 +449,9 @@
|
|||||||
5F4541A521C4449E00994C13 /* ButtonCell.swift */,
|
5F4541A521C4449E00994C13 /* ButtonCell.swift */,
|
||||||
5F45419121C2D55800994C13 /* CheckmarkCell.swift */,
|
5F45419121C2D55800994C13 /* CheckmarkCell.swift */,
|
||||||
5F4541A121C2D6DF00994C13 /* BorderedTextButton.swift */,
|
5F4541A121C2D6DF00994C13 /* BorderedTextButton.swift */,
|
||||||
|
6F8F0D7322267AD2000E8335 /* ChevronCell.swift */,
|
||||||
|
6F0F44C8222D55BB00B0FF04 /* TextCell.swift */,
|
||||||
|
6F0F44CA222D55FD00B0FF04 /* EditableTextCell.swift */,
|
||||||
);
|
);
|
||||||
path = View;
|
path = View;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -405,6 +465,9 @@
|
|||||||
6F628C40217F47DB003482A3 /* TunnelDetailTableViewController.swift */,
|
6F628C40217F47DB003482A3 /* TunnelDetailTableViewController.swift */,
|
||||||
6FDEF805218725D200D8FBF6 /* SettingsTableViewController.swift */,
|
6FDEF805218725D200D8FBF6 /* SettingsTableViewController.swift */,
|
||||||
6F7774DF217181B1006A79B3 /* MainViewController.swift */,
|
6F7774DF217181B1006A79B3 /* MainViewController.swift */,
|
||||||
|
6F8F0D7622267C57000E8335 /* SSIDOptionEditTableViewController.swift */,
|
||||||
|
6F9B8A8D223398610041B9C4 /* SSIDOptionDetailTableViewController.swift */,
|
||||||
|
6FDB6D16224CC04E00EE4BC3 /* LogViewController.swift */,
|
||||||
);
|
);
|
||||||
path = ViewController;
|
path = ViewController;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -418,8 +481,11 @@
|
|||||||
6F613D9A21DE33B8004B217A /* KeyValueRow.swift */,
|
6F613D9A21DE33B8004B217A /* KeyValueRow.swift */,
|
||||||
5F52D0BA21E3781B00283CEA /* ConfTextView.swift */,
|
5F52D0BA21E3781B00283CEA /* ConfTextView.swift */,
|
||||||
5F52D0BC21E3785C00283CEA /* ConfTextStorage.swift */,
|
5F52D0BC21E3785C00283CEA /* ConfTextStorage.swift */,
|
||||||
6F9B582721E8CD4300544D02 /* PopupRow.swift */,
|
|
||||||
6FE3661C21F64F6B00F78C7D /* ConfTextColorTheme.swift */,
|
6FE3661C21F64F6B00F78C7D /* ConfTextColorTheme.swift */,
|
||||||
|
6F5EA59A223E58A8002B380A /* ButtonRow.swift */,
|
||||||
|
6FB17945222FD5960018AE71 /* OnDemandWiFiControls.swift */,
|
||||||
|
6FDB6D14224CB2CE00EE4BC3 /* LogViewCell.swift */,
|
||||||
|
6F1075632258AE9800D78929 /* DeleteTunnelsConfirmationAlert.swift */,
|
||||||
);
|
);
|
||||||
path = View;
|
path = View;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -460,15 +526,27 @@
|
|||||||
path = Crypto;
|
path = Crypto;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
6F70E22A22106A2D008BDFB4 /* LoginItemHelper */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
6F70E23922109BEF008BDFB4 /* LoginItemHelper.entitlements */,
|
||||||
|
6F70E23222106A31008BDFB4 /* Info.plist */,
|
||||||
|
6B6956352211DA80001B618A /* main.m */,
|
||||||
|
);
|
||||||
|
path = LoginItemHelper;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
6F7774DD217181B1006A79B3 /* UI */ = {
|
6F7774DD217181B1006A79B3 /* UI */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
6FB1BD5E21D2607A00A991BF /* macOS */,
|
6FB1BD5E21D2607A00A991BF /* macOS */,
|
||||||
6F7774DE217181B1006A79B3 /* iOS */,
|
6F7774DE217181B1006A79B3 /* iOS */,
|
||||||
6F628C3C217F09E9003482A3 /* TunnelViewModel.swift */,
|
6F628C3C217F09E9003482A3 /* TunnelViewModel.swift */,
|
||||||
|
6F8F0D7022258153000E8335 /* ActivateOnDemandViewModel.swift */,
|
||||||
6FBA103D21D6B6D70051C35F /* TunnelImporter.swift */,
|
6FBA103D21D6B6D70051C35F /* TunnelImporter.swift */,
|
||||||
6FBA103A21D6B4280051C35F /* ErrorPresenterProtocol.swift */,
|
6FBA103A21D6B4280051C35F /* ErrorPresenterProtocol.swift */,
|
||||||
6B62E45E220A6FA900EF34A6 /* PrivateDataConfirmation.swift */,
|
6B62E45E220A6FA900EF34A6 /* PrivateDataConfirmation.swift */,
|
||||||
|
6F907C9B224663A2003CED21 /* LogViewHelper.swift */,
|
||||||
);
|
);
|
||||||
path = UI;
|
path = UI;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -478,10 +556,11 @@
|
|||||||
children = (
|
children = (
|
||||||
6FF4AC1E211EC472002C96EB /* Assets.xcassets */,
|
6FF4AC1E211EC472002C96EB /* Assets.xcassets */,
|
||||||
6FF4AC20211EC472002C96EB /* LaunchScreen.storyboard */,
|
6FF4AC20211EC472002C96EB /* LaunchScreen.storyboard */,
|
||||||
5F4541AC21C4720B00994C13 /* ViewController */,
|
|
||||||
5F4541A721C44F5B00994C13 /* View */,
|
5F4541A721C44F5B00994C13 /* View */,
|
||||||
|
5F4541AC21C4720B00994C13 /* ViewController */,
|
||||||
6F7774E0217181B1006A79B3 /* AppDelegate.swift */,
|
6F7774E0217181B1006A79B3 /* AppDelegate.swift */,
|
||||||
6F919EC2218A2AE90023B400 /* ErrorPresenter.swift */,
|
6F919EC2218A2AE90023B400 /* ErrorPresenter.swift */,
|
||||||
|
6F19D30322402B8700A126F2 /* ConfirmationAlertPresenter.swift */,
|
||||||
5F45417C21C1B23600994C13 /* UITableViewCell+Reuse.swift */,
|
5F45417C21C1B23600994C13 /* UITableViewCell+Reuse.swift */,
|
||||||
6FF4AC23211EC472002C96EB /* Info.plist */,
|
6FF4AC23211EC472002C96EB /* Info.plist */,
|
||||||
6FF4AC482120B9E0002C96EB /* WireGuard.entitlements */,
|
6FF4AC482120B9E0002C96EB /* WireGuard.entitlements */,
|
||||||
@@ -513,7 +592,7 @@
|
|||||||
children = (
|
children = (
|
||||||
6F7774EE21722D97006A79B3 /* TunnelsManager.swift */,
|
6F7774EE21722D97006A79B3 /* TunnelsManager.swift */,
|
||||||
6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */,
|
6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */,
|
||||||
6FFA5DA32197085D0001E2F7 /* ActivateOnDemandSetting.swift */,
|
6FFA5DA32197085D0001E2F7 /* ActivateOnDemandOption.swift */,
|
||||||
5F4541A821C451D100994C13 /* TunnelStatus.swift */,
|
5F4541A821C451D100994C13 /* TunnelStatus.swift */,
|
||||||
6FB1017821C57DE600766195 /* MockTunnels.swift */,
|
6FB1017821C57DE600766195 /* MockTunnels.swift */,
|
||||||
6F7F7E5E21C7D74B00527607 /* TunnelErrors.swift */,
|
6F7F7E5E21C7D74B00527607 /* TunnelErrors.swift */,
|
||||||
@@ -543,6 +622,7 @@
|
|||||||
6FB1BD5E21D2607A00A991BF /* macOS */ = {
|
6FB1BD5E21D2607A00A991BF /* macOS */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
6F70E22A22106A2D008BDFB4 /* LoginItemHelper */,
|
||||||
6F4DD16921DA556600690EAE /* View */,
|
6F4DD16921DA556600690EAE /* View */,
|
||||||
6FBA104421D7EA750051C35F /* ViewController */,
|
6FBA104421D7EA750051C35F /* ViewController */,
|
||||||
6FBA101321D613F30051C35F /* Application.swift */,
|
6FBA101321D613F30051C35F /* Application.swift */,
|
||||||
@@ -568,8 +648,10 @@
|
|||||||
6FBA104521D7EBFA0051C35F /* TunnelsListTableViewController.swift */,
|
6FBA104521D7EBFA0051C35F /* TunnelsListTableViewController.swift */,
|
||||||
6F4DD16D21DBEA0700690EAE /* ManageTunnelsRootViewController.swift */,
|
6F4DD16D21DBEA0700690EAE /* ManageTunnelsRootViewController.swift */,
|
||||||
6FDB3C3A21DCF47400A0C0BF /* TunnelDetailTableViewController.swift */,
|
6FDB3C3A21DCF47400A0C0BF /* TunnelDetailTableViewController.swift */,
|
||||||
6FCD99A821E0E0C700BA4C82 /* NoTunnelsDetailViewController.swift */,
|
6FCD99A821E0E0C700BA4C82 /* ButtonedDetailViewController.swift */,
|
||||||
6FCD99B021E0EDA900BA4C82 /* TunnelEditViewController.swift */,
|
6FCD99B021E0EDA900BA4C82 /* TunnelEditViewController.swift */,
|
||||||
|
6FDB6D12224A15BE00EE4BC3 /* LogViewController.swift */,
|
||||||
|
6FADE96A2254A6C200B838A4 /* UnusableTunnelDetailViewController.swift */,
|
||||||
);
|
);
|
||||||
path = ViewController;
|
path = ViewController;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -619,6 +701,7 @@
|
|||||||
6FF4AC0B211EC46F002C96EB = {
|
6FF4AC0B211EC46F002C96EB = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
6F70E20C221058DF008BDFB4 /* InfoPlist.strings */,
|
||||||
6FE1765421C90BBE002690EA /* Localizable.strings */,
|
6FE1765421C90BBE002690EA /* Localizable.strings */,
|
||||||
6F5D0C432183B4A4000F85AD /* Shared */,
|
6F5D0C432183B4A4000F85AD /* Shared */,
|
||||||
6FF4AC16211EC46F002C96EB /* WireGuard */,
|
6FF4AC16211EC46F002C96EB /* WireGuard */,
|
||||||
@@ -635,6 +718,7 @@
|
|||||||
6F5D0C1A218352EF000F85AD /* WireGuardNetworkExtension.appex */,
|
6F5D0C1A218352EF000F85AD /* WireGuardNetworkExtension.appex */,
|
||||||
6FB1BD5D21D2607A00A991BF /* WireGuard.app */,
|
6FB1BD5D21D2607A00A991BF /* WireGuard.app */,
|
||||||
6FB1BD9121D4BFE600A991BF /* WireGuardNetworkExtension.appex */,
|
6FB1BD9121D4BFE600A991BF /* WireGuardNetworkExtension.appex */,
|
||||||
|
6F70E22922106A2D008BDFB4 /* WireGuardLoginItemHelper.app */,
|
||||||
);
|
);
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -721,6 +805,21 @@
|
|||||||
productReference = 6F5D0C1A218352EF000F85AD /* WireGuardNetworkExtension.appex */;
|
productReference = 6F5D0C1A218352EF000F85AD /* WireGuardNetworkExtension.appex */;
|
||||||
productType = "com.apple.product-type.app-extension";
|
productType = "com.apple.product-type.app-extension";
|
||||||
};
|
};
|
||||||
|
6F70E22822106A2D008BDFB4 /* WireGuardmacOSLoginItemHelper */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 6F70E23622106A31008BDFB4 /* Build configuration list for PBXNativeTarget "WireGuardmacOSLoginItemHelper" */;
|
||||||
|
buildPhases = (
|
||||||
|
6F70E22522106A2D008BDFB4 /* Sources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = WireGuardmacOSLoginItemHelper;
|
||||||
|
productName = WireGuardmacOSLoginItemHelper;
|
||||||
|
productReference = 6F70E22922106A2D008BDFB4 /* WireGuardLoginItemHelper.app */;
|
||||||
|
productType = "com.apple.product-type.application";
|
||||||
|
};
|
||||||
6FB1BD5C21D2607A00A991BF /* WireGuardmacOS */ = {
|
6FB1BD5C21D2607A00A991BF /* WireGuardmacOS */ = {
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 6FB1BD6A21D2607E00A991BF /* Build configuration list for PBXNativeTarget "WireGuardmacOS" */;
|
buildConfigurationList = 6FB1BD6A21D2607E00A991BF /* Build configuration list for PBXNativeTarget "WireGuardmacOS" */;
|
||||||
@@ -732,10 +831,12 @@
|
|||||||
6FB1BD5A21D2607A00A991BF /* Frameworks */,
|
6FB1BD5A21D2607A00A991BF /* Frameworks */,
|
||||||
6FB1BD5B21D2607A00A991BF /* Resources */,
|
6FB1BD5B21D2607A00A991BF /* Resources */,
|
||||||
6FB1BD9D21D4BFE700A991BF /* Embed App Extensions */,
|
6FB1BD9D21D4BFE700A991BF /* Embed App Extensions */,
|
||||||
|
6F70E23C22109DE5008BDFB4 /* Embed Login Item Helper */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
|
6F70E23B22109DD3008BDFB4 /* PBXTargetDependency */,
|
||||||
6FB1BD9821D4BFE700A991BF /* PBXTargetDependency */,
|
6FB1BD9821D4BFE700A991BF /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
name = WireGuardmacOS;
|
name = WireGuardmacOS;
|
||||||
@@ -797,10 +898,19 @@
|
|||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
6F5D0C19218352EF000F85AD = {
|
6F5D0C19218352EF000F85AD = {
|
||||||
CreatedOnToolsVersion = 10.0;
|
CreatedOnToolsVersion = 10.0;
|
||||||
LastSwiftMigration = 1000;
|
LastSwiftMigration = 1020;
|
||||||
|
};
|
||||||
|
6F70E22822106A2D008BDFB4 = {
|
||||||
|
CreatedOnToolsVersion = 10.1;
|
||||||
|
SystemCapabilities = {
|
||||||
|
com.apple.Sandbox = {
|
||||||
|
enabled = 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
6FB1BD5C21D2607A00A991BF = {
|
6FB1BD5C21D2607A00A991BF = {
|
||||||
CreatedOnToolsVersion = 10.1;
|
CreatedOnToolsVersion = 10.1;
|
||||||
|
LastSwiftMigration = 1020;
|
||||||
SystemCapabilities = {
|
SystemCapabilities = {
|
||||||
com.apple.ApplicationGroups.Mac = {
|
com.apple.ApplicationGroups.Mac = {
|
||||||
enabled = 1;
|
enabled = 1;
|
||||||
@@ -815,6 +925,7 @@
|
|||||||
};
|
};
|
||||||
6FB1BD9021D4BFE600A991BF = {
|
6FB1BD9021D4BFE600A991BF = {
|
||||||
CreatedOnToolsVersion = 10.1;
|
CreatedOnToolsVersion = 10.1;
|
||||||
|
LastSwiftMigration = 1020;
|
||||||
SystemCapabilities = {
|
SystemCapabilities = {
|
||||||
com.apple.ApplicationGroups.Mac = {
|
com.apple.ApplicationGroups.Mac = {
|
||||||
enabled = 1;
|
enabled = 1;
|
||||||
@@ -826,8 +937,11 @@
|
|||||||
};
|
};
|
||||||
6FF4AC13211EC46F002C96EB = {
|
6FF4AC13211EC46F002C96EB = {
|
||||||
CreatedOnToolsVersion = 9.4.1;
|
CreatedOnToolsVersion = 9.4.1;
|
||||||
LastSwiftMigration = 1000;
|
LastSwiftMigration = 1020;
|
||||||
SystemCapabilities = {
|
SystemCapabilities = {
|
||||||
|
com.apple.AccessWiFi = {
|
||||||
|
enabled = 1;
|
||||||
|
};
|
||||||
com.apple.ApplicationGroups.iOS = {
|
com.apple.ApplicationGroups.iOS = {
|
||||||
enabled = 1;
|
enabled = 1;
|
||||||
};
|
};
|
||||||
@@ -857,6 +971,7 @@
|
|||||||
6FB1BD5C21D2607A00A991BF /* WireGuardmacOS */,
|
6FB1BD5C21D2607A00A991BF /* WireGuardmacOS */,
|
||||||
6FB1BD9021D4BFE600A991BF /* WireGuardNetworkExtensionmacOS */,
|
6FB1BD9021D4BFE600A991BF /* WireGuardNetworkExtensionmacOS */,
|
||||||
6FDAA03421CE69D000FA6925 /* WireGuardGoBridgemacOS */,
|
6FDAA03421CE69D000FA6925 /* WireGuardGoBridgemacOS */,
|
||||||
|
6F70E22822106A2D008BDFB4 /* WireGuardmacOSLoginItemHelper */,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
@@ -874,6 +989,7 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
6FB1BD6221D2607E00A991BF /* Assets.xcassets in Resources */,
|
6FB1BD6221D2607E00A991BF /* Assets.xcassets in Resources */,
|
||||||
|
6F70E20F221058E1008BDFB4 /* InfoPlist.strings in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -889,6 +1005,7 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
6F919ED9218C65C50023B400 /* wireguard_doc_logo_22x29.png in Resources */,
|
6F919ED9218C65C50023B400 /* wireguard_doc_logo_22x29.png in Resources */,
|
||||||
|
6F70E20E221058E1008BDFB4 /* InfoPlist.strings in Resources */,
|
||||||
6FE1765621C90BBE002690EA /* Localizable.strings in Resources */,
|
6FE1765621C90BBE002690EA /* Localizable.strings in Resources */,
|
||||||
6FF4AC22211EC472002C96EB /* LaunchScreen.storyboard in Resources */,
|
6FF4AC22211EC472002C96EB /* LaunchScreen.storyboard in Resources */,
|
||||||
6F919EDA218C65C50023B400 /* wireguard_doc_logo_44x58.png in Resources */,
|
6F919EDA218C65C50023B400 /* wireguard_doc_logo_44x58.png in Resources */,
|
||||||
@@ -1114,13 +1231,22 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
6F70E22522106A2D008BDFB4 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
6B6956362211DA80001B618A /* main.m in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
6FB1BD5921D2607A00A991BF /* Sources */ = {
|
6FB1BD5921D2607A00A991BF /* Sources */ = {
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
6FBA101521D613F90051C35F /* Application.swift in Sources */,
|
6FBA101521D613F90051C35F /* Application.swift in Sources */,
|
||||||
6FB1BDCC21D50F5300A991BF /* TunnelsManager.swift in Sources */,
|
6FB1BDCC21D50F5300A991BF /* TunnelsManager.swift in Sources */,
|
||||||
6FB1BDCD21D50F5300A991BF /* ActivateOnDemandSetting.swift in Sources */,
|
6F8F0D7222258153000E8335 /* ActivateOnDemandViewModel.swift in Sources */,
|
||||||
|
6FB1BDCD21D50F5300A991BF /* ActivateOnDemandOption.swift in Sources */,
|
||||||
6FB1BDCE21D50F5300A991BF /* TunnelStatus.swift in Sources */,
|
6FB1BDCE21D50F5300A991BF /* TunnelStatus.swift in Sources */,
|
||||||
6FB1BDD021D50F5300A991BF /* TunnelErrors.swift in Sources */,
|
6FB1BDD021D50F5300A991BF /* TunnelErrors.swift in Sources */,
|
||||||
6FB1BDD121D50F5300A991BF /* ZipImporter.swift in Sources */,
|
6FB1BDD121D50F5300A991BF /* ZipImporter.swift in Sources */,
|
||||||
@@ -1129,6 +1255,7 @@
|
|||||||
6FB1BDD321D50F5300A991BF /* ZipArchive.swift in Sources */,
|
6FB1BDD321D50F5300A991BF /* ZipArchive.swift in Sources */,
|
||||||
6FB1BDD421D50F5300A991BF /* ioapi.c in Sources */,
|
6FB1BDD421D50F5300A991BF /* ioapi.c in Sources */,
|
||||||
6FDB3C3C21DCF6BB00A0C0BF /* TunnelViewModel.swift in Sources */,
|
6FDB3C3C21DCF6BB00A0C0BF /* TunnelViewModel.swift in Sources */,
|
||||||
|
6FDB6D13224A15BF00EE4BC3 /* LogViewController.swift in Sources */,
|
||||||
6B5C5E29220A48D30024272E /* Keychain.swift in Sources */,
|
6B5C5E29220A48D30024272E /* Keychain.swift in Sources */,
|
||||||
6FCD99AF21E0EA1700BA4C82 /* ImportPanelPresenter.swift in Sources */,
|
6FCD99AF21E0EA1700BA4C82 /* ImportPanelPresenter.swift in Sources */,
|
||||||
6FB1BDD521D50F5300A991BF /* unzip.c in Sources */,
|
6FB1BDD521D50F5300A991BF /* unzip.c in Sources */,
|
||||||
@@ -1149,31 +1276,36 @@
|
|||||||
6FB1BDCA21D50F1700A991BF /* x25519.c in Sources */,
|
6FB1BDCA21D50F1700A991BF /* x25519.c in Sources */,
|
||||||
6FB1BDCB21D50F1700A991BF /* Curve25519.swift in Sources */,
|
6FB1BDCB21D50F1700A991BF /* Curve25519.swift in Sources */,
|
||||||
6B586C55220CBA6D00427C51 /* Data+KeyEncoding.swift in Sources */,
|
6B586C55220CBA6D00427C51 /* Data+KeyEncoding.swift in Sources */,
|
||||||
6F9B582921E8D6D100544D02 /* PopupRow.swift in Sources */,
|
6FB17946222FD5960018AE71 /* OnDemandWiFiControls.swift in Sources */,
|
||||||
6FB1BDBB21D50F0200A991BF /* Localizable.strings in Sources */,
|
6FB1BDBB21D50F0200A991BF /* Localizable.strings in Sources */,
|
||||||
6FB1BDBC21D50F0200A991BF /* ringlogger.c in Sources */,
|
6FB1BDBC21D50F0200A991BF /* ringlogger.c in Sources */,
|
||||||
6FB1BDBD21D50F0200A991BF /* ringlogger.h in Sources */,
|
6FB1BDBD21D50F0200A991BF /* ringlogger.h in Sources */,
|
||||||
6FBA103F21D6B6FF0051C35F /* TunnelImporter.swift in Sources */,
|
6FBA103F21D6B6FF0051C35F /* TunnelImporter.swift in Sources */,
|
||||||
6F89E17A21EDEB0E00C97BB9 /* StatusItemController.swift in Sources */,
|
6F89E17A21EDEB0E00C97BB9 /* StatusItemController.swift in Sources */,
|
||||||
|
6F5EA59B223E58A8002B380A /* ButtonRow.swift in Sources */,
|
||||||
6F4DD16B21DA558800690EAE /* TunnelListRow.swift in Sources */,
|
6F4DD16B21DA558800690EAE /* TunnelListRow.swift in Sources */,
|
||||||
|
6FDB6D15224CB2CE00EE4BC3 /* LogViewCell.swift in Sources */,
|
||||||
6FE3661D21F64F6B00F78C7D /* ConfTextColorTheme.swift in Sources */,
|
6FE3661D21F64F6B00F78C7D /* ConfTextColorTheme.swift in Sources */,
|
||||||
5F52D0BF21E3788900283CEA /* NSColor+Hex.swift in Sources */,
|
5F52D0BF21E3788900283CEA /* NSColor+Hex.swift in Sources */,
|
||||||
6FB1BDBE21D50F0200A991BF /* Logger.swift in Sources */,
|
6FB1BDBE21D50F0200A991BF /* Logger.swift in Sources */,
|
||||||
6FB1BDBF21D50F0200A991BF /* TunnelConfiguration+WgQuickConfig.swift in Sources */,
|
6FB1BDBF21D50F0200A991BF /* TunnelConfiguration+WgQuickConfig.swift in Sources */,
|
||||||
|
6FADE96C2254B8C300B838A4 /* UnusableTunnelDetailViewController.swift in Sources */,
|
||||||
6FFACD2021E4D8D500E9A2A5 /* ParseError+WireGuardAppError.swift in Sources */,
|
6FFACD2021E4D8D500E9A2A5 /* ParseError+WireGuardAppError.swift in Sources */,
|
||||||
6FB1BDC021D50F0200A991BF /* NETunnelProviderProtocol+Extension.swift in Sources */,
|
6FB1BDC021D50F0200A991BF /* NETunnelProviderProtocol+Extension.swift in Sources */,
|
||||||
|
6F1075642258AE9800D78929 /* DeleteTunnelsConfirmationAlert.swift in Sources */,
|
||||||
6FBA101821D656000051C35F /* StatusMenu.swift in Sources */,
|
6FBA101821D656000051C35F /* StatusMenu.swift in Sources */,
|
||||||
6F613D9B21DE33B8004B217A /* KeyValueRow.swift in Sources */,
|
6F613D9B21DE33B8004B217A /* KeyValueRow.swift in Sources */,
|
||||||
6FB1BDC121D50F0200A991BF /* String+ArrayConversion.swift in Sources */,
|
6FB1BDC121D50F0200A991BF /* String+ArrayConversion.swift in Sources */,
|
||||||
5F52D0BB21E3781B00283CEA /* ConfTextView.swift in Sources */,
|
5F52D0BB21E3781B00283CEA /* ConfTextView.swift in Sources */,
|
||||||
6FBA104021D6B7040051C35F /* ErrorPresenterProtocol.swift in Sources */,
|
6FBA104021D6B7040051C35F /* ErrorPresenterProtocol.swift in Sources */,
|
||||||
6FCD99AA21E0E14700BA4C82 /* NoTunnelsDetailViewController.swift in Sources */,
|
6FCD99AA21E0E14700BA4C82 /* ButtonedDetailViewController.swift in Sources */,
|
||||||
6FB1BDC321D50F0300A991BF /* TunnelConfiguration.swift in Sources */,
|
6FB1BDC321D50F0300A991BF /* TunnelConfiguration.swift in Sources */,
|
||||||
6FB1BDC421D50F0300A991BF /* IPAddressRange.swift in Sources */,
|
6FB1BDC421D50F0300A991BF /* IPAddressRange.swift in Sources */,
|
||||||
6FBA104321D6BC250051C35F /* ErrorPresenter.swift in Sources */,
|
6FBA104321D6BC250051C35F /* ErrorPresenter.swift in Sources */,
|
||||||
6FB1BDC521D50F0300A991BF /* Endpoint.swift in Sources */,
|
6FB1BDC521D50F0300A991BF /* Endpoint.swift in Sources */,
|
||||||
6FB1BDC621D50F0300A991BF /* DNSServer.swift in Sources */,
|
6FB1BDC621D50F0300A991BF /* DNSServer.swift in Sources */,
|
||||||
6FB1BDC721D50F0300A991BF /* InterfaceConfiguration.swift in Sources */,
|
6FB1BDC721D50F0300A991BF /* InterfaceConfiguration.swift in Sources */,
|
||||||
|
6F907C9D224663A2003CED21 /* LogViewHelper.swift in Sources */,
|
||||||
6FB1BDC821D50F0300A991BF /* PeerConfiguration.swift in Sources */,
|
6FB1BDC821D50F0300A991BF /* PeerConfiguration.swift in Sources */,
|
||||||
6FB1BDC921D50F0300A991BF /* FileManager+Extension.swift in Sources */,
|
6FB1BDC921D50F0300A991BF /* FileManager+Extension.swift in Sources */,
|
||||||
6FB1BD6021D2607A00A991BF /* AppDelegate.swift in Sources */,
|
6FB1BD6021D2607A00A991BF /* AppDelegate.swift in Sources */,
|
||||||
@@ -1212,18 +1344,23 @@
|
|||||||
files = (
|
files = (
|
||||||
6FE1765A21C90E87002690EA /* LocalizationHelper.swift in Sources */,
|
6FE1765A21C90E87002690EA /* LocalizationHelper.swift in Sources */,
|
||||||
6FF3527221C2616C0008484E /* ringlogger.c in Sources */,
|
6FF3527221C2616C0008484E /* ringlogger.c in Sources */,
|
||||||
|
6F0F44CB222D55FD00B0FF04 /* EditableTextCell.swift in Sources */,
|
||||||
6FF3527321C2616C0008484E /* Logger.swift in Sources */,
|
6FF3527321C2616C0008484E /* Logger.swift in Sources */,
|
||||||
6F7774E421718281006A79B3 /* TunnelsListTableViewController.swift in Sources */,
|
6F7774E421718281006A79B3 /* TunnelsListTableViewController.swift in Sources */,
|
||||||
6F7774EF21722D97006A79B3 /* TunnelsManager.swift in Sources */,
|
6F7774EF21722D97006A79B3 /* TunnelsManager.swift in Sources */,
|
||||||
5F45417D21C1B23600994C13 /* UITableViewCell+Reuse.swift in Sources */,
|
5F45417D21C1B23600994C13 /* UITableViewCell+Reuse.swift in Sources */,
|
||||||
5F45419221C2D55800994C13 /* CheckmarkCell.swift in Sources */,
|
5F45419221C2D55800994C13 /* CheckmarkCell.swift in Sources */,
|
||||||
6FE254FF219C60290028284D /* ZipExporter.swift in Sources */,
|
6FE254FF219C60290028284D /* ZipExporter.swift in Sources */,
|
||||||
|
6F8F0D7122258153000E8335 /* ActivateOnDemandViewModel.swift in Sources */,
|
||||||
|
6F8F0D7722267C57000E8335 /* SSIDOptionEditTableViewController.swift in Sources */,
|
||||||
6B586C53220CBA6D00427C51 /* Data+KeyEncoding.swift in Sources */,
|
6B586C53220CBA6D00427C51 /* Data+KeyEncoding.swift in Sources */,
|
||||||
6F693A562179E556008551C1 /* Endpoint.swift in Sources */,
|
6F693A562179E556008551C1 /* Endpoint.swift in Sources */,
|
||||||
6FDEF7E62185EFB200D8FBF6 /* QRScanViewController.swift in Sources */,
|
6FDEF7E62185EFB200D8FBF6 /* QRScanViewController.swift in Sources */,
|
||||||
|
6FDB6D18224CC05A00EE4BC3 /* LogViewController.swift in Sources */,
|
||||||
6FFA5D952194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */,
|
6FFA5D952194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */,
|
||||||
5FF7B96221CC95DE00A7DD74 /* InterfaceConfiguration.swift in Sources */,
|
5FF7B96221CC95DE00A7DD74 /* InterfaceConfiguration.swift in Sources */,
|
||||||
5F4541A921C451D100994C13 /* TunnelStatus.swift in Sources */,
|
5F4541A921C451D100994C13 /* TunnelStatus.swift in Sources */,
|
||||||
|
6F8F0D7422267AD2000E8335 /* ChevronCell.swift in Sources */,
|
||||||
6F61F1E921B932F700483816 /* WireGuardAppError.swift in Sources */,
|
6F61F1E921B932F700483816 /* WireGuardAppError.swift in Sources */,
|
||||||
6F6899A62180447E0012E523 /* x25519.c in Sources */,
|
6F6899A62180447E0012E523 /* x25519.c in Sources */,
|
||||||
6F7774E2217181B1006A79B3 /* AppDelegate.swift in Sources */,
|
6F7774E2217181B1006A79B3 /* AppDelegate.swift in Sources */,
|
||||||
@@ -1236,6 +1373,8 @@
|
|||||||
5F4541A621C4449E00994C13 /* ButtonCell.swift in Sources */,
|
5F4541A621C4449E00994C13 /* ButtonCell.swift in Sources */,
|
||||||
5F45419821C2D60500994C13 /* KeyValueCell.swift in Sources */,
|
5F45419821C2D60500994C13 /* KeyValueCell.swift in Sources */,
|
||||||
6FBA103E21D6B6D70051C35F /* TunnelImporter.swift in Sources */,
|
6FBA103E21D6B6D70051C35F /* TunnelImporter.swift in Sources */,
|
||||||
|
6F9B8A8E223398610041B9C4 /* SSIDOptionDetailTableViewController.swift in Sources */,
|
||||||
|
6F19D30422402B8700A126F2 /* ConfirmationAlertPresenter.swift in Sources */,
|
||||||
6F919EC3218A2AE90023B400 /* ErrorPresenter.swift in Sources */,
|
6F919EC3218A2AE90023B400 /* ErrorPresenter.swift in Sources */,
|
||||||
6B62E45F220A6FA900EF34A6 /* PrivateDataConfirmation.swift in Sources */,
|
6B62E45F220A6FA900EF34A6 /* PrivateDataConfirmation.swift in Sources */,
|
||||||
6F5A2B4821AFF49A0081EDD8 /* FileManager+Extension.swift in Sources */,
|
6F5A2B4821AFF49A0081EDD8 /* FileManager+Extension.swift in Sources */,
|
||||||
@@ -1245,6 +1384,7 @@
|
|||||||
6F7774E82172020C006A79B3 /* TunnelConfiguration.swift in Sources */,
|
6F7774E82172020C006A79B3 /* TunnelConfiguration.swift in Sources */,
|
||||||
6FDEF7FB21863B6100D8FBF6 /* unzip.c in Sources */,
|
6FDEF7FB21863B6100D8FBF6 /* unzip.c in Sources */,
|
||||||
6F6899A8218044FC0012E523 /* Curve25519.swift in Sources */,
|
6F6899A8218044FC0012E523 /* Curve25519.swift in Sources */,
|
||||||
|
6F0F44C9222D55BB00B0FF04 /* TextCell.swift in Sources */,
|
||||||
5F4541A021C2D6B700994C13 /* TunnelListCell.swift in Sources */,
|
5F4541A021C2D6B700994C13 /* TunnelListCell.swift in Sources */,
|
||||||
5F9696B021CD7128008063FE /* TunnelConfiguration+WgQuickConfig.swift in Sources */,
|
5F9696B021CD7128008063FE /* TunnelConfiguration+WgQuickConfig.swift in Sources */,
|
||||||
6F628C41217F47DB003482A3 /* TunnelDetailTableViewController.swift in Sources */,
|
6F628C41217F47DB003482A3 /* TunnelDetailTableViewController.swift in Sources */,
|
||||||
@@ -1260,8 +1400,9 @@
|
|||||||
5F4541A221C2D6DF00994C13 /* BorderedTextButton.swift in Sources */,
|
5F4541A221C2D6DF00994C13 /* BorderedTextButton.swift in Sources */,
|
||||||
5FF7B96521CC95FA00A7DD74 /* PeerConfiguration.swift in Sources */,
|
5FF7B96521CC95FA00A7DD74 /* PeerConfiguration.swift in Sources */,
|
||||||
6F7774E1217181B1006A79B3 /* MainViewController.swift in Sources */,
|
6F7774E1217181B1006A79B3 /* MainViewController.swift in Sources */,
|
||||||
6FFA5DA42197085D0001E2F7 /* ActivateOnDemandSetting.swift in Sources */,
|
6FFA5DA42197085D0001E2F7 /* ActivateOnDemandOption.swift in Sources */,
|
||||||
5F4541B221CBFAEE00994C13 /* String+ArrayConversion.swift in Sources */,
|
5F4541B221CBFAEE00994C13 /* String+ArrayConversion.swift in Sources */,
|
||||||
|
6F907C9C224663A2003CED21 /* LogViewHelper.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -1273,6 +1414,11 @@
|
|||||||
target = 6F5D0C19218352EF000F85AD /* WireGuardNetworkExtensioniOS */;
|
target = 6F5D0C19218352EF000F85AD /* WireGuardNetworkExtensioniOS */;
|
||||||
targetProxy = 6F5D0C20218352EF000F85AD /* PBXContainerItemProxy */;
|
targetProxy = 6F5D0C20218352EF000F85AD /* PBXContainerItemProxy */;
|
||||||
};
|
};
|
||||||
|
6F70E23B22109DD3008BDFB4 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = 6F70E22822106A2D008BDFB4 /* WireGuardmacOSLoginItemHelper */;
|
||||||
|
targetProxy = 6F70E23A22109DD3008BDFB4 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
6FB1BD9821D4BFE700A991BF /* PBXTargetDependency */ = {
|
6FB1BD9821D4BFE700A991BF /* PBXTargetDependency */ = {
|
||||||
isa = PBXTargetDependency;
|
isa = PBXTargetDependency;
|
||||||
target = 6FB1BD9021D4BFE600A991BF /* WireGuardNetworkExtensionmacOS */;
|
target = 6FB1BD9021D4BFE600A991BF /* WireGuardNetworkExtensionmacOS */;
|
||||||
@@ -1291,6 +1437,14 @@
|
|||||||
/* End PBXTargetDependency section */
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
/* Begin PBXVariantGroup section */
|
/* Begin PBXVariantGroup section */
|
||||||
|
6F70E20C221058DF008BDFB4 /* InfoPlist.strings */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
6F70E20D221058DF008BDFB4 /* Base */,
|
||||||
|
);
|
||||||
|
name = InfoPlist.strings;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
6FE1765421C90BBE002690EA /* Localizable.strings */ = {
|
6FE1765421C90BBE002690EA /* Localizable.strings */ = {
|
||||||
isa = PBXVariantGroup;
|
isa = PBXVariantGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -1328,7 +1482,7 @@
|
|||||||
PRODUCT_NAME = WireGuardNetworkExtension;
|
PRODUCT_NAME = WireGuardNetworkExtension;
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "WireGuardNetworkExtension/WireGuardNetworkExtension-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "WireGuardNetworkExtension/WireGuardNetworkExtension-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 4.2;
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
@@ -1350,7 +1504,33 @@
|
|||||||
PRODUCT_NAME = WireGuardNetworkExtension;
|
PRODUCT_NAME = WireGuardNetworkExtension;
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "WireGuardNetworkExtension/WireGuardNetworkExtension-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "WireGuardNetworkExtension/WireGuardNetworkExtension-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 4.2;
|
SWIFT_VERSION = 5.0;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
6F70E23422106A31008BDFB4 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CODE_SIGN_ENTITLEMENTS = WireGuard/UI/macOS/LoginItemHelper/LoginItemHelper.entitlements;
|
||||||
|
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||||
|
INFOPLIST_FILE = WireGuard/UI/macOS/Info.plist;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_MACOS).login-item-helper";
|
||||||
|
PRODUCT_NAME = WireGuardLoginItemHelper;
|
||||||
|
SDKROOT = macosx;
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
6F70E23522106A31008BDFB4 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CODE_SIGN_ENTITLEMENTS = WireGuard/UI/macOS/LoginItemHelper/LoginItemHelper.entitlements;
|
||||||
|
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||||
|
INFOPLIST_FILE = WireGuard/UI/macOS/Info.plist;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_MACOS).login-item-helper";
|
||||||
|
PRODUCT_NAME = WireGuardLoginItemHelper;
|
||||||
|
SDKROOT = macosx;
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
@@ -1370,6 +1550,7 @@
|
|||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_MACOS)";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_MACOS)";
|
||||||
PRODUCT_NAME = WireGuard;
|
PRODUCT_NAME = WireGuard;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
@@ -1389,6 +1570,7 @@
|
|||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_MACOS)";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_MACOS)";
|
||||||
PRODUCT_NAME = WireGuard;
|
PRODUCT_NAME = WireGuard;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
@@ -1410,6 +1592,7 @@
|
|||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "WireGuardNetworkExtension/WireGuardNetworkExtension-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "WireGuardNetworkExtension/WireGuardNetworkExtension-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
@@ -1431,6 +1614,7 @@
|
|||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "WireGuardNetworkExtension/WireGuardNetworkExtension-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "WireGuardNetworkExtension/WireGuardNetworkExtension-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
@@ -1607,6 +1791,7 @@
|
|||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_IOS)";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_IOS)";
|
||||||
PRODUCT_NAME = WireGuard;
|
PRODUCT_NAME = WireGuard;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
@@ -1623,6 +1808,7 @@
|
|||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_IOS)";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_IOS)";
|
||||||
PRODUCT_NAME = WireGuard;
|
PRODUCT_NAME = WireGuard;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
@@ -1638,6 +1824,15 @@
|
|||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
|
6F70E23622106A31008BDFB4 /* Build configuration list for PBXNativeTarget "WireGuardmacOSLoginItemHelper" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
6F70E23422106A31008BDFB4 /* Debug */,
|
||||||
|
6F70E23522106A31008BDFB4 /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
6FB1BD6A21D2607E00A991BF /* Build configuration list for PBXNativeTarget "WireGuardmacOS" */ = {
|
6FB1BD6A21D2607E00A991BF /* Build configuration list for PBXNativeTarget "WireGuardmacOS" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
|
// iOS permission prompts
|
||||||
|
|
||||||
|
NSCameraUsageDescription = "Camera is used for scanning QR codes for importing WireGuard configurations";
|
||||||
|
NSFaceIDUsageDescription = "Face ID is used for authenticating viewing and exporting of private keys";
|
||||||
@@ -13,6 +13,10 @@
|
|||||||
"tunnelsListSettingsButtonTitle" = "Settings";
|
"tunnelsListSettingsButtonTitle" = "Settings";
|
||||||
"tunnelsListCenteredAddTunnelButtonTitle" = "Add a tunnel";
|
"tunnelsListCenteredAddTunnelButtonTitle" = "Add a tunnel";
|
||||||
"tunnelsListSwipeDeleteButtonTitle" = "Delete";
|
"tunnelsListSwipeDeleteButtonTitle" = "Delete";
|
||||||
|
"tunnelsListSelectButtonTitle" = "Select";
|
||||||
|
"tunnelsListSelectAllButtonTitle" = "Select All";
|
||||||
|
"tunnelsListDeleteButtonTitle" = "Delete";
|
||||||
|
"tunnelsListSelectedTitle (%d)" = "%d selected";
|
||||||
|
|
||||||
// Tunnels list menu
|
// Tunnels list menu
|
||||||
|
|
||||||
@@ -23,11 +27,18 @@
|
|||||||
|
|
||||||
// Tunnels list alerts
|
// Tunnels list alerts
|
||||||
|
|
||||||
|
"alertImportedFromMultipleFilesTitle (%d)" = "Created %d tunnels";
|
||||||
|
"alertImportedFromMultipleFilesMessage (%1$d of %2$d)" = "Created %1$d of %2$d tunnels from imported files";
|
||||||
|
|
||||||
"alertImportedFromZipTitle (%d)" = "Created %d tunnels";
|
"alertImportedFromZipTitle (%d)" = "Created %d tunnels";
|
||||||
"alertImportedFromZipMessage (%1$d of %2$d)" = "Created %1$d of %2$d tunnels from zip archive";
|
"alertImportedFromZipMessage (%1$d of %2$d)" = "Created %1$d of %2$d tunnels from zip archive";
|
||||||
|
|
||||||
"alertUnableToImportTitle" = "Unable to import tunnel";
|
"alertBadConfigImportTitle" = "Unable to import tunnel";
|
||||||
"alertUnableToImportMessage" = "An error occured when importing the tunnel configuration.";
|
"alertBadConfigImportMessage (%@)" = "The file ‘%@’ does not contain a valid WireGuard configuration";
|
||||||
|
|
||||||
|
"deleteTunnelsConfirmationAlertButtonTitle" = "Delete";
|
||||||
|
"deleteTunnelConfirmationAlertButtonMessage (%d)" = "Delete %d tunnel?";
|
||||||
|
"deleteTunnelsConfirmationAlertButtonMessage (%d)" = "Delete %d tunnels?";
|
||||||
|
|
||||||
// Tunnel detail and edit UI
|
// Tunnel detail and edit UI
|
||||||
|
|
||||||
@@ -44,6 +55,14 @@
|
|||||||
"tunnelStatusRestarting" = "Restarting";
|
"tunnelStatusRestarting" = "Restarting";
|
||||||
"tunnelStatusWaiting" = "Waiting";
|
"tunnelStatusWaiting" = "Waiting";
|
||||||
|
|
||||||
|
"macToggleStatusButtonActivate" = "Activate";
|
||||||
|
"macToggleStatusButtonActivating" = "Activating…";
|
||||||
|
"macToggleStatusButtonDeactivate" = "Deactivate";
|
||||||
|
"macToggleStatusButtonDeactivating" = "Deactivating…";
|
||||||
|
"macToggleStatusButtonReasserting" = "Reactivating…";
|
||||||
|
"macToggleStatusButtonRestarting" = "Restarting…";
|
||||||
|
"macToggleStatusButtonWaiting" = "Waiting…";
|
||||||
|
|
||||||
"tunnelSectionTitleInterface" = "Interface";
|
"tunnelSectionTitleInterface" = "Interface";
|
||||||
|
|
||||||
"tunnelInterfaceName" = "Name";
|
"tunnelInterfaceName" = "Name";
|
||||||
@@ -54,6 +73,7 @@
|
|||||||
"tunnelInterfaceListenPort" = "Listen port";
|
"tunnelInterfaceListenPort" = "Listen port";
|
||||||
"tunnelInterfaceMTU" = "MTU";
|
"tunnelInterfaceMTU" = "MTU";
|
||||||
"tunnelInterfaceDNS" = "DNS servers";
|
"tunnelInterfaceDNS" = "DNS servers";
|
||||||
|
"tunnelInterfaceStatus" = "Status";
|
||||||
|
|
||||||
"tunnelSectionTitlePeer" = "Peer";
|
"tunnelSectionTitlePeer" = "Peer";
|
||||||
|
|
||||||
@@ -69,7 +89,28 @@
|
|||||||
|
|
||||||
"tunnelSectionTitleOnDemand" = "On-Demand Activation";
|
"tunnelSectionTitleOnDemand" = "On-Demand Activation";
|
||||||
|
|
||||||
"tunnelOnDemandKey" = "Activate on demand";
|
"tunnelOnDemandCellular" = "Cellular";
|
||||||
|
"tunnelOnDemandEthernet" = "Ethernet";
|
||||||
|
"tunnelOnDemandWiFi" = "Wi-Fi";
|
||||||
|
"tunnelOnDemandSSIDsKey" = "SSIDs";
|
||||||
|
|
||||||
|
"tunnelOnDemandAnySSID" = "Any SSID";
|
||||||
|
"tunnelOnDemandOnlyTheseSSIDs" = "Only these SSIDs";
|
||||||
|
"tunnelOnDemandExceptTheseSSIDs" = "Except these SSIDs";
|
||||||
|
"tunnelOnDemandOnlySSID (%d)" = "Only %d SSID";
|
||||||
|
"tunnelOnDemandOnlySSIDs (%d)" = "Only %d SSIDs";
|
||||||
|
"tunnelOnDemandExceptSSID (%d)" = "Except %d SSID";
|
||||||
|
"tunnelOnDemandExceptSSIDs (%d)" = "Except %d SSIDs";
|
||||||
|
"tunnelOnDemandSSIDOptionDescriptionMac (%1$@: %2$@)" = "%1$@: %2$@";
|
||||||
|
|
||||||
|
"tunnelOnDemandSSIDViewTitle" = "SSIDs";
|
||||||
|
"tunnelOnDemandSectionTitleSelectedSSIDs" = "SSIDs";
|
||||||
|
"tunnelOnDemandNoSSIDs" = "No SSIDs";
|
||||||
|
"tunnelOnDemandSectionTitleAddSSIDs" = "Add SSIDs";
|
||||||
|
"tunnelOnDemandAddMessageAddConnectedSSID (%@)" = "Add connected: %@";
|
||||||
|
"tunnelOnDemandAddMessageAddNewSSID" = "Add new";
|
||||||
|
|
||||||
|
"tunnelOnDemandKey" = "On demand";
|
||||||
"tunnelOnDemandOptionOff" = "Off";
|
"tunnelOnDemandOptionOff" = "Off";
|
||||||
"tunnelOnDemandOptionWiFiOnly" = "Wi-Fi only";
|
"tunnelOnDemandOptionWiFiOnly" = "Wi-Fi only";
|
||||||
"tunnelOnDemandOptionWiFiOrCellular" = "Wi-Fi or cellular";
|
"tunnelOnDemandOptionWiFiOrCellular" = "Wi-Fi or cellular";
|
||||||
@@ -111,6 +152,8 @@
|
|||||||
"tunnelHandshakeTimestampHours hh:mm:ss (%@)" = "%@ hours";
|
"tunnelHandshakeTimestampHours hh:mm:ss (%@)" = "%@ hours";
|
||||||
"tunnelHandshakeTimestampMinutes mm:ss (%@)" = "%@ minutes";
|
"tunnelHandshakeTimestampMinutes mm:ss (%@)" = "%@ minutes";
|
||||||
|
|
||||||
|
"tunnelPeerPresharedKeyEnabled" = "enabled";
|
||||||
|
|
||||||
// Error alerts while creating / editing a tunnel configuration
|
// Error alerts while creating / editing a tunnel configuration
|
||||||
|
|
||||||
/* Alert title for error in the interface data */
|
/* Alert title for error in the interface data */
|
||||||
@@ -166,17 +209,18 @@
|
|||||||
"settingsSectionTitleExportConfigurations" = "Export configurations";
|
"settingsSectionTitleExportConfigurations" = "Export configurations";
|
||||||
"settingsExportZipButtonTitle" = "Export zip archive";
|
"settingsExportZipButtonTitle" = "Export zip archive";
|
||||||
|
|
||||||
"settingsSectionTitleTunnelLog" = "Tunnel log";
|
"settingsSectionTitleTunnelLog" = "Log";
|
||||||
"settingsExportLogFileButtonTitle" = "Export log file";
|
"settingsViewLogButtonTitle" = "View log";
|
||||||
|
|
||||||
// Settings alerts
|
// Log view
|
||||||
|
|
||||||
|
"logViewTitle" = "Log";
|
||||||
|
|
||||||
|
// Log alerts
|
||||||
|
|
||||||
"alertUnableToRemovePreviousLogTitle" = "Log export failed";
|
"alertUnableToRemovePreviousLogTitle" = "Log export failed";
|
||||||
"alertUnableToRemovePreviousLogMessage" = "The pre-existing log could not be cleared";
|
"alertUnableToRemovePreviousLogMessage" = "The pre-existing log could not be cleared";
|
||||||
|
|
||||||
"alertUnableToFindExtensionLogPathTitle" = "Log export failed";
|
|
||||||
"alertUnableToFindExtensionLogPathMessage" = "Unable to determine extension log path";
|
|
||||||
|
|
||||||
"alertUnableToWriteLogTitle" = "Log export failed";
|
"alertUnableToWriteLogTitle" = "Log export failed";
|
||||||
"alertUnableToWriteLogMessage" = "Unable to write logs to file";
|
"alertUnableToWriteLogMessage" = "Unable to write logs to file";
|
||||||
|
|
||||||
@@ -197,6 +241,11 @@
|
|||||||
"alertNoTunnelsInImportedZipArchiveTitle" = "No tunnels in zip archive";
|
"alertNoTunnelsInImportedZipArchiveTitle" = "No tunnels in zip archive";
|
||||||
"alertNoTunnelsInImportedZipArchiveMessage" = "No .conf tunnel files were found inside the zip archive.";
|
"alertNoTunnelsInImportedZipArchiveMessage" = "No .conf tunnel files were found inside the zip archive.";
|
||||||
|
|
||||||
|
// Conf import error alerts
|
||||||
|
|
||||||
|
"alertCantOpenInputConfFileTitle" = "Unable to import from file";
|
||||||
|
"alertCantOpenInputConfFileMessage (%@)" = "The file ‘%@’ could not be read.";
|
||||||
|
|
||||||
// Tunnel management error alerts
|
// Tunnel management error alerts
|
||||||
|
|
||||||
"alertTunnelActivationFailureTitle" = "Activation failure";
|
"alertTunnelActivationFailureTitle" = "Activation failure";
|
||||||
@@ -251,7 +300,7 @@
|
|||||||
"macMenuManageTunnels" = "Manage tunnels";
|
"macMenuManageTunnels" = "Manage tunnels";
|
||||||
"macMenuImportTunnels" = "Import tunnel(s) from file…";
|
"macMenuImportTunnels" = "Import tunnel(s) from file…";
|
||||||
"macMenuAddEmptyTunnel" = "Add empty tunnel…";
|
"macMenuAddEmptyTunnel" = "Add empty tunnel…";
|
||||||
"macMenuExportLog" = "Export log to file…";
|
"macMenuViewLog" = "View log";
|
||||||
"macMenuExportTunnels" = "Export tunnels to zip…";
|
"macMenuExportTunnels" = "Export tunnels to zip…";
|
||||||
"macMenuAbout" = "About WireGuard";
|
"macMenuAbout" = "About WireGuard";
|
||||||
"macMenuQuit" = "Quit";
|
"macMenuQuit" = "Quit";
|
||||||
@@ -261,23 +310,28 @@
|
|||||||
"macWindowTitleManageTunnels" = "Manage WireGuard Tunnels";
|
"macWindowTitleManageTunnels" = "Manage WireGuard Tunnels";
|
||||||
|
|
||||||
"macDeleteTunnelConfirmationAlertMessage (%@)" = "Are you sure you want to delete ‘%@’?";
|
"macDeleteTunnelConfirmationAlertMessage (%@)" = "Are you sure you want to delete ‘%@’?";
|
||||||
|
"macDeleteMultipleTunnelsConfirmationAlertMessage (%d)" = "Are you sure you want to delete %d tunnels?";
|
||||||
"macDeleteTunnelConfirmationAlertInfo" = "You cannot undo this action.";
|
"macDeleteTunnelConfirmationAlertInfo" = "You cannot undo this action.";
|
||||||
"macDeleteTunnelConfirmationAlertButtonTitleDelete" = "Delete";
|
"macDeleteTunnelConfirmationAlertButtonTitleDelete" = "Delete";
|
||||||
"macDeleteTunnelConfirmationAlertButtonTitleCancel" = "Cancel";
|
"macDeleteTunnelConfirmationAlertButtonTitleCancel" = "Cancel";
|
||||||
|
"macDeleteTunnelConfirmationAlertButtonTitleDeleting" = "Deleting…";
|
||||||
|
|
||||||
"macButtonImportTunnels" = "Import tunnel(s) from file";
|
"macButtonImportTunnels" = "Import tunnel(s) from file";
|
||||||
"macSheetButtonImport" = "Import";
|
"macSheetButtonImport" = "Import";
|
||||||
|
|
||||||
"macNameFieldExportLog" = "Export log to";
|
"macNameFieldExportLog" = "Save log to:";
|
||||||
"macSheetButtonExportLog" = "Save";
|
"macSheetButtonExportLog" = "Save";
|
||||||
|
|
||||||
"macNameFieldExportZip" = "Export tunnels to";
|
"macNameFieldExportZip" = "Export tunnels to:";
|
||||||
"macSheetButtonExportZip" = "Save";
|
"macSheetButtonExportZip" = "Save";
|
||||||
|
|
||||||
|
"macButtonDeleteTunnels (%d)" = "Delete %d tunnels";
|
||||||
|
|
||||||
// Mac detail/edit view fields
|
// Mac detail/edit view fields
|
||||||
|
|
||||||
"macFieldKey (%@)" = "%@:";
|
"macFieldKey (%@)" = "%@:";
|
||||||
"macFieldOnDemand" = "On-Demand:";
|
"macFieldOnDemand" = "On-Demand:";
|
||||||
|
"macFieldOnDemandSSIDs" = "SSIDs:";
|
||||||
|
|
||||||
// Mac status display
|
// Mac status display
|
||||||
|
|
||||||
@@ -331,3 +385,23 @@
|
|||||||
|
|
||||||
"macAppExitingWithActiveTunnelMessage" = "WireGuard is exiting with an active tunnel";
|
"macAppExitingWithActiveTunnelMessage" = "WireGuard is exiting with an active tunnel";
|
||||||
"macAppExitingWithActiveTunnelInfo" = "The tunnel will remain active after exiting. You may disable it by reopening this application or through the Network panel in System Preferences.";
|
"macAppExitingWithActiveTunnelInfo" = "The tunnel will remain active after exiting. You may disable it by reopening this application or through the Network panel in System Preferences.";
|
||||||
|
"macPrivacyNoticeMessage" = "Privacy notice: be sure you trust this configuration file";
|
||||||
|
"macPrivacyNoticeInfo" = "You will be prompted by the system to allow or disallow adding a VPN configuration. While this application does not send any information to the WireGuard project, information is by design sent to the servers specified inside of the configuration file you have just added, which configures your computer to use those servers as a VPN. Be certain that you trust this configuration before clicking “Allow” in the following dialog.";
|
||||||
|
|
||||||
|
// Mac tooltip
|
||||||
|
|
||||||
|
"macToolTipEditTunnel" = "Edit tunnel (⌘E)";
|
||||||
|
"macToolTipToggleStatus" = "Toggle status (⌘T)";
|
||||||
|
|
||||||
|
// Mac log view
|
||||||
|
|
||||||
|
"macLogColumnTitleTime" = "Time";
|
||||||
|
"macLogColumnTitleLogMessage" = "Log message";
|
||||||
|
"macLogButtonTitleClose" = "Close";
|
||||||
|
"macLogButtonTitleSave" = "Save…";
|
||||||
|
|
||||||
|
// Mac unusable tunnel view
|
||||||
|
|
||||||
|
"macUnusableTunnelMessage" = "The configuration for this tunnel cannot be found in the keychain.";
|
||||||
|
"macUnusableTunnelInfo" = "In case this tunnel was created by another user, only that user can view, edit, or activate this tunnel.";
|
||||||
|
"macUnusableTunnelButtonTitleDeleteTunnel" = "Delete tunnel";
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
VERSION_NAME = 0.0.20190207
|
VERSION_NAME = 0.0.20190409
|
||||||
VERSION_ID = 2
|
VERSION_ID = 7
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ struct Curve25519 {
|
|||||||
|
|
||||||
static func generatePrivateKey() -> Data {
|
static func generatePrivateKey() -> Data {
|
||||||
var privateKey = Data(repeating: 0, count: TunnelConfiguration.keyLength)
|
var privateKey = Data(repeating: 0, count: TunnelConfiguration.keyLength)
|
||||||
privateKey.withUnsafeMutableBytes { bytes in
|
privateKey.withUnsafeMutableUInt8Bytes { bytes in
|
||||||
curve25519_generate_private_key(bytes)
|
curve25519_generate_private_key(bytes)
|
||||||
}
|
}
|
||||||
assert(privateKey.count == TunnelConfiguration.keyLength)
|
assert(privateKey.count == TunnelConfiguration.keyLength)
|
||||||
@@ -19,8 +19,8 @@ struct Curve25519 {
|
|||||||
static func generatePublicKey(fromPrivateKey privateKey: Data) -> Data {
|
static func generatePublicKey(fromPrivateKey privateKey: Data) -> Data {
|
||||||
assert(privateKey.count == TunnelConfiguration.keyLength)
|
assert(privateKey.count == TunnelConfiguration.keyLength)
|
||||||
var publicKey = Data(repeating: 0, count: TunnelConfiguration.keyLength)
|
var publicKey = Data(repeating: 0, count: TunnelConfiguration.keyLength)
|
||||||
privateKey.withUnsafeBytes { privateKeyBytes in
|
privateKey.withUnsafeUInt8Bytes { privateKeyBytes in
|
||||||
publicKey.withUnsafeMutableBytes { bytes in
|
publicKey.withUnsafeMutableUInt8Bytes { bytes in
|
||||||
curve25519_derive_public_key(bytes, privateKeyBytes)
|
curve25519_derive_public_key(bytes, privateKeyBytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
|
import NetworkExtension
|
||||||
|
|
||||||
|
enum ActivateOnDemandOption: Equatable {
|
||||||
|
case off
|
||||||
|
case wiFiInterfaceOnly(ActivateOnDemandSSIDOption)
|
||||||
|
case nonWiFiInterfaceOnly
|
||||||
|
case anyInterface(ActivateOnDemandSSIDOption)
|
||||||
|
}
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
private let nonWiFiInterfaceType: NEOnDemandRuleInterfaceType = .cellular
|
||||||
|
#elseif os(macOS)
|
||||||
|
private let nonWiFiInterfaceType: NEOnDemandRuleInterfaceType = .ethernet
|
||||||
|
#else
|
||||||
|
#error("Unimplemented")
|
||||||
|
#endif
|
||||||
|
|
||||||
|
enum ActivateOnDemandSSIDOption: Equatable {
|
||||||
|
case anySSID
|
||||||
|
case onlySpecificSSIDs([String])
|
||||||
|
case exceptSpecificSSIDs([String])
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ActivateOnDemandOption {
|
||||||
|
func apply(on tunnelProviderManager: NETunnelProviderManager) {
|
||||||
|
let rules: [NEOnDemandRule]?
|
||||||
|
switch self {
|
||||||
|
case .off:
|
||||||
|
rules = nil
|
||||||
|
case .wiFiInterfaceOnly(let ssidOption):
|
||||||
|
rules = ssidOnDemandRules(option: ssidOption) + [NEOnDemandRuleDisconnect(interfaceType: nonWiFiInterfaceType)]
|
||||||
|
case .nonWiFiInterfaceOnly:
|
||||||
|
rules = [NEOnDemandRuleConnect(interfaceType: nonWiFiInterfaceType), NEOnDemandRuleDisconnect(interfaceType: .wiFi)]
|
||||||
|
case .anyInterface(let ssidOption):
|
||||||
|
if case .anySSID = ssidOption {
|
||||||
|
rules = [NEOnDemandRuleConnect(interfaceType: .any)]
|
||||||
|
} else {
|
||||||
|
rules = ssidOnDemandRules(option: ssidOption) + [NEOnDemandRuleConnect(interfaceType: nonWiFiInterfaceType)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tunnelProviderManager.onDemandRules = rules
|
||||||
|
tunnelProviderManager.isOnDemandEnabled = self != .off
|
||||||
|
}
|
||||||
|
|
||||||
|
init(from tunnelProviderManager: NETunnelProviderManager) {
|
||||||
|
let rules = tunnelProviderManager.onDemandRules ?? []
|
||||||
|
let activateOnDemandOption: ActivateOnDemandOption
|
||||||
|
switch rules.count {
|
||||||
|
case 0:
|
||||||
|
activateOnDemandOption = .off
|
||||||
|
case 1:
|
||||||
|
let rule = rules[0]
|
||||||
|
precondition(rule.action == .connect)
|
||||||
|
activateOnDemandOption = .anyInterface(.anySSID)
|
||||||
|
case 2:
|
||||||
|
let connectRule = rules.first(where: { $0.action == .connect })!
|
||||||
|
let disconnectRule = rules.first(where: { $0.action == .disconnect })!
|
||||||
|
if connectRule.interfaceTypeMatch == .wiFi && disconnectRule.interfaceTypeMatch == nonWiFiInterfaceType {
|
||||||
|
activateOnDemandOption = .wiFiInterfaceOnly(.anySSID)
|
||||||
|
} else if connectRule.interfaceTypeMatch == nonWiFiInterfaceType && disconnectRule.interfaceTypeMatch == .wiFi {
|
||||||
|
activateOnDemandOption = .nonWiFiInterfaceOnly
|
||||||
|
} else {
|
||||||
|
fatalError("Unexpected onDemandRules set on tunnel provider manager")
|
||||||
|
}
|
||||||
|
case 3:
|
||||||
|
let ssidRule = rules.first(where: { $0.interfaceTypeMatch == .wiFi && $0.ssidMatch != nil })!
|
||||||
|
let nonWiFiRule = rules.first(where: { $0.interfaceTypeMatch == nonWiFiInterfaceType })!
|
||||||
|
let ssids = ssidRule.ssidMatch!
|
||||||
|
switch (ssidRule.action, nonWiFiRule.action) {
|
||||||
|
case (.connect, .connect):
|
||||||
|
activateOnDemandOption = .anyInterface(.onlySpecificSSIDs(ssids))
|
||||||
|
case (.connect, .disconnect):
|
||||||
|
activateOnDemandOption = .wiFiInterfaceOnly(.onlySpecificSSIDs(ssids))
|
||||||
|
case (.disconnect, .connect):
|
||||||
|
activateOnDemandOption = .anyInterface(.exceptSpecificSSIDs(ssids))
|
||||||
|
case (.disconnect, .disconnect):
|
||||||
|
activateOnDemandOption = .wiFiInterfaceOnly(.exceptSpecificSSIDs(ssids))
|
||||||
|
default:
|
||||||
|
fatalError("Unexpected SSID onDemandRules set on tunnel provider manager")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
fatalError("Unexpected number of onDemandRules set on tunnel provider manager")
|
||||||
|
}
|
||||||
|
|
||||||
|
self = activateOnDemandOption
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension NEOnDemandRuleConnect {
|
||||||
|
convenience init(interfaceType: NEOnDemandRuleInterfaceType, ssids: [String]? = nil) {
|
||||||
|
self.init()
|
||||||
|
interfaceTypeMatch = interfaceType
|
||||||
|
ssidMatch = ssids
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension NEOnDemandRuleDisconnect {
|
||||||
|
convenience init(interfaceType: NEOnDemandRuleInterfaceType, ssids: [String]? = nil) {
|
||||||
|
self.init()
|
||||||
|
interfaceTypeMatch = interfaceType
|
||||||
|
ssidMatch = ssids
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func ssidOnDemandRules(option: ActivateOnDemandSSIDOption) -> [NEOnDemandRule] {
|
||||||
|
switch option {
|
||||||
|
case .anySSID:
|
||||||
|
return [NEOnDemandRuleConnect(interfaceType: .wiFi)]
|
||||||
|
case .onlySpecificSSIDs(let ssids):
|
||||||
|
assert(!ssids.isEmpty)
|
||||||
|
return [NEOnDemandRuleConnect(interfaceType: .wiFi, ssids: ssids),
|
||||||
|
NEOnDemandRuleDisconnect(interfaceType: .wiFi)]
|
||||||
|
case .exceptSpecificSSIDs(let ssids):
|
||||||
|
return [NEOnDemandRuleDisconnect(interfaceType: .wiFi, ssids: ssids),
|
||||||
|
NEOnDemandRuleConnect(interfaceType: .wiFi)]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
|
||||||
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
|
||||||
|
|
||||||
import NetworkExtension
|
|
||||||
|
|
||||||
struct ActivateOnDemandSetting {
|
|
||||||
var isActivateOnDemandEnabled: Bool
|
|
||||||
var activateOnDemandOption: ActivateOnDemandOption
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ActivateOnDemandOption {
|
|
||||||
case none // Valid only when isActivateOnDemandEnabled is false
|
|
||||||
case useOnDemandOverWiFiOnly
|
|
||||||
#if os(iOS)
|
|
||||||
case useOnDemandOverWiFiOrCellular
|
|
||||||
case useOnDemandOverCellularOnly
|
|
||||||
#elseif os(macOS)
|
|
||||||
case useOnDemandOverWiFiOrEthernet
|
|
||||||
case useOnDemandOverEthernetOnly
|
|
||||||
#else
|
|
||||||
#error("Unimplemented")
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ActivateOnDemandSetting {
|
|
||||||
func apply(on tunnelProviderManager: NETunnelProviderManager) {
|
|
||||||
tunnelProviderManager.isOnDemandEnabled = isActivateOnDemandEnabled
|
|
||||||
let rules: [NEOnDemandRule]?
|
|
||||||
let connectRule = NEOnDemandRuleConnect()
|
|
||||||
let disconnectRule = NEOnDemandRuleDisconnect()
|
|
||||||
switch activateOnDemandOption {
|
|
||||||
case .none:
|
|
||||||
rules = nil
|
|
||||||
#if os(iOS)
|
|
||||||
case .useOnDemandOverWiFiOrCellular:
|
|
||||||
rules = [connectRule]
|
|
||||||
case .useOnDemandOverWiFiOnly:
|
|
||||||
connectRule.interfaceTypeMatch = .wiFi
|
|
||||||
disconnectRule.interfaceTypeMatch = .cellular
|
|
||||||
rules = [connectRule, disconnectRule]
|
|
||||||
case .useOnDemandOverCellularOnly:
|
|
||||||
connectRule.interfaceTypeMatch = .cellular
|
|
||||||
disconnectRule.interfaceTypeMatch = .wiFi
|
|
||||||
rules = [connectRule, disconnectRule]
|
|
||||||
#elseif os(macOS)
|
|
||||||
case .useOnDemandOverWiFiOrEthernet:
|
|
||||||
rules = [connectRule]
|
|
||||||
case .useOnDemandOverWiFiOnly:
|
|
||||||
connectRule.interfaceTypeMatch = .wiFi
|
|
||||||
disconnectRule.interfaceTypeMatch = .ethernet
|
|
||||||
rules = [connectRule, disconnectRule]
|
|
||||||
case .useOnDemandOverEthernetOnly:
|
|
||||||
connectRule.interfaceTypeMatch = .ethernet
|
|
||||||
disconnectRule.interfaceTypeMatch = .wiFi
|
|
||||||
rules = [connectRule, disconnectRule]
|
|
||||||
#else
|
|
||||||
#error("Unimplemented")
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
tunnelProviderManager.onDemandRules = rules
|
|
||||||
}
|
|
||||||
|
|
||||||
init(from tunnelProviderManager: NETunnelProviderManager) {
|
|
||||||
let rules = tunnelProviderManager.onDemandRules ?? []
|
|
||||||
#if os(iOS)
|
|
||||||
let otherInterfaceType: NEOnDemandRuleInterfaceType = .cellular
|
|
||||||
let useWiFiOrOtherOption: ActivateOnDemandOption = .useOnDemandOverWiFiOrCellular
|
|
||||||
let useOtherOnlyOption: ActivateOnDemandOption = .useOnDemandOverCellularOnly
|
|
||||||
#elseif os(macOS)
|
|
||||||
let otherInterfaceType: NEOnDemandRuleInterfaceType = .ethernet
|
|
||||||
let useWiFiOrOtherOption: ActivateOnDemandOption = .useOnDemandOverWiFiOrEthernet
|
|
||||||
let useOtherOnlyOption: ActivateOnDemandOption = .useOnDemandOverEthernetOnly
|
|
||||||
#else
|
|
||||||
#error("Unimplemented")
|
|
||||||
#endif
|
|
||||||
let activateOnDemandOption: ActivateOnDemandOption
|
|
||||||
switch rules.count {
|
|
||||||
case 0:
|
|
||||||
activateOnDemandOption = .none
|
|
||||||
case 1:
|
|
||||||
let rule = rules[0]
|
|
||||||
precondition(rule.action == .connect)
|
|
||||||
activateOnDemandOption = useWiFiOrOtherOption
|
|
||||||
case 2:
|
|
||||||
let connectRule = rules.first(where: { $0.action == .connect })!
|
|
||||||
let disconnectRule = rules.first(where: { $0.action == .disconnect })!
|
|
||||||
if connectRule.interfaceTypeMatch == .wiFi && disconnectRule.interfaceTypeMatch == otherInterfaceType {
|
|
||||||
activateOnDemandOption = .useOnDemandOverWiFiOnly
|
|
||||||
} else if connectRule.interfaceTypeMatch == otherInterfaceType && disconnectRule.interfaceTypeMatch == .wiFi {
|
|
||||||
activateOnDemandOption = useOtherOnlyOption
|
|
||||||
} else {
|
|
||||||
fatalError("Unexpected onDemandRules set on tunnel provider manager")
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
fatalError("Unexpected number of onDemandRules set on tunnel provider manager")
|
|
||||||
}
|
|
||||||
|
|
||||||
self.activateOnDemandOption = activateOnDemandOption
|
|
||||||
if activateOnDemandOption == .none {
|
|
||||||
isActivateOnDemandEnabled = false
|
|
||||||
} else {
|
|
||||||
isActivateOnDemandEnabled = tunnelProviderManager.isOnDemandEnabled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ActivateOnDemandSetting {
|
|
||||||
static var defaultSetting = ActivateOnDemandSetting(isActivateOnDemandEnabled: false, activateOnDemandOption: .none)
|
|
||||||
}
|
|
||||||
@@ -27,6 +27,8 @@ import NetworkExtension
|
|||||||
self = .reasserting
|
self = .reasserting
|
||||||
case .invalid:
|
case .invalid:
|
||||||
self = .inactive
|
self = .inactive
|
||||||
|
@unknown default:
|
||||||
|
fatalError()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,6 +56,8 @@ extension NEVPNStatus: CustomDebugStringConvertible {
|
|||||||
case .disconnecting: return "disconnecting"
|
case .disconnecting: return "disconnecting"
|
||||||
case .reasserting: return "reasserting"
|
case .reasserting: return "reasserting"
|
||||||
case .invalid: return "invalid"
|
case .invalid: return "invalid"
|
||||||
|
@unknown default:
|
||||||
|
fatalError()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class TunnelsManager {
|
|||||||
private var configurationsObservationToken: AnyObject?
|
private var configurationsObservationToken: AnyObject?
|
||||||
|
|
||||||
init(tunnelProviders: [NETunnelProviderManager]) {
|
init(tunnelProviders: [NETunnelProviderManager]) {
|
||||||
tunnels = tunnelProviders.map { TunnelContainer(tunnel: $0) }.sorted { $0.name < $1.name }
|
tunnels = tunnelProviders.map { TunnelContainer(tunnel: $0) }.sorted { TunnelsManager.tunnelNameIsLessThan($0.name, $1.name) }
|
||||||
startObservingTunnelStatuses()
|
startObservingTunnelStatuses()
|
||||||
startObservingTunnelConfigurations()
|
startObservingTunnelConfigurations()
|
||||||
}
|
}
|
||||||
@@ -47,11 +47,18 @@ class TunnelsManager {
|
|||||||
var tunnelManagers = managers ?? []
|
var tunnelManagers = managers ?? []
|
||||||
var refs: Set<Data> = []
|
var refs: Set<Data> = []
|
||||||
for (index, tunnelManager) in tunnelManagers.enumerated().reversed() {
|
for (index, tunnelManager) in tunnelManagers.enumerated().reversed() {
|
||||||
let proto = tunnelManager.protocolConfiguration as? NETunnelProviderProtocol
|
guard let proto = tunnelManager.protocolConfiguration as? NETunnelProviderProtocol else { continue }
|
||||||
if proto?.migrateConfigurationIfNeeded(called: tunnelManager.localizedDescription ?? "unknown") ?? false {
|
if proto.migrateConfigurationIfNeeded(called: tunnelManager.localizedDescription ?? "unknown") {
|
||||||
tunnelManager.saveToPreferences { _ in }
|
tunnelManager.saveToPreferences { _ in }
|
||||||
}
|
}
|
||||||
if let ref = proto?.verifyConfigurationReference() {
|
#if os(iOS)
|
||||||
|
let passwordRef = proto.verifyConfigurationReference() ? proto.passwordReference : nil
|
||||||
|
#elseif os(macOS)
|
||||||
|
let passwordRef = proto.passwordReference // To handle multiple users in macOS, we skip verifying
|
||||||
|
#else
|
||||||
|
#error("Unimplemented")
|
||||||
|
#endif
|
||||||
|
if let ref = passwordRef {
|
||||||
refs.insert(ref)
|
refs.insert(ref)
|
||||||
} else {
|
} else {
|
||||||
tunnelManager.removeFromPreferences { _ in }
|
tunnelManager.removeFromPreferences { _ in }
|
||||||
@@ -71,28 +78,33 @@ class TunnelsManager {
|
|||||||
let loadedTunnelProviders = managers ?? []
|
let loadedTunnelProviders = managers ?? []
|
||||||
|
|
||||||
for (index, currentTunnel) in self.tunnels.enumerated().reversed() {
|
for (index, currentTunnel) in self.tunnels.enumerated().reversed() {
|
||||||
if !loadedTunnelProviders.contains(where: { $0.tunnelConfiguration == currentTunnel.tunnelConfiguration }) {
|
if !loadedTunnelProviders.contains(where: { $0.isEquivalentTo(currentTunnel) }) {
|
||||||
// Tunnel was deleted outside the app
|
// Tunnel was deleted outside the app
|
||||||
self.tunnels.remove(at: index)
|
self.tunnels.remove(at: index)
|
||||||
self.tunnelsListDelegate?.tunnelRemoved(at: index, tunnel: currentTunnel)
|
self.tunnelsListDelegate?.tunnelRemoved(at: index, tunnel: currentTunnel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for loadedTunnelProvider in loadedTunnelProviders {
|
for loadedTunnelProvider in loadedTunnelProviders {
|
||||||
if let matchingTunnel = self.tunnels.first(where: { $0.tunnelConfiguration == loadedTunnelProvider.tunnelConfiguration }) {
|
if let matchingTunnel = self.tunnels.first(where: { loadedTunnelProvider.isEquivalentTo($0) }) {
|
||||||
matchingTunnel.tunnelProvider = loadedTunnelProvider
|
matchingTunnel.tunnelProvider = loadedTunnelProvider
|
||||||
matchingTunnel.refreshStatus()
|
matchingTunnel.refreshStatus()
|
||||||
} else {
|
} else {
|
||||||
// Tunnel was added outside the app
|
// Tunnel was added outside the app
|
||||||
|
if let proto = loadedTunnelProvider.protocolConfiguration as? NETunnelProviderProtocol {
|
||||||
|
if proto.migrateConfigurationIfNeeded(called: loadedTunnelProvider.localizedDescription ?? "unknown") {
|
||||||
|
loadedTunnelProvider.saveToPreferences { _ in }
|
||||||
|
}
|
||||||
|
}
|
||||||
let tunnel = TunnelContainer(tunnel: loadedTunnelProvider)
|
let tunnel = TunnelContainer(tunnel: loadedTunnelProvider)
|
||||||
self.tunnels.append(tunnel)
|
self.tunnels.append(tunnel)
|
||||||
self.tunnels.sort { $0.name < $1.name }
|
self.tunnels.sort { TunnelsManager.tunnelNameIsLessThan($0.name, $1.name) }
|
||||||
self.tunnelsListDelegate?.tunnelAdded(at: self.tunnels.firstIndex(of: tunnel)!)
|
self.tunnelsListDelegate?.tunnelAdded(at: self.tunnels.firstIndex(of: tunnel)!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func add(tunnelConfiguration: TunnelConfiguration, activateOnDemandSetting: ActivateOnDemandSetting = ActivateOnDemandSetting.defaultSetting, completionHandler: @escaping (WireGuardResult<TunnelContainer>) -> Void) {
|
func add(tunnelConfiguration: TunnelConfiguration, onDemandOption: ActivateOnDemandOption = .off, completionHandler: @escaping (WireGuardResult<TunnelContainer>) -> Void) {
|
||||||
let tunnelName = tunnelConfiguration.name ?? ""
|
let tunnelName = tunnelConfiguration.name ?? ""
|
||||||
if tunnelName.isEmpty {
|
if tunnelName.isEmpty {
|
||||||
completionHandler(.failure(TunnelsManagerError.tunnelNameEmpty))
|
completionHandler(.failure(TunnelsManagerError.tunnelNameEmpty))
|
||||||
@@ -108,7 +120,9 @@ class TunnelsManager {
|
|||||||
tunnelProviderManager.setTunnelConfiguration(tunnelConfiguration)
|
tunnelProviderManager.setTunnelConfiguration(tunnelConfiguration)
|
||||||
tunnelProviderManager.isEnabled = true
|
tunnelProviderManager.isEnabled = true
|
||||||
|
|
||||||
activateOnDemandSetting.apply(on: tunnelProviderManager)
|
onDemandOption.apply(on: tunnelProviderManager)
|
||||||
|
|
||||||
|
let activeTunnel = tunnels.first { $0.status == .active || $0.status == .activating }
|
||||||
|
|
||||||
tunnelProviderManager.saveToPreferences { [weak self] error in
|
tunnelProviderManager.saveToPreferences { [weak self] error in
|
||||||
guard error == nil else {
|
guard error == nil else {
|
||||||
@@ -120,32 +134,47 @@ class TunnelsManager {
|
|||||||
|
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
// HACK: In iOS, adding a tunnel causes deactivation of any currently active tunnel.
|
||||||
|
// This is an ugly hack to reactivate the tunnel that has been deactivated like that.
|
||||||
|
if let activeTunnel = activeTunnel {
|
||||||
|
if activeTunnel.status == .inactive || activeTunnel.status == .deactivating {
|
||||||
|
self.startActivation(of: activeTunnel)
|
||||||
|
}
|
||||||
|
if activeTunnel.status == .active || activeTunnel.status == .activating {
|
||||||
|
activeTunnel.status = .restarting
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
let tunnel = TunnelContainer(tunnel: tunnelProviderManager)
|
let tunnel = TunnelContainer(tunnel: tunnelProviderManager)
|
||||||
self.tunnels.append(tunnel)
|
self.tunnels.append(tunnel)
|
||||||
self.tunnels.sort { $0.name < $1.name }
|
self.tunnels.sort { TunnelsManager.tunnelNameIsLessThan($0.name, $1.name) }
|
||||||
self.tunnelsListDelegate?.tunnelAdded(at: self.tunnels.firstIndex(of: tunnel)!)
|
self.tunnelsListDelegate?.tunnelAdded(at: self.tunnels.firstIndex(of: tunnel)!)
|
||||||
completionHandler(.success(tunnel))
|
completionHandler(.success(tunnel))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addMultiple(tunnelConfigurations: [TunnelConfiguration], completionHandler: @escaping (UInt) -> Void) {
|
func addMultiple(tunnelConfigurations: [TunnelConfiguration], completionHandler: @escaping (UInt, TunnelsManagerError?) -> Void) {
|
||||||
addMultiple(tunnelConfigurations: ArraySlice(tunnelConfigurations), numberSuccessful: 0, completionHandler: completionHandler)
|
addMultiple(tunnelConfigurations: ArraySlice(tunnelConfigurations), numberSuccessful: 0, lastError: nil, completionHandler: completionHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func addMultiple(tunnelConfigurations: ArraySlice<TunnelConfiguration>, numberSuccessful: UInt, completionHandler: @escaping (UInt) -> Void) {
|
private func addMultiple(tunnelConfigurations: ArraySlice<TunnelConfiguration>, numberSuccessful: UInt, lastError: TunnelsManagerError?, completionHandler: @escaping (UInt, TunnelsManagerError?) -> Void) {
|
||||||
guard let head = tunnelConfigurations.first else {
|
guard let head = tunnelConfigurations.first else {
|
||||||
completionHandler(numberSuccessful)
|
completionHandler(numberSuccessful, lastError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let tail = tunnelConfigurations.dropFirst()
|
let tail = tunnelConfigurations.dropFirst()
|
||||||
add(tunnelConfiguration: head) { [weak self, tail] result in
|
add(tunnelConfiguration: head) { [weak self, tail] result in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self?.addMultiple(tunnelConfigurations: tail, numberSuccessful: numberSuccessful + (result.isSuccess ? 1 : 0), completionHandler: completionHandler)
|
let numberSuccessful = numberSuccessful + (result.isSuccess ? 1 : 0)
|
||||||
|
let lastError = lastError ?? (result.error as? TunnelsManagerError)
|
||||||
|
self?.addMultiple(tunnelConfigurations: tail, numberSuccessful: numberSuccessful, lastError: lastError, completionHandler: completionHandler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func modify(tunnel: TunnelContainer, tunnelConfiguration: TunnelConfiguration, activateOnDemandSetting: ActivateOnDemandSetting, completionHandler: @escaping (TunnelsManagerError?) -> Void) {
|
func modify(tunnel: TunnelContainer, tunnelConfiguration: TunnelConfiguration, onDemandOption: ActivateOnDemandOption, completionHandler: @escaping (TunnelsManagerError?) -> Void) {
|
||||||
let tunnelName = tunnelConfiguration.name ?? ""
|
let tunnelName = tunnelConfiguration.name ?? ""
|
||||||
if tunnelName.isEmpty {
|
if tunnelName.isEmpty {
|
||||||
completionHandler(TunnelsManagerError.tunnelNameEmpty)
|
completionHandler(TunnelsManagerError.tunnelNameEmpty)
|
||||||
@@ -162,11 +191,15 @@ class TunnelsManager {
|
|||||||
tunnel.name = tunnelName
|
tunnel.name = tunnelName
|
||||||
}
|
}
|
||||||
|
|
||||||
tunnelProviderManager.setTunnelConfiguration(tunnelConfiguration)
|
var isTunnelConfigurationChanged = false
|
||||||
|
if tunnelProviderManager.tunnelConfiguration != tunnelConfiguration {
|
||||||
|
tunnelProviderManager.setTunnelConfiguration(tunnelConfiguration)
|
||||||
|
isTunnelConfigurationChanged = true
|
||||||
|
}
|
||||||
tunnelProviderManager.isEnabled = true
|
tunnelProviderManager.isEnabled = true
|
||||||
|
|
||||||
let isActivatingOnDemand = !tunnelProviderManager.isOnDemandEnabled && activateOnDemandSetting.isActivateOnDemandEnabled
|
let isActivatingOnDemand = !tunnelProviderManager.isOnDemandEnabled && onDemandOption != .off
|
||||||
activateOnDemandSetting.apply(on: tunnelProviderManager)
|
onDemandOption.apply(on: tunnelProviderManager)
|
||||||
|
|
||||||
tunnelProviderManager.saveToPreferences { [weak self] error in
|
tunnelProviderManager.saveToPreferences { [weak self] error in
|
||||||
guard error == nil else {
|
guard error == nil else {
|
||||||
@@ -178,16 +211,18 @@ class TunnelsManager {
|
|||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
if isNameChanged {
|
if isNameChanged {
|
||||||
let oldIndex = self.tunnels.firstIndex(of: tunnel)!
|
let oldIndex = self.tunnels.firstIndex(of: tunnel)!
|
||||||
self.tunnels.sort { $0.name < $1.name }
|
self.tunnels.sort { TunnelsManager.tunnelNameIsLessThan($0.name, $1.name) }
|
||||||
let newIndex = self.tunnels.firstIndex(of: tunnel)!
|
let newIndex = self.tunnels.firstIndex(of: tunnel)!
|
||||||
self.tunnelsListDelegate?.tunnelMoved(from: oldIndex, to: newIndex)
|
self.tunnelsListDelegate?.tunnelMoved(from: oldIndex, to: newIndex)
|
||||||
}
|
}
|
||||||
self.tunnelsListDelegate?.tunnelModified(at: self.tunnels.firstIndex(of: tunnel)!)
|
self.tunnelsListDelegate?.tunnelModified(at: self.tunnels.firstIndex(of: tunnel)!)
|
||||||
|
|
||||||
if tunnel.status == .active || tunnel.status == .activating || tunnel.status == .reasserting {
|
if isTunnelConfigurationChanged {
|
||||||
// Turn off the tunnel, and then turn it back on, so the changes are made effective
|
if tunnel.status == .active || tunnel.status == .activating || tunnel.status == .reasserting {
|
||||||
tunnel.status = .restarting
|
// Turn off the tunnel, and then turn it back on, so the changes are made effective
|
||||||
(tunnel.tunnelProvider.connection as? NETunnelProviderSession)?.stopTunnel()
|
tunnel.status = .restarting
|
||||||
|
(tunnel.tunnelProvider.connection as? NETunnelProviderSession)?.stopTunnel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if isActivatingOnDemand {
|
if isActivatingOnDemand {
|
||||||
@@ -210,7 +245,9 @@ class TunnelsManager {
|
|||||||
|
|
||||||
func remove(tunnel: TunnelContainer, completionHandler: @escaping (TunnelsManagerError?) -> Void) {
|
func remove(tunnel: TunnelContainer, completionHandler: @escaping (TunnelsManagerError?) -> Void) {
|
||||||
let tunnelProviderManager = tunnel.tunnelProvider
|
let tunnelProviderManager = tunnel.tunnelProvider
|
||||||
(tunnelProviderManager.protocolConfiguration as? NETunnelProviderProtocol)?.destroyConfigurationReference()
|
if tunnel.isTunnelConfigurationAvailableInKeychain {
|
||||||
|
(tunnelProviderManager.protocolConfiguration as? NETunnelProviderProtocol)?.destroyConfigurationReference()
|
||||||
|
}
|
||||||
|
|
||||||
tunnelProviderManager.removeFromPreferences { [weak self] error in
|
tunnelProviderManager.removeFromPreferences { [weak self] error in
|
||||||
guard error == nil else {
|
guard error == nil else {
|
||||||
@@ -218,8 +255,7 @@ class TunnelsManager {
|
|||||||
completionHandler(TunnelsManagerError.systemErrorOnRemoveTunnel(systemError: error!))
|
completionHandler(TunnelsManagerError.systemErrorOnRemoveTunnel(systemError: error!))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let self = self {
|
if let self = self, let index = self.tunnels.firstIndex(of: tunnel) {
|
||||||
let index = self.tunnels.firstIndex(of: tunnel)!
|
|
||||||
self.tunnels.remove(at: index)
|
self.tunnels.remove(at: index)
|
||||||
self.tunnelsListDelegate?.tunnelRemoved(at: index, tunnel: tunnel)
|
self.tunnelsListDelegate?.tunnelRemoved(at: index, tunnel: tunnel)
|
||||||
}
|
}
|
||||||
@@ -227,6 +263,27 @@ class TunnelsManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func removeMultiple(tunnels: [TunnelContainer], completionHandler: @escaping (TunnelsManagerError?) -> Void) {
|
||||||
|
removeMultiple(tunnels: ArraySlice(tunnels), completionHandler: completionHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func removeMultiple(tunnels: ArraySlice<TunnelContainer>, completionHandler: @escaping (TunnelsManagerError?) -> Void) {
|
||||||
|
guard let head = tunnels.first else {
|
||||||
|
completionHandler(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let tail = tunnels.dropFirst()
|
||||||
|
remove(tunnel: head) { [weak self, tail] error in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
if let error = error {
|
||||||
|
completionHandler(error)
|
||||||
|
} else {
|
||||||
|
self?.removeMultiple(tunnels: tail, completionHandler: completionHandler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func numberOfTunnels() -> Int {
|
func numberOfTunnels() -> Int {
|
||||||
return tunnels.count
|
return tunnels.count
|
||||||
}
|
}
|
||||||
@@ -313,11 +370,7 @@ class TunnelsManager {
|
|||||||
guard let self = self,
|
guard let self = self,
|
||||||
let session = statusChangeNotification.object as? NETunnelProviderSession,
|
let session = statusChangeNotification.object as? NETunnelProviderSession,
|
||||||
let tunnelProvider = session.manager as? NETunnelProviderManager,
|
let tunnelProvider = session.manager as? NETunnelProviderManager,
|
||||||
let tunnelConfiguration = tunnelProvider.tunnelConfiguration,
|
let tunnel = self.tunnels.first(where: { $0.tunnelProvider == tunnelProvider }) else { return }
|
||||||
let tunnel = self.tunnels.first(where: { $0.tunnelConfiguration == tunnelConfiguration }) else { return }
|
|
||||||
if tunnel.tunnelProvider != tunnelProvider {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
wg_log(.debug, message: "Tunnel '\(tunnel.name)' connection status changed to '\(tunnel.tunnelProvider.connection.status)'")
|
wg_log(.debug, message: "Tunnel '\(tunnel.name)' connection status changed to '\(tunnel.tunnelProvider.connection.status)'")
|
||||||
|
|
||||||
@@ -356,6 +409,9 @@ class TunnelsManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func tunnelNameIsLessThan(_ a: String, _ b: String) -> Bool {
|
||||||
|
return a.compare(b, options: [.caseInsensitive, .diacriticInsensitive, .widthInsensitive, .numeric]) == .orderedAscending
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func lastErrorTextFromNetworkExtension(for tunnel: TunnelContainer) -> (title: String, message: String)? {
|
private func lastErrorTextFromNetworkExtension(for tunnel: TunnelContainer) -> (title: String, message: String)? {
|
||||||
@@ -408,8 +464,12 @@ class TunnelContainer: NSObject {
|
|||||||
return tunnelProvider.tunnelConfiguration
|
return tunnelProvider.tunnelConfiguration
|
||||||
}
|
}
|
||||||
|
|
||||||
var activateOnDemandSetting: ActivateOnDemandSetting {
|
var isTunnelConfigurationAvailableInKeychain: Bool {
|
||||||
return ActivateOnDemandSetting(from: tunnelProvider)
|
return tunnelProvider.isTunnelConfigurationAvailableInKeychain
|
||||||
|
}
|
||||||
|
|
||||||
|
var onDemandOption: ActivateOnDemandOption {
|
||||||
|
return ActivateOnDemandOption(from: tunnelProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(tunnel: NETunnelProviderManager) {
|
init(tunnel: NETunnelProviderManager) {
|
||||||
@@ -426,7 +486,7 @@ class TunnelContainer: NSObject {
|
|||||||
completionHandler(tunnelConfiguration)
|
completionHandler(tunnelConfiguration)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard nil != (try? session.sendProviderMessage(Data(bytes: [ 0 ]), responseHandler: {
|
guard nil != (try? session.sendProviderMessage(Data([ UInt8(0) ]), responseHandler: {
|
||||||
guard self.status != .inactive, let data = $0, let base = self.tunnelConfiguration, let settings = String(data: data, encoding: .utf8) else {
|
guard self.status != .inactive, let data = $0, let base = self.tunnelConfiguration, let settings = String(data: data, encoding: .utf8) else {
|
||||||
completionHandler(self.tunnelConfiguration)
|
completionHandler(self.tunnelConfiguration)
|
||||||
return
|
return
|
||||||
@@ -439,7 +499,7 @@ class TunnelContainer: NSObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func refreshStatus() {
|
func refreshStatus() {
|
||||||
if (status == .restarting) && (tunnelProvider.connection.status == .disconnected || tunnelProvider.connection.status == .disconnecting) {
|
if status == .restarting {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
status = TunnelStatus(from: tunnelProvider.connection.status)
|
status = TunnelStatus(from: tunnelProvider.connection.status)
|
||||||
@@ -520,7 +580,18 @@ class TunnelContainer: NSObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension NETunnelProviderManager {
|
extension NETunnelProviderManager {
|
||||||
|
private static var cachedIsConfigAvailableInKeychainKey: UInt8 = 0
|
||||||
private static var cachedConfigKey: UInt8 = 0
|
private static var cachedConfigKey: UInt8 = 0
|
||||||
|
|
||||||
|
var isTunnelConfigurationAvailableInKeychain: Bool {
|
||||||
|
if let cachedNumber = objc_getAssociatedObject(self, &NETunnelProviderManager.cachedIsConfigAvailableInKeychainKey) as? NSNumber {
|
||||||
|
return cachedNumber.boolValue
|
||||||
|
}
|
||||||
|
let isAvailable = (protocolConfiguration as? NETunnelProviderProtocol)?.verifyConfigurationReference() ?? false
|
||||||
|
objc_setAssociatedObject(self, &NETunnelProviderManager.cachedIsConfigAvailableInKeychainKey, NSNumber(value: isAvailable), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||||
|
return isAvailable
|
||||||
|
}
|
||||||
|
|
||||||
var tunnelConfiguration: TunnelConfiguration? {
|
var tunnelConfiguration: TunnelConfiguration? {
|
||||||
if let cached = objc_getAssociatedObject(self, &NETunnelProviderManager.cachedConfigKey) as? TunnelConfiguration {
|
if let cached = objc_getAssociatedObject(self, &NETunnelProviderManager.cachedConfigKey) as? TunnelConfiguration {
|
||||||
return cached
|
return cached
|
||||||
@@ -536,5 +607,17 @@ extension NETunnelProviderManager {
|
|||||||
protocolConfiguration = NETunnelProviderProtocol(tunnelConfiguration: tunnelConfiguration, previouslyFrom: protocolConfiguration)
|
protocolConfiguration = NETunnelProviderProtocol(tunnelConfiguration: tunnelConfiguration, previouslyFrom: protocolConfiguration)
|
||||||
localizedDescription = tunnelConfiguration.name
|
localizedDescription = tunnelConfiguration.name
|
||||||
objc_setAssociatedObject(self, &NETunnelProviderManager.cachedConfigKey, tunnelConfiguration, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
objc_setAssociatedObject(self, &NETunnelProviderManager.cachedConfigKey, tunnelConfiguration, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||||
|
objc_setAssociatedObject(self, &NETunnelProviderManager.cachedIsConfigAvailableInKeychainKey, NSNumber(value: true), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isEquivalentTo(_ tunnel: TunnelContainer) -> Bool {
|
||||||
|
switch (isTunnelConfigurationAvailableInKeychain, tunnel.isTunnelConfigurationAvailableInKeychain) {
|
||||||
|
case (true, true):
|
||||||
|
return tunnelConfiguration == tunnel.tunnelConfiguration
|
||||||
|
case (false, false):
|
||||||
|
return localizedDescription == tunnel.name
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,198 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
class ActivateOnDemandViewModel {
|
||||||
|
enum OnDemandField {
|
||||||
|
case onDemand
|
||||||
|
case nonWiFiInterface
|
||||||
|
case wiFiInterface
|
||||||
|
case ssid
|
||||||
|
|
||||||
|
var localizedUIString: String {
|
||||||
|
switch self {
|
||||||
|
case .onDemand:
|
||||||
|
return tr("tunnelOnDemandKey")
|
||||||
|
case .nonWiFiInterface:
|
||||||
|
#if os(iOS)
|
||||||
|
return tr("tunnelOnDemandCellular")
|
||||||
|
#elseif os(macOS)
|
||||||
|
return tr("tunnelOnDemandEthernet")
|
||||||
|
#else
|
||||||
|
#error("Unimplemented")
|
||||||
|
#endif
|
||||||
|
case .wiFiInterface: return tr("tunnelOnDemandWiFi")
|
||||||
|
case .ssid: return tr("tunnelOnDemandSSIDsKey")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OnDemandSSIDOption {
|
||||||
|
case anySSID
|
||||||
|
case onlySpecificSSIDs
|
||||||
|
case exceptSpecificSSIDs
|
||||||
|
|
||||||
|
var localizedUIString: String {
|
||||||
|
switch self {
|
||||||
|
case .anySSID: return tr("tunnelOnDemandAnySSID")
|
||||||
|
case .onlySpecificSSIDs: return tr("tunnelOnDemandOnlyTheseSSIDs")
|
||||||
|
case .exceptSpecificSSIDs: return tr("tunnelOnDemandExceptTheseSSIDs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var isNonWiFiInterfaceEnabled = false
|
||||||
|
var isWiFiInterfaceEnabled = false
|
||||||
|
var selectedSSIDs = [String]()
|
||||||
|
var ssidOption: OnDemandSSIDOption = .anySSID
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toOnDemandOption() -> ActivateOnDemandOption {
|
||||||
|
switch (isWiFiInterfaceEnabled, isNonWiFiInterfaceEnabled) {
|
||||||
|
case (false, false):
|
||||||
|
return .off
|
||||||
|
case (false, true):
|
||||||
|
return .nonWiFiInterfaceOnly
|
||||||
|
case (true, false):
|
||||||
|
return .wiFiInterfaceOnly(toSSIDOption())
|
||||||
|
case (true, true):
|
||||||
|
return .anyInterface(toSSIDOption())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ActivateOnDemandViewModel {
|
||||||
|
func isEnabled(field: OnDemandField) -> Bool {
|
||||||
|
switch field {
|
||||||
|
case .nonWiFiInterface:
|
||||||
|
return isNonWiFiInterfaceEnabled
|
||||||
|
case .wiFiInterface:
|
||||||
|
return isWiFiInterfaceEnabled
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setEnabled(field: OnDemandField, isEnabled: Bool) {
|
||||||
|
switch field {
|
||||||
|
case .nonWiFiInterface:
|
||||||
|
isNonWiFiInterfaceEnabled = isEnabled
|
||||||
|
case .wiFiInterface:
|
||||||
|
isWiFiInterfaceEnabled = isEnabled
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ActivateOnDemandViewModel {
|
||||||
|
var localizedInterfaceDescription: String {
|
||||||
|
switch (isWiFiInterfaceEnabled, isNonWiFiInterfaceEnabled) {
|
||||||
|
case (false, false):
|
||||||
|
return tr("tunnelOnDemandOptionOff")
|
||||||
|
case (true, false):
|
||||||
|
return tr("tunnelOnDemandOptionWiFiOnly")
|
||||||
|
case (false, true):
|
||||||
|
#if os(iOS)
|
||||||
|
return tr("tunnelOnDemandOptionCellularOnly")
|
||||||
|
#elseif os(macOS)
|
||||||
|
return tr("tunnelOnDemandOptionEthernetOnly")
|
||||||
|
#else
|
||||||
|
#error("Unimplemented")
|
||||||
|
#endif
|
||||||
|
case (true, true):
|
||||||
|
#if os(iOS)
|
||||||
|
return tr("tunnelOnDemandOptionWiFiOrCellular")
|
||||||
|
#elseif os(macOS)
|
||||||
|
return tr("tunnelOnDemandOptionWiFiOrEthernet")
|
||||||
|
#else
|
||||||
|
#error("Unimplemented")
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var localizedSSIDDescription: String {
|
||||||
|
guard isWiFiInterfaceEnabled else { return "" }
|
||||||
|
switch ssidOption {
|
||||||
|
case .anySSID: return tr("tunnelOnDemandAnySSID")
|
||||||
|
case .onlySpecificSSIDs:
|
||||||
|
if selectedSSIDs.count == 1 {
|
||||||
|
return tr(format: "tunnelOnDemandOnlySSID (%d)", selectedSSIDs.count)
|
||||||
|
} else {
|
||||||
|
return tr(format: "tunnelOnDemandOnlySSIDs (%d)", selectedSSIDs.count)
|
||||||
|
}
|
||||||
|
case .exceptSpecificSSIDs:
|
||||||
|
if selectedSSIDs.count == 1 {
|
||||||
|
return tr(format: "tunnelOnDemandExceptSSID (%d)", selectedSSIDs.count)
|
||||||
|
} else {
|
||||||
|
return tr(format: "tunnelOnDemandExceptSSIDs (%d)", selectedSSIDs.count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fixSSIDOption() {
|
||||||
|
selectedSSIDs = uniquifiedNonEmptySelectedSSIDs()
|
||||||
|
if selectedSSIDs.isEmpty {
|
||||||
|
ssidOption = .anySSID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension ActivateOnDemandViewModel {
|
||||||
|
func ssidViewModel(from ssidOption: ActivateOnDemandSSIDOption) -> (OnDemandSSIDOption, [String]) {
|
||||||
|
switch ssidOption {
|
||||||
|
case .anySSID:
|
||||||
|
return (.anySSID, [])
|
||||||
|
case .onlySpecificSSIDs(let ssids):
|
||||||
|
return (.onlySpecificSSIDs, ssids)
|
||||||
|
case .exceptSpecificSSIDs(let ssids):
|
||||||
|
return (.exceptSpecificSSIDs, ssids)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toSSIDOption() -> ActivateOnDemandSSIDOption {
|
||||||
|
switch ssidOption {
|
||||||
|
case .anySSID:
|
||||||
|
return .anySSID
|
||||||
|
case .onlySpecificSSIDs:
|
||||||
|
let ssids = uniquifiedNonEmptySelectedSSIDs()
|
||||||
|
return ssids.isEmpty ? .anySSID : .onlySpecificSSIDs(selectedSSIDs)
|
||||||
|
case .exceptSpecificSSIDs:
|
||||||
|
let ssids = uniquifiedNonEmptySelectedSSIDs()
|
||||||
|
return ssids.isEmpty ? .anySSID : .exceptSpecificSSIDs(selectedSSIDs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func uniquifiedNonEmptySelectedSSIDs() -> [String] {
|
||||||
|
let nonEmptySSIDs = selectedSSIDs.filter { !$0.isEmpty }
|
||||||
|
var seenSSIDs = Set<String>()
|
||||||
|
var uniquified = [String]()
|
||||||
|
for ssid in nonEmptySSIDs {
|
||||||
|
guard !seenSSIDs.contains(ssid) else { continue }
|
||||||
|
uniquified.append(ssid)
|
||||||
|
seenSSIDs.insert(ssid)
|
||||||
|
}
|
||||||
|
return uniquified
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class LogViewHelper {
|
||||||
|
var log: OpaquePointer
|
||||||
|
var cursor: UInt32 = UINT32_MAX
|
||||||
|
static let formatOptions: ISO8601DateFormatter.Options = [
|
||||||
|
.withYear, .withMonth, .withDay, .withTime,
|
||||||
|
.withDashSeparatorInDate, .withColonSeparatorInTime, .withSpaceBetweenDateAndTime,
|
||||||
|
.withFractionalSeconds
|
||||||
|
]
|
||||||
|
|
||||||
|
struct LogEntry {
|
||||||
|
let timestamp: String
|
||||||
|
let message: String
|
||||||
|
|
||||||
|
func text() -> String {
|
||||||
|
return timestamp + " " + message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LogEntries {
|
||||||
|
var entries: [LogEntry] = []
|
||||||
|
}
|
||||||
|
|
||||||
|
init?(logFilePath: String?) {
|
||||||
|
guard let logFilePath = logFilePath else { return nil }
|
||||||
|
guard let log = open_log(logFilePath) else { return nil }
|
||||||
|
self.log = log
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
close_log(self.log)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchLogEntriesSinceLastFetch(completion: @escaping ([LogViewHelper.LogEntry]) -> Void) {
|
||||||
|
var logEntries = LogEntries()
|
||||||
|
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
let newCursor = view_lines_from_cursor(self.log, self.cursor, &logEntries) { cStr, timestamp, ctx in
|
||||||
|
let message = cStr != nil ? String(cString: cStr!) : ""
|
||||||
|
let date = Date(timeIntervalSince1970: Double(timestamp) / 1000000000)
|
||||||
|
let dateString = ISO8601DateFormatter.string(from: date, timeZone: TimeZone.current, formatOptions: LogViewHelper.formatOptions)
|
||||||
|
if let logEntries = ctx?.bindMemory(to: LogEntries.self, capacity: 1) {
|
||||||
|
logEntries.pointee.entries.append(LogEntry(timestamp: dateString, message: message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.cursor = newCursor
|
||||||
|
completion(logEntries.entries)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,40 +4,81 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class TunnelImporter {
|
class TunnelImporter {
|
||||||
static func importFromFile(url: URL, into tunnelsManager: TunnelsManager, sourceVC: AnyObject?, errorPresenterType: ErrorPresenterProtocol.Type, completionHandler: (() -> Void)? = nil) {
|
static func importFromFile(urls: [URL], into tunnelsManager: TunnelsManager, sourceVC: AnyObject?, errorPresenterType: ErrorPresenterProtocol.Type, completionHandler: (() -> Void)? = nil) {
|
||||||
if url.pathExtension == "zip" {
|
guard !urls.isEmpty else {
|
||||||
ZipImporter.importConfigFiles(from: url) { result in
|
completionHandler?()
|
||||||
if let error = result.error {
|
return
|
||||||
errorPresenterType.showErrorAlert(error: error, from: sourceVC)
|
}
|
||||||
return
|
let dispatchGroup = DispatchGroup()
|
||||||
|
var configs = [TunnelConfiguration?]()
|
||||||
|
var lastFileImportErrorText: (title: String, message: String)?
|
||||||
|
for url in urls {
|
||||||
|
if url.pathExtension.lowercased() == "zip" {
|
||||||
|
dispatchGroup.enter()
|
||||||
|
ZipImporter.importConfigFiles(from: url) { result in
|
||||||
|
if let error = result.error {
|
||||||
|
lastFileImportErrorText = error.alertText
|
||||||
|
}
|
||||||
|
if let configsInZip = result.value {
|
||||||
|
configs.append(contentsOf: configsInZip)
|
||||||
|
}
|
||||||
|
dispatchGroup.leave()
|
||||||
}
|
}
|
||||||
let configs = result.value!
|
} else { /* if it is not a zip, we assume it is a conf */
|
||||||
tunnelsManager.addMultiple(tunnelConfigurations: configs.compactMap { $0 }) { numberSuccessful in
|
let fileName = url.lastPathComponent
|
||||||
if numberSuccessful == configs.count {
|
let fileBaseName = url.deletingPathExtension().lastPathComponent.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
completionHandler?()
|
dispatchGroup.enter()
|
||||||
|
DispatchQueue.global(qos: .userInitiated).async {
|
||||||
|
let fileContents: String
|
||||||
|
do {
|
||||||
|
fileContents = try String(contentsOf: url)
|
||||||
|
} catch let error {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
if let cocoaError = error as? CocoaError, cocoaError.isFileError {
|
||||||
|
lastFileImportErrorText = (title: tr("alertCantOpenInputConfFileTitle"), message: error.localizedDescription)
|
||||||
|
} else {
|
||||||
|
lastFileImportErrorText = (title: tr("alertCantOpenInputConfFileTitle"), message: tr(format: "alertCantOpenInputConfFileMessage (%@)", fileName))
|
||||||
|
}
|
||||||
|
configs.append(nil)
|
||||||
|
dispatchGroup.leave()
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let title = tr(format: "alertImportedFromZipTitle (%d)", numberSuccessful)
|
let tunnelConfiguration = try? TunnelConfiguration(fromWgQuickConfig: fileContents, called: fileBaseName)
|
||||||
let message = tr(format: "alertImportedFromZipMessage (%1$d of %2$d)", numberSuccessful, configs.count)
|
DispatchQueue.main.async {
|
||||||
errorPresenterType.showErrorAlert(title: title, message: message, from: sourceVC, onPresented: completionHandler)
|
if tunnelConfiguration == nil {
|
||||||
}
|
lastFileImportErrorText = (title: tr("alertBadConfigImportTitle"), message: tr(format: "alertBadConfigImportMessage (%@)", fileName))
|
||||||
}
|
}
|
||||||
} else /* if (url.pathExtension == "conf") -- we assume everything else is a conf */ {
|
configs.append(tunnelConfiguration)
|
||||||
let fileBaseName = url.deletingPathExtension().lastPathComponent.trimmingCharacters(in: .whitespacesAndNewlines)
|
dispatchGroup.leave()
|
||||||
if let fileContents = try? String(contentsOf: url),
|
|
||||||
let tunnelConfiguration = try? TunnelConfiguration(fromWgQuickConfig: fileContents, called: fileBaseName) {
|
|
||||||
tunnelsManager.add(tunnelConfiguration: tunnelConfiguration) { result in
|
|
||||||
if let error = result.error {
|
|
||||||
errorPresenterType.showErrorAlert(error: error, from: sourceVC, onPresented: completionHandler)
|
|
||||||
} else {
|
|
||||||
completionHandler?()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
errorPresenterType.showErrorAlert(title: tr("alertUnableToImportTitle"), message: tr("alertUnableToImportMessage"),
|
}
|
||||||
from: sourceVC, onPresented: completionHandler)
|
dispatchGroup.notify(queue: .main) {
|
||||||
|
tunnelsManager.addMultiple(tunnelConfigurations: configs.compactMap { $0 }) { numberSuccessful, lastAddError in
|
||||||
|
if !configs.isEmpty && numberSuccessful == configs.count {
|
||||||
|
completionHandler?()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let alertText: (title: String, message: String)?
|
||||||
|
if urls.count == 1 {
|
||||||
|
if urls.first!.pathExtension.lowercased() == "zip" && !configs.isEmpty {
|
||||||
|
alertText = (title: tr(format: "alertImportedFromZipTitle (%d)", numberSuccessful),
|
||||||
|
message: tr(format: "alertImportedFromZipMessage (%1$d of %2$d)", numberSuccessful, configs.count))
|
||||||
|
} else {
|
||||||
|
alertText = lastFileImportErrorText ?? lastAddError?.alertText
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alertText = (title: tr(format: "alertImportedFromMultipleFilesTitle (%d)", numberSuccessful),
|
||||||
|
message: tr(format: "alertImportedFromMultipleFilesMessage (%1$d of %2$d)", numberSuccessful, configs.count))
|
||||||
|
}
|
||||||
|
if let alertText = alertText {
|
||||||
|
errorPresenterType.showErrorAlert(title: alertText.title, message: alertText.message, from: sourceVC, onPresented: completionHandler)
|
||||||
|
} else {
|
||||||
|
completionHandler?()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ class TunnelViewModel {
|
|||||||
case listenPort
|
case listenPort
|
||||||
case mtu
|
case mtu
|
||||||
case dns
|
case dns
|
||||||
|
case status
|
||||||
|
case toggleStatus
|
||||||
|
|
||||||
var localizedUIString: String {
|
var localizedUIString: String {
|
||||||
switch self {
|
switch self {
|
||||||
@@ -25,6 +27,8 @@ class TunnelViewModel {
|
|||||||
case .listenPort: return tr("tunnelInterfaceListenPort")
|
case .listenPort: return tr("tunnelInterfaceListenPort")
|
||||||
case .mtu: return tr("tunnelInterfaceMTU")
|
case .mtu: return tr("tunnelInterfaceMTU")
|
||||||
case .dns: return tr("tunnelInterfaceDNS")
|
case .dns: return tr("tunnelInterfaceDNS")
|
||||||
|
case .status: return tr("tunnelInterfaceStatus")
|
||||||
|
case .toggleStatus: return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,17 +71,17 @@ class TunnelViewModel {
|
|||||||
|
|
||||||
static let keyLengthInBase64 = 44
|
static let keyLengthInBase64 = 44
|
||||||
|
|
||||||
struct ChangeHandlers {
|
struct Changes {
|
||||||
enum FieldChange {
|
enum FieldChange: Equatable {
|
||||||
case added
|
case added
|
||||||
case removed
|
case removed
|
||||||
case modified
|
case modified(newValue: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
var interfaceChanged: ([InterfaceField: FieldChange]) -> Void
|
var interfaceChanges: [InterfaceField: FieldChange]
|
||||||
var peerChangedAt: (Int, [PeerField: FieldChange]) -> Void
|
var peerChanges: [(peerIndex: Int, changes: [PeerField: FieldChange])]
|
||||||
var peersRemovedAt: ([Int]) -> Void
|
var peersRemovedIndices: [Int]
|
||||||
var peersInsertedAt: ([Int]) -> Void
|
var peersInsertedIndices: [Int]
|
||||||
}
|
}
|
||||||
|
|
||||||
class InterfaceData {
|
class InterfaceData {
|
||||||
@@ -217,12 +221,12 @@ class TunnelViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyConfiguration(other: InterfaceConfiguration, otherName: String, changeHandler: ([InterfaceField: ChangeHandlers.FieldChange]) -> Void) {
|
func applyConfiguration(other: InterfaceConfiguration, otherName: String) -> [InterfaceField: Changes.FieldChange] {
|
||||||
if scratchpad.isEmpty {
|
if scratchpad.isEmpty {
|
||||||
populateScratchpad()
|
populateScratchpad()
|
||||||
}
|
}
|
||||||
let otherScratchPad = InterfaceData.createScratchPad(from: other, name: otherName)
|
let otherScratchPad = InterfaceData.createScratchPad(from: other, name: otherName)
|
||||||
var changes = [InterfaceField: ChangeHandlers.FieldChange]()
|
var changes = [InterfaceField: Changes.FieldChange]()
|
||||||
for field in InterfaceField.allCases {
|
for field in InterfaceField.allCases {
|
||||||
switch (scratchpad[field] ?? "", otherScratchPad[field] ?? "") {
|
switch (scratchpad[field] ?? "", otherScratchPad[field] ?? "") {
|
||||||
case ("", ""):
|
case ("", ""):
|
||||||
@@ -233,14 +237,12 @@ class TunnelViewModel {
|
|||||||
changes[field] = .removed
|
changes[field] = .removed
|
||||||
case (let this, let other):
|
case (let this, let other):
|
||||||
if this != other {
|
if this != other {
|
||||||
changes[field] = .modified
|
changes[field] = .modified(newValue: other)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scratchpad = otherScratchPad
|
scratchpad = otherScratchPad
|
||||||
if !changes.isEmpty {
|
return changes
|
||||||
changeHandler(changes)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,50 +405,60 @@ class TunnelViewModel {
|
|||||||
"193.0.0.0/8", "194.0.0.0/7", "196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4"
|
"193.0.0.0/8", "194.0.0.0/7", "196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
static func excludePrivateIPsFieldStates(isSinglePeer: Bool, allowedIPs: Set<String>) -> (shouldAllowExcludePrivateIPsControl: Bool, excludePrivateIPsValue: Bool) {
|
||||||
|
guard isSinglePeer else {
|
||||||
|
return (shouldAllowExcludePrivateIPsControl: false, excludePrivateIPsValue: false)
|
||||||
|
}
|
||||||
|
let allowedIPStrings = Set<String>(allowedIPs)
|
||||||
|
if allowedIPStrings.contains(TunnelViewModel.PeerData.ipv4DefaultRouteString) {
|
||||||
|
return (shouldAllowExcludePrivateIPsControl: true, excludePrivateIPsValue: false)
|
||||||
|
} else if allowedIPStrings.isSuperset(of: TunnelViewModel.PeerData.ipv4DefaultRouteModRFC1918String) {
|
||||||
|
return (shouldAllowExcludePrivateIPsControl: true, excludePrivateIPsValue: true)
|
||||||
|
} else {
|
||||||
|
return (shouldAllowExcludePrivateIPsControl: false, excludePrivateIPsValue: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func updateExcludePrivateIPsFieldState() {
|
func updateExcludePrivateIPsFieldState() {
|
||||||
if scratchpad.isEmpty {
|
if scratchpad.isEmpty {
|
||||||
populateScratchpad()
|
populateScratchpad()
|
||||||
}
|
}
|
||||||
let allowedIPStrings = Set<String>(scratchpad[.allowedIPs].splitToArray(trimmingCharacters: .whitespacesAndNewlines))
|
let allowedIPStrings = Set<String>(scratchpad[.allowedIPs].splitToArray(trimmingCharacters: .whitespacesAndNewlines))
|
||||||
|
(shouldAllowExcludePrivateIPsControl, excludePrivateIPsValue) = TunnelViewModel.PeerData.excludePrivateIPsFieldStates(isSinglePeer: numberOfPeers == 1, allowedIPs: allowedIPStrings)
|
||||||
shouldStronglyRecommendDNS = allowedIPStrings.contains(TunnelViewModel.PeerData.ipv4DefaultRouteString) || allowedIPStrings.isSuperset(of: TunnelViewModel.PeerData.ipv4DefaultRouteModRFC1918String)
|
shouldStronglyRecommendDNS = allowedIPStrings.contains(TunnelViewModel.PeerData.ipv4DefaultRouteString) || allowedIPStrings.isSuperset(of: TunnelViewModel.PeerData.ipv4DefaultRouteModRFC1918String)
|
||||||
guard numberOfPeers == 1 else {
|
}
|
||||||
shouldAllowExcludePrivateIPsControl = false
|
|
||||||
excludePrivateIPsValue = false
|
static func normalizedIPAddressRangeStrings(_ list: [String]) -> [String] {
|
||||||
return
|
return list.compactMap { IPAddressRange(from: $0) }.map { $0.stringRepresentation }
|
||||||
}
|
}
|
||||||
if allowedIPStrings.contains(TunnelViewModel.PeerData.ipv4DefaultRouteString) {
|
|
||||||
shouldAllowExcludePrivateIPsControl = true
|
static func modifiedAllowedIPs(currentAllowedIPs: [String], excludePrivateIPs: Bool, dnsServers: [String], oldDNSServers: [String]?) -> [String] {
|
||||||
excludePrivateIPsValue = false
|
let normalizedDNSServers = normalizedIPAddressRangeStrings(dnsServers)
|
||||||
} else if allowedIPStrings.isSuperset(of: TunnelViewModel.PeerData.ipv4DefaultRouteModRFC1918String) {
|
let normalizedOldDNSServers = oldDNSServers == nil ? normalizedDNSServers : normalizedIPAddressRangeStrings(oldDNSServers!)
|
||||||
shouldAllowExcludePrivateIPsControl = true
|
let ipv6Addresses = normalizedIPAddressRangeStrings(currentAllowedIPs.filter { $0.contains(":") })
|
||||||
excludePrivateIPsValue = true
|
if excludePrivateIPs {
|
||||||
|
return ipv6Addresses + TunnelViewModel.PeerData.ipv4DefaultRouteModRFC1918String + normalizedDNSServers
|
||||||
} else {
|
} else {
|
||||||
shouldAllowExcludePrivateIPsControl = false
|
return ipv6Addresses.filter { !normalizedOldDNSServers.contains($0) } + [TunnelViewModel.PeerData.ipv4DefaultRouteString]
|
||||||
excludePrivateIPsValue = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func excludePrivateIPsValueChanged(isOn: Bool, dnsServers: String) {
|
func excludePrivateIPsValueChanged(isOn: Bool, dnsServers: String, oldDNSServers: String? = nil) {
|
||||||
let allowedIPStrings = scratchpad[.allowedIPs].splitToArray(trimmingCharacters: .whitespacesAndNewlines)
|
let allowedIPStrings = scratchpad[.allowedIPs].splitToArray(trimmingCharacters: .whitespacesAndNewlines)
|
||||||
let dnsServerStrings = dnsServers.splitToArray(trimmingCharacters: .whitespacesAndNewlines)
|
let dnsServerStrings = dnsServers.splitToArray(trimmingCharacters: .whitespacesAndNewlines)
|
||||||
let ipv6Addresses = allowedIPStrings.filter { $0.contains(":") }
|
let oldDNSServerStrings = oldDNSServers?.splitToArray(trimmingCharacters: .whitespacesAndNewlines)
|
||||||
let modifiedAllowedIPStrings: [String]
|
let modifiedAllowedIPStrings = TunnelViewModel.PeerData.modifiedAllowedIPs(currentAllowedIPs: allowedIPStrings, excludePrivateIPs: isOn, dnsServers: dnsServerStrings, oldDNSServers: oldDNSServerStrings)
|
||||||
if isOn {
|
|
||||||
modifiedAllowedIPStrings = ipv6Addresses + TunnelViewModel.PeerData.ipv4DefaultRouteModRFC1918String + dnsServerStrings
|
|
||||||
} else {
|
|
||||||
modifiedAllowedIPStrings = ipv6Addresses + [TunnelViewModel.PeerData.ipv4DefaultRouteString]
|
|
||||||
}
|
|
||||||
scratchpad[.allowedIPs] = modifiedAllowedIPStrings.joined(separator: ", ")
|
scratchpad[.allowedIPs] = modifiedAllowedIPStrings.joined(separator: ", ")
|
||||||
validatedConfiguration = nil
|
validatedConfiguration = nil
|
||||||
excludePrivateIPsValue = isOn
|
excludePrivateIPsValue = isOn
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyConfiguration(other: PeerConfiguration, peerIndex: Int, changeHandler: (Int, [PeerField: ChangeHandlers.FieldChange]) -> Void) {
|
func applyConfiguration(other: PeerConfiguration) -> [PeerField: Changes.FieldChange] {
|
||||||
if scratchpad.isEmpty {
|
if scratchpad.isEmpty {
|
||||||
populateScratchpad()
|
populateScratchpad()
|
||||||
}
|
}
|
||||||
let otherScratchPad = PeerData.createScratchPad(from: other)
|
let otherScratchPad = PeerData.createScratchPad(from: other)
|
||||||
var changes = [PeerField: ChangeHandlers.FieldChange]()
|
var changes = [PeerField: Changes.FieldChange]()
|
||||||
for field in PeerField.allCases {
|
for field in PeerField.allCases {
|
||||||
switch (scratchpad[field] ?? "", otherScratchPad[field] ?? "") {
|
switch (scratchpad[field] ?? "", otherScratchPad[field] ?? "") {
|
||||||
case ("", ""):
|
case ("", ""):
|
||||||
@@ -457,14 +469,12 @@ class TunnelViewModel {
|
|||||||
changes[field] = .removed
|
changes[field] = .removed
|
||||||
case (let this, let other):
|
case (let this, let other):
|
||||||
if this != other {
|
if this != other {
|
||||||
changes[field] = .modified
|
changes[field] = .modified(newValue: other)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scratchpad = otherScratchPad
|
scratchpad = otherScratchPad
|
||||||
if !changes.isEmpty {
|
return changes
|
||||||
changeHandler(peerIndex, changes)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -519,6 +529,17 @@ class TunnelViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateDNSServersInAllowedIPsIfRequired(oldDNSServers: String, newDNSServers: String) -> Bool {
|
||||||
|
guard peersData.count == 1, let firstPeer = peersData.first else { return false }
|
||||||
|
guard firstPeer.shouldAllowExcludePrivateIPsControl && firstPeer.excludePrivateIPsValue else { return false }
|
||||||
|
let allowedIPStrings = firstPeer[.allowedIPs].splitToArray(trimmingCharacters: .whitespacesAndNewlines)
|
||||||
|
let oldDNSServerStrings = TunnelViewModel.PeerData.normalizedIPAddressRangeStrings(oldDNSServers.splitToArray(trimmingCharacters: .whitespacesAndNewlines))
|
||||||
|
let newDNSServerStrings = TunnelViewModel.PeerData.normalizedIPAddressRangeStrings(newDNSServers.splitToArray(trimmingCharacters: .whitespacesAndNewlines))
|
||||||
|
let updatedAllowedIPStrings = allowedIPStrings.filter { !oldDNSServerStrings.contains($0) } + newDNSServerStrings
|
||||||
|
firstPeer[.allowedIPs] = updatedAllowedIPStrings.joined(separator: ", ")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func save() -> SaveResult<TunnelConfiguration> {
|
func save() -> SaveResult<TunnelConfiguration> {
|
||||||
let interfaceSaveResult = interfaceData.save()
|
let interfaceSaveResult = interfaceData.save()
|
||||||
let peerSaveResults = peersData.map { $0.save() } // Save all, to help mark erroring fields in red
|
let peerSaveResults = peersData.map { $0.save() } // Save all, to help mark erroring fields in red
|
||||||
@@ -548,21 +569,28 @@ class TunnelViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyConfiguration(other: TunnelConfiguration, changeHandlers: ChangeHandlers) {
|
func asWgQuickConfig() -> String? {
|
||||||
|
let saveResult = save()
|
||||||
|
if case .saved(let tunnelConfiguration) = saveResult {
|
||||||
|
return tunnelConfiguration.asWgQuickConfig()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
func applyConfiguration(other: TunnelConfiguration) -> Changes {
|
||||||
// Replaces current data with data from other TunnelConfiguration, ignoring any changes in peer ordering.
|
// Replaces current data with data from other TunnelConfiguration, ignoring any changes in peer ordering.
|
||||||
// Change handler callbacks are processed in the following order, which is designed to work with both the
|
|
||||||
// UITableView way (modify - delete - insert) and the NSTableView way (indices are based on past operations):
|
|
||||||
// - interfaceChanged
|
|
||||||
// - peerChangedAt
|
|
||||||
// - peersRemovedAt
|
|
||||||
// - peersInsertedAt
|
|
||||||
|
|
||||||
interfaceData.applyConfiguration(other: other.interface, otherName: other.name ?? "", changeHandler: changeHandlers.interfaceChanged)
|
let interfaceChanges = interfaceData.applyConfiguration(other: other.interface, otherName: other.name ?? "")
|
||||||
|
|
||||||
|
var peerChanges = [(peerIndex: Int, changes: [PeerField: Changes.FieldChange])]()
|
||||||
for otherPeer in other.peers {
|
for otherPeer in other.peers {
|
||||||
if let peersDataIndex = peersData.firstIndex(where: { $0.publicKey == otherPeer.publicKey }) {
|
if let peersDataIndex = peersData.firstIndex(where: { $0.publicKey == otherPeer.publicKey }) {
|
||||||
let peerData = peersData[peersDataIndex]
|
let peerData = peersData[peersDataIndex]
|
||||||
peerData.applyConfiguration(other: otherPeer, peerIndex: peersDataIndex, changeHandler: changeHandlers.peerChangedAt)
|
let changes = peerData.applyConfiguration(other: otherPeer)
|
||||||
|
if !changes.isEmpty {
|
||||||
|
peerChanges.append((peerIndex: peersDataIndex, changes: changes))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -573,9 +601,6 @@ class TunnelViewModel {
|
|||||||
peersData.remove(at: index)
|
peersData.remove(at: index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !removedPeerIndices.isEmpty {
|
|
||||||
changeHandlers.peersRemovedAt(removedPeerIndices)
|
|
||||||
}
|
|
||||||
|
|
||||||
var addedPeerIndices = [Int]()
|
var addedPeerIndices = [Int]()
|
||||||
for otherPeer in other.peers {
|
for otherPeer in other.peers {
|
||||||
@@ -586,61 +611,14 @@ class TunnelViewModel {
|
|||||||
peersData.append(peerData)
|
peersData.append(peerData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !addedPeerIndices.isEmpty {
|
|
||||||
changeHandlers.peersInsertedAt(addedPeerIndices)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (index, peer) in peersData.enumerated() {
|
for (index, peer) in peersData.enumerated() {
|
||||||
peer.index = index
|
peer.index = index
|
||||||
peer.numberOfPeers = peersData.count
|
peer.numberOfPeers = peersData.count
|
||||||
peer.updateExcludePrivateIPsFieldState()
|
peer.updateExcludePrivateIPsFieldState()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension TunnelViewModel {
|
return Changes(interfaceChanges: interfaceChanges, peerChanges: peerChanges, peersRemovedIndices: removedPeerIndices, peersInsertedIndices: addedPeerIndices)
|
||||||
static func activateOnDemandOptionText(for activateOnDemandOption: ActivateOnDemandOption) -> String {
|
|
||||||
switch activateOnDemandOption {
|
|
||||||
case .none:
|
|
||||||
return tr("tunnelOnDemandOptionOff")
|
|
||||||
case .useOnDemandOverWiFiOnly:
|
|
||||||
return tr("tunnelOnDemandOptionWiFiOnly")
|
|
||||||
#if os(iOS)
|
|
||||||
case .useOnDemandOverWiFiOrCellular:
|
|
||||||
return tr("tunnelOnDemandOptionWiFiOrCellular")
|
|
||||||
case .useOnDemandOverCellularOnly:
|
|
||||||
return tr("tunnelOnDemandOptionCellularOnly")
|
|
||||||
#elseif os(macOS)
|
|
||||||
case .useOnDemandOverWiFiOrEthernet:
|
|
||||||
return tr("tunnelOnDemandOptionWiFiOrEthernet")
|
|
||||||
case .useOnDemandOverEthernetOnly:
|
|
||||||
return tr("tunnelOnDemandOptionEthernetOnly")
|
|
||||||
#else
|
|
||||||
#error("Unimplemented")
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static func activateOnDemandDetailText(for activateOnDemandSetting: ActivateOnDemandSetting?) -> String {
|
|
||||||
if let activateOnDemandSetting = activateOnDemandSetting {
|
|
||||||
if activateOnDemandSetting.isActivateOnDemandEnabled {
|
|
||||||
return TunnelViewModel.activateOnDemandOptionText(for: activateOnDemandSetting.activateOnDemandOption)
|
|
||||||
} else {
|
|
||||||
return TunnelViewModel.activateOnDemandOptionText(for: .none)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return TunnelViewModel.activateOnDemandOptionText(for: .none)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static func defaultActivateOnDemandOption() -> ActivateOnDemandOption {
|
|
||||||
#if os(iOS)
|
|
||||||
return .useOnDemandOverWiFiOrCellular
|
|
||||||
#elseif os(macOS)
|
|
||||||
return .useOnDemandOverWiFiOrEthernet
|
|
||||||
#else
|
|
||||||
#error("Unimplemented")
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
var mainVC: MainViewController?
|
var mainVC: MainViewController?
|
||||||
|
|
||||||
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||||
Logger.configureGlobal(withFilePath: FileManager.appLogFileURL?.path)
|
Logger.configureGlobal(tagged: "APP", withFilePath: FileManager.logFileURL?.path)
|
||||||
|
|
||||||
let window = UIWindow(frame: UIScreen.main.bounds)
|
let window = UIWindow(frame: UIScreen.main.bounds)
|
||||||
window.backgroundColor = .white
|
window.backgroundColor = .white
|
||||||
@@ -28,7 +28,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
|
|
||||||
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
|
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
|
||||||
guard let tunnelsManager = mainVC?.tunnelsManager else { return true }
|
guard let tunnelsManager = mainVC?.tunnelsManager else { return true }
|
||||||
TunnelImporter.importFromFile(url: url, into: tunnelsManager, sourceVC: mainVC, errorPresenterType: ErrorPresenter.self) {
|
TunnelImporter.importFromFile(urls: [url], into: tunnelsManager, sourceVC: mainVC, errorPresenterType: ErrorPresenter.self) {
|
||||||
_ = FileManager.deleteFile(at: url)
|
_ = FileManager.deleteFile(at: url)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class ConfirmationAlertPresenter {
|
||||||
|
static func showConfirmationAlert(message: String, buttonTitle: String, from sourceObject: AnyObject, presentingVC: UIViewController, onConfirmed: @escaping (() -> Void)) {
|
||||||
|
let destroyAction = UIAlertAction(title: buttonTitle, style: .destructive) { _ in
|
||||||
|
onConfirmed()
|
||||||
|
}
|
||||||
|
let cancelAction = UIAlertAction(title: tr("actionCancel"), style: .cancel)
|
||||||
|
let alert = UIAlertController(title: "", message: message, preferredStyle: .actionSheet)
|
||||||
|
alert.addAction(destroyAction)
|
||||||
|
alert.addAction(cancelAction)
|
||||||
|
|
||||||
|
if let sourceView = sourceObject as? UIView {
|
||||||
|
alert.popoverPresentationController?.sourceView = sourceView
|
||||||
|
alert.popoverPresentationController?.sourceRect = sourceView.bounds
|
||||||
|
} else if let sourceBarButtonItem = sourceObject as? UIBarButtonItem {
|
||||||
|
alert.popoverPresentationController?.barButtonItem = sourceBarButtonItem
|
||||||
|
}
|
||||||
|
|
||||||
|
presentingVC.present(alert, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -77,12 +77,11 @@
|
|||||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||||
<false/>
|
<false/>
|
||||||
<key>NSCameraUsageDescription</key>
|
<key>NSCameraUsageDescription</key>
|
||||||
<string>Camera is used for scanning QR codes for importing WireGuard configurations</string>
|
<string>Localized</string>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIRequiredDeviceCapabilities</key>
|
<key>UIRequiredDeviceCapabilities</key>
|
||||||
<array>
|
<array>
|
||||||
<string>armv7</string>
|
|
||||||
</array>
|
</array>
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
<array>
|
<array>
|
||||||
@@ -123,7 +122,7 @@
|
|||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>NSFaceIDUsageDescription</key>
|
<key>NSFaceIDUsageDescription</key>
|
||||||
<string>Face ID is used for authenticating viewing and exporting of private keys</string>
|
<string>Localized</string>
|
||||||
<key>com.wireguard.ios.app_group_id</key>
|
<key>com.wireguard.ios.app_group_id</key>
|
||||||
<string>group.$(APP_ID_IOS)</string>
|
<string>group.$(APP_ID_IOS)</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class ChevronCell: UITableViewCell {
|
||||||
|
var message: String {
|
||||||
|
get { return textLabel?.text ?? "" }
|
||||||
|
set(value) { textLabel?.text = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
var detailMessage: String {
|
||||||
|
get { return detailTextLabel?.text ?? "" }
|
||||||
|
set(value) { detailTextLabel?.text = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
|
super.init(style: .value1, reuseIdentifier: reuseIdentifier)
|
||||||
|
accessoryType = .disclosureIndicator
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func prepareForReuse() {
|
||||||
|
super.prepareForReuse()
|
||||||
|
message = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class EditableTextCell: UITableViewCell {
|
||||||
|
var message: String {
|
||||||
|
get { return valueTextField.text ?? "" }
|
||||||
|
set(value) { valueTextField.text = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
let valueTextField: UITextField = {
|
||||||
|
let valueTextField = UITextField()
|
||||||
|
valueTextField.textAlignment = .left
|
||||||
|
valueTextField.isEnabled = true
|
||||||
|
valueTextField.font = UIFont.preferredFont(forTextStyle: .body)
|
||||||
|
valueTextField.adjustsFontForContentSizeCategory = true
|
||||||
|
valueTextField.autocapitalizationType = .none
|
||||||
|
valueTextField.autocorrectionType = .no
|
||||||
|
valueTextField.spellCheckingType = .no
|
||||||
|
return valueTextField
|
||||||
|
}()
|
||||||
|
|
||||||
|
var onValueBeingEdited: ((String) -> Void)?
|
||||||
|
|
||||||
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||||
|
|
||||||
|
valueTextField.delegate = self
|
||||||
|
contentView.addSubview(valueTextField)
|
||||||
|
valueTextField.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
let bottomAnchorConstraint = contentView.layoutMarginsGuide.bottomAnchor.constraint(equalToSystemSpacingBelow: valueTextField.bottomAnchor, multiplier: 1)
|
||||||
|
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),
|
||||||
|
bottomAnchorConstraint
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func beginEditing() {
|
||||||
|
valueTextField.becomeFirstResponder()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func prepareForReuse() {
|
||||||
|
super.prepareForReuse()
|
||||||
|
message = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension EditableTextCell: UITextFieldDelegate {
|
||||||
|
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||||
|
if let onValueBeingEdited = onValueBeingEdited {
|
||||||
|
let modifiedText = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string)
|
||||||
|
onValueBeingEdited(modifiedText)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -68,7 +68,7 @@ class KeyValueCell: UITableViewCell {
|
|||||||
var isStackedVertically = false
|
var isStackedVertically = false
|
||||||
var contentSizeBasedConstraints = [NSLayoutConstraint]()
|
var contentSizeBasedConstraints = [NSLayoutConstraint]()
|
||||||
|
|
||||||
var onValueChanged: ((String) -> Void)?
|
var onValueChanged: ((String, String) -> Void)?
|
||||||
var onValueBeingEdited: ((String) -> Void)?
|
var onValueBeingEdited: ((String) -> Void)?
|
||||||
|
|
||||||
var observationToken: AnyObject?
|
var observationToken: AnyObject?
|
||||||
@@ -206,7 +206,7 @@ extension KeyValueCell: UITextFieldDelegate {
|
|||||||
func textFieldDidEndEditing(_ textField: UITextField) {
|
func textFieldDidEndEditing(_ textField: UITextField) {
|
||||||
let isModified = textField.text ?? "" != textFieldValueOnBeginEditing
|
let isModified = textField.text ?? "" != textFieldValueOnBeginEditing
|
||||||
guard isModified else { return }
|
guard isModified else { return }
|
||||||
onValueChanged?(textField.text ?? "")
|
onValueChanged?(textFieldValueOnBeginEditing, textField.text ?? "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class TextCell: UITableViewCell {
|
||||||
|
var message: String {
|
||||||
|
get { return textLabel?.text ?? "" }
|
||||||
|
set(value) { textLabel!.text = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
|
super.init(style: .default, reuseIdentifier: reuseIdentifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func setTextColor(_ color: UIColor) {
|
||||||
|
textLabel?.textColor = color
|
||||||
|
}
|
||||||
|
|
||||||
|
func setTextAlignment(_ alignment: NSTextAlignment) {
|
||||||
|
textLabel?.textAlignment = alignment
|
||||||
|
}
|
||||||
|
|
||||||
|
override func prepareForReuse() {
|
||||||
|
super.prepareForReuse()
|
||||||
|
message = ""
|
||||||
|
setTextColor(.black)
|
||||||
|
setTextAlignment(.left)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -98,6 +98,11 @@ class TunnelListCell: UITableViewCell {
|
|||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func setEditing(_ editing: Bool, animated: Bool) {
|
||||||
|
super.setEditing(editing, animated: animated)
|
||||||
|
statusSwitch.isEnabled = !editing
|
||||||
|
}
|
||||||
|
|
||||||
private func reset() {
|
private func reset() {
|
||||||
statusSwitch.isOn = false
|
statusSwitch.isOn = false
|
||||||
statusSwitch.isUserInteractionEnabled = false
|
statusSwitch.isUserInteractionEnabled = false
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class LogViewController: UIViewController {
|
||||||
|
|
||||||
|
let textView: UITextView = {
|
||||||
|
let textView = UITextView()
|
||||||
|
textView.isEditable = false
|
||||||
|
textView.isSelectable = true
|
||||||
|
textView.font = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body)
|
||||||
|
textView.adjustsFontForContentSizeCategory = true
|
||||||
|
return textView
|
||||||
|
}()
|
||||||
|
|
||||||
|
let busyIndicator: UIActivityIndicatorView = {
|
||||||
|
let busyIndicator = UIActivityIndicatorView(style: .gray)
|
||||||
|
busyIndicator.hidesWhenStopped = true
|
||||||
|
return busyIndicator
|
||||||
|
}()
|
||||||
|
|
||||||
|
var logViewHelper: LogViewHelper?
|
||||||
|
var isFetchingLogEntries = false
|
||||||
|
private var updateLogEntriesTimer: Timer?
|
||||||
|
|
||||||
|
override func loadView() {
|
||||||
|
view = UIView()
|
||||||
|
view.backgroundColor = .white
|
||||||
|
|
||||||
|
view.addSubview(textView)
|
||||||
|
textView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
textView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||||
|
textView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||||
|
textView.topAnchor.constraint(equalTo: view.topAnchor),
|
||||||
|
textView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
|
||||||
|
])
|
||||||
|
|
||||||
|
view.addSubview(busyIndicator)
|
||||||
|
busyIndicator.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
busyIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
||||||
|
busyIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor)
|
||||||
|
])
|
||||||
|
|
||||||
|
busyIndicator.startAnimating()
|
||||||
|
|
||||||
|
logViewHelper = LogViewHelper(logFilePath: FileManager.logFileURL?.path)
|
||||||
|
startUpdatingLogEntries()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
title = tr("logViewTitle")
|
||||||
|
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(saveTapped(sender:)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateLogEntries() {
|
||||||
|
guard !isFetchingLogEntries else { return }
|
||||||
|
isFetchingLogEntries = true
|
||||||
|
logViewHelper?.fetchLogEntriesSinceLastFetch { [weak self] fetchedLogEntries in
|
||||||
|
guard let self = self else { return }
|
||||||
|
defer {
|
||||||
|
self.isFetchingLogEntries = false
|
||||||
|
}
|
||||||
|
if self.busyIndicator.isAnimating {
|
||||||
|
self.busyIndicator.stopAnimating()
|
||||||
|
}
|
||||||
|
guard !fetchedLogEntries.isEmpty else { return }
|
||||||
|
let isScrolledToEnd = self.textView.contentSize.height - self.textView.bounds.height - self.textView.contentOffset.y < 1
|
||||||
|
let text = fetchedLogEntries.reduce("") { $0 + $1.text() + "\n" }
|
||||||
|
let font = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body)
|
||||||
|
let richText = NSAttributedString(string: text, attributes: [.font: font])
|
||||||
|
self.textView.textStorage.append(richText)
|
||||||
|
if isScrolledToEnd {
|
||||||
|
let endOfCurrentText = NSRange(location: (self.textView.text as NSString).length, length: 0)
|
||||||
|
self.textView.scrollRangeToVisible(endOfCurrentText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func startUpdatingLogEntries() {
|
||||||
|
updateLogEntries()
|
||||||
|
updateLogEntriesTimer?.invalidate()
|
||||||
|
let timer = Timer(timeInterval: 1 /* second */, repeats: true) { [weak self] _ in
|
||||||
|
self?.updateLogEntries()
|
||||||
|
}
|
||||||
|
updateLogEntriesTimer = timer
|
||||||
|
RunLoop.main.add(timer, forMode: .common)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func saveTapped(sender: AnyObject) {
|
||||||
|
guard let destinationDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
|
||||||
|
|
||||||
|
let dateFormatter = ISO8601DateFormatter()
|
||||||
|
dateFormatter.formatOptions = [.withFullDate, .withTime, .withTimeZone] // Avoid ':' in the filename
|
||||||
|
let timeStampString = dateFormatter.string(from: Date())
|
||||||
|
let destinationURL = destinationDir.appendingPathComponent("wireguard-log-\(timeStampString).txt")
|
||||||
|
|
||||||
|
DispatchQueue.global(qos: .userInitiated).async {
|
||||||
|
|
||||||
|
if FileManager.default.fileExists(atPath: destinationURL.path) {
|
||||||
|
let isDeleted = FileManager.deleteFile(at: destinationURL)
|
||||||
|
if !isDeleted {
|
||||||
|
ErrorPresenter.showErrorAlert(title: tr("alertUnableToRemovePreviousLogTitle"), message: tr("alertUnableToRemovePreviousLogMessage"), from: self)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let isWritten = Logger.global?.writeLog(to: destinationURL.path) ?? false
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
guard isWritten else {
|
||||||
|
ErrorPresenter.showErrorAlert(title: tr("alertUnableToWriteLogTitle"), message: tr("alertUnableToWriteLogMessage"), from: self)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let activityVC = UIActivityViewController(activityItems: [destinationURL], applicationActivities: nil)
|
||||||
|
if let sender = sender as? UIBarButtonItem {
|
||||||
|
activityVC.popoverPresentationController?.barButtonItem = sender
|
||||||
|
}
|
||||||
|
activityVC.completionWithItemsHandler = { _, _, _, _ in
|
||||||
|
// Remove the exported log file after the activity has completed
|
||||||
|
_ = FileManager.deleteFile(at: destinationURL)
|
||||||
|
}
|
||||||
|
self.present(activityVC, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SSIDOptionDetailTableViewController: UITableViewController {
|
||||||
|
|
||||||
|
let selectedSSIDs: [String]
|
||||||
|
|
||||||
|
init(title: String, ssids: [String]) {
|
||||||
|
selectedSSIDs = ssids
|
||||||
|
super.init(style: .grouped)
|
||||||
|
self.title = title
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
tableView.estimatedRowHeight = 44
|
||||||
|
tableView.rowHeight = UITableView.automaticDimension
|
||||||
|
tableView.allowsSelection = false
|
||||||
|
|
||||||
|
tableView.register(TextCell.self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SSIDOptionDetailTableViewController {
|
||||||
|
override func numberOfSections(in tableView: UITableView) -> Int {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||||
|
return selectedSSIDs.count
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||||
|
return tr("tunnelOnDemandSectionTitleSelectedSSIDs")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
|
let cell: TextCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
|
cell.message = selectedSSIDs[indexPath.row]
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,295 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SystemConfiguration.CaptiveNetwork
|
||||||
|
|
||||||
|
protocol SSIDOptionEditTableViewControllerDelegate: class {
|
||||||
|
func ssidOptionSaved(option: ActivateOnDemandViewModel.OnDemandSSIDOption, ssids: [String])
|
||||||
|
}
|
||||||
|
|
||||||
|
class SSIDOptionEditTableViewController: UITableViewController {
|
||||||
|
private enum Section {
|
||||||
|
case ssidOption
|
||||||
|
case selectedSSIDs
|
||||||
|
case addSSIDs
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum AddSSIDRow {
|
||||||
|
case addConnectedSSID(connectedSSID: String)
|
||||||
|
case addNewSSID
|
||||||
|
}
|
||||||
|
|
||||||
|
weak var delegate: SSIDOptionEditTableViewControllerDelegate?
|
||||||
|
|
||||||
|
private var sections = [Section]()
|
||||||
|
private var addSSIDRows = [AddSSIDRow]()
|
||||||
|
|
||||||
|
let ssidOptionFields: [ActivateOnDemandViewModel.OnDemandSSIDOption] = [
|
||||||
|
.anySSID,
|
||||||
|
.onlySpecificSSIDs,
|
||||||
|
.exceptSpecificSSIDs
|
||||||
|
]
|
||||||
|
|
||||||
|
var selectedOption: ActivateOnDemandViewModel.OnDemandSSIDOption
|
||||||
|
var selectedSSIDs: [String]
|
||||||
|
var connectedSSID: String?
|
||||||
|
|
||||||
|
init(option: ActivateOnDemandViewModel.OnDemandSSIDOption, ssids: [String]) {
|
||||||
|
selectedOption = option
|
||||||
|
selectedSSIDs = ssids
|
||||||
|
super.init(style: .grouped)
|
||||||
|
connectedSSID = getConnectedSSID()
|
||||||
|
loadSections()
|
||||||
|
loadAddSSIDRows()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
title = tr("tunnelOnDemandSSIDViewTitle")
|
||||||
|
|
||||||
|
tableView.estimatedRowHeight = 44
|
||||||
|
tableView.rowHeight = UITableView.automaticDimension
|
||||||
|
|
||||||
|
tableView.register(CheckmarkCell.self)
|
||||||
|
tableView.register(EditableTextCell.self)
|
||||||
|
tableView.register(TextCell.self)
|
||||||
|
tableView.isEditing = true
|
||||||
|
tableView.allowsSelectionDuringEditing = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadSections() {
|
||||||
|
sections.removeAll()
|
||||||
|
sections.append(.ssidOption)
|
||||||
|
if selectedOption != .anySSID {
|
||||||
|
sections.append(.selectedSSIDs)
|
||||||
|
sections.append(.addSSIDs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadAddSSIDRows() {
|
||||||
|
addSSIDRows.removeAll()
|
||||||
|
if let connectedSSID = connectedSSID {
|
||||||
|
if !selectedSSIDs.contains(connectedSSID) {
|
||||||
|
addSSIDRows.append(.addConnectedSSID(connectedSSID: connectedSSID))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addSSIDRows.append(.addNewSSID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateTableViewAddSSIDRows() {
|
||||||
|
guard let addSSIDSection = sections.firstIndex(of: .addSSIDs) else { return }
|
||||||
|
let numberOfAddSSIDRows = addSSIDRows.count
|
||||||
|
let numberOfAddSSIDRowsInTableView = tableView.numberOfRows(inSection: addSSIDSection)
|
||||||
|
switch (numberOfAddSSIDRowsInTableView, numberOfAddSSIDRows) {
|
||||||
|
case (1, 2):
|
||||||
|
tableView.insertRows(at: [IndexPath(row: 0, section: addSSIDSection)], with: .automatic)
|
||||||
|
case (2, 1):
|
||||||
|
tableView.deleteRows(at: [IndexPath(row: 0, section: addSSIDSection)], with: .automatic)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillDisappear(_ animated: Bool) {
|
||||||
|
delegate?.ssidOptionSaved(option: selectedOption, ssids: selectedSSIDs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SSIDOptionEditTableViewController {
|
||||||
|
override func numberOfSections(in tableView: UITableView) -> Int {
|
||||||
|
return sections.count
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||||
|
switch sections[section] {
|
||||||
|
case .ssidOption:
|
||||||
|
return ssidOptionFields.count
|
||||||
|
case .selectedSSIDs:
|
||||||
|
return selectedSSIDs.isEmpty ? 1 : selectedSSIDs.count
|
||||||
|
case .addSSIDs:
|
||||||
|
return addSSIDRows.count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
|
switch sections[indexPath.section] {
|
||||||
|
case .ssidOption:
|
||||||
|
return ssidOptionCell(for: tableView, at: indexPath)
|
||||||
|
case .selectedSSIDs:
|
||||||
|
if !selectedSSIDs.isEmpty {
|
||||||
|
return selectedSSIDCell(for: tableView, at: indexPath)
|
||||||
|
} else {
|
||||||
|
return noSSIDsCell(for: tableView, at: indexPath)
|
||||||
|
}
|
||||||
|
case .addSSIDs:
|
||||||
|
return addSSIDCell(for: tableView, at: indexPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
||||||
|
switch sections[indexPath.section] {
|
||||||
|
case .ssidOption:
|
||||||
|
return false
|
||||||
|
case .selectedSSIDs:
|
||||||
|
return !selectedSSIDs.isEmpty
|
||||||
|
case .addSSIDs:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
|
||||||
|
switch sections[indexPath.section] {
|
||||||
|
case .ssidOption:
|
||||||
|
return .none
|
||||||
|
case .selectedSSIDs:
|
||||||
|
return .delete
|
||||||
|
case .addSSIDs:
|
||||||
|
return .insert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||||
|
switch sections[section] {
|
||||||
|
case .ssidOption:
|
||||||
|
return nil
|
||||||
|
case .selectedSSIDs:
|
||||||
|
return tr("tunnelOnDemandSectionTitleSelectedSSIDs")
|
||||||
|
case .addSSIDs:
|
||||||
|
return tr("tunnelOnDemandSectionTitleAddSSIDs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func ssidOptionCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
||||||
|
let field = ssidOptionFields[indexPath.row]
|
||||||
|
let cell: CheckmarkCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
|
cell.message = field.localizedUIString
|
||||||
|
cell.isChecked = selectedOption == field
|
||||||
|
cell.isEditing = false
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
private func noSSIDsCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
||||||
|
let cell: TextCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
|
cell.message = tr("tunnelOnDemandNoSSIDs")
|
||||||
|
cell.setTextColor(.gray)
|
||||||
|
cell.setTextAlignment(.center)
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
private func selectedSSIDCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
||||||
|
let cell: EditableTextCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
|
cell.message = selectedSSIDs[indexPath.row]
|
||||||
|
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.updateTableViewAddSSIDRows()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
private func addSSIDCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
||||||
|
let cell: TextCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
|
switch addSSIDRows[indexPath.row] {
|
||||||
|
case .addConnectedSSID:
|
||||||
|
cell.message = tr(format: "tunnelOnDemandAddMessageAddConnectedSSID (%@)", connectedSSID!)
|
||||||
|
case .addNewSSID:
|
||||||
|
cell.message = tr("tunnelOnDemandAddMessageAddNewSSID")
|
||||||
|
}
|
||||||
|
cell.isEditing = true
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
|
||||||
|
switch sections[indexPath.section] {
|
||||||
|
case .ssidOption:
|
||||||
|
assertionFailure()
|
||||||
|
case .selectedSSIDs:
|
||||||
|
assert(editingStyle == .delete)
|
||||||
|
selectedSSIDs.remove(at: indexPath.row)
|
||||||
|
if !selectedSSIDs.isEmpty {
|
||||||
|
tableView.deleteRows(at: [indexPath], with: .automatic)
|
||||||
|
} else {
|
||||||
|
tableView.reloadRows(at: [indexPath], with: .automatic)
|
||||||
|
}
|
||||||
|
loadAddSSIDRows()
|
||||||
|
updateTableViewAddSSIDRows()
|
||||||
|
case .addSSIDs:
|
||||||
|
assert(editingStyle == .insert)
|
||||||
|
let newSSID: String
|
||||||
|
switch addSSIDRows[indexPath.row] {
|
||||||
|
case .addConnectedSSID(let connectedSSID):
|
||||||
|
newSSID = connectedSSID
|
||||||
|
case .addNewSSID:
|
||||||
|
newSSID = ""
|
||||||
|
}
|
||||||
|
selectedSSIDs.append(newSSID)
|
||||||
|
loadSections()
|
||||||
|
let selectedSSIDsSection = sections.firstIndex(of: .selectedSSIDs)!
|
||||||
|
let indexPath = IndexPath(row: selectedSSIDs.count - 1, section: selectedSSIDsSection)
|
||||||
|
if selectedSSIDs.count == 1 {
|
||||||
|
tableView.reloadRows(at: [indexPath], with: .automatic)
|
||||||
|
} else {
|
||||||
|
tableView.insertRows(at: [indexPath], with: .automatic)
|
||||||
|
}
|
||||||
|
loadAddSSIDRows()
|
||||||
|
updateTableViewAddSSIDRows()
|
||||||
|
if newSSID.isEmpty {
|
||||||
|
if let selectedSSIDCell = tableView.cellForRow(at: indexPath) as? EditableTextCell {
|
||||||
|
selectedSSIDCell.beginEditing()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SSIDOptionEditTableViewController {
|
||||||
|
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
|
||||||
|
switch sections[indexPath.section] {
|
||||||
|
case .ssidOption:
|
||||||
|
return indexPath
|
||||||
|
case .selectedSSIDs, .addSSIDs:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
|
switch sections[indexPath.section] {
|
||||||
|
case .ssidOption:
|
||||||
|
let previousOption = selectedOption
|
||||||
|
selectedOption = ssidOptionFields[indexPath.row]
|
||||||
|
loadSections()
|
||||||
|
if previousOption == .anySSID {
|
||||||
|
let indexSet = IndexSet(1 ... 2)
|
||||||
|
tableView.insertSections(indexSet, with: .fade)
|
||||||
|
}
|
||||||
|
if selectedOption == .anySSID {
|
||||||
|
let indexSet = IndexSet(1 ... 2)
|
||||||
|
tableView.deleteSections(indexSet, with: .fade)
|
||||||
|
}
|
||||||
|
tableView.reloadSections(IndexSet(integer: indexPath.section), with: .none)
|
||||||
|
case .selectedSSIDs, .addSSIDs:
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -10,14 +10,14 @@ class SettingsTableViewController: UITableViewController {
|
|||||||
case iosAppVersion
|
case iosAppVersion
|
||||||
case goBackendVersion
|
case goBackendVersion
|
||||||
case exportZipArchive
|
case exportZipArchive
|
||||||
case exportLogFile
|
case viewLog
|
||||||
|
|
||||||
var localizedUIString: String {
|
var localizedUIString: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .iosAppVersion: return tr("settingsVersionKeyWireGuardForIOS")
|
case .iosAppVersion: return tr("settingsVersionKeyWireGuardForIOS")
|
||||||
case .goBackendVersion: return tr("settingsVersionKeyWireGuardGoBackend")
|
case .goBackendVersion: return tr("settingsVersionKeyWireGuardGoBackend")
|
||||||
case .exportZipArchive: return tr("settingsExportZipButtonTitle")
|
case .exportZipArchive: return tr("settingsExportZipButtonTitle")
|
||||||
case .exportLogFile: return tr("settingsExportLogFileButtonTitle")
|
case .viewLog: return tr("settingsViewLogButtonTitle")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,7 @@ class SettingsTableViewController: UITableViewController {
|
|||||||
let settingsFieldsBySection: [[SettingsFields]] = [
|
let settingsFieldsBySection: [[SettingsFields]] = [
|
||||||
[.iosAppVersion, .goBackendVersion],
|
[.iosAppVersion, .goBackendVersion],
|
||||||
[.exportZipArchive],
|
[.exportZipArchive],
|
||||||
[.exportLogFile]
|
[.viewLog]
|
||||||
]
|
]
|
||||||
|
|
||||||
let tunnelsManager: TunnelsManager?
|
let tunnelsManager: TunnelsManager?
|
||||||
@@ -108,46 +108,10 @@ class SettingsTableViewController: UITableViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func exportLogForLastActivatedTunnel(sourceView: UIView) {
|
func presentLogView() {
|
||||||
guard let destinationDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
|
let logVC = LogViewController()
|
||||||
|
navigationController?.pushViewController(logVC, animated: true)
|
||||||
|
|
||||||
let dateFormatter = ISO8601DateFormatter()
|
|
||||||
dateFormatter.formatOptions = [.withFullDate, .withTime, .withTimeZone] // Avoid ':' in the filename
|
|
||||||
let timeStampString = dateFormatter.string(from: Date())
|
|
||||||
let destinationURL = destinationDir.appendingPathComponent("wireguard-log-\(timeStampString).txt")
|
|
||||||
|
|
||||||
DispatchQueue.global(qos: .userInitiated).async {
|
|
||||||
|
|
||||||
if FileManager.default.fileExists(atPath: destinationURL.path) {
|
|
||||||
let isDeleted = FileManager.deleteFile(at: destinationURL)
|
|
||||||
if !isDeleted {
|
|
||||||
ErrorPresenter.showErrorAlert(title: tr("alertUnableToRemovePreviousLogTitle"), message: tr("alertUnableToRemovePreviousLogMessage"), from: self)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let networkExtensionLogFilePath = FileManager.networkExtensionLogFileURL?.path else {
|
|
||||||
ErrorPresenter.showErrorAlert(title: tr("alertUnableToFindExtensionLogPathTitle"), message: tr("alertUnableToFindExtensionLogPathMessage"), from: self)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let isWritten = Logger.global?.writeLog(called: "APP", mergedWith: networkExtensionLogFilePath, called: "NET", to: destinationURL.path) ?? false
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
guard isWritten else {
|
|
||||||
ErrorPresenter.showErrorAlert(title: tr("alertUnableToWriteLogTitle"), message: tr("alertUnableToWriteLogMessage"), from: self)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let activityVC = UIActivityViewController(activityItems: [destinationURL], applicationActivities: nil)
|
|
||||||
activityVC.popoverPresentationController?.sourceView = sourceView
|
|
||||||
activityVC.popoverPresentationController?.sourceRect = sourceView.bounds
|
|
||||||
activityVC.completionWithItemsHandler = { _, _, _, _ in
|
|
||||||
// Remove the exported log file after the activity has completed
|
|
||||||
_ = FileManager.deleteFile(at: destinationURL)
|
|
||||||
}
|
|
||||||
self.present(activityVC, animated: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,11 +161,11 @@ extension SettingsTableViewController {
|
|||||||
}
|
}
|
||||||
return cell
|
return cell
|
||||||
} else {
|
} else {
|
||||||
assert(field == .exportLogFile)
|
assert(field == .viewLog)
|
||||||
let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath)
|
let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
cell.buttonText = field.localizedUIString
|
cell.buttonText = field.localizedUIString
|
||||||
cell.onTapped = { [weak self] in
|
cell.onTapped = { [weak self] in
|
||||||
self?.exportLogForLastActivatedTunnel(sourceView: cell.button)
|
self?.presentLogView()
|
||||||
}
|
}
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,21 +24,28 @@ class TunnelDetailTableViewController: UITableViewController {
|
|||||||
.rxBytes, .txBytes, .lastHandshakeTime
|
.rxBytes, .txBytes, .lastHandshakeTime
|
||||||
]
|
]
|
||||||
|
|
||||||
|
static let onDemandFields: [ActivateOnDemandViewModel.OnDemandField] = [
|
||||||
|
.onDemand, .ssid
|
||||||
|
]
|
||||||
|
|
||||||
let tunnelsManager: TunnelsManager
|
let tunnelsManager: TunnelsManager
|
||||||
let tunnel: TunnelContainer
|
let tunnel: TunnelContainer
|
||||||
var tunnelViewModel: TunnelViewModel
|
var tunnelViewModel: TunnelViewModel
|
||||||
|
var onDemandViewModel: ActivateOnDemandViewModel
|
||||||
|
|
||||||
private var sections = [Section]()
|
private var sections = [Section]()
|
||||||
private var interfaceFieldIsVisible = [Bool]()
|
private var interfaceFieldIsVisible = [Bool]()
|
||||||
private var peerFieldIsVisible = [[Bool]]()
|
private var peerFieldIsVisible = [[Bool]]()
|
||||||
|
|
||||||
private var statusObservationToken: AnyObject?
|
private var statusObservationToken: AnyObject?
|
||||||
|
private var onDemandObservationToken: AnyObject?
|
||||||
private var reloadRuntimeConfigurationTimer: Timer?
|
private var reloadRuntimeConfigurationTimer: Timer?
|
||||||
|
|
||||||
init(tunnelsManager: TunnelsManager, tunnel: TunnelContainer) {
|
init(tunnelsManager: TunnelsManager, tunnel: TunnelContainer) {
|
||||||
self.tunnelsManager = tunnelsManager
|
self.tunnelsManager = tunnelsManager
|
||||||
self.tunnel = tunnel
|
self.tunnel = tunnel
|
||||||
tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration)
|
tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration)
|
||||||
|
onDemandViewModel = ActivateOnDemandViewModel(tunnel: tunnel)
|
||||||
super.init(style: .grouped)
|
super.init(style: .grouped)
|
||||||
loadSections()
|
loadSections()
|
||||||
loadVisibleFields()
|
loadVisibleFields()
|
||||||
@@ -51,6 +58,11 @@ class TunnelDetailTableViewController: UITableViewController {
|
|||||||
self.stopUpdatingRuntimeConfiguration()
|
self.stopUpdatingRuntimeConfiguration()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onDemandObservationToken = tunnel.observe(\.isActivateOnDemandEnabled) { [weak self] tunnel, _ in
|
||||||
|
// Handle On-Demand getting turned on/off outside of the app
|
||||||
|
self?.onDemandViewModel = ActivateOnDemandViewModel(tunnel: tunnel)
|
||||||
|
self?.updateActivateOnDemandFields()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
@@ -64,10 +76,10 @@ class TunnelDetailTableViewController: UITableViewController {
|
|||||||
|
|
||||||
tableView.estimatedRowHeight = 44
|
tableView.estimatedRowHeight = 44
|
||||||
tableView.rowHeight = UITableView.automaticDimension
|
tableView.rowHeight = UITableView.automaticDimension
|
||||||
tableView.allowsSelection = false
|
|
||||||
tableView.register(SwitchCell.self)
|
tableView.register(SwitchCell.self)
|
||||||
tableView.register(KeyValueCell.self)
|
tableView.register(KeyValueCell.self)
|
||||||
tableView.register(ButtonCell.self)
|
tableView.register(ButtonCell.self)
|
||||||
|
tableView.register(ChevronCell.self)
|
||||||
|
|
||||||
restorationIdentifier = "TunnelDetailVC:\(tunnel.name)"
|
restorationIdentifier = "TunnelDetailVC:\(tunnel.name)"
|
||||||
}
|
}
|
||||||
@@ -113,21 +125,6 @@ class TunnelDetailTableViewController: UITableViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func showConfirmationAlert(message: String, buttonTitle: String, from sourceView: UIView, onConfirmed: @escaping (() -> Void)) {
|
|
||||||
let destroyAction = UIAlertAction(title: buttonTitle, style: .destructive) { _ in
|
|
||||||
onConfirmed()
|
|
||||||
}
|
|
||||||
let cancelAction = UIAlertAction(title: tr("actionCancel"), style: .cancel)
|
|
||||||
let alert = UIAlertController(title: "", message: message, preferredStyle: .actionSheet)
|
|
||||||
alert.addAction(destroyAction)
|
|
||||||
alert.addAction(cancelAction)
|
|
||||||
|
|
||||||
alert.popoverPresentationController?.sourceView = sourceView
|
|
||||||
alert.popoverPresentationController?.sourceRect = sourceView.bounds
|
|
||||||
|
|
||||||
present(alert, animated: true, completion: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func startUpdatingRuntimeConfiguration() {
|
func startUpdatingRuntimeConfiguration() {
|
||||||
reloadRuntimeConfiguration()
|
reloadRuntimeConfiguration()
|
||||||
reloadRuntimeConfigurationTimer?.invalidate()
|
reloadRuntimeConfigurationTimer?.invalidate()
|
||||||
@@ -158,16 +155,21 @@ class TunnelDetailTableViewController: UITableViewController {
|
|||||||
var interfaceFieldIsVisible = self.interfaceFieldIsVisible
|
var interfaceFieldIsVisible = self.interfaceFieldIsVisible
|
||||||
var peerFieldIsVisible = self.peerFieldIsVisible
|
var peerFieldIsVisible = self.peerFieldIsVisible
|
||||||
|
|
||||||
func sectionChanged<T>(fields: [T], fieldIsVisible fieldIsVisibleInput: [Bool], tableView: UITableView, section: Int, changes: [T: TunnelViewModel.ChangeHandlers.FieldChange]) {
|
func handleSectionFieldsModified<T>(fields: [T], fieldIsVisible: [Bool], section: Int, changes: [T: TunnelViewModel.Changes.FieldChange]) {
|
||||||
|
for (index, field) in fields.enumerated() {
|
||||||
|
guard let change = changes[field] else { continue }
|
||||||
|
if case .modified(let newValue) = change {
|
||||||
|
let row = fieldIsVisible[0 ..< index].filter { $0 }.count
|
||||||
|
let indexPath = IndexPath(row: row, section: section)
|
||||||
|
if let cell = tableView.cellForRow(at: indexPath) as? KeyValueCell {
|
||||||
|
cell.value = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSectionRowsInsertedOrRemoved<T>(fields: [T], fieldIsVisible fieldIsVisibleInput: [Bool], section: Int, changes: [T: TunnelViewModel.Changes.FieldChange]) {
|
||||||
var fieldIsVisible = fieldIsVisibleInput
|
var fieldIsVisible = fieldIsVisibleInput
|
||||||
var modifiedIndexPaths = [IndexPath]()
|
|
||||||
for (index, field) in fields.enumerated() where changes[field] == .modified {
|
|
||||||
let row = fieldIsVisible[0 ..< index].filter { $0 }.count
|
|
||||||
modifiedIndexPaths.append(IndexPath(row: row, section: section))
|
|
||||||
}
|
|
||||||
if !modifiedIndexPaths.isEmpty {
|
|
||||||
tableView.reloadRows(at: modifiedIndexPaths, with: .none)
|
|
||||||
}
|
|
||||||
|
|
||||||
var removedIndexPaths = [IndexPath]()
|
var removedIndexPaths = [IndexPath]()
|
||||||
for (index, field) in fields.enumerated().reversed() where changes[field] == .removed {
|
for (index, field) in fields.enumerated().reversed() where changes[field] == .removed {
|
||||||
@@ -190,30 +192,44 @@ class TunnelDetailTableViewController: UITableViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let changeHandlers = TunnelViewModel.ChangeHandlers(
|
let changes = self.tunnelViewModel.applyConfiguration(other: tunnelConfiguration)
|
||||||
interfaceChanged: { changes in
|
|
||||||
sectionChanged(fields: TunnelDetailTableViewController.interfaceFields, fieldIsVisible: interfaceFieldIsVisible,
|
|
||||||
tableView: tableView, section: interfaceSectionIndex, changes: changes)
|
|
||||||
},
|
|
||||||
peerChangedAt: { peerIndex, changes in
|
|
||||||
sectionChanged(fields: TunnelDetailTableViewController.peerFields, fieldIsVisible: peerFieldIsVisible[peerIndex],
|
|
||||||
tableView: tableView, section: firstPeerSectionIndex + peerIndex, changes: changes)
|
|
||||||
},
|
|
||||||
peersRemovedAt: { peerIndices in
|
|
||||||
let sectionIndices = peerIndices.map { firstPeerSectionIndex + $0 }
|
|
||||||
tableView.deleteSections(IndexSet(sectionIndices), with: .automatic)
|
|
||||||
},
|
|
||||||
peersInsertedAt: { peerIndices in
|
|
||||||
let sectionIndices = peerIndices.map { firstPeerSectionIndex + $0 }
|
|
||||||
tableView.insertSections(IndexSet(sectionIndices), with: .automatic)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
tableView.beginUpdates()
|
if !changes.interfaceChanges.isEmpty {
|
||||||
self.tunnelViewModel.applyConfiguration(other: tunnelConfiguration, changeHandlers: changeHandlers)
|
handleSectionFieldsModified(fields: TunnelDetailTableViewController.interfaceFields, fieldIsVisible: interfaceFieldIsVisible,
|
||||||
self.loadSections()
|
section: interfaceSectionIndex, changes: changes.interfaceChanges)
|
||||||
self.loadVisibleFields()
|
}
|
||||||
tableView.endUpdates()
|
for (peerIndex, peerChanges) in changes.peerChanges {
|
||||||
|
handleSectionFieldsModified(fields: TunnelDetailTableViewController.peerFields, fieldIsVisible: peerFieldIsVisible[peerIndex], section: firstPeerSectionIndex + peerIndex, changes: peerChanges)
|
||||||
|
}
|
||||||
|
|
||||||
|
let isAnyInterfaceFieldAddedOrRemoved = changes.interfaceChanges.contains { $0.value == .added || $0.value == .removed }
|
||||||
|
let isAnyPeerFieldAddedOrRemoved = changes.peerChanges.contains { $0.changes.contains { $0.value == .added || $0.value == .removed } }
|
||||||
|
let peersRemovedSectionIndices = changes.peersRemovedIndices.map { firstPeerSectionIndex + $0 }
|
||||||
|
let peersInsertedSectionIndices = changes.peersInsertedIndices.map { firstPeerSectionIndex + $0 }
|
||||||
|
|
||||||
|
if isAnyInterfaceFieldAddedOrRemoved || isAnyPeerFieldAddedOrRemoved || !peersRemovedSectionIndices.isEmpty || !peersInsertedSectionIndices.isEmpty {
|
||||||
|
tableView.beginUpdates()
|
||||||
|
if isAnyInterfaceFieldAddedOrRemoved {
|
||||||
|
handleSectionRowsInsertedOrRemoved(fields: TunnelDetailTableViewController.interfaceFields, fieldIsVisible: interfaceFieldIsVisible, section: interfaceSectionIndex, changes: changes.interfaceChanges)
|
||||||
|
}
|
||||||
|
if isAnyPeerFieldAddedOrRemoved {
|
||||||
|
for (peerIndex, peerChanges) in changes.peerChanges {
|
||||||
|
handleSectionRowsInsertedOrRemoved(fields: TunnelDetailTableViewController.peerFields, fieldIsVisible: peerFieldIsVisible[peerIndex], section: firstPeerSectionIndex + peerIndex, changes: peerChanges)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !peersRemovedSectionIndices.isEmpty {
|
||||||
|
tableView.deleteSections(IndexSet(peersRemovedSectionIndices), with: .automatic)
|
||||||
|
}
|
||||||
|
if !peersInsertedSectionIndices.isEmpty {
|
||||||
|
tableView.insertSections(IndexSet(peersInsertedSectionIndices), with: .automatic)
|
||||||
|
}
|
||||||
|
self.loadSections()
|
||||||
|
self.loadVisibleFields()
|
||||||
|
tableView.endUpdates()
|
||||||
|
} else {
|
||||||
|
self.loadSections()
|
||||||
|
self.loadVisibleFields()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func reloadRuntimeConfiguration() {
|
private func reloadRuntimeConfiguration() {
|
||||||
@@ -223,11 +239,27 @@ class TunnelDetailTableViewController: UITableViewController {
|
|||||||
self.applyTunnelConfiguration(tunnelConfiguration: tunnelConfiguration)
|
self.applyTunnelConfiguration(tunnelConfiguration: tunnelConfiguration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func updateActivateOnDemandFields() {
|
||||||
|
guard let onDemandSection = sections.firstIndex(where: { if case .onDemand = $0 { return true } else { return false } }) else { return }
|
||||||
|
let numberOfTableViewOnDemandRows = tableView.numberOfRows(inSection: onDemandSection)
|
||||||
|
let ssidRowIndexPath = IndexPath(row: 1, section: onDemandSection)
|
||||||
|
switch (numberOfTableViewOnDemandRows, onDemandViewModel.isWiFiInterfaceEnabled) {
|
||||||
|
case (1, true):
|
||||||
|
tableView.insertRows(at: [ssidRowIndexPath], with: .automatic)
|
||||||
|
case (2, false):
|
||||||
|
tableView.deleteRows(at: [ssidRowIndexPath], with: .automatic)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
tableView.reloadSections(IndexSet(integer: onDemandSection), with: .automatic)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TunnelDetailTableViewController: TunnelEditTableViewControllerDelegate {
|
extension TunnelDetailTableViewController: TunnelEditTableViewControllerDelegate {
|
||||||
func tunnelSaved(tunnel: TunnelContainer) {
|
func tunnelSaved(tunnel: TunnelContainer) {
|
||||||
tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration)
|
tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration)
|
||||||
|
onDemandViewModel = ActivateOnDemandViewModel(tunnel: tunnel)
|
||||||
loadSections()
|
loadSections()
|
||||||
loadVisibleFields()
|
loadVisibleFields()
|
||||||
title = tunnel.name
|
title = tunnel.name
|
||||||
@@ -253,7 +285,7 @@ extension TunnelDetailTableViewController {
|
|||||||
case .peer(let peerIndex, _):
|
case .peer(let peerIndex, _):
|
||||||
return peerFieldIsVisible[peerIndex].filter { $0 }.count
|
return peerFieldIsVisible[peerIndex].filter { $0 }.count
|
||||||
case .onDemand:
|
case .onDemand:
|
||||||
return 1
|
return onDemandViewModel.isWiFiInterfaceEnabled ? 2 : 1
|
||||||
case .delete:
|
case .delete:
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
@@ -351,6 +383,8 @@ extension TunnelDetailTableViewController {
|
|||||||
cell.key = field.localizedUIString
|
cell.key = field.localizedUIString
|
||||||
if field == .persistentKeepAlive {
|
if field == .persistentKeepAlive {
|
||||||
cell.value = tr(format: "tunnelPeerPersistentKeepaliveValue (%@)", peerData[field])
|
cell.value = tr(format: "tunnelPeerPersistentKeepaliveValue (%@)", peerData[field])
|
||||||
|
} else if field == .preSharedKey {
|
||||||
|
cell.value = tr("tunnelPeerPresharedKeyEnabled")
|
||||||
} else {
|
} else {
|
||||||
cell.value = peerData[field]
|
cell.value = peerData[field]
|
||||||
}
|
}
|
||||||
@@ -358,13 +392,26 @@ extension TunnelDetailTableViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func onDemandCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
private func onDemandCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
||||||
let cell: KeyValueCell = tableView.dequeueReusableCell(for: indexPath)
|
let field = TunnelDetailTableViewController.onDemandFields[indexPath.row]
|
||||||
cell.key = tr("tunnelOnDemandKey")
|
if field == .onDemand {
|
||||||
cell.value = TunnelViewModel.activateOnDemandDetailText(for: tunnel.activateOnDemandSetting)
|
let cell: KeyValueCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
cell.observationToken = tunnel.observe(\.isActivateOnDemandEnabled) { [weak cell] tunnel, _ in
|
cell.key = field.localizedUIString
|
||||||
cell?.value = TunnelViewModel.activateOnDemandDetailText(for: tunnel.activateOnDemandSetting)
|
cell.value = onDemandViewModel.localizedInterfaceDescription
|
||||||
|
return cell
|
||||||
|
} else {
|
||||||
|
assert(field == .ssid)
|
||||||
|
if onDemandViewModel.ssidOption == .anySSID {
|
||||||
|
let cell: KeyValueCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
|
cell.key = field.localizedUIString
|
||||||
|
cell.value = onDemandViewModel.ssidOption.localizedUIString
|
||||||
|
return cell
|
||||||
|
} else {
|
||||||
|
let cell: ChevronCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
|
cell.message = field.localizedUIString
|
||||||
|
cell.detailMessage = onDemandViewModel.localizedSSIDDescription
|
||||||
|
return cell
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return cell
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func deleteConfigurationCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
private func deleteConfigurationCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
||||||
@@ -373,7 +420,9 @@ extension TunnelDetailTableViewController {
|
|||||||
cell.hasDestructiveAction = true
|
cell.hasDestructiveAction = true
|
||||||
cell.onTapped = { [weak self] in
|
cell.onTapped = { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
self.showConfirmationAlert(message: tr("deleteTunnelConfirmationAlertMessage"), buttonTitle: tr("deleteTunnelConfirmationAlertButtonTitle"), from: cell) { [weak self] in
|
ConfirmationAlertPresenter.showConfirmationAlert(message: tr("deleteTunnelConfirmationAlertMessage"),
|
||||||
|
buttonTitle: tr("deleteTunnelConfirmationAlertButtonTitle"),
|
||||||
|
from: cell, presentingVC: self) { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
self.tunnelsManager.remove(tunnel: self.tunnel) { error in
|
self.tunnelsManager.remove(tunnel: self.tunnel) { error in
|
||||||
if error != nil {
|
if error != nil {
|
||||||
@@ -387,3 +436,22 @@ extension TunnelDetailTableViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension TunnelDetailTableViewController {
|
||||||
|
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
|
||||||
|
if case .onDemand = sections[indexPath.section],
|
||||||
|
case .ssid = TunnelDetailTableViewController.onDemandFields[indexPath.row] {
|
||||||
|
return indexPath
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
|
if case .onDemand = sections[indexPath.section],
|
||||||
|
case .ssid = TunnelDetailTableViewController.onDemandFields[indexPath.row] {
|
||||||
|
let ssidDetailVC = SSIDOptionDetailTableViewController(title: onDemandViewModel.ssidOption.localizedUIString, ssids: onDemandViewModel.selectedSSIDs)
|
||||||
|
navigationController?.pushViewController(ssidDetailVC, animated: true)
|
||||||
|
}
|
||||||
|
tableView.deselectRow(at: indexPath, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -43,16 +43,16 @@ class TunnelEditTableViewController: UITableViewController {
|
|||||||
.deletePeer
|
.deletePeer
|
||||||
]
|
]
|
||||||
|
|
||||||
let activateOnDemandOptions: [ActivateOnDemandOption] = [
|
let onDemandFields: [ActivateOnDemandViewModel.OnDemandField] = [
|
||||||
.useOnDemandOverWiFiOrCellular,
|
.nonWiFiInterface,
|
||||||
.useOnDemandOverWiFiOnly,
|
.wiFiInterface,
|
||||||
.useOnDemandOverCellularOnly
|
.ssid
|
||||||
]
|
]
|
||||||
|
|
||||||
let tunnelsManager: TunnelsManager
|
let tunnelsManager: TunnelsManager
|
||||||
let tunnel: TunnelContainer?
|
let tunnel: TunnelContainer?
|
||||||
let tunnelViewModel: TunnelViewModel
|
let tunnelViewModel: TunnelViewModel
|
||||||
var activateOnDemandSetting: ActivateOnDemandSetting
|
var onDemandViewModel: ActivateOnDemandViewModel
|
||||||
private var sections = [Section]()
|
private var sections = [Section]()
|
||||||
|
|
||||||
// Use this initializer to edit an existing tunnel.
|
// Use this initializer to edit an existing tunnel.
|
||||||
@@ -60,7 +60,7 @@ class TunnelEditTableViewController: UITableViewController {
|
|||||||
self.tunnelsManager = tunnelsManager
|
self.tunnelsManager = tunnelsManager
|
||||||
self.tunnel = tunnel
|
self.tunnel = tunnel
|
||||||
tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration)
|
tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration)
|
||||||
activateOnDemandSetting = tunnel.activateOnDemandSetting
|
onDemandViewModel = ActivateOnDemandViewModel(tunnel: tunnel)
|
||||||
super.init(style: .grouped)
|
super.init(style: .grouped)
|
||||||
loadSections()
|
loadSections()
|
||||||
}
|
}
|
||||||
@@ -70,7 +70,7 @@ class TunnelEditTableViewController: UITableViewController {
|
|||||||
self.tunnelsManager = tunnelsManager
|
self.tunnelsManager = tunnelsManager
|
||||||
tunnel = nil
|
tunnel = nil
|
||||||
tunnelViewModel = TunnelViewModel(tunnelConfiguration: nil)
|
tunnelViewModel = TunnelViewModel(tunnelConfiguration: nil)
|
||||||
activateOnDemandSetting = ActivateOnDemandSetting.defaultSetting
|
onDemandViewModel = ActivateOnDemandViewModel()
|
||||||
super.init(style: .grouped)
|
super.init(style: .grouped)
|
||||||
loadSections()
|
loadSections()
|
||||||
}
|
}
|
||||||
@@ -92,7 +92,7 @@ class TunnelEditTableViewController: UITableViewController {
|
|||||||
tableView.register(TunnelEditEditableKeyValueCell.self)
|
tableView.register(TunnelEditEditableKeyValueCell.self)
|
||||||
tableView.register(ButtonCell.self)
|
tableView.register(ButtonCell.self)
|
||||||
tableView.register(SwitchCell.self)
|
tableView.register(SwitchCell.self)
|
||||||
tableView.register(CheckmarkCell.self)
|
tableView.register(ChevronCell.self)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func loadSections() {
|
private func loadSections() {
|
||||||
@@ -113,9 +113,10 @@ class TunnelEditTableViewController: UITableViewController {
|
|||||||
ErrorPresenter.showErrorAlert(title: alertTitle, message: errorMessage, from: self)
|
ErrorPresenter.showErrorAlert(title: alertTitle, message: errorMessage, from: self)
|
||||||
tableView.reloadData() // Highlight erroring fields
|
tableView.reloadData() // Highlight erroring fields
|
||||||
case .saved(let tunnelConfiguration):
|
case .saved(let tunnelConfiguration):
|
||||||
|
let onDemandOption = onDemandViewModel.toOnDemandOption()
|
||||||
if let tunnel = tunnel {
|
if let tunnel = tunnel {
|
||||||
// We're modifying an existing tunnel
|
// We're modifying an existing tunnel
|
||||||
tunnelsManager.modify(tunnel: tunnel, tunnelConfiguration: tunnelConfiguration, activateOnDemandSetting: activateOnDemandSetting) { [weak self] error in
|
tunnelsManager.modify(tunnel: tunnel, tunnelConfiguration: tunnelConfiguration, onDemandOption: onDemandOption) { [weak self] error in
|
||||||
if let error = error {
|
if let error = error {
|
||||||
ErrorPresenter.showErrorAlert(error: error, from: self)
|
ErrorPresenter.showErrorAlert(error: error, from: self)
|
||||||
} else {
|
} else {
|
||||||
@@ -125,7 +126,7 @@ class TunnelEditTableViewController: UITableViewController {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// We're adding a new tunnel
|
// We're adding a new tunnel
|
||||||
tunnelsManager.add(tunnelConfiguration: tunnelConfiguration, activateOnDemandSetting: activateOnDemandSetting) { [weak self] result in
|
tunnelsManager.add(tunnelConfiguration: tunnelConfiguration, onDemandOption: onDemandOption) { [weak self] result in
|
||||||
if let error = result.error {
|
if let error = result.error {
|
||||||
ErrorPresenter.showErrorAlert(error: error, from: self)
|
ErrorPresenter.showErrorAlert(error: error, from: self)
|
||||||
} else {
|
} else {
|
||||||
@@ -161,10 +162,10 @@ extension TunnelEditTableViewController {
|
|||||||
case .addPeer:
|
case .addPeer:
|
||||||
return 1
|
return 1
|
||||||
case .onDemand:
|
case .onDemand:
|
||||||
if activateOnDemandSetting.isActivateOnDemandEnabled {
|
if onDemandViewModel.isWiFiInterfaceEnabled {
|
||||||
return 4
|
return 3
|
||||||
} else {
|
} else {
|
||||||
return 1
|
return 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -250,6 +251,8 @@ extension TunnelEditTableViewController {
|
|||||||
cell.keyboardType = .numberPad
|
cell.keyboardType = .numberPad
|
||||||
case .publicKey, .generateKeyPair:
|
case .publicKey, .generateKeyPair:
|
||||||
cell.keyboardType = .default
|
cell.keyboardType = .default
|
||||||
|
case .status, .toggleStatus:
|
||||||
|
fatalError("Unexpected interface field")
|
||||||
}
|
}
|
||||||
|
|
||||||
cell.isValueValid = (!tunnelViewModel.interfaceData.fieldsWithError.contains(field))
|
cell.isValueValid = (!tunnelViewModel.interfaceData.fieldsWithError.contains(field))
|
||||||
@@ -259,8 +262,18 @@ extension TunnelEditTableViewController {
|
|||||||
cell.onValueBeingEdited = { [weak self] value in
|
cell.onValueBeingEdited = { [weak self] value in
|
||||||
self?.tunnelViewModel.interfaceData[field] = value
|
self?.tunnelViewModel.interfaceData[field] = value
|
||||||
}
|
}
|
||||||
|
cell.onValueChanged = { [weak self] oldValue, newValue in
|
||||||
|
guard let self = self else { return }
|
||||||
|
let isAllowedIPsChanged = self.tunnelViewModel.updateDNSServersInAllowedIPsIfRequired(oldDNSServers: oldValue, newDNSServers: newValue)
|
||||||
|
if isAllowedIPsChanged {
|
||||||
|
let section = self.sections.firstIndex { if case .peer(_) = $0 { return true } else { return false } }
|
||||||
|
if let section = section, let row = self.peerFields.firstIndex(of: .allowedIPs) {
|
||||||
|
self.tableView.reloadRows(at: [IndexPath(row: row, section: section)], with: .none)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
cell.onValueChanged = { [weak self] value in
|
cell.onValueChanged = { [weak self] _, value in
|
||||||
self?.tunnelViewModel.interfaceData[field] = value
|
self?.tunnelViewModel.interfaceData[field] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -298,7 +311,9 @@ extension TunnelEditTableViewController {
|
|||||||
cell.hasDestructiveAction = true
|
cell.hasDestructiveAction = true
|
||||||
cell.onTapped = { [weak self, weak peerData] in
|
cell.onTapped = { [weak self, weak peerData] in
|
||||||
guard let self = self, let peerData = peerData else { return }
|
guard let self = self, let peerData = peerData else { return }
|
||||||
self.showConfirmationAlert(message: tr("deletePeerConfirmationAlertMessage"), buttonTitle: tr("deletePeerConfirmationAlertButtonTitle"), from: cell) { [weak self] in
|
ConfirmationAlertPresenter.showConfirmationAlert(message: tr("deletePeerConfirmationAlertMessage"),
|
||||||
|
buttonTitle: tr("deletePeerConfirmationAlertButtonTitle"),
|
||||||
|
from: cell, presentingVC: self) { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
let removedSectionIndices = self.deletePeer(peer: peerData)
|
let removedSectionIndices = self.deletePeer(peer: peerData)
|
||||||
let shouldShowExcludePrivateIPs = (self.tunnelViewModel.peersData.count == 1 && self.tunnelViewModel.peersData[0].shouldAllowExcludePrivateIPsControl)
|
let shouldShowExcludePrivateIPs = (self.tunnelViewModel.peersData.count == 1 && self.tunnelViewModel.peersData[0].shouldAllowExcludePrivateIPsControl)
|
||||||
@@ -380,7 +395,7 @@ extension TunnelEditTableViewController {
|
|||||||
tableView.reloadRows(at: [IndexPath(row: dnsRow, section: firstInterfaceSection + interfaceSubSection)], with: .none)
|
tableView.reloadRows(at: [IndexPath(row: dnsRow, section: firstInterfaceSection + interfaceSubSection)], with: .none)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cell.onValueChanged = { [weak peerData] value in
|
cell.onValueChanged = { [weak peerData] _, value in
|
||||||
peerData?[field] = value
|
peerData?[field] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -409,36 +424,29 @@ extension TunnelEditTableViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func onDemandCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
private func onDemandCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
||||||
if indexPath.row == 0 {
|
let field = onDemandFields[indexPath.row]
|
||||||
|
if indexPath.row < 2 {
|
||||||
let cell: SwitchCell = tableView.dequeueReusableCell(for: indexPath)
|
let cell: SwitchCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
cell.message = tr("tunnelOnDemandKey")
|
cell.message = field.localizedUIString
|
||||||
cell.isOn = activateOnDemandSetting.isActivateOnDemandEnabled
|
cell.isOn = onDemandViewModel.isEnabled(field: field)
|
||||||
cell.onSwitchToggled = { [weak self] isOn in
|
cell.onSwitchToggled = { [weak self] isOn in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
guard isOn != self.activateOnDemandSetting.isActivateOnDemandEnabled else { return }
|
self.onDemandViewModel.setEnabled(field: field, isEnabled: isOn)
|
||||||
|
|
||||||
self.activateOnDemandSetting.isActivateOnDemandEnabled = isOn
|
|
||||||
self.loadSections()
|
|
||||||
|
|
||||||
let section = self.sections.firstIndex { $0 == .onDemand }!
|
let section = self.sections.firstIndex { $0 == .onDemand }!
|
||||||
let indexPaths = (1 ..< 4).map { IndexPath(row: $0, section: section) }
|
let indexPath = IndexPath(row: 2, section: section)
|
||||||
if isOn {
|
if field == .wiFiInterface {
|
||||||
if self.activateOnDemandSetting.activateOnDemandOption == .none {
|
if isOn {
|
||||||
self.activateOnDemandSetting.activateOnDemandOption = TunnelViewModel.defaultActivateOnDemandOption()
|
tableView.insertRows(at: [indexPath], with: .fade)
|
||||||
|
} else {
|
||||||
|
tableView.deleteRows(at: [indexPath], with: .fade)
|
||||||
}
|
}
|
||||||
self.tableView.insertRows(at: indexPaths, with: .fade)
|
|
||||||
} else {
|
|
||||||
self.tableView.deleteRows(at: indexPaths, with: .fade)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cell
|
return cell
|
||||||
} else {
|
} else {
|
||||||
let cell: CheckmarkCell = tableView.dequeueReusableCell(for: indexPath)
|
let cell: ChevronCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
let rowOption = activateOnDemandOptions[indexPath.row - 1]
|
cell.message = field.localizedUIString
|
||||||
let selectedOption = activateOnDemandSetting.activateOnDemandOption
|
cell.detailMessage = onDemandViewModel.localizedSSIDDescription
|
||||||
assert(selectedOption != .none)
|
|
||||||
cell.message = TunnelViewModel.activateOnDemandOptionText(for: rowOption)
|
|
||||||
cell.isChecked = selectedOption == rowOption
|
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -455,26 +463,11 @@ extension TunnelEditTableViewController {
|
|||||||
loadSections()
|
loadSections()
|
||||||
return IndexSet(integer: interfaceFieldsBySection.count + peer.index)
|
return IndexSet(integer: interfaceFieldsBySection.count + peer.index)
|
||||||
}
|
}
|
||||||
|
|
||||||
func showConfirmationAlert(message: String, buttonTitle: String, from sourceView: UIView, onConfirmed: @escaping (() -> Void)) {
|
|
||||||
let destroyAction = UIAlertAction(title: buttonTitle, style: .destructive) { _ in
|
|
||||||
onConfirmed()
|
|
||||||
}
|
|
||||||
let cancelAction = UIAlertAction(title: tr("actionCancel"), style: .cancel)
|
|
||||||
let alert = UIAlertController(title: "", message: message, preferredStyle: .actionSheet)
|
|
||||||
alert.addAction(destroyAction)
|
|
||||||
alert.addAction(cancelAction)
|
|
||||||
|
|
||||||
alert.popoverPresentationController?.sourceView = sourceView
|
|
||||||
alert.popoverPresentationController?.sourceRect = sourceView.bounds
|
|
||||||
|
|
||||||
present(alert, animated: true, completion: nil)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TunnelEditTableViewController {
|
extension TunnelEditTableViewController {
|
||||||
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
|
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
|
||||||
if case .onDemand = sections[indexPath.section], indexPath.row > 0 {
|
if case .onDemand = sections[indexPath.section], indexPath.row == 2 {
|
||||||
return indexPath
|
return indexPath
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
@@ -484,16 +477,27 @@ extension TunnelEditTableViewController {
|
|||||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
switch sections[indexPath.section] {
|
switch sections[indexPath.section] {
|
||||||
case .onDemand:
|
case .onDemand:
|
||||||
let option = activateOnDemandOptions[indexPath.row - 1]
|
assert(indexPath.row == 2)
|
||||||
assert(option != .none)
|
tableView.deselectRow(at: indexPath, animated: true)
|
||||||
activateOnDemandSetting.activateOnDemandOption = option
|
let ssidOptionVC = SSIDOptionEditTableViewController(option: onDemandViewModel.ssidOption, ssids: onDemandViewModel.selectedSSIDs)
|
||||||
|
ssidOptionVC.delegate = self
|
||||||
let indexPaths = (1 ..< 4).map { IndexPath(row: $0, section: indexPath.section) }
|
navigationController?.pushViewController(ssidOptionVC, animated: true)
|
||||||
UIView.performWithoutAnimation {
|
|
||||||
tableView.reloadRows(at: indexPaths, with: .none)
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
assertionFailure()
|
assertionFailure()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension TunnelEditTableViewController: SSIDOptionEditTableViewControllerDelegate {
|
||||||
|
func ssidOptionSaved(option: ActivateOnDemandViewModel.OnDemandSSIDOption, ssids: [String]) {
|
||||||
|
onDemandViewModel.selectedSSIDs = ssids
|
||||||
|
onDemandViewModel.ssidOption = option
|
||||||
|
onDemandViewModel.fixSSIDOption()
|
||||||
|
if let onDemandSection = sections.firstIndex(where: { $0 == .onDemand }) {
|
||||||
|
if let ssidRowIndex = onDemandFields.firstIndex(of: .ssid) {
|
||||||
|
let indexPath = IndexPath(row: ssidRowIndex, section: onDemandSection)
|
||||||
|
tableView.reloadRows(at: [indexPath], with: .none)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ class TunnelsListTableViewController: UIViewController {
|
|||||||
|
|
||||||
var tunnelsManager: TunnelsManager?
|
var tunnelsManager: TunnelsManager?
|
||||||
|
|
||||||
|
enum TableState: Equatable {
|
||||||
|
case normal
|
||||||
|
case rowSwiped
|
||||||
|
case multiSelect(selectionCount: Int)
|
||||||
|
}
|
||||||
|
|
||||||
let tableView: UITableView = {
|
let tableView: UITableView = {
|
||||||
let tableView = UITableView(frame: CGRect.zero, style: .plain)
|
let tableView = UITableView(frame: CGRect.zero, style: .plain)
|
||||||
tableView.estimatedRowHeight = 60
|
tableView.estimatedRowHeight = 60
|
||||||
@@ -32,6 +38,11 @@ class TunnelsListTableViewController: UIViewController {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
var detailDisplayedTunnel: TunnelContainer?
|
var detailDisplayedTunnel: TunnelContainer?
|
||||||
|
var tableState: TableState = .normal {
|
||||||
|
didSet {
|
||||||
|
handleTableStateChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override func loadView() {
|
override func loadView() {
|
||||||
view = UIView()
|
view = UIView()
|
||||||
@@ -74,13 +85,39 @@ class TunnelsListTableViewController: UIViewController {
|
|||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
title = tr("tunnelsListTitle")
|
tableState = .normal
|
||||||
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addButtonTapped(sender:)))
|
|
||||||
navigationItem.leftBarButtonItem = UIBarButtonItem(title: tr("tunnelsListSettingsButtonTitle"), style: .plain, target: self, action: #selector(settingsButtonTapped(sender:)))
|
|
||||||
|
|
||||||
restorationIdentifier = "TunnelsListVC"
|
restorationIdentifier = "TunnelsListVC"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleTableStateChange() {
|
||||||
|
switch tableState {
|
||||||
|
case .normal:
|
||||||
|
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addButtonTapped(sender:)))
|
||||||
|
navigationItem.leftBarButtonItem = UIBarButtonItem(title: tr("tunnelsListSettingsButtonTitle"), style: .plain, target: self, action: #selector(settingsButtonTapped(sender:)))
|
||||||
|
case .rowSwiped:
|
||||||
|
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneButtonTapped))
|
||||||
|
navigationItem.leftBarButtonItem = UIBarButtonItem(title: tr("tunnelsListSelectButtonTitle"), style: .plain, target: self, action: #selector(selectButtonTapped))
|
||||||
|
case .multiSelect(let selectionCount):
|
||||||
|
if selectionCount > 0 {
|
||||||
|
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelButtonTapped))
|
||||||
|
navigationItem.leftBarButtonItem = UIBarButtonItem(title: tr("tunnelsListDeleteButtonTitle"), style: .plain, target: self, action: #selector(deleteButtonTapped(sender:)))
|
||||||
|
} else {
|
||||||
|
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelButtonTapped))
|
||||||
|
navigationItem.leftBarButtonItem = UIBarButtonItem(title: tr("tunnelsListSelectAllButtonTitle"), style: .plain, target: self, action: #selector(selectAllButtonTapped))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if case .multiSelect(let selectionCount) = tableState, selectionCount > 0 {
|
||||||
|
navigationItem.title = tr(format: "tunnelsListSelectedTitle (%d)", selectionCount)
|
||||||
|
} else {
|
||||||
|
navigationItem.title = tr("tunnelsListTitle")
|
||||||
|
}
|
||||||
|
if case .multiSelect = tableState {
|
||||||
|
tableView.allowsMultipleSelectionDuringEditing = true
|
||||||
|
} else {
|
||||||
|
tableView.allowsMultipleSelectionDuringEditing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func setTunnelsManager(tunnelsManager: TunnelsManager) {
|
func setTunnelsManager(tunnelsManager: TunnelsManager) {
|
||||||
self.tunnelsManager = tunnelsManager
|
self.tunnelsManager = tunnelsManager
|
||||||
tunnelsManager.tunnelsListDelegate = self
|
tunnelsManager.tunnelsListDelegate = self
|
||||||
@@ -159,14 +196,67 @@ class TunnelsListTableViewController: UIViewController {
|
|||||||
scanQRCodeNC.modalPresentationStyle = .fullScreen
|
scanQRCodeNC.modalPresentationStyle = .fullScreen
|
||||||
present(scanQRCodeNC, animated: true)
|
present(scanQRCodeNC, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func selectButtonTapped() {
|
||||||
|
let shouldCancelSwipe = tableState == .rowSwiped
|
||||||
|
tableState = .multiSelect(selectionCount: 0)
|
||||||
|
if shouldCancelSwipe {
|
||||||
|
tableView.setEditing(false, animated: false)
|
||||||
|
}
|
||||||
|
tableView.setEditing(true, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func doneButtonTapped() {
|
||||||
|
tableState = .normal
|
||||||
|
tableView.setEditing(false, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func selectAllButtonTapped() {
|
||||||
|
guard tableView.isEditing else { return }
|
||||||
|
guard let tunnelsManager = tunnelsManager else { return }
|
||||||
|
for index in 0 ..< tunnelsManager.numberOfTunnels() {
|
||||||
|
tableView.selectRow(at: IndexPath(row: index, section: 0), animated: false, scrollPosition: .none)
|
||||||
|
}
|
||||||
|
tableState = .multiSelect(selectionCount: tableView.indexPathsForSelectedRows?.count ?? 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func cancelButtonTapped() {
|
||||||
|
tableState = .normal
|
||||||
|
tableView.setEditing(false, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func deleteButtonTapped(sender: AnyObject?) {
|
||||||
|
guard let sender = sender as? UIBarButtonItem else { return }
|
||||||
|
guard let tunnelsManager = tunnelsManager else { return }
|
||||||
|
|
||||||
|
let selectedTunnelIndices = tableView.indexPathsForSelectedRows?.map { $0.row } ?? []
|
||||||
|
let selectedTunnels = selectedTunnelIndices.compactMap { tunnelIndex in
|
||||||
|
tunnelIndex >= 0 && tunnelIndex < tunnelsManager.numberOfTunnels() ? tunnelsManager.tunnel(at: tunnelIndex) : nil
|
||||||
|
}
|
||||||
|
guard !selectedTunnels.isEmpty else { return }
|
||||||
|
let message = selectedTunnels.count == 1 ?
|
||||||
|
tr(format: "deleteTunnelConfirmationAlertButtonMessage (%d)", selectedTunnels.count) :
|
||||||
|
tr(format: "deleteTunnelsConfirmationAlertButtonMessage (%d)", selectedTunnels.count)
|
||||||
|
let title = tr("deleteTunnelsConfirmationAlertButtonTitle")
|
||||||
|
ConfirmationAlertPresenter.showConfirmationAlert(message: message, buttonTitle: title,
|
||||||
|
from: sender, presentingVC: self) { [weak self] in
|
||||||
|
self?.tunnelsManager?.removeMultiple(tunnels: selectedTunnels) { [weak self] error in
|
||||||
|
guard let self = self else { return }
|
||||||
|
if let error = error {
|
||||||
|
ErrorPresenter.showErrorAlert(error: error, from: self)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.tableState = .normal
|
||||||
|
self.tableView.setEditing(false, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TunnelsListTableViewController: UIDocumentPickerDelegate {
|
extension TunnelsListTableViewController: UIDocumentPickerDelegate {
|
||||||
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
|
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
|
||||||
guard let tunnelsManager = tunnelsManager else { return }
|
guard let tunnelsManager = tunnelsManager else { return }
|
||||||
urls.forEach { url in
|
TunnelImporter.importFromFile(urls: urls, into: tunnelsManager, sourceVC: self, errorPresenterType: ErrorPresenter.self)
|
||||||
TunnelImporter.importFromFile(url: url, into: tunnelsManager, sourceVC: self, errorPresenterType: ErrorPresenter.self)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,6 +302,10 @@ extension TunnelsListTableViewController: UITableViewDataSource {
|
|||||||
|
|
||||||
extension TunnelsListTableViewController: UITableViewDelegate {
|
extension TunnelsListTableViewController: UITableViewDelegate {
|
||||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
|
guard !tableView.isEditing else {
|
||||||
|
tableState = .multiSelect(selectionCount: tableView.indexPathsForSelectedRows?.count ?? 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
guard let tunnelsManager = tunnelsManager else { return }
|
guard let tunnelsManager = tunnelsManager else { return }
|
||||||
let tunnel = tunnelsManager.tunnel(at: indexPath.row)
|
let tunnel = tunnelsManager.tunnel(at: indexPath.row)
|
||||||
let tunnelDetailVC = TunnelDetailTableViewController(tunnelsManager: tunnelsManager,
|
let tunnelDetailVC = TunnelDetailTableViewController(tunnelsManager: tunnelsManager,
|
||||||
@@ -222,6 +316,13 @@ extension TunnelsListTableViewController: UITableViewDelegate {
|
|||||||
detailDisplayedTunnel = tunnel
|
detailDisplayedTunnel = tunnel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
|
||||||
|
guard !tableView.isEditing else {
|
||||||
|
tableState = .multiSelect(selectionCount: tableView.indexPathsForSelectedRows?.count ?? 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView,
|
func tableView(_ tableView: UITableView,
|
||||||
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
||||||
let deleteAction = UIContextualAction(style: .destructive, title: tr("tunnelsListSwipeDeleteButtonTitle")) { [weak self] _, _, completionHandler in
|
let deleteAction = UIContextualAction(style: .destructive, title: tr("tunnelsListSwipeDeleteButtonTitle")) { [weak self] _, _, completionHandler in
|
||||||
@@ -238,6 +339,18 @@ extension TunnelsListTableViewController: UITableViewDelegate {
|
|||||||
}
|
}
|
||||||
return UISwipeActionsConfiguration(actions: [deleteAction])
|
return UISwipeActionsConfiguration(actions: [deleteAction])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath) {
|
||||||
|
if tableState == .normal {
|
||||||
|
tableState = .rowSwiped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableView(_ tableView: UITableView, didEndEditingRowAt indexPath: IndexPath?) {
|
||||||
|
if tableState == .rowSwiped {
|
||||||
|
tableState = .normal
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TunnelsListTableViewController: TunnelsManagerListDelegate {
|
extension TunnelsListTableViewController: TunnelsManagerListDelegate {
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
<array>
|
<array>
|
||||||
<string>packet-tunnel-provider</string>
|
<string>packet-tunnel-provider</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>com.apple.developer.networking.wifi-info</key>
|
||||||
|
<true/>
|
||||||
<key>com.apple.security.application-groups</key>
|
<key>com.apple.security.application-groups</key>
|
||||||
<array>
|
<array>
|
||||||
<string>group.$(APP_ID_IOS)</string>
|
<string>group.$(APP_ID_IOS)</string>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
import ServiceManagement
|
||||||
|
|
||||||
@NSApplicationMain
|
@NSApplicationMain
|
||||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
@@ -12,10 +13,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
|
|
||||||
var manageTunnelsRootVC: ManageTunnelsRootViewController?
|
var manageTunnelsRootVC: ManageTunnelsRootViewController?
|
||||||
var manageTunnelsWindowObject: NSWindow?
|
var manageTunnelsWindowObject: NSWindow?
|
||||||
var isTerminationAlertShown = false
|
|
||||||
|
|
||||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||||
Logger.configureGlobal(withFilePath: FileManager.appLogFileURL?.path)
|
Logger.configureGlobal(tagged: "APP", withFilePath: FileManager.logFileURL?.path)
|
||||||
|
registerLoginItem(shouldLaunchAtLogin: true)
|
||||||
|
|
||||||
TunnelsManager.create { [weak self] result in
|
TunnelsManager.create { [weak self] result in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
@@ -42,25 +43,29 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
|
@objc func quit() {
|
||||||
guard let currentTunnel = tunnelsTracker?.currentTunnel, currentTunnel.status == .active || currentTunnel.status == .activating else {
|
if let manageWindow = manageTunnelsWindowObject, manageWindow.attachedSheet != nil {
|
||||||
return .terminateNow
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
|
manageWindow.orderFront(self)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if isTerminationAlertShown {
|
registerLoginItem(shouldLaunchAtLogin: false)
|
||||||
return .terminateNow
|
guard let currentTunnel = tunnelsTracker?.currentTunnel, currentTunnel.status == .active || currentTunnel.status == .activating else {
|
||||||
|
NSApp.terminate(nil)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
let alert = NSAlert()
|
let alert = NSAlert()
|
||||||
alert.messageText = tr("macAppExitingWithActiveTunnelMessage")
|
alert.messageText = tr("macAppExitingWithActiveTunnelMessage")
|
||||||
alert.informativeText = tr("macAppExitingWithActiveTunnelInfo")
|
alert.informativeText = tr("macAppExitingWithActiveTunnelInfo")
|
||||||
if let window = manageTunnelsWindowObject {
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
alert.beginSheetModal(for: window) { [weak self] _ in
|
if let manageWindow = manageTunnelsWindowObject {
|
||||||
self?.isTerminationAlertShown = true
|
manageWindow.orderFront(self)
|
||||||
|
alert.beginSheetModal(for: manageWindow) { _ in
|
||||||
NSApp.terminate(nil)
|
NSApp.terminate(nil)
|
||||||
}
|
}
|
||||||
return .terminateCancel
|
|
||||||
} else {
|
} else {
|
||||||
alert.runModal()
|
alert.runModal()
|
||||||
return .terminateNow
|
NSApp.terminate(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,3 +84,10 @@ extension AppDelegate: StatusMenuWindowDelegate {
|
|||||||
return manageTunnelsWindowObject!
|
return manageTunnelsWindowObject!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
func registerLoginItem(shouldLaunchAtLogin: Bool) -> Bool {
|
||||||
|
let appId = Bundle.main.bundleIdentifier!
|
||||||
|
let helperBundleId = "\(appId).login-item-helper"
|
||||||
|
return SMLoginItemSetEnabled(helperBundleId as CFString, shouldLaunchAtLogin)
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class Application: NSApplication {
|
|||||||
"Z": #selector(UndoActionRespondable.redo(_:)),
|
"Z": #selector(UndoActionRespondable.redo(_:)),
|
||||||
"w": #selector(NSWindow.performClose(_:)),
|
"w": #selector(NSWindow.performClose(_:)),
|
||||||
"m": #selector(NSWindow.performMiniaturize(_:)),
|
"m": #selector(NSWindow.performMiniaturize(_:)),
|
||||||
"q": #selector(NSApplication.terminate(_:))
|
"q": #selector(AppDelegate.quit)
|
||||||
]
|
]
|
||||||
|
|
||||||
private var appDelegate: AppDelegate? //swiftlint:disable:this weak_delegate
|
private var appDelegate: AppDelegate? //swiftlint:disable:this weak_delegate
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ class ImportPanelPresenter {
|
|||||||
let openPanel = NSOpenPanel()
|
let openPanel = NSOpenPanel()
|
||||||
openPanel.prompt = tr("macSheetButtonImport")
|
openPanel.prompt = tr("macSheetButtonImport")
|
||||||
openPanel.allowedFileTypes = ["conf", "zip"]
|
openPanel.allowedFileTypes = ["conf", "zip"]
|
||||||
|
openPanel.allowsMultipleSelection = true
|
||||||
openPanel.beginSheetModal(for: window) { [weak tunnelsManager] response in
|
openPanel.beginSheetModal(for: window) { [weak tunnelsManager] response in
|
||||||
guard let tunnelsManager = tunnelsManager else { return }
|
guard let tunnelsManager = tunnelsManager else { return }
|
||||||
guard response == .OK else { return }
|
guard response == .OK else { return }
|
||||||
guard let url = openPanel.url else { return }
|
TunnelImporter.importFromFile(urls: openPanel.urls, into: tunnelsManager, sourceVC: sourceVC, errorPresenterType: ErrorPresenter.self)
|
||||||
TunnelImporter.importFromFile(url: url, into: tunnelsManager, sourceVC: sourceVC, errorPresenterType: ErrorPresenter.self)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
|
<false/>
|
||||||
<key>LSMultipleInstancesProhibited</key>
|
<key>LSMultipleInstancesProhibited</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
|
<false/>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string></string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>$(PRODUCT_NAME)</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1</string>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string>Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.</string>
|
||||||
|
<key>NSPrincipalClass</key>
|
||||||
|
<string>NSApplication</string>
|
||||||
|
<key>LSBackgroundOnly</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.app-sandbox</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
NSURL *bundleURL = [NSBundle.mainBundle bundleURL];
|
||||||
|
|
||||||
|
// From <path>/WireGuard.app/Contents/Library/LoginItems/WireGuardLoginItemHelper.app, derive <path>/WireGuard.app
|
||||||
|
for (int i = 0; i < 4; ++i)
|
||||||
|
bundleURL = [bundleURL URLByDeletingLastPathComponent];
|
||||||
|
|
||||||
|
[NSWorkspace.sharedWorkspace launchApplication:[bundleURL path]];
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ class StatusMenu: NSMenu {
|
|||||||
|
|
||||||
var statusMenuItem: NSMenuItem?
|
var statusMenuItem: NSMenuItem?
|
||||||
var networksMenuItem: NSMenuItem?
|
var networksMenuItem: NSMenuItem?
|
||||||
|
var deactivateMenuItem: NSMenuItem?
|
||||||
var firstTunnelMenuItemIndex = 0
|
var firstTunnelMenuItemIndex = 0
|
||||||
var numberOfTunnelMenuItems = 0
|
var numberOfTunnelMenuItems = 0
|
||||||
|
|
||||||
@@ -53,16 +54,22 @@ class StatusMenu: NSMenu {
|
|||||||
networksMenuItem.isEnabled = false
|
networksMenuItem.isEnabled = false
|
||||||
networksMenuItem.isHidden = true
|
networksMenuItem.isHidden = true
|
||||||
addItem(networksMenuItem)
|
addItem(networksMenuItem)
|
||||||
|
let deactivateMenuItem = NSMenuItem(title: tr("macToggleStatusButtonDeactivate"), action: #selector(deactivateClicked), keyEquivalent: "")
|
||||||
|
deactivateMenuItem.target = self
|
||||||
|
deactivateMenuItem.isHidden = true
|
||||||
|
addItem(deactivateMenuItem)
|
||||||
self.statusMenuItem = statusMenuItem
|
self.statusMenuItem = statusMenuItem
|
||||||
self.networksMenuItem = networksMenuItem
|
self.networksMenuItem = networksMenuItem
|
||||||
|
self.deactivateMenuItem = deactivateMenuItem
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateStatusMenuItems(with tunnel: TunnelContainer?) {
|
func updateStatusMenuItems(with tunnel: TunnelContainer?) {
|
||||||
guard let statusMenuItem = statusMenuItem, let networksMenuItem = networksMenuItem else { return }
|
guard let statusMenuItem = statusMenuItem, let networksMenuItem = networksMenuItem, let deactivateMenuItem = deactivateMenuItem else { return }
|
||||||
guard let tunnel = tunnel else {
|
guard let tunnel = tunnel else {
|
||||||
statusMenuItem.title = tr(format: "macStatus (%@)", tr("tunnelStatusInactive"))
|
statusMenuItem.title = tr(format: "macStatus (%@)", tr("tunnelStatusInactive"))
|
||||||
networksMenuItem.title = ""
|
networksMenuItem.title = ""
|
||||||
networksMenuItem.isHidden = true
|
networksMenuItem.isHidden = true
|
||||||
|
deactivateMenuItem.isHidden = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var statusText: String
|
var statusText: String
|
||||||
@@ -98,6 +105,7 @@ class StatusMenu: NSMenu {
|
|||||||
}
|
}
|
||||||
networksMenuItem.isHidden = false
|
networksMenuItem.isHidden = false
|
||||||
}
|
}
|
||||||
|
deactivateMenuItem.isHidden = tunnel.status != .active
|
||||||
}
|
}
|
||||||
|
|
||||||
func addTunnelMenuItems() -> Bool {
|
func addTunnelMenuItems() -> Bool {
|
||||||
@@ -122,11 +130,17 @@ class StatusMenu: NSMenu {
|
|||||||
let aboutItem = NSMenuItem(title: tr("macMenuAbout"), action: #selector(aboutClicked), keyEquivalent: "")
|
let aboutItem = NSMenuItem(title: tr("macMenuAbout"), action: #selector(aboutClicked), keyEquivalent: "")
|
||||||
aboutItem.target = self
|
aboutItem.target = self
|
||||||
addItem(aboutItem)
|
addItem(aboutItem)
|
||||||
let quitItem = NSMenuItem(title: tr("macMenuQuit"), action: #selector(NSApplication.terminate), keyEquivalent: "")
|
let quitItem = NSMenuItem(title: tr("macMenuQuit"), action: #selector(AppDelegate.quit), keyEquivalent: "")
|
||||||
quitItem.target = NSApp
|
quitItem.target = NSApp.delegate
|
||||||
addItem(quitItem)
|
addItem(quitItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func deactivateClicked() {
|
||||||
|
if let currentTunnel = currentTunnel {
|
||||||
|
tunnelsManager.startDeactivation(of: currentTunnel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@objc func tunnelClicked(sender: AnyObject) {
|
@objc func tunnelClicked(sender: AnyObject) {
|
||||||
guard let tunnelMenuItem = sender as? TunnelMenuItem else { return }
|
guard let tunnelMenuItem = sender as? TunnelMenuItem else { return }
|
||||||
if tunnelMenuItem.state == .off {
|
if tunnelMenuItem.state == .off {
|
||||||
@@ -170,6 +184,7 @@ extension StatusMenu {
|
|||||||
func insertTunnelMenuItem(for tunnel: TunnelContainer, at tunnelIndex: Int) {
|
func insertTunnelMenuItem(for tunnel: TunnelContainer, at tunnelIndex: Int) {
|
||||||
let menuItem = TunnelMenuItem(tunnel: tunnel, action: #selector(tunnelClicked(sender:)))
|
let menuItem = TunnelMenuItem(tunnel: tunnel, action: #selector(tunnelClicked(sender:)))
|
||||||
menuItem.target = self
|
menuItem.target = self
|
||||||
|
menuItem.isHidden = !tunnel.isTunnelConfigurationAvailableInKeychain
|
||||||
insertItem(menuItem, at: firstTunnelMenuItemIndex + tunnelIndex)
|
insertItem(menuItem, at: firstTunnelMenuItemIndex + tunnelIndex)
|
||||||
if numberOfTunnelMenuItems == 0 {
|
if numberOfTunnelMenuItems == 0 {
|
||||||
insertItem(NSMenuItem.separator(), at: firstTunnelMenuItemIndex + tunnelIndex + 1)
|
insertItem(NSMenuItem.separator(), at: firstTunnelMenuItemIndex + tunnelIndex + 1)
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
class ButtonRow: NSView {
|
||||||
|
let button: NSButton = {
|
||||||
|
let button = NSButton()
|
||||||
|
button.title = ""
|
||||||
|
button.setButtonType(.momentaryPushIn)
|
||||||
|
button.bezelStyle = .rounded
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
|
var buttonTitle: String {
|
||||||
|
get { return button.title }
|
||||||
|
set(value) { button.title = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
var isButtonEnabled: Bool {
|
||||||
|
get { return button.isEnabled }
|
||||||
|
set(value) { button.isEnabled = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
var buttonToolTip: String {
|
||||||
|
get { return button.toolTip ?? "" }
|
||||||
|
set(value) { button.toolTip = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
var onButtonClicked: (() -> Void)?
|
||||||
|
var observationToken: AnyObject?
|
||||||
|
|
||||||
|
override var intrinsicContentSize: NSSize {
|
||||||
|
return NSSize(width: NSView.noIntrinsicMetric, height: button.intrinsicContentSize.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
super.init(frame: CGRect.zero)
|
||||||
|
|
||||||
|
button.target = self
|
||||||
|
button.action = #selector(buttonClicked)
|
||||||
|
|
||||||
|
addSubview(button)
|
||||||
|
button.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
button.centerYAnchor.constraint(equalTo: self.centerYAnchor),
|
||||||
|
button.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 155),
|
||||||
|
button.widthAnchor.constraint(greaterThanOrEqualToConstant: 100)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder decoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func buttonClicked() {
|
||||||
|
onButtonClicked?()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func prepareForReuse() {
|
||||||
|
buttonTitle = ""
|
||||||
|
buttonToolTip = ""
|
||||||
|
onButtonClicked = nil
|
||||||
|
observationToken = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,6 @@ import Cocoa
|
|||||||
private let fontSize: CGFloat = 15
|
private let fontSize: CGFloat = 15
|
||||||
|
|
||||||
class ConfTextStorage: NSTextStorage {
|
class ConfTextStorage: NSTextStorage {
|
||||||
|
|
||||||
let defaultFont = NSFontManager.shared.convertWeight(true, of: NSFont.systemFont(ofSize: fontSize))
|
let defaultFont = NSFontManager.shared.convertWeight(true, of: NSFont.systemFont(ofSize: fontSize))
|
||||||
private let boldFont = NSFont.boldSystemFont(ofSize: fontSize)
|
private let boldFont = NSFont.boldSystemFont(ofSize: fontSize)
|
||||||
private lazy var italicFont = NSFontManager.shared.convert(defaultFont, toHaveTrait: .italicFontMask)
|
private lazy var italicFont = NSFontManager.shared.convert(defaultFont, toHaveTrait: .italicFontMask)
|
||||||
@@ -17,6 +16,11 @@ class ConfTextStorage: NSTextStorage {
|
|||||||
private(set) var hasError = false
|
private(set) var hasError = false
|
||||||
private(set) var privateKeyString: String?
|
private(set) var privateKeyString: String?
|
||||||
|
|
||||||
|
private(set) var hasOnePeer: Bool = false
|
||||||
|
private(set) var lastOnePeerAllowedIPs = [String]()
|
||||||
|
private(set) var lastOnePeerDNSServers = [String]()
|
||||||
|
private(set) var lastOnePeerHasPublicKey = false
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
backingStore = NSMutableAttributedString(string: "")
|
backingStore = NSMutableAttributedString(string: "")
|
||||||
super.init()
|
super.init()
|
||||||
@@ -81,6 +85,55 @@ class ConfTextStorage: NSTextStorage {
|
|||||||
endEditing()
|
endEditing()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resetLastPeer() {
|
||||||
|
hasOnePeer = false
|
||||||
|
lastOnePeerAllowedIPs = []
|
||||||
|
lastOnePeerDNSServers = []
|
||||||
|
lastOnePeerHasPublicKey = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func evaluateExcludePrivateIPs(highlightSpans: UnsafePointer<highlight_span>) {
|
||||||
|
var spans = highlightSpans
|
||||||
|
enum FieldType: String {
|
||||||
|
case dns
|
||||||
|
case allowedips
|
||||||
|
}
|
||||||
|
var fieldType: FieldType?
|
||||||
|
resetLastPeer()
|
||||||
|
while spans.pointee.type != HighlightEnd {
|
||||||
|
let span = spans.pointee
|
||||||
|
var substring = backingStore.attributedSubstring(from: NSRange(location: span.start, length: span.len)).string.lowercased()
|
||||||
|
|
||||||
|
if span.type == HighlightError {
|
||||||
|
resetLastPeer()
|
||||||
|
return
|
||||||
|
} else if span.type == HighlightSection {
|
||||||
|
if substring == "[peer]" {
|
||||||
|
if hasOnePeer {
|
||||||
|
resetLastPeer()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hasOnePeer = true
|
||||||
|
}
|
||||||
|
} else if span.type == HighlightField {
|
||||||
|
fieldType = FieldType(rawValue: substring)
|
||||||
|
} else if span.type == HighlightIP && fieldType == .dns {
|
||||||
|
lastOnePeerDNSServers.append(substring)
|
||||||
|
} else if span.type == HighlightIP && fieldType == .allowedips {
|
||||||
|
let next = spans.successor()
|
||||||
|
let nextnext = next.successor()
|
||||||
|
if next.pointee.type == HighlightDelimiter && nextnext.pointee.type == HighlightCidr {
|
||||||
|
substring += backingStore.attributedSubstring(from: NSRange(location: next.pointee.start, length: next.pointee.len)).string +
|
||||||
|
backingStore.attributedSubstring(from: NSRange(location: nextnext.pointee.start, length: nextnext.pointee.len)).string
|
||||||
|
}
|
||||||
|
lastOnePeerAllowedIPs.append(substring)
|
||||||
|
} else if span.type == HighlightPublicKey {
|
||||||
|
lastOnePeerHasPublicKey = true
|
||||||
|
}
|
||||||
|
spans = spans.successor()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func highlightSyntax() {
|
func highlightSyntax() {
|
||||||
guard let textColorTheme = textColorTheme else { return }
|
guard let textColorTheme = textColorTheme else { return }
|
||||||
hasError = false
|
hasError = false
|
||||||
@@ -94,8 +147,10 @@ class ConfTextStorage: NSTextStorage {
|
|||||||
.font: defaultFont
|
.font: defaultFont
|
||||||
]
|
]
|
||||||
backingStore.setAttributes(defaultAttributes, range: fullTextRange)
|
backingStore.setAttributes(defaultAttributes, range: fullTextRange)
|
||||||
var spans = highlight_config(backingStore.string.cString(using: String.Encoding.utf8))!
|
var spans = highlight_config(backingStore.string)!
|
||||||
|
evaluateExcludePrivateIPs(highlightSpans: spans)
|
||||||
|
|
||||||
|
let spansStart = spans
|
||||||
while spans.pointee.type != HighlightEnd {
|
while spans.pointee.type != HighlightEnd {
|
||||||
let span = spans.pointee
|
let span = spans.pointee
|
||||||
|
|
||||||
@@ -115,6 +170,7 @@ class ConfTextStorage: NSTextStorage {
|
|||||||
spans = spans.successor()
|
spans = spans.successor()
|
||||||
}
|
}
|
||||||
backingStore.endEditing()
|
backingStore.endEditing()
|
||||||
|
free(spansStart)
|
||||||
|
|
||||||
beginEditing()
|
beginEditing()
|
||||||
edited(.editedAttributes, range: fullTextRange, changeInLength: 0)
|
edited(.editedAttributes, range: fullTextRange, changeInLength: 0)
|
||||||
|
|||||||
@@ -9,10 +9,12 @@ class ConfTextView: NSTextView {
|
|||||||
|
|
||||||
@objc dynamic var hasError: Bool = false
|
@objc dynamic var hasError: Bool = false
|
||||||
@objc dynamic var privateKeyString: String?
|
@objc dynamic var privateKeyString: String?
|
||||||
|
@objc dynamic var singlePeerAllowedIPs: [String]?
|
||||||
|
|
||||||
override var string: String {
|
override var string: String {
|
||||||
didSet {
|
didSet {
|
||||||
confTextStorage.highlightSyntax()
|
confTextStorage.highlightSyntax()
|
||||||
|
updateConfigData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,18 +54,35 @@ class ConfTextView: NSTextView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
private func updateConfigData() {
|
||||||
|
|
||||||
extension ConfTextView: NSTextViewDelegate {
|
|
||||||
|
|
||||||
func textDidChange(_ notification: Notification) {
|
|
||||||
confTextStorage.highlightSyntax()
|
|
||||||
if hasError != confTextStorage.hasError {
|
if hasError != confTextStorage.hasError {
|
||||||
hasError = confTextStorage.hasError
|
hasError = confTextStorage.hasError
|
||||||
}
|
}
|
||||||
if privateKeyString != confTextStorage.privateKeyString {
|
if privateKeyString != confTextStorage.privateKeyString {
|
||||||
privateKeyString = confTextStorage.privateKeyString
|
privateKeyString = confTextStorage.privateKeyString
|
||||||
}
|
}
|
||||||
|
let hasSyntaxError = confTextStorage.hasError
|
||||||
|
let hasSemanticError = confTextStorage.privateKeyString == nil || !confTextStorage.lastOnePeerHasPublicKey
|
||||||
|
let updatedSinglePeerAllowedIPs = confTextStorage.hasOnePeer && !hasSyntaxError && !hasSemanticError ? confTextStorage.lastOnePeerAllowedIPs : nil
|
||||||
|
if singlePeerAllowedIPs != updatedSinglePeerAllowedIPs {
|
||||||
|
singlePeerAllowedIPs = updatedSinglePeerAllowedIPs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setConfText(_ text: String) {
|
||||||
|
let fullTextRange = NSRange(location: 0, length: (string as NSString).length)
|
||||||
|
if shouldChangeText(in: fullTextRange, replacementString: text) {
|
||||||
|
replaceCharacters(in: fullTextRange, with: text)
|
||||||
|
didChangeText()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ConfTextView: NSTextViewDelegate {
|
||||||
|
|
||||||
|
func textDidChange(_ notification: Notification) {
|
||||||
|
confTextStorage.highlightSyntax()
|
||||||
|
updateConfigData()
|
||||||
needsDisplay = true
|
needsDisplay = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
class DeleteTunnelsConfirmationAlert: NSAlert {
|
||||||
|
var alertDeleteButton: NSButton?
|
||||||
|
var alertCancelButton: NSButton?
|
||||||
|
|
||||||
|
var onDeleteClicked: ((_ completionHandler: @escaping () -> Void) -> Void)?
|
||||||
|
|
||||||
|
override init() {
|
||||||
|
super.init()
|
||||||
|
let alertDeleteButton = addButton(withTitle: tr("macDeleteTunnelConfirmationAlertButtonTitleDelete"))
|
||||||
|
alertDeleteButton.target = self
|
||||||
|
alertDeleteButton.action = #selector(removeTunnelAlertDeleteClicked)
|
||||||
|
self.alertDeleteButton = alertDeleteButton
|
||||||
|
self.alertCancelButton = addButton(withTitle: tr("macDeleteTunnelConfirmationAlertButtonTitleCancel"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func removeTunnelAlertDeleteClicked() {
|
||||||
|
alertDeleteButton?.title = tr("macDeleteTunnelConfirmationAlertButtonTitleDeleting")
|
||||||
|
alertDeleteButton?.isEnabled = false
|
||||||
|
alertCancelButton?.isEnabled = false
|
||||||
|
if let onDeleteClicked = onDeleteClicked {
|
||||||
|
onDeleteClicked { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.window.sheetParent?.endSheet(self.window)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func beginSheetModal(for sheetWindow: NSWindow) {
|
||||||
|
beginSheetModal(for: sheetWindow) { _ in }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,8 @@ class EditableKeyValueRow: NSView {
|
|||||||
return valueLabel
|
return valueLabel
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
let valueImageView: NSImageView?
|
||||||
|
|
||||||
var key: String {
|
var key: String {
|
||||||
get { return keyLabel.stringValue }
|
get { return keyLabel.stringValue }
|
||||||
set(value) { keyLabel.stringValue = value }
|
set(value) { keyLabel.stringValue = value }
|
||||||
@@ -42,13 +44,24 @@ class EditableKeyValueRow: NSView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var valueImage: NSImage? {
|
||||||
|
get { return valueImageView?.image }
|
||||||
|
set(value) { valueImageView?.image = value }
|
||||||
|
}
|
||||||
|
|
||||||
|
var observationToken: AnyObject?
|
||||||
|
|
||||||
override var intrinsicContentSize: NSSize {
|
override var intrinsicContentSize: NSSize {
|
||||||
let height = max(keyLabel.intrinsicContentSize.height, valueLabel.intrinsicContentSize.height)
|
let height = max(keyLabel.intrinsicContentSize.height, valueLabel.intrinsicContentSize.height)
|
||||||
return NSSize(width: NSView.noIntrinsicMetric, height: height)
|
return NSSize(width: NSView.noIntrinsicMetric, height: height)
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
convenience init() {
|
||||||
|
self.init(hasValueImage: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate init(hasValueImage: Bool) {
|
||||||
|
valueImageView = hasValueImage ? NSImageView() : nil
|
||||||
super.init(frame: CGRect.zero)
|
super.init(frame: CGRect.zero)
|
||||||
|
|
||||||
addSubview(keyLabel)
|
addSubview(keyLabel)
|
||||||
@@ -60,10 +73,24 @@ class EditableKeyValueRow: NSView {
|
|||||||
keyLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor),
|
keyLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor),
|
||||||
keyLabel.firstBaselineAnchor.constraint(equalTo: valueLabel.firstBaselineAnchor),
|
keyLabel.firstBaselineAnchor.constraint(equalTo: valueLabel.firstBaselineAnchor),
|
||||||
self.leadingAnchor.constraint(equalTo: keyLabel.leadingAnchor),
|
self.leadingAnchor.constraint(equalTo: keyLabel.leadingAnchor),
|
||||||
keyLabel.trailingAnchor.constraint(equalTo: valueLabel.leadingAnchor, constant: -5),
|
|
||||||
valueLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor)
|
valueLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor)
|
||||||
])
|
])
|
||||||
|
|
||||||
|
let spacing: CGFloat = 5
|
||||||
|
if let valueImageView = valueImageView {
|
||||||
|
addSubview(valueImageView)
|
||||||
|
valueImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
valueImageView.centerYAnchor.constraint(equalTo: self.centerYAnchor),
|
||||||
|
valueImageView.leadingAnchor.constraint(equalTo: keyLabel.trailingAnchor, constant: spacing),
|
||||||
|
valueLabel.leadingAnchor.constraint(equalTo: valueImageView.trailingAnchor)
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
valueLabel.leadingAnchor.constraint(equalTo: keyLabel.trailingAnchor, constant: spacing)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
keyLabel.setContentCompressionResistancePriority(.defaultHigh + 2, for: .horizontal)
|
keyLabel.setContentCompressionResistancePriority(.defaultHigh + 2, for: .horizontal)
|
||||||
keyLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
|
keyLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
|
||||||
valueLabel.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
valueLabel.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
||||||
@@ -81,12 +108,26 @@ class EditableKeyValueRow: NSView {
|
|||||||
key = ""
|
key = ""
|
||||||
value = ""
|
value = ""
|
||||||
isKeyInBold = false
|
isKeyInBold = false
|
||||||
|
observationToken = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class KeyValueRow: EditableKeyValueRow {
|
class KeyValueRow: EditableKeyValueRow {
|
||||||
override init() {
|
init() {
|
||||||
super.init()
|
super.init(hasValueImage: false)
|
||||||
|
valueLabel.isEditable = false
|
||||||
|
valueLabel.isBordered = false
|
||||||
|
valueLabel.backgroundColor = .clear
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder decoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class KeyValueImageRow: EditableKeyValueRow {
|
||||||
|
init() {
|
||||||
|
super.init(hasValueImage: true)
|
||||||
valueLabel.isEditable = false
|
valueLabel.isEditable = false
|
||||||
valueLabel.isBordered = false
|
valueLabel.isBordered = false
|
||||||
valueLabel.backgroundColor = .clear
|
valueLabel.backgroundColor = .clear
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
class LogViewCell: NSTextField {
|
||||||
|
init() {
|
||||||
|
super.init(frame: .zero)
|
||||||
|
isSelectable = false
|
||||||
|
isEditable = false
|
||||||
|
isBordered = false
|
||||||
|
backgroundColor = .clear
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func prepareForReuse() {
|
||||||
|
stringValue = ""
|
||||||
|
preferredMaxLayoutWidth = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LogViewTimestampCell: LogViewCell {
|
||||||
|
override init() {
|
||||||
|
super.init()
|
||||||
|
maximumNumberOfLines = 1
|
||||||
|
lineBreakMode = .byClipping
|
||||||
|
preferredMaxLayoutWidth = 0
|
||||||
|
setContentCompressionResistancePriority(.defaultHigh, for: .vertical)
|
||||||
|
setContentHuggingPriority(.defaultLow, for: .vertical)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LogViewMessageCell: LogViewCell {
|
||||||
|
override init() {
|
||||||
|
super.init()
|
||||||
|
maximumNumberOfLines = 0
|
||||||
|
lineBreakMode = .byWordWrapping
|
||||||
|
setContentCompressionResistancePriority(.required, for: .vertical)
|
||||||
|
setContentHuggingPriority(.required, for: .vertical)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
import CoreWLAN
|
||||||
|
|
||||||
|
class OnDemandControlsRow: NSView {
|
||||||
|
let keyLabel: NSTextField = {
|
||||||
|
let keyLabel = NSTextField()
|
||||||
|
keyLabel.stringValue = tr("macFieldOnDemand")
|
||||||
|
keyLabel.isEditable = false
|
||||||
|
keyLabel.isSelectable = false
|
||||||
|
keyLabel.isBordered = false
|
||||||
|
keyLabel.alignment = .right
|
||||||
|
keyLabel.maximumNumberOfLines = 1
|
||||||
|
keyLabel.lineBreakMode = .byTruncatingTail
|
||||||
|
keyLabel.backgroundColor = .clear
|
||||||
|
return keyLabel
|
||||||
|
}()
|
||||||
|
|
||||||
|
let onDemandEthernetCheckbox: NSButton = {
|
||||||
|
let checkbox = NSButton()
|
||||||
|
checkbox.title = tr("tunnelOnDemandEthernet")
|
||||||
|
checkbox.setButtonType(.switch)
|
||||||
|
checkbox.state = .off
|
||||||
|
return checkbox
|
||||||
|
}()
|
||||||
|
|
||||||
|
let onDemandWiFiCheckbox: NSButton = {
|
||||||
|
let checkbox = NSButton()
|
||||||
|
checkbox.title = tr("tunnelOnDemandWiFi")
|
||||||
|
checkbox.setButtonType(.switch)
|
||||||
|
checkbox.state = .off
|
||||||
|
return checkbox
|
||||||
|
}()
|
||||||
|
|
||||||
|
static let onDemandSSIDOptions: [ActivateOnDemandViewModel.OnDemandSSIDOption] = [
|
||||||
|
.anySSID, .onlySpecificSSIDs, .exceptSpecificSSIDs
|
||||||
|
]
|
||||||
|
|
||||||
|
let onDemandSSIDOptionsPopup = NSPopUpButton()
|
||||||
|
|
||||||
|
let onDemandSSIDsField: NSTokenField = {
|
||||||
|
let tokenField = NSTokenField()
|
||||||
|
tokenField.tokenizingCharacterSet = CharacterSet([])
|
||||||
|
tokenField.tokenStyle = .squared
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
tokenField.widthAnchor.constraint(greaterThanOrEqualToConstant: 180)
|
||||||
|
])
|
||||||
|
return tokenField
|
||||||
|
}()
|
||||||
|
|
||||||
|
override var intrinsicContentSize: NSSize {
|
||||||
|
let minHeight: CGFloat = 22
|
||||||
|
let height = max(minHeight, keyLabel.intrinsicContentSize.height,
|
||||||
|
onDemandEthernetCheckbox.intrinsicContentSize.height, onDemandWiFiCheckbox.intrinsicContentSize.height,
|
||||||
|
onDemandSSIDOptionsPopup.intrinsicContentSize.height, onDemandSSIDsField.intrinsicContentSize.height)
|
||||||
|
return NSSize(width: NSView.noIntrinsicMetric, height: height)
|
||||||
|
}
|
||||||
|
|
||||||
|
var onDemandViewModel: ActivateOnDemandViewModel? {
|
||||||
|
didSet { updateControls() }
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentSSIDs: [String]
|
||||||
|
|
||||||
|
init() {
|
||||||
|
currentSSIDs = getCurrentSSIDs()
|
||||||
|
super.init(frame: CGRect.zero)
|
||||||
|
|
||||||
|
onDemandSSIDOptionsPopup.addItems(withTitles: OnDemandControlsRow.onDemandSSIDOptions.map { $0.localizedUIString })
|
||||||
|
|
||||||
|
let stackView = NSStackView()
|
||||||
|
stackView.setViews([onDemandEthernetCheckbox, onDemandWiFiCheckbox, onDemandSSIDOptionsPopup, onDemandSSIDsField], in: .leading)
|
||||||
|
stackView.orientation = .horizontal
|
||||||
|
|
||||||
|
addSubview(keyLabel)
|
||||||
|
addSubview(stackView)
|
||||||
|
keyLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
keyLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor),
|
||||||
|
stackView.centerYAnchor.constraint(equalTo: self.centerYAnchor),
|
||||||
|
self.leadingAnchor.constraint(equalTo: keyLabel.leadingAnchor),
|
||||||
|
stackView.leadingAnchor.constraint(equalTo: keyLabel.trailingAnchor, constant: 5),
|
||||||
|
stackView.trailingAnchor.constraint(equalTo: self.trailingAnchor)
|
||||||
|
])
|
||||||
|
|
||||||
|
keyLabel.setContentCompressionResistancePriority(.defaultHigh + 2, for: .horizontal)
|
||||||
|
keyLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
|
||||||
|
|
||||||
|
let widthConstraint = keyLabel.widthAnchor.constraint(equalToConstant: 150)
|
||||||
|
widthConstraint.priority = .defaultHigh + 1
|
||||||
|
widthConstraint.isActive = true
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
onDemandEthernetCheckbox.centerYAnchor.constraint(equalTo: stackView.centerYAnchor),
|
||||||
|
onDemandWiFiCheckbox.lastBaselineAnchor.constraint(equalTo: onDemandEthernetCheckbox.lastBaselineAnchor),
|
||||||
|
onDemandSSIDOptionsPopup.lastBaselineAnchor.constraint(equalTo: onDemandEthernetCheckbox.lastBaselineAnchor),
|
||||||
|
onDemandSSIDsField.lastBaselineAnchor.constraint(equalTo: onDemandEthernetCheckbox.lastBaselineAnchor)
|
||||||
|
])
|
||||||
|
|
||||||
|
onDemandSSIDsField.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
||||||
|
|
||||||
|
onDemandEthernetCheckbox.target = self
|
||||||
|
onDemandEthernetCheckbox.action = #selector(ethernetCheckboxToggled)
|
||||||
|
|
||||||
|
onDemandWiFiCheckbox.target = self
|
||||||
|
onDemandWiFiCheckbox.action = #selector(wiFiCheckboxToggled)
|
||||||
|
|
||||||
|
onDemandSSIDOptionsPopup.target = self
|
||||||
|
onDemandSSIDOptionsPopup.action = #selector(ssidOptionsPopupValueChanged)
|
||||||
|
|
||||||
|
onDemandSSIDsField.delegate = self
|
||||||
|
|
||||||
|
updateControls()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder decoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveToViewModel() {
|
||||||
|
guard let onDemandViewModel = onDemandViewModel else { return }
|
||||||
|
onDemandViewModel.isNonWiFiInterfaceEnabled = onDemandEthernetCheckbox.state == .on
|
||||||
|
onDemandViewModel.isWiFiInterfaceEnabled = onDemandWiFiCheckbox.state == .on
|
||||||
|
onDemandViewModel.ssidOption = OnDemandControlsRow.onDemandSSIDOptions[onDemandSSIDOptionsPopup.indexOfSelectedItem]
|
||||||
|
onDemandViewModel.selectedSSIDs = (onDemandSSIDsField.objectValue as? [String]) ?? []
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateControls() {
|
||||||
|
guard let onDemandViewModel = onDemandViewModel else { return }
|
||||||
|
onDemandEthernetCheckbox.state = onDemandViewModel.isNonWiFiInterfaceEnabled ? .on : .off
|
||||||
|
onDemandWiFiCheckbox.state = onDemandViewModel.isWiFiInterfaceEnabled ? .on : .off
|
||||||
|
let optionIndex = OnDemandControlsRow.onDemandSSIDOptions.firstIndex(of: onDemandViewModel.ssidOption)
|
||||||
|
onDemandSSIDOptionsPopup.selectItem(at: optionIndex ?? 0)
|
||||||
|
onDemandSSIDsField.objectValue = onDemandViewModel.selectedSSIDs
|
||||||
|
onDemandSSIDOptionsPopup.isHidden = !onDemandViewModel.isWiFiInterfaceEnabled
|
||||||
|
onDemandSSIDsField.isHidden = !onDemandViewModel.isWiFiInterfaceEnabled || onDemandViewModel.ssidOption == .anySSID
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func ethernetCheckboxToggled() {
|
||||||
|
onDemandViewModel?.isNonWiFiInterfaceEnabled = onDemandEthernetCheckbox.state == .on
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func wiFiCheckboxToggled() {
|
||||||
|
onDemandViewModel?.isWiFiInterfaceEnabled = onDemandWiFiCheckbox.state == .on
|
||||||
|
updateControls()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func ssidOptionsPopupValueChanged() {
|
||||||
|
let selectedIndex = onDemandSSIDOptionsPopup.indexOfSelectedItem
|
||||||
|
onDemandViewModel?.ssidOption = OnDemandControlsRow.onDemandSSIDOptions[selectedIndex]
|
||||||
|
onDemandViewModel?.selectedSSIDs = (onDemandSSIDsField.objectValue as? [String]) ?? []
|
||||||
|
updateControls()
|
||||||
|
if !onDemandSSIDsField.isHidden {
|
||||||
|
onDemandSSIDsField.becomeFirstResponder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension OnDemandControlsRow: NSTokenFieldDelegate {
|
||||||
|
func tokenField(_ tokenField: NSTokenField, completionsForSubstring substring: String, indexOfToken tokenIndex: Int, indexOfSelectedItem selectedIndex: UnsafeMutablePointer<Int>?) -> [Any]? {
|
||||||
|
return currentSSIDs.filter { $0.hasPrefix(substring) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getCurrentSSIDs() -> [String] {
|
||||||
|
return CWWiFiClient.shared().interfaces()?.compactMap { $0.ssid() } ?? []
|
||||||
|
}
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
|
||||||
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
|
||||||
|
|
||||||
import Cocoa
|
|
||||||
|
|
||||||
class PopupRow: NSView {
|
|
||||||
let keyLabel: NSTextField = {
|
|
||||||
let keyLabel = NSTextField()
|
|
||||||
keyLabel.isEditable = false
|
|
||||||
keyLabel.isSelectable = false
|
|
||||||
keyLabel.isBordered = false
|
|
||||||
keyLabel.alignment = .right
|
|
||||||
keyLabel.maximumNumberOfLines = 1
|
|
||||||
keyLabel.lineBreakMode = .byTruncatingTail
|
|
||||||
keyLabel.backgroundColor = .clear
|
|
||||||
return keyLabel
|
|
||||||
}()
|
|
||||||
|
|
||||||
let valuePopup = NSPopUpButton()
|
|
||||||
|
|
||||||
var key: String {
|
|
||||||
get { return keyLabel.stringValue }
|
|
||||||
set(value) { keyLabel.stringValue = value }
|
|
||||||
}
|
|
||||||
|
|
||||||
var valueOptions: [String] {
|
|
||||||
get { return valuePopup.itemTitles }
|
|
||||||
set(value) {
|
|
||||||
valuePopup.removeAllItems()
|
|
||||||
valuePopup.addItems(withTitles: value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var selectedOptionIndex: Int {
|
|
||||||
get { return valuePopup.indexOfSelectedItem }
|
|
||||||
set(value) { valuePopup.selectItem(at: value) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override var intrinsicContentSize: NSSize {
|
|
||||||
let height = max(keyLabel.intrinsicContentSize.height, valuePopup.intrinsicContentSize.height)
|
|
||||||
return NSSize(width: NSView.noIntrinsicMetric, height: height)
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
super.init(frame: CGRect.zero)
|
|
||||||
|
|
||||||
addSubview(keyLabel)
|
|
||||||
addSubview(valuePopup)
|
|
||||||
keyLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
valuePopup.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
keyLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor),
|
|
||||||
keyLabel.firstBaselineAnchor.constraint(equalTo: valuePopup.firstBaselineAnchor),
|
|
||||||
self.leadingAnchor.constraint(equalTo: keyLabel.leadingAnchor),
|
|
||||||
keyLabel.trailingAnchor.constraint(equalTo: valuePopup.leadingAnchor, constant: -5)
|
|
||||||
])
|
|
||||||
|
|
||||||
keyLabel.setContentCompressionResistancePriority(.defaultHigh + 2, for: .horizontal)
|
|
||||||
keyLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
|
|
||||||
|
|
||||||
let widthConstraint = keyLabel.widthAnchor.constraint(equalToConstant: 150)
|
|
||||||
widthConstraint.priority = .defaultHigh + 1
|
|
||||||
widthConstraint.isActive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder decoder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override func prepareForReuse() {
|
|
||||||
key = ""
|
|
||||||
valueOptions = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
class ButtonedDetailViewController: NSViewController {
|
||||||
|
|
||||||
|
var onButtonClicked: (() -> Void)?
|
||||||
|
|
||||||
|
let button: NSButton = {
|
||||||
|
let button = NSButton()
|
||||||
|
button.title = ""
|
||||||
|
button.setButtonType(.momentaryPushIn)
|
||||||
|
button.bezelStyle = .rounded
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
|
init() {
|
||||||
|
super.init(nibName: nil, bundle: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func loadView() {
|
||||||
|
let view = NSView()
|
||||||
|
|
||||||
|
button.target = self
|
||||||
|
button.action = #selector(buttonClicked)
|
||||||
|
|
||||||
|
view.addSubview(button)
|
||||||
|
button.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
||||||
|
button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
|
||||||
|
])
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
view.widthAnchor.constraint(greaterThanOrEqualToConstant: 320),
|
||||||
|
view.heightAnchor.constraint(greaterThanOrEqualToConstant: 120)
|
||||||
|
])
|
||||||
|
|
||||||
|
self.view = view
|
||||||
|
}
|
||||||
|
|
||||||
|
func setButtonTitle(_ title: String) {
|
||||||
|
button.title = title
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func buttonClicked() {
|
||||||
|
onButtonClicked?()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,263 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
class LogViewController: NSViewController {
|
||||||
|
|
||||||
|
enum LogColumn: String {
|
||||||
|
case time = "Time"
|
||||||
|
case logMessage = "LogMessage"
|
||||||
|
|
||||||
|
func createColumn() -> NSTableColumn {
|
||||||
|
return NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue))
|
||||||
|
}
|
||||||
|
|
||||||
|
func isRepresenting(tableColumn: NSTableColumn?) -> Bool {
|
||||||
|
return tableColumn?.identifier.rawValue == rawValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let scrollView: NSScrollView = {
|
||||||
|
let scrollView = NSScrollView()
|
||||||
|
scrollView.hasVerticalScroller = true
|
||||||
|
scrollView.autohidesScrollers = false
|
||||||
|
scrollView.borderType = .bezelBorder
|
||||||
|
return scrollView
|
||||||
|
}()
|
||||||
|
|
||||||
|
let tableView: NSTableView = {
|
||||||
|
let tableView = NSTableView()
|
||||||
|
let timeColumn = LogColumn.time.createColumn()
|
||||||
|
timeColumn.title = tr("macLogColumnTitleTime")
|
||||||
|
timeColumn.width = 160
|
||||||
|
timeColumn.resizingMask = []
|
||||||
|
tableView.addTableColumn(timeColumn)
|
||||||
|
let messageColumn = LogColumn.logMessage.createColumn()
|
||||||
|
messageColumn.title = tr("macLogColumnTitleLogMessage")
|
||||||
|
messageColumn.minWidth = 360
|
||||||
|
messageColumn.resizingMask = .autoresizingMask
|
||||||
|
tableView.addTableColumn(messageColumn)
|
||||||
|
tableView.rowSizeStyle = .custom
|
||||||
|
tableView.rowHeight = 16
|
||||||
|
tableView.usesAlternatingRowBackgroundColors = true
|
||||||
|
tableView.usesAutomaticRowHeights = true
|
||||||
|
tableView.allowsColumnReordering = false
|
||||||
|
tableView.allowsColumnResizing = true
|
||||||
|
tableView.allowsMultipleSelection = true
|
||||||
|
return tableView
|
||||||
|
}()
|
||||||
|
|
||||||
|
let progressIndicator: NSProgressIndicator = {
|
||||||
|
let progressIndicator = NSProgressIndicator()
|
||||||
|
progressIndicator.controlSize = .small
|
||||||
|
progressIndicator.isIndeterminate = true
|
||||||
|
progressIndicator.style = .spinning
|
||||||
|
progressIndicator.isDisplayedWhenStopped = false
|
||||||
|
return progressIndicator
|
||||||
|
}()
|
||||||
|
|
||||||
|
let closeButton: NSButton = {
|
||||||
|
let button = NSButton()
|
||||||
|
button.title = tr("macLogButtonTitleClose")
|
||||||
|
button.setButtonType(.momentaryPushIn)
|
||||||
|
button.bezelStyle = .rounded
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
|
let saveButton: NSButton = {
|
||||||
|
let button = NSButton()
|
||||||
|
button.title = tr("macLogButtonTitleSave")
|
||||||
|
button.setButtonType(.momentaryPushIn)
|
||||||
|
button.bezelStyle = .rounded
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
|
let logViewHelper: LogViewHelper?
|
||||||
|
var logEntries = [LogViewHelper.LogEntry]()
|
||||||
|
var isFetchingLogEntries = false
|
||||||
|
var isInScrolledToEndMode = true
|
||||||
|
|
||||||
|
private var updateLogEntriesTimer: Timer?
|
||||||
|
|
||||||
|
init() {
|
||||||
|
logViewHelper = LogViewHelper(logFilePath: FileManager.logFileURL?.path)
|
||||||
|
super.init(nibName: nil, bundle: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func loadView() {
|
||||||
|
tableView.dataSource = self
|
||||||
|
tableView.delegate = self
|
||||||
|
|
||||||
|
closeButton.target = self
|
||||||
|
closeButton.action = #selector(closeClicked)
|
||||||
|
|
||||||
|
saveButton.target = self
|
||||||
|
saveButton.action = #selector(saveClicked)
|
||||||
|
saveButton.isEnabled = false
|
||||||
|
|
||||||
|
let clipView = NSClipView()
|
||||||
|
clipView.documentView = tableView
|
||||||
|
scrollView.contentView = clipView
|
||||||
|
|
||||||
|
_ = NotificationCenter.default.addObserver(forName: NSView.boundsDidChangeNotification, object: clipView, queue: OperationQueue.main) { [weak self] _ in
|
||||||
|
guard let self = self else { return }
|
||||||
|
let lastVisibleRowIndex = self.tableView.row(at: NSPoint(x: 0, y: self.scrollView.contentView.documentVisibleRect.maxY - 1))
|
||||||
|
self.isInScrolledToEndMode = lastVisibleRowIndex < 0 || lastVisibleRowIndex == self.logEntries.count - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = NotificationCenter.default.addObserver(forName: NSView.frameDidChangeNotification, object: tableView, queue: OperationQueue.main) { [weak self] _ in
|
||||||
|
guard let self = self else { return }
|
||||||
|
if self.isInScrolledToEndMode {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.tableView.scroll(NSPoint(x: 0, y: self.tableView.frame.maxY - clipView.documentVisibleRect.height))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let margin: CGFloat = 20
|
||||||
|
let internalSpacing: CGFloat = 10
|
||||||
|
|
||||||
|
let buttonRowStackView = NSStackView()
|
||||||
|
buttonRowStackView.addView(closeButton, in: .leading)
|
||||||
|
buttonRowStackView.addView(saveButton, in: .trailing)
|
||||||
|
buttonRowStackView.orientation = .horizontal
|
||||||
|
buttonRowStackView.spacing = internalSpacing
|
||||||
|
|
||||||
|
let containerView = NSView()
|
||||||
|
[scrollView, progressIndicator, buttonRowStackView].forEach { view in
|
||||||
|
containerView.addSubview(view)
|
||||||
|
view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
}
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
scrollView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: margin),
|
||||||
|
scrollView.leftAnchor.constraint(equalTo: containerView.leftAnchor, constant: margin),
|
||||||
|
containerView.rightAnchor.constraint(equalTo: scrollView.rightAnchor, constant: margin),
|
||||||
|
buttonRowStackView.topAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: internalSpacing),
|
||||||
|
buttonRowStackView.leftAnchor.constraint(equalTo: containerView.leftAnchor, constant: margin),
|
||||||
|
containerView.rightAnchor.constraint(equalTo: buttonRowStackView.rightAnchor, constant: margin),
|
||||||
|
containerView.bottomAnchor.constraint(equalTo: buttonRowStackView.bottomAnchor, constant: margin),
|
||||||
|
progressIndicator.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
|
||||||
|
progressIndicator.centerYAnchor.constraint(equalTo: scrollView.centerYAnchor)
|
||||||
|
])
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
containerView.widthAnchor.constraint(equalToConstant: 640),
|
||||||
|
containerView.heightAnchor.constraint(greaterThanOrEqualToConstant: 240)
|
||||||
|
])
|
||||||
|
|
||||||
|
containerView.frame = NSRect(x: 0, y: 0, width: 640, height: 480)
|
||||||
|
|
||||||
|
view = containerView
|
||||||
|
|
||||||
|
progressIndicator.startAnimation(self)
|
||||||
|
startUpdatingLogEntries()
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateLogEntries() {
|
||||||
|
guard !isFetchingLogEntries else { return }
|
||||||
|
isFetchingLogEntries = true
|
||||||
|
logViewHelper?.fetchLogEntriesSinceLastFetch { [weak self] fetchedLogEntries in
|
||||||
|
guard let self = self else { return }
|
||||||
|
defer {
|
||||||
|
self.isFetchingLogEntries = false
|
||||||
|
}
|
||||||
|
if !self.progressIndicator.isHidden {
|
||||||
|
self.progressIndicator.stopAnimation(self)
|
||||||
|
self.saveButton.isEnabled = true
|
||||||
|
}
|
||||||
|
guard !fetchedLogEntries.isEmpty else { return }
|
||||||
|
let oldCount = self.logEntries.count
|
||||||
|
self.logEntries.append(contentsOf: fetchedLogEntries)
|
||||||
|
self.tableView.insertRows(at: IndexSet(integersIn: oldCount ..< oldCount + fetchedLogEntries.count), withAnimation: .slideDown)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func startUpdatingLogEntries() {
|
||||||
|
updateLogEntries()
|
||||||
|
updateLogEntriesTimer?.invalidate()
|
||||||
|
let timer = Timer(timeInterval: 1 /* second */, repeats: true) { [weak self] _ in
|
||||||
|
self?.updateLogEntries()
|
||||||
|
}
|
||||||
|
updateLogEntriesTimer = timer
|
||||||
|
RunLoop.main.add(timer, forMode: .common)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stopUpdatingLogEntries() {
|
||||||
|
updateLogEntriesTimer?.invalidate()
|
||||||
|
updateLogEntriesTimer = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillDisappear() {
|
||||||
|
super.viewWillDisappear()
|
||||||
|
stopUpdatingLogEntries()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func saveClicked() {
|
||||||
|
let savePanel = NSSavePanel()
|
||||||
|
savePanel.prompt = tr("macSheetButtonExportLog")
|
||||||
|
savePanel.nameFieldLabel = tr("macNameFieldExportLog")
|
||||||
|
|
||||||
|
let dateFormatter = ISO8601DateFormatter()
|
||||||
|
dateFormatter.formatOptions = [.withFullDate, .withTime, .withTimeZone] // Avoid ':' in the filename
|
||||||
|
let timeStampString = dateFormatter.string(from: Date())
|
||||||
|
savePanel.nameFieldStringValue = "wireguard-log-\(timeStampString).txt"
|
||||||
|
|
||||||
|
savePanel.beginSheetModal(for: self.view.window!) { [weak self] response in
|
||||||
|
guard response == .OK else { return }
|
||||||
|
guard let destinationURL = savePanel.url else { return }
|
||||||
|
|
||||||
|
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
||||||
|
let isWritten = Logger.global?.writeLog(to: destinationURL.path) ?? false
|
||||||
|
guard isWritten else {
|
||||||
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
ErrorPresenter.showErrorAlert(title: tr("alertUnableToWriteLogTitle"), message: tr("alertUnableToWriteLogMessage"), from: self)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
self?.dismiss(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func closeClicked() {
|
||||||
|
dismiss(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func copy(_ sender: Any?) {
|
||||||
|
let text = tableView.selectedRowIndexes.sorted().reduce("") { $0 + self.logEntries[$1].text() + "\n" }
|
||||||
|
let pasteboard = NSPasteboard.general
|
||||||
|
pasteboard.clearContents()
|
||||||
|
pasteboard.writeObjects([text as NSString])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension LogViewController: NSTableViewDataSource {
|
||||||
|
func numberOfRows(in tableView: NSTableView) -> Int {
|
||||||
|
return logEntries.count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension LogViewController: NSTableViewDelegate {
|
||||||
|
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
|
||||||
|
if LogColumn.time.isRepresenting(tableColumn: tableColumn) {
|
||||||
|
let cell: LogViewTimestampCell = tableView.dequeueReusableCell()
|
||||||
|
cell.stringValue = logEntries[row].timestamp
|
||||||
|
return cell
|
||||||
|
} else if LogColumn.logMessage.isRepresenting(tableColumn: tableColumn) {
|
||||||
|
let cell: LogViewMessageCell = tableView.dequeueReusableCell()
|
||||||
|
cell.stringValue = logEntries[row].message
|
||||||
|
cell.preferredMaxLayoutWidth = tableColumn?.width ?? 0
|
||||||
|
return cell
|
||||||
|
} else {
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -54,8 +54,7 @@ class ManageTunnelsRootViewController: NSViewController {
|
|||||||
tunnelDetailContainerView.topAnchor.constraint(equalTo: container.topAnchor),
|
tunnelDetailContainerView.topAnchor.constraint(equalTo: container.topAnchor),
|
||||||
tunnelDetailContainerView.bottomAnchor.constraint(equalTo: container.bottomAnchor),
|
tunnelDetailContainerView.bottomAnchor.constraint(equalTo: container.bottomAnchor),
|
||||||
tunnelDetailContainerView.leadingAnchor.constraint(equalTo: tunnelsListView.trailingAnchor, constant: centralSpacing),
|
tunnelDetailContainerView.leadingAnchor.constraint(equalTo: tunnelsListView.trailingAnchor, constant: centralSpacing),
|
||||||
tunnelDetailContainerView.trailingAnchor.constraint(equalTo: container.trailingAnchor),
|
tunnelDetailContainerView.trailingAnchor.constraint(equalTo: container.trailingAnchor)
|
||||||
tunnelsListView.widthAnchor.constraint(equalTo: container.widthAnchor, multiplier: 0.3)
|
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,14 +77,40 @@ class ManageTunnelsRootViewController: NSViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension ManageTunnelsRootViewController: TunnelsListTableViewControllerDelegate {
|
extension ManageTunnelsRootViewController: TunnelsListTableViewControllerDelegate {
|
||||||
func tunnelSelected(tunnel: TunnelContainer) {
|
func tunnelsSelected(tunnelIndices: [Int]) {
|
||||||
let tunnelDetailVC = TunnelDetailTableViewController(tunnelsManager: tunnelsManager, tunnel: tunnel)
|
assert(!tunnelIndices.isEmpty)
|
||||||
setTunnelDetailContentVC(tunnelDetailVC)
|
if tunnelIndices.count == 1 {
|
||||||
self.tunnelDetailVC = tunnelDetailVC
|
let tunnel = tunnelsManager.tunnel(at: tunnelIndices.first!)
|
||||||
|
if tunnel.isTunnelConfigurationAvailableInKeychain {
|
||||||
|
let tunnelDetailVC = TunnelDetailTableViewController(tunnelsManager: tunnelsManager, tunnel: tunnel)
|
||||||
|
setTunnelDetailContentVC(tunnelDetailVC)
|
||||||
|
self.tunnelDetailVC = tunnelDetailVC
|
||||||
|
} else {
|
||||||
|
let unusableTunnelDetailVC = tunnelDetailContentVC as? UnusableTunnelDetailViewController ?? UnusableTunnelDetailViewController()
|
||||||
|
unusableTunnelDetailVC.onButtonClicked = { [weak tunnelsListVC] in
|
||||||
|
tunnelsListVC?.handleRemoveTunnelAction()
|
||||||
|
}
|
||||||
|
setTunnelDetailContentVC(unusableTunnelDetailVC)
|
||||||
|
self.tunnelDetailVC = nil
|
||||||
|
}
|
||||||
|
} else if tunnelIndices.count > 1 {
|
||||||
|
let multiSelectionVC = tunnelDetailContentVC as? ButtonedDetailViewController ?? ButtonedDetailViewController()
|
||||||
|
multiSelectionVC.setButtonTitle(tr(format: "macButtonDeleteTunnels (%d)", tunnelIndices.count))
|
||||||
|
multiSelectionVC.onButtonClicked = { [weak tunnelsListVC] in
|
||||||
|
tunnelsListVC?.handleRemoveTunnelAction()
|
||||||
|
}
|
||||||
|
setTunnelDetailContentVC(multiSelectionVC)
|
||||||
|
self.tunnelDetailVC = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func tunnelsListEmpty() {
|
func tunnelsListEmpty() {
|
||||||
let noTunnelsVC = NoTunnelsDetailViewController(tunnelsManager: tunnelsManager)
|
let noTunnelsVC = ButtonedDetailViewController()
|
||||||
|
noTunnelsVC.setButtonTitle(tr("macButtonImportTunnels"))
|
||||||
|
noTunnelsVC.onButtonClicked = { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
ImportPanelPresenter.presentImportPanel(tunnelsManager: self.tunnelsManager, sourceVC: self)
|
||||||
|
}
|
||||||
setTunnelDetailContentVC(noTunnelsVC)
|
setTunnelDetailContentVC(noTunnelsVC)
|
||||||
self.tunnelDetailVC = nil
|
self.tunnelDetailVC = nil
|
||||||
}
|
}
|
||||||
@@ -102,7 +127,7 @@ extension ManageTunnelsRootViewController {
|
|||||||
switch event.charactersIgnoringModifiers {
|
switch event.charactersIgnoringModifiers {
|
||||||
case "n":
|
case "n":
|
||||||
tunnelsListVC?.handleAddEmptyTunnelAction()
|
tunnelsListVC?.handleAddEmptyTunnelAction()
|
||||||
case "i":
|
case "o":
|
||||||
tunnelsListVC?.handleImportTunnelAction()
|
tunnelsListVC?.handleImportTunnelAction()
|
||||||
case "t":
|
case "t":
|
||||||
tunnelDetailVC?.handleToggleActiveStatusAction()
|
tunnelDetailVC?.handleToggleActiveStatusAction()
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
|
||||||
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
|
||||||
|
|
||||||
import Cocoa
|
|
||||||
|
|
||||||
class NoTunnelsDetailViewController: NSViewController {
|
|
||||||
|
|
||||||
let tunnelsManager: TunnelsManager
|
|
||||||
|
|
||||||
let importButton: NSButton = {
|
|
||||||
let button = NSButton()
|
|
||||||
button.title = tr("macButtonImportTunnels")
|
|
||||||
button.setButtonType(.momentaryPushIn)
|
|
||||||
button.bezelStyle = .rounded
|
|
||||||
return button
|
|
||||||
}()
|
|
||||||
|
|
||||||
init(tunnelsManager: TunnelsManager) {
|
|
||||||
self.tunnelsManager = tunnelsManager
|
|
||||||
super.init(nibName: nil, bundle: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override func loadView() {
|
|
||||||
let view = NSView()
|
|
||||||
|
|
||||||
importButton.target = self
|
|
||||||
importButton.action = #selector(importTunnelClicked)
|
|
||||||
|
|
||||||
view.addSubview(importButton)
|
|
||||||
importButton.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
importButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
|
||||||
importButton.centerYAnchor.constraint(equalTo: view.centerYAnchor)
|
|
||||||
])
|
|
||||||
self.view = view
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func importTunnelClicked() {
|
|
||||||
// We pass sourceVC as parent instead of self because this VC will not be visible when the import completes
|
|
||||||
ImportPanelPresenter.presentImportPanel(tunnelsManager: tunnelsManager, sourceVC: parent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+194
-95
@@ -9,13 +9,15 @@ class TunnelDetailTableViewController: NSViewController {
|
|||||||
case interfaceFieldRow(TunnelViewModel.InterfaceField)
|
case interfaceFieldRow(TunnelViewModel.InterfaceField)
|
||||||
case peerFieldRow(peer: TunnelViewModel.PeerData, field: TunnelViewModel.PeerField)
|
case peerFieldRow(peer: TunnelViewModel.PeerData, field: TunnelViewModel.PeerField)
|
||||||
case onDemandRow
|
case onDemandRow
|
||||||
|
case onDemandSSIDRow
|
||||||
case spacerRow
|
case spacerRow
|
||||||
|
|
||||||
func localizedSectionKeyString() -> String {
|
func localizedSectionKeyString() -> String {
|
||||||
switch self {
|
switch self {
|
||||||
case .interfaceFieldRow: return tr("tunnelSectionTitleInterface")
|
case .interfaceFieldRow: return tr("tunnelSectionTitleInterface")
|
||||||
case .peerFieldRow: return tr("tunnelSectionTitlePeer")
|
case .peerFieldRow: return tr("tunnelSectionTitlePeer")
|
||||||
case .onDemandRow: return ""
|
case .onDemandRow: return tr("macFieldOnDemand")
|
||||||
|
case .onDemandSSIDRow: return ""
|
||||||
case .spacerRow: return ""
|
case .spacerRow: return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,14 +27,15 @@ class TunnelDetailTableViewController: NSViewController {
|
|||||||
case .interfaceFieldRow(let field): return field == .name
|
case .interfaceFieldRow(let field): return field == .name
|
||||||
case .peerFieldRow(_, let field): return field == .publicKey
|
case .peerFieldRow(_, let field): return field == .publicKey
|
||||||
case .onDemandRow: return true
|
case .onDemandRow: return true
|
||||||
|
case .onDemandSSIDRow: return false
|
||||||
case .spacerRow: return false
|
case .spacerRow: return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static let interfaceFields: [TunnelViewModel.InterfaceField] = [
|
static let interfaceFields: [TunnelViewModel.InterfaceField] = [
|
||||||
.name, .publicKey, .addresses,
|
.name, .status, .publicKey, .addresses,
|
||||||
.listenPort, .mtu, .dns
|
.listenPort, .mtu, .dns, .toggleStatus
|
||||||
]
|
]
|
||||||
|
|
||||||
static let peerFields: [TunnelViewModel.PeerField] = [
|
static let peerFields: [TunnelViewModel.PeerField] = [
|
||||||
@@ -41,6 +44,10 @@ class TunnelDetailTableViewController: NSViewController {
|
|||||||
.rxBytes, .txBytes, .lastHandshakeTime
|
.rxBytes, .txBytes, .lastHandshakeTime
|
||||||
]
|
]
|
||||||
|
|
||||||
|
static let onDemandFields: [ActivateOnDemandViewModel.OnDemandField] = [
|
||||||
|
.onDemand, .ssid
|
||||||
|
]
|
||||||
|
|
||||||
let tableView: NSTableView = {
|
let tableView: NSTableView = {
|
||||||
let tableView = NSTableView()
|
let tableView = NSTableView()
|
||||||
tableView.addTableColumn(NSTableColumn(identifier: NSUserInterfaceItemIdentifier("TunnelDetail")))
|
tableView.addTableColumn(NSTableColumn(identifier: NSUserInterfaceItemIdentifier("TunnelDetail")))
|
||||||
@@ -51,21 +58,12 @@ class TunnelDetailTableViewController: NSViewController {
|
|||||||
return tableView
|
return tableView
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let statusCheckbox: NSButton = {
|
|
||||||
let checkbox = NSButton()
|
|
||||||
checkbox.title = ""
|
|
||||||
checkbox.setButtonType(.switch)
|
|
||||||
checkbox.state = .off
|
|
||||||
checkbox.toolTip = "Toggle status (⌘T)"
|
|
||||||
return checkbox
|
|
||||||
}()
|
|
||||||
|
|
||||||
let editButton: NSButton = {
|
let editButton: NSButton = {
|
||||||
let button = NSButton()
|
let button = NSButton()
|
||||||
button.title = tr("Edit")
|
button.title = tr("Edit")
|
||||||
button.setButtonType(.momentaryPushIn)
|
button.setButtonType(.momentaryPushIn)
|
||||||
button.bezelStyle = .rounded
|
button.bezelStyle = .rounded
|
||||||
button.toolTip = "Edit tunnel (⌘E)"
|
button.toolTip = tr("macToolTipEditTunnel")
|
||||||
return button
|
return button
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -85,6 +83,9 @@ class TunnelDetailTableViewController: NSViewController {
|
|||||||
updateTableViewModelRows()
|
updateTableViewModelRows()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var onDemandViewModel: ActivateOnDemandViewModel
|
||||||
|
|
||||||
private var tableViewModelRowsBySection = [[(isVisible: Bool, modelRow: TableViewModelRow)]]()
|
private var tableViewModelRowsBySection = [[(isVisible: Bool, modelRow: TableViewModelRow)]]()
|
||||||
private var tableViewModelRows = [TableViewModelRow]()
|
private var tableViewModelRows = [TableViewModelRow]()
|
||||||
|
|
||||||
@@ -96,6 +97,7 @@ class TunnelDetailTableViewController: NSViewController {
|
|||||||
self.tunnelsManager = tunnelsManager
|
self.tunnelsManager = tunnelsManager
|
||||||
self.tunnel = tunnel
|
self.tunnel = tunnel
|
||||||
tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration)
|
tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration)
|
||||||
|
onDemandViewModel = ActivateOnDemandViewModel(tunnel: tunnel)
|
||||||
super.init(nibName: nil, bundle: nil)
|
super.init(nibName: nil, bundle: nil)
|
||||||
updateTableViewModelRowsBySection()
|
updateTableViewModelRowsBySection()
|
||||||
updateTableViewModelRows()
|
updateTableViewModelRows()
|
||||||
@@ -113,9 +115,6 @@ class TunnelDetailTableViewController: NSViewController {
|
|||||||
tableView.dataSource = self
|
tableView.dataSource = self
|
||||||
tableView.delegate = self
|
tableView.delegate = self
|
||||||
|
|
||||||
statusCheckbox.target = self
|
|
||||||
statusCheckbox.action = #selector(statusCheckboxToggled(sender:))
|
|
||||||
|
|
||||||
editButton.target = self
|
editButton.target = self
|
||||||
editButton.action = #selector(handleEditTunnelAction)
|
editButton.action = #selector(handleEditTunnelAction)
|
||||||
|
|
||||||
@@ -133,11 +132,9 @@ class TunnelDetailTableViewController: NSViewController {
|
|||||||
containerView.addLayoutGuide(bottomControlsContainer)
|
containerView.addLayoutGuide(bottomControlsContainer)
|
||||||
containerView.addSubview(box)
|
containerView.addSubview(box)
|
||||||
containerView.addSubview(scrollView)
|
containerView.addSubview(scrollView)
|
||||||
containerView.addSubview(statusCheckbox)
|
|
||||||
containerView.addSubview(editButton)
|
containerView.addSubview(editButton)
|
||||||
box.translatesAutoresizingMaskIntoConstraints = false
|
box.translatesAutoresizingMaskIntoConstraints = false
|
||||||
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
statusCheckbox.translatesAutoresizingMaskIntoConstraints = false
|
|
||||||
editButton.translatesAutoresizingMaskIntoConstraints = false
|
editButton.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
@@ -149,10 +146,8 @@ class TunnelDetailTableViewController: NSViewController {
|
|||||||
bottomControlsContainer.heightAnchor.constraint(equalToConstant: 32),
|
bottomControlsContainer.heightAnchor.constraint(equalToConstant: 32),
|
||||||
scrollView.bottomAnchor.constraint(equalTo: bottomControlsContainer.topAnchor),
|
scrollView.bottomAnchor.constraint(equalTo: bottomControlsContainer.topAnchor),
|
||||||
bottomControlsContainer.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
|
bottomControlsContainer.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
|
||||||
statusCheckbox.leadingAnchor.constraint(equalTo: bottomControlsContainer.leadingAnchor),
|
|
||||||
bottomControlsContainer.bottomAnchor.constraint(equalTo: statusCheckbox.bottomAnchor, constant: 4),
|
|
||||||
editButton.trailingAnchor.constraint(equalTo: bottomControlsContainer.trailingAnchor),
|
editButton.trailingAnchor.constraint(equalTo: bottomControlsContainer.trailingAnchor),
|
||||||
bottomControlsContainer.bottomAnchor.constraint(equalTo: editButton.bottomAnchor, constant: 4)
|
bottomControlsContainer.bottomAnchor.constraint(equalTo: editButton.bottomAnchor, constant: 0)
|
||||||
])
|
])
|
||||||
|
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
@@ -162,6 +157,11 @@ class TunnelDetailTableViewController: NSViewController {
|
|||||||
scrollView.trailingAnchor.constraint(equalTo: box.trailingAnchor)
|
scrollView.trailingAnchor.constraint(equalTo: box.trailingAnchor)
|
||||||
])
|
])
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
containerView.widthAnchor.constraint(greaterThanOrEqualToConstant: 320),
|
||||||
|
containerView.heightAnchor.constraint(greaterThanOrEqualToConstant: 120)
|
||||||
|
])
|
||||||
|
|
||||||
view = containerView
|
view = containerView
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,7 +170,9 @@ class TunnelDetailTableViewController: NSViewController {
|
|||||||
|
|
||||||
var interfaceSection = [(isVisible: Bool, modelRow: TableViewModelRow)]()
|
var interfaceSection = [(isVisible: Bool, modelRow: TableViewModelRow)]()
|
||||||
for field in TunnelDetailTableViewController.interfaceFields {
|
for field in TunnelDetailTableViewController.interfaceFields {
|
||||||
interfaceSection.append((isVisible: !tunnelViewModel.interfaceData[field].isEmpty, modelRow: .interfaceFieldRow(field)))
|
let isStatus = field == .status || field == .toggleStatus
|
||||||
|
let isEmpty = tunnelViewModel.interfaceData[field].isEmpty
|
||||||
|
interfaceSection.append((isVisible: isStatus || !isEmpty, modelRow: .interfaceFieldRow(field)))
|
||||||
}
|
}
|
||||||
interfaceSection.append((isVisible: true, modelRow: .spacerRow))
|
interfaceSection.append((isVisible: true, modelRow: .spacerRow))
|
||||||
modelRowsBySection.append(interfaceSection)
|
modelRowsBySection.append(interfaceSection)
|
||||||
@@ -186,6 +188,9 @@ class TunnelDetailTableViewController: NSViewController {
|
|||||||
|
|
||||||
var onDemandSection = [(isVisible: Bool, modelRow: TableViewModelRow)]()
|
var onDemandSection = [(isVisible: Bool, modelRow: TableViewModelRow)]()
|
||||||
onDemandSection.append((isVisible: true, modelRow: .onDemandRow))
|
onDemandSection.append((isVisible: true, modelRow: .onDemandRow))
|
||||||
|
if onDemandViewModel.isWiFiInterfaceEnabled {
|
||||||
|
onDemandSection.append((isVisible: true, modelRow: .onDemandSSIDRow))
|
||||||
|
}
|
||||||
modelRowsBySection.append(onDemandSection)
|
modelRowsBySection.append(onDemandSection)
|
||||||
|
|
||||||
tableViewModelRowsBySection = modelRowsBySection
|
tableViewModelRowsBySection = modelRowsBySection
|
||||||
@@ -196,28 +201,6 @@ class TunnelDetailTableViewController: NSViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateStatus() {
|
func updateStatus() {
|
||||||
let statusText: String
|
|
||||||
switch tunnel.status {
|
|
||||||
case .waiting:
|
|
||||||
statusText = tr("tunnelStatusWaiting")
|
|
||||||
case .inactive:
|
|
||||||
statusText = tr("tunnelStatusInactive")
|
|
||||||
case .activating:
|
|
||||||
statusText = tr("tunnelStatusActivating")
|
|
||||||
case .active:
|
|
||||||
statusText = tr("tunnelStatusActive")
|
|
||||||
case .deactivating:
|
|
||||||
statusText = tr("tunnelStatusDeactivating")
|
|
||||||
case .reasserting:
|
|
||||||
statusText = tr("tunnelStatusReasserting")
|
|
||||||
case .restarting:
|
|
||||||
statusText = tr("tunnelStatusRestarting")
|
|
||||||
}
|
|
||||||
statusCheckbox.title = tr(format: "macStatus (%@)", statusText)
|
|
||||||
let shouldBeChecked = (tunnel.status != .inactive && tunnel.status != .deactivating)
|
|
||||||
let shouldBeEnabled = (tunnel.status == .active || tunnel.status == .inactive)
|
|
||||||
statusCheckbox.state = shouldBeChecked ? .on : .off
|
|
||||||
statusCheckbox.isEnabled = shouldBeEnabled
|
|
||||||
if tunnel.status == .active {
|
if tunnel.status == .active {
|
||||||
startUpdatingRuntimeConfiguration()
|
startUpdatingRuntimeConfiguration()
|
||||||
} else if tunnel.status == .inactive {
|
} else if tunnel.status == .inactive {
|
||||||
@@ -244,15 +227,6 @@ class TunnelDetailTableViewController: NSViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func statusCheckboxToggled(sender: AnyObject?) {
|
|
||||||
guard let statusCheckbox = sender as? NSButton else { return }
|
|
||||||
if statusCheckbox.state == .on {
|
|
||||||
tunnelsManager.startActivation(of: tunnel)
|
|
||||||
} else if statusCheckbox.state == .off {
|
|
||||||
tunnelsManager.startDeactivation(of: tunnel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override func viewWillDisappear() {
|
override func viewWillDisappear() {
|
||||||
super.viewWillDisappear()
|
super.viewWillDisappear()
|
||||||
if let tunnelEditVC = tunnelEditVC {
|
if let tunnelEditVC = tunnelEditVC {
|
||||||
@@ -263,7 +237,24 @@ class TunnelDetailTableViewController: NSViewController {
|
|||||||
|
|
||||||
func applyTunnelConfiguration(tunnelConfiguration: TunnelConfiguration) {
|
func applyTunnelConfiguration(tunnelConfiguration: TunnelConfiguration) {
|
||||||
// Incorporates changes from tunnelConfiguation. Ignores any changes in peer ordering.
|
// Incorporates changes from tunnelConfiguation. Ignores any changes in peer ordering.
|
||||||
func sectionChanged<T>(fields: [T], modelRowsInSection: inout [(isVisible: Bool, modelRow: TableViewModelRow)], tableView: NSTableView, rowOffset: Int, changes: [T: TunnelViewModel.ChangeHandlers.FieldChange]) {
|
|
||||||
|
let tableView = self.tableView
|
||||||
|
|
||||||
|
func handleSectionFieldsModified<T>(fields: [T], modelRowsInSection: [(isVisible: Bool, modelRow: TableViewModelRow)], rowOffset: Int, changes: [T: TunnelViewModel.Changes.FieldChange]) {
|
||||||
|
var modifiedRowIndices = IndexSet()
|
||||||
|
for (index, field) in fields.enumerated() {
|
||||||
|
guard let change = changes[field] else { continue }
|
||||||
|
if case .modified(_) = change {
|
||||||
|
let row = modelRowsInSection[0 ..< index].filter { $0.isVisible }.count
|
||||||
|
modifiedRowIndices.insert(rowOffset + row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !modifiedRowIndices.isEmpty {
|
||||||
|
tableView.reloadData(forRowIndexes: modifiedRowIndices, columnIndexes: IndexSet(integer: 0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSectionFieldsAddedOrRemoved<T>(fields: [T], modelRowsInSection: inout [(isVisible: Bool, modelRow: TableViewModelRow)], rowOffset: Int, changes: [T: TunnelViewModel.Changes.FieldChange]) {
|
||||||
for (index, field) in fields.enumerated() {
|
for (index, field) in fields.enumerated() {
|
||||||
guard let change = changes[field] else { continue }
|
guard let change = changes[field] else { continue }
|
||||||
let row = modelRowsInSection[0 ..< index].filter { $0.isVisible }.count
|
let row = modelRowsInSection[0 ..< index].filter { $0.isVisible }.count
|
||||||
@@ -275,42 +266,54 @@ class TunnelDetailTableViewController: NSViewController {
|
|||||||
tableView.removeRows(at: IndexSet(integer: rowOffset + row), withAnimation: .effectFade)
|
tableView.removeRows(at: IndexSet(integer: rowOffset + row), withAnimation: .effectFade)
|
||||||
modelRowsInSection[index].isVisible = false
|
modelRowsInSection[index].isVisible = false
|
||||||
case .modified:
|
case .modified:
|
||||||
tableView.removeRows(at: IndexSet(integer: rowOffset + row), withAnimation: [])
|
break
|
||||||
tableView.insertRows(at: IndexSet(integer: rowOffset + row), withAnimation: [])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var isChanged = false
|
let changes = self.tunnelViewModel.applyConfiguration(other: tunnelConfiguration)
|
||||||
let changeHandlers = TunnelViewModel.ChangeHandlers(
|
|
||||||
interfaceChanged: { [weak self] changes in
|
if !changes.interfaceChanges.isEmpty {
|
||||||
guard let self = self else { return }
|
handleSectionFieldsModified(fields: TunnelDetailTableViewController.interfaceFields,
|
||||||
sectionChanged(fields: TunnelDetailTableViewController.interfaceFields, modelRowsInSection: &self.tableViewModelRowsBySection[0],
|
modelRowsInSection: self.tableViewModelRowsBySection[0],
|
||||||
tableView: self.tableView, rowOffset: 0, changes: changes)
|
rowOffset: 0, changes: changes.interfaceChanges)
|
||||||
isChanged = true
|
}
|
||||||
},
|
for (peerIndex, peerChanges) in changes.peerChanges {
|
||||||
peerChangedAt: { [weak self] peerIndex, changes in
|
let sectionIndex = 1 + peerIndex
|
||||||
guard let self = self else { return }
|
let rowOffset = self.tableViewModelRowsBySection[0 ..< sectionIndex].flatMap { $0.filter { $0.isVisible } }.count
|
||||||
let sectionIndex = 1 + peerIndex
|
handleSectionFieldsModified(fields: TunnelDetailTableViewController.peerFields,
|
||||||
let rowOffset = self.tableViewModelRowsBySection[0 ..< sectionIndex].flatMap { $0.filter { $0.isVisible } }.count
|
modelRowsInSection: self.tableViewModelRowsBySection[sectionIndex],
|
||||||
sectionChanged(fields: TunnelDetailTableViewController.peerFields, modelRowsInSection: &self.tableViewModelRowsBySection[sectionIndex],
|
rowOffset: rowOffset, changes: peerChanges)
|
||||||
tableView: self.tableView, rowOffset: rowOffset, changes: changes)
|
}
|
||||||
isChanged = true
|
|
||||||
},
|
let isAnyInterfaceFieldAddedOrRemoved = changes.interfaceChanges.contains { $0.value == .added || $0.value == .removed }
|
||||||
peersRemovedAt: { [weak self] peerIndices in
|
let isAnyPeerFieldAddedOrRemoved = changes.peerChanges.contains { $0.changes.contains { $0.value == .added || $0.value == .removed } }
|
||||||
guard let self = self else { return }
|
|
||||||
for peerIndex in peerIndices {
|
if isAnyInterfaceFieldAddedOrRemoved || isAnyPeerFieldAddedOrRemoved || !changes.peersRemovedIndices.isEmpty || !changes.peersInsertedIndices.isEmpty {
|
||||||
|
tableView.beginUpdates()
|
||||||
|
if isAnyInterfaceFieldAddedOrRemoved {
|
||||||
|
handleSectionFieldsAddedOrRemoved(fields: TunnelDetailTableViewController.interfaceFields,
|
||||||
|
modelRowsInSection: &self.tableViewModelRowsBySection[0],
|
||||||
|
rowOffset: 0, changes: changes.interfaceChanges)
|
||||||
|
}
|
||||||
|
if isAnyPeerFieldAddedOrRemoved {
|
||||||
|
for (peerIndex, peerChanges) in changes.peerChanges {
|
||||||
|
let sectionIndex = 1 + peerIndex
|
||||||
|
let rowOffset = self.tableViewModelRowsBySection[0 ..< sectionIndex].flatMap { $0.filter { $0.isVisible } }.count
|
||||||
|
handleSectionFieldsAddedOrRemoved(fields: TunnelDetailTableViewController.peerFields, modelRowsInSection: &self.tableViewModelRowsBySection[sectionIndex], rowOffset: rowOffset, changes: peerChanges)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !changes.peersRemovedIndices.isEmpty {
|
||||||
|
for peerIndex in changes.peersRemovedIndices {
|
||||||
let sectionIndex = 1 + peerIndex
|
let sectionIndex = 1 + peerIndex
|
||||||
let rowOffset = self.tableViewModelRowsBySection[0 ..< sectionIndex].flatMap { $0.filter { $0.isVisible } }.count
|
let rowOffset = self.tableViewModelRowsBySection[0 ..< sectionIndex].flatMap { $0.filter { $0.isVisible } }.count
|
||||||
let count = self.tableViewModelRowsBySection[sectionIndex].filter { $0.isVisible }.count
|
let count = self.tableViewModelRowsBySection[sectionIndex].filter { $0.isVisible }.count
|
||||||
self.tableView.removeRows(at: IndexSet(integersIn: rowOffset ..< rowOffset + count), withAnimation: .effectFade)
|
self.tableView.removeRows(at: IndexSet(integersIn: rowOffset ..< rowOffset + count), withAnimation: .effectFade)
|
||||||
self.tableViewModelRowsBySection.remove(at: sectionIndex)
|
self.tableViewModelRowsBySection.remove(at: sectionIndex)
|
||||||
}
|
}
|
||||||
isChanged = true
|
}
|
||||||
},
|
if !changes.peersInsertedIndices.isEmpty {
|
||||||
peersInsertedAt: { [weak self] peerIndices in
|
for peerIndex in changes.peersInsertedIndices {
|
||||||
guard let self = self else { return }
|
|
||||||
for peerIndex in peerIndices {
|
|
||||||
let peerData = self.tunnelViewModel.peersData[peerIndex]
|
let peerData = self.tunnelViewModel.peersData[peerIndex]
|
||||||
let sectionIndex = 1 + peerIndex
|
let sectionIndex = 1 + peerIndex
|
||||||
let rowOffset = self.tableViewModelRowsBySection[0 ..< sectionIndex].flatMap { $0.filter { $0.isVisible } }.count
|
let rowOffset = self.tableViewModelRowsBySection[0 ..< sectionIndex].flatMap { $0.filter { $0.isVisible } }.count
|
||||||
@@ -322,16 +325,10 @@ class TunnelDetailTableViewController: NSViewController {
|
|||||||
self.tableView.insertRows(at: IndexSet(integersIn: rowOffset ..< rowOffset + count), withAnimation: .effectFade)
|
self.tableView.insertRows(at: IndexSet(integersIn: rowOffset ..< rowOffset + count), withAnimation: .effectFade)
|
||||||
self.tableViewModelRowsBySection.insert(modelRowsInSection, at: sectionIndex)
|
self.tableViewModelRowsBySection.insert(modelRowsInSection, at: sectionIndex)
|
||||||
}
|
}
|
||||||
isChanged = true
|
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
|
||||||
tableView.beginUpdates()
|
|
||||||
self.tunnelViewModel.applyConfiguration(other: tunnelConfiguration, changeHandlers: changeHandlers)
|
|
||||||
if isChanged {
|
|
||||||
updateTableViewModelRows()
|
updateTableViewModelRows()
|
||||||
|
tableView.endUpdates()
|
||||||
}
|
}
|
||||||
tableView.endUpdates()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func reloadRuntimeConfiguration() {
|
private func reloadRuntimeConfiguration() {
|
||||||
@@ -370,18 +367,26 @@ extension TunnelDetailTableViewController: NSTableViewDelegate {
|
|||||||
let modelRow = tableViewModelRows[row]
|
let modelRow = tableViewModelRows[row]
|
||||||
switch modelRow {
|
switch modelRow {
|
||||||
case .interfaceFieldRow(let field):
|
case .interfaceFieldRow(let field):
|
||||||
let cell: KeyValueRow = tableView.dequeueReusableCell()
|
if field == .status {
|
||||||
let localizedKeyString = modelRow.isTitleRow() ? modelRow.localizedSectionKeyString() : field.localizedUIString
|
return statusCell()
|
||||||
cell.key = tr(format: "macFieldKey (%@)", localizedKeyString)
|
} else if field == .toggleStatus {
|
||||||
cell.value = tunnelViewModel.interfaceData[field]
|
return toggleStatusCell()
|
||||||
cell.isKeyInBold = modelRow.isTitleRow()
|
} else {
|
||||||
return cell
|
let cell: KeyValueRow = tableView.dequeueReusableCell()
|
||||||
|
let localizedKeyString = modelRow.isTitleRow() ? modelRow.localizedSectionKeyString() : field.localizedUIString
|
||||||
|
cell.key = tr(format: "macFieldKey (%@)", localizedKeyString)
|
||||||
|
cell.value = tunnelViewModel.interfaceData[field]
|
||||||
|
cell.isKeyInBold = modelRow.isTitleRow()
|
||||||
|
return cell
|
||||||
|
}
|
||||||
case .peerFieldRow(let peerData, let field):
|
case .peerFieldRow(let peerData, let field):
|
||||||
let cell: KeyValueRow = tableView.dequeueReusableCell()
|
let cell: KeyValueRow = tableView.dequeueReusableCell()
|
||||||
let localizedKeyString = modelRow.isTitleRow() ? modelRow.localizedSectionKeyString() : field.localizedUIString
|
let localizedKeyString = modelRow.isTitleRow() ? modelRow.localizedSectionKeyString() : field.localizedUIString
|
||||||
cell.key = tr(format: "macFieldKey (%@)", localizedKeyString)
|
cell.key = tr(format: "macFieldKey (%@)", localizedKeyString)
|
||||||
if field == .persistentKeepAlive {
|
if field == .persistentKeepAlive {
|
||||||
cell.value = tr(format: "tunnelPeerPersistentKeepaliveValue (%@)", peerData[field])
|
cell.value = tr(format: "tunnelPeerPersistentKeepaliveValue (%@)", peerData[field])
|
||||||
|
} else if field == .preSharedKey {
|
||||||
|
cell.value = tr("tunnelPeerPresharedKeyEnabled")
|
||||||
} else {
|
} else {
|
||||||
cell.value = peerData[field]
|
cell.value = peerData[field]
|
||||||
}
|
}
|
||||||
@@ -391,10 +396,103 @@ extension TunnelDetailTableViewController: NSTableViewDelegate {
|
|||||||
return NSView()
|
return NSView()
|
||||||
case .onDemandRow:
|
case .onDemandRow:
|
||||||
let cell: KeyValueRow = tableView.dequeueReusableCell()
|
let cell: KeyValueRow = tableView.dequeueReusableCell()
|
||||||
cell.key = tr("macFieldOnDemand")
|
cell.key = modelRow.localizedSectionKeyString()
|
||||||
cell.value = TunnelViewModel.activateOnDemandDetailText(for: tunnel.activateOnDemandSetting)
|
cell.value = onDemandViewModel.localizedInterfaceDescription
|
||||||
cell.isKeyInBold = true
|
cell.isKeyInBold = true
|
||||||
return cell
|
return cell
|
||||||
|
case .onDemandSSIDRow:
|
||||||
|
let cell: KeyValueRow = tableView.dequeueReusableCell()
|
||||||
|
cell.key = tr("macFieldOnDemandSSIDs")
|
||||||
|
let value: String
|
||||||
|
if onDemandViewModel.ssidOption == .anySSID {
|
||||||
|
value = onDemandViewModel.ssidOption.localizedUIString
|
||||||
|
} else {
|
||||||
|
value = tr(format: "tunnelOnDemandSSIDOptionDescriptionMac (%1$@: %2$@)",
|
||||||
|
onDemandViewModel.ssidOption.localizedUIString,
|
||||||
|
onDemandViewModel.selectedSSIDs.joined(separator: ", "))
|
||||||
|
}
|
||||||
|
cell.value = value
|
||||||
|
cell.isKeyInBold = false
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
guard let cell = cell else { return }
|
||||||
|
cell.value = TunnelDetailTableViewController.localizedStatusDescription(forStatus: tunnel.status)
|
||||||
|
cell.valueImage = TunnelDetailTableViewController.image(forStatus: tunnel.status)
|
||||||
|
}
|
||||||
|
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.buttonToolTip = tr("macToolTipToggleStatus")
|
||||||
|
cell.onButtonClicked = { [weak self] in
|
||||||
|
self?.handleToggleActiveStatusAction()
|
||||||
|
}
|
||||||
|
cell.observationToken = tunnel.observe(\.status) { [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)
|
||||||
|
}
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func localizedStatusDescription(forStatus status: TunnelStatus) -> String {
|
||||||
|
switch status {
|
||||||
|
case .inactive:
|
||||||
|
return tr("tunnelStatusInactive")
|
||||||
|
case .activating:
|
||||||
|
return tr("tunnelStatusActivating")
|
||||||
|
case .active:
|
||||||
|
return tr("tunnelStatusActive")
|
||||||
|
case .deactivating:
|
||||||
|
return tr("tunnelStatusDeactivating")
|
||||||
|
case .reasserting:
|
||||||
|
return tr("tunnelStatusReasserting")
|
||||||
|
case .restarting:
|
||||||
|
return tr("tunnelStatusRestarting")
|
||||||
|
case .waiting:
|
||||||
|
return tr("tunnelStatusWaiting")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func image(forStatus status: TunnelStatus?) -> NSImage? {
|
||||||
|
guard let status = status else { return nil }
|
||||||
|
switch 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -402,6 +500,7 @@ extension TunnelDetailTableViewController: NSTableViewDelegate {
|
|||||||
extension TunnelDetailTableViewController: TunnelEditViewControllerDelegate {
|
extension TunnelDetailTableViewController: TunnelEditViewControllerDelegate {
|
||||||
func tunnelSaved(tunnel: TunnelContainer) {
|
func tunnelSaved(tunnel: TunnelContainer) {
|
||||||
tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration)
|
tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration)
|
||||||
|
onDemandViewModel = ActivateOnDemandViewModel(tunnel: tunnel)
|
||||||
updateTableViewModelRowsBySection()
|
updateTableViewModelRowsBySection()
|
||||||
updateTableViewModelRows()
|
updateTableViewModelRows()
|
||||||
updateStatus()
|
updateStatus()
|
||||||
|
|||||||
@@ -42,11 +42,7 @@ class TunnelEditViewController: NSViewController {
|
|||||||
return textView
|
return textView
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let onDemandRow: PopupRow = {
|
let onDemandControlsRow = OnDemandControlsRow()
|
||||||
let popupRow = PopupRow()
|
|
||||||
popupRow.key = tr("macFieldOnDemand")
|
|
||||||
return popupRow
|
|
||||||
}()
|
|
||||||
|
|
||||||
let scrollView: NSScrollView = {
|
let scrollView: NSScrollView = {
|
||||||
let scrollView = NSScrollView()
|
let scrollView = NSScrollView()
|
||||||
@@ -56,6 +52,14 @@ class TunnelEditViewController: NSViewController {
|
|||||||
return scrollView
|
return scrollView
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
let excludePrivateIPsCheckbox: NSButton = {
|
||||||
|
let checkbox = NSButton()
|
||||||
|
checkbox.title = tr("tunnelPeerExcludePrivateIPs")
|
||||||
|
checkbox.setButtonType(.switch)
|
||||||
|
checkbox.state = .off
|
||||||
|
return checkbox
|
||||||
|
}()
|
||||||
|
|
||||||
let discardButton: NSButton = {
|
let discardButton: NSButton = {
|
||||||
let button = NSButton()
|
let button = NSButton()
|
||||||
button.title = tr("macEditDiscard")
|
button.title = tr("macEditDiscard")
|
||||||
@@ -72,24 +76,22 @@ class TunnelEditViewController: NSViewController {
|
|||||||
return button
|
return button
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let activateOnDemandOptions: [ActivateOnDemandOption] = [
|
|
||||||
.none,
|
|
||||||
.useOnDemandOverWiFiOrEthernet,
|
|
||||||
.useOnDemandOverWiFiOnly,
|
|
||||||
.useOnDemandOverEthernetOnly
|
|
||||||
]
|
|
||||||
|
|
||||||
let tunnelsManager: TunnelsManager
|
let tunnelsManager: TunnelsManager
|
||||||
let tunnel: TunnelContainer?
|
let tunnel: TunnelContainer?
|
||||||
|
var onDemandViewModel: ActivateOnDemandViewModel
|
||||||
|
|
||||||
weak var delegate: TunnelEditViewControllerDelegate?
|
weak var delegate: TunnelEditViewControllerDelegate?
|
||||||
|
|
||||||
var privateKeyObservationToken: AnyObject?
|
var privateKeyObservationToken: AnyObject?
|
||||||
var hasErrorObservationToken: AnyObject?
|
var hasErrorObservationToken: AnyObject?
|
||||||
|
var singlePeerAllowedIPsObservationToken: AnyObject?
|
||||||
|
|
||||||
|
var dnsServersAddedToAllowedIPs: String?
|
||||||
|
|
||||||
init(tunnelsManager: TunnelsManager, tunnel: TunnelContainer?) {
|
init(tunnelsManager: TunnelsManager, tunnel: TunnelContainer?) {
|
||||||
self.tunnelsManager = tunnelsManager
|
self.tunnelsManager = tunnelsManager
|
||||||
self.tunnel = tunnel
|
self.tunnel = tunnel
|
||||||
|
self.onDemandViewModel = tunnel != nil ? ActivateOnDemandViewModel(tunnel: tunnel!) : ActivateOnDemandViewModel()
|
||||||
super.init(nibName: nil, bundle: nil)
|
super.init(nibName: nil, bundle: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,8 +99,7 @@ class TunnelEditViewController: NSViewController {
|
|||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func populateTextFields() {
|
func populateFields() {
|
||||||
let selectedActivateOnDemandOption: ActivateOnDemandOption
|
|
||||||
if let tunnel = tunnel {
|
if let tunnel = tunnel {
|
||||||
// Editing an existing tunnel
|
// Editing an existing tunnel
|
||||||
let tunnelConfiguration = tunnel.tunnelConfiguration!
|
let tunnelConfiguration = tunnel.tunnelConfiguration!
|
||||||
@@ -106,11 +107,9 @@ class TunnelEditViewController: NSViewController {
|
|||||||
textView.string = tunnelConfiguration.asWgQuickConfig()
|
textView.string = tunnelConfiguration.asWgQuickConfig()
|
||||||
publicKeyRow.value = tunnelConfiguration.interface.publicKey.base64Key() ?? ""
|
publicKeyRow.value = tunnelConfiguration.interface.publicKey.base64Key() ?? ""
|
||||||
textView.privateKeyString = tunnelConfiguration.interface.privateKey.base64Key() ?? ""
|
textView.privateKeyString = tunnelConfiguration.interface.privateKey.base64Key() ?? ""
|
||||||
if tunnel.activateOnDemandSetting.isActivateOnDemandEnabled {
|
let singlePeer = tunnelConfiguration.peers.count == 1 ? tunnelConfiguration.peers.first : nil
|
||||||
selectedActivateOnDemandOption = tunnel.activateOnDemandSetting.activateOnDemandOption
|
updateExcludePrivateIPsVisibility(singlePeerAllowedIPs: singlePeer?.allowedIPs.map { $0.stringRepresentation })
|
||||||
} else {
|
dnsServersAddedToAllowedIPs = excludePrivateIPsCheckbox.state == .on ? tunnelConfiguration.interface.dns.map { $0.stringRepresentation }.joined(separator: ", ") : nil
|
||||||
selectedActivateOnDemandOption = .none
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Creating a new tunnel
|
// Creating a new tunnel
|
||||||
let privateKey = Curve25519.generatePrivateKey()
|
let privateKey = Curve25519.generatePrivateKey()
|
||||||
@@ -118,7 +117,8 @@ class TunnelEditViewController: NSViewController {
|
|||||||
let bootstrappingText = "[Interface]\nPrivateKey = \(privateKey.base64Key() ?? "")\n"
|
let bootstrappingText = "[Interface]\nPrivateKey = \(privateKey.base64Key() ?? "")\n"
|
||||||
publicKeyRow.value = publicKey.base64Key() ?? ""
|
publicKeyRow.value = publicKey.base64Key() ?? ""
|
||||||
textView.string = bootstrappingText
|
textView.string = bootstrappingText
|
||||||
selectedActivateOnDemandOption = .none
|
updateExcludePrivateIPsVisibility(singlePeerAllowedIPs: nil)
|
||||||
|
dnsServersAddedToAllowedIPs = nil
|
||||||
}
|
}
|
||||||
privateKeyObservationToken = textView.observe(\.privateKeyString) { [weak publicKeyRow] textView, _ in
|
privateKeyObservationToken = textView.observe(\.privateKeyString) { [weak publicKeyRow] textView, _ in
|
||||||
if let privateKeyString = textView.privateKeyString,
|
if let privateKeyString = textView.privateKeyString,
|
||||||
@@ -133,13 +133,13 @@ class TunnelEditViewController: NSViewController {
|
|||||||
hasErrorObservationToken = textView.observe(\.hasError) { [weak saveButton] textView, _ in
|
hasErrorObservationToken = textView.observe(\.hasError) { [weak saveButton] textView, _ in
|
||||||
saveButton?.isEnabled = !textView.hasError
|
saveButton?.isEnabled = !textView.hasError
|
||||||
}
|
}
|
||||||
|
singlePeerAllowedIPsObservationToken = textView.observe(\.singlePeerAllowedIPs) { [weak self] textView, _ in
|
||||||
onDemandRow.valueOptions = activateOnDemandOptions.map { TunnelViewModel.activateOnDemandOptionText(for: $0) }
|
self?.updateExcludePrivateIPsVisibility(singlePeerAllowedIPs: textView.singlePeerAllowedIPs)
|
||||||
onDemandRow.selectedOptionIndex = activateOnDemandOptions.firstIndex(of: selectedActivateOnDemandOption)!
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func loadView() {
|
override func loadView() {
|
||||||
populateTextFields()
|
populateFields()
|
||||||
|
|
||||||
scrollView.documentView = textView
|
scrollView.documentView = textView
|
||||||
|
|
||||||
@@ -149,16 +149,22 @@ class TunnelEditViewController: NSViewController {
|
|||||||
discardButton.target = self
|
discardButton.target = self
|
||||||
discardButton.action = #selector(handleDiscardAction)
|
discardButton.action = #selector(handleDiscardAction)
|
||||||
|
|
||||||
|
excludePrivateIPsCheckbox.target = self
|
||||||
|
excludePrivateIPsCheckbox.action = #selector(excludePrivateIPsCheckboxToggled(sender:))
|
||||||
|
|
||||||
|
onDemandControlsRow.onDemandViewModel = onDemandViewModel
|
||||||
|
|
||||||
let margin: CGFloat = 20
|
let margin: CGFloat = 20
|
||||||
let internalSpacing: CGFloat = 10
|
let internalSpacing: CGFloat = 10
|
||||||
|
|
||||||
let editorStackView = NSStackView(views: [nameRow, publicKeyRow, onDemandRow, scrollView])
|
let editorStackView = NSStackView(views: [nameRow, publicKeyRow, onDemandControlsRow, scrollView])
|
||||||
editorStackView.orientation = .vertical
|
editorStackView.orientation = .vertical
|
||||||
editorStackView.setHuggingPriority(.defaultHigh, for: .horizontal)
|
editorStackView.setHuggingPriority(.defaultHigh, for: .horizontal)
|
||||||
editorStackView.spacing = internalSpacing
|
editorStackView.spacing = internalSpacing
|
||||||
|
|
||||||
let buttonRowStackView = NSStackView()
|
let buttonRowStackView = NSStackView()
|
||||||
buttonRowStackView.setViews([discardButton, saveButton], in: .trailing)
|
buttonRowStackView.setViews([discardButton, saveButton], in: .trailing)
|
||||||
|
buttonRowStackView.addView(excludePrivateIPsCheckbox, in: .leading)
|
||||||
buttonRowStackView.orientation = .horizontal
|
buttonRowStackView.orientation = .horizontal
|
||||||
buttonRowStackView.spacing = internalSpacing
|
buttonRowStackView.spacing = internalSpacing
|
||||||
|
|
||||||
@@ -177,19 +183,22 @@ class TunnelEditViewController: NSViewController {
|
|||||||
self.view = containerView
|
self.view = containerView
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setUserInteractionEnabled(_ enabled: Bool) {
|
||||||
|
view.window?.ignoresMouseEvents = !enabled
|
||||||
|
nameRow.valueLabel.isEditable = enabled
|
||||||
|
textView.isEditable = enabled
|
||||||
|
onDemandControlsRow.onDemandSSIDsField.isEnabled = enabled
|
||||||
|
}
|
||||||
|
|
||||||
@objc func handleSaveAction() {
|
@objc func handleSaveAction() {
|
||||||
let name = nameRow.value
|
let name = nameRow.value
|
||||||
guard !name.isEmpty else {
|
guard !name.isEmpty else {
|
||||||
ErrorPresenter.showErrorAlert(title: tr("macAlertNameIsEmpty"), message: "", from: self)
|
ErrorPresenter.showErrorAlert(title: tr("macAlertNameIsEmpty"), message: "", from: self)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let onDemandSetting: ActivateOnDemandSetting
|
|
||||||
let onDemandOption = activateOnDemandOptions[onDemandRow.selectedOptionIndex]
|
onDemandControlsRow.saveToViewModel()
|
||||||
if onDemandOption == .none {
|
let onDemandOption = onDemandViewModel.toOnDemandOption()
|
||||||
onDemandSetting = ActivateOnDemandSetting.defaultSetting
|
|
||||||
} else {
|
|
||||||
onDemandSetting = ActivateOnDemandSetting(isActivateOnDemandEnabled: true, activateOnDemandOption: onDemandOption)
|
|
||||||
}
|
|
||||||
|
|
||||||
let isTunnelModifiedWithoutChangingName = (tunnel != nil && tunnel!.name == name)
|
let isTunnelModifiedWithoutChangingName = (tunnel != nil && tunnel!.name == name)
|
||||||
guard isTunnelModifiedWithoutChangingName || tunnelsManager.tunnel(named: name) == nil else {
|
guard isTunnelModifiedWithoutChangingName || tunnelsManager.tunnel(named: name) == nil else {
|
||||||
@@ -197,7 +206,7 @@ class TunnelEditViewController: NSViewController {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let tunnelConfiguration: TunnelConfiguration
|
var tunnelConfiguration: TunnelConfiguration
|
||||||
do {
|
do {
|
||||||
tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: textView.string, called: nameRow.value)
|
tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: textView.string, called: nameRow.value)
|
||||||
} catch let error as WireGuardAppError {
|
} catch let error as WireGuardAppError {
|
||||||
@@ -207,9 +216,26 @@ class TunnelEditViewController: NSViewController {
|
|||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if excludePrivateIPsCheckbox.state == .on, tunnelConfiguration.peers.count == 1, let dnsServersAddedToAllowedIPs = dnsServersAddedToAllowedIPs {
|
||||||
|
// Update the DNS servers in the AllowedIPs
|
||||||
|
let tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnelConfiguration)
|
||||||
|
let originalAllowedIPs = tunnelViewModel.peersData[0][.allowedIPs].splitToArray(trimmingCharacters: .whitespacesAndNewlines)
|
||||||
|
let dnsServersInAllowedIPs = TunnelViewModel.PeerData.normalizedIPAddressRangeStrings(dnsServersAddedToAllowedIPs.splitToArray(trimmingCharacters: .whitespacesAndNewlines))
|
||||||
|
let dnsServersCurrent = TunnelViewModel.PeerData.normalizedIPAddressRangeStrings(tunnelViewModel.interfaceData[.dns].splitToArray(trimmingCharacters: .whitespacesAndNewlines))
|
||||||
|
let modifiedAllowedIPs = originalAllowedIPs.filter { !dnsServersInAllowedIPs.contains($0) } + dnsServersCurrent
|
||||||
|
tunnelViewModel.peersData[0][.allowedIPs] = modifiedAllowedIPs.joined(separator: ", ")
|
||||||
|
let saveResult = tunnelViewModel.save()
|
||||||
|
if case .saved(let modifiedTunnelConfiguration) = saveResult {
|
||||||
|
tunnelConfiguration = modifiedTunnelConfiguration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setUserInteractionEnabled(false)
|
||||||
|
|
||||||
if let tunnel = tunnel {
|
if let tunnel = tunnel {
|
||||||
// We're modifying an existing tunnel
|
// We're modifying an existing tunnel
|
||||||
tunnelsManager.modify(tunnel: tunnel, tunnelConfiguration: tunnelConfiguration, activateOnDemandSetting: onDemandSetting) { [weak self] error in
|
tunnelsManager.modify(tunnel: tunnel, tunnelConfiguration: tunnelConfiguration, onDemandOption: onDemandOption) { [weak self] error in
|
||||||
|
self?.setUserInteractionEnabled(true)
|
||||||
if let error = error {
|
if let error = error {
|
||||||
ErrorPresenter.showErrorAlert(error: error, from: self)
|
ErrorPresenter.showErrorAlert(error: error, from: self)
|
||||||
return
|
return
|
||||||
@@ -219,7 +245,8 @@ class TunnelEditViewController: NSViewController {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// We're creating a new tunnel
|
// We're creating a new tunnel
|
||||||
tunnelsManager.add(tunnelConfiguration: tunnelConfiguration, activateOnDemandSetting: onDemandSetting) { [weak self] result in
|
self.tunnelsManager.add(tunnelConfiguration: tunnelConfiguration, onDemandOption: onDemandOption) { [weak self] result in
|
||||||
|
self?.setUserInteractionEnabled(true)
|
||||||
if let error = result.error {
|
if let error = result.error {
|
||||||
ErrorPresenter.showErrorAlert(error: error, from: self)
|
ErrorPresenter.showErrorAlert(error: error, from: self)
|
||||||
return
|
return
|
||||||
@@ -235,4 +262,28 @@ class TunnelEditViewController: NSViewController {
|
|||||||
delegate?.tunnelEditingCancelled()
|
delegate?.tunnelEditingCancelled()
|
||||||
dismiss(self)
|
dismiss(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateExcludePrivateIPsVisibility(singlePeerAllowedIPs: [String]?) {
|
||||||
|
let shouldAllowExcludePrivateIPsControl: Bool
|
||||||
|
let excludePrivateIPsValue: Bool
|
||||||
|
if let singlePeerAllowedIPs = singlePeerAllowedIPs {
|
||||||
|
(shouldAllowExcludePrivateIPsControl, excludePrivateIPsValue) = TunnelViewModel.PeerData.excludePrivateIPsFieldStates(isSinglePeer: true, allowedIPs: Set<String>(singlePeerAllowedIPs))
|
||||||
|
} else {
|
||||||
|
(shouldAllowExcludePrivateIPsControl, excludePrivateIPsValue) = TunnelViewModel.PeerData.excludePrivateIPsFieldStates(isSinglePeer: false, allowedIPs: Set<String>())
|
||||||
|
}
|
||||||
|
excludePrivateIPsCheckbox.isHidden = !shouldAllowExcludePrivateIPsControl
|
||||||
|
excludePrivateIPsCheckbox.state = excludePrivateIPsValue ? .on : .off
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func excludePrivateIPsCheckboxToggled(sender: AnyObject?) {
|
||||||
|
guard let excludePrivateIPsCheckbox = sender as? NSButton else { return }
|
||||||
|
guard let tunnelConfiguration = try? TunnelConfiguration(fromWgQuickConfig: textView.string, called: nameRow.value) else { return }
|
||||||
|
let isOn = excludePrivateIPsCheckbox.state == .on
|
||||||
|
let tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnelConfiguration)
|
||||||
|
tunnelViewModel.peersData.first?.excludePrivateIPsValueChanged(isOn: isOn, dnsServers: tunnelViewModel.interfaceData[.dns], oldDNSServers: dnsServersAddedToAllowedIPs)
|
||||||
|
if let modifiedConfig = tunnelViewModel.asWgQuickConfig() {
|
||||||
|
textView.setConfText(modifiedConfig)
|
||||||
|
dnsServersAddedToAllowedIPs = isOn ? tunnelViewModel.interfaceData[.dns] : nil
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
protocol TunnelsListTableViewControllerDelegate: class {
|
protocol TunnelsListTableViewControllerDelegate: class {
|
||||||
func tunnelSelected(tunnel: TunnelContainer)
|
func tunnelsSelected(tunnelIndices: [Int])
|
||||||
func tunnelsListEmpty()
|
func tunnelsListEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12,41 +12,57 @@ class TunnelsListTableViewController: NSViewController {
|
|||||||
|
|
||||||
let tunnelsManager: TunnelsManager
|
let tunnelsManager: TunnelsManager
|
||||||
weak var delegate: TunnelsListTableViewControllerDelegate?
|
weak var delegate: TunnelsListTableViewControllerDelegate?
|
||||||
|
var isRemovingTunnels = false
|
||||||
|
|
||||||
let tableView: NSTableView = {
|
let tableView: NSTableView = {
|
||||||
let tableView = NSTableView()
|
let tableView = NSTableView()
|
||||||
tableView.addTableColumn(NSTableColumn(identifier: NSUserInterfaceItemIdentifier("TunnelsList")))
|
tableView.addTableColumn(NSTableColumn(identifier: NSUserInterfaceItemIdentifier("TunnelsList")))
|
||||||
tableView.headerView = nil
|
tableView.headerView = nil
|
||||||
tableView.rowSizeStyle = .medium
|
tableView.rowSizeStyle = .medium
|
||||||
|
tableView.allowsMultipleSelection = true
|
||||||
return tableView
|
return tableView
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let buttonBar: NSSegmentedControl = {
|
let addButton: NSPopUpButton = {
|
||||||
let addButtonImage = NSImage(named: NSImage.addTemplateName)!
|
let imageItem = NSMenuItem(title: "", action: nil, keyEquivalent: "")
|
||||||
let removeButtonImage = NSImage(named: NSImage.removeTemplateName)!
|
imageItem.image = NSImage(named: NSImage.addTemplateName)!
|
||||||
let actionButtonImage = NSImage(named: NSImage.actionTemplateName)!
|
|
||||||
let buttonBar = NSSegmentedControl(images: [addButtonImage, removeButtonImage, actionButtonImage],
|
let menu = NSMenu()
|
||||||
trackingMode: .momentary, target: nil, action: #selector(buttonBarClicked(sender:)))
|
menu.addItem(imageItem)
|
||||||
buttonBar.segmentStyle = .smallSquare
|
menu.addItem(withTitle: tr("macMenuAddEmptyTunnel"), action: #selector(handleAddEmptyTunnelAction), keyEquivalent: "n")
|
||||||
buttonBar.segmentDistribution = .fit
|
menu.addItem(withTitle: tr("macMenuImportTunnels"), action: #selector(handleImportTunnelAction), keyEquivalent: "o")
|
||||||
buttonBar.setShowsMenuIndicator(true, forSegment: 0)
|
menu.autoenablesItems = false
|
||||||
buttonBar.setShowsMenuIndicator(false, forSegment: 1)
|
|
||||||
buttonBar.setShowsMenuIndicator(true, forSegment: 2)
|
let button = NSPopUpButton(frame: NSRect.zero, pullsDown: true)
|
||||||
return buttonBar
|
button.menu = menu
|
||||||
|
button.bezelStyle = .smallSquare
|
||||||
|
(button.cell as? NSPopUpButtonCell)?.arrowPosition = .arrowAtBottom
|
||||||
|
return button
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let addMenu: NSMenu = {
|
let removeButton: NSButton = {
|
||||||
let addMenu = NSMenu(title: "TunnelsListAdd")
|
let image = NSImage(named: NSImage.removeTemplateName)!
|
||||||
addMenu.addItem(withTitle: tr("macMenuAddEmptyTunnel"), action: #selector(handleAddEmptyTunnelAction), keyEquivalent: "n")
|
let button = NSButton(image: image, target: self, action: #selector(handleRemoveTunnelAction))
|
||||||
addMenu.addItem(withTitle: tr("macMenuImportTunnels"), action: #selector(handleImportTunnelAction), keyEquivalent: "i")
|
button.bezelStyle = .smallSquare
|
||||||
return addMenu
|
button.imagePosition = .imageOnly
|
||||||
|
return button
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let actionMenu: NSMenu = {
|
let actionButton: NSPopUpButton = {
|
||||||
let actionMenu = NSMenu(title: "TunnelsListAction")
|
let imageItem = NSMenuItem(title: "", action: nil, keyEquivalent: "")
|
||||||
actionMenu.addItem(withTitle: tr("macMenuExportLog"), action: #selector(handleExportLogAction), keyEquivalent: "")
|
imageItem.image = NSImage(named: NSImage.actionTemplateName)!
|
||||||
actionMenu.addItem(withTitle: tr("macMenuExportTunnels"), action: #selector(handleExportTunnelsAction), keyEquivalent: "")
|
|
||||||
return actionMenu
|
let menu = NSMenu()
|
||||||
|
menu.addItem(imageItem)
|
||||||
|
menu.addItem(withTitle: tr("macMenuViewLog"), action: #selector(handleViewLogAction), keyEquivalent: "")
|
||||||
|
menu.addItem(withTitle: tr("macMenuExportTunnels"), action: #selector(handleExportTunnelsAction), keyEquivalent: "")
|
||||||
|
menu.autoenablesItems = false
|
||||||
|
|
||||||
|
let button = NSPopUpButton(frame: NSRect.zero, pullsDown: true)
|
||||||
|
button.menu = menu
|
||||||
|
button.bezelStyle = .smallSquare
|
||||||
|
(button.cell as? NSPopUpButtonCell)?.arrowPosition = .arrowAtBottom
|
||||||
|
return button
|
||||||
}()
|
}()
|
||||||
|
|
||||||
init(tunnelsManager: TunnelsManager) {
|
init(tunnelsManager: TunnelsManager) {
|
||||||
@@ -62,10 +78,13 @@ class TunnelsListTableViewController: NSViewController {
|
|||||||
tableView.dataSource = self
|
tableView.dataSource = self
|
||||||
tableView.delegate = self
|
tableView.delegate = self
|
||||||
|
|
||||||
|
tableView.doubleAction = #selector(listDoubleClicked(sender:))
|
||||||
|
|
||||||
let isSelected = selectTunnelInOperation() || selectTunnel(at: 0)
|
let isSelected = selectTunnelInOperation() || selectTunnel(at: 0)
|
||||||
if !isSelected {
|
if !isSelected {
|
||||||
delegate?.tunnelsListEmpty()
|
delegate?.tunnelsListEmpty()
|
||||||
}
|
}
|
||||||
|
tableView.allowsEmptySelection = false
|
||||||
|
|
||||||
let scrollView = NSScrollView()
|
let scrollView = NSScrollView()
|
||||||
scrollView.hasVerticalScroller = true
|
scrollView.hasVerticalScroller = true
|
||||||
@@ -76,6 +95,16 @@ class TunnelsListTableViewController: NSViewController {
|
|||||||
clipView.documentView = tableView
|
clipView.documentView = tableView
|
||||||
scrollView.contentView = clipView
|
scrollView.contentView = clipView
|
||||||
|
|
||||||
|
let buttonBar = NSStackView(views: [addButton, removeButton, actionButton])
|
||||||
|
buttonBar.orientation = .horizontal
|
||||||
|
buttonBar.spacing = -1
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
removeButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 26),
|
||||||
|
removeButton.topAnchor.constraint(equalTo: buttonBar.topAnchor),
|
||||||
|
removeButton.bottomAnchor.constraint(equalTo: buttonBar.bottomAnchor)
|
||||||
|
])
|
||||||
|
|
||||||
let fillerButton = FillerButton()
|
let fillerButton = FillerButton()
|
||||||
|
|
||||||
let containerView = NSView()
|
let containerView = NSView()
|
||||||
@@ -100,13 +129,12 @@ class TunnelsListTableViewController: NSViewController {
|
|||||||
])
|
])
|
||||||
|
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
containerView.widthAnchor.constraint(greaterThanOrEqualToConstant: 120),
|
containerView.widthAnchor.constraint(equalToConstant: 180),
|
||||||
containerView.heightAnchor.constraint(greaterThanOrEqualToConstant: 120)
|
containerView.heightAnchor.constraint(greaterThanOrEqualToConstant: 120)
|
||||||
])
|
])
|
||||||
|
|
||||||
buttonBar.target = self
|
addButton.menu?.items.forEach { $0.target = self }
|
||||||
addMenu.items.forEach { $0.target = self }
|
actionButton.menu?.items.forEach { $0.target = self }
|
||||||
actionMenu.items.forEach { $0.target = self }
|
|
||||||
|
|
||||||
view = containerView
|
view = containerView
|
||||||
}
|
}
|
||||||
@@ -123,22 +151,6 @@ class TunnelsListTableViewController: NSViewController {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func buttonBarClicked(sender: AnyObject?) {
|
|
||||||
guard let buttonBar = sender as? NSSegmentedControl else { return }
|
|
||||||
// We have to resort to explicitly showing the menu instead of using NSSegmentedControl.setMenu()
|
|
||||||
// because we have a mix of menu and non-menu segments.
|
|
||||||
// See: http://openradar.appspot.com/radar?id=61419
|
|
||||||
if buttonBar.selectedSegment == 0 {
|
|
||||||
let segmentBottomLeft = NSPoint(x: 0, y: buttonBar.bounds.height + 2)
|
|
||||||
addMenu.popUp(positioning: nil, at: segmentBottomLeft, in: buttonBar)
|
|
||||||
} else if buttonBar.selectedSegment == 1 {
|
|
||||||
handleRemoveTunnelAction()
|
|
||||||
} else if buttonBar.selectedSegment == 2 {
|
|
||||||
let segmentBottomLeft = NSPoint(x: buttonBar.bounds.width * 0.66, y: buttonBar.bounds.height + 2)
|
|
||||||
actionMenu.popUp(positioning: nil, at: segmentBottomLeft, in: buttonBar)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func handleAddEmptyTunnelAction() {
|
@objc func handleAddEmptyTunnelAction() {
|
||||||
let tunnelEditVC = TunnelEditViewController(tunnelsManager: tunnelsManager, tunnel: nil)
|
let tunnelEditVC = TunnelEditViewController(tunnelsManager: tunnelsManager, tunnel: nil)
|
||||||
tunnelEditVC.delegate = self
|
tunnelEditVC.delegate = self
|
||||||
@@ -151,59 +163,41 @@ class TunnelsListTableViewController: NSViewController {
|
|||||||
|
|
||||||
@objc func handleRemoveTunnelAction() {
|
@objc func handleRemoveTunnelAction() {
|
||||||
guard let window = view.window else { return }
|
guard let window = view.window else { return }
|
||||||
let selectedTunnelIndex = tableView.selectedRow
|
|
||||||
guard selectedTunnelIndex >= 0 && selectedTunnelIndex < tunnelsManager.numberOfTunnels() else { return }
|
let selectedTunnelIndices = tableView.selectedRowIndexes.sorted().filter { $0 >= 0 && $0 < tunnelsManager.numberOfTunnels() }
|
||||||
let selectedTunnel = tunnelsManager.tunnel(at: selectedTunnelIndex)
|
guard !selectedTunnelIndices.isEmpty else { return }
|
||||||
let alert = NSAlert()
|
var nextSelection = selectedTunnelIndices.last! + 1
|
||||||
alert.messageText = tr(format: "macDeleteTunnelConfirmationAlertMessage (%@)", selectedTunnel.name)
|
if nextSelection >= tunnelsManager.numberOfTunnels() {
|
||||||
|
nextSelection = max(selectedTunnelIndices.first! - 1, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
let alert = DeleteTunnelsConfirmationAlert()
|
||||||
|
if selectedTunnelIndices.count == 1 {
|
||||||
|
let firstSelectedTunnel = tunnelsManager.tunnel(at: selectedTunnelIndices.first!)
|
||||||
|
alert.messageText = tr(format: "macDeleteTunnelConfirmationAlertMessage (%@)", firstSelectedTunnel.name)
|
||||||
|
} else {
|
||||||
|
alert.messageText = tr(format: "macDeleteMultipleTunnelsConfirmationAlertMessage (%d)", selectedTunnelIndices.count)
|
||||||
|
}
|
||||||
alert.informativeText = tr("macDeleteTunnelConfirmationAlertInfo")
|
alert.informativeText = tr("macDeleteTunnelConfirmationAlertInfo")
|
||||||
alert.addButton(withTitle: tr("macDeleteTunnelConfirmationAlertButtonTitleDelete"))
|
alert.onDeleteClicked = { [weak self] completion in
|
||||||
alert.addButton(withTitle: tr("macDeleteTunnelConfirmationAlertButtonTitleCancel"))
|
guard let self = self else { return }
|
||||||
alert.beginSheetModal(for: window) { [weak self] response in
|
self.selectTunnel(at: nextSelection)
|
||||||
guard response == .alertFirstButtonReturn else { return }
|
let selectedTunnels = selectedTunnelIndices.map { self.tunnelsManager.tunnel(at: $0) }
|
||||||
self?.buttonBar.setEnabled(false, forSegment: 1)
|
self.tunnelsManager.removeMultiple(tunnels: selectedTunnels) { [weak self] error in
|
||||||
self?.tunnelsManager.remove(tunnel: selectedTunnel) { [weak self] error in
|
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
defer { self.buttonBar.setEnabled(true, forSegment: 1) }
|
defer { completion() }
|
||||||
if let error = error {
|
if let error = error {
|
||||||
ErrorPresenter.showErrorAlert(error: error, from: self)
|
ErrorPresenter.showErrorAlert(error: error, from: self)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
alert.beginSheetModal(for: window)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func handleExportLogAction() {
|
@objc func handleViewLogAction() {
|
||||||
guard let window = view.window else { return }
|
let logVC = LogViewController()
|
||||||
let savePanel = NSSavePanel()
|
self.presentAsSheet(logVC)
|
||||||
savePanel.prompt = tr("macSheetButtonExportLog")
|
|
||||||
savePanel.nameFieldLabel = tr("macNameFieldExportLog")
|
|
||||||
|
|
||||||
let dateFormatter = ISO8601DateFormatter()
|
|
||||||
dateFormatter.formatOptions = [.withFullDate, .withTime, .withTimeZone] // Avoid ':' in the filename
|
|
||||||
let timeStampString = dateFormatter.string(from: Date())
|
|
||||||
savePanel.nameFieldStringValue = "wireguard-log-\(timeStampString).txt"
|
|
||||||
|
|
||||||
guard let networkExtensionLogFilePath = FileManager.networkExtensionLogFileURL?.path else {
|
|
||||||
ErrorPresenter.showErrorAlert(title: tr("alertUnableToFindExtensionLogPathTitle"), message: tr("alertUnableToFindExtensionLogPathMessage"), from: self)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
savePanel.beginSheetModal(for: window) { response in
|
|
||||||
guard response == .OK else { return }
|
|
||||||
guard let destinationURL = savePanel.url else { return }
|
|
||||||
|
|
||||||
DispatchQueue.global(qos: .userInitiated).async {
|
|
||||||
let isWritten = Logger.global?.writeLog(called: "APP", mergedWith: networkExtensionLogFilePath, called: "NET", to: destinationURL.path) ?? false
|
|
||||||
guard isWritten else {
|
|
||||||
DispatchQueue.main.async { [weak self] in
|
|
||||||
ErrorPresenter.showErrorAlert(title: tr("alertUnableToWriteLogTitle"), message: tr("alertUnableToWriteLogMessage"), from: self)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func handleExportTunnelsAction() {
|
@objc func handleExportTunnelsAction() {
|
||||||
@@ -232,9 +226,21 @@ class TunnelsListTableViewController: NSViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func listDoubleClicked(sender: AnyObject) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
private func selectTunnel(at index: Int) -> Bool {
|
private func selectTunnel(at index: Int) -> Bool {
|
||||||
if index < tunnelsManager.numberOfTunnels() {
|
if index < tunnelsManager.numberOfTunnels() {
|
||||||
|
tableView.scrollRowToVisible(index)
|
||||||
tableView.selectRowIndexes(IndexSet(integer: index), byExtendingSelection: false)
|
tableView.selectRowIndexes(IndexSet(integer: index), byExtendingSelection: false)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -275,15 +281,10 @@ extension TunnelsListTableViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func tunnelRemoved(at index: Int) {
|
func tunnelRemoved(at index: Int) {
|
||||||
let selectedTunnelIndex = tableView.selectedRow
|
|
||||||
tableView.removeRows(at: IndexSet(integer: index), withAnimation: .slideLeft)
|
tableView.removeRows(at: IndexSet(integer: index), withAnimation: .slideLeft)
|
||||||
if tunnelsManager.numberOfTunnels() == 0 {
|
if tunnelsManager.numberOfTunnels() == 0 {
|
||||||
delegate?.tunnelsListEmpty()
|
delegate?.tunnelsListEmpty()
|
||||||
}
|
}
|
||||||
let tunnelIndex = min(selectedTunnelIndex, self.tunnelsManager.numberOfTunnels() - 1)
|
|
||||||
if tunnelIndex >= 0 {
|
|
||||||
self.selectTunnel(at: tunnelIndex)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,9 +302,10 @@ extension TunnelsListTableViewController: NSTableViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func tableViewSelectionDidChange(_ notification: Notification) {
|
func tableViewSelectionDidChange(_ notification: Notification) {
|
||||||
guard tableView.selectedRow >= 0 else { return }
|
let selectedTunnelIndices = tableView.selectedRowIndexes.sorted()
|
||||||
let selectedTunnel = tunnelsManager.tunnel(at: tableView.selectedRow)
|
if !selectedTunnelIndices.isEmpty {
|
||||||
delegate?.tunnelSelected(tunnel: selectedTunnel)
|
delegate?.tunnelsSelected(tunnelIndices: tableView.selectedRowIndexes.sorted())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
class UnusableTunnelDetailViewController: NSViewController {
|
||||||
|
|
||||||
|
var onButtonClicked: (() -> Void)?
|
||||||
|
|
||||||
|
let messageLabel: NSTextField = {
|
||||||
|
let text = tr("macUnusableTunnelMessage")
|
||||||
|
let boldFont = NSFont.boldSystemFont(ofSize: NSFont.systemFontSize)
|
||||||
|
let boldText = NSAttributedString(string: text, attributes: [.font: boldFont])
|
||||||
|
let label = NSTextField(labelWithAttributedString: boldText)
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
let infoLabel: NSTextField = {
|
||||||
|
let label = NSTextField(wrappingLabelWithString: tr("macUnusableTunnelInfo"))
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
let button: NSButton = {
|
||||||
|
let button = NSButton()
|
||||||
|
button.title = tr("macUnusableTunnelButtonTitleDeleteTunnel")
|
||||||
|
button.setButtonType(.momentaryPushIn)
|
||||||
|
button.bezelStyle = .rounded
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
|
init() {
|
||||||
|
super.init(nibName: nil, bundle: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func loadView() {
|
||||||
|
|
||||||
|
button.target = self
|
||||||
|
button.action = #selector(buttonClicked)
|
||||||
|
|
||||||
|
let margin: CGFloat = 20
|
||||||
|
let internalSpacing: CGFloat = 20
|
||||||
|
let buttonSpacing: CGFloat = 30
|
||||||
|
let stackView = NSStackView(views: [messageLabel, infoLabel, button])
|
||||||
|
stackView.orientation = .vertical
|
||||||
|
stackView.edgeInsets = NSEdgeInsets(top: margin, left: margin, bottom: margin, right: margin)
|
||||||
|
stackView.spacing = internalSpacing
|
||||||
|
stackView.setCustomSpacing(buttonSpacing, after: infoLabel)
|
||||||
|
|
||||||
|
let view = NSView()
|
||||||
|
view.addSubview(stackView)
|
||||||
|
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
stackView.widthAnchor.constraint(equalToConstant: 360),
|
||||||
|
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
||||||
|
stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
|
||||||
|
view.widthAnchor.constraint(greaterThanOrEqualToConstant: 420),
|
||||||
|
view.heightAnchor.constraint(greaterThanOrEqualToConstant: 240)
|
||||||
|
])
|
||||||
|
|
||||||
|
self.view = view
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func buttonClicked() {
|
||||||
|
onButtonClicked?()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,7 +31,7 @@ class ZipArchive {
|
|||||||
let fileName = input.fileName
|
let fileName = input.fileName
|
||||||
let contents = input.contents
|
let contents = input.contents
|
||||||
zipOpenNewFileInZip(zipFile, fileName.cString(using: .utf8), nil, nil, 0, nil, 0, nil, Z_DEFLATED, Z_DEFAULT_COMPRESSION)
|
zipOpenNewFileInZip(zipFile, fileName.cString(using: .utf8), nil, nil, 0, nil, 0, nil, Z_DEFLATED, Z_DEFAULT_COMPRESSION)
|
||||||
contents.withUnsafeBytes { (ptr: UnsafePointer<UInt8>) -> Void in
|
contents.withUnsafeUInt8Bytes { ptr -> Void in
|
||||||
zipWriteInFileInZip(zipFile, UnsafeRawPointer(ptr), UInt32(contents.count))
|
zipWriteInFileInZip(zipFile, UnsafeRawPointer(ptr), UInt32(contents.count))
|
||||||
}
|
}
|
||||||
zipCloseFileInZip(zipFile)
|
zipCloseFileInZip(zipFile)
|
||||||
@@ -42,6 +42,7 @@ class ZipArchive {
|
|||||||
static func unarchive(url: URL, requiredFileExtensions: [String]) throws -> [(fileBaseName: String, contents: Data)] {
|
static func unarchive(url: URL, requiredFileExtensions: [String]) throws -> [(fileBaseName: String, contents: Data)] {
|
||||||
|
|
||||||
var results = [(fileBaseName: String, contents: Data)]()
|
var results = [(fileBaseName: String, contents: Data)]()
|
||||||
|
var requiredFileExtensionsLowercased = requiredFileExtensions.map { $0.lowercased() }
|
||||||
|
|
||||||
guard let zipFile = unzOpen64(url.path) else {
|
guard let zipFile = unzOpen64(url.path) else {
|
||||||
throw ZipArchiveError.cantOpenInputZipFile
|
throw ZipArchiveError.cantOpenInputZipFile
|
||||||
@@ -70,7 +71,7 @@ class ZipArchive {
|
|||||||
let isDirectory = (lastChar == "/" || lastChar == "\\")
|
let isDirectory = (lastChar == "/" || lastChar == "\\")
|
||||||
let fileURL = URL(fileURLWithFileSystemRepresentation: fileNameBuffer, isDirectory: isDirectory, relativeTo: nil)
|
let fileURL = URL(fileURLWithFileSystemRepresentation: fileNameBuffer, isDirectory: isDirectory, relativeTo: nil)
|
||||||
|
|
||||||
if !isDirectory && requiredFileExtensions.contains(fileURL.pathExtension) {
|
if !isDirectory && requiredFileExtensionsLowercased.contains(fileURL.pathExtension.lowercased()) {
|
||||||
var unzippedData = Data()
|
var unzippedData = Data()
|
||||||
var bytesRead: Int32 = 0
|
var bytesRead: Int32 = 0
|
||||||
repeat {
|
repeat {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class ZipImporter {
|
|||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
|
|
||||||
unarchivedFiles.sort { $0.fileBaseName < $1.fileBaseName }
|
unarchivedFiles.sort { TunnelsManager.tunnelNameIsLessThan($0.fileBaseName, $1.fileBaseName) }
|
||||||
var configs: [TunnelConfiguration?] = Array(repeating: nil, count: unarchivedFiles.count)
|
var configs: [TunnelConfiguration?] = Array(repeating: nil, count: unarchivedFiles.count)
|
||||||
for (index, file) in unarchivedFiles.enumerated() {
|
for (index, file) in unarchivedFiles.enumerated() {
|
||||||
if index > 0 && file == unarchivedFiles[index - 1] {
|
if index > 0 && file == unarchivedFiles[index - 1] {
|
||||||
|
|||||||
@@ -117,6 +117,8 @@ extension Endpoint {
|
|||||||
hostname = "\(address)"
|
hostname = "\(address)"
|
||||||
case .ipv6(let address):
|
case .ipv6(let address):
|
||||||
hostname = "\(address)"
|
hostname = "\(address)"
|
||||||
|
@unknown default:
|
||||||
|
fatalError()
|
||||||
}
|
}
|
||||||
|
|
||||||
var resultPointer = UnsafeMutablePointer<addrinfo>(OpaquePointer(bitPattern: 0))
|
var resultPointer = UnsafeMutablePointer<addrinfo>(OpaquePointer(bitPattern: 0))
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func configureLogger() {
|
private func configureLogger() {
|
||||||
Logger.configureGlobal(withFilePath: FileManager.networkExtensionLogFileURL?.path)
|
Logger.configureGlobal(tagged: "NET", withFilePath: FileManager.logFileURL?.path)
|
||||||
wgSetLogger { level, msgC in
|
wgSetLogger { level, msgC in
|
||||||
guard let msgC = msgC else { return }
|
guard let msgC = msgC else { return }
|
||||||
let logType: OSLogType
|
let logType: OSLogType
|
||||||
|
|||||||
-1
Submodule wireguard-go deleted from c4b43e35a7
@@ -26,61 +26,48 @@ export CGO_ENABLED := 1
|
|||||||
build: $(DESTDIR)/libwg-go.a
|
build: $(DESTDIR)/libwg-go.a
|
||||||
version-header: $(DESTDIR)/wireguard-go-version.h
|
version-header: $(DESTDIR)/wireguard-go-version.h
|
||||||
|
|
||||||
GOBUILDARCH := $(GOARCH_$(shell uname -m))
|
GOBUILDVERSION_NEEDED := go version go1.12.1 darwin/amd64
|
||||||
GOBUILDOS := $(shell uname -s | tr '[:upper:]' '[:lower:]')
|
GOBUILDVERSION_CURRENT := $(shell go version 2>/dev/null)
|
||||||
GOBUILDVERSION := 1.11.5
|
export REAL_GOROOT := $(shell go env GOROOT 2>/dev/null)
|
||||||
GOBUILDTARBALL := go$(GOBUILDVERSION).$(GOBUILDOS)-$(GOBUILDARCH).tar.gz
|
|
||||||
GOBUILDTARBALLURL := https://dl.google.com/go/$(GOBUILDTARBALL)
|
|
||||||
GOBUILDVERSION_NEEDED := go version go$(GOBUILDVERSION) $(GOBUILDOS)/$(GOBUILDARCH)
|
|
||||||
export GOROOT := $(BUILDDIR)/goroot
|
export GOROOT := $(BUILDDIR)/goroot
|
||||||
export GOPATH := $(BUILDDIR)/gopath
|
export GOPATH := $(BUILDDIR)/gopath
|
||||||
export PATH := $(GOROOT)/bin:$(PATH)
|
export PATH := $(GOPATH)/bin:$(PATH)
|
||||||
GOBUILDVERSION_CURRENT := $(shell $(GOROOT)/bin/go version 2>/dev/null)
|
GOBUILDVERSION_FAKE := $(shell $(GOROOT)/bin/go version 2>/dev/null)
|
||||||
ifneq ($(GOBUILDVERSION_NEEDED),$(GOBUILDVERSION_CURRENT))
|
ifneq ($(GOBUILDVERSION_NEEDED),$(GOBUILDVERSION_CURRENT))
|
||||||
$(shell rm -f $(GOROOT)/bin/go)
|
$(error This requires $(GOBUILDVERSION_NEEDED))
|
||||||
|
endif
|
||||||
|
ifneq ($(GOBUILDVERSION_NEEDED),$(GOBUILDVERSION_FAKE))
|
||||||
|
$(shell rm -f $(GOROOT)/.prepared)
|
||||||
endif
|
endif
|
||||||
.cache/$(GOBUILDTARBALL):
|
|
||||||
mkdir -p $(dir $@)
|
|
||||||
curl -o $@ $(GOBUILDTARBALLURL) || { rm -f $@; exit 1; }
|
|
||||||
|
|
||||||
$(GOROOT)/bin/go: .cache/$(GOBUILDTARBALL)
|
$(GOROOT)/.prepared:
|
||||||
rm -rf "$(GOROOT)"
|
[ -n "$(REAL_GOROOT)" ]
|
||||||
mkdir -p "$(GOROOT)"
|
mkdir -p "$(GOROOT)"
|
||||||
tar -C "$(GOROOT)" --strip-components=1 -xzf - < .cache/$(GOBUILDTARBALL) || { rm -rf "$(GOROOT)"; exit 1; }
|
rsync -a --delete --exclude=pkg/obj/go-build "$(REAL_GOROOT)/" "$(GOROOT)/"
|
||||||
patch -p1 -f -N -r- -d "$(GOROOT)" < goruntime-boottime-over-monotonic.diff || { rm -rf "$(GOROOT)"; exit 1; }
|
cat goruntime-*.diff | patch -p1 -f -N -r- -d "$(GOROOT)"
|
||||||
|
rm -rf "$(GOPATH)/pkg/mod"
|
||||||
|
go get -d -tags ios; chmod -fR +w "$(GOPATH)/pkg/mod"
|
||||||
|
for sys in "$(GOPATH)/pkg/mod/golang.org/x/sys@"*; do cat sys-unix-*.diff | patch -p1 -f -N -r- -d "$$sys"; done
|
||||||
|
touch "$@"
|
||||||
|
|
||||||
$(shell test "$$(cat "$(BUILDDIR)/.gobuildversion" 2>/dev/null)" = "$(GOBUILDVERSION_CURRENT)" || rm -f "$(DESTDIR)/libwg-go.a")
|
$(shell test "$$(cat "$(BUILDDIR)/.gobuildversion" 2>/dev/null)" = "$(GOBUILDVERSION_CURRENT)" || rm -f "$(DESTDIR)/libwg-go.a")
|
||||||
|
|
||||||
define copy-src-to-build
|
|
||||||
$(subst $(1),$(BUILDDIR)/,$(2)): $(2)
|
|
||||||
@mkdir -vp "$$(dir $$@)"
|
|
||||||
@cp -vp "$$<" "$$@"
|
|
||||||
$(BUILDDIR)/.prepared: $(subst $(1),$(BUILDDIR)/,$(2))
|
|
||||||
endef
|
|
||||||
|
|
||||||
$(foreach FILE,$(UPSTREAM_FILES),$(eval $(call copy-src-to-build,../wireguard-go/,$(FILE))))
|
|
||||||
$(foreach FILE,$(DOWNSTREAM_FILES),$(eval $(call copy-src-to-build,src/,$(FILE))))
|
|
||||||
|
|
||||||
$(BUILDDIR)/.prepared: $(GOROOT)/bin/go
|
|
||||||
cd "$(BUILDDIR)" || exit $$?; $(foreach ARCH,$(ARCHS),CGO_CFLAGS="$(CFLAGS_PREFIX) $(ARCH)" CGO_LDFLAGS="$(CFLAGS_PREFIX) $(ARCH)" GOARCH="$(GOARCH_$(ARCH))" go get -tags ios || { ret=$$?; chmod -fR +w "$(GOPATH)/pkg/mod"; rm -rf "$(GOPATH)/pkg/mod"; exit $$ret; };)
|
|
||||||
chmod -fR +w "$(GOPATH)/pkg/mod"
|
|
||||||
touch "$@"
|
|
||||||
|
|
||||||
define libwg-go-a
|
define libwg-go-a
|
||||||
$(BUILDDIR)/libwg-go-$(1).a: $(BUILDDIR)/.prepared
|
$(BUILDDIR)/libwg-go-$(1).a: $(GOROOT)/.prepared
|
||||||
cd "$(BUILDDIR)" || exit $$$$?; \
|
|
||||||
CGO_CFLAGS="$(CFLAGS_PREFIX) $(ARCH)" \
|
CGO_CFLAGS="$(CFLAGS_PREFIX) $(ARCH)" \
|
||||||
CGO_LDFLAGS="$(CFLAGS_PREFIX) $(ARCH)" \
|
CGO_LDFLAGS="$(CFLAGS_PREFIX) $(ARCH)" \
|
||||||
GOARCH="$(GOARCH_$(1))" \
|
GOARCH="$(GOARCH_$(1))" \
|
||||||
go build -tags ios -ldflags=-w -v -o "$(BUILDDIR)/libwg-go-$(1).a" -buildmode c-archive && go version > "$(BUILDDIR)/.gobuildversion"; \
|
go build -tags ios -ldflags=-w -v -o "$(BUILDDIR)/libwg-go-$(1).a" -buildmode c-archive && go version > "$(BUILDDIR)/.gobuildversion"; \
|
||||||
|
chmod -fR +w "$(GOPATH)/pkg/mod"; \
|
||||||
ret=$$$$?; \
|
ret=$$$$?; \
|
||||||
rm -f "$(BUILDDIR)/libwg-go-$(1).h"; \
|
rm -f "$(BUILDDIR)/libwg-go-$(1).h"; \
|
||||||
exit $$$$ret
|
exit $$$$ret
|
||||||
endef
|
endef
|
||||||
$(foreach ARCH,$(ARCHS),$(eval $(call libwg-go-a,$(ARCH))))
|
$(foreach ARCH,$(ARCHS),$(eval $(call libwg-go-a,$(ARCH))))
|
||||||
|
|
||||||
$(DESTDIR)/wireguard-go-version.h: ../wireguard-go/version.go
|
$(DESTDIR)/wireguard-go-version.h: go.mod $(GOROOT)/.prepared
|
||||||
sed -n 's/.*WireGuardGoVersion = "\(.*\)"/#define WIREGUARD_GO_VERSION "\1"/p' "$^" > "$@"
|
wggo="$(GOPATH)/pkg/mod/$$(sed -n 's/.*\(golang\.zx2c4\.com\/wireguard\) \(.*\)$$/\1@\2/p' go.mod)"; \
|
||||||
|
sed -n 's/.*WireGuardGoVersion = "\(.*\)"/#define WIREGUARD_GO_VERSION "\1"/p' "$$wggo/device/version.go" > "$@"
|
||||||
|
|
||||||
$(DESTDIR)/libwg-go.a: $(foreach ARCH,$(ARCHS),$(BUILDDIR)/libwg-go-$(ARCH).a)
|
$(DESTDIR)/libwg-go.a: $(foreach ARCH,$(ARCHS),$(BUILDDIR)/libwg-go-$(ARCH).a)
|
||||||
@mkdir -vp "$(DESTDIR)"
|
@mkdir -vp "$(DESTDIR)"
|
||||||
@@ -89,9 +76,6 @@ $(DESTDIR)/libwg-go.a: $(foreach ARCH,$(ARCHS),$(BUILDDIR)/libwg-go-$(ARCH).a)
|
|||||||
clean:
|
clean:
|
||||||
rm -rf "$(BUILDDIR)" "$(DESTDIR)/libwg-go.a" "$(DESTDIR)/wireguard-go-version.h"
|
rm -rf "$(BUILDDIR)" "$(DESTDIR)/libwg-go.a" "$(DESTDIR)/wireguard-go-version.h"
|
||||||
|
|
||||||
distclean: clean
|
|
||||||
rm -rf .cache
|
|
||||||
|
|
||||||
install: build
|
install: build
|
||||||
|
|
||||||
.PHONY: distclean clean build version-header install
|
.PHONY: clean build version-header install
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* SPDX-License-Identifier: GPL-2.0
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2018-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
* Copyright (C) 2018-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
@@ -17,8 +17,9 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"git.zx2c4.com/wireguard-go/tun"
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
|
"golang.zx2c4.com/wireguard/device"
|
||||||
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
@@ -45,12 +46,16 @@ func (l *CLogger) Write(p []byte) (int, error) {
|
|||||||
return len(p), nil
|
return len(p), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var tunnelHandles map[int32]*Device
|
type tunnelHandle struct {
|
||||||
|
*device.Device
|
||||||
|
*device.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
var tunnelHandles = make(map[int32]tunnelHandle)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
versionString = C.CString(WireGuardGoVersion)
|
versionString = C.CString(device.WireGuardGoVersion)
|
||||||
roamingDisabled = true
|
device.RoamingDisabled = true
|
||||||
tunnelHandles = make(map[int32]*Device)
|
|
||||||
signals := make(chan os.Signal)
|
signals := make(chan os.Signal)
|
||||||
signal.Notify(signals, unix.SIGUSR2)
|
signal.Notify(signals, unix.SIGUSR2)
|
||||||
go func() {
|
go func() {
|
||||||
@@ -61,7 +66,7 @@ func init() {
|
|||||||
n := runtime.Stack(buf, true)
|
n := runtime.Stack(buf, true)
|
||||||
buf[n] = 0
|
buf[n] = 0
|
||||||
if uintptr(loggerFunc) != 0 {
|
if uintptr(loggerFunc) != 0 {
|
||||||
C.callLogger(loggerFunc, 0, (*_Ctype_char)(unsafe.Pointer(&buf[0])))
|
C.callLogger(loggerFunc, 0, (*C.char)(unsafe.Pointer(&buf[0])))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,7 +75,7 @@ func init() {
|
|||||||
|
|
||||||
//export wgEnableRoaming
|
//export wgEnableRoaming
|
||||||
func wgEnableRoaming(enabled bool) {
|
func wgEnableRoaming(enabled bool) {
|
||||||
roamingDisabled = !enabled
|
device.RoamingDisabled = !enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
//export wgSetLogger
|
//export wgSetLogger
|
||||||
@@ -80,21 +85,26 @@ func wgSetLogger(loggerFn uintptr) {
|
|||||||
|
|
||||||
//export wgTurnOn
|
//export wgTurnOn
|
||||||
func wgTurnOn(settings string, tunFd int32) int32 {
|
func wgTurnOn(settings string, tunFd int32) int32 {
|
||||||
logger := &Logger{
|
logger := &device.Logger{
|
||||||
Debug: log.New(&CLogger{level: 0}, "", 0),
|
Debug: log.New(&CLogger{level: 0}, "", 0),
|
||||||
Info: log.New(&CLogger{level: 1}, "", 0),
|
Info: log.New(&CLogger{level: 1}, "", 0),
|
||||||
Error: log.New(&CLogger{level: 2}, "", 0),
|
Error: log.New(&CLogger{level: 2}, "", 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
tun, _, err := tun.CreateTUNFromFD(int(tunFd))
|
err := unix.SetNonblock(int(tunFd), true)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error.Println(err)
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
tun, err := tun.CreateTUNFromFile(os.NewFile(uintptr(tunFd), "/dev/tun"), 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error.Println(err)
|
logger.Error.Println(err)
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
logger.Info.Println("Attaching to interface")
|
logger.Info.Println("Attaching to interface")
|
||||||
device := NewDevice(tun, logger)
|
device := device.NewDevice(tun, logger)
|
||||||
|
|
||||||
setError := ipcSetOperation(device, bufio.NewReader(strings.NewReader(settings)))
|
setError := device.IpcSetOperation(bufio.NewReader(strings.NewReader(settings)))
|
||||||
if setError != nil {
|
if setError != nil {
|
||||||
logger.Error.Println(setError)
|
logger.Error.Println(setError)
|
||||||
return -1
|
return -1
|
||||||
@@ -112,7 +122,7 @@ func wgTurnOn(settings string, tunFd int32) int32 {
|
|||||||
if i == math.MaxInt32 {
|
if i == math.MaxInt32 {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
tunnelHandles[i] = device
|
tunnelHandles[i] = tunnelHandle{device, logger}
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,10 +142,10 @@ func wgSetConfig(tunnelHandle int32, settings string) int64 {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
err := ipcSetOperation(device, bufio.NewReader(strings.NewReader(settings)))
|
err := device.IpcSetOperation(bufio.NewReader(strings.NewReader(settings)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
device.log.Error.Println(err)
|
device.Error.Println(err)
|
||||||
return err.int64
|
return err.ErrorCode()
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -148,7 +158,7 @@ func wgGetConfig(tunnelHandle int32) *C.char {
|
|||||||
}
|
}
|
||||||
settings := new(bytes.Buffer)
|
settings := new(bytes.Buffer)
|
||||||
writer := bufio.NewWriter(settings)
|
writer := bufio.NewWriter(settings)
|
||||||
err := ipcGetOperation(device, writer)
|
err := device.IpcGetOperation(writer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -158,46 +168,18 @@ func wgGetConfig(tunnelHandle int32) *C.char {
|
|||||||
|
|
||||||
//export wgBindInterfaceScope
|
//export wgBindInterfaceScope
|
||||||
func wgBindInterfaceScope(tunnelHandle int32, ifscope int32) {
|
func wgBindInterfaceScope(tunnelHandle int32, ifscope int32) {
|
||||||
var operr error
|
|
||||||
device, ok := tunnelHandles[tunnelHandle]
|
device, ok := tunnelHandles[tunnelHandle]
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
device.log.Info.Printf("Binding sockets to interface %d\n", ifscope)
|
device.Info.Printf("Binding sockets to interface %d\n", ifscope)
|
||||||
bind := device.net.bind.(*NativeBind)
|
err := device.BindSocketToInterface4(uint32(ifscope))
|
||||||
for bind.ipv4 != nil {
|
if err != nil {
|
||||||
fd, err := bind.ipv4.SyscallConn()
|
device.Error.Printf("Unable to bind v4 socket to interface:", err)
|
||||||
if err != nil {
|
|
||||||
device.log.Error.Printf("Unable to bind v4 socket to interface:", err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
err = fd.Control(func(fd uintptr) {
|
|
||||||
operr = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, int(ifscope))
|
|
||||||
})
|
|
||||||
if err == nil {
|
|
||||||
err = operr
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
device.log.Error.Printf("Unable to bind v4 socket to interface:", err)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
for bind.ipv6 != nil {
|
err = device.BindSocketToInterface6(uint32(ifscope))
|
||||||
fd, err := bind.ipv6.SyscallConn()
|
if err != nil {
|
||||||
if err != nil {
|
device.Error.Printf("Unable to bind v6 socket to interface:", err)
|
||||||
device.log.Error.Printf("Unable to bind v6 socket to interface:", err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
err = fd.Control(func(fd uintptr) {
|
|
||||||
operr = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, int(ifscope))
|
|
||||||
})
|
|
||||||
if err == nil {
|
|
||||||
err = operr
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
device.log.Error.Printf("Unable to bind v6 socket to interface:", err)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
module golang.zx2c4.com/wireguard/ios
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
||||||
|
require (
|
||||||
|
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20190409083948-18fa27047265
|
||||||
|
)
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576 h1:aUX/1G2gFSs4AsJJg2cL3HuoRhCSCz733FE5GUSuaT4=
|
||||||
|
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53 h1:kcXqo9vE6fsZY5X5Rd7R1l7fTgnWaDCVmln65REefiE=
|
||||||
|
golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67 h1:1Fzlr8kkDLQwqMP8GxrhptBLqZG/EDpiATneiZHY998=
|
||||||
|
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20190409083948-18fa27047265 h1:ujM5BaP4MD/2MJZ1n7pmw6IIsyyS7SyPl0fDefnx/2o=
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20190409083948-18fa27047265/go.mod h1:u0Cl3X+pyWdXaax3S583DQrnGDuTASO0QdlKFrs8r/8=
|
||||||
@@ -1,55 +1,27 @@
|
|||||||
diff -r -u go/src/runtime/sys_darwin_386.s go-modified/src/runtime/sys_darwin_386.s
|
From 74523c5a12d37fa792e77a252bcc569484c3d41a Mon Sep 17 00:00:00 2001
|
||||||
--- go/src/runtime/sys_darwin_386.s 2018-10-01 23:02:54.000000000 +0200
|
From: "Jason A. Donenfeld" <Jason@zx2c4.com>
|
||||||
+++ go-modified/src/runtime/sys_darwin_386.s 2018-11-01 23:18:04.383055355 +0100
|
Date: Wed, 27 Feb 2019 05:33:01 +0100
|
||||||
@@ -184,7 +184,7 @@
|
Subject: [PATCH] runtime: use libc_mach_continuous_time in nanotime on Darwin
|
||||||
PUSHL BP
|
|
||||||
MOVL SP, BP
|
This makes timers account for having expired while a computer was
|
||||||
SUBL $8+(machTimebaseInfo__size+15)/16*16, SP
|
asleep, which is quite common on mobile devices. Note that
|
||||||
- CALL libc_mach_absolute_time(SB)
|
continuous_time absolute_time, except that it takes into account
|
||||||
+ CALL libc_mach_continuous_time(SB)
|
time spent in suspend.
|
||||||
MOVL 16+(machTimebaseInfo__size+15)/16*16(SP), CX
|
|
||||||
MOVL AX, 0(CX)
|
Fixes #24595
|
||||||
MOVL DX, 4(CX)
|
---
|
||||||
diff -r -u go/src/runtime/sys_darwin_amd64.s go-modified/src/runtime/sys_darwin_amd64.s
|
src/runtime/sys_darwin.go | 2 +-
|
||||||
--- go/src/runtime/sys_darwin_amd64.s 2018-10-01 23:02:54.000000000 +0200
|
src/runtime/sys_darwin_386.s | 2 +-
|
||||||
+++ go-modified/src/runtime/sys_darwin_amd64.s 2018-11-01 23:18:04.382055360 +0100
|
src/runtime/sys_darwin_amd64.s | 2 +-
|
||||||
@@ -85,7 +85,7 @@
|
src/runtime/sys_darwin_arm.s | 2 +-
|
||||||
PUSHQ BP
|
src/runtime/sys_darwin_arm64.s | 2 +-
|
||||||
MOVQ SP, BP
|
5 files changed, 5 insertions(+), 5 deletions(-)
|
||||||
MOVQ DI, BX
|
|
||||||
- CALL libc_mach_absolute_time(SB)
|
diff --git a/src/runtime/sys_darwin.go b/src/runtime/sys_darwin.go
|
||||||
+ CALL libc_mach_continuous_time(SB)
|
index f34ac88352..416fcb673f 100644
|
||||||
MOVQ AX, 0(BX)
|
--- a/src/runtime/sys_darwin.go
|
||||||
MOVL timebase<>+machTimebaseInfo_numer(SB), SI
|
+++ b/src/runtime/sys_darwin.go
|
||||||
MOVL timebase<>+machTimebaseInfo_denom(SB), DI // atomic read
|
@@ -403,7 +403,7 @@ func closeonexec(fd int32) {
|
||||||
diff -r -u go/src/runtime/sys_darwin_arm64.s go-modified/src/runtime/sys_darwin_arm64.s
|
|
||||||
--- go/src/runtime/sys_darwin_arm64.s 2018-10-01 23:02:54.000000000 +0200
|
|
||||||
+++ go-modified/src/runtime/sys_darwin_arm64.s 2018-11-01 23:18:04.380055369 +0100
|
|
||||||
@@ -110,7 +110,7 @@
|
|
||||||
|
|
||||||
TEXT runtime·nanotime_trampoline(SB),NOSPLIT,$40
|
|
||||||
MOVD R0, R19
|
|
||||||
- BL libc_mach_absolute_time(SB)
|
|
||||||
+ BL libc_mach_continuous_time(SB)
|
|
||||||
MOVD R0, 0(R19)
|
|
||||||
MOVW timebase<>+machTimebaseInfo_numer(SB), R20
|
|
||||||
MOVD $timebase<>+machTimebaseInfo_denom(SB), R21
|
|
||||||
diff -r -u go/src/runtime/sys_darwin_arm.s go-modified/src/runtime/sys_darwin_arm.s
|
|
||||||
--- go/src/runtime/sys_darwin_arm.s 2018-10-01 23:02:54.000000000 +0200
|
|
||||||
+++ go-modified/src/runtime/sys_darwin_arm.s 2018-11-01 23:18:04.381055364 +0100
|
|
||||||
@@ -118,7 +118,7 @@
|
|
||||||
|
|
||||||
TEXT runtime·nanotime_trampoline(SB),NOSPLIT,$0
|
|
||||||
MOVW R0, R8
|
|
||||||
- BL libc_mach_absolute_time(SB)
|
|
||||||
+ BL libc_mach_continuous_time(SB)
|
|
||||||
MOVW R0, 0(R8)
|
|
||||||
MOVW R1, 4(R8)
|
|
||||||
MOVW timebase<>+machTimebaseInfo_numer(SB), R6
|
|
||||||
diff -r -u go/src/runtime/sys_darwin.go go-modified/src/runtime/sys_darwin.go
|
|
||||||
--- go/src/runtime/sys_darwin.go 2018-10-01 23:02:54.000000000 +0200
|
|
||||||
+++ go-modified/src/runtime/sys_darwin.go 2018-11-01 23:18:04.384055350 +0100
|
|
||||||
@@ -348,7 +348,7 @@
|
|
||||||
//go:cgo_import_dynamic libc_usleep usleep "/usr/lib/libSystem.B.dylib"
|
//go:cgo_import_dynamic libc_usleep usleep "/usr/lib/libSystem.B.dylib"
|
||||||
|
|
||||||
//go:cgo_import_dynamic libc_mach_timebase_info mach_timebase_info "/usr/lib/libSystem.B.dylib"
|
//go:cgo_import_dynamic libc_mach_timebase_info mach_timebase_info "/usr/lib/libSystem.B.dylib"
|
||||||
@@ -58,3 +30,58 @@ diff -r -u go/src/runtime/sys_darwin.go go-modified/src/runtime/sys_darwin.go
|
|||||||
//go:cgo_import_dynamic libc_gettimeofday gettimeofday "/usr/lib/libSystem.B.dylib"
|
//go:cgo_import_dynamic libc_gettimeofday gettimeofday "/usr/lib/libSystem.B.dylib"
|
||||||
//go:cgo_import_dynamic libc_sigaction sigaction "/usr/lib/libSystem.B.dylib"
|
//go:cgo_import_dynamic libc_sigaction sigaction "/usr/lib/libSystem.B.dylib"
|
||||||
//go:cgo_import_dynamic libc_pthread_sigmask pthread_sigmask "/usr/lib/libSystem.B.dylib"
|
//go:cgo_import_dynamic libc_pthread_sigmask pthread_sigmask "/usr/lib/libSystem.B.dylib"
|
||||||
|
diff --git a/src/runtime/sys_darwin_386.s b/src/runtime/sys_darwin_386.s
|
||||||
|
index 1bc1a63c28..34a3561350 100644
|
||||||
|
--- a/src/runtime/sys_darwin_386.s
|
||||||
|
+++ b/src/runtime/sys_darwin_386.s
|
||||||
|
@@ -184,7 +184,7 @@ TEXT runtime·nanotime_trampoline(SB),NOSPLIT,$0
|
||||||
|
PUSHL BP
|
||||||
|
MOVL SP, BP
|
||||||
|
SUBL $8+(machTimebaseInfo__size+15)/16*16, SP
|
||||||
|
- CALL libc_mach_absolute_time(SB)
|
||||||
|
+ CALL libc_mach_continuous_time(SB)
|
||||||
|
MOVL 16+(machTimebaseInfo__size+15)/16*16(SP), CX
|
||||||
|
MOVL AX, 0(CX)
|
||||||
|
MOVL DX, 4(CX)
|
||||||
|
diff --git a/src/runtime/sys_darwin_amd64.s b/src/runtime/sys_darwin_amd64.s
|
||||||
|
index f99cb00ab8..8b99316983 100644
|
||||||
|
--- a/src/runtime/sys_darwin_amd64.s
|
||||||
|
+++ b/src/runtime/sys_darwin_amd64.s
|
||||||
|
@@ -86,7 +86,7 @@ TEXT runtime·nanotime_trampoline(SB),NOSPLIT,$0
|
||||||
|
PUSHQ BP
|
||||||
|
MOVQ SP, BP
|
||||||
|
MOVQ DI, BX
|
||||||
|
- CALL libc_mach_absolute_time(SB)
|
||||||
|
+ CALL libc_mach_continuous_time(SB)
|
||||||
|
MOVQ AX, 0(BX)
|
||||||
|
MOVL timebase<>+machTimebaseInfo_numer(SB), SI
|
||||||
|
MOVL timebase<>+machTimebaseInfo_denom(SB), DI // atomic read
|
||||||
|
diff --git a/src/runtime/sys_darwin_arm.s b/src/runtime/sys_darwin_arm.s
|
||||||
|
index 54c7afbf34..a4f06fdb85 100644
|
||||||
|
--- a/src/runtime/sys_darwin_arm.s
|
||||||
|
+++ b/src/runtime/sys_darwin_arm.s
|
||||||
|
@@ -118,7 +118,7 @@ GLOBL timebase<>(SB),NOPTR,$(machTimebaseInfo__size)
|
||||||
|
|
||||||
|
TEXT runtime·nanotime_trampoline(SB),NOSPLIT,$0
|
||||||
|
MOVW R0, R8
|
||||||
|
- BL libc_mach_absolute_time(SB)
|
||||||
|
+ BL libc_mach_continuous_time(SB)
|
||||||
|
MOVW R0, 0(R8)
|
||||||
|
MOVW R1, 4(R8)
|
||||||
|
MOVW timebase<>+machTimebaseInfo_numer(SB), R6
|
||||||
|
diff --git a/src/runtime/sys_darwin_arm64.s b/src/runtime/sys_darwin_arm64.s
|
||||||
|
index 29951d8ad7..cdaf0a630e 100644
|
||||||
|
--- a/src/runtime/sys_darwin_arm64.s
|
||||||
|
+++ b/src/runtime/sys_darwin_arm64.s
|
||||||
|
@@ -113,7 +113,7 @@ GLOBL timebase<>(SB),NOPTR,$(machTimebaseInfo__size)
|
||||||
|
|
||||||
|
TEXT runtime·nanotime_trampoline(SB),NOSPLIT,$40
|
||||||
|
MOVD R0, R19
|
||||||
|
- BL libc_mach_absolute_time(SB)
|
||||||
|
+ BL libc_mach_continuous_time(SB)
|
||||||
|
MOVD R0, 0(R19)
|
||||||
|
MOVW timebase<>+machTimebaseInfo_numer(SB), R20
|
||||||
|
MOVD $timebase<>+machTimebaseInfo_denom(SB), R21
|
||||||
|
--
|
||||||
|
2.20.1
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,279 @@
|
|||||||
|
From bc77ad792117829909eeeca3aa492d6f292d004d Mon Sep 17 00:00:00 2001
|
||||||
|
From: "Jason A. Donenfeld" <Jason@zx2c4.com>
|
||||||
|
Date: Tue, 19 Mar 2019 13:55:44 -0600
|
||||||
|
Subject: [PATCH] syscall: do not link against ___getdirentries64
|
||||||
|
|
||||||
|
---
|
||||||
|
.../x/sys/unix/syscall_darwin_386.go | 5 ++++-
|
||||||
|
.../x/sys/unix/syscall_darwin_amd64.go | 5 ++++-
|
||||||
|
.../x/sys/unix/zsyscall_darwin_386.go | 22 -------------------
|
||||||
|
.../x/sys/unix/zsyscall_darwin_386.s | 2 --
|
||||||
|
.../x/sys/unix/zsyscall_darwin_amd64.go | 22 -------------------
|
||||||
|
.../x/sys/unix/zsyscall_darwin_amd64.s | 2 --
|
||||||
|
src/syscall/syscall_darwin_386.go | 5 ++++-
|
||||||
|
src/syscall/syscall_darwin_amd64.go | 5 ++++-
|
||||||
|
src/syscall/zsyscall_darwin_386.go | 20 -----------------
|
||||||
|
src/syscall/zsyscall_darwin_386.s | 2 --
|
||||||
|
src/syscall/zsyscall_darwin_amd64.go | 20 -----------------
|
||||||
|
src/syscall/zsyscall_darwin_amd64.s | 2 --
|
||||||
|
12 files changed, 16 insertions(+), 96 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/src/cmd/vendor/golang.org/x/sys/unix/syscall_darwin_386.go b/src/cmd/vendor/golang.org/x/sys/unix/syscall_darwin_386.go
|
||||||
|
index 489726fa9b..900dd7c91f 100644
|
||||||
|
--- a/src/cmd/vendor/golang.org/x/sys/unix/syscall_darwin_386.go
|
||||||
|
+++ b/src/cmd/vendor/golang.org/x/sys/unix/syscall_darwin_386.go
|
||||||
|
@@ -56,8 +56,11 @@ const SYS___SYSCTL = SYS_SYSCTL
|
||||||
|
//sys Fstat(fd int, stat *Stat_t) (err error) = SYS_FSTAT64
|
||||||
|
//sys Fstatat(fd int, path string, stat *Stat_t, flags int) (err error) = SYS_FSTATAT64
|
||||||
|
//sys Fstatfs(fd int, stat *Statfs_t) (err error) = SYS_FSTATFS64
|
||||||
|
-//sys Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) = SYS_GETDIRENTRIES64
|
||||||
|
//sys getfsstat(buf unsafe.Pointer, size uintptr, flags int) (n int, err error) = SYS_GETFSSTAT64
|
||||||
|
//sys Lstat(path string, stat *Stat_t) (err error) = SYS_LSTAT64
|
||||||
|
//sys Stat(path string, stat *Stat_t) (err error) = SYS_STAT64
|
||||||
|
//sys Statfs(path string, stat *Statfs_t) (err error) = SYS_STATFS64
|
||||||
|
+
|
||||||
|
+func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
|
||||||
|
+ return 0, ENOSYS
|
||||||
|
+}
|
||||||
|
diff --git a/src/cmd/vendor/golang.org/x/sys/unix/syscall_darwin_amd64.go b/src/cmd/vendor/golang.org/x/sys/unix/syscall_darwin_amd64.go
|
||||||
|
index 914b89bde5..95e245fff7 100644
|
||||||
|
--- a/src/cmd/vendor/golang.org/x/sys/unix/syscall_darwin_amd64.go
|
||||||
|
+++ b/src/cmd/vendor/golang.org/x/sys/unix/syscall_darwin_amd64.go
|
||||||
|
@@ -56,8 +56,11 @@ const SYS___SYSCTL = SYS_SYSCTL
|
||||||
|
//sys Fstat(fd int, stat *Stat_t) (err error) = SYS_FSTAT64
|
||||||
|
//sys Fstatat(fd int, path string, stat *Stat_t, flags int) (err error) = SYS_FSTATAT64
|
||||||
|
//sys Fstatfs(fd int, stat *Statfs_t) (err error) = SYS_FSTATFS64
|
||||||
|
-//sys Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) = SYS_GETDIRENTRIES64
|
||||||
|
//sys getfsstat(buf unsafe.Pointer, size uintptr, flags int) (n int, err error) = SYS_GETFSSTAT64
|
||||||
|
//sys Lstat(path string, stat *Stat_t) (err error) = SYS_LSTAT64
|
||||||
|
//sys Stat(path string, stat *Stat_t) (err error) = SYS_STAT64
|
||||||
|
//sys Statfs(path string, stat *Statfs_t) (err error) = SYS_STATFS64
|
||||||
|
+
|
||||||
|
+func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
|
||||||
|
+ return 0, ENOSYS
|
||||||
|
+}
|
||||||
|
diff --git a/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_386.go b/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_386.go
|
||||||
|
index 23346dc68f..db4f1eaf1c 100644
|
||||||
|
--- a/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_386.go
|
||||||
|
+++ b/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_386.go
|
||||||
|
@@ -2408,28 +2408,6 @@ func libc_fstatfs64_trampoline()
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
-func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
|
||||||
|
- var _p0 unsafe.Pointer
|
||||||
|
- if len(buf) > 0 {
|
||||||
|
- _p0 = unsafe.Pointer(&buf[0])
|
||||||
|
- } else {
|
||||||
|
- _p0 = unsafe.Pointer(&_zero)
|
||||||
|
- }
|
||||||
|
- r0, _, e1 := syscall_syscall6(funcPC(libc___getdirentries64_trampoline), uintptr(fd), uintptr(_p0), uintptr(len(buf)), uintptr(unsafe.Pointer(basep)), 0, 0)
|
||||||
|
- n = int(r0)
|
||||||
|
- if e1 != 0 {
|
||||||
|
- err = errnoErr(e1)
|
||||||
|
- }
|
||||||
|
- return
|
||||||
|
-}
|
||||||
|
-
|
||||||
|
-func libc___getdirentries64_trampoline()
|
||||||
|
-
|
||||||
|
-//go:linkname libc___getdirentries64 libc___getdirentries64
|
||||||
|
-//go:cgo_import_dynamic libc___getdirentries64 __getdirentries64 "/usr/lib/libSystem.B.dylib"
|
||||||
|
-
|
||||||
|
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
-
|
||||||
|
func getfsstat(buf unsafe.Pointer, size uintptr, flags int) (n int, err error) {
|
||||||
|
r0, _, e1 := syscall_syscall(funcPC(libc_getfsstat64_trampoline), uintptr(buf), uintptr(size), uintptr(flags))
|
||||||
|
n = int(r0)
|
||||||
|
diff --git a/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_386.s b/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_386.s
|
||||||
|
index 37b85b4f61..6165f70e33 100644
|
||||||
|
--- a/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_386.s
|
||||||
|
+++ b/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_386.s
|
||||||
|
@@ -272,8 +272,6 @@ TEXT ·libc_fstatat64_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
JMP libc_fstatat64(SB)
|
||||||
|
TEXT ·libc_fstatfs64_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
JMP libc_fstatfs64(SB)
|
||||||
|
-TEXT ·libc___getdirentries64_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
- JMP libc___getdirentries64(SB)
|
||||||
|
TEXT ·libc_getfsstat64_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
JMP libc_getfsstat64(SB)
|
||||||
|
TEXT ·libc_lstat64_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
diff --git a/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go b/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go
|
||||||
|
index b50178d679..dea5dee75e 100644
|
||||||
|
--- a/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go
|
||||||
|
+++ b/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go
|
||||||
|
@@ -2408,28 +2408,6 @@ func libc_fstatfs64_trampoline()
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
-func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
|
||||||
|
- var _p0 unsafe.Pointer
|
||||||
|
- if len(buf) > 0 {
|
||||||
|
- _p0 = unsafe.Pointer(&buf[0])
|
||||||
|
- } else {
|
||||||
|
- _p0 = unsafe.Pointer(&_zero)
|
||||||
|
- }
|
||||||
|
- r0, _, e1 := syscall_syscall6(funcPC(libc___getdirentries64_trampoline), uintptr(fd), uintptr(_p0), uintptr(len(buf)), uintptr(unsafe.Pointer(basep)), 0, 0)
|
||||||
|
- n = int(r0)
|
||||||
|
- if e1 != 0 {
|
||||||
|
- err = errnoErr(e1)
|
||||||
|
- }
|
||||||
|
- return
|
||||||
|
-}
|
||||||
|
-
|
||||||
|
-func libc___getdirentries64_trampoline()
|
||||||
|
-
|
||||||
|
-//go:linkname libc___getdirentries64 libc___getdirentries64
|
||||||
|
-//go:cgo_import_dynamic libc___getdirentries64 __getdirentries64 "/usr/lib/libSystem.B.dylib"
|
||||||
|
-
|
||||||
|
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
-
|
||||||
|
func getfsstat(buf unsafe.Pointer, size uintptr, flags int) (n int, err error) {
|
||||||
|
r0, _, e1 := syscall_syscall(funcPC(libc_getfsstat64_trampoline), uintptr(buf), uintptr(size), uintptr(flags))
|
||||||
|
n = int(r0)
|
||||||
|
diff --git a/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s b/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s
|
||||||
|
index da9b900a8c..f1e2d7e9a4 100644
|
||||||
|
--- a/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s
|
||||||
|
+++ b/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s
|
||||||
|
@@ -272,8 +272,6 @@ TEXT ·libc_fstatat64_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
JMP libc_fstatat64(SB)
|
||||||
|
TEXT ·libc_fstatfs64_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
JMP libc_fstatfs64(SB)
|
||||||
|
-TEXT ·libc___getdirentries64_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
- JMP libc___getdirentries64(SB)
|
||||||
|
TEXT ·libc_getfsstat64_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
JMP libc_getfsstat64(SB)
|
||||||
|
TEXT ·libc_lstat64_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
diff --git a/src/syscall/syscall_darwin_386.go b/src/syscall/syscall_darwin_386.go
|
||||||
|
index 045ebc726b..826d76f569 100644
|
||||||
|
--- a/src/syscall/syscall_darwin_386.go
|
||||||
|
+++ b/src/syscall/syscall_darwin_386.go
|
||||||
|
@@ -16,7 +16,6 @@ func setTimeval(sec, usec int64) Timeval {
|
||||||
|
|
||||||
|
//sys Fstat(fd int, stat *Stat_t) (err error) = SYS_fstat64
|
||||||
|
//sys Fstatfs(fd int, stat *Statfs_t) (err error) = SYS_fstatfs64
|
||||||
|
-//sys Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) = SYS___getdirentries64
|
||||||
|
//sysnb Gettimeofday(tp *Timeval) (err error)
|
||||||
|
//sys Lstat(path string, stat *Stat_t) (err error) = SYS_lstat64
|
||||||
|
//sys Stat(path string, stat *Stat_t) (err error) = SYS_stat64
|
||||||
|
@@ -63,3 +62,7 @@ func libc_sendfile_trampoline()
|
||||||
|
func syscall9(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr, err Errno)
|
||||||
|
|
||||||
|
func Syscall9(num, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr, err Errno) // sic
|
||||||
|
+
|
||||||
|
+func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
|
||||||
|
+ return 0, ENOSYS
|
||||||
|
+}
|
||||||
|
diff --git a/src/syscall/syscall_darwin_amd64.go b/src/syscall/syscall_darwin_amd64.go
|
||||||
|
index 7b6493bf9f..3868790049 100644
|
||||||
|
--- a/src/syscall/syscall_darwin_amd64.go
|
||||||
|
+++ b/src/syscall/syscall_darwin_amd64.go
|
||||||
|
@@ -16,7 +16,6 @@ func setTimeval(sec, usec int64) Timeval {
|
||||||
|
|
||||||
|
//sys Fstat(fd int, stat *Stat_t) (err error) = SYS_fstat64
|
||||||
|
//sys Fstatfs(fd int, stat *Statfs_t) (err error) = SYS_fstatfs64
|
||||||
|
-//sys Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) = SYS___getdirentries64
|
||||||
|
//sysnb Gettimeofday(tp *Timeval) (err error)
|
||||||
|
//sys Lstat(path string, stat *Stat_t) (err error) = SYS_lstat64
|
||||||
|
//sys Stat(path string, stat *Stat_t) (err error) = SYS_stat64
|
||||||
|
@@ -63,3 +62,7 @@ func libc_sendfile_trampoline()
|
||||||
|
func syscallX(fn, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)
|
||||||
|
|
||||||
|
func Syscall9(trap, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr, err Errno)
|
||||||
|
+
|
||||||
|
+func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
|
||||||
|
+ return 0, ENOSYS
|
||||||
|
+}
|
||||||
|
diff --git a/src/syscall/zsyscall_darwin_386.go b/src/syscall/zsyscall_darwin_386.go
|
||||||
|
index 758ff7b129..a666a1f65e 100644
|
||||||
|
--- a/src/syscall/zsyscall_darwin_386.go
|
||||||
|
+++ b/src/syscall/zsyscall_darwin_386.go
|
||||||
|
@@ -1845,27 +1845,7 @@ func libc_fstatfs64_trampoline()
|
||||||
|
|
||||||
|
//go:linkname libc_fstatfs64 libc_fstatfs64
|
||||||
|
//go:cgo_import_dynamic libc_fstatfs64 fstatfs64 "/usr/lib/libSystem.B.dylib"
|
||||||
|
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
-
|
||||||
|
-func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
|
||||||
|
- var _p0 unsafe.Pointer
|
||||||
|
- if len(buf) > 0 {
|
||||||
|
- _p0 = unsafe.Pointer(&buf[0])
|
||||||
|
- } else {
|
||||||
|
- _p0 = unsafe.Pointer(&_zero)
|
||||||
|
- }
|
||||||
|
- r0, _, e1 := syscall6(funcPC(libc___getdirentries64_trampoline), uintptr(fd), uintptr(_p0), uintptr(len(buf)), uintptr(unsafe.Pointer(basep)), 0, 0)
|
||||||
|
- n = int(r0)
|
||||||
|
- if e1 != 0 {
|
||||||
|
- err = errnoErr(e1)
|
||||||
|
- }
|
||||||
|
- return
|
||||||
|
-}
|
||||||
|
-
|
||||||
|
-func libc___getdirentries64_trampoline()
|
||||||
|
|
||||||
|
-//go:linkname libc___getdirentries64 libc___getdirentries64
|
||||||
|
-//go:cgo_import_dynamic libc___getdirentries64 __getdirentries64 "/usr/lib/libSystem.B.dylib"
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func Gettimeofday(tp *Timeval) (err error) {
|
||||||
|
diff --git a/src/syscall/zsyscall_darwin_386.s b/src/syscall/zsyscall_darwin_386.s
|
||||||
|
index a688192501..a5af9b64b9 100644
|
||||||
|
--- a/src/syscall/zsyscall_darwin_386.s
|
||||||
|
+++ b/src/syscall/zsyscall_darwin_386.s
|
||||||
|
@@ -235,8 +235,6 @@ TEXT ·libc_fstat64_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
JMP libc_fstat64(SB)
|
||||||
|
TEXT ·libc_fstatfs64_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
JMP libc_fstatfs64(SB)
|
||||||
|
-TEXT ·libc___getdirentries64_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
- JMP libc___getdirentries64(SB)
|
||||||
|
TEXT ·libc_gettimeofday_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
JMP libc_gettimeofday(SB)
|
||||||
|
TEXT ·libc_lstat64_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
diff --git a/src/syscall/zsyscall_darwin_amd64.go b/src/syscall/zsyscall_darwin_amd64.go
|
||||||
|
index afc3d72d8d..bb87aef1f9 100644
|
||||||
|
--- a/src/syscall/zsyscall_darwin_amd64.go
|
||||||
|
+++ b/src/syscall/zsyscall_darwin_amd64.go
|
||||||
|
@@ -1845,27 +1845,7 @@ func libc_fstatfs64_trampoline()
|
||||||
|
|
||||||
|
//go:linkname libc_fstatfs64 libc_fstatfs64
|
||||||
|
//go:cgo_import_dynamic libc_fstatfs64 fstatfs64 "/usr/lib/libSystem.B.dylib"
|
||||||
|
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
-
|
||||||
|
-func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
|
||||||
|
- var _p0 unsafe.Pointer
|
||||||
|
- if len(buf) > 0 {
|
||||||
|
- _p0 = unsafe.Pointer(&buf[0])
|
||||||
|
- } else {
|
||||||
|
- _p0 = unsafe.Pointer(&_zero)
|
||||||
|
- }
|
||||||
|
- r0, _, e1 := syscall6(funcPC(libc___getdirentries64_trampoline), uintptr(fd), uintptr(_p0), uintptr(len(buf)), uintptr(unsafe.Pointer(basep)), 0, 0)
|
||||||
|
- n = int(r0)
|
||||||
|
- if e1 != 0 {
|
||||||
|
- err = errnoErr(e1)
|
||||||
|
- }
|
||||||
|
- return
|
||||||
|
-}
|
||||||
|
-
|
||||||
|
-func libc___getdirentries64_trampoline()
|
||||||
|
|
||||||
|
-//go:linkname libc___getdirentries64 libc___getdirentries64
|
||||||
|
-//go:cgo_import_dynamic libc___getdirentries64 __getdirentries64 "/usr/lib/libSystem.B.dylib"
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func Gettimeofday(tp *Timeval) (err error) {
|
||||||
|
diff --git a/src/syscall/zsyscall_darwin_amd64.s b/src/syscall/zsyscall_darwin_amd64.s
|
||||||
|
index 21ab38e3ee..409320dea5 100644
|
||||||
|
--- a/src/syscall/zsyscall_darwin_amd64.s
|
||||||
|
+++ b/src/syscall/zsyscall_darwin_amd64.s
|
||||||
|
@@ -235,8 +235,6 @@ TEXT ·libc_fstat64_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
JMP libc_fstat64(SB)
|
||||||
|
TEXT ·libc_fstatfs64_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
JMP libc_fstatfs64(SB)
|
||||||
|
-TEXT ·libc___getdirentries64_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
- JMP libc___getdirentries64(SB)
|
||||||
|
TEXT ·libc_gettimeofday_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
JMP libc_gettimeofday(SB)
|
||||||
|
TEXT ·libc_lstat64_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
--
|
||||||
|
2.21.0
|
||||||
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
/* SPDX-License-Identifier: GPL-2.0
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
/* Fit within memory limits for iOS */
|
|
||||||
|
|
||||||
const (
|
|
||||||
QueueOutboundSize = 1024
|
|
||||||
QueueInboundSize = 1024
|
|
||||||
QueueHandshakeSize = 1024
|
|
||||||
MaxSegmentSize = 1700
|
|
||||||
PreallocatedBuffersPerPool = 1024
|
|
||||||
)
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
/* SPDX-License-Identifier: GPL-2.0
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package tun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.zx2c4.com/wireguard-go/rwcancel"
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func CreateTUNFromFD(tunFd int) (TUNDevice, string, error) {
|
|
||||||
file := os.NewFile(uintptr(tunFd), "/dev/tun")
|
|
||||||
tun := &nativeTun{
|
|
||||||
tunFile: file,
|
|
||||||
fd: file.Fd(),
|
|
||||||
events: make(chan TUNEvent, 5),
|
|
||||||
errors: make(chan error, 5),
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
tun.rwcancel, err = rwcancel.NewRWCancel(tunFd)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
name, err := tun.Name()
|
|
||||||
if err != nil {
|
|
||||||
tun.rwcancel.Cancel()
|
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
tunIfindex, err := func() (int, error) {
|
|
||||||
iface, err := net.InterfaceByName(name)
|
|
||||||
if err != nil {
|
|
||||||
return -1, err
|
|
||||||
}
|
|
||||||
return iface.Index, nil
|
|
||||||
}()
|
|
||||||
if err != nil {
|
|
||||||
tun.tunFile.Close()
|
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
tun.routeSocket, err = unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)
|
|
||||||
if err != nil {
|
|
||||||
tun.tunFile.Close()
|
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
go tun.routineRouteListener(tunIfindex)
|
|
||||||
|
|
||||||
return tun, name, nil
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
From 3efe4df9b66d4af86363e37768ea469abb8f7bdc Mon Sep 17 00:00:00 2001
|
||||||
|
From: "Jason A. Donenfeld" <Jason@zx2c4.com>
|
||||||
|
Date: Tue, 19 Mar 2019 14:01:21 -0600
|
||||||
|
Subject: [PATCH] unix: do not link against ___getdirentries64
|
||||||
|
|
||||||
|
---
|
||||||
|
unix/syscall_darwin_386.go | 5 ++++-
|
||||||
|
unix/syscall_darwin_amd64.go | 5 ++++-
|
||||||
|
unix/zsyscall_darwin_386.go | 22 ----------------------
|
||||||
|
unix/zsyscall_darwin_386.s | 2 --
|
||||||
|
unix/zsyscall_darwin_amd64.go | 22 ----------------------
|
||||||
|
unix/zsyscall_darwin_amd64.s | 2 --
|
||||||
|
6 files changed, 8 insertions(+), 50 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/unix/syscall_darwin_386.go b/unix/syscall_darwin_386.go
|
||||||
|
index 489726f..900dd7c 100644
|
||||||
|
--- a/unix/syscall_darwin_386.go
|
||||||
|
+++ b/unix/syscall_darwin_386.go
|
||||||
|
@@ -56,8 +56,11 @@ const SYS___SYSCTL = SYS_SYSCTL
|
||||||
|
//sys Fstat(fd int, stat *Stat_t) (err error) = SYS_FSTAT64
|
||||||
|
//sys Fstatat(fd int, path string, stat *Stat_t, flags int) (err error) = SYS_FSTATAT64
|
||||||
|
//sys Fstatfs(fd int, stat *Statfs_t) (err error) = SYS_FSTATFS64
|
||||||
|
-//sys Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) = SYS_GETDIRENTRIES64
|
||||||
|
//sys getfsstat(buf unsafe.Pointer, size uintptr, flags int) (n int, err error) = SYS_GETFSSTAT64
|
||||||
|
//sys Lstat(path string, stat *Stat_t) (err error) = SYS_LSTAT64
|
||||||
|
//sys Stat(path string, stat *Stat_t) (err error) = SYS_STAT64
|
||||||
|
//sys Statfs(path string, stat *Statfs_t) (err error) = SYS_STATFS64
|
||||||
|
+
|
||||||
|
+func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
|
||||||
|
+ return 0, ENOSYS
|
||||||
|
+}
|
||||||
|
diff --git a/unix/syscall_darwin_amd64.go b/unix/syscall_darwin_amd64.go
|
||||||
|
index 914b89b..95e245f 100644
|
||||||
|
--- a/unix/syscall_darwin_amd64.go
|
||||||
|
+++ b/unix/syscall_darwin_amd64.go
|
||||||
|
@@ -56,8 +56,11 @@ const SYS___SYSCTL = SYS_SYSCTL
|
||||||
|
//sys Fstat(fd int, stat *Stat_t) (err error) = SYS_FSTAT64
|
||||||
|
//sys Fstatat(fd int, path string, stat *Stat_t, flags int) (err error) = SYS_FSTATAT64
|
||||||
|
//sys Fstatfs(fd int, stat *Statfs_t) (err error) = SYS_FSTATFS64
|
||||||
|
-//sys Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) = SYS_GETDIRENTRIES64
|
||||||
|
//sys getfsstat(buf unsafe.Pointer, size uintptr, flags int) (n int, err error) = SYS_GETFSSTAT64
|
||||||
|
//sys Lstat(path string, stat *Stat_t) (err error) = SYS_LSTAT64
|
||||||
|
//sys Stat(path string, stat *Stat_t) (err error) = SYS_STAT64
|
||||||
|
//sys Statfs(path string, stat *Statfs_t) (err error) = SYS_STATFS64
|
||||||
|
+
|
||||||
|
+func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
|
||||||
|
+ return 0, ENOSYS
|
||||||
|
+}
|
||||||
|
diff --git a/unix/zsyscall_darwin_386.go b/unix/zsyscall_darwin_386.go
|
||||||
|
index 23346dc..db4f1ea 100644
|
||||||
|
--- a/unix/zsyscall_darwin_386.go
|
||||||
|
+++ b/unix/zsyscall_darwin_386.go
|
||||||
|
@@ -2408,28 +2408,6 @@ func libc_fstatfs64_trampoline()
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
-func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
|
||||||
|
- var _p0 unsafe.Pointer
|
||||||
|
- if len(buf) > 0 {
|
||||||
|
- _p0 = unsafe.Pointer(&buf[0])
|
||||||
|
- } else {
|
||||||
|
- _p0 = unsafe.Pointer(&_zero)
|
||||||
|
- }
|
||||||
|
- r0, _, e1 := syscall_syscall6(funcPC(libc___getdirentries64_trampoline), uintptr(fd), uintptr(_p0), uintptr(len(buf)), uintptr(unsafe.Pointer(basep)), 0, 0)
|
||||||
|
- n = int(r0)
|
||||||
|
- if e1 != 0 {
|
||||||
|
- err = errnoErr(e1)
|
||||||
|
- }
|
||||||
|
- return
|
||||||
|
-}
|
||||||
|
-
|
||||||
|
-func libc___getdirentries64_trampoline()
|
||||||
|
-
|
||||||
|
-//go:linkname libc___getdirentries64 libc___getdirentries64
|
||||||
|
-//go:cgo_import_dynamic libc___getdirentries64 __getdirentries64 "/usr/lib/libSystem.B.dylib"
|
||||||
|
-
|
||||||
|
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
-
|
||||||
|
func getfsstat(buf unsafe.Pointer, size uintptr, flags int) (n int, err error) {
|
||||||
|
r0, _, e1 := syscall_syscall(funcPC(libc_getfsstat64_trampoline), uintptr(buf), uintptr(size), uintptr(flags))
|
||||||
|
n = int(r0)
|
||||||
|
diff --git a/unix/zsyscall_darwin_386.s b/unix/zsyscall_darwin_386.s
|
||||||
|
index 37b85b4..6165f70 100644
|
||||||
|
--- a/unix/zsyscall_darwin_386.s
|
||||||
|
+++ b/unix/zsyscall_darwin_386.s
|
||||||
|
@@ -272,8 +272,6 @@ TEXT ·libc_fstatat64_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
JMP libc_fstatat64(SB)
|
||||||
|
TEXT ·libc_fstatfs64_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
JMP libc_fstatfs64(SB)
|
||||||
|
-TEXT ·libc___getdirentries64_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
- JMP libc___getdirentries64(SB)
|
||||||
|
TEXT ·libc_getfsstat64_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
JMP libc_getfsstat64(SB)
|
||||||
|
TEXT ·libc_lstat64_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
diff --git a/unix/zsyscall_darwin_amd64.go b/unix/zsyscall_darwin_amd64.go
|
||||||
|
index c142e33..126f993 100644
|
||||||
|
--- a/unix/zsyscall_darwin_amd64.go
|
||||||
|
+++ b/unix/zsyscall_darwin_amd64.go
|
||||||
|
@@ -2423,28 +2423,6 @@ func libc_fstatfs64_trampoline()
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
-func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
|
||||||
|
- var _p0 unsafe.Pointer
|
||||||
|
- if len(buf) > 0 {
|
||||||
|
- _p0 = unsafe.Pointer(&buf[0])
|
||||||
|
- } else {
|
||||||
|
- _p0 = unsafe.Pointer(&_zero)
|
||||||
|
- }
|
||||||
|
- r0, _, e1 := syscall_syscall6(funcPC(libc___getdirentries64_trampoline), uintptr(fd), uintptr(_p0), uintptr(len(buf)), uintptr(unsafe.Pointer(basep)), 0, 0)
|
||||||
|
- n = int(r0)
|
||||||
|
- if e1 != 0 {
|
||||||
|
- err = errnoErr(e1)
|
||||||
|
- }
|
||||||
|
- return
|
||||||
|
-}
|
||||||
|
-
|
||||||
|
-func libc___getdirentries64_trampoline()
|
||||||
|
-
|
||||||
|
-//go:linkname libc___getdirentries64 libc___getdirentries64
|
||||||
|
-//go:cgo_import_dynamic libc___getdirentries64 __getdirentries64 "/usr/lib/libSystem.B.dylib"
|
||||||
|
-
|
||||||
|
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
-
|
||||||
|
func getfsstat(buf unsafe.Pointer, size uintptr, flags int) (n int, err error) {
|
||||||
|
r0, _, e1 := syscall_syscall(funcPC(libc_getfsstat64_trampoline), uintptr(buf), uintptr(size), uintptr(flags))
|
||||||
|
n = int(r0)
|
||||||
|
diff --git a/unix/zsyscall_darwin_amd64.s b/unix/zsyscall_darwin_amd64.s
|
||||||
|
index 1a39151..a19c4f5 100644
|
||||||
|
--- a/unix/zsyscall_darwin_amd64.s
|
||||||
|
+++ b/unix/zsyscall_darwin_amd64.s
|
||||||
|
@@ -274,8 +274,6 @@ TEXT ·libc_fstatat64_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
JMP libc_fstatat64(SB)
|
||||||
|
TEXT ·libc_fstatfs64_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
JMP libc_fstatfs64(SB)
|
||||||
|
-TEXT ·libc___getdirentries64_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
- JMP libc___getdirentries64(SB)
|
||||||
|
TEXT ·libc_getfsstat64_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
JMP libc_getfsstat64(SB)
|
||||||
|
TEXT ·libc_lstat64_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
--
|
||||||
|
2.21.0
|
||||||
|
|
||||||
Reference in New Issue
Block a user