Revision control

++ // Copyright (C) 2021 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ // Qt-Security score:critical reason:network-protocol
++
++ #include "qwaylandtextinputv3.h"
++ #include "qwaylandtextinputv3_p.h"
++
++ #include <QtWaylandCompositor/QWaylandCompositor>
++ #include <QtWaylandCompositor/private/qwaylandseat_p.h>
++
++ #include "qwaylandsurface.h"
++ #include "qwaylandview.h"
++ #include "qwaylandinputmethodeventbuilder_p.h"
++
++ #include <QGuiApplication>
++ #include <QInputMethodEvent>
++ #include <qpa/qwindowsysteminterface.h>
++
++ #if QT_CONFIG(xkbcommon)
++ #include <QtGui/private/qxkbcommon_p.h>
++ #endif
++
++ QT_BEGIN_NAMESPACE
++
++ QWaylandTextInputV3ClientState::QWaylandTextInputV3ClientState()
++ {
++ }
++
++ Qt::InputMethodQueries QWaylandTextInputV3ClientState::updatedQueries(const QWaylandTextInputV3ClientState &other) const
++ {
++ qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO;
++
++ Qt::InputMethodQueries queries;
++
++ if (hints != other.hints)
++ queries |= Qt::ImHints;
++ if (cursorRectangle != other.cursorRectangle)
++ queries |= Qt::ImCursorRectangle;
++ if (surroundingText != other.surroundingText)
++ queries |= Qt::ImSurroundingText | Qt::ImCurrentSelection;
++ if (cursorPosition != other.cursorPosition)
++ queries |= Qt::ImCursorPosition | Qt::ImCurrentSelection;
++ if (anchorPosition != other.anchorPosition)
++ queries |= Qt::ImAnchorPosition | Qt::ImCurrentSelection;
++
++ return queries;
++ }
++
++ Qt::InputMethodQueries QWaylandTextInputV3ClientState::mergeChanged(const QWaylandTextInputV3ClientState &other) {
++
++ Qt::InputMethodQueries queries;
++
++ if ((other.changedState & Qt::ImHints) && hints != other.hints) {
++ hints = other.hints;
++ queries |= Qt::ImHints;
++ }
++
++ if ((other.changedState & Qt::ImCursorRectangle) && cursorRectangle != other.cursorRectangle) {
++ cursorRectangle = other.cursorRectangle;
++ queries |= Qt::ImCursorRectangle;
++ }
++
++ if ((other.changedState & Qt::ImSurroundingText) && surroundingText != other.surroundingText) {
++ surroundingText = other.surroundingText;
++ queries |= Qt::ImSurroundingText | Qt::ImCurrentSelection;
++ }
++
++ if ((other.changedState & Qt::ImCursorPosition) && cursorPosition != other.cursorPosition) {
++ cursorPosition = other.cursorPosition;
++ queries |= Qt::ImCursorPosition | Qt::ImCurrentSelection;
++ }
++
++ if ((other.changedState & Qt::ImAnchorPosition) && anchorPosition != other.anchorPosition) {
++ anchorPosition = other.anchorPosition;
++ queries |= Qt::ImAnchorPosition | Qt::ImCurrentSelection;
++ }
++
++ return queries;
++ }
++
++ QWaylandTextInputV3Private::QWaylandTextInputV3Private(QWaylandCompositor *compositor)
++ : compositor(compositor)
++ , currentState(new QWaylandTextInputV3ClientState)
++ , pendingState(new QWaylandTextInputV3ClientState)
++ {
++ }
++
++ void QWaylandTextInputV3Private::sendInputMethodEvent(QInputMethodEvent *event)
++ {
++ qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO;
++
++ if (!focusResource || !focusResource->handle)
++ return;
++
++ bool needsDone = false;
++
++ const QString &newPreeditString = event->preeditString();
++
++ // Current cursor shape is only line. It means both cursorBegin
++ // and cursorEnd will be the same values.
++ int32_t preeditCursorPos = newPreeditString.toUtf8().size();
++
++ if (event->replacementLength() > 0) {
++ int replacementStart = event->replacementStart();
++ int replacementLength = event->replacementLength();
++ const int cursorPos = currentState->cursorPosition;
++ if (currentState->cursorPosition < -event->replacementStart()) {
++ qCWarning(qLcWaylandCompositorTextInput)
++ << Q_FUNC_INFO
++ << "Invalid replacementStart :" << replacementStart
++ << "on the cursorPosition :" << cursorPos;
++ replacementStart = -cursorPos;
++ }
++ auto targetText = QStringView{currentState->surroundingText}.sliced(cursorPos + replacementStart);
++ if (targetText.length() < replacementLength) {
++ qCWarning(qLcWaylandCompositorTextInput)
++ << Q_FUNC_INFO
++ << "Invalid replacementLength :" << replacementLength
++ << "for the surrounding text :" << targetText;
++ replacementLength = targetText.length();
++ }
++ const int before = targetText.first(-replacementStart).toUtf8().size();
++ const int after = targetText.first(replacementLength).toUtf8().size() - before;
++
++ send_delete_surrounding_text(focusResource->handle, before, after);
++ needsDone = true;
++
++ // The commit will also be applied here
++ currentState->surroundingText.replace(cursorPos + replacementStart,
++ replacementLength,
++ event->commitString());
++ currentState->cursorPosition = cursorPos + replacementStart + event->commitString().length();
++ currentState->anchorPosition = cursorPos + replacementStart + event->commitString().length();
++ qApp->inputMethod()->update(Qt::ImSurroundingText | Qt::ImCursorPosition | Qt::ImAnchorPosition);
++ }
++
++ if (currentPreeditString != newPreeditString) {
++ currentPreeditString = newPreeditString;
++ send_preedit_string(focusResource->handle, currentPreeditString, preeditCursorPos, preeditCursorPos);
++ needsDone = true;
++ }
++ if (!event->commitString().isEmpty()) {
++ send_commit_string(focusResource->handle, event->commitString());
++ needsDone = true;
++ }
++
++ if (needsDone)
++ send_done(focusResource->handle, serials[focusResource]);
++ }
++
++
++ void QWaylandTextInputV3Private::sendKeyEvent(QKeyEvent *event)
++ {
++ qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO;
++
++ if (!focusResource || !focusResource->handle)
++ return;
++
++ send_commit_string(focusResource->handle, event->text());
++
++ send_done(focusResource->handle, serials[focusResource]);
++ }
++
++ QVariant QWaylandTextInputV3Private::inputMethodQuery(Qt::InputMethodQuery property, QVariant argument) const
++ {
++ qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO << property;
++
++ switch (property) {
++ case Qt::ImHints:
++ return QVariant(static_cast<int>(currentState->hints));
++ case Qt::ImCursorRectangle:
++ return currentState->cursorRectangle;
++ case Qt::ImFont:
++ // Not supported
++ return QVariant();
++ case Qt::ImCursorPosition:
++ qCDebug(qLcWaylandCompositorTextInput) << currentState->cursorPosition;
++ return currentState->cursorPosition;
++ case Qt::ImSurroundingText:
++ qCDebug(qLcWaylandCompositorTextInput) << currentState->surroundingText;
++ return currentState->surroundingText;
++ case Qt::ImCurrentSelection:
++ return currentState->surroundingText.mid(qMin(currentState->cursorPosition, currentState->anchorPosition),
++ qAbs(currentState->anchorPosition - currentState->cursorPosition));
++ case Qt::ImMaximumTextLength:
++ // Not supported
++ return QVariant();
++ case Qt::ImAnchorPosition:
++ qCDebug(qLcWaylandCompositorTextInput) << currentState->anchorPosition;
++ return currentState->anchorPosition;
++ case Qt::ImAbsolutePosition:
++ // We assume the surrounding text is our whole document for now
++ return currentState->cursorPosition;
++ case Qt::ImTextAfterCursor:
++ if (argument.isValid())
++ return currentState->surroundingText.mid(currentState->cursorPosition, argument.toInt());
++ return currentState->surroundingText.mid(currentState->cursorPosition);
++ case Qt::ImTextBeforeCursor:
++ if (argument.isValid())
++ return currentState->surroundingText.left(currentState->cursorPosition).right(argument.toInt());
++ return currentState->surroundingText.left(currentState->cursorPosition);
++
++ default:
++ return QVariant();
++ }
++ }
++
++ void QWaylandTextInputV3Private::setFocus(QWaylandSurface *surface)
++ {
++ qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO << surface;
++
++ if (focusResource && focus) {
++ qApp->inputMethod()->commit();
++ qApp->inputMethod()->hide();
++ inputPanelVisible = false;
++ send_leave(focusResource->handle, focus->resource());
++ currentPreeditString.clear();
++ }
++
++ if (focus != surface)
++ focusDestroyListener.reset();
++
++ Resource *resource = surface ? resourceMap().value(surface->waylandClient()) : 0;
++ if (resource && surface) {
++ send_enter(resource->handle, surface->resource());
++
++ if (focus != surface)
++ focusDestroyListener.listenForDestruction(surface->resource());
++ }
++
++ focus = surface;
++ focusResource = resource;
++ }
++
++ void QWaylandTextInputV3Private::zwp_text_input_v3_bind_resource(Resource *resource)
++ {
++ qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO;
++
++ serials.insert(resource, 0);
++ }
++
++ void QWaylandTextInputV3Private::zwp_text_input_v3_destroy_resource(Resource *resource)
++ {
++ qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO;
++
++ serials.remove(resource);
++ if (focusResource == resource)
++ focusResource = nullptr;
++ }
++
++ void QWaylandTextInputV3Private::zwp_text_input_v3_destroy(Resource *resource)
++ {
++ qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO;
++
++ wl_resource_destroy(resource->handle);
++ }
++
++ void QWaylandTextInputV3Private::zwp_text_input_v3_enable(Resource *resource)
++ {
++ qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO;
++
++ Q_Q(QWaylandTextInputV3);
++
++ pendingState.reset(new QWaylandTextInputV3ClientState);
++
++ enabledSurfaces.insert(resource, focus);
++ emit q->surfaceEnabled(focus);
++
++ inputPanelVisible = true;
++ qApp->inputMethod()->show();
++ }
++
++ void QWaylandTextInputV3Private::zwp_text_input_v3_disable(QtWaylandServer::zwp_text_input_v3::Resource *resource)
++ {
++ qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO;
++
++ Q_Q(QWaylandTextInputV3);
++
++ QWaylandSurface *s = enabledSurfaces.take(resource);
++ emit q->surfaceDisabled(s);
++
++ // When reselecting a word by setFocus
++ qApp->inputMethod()->commit();
++
++ qApp->inputMethod()->reset();
++ pendingState.reset(new QWaylandTextInputV3ClientState);
++ }
++
++ void QWaylandTextInputV3Private::zwp_text_input_v3_set_cursor_rectangle(Resource *resource, int32_t x, int32_t y, int32_t width, int32_t height)
++ {
++ qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO << x << y << width << height;
++
++ if (resource != focusResource)
++ return;
++
++ pendingState->cursorRectangle = QRect(x, y, width, height);
++
++ pendingState->changedState |= Qt::ImCursorRectangle;
++ }
++
++ void QWaylandTextInputV3Private::zwp_text_input_v3_commit(Resource *resource)
++ {
++ qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO;
++
++ serials[resource] = serials[resource] < UINT_MAX ? serials[resource] + 1U : 0U;
++
++ // Just increase serials and ignore empty commits
++ if (!pendingState->changedState) {
++ qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO << "pendingState is not changed";
++ return;
++ }
++
++ // Selection starts.
++ // But since qtvirtualkeyboard with hunspell does not reset its preedit string,
++ // compositor forces to reset inputMethod.
++ if ((currentState->cursorPosition == currentState->anchorPosition)
++ && (pendingState->cursorPosition != pendingState->anchorPosition))
++ qApp->inputMethod()->reset();
++
++ // Enable reselection
++ // This is a workaround to make qtvirtualkeyboad's state empty by clearing State::InputMethodClick.
++ if (currentState->surroundingText == pendingState->surroundingText && currentState->cursorPosition != pendingState->cursorPosition)
++ qApp->inputMethod()->invokeAction(QInputMethod::Click, pendingState->cursorPosition);
++
++ Qt::InputMethodQueries queries = currentState->mergeChanged(*pendingState.data());
++ pendingState.reset(new QWaylandTextInputV3ClientState);
++
++ if (queries) {
++ qCDebug(qLcWaylandCompositorTextInput) << "QInputMethod::update() after commit with" << queries;
++
++ qApp->inputMethod()->update(queries);
++ }
++ }
++
++ void QWaylandTextInputV3Private::zwp_text_input_v3_set_content_type(Resource *resource, uint32_t hint, uint32_t purpose)
++ {
++ qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO << hint << purpose;
++
++ if (resource != focusResource)
++ return;
++
++ pendingState->hints = Qt::ImhNone;
++
++ if ((hint & content_hint_completion) == 0)
++ pendingState->hints |= Qt::ImhNoPredictiveText;
++ if ((hint & content_hint_auto_capitalization) == 0)
++ pendingState->hints |= Qt::ImhNoAutoUppercase;
++ if ((hint & content_hint_lowercase) != 0)
++ pendingState->hints |= Qt::ImhPreferLowercase;
++ if ((hint & content_hint_uppercase) != 0)
++ pendingState->hints |= Qt::ImhPreferUppercase;
++ if ((hint & content_hint_hidden_text) != 0)
++ pendingState->hints |= Qt::ImhHiddenText;
++ if ((hint & content_hint_sensitive_data) != 0)
++ pendingState->hints |= Qt::ImhSensitiveData;
++ if ((hint & content_hint_latin) != 0)
++ pendingState->hints |= Qt::ImhLatinOnly;
++ if ((hint & content_hint_multiline) != 0)
++ pendingState->hints |= Qt::ImhMultiLine;
++
++ switch (purpose) {
++ case content_purpose_normal:
++ break;
++ case content_purpose_alpha:
++ pendingState->hints |= Qt::ImhUppercaseOnly | Qt::ImhLowercaseOnly;
++ break;
++ case content_purpose_digits:
++ pendingState->hints |= Qt::ImhDigitsOnly;
++ break;
++ case content_purpose_number:
++ pendingState->hints |= Qt::ImhFormattedNumbersOnly;
++ break;
++ case content_purpose_phone:
++ pendingState->hints |= Qt::ImhDialableCharactersOnly;
++ break;
++ case content_purpose_url:
++ pendingState->hints |= Qt::ImhUrlCharactersOnly;
++ break;
++ case content_purpose_email:
++ pendingState->hints |= Qt::ImhEmailCharactersOnly;
++ break;
++ case content_purpose_name:
++ case content_purpose_password:
++ break;
++ case content_purpose_date:
++ pendingState->hints |= Qt::ImhDate;
++ break;
++ case content_purpose_time:
++ pendingState->hints |= Qt::ImhTime;
++ break;
++ case content_purpose_datetime:
++ pendingState->hints |= Qt::ImhDate | Qt::ImhTime;
++ break;
++ case content_purpose_terminal:
++ default:
++ break;
++ }
++
++ qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO << pendingState->hints;
++
++ pendingState->changedState |= Qt::ImHints;
++ }
++
++ void QWaylandTextInputV3Private::zwp_text_input_v3_set_surrounding_text(Resource *resource, const QString &text, int32_t cursor, int32_t anchor)
++ {
++ qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO << text << cursor << anchor;
++
++ if (resource != focusResource)
++ return;
++
++ pendingState->surroundingText = text;
++ pendingState->cursorPosition = QWaylandInputMethodEventBuilder::indexFromWayland(text, cursor);
++ pendingState->anchorPosition = QWaylandInputMethodEventBuilder::indexFromWayland(text, anchor);
++
++ pendingState->changedState |= Qt::ImSurroundingText | Qt::ImCursorPosition | Qt::ImAnchorPosition;
++ }
++
++ void QWaylandTextInputV3Private::zwp_text_input_v3_set_text_change_cause(Resource *resource, uint32_t cause)
++ {
++ qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO;
++
++ Q_UNUSED(resource);
++ Q_UNUSED(cause);
++ }
++
++ QWaylandTextInputV3::QWaylandTextInputV3(QWaylandObject *container, QWaylandCompositor *compositor)
++ : QWaylandCompositorExtensionTemplate(container, *new QWaylandTextInputV3Private(compositor))
++ {
++ connect(&d_func()->focusDestroyListener, &QWaylandDestroyListener::fired,
++ this, &QWaylandTextInputV3::focusSurfaceDestroyed);
++ }
++
++ QWaylandTextInputV3::~QWaylandTextInputV3()
++ {
++ }
++
++ void QWaylandTextInputV3::sendInputMethodEvent(QInputMethodEvent *event)
++ {
++ Q_D(QWaylandTextInputV3);
++
++ d->sendInputMethodEvent(event);
++ }
++
++ void QWaylandTextInputV3::sendKeyEvent(QKeyEvent *event)
++ {
++ Q_D(QWaylandTextInputV3);
++
++ d->sendKeyEvent(event);
++ }
++
++ QVariant QWaylandTextInputV3::inputMethodQuery(Qt::InputMethodQuery property, QVariant argument) const
++ {
++ const Q_D(QWaylandTextInputV3);
++
++ return d->inputMethodQuery(property, argument);
++ }
++
++ QWaylandSurface *QWaylandTextInputV3::focus() const
++ {
++ const Q_D(QWaylandTextInputV3);
++
++ return d->focus;
++ }
++
++ void QWaylandTextInputV3::setFocus(QWaylandSurface *surface)
++ {
++ Q_D(QWaylandTextInputV3);
++
++ d->setFocus(surface);
++ }
++
++ void QWaylandTextInputV3::focusSurfaceDestroyed(void *)
++ {
++ qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO;
++
++ Q_D(QWaylandTextInputV3);
++
++ d->focusDestroyListener.reset();
++
++ d->focus = nullptr;
++ d->focusResource = nullptr;
++ }
++
++ bool QWaylandTextInputV3::isSurfaceEnabled(QWaylandSurface *surface) const
++ {
++ qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO;
++
++ const Q_D(QWaylandTextInputV3);
++
++ return d->enabledSurfaces.values().contains(surface);
++ }
++
++ void QWaylandTextInputV3::add(::wl_client *client, uint32_t id, int version)
++ {
++ qCDebug(qLcWaylandCompositorTextInput) << Q_FUNC_INFO << client << id << version;
++
++ Q_D(QWaylandTextInputV3);
++
++ d->add(client, id, version);
++ }
++
++ const wl_interface *QWaylandTextInputV3::interface()
++ {
++ return QWaylandTextInputV3Private::interface();
++ }
++
++ QByteArray QWaylandTextInputV3::interfaceName()
++ {
++ return QWaylandTextInputV3Private::interfaceName();
++ }
++
++ QT_END_NAMESPACE