File indexing completed on 2024-11-10 04:55:59
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"