/* -------------------------------------------------------------------------
* Project: GSocket (Generic Socket) for WX
- * Name: gsockosx.c
+ * Name: src/mac/corefoundation/gsockosx.c
* Purpose: GSocket: Mac OS X mach-o part
* CVSID: $Id$
* Mac code by Brian Victor, February 2002. Email comments to bhv1@psu.edu
* ------------------------------------------------------------------------- */
-#include "wx/setup.h"
+#include "wx/wxprec.h"
#if wxUSE_SOCKETS
-#include <stdlib.h>
#include "wx/gsocket.h"
-#include "wx/unix/gsockunx.h"
+#include "wx/apptrait.h"
#include <CoreFoundation/CoreFoundation.h>
-#define ALL_CALLBACK_TYPES (kCFSocketReadCallBack | kCFSocketWriteCallBack | kCFSocketConnectCallBack)
+// ----------------------------------------------------------------------------
+// Mac-specific data associated with each socket by GSocketCFManager
+// ----------------------------------------------------------------------------
-struct MacGSocketData
+class MacGSocketData
{
- CFSocketRef socket;
- CFRunLoopSourceRef source;
+public:
+ // default ctor creates the object in uninitialized state, use Initialize()
+ // later to make it usable
+ MacGSocketData()
+ {
+ m_socket = NULL;
+ m_source = NULL;
+ }
+
+ // initialize the data associated with the given socket
+ bool Initialize(GSocket *socket)
+ {
+ wxASSERT_MSG( !IsInitialized(), "shouldn't be called twice" );
+
+ // we need a valid Unix socket to create a CFSocket
+ if ( socket->m_fd < 0 )
+ return false;
+
+ CFSocketContext cont;
+ cont.version = 0; // this currently must be 0
+ cont.info = socket; // pointer passed to our callback
+ cont.retain = NULL; // no need to retain/release/copy the
+ cont.release = NULL; // socket pointer, so all callbacks
+ cont.copyDescription = NULL; // can be left NULL
+
+ m_socket = CFSocketCreateWithNative
+ (
+ NULL, // default allocator
+ socket->m_fd,
+ kCFSocketReadCallBack |
+ kCFSocketWriteCallBack |
+ kCFSocketConnectCallBack,
+ SocketCallback,
+ &cont
+ );
+ if ( !m_socket )
+ return false;
+
+ m_source = CFSocketCreateRunLoopSource(NULL, m_socket, 0);
+
+ return m_source != NULL;
+ }
+
+ // free the objects created by Initialize()
+ ~MacGSocketData()
+ {
+ if ( m_source )
+ CFRelease(m_source);
+ if ( m_socket )
+ CFRelease(m_socket);
+ }
+
+ // return true if Initialize() had already been called successfully
+ bool IsInitialized() const { return m_source && m_socket; }
+
+
+ // accessors: should only be called if IsInitialized()
+ CFSocketRef GetSocket() const
+ {
+ wxASSERT( IsInitialized() );
+
+ return m_socket;
+ }
+
+ CFRunLoopSourceRef GetSource() const
+ {
+ wxASSERT( IsInitialized() );
+
+ return m_source;
+ }
+
+private:
+ static void SocketCallback(CFSocketRef WXUNUSED(s),
+ CFSocketCallBackType callbackType,
+ CFDataRef WXUNUSED(address),
+ const void* data,
+ void* info)
+ {
+ GSocket * const socket = wx_static_cast(GSocket *, info);
+ MacGSocketData * const
+ macdata = wx_static_cast(MacGSocketData *, socket->m_gui_dependent);
+ if ( !macdata )
+ return;
+
+ switch (callbackType)
+ {
+ case kCFSocketConnectCallBack:
+ wxASSERT(!socket->m_server);
+ // KH: If data is non-NULL, the connect failed, do not call Detected_Write,
+ // which will only end up creating a spurious connect event because the
+ // call to getsocketopt SO_ERROR inexplicably returns no error.
+ // The change in behavior cannot be traced to any particular commit or
+ // timeframe so I'm not sure what to think, but after so many hours,
+ // this seems to address the issue and it's time to move on.
+ if (data == NULL)
+ socket->Detected_Write();
+ break;
+
+ case kCFSocketReadCallBack:
+ socket->Detected_Read();
+ break;
+
+ case kCFSocketWriteCallBack:
+ socket->Detected_Write();
+ break;
+
+ default:
+ wxFAIL_MSG( "unexpected socket callback" );
+ }
+ }
+
+ CFSocketRef m_socket;
+ CFRunLoopSourceRef m_source;
+
+ DECLARE_NO_COPY_CLASS(MacGSocketData);
};
-// Sockets must use the event loop on the main thread
-// We will store the main loop's reference when Initialize is called
-static CFRunLoopRef s_mainRunLoop = NULL;
+// ----------------------------------------------------------------------------
+// CoreFoundation implementation of GSocketManager
+// ----------------------------------------------------------------------------
-void Mac_Socket_Callback(CFSocketRef s, CFSocketCallBackType callbackType,
- CFDataRef address, const void* data, void* info)
+class GSocketCFManager : public GSocketManager
{
- GSocket* socket = (GSocket*)info;
- struct MacGSocketData* macdata;
- macdata = (struct MacGSocketData*)socket->m_gui_dependent;
- if (!macdata) return;
- switch (callbackType)
- {
- case kCFSocketConnectCallBack:
- assert(!socket->m_server);
- // KH: If data is non-NULL, the connect failed, do not call Detected_Write,
- // which will only end up creating a spurious connect event because the
- // call to getsocketopt SO_ERROR inexplicably returns no error.
- // The change in behavior cannot be traced to any particular commit or
- // timeframe so I'm not sure what to think, but after so many hours,
- // this seems to address the issue and it's time to move on.
- if (data == NULL)
- socket->Detected_Write();
- break;
- case kCFSocketReadCallBack:
- socket->Detected_Read();
- break;
- case kCFSocketWriteCallBack:
- socket->Detected_Write();
- break;
- default:
- break; /* We shouldn't get here. */
- }
-}
+public:
+ virtual bool OnInit();
+ virtual void OnExit();
-struct MacGSocketData* _GSocket_Get_Mac_Socket(GSocket *socket)
-{
- /* If socket is already created, returns a pointer to the data */
- /* Otherwise, creates socket and returns the pointer */
- CFSocketContext cont;
- struct MacGSocketData* data = (struct MacGSocketData*)socket->m_gui_dependent;
-
- if (data && data->source) return data;
-
- /* CFSocket has not been created, create it: */
- if (socket->m_fd < 0 || !data) return NULL;
- cont.version = 0; cont.retain = NULL;
- cont.release = NULL; cont.copyDescription = NULL;
- cont.info = socket;
-
- CFSocketRef cf = CFSocketCreateWithNative(NULL, socket->m_fd,
- ALL_CALLBACK_TYPES, Mac_Socket_Callback, &cont);
- CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(NULL, cf, 0);
- assert(source);
- socket->m_gui_dependent = (char*)data;
-
- /* Keep the source and the socket around. */
- data->source = source;
- data->socket = cf;
-
- return data;
-}
+ virtual bool Init_Socket(GSocket *socket);
+ virtual void Destroy_Socket(GSocket *socket);
+
+ virtual void Install_Callback(GSocket *socket, GSocketEvent event);
+ virtual void Uninstall_Callback(GSocket *socket, GSocketEvent event);
+
+ virtual void Enable_Events(GSocket *socket);
+ virtual void Disable_Events(GSocket *socket);
-bool GSocketGUIFunctionsTableConcrete::CanUseEventLoop()
-{ return true; }
+private:
+ // retrieve our custom data associated with the given socket
+ //
+ // this is a low level function, use GetInitializedData() instead if the
+ // data pointer should also be correctly initialized if it hadn't been done
+ // yet
+ //
+ // may return NULL if we hadn't created the data for this socket yet
+ MacGSocketData *GetData(GSocket *socket) const
+ {
+ return wx_static_cast(MacGSocketData *, socket->m_gui_dependent);
+ }
+
+ // return the custom data pointer initializing it if it hadn't been done
+ // yet
+ //
+ // may return NULL if there is no associated data
+ MacGSocketData *GetInitializedData(GSocket *socket) const
+ {
+ MacGSocketData * const data = GetData(socket);
+ if ( data && !data->IsInitialized() )
+ {
+ if ( !data->Initialize(socket) )
+ return NULL;
+ }
+
+ return data;
+ }
-bool GSocketGUIFunctionsTableConcrete::OnInit(void)
+ // return CFSocket callback mask corresponding to the given event (the
+ // socket parameter is needed because some events are interpreted
+ // differently depending on whether they happen on a server or on a client
+ // socket)
+ static int GetCFCallback(GSocket *socket, GSocketEvent event);
+
+
+ // Sockets must use the event loop on the main thread so we store a
+ // reference to the main loop here in OnInit()
+ static CFRunLoopRef ms_mainRunLoop;
+};
+
+CFRunLoopRef GSocketCFManager::ms_mainRunLoop = NULL;
+
+bool GSocketCFManager::OnInit()
{
// No need to store the main loop again
- if (s_mainRunLoop != NULL)
+ if (ms_mainRunLoop != NULL)
return true;
// Get the loop for the main thread so our events will actually fire.
// The common socket.cpp code will assert if initialize is called from a
// secondary thread, otherwise Mac would have the same problems as MSW
- s_mainRunLoop = CFRunLoopGetCurrent();
- CFRetain(s_mainRunLoop);
+ ms_mainRunLoop = CFRunLoopGetCurrent();
+ if ( !ms_mainRunLoop )
+ return false;
+
+ CFRetain(ms_mainRunLoop);
return true;
}
-void GSocketGUIFunctionsTableConcrete::OnExit(void)
+void GSocketCFManager::OnExit()
{
// Release the reference count, and set the reference back to NULL
- CFRelease(s_mainRunLoop);
- s_mainRunLoop = NULL;
+ CFRelease(ms_mainRunLoop);
+ ms_mainRunLoop = NULL;
}
-bool GSocketGUIFunctionsTableConcrete::Init_Socket(GSocket *socket)
+bool GSocketCFManager::Init_Socket(GSocket *socket)
{
- struct MacGSocketData *data = (struct MacGSocketData *)malloc(sizeof(struct MacGSocketData));
- if (data)
- {
- socket->m_gui_dependent = (char*)data;
- data->socket = NULL;
- data->source = NULL;
- return 1;
- }
- return 0;
+ socket->m_gui_dependent = new MacGSocketData;
+ return true;
}
-void GSocketGUIFunctionsTableConcrete::Destroy_Socket(GSocket *socket)
+void GSocketCFManager::Destroy_Socket(GSocket *socket)
{
- struct MacGSocketData *data = (struct MacGSocketData*)(socket->m_gui_dependent);
- if (data)
+ MacGSocketData * const data = GetData(socket);
+ if ( data )
{
- if ( data->source )
- CFRelease(data->source);
- if ( data->socket )
- CFRelease(data->socket);
- free(data);
+ delete data;
+ socket->m_gui_dependent = NULL;
}
}
-void GSocketGUIFunctionsTableConcrete::Install_Callback(GSocket *socket, GSocketEvent event)
+/* static */
+int GSocketCFManager::GetCFCallback(GSocket *socket, GSocketEvent event)
{
- int c;
- struct MacGSocketData* data = _GSocket_Get_Mac_Socket(socket);
- if (!data) return;
- switch (event)
+ switch ( event )
{
- case GSOCK_CONNECTION:
- if(socket->m_server)
- c = kCFSocketReadCallBack;
- else
- c = kCFSocketConnectCallBack;
- break;
- case GSOCK_LOST:
- case GSOCK_INPUT:
- c = kCFSocketReadCallBack;
- break;
- case GSOCK_OUTPUT:
- c = kCFSocketWriteCallBack;
- break;
- default:
- c = 0;
+ case GSOCK_CONNECTION:
+ return socket->m_server ? kCFSocketReadCallBack
+ : kCFSocketConnectCallBack;
+
+ case GSOCK_LOST:
+ case GSOCK_INPUT:
+ return kCFSocketReadCallBack;
+
+ case GSOCK_OUTPUT:
+ return kCFSocketWriteCallBack;
+
+ case GSOCK_MAX_EVENT:
+ wxFAIL_MSG( "invalid GSocketEvent" );
+ return 0;
+
+ default:
+ wxFAIL_MSG( "unknown GSocketEvent" );
+ return 0;
}
- CFSocketEnableCallBacks(data->socket, c);
}
-void GSocketGUIFunctionsTableConcrete::Uninstall_Callback(GSocket *socket, GSocketEvent event)
+void GSocketCFManager::Install_Callback(GSocket *socket, GSocketEvent event)
{
- int c;
- struct MacGSocketData* data = _GSocket_Get_Mac_Socket(socket);
- if (!data) return;
- switch (event)
- {
- case GSOCK_CONNECTION:
- if(socket->m_server)
- c = kCFSocketReadCallBack;
- else
- c = kCFSocketConnectCallBack;
- break;
- case GSOCK_LOST:
- case GSOCK_INPUT:
- c = kCFSocketReadCallBack;
- break;
- case GSOCK_OUTPUT:
- c = kCFSocketWriteCallBack;
- break;
- default:
- c = 0;
- }
- CFSocketDisableCallBacks(data->socket, c);
+ const MacGSocketData * const data = GetInitializedData(socket);
+ if ( !data )
+ return;
+
+ CFSocketEnableCallBacks(data->GetSocket(), GetCFCallback(socket, event));
}
-void GSocketGUIFunctionsTableConcrete::Enable_Events(GSocket *socket)
+void GSocketCFManager::Uninstall_Callback(GSocket *socket, GSocketEvent event)
{
- struct MacGSocketData* data = _GSocket_Get_Mac_Socket(socket);
- if (!data) return;
+ const MacGSocketData * const data = GetInitializedData(socket);
+ if ( !data )
+ return;
- CFRunLoopAddSource(s_mainRunLoop, data->source, kCFRunLoopCommonModes);
+ CFSocketDisableCallBacks(data->GetSocket(), GetCFCallback(socket, event));
}
-void GSocketGUIFunctionsTableConcrete::Disable_Events(GSocket *socket)
+void GSocketCFManager::Enable_Events(GSocket *socket)
{
- struct MacGSocketData* data = _GSocket_Get_Mac_Socket(socket);
- if (!data) return;
+ const MacGSocketData * const data = GetInitializedData(socket);
+ if ( !data )
+ return;
- /* CFSocketInvalidate does CFRunLoopRemoveSource anyway */
- CFRunLoopRemoveSource(s_mainRunLoop, data->source, kCFRunLoopCommonModes);
- CFSocketInvalidate(data->socket);
+ CFRunLoopAddSource(ms_mainRunLoop, data->GetSource(), kCFRunLoopCommonModes);
}
+void GSocketCFManager::Disable_Events(GSocket *socket)
+{
+ const MacGSocketData * const data = GetInitializedData(socket);
+ if ( !data )
+ return;
+
+ // CFSocketInvalidate does CFRunLoopRemoveSource anyway
+ CFRunLoopRemoveSource(ms_mainRunLoop, data->GetSource(), kCFRunLoopCommonModes);
+ CFSocketInvalidate(data->GetSocket());
+
+ // CFSocketInvalidate has closed the socket so we want to make sure GSocket knows this
+ socket->m_fd = -1;
+}
+
+GSocketManager *wxAppTraits::GetSocketManager()
+{
+ static GSocketCFManager s_manager;
+
+ return &s_manager;
+};
+
#endif // wxUSE_SOCKETS