Blame SOURCES/0019-client-Allow-QWaylandInputContext-to-accept-composed.patch

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