wasm: Use new event dispatcher for QtGui
The event dispatcher implementation is now in QtCore, except for the call to QWindowSystemInterface::sendWindowSystemEvents(). Implement QWasmWindow::requestUpdate() using emscripten_request_animation_frame(), instead of the previous registerRequestUpdateCallback() function which now is removed. Pick-to: 6.3 Change-Id: I7a13eb5391d48dba0f2afe4704ef3188b8daa74b Reviewed-by: Lorn Potter <lorn.potter@gmail.com>
This commit is contained in:
parent
e98f5de6e1
commit
23964ce05f
@ -29,197 +29,11 @@
|
||||
|
||||
#include "qwasmeventdispatcher.h"
|
||||
|
||||
#include <QtCore/qcoreapplication.h>
|
||||
#include <QtGui/qpa/qwindowsysteminterface.h>
|
||||
|
||||
#include <emscripten.h>
|
||||
|
||||
#if QT_CONFIG(thread)
|
||||
#if (__EMSCRIPTEN_major__ > 1 || __EMSCRIPTEN_minor__ > 38 || __EMSCRIPTEN_minor__ == 38 && __EMSCRIPTEN_tiny__ >= 22)
|
||||
# define EMSCRIPTEN_HAS_ASYNC_RUN_IN_MAIN_RUNTIME_THREAD
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef EMSCRIPTEN_HAS_ASYNC_RUN_IN_MAIN_RUNTIME_THREAD
|
||||
#include <emscripten/threading.h>
|
||||
#endif
|
||||
|
||||
class QWasmEventDispatcherPrivate : public QEventDispatcherUNIXPrivate
|
||||
// Note: All event dispatcher functionality is implemented in QEventDispatcherWasm
|
||||
// in QtCore, except for processWindowSystemEvents() below which uses API from QtGui.
|
||||
void QWasmEventDispatcher::processWindowSystemEvents(QEventLoop::ProcessEventsFlags flags)
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
QWasmEventDispatcher *g_htmlEventDispatcher;
|
||||
|
||||
QWasmEventDispatcher::QWasmEventDispatcher(QObject *parent)
|
||||
: QUnixEventDispatcherQPA(parent)
|
||||
{
|
||||
|
||||
g_htmlEventDispatcher = this;
|
||||
}
|
||||
|
||||
QWasmEventDispatcher::~QWasmEventDispatcher()
|
||||
{
|
||||
g_htmlEventDispatcher = nullptr;
|
||||
}
|
||||
|
||||
bool QWasmEventDispatcher::registerRequestUpdateCallback(std::function<void(void)> callback)
|
||||
{
|
||||
if (!g_htmlEventDispatcher || !g_htmlEventDispatcher->m_hasMainLoop)
|
||||
return false;
|
||||
|
||||
g_htmlEventDispatcher->m_requestUpdateCallbacks.append(callback);
|
||||
emscripten_resume_main_loop();
|
||||
return true;
|
||||
}
|
||||
|
||||
void QWasmEventDispatcher::maintainTimers()
|
||||
{
|
||||
if (!g_htmlEventDispatcher || !g_htmlEventDispatcher->m_hasMainLoop)
|
||||
return;
|
||||
|
||||
g_htmlEventDispatcher->doMaintainTimers();
|
||||
}
|
||||
|
||||
bool QWasmEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags)
|
||||
{
|
||||
// WaitForMoreEvents is not supported (except for in combination with EventLoopExec below),
|
||||
// and we don't want the unix event dispatcher base class to attempt to wait either.
|
||||
flags &= ~QEventLoop::WaitForMoreEvents;
|
||||
|
||||
// Handle normal processEvents.
|
||||
if (!(flags & QEventLoop::EventLoopExec))
|
||||
return QUnixEventDispatcherQPA::processEvents(flags);
|
||||
|
||||
if (flags & QEventLoop::DialogExec) {
|
||||
qWarning() << "Warning: dialog exec() is not supported on Qt for WebAssembly, please use"
|
||||
<< "show() instead. When using exec() the dialog will show, the user can interact"
|
||||
<< "with it and the appropriate signals will be emitted on close. However, the"
|
||||
<< "exec() call never returns, stack content at the time of the exec() call"
|
||||
<< "is leaked, and the exec() call may interfere with input event processing";
|
||||
|
||||
emscripten_sleep(1); // This call never returns
|
||||
}
|
||||
|
||||
// Handle processEvents from QEventLoop::exec():
|
||||
//
|
||||
// At this point the application has created its root objects on
|
||||
// the stack and has called app.exec() which has called into this
|
||||
// function via QEventLoop.
|
||||
//
|
||||
// The application now expects that exec() will not return until
|
||||
// app exit time. However, the browser expects that we return
|
||||
// control to it periodically, also after initial setup in main().
|
||||
|
||||
// EventLoopExec for nested event loops is not supported.
|
||||
Q_ASSERT(!m_hasMainLoop);
|
||||
m_hasMainLoop = true;
|
||||
|
||||
// Call emscripten_set_main_loop_arg() with a callback which processes
|
||||
// events. Also set simulateInfiniteLoop to true which makes emscripten
|
||||
// return control to the browser without unwinding the C++ stack.
|
||||
auto callback = [](void *eventDispatcher) {
|
||||
QWasmEventDispatcher *that = static_cast<QWasmEventDispatcher *>(eventDispatcher);
|
||||
|
||||
// Save and clear updateRequest callbacks so we can register new ones
|
||||
auto requestUpdateCallbacksCopy = that->m_requestUpdateCallbacks;
|
||||
that->m_requestUpdateCallbacks.clear();
|
||||
|
||||
// Repaint all windows
|
||||
for (auto callback : qAsConst(requestUpdateCallbacksCopy))
|
||||
callback();
|
||||
|
||||
// Pause main loop if no updates were requested. Updates will be
|
||||
// restarted again by registerRequestUpdateCallback().
|
||||
if (that->m_requestUpdateCallbacks.isEmpty())
|
||||
emscripten_pause_main_loop();
|
||||
|
||||
that->doMaintainTimers();
|
||||
};
|
||||
int fps = 0; // update using requestAnimationFrame
|
||||
int simulateInfiniteLoop = 1;
|
||||
emscripten_set_main_loop_arg(callback, this, fps, simulateInfiniteLoop);
|
||||
|
||||
// Note: the above call never returns, not even at app exit
|
||||
return false;
|
||||
}
|
||||
|
||||
void QWasmEventDispatcher::doMaintainTimers()
|
||||
{
|
||||
Q_D(QWasmEventDispatcher);
|
||||
|
||||
// This function schedules native timers in order to wake up to
|
||||
// process events and activate Qt timers. This is done using the
|
||||
// emscripten_async_call() API which schedules a new timer.
|
||||
// There is unfortunately no way to cancel or update a current
|
||||
// native timer.
|
||||
|
||||
// Schedule a zero-timer to continue processing any pending events.
|
||||
extern uint qGlobalPostedEventsCount(); // from qapplication.cpp
|
||||
if (!m_hasZeroTimer && (qGlobalPostedEventsCount() || QWindowSystemInterface::windowSystemEventsQueued())) {
|
||||
auto callback = [](void *eventDispatcher) {
|
||||
QWasmEventDispatcher *that = static_cast<QWasmEventDispatcher *>(eventDispatcher);
|
||||
that->m_hasZeroTimer = false;
|
||||
that->QUnixEventDispatcherQPA::processEvents(QEventLoop::AllEvents);
|
||||
|
||||
// Processing events may have posted new events or created new timers
|
||||
that->doMaintainTimers();
|
||||
};
|
||||
|
||||
emscripten_async_call(callback, this, 0);
|
||||
m_hasZeroTimer = true;
|
||||
return;
|
||||
}
|
||||
|
||||
auto timespecToNanosec = [](timespec ts) -> uint64_t { return ts.tv_sec * 1000 + ts.tv_nsec / (1000 * 1000); };
|
||||
|
||||
// Get current time and time-to-first-Qt-timer. This polls for system
|
||||
// time, and we use this time as the current time for the duration of this call.
|
||||
timespec toWait;
|
||||
bool hasTimers = d->timerList.timerWait(toWait);
|
||||
if (!hasTimers)
|
||||
return; // no timer needed
|
||||
|
||||
uint64_t currentTime = timespecToNanosec(d->timerList.currentTime);
|
||||
uint64_t toWaitDuration = timespecToNanosec(toWait);
|
||||
|
||||
// The currently scheduled timer target is stored in m_currentTargetTime.
|
||||
// We can re-use it if the new target is equivalent or later.
|
||||
uint64_t newTargetTime = currentTime + toWaitDuration;
|
||||
if (newTargetTime >= m_currentTargetTime)
|
||||
return; // existing timer is good
|
||||
|
||||
// Schedule a native timer with a callback which processes events (and timers)
|
||||
auto callback = [](void *eventDispatcher) {
|
||||
QWasmEventDispatcher *that = static_cast<QWasmEventDispatcher *>(eventDispatcher);
|
||||
that->m_currentTargetTime = std::numeric_limits<uint64_t>::max();
|
||||
that->QUnixEventDispatcherQPA::processEvents(QEventLoop::AllEvents);
|
||||
|
||||
// Processing events may have posted new events or created new timers
|
||||
that->doMaintainTimers();
|
||||
};
|
||||
emscripten_async_call(callback, this, toWaitDuration);
|
||||
m_currentTargetTime = newTargetTime;
|
||||
}
|
||||
|
||||
void QWasmEventDispatcher::wakeUp()
|
||||
{
|
||||
#ifdef EMSCRIPTEN_HAS_ASYNC_RUN_IN_MAIN_RUNTIME_THREAD
|
||||
if (!emscripten_is_main_runtime_thread() && m_hasMainLoop) {
|
||||
|
||||
// Make two-step async call to mainThreadWakeUp in order to make sure the
|
||||
// call is made at a point where the main thread is idle.
|
||||
void (*intermediate)(void *) = [](void *eventdispatcher){
|
||||
emscripten_async_call(QWasmEventDispatcher::mainThreadWakeUp, eventdispatcher, 0);
|
||||
};
|
||||
emscripten_async_run_in_main_runtime_thread_(EM_FUNC_SIG_VI, (void *)intermediate, this);
|
||||
}
|
||||
#endif
|
||||
QEventDispatcherUNIX::wakeUp();
|
||||
}
|
||||
|
||||
void QWasmEventDispatcher::mainThreadWakeUp(void *eventDispatcher)
|
||||
{
|
||||
emscripten_resume_main_loop(); // Service possible requestUpdate Calls
|
||||
static_cast<QWasmEventDispatcher *>(eventDispatcher)->processEvents(QEventLoop::AllEvents);
|
||||
QWindowSystemInterface::sendWindowSystemEvents(flags);
|
||||
}
|
||||
|
@ -30,35 +30,14 @@
|
||||
#ifndef QWASMEVENTDISPATCHER_H
|
||||
#define QWASMEVENTDISPATCHER_H
|
||||
|
||||
#include <QtCore/qhash.h>
|
||||
#include <QtCore/qloggingcategory.h>
|
||||
#include <QtGui/private/qunixeventdispatcher_qpa_p.h>
|
||||
#include <QtCore/private/qeventdispatcher_wasm_p.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class QWasmEventDispatcherPrivate;
|
||||
|
||||
class QWasmEventDispatcher : public QUnixEventDispatcherQPA
|
||||
class QWasmEventDispatcher : public QEventDispatcherWasm
|
||||
{
|
||||
Q_DECLARE_PRIVATE(QWasmEventDispatcher)
|
||||
public:
|
||||
explicit QWasmEventDispatcher(QObject *parent = nullptr);
|
||||
~QWasmEventDispatcher();
|
||||
|
||||
static bool registerRequestUpdateCallback(std::function<void(void)> callback);
|
||||
static void maintainTimers();
|
||||
|
||||
protected:
|
||||
bool processEvents(QEventLoop::ProcessEventsFlags flags) override;
|
||||
void doMaintainTimers();
|
||||
void wakeUp() override;
|
||||
static void mainThreadWakeUp(void *eventDispatcher);
|
||||
|
||||
private:
|
||||
bool m_hasMainLoop = false;
|
||||
bool m_hasZeroTimer = false;
|
||||
uint64_t m_currentTargetTime = std::numeric_limits<uint64_t>::max();
|
||||
QList<std::function<void(void)>> m_requestUpdateCallbacks;
|
||||
void processWindowSystemEvents(QEventLoop::ProcessEventsFlags flags) override;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
@ -371,7 +371,6 @@ int QWasmEventTranslator::mouse_cb(int eventType, const EmscriptenMouseEvent *mo
|
||||
{
|
||||
QWasmEventTranslator *translator = (QWasmEventTranslator*)userData;
|
||||
bool accepted = translator->processMouse(eventType,mouseEvent);
|
||||
QWasmEventDispatcher::maintainTimers();
|
||||
return accepted;
|
||||
}
|
||||
|
||||
@ -592,7 +591,6 @@ int QWasmEventTranslator::wheel_cb(int eventType, const EmscriptenWheelEvent *wh
|
||||
|
||||
bool accepted = QWindowSystemInterface::handleWheelEvent(window2, getTimestamp(), localPoint,
|
||||
globalPoint, QPoint(), pixelDelta, modifiers);
|
||||
QWasmEventDispatcher::maintainTimers();
|
||||
return static_cast<int>(accepted);
|
||||
}
|
||||
|
||||
@ -676,8 +674,6 @@ int QWasmEventTranslator::handleTouch(int eventType, const EmscriptenTouchEvent
|
||||
if (eventType == EMSCRIPTEN_EVENT_TOUCHCANCEL)
|
||||
accepted = QWindowSystemInterface::handleTouchCancelEvent(window2, getTimestamp(), touchDevice, keyModifier);
|
||||
|
||||
QWasmEventDispatcher::maintainTimers();
|
||||
|
||||
return static_cast<int>(accepted);
|
||||
}
|
||||
|
||||
@ -873,8 +869,6 @@ bool QWasmEventTranslator::processKeyboard(int eventType, const EmscriptenKeyboa
|
||||
accepted = false; // continue on to event
|
||||
}
|
||||
|
||||
QWasmEventDispatcher::maintainTimers();
|
||||
|
||||
return accepted;
|
||||
}
|
||||
|
||||
|
@ -63,6 +63,8 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmCompositor *compositor, QWasmBackingSt
|
||||
QWasmWindow::~QWasmWindow()
|
||||
{
|
||||
m_compositor->removeWindow(this);
|
||||
if (m_requestAnimationFrameId > -1)
|
||||
emscripten_cancel_animation_frame(m_requestAnimationFrameId);
|
||||
}
|
||||
|
||||
void QWasmWindow::destroy()
|
||||
@ -395,16 +397,14 @@ qreal QWasmWindow::devicePixelRatio() const
|
||||
|
||||
void QWasmWindow::requestUpdate()
|
||||
{
|
||||
QPointer<QWindow> windowPointer(window());
|
||||
bool registered = QWasmEventDispatcher::registerRequestUpdateCallback([=](){
|
||||
if (windowPointer.isNull())
|
||||
return;
|
||||
|
||||
deliverUpdateRequest();
|
||||
});
|
||||
|
||||
if (!registered)
|
||||
QPlatformWindow::requestUpdate();
|
||||
static auto frame = [](double time, void *context) -> int {
|
||||
Q_UNUSED(time);
|
||||
QWasmWindow *window = static_cast<QWasmWindow *>(context);
|
||||
window->m_requestAnimationFrameId = -1;
|
||||
window->deliverUpdateRequest();
|
||||
return 0;
|
||||
};
|
||||
m_requestAnimationFrameId = emscripten_request_animation_frame(frame, this);
|
||||
}
|
||||
|
||||
bool QWasmWindow::hasTitleBar() const
|
||||
|
@ -122,6 +122,7 @@ protected:
|
||||
WId m_winid = 0;
|
||||
bool m_hasTitle = false;
|
||||
bool m_needsCompositor = false;
|
||||
long m_requestAnimationFrameId = -1;
|
||||
friend class QWasmCompositor;
|
||||
friend class QWasmEventTranslator;
|
||||
bool windowIsPopupType(Qt::WindowFlags flags) const;
|
||||
|
Loading…
x
Reference in New Issue
Block a user