diff --git a/src/3rdparty/wayland/extensions/qt-text-input-method-unstable-v1.xml b/src/3rdparty/wayland/extensions/qt-text-input-method-unstable-v1.xml new file mode 100644 index 00000000000..2e8cd4ec3a6 --- /dev/null +++ b/src/3rdparty/wayland/extensions/qt-text-input-method-unstable-v1.xml @@ -0,0 +1,301 @@ + + + + + Copyright © 2020 The Qt Company Ltd. + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + The qt_text_input_method interface represents input method events + associated with a seat, and is intended to exactly match the + internal events of the Qt framework. + + + + + Destroy the qt_text_input_method object. + + + + + + Enable text input in a surface (usually when a text entry inside of it + has focus). + + This can be called before or after a surface gets text (or keyboard) + focus via the enter event. Text input to a surface is only active + when it has the current text (or keyboard) focus and is enabled. + + + + + + + Disable text input in a surface (typically when there is no focus on any + text entry inside the surface). + + + + + + + Notification that this seat's text-input focus is on a certain surface. + + When the seat has the keyboard capability the text-input focus follows + the keyboard focus. + + + + + + + Notification that this seat's text-input focus is no longer on + a certain surface. + + The leave notification is sent before the enter notification + for the new focus. + + When the seat has the keyboard capability the text-input focus follows + the keyboard focus. + + + + + + + A QKeyEvent has been sent from the input method. + + + + + + + + + + + + + + + Starts an input method event. This can be followed by + any number of input_method_event_attribute events + and will always be finished by an end_input_method_event. + + + + + + + + Appends an attribute to the input method event with + the given serial. Must be preceded by a start_input_method_event + and concluded by a an end_input_method_event. See documentation + of QInputMethodEvent for details on the attributes. + + + + + + + + + + + Concludes a previously started input method event. Together with + the preceding input_method_event_attribute events with the same + serial, this should be converted into a QInputMethodEvent. + + + + + + + + + + + Event to notify client that the visibility of the input method has + been changed. + + + + + + + Event to notify client that the keyboard rectangle of the input method + has been changed. + + + + + + + + + + Event to notify client that the locale of the input method + has been changed. + + + + + + + Event to notify client that the input direction of the input method + has been changed. + + + + + + + Request for the input method to reset. Corresponds to QInputMethod::reset(). + + + + + + Request for the input method to commit its current content. Corresponds to QInputMethod::commit(). + + + + + + Passes a mouse click or context menu request from the client to the server. Corresponds to QInputMethod::invokeAction(). + + + + + + + + Notifies the server of the client's current input method hints. + + + + + + + Notifies the server of the client's current cursor rectangle. + + + + + + + + + + Notifies the server of the client's current cursor position. + + + + + + + Notifies the server of the client's current surrounding text and its offset in the + complete text. + + + + + + + + Notifies the server of the client's current anchor position. + + + + + + + Notifies the server of the client's current absolute cursor position. + + + + + + + Notifies the server of the client's current preferred language. + + + + + + + Starts an update sequence to notify the server that the client's state has + changed. This is followed by any number of update requests for specific + parts of the state and concluded by an end_update request. + + + + + + + Concludes the previously started update request. + + + + + + Requests that the input panel of the input method is visible. + + + + + + Requests that the input panel of the input method is not visible. + + + + + + Sent on receipt of an end_input_method_event to acknowledge that + the client has received and handled the event. + + + + + + + Manages qt_text_input_method objects. + + + + + Destroy the qt_text_input_method_manager object. + + + + + + Creates a new text-input-method object for a given seat. + + + + + + diff --git a/src/plugins/platforms/wayland/.prev_CMakeLists.txt b/src/plugins/platforms/wayland/.prev_CMakeLists.txt index ac323a56f3f..454b06ae38f 100644 --- a/src/plugins/platforms/wayland/.prev_CMakeLists.txt +++ b/src/plugins/platforms/wayland/.prev_CMakeLists.txt @@ -30,6 +30,7 @@ qt_add_module(WaylandClient qwaylandextendedsurface.cpp qwaylandextendedsurface_p.h qwaylandinputcontext.cpp qwaylandinputcontext_p.h qwaylandinputdevice.cpp qwaylandinputdevice_p.h + qwaylandinputmethodcontext.cpp qwaylandinputmethodcontext_p.h qwaylandintegration.cpp qwaylandintegration_p.h qwaylandnativeinterface.cpp qwaylandnativeinterface_p.h qwaylandqtkey.cpp qwaylandqtkey_p.h @@ -56,7 +57,6 @@ qt_add_module(WaylandClient LIBRARIES Qt::CorePrivate Qt::GuiPrivate - Qt::PlatformHeadersPrivate PUBLIC_LIBRARIES Qt::Core Qt::Gui @@ -74,6 +74,7 @@ qt6_generate_wayland_protocol_client_sources(WaylandClient ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/wp-primary-selection-unstable-v1.xml ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/xdg-output-unstable-v1.xml ${CMAKE_CURRENT_SOURCE_DIR}/../extensions/qt-key-unstable-v1.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../extensions/qt-text-input-method-unstable-v1.xml ${CMAKE_CURRENT_SOURCE_DIR}/../extensions/qt-windowmanager.xml ${CMAKE_CURRENT_SOURCE_DIR}/../extensions/surface-extension.xml ${CMAKE_CURRENT_SOURCE_DIR}/../extensions/touch-extension.xml diff --git a/src/plugins/platforms/wayland/CMakeLists.txt b/src/plugins/platforms/wayland/CMakeLists.txt index 88b27b5f3a9..01957eacd7f 100644 --- a/src/plugins/platforms/wayland/CMakeLists.txt +++ b/src/plugins/platforms/wayland/CMakeLists.txt @@ -30,6 +30,7 @@ qt_add_module(WaylandClient qwaylandextendedsurface.cpp qwaylandextendedsurface_p.h qwaylandinputcontext.cpp qwaylandinputcontext_p.h qwaylandinputdevice.cpp qwaylandinputdevice_p.h + qwaylandinputmethodcontext.cpp qwaylandinputmethodcontext_p.h qwaylandintegration.cpp qwaylandintegration_p.h qwaylandnativeinterface.cpp qwaylandnativeinterface_p.h qwaylandqtkey.cpp qwaylandqtkey_p.h @@ -73,6 +74,7 @@ qt6_generate_wayland_protocol_client_sources(WaylandClient ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/wp-primary-selection-unstable-v1.xml ${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty/protocol/xdg-output-unstable-v1.xml ${CMAKE_CURRENT_SOURCE_DIR}/../extensions/qt-key-unstable-v1.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../extensions/qt-text-input-method-unstable-v1.xml ${CMAKE_CURRENT_SOURCE_DIR}/../extensions/qt-windowmanager.xml ${CMAKE_CURRENT_SOURCE_DIR}/../extensions/surface-extension.xml ${CMAKE_CURRENT_SOURCE_DIR}/../extensions/touch-extension.xml diff --git a/src/plugins/platforms/wayland/client.pro b/src/plugins/platforms/wayland/client.pro index d6df946b58e..da6a926dc48 100644 --- a/src/plugins/platforms/wayland/client.pro +++ b/src/plugins/platforms/wayland/client.pro @@ -22,6 +22,7 @@ WAYLANDCLIENTSOURCES += \ ../extensions/touch-extension.xml \ ../extensions/qt-key-unstable-v1.xml \ ../extensions/qt-windowmanager.xml \ + ../extensions/qt-text-input-method-unstable-v1.xml \ ../3rdparty/protocol/wp-primary-selection-unstable-v1.xml \ ../3rdparty/protocol/tablet-unstable-v2.xml \ ../3rdparty/protocol/text-input-unstable-v2.xml \ @@ -50,6 +51,7 @@ SOURCES += qwaylandintegration.cpp \ qwaylanddecorationplugin.cpp \ qwaylandwindowmanagerintegration.cpp \ qwaylandinputcontext.cpp \ + qwaylandinputmethodcontext.cpp \ qwaylandshm.cpp \ qwaylandbuffer.cpp \ @@ -74,6 +76,7 @@ HEADERS += qwaylandintegration_p.h \ qwaylanddecorationplugin_p.h \ qwaylandwindowmanagerintegration_p.h \ qwaylandinputcontext_p.h \ + qwaylandinputmethodcontext_p.h \ qwaylandshm_p.h \ qtwaylandclientglobal.h \ qtwaylandclientglobal_p.h \ diff --git a/src/plugins/platforms/wayland/qwaylanddisplay.cpp b/src/plugins/platforms/wayland/qwaylanddisplay.cpp index 9e5359357d8..e39f912809b 100644 --- a/src/plugins/platforms/wayland/qwaylanddisplay.cpp +++ b/src/plugins/platforms/wayland/qwaylanddisplay.cpp @@ -61,6 +61,7 @@ #endif #include "qwaylandhardwareintegration_p.h" #include "qwaylandinputcontext_p.h" +#include "qwaylandinputmethodcontext_p.h" #include "qwaylandwindowmanagerintegration_p.h" #include "qwaylandshellintegration_p.h" @@ -74,6 +75,7 @@ #include #include +#include #include @@ -339,6 +341,11 @@ void QWaylandDisplay::registry_global(uint32_t id, const QString &interface, uin } else if (interface == QStringLiteral("zwp_primary_selection_device_manager_v1")) { mPrimarySelectionManager.reset(new QWaylandPrimarySelectionDeviceManagerV1(this, id, 1)); #endif + } else if (interface == QStringLiteral("qt_text_input_method_manager_v1") && !mClientSideInputContextRequested) { + mTextInputMethodManager.reset(new QtWayland::qt_text_input_method_manager_v1(registry, id, 1)); + for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) + inputDevice->setTextInputMethod(new QWaylandTextInputMethod(this, mTextInputMethodManager->get_text_input_method(inputDevice->wl_seat()))); + mWaylandIntegration->reconfigureInputContext(); } else if (interface == QStringLiteral("zwp_text_input_manager_v2") && !mClientSideInputContextRequested) { mTextInputManager.reset(new QtWayland::zwp_text_input_manager_v2(registry, id, 1)); for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) @@ -396,6 +403,12 @@ void QWaylandDisplay::registry_global_remove(uint32_t id) inputDevice->setTextInput(nullptr); mWaylandIntegration->reconfigureInputContext(); } + if (global.interface == QStringLiteral("qt_text_input_method_manager_v1")) { + mTextInputMethodManager.reset(); + for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) + inputDevice->setTextInputMethod(nullptr); + mWaylandIntegration->reconfigureInputContext(); + } mGlobals.removeAt(i); break; } diff --git a/src/plugins/platforms/wayland/qwaylanddisplay_p.h b/src/plugins/platforms/wayland/qwaylanddisplay_p.h index d386c66ac68..68d72812f52 100644 --- a/src/plugins/platforms/wayland/qwaylanddisplay_p.h +++ b/src/plugins/platforms/wayland/qwaylanddisplay_p.h @@ -81,6 +81,7 @@ class QPlatformPlaceholderScreen; namespace QtWayland { class qt_surface_extension; class zwp_text_input_manager_v2; + class qt_text_input_method_manager_v1; } namespace QtWaylandClient { @@ -163,6 +164,7 @@ public: QtWayland::qt_surface_extension *windowExtension() const { return mWindowExtension.data(); } QWaylandTabletManagerV2 *tabletManager() const { return mTabletManager.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(); } QWaylandHardwareIntegration *hardwareIntegration() const { return mHardwareIntegration.data(); } QWaylandXdgOutputManagerV1 *xdgOutputManager() const { return mXdgOutputManager.data(); } @@ -268,6 +270,7 @@ private: #if QT_CONFIG(wayland_client_primary_selection) QScopedPointer mPrimarySelectionManager; #endif + QScopedPointer mTextInputMethodManager; QScopedPointer mTextInputManager; QScopedPointer mHardwareIntegration; QScopedPointer mXdgOutputManager; diff --git a/src/plugins/platforms/wayland/qwaylandinputdevice.cpp b/src/plugins/platforms/wayland/qwaylandinputdevice.cpp index e86274276f0..343ccc8d7e2 100644 --- a/src/plugins/platforms/wayland/qwaylandinputdevice.cpp +++ b/src/plugins/platforms/wayland/qwaylandinputdevice.cpp @@ -57,6 +57,7 @@ #include "qwaylanddisplay_p.h" #include "qwaylandshmbackingstore_p.h" #include "qwaylandinputcontext_p.h" +#include "qwaylandinputmethodcontext_p.h" #include #include @@ -420,6 +421,9 @@ QWaylandInputDevice::QWaylandInputDevice(QWaylandDisplay *display, int version, if (mQDisplay->textInputManager()) mTextInput.reset(new QWaylandTextInput(mQDisplay, mQDisplay->textInputManager()->get_text_input(wl_seat()))); + if (mQDisplay->textInputMethodManager()) + mTextInputMethod.reset(new QWaylandTextInputMethod(mQDisplay, mQDisplay->textInputMethodManager()->get_text_input_method(wl_seat()))); + if (auto *tm = mQDisplay->tabletManager()) mTabletSeat.reset(new QWaylandTabletSeatV2(tm, this)); } @@ -535,11 +539,21 @@ void QWaylandInputDevice::setTextInput(QWaylandTextInput *textInput) mTextInput.reset(textInput); } +void QWaylandInputDevice::setTextInputMethod(QWaylandTextInputMethod *textInputMethod) +{ + mTextInputMethod.reset(textInputMethod); +} + QWaylandTextInput *QWaylandInputDevice::textInput() const { return mTextInput.data(); } +QWaylandTextInputMethod *QWaylandInputDevice::textInputMethod() const +{ + return mTextInputMethod.data(); +} + void QWaylandInputDevice::removeMouseButtonFromState(Qt::MouseButton button) { if (mPointer) diff --git a/src/plugins/platforms/wayland/qwaylandinputdevice_p.h b/src/plugins/platforms/wayland/qwaylandinputdevice_p.h index 31bd3008849..53691e68254 100644 --- a/src/plugins/platforms/wayland/qwaylandinputdevice_p.h +++ b/src/plugins/platforms/wayland/qwaylandinputdevice_p.h @@ -91,6 +91,7 @@ class QWaylandPrimarySelectionDeviceV1; #endif class QWaylandTabletSeatV2; class QWaylandTextInput; +class QWaylandTextInputMethod; #if QT_CONFIG(cursor) class QWaylandCursorTheme; class CursorSurface; @@ -134,6 +135,9 @@ public: void setTextInput(QWaylandTextInput *textInput); QWaylandTextInput *textInput() const; + void setTextInputMethod(QWaylandTextInputMethod *textInputMethod); + QWaylandTextInputMethod *textInputMethod() const; + void removeMouseButtonFromState(Qt::MouseButton button); QWaylandWindow *pointerFocus() const; @@ -187,6 +191,7 @@ private: Touch *mTouch = nullptr; QScopedPointer mTextInput; + QScopedPointer mTextInputMethod; QScopedPointer mTabletSeat; uint32_t mTime = 0; diff --git a/src/plugins/platforms/wayland/qwaylandinputmethodcontext.cpp b/src/plugins/platforms/wayland/qwaylandinputmethodcontext.cpp new file mode 100644 index 00000000000..e6ab1da4ff1 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandinputmethodcontext.cpp @@ -0,0 +1,421 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 "qwaylandinputmethodcontext_p.h" +#include "qwaylanddisplay_p.h" +#include "qwaylandinputdevice_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(qLcQpaInputMethods) + +namespace QtWaylandClient { + +static constexpr int maxStringSize = 1000; // actual max is 4096/3 + +QWaylandTextInputMethod::QWaylandTextInputMethod(QWaylandDisplay *display, struct ::qt_text_input_method_v1 *textInputMethod) + : QtWayland::qt_text_input_method_v1(textInputMethod) + , m_display(display) +{ +} + +QWaylandTextInputMethod::~QWaylandTextInputMethod() +{ +} + +void QWaylandTextInputMethod::text_input_method_v1_visible_changed(int32_t visible) +{ + m_isVisible = visible; +} + +void QWaylandTextInputMethod::text_input_method_v1_locale_changed(const QString &localeName) +{ + m_locale = QLocale(localeName); +} + +void QWaylandTextInputMethod::text_input_method_v1_input_direction_changed(int32_t inputDirection) +{ + m_layoutDirection = Qt::LayoutDirection(inputDirection); +} + +void QWaylandTextInputMethod::text_input_method_v1_keyboard_rectangle_changed(wl_fixed_t x, wl_fixed_t y, wl_fixed_t width, wl_fixed_t height) +{ + m_keyboardRect = QRectF(wl_fixed_to_double(x), + wl_fixed_to_double(y), + wl_fixed_to_double(width), + wl_fixed_to_double(height)); +} + +void QWaylandTextInputMethod::text_input_method_v1_start_input_method_event(uint32_t serial, int32_t surrounding_text_offset) +{ + if (m_pendingInputMethodEvents.contains(serial)) { + qCWarning(qLcQpaInputMethods) << "Input method event with serial" << serial << "already started"; + return; + } + + m_pendingInputMethodEvents[serial] = QList{}; + m_offsetFromCompositor[serial] = surrounding_text_offset; +} + +// We need to keep surrounding text below maxStringSize characters, with cursorPos centered in that substring + +static int calculateOffset(const QString &text, int cursorPos) +{ + int size = text.size(); + int halfSize = maxStringSize/2; + if (size <= maxStringSize || cursorPos < halfSize) + return 0; + if (cursorPos > size - halfSize) + return size - maxStringSize; + return cursorPos - halfSize; +} + +static QString mapSurroundingTextToCompositor(const QString &s, int offset) +{ + return s.mid(offset, maxStringSize); +} + +static int mapPositionToCompositor(int pos, int offset) +{ + return pos - offset; +} + +static int mapPositionFromCompositor(int pos, int offset) +{ + return pos + offset; +} + +void QWaylandTextInputMethod::text_input_method_v1_input_method_event_attribute(uint32_t serial, int32_t type, int32_t start, int32_t length, const QString &value) +{ + if (!m_pendingInputMethodEvents.contains(serial)) { + qCWarning(qLcQpaInputMethods) << "Input method event with serial" << serial << "does not exist"; + return; + } + + int startMapped = mapPositionFromCompositor(start, m_offsetFromCompositor[serial]); + QList &attributes = m_pendingInputMethodEvents[serial]; + switch (type) { + case QInputMethodEvent::Selection: + attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::AttributeType(type), startMapped, length)); + break; + case QInputMethodEvent::Cursor: + attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::AttributeType(type), start, length, QColor(value))); + break; + case QInputMethodEvent::TextFormat: + { + QTextCharFormat textFormat; + textFormat.setProperty(QTextFormat::FontUnderline, true); + textFormat.setProperty(QTextFormat::TextUnderlineStyle, QTextCharFormat::SingleUnderline); + attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::AttributeType(type), start, length, textFormat)); + break; + } + case QInputMethodEvent::Language: + case QInputMethodEvent::Ruby: + attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::AttributeType(type), start, length, value)); + break; + }; +} + +void QWaylandTextInputMethod::sendInputState(QInputMethodQueryEvent *event, Qt::InputMethodQueries queries) +{ + int cursorPosition = event->value(Qt::ImCursorPosition).toInt(); + int anchorPosition = event->value(Qt::ImAnchorPosition).toInt(); + QString surroundingText = event->value(Qt::ImSurroundingText).toString(); + int offset = calculateOffset(surroundingText, cursorPosition); + + if (queries & Qt::ImCursorPosition) + update_cursor_position(mapPositionToCompositor(cursorPosition, offset)); + if (queries & Qt::ImSurroundingText) + update_surrounding_text(mapSurroundingTextToCompositor(surroundingText, offset), offset); + if (queries & Qt::ImAnchorPosition) + update_anchor_position(mapPositionToCompositor(anchorPosition, offset)); + if (queries & Qt::ImAbsolutePosition) + update_absolute_position(event->value(Qt::ImAbsolutePosition).toInt()); // do not map: this is the position in the whole document +} + + +void QWaylandTextInputMethod::text_input_method_v1_end_input_method_event(uint32_t serial, const QString &commitString, const QString &preeditString, int32_t replacementStart, int32_t replacementLength) +{ + if (!m_pendingInputMethodEvents.contains(serial)) { + qCWarning(qLcQpaInputMethods) << "Input method event with serial" << serial << "does not exist"; + return; + } + + QList attributes = m_pendingInputMethodEvents.take(serial); + m_offsetFromCompositor.remove(serial); + if (QGuiApplication::focusObject() != nullptr) { + QInputMethodEvent event(preeditString, attributes); + event.setCommitString(commitString, replacementStart, replacementLength); + QCoreApplication::sendEvent(QGuiApplication::focusObject(), &event); + } + + // Send current state to make sure it matches + if (QGuiApplication::focusObject() != nullptr) { + QInputMethodQueryEvent event(Qt::ImCursorPosition | Qt::ImSurroundingText | Qt::ImAnchorPosition | Qt::ImAbsolutePosition); + QCoreApplication::sendEvent(QGuiApplication::focusObject(), &event); + sendInputState(&event); + } + + acknowledge_input_method(); +} + +void QWaylandTextInputMethod::text_input_method_v1_key(int32_t type, + int32_t key, + int32_t modifiers, + int32_t autoRepeat, + int32_t count, + int32_t nativeScanCode, + int32_t nativeVirtualKey, + int32_t nativeModifiers, + const QString &text) +{ + if (QGuiApplication::focusObject() != nullptr) { + QKeyEvent event(QKeyEvent::Type(type), + key, + Qt::KeyboardModifiers(modifiers), + nativeScanCode, + nativeVirtualKey, + nativeModifiers, + text, + autoRepeat, + count); + QCoreApplication::sendEvent(QGuiApplication::focusObject(), &event); + } +} + +void QWaylandTextInputMethod::text_input_method_v1_enter(struct ::wl_surface *surface) +{ + m_surface = surface; +} + +void QWaylandTextInputMethod::text_input_method_v1_leave(struct ::wl_surface *surface) +{ + if (surface != m_surface) { + qCWarning(qLcQpaInputMethods) << "Got leave event for surface without corresponding enter"; + } else { + m_surface = nullptr; + } +} + +QWaylandInputMethodContext::QWaylandInputMethodContext(QWaylandDisplay *display) + : m_display(display) +{ +} + +QWaylandInputMethodContext::~QWaylandInputMethodContext() +{ +} + +bool QWaylandInputMethodContext::isValid() const +{ + return m_display->textInputMethodManager() != nullptr; +} + +void QWaylandInputMethodContext::reset() +{ + QWaylandTextInputMethod *inputMethod = textInputMethod(); + if (inputMethod != nullptr) + inputMethod->reset(); +} + +void QWaylandInputMethodContext::commit() +{ + QWaylandTextInputMethod *inputMethod = textInputMethod(); + if (inputMethod != nullptr) + inputMethod->commit(); + + m_display->forceRoundTrip(); +} + +void QWaylandInputMethodContext::update(Qt::InputMethodQueries queries) +{ + wl_surface *currentSurface = m_currentWindow != nullptr && m_currentWindow->handle() != nullptr + ? static_cast(m_currentWindow->handle())->wlSurface() + : nullptr; + if (currentSurface != nullptr && !inputMethodAccepted()) { + textInputMethod()->disable(currentSurface); + m_currentWindow.clear(); + } else if (currentSurface == nullptr && inputMethodAccepted()) { + QWindow *window = QGuiApplication::focusWindow(); + currentSurface = window != nullptr && window->handle() != nullptr + ? static_cast(window->handle())->wlSurface() + : nullptr; + if (currentSurface != nullptr) { + textInputMethod()->disable(currentSurface); + m_currentWindow = window; + } + } + + queries &= (Qt::ImEnabled + | Qt::ImHints + | Qt::ImCursorRectangle + | Qt::ImCursorPosition + | Qt::ImSurroundingText + | Qt::ImCurrentSelection + | Qt::ImAnchorPosition + | Qt::ImTextAfterCursor + | Qt::ImTextBeforeCursor + | Qt::ImPreferredLanguage); + + const Qt::InputMethodQueries queriesNeedingOffset = Qt::ImCursorPosition | Qt::ImSurroundingText | Qt::ImAnchorPosition; + if (queries & queriesNeedingOffset) + queries |= queriesNeedingOffset; + + QWaylandTextInputMethod *inputMethod = textInputMethod(); + if (inputMethod != nullptr && QGuiApplication::focusObject() != nullptr) { + QInputMethodQueryEvent event(queries); + QCoreApplication::sendEvent(QGuiApplication::focusObject(), &event); + + inputMethod->start_update(int(queries)); + + if (queries & Qt::ImHints) + inputMethod->update_hints(event.value(Qt::ImHints).toInt()); + + if (queries & Qt::ImCursorRectangle) { + QRect rect = event.value(Qt::ImCursorRectangle).toRect(); + inputMethod->update_cursor_rectangle(rect.x(), rect.y(), rect.width(), rect.height()); + } + + inputMethod->sendInputState(&event, queries); + + if (queries & Qt::ImPreferredLanguage) + inputMethod->update_preferred_language(event.value(Qt::ImPreferredLanguage).toString()); + + inputMethod->end_update(); + + // ### Should we do a display sync here and ignore all events until it is received? + } +} + +void QWaylandInputMethodContext::invokeAction(QInputMethod::Action action, int cursorPosition) +{ + QWaylandTextInputMethod *inputMethod = textInputMethod(); + if (inputMethod != nullptr) + inputMethod->invoke_action(int(action), cursorPosition); +} + +void QWaylandInputMethodContext::showInputPanel() +{ + QWaylandTextInputMethod *inputMethod = textInputMethod(); + if (inputMethod != nullptr) + inputMethod->show_input_panel(); +} + +void QWaylandInputMethodContext::hideInputPanel() +{ + QWaylandTextInputMethod *inputMethod = textInputMethod(); + if (inputMethod != nullptr) + inputMethod->hide_input_panel(); +} + +bool QWaylandInputMethodContext::isInputPanelVisible() const +{ + QWaylandTextInputMethod *inputMethod = textInputMethod(); + if (inputMethod != nullptr) + return inputMethod->isVisible(); + else + return false; +} + +QRectF QWaylandInputMethodContext::keyboardRect() const +{ + QWaylandTextInputMethod *inputMethod = textInputMethod(); + if (inputMethod != nullptr) + return inputMethod->keyboardRect(); + else + return QRectF(); +} + +QLocale QWaylandInputMethodContext::locale() const +{ + QWaylandTextInputMethod *inputMethod = textInputMethod(); + if (inputMethod != nullptr) + return inputMethod->locale(); + else + return QLocale(); +} + +Qt::LayoutDirection QWaylandInputMethodContext::inputDirection() const +{ + QWaylandTextInputMethod *inputMethod = textInputMethod(); + if (inputMethod != nullptr) + return inputMethod->inputDirection(); + else + return Qt::LeftToRight; +} + +void QWaylandInputMethodContext::setFocusObject(QObject *) +{ + QWaylandTextInputMethod *inputMethod = textInputMethod(); + if (inputMethod == nullptr) + return; + + QWindow *window = QGuiApplication::focusWindow(); + + if (m_currentWindow != nullptr && m_currentWindow->handle() != nullptr) { + if (m_currentWindow.data() != window || !inputMethodAccepted()) { + auto *surface = static_cast(m_currentWindow->handle())->wlSurface(); + if (surface) + inputMethod->disable(surface); + m_currentWindow.clear(); + } + } + + if (window != nullptr && window->handle() != nullptr && inputMethodAccepted()) { + if (m_currentWindow.data() != window) { + auto *surface = static_cast(window->handle())->wlSurface(); + if (surface != nullptr) { + inputMethod->enable(surface); + m_currentWindow = window; + } + } + } +} + +QWaylandTextInputMethod *QWaylandInputMethodContext::textInputMethod() const +{ + return m_display->defaultInputDevice()->textInputMethod(); +} + +} // QtWaylandClient + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylandinputmethodcontext_p.h b/src/plugins/platforms/wayland/qwaylandinputmethodcontext_p.h new file mode 100644 index 00000000000..8be65f447b3 --- /dev/null +++ b/src/plugins/platforms/wayland/qwaylandinputmethodcontext_p.h @@ -0,0 +1,152 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 QWAYLANDINPUTMETHODCONTEXT_P_H +#define QWAYLANDINPUTMETHODCONTEXT_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 +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace QtWaylandClient { + class QWaylandDisplay; + +class QWaylandTextInputMethod : public QtWayland::qt_text_input_method_v1 +{ +public: + QWaylandTextInputMethod(QWaylandDisplay *display, struct ::qt_text_input_method_v1 *textInputMethod); + ~QWaylandTextInputMethod() override; + + void text_input_method_v1_visible_changed(int32_t visible) override; + void text_input_method_v1_enter(struct ::wl_surface *surface) override; + void text_input_method_v1_leave(struct ::wl_surface *surface) override; + void text_input_method_v1_locale_changed(const QString &localeName) override; + void text_input_method_v1_input_direction_changed(int32_t inputDirection) override; + void text_input_method_v1_keyboard_rectangle_changed(wl_fixed_t x, wl_fixed_t y, wl_fixed_t width, wl_fixed_t height) override; + void text_input_method_v1_key(int32_t type, int32_t key, int32_t modifiers, int32_t autoRepeat, int32_t count, int32_t nativeScanCode, int32_t nativeVirtualKey, int32_t nativeModifiers, const QString &text) override; + void text_input_method_v1_start_input_method_event(uint32_t serial, int32_t surrounding_text_offset) override; + void text_input_method_v1_end_input_method_event(uint32_t serial, const QString &commitString, const QString &preeditString, int32_t replacementStart, int32_t replacementLength) override; + void text_input_method_v1_input_method_event_attribute(uint32_t serial, int32_t type, int32_t start, int32_t length, const QString &value) override; + + inline bool isVisible() const + { + return m_isVisible; + } + + inline QRectF keyboardRect() const + { + return m_keyboardRect; + } + + inline QLocale locale() const + { + return m_locale; + } + + inline Qt::LayoutDirection inputDirection() const + { + return m_layoutDirection; + } + + void sendInputState(QInputMethodQueryEvent *state, Qt::InputMethodQueries queries = Qt::ImQueryInput); + +private: + QWaylandDisplay *m_display; + + QHash > m_pendingInputMethodEvents; + QHash m_offsetFromCompositor; + + struct ::wl_surface *m_surface; + + // Cached state + bool m_isVisible = false; + QRectF m_keyboardRect; + QLocale m_locale; + Qt::LayoutDirection m_layoutDirection; +}; + +class QWaylandInputMethodContext : public QPlatformInputContext +{ +public: + QWaylandInputMethodContext(QWaylandDisplay *display); + ~QWaylandInputMethodContext() override; + + bool isValid() const override; + void reset() override; + void commit() override; + void update(Qt::InputMethodQueries) override; + void invokeAction(QInputMethod::Action, int cursorPosition) override; + void showInputPanel() override; + void hideInputPanel() override; + + bool isInputPanelVisible() const override; + QRectF keyboardRect() const override; + QLocale locale() const override; + Qt::LayoutDirection inputDirection() const override; + + void setFocusObject(QObject *object) override; + +private: + QWaylandTextInputMethod *textInputMethod() const; + + QWaylandDisplay *m_display; + QPointer m_currentWindow; +}; + +} // QtWaylandClient + +QT_END_NAMESPACE + +#endif // QWAYLANDINPUTMETHODCONTEXT_P_H diff --git a/src/plugins/platforms/wayland/qwaylandintegration.cpp b/src/plugins/platforms/wayland/qwaylandintegration.cpp index 0aa18f09825..764acc2d919 100644 --- a/src/plugins/platforms/wayland/qwaylandintegration.cpp +++ b/src/plugins/platforms/wayland/qwaylandintegration.cpp @@ -43,6 +43,7 @@ #include "qwaylandshmwindow_p.h" #include "qwaylandinputdevice_p.h" #include "qwaylandinputcontext_p.h" +#include "qwaylandinputmethodcontext_p.h" #include "qwaylandshmbackingstore_p.h" #include "qwaylandnativeinterface_p.h" #if QT_CONFIG(clipboard) @@ -465,10 +466,14 @@ void QWaylandIntegration::reconfigureInputContext() qCWarning(lcQpaWayland) << "qtvirtualkeyboard currently is not supported at client-side," " use QT_IM_MODULE=qtvirtualkeyboard at compositor-side."; - if (requested.isNull()) - mInputContext.reset(new QWaylandInputContext(mDisplay.data())); - else + if (requested.isNull()) { + if (mDisplay->textInputMethodManager() != nullptr) + mInputContext.reset(new QWaylandInputMethodContext(mDisplay.data())); + else + mInputContext.reset(new QWaylandInputContext(mDisplay.data())); + } else { mInputContext.reset(QPlatformInputContextFactory::create(requested)); + } const QString defaultInputContext(QStringLiteral("compose")); if ((!mInputContext || !mInputContext->isValid()) && requested != defaultInputContext)