/**************************************************************************** | |
** | |
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). | |
** All rights reserved. | |
** Contact: Nokia Corporation (qt-info@nokia.com) | |
** | |
** This file is part of the QtCore module of the Qt Toolkit. | |
** | |
** $QT_BEGIN_LICENSE:LGPL$ | |
** GNU Lesser General Public License Usage | |
** This file may be used under the terms of the GNU Lesser General Public | |
** License version 2.1 as published by the Free Software Foundation and | |
** appearing in the file LICENSE.LGPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU Lesser | |
** General Public License version 2.1 requirements will be met: | |
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. | |
** | |
** In addition, as a special exception, Nokia gives you certain additional | |
** rights. These rights are described in the Nokia Qt LGPL Exception | |
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. | |
** | |
** GNU General Public License Usage | |
** Alternatively, this file may be used under the terms of the GNU General | |
** Public License version 3.0 as published by the Free Software Foundation | |
** and appearing in the file LICENSE.GPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU General | |
** Public License version 3.0 requirements will be met: | |
** http://www.gnu.org/copyleft/gpl.html. | |
** | |
** Other Usage | |
** Alternatively, this file may be used in accordance with the terms and | |
** conditions contained in a signed written agreement between you and Nokia. | |
** | |
** | |
** | |
** | |
** | |
** $QT_END_LICENSE$ | |
** | |
****************************************************************************/ | |
#include "qeventdispatcher_symbian_p.h" | |
#include <private/qthread_p.h> | |
#include <qcoreapplication.h> | |
#include <private/qcoreapplication_p.h> | |
#include <unistd.h> | |
#include <errno.h> | |
QT_BEGIN_NAMESPACE | |
#ifdef SYMBIAN_GRAPHICS_WSERV_QT_EFFECTS | |
// when the system UI is Qt based, priority drop is not needed as CPU starved processes will not be killed. | |
#undef QT_SYMBIAN_PRIORITY_DROP | |
#else | |
#define QT_SYMBIAN_PRIORITY_DROP | |
#endif | |
#define WAKE_UP_PRIORITY CActive::EPriorityStandard | |
#define TIMER_PRIORITY CActive::EPriorityHigh | |
#define NULLTIMER_PRIORITY CActive::EPriorityLow | |
#define COMPLETE_DEFERRED_ACTIVE_OBJECTS_PRIORITY CActive::EPriorityIdle | |
static inline int qt_pipe_write(int socket, const char *data, qint64 len) | |
{ | |
return ::write(socket, data, len); | |
} | |
#if defined(write) | |
# undef write | |
#endif | |
static inline int qt_pipe_close(int socket) | |
{ | |
return ::close(socket); | |
} | |
#if defined(close) | |
# undef close | |
#endif | |
static inline int qt_pipe_fcntl(int socket, int command) | |
{ | |
return ::fcntl(socket, command); | |
} | |
static inline int qt_pipe2_fcntl(int socket, int command, int option) | |
{ | |
return ::fcntl(socket, command, option); | |
} | |
#if defined(fcntl) | |
# undef fcntl | |
#endif | |
static inline int qt_socket_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) | |
{ | |
return ::select(nfds, readfds, writefds, exceptfds, timeout); | |
} | |
// This simply interrupts the select and locks the mutex until destroyed. | |
class QSelectMutexGrabber | |
{ | |
public: | |
QSelectMutexGrabber(int writeFd, int readFd, QMutex *mutex) | |
: m_mutex(mutex) | |
{ | |
if (m_mutex->tryLock()) | |
return; | |
char dummy = 0; | |
qt_pipe_write(writeFd, &dummy, 1); | |
m_mutex->lock(); | |
char buffer; | |
while (::read(readFd, &buffer, 1) > 0) {} | |
} | |
~QSelectMutexGrabber() | |
{ | |
m_mutex->unlock(); | |
} | |
private: | |
QMutex *m_mutex; | |
}; | |
/* | |
* This class is designed to aid in implementing event handling in a more round robin fashion. We | |
* cannot change active objects that we do not own, but the active objects that Qt owns will use | |
* this as a base class with convenience functions. | |
* | |
* Here is how it works: On every RunL, the deriving class should call maybeQueueForLater(). | |
* This will return whether the active object has been queued, or whether it should run immediately. | |
* Queued objects will run again after other events have been processed. | |
* | |
* The QCompleteDeferredAOs class is a special object that runs after all others, which will | |
* reactivate the objects that were previously not run. | |
*/ | |
inline QActiveObject::QActiveObject(TInt priority, QEventDispatcherSymbian *dispatcher) | |
: CActive(priority), | |
m_dispatcher(dispatcher), | |
m_hasAlreadyRun(false), | |
m_hasRunAgain(false), | |
m_iterationCount(1) | |
{ | |
} | |
QActiveObject::~QActiveObject() | |
{ | |
if (m_hasRunAgain) | |
m_dispatcher->removeDeferredActiveObject(this); | |
} | |
bool QActiveObject::maybeQueueForLater() | |
{ | |
Q_ASSERT(!m_hasRunAgain); | |
if (!m_hasAlreadyRun || m_dispatcher->iterationCount() != m_iterationCount) { | |
// First occurrence of this event in this iteration. | |
m_hasAlreadyRun = true; | |
m_iterationCount = m_dispatcher->iterationCount(); | |
return false; | |
} else { | |
// The event has already occurred. | |
m_dispatcher->addDeferredActiveObject(this); | |
m_hasRunAgain = true; | |
return true; | |
} | |
} | |
void QActiveObject::reactivateAndComplete() | |
{ | |
iStatus = KRequestPending; | |
SetActive(); | |
TRequestStatus *status = &iStatus; | |
QEventDispatcherSymbian::RequestComplete(status, KErrNone); | |
m_hasRunAgain = false; | |
m_hasAlreadyRun = false; | |
} | |
QWakeUpActiveObject::QWakeUpActiveObject(QEventDispatcherSymbian *dispatcher) | |
: QActiveObject(WAKE_UP_PRIORITY, dispatcher) | |
{ | |
CActiveScheduler::Add(this); | |
iStatus = KRequestPending; | |
SetActive(); | |
} | |
QWakeUpActiveObject::~QWakeUpActiveObject() | |
{ | |
Cancel(); | |
} | |
void QWakeUpActiveObject::DoCancel() | |
{ | |
if (iStatus.Int() == KRequestPending) { | |
TRequestStatus *status = &iStatus; | |
QEventDispatcherSymbian::RequestComplete(status, KErrNone); | |
} | |
} | |
void QWakeUpActiveObject::RunL() | |
{ | |
if (maybeQueueForLater()) | |
return; | |
iStatus = KRequestPending; | |
SetActive(); | |
QT_TRYCATCH_LEAVING(m_dispatcher->wakeUpWasCalled()); | |
} | |
QTimerActiveObject::QTimerActiveObject(QEventDispatcherSymbian *dispatcher, SymbianTimerInfo *timerInfo) | |
: QActiveObject((timerInfo->interval) ? TIMER_PRIORITY : NULLTIMER_PRIORITY , dispatcher), | |
m_timerInfo(timerInfo), m_expectedTimeSinceLastEvent(0) | |
{ | |
// start the timeout timer to ensure initialisation | |
m_timeoutTimer.start(); | |
} | |
QTimerActiveObject::~QTimerActiveObject() | |
{ | |
Cancel(); | |
m_rTimer.Close(); //close of null handle is safe | |
} | |
void QTimerActiveObject::DoCancel() | |
{ | |
if (m_timerInfo->interval > 0) { | |
m_rTimer.Cancel(); | |
} else { | |
if (iStatus.Int() == KRequestPending) { | |
TRequestStatus *status = &iStatus; | |
QEventDispatcherSymbian::RequestComplete(status, KErrNone); | |
} | |
} | |
} | |
void QTimerActiveObject::RunL() | |
{ | |
int error = KErrNone; | |
if (iStatus == KErrNone) { | |
QT_TRYCATCH_ERROR(error, Run()); | |
} else { | |
error = iStatus.Int(); | |
} | |
// All Symbian error codes are negative. | |
if (error < 0) { | |
CActiveScheduler::Current()->Error(error); // stop and report here, as this timer will be deleted on scope exit | |
} | |
} | |
#define MAX_SYMBIAN_TIMEOUT_MS 2000000 | |
void QTimerActiveObject::StartTimer() | |
{ | |
if (m_timerInfo->msLeft > MAX_SYMBIAN_TIMEOUT_MS) { | |
//There is loss of accuracy anyway due to needing to restart the timer every 33 minutes, | |
//so the 1/64s res of After() is acceptable for these very long timers. | |
m_rTimer.After(iStatus, MAX_SYMBIAN_TIMEOUT_MS * 1000); | |
m_timerInfo->msLeft -= MAX_SYMBIAN_TIMEOUT_MS; | |
} else { | |
// this algorithm implements drift correction for repeating timers | |
// calculate how late we are for this event | |
int timeSinceLastEvent = m_timeoutTimer.restart(); | |
int overshoot = timeSinceLastEvent - m_expectedTimeSinceLastEvent; | |
if (overshoot > m_timerInfo->msLeft) { | |
// we skipped a whole timeout, restart from here | |
overshoot = 0; | |
} | |
// calculate when the next event should happen | |
int waitTime = m_timerInfo->msLeft - overshoot; | |
m_expectedTimeSinceLastEvent = waitTime; | |
// limit the actual ms wait time to avoid wild corrections | |
// this will cause the real event time to slowly drift back to the expected event time | |
// measurements show that Symbian timers always fire 1 or 2 ms late | |
const int limit = 4; | |
waitTime = qMax(m_timerInfo->msLeft - limit, waitTime); | |
m_rTimer.HighRes(iStatus, waitTime * 1000); | |
m_timerInfo->msLeft = 0; | |
} | |
SetActive(); | |
} | |
void QTimerActiveObject::Run() | |
{ | |
//restart timer immediately, if the timeout has been split because it overflows max for platform. | |
if (m_timerInfo->msLeft > 0) { | |
StartTimer(); | |
return; | |
} | |
if (maybeQueueForLater()) | |
return; | |
if (m_timerInfo->interval > 0) { | |
// Start a new timer immediately so that we don't lose time. | |
m_timerInfo->msLeft = m_timerInfo->interval; | |
StartTimer(); | |
m_timerInfo->dispatcher->timerFired(m_timerInfo->timerId); | |
} else { | |
// However, we only complete zero timers after the event has finished, | |
// in order to prevent busy looping when doing nested loops. | |
// Keep the refpointer around in order to avoid deletion until the end of this function. | |
SymbianTimerInfoPtr timerInfoPtr(m_timerInfo); | |
m_timerInfo->dispatcher->timerFired(m_timerInfo->timerId); | |
iStatus = KRequestPending; | |
SetActive(); | |
TRequestStatus *status = &iStatus; | |
QEventDispatcherSymbian::RequestComplete(status, KErrNone); | |
} | |
} | |
void QTimerActiveObject::Start() | |
{ | |
CActiveScheduler::Add(this); | |
m_timerInfo->msLeft = m_timerInfo->interval; | |
if (m_timerInfo->interval > 0) { | |
if (!m_rTimer.Handle()) { | |
qt_symbian_throwIfError(m_rTimer.CreateLocal()); | |
} | |
m_timeoutTimer.start(); | |
m_expectedTimeSinceLastEvent = 0; | |
StartTimer(); | |
} else { | |
iStatus = KRequestPending; | |
SetActive(); | |
TRequestStatus *status = &iStatus; | |
QEventDispatcherSymbian::RequestComplete(status, KErrNone); | |
} | |
} | |
SymbianTimerInfo::SymbianTimerInfo() | |
: timerAO(0) | |
{ | |
} | |
SymbianTimerInfo::~SymbianTimerInfo() | |
{ | |
delete timerAO; | |
} | |
QCompleteDeferredAOs::QCompleteDeferredAOs(QEventDispatcherSymbian *dispatcher) | |
: CActive(COMPLETE_DEFERRED_ACTIVE_OBJECTS_PRIORITY), | |
m_dispatcher(dispatcher) | |
{ | |
CActiveScheduler::Add(this); | |
iStatus = KRequestPending; | |
SetActive(); | |
} | |
QCompleteDeferredAOs::~QCompleteDeferredAOs() | |
{ | |
Cancel(); | |
} | |
void QCompleteDeferredAOs::complete() | |
{ | |
if (iStatus.Int() == KRequestPending) { | |
TRequestStatus *status = &iStatus; | |
QEventDispatcherSymbian::RequestComplete(status, KErrNone); | |
} | |
} | |
void QCompleteDeferredAOs::DoCancel() | |
{ | |
if (iStatus.Int() == KRequestPending) { | |
TRequestStatus *status = &iStatus; | |
QEventDispatcherSymbian::RequestComplete(status, KErrNone); | |
} | |
} | |
void QCompleteDeferredAOs::RunL() | |
{ | |
iStatus = KRequestPending; | |
SetActive(); | |
QT_TRYCATCH_LEAVING(m_dispatcher->reactivateDeferredActiveObjects()); | |
} | |
QSelectThread::QSelectThread() | |
: m_quit(false) | |
{ | |
if (::pipe(m_pipeEnds) != 0) { | |
qWarning("Select thread was unable to open a pipe, errno: %i", errno); | |
} else { | |
int flags0 = qt_pipe_fcntl(m_pipeEnds[0], F_GETFL); | |
int flags1 = qt_pipe_fcntl(m_pipeEnds[1], F_GETFL); | |
// We should check the error code here, but Open C has a bug that returns | |
// failure even though the operation was successful. | |
qt_pipe2_fcntl(m_pipeEnds[0], F_SETFL, flags0 | O_NONBLOCK); | |
qt_pipe2_fcntl(m_pipeEnds[1], F_SETFL, flags1 | O_NONBLOCK); | |
} | |
} | |
QSelectThread::~QSelectThread() | |
{ | |
qt_pipe_close(m_pipeEnds[1]); | |
qt_pipe_close(m_pipeEnds[0]); | |
} | |
void QSelectThread::run() | |
{ | |
Q_D(QThread); | |
m_mutex.lock(); | |
while (!m_quit) { | |
fd_set readfds; | |
fd_set writefds; | |
fd_set exceptionfds; | |
FD_ZERO(&readfds); | |
FD_ZERO(&writefds); | |
FD_ZERO(&exceptionfds); | |
int maxfd = 0; | |
maxfd = qMax(maxfd, updateSocketSet(QSocketNotifier::Read, &readfds)); | |
maxfd = qMax(maxfd, updateSocketSet(QSocketNotifier::Write, &writefds)); | |
maxfd = qMax(maxfd, updateSocketSet(QSocketNotifier::Exception, &exceptionfds)); | |
maxfd = qMax(maxfd, m_pipeEnds[0]); | |
maxfd++; | |
FD_SET(m_pipeEnds[0], &readfds); | |
int ret; | |
int savedSelectErrno; | |
ret = qt_socket_select(maxfd, &readfds, &writefds, &exceptionfds, 0); | |
savedSelectErrno = errno; | |
if(ret == 0) { | |
// do nothing | |
} else if (ret < 0) { | |
switch (savedSelectErrno) { | |
case EBADF: | |
case EINVAL: | |
case ENOMEM: | |
case EFAULT: | |
qWarning("::select() returned an error: %i", savedSelectErrno); | |
break; | |
case ECONNREFUSED: | |
case EPIPE: | |
qWarning("::select() returned an error: %i (go through sockets)", savedSelectErrno); | |
// prepare to go through all sockets | |
// mark in fd sets both: | |
// good ones | |
// ones that return -1 in select | |
// after loop update notifiers for all of them | |
// as we don't have "exception" notifier type | |
// we should force monitoring fd_set of this | |
// type as well | |
// clean @ start | |
FD_ZERO(&readfds); | |
FD_ZERO(&writefds); | |
FD_ZERO(&exceptionfds); | |
for (QHash<QSocketNotifier *, TRequestStatus *>::const_iterator i = m_AOStatuses.begin(); | |
i != m_AOStatuses.end(); ++i) { | |
fd_set onefds; | |
FD_ZERO(&onefds); | |
FD_SET(i.key()->socket(), &onefds); | |
fd_set excfds; | |
FD_ZERO(&excfds); | |
FD_SET(i.key()->socket(), &excfds); | |
maxfd = i.key()->socket() + 1; | |
struct timeval timeout; | |
timeout.tv_sec = 0; | |
timeout.tv_usec = 0; | |
ret = 0; | |
if(i.key()->type() == QSocketNotifier::Read) { | |
ret = ::select(maxfd, &onefds, 0, &excfds, &timeout); | |
if(ret != 0) FD_SET(i.key()->socket(), &readfds); | |
} else if(i.key()->type() == QSocketNotifier::Write) { | |
ret = ::select(maxfd, 0, &onefds, &excfds, &timeout); | |
if(ret != 0) FD_SET(i.key()->socket(), &writefds); | |
} | |
} // end for | |
// traversed all, so update | |
updateActivatedNotifiers(QSocketNotifier::Exception, &exceptionfds); | |
updateActivatedNotifiers(QSocketNotifier::Read, &readfds); | |
updateActivatedNotifiers(QSocketNotifier::Write, &writefds); | |
break; | |
case EINTR: // Should never occur on Symbian, but this is future proof! | |
default: | |
qWarning("::select() returned an unknown error: %i", savedSelectErrno); | |
break; | |
} | |
} else { | |
updateActivatedNotifiers(QSocketNotifier::Exception, &exceptionfds); | |
updateActivatedNotifiers(QSocketNotifier::Read, &readfds); | |
updateActivatedNotifiers(QSocketNotifier::Write, &writefds); | |
} | |
if (FD_ISSET(m_pipeEnds[0], &readfds)) | |
m_waitCond.wait(&m_mutex); | |
} | |
m_mutex.unlock(); | |
} | |
void QSelectThread::requestSocketEvents ( QSocketNotifier *notifier, TRequestStatus *status ) | |
{ | |
Q_D(QThread); | |
if (!isRunning()) { | |
start(); | |
} | |
Q_ASSERT(QThread::currentThread() == this->thread()); | |
QSelectMutexGrabber lock(m_pipeEnds[1], m_pipeEnds[0], &m_mutex); | |
Q_ASSERT(!m_AOStatuses.contains(notifier)); | |
m_AOStatuses.insert(notifier, status); | |
m_waitCond.wakeAll(); | |
} | |
void QSelectThread::cancelSocketEvents ( QSocketNotifier *notifier ) | |
{ | |
Q_ASSERT(QThread::currentThread() == this->thread()); | |
QSelectMutexGrabber lock(m_pipeEnds[1], m_pipeEnds[0], &m_mutex); | |
m_AOStatuses.remove(notifier); | |
m_waitCond.wakeAll(); | |
} | |
void QSelectThread::restart() | |
{ | |
Q_ASSERT(QThread::currentThread() == this->thread()); | |
QSelectMutexGrabber lock(m_pipeEnds[1], m_pipeEnds[0], &m_mutex); | |
m_waitCond.wakeAll(); | |
} | |
int QSelectThread::updateSocketSet(QSocketNotifier::Type type, fd_set *fds) | |
{ | |
int maxfd = 0; | |
if(m_AOStatuses.isEmpty()) { | |
/* | |
* Wonder if should return -1 | |
* to signal that no descriptors | |
* added to fds | |
*/ | |
return maxfd; | |
} | |
for ( QHash<QSocketNotifier *, TRequestStatus *>::const_iterator i = m_AOStatuses.begin(); | |
i != m_AOStatuses.end(); ++i) { | |
if (i.key()->type() == type) { | |
FD_SET(i.key()->socket(), fds); | |
maxfd = qMax(maxfd, i.key()->socket()); | |
} else if(type == QSocketNotifier::Exception) { | |
/* | |
* We are registering existing sockets | |
* always to exception set | |
* | |
* Doing double FD_SET shouldn't | |
* matter | |
*/ | |
FD_SET(i.key()->socket(), fds); | |
maxfd = qMax(maxfd, i.key()->socket()); | |
} | |
} | |
return maxfd; | |
} | |
void QSelectThread::updateActivatedNotifiers(QSocketNotifier::Type type, fd_set *fds) | |
{ | |
Q_D(QThread); | |
if(m_AOStatuses.isEmpty()) { | |
return; | |
} | |
QList<QSocketNotifier *> toRemove; | |
for (QHash<QSocketNotifier *, TRequestStatus *>::const_iterator i = m_AOStatuses.begin(); | |
i != m_AOStatuses.end(); ++i) { | |
if (i.key()->type() == type && FD_ISSET(i.key()->socket(), fds)) { | |
toRemove.append(i.key()); | |
TRequestStatus *status = i.value(); | |
// Thread data is still owned by the main thread. | |
QEventDispatcherSymbian::RequestComplete(d->threadData->symbian_thread_handle, status, KErrNone); | |
} else if(type == QSocketNotifier::Exception && FD_ISSET(i.key()->socket(), fds)) { | |
/* | |
* check if socket is in exception set | |
* then signal RequestComplete for it | |
*/ | |
qWarning("exception on %d [will close the socket handle - hack]", i.key()->socket()); | |
// quick fix; there is a bug | |
// when doing read on socket | |
// errors not preoperly mapped | |
// after offline-ing the device | |
// on some devices we do get exception | |
::close(i.key()->socket()); | |
toRemove.append(i.key()); | |
TRequestStatus *status = i.value(); | |
QEventDispatcherSymbian::RequestComplete(d->threadData->symbian_thread_handle, status, KErrNone); | |
} | |
} | |
for (int c = 0; c < toRemove.size(); ++c) { | |
m_AOStatuses.remove(toRemove[c]); | |
} | |
} | |
void QSelectThread::stop() | |
{ | |
m_quit = true; | |
restart(); | |
wait(); | |
} | |
QSocketActiveObject::QSocketActiveObject(QEventDispatcherSymbian *dispatcher, QSocketNotifier *notifier) | |
: QActiveObject(CActive::EPriorityStandard, dispatcher), | |
m_notifier(notifier), | |
m_inSocketEvent(false), | |
m_deleteLater(false) | |
{ | |
CActiveScheduler::Add(this); | |
iStatus = KRequestPending; | |
SetActive(); | |
} | |
QSocketActiveObject::~QSocketActiveObject() | |
{ | |
Cancel(); | |
} | |
void QSocketActiveObject::DoCancel() | |
{ | |
if (iStatus.Int() == KRequestPending) { | |
TRequestStatus *status = &iStatus; | |
QEventDispatcherSymbian::RequestComplete(status, KErrNone); | |
} | |
} | |
void QSocketActiveObject::RunL() | |
{ | |
if (maybeQueueForLater()) | |
return; | |
QT_TRYCATCH_LEAVING(m_dispatcher->socketFired(this)); | |
} | |
void QSocketActiveObject::deleteLater() | |
{ | |
if (m_inSocketEvent) { | |
m_deleteLater = true; | |
} else { | |
delete this; | |
} | |
} | |
#ifdef QT_SYMBIAN_PRIORITY_DROP | |
class QIdleDetectorThread | |
{ | |
public: | |
QIdleDetectorThread() | |
: m_state(STATE_RUN), m_stop(false) | |
{ | |
qt_symbian_throwIfError(m_lock.CreateLocal(0)); | |
TInt err = m_idleDetectorThread.Create(KNullDesC(), &idleDetectorThreadFunc, 1024, NULL, this); | |
if (err != KErrNone) | |
m_lock.Close(); | |
qt_symbian_throwIfError(err); | |
m_idleDetectorThread.SetPriority(EPriorityAbsoluteBackgroundNormal); | |
m_idleDetectorThread.Resume(); | |
} | |
~QIdleDetectorThread() | |
{ | |
// close down the idle thread because if corelib is loaded temporarily, this would leak threads into the host process | |
m_stop = true; | |
m_lock.Signal(); | |
m_idleDetectorThread.SetPriority(EPriorityNormal); | |
TRequestStatus s; | |
m_idleDetectorThread.Logon(s); | |
User::WaitForRequest(s); | |
m_idleDetectorThread.Close(); | |
m_lock.Close(); | |
} | |
void kick() | |
{ | |
m_state = STATE_KICKED; | |
m_lock.Signal(); | |
} | |
bool hasRun() | |
{ | |
return m_state == STATE_RUN; | |
} | |
private: | |
static TInt idleDetectorThreadFunc(TAny* self) | |
{ | |
static_cast<QIdleDetectorThread*>(self)->IdleLoop(); | |
return KErrNone; | |
} | |
void IdleLoop() | |
{ | |
while (!m_stop) { | |
m_lock.Wait(); | |
m_state = STATE_RUN; | |
} | |
} | |
private: | |
enum IdleStates {STATE_KICKED, STATE_RUN} m_state; | |
bool m_stop; | |
RThread m_idleDetectorThread; | |
RSemaphore m_lock; | |
}; | |
Q_GLOBAL_STATIC(QIdleDetectorThread, idleDetectorThread); | |
const int maxBusyTime = 2000; // maximum time we allow idle detector to be blocked before worrying, in milliseconds | |
const int baseDelay = 1000; // minimum delay time used when backing off to allow idling, in microseconds | |
#endif | |
QEventDispatcherSymbian::QEventDispatcherSymbian(QObject *parent) | |
: QAbstractEventDispatcher(parent), | |
m_selectThread(0), | |
m_activeScheduler(0), | |
m_wakeUpAO(0), | |
m_completeDeferredAOs(0), | |
m_interrupt(false), | |
m_wakeUpDone(0), | |
m_iterationCount(0), | |
m_insideTimerEvent(false), | |
m_noSocketEvents(false) | |
{ | |
#ifdef QT_SYMBIAN_PRIORITY_DROP | |
m_delay = baseDelay; | |
m_avgEventTime = 0; | |
idleDetectorThread(); | |
#endif | |
} | |
QEventDispatcherSymbian::~QEventDispatcherSymbian() | |
{ | |
} | |
void QEventDispatcherSymbian::startingUp() | |
{ | |
if( !CActiveScheduler::Current() ) { | |
m_activeScheduler = q_check_ptr(new CQtActiveScheduler()); // CBase derived class needs to be checked on new | |
CActiveScheduler::Install(m_activeScheduler); | |
} | |
m_wakeUpAO = q_check_ptr(new QWakeUpActiveObject(this)); | |
m_completeDeferredAOs = q_check_ptr(new QCompleteDeferredAOs(this)); | |
// We already might have posted events, wakeup once to process them | |
wakeUp(); | |
} | |
QSelectThread& QEventDispatcherSymbian::selectThread() { | |
if (!m_selectThread) | |
m_selectThread = new QSelectThread; | |
return *m_selectThread; | |
} | |
void QEventDispatcherSymbian::closingDown() | |
{ | |
if (m_selectThread && m_selectThread->isRunning()) { | |
m_selectThread->stop(); | |
} | |
delete m_selectThread; | |
m_selectThread = 0; | |
delete m_completeDeferredAOs; | |
delete m_wakeUpAO; | |
if (m_activeScheduler) { | |
delete m_activeScheduler; | |
} | |
} | |
bool QEventDispatcherSymbian::processEvents ( QEventLoop::ProcessEventsFlags flags ) | |
{ | |
bool handledAnyEvent = false; | |
bool oldNoSocketEventsValue = m_noSocketEvents; | |
bool oldInsideTimerEventValue = m_insideTimerEvent; | |
m_insideTimerEvent = false; | |
QT_TRY { | |
Q_D(QAbstractEventDispatcher); | |
// It is safe if this counter overflows. The main importance is that each | |
// iteration count is different from the last. | |
m_iterationCount++; | |
RThread &thread = d->threadData->symbian_thread_handle; | |
bool block; | |
if (flags & QEventLoop::WaitForMoreEvents) { | |
block = true; | |
emit aboutToBlock(); | |
} else { | |
block = false; | |
} | |
if (flags & QEventLoop::ExcludeSocketNotifiers) { | |
m_noSocketEvents = true; | |
} else { | |
m_noSocketEvents = false; | |
handledAnyEvent = sendDeferredSocketEvents(); | |
} | |
bool handledSymbianEvent = false; | |
m_interrupt = false; | |
#ifdef QT_SYMBIAN_PRIORITY_DROP | |
QTime eventTimer; | |
#endif | |
while (1) { | |
if (block) { | |
// This is where Qt will spend most of its time. | |
CActiveScheduler::Current()->WaitForAnyRequest(); | |
} else { | |
if (thread.RequestCount() == 0) { | |
break; | |
} | |
// This one should return without delay. | |
CActiveScheduler::Current()->WaitForAnyRequest(); | |
} | |
#ifdef QT_SYMBIAN_PRIORITY_DROP | |
if (idleDetectorThread()->hasRun()) { | |
if (m_delay > baseDelay) | |
m_delay -= baseDelay; | |
m_lastIdleRequestTimer.start(); | |
idleDetectorThread()->kick(); | |
} else if (m_lastIdleRequestTimer.elapsed() > maxBusyTime) { | |
User::AfterHighRes(m_delay); | |
// allow delay to be up to 1/4 of execution time | |
if (!idleDetectorThread()->hasRun() && m_delay*3 < m_avgEventTime) | |
m_delay += baseDelay; | |
} | |
eventTimer.start(); | |
#endif | |
TInt error; | |
handledSymbianEvent = CActiveScheduler::RunIfReady(error, KMinTInt); | |
if (error) { | |
qWarning("CActiveScheduler::RunIfReady() returned error: %i\n", error); | |
CActiveScheduler::Current()->Error(error); | |
} | |
#ifdef QT_SYMBIAN_PRIORITY_DROP | |
int eventDur = eventTimer.elapsed()*1000; | |
// average is calcualted as a 5% decaying exponential average | |
m_avgEventTime = (m_avgEventTime * 95 + eventDur * 5) / 100; | |
#endif | |
if (!handledSymbianEvent) { | |
qFatal("QEventDispatcherSymbian::processEvents(): Caught Symbian stray signal"); | |
} | |
handledAnyEvent = true; | |
if (m_interrupt) { | |
break; | |
} | |
block = false; | |
}; | |
emit awake(); | |
} QT_CATCH (const std::exception& ex) { | |
#ifndef QT_NO_EXCEPTIONS | |
CActiveScheduler::Current()->Error(qt_symbian_exception2Error(ex)); | |
#endif | |
} | |
m_noSocketEvents = oldNoSocketEventsValue; | |
m_insideTimerEvent = oldInsideTimerEventValue; | |
return handledAnyEvent; | |
} | |
void QEventDispatcherSymbian::timerFired(int timerId) | |
{ | |
QHash<int, SymbianTimerInfoPtr>::iterator i = m_timerList.find(timerId); | |
if (i == m_timerList.end()) { | |
// The timer has been deleted. Ignore this event. | |
return; | |
} | |
SymbianTimerInfoPtr timerInfo = *i; | |
// Prevent infinite timer recursion. | |
if (timerInfo->inTimerEvent) { | |
return; | |
} | |
timerInfo->inTimerEvent = true; | |
bool oldInsideTimerEventValue = m_insideTimerEvent; | |
m_insideTimerEvent = true; | |
QTimerEvent event(timerInfo->timerId); | |
QCoreApplication::sendEvent(timerInfo->receiver, &event); | |
m_insideTimerEvent = oldInsideTimerEventValue; | |
timerInfo->inTimerEvent = false; | |
return; | |
} | |
void QEventDispatcherSymbian::socketFired(QSocketActiveObject *socketAO) | |
{ | |
if (m_noSocketEvents) { | |
m_deferredSocketEvents.append(socketAO); | |
return; | |
} | |
QEvent e(QEvent::SockAct); | |
socketAO->m_inSocketEvent = true; | |
QCoreApplication::sendEvent(socketAO->m_notifier, &e); | |
socketAO->m_inSocketEvent = false; | |
if (socketAO->m_deleteLater) { | |
delete socketAO; | |
} else { | |
socketAO->iStatus = KRequestPending; | |
socketAO->SetActive(); | |
reactivateSocketNotifier(socketAO->m_notifier); | |
} | |
} | |
void QEventDispatcherSymbian::wakeUpWasCalled() | |
{ | |
// The reactivation should happen in RunL, right before the call to this function. | |
// This is because m_wakeUpDone is the "signal" that the object can be completed | |
// once more. | |
// Also, by dispatching the posted events after resetting m_wakeUpDone, we guarantee | |
// that no posted event notification will be lost. If we did it the other way | |
// around, it would be possible for another thread to post an event right after | |
// the sendPostedEvents was done, but before the object was ready to be completed | |
// again. This could deadlock the application if there are no other posted events. | |
m_wakeUpDone.fetchAndStoreOrdered(0); | |
sendPostedEvents(); | |
} | |
void QEventDispatcherSymbian::interrupt() | |
{ | |
m_interrupt = true; | |
wakeUp(); | |
} | |
void QEventDispatcherSymbian::wakeUp() | |
{ | |
Q_D(QAbstractEventDispatcher); | |
if (m_wakeUpAO && m_wakeUpDone.testAndSetAcquire(0, 1)) { | |
TRequestStatus *status = &m_wakeUpAO->iStatus; | |
QEventDispatcherSymbian::RequestComplete(d->threadData->symbian_thread_handle, status, KErrNone); | |
} | |
} | |
bool QEventDispatcherSymbian::sendPostedEvents() | |
{ | |
Q_D(QAbstractEventDispatcher); | |
// moveToThread calls this and canWait == true -> Events will never get processed | |
// if we check for d->threadData->canWait | |
// | |
// QCoreApplication::postEvent sets canWait = false, but after the object and events | |
// are moved to a new thread, the canWait in new thread is true i.e. not changed to reflect | |
// the flag on old thread. That's why events in a new thread will not get processed. | |
// This migth be actually bug in moveToThread functionality, but because other platforms | |
// do not check canWait in wakeUp (where we essentially are now) - decided to remove it from | |
// here as well. | |
//if (!d->threadData->canWait) { | |
QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData); | |
return true; | |
//} | |
//return false; | |
} | |
inline void QEventDispatcherSymbian::addDeferredActiveObject(QActiveObject *object) | |
{ | |
queueDeferredActiveObjectsCompletion(); | |
m_deferredActiveObjects.append(object); | |
} | |
inline void QEventDispatcherSymbian::removeDeferredActiveObject(QActiveObject *object) | |
{ | |
m_deferredActiveObjects.removeAll(object); | |
} | |
void QEventDispatcherSymbian::queueDeferredActiveObjectsCompletion() | |
{ | |
m_completeDeferredAOs->complete(); | |
} | |
void QEventDispatcherSymbian::reactivateDeferredActiveObjects() | |
{ | |
while (!m_deferredActiveObjects.isEmpty()) { | |
QActiveObject *object = m_deferredActiveObjects.takeFirst(); | |
object->reactivateAndComplete(); | |
} | |
// We do this because we want to return from processEvents. This is because | |
// each invocation of processEvents should only run each active object once. | |
// The active scheduler should run them continously, however. | |
m_interrupt = true; | |
} | |
bool QEventDispatcherSymbian::sendDeferredSocketEvents() | |
{ | |
bool sentAnyEvents = false; | |
while (!m_deferredSocketEvents.isEmpty()) { | |
sentAnyEvents = true; | |
socketFired(m_deferredSocketEvents.takeFirst()); | |
} | |
return sentAnyEvents; | |
} | |
void QEventDispatcherSymbian::flush() | |
{ | |
} | |
bool QEventDispatcherSymbian::hasPendingEvents() | |
{ | |
Q_D(QAbstractEventDispatcher); | |
return (d->threadData->symbian_thread_handle.RequestCount() != 0 | |
|| !d->threadData->canWait || !m_deferredSocketEvents.isEmpty()); | |
} | |
void QEventDispatcherSymbian::registerSocketNotifier ( QSocketNotifier * notifier ) | |
{ | |
QSocketActiveObject *socketAO = q_check_ptr(new QSocketActiveObject(this, notifier)); | |
m_notifiers.insert(notifier, socketAO); | |
selectThread().requestSocketEvents(notifier, &socketAO->iStatus); | |
} | |
void QEventDispatcherSymbian::unregisterSocketNotifier ( QSocketNotifier * notifier ) | |
{ | |
if (m_selectThread) | |
m_selectThread->cancelSocketEvents(notifier); | |
if (m_notifiers.contains(notifier)) { | |
QSocketActiveObject *sockObj = *m_notifiers.find(notifier); | |
m_deferredSocketEvents.removeAll(sockObj); | |
sockObj->deleteLater(); | |
m_notifiers.remove(notifier); | |
} | |
} | |
void QEventDispatcherSymbian::reactivateSocketNotifier(QSocketNotifier *notifier) | |
{ | |
selectThread().requestSocketEvents(notifier, &m_notifiers[notifier]->iStatus); | |
} | |
void QEventDispatcherSymbian::registerTimer ( int timerId, int interval, QObject * object ) | |
{ | |
if (interval < 0) { | |
qWarning("Timer interval < 0"); | |
interval = 0; | |
} | |
SymbianTimerInfoPtr timer(new SymbianTimerInfo); | |
timer->timerId = timerId; | |
timer->interval = interval; | |
timer->inTimerEvent = false; | |
timer->receiver = object; | |
timer->dispatcher = this; | |
timer->timerAO = q_check_ptr(new QTimerActiveObject(this, timer.data())); | |
m_timerList.insert(timerId, timer); | |
timer->timerAO->Start(); | |
if (m_insideTimerEvent) | |
// If we are inside a timer event, we need to prevent event starvation | |
// by preventing newly created timers from running in the same event processing | |
// iteration. Do this by calling the maybeQueueForLater() function to "fake" that we have | |
// already run once. This will cause the next run to be added to the deferred | |
// queue instead. | |
timer->timerAO->maybeQueueForLater(); | |
} | |
bool QEventDispatcherSymbian::unregisterTimer ( int timerId ) | |
{ | |
if (!m_timerList.contains(timerId)) { | |
return false; | |
} | |
SymbianTimerInfoPtr timerInfo = m_timerList.take(timerId); | |
if (!QObjectPrivate::get(timerInfo->receiver)->inThreadChangeEvent) | |
QAbstractEventDispatcherPrivate::releaseTimerId(timerId); | |
return true; | |
} | |
bool QEventDispatcherSymbian::unregisterTimers ( QObject * object ) | |
{ | |
if (m_timerList.isEmpty()) | |
return false; | |
bool unregistered = false; | |
for (QHash<int, SymbianTimerInfoPtr>::iterator i = m_timerList.begin(); i != m_timerList.end(); ) { | |
if ((*i)->receiver == object) { | |
i = m_timerList.erase(i); | |
unregistered = true; | |
} else { | |
++i; | |
} | |
} | |
return unregistered; | |
} | |
QList<QEventDispatcherSymbian::TimerInfo> QEventDispatcherSymbian::registeredTimers ( QObject * object ) const | |
{ | |
QList<TimerInfo> list; | |
for (QHash<int, SymbianTimerInfoPtr>::const_iterator i = m_timerList.begin(); i != m_timerList.end(); ++i) { | |
if ((*i)->receiver == object) { | |
list.push_back(TimerInfo((*i)->timerId, (*i)->interval)); | |
} | |
} | |
return list; | |
} | |
/* | |
* This active scheduler class implements a simple report and continue policy, for Symbian OS leaves | |
* or exceptions from Qt that fall back to the scheduler. | |
* It will be used in cases where there is no existing active scheduler installed. | |
* Apps which link to qts60main.lib will have the UI active scheduler installed in the main thread | |
* instead of this one. But this would be used in other threads in the UI. | |
* An app could replace this behaviour by installing an alternative active scheduler. | |
*/ | |
void CQtActiveScheduler::Error(TInt aError) const | |
{ | |
QT_TRY { | |
qWarning("Error from active scheduler %d", aError); | |
} | |
QT_CATCH (const std::bad_alloc&) {} // ignore alloc fails, nothing more can be done | |
} | |
QT_END_NAMESPACE | |
#include "moc_qeventdispatcher_symbian_p.cpp" |