diff options
Diffstat (limited to 'src/compositor/extensions/qwaylandtextinputv3.cpp')
-rw-r--r-- | src/compositor/extensions/qwaylandtextinputv3.cpp | 503 |
1 files changed, 503 insertions, 0 deletions
diff --git a/src/compositor/extensions/qwaylandtextinputv3.cpp b/src/compositor/extensions/qwaylandtextinputv3.cpp new file mode 100644 index 000000000..3da89e630 --- /dev/null +++ b/src/compositor/extensions/qwaylandtextinputv3.cpp @@ -0,0 +1,503 @@ +// 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.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 + +Q_DECLARE_LOGGING_CATEGORY(qLcWaylandCompositorTextInput) + +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.length(); + + if (event->replacementLength() > 0 || event->replacementStart() < 0) { + if (event->replacementStart() <= 0 && (event->replacementLength() >= -event->replacementStart())) { + const int selectionStart = qMin(currentState->cursorPosition, currentState->anchorPosition); + const int selectionEnd = qMax(currentState->cursorPosition, currentState->anchorPosition); + const int before = QWaylandInputMethodEventBuilder::indexToWayland(currentState->surroundingText, -event->replacementStart(), selectionStart + event->replacementStart()); + const int after = QWaylandInputMethodEventBuilder::indexToWayland(currentState->surroundingText, event->replacementLength() + event->replacementStart(), selectionEnd); + send_delete_surrounding_text(focusResource->handle, before, after); + needsDone = true; + } else { + qCWarning(qLcWaylandCompositorTextInput) << "Not yet supported case of replacement. Start:" << event->replacementStart() << "length:" << event->replacementLength(); + } + preeditCursorPos = event->replacementStart() + event->replacementLength(); + } + + 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) { + // sync before leave + // IBUS commits by itself but qtvirtualkeyboard doesn't + // And when handling chinese input, it is required to commit + // before leaving the focus. + if (qgetenv("QT_IM_MODULE") != QByteArrayLiteral("ibus") + || qApp->inputMethod()->locale().language() == QLocale::Chinese) { + 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 + if (qgetenv("QT_IM_MODULE") != QByteArrayLiteral("ibus") + || qApp->inputMethod()->locale().language() == QLocale::Chinese) { + 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 |