/** * Purple */ #import #import #import #import #import #import #import #import #import #import #import "Purple.h" #import "PurpDNSRequest.h" #define PURPLE_SOCKET_DEBUG 0 typedef enum { PURPLE_BUDDY_NONE = 0x00, /**< No events. */ PURPLE_BUDDY_SIGNON = 0x01, /**< The buddy signed on. */ PURPLE_BUDDY_SIGNOFF = 0x02, /**< The buddy signed off. */ PURPLE_BUDDY_INFO_UPDATED = 0x10, /**< The buddy's information (profile) changed. */ PURPLE_BUDDY_ICON = 0x40, /**< The buddy's icon changed. */ PURPLE_BUDDY_MISCELLANEOUS = 0x80, /**< The buddy's service-specific miscalleneous info changed. */ PURPLE_BUDDY_SIGNON_TIME = 0x11, /**< The buddy's signon time changed. */ PURPLE_BUDDY_EVIL = 0x12, /**< The buddy's warning level changed. */ PURPLE_BUDDY_DIRECTIM_CONNECTED = 0x14, /**< Connected to the buddy via DirectIM. */ PURPLE_BUDDY_DIRECTIM_DISCONNECTED = 0x18, /**< Disconnected from the buddy via DirectIM. */ PURPLE_BUDDY_NAME = 0x20 /** socket = 0; info->fd = 0; info->run_loop_source = 0; info->timer_tag = 0; info->timer_function = 0; info->timer = 0; info->timer_user_data = 0; info->write_tag = 0; info->write_ioFunction = 0; info->write_user_data = 0; info->read_tag = 0; info->read_ioFunction = 0; info->read_user_data = 0; return info; } #pragma mark Remove /*! * @brief Given a SourceInfo struct for a socket which was for reading *and* writing, recreate its socket to be for just one * * If the sourceInfo still has a read_tag, the resulting CFSocket will be just for reading. * If the sourceInfo still has a write_tag, the resulting CFSocket will be just for writing. * * This is necessary to prevent the now-unneeded condition from triggerring its callback. */ void updateSocketForSourceInfo(SourceInfo_t * sourceInfo) { assert(sourceInfo); CFSocketRef socket = sourceInfo->socket; if (!socket) { return; } if (!CFSocketIsValid(socket)) { LOG(@"Invalid socket."); return; } /** * Read */ if (sourceInfo->read_tag) { CFSocketEnableCallBacks(socket, kCFSocketReadCallBack); } else { CFSocketDisableCallBacks(socket, kCFSocketReadCallBack); } /** * Write */ if (sourceInfo->write_tag) { CFSocketEnableCallBacks(socket, kCFSocketWriteCallBack); } else { CFSocketDisableCallBacks(socket, kCFSocketWriteCallBack); } CFOptionFlags flags = 0; if (sourceInfo->read_tag) { flags |= kCFSocketAutomaticallyReenableReadCallBack; } if (sourceInfo->write_tag) { flags |= kCFSocketAutomaticallyReenableWriteCallBack; } CFSocketSetSocketFlags(socket, flags); } bool source_remove(unsigned int tag) { SourceInfo_t * sourceInfo = (SourceInfo_t *)[[g_sourceInfoDict objectForKey:[NSNumber numberWithUnsignedInt:tag]] pointerValue ]; if (sourceInfo) { #if (defined PURPLE_SOCKET_DEBUG && PURPLE_SOCKET_DEBUG) LOG( @"Removing for fd %i [sourceInfo %x]: tag is %i (t %i, r %i, w %i)", sourceInfo->fd, sourceInfo, tag, sourceInfo->timer_tag, sourceInfo->read_tag, sourceInfo->write_tag ); #endif if (sourceInfo->timer_tag == tag) { sourceInfo->timer_tag = 0; } else if (sourceInfo->read_tag == tag) { sourceInfo->read_tag = 0; } else if (sourceInfo->write_tag == tag) { sourceInfo->write_tag = 0; } [g_sourceInfoDict removeObjectForKey:[NSNumber numberWithUnsignedInt:tag]]; if ( sourceInfo->timer_tag == 0 && sourceInfo->read_tag == 0 && sourceInfo->write_tag == 0 ) { if (sourceInfo->timer) { CFRunLoopTimerInvalidate(sourceInfo->timer); CFRelease(sourceInfo->timer); } if (sourceInfo->socket) { #if (defined PURPLE_SOCKET_DEBUG && PURPLE_SOCKET_DEBUG) LOG( @"Source info %x done with a socket %x, so invalidating it", sourceInfo, sourceInfo->socket ); #endif CFSocketInvalidate(sourceInfo->socket); CFRelease(sourceInfo->socket); sourceInfo->socket = 0; } if (sourceInfo->run_loop_source) { CFRelease(sourceInfo->run_loop_source); } free(sourceInfo); } else { if ((sourceInfo->timer_tag == 0) && (sourceInfo->timer)) { CFRunLoopTimerInvalidate(sourceInfo->timer); CFRelease(sourceInfo->timer); sourceInfo->timer = 0; } if (sourceInfo->socket && (sourceInfo->read_tag || sourceInfo->write_tag)) { #if (defined PURPLE_SOCKET_DEBUG && PURPLE_SOCKET_DEBUG) LOG(@"source_remove(): Calling updateSocketForSourceInfo(%x)", sourceInfo); #endif updateSocketForSourceInfo(sourceInfo); } } return TRUE; } return FALSE; } bool timeout_remove(unsigned int tag) { return (source_remove(tag)); } #pragma mark Add void callTimerFunc(CFRunLoopTimerRef timer, void * info) { SourceInfo_t * sourceInfo = (SourceInfo_t *)info; if (!sourceInfo->timer_function || !sourceInfo->timer_function(sourceInfo->timer_user_data)) { source_remove(sourceInfo->timer_tag); } } unsigned int timeout_add(unsigned int interval, GSourceFunc function, gpointer data) { SourceInfo_t * info = newSourceInfo(); NSTimeInterval intervalInSec = (NSTimeInterval)interval / 1000; CFRunLoopTimerContext runLoopTimerContext = { 0, info, 0, 0, 0 }; CFRunLoopTimerRef runLoopTimer = CFRunLoopTimerCreate(kCFAllocatorDefault, (CFAbsoluteTimeGetCurrent() + intervalInSec), intervalInSec, 0, 0, callTimerFunc, &runLoopTimerContext ); CFRunLoopAddTimer(g_runLoop, runLoopTimer, kCFRunLoopCommonModes); info->timer_function = function; info->timer = runLoopTimer; info->timer_user_data = data; info->timer_tag = ++sourceId; [g_sourceInfoDict setObject:[NSValue valueWithPointer:info] forKey:[NSNumber numberWithUnsignedInt:info->timer_tag] ]; return info->timer_tag; } unsigned int input_add( int fd, PurpleInputCondition condition, PurpleInputFunction func, gpointer user_data ) { if (fd < 0) { LOG(@"input_add: fd was %i; returning tag %i", fd, sourceId + 1); return ++sourceId; } SourceInfo_t * info = newSourceInfo(); CFSocketContext context = { 0, info, 0, 0, 0 }; #if (defined PURPLE_SOCKET_DEBUG && PURPLE_SOCKET_DEBUG) LOG(@"input_add(): Adding input %i on fd %i", condition, fd); #endif CFSocketRef socket = CFSocketCreateWithNative( kCFAllocatorDefault, fd, (kCFSocketReadCallBack | kCFSocketWriteCallBack), socketCallback, &context ); CFSocketContext actualSocketContext = { 0, 0, 0, 0, 0 }; CFSocketGetContext(socket, &actualSocketContext); if (actualSocketContext.info != info) { free(info); CFRelease(socket); info = actualSocketContext.info; } info->fd = fd; info->socket = socket; if ((condition & PURPLE_INPUT_READ)) { info->read_tag = ++sourceId; info->read_ioFunction = func; info->read_user_data = user_data; [g_sourceInfoDict setObject:[NSValue valueWithPointer:info] forKey:[NSNumber numberWithUnsignedInt:info->read_tag] ]; } else { info->write_tag = ++sourceId; info->write_ioFunction = func; info->write_user_data = user_data; [g_sourceInfoDict setObject:[NSValue valueWithPointer:info] forKey:[NSNumber numberWithUnsignedInt:info->write_tag] ]; } updateSocketForSourceInfo(info); if (!(info->run_loop_source)) { info->run_loop_source = CFSocketCreateRunLoopSource( kCFAllocatorDefault, socket, 0 ); if (info->run_loop_source) { CFRunLoopAddSource( g_runLoop, info->run_loop_source, kCFRunLoopCommonModes ); } else { LOG(@"Unable to create run loop source for socket %p", socket); } } return sourceId; } #pragma mark Socket Callback static void socketCallback( CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void * data, void * infoVoid ) { SourceInfo_t * sourceInfo = (SourceInfo_t *)infoVoid; gpointer user_data; PurpleInputCondition c; PurpleInputFunction ioFunction = 0; int fd = sourceInfo->fd; if ((callbackType & kCFSocketReadCallBack)) { if (sourceInfo->read_tag) { user_data = sourceInfo->read_user_data; c = PURPLE_INPUT_READ; ioFunction = sourceInfo->read_ioFunction; } else { LOG( @"Read with no read_tag(read_tag %i write_tag %i) for %x" , sourceInfo->read_tag, sourceInfo->write_tag, sourceInfo->socket ); } } else { if (sourceInfo->write_tag) { user_data = sourceInfo->write_user_data; c = PURPLE_INPUT_WRITE; ioFunction = sourceInfo->write_ioFunction; } else { LOG( @"Wite without write_tag(read_tag %i write_tag %i) for %x", sourceInfo->read_tag, sourceInfo->write_tag, sourceInfo->socket ); } } if (ioFunction) { #if (defined PURPLE_SOCKET_DEBUG && PURPLE_SOCKET_DEBUG) LOG( @"socketCallback: Calling the ioFunction for %x, callback type %i " "(%s: tag is %i)", s, callbackType, ((callbackType & kCFSocketReadCallBack) ? "reading" : "writing"), ((callbackType & kCFSocketReadCallBack) ? sourceInfo->read_tag : sourceInfo->write_tag) ); #endif ioFunction(user_data, fd, c); } } int input_get_error(int fd, int *error) { int ret; socklen_t len; len = sizeof(*error); ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, error, &len); if (!ret && !(*error)) { fd_set thisfd; struct timeval timeout; FD_ZERO(&thisfd); FD_SET(fd, &thisfd); timeout.tv_sec = 0; timeout.tv_usec = 0; select(fd+1, 0, &thisfd, 0, &timeout); if (FD_ISSET(fd, &thisfd)) { ssize_t length = 0; char buffer[4] = {0, 0, 0, 0}; length = write(fd, buffer, length); if (length == -1) { ret = -1; *error = ENOTCONN; LOG(@"input_get_error(%i): Socket is NOT valid", fd); } } } return ret; } static PurpleEventLoopUiOps purpEventLoopUiOps = { timeout_add, timeout_remove, input_add, source_remove, input_get_error, /* timeout_add_seconds */ 0 }; static void signed_on(PurpleConnection * gc, gpointer null) { PurpleAccount * account = purple_connection_get_account(gc); LOG( @"Account connected: %s %s\n", account->username, account->protocol_id ); NSMutableDictionary * dict = [NSMutableDictionary dictionary]; [[NSNotificationCenter defaultCenter] postNotificationName:kPurpAccountDidSignOnNotification object:dict userInfo:nil ]; } static void signed_off(PurpleConnection * gc, gpointer null) { PurpleAccount * account = purple_connection_get_account(gc); LOG( @"Account disconnected: %s %s\n", account->username, account->protocol_id ); [[Purple sharedInstance] signedOff:account]; } static void disconnected(PurpleConnection * gc, const char * text) { LOG(@"disconnected"); } static void buddy_event_idle_time( PurpleBuddy * buddy, bool old_idle, bool idle, PurpleBuddyEvent event ) { PurplePresence * presence = purple_buddy_get_presence(buddy); time_t idleTime = purple_presence_get_idle_time(presence); LOG( @"BUDDY %s IDLE SINCE %@", buddy->name, [NSDate dateWithTimeIntervalSince1970:idleTime] ); } static void buddy_event_status( PurpleBuddy * buddy, PurpleStatus * old_status, PurpleStatus * new_status, PurpleBuddyEvent event ) { [g_lock lock]; PurplePresence * presence = 0; const char * buddy_alias = 0; const char * msg = 0; LOG(@"buddy_event_status:"); if (buddy) { NSMutableDictionary * aBuddy = [NSMutableDictionary dictionary]; /** * Set the buddy name. */ [aBuddy setObject:[NSString stringWithUTF8String:buddy->name] forKey:@"name" ]; [aBuddy setObject: [NSValue valueWithPointer:buddy->account] forKey:@"account" ]; NSUInteger statusType = purple_status_type_get_primitive( purple_status_get_type(old_status) ); /** * Set the status type. */ [aBuddy setObject:[NSNumber numberWithUnsignedInt:statusType] forKey:@"statusType" ]; buddy_alias = purple_buddy_get_alias(buddy); presence = purple_buddy_get_presence(buddy); if (new_status) { msg = purple_status_get_attr_string(new_status, "message"); statusType = purple_status_type_get_primitive( purple_status_get_type(new_status) ); /** * Is the buddy available? */ BOOL available = (statusType == PURPLE_STATUS_AVAILABLE); /** * Set the status type. */ [aBuddy setObject:[NSNumber numberWithUnsignedInt:statusType] forKey:@"statusType" ]; /* Notes struct _PurpleBuddy { PurpleBlistNode node; < The node that this buddy inherits from char *name; < The screenname of the buddy. char *alias; < The user-set alias of the buddy char *server_alias; < The server-specified alias of the buddy. (i.e. MSN "Friendly Names") void *proto_data; < This allows the prpl to associate whatever data it wants with a buddy PurpleBuddyIcon *icon; < The buddy icon. PurpleAccount *account; < the account this buddy belongs to PurplePresence *presence; }; */ if (buddy_alias) { LOG( @"Buddy (%s) status changed to %s, available = %d.", buddy_alias, purple_status_get_name(new_status), available ); } else if (msg && buddy_alias) { LOG( @"Buddy (%s) status changed: msg = %s, available = %d.", buddy_alias, msg, available ); /** * Set the status string. */ [aBuddy setObject:[NSString stringWithUTF8String:msg] forKey:@"statusString" ]; } else if (old_status && buddy_alias) { char * buddy_status = g_strdup_printf( "Buddy (%s) changed status from %s to %s, available = %d.", buddy_alias, purple_status_get_name(old_status), purple_status_get_name(new_status), available ); LOG(@"%s.", buddy_status); g_free(buddy_status); } /** * Update the buddy. */ [[Purple sharedInstance] updateBuddy:aBuddy withEvent:BUDDY_EVENT_STATUS ]; } } [g_lock unlock]; } static void buddy_event(PurpleBuddy * buddy, PurpleBuddyEvent event) { [g_lock lock]; LOG(@"buddy_event:"); if (buddy) { NSMutableDictionary * aBuddy = [NSMutableDictionary dictionary]; [aBuddy setObject:[NSString stringWithUTF8String:buddy->name] forKey:@"name" ]; switch (event) { case PURPLE_BUDDY_SIGNON: { LOG(@"Got buddy sign on = %s.", buddy->name); /** * Set the status type to "Unset". */ [aBuddy setObject:[NSNumber numberWithUnsignedInt:PURPLE_STATUS_AVAILABLE /* _UNSET */] forKey:@"statusType" ]; [aBuddy setObject: [NSValue valueWithPointer:buddy->account] forKey:@"account" ]; /** * Set the status type to "Offline". */ [aBuddy setObject:[NSNumber numberWithUnsignedInt:PURPLE_STATUS_OFFLINE] forKey:@"statusType" ]; /** * Update the buddy. */ [[Purple sharedInstance] updateBuddy:aBuddy withEvent:BUDDY_EVENT_SIGNON ]; } break; case PURPLE_BUDDY_SIGNOFF: { LOG(@"Got buddy sign off = %s.", buddy->name); /** * Set the status type to "Offline". */ [aBuddy setObject:[NSNumber numberWithUnsignedInt:PURPLE_STATUS_OFFLINE] forKey:@"statusType" ]; /** * Update the buddy. */ [[Purple sharedInstance] updateBuddy:aBuddy withEvent:BUDDY_EVENT_SIGNOFF ]; } break; case PURPLE_BUDDY_ICON: { LOG(@"Got buddy icon = %s.", buddy->name); } break; case PURPLE_BUDDY_NAME: { LOG(@"Got buddy name = %s.", buddy->name); } break; default: { LOG(@"Got buddy unkown event."); } break; } } [g_lock unlock]; } static void purp_conv_create( PurpleConversation * conv, const char * who, const char * message, PurpleMessageFlags flags, time_t mtime ) { LOG(@"Conversation Created."); } static void purp_conv_destroy(PurpleConversation * conv) { LOG(@"purp_conv_destroy"); } static void purp_conv_update( PurpleConversation * conv, PurpleConvUpdateType type ) { LOG(@"purp_conv_update"); } static void purp_notify_message( PurpleNotifyMsgType type, const char * title, const char * primary, const char * secondary ) { LOG( @"purp_notify_message: Title: %s Primary: %s Secondary: %s", title, primary, secondary ); } static void purp_receive_im( PurpleConversation * conv, const char * who, const char * alias, const char * message, PurpleMessageFlags flags, time_t mtime ) { if ((flags & PURPLE_MESSAGE_SEND) == 0) { LOG(@"purp_receive_im: who: %s", who); LOG(@"purp_receive_im: message: %s", message); LOG( @"purp_receive_im: TIMESTAMP: %s", purple_utf8_strftime("(%H:%M:%S)", localtime(&mtime)) ); @try { [[Purple sharedInstance] receiveConversationMessage:[NSString stringWithUTF8String:who] alias:[NSString stringWithUTF8String:alias] message:[NSString stringWithUTF8String:message] time:[NSDate dateWithTimeIntervalSince1970:mtime] ]; } @catch (NSException * exception) { LOG( @"purp_receive_im: (%@) %@", [exception name], [exception reason] ); } } } static bool purp_purple_has_focus(PurpleConversation * conv) { LOG(@"purp_purple_has_focus: returning NO"); return NO; } static void purp_conv_present(PurpleConversation * conv) { LOG(@"purp_conv_present:"); } static void purp_confirm(PurpleConversation * conv, const char * message) { LOG(@"purp_confirm: msg: %s", message); } static PurpleConversationUiOps purp_conv_uiops = { purp_conv_create, /* create_conversation */ purp_conv_destroy, /* destroy_conversation */ 0, /* write_chat */ 0, /* write_im */ purp_receive_im, /* write_conv */ 0, /* chat_add_users */ 0, /* chat_rename_user */ 0, /* chat_remove_users */ 0, /* chat_update_user */ purp_conv_present, /* present */ purp_purple_has_focus, /* has_focus */ 0, /* custom_smiley_add */ 0, /* custom_smiley_write */ 0, /* custom_smiley_close */ purp_confirm /*sendconfirm*/ }; static void purpleConnConnectProgress( PurpleConnection * gc, const char * text, size_t step, size_t step_count ) { LOG( @"purpleConnConnectProgress: gc=0x%x (%s) %i / %i", gc, text, step, step_count ); } static void purpleConnConnected(PurpleConnection * gc) { LOG(@"purpleConnConnected: PurpleConnection = %x", gc); } static void purpleConnDisconnected(PurpleConnection * gc) { LOG(@"purpleConnDisconnected: PurpleConnection = %x", gc); } static void purpleConnNotice(PurpleConnection * gc, const char * text) { LOG(@"purpleConnNotice: PurpleConnection = %x", gc); LOG(@"purpleConnNotice: PurpleConnection = %x (%s)", gc, text); } static void purpleConnReportDisconnect(PurpleConnection * gc, const char * text) { LOG( @"purpleConnReportDisconnect: PurpleConnection = %x (%s)", gc, text ); } static void purpleConnReportRealDisconnect() { LOG(@"purpleConnReportRealDisconnect"); } static void purpleConnReportRealRealDisconnect() { LOG(@"purpleConnReportRealRealDisconnect"); } static PurpleConnectionUiOps purp_conn_uiops = { purpleConnConnectProgress, purpleConnConnected, purpleConnDisconnected, purpleConnNotice, purpleConnReportDisconnect, purpleConnReportRealDisconnect, purpleConnReportRealRealDisconnect }; gboolean purpPurpleDnsRequestResolve( PurpleDnsQueryData * query_data, PurpleDnsQueryResolvedCallback resolved_cb, PurpleDnsQueryFailedCallback failed_cb ) { [[[PurpDNSRequest alloc] initWithData:query_data resolvedCB:resolved_cb failedCB:failed_cb] autorelease ]; return TRUE; } void purpPurpleDnsRequestDestroy(PurpleDnsQueryData *query_data) { [[PurpDNSRequest lookupRequestForData:query_data] cancel]; } static PurpleDnsQueryUiOps purpPurpleDnsRequestOps = { purpPurpleDnsRequestResolve, purpPurpleDnsRequestDestroy }; static void ui_init() { LOG(@"ui_init:"); purple_connections_set_ui_ops(&purp_conn_uiops); purple_conversations_set_ui_ops(&purp_conv_uiops); } static PurpleCoreUiOps core_uiops = { 0, 0, ui_init, 0, /* pad */ 0, 0, 0, 0 }; void connect_signals() { void * connections = purple_connections_get_handle(); void * blist_handle = purple_blist_get_handle(); static int handle; purple_signal_connect( connections, "signed-on", &handle, PURPLE_CALLBACK(signed_on), 0 ); purple_signal_connect( connections, "signed-off", &handle, PURPLE_CALLBACK(signed_off), 0 ); purple_signal_connect( blist_handle, "buddy-signed-on", &handle, PURPLE_CALLBACK(buddy_event), GINT_TO_POINTER(PURPLE_BUDDY_SIGNON) ); purple_signal_connect( blist_handle, "buddy-signed-off", &handle, PURPLE_CALLBACK(buddy_event), GINT_TO_POINTER(PURPLE_BUDDY_SIGNOFF) ); purple_signal_connect( blist_handle, "buddy-icon-changed", &handle, PURPLE_CALLBACK(buddy_event), GINT_TO_POINTER(PURPLE_BUDDY_ICON) ); purple_signal_connect( blist_handle, "buddy-got-login-time", &handle, PURPLE_CALLBACK(buddy_event), GINT_TO_POINTER(PURPLE_BUDDY_SIGNON_TIME) ); purple_signal_connect( blist_handle, "buddy-status-changed", &handle, PURPLE_CALLBACK(buddy_event_status), 0 ); purple_signal_connect( blist_handle, "buddy-idle-changed", &handle, PURPLE_CALLBACK(buddy_event_idle_time), 0 ); purple_signal_connect( purple_conversations_get_handle(), "conversation-updated", &handle, PURPLE_CALLBACK(purp_conv_update), 0 ); } extern gboolean purple_init_ssl_plugin(); extern gboolean purple_init_ssl_openssl_plugin(); extern gboolean purple_init_ssl_cdsa_plugin(); void init_libpurple() { /** * :TODO: */ if (!g_sourceInfoDict) { g_sourceInfoDict = [[NSMutableDictionary alloc] init]; } /** * Prior to any use of the type system, g_type_init() has to be called to * initialize the type system and assorted other code portions. */ g_type_init(); /** * Determine the application support directory. */ NSArray * paths = NSSearchPathForDirectoriesInDomains( NSApplicationSupportDirectory, NSUserDomainMask, YES ); NSString * appSupportPath = ([paths count] > 0) ? [paths objectAtIndex:0] : NSTemporaryDirectory() ; appSupportPath = [appSupportPath stringByAppendingPathComponent:@"Proteus"]; /** * Set the user directory. */ purple_util_set_user_dir([appSupportPath fileSystemRepresentation]); /** * Set debug enabled. */ purple_debug_set_enabled(1); purple_core_set_ui_ops(&core_uiops); purple_eventloop_set_ui_ops(&purpEventLoopUiOps); purple_dnsquery_set_ui_ops(&purpPurpleDnsRequestOps); /** * Assign runloop. */ g_runLoop = [[NSRunLoop currentRunLoop] getCFRunLoop]; /** * Retain runloop. */ CFRetain(g_runLoop); /** * Add plugins search path. */ purple_plugins_add_search_path( [[[NSBundle mainBundle] builtInPlugInsPath] fileSystemRepresentation] ); /** * Initialize purple core. */ if (!purple_core_init("Proteus")) { LOG(@"Purple initialization failed.\n"); exit(-1); } /** * Allocate and set buddy list. */ purple_set_blist(purple_blist_new()); /** * Load buddy list. */ purple_blist_load(); /** * Load preferences. */ purple_prefs_load(); /** * Load saved plugins. */ purple_plugins_load_saved( [[[NSBundle mainBundle] builtInPlugInsPath] fileSystemRepresentation] ); /** * Load pounces. */ purple_pounces_load(); purple_ssl_init(); purple_init_ssl_cdsa_plugin(); } void dealloc_libpurple() { /** * Quit the core. */ purple_core_quit(); /** * Free the run loop. */ CFRelease(g_runLoop); }