/**************************************************************************** | |
** | |
** 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_glib_p.h" | |
#include "qeventdispatcher_unix_p.h" | |
#include <private/qmutexpool_p.h> | |
#include <private/qthread_p.h> | |
#include "qcoreapplication.h" | |
#include "qsocketnotifier.h" | |
#include <QtCore/qhash.h> | |
#include <QtCore/qlist.h> | |
#include <QtCore/qpair.h> | |
#include <glib.h> | |
QT_BEGIN_NAMESPACE | |
struct GPollFDWithQSocketNotifier | |
{ | |
GPollFD pollfd; | |
QSocketNotifier *socketNotifier; | |
}; | |
struct GSocketNotifierSource | |
{ | |
GSource source; | |
QList<GPollFDWithQSocketNotifier *> pollfds; | |
}; | |
static gboolean socketNotifierSourcePrepare(GSource *, gint *timeout) | |
{ | |
if (timeout) | |
*timeout = -1; | |
return false; | |
} | |
static gboolean socketNotifierSourceCheck(GSource *source) | |
{ | |
GSocketNotifierSource *src = reinterpret_cast<GSocketNotifierSource *>(source); | |
bool pending = false; | |
for (int i = 0; !pending && i < src->pollfds.count(); ++i) { | |
GPollFDWithQSocketNotifier *p = src->pollfds.at(i); | |
if (p->pollfd.revents & G_IO_NVAL) { | |
// disable the invalid socket notifier | |
static const char *t[] = { "Read", "Write", "Exception" }; | |
qWarning("QSocketNotifier: Invalid socket %d and type '%s', disabling...", | |
p->pollfd.fd, t[int(p->socketNotifier->type())]); | |
// ### note, modifies src->pollfds! | |
p->socketNotifier->setEnabled(false); | |
} | |
pending = ((p->pollfd.revents & p->pollfd.events) != 0); | |
} | |
return pending; | |
} | |
static gboolean socketNotifierSourceDispatch(GSource *source, GSourceFunc, gpointer) | |
{ | |
QEvent event(QEvent::SockAct); | |
GSocketNotifierSource *src = reinterpret_cast<GSocketNotifierSource *>(source); | |
for (int i = 0; i < src->pollfds.count(); ++i) { | |
GPollFDWithQSocketNotifier *p = src->pollfds.at(i); | |
if ((p->pollfd.revents & p->pollfd.events) != 0) | |
QCoreApplication::sendEvent(p->socketNotifier, &event); | |
} | |
return true; // ??? don't remove, right? | |
} | |
static GSourceFuncs socketNotifierSourceFuncs = { | |
socketNotifierSourcePrepare, | |
socketNotifierSourceCheck, | |
socketNotifierSourceDispatch, | |
NULL, | |
NULL, | |
NULL | |
}; | |
struct GTimerSource | |
{ | |
GSource source; | |
QTimerInfoList timerList; | |
QEventLoop::ProcessEventsFlags processEventsFlags; | |
bool runWithIdlePriority; | |
}; | |
static gboolean timerSourcePrepareHelper(GTimerSource *src, gint *timeout) | |
{ | |
timeval tv = { 0l, 0l }; | |
if (!(src->processEventsFlags & QEventLoop::X11ExcludeTimers) && src->timerList.timerWait(tv)) | |
*timeout = (tv.tv_sec * 1000) + (tv.tv_usec / 1000); | |
else | |
*timeout = -1; | |
return (*timeout == 0); | |
} | |
static gboolean timerSourceCheckHelper(GTimerSource *src) | |
{ | |
if (src->timerList.isEmpty() | |
|| (src->processEventsFlags & QEventLoop::X11ExcludeTimers)) | |
return false; | |
if (src->timerList.updateCurrentTime() < src->timerList.first()->timeout) | |
return false; | |
return true; | |
} | |
static gboolean timerSourcePrepare(GSource *source, gint *timeout) | |
{ | |
gint dummy; | |
if (!timeout) | |
timeout = &dummy; | |
GTimerSource *src = reinterpret_cast<GTimerSource *>(source); | |
if (src->runWithIdlePriority) { | |
if (timeout) | |
*timeout = -1; | |
return false; | |
} | |
return timerSourcePrepareHelper(src, timeout); | |
} | |
static gboolean timerSourceCheck(GSource *source) | |
{ | |
GTimerSource *src = reinterpret_cast<GTimerSource *>(source); | |
if (src->runWithIdlePriority) | |
return false; | |
return timerSourceCheckHelper(src); | |
} | |
static gboolean timerSourceDispatch(GSource *source, GSourceFunc, gpointer) | |
{ | |
GTimerSource *timerSource = reinterpret_cast<GTimerSource *>(source); | |
timerSource->runWithIdlePriority = true; | |
(void) timerSource->timerList.activateTimers(); | |
return true; // ??? don't remove, right again? | |
} | |
static GSourceFuncs timerSourceFuncs = { | |
timerSourcePrepare, | |
timerSourceCheck, | |
timerSourceDispatch, | |
NULL, | |
NULL, | |
NULL | |
}; | |
struct GIdleTimerSource | |
{ | |
GSource source; | |
GTimerSource *timerSource; | |
}; | |
static gboolean idleTimerSourcePrepare(GSource *source, gint *timeout) | |
{ | |
GIdleTimerSource *idleTimerSource = reinterpret_cast<GIdleTimerSource *>(source); | |
GTimerSource *timerSource = idleTimerSource->timerSource; | |
if (!timerSource->runWithIdlePriority) { | |
// Yield to the normal priority timer source | |
if (timeout) | |
*timeout = -1; | |
return false; | |
} | |
return timerSourcePrepareHelper(timerSource, timeout); | |
} | |
static gboolean idleTimerSourceCheck(GSource *source) | |
{ | |
GIdleTimerSource *idleTimerSource = reinterpret_cast<GIdleTimerSource *>(source); | |
GTimerSource *timerSource = idleTimerSource->timerSource; | |
if (!timerSource->runWithIdlePriority) { | |
// Yield to the normal priority timer source | |
return false; | |
} | |
return timerSourceCheckHelper(timerSource); | |
} | |
static gboolean idleTimerSourceDispatch(GSource *source, GSourceFunc, gpointer) | |
{ | |
GTimerSource *timerSource = reinterpret_cast<GIdleTimerSource *>(source)->timerSource; | |
(void) timerSourceDispatch(&timerSource->source, 0, 0); | |
return true; | |
} | |
static GSourceFuncs idleTimerSourceFuncs = { | |
idleTimerSourcePrepare, | |
idleTimerSourceCheck, | |
idleTimerSourceDispatch, | |
NULL, | |
NULL, | |
NULL | |
}; | |
struct GPostEventSource | |
{ | |
GSource source; | |
QAtomicInt serialNumber; | |
int lastSerialNumber; | |
QEventDispatcherGlibPrivate *d; | |
}; | |
static gboolean postEventSourcePrepare(GSource *s, gint *timeout) | |
{ | |
QThreadData *data = QThreadData::current(); | |
if (!data) | |
return false; | |
gint dummy; | |
if (!timeout) | |
timeout = &dummy; | |
*timeout = data->canWait ? -1 : 0; | |
GPostEventSource *source = reinterpret_cast<GPostEventSource *>(s); | |
return (!data->canWait | |
|| (source->serialNumber != source->lastSerialNumber)); | |
} | |
static gboolean postEventSourceCheck(GSource *source) | |
{ | |
return postEventSourcePrepare(source, 0); | |
} | |
static gboolean postEventSourceDispatch(GSource *s, GSourceFunc, gpointer) | |
{ | |
GPostEventSource *source = reinterpret_cast<GPostEventSource *>(s); | |
source->lastSerialNumber = source->serialNumber; | |
QCoreApplication::sendPostedEvents(); | |
source->d->runTimersOnceWithNormalPriority(); | |
return true; // i dunno, george... | |
} | |
static GSourceFuncs postEventSourceFuncs = { | |
postEventSourcePrepare, | |
postEventSourceCheck, | |
postEventSourceDispatch, | |
NULL, | |
NULL, | |
NULL | |
}; | |
QEventDispatcherGlibPrivate::QEventDispatcherGlibPrivate(GMainContext *context) | |
: mainContext(context) | |
{ | |
if (qgetenv("QT_NO_THREADED_GLIB").isEmpty()) { | |
static int dummyValue = 0; // only used for its address | |
QMutexLocker locker(QMutexPool::instance()->get(&dummyValue)); | |
if (!g_thread_supported()) | |
g_thread_init(NULL); | |
} | |
if (mainContext) { | |
g_main_context_ref(mainContext); | |
} else { | |
QCoreApplication *app = QCoreApplication::instance(); | |
if (app && QThread::currentThread() == app->thread()) { | |
mainContext = g_main_context_default(); | |
g_main_context_ref(mainContext); | |
} else { | |
mainContext = g_main_context_new(); | |
} | |
} | |
#if GLIB_CHECK_VERSION (2, 22, 0) | |
g_main_context_push_thread_default (mainContext); | |
#endif | |
// setup post event source | |
postEventSource = reinterpret_cast<GPostEventSource *>(g_source_new(&postEventSourceFuncs, | |
sizeof(GPostEventSource))); | |
postEventSource->serialNumber = 1; | |
postEventSource->d = this; | |
g_source_set_can_recurse(&postEventSource->source, true); | |
g_source_attach(&postEventSource->source, mainContext); | |
// setup socketNotifierSource | |
socketNotifierSource = | |
reinterpret_cast<GSocketNotifierSource *>(g_source_new(&socketNotifierSourceFuncs, | |
sizeof(GSocketNotifierSource))); | |
(void) new (&socketNotifierSource->pollfds) QList<GPollFDWithQSocketNotifier *>(); | |
g_source_set_can_recurse(&socketNotifierSource->source, true); | |
g_source_attach(&socketNotifierSource->source, mainContext); | |
// setup normal and idle timer sources | |
timerSource = reinterpret_cast<GTimerSource *>(g_source_new(&timerSourceFuncs, | |
sizeof(GTimerSource))); | |
(void) new (&timerSource->timerList) QTimerInfoList(); | |
timerSource->processEventsFlags = QEventLoop::AllEvents; | |
timerSource->runWithIdlePriority = false; | |
g_source_set_can_recurse(&timerSource->source, true); | |
g_source_attach(&timerSource->source, mainContext); | |
idleTimerSource = reinterpret_cast<GIdleTimerSource *>(g_source_new(&idleTimerSourceFuncs, | |
sizeof(GIdleTimerSource))); | |
idleTimerSource->timerSource = timerSource; | |
g_source_set_can_recurse(&idleTimerSource->source, true); | |
g_source_set_priority(&idleTimerSource->source, G_PRIORITY_DEFAULT_IDLE); | |
g_source_attach(&idleTimerSource->source, mainContext); | |
} | |
void QEventDispatcherGlibPrivate::runTimersOnceWithNormalPriority() | |
{ | |
timerSource->runWithIdlePriority = false; | |
} | |
QEventDispatcherGlib::QEventDispatcherGlib(QObject *parent) | |
: QAbstractEventDispatcher(*(new QEventDispatcherGlibPrivate), parent) | |
{ | |
} | |
QEventDispatcherGlib::QEventDispatcherGlib(GMainContext *mainContext, QObject *parent) | |
: QAbstractEventDispatcher(*(new QEventDispatcherGlibPrivate(mainContext)), parent) | |
{ } | |
QEventDispatcherGlib::~QEventDispatcherGlib() | |
{ | |
Q_D(QEventDispatcherGlib); | |
// destroy all timer sources | |
qDeleteAll(d->timerSource->timerList); | |
d->timerSource->timerList.~QTimerInfoList(); | |
g_source_destroy(&d->timerSource->source); | |
g_source_unref(&d->timerSource->source); | |
d->timerSource = 0; | |
g_source_destroy(&d->idleTimerSource->source); | |
g_source_unref(&d->idleTimerSource->source); | |
d->idleTimerSource = 0; | |
// destroy socket notifier source | |
for (int i = 0; i < d->socketNotifierSource->pollfds.count(); ++i) { | |
GPollFDWithQSocketNotifier *p = d->socketNotifierSource->pollfds[i]; | |
g_source_remove_poll(&d->socketNotifierSource->source, &p->pollfd); | |
delete p; | |
} | |
d->socketNotifierSource->pollfds.~QList<GPollFDWithQSocketNotifier *>(); | |
g_source_destroy(&d->socketNotifierSource->source); | |
g_source_unref(&d->socketNotifierSource->source); | |
d->socketNotifierSource = 0; | |
// destroy post event source | |
g_source_destroy(&d->postEventSource->source); | |
g_source_unref(&d->postEventSource->source); | |
d->postEventSource = 0; | |
Q_ASSERT(d->mainContext != 0); | |
#if GLIB_CHECK_VERSION (2, 22, 0) | |
g_main_context_pop_thread_default (d->mainContext); | |
#endif | |
g_main_context_unref(d->mainContext); | |
d->mainContext = 0; | |
} | |
bool QEventDispatcherGlib::processEvents(QEventLoop::ProcessEventsFlags flags) | |
{ | |
Q_D(QEventDispatcherGlib); | |
const bool canWait = (flags & QEventLoop::WaitForMoreEvents); | |
if (canWait) | |
emit aboutToBlock(); | |
else | |
emit awake(); | |
// tell postEventSourcePrepare() and timerSource about any new flags | |
QEventLoop::ProcessEventsFlags savedFlags = d->timerSource->processEventsFlags; | |
d->timerSource->processEventsFlags = flags; | |
if (!(flags & QEventLoop::EventLoopExec)) { | |
// force timers to be sent at normal priority | |
d->timerSource->runWithIdlePriority = false; | |
} | |
bool result = g_main_context_iteration(d->mainContext, canWait); | |
while (!result && canWait) | |
result = g_main_context_iteration(d->mainContext, canWait); | |
d->timerSource->processEventsFlags = savedFlags; | |
if (canWait) | |
emit awake(); | |
return result; | |
} | |
bool QEventDispatcherGlib::hasPendingEvents() | |
{ | |
Q_D(QEventDispatcherGlib); | |
return g_main_context_pending(d->mainContext); | |
} | |
void QEventDispatcherGlib::registerSocketNotifier(QSocketNotifier *notifier) | |
{ | |
Q_ASSERT(notifier); | |
int sockfd = notifier->socket(); | |
int type = notifier->type(); | |
#ifndef QT_NO_DEBUG | |
if (sockfd < 0) { | |
qWarning("QSocketNotifier: Internal error"); | |
return; | |
} else if (notifier->thread() != thread() | |
|| thread() != QThread::currentThread()) { | |
qWarning("QSocketNotifier: socket notifiers cannot be enabled from another thread"); | |
return; | |
} | |
#endif | |
Q_D(QEventDispatcherGlib); | |
GPollFDWithQSocketNotifier *p = new GPollFDWithQSocketNotifier; | |
p->pollfd.fd = sockfd; | |
switch (type) { | |
case QSocketNotifier::Read: | |
p->pollfd.events = G_IO_IN | G_IO_HUP | G_IO_ERR; | |
break; | |
case QSocketNotifier::Write: | |
p->pollfd.events = G_IO_OUT | G_IO_ERR; | |
break; | |
case QSocketNotifier::Exception: | |
p->pollfd.events = G_IO_PRI | G_IO_ERR; | |
break; | |
} | |
p->socketNotifier = notifier; | |
d->socketNotifierSource->pollfds.append(p); | |
g_source_add_poll(&d->socketNotifierSource->source, &p->pollfd); | |
} | |
void QEventDispatcherGlib::unregisterSocketNotifier(QSocketNotifier *notifier) | |
{ | |
Q_ASSERT(notifier); | |
#ifndef QT_NO_DEBUG | |
int sockfd = notifier->socket(); | |
if (sockfd < 0) { | |
qWarning("QSocketNotifier: Internal error"); | |
return; | |
} else if (notifier->thread() != thread() | |
|| thread() != QThread::currentThread()) { | |
qWarning("QSocketNotifier: socket notifiers cannot be disabled from another thread"); | |
return; | |
} | |
#endif | |
Q_D(QEventDispatcherGlib); | |
for (int i = 0; i < d->socketNotifierSource->pollfds.count(); ++i) { | |
GPollFDWithQSocketNotifier *p = d->socketNotifierSource->pollfds.at(i); | |
if (p->socketNotifier == notifier) { | |
// found it | |
g_source_remove_poll(&d->socketNotifierSource->source, &p->pollfd); | |
d->socketNotifierSource->pollfds.removeAt(i); | |
delete p; | |
return; | |
} | |
} | |
} | |
void QEventDispatcherGlib::registerTimer(int timerId, int interval, QObject *object) | |
{ | |
#ifndef QT_NO_DEBUG | |
if (timerId < 1 || interval < 0 || !object) { | |
qWarning("QEventDispatcherGlib::registerTimer: invalid arguments"); | |
return; | |
} else if (object->thread() != thread() || thread() != QThread::currentThread()) { | |
qWarning("QObject::startTimer: timers cannot be started from another thread"); | |
return; | |
} | |
#endif | |
Q_D(QEventDispatcherGlib); | |
d->timerSource->timerList.registerTimer(timerId, interval, object); | |
} | |
bool QEventDispatcherGlib::unregisterTimer(int timerId) | |
{ | |
#ifndef QT_NO_DEBUG | |
if (timerId < 1) { | |
qWarning("QEventDispatcherGlib::unregisterTimer: invalid argument"); | |
return false; | |
} else if (thread() != QThread::currentThread()) { | |
qWarning("QObject::killTimer: timers cannot be stopped from another thread"); | |
return false; | |
} | |
#endif | |
Q_D(QEventDispatcherGlib); | |
return d->timerSource->timerList.unregisterTimer(timerId); | |
} | |
bool QEventDispatcherGlib::unregisterTimers(QObject *object) | |
{ | |
#ifndef QT_NO_DEBUG | |
if (!object) { | |
qWarning("QEventDispatcherGlib::unregisterTimers: invalid argument"); | |
return false; | |
} else if (object->thread() != thread() || thread() != QThread::currentThread()) { | |
qWarning("QObject::killTimers: timers cannot be stopped from another thread"); | |
return false; | |
} | |
#endif | |
Q_D(QEventDispatcherGlib); | |
return d->timerSource->timerList.unregisterTimers(object); | |
} | |
QList<QEventDispatcherGlib::TimerInfo> QEventDispatcherGlib::registeredTimers(QObject *object) const | |
{ | |
if (!object) { | |
qWarning("QEventDispatcherUNIX:registeredTimers: invalid argument"); | |
return QList<TimerInfo>(); | |
} | |
Q_D(const QEventDispatcherGlib); | |
return d->timerSource->timerList.registeredTimers(object); | |
} | |
void QEventDispatcherGlib::interrupt() | |
{ | |
wakeUp(); | |
} | |
void QEventDispatcherGlib::wakeUp() | |
{ | |
Q_D(QEventDispatcherGlib); | |
d->postEventSource->serialNumber.ref(); | |
g_main_context_wakeup(d->mainContext); | |
} | |
void QEventDispatcherGlib::flush() | |
{ | |
} | |
bool QEventDispatcherGlib::versionSupported() | |
{ | |
#if !defined(GLIB_MAJOR_VERSION) || !defined(GLIB_MINOR_VERSION) || !defined(GLIB_MICRO_VERSION) | |
return false; | |
#else | |
return ((GLIB_MAJOR_VERSION << 16) + (GLIB_MINOR_VERSION << 8) + GLIB_MICRO_VERSION) >= 0x020301; | |
#endif | |
} | |
QEventDispatcherGlib::QEventDispatcherGlib(QEventDispatcherGlibPrivate &dd, QObject *parent) | |
: QAbstractEventDispatcher(dd, parent) | |
{ | |
} | |
QT_END_NAMESPACE |