File indexing completed on 2024-05-12 05:30:34

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