1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Research In Motion <blackberry-qt@qnx.com>
4 ** Contact: http://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 http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://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 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file. Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qeventdispatcher_blackberry_p.h"
43 #include "qsocketnotifier.h"
44 #include "qdebug.h"
45 #include "qelapsedtimer.h"
46 #include "private/qthread_p.h"
47
48 #include <bps/bps.h>
49 #include <bps/event.h>
50
51 //#define QEVENTDISPATCHERBLACKBERRY_DEBUG
52
53 #ifdef QEVENTDISPATCHERBLACKBERRY_DEBUG
54 #define qEventDispatcherDebug qDebug() << QThread::currentThread()
55 #else
56 #define qEventDispatcherDebug QT_NO_QDEBUG_MACRO()
57 #endif
58
59 class BpsChannelScopeSwitcher
60 {
61 public:
BpsChannelScopeSwitcher(int scopeChannel)62 BpsChannelScopeSwitcher(int scopeChannel) : innerChannel(scopeChannel)
63 {
64 outerChannel = bps_channel_get_active();
65 if (outerChannel != innerChannel)
66 bps_channel_set_active(innerChannel);
67 }
68
~BpsChannelScopeSwitcher()69 ~BpsChannelScopeSwitcher()
70 {
71 if (outerChannel != innerChannel)
72 bps_channel_set_active(outerChannel);
73 }
74
75 private:
76 int innerChannel;
77 int outerChannel;
78 };
79
80 class BBScopedLoopLevelCounter
81 {
82 QEventDispatcherBlackberryPrivate *d;
83
84 public:
BBScopedLoopLevelCounter(QEventDispatcherBlackberryPrivate * p)85 inline BBScopedLoopLevelCounter(QEventDispatcherBlackberryPrivate *p)
86 : d(p)
87 { ++d->loop_level; }
88
~BBScopedLoopLevelCounter()89 inline ~BBScopedLoopLevelCounter()
90 { --d->loop_level; }
91 };
92
93 struct bpsIOHandlerData
94 {
bpsIOHandlerDatabpsIOHandlerData95 bpsIOHandlerData()
96 : count(0), readfds(0), writefds(0), exceptfds(0)
97 {
98 }
99
100 int count;
101 fd_set *readfds;
102 fd_set *writefds;
103 fd_set *exceptfds;
104 };
105
106 static int bpsUnblockDomain = -1;
107
bpsIOHandler(int fd,int io_events,void * data)108 static int bpsIOHandler(int fd, int io_events, void *data)
109 {
110 qEventDispatcherDebug << Q_FUNC_INFO;
111 // decode callback payload
112 bpsIOHandlerData *ioData = static_cast<bpsIOHandlerData*>(data);
113
114 // check if first file is ready
115 bool firstReady = (ioData->count == 0);
116
117 // update ready state of file
118 if (io_events & BPS_IO_INPUT) {
119 qEventDispatcherDebug << fd << "ready for Read";
120 FD_SET(fd, ioData->readfds);
121 ioData->count++;
122 }
123
124 if (io_events & BPS_IO_OUTPUT) {
125 qEventDispatcherDebug << fd << "ready for Write";
126 FD_SET(fd, ioData->writefds);
127 ioData->count++;
128 }
129
130 if (io_events & BPS_IO_EXCEPT) {
131 qEventDispatcherDebug << fd << "ready for Exception";
132 FD_SET(fd, ioData->exceptfds);
133 ioData->count++;
134 }
135
136 // force bps_get_event() to return immediately by posting an event to ourselves;
137 // but this only needs to happen once if multiple files become ready at the same time
138 if (firstReady) {
139 qEventDispatcherDebug << "Sending bpsIOReadyDomain event";
140 // create unblock event
141 bps_event_t *event;
142 int result = bps_event_create(&event, bpsUnblockDomain, 0, NULL, NULL);
143 if (Q_UNLIKELY(result != BPS_SUCCESS)) {
144 qWarning("QEventDispatcherBlackberry: bps_event_create failed");
145 return BPS_FAILURE;
146 }
147
148 // post unblock event to our thread; in this callback the bps channel is
149 // guaranteed to be the same that was active when bps_add_fd was called
150 result = bps_push_event(event);
151 if (Q_UNLIKELY(result != BPS_SUCCESS)) {
152 qWarning("QEventDispatcherBlackberry: bps_push_event failed");
153 bps_event_destroy(event);
154 return BPS_FAILURE;
155 }
156 }
157
158 return BPS_SUCCESS;
159 }
160
QEventDispatcherBlackberryPrivate()161 QEventDispatcherBlackberryPrivate::QEventDispatcherBlackberryPrivate()
162 : loop_level(0)
163 , ioData(new bpsIOHandlerData)
164 {
165 // prepare to use BPS
166 int result = bps_initialize();
167 if (Q_UNLIKELY(result != BPS_SUCCESS))
168 qFatal("QEventDispatcherBlackberry: bps_initialize failed");
169
170 bps_channel = bps_channel_get_active();
171
172 if (bps_channel_create(&holding_channel, 0) != BPS_SUCCESS) {
173 qWarning("QEventDispatcherBlackberry: bps_channel_create failed");
174 holding_channel = -1;
175 }
176
177 // get domain for IO ready and wake up events - ignoring race condition here for now
178 if (bpsUnblockDomain == -1) {
179 bpsUnblockDomain = bps_register_domain();
180 if (Q_UNLIKELY(bpsUnblockDomain == -1))
181 qWarning("QEventDispatcherBlackberry: bps_register_domain failed");
182 }
183 }
184
~QEventDispatcherBlackberryPrivate()185 QEventDispatcherBlackberryPrivate::~QEventDispatcherBlackberryPrivate()
186 {
187 if ((holding_channel != -1) &&
188 (bps_channel_destroy(holding_channel) != BPS_SUCCESS)) {
189 qWarning("QEventDispatcherBlackberry: bps_channel_destroy failed");
190 }
191
192 // we're done using BPS
193 bps_shutdown();
194 }
195
initThreadWakeUp()196 int QEventDispatcherBlackberryPrivate::initThreadWakeUp()
197 {
198 return -1; // no fd's used
199 }
200
processThreadWakeUp(int nsel)201 int QEventDispatcherBlackberryPrivate::processThreadWakeUp(int nsel)
202 {
203 Q_UNUSED(nsel);
204 return wakeUps.fetchAndStoreRelaxed(0);
205 }
206
207 /////////////////////////////////////////////////////////////////////////////
208
QEventDispatcherBlackberry(QObject * parent)209 QEventDispatcherBlackberry::QEventDispatcherBlackberry(QObject *parent)
210 : QEventDispatcherUNIX(*new QEventDispatcherBlackberryPrivate, parent)
211 {
212 }
213
QEventDispatcherBlackberry(QEventDispatcherBlackberryPrivate & dd,QObject * parent)214 QEventDispatcherBlackberry::QEventDispatcherBlackberry(QEventDispatcherBlackberryPrivate &dd, QObject *parent)
215 : QEventDispatcherUNIX(dd, parent)
216 {
217 }
218
~QEventDispatcherBlackberry()219 QEventDispatcherBlackberry::~QEventDispatcherBlackberry()
220 {
221 }
222
registerSocketNotifier(QSocketNotifier * notifier)223 void QEventDispatcherBlackberry::registerSocketNotifier(QSocketNotifier *notifier)
224 {
225 Q_ASSERT(notifier);
226 Q_D(QEventDispatcherBlackberry);
227
228 int sockfd = notifier->socket();
229 int type = notifier->type();
230
231 qEventDispatcherDebug << Q_FUNC_INFO << "fd =" << sockfd;
232
233 if (Q_UNLIKELY(sockfd >= FD_SETSIZE)) {
234 qWarning() << "QEventDispatcherBlackberry: cannot register QSocketNotifier (fd too high)"
235 << sockfd;
236 return;
237 }
238
239 // Register the fd with bps
240 BpsChannelScopeSwitcher channelSwitcher(d->bps_channel);
241 int io_events = ioEvents(sockfd);
242 if (io_events)
243 bps_remove_fd(sockfd);
244
245 switch (type) {
246 case QSocketNotifier::Read:
247 qEventDispatcherDebug << "Registering" << sockfd << "for Reads";
248 io_events |= BPS_IO_INPUT;
249 break;
250 case QSocketNotifier::Write:
251 qEventDispatcherDebug << "Registering" << sockfd << "for Writes";
252 io_events |= BPS_IO_OUTPUT;
253 break;
254 case QSocketNotifier::Exception:
255 default:
256 qEventDispatcherDebug << "Registering" << sockfd << "for Exceptions";
257 io_events |= BPS_IO_EXCEPT;
258 break;
259 }
260
261 const int result = bps_add_fd(sockfd, io_events, &bpsIOHandler, d->ioData.data());
262 if (Q_UNLIKELY(result != BPS_SUCCESS))
263 qWarning() << "QEventDispatcherBlackberry: bps_add_fd failed";
264
265 // Call the base Unix implementation. Needed to allow select() to be called correctly
266 QEventDispatcherUNIX::registerSocketNotifier(notifier);
267 }
268
unregisterSocketNotifier(QSocketNotifier * notifier)269 void QEventDispatcherBlackberry::unregisterSocketNotifier(QSocketNotifier *notifier)
270 {
271 Q_D(QEventDispatcherBlackberry);
272
273 int sockfd = notifier->socket();
274
275 qEventDispatcherDebug << Q_FUNC_INFO << "fd =" << sockfd;
276
277 if (Q_UNLIKELY(sockfd >= FD_SETSIZE)) {
278 qWarning() << "QEventDispatcherBlackberry: cannot unregister QSocketNotifier" << sockfd;
279 return;
280 }
281
282 // Allow the base Unix implementation to unregister the fd too (before call to ioEvents()!)
283 QEventDispatcherUNIX::unregisterSocketNotifier(notifier);
284
285 // Unregister the fd with bps
286 BpsChannelScopeSwitcher channelSwitcher(d->bps_channel);
287 int result = bps_remove_fd(sockfd);
288 if (Q_UNLIKELY(result != BPS_SUCCESS))
289 qWarning() << "QEventDispatcherBlackberry: bps_remove_fd failed" << sockfd;
290
291 const int io_events = ioEvents(sockfd);
292 // if other socket notifier is watching sockfd, readd it
293 if (io_events) {
294 result = bps_add_fd(sockfd, io_events, &bpsIOHandler, d->ioData.data());
295 if (Q_UNLIKELY(result != BPS_SUCCESS))
296 qWarning("QEventDispatcherBlackberry: bps_add_fd error");
297 }
298 }
299
timevalToMillisecs(const timeval & tv)300 static inline int timevalToMillisecs(const timeval &tv)
301 {
302 return (tv.tv_sec * 1000) + (tv.tv_usec / 1000);
303 }
304
destroyHeldBpsEvent(int holding_channel)305 static inline void destroyHeldBpsEvent(int holding_channel)
306 {
307 // Switch to the holding channel and use bps_get_event() to trigger its destruction. We
308 // don't care about the return value from this call to bps_get_event().
309 BpsChannelScopeSwitcher holdingChannelSwitcher(holding_channel);
310 bps_event_t *held_event = 0;
311 (void)bps_get_event(&held_event, 0);
312 }
313
select(int nfds,fd_set * readfds,fd_set * writefds,fd_set * exceptfds,timeval * timeout)314 int QEventDispatcherBlackberry::select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
315 timeval *timeout)
316 {
317 Q_UNUSED(nfds);
318 Q_D(QEventDispatcherBlackberry);
319 const BBScopedLoopLevelCounter bbLoopCounter(d);
320
321 BpsChannelScopeSwitcher channelSwitcher(d->bps_channel);
322
323 // prepare file sets for bps callback
324 d->ioData->count = 0;
325 d->ioData->readfds = readfds;
326 d->ioData->writefds = writefds;
327 d->ioData->exceptfds = exceptfds;
328
329 // reset all file sets
330 if (readfds)
331 FD_ZERO(readfds);
332
333 if (writefds)
334 FD_ZERO(writefds);
335
336 if (exceptfds)
337 FD_ZERO(exceptfds);
338
339 bps_event_t *event = 0;
340 unsigned int eventCount = 0;
341
342 // If an event handler called through filterEvent() starts a nested event loop by creating a
343 // new QEventLoop, we will recursively enter this function again. However, each time
344 // bps_get_event() is called, it destroys the last event it handed out before returning the
345 // next event. We don't want it to destroy the event that triggered the nested event loop,
346 // since there may still be more handlers that need to get that event, once the nested event
347 // loop is done and control returns to the outer event loop.
348 //
349 // So we move an event to a holding channel, which takes ownership of the event. Putting
350 // the event on our own channel allows us to manage when it is destroyed, keeping it alive
351 // until we know we are done with it. Each recursive call of this function needs to have
352 // it's own holding channel, since a channel is a queue, not a stack.
353 //
354 // However, a recursive call into this function happens very rarely compared to the many
355 // times this function is called. We don't want to create a holding channel for each time
356 // this function is called, only when it is called recursively. Thus we have the instance
357 // variable d->holding_channel to use in the common case. We keep track of recursive calls
358 // with d->loop_level. If we are in a recursive call, then we create a new holding channel
359 // for this run.
360 int holding_channel = d->holding_channel;
361 if ((d->loop_level > 1) &&
362 Q_UNLIKELY(bps_channel_create(&holding_channel, 0) != BPS_SUCCESS)) {
363 qWarning("QEventDispatcherBlackberry: bps_channel_create failed");
364 holding_channel = -1;
365 }
366
367 // Convert timeout to milliseconds
368 int timeoutTotal = -1;
369 if (timeout)
370 timeoutTotal = timevalToMillisecs(*timeout);
371 int timeoutLeft = timeoutTotal;
372 timeval startTime = qt_gettime();
373
374 // This loop exists such that we can drain the bps event queue of all native events
375 // more efficiently than if we were to return control to Qt after each event. This
376 // is important for handling touch events which can come in rapidly.
377 forever {
378 // Only emit the awake() and aboutToBlock() signals in the second iteration. For the
379 // first iteration, the UNIX event dispatcher will have taken care of that already.
380 // Also native events are actually processed one loop iteration after they were
381 // retrieved with bps_get_event().
382
383 // Filtering the native event should happen between the awake() and aboutToBlock()
384 // signal emissions. The calls awake() - filterNativeEvent() - aboutToBlock() -
385 // bps_get_event() need not to be interrupted by a break or return statement.
386 if (eventCount > 0) {
387 if (event) {
388 emit awake();
389 filterEvent(static_cast<void*>(event));
390 emit aboutToBlock();
391
392 if (Q_LIKELY(holding_channel != -1)) {
393 // We are now done with this BPS event. Destroy it.
394 destroyHeldBpsEvent(holding_channel);
395 }
396 }
397
398 // Update the timeout
399 // Clock source is monotonic, so we can recalculate how much timeout is left
400 if (timeoutTotal != -1) {
401 timeval t2 = qt_gettime();
402 timeoutLeft = timeoutTotal
403 - (timevalToMillisecs(t2) - timevalToMillisecs(startTime));
404 if (timeoutLeft < 0)
405 timeoutLeft = 0;
406 }
407
408 timeval tnext;
409 if (d->timerList.timerWait(tnext)) {
410 int timeoutNext = timevalToMillisecs(tnext);
411 if (timeoutNext < timeoutLeft || timeoutTotal == -1) {
412 timeoutTotal = timeoutLeft = timeoutNext;
413 startTime = qt_gettime();
414 }
415 }
416 }
417
418 event = 0;
419 { // We need to increase loop level in this scope,
420 // because bps_get_event can also invoke callbacks
421 QScopedLoopLevelCounter loopLevelCounter(d->threadData);
422
423 // Wait for event or file to be ready
424 const int result = bps_get_event(&event, timeoutLeft);
425 if (Q_UNLIKELY(result != BPS_SUCCESS))
426 qWarning("QEventDispatcherBlackberry: bps_get_event failed");
427 }
428
429 if (!event) // In case of !event, we break out of the loop to let Qt process the timers
430 break; // (since timeout has expired) and socket notifiers that are now ready.
431
432 if (bps_event_get_domain(event) == bpsUnblockDomain) {
433 timeoutTotal = 0; // in order to immediately drain the event queue of native events
434 event = 0; // (especially touch move events) we don't break out here
435 } else {
436 // Move the event to our holding channel so we can manage when it is destroyed.
437 if (Q_LIKELY(holding_channel != 1) &&
438 Q_UNLIKELY(bps_channel_push_event(holding_channel, event) != BPS_SUCCESS)) {
439 qWarning("QEventDispatcherBlackberry: bps_channel_push_event failed");
440 }
441 }
442
443 ++eventCount;
444
445 // Make sure we are not trapped in this loop due to continuous native events
446 // also we cannot recalculate the timeout without a monotonic clock as the time may have changed
447 const unsigned int maximumEventCount = 12;
448 if (Q_UNLIKELY((eventCount > maximumEventCount && timeoutLeft == 0)
449 || !QElapsedTimer::isMonotonic())) {
450 if (event) {
451 filterEvent(static_cast<void*>(event));
452
453 if (Q_LIKELY(holding_channel != -1)) {
454 // We are now done with this BPS event. Destroy it.
455 destroyHeldBpsEvent(holding_channel);
456 }
457 }
458 break;
459 }
460 }
461
462 // If this was a recursive call into this function, a new holding channel was created for
463 // this run, so destroy it now.
464 if ((holding_channel != d->holding_channel) &&
465 Q_LIKELY(holding_channel != -1) &&
466 Q_UNLIKELY(bps_channel_destroy(holding_channel) != BPS_SUCCESS)) {
467 qWarning("QEventDispatcherBlackberry: bps_channel_destroy failed");
468 }
469
470 // the number of bits set in the file sets
471 return d->ioData->count;
472 }
473
wakeUp()474 void QEventDispatcherBlackberry::wakeUp()
475 {
476 Q_D(QEventDispatcherBlackberry);
477 if (d->wakeUps.testAndSetAcquire(0, 1)) {
478 bps_event_t *event;
479 if (Q_LIKELY(bps_event_create(&event, bpsUnblockDomain, 0, 0, 0) == BPS_SUCCESS)) {
480 if (Q_LIKELY(bps_channel_push_event(d->bps_channel, event) == BPS_SUCCESS))
481 return;
482 else
483 bps_event_destroy(event);
484 }
485 qWarning("QEventDispatcherBlackberry: wakeUp failed");
486 }
487 }
488
ioEvents(int fd)489 int QEventDispatcherBlackberry::ioEvents(int fd)
490 {
491 int io_events = 0;
492
493 Q_D(QEventDispatcherBlackberry);
494
495 if (FD_ISSET(fd, &d->sn_vec[0].enabled_fds))
496 io_events |= BPS_IO_INPUT;
497
498 if (FD_ISSET(fd, &d->sn_vec[1].enabled_fds))
499 io_events |= BPS_IO_OUTPUT;
500
501 if (FD_ISSET(fd, &d->sn_vec[2].enabled_fds))
502 io_events |= BPS_IO_EXCEPT;
503
504 return io_events;
505 }
506
507 QT_END_NAMESPACE
508