/**************************************************************************** ** ** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 or (at your option) any later version ** approved by the KDE Free Qt Foundation. The licenses are as published by ** the Free Software Foundation and appearing in the file LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qwasmeventtranslator.h" #include "qwasmeventdispatcher.h" #include "qwasmcompositor.h" #include "qwasmintegration.h" #include "qwasmclipboard.h" #include #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE using namespace emscripten; typedef struct emkb2qt { const char *em; unsigned int qt; constexpr bool operator <=(const emkb2qt &that) const noexcept { return !(strcmp(that) > 0); } bool operator <(const emkb2qt &that) const noexcept { return ::strcmp(em, that.em) < 0; } constexpr int strcmp(const emkb2qt &that, const int i = 0) const { return em[i] == 0 && that.em[i] == 0 ? 0 : em[i] == 0 ? -1 : that.em[i] == 0 ? 1 : em[i] < that.em[i] ? -1 : em[i] > that.em[i] ? 1 : strcmp(that, i + 1); } } emkb2qt_t; template struct Emkb2Qt { static constexpr const char storage[sizeof ... (EmChar) + 1] = {EmChar..., '\0'}; using Type = emkb2qt_t; static constexpr Type data() noexcept { return Type{storage, Qt}; } }; template constexpr char Emkb2Qt::storage[]; static constexpr const auto KeyTbl = qMakeArray( QSortedData< Emkb2Qt< Qt::Key_Escape, 'E','s','c','a','p','e' >, Emkb2Qt< Qt::Key_Tab, 'T','a','b' >, Emkb2Qt< Qt::Key_Backspace, 'B','a','c','k','s','p','a','c','e' >, Emkb2Qt< Qt::Key_Return, 'E','n','t','e','r' >, Emkb2Qt< Qt::Key_Insert, 'I','n','s','e','r','t' >, Emkb2Qt< Qt::Key_Delete, 'D','e','l','e','t','e' >, Emkb2Qt< Qt::Key_Pause, 'P','a','u','s','e' >, Emkb2Qt< Qt::Key_Pause, 'C','l','e','a','r' >, Emkb2Qt< Qt::Key_Home, 'H','o','m','e' >, Emkb2Qt< Qt::Key_End, 'E','n','d' >, Emkb2Qt< Qt::Key_Left, 'A','r','r','o','w','L','e','f','t' >, Emkb2Qt< Qt::Key_Up, 'A','r','r','o','w','U','p' >, Emkb2Qt< Qt::Key_Right, 'A','r','r','o','w','R','i','g','h','t' >, Emkb2Qt< Qt::Key_Down, 'A','r','r','o','w','D','o','w','n' >, Emkb2Qt< Qt::Key_PageUp, 'P','a','g','e','U','p' >, Emkb2Qt< Qt::Key_PageDown, 'P','a','g','e','D','o','w','n' >, Emkb2Qt< Qt::Key_Shift, 'S','h','i','f','t' >, Emkb2Qt< Qt::Key_Control, 'C','o','n','t','r','o','l' >, Emkb2Qt< Qt::Key_Meta, 'O','S'>, Emkb2Qt< Qt::Key_Alt, 'A','l','t','L','e','f','t' >, Emkb2Qt< Qt::Key_Alt, 'A','l','t' >, Emkb2Qt< Qt::Key_CapsLock, 'C','a','p','s','L','o','c','k' >, Emkb2Qt< Qt::Key_NumLock, 'N','u','m','L','o','c','k' >, Emkb2Qt< Qt::Key_ScrollLock, 'S','c','r','o','l','l','L','o','c','k' >, Emkb2Qt< Qt::Key_F1, 'F','1' >, Emkb2Qt< Qt::Key_F2, 'F','2' >, Emkb2Qt< Qt::Key_F3, 'F','3' >, Emkb2Qt< Qt::Key_F4, 'F','4' >, Emkb2Qt< Qt::Key_F5, 'F','5' >, Emkb2Qt< Qt::Key_F6, 'F','6' >, Emkb2Qt< Qt::Key_F7, 'F','7' >, Emkb2Qt< Qt::Key_F8, 'F','8' >, Emkb2Qt< Qt::Key_F9, 'F','9' >, Emkb2Qt< Qt::Key_F10, 'F','1','0' >, Emkb2Qt< Qt::Key_F11, 'F','1','1' >, Emkb2Qt< Qt::Key_F12, 'F','1','2' >, Emkb2Qt< Qt::Key_F13, 'F','1','3' >, Emkb2Qt< Qt::Key_F14, 'F','1','4' >, Emkb2Qt< Qt::Key_F15, 'F','1','5' >, Emkb2Qt< Qt::Key_F16, 'F','1','6' >, Emkb2Qt< Qt::Key_F17, 'F','1','7' >, Emkb2Qt< Qt::Key_F18, 'F','1','8' >, Emkb2Qt< Qt::Key_F19, 'F','1','9' >, Emkb2Qt< Qt::Key_F20, 'F','2','0' >, Emkb2Qt< Qt::Key_F21, 'F','2','1' >, Emkb2Qt< Qt::Key_F22, 'F','2','2' >, Emkb2Qt< Qt::Key_F23, 'F','2','3' >, Emkb2Qt< Qt::Key_Space, ' ' >, Emkb2Qt< Qt::Key_Comma, ',' >, Emkb2Qt< Qt::Key_Minus, '-' >, Emkb2Qt< Qt::Key_Period, '.' >, Emkb2Qt< Qt::Key_Slash, '/' >, Emkb2Qt< Qt::Key_0, 'D','i','g','i','t','0' >, Emkb2Qt< Qt::Key_1, 'D','i','g','i','t','1' >, Emkb2Qt< Qt::Key_2, 'D','i','g','i','t','2' >, Emkb2Qt< Qt::Key_3, 'D','i','g','i','t','3' >, Emkb2Qt< Qt::Key_4, 'D','i','g','i','t','4' >, Emkb2Qt< Qt::Key_5, 'D','i','g','i','t','5' >, Emkb2Qt< Qt::Key_6, 'D','i','g','i','t','6' >, Emkb2Qt< Qt::Key_7, 'D','i','g','i','t','7' >, Emkb2Qt< Qt::Key_8, 'D','i','g','i','t','8' >, Emkb2Qt< Qt::Key_9, 'D','i','g','i','t','9' >, Emkb2Qt< Qt::Key_Semicolon, ';' >, Emkb2Qt< Qt::Key_Equal, '=' >, Emkb2Qt< Qt::Key_A, 'K','e','y','A' >, Emkb2Qt< Qt::Key_B, 'K','e','y','B' >, Emkb2Qt< Qt::Key_C, 'K','e','y','C' >, Emkb2Qt< Qt::Key_D, 'K','e','y','D' >, Emkb2Qt< Qt::Key_E, 'K','e','y','E' >, Emkb2Qt< Qt::Key_F, 'K','e','y','F' >, Emkb2Qt< Qt::Key_G, 'K','e','y','G' >, Emkb2Qt< Qt::Key_H, 'K','e','y','H' >, Emkb2Qt< Qt::Key_I, 'K','e','y','I' >, Emkb2Qt< Qt::Key_J, 'K','e','y','J' >, Emkb2Qt< Qt::Key_K, 'K','e','y','K' >, Emkb2Qt< Qt::Key_L, 'K','e','y','L' >, Emkb2Qt< Qt::Key_M, 'K','e','y','M' >, Emkb2Qt< Qt::Key_N, 'K','e','y','N' >, Emkb2Qt< Qt::Key_O, 'K','e','y','O' >, Emkb2Qt< Qt::Key_P, 'K','e','y','P' >, Emkb2Qt< Qt::Key_Q, 'K','e','y','Q' >, Emkb2Qt< Qt::Key_R, 'K','e','y','R' >, Emkb2Qt< Qt::Key_S, 'K','e','y','S' >, Emkb2Qt< Qt::Key_T, 'K','e','y','T' >, Emkb2Qt< Qt::Key_U, 'K','e','y','U' >, Emkb2Qt< Qt::Key_V, 'K','e','y','V' >, Emkb2Qt< Qt::Key_W, 'K','e','y','W' >, Emkb2Qt< Qt::Key_X, 'K','e','y','X' >, Emkb2Qt< Qt::Key_Y, 'K','e','y','Y' >, Emkb2Qt< Qt::Key_Z, 'K','e','y','Z' >, Emkb2Qt< Qt::Key_BracketLeft, '[' >, Emkb2Qt< Qt::Key_Backslash, '\\' >, Emkb2Qt< Qt::Key_BracketRight, ']' >, Emkb2Qt< Qt::Key_Apostrophe, '\'' >, Emkb2Qt< Qt::Key_QuoteLeft, 'B','a','c','k','q','u','o','t','e' >, Emkb2Qt< Qt::Key_multiply, 'N','u','m','p','a','d','M','u','l','t','i','p','l','y' >, Emkb2Qt< Qt::Key_Minus, 'N','u','m','p','a','d','S','u','b','t','r','a','c','t' >, Emkb2Qt< Qt::Key_Period, 'N','u','m','p','a','d','D','e','c','i','m','a','l' >, Emkb2Qt< Qt::Key_Plus, 'N','u','m','p','a','d','A','d','d' >, Emkb2Qt< Qt::Key_division, 'N','u','m','p','a','d','D','i','v','i','d','e' >, Emkb2Qt< Qt::Key_Equal, 'N','u','m','p','a','d','E','q','u','a','l' >, Emkb2Qt< Qt::Key_0, 'N','u','m','p','a','d','0' >, Emkb2Qt< Qt::Key_1, 'N','u','m','p','a','d','1' >, Emkb2Qt< Qt::Key_2, 'N','u','m','p','a','d','2' >, Emkb2Qt< Qt::Key_3, 'N','u','m','p','a','d','3' >, Emkb2Qt< Qt::Key_4, 'N','u','m','p','a','d','4' >, Emkb2Qt< Qt::Key_5, 'N','u','m','p','a','d','5' >, Emkb2Qt< Qt::Key_6, 'N','u','m','p','a','d','6' >, Emkb2Qt< Qt::Key_7, 'N','u','m','p','a','d','7' >, Emkb2Qt< Qt::Key_8, 'N','u','m','p','a','d','8' >, Emkb2Qt< Qt::Key_9, 'N','u','m','p','a','d','9' >, Emkb2Qt< Qt::Key_Comma, 'N','u','m','p','a','d','C','o','m','m','a' >, Emkb2Qt< Qt::Key_Enter, 'N','u','m','p','a','d','E','n','t','e','r' >, Emkb2Qt< Qt::Key_Paste, 'P','a','s','t','e' >, Emkb2Qt< Qt::Key_AltGr, 'A','l','t','R','i','g','h','t' >, Emkb2Qt< Qt::Key_Help, 'H','e','l','p' >, Emkb2Qt< Qt::Key_Equal, '=' >, Emkb2Qt< Qt::Key_yen, 'I','n','t','l','Y','e','n' >, Emkb2Qt< Qt::Key_Exclam, '\x21' >, Emkb2Qt< Qt::Key_QuoteDbl, '\x22' >, Emkb2Qt< Qt::Key_NumberSign, '\x23' >, Emkb2Qt< Qt::Key_Dollar, '\x24' >, Emkb2Qt< Qt::Key_Percent, '\x25' >, Emkb2Qt< Qt::Key_Ampersand, '\x26' >, Emkb2Qt< Qt::Key_ParenLeft, '\x28' >, Emkb2Qt< Qt::Key_ParenRight, '\x29' >, Emkb2Qt< Qt::Key_Asterisk, '\x2a' >, Emkb2Qt< Qt::Key_Plus, '\x2b' >, Emkb2Qt< Qt::Key_Colon, '\x3a' >, Emkb2Qt< Qt::Key_Semicolon, '\x3b' >, Emkb2Qt< Qt::Key_Less, '\x3c' >, Emkb2Qt< Qt::Key_Equal, '\x3d' >, Emkb2Qt< Qt::Key_Greater, '\x3e' >, Emkb2Qt< Qt::Key_Question, '\x3f' >, Emkb2Qt< Qt::Key_At, '\x40' >, Emkb2Qt< Qt::Key_BracketLeft, '\x5b' >, Emkb2Qt< Qt::Key_Backslash, '\x5c' >, Emkb2Qt< Qt::Key_BracketRight, '\x5d' >, Emkb2Qt< Qt::Key_AsciiCircum, '\x5e' >, Emkb2Qt< Qt::Key_Underscore, '\x5f' >, Emkb2Qt< Qt::Key_QuoteLeft, '\x60'>, Emkb2Qt< Qt::Key_BraceLeft, '\x7b'>, Emkb2Qt< Qt::Key_Bar, '\x7c'>, Emkb2Qt< Qt::Key_BraceRight, '\x7d'>, Emkb2Qt< Qt::Key_AsciiTilde, '\x7e'>, Emkb2Qt< Qt::Key_Space, '\x20' >, Emkb2Qt< Qt::Key_Comma, '\x2c' >, Emkb2Qt< Qt::Key_Minus, '\x2d' >, Emkb2Qt< Qt::Key_Period, '\x2e' >, Emkb2Qt< Qt::Key_Slash, '\x2f' >, Emkb2Qt< Qt::Key_Apostrophe, '\x27' >, Emkb2Qt< Qt::Key_Menu, 'C','o','n','t','e','x','t','M','e','n','u' >, Emkb2Qt< Qt::Key_Agrave, '\xc3','\xa0' >, Emkb2Qt< Qt::Key_Aacute, '\xc3','\xa1' >, Emkb2Qt< Qt::Key_Acircumflex, '\xc3','\xa2' >, Emkb2Qt< Qt::Key_Adiaeresis, '\xc3','\xa4' >, Emkb2Qt< Qt::Key_AE, '\xc3','\xa6' >, Emkb2Qt< Qt::Key_Atilde, '\xc3','\xa3' >, Emkb2Qt< Qt::Key_Aring, '\xc3','\xa5' >, Emkb2Qt< Qt::Key_Ccedilla, '\xc3','\xa7' >, Emkb2Qt< Qt::Key_Egrave, '\xc3','\xa8' >, Emkb2Qt< Qt::Key_Eacute, '\xc3','\xa9' >, Emkb2Qt< Qt::Key_Ecircumflex, '\xc3','\xaa' >, Emkb2Qt< Qt::Key_Ediaeresis, '\xc3','\xab' >, Emkb2Qt< Qt::Key_Icircumflex, '\xc3','\xae' >, Emkb2Qt< Qt::Key_Idiaeresis, '\xc3','\xaf' >, Emkb2Qt< Qt::Key_Ocircumflex, '\xc3','\xb4' >, Emkb2Qt< Qt::Key_Odiaeresis, '\xc3','\xb6' >, Emkb2Qt< Qt::Key_Ograve, '\xc3','\xb2' >, Emkb2Qt< Qt::Key_Oacute, '\xc3','\xb3' >, Emkb2Qt< Qt::Key_Ooblique, '\xc3','\xb8' >, Emkb2Qt< Qt::Key_Otilde, '\xc3','\xb5' >, Emkb2Qt< Qt::Key_Ucircumflex, '\xc3','\xbb' >, Emkb2Qt< Qt::Key_Udiaeresis, '\xc3','\xbc' >, Emkb2Qt< Qt::Key_Ugrave, '\xc3','\xb9' >, Emkb2Qt< Qt::Key_Uacute, '\xc3','\xba' >, Emkb2Qt< Qt::Key_Ntilde, '\xc3','\xb1' >, Emkb2Qt< Qt::Key_ydiaeresis, '\xc3','\xbf' > >::Data{} ); static constexpr const auto DeadKeyShiftTbl = qMakeArray( QSortedData< // shifted Emkb2Qt< Qt::Key_Agrave, '\xc3','\x80' >, Emkb2Qt< Qt::Key_Aacute, '\xc3','\x81' >, Emkb2Qt< Qt::Key_Acircumflex, '\xc3','\x82' >, Emkb2Qt< Qt::Key_Adiaeresis, '\xc3','\x84' >, Emkb2Qt< Qt::Key_AE, '\xc3','\x86' >, Emkb2Qt< Qt::Key_Atilde, '\xc3','\x83' >, Emkb2Qt< Qt::Key_Aring, '\xc3','\x85' >, Emkb2Qt< Qt::Key_Egrave, '\xc3','\x88' >, Emkb2Qt< Qt::Key_Eacute, '\xc3','\x89' >, Emkb2Qt< Qt::Key_Ecircumflex, '\xc3','\x8a' >, Emkb2Qt< Qt::Key_Ediaeresis, '\xc3','\x8b' >, Emkb2Qt< Qt::Key_Icircumflex, '\xc3','\x8e' >, Emkb2Qt< Qt::Key_Idiaeresis, '\xc3','\x8f' >, Emkb2Qt< Qt::Key_Ocircumflex, '\xc3','\x94' >, Emkb2Qt< Qt::Key_Odiaeresis, '\xc3','\x96' >, Emkb2Qt< Qt::Key_Ograve, '\xc3','\x92' >, Emkb2Qt< Qt::Key_Oacute, '\xc3','\x93' >, Emkb2Qt< Qt::Key_Ooblique, '\xc3','\x98' >, Emkb2Qt< Qt::Key_Otilde, '\xc3','\x95' >, Emkb2Qt< Qt::Key_Ucircumflex, '\xc3','\x9b' >, Emkb2Qt< Qt::Key_Udiaeresis, '\xc3','\x9c' >, Emkb2Qt< Qt::Key_Ugrave, '\xc3','\x99' >, Emkb2Qt< Qt::Key_Uacute, '\xc3','\x9a' >, Emkb2Qt< Qt::Key_Ntilde, '\xc3','\x91' >, Emkb2Qt< Qt::Key_Ccedilla, '\xc3','\x87' >, Emkb2Qt< Qt::Key_ydiaeresis, '\xc3','\x8f' > >::Data{} ); // macOS CTRL <-> META switching. We most likely want to enable // the existing switching code in QtGui, but for now do it here. static bool g_usePlatformMacCtrlMetaSwitching = false; bool g_useNaturalScrolling = true; // natural scrolling is default on linux/windows static void mouseWheelEvent(emscripten::val event) { emscripten::val wheelInterted = event["webkitDirectionInvertedFromDevice"]; if (wheelInterted.as()) { g_useNaturalScrolling = true; } } EMSCRIPTEN_BINDINGS(qtMouseModule) { function("qtMouseWheelEvent", &mouseWheelEvent); } QWasmEventTranslator::QWasmEventTranslator(QWasmScreen *screen) : QObject(screen) , draggedWindow(nullptr) , lastWindow(nullptr) , pressedButtons(Qt::NoButton) , resizeMode(QWasmWindow::ResizeNone) { touchDevice = new QTouchDevice; touchDevice->setType(QTouchDevice::TouchScreen); touchDevice->setCapabilities(QTouchDevice::Position | QTouchDevice::Area | QTouchDevice::NormalizedPosition); QWindowSystemInterface::registerTouchDevice(touchDevice); initEventHandlers(); } void QWasmEventTranslator::initEventHandlers() { QByteArray _canvasId = screen()->canvasId().toUtf8(); const char *canvasId = _canvasId.constData(); // The Platform Detect: expand coverage and move as needed enum Platform { GenericPlatform, MacOSPlatform }; Platform platform = Platform(emscripten::val::global("navigator")["platform"] .call("includes", emscripten::val("Mac"))); g_usePlatformMacCtrlMetaSwitching = (platform == MacOSPlatform); if (platform == MacOSPlatform) { g_useNaturalScrolling = false; // make this !default on macOS if (emscripten::val::global("window")["safari"].isUndefined()) { emscripten::val::global(canvasId).call("addEventListener", std::string("wheel"), val::module_property("qtMouseWheelEvent")); } } emscripten_set_keydown_callback(canvasId, (void *)this, 1, &keyboard_cb); emscripten_set_keyup_callback(canvasId, (void *)this, 1, &keyboard_cb); emscripten_set_mousedown_callback(canvasId, (void *)this, 1, &mouse_cb); emscripten_set_mouseup_callback(canvasId, (void *)this, 1, &mouse_cb); emscripten_set_mousemove_callback(canvasId, (void *)this, 1, &mouse_cb); emscripten_set_focus_callback(canvasId, (void *)this, 1, &focus_cb); emscripten_set_wheel_callback(canvasId, (void *)this, 1, &wheel_cb); emscripten_set_touchstart_callback(canvasId, (void *)this, 1, &touchCallback); emscripten_set_touchend_callback(canvasId, (void *)this, 1, &touchCallback); emscripten_set_touchmove_callback(canvasId, (void *)this, 1, &touchCallback); emscripten_set_touchcancel_callback(canvasId, (void *)this, 1, &touchCallback); } template QFlags QWasmEventTranslator::translatKeyModifier(const Event *event) { QFlags keyModifier = Qt::NoModifier; if (event->shiftKey) keyModifier |= Qt::ShiftModifier; if (event->ctrlKey) { if (g_usePlatformMacCtrlMetaSwitching) keyModifier |= Qt::MetaModifier; else keyModifier |= Qt::ControlModifier; } if (event->altKey) keyModifier |= Qt::AltModifier; if (event->metaKey) { if (g_usePlatformMacCtrlMetaSwitching) keyModifier |= Qt::ControlModifier; else keyModifier |= Qt::MetaModifier; } return keyModifier; } QFlags QWasmEventTranslator::translateKeyboardEventModifier(const EmscriptenKeyboardEvent *keyEvent) { QFlags keyModifier = translatKeyModifier(keyEvent); if (keyEvent->location == DOM_KEY_LOCATION_NUMPAD) { keyModifier |= Qt::KeypadModifier; } return keyModifier; } QFlags QWasmEventTranslator::translateMouseEventModifier(const EmscriptenMouseEvent *mouseEvent) { return translatKeyModifier(mouseEvent); } int QWasmEventTranslator::keyboard_cb(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData) { QWasmEventTranslator *wasmTranslator = reinterpret_cast(userData); bool accepted = wasmTranslator->processKeyboard(eventType, keyEvent); return accepted ? 1 : 0; } QWasmScreen *QWasmEventTranslator::screen() { return static_cast(parent()); } Qt::Key QWasmEventTranslator::translateEmscriptKey(const EmscriptenKeyboardEvent *emscriptKey) { Qt::Key qtKey = Qt::Key_unknown; if (qstrncmp(emscriptKey->code, "Key", 3) == 0 || qstrncmp(emscriptKey->code, "Numpad", 6) == 0 || qstrncmp(emscriptKey->code, "Digit", 5) == 0) { emkb2qt_t searchKey{emscriptKey->code, 0}; // search emcsripten code auto it1 = std::lower_bound(KeyTbl.cbegin(), KeyTbl.cend(), searchKey); if (it1 != KeyTbl.end() && !(searchKey < *it1)) { qtKey = static_cast(it1->qt); } } else if (qstrncmp(emscriptKey->key, "Dead", 4) == 0 ) { emkb2qt_t searchKey1{emscriptKey->code, 0}; for (auto it1 = KeyTbl.cbegin(); it1 != KeyTbl.end(); ++it1) if (it1 != KeyTbl.end() && (qstrcmp(searchKey1.em, it1->em) == 0)) { qtKey = static_cast(it1->qt); } } if (qtKey == Qt::Key_unknown) { emkb2qt_t searchKey{emscriptKey->key, 0}; // search unicode key auto it1 = std::lower_bound(KeyTbl.cbegin(), KeyTbl.cend(), searchKey); if (it1 != KeyTbl.end() && !(searchKey < *it1)) { qtKey = static_cast(it1->qt); } } if (qtKey == Qt::Key_unknown) {//try harder with shifted number keys emkb2qt_t searchKey1{emscriptKey->key, 0}; for (auto it1 = KeyTbl.cbegin(); it1 != KeyTbl.end(); ++it1) if (it1 != KeyTbl.end() && (qstrcmp(searchKey1.em, it1->em) == 0)) { qtKey = static_cast(it1->qt); } } return qtKey; } Qt::MouseButton QWasmEventTranslator::translateMouseButton(unsigned short button) { if (button == 0) return Qt::LeftButton; else if (button == 1) return Qt::MiddleButton; else if (button == 2) return Qt::RightButton; return Qt::NoButton; } int QWasmEventTranslator::mouse_cb(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) { QWasmEventTranslator *translator = (QWasmEventTranslator*)userData; translator->processMouse(eventType,mouseEvent); QWasmEventDispatcher::maintainTimers(); return 0; } void resizeWindow(QWindow *window, QWasmWindow::ResizeMode mode, QRect startRect, QPoint amount) { if (mode == QWasmWindow::ResizeNone) return; bool top = mode == QWasmWindow::ResizeTopLeft || mode == QWasmWindow::ResizeTop || mode == QWasmWindow::ResizeTopRight; bool bottom = mode == QWasmWindow::ResizeBottomLeft || mode == QWasmWindow::ResizeBottom || mode == QWasmWindow::ResizeBottomRight; bool left = mode == QWasmWindow::ResizeLeft || mode == QWasmWindow::ResizeTopLeft || mode == QWasmWindow::ResizeBottomLeft; bool right = mode == QWasmWindow::ResizeRight || mode == QWasmWindow::ResizeTopRight || mode == QWasmWindow::ResizeBottomRight; int x1 = startRect.left(); int y1 = startRect.top(); int x2 = startRect.right(); int y2 = startRect.bottom(); if (left) x1 += amount.x(); if (top) y1 += amount.y(); if (right) x2 += amount.x(); if (bottom) y2 += amount.y(); int w = x2-x1; int h = y2-y1; if (w < window->minimumWidth()) { if (left) x1 -= window->minimumWidth() - w; w = window->minimumWidth(); } if (h < window->minimumHeight()) { if (top) y1 -= window->minimumHeight() - h; h = window->minimumHeight(); } window->setGeometry(x1, y1, w, h); } void QWasmEventTranslator::processMouse(int eventType, const EmscriptenMouseEvent *mouseEvent) { auto timestamp = mouseEvent->timestamp; QPoint targetPoint(mouseEvent->targetX, mouseEvent->targetY); QPoint globalPoint = screen()->geometry().topLeft() + targetPoint; QEvent::Type buttonEventType = QEvent::None; Qt::MouseButton button = translateMouseButton(mouseEvent->button); Qt::KeyboardModifiers modifiers = translateMouseEventModifier(mouseEvent); QWindow *window2 = screen()->compositor()->windowAt(globalPoint, 5); if (window2 == nullptr) { window2 = lastWindow; } else { lastWindow = window2; } QPoint localPoint = window2->mapFromGlobal(globalPoint); bool interior = window2->geometry().contains(globalPoint); QWasmWindow *htmlWindow = static_cast(window2->handle()); switch (eventType) { case EMSCRIPTEN_EVENT_MOUSEDOWN: { if (window2) window2->requestActivate(); pressedButtons.setFlag(button); if (mouseEvent->button == 0) { pressedWindow = window2; buttonEventType = QEvent::MouseButtonPress; if (!(htmlWindow->m_windowState & Qt::WindowFullScreen) && !(htmlWindow->m_windowState & Qt::WindowMaximized)) { if (htmlWindow && window2->flags().testFlag(Qt::WindowTitleHint) && htmlWindow->isPointOnTitle(globalPoint)) draggedWindow = window2; else if (htmlWindow && htmlWindow->isPointOnResizeRegion(globalPoint)) { draggedWindow = window2; resizeMode = htmlWindow->resizeModeAtPoint(globalPoint); resizePoint = globalPoint; resizeStartRect = window2->geometry(); } } } htmlWindow->injectMousePressed(localPoint, globalPoint, button, modifiers); break; } case EMSCRIPTEN_EVENT_MOUSEUP: { pressedButtons.setFlag(translateMouseButton(mouseEvent->button), false); buttonEventType = QEvent::MouseButtonRelease; QWasmWindow *oldWindow = nullptr; if (mouseEvent->button == 0 && pressedWindow) { oldWindow = static_cast(pressedWindow->handle()); pressedWindow = nullptr; } if (mouseEvent->button == 0) { draggedWindow = nullptr; resizeMode = QWasmWindow::ResizeNone; } if (oldWindow) oldWindow->injectMouseReleased(localPoint, globalPoint, button, modifiers); break; } case EMSCRIPTEN_EVENT_MOUSEMOVE: // drag event { buttonEventType = QEvent::MouseMove; if (!(htmlWindow->m_windowState & Qt::WindowFullScreen) && !(htmlWindow->m_windowState & Qt::WindowMaximized)) { if (resizeMode == QWasmWindow::ResizeNone && draggedWindow) { draggedWindow->setX(draggedWindow->x() + mouseEvent->movementX); draggedWindow->setY(draggedWindow->y() + mouseEvent->movementY); } if (resizeMode != QWasmWindow::ResizeNone && !(htmlWindow->m_windowState & Qt::WindowFullScreen)) { QPoint delta = QPoint(mouseEvent->targetX, mouseEvent->targetY) - resizePoint; resizeWindow(draggedWindow, resizeMode, resizeStartRect, delta); } } break; } default: // MOUSELEAVE MOUSEENTER break; }; if (!window2 && buttonEventType == QEvent::MouseButtonRelease) { window2 = lastWindow; lastWindow = nullptr; interior = true; } if (window2 && interior) { QWindowSystemInterface::handleMouseEvent( window2, timestamp, localPoint, globalPoint, pressedButtons, button, buttonEventType, modifiers); } } int QWasmEventTranslator::focus_cb(int /*eventType*/, const EmscriptenFocusEvent */*focusEvent*/, void */*userData*/) { return 0; } int QWasmEventTranslator::wheel_cb(int eventType, const EmscriptenWheelEvent *wheelEvent, void *userData) { Q_UNUSED(eventType) QWasmEventTranslator *eventTranslator = static_cast(userData); EmscriptenMouseEvent mouseEvent = wheelEvent->mouse; int scrollFactor = 0; switch (wheelEvent->deltaMode) { case DOM_DELTA_PIXEL://chrome safari scrollFactor = 1; break; case DOM_DELTA_LINE: //firefox scrollFactor = 12; break; case DOM_DELTA_PAGE: scrollFactor = 20; break; }; if (g_useNaturalScrolling) //macOS platform has document oriented scrolling scrollFactor = -scrollFactor; QWasmEventTranslator *translator = (QWasmEventTranslator*)userData; Qt::KeyboardModifiers modifiers = translator->translateMouseEventModifier(&mouseEvent); auto timestamp = mouseEvent.timestamp; QPoint targetPoint(mouseEvent.targetX, mouseEvent.targetY); QPoint globalPoint = eventTranslator->screen()->geometry().topLeft() + targetPoint; QWindow *window2 = eventTranslator->screen()->compositor()->windowAt(globalPoint, 5); if (!window2) return 0; QPoint localPoint = window2->mapFromGlobal(globalPoint); QPoint pixelDelta; if (wheelEvent->deltaY != 0) pixelDelta.setY(wheelEvent->deltaY * scrollFactor); if (wheelEvent->deltaX != 0) pixelDelta.setX(wheelEvent->deltaX * scrollFactor); QWindowSystemInterface::handleWheelEvent(window2, timestamp, localPoint, globalPoint, QPoint(), pixelDelta, modifiers); QWasmEventDispatcher::maintainTimers(); return 1; } int QWasmEventTranslator::touchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData) { auto translator = reinterpret_cast(userData); return translator->handleTouch(eventType, touchEvent); } int QWasmEventTranslator::handleTouch(int eventType, const EmscriptenTouchEvent *touchEvent) { QList touchPointList; touchPointList.reserve(touchEvent->numTouches); QWindow *window2; for (int i = 0; i < touchEvent->numTouches; i++) { const EmscriptenTouchPoint *touches = &touchEvent->touches[i]; QPoint targetPoint(touches->targetX, touches->targetY); QPoint globalPoint = screen()->geometry().topLeft() + targetPoint; window2 = this->screen()->compositor()->windowAt(globalPoint, 5); if (window2 == nullptr) continue; QWindowSystemInterface::TouchPoint touchPoint; touchPoint.area = QRect(0, 0, 8, 8); touchPoint.id = touches->identifier; touchPoint.pressure = 1.0; touchPoint.area.moveCenter(globalPoint); const auto tp = pressedTouchIds.constFind(touchPoint.id); if (tp != pressedTouchIds.constEnd()) touchPoint.normalPosition = tp.value(); QPointF localPoint = QPointF(window2->mapFromGlobal(globalPoint)); QPointF normalPosition(localPoint.x() / window2->width(), localPoint.y() / window2->height()); const bool stationaryTouchPoint = (normalPosition == touchPoint.normalPosition); touchPoint.normalPosition = normalPosition; switch (eventType) { case EMSCRIPTEN_EVENT_TOUCHSTART: if (tp != pressedTouchIds.constEnd()) { touchPoint.state = (stationaryTouchPoint ? Qt::TouchPointStationary : Qt::TouchPointMoved); } else { touchPoint.state = Qt::TouchPointPressed; } pressedTouchIds.insert(touchPoint.id, touchPoint.normalPosition); break; case EMSCRIPTEN_EVENT_TOUCHEND: touchPoint.state = Qt::TouchPointReleased; pressedTouchIds.remove(touchPoint.id); break; case EMSCRIPTEN_EVENT_TOUCHMOVE: touchPoint.state = (stationaryTouchPoint ? Qt::TouchPointStationary : Qt::TouchPointMoved); pressedTouchIds.insert(touchPoint.id, touchPoint.normalPosition); break; default: break; } touchPointList.append(touchPoint); } QFlags keyModifier = translatKeyModifier(touchEvent); QWindowSystemInterface::handleTouchEvent( window2, getTimestamp(), touchDevice, touchPointList, keyModifier); if (eventType == EMSCRIPTEN_EVENT_TOUCHCANCEL) QWindowSystemInterface::handleTouchCancelEvent(window2, getTimestamp(), touchDevice, keyModifier); QWasmEventDispatcher::maintainTimers(); return 1; } quint64 QWasmEventTranslator::getTimestamp() { return QDeadlineTimer::current().deadlineNSecs() / 1000; } struct KeyMapping { Qt::Key from, to; }; constexpr KeyMapping tildeKeyTable[] = { // ~ { Qt::Key_A, Qt::Key_Atilde }, { Qt::Key_N, Qt::Key_Ntilde }, { Qt::Key_O, Qt::Key_Otilde }, }; constexpr KeyMapping graveKeyTable[] = { // ` { Qt::Key_A, Qt::Key_Agrave }, { Qt::Key_E, Qt::Key_Egrave }, { Qt::Key_I, Qt::Key_Igrave }, { Qt::Key_O, Qt::Key_Ograve }, { Qt::Key_U, Qt::Key_Ugrave }, }; constexpr KeyMapping acuteKeyTable[] = { // ' { Qt::Key_A, Qt::Key_Aacute }, { Qt::Key_E, Qt::Key_Eacute }, { Qt::Key_I, Qt::Key_Iacute }, { Qt::Key_O, Qt::Key_Oacute }, { Qt::Key_U, Qt::Key_Uacute }, { Qt::Key_Y, Qt::Key_Yacute }, }; constexpr KeyMapping diaeresisKeyTable[] = { // umlaut ¨ { Qt::Key_A, Qt::Key_Adiaeresis }, { Qt::Key_E, Qt::Key_Ediaeresis }, { Qt::Key_I, Qt::Key_Idiaeresis }, { Qt::Key_O, Qt::Key_Odiaeresis }, { Qt::Key_U, Qt::Key_Udiaeresis }, { Qt::Key_Y, Qt::Key_ydiaeresis }, }; constexpr KeyMapping circumflexKeyTable[] = { // ^ { Qt::Key_A, Qt::Key_Acircumflex }, { Qt::Key_E, Qt::Key_Ecircumflex }, { Qt::Key_I, Qt::Key_Icircumflex }, { Qt::Key_O, Qt::Key_Ocircumflex }, { Qt::Key_U, Qt::Key_Ucircumflex }, }; static Qt::Key find_impl(const KeyMapping *first, const KeyMapping *last, Qt::Key key) noexcept { while (first != last) { if (first->from == key) return first->to; ++first; } return Qt::Key_unknown; } template static Qt::Key find(const KeyMapping (&map)[N], Qt::Key key) noexcept { return find_impl(map, map + N, key); } Qt::Key QWasmEventTranslator::translateDeadKey(Qt::Key deadKey, Qt::Key accentBaseKey) { Qt::Key wasmKey = Qt::Key_unknown; switch (deadKey) { #ifdef Q_OS_MACOS case Qt::Key_QuoteLeft: // ` macOS: Key_Dead_Grave #else case Qt::Key_O: // ´ Key_Dead_Grave #endif wasmKey = find(graveKeyTable, accentBaseKey); break; case Qt::Key_E: // ´ Key_Dead_Acute wasmKey = find(acuteKeyTable, accentBaseKey); break; case Qt::Key_AsciiTilde: case Qt::Key_N:// Key_Dead_Tilde wasmKey = find(tildeKeyTable, accentBaseKey); break; #ifndef Q_OS_MACOS case Qt::Key_QuoteLeft: #endif case Qt::Key_U:// ¨ Key_Dead_Diaeresis wasmKey = find(diaeresisKeyTable, accentBaseKey); break; case Qt::Key_I:// macOS Key_Dead_Circumflex case Qt::Key_6:// linux case Qt::Key_Apostrophe:// linux wasmKey = find(circumflexKeyTable, accentBaseKey); break; default: break; }; return wasmKey; } bool QWasmEventTranslator::processKeyboard(int eventType, const EmscriptenKeyboardEvent *keyEvent) { Qt::Key qtKey = translateEmscriptKey(keyEvent); Qt::KeyboardModifiers modifiers = translateKeyboardEventModifier(keyEvent); QString keyText; QEvent::Type keyType = QEvent::None; switch (eventType) { case EMSCRIPTEN_EVENT_KEYPRESS: case EMSCRIPTEN_EVENT_KEYDOWN: // down keyType = QEvent::KeyPress; if (m_emDeadKey != Qt::Key_unknown) { Qt::Key transformedKey = translateDeadKey(m_emDeadKey, qtKey); if (transformedKey != Qt::Key_unknown) qtKey = transformedKey; if (keyEvent->shiftKey == 0) { for (auto it = KeyTbl.cbegin(); it != KeyTbl.end(); ++it) { if (it != KeyTbl.end() && (qtKey == static_cast(it->qt))) { keyText = it->em; m_emDeadKey = Qt::Key_unknown; break; } } } else { for (auto it = DeadKeyShiftTbl.cbegin(); it != DeadKeyShiftTbl.end(); ++it) { if (it != DeadKeyShiftTbl.end() && (qtKey == static_cast(it->qt))) { keyText = it->em; m_emDeadKey = Qt::Key_unknown; break; } } } } if (qstrncmp(keyEvent->key, "Dead", 4) == 0 || qtKey == Qt::Key_AltGr) { qtKey = translateEmscriptKey(keyEvent); m_emStickyDeadKey = true; if (keyEvent->shiftKey == 1 && qtKey == Qt::Key_QuoteLeft) qtKey = Qt::Key_AsciiTilde; m_emDeadKey = qtKey; } break; case EMSCRIPTEN_EVENT_KEYUP: // up keyType = QEvent::KeyRelease; if (m_emStickyDeadKey && qtKey != Qt::Key_Alt) { m_emStickyDeadKey = false; } break; default: break; }; if (keyType == QEvent::None) return 0; QFlags mods = translateKeyboardEventModifier(keyEvent); // Clipboard fallback path: cut/copy/paste are handled by clipboard event // handlers if direct clipboard access is not available. if (!QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi && modifiers & Qt::ControlModifier && (qtKey == Qt::Key_X || qtKey == Qt::Key_C || qtKey == Qt::Key_V)) { return 0; } bool accepted = false; if (keyType == QEvent::KeyPress && mods.testFlag(Qt::ControlModifier) && qtKey == Qt::Key_V) { QWasmIntegration::get()->getWasmClipboard()->readTextFromClipboard(); } else { if (keyText.isEmpty()) keyText = QString(keyEvent->key); if (keyText.size() > 1) keyText.clear(); accepted = QWindowSystemInterface::handleKeyEvent( 0, keyType, qtKey, modifiers, keyText); } if (keyType == QEvent::KeyPress && mods.testFlag(Qt::ControlModifier) && qtKey == Qt::Key_C) { QWasmIntegration::get()->getWasmClipboard()->writeTextToClipboard(); } QWasmEventDispatcher::maintainTimers(); return accepted; } QT_END_NAMESPACE