Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
1312 lines (1082 sloc) 41.7 KB
/*
* Copyright (c) 2009 Stuart Crook. All rights reserved.
*
* Created by Stuart Crook on 23/02/2009.
* This source code is a reverse-engineered implementation of the notification center from
* Apple's core foundation library.
*
* The PureFoundation code base consists of a combination of original code provided by contributors
* and open-source code drawn from a nuber of other projects -- chiefly Cocotron (www.coctron.org)
* and GNUStep (www.gnustep.org). Under the principal that the least-liberal licence trumps the others,
* the PureFoundation project as a whole is released under the GNU Lesser General Public License (LGPL).
* Where code has been included from other projects, that project's licence, along with a note of the
* exact source (eg. file name) is included inline in the source.
*
* Since PureFoundation is a dynamically-loaded shared library, it is my interpretation of the LGPL
* that any application linking to it is not automatically bound by its terms.
*
* See the text of the LGPL (from http://www.gnu.org/licenses/lgpl-3.0.txt, accessed 26/2/09):
*
*/
#include "CFNotificationCenter.h"
#include <CoreFoundation/CFPropertyList.h>
#include <CoreFoundation/CFNumber.h>
#include <CoreFoundation/CFMessagePort.h>
#include <CoreFoundation/CFStream.h>
#include <CoreFoundation/CFString.h>
#include "CFRuntime.h"
#include "CFPriv.h"
#include "CFInternal.h"
#include <stdlib.h>
#include <stdio.h>
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_LINUX
#include <unistd.h>
#endif
#define CF_LOCAL_CENTER 0
#define CF_DIST_CENTER 1
#define CF_DARWIN_CENTER 2
#define CF_OBS_SIZE 32
typedef struct __CFObserver {
//CFStringRef name; // can be NULL
CFHashCode hash; // hashed string, for speed. can be NULL
const void *object; // can be NULL
const void *observer; // may be NULL
CFNotificationCallback callback;
CFNotificationSuspensionBehavior sb;
} __CFObserver;
struct __CFNotificationCenter {
CFRuntimeBase _base;
CFIndex type;
CFIndex suspended; // <- move into base bits?
CFIndex observers;
CFIndex capacity;
__CFObserver *obs;
CFSpinLock_t lock;
};
// STRUCTURES FOR DISTRIBUTED CENTER HERE
// message ids recognised by ddistnoted
#define NOTIFICATION 0
#define REGISTER_PORT 1
#define UNREGISTER_PORT 2
#define REGISTER_NOTIFICATION 3
#define UNREGISTER_NOTIFICATION 4
#define CF_DIST_SIZE 32
#define CF_QUEUE_SIZE 32
typedef struct __CFDistNotification {
CFHashCode hash;
CFHashCode object;
CFIndex count;
} __CFDistNotification;
typedef struct __CFQueueRecord {
CFStringRef name;
const void *object; // will be a CFStringRef...
const void *observer;
CFDictionaryRef userInfo;
CFNotificationCallback callback;
} __CFQueueRecord;
typedef struct __CFDistributedCenterInfo {
CFMessagePortRef local;
CFMessagePortRef remote;
long session;
CFHashCode uid;
CFRunLoopSourceRef rls;
CFIndex count;
CFIndex capacity;
__CFDistNotification *nots;
CFIndex queueCount;
CFIndex queueCapacity;
__CFQueueRecord *queue;
} __CFDistributedCenterInfo;
static __CFDistributedCenterInfo __CFDistInfo = { NULL, NULL, 0, 0, NULL, 0, 0, NULL, 0, 0, NULL };
// these structures come from the ddistnoted project -- beter keep their definitions synced
typedef struct dndNotReg {
CFHashCode uid;
CFHashCode name;
CFHashCode object;
} dndNotReg;
typedef struct dndNotHeader {
long session;
CFHashCode name;
CFHashCode object;
CFIndex flags;
} dndNotHeader;
#if DEPLOYMENT_TARGET_MACOSX
#include <notify.h>
#include <CoreFoundation/CFMachPort.h>
#endif
/*
* Darwin notifications
*
* There are two ways we could organise recieving notify messages via Mach ports. The
* method we are using here is to use a single port for all messages, with an int token
* in the message header used to identify the notification name.
*
* The alternative, which we may have to consider if the number of notifications in
* general use swamps a single port's message queue, is to allocate one Mach port per
* notification name.
*/
#define CF_DARWIN_SIZE 32
typedef struct __CFDarwinNotifications {
//CFStringRef name;
CFHashCode hash;
int token;
CFIndex count;
} __CFDarwinNotifications;
typedef struct __CFDarwinCenterInfo {
#if defined(__MACH__)
CFMachPortRef port;
#endif
CFRunLoopSourceRef rls;
CFIndex count;
CFIndex capacity;
__CFDarwinNotifications *nots;
} __CFDarwinCenterInfo;
#if defined(__MACH__)
static __CFDarwinCenterInfo __CFDarwinInfo = { NULL, NULL, 0, 0, NULL };
#else
static __CFDarwinCenterInfo __CFDarwinInfo = { NULL, 0, 0, NULL };
#endif
// the task's singleton centres
static CFNotificationCenterRef __CFLocalCenter = NULL;
static CFNotificationCenterRef __CFDistributedCenter = NULL;
static CFNotificationCenterRef __CFDarwinCenter = NULL;
static CFMutableDictionaryRef __CFHashStore = NULL;
// these control access to the Get functions. access to individual structure are
// embeded within those structures
static CFSpinLock_t __CFLocalCenterLock = CFSpinLockInit;
static CFSpinLock_t __CFDistributedCenterLock = CFSpinLockInit;
static CFSpinLock_t __CFDarwinCenterLock = CFSpinLockInit;
static CFSpinLock_t __CFHashStoreLock = CFSpinLockInit;
// store for our typeID
static CFTypeID __kCFNotificationCenterTypeID = _kCFRuntimeNotATypeID;
// life-cycle stuff can wait for now
static const CFRuntimeClass __CFNotificationCenterClass = {
0,
"CFNotificationCenter",
NULL, // init
NULL, // copy
NULL, //__CFDataDeallocate,
NULL, //__CFDataEqual,
NULL, //__CFDataHash,
NULL, //
NULL, //__CFDataCopyDescription
};
__private_extern__ void __CFNotificationCenterInitialize(void)
{
__kCFNotificationCenterTypeID = _CFRuntimeRegisterClass(&__CFNotificationCenterClass);
__CFHashStore = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, NULL, NULL );
}
CFTypeID CFNotificationCenterGetTypeID(void) { return __kCFNotificationCenterTypeID; }
/*
* The __CFAdd and Remove functions take an optional callback of this type which is called once
* for each observer added or removed. It runs under the current spinlock and gets over the
* problem of returning a list of notification to remove from the RemoveEvery function
*/
typedef void (*__CFAdderCallBack)( CFStringRef name, CFHashCode hash, CFHashCode object );
typedef void (*__CFRemoverCallBack)( CFHashCode name, CFHashCode object );
/*
* Declaration of functions internal to this file
*/
CFNotificationCenterRef __CFCreateCenter( CFIndex type );
void __CFAddObserver( CFNotificationCenterRef center, const void *observer, CFNotificationCallback callBack, CFStringRef name, const void *object, CFNotificationSuspensionBehavior suspensionBehavior, __CFAdderCallBack cb );
void __CFRemoveObserver( CFNotificationCenterRef center, const void *observer, CFHashCode name, const void *object, __CFRemoverCallBack cb );
void __CFRemoveEveryObserver( CFNotificationCenterRef center, const void *observer, __CFRemoverCallBack cb );
void __CFInvokeCallBacks( CFNotificationCenterRef center, CFHashCode name, CFStringRef nameReturn, const void *object, const void *objectReturn, CFDictionaryRef userInfo, Boolean deliverNow );
//void __PFPostLocalNotification( PFNotificationCenterRef center, CFStringRef name, const void *object, CFDictionaryRef userInfo );
void __CFPostDistributedNotification( CFNotificationCenterRef center, CFStringRef name, CFStringRef object, CFDictionaryRef userInfom, CFOptionFlags options );
void __CFPostDarwinNotification( CFNotificationCenterRef center, CFStringRef name );
void __CFAddQueue( CFStringRef name, const void *object, const void *observer, CFDictionaryRef userInfo, CFNotificationCallback callback, Boolean coalesce );
void __CFDeliverQueue( void );
Boolean _CFNotificationCenterIsSuspended( CFNotificationCenterRef center );
void _CFNotificationCenterSetSuspended( CFNotificationCenterRef center, Boolean suspended );
#if DEPLOYMENT_TARGET_MACOSX
void __CFDarwinCallBack(CFMachPortRef port, void *msg, CFIndex size, void *info);
#endif
/*
* Manage the message queue, which is used to store distributed notifications
* when delivery has been suspended.
*/
void __CFAddQueue( CFStringRef name, const void *object, const void *observer, CFDictionaryRef userInfo, CFNotificationCallback callback, Boolean coalesce )
{
CFRetain(userInfo);
__CFQueueRecord *queue = __CFDistInfo.queue;
CFIndex count = __CFDistInfo.queueCount;
if( coalesce ) // do we check for this notification in the queue?
{
while( count-- )
{
// we're looking for exactl matches on name, object, observer and callback
if( (queue->name == name) && (queue->object == object)
&& (queue->observer == observer) && (queue->callback == callback) )
{
CFRelease(queue->userInfo);
queue->userInfo = userInfo;
break;
}
queue++;
}
}
// either we're not coalescing or this notification wasn't already enqueued
if( __CFDistInfo.count == __CFDistInfo.capacity )
{
__CFDistInfo.capacity += CF_QUEUE_SIZE;
__CFDistInfo.queue = (__CFQueueRecord*)realloc( __CFDistInfo.queue, (CF_QUEUE_SIZE * sizeof(__CFQueueRecord)) );
if( __CFDistInfo.queue == NULL ) return;
}
queue = __CFDistInfo.queue + __CFDistInfo.queueCount;
queue->name = name;
queue->object = object;
queue->observer = observer;
queue->callback = callback;
queue->userInfo = userInfo;
__CFDistInfo.queueCount++;
}
void __CFDeliverQueue( void )
{
CFIndex count = __CFDistInfo.queueCount;
__CFQueueRecord *queue = __CFDistInfo.queue;
while( count-- )
{
queue->callback( (CFNotificationCenterRef)__CFDistributedCenter, (void*)queue->observer, queue->name, queue->object, queue->userInfo );
CFRelease(queue->userInfo);
queue->name = NULL;
queue->object = NULL;
queue->observer = NULL;
queue->callback = NULL;
queue->userInfo = NULL;
}
__CFDistInfo.queueCount = 0;
}
/*
* Set the suspended condition of the distributed notification centre (only). At the
* moment these will be left as no-ops, to be activated when we write the
* NSDitributedNotificationCenter class (which provides -setSuspended: and -suspended
* methods).
*
* The docs suggested that these functions existed, and running nm over CoreFoundation
* revealed their names. I'm just guessing their prototypes at the moment, and hoping
* that nothing tries to call them directly.
*/
Boolean _CFNotificationCenterIsSuspended( CFNotificationCenterRef center ) { return center->suspended; }
void _CFNotificationCenterSetSuspended( CFNotificationCenterRef center, Boolean suspended )
{
if( center->type == CF_DIST_CENTER ) // only suspendable type
{
__CFSpinLock(&center->lock);
if( center->suspended != suspended ) // changing state?
{
center->suspended = suspended;
// have we just un-suspended and are there notifications to deliver?
if( !suspended && (__CFDistInfo.queueCount != 0) )
__CFDeliverQueue();
}
__CFSpinUnlock(&center->lock);
}
}
/*
* Strings (names for all notifications, and objects from distributed observers) are
* stored as hashes in the look-up tables, with strings stored in the __CFHashStore
* dictionary
*
* Strings are copied into the dictionary. At present, they are never removed.
*/
CFHashCode __CFNCHash( CFStringRef string );
CFStringRef __CFNCUnhash( CFHashCode hash );
// this should maybe be called "hashAndStore" - use CFHash() for just the hash
CFHashCode __CFNCHash( CFStringRef string )
{
if( string == NULL ) return 0;
CFHashCode hash = CFHash(string);
__CFSpinLock(&__CFHashStoreLock);
if( !CFDictionaryContainsKey(__CFHashStore, (const void *)hash) )
CFDictionaryAddValue( __CFHashStore, (const void *)hash, CFStringCreateCopy( kCFAllocatorDefault, string) );
__CFSpinUnlock(&__CFHashStoreLock);
return hash;
}
CFStringRef __CFNCUnhash( CFHashCode hash )
{
CFStringRef string;
__CFSpinLock(&__CFHashStoreLock);
if( hash == 0 )
string = NULL;
else
string = (CFStringRef)CFDictionaryGetValue(__CFHashStore, (const void *)hash);
__CFSpinUnlock(&__CFHashStoreLock);
return string;
}
/*
* Basic diagnostic functions
*/
#include <stdio.h>
void __CFCenterDiag( CFNotificationCenterRef c );
void __CFObserverDiag( __CFObserver o );
void __CFCenterDiag( CFNotificationCenterRef c )
{
//printf("centre: type = %d, observers = %d, capacity = %d, obs at 0x%X\n", c->type, c->observers, c->capacity, c->obs);
}
void __CFObserverDiag( __CFObserver o )
{
//printf("observer: hash = %u, object = 0x%X, observer = 0x%X, callback to 0x%X, sb = %d\n", o.hash, o.object, o.observer, o.callback, o.sb);
}
// END DIAGNOSTIC FUNCTIONS
/*
* Add or remove a notification from the table of distributed notifications we are
* monitoring. When the first notification with a unique signature is added (or the
* last is removed) we make a call to the ddistnoted daemon to register (or unregister)
* for that notification.
*
* When the first monitored notification is added (or the last is removed) we add the
* message port source to (or remove it from) the main runloop.
*/
#if DEPLOYMENT_TARGET_MACOSX
CFDataRef __CFDistRecieve( CFMessagePortRef local, SInt32 msgid, CFDataRef data, void *info );
#endif
void __CFDistAddNotification( CFStringRef name, CFHashCode hash, CFHashCode object );
void __CFDistRemoveNotification( CFHashCode hash, CFHashCode object );
#if DEPLOYMENT_TARGET_MACOSX
/*
* recieves messages from the ddistnoted daemon
*
* In a later version we're going to have to take note of the centre's suspended
* state, queuing and coalescing messages as needed
*/
CFDataRef __CFDistRecieve( CFMessagePortRef local, SInt32 msgid, CFDataRef data, void *info )
{
//printf("local port done got a message.\n");
// center hasn't been created at the time the callback's set, so just as easier to pull in the global
CFNotificationCenterRef center = __CFDistributedCenter;
CFIndex length = CFDataGetLength(data);
if( length < sizeof(dndNotHeader) ) return NULL;
CFReadStreamRef rs = CFReadStreamCreateWithBytesNoCopy( kCFAllocatorDefault, CFDataGetBytePtr(data), length, NULL );
CFReadStreamOpen(rs);
dndNotHeader header;
CFReadStreamRead( rs, (UInt8 *)&header, sizeof(dndNotHeader) );
//printf("\tmessage name = %u, object = %u, flags = %u\n", header.name, header.object, header.flags);
// the rest of the data contains an array: [name, object, userInfo] with kCFBooleanFalse representing
// a missing object or userInfo
CFPropertyListRef array = NULL;
CFIndex format = kCFPropertyListBinaryFormat_v1_0;
array = CFPropertyListCreateFromStream( kCFAllocatorDefault, rs, 0, kCFPropertyListImmutable, &format, NULL );
if( (array == NULL) || (CFGetTypeID(array) != CFArrayGetTypeID()) ) return NULL;
// now recover and set up the arguments to invoke callbacks. If either name or object hasn't been
// encountered (and hashed) before, add it to the hash store now, so that coalescing based on
// pointers will work later. If it ever needs to.
CFStringRef name;
if( (name = __CFNCUnhash(header.name)) == NULL ) // we haven't met this name before
{
__CFNCHash( CFArrayGetValueAtIndex(array, 0) );
name = __CFNCUnhash(header.name);
}
CFStringRef object;
if( header.object == 0 )
object = NULL;
else
if( (object = __CFNCUnhash(header.object)) == NULL )
{
__CFNCHash( CFArrayGetValueAtIndex(array, 1) );
object = __CFNCUnhash(header.object);
}
CFPropertyListRef userInfo = CFArrayGetValueAtIndex(array, 2);
if( CFGetTypeID(userInfo) != CFDictionaryGetTypeID() ) userInfo = NULL;
// we deliver now if the centre isn't suspended or the messages header says we should
Boolean deliverNow = header.flags | kCFNotificationDeliverImmediately;
__CFInvokeCallBacks(center, header.name, name, (const void *)header.object, (const void *)object, userInfo, deliverNow);
if( userInfo != NULL ) CFRelease(userInfo);
return NULL;
}
#endif
/*
* Add a notification to the table of registered notifications, optionally registering it with
* the ddistnoted daemon and adding the local message port's source to the main runloop
*
* We can ignore name.
*/
void __CFDistAddNotification( CFStringRef name, CFHashCode hash, CFHashCode object )
{
//fprintf(stderr, "register distributed notification %8X, %8X\n", hash, object);
CFIndex count = __CFDistInfo.count;
__CFDistNotification *nots = __CFDistInfo.nots;
while( count-- )
{
while( nots->count == 0 ) nots++; // count == 0 is the empty record marker
if( (nots->hash == hash) && (nots->object == object) )
{
nots->count++;
return;
}
nots++;
}
// we need to register for this notification
dndNotReg info = { __CFDistInfo.uid, hash, object };
CFDataRef data = CFDataCreate( kCFAllocatorDefault, (const UInt8 *)&info, sizeof(dndNotReg) );
#if DEPLOYMENT_TARGET_MACOSX
SInt32 result = CFMessagePortSendRequest( __CFDistInfo.remote, REGISTER_NOTIFICATION, data, 1.0, 0.0, NULL, NULL );
#else
SInt32 result = 0;
#endif
CFRelease(data);
if(result != kCFMessagePortSuccess) return;
if( __CFDistInfo.count == __CFDistInfo.capacity )
{
fprintf(stderr, "Increasing capacity of dist notification list\n");
__CFDistInfo.capacity += CF_DIST_SIZE;
__CFDistInfo.nots = (__CFDistNotification*)realloc( __CFDistInfo.nots, (__CFDistInfo.capacity * sizeof(__CFDistNotification)) );
if( __CFDistInfo.nots == NULL ) return;
// because I'm unsure if realloc zeroes the new memory...
nots = __CFDistInfo.nots + __CFDistInfo.count;
for( int i = 0; i < CF_DIST_SIZE; i++ ) (nots++)->count = 0;
nots = __CFDistInfo.nots + __CFDistInfo.count;
}
else
{
nots = __CFDistInfo.nots;
while( nots->count != 0 ) nots++;
}
nots->hash = hash;
nots->object = object;
nots->count = 1;
if( 0 == __CFDistInfo.count++ )
CFRunLoopAddSource( CFRunLoopGetMain(), __CFDistInfo.rls, kCFRunLoopCommonModes );
//fprintf(stderr, "added notification (no.%d) and leaving\n", __CFDistInfo.count);
}
void __CFDistRemoveNotification( CFHashCode hash, CFHashCode object )
{
//fprintf(stderr, "dist remove notification\n");
CFIndex count = __CFDistInfo.count;
__CFDistNotification *nots = __CFDistInfo.nots;
while( count-- )
{
while( nots->count == 0 ) nots++; // count == 0 is the empty record marker
if( (nots->hash == hash) && (nots->object == object) )
{
if( 0 == --nots->count )
{
dndNotReg info = { __CFDistInfo.uid, hash, object };
CFDataRef data = CFDataCreate( kCFAllocatorDefault, (const UInt8 *)&info, sizeof(dndNotReg) );
if( data != NULL )
{
#if DEPLOYMENT_TARGET_MACOSX
CFMessagePortSendRequest(__CFDistInfo.remote, UNREGISTER_NOTIFICATION, data, 1.0, 1.0, NULL, NULL);
#endif
CFRelease(data);
}
nots->hash = 0;
nots->object = 0;
if( 0 == --__CFDistInfo.count )
CFRunLoopRemoveSource( CFRunLoopGetMain(), __CFDistInfo.rls, kCFRunLoopCommonModes );
}
return;
}
nots++;
}
//fprintf(stderr, "dist remove notification done\n");
}
/*
* Functions used to add and remove notifications to the list we are monitoring and register
* with notifyd to recieve them. We track how many times each is added and removed, so we can
* unregister for notifications when they're no-longer needed. As above, assumes the structure
* is spinlocked.
*/
void __CFDarwinAddNotification( CFStringRef name, CFHashCode hash, CFHashCode ignored );
void __CFDarwinRemoveNotification( CFHashCode hash, CFHashCode ignored );
void __CFDarwinAddNotification( CFStringRef name, CFHashCode hash, CFHashCode ignored )
{
//CFHashCode hash = CFHash(name);
CFIndex count = __CFDarwinInfo.count;
__CFDarwinNotifications *nots = __CFDarwinInfo.nots;
while( count-- )
{
while( nots->hash == 0 ) nots++;
if( nots->hash == hash ) // already registered for this notification
{
nots->count++;
return;
}
nots++;
}
// haven't registered for this notification yet
CFIndex length = CFStringGetLength(name);
STACK_BUFFER_DECL(char, buffer, ++length);
CFStringGetCString(name, buffer, length, kCFStringEncodingASCII);
int token;
#if DEPLOYMENT_TARGET_MACOSX
mach_port_t port = CFMachPortGetPort(__CFDarwinInfo.port);
if( 0 != notify_register_mach_port(buffer, &port, NOTIFY_REUSE, &token) ) return;
#endif
if( __CFDarwinInfo.count == __CFDarwinInfo.capacity )
{
__CFDarwinInfo.capacity += CF_DARWIN_SIZE;
__CFDarwinInfo.nots = (__CFDarwinNotifications*)realloc( __CFDarwinInfo.nots, ( __CFDarwinInfo.capacity * sizeof(__CFDarwinNotifications)));
if( __CFDarwinInfo.nots == NULL ) return;
// because I'm unsure if realloc zeroes the new memory...
nots = __CFDarwinInfo.nots + __CFDarwinInfo.count;
for( int i = 0; i < CF_DARWIN_SIZE; i++ ) (nots++)->hash = 0;
nots = __CFDarwinInfo.nots + __CFDarwinInfo.count;
}
else
{
nots = __CFDarwinInfo.nots;
while( nots->hash != 0 ) nots++;
}
nots->hash = hash;
//nots->name = name;
nots->token = token;
nots->count = 1;
if( 0 == __CFDarwinInfo.count++ )
CFRunLoopAddSource( CFRunLoopGetMain(), __CFDarwinInfo.rls, kCFRunLoopCommonModes );
}
void __CFDarwinRemoveNotification( CFHashCode hash, CFHashCode ignored )
{
//CFHashCode hash = CFHash(name);
CFIndex count = __CFDarwinInfo.count;
__CFDarwinNotifications *nots = __CFDarwinInfo.nots;
while( count-- )
{
while( nots->hash == 0 ) nots++;
if( nots->hash == hash )
{
if( nots->count-- > 1 ) return; // still want to recieve the notification
#if defined(__MACH__)
notify_cancel(nots->token);
#endif
nots->hash = 0;
//nots->name = NULL;
nots->token = 0;
nots->count = 0;
if( --__CFDarwinInfo.count == 0 )
CFRunLoopRemoveSource( CFRunLoopGetMain(), __CFDarwinInfo.rls, kCFRunLoopCommonModes );
return;
}
nots++;
}
}
/*
* Add the observer info into the table of observers for the notification center, growing the
* table if need be. Duplicate observers with identical signatures are allowed.
*/
void __CFAddObserver( CFNotificationCenterRef center, const void *observer, CFNotificationCallback callBack, CFStringRef name, const void *object, CFNotificationSuspensionBehavior suspensionBehavior, __CFAdderCallBack cb )
{
__CFObserver *obs;
__CFSpinLock(&center->lock);
if( center->observers == center->capacity )
{
//fprintf(stderr, "increasing size of observer records for center type %d\n", center->type);
center->capacity += CF_OBS_SIZE;
center->obs = (__CFObserver*)realloc(center->obs, (center->capacity * sizeof(__CFObserver)));
if( center->obs == NULL )
{
fprintf(stderr, "Couldn't realloc observer records for notification center type %d\n", center->type);
__CFSpinUnlock(&center->lock);
return;
}
obs = center->obs + center->observers;
// because I'm unsure if realloc zeroes the new memory...
for( int i = 0; i < CF_OBS_SIZE; i++ ) (obs++)->callback = NULL;
obs = center->obs + center->observers;
}
else
{
obs = center->obs;
while( obs->callback != NULL ) obs++;
}
// hash and store the name
CFHashCode hash = __CFNCHash(name);
//obs->name = name;
obs->hash = hash;
obs->object = object;
obs->observer = observer;
obs->callback = callBack;
obs->sb = suspensionBehavior;
center->observers++;
if( cb != NULL ) cb(name, hash, (CFHashCode)object);
__CFSpinUnlock(&center->lock);
}
/*
* Remove the observer with the given signature from the notification center's table.
*/
void __CFRemoveObserver( CFNotificationCenterRef center, const void *observer, CFHashCode name, const void *object, __CFRemoverCallBack cb )
{
__CFSpinLock(&center->lock);
//CFHashCode hash = (name == NULL) ? 0 : CFHash(name);
CFIndex count = center->observers;
__CFObserver *obs = center->obs;
while( count-- )
{
while( obs->callback == NULL ) obs++;
if( (obs->observer == observer)
&& /* match name hash */ ((name == 0) || (name == obs->hash))
&& /* match object */((object == NULL) || (object == obs->object)) )
{
//obs->name = NULL;
obs->hash = 0;
obs->object = NULL;
obs->observer = NULL;
obs->callback = NULL;
obs->sb = 0;
center->observers--;
if( cb != NULL ) cb(name, (CFHashCode)object);
}
obs++;
}
__CFSpinUnlock(&center->lock);
}
/*
* Remove every instance of the observer from the notification centre's table.
*/
void __CFRemoveEveryObserver( CFNotificationCenterRef center, const void *observer, __CFRemoverCallBack cb )
{
__CFSpinLock(&center->lock);
CFIndex count = center->observers;
__CFObserver *obs = center->obs;
while( count-- )
{
while( obs->callback == NULL ) obs++;
if( obs->observer == observer )
{
if( cb != NULL ) cb(obs->hash, (CFHashCode)obs->object);
//obs->name = NULL;
obs->hash = 0;
obs->object = NULL;
obs->observer = NULL;
obs->callback = NULL;
obs->sb = 0;
center->observers--;
}
obs++;
}
__CFSpinUnlock(&center->lock);
}
/*
* Invoke the callback functions for the observers whose signature match the arguments.
*
* This is a general routine which handles the special suspension behaviour of the
* distributed centre at the cost of extra un-needed checks while processing local
* and Darwin centre notifications.
*
* I'm not entirely sure whether notifications sent with kCFNotificationDeliverImmediately
* should behave like notifications delivered to suspended observers with the deliver
* immediately behaviour and flush the notification queue... so at the moment it just
* jumps the queue and is delivered on its own.
*
* The name and object arguments are used to find matching observers, while the nameReturn
* and objectReturn are passed to the observer's callbacks or queued.
* Local: object == objectReturn
* Distributed: object == hash, objectReturn == CFStringRef
* Darwin: object == objectReturn == NULL
*/
void __CFInvokeCallBacks( CFNotificationCenterRef center, CFHashCode name, CFStringRef nameReturn, const void *object, const void *objectReturn, CFDictionaryRef userInfo, Boolean deliverNow )
{
__CFSpinLock(&center->lock);
CFIndex count = center->observers;
__CFObserver *obs = center->obs;
//printf("A\n");
//__CFCenterDiag(center);
// process each observer in the table
while( count-- )
{
//printf("B\n");
// find a record with a non-NULL callback field
while( obs->callback == NULL ) obs++;
//printf("C\n");
//__CFObserverDiag( *obs );
//printf("observer: 0x%X\n");
// for an observer to qualify to recieve a notification, it need to match
// both name and object, taking into account the NULL-case "match any name
// or object"
if( /* match name hash */ ((obs->hash == 0) || (obs->hash == name))
/* match object */ && ((obs->object == NULL) || (obs->object == object)) )
{
// found a match, now do we deliver the notification?
if( deliverNow /* non-dist short-circuit */ || !center->suspended )
{
// CFMachPort source suggested unlocking before invoking callbacks
__CFSpinUnlock(&center->lock);
obs->callback((CFNotificationCenterRef)center, (void*)obs->observer, nameReturn, objectReturn, userInfo);
__CFSpinLock(&center->lock);
}
else switch(obs->sb)
{
case CFNotificationSuspensionBehaviorDrop: break;
case CFNotificationSuspensionBehaviorCoalesce:
__CFAddQueue( nameReturn, objectReturn, obs->observer, userInfo, obs->callback, TRUE );
break;
case CFNotificationSuspensionBehaviorHold:
__CFAddQueue( nameReturn, objectReturn, obs->observer, userInfo, obs->callback, FALSE );
break;
case CFNotificationSuspensionBehaviorDeliverImmediately:
if( __CFDistInfo.queueCount != 0 ) __CFDeliverQueue();
obs->callback( (CFNotificationCenterRef)center, (void*)obs->observer, nameReturn, objectReturn, userInfo );
break;
}
}
obs++;
}
__CFSpinUnlock(&center->lock);
}
#if defined(__MACH__)
/*
* The CFMachPortCallBack invoked when a message arrives at the runloop from notifyd. The
* Darwin notification centre is passed in as the info... just because.
*/
void __CFDarwinCallBack(CFMachPortRef port, void *msg, CFIndex size, void *info)
{
//printf("Got a message from a darwin port!\n");
// first we retrieve the token associated with the notification
int token = ((mach_msg_header_t *)msg)->msgh_id;
// then we look for the matching record
CFIndex count = __CFDarwinInfo.count;
__CFDarwinNotifications *nots = __CFDarwinInfo.nots;
while( count-- )
{
while( nots->hash == 0 ) nots++;
if( nots->token == token ) // found it
{
count = -1;
break;
}
nots++;
}
if( count != -1 ) return; // couldn't find the matching notification
// then we send the notification to all the matching observers
__CFInvokeCallBacks(__CFDarwinCenter, nots->hash, __CFNCUnhash(nots->hash), NULL, NULL, NULL, TRUE);
}
#endif
// shared creation method
CFNotificationCenterRef __CFCreateCenter( CFIndex type )
{
// allocate the memory
CFIndex size = sizeof(struct __CFNotificationCenter) - sizeof(CFRuntimeBase);
struct __CFNotificationCenter *memory = (CFNotificationCenterRef)_CFRuntimeCreateInstance( kCFAllocatorDefault, __kCFNotificationCenterTypeID, size, NULL);
if (NULL == memory) return NULL;
memory->suspended = FALSE;
memory->type = type;
CF_SPINLOCK_INIT_FOR_STRUCTS(memory->lock);
// allocate storage and set counters
memory->observers = 0;
memory->capacity = CF_OBS_SIZE;
// IMPORTANT: after calloc, we assume memory is zeroed
memory->obs = (__CFObserver*)calloc(CF_OBS_SIZE, sizeof(__CFObserver));
if( memory->obs == NULL )
{
CFAllocatorDeallocate( kCFAllocatorDefault, memory );
memory = NULL;
}
return memory;
}
/*
* The local notification center is used for synchronous intra-task communication and
* can always be created.
*/
CFNotificationCenterRef CFNotificationCenterGetLocalCenter(void)
{
__CFSpinLock(&__CFLocalCenterLock);
if( __CFLocalCenter == NULL )
__CFLocalCenter = __CFCreateCenter(CF_LOCAL_CENTER);
__CFSpinUnlock(&__CFLocalCenterLock);
return __CFLocalCenter;
}
/*
* The distributed center relies on the presence of a daemon (our ddistnoted on
* Darwin) to deliver inter-task notifications. This implementation tries to contact
* it via CFMessagePort, and fails if a connection cannot be made.
*/
CFNotificationCenterRef CFNotificationCenterGetDistributedCenter(void)
{
//fprintf(stderr, "Entering CFNotificationCenterGetDistributedCenter()\n");
__CFSpinLock(&__CFDistributedCenterLock);
if( __CFDistributedCenter == NULL )
{
// generate a 'unique' port name for the current task
char uname[128];
//sprintf(uname, "ddistnoted-%s-%u", getprogname(), getpid());
//printf("unique string is '%s'\n", uname);
CFStringRef name = CFStringCreateWithCString( kCFAllocatorDefault, uname, kCFStringEncodingASCII );
// create the local port now, because the daemon will look for it
CFMessagePortContext context = { 0, NULL, NULL, NULL, NULL };
#if defined(__MACH__)
CFMessagePortRef local = CFMessagePortCreateLocal( kCFAllocatorDefault, name, __CFDistRecieve, &context, NULL );
if( local == NULL )
{
fprintf(stderr, "CFNC: Couldn't create a local message port.\n");
__CFSpinUnlock(&__CFDistributedCenterLock);
return NULL;
}
#else
CFMessagePortRef local = 0;
#endif
//CFRunLoopSourceRef rls =
//CFRunLoopAddSource( CFRunLoopGetMain(), rls, kCFRunLoopCommonModes );
#if defined(DEPLOYMENT_TARGET_MACOSX)
// create the remote port
CFMessagePortRef remote = CFMessagePortCreateRemote( kCFAllocatorDefault, CFSTR("org.puredarwin.ddistnoted") );
if( remote == NULL )
{
fprintf(stderr, "CFNC: Couldn't connect to message port.\n");
__CFSpinUnlock(&__CFDistributedCenterLock);
return NULL;
}
#else
CFMessagePortRef remote = 0;
#endif
// squeeze our unique name, as ASCII, into a data object...
CFIndex length = CFStringGetLength(name);
STACK_BUFFER_DECL(UInt8, data, (++length + sizeof(long)));
/* Security sessions come via one of the security components, although
I'm not sure which. Once we get everything working we may be able to
actually get this working properly. */
// if this isn't set -- and on other platforms -- we could maybe use userIds
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_LINUX
long session = getuid();
#else
long session = 0;
#endif
if( session == 0 ) session = 1;
//session = strtol( getenv("SECURITYSESSIONID"), NULL, 16 );
*(long *)data = session;
//printf("session id = %d\n", *(long *)data);
// getsid() returns the same as getpid()...
//printf("and getsid() reports %u\n", getsid(0));
CFStringGetCString(name, (char *)(data + sizeof(long)), length, kCFStringEncodingASCII);
CFDataRef dataOut = CFDataCreate( kCFAllocatorDefault, (const UInt8 *)data, (length + sizeof(long)) );
//printf("'unique' name: '%s'\n", data);
// ...send the register message...
CFDataRef dataIn = NULL;
#if DEPLOYMENT_TARGET_MACOSX
CFMessagePortSendRequest( remote, REGISTER_PORT, dataOut, 1.0, 1.0, kCFRunLoopDefaultMode, &dataIn);
#endif
CFRelease(name);
CFRelease(dataOut);
if( (dataIn == NULL) || (CFDataGetLength(dataIn) == 0) ) return NULL;
CFHashCode hash;
CFRange range = { 0, sizeof(CFHashCode) };
CFDataGetBytes(dataIn, range, (UInt8 *)&hash);
//if( h2 == -1 ) return NULL;
//printf("Worked. Our hash = %u, returned hash = %u\n", h1, h2);
// that seemed to work, so we'll try to allocate the centre object
__CFDistributedCenter = __CFCreateCenter(CF_DIST_CENTER);
if( __CFDistributedCenter == NULL ) return NULL;
__CFDistInfo.nots = (__CFDistNotification*)calloc(CF_DIST_SIZE, sizeof(__CFDistNotification));
if( __CFDistInfo.nots == NULL ) return NULL;
__CFDistInfo.queue = (__CFQueueRecord*)calloc(CF_QUEUE_SIZE, sizeof(__CFQueueRecord));
if( __CFDistInfo.queue == NULL ) return NULL;
__CFDistInfo.local = local;
__CFDistInfo.remote = remote;
__CFDistInfo.session = session;
__CFDistInfo.uid = hash;
#if defined(DEPLOYMENT_TARGET_MACOSX)
__CFDistInfo.rls = CFMessagePortCreateRunLoopSource( kCFAllocatorDefault, local, 0 );
// do we need an "added to runloop" flag?
#endif
//__PFDistInfo.count = 0;
__CFDistInfo.capacity = CF_DIST_SIZE;
//__PFDistInfo.count = 0;
__CFDistInfo.capacity = CF_QUEUE_SIZE;
}
__CFSpinUnlock(&__CFDistributedCenterLock);
//fprintf(stderr, "Leaving CFNotificationCenterGetDistributedCenter()\n");
return __CFDistributedCenter;
}
/*
* The Darwin notification center passes inter-task notifications via the notifyd
* daemon, with which it comunicates using the functions in <notify.h> for sending
* and a runloop-attached Mach port for recieving. I think this is Darwin-specific,
* altough other BSDs may have a similar set-up.
*
* We create the CFMachPort and its runloop source used for recieving messages here
* so that they're available when observers are added.
*/
CFNotificationCenterRef CFNotificationCenterGetDarwinNotifyCenter(void)
{
__CFSpinLock(&__CFDarwinCenterLock);
if( __CFDarwinCenter == NULL )
{
__CFDarwinCenter = __CFCreateCenter(CF_DARWIN_CENTER);
#if DEPLOYMENT_TARGET_MACOSX
CFMachPortContext context = { 0, (void *)__CFDarwinCenter, NULL, NULL, NULL };
__CFDarwinInfo.port = CFMachPortCreate( kCFAllocatorDefault, __CFDarwinCallBack, &context, NULL );
__CFDarwinInfo.rls = CFMachPortCreateRunLoopSource( kCFAllocatorDefault, __CFDarwinInfo.port, 0 );
#endif
__CFDarwinInfo.nots = (__CFDarwinNotifications*)calloc(CF_DARWIN_SIZE, sizeof(__CFDarwinNotifications));
__CFDarwinInfo.count = 0;
__CFDarwinInfo.capacity = CF_DARWIN_SIZE;
}
__CFSpinUnlock(&__CFDarwinCenterLock);
return __CFDarwinCenter;
}
/*
* Add an observer to the notification centre. In the local case at least, the CF
* behaviour is to allow multiple indentical observers.
*/
void CFNotificationCenterAddObserver(CFNotificationCenterRef center, const void *observer, CFNotificationCallback callBack, CFStringRef name, const void *object, CFNotificationSuspensionBehavior suspensionBehavior)
{
// common causes of failure
if( (center == NULL) || (CFGetTypeID(center) != __kCFNotificationCenterTypeID) || (callBack == NULL) || ((name == NULL) && (object == NULL)) )
return;
switch (center->type)
{
case CF_LOCAL_CENTER:
__CFAddObserver(center, observer, callBack, name, object, 0, NULL);
break;
case CF_DIST_CENTER:
// For distributed centres, object should always be a string. We hash the string and use
// this value for all comparisons until notifications are delivered
if( object != NULL )
{
if( CFGetTypeID((CFTypeRef)object) != CFStringGetTypeID() ) return;
object = (const void *)__CFNCHash((CFStringRef)object);
}
__CFAddObserver(center, observer, callBack, name, object, suspensionBehavior, __CFDistAddNotification);
break;
case CF_DARWIN_CENTER:
if( name == NULL ) return;
__CFAddObserver(center, observer, callBack, name, NULL, 0, __CFDarwinAddNotification);
break;
}
}
/*
*
*/
void CFNotificationCenterRemoveObserver(CFNotificationCenterRef center, const void *observer, CFStringRef name, const void *object)
{
if( (center == NULL) || (CFGetTypeID(center) != __kCFNotificationCenterTypeID) || (observer == NULL) || (center->observers == 0) )
return;
CFHashCode hash = (name == NULL) ? 0 : CFHash(name);
switch(center->type)
{
case CF_DIST_CENTER:
if( object != NULL )
{
if( CFGetTypeID(object) != CFStringGetTypeID() ) return; // wouldn't have been added
object = (void *)CFHash(object);
}
if( (name == NULL) && (object == NULL) )
__CFRemoveEveryObserver(center, observer, __CFDistRemoveNotification);
else
__CFRemoveObserver(center, observer, hash, object, __CFDistRemoveNotification);
break;
case CF_LOCAL_CENTER:
if( (name == NULL) && (object == NULL) )
__CFRemoveEveryObserver(center, observer, NULL);
else
__CFRemoveObserver(center, observer, hash, object, NULL);
break;
case CF_DARWIN_CENTER:
if( name == NULL )
__CFRemoveEveryObserver(center, observer, __CFDarwinRemoveNotification);
else
__CFRemoveObserver(center, observer, hash, NULL, __CFDarwinRemoveNotification);
break;
}
}
/*
*
*/
void CFNotificationCenterRemoveEveryObserver(CFNotificationCenterRef center, const void *observer)
{
if( (center == NULL) || (CFGetTypeID(center) != __kCFNotificationCenterTypeID) || (observer == NULL) || (center->observers == 0) )
return;
switch(center->type)
{
case CF_LOCAL_CENTER:
__CFRemoveEveryObserver(center, observer, NULL);
break;
case CF_DIST_CENTER:
__CFRemoveEveryObserver(center, observer, __CFDistRemoveNotification);
break;
case CF_DARWIN_CENTER:
__CFRemoveEveryObserver(center, observer, __CFDarwinRemoveNotification);
break;
}
}
/*
* Type-specific notification posting functions
*/
/*
* Post a notification to the ddistnoted daemon which runs the distributed
* notification centre.
*
* ddistnoted uses name and object hashes, along with session ids, to route
* distributions. The data object contains a 3-item array containing the name,
* object string and userInfo dictionary. We have to send the name and object
* because an observer may not have previously hashed and stored either string.
*/
void __CFPostDistributedNotification( CFNotificationCenterRef center, CFStringRef name, CFStringRef object, CFDictionaryRef userInfo, CFOptionFlags options )
{
CFPropertyListRef plist[3];
plist[0] = name;
// object must be a string when posting to a distributed centre
CFHashCode objectHash;
if(object == NULL)
{
objectHash = 0;
plist[1] = kCFBooleanFalse; // used to signal a null entry
}
else
{
if(CFGetTypeID(object) != CFStringGetTypeID()) return;
objectHash = __CFNCHash(object);
plist[1] = object;
}
plist[2] = (userInfo == NULL) ? (CFDictionaryRef)kCFBooleanFalse : userInfo;
CFArrayRef array = CFArrayCreate( kCFAllocatorDefault, (const void **)&plist, 3, NULL );
// we don't need to store the strings these hashes represent
CFHashCode nameHash = CFHash(name);
dndNotHeader info = { __CFDistInfo.session, nameHash, objectHash, options };
CFWriteStreamRef ws = CFWriteStreamCreateWithAllocatedBuffers( kCFAllocatorDefault, kCFAllocatorDefault );
CFWriteStreamOpen(ws);
CFWriteStreamWrite( ws, (const UInt8 *)&info, sizeof(dndNotHeader) );
// write the property list, but don't worry if an error occurs
CFPropertyListWriteToStream( array, ws, kCFPropertyListBinaryFormat_v1_0, NULL );
CFWriteStreamClose(ws);
CFDataRef data = (CFDataRef)CFWriteStreamCopyProperty( ws, kCFStreamPropertyDataWritten );
CFRelease(ws);
CFRelease(array);
if( data == NULL ) return;
#if DEPLOYMENT_TARGET_MACOSX
CFMessagePortSendRequest( __CFDistInfo.remote, NOTIFICATION, data, 1.0, 1.0, NULL, NULL );
#endif
}
/*
* All things considered, posting Darwin/notifyd notifications is fairly painless.
*/
void __CFPostDarwinNotification( CFNotificationCenterRef center, CFStringRef name )
{
CFIndex length = CFStringGetLength(name);
if( length == 0 ) return;
STACK_BUFFER_DECL(char, buffer, ++length);
CFStringGetCString(name, buffer, length, kCFStringEncodingASCII);
#if defined(__MACH__)
notify_post(buffer);
#endif
}
/*
* Post notification: Interprets the deliverImmediately flag and invokes the other
* post notification method.
*/
void CFNotificationCenterPostNotification(CFNotificationCenterRef center, CFStringRef name, const void *object, CFDictionaryRef userInfo, Boolean deliverImmediately)
{
CFOptionFlags options = deliverImmediately ? kCFNotificationDeliverImmediately : 0;
return CFNotificationCenterPostNotificationWithOptions(center, name, object, userInfo, options);
}
/*
* Checks for some common reasons why posting a notification could fail, then invokes
* the centre-specific posting message with the required sub-set of arguments.
*/
void CFNotificationCenterPostNotificationWithOptions(CFNotificationCenterRef center, CFStringRef name, const void *object, CFDictionaryRef userInfo, CFOptionFlags options)
{
//fprintf(stderr, "Entering CFNotificationCenterPostNotificationWithOptions()\n");
if( (center == NULL) || (CFGetTypeID(center) != __kCFNotificationCenterTypeID) || (name == NULL) )
return;
CFHashCode hash = (name == NULL) ? 0 : CFHash(name);
if( (center->type == CF_LOCAL_CENTER) && (center->observers != 0) )
return __CFInvokeCallBacks( center, hash, name, object, object, userInfo, TRUE );
else if( center->type == CF_DIST_CENTER )
{
//return
__CFPostDistributedNotification( center, name, (CFStringRef)object, userInfo, options );
//fprintf(stderr, "Leaving CFNotificationCenterPostNotificationWithOptions()\n");
return;
}
else if( center->type == CF_DARWIN_CENTER )
return __CFPostDarwinNotification( center, name );
}