summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/plugins/platforms/wasm/qwasmclipboard.cpp13
-rw-r--r--src/plugins/platforms/wasm/qwasminputcontext.cpp469
-rw-r--r--src/plugins/platforms/wasm/qwasminputcontext.h26
-rw-r--r--src/plugins/platforms/wasm/qwasmintegration.cpp12
-rw-r--r--src/plugins/platforms/wasm/qwasmintegration.h4
-rw-r--r--src/plugins/platforms/wasm/qwasmwindow.cpp104
-rw-r--r--src/plugins/platforms/wasm/qwasmwindow.h5
7 files changed, 503 insertions, 130 deletions
diff --git a/src/plugins/platforms/wasm/qwasmclipboard.cpp b/src/plugins/platforms/wasm/qwasmclipboard.cpp
index 1aa3ffa5b36..2d00d9f723a 100644
--- a/src/plugins/platforms/wasm/qwasmclipboard.cpp
+++ b/src/plugins/platforms/wasm/qwasmclipboard.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qwasmclipboard.h"
+#include "qwasminputcontext.h"
#include "qwasmdom.h"
#include "qwasmevent.h"
#include "qwasmwindow.h"
@@ -47,6 +48,10 @@ static void commonCopyEvent(val event)
static void qClipboardCutTo(val event)
{
+ QWasmInputContext *wasmInput = QWasmIntegration::get()->wasmInputContext();
+ if (wasmInput && wasmInput->usingTextInput())
+ return;
+
if (!QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi()) {
// Send synthetic Ctrl+X to make the app cut data to Qt's clipboard
QWindowSystemInterface::handleKeyEvent(
@@ -58,6 +63,10 @@ static void qClipboardCutTo(val event)
static void qClipboardCopyTo(val event)
{
+ QWasmInputContext *wasmInput = QWasmIntegration::get()->wasmInputContext();
+ if (wasmInput && wasmInput->usingTextInput())
+ return;
+
if (!QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi()) {
// Send synthetic Ctrl+C to make the app copy data to Qt's clipboard
QWindowSystemInterface::handleKeyEvent(
@@ -68,6 +77,10 @@ static void qClipboardCopyTo(val event)
static void qClipboardPasteTo(val event)
{
+ QWasmInputContext *wasmInput = QWasmIntegration::get()->wasmInputContext();
+ if (wasmInput && wasmInput->usingTextInput())
+ return;
+
event.call<void>("preventDefault"); // prevent browser from handling drop event
QWasmIntegration::get()->getWasmClipboard()->sendClipboardData(event);
diff --git a/src/plugins/platforms/wasm/qwasminputcontext.cpp b/src/plugins/platforms/wasm/qwasminputcontext.cpp
index ae72e7b7f99..f8dbd86a193 100644
--- a/src/plugins/platforms/wasm/qwasminputcontext.cpp
+++ b/src/plugins/platforms/wasm/qwasminputcontext.cpp
@@ -1,161 +1,432 @@
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
-#include <emscripten/bind.h>
-
#include "qwasminputcontext.h"
-#include "qwasmintegration.h"
-#include "qwasmplatform.h"
+
#include <QRectF>
-#include <qpa/qplatforminputcontext.h>
-#include "qwasmscreen.h"
+#include <QLoggingCategory>
#include <qguiapplication.h>
#include <qwindow.h>
-#include <QKeySequence>
+#include <qpa/qplatforminputcontext.h>
#include <qpa/qwindowsysteminterface.h>
+#include <QClipboard>
QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(qLcQpaWasmInputContext, "qt.qpa.wasm.inputcontext")
+
using namespace qstdweb;
static void inputCallback(emscripten::val event)
{
- // android sends all the characters typed since the user started typing in this element
- int length = event["target"]["value"]["length"].as<int>();
- if (length <= 0)
+ qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "isComposing : " << event["isComposing"].as<bool>();
+
+ QString inputStr = (event["data"] != emscripten::val::null()
+ && event["data"] != emscripten::val::undefined()) ?
+ QString::fromStdString(event["data"].as<std::string>()) : QString();
+
+ QWasmInputContext *wasmInput =
+ reinterpret_cast<QWasmInputContext *>(event["target"]["data-qinputcontext"].as<quintptr>());
+
+ emscripten::val inputType = event["inputType"];
+ if (inputType != emscripten::val::null()
+ && inputType != emscripten::val::undefined()) {
+ const auto inputTypeString = inputType.as<std::string>();
+ // There are many inputTypes for InputEvent
+ // https://www.w3.org/TR/input-events-1/
+ // Some of them should be implemented here later.
+ qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "inputType : " << inputTypeString;
+ if (!inputTypeString.compare("deleteContentBackward")) {
+ QWindowSystemInterface::handleKeyEvent(0,
+ QEvent::KeyPress,
+ Qt::Key_Backspace,
+ Qt::NoModifier);
+ QWindowSystemInterface::handleKeyEvent(0,
+ QEvent::KeyRelease,
+ Qt::Key_Backspace,
+ Qt::NoModifier);
+ event.call<void>("stopImmediatePropagation");
+ return;
+ } else if (!inputTypeString.compare("deleteContentForward")) {
+ QWindowSystemInterface::handleKeyEvent(0,
+ QEvent::KeyPress,
+ Qt::Key_Delete,
+ Qt::NoModifier);
+ QWindowSystemInterface::handleKeyEvent(0,
+ QEvent::KeyRelease,
+ Qt::Key_Delete,
+ Qt::NoModifier);
+ event.call<void>("stopImmediatePropagation");
+ return;
+ } else if (!inputTypeString.compare("insertCompositionText")) {
+ qCDebug(qLcQpaWasmInputContext) << "inputString : " << inputStr;
+ wasmInput->insertPreedit();
+ event.call<void>("stopImmediatePropagation");
+ return;
+ } else if (!inputTypeString.compare("insertReplacementText")) {
+ qCDebug(qLcQpaWasmInputContext) << "inputString : " << inputStr;
+ //auto ranges = event.call<emscripten::val>("getTargetRanges");
+ //qCDebug(qLcQpaWasmInputContext) << ranges["length"].as<int>();
+ // WA For Korean IME
+ // insertReplacementText should have targetRanges but
+ // Safari cannot have it and just it seems to be supposed
+ // to replace previous input.
+ wasmInput->insertText(inputStr, true);
+
+ event.call<void>("stopImmediatePropagation");
+ return;
+ } else if (!inputTypeString.compare("deleteCompositionText")) {
+ wasmInput->setPreeditString("", 0);
+ wasmInput->insertPreedit();
+ event.call<void>("stopImmediatePropagation");
+ return;
+ } else if (!inputTypeString.compare("insertFromComposition")) {
+ wasmInput->setPreeditString(inputStr, 0);
+ wasmInput->insertPreedit();
+ event.call<void>("stopImmediatePropagation");
+ return;
+ } else if (!inputTypeString.compare("insertText")) {
+ wasmInput->insertText(inputStr);
+ event.call<void>("stopImmediatePropagation");
+ } else if (!inputTypeString.compare("insertFromPaste")) {
+ wasmInput->insertText(QGuiApplication::clipboard()->text());
+ event.call<void>("stopImmediatePropagation");
+ // These can be supported here,
+ // But now, keyCallback in QWasmWindow
+ // will take them as exceptions.
+ //} else if (!inputTypeString.compare("deleteByCut")) {
+ } else {
+ qCWarning(qLcQpaWasmInputContext) << Q_FUNC_INFO << "inputType \"" << inputType.as<std::string>() << "\" is not supported in Qt yet";
+ }
+ }
+}
+
+static void compositionEndCallback(emscripten::val event)
+{
+ const auto inputStr = QString::fromStdString(event["data"].as<std::string>());
+ qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << inputStr;
+
+ QWasmInputContext *wasmInput =
+ reinterpret_cast<QWasmInputContext *>(event["target"]["data-qinputcontext"].as<quintptr>());
+
+ if (wasmInput->preeditString().isEmpty())
return;
- emscripten::val _incomingCharVal = event["data"];
- if (_incomingCharVal != emscripten::val::undefined() && _incomingCharVal != emscripten::val::null()) {
+ if (inputStr != wasmInput->preeditString()) {
+ qCWarning(qLcQpaWasmInputContext) << Q_FUNC_INFO
+ << "Composition string" << inputStr
+ << "is differ from" << wasmInput->preeditString();
+ }
+ wasmInput->commitPreeditAndClear();
+}
- QString str = QString::fromStdString(_incomingCharVal.as<std::string>());
- QWasmInputContext *wasmInput =
- reinterpret_cast<QWasmInputContext*>(event["target"]["data-qinputcontext"].as<quintptr>());
- wasmInput->inputStringChanged(str, EMSCRIPTEN_EVENT_KEYDOWN, wasmInput);
+static void compositionStartCallback(emscripten::val event)
+{
+ Q_UNUSED(event);
+ qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
+
+ // Do nothing when starting composition
+}
+
+/*
+// Test implementation
+static void beforeInputCallback(emscripten::val event)
+{
+ qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
+
+ auto ranges = event.call<emscripten::val>("getTargetRanges");
+ auto length = ranges["length"].as<int>();
+ for (auto i = 0; i < length; i++) {
+ qCDebug(qLcQpaWasmInputContext) << ranges.call<emscripten::val>("get", i)["startOffset"].as<int>();
+ qCDebug(qLcQpaWasmInputContext) << ranges.call<emscripten::val>("get", i)["endOffset"].as<int>();
}
- // this clears the input string, so backspaces do not send a character
- // but stops suggestions
- event["target"].set("value", "");
}
+*/
+
+static void compositionUpdateCallback(emscripten::val event)
+{
+ const auto compositionStr = QString::fromStdString(event["data"].as<std::string>());
+ qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << compositionStr;
+
+ QWasmInputContext *wasmInput =
+ reinterpret_cast<QWasmInputContext *>(event["target"]["data-qinputcontext"].as<quintptr>());
+
+ // WA for IOS.
+ // Not sure now because I cannot test it anymore.
+// int replaceSize = 0;
+// emscripten::val win = emscripten::val::global("window");
+// emscripten::val sel = win.call<emscripten::val>("getSelection");
+// if (sel != emscripten::val::null()
+// && sel != emscripten::val::undefined()
+// && sel["rangeCount"].as<int>() > 0) {
+// QInputMethodQueryEvent queryEvent(Qt::ImQueryAll);
+// QCoreApplication::sendEvent(QGuiApplication::focusObject(), &queryEvent);
+// qCDebug(qLcQpaWasmInputContext) << "Qt surrounding text: " << queryEvent.value(Qt::ImSurroundingText).toString();
+// qCDebug(qLcQpaWasmInputContext) << "Qt current selection: " << queryEvent.value(Qt::ImCurrentSelection).toString();
+// qCDebug(qLcQpaWasmInputContext) << "Qt text before cursor: " << queryEvent.value(Qt::ImTextBeforeCursor).toString();
+// qCDebug(qLcQpaWasmInputContext) << "Qt text after cursor: " << queryEvent.value(Qt::ImTextAfterCursor).toString();
+//
+// const QString &selectedStr = QString::fromUtf8(sel.call<emscripten::val>("toString").as<std::string>());
+// const auto &preeditStr = wasmInput->preeditString();
+// qCDebug(qLcQpaWasmInputContext) << "Selection.type : " << sel["type"].as<std::string>();
+// qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "Selected: " << selectedStr;
+// qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "PreeditString: " << preeditStr;
+// if (!sel["type"].as<std::string>().compare("Range")) {
+// QString surroundingTextBeforeCursor = queryEvent.value(Qt::ImTextBeforeCursor).toString();
+// if (surroundingTextBeforeCursor.endsWith(selectedStr)) {
+// replaceSize = selectedStr.size();
+// qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "Current Preedit: " << preeditStr << replaceSize;
+// }
+// }
+// emscripten::val range = sel.call<emscripten::val>("getRangeAt", 0);
+// qCDebug(qLcQpaWasmInputContext) << "Range.startOffset : " << range["startOffset"].as<int>();
+// qCDebug(qLcQpaWasmInputContext) << "Range.endOffset : " << range["endOffset"].as<int>();
+// }
+//
+// wasmInput->setPreeditString(compositionStr, replaceSize);
+ wasmInput->setPreeditString(compositionStr, 0);
+}
+
+static void copyCallback(emscripten::val event)
+{
+ qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
+
+ QClipboard *clipboard = QGuiApplication::clipboard();
+ QString inputStr = clipboard->text();
+ qCDebug(qLcQpaWasmInputContext) << "QClipboard : " << inputStr;
+ event["clipboardData"].call<void>("setData",
+ emscripten::val("text/plain"),
+ inputStr.toStdString());
+ event.call<void>("preventDefault");
+ event.call<void>("stopImmediatePropagation");
+}
+
+static void cutCallback(emscripten::val event)
+{
+ qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
+
+ QClipboard *clipboard = QGuiApplication::clipboard();
+ QString inputStr = clipboard->text();
+ qCDebug(qLcQpaWasmInputContext) << "QClipboard : " << inputStr;
+ event["clipboardData"].call<void>("setData",
+ emscripten::val("text/plain"),
+ inputStr.toStdString());
+ event.call<void>("preventDefault");
+ event.call<void>("stopImmediatePropagation");
+}
+
+static void pasteCallback(emscripten::val event)
+{
+ qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
-EMSCRIPTEN_BINDINGS(clipboard_module) {
- function("qtInputContextCallback", &inputCallback);
+ emscripten::val clipboardData = event["clipboardData"].call<emscripten::val>("getData", emscripten::val("text/plain"));
+ QString clipboardStr = QString::fromStdString(clipboardData.as<std::string>());
+ qCDebug(qLcQpaWasmInputContext) << "wasm clipboard : " << clipboardStr;
+ QClipboard *clipboard = QGuiApplication::clipboard();
+ if (clipboard->text() != clipboardStr)
+ clipboard->setText(clipboardStr);
+
+ // propagate to input event (insertFromPaste)
+}
+
+EMSCRIPTEN_BINDINGS(wasminputcontext_module) {
+ function("qtCompositionEndCallback", &compositionEndCallback);
+ function("qtCompositionStartCallback", &compositionStartCallback);
+ function("qtCompositionUpdateCallback", &compositionUpdateCallback);
+ function("qtInputCallback", &inputCallback);
+ //function("qtBeforeInputCallback", &beforeInputCallback);
+
+ function("qtCopyCallback", &copyCallback);
+ function("qtCutCallback", &cutCallback);
+ function("qtPasteCallback", &pasteCallback);
}
QWasmInputContext::QWasmInputContext()
{
+ qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
emscripten::val document = emscripten::val::global("document");
+ // This 'input' can be an issue to handle multiple lines,
+ // 'textarea' can be used instead.
m_inputElement = document.call<emscripten::val>("createElement", std::string("input"));
m_inputElement.set("type", "text");
- m_inputElement.set("style", "position:absolute;left:-1000px;top:-1000px"); // offscreen
m_inputElement.set("contenteditable","true");
- if (platform() == Platform::MacOS || platform() == Platform::iOS) {
- auto callback = [=](emscripten::val) {
- m_inputElement["parentElement"].call<void>("removeChild", m_inputElement);
- inputPanelIsOpen = false;
- };
- m_blurEventHandler.reset(new EventCallback(m_inputElement, "blur", callback));
-
- } else {
+ m_inputElement["style"].set("position", "absolute");
+ m_inputElement["style"].set("opacity", 0);
+ m_inputElement["style"].set("display", "");
+ m_inputElement["style"].set("z-index", -2);
- const std::string inputType = platform() == Platform::Windows ? "textInput" : "input";
+ m_inputElement.set("data-qinputcontext",
+ emscripten::val(quintptr(reinterpret_cast<void *>(this))));
+ emscripten::val body = document["body"];
+ body.call<void>("appendChild", m_inputElement);
- document.call<void>("addEventListener", inputType,
- emscripten::val::module_property("qtInputContextCallback"),
- emscripten::val(false));
- m_inputElement.set("data-qinputcontext",
- emscripten::val(quintptr(reinterpret_cast<void *>(this))));
- emscripten::val body = document["body"];
- body.call<void>("appendChild", m_inputElement);
- }
+ m_inputElement.call<void>("addEventListener", std::string("compositionstart"),
+ emscripten::val::module_property("qtCompositionStartCallback"),
+ emscripten::val(false));
+ m_inputElement.call<void>("addEventListener", std::string("compositionupdate"),
+ emscripten::val::module_property("qtCompositionUpdateCallback"),
+ emscripten::val(false));
+ m_inputElement.call<void>("addEventListener", std::string("compositionend"),
+ emscripten::val::module_property("qtCompositionEndCallback"),
+ emscripten::val(false));
+ m_inputElement.call<void>("addEventListener", std::string("input"),
+ emscripten::val::module_property("qtInputCallback"),
+ emscripten::val(false));
+ //m_inputElement.call<void>("addEventListener", std::string("beforeinput"),
+ // emscripten::val::module_property("qtBeforeInputCallback"),
+ // emscripten::val(false));
- QObject::connect(qGuiApp, &QGuiApplication::focusWindowChanged, this,
- &QWasmInputContext::focusWindowChanged);
+ // Clipboard for InputContext
+ m_inputElement.call<void>("addEventListener", std::string("cut"),
+ emscripten::val::module_property("qtCutCallback"),
+ emscripten::val(false));
+ m_inputElement.call<void>("addEventListener", std::string("copy"),
+ emscripten::val::module_property("qtCopyCallback"),
+ emscripten::val(false));
+ m_inputElement.call<void>("addEventListener", std::string("paste"),
+ emscripten::val::module_property("qtPasteCallback"),
+ emscripten::val(false));
}
QWasmInputContext::~QWasmInputContext()
{
- if (platform() == Platform::Android || platform() == Platform::Windows)
- emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, 0, NULL);
}
-void QWasmInputContext::focusWindowChanged(QWindow *focusWindow)
+void QWasmInputContext::update(Qt::InputMethodQueries queries)
{
- m_focusWindow = focusWindow;
+ qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << queries;
+
+ QPlatformInputContext::update(queries);
}
-emscripten::val QWasmInputContext::inputHandlerElementForFocusedWindow()
+void QWasmInputContext::showInputPanel()
{
- if (!m_focusWindow)
- return emscripten::val::undefined();
- return static_cast<QWasmWindow *>(m_focusWindow->handle())->inputHandlerElement();
+ qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
+
+ if (!inputMethodAccepted())
+ return;
+
+ m_inputElement.call<void>("focus");
+ m_usingTextInput = true;
+
+ QWindow *window = QGuiApplication::focusWindow();
+ if (!window || !m_focusObject)
+ return;
+
+ const QRect cursorRectangle = QPlatformInputContext::cursorRectangle().toRect();
+ if (!cursorRectangle.isValid())
+ return;
+ qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "cursorRectangle: " << cursorRectangle;
+ const QPoint &globalPos = window->mapToGlobal(cursorRectangle.topLeft());
+ const auto styleLeft = std::to_string(globalPos.x()) + "px";
+ const auto styleTop = std::to_string(globalPos.y()) + "px";
+ m_inputElement["style"].set("left", styleLeft);
+ m_inputElement["style"].set("top", styleTop);
+
+ qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << QRectF::fromDOMRect(m_inputElement.call<emscripten::val>("getBoundingClientRect"));
+
+ QInputMethodQueryEvent queryEvent(Qt::ImQueryAll);
+ QCoreApplication::sendEvent(m_focusObject, &queryEvent);
+ qCDebug(qLcQpaWasmInputContext) << "Qt surrounding text: " << queryEvent.value(Qt::ImSurroundingText).toString();
+ qCDebug(qLcQpaWasmInputContext) << "Qt current selection: " << queryEvent.value(Qt::ImCurrentSelection).toString();
+ qCDebug(qLcQpaWasmInputContext) << "Qt text before cursor: " << queryEvent.value(Qt::ImTextBeforeCursor).toString();
+ qCDebug(qLcQpaWasmInputContext) << "Qt text after cursor: " << queryEvent.value(Qt::ImTextAfterCursor).toString();
+ qCDebug(qLcQpaWasmInputContext) << "Qt cursor position: " << queryEvent.value(Qt::ImCursorPosition).toInt();
+ qCDebug(qLcQpaWasmInputContext) << "Qt anchor position: " << queryEvent.value(Qt::ImAnchorPosition).toInt();
+
+ m_inputElement.set("value", queryEvent.value(Qt::ImSurroundingText).toString().toStdString());
+
+ m_inputElement.set("selectionStart", queryEvent.value(Qt::ImAnchorPosition).toUInt());
+ m_inputElement.set("selectionEnd", queryEvent.value(Qt::ImCursorPosition).toUInt());
}
-void QWasmInputContext::update(Qt::InputMethodQueries queries)
+void QWasmInputContext::setFocusObject(QObject *object)
{
- QPlatformInputContext::update(queries);
+ qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << object << inputMethodAccepted();
+
+ // Commit the previous composition before change m_focusObject
+ if (m_focusObject && !m_preeditString.isEmpty())
+ commitPreeditAndClear();
+
+ if (inputMethodAccepted()) {
+ m_inputElement.call<void>("focus");
+ m_usingTextInput = true;
+
+ m_focusObject = object;
+ } else {
+ m_inputElement.call<void>("blur");
+ m_usingTextInput = false;
+
+ m_focusObject = nullptr;
+ }
+ QPlatformInputContext::setFocusObject(object);
}
-void QWasmInputContext::showInputPanel()
+void QWasmInputContext::hideInputPanel()
{
- if (platform() == Platform::Windows
- && !inputPanelIsOpen) { // call this only once for win32
- m_inputElement.call<void>("focus");
- return;
- }
- // this is called each time the keyboard is touched
-
- // Add the input element as a child of the screen for the
- // currently focused window and give it focus. The browser
- // will not display the input element, but mobile browsers
- // should display the virtual keyboard. Key events will be
- // captured by the keyboard event handler installed on the
- // screen element.
-
- if (platform() == Platform::MacOS // keep for compatibility
- || platform() == Platform::iOS
- || platform() == Platform::Windows) {
- emscripten::val inputWrapper = inputHandlerElementForFocusedWindow();
- if (inputWrapper.isUndefined())
- return;
- inputWrapper.call<void>("appendChild", m_inputElement);
- }
+ qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
- m_inputElement.call<void>("focus");
- inputPanelIsOpen = true;
+ // hide only if m_focusObject does not exist
+ if (!m_focusObject) {
+ m_inputElement.call<void>("blur");
+ m_usingTextInput = false;
+ }
}
-void QWasmInputContext::hideInputPanel()
+void QWasmInputContext::setPreeditString(QString preeditStr, int replaceSize)
{
- if (QWasmIntegration::get()->touchPoints < 1)
- return;
- m_inputElement.call<void>("blur");
- inputPanelIsOpen = false;
+ m_preeditString = preeditStr;
+ m_replaceSize = replaceSize;
}
-void QWasmInputContext::inputStringChanged(QString &inputString, int eventType, QWasmInputContext *context)
+void QWasmInputContext::insertPreedit()
{
- Q_UNUSED(context)
- QKeySequence keys = QKeySequence::fromString(inputString);
- Qt::Key thisKey = keys[0].key();
+ qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << m_preeditString;
- // synthesize this keyevent as android is not normal
- if (inputString.size() > 2 && (thisKey < Qt::Key_F35
- || thisKey > Qt::Key_Back)) {
- inputString.clear();
- }
- if (inputString == QStringLiteral("Escape")) {
- thisKey = Qt::Key_Escape;
- inputString.clear();
- } else if (thisKey == Qt::Key(0)) {
- thisKey = Qt::Key_Return;
+ QList<QInputMethodEvent::Attribute> attributes;
+ {
+ QInputMethodEvent::Attribute attr_cursor(QInputMethodEvent::Cursor,
+ m_preeditString.length(),
+ 1);
+ attributes.append(attr_cursor);
+
+ QTextCharFormat format;
+ format.setFontUnderline(true);
+ format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
+ QInputMethodEvent::Attribute attr_format(QInputMethodEvent::TextFormat,
+ 0,
+ m_preeditString.length(), format);
+ attributes.append(attr_format);
}
- QWindowSystemInterface::handleKeyEvent(
- 0, eventType == EMSCRIPTEN_EVENT_KEYDOWN ? QEvent::KeyPress : QEvent::KeyRelease,
- thisKey, keys[0].keyboardModifiers(),
- eventType == EMSCRIPTEN_EVENT_KEYDOWN ? inputString : QStringLiteral(""));
+ QInputMethodEvent e(m_preeditString, attributes);
+ if (m_replaceSize > 0)
+ e.setCommitString("", -m_replaceSize, m_replaceSize);
+ QCoreApplication::sendEvent(m_focusObject, &e);
+}
+
+void QWasmInputContext::commitPreeditAndClear()
+{
+ qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << m_preeditString;
+
+ if (m_preeditString.isEmpty())
+ return;
+ QInputMethodEvent e;
+ e.setCommitString(m_preeditString);
+ m_preeditString.clear();
+ QCoreApplication::sendEvent(m_focusObject, &e);
}
+void QWasmInputContext::insertText(QString inputStr, bool replace)
+{
+ Q_UNUSED(replace);
+ if (!inputStr.isEmpty()) {
+ const int replaceLen = 0;
+ QInputMethodEvent e;
+ e.setCommitString(inputStr, -replaceLen, replaceLen);
+ QCoreApplication::sendEvent(m_focusObject, &e);
+ }
+}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasminputcontext.h b/src/plugins/platforms/wasm/qwasminputcontext.h
index 10dd1a09506..f7683e8e96e 100644
--- a/src/plugins/platforms/wasm/qwasminputcontext.h
+++ b/src/plugins/platforms/wasm/qwasminputcontext.h
@@ -6,7 +6,6 @@
#include <qpa/qplatforminputcontext.h>
-#include <QtCore/qpointer.h>
#include <private/qstdweb_p.h>
#include <emscripten/bind.h>
#include <emscripten/html5.h>
@@ -14,6 +13,8 @@
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(qLcQpaWasmInputContext)
+
class QWasmInputContext : public QPlatformInputContext
{
Q_DISABLE_COPY(QWasmInputContext)
@@ -28,19 +29,24 @@ public:
void hideInputPanel() override;
bool isValid() const override { return true; }
- void focusWindowChanged(QWindow *focusWindow);
- void inputStringChanged(QString &, int eventType, QWasmInputContext *context);
+ const QString preeditString() { return m_preeditString; }
+ void setPreeditString(QString preeditStr, int replaceSize);
+ void insertPreedit();
+ void commitPreeditAndClear();
emscripten::val m_inputElement = emscripten::val::null();
-private:
- emscripten::val inputHandlerElementForFocusedWindow();
+ void insertText(QString inputStr, bool replace = false);
- bool m_inputPanelVisible = false;
+ bool usingTextInput() { return m_usingTextInput; }
+ void setUsingTextInput(bool enable) { m_usingTextInput = enable; }
+ void setFocusObject(QObject *object) override;
+
+private:
+ QString m_preeditString;
+ int m_replaceSize = 0;
- QPointer<QWindow> m_focusWindow;
- std::unique_ptr<qstdweb::EventCallback> m_blurEventHandler;
- std::unique_ptr<qstdweb::EventCallback> m_inputEventHandler;
- bool inputPanelIsOpen = false;
+ bool m_usingTextInput = false;
+ QObject *m_focusObject = nullptr;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmintegration.cpp b/src/plugins/platforms/wasm/qwasmintegration.cpp
index f5cc3e2eee6..e4373749d82 100644
--- a/src/plugins/platforms/wasm/qwasmintegration.cpp
+++ b/src/plugins/platforms/wasm/qwasmintegration.cpp
@@ -157,8 +157,6 @@ QWasmIntegration::~QWasmIntegration()
delete m_fontDb;
delete m_desktopServices;
- if (m_platformInputContext)
- delete m_platformInputContext;
#if QT_CONFIG(accessibility)
delete m_accessibility;
#endif
@@ -226,13 +224,13 @@ QPlatformOpenGLContext *QWasmIntegration::createPlatformOpenGLContext(QOpenGLCon
void QWasmIntegration::initialize()
{
auto icStrs = QPlatformInputContextFactory::requested();
- if (icStrs.isEmpty() && touchPoints < 1)
- return;
-
- if (!icStrs.isEmpty())
+ if (!icStrs.isEmpty()) {
m_inputContext.reset(QPlatformInputContextFactory::create(icStrs));
- else
+ m_wasmInputContext = nullptr;
+ } else {
m_inputContext.reset(new QWasmInputContext());
+ m_wasmInputContext = static_cast<QWasmInputContext *>(m_inputContext.data());
+ }
}
QPlatformInputContext *QWasmIntegration::inputContext() const
diff --git a/src/plugins/platforms/wasm/qwasmintegration.h b/src/plugins/platforms/wasm/qwasmintegration.h
index 870bd0d16b4..99ccf333fb2 100644
--- a/src/plugins/platforms/wasm/qwasmintegration.h
+++ b/src/plugins/platforms/wasm/qwasmintegration.h
@@ -61,13 +61,13 @@ public:
#endif
void initialize() override;
QPlatformInputContext *inputContext() const override;
+ QWasmInputContext *wasmInputContext() const { return m_wasmInputContext; }
#if QT_CONFIG(draganddrop)
QPlatformDrag *drag() const override;
#endif
QWasmClipboard *getWasmClipboard() { return m_clipboard; }
- QWasmInputContext *getWasmInputContext() { return m_platformInputContext; }
static QWasmIntegration *get() { return s_instance; }
void setContainerElements(emscripten::val elementArray);
@@ -100,7 +100,7 @@ private:
mutable QScopedPointer<QPlatformInputContext> m_inputContext;
static QWasmIntegration *s_instance;
- mutable QWasmInputContext *m_platformInputContext = nullptr;
+ QWasmInputContext *m_wasmInputContext = nullptr;
#if QT_CONFIG(draganddrop)
std::unique_ptr<QWasmDrag> m_drag;
diff --git a/src/plugins/platforms/wasm/qwasmwindow.cpp b/src/plugins/platforms/wasm/qwasmwindow.cpp
index 174fff174e7..eb6b1cd2e3c 100644
--- a/src/plugins/platforms/wasm/qwasmwindow.cpp
+++ b/src/plugins/platforms/wasm/qwasmwindow.cpp
@@ -30,6 +30,7 @@
#include <emscripten/val.h>
#include <QtCore/private/qstdweb_p.h>
+#include <QKeySequence>
QT_BEGIN_NAMESPACE
@@ -106,6 +107,15 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport,
m_flags = window()->flags();
const auto pointerCallback = std::function([this](emscripten::val event) {
+ const auto eventTypeString = event["type"].as<std::string>();
+
+ // Ideally it should not be happened but
+ // it takes place sometime with some reason
+ // without compositionend.
+ QWasmInputContext *wasmInput = QWasmIntegration::get()->wasmInputContext();
+ if (wasmInput && !wasmInput->preeditString().isEmpty())
+ wasmInput->commitPreeditAndClear();
+
if (processPointer(*PointerEvent::fromWeb(event)))
event.call<void>("preventDefault");
});
@@ -121,25 +131,74 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport,
event.call<void>("preventDefault");
});
+ const auto keyCallbackForInputContext = std::function([this](emscripten::val event) {
+ QWasmInputContext *wasmInput = QWasmIntegration::get()->wasmInputContext();
+ if (wasmInput) {
+ const auto 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["ctrlKey"].as<bool>()
+ || event["altKey"].as<bool>()
+ || event["metaKey"].as<bool>()) {
+ if (processKeyForInputContext(*KeyEvent::fromWebWithDeadKeyTranslation(event, m_deadKeySupport)))
+ event.call<void>("preventDefault");
+ event.call<void>("stopImmediatePropagation");
+ return;
+ } else if (keyString.size() != 1) {
+ if (!wasmInput->preeditString().isEmpty()) {
+ if (keyString == "Process" || keyString == "Backspace") {
+ // processed by InputContext
+ // "Process" should be handled by InputContext but
+ // QWasmInputContext's function is incomplete now
+ // so, there will be some exceptions here.
+ return;
+ } else if (keyString != "Shift"
+ && keyString != "Meta"
+ && keyString != "Alt"
+ && keyString != "Control"
+ && !keyString.startsWith("Arrow")) {
+ wasmInput->commitPreeditAndClear();
+ }
+ }
+ } else if (wasmInput->inputMethodAccepted()) {
+ // processed in inputContext with skipping processKey
+ return;
+ }
+ }
+
+ qCDebug(qLcQpaWasmInputContext) << "processKey as KeyEvent";
+ if (processKeyForInputContext(*KeyEvent::fromWebWithDeadKeyTranslation(event, m_deadKeySupport)))
+ event.call<void>("preventDefault");
+ event.call<void>("stopImmediatePropagation");
+ });
+
const auto keyCallback = std::function([this](emscripten::val event) {
+ qCDebug(qLcQpaWasmInputContext) << "processKey as KeyEvent";
if (processKey(*KeyEvent::fromWebWithDeadKeyTranslation(event, m_deadKeySupport)))
event.call<void>("preventDefault");
event.call<void>("stopPropagation");
});
- emscripten::val keyFocusWindow;
- if (QWasmInputContext *wasmContext =
- qobject_cast<QWasmInputContext *>(QWasmIntegration::get()->inputContext())) {
- // if there is an touchscreen input context,
- // use that window for key input
- keyFocusWindow = wasmContext->m_inputElement;
- } else {
- keyFocusWindow = m_qtWindow;
+ QWasmInputContext *wasmInput = QWasmIntegration::get()->wasmInputContext();
+ if (wasmInput) {
+ m_keyDownCallbackForInputContext =
+ std::make_unique<qstdweb::EventCallback>(wasmInput->m_inputElement,
+ "keydown",
+ keyCallbackForInputContext);
+ m_keyUpCallbackForInputContext =
+ std::make_unique<qstdweb::EventCallback>(wasmInput->m_inputElement,
+ "keyup",
+ keyCallbackForInputContext);
}
m_keyDownCallback =
- std::make_unique<qstdweb::EventCallback>(keyFocusWindow, "keydown", keyCallback);
- m_keyUpCallback = std::make_unique<qstdweb::EventCallback>(keyFocusWindow, "keyup", keyCallback);
+ std::make_unique<qstdweb::EventCallback>(m_qtWindow, "keydown", keyCallback);
+ m_keyUpCallback =std::make_unique<qstdweb::EventCallback>(m_qtWindow, "keyup", keyCallback);
+
setParent(parent());
}
@@ -356,8 +415,6 @@ void QWasmWindow::raise()
{
bringToTop();
invalidate();
- if (QWasmIntegration::get()->inputContext())
- m_canvas.call<void>("focus");
}
void QWasmWindow::lower()
@@ -526,6 +583,29 @@ bool QWasmWindow::processKey(const KeyEvent &event)
: result;
}
+bool QWasmWindow::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 inputCallback
+ return false;
+ }
+
+ const auto result = QWindowSystemInterface::handleKeyEvent(
+ 0, event.type == EventType::KeyDown ? QEvent::KeyPress : QEvent::KeyRelease, event.key,
+ event.modifiers, event.text);
+
+ // Copy/Cut callback required to copy qtClipboard to system clipboard
+ if (keySeq == QKeySequence::Copy || keySeq == QKeySequence::Cut)
+ return false;
+
+ return result;
+}
+
bool QWasmWindow::processPointer(const PointerEvent &event)
{
if (event.pointerType != PointerType::Mouse && event.pointerType != PointerType::Pen)
diff --git a/src/plugins/platforms/wasm/qwasmwindow.h b/src/plugins/platforms/wasm/qwasmwindow.h
index ab0dc68e839..480ad6de721 100644
--- a/src/plugins/platforms/wasm/qwasmwindow.h
+++ b/src/plugins/platforms/wasm/qwasmwindow.h
@@ -35,6 +35,8 @@ struct PointerEvent;
class QWasmDeadKeySupport;
struct WheelEvent;
+Q_DECLARE_LOGGING_CATEGORY(qLcQpaWasmInputContext)
+
class QWasmWindow final : public QPlatformWindow,
public QWasmWindowTreeNode,
public QNativeInterface::Private::QWasmWindow
@@ -122,6 +124,7 @@ private:
void commitParent(QWasmWindowTreeNode *parent);
bool processKey(const KeyEvent &event);
+ bool processKeyForInputContext(const KeyEvent &event);
bool processPointer(const PointerEvent &event);
bool processWheel(const WheelEvent &event);
@@ -146,6 +149,8 @@ private:
std::unique_ptr<qstdweb::EventCallback> m_keyDownCallback;
std::unique_ptr<qstdweb::EventCallback> m_keyUpCallback;
+ std::unique_ptr<qstdweb::EventCallback> m_keyDownCallbackForInputContext;
+ std::unique_ptr<qstdweb::EventCallback> m_keyUpCallbackForInputContext;
std::unique_ptr<qstdweb::EventCallback> m_pointerLeaveCallback;
std::unique_ptr<qstdweb::EventCallback> m_pointerEnterCallback;