blob: 782d83aa92409c650973f43c3539b1bc1e1cab07 [file] [log] [blame]
/****************************************************************************
**
** 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"