1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtCore module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39
40 #include "qcfsocketnotifier_p.h"
41 #include <QtCore/qcoreapplication.h>
42 #include <QtCore/qsocketnotifier.h>
43 #include <QtCore/qthread.h>
44
45 QT_BEGIN_NAMESPACE
46
47 /**************************************************************************
48 Socket Notifiers
49 *************************************************************************/
qt_mac_socket_callback(CFSocketRef s,CFSocketCallBackType callbackType,CFDataRef,const void *,void * info)50 void qt_mac_socket_callback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef,
51 const void *, void *info)
52 {
53
54 QCFSocketNotifier *cfSocketNotifier = static_cast<QCFSocketNotifier *>(info);
55 int nativeSocket = CFSocketGetNative(s);
56 MacSocketInfo *socketInfo = cfSocketNotifier->macSockets.value(nativeSocket);
57 QEvent notifierEvent(QEvent::SockAct);
58
59 // There is a race condition that happen where we disable the notifier and
60 // the kernel still has a notification to pass on. We then get this
61 // notification after we've successfully disabled the CFSocket, but our Qt
62 // notifier is now gone. The upshot is we have to check the notifier
63 // every time.
64 if (callbackType == kCFSocketReadCallBack) {
65 if (socketInfo->readNotifier && socketInfo->readEnabled) {
66 socketInfo->readEnabled = false;
67 QCoreApplication::sendEvent(socketInfo->readNotifier, ¬ifierEvent);
68 }
69 } else if (callbackType == kCFSocketWriteCallBack) {
70 if (socketInfo->writeNotifier && socketInfo->writeEnabled) {
71 socketInfo->writeEnabled = false;
72 QCoreApplication::sendEvent(socketInfo->writeNotifier, ¬ifierEvent);
73 }
74 }
75
76 if (cfSocketNotifier->maybeCancelWaitForMoreEvents)
77 cfSocketNotifier->maybeCancelWaitForMoreEvents(cfSocketNotifier->eventDispatcher);
78 }
79
80 /*
81 Adds a loop source for the given socket to the current run loop.
82 */
qt_mac_add_socket_to_runloop(const CFSocketRef socket)83 CFRunLoopSourceRef qt_mac_add_socket_to_runloop(const CFSocketRef socket)
84 {
85 CFRunLoopSourceRef loopSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0);
86 if (!loopSource)
87 return 0;
88
89 CFRunLoopAddSource(CFRunLoopGetCurrent(), loopSource, kCFRunLoopCommonModes);
90 return loopSource;
91 }
92
93 /*
94 Removes the loop source for the given socket from the current run loop.
95 */
qt_mac_remove_socket_from_runloop(const CFSocketRef socket,CFRunLoopSourceRef runloop)96 void qt_mac_remove_socket_from_runloop(const CFSocketRef socket, CFRunLoopSourceRef runloop)
97 {
98 Q_ASSERT(runloop);
99 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), runloop, kCFRunLoopCommonModes);
100 CFSocketDisableCallBacks(socket, kCFSocketReadCallBack);
101 CFSocketDisableCallBacks(socket, kCFSocketWriteCallBack);
102 }
103
QCFSocketNotifier()104 QCFSocketNotifier::QCFSocketNotifier()
105 : eventDispatcher(0)
106 , maybeCancelWaitForMoreEvents(0)
107 , enableNotifiersObserver(0)
108 {
109
110 }
111
~QCFSocketNotifier()112 QCFSocketNotifier::~QCFSocketNotifier()
113 {
114
115 }
116
setHostEventDispatcher(QAbstractEventDispatcher * hostEventDispacher)117 void QCFSocketNotifier::setHostEventDispatcher(QAbstractEventDispatcher *hostEventDispacher)
118 {
119 eventDispatcher = hostEventDispacher;
120 }
121
setMaybeCancelWaitForMoreEventsCallback(MaybeCancelWaitForMoreEventsFn callBack)122 void QCFSocketNotifier::setMaybeCancelWaitForMoreEventsCallback(MaybeCancelWaitForMoreEventsFn callBack)
123 {
124 maybeCancelWaitForMoreEvents = callBack;
125 }
126
registerSocketNotifier(QSocketNotifier * notifier)127 void QCFSocketNotifier::registerSocketNotifier(QSocketNotifier *notifier)
128 {
129 Q_ASSERT(notifier);
130 int nativeSocket = notifier->socket();
131 int type = notifier->type();
132 #ifndef QT_NO_DEBUG
133 if (nativeSocket < 0 || nativeSocket > FD_SETSIZE) {
134 qWarning("QSocketNotifier: Internal error");
135 return;
136 } else if (notifier->thread() != eventDispatcher->thread()
137 || eventDispatcher->thread() != QThread::currentThread()) {
138 qWarning("QSocketNotifier: socket notifiers cannot be enabled from another thread");
139 return;
140 }
141 #endif
142
143 if (type == QSocketNotifier::Exception) {
144 qWarning("QSocketNotifier::Exception is not supported on iOS");
145 return;
146 }
147
148 // Check if we have a CFSocket for the native socket, create one if not.
149 MacSocketInfo *socketInfo = macSockets.value(nativeSocket);
150 if (!socketInfo) {
151 socketInfo = new MacSocketInfo();
152
153 // Create CFSocket, specify that we want both read and write callbacks (the callbacks
154 // are enabled/disabled later on).
155 const int callbackTypes = kCFSocketReadCallBack | kCFSocketWriteCallBack;
156 CFSocketContext context = {0, this, 0, 0, 0};
157 socketInfo->socket = CFSocketCreateWithNative(kCFAllocatorDefault, nativeSocket, callbackTypes, qt_mac_socket_callback, &context);
158 if (CFSocketIsValid(socketInfo->socket) == false) {
159 qWarning("QEventDispatcherMac::registerSocketNotifier: Failed to create CFSocket");
160 return;
161 }
162
163 CFOptionFlags flags = CFSocketGetSocketFlags(socketInfo->socket);
164 // QSocketNotifier doesn't close the socket upon destruction/invalidation
165 flags &= ~kCFSocketCloseOnInvalidate;
166 // Expicitly disable automatic re-enable, as we do that manually on each runloop pass
167 flags &= ~(kCFSocketAutomaticallyReenableWriteCallBack | kCFSocketAutomaticallyReenableReadCallBack);
168 CFSocketSetSocketFlags(socketInfo->socket, flags);
169
170 macSockets.insert(nativeSocket, socketInfo);
171 }
172
173 if (type == QSocketNotifier::Read) {
174 Q_ASSERT(socketInfo->readNotifier == 0);
175 socketInfo->readNotifier = notifier;
176 socketInfo->readEnabled = false;
177 } else if (type == QSocketNotifier::Write) {
178 Q_ASSERT(socketInfo->writeNotifier == 0);
179 socketInfo->writeNotifier = notifier;
180 socketInfo->writeEnabled = false;
181 }
182
183 if (!enableNotifiersObserver) {
184 // Create a run loop observer which enables the socket notifiers on each
185 // pass of the run loop, before any sources are processed.
186 CFRunLoopObserverContext context = {};
187 context.info = this;
188 enableNotifiersObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeSources,
189 true, 0, enableSocketNotifiers, &context);
190 Q_ASSERT(enableNotifiersObserver);
191 CFRunLoopAddObserver(CFRunLoopGetCurrent(), enableNotifiersObserver, kCFRunLoopCommonModes);
192 }
193 }
194
unregisterSocketNotifier(QSocketNotifier * notifier)195 void QCFSocketNotifier::unregisterSocketNotifier(QSocketNotifier *notifier)
196 {
197 Q_ASSERT(notifier);
198 int nativeSocket = notifier->socket();
199 int type = notifier->type();
200 #ifndef QT_NO_DEBUG
201 if (nativeSocket < 0 || nativeSocket > FD_SETSIZE) {
202 qWarning("QSocketNotifier: Internal error");
203 return;
204 } else if (notifier->thread() != eventDispatcher->thread() || eventDispatcher->thread() != QThread::currentThread()) {
205 qWarning("QSocketNotifier: socket notifiers cannot be disabled from another thread");
206 return;
207 }
208 #endif
209
210 if (type == QSocketNotifier::Exception) {
211 qWarning("QSocketNotifier::Exception is not supported on iOS");
212 return;
213 }
214 MacSocketInfo *socketInfo = macSockets.value(nativeSocket);
215 if (!socketInfo) {
216 qWarning("QEventDispatcherMac::unregisterSocketNotifier: Tried to unregister a not registered notifier");
217 return;
218 }
219
220 // Decrement read/write counters and disable callbacks if necessary.
221 if (type == QSocketNotifier::Read) {
222 Q_ASSERT(notifier == socketInfo->readNotifier);
223 socketInfo->readNotifier = 0;
224 socketInfo->readEnabled = false;
225 CFSocketDisableCallBacks(socketInfo->socket, kCFSocketReadCallBack);
226 } else if (type == QSocketNotifier::Write) {
227 Q_ASSERT(notifier == socketInfo->writeNotifier);
228 socketInfo->writeNotifier = 0;
229 socketInfo->writeEnabled = false;
230 CFSocketDisableCallBacks(socketInfo->socket, kCFSocketWriteCallBack);
231 }
232
233 // Remove CFSocket from runloop if this was the last QSocketNotifier.
234 if (socketInfo->readNotifier == 0 && socketInfo->writeNotifier == 0) {
235 unregisterSocketInfo(socketInfo);
236 delete socketInfo;
237 macSockets.remove(nativeSocket);
238 }
239 }
240
removeSocketNotifiers()241 void QCFSocketNotifier::removeSocketNotifiers()
242 {
243 // Remove CFSockets from the runloop.
244 for (MacSocketInfo *socketInfo : qAsConst(macSockets)) {
245 unregisterSocketInfo(socketInfo);
246 delete socketInfo;
247 }
248
249 macSockets.clear();
250
251 destroyRunLoopObserver();
252 }
253
destroyRunLoopObserver()254 void QCFSocketNotifier::destroyRunLoopObserver()
255 {
256 if (!enableNotifiersObserver)
257 return;
258
259 CFRunLoopObserverInvalidate(enableNotifiersObserver);
260 CFRelease(enableNotifiersObserver);
261 enableNotifiersObserver = 0;
262 }
263
unregisterSocketInfo(MacSocketInfo * socketInfo)264 void QCFSocketNotifier::unregisterSocketInfo(MacSocketInfo *socketInfo)
265 {
266 if (socketInfo->runloop) {
267 if (CFSocketIsValid(socketInfo->socket))
268 qt_mac_remove_socket_from_runloop(socketInfo->socket, socketInfo->runloop);
269 CFRunLoopSourceInvalidate(socketInfo->runloop);
270 CFRelease(socketInfo->runloop);
271 }
272 CFSocketInvalidate(socketInfo->socket);
273 CFRelease(socketInfo->socket);
274 }
275
enableSocketNotifiers(CFRunLoopObserverRef ref,CFRunLoopActivity activity,void * info)276 void QCFSocketNotifier::enableSocketNotifiers(CFRunLoopObserverRef ref, CFRunLoopActivity activity, void *info)
277 {
278 Q_UNUSED(ref);
279 Q_UNUSED(activity);
280
281 const QCFSocketNotifier *that = static_cast<QCFSocketNotifier *>(info);
282
283 for (MacSocketInfo *socketInfo : that->macSockets) {
284 if (!CFSocketIsValid(socketInfo->socket))
285 continue;
286
287 if (!socketInfo->runloop) {
288 // Add CFSocket to runloop.
289 if (!(socketInfo->runloop = qt_mac_add_socket_to_runloop(socketInfo->socket))) {
290 qWarning("QEventDispatcherMac::registerSocketNotifier: Failed to add CFSocket to runloop");
291 CFSocketInvalidate(socketInfo->socket);
292 continue;
293 }
294
295 // Apple docs say: "If a callback is automatically re-enabled,
296 // it is called every time the condition becomes true ... If a
297 // callback is not automatically re-enabled, then it gets called
298 // exactly once, and is not called again until you manually
299 // re-enable that callback by calling CFSocketEnableCallBacks()".
300 // So, we don't need to enable callbacks on registering.
301 socketInfo->readEnabled = (socketInfo->readNotifier != nullptr);
302 if (!socketInfo->readEnabled)
303 CFSocketDisableCallBacks(socketInfo->socket, kCFSocketReadCallBack);
304 socketInfo->writeEnabled = (socketInfo->writeNotifier != nullptr);
305 if (!socketInfo->writeEnabled)
306 CFSocketDisableCallBacks(socketInfo->socket, kCFSocketWriteCallBack);
307 continue;
308 }
309
310 if (socketInfo->readNotifier && !socketInfo->readEnabled) {
311 socketInfo->readEnabled = true;
312 CFSocketEnableCallBacks(socketInfo->socket, kCFSocketReadCallBack);
313 }
314 if (socketInfo->writeNotifier && !socketInfo->writeEnabled) {
315 socketInfo->writeEnabled = true;
316 CFSocketEnableCallBacks(socketInfo->socket, kCFSocketWriteCallBack);
317 }
318 }
319 }
320
321 QT_END_NAMESPACE
322
323