Revision control
++ // Copyright (C) 2020 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ // Qt-Security score:critical reason:network-protocol
++
++ #include "qwaylandqttextinputmethod.h"
++ #include "qwaylandqttextinputmethod_p.h"
++
++ #include <QtGui/qevent.h>
++ #include <QtGui/qguiapplication.h>
++ #include <QtGui/qinputmethod.h>
++ #include <QtGui/qcolor.h>
++ #include <QtGui/qtextformat.h>
++
++ #include <QtCore/qelapsedtimer.h>
++
++ #include <QtWaylandCompositor/qwaylandcompositor.h>
++ #include <QtWaylandCompositor/qwaylandsurface.h>
++
++ QT_BEGIN_NAMESPACE
++
++ QWaylandQtTextInputMethodPrivate::QWaylandQtTextInputMethodPrivate(QWaylandCompositor *c)
++ : compositor(c)
++ {
++ }
++
++ void QWaylandQtTextInputMethodPrivate::text_input_method_v1_enable(Resource *resource, struct ::wl_resource *surface)
++ {
++ Q_Q(QWaylandQtTextInputMethod);
++ if (this->resource == resource) {
++ QWaylandSurface *waylandSurface = QWaylandSurface::fromResource(surface);
++ if (surface != nullptr) {
++ enabledSurfaces[resource] = waylandSurface;
++ emit q->surfaceEnabled(waylandSurface);
++ }
++ }
++ }
++
++ void QWaylandQtTextInputMethodPrivate::text_input_method_v1_disable(Resource *resource, struct ::wl_resource *surface)
++ {
++ Q_Q(QWaylandQtTextInputMethod);
++ if (this->resource == resource) {
++ QWaylandSurface *waylandSurface = QWaylandSurface::fromResource(surface);
++ QWaylandSurface *enabledSurface = enabledSurfaces.take(resource);
++
++ if (Q_UNLIKELY(enabledSurface != waylandSurface))
++ qCWarning(qLcWaylandCompositorInputMethods) << "Disabled surface does not match the one currently enabled";
++
++ emit q->surfaceEnabled(waylandSurface);
++ }
++ }
++
++ void QWaylandQtTextInputMethodPrivate::text_input_method_v1_destroy(Resource *resource)
++ {
++ if (this->resource == resource)
++ wl_resource_destroy(resource->handle);
++ }
++
++ void QWaylandQtTextInputMethodPrivate::text_input_method_v1_reset(Resource *resource)
++ {
++ if (this->resource == resource)
++ qApp->inputMethod()->reset();
++ }
++
++ void QWaylandQtTextInputMethodPrivate::text_input_method_v1_commit(Resource *resource)
++ {
++ if (this->resource == resource)
++ qApp->inputMethod()->commit();
++ }
++
++ void QWaylandQtTextInputMethodPrivate::text_input_method_v1_show_input_panel(Resource *resource)
++ {
++ if (this->resource == resource)
++ qApp->inputMethod()->show();
++ }
++
++ void QWaylandQtTextInputMethodPrivate::text_input_method_v1_hide_input_panel(Resource *resource)
++ {
++ if (this->resource == resource)
++ qApp->inputMethod()->hide();
++ }
++
++ void QWaylandQtTextInputMethodPrivate::text_input_method_v1_invoke_action(Resource *resource, int32_t type, int32_t cursorPosition)
++ {
++ if (this->resource == resource)
++ qApp->inputMethod()->invokeAction(QInputMethod::Action(type), cursorPosition);
++ }
++
++ void QWaylandQtTextInputMethodPrivate::text_input_method_v1_update_cursor_rectangle(Resource *resource, int32_t x, int32_t y, int32_t width, int32_t height)
++ {
++ if (this->resource == resource)
++ cursorRectangle = QRect(x, y, width, height);
++ }
++
++ void QWaylandQtTextInputMethodPrivate::text_input_method_v1_start_update(Resource *resource, int32_t queries)
++ {
++ if (this->resource == resource)
++ updatingQueries = Qt::InputMethodQueries(queries);
++ }
++
++ void QWaylandQtTextInputMethodPrivate::text_input_method_v1_update_hints(Resource *resource, int32_t hints)
++ {
++ if (this->resource == resource)
++ this->hints = Qt::InputMethodHints(hints);
++ }
++
++ void QWaylandQtTextInputMethodPrivate::text_input_method_v1_update_anchor_position(Resource *resource, int32_t anchorPosition)
++ {
++ if (this->resource == resource)
++ this->anchorPosition = anchorPosition;
++ }
++
++ void QWaylandQtTextInputMethodPrivate::text_input_method_v1_update_cursor_position(Resource *resource, int32_t cursorPosition)
++ {
++ if (this->resource == resource)
++ this->cursorPosition = cursorPosition;
++ }
++
++ void QWaylandQtTextInputMethodPrivate::text_input_method_v1_update_surrounding_text(Resource *resource, const QString &surroundingText, int32_t surroundingTextOffset)
++ {
++ if (this->resource == resource) {
++ this->surroundingText = surroundingText;
++ this->surroundingTextOffset = surroundingTextOffset;
++ }
++ }
++
++ void QWaylandQtTextInputMethodPrivate::text_input_method_v1_update_absolute_position(Resource *resource, int32_t absolutePosition)
++ {
++ if (this->resource == resource)
++ this->absolutePosition = absolutePosition;
++ }
++
++ void QWaylandQtTextInputMethodPrivate::text_input_method_v1_update_preferred_language(Resource *resource, const QString &preferredLanguage)
++ {
++ if (this->resource == resource)
++ this->preferredLanguage = preferredLanguage;
++ }
++
++ void QWaylandQtTextInputMethodPrivate::text_input_method_v1_end_update(Resource *resource)
++ {
++ Q_Q(QWaylandQtTextInputMethod);
++ if (this->resource == resource && updatingQueries != 0) {
++ Qt::InputMethodQueries queries = updatingQueries;
++ updatingQueries = Qt::InputMethodQueries();
++ emit q->updateInputMethod(queries);
++ }
++ }
++
++ void QWaylandQtTextInputMethodPrivate::text_input_method_v1_acknowledge_input_method(Resource *resource)
++ {
++ if (this->resource == resource)
++ waitingForSync = false;
++ }
++
++ QWaylandQtTextInputMethod::QWaylandQtTextInputMethod(QWaylandObject *container, QWaylandCompositor *compositor)
++ : QWaylandCompositorExtensionTemplate(container, *new QWaylandQtTextInputMethodPrivate(compositor))
++ {
++ connect(&d_func()->focusDestroyListener, &QWaylandDestroyListener::fired,
++ this, &QWaylandQtTextInputMethod::focusSurfaceDestroyed);
++
++ connect(qGuiApp->inputMethod(), &QInputMethod::visibleChanged, this, &QWaylandQtTextInputMethod::sendVisibleChanged);
++ connect(qGuiApp->inputMethod(), &QInputMethod::keyboardRectangleChanged, this, &QWaylandQtTextInputMethod::sendKeyboardRectangleChanged);
++ connect(qGuiApp->inputMethod(), &QInputMethod::inputDirectionChanged, this, &QWaylandQtTextInputMethod::sendInputDirectionChanged);
++ connect(qGuiApp->inputMethod(), &QInputMethod::localeChanged, this, &QWaylandQtTextInputMethod::sendLocaleChanged);
++ }
++
++
++ QWaylandQtTextInputMethod::~QWaylandQtTextInputMethod()
++ {
++ }
++
++ void QWaylandQtTextInputMethod::focusSurfaceDestroyed()
++ {
++ Q_D(QWaylandQtTextInputMethod);
++ d->focusDestroyListener.reset();
++ d->waitingForSync = false;
++ d->resource = nullptr;
++ d->focusedSurface = nullptr;
++ }
++
++ QWaylandSurface *QWaylandQtTextInputMethod::focusedSurface() const
++ {
++ Q_D(const QWaylandQtTextInputMethod);
++ return d->focusedSurface;
++ }
++
++ QVariant QWaylandQtTextInputMethod::inputMethodQuery(Qt::InputMethodQuery property, QVariant argument) const
++ {
++ Q_D(const QWaylandQtTextInputMethod);
++ switch (property) {
++ case Qt::ImHints:
++ return int(d->hints);
++ case Qt::ImCursorRectangle:
++ return d->cursorRectangle;
++ case Qt::ImCursorPosition:
++ return d->cursorPosition;
++ case Qt::ImSurroundingText:
++ return d->surroundingText;
++ case Qt::ImAbsolutePosition:
++ return d->absolutePosition;
++ case Qt::ImCurrentSelection:
++ return d->surroundingText.mid(qMin(d->cursorPosition, d->anchorPosition),
++ qAbs(d->anchorPosition - d->cursorPosition));
++ case Qt::ImAnchorPosition:
++ return d->anchorPosition;
++ case Qt::ImTextAfterCursor:
++ if (argument.isValid())
++ return d->surroundingText.mid(d->cursorPosition, argument.toInt());
++ return d->surroundingText.mid(d->cursorPosition);
++ case Qt::ImTextBeforeCursor:
++ if (argument.isValid())
++ return d->surroundingText.left(d->cursorPosition).right(argument.toInt());
++ return d->surroundingText.left(d->cursorPosition);
++ case Qt::ImPreferredLanguage:
++ return d->preferredLanguage;
++
++ default:
++ return QVariant();
++ }
++ }
++
++ void QWaylandQtTextInputMethod::sendKeyEvent(QKeyEvent *event)
++ {
++ Q_D(QWaylandQtTextInputMethod);
++ if (d->resource == nullptr || d->resource->handle == nullptr)
++ return;
++
++ d->send_key(d->resource->handle,
++ int(event->type()),
++ event->key(),
++ event->modifiers(),
++ event->isAutoRepeat(),
++ event->count(),
++ event->nativeScanCode(),
++ event->nativeVirtualKey(),
++ event->nativeModifiers(),
++ event->text());
++ }
++
++ void QWaylandQtTextInputMethod::sendInputMethodEvent(QInputMethodEvent *event)
++ {
++ Q_D(QWaylandQtTextInputMethod);
++ if (d->resource == nullptr || d->resource->handle == nullptr || d->compositor == nullptr)
++ return;
++
++ if (d->updatingQueries != 0) {
++ qCWarning(qLcWaylandCompositorInputMethods) << "Input method event sent while client is updating. Ignored.";
++ return;
++ }
++
++ Q_ASSERT(!d->waitingForSync);
++
++ QString oldSurroundText = d->surroundingText;
++ int oldCursorPosition = d->cursorPosition;
++ int oldAnchorPosition = d->anchorPosition;
++ int oldAbsolutePosition = d->absolutePosition;
++ QRect oldCursorRectangle = d->cursorRectangle;
++ QString oldPreferredLanguage = d->preferredLanguage;
++ Qt::InputMethodHints oldHints = d->hints;
++
++ uint serial = d->compositor->nextSerial(); // ### Not needed if we block on this?
++ d->send_start_input_method_event(d->resource->handle, serial, d->surroundingTextOffset);
++ for (const QInputMethodEvent::Attribute &attribute : event->attributes()) {
++ switch (attribute.type) {
++ case QInputMethodEvent::TextFormat:
++ {
++ auto properties = attribute.value.value<QTextFormat>().properties();
++ if (properties.size() != 2 || properties.firstKey() != QTextFormat::FontUnderline || properties.lastKey() != QTextFormat::TextUnderlineStyle) {
++ qCWarning(qLcWaylandCompositorInputMethods()) << "Only underline text formats currently supported";
++ }
++
++ d->send_input_method_event_attribute(d->resource->handle,
++ serial,
++ attribute.type,
++ attribute.start,
++ attribute.length,
++ QString());
++ break;
++ }
++ case QInputMethodEvent::Cursor:
++ d->cursorPosition = attribute.start;
++ d->send_input_method_event_attribute(d->resource->handle,
++ serial,
++ attribute.type,
++ attribute.start,
++ attribute.length,
++ attribute.value.typeId() == QMetaType::QColor ? attribute.value.value<QColor>().name() : QString());
++ break;
++ case QInputMethodEvent::Language: // ### What is the type of value? Is it string?
++ Q_FALLTHROUGH();
++ case QInputMethodEvent::Ruby:
++ d->send_input_method_event_attribute(d->resource->handle,
++ serial,
++ attribute.type,
++ attribute.start,
++ attribute.length,
++ attribute.value.toString());
++ break;
++ case QInputMethodEvent::Selection:
++ d->send_input_method_event_attribute(d->resource->handle,
++ serial,
++ attribute.type,
++ attribute.start,
++ attribute.length,
++ QString());
++ break;
++ case QInputMethodEvent::MimeData:
++ qCWarning(qLcWaylandCompositorInputMethods())
++ << "Ignoring QInputMethodEvent::MimeData";
++ continue;
++ }
++ }
++
++ d->waitingForSync = true;
++ d->send_end_input_method_event(d->resource->handle,
++ serial,
++ event->commitString(),
++ event->preeditString(),
++ event->replacementStart(),
++ event->replacementLength());
++
++ static const qint64 acknowledgeTimeoutMs = qEnvironmentVariable("QT_WAYLAND_QTTEXTINPUTMETHOD_TIMEOUT",
++ QLatin1String("1000")).toInt();
++ QElapsedTimer timer;
++ timer.start();
++ while (d->waitingForSync) {
++ if (acknowledgeTimeoutMs > 0 && timer.hasExpired(acknowledgeTimeoutMs)) {
++ qCWarning(qLcWaylandCompositorInputMethods)
++ << "Timed out waiting for acknowledge_input_method()";
++ d->waitingForSync = false;
++ break;
++ }
++ d->compositor->processWaylandEvents();
++ // We might get into a situation where the client is waiting for us to
++ // until we confirm the frame is rendered until that he cannot answer
++ // our input events.
++ QCoreApplication::processEvents();
++ }
++
++ Qt::InputMethodQueries queries;
++ if (d->surroundingText != oldSurroundText)
++ queries |= Qt::ImSurroundingText;
++ if (d->cursorPosition != oldCursorPosition)
++ queries |= Qt::ImCursorPosition;
++ if (d->anchorPosition != oldAnchorPosition)
++ queries |= Qt::ImAnchorPosition;
++ if (d->absolutePosition != oldAbsolutePosition)
++ queries |= Qt::ImAbsolutePosition;
++ if (d->cursorRectangle != oldCursorRectangle)
++ queries |= Qt::ImCursorRectangle;
++ if (d->preferredLanguage != oldPreferredLanguage)
++ queries |= Qt::ImPreferredLanguage;
++ if (d->hints != oldHints)
++ queries |= Qt::ImHints;
++ if (queries != 0)
++ emit updateInputMethod(queries);
++ }
++
++ bool QWaylandQtTextInputMethod::isSurfaceEnabled(QWaylandSurface *surface) const
++ {
++ Q_D(const QWaylandQtTextInputMethod);
++ return d->enabledSurfaces.values().contains(surface);
++ }
++
++ void QWaylandQtTextInputMethod::setFocus(QWaylandSurface *surface)
++ {
++ Q_D(QWaylandQtTextInputMethod);
++
++ QWaylandQtTextInputMethodPrivate::Resource *resource = surface != nullptr ? d->resourceMap().value(surface->waylandClient()) : nullptr;
++ if (d->resource == resource && d->focusedSurface == surface) // same client, same surface
++ return;
++
++ if (d->resource != nullptr && d->focusedSurface != nullptr) {
++ d->send_leave(d->resource->handle, d->focusedSurface->resource());
++ d->focusDestroyListener.reset();
++ }
++
++ d->resource = resource;
++ d->focusedSurface = surface;
++
++ if (d->resource != nullptr && d->focusedSurface != nullptr) {
++ d->surroundingText.clear();
++ d->cursorPosition = 0;
++ d->anchorPosition = 0;
++ d->absolutePosition = 0;
++ d->cursorRectangle = QRect();
++ d->preferredLanguage.clear();
++ d->hints = Qt::InputMethodHints();
++ d->send_enter(d->resource->handle, d->focusedSurface->resource());
++ sendInputDirectionChanged();
++ sendLocaleChanged();
++ sendInputDirectionChanged();
++ d->focusDestroyListener.listenForDestruction(surface->resource());
++ if (d->inputPanelVisible && d->enabledSurfaces.values().contains(surface))
++ qGuiApp->inputMethod()->show();
++ }
++ }
++
++ void QWaylandQtTextInputMethod::sendLocaleChanged()
++ {
++ Q_D(QWaylandQtTextInputMethod);
++ if (d->resource == nullptr || d->resource->handle == nullptr)
++ return;
++
++ d->send_locale_changed(d->resource->handle, qGuiApp->inputMethod()->locale().bcp47Name());
++ }
++
++ void QWaylandQtTextInputMethod::sendInputDirectionChanged()
++ {
++ Q_D(QWaylandQtTextInputMethod);
++ if (d->resource == nullptr || d->resource->handle == nullptr)
++ return;
++
++ d->send_input_direction_changed(d->resource->handle, int(qGuiApp->inputMethod()->inputDirection()));
++ }
++
++ void QWaylandQtTextInputMethod::sendKeyboardRectangleChanged()
++ {
++ Q_D(QWaylandQtTextInputMethod);
++ if (d->resource == nullptr || d->resource->handle == nullptr)
++ return;
++
++ QRectF keyboardRectangle = qGuiApp->inputMethod()->keyboardRectangle();
++ d->send_keyboard_rectangle_changed(d->resource->handle,
++ wl_fixed_from_double(keyboardRectangle.x()),
++ wl_fixed_from_double(keyboardRectangle.y()),
++ wl_fixed_from_double(keyboardRectangle.width()),
++ wl_fixed_from_double(keyboardRectangle.height()));
++ }
++
++ void QWaylandQtTextInputMethod::sendVisibleChanged()
++ {
++ Q_D(QWaylandQtTextInputMethod);
++ if (d->resource == nullptr || d->resource->handle == nullptr)
++ return;
++
++ d->send_visible_changed(d->resource->handle, int(qGuiApp->inputMethod()->isVisible()));
++ }
++
++ void QWaylandQtTextInputMethod::add(::wl_client *client, uint32_t id, int version)
++ {
++ Q_D(QWaylandQtTextInputMethod);
++ d->add(client, id, version);
++ }
++
++ const struct wl_interface *QWaylandQtTextInputMethod::interface()
++ {
++ return QWaylandQtTextInputMethodPrivate::interface();
++ }
++
++ QByteArray QWaylandQtTextInputMethod::interfaceName()
++ {
++ return QWaylandQtTextInputMethodPrivate::interfaceName();
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qwaylandqttextinputmethod.cpp"