summaryrefslogtreecommitdiffstats
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
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>
-rw-r--r--examples/wayland/pure-qml/qml/main.qml2
-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
-rw-r--r--tests/auto/client/client.pro2
-rw-r--r--tests/auto/client/inputcontext/inputcontext.pro6
-rw-r--r--tests/auto/client/inputcontext/tst_inputcontext.cpp184
-rw-r--r--tests/auto/client/shared/shared.pri9
-rw-r--r--tests/auto/client/shared/textinput.cpp45
-rw-r--r--tests/auto/client/shared/textinput.h51
14 files changed, 378 insertions, 105 deletions
diff --git a/examples/wayland/pure-qml/qml/main.qml b/examples/wayland/pure-qml/qml/main.qml
index 69be7cf10..483de7514 100644
--- a/examples/wayland/pure-qml/qml/main.qml
+++ b/examples/wayland/pure-qml/qml/main.qml
@@ -72,6 +72,6 @@ WaylandCompositor {
onWlShellSurfaceCreated: screen.handleShellSurface(shellSurface)
}
- // Extension for Virtual keyboard support
+ // Extension for Input Method (QT_IM_MODULE) support at compositor-side
TextInputManager {}
}
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.
diff --git a/tests/auto/client/client.pro b/tests/auto/client/client.pro
index e99db20ba..051cb4e3d 100644
--- a/tests/auto/client/client.pro
+++ b/tests/auto/client/client.pro
@@ -12,3 +12,5 @@ SUBDIRS += \
xdgoutput \
xdgshell \
xdgshellv6
+
+qtConfig(im): SUBDIRS += inputcontext
diff --git a/tests/auto/client/inputcontext/inputcontext.pro b/tests/auto/client/inputcontext/inputcontext.pro
new file mode 100644
index 000000000..4419b3e77
--- /dev/null
+++ b/tests/auto/client/inputcontext/inputcontext.pro
@@ -0,0 +1,6 @@
+include (../shared/shared.pri)
+
+QT += waylandcompositor
+
+TARGET = tst_inputcontext
+SOURCES += tst_inputcontext.cpp
diff --git a/tests/auto/client/inputcontext/tst_inputcontext.cpp b/tests/auto/client/inputcontext/tst_inputcontext.cpp
new file mode 100644
index 000000000..b1a5a7f17
--- /dev/null
+++ b/tests/auto/client/inputcontext/tst_inputcontext.cpp
@@ -0,0 +1,184 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "mockcompositor.h"
+#include "textinput.h"
+
+#include <QtCore/QString>
+#include <QtCore/QByteArray>
+
+#include <QtGui/private/qguiapplication_p.h>
+#include <QtGui/qpa/qplatforminputcontext.h>
+#include <QtGui/qpa/qplatformintegration.h>
+#include <QtGui/qpa/qplatforminputcontextfactory_p.h>
+
+#include <QtTest/QtTest>
+
+using namespace MockCompositor;
+
+class tst_inputcontext : public QObject, private DefaultCompositor
+{
+ Q_OBJECT
+private slots:
+ void initTestCase();
+ void selectingInputContext_data();
+ void selectingInputContext();
+ void inputContextReconfigurationWhenTogglingTextInputExtension();
+
+private:
+ QByteArray inputContextName() const;
+ void ensureTextInputPresentOnCompositor();
+ void ensureTextInputNotPresentOnCompositor();
+
+ QByteArray mComposeModule = QByteArray("QComposeInputContext"); // default input context
+ QByteArray mIbusModule = QByteArray("QIBusPlatformInputContext");
+ QByteArray mWaylandModule = QByteArray("QtWaylandClient::QWaylandInputContext");
+
+ TextInputManager *mTextInputManager = nullptr;
+};
+
+void tst_inputcontext::initTestCase()
+{
+ // Verify that plugins are present and valid
+ QPlatformInputContext *context = QPlatformInputContextFactory::create(QStringLiteral("compose"));
+ QVERIFY(context && context->isValid());
+
+ context = QPlatformInputContextFactory::create(QStringLiteral("ibus"));
+ // The ibus plugin depends on properly configured system services, if plugin is not valid
+ // verify that wayland qpa plugin properly fallbacks to default input context.
+ if (!context || !context->isValid())
+ mIbusModule = mComposeModule;
+}
+
+QByteArray tst_inputcontext::inputContextName() const
+{
+ QPlatformIntegration *platformIntegration = QGuiApplicationPrivate::platformIntegration();
+ if (platformIntegration->inputContext())
+ return platformIntegration->inputContext()->metaObject()->className();
+
+ return QByteArray("");
+}
+
+void tst_inputcontext::ensureTextInputPresentOnCompositor()
+{
+ exec([&] {
+ QVector<TextInputManager *> extensions = getAll<TextInputManager>();
+ if (extensions.length() > 1)
+ QFAIL("TextInputManager is a singleton, hence there should not be more then one object returned");
+ if (extensions.length() == 0)
+ add<TextInputManager>();
+ });
+}
+
+void tst_inputcontext::ensureTextInputNotPresentOnCompositor()
+{
+ exec([&] {
+ QVector<TextInputManager *> extensions = getAll<TextInputManager>();
+ if (extensions.length() > 1)
+ QFAIL("TextInputManager is a singleton, hence there should not be more then one object returned");
+ if (extensions.length() == 1)
+ remove(extensions.first());
+ });
+}
+
+void tst_inputcontext::selectingInputContext_data()
+{
+ QTest::addColumn<QByteArray>("requestedModule");
+ QTest::addColumn<QByteArray>("expectedModule");
+
+ // Test compositor without Text Input extension
+ QTest::newRow("ibus") << QByteArray("ibus") << mIbusModule;
+ QTest::newRow("compose") << QByteArray("compose") << mComposeModule;
+ QTest::newRow("empty") << QByteArray("") << mComposeModule;
+ QTest::newRow("null") << QByteArray() << mComposeModule;
+ QTest::newRow("fake") << QByteArray("fake") << mComposeModule;
+
+ // Test compositor with Text Input extension
+ QTest::newRow("ibus:text-input") << QByteArray("ibus") << mIbusModule;
+ QTest::newRow("compose:text-input") << QByteArray("compose") << mComposeModule;
+ QTest::newRow("empty:text-input") << QByteArray("") << mComposeModule;
+ QTest::newRow("null:text-input") << QByteArray() << mWaylandModule;
+ QTest::newRow("fake:text-input") << QByteArray("fake") << mComposeModule;
+}
+
+void tst_inputcontext::selectingInputContext()
+{
+ QFETCH(QByteArray, requestedModule);
+ QFETCH(QByteArray, expectedModule);
+
+ if (requestedModule.isNull())
+ qunsetenv("QT_IM_MODULE");
+ else
+ qputenv("QT_IM_MODULE", requestedModule);
+
+ const bool withTextInputAtCompositorSide = QByteArray(QTest::currentDataTag()).endsWith(":text-input");
+
+ if (withTextInputAtCompositorSide)
+ ensureTextInputPresentOnCompositor();
+ else
+ ensureTextInputNotPresentOnCompositor();
+
+ int argc = 0;
+ QGuiApplication app(argc, nullptr); // loads the platform plugin
+
+ QCOMPARE(inputContextName(), expectedModule);
+}
+
+void tst_inputcontext::inputContextReconfigurationWhenTogglingTextInputExtension()
+{
+ qunsetenv("QT_IM_MODULE");
+
+ ensureTextInputPresentOnCompositor();
+ int argc = 0;
+ QGuiApplication app(argc, nullptr); // loads the platform plugin
+ QCOMPARE(inputContextName(), mWaylandModule);
+
+ // remove text input extension after the platform plugin has been loaded
+ ensureTextInputNotPresentOnCompositor();
+ // QTRY_* because we need to spin the event loop for wayland QPA plugin
+ // to handle registry_global_remove()
+ QTRY_COMPARE(inputContextName(), mComposeModule);
+
+ // add text input extension after the platform plugin has been loaded
+ ensureTextInputPresentOnCompositor();
+ // QTRY_* because we need to spin the event loop for wayland QPA plugin
+ // to handle registry_global()
+ QTRY_COMPARE(inputContextName(), mWaylandModule);
+}
+
+int main(int argc, char *argv[])
+{
+ qputenv("XDG_RUNTIME_DIR", ".");
+ qputenv("QT_QPA_PLATFORM", "wayland");
+
+ tst_inputcontext tc;
+ QTEST_SET_MAIN_SOURCE_PATH
+ return QTest::qExec(&tc, argc, argv);
+}
+
+#include "tst_inputcontext.moc"
diff --git a/tests/auto/client/shared/shared.pri b/tests/auto/client/shared/shared.pri
index 303e13046..c86183b3d 100644
--- a/tests/auto/client/shared/shared.pri
+++ b/tests/auto/client/shared/shared.pri
@@ -4,7 +4,8 @@ QMAKE_USE += wayland-server
WAYLANDSERVERSOURCES += \
$$PWD/../../../../src/3rdparty/protocol/wayland.xml \
- $$PWD/../../../../src/3rdparty/protocol/xdg-shell.xml
+ $$PWD/../../../../src/3rdparty/protocol/xdg-shell.xml \
+ $$PWD/../../../../src/3rdparty/protocol/text-input-unstable-v2.xml
INCLUDEPATH += ../shared
@@ -13,11 +14,13 @@ HEADERS += \
$$PWD/coreprotocol.h \
$$PWD/datadevice.h \
$$PWD/mockcompositor.h \
- $$PWD/xdgshell.h
+ $$PWD/xdgshell.h \
+ $$PWD/textinput.h
SOURCES += \
$$PWD/corecompositor.cpp \
$$PWD/coreprotocol.cpp \
$$PWD/datadevice.cpp \
$$PWD/mockcompositor.cpp \
- $$PWD/xdgshell.cpp
+ $$PWD/xdgshell.cpp \
+ $$PWD/textinput.cpp
diff --git a/tests/auto/client/shared/textinput.cpp b/tests/auto/client/shared/textinput.cpp
new file mode 100644
index 000000000..f9fd287bb
--- /dev/null
+++ b/tests/auto/client/shared/textinput.cpp
@@ -0,0 +1,45 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "textinput.h"
+
+namespace MockCompositor {
+
+TextInputManager::TextInputManager(CoreCompositor *compositor)
+{
+ init(compositor->m_display, 1);
+}
+
+void TextInputManager::zwp_text_input_manager_v2_get_text_input(Resource *resource, uint32_t id, wl_resource *seatResource)
+{
+ Q_UNUSED(resource);
+ Q_UNUSED(id);
+ Q_UNUSED(seatResource);
+}
+
+} // namespace MockCompositor
diff --git a/tests/auto/client/shared/textinput.h b/tests/auto/client/shared/textinput.h
new file mode 100644
index 000000000..85072e74b
--- /dev/null
+++ b/tests/auto/client/shared/textinput.h
@@ -0,0 +1,51 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef MOCKCOMPOSITOR_TEXTINPUT_H
+#define MOCKCOMPOSITOR_TEXTINPUT_H
+
+#include "coreprotocol.h"
+#include <qwayland-server-text-input-unstable-v2.h>
+
+#include <QtGui/qpa/qplatformnativeinterface.h>
+
+namespace MockCompositor {
+
+class TextInputManager : public Global, public QtWaylandServer::zwp_text_input_manager_v2
+{
+ Q_OBJECT
+public:
+ TextInputManager(CoreCompositor *compositor);
+
+protected:
+ void zwp_text_input_manager_v2_get_text_input(Resource *resource, uint32_t id, struct ::wl_resource *seatResource) override;
+};
+
+} // namespace MockCompositor
+
+#endif // MOCKCOMPOSITOR_TEXTINPUT_H