16#include "qwayland-input-method-unstable-v1.h"
17#include "qwayland-text-input-unstable-v3.h"
28#include <QDBusConnection>
29#include <QDBusMessage>
30#include <QDBusPendingReply>
34#include <KWayland/Client/compositor.h>
35#include <KWayland/Client/keyboard.h>
36#include <KWayland/Client/output.h>
37#include <KWayland/Client/region.h>
38#include <KWayland/Client/seat.h>
39#include <KWayland/Client/surface.h>
40#include <KWayland/Client/textinput.h>
41#include <linux/input-event-codes.h>
46static const QString s_socketName = QStringLiteral(
"wayland_test_kwin_inputmethod-0");
57 void testEnableDisableV3();
58 void testEnableActive();
60 void testReactivateFocus();
61 void testSwitchFocusedSurfaces();
62 void testV2V3SameClient();
64 void testDisableShowInputPanel();
65 void testModifierForwarding();
66 void testFakeEventFallback();
67 void testOverlayPositioning_data();
68 void testOverlayPositioning();
79void InputMethodTest::initTestCase()
81 QDBusConnection::sessionBus().registerService(QStringLiteral(
"org.kde.kwin.testvirtualkeyboard"));
83 qRegisterMetaType<KWin::Window *>();
84 qRegisterMetaType<KWayland::Client::Output *>();
89 QRect(0, 0, 1280, 1024),
90 QRect(1280, 0, 1280, 1024),
95 QVERIFY(applicationStartedSpy.wait());
98 QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024));
99 QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024));
102void InputMethodTest::init()
109 QVERIFY(
Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::TextInputManagerV2 | Test::AdditionalWaylandInterface::InputMethodV1 | Test::AdditionalWaylandInterface::TextInputManagerV3));
111 kwinApp()->inputMethod()->setEnabled(
true);
114void InputMethodTest::cleanup()
119void InputMethodTest::testOpenClose()
129 QVERIFY(window->isActive());
130 QCOMPARE(window->frameGeometry().size(), QSize(1280, 1024));
131 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
134 QVERIFY(surfaceConfigureRequestedSpy.wait());
137 QVERIFY(textInput !=
nullptr);
141 textInput->enable(surface.get());
142 textInput->showInputPanel();
144 QVERIFY(windowAddedSpy.wait());
145 QCOMPARE(paneladded.count(), 1);
147 Window *keyboardClient = windowAddedSpy.last().first().value<
Window *>();
148 QVERIFY(keyboardClient);
149 QVERIFY(keyboardClient->isInputMethod());
152 QVERIFY(surfaceConfigureRequestedSpy.wait());
154 Test::render(surface.get(), toplevelConfigureRequestedSpy.last().first().value<QSize>(), Qt::red);
155 QVERIFY(frameGeometryChangedSpy.wait());
157 QCOMPARE(window->frameGeometry().height(), 1024 - keyboardClient->frameGeometry().height());
160 textInput->hideInputPanel();
162 QVERIFY(surfaceConfigureRequestedSpy.wait());
163 Test::render(surface.get(), toplevelConfigureRequestedSpy.last().first().value<QSize>(), Qt::red);
164 QVERIFY(frameGeometryChangedSpy.wait());
166 QCOMPARE(window->frameGeometry().height(), 1024);
170 textInput->enable(surface.get());
171 textInput->showInputPanel();
173 QVERIFY(surfaceConfigureRequestedSpy.wait());
174 QVERIFY(keyboardClient->isShown());
177 shellSurface.reset();
181void InputMethodTest::testEnableDisableV3()
188 QVERIFY(window->isActive());
189 QCOMPARE(window->frameGeometry().size(), QSize(1280, 1024));
191 auto textInputV3 = std::make_unique<Test::TextInputV3>();
196 textInputV3->enable();
202 textInputV3->commit();
203 QVERIFY(inputMethodActiveSpy.count() || inputMethodActiveSpy.wait());
206 QVERIFY(windowAddedSpy.wait());
207 Window *keyboardClient = windowAddedSpy.last().first().value<
Window *>();
208 QVERIFY(keyboardClient);
209 QVERIFY(keyboardClient->isInputMethod());
210 QVERIFY(keyboardClient->isShown());
213 kwinApp()->inputMethod()->hide();
214 QVERIFY(!keyboardClient->isShown());
216 QSignalSpy windowShownSpy(keyboardClient, &Window::windowShown);
218 textInputV3->enable();
219 textInputV3->commit();
221 windowShownSpy.wait();
222 QVERIFY(keyboardClient->isShown());
225 inputMethodActiveSpy.clear();
227 textInputV3->disable();
228 textInputV3->commit();
229 QVERIFY(inputMethodActiveSpy.count() || inputMethodActiveSpy.wait());
233void InputMethodTest::testEnableActive()
243 QVERIFY(window->isActive());
248 textInput->enable(surface.get());
250 QVERIFY(paneladded.wait());
251 textInput->showInputPanel();
252 QVERIFY(windowAddedSpy.wait());
257 textInput->enable(surface.get());
258 textInput->showInputPanel();
259 activateSpy.wait(200);
260 QVERIFY(activateSpy.isEmpty());
264 shellSurface.reset();
268void InputMethodTest::testHidePanel()
285 textInput->enable(surface.get());
287 QVERIFY(paneladded.wait());
288 textInput->showInputPanel();
289 QVERIFY(windowAddedSpy.wait());
291 QCOMPARE(
workspace()->activeWindow(), window);
293 QCOMPARE(windowAddedSpy.count(), 2);
294 QVERIFY(activateSpy.count() || activateSpy.wait());
297 auto keyboardWindow = kwinApp()->inputMethod()->panel();
299 QVERIFY(keyboardWindow);
300 windowRemovedSpy.clear();
303 QVERIFY(windowRemovedSpy.count() || windowRemovedSpy.wait());
307 shellSurface.reset();
311void InputMethodTest::testReactivateFocus()
320 QVERIFY(window->isActive());
321 QCOMPARE(window->frameGeometry().size(), QSize(1280, 1024));
326 textInput->enable(surface.get());
328 QVERIFY(paneladded.wait());
329 textInput->showInputPanel();
330 QVERIFY(windowAddedSpy.wait());
336 kwinApp()->inputMethod()->setActive(
false);
341 textInput->enable(surface.get());
342 textInput->showInputPanel();
347 shellSurface.reset();
351void InputMethodTest::testSwitchFocusedSurfaces()
362 QList<Window *> windows;
363 std::vector<std::unique_ptr<KWayland::Client::Surface>> surfaces;
364 QList<Test::XdgToplevel *> toplevels;
366 for (
int i = 0; i < 3; ++i) {
370 QCOMPARE(
workspace()->activeWindow(), windows.constLast());
371 surfaces.push_back(std::move(surface));
372 toplevels += shellSurface;
374 QCOMPARE(windowAddedSpy.count(), 3);
378 textInput->enable(surfaces.back().get());
384 QVERIFY(activateSpy.count() || activateSpy.wait());
389 QVERIFY(activateSpy.count() || activateSpy.wait());
393 for (
int i = 0; i < windows.count(); ++i) {
399void InputMethodTest::testV2V3SameClient()
410 auto textInputV3 = std::make_unique<Test::TextInputV3>();
416 QCOMPARE(
workspace()->activeWindow(), window);
417 QCOMPARE(windowAddedSpy.count(), 1);
422 textInput->enable(surface.get());
423 QVERIFY(activateSpy.count() || activateSpy.wait());
427 textInput->disable(surface.get());
428 QVERIFY(activateSpy.count() || activateSpy.wait());
433 textInputV3->enable();
434 textInputV3->commit();
435 QVERIFY(activateSpy.count() || activateSpy.wait());
439 textInputV3->disable();
440 textInputV3->commit();
442 QVERIFY(activateSpy.count() || activateSpy.wait());
447 textInputV3->enable();
448 textInputV3->commit();
449 textInput->enable(surface.get());
450 QVERIFY(activateSpy.count() || activateSpy.wait());
455 textInputV3->disable();
456 textInputV3->commit();
457 activateSpy.wait(200);
462 textInput->disable(surface.get());
463 QVERIFY(activateSpy.count() || activateSpy.wait());
470void InputMethodTest::testV3Styling()
477 QVERIFY(window->isActive());
478 QCOMPARE(window->frameGeometry().size(), QSize(1280, 1024));
480 auto textInputV3 = std::make_unique<Test::TextInputV3>();
482 textInputV3->enable();
488 textInputV3->commit();
489 QVERIFY(inputMethodActiveSpy.count() || inputMethodActiveSpy.wait());
491 QVERIFY(inputMethodActivateSpy.wait());
494 zwp_input_method_context_v1_preedit_cursor(context, 0);
495 zwp_input_method_context_v1_preedit_styling(context, 0, 3, 7);
496 zwp_input_method_context_v1_preedit_string(context, 0,
"ABCD",
"ABCD");
497 QVERIFY(textInputPreeditSpy.wait());
498 QCOMPARE(textInputPreeditSpy.last().at(0), QString(
"ABCD"));
499 QCOMPARE(textInputPreeditSpy.last().at(1), 0);
500 QCOMPARE(textInputPreeditSpy.last().at(2), 0);
502 zwp_input_method_context_v1_preedit_cursor(context, 1);
503 zwp_input_method_context_v1_preedit_styling(context, 0, 3, 7);
504 zwp_input_method_context_v1_preedit_string(context, 0,
"ABCDE",
"ABCDE");
505 QVERIFY(textInputPreeditSpy.wait());
506 QCOMPARE(textInputPreeditSpy.last().at(0), QString(
"ABCDE"));
507 QCOMPARE(textInputPreeditSpy.last().at(1), 1);
508 QCOMPARE(textInputPreeditSpy.last().at(2), 1);
510 zwp_input_method_context_v1_preedit_cursor(context, 2);
512 zwp_input_method_context_v1_preedit_styling(context, 2, 2, 6);
514 zwp_input_method_context_v1_preedit_styling(context, 3, 3, 4);
515 zwp_input_method_context_v1_preedit_string(context, 0,
"ABCDEF",
"ABCDEF");
516 QVERIFY(textInputPreeditSpy.wait());
517 QCOMPARE(textInputPreeditSpy.last().at(0), QString(
"ABCDEF"));
519 QCOMPARE(textInputPreeditSpy.last().at(1), 2);
520 QCOMPARE(textInputPreeditSpy.last().at(2), 6);
522 zwp_input_method_context_v1_preedit_cursor(context, 2);
524 zwp_input_method_context_v1_preedit_styling(context, 0, 2, 6);
526 zwp_input_method_context_v1_preedit_styling(context, 3, 3, 4);
527 zwp_input_method_context_v1_preedit_string(context, 0,
"ABCDEF",
"ABCDEF");
528 QVERIFY(textInputPreeditSpy.wait());
529 QCOMPARE(textInputPreeditSpy.last().at(0), QString(
"ABCDEF"));
531 QCOMPARE(textInputPreeditSpy.last().at(1), 2);
532 QCOMPARE(textInputPreeditSpy.last().at(2), 2);
534 zwp_input_method_context_v1_preedit_cursor(context, 1);
536 zwp_input_method_context_v1_preedit_styling(context, 0, 2, 6);
538 zwp_input_method_context_v1_preedit_styling(context, 2, 3, 4);
539 zwp_input_method_context_v1_preedit_string(context, 0,
"ABCDEF",
"ABCDEF");
540 QVERIFY(textInputPreeditSpy.wait());
541 QCOMPARE(textInputPreeditSpy.last().at(0), QString(
"ABCDEF"));
543 QCOMPARE(textInputPreeditSpy.last().at(1), 1);
544 QCOMPARE(textInputPreeditSpy.last().at(2), 1);
548 zwp_input_method_context_v1_preedit_styling(context, 3, 3, 4);
549 zwp_input_method_context_v1_preedit_cursor(context, 1);
551 zwp_input_method_context_v1_preedit_styling(context, 1, 2, 6);
552 zwp_input_method_context_v1_preedit_string(context, 0,
"ABCDEF",
"ABCDEF");
553 QVERIFY(textInputPreeditSpy.wait());
554 QCOMPARE(textInputPreeditSpy.last().at(0), QString(
"ABCDEF"));
556 QCOMPARE(textInputPreeditSpy.last().at(1), 1);
557 QCOMPARE(textInputPreeditSpy.last().at(2), 6);
559 shellSurface.reset();
564void InputMethodTest::testDisableShowInputPanel()
571 QVERIFY(window->isActive());
572 QCOMPARE(window->frameGeometry().size(), QSize(1280, 1024));
579 textInputV2->enable(surface.get());
580 QVERIFY(inputMethodActiveSpy.count() || inputMethodActiveSpy.wait());
584 inputMethodActiveSpy.clear();
586 textInputV2->disable(surface.get());
587 QVERIFY(inputMethodActiveSpy.count() || inputMethodActiveSpy.wait());
591 textInputV2->showInputPanel();
592 QVERIFY(requestShowInputPanelSpy.count() || requestShowInputPanelSpy.wait());
595 shellSurface.reset();
599void InputMethodTest::testModifierForwarding()
606 QVERIFY(window->isActive());
607 QCOMPARE(window->frameGeometry().size(), QSize(1280, 1024));
609 auto textInputV3 = std::make_unique<Test::TextInputV3>();
611 textInputV3->enable();
617 textInputV3->commit();
618 QVERIFY(inputMethodActiveSpy.count() || inputMethodActiveSpy.wait());
620 QVERIFY(inputMethodActivateSpy.wait());
622 std::unique_ptr<KWayland::Client::Keyboard> keyboardGrab(
new KWayland::Client::Keyboard);
623 keyboardGrab->setup(zwp_input_method_context_v1_grab_keyboard(context));
624 QSignalSpy modifierSpy(keyboardGrab.get(), &KWayland::Client::Keyboard::modifiersChanged);
626 QVERIFY(modifierSpy.wait());
628 quint32 timestamp = 1;
630 QSignalSpy keySpy(keyboardGrab.get(), &KWayland::Client::Keyboard::keyChanged);
631 bool keyChanged =
false;
632 bool modifiersChanged =
false;
634 auto keyChangedConnection = connect(keyboardGrab.get(), &KWayland::Client::Keyboard::keyChanged, [&keyChanged, &modifiersChanged]() {
635 QVERIFY(!modifiersChanged);
638 auto modifiersChangedConnection = connect(keyboardGrab.get(), &KWayland::Client::Keyboard::modifiersChanged, [&keyChanged, &modifiersChanged]() {
640 modifiersChanged = true;
643 QVERIFY(keySpy.count() == 1 || keySpy.wait());
644 QVERIFY(modifierSpy.count() == 2 || modifierSpy.wait());
645 disconnect(keyChangedConnection);
646 disconnect(modifiersChangedConnection);
649 QVERIFY(keySpy.count() == 2 || keySpy.wait());
650 QVERIFY(modifierSpy.count() == 2 || modifierSpy.wait());
654 modifiersChanged =
false;
655 keyChangedConnection = connect(keyboardGrab.get(), &KWayland::Client::Keyboard::keyChanged, [&keyChanged, &modifiersChanged]() {
656 QVERIFY(!modifiersChanged);
659 modifiersChangedConnection = connect(keyboardGrab.get(), &KWayland::Client::Keyboard::modifiersChanged, [&keyChanged, &modifiersChanged]() {
661 modifiersChanged = true;
664 QVERIFY(keySpy.count() == 3 || keySpy.wait());
665 QVERIFY(modifierSpy.count() == 3 || modifierSpy.wait());
666 disconnect(keyChangedConnection);
667 disconnect(modifiersChangedConnection);
669 shellSurface.reset();
674void InputMethodTest::testFakeEventFallback()
681 QVERIFY(window->isActive());
682 QCOMPARE(window->frameGeometry().size(), QSize(1280, 1024));
687 kwinApp()->inputMethod()->setActive(
true);
688 QVERIFY(inputMethodActiveSpy.count() || inputMethodActiveSpy.wait());
694 QSignalSpy keySpy(keyboard, &KWayland::Client::Keyboard::keyChanged);
701 zwp_input_method_context_v1_commit_string(context, 0,
"a");
704 QVERIFY(keySpy.count() == 2);
706 auto compare = [](
const QList<QVariant> &
input, quint32 key, KWayland::Client::Keyboard::KeyState state) {
707 auto inputKey =
input.at(0).toInt();
708 auto inputState =
input.at(1).value<KWayland::Client::Keyboard::KeyState>();
709 QCOMPARE(inputKey, key);
710 QCOMPARE(inputState, state);
713 compare(keySpy.at(0), KEY_A, KWayland::Client::Keyboard::KeyState::Pressed);
714 compare(keySpy.at(1), KEY_A, KWayland::Client::Keyboard::KeyState::Released);
721 zwp_input_method_context_v1_commit_string(context, 0,
"A");
724 QVERIFY(keySpy.count() == 4);
726 compare(keySpy.at(0), KEY_LEFTSHIFT, KWayland::Client::Keyboard::KeyState::Pressed);
727 compare(keySpy.at(1), KEY_A, KWayland::Client::Keyboard::KeyState::Pressed);
728 compare(keySpy.at(2), KEY_A, KWayland::Client::Keyboard::KeyState::Released);
729 compare(keySpy.at(3), KEY_LEFTSHIFT, KWayland::Client::Keyboard::KeyState::Released);
735 zwp_input_method_context_v1_keysym(context, 0, 0, enter, uint32_t(KeyboardKeyState::Pressed), 0);
736 zwp_input_method_context_v1_keysym(context, 0, 1, enter, uint32_t(KeyboardKeyState::Released), 0);
739 QVERIFY(keySpy.count() == 2);
741 compare(keySpy.at(0), KEY_ENTER, KWayland::Client::Keyboard::KeyState::Pressed);
742 compare(keySpy.at(1), KEY_ENTER, KWayland::Client::Keyboard::KeyState::Released);
744 shellSurface.reset();
746 kwinApp()->inputMethod()->setActive(
false);
750void InputMethodTest::testOverlayPositioning_data()
752 QTest::addColumn<QRect>(
"cursorRectangle");
753 QTest::addColumn<QRect>(
"result");
755 QTest::newRow(
"regular") << QRect(10, 20, 30, 40) << QRect(60, 160, 200, 50);
756 QTest::newRow(
"offscreen-left") << QRect(-200, 40, 30, 40) << QRect(0, 180, 200, 50);
757 QTest::newRow(
"offscreen-right") << QRect(1200, 40, 30, 40) << QRect(1080, 180, 200, 50);
758 QTest::newRow(
"offscreen-top") << QRect(1200, -400, 30, 40) << QRect(1080, 0, 200, 50);
760 QTest::newRow(
"offscreen-bottom-flip") << QRect(1200, 844, 30, 40) << QRect(1080, 894, 200, 50);
762 QTest::newRow(
"offscreen-bottom-slide") << QRect(1200, 1200, 30, 40) << QRect(1080, 974, 200, 50);
765void InputMethodTest::testOverlayPositioning()
767 QFETCH(QRect, cursorRectangle);
768 QFETCH(QRect, result);
784 window->move(QPointF(50, 100));
787 textInput->setCursorRectangle(cursorRectangle);
788 textInput->enable(surface.get());
790 QVERIFY(windowAddedSpy.wait());
792 QCOMPARE(
workspace()->activeWindow(), window);
794 QCOMPARE(windowAddedSpy.count(), 2);
795 QVERIFY(activateSpy.count() || activateSpy.wait());
798 auto keyboardWindow = kwinApp()->inputMethod()->panel();
799 QVERIFY(keyboardWindow);
801 QCOMPARE(keyboardWindow->frameGeometry(), result);
804 shellSurface.reset();
812#include "inputmethod_test.moc"
void setFocusedTextInputSurface(SurfaceInterface *surface)
void preeditString(const QString &text, int cursor_begin, int cursor_end)
void configureRequested(quint32 serial)
void configureRequested(const QSize &size, KWin::Test::XdgToplevel::States states)
void requestShowInputPanel()
SeatInterface * seat() const
void windowRemoved(KWin::Window *)
void windowAdded(KWin::Window *)
QList< Output * > outputs() const
void setActiveOutput(Output *output)
xkb_keysym_t toKeysym(uint32_t key)
#define WAYLANDTEST_MAIN(TestObject)
Window * renderAndWaitForShown(KWayland::Client::Surface *surface, const QSize &size, const QColor &color, const QImage::Format &format=QImage::Format_ARGB32, int timeout=5000)
void keyboardKeyReleased(quint32 key, quint32 time)
void destroyWaylandConnection()
void setOutputConfig(const QList< QRect > &geometries)
KWayland::Client::Seat * seat
void touchDown(qint32 id, const QPointF &pos, quint32 time)
void keyboardKeyPressed(quint32 key, quint32 time)
TextInputManagerV3 * waylandTextInputManagerV3()
bool setupWaylandConnection(AdditionalWaylandInterfaces flags=AdditionalWaylandInterfaces())
void render(KWayland::Client::Surface *surface, const QSize &size, const QColor &color, const QImage::Format &format=QImage::Format_ARGB32_Premultiplied)
KWayland::Client::Seat * waylandSeat()
MockInputMethod * inputMethod()
KWayland::Client::Surface * inputPanelSurface()
QList< KWayland::Client::Output * > outputs
std::unique_ptr< KWayland::Client::Surface > createSurface()
XdgToplevel * createXdgToplevelSurface(KWayland::Client::Surface *surface, QObject *parent=nullptr)
KWayland::Client::TextInputManager * waylandTextInputManager()
void touchUp(qint32 id, quint32 time)
bool waitForWindowClosed(Window *window)
WaylandServer * waylandServer()
InputRedirection * input()