summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorGatis Paeglis <gatis.paeglis@qt.io>2018-12-13 12:14:59 +0100
committerGatis Paeglis <gatis.paeglis@qt.io>2019-04-15 14:10:28 +0000
commit3aedd01271dc4f4a13103d632df224971ab2b6df (patch)
treec4e5b95e00fb1051ae26d151adfe80f9de19092d /src
parent1b773df93f5bccb6b616d2a228cb15cffe8e32d5 (diff)
client: rework input method handling
The existing solution was parsing compose tables on startup, it is better to lazy initialize the compose table/state on a first key press, instead of doing it on an application startup. This logic is inside of the compose input plugin. The existing code did not utilize correctly how Qt handles complex text input. It used libxkbcommon-compose APIs to compose user input and then passed the same input again to QPlatformInputContext (from QWaylandInputDevice::Keyboard::sendKey), which was erroneous. This also means that code was forcing "xkb compose", and did not respect QT_IM_MODULE at client-side. From commit that added compose key handling (57c4af2b18c0fb1d266b245a107fa6cb876b9d9e): "We should expand on it in the future to handle things like resetting the compose state on text field switching". This is now handled by properly utilizing Qt IM framework. Converted QWaylandInputDevice::Keyboard::sendKey into a class member function to avoid adding one more arg (mXkbContext) to the already long argument list. That whole function should be simplified, but that is out-of-scope for this patch. The reworked code uses qxkbcommon support library to reduce code duplication between platforms and to unify behavior. Some users might mistakenly think that this patch introduces a regression with Qt on KDE, but it is actually a KWin/Wayland compositor bug: https://bugs.kde.org/show_bug.cgi?id=405388 The work around on KDE is to use QT_IM_MODULE at client-side to select input method, as KWin compositor over the wire supports only the qtvirtualkeyboard module. Setting this envvar is not someting out of the ordinary for users on Linux. Input method handling at compositor-side is new feature and clearly not very well supported yet. Task-number: QTBUG-65503 Change-Id: Ie511d950396fa2fb6cbe6672996cee9791f3ab11 Reviewed-by: Johan Helsing <johan.helsing@qt.io>
Diffstat (limited to 'src')
-rw-r--r--src/client/client.pro5
-rw-r--r--src/client/qwaylanddisplay.cpp12
-rw-r--r--src/client/qwaylanddisplay_p.h8
-rw-r--r--src/client/qwaylandinputdevice.cpp85
-rw-r--r--src/client/qwaylandinputdevice_p.h16
-rw-r--r--src/client/qwaylandintegration.cpp56
-rw-r--r--src/client/qwaylandintegration_p.h2
7 files changed, 83 insertions, 101 deletions
diff --git a/src/client/client.pro b/src/client/client.pro
index 38d0ac3e1..9f7d979dc 100644
--- a/src/client/client.pro
+++ b/src/client/client.pro
@@ -15,8 +15,9 @@ use_gold_linker: CONFIG += no_linker_version_script
CONFIG -= precompile_header
CONFIG += link_pkgconfig wayland-scanner
-qtConfig(xkbcommon): \
- QMAKE_USE_PRIVATE += xkbcommon
+qtConfig(xkbcommon) {
+ QT_PRIVATE += xkbcommon_support-private
+}
qtHaveModule(linuxaccessibility_support_private): \
QT += linuxaccessibility_support_private
diff --git a/src/client/qwaylanddisplay.cpp b/src/client/qwaylanddisplay.cpp
index 5b1b9bffb..22a79124d 100644
--- a/src/client/qwaylanddisplay.cpp
+++ b/src/client/qwaylanddisplay.cpp
@@ -266,11 +266,11 @@ void QWaylandDisplay::registry_global(uint32_t id, const QString &interface, uin
mTouchExtension.reset(new QWaylandTouchExtension(this, id));
} else if (interface == QStringLiteral("zqt_key_v1")) {
mQtKeyExtension.reset(new QWaylandQtKeyExtension(this, id));
- } else if (interface == QStringLiteral("zwp_text_input_manager_v2")) {
+ } else if (interface == QStringLiteral("zwp_text_input_manager_v2") && !mClientSideInputContextRequested) {
mTextInputManager.reset(new QtWayland::zwp_text_input_manager_v2(registry, id, 1));
- foreach (QWaylandInputDevice *inputDevice, mInputDevices) {
+ for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices))
inputDevice->setTextInput(new QWaylandTextInput(this, mTextInputManager->get_text_input(inputDevice->wl_seat())));
- }
+ mWaylandIntegration->reconfigureInputContext();
} else if (interface == QStringLiteral("qt_hardware_integration")) {
bool disableHardwareIntegration = qEnvironmentVariableIntValue("QT_WAYLAND_DISABLE_HW_INTEGRATION");
if (!disableHardwareIntegration) {
@@ -306,6 +306,12 @@ void QWaylandDisplay::registry_global_remove(uint32_t id)
}
}
}
+ if (global.interface == QStringLiteral("zwp_text_input_manager_v2")) {
+ mTextInputManager.reset();
+ for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices))
+ inputDevice->setTextInput(nullptr);
+ mWaylandIntegration->reconfigureInputContext();
+ }
mGlobals.removeAt(i);
break;
}
diff --git a/src/client/qwaylanddisplay_p.h b/src/client/qwaylanddisplay_p.h
index 4a98b935b..836ee0f9a 100644
--- a/src/client/qwaylanddisplay_p.h
+++ b/src/client/qwaylanddisplay_p.h
@@ -63,6 +63,8 @@
#include <QtWaylandClient/private/qtwaylandclientglobal_p.h>
#include <QtWaylandClient/private/qwaylandshm_p.h>
+#include <qpa/qplatforminputcontextfactory_p.h>
+
struct wl_cursor_image;
QT_BEGIN_NAMESPACE
@@ -144,6 +146,7 @@ public:
QWaylandHardwareIntegration *hardwareIntegration() const { return mHardwareIntegration.data(); }
QtWayland::zxdg_output_manager_v1 *xdgOutputManager() const { return mXdgOutputManager.data(); }
+ bool usingInputContextFromCompositor() const { return mUsingInputContextFromCompositor; }
struct RegistryGlobal {
uint32_t id;
@@ -237,8 +240,13 @@ private:
struct wl_callback *mSyncCallback = nullptr;
static const wl_callback_listener syncCallbackListener;
+ 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;
+
+ friend class QWaylandIntegration;
};
}
diff --git a/src/client/qwaylandinputdevice.cpp b/src/client/qwaylandinputdevice.cpp
index 2ae2caca2..f31ab2745 100644
--- a/src/client/qwaylandinputdevice.cpp
+++ b/src/client/qwaylandinputdevice.cpp
@@ -71,7 +71,7 @@
#include <QtGui/QGuiApplication>
#if QT_CONFIG(xkbcommon)
-#include <xkbcommon/xkbcommon-compose.h>
+#include <xkbcommon/xkbcommon.h>
#endif
QT_BEGIN_NAMESPACE
@@ -110,7 +110,7 @@ bool QWaylandInputDevice::Keyboard::createDefaultKeyMap()
qWarning() << "xkb_map_new_from_names failed, no key input";
return false;
}
- createComposeState();
+
return true;
}
@@ -123,41 +123,11 @@ void QWaylandInputDevice::Keyboard::releaseKeyMap()
if (mXkbContext)
xkb_context_unref(mXkbContext);
}
-
-void QWaylandInputDevice::Keyboard::createComposeState()
-{
- static const char *locale = nullptr;
- if (!locale) {
- locale = getenv("LC_ALL");
- if (!locale)
- locale = getenv("LC_CTYPE");
- if (!locale)
- locale = getenv("LANG");
- if (!locale)
- locale = "C";
- }
-
- mXkbComposeTable = xkb_compose_table_new_from_locale(mXkbContext, locale, XKB_COMPOSE_COMPILE_NO_FLAGS);
- if (mXkbComposeTable)
- mXkbComposeState = xkb_compose_state_new(mXkbComposeTable, XKB_COMPOSE_STATE_NO_FLAGS);
-}
-
-void QWaylandInputDevice::Keyboard::releaseComposeState()
-{
- if (mXkbComposeState)
- xkb_compose_state_unref(mXkbComposeState);
- if (mXkbComposeTable)
- xkb_compose_table_unref(mXkbComposeTable);
- mXkbComposeState = nullptr;
- mXkbComposeTable = nullptr;
-}
-
#endif
QWaylandInputDevice::Keyboard::~Keyboard()
{
#if QT_CONFIG(xkbcommon)
- releaseComposeState();
releaseKeyMap();
#endif
if (mFocus)
@@ -396,9 +366,9 @@ QWaylandInputDevice::QWaylandInputDevice(QWaylandDisplay *display, int version,
}
#endif
- if (mQDisplay->textInputManager()) {
- mTextInput = new QWaylandTextInput(mQDisplay, mQDisplay->textInputManager()->get_text_input(wl_seat()));
- }
+ if (mQDisplay->textInputManager())
+ mTextInput.reset(new QWaylandTextInput(mQDisplay, mQDisplay->textInputManager()->get_text_input(wl_seat())));
+
}
QWaylandInputDevice::~QWaylandInputDevice()
@@ -481,12 +451,12 @@ QWaylandDataDevice *QWaylandInputDevice::dataDevice() const
void QWaylandInputDevice::setTextInput(QWaylandTextInput *textInput)
{
- mTextInput = textInput;
+ mTextInput.reset(textInput);
}
QWaylandTextInput *QWaylandInputDevice::textInput() const
{
- return mTextInput;
+ return mTextInput.data();
}
void QWaylandInputDevice::removeMouseButtonFromState(Qt::MouseButton button)
@@ -793,7 +763,6 @@ void QWaylandInputDevice::Keyboard::keyboard_keymap(uint32_t format, int32_t fd,
// Release the old keymap resources in the case they were already created in
// the key event or when the compositor issues a new map
- releaseComposeState();
releaseKeyMap();
mXkbContext = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
@@ -802,8 +771,6 @@ void QWaylandInputDevice::Keyboard::keyboard_keymap(uint32_t format, int32_t fd,
close(fd);
mXkbState = xkb_state_new(mXkbMap);
- createComposeState();
-
#else
Q_UNUSED(format);
Q_UNUSED(fd);
@@ -852,16 +819,17 @@ void QWaylandInputDevice::Keyboard::keyboard_leave(uint32_t time, struct wl_surf
handleFocusLost();
}
-static void sendKey(QWindow *tlw, ulong timestamp, QEvent::Type type, int key, Qt::KeyboardModifiers modifiers,
- quint32 nativeScanCode, quint32 nativeVirtualKey, quint32 nativeModifiers,
- const QString& text = QString(), bool autorep = false, ushort count = 1)
+void QWaylandInputDevice::Keyboard::sendKey(QWindow *tlw, ulong timestamp, QEvent::Type type, int key,
+ Qt::KeyboardModifiers modifiers, quint32 nativeScanCode,
+ quint32 nativeVirtualKey, quint32 nativeModifiers,
+ const QString& text, bool autorep, ushort count)
{
QPlatformInputContext *inputContext = QGuiApplicationPrivate::platformIntegration()->inputContext();
bool filtered = false;
- if (inputContext) {
- QKeyEvent event(type, key, modifiers, nativeScanCode, nativeVirtualKey, nativeModifiers,
- text, autorep, count);
+ if (inputContext && !mParent->mQDisplay->usingInputContextFromCompositor()) {
+ QKeyEvent event(type, key, modifiers, nativeScanCode, nativeVirtualKey,
+ nativeModifiers, text, autorep, count);
event.setTimestamp(timestamp);
filtered = inputContext->filterEvent(&event);
}
@@ -896,37 +864,12 @@ void QWaylandInputDevice::Keyboard::keyboard_key(uint32_t serial, uint32_t time,
return;
}
- QString composedText;
xkb_keysym_t sym = xkb_state_key_get_one_sym(mXkbState, code);
- if (mXkbComposeState) {
- if (isDown)
- xkb_compose_state_feed(mXkbComposeState, sym);
- xkb_compose_status status = xkb_compose_state_get_status(mXkbComposeState);
-
- switch (status) {
- case XKB_COMPOSE_COMPOSED: {
- int size = xkb_compose_state_get_utf8(mXkbComposeState, nullptr, 0);
- QVarLengthArray<char, 32> buffer(size + 1);
- xkb_compose_state_get_utf8(mXkbComposeState, buffer.data(), buffer.size());
- composedText = QString::fromUtf8(buffer.constData());
- sym = xkb_compose_state_get_one_sym(mXkbComposeState);
- xkb_compose_state_reset(mXkbComposeState);
- } break;
- case XKB_COMPOSE_COMPOSING:
- case XKB_COMPOSE_CANCELLED:
- return;
- case XKB_COMPOSE_NOTHING:
- break;
- }
- }
Qt::KeyboardModifiers modifiers = mParent->modifiers();
std::tie(qtkey, text) = QWaylandXkb::keysymToQtKey(sym, modifiers);
- if (!composedText.isNull())
- text = composedText;
-
sendKey(window->window(), time, type, qtkey, modifiers, code, sym, mNativeModifiers, text);
#else
// Generic fallback for single hard keys: Assume 'key' is a Qt key code.
diff --git a/src/client/qwaylandinputdevice_p.h b/src/client/qwaylandinputdevice_p.h
index 50b1af385..4149e5005 100644
--- a/src/client/qwaylandinputdevice_p.h
+++ b/src/client/qwaylandinputdevice_p.h
@@ -54,6 +54,7 @@
#include <QtWaylandClient/private/qtwaylandclientglobal_p.h>
#include <QtWaylandClient/private/qwaylandwindow_p.h>
+#include <QtCore/QScopedPointer>
#include <QSocketNotifier>
#include <QObject>
#include <QTimer>
@@ -75,11 +76,6 @@
struct wl_cursor_image;
#endif
-#if QT_CONFIG(xkbcommon)
-struct xkb_compose_state;
-struct xkb_compose_table;
-#endif
-
QT_BEGIN_NAMESPACE
namespace QtWaylandClient {
@@ -164,7 +160,7 @@ private:
Pointer *mPointer = nullptr;
Touch *mTouch = nullptr;
- QWaylandTextInput *mTextInput = nullptr;
+ QScopedPointer<QWaylandTextInput> mTextInput;
uint32_t mTime = 0;
uint32_t mSerial = 0;
@@ -217,8 +213,6 @@ public:
xkb_context *mXkbContext = nullptr;
xkb_keymap *mXkbMap = nullptr;
xkb_state *mXkbState = nullptr;
- xkb_compose_table *mXkbComposeTable = nullptr;
- xkb_compose_state *mXkbComposeState = nullptr;
#endif
uint32_t mNativeModifiers = 0;
@@ -244,10 +238,10 @@ private:
#if QT_CONFIG(xkbcommon)
bool createDefaultKeyMap();
void releaseKeyMap();
- void createComposeState();
- void releaseComposeState();
#endif
-
+ void sendKey(QWindow *tlw, ulong timestamp, QEvent::Type type, int key, Qt::KeyboardModifiers modifiers,
+ quint32 nativeScanCode, quint32 nativeVirtualKey, quint32 nativeModifiers,
+ const QString& text = QString(), bool autorep = false, ushort count = 1);
};
class Q_WAYLAND_CLIENT_EXPORT QWaylandInputDevice::Pointer : public QObject, public QtWayland::wl_pointer
diff --git a/src/client/qwaylandintegration.cpp b/src/client/qwaylandintegration.cpp
index 45957629f..8bfe3b6fc 100644
--- a/src/client/qwaylandintegration.cpp
+++ b/src/client/qwaylandintegration.cpp
@@ -90,6 +90,10 @@
#include <QtLinuxAccessibilitySupport/private/bridge_p.h>
#endif
+#if QT_CONFIG(xkbcommon)
+#include <QtXkbCommonSupport/private/qxkbcommon_p.h>
+#endif
+
QT_BEGIN_NAMESPACE
namespace QtWaylandClient {
@@ -146,20 +150,8 @@ QWaylandIntegration::QWaylandIntegration()
#if QT_CONFIG(draganddrop)
mDrag.reset(new QWaylandDrag(mDisplay.data()));
#endif
- QString icStr = QPlatformInputContextFactory::requested();
- if (!icStr.isNull()) {
- mInputContext.reset(QPlatformInputContextFactory::create(icStr));
- } else {
- //try to use the input context using the wl_text_input interface
- QPlatformInputContext *ctx = new QWaylandInputContext(mDisplay.data());
- mInputContext.reset(ctx);
-
- //use the traditional way for on screen keyboards for now
- if (!mInputContext.data()->isValid()) {
- ctx = QPlatformInputContextFactory::create();
- mInputContext.reset(ctx);
- }
- }
+
+ reconfigureInputContext();
}
QWaylandIntegration::~QWaylandIntegration()
@@ -462,6 +454,42 @@ void QWaylandIntegration::initializeInputDeviceIntegration()
}
}
+void QWaylandIntegration::reconfigureInputContext()
+{
+ if (!mDisplay) {
+ // This function can be called from QWaylandDisplay::registry_global() when we
+ // are in process of constructing QWaylandDisplay. Configuring input context
+ // in that case is done by calling reconfigureInputContext() from QWaylandIntegration
+ // constructor, after QWaylandDisplay has been constructed.
+ return;
+ }
+
+ const QString &requested = QPlatformInputContextFactory::requested();
+ if (requested == QLatin1String("qtvirtualkeyboard"))
+ qCWarning(lcQpaWayland) << "qtvirtualkeyboard currently is not supported at client-side,"
+ " use QT_IM_MODULE=qtvirtualkeyboard at compositor-side.";
+
+ if (requested.isNull())
+ mInputContext.reset(new QWaylandInputContext(mDisplay.data()));
+ else
+ mInputContext.reset(QPlatformInputContextFactory::create(requested));
+
+ const QString defaultInputContext(QStringLiteral("compose"));
+ if ((!mInputContext || !mInputContext->isValid()) && requested != defaultInputContext)
+ mInputContext.reset(QPlatformInputContextFactory::create(defaultInputContext));
+
+#if QT_CONFIG(xkbcommon)
+ QXkbCommon::setXkbContext(mInputContext.data(), xkb_context_new(XKB_CONTEXT_NO_FLAGS));
+#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();
+}
+
QWaylandShellIntegration *QWaylandIntegration::createShellIntegration(const QString &integrationName)
{
if (QWaylandShellIntegrationFactory::keys().contains(integrationName)) {
diff --git a/src/client/qwaylandintegration_p.h b/src/client/qwaylandintegration_p.h
index 944f635bb..5e6f16d09 100644
--- a/src/client/qwaylandintegration_p.h
+++ b/src/client/qwaylandintegration_p.h
@@ -116,6 +116,8 @@ public:
virtual QWaylandServerBufferIntegration *serverBufferIntegration() const;
virtual QWaylandShellIntegration *shellIntegration() const;
+ void reconfigureInputContext();
+
private:
// NOTE: mDisplay *must* be destructed after mDrag and mClientBufferIntegration
// and mShellIntegration.