From 0cec50bece2dcd69127f19c87a3062e4b13f3723 Mon Sep 17 00:00:00 2001 From: Inho Lee Date: Sat, 19 Jun 2021 06:27:22 +0200 Subject: Support text-input-unstable-v4-wip This feature can be enabled by -feature-wayland-text-input-v4-wip. It is disabled by default. TextInputManagerV4 is available in a compositor. zwp_text_input_v4 is available for QT_WAYLAND_TEXT_INPUT_PROTOCOL in a client It supports Hangul(Korean) with a qtvirtualkeyboard patchset (refs/changes/02/357902/3) It includes some workarounds for ibus because each ibus module has its own policy for focus-in/focus-out. enter/leave will synchronize with enable/disable and they will happen whenever focus-in/focus-out happen. Cursor/anchor positions are byte offsets. Surrounding text will be trimmed when it is over 4000 byte. For debugging, uses "qt.waylandcompositor.textinput" in a compositor side uses "qt.qpa.wayland.textinput" in a client side Tested on qtvirtualkeyboard and ibus TODO : * QTBUG-97248 - event:preedit_commit_mode is not implemented yet. Current preedit_commit_mode is 'commit'. * request:set_text_change_cause is not implemented. Task-number: QTBUG-94327 Change-Id: I72644893f40f30c4b03cd6a7d05483d12bde1070 Reviewed-by: Eskil Abrahamsen Blomfeldt --- src/client/CMakeLists.txt | 8 + src/client/qwaylanddisplay.cpp | 47 +++- src/client/qwaylanddisplay_p.h | 7 +- src/client/qwaylandinputcontext.cpp | 6 +- src/client/qwaylandinputdevice.cpp | 12 +- src/client/qwaylandintegration.cpp | 6 +- src/client/qwaylandtextinputinterface_p.h | 5 +- src/client/qwaylandtextinputv4.cpp | 409 ++++++++++++++++++++++++++++++ src/client/qwaylandtextinputv4_p.h | 139 ++++++++++ 9 files changed, 627 insertions(+), 12 deletions(-) create mode 100644 src/client/qwaylandtextinputv4.cpp create mode 100644 src/client/qwaylandtextinputv4_p.h (limited to 'src/client') diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt index 79a070332..c7264d677 100644 --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -76,6 +76,7 @@ qt6_generate_wayland_protocol_client_sources(WaylandClient ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/pointer-gestures-unstable-v1.xml ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/tablet-unstable-v2.xml ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/text-input-unstable-v2.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/text-input-unstable-v4-wip.xml ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/wayland.xml ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/wp-primary-selection-unstable-v1.xml ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/xdg-output-unstable-v1.xml @@ -105,6 +106,13 @@ qt6_generate_wayland_protocol_client_sources(WaylandClient # ) # special case end +qt_internal_extend_target(WaylandClient CONDITION QT_FEATURE_wayland_text_input_v4_wip + SOURCES + qwaylandtextinputv4.cpp qwaylandtextinputv4_p.h + DEFINES + QT_WAYLAND_TEXT_INPUT_V4_WIP=1 +) + qt_internal_extend_target(WaylandClient CONDITION QT_FEATURE_clipboard SOURCES qwaylandclipboard.cpp qwaylandclipboard_p.h diff --git a/src/client/qwaylanddisplay.cpp b/src/client/qwaylanddisplay.cpp index 523de1f3c..8043115af 100644 --- a/src/client/qwaylanddisplay.cpp +++ b/src/client/qwaylanddisplay.cpp @@ -61,6 +61,9 @@ #endif #include "qwaylandhardwareintegration_p.h" #include "qwaylandtextinputv2_p.h" +#if QT_WAYLAND_TEXT_INPUT_V4_WIP +#include "qwaylandtextinputv4_p.h" +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP #include "qwaylandinputcontext_p.h" #include "qwaylandinputmethodcontext_p.h" @@ -76,6 +79,7 @@ #include "qwaylandqtkey_p.h" #include +#include #include #include @@ -450,6 +454,10 @@ void QWaylandDisplay::checkTextInputProtocol() << QLatin1String(QtWayland::zwp_text_input_v2::interface()->name); timps << QLatin1String(QtWayland::qt_text_input_method_manager_v1::interface()->name) << QLatin1String(QtWayland::zwp_text_input_manager_v2::interface()->name); +#if QT_WAYLAND_TEXT_INPUT_V4_WIP + tips << QLatin1String(QtWayland::zwp_text_input_v4::interface()->name); + timps << QLatin1String(QtWayland::zwp_text_input_manager_v4::interface()->name); +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP QString tiProtocols = QString::fromLocal8Bit(qgetenv("QT_WAYLAND_TEXT_INPUT_PROTOCOL")); qCDebug(lcQpaWayland) << "QT_WAYLAND_TEXT_INPUT_PROTOCOL=" << tiProtocols; @@ -528,7 +536,10 @@ void QWaylandDisplay::registry_global(uint32_t id, const QString &interface, uin && (mTextInputManagerList.contains(interface) && mTextInputManagerList.indexOf(interface) < mTextInputManagerIndex)) { qCDebug(lcQpaWayland) << "text input: register qt_text_input_method_manager_v1"; if (mTextInputManagerIndex < INT_MAX) { - mTextInputManager.reset(); + mTextInputManagerv2.reset(); +#if QT_WAYLAND_TEXT_INPUT_V4_WIP + mTextInputManagerv4.reset(); +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) inputDevice->setTextInput(nullptr); } @@ -543,15 +554,35 @@ void QWaylandDisplay::registry_global(uint32_t id, const QString &interface, uin qCDebug(lcQpaWayland) << "text input: register zwp_text_input_v2"; if (mTextInputManagerIndex < INT_MAX) { mTextInputMethodManager.reset(); +#if QT_WAYLAND_TEXT_INPUT_V4_WIP + mTextInputManagerv4.reset(); +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) inputDevice->setTextInputMethod(nullptr); } - mTextInputManager.reset(new QtWayland::zwp_text_input_manager_v2(registry, id, 1)); + mTextInputManagerv2.reset(new QtWayland::zwp_text_input_manager_v2(registry, id, 1)); for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) - inputDevice->setTextInput(new QWaylandTextInputv2(this, mTextInputManager->get_text_input(inputDevice->wl_seat()))); + inputDevice->setTextInput(new QWaylandTextInputv2(this, mTextInputManagerv2->get_text_input(inputDevice->wl_seat()))); mWaylandIntegration->reconfigureInputContext(); mTextInputManagerIndex = mTextInputManagerList.indexOf(interface); +#if QT_WAYLAND_TEXT_INPUT_V4_WIP + } else if (interface == QLatin1String(QtWayland::zwp_text_input_manager_v4::interface()->name) + && (mTextInputManagerList.contains(interface) && mTextInputManagerList.indexOf(interface) < mTextInputManagerIndex)) { + qCDebug(lcQpaWayland) << "text input: register zwp_text_input_v4"; + if (mTextInputManagerIndex < INT_MAX) { + mTextInputMethodManager.reset(); + mTextInputManagerv2.reset(); + for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) + inputDevice->setTextInputMethod(nullptr); + } + + mTextInputManagerv4.reset(new QtWayland::zwp_text_input_manager_v4(registry, id, 1)); + for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) + inputDevice->setTextInput(new QWaylandTextInputv4(this, mTextInputManagerv4->get_text_input(inputDevice->wl_seat()))); + mWaylandIntegration->reconfigureInputContext(); + mTextInputManagerIndex = mTextInputManagerList.indexOf(interface); +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP } else if (interface == QLatin1String(QWaylandHardwareIntegration::interface()->name)) { bool disableHardwareIntegration = qEnvironmentVariableIntValue("QT_WAYLAND_DISABLE_HW_INTEGRATION"); if (!disableHardwareIntegration) { @@ -599,11 +630,19 @@ void QWaylandDisplay::registry_global_remove(uint32_t id) } } if (global.interface == QLatin1String(QtWayland::zwp_text_input_manager_v2::interface()->name)) { - mTextInputManager.reset(); + mTextInputManagerv2.reset(); + for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) + inputDevice->setTextInput(nullptr); + mWaylandIntegration->reconfigureInputContext(); + } +#if QT_WAYLAND_TEXT_INPUT_V4_WIP + if (global.interface == QLatin1String(QtWayland::zwp_text_input_manager_v4::interface()->name)) { + mTextInputManagerv4.reset(); for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) inputDevice->setTextInput(nullptr); mWaylandIntegration->reconfigureInputContext(); } +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP if (global.interface == QLatin1String(QtWayland::qt_text_input_method_manager_v1::interface()->name)) { mTextInputMethodManager.reset(); for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) diff --git a/src/client/qwaylanddisplay_p.h b/src/client/qwaylanddisplay_p.h index b58099ee8..8be911188 100644 --- a/src/client/qwaylanddisplay_p.h +++ b/src/client/qwaylanddisplay_p.h @@ -83,6 +83,7 @@ class QPlatformPlaceholderScreen; namespace QtWayland { class qt_surface_extension; class zwp_text_input_manager_v2; + class zwp_text_input_manager_v4; class qt_text_input_method_manager_v1; } @@ -171,7 +172,8 @@ public: QWaylandPointerGestures *pointerGestures() const { return mPointerGestures.data(); } QWaylandTouchExtension *touchExtension() const { return mTouchExtension.data(); } QtWayland::qt_text_input_method_manager_v1 *textInputMethodManager() const { return mTextInputMethodManager.data(); } - QtWayland::zwp_text_input_manager_v2 *textInputManager() const { return mTextInputManager.data(); } + QtWayland::zwp_text_input_manager_v2 *textInputManagerv2() const { return mTextInputManagerv2.data(); } + QtWayland::zwp_text_input_manager_v4 *textInputManagerv4() const { return mTextInputManagerv4.data(); } QWaylandHardwareIntegration *hardwareIntegration() const { return mHardwareIntegration.data(); } QWaylandXdgOutputManagerV1 *xdgOutputManager() const { return mXdgOutputManager.data(); } @@ -280,7 +282,8 @@ private: QScopedPointer mPrimarySelectionManager; #endif QScopedPointer mTextInputMethodManager; - QScopedPointer mTextInputManager; + QScopedPointer mTextInputManagerv2; + QScopedPointer mTextInputManagerv4; QScopedPointer mHardwareIntegration; QScopedPointer mXdgOutputManager; int mFd = -1; diff --git a/src/client/qwaylandinputcontext.cpp b/src/client/qwaylandinputcontext.cpp index 9ac1c3df6..c59485277 100644 --- a/src/client/qwaylandinputcontext.cpp +++ b/src/client/qwaylandinputcontext.cpp @@ -70,7 +70,11 @@ QWaylandInputContext::~QWaylandInputContext() bool QWaylandInputContext::isValid() const { - return mDisplay->textInputManager() != nullptr; +#if QT_WAYLAND_TEXT_INPUT_V4_WIP + return mDisplay->textInputManagerv2() != nullptr || mDisplay->textInputManagerv4() != nullptr; +#else // QT_WAYLAND_TEXT_INPUT_V4_WIP + return mDisplay->textInputManagerv2() != nullptr; +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP } void QWaylandInputContext::reset() diff --git a/src/client/qwaylandinputdevice.cpp b/src/client/qwaylandinputdevice.cpp index 5fbdd2418..7f503ee1e 100644 --- a/src/client/qwaylandinputdevice.cpp +++ b/src/client/qwaylandinputdevice.cpp @@ -58,6 +58,9 @@ #include "qwaylanddisplay_p.h" #include "qwaylandshmbackingstore_p.h" #include "qwaylandtextinputv2_p.h" +#if QT_WAYLAND_TEXT_INPUT_V4_WIP +#include "qwaylandtextinputv4_p.h" +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP #include "qwaylandtextinputinterface_p.h" #include "qwaylandinputcontext_p.h" #include "qwaylandinputmethodcontext_p.h" @@ -421,8 +424,13 @@ QWaylandInputDevice::QWaylandInputDevice(QWaylandDisplay *display, int version, setPrimarySelectionDevice(psm->createDevice(this)); #endif - if (mQDisplay->textInputManager()) - mTextInput.reset(new QWaylandTextInputv2(mQDisplay, mQDisplay->textInputManager()->get_text_input(wl_seat()))); + if (mQDisplay->textInputManagerv2()) + mTextInput.reset(new QWaylandTextInputv2(mQDisplay, mQDisplay->textInputManagerv2()->get_text_input(wl_seat()))); + +#if QT_WAYLAND_TEXT_INPUT_V4_WIP + if (mQDisplay->textInputManagerv4()) + mTextInput.reset(new QWaylandTextInputv4(mQDisplay, mQDisplay->textInputManagerv4()->get_text_input(wl_seat()))); +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP if (mQDisplay->textInputMethodManager()) mTextInputMethod.reset(new QWaylandTextInputMethod(mQDisplay, mQDisplay->textInputMethodManager()->get_text_input_method(wl_seat()))); diff --git a/src/client/qwaylandintegration.cpp b/src/client/qwaylandintegration.cpp index 67af87cb2..a1da5ccff 100644 --- a/src/client/qwaylandintegration.cpp +++ b/src/client/qwaylandintegration.cpp @@ -497,7 +497,11 @@ void QWaylandIntegration::reconfigureInputContext() if (requested.isNull()) { if (mDisplay->textInputMethodManager() != nullptr) mInputContext.reset(new QWaylandInputMethodContext(mDisplay.data())); - else if (mDisplay->textInputManager() != nullptr) +#if QT_WAYLAND_TEXT_INPUT_V4_WIP + else if (mDisplay->textInputManagerv2() != nullptr || mDisplay->textInputManagerv4() != nullptr) +#else // QT_WAYLAND_TEXT_INPUT_V4_WIP + else if (mDisplay->textInputManagerv2() != nullptr) +#endif // QT_WAYLAND_TEXT_INPUT_V4_WIP mInputContext.reset(new QWaylandInputContext(mDisplay.data())); } else { mInputContext.reset(QPlatformInputContextFactory::create(requested)); diff --git a/src/client/qwaylandtextinputinterface_p.h b/src/client/qwaylandtextinputinterface_p.h index d918f0835..2b0f381b4 100644 --- a/src/client/qwaylandtextinputinterface_p.h +++ b/src/client/qwaylandtextinputinterface_p.h @@ -39,6 +39,7 @@ #ifndef QWAYLANDTEXTINPUTINTERFACE_P_H #define QWAYLANDTEXTINPUTINTERFACE_P_H + // // W A R N I N G // ------------- @@ -68,8 +69,8 @@ public: virtual void disableSurface(::wl_surface *surface) = 0; virtual void enableSurface(::wl_surface *surface) = 0; virtual void updateState(Qt::InputMethodQueries queries, uint32_t flags) = 0; - virtual void showInputPanel() = 0; - virtual void hideInputPanel() = 0; + virtual void showInputPanel() {} + virtual void hideInputPanel() {} virtual bool isInputPanelVisible() const = 0; virtual QRectF keyboardRect() const = 0; virtual QLocale locale() const = 0; diff --git a/src/client/qwaylandtextinputv4.cpp b/src/client/qwaylandtextinputv4.cpp new file mode 100644 index 000000000..5cecaf7a4 --- /dev/null +++ b/src/client/qwaylandtextinputv4.cpp @@ -0,0 +1,409 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwaylandtextinputv4_p.h" + +#include "qwaylandwindow_p.h" +#include "qwaylandinputmethodeventbuilder_p.h" + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(qLcQpaWaylandTextInput, "qt.qpa.wayland.textinput") + +namespace QtWaylandClient { + +QWaylandTextInputv4::QWaylandTextInputv4(QWaylandDisplay *display, + struct ::zwp_text_input_v4 *text_input) + : QtWayland::zwp_text_input_v4(text_input) + , m_display(display) +{ + +} + +QWaylandTextInputv4::~QWaylandTextInputv4() +{ +} + +namespace { +const Qt::InputMethodQueries supportedQueries = Qt::ImEnabled | + Qt::ImSurroundingText | + Qt::ImCursorPosition | + Qt::ImAnchorPosition | + Qt::ImHints | + Qt::ImCursorRectangle; +} + +void QWaylandTextInputv4::zwp_text_input_v4_enter(struct ::wl_surface *surface) +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO; + + m_surface = surface; + + m_pendingPreeditString.clear(); + m_pendingCommitString.clear(); + m_pendingDeleteBeforeText = 0; + m_pendingDeleteAfterText = 0; + + enable(); + updateState(supportedQueries, update_state_enter); +} + +void QWaylandTextInputv4::zwp_text_input_v4_leave(struct ::wl_surface *surface) +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO; + + if (m_surface != surface) { + qCWarning(qLcQpaWaylandTextInput()) << Q_FUNC_INFO << "Got leave event for surface" << surface << "focused surface" << m_surface; + return; + } + + // QTBUG-97248: check commit_mode + // Currently text-input-unstable-v4-wip is implemented with preedit_commit_mode + // 'commit' + + m_currentPreeditString.clear(); + + m_surface = nullptr; + m_currentSerial = 0U; + + disable(); + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "Done"; +} + +void QWaylandTextInputv4::zwp_text_input_v4_preedit_string(const QString &text, int32_t cursorBegin, int32_t cursorEnd) +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << text << cursorBegin << cursorEnd; + + if (!QGuiApplication::focusObject()) + return; + + m_pendingPreeditString.text = text; + m_pendingPreeditString.cursorBegin = cursorBegin; + m_pendingPreeditString.cursorEnd = cursorEnd; +} + +void QWaylandTextInputv4::zwp_text_input_v4_commit_string(const QString &text) +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << text; + + if (!QGuiApplication::focusObject()) + return; + + m_pendingCommitString = text; +} + +void QWaylandTextInputv4::zwp_text_input_v4_delete_surrounding_text(uint32_t beforeText, uint32_t afterText) +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << beforeText << afterText; + + if (!QGuiApplication::focusObject()) + return; + + m_pendingDeleteBeforeText = QWaylandInputMethodEventBuilder::indexFromWayland(m_surroundingText, beforeText); + m_pendingDeleteAfterText = QWaylandInputMethodEventBuilder::indexFromWayland(m_surroundingText, afterText); +} + +void QWaylandTextInputv4::zwp_text_input_v4_done(uint32_t serial) +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "with serial" << serial << m_currentSerial; + + // This is a case of double click. + // text_input_v4 will ignore this done signal and just keep the selection of the clicked word. + if (m_cursorPos != m_anchorPos && (m_pendingDeleteBeforeText != 0 || m_pendingDeleteAfterText != 0)) { + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "Ignore done"; + m_pendingDeleteBeforeText = 0; + m_pendingDeleteAfterText = 0; + m_pendingPreeditString.clear(); + m_pendingCommitString.clear(); + return; + } + + QObject *focusObject = QGuiApplication::focusObject(); + if (!focusObject) + return; + + if (!m_surface) { + qCWarning(qLcQpaWaylandTextInput) << Q_FUNC_INFO << serial << "Surface is not enabled yet"; + return; + } + + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "PREEDIT" << m_pendingPreeditString.text << m_pendingPreeditString.cursorBegin; + + QList attributes; + { + if (m_pendingPreeditString.cursorBegin != -1 || + m_pendingPreeditString.cursorEnd != -1) { + // Current supported cursor shape is just line. + // It means, cursorEnd and cursorBegin are the same. + QInputMethodEvent::Attribute attribute1(QInputMethodEvent::Cursor, + m_pendingPreeditString.text.length(), + 1); + attributes.append(attribute1); + } + + // only use single underline style for now + QTextCharFormat format; + format.setFontUnderline(true); + format.setUnderlineStyle(QTextCharFormat::SingleUnderline); + QInputMethodEvent::Attribute attribute2(QInputMethodEvent::TextFormat, + 0, + m_pendingPreeditString.text.length(), format); + attributes.append(attribute2); + } + QInputMethodEvent event(m_pendingPreeditString.text, attributes); + + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "DELETE" << m_pendingDeleteBeforeText << m_pendingDeleteAfterText; + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "COMMIT" << m_pendingCommitString; + + // A workaround for reselection + // It will disable redundant commit after reselection + if (m_pendingDeleteBeforeText != 0 || m_pendingDeleteAfterText != 0) + m_condReselection = true; + + event.setCommitString(m_pendingCommitString, + -m_pendingDeleteBeforeText, + m_pendingDeleteBeforeText + m_pendingDeleteAfterText); + m_currentPreeditString = m_pendingPreeditString; + m_pendingPreeditString.clear(); + m_pendingCommitString.clear(); + m_pendingDeleteBeforeText = 0; + m_pendingDeleteAfterText = 0; + QCoreApplication::sendEvent(focusObject, &event); + + if (serial == m_currentSerial) + updateState(supportedQueries, update_state_full); +} + +void QWaylandTextInputv4::reset() +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO; + + m_pendingPreeditString.clear(); +} + +void QWaylandTextInputv4::enableSurface(::wl_surface *) +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO; +} + +void QWaylandTextInputv4::disableSurface(::wl_surface *surface) +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO; + + if (m_surface != surface) { + qCWarning(qLcQpaWaylandTextInput()) << Q_FUNC_INFO << "for surface" << surface << "focused surface" << m_surface; + return; + } +} + +void QWaylandTextInputv4::commit() +{ + m_currentSerial = (m_currentSerial < UINT_MAX) ? m_currentSerial + 1U: 0U; + + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "with serial" << m_currentSerial; + QtWayland::zwp_text_input_v4::commit(); +} + +void QWaylandTextInputv4::updateState(Qt::InputMethodQueries queries, uint32_t flags) +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << queries << flags; + + if (!QGuiApplication::focusObject()) + return; + + if (!QGuiApplication::focusWindow() || !QGuiApplication::focusWindow()->handle()) + return; + + auto *window = static_cast(QGuiApplication::focusWindow()->handle()); + auto *surface = window->wlSurface(); + if (!surface || (surface != m_surface)) + return; + + queries &= supportedQueries; + bool needsCommit = false; + + QInputMethodQueryEvent event(queries); + QCoreApplication::sendEvent(QGuiApplication::focusObject(), &event); + + // For some reason, a query for Qt::ImSurroundingText gives an empty string even though it is not. + if (!(queries & Qt::ImSurroundingText) && event.value(Qt::ImSurroundingText).toString().isEmpty()) { + return; + } + + if (queries & Qt::ImCursorRectangle) { + const QRect &cRect = event.value(Qt::ImCursorRectangle).toRect(); + const QRect &windowRect = QGuiApplication::inputMethod()->inputItemTransform().mapRect(cRect); + const QMargins margins = window->frameMargins(); + const QRect &surfaceRect = windowRect.translated(margins.left(), margins.top()); + if (surfaceRect != m_cursorRect) { + set_cursor_rectangle(surfaceRect.x(), surfaceRect.y(), surfaceRect.width(), surfaceRect.height()); + m_cursorRect = surfaceRect; + needsCommit = true; + } + } + + if ((queries & Qt::ImSurroundingText) || (queries & Qt::ImCursorPosition) || (queries & Qt::ImAnchorPosition)) { + QString text = event.value(Qt::ImSurroundingText).toString(); + int cursor = event.value(Qt::ImCursorPosition).toInt(); + int anchor = event.value(Qt::ImAnchorPosition).toInt(); + + qCDebug(qLcQpaWaylandTextInput) << "Orginal surrounding_text from InputMethodQuery: " << text << cursor << anchor; + + // Make sure text is not too big + // surround_text cannot exceed 4000byte in wayland protocol + // The worst case will be supposed here. + const int MAX_MESSAGE_SIZE = 4000; + + if (text.toUtf8().size() > MAX_MESSAGE_SIZE) { + const int selectionStart = QWaylandInputMethodEventBuilder::indexToWayland(text, qMin(cursor, anchor)); + const int selectionEnd = QWaylandInputMethodEventBuilder::indexToWayland(text, qMax(cursor, anchor)); + const int selectionLength = selectionEnd - selectionStart; + // If selection is bigger than 4000 byte, it is fixed to 4000 byte. + // anchor will be moved in the 4000 byte boundary. + if (selectionLength > MAX_MESSAGE_SIZE) { + if (anchor > cursor) { + const int length = MAX_MESSAGE_SIZE; + anchor = QWaylandInputMethodEventBuilder::trimmedIndexFromWayland(text, length, cursor); + anchor -= cursor; + text = text.mid(cursor, anchor); + cursor = 0; + } else { + const int length = -MAX_MESSAGE_SIZE; + anchor = QWaylandInputMethodEventBuilder::trimmedIndexFromWayland(text, length, cursor); + cursor -= anchor; + text = text.mid(anchor, cursor); + anchor = 0; + } + } else { + const int offset = (MAX_MESSAGE_SIZE - selectionLength) / 2; + + int textStart = QWaylandInputMethodEventBuilder::trimmedIndexFromWayland(text, -offset, qMin(cursor, anchor)); + int textEnd = QWaylandInputMethodEventBuilder::trimmedIndexFromWayland(text, MAX_MESSAGE_SIZE, textStart); + + anchor -= textStart; + cursor -= textStart; + text = text.mid(textStart, textEnd - textStart); + } + } + qCDebug(qLcQpaWaylandTextInput) << "Modified surrounding_text: " << text << cursor << anchor; + + const int cursorPos = QWaylandInputMethodEventBuilder::indexToWayland(text, cursor); + const int anchorPos = QWaylandInputMethodEventBuilder::indexToWayland(text, anchor); + + if (m_surroundingText != text || m_cursorPos != cursorPos || m_anchorPos != anchorPos) { + qCDebug(qLcQpaWaylandTextInput) << "Current surrounding_text: " << m_surroundingText << m_cursorPos << m_anchorPos; + qCDebug(qLcQpaWaylandTextInput) << "New surrounding_text: " << text << cursorPos << anchorPos; + + set_surrounding_text(text, cursorPos, anchorPos); + + // A workaround in the case of reselection + // It will work when re-clicking a preedit text + if (m_condReselection) { + qCDebug(qLcQpaWaylandTextInput) << "\"commit\" is disabled when Reselection by changing focus"; + m_condReselection = false; + needsCommit = false; + + } + + m_surroundingText = text; + m_cursorPos = cursorPos; + m_anchorPos = anchorPos; + m_cursor = cursor; + } + } + + if (queries & Qt::ImHints) { + QWaylandInputMethodContentType contentType = QWaylandInputMethodContentType::convertV4(static_cast(event.value(Qt::ImHints).toInt())); + qCDebug(qLcQpaWaylandTextInput) << m_contentHint << contentType.hint; + qCDebug(qLcQpaWaylandTextInput) << m_contentPurpose << contentType.purpose; + + if (m_contentHint != contentType.hint || m_contentPurpose != contentType.purpose) { + qCDebug(qLcQpaWaylandTextInput) << "set_content_type: " << contentType.hint << contentType.purpose; + set_content_type(contentType.hint, contentType.purpose); + + m_contentHint = contentType.hint; + m_contentPurpose = contentType.purpose; + needsCommit = true; + } + } + + if (needsCommit + && (flags == update_state_change || flags == update_state_enter)) + commit(); +} + +void QWaylandTextInputv4::setCursorInsidePreedit(int cursor) +{ + Q_UNUSED(cursor); + qCWarning(qLcQpaWaylandTextInput) << "QWaylandTextInputV4: Input protocol \"text-input-unstable-v4-wip\" does not support setting cursor inside preedit. Use qt-text-input-method-unstable-v1 instead for full support of Qt input method events."; +} + +bool QWaylandTextInputv4::isInputPanelVisible() const +{ + qCWarning(qLcQpaWaylandTextInput) << "QWaylandTextInputV4: Input protocol \"text-input-unstable-v4-wip\" does not support querying input method visibility. Use qt-text-input-method-unstable-v1 instead for full support of Qt input method events."; + return false; +} + +QRectF QWaylandTextInputv4::keyboardRect() const +{ + qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO; + return m_cursorRect; +} + +QLocale QWaylandTextInputv4::locale() const +{ + qCWarning(qLcQpaWaylandTextInput) << "QWaylandTextInputV4: Input protocol \"text-input-unstable-v4-wip\" does not support querying input language. Use qt-text-input-method-unstable-v1 instead for full support of Qt input method events."; + return QLocale(); +} + +Qt::LayoutDirection QWaylandTextInputv4::inputDirection() const +{ + qCWarning(qLcQpaWaylandTextInput) << "QWaylandTextInputV4: Input protocol \"text-input-unstable-v4-wip\" does not support querying input direction. Use qt-text-input-method-unstable-v1 instead for full support of Qt input method events."; + return Qt::LeftToRight; +} + +} + +QT_END_NAMESPACE diff --git a/src/client/qwaylandtextinputv4_p.h b/src/client/qwaylandtextinputv4_p.h new file mode 100644 index 000000000..d273cef27 --- /dev/null +++ b/src/client/qwaylandtextinputv4_p.h @@ -0,0 +1,139 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWAYLANDTEXTINPUTV4_P_H +#define QWAYLANDTEXTINPUTV4_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qwaylandtextinputinterface_p.h" +#include +#include + +struct wl_callback; +struct wl_callback_listener; + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(qLcQpaWaylandTextInput) + +namespace QtWaylandClient { + +class QWaylandDisplay; + +class QWaylandTextInputv4 : public QtWayland::zwp_text_input_v4, public QWaylandTextInputInterface +{ +public: + QWaylandTextInputv4(QWaylandDisplay *display, struct ::zwp_text_input_v4 *text_input); + ~QWaylandTextInputv4() override; + + void reset() override; + void commit() override; + void updateState(Qt::InputMethodQueries queries, uint32_t flags) override; + // TODO: not supported yet + void setCursorInsidePreedit(int cursor) override; + + bool isInputPanelVisible() const override; + QRectF keyboardRect() const override; + + QLocale locale() const override; + Qt::LayoutDirection inputDirection() const override; + + void enableSurface(::wl_surface *surface) override; + void disableSurface(::wl_surface *surface) override; + +protected: + void zwp_text_input_v4_enter(struct ::wl_surface *surface) override; + void zwp_text_input_v4_leave(struct ::wl_surface *surface) override; + void zwp_text_input_v4_preedit_string(const QString &text, int32_t cursor_begin, int32_t cursor_end) override; + void zwp_text_input_v4_commit_string(const QString &text) override; + void zwp_text_input_v4_delete_surrounding_text(uint32_t before_length, uint32_t after_length) override; + void zwp_text_input_v4_done(uint32_t serial) override; + +private: + QWaylandDisplay *m_display; + QWaylandInputMethodEventBuilder m_builder; + + ::wl_surface *m_surface = nullptr; // ### Here for debugging purposes + + struct PreeditInfo { + QString text; + int cursorBegin = 0; + int cursorEnd = 0; + + void clear() { + text.clear(); + cursorBegin = 0; + cursorEnd = 0; + } + }; + + PreeditInfo m_pendingPreeditString; + PreeditInfo m_currentPreeditString; + QString m_pendingCommitString; + uint m_pendingDeleteBeforeText = 0; + uint m_pendingDeleteAfterText = 0; + + QString m_surroundingText; + int m_cursor; // cursor position in QString + int m_cursorPos; // cursor position in wayland index + int m_anchorPos; // anchor position in wayland index + uint32_t m_contentHint = 0; + uint32_t m_contentPurpose = 0; + QRect m_cursorRect; + + uint m_currentSerial = 0; + + bool m_condReselection = false; +}; + +} + +QT_END_NAMESPACE + +#endif // QWAYLANDTEXTINPUTV4_P_H -- cgit v1.2.3