1 /* -*- Mode: C; tab-width: 4 -*-
3 * Copyright (c) 2010 Apple Inc. All rights reserved.
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
18 #include <CoreFoundation/CoreFoundation.h>
20 #include <UserEventAgentInterface.h>
28 static const char* sPluginIdentifier
= "com.apple.bonjour.events";
31 static const CFStringRef sServiceNameKey
= CFSTR("ServiceName");
32 static const CFStringRef sServiceTypeKey
= CFSTR("ServiceType");
33 static const CFStringRef sServiceDomainKey
= CFSTR("ServiceDomain");
35 static const CFStringRef sOnServiceAddKey
= CFSTR("OnServiceAdd");
36 static const CFStringRef sOnServiceRemoveKey
= CFSTR("OnServiceRemove");
37 static const CFStringRef sWhileServiceExistsKey
= CFSTR("WhileServiceExists");
39 static const CFStringRef sLaunchdTokenKey
= CFSTR("LaunchdToken");
41 static const CFStringRef sPluginTimersKey
= CFSTR("PluginTimers");
44 /************************************************
45 * Launch Event Dictionary (input from launchd)
46 * Passed To: ManageEventsCallback
47 *-----------------------------------------------
48 * Typing in this dictionary is not enforced
49 * above us. So this may not be true. Type check
50 * all input before using it.
51 *-----------------------------------------------
52 * sServiceNameKey - CFString (Optional)
53 * sServiceTypeKey - CFString
54 * sServiceDomainKey - CFString
56 * One or more of the following.
57 *-----------------------------------
58 * sOnServiceAddKey - CFBoolean
59 * sOnServiceRemoveKey - CFBoolean
60 * sWhileServiceExistsKey - CFBoolean
61 ************************************************/
63 /************************************************
65 *-----------------------------------------------
66 * sServiceDomainKey - CFString
67 * sServiceTypeKey - CFString
68 ************************************************/
70 /************************************************
72 *-----------------------------------------------
73 * sServiceNameKey - CFString (Optional)
74 * sLaunchdTokenKey - CFNumber
75 ************************************************/
78 UserEventAgentInterfaceStruct
* _UserEventAgentInterface
;
84 CFMutableDictionaryRef _tokenToBrowserMap
; // Maps a token to a browser that can be used to scan the remaining dictionaries.
85 CFMutableDictionaryRef _browsers
; // A Dictionary of "Browser Dictionarys" where the resposible browser is the key.
86 CFMutableDictionaryRef _onAddEvents
; // A Dictionary of "Event Dictionarys" that describe events to trigger on a service appearing.
87 CFMutableDictionaryRef _onRemoveEvents
; // A Dictionary of "Event Dictionarys" that describe events to trigger on a service disappearing.
88 CFMutableDictionaryRef _whileServiceExist
; // A Dictionary of "Event Dictionarys" that describe events to trigger on a service disappearing.
91 CFMutableArrayRef _timers
;
93 } BonjourUserEventsPlugin
;
99 BonjourUserEventsPlugin
* plugin
;
106 DNSServiceRef browserRef
;
110 #pragma mark Prototypes
113 static HRESULT
QueryInterface(void *myInstance
, REFIID iid
, LPVOID
*ppv
);
114 static ULONG
AddRef(void* instance
);
115 static ULONG
Release(void* instance
);
117 static BonjourUserEventsPlugin
* Alloc(CFUUIDRef factoryID
);
118 static void Dealloc(BonjourUserEventsPlugin
* plugin
);
120 void * UserEventAgentFactory(CFAllocatorRef allocator
, CFUUIDRef typeID
);
123 static void Install(void* instance
);
124 static void ManageEventsCallback(
125 UserEventAgentLaunchdAction action
,
127 CFTypeRef eventMatchDict
,
132 void AddEventToPlugin(BonjourUserEventsPlugin
* plugin
, CFNumberRef launchdToken
, CFDictionaryRef eventParameters
);
133 void RemoveEventFromPlugin(BonjourUserEventsPlugin
* plugin
, CFNumberRef launchToken
);
135 NetBrowserInfo
* CreateBrowserForTypeAndDomain(BonjourUserEventsPlugin
* plugin
, CFStringRef type
, CFStringRef domain
);
136 NetBrowserInfo
* BrowserForSDRef(BonjourUserEventsPlugin
* plugin
, DNSServiceRef sdRef
);
137 void AddEventDictionary(CFDictionaryRef eventDict
, CFMutableDictionaryRef allEventsDictionary
, NetBrowserInfo
* key
);
138 void RemoveEventFromArray(CFMutableArrayRef array
, CFNumberRef launchdToken
);
140 // Net Service Browser Stuff
141 void ServiceBrowserCallback (DNSServiceRef sdRef
, DNSServiceFlags flags
, uint32_t interfaceIndex
, DNSServiceErrorType errorCode
, const char* serviceName
, const char* regtype
, const char* replyDomain
, void* context
);
142 void HandleTemporaryEventsForService(BonjourUserEventsPlugin
* plugin
, NetBrowserInfo
* browser
, CFStringRef serviceName
, CFMutableDictionaryRef eventsDictionary
);
143 void HandleStateEventsForService(BonjourUserEventsPlugin
* plugin
, NetBrowserInfo
* browser
, CFStringRef serviceName
, Boolean didAppear
);
144 void TemporaryEventTimerCallout ( CFRunLoopTimerRef timer
, void *info
);
147 const char* CStringFromCFString(CFStringRef string
);
150 // TimerContextInfo "Object"
151 TimerContextInfo
* TimerContextInfoCreate(BonjourUserEventsPlugin
* plugin
, CFNumberRef token
);
152 const void* TimerContextInfoRetain(const void* info
);
153 void TimerContextInfoRelease(const void* info
);
154 CFStringRef
TimerContextInfoCopyDescription(const void* info
);
156 // NetBrowserInfo "Object"
157 NetBrowserInfo
* NetBrowserInfoCreate(CFStringRef serviceType
, CFStringRef domain
, void* context
);
158 const void* NetBrowserInfoRetain(CFAllocatorRef allocator
, const void* info
);
159 void NetBrowserInfoRelease(CFAllocatorRef allocator
, const void* info
);
160 Boolean
NetBrowserInfoEqual(const void *value1
, const void *value2
);
161 CFHashCode
NetBrowserInfoHash(const void *value
);
162 CFStringRef
NetBrowserInfoCopyDescription(const void *value
);
165 static const CFDictionaryKeyCallBacks kNetBrowserInfoDictionaryKeyCallbacks
= {
167 NetBrowserInfoRetain
,
168 NetBrowserInfoRelease
,
169 NetBrowserInfoCopyDescription
,
174 static const CFDictionaryValueCallBacks kNetBrowserInfoDictionaryValueCallbacks
= {
176 NetBrowserInfoRetain
,
177 NetBrowserInfoRelease
,
178 NetBrowserInfoCopyDescription
,
182 // COM type definition goop.
183 static UserEventAgentInterfaceStruct UserEventAgentInterfaceFtbl
= {
184 NULL
, // Required padding for COM
185 QueryInterface
, // Query Interface
187 Release
, // Release()
192 #pragma mark COM Management
195 /*****************************************************************************
196 *****************************************************************************/
197 static HRESULT
QueryInterface(void *myInstance
, REFIID iid
, LPVOID
*ppv
)
199 CFUUIDRef interfaceID
= CFUUIDCreateFromUUIDBytes(NULL
, iid
);
201 // Test the requested ID against the valid interfaces.
202 if(CFEqual(interfaceID
, kUserEventAgentInterfaceID
))
204 ((BonjourUserEventsPlugin
*) myInstance
)->_UserEventAgentInterface
->AddRef(myInstance
);
206 CFRelease(interfaceID
);
209 else if(CFEqual(interfaceID
, IUnknownUUID
))
211 ((BonjourUserEventsPlugin
*) myInstance
)->_UserEventAgentInterface
->AddRef(myInstance
);
213 CFRelease(interfaceID
);
216 else // Requested interface unknown, bail with error.
219 CFRelease(interfaceID
);
220 return E_NOINTERFACE
;
224 /*****************************************************************************
225 *****************************************************************************/
226 static ULONG
AddRef(void* instance
)
228 BonjourUserEventsPlugin
* plugin
= (BonjourUserEventsPlugin
*)instance
;
229 return ++plugin
->_refCount
;
232 /*****************************************************************************
233 *****************************************************************************/
234 static ULONG
Release(void* instance
)
236 BonjourUserEventsPlugin
* plugin
= (BonjourUserEventsPlugin
*)instance
;
238 if (plugin
->_refCount
!= 0)
241 if (plugin
->_refCount
== 0)
247 return plugin
->_refCount
;
250 /*****************************************************************************
253 * Functionas as both +[alloc] and -[init] for the plugin. Add any
254 * initalization of member variables here.
255 *****************************************************************************/
256 static BonjourUserEventsPlugin
* Alloc(CFUUIDRef factoryID
)
258 BonjourUserEventsPlugin
* plugin
= malloc(sizeof(BonjourUserEventsPlugin
));
260 plugin
->_UserEventAgentInterface
= &UserEventAgentInterfaceFtbl
;
261 plugin
->_pluginContext
= NULL
;
265 plugin
->_factoryID
= (CFUUIDRef
)CFRetain(factoryID
);
266 CFPlugInAddInstanceForFactory(factoryID
);
269 plugin
->_refCount
= 1;
270 plugin
->_tokenToBrowserMap
= CFDictionaryCreateMutable(NULL
, 0, &kCFTypeDictionaryKeyCallBacks
, &kNetBrowserInfoDictionaryValueCallbacks
);
271 plugin
->_browsers
= CFDictionaryCreateMutable(NULL
, 0, &kNetBrowserInfoDictionaryKeyCallbacks
, &kCFTypeDictionaryValueCallBacks
);
272 plugin
->_onAddEvents
= CFDictionaryCreateMutable(NULL
, 0, &kNetBrowserInfoDictionaryKeyCallbacks
, &kCFTypeDictionaryValueCallBacks
);
273 plugin
->_onRemoveEvents
= CFDictionaryCreateMutable(NULL
, 0, &kNetBrowserInfoDictionaryKeyCallbacks
, &kCFTypeDictionaryValueCallBacks
);
274 plugin
->_whileServiceExist
= CFDictionaryCreateMutable(NULL
, 0, &kNetBrowserInfoDictionaryKeyCallbacks
, &kCFTypeDictionaryValueCallBacks
);
276 plugin
->_timers
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
);
281 /*****************************************************************************
284 * Much like Obj-C dealloc this method is responsible for releasing any object
285 * this plugin is holding. Unlike ObjC, you call directly free() instead of
287 *****************************************************************************/
288 static void Dealloc(BonjourUserEventsPlugin
* plugin
)
290 CFUUIDRef factoryID
= plugin
->_factoryID
;
294 CFPlugInRemoveInstanceForFactory(factoryID
);
295 CFRelease(factoryID
);
298 if (plugin
->_tokenToBrowserMap
)
299 CFRelease(plugin
->_tokenToBrowserMap
);
301 if (plugin
->_browsers
)
302 CFRelease(plugin
->_browsers
);
304 if (plugin
->_onAddEvents
)
305 CFRelease(plugin
->_onAddEvents
);
307 if (plugin
->_onRemoveEvents
)
308 CFRelease(plugin
->_onRemoveEvents
);
310 if (plugin
->_whileServiceExist
)
311 CFRelease(plugin
->_whileServiceExist
);
316 CFIndex count
= CFArrayGetCount(plugin
->_timers
);
317 CFRunLoopRef crl
= CFRunLoopGetCurrent();
319 for (i
= 0; i
< count
; ++i
)
321 CFRunLoopTimerRef timer
= (CFRunLoopTimerRef
)CFArrayGetValueAtIndex(plugin
->_timers
, i
);
322 CFRunLoopRemoveTimer(crl
, timer
, kCFRunLoopCommonModes
);
325 CFRelease(plugin
->_timers
);
331 /*******************************************************************************
332 *******************************************************************************/
333 void * UserEventAgentFactory(CFAllocatorRef allocator
, CFUUIDRef typeID
)
336 BonjourUserEventsPlugin
* result
= NULL
;
338 if (typeID
&& CFEqual(typeID
, kUserEventAgentTypeID
)) {
339 result
= Alloc(kUserEventAgentFactoryID
);
342 return (void *)result
;
346 #pragma mark Plugin Management
348 /*****************************************************************************
351 * This is invoked once when the plugin is loaded to do initial setup and
352 * allow us to register with launchd. If UserEventAgent crashes, the plugin
353 * will need to be reloaded, and hence this will get invoked again.
354 *****************************************************************************/
355 static void Install(void *instance
)
357 BonjourUserEventsPlugin
* plugin
= (BonjourUserEventsPlugin
*)instance
;
359 plugin
->_pluginContext
= UserEventAgentRegisterForLaunchEvents(sPluginIdentifier
, &ManageEventsCallback
, plugin
);
361 if (!plugin
->_pluginContext
)
363 fprintf(stderr
, "%s: failed to register for launch events.\n", sPluginIdentifier
);
369 /*****************************************************************************
370 * ManageEventsCallback
372 * This is invoked when launchd loads a event dictionary and needs to inform
373 * us what a daemon / agent is looking for.
374 *****************************************************************************/
375 static void ManageEventsCallback(UserEventAgentLaunchdAction action
, CFNumberRef token
, CFTypeRef eventMatchDict
, void* vContext
)
378 if (!eventMatchDict
|| CFGetTypeID(eventMatchDict
) != CFDictionaryGetTypeID())
380 fprintf(stderr
, "%s given non-dictionary for event dictionary\n", sPluginIdentifier
);
384 if (action
== kUserEventAgentLaunchdAdd
)
386 // Launchd wants us to add a launch event for this token and matching dictionary.
387 AddEventToPlugin((BonjourUserEventsPlugin
*)vContext
, token
, (CFDictionaryRef
)eventMatchDict
);
389 else if (action
== kUserEventAgentLaunchdRemove
)
391 // Launchd wants us to remove the event hook we setup for this token / matching dictionary.
392 RemoveEventFromPlugin((BonjourUserEventsPlugin
*)vContext
, token
);
396 fprintf(stderr
, "%s got unknown UserEventAction: %d\n", sPluginIdentifier
, action
);
402 #pragma mark Plugin Guts
405 /*****************************************************************************
408 * This method is invoked when launchd wishes the plugin to setup a launch
409 * event matching the parameters in the dictionary.
410 *****************************************************************************/
411 void AddEventToPlugin(BonjourUserEventsPlugin
* plugin
, CFNumberRef launchdToken
, CFDictionaryRef eventParameters
)
413 CFStringRef domain
= CFDictionaryGetValue(eventParameters
, sServiceDomainKey
);
414 CFStringRef type
= CFDictionaryGetValue(eventParameters
, sServiceTypeKey
);
415 CFStringRef name
= CFDictionaryGetValue(eventParameters
, sServiceNameKey
);
416 CFBooleanRef cfOnAdd
= CFDictionaryGetValue(eventParameters
, sOnServiceAddKey
);
417 CFBooleanRef cfOnRemove
= CFDictionaryGetValue(eventParameters
, sOnServiceRemoveKey
);
418 CFBooleanRef cfWhileSericeExists
= CFDictionaryGetValue(eventParameters
, sWhileServiceExistsKey
);
420 Boolean onAdd
= false;
421 Boolean onRemove
= false;
422 Boolean whileExists
= false;
424 if (cfOnAdd
&& CFGetTypeID(cfOnRemove
) == CFBooleanGetTypeID() && CFBooleanGetValue(cfOnAdd
))
427 if (cfOnRemove
&& CFGetTypeID(cfOnRemove
) == CFBooleanGetTypeID() && CFBooleanGetValue(cfOnRemove
))
430 if (cfWhileSericeExists
&& CFGetTypeID(cfWhileSericeExists
) == CFBooleanGetTypeID() && CFBooleanGetValue(cfWhileSericeExists
))
433 // A type is required. If none is specified, BAIL
434 if (!type
|| CFGetTypeID(type
) != CFStringGetTypeID())
436 fprintf(stderr
, "%s, a LaunchEvent is missing a service type.\n", sPluginIdentifier
);
440 // If we aren't suppose to launch on services appearing or disappearing, this service does nothing. Ignore.
441 if ((!onAdd
&& !onRemove
&& !whileExists
) || (onAdd
&& onRemove
&& whileExists
))
443 fprintf(stderr
, "%s, a LaunchEvent is missing both onAdd/onRemove/existance or has both.\n", sPluginIdentifier
);
447 // If no domain is specified, assume local.
450 domain
= CFSTR("local");
452 else if (CFGetTypeID(domain
) != CFStringGetTypeID() ) // If the domain is not a string, fai;
454 fprintf(stderr
, "%s, a LaunchEvent has a domain that is not a string.\n", sPluginIdentifier
);
459 // If we have a name filter, but it's not a string. This event it broken, bail.
460 if (name
&& CFGetTypeID(name
) != CFStringGetTypeID())
462 fprintf(stderr
, "%s, a LaunchEvent has a domain that is not a string.\n", sPluginIdentifier
);
467 NetBrowserInfo
* browser
= CreateBrowserForTypeAndDomain(plugin
, type
, domain
);
471 fprintf(stderr
, "%s, a LaunchEvent has a domain that is not a string.\n", sPluginIdentifier
);
475 // Create Event Dictionary
476 CFMutableDictionaryRef eventDictionary
= CFDictionaryCreateMutable(NULL
, 4, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
479 CFDictionarySetValue(eventDictionary
, sLaunchdTokenKey
, launchdToken
);
482 CFDictionarySetValue(eventDictionary
, sServiceNameKey
, name
);
484 // Add to the correct dictionary.
486 AddEventDictionary(eventDictionary
, plugin
->_onAddEvents
, browser
);
489 AddEventDictionary(eventDictionary
, plugin
->_onRemoveEvents
, browser
);
492 AddEventDictionary(eventDictionary
, plugin
->_whileServiceExist
, browser
);
495 CFDictionarySetValue(plugin
->_tokenToBrowserMap
, launchdToken
, browser
);
498 CFRelease(eventDictionary
);
499 NetBrowserInfoRelease(NULL
, browser
);
505 /*****************************************************************************
506 * RemoveEventFromPlugin
508 * This method is invoked when launchd wishes the plugin to setup a launch
509 * event matching the parameters in the dictionary.
510 *****************************************************************************/
511 void RemoveEventFromPlugin(BonjourUserEventsPlugin
* plugin
, CFNumberRef launchdToken
)
513 NetBrowserInfo
* browser
= (NetBrowserInfo
*)CFDictionaryGetValue(plugin
->_tokenToBrowserMap
, launchdToken
);
514 Boolean othersUsingBrowser
= false;
519 CFNumberGetValue(launchdToken
, kCFNumberLongLongType
, &value
);
520 fprintf(stderr
, "%s, Launchd asked us to remove a token we did not register!\nToken:%lld\n", sPluginIdentifier
, value
);
524 CFMutableArrayRef onAddEvents
= (CFMutableArrayRef
)CFDictionaryGetValue(plugin
->_onAddEvents
, browser
);
525 CFMutableArrayRef onRemoveEvents
= (CFMutableArrayRef
)CFDictionaryGetValue(plugin
->_onRemoveEvents
, browser
);
529 RemoveEventFromArray(onAddEvents
, launchdToken
);
531 // Is the array now empty, clean up
532 if (CFArrayGetCount(onAddEvents
) == 0)
533 CFDictionaryRemoveValue(plugin
->_onAddEvents
, browser
);
538 RemoveEventFromArray(onRemoveEvents
, launchdToken
);
540 // Is the array now empty, clean up
541 if (CFArrayGetCount(onRemoveEvents
) == 0)
542 CFDictionaryRemoveValue(plugin
->_onRemoveEvents
, browser
);
545 // Remove ourselves from the token dictionary.
546 CFDictionaryRemoveValue(plugin
->_tokenToBrowserMap
, launchdToken
);
548 // Check to see if anyone else is using this browser.
550 CFIndex count
= CFDictionaryGetCount(plugin
->_tokenToBrowserMap
);
551 NetBrowserInfo
** browsers
= malloc(count
* sizeof(NetBrowserInfo
*));
553 // Fetch the values of the token dictionary
554 CFDictionaryGetKeysAndValues(plugin
->_tokenToBrowserMap
, NULL
, (const void**)browsers
);
556 for (i
= 0; i
< count
; ++i
)
558 if (NetBrowserInfoEqual(browsers
[i
], browser
))
560 othersUsingBrowser
= true;
565 // If no one else is useing our browser, clean up!
566 if (!othersUsingBrowser
)
568 CFDictionaryRemoveValue(plugin
->_tokenToBrowserMap
, launchdToken
); // This triggers release and dealloc of the browser
575 /*****************************************************************************
576 * CreateBrowserForTypeAndDomain
578 * This method returns a NetBrowserInfo that is looking for a type of
579 * service in a domain. If no browser exists, it will create one and return it.
580 *****************************************************************************/
581 NetBrowserInfo
* CreateBrowserForTypeAndDomain(BonjourUserEventsPlugin
* plugin
, CFStringRef type
, CFStringRef domain
)
584 CFIndex count
= CFDictionaryGetCount(plugin
->_browsers
);
585 NetBrowserInfo
* browser
= NULL
;
586 CFDictionaryRef
* dicts
= malloc(count
* sizeof(CFDictionaryRef
));
587 NetBrowserInfo
** browsers
= malloc(count
* sizeof(NetBrowserInfo
*));
589 // Fetch the values of the browser dictionary
590 CFDictionaryGetKeysAndValues(plugin
->_browsers
, (const void**)browsers
, (const void**)dicts
);
592 // Loop thru the browsers list and see if we can find a matching one.
593 for (i
= 0; i
< count
; ++i
)
595 CFDictionaryRef browserDict
= dicts
[i
];
597 CFStringRef browserType
= CFDictionaryGetValue(browserDict
, sServiceTypeKey
);
598 CFStringRef browserDomain
= CFDictionaryGetValue(browserDict
, sServiceDomainKey
);
600 // If we have a matching browser, break
601 if (CFStringCompare(browserType
, type
, kCFCompareCaseInsensitive
) &&
602 CFStringCompare(browserDomain
, domain
, kCFCompareCaseInsensitive
))
604 browser
= browsers
[i
];
605 NetBrowserInfoRetain(NULL
, browser
);
610 // No match found, lets create one!
614 browser
= NetBrowserInfoCreate(type
, domain
, plugin
);
618 fprintf(stderr
, "%s, failed to search for %s.%s", sPluginIdentifier
, CStringFromCFString(type
) , CStringFromCFString(domain
));
624 // Service browser created, lets add this to ourselves to the dictionary.
625 CFMutableDictionaryRef browserDict
= CFDictionaryCreateMutable(NULL
, 2, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
627 CFDictionarySetValue(browserDict
, sServiceTypeKey
, type
);
628 CFDictionarySetValue(browserDict
, sServiceDomainKey
, domain
);
630 // Add the dictionary to the browsers dictionary.
631 CFDictionarySetValue(plugin
->_browsers
, browser
, browserDict
);
634 CFRelease(browserDict
);
643 /*****************************************************************************
646 * This method returns a NetBrowserInfo that matches the calling SDRef passed
647 * in via the callback.
648 *****************************************************************************/
649 NetBrowserInfo
* BrowserForSDRef(BonjourUserEventsPlugin
* plugin
, DNSServiceRef sdRef
)
652 CFIndex count
= CFDictionaryGetCount(plugin
->_browsers
);
653 NetBrowserInfo
* browser
= NULL
;
654 NetBrowserInfo
** browsers
= malloc(count
* sizeof(NetBrowserInfo
*));
656 // Fetch the values of the browser dictionary
657 CFDictionaryGetKeysAndValues(plugin
->_browsers
, (const void**)browsers
, NULL
);
659 // Loop thru the browsers list and see if we can find a matching one.
660 for (i
= 0; i
< count
; ++i
)
662 NetBrowserInfo
* currentBrowser
= browsers
[i
];
664 if (currentBrowser
->browserRef
== sdRef
)
666 browser
= currentBrowser
;
677 /*****************************************************************************
680 * Adds a event to a browser's event dictionary
681 *****************************************************************************/
683 void AddEventDictionary(CFDictionaryRef eventDict
, CFMutableDictionaryRef allEventsDictionary
, NetBrowserInfo
* key
)
685 CFMutableArrayRef eventsForBrowser
= (CFMutableArrayRef
)CFDictionaryGetValue(allEventsDictionary
, key
);
687 if (!eventsForBrowser
) // We have no events for this browser yet, lets add him.
689 eventsForBrowser
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
);
690 CFDictionarySetValue(allEventsDictionary
, key
, eventsForBrowser
);
694 CFRetain(eventsForBrowser
);
697 CFArrayAppendValue(eventsForBrowser
, eventDict
);
698 CFRelease(eventsForBrowser
);
701 /*****************************************************************************
702 * RemoveEventFromArray
704 * Searches a Array of Event Dictionaries to find one with a matching launchd
705 * token and remove it.
706 *****************************************************************************/
708 void RemoveEventFromArray(CFMutableArrayRef array
, CFNumberRef launchdToken
)
711 CFIndex count
= CFArrayGetCount(array
);
712 // Loop thru looking for us.
713 for (i
= 0; i
< count
; )
715 CFDictionaryRef eventDict
= CFArrayGetValueAtIndex(array
, i
);
716 CFNumberRef token
= CFDictionaryGetValue(eventDict
, sLaunchdTokenKey
);
718 if (CFEqual(token
, launchdToken
)) // This is the same event?
720 CFArrayRemoveValueAtIndex(array
, i
); // Remove the event,
721 break; // The token should only exist once, so it make no sense to continue.
725 ++i
; // If it's not us, advance.
731 #pragma mark Net Service Browser Stuff
734 /*****************************************************************************
735 * ServiceBrowserCallback
737 * This method is the heart of the plugin. It's the runloop callback annoucing
738 * the appearence and disappearance of network services.
739 *****************************************************************************/
741 void ServiceBrowserCallback (DNSServiceRef sdRef
,
742 DNSServiceFlags flags
,
743 uint32_t interfaceIndex
,
744 DNSServiceErrorType errorCode
,
745 const char* serviceName
,
747 const char* replyDomain
,
750 (void)interfaceIndex
;
753 BonjourUserEventsPlugin
* plugin
= (BonjourUserEventsPlugin
*)context
;
754 NetBrowserInfo
* browser
= BrowserForSDRef(plugin
, sdRef
);
756 if (!browser
) // Missing browser?
759 if (errorCode
!= kDNSServiceErr_NoError
)
762 CFStringRef cfServiceName
= CFStringCreateWithCString(NULL
, serviceName
, kCFStringEncodingUTF8
);
764 if (flags
& kDNSServiceFlagsAdd
)
766 HandleTemporaryEventsForService(plugin
, browser
, cfServiceName
, plugin
->_onAddEvents
);
767 HandleStateEventsForService(plugin
, browser
, cfServiceName
, true);
771 HandleTemporaryEventsForService(plugin
, browser
, cfServiceName
, plugin
->_onRemoveEvents
);
772 HandleStateEventsForService(plugin
, browser
, cfServiceName
, false);
775 CFRelease(cfServiceName
);
778 /*****************************************************************************
779 * HandleTemporaryEventsForService
781 * This method handles the firing of one shot events. Aka. Events that are
782 * signaled when a service appears / disappears. They have a temporarly
784 *****************************************************************************/
785 void HandleTemporaryEventsForService(BonjourUserEventsPlugin
* plugin
, NetBrowserInfo
* browser
, CFStringRef serviceName
, CFMutableDictionaryRef eventsDictionary
)
787 CFArrayRef events
= (CFArrayRef
)CFDictionaryGetValue(eventsDictionary
, browser
); // Get events for the browser we passed in.
791 if (!events
) // Somehow we have a orphan browser...
794 count
= CFArrayGetCount(events
);
796 // Go thru the events and run filters, notifity if they pass.
797 for (i
= 0; i
< count
; ++i
)
799 CFDictionaryRef eventDict
= (CFDictionaryRef
)CFArrayGetValueAtIndex(events
, i
);
800 CFStringRef eventServiceName
= (CFStringRef
)CFDictionaryGetValue(eventDict
, sServiceNameKey
);
801 CFNumberRef token
= (CFNumberRef
) CFDictionaryGetValue(eventDict
, sLaunchdTokenKey
);
803 // Currently we only filter on service name, that makes this as simple as...
804 if (!eventServiceName
|| CFEqual(serviceName
, eventServiceName
))
806 // Create Context Info
807 CFRunLoopTimerContext context
;
808 TimerContextInfo
* info
= TimerContextInfoCreate(plugin
, token
);
812 context
.retain
= TimerContextInfoRetain
;
813 context
.release
= TimerContextInfoRelease
;
814 context
.copyDescription
= TimerContextInfoCopyDescription
;
816 // Create and add one shot timer to flip the event off after a second
817 CFRunLoopTimerRef timer
= CFRunLoopTimerCreate(NULL
, CFAbsoluteTimeGetCurrent() + 1.0, 0, 0, 0, TemporaryEventTimerCallout
, &context
);
818 CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer
, kCFRunLoopCommonModes
);
821 UserEventAgentSetLaunchEventState(plugin
->_pluginContext
, token
, true);
824 TimerContextInfoRelease(info
);
832 /*****************************************************************************
833 * HandleStateEventsForService
835 * This method handles the toggling the state of a while exists event to
836 * reflect the network.
837 *****************************************************************************/
838 void HandleStateEventsForService(BonjourUserEventsPlugin
* plugin
, NetBrowserInfo
* browser
, CFStringRef serviceName
, Boolean didAppear
)
840 CFArrayRef events
= (CFArrayRef
)CFDictionaryGetValue(plugin
->_whileServiceExist
, browser
); // Get the _whileServiceExist events that are interested in this browser.
844 if (!events
) // Somehow we have a orphan browser...
847 count
= CFArrayGetCount(events
);
849 // Go thru the events and run filters, notifity if they pass.
850 for (i
= 0; i
< count
; ++i
)
852 CFDictionaryRef eventDict
= (CFDictionaryRef
)CFArrayGetValueAtIndex(events
, i
);
853 CFStringRef eventServiceName
= (CFStringRef
)CFDictionaryGetValue(eventDict
, sServiceNameKey
);
854 CFNumberRef token
= (CFNumberRef
) CFDictionaryGetValue(eventDict
, sLaunchdTokenKey
);
856 // Currently we only filter on service name, that makes this as simple as...
857 if (!eventServiceName
|| CFEqual(serviceName
, eventServiceName
))
858 UserEventAgentSetLaunchEventState(plugin
->_pluginContext
, token
, didAppear
);
862 /*****************************************************************************
863 * TemporaryEventTimerCallout
865 * This method is invoked a second after a watched service appears / disappears
866 * to toggle the state of the launch event back to false.
867 *****************************************************************************/
868 void TemporaryEventTimerCallout ( CFRunLoopTimerRef timer
, void *info
)
870 TimerContextInfo
* contextInfo
= (TimerContextInfo
*)info
;
872 UserEventAgentSetLaunchEventState(contextInfo
->plugin
->_pluginContext
, contextInfo
->token
, false);
874 // Remove from pending timers array.
876 CFIndex count
= CFArrayGetCount(contextInfo
->plugin
->_timers
);
878 for (i
= 0; i
< count
; ++i
)
880 CFRunLoopTimerRef item
= (CFRunLoopTimerRef
)CFArrayGetValueAtIndex(contextInfo
->plugin
->_timers
, i
);
887 CFArrayRemoveValueAtIndex(contextInfo
->plugin
->_timers
, i
);
891 #pragma mark Convenence
894 /*****************************************************************************
895 * CStringFromCFString
897 * Silly convenence function for dealing with non-critical CFSTR -> cStr
899 *****************************************************************************/
901 const char* CStringFromCFString(CFStringRef string
)
903 const char* defaultString
= "??????";
907 return defaultString
;
909 cstring
= CFStringGetCStringPtr(string
, kCFStringEncodingUTF8
);
911 return (cstring
) ? cstring
: defaultString
;
916 #pragma mark TimerContextInfo "Object"
919 /*****************************************************************************
920 * TimerContextInfoCreate
922 * Convenence for creating TimerContextInfo pseudo-objects
923 *****************************************************************************/
924 TimerContextInfo
* TimerContextInfoCreate(BonjourUserEventsPlugin
* plugin
, CFNumberRef token
)
926 TimerContextInfo
* info
= malloc(sizeof(TimerContextInfo
));
929 info
->plugin
= plugin
;
930 info
->token
= (CFNumberRef
)CFRetain(token
);
935 /*****************************************************************************
936 * TimerContextInfoRetain
938 * Convenence for retaining TimerContextInfo pseudo-objects
939 *****************************************************************************/
940 const void* TimerContextInfoRetain(const void* info
)
942 TimerContextInfo
* context
= (TimerContextInfo
*)info
;
952 /*****************************************************************************
953 * TimerContextInfoRelease
955 * Convenence for releasing TimerContextInfo pseudo-objects
956 *****************************************************************************/
957 void TimerContextInfoRelease(const void* info
)
959 TimerContextInfo
* context
= (TimerContextInfo
*)info
;
964 if (context
->refCount
== 1)
966 CFRelease(context
->token
);
976 /*****************************************************************************
977 * TimerContextInfoCopyDescription
979 * This method actually does nothing, but is just a stub so CF is happy.
980 *****************************************************************************/
981 CFStringRef
TimerContextInfoCopyDescription(const void* info
)
984 return CFStringCreateWithCString(NULL
, "TimerContextInfo: No useful description", kCFStringEncodingUTF8
);
989 #pragma mark NetBrowserInfo "Object"
991 /*****************************************************************************
992 * NetBrowserInfoCreate
994 * The method creates a NetBrowserInfo Object and initalizes it.
995 *****************************************************************************/
996 NetBrowserInfo
* NetBrowserInfoCreate(CFStringRef serviceType
, CFStringRef domain
, void* context
)
998 NetBrowserInfo
* outObj
= NULL
;
999 DNSServiceRef browserRef
= NULL
;
1000 char* cServiceType
= NULL
;
1001 char* cDomain
= NULL
;
1002 Boolean success
= true;
1004 CFIndex serviceSize
= CFStringGetMaximumSizeForEncoding(CFStringGetLength(serviceType
), kCFStringEncodingUTF8
);
1005 cServiceType
= calloc(serviceSize
, 1);
1006 success
= CFStringGetCString(serviceType
, cServiceType
, serviceSize
, kCFStringEncodingUTF8
);
1010 CFIndex domainSize
= CFStringGetMaximumSizeForEncoding(CFStringGetLength(domain
), kCFStringEncodingUTF8
);
1011 cDomain
= calloc(serviceSize
, 1);
1012 success
= success
&& CFStringGetCString(domain
, cDomain
, domainSize
, kCFStringEncodingUTF8
);
1017 fprintf(stderr
, "LaunchEvent has badly encoded service type or domain.\n");
1026 DNSServiceErrorType err
= DNSServiceBrowse(&browserRef
, 0, 0, cServiceType
, cDomain
, ServiceBrowserCallback
, context
);
1028 if (err
!= kDNSServiceErr_NoError
)
1030 fprintf(stderr
, "Failed to create browser for %s, %s\n", cServiceType
, cDomain
);
1039 DNSServiceSetDispatchQueue(browserRef
, dispatch_get_main_queue());
1042 outObj
= malloc(sizeof(NetBrowserInfo
));
1044 outObj
->refCount
= 1;
1045 outObj
->browserRef
= browserRef
;
1055 /*****************************************************************************
1056 * NetBrowserInfoRetain
1058 * The method retains a NetBrowserInfo object.
1059 *****************************************************************************/
1060 const void* NetBrowserInfoRetain(CFAllocatorRef allocator
, const void* info
)
1063 NetBrowserInfo
* obj
= (NetBrowserInfo
*)info
;
1073 /*****************************************************************************
1074 * NetBrowserInfoRelease
1076 * The method releases a NetBrowserInfo object.
1077 *****************************************************************************/
1078 void NetBrowserInfoRelease(CFAllocatorRef allocator
, const void* info
)
1081 NetBrowserInfo
* obj
= (NetBrowserInfo
*)info
;
1086 if (obj
->refCount
== 1)
1088 DNSServiceRefDeallocate(obj
->browserRef
);
1098 /*****************************************************************************
1099 * NetBrowserInfoEqual
1101 * The method is used to compare two NetBrowserInfo objects for equality.
1102 *****************************************************************************/
1103 Boolean
NetBrowserInfoEqual(const void *value1
, const void *value2
)
1105 NetBrowserInfo
* obj1
= (NetBrowserInfo
*)value1
;
1106 NetBrowserInfo
* obj2
= (NetBrowserInfo
*)value2
;
1108 if (obj1
->browserRef
== obj2
->browserRef
)
1114 /*****************************************************************************
1115 * NetBrowserInfoHash
1117 * The method is used to make a hash for the object. We can cheat and use the
1119 *****************************************************************************/
1120 CFHashCode
NetBrowserInfoHash(const void *value
)
1122 return (CFHashCode
)((NetBrowserInfo
*)value
)->browserRef
;
1126 /*****************************************************************************
1127 * NetBrowserInfoCopyDescription
1130 *****************************************************************************/
1131 CFStringRef
NetBrowserInfoCopyDescription(const void *value
)
1134 return CFStringCreateWithCString(NULL
, "NetBrowserInfo: No useful description", kCFStringEncodingUTF8
);