File indexing completed on 2024-05-05 17:35:47

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2020 Marco Martin <mart@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "kwin_wayland_test.h"
0010 
0011 #include "core/output.h"
0012 #include "core/outputbackend.h"
0013 #include "cursor.h"
0014 #include "deleted.h"
0015 #include "effects.h"
0016 #include "inputmethod.h"
0017 #include "keyboard_input.h"
0018 #include "qwayland-input-method-unstable-v1.h"
0019 #include "qwayland-text-input-unstable-v3.h"
0020 #include "virtualkeyboard_dbus.h"
0021 #include "wayland/clientconnection.h"
0022 #include "wayland/display.h"
0023 #include "wayland/seat_interface.h"
0024 #include "wayland/surface_interface.h"
0025 #include "wayland_server.h"
0026 #include "window.h"
0027 #include "workspace.h"
0028 #include "xkb.h"
0029 
0030 #include <QDBusConnection>
0031 #include <QDBusMessage>
0032 #include <QDBusPendingReply>
0033 #include <QSignalSpy>
0034 #include <QTest>
0035 
0036 #include <KWayland/Client/compositor.h>
0037 #include <KWayland/Client/keyboard.h>
0038 #include <KWayland/Client/output.h>
0039 #include <KWayland/Client/region.h>
0040 #include <KWayland/Client/seat.h>
0041 #include <KWayland/Client/surface.h>
0042 #include <KWayland/Client/textinput.h>
0043 #include <linux/input-event-codes.h>
0044 
0045 using namespace KWin;
0046 using KWin::VirtualKeyboardDBus;
0047 
0048 static const QString s_socketName = QStringLiteral("wayland_test_kwin_inputmethod-0");
0049 
0050 class InputMethodTest : public QObject
0051 {
0052     Q_OBJECT
0053 private Q_SLOTS:
0054     void initTestCase();
0055     void init();
0056     void cleanup();
0057 
0058     void testOpenClose();
0059     void testEnableDisableV3();
0060     void testEnableActive();
0061     void testHidePanel();
0062     void testSwitchFocusedSurfaces();
0063     void testV2V3SameClient();
0064     void testV3Styling();
0065     void testDisableShowInputPanel();
0066     void testModifierForwarding();
0067     void testFakeEventFallback();
0068 
0069 private:
0070     void touchNow()
0071     {
0072         static int time = 0;
0073         Test::touchDown(0, {100, 100}, ++time);
0074         Test::touchUp(0, ++time);
0075     }
0076 };
0077 
0078 void InputMethodTest::initTestCase()
0079 {
0080     QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.kwin.testvirtualkeyboard"));
0081 
0082     qRegisterMetaType<KWin::Deleted *>();
0083     qRegisterMetaType<KWin::Window *>();
0084     qRegisterMetaType<KWayland::Client::Output *>();
0085 
0086     QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
0087     QVERIFY(waylandServer()->init(s_socketName));
0088     QMetaObject::invokeMethod(kwinApp()->outputBackend(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(QVector<QRect>, QVector<QRect>() << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 1024)));
0089 
0090     static_cast<WaylandTestApplication *>(kwinApp())->setInputMethodServerToStart("internal");
0091     kwinApp()->start();
0092     QVERIFY(applicationStartedSpy.wait());
0093     const auto outputs = workspace()->outputs();
0094     QCOMPARE(outputs.count(), 2);
0095     QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024));
0096     QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024));
0097 }
0098 
0099 void InputMethodTest::init()
0100 {
0101     touchNow();
0102     QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::TextInputManagerV2 | Test::AdditionalWaylandInterface::InputMethodV1 | Test::AdditionalWaylandInterface::TextInputManagerV3));
0103 
0104     workspace()->setActiveOutput(QPoint(640, 512));
0105     KWin::Cursors::self()->mouse()->setPos(QPoint(640, 512));
0106 
0107     kwinApp()->inputMethod()->setEnabled(true);
0108 }
0109 
0110 void InputMethodTest::cleanup()
0111 {
0112     Test::destroyWaylandConnection();
0113 }
0114 
0115 void InputMethodTest::testOpenClose()
0116 {
0117     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0118     QSignalSpy windowRemovedSpy(workspace(), &Workspace::windowRemoved);
0119 
0120     // Create an xdg_toplevel surface and wait for the compositor to catch up.
0121     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0122     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0123     Window *window = Test::renderAndWaitForShown(surface.get(), QSize(1280, 1024), Qt::red);
0124     QVERIFY(window);
0125     QVERIFY(window->isActive());
0126     QCOMPARE(window->frameGeometry().size(), QSize(1280, 1024));
0127     QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
0128     QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
0129     QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
0130     QVERIFY(surfaceConfigureRequestedSpy.wait());
0131 
0132     std::unique_ptr<KWayland::Client::TextInput> textInput(Test::waylandTextInputManager()->createTextInput(Test::waylandSeat()));
0133     QVERIFY(textInput != nullptr);
0134 
0135     // Show the keyboard
0136     touchNow();
0137     textInput->enable(surface.get());
0138     textInput->showInputPanel();
0139     QSignalSpy paneladded(kwinApp()->inputMethod(), &KWin::InputMethod::panelChanged);
0140     QVERIFY(windowAddedSpy.wait());
0141     QCOMPARE(paneladded.count(), 1);
0142 
0143     Window *keyboardClient = windowAddedSpy.last().first().value<Window *>();
0144     QVERIFY(keyboardClient);
0145     QVERIFY(keyboardClient->isInputMethod());
0146 
0147     // Do the actual resize
0148     QVERIFY(surfaceConfigureRequestedSpy.wait());
0149 
0150     Test::render(surface.get(), toplevelConfigureRequestedSpy.last().first().value<QSize>(), Qt::red);
0151     QVERIFY(frameGeometryChangedSpy.wait());
0152 
0153     QCOMPARE(window->frameGeometry().height(), 1024 - keyboardClient->inputGeometry().height());
0154 
0155     // Hide the keyboard
0156     textInput->hideInputPanel();
0157 
0158     QVERIFY(surfaceConfigureRequestedSpy.wait());
0159     Test::render(surface.get(), toplevelConfigureRequestedSpy.last().first().value<QSize>(), Qt::red);
0160     QVERIFY(frameGeometryChangedSpy.wait());
0161 
0162     QCOMPARE(window->frameGeometry().height(), 1024);
0163 
0164     // show the keyboard again
0165     touchNow();
0166     textInput->enable(surface.get());
0167     textInput->showInputPanel();
0168 
0169     QVERIFY(surfaceConfigureRequestedSpy.wait());
0170     QVERIFY(keyboardClient->isShown());
0171 
0172     // Destroy the test window.
0173     shellSurface.reset();
0174     QVERIFY(Test::waitForWindowDestroyed(window));
0175 }
0176 
0177 void InputMethodTest::testEnableDisableV3()
0178 {
0179     // Create an xdg_toplevel surface and wait for the compositor to catch up.
0180     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0181     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0182     Window *window = Test::renderAndWaitForShown(surface.get(), QSize(1280, 1024), Qt::red);
0183     QVERIFY(window);
0184     QVERIFY(window->isActive());
0185     QCOMPARE(window->frameGeometry().size(), QSize(1280, 1024));
0186 
0187     auto textInputV3 = std::make_unique<Test::TextInputV3>();
0188     textInputV3->init(Test::waylandTextInputManagerV3()->get_text_input(*(Test::waylandSeat())));
0189 
0190     // Show the keyboard
0191     touchNow();
0192     textInputV3->enable();
0193 
0194     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0195     QSignalSpy inputMethodActiveSpy(kwinApp()->inputMethod(), &InputMethod::activeChanged);
0196     // just enabling the text-input should not show it but rather on commit
0197     QVERIFY(!kwinApp()->inputMethod()->isActive());
0198     textInputV3->commit();
0199     QVERIFY(inputMethodActiveSpy.count() || inputMethodActiveSpy.wait());
0200     QVERIFY(kwinApp()->inputMethod()->isActive());
0201 
0202     QVERIFY(windowAddedSpy.wait());
0203     Window *keyboardClient = windowAddedSpy.last().first().value<Window *>();
0204     QVERIFY(keyboardClient);
0205     QVERIFY(keyboardClient->isInputMethod());
0206     QVERIFY(keyboardClient->isShown());
0207 
0208     // Text input v3 doesn't have hideInputPanel, just simiulate the hide from dbus call
0209     kwinApp()->inputMethod()->hide();
0210     QVERIFY(!keyboardClient->isShown());
0211 
0212     QSignalSpy windowShownSpy(keyboardClient, &Window::windowShown);
0213     // Force enable the text input object. This is what's done by Gtk.
0214     textInputV3->enable();
0215     textInputV3->commit();
0216 
0217     windowShownSpy.wait();
0218     QVERIFY(keyboardClient->isShown());
0219 
0220     // disable text input and ensure that it is not hiding input panel without commit
0221     inputMethodActiveSpy.clear();
0222     QVERIFY(kwinApp()->inputMethod()->isActive());
0223     textInputV3->disable();
0224     textInputV3->commit();
0225     QVERIFY(inputMethodActiveSpy.count() || inputMethodActiveSpy.wait());
0226     QVERIFY(!kwinApp()->inputMethod()->isActive());
0227 }
0228 
0229 void InputMethodTest::testEnableActive()
0230 {
0231     // This test verifies that enabling text-input twice won't change the active input method status.
0232     QVERIFY(!kwinApp()->inputMethod()->isActive());
0233 
0234     // Create an xdg_toplevel surface and wait for the compositor to catch up.
0235     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0236     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0237     Window *window = Test::renderAndWaitForShown(surface.get(), QSize(1280, 1024), Qt::red);
0238     QVERIFY(window);
0239     QVERIFY(window->isActive());
0240 
0241     // Show the keyboard
0242     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0243     std::unique_ptr<KWayland::Client::TextInput> textInput(Test::waylandTextInputManager()->createTextInput(Test::waylandSeat()));
0244     textInput->enable(surface.get());
0245     QSignalSpy paneladded(kwinApp()->inputMethod(), &KWin::InputMethod::panelChanged);
0246     QVERIFY(paneladded.wait());
0247     textInput->showInputPanel();
0248     QVERIFY(windowAddedSpy.wait());
0249     QVERIFY(kwinApp()->inputMethod()->isActive());
0250 
0251     // Ask the keyboard to be shown again.
0252     QSignalSpy activateSpy(kwinApp()->inputMethod(), &InputMethod::activeChanged);
0253     textInput->enable(surface.get());
0254     textInput->showInputPanel();
0255     activateSpy.wait(200);
0256     QVERIFY(activateSpy.isEmpty());
0257     QVERIFY(kwinApp()->inputMethod()->isActive());
0258 
0259     // Destroy the test window.
0260     shellSurface.reset();
0261     QVERIFY(Test::waitForWindowDestroyed(window));
0262 }
0263 
0264 void InputMethodTest::testHidePanel()
0265 {
0266     QVERIFY(!kwinApp()->inputMethod()->isActive());
0267 
0268     touchNow();
0269     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0270     QSignalSpy windowRemovedSpy(workspace(), &Workspace::windowRemoved);
0271 
0272     QSignalSpy activateSpy(kwinApp()->inputMethod(), &InputMethod::activeChanged);
0273     std::unique_ptr<KWayland::Client::TextInput> textInput(Test::waylandTextInputManager()->createTextInput(Test::waylandSeat()));
0274 
0275     // Create an xdg_toplevel surface and wait for the compositor to catch up.
0276     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0277     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0278     Window *window = Test::renderAndWaitForShown(surface.get(), QSize(1280, 1024), Qt::red);
0279     waylandServer()->seat()->setFocusedTextInputSurface(window->surface());
0280 
0281     textInput->enable(surface.get());
0282     QSignalSpy paneladded(kwinApp()->inputMethod(), &KWin::InputMethod::panelChanged);
0283     QVERIFY(paneladded.wait());
0284     textInput->showInputPanel();
0285     QVERIFY(windowAddedSpy.wait());
0286 
0287     QCOMPARE(workspace()->activeWindow(), window);
0288 
0289     QCOMPARE(windowAddedSpy.count(), 2);
0290     QVERIFY(activateSpy.count() || activateSpy.wait());
0291     QVERIFY(kwinApp()->inputMethod()->isActive());
0292 
0293     auto keyboardWindow = kwinApp()->inputMethod()->panel();
0294     auto ipsurface = Test::inputPanelSurface();
0295     QVERIFY(keyboardWindow);
0296     windowRemovedSpy.clear();
0297     delete ipsurface;
0298     QVERIFY(kwinApp()->inputMethod()->isVisible());
0299     QVERIFY(windowRemovedSpy.count() || windowRemovedSpy.wait());
0300     QVERIFY(!kwinApp()->inputMethod()->isVisible());
0301 
0302     // Destroy the test window.
0303     shellSurface.reset();
0304     QVERIFY(Test::waitForWindowDestroyed(window));
0305 }
0306 
0307 void InputMethodTest::testSwitchFocusedSurfaces()
0308 {
0309     touchNow();
0310     QVERIFY(!kwinApp()->inputMethod()->isActive());
0311 
0312     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0313     QSignalSpy windowRemovedSpy(workspace(), &Workspace::windowRemoved);
0314 
0315     QSignalSpy activateSpy(kwinApp()->inputMethod(), &InputMethod::activeChanged);
0316     std::unique_ptr<KWayland::Client::TextInput> textInput(Test::waylandTextInputManager()->createTextInput(Test::waylandSeat()));
0317 
0318     QVector<Window *> windows;
0319     std::vector<std::unique_ptr<KWayland::Client::Surface>> surfaces;
0320     QVector<Test::XdgToplevel *> toplevels;
0321     // We create 3 surfaces
0322     for (int i = 0; i < 3; ++i) {
0323         std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
0324         auto shellSurface = Test::createXdgToplevelSurface(surface.get());
0325         windows += Test::renderAndWaitForShown(surface.get(), QSize(1280, 1024), Qt::red);
0326         QCOMPARE(workspace()->activeWindow(), windows.constLast());
0327         surfaces.push_back(std::move(surface));
0328         toplevels += shellSurface;
0329     }
0330     QCOMPARE(windowAddedSpy.count(), 3);
0331     waylandServer()->seat()->setFocusedTextInputSurface(windows.constFirst()->surface());
0332 
0333     QVERIFY(!kwinApp()->inputMethod()->isActive());
0334     textInput->enable(surfaces.back().get());
0335     QVERIFY(!kwinApp()->inputMethod()->isActive());
0336     waylandServer()->seat()->setFocusedTextInputSurface(windows.first()->surface());
0337     QVERIFY(!kwinApp()->inputMethod()->isActive());
0338     activateSpy.clear();
0339     waylandServer()->seat()->setFocusedTextInputSurface(windows.last()->surface());
0340     QVERIFY(activateSpy.count() || activateSpy.wait());
0341     QVERIFY(kwinApp()->inputMethod()->isActive());
0342 
0343     activateSpy.clear();
0344     waylandServer()->seat()->setFocusedTextInputSurface(windows.first()->surface());
0345     QVERIFY(activateSpy.count() || activateSpy.wait());
0346     QVERIFY(!kwinApp()->inputMethod()->isActive());
0347 
0348     // Destroy the test window.
0349     for (int i = 0; i < windows.count(); ++i) {
0350         delete toplevels[i];
0351         QVERIFY(Test::waitForWindowDestroyed(windows[i]));
0352     }
0353 }
0354 
0355 void InputMethodTest::testV2V3SameClient()
0356 {
0357     touchNow();
0358     QVERIFY(!kwinApp()->inputMethod()->isActive());
0359 
0360     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0361     QSignalSpy windowRemovedSpy(workspace(), &Workspace::windowRemoved);
0362 
0363     QSignalSpy activateSpy(kwinApp()->inputMethod(), &InputMethod::activeChanged);
0364     std::unique_ptr<KWayland::Client::TextInput> textInput(Test::waylandTextInputManager()->createTextInput(Test::waylandSeat()));
0365 
0366     auto textInputV3 = std::make_unique<Test::TextInputV3>();
0367     textInputV3->init(Test::waylandTextInputManagerV3()->get_text_input(*(Test::waylandSeat())));
0368 
0369     std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
0370     std::unique_ptr<Test::XdgToplevel> toplevel(Test::createXdgToplevelSurface(surface.get()));
0371     Window *window = Test::renderAndWaitForShown(surface.get(), QSize(1280, 1024), Qt::red);
0372     QCOMPARE(workspace()->activeWindow(), window);
0373     QCOMPARE(windowAddedSpy.count(), 1);
0374     waylandServer()->seat()->setFocusedTextInputSurface(window->surface());
0375     QVERIFY(!kwinApp()->inputMethod()->isActive());
0376 
0377     // Enable and disable v2
0378     textInput->enable(surface.get());
0379     QVERIFY(activateSpy.count() || activateSpy.wait());
0380     QVERIFY(kwinApp()->inputMethod()->isActive());
0381 
0382     activateSpy.clear();
0383     textInput->disable(surface.get());
0384     QVERIFY(activateSpy.count() || activateSpy.wait());
0385     QVERIFY(!kwinApp()->inputMethod()->isActive());
0386 
0387     // Enable and disable v3
0388     activateSpy.clear();
0389     textInputV3->enable();
0390     textInputV3->commit();
0391     QVERIFY(activateSpy.count() || activateSpy.wait());
0392     QVERIFY(kwinApp()->inputMethod()->isActive());
0393 
0394     activateSpy.clear();
0395     textInputV3->disable();
0396     textInputV3->commit();
0397     activateSpy.clear();
0398     QVERIFY(activateSpy.count() || activateSpy.wait());
0399     QVERIFY(!kwinApp()->inputMethod()->isActive());
0400 
0401     // Enable v2 and v3
0402     activateSpy.clear();
0403     textInputV3->enable();
0404     textInputV3->commit();
0405     textInput->enable(surface.get());
0406     QVERIFY(activateSpy.count() || activateSpy.wait());
0407     QVERIFY(kwinApp()->inputMethod()->isActive());
0408 
0409     // Disable v3, should still be active since v2 is active.
0410     activateSpy.clear();
0411     textInputV3->disable();
0412     textInputV3->commit();
0413     activateSpy.wait(200);
0414     QVERIFY(kwinApp()->inputMethod()->isActive());
0415 
0416     // Disable v2
0417     activateSpy.clear();
0418     textInput->disable(surface.get());
0419     QVERIFY(activateSpy.count() || activateSpy.wait());
0420     QVERIFY(!kwinApp()->inputMethod()->isActive());
0421 
0422     toplevel.reset();
0423     QVERIFY(Test::waitForWindowDestroyed(window));
0424 }
0425 
0426 void InputMethodTest::testV3Styling()
0427 {
0428     // Create an xdg_toplevel surface and wait for the compositor to catch up.
0429     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0430     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0431     Window *window = Test::renderAndWaitForShown(surface.get(), QSize(1280, 1024), Qt::red);
0432     QVERIFY(window);
0433     QVERIFY(window->isActive());
0434     QCOMPARE(window->frameGeometry().size(), QSize(1280, 1024));
0435 
0436     auto textInputV3 = std::make_unique<Test::TextInputV3>();
0437     textInputV3->init(Test::waylandTextInputManagerV3()->get_text_input(*(Test::waylandSeat())));
0438     textInputV3->enable();
0439 
0440     QSignalSpy inputMethodActiveSpy(kwinApp()->inputMethod(), &InputMethod::activeChanged);
0441     QSignalSpy inputMethodActivateSpy(Test::inputMethod(), &Test::MockInputMethod::activate);
0442     // just enabling the text-input should not show it but rather on commit
0443     QVERIFY(!kwinApp()->inputMethod()->isActive());
0444     textInputV3->commit();
0445     QVERIFY(inputMethodActiveSpy.count() || inputMethodActiveSpy.wait());
0446     QVERIFY(kwinApp()->inputMethod()->isActive());
0447     QVERIFY(inputMethodActivateSpy.wait());
0448     auto context = Test::inputMethod()->context();
0449     QSignalSpy textInputPreeditSpy(textInputV3.get(), &Test::TextInputV3::preeditString);
0450     zwp_input_method_context_v1_preedit_cursor(context, 0);
0451     zwp_input_method_context_v1_preedit_styling(context, 0, 3, 7);
0452     zwp_input_method_context_v1_preedit_string(context, 0, "ABCD", "ABCD");
0453     QVERIFY(textInputPreeditSpy.wait());
0454     QCOMPARE(textInputPreeditSpy.last().at(0), QString("ABCD"));
0455     QCOMPARE(textInputPreeditSpy.last().at(1), 0);
0456     QCOMPARE(textInputPreeditSpy.last().at(2), 0);
0457 
0458     zwp_input_method_context_v1_preedit_cursor(context, 1);
0459     zwp_input_method_context_v1_preedit_styling(context, 0, 3, 7);
0460     zwp_input_method_context_v1_preedit_string(context, 0, "ABCDE", "ABCDE");
0461     QVERIFY(textInputPreeditSpy.wait());
0462     QCOMPARE(textInputPreeditSpy.last().at(0), QString("ABCDE"));
0463     QCOMPARE(textInputPreeditSpy.last().at(1), 1);
0464     QCOMPARE(textInputPreeditSpy.last().at(2), 1);
0465 
0466     zwp_input_method_context_v1_preedit_cursor(context, 2);
0467     // Use selection for [2, 2+2)
0468     zwp_input_method_context_v1_preedit_styling(context, 2, 2, 6);
0469     // Use high light for [3, 3+3)
0470     zwp_input_method_context_v1_preedit_styling(context, 3, 3, 4);
0471     zwp_input_method_context_v1_preedit_string(context, 0, "ABCDEF", "ABCDEF");
0472     QVERIFY(textInputPreeditSpy.wait());
0473     QCOMPARE(textInputPreeditSpy.last().at(0), QString("ABCDEF"));
0474     // Merged range should be [2, 6)
0475     QCOMPARE(textInputPreeditSpy.last().at(1), 2);
0476     QCOMPARE(textInputPreeditSpy.last().at(2), 6);
0477 
0478     zwp_input_method_context_v1_preedit_cursor(context, 2);
0479     // Use selection for [0, 0+2)
0480     zwp_input_method_context_v1_preedit_styling(context, 0, 2, 6);
0481     // Use high light for [3, 3+3)
0482     zwp_input_method_context_v1_preedit_styling(context, 3, 3, 4);
0483     zwp_input_method_context_v1_preedit_string(context, 0, "ABCDEF", "ABCDEF");
0484     QVERIFY(textInputPreeditSpy.wait());
0485     QCOMPARE(textInputPreeditSpy.last().at(0), QString("ABCDEF"));
0486     // Merged range should be none, because of the disjunction highlight.
0487     QCOMPARE(textInputPreeditSpy.last().at(1), 2);
0488     QCOMPARE(textInputPreeditSpy.last().at(2), 2);
0489 
0490     zwp_input_method_context_v1_preedit_cursor(context, 1);
0491     // Use selection for [0, 0+2)
0492     zwp_input_method_context_v1_preedit_styling(context, 0, 2, 6);
0493     // Use high light for [2, 2+3)
0494     zwp_input_method_context_v1_preedit_styling(context, 2, 3, 4);
0495     zwp_input_method_context_v1_preedit_string(context, 0, "ABCDEF", "ABCDEF");
0496     QVERIFY(textInputPreeditSpy.wait());
0497     QCOMPARE(textInputPreeditSpy.last().at(0), QString("ABCDEF"));
0498     // Merged range should be none, starting offset does not match.
0499     QCOMPARE(textInputPreeditSpy.last().at(1), 1);
0500     QCOMPARE(textInputPreeditSpy.last().at(2), 1);
0501 
0502     // Use different order of styling and cursor
0503     // Use high light for [3, 3+3)
0504     zwp_input_method_context_v1_preedit_styling(context, 3, 3, 4);
0505     zwp_input_method_context_v1_preedit_cursor(context, 1);
0506     // Use selection for [1, 1+2)
0507     zwp_input_method_context_v1_preedit_styling(context, 1, 2, 6);
0508     zwp_input_method_context_v1_preedit_string(context, 0, "ABCDEF", "ABCDEF");
0509     QVERIFY(textInputPreeditSpy.wait());
0510     QCOMPARE(textInputPreeditSpy.last().at(0), QString("ABCDEF"));
0511     // Merged range should be [1,6).
0512     QCOMPARE(textInputPreeditSpy.last().at(1), 1);
0513     QCOMPARE(textInputPreeditSpy.last().at(2), 6);
0514 }
0515 
0516 void InputMethodTest::testDisableShowInputPanel()
0517 {
0518     // Create an xdg_toplevel surface and wait for the compositor to catch up.
0519     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0520     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0521     Window *window = Test::renderAndWaitForShown(surface.get(), QSize(1280, 1024), Qt::red);
0522     QVERIFY(window);
0523     QVERIFY(window->isActive());
0524     QCOMPARE(window->frameGeometry().size(), QSize(1280, 1024));
0525 
0526     std::unique_ptr<KWayland::Client::TextInput> textInputV2(Test::waylandTextInputManager()->createTextInput(Test::waylandSeat()));
0527 
0528     QSignalSpy inputMethodActiveSpy(kwinApp()->inputMethod(), &InputMethod::activeChanged);
0529     // just enabling the text-input should not show it but rather on commit
0530     QVERIFY(!kwinApp()->inputMethod()->isActive());
0531     textInputV2->enable(surface.get());
0532     QVERIFY(inputMethodActiveSpy.count() || inputMethodActiveSpy.wait());
0533     QVERIFY(kwinApp()->inputMethod()->isActive());
0534 
0535     // disable text input and ensure that it is not hiding input panel without commit
0536     inputMethodActiveSpy.clear();
0537     QVERIFY(kwinApp()->inputMethod()->isActive());
0538     textInputV2->disable(surface.get());
0539     QVERIFY(inputMethodActiveSpy.count() || inputMethodActiveSpy.wait());
0540     QVERIFY(!kwinApp()->inputMethod()->isActive());
0541 
0542     QSignalSpy requestShowInputPanelSpy(waylandServer()->seat()->textInputV2(), &KWaylandServer::TextInputV2Interface::requestShowInputPanel);
0543     textInputV2->showInputPanel();
0544     QVERIFY(requestShowInputPanelSpy.count() || requestShowInputPanelSpy.wait());
0545     QVERIFY(!kwinApp()->inputMethod()->isActive());
0546 }
0547 
0548 void InputMethodTest::testModifierForwarding()
0549 {
0550     // Create an xdg_toplevel surface and wait for the compositor to catch up.
0551     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0552     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0553     Window *window = Test::renderAndWaitForShown(surface.get(), QSize(1280, 1024), Qt::red);
0554     QVERIFY(window);
0555     QVERIFY(window->isActive());
0556     QCOMPARE(window->frameGeometry().size(), QSize(1280, 1024));
0557 
0558     auto textInputV3 = std::make_unique<Test::TextInputV3>();
0559     textInputV3->init(Test::waylandTextInputManagerV3()->get_text_input(*(Test::waylandSeat())));
0560     textInputV3->enable();
0561 
0562     QSignalSpy inputMethodActiveSpy(kwinApp()->inputMethod(), &InputMethod::activeChanged);
0563     QSignalSpy inputMethodActivateSpy(Test::inputMethod(), &Test::MockInputMethod::activate);
0564     // just enabling the text-input should not show it but rather on commit
0565     QVERIFY(!kwinApp()->inputMethod()->isActive());
0566     textInputV3->commit();
0567     QVERIFY(inputMethodActiveSpy.count() || inputMethodActiveSpy.wait());
0568     QVERIFY(kwinApp()->inputMethod()->isActive());
0569     QVERIFY(inputMethodActivateSpy.wait());
0570     auto context = Test::inputMethod()->context();
0571     std::unique_ptr<KWayland::Client::Keyboard> keyboardGrab(new KWayland::Client::Keyboard);
0572     keyboardGrab->setup(zwp_input_method_context_v1_grab_keyboard(context));
0573     QSignalSpy modifierSpy(keyboardGrab.get(), &KWayland::Client::Keyboard::modifiersChanged);
0574     // Wait for initial modifiers update
0575     QVERIFY(modifierSpy.wait());
0576 
0577     quint32 timestamp = 1;
0578 
0579     QSignalSpy keySpy(keyboardGrab.get(), &KWayland::Client::Keyboard::keyChanged);
0580     bool keyChanged = false;
0581     bool modifiersChanged = false;
0582     // We want to verify the order of two signals, so SignalSpy is not very useful here.
0583     auto keyChangedConnection = connect(keyboardGrab.get(), &KWayland::Client::Keyboard::keyChanged, [&keyChanged, &modifiersChanged]() {
0584         QVERIFY(!modifiersChanged);
0585         keyChanged = true;
0586     });
0587     auto modifiersChangedConnection = connect(keyboardGrab.get(), &KWayland::Client::Keyboard::modifiersChanged, [&keyChanged, &modifiersChanged]() {
0588         QVERIFY(keyChanged);
0589         modifiersChanged = true;
0590     });
0591     Test::keyboardKeyPressed(KEY_LEFTCTRL, timestamp++);
0592     QVERIFY(keySpy.count() == 1 || keySpy.wait());
0593     QVERIFY(modifierSpy.count() == 2 || modifierSpy.wait());
0594     disconnect(keyChangedConnection);
0595     disconnect(modifiersChangedConnection);
0596 
0597     Test::keyboardKeyPressed(KEY_A, timestamp++);
0598     QVERIFY(keySpy.count() == 2 || keySpy.wait());
0599     QVERIFY(modifierSpy.count() == 2 || modifierSpy.wait());
0600 
0601     // verify the order of key and modifiers again. Key first, then modifiers.
0602     keyChanged = false;
0603     modifiersChanged = false;
0604     keyChangedConnection = connect(keyboardGrab.get(), &KWayland::Client::Keyboard::keyChanged, [&keyChanged, &modifiersChanged]() {
0605         QVERIFY(!modifiersChanged);
0606         keyChanged = true;
0607     });
0608     modifiersChangedConnection = connect(keyboardGrab.get(), &KWayland::Client::Keyboard::modifiersChanged, [&keyChanged, &modifiersChanged]() {
0609         QVERIFY(keyChanged);
0610         modifiersChanged = true;
0611     });
0612     Test::keyboardKeyReleased(KEY_LEFTCTRL, timestamp++);
0613     QVERIFY(keySpy.count() == 3 || keySpy.wait());
0614     QVERIFY(modifierSpy.count() == 3 || modifierSpy.wait());
0615     disconnect(keyChangedConnection);
0616     disconnect(modifiersChangedConnection);
0617 }
0618 
0619 void InputMethodTest::testFakeEventFallback()
0620 {
0621     // Create an xdg_toplevel surface and wait for the compositor to catch up.
0622     std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
0623     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0624     Window *window = Test::renderAndWaitForShown(surface.get(), QSize(1280, 1024), Qt::red);
0625     QVERIFY(window);
0626     QVERIFY(window->isActive());
0627     QCOMPARE(window->frameGeometry().size(), QSize(1280, 1024));
0628 
0629     // Since we don't have a way to communicate with the client, manually activate
0630     // the input method.
0631     QSignalSpy inputMethodActiveSpy(Test::inputMethod(), &Test::MockInputMethod::activate);
0632     kwinApp()->inputMethod()->setActive(true);
0633     QVERIFY(inputMethodActiveSpy.count() || inputMethodActiveSpy.wait());
0634 
0635     // Without a way to communicate to the client, we send fake key events. This
0636     // means the client needs to be able to receive them, so create a keyboard for
0637     // the client and listen whether it gets the right events.
0638     auto keyboard = Test::waylandSeat()->createKeyboard(window);
0639     QSignalSpy keySpy(keyboard, &KWayland::Client::Keyboard::keyChanged);
0640 
0641     auto context = Test::inputMethod()->context();
0642     QVERIFY(context);
0643 
0644     // First, send a simple one-character string and check to see if that
0645     // generates a key press followed by a key release on the client side.
0646     zwp_input_method_context_v1_commit_string(context, 0, "a");
0647 
0648     keySpy.wait();
0649     QVERIFY(keySpy.count() == 2);
0650 
0651     auto compare = [](const QList<QVariant> &input, quint32 key, KWayland::Client::Keyboard::KeyState state) {
0652         auto inputKey = input.at(0).toInt();
0653         auto inputState = input.at(1).value<KWayland::Client::Keyboard::KeyState>();
0654         QCOMPARE(inputKey, key);
0655         QCOMPARE(inputState, state);
0656     };
0657 
0658     compare(keySpy.at(0), KEY_A, KWayland::Client::Keyboard::KeyState::Pressed);
0659     compare(keySpy.at(1), KEY_A, KWayland::Client::Keyboard::KeyState::Released);
0660 
0661     keySpy.clear();
0662 
0663     // Capital letters are recognised and sent as a combination of Shift + the
0664     // letter.
0665 
0666     zwp_input_method_context_v1_commit_string(context, 0, "A");
0667 
0668     keySpy.wait();
0669     QVERIFY(keySpy.count() == 4);
0670 
0671     compare(keySpy.at(0), KEY_LEFTSHIFT, KWayland::Client::Keyboard::KeyState::Pressed);
0672     compare(keySpy.at(1), KEY_A, KWayland::Client::Keyboard::KeyState::Pressed);
0673     compare(keySpy.at(2), KEY_A, KWayland::Client::Keyboard::KeyState::Released);
0674     compare(keySpy.at(3), KEY_LEFTSHIFT, KWayland::Client::Keyboard::KeyState::Released);
0675 
0676     keySpy.clear();
0677 
0678     // Special keys are not sent through commit_string but instead use keysym.
0679     auto enter = input()->keyboard()->xkb()->toKeysym(KEY_ENTER);
0680     zwp_input_method_context_v1_keysym(context, 0, 0, enter, uint32_t(KWaylandServer::KeyboardKeyState::Pressed), 0);
0681     zwp_input_method_context_v1_keysym(context, 0, 1, enter, uint32_t(KWaylandServer::KeyboardKeyState::Released), 0);
0682 
0683     keySpy.wait();
0684     QVERIFY(keySpy.count() == 2);
0685 
0686     compare(keySpy.at(0), KEY_ENTER, KWayland::Client::Keyboard::KeyState::Pressed);
0687     compare(keySpy.at(1), KEY_ENTER, KWayland::Client::Keyboard::KeyState::Released);
0688 }
0689 
0690 WAYLANDTEST_MAIN(InputMethodTest)
0691 
0692 #include "inputmethod_test.moc"