123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032 | // Copyright (C) 2018 The Qt Company Ltd.// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only#include <qpa/qwindowsysteminterface.h>#include <private/qguiapplication_p.h>#include <QtCore/qfile.h>#include <QtGui/private/qwindow_p.h>#include <QtGui/private/qhighdpiscaling_p.h>#include <private/qpixmapcache_p.h>#include <QtGui/qopenglfunctions.h>#include <QBuffer>#include"qwasmbase64iconstore.h"#include"qwasmdom.h"#if QT_CONFIG(clipboard)#include"qwasmclipboard.h"#endif#include"qwasmintegration.h"#include"qwasmkeytranslator.h"#include"qwasmwindow.h"#include"qwasmscreen.h"#include"qwasmcompositor.h"#include"qwasmevent.h"#include"qwasmeventdispatcher.h"#include"qwasmaccessibility.h"#if QT_CONFIG(draganddrop)#include"qwasmdrag.h"#endif#include <iostream>#include <sstream>#include <emscripten/val.h>#include <QtCore/private/qstdweb_p.h>#include <QKeySequence> QT_BEGIN_NAMESPACE namespace{QWasmWindowStack::PositionPreference positionPreferenceFromWindowFlags(Qt::WindowFlags flags){if(flags.testFlag(Qt::WindowStaysOnTopHint))returnQWasmWindowStack::PositionPreference::StayOnTop;if(flags.testFlag(Qt::WindowStaysOnBottomHint))returnQWasmWindowStack::PositionPreference::StayOnBottom;returnQWasmWindowStack::PositionPreference::Regular;}}// namespace Q_GUI_EXPORT intqt_defaultDpiX();QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport, QWasmCompositor *compositor, QWasmBackingStore *backingStore, WId nativeHandle):QPlatformWindow(w),m_compositor(compositor),m_backingStore(backingStore),m_deadKeySupport(deadKeySupport),m_document(dom::document()),m_decoratedWindow(m_document.call<emscripten::val>("createElement",emscripten::val("div"))),m_window(m_document.call<emscripten::val>("createElement",emscripten::val("div"))),m_windowInput(m_document.call<emscripten::val>("createElement",emscripten::val("div"))),m_a11yContainer(m_document.call<emscripten::val>("createElement",emscripten::val("div"))),m_canvas(m_document.call<emscripten::val>("createElement",emscripten::val("canvas"))){ m_decoratedWindow.set("className","qt-decorated-window"); m_decoratedWindow["style"].set("display",std::string("none")); m_nonClientArea =std::make_unique<NonClientArea>(this, m_decoratedWindow); m_nonClientArea->titleBar()->setTitle(window()->title());// If we are wrapping a foregin window, a.k.a. a native html element then that element becomes// the m_window element. In this case setting up event handlers and accessibility etc is not// needed since that is (presumably) handled by the native html element.//// The WId is an emscripten::val *, owned by QWindow user code. We dereference and make// a copy of the val here and don't strictly need it to be kept alive, but that's an// implementation detail. The pointer will be dereferenced again if the window is destroyed// and recreated.if(nativeHandle) { m_window = *(emscripten::val *)(nativeHandle); m_winId = nativeHandle; m_decoratedWindow.set("id","qt-window-"+std::to_string(m_winId)); m_decoratedWindow.call<void>("appendChild", m_window);return;} m_window.set("className","qt-window"); m_decoratedWindow.call<void>("appendChild", m_window); m_window.call<void>("appendChild", m_windowInput); m_canvas["classList"].call<void>("add",emscripten::val("qt-window-canvas"));#if QT_CONFIG(clipboard)if(QWasmClipboard::shouldInstallWindowEventHandlers()) {// Set contentEditable so that the window gets clipboard events,// then hide the resulting focus frame. m_windowInput.set("contentEditable",std::string("true")); m_windowInput["style"].set("outline",std::string("none")); m_cutCallback =QWasmEventHandler(m_windowInput,"cut",QWasmClipboard::cut); m_copyCallback =QWasmEventHandler(m_windowInput,"copy",QWasmClipboard::copy); m_pasteCallback =QWasmEventHandler(m_windowInput,"paste",QWasmClipboard::paste); m_beforeInputCallback =QWasmEventHandler(m_windowInput,"beforeinput",QWasmClipboard::beforeInput); m_inputCallback =QWasmEventHandler(m_windowInput,"input",QWasmClipboard::input);}#endif// Set inputMode to none to stop the mobile keyboard from opening// when the user clicks on the window. m_window.set("inputMode",std::string("none"));// Hide the canvas from screen readers. m_canvas.call<void>("setAttribute",std::string("aria-hidden"),std::string("true")); m_window.call<void>("appendChild", m_canvas); m_a11yContainer["classList"].call<void>("add",emscripten::val("qt-window-a11y-container")); m_window.call<void>("appendChild", m_a11yContainer);const bool rendersTo2dContext = w->surfaceType() !=QSurface::OpenGLSurface;if(rendersTo2dContext) m_context2d = m_canvas.call<emscripten::val>("getContext",emscripten::val("2d")); m_winId =WId(&m_window); m_decoratedWindow.set("id","qt-window-"+std::to_string(m_winId));emscripten::val::module_property("specialHTMLTargets").set(canvasSelector(), m_canvas); m_flags =window()->flags();registerEventHandlers();setParent(parent());}voidQWasmWindow::registerEventHandlers(){ m_pointerDownCallback =QWasmEventHandler(m_window,"pointerdown",[this](emscripten::val event){processPointer(PointerEvent(EventType::PointerDown, event)); }); m_pointerMoveCallback =QWasmEventHandler(m_window,"pointermove",[this](emscripten::val event){processPointer(PointerEvent(EventType::PointerMove, event)); }); m_pointerUpCallback =QWasmEventHandler(m_window,"pointerup",[this](emscripten::val event){processPointer(PointerEvent(EventType::PointerUp, event)); }); m_pointerCancelCallback =QWasmEventHandler(m_window,"pointercancel",[this](emscripten::val event){processPointer(PointerEvent(EventType::PointerCancel, event)); }); m_pointerEnterCallback =QWasmEventHandler(m_window,"pointerenter",[this](emscripten::val event) {this->handlePointerEnterLeaveEvent(PointerEvent(EventType::PointerEnter, event)); }); m_pointerLeaveCallback =QWasmEventHandler(m_window,"pointerleave",[this](emscripten::val event) {this->handlePointerEnterLeaveEvent(PointerEvent(EventType::PointerLeave, event)); });#if QT_CONFIG(draganddrop) m_window.call<void>("setAttribute",emscripten::val("draggable"),emscripten::val("true")); m_dragStartCallback =QWasmEventHandler(m_window,"dragstart",[this](emscripten::val event) { DragEvent dragEvent(EventType::DragStart, event,window());QWasmDrag::instance()->onNativeDragStarted(&dragEvent);}); m_dragOverCallback =QWasmEventHandler(m_window,"dragover",[this](emscripten::val event) { DragEvent dragEvent(EventType::DragOver, event,window());QWasmDrag::instance()->onNativeDragOver(&dragEvent);}); m_dropCallback =QWasmEventHandler(m_window,"drop",[this](emscripten::val event) { DragEvent dragEvent(EventType::Drop, event,window());QWasmDrag::instance()->onNativeDrop(&dragEvent);}); m_dragEndCallback =QWasmEventHandler(m_window,"dragend",[this](emscripten::val event) { DragEvent dragEvent(EventType::DragEnd, event,window());QWasmDrag::instance()->onNativeDragFinished(&dragEvent);}); m_dragLeaveCallback =QWasmEventHandler(m_window,"dragleave",[this](emscripten::val event) { DragEvent dragEvent(EventType::DragLeave, event,window());QWasmDrag::instance()->onNativeDragLeave(&dragEvent);});#endif// QT_CONFIG(draganddrop) m_wheelEventCallback =QWasmEventHandler(m_window,"wheel",[this](emscripten::val event) {this->handleWheelEvent(event); }); QWasmInputContext *wasmInput =QWasmIntegration::get()->wasmInputContext();if(wasmInput) { m_keyDownCallbackForInputContext =QWasmEventHandler(wasmInput->m_inputElement,"keydown",[this](emscripten::val event) {this->handleKeyForInputContextEvent(EventType::KeyDown, event); }); m_keyUpCallbackForInputContext =QWasmEventHandler(wasmInput->m_inputElement,"keyup",[this](emscripten::val event) {this->handleKeyForInputContextEvent(EventType::KeyUp, event); });} m_keyDownCallback =QWasmEventHandler(m_window,"keydown",[this](emscripten::val event) {this->handleKeyEvent(KeyEvent(EventType::KeyDown, event, m_deadKeySupport)); }); m_keyUpCallback =QWasmEventHandler(m_window,"keyup",[this](emscripten::val event) {this->handleKeyEvent(KeyEvent(EventType::KeyUp, event, m_deadKeySupport)); });}QWasmWindow::~QWasmWindow(){#if QT_CONFIG(accessibility)QWasmAccessibility::removeAccessibilityEnableButton(window());#endifshutdown();emscripten::val::module_property("specialHTMLTargets").delete_(canvasSelector()); m_window.call<void>("removeChild", m_canvas); m_context2d =emscripten::val::undefined();commitParent(nullptr);if(m_requestAnimationFrameId > -1)emscripten_cancel_animation_frame(m_requestAnimationFrameId);} QSurfaceFormat QWasmWindow::format()const{returnwindow()->requestedFormat();} QWasmWindow *QWasmWindow::fromWindow(const QWindow *window){if(!window ||!window->handle())returnnullptr;return static_cast<QWasmWindow *>(window->handle());}voidQWasmWindow::onRestoreClicked(){window()->setWindowState(Qt::WindowNoState);}voidQWasmWindow::onMaximizeClicked(){window()->setWindowState(Qt::WindowMaximized);}voidQWasmWindow::onToggleMaximized(){window()->setWindowState(m_state.testFlag(Qt::WindowMaximized) ?Qt::WindowNoState :Qt::WindowMaximized);}voidQWasmWindow::onCloseClicked(){window()->close();}voidQWasmWindow::onNonClientAreaInteraction(){requestActivateWindow();QGuiApplicationPrivate::instance()->closeAllPopups();}boolQWasmWindow::onNonClientEvent(const PointerEvent &event){ QPointF pointInScreen =platformScreen()->mapFromLocal(dom::mapPoint(event.target(),platformScreen()->element(), event.localPoint));returnQWindowSystemInterface::handleMouseEvent(window(),QWasmIntegration::getTimestamp(),window()->mapFromGlobal(pointInScreen), pointInScreen, event.mouseButtons, event.mouseButton,MouseEvent::mouseEventTypeFromEventType(event.type,WindowArea::NonClient), event.modifiers);}voidQWasmWindow::initialize(){auto initialGeometry =QPlatformWindow::initialGeometry(window(),windowGeometry(), defaultWindowSize, defaultWindowSize); m_normalGeometry = initialGeometry;setWindowState(window()->windowStates());setWindowFlags(window()->flags());setWindowTitle(window()->title());setMask(QHighDpi::toNativeLocalRegion(window()->mask(),window()));if(window()->isTopLevel())setWindowIcon(window()->icon());QPlatformWindow::setGeometry(m_normalGeometry);#if QT_CONFIG(accessibility)// Add accessibility-enable button. The user can activate this// button to opt-in to accessibility.if(window()->isTopLevel())QWasmAccessibility::addAccessibilityEnableButton(window());#endif} QWasmScreen *QWasmWindow::platformScreen()const{return static_cast<QWasmScreen *>(window()->screen()->handle());}voidQWasmWindow::paint(){if(!m_backingStore || !isVisible() || m_context2d.isUndefined())return;auto image = m_backingStore->getUpdatedWebImage(this);if(image.isUndefined())return; m_context2d.call<void>("putImageData", image,emscripten::val(0),emscripten::val(0));}voidQWasmWindow::setZOrder(int z){ m_decoratedWindow["style"].set("zIndex",std::to_string(z));}voidQWasmWindow::setWindowCursor(QByteArray cssCursorName){ m_window["style"].set("cursor",emscripten::val(cssCursorName.constData()));}voidQWasmWindow::setGeometry(const QRect &rect){constauto margins =frameMargins();const QRect clientAreaRect = ([this, &rect, &margins]() {if(m_state.testFlag(Qt::WindowFullScreen))returnplatformScreen()->geometry();if(m_state.testFlag(Qt::WindowMaximized))returnplatformScreen()->availableGeometry().marginsRemoved(frameMargins());auto offset = rect.topLeft() - (!parent() ?screen()->geometry().topLeft() :QPoint());// In viewportauto containerGeometryInViewport =QRectF::fromDOMRect(parentNode()->containerElement().call<emscripten::val>("getBoundingClientRect")).toRect();auto rectInViewport =QRect(containerGeometryInViewport.topLeft() + offset, rect.size()); QRect cappedGeometry(rectInViewport);if(!parent()) {// Clamp top level windows top position to the screen bounds cappedGeometry.moveTop(std::max(std::min(rectInViewport.y(), containerGeometryInViewport.bottom()), containerGeometryInViewport.y() + margins.top()));} cappedGeometry.setSize( cappedGeometry.size().expandedTo(windowMinimumSize()).boundedTo(windowMaximumSize()));returnQRect(QPoint(rect.x(), rect.y() + cappedGeometry.y() - rectInViewport.y()), rect.size());})(); m_nonClientArea->onClientAreaWidthChange(clientAreaRect.width());constauto frameRect = clientAreaRect .adjusted(-margins.left(), -margins.top(), margins.right(), margins.bottom()).translated(!parent() ? -screen()->geometry().topLeft() :QPoint()); m_decoratedWindow["style"].set("left",std::to_string(frameRect.left()) +"px"); m_decoratedWindow["style"].set("top",std::to_string(frameRect.top()) +"px"); m_canvas["style"].set("width",std::to_string(clientAreaRect.width()) +"px"); m_canvas["style"].set("height",std::to_string(clientAreaRect.height()) +"px"); m_a11yContainer["style"].set("width",std::to_string(clientAreaRect.width()) +"px"); m_a11yContainer["style"].set("height",std::to_string(clientAreaRect.height()) +"px");// Important for the title flexbox to shrink correctly m_window["style"].set("width",std::to_string(clientAreaRect.width()) +"px"); QSizeF canvasSize = clientAreaRect.size() *devicePixelRatio(); m_canvas.set("width", canvasSize.width()); m_canvas.set("height", canvasSize.height());bool shouldInvalidate =true;if(!m_state.testFlag(Qt::WindowFullScreen) && !m_state.testFlag(Qt::WindowMaximized)) { shouldInvalidate = m_normalGeometry.size() != clientAreaRect.size(); m_normalGeometry = clientAreaRect;}QWindowSystemInterface::handleGeometryChange(window(), clientAreaRect);if(shouldInvalidate)invalidate();else m_compositor->requestUpdateWindow(this,QRect(QPoint(0,0),geometry().size()));}voidQWasmWindow::setVisible(bool visible){// TODO(mikolajboc): isVisible()?const bool nowVisible = m_decoratedWindow["style"]["display"].as<std::string>() =="block";if(visible == nowVisible)return; m_compositor->requestUpdateWindow(this,QRect(QPoint(0,0),geometry().size()),QWasmCompositor::ExposeEventDelivery); m_decoratedWindow["style"].set("display", visible ?"block":"none");if(window() ==QGuiApplication::focusWindow())focus();if(visible) {applyWindowState();#if QT_CONFIG(accessibility)QWasmAccessibility::onShowWindow(window());#endif}}boolQWasmWindow::isVisible()const{returnwindow()->isVisible();} QMargins QWasmWindow::frameMargins()const{constauto frameRect =QRectF::fromDOMRect(m_decoratedWindow.call<emscripten::val>("getBoundingClientRect"));constauto canvasRect =QRectF::fromDOMRect(m_window.call<emscripten::val>("getBoundingClientRect"));returnQMarginsF(canvasRect.left() - frameRect.left(), canvasRect.top() - frameRect.top(), frameRect.right() - canvasRect.right(), frameRect.bottom() - canvasRect.bottom()).toMargins();}voidQWasmWindow::raise(){bringToTop();invalidate();}voidQWasmWindow::lower(){sendToBottom();invalidate();} WId QWasmWindow::winId()const{return m_winId;}voidQWasmWindow::propagateSizeHints(){// setGeometry() will take care of minimum and maximum size constraintssetGeometry(windowGeometry()); m_nonClientArea->propagateSizeHints();}voidQWasmWindow::setOpacity(qreal level){ m_decoratedWindow["style"].set("opacity",qBound(0.0, level,1.0));}voidQWasmWindow::invalidate(){ m_compositor->requestUpdateWindow(this,QRect(QPoint(0,0),geometry().size()));}voidQWasmWindow::onActivationChanged(bool active){dom::syncCSSClassWith(m_decoratedWindow,"inactive", !active);}// Fix top level window flags in case only the type flags are passed.staticinline Qt::WindowFlags fixTopLevelWindowFlags(Qt::WindowFlags flags){if(!(flags.testFlag(Qt::CustomizeWindowHint))) {if(flags.testFlag(Qt::Window)) { flags |=Qt::WindowTitleHint |Qt::WindowSystemMenuHint |Qt::WindowMaximizeButtonHint|Qt::WindowCloseButtonHint;}if(flags.testFlag(Qt::Dialog) || flags.testFlag(Qt::Tool)) flags |=Qt::WindowTitleHint |Qt::WindowSystemMenuHint |Qt::WindowCloseButtonHint;if((flags &Qt::WindowType_Mask) ==Qt::SplashScreen) flags |=Qt::FramelessWindowHint;}return flags;}voidQWasmWindow::setWindowFlags(Qt::WindowFlags flags){ flags =fixTopLevelWindowFlags(flags);if(flags.testFlag(Qt::WindowStaysOnTopHint) != m_flags.testFlag(Qt::WindowStaysOnTopHint)|| flags.testFlag(Qt::WindowStaysOnBottomHint)!= m_flags.testFlag(Qt::WindowStaysOnBottomHint)) {onPositionPreferenceChanged(positionPreferenceFromWindowFlags(flags));} m_flags = flags;dom::syncCSSClassWith(m_decoratedWindow,"frameless", !hasFrame() || !window()->isTopLevel());dom::syncCSSClassWith(m_decoratedWindow,"has-border",hasBorder());dom::syncCSSClassWith(m_decoratedWindow,"has-shadow",hasShadow());dom::syncCSSClassWith(m_decoratedWindow,"has-title",hasTitleBar());dom::syncCSSClassWith(m_decoratedWindow,"transparent-for-input", flags.testFlag(Qt::WindowTransparentForInput)); m_nonClientArea->titleBar()->setMaximizeVisible(hasMaximizeButton()); m_nonClientArea->titleBar()->setCloseVisible(m_flags.testFlag(Qt::WindowCloseButtonHint));}voidQWasmWindow::setWindowState(Qt::WindowStates newState){// Child windows can not have window states other than Qt::WindowActiveif(parent()) newState &=Qt::WindowActive;constQt::WindowStates oldState = m_state;if(newState.testFlag(Qt::WindowMinimized)) { newState.setFlag(Qt::WindowMinimized,false);qWarning("Qt::WindowMinimized is not implemented in wasm");window()->setWindowStates(newState);return;}if(newState == oldState)return; m_state = newState; m_previousWindowState = oldState;applyWindowState();}voidQWasmWindow::setWindowTitle(const QString &title){ m_nonClientArea->titleBar()->setTitle(title);}voidQWasmWindow::setWindowIcon(const QIcon &icon){constauto dpi =screen()->devicePixelRatio();auto pixmap = icon.pixmap(10* dpi,10* dpi);if(pixmap.isNull()) { m_nonClientArea->titleBar()->setIcon(Base64IconStore::get()->getIcon(Base64IconStore::IconType::QtLogo),"svg+xml");return;} QByteArray bytes; QBuffer buffer(&bytes); pixmap.save(&buffer,"png"); m_nonClientArea->titleBar()->setIcon(bytes.toBase64().toStdString(),"png");}voidQWasmWindow::applyWindowState(){ QRect newGeom;const bool isFullscreen = m_state.testFlag(Qt::WindowFullScreen);const bool isMaximized = m_state.testFlag(Qt::WindowMaximized);if(isFullscreen) newGeom =platformScreen()->geometry();else if(isMaximized) newGeom =platformScreen()->availableGeometry().marginsRemoved(frameMargins());else newGeom =normalGeometry();dom::syncCSSClassWith(m_decoratedWindow,"has-border",hasBorder());dom::syncCSSClassWith(m_decoratedWindow,"maximized", isMaximized); m_nonClientArea->titleBar()->setRestoreVisible(isMaximized); m_nonClientArea->titleBar()->setMaximizeVisible(hasMaximizeButton());if(isVisible())QWindowSystemInterface::handleWindowStateChanged(window(), m_state, m_previousWindowState);setGeometry(newGeom);}voidQWasmWindow::commitParent(QWasmWindowTreeNode *parent){onParentChanged(m_commitedParent, parent,positionPreferenceFromWindowFlags(window()->flags())); m_commitedParent = parent;}voidQWasmWindow::handleKeyEvent(const KeyEvent &event){qCDebug(qLcQpaWasmInputContext) <<"processKey as KeyEvent";if(processKey(event)) { event.webEvent.call<void>("preventDefault"); event.webEvent.call<void>("stopPropagation");}}boolQWasmWindow::processKey(const KeyEvent &event){constexprbool ProceedToNativeEvent =false;Q_ASSERT(event.type ==EventType::KeyDown || event.type ==EventType::KeyUp);#if QT_CONFIG(clipboard)constauto clipboardResult =QWasmIntegration::get()->getWasmClipboard()->processKeyboard(event);using ProcessKeyboardResult =QWasmClipboard::ProcessKeyboardResult;if(clipboardResult ==ProcessKeyboardResult::NativeClipboardEventNeeded)return ProceedToNativeEvent;#endifconstauto result =QWindowSystemInterface::handleKeyEvent(0, event.type ==EventType::KeyDown ?QEvent::KeyPress :QEvent::KeyRelease, event.key, event.modifiers, event.text, event.autoRepeat);#if QT_CONFIG(clipboard)return clipboardResult ==ProcessKeyboardResult::NativeClipboardEventAndCopiedDataNeeded ? ProceedToNativeEvent : result;#elsereturn result;#endif}voidQWasmWindow::handleKeyForInputContextEvent(EventType eventType,constemscripten::val &event){//// Things to consider://// (Alt + '̃~') + a -> compose('~', 'a')// (Compose) + '\'' + e -> compose('\'', 'e')// complex (i.e Chinese et al) input handling// Multiline text edit backspace at start of line//const QWasmInputContext *wasmInput =QWasmIntegration::get()->wasmInputContext();if(wasmInput) {constauto keyString =QString::fromStdString(event["key"].as<std::string>());qCDebug(qLcQpaWasmInputContext) <<"Key callback"<< keyString << keyString.size();if(keyString =="Unidentified") {// Android makes a bunch of KeyEvents as "Unidentified"// They will be processed just in InputContext.return;}else if(event["isComposing"].as<bool>()) {// Handled by the input contextreturn;}else if(event["ctrlKey"].as<bool>()|| event["altKey"].as<bool>()|| event["metaKey"].as<bool>()) {// Not all platforms use 'isComposing' for '~' + 'a', in this// case send the key with state ('ctrl', 'alt', or 'meta') to// processKeyForInputContext;// fallthrough}else if(keyString.size() !=1) {// This is like; 'Shift','ArrowRight','AltGraph', ...// send all of these to processKeyForInputContext;// fallthrough}else if(wasmInput->inputMethodAccepted()) {// processed in inputContext with skipping processKeyreturn;}}qCDebug(qLcQpaWasmInputContext) <<"processKey as KeyEvent";if(processKeyForInputContext(KeyEvent(eventType, event, m_deadKeySupport))) event.call<void>("preventDefault"); event.call<void>("stopImmediatePropagation");}boolQWasmWindow::processKeyForInputContext(const KeyEvent &event){qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;Q_ASSERT(event.type ==EventType::KeyDown || event.type ==EventType::KeyUp); QKeySequence keySeq(event.modifiers | event.key);if(keySeq ==QKeySequence::Paste) {// Process it in pasteCallback and inputCallbackreturn false;}constauto result =QWindowSystemInterface::handleKeyEvent(0, event.type ==EventType::KeyDown ?QEvent::KeyPress :QEvent::KeyRelease, event.key, event.modifiers, event.text);#if QT_CONFIG(clipboard)// Copy/Cut callback required to copy qtClipboard to system clipboardif(keySeq ==QKeySequence::Copy || keySeq ==QKeySequence::Cut)return false;#endifreturn result;}voidQWasmWindow::handlePointerEnterLeaveEvent(const PointerEvent &event){if(processPointerEnterLeave(event)) event.webEvent.call<void>("preventDefault");}boolQWasmWindow::processPointerEnterLeave(const PointerEvent &event){if(event.pointerType !=PointerType::Mouse && event.pointerType !=PointerType::Pen)return false;switch(event.type) {caseEventType::PointerEnter: {constauto pointInScreen =platformScreen()->mapFromLocal(dom::mapPoint(event.target(),platformScreen()->element(), event.localPoint));QWindowSystemInterface::handleEnterEvent(window(),mapFromGlobal(pointInScreen.toPoint()), pointInScreen);break;}caseEventType::PointerLeave:QWindowSystemInterface::handleLeaveEvent(window());break;default:break;}return false;}voidQWasmWindow::processPointer(const PointerEvent &event){switch(event.type) {caseEventType::PointerDown:if(event.isTargetedForQtElement()) m_window.call<void>("setPointerCapture", event.pointerId);if((window()->flags() &Qt::WindowDoesNotAcceptFocus)!=Qt::WindowDoesNotAcceptFocus &&window()->isTopLevel())window()->requestActivate();break;caseEventType::PointerUp:if(event.isTargetedForQtElement()) m_window.call<void>("releasePointerCapture", event.pointerId);break;default:break;};const bool eventAccepted =deliverPointerEvent(event);if(!eventAccepted && event.type ==EventType::PointerDown)QGuiApplicationPrivate::instance()->closeAllPopups();if(eventAccepted) { event.webEvent.call<void>("preventDefault"); event.webEvent.call<void>("stopPropagation");}}boolQWasmWindow::deliverPointerEvent(const PointerEvent &event){constauto pointInScreen =platformScreen()->mapFromLocal(dom::mapPoint(event.target(),platformScreen()->element(), event.localPoint));constauto geometryF =platformScreen()->geometry().toRectF();const QPointF targetPointClippedToScreen(qBound(geometryF.left(), pointInScreen.x(), geometryF.right()),qBound(geometryF.top(), pointInScreen.y(), geometryF.bottom()));if(event.pointerType ==PointerType::Mouse) {constQEvent::Type eventType =MouseEvent::mouseEventTypeFromEventType(event.type,WindowArea::Client);return eventType !=QEvent::None &&QWindowSystemInterface::handleMouseEvent(window(),QWasmIntegration::getTimestamp(),window()->mapFromGlobal(targetPointClippedToScreen), targetPointClippedToScreen, event.mouseButtons, event.mouseButton, eventType, event.modifiers);}if(event.pointerType ==PointerType::Pen) { qreal pressure;switch(event.type) {caseEventType::PointerDown :caseEventType::PointerMove : pressure = event.pressure;break;caseEventType::PointerUp : pressure =0.0;break;default:return false;}// Tilt in the browser is in the range +-90, but QTabletEvent only goes to +-60. qreal xTilt =qBound(-60.0, event.tiltX,60.0); qreal yTilt =qBound(-60.0, event.tiltY,60.0);// Barrel rotation is reported as 0 to 359, but QTabletEvent wants a signed value. qreal rotation = event.twist >180.0?360.0- event.twist : event.twist;returnQWindowSystemInterface::handleTabletEvent(window(),QWasmIntegration::getTimestamp(),platformScreen()->tabletDevice(),window()->mapFromGlobal(targetPointClippedToScreen), targetPointClippedToScreen, event.mouseButtons, pressure, xTilt, yTilt, event.tangentialPressure, rotation, event.modifiers);}QWindowSystemInterface::TouchPoint *touchPoint; QPointF pointInTargetWindowCoords =QPointF(window()->mapFromGlobal(targetPointClippedToScreen)); QPointF normalPosition(pointInTargetWindowCoords.x() /window()->width(), pointInTargetWindowCoords.y() /window()->height());constauto tp = m_pointerIdToTouchPoints.find(event.pointerId);if(event.pointerType !=PointerType::Pen && tp != m_pointerIdToTouchPoints.end()) { touchPoint = &tp.value();}else{ touchPoint = &m_pointerIdToTouchPoints .insert(event.pointerId,QWindowSystemInterface::TouchPoint()).value();// Assign touch point id. TouchPoint::id is int, but QGuiApplicationPrivate::processTouchEvent()// will not synthesize mouse events for touch points with negative id; use the absolute value for// the touch point id. touchPoint->id =qAbs(event.pointerId); touchPoint->state =QEventPoint::State::Pressed;}const bool stationaryTouchPoint = (normalPosition == touchPoint->normalPosition); touchPoint->normalPosition = normalPosition; touchPoint->area =QRectF(targetPointClippedToScreen,QSizeF(event.width, event.height)).translated(-event.width /2, -event.height /2); touchPoint->pressure = event.pressure;switch(event.type) {caseEventType::PointerUp: touchPoint->state =QEventPoint::State::Released;break;caseEventType::PointerMove: touchPoint->state = (stationaryTouchPoint ?QEventPoint::State::Stationary :QEventPoint::State::Updated);break;default:break;} QList<QWindowSystemInterface::TouchPoint> touchPointList; touchPointList.reserve(m_pointerIdToTouchPoints.size());std::transform(m_pointerIdToTouchPoints.begin(), m_pointerIdToTouchPoints.end(),std::back_inserter(touchPointList),[](constQWindowSystemInterface::TouchPoint &val) {return val; });if(event.type ==EventType::PointerUp) m_pointerIdToTouchPoints.remove(event.pointerId);return event.type ==EventType::PointerCancel ?QWindowSystemInterface::handleTouchCancelEvent(window(),QWasmIntegration::getTimestamp(),platformScreen()->touchDevice(), event.modifiers):QWindowSystemInterface::handleTouchEvent(window(),QWasmIntegration::getTimestamp(),platformScreen()->touchDevice(), touchPointList, event.modifiers);}voidQWasmWindow::handleWheelEvent(constemscripten::val &event){if(processWheel(WheelEvent(EventType::Wheel, event))) event.call<void>("preventDefault");}boolQWasmWindow::processWheel(const WheelEvent &event){// Web scroll deltas are inverted from Qt deltas - negate.const int scrollFactor = -([&event]() {switch(event.deltaMode) {caseDeltaMode::Pixel:return1;caseDeltaMode::Line:return12;caseDeltaMode::Page:return20;};})();constauto pointInScreen =platformScreen()->mapFromLocal(dom::mapPoint(event.target(),platformScreen()->element(), event.localPoint));returnQWindowSystemInterface::handleWheelEvent(window(),QWasmIntegration::getTimestamp(),window()->mapFromGlobal(pointInScreen), pointInScreen, (event.delta * scrollFactor).toPoint(),(event.delta * scrollFactor).toPoint(), event.modifiers,Qt::NoScrollPhase,Qt::MouseEventNotSynthesized, event.webkitDirectionInvertedFromDevice);} QRect QWasmWindow::normalGeometry()const{return m_normalGeometry;} qreal QWasmWindow::devicePixelRatio()const{returnscreen()->devicePixelRatio();}voidQWasmWindow::requestUpdate(){ m_compositor->requestUpdateWindow(this,QRect(QPoint(0,0),geometry().size()),QWasmCompositor::UpdateRequestDelivery);}boolQWasmWindow::hasFrame()const{return!m_flags.testFlag(Qt::FramelessWindowHint);}boolQWasmWindow::hasBorder()const{returnhasFrame() && !m_state.testFlag(Qt::WindowFullScreen) && !m_flags.testFlag(Qt::SubWindow)&& !windowIsPopupType(m_flags) && !parent();}boolQWasmWindow::hasTitleBar()const{returnhasBorder() && m_flags.testFlag(Qt::WindowTitleHint);}boolQWasmWindow::hasShadow()const{returnhasBorder() && !m_flags.testFlag(Qt::NoDropShadowWindowHint);}boolQWasmWindow::hasMaximizeButton()const{return!m_state.testFlag(Qt::WindowMaximized) && m_flags.testFlag(Qt::WindowMaximizeButtonHint);}boolQWasmWindow::windowIsPopupType(Qt::WindowFlags flags)const{if(flags.testFlag(Qt::Tool))return false;// Qt::Tool has the Popup bit set but isn't an actual Popup windowreturn(flags.testFlag(Qt::Popup));}voidQWasmWindow::requestActivateWindow(){ QWindow *modalWindow;if(QGuiApplicationPrivate::instance()->isWindowBlocked(window(), &modalWindow)) {static_cast<QWasmWindow *>(modalWindow->handle())->requestActivateWindow();return;}raise();setAsActiveNode();if(!QWasmIntegration::get()->inputContext())focus();QPlatformWindow::requestActivateWindow();}voidQWasmWindow::focus(){ m_windowInput.call<void>("focus");}boolQWasmWindow::setMouseGrabEnabled(bool grab){Q_UNUSED(grab);return false;}boolQWasmWindow::windowEvent(QEvent *event){switch(event->type()) {caseQEvent::WindowBlocked: m_decoratedWindow["classList"].call<void>("add",emscripten::val("blocked"));return false;// Propagate furthercaseQEvent::WindowUnblocked:; m_decoratedWindow["classList"].call<void>("remove",emscripten::val("blocked"));return false;// Propagate furtherdefault:returnQPlatformWindow::windowEvent(event);}}voidQWasmWindow::setMask(const QRegion ®ion){if(region.isEmpty()) { m_decoratedWindow["style"].set("clipPath",emscripten::val(""));return;}std::ostringstream cssClipPath; cssClipPath <<"path('";for(constauto&rect : region) {constauto cssRect = rect.adjusted(0,0,1,1); cssClipPath <<"M "<< cssRect.left() <<" "<< cssRect.top() <<" "; cssClipPath <<"L "<< cssRect.right() <<" "<< cssRect.top() <<" "; cssClipPath <<"L "<< cssRect.right() <<" "<< cssRect.bottom() <<" "; cssClipPath <<"L "<< cssRect.left() <<" "<< cssRect.bottom() <<" z ";} cssClipPath <<"')"; m_decoratedWindow["style"].set("clipPath",emscripten::val(cssClipPath.str()));}voidQWasmWindow::setParent(const QPlatformWindow *){// The window flags depend on whether we are a// child window or not, so update them here.setWindowFlags(window()->flags());commitParent(parentNode());}std::string QWasmWindow::canvasSelector()const{return"!qtwindow"+std::to_string(m_winId);}emscripten::val QWasmWindow::containerElement(){return m_window;} QWasmWindowTreeNode *QWasmWindow::parentNode(){if(parent())return static_cast<QWasmWindow *>(parent());returnplatformScreen();} QWasmWindow *QWasmWindow::asWasmWindow(){return this;}voidQWasmWindow::onParentChanged(QWasmWindowTreeNode *previous, QWasmWindowTreeNode *current,QWasmWindowStack::PositionPreference positionPreference){if(previous) previous->containerElement().call<void>("removeChild", m_decoratedWindow);if(current) current->containerElement().call<void>("appendChild", m_decoratedWindow);QWasmWindowTreeNode::onParentChanged(previous, current, positionPreference);} QT_END_NAMESPACE
|