// // DDWRT_MonitorAppDelegate.m // DDWRT-Monitor // // Created by Matthieu Lalonde & Spike Grobstein on 11-05-27. // Copyleft 2011 Spurf CC BY-SH-NC. Some rights reserved. // #import "DDWRT_MonitorAppDelegate.h" @implementation DDWRT_MonitorAppDelegate # pragma mark - # pragma mark Application Delegates: - (void) awakeFromNib { // Insert code here to initialize your application [self showMenubar]; defaults = [NSUserDefaults standardUserDefaults]; // check if the app is configured yet if (![defaults boolForKey:@"configured"]) { [self initDefaults]; } else { [self initialize]; } } - (void) dealloc { [AppMenu release]; [_appMenu release]; [ConfigPanel release]; [super dealloc]; } - (void) terminate:(id)sender { [[NSUserDefaults standardUserDefaults] synchronize]; [self dealloc]; [[NSApplication sharedApplication] terminate:self]; } - (void) initialize { NSLog(@"Reading defaults..."); [self readDefaults]; _statusDataRequestNumber = 0; _statusStringData = @""; wrt_update_client = [[WRTStatusClient alloc] initWithHostname:hostname port:port protocol:protocol username:username password:password]; wrt_request_client = [[WRTStatusClient alloc] initWithHostname:hostname port:port protocol:protocol username:username password:password]; [wrt_update_client registerStatusCallback:self callback:@selector(cbUpdateStatus)]; [wrt_update_client getConnectionStatus]; } - (void) deinitialize { [wrt_update_client release]; wrt_update_client = nil; [wrt_request_client release]; wrt_request_client = nil; } # pragma mark - # pragma mark Menu Methods: - (void) showMenubar { NSLog(@"Showing Menu Item"); NSStatusBar *bar = [NSStatusBar systemStatusBar]; _appMenu = [bar statusItemWithLength:NSVariableStatusItemLength]; [_appMenu setAction:@selector(refreshMenu:)]; [_appMenu setTarget:self]; [_appMenu retain]; [self showMenubarIcon:false]; [_appMenu setHighlightMode:YES]; //[_appMenu setMenu:AppMenu]; } - (void) hideMenubar { [_appMenu release]; _appMenu = nil; [AppMenu release]; AppMenu = nil; } - (void) showMenubarIcon:(BOOL)enabled { NSImage* icon = [NSImage imageNamed:(enabled == true ? @"MenuIcon" : @"MenuIconDisabled")]; [_appMenu setImage:icon]; [_appMenu setAlternateImage:nil]; [icon release]; } - (void) hideMenubarIcon { [_appMenu setImage:nil]; [_appMenu setAlternateImage:nil]; } - (void) setMenubarText:(NSString *)menubarText { // TODO set proper line height and proper right align NSFontManager *fontManager = [NSFontManager sharedFontManager]; NSFont *menuFont = [fontManager fontWithFamily:@"Lucida Grande" traits:NSBoldFontMask weight:10 size:8 ]; NSDictionary *titleAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:menuFont, NSFontAttributeName, [NSColor blackColor], NSForegroundColorAttributeName, nil]; NSAttributedString *menuTitle = [[NSAttributedString alloc] initWithString:menubarText attributes:titleAttributes]; [_appMenu setAttributedTitle:menuTitle]; [fontManager release]; [menuFont release]; [menuTitle release]; } # pragma mark - # pragma mark Configs Methods: - (void) initDefaults { NSLog(@"Creating new user defaults"); // do any other initialization you want to do here - e.g. the starting default values. [defaults setValue:@"http" forKey:@"protocol"]; [defaults setValue:@"192.168.1.1" forKey:@"hostname"]; [defaults setInteger:80 forKey:@"port"]; [defaults setValue:@"" forKey:@"username"]; [defaults setValue:@"" forKey:@"password"]; [defaults setBool:YES forKey:@"showMenuIcon"]; [defaults setBool:YES forKey:@"useBytes"]; [defaults setInteger:4 forKey:@"refreshTime"]; [defaults setBool:YES forKey:@"configured"]; // TODO: Add modal alert "First launch" here... [self showConfigPanel:self]; } - (void) readDefaults { hostname = [defaults valueForKey:@"hostname"]; protocol = [defaults valueForKey:@"protocol"]; port = [defaults integerForKey:@"port"]; username = [defaults valueForKey:@"username"]; password = [defaults valueForKey:@"password"]; showMenuIcon = [defaults boolForKey:@"showMenuIcon"]; useBytes = [defaults boolForKey:@"useBytes"]; refreshTime = [defaults integerForKey:@"refreshTime"]; } - (void) writeDefaults { // do any other initialization you want to do here - e.g. the starting default values. [defaults setValue:hostname forKey:@"hostname"]; [defaults setValue:protocol forKey:@"protocol"]; [defaults setInteger:port forKey:@"port"]; [defaults setValue:username forKey:@"username"]; [defaults setValue:password forKey:@"password"]; [defaults setBool:showMenuIcon forKey:@"showMenuIcon"]; [defaults setBool:useBytes forKey:@"useBytes"]; [defaults setInteger:refreshTime forKey:@"refreshTime"]; // sync the defaults to disk [defaults synchronize]; } - (IBAction) showConfigPanel:(id)sender { [configFieldShowIcon setState:([defaults boolForKey:@"showMenuIcon"] ? 1 : 0)]; [configFieldUseBytes setState:([defaults boolForKey:@"useBytes"] ? 1 : 0)]; if (hostname != nil) { [configFieldHostname setStringValue:hostname]; } // TODO [configFieldPort setIntValue:[defaults integerForKey:@"port"]]; [configFieldRefresh setIntegerValue:[defaults integerForKey:@"refreshTime"]]; [configLabelRefresh setStringValue:[NSString stringWithFormat:@"%d s", ([defaults integerForKey:@"refreshTime"] + 1)]]; if ([protocol isEqualToString:@"http"] == true) { [configFieldProtocolHTTP setState:1]; [configFieldProtocolHTTPS setState:0]; } else if ([protocol isEqualToString:@"https"] == true) { [configFieldProtocolHTTP setState:0]; [configFieldProtocolHTTPS setState:1]; } [ConfigPanel makeKeyAndOrderFront:nil]; } - (IBAction) hideConfigPanel:(id)sender { if (hostname == nil && [[sender title] isEqualToString:@"Cancel"]) { // TODO: Add modal alert "will quit if not configured" [self terminate:nil]; } [ConfigPanel orderOut:nil]; } - (IBAction) setConfigProtocol:(id)sender { if ([configFieldProtocolHTTP state] == 1 && [[configFieldPort stringValue] isEqualToString:@"443"] == true) { [configFieldPort setStringValue:@"80"]; } else if ([configFieldProtocolHTTPS state] == 1 && [[configFieldPort stringValue] isEqualToString:@"80"] == true) { [configFieldPort setStringValue:@"443"]; } } - (IBAction) setConfigRefresh:(id)sender { [configLabelRefresh setStringValue:[NSString stringWithFormat:@"%u s", ([sender integerValue] + 1)]]; } - (IBAction) saveConfig:(id)sender { [self deinitialize]; [configStateStatus startAnimation:nil]; [configStateStatus setHidden:false]; hostname = [configFieldHostname stringValue]; port = [configFieldPort intValue]; if ([configFieldProtocolHTTP state] == 1) { protocol = @"http"; } else if ([configFieldProtocolHTTPS state] == 1) { protocol = @"https"; } refreshTime = [configFieldRefresh integerValue]; useBytes = ([configFieldUseBytes state] == 1 ? true: false); showMenuIcon = ([configFieldShowIcon state] == 1 ? true: false); [configStateStatus stopAnimation:nil]; [configStateStatus setHidden:true]; [self writeDefaults]; [defaults synchronize]; [self initialize]; [self hideConfigPanel:nil]; } # pragma mark - # pragma mark Request Handlers: - (void) cbUpdateStatus { NSLog(@"Status %@ wan port: %@", ([wrt_update_client getWrtReachable] ? @"Up" : @"Down"), [wrt_update_client getWanPort]); if ([wrt_update_client getWrtReachable] == true) { if (showMenuIcon == true) { [self showMenubarIcon:true]; } else { [self hideMenubarIcon]; } if (_updateTimer != nil) { [_updateTimer invalidate]; _updateTimer = nil; } _updateTimer = [NSTimer scheduledTimerWithTimeInterval:refreshTime+1 target:self selector:@selector(getThroughput:) userInfo:nil repeats:YES]; [_updateTimer fire]; } else if ([wrt_update_client getWrtReachable] == false) { [self showMenubarIcon:false]; [self setMenubarText:@""]; if (_updateTimer != nil) { [_updateTimer invalidate]; _updateTimer = nil; } _updateTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:wrt_update_client selector:@selector(getConnectionStatus:) userInfo:nil repeats:NO]; [_updateTimer fire]; } } - (void) getThroughput:(NSTimer *)timer { //NSLog(@"Status %@ wan port: %@", ([wrt_client getWrtReachable] ? @"Up" : @"Down"), [wrt_client getWanPort]); [wrt_update_client getStatusUpdate:[NSString stringWithFormat:@"fetchif.cgi?%@", [wrt_update_client getWanPort]] delegate:self callback:@selector(cbThroughput:)]; } - (void) cbThroughput:(NSData *)data { NSString *stringData = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; NSString *key = [NSString stringWithFormat:@"%@:", [wrt_update_client getWanPort]]; stringData = [stringData substringFromIndex:([stringData rangeOfString:key].location + [key length])]; NSArray *parts = [stringData componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; NSArray *listItems = [parts filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"SELF != ''"]]; uint64_t ifIn = strtoull([[listItems objectAtIndex:0] UTF8String], NULL, 0); uint64_t ifOut = strtoull([[listItems objectAtIndex:8] UTF8String], NULL, 0); if (_lastInThroughput != 0) { uint64_t diffIn = ifIn - _lastInThroughput; uint64_t diffOut = ifOut - _lastOutThroughput; double timeDiff = fabs([_lastDateThroughput timeIntervalSinceNow]); _lastDateThroughput = [[NSDate date] retain]; if (timeDiff <= 0) timeDiff = 1; // avoid division by zero double speedIn = diffIn / timeDiff; double speedOut = diffOut / timeDiff; speedIn = round(2.2f * speedIn) / 2.2f; speedOut = round(2.2f * speedOut) / 2.2f; [ self setMenubarText:[NSString stringWithFormat:@"%@s\n%@s", [self stringFromSpeed:speedOut], [self stringFromSpeed:speedIn]] ]; //NSLog(@"In %llu Out %llu Last In %llu Last Out %llu diffIn %llu diffOut %llu In %f Out %f Interval %f", ifIn, ifOut, _lastInThroughput, // _lastOutThroughput, diffIn, diffOut, speedIn, speedOut, timeDiff); } _lastInThroughput = ifIn; _lastOutThroughput = ifOut; } - (void) getRouterData:(NSData *)data { NSLog(@"Gathering status data"); if (data != nil) { NSString *stringData = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; NSString *newStringData = [_statusStringData stringByAppendingString:stringData]; _statusStringData = [newStringData retain]; } else { NSLog(@"No data received yet"); } switch (_statusDataRequestNumber++) { case 0: NSLog(@"Status 0"); [wrt_request_client getStatusUpdate:@"Status_Router.live.asp" delegate:self callback:@selector(getRouterData:)]; break; case 1: NSLog(@"Status 1"); [wrt_request_client getStatusUpdate:@"Status_Internet.live.asp" delegate:self callback:@selector(getRouterData:)]; break; case 2: NSLog(@"Status 2"); [wrt_request_client getStatusUpdate:@"Status_Lan.live.asp" delegate:self callback:@selector(getRouterData:)]; break; case 3: default: // Populate the menu items [self populateMenuMain]; [self populateMenuWan]; [self populateMenuClients]; // Show the menu [_appMenu popUpStatusItemMenu:AppMenu]; _statusStringData = @""; _statusDataRequestNumber = 0; break; } } # pragma mark - # pragma mark Menu Handlers: - (IBAction) refreshMenu:(id)sender { NSLog(@"Refresh Menu"); if ([wrt_update_client getWrtReachable] == true) { [self getRouterData:nil]; } else { [_appMenu popUpStatusItemMenu:AppMenu]; } } - (void) populateMenuMain { NSString *uptime = [wrt_request_client getKey:_statusStringData key:@"uptime"]; NSString *loadSplit = @", load average: "; if ([uptime length] > 0) { NSString *load = [uptime substringFromIndex:[uptime rangeOfString:loadSplit].location+[loadSplit length]]; uptime = [uptime substringToIndex:[uptime rangeOfString:@", load"].location]; // Cut the load uptime = [uptime substringFromIndex:[uptime rangeOfString:@" "].location+1]; // Trim uptime = [uptime stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:[[uptime substringToIndex:1] uppercaseString]]; // UCFirst [menuUptime setTitle:uptime]; //[menuUptime setHidden:false]; [menuLoad setTitle:load]; [menuLoad setHidden:false]; //[[AppMenu itemAtIndex:2] setHidden:false]; } else { [menuUptime setTitle:@"Connected"]; //[menuUptime setHidden:true]; [menuLoad setHidden:true]; //[[AppMenu itemAtIndex:2] setHidden:true]; } // NSString *memInfo = [wrt_client getKey:stringData key:@"mem_info"]; // Remove any white spaces // NSArray *parts = [memInfo componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; // NSArray *filteredArray = [parts filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"SELF != ''"]]; // memInfo = [filteredArray componentsJoinedByString:@" "]; // parts = nil; // filteredArray = nil; // Remove any quotes // parts = [memInfo componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"\'"]]; // filteredArray = [parts filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"SELF != ''"]]; // memInfo = [filteredArray componentsJoinedByString:@""]; // NSArray *memData = [memInfo componentsSeparatedByString:@","]; // unint32_t memTotal = strtoull([[memData objectAtIndex:17] UTF8String], NULL, 0); // unint32_t memFree = strtoull([[memData objectAtIndex:21] UTF8String], NULL, 0); } - (void) populateMenuWan { NSString *wanip = [wrt_request_client getKey:_statusStringData key:@"ipinfo"]; NSRange startRange = [wanip rangeOfString:@" "]; wanip = [wanip substringFromIndex:(startRange.location+1)]; [menuWanIP setTitle:wanip]; NSString *wanStatus = [wrt_request_client getKey:_statusStringData key:@"wan_status"]; NSString *wanName = [wrt_request_client getKey:_statusStringData key:@"wan_shortproto"]; if ([wanName isEqualToString:@"pppoe"] == true) { wanName = @"PPPoE"; } else if ([wanName isEqualToString:@"static"] == true) { wanName = @"Static"; } else { wanName = [wanName uppercaseString]; } wanStatus = [wanStatus substringToIndex:[wanStatus rangeOfString:@"&"].location]; [menuWanStatus setTitle:[NSString stringWithFormat:@"%@ %@", wanName, wanStatus]]; //if ([_appMenu image] != nil) { // [self showMenubarIcon:[wanStatus isEqualToString:@"Connected"]]; //} NSString *wanUptime = [wrt_request_client getKey:_statusStringData key:@"wan_uptime"]; if ([wanStatus isEqualToString:@"Connected"] == true) { [menuWanUptime setTitle:wanUptime]; [menuWanUptime setHidden:false]; [menuWanSeparator setHidden:false]; [menuWanBandwidthGraph setEnabled:true]; [menuWanTrafficIn setHidden:false]; [menuWanTrafficOut setHidden:false]; } else { [menuWanUptime setHidden:true]; [menuWanSeparator setHidden:true]; [menuWanBandwidthGraph setEnabled:false]; [menuWanTrafficIn setHidden:true]; [menuWanTrafficOut setHidden:true]; } NSString *trafficIn = [wrt_request_client getKey:_statusStringData key:@"ttraff_in"]; NSString *trafficOut = [wrt_request_client getKey:_statusStringData key:@"ttraff_out"]; trafficIn = [self stringFromSize:([trafficIn longLongValue] * 1024 * 1024)]; trafficOut = [self stringFromSize:([trafficOut longLongValue] * 1024 * 1024)]; [menuWanTrafficIn setTitle:[NSString stringWithFormat:@"In: %@", trafficIn]]; [menuWanTrafficOut setTitle:[NSString stringWithFormat:@"Out: %@", trafficOut]]; NSString *wanDNS0 = [wrt_request_client getKey:_statusStringData key:@"wan_dns0"]; NSString *wanDNS1 = [wrt_request_client getKey:_statusStringData key:@"wan_dns1"]; NSString *wanDNS2 = [wrt_request_client getKey:_statusStringData key:@"wan_dns2"]; if ([wanDNS0 length] > 0) { [menuWanDNS setEnabled:true]; [menuWanDNSItem0 setTitle: wanDNS0]; [menuWanDNSItem0 setHidden:false]; } if ([wanDNS1 length] > 0) { [menuWanDNS setEnabled:true]; [menuWanDNSItem1 setTitle: wanDNS1]; [menuWanDNSItem1 setHidden:false]; } if ([wanDNS2 length] > 0) { [menuWanDNS setEnabled:true]; [menuWanDNSItem2 setTitle: wanDNS2]; [menuWanDNSItem2 setHidden:false]; } } - (void) populateMenuClients { NSString * clientsStringData; NSString *keyData = @"{arp_table::"; clientsStringData = [ _statusStringData substringFromIndex:([_statusStringData rangeOfString:keyData].location + [keyData length]) ]; clientsStringData = [ clientsStringData substringToIndex:[clientsStringData rangeOfString:@"}"].location ]; // Remove any white spaces NSArray *parts = [clientsStringData componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; NSArray *filteredArray = [parts filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"SELF != ''"]]; clientsStringData = [filteredArray componentsJoinedByString:@" "]; parts = nil; filteredArray = nil; // Remove any quotes parts = [clientsStringData componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"\'"]]; filteredArray = [parts filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"SELF != ''"]]; clientsStringData = [filteredArray componentsJoinedByString:@""]; NSArray *clientsData = [clientsStringData componentsSeparatedByString:@","]; NSMenu *clientMenu; NSMenuItem *clientMenuItem; NSMenuItem *clientItem; NSString *ipconn = [wrt_request_client getKey:_statusStringData key:@"ip_conntrack"]; [menuClientsConnections setTitle:[NSString stringWithFormat:@"%@ Connection%@", ipconn, ([ipconn isEqualToString:@"1"] ? @"" : @"s")]]; uint16_t clientsCount = [clientsData count] / 4; [menuClientsCount setTitle:[NSString stringWithFormat:@"%lu Active Client%@", clientsCount, (clientsCount > 1 ? @"s" : @"")]]; // Clear the old client data uint16_t clientIndex; uint16_t firstItem = [menuClients indexOfItem:menuClientsSeparator]; if ([menuClients numberOfItems] > firstItem) { for (clientIndex = ([menuClients numberOfItems] - 1); clientIndex > (firstItem + 1); clientIndex--) { [menuClients removeItemAtIndex:clientIndex]; } } for (clientIndex = 0; clientIndex < [clientsData count]; clientIndex+=4) { // Allocate a new menu for the client data clientMenu = [[NSMenu alloc] init]; // Hostname clientMenuItem = [[NSMenuItem alloc] initWithTitle:[clientsData objectAtIndex:clientIndex] action:nil keyEquivalent:@""]; [clientMenuItem setSubmenu:[self createCopyMenuItem]]; [clientMenu addItem:clientMenuItem]; // IP clientMenuItem = [[NSMenuItem alloc] initWithTitle:[clientsData objectAtIndex:(clientIndex+1)] action:nil keyEquivalent:@""]; [clientMenuItem setSubmenu:[self createCopyMenuItem]]; [clientMenu addItem:clientMenuItem]; // MAC clientMenuItem = [[NSMenuItem alloc] initWithTitle:[[clientsData objectAtIndex:(clientIndex+2)] uppercaseString] action:nil keyEquivalent:@""]; [clientMenuItem setSubmenu:[self createCopyMenuItem]]; [clientMenu addItem:clientMenuItem]; // Connections uint16_t conn = strtoull([[clientsData objectAtIndex:(clientIndex+3)] UTF8String], NULL, 0); clientMenuItem = [[NSMenuItem alloc] initWithTitle:[NSString stringWithFormat:@"%lu Connection%@", conn, (conn > 1 ? @"s" : @"")] action:nil keyEquivalent:@""]; [clientMenu addItem:clientMenuItem]; clientItem = [[NSMenuItem alloc] initWithTitle:[clientsData objectAtIndex:clientIndex] action:nil keyEquivalent:@""]; [clientItem setSubmenu:clientMenu]; [menuClients addItem:clientItem]; } } - (NSMenu *) createCopyMenuItem { NSMenu *copyMenu = [[NSMenu alloc] init]; NSMenuItem *copyMenuItem = [[NSMenuItem alloc] initWithTitle:@"Copy" action:@selector(copyParentMenuTitle:) keyEquivalent:@""]; [copyMenuItem setEnabled:true]; [copyMenuItem setTarget:self]; [copyMenu addItem:copyMenuItem]; return [copyMenu retain]; } # pragma mark - # pragma mark Utilities: - (IBAction) copyParentMenuTitle:(id)sender { NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard]; [pasteBoard declareTypes:[NSArray arrayWithObjects:NSStringPboardType, nil] owner: nil]; [pasteBoard setString:[[sender parentItem] title] forType:NSStringPboardType]; } - (NSString *) stringFromSize:(uint64_t)theBytes { double floatSize = theBytes; if (theBytes<1023) return([NSString stringWithFormat:@"%lluB", theBytes]); floatSize = floatSize / 1024; if (floatSize<1023) return([NSString stringWithFormat:@"%1.1fKiB", floatSize]); floatSize = floatSize / 1024; if (floatSize<1023) return([NSString stringWithFormat:@"%1.2fMiB", floatSize]); floatSize = floatSize / 1024; if (floatSize<1023) return([NSString stringWithFormat:@"%1.2fGiB", floatSize]); floatSize = floatSize / 1024; return([NSString stringWithFormat:@"%1.2fTiB", floatSize]); } - (NSString *) stringFromSpeed:(uint64_t)theBytes { NSString *suffix; if (useBytes == false) { theBytes *= 8; } double floatSize = theBytes; suffix = (useBytes ? @"B" : @"b"); if (theBytes<1023) return([NSString stringWithFormat:@"%llu%@", theBytes, suffix]); floatSize = floatSize / 1024; suffix = (useBytes ? @"KiB" : @"Kb"); if (floatSize<1023) return([NSString stringWithFormat:@"%1.1f%@", floatSize, suffix]); floatSize = floatSize / 1024; suffix = (useBytes ? @"MiB" : @"Mb"); if (floatSize<1023) return([NSString stringWithFormat:@"%1.2f%@", floatSize, suffix]); floatSize = floatSize / 1024; suffix = (useBytes ? @"GiB" : @"Gb"); if (floatSize<1023) return([NSString stringWithFormat:@"%1.2f%@", floatSize, suffix]); floatSize = floatSize / 1024; suffix = (useBytes ? @"TiB" : @"Tb"); return([NSString stringWithFormat:@"%1.2f%@", floatSize, suffix]); } @end