Warning, file /plasma/kwin/autotests/integration/inputmethod_test.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
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"