2 * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
23 Change History (most recent first):
25 $Log: BrowserController.m,v $
26 Revision 1.18 2003/08/12 19:55:07 cheshire
31 #import "BrowserController.h"
33 #include "arpa/inet.h"
36 MyHandleMachMessage ( CFMachPortRef port, void * msg, CFIndex size, void * info )
38 DNSServiceDiscovery_handleReply(msg);
42 DNSServiceBrowserReplyResultType resultType, // One of DNSServiceBrowserReplyResultType
43 const char *replyName,
44 const char *replyType,
45 const char *replyDomain,
46 DNSServiceDiscoveryReplyFlags flags, // DNS Service Discovery reply flags information
50 [[NSApp delegate] updateBrowseWithResult:resultType name:[NSString stringWithUTF8String:replyName] type:[NSString stringWithUTF8String:replyType] domain:[NSString stringWithUTF8String:replyDomain] flags:flags];
55 DNSServiceDomainEnumerationReplyResultType resultType,
56 const char *replyDomain,
57 DNSServiceDiscoveryReplyFlags flags,
61 [[NSApp delegate] updateEnumWithResult:resultType domain:[NSString stringWithUTF8String:replyDomain] flags:flags];
67 struct sockaddr *interface,
68 struct sockaddr *address,
69 const char *txtRecord,
70 DNSServiceDiscoveryReplyFlags flags,
74 [[NSApp delegate] resolveClientWithInterface:interface address:address txtRecord:[NSString stringWithUTF8String:txtRecord]];
79 @implementation BrowserController //Begin implementation of BrowserController methods
81 - (void)registerDefaults
83 NSMutableDictionary *regDict = [NSMutableDictionary dictionary];
85 NSArray *typeArray = [NSArray arrayWithObjects:@"_ftp._tcp.", @"_tftp._tcp.",
86 @"_ssh._tcp.", @"_telnet._tcp.",
88 @"_printer._tcp.", @"_ipp._tcp.",
89 @"_ichat._tcp.", @"_eppc._tcp.",
90 @"_afpovertcp._tcp.", @"_afpovertcp._tcp.", @"_MacOSXDupSuppress._tcp.", nil];
91 NSArray *nameArray = [NSArray arrayWithObjects:@"File Transfer (ftp)", @"Trivial File Transfer (tftp)",
92 @"Secure Shell (ssh)", @"Telnet",
94 @"LPR Printer", @"IPP Printer",
95 @"iChat", @"Remote AppleEvents",
96 @"AppleShare Server", @"SMB File Server", @"Mystery Service", nil];
98 [regDict setObject:typeArray forKey:@"SrvTypeKeys"];
99 [regDict setObject:nameArray forKey:@"SrvNameKeys"];
101 [[NSUserDefaults standardUserDefaults] registerDefaults:regDict];
107 [self registerDefaults];
114 - (void)awakeFromNib //BrowserController startup procedure
118 srvtypeKeys = [NSMutableArray array]; //Define arrays for Type, Domain, and Name
119 srvnameKeys = [NSMutableArray array];
121 domainKeys = [NSMutableArray array];
124 nameKeys = [NSMutableArray array];
127 [srvtypeKeys retain]; //Keep arrays in memory until BrowserController closes
128 [srvnameKeys retain]; //Keep arrays in memory until BrowserController closes
129 [typeField setDataSource:self]; //Set application fields' data source to BrowserController
130 [typeField sizeLastColumnToFit]; //and set column sizes to use their whole table's width.
131 [nameField setDataSource:self];
132 [nameField sizeLastColumnToFit];
133 [domainField setDataSource:self];
134 [domainField sizeLastColumnToFit];
136 [nameField setDoubleAction:@selector(connect:)];
138 //[srvtypeKeys addObject:@"_ftp._tcp."]; //Add supported protocols and domains to their
139 //[srvnameKeys addObject:@"File Transfer (ftp)"];
140 //[srvtypeKeys addObject:@"_printer._tcp."]; //respective arrays
141 //[srvnameKeys addObject:@"Printer (lpr)"];
142 //[srvtypeKeys addObject:@"_http._tcp."]; //respective arrays
143 //[srvnameKeys addObject:@"Web Server (http)"];
144 //[srvtypeKeys addObject:@"_afp._tcp."]; //respective arrays
145 //[srvnameKeys addObject:@"AppleShare Server (afp)"];
147 [ipAddressField setStringValue:@""];
148 [portField setStringValue:@""];
149 [textField setStringValue:@""];
151 [srvtypeKeys addObjectsFromArray:[[NSUserDefaults standardUserDefaults] arrayForKey:@"SrvTypeKeys"]];
152 [srvnameKeys addObjectsFromArray:[[NSUserDefaults standardUserDefaults] arrayForKey:@"SrvNameKeys"]];
155 [typeField reloadData]; //Reload (redraw) data in fields
156 [domainField reloadData];
158 [self loadDomains:self];
162 - (void)dealloc //Deallocation method
164 [srvtypeKeys release];
165 [srvnameKeys release];
167 [domainKeys release];
170 -(void)tableView:(NSTableView *)theTableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(int)row
175 - (int)numberOfRowsInTableView:(NSTableView *)theTableView //Begin mandatory TableView methods
177 if (theTableView == typeField)
179 return [srvnameKeys count];
181 if (theTableView == domainField)
183 return [domainKeys count];
185 if (theTableView == nameField)
187 return [nameKeys count];
189 if (theTableView == serviceDisplayTable)
191 return [srvnameKeys count];
196 - (id)tableView:(NSTableView *)theTableView objectValueForTableColumn:(NSTableColumn *)theColumn row:(int)rowIndex
198 if (theTableView == typeField)
200 return [srvnameKeys objectAtIndex:rowIndex];
202 if (theTableView == domainField)
204 return [domainKeys objectAtIndex:rowIndex];
206 if (theTableView == nameField)
208 return [[nameKeys sortedArrayUsingSelector:@selector(compare:)] objectAtIndex:rowIndex];
210 if (theTableView == serviceDisplayTable)
212 if (theColumn == typeColumn) {
213 return [srvtypeKeys objectAtIndex:rowIndex];
215 if (theColumn == nameColumn) {
216 return [srvnameKeys objectAtIndex:rowIndex];
222 } //End of mandatory TableView methods
224 - (IBAction)handleTypeClick:(id)sender //Handle clicks for Type
226 int index=[sender selectedRow]; //Find index of selected row
227 if (index==-1) return; //Error checking
228 SrvType = [srvtypeKeys objectAtIndex:index]; //Save desired Type
229 SrvName = [srvnameKeys objectAtIndex:index]; //Save desired Type
231 [ipAddressField setStringValue:@""];
232 [portField setStringValue:@""];
233 [textField setStringValue:@""];
235 [self update:SrvType Domain:Domain]; //If Type and Domain are set, update records
238 - (IBAction)handleDomainClick:(id)sender //Handle clicks for Domain
240 int index=[sender selectedRow]; //Find index of selected row
241 if (index==-1) return; //Error checking
242 Domain = [domainKeys objectAtIndex:index]; //Save desired Domain
244 [ipAddressField setStringValue:@""];
245 [portField setStringValue:@""];
246 [textField setStringValue:@""];
248 if (SrvType!=NULL) [self update:SrvType Domain:Domain]; //If Type and Domain are set, update records
251 - (IBAction)handleNameClick:(id)sender //Handle clicks for Name
253 int index=[sender selectedRow]; //Find index of selected row
254 if (index==-1) return; //Error checking
255 Name=[[nameKeys sortedArrayUsingSelector:@selector(compare:)] objectAtIndex:index]; //Save desired name
258 CFMachPortRef cfMachPort;
259 CFMachPortContext context;
260 Boolean shouldFreeInfo;
261 dns_service_discovery_ref dns_client;
263 CFRunLoopSourceRef rls;
267 context.retain = NULL;
268 context.release = NULL;
269 context.copyDescription = NULL;
271 [ipAddressField setStringValue:@"?"];
272 [portField setStringValue:@"?"];
273 [textField setStringValue:@"?"];
274 // start an enumerator on the local server
275 dns_client = DNSServiceResolverResolve
277 (char *)[Name UTF8String],
278 (char *)[SrvType UTF8String],
279 (char *)(Domain?[Domain UTF8String]:""),
284 port = DNSServiceDiscoveryMachPort(dns_client);
287 cfMachPort = CFMachPortCreateWithPort ( kCFAllocatorDefault, port, ( CFMachPortCallBack ) MyHandleMachMessage,&context,&shouldFreeInfo );
289 /* Create and add a run loop source for the port */
290 rls = CFMachPortCreateRunLoopSource(NULL, cfMachPort, 0);
291 CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
294 printf("Could not obtain client port\n");
300 - (IBAction)loadDomains:(id)sender
302 CFMachPortRef cfMachPort;
303 CFMachPortContext context;
304 Boolean shouldFreeInfo;
305 dns_service_discovery_ref dns_client;
307 CFRunLoopSourceRef rls;
311 context.retain = NULL;
312 context.release = NULL;
313 context.copyDescription = NULL;
315 // start an enumerator on the local server
316 dns_client = DNSServiceDomainEnumerationCreate
323 port = DNSServiceDiscoveryMachPort(dns_client);
326 cfMachPort = CFMachPortCreateWithPort ( kCFAllocatorDefault, port, ( CFMachPortCallBack ) MyHandleMachMessage,&context,&shouldFreeInfo );
328 /* Create and add a run loop source for the port */
329 rls = CFMachPortCreateRunLoopSource(NULL, cfMachPort, 0);
330 CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
333 printf("Could not obtain client port\n");
338 - (IBAction)update:theType Domain:theDomain; //The Big Kahuna: Fetch PTR records and update application
340 const char * DomainC;
341 const char * TypeC=[theType UTF8String]; //Type in C string format
344 DomainC = [theDomain UTF8String]; //Domain in C string format
349 [nameKeys removeAllObjects]; //Get rid of displayed records if we're going to go get new ones
350 [nameField reloadData]; //Reload (redraw) names to show the old data is gone
352 // get rid of the previous browser if one exists
354 DNSServiceDiscoveryDeallocate(browse_client);
358 // now create a browser to return the values for the nameField ...
360 CFMachPortRef cfMachPort;
361 CFMachPortContext context;
362 Boolean shouldFreeInfo;
364 CFRunLoopSourceRef rls;
368 context.retain = NULL;
369 context.release = NULL;
370 context.copyDescription = NULL;
372 // start an enumerator on the local server
373 browse_client = DNSServiceBrowserCreate
381 port = DNSServiceDiscoveryMachPort(browse_client);
384 cfMachPort = CFMachPortCreateWithPort ( kCFAllocatorDefault, port, ( CFMachPortCallBack ) MyHandleMachMessage,&context,&shouldFreeInfo );
386 /* Create and add a run loop source for the port */
387 rls = CFMachPortCreateRunLoopSource(NULL, cfMachPort, 0);
388 CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
391 printf("Could not obtain client port\n");
399 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication //Quit when main window is closed
404 - (BOOL)windowShouldClose:(NSWindow *)sender //Save domains to our domain file when quitting
406 [domainField reloadData];
410 - (void)updateEnumWithResult:(int)resultType domain:(NSString *)domain flags:(int)flags
412 // new domain received
413 if (DNSServiceDomainEnumerationReplyAddDomain == resultType || DNSServiceDomainEnumerationReplyAddDomainDefault == resultType) {
414 // add the domain to the list
415 [domainKeys addObject:domain];
417 // remove the domain from the list
418 NSEnumerator *dmnEnum = [domainKeys objectEnumerator];
419 NSString *aDomain = nil;
421 while (aDomain = [dmnEnum nextObject]) {
422 if ([aDomain isEqualToString:domain]) {
423 [domainKeys removeObject:domain];
428 // update the domain table
429 [domainField reloadData];
435 - (void)updateBrowseWithResult:(int)type name:(NSString *)name type:(NSString *)resulttype domain:(NSString *)domain flags:(int)flags
438 //NSLog(@"Received result %@ %@ %@ %d", name, resulttype, domain, type);
440 if (([domain isEqualToString:Domain] || [domain isEqualToString:@"local."]) && [resulttype isEqualToString:SrvType]) {
442 if (type == DNSServiceBrowserReplyRemoveInstance) {
443 if ([nameKeys containsObject:name]) {
444 [nameKeys removeObject:name];
447 if (type == DNSServiceBrowserReplyAddInstance) {
448 if (![nameKeys containsObject:name]) {
449 [nameKeys addObject:name];
453 // If not expecting any more data, then reload (redraw) Name TableView with newly found data
454 if ((flags & kDNSServiceDiscoveryMoreRepliesImmediately) == 0)
455 [nameField reloadData];
460 - (void)resolveClientWithInterface:(struct sockaddr *)interface address:(struct sockaddr *)address txtRecord:(NSString *)txtRecord
462 if (address->sa_family != AF_INET) return; // For now we only handle IPv4
463 //printf("interface length = %d, port = %d, family = %d, address = %s\n", ((struct sockaddr_in *)interface)->sin_len, ((struct sockaddr_in *)interface)->sin_port, ((struct sockaddr_in *)interface)->sin_family, inet_ntoa(((struct in_addr)((struct sockaddr_in *)interface)->sin_addr)));
464 //printf("address length = %d, port = %d, family = %d, address = %s\n", ((struct sockaddr_in *)address)->sin_len, ((struct sockaddr_in *)address)->sin_port, ((struct sockaddr_in *)address)->sin_family, inet_ntoa(((struct in_addr)((struct sockaddr_in *)address)->sin_addr)));
465 NSString *ipAddr = [NSString stringWithCString:inet_ntoa(((struct in_addr)((struct sockaddr_in *)address)->sin_addr))];
466 int port = ((struct sockaddr_in *)address)->sin_port;
468 [ipAddressField setStringValue:ipAddr];
469 [portField setIntValue:port];
470 [textField setStringValue:txtRecord];
475 - (void)connect:(id)sender
477 NSString *ipAddr = [ipAddressField stringValue];
478 int port = [portField intValue];
479 NSString *txtRecord = [textField stringValue];
481 if (!txtRecord) txtRecord = @"";
483 if (!ipAddr || !port) return;
485 if ([SrvType isEqualToString:@"_ftp._tcp."]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"ftp://%@:%d/", ipAddr, port]]];
486 else if ([SrvType isEqualToString:@"_tftp._tcp."]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"tftp://%@:%d/", ipAddr, port]]];
487 else if ([SrvType isEqualToString:@"_ssh._tcp."]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"ssh://%@:%d/", ipAddr, port]]];
488 else if ([SrvType isEqualToString:@"_telnet._tcp."]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"telnet://%@:%d/", ipAddr, port]]];
489 else if ([SrvType isEqualToString:@"_http._tcp."]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%d", ipAddr, port]]];
490 else if ([SrvType isEqualToString:@"_printer._tcp."]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"lpr://%@:%d/", ipAddr, port]]];
491 else if ([SrvType isEqualToString:@"_ipp._tcp."]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"ipp://%@:%d/", ipAddr, port]]];
492 else if ([SrvType isEqualToString:@"_afpovertcp._tcp."]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"afp://%@:%d/", ipAddr, port]]];
493 else if ([SrvType isEqualToString:@"_smb._tcp."]) [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"smb://%@:%d/", ipAddr, port]]];
498 - (IBAction)handleTableClick:(id)sender
500 //populate the text fields
503 - (IBAction)removeSelected:(id)sender
505 // remove the selected row and force a refresh
507 int selectedRow = [serviceDisplayTable selectedRow];
511 [srvtypeKeys removeObjectAtIndex:selectedRow];
512 [srvnameKeys removeObjectAtIndex:selectedRow];
514 [[NSUserDefaults standardUserDefaults] setObject:srvtypeKeys forKey:@"SrvTypeKeys"];
515 [[NSUserDefaults standardUserDefaults] setObject:srvnameKeys forKey:@"SrvNameKeys"];
517 [typeField reloadData];
518 [serviceDisplayTable reloadData];
522 - (IBAction)addNewService:(id)sender
524 // add new entries from the edit fields to the arrays for the defaults
526 if ([[serviceTypeField stringValue] length] && [[serviceNameField stringValue] length]) {
527 [srvtypeKeys addObject:[serviceTypeField stringValue]];
528 [srvnameKeys addObject:[serviceNameField stringValue]];
530 [[NSUserDefaults standardUserDefaults] setObject:srvtypeKeys forKey:@"SrvTypeKeys"];
531 [[NSUserDefaults standardUserDefaults] setObject:srvnameKeys forKey:@"SrvNameKeys"];
533 [typeField reloadData];
534 [serviceDisplayTable reloadData];