258 lines
9.0 KiB
Diff
258 lines
9.0 KiB
Diff
From 91c48320633e493b4cd519e5d73b836a878b2b77 Mon Sep 17 00:00:00 2001
|
|
From: Aleix Pol <aleixpol@kde.org>
|
|
Date: Wed, 10 Mar 2021 01:09:13 +0100
|
|
Subject: [PATCH 19/36] 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 3aedd01271dc4f4a13103d632df224971ab2b6df had
|
|
with 57c4af2b18c0fb1d266b245a107fa6cb876b9d9e, 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 <davidedmundson@kde.org>
|
|
---
|
|
src/client/qwaylanddisplay_p.h | 3 -
|
|
src/client/qwaylandinputcontext.cpp | 95 ++++++++++++++++++++++++++++-
|
|
src/client/qwaylandinputcontext_p.h | 21 +++++++
|
|
src/client/qwaylandinputdevice.cpp | 2 +-
|
|
src/client/qwaylandintegration.cpp | 8 +--
|
|
5 files changed, 119 insertions(+), 10 deletions(-)
|
|
|
|
diff --git a/src/client/qwaylanddisplay_p.h b/src/client/qwaylanddisplay_p.h
|
|
index 188e9131..3b092bc8 100644
|
|
--- a/src/client/qwaylanddisplay_p.h
|
|
+++ b/src/client/qwaylanddisplay_p.h
|
|
@@ -175,8 +175,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;
|
|
@@ -282,7 +280,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/client/qwaylandinputcontext.cpp b/src/client/qwaylandinputcontext.cpp
|
|
index e9afe05e..ef5aa375 100644
|
|
--- a/src/client/qwaylandinputcontext.cpp
|
|
+++ b/src/client/qwaylandinputcontext.cpp
|
|
@@ -406,6 +406,8 @@ bool QWaylandInputContext::isValid() const
|
|
void QWaylandInputContext::reset()
|
|
{
|
|
qCDebug(qLcQpaInputMethods) << Q_FUNC_INFO;
|
|
+ if (m_composeState)
|
|
+ xkb_compose_state_reset(m_composeState);
|
|
|
|
QPlatformInputContext::reset();
|
|
|
|
@@ -526,9 +528,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;
|
|
@@ -561,6 +568,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<const QKeyEvent *>(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<char, 32> 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/client/qwaylandinputcontext_p.h b/src/client/qwaylandinputcontext_p.h
|
|
index 10132dfe..50db6344 100644
|
|
--- a/src/client/qwaylandinputcontext_p.h
|
|
+++ b/src/client/qwaylandinputcontext_p.h
|
|
@@ -61,6 +61,10 @@
|
|
|
|
#include <QtWaylandClient/private/qwayland-text-input-unstable-v2.h>
|
|
#include <qwaylandinputmethodeventbuilder_p.h>
|
|
+#include <qtwaylandclientglobal_p.h>
|
|
+#if QT_CONFIG(xkbcommon)
|
|
+#include <xkbcommon/xkbcommon-compose.h>
|
|
+#endif
|
|
|
|
struct wl_callback;
|
|
struct wl_callback_listener;
|
|
@@ -155,11 +159,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<QWindow> 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/client/qwaylandinputdevice.cpp b/src/client/qwaylandinputdevice.cpp
|
|
index ed4a0eb4..ae045f4f 100644
|
|
--- a/src/client/qwaylandinputdevice.cpp
|
|
+++ b/src/client/qwaylandinputdevice.cpp
|
|
@@ -1201,7 +1201,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/client/qwaylandintegration.cpp b/src/client/qwaylandintegration.cpp
|
|
index 7ad8e05e..c53ccb78 100644
|
|
--- a/src/client/qwaylandintegration.cpp
|
|
+++ b/src/client/qwaylandintegration.cpp
|
|
@@ -474,13 +474,11 @@ void QWaylandIntegration::reconfigureInputContext()
|
|
|
|
#if QT_CONFIG(xkbcommon)
|
|
QXkbCommon::setXkbContext(mInputContext.data(), mDisplay->xkbContext());
|
|
+ if (QWaylandInputContext* waylandInput = qobject_cast<QWaylandInputContext*>(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<QWaylandInputContext *>(mInputContext.data());
|
|
-
|
|
qCDebug(lcQpaWayland) << "using input method:" << inputContext()->metaObject()->className();
|
|
}
|
|
|
|
--
|
|
2.33.1
|
|
|