Revision control
Copy as Markdown
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default
#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/q26numeric.h>
#include <glib.h>
using namespace std::chrono;
using namespace std::chrono_literals;
QT_BEGIN_NAMESPACE
struct GPollFDWithQSocketNotifier
{
GPollFD pollfd;
QSocketNotifier *socketNotifier;
};
struct GSocketNotifierSource
{
GSource source;
QList<GPollFDWithQSocketNotifier *> pollfds;
qsizetype activeNotifierPos;
};
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 (qsizetype i = 0; !pending && i < src->pollfds.size(); ++i) {
GPollFDWithQSocketNotifier *p = src->pollfds.at(i);
if (p->pollfd.revents & G_IO_NVAL) {
// disable the invalid socket notifier
const char * const t[] = { "Read", "Write", "Exception" };
qWarning("QSocketNotifier: Invalid socket %d and type '%s', disabling...",
p->pollfd.fd, t[p->socketNotifier->type()]);
// ### note, modifies src->pollfds, turning this loop quadratic (QTBUG-143853)!
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 (src->activeNotifierPos = 0; src->activeNotifierPos < src->pollfds.size();
++src->activeNotifierPos) {
GPollFDWithQSocketNotifier *p = src->pollfds.at(src->activeNotifierPos);
if ((p->pollfd.revents & p->pollfd.events) != 0)
QCoreApplication::sendEvent(p->socketNotifier, &event);
}
return true; // ??? don't remove, right?
}
Q_CONSTINIT static GSourceFuncs socketNotifierSourceFuncs = {
socketNotifierSourcePrepare,
socketNotifierSourceCheck,
socketNotifierSourceDispatch,
nullptr,
nullptr,
nullptr
};
struct GTimerSource
{
GSource source;
QTimerInfoList timerList;
QEventLoop::ProcessEventsFlags processEventsFlags;
bool runWithIdlePriority;
};
static gboolean timerSourcePrepareHelper(GTimerSource *src, gint *timeout)
{
if (src->processEventsFlags & QEventLoop::X11ExcludeTimers) {
*timeout = -1;
return true;
}
auto remaining = src->timerList.timerWait().value_or(-1ms);
*timeout = q26::saturating_cast<gint>(ceil<milliseconds>(remaining).count());
return (*timeout == 0);
}
static gboolean timerSourceCheckHelper(GTimerSource *src)
{
if (src->timerList.isEmpty()
|| (src->processEventsFlags & QEventLoop::X11ExcludeTimers))
return false;
return !src->timerList.hasPendingTimers();
}
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?
}
Q_CONSTINIT static GSourceFuncs timerSourceFuncs = {
timerSourcePrepare,
timerSourceCheck,
timerSourceDispatch,
nullptr,
nullptr,
nullptr
};
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, nullptr, nullptr);
return true;
}
Q_CONSTINIT static GSourceFuncs idleTimerSourceFuncs = {
idleTimerSourcePrepare,
idleTimerSourceCheck,
idleTimerSourceDispatch,
nullptr,
nullptr,
nullptr
};
struct GPostEventSource
{
GSource source;
QAtomicInt serialNumber;
int lastSerialNumber;
int pendingSerialNumber;
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, nullptr);
}
static gboolean postEventSourceDispatch(GSource *s, GSourceFunc, gpointer)
{
GPostEventSource *source = reinterpret_cast<GPostEventSource *>(s);
source->pendingSerialNumber = source->serialNumber.loadRelaxed();
QCoreApplication::sendPostedEvents();
source->lastSerialNumber = source->pendingSerialNumber;
source->d->runTimersOnceWithNormalPriority();
return true; // i dunno, george...
}
Q_CONSTINIT static GSourceFuncs postEventSourceFuncs = {
postEventSourcePrepare,
postEventSourceCheck,
postEventSourceDispatch,
nullptr,
nullptr,
nullptr
};
QEventDispatcherGlibPrivate::QEventDispatcherGlibPrivate(GMainContext *context)
: mainContext(context)
{
#if GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION < 32
if (qEnvironmentVariableIsEmpty("QT_NO_THREADED_GLIB")) {
Q_CONSTINIT 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::isMainThread()) {
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
GSource *source = g_source_new(&postEventSourceFuncs, sizeof(GPostEventSource));
g_source_set_name(source, "[Qt] GPostEventSource");
postEventSource = reinterpret_cast<GPostEventSource *>(source);
postEventSource->serialNumber.storeRelaxed(1);
postEventSource->d = this;
g_source_set_can_recurse(&postEventSource->source, true);
g_source_attach(&postEventSource->source, mainContext);
// setup socketNotifierSource
source = g_source_new(&socketNotifierSourceFuncs, sizeof(GSocketNotifierSource));
g_source_set_name(source, "[Qt] GSocketNotifierSource");
socketNotifierSource = reinterpret_cast<GSocketNotifierSource *>(source);
(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
source = g_source_new(&timerSourceFuncs, sizeof(GTimerSource));
g_source_set_name(source, "[Qt] GTimerSource");
timerSource = reinterpret_cast<GTimerSource *>(source);
(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);
source = g_source_new(&idleTimerSourceFuncs, sizeof(GIdleTimerSource));
g_source_set_name(source, "[Qt] GIdleTimerSource");
idleTimerSource = reinterpret_cast<GIdleTimerSource *>(source);
idleTimerSource->timerSource = timerSource;
g_source_set_can_recurse(&idleTimerSource->source, true);
g_source_attach(&idleTimerSource->source, mainContext);
}
QEventDispatcherGlibPrivate::~QEventDispatcherGlibPrivate()
= default;
void QEventDispatcherGlibPrivate::runTimersOnceWithNormalPriority()
{
timerSource->runWithIdlePriority = false;
}
QEventDispatcherGlib::QEventDispatcherGlib(QObject *parent)
: QAbstractEventDispatcherV2(*(new QEventDispatcherGlibPrivate), parent)
{
}
QEventDispatcherGlib::QEventDispatcherGlib(GMainContext *mainContext, QObject *parent)
: QAbstractEventDispatcherV2(*(new QEventDispatcherGlibPrivate(mainContext)), parent)
{ }
QEventDispatcherGlib::~QEventDispatcherGlib()
{
Q_D(QEventDispatcherGlib);
// destroy all timer sources
d->timerSource->timerList.clearTimers();
d->timerSource->timerList.~QTimerInfoList();
g_source_destroy(&d->timerSource->source);
g_source_unref(&d->timerSource->source);
d->timerSource = nullptr;
g_source_destroy(&d->idleTimerSource->source);
g_source_unref(&d->idleTimerSource->source);
d->idleTimerSource = nullptr;
// destroy socket notifier source
for (qsizetype i = 0; i < d->socketNotifierSource->pollfds.size(); ++i) {
GPollFDWithQSocketNotifier *p = d->socketNotifierSource->pollfds.at(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 = nullptr;
// destroy post event source
g_source_destroy(&d->postEventSource->source);
g_source_unref(&d->postEventSource->source);
d->postEventSource = nullptr;
Q_ASSERT(d->mainContext != nullptr);
#if GLIB_CHECK_VERSION (2, 22, 0)
g_main_context_pop_thread_default (d->mainContext);
#endif
g_main_context_unref(d->mainContext);
d->mainContext = nullptr;
}
bool QEventDispatcherGlib::processEvents(QEventLoop::ProcessEventsFlags flags)
{
Q_D(QEventDispatcherGlib);
const bool canWait = flags.testAnyFlag(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;
}
void QEventDispatcherGlib::registerSocketNotifier(QSocketNotifier *notifier)
{
Q_ASSERT(notifier);
int sockfd = int(notifier->socket());
const auto 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_HUP | G_IO_ERR;
break;
case QSocketNotifier::Exception:
p->pollfd.events = G_IO_PRI | G_IO_HUP | 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
if (notifier->socket() < 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 (qsizetype i = 0; i < d->socketNotifierSource->pollfds.size(); ++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;
// Keep a position in the list for the next item.
if (i <= d->socketNotifierSource->activeNotifierPos)
--d->socketNotifierSource->activeNotifierPos;
return;
}
}
}
void QEventDispatcherGlib::registerTimer(Qt::TimerId timerId, Duration interval,
Qt::TimerType timerType, QObject *object)
{
#ifndef QT_NO_DEBUG
if (qToUnderlying(timerId) < 1 || interval < 0ns || !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(Qt::TimerId timerId)
{
#ifndef QT_NO_DEBUG
if (qToUnderlying(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::TimerInfoV2> QEventDispatcherGlib::timersForObject(QObject *object) const
{
#ifndef QT_NO_DEBUG
if (!object) {
qWarning("QEventDispatcherGlib:timersForObject: invalid argument");
return {};
}
#endif
Q_D(const QEventDispatcherGlib);
return d->timerSource->timerList.registeredTimers(object);
}
QEventDispatcherGlib::Duration QEventDispatcherGlib::remainingTime(Qt::TimerId timerId) const
{
#ifndef QT_NO_DEBUG
if (qToUnderlying(timerId) < 1) {
qWarning("QEventDispatcherGlib::remainingTimeTime: invalid argument");
return Duration::min();
}
#endif
Q_D(const QEventDispatcherGlib);
return d->timerSource->timerList.remainingDuration(timerId);
}
void QEventDispatcherGlib::interrupt()
{
wakeUp();
}
void QEventDispatcherGlib::wakeUp()
{
Q_D(QEventDispatcherGlib);
d->postEventSource->serialNumber.ref();
g_main_context_wakeup(d->mainContext);
}
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)
: QAbstractEventDispatcherV2(dd, parent)
{
}
QT_END_NAMESPACE
#include "moc_qeventdispatcher_glib_p.cpp"