]>
Commit | Line | Data |
---|---|---|
02dad85e DE |
1 | /* ------------------------------------------------------------------------- |
2 | * Project: GSocket (Generic Socket) for WX | |
0ba6a836 | 3 | * Name: src/mac/corefoundation/gsockosx.c |
02dad85e DE |
4 | * Purpose: GSocket: Mac OS X mach-o part |
5 | * CVSID: $Id$ | |
6 | * Mac code by Brian Victor, February 2002. Email comments to bhv1@psu.edu | |
7 | * ------------------------------------------------------------------------- */ | |
8 | ||
0ba6a836 | 9 | #include "wx/wxprec.h" |
02dad85e DE |
10 | |
11 | #if wxUSE_SOCKETS | |
12 | ||
02dad85e | 13 | #include "wx/gsocket.h" |
2804f77d | 14 | #include "wx/apptrait.h" |
02dad85e DE |
15 | |
16 | #include <CoreFoundation/CoreFoundation.h> | |
17 | ||
2804f77d VZ |
18 | // ---------------------------------------------------------------------------- |
19 | // Mac-specific data associated with each socket by GSocketCFManager | |
20 | // ---------------------------------------------------------------------------- | |
02dad85e | 21 | |
2804f77d | 22 | class MacGSocketData |
02dad85e | 23 | { |
2804f77d VZ |
24 | public: |
25 | // default ctor creates the object in uninitialized state, use Initialize() | |
26 | // later to make it usable | |
27 | MacGSocketData() | |
28 | { | |
29 | m_socket = NULL; | |
30 | m_source = NULL; | |
31 | } | |
32 | ||
33 | // initialize the data associated with the given socket | |
34 | bool Initialize(GSocket *socket) | |
35 | { | |
36 | wxASSERT_MSG( !IsInitialized(), "shouldn't be called twice" ); | |
37 | ||
38 | // we need a valid Unix socket to create a CFSocket | |
39 | if ( socket->m_fd < 0 ) | |
40 | return false; | |
41 | ||
42 | CFSocketContext cont; | |
43 | cont.version = 0; // this currently must be 0 | |
44 | cont.info = socket; // pointer passed to our callback | |
45 | cont.retain = NULL; // no need to retain/release/copy the | |
46 | cont.release = NULL; // socket pointer, so all callbacks | |
47 | cont.copyDescription = NULL; // can be left NULL | |
48 | ||
49 | m_socket = CFSocketCreateWithNative | |
50 | ( | |
51 | NULL, // default allocator | |
52 | socket->m_fd, | |
53 | kCFSocketReadCallBack | | |
54 | kCFSocketWriteCallBack | | |
55 | kCFSocketConnectCallBack, | |
56 | SocketCallback, | |
57 | &cont | |
58 | ); | |
59 | if ( !m_socket ) | |
60 | return false; | |
61 | ||
62 | m_source = CFSocketCreateRunLoopSource(NULL, m_socket, 0); | |
63 | ||
64 | return m_source != NULL; | |
65 | } | |
66 | ||
67 | // free the objects created by Initialize() | |
68 | ~MacGSocketData() | |
69 | { | |
70 | if ( m_source ) | |
71 | CFRelease(m_source); | |
72 | if ( m_socket ) | |
73 | CFRelease(m_socket); | |
74 | } | |
75 | ||
76 | // return true if Initialize() had already been called successfully | |
77 | bool IsInitialized() const { return m_source && m_socket; } | |
78 | ||
79 | ||
80 | // accessors: should only be called if IsInitialized() | |
81 | CFSocketRef GetSocket() const | |
82 | { | |
83 | wxASSERT( IsInitialized() ); | |
84 | ||
85 | return m_socket; | |
86 | } | |
87 | ||
88 | CFRunLoopSourceRef GetSource() const | |
89 | { | |
90 | wxASSERT( IsInitialized() ); | |
91 | ||
92 | return m_source; | |
93 | } | |
94 | ||
95 | private: | |
96 | static void SocketCallback(CFSocketRef WXUNUSED(s), | |
97 | CFSocketCallBackType callbackType, | |
98 | CFDataRef WXUNUSED(address), | |
99 | const void* data, | |
100 | void* info) | |
101 | { | |
102 | GSocket * const socket = wx_static_cast(GSocket *, info); | |
103 | MacGSocketData * const | |
104 | macdata = wx_static_cast(MacGSocketData *, socket->m_gui_dependent); | |
105 | if ( !macdata ) | |
106 | return; | |
107 | ||
108 | switch (callbackType) | |
109 | { | |
110 | case kCFSocketConnectCallBack: | |
111 | wxASSERT(!socket->m_server); | |
112 | // KH: If data is non-NULL, the connect failed, do not call Detected_Write, | |
113 | // which will only end up creating a spurious connect event because the | |
114 | // call to getsocketopt SO_ERROR inexplicably returns no error. | |
115 | // The change in behavior cannot be traced to any particular commit or | |
116 | // timeframe so I'm not sure what to think, but after so many hours, | |
117 | // this seems to address the issue and it's time to move on. | |
118 | if (data == NULL) | |
119 | socket->Detected_Write(); | |
120 | break; | |
121 | ||
122 | case kCFSocketReadCallBack: | |
123 | socket->Detected_Read(); | |
124 | break; | |
125 | ||
126 | case kCFSocketWriteCallBack: | |
127 | socket->Detected_Write(); | |
128 | break; | |
129 | ||
130 | default: | |
131 | wxFAIL_MSG( "unexpected socket callback" ); | |
132 | } | |
133 | } | |
134 | ||
135 | CFSocketRef m_socket; | |
136 | CFRunLoopSourceRef m_source; | |
137 | ||
138 | DECLARE_NO_COPY_CLASS(MacGSocketData); | |
02dad85e DE |
139 | }; |
140 | ||
2804f77d VZ |
141 | // ---------------------------------------------------------------------------- |
142 | // CoreFoundation implementation of GSocketManager | |
143 | // ---------------------------------------------------------------------------- | |
7725dc7c | 144 | |
2804f77d | 145 | class GSocketCFManager : public GSocketManager |
02dad85e | 146 | { |
2804f77d VZ |
147 | public: |
148 | virtual bool OnInit(); | |
149 | virtual void OnExit(); | |
02dad85e | 150 | |
2804f77d VZ |
151 | virtual bool Init_Socket(GSocket *socket); |
152 | virtual void Destroy_Socket(GSocket *socket); | |
153 | ||
154 | virtual void Install_Callback(GSocket *socket, GSocketEvent event); | |
155 | virtual void Uninstall_Callback(GSocket *socket, GSocketEvent event); | |
156 | ||
157 | virtual void Enable_Events(GSocket *socket); | |
158 | virtual void Disable_Events(GSocket *socket); | |
02dad85e | 159 | |
2804f77d VZ |
160 | private: |
161 | // retrieve our custom data associated with the given socket | |
162 | // | |
163 | // this is a low level function, use GetInitializedData() instead if the | |
164 | // data pointer should also be correctly initialized if it hadn't been done | |
165 | // yet | |
166 | // | |
167 | // may return NULL if we hadn't created the data for this socket yet | |
168 | MacGSocketData *GetData(GSocket *socket) const | |
169 | { | |
170 | return wx_static_cast(MacGSocketData *, socket->m_gui_dependent); | |
171 | } | |
172 | ||
173 | // return the custom data pointer initializing it if it hadn't been done | |
174 | // yet | |
175 | // | |
176 | // may return NULL if there is no associated data | |
177 | MacGSocketData *GetInitializedData(GSocket *socket) const | |
178 | { | |
179 | MacGSocketData * const data = GetData(socket); | |
180 | if ( data && !data->IsInitialized() ) | |
181 | { | |
182 | if ( !data->Initialize(socket) ) | |
183 | return NULL; | |
184 | } | |
185 | ||
186 | return data; | |
187 | } | |
0a647691 | 188 | |
2804f77d VZ |
189 | // return CFSocket callback mask corresponding to the given event (the |
190 | // socket parameter is needed because some events are interpreted | |
191 | // differently depending on whether they happen on a server or on a client | |
192 | // socket) | |
193 | static int GetCFCallback(GSocket *socket, GSocketEvent event); | |
194 | ||
195 | ||
196 | // Sockets must use the event loop on the main thread so we store a | |
197 | // reference to the main loop here in OnInit() | |
198 | static CFRunLoopRef ms_mainRunLoop; | |
199 | }; | |
200 | ||
201 | CFRunLoopRef GSocketCFManager::ms_mainRunLoop = NULL; | |
202 | ||
203 | bool GSocketCFManager::OnInit() | |
02dad85e | 204 | { |
7725dc7c | 205 | // No need to store the main loop again |
2804f77d | 206 | if (ms_mainRunLoop != NULL) |
7725dc7c KH |
207 | return true; |
208 | ||
209 | // Get the loop for the main thread so our events will actually fire. | |
210 | // The common socket.cpp code will assert if initialize is called from a | |
211 | // secondary thread, otherwise Mac would have the same problems as MSW | |
2804f77d VZ |
212 | ms_mainRunLoop = CFRunLoopGetCurrent(); |
213 | if ( !ms_mainRunLoop ) | |
214 | return false; | |
215 | ||
216 | CFRetain(ms_mainRunLoop); | |
7725dc7c | 217 | |
0a647691 | 218 | return true; |
02dad85e DE |
219 | } |
220 | ||
2804f77d | 221 | void GSocketCFManager::OnExit() |
02dad85e | 222 | { |
7725dc7c | 223 | // Release the reference count, and set the reference back to NULL |
2804f77d VZ |
224 | CFRelease(ms_mainRunLoop); |
225 | ms_mainRunLoop = NULL; | |
02dad85e DE |
226 | } |
227 | ||
2804f77d | 228 | bool GSocketCFManager::Init_Socket(GSocket *socket) |
02dad85e | 229 | { |
2804f77d VZ |
230 | socket->m_gui_dependent = new MacGSocketData; |
231 | return true; | |
02dad85e DE |
232 | } |
233 | ||
2804f77d | 234 | void GSocketCFManager::Destroy_Socket(GSocket *socket) |
02dad85e | 235 | { |
2804f77d VZ |
236 | MacGSocketData * const data = GetData(socket); |
237 | if ( data ) | |
02dad85e | 238 | { |
2804f77d VZ |
239 | delete data; |
240 | socket->m_gui_dependent = NULL; | |
02dad85e DE |
241 | } |
242 | } | |
243 | ||
2804f77d VZ |
244 | /* static */ |
245 | int GSocketCFManager::GetCFCallback(GSocket *socket, GSocketEvent event) | |
02dad85e | 246 | { |
2804f77d | 247 | switch ( event ) |
02dad85e | 248 | { |
2804f77d VZ |
249 | case GSOCK_CONNECTION: |
250 | return socket->m_server ? kCFSocketReadCallBack | |
251 | : kCFSocketConnectCallBack; | |
252 | ||
253 | case GSOCK_LOST: | |
254 | case GSOCK_INPUT: | |
255 | return kCFSocketReadCallBack; | |
256 | ||
257 | case GSOCK_OUTPUT: | |
258 | return kCFSocketWriteCallBack; | |
259 | ||
260 | case GSOCK_MAX_EVENT: | |
261 | wxFAIL_MSG( "invalid GSocketEvent" ); | |
262 | return 0; | |
263 | ||
264 | default: | |
265 | wxFAIL_MSG( "unknown GSocketEvent" ); | |
266 | return 0; | |
02dad85e | 267 | } |
02dad85e DE |
268 | } |
269 | ||
2804f77d | 270 | void GSocketCFManager::Install_Callback(GSocket *socket, GSocketEvent event) |
02dad85e | 271 | { |
2804f77d VZ |
272 | const MacGSocketData * const data = GetInitializedData(socket); |
273 | if ( !data ) | |
274 | return; | |
275 | ||
276 | CFSocketEnableCallBacks(data->GetSocket(), GetCFCallback(socket, event)); | |
02dad85e DE |
277 | } |
278 | ||
2804f77d | 279 | void GSocketCFManager::Uninstall_Callback(GSocket *socket, GSocketEvent event) |
02dad85e | 280 | { |
2804f77d VZ |
281 | const MacGSocketData * const data = GetInitializedData(socket); |
282 | if ( !data ) | |
283 | return; | |
02dad85e | 284 | |
2804f77d | 285 | CFSocketDisableCallBacks(data->GetSocket(), GetCFCallback(socket, event)); |
02dad85e DE |
286 | } |
287 | ||
2804f77d | 288 | void GSocketCFManager::Enable_Events(GSocket *socket) |
02dad85e | 289 | { |
2804f77d VZ |
290 | const MacGSocketData * const data = GetInitializedData(socket); |
291 | if ( !data ) | |
292 | return; | |
02dad85e | 293 | |
2804f77d VZ |
294 | CFRunLoopAddSource(ms_mainRunLoop, data->GetSource(), kCFRunLoopCommonModes); |
295 | } | |
296 | ||
297 | void GSocketCFManager::Disable_Events(GSocket *socket) | |
298 | { | |
299 | const MacGSocketData * const data = GetInitializedData(socket); | |
300 | if ( !data ) | |
301 | return; | |
302 | ||
303 | // CFSocketInvalidate does CFRunLoopRemoveSource anyway | |
304 | CFRunLoopRemoveSource(ms_mainRunLoop, data->GetSource(), kCFRunLoopCommonModes); | |
305 | CFSocketInvalidate(data->GetSocket()); | |
dd6c8be6 DE |
306 | |
307 | // CFSocketInvalidate has closed the socket so we want to make sure GSocket knows this | |
2804f77d | 308 | socket->m_fd = -1; |
02dad85e DE |
309 | } |
310 | ||
2804f77d VZ |
311 | GSocketManager *wxAppTraits::GetSocketManager() |
312 | { | |
313 | static GSocketCFManager s_manager; | |
314 | ||
315 | return &s_manager; | |
316 | }; | |
317 | ||
02dad85e | 318 | #endif // wxUSE_SOCKETS |