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