/* FILE: AppController.m * * Project TChat * Class AppController * Creator Chris B. Vetter * Maintainer Chris B. Vetter * Creation Date Mon Sep 11 15:17:32 CEST 2006 * * Copyright (c) 2006 * * TChat 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 "WindowController.h" #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import // // Define // #define Localize( key ) NSLocalizedString(key, @"") #define LOG( f, args... ) NSDebugLLog(@"TChat", f, ##args) #define NC [NSNotificationCenter defaultCenter] // // Typedef // // // Public // // // Private // static AppController * sharedController = nil; // // Private Interface // @interface AppController (Private) - (void) createMenus; - (void) setupService; - (void) setupBrowser; - (void) stopService; @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; [NSBundle loadNibNamed: @"Controller.gorm" owner: self]; image = [NSImage imageNamed: @"TChat.png"]; [NSApp setApplicationIconImage: image]; } // // That's it // return sharedController; } /** * Description forthcoming * * */ - (void) dealloc { { if( listeningSocket ) [self stopService]; } [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"]; [self stopService]; [serviceBrowser stop]; [domainBrowser stop]; // // That's it // return; } /** * Description forthcoming * * */ - (void) awakeFromNib { [title setStringValue: Localize(@"Name:")]; [title setEditable: NO]; [title setSelectable: NO]; [title setAlignment: NSRightTextAlignment]; [nameField setStringValue: NSFullUserName()]; [browseButton setTitle: Localize(@"Start Service")]; [browseButton setTarget: self]; [browseButton setAction: @selector(toggleServiceActivation:)]; { NSTableColumn *column = nil; column = [discoveredServicesList tableColumnWithIdentifier: @"column2"]; if( column ) [discoveredServicesList removeTableColumn: column]; column = [discoveredServicesList tableColumnWithIdentifier: @"column1"]; [[column headerCell] setStringValue: Localize(@"Service Name")]; [[column headerCell] setAlignment: NSLeftTextAlignment]; [column setResizable: YES]; [column setWidth: 458.]; } #if 0 [discoveredServicesList setAllowsMultipleSelection: NO]; [discoveredServicesList setAllowsColumnSelection: NO]; [discoveredServicesList setAllowsEmptySelection: NO]; [discoveredServicesList setAllowsColumnResizing: YES]; #endif /* 0 */ [discoveredServicesList setDrawsGrid: YES]; [discoveredServicesList setAutoresizesAllColumnsToFit: YES]; [discoveredServicesList setDoubleAction: @selector(openChatSender:)]; [discoveredServicesList setDataSource: self]; [discoveredServicesList setDelegate: self]; [discoveredServicesList setTarget: self]; [discoveredServicesList setCornerView: nil]; [window setTitle: Localize(@"Tryst Chat Example")]; [window becomeKeyWindow]; // // 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 * * */ - (void) setupService { struct sockaddr_in6 addr; int sockfd, namelen; sockfd = socket(AF_INET6, SOCK_STREAM, 0); memset(&addr, 0, sizeof(struct sockaddr_in6)); addr.sin6_family = AF_INET6; addr.sin6_addr = in6addr_any; addr.sin6_port = htons(0); bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr)); listen(sockfd, 5 ); namelen = sizeof(struct sockaddr_in6); getsockname(sockfd, (struct sockaddr *)&addr, &namelen); listeningSocket = [[NSFileHandle alloc] initWithFileDescriptor: sockfd]; [NC addObserver: self selector: @selector(openChatReceiver:) name: NSFileHandleConnectionAcceptedNotification object: nil]; [listeningSocket acceptConnectionInBackgroundAndNotify]; service = [[NSNetService alloc] initWithDomain: @"local." type: @"_trystchat._tcp." name: [nameField stringValue] port: htons(addr.sin6_port)]; [service setDelegate: self]; { NSData *data = nil; NSDictionary *dictionary = nil; dictionary = [NSDictionary dictionaryWithObject: [nameField stringValue] forKey: @"user"]; data = [NSNetService dataFromTXTRecordDictionary: dictionary]; [service setTXTRecordData: data]; } [service publish]; // // That's it // return; } /** * Description forthcoming * * */ - (void) setupBrowser { if( ! serviceBrowser ) { serviceBrowser = [[NSNetServiceBrowser alloc] init]; [serviceBrowser setDelegate:self]; } if( ! domainBrowser ) { domainBrowser = [[NSNetServiceBrowser alloc] init]; [domainBrowser setDelegate:self]; } if( ! foundServices ) foundServices = [[NSMutableArray alloc] init]; [serviceBrowser searchForServicesOfType: @"_trystchat._tcp." inDomain: @""]; // // That's it // return; } /** * Description forthcoming * * */ - (void) stopService { [service stop]; [[NSNotificationCenter defaultCenter] removeObserver: self]; [listeningSocket closeFile]; [listeningSocket release]; listeningSocket = nil; // // That's it // return; } /** * Description forthcoming * * */ - (IBAction) toggleServiceActivation: (id) sender { switch( [sender state] ) { case NSOnState : { [self setupService]; [self setupBrowser]; [nameField setEnabled: NO]; [browseButton setTitle: @"Stop Service"]; } break; case NSOffState : { [self stopService]; [serviceBrowser stop]; [domainBrowser stop]; [nameField setEnabled: YES]; [browseButton setTitle: @"Start Service"]; } break; } // // That's it // return; } /** * Description forthcoming * * */ - (void) openChatSender: (id) sender { NSNetService *remoteService = nil; NSData *address = nil; NSFileHandle *remoteFH = nil; WindowController *wc = nil; struct sockaddr *socketAddress = { 0 }; int socketToRemoteServer; remoteService = [foundServices objectAtIndex: [sender selectedRow]]; address = [[remoteService addresses] objectAtIndex: 0]; socketAddress = (struct sockaddr *)[address bytes]; if( socketAddress ) { char buffer[256]; switch(socketAddress->sa_family) { case AF_INET: break; default: LOG(@"socket family not supported"); return; } socketToRemoteServer = socket(AF_INET, SOCK_STREAM, 0); if( 0 < socketToRemoteServer ) { if(connect(socketToRemoteServer, (struct sockaddr *)socketAddress, sizeof(*socketAddress)) == 0) { remoteFH = [[NSFileHandle alloc] initWithFileDescriptor: socketToRemoteServer]; wc = [[WindowController alloc] initWithConnection: remoteFH myName: [nameField stringValue]]; [wc showWindow: nil]; } else { LOG(@"couldn't connect -- %d", errno); close(socketToRemoteServer); } } else { LOG(@"no socket"); } } // // That's it // return; } /** * Description forthcoming * * */ - (void) openChatReceiver: (NSNotification *) note { WindowController *wc = nil; NSFileHandle *remoteFH = nil; remoteFH = [[note userInfo] objectForKey: NSFileHandleNotificationFileHandleItem]; wc = [[WindowController alloc] initWithConnection: remoteFH myName: [nameField stringValue]]; // // That's it // return; } /*************************************************************************** ** ** 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 { // // That's it // return [(NSNetService*)[foundServices objectAtIndex: rowIndex] name]; } /** * 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; } - (void) netServiceDidPublish: (NSNetService *) sender { LOG(@"Did publish service %@", [sender name]); // // That's it // return; } /** * Description forthcoming * * */ - (void) netService: (NSNetService *) sender didNotPublish: (NSDictionary *) errorDict { LOG(@"Could not publish service %@", [sender name]); [browseButton setState: NSOffState]; [self toggleServiceActivation: browseButton]; // // 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 { NSData *address = nil; struct sockaddr *socketAddress = { 0 }; NSString *ipAddressString = nil, *portString = nil; char buffer[256]; int index = 0; LOG(@"Did resolve service %@", [sender name]); LOG(@"Getting address, total %d", [[sender addresses] count]); for( ; index < [[sender addresses] count]; index++ ) { address = [[sender addresses] objectAtIndex:index]; socketAddress = (struct sockaddr *)[address bytes]; if( socketAddress->sa_family == AF_INET ) { LOG(@"IPv4"); break; } if( socketAddress->sa_family == AF_INET6 ) { LOG(@"IPv6"); break; } } if( socketAddress ) { switch( socketAddress->sa_family ) { case AF_INET: if (inet_ntop(AF_INET, &((struct sockaddr_in *)socketAddress)->sin_addr, buffer, sizeof(buffer))) { ipAddressString = [NSString stringWithCString: buffer]; portString = [NSString stringWithFormat:@"%d", ntohs(((struct sockaddr_in *)socketAddress)->sin_port)]; } LOG(@"Got an address -- %@:%@", ipAddressString, portString); break; case AF_INET6: LOG(@"Looks like IPv6"); return; default: LOG(@"Unknown address family"); break; } } else LOG(@"Cannot figure out address"); // // That's it // return; } /** * Description forthcoming * * */ - (void) netService: (NSNetService *) sender didNotResolve: (NSDictionary *) errorDict { LOG(@"Could not resolve service %@", [sender name]); // // That's it // return; } /*************************************************************************** ** ** NSNetServiceBrowserDelegation ** */ /** * Description forthcoming * * */ - (void) netServiceBrowser: (NSNetServiceBrowser *) aNetServiceBrowser didFindService: (NSNetService *) aNetService moreComing: (BOOL) moreComing { LOG(@"Did find service %@", [aNetService name]); // Prevent adding our own name if( ! [[aNetService name] isEqualToString: [nameField stringValue]] ) { [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(@"Did remove service %@", [aNetService name]); [foundServices removeObject: aNetService]; if( ! moreComing ) [discoveredServicesList reloadData]; // // That's it // return; } /** * Description forthcoming * * */ - (void) netServiceBrowserDidStopSearch: (NSNetServiceBrowser *) aNetServiceBrowser { LOG(@"Did stop searching"); if( aNetServiceBrowser == serviceBrowser ) { [foundServices removeAllObjects]; [discoveredServicesList reloadData]; } // // That's it // return; } /** * Description forthcoming * * */ - (void) netServiceBrowser: (NSNetServiceBrowser *) aNetServiceBrowser didNotSearch: (NSDictionary *) errorDict { if( aNetServiceBrowser == serviceBrowser ) LOG(@"Did not search for services"); else LOG(@"Did not search for domains"); [browseButton setState: NSOffState]; [self toggleServiceActivation: browseButton]; // // That's it // return; } /** * Description forthcoming * * */ - (void) netServiceBrowserWillSearch: (NSNetServiceBrowser *) aNetServiceBrowser { if( aNetServiceBrowser == serviceBrowser ) LOG(@"Will search for services"); else LOG(@"Will search for domains"); // // That's it // return; } /** * Description forthcoming * * */ - (void) netServiceBrowser: (NSNetServiceBrowser *) aNetServiceBrowser didFindDomain: (NSString *) domainString moreComing: (BOOL) moreComing { LOG(@"Did find domain %@", domainString); // // That's it // return; } /** * Description forthcoming * * */ - (void) netServiceBrowser: (NSNetServiceBrowser *) aNetServiceBrowser didRemoveDomain: (NSString *) domainString moreComing: (BOOL) moreComing { LOG(@"Did remove domain %@", domainString); // // That's it // return; } @end /*************************************************************************** ** ** Functions ** */ /* ** End of File. ** ****************************************************************************/