summaryrefslogtreecommitdiffstats
path: root/src/client/qwaylandinputmethodcontext.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/qwaylandinputmethodcontext.cpp')
-rw-r--r--src/client/qwaylandinputmethodcontext.cpp401
1 files changed, 401 insertions, 0 deletions
diff --git a/src/client/qwaylandinputmethodcontext.cpp b/src/client/qwaylandinputmethodcontext.cpp
new file mode 100644
index 000000000..2733e4f3a
--- /dev/null
+++ b/src/client/qwaylandinputmethodcontext.cpp
@@ -0,0 +1,401 @@
+// Copyright (C) 2020 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 "qwaylandinputmethodcontext_p.h"
+#include "qwaylanddisplay_p.h"
+#include "qwaylandinputdevice_p.h"
+
+#include <QtGui/qguiapplication.h>
+#include <QtGui/qtextformat.h>
+#include <QtGui/private/qguiapplication_p.h>
+
+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)
+{
+ Q_UNUSED(display);
+}
+
+QWaylandTextInputMethod::~QWaylandTextInputMethod()
+{
+ qt_text_input_method_v1_destroy(object());
+}
+
+void QWaylandTextInputMethod::text_input_method_v1_visible_changed(int32_t visible)
+{
+ if (m_isVisible != visible) {
+ m_isVisible = visible;
+ QGuiApplicationPrivate::platformIntegration()->inputContext()->emitInputPanelVisibleChanged();
+ }
+}
+
+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)
+{
+ const QRectF keyboardRectangle(wl_fixed_to_double(x),
+ wl_fixed_to_double(y),
+ wl_fixed_to_double(width),
+ wl_fixed_to_double(height));
+ if (m_keyboardRect != keyboardRectangle) {
+ m_keyboardRect = keyboardRectangle;
+ QGuiApplicationPrivate::platformIntegration()->inputContext()->emitKeyboardRectChanged();
+ }
+}
+
+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<QInputMethodEvent::Attribute>{};
+ 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<QInputMethodEvent::Attribute> &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::fromString(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<QInputMethodEvent::Attribute> 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<QWaylandWindow *>(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<QWaylandWindow *>(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;
+
+ if (inputMethod->isVisible() && !inputMethodAccepted())
+ inputMethod->hide_input_panel();
+
+ QWindow *window = QGuiApplication::focusWindow();
+
+ if (m_currentWindow != nullptr && m_currentWindow->handle() != nullptr) {
+ if (m_currentWindow.data() != window || !inputMethodAccepted()) {
+ auto *surface = static_cast<QWaylandWindow *>(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<QWaylandWindow *>(window->handle())->wlSurface();
+ if (surface != nullptr) {
+ inputMethod->enable(surface);
+ m_currentWindow = window;
+ }
+ }
+
+ update(Qt::ImQueryAll);
+ }
+}
+
+QWaylandTextInputMethod *QWaylandInputMethodContext::textInputMethod() const
+{
+ return m_display->defaultInputDevice() ? m_display->defaultInputDevice()->textInputMethod() : nullptr;
+}
+
+} // QtWaylandClient
+
+QT_END_NAMESPACE
+
+#include "moc_qwaylandinputmethodcontext_p.cpp"