AppDelegate.m 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. /*
  2. * ZeroTier One - Network Virtualization Everywhere
  3. * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. #import "AppDelegate.h"
  19. #import "NetworkMonitor.h"
  20. #import "Network.h"
  21. #import "NodeStatus.h"
  22. #import "JoinNetworkViewController.h"
  23. #import "ShowNetworksViewController.h"
  24. #import "PreferencesViewController.h"
  25. #import "AboutViewController.h"
  26. #import "ServiceCom.h"
  27. @implementation AppDelegate
  28. - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
  29. self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:-2.0f];
  30. self.networkListPopover = [[NSPopover alloc] init];
  31. self.joinNetworkPopover = [[NSPopover alloc] init];
  32. self.preferencesPopover = [[NSPopover alloc] init];
  33. self.aboutPopover = [[NSPopover alloc] init];
  34. self.transientMonitor = nil;
  35. self.monitor = [[NetworkMonitor alloc] init];
  36. self.networks = [NSMutableArray<Network*> array];
  37. self.status = nil;
  38. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  39. NSDictionary *defaultsDict = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:@"firstRun"];
  40. [defaults registerDefaults:defaultsDict];
  41. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  42. [nc addObserver:self
  43. selector:@selector(onNetworkListUpdated:)
  44. name:NetworkUpdateKey
  45. object:nil];
  46. [nc addObserver:self
  47. selector:@selector(onNodeStatusUpdated:)
  48. name:StatusUpdateKey
  49. object:nil];
  50. NSString *osxMode = [defaults stringForKey:@"AppleInterfaceStyle"];
  51. if(osxMode != nil && [osxMode isEqualToString:@"Dark"]) {
  52. self.statusItem.image = [NSImage imageNamed:@"MenuBarIconMacWhite"];
  53. }
  54. else {
  55. self.statusItem.image = [NSImage imageNamed:@"MenuBarIconMac"];
  56. }
  57. [[NSDistributedNotificationCenter defaultCenter] addObserver:self
  58. selector:@selector(darkModeChanged:)
  59. name:@"AppleInterfaceThemeChangedNotification"
  60. object:nil];
  61. [self buildMenu];
  62. JoinNetworkViewController *jnvc = [[JoinNetworkViewController alloc] initWithNibName:@"JoinNetworkViewController" bundle:nil];
  63. jnvc.appDelegate = self;
  64. self.joinNetworkPopover.contentViewController = jnvc;
  65. self.joinNetworkPopover.behavior = NSPopoverBehaviorTransient;
  66. ShowNetworksViewController *showNetworksView = [[ShowNetworksViewController alloc] initWithNibName:@"ShowNetworksViewController" bundle:nil];
  67. showNetworksView.netMonitor = self.monitor;
  68. self.networkListPopover.contentViewController = showNetworksView;
  69. self.networkListPopover.behavior = NSPopoverBehaviorTransient;
  70. PreferencesViewController *prefsView = [[PreferencesViewController alloc] initWithNibName:@"PreferencesViewController" bundle:nil];
  71. self.preferencesPopover.contentViewController = prefsView;
  72. self.preferencesPopover.behavior = NSPopoverBehaviorTransient;
  73. self.aboutPopover.contentViewController = [[AboutViewController alloc] initWithNibName:@"AboutViewController" bundle:nil];
  74. self.aboutPopover.behavior = NSPopoverBehaviorTransient;
  75. BOOL firstRun = [defaults boolForKey:@"firstRun"];
  76. if(firstRun) {
  77. [defaults setBool:NO forKey:@"firstRun"];
  78. [defaults synchronize];
  79. [prefsView setLaunchAtLoginEnabled:YES];
  80. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  81. sleep(2);
  82. [[NSOperationQueue mainQueue] addOperationWithBlock:^{
  83. [self showAbout];
  84. }];
  85. });
  86. }
  87. [self.monitor updateNetworkInfo];
  88. [self.monitor start];
  89. }
  90. - (void)applicationWillTerminate:(NSNotification *)aNotification {
  91. [[NSNotificationCenter defaultCenter] removeObserver:self];
  92. [[NSDistributedNotificationCenter defaultCenter] removeObserver:self
  93. name:@"AppleInterfaceThemeChangedNotification"
  94. object:nil];
  95. }
  96. - (void)showNetworks {
  97. NSButton *button = nil;
  98. NSRect frame;
  99. if ([self.statusItem respondsToSelector:@selector(button)]) {
  100. button = self.statusItem.button;
  101. frame = button.bounds;
  102. } else if ([self.statusItem respondsToSelector:@selector(_button)]) {
  103. button = [self.statusItem performSelector:@selector(_button)];
  104. frame = button.bounds;
  105. } else {
  106. NSLog(@"Can't get view. Uh oh.");
  107. return;
  108. }
  109. [self.networkListPopover showRelativeToRect:frame
  110. ofView:button
  111. preferredEdge:NSMinYEdge];
  112. if(self.transientMonitor == nil) {
  113. self.transientMonitor =
  114. [NSEvent addGlobalMonitorForEventsMatchingMask:(NSLeftMouseDown|NSRightMouseDown|NSOtherMouseDown)
  115. handler:^(NSEvent * _Nonnull e) {
  116. [NSEvent removeMonitor:self.transientMonitor];
  117. self.transientMonitor = nil;
  118. [self.networkListPopover close];
  119. }];
  120. }
  121. }
  122. - (void)joinNetwork {
  123. NSButton *button = nil;
  124. NSRect frame;
  125. if ([self.statusItem respondsToSelector:@selector(button)]) {
  126. button = self.statusItem.button;
  127. frame = button.bounds;
  128. } else if ([self.statusItem respondsToSelector:@selector(_button)]) {
  129. button = [self.statusItem performSelector:@selector(_button)];
  130. frame = button.bounds;
  131. } else {
  132. NSLog(@"Can't get view. Uh oh.");
  133. return;
  134. }
  135. [self.joinNetworkPopover showRelativeToRect:button.bounds
  136. ofView:button
  137. preferredEdge:NSMinYEdge];
  138. if(self.transientMonitor == nil) {
  139. self.transientMonitor =
  140. [NSEvent addGlobalMonitorForEventsMatchingMask:(NSLeftMouseDown|NSRightMouseDown|NSOtherMouseDown)
  141. handler:^(NSEvent * _Nonnull e) {
  142. [NSEvent removeMonitor:self.transientMonitor];
  143. self.transientMonitor = nil;
  144. [self.joinNetworkPopover close];
  145. }];
  146. }
  147. }
  148. - (void)showPreferences {
  149. NSButton *button = nil;
  150. NSRect frame;
  151. if ([self.statusItem respondsToSelector:@selector(button)]) {
  152. button = self.statusItem.button;
  153. frame = button.bounds;
  154. } else if ([self.statusItem respondsToSelector:@selector(_button)]) {
  155. button = [self.statusItem performSelector:@selector(_button)];
  156. frame = button.bounds;
  157. } else {
  158. NSLog(@"Can't get view. Uh oh.");
  159. return;
  160. }
  161. [self.preferencesPopover showRelativeToRect:button.bounds
  162. ofView:button
  163. preferredEdge:NSMinYEdge];
  164. if(self.transientMonitor == nil) {
  165. [NSEvent addGlobalMonitorForEventsMatchingMask:(NSLeftMouseDown|NSRightMouseDown|NSOtherMouseDown)
  166. handler:^(NSEvent * _Nonnull e) {
  167. [NSEvent removeMonitor:self.transientMonitor];
  168. self.transientMonitor = nil;
  169. [self.preferencesPopover close];
  170. }];
  171. }
  172. }
  173. - (void)showAbout {
  174. NSButton *button = nil;
  175. NSRect frame;
  176. if ([self.statusItem respondsToSelector:@selector(button)]) {
  177. button = self.statusItem.button;
  178. frame = button.bounds;
  179. } else if ([self.statusItem respondsToSelector:@selector(_button)]) {
  180. button = [self.statusItem performSelector:@selector(_button)];
  181. frame = button.bounds;
  182. } else {
  183. NSLog(@"Can't get view. Uh oh.");
  184. return;
  185. }
  186. [self.aboutPopover showRelativeToRect:button.bounds
  187. ofView:button
  188. preferredEdge:NSMinYEdge];
  189. if(self.transientMonitor == nil) {
  190. [NSEvent addGlobalMonitorForEventsMatchingMask:(NSLeftMouseDown|NSRightMouseDown|NSOtherMouseDown)
  191. handler:^(NSEvent * _Nonnull e) {
  192. [NSEvent removeMonitor:self.transientMonitor];
  193. self.transientMonitor = nil;
  194. [self.aboutPopover close];
  195. }];
  196. }
  197. }
  198. - (void)quit {
  199. [NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0];
  200. }
  201. - (void)onNetworkListUpdated:(NSNotification*)note {
  202. NSArray<Network*> *netList = [note.userInfo objectForKey:@"networks"];
  203. [(ShowNetworksViewController*)self.networkListPopover.contentViewController setNetworks:netList];
  204. self.networks = [netList mutableCopy];
  205. [self buildMenu];
  206. }
  207. - (void)onNodeStatusUpdated:(NSNotification*)note {
  208. NodeStatus *status = [note.userInfo objectForKey:@"status"];
  209. self.status = status;
  210. [self buildMenu];
  211. }
  212. - (void)buildMenu {
  213. NSMenu *menu = [[NSMenu alloc] init];
  214. if(self.status != nil) {
  215. NSString *nodeId = @"Node ID: ";
  216. nodeId = [nodeId stringByAppendingString:self.status.address];
  217. [menu addItem:[[NSMenuItem alloc] initWithTitle:nodeId
  218. action:@selector(copyNodeID)
  219. keyEquivalent:@""]];
  220. [menu addItem:[NSMenuItem separatorItem]];
  221. }
  222. [menu addItem:[[NSMenuItem alloc] initWithTitle:@"Network Details..."
  223. action:@selector(showNetworks)
  224. keyEquivalent:@"n"]];
  225. [menu addItem:[[NSMenuItem alloc] initWithTitle:@"Join Network..."
  226. action:@selector(joinNetwork)
  227. keyEquivalent:@"j"]];
  228. [menu addItem:[NSMenuItem separatorItem]];
  229. if([self.networks count] > 0) {
  230. for(Network *net in self.networks) {
  231. NSString *nwid = [NSString stringWithFormat:@"%10llx", net.nwid];
  232. NSString *networkName = @"";
  233. if([net.name lengthOfBytesUsingEncoding:NSUTF8StringEncoding] == 0) {
  234. networkName = nwid;
  235. }
  236. else {
  237. networkName = [NSString stringWithFormat:@"%@ (%@)", nwid, net.name];
  238. }
  239. if(net.allowDefault && net.connected) {
  240. networkName = [networkName stringByAppendingString:@" [default]"];
  241. }
  242. NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:networkName
  243. action:@selector(toggleNetwork:)
  244. keyEquivalent:@""];
  245. if(net.connected) {
  246. item.state = NSOnState;
  247. }
  248. else {
  249. item.state = NSOffState;
  250. }
  251. item.representedObject = net;
  252. [menu addItem:item];
  253. }
  254. [menu addItem:[NSMenuItem separatorItem]];
  255. }
  256. [menu addItem:[[NSMenuItem alloc] initWithTitle:@"About ZeroTier One..."
  257. action:@selector(showAbout)
  258. keyEquivalent:@""]];
  259. [menu addItem:[[NSMenuItem alloc] initWithTitle:@"Preferences..."
  260. action:@selector(showPreferences)
  261. keyEquivalent:@""]];
  262. [menu addItem:[NSMenuItem separatorItem]];
  263. [menu addItem:[[NSMenuItem alloc] initWithTitle:@"Quit"
  264. action:@selector(quit)
  265. keyEquivalent:@"q"]];
  266. self.statusItem.menu = menu;
  267. }
  268. - (void)toggleNetwork:(NSMenuItem*)sender {
  269. Network *network = sender.representedObject;
  270. NSString *nwid = [NSString stringWithFormat:@"%10llx", network.nwid];
  271. if(network.connected) {
  272. NSError *error = nil;
  273. [[ServiceCom sharedInstance] leaveNetwork:nwid error:&error];
  274. if (error) {
  275. NSAlert *alert = [NSAlert alertWithError:error];
  276. alert.alertStyle = NSCriticalAlertStyle;
  277. [alert addButtonWithTitle:@"Ok"];
  278. [alert runModal];
  279. }
  280. }
  281. else {
  282. NSError *error = nil;
  283. [[ServiceCom sharedInstance] joinNetwork:nwid
  284. allowManaged:network.allowManaged
  285. allowGlobal:network.allowGlobal
  286. allowDefault:(network.allowDefault && ![Network defaultRouteExists:self.networks])
  287. error:&error];
  288. if (error) {
  289. NSAlert *alert = [NSAlert alertWithError:error];
  290. alert.alertStyle = NSCriticalAlertStyle;
  291. [alert addButtonWithTitle:@"Ok"];
  292. [alert runModal];
  293. }
  294. }
  295. }
  296. - (void)copyNodeID {
  297. if(self.status != nil) {
  298. NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
  299. [pasteboard declareTypes:[NSArray arrayWithObject:NSPasteboardTypeString] owner:nil];
  300. [pasteboard setString:self.status.address forType:NSPasteboardTypeString];
  301. }
  302. }
  303. - (void)darkModeChanged:(NSNotification*)note {
  304. NSString *osxMode = [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"];
  305. if(osxMode != nil && [osxMode isEqualToString:@"Dark"]) {
  306. self.statusItem.image = [NSImage imageNamed:@"MenuBarIconMacWhite"];
  307. }
  308. else {
  309. self.statusItem.image = [NSImage imageNamed:@"MenuBarIconMac"];
  310. }
  311. }
  312. - (void)closeJoinNetworkPopover {
  313. if (self.transientMonitor) {
  314. [NSEvent removeMonitor:self.transientMonitor];
  315. self.transientMonitor = nil;
  316. }
  317. [self.joinNetworkPopover close];
  318. }
  319. @end