/* FILE: AppController.m * * Project TBrowser * Class AppController * Creator Chris B. Vetter * Maintainer Chris B. Vetter * Creation Date Mon Sep 11 14:56:01 CEST 2006 * * Copyright (c) 2006 * * TBrowser is free software under the terms of a dual BSD/LGPL license. * For a full copyright description, see the COPYRIGHT file. * ***************************************************************************/ // // Include // #import "main.h" #import "AppController.h" #import #import #import #import #import #import #import #import #import #import #import #import // inet_ntop(3) #import #import #import // // Define // #define Localize(key) NSLocalizedString(key, @"") #define LOG( f, args... ) NSDebugLLog(@"TBrowser", f, ##args) // // Typedef // // // Public // // // Private // static AppController *sharedController = nil; // // Private Interface // @interface AppController (Private) - (void) createMenus; - (void) toggleServiceActivation: (id) sender; @end // // Prototype // /*************************************************************************** ** ** Implementation ** */ @implementation AppController /*************************************************************************** ** ** Factory Methods ** */ /** * Description forthcoming * * */ + (id) sharedController { // // That's it // return ( sharedController ? (id) sharedController : (id) [[self alloc] init] ); } /*************************************************************************** ** ** Instance Methods ** */ /*************************************************************************** ** ** Accessor Methods ** */ /*************************************************************************** ** ** Protocol Methods ** */ /*************************************************************************** ** ** Override Methods ** */ /** * Description forthcoming * * */ - (id) init { if( sharedController ) { [self dealloc]; } else { NSImage *image = nil; if( ! (sharedController = [super init]) ) { return nil; } browsers = [[NSMutableArray alloc] init]; foundServices = [[NSMutableArray alloc] init]; [NSBundle loadNibNamed: @"Controller.gorm" owner: self]; image = [NSImage imageNamed: @"TBrowser.png"]; [NSApp setApplicationIconImage: image]; } // // That's it // return sharedController; } /** * Description forthcoming * * */ - (void) dealloc { [browsers release]; [foundServices release]; [super dealloc]; // // That's it // return; } /** * Description forthcoming * * */ - (void) applicationWillFinishLaunching: (NSNotification*) note { [self createMenus]; // // That's it // return; } /** * Description forthcoming * * */ - (void) applicationDidFinishLaunching: (NSNotification*) note { // // That's it // return; } /** * Description forthcoming * * */ - (BOOL) applicationShouldTerminateAfterLastWindowClosed: (NSApplication *) app { // // That's it // return YES; } /** * Description forthcoming * * */ - (BOOL) applicationShouldTerminate: (id) sender { // // That's it // return YES; } /** * Description forthcoming * * */ - (void) applicationWillTerminate: (NSNotification *) note { [window saveFrameUsingName: @"main"]; isSearching = YES; [self toggleServiceActivation: self]; // // That's it // return; } /** * Description forthcoming * * */ - (void) awakeFromNib { // // -awakeFromNib is called twice, but we only want to create the new // columns once, so we check how many columns already exist... // if( 8 != [[discoveredServicesList tableColumns] count] ) { NSTableColumn *column = nil; // // First, remove the two columns that Gorm automatically creates // { column = [discoveredServicesList tableColumnWithIdentifier: @"column2"]; if( column ) { [discoveredServicesList removeTableColumn: column]; } column = [discoveredServicesList tableColumnWithIdentifier: @"column1"]; if( column ) { [discoveredServicesList removeTableColumn: column]; } } // // Second, create our own columns // column = [[NSTableColumn alloc] initWithIdentifier: @"servicename"]; { [[column headerCell] setStringValue: Localize(@"Name")]; [[column headerCell] setAlignment: NSLeftTextAlignment]; [column setResizable: YES]; [column setWidth: 90.]; } [discoveredServicesList addTableColumn: column]; column = [[NSTableColumn alloc] initWithIdentifier: @"servicetype"]; { [[column headerCell] setStringValue: Localize(@"Type")]; [[column headerCell] setAlignment: NSLeftTextAlignment]; [column setResizable: YES]; [column setWidth: 90.]; } [discoveredServicesList addTableColumn: column]; column = [[NSTableColumn alloc] initWithIdentifier: @"serviceprotocol"]; { [[column headerCell] setStringValue: Localize(@"Protocol")]; [[column headerCell] setAlignment: NSLeftTextAlignment]; [column setResizable: YES]; [column setWidth: 90.]; } [discoveredServicesList addTableColumn: column]; column = [[NSTableColumn alloc] initWithIdentifier: @"servicedomain"]; { [[column headerCell] setStringValue: Localize(@"Domain")]; [[column headerCell] setAlignment: NSLeftTextAlignment]; [column setResizable: YES]; [column setWidth: 90.]; } [discoveredServicesList addTableColumn: column]; column = [[NSTableColumn alloc] initWithIdentifier: @"servicehost"]; { [[column headerCell] setStringValue: Localize(@"Hostname")]; [[column headerCell] setAlignment: NSLeftTextAlignment]; [column setResizable: YES]; [column setWidth: 90.]; } [discoveredServicesList addTableColumn: column]; column = [[NSTableColumn alloc] initWithIdentifier: @"serviceaddress"]; { [[column headerCell] setStringValue: Localize(@"Address")]; [[column headerCell] setAlignment: NSLeftTextAlignment]; [column setResizable: YES]; [column setWidth: 90.]; } [discoveredServicesList addTableColumn: column]; column = [[NSTableColumn alloc] initWithIdentifier: @"serviceport"]; { [[column headerCell] setStringValue: Localize(@"Port")]; [[column headerCell] setAlignment: NSLeftTextAlignment]; [column setResizable: YES]; [column setWidth: 90.]; } [discoveredServicesList addTableColumn: column]; column = [[NSTableColumn alloc] initWithIdentifier: @"servicetxtrecord"]; { [[column headerCell] setStringValue: Localize(@"TXT Record")]; [[column headerCell] setAlignment: NSLeftTextAlignment]; [column setResizable: YES]; [column setWidth: 90.]; } [discoveredServicesList addTableColumn: column]; } // // Finalize the list... // [discoveredServicesList setDrawsGrid: YES]; [discoveredServicesList setAllowsColumnResizing: YES]; [discoveredServicesList setAutoresizesAllColumnsToFit: YES]; [discoveredServicesList setDataSource: self]; [discoveredServicesList setDelegate: self]; [discoveredServicesList setTarget:self]; [discoveredServicesList setCornerView: nil]; [discoveredServicesList tile]; // // ...the button... // [browseButton setTitle: Localize(@"Start")]; [browseButton setTarget: self]; [browseButton setAction: @selector(toggleServiceActivation:)]; // // ...and the window // [window setTitle: Localize(@"Tryst Browsing Example")]; [window setMinSize: NSMakeSize(300., 200.)]; [window becomeKeyWindow]; [window setFrameUsingName: @"main"]; // // That's it // return; } /*************************************************************************** ** ** Private Methods ** */ /** * Description forthcoming * * */ - (void) createMenus { NSMenu *menu, *info, *windows, *services; menu = [[NSMenu alloc] init]; { [menu addItemWithTitle: Localize(@"Info") action: NULL keyEquivalent: nil]; [menu addItemWithTitle: Localize(@"Windows") action: NULL keyEquivalent: nil]; [menu addItemWithTitle: Localize(@"Services") action: NULL keyEquivalent: nil]; [menu addItemWithTitle: Localize(@"Hide") action: @selector(hide:) keyEquivalent: @"h"]; [menu addItemWithTitle: Localize(@"Quit") action: @selector(terminate:) keyEquivalent: @"q"]; } [NSApp setMainMenu: menu]; info = [[NSMenu alloc] init]; { [menu setSubmenu: info forItem: [menu itemWithTitle: Localize(@"Info")]]; [info addItemWithTitle: Localize(@"About...") action: @selector(orderFrontStandardAboutPanel:) keyEquivalent: nil]; [info addItemWithTitle: Localize(@"Preferences...") action: @selector(showPreferencePanel:) keyEquivalent: nil]; [info addItemWithTitle: Localize(@"Help...") action: @selector(orderFrontHelpPanel:) keyEquivalent: @"?"]; } windows = [[NSMenu alloc] init]; { [menu setSubmenu: windows forItem: [menu itemWithTitle: Localize(@"Windows")]]; [windows addItemWithTitle: Localize(@"Arrange in Front") action: @selector(arrangeInFront:) keyEquivalent: nil]; [windows addItemWithTitle: Localize(@"Miniaturize Window") action: @selector(performMiniaturize:) keyEquivalent: @"m"]; [windows addItemWithTitle: Localize(@"Close Window") action: @selector(performClose:) keyEquivalent: @"w"]; } [NSApp setWindowsMenu: windows]; // // Finalize // [menu setTitle: [[NSProcessInfo processInfo] processName]]; { [info release]; [windows release]; } [menu release]; // // That's it // return; } /** * Description forthcoming * * */ - (IBAction) toggleServiceActivation: (id) sender { if( NO == isSearching ) { NSNetServiceBrowser *browser = nil; if( [foundServices count] ) { [foundServices removeAllObjects]; [discoveredServicesList reloadData]; } if( ! browsers ) { browsers = [[NSMutableArray alloc] init]; [browsers retain]; } browser = [[NSNetServiceBrowser alloc] init]; { [browser setDelegate: self]; [browser searchForServicesOfType: @"_services._dns-sd._udp." inDomain: @""]; } [browsers addObject: browser]; [browseButton setTitle: @"Stop"]; isSearching = YES; } else { NSEnumerator *enumerator = nil; NSNetServiceBrowser *item = nil; enumerator = [browsers objectEnumerator]; while( (item = [enumerator nextObject]) ) { [item stop]; } [browsers removeAllObjects]; [browseButton setTitle: @"Start"]; isSearching = NO; } // // That's it // return; } /** * Description forthcoming * * */ - (id) convertAddresses: (id) array { NSMutableString *s = nil; NSString *ret = nil; int i = 0, count = 0; s = [[NSMutableString alloc] initWithString: @""]; count = [array count]; i = 0; for( ; i < count; i++ ) { NSData *data = nil; struct sockaddr *address; data = [array objectAtIndex: i]; address = (struct sockaddr *) [data bytes]; switch( address->sa_family ) { case AF_INET: { struct sockaddr_in *ip4; char dest[INET_ADDRSTRLEN]; ip4 = (struct sockaddr_in *) address; [s appendFormat: @"%s, ", inet_ntop(AF_INET, &ip4->sin_addr, dest, sizeof dest)]; } break; case AF_INET6: { struct sockaddr_in6 *ip6; char dest[INET6_ADDRSTRLEN]; ip6 = (struct sockaddr_in6 *) address; [s appendFormat: @"%s, ", inet_ntop(AF_INET6, &ip6->sin6_addr, dest, sizeof dest)]; } break; default: break; } } ret = ( [s length] ? [s stringByDeletingSuffix: @", "] : (NSString *) @"(unknown)" ); [s release]; // // That's it // return ret; } /** * Description forthcoming * * */ - (id) convertPorts: (id) array { NSData *data = nil; struct sockaddr *address; data = [array objectAtIndex: 0]; address = (struct sockaddr *) [data bytes]; switch( address->sa_family ) { case AF_INET: { struct sockaddr_in *ip4; ip4 = (struct sockaddr_in *) address; return [NSString stringWithFormat: @"%d", ntohs(ip4->sin_port)]; } break; case AF_INET6: { struct sockaddr_in6 *ip6; ip6 = (struct sockaddr_in6 *) address; return [NSString stringWithFormat: @"%d", ntohs(ip6->sin6_port)]; } break; default: break; } // // That's it // return @"(unknown)"; } /*************************************************************************** ** ** NSTableViewDelegate ** */ /** * Description forthcoming * * */ - (int) numberOfRowsInTableView: (NSTableView *) aTableView { // // That's it // return [foundServices count]; } /** * Description forthcoming * * */ - (id) tableView: (NSTableView *) aTableView objectValueForTableColumn: (NSTableColumn *) aTableColumn row: (int) rowIndex { NSNetService *obj = nil; obj = [foundServices objectAtIndex: rowIndex]; if( aTableColumn == [aTableView tableColumnWithIdentifier: @"servicename"] ) { return [obj name]; } if( aTableColumn == [aTableView tableColumnWithIdentifier: @"servicetype"] ) { id type = nil; type = [[[obj type] componentsSeparatedByString: @"_"] objectAtIndex: 1]; return [type stringByDeletingSuffix: @"."]; } if( aTableColumn == [aTableView tableColumnWithIdentifier: @"serviceprotocol"] ) { id entry = nil, proto = nil; entry = [[obj type] componentsSeparatedByString: @"_"]; if( [entry count] > 2 ) { proto = [entry objectAtIndex: 2]; return [[proto stringByDeletingSuffix: @"."] uppercaseString]; } return @""; } if( aTableColumn == [aTableView tableColumnWithIdentifier: @"servicedomain"] ) { return [[obj domain] stringByDeletingSuffix: @"."]; } if( aTableColumn == [aTableView tableColumnWithIdentifier: @"servicehost"] ) { return [obj hostName]; } if( aTableColumn == [aTableView tableColumnWithIdentifier: @"serviceaddress"] ) { NSArray *array = nil; array = [obj addresses]; if( nil != array && 0 < [array count] ) { id foo = [self convertAddresses: array]; return foo; } } if( aTableColumn == [aTableView tableColumnWithIdentifier: @"serviceport"] ) { NSArray *array = nil; array = [obj addresses]; if( nil != array && 0 < [array count] ) { return [self convertPorts: array]; } } if( aTableColumn == [aTableView tableColumnWithIdentifier: @"servicetxtrecord"] ) { NSDictionary *dict = nil; NSMutableString *result = nil; NSEnumerator *en; id key = nil, value = nil; result = [NSMutableString string]; dict = [NSNetService dictionaryFromTXTRecordData: [obj TXTRecordData]]; en = [dict keyEnumerator]; while( key = [en nextObject] ) { value = [dict objectForKey: key]; if( value != (NSData *) [NSNull null] ) { [result appendFormat: @" %@=%@, ", key, [NSString stringWithCString: [value bytes] length: [value length]]]; } else { [result appendFormat: @" %@, ", key]; } } // you never know... if( [result hasSuffix: @", "] ) { return [result substringToIndex: [result length] - 2]; } return result; } // // That's it // return @"(unresolved)"; } /** * Description forthcoming * * */ - (void) tableViewSelectionDidChange: (NSNotification *) note { // // That's it // return; } /*************************************************************************** ** ** NSNetServiceDelegation ** */ /** * Description forthcoming * * */ - (void) netServiceDidStop: (NSNetService *) sender { LOG(@"Did stop service <%@>", [sender name]); // // That's it // return; } /** * Description forthcoming * * */ - (void) netServiceWillPublish: (NSNetService *) sender { LOG(@"Will publish service <%@>", [sender name]); // // That's it // return; } /** * Description forthcoming * * */ - (void) netServiceDidPublish: (NSNetService *) sender { LOG(@"Published service <%@>", [sender name]); // // That's it // return; } /** * Description forthcoming * * */ - (void) netService: (NSNetService *) sender didNotPublish: (NSDictionary *) errorDict { LOG(@"Did not publish service <%@>", [sender name]); // // That's it // return; } /** * Description forthcoming * * */ - (void) netServiceWillResolve: (NSNetService *) sender { LOG(@"Will resolve service <%@>", [sender name]); // // That's it // return; } /** * Description forthcoming * * */ - (void) netServiceDidResolveAddress: (NSNetService *) sender { LOG(@"Resolved service <%@>", [sender name]); [discoveredServicesList reloadData]; [sender startMonitoring]; // // That's it // return; } /** * Description forthcoming * * */ - (void) netService: (NSNetService *) sender didNotResolve: (NSDictionary *) errorDict { LOG(@"Did not resolve service <%@>", [sender name]); // // That's it // return; } /** * Description forthcoming * * */ - (void) netService: (NSNetService *) sender didUpdateTXTRecordData: (NSData *) data { LOG(@"Service %@ did update: <%@>", [sender name], data); [discoveredServicesList reloadData]; // // That's it // return; } /*************************************************************************** ** ** NSNetServiceBrowserDelegation ** */ /** * Description forthcoming * * */ - (void) netServiceBrowserWillSearch: (NSNetServiceBrowser *) aNetServiceBrowser { LOG(@"Will search for <%@>", aNetServiceBrowser); isSearching = YES; // // That's it // return; } /** * Description forthcoming * * */ - (void) netServiceBrowserDidStopSearch: (NSNetServiceBrowser *) aNetServiceBrowser { LOG(@"Did stop searching"); isSearching = NO; [discoveredServicesList reloadData]; // // That's it // return; } /** * Description forthcoming * * */ - (void) netServiceBrowser: (NSNetServiceBrowser *) aNetServiceBrowser didNotSearch: (NSDictionary *) errorDict { LOG(@"Did not search for <%@>", aNetServiceBrowser); // Fake that we are searching to toggle the Start/Stop button isSearching = YES; [self toggleServiceActivation: self]; // // That's it // return; } /** * Description forthcoming * * */ - (void) netServiceBrowser: (NSNetServiceBrowser *) aNetServiceBrowser didFindDomain: (NSString *) domainString moreComing: (BOOL) moreComing { LOG(@"Found domain <%@>%@", domainString, moreComing ? @", more coming" : @""); if( ! moreComing ) [discoveredServicesList reloadData]; // // That's it // return; } /** * Description forthcoming * * */ - (void) netServiceBrowser: (NSNetServiceBrowser *) aNetServiceBrowser didRemoveDomain: (NSString *) domainString moreComing: (BOOL) moreComing { LOG(@"Removed domain <%@>%@", domainString, moreComing ? @", more coming" : @""); if( ! moreComing ) [discoveredServicesList reloadData]; // // That's it // return; } /** * Description forthcoming * * */ - (void) netServiceBrowser: (NSNetServiceBrowser *) aNetServiceBrowser didFindService: (NSNetService *) aNetService moreComing: (BOOL) moreComing { LOG(@"Found service <%@> <%@> <%@>%@", [aNetService name], [aNetService type], [aNetService domain], moreComing ? @", more coming" : @""); if( [[aNetService domain] isEqualToString: @"."] ) // local domain { NSNetServiceBrowser *browser = nil; NSString *service = nil; browser = [[NSNetServiceBrowser alloc] init]; [browsers addObject: browser]; service = [NSString stringWithFormat: @"%@.%@", [aNetService name], [[[aNetService type] componentsSeparatedByString: @"."] objectAtIndex: 0]]; [browser setDelegate: self]; [browser searchForServicesOfType: service inDomain: @"local"]; } else if( [[aNetService domain] isEqualToString: @"org."] ) { NSNetServiceBrowser *browser = nil; NSString *service = nil; browser = [[NSNetServiceBrowser alloc] init]; [browsers addObject: browser]; service = [NSString stringWithFormat: @"%@.%@", [aNetService name], [[[aNetService type] componentsSeparatedByString: @"."] objectAtIndex: 0]]; [browser setDelegate: self]; [browser searchForServicesOfType: service inDomain: @"dns-sd.org."]; } else { [foundServices addObject: aNetService]; [aNetService setDelegate: self]; [aNetService resolve]; } if( ! moreComing ) [discoveredServicesList reloadData]; // // That's it // return; } /** * Description forthcoming * * */ - (void) netServiceBrowser: (NSNetServiceBrowser *) aNetServiceBrowser didRemoveService: (NSNetService *) aNetService moreComing: (BOOL) moreComing { LOG(@"Removed service <%@>%@", [aNetService name], moreComing ? @", more coming" : @""); [foundServices removeObject: aNetService]; if( ! moreComing ) [discoveredServicesList reloadData]; // // That's it // return; } @end /*************************************************************************** ** ** Functions ** */ /* ** End of File. ** ****************************************************************************/