Source code

Revision control

Copy as Markdown

// 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"