KWin
Loading...
Searching...
No Matches
test_inputmethod_interface.cpp
Go to the documentation of this file.
1/*
2 SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
3 SPDX-FileCopyrightText: 2020 Bhushan Shah <bshah@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6*/
7// Qt
8#include <QHash>
9#include <QSignalSpy>
10#include <QTest>
11#include <QThread>
12
14
15// WaylandServer
16#include "wayland/compositor.h"
17#include "wayland/display.h"
19#include "wayland/output.h"
20#include "wayland/seat.h"
21
22#include "KWayland/Client/compositor.h"
23#include "KWayland/Client/connection_thread.h"
24#include "KWayland/Client/event_queue.h"
25#include "KWayland/Client/keyboard.h"
26#include "KWayland/Client/output.h"
27#include "KWayland/Client/registry.h"
28#include "KWayland/Client/seat.h"
29#include "KWayland/Client/surface.h"
30
31#include "qwayland-input-method-unstable-v1.h"
32#include "qwayland-server-text-input-unstable-v1.h"
33
34#include <linux/input-event-codes.h>
35
36using namespace KWin;
37
38class InputPanelSurface : public QObject, public QtWayland::zwp_input_panel_surface_v1
39{
40 Q_OBJECT
41public:
42 InputPanelSurface(::zwp_input_panel_surface_v1 *t)
43 : QtWayland::zwp_input_panel_surface_v1(t)
44 {
45 }
46};
47
48class InputPanel : public QtWayland::zwp_input_panel_v1
49{
50public:
51 InputPanel(struct ::wl_registry *registry, int id, int version)
52 : QtWayland::zwp_input_panel_v1(registry, id, version)
53 {
54 }
55
56 InputPanelSurface *panelForSurface(KWayland::Client::Surface *surface)
57 {
58 auto panelSurface = new InputPanelSurface(get_input_panel_surface(*surface));
59 QObject::connect(surface, &QObject::destroyed, panelSurface, &QObject::deleteLater);
60 return panelSurface;
61 }
62};
63
64class InputMethodV1Context : public QObject, public QtWayland::zwp_input_method_context_v1
65{
66 Q_OBJECT
67public:
69 {
70 return imPurpose;
71 }
72 quint32 contentHints()
73 {
74 return imHint;
75 }
76Q_SIGNALS:
78 void invoke_action(quint32 button, quint32 index);
79 void preferred_language(QString lang);
80 void surrounding_text(QString lang, quint32 cursor, quint32 anchor);
81 void reset();
82
83protected:
84 void zwp_input_method_context_v1_content_type(uint32_t hint, uint32_t purpose) override
85 {
86 imHint = hint;
87 imPurpose = purpose;
88 Q_EMIT content_type_changed();
89 }
90 void zwp_input_method_context_v1_invoke_action(uint32_t button, uint32_t index) override
91 {
92 Q_EMIT invoke_action(button, index);
93 }
94 void zwp_input_method_context_v1_preferred_language(const QString &language) override
95 {
96 Q_EMIT preferred_language(language);
97 }
98 void zwp_input_method_context_v1_surrounding_text(const QString &text, uint32_t cursor, uint32_t anchor) override
99 {
100 Q_EMIT surrounding_text(text, cursor, anchor);
101 }
103 {
104 Q_EMIT reset();
105 }
106
107private:
108 quint32 imHint = 0;
109 quint32 imPurpose = 0;
110};
111
112class InputMethodV1 : public QObject, public QtWayland::zwp_input_method_v1
113{
114 Q_OBJECT
115public:
116 InputMethodV1(struct ::wl_registry *registry, int id, int version)
117 : QtWayland::zwp_input_method_v1(registry, id, version)
118 {
119 }
121 {
122 return m_context;
123 }
124
125Q_SIGNALS:
126 void activated();
128
129protected:
130 void zwp_input_method_v1_activate(struct ::zwp_input_method_context_v1 *context) override
131 {
132 m_context = new InputMethodV1Context();
133 m_context->init(context);
134 Q_EMIT activated();
135 };
136 void zwp_input_method_v1_deactivate(struct ::zwp_input_method_context_v1 *context) override
137 {
138 delete m_context;
139 m_context = nullptr;
140 Q_EMIT deactivated();
141 };
142
143private:
144 InputMethodV1Context *m_context;
145};
146
147class TestInputMethodInterface : public QObject
148{
149 Q_OBJECT
150public:
154 ~TestInputMethodInterface() override;
155
156private Q_SLOTS:
157 void initTestCase();
158 void testAdd();
159 void testActivate();
160 void testContext();
161 void testGrabkeyboard();
162 void testContentHints_data();
163 void testContentHints();
164 void testContentPurpose_data();
165 void testContentPurpose();
166 void testKeyboardGrab();
167
168private:
169 KWayland::Client::ConnectionThread *m_connection;
170 KWayland::Client::EventQueue *m_queue;
171 KWayland::Client::Compositor *m_clientCompositor;
172 KWayland::Client::Seat *m_clientSeat = nullptr;
173 KWayland::Client::Output *m_output = nullptr;
174
175 InputMethodV1 *m_inputMethod;
176 InputPanel *m_inputPanel;
177 QThread *m_thread;
178 KWin::Display m_display;
179 SeatInterface *m_seat;
180 CompositorInterface *m_serverCompositor;
181 std::unique_ptr<FakeOutput> m_outputHandle;
182 std::unique_ptr<OutputInterface> m_outputInterface;
183
184 KWin::InputMethodV1Interface *m_inputMethodIface;
185 KWin::InputPanelV1Interface *m_inputPanelIface;
186
187 QList<SurfaceInterface *> m_surfaces;
188};
189
190static const QString s_socketName = QStringLiteral("kwin-wayland-server-inputmethod-test-0");
191
192void TestInputMethodInterface::initTestCase()
193{
194 m_display.addSocketName(s_socketName);
195 m_display.start();
196 QVERIFY(m_display.isRunning());
197
198 m_seat = new SeatInterface(&m_display, this);
199 m_serverCompositor = new CompositorInterface(&m_display, this);
200 m_inputMethodIface = new InputMethodV1Interface(&m_display, this);
201 m_inputPanelIface = new InputPanelV1Interface(&m_display, this);
202
203 m_outputHandle = std::make_unique<FakeOutput>();
204 m_outputInterface = std::make_unique<OutputInterface>(&m_display, m_outputHandle.get());
205
206 connect(m_serverCompositor, &CompositorInterface::surfaceCreated, this, [this](SurfaceInterface *surface) {
207 m_surfaces += surface;
208 });
209
210 // setup connection
211 m_connection = new KWayland::Client::ConnectionThread;
212 QSignalSpy connectedSpy(m_connection, &KWayland::Client::ConnectionThread::connected);
213 m_connection->setSocketName(s_socketName);
214
215 m_thread = new QThread(this);
216 m_connection->moveToThread(m_thread);
217 m_thread->start();
218
219 m_connection->initConnection();
220 QVERIFY(connectedSpy.wait());
221 QVERIFY(!m_connection->connections().isEmpty());
222
223 m_queue = new KWayland::Client::EventQueue(this);
224 QVERIFY(!m_queue->isValid());
225 m_queue->setup(m_connection);
226 QVERIFY(m_queue->isValid());
227
228 auto registry = new KWayland::Client::Registry(this);
229 QSignalSpy interfacesSpy(registry, &KWayland::Client::Registry::interfacesAnnounced);
230 connect(registry, &KWayland::Client::Registry::outputAnnounced, this, [this, registry](quint32 name, quint32 version) {
231 m_output = new KWayland::Client::Output(this);
232 m_output->setup(registry->bindOutput(name, version));
233 });
234 connect(registry, &KWayland::Client::Registry::interfaceAnnounced, this, [this, registry](const QByteArray &interface, quint32 name, quint32 version) {
235 if (interface == "zwp_input_panel_v1") {
236 m_inputPanel = new InputPanel(registry->registry(), name, version);
237 } else if (interface == "zwp_input_method_v1") {
238 m_inputMethod = new InputMethodV1(registry->registry(), name, version);
239 }
240 });
241 connect(registry, &KWayland::Client::Registry::seatAnnounced, this, [this, registry](quint32 name, quint32 version) {
242 m_clientSeat = registry->createSeat(name, version);
243 });
244 registry->setEventQueue(m_queue);
245 QSignalSpy compositorSpy(registry, &KWayland::Client::Registry::compositorAnnounced);
246 registry->create(m_connection->display());
247 QVERIFY(registry->isValid());
248 registry->setup();
249 wl_display_flush(m_connection->display());
250
251 QVERIFY(compositorSpy.wait());
252 m_clientCompositor = registry->createCompositor(compositorSpy.first().first().value<quint32>(), compositorSpy.first().last().value<quint32>(), this);
253 QVERIFY(m_clientCompositor->isValid());
254
255 QVERIFY(interfacesSpy.count() || interfacesSpy.wait());
256
257 QSignalSpy surfaceSpy(m_serverCompositor, &CompositorInterface::surfaceCreated);
258 for (int i = 0; i < 3; ++i) {
259 m_clientCompositor->createSurface(this);
260 }
261 QVERIFY(surfaceSpy.count() < 3 && surfaceSpy.wait(200));
262 QVERIFY(m_surfaces.count() == 3);
263 QVERIFY(m_inputPanel);
264 QVERIFY(m_output);
265}
266
268{
269 if (m_queue) {
270 delete m_queue;
271 m_queue = nullptr;
272 }
273 if (m_thread) {
274 m_thread->quit();
275 m_thread->wait();
276 delete m_thread;
277 m_thread = nullptr;
278 }
279 delete m_inputPanel;
280 delete m_inputMethod;
281 delete m_inputMethodIface;
282 delete m_inputPanelIface;
283 m_connection->deleteLater();
284 m_connection = nullptr;
285}
286
287void TestInputMethodInterface::testAdd()
288{
289 QSignalSpy panelSpy(m_inputPanelIface, &InputPanelV1Interface::inputPanelSurfaceAdded);
290 QPointer<InputPanelSurfaceV1Interface> panelSurfaceIface;
291 connect(m_inputPanelIface, &InputPanelV1Interface::inputPanelSurfaceAdded, this, [&panelSurfaceIface](InputPanelSurfaceV1Interface *surface) {
292 panelSurfaceIface = surface;
293 });
294
295 auto surface = m_clientCompositor->createSurface(this);
296 auto panelSurface = m_inputPanel->panelForSurface(surface);
297
298 QVERIFY(panelSpy.wait() || panelSurfaceIface);
299 Q_ASSERT(panelSurfaceIface);
300 Q_ASSERT(panelSurfaceIface->surface() == m_surfaces.constLast());
301
302 QSignalSpy panelTopLevelSpy(panelSurfaceIface, &InputPanelSurfaceV1Interface::topLevel);
303 panelSurface->set_toplevel(*m_output, InputPanelSurface::position_center_bottom);
304 QVERIFY(panelTopLevelSpy.wait());
305}
306
307void TestInputMethodInterface::testActivate()
308{
309 QVERIFY(m_inputMethodIface);
310 QSignalSpy inputMethodActivateSpy(m_inputMethod, &InputMethodV1::activated);
311 QSignalSpy inputMethodDeactivateSpy(m_inputMethod, &InputMethodV1::deactivated);
312
313 // before sending activate the context should be null
314 QVERIFY(!m_inputMethodIface->context());
315
316 // send activate now
317 m_inputMethodIface->sendActivate();
318 QVERIFY(inputMethodActivateSpy.wait());
319 QCOMPARE(inputMethodActivateSpy.count(), 1);
320 QVERIFY(m_inputMethodIface->context());
321
322 // send deactivate and verify server interface resets context
323 m_inputMethodIface->sendDeactivate();
324 QVERIFY(inputMethodDeactivateSpy.wait());
325 QCOMPARE(inputMethodActivateSpy.count(), 1);
326 QVERIFY(!m_inputMethodIface->context());
327}
328
329void TestInputMethodInterface::testContext()
330{
331 QVERIFY(m_inputMethodIface);
332 QSignalSpy inputMethodActivateSpy(m_inputMethod, &InputMethodV1::activated);
333 QSignalSpy inputMethodDeactivateSpy(m_inputMethod, &InputMethodV1::deactivated);
334
335 // before sending activate the context should be null
336 QVERIFY(!m_inputMethodIface->context());
337
338 // send activate now
339 m_inputMethodIface->sendActivate();
340 QVERIFY(inputMethodActivateSpy.wait());
341 QCOMPARE(inputMethodActivateSpy.count(), 1);
342
343 KWin::InputMethodContextV1Interface *serverContext = m_inputMethodIface->context();
344 QVERIFY(serverContext);
345
346 InputMethodV1Context *imContext = m_inputMethod->context();
347 QVERIFY(imContext);
348
349 quint32 serial = 1;
350
351 // commit some text
352 QSignalSpy commitStringSpy(serverContext, &KWin::InputMethodContextV1Interface::commitString);
353 imContext->commit_string(serial, "hello");
354 QVERIFY(commitStringSpy.wait());
355 QCOMPARE(commitStringSpy.count(), serial);
356 QCOMPARE(commitStringSpy.last().at(0).value<quint32>(), serial);
357 QCOMPARE(commitStringSpy.last().at(1).value<QString>(), "hello");
358 serial++;
359
360 // preedit styling event
361 QSignalSpy preeditStylingSpy(serverContext, &KWin::InputMethodContextV1Interface::preeditStyling);
362 // protocol does not document 3rd argument mean in much details (styling)
363 imContext->preedit_styling(0, 5, 1);
364 QVERIFY(preeditStylingSpy.wait());
365 QCOMPARE(preeditStylingSpy.count(), 1);
366 QCOMPARE(preeditStylingSpy.last().at(0).value<quint32>(), 0);
367 QCOMPARE(preeditStylingSpy.last().at(1).value<quint32>(), 5);
368 QCOMPARE(preeditStylingSpy.last().at(2).value<quint32>(), 1);
369
370 // preedit cursor event
371 QSignalSpy preeditCursorSpy(serverContext, &KWin::InputMethodContextV1Interface::preeditCursor);
372 imContext->preedit_cursor(3);
373 QVERIFY(preeditCursorSpy.wait());
374 QCOMPARE(preeditCursorSpy.count(), 1);
375 QCOMPARE(preeditCursorSpy.last().at(0).value<quint32>(), 3);
376
377 // commit preedit_string
378 QSignalSpy preeditStringSpy(serverContext, &KWin::InputMethodContextV1Interface::preeditString);
379 imContext->preedit_string(serial, "hello", "kde");
380 QVERIFY(preeditStringSpy.wait());
381 QCOMPARE(preeditStringSpy.count(), 1);
382 QCOMPARE(preeditStringSpy.last().at(0).value<quint32>(), serial);
383 QCOMPARE(preeditStringSpy.last().at(1).value<QString>(), "hello");
384 QCOMPARE(preeditStringSpy.last().at(2).value<QString>(), "kde");
385 serial++;
386
387 // delete surrounding text
388 QSignalSpy deleteSurroundingSpy(serverContext, &KWin::InputMethodContextV1Interface::deleteSurroundingText);
389 imContext->delete_surrounding_text(0, 5);
390 QVERIFY(deleteSurroundingSpy.wait());
391 QCOMPARE(deleteSurroundingSpy.count(), 1);
392 QCOMPARE(deleteSurroundingSpy.last().at(0).value<quint32>(), 0);
393 QCOMPARE(deleteSurroundingSpy.last().at(1).value<quint32>(), 5);
394
395 // set cursor position
396 QSignalSpy cursorPositionSpy(serverContext, &KWin::InputMethodContextV1Interface::cursorPosition);
397 imContext->cursor_position(2, 4);
398 QVERIFY(cursorPositionSpy.wait());
399 QCOMPARE(cursorPositionSpy.count(), 1);
400 QCOMPARE(cursorPositionSpy.last().at(0).value<quint32>(), 2);
401 QCOMPARE(cursorPositionSpy.last().at(1).value<quint32>(), 4);
402
403 // invoke action
404 QSignalSpy invokeActionSpy(imContext, &InputMethodV1Context::invoke_action);
405 serverContext->sendInvokeAction(3, 5);
406 QVERIFY(invokeActionSpy.wait());
407 QCOMPARE(invokeActionSpy.count(), 1);
408 QCOMPARE(invokeActionSpy.last().at(0).value<quint32>(), 3);
409 QCOMPARE(invokeActionSpy.last().at(1).value<quint32>(), 5);
410
411 // preferred language
412 QSignalSpy preferredLanguageSpy(imContext, &InputMethodV1Context::preferred_language);
413 serverContext->sendPreferredLanguage("gu_IN");
414 QVERIFY(preferredLanguageSpy.wait());
415 QCOMPARE(preferredLanguageSpy.count(), 1);
416 QCOMPARE(preferredLanguageSpy.last().at(0).value<QString>(), "gu_IN");
417
418 // surrounding text
419 QSignalSpy surroundingTextSpy(imContext, &InputMethodV1Context::surrounding_text);
420 serverContext->sendSurroundingText("Hello Plasma!", 2, 4);
421 QVERIFY(surroundingTextSpy.wait());
422 QCOMPARE(surroundingTextSpy.count(), 1);
423 QCOMPARE(surroundingTextSpy.last().at(0).value<QString>(), "Hello Plasma!");
424 QCOMPARE(surroundingTextSpy.last().at(1).value<quint32>(), 2);
425 QCOMPARE(surroundingTextSpy.last().at(2).value<quint32>(), 4);
426
427 // reset
428 QSignalSpy resetSpy(imContext, &InputMethodV1Context::reset);
429 serverContext->sendReset();
430 QVERIFY(resetSpy.wait());
431 QCOMPARE(resetSpy.count(), 1);
432
433 // send deactivate and verify server interface resets context
434 m_inputMethodIface->sendDeactivate();
435 QVERIFY(inputMethodDeactivateSpy.wait());
436 QCOMPARE(inputMethodActivateSpy.count(), 1);
437 QVERIFY(!m_inputMethodIface->context());
438 QVERIFY(!m_inputMethod->context());
439}
440
441void TestInputMethodInterface::testGrabkeyboard()
442{
443 QVERIFY(m_inputMethodIface);
444 QSignalSpy inputMethodActivateSpy(m_inputMethod, &InputMethodV1::activated);
445 QSignalSpy inputMethodDeactivateSpy(m_inputMethod, &InputMethodV1::deactivated);
446
447 // before sending activate the context should be null
448 QVERIFY(!m_inputMethodIface->context());
449
450 // send activate now
451 m_inputMethodIface->sendActivate();
452 QVERIFY(inputMethodActivateSpy.wait());
453 QCOMPARE(inputMethodActivateSpy.count(), 1);
454
455 KWin::InputMethodContextV1Interface *serverContext = m_inputMethodIface->context();
456 QVERIFY(serverContext);
457
458 InputMethodV1Context *imContext = m_inputMethod->context();
459 QVERIFY(imContext);
460
461 QSignalSpy keyEventSpy(serverContext, &KWin::InputMethodContextV1Interface::key);
462 imContext->key(0, 123, 56, 1);
463 QEXPECT_FAIL("", "We should be not get key event if keyboard is not grabbed", Continue);
464 QVERIFY(!keyEventSpy.wait(200));
465
466 QSignalSpy modifierEventSpy(serverContext, &KWin::InputMethodContextV1Interface::modifiers);
467 imContext->modifiers(1234, 0, 0, 0, 0);
468 QEXPECT_FAIL("", "We should be not get modifiers event if keyboard is not grabbed", Continue);
469 QVERIFY(!modifierEventSpy.wait(200));
470
471 // grab the keyboard
472 wl_keyboard *keyboard = imContext->grab_keyboard();
473 QVERIFY(keyboard);
474
475 // TODO: add more tests about keyboard grab here
476
477 // send deactivate and verify server interface resets context
478 m_inputMethodIface->sendDeactivate();
479 QVERIFY(inputMethodDeactivateSpy.wait());
480 QCOMPARE(inputMethodActivateSpy.count(), 1);
481 QVERIFY(!m_inputMethodIface->context());
482 QVERIFY(!m_inputMethod->context());
483}
484
485void TestInputMethodInterface::testContentHints_data()
486{
487 QTest::addColumn<KWin::TextInputContentHints>("serverHints");
488 QTest::addColumn<quint32>("imHint");
489 QTest::addRow("Spellcheck") << TextInputContentHints(TextInputContentHint::AutoCorrection)
490 << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_auto_correction);
491 QTest::addRow("AutoCapital") << TextInputContentHints(TextInputContentHint::AutoCapitalization)
492 << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_auto_capitalization);
493 QTest::addRow("Lowercase") << TextInputContentHints(TextInputContentHint::LowerCase) << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_lowercase);
494 QTest::addRow("Uppercase") << TextInputContentHints(TextInputContentHint::UpperCase) << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_uppercase);
495 QTest::addRow("Titlecase") << TextInputContentHints(TextInputContentHint::TitleCase) << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_titlecase);
496 QTest::addRow("HiddenText") << TextInputContentHints(TextInputContentHint::HiddenText)
497 << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_hidden_text);
498 QTest::addRow("SensitiveData") << TextInputContentHints(TextInputContentHint::SensitiveData)
499 << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_sensitive_data);
500 QTest::addRow("Latin") << TextInputContentHints(TextInputContentHint::Latin) << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_latin);
501 QTest::addRow("Multiline") << TextInputContentHints(TextInputContentHint::MultiLine) << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_multiline);
502 QTest::addRow("Auto") << TextInputContentHints(TextInputContentHint::AutoCorrection | TextInputContentHint::AutoCapitalization)
503 << quint32(QtWaylandServer::zwp_text_input_v1::content_hint_auto_correction
504 | QtWaylandServer::zwp_text_input_v1::content_hint_auto_capitalization);
505}
506
507void TestInputMethodInterface::testContentHints()
508{
509 QVERIFY(m_inputMethodIface);
510 QSignalSpy inputMethodActivateSpy(m_inputMethod, &InputMethodV1::activated);
511 QSignalSpy inputMethodDeactivateSpy(m_inputMethod, &InputMethodV1::deactivated);
512
513 // before sending activate the context should be null
514 QVERIFY(!m_inputMethodIface->context());
515
516 // send activate now
517 m_inputMethodIface->sendActivate();
518 QVERIFY(inputMethodActivateSpy.wait());
519 QCOMPARE(inputMethodActivateSpy.count(), 1);
520
521 KWin::InputMethodContextV1Interface *serverContext = m_inputMethodIface->context();
522 QVERIFY(serverContext);
523
524 InputMethodV1Context *imContext = m_inputMethod->context();
525 QVERIFY(imContext);
526
527 QSignalSpy contentTypeChangedSpy(imContext, &InputMethodV1Context::content_type_changed);
528
529 QFETCH(KWin::TextInputContentHints, serverHints);
530 serverContext->sendContentType(serverHints, KWin::TextInputContentPurpose::Normal);
531 QVERIFY(contentTypeChangedSpy.wait());
532 QCOMPARE(contentTypeChangedSpy.count(), 1);
533 QEXPECT_FAIL("SensitiveData", "SensitiveData content hint need fixing", Continue);
534 QTEST(imContext->contentHints(), "imHint");
535
536 // send deactivate and verify server interface resets context
537 m_inputMethodIface->sendDeactivate();
538 QVERIFY(inputMethodDeactivateSpy.wait());
539 QCOMPARE(inputMethodActivateSpy.count(), 1);
540 QVERIFY(!m_inputMethodIface->context());
541 QVERIFY(!m_inputMethod->context());
542}
543
544void TestInputMethodInterface::testContentPurpose_data()
545{
546 QTest::addColumn<KWin::TextInputContentPurpose>("serverPurpose");
547 QTest::addColumn<quint32>("imPurpose");
548
549 QTest::newRow("Alpha") << TextInputContentPurpose::Alpha << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_alpha);
550 QTest::newRow("Digits") << TextInputContentPurpose::Digits << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_digits);
551 QTest::newRow("Number") << TextInputContentPurpose::Number << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_number);
552 QTest::newRow("Phone") << TextInputContentPurpose::Phone << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_phone);
553 QTest::newRow("Url") << TextInputContentPurpose::Url << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_url);
554 QTest::newRow("Email") << TextInputContentPurpose::Email << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_email);
555 QTest::newRow("Name") << TextInputContentPurpose::Name << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_name);
556 QTest::newRow("Password") << TextInputContentPurpose::Password << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_password);
557 QTest::newRow("Date") << TextInputContentPurpose::Date << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_date);
558 QTest::newRow("Time") << TextInputContentPurpose::Time << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_time);
559 QTest::newRow("DateTime") << TextInputContentPurpose::DateTime << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_datetime);
560 QTest::newRow("Terminal") << TextInputContentPurpose::Terminal << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_terminal);
561 QTest::newRow("Normal") << TextInputContentPurpose::Normal << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_normal);
562 QTest::newRow("Pin") << TextInputContentPurpose::Pin << quint32(QtWaylandServer::zwp_text_input_v1::content_purpose_password);
563}
564
565void TestInputMethodInterface::testContentPurpose()
566{
567 QVERIFY(m_inputMethodIface);
568 QSignalSpy inputMethodActivateSpy(m_inputMethod, &InputMethodV1::activated);
569 QSignalSpy inputMethodDeactivateSpy(m_inputMethod, &InputMethodV1::deactivated);
570
571 // before sending activate the context should be null
572 QVERIFY(!m_inputMethodIface->context());
573
574 // send activate now
575 m_inputMethodIface->sendActivate();
576 QVERIFY(inputMethodActivateSpy.wait());
577 QCOMPARE(inputMethodActivateSpy.count(), 1);
578
579 KWin::InputMethodContextV1Interface *serverContext = m_inputMethodIface->context();
580 QVERIFY(serverContext);
581
582 InputMethodV1Context *imContext = m_inputMethod->context();
583 QVERIFY(imContext);
584
585 QSignalSpy contentTypeChangedSpy(imContext, &InputMethodV1Context::content_type_changed);
586
587 QFETCH(KWin::TextInputContentPurpose, serverPurpose);
588 serverContext->sendContentType(KWin::TextInputContentHints(KWin::TextInputContentHint::None), serverPurpose);
589 QVERIFY(contentTypeChangedSpy.wait());
590 QCOMPARE(contentTypeChangedSpy.count(), 1);
591 QEXPECT_FAIL("Pin", "Pin should return content_purpose_password", Continue);
592 QTEST(imContext->contentPurpose(), "imPurpose");
593
594 // send deactivate and verify server interface resets context
595 m_inputMethodIface->sendDeactivate();
596 QVERIFY(inputMethodDeactivateSpy.wait());
597 QCOMPARE(inputMethodActivateSpy.count(), 1);
598 QVERIFY(!m_inputMethodIface->context());
599 QVERIFY(!m_inputMethod->context());
600}
601
602void TestInputMethodInterface::testKeyboardGrab()
603{
604 QVERIFY(m_inputMethodIface);
605 QSignalSpy inputMethodActivateSpy(m_inputMethod, &InputMethodV1::activated);
606
607 m_inputMethodIface->sendActivate();
608 QVERIFY(inputMethodActivateSpy.wait());
609
610 QSignalSpy keyboardGrabSpy(m_inputMethodIface->context(), &InputMethodContextV1Interface::keyboardGrabRequested);
611 InputMethodV1Context *imContext = m_inputMethod->context();
612 QVERIFY(imContext);
613 KWayland::Client::Keyboard *keyboard = new KWayland::Client::Keyboard(this);
614 keyboard->setup(imContext->grab_keyboard());
615 QVERIFY(keyboard->isValid());
616 QVERIFY(keyboardGrabSpy.count() || keyboardGrabSpy.wait());
617
618 QSignalSpy keyboardSpy(keyboard, &KWayland::Client::Keyboard::keyChanged);
619 m_inputMethodIface->context()->keyboardGrab()->sendKey(0, 0, KEY_F1, KeyboardKeyState::Pressed);
620 m_inputMethodIface->context()->keyboardGrab()->sendKey(0, 0, KEY_F1, KeyboardKeyState::Released);
621 keyboardSpy.wait();
622 QCOMPARE(keyboardSpy.count(), 2);
623
624 m_inputMethodIface->sendDeactivate();
625}
626
627QTEST_GUILESS_MAIN(TestInputMethodInterface)
628#include "test_inputmethod_interface.moc"
void preferred_language(QString lang)
void surrounding_text(QString lang, quint32 cursor, quint32 anchor)
void zwp_input_method_context_v1_content_type(uint32_t hint, uint32_t purpose) override
void zwp_input_method_context_v1_invoke_action(uint32_t button, uint32_t index) override
void zwp_input_method_context_v1_reset() override
void invoke_action(quint32 button, quint32 index)
void zwp_input_method_context_v1_surrounding_text(const QString &text, uint32_t cursor, uint32_t anchor) override
void zwp_input_method_context_v1_preferred_language(const QString &language) override
void deactivated()
void zwp_input_method_v1_activate(struct ::zwp_input_method_context_v1 *context) override
InputMethodV1Context * context()
void activated()
void zwp_input_method_v1_deactivate(struct ::zwp_input_method_context_v1 *context) override
InputMethodV1(struct ::wl_registry *registry, int id, int version)
InputPanelSurface * panelForSurface(KWayland::Client::Surface *surface)
InputPanel(struct ::wl_registry *registry, int id, int version)
InputPanelSurface(::zwp_input_panel_surface_v1 *t)
void surfaceCreated(KWin::SurfaceInterface *surface)
Class holding the Wayland server display loop.
Definition display.h:34
bool addSocketName(const QString &name=QString())
Definition display.cpp:68
bool isRunning() const
Definition display.cpp:144
bool start()
Definition display.cpp:92
void deleteSurroundingText(qint32 index, quint32 length)
InputMethodGrabV1 * keyboardGrab() const
void sendSurroundingText(const QString &text, quint32 cursor, quint32 anchor)
void sendInvokeAction(quint32 button, quint32 index)
void preeditString(quint32 serial, const QString &text, const QString &commit)
void key(quint32 serial, quint32 time, quint32 key, bool pressed)
void modifiers(quint32 serial, quint32 mods_depressed, quint32 mods_latched, quint32 mods_locked, quint32 group)
void cursorPosition(qint32 index, qint32 anchor)
void sendContentType(KWin::TextInputContentHints hint, KWin::TextInputContentPurpose purpose)
void keyboardGrabRequested(InputMethodGrabV1 *keyboardGrab)
void sendPreferredLanguage(const QString &language)
void preeditStyling(quint32 index, quint32 length, quint32 style)
void commitString(quint32 serial, const QString &text)
void sendKey(quint32 serial, quint32 timestamp, quint32 key, KeyboardKeyState state)
InputMethodContextV1Interface * context() const
void topLevel(OutputInterface *output, Position position)
void inputPanelSurfaceAdded(InputPanelSurfaceV1Interface *surface)
Represents a Seat on the Wayland Display.
Definition seat.h:134
Resource representing a wl_surface.
Definition surface.h:80
KWayland::Client::Registry * registry
constexpr int version
TextInputContentPurpose
Definition textinput.h:72