Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| de8fedf87a | |||
| 98d306da5b | |||
| c7b7b1247b | |||
| a66f13eb01 | |||
| f50e7ae686 | |||
| 4d6692548c | |||
| 1dccd39818 | |||
| 4cb775c72f | |||
| 4cb783c447 | |||
| d20daa345a | |||
| 168ba2da8a | |||
| 714d6a41bd | |||
| 9b92a8f933 | |||
| 5e9780ef8f | |||
| 9faf814e8b | |||
| 30da10a0e9 | |||
| a18614d6b3 | |||
| 5100e597aa | |||
| 0340641c4c | |||
| 813dea6902 | |||
| c30d491edc | |||
| 88c80d6694 | |||
| 393718dfaf | |||
| f852b6f919 | |||
| 8926434682 | |||
| 493c7b102e | |||
| 717bc8a26f | |||
| e582155a10 | |||
| 70d19691a7 | |||
| 40b1f0bac8 | |||
| 52ac9b82c2 | |||
| fc1fdbbcdb | |||
| 300268daa0 | |||
| 9bf304a9ac | |||
| 586a592b68 | |||
| c0526d2efb | |||
| fdbd4f875e | |||
| 4a037cc706 | |||
| 5190fc2249 | |||
| 3f25d54dcc | |||
| f9880907a2 | |||
| 404fa741e8 | |||
| 6e1f03e41c | |||
| 6d8965e97d | |||
| 5e5481b69b | |||
| 69b33c0fad | |||
| 167e4f0bf2 | |||
| 6e3b28852a | |||
| 5914e868ab | |||
| 83ea9d6fa7 | |||
| b954e9a4fd | |||
| 76894fba68 | |||
| 89a564ce62 | |||
| 178fe86d36 | |||
| 571349bb3d | |||
| 98ebd55208 | |||
| 83d0d34411 |
@@ -1,12 +1,14 @@
|
|||||||
# [WireGuard](https://www.wireguard.com/) for iOS and macOS
|
# [WireGuard](https://www.wireguard.com/) for iOS and macOS
|
||||||
|
|
||||||
|
This project contains an application for iOS and for macOS, as well as many components shared between the two of them. You may toggle between the two platforms by selecting the target from within Xcode.
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
- Clone this repo recursively:
|
- Clone this repo:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ git clone --recursive https://git.zx2c4.com/wireguard-ios
|
$ git clone https://git.zx2c4.com/wireguard-apple
|
||||||
$ cd wireguard-ios
|
$ cd wireguard-apple
|
||||||
```
|
```
|
||||||
|
|
||||||
- Rename and populate developer team ID file:
|
- Rename and populate developer team ID file:
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ err:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t view_lines_from_cursor(const struct log *input_log, uint32_t cursor, void(*cb)(const char *, uint64_t))
|
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;
|
struct log *log;
|
||||||
uint32_t l, i = cursor;
|
uint32_t l, i = cursor;
|
||||||
@@ -132,7 +132,7 @@ uint32_t view_lines_from_cursor(const struct log *input_log, uint32_t cursor, vo
|
|||||||
else
|
else
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
cb(line->line, line->time_ns);
|
cb(line->line, line->time_ns, ctx);
|
||||||
cursor = (i + 1) % MAX_LINES;
|
cursor = (i + 1) % MAX_LINES;
|
||||||
}
|
}
|
||||||
free(log);
|
free(log);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
struct log;
|
struct log;
|
||||||
void write_msg_to_log(struct log *log, const char *tag, const char *msg);
|
void write_msg_to_log(struct log *log, const char *tag, const char *msg);
|
||||||
int write_log_to_file(const char *file_name, const struct log *input_log);
|
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(*)(const char *, uint64_t));
|
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);
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,10 @@
|
|||||||
6F0F44CB222D55FD00B0FF04 /* EditableTextCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F0F44CA222D55FD00B0FF04 /* EditableTextCell.swift */; };
|
6F0F44CB222D55FD00B0FF04 /* EditableTextCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F0F44CA222D55FD00B0FF04 /* EditableTextCell.swift */; };
|
||||||
6F1075642258AE9800D78929 /* DeleteTunnelsConfirmationAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F1075632258AE9800D78929 /* DeleteTunnelsConfirmationAlert.swift */; };
|
6F1075642258AE9800D78929 /* DeleteTunnelsConfirmationAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F1075632258AE9800D78929 /* DeleteTunnelsConfirmationAlert.swift */; };
|
||||||
6F19D30422402B8700A126F2 /* ConfirmationAlertPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F19D30322402B8700A126F2 /* ConfirmationAlertPresenter.swift */; };
|
6F19D30422402B8700A126F2 /* ConfirmationAlertPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F19D30322402B8700A126F2 /* ConfirmationAlertPresenter.swift */; };
|
||||||
|
6F2449E8226587B90047B9E9 /* MacAppStoreUpdateDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F2449E7226587B80047B9E9 /* MacAppStoreUpdateDetector.swift */; };
|
||||||
|
6F3E02E9228000F6001FE7E3 /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F3E02E8228000F6001FE7E3 /* MainMenu.swift */; };
|
||||||
|
6F29A9432278518D00DC6A6B /* RecentTunnelsTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F29A9422278518D00DC6A6B /* RecentTunnelsTracker.swift */; };
|
||||||
|
6F29A94722787B1600DC6A6B /* QuickActionItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F29A94622787B1600DC6A6B /* QuickActionItem.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 */; };
|
||||||
@@ -66,6 +70,7 @@
|
|||||||
6F628C3D217F09E9003482A3 /* TunnelViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F628C3C217F09E9003482A3 /* TunnelViewModel.swift */; };
|
6F628C3D217F09E9003482A3 /* TunnelViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F628C3C217F09E9003482A3 /* TunnelViewModel.swift */; };
|
||||||
6F628C3F217F3413003482A3 /* DNSServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F628C3E217F3413003482A3 /* DNSServer.swift */; };
|
6F628C3F217F3413003482A3 /* DNSServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F628C3E217F3413003482A3 /* DNSServer.swift */; };
|
||||||
6F628C41217F47DB003482A3 /* TunnelDetailTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F628C40217F47DB003482A3 /* TunnelDetailTableViewController.swift */; };
|
6F628C41217F47DB003482A3 /* TunnelDetailTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F628C40217F47DB003482A3 /* TunnelDetailTableViewController.swift */; };
|
||||||
|
6F6483E7229293300075BA15 /* LaunchedAtLoginDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F6483E6229293300075BA15 /* LaunchedAtLoginDetector.swift */; };
|
||||||
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 */; };
|
||||||
@@ -298,6 +303,10 @@
|
|||||||
6F0F44CA222D55FD00B0FF04 /* EditableTextCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditableTextCell.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>"; };
|
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>"; };
|
6F19D30322402B8700A126F2 /* ConfirmationAlertPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationAlertPresenter.swift; sourceTree = "<group>"; };
|
||||||
|
6F2449E7226587B80047B9E9 /* MacAppStoreUpdateDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacAppStoreUpdateDetector.swift; sourceTree = "<group>"; };
|
||||||
|
6F3E02E8228000F6001FE7E3 /* MainMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainMenu.swift; sourceTree = "<group>"; };
|
||||||
|
6F29A9422278518D00DC6A6B /* RecentTunnelsTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentTunnelsTracker.swift; sourceTree = "<group>"; };
|
||||||
|
6F29A94622787B1600DC6A6B /* QuickActionItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickActionItem.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>"; };
|
||||||
@@ -316,6 +325,7 @@
|
|||||||
6F628C3C217F09E9003482A3 /* TunnelViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelViewModel.swift; sourceTree = "<group>"; };
|
6F628C3C217F09E9003482A3 /* TunnelViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelViewModel.swift; sourceTree = "<group>"; };
|
||||||
6F628C3E217F3413003482A3 /* DNSServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSServer.swift; sourceTree = "<group>"; };
|
6F628C3E217F3413003482A3 /* DNSServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSServer.swift; sourceTree = "<group>"; };
|
||||||
6F628C40217F47DB003482A3 /* TunnelDetailTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelDetailTableViewController.swift; sourceTree = "<group>"; };
|
6F628C40217F47DB003482A3 /* TunnelDetailTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelDetailTableViewController.swift; sourceTree = "<group>"; };
|
||||||
|
6F6483E6229293300075BA15 /* LaunchedAtLoginDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchedAtLoginDetector.swift; sourceTree = "<group>"; };
|
||||||
6F689999218043390012E523 /* WireGuard-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "WireGuard-Bridging-Header.h"; sourceTree = "<group>"; };
|
6F689999218043390012E523 /* WireGuard-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "WireGuard-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
6F6899A42180447E0012E523 /* x25519.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = x25519.h; sourceTree = "<group>"; };
|
6F6899A42180447E0012E523 /* x25519.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = x25519.h; sourceTree = "<group>"; };
|
||||||
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>"; };
|
||||||
@@ -562,6 +572,8 @@
|
|||||||
6F919EC2218A2AE90023B400 /* ErrorPresenter.swift */,
|
6F919EC2218A2AE90023B400 /* ErrorPresenter.swift */,
|
||||||
6F19D30322402B8700A126F2 /* ConfirmationAlertPresenter.swift */,
|
6F19D30322402B8700A126F2 /* ConfirmationAlertPresenter.swift */,
|
||||||
5F45417C21C1B23600994C13 /* UITableViewCell+Reuse.swift */,
|
5F45417C21C1B23600994C13 /* UITableViewCell+Reuse.swift */,
|
||||||
|
6F29A9422278518D00DC6A6B /* RecentTunnelsTracker.swift */,
|
||||||
|
6F29A94622787B1600DC6A6B /* QuickActionItem.swift */,
|
||||||
6FF4AC23211EC472002C96EB /* Info.plist */,
|
6FF4AC23211EC472002C96EB /* Info.plist */,
|
||||||
6FF4AC482120B9E0002C96EB /* WireGuard.entitlements */,
|
6FF4AC482120B9E0002C96EB /* WireGuard.entitlements */,
|
||||||
);
|
);
|
||||||
@@ -629,9 +641,12 @@
|
|||||||
6FB1BD5F21D2607A00A991BF /* AppDelegate.swift */,
|
6FB1BD5F21D2607A00A991BF /* AppDelegate.swift */,
|
||||||
6F89E17921EDEB0E00C97BB9 /* StatusItemController.swift */,
|
6F89E17921EDEB0E00C97BB9 /* StatusItemController.swift */,
|
||||||
6FBA101621D655340051C35F /* StatusMenu.swift */,
|
6FBA101621D655340051C35F /* StatusMenu.swift */,
|
||||||
|
6F3E02E8228000F6001FE7E3 /* MainMenu.swift */,
|
||||||
6F89E17B21F090CC00C97BB9 /* TunnelsTracker.swift */,
|
6F89E17B21F090CC00C97BB9 /* TunnelsTracker.swift */,
|
||||||
6FBA104121D6BC210051C35F /* ErrorPresenter.swift */,
|
6FBA104121D6BC210051C35F /* ErrorPresenter.swift */,
|
||||||
6FCD99AE21E0EA1700BA4C82 /* ImportPanelPresenter.swift */,
|
6FCD99AE21E0EA1700BA4C82 /* ImportPanelPresenter.swift */,
|
||||||
|
6F2449E7226587B80047B9E9 /* MacAppStoreUpdateDetector.swift */,
|
||||||
|
6F6483E6229293300075BA15 /* LaunchedAtLoginDetector.swift */,
|
||||||
6FB1BD6121D2607E00A991BF /* Assets.xcassets */,
|
6FB1BD6121D2607E00A991BF /* Assets.xcassets */,
|
||||||
6FB1BD6621D2607E00A991BF /* Info.plist */,
|
6FB1BD6621D2607E00A991BF /* Info.plist */,
|
||||||
6FB1BD6721D2607E00A991BF /* WireGuard.entitlements */,
|
6FB1BD6721D2607E00A991BF /* WireGuard.entitlements */,
|
||||||
@@ -1286,8 +1301,10 @@
|
|||||||
6F4DD16B21DA558800690EAE /* TunnelListRow.swift in Sources */,
|
6F4DD16B21DA558800690EAE /* TunnelListRow.swift in Sources */,
|
||||||
6FDB6D15224CB2CE00EE4BC3 /* LogViewCell.swift in Sources */,
|
6FDB6D15224CB2CE00EE4BC3 /* LogViewCell.swift in Sources */,
|
||||||
6FE3661D21F64F6B00F78C7D /* ConfTextColorTheme.swift in Sources */,
|
6FE3661D21F64F6B00F78C7D /* ConfTextColorTheme.swift in Sources */,
|
||||||
|
6F3E02E9228000F6001FE7E3 /* MainMenu.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 */,
|
||||||
|
6F6483E7229293300075BA15 /* LaunchedAtLoginDetector.swift in Sources */,
|
||||||
6FB1BDBF21D50F0200A991BF /* TunnelConfiguration+WgQuickConfig.swift in Sources */,
|
6FB1BDBF21D50F0200A991BF /* TunnelConfiguration+WgQuickConfig.swift in Sources */,
|
||||||
6FADE96C2254B8C300B838A4 /* UnusableTunnelDetailViewController.swift in Sources */,
|
6FADE96C2254B8C300B838A4 /* UnusableTunnelDetailViewController.swift in Sources */,
|
||||||
6FFACD2021E4D8D500E9A2A5 /* ParseError+WireGuardAppError.swift in Sources */,
|
6FFACD2021E4D8D500E9A2A5 /* ParseError+WireGuardAppError.swift in Sources */,
|
||||||
@@ -1304,6 +1321,7 @@
|
|||||||
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 */,
|
||||||
|
6F2449E8226587B90047B9E9 /* MacAppStoreUpdateDetector.swift in Sources */,
|
||||||
6FB1BDC721D50F0300A991BF /* InterfaceConfiguration.swift in Sources */,
|
6FB1BDC721D50F0300A991BF /* InterfaceConfiguration.swift in Sources */,
|
||||||
6F907C9D224663A2003CED21 /* LogViewHelper.swift in Sources */,
|
6F907C9D224663A2003CED21 /* LogViewHelper.swift in Sources */,
|
||||||
6FB1BDC821D50F0300A991BF /* PeerConfiguration.swift in Sources */,
|
6FB1BDC821D50F0300A991BF /* PeerConfiguration.swift in Sources */,
|
||||||
@@ -1343,6 +1361,7 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
6FE1765A21C90E87002690EA /* LocalizationHelper.swift in Sources */,
|
6FE1765A21C90E87002690EA /* LocalizationHelper.swift in Sources */,
|
||||||
|
6F29A94722787B1600DC6A6B /* QuickActionItem.swift in Sources */,
|
||||||
6FF3527221C2616C0008484E /* ringlogger.c in Sources */,
|
6FF3527221C2616C0008484E /* ringlogger.c in Sources */,
|
||||||
6F0F44CB222D55FD00B0FF04 /* EditableTextCell.swift in Sources */,
|
6F0F44CB222D55FD00B0FF04 /* EditableTextCell.swift in Sources */,
|
||||||
6FF3527321C2616C0008484E /* Logger.swift in Sources */,
|
6FF3527321C2616C0008484E /* Logger.swift in Sources */,
|
||||||
@@ -1383,6 +1402,7 @@
|
|||||||
6F7774EA217229DB006A79B3 /* IPAddressRange.swift in Sources */,
|
6F7774EA217229DB006A79B3 /* IPAddressRange.swift in Sources */,
|
||||||
6F7774E82172020C006A79B3 /* TunnelConfiguration.swift in Sources */,
|
6F7774E82172020C006A79B3 /* TunnelConfiguration.swift in Sources */,
|
||||||
6FDEF7FB21863B6100D8FBF6 /* unzip.c in Sources */,
|
6FDEF7FB21863B6100D8FBF6 /* unzip.c in Sources */,
|
||||||
|
6F29A9432278518D00DC6A6B /* RecentTunnelsTracker.swift in Sources */,
|
||||||
6F6899A8218044FC0012E523 /* Curve25519.swift in Sources */,
|
6F6899A8218044FC0012E523 /* Curve25519.swift in Sources */,
|
||||||
6F0F44C9222D55BB00B0FF04 /* TextCell.swift in Sources */,
|
6F0F44C9222D55BB00B0FF04 /* TextCell.swift in Sources */,
|
||||||
5F4541A021C2D6B700994C13 /* TunnelListCell.swift in Sources */,
|
5F4541A021C2D6B700994C13 /* TunnelListCell.swift in Sources */,
|
||||||
@@ -1513,7 +1533,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_ENTITLEMENTS = WireGuard/UI/macOS/LoginItemHelper/LoginItemHelper.entitlements;
|
CODE_SIGN_ENTITLEMENTS = WireGuard/UI/macOS/LoginItemHelper/LoginItemHelper.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||||
INFOPLIST_FILE = WireGuard/UI/macOS/Info.plist;
|
INFOPLIST_FILE = WireGuard/UI/macOS/LoginItemHelper/Info.plist;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_MACOS).login-item-helper";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_MACOS).login-item-helper";
|
||||||
PRODUCT_NAME = WireGuardLoginItemHelper;
|
PRODUCT_NAME = WireGuardLoginItemHelper;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
@@ -1526,7 +1546,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_ENTITLEMENTS = WireGuard/UI/macOS/LoginItemHelper/LoginItemHelper.entitlements;
|
CODE_SIGN_ENTITLEMENTS = WireGuard/UI/macOS/LoginItemHelper/LoginItemHelper.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||||
INFOPLIST_FILE = WireGuard/UI/macOS/Info.plist;
|
INFOPLIST_FILE = WireGuard/UI/macOS/LoginItemHelper/Info.plist;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_MACOS).login-item-helper";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_MACOS).login-item-helper";
|
||||||
PRODUCT_NAME = WireGuardLoginItemHelper;
|
PRODUCT_NAME = WireGuardLoginItemHelper;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
@@ -1687,6 +1707,7 @@
|
|||||||
CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE;
|
CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE;
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
ENABLE_TESTABILITY = YES;
|
ENABLE_TESTABILITY = YES;
|
||||||
@@ -1752,6 +1773,7 @@
|
|||||||
CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE;
|
CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE;
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
|||||||
@@ -291,19 +291,41 @@
|
|||||||
"alertSystemErrorMessageTunnelConfigurationReadWriteFailed" = "Reading or writing the configuration failed.";
|
"alertSystemErrorMessageTunnelConfigurationReadWriteFailed" = "Reading or writing the configuration failed.";
|
||||||
"alertSystemErrorMessageTunnelConfigurationUnknown" = "Unknown system error.";
|
"alertSystemErrorMessageTunnelConfigurationUnknown" = "Unknown system error.";
|
||||||
|
|
||||||
// Mac status bar menu / pulldown menu
|
// Mac status bar menu / pulldown menu / main menu
|
||||||
|
|
||||||
"macMenuNetworks (%@)" = "Networks: %@";
|
"macMenuNetworks (%@)" = "Networks: %@";
|
||||||
"macMenuNetworksNone" = "Networks: None";
|
"macMenuNetworksNone" = "Networks: None";
|
||||||
|
|
||||||
"macMenuTitle" = "WireGuard";
|
"macMenuTitle" = "WireGuard";
|
||||||
"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…";
|
||||||
"macMenuViewLog" = "View log";
|
"macMenuViewLog" = "View Log";
|
||||||
"macMenuExportTunnels" = "Export tunnels to zip…";
|
"macMenuExportTunnels" = "Export Tunnels to Zip…";
|
||||||
"macMenuAbout" = "About WireGuard";
|
"macMenuAbout" = "About WireGuard";
|
||||||
"macMenuQuit" = "Quit";
|
"macMenuQuit" = "Quit WireGuard";
|
||||||
|
|
||||||
|
"macMenuHideApp" = "Hide WireGuard";
|
||||||
|
"macMenuHideOtherApps" = "Hide Others";
|
||||||
|
"macMenuShowAllApps" = "Show All";
|
||||||
|
|
||||||
|
"macMenuFile" = "File";
|
||||||
|
"macMenuCloseWindow" = "Close Window";
|
||||||
|
|
||||||
|
"macMenuEdit" = "Edit";
|
||||||
|
"macMenuCut" = "Cut";
|
||||||
|
"macMenuCopy" = "Copy";
|
||||||
|
"macMenuPaste" = "Paste";
|
||||||
|
"macMenuSelectAll" = "Select All";
|
||||||
|
|
||||||
|
"macMenuTunnel" = "Tunnel";
|
||||||
|
"macMenuToggleStatus" = "Toggle Status";
|
||||||
|
"macMenuEditTunnel" = "Edit…";
|
||||||
|
"macMenuDeleteSelected" = "Delete Selected";
|
||||||
|
|
||||||
|
"macMenuWindow" = "Window";
|
||||||
|
"macMenuMinimize" = "Minimize";
|
||||||
|
"macMenuZoom" = "Zoom";
|
||||||
|
|
||||||
// Mac manage tunnels window
|
// Mac manage tunnels window
|
||||||
|
|
||||||
@@ -383,6 +405,12 @@
|
|||||||
|
|
||||||
// Mac alert
|
// Mac alert
|
||||||
|
|
||||||
|
"macConfirmAndQuitAlertMessage" = "Do you want to close the tunnels manager or quit WireGuard entirely?";
|
||||||
|
"macConfirmAndQuitAlertInfo" = "If you close the tunnels manager, WireGuard will continue to be available from the menu bar icon.";
|
||||||
|
"macConfirmAndQuitInfoWithActiveTunnel (%@)" = "If you close the tunnels manager, WireGuard will continue to be available from the menu bar icon.\n\nNote that if you quit WireGuard entirely the currently active tunnel ('%@') will still remain active until you deactivate it from this application or through the Network panel in System Preferences.";
|
||||||
|
"macConfirmAndQuitAlertQuitWireGuard" = "Quit WireGuard";
|
||||||
|
"macConfirmAndQuitAlertCloseWindow" = "Close Tunnels Manager";
|
||||||
|
|
||||||
"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";
|
"macPrivacyNoticeMessage" = "Privacy notice: be sure you trust this configuration file";
|
||||||
@@ -405,3 +433,9 @@
|
|||||||
"macUnusableTunnelMessage" = "The configuration for this tunnel cannot be found in the keychain.";
|
"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.";
|
"macUnusableTunnelInfo" = "In case this tunnel was created by another user, only that user can view, edit, or activate this tunnel.";
|
||||||
"macUnusableTunnelButtonTitleDeleteTunnel" = "Delete tunnel";
|
"macUnusableTunnelButtonTitleDeleteTunnel" = "Delete tunnel";
|
||||||
|
|
||||||
|
// Mac App Store updating alert
|
||||||
|
|
||||||
|
"macAppStoreUpdatingAlertMessage" = "App Store would like to update WireGuard";
|
||||||
|
"macAppStoreUpdatingAlertInfoWithOnDemand (%@)" = "Please disable on-demand for tunnel ‘%@’, deactivate it, and then continue updating in App Store.";
|
||||||
|
"macAppStoreUpdatingAlertInfoWithoutOnDemand (%@)" = "Please deactivate tunnel ‘%@’ and then continue updating in App Store.";
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
VERSION_NAME = 0.0.20190409
|
VERSION_NAME = 0.0.20190609
|
||||||
VERSION_ID = 6
|
VERSION_ID = 11
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class TunnelsManager {
|
|||||||
startObservingTunnelConfigurations()
|
startObservingTunnelConfigurations()
|
||||||
}
|
}
|
||||||
|
|
||||||
static func create(completionHandler: @escaping (WireGuardResult<TunnelsManager>) -> Void) {
|
static func create(completionHandler: @escaping (Result<TunnelsManager, TunnelsManagerError>) -> Void) {
|
||||||
#if targetEnvironment(simulator)
|
#if targetEnvironment(simulator)
|
||||||
completionHandler(.success(TunnelsManager(tunnelProviders: MockTunnels.createMockTunnels())))
|
completionHandler(.success(TunnelsManager(tunnelProviders: MockTunnels.createMockTunnels())))
|
||||||
#else
|
#else
|
||||||
@@ -46,7 +46,11 @@ class TunnelsManager {
|
|||||||
|
|
||||||
var tunnelManagers = managers ?? []
|
var tunnelManagers = managers ?? []
|
||||||
var refs: Set<Data> = []
|
var refs: Set<Data> = []
|
||||||
|
var tunnelNames: Set<String> = []
|
||||||
for (index, tunnelManager) in tunnelManagers.enumerated().reversed() {
|
for (index, tunnelManager) in tunnelManagers.enumerated().reversed() {
|
||||||
|
if let tunnelName = tunnelManager.localizedDescription {
|
||||||
|
tunnelNames.insert(tunnelName)
|
||||||
|
}
|
||||||
guard let proto = tunnelManager.protocolConfiguration as? NETunnelProviderProtocol else { continue }
|
guard let proto = tunnelManager.protocolConfiguration as? NETunnelProviderProtocol else { continue }
|
||||||
if proto.migrateConfigurationIfNeeded(called: tunnelManager.localizedDescription ?? "unknown") {
|
if proto.migrateConfigurationIfNeeded(called: tunnelManager.localizedDescription ?? "unknown") {
|
||||||
tunnelManager.saveToPreferences { _ in }
|
tunnelManager.saveToPreferences { _ in }
|
||||||
@@ -66,6 +70,9 @@ class TunnelsManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Keychain.deleteReferences(except: refs)
|
Keychain.deleteReferences(except: refs)
|
||||||
|
#if os(iOS)
|
||||||
|
RecentTunnelsTracker.cleanupTunnels(except: tunnelNames)
|
||||||
|
#endif
|
||||||
completionHandler(.success(TunnelsManager(tunnelProviders: tunnelManagers)))
|
completionHandler(.success(TunnelsManager(tunnelProviders: tunnelManagers)))
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -104,7 +111,7 @@ class TunnelsManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func add(tunnelConfiguration: TunnelConfiguration, onDemandOption: ActivateOnDemandOption = .off, completionHandler: @escaping (WireGuardResult<TunnelContainer>) -> Void) {
|
func add(tunnelConfiguration: TunnelConfiguration, onDemandOption: ActivateOnDemandOption = .off, completionHandler: @escaping (Result<TunnelContainer, TunnelsManagerError>) -> Void) {
|
||||||
let tunnelName = tunnelConfiguration.name ?? ""
|
let tunnelName = tunnelConfiguration.name ?? ""
|
||||||
if tunnelName.isEmpty {
|
if tunnelName.isEmpty {
|
||||||
completionHandler(.failure(TunnelsManagerError.tunnelNameEmpty))
|
completionHandler(.failure(TunnelsManagerError.tunnelNameEmpty))
|
||||||
@@ -167,9 +174,15 @@ class TunnelsManager {
|
|||||||
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 {
|
||||||
let numberSuccessful = numberSuccessful + (result.isSuccess ? 1 : 0)
|
var numberSuccessfulCount = numberSuccessful
|
||||||
let lastError = lastError ?? (result.error as? TunnelsManagerError)
|
var lastError: TunnelsManagerError?
|
||||||
self?.addMultiple(tunnelConfigurations: tail, numberSuccessful: numberSuccessful, lastError: lastError, completionHandler: completionHandler)
|
switch result {
|
||||||
|
case .failure(let error):
|
||||||
|
lastError = error
|
||||||
|
case .success:
|
||||||
|
numberSuccessfulCount = numberSuccessful + 1
|
||||||
|
}
|
||||||
|
self?.addMultiple(tunnelConfigurations: tail, numberSuccessful: numberSuccessfulCount, lastError: lastError, completionHandler: completionHandler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -182,7 +195,8 @@ class TunnelsManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let tunnelProviderManager = tunnel.tunnelProvider
|
let tunnelProviderManager = tunnel.tunnelProvider
|
||||||
let isNameChanged = tunnelName != tunnelProviderManager.localizedDescription
|
let oldName = tunnelProviderManager.localizedDescription ?? ""
|
||||||
|
let isNameChanged = tunnelName != oldName
|
||||||
if isNameChanged {
|
if isNameChanged {
|
||||||
guard !tunnels.contains(where: { $0.name == tunnelName }) else {
|
guard !tunnels.contains(where: { $0.name == tunnelName }) else {
|
||||||
completionHandler(TunnelsManagerError.tunnelAlreadyExistsWithThatName)
|
completionHandler(TunnelsManagerError.tunnelAlreadyExistsWithThatName)
|
||||||
@@ -214,6 +228,9 @@ class TunnelsManager {
|
|||||||
self.tunnels.sort { TunnelsManager.tunnelNameIsLessThan($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)
|
||||||
|
#if os(iOS)
|
||||||
|
RecentTunnelsTracker.handleTunnelRenamed(oldName: oldName, newName: tunnelName)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
self.tunnelsListDelegate?.tunnelModified(at: self.tunnels.firstIndex(of: tunnel)!)
|
self.tunnelsListDelegate?.tunnelModified(at: self.tunnels.firstIndex(of: tunnel)!)
|
||||||
|
|
||||||
@@ -260,6 +277,10 @@ class TunnelsManager {
|
|||||||
self.tunnelsListDelegate?.tunnelRemoved(at: index, tunnel: tunnel)
|
self.tunnelsListDelegate?.tunnelRemoved(at: index, tunnel: tunnel)
|
||||||
}
|
}
|
||||||
completionHandler(nil)
|
completionHandler(nil)
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
RecentTunnelsTracker.handleTunnelRemoved(tunnelName: tunnel.name)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,6 +313,10 @@ class TunnelsManager {
|
|||||||
return tunnels[index]
|
return tunnels[index]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mapTunnels<T>(transform: (TunnelContainer) throws -> T) rethrows -> [T] {
|
||||||
|
return try tunnels.map(transform)
|
||||||
|
}
|
||||||
|
|
||||||
func index(of tunnel: TunnelContainer) -> Int? {
|
func index(of tunnel: TunnelContainer) -> Int? {
|
||||||
return tunnels.firstIndex(of: tunnel)
|
return tunnels.firstIndex(of: tunnel)
|
||||||
}
|
}
|
||||||
@@ -337,6 +362,10 @@ class TunnelsManager {
|
|||||||
#else
|
#else
|
||||||
tunnel.startActivation(activationDelegate: activationDelegate)
|
tunnel.startActivation(activationDelegate: activationDelegate)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
RecentTunnelsTracker.handleTunnelActivated(tunnelName: tunnel.name)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
func startDeactivation(of tunnel: TunnelContainer) {
|
func startDeactivation(of tunnel: TunnelContainer) {
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ public class LogViewHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static var logEntries = [LogEntry]()
|
class LogEntries {
|
||||||
|
var entries: [LogEntry] = []
|
||||||
|
}
|
||||||
|
|
||||||
init?(logFilePath: String?) {
|
init?(logFilePath: String?) {
|
||||||
guard let logFilePath = logFilePath else { return nil }
|
guard let logFilePath = logFilePath else { return nil }
|
||||||
@@ -34,19 +36,21 @@ public class LogViewHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func fetchLogEntriesSinceLastFetch(completion: @escaping ([LogViewHelper.LogEntry]) -> Void) {
|
func fetchLogEntriesSinceLastFetch(completion: @escaping ([LogViewHelper.LogEntry]) -> Void) {
|
||||||
LogViewHelper.logEntries = []
|
var logEntries = LogEntries()
|
||||||
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
let newCursor = view_lines_from_cursor(self.log, self.cursor) { cStr, timestamp in
|
let newCursor = view_lines_from_cursor(self.log, self.cursor, &logEntries) { cStr, timestamp, ctx in
|
||||||
let message = cStr != nil ? String(cString: cStr!) : ""
|
let message = cStr != nil ? String(cString: cStr!) : ""
|
||||||
let date = Date(timeIntervalSince1970: Double(timestamp) / 1000000000)
|
let date = Date(timeIntervalSince1970: Double(timestamp) / 1000000000)
|
||||||
let dateString = ISO8601DateFormatter.string(from: date, timeZone: TimeZone.current, formatOptions: LogViewHelper.formatOptions)
|
let dateString = ISO8601DateFormatter.string(from: date, timeZone: TimeZone.current, formatOptions: LogViewHelper.formatOptions)
|
||||||
LogViewHelper.logEntries.append(LogEntry(timestamp: dateString, message: message))
|
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
|
DispatchQueue.main.async { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
self.cursor = newCursor
|
self.cursor = newCursor
|
||||||
completion(LogViewHelper.logEntries)
|
completion(logEntries.entries)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ class TunnelImporter {
|
|||||||
if url.pathExtension.lowercased() == "zip" {
|
if url.pathExtension.lowercased() == "zip" {
|
||||||
dispatchGroup.enter()
|
dispatchGroup.enter()
|
||||||
ZipImporter.importConfigFiles(from: url) { result in
|
ZipImporter.importConfigFiles(from: url) { result in
|
||||||
if let error = result.error {
|
switch result {
|
||||||
|
case .failure(let error):
|
||||||
lastFileImportErrorText = error.alertText
|
lastFileImportErrorText = error.alertText
|
||||||
}
|
case .success(let configsInZip):
|
||||||
if let configsInZip = result.value {
|
|
||||||
configs.append(contentsOf: configsInZip)
|
configs.append(contentsOf: configsInZip)
|
||||||
}
|
}
|
||||||
dispatchGroup.leave()
|
dispatchGroup.leave()
|
||||||
|
|||||||
@@ -9,10 +9,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
|
|
||||||
var window: UIWindow?
|
var window: UIWindow?
|
||||||
var mainVC: MainViewController?
|
var mainVC: MainViewController?
|
||||||
|
var isLaunchedForSpecificAction = false
|
||||||
|
|
||||||
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||||
Logger.configureGlobal(tagged: "APP", withFilePath: FileManager.logFileURL?.path)
|
Logger.configureGlobal(tagged: "APP", withFilePath: FileManager.logFileURL?.path)
|
||||||
|
|
||||||
|
if let launchOptions = launchOptions {
|
||||||
|
if launchOptions[.url] != nil || launchOptions[.shortcutItem] != nil {
|
||||||
|
isLaunchedForSpecificAction = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let window = UIWindow(frame: UIScreen.main.bounds)
|
let window = UIWindow(frame: UIScreen.main.bounds)
|
||||||
window.backgroundColor = .white
|
window.backgroundColor = .white
|
||||||
self.window = window
|
self.window = window
|
||||||
@@ -37,6 +44,21 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
func applicationDidBecomeActive(_ application: UIApplication) {
|
func applicationDidBecomeActive(_ application: UIApplication) {
|
||||||
mainVC?.refreshTunnelConnectionStatuses()
|
mainVC?.refreshTunnelConnectionStatuses()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func applicationWillResignActive(_ application: UIApplication) {
|
||||||
|
guard let allTunnelNames = mainVC?.allTunnelNames() else { return }
|
||||||
|
application.shortcutItems = QuickActionItem.createItems(allTunnelNames: allTunnelNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
|
||||||
|
guard shortcutItem.type == QuickActionItem.type else {
|
||||||
|
completionHandler(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let tunnelName = shortcutItem.localizedTitle
|
||||||
|
mainVC?.showTunnelDetailForTunnel(named: tunnelName, animated: false, shouldToggleStatus: true)
|
||||||
|
completionHandler(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AppDelegate {
|
extension AppDelegate {
|
||||||
@@ -45,7 +67,7 @@ extension AppDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func application(_ application: UIApplication, shouldRestoreApplicationState coder: NSCoder) -> Bool {
|
func application(_ application: UIApplication, shouldRestoreApplicationState coder: NSCoder) -> Bool {
|
||||||
return true
|
return !self.isLaunchedForSpecificAction
|
||||||
}
|
}
|
||||||
|
|
||||||
func application(_ application: UIApplication, viewControllerWithRestorationIdentifierPath identifierComponents: [String], coder: NSCoder) -> UIViewController? {
|
func application(_ application: UIApplication, viewControllerWithRestorationIdentifierPath identifierComponents: [String], coder: NSCoder) -> UIViewController? {
|
||||||
@@ -58,7 +80,7 @@ extension AppDelegate {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Show it when tunnelsManager is available
|
// Show it when tunnelsManager is available
|
||||||
mainVC?.showTunnelDetailForTunnel(named: tunnelName, animated: false)
|
mainVC?.showTunnelDetailForTunnel(named: tunnelName, animated: false, shouldToggleStatus: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class QuickActionItem: UIApplicationShortcutItem {
|
||||||
|
static let type = "WireGuardTunnelActivateAndShow"
|
||||||
|
|
||||||
|
init(tunnelName: String) {
|
||||||
|
super.init(type: QuickActionItem.type, localizedTitle: tunnelName, localizedSubtitle: nil, icon: nil, userInfo: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func createItems(allTunnelNames: [String]) -> [QuickActionItem] {
|
||||||
|
let numberOfItems = 10
|
||||||
|
// Currently, only 4 items shown by iOS, but that can increase in the future.
|
||||||
|
// iOS will discard additional items we give it.
|
||||||
|
var tunnelNames = RecentTunnelsTracker.recentlyActivatedTunnelNames(limit: numberOfItems)
|
||||||
|
let numberOfSlotsRemaining = numberOfItems - tunnelNames.count
|
||||||
|
if numberOfSlotsRemaining > 0 {
|
||||||
|
let moreTunnels = allTunnelNames.filter { !tunnelNames.contains($0) }.prefix(numberOfSlotsRemaining)
|
||||||
|
tunnelNames.append(contentsOf: moreTunnels)
|
||||||
|
}
|
||||||
|
return tunnelNames.map { QuickActionItem(tunnelName: $0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
class RecentTunnelsTracker {
|
||||||
|
|
||||||
|
private static let keyRecentlyActivatedTunnelNames = "recentlyActivatedTunnelNames"
|
||||||
|
private static let maxNumberOfTunnels = 10
|
||||||
|
|
||||||
|
private static var userDefaults: UserDefaults? {
|
||||||
|
guard let appGroupId = FileManager.appGroupId else {
|
||||||
|
wg_log(.error, staticMessage: "Cannot obtain app group ID from bundle for tracking recently used tunnels")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
guard let userDefaults = UserDefaults(suiteName: appGroupId) else {
|
||||||
|
wg_log(.error, staticMessage: "Cannot obtain shared user defaults for tracking recently used tunnels")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return userDefaults
|
||||||
|
}
|
||||||
|
|
||||||
|
static func handleTunnelActivated(tunnelName: String) {
|
||||||
|
guard let userDefaults = RecentTunnelsTracker.userDefaults else { return }
|
||||||
|
var recentTunnels = userDefaults.stringArray(forKey: keyRecentlyActivatedTunnelNames) ?? []
|
||||||
|
if let existingIndex = recentTunnels.firstIndex(of: tunnelName) {
|
||||||
|
recentTunnels.remove(at: existingIndex)
|
||||||
|
}
|
||||||
|
recentTunnels.insert(tunnelName, at: 0)
|
||||||
|
if recentTunnels.count > maxNumberOfTunnels {
|
||||||
|
recentTunnels.removeLast(recentTunnels.count - maxNumberOfTunnels)
|
||||||
|
}
|
||||||
|
userDefaults.set(recentTunnels, forKey: keyRecentlyActivatedTunnelNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func handleTunnelRemoved(tunnelName: String) {
|
||||||
|
guard let userDefaults = RecentTunnelsTracker.userDefaults else { return }
|
||||||
|
var recentTunnels = userDefaults.stringArray(forKey: keyRecentlyActivatedTunnelNames) ?? []
|
||||||
|
if let existingIndex = recentTunnels.firstIndex(of: tunnelName) {
|
||||||
|
recentTunnels.remove(at: existingIndex)
|
||||||
|
userDefaults.set(recentTunnels, forKey: keyRecentlyActivatedTunnelNames)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func handleTunnelRenamed(oldName: String, newName: String) {
|
||||||
|
guard let userDefaults = RecentTunnelsTracker.userDefaults else { return }
|
||||||
|
var recentTunnels = userDefaults.stringArray(forKey: keyRecentlyActivatedTunnelNames) ?? []
|
||||||
|
if let existingIndex = recentTunnels.firstIndex(of: oldName) {
|
||||||
|
recentTunnels[existingIndex] = newName
|
||||||
|
userDefaults.set(recentTunnels, forKey: keyRecentlyActivatedTunnelNames)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func cleanupTunnels(except tunnelNamesToKeep: Set<String>) {
|
||||||
|
guard let userDefaults = RecentTunnelsTracker.userDefaults else { return }
|
||||||
|
var recentTunnels = userDefaults.stringArray(forKey: keyRecentlyActivatedTunnelNames) ?? []
|
||||||
|
let oldCount = recentTunnels.count
|
||||||
|
recentTunnels.removeAll { !tunnelNamesToKeep.contains($0) }
|
||||||
|
if oldCount != recentTunnels.count {
|
||||||
|
userDefaults.set(recentTunnels, forKey: keyRecentlyActivatedTunnelNames)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func recentlyActivatedTunnelNames(limit: Int) -> [String] {
|
||||||
|
guard let userDefaults = RecentTunnelsTracker.userDefaults else { return [] }
|
||||||
|
var recentTunnels = userDefaults.stringArray(forKey: keyRecentlyActivatedTunnelNames) ?? []
|
||||||
|
if limit < recentTunnels.count {
|
||||||
|
recentTunnels.removeLast(recentTunnels.count - limit)
|
||||||
|
}
|
||||||
|
return recentTunnels
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,15 @@ class LogViewController: UIViewController {
|
|||||||
return busyIndicator
|
return busyIndicator
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
let paragraphStyle: NSParagraphStyle = {
|
||||||
|
let paragraphStyle = NSMutableParagraphStyle()
|
||||||
|
paragraphStyle.setParagraphStyle(NSParagraphStyle.default)
|
||||||
|
paragraphStyle.lineHeightMultiple = 1.2
|
||||||
|
return paragraphStyle
|
||||||
|
}()
|
||||||
|
|
||||||
|
var isNextLineHighlighted = false
|
||||||
|
|
||||||
var logViewHelper: LogViewHelper?
|
var logViewHelper: LogViewHelper?
|
||||||
var isFetchingLogEntries = false
|
var isFetchingLogEntries = false
|
||||||
private var updateLogEntriesTimer: Timer?
|
private var updateLogEntriesTimer: Timer?
|
||||||
@@ -68,9 +77,20 @@ class LogViewController: UIViewController {
|
|||||||
}
|
}
|
||||||
guard !fetchedLogEntries.isEmpty else { return }
|
guard !fetchedLogEntries.isEmpty else { return }
|
||||||
let isScrolledToEnd = self.textView.contentSize.height - self.textView.bounds.height - self.textView.contentOffset.y < 1
|
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 = NSMutableAttributedString()
|
||||||
let richText = NSAttributedString(string: text, attributes: [.font: font])
|
let bodyFont = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body)
|
||||||
|
let captionFont = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.caption1)
|
||||||
|
let lightGrayColor = UIColor(white: 0.88, alpha: 1.0)
|
||||||
|
|
||||||
|
for logEntry in fetchedLogEntries {
|
||||||
|
let bgColor = self.isNextLineHighlighted ? lightGrayColor : UIColor.white
|
||||||
|
let timestampText = NSAttributedString(string: logEntry.timestamp + "\n", attributes: [.font: captionFont, .backgroundColor: bgColor, .paragraphStyle: self.paragraphStyle])
|
||||||
|
let messageText = NSAttributedString(string: logEntry.message + "\n", attributes: [.font: bodyFont, .backgroundColor: bgColor, .paragraphStyle: self.paragraphStyle])
|
||||||
|
richText.append(timestampText)
|
||||||
|
richText.append(messageText)
|
||||||
|
self.isNextLineHighlighted.toggle()
|
||||||
|
}
|
||||||
self.textView.textStorage.append(richText)
|
self.textView.textStorage.append(richText)
|
||||||
if isScrolledToEnd {
|
if isScrolledToEnd {
|
||||||
let endOfCurrentText = NSRange(location: (self.textView.text as NSString).length, length: 0)
|
let endOfCurrentText = NSRange(location: (self.textView.text as NSString).length, length: 0)
|
||||||
|
|||||||
@@ -42,22 +42,25 @@ class MainViewController: UISplitViewController {
|
|||||||
TunnelsManager.create { [weak self] result in
|
TunnelsManager.create { [weak self] result in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
|
||||||
if let error = result.error {
|
switch result {
|
||||||
|
case .failure(let error):
|
||||||
ErrorPresenter.showErrorAlert(error: error, from: self)
|
ErrorPresenter.showErrorAlert(error: error, from: self)
|
||||||
return
|
case .success(let tunnelsManager):
|
||||||
|
self.tunnelsManager = tunnelsManager
|
||||||
|
self.tunnelsListVC?.setTunnelsManager(tunnelsManager: tunnelsManager)
|
||||||
|
|
||||||
|
tunnelsManager.activationDelegate = self
|
||||||
|
|
||||||
|
self.onTunnelsManagerReady?(tunnelsManager)
|
||||||
|
self.onTunnelsManagerReady = nil
|
||||||
}
|
}
|
||||||
let tunnelsManager: TunnelsManager = result.value!
|
|
||||||
|
|
||||||
self.tunnelsManager = tunnelsManager
|
|
||||||
self.tunnelsListVC?.setTunnelsManager(tunnelsManager: tunnelsManager)
|
|
||||||
|
|
||||||
tunnelsManager.activationDelegate = self
|
|
||||||
|
|
||||||
self.onTunnelsManagerReady?(tunnelsManager)
|
|
||||||
self.onTunnelsManagerReady = nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func allTunnelNames() -> [String]? {
|
||||||
|
guard let tunnelsManager = self.tunnelsManager else { return nil }
|
||||||
|
return tunnelsManager.mapTunnels { $0.name }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension MainViewController: TunnelsManagerActivationDelegate {
|
extension MainViewController: TunnelsManagerActivationDelegate {
|
||||||
@@ -85,19 +88,17 @@ extension MainViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func showTunnelDetailForTunnel(named tunnelName: String, animated: Bool) {
|
func showTunnelDetailForTunnel(named tunnelName: String, animated: Bool, shouldToggleStatus: Bool) {
|
||||||
let showTunnelDetailBlock: (TunnelsManager) -> Void = { [weak self] tunnelsManager in
|
let showTunnelDetailBlock: (TunnelsManager) -> Void = { [weak self] tunnelsManager in
|
||||||
|
guard let self = self else { return }
|
||||||
|
guard let tunnelsListVC = self.tunnelsListVC else { return }
|
||||||
if let tunnel = tunnelsManager.tunnel(named: tunnelName) {
|
if let tunnel = tunnelsManager.tunnel(named: tunnelName) {
|
||||||
let tunnelDetailVC = TunnelDetailTableViewController(tunnelsManager: tunnelsManager, tunnel: tunnel)
|
tunnelsListVC.showTunnelDetail(for: tunnel, animated: false)
|
||||||
let tunnelDetailNC = UINavigationController(rootViewController: tunnelDetailVC)
|
if shouldToggleStatus {
|
||||||
tunnelDetailNC.restorationIdentifier = "DetailNC"
|
if tunnel.status == .inactive {
|
||||||
if let self = self {
|
tunnelsManager.startActivation(of: tunnel)
|
||||||
if animated {
|
} else if tunnel.status == .active {
|
||||||
self.showDetailViewController(tunnelDetailNC, sender: self)
|
tunnelsManager.startDeactivation(of: tunnel)
|
||||||
} else {
|
|
||||||
UIView.performWithoutAnimation {
|
|
||||||
self.showDetailViewController(tunnelDetailNC, sender: self)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,10 +127,10 @@ class TunnelEditTableViewController: UITableViewController {
|
|||||||
} else {
|
} else {
|
||||||
// We're adding a new tunnel
|
// We're adding a new tunnel
|
||||||
tunnelsManager.add(tunnelConfiguration: tunnelConfiguration, onDemandOption: onDemandOption) { [weak self] result in
|
tunnelsManager.add(tunnelConfiguration: tunnelConfiguration, onDemandOption: onDemandOption) { [weak self] result in
|
||||||
if let error = result.error {
|
switch result {
|
||||||
|
case .failure(let error):
|
||||||
ErrorPresenter.showErrorAlert(error: error, from: self)
|
ErrorPresenter.showErrorAlert(error: error, from: self)
|
||||||
} else {
|
case .success(let tunnel):
|
||||||
let tunnel: TunnelContainer = result.value!
|
|
||||||
self?.dismiss(animated: true, completion: nil)
|
self?.dismiss(animated: true, completion: nil)
|
||||||
self?.delegate?.tunnelSaved(tunnel: tunnel)
|
self?.delegate?.tunnelSaved(tunnel: tunnel)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -251,6 +251,24 @@ class TunnelsListTableViewController: UIViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func showTunnelDetail(for tunnel: TunnelContainer, animated: Bool) {
|
||||||
|
guard let tunnelsManager = tunnelsManager else { return }
|
||||||
|
guard let splitViewController = splitViewController else { return }
|
||||||
|
guard let navController = navigationController else { return }
|
||||||
|
|
||||||
|
let tunnelDetailVC = TunnelDetailTableViewController(tunnelsManager: tunnelsManager,
|
||||||
|
tunnel: tunnel)
|
||||||
|
let tunnelDetailNC = UINavigationController(rootViewController: tunnelDetailVC)
|
||||||
|
tunnelDetailNC.restorationIdentifier = "DetailNC"
|
||||||
|
if splitViewController.isCollapsed && navController.viewControllers.count > 1 {
|
||||||
|
navController.setViewControllers([self, tunnelDetailNC], animated: animated)
|
||||||
|
} else {
|
||||||
|
splitViewController.showDetailViewController(tunnelDetailNC, sender: self, animated: animated)
|
||||||
|
}
|
||||||
|
detailDisplayedTunnel = tunnel
|
||||||
|
self.presentedViewController?.dismiss(animated: false, completion: nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TunnelsListTableViewController: UIDocumentPickerDelegate {
|
extension TunnelsListTableViewController: UIDocumentPickerDelegate {
|
||||||
@@ -264,9 +282,10 @@ extension TunnelsListTableViewController: QRScanViewControllerDelegate {
|
|||||||
func addScannedQRCode(tunnelConfiguration: TunnelConfiguration, qrScanViewController: QRScanViewController,
|
func addScannedQRCode(tunnelConfiguration: TunnelConfiguration, qrScanViewController: QRScanViewController,
|
||||||
completionHandler: (() -> Void)?) {
|
completionHandler: (() -> Void)?) {
|
||||||
tunnelsManager?.add(tunnelConfiguration: tunnelConfiguration) { result in
|
tunnelsManager?.add(tunnelConfiguration: tunnelConfiguration) { result in
|
||||||
if let error = result.error {
|
switch result {
|
||||||
|
case .failure(let error):
|
||||||
ErrorPresenter.showErrorAlert(error: error, from: qrScanViewController, onDismissal: completionHandler)
|
ErrorPresenter.showErrorAlert(error: error, from: qrScanViewController, onDismissal: completionHandler)
|
||||||
} else {
|
case .success:
|
||||||
completionHandler?()
|
completionHandler?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -308,12 +327,7 @@ extension TunnelsListTableViewController: UITableViewDelegate {
|
|||||||
}
|
}
|
||||||
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,
|
showTunnelDetail(for: tunnel, animated: true)
|
||||||
tunnel: tunnel)
|
|
||||||
let tunnelDetailNC = UINavigationController(rootViewController: tunnelDetailVC)
|
|
||||||
tunnelDetailNC.restorationIdentifier = "DetailNC"
|
|
||||||
showDetailViewController(tunnelDetailNC, sender: self) // Shall get propagated up to the split-vc
|
|
||||||
detailDisplayedTunnel = tunnel
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
|
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
|
||||||
@@ -386,3 +400,15 @@ extension TunnelsListTableViewController: TunnelsManagerListDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension UISplitViewController {
|
||||||
|
func showDetailViewController(_ vc: UIViewController, sender: Any?, animated: Bool) {
|
||||||
|
if animated {
|
||||||
|
showDetailViewController(vc, sender: sender)
|
||||||
|
} else {
|
||||||
|
UIView.performWithoutAnimation {
|
||||||
|
showDetailViewController(vc, sender: sender)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,33 +13,95 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
|
|
||||||
var manageTunnelsRootVC: ManageTunnelsRootViewController?
|
var manageTunnelsRootVC: ManageTunnelsRootViewController?
|
||||||
var manageTunnelsWindowObject: NSWindow?
|
var manageTunnelsWindowObject: NSWindow?
|
||||||
|
var onAppDeactivation: (() -> Void)?
|
||||||
|
|
||||||
|
func applicationWillFinishLaunching(_ notification: Notification) {
|
||||||
|
// To workaround a possible AppKit bug that causes the main menu to become unresponsive sometimes
|
||||||
|
// (especially when launched through Xcode) if we call setActivationPolicy(.regular) in
|
||||||
|
// in applicationDidFinishLaunching, we set it to .prohibited here.
|
||||||
|
// Setting it to .regular would fix that problem too, but at this point, we don't know
|
||||||
|
// whether the app was launched at login or not, so we're not sure whether we should
|
||||||
|
// show the app icon in the dock or not.
|
||||||
|
NSApp.setActivationPolicy(.prohibited)
|
||||||
|
}
|
||||||
|
|
||||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||||
Logger.configureGlobal(tagged: "APP", withFilePath: FileManager.logFileURL?.path)
|
Logger.configureGlobal(tagged: "APP", withFilePath: FileManager.logFileURL?.path)
|
||||||
registerLoginItem(shouldLaunchAtLogin: true)
|
registerLoginItem(shouldLaunchAtLogin: true)
|
||||||
|
|
||||||
|
var isLaunchedAtLogin = false
|
||||||
|
if let appleEvent = NSAppleEventManager.shared().currentAppleEvent {
|
||||||
|
isLaunchedAtLogin = LaunchedAtLoginDetector.isLaunchedAtLogin(openAppleEvent: appleEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
NSApp.mainMenu = MainMenu()
|
||||||
|
setDockIconAndMainMenuVisibility(isVisible: !isLaunchedAtLogin)
|
||||||
|
|
||||||
TunnelsManager.create { [weak self] result in
|
TunnelsManager.create { [weak self] result in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
if let error = result.error {
|
|
||||||
|
switch result {
|
||||||
|
case .failure(let error):
|
||||||
ErrorPresenter.showErrorAlert(error: error, from: nil)
|
ErrorPresenter.showErrorAlert(error: error, from: nil)
|
||||||
return
|
case .success(let tunnelsManager):
|
||||||
|
let statusMenu = StatusMenu(tunnelsManager: tunnelsManager)
|
||||||
|
statusMenu.windowDelegate = self
|
||||||
|
|
||||||
|
let statusItemController = StatusItemController()
|
||||||
|
statusItemController.statusItem.menu = statusMenu
|
||||||
|
|
||||||
|
let tunnelsTracker = TunnelsTracker(tunnelsManager: tunnelsManager)
|
||||||
|
tunnelsTracker.statusMenu = statusMenu
|
||||||
|
tunnelsTracker.statusItemController = statusItemController
|
||||||
|
|
||||||
|
self.tunnelsManager = tunnelsManager
|
||||||
|
self.tunnelsTracker = tunnelsTracker
|
||||||
|
self.statusItemController = statusItemController
|
||||||
|
|
||||||
|
if !isLaunchedAtLogin {
|
||||||
|
self.showManageTunnelsWindow(completion: nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let tunnelsManager: TunnelsManager = result.value!
|
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows: Bool) -> Bool {
|
||||||
|
if let appleEvent = NSAppleEventManager.shared().currentAppleEvent {
|
||||||
|
if LaunchedAtLoginDetector.isReopenedByLoginItemHelper(reopenAppleEvent: appleEvent) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hasVisibleWindows {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
showManageTunnelsWindow(completion: nil)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
let statusMenu = StatusMenu(tunnelsManager: tunnelsManager)
|
@objc func confirmAndQuit() {
|
||||||
statusMenu.windowDelegate = self
|
let alert = NSAlert()
|
||||||
|
alert.messageText = tr("macConfirmAndQuitAlertMessage")
|
||||||
|
if let currentTunnel = tunnelsTracker?.currentTunnel, currentTunnel.status == .active || currentTunnel.status == .activating {
|
||||||
|
alert.informativeText = tr(format: "macConfirmAndQuitInfoWithActiveTunnel (%@)", currentTunnel.name)
|
||||||
|
} else {
|
||||||
|
alert.informativeText = tr("macConfirmAndQuitAlertInfo")
|
||||||
|
}
|
||||||
|
alert.addButton(withTitle: tr("macConfirmAndQuitAlertCloseWindow"))
|
||||||
|
alert.addButton(withTitle: tr("macConfirmAndQuitAlertQuitWireGuard"))
|
||||||
|
|
||||||
let statusItemController = StatusItemController()
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
statusItemController.statusItem.menu = statusMenu
|
if let manageWindow = manageTunnelsWindowObject {
|
||||||
|
manageWindow.orderFront(self)
|
||||||
let tunnelsTracker = TunnelsTracker(tunnelsManager: tunnelsManager)
|
alert.beginSheetModal(for: manageWindow) { response in
|
||||||
tunnelsTracker.statusMenu = statusMenu
|
switch response {
|
||||||
tunnelsTracker.statusItemController = statusItemController
|
case .alertFirstButtonReturn:
|
||||||
|
manageWindow.close()
|
||||||
self.tunnelsManager = tunnelsManager
|
case .alertSecondButtonReturn:
|
||||||
self.tunnelsTracker = tunnelsTracker
|
NSApp.terminate(nil)
|
||||||
self.statusItemController = statusItemController
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,12 +130,102 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
NSApp.terminate(nil)
|
NSApp.terminate(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
|
||||||
|
guard let currentTunnel = tunnelsTracker?.currentTunnel, currentTunnel.status == .active || currentTunnel.status == .activating else {
|
||||||
|
return .terminateNow
|
||||||
|
}
|
||||||
|
guard let appleEvent = NSAppleEventManager.shared().currentAppleEvent else {
|
||||||
|
return .terminateNow
|
||||||
|
}
|
||||||
|
guard MacAppStoreUpdateDetector.isUpdatingFromMacAppStore(quitAppleEvent: appleEvent) else {
|
||||||
|
return .terminateNow
|
||||||
|
}
|
||||||
|
let alert = NSAlert()
|
||||||
|
alert.messageText = tr("macAppStoreUpdatingAlertMessage")
|
||||||
|
if currentTunnel.isActivateOnDemandEnabled {
|
||||||
|
alert.informativeText = tr(format: "macAppStoreUpdatingAlertInfoWithOnDemand (%@)", currentTunnel.name)
|
||||||
|
} else {
|
||||||
|
alert.informativeText = tr(format: "macAppStoreUpdatingAlertInfoWithoutOnDemand (%@)", currentTunnel.name)
|
||||||
|
}
|
||||||
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
|
if let manageWindow = manageTunnelsWindowObject {
|
||||||
|
alert.beginSheetModal(for: manageWindow) { _ in }
|
||||||
|
} else {
|
||||||
|
alert.runModal()
|
||||||
|
}
|
||||||
|
return .terminateCancel
|
||||||
|
}
|
||||||
|
|
||||||
|
func applicationShouldTerminateAfterLastWindowClosed(_ application: NSApplication) -> Bool {
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(200)) { [weak self] in
|
||||||
|
self?.setDockIconAndMainMenuVisibility(isVisible: false)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setDockIconAndMainMenuVisibility(isVisible: Bool, completion: (() -> Void)? = nil) {
|
||||||
|
let currentActivationPolicy = NSApp.activationPolicy()
|
||||||
|
let newActivationPolicy: NSApplication.ActivationPolicy = isVisible ? .regular : .accessory
|
||||||
|
guard currentActivationPolicy != newActivationPolicy else {
|
||||||
|
if newActivationPolicy == .regular {
|
||||||
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
|
}
|
||||||
|
completion?()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if newActivationPolicy == .regular && NSApp.isActive {
|
||||||
|
// To workaround a possible AppKit bug that causes the main menu to become unresponsive,
|
||||||
|
// we should deactivate the app first and then set the activation policy.
|
||||||
|
// NSApp.deactivate() doesn't always deactivate the app, so we instead use
|
||||||
|
// setActivationPolicy(.prohibited).
|
||||||
|
onAppDeactivation = {
|
||||||
|
NSApp.setActivationPolicy(.regular)
|
||||||
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
|
completion?()
|
||||||
|
}
|
||||||
|
NSApp.setActivationPolicy(.prohibited)
|
||||||
|
} else {
|
||||||
|
NSApp.setActivationPolicy(newActivationPolicy)
|
||||||
|
if newActivationPolicy == .regular {
|
||||||
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
|
}
|
||||||
|
completion?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func applicationDidResignActive(_ notification: Notification) {
|
||||||
|
onAppDeactivation?()
|
||||||
|
onAppDeactivation = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AppDelegate {
|
||||||
|
@objc func aboutClicked() {
|
||||||
|
var appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown"
|
||||||
|
if let appBuild = Bundle.main.infoDictionary?["CFBundleVersion"] as? String {
|
||||||
|
appVersion += " (\(appBuild))"
|
||||||
|
}
|
||||||
|
let appVersionString = [
|
||||||
|
tr(format: "macAppVersion (%@)", appVersion),
|
||||||
|
tr(format: "macGoBackendVersion (%@)", WIREGUARD_GO_VERSION)
|
||||||
|
].joined(separator: "\n")
|
||||||
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
|
NSApp.orderFrontStandardAboutPanel(options: [
|
||||||
|
.applicationVersion: appVersionString,
|
||||||
|
.version: ""
|
||||||
|
])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AppDelegate: StatusMenuWindowDelegate {
|
extension AppDelegate: StatusMenuWindowDelegate {
|
||||||
func manageTunnelsWindow() -> NSWindow {
|
func showManageTunnelsWindow(completion: ((NSWindow?) -> Void)?) {
|
||||||
|
guard let tunnelsManager = tunnelsManager else {
|
||||||
|
completion?(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
if manageTunnelsWindowObject == nil {
|
if manageTunnelsWindowObject == nil {
|
||||||
manageTunnelsRootVC = ManageTunnelsRootViewController(tunnelsManager: tunnelsManager!)
|
manageTunnelsRootVC = ManageTunnelsRootViewController(tunnelsManager: tunnelsManager)
|
||||||
let window = NSWindow(contentViewController: manageTunnelsRootVC!)
|
let window = NSWindow(contentViewController: manageTunnelsRootVC!)
|
||||||
window.title = tr("macWindowTitleManageTunnels")
|
window.title = tr("macWindowTitleManageTunnels")
|
||||||
window.setContentSize(NSSize(width: 800, height: 480))
|
window.setContentSize(NSSize(width: 800, height: 480))
|
||||||
@@ -81,7 +233,10 @@ extension AppDelegate: StatusMenuWindowDelegate {
|
|||||||
manageTunnelsWindowObject = window
|
manageTunnelsWindowObject = window
|
||||||
tunnelsTracker?.manageTunnelsRootVC = manageTunnelsRootVC
|
tunnelsTracker?.manageTunnelsRootVC = manageTunnelsRootVC
|
||||||
}
|
}
|
||||||
return manageTunnelsWindowObject!
|
setDockIconAndMainMenuVisibility(isVisible: true) { [weak manageTunnelsWindowObject] in
|
||||||
|
manageTunnelsWindowObject?.makeKeyAndOrderFront(self)
|
||||||
|
completion?(manageTunnelsWindowObject)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,18 +5,6 @@ import Cocoa
|
|||||||
|
|
||||||
class Application: NSApplication {
|
class Application: NSApplication {
|
||||||
|
|
||||||
private let characterKeyCommands = [
|
|
||||||
"x": #selector(NSText.cut(_:)),
|
|
||||||
"c": #selector(NSText.copy(_:)),
|
|
||||||
"v": #selector(NSText.paste(_:)),
|
|
||||||
"z": #selector(UndoActionRespondable.undo(_:)),
|
|
||||||
"a": #selector(NSResponder.selectAll(_:)),
|
|
||||||
"Z": #selector(UndoActionRespondable.redo(_:)),
|
|
||||||
"w": #selector(NSWindow.performClose(_:)),
|
|
||||||
"m": #selector(NSWindow.performMiniaturize(_:)),
|
|
||||||
"q": #selector(AppDelegate.quit)
|
|
||||||
]
|
|
||||||
|
|
||||||
private var appDelegate: AppDelegate? //swiftlint:disable:this weak_delegate
|
private var appDelegate: AppDelegate? //swiftlint:disable:this weak_delegate
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
@@ -28,21 +16,4 @@ class Application: NSApplication {
|
|||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
override func sendEvent(_ event: NSEvent) {
|
|
||||||
let modifierFlags = event.modifierFlags.rawValue & NSEvent.ModifierFlags.deviceIndependentFlagsMask.rawValue
|
|
||||||
|
|
||||||
if event.type == .keyDown,
|
|
||||||
(modifierFlags == NSEvent.ModifierFlags.command.rawValue || modifierFlags == NSEvent.ModifierFlags.command.rawValue | NSEvent.ModifierFlags.shift.rawValue),
|
|
||||||
let selector = characterKeyCommands[event.charactersIgnoringModifiers ?? ""] {
|
|
||||||
sendAction(selector, to: nil, from: self)
|
|
||||||
} else {
|
|
||||||
super.sendEvent(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc protocol UndoActionRespondable {
|
|
||||||
func undo(_ sender: AnyObject)
|
|
||||||
func redo(_ sender: AnyObject)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,4 +65,4 @@
|
|||||||
"version" : 1,
|
"version" : 1,
|
||||||
"author" : "xcode"
|
"author" : "xcode"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 995 B After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.9 KiB |
@@ -0,0 +1,28 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
class LaunchedAtLoginDetector {
|
||||||
|
static let launchCode = "LaunchedByWireGuardLoginItemHelper"
|
||||||
|
|
||||||
|
static func isLaunchedAtLogin(openAppleEvent: NSAppleEventDescriptor) -> Bool {
|
||||||
|
guard isOpenEvent(openAppleEvent) else { return false }
|
||||||
|
guard let propData = openAppleEvent.paramDescriptor(forKeyword: keyAEPropData) else { return false }
|
||||||
|
return propData.stringValue == launchCode
|
||||||
|
}
|
||||||
|
|
||||||
|
static func isReopenedByLoginItemHelper(reopenAppleEvent: NSAppleEventDescriptor) -> Bool {
|
||||||
|
guard isReopenEvent(reopenAppleEvent) else { return false }
|
||||||
|
guard let propData = reopenAppleEvent.paramDescriptor(forKeyword: keyAEPropData) else { return false }
|
||||||
|
return propData.stringValue == launchCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func isOpenEvent(_ event: NSAppleEventDescriptor) -> Bool {
|
||||||
|
return event.eventClass == kCoreEventClass && event.eventID == kAEOpenApplication
|
||||||
|
}
|
||||||
|
|
||||||
|
private func isReopenEvent(_ event: NSAppleEventDescriptor) -> Bool {
|
||||||
|
return event.eventClass == kCoreEventClass && event.eventID == kAEReopenApplication
|
||||||
|
}
|
||||||
@@ -19,9 +19,9 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.0</string>
|
<string>$(VERSION_NAME)</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>$(VERSION_ID)</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
@@ -30,5 +30,7 @@
|
|||||||
<string>NSApplication</string>
|
<string>NSApplication</string>
|
||||||
<key>LSBackgroundOnly</key>
|
<key>LSBackgroundOnly</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>com.wireguard.macos.app_id</key>
|
||||||
|
<string>$(APP_ID_MACOS)</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -5,12 +5,13 @@
|
|||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
NSURL *bundleURL = [NSBundle.mainBundle bundleURL];
|
NSString *appIdInfoDictionaryKey = @"com.wireguard.macos.app_id";
|
||||||
|
NSString *appId = [NSBundle.mainBundle objectForInfoDictionaryKey:appIdInfoDictionaryKey];
|
||||||
|
|
||||||
// From <path>/WireGuard.app/Contents/Library/LoginItems/WireGuardLoginItemHelper.app, derive <path>/WireGuard.app
|
NSString *launchCode = @"LaunchedByWireGuardLoginItemHelper";
|
||||||
for (int i = 0; i < 4; ++i)
|
NSAppleEventDescriptor *paramDescriptor = [NSAppleEventDescriptor descriptorWithString:launchCode];
|
||||||
bundleURL = [bundleURL URLByDeletingLastPathComponent];
|
|
||||||
|
|
||||||
[NSWorkspace.sharedWorkspace launchApplication:[bundleURL path]];
|
[NSWorkspace.sharedWorkspace launchAppWithBundleIdentifier:appId options:NSWorkspaceLaunchWithoutActivation
|
||||||
|
additionalEventParamDescriptor:paramDescriptor launchIdentifier:NULL];
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
class MacAppStoreUpdateDetector {
|
||||||
|
static func isUpdatingFromMacAppStore(quitAppleEvent: NSAppleEventDescriptor) -> Bool {
|
||||||
|
guard isQuitEvent(quitAppleEvent) else { return false }
|
||||||
|
guard let senderPIDDescriptor = quitAppleEvent.attributeDescriptor(forKeyword: keySenderPIDAttr) else { return false }
|
||||||
|
let pid = senderPIDDescriptor.int32Value
|
||||||
|
guard let executablePath = getExecutablePath(from: pid) else { return false }
|
||||||
|
wg_log(.debug, message: "aevt/quit Apple event received from: \(executablePath)")
|
||||||
|
if executablePath.hasPrefix("/System/Library/") {
|
||||||
|
let executableName = URL(fileURLWithPath: executablePath, isDirectory: false).lastPathComponent
|
||||||
|
return executableName == "com.apple.CommerceKit.StoreAEService"
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func isQuitEvent(_ event: NSAppleEventDescriptor) -> Bool {
|
||||||
|
return event.eventClass == kCoreEventClass && event.eventID == kAEQuitApplication
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getExecutablePath(from pid: pid_t) -> String? {
|
||||||
|
let bufferSize = Int(PATH_MAX)
|
||||||
|
var buffer = Data(capacity: bufferSize)
|
||||||
|
return buffer.withUnsafeMutableBytes { (ptr: UnsafeMutableRawBufferPointer) -> String? in
|
||||||
|
if let basePtr = ptr.baseAddress {
|
||||||
|
let byteCount = proc_pidpath(pid, basePtr, UInt32(bufferSize))
|
||||||
|
return byteCount > 0 ? String(cString: basePtr.bindMemory(to: CChar.self, capacity: bufferSize)) : nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright © 2018 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
// swiftlint:disable colon
|
||||||
|
|
||||||
|
class MainMenu: NSMenu {
|
||||||
|
init() {
|
||||||
|
super.init(title: "")
|
||||||
|
addSubmenu(createApplicationMenu())
|
||||||
|
addSubmenu(createFileMenu())
|
||||||
|
addSubmenu(createEditMenu())
|
||||||
|
addSubmenu(createTunnelMenu())
|
||||||
|
addSubmenu(createWindowMenu())
|
||||||
|
}
|
||||||
|
|
||||||
|
required init(coder decoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
private func addSubmenu(_ menu: NSMenu) {
|
||||||
|
let menuItem = self.addItem(withTitle: "", action: nil, keyEquivalent: "")
|
||||||
|
self.setSubmenu(menu, for: menuItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func createApplicationMenu() -> NSMenu {
|
||||||
|
let menu = NSMenu()
|
||||||
|
|
||||||
|
let aboutMenuItem = menu.addItem(withTitle: tr("macMenuAbout"),
|
||||||
|
action: #selector(AppDelegate.aboutClicked), keyEquivalent: "")
|
||||||
|
aboutMenuItem.target = NSApp.delegate
|
||||||
|
|
||||||
|
menu.addItem(NSMenuItem.separator())
|
||||||
|
|
||||||
|
menu.addItem(withTitle: tr("macMenuViewLog"),
|
||||||
|
action: #selector(TunnelsListTableViewController.handleViewLogAction), keyEquivalent: "")
|
||||||
|
|
||||||
|
menu.addItem(NSMenuItem.separator())
|
||||||
|
|
||||||
|
let hideMenuItem = menu.addItem(withTitle: tr("macMenuHideApp"),
|
||||||
|
action: #selector(NSApplication.hide), keyEquivalent: "h")
|
||||||
|
hideMenuItem.target = NSApp
|
||||||
|
let hideOthersMenuItem = menu.addItem(withTitle: tr("macMenuHideOtherApps"),
|
||||||
|
action: #selector(NSApplication.hideOtherApplications), keyEquivalent: "h")
|
||||||
|
hideOthersMenuItem.keyEquivalentModifierMask = [.command, .option]
|
||||||
|
hideOthersMenuItem.target = NSApp
|
||||||
|
let showAllMenuItem = menu.addItem(withTitle: tr("macMenuShowAllApps"),
|
||||||
|
action: #selector(NSApplication.unhideAllApplications), keyEquivalent: "")
|
||||||
|
showAllMenuItem.target = NSApp
|
||||||
|
|
||||||
|
menu.addItem(NSMenuItem.separator())
|
||||||
|
|
||||||
|
menu.addItem(withTitle: tr("macMenuQuit"),
|
||||||
|
action: #selector(AppDelegate.confirmAndQuit), keyEquivalent: "q")
|
||||||
|
|
||||||
|
return menu
|
||||||
|
}
|
||||||
|
|
||||||
|
private func createFileMenu() -> NSMenu {
|
||||||
|
let menu = NSMenu(title: tr("macMenuFile"))
|
||||||
|
|
||||||
|
menu.addItem(withTitle: tr("macMenuAddEmptyTunnel"),
|
||||||
|
action: #selector(TunnelsListTableViewController.handleAddEmptyTunnelAction), keyEquivalent: "n")
|
||||||
|
menu.addItem(withTitle: tr("macMenuImportTunnels"),
|
||||||
|
action: #selector(TunnelsListTableViewController.handleImportTunnelAction), keyEquivalent: "o")
|
||||||
|
|
||||||
|
menu.addItem(NSMenuItem.separator())
|
||||||
|
|
||||||
|
menu.addItem(withTitle: tr("macMenuExportTunnels"),
|
||||||
|
action: #selector(TunnelsListTableViewController.handleExportTunnelsAction), keyEquivalent: "")
|
||||||
|
|
||||||
|
menu.addItem(NSMenuItem.separator())
|
||||||
|
|
||||||
|
menu.addItem(withTitle: tr("macMenuCloseWindow"), action: #selector(NSWindow.performClose(_:)), keyEquivalent:"w")
|
||||||
|
|
||||||
|
return menu
|
||||||
|
}
|
||||||
|
|
||||||
|
private func createEditMenu() -> NSMenu {
|
||||||
|
let menu = NSMenu(title: tr("macMenuEdit"))
|
||||||
|
|
||||||
|
menu.addItem(withTitle: "", action: #selector(UndoActionRespondable.undo(_:)), keyEquivalent:"z")
|
||||||
|
menu.addItem(withTitle: "", action: #selector(UndoActionRespondable.redo(_:)), keyEquivalent:"Z")
|
||||||
|
|
||||||
|
menu.addItem(NSMenuItem.separator())
|
||||||
|
|
||||||
|
menu.addItem(withTitle: tr("macMenuCut"), action: #selector(NSText.cut(_:)), keyEquivalent:"x")
|
||||||
|
menu.addItem(withTitle: tr("macMenuCopy"), action: #selector(NSText.copy(_:)), keyEquivalent:"c")
|
||||||
|
menu.addItem(withTitle: tr("macMenuPaste"), action: #selector(NSText.paste(_:)), keyEquivalent:"v")
|
||||||
|
|
||||||
|
menu.addItem(NSMenuItem.separator())
|
||||||
|
|
||||||
|
menu.addItem(withTitle: tr("macMenuSelectAll"), action: #selector(NSText.selectAll(_:)), keyEquivalent:"a")
|
||||||
|
|
||||||
|
return menu
|
||||||
|
}
|
||||||
|
|
||||||
|
private func createTunnelMenu() -> NSMenu {
|
||||||
|
let menu = NSMenu(title: tr("macMenuTunnel"))
|
||||||
|
|
||||||
|
menu.addItem(withTitle: tr("macMenuToggleStatus"), action: #selector(TunnelDetailTableViewController.handleToggleActiveStatusAction), keyEquivalent:"t")
|
||||||
|
|
||||||
|
menu.addItem(NSMenuItem.separator())
|
||||||
|
|
||||||
|
menu.addItem(withTitle: tr("macMenuEditTunnel"), action: #selector(TunnelDetailTableViewController.handleEditTunnelAction), keyEquivalent:"e")
|
||||||
|
menu.addItem(withTitle: tr("macMenuDeleteSelected"), action: #selector(TunnelsListTableViewController.handleRemoveTunnelAction), keyEquivalent: "")
|
||||||
|
|
||||||
|
return menu
|
||||||
|
}
|
||||||
|
|
||||||
|
private func createWindowMenu() -> NSMenu {
|
||||||
|
let menu = NSMenu(title: tr("macMenuWindow"))
|
||||||
|
|
||||||
|
menu.addItem(withTitle: tr("macMenuMinimize"), action: #selector(NSWindow.performMiniaturize(_:)), keyEquivalent:"m")
|
||||||
|
menu.addItem(withTitle: tr("macMenuZoom"), action: #selector(NSWindow.performZoom(_:)), keyEquivalent:"")
|
||||||
|
|
||||||
|
menu.addItem(NSMenuItem.separator())
|
||||||
|
|
||||||
|
let fullScreenMenuItem = menu.addItem(withTitle: "", action: #selector(NSWindow.toggleFullScreen(_:)), keyEquivalent:"f")
|
||||||
|
fullScreenMenuItem.keyEquivalentModifierMask = [.command, .control]
|
||||||
|
|
||||||
|
return menu
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc protocol UndoActionRespondable {
|
||||||
|
func undo(_ sender: AnyObject)
|
||||||
|
func redo(_ sender: AnyObject)
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
protocol StatusMenuWindowDelegate: class {
|
protocol StatusMenuWindowDelegate: class {
|
||||||
func manageTunnelsWindow() -> NSWindow
|
func showManageTunnelsWindow(completion: ((NSWindow?) -> Void)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
class StatusMenu: NSMenu {
|
class StatusMenu: NSMenu {
|
||||||
@@ -127,8 +127,8 @@ class StatusMenu: NSMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func addApplicationItems() {
|
func addApplicationItems() {
|
||||||
let aboutItem = NSMenuItem(title: tr("macMenuAbout"), action: #selector(aboutClicked), keyEquivalent: "")
|
let aboutItem = NSMenuItem(title: tr("macMenuAbout"), action: #selector(AppDelegate.aboutClicked), keyEquivalent: "")
|
||||||
aboutItem.target = self
|
aboutItem.target = NSApp.delegate
|
||||||
addItem(aboutItem)
|
addItem(aboutItem)
|
||||||
let quitItem = NSMenuItem(title: tr("macMenuQuit"), action: #selector(AppDelegate.quit), keyEquivalent: "")
|
let quitItem = NSMenuItem(title: tr("macMenuQuit"), action: #selector(AppDelegate.quit), keyEquivalent: "")
|
||||||
quitItem.target = NSApp.delegate
|
quitItem.target = NSApp.delegate
|
||||||
@@ -151,32 +151,16 @@ class StatusMenu: NSMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc func manageTunnelsClicked() {
|
@objc func manageTunnelsClicked() {
|
||||||
NSApp.activate(ignoringOtherApps: true)
|
windowDelegate?.showManageTunnelsWindow(completion: nil)
|
||||||
guard let manageTunnelsWindow = windowDelegate?.manageTunnelsWindow() else { return }
|
|
||||||
manageTunnelsWindow.makeKeyAndOrderFront(self)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func importTunnelsClicked() {
|
@objc func importTunnelsClicked() {
|
||||||
NSApp.activate(ignoringOtherApps: true)
|
windowDelegate?.showManageTunnelsWindow { [weak self] manageTunnelsWindow in
|
||||||
guard let manageTunnelsWindow = windowDelegate?.manageTunnelsWindow() else { return }
|
guard let self = self else { return }
|
||||||
manageTunnelsWindow.makeKeyAndOrderFront(self)
|
guard let manageTunnelsWindow = manageTunnelsWindow else { return }
|
||||||
ImportPanelPresenter.presentImportPanel(tunnelsManager: tunnelsManager, sourceVC: manageTunnelsWindow.contentViewController)
|
ImportPanelPresenter.presentImportPanel(tunnelsManager: self.tunnelsManager,
|
||||||
}
|
sourceVC: manageTunnelsWindow.contentViewController)
|
||||||
|
|
||||||
@objc func aboutClicked() {
|
|
||||||
var appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown"
|
|
||||||
if let appBuild = Bundle.main.infoDictionary?["CFBundleVersion"] as? String {
|
|
||||||
appVersion += " (\(appBuild))"
|
|
||||||
}
|
}
|
||||||
let appVersionString = [
|
|
||||||
tr(format: "macAppVersion (%@)", appVersion),
|
|
||||||
tr(format: "macGoBackendVersion (%@)", WIREGUARD_GO_VERSION)
|
|
||||||
].joined(separator: "\n")
|
|
||||||
NSApp.activate(ignoringOtherApps: true)
|
|
||||||
NSApp.orderFrontStandardAboutPanel(options: [
|
|
||||||
.applicationVersion: appVersionString,
|
|
||||||
.version: ""
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,25 @@
|
|||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
class LogViewCell: NSTextField {
|
class LogViewCell: NSTableCellView {
|
||||||
|
var text: String = "" {
|
||||||
|
didSet { textField?.stringValue = text }
|
||||||
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
isSelectable = false
|
|
||||||
isEditable = false
|
let textField = NSTextField(wrappingLabelWithString: "")
|
||||||
isBordered = false
|
addSubview(textField)
|
||||||
backgroundColor = .clear
|
textField.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
textField.leadingAnchor.constraint(equalTo: self.leadingAnchor),
|
||||||
|
textField.trailingAnchor.constraint(equalTo: self.trailingAnchor),
|
||||||
|
textField.topAnchor.constraint(equalTo: self.topAnchor),
|
||||||
|
textField.bottomAnchor.constraint(equalTo: self.bottomAnchor)
|
||||||
|
])
|
||||||
|
|
||||||
|
self.textField = textField
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
@@ -17,19 +29,19 @@ class LogViewCell: NSTextField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func prepareForReuse() {
|
override func prepareForReuse() {
|
||||||
stringValue = ""
|
textField?.stringValue = ""
|
||||||
preferredMaxLayoutWidth = 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LogViewTimestampCell: LogViewCell {
|
class LogViewTimestampCell: LogViewCell {
|
||||||
override init() {
|
override init() {
|
||||||
super.init()
|
super.init()
|
||||||
maximumNumberOfLines = 1
|
if let textField = textField {
|
||||||
lineBreakMode = .byClipping
|
textField.maximumNumberOfLines = 1
|
||||||
preferredMaxLayoutWidth = 0
|
textField.lineBreakMode = .byClipping
|
||||||
setContentCompressionResistancePriority(.defaultHigh, for: .vertical)
|
textField.setContentCompressionResistancePriority(.defaultHigh, for: .vertical)
|
||||||
setContentHuggingPriority(.defaultLow, for: .vertical)
|
textField.setContentHuggingPriority(.defaultLow, for: .vertical)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
@@ -40,10 +52,12 @@ class LogViewTimestampCell: LogViewCell {
|
|||||||
class LogViewMessageCell: LogViewCell {
|
class LogViewMessageCell: LogViewCell {
|
||||||
override init() {
|
override init() {
|
||||||
super.init()
|
super.init()
|
||||||
maximumNumberOfLines = 0
|
if let textField = textField {
|
||||||
lineBreakMode = .byWordWrapping
|
textField.maximumNumberOfLines = 0
|
||||||
setContentCompressionResistancePriority(.required, for: .vertical)
|
textField.lineBreakMode = .byWordWrapping
|
||||||
setContentHuggingPriority(.required, for: .vertical)
|
textField.setContentCompressionResistancePriority(.required, for: .vertical)
|
||||||
|
textField.setContentHuggingPriority(.required, for: .vertical)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
|
|||||||
@@ -146,7 +146,8 @@ class LogViewController: NSViewController {
|
|||||||
])
|
])
|
||||||
|
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
containerView.widthAnchor.constraint(equalToConstant: 640),
|
containerView.widthAnchor.constraint(greaterThanOrEqualToConstant: 640),
|
||||||
|
containerView.widthAnchor.constraint(lessThanOrEqualToConstant: 1200),
|
||||||
containerView.heightAnchor.constraint(greaterThanOrEqualToConstant: 240)
|
containerView.heightAnchor.constraint(greaterThanOrEqualToConstant: 240)
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -187,6 +188,20 @@ class LogViewController: NSViewController {
|
|||||||
RunLoop.main.add(timer, forMode: .common)
|
RunLoop.main.add(timer, forMode: .common)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func stopUpdatingLogEntries() {
|
||||||
|
updateLogEntriesTimer?.invalidate()
|
||||||
|
updateLogEntriesTimer = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillAppear() {
|
||||||
|
view.window?.setFrameAutosaveName(NSWindow.FrameAutosaveName("LogWindow"))
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillDisappear() {
|
||||||
|
super.viewWillDisappear()
|
||||||
|
stopUpdatingLogEntries()
|
||||||
|
}
|
||||||
|
|
||||||
@objc func saveClicked() {
|
@objc func saveClicked() {
|
||||||
let savePanel = NSSavePanel()
|
let savePanel = NSSavePanel()
|
||||||
savePanel.prompt = tr("macSheetButtonExportLog")
|
savePanel.prompt = tr("macSheetButtonExportLog")
|
||||||
@@ -210,7 +225,8 @@ class LogViewController: NSViewController {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
DispatchQueue.main.async { [weak self] in
|
DispatchQueue.main.async { [weak self] in
|
||||||
self?.dismiss(self)
|
guard let self = self else { return }
|
||||||
|
self.presentingViewController?.dismiss(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,7 +234,7 @@ class LogViewController: NSViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc func closeClicked() {
|
@objc func closeClicked() {
|
||||||
dismiss(self)
|
presentingViewController?.dismiss(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func copy(_ sender: Any?) {
|
@objc func copy(_ sender: Any?) {
|
||||||
@@ -239,15 +255,20 @@ extension LogViewController: NSTableViewDelegate {
|
|||||||
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
|
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
|
||||||
if LogColumn.time.isRepresenting(tableColumn: tableColumn) {
|
if LogColumn.time.isRepresenting(tableColumn: tableColumn) {
|
||||||
let cell: LogViewTimestampCell = tableView.dequeueReusableCell()
|
let cell: LogViewTimestampCell = tableView.dequeueReusableCell()
|
||||||
cell.stringValue = logEntries[row].timestamp
|
cell.text = logEntries[row].timestamp
|
||||||
return cell
|
return cell
|
||||||
} else if LogColumn.logMessage.isRepresenting(tableColumn: tableColumn) {
|
} else if LogColumn.logMessage.isRepresenting(tableColumn: tableColumn) {
|
||||||
let cell: LogViewMessageCell = tableView.dequeueReusableCell()
|
let cell: LogViewMessageCell = tableView.dequeueReusableCell()
|
||||||
cell.stringValue = logEntries[row].message
|
cell.text = logEntries[row].message
|
||||||
cell.preferredMaxLayoutWidth = tableColumn?.width ?? 0
|
|
||||||
return cell
|
return cell
|
||||||
} else {
|
} else {
|
||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension LogViewController {
|
||||||
|
override func cancelOperation(_ sender: Any?) {
|
||||||
|
closeClicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -117,25 +117,19 @@ extension ManageTunnelsRootViewController: TunnelsListTableViewControllerDelegat
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension ManageTunnelsRootViewController {
|
extension ManageTunnelsRootViewController {
|
||||||
override func keyDown(with event: NSEvent) {
|
override func supplementalTarget(forAction action: Selector, sender: Any?) -> Any? {
|
||||||
let modifierFlags = event.modifierFlags.rawValue & NSEvent.ModifierFlags.deviceIndependentFlagsMask.rawValue
|
switch action {
|
||||||
let isCmdOrCmdShiftDown = (modifierFlags == NSEvent.ModifierFlags.command.rawValue || modifierFlags == NSEvent.ModifierFlags.command.rawValue | NSEvent.ModifierFlags.shift.rawValue)
|
case #selector(TunnelsListTableViewController.handleViewLogAction),
|
||||||
|
#selector(TunnelsListTableViewController.handleAddEmptyTunnelAction),
|
||||||
if event.specialKey == .delete {
|
#selector(TunnelsListTableViewController.handleImportTunnelAction),
|
||||||
tunnelsListVC?.handleRemoveTunnelAction()
|
#selector(TunnelsListTableViewController.handleExportTunnelsAction),
|
||||||
} else if isCmdOrCmdShiftDown {
|
#selector(TunnelsListTableViewController.handleRemoveTunnelAction):
|
||||||
switch event.charactersIgnoringModifiers {
|
return tunnelsListVC
|
||||||
case "n":
|
case #selector(TunnelDetailTableViewController.handleToggleActiveStatusAction),
|
||||||
tunnelsListVC?.handleAddEmptyTunnelAction()
|
#selector(TunnelDetailTableViewController.handleEditTunnelAction):
|
||||||
case "o":
|
return tunnelDetailVC
|
||||||
tunnelsListVC?.handleImportTunnelAction()
|
default:
|
||||||
case "t":
|
return super.supplementalTarget(forAction: action, sender: sender)
|
||||||
tunnelDetailVC?.handleToggleActiveStatusAction()
|
|
||||||
case "e":
|
|
||||||
tunnelDetailVC?.handleEditTunnelAction()
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,9 +101,14 @@ class TunnelDetailTableViewController: NSViewController {
|
|||||||
super.init(nibName: nil, bundle: nil)
|
super.init(nibName: nil, bundle: nil)
|
||||||
updateTableViewModelRowsBySection()
|
updateTableViewModelRowsBySection()
|
||||||
updateTableViewModelRows()
|
updateTableViewModelRows()
|
||||||
updateStatus()
|
|
||||||
statusObservationToken = tunnel.observe(\TunnelContainer.status) { [weak self] _, _ in
|
statusObservationToken = tunnel.observe(\TunnelContainer.status) { [weak self] _, _ in
|
||||||
self?.updateStatus()
|
guard let self = self else { return }
|
||||||
|
if tunnel.status == .active {
|
||||||
|
self.startUpdatingRuntimeConfiguration()
|
||||||
|
} else if tunnel.status == .inactive {
|
||||||
|
self.reloadRuntimeConfiguration()
|
||||||
|
self.stopUpdatingRuntimeConfiguration()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,15 +205,6 @@ class TunnelDetailTableViewController: NSViewController {
|
|||||||
tableViewModelRows = tableViewModelRowsBySection.flatMap { $0.filter { $0.isVisible }.map { $0.modelRow } }
|
tableViewModelRows = tableViewModelRowsBySection.flatMap { $0.filter { $0.isVisible }.map { $0.modelRow } }
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateStatus() {
|
|
||||||
if tunnel.status == .active {
|
|
||||||
startUpdatingRuntimeConfiguration()
|
|
||||||
} else if tunnel.status == .inactive {
|
|
||||||
reloadRuntimeConfiguration()
|
|
||||||
stopUpdatingRuntimeConfiguration()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func handleEditTunnelAction() {
|
@objc func handleEditTunnelAction() {
|
||||||
PrivateDataConfirmation.confirmAccess(to: tr("macViewPrivateData")) { [weak self] in
|
PrivateDataConfirmation.confirmAccess(to: tr("macViewPrivateData")) { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
@@ -227,6 +223,12 @@ class TunnelDetailTableViewController: NSViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func viewWillAppear() {
|
||||||
|
if tunnel.status == .active {
|
||||||
|
startUpdatingRuntimeConfiguration()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override func viewWillDisappear() {
|
override func viewWillDisappear() {
|
||||||
super.viewWillDisappear()
|
super.viewWillDisappear()
|
||||||
if let tunnelEditVC = tunnelEditVC {
|
if let tunnelEditVC = tunnelEditVC {
|
||||||
@@ -503,7 +505,6 @@ extension TunnelDetailTableViewController: TunnelEditViewControllerDelegate {
|
|||||||
onDemandViewModel = ActivateOnDemandViewModel(tunnel: tunnel)
|
onDemandViewModel = ActivateOnDemandViewModel(tunnel: tunnel)
|
||||||
updateTableViewModelRowsBySection()
|
updateTableViewModelRowsBySection()
|
||||||
updateTableViewModelRows()
|
updateTableViewModelRows()
|
||||||
updateStatus()
|
|
||||||
tableView.reloadData()
|
tableView.reloadData()
|
||||||
self.tunnelEditVC = nil
|
self.tunnelEditVC = nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,6 +73,8 @@ class TunnelEditViewController: NSViewController {
|
|||||||
button.title = tr("macEditSave")
|
button.title = tr("macEditSave")
|
||||||
button.setButtonType(.momentaryPushIn)
|
button.setButtonType(.momentaryPushIn)
|
||||||
button.bezelStyle = .rounded
|
button.bezelStyle = .rounded
|
||||||
|
button.keyEquivalent = "s"
|
||||||
|
button.keyEquivalentModifierMask = [.command]
|
||||||
return button
|
return button
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -235,32 +237,34 @@ class TunnelEditViewController: NSViewController {
|
|||||||
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, onDemandOption: onDemandOption) { [weak self] error in
|
tunnelsManager.modify(tunnel: tunnel, tunnelConfiguration: tunnelConfiguration, onDemandOption: onDemandOption) { [weak self] error in
|
||||||
self?.setUserInteractionEnabled(true)
|
guard let self = self else { return }
|
||||||
|
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
|
||||||
}
|
}
|
||||||
self?.dismiss(self)
|
self.delegate?.tunnelSaved(tunnel: tunnel)
|
||||||
self?.delegate?.tunnelSaved(tunnel: tunnel)
|
self.presentingViewController?.dismiss(self)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// We're creating a new tunnel
|
// We're creating a new tunnel
|
||||||
self.tunnelsManager.add(tunnelConfiguration: tunnelConfiguration, onDemandOption: onDemandOption) { [weak self] result in
|
self.tunnelsManager.add(tunnelConfiguration: tunnelConfiguration, onDemandOption: onDemandOption) { [weak self] result in
|
||||||
self?.setUserInteractionEnabled(true)
|
guard let self = self else { return }
|
||||||
if let error = result.error {
|
self.setUserInteractionEnabled(true)
|
||||||
|
switch result {
|
||||||
|
case .failure(let error):
|
||||||
ErrorPresenter.showErrorAlert(error: error, from: self)
|
ErrorPresenter.showErrorAlert(error: error, from: self)
|
||||||
return
|
case .success(let tunnel):
|
||||||
|
self.delegate?.tunnelSaved(tunnel: tunnel)
|
||||||
|
self.presentingViewController?.dismiss(self)
|
||||||
}
|
}
|
||||||
let tunnel: TunnelContainer = result.value!
|
|
||||||
self?.dismiss(self)
|
|
||||||
self?.delegate?.tunnelSaved(tunnel: tunnel)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func handleDiscardAction() {
|
@objc func handleDiscardAction() {
|
||||||
delegate?.tunnelEditingCancelled()
|
delegate?.tunnelEditingCancelled()
|
||||||
dismiss(self)
|
presentingViewController?.dismiss(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateExcludePrivateIPsVisibility(singlePeerAllowedIPs: [String]?) {
|
func updateExcludePrivateIPsVisibility(singlePeerAllowedIPs: [String]?) {
|
||||||
@@ -287,3 +291,9 @@ class TunnelEditViewController: NSViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension TunnelEditViewController {
|
||||||
|
override func cancelOperation(_ sender: Any?) {
|
||||||
|
handleDiscardAction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -309,6 +309,23 @@ extension TunnelsListTableViewController: NSTableViewDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension TunnelsListTableViewController {
|
||||||
|
override func keyDown(with event: NSEvent) {
|
||||||
|
if event.specialKey == .delete {
|
||||||
|
handleRemoveTunnelAction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TunnelsListTableViewController: NSMenuItemValidation {
|
||||||
|
func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
|
||||||
|
if menuItem.action == #selector(TunnelsListTableViewController.handleRemoveTunnelAction) {
|
||||||
|
return !tableView.selectedRowIndexes.isEmpty
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class FillerButton: NSButton {
|
class FillerButton: NSButton {
|
||||||
override var intrinsicContentSize: NSSize {
|
override var intrinsicContentSize: NSSize {
|
||||||
return NSSize(width: NSView.noIntrinsicMetric, height: NSView.noIntrinsicMetric)
|
return NSSize(width: NSView.noIntrinsicMetric, height: NSView.noIntrinsicMetric)
|
||||||
|
|||||||
@@ -5,3 +5,8 @@
|
|||||||
#include "ringlogger.h"
|
#include "ringlogger.h"
|
||||||
#include "highlighter.h"
|
#include "highlighter.h"
|
||||||
#include "key.h"
|
#include "key.h"
|
||||||
|
|
||||||
|
#import "TargetConditionals.h"
|
||||||
|
#if TARGET_OS_OSX
|
||||||
|
#include <libproc.h>
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ enum ZipArchiveError: WireGuardAppError {
|
|||||||
case cantOpenInputZipFile
|
case cantOpenInputZipFile
|
||||||
case cantOpenOutputZipFileForWriting
|
case cantOpenOutputZipFileForWriting
|
||||||
case badArchive
|
case badArchive
|
||||||
|
case noTunnelsInZipArchive
|
||||||
|
|
||||||
var alertText: AlertText {
|
var alertText: AlertText {
|
||||||
switch self {
|
switch self {
|
||||||
@@ -16,6 +17,8 @@ enum ZipArchiveError: WireGuardAppError {
|
|||||||
return (tr("alertCantOpenOutputZipFileForWritingTitle"), tr("alertCantOpenOutputZipFileForWritingMessage"))
|
return (tr("alertCantOpenOutputZipFileForWritingTitle"), tr("alertCantOpenOutputZipFileForWritingMessage"))
|
||||||
case .badArchive:
|
case .badArchive:
|
||||||
return (tr("alertBadArchiveTitle"), tr("alertBadArchiveMessage"))
|
return (tr("alertBadArchiveTitle"), tr("alertBadArchiveMessage"))
|
||||||
|
case .noTunnelsInZipArchive:
|
||||||
|
return (tr("alertNoTunnelsInImportedZipArchiveTitle"), tr("alertNoTunnelsInImportedZipArchiveMessage"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,8 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
enum ZipImporterError: WireGuardAppError {
|
|
||||||
case noTunnelsInZipArchive
|
|
||||||
|
|
||||||
var alertText: AlertText {
|
|
||||||
return (tr("alertNoTunnelsInImportedZipArchiveTitle"), tr("alertNoTunnelsInImportedZipArchiveMessage"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ZipImporter {
|
class ZipImporter {
|
||||||
static func importConfigFiles(from url: URL, completion: @escaping (WireGuardResult<[TunnelConfiguration?]>) -> Void) {
|
static func importConfigFiles(from url: URL, completion: @escaping (Result<[TunnelConfiguration?], ZipArchiveError>) -> Void) {
|
||||||
DispatchQueue.global(qos: .userInitiated).async {
|
DispatchQueue.global(qos: .userInitiated).async {
|
||||||
var unarchivedFiles: [(fileBaseName: String, contents: Data)]
|
var unarchivedFiles: [(fileBaseName: String, contents: Data)]
|
||||||
do {
|
do {
|
||||||
@@ -28,9 +20,9 @@ class ZipImporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if unarchivedFiles.isEmpty {
|
if unarchivedFiles.isEmpty {
|
||||||
throw ZipImporterError.noTunnelsInZipArchive
|
throw ZipArchiveError.noTunnelsInZipArchive
|
||||||
}
|
}
|
||||||
} catch let error as WireGuardAppError {
|
} catch let error as ZipArchiveError {
|
||||||
DispatchQueue.main.async { completion(.failure(error)) }
|
DispatchQueue.main.async { completion(.failure(error)) }
|
||||||
return
|
return
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
private var handle: Int32?
|
private var handle: Int32?
|
||||||
private var networkMonitor: NWPathMonitor?
|
private var networkMonitor: NWPathMonitor?
|
||||||
private var ifname: String?
|
private var ifname: String?
|
||||||
|
private var lastPath: Network.NWPath?
|
||||||
private var packetTunnelSettingsGenerator: PacketTunnelSettingsGenerator?
|
private var packetTunnelSettingsGenerator: PacketTunnelSettingsGenerator?
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
@@ -143,17 +144,15 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
private func pathUpdate(path: Network.NWPath) {
|
private func pathUpdate(path: Network.NWPath) {
|
||||||
guard let handle = handle else { return }
|
guard let handle = handle else { return }
|
||||||
wg_log(.debug, message: "Network change detected with \(path.status) route and interface order \(path.availableInterfaces)")
|
wg_log(.debug, message: "Network change detected with \(path.status) route and interface order \(path.availableInterfaces)")
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
if let packetTunnelSettingsGenerator = packetTunnelSettingsGenerator {
|
if let packetTunnelSettingsGenerator = packetTunnelSettingsGenerator {
|
||||||
_ = packetTunnelSettingsGenerator.endpointUapiConfiguration().withGoString { return wgSetConfig(handle, $0) }
|
_ = packetTunnelSettingsGenerator.endpointUapiConfiguration().withGoString { return wgSetConfig(handle, $0) }
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
var interfaces = path.availableInterfaces
|
if path != lastPath {
|
||||||
if let ifname = ifname {
|
lastPath = path
|
||||||
interfaces = interfaces.filter { $0.name != ifname }
|
wgBumpSockets(handle)
|
||||||
}
|
|
||||||
if let ifscope = interfaces.first?.index {
|
|
||||||
wgBindInterfaceScope(handle, Int32(ifscope))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ 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
|
||||||
|
|
||||||
GOBUILDVERSION_NEEDED := go version go1.12.1 darwin/amd64
|
GOBUILDVERSION_NEEDED := go version go1.12.5 darwin/amd64
|
||||||
GOBUILDVERSION_CURRENT := $(shell go version 2>/dev/null)
|
GOBUILDVERSION_CURRENT := $(shell go version 2>/dev/null)
|
||||||
export REAL_GOROOT := $(shell go env GOROOT 2>/dev/null)
|
export REAL_GOROOT := $(shell go env GOROOT 2>/dev/null)
|
||||||
export GOROOT := $(BUILDDIR)/goroot
|
export GOROOT := $(BUILDDIR)/goroot
|
||||||
|
|||||||
@@ -165,22 +165,14 @@ func wgGetConfig(tunnelHandle int32) *C.char {
|
|||||||
writer.Flush()
|
writer.Flush()
|
||||||
return C.CString(settings.String())
|
return C.CString(settings.String())
|
||||||
}
|
}
|
||||||
|
//export wgBumpSockets
|
||||||
//export wgBindInterfaceScope
|
func wgBumpSockets(tunnelHandle int32) {
|
||||||
func wgBindInterfaceScope(tunnelHandle int32, ifscope int32) {
|
|
||||||
device, ok := tunnelHandles[tunnelHandle]
|
device, ok := tunnelHandles[tunnelHandle]
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
device.Info.Printf("Binding sockets to interface %d\n", ifscope)
|
device.BindUpdate()
|
||||||
err := device.BindSocketToInterface4(uint32(ifscope))
|
device.SendKeepalivesToPeersWithCurrentKeypair()
|
||||||
if err != nil {
|
|
||||||
device.Error.Printf("Unable to bind v4 socket to interface:", err)
|
|
||||||
}
|
|
||||||
err = device.BindSocketToInterface6(uint32(ifscope))
|
|
||||||
if err != nil {
|
|
||||||
device.Error.Printf("Unable to bind v6 socket to interface:", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//export wgVersion
|
//export wgVersion
|
||||||
|
|||||||
@@ -3,6 +3,6 @@ module golang.zx2c4.com/wireguard/ios
|
|||||||
go 1.12
|
go 1.12
|
||||||
|
|
||||||
require (
|
require (
|
||||||
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67
|
golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20190409083948-18fa27047265
|
golang.zx2c4.com/wireguard v0.0.20190518-0.20190530131616-d9f995209c3c
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,12 +2,22 @@ github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcy
|
|||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
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 h1:aUX/1G2gFSs4AsJJg2cL3HuoRhCSCz733FE5GUSuaT4=
|
||||||
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f h1:R423Cnkcp5JABoeemiGEPlt9tHXFfw5kvc0yqlxRPWo=
|
||||||
|
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53 h1:kcXqo9vE6fsZY5X5Rd7R1l7fTgnWaDCVmln65REefiE=
|
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/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
|
||||||
|
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
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-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 h1:1Fzlr8kkDLQwqMP8GxrhptBLqZG/EDpiATneiZHY998=
|
||||||
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5 h1:f005F/Jl5JLP036x7QIvUVhNTqxvSYwFIiyOh2q12iU=
|
||||||
|
golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
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 h1:ujM5BaP4MD/2MJZ1n7pmw6IIsyyS7SyPl0fDefnx/2o=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20190409083948-18fa27047265/go.mod h1:u0Cl3X+pyWdXaax3S583DQrnGDuTASO0QdlKFrs8r/8=
|
golang.zx2c4.com/wireguard v0.0.0-20190409083948-18fa27047265/go.mod h1:u0Cl3X+pyWdXaax3S583DQrnGDuTASO0QdlKFrs8r/8=
|
||||||
|
golang.zx2c4.com/wireguard v0.0.20190518-0.20190530131616-d9f995209c3c h1:nUKiRKxUjK8Zghs+JBZZYXo0g51BlXVqkr6mDfm22ow=
|
||||||
|
golang.zx2c4.com/wireguard v0.0.20190518-0.20190530131616-d9f995209c3c/go.mod h1:8X7vp4RrsvM83bde6vQ94DsL4ZpjUViVUym8aa8zGhs=
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ extern int wgTurnOn(gostring_t settings, int32_t tun_fd);
|
|||||||
extern void wgTurnOff(int handle);
|
extern void wgTurnOff(int handle);
|
||||||
extern int64_t wgSetConfig(int handle, gostring_t settings);
|
extern int64_t wgSetConfig(int handle, gostring_t settings);
|
||||||
extern char *wgGetConfig(int handle);
|
extern char *wgGetConfig(int handle);
|
||||||
extern void wgBindInterfaceScope(int handle, int32_t ifscope);
|
extern void wgBumpSockets(int handle);
|
||||||
extern const char *wgVersion();
|
extern const char *wgVersion();
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||