Mac OS X Reference Library Apple Developer
Search

DockBrowser.c

/*
    File:  DockBrowser.c
 
    Copyright:  (c) Copyright 2003-2005 Apple Computer, Inc. All rights reserved.
 
    Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple Computer, Inc.
    ("Apple") in consideration of your agreement to the following terms, and your
    use, installation, modification or redistribution of this Apple software
    constitutes acceptance of these terms.  If you do not agree with these terms,
    please do not use, install, modify or redistribute this Apple software.
 
    In consideration of your agreement to abide by the following terms, and subject
    to these terms, Apple grants you a personal, non-exclusive license, under Apple's
    copyrights in this original Apple software (the "Apple Software"), to use,
    reproduce, modify and redistribute the Apple Software, with or without
    modifications, in source and/or binary forms; provided that if you redistribute
    the Apple Software in its entirety and without modifications, you must retain
    this notice and the following text and disclaimers in all such redistributions of
    the Apple Software.  Neither the name, trademarks, service marks or logos of
    Apple Computer, Inc. may be used to endorse or promote products derived from the
    Apple Software without specific prior written permission from Apple.  Except as
    expressly stated in this notice, no other rights or licenses, express or implied,
    are granted by Apple herein, including but not limited to any patent rights that
    may be infringed by your derivative works or by other works in which the Apple
    Software may be incorporated.
 
    The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO
    WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
    WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
    PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
    COMBINATION WITH YOUR PRODUCTS.
 
    IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
    GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
    ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
    OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
    (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
    ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
    Change History (most recent first):
        02/11/05        1.1
        04/01/04        1.0d4
        10/17/03        1.0d3
        02/14/03        1.0d2
*/
 
 
#include <Carbon/Carbon.h>
#include <CoreFoundation/CoreFoundation.h>
#include <CoreServices/CoreServices.h>
#include <ApplicationServices/ApplicationServices.h>
 
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
 
 
#pragma mark *********** Constants ***********
 
#define kMyPathKey              CFSTR("path")
#define kMyTextRecordHTTP       CFSTR("note=Marc's Office\001path=/index.html")
 
#define kMyDefaultDomain        CFSTR("")
#define kMyDefaultName          CFSTR("")
 
#define kMyTypeAFP              CFSTR("_afpovertcp._tcp.")
#define kMyTypeHTTP             CFSTR("_http._tcp.")
#define kMyPortAFP              548
#define kMyPortHTTP             80
 
#define kMyServiceTypeKey       CFSTR("ServiceType")
#define kMyRegisterEnabledKey   CFSTR("RegisterEnabled")
 
 
enum {
    kMyRadioButtonAFP = 1,
    kMyRadioButtonHTTP = 2,
    kMyRadioButtons = 1,
    kMyCheckBox = 2,
    kMyDockBrowser = 'DBRO',
    kMyServiceInfo = 'SRVI'
};
 
 
typedef struct{
    int refCount;
    char name[64];
    char type[256];
    char domain[256];
} MyService;
        
        
// Bonjour Globals
CFNetServiceBrowserRef gServiceBrowserRef;
CFNetServiceRef gRegisteredService;
CFNetServiceRef gServiceBeingResolved;
CFMutableDictionaryRef gServiceDictionary;
CFStringRef gServiceType;
UInt16 gPortNumber;
CFStringRef gTextRecord;
 
 
// Other Globals
SInt16 gRadioButtonValue;
WindowRef gPreferencesWindow;
ControlRef gRadioButtonControl;
ControlRef gCheckBoxControl;
Boolean gRegisterEnabled;
Boolean gPreferencesChanged;
MenuRef gDockMenu;
 
 
 
#pragma mark *********** Prototypes **********
 
static void MyRegisterCallBack(CFNetServiceRef, CFStreamError *, void *);
static void MyResolveCallback(CFNetServiceRef, CFStreamError *, void *);
static void MyBrowseCallBack(CFNetServiceBrowserRef, CFOptionFlags, CFTypeRef, CFStreamError *, void *);
static CFStringRef MyCreateDictionaryKey(CFNetServiceRef);
 
#pragma mark *********** Functions ***********
 
 
 
void
MyCancelResolve()
{
    assert(gServiceBeingResolved != NULL);
    
    CFNetServiceUnscheduleFromRunLoop(gServiceBeingResolved, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
    CFNetServiceSetClient(gServiceBeingResolved, NULL, NULL);
    CFNetServiceCancel(gServiceBeingResolved);
    CFRelease(gServiceBeingResolved);
    gServiceBeingResolved = NULL;
    
    return;
}
 
 
 
static void
MyResolveService(CFStringRef name, CFStringRef type, CFStringRef domain)
{
    CFNetServiceClientContext context = { 0, NULL, NULL, NULL, NULL };
    CFStreamError error;
    
    assert(name   != NULL);
    assert(type   != NULL);
    assert(domain != NULL);
        
    if (gServiceBeingResolved) {
    
        /* This app only allows one resolve at a time, but CFNetServices places no restrictions on the number of
        simultaneous resolves.  However, leaving resolves running is bad for network traffic, so you should cancel the 
        resolve as soon as your callback is called.  Because most resolves will happen instantaneously after calling
        CFNetServiceResolve, if we end up canceling a previous resolve, it's probably because the previous service became
        unreachable. */
        
        fprintf(stderr, "Resolve canceled\n");
        MyCancelResolve();
    }
    
    gServiceBeingResolved = CFNetServiceCreate(kCFAllocatorDefault, domain, type, name, 0);
    assert(gServiceBeingResolved != NULL);
    
    CFNetServiceSetClient(gServiceBeingResolved, MyResolveCallback, &context);
    CFNetServiceScheduleWithRunLoop(gServiceBeingResolved, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
    
    if (CFNetServiceResolve(gServiceBeingResolved, &error) == false) {
    
        // Something went wrong so lets clean up.
        CFNetServiceUnscheduleFromRunLoop(gServiceBeingResolved, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
        CFNetServiceSetClient(gServiceBeingResolved, NULL, NULL);
        CFRelease(gServiceBeingResolved);
        gServiceBeingResolved = NULL;
        
        fprintf(stderr, "CFNetServiceResolve returned (domain = %d, error = %ld)\n", error.domain, error.error);
    }
    
    return;
}
 
 
 
static CGImageRef
MyCreateCGImageFromPNG(CFStringRef fileName)
{
    CGImageRef image;
    CFBundleRef bundle;
    CGDataProviderRef myProvider;
    CFURLRef url;
    
    assert(fileName != NULL);
    
    bundle = CFBundleGetMainBundle();
    assert(bundle != NULL);
    
    url = CFBundleCopyResourceURL(bundle, fileName, NULL, NULL);
    assert(url != NULL);
    
    /* If DockBrowser crashes, it's probably because CGDataProviderCreateWithURL()
    returned NULL after waking from sleep.  You'll probably see this in the Console...
    kCGErrorFailure : CGDataProviderCreateWithURL: failed with error code -10 */
    
    myProvider = CGDataProviderCreateWithURL(url);
    assert(myProvider != NULL);
    
    image = CGImageCreateWithPNGDataProvider(myProvider, NULL, false, kCGRenderingIntentDefault);
    
    CGDataProviderRelease(myProvider);
    CFRelease(url);
    
    return image;
}
 
 
 
static void
MyUpdateDockIcon()
{
    int digits;
    static CGImageRef badgeImage = NULL;
    static CGImageRef iconImage = NULL;
    static int previousDigits = 0;
    CGContextRef cgContext;
    CFIndex count;
 
    assert(gServiceDictionary != NULL);
    count = CFDictionaryGetCount(gServiceDictionary);
 
    if (count > 0 && count < 100000) {
    
        digits = 1 + (int)log10(count);
 
        if (badgeImage == NULL || digits != previousDigits) {
            
            if (digits != previousDigits && badgeImage != NULL) {
                CGImageRelease(badgeImage);
            }
            
            switch (digits) {
              
                case 1:
                case 2:
                    badgeImage = MyCreateCGImageFromPNG(CFSTR("Badge1&2.png"));
                    break;
                case 3:
                    badgeImage = MyCreateCGImageFromPNG(CFSTR("Badge3.png"));
                    break;
                case 4:
                    badgeImage = MyCreateCGImageFromPNG(CFSTR("Badge4.png"));
                    break;
                case 5:
                    badgeImage = MyCreateCGImageFromPNG(CFSTR("Badge5.png"));
                    break;
            }
 
            previousDigits = digits;
        }
        
        if (iconImage == NULL) {
            iconImage = MyCreateCGImageFromPNG(CFSTR("Bonjour.png"));
        }
    }
 
    if (iconImage) {
    
        cgContext = BeginCGContextForApplicationDockTile();
        if (cgContext) {
        
            const CGRect iconRect = CGRectMake(0, 0, 128, 128);
            static const CGPoint lowerRightForBadge = { 125.0, 78.0 };
            char countString[6];
            CGRect badgeRect;
            CGPoint textLocation, badgeLocation;
            CGPoint begin;
            CGPoint end;
            
            CGContextClearRect(cgContext, iconRect);
            CGContextDrawImage(cgContext, iconRect, iconImage);
            
            if (count > 0 && count < 100000 && badgeImage) {
            
                badgeLocation = lowerRightForBadge;
                badgeLocation.x -= CGImageGetWidth(badgeImage);
                
                sprintf(countString, "%ld", (SInt32)count);
                
                // Measure the width of the count string so we can center it inside the badge.
                begin = CGContextGetTextPosition(cgContext);
                CGContextSetTextDrawingMode(cgContext, kCGTextInvisible);
                CGContextSelectFont(cgContext, "Helvetica-Bold", 24.0, kCGEncodingMacRoman);
                CGContextShowTextAtPoint(cgContext, 0, 0, countString, strlen(countString));
                end = CGContextGetTextPosition(cgContext);
                
                textLocation.y = badgeLocation.y + CGImageGetHeight(badgeImage) / 2 - ((14)/2);
                textLocation.x = badgeLocation.x + CGImageGetWidth(badgeImage) / 2 - (int)(end.x - begin.x)/2;
                
                badgeRect = CGRectMake(badgeLocation.x, badgeLocation.y, CGImageGetWidth(badgeImage), CGImageGetHeight(badgeImage));
                
                CGContextDrawImage(cgContext, badgeRect, badgeImage);
                
                CGContextSetTextDrawingMode(cgContext, kCGTextFill);
                CGContextSetRGBFillColor(cgContext, 1, 1, 1, 1);
                CGContextSelectFont(cgContext, "Helvetica-Bold", 24.0, kCGEncodingMacRoman);
                CGContextShowTextAtPoint(cgContext, textLocation.x, textLocation.y, countString, strlen(countString));
            }
            
            CGContextFlush(cgContext);
            EndCGContextForApplicationDockTile(cgContext);
        }
    }
    
    return;
}
 
 
 
static Boolean
MyStartBrowsingForServices(CFStringRef type)
{
    CFNetServiceClientContext clientContext = { 0, NULL, NULL, NULL, NULL };
    CFStreamError error;
    Boolean result;
 
    assert(type != NULL);
 
    gServiceBrowserRef = CFNetServiceBrowserCreate(kCFAllocatorDefault, MyBrowseCallBack, &clientContext);
    assert(gServiceBrowserRef != NULL);
 
    CFNetServiceBrowserScheduleWithRunLoop(gServiceBrowserRef, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
 
    result = CFNetServiceBrowserSearchForServices(gServiceBrowserRef, kMyDefaultDomain, type, &error);
    if (result == false) {
    
        // Something went wrong so lets clean up.
        CFNetServiceBrowserUnscheduleFromRunLoop(gServiceBrowserRef, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
        CFRelease(gServiceBrowserRef);
        gServiceBrowserRef = NULL;
 
        fprintf(stderr, "CFNetServiceBrowserSearchForServices returned (domain = %d, error = %ld)\n", error.domain, error.error);
    }
 
    return result;
}
 
 
 
static void
MyStopBrowsingForServices()
{    
    assert(gServiceBrowserRef != NULL);
    
    CFNetServiceBrowserInvalidate(gServiceBrowserRef);
    CFRelease(gServiceBrowserRef);
    gServiceBrowserRef = NULL;
    
    assert(gServiceDictionary != NULL);
    
    CFDictionaryRemoveAllValues(gServiceDictionary);
 
    MyUpdateDockIcon();
 
    return;
}
 
 
 
static Boolean
MyRegisterService(CFStringRef name, CFStringRef type, CFStringRef domain, UInt32 port, CFStringRef txtRecord)
{    
    CFNetServiceClientContext context = { 0, NULL, NULL, NULL, NULL };
    CFStreamError error;
    Boolean result;
 
    assert(name   != NULL);
    assert(type   != NULL);
    assert(domain != NULL);
    
    /* This application passes in an empty string for the name, and this causes the system to
    automatically use the "Computer Name" from the Sharing preference panel for our registration.
    Another benefit of using the empty string is that the system will automatically handle name
    collisions by appending a digit to the end of the name, thus our Callback will never be called
    in the event of a name collision.  Using an empty string will even handle situations where the
    user changes the "Computer Name" in the Sharing preference panel.  Most applications can simply
    resgister using an empty string for the name and an empty string for the domain. */
 
    gRegisteredService = CFNetServiceCreate(kCFAllocatorDefault, domain, type, name, port); 
    assert(gRegisteredService != NULL);
    
    if (txtRecord) {
        CFNetServiceSetProtocolSpecificInformation(gRegisteredService, txtRecord);
    }
 
    CFNetServiceSetClient(gRegisteredService, MyRegisterCallBack, &context);
    CFNetServiceScheduleWithRunLoop(gRegisteredService, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
 
    result = CFNetServiceRegister(gRegisteredService, &error);
    if (result == false) {
    
        // Something went wrong so lets clean up.
        CFNetServiceUnscheduleFromRunLoop(gRegisteredService, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
        CFNetServiceSetClient(gRegisteredService, NULL, NULL);
        CFRelease(gRegisteredService);
        gRegisteredService = NULL;
 
        fprintf(stderr, "CFNetServiceRegister returned (domain = %d, error = %ld)\n", error.domain, error.error);
    }
 
    return result;
}
 
 
 
static void
MyCancelRegistration()
{
    assert(gRegisteredService != NULL);
    
    CFNetServiceUnscheduleFromRunLoop(gRegisteredService, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
    CFNetServiceSetClient(gRegisteredService, NULL, NULL);
    CFNetServiceCancel(gRegisteredService);
    CFRelease(gRegisteredService);
    gRegisteredService = NULL;
 
    return;
}
 
 
 
static void
MyAddService(CFNetServiceRef service, CFOptionFlags flags)
{
    CFStringRef hostName;
    MyService * theService;
 
    /* You should do reference counting of each service because if the computer has two network
    interfaces set up, like Ethernet and AirPort, you may get notified about the same service
    twice, once from each interface.  You probably don't want both items to be shown to the user. */
 
    assert(service != NULL);
    assert(gServiceDictionary != NULL);
    
    hostName = MyCreateDictionaryKey(service);
    assert(hostName != NULL);
        
    theService = malloc(sizeof(MyService));
    assert(theService != NULL);
    
    CFStringGetCString(CFNetServiceGetName(service),   theService->name,    64, kCFStringEncodingUTF8);
    CFStringGetCString(CFNetServiceGetType(service),   theService->type,   256, kCFStringEncodingUTF8);
    CFStringGetCString(CFNetServiceGetDomain(service), theService->domain, 256, kCFStringEncodingUTF8);
 
    if (CFDictionaryGetValueIfPresent(gServiceDictionary, hostName, (const void **)&theService) == false) {
        theService->refCount = 0;
    }
        
    theService->refCount++;
    CFDictionarySetValue(gServiceDictionary, hostName, (const void **)theService);
 
    /* Only update the Dock icon when you know that no more Services will be added in the next 50 ms. */
    if ((flags & kCFNetServiceFlagMoreComing) == 0) MyUpdateDockIcon();
 
    return;
}
 
 
 
static void
MyRemoveService(CFNetServiceRef service, CFOptionFlags flags)
{
    CFStringRef hostName;
    MyService * theService;
    
    assert(service != NULL);
    hostName = MyCreateDictionaryKey(service);
    
    assert(hostName != NULL);
    assert(gServiceDictionary != NULL);
    
    if (CFDictionaryGetValueIfPresent(gServiceDictionary, hostName, (const void **)&theService)) {
        if (--(theService->refCount) == 0) {
            CFDictionaryRemoveValue(gServiceDictionary, hostName);
            free(theService);
        } else {
            CFDictionarySetValue(gServiceDictionary, hostName, (const void *)theService);
        }
    }
 
    /* Only update the Dock icon when you know that no more Services will be removed in the next 50 ms. */
    if ((flags & kCFNetServiceFlagMoreComing) == 0) MyUpdateDockIcon();
    
    return;
}
 
 
 
static void
MyBrowseCallBack(CFNetServiceBrowserRef browser, CFOptionFlags flags, CFTypeRef service, CFStreamError* error, void* info)
{
    #pragma unsused(browser)
    #pragma unsused(info)
    
    if (error->error == noErr)  {
        if (flags & kCFNetServiceFlagRemove) {
            MyRemoveService((CFNetServiceRef)service, flags);
        } else {
            MyAddService((CFNetServiceRef)service, flags);
        }
    } else {
        fprintf(stderr, "MyBrowseCallBack returned (domain = %d, error = %ld)\n", error->domain, error->error);
    }
}
 
 
 
static void
MyRestartWithNewType(CFStringRef type, UInt16 port, CFStringRef txtRecord) 
{    
    assert(type != NULL);
    assert(gServiceType != NULL);
    
    CFRelease(gServiceType);
    gServiceType = type;
    CFRetain(gServiceType);
    gPortNumber = port;
    
    if (gTextRecord) CFRelease(gTextRecord);
    gTextRecord = txtRecord;
    if (gTextRecord) CFRetain(gTextRecord);
 
    if (gRegisterEnabled) {
        MyCancelRegistration();
        MyRegisterService(kMyDefaultName, type, kMyDefaultDomain, port, txtRecord);
    }
    
    MyStopBrowsingForServices();
    MyStartBrowsingForServices(type);
    
    return;
}
 
 
 
static OSStatus
MyRadioButtonEventHandler(EventHandlerCallRef inCallRef, EventRef inEvent, void * inUserData)
{
    #pragma unsused(inCallRef)
    #pragma unsused(inEvent)
    #pragma unsused(inUserData)
 
    SInt16 value;
    
    assert(gRadioButtonControl != NULL); 
    value = GetControlValue(gRadioButtonControl);
    
    if (value != gRadioButtonValue) {
        
        switch(value) {
            case kMyRadioButtonAFP:
                gRadioButtonValue = kMyRadioButtonAFP;
                MyRestartWithNewType(kMyTypeAFP, kMyPortAFP, NULL);
                break;
            case kMyRadioButtonHTTP:
                gRadioButtonValue = kMyRadioButtonHTTP;
                MyRestartWithNewType(kMyTypeHTTP, kMyPortHTTP, kMyTextRecordHTTP);
                break;
        }
        
        gPreferencesChanged = true;
    }
    
    return noErr;
}
 
 
 
static void
MyRegisterCallBack(CFNetServiceRef theService, CFStreamError* error, void* info)
{    
    if (error->domain == kCFStreamErrorDomainNetServices) {
        
        switch(error->error) {
            case kCFNetServicesErrorCollision:
            
                MyCancelRegistration();
                fprintf(stderr, "kCFNetServicesErrorCollision occured\n");
                break;
            default:
            
                MyCancelRegistration();
                fprintf(stderr, "MyRegisterCallBack returned (domain = %d, error = %ld)\n", error->domain, error->error);
                break;
        }
    }
}
 
 
 
static OSStatus
MyCheckBoxEventHandler(EventHandlerCallRef inCallRef, EventRef inEvent, void * inUserData)
{     
    #pragma unsused(inCallRef)
    #pragma unsused(inEvent)
    #pragma unsused(inUserData)    
    
    const Boolean value = GetControlValue(gCheckBoxControl);
 
    if (value != gRegisterEnabled) {
 
        if (value) {
            gRegisterEnabled = true;
            MyRegisterService(kMyDefaultName, gServiceType, kMyDefaultDomain, gPortNumber, gTextRecord);
        } else {
            gRegisterEnabled = false;
            MyCancelRegistration();
        }
        gPreferencesChanged = true;
    }
 
    return noErr;
}
 
 
 
static void
MyLoadPreferences()
{
    CFBooleanRef registerEnabled = CFPreferencesCopyAppValue(kMyRegisterEnabledKey, kCFPreferencesCurrentApplication);
    CFNumberRef radioValue = CFPreferencesCopyAppValue(kMyServiceTypeKey, kCFPreferencesCurrentApplication);
 
    if (!radioValue || CFNumberGetValue(radioValue, kCFNumberSInt16Type, &gRadioButtonValue) == false) {
        gRadioButtonValue = kMyRadioButtonAFP;
    } else {
        CFRelease(radioValue);
    }
 
    switch(gRadioButtonValue) {
        default:
            gRadioButtonValue = kMyRadioButtonAFP;
        case kMyRadioButtonAFP:
            gServiceType = CFStringCreateCopy(kCFAllocatorDefault, kMyTypeAFP);
            gPortNumber = kMyPortAFP;
            gTextRecord = NULL;
            break;
        case kMyRadioButtonHTTP:
            gServiceType = CFStringCreateCopy(kCFAllocatorDefault, kMyTypeHTTP);
            gPortNumber = kMyPortHTTP;
            gTextRecord = kMyTextRecordHTTP;
            break;
    }
 
    if (registerEnabled && CFBooleanGetValue(registerEnabled)) {
        gRegisterEnabled = true;
        CFRelease(registerEnabled);
    } else {
        gRegisterEnabled = false;
    }
 
    SetControlValue(gRadioButtonControl, gRadioButtonValue);
    SetControlValue(gCheckBoxControl, gRegisterEnabled);
    gPreferencesChanged = false;
 
    return;
}
 
 
 
static void
MySavePreferences()
{
    if (gPreferencesChanged) {
 
        CFNumberRef radioValue = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt16Type, &gRadioButtonValue);
        assert(radioValue != NULL);
 
        CFPreferencesSetAppValue(kMyServiceTypeKey, radioValue, kCFPreferencesCurrentApplication);
        CFRelease(radioValue);
 
        if (gRegisterEnabled) {
            CFPreferencesSetAppValue(kMyRegisterEnabledKey, kCFBooleanTrue, kCFPreferencesCurrentApplication);
        } else {
            CFPreferencesSetAppValue(kMyRegisterEnabledKey, kCFBooleanFalse, kCFPreferencesCurrentApplication);
        }
 
        CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication);
        gPreferencesChanged = false;
    }
 
    return;
}
 
 
 
static CFComparisonResult
MyArrayCompareFunction(const void *val1, const void *val2, void *context)
{
    return CFStringCompare((CFStringRef)val1, (CFStringRef)val2, kCFCompareCaseInsensitive);
}
 
 
 
static void
MyUpdateDockMenu()
{
    CFIndex index;
    CFIndex count;
    MenuItemIndex menuItem;
    OSStatus err;
 
    assert(gServiceDictionary != NULL);
    count = CFDictionaryGetCount(gServiceDictionary);
 
    if (gDockMenu) {
        ReleaseMenu(gDockMenu);        
    }
 
    err = CreateNewMenu(255, 0, &gDockMenu);
    assert(err == noErr);
 
    RetainMenu(gDockMenu);
 
    if (count > 0) {
    
        CFStringRef * array;
        CFMutableArrayRef cfArray;
        
        array = malloc(count * sizeof(CFStringRef));
        assert(array != NULL);
        
        cfArray = CFArrayCreateMutable(kCFAllocatorDefault, count, &kCFTypeArrayCallBacks);
        assert(cfArray != NULL);
      
        CFDictionaryGetKeysAndValues(gServiceDictionary, (const void **)array, NULL);
 
        CFArrayReplaceValues(cfArray, CFRangeMake(0, 0), (const void **)array, count);
        CFArraySortValues(cfArray, CFRangeMake(0, count), MyArrayCompareFunction, NULL);
 
        for (index = 0; index < CFArrayGetCount(cfArray); index++) {
        
            MyService * theService;
            CFStringRef itemString;
                        
            theService = (MyService *)CFDictionaryGetValue(gServiceDictionary, CFArrayGetValueAtIndex(cfArray, index));
            assert(theService != NULL);
            
            itemString = CFStringCreateWithCString(NULL, theService->name, kCFStringEncodingUTF8);
            assert(itemString != NULL);
            
            AppendMenuItemTextWithCFString(gDockMenu, itemString, 0, index + 1, &menuItem);
            SetMenuItemIconHandle(gDockMenu, menuItem, kMenuSystemIconSelectorType, (Handle)kGenericNetworkIcon);
            SetMenuItemProperty(gDockMenu, menuItem, kMyDockBrowser, kMyServiceInfo, sizeof(MyService), theService);
            
            CFRelease(itemString);
        }
        
        CFRelease(cfArray);
        free(array);
    }
 
    return;
}
 
 
 
static OSStatus
MyDockMenuEventHandler(EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData)
{
    #pragma unsused(inHandlerCallRef)
    #pragma unsused(inUserData)
    
    HICommand command;
    const UInt32 eventClass = GetEventClass(inEvent);
    const UInt32 eventKind = GetEventKind(inEvent);
    OSStatus err = eventNotHandledErr;
 
    if (eventClass == kEventClassCommand && eventKind == kEventCommandProcess) {
    
        err = GetEventParameter(inEvent, kEventParamDirectObject, typeHICommand, NULL, sizeof(HICommand), NULL, &command);
        if (err == noErr) {
        
            assert(gServiceDictionary != NULL);
            if (command.commandID > 0 && command.commandID <= CFDictionaryGetCount(gServiceDictionary)) {
            
                MenuItemIndex menuItem;
                
                err = GetIndMenuItemWithCommandID(gDockMenu, command.commandID, 1, NULL, &menuItem);
                if (err == noErr) {
                
                    MyService * theService;
                    CFStringRef name;
                    CFStringRef type;
                    CFStringRef domain;
                    OSStatus err;
                    
                    theService = malloc(sizeof(MyService));
                    assert(theService != NULL);
                    
                    err = GetMenuItemProperty(gDockMenu, menuItem, kMyDockBrowser, kMyServiceInfo, sizeof(MyService), NULL, theService);
                    if (err == noErr) {
                    
                        name = CFStringCreateWithCString(NULL, theService->name, kCFStringEncodingUTF8);
                        assert(name != NULL);
                        
                        type = CFStringCreateWithCString(NULL, theService->type, kCFStringEncodingUTF8);
                        assert(type != NULL);
 
                        domain = CFStringCreateWithCString(NULL, theService->domain, kCFStringEncodingUTF8);
                        assert(domain != NULL);
                        
                        MyResolveService(name, type, domain);
                        
                        CFRelease(name);
                        CFRelease(type);
                        CFRelease(domain);
                    }
                    free(theService);
                }
                
                /* Partially works around a bug (r. 3090633), where the Dock icon doesn't update properly
                while the menu is being shown, by updating the icon after a selection is made from the menu. */
                MyUpdateDockIcon();
          
                err = noErr;
            }
            else if (command.commandID == kHICommandPreferences) {
                ShowWindow(gPreferencesWindow);
                err = noErr;
            }
            else {
                err = eventNotHandledErr;
            }
        }
    }
    else if (eventClass == kEventClassApplication && eventKind == kEventAppGetDockTileMenu) {
        MyUpdateDockMenu();
        SetEventParameter(inEvent, kEventParamMenuRef, typeMenuRef, sizeof(MenuRef), &gDockMenu);
        err = noErr;
    }
 
    return err;
}
 
 
 
static OSStatus
MyWindowEventHandler(EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData)
{
    #pragma unsused(inHandlerCallRef)
    #pragma unsused(inEvent)
    #pragma unsused(inUserData)
    
    OSStatus err = eventNotHandledErr;
    
    if (GetEventKind(inEvent) == kEventWindowClose) {
    
        HideWindow(gPreferencesWindow);
        MySavePreferences();
        err = noErr;
    }
 
    return err;
}
 
 
 
static OSStatus
MyInstallEventHandlers()
{
    OSStatus err;
    static const ControlID radioButtonID = { kMyDockBrowser, kMyRadioButtons };
    static const ControlID checkBoxID    = { kMyDockBrowser, kMyCheckBox };
    static const EventTypeSpec windowEvents[]  = { { kEventClassWindow, kEventWindowClose } };
    static const EventTypeSpec controlEvents[] = { { kEventClassControl, kEventControlHit } };
    static const EventTypeSpec appEvents[] = { { kEventClassCommand,     kEventProcessCommand     },
                                               { kEventClassApplication, kEventAppGetDockTileMenu } };
 
    do
    {
        err = InstallApplicationEventHandler(NewEventHandlerUPP(MyDockMenuEventHandler),
            GetEventTypeCount(appEvents), appEvents, NULL, NULL);
        if (err != noErr) break;
        
        EnableMenuCommand(NULL, kHICommandPreferences);
    
        err = GetControlByID(gPreferencesWindow, &radioButtonID, &gRadioButtonControl);
        if (err != noErr) break;
    
        err = InstallControlEventHandler(gRadioButtonControl,
            NewEventHandlerUPP(MyRadioButtonEventHandler), GetEventTypeCount(controlEvents), controlEvents, NULL, NULL);
        if (err != noErr) break;
    
        err = GetControlByID(gPreferencesWindow, &checkBoxID, &gCheckBoxControl);
        if (err != noErr) break;
    
        err = InstallControlEventHandler(gCheckBoxControl,
            NewEventHandlerUPP(MyCheckBoxEventHandler), GetEventTypeCount(controlEvents), controlEvents, NULL, NULL);
        if (err != noErr) break;
    
        err = InstallWindowEventHandler(gPreferencesWindow,
            NewEventHandlerUPP(MyWindowEventHandler), GetEventTypeCount(windowEvents), windowEvents, NULL, NULL);
          
    } while(0);
 
    return err;
}
 
 
 
static OSStatus
MySetupApplication()
{
    IBNibRef nibRef;
    OSStatus err;
 
    err = CreateNibReference(CFSTR("DockBrowser"), &nibRef);
    if (err == noErr) {
        
        err = SetMenuBarFromNib(nibRef, CFSTR("MenuBar"));
        if (err == noErr) {
            
            err = CreateWindowFromNib(nibRef, CFSTR("Preferences"), &gPreferencesWindow);
            if (err == noErr) {
                
                err = MyInstallEventHandlers();
                
                if (err == noErr) {
                    MyLoadPreferences();
 
                    gDockMenu = NULL;
                    gServiceBeingResolved = NULL;
        
                    gServiceDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFCopyStringDictionaryKeyCallBacks, NULL);
                    if (gServiceDictionary == NULL) {
                        err = coreFoundationUnknownErr;
                    }
                }
            }
        }
        DisposeNibReference(nibRef);
    }
    
    return err;
}
 
 
 
static void
MyCleanupApplication()
{
    MySavePreferences();
    RestoreApplicationDockTileImage();
    
    return;
}
 
 
 
static void
MyViewWebPageAtLocation(CFStringRef hostName, CFStringRef portString, CFStringRef pathString)
{
    CFStringRef urlString;
    CFURLRef addressURL;
    
    assert(hostName != NULL);
    assert(portString != NULL);
    
    if (pathString && CFEqual(portString, CFSTR("80")) == false) {
        urlString = CFStringCreateWithFormat(kCFAllocatorDefault, 0, CFSTR("http://%@:%@%@"), hostName, portString, pathString);
    } else if (pathString) {
        urlString = CFStringCreateWithFormat(kCFAllocatorDefault, 0, CFSTR("http://%@%@"), hostName, pathString);
    } else if (CFEqual(portString, CFSTR("80")) == false) {
        urlString = CFStringCreateWithFormat(kCFAllocatorDefault, 0, CFSTR("http://%@:%@"), hostName, portString);
    } else {
        urlString = CFStringCreateWithFormat(kCFAllocatorDefault, 0, CFSTR("http://%@"), hostName);
    }
 
    assert(urlString != NULL);
        
    addressURL = CFURLCreateWithString(kCFAllocatorDefault, urlString, NULL);
    assert(addressURL != NULL);
    
    LSOpenCFURLRef(addressURL, NULL);
    
    CFRelease(addressURL);
    CFRelease(urlString);
 
    return;
}
 
 
 
static void
MyVolumeMountCallback(FSVolumeOperation volumeOp, void *clientData, OSStatus err, FSVolumeRefNum mountedVolumeRefNum)
{
    #pragma unsused(clientData)
    #pragma unsused(err)
    #pragma unsused(mountedVolumeRefNum)
    
    FSDisposeVolumeOperation(volumeOp);
 
    return;
}
 
 
 
static void
MyMountServerAtLocation(CFStringRef hostName, CFStringRef portString)
{
    FSVolumeMountUPP volumeMountUPP = NewFSVolumeMountUPP(MyVolumeMountCallback);
    FSVolumeOperation volumeOp;
    CFURLRef addressURL;
    CFStringRef urlString;
    OSStatus err;
 
    assert(hostName != NULL);
    assert(portString != NULL);
    assert(volumeMountUPP != NULL);
    
    if (CFEqual(portString, CFSTR("548")) == false) {
        urlString = CFStringCreateWithFormat(kCFAllocatorDefault, 0, CFSTR("afp://%@:%@"), hostName, portString);
    } else {
        urlString = CFStringCreateWithFormat(kCFAllocatorDefault, 0, CFSTR("afp://%@"), hostName);
    }
    
    assert(urlString != NULL);
 
    addressURL = CFURLCreateWithString(kCFAllocatorDefault, urlString, NULL);        
    assert(addressURL != NULL);
    
    err = FSCreateVolumeOperation(&volumeOp);
    if (err == noErr) {
    
        static const ProcessSerialNumber psn = { 0, kCurrentProcess };
        
        /* If we aren't the front process, the user might not see the password dialog
        because it could be hidden behind other windows, so make us the front process. */
        
        SetFrontProcess(&psn);
        
        err = FSMountServerVolumeAsync(addressURL, NULL, NULL, NULL, volumeOp, NULL, 0, volumeMountUPP,
          CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
          
        if (err != noErr) {
            FSDisposeVolumeOperation(volumeOp);
        }
    }
    
    CFRelease(addressURL);
    CFRelease(urlString);
 
    return;
}
 
 
 
Boolean
MyCopyFirstIPv4Address(CFNetServiceRef service, CFStringRef * addressString, CFStringRef * portString)
{
    struct sockaddr * socketAddress = NULL;
    CFArrayRef addresses;
    char buffer[256];
    uint16_t port;
    int count;
    Boolean result = false;
    
    assert(service       != NULL);
    assert(addressString != NULL);
    assert(portString    != NULL);
    
    addresses = CFNetServiceGetAddressing(service);
    
    assert(addresses != NULL);
    assert(CFArrayGetCount(addresses) > 0);
    
    /* Search for the first IPv4 address in the array. */
    for (count = 0; count < CFArrayGetCount(addresses); count++) {
    
        socketAddress = (struct sockaddr *)CFDataGetBytePtr(CFArrayGetValueAtIndex(addresses, count));
    
        /* Only continue if this is an IPv4 address. */
        if (socketAddress && socketAddress->sa_family == AF_INET) {
        
            if (inet_ntop(AF_INET, &((struct sockaddr_in *)socketAddress)->sin_addr, buffer, sizeof(buffer))) {
            
                *addressString = CFStringCreateWithCString(kCFAllocatorDefault, buffer, kCFStringEncodingASCII);
                port = ntohs(((struct sockaddr_in *)socketAddress)->sin_port);
                *portString = CFStringCreateWithFormat(kCFAllocatorDefault, 0, CFSTR("%d"), port);
                result = true;
            }
            break;
        }
    }
    return result;
}
 
 
 
CFStringRef
MyCreateDictionaryKey(CFNetServiceRef service)
{
    CFStringRef name;
    CFStringRef type;
    CFStringRef domain;
    
    assert(service != NULL);
 
    name   = CFNetServiceGetName(service);
    type   = CFNetServiceGetType(service);
    domain = CFNetServiceGetDomain(service);
    
    return CFStringCreateWithFormat(kCFAllocatorDefault, 0, CFSTR("%@.%@%@"), name, type, domain);
}
 
 
 
static Boolean
MyDictionaryKeyEqualCallBack(const void *value1, const void *value2)
{
    const CFTypeID stringID = CFStringGetTypeID();
 
    if (CFGetTypeID(value1) == stringID && CFGetTypeID(value2) == stringID) {
    
        if (CFStringCompare((CFStringRef)value1, (CFStringRef)value2, kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
            return true;
        }
        
        return false;
    } else {
        return kCFTypeDictionaryKeyCallBacks.equal(value1, value2);
    }
} 
 
 
 
static CFHashCode
MyDictionaryKeyHashCallBack(const void *value)
{
    const CFTypeID stringID = CFStringGetTypeID();
    
    if (CFGetTypeID(value) == stringID) {
            
        CFIndex length = CFStringGetLength(value);                
        unsigned char * pointer = malloc(length + 1);
        unsigned char * cStr = pointer;
        int index;
        
        assert(cStr != NULL);
        
        CFStringGetCString(value, (char *)cStr, length + 1, kCFStringEncodingASCII);
        
        for (index = 0; index < length; index++) {
            cStr[index] = tolower(cStr[index]);
        }
        
        CFHashCode result = 0;
        if (length <= 4) {  // All chars
            unsigned cnt = length;
            while (cnt--) result += (result << 8) + *cStr++;
        } else {        // First and last 2 chars
            result += (result << 8) + cStr[0];
            result += (result << 8) + cStr[1];
            result += (result << 8) + cStr[length-2];
            result += (result << 8) + cStr[length-1];
        }
        result += (result << (length & 31));
        free(pointer);
        return result;
    }
    else {
        return kCFTypeDictionaryKeyCallBacks.hash(value);
    }
}
 
 
 
static CFDictionaryRef
MyCreateCFDictionaryFromTXT(CFStringRef txtString)
{
    CFRange endOfString;
    CFRange range;
    CFIndex location = 0;
    CFIndex txtStringLength;
    CFMutableDictionaryRef txtDictionary;
    const CFDictionaryKeyCallBacks keyCallBacks = { kCFTypeDictionaryKeyCallBacks.version, kCFTypeDictionaryKeyCallBacks.retain,
                      kCFTypeDictionaryKeyCallBacks.release, kCFTypeDictionaryKeyCallBacks.copyDescription,
                      MyDictionaryKeyEqualCallBack, MyDictionaryKeyHashCallBack };
    assert(txtString != NULL);
    txtStringLength = CFStringGetLength(txtString);
 
    // We create our own equal and hash callback functions because TXT record key names should be case insensitive.
    txtDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &keyCallBacks, &kCFTypeDictionaryValueCallBacks);
    assert(txtDictionary != NULL);
    
    while (location < txtStringLength) {
    
        CFStringRef key;
        CFStringRef value = NULL;
 
        if (CFStringFindWithOptions(txtString, CFSTR("\001"), CFRangeMake(location, txtStringLength - location), 0, &endOfString) == false) {
            endOfString.location = txtStringLength;
        }
        
        if (CFStringFindWithOptions(txtString, CFSTR("="), CFRangeMake(location, endOfString.location - location), 0, &range)) {
            key = CFStringCreateWithSubstring(NULL, txtString, CFRangeMake(location, range.location - location));
            location = range.location + 1;
            value = CFStringCreateWithSubstring(NULL, txtString, CFRangeMake(location, endOfString.location - location));
            location = endOfString.location + 1;
        } else {
            key = CFStringCreateWithSubstring(NULL, txtString, CFRangeMake(location, endOfString.location - location));
            location = endOfString.location + 1;
        }
 
        if (key) {
        
            // If key length is 0, it means the string started with a '=', so we should ignore it.
            if (CFStringGetLength(key) > 0) {
                    
                // Only the first occurence of a key should be used.
                if (CFDictionaryContainsKey(txtDictionary, key) == false) {
                
                    if (value) {
                        CFDictionaryAddValue(txtDictionary, key, value);
                    } else {
                        CFDictionaryAddValue(txtDictionary, key, CFSTR(""));
                    }
                }
            }
            CFRelease(key);
        }
        
        if (value) {
            CFRelease(value);       
        }
    }
 
    return txtDictionary;
}
 
 
 
static CFStringRef
MyCreateTXTFromCFDictionary(CFDictionaryRef dictionary)
{
    CFMutableStringRef txtString;
    CFStringRef * keys;
    CFStringRef * values;
    CFIndex index;
    CFIndex count;
    
    assert(dictionary != NULL);
    count = CFDictionaryGetCount(dictionary);
 
    keys = malloc(count * sizeof(CFStringRef));
    assert(keys != NULL);
 
    values = malloc(count * sizeof(CFStringRef));
    assert(keys != NULL);
    
    txtString = CFStringCreateMutable(kCFAllocatorDefault, 1024);
    assert(txtString != NULL);
    
    CFDictionaryGetKeysAndValues(dictionary, (const void **)keys, (const void **)values);
    
    for (index = 0; index < count; index++) {
        CFStringAppendFormat(txtString, NULL, CFSTR("%@=%@%@"), keys[index], values[index], CFSTR("\001"));
    }
 
    CFStringDelete(txtString, CFRangeMake(CFStringGetLength(txtString)-1, 1));
    
    free(keys);
    free(values);
    
    return txtString;
}
 
 
 
static void
MyResolveCallback(CFNetServiceRef service, CFStreamError* error, void* info)
{
    #pragma unused(info)
    
    CFStringRef addressString = NULL;
    CFStringRef portString = NULL;
 
    /* In situations where the service you're resolving is advertising on multiple interfaces,
    like Ethernet and AirPort, your Resolve callback may be called twice, once for each IP address.
    Chances are that both of these IP addresses represent the same service running on the same machine,
    so we cancel the Resolve after getting the first callback because you only need one address to
    connect to the service.  However, it would also be possible that two different machines are
    advertising the same service name, with one being on Ethernet and one on AirPort.  In that
    situation, one of the machines will be unreachable, or more likly, everytime we call Resolve,
    we may connect to a different machine.  The odds of this happening are extremely small. */
 
    
    if (MyCopyFirstIPv4Address(service, &addressString, &portString) == true) {
    
        // Cancel the Resolve now that we have an IPv4 address.
        MyCancelResolve();
 
        if (addressString && portString) {
        
            if (CFEqual(CFNetServiceGetType(service), kMyTypeHTTP)) {
            
                /* Even if no Protocol Specific Information was set for this service, calling
                CFNetServiceGetProtocolSpecificInformation will still return a valid CFStringRef
                but it will contain an empty string.  The comments in "CFNetService.h" that say
                it returns NULL are wrong (r. 3095789). */
    
                CFStringRef txtRecord = CFNetServiceGetProtocolSpecificInformation(service);
                if (txtRecord) {
                    CFDictionaryRef two = MyCreateCFDictionaryFromTXT(txtRecord);
                    CFDictionaryRef txtDictionary;
                    CFStringRef string;
                    assert(two != NULL);
                    
                    string = MyCreateTXTFromCFDictionary(two);
                    txtDictionary = MyCreateCFDictionaryFromTXT(string);
    
                    MyViewWebPageAtLocation(addressString, portString, CFDictionaryGetValue(txtDictionary, kMyPathKey));
                    CFRelease(txtDictionary);
                } else {
                    MyViewWebPageAtLocation(addressString, portString, NULL);
                }
            }
            else {
                MyMountServerAtLocation(addressString, portString);
            }
        }
    }
    
    if (addressString) CFRelease(addressString);
    if (portString) CFRelease(portString);
    
    return;
}
 
 
 
int
main(int argc, char* argv[])
{
    #pragma unsused(argc)
    #pragma unsused(argv)
    
    OSStatus error;
 
    error = MySetupApplication();
    if (error != noErr) {
        fprintf(stderr, "MySetupApplication returned  %ld\n", error);
        return EXIT_FAILURE;
    }
        
    if (MyStartBrowsingForServices(gServiceType) == false)  {
        fprintf(stderr, "MyStartBrowsingForServices returned false\n");
    }
    
    if (gRegisterEnabled) {
       
        if (MyRegisterService(kMyDefaultName, gServiceType, kMyDefaultDomain, gPortNumber, gTextRecord) == false) {
            fprintf(stderr, "MyRegisterService returned false\n");
            return EXIT_FAILURE;
        }
    }
 
    RunApplicationEventLoop();
 
    MyCleanupApplication();
 
    return EXIT_SUCCESS;
}



Last updated: 2005-02-08

Did this document help you? Yes It's good, but... Not helpful...