]>
Commit | Line | Data |
---|---|---|
1 | /* ------------------------------------------------------------------------- | |
2 | * Project: GSocket (Generic Socket) for WX | |
3 | * Name: src/mac/corefoundation/gsockosx.c | |
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 | ||
9 | #include "wx/wxprec.h" | |
10 | ||
11 | #if wxUSE_SOCKETS | |
12 | ||
13 | #include "wx/gsocket.h" | |
14 | #include "wx/apptrait.h" | |
15 | ||
16 | #include <CoreFoundation/CoreFoundation.h> | |
17 | ||
18 | // ---------------------------------------------------------------------------- | |
19 | // Mac-specific data associated with each socket by GSocketCFManager | |
20 | // ---------------------------------------------------------------------------- | |
21 | ||
22 | class MacGSocketData | |
23 | { | |
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); | |
139 | }; | |
140 | ||
141 | // ---------------------------------------------------------------------------- | |
142 | // CoreFoundation implementation of GSocketManager | |
143 | // ---------------------------------------------------------------------------- | |
144 | ||
145 | class GSocketCFManager : public GSocketManager | |
146 | { | |
147 | public: | |
148 | virtual bool OnInit(); | |
149 | virtual void OnExit(); | |
150 | ||
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); | |
159 | ||
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 | } | |
188 | ||
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() | |
204 | { | |
205 | // No need to store the main loop again | |
206 | if (ms_mainRunLoop != NULL) | |
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 | |
212 | ms_mainRunLoop = CFRunLoopGetCurrent(); | |
213 | if ( !ms_mainRunLoop ) | |
214 | return false; | |
215 | ||
216 | CFRetain(ms_mainRunLoop); | |
217 | ||
218 | return true; | |
219 | } | |
220 | ||
221 | void GSocketCFManager::OnExit() | |
222 | { | |
223 | // Release the reference count, and set the reference back to NULL | |
224 | CFRelease(ms_mainRunLoop); | |
225 | ms_mainRunLoop = NULL; | |
226 | } | |
227 | ||
228 | bool GSocketCFManager::Init_Socket(GSocket *socket) | |
229 | { | |
230 | socket->m_gui_dependent = new MacGSocketData; | |
231 | return true; | |
232 | } | |
233 | ||
234 | void GSocketCFManager::Destroy_Socket(GSocket *socket) | |
235 | { | |
236 | MacGSocketData * const data = GetData(socket); | |
237 | if ( data ) | |
238 | { | |
239 | delete data; | |
240 | socket->m_gui_dependent = NULL; | |
241 | } | |
242 | } | |
243 | ||
244 | /* static */ | |
245 | int GSocketCFManager::GetCFCallback(GSocket *socket, GSocketEvent event) | |
246 | { | |
247 | switch ( event ) | |
248 | { | |
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; | |
267 | } | |
268 | } | |
269 | ||
270 | void GSocketCFManager::Install_Callback(GSocket *socket, GSocketEvent event) | |
271 | { | |
272 | const MacGSocketData * const data = GetInitializedData(socket); | |
273 | if ( !data ) | |
274 | return; | |
275 | ||
276 | CFSocketEnableCallBacks(data->GetSocket(), GetCFCallback(socket, event)); | |
277 | } | |
278 | ||
279 | void GSocketCFManager::Uninstall_Callback(GSocket *socket, GSocketEvent event) | |
280 | { | |
281 | const MacGSocketData * const data = GetInitializedData(socket); | |
282 | if ( !data ) | |
283 | return; | |
284 | ||
285 | CFSocketDisableCallBacks(data->GetSocket(), GetCFCallback(socket, event)); | |
286 | } | |
287 | ||
288 | void GSocketCFManager::Enable_Events(GSocket *socket) | |
289 | { | |
290 | const MacGSocketData * const data = GetInitializedData(socket); | |
291 | if ( !data ) | |
292 | return; | |
293 | ||
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()); | |
306 | ||
307 | // CFSocketInvalidate has closed the socket so we want to make sure GSocket knows this | |
308 | socket->m_fd = -1; | |
309 | } | |
310 | ||
311 | GSocketManager *wxAppTraits::GetSocketManager() | |
312 | { | |
313 | static GSocketCFManager s_manager; | |
314 | ||
315 | return &s_manager; | |
316 | }; | |
317 | ||
318 | #endif // wxUSE_SOCKETS |