summaryrefslogtreecommitdiffstats
path: root/src/client/qwaylandtextinputv3.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/qwaylandtextinputv3.cpp')
-rw-r--r--src/client/qwaylandtextinputv3.cpp351
1 files changed, 351 insertions, 0 deletions
diff --git a/src/client/qwaylandtextinputv3.cpp b/src/client/qwaylandtextinputv3.cpp
new file mode 100644
index 000000000..017456ac2
--- /dev/null
+++ b/src/client/qwaylandtextinputv3.cpp
@@ -0,0 +1,351 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qwaylandtextinputv3_p.h"
+
+#include "qwaylandwindow_p.h"
+#include "qwaylandinputmethodeventbuilder_p.h"
+
+#include <QtCore/qloggingcategory.h>
+#include <QtGui/qguiapplication.h>
+#include <QtGui/private/qhighdpiscaling_p.h>
+#include <QtGui/qevent.h>
+#include <QtGui/qwindow.h>
+#include <QTextCharFormat>
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(qLcQpaWaylandTextInput, "qt.qpa.wayland.textinput")
+
+namespace QtWaylandClient {
+
+QWaylandTextInputv3::QWaylandTextInputv3(QWaylandDisplay *display,
+ struct ::zwp_text_input_v3 *text_input)
+ : QtWayland::zwp_text_input_v3(text_input)
+{
+ Q_UNUSED(display)
+}
+
+QWaylandTextInputv3::~QWaylandTextInputv3()
+{
+ destroy();
+}
+
+namespace {
+const Qt::InputMethodQueries supportedQueries3 = Qt::ImEnabled |
+ Qt::ImSurroundingText |
+ Qt::ImCursorPosition |
+ Qt::ImAnchorPosition |
+ Qt::ImHints |
+ Qt::ImCursorRectangle;
+}
+
+void QWaylandTextInputv3::zwp_text_input_v3_enter(struct ::wl_surface *surface)
+{
+ qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << m_surface << surface;
+
+ m_surface = surface;
+
+ m_pendingPreeditString.clear();
+ m_pendingCommitString.clear();
+ m_pendingDeleteBeforeText = 0;
+ m_pendingDeleteAfterText = 0;
+
+ enable();
+ updateState(supportedQueries3, update_state_enter);
+}
+
+void QWaylandTextInputv3::zwp_text_input_v3_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;
+ }
+
+ m_currentPreeditString.clear();
+
+ m_surface = nullptr;
+
+ disable();
+ qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "Done";
+}
+
+void QWaylandTextInputv3::zwp_text_input_v3_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 QWaylandTextInputv3::zwp_text_input_v3_commit_string(const QString &text)
+{
+ qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << text;
+
+ if (!QGuiApplication::focusObject())
+ return;
+
+ m_pendingCommitString = text;
+}
+
+void QWaylandTextInputv3::zwp_text_input_v3_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 QWaylandTextInputv3::zwp_text_input_v3_done(uint32_t serial)
+{
+ qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "with serial" << serial << m_currentSerial;
+
+ // This is a case of double click.
+ // text_input_v3 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<QInputMethodEvent::Attribute> 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(supportedQueries3, update_state_full);
+}
+
+void QWaylandTextInputv3::reset()
+{
+ qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO;
+
+ m_pendingPreeditString.clear();
+}
+
+void QWaylandTextInputv3::commit()
+{
+ m_currentSerial = (m_currentSerial < UINT_MAX) ? m_currentSerial + 1U: 0U;
+
+ qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO << "with serial" << m_currentSerial;
+ QtWayland::zwp_text_input_v3::commit();
+}
+
+void QWaylandTextInputv3::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<QWaylandWindow *>(QGuiApplication::focusWindow()->handle());
+ auto *surface = window->wlSurface();
+ if (!surface || (surface != m_surface))
+ return;
+
+ queries &= supportedQueries3;
+ 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 QRect &nativeRect = QHighDpi::toNativePixels(windowRect, QGuiApplication::focusWindow());
+ const QMargins margins = window->clientSideMargins();
+ const QRect &surfaceRect = nativeRect.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::convertV3(static_cast<Qt::InputMethodHints>(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 QWaylandTextInputv3::setCursorInsidePreedit(int cursor)
+{
+ Q_UNUSED(cursor);
+}
+
+bool QWaylandTextInputv3::isInputPanelVisible() const
+{
+ return false;
+}
+
+QRectF QWaylandTextInputv3::keyboardRect() const
+{
+ qCDebug(qLcQpaWaylandTextInput) << Q_FUNC_INFO;
+ return m_cursorRect;
+}
+
+QLocale QWaylandTextInputv3::locale() const
+{
+ return QLocale();
+}
+
+Qt::LayoutDirection QWaylandTextInputv3::inputDirection() const
+{
+ return Qt::LeftToRight;
+}
+
+}
+
+QT_END_NAMESPACE