/*
* ZeroTier One - Network Virtualization Everywhere
* Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#import "AppDelegate.h"
#import "NetworkMonitor.h"
#import "Network.h"
#import "NodeStatus.h"
#import "JoinNetworkViewController.h"
#import "ShowNetworksViewController.h"
#import "PreferencesViewController.h"
#import "AboutViewController.h"
#import "ServiceCom.h"
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:-2.0f];
self.networkListPopover = [[NSPopover alloc] init];
self.joinNetworkPopover = [[NSPopover alloc] init];
self.preferencesPopover = [[NSPopover alloc] init];
self.aboutPopover = [[NSPopover alloc] init];
self.transientMonitor = nil;
self.monitor = [[NetworkMonitor alloc] init];
self.networks = [NSMutableArray array];
self.status = nil;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *defaultsDict = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:@"firstRun"];
[defaults registerDefaults:defaultsDict];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:@selector(onNetworkListUpdated:)
name:NetworkUpdateKey
object:nil];
[nc addObserver:self
selector:@selector(onNodeStatusUpdated:)
name:StatusUpdateKey
object:nil];
NSString *osxMode = [defaults stringForKey:@"AppleInterfaceStyle"];
if(osxMode != nil && [osxMode isEqualToString:@"Dark"]) {
self.statusItem.image = [NSImage imageNamed:@"MenuBarIconMacWhite"];
}
else {
self.statusItem.image = [NSImage imageNamed:@"MenuBarIconMac"];
}
[[NSDistributedNotificationCenter defaultCenter] addObserver:self
selector:@selector(darkModeChanged:)
name:@"AppleInterfaceThemeChangedNotification"
object:nil];
[self buildMenu];
JoinNetworkViewController *jnvc = [[JoinNetworkViewController alloc] initWithNibName:@"JoinNetworkViewController" bundle:nil];
jnvc.appDelegate = self;
self.joinNetworkPopover.contentViewController = jnvc;
self.joinNetworkPopover.behavior = NSPopoverBehaviorTransient;
ShowNetworksViewController *showNetworksView = [[ShowNetworksViewController alloc] initWithNibName:@"ShowNetworksViewController" bundle:nil];
showNetworksView.netMonitor = self.monitor;
self.networkListPopover.contentViewController = showNetworksView;
self.networkListPopover.behavior = NSPopoverBehaviorTransient;
PreferencesViewController *prefsView = [[PreferencesViewController alloc] initWithNibName:@"PreferencesViewController" bundle:nil];
self.preferencesPopover.contentViewController = prefsView;
self.preferencesPopover.behavior = NSPopoverBehaviorTransient;
self.aboutPopover.contentViewController = [[AboutViewController alloc] initWithNibName:@"AboutViewController" bundle:nil];
self.aboutPopover.behavior = NSPopoverBehaviorTransient;
BOOL firstRun = [defaults boolForKey:@"firstRun"];
if(firstRun) {
[defaults setBool:NO forKey:@"firstRun"];
[defaults synchronize];
[prefsView setLaunchAtLoginEnabled:YES];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(2);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self showAbout];
}];
});
}
[self.monitor updateNetworkInfo];
[self.monitor start];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSDistributedNotificationCenter defaultCenter] removeObserver:self
name:@"AppleInterfaceThemeChangedNotification"
object:nil];
}
- (void)showNetworks {
if(self.statusItem.button != nil) {
NSStatusBarButton *button = self.statusItem.button;
[self.networkListPopover showRelativeToRect:button.bounds
ofView:button
preferredEdge:NSMinYEdge];
if(self.transientMonitor == nil) {
self.transientMonitor =
[NSEvent addGlobalMonitorForEventsMatchingMask:(NSLeftMouseDown|NSRightMouseDown|NSOtherMouseDown)
handler:^(NSEvent * _Nonnull e) {
[NSEvent removeMonitor:self.transientMonitor];
self.transientMonitor = nil;
[self.networkListPopover close];
}];
}
}
}
- (void)joinNetwork {
if(self.statusItem.button != nil) {
NSStatusBarButton *button = self.statusItem.button;
[self.joinNetworkPopover showRelativeToRect:button.bounds
ofView:button
preferredEdge:NSMinYEdge];
if(self.transientMonitor == nil) {
self.transientMonitor =
[NSEvent addGlobalMonitorForEventsMatchingMask:(NSLeftMouseDown|NSRightMouseDown|NSOtherMouseDown)
handler:^(NSEvent * _Nonnull e) {
[NSEvent removeMonitor:self.transientMonitor];
self.transientMonitor = nil;
[self.joinNetworkPopover close];
}];
}
}
}
- (void)showPreferences {
if(self.statusItem.button != nil) {
NSStatusBarButton *button = self.statusItem.button;
[self.preferencesPopover showRelativeToRect:button.bounds
ofView:button
preferredEdge:NSMinYEdge];
if(self.transientMonitor == nil) {
[NSEvent addGlobalMonitorForEventsMatchingMask:(NSLeftMouseDown|NSRightMouseDown|NSOtherMouseDown)
handler:^(NSEvent * _Nonnull e) {
[NSEvent removeMonitor:self.transientMonitor];
self.transientMonitor = nil;
[self.preferencesPopover close];
}];
}
}
}
- (void)showAbout {
if(self.statusItem.button != nil) {
NSStatusBarButton *button = self.statusItem.button;
[self.aboutPopover showRelativeToRect:button.bounds
ofView:button
preferredEdge:NSMinYEdge];
if(self.transientMonitor == nil) {
[NSEvent addGlobalMonitorForEventsMatchingMask:(NSLeftMouseDown|NSRightMouseDown|NSOtherMouseDown)
handler:^(NSEvent * _Nonnull e) {
[NSEvent removeMonitor:self.transientMonitor];
self.transientMonitor = nil;
[self.aboutPopover close];
}];
}
}
}
- (void)quit {
[NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0];
}
- (void)onNetworkListUpdated:(NSNotification*)note {
NSArray *netList = [note.userInfo objectForKey:@"networks"];
[(ShowNetworksViewController*)self.networkListPopover.contentViewController setNetworks:netList];
self.networks = [netList mutableCopy];
[self buildMenu];
}
- (void)onNodeStatusUpdated:(NSNotification*)note {
NodeStatus *status = [note.userInfo objectForKey:@"status"];
self.status = status;
[self buildMenu];
}
- (void)buildMenu {
NSMenu *menu = [[NSMenu alloc] init];
if(self.status != nil) {
NSString *nodeId = @"Node ID: ";
nodeId = [nodeId stringByAppendingString:self.status.address];
[menu addItem:[[NSMenuItem alloc] initWithTitle:nodeId
action:@selector(copyNodeID)
keyEquivalent:@""]];
[menu addItem:[NSMenuItem separatorItem]];
}
[menu addItem:[[NSMenuItem alloc] initWithTitle:@"Network Details..."
action:@selector(showNetworks)
keyEquivalent:@"n"]];
[menu addItem:[[NSMenuItem alloc] initWithTitle:@"Join Network..."
action:@selector(joinNetwork)
keyEquivalent:@"j"]];
[menu addItem:[NSMenuItem separatorItem]];
if([self.networks count] > 0) {
for(Network *net in self.networks) {
NSString *nwid = [NSString stringWithFormat:@"%10llx", net.nwid];
NSString *networkName = @"";
if([net.name lengthOfBytesUsingEncoding:NSUTF8StringEncoding] == 0) {
networkName = nwid;
}
else {
networkName = [NSString stringWithFormat:@"%@ (%@)", nwid, net.name];
}
if(net.allowDefault && net.connected) {
networkName = [networkName stringByAppendingString:@" [default]"];
}
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:networkName
action:@selector(toggleNetwork:)
keyEquivalent:@""];
if(net.connected) {
item.state = NSOnState;
}
else {
item.state = NSOffState;
}
item.representedObject = net;
[menu addItem:item];
}
[menu addItem:[NSMenuItem separatorItem]];
}
[menu addItem:[[NSMenuItem alloc] initWithTitle:@"About ZeroTier One..."
action:@selector(showAbout)
keyEquivalent:@""]];
[menu addItem:[[NSMenuItem alloc] initWithTitle:@"Preferences..."
action:@selector(showPreferences)
keyEquivalent:@""]];
[menu addItem:[NSMenuItem separatorItem]];
[menu addItem:[[NSMenuItem alloc] initWithTitle:@"Quit"
action:@selector(quit)
keyEquivalent:@"q"]];
self.statusItem.menu = menu;
}
- (void)toggleNetwork:(NSMenuItem*)sender {
Network *network = sender.representedObject;
NSString *nwid = [NSString stringWithFormat:@"%10llx", network.nwid];
if(network.connected) {
NSError *error = nil;
[[ServiceCom sharedInstance] leaveNetwork:nwid error:&error];
if (error) {
NSAlert *alert = [NSAlert alertWithError:error];
alert.alertStyle = NSCriticalAlertStyle;
[alert addButtonWithTitle:@"Ok"];
[alert runModal];
}
}
else {
NSError *error = nil;
[[ServiceCom sharedInstance] joinNetwork:nwid
allowManaged:network.allowManaged
allowGlobal:network.allowGlobal
allowDefault:(network.allowDefault && ![Network defaultRouteExists:self.networks])
error:&error];
if (error) {
NSAlert *alert = [NSAlert alertWithError:error];
alert.alertStyle = NSCriticalAlertStyle;
[alert addButtonWithTitle:@"Ok"];
[alert runModal];
}
}
}
- (void)copyNodeID {
if(self.status != nil) {
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
[pasteboard declareTypes:[NSArray arrayWithObject:NSPasteboardTypeString] owner:nil];
[pasteboard setString:self.status.address forType:NSPasteboardTypeString];
}
}
- (void)darkModeChanged:(NSNotification*)note {
NSString *osxMode = [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"];
if(osxMode != nil && [osxMode isEqualToString:@"Dark"]) {
self.statusItem.image = [NSImage imageNamed:@"MenuBarIconMacWhite"];
}
else {
self.statusItem.image = [NSImage imageNamed:@"MenuBarIconMac"];
}
}
- (void)closeJoinNetworkPopover {
if (self.transientMonitor) {
[NSEvent removeMonitor:self.transientMonitor];
self.transientMonitor = nil;
}
[self.joinNetworkPopover close];
}
@end