blob: d9746ef6e205133e035acae4c54ad065fb569db5 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qeventdispatcher_glib_p.h"
#include "qeventdispatcher_unix_p.h"
#include <private/qthread_p.h>
#include "qcoreapplication.h"
#include "qsocketnotifier.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);
i--;
} else {
pending = 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)
{
timespec tv = { 0l, 0l };
if (!(src->processEventsFlags & QEventLoop::X11ExcludeTimers) && src->timerList.timerWait(tv))
*timeout = (tv.tv_sec * 1000) + ((tv.tv_nsec + 999999) / 1000 / 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.constFirst()->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);
if (timerSource->processEventsFlags & QEventLoop::X11ExcludeTimers)
return true;
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;
const bool canWait = data->canWaitLocked();
*timeout = canWait ? -1 : 0;
GPostEventSource *source = reinterpret_cast<GPostEventSource *>(s);
source->d->wakeUpCalled = source->serialNumber.loadRelaxed() != source->lastSerialNumber;
return !canWait || source->d->wakeUpCalled;
}
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.loadRelaxed();
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 GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION < 32
if (qEnvironmentVariableIsEmpty("QT_NO_THREADED_GLIB")) {
static QBasicMutex mutex;
QMutexLocker locker(&mutex);
if (!g_thread_supported())
g_thread_init(NULL);
}
#endif
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.storeRelaxed(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_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, Qt::TimerType timerType, 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("QEventDispatcherGlib::registerTimer: timers cannot be started from another thread");
return;
}
#endif
Q_D(QEventDispatcherGlib);
d->timerSource->timerList.registerTimer(timerId, interval, timerType, 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("QEventDispatcherGlib::unregisterTimer: 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("QEventDispatcherGlib::unregisterTimers: 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);
}
int QEventDispatcherGlib::remainingTime(int timerId)
{
#ifndef QT_NO_DEBUG
if (timerId < 1) {
qWarning("QEventDispatcherGlib::remainingTimeTime: invalid argument");
return -1;
}
#endif
Q_D(QEventDispatcherGlib);
return d->timerSource->timerList.timerRemainingTime(timerId);
}
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
#include "moc_qeventdispatcher_glib_p.cpp"