From 98c921c0e2250ce85ab3de05ea58e98587102ae3 Mon Sep 17 00:00:00 2001 From: Aleix Pol Date: Wed, 10 Mar 2021 01:09:13 +0100 Subject: [PATCH] client: Allow QWaylandInputContext to accept composed key combinations At the moment, we are forcing user to choose to either compose or use the text-input channel. This patch brings some of the QComposeInputContext functionality in order to let applications understand dead key combinations like they are supposed to. Having it in QWaylandInputContext rather than in QWaylandInputDevice should solve the problems 9a782df3e7f6f937a1bae3fa5e964a29fbe5980b had with dd13698a730594d9f40baa367dcfe05bc23f1016, because we are doing it in the input context rather than before. This way, if the user is overriding the input method (e.g. by setting QT_IM_MODULE), all the key strokes will still be properly forwarded to the module to use. This in turn allows us to solve https://bugs.kde.org/show_bug.cgi?id=411729 and https://bugs.kde.org/show_bug.cgi?id=405388 since we don't need to choose anymore between physical and virual keyboards anymore. Pick-to: 5.15 Change-Id: I8601f5d7ae21edf4b3a1191fa75877286e505588 Reviewed-by: David Edmundson --- .../platforms/wayland/qwaylanddisplay_p.h | 3 - .../wayland/qwaylandinputcontext.cpp | 95 ++++++++++++++++++- .../wayland/qwaylandinputcontext_p.h | 21 ++++ .../platforms/wayland/qwaylandinputdevice.cpp | 2 +- .../platforms/wayland/qwaylandintegration.cpp | 8 +- 5 files changed, 119 insertions(+), 10 deletions(-) diff --git a/src/plugins/platforms/wayland/qwaylanddisplay_p.h b/src/plugins/platforms/wayland/qwaylanddisplay_p.h index 6443cff2b85..91715a14458 100644 --- a/src/plugins/platforms/wayland/qwaylanddisplay_p.h +++ b/src/plugins/platforms/wayland/qwaylanddisplay_p.h @@ -178,8 +178,6 @@ public: QWaylandHardwareIntegration *hardwareIntegration() const { return mHardwareIntegration.data(); } QWaylandXdgOutputManagerV1 *xdgOutputManager() const { return mXdgOutputManager.data(); } - bool usingInputContextFromCompositor() const { return mUsingInputContextFromCompositor; } - struct RegistryGlobal { uint32_t id; QString interface; @@ -299,7 +297,6 @@ private: QReadWriteLock m_frameQueueLock; bool mClientSideInputContextRequested = !QPlatformInputContextFactory::requested().isNull(); - bool mUsingInputContextFromCompositor = false; void registry_global(uint32_t id, const QString &interface, uint32_t version) override; void registry_global_remove(uint32_t id) override; diff --git a/src/plugins/platforms/wayland/qwaylandinputcontext.cpp b/src/plugins/platforms/wayland/qwaylandinputcontext.cpp index 6c586960ea9..16e03ea170c 100644 --- a/src/plugins/platforms/wayland/qwaylandinputcontext.cpp +++ b/src/plugins/platforms/wayland/qwaylandinputcontext.cpp @@ -408,6 +408,8 @@ bool QWaylandInputContext::isValid() const void QWaylandInputContext::reset() { qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO; + if (m_composeState) + xkb_compose_state_reset(m_composeState); QPlatformInputContext::reset(); @@ -528,9 +530,14 @@ Qt::LayoutDirection QWaylandInputContext::inputDirection() const return textInput()->inputDirection(); } -void QWaylandInputContext::setFocusObject(QObject *) +void QWaylandInputContext::setFocusObject(QObject *object) { qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO; +#if QT_CONFIG(xkbcommon) + m_focusObject = object; +#else + Q_UNUSED(object); +#endif if (!textInput()) return; @@ -563,6 +570,92 @@ QWaylandTextInput *QWaylandInputContext::textInput() const return mDisplay->defaultInputDevice()->textInput(); } +#if QT_CONFIG(xkbcommon) + +void QWaylandInputContext::ensureInitialized() +{ + if (m_initialized) + return; + + if (!m_XkbContext) { + qCWarning(qLcQpaInputMethods) << "error: xkb context has not been set on" << metaObject()->className(); + return; + } + + m_initialized = true; + const char *locale = setlocale(LC_CTYPE, ""); + if (!locale) + locale = setlocale(LC_CTYPE, nullptr); + qCDebug(qLcQpaInputMethods) << "detected locale (LC_CTYPE):" << locale; + + m_composeTable = xkb_compose_table_new_from_locale(m_XkbContext, locale, XKB_COMPOSE_COMPILE_NO_FLAGS); + if (m_composeTable) + m_composeState = xkb_compose_state_new(m_composeTable, XKB_COMPOSE_STATE_NO_FLAGS); + + if (!m_composeTable) { + qCWarning(qLcQpaInputMethods, "failed to create compose table"); + return; + } + if (!m_composeState) { + qCWarning(qLcQpaInputMethods, "failed to create compose state"); + return; + } +} + +bool QWaylandInputContext::filterEvent(const QEvent *event) +{ + auto keyEvent = static_cast(event); + if (keyEvent->type() != QEvent::KeyPress) + return false; + + if (!inputMethodAccepted()) + return false; + + // lazy initialization - we don't want to do this on an app startup + ensureInitialized(); + + if (!m_composeTable || !m_composeState) + return false; + + xkb_compose_state_feed(m_composeState, keyEvent->nativeVirtualKey()); + + switch (xkb_compose_state_get_status(m_composeState)) { + case XKB_COMPOSE_COMPOSING: + return true; + case XKB_COMPOSE_CANCELLED: + reset(); + return false; + case XKB_COMPOSE_COMPOSED: + { + const int size = xkb_compose_state_get_utf8(m_composeState, nullptr, 0); + QVarLengthArray buffer(size + 1); + xkb_compose_state_get_utf8(m_composeState, buffer.data(), buffer.size()); + QString composedText = QString::fromUtf8(buffer.constData()); + + QInputMethodEvent event; + event.setCommitString(composedText); + + if (!m_focusObject && qApp) + m_focusObject = qApp->focusObject(); + + if (m_focusObject) + QCoreApplication::sendEvent(m_focusObject, &event); + else + qCWarning(qLcQpaInputMethods, "no focus object"); + + reset(); + return true; + } + case XKB_COMPOSE_NOTHING: + return false; + default: + Q_UNREACHABLE(); + return false; + } +} + +#endif + } QT_END_NAMESPACE diff --git a/src/plugins/platforms/wayland/qwaylandinputcontext_p.h b/src/plugins/platforms/wayland/qwaylandinputcontext_p.h index df91856c460..912c68ad400 100644 --- a/src/plugins/platforms/wayland/qwaylandinputcontext_p.h +++ b/src/plugins/platforms/wayland/qwaylandinputcontext_p.h @@ -62,6 +62,10 @@ #include #include +#include +#if QT_CONFIG(xkbcommon) +#include +#endif struct wl_callback; struct wl_callback_listener; @@ -156,11 +160,28 @@ public: void setFocusObject(QObject *object) override; +#if QT_CONFIG(xkbcommon) + bool filterEvent(const QEvent *event) override; + + // This invokable is called from QXkbCommon::setXkbContext(). + Q_INVOKABLE void setXkbContext(struct xkb_context *context) { m_XkbContext = context; } +#endif + private: QWaylandTextInput *textInput() const; QWaylandDisplay *mDisplay = nullptr; QPointer mCurrentWindow; + +#if QT_CONFIG(xkbcommon) + void ensureInitialized(); + + bool m_initialized = false; + QObject *m_focusObject = nullptr; + xkb_compose_table *m_composeTable = nullptr; + xkb_compose_state *m_composeState = nullptr; + struct xkb_context *m_XkbContext = nullptr; +#endif }; } diff --git a/src/plugins/platforms/wayland/qwaylandinputdevice.cpp b/src/plugins/platforms/wayland/qwaylandinputdevice.cpp index 420194733b9..4a046a134ee 100644 --- a/src/plugins/platforms/wayland/qwaylandinputdevice.cpp +++ b/src/plugins/platforms/wayland/qwaylandinputdevice.cpp @@ -1213,7 +1213,7 @@ void QWaylandInputDevice::Keyboard::handleKey(ulong timestamp, QEvent::Type type QPlatformInputContext *inputContext = QGuiApplicationPrivate::platformIntegration()->inputContext(); bool filtered = false; - if (inputContext && !mParent->mQDisplay->usingInputContextFromCompositor()) { + if (inputContext) { QKeyEvent event(type, key, modifiers, nativeScanCode, nativeVirtualKey, nativeModifiers, text, autorepeat, count); event.setTimestamp(timestamp); diff --git a/src/plugins/platforms/wayland/qwaylandintegration.cpp b/src/plugins/platforms/wayland/qwaylandintegration.cpp index 7e7567aa84f..09de7dddd7f 100644 --- a/src/plugins/platforms/wayland/qwaylandintegration.cpp +++ b/src/plugins/platforms/wayland/qwaylandintegration.cpp @@ -500,13 +500,11 @@ void QWaylandIntegration::reconfigureInputContext() #if QT_CONFIG(xkbcommon) QXkbCommon::setXkbContext(mInputContext.data(), mDisplay->xkbContext()); + if (QWaylandInputContext* waylandInput = qobject_cast(mInputContext.get())) { + waylandInput->setXkbContext(mDisplay->xkbContext()); + } #endif - // Even if compositor-side input context handling has been requested, we fallback to - // client-side handling if compositor does not provide the text-input extension. This - // is why we need to check here which input context actually is being used. - mDisplay->mUsingInputContextFromCompositor = qobject_cast(mInputContext.data()); - qCDebug(lcQpaWayland) << "using input method:" << inputContext()->metaObject()->className(); }