1 /*
2     SPDX-FileCopyrightText: 2021 Roman Gilg <subdiff@gmail.com>
3 
4     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only
5 */
6 #include <QtTest>
7 
8 #include "../../src/client/compositor.h"
9 #include "../../src/client/connection_thread.h"
10 #include "../../src/client/event_queue.h"
11 #include "../../src/client/input_method_v2.h"
12 #include "../../src/client/registry.h"
13 #include "../../src/client/seat.h"
14 #include "../../src/client/surface.h"
15 
16 #include "../../server/compositor.h"
17 #include "../../server/display.h"
18 #include "../../server/input_method_v2.h"
19 #include "../../server/seat.h"
20 #include "../../server/surface.h"
21 
22 #include <linux/input.h>
23 
24 class input_method_v2_test : public QObject
25 {
26     Q_OBJECT
27 private Q_SLOTS:
28     void init();
29     void cleanup();
30 
31     void test_activate();
32     void test_surrounding_text();
33     void test_content_hints_data();
34     void test_content_hints();
35     void test_content_purpose_data();
36     void test_content_purpose();
37     void test_commit();
38     void test_popup_surface();
39     void test_keyboard_grab();
40 
41 private:
42     Wrapland::Client::input_method_v2* get_input_method();
43 
44     struct {
45         Wrapland::Server::Display* display{nullptr};
46         Wrapland::Server::Compositor* compositor{nullptr};
47         Wrapland::Server::Seat* seat{nullptr};
48         Wrapland::Server::text_input_manager_v3* text_input{nullptr};
49         Wrapland::Server::input_method_manager_v2* input_method{nullptr};
50     } server;
51 
52     struct client {
53         Wrapland::Client::ConnectionThread* connection{nullptr};
54         Wrapland::Client::EventQueue* queue{nullptr};
55         Wrapland::Client::Registry* registry{nullptr};
56         Wrapland::Client::Compositor* compositor{nullptr};
57         Wrapland::Client::Seat* seat{nullptr};
58         Wrapland::Client::text_input_manager_v3* text_input{nullptr};
59         Wrapland::Client::input_method_manager_v2* input_method{nullptr};
60         QThread* thread{nullptr};
61     } client1;
62 };
63 
64 constexpr auto socket_name = "wrapland-test-input-method-v2-0";
65 
init()66 void input_method_v2_test::init()
67 {
68     qRegisterMetaType<Wrapland::Server::Surface*>();
69 
70     delete server.display;
71     server.display = new Wrapland::Server::Display(this);
72     server.display->setSocketName(std::string(socket_name));
73     server.display->start();
74 
75     server.display->createShm();
76     server.seat = server.display->createSeat();
77     server.seat->setHasKeyboard(true);
78 
79     server.compositor = server.display->createCompositor();
80     server.text_input = server.display->createTextInputManagerV3();
81     server.input_method = server.display->createInputMethodManagerV2();
82 
83     // setup connection
84     client1.connection = new Wrapland::Client::ConnectionThread;
85     QSignalSpy connectedSpy(client1.connection,
86                             &Wrapland::Client::ConnectionThread::establishedChanged);
87     QVERIFY(connectedSpy.isValid());
88     client1.connection->setSocketName(socket_name);
89 
90     client1.thread = new QThread(this);
91     client1.connection->moveToThread(client1.thread);
92     client1.thread->start();
93 
94     client1.connection->establishConnection();
95     QVERIFY(connectedSpy.count() || connectedSpy.wait());
96     QCOMPARE(connectedSpy.count(), 1);
97 
98     client1.queue = new Wrapland::Client::EventQueue(this);
99     client1.queue->setup(client1.connection);
100 
101     Wrapland::Client::Registry registry;
102     QSignalSpy interfacesAnnouncedSpy(&registry, &Wrapland::Client::Registry::interfacesAnnounced);
103     QVERIFY(interfacesAnnouncedSpy.isValid());
104     registry.setEventQueue(client1.queue);
105     registry.create(client1.connection);
106     QVERIFY(registry.isValid());
107     registry.setup();
108     QVERIFY(interfacesAnnouncedSpy.wait());
109 
110     client1.seat = registry.createSeat(
111         registry.interface(Wrapland::Client::Registry::Interface::Seat).name,
112         registry.interface(Wrapland::Client::Registry::Interface::Seat).version,
113         this);
114     QVERIFY(client1.seat->isValid());
115 
116     client1.compositor = registry.createCompositor(
117         registry.interface(Wrapland::Client::Registry::Interface::Compositor).name,
118         registry.interface(Wrapland::Client::Registry::Interface::Compositor).version,
119         this);
120     QVERIFY(client1.compositor->isValid());
121 
122     client1.text_input = registry.createTextInputManagerV3(
123         registry.interface(Wrapland::Client::Registry::Interface::TextInputManagerV3).name,
124         registry.interface(Wrapland::Client::Registry::Interface::TextInputManagerV3).version,
125         this);
126     QVERIFY(client1.text_input->isValid());
127 
128     client1.input_method = registry.createInputMethodManagerV2(
129         registry.interface(Wrapland::Client::Registry::Interface::InputMethodManagerV2).name,
130         registry.interface(Wrapland::Client::Registry::Interface::InputMethodManagerV2).version,
131         this);
132     QVERIFY(client1.input_method->isValid());
133 }
134 
cleanup()135 void input_method_v2_test::cleanup()
136 {
137 #define CLEANUP(variable)                                                                          \
138     if (variable) {                                                                                \
139         delete variable;                                                                           \
140         variable = nullptr;                                                                        \
141     }
142     CLEANUP(client1.input_method)
143     CLEANUP(client1.text_input)
144     CLEANUP(client1.seat)
145     CLEANUP(client1.compositor)
146     CLEANUP(client1.queue)
147     if (client1.connection) {
148         client1.connection->deleteLater();
149         client1.connection = nullptr;
150     }
151     if (client1.thread) {
152         client1.thread->quit();
153         client1.thread->wait();
154         delete client1.thread;
155         client1.thread = nullptr;
156     }
157 
158     CLEANUP(server.input_method)
159     CLEANUP(server.text_input)
160     CLEANUP(server.compositor)
161     CLEANUP(server.seat)
162     CLEANUP(server.display)
163 #undef CLEANUP
164 }
165 
get_input_method()166 Wrapland::Client::input_method_v2* input_method_v2_test::get_input_method()
167 {
168     return client1.input_method->get_input_method(client1.seat);
169 }
170 
test_activate()171 void input_method_v2_test::test_activate()
172 {
173     // This test verifies that activation is done correctly.
174     QSignalSpy input_method_spy(server.seat, &Wrapland::Server::Seat::input_method_v2_changed);
175     QVERIFY(input_method_spy.isValid());
176 
177     auto input_method = std::unique_ptr<Wrapland::Client::input_method_v2>(get_input_method());
178     QVERIFY(input_method);
179     QVERIFY(input_method_spy.wait());
180 
181     auto server_input_method = server.seat->get_input_method_v2();
182     QVERIFY(server_input_method);
183 
184     QSignalSpy done_spy(input_method.get(), &Wrapland::Client::input_method_v2::done);
185     QVERIFY(done_spy.isValid());
186 
187     // Now activate.
188     server_input_method->set_active(true);
189     server_input_method->done();
190 
191     QVERIFY(done_spy.wait());
192     QCOMPARE(input_method->state().active, true);
193 
194     // Now deactivate.
195     server_input_method->set_active(false);
196     server_input_method->done();
197 
198     QVERIFY(done_spy.wait());
199     QCOMPARE(input_method->state().active, false);
200 }
201 
test_surrounding_text()202 void input_method_v2_test::test_surrounding_text()
203 {
204     // This test verifies that surrounding text is sent correctly.
205     QSignalSpy input_method_spy(server.seat, &Wrapland::Server::Seat::input_method_v2_changed);
206     QVERIFY(input_method_spy.isValid());
207 
208     auto input_method = std::unique_ptr<Wrapland::Client::input_method_v2>(get_input_method());
209     QVERIFY(input_method);
210     QVERIFY(input_method_spy.wait());
211 
212     auto server_input_method = server.seat->get_input_method_v2();
213     QVERIFY(server_input_method);
214 
215     QSignalSpy done_spy(input_method.get(), &Wrapland::Client::input_method_v2::done);
216     QVERIFY(done_spy.isValid());
217 
218     QVERIFY(input_method->state().surrounding_text.data.empty());
219     QCOMPARE(input_method->state().surrounding_text.cursor_position, 0);
220     QCOMPARE(input_method->state().surrounding_text.selection_anchor, 0);
221     QCOMPARE(input_method->state().surrounding_text.change_cause,
222              Wrapland::Client::text_input_v3_change_cause::other);
223 
224     // Now activate and send surrounding text.
225     auto const surrounding_text = "100 €, 100 $";
226 
227     server_input_method->set_active(true);
228     server_input_method->set_surrounding_text(
229         surrounding_text, 5, 6, Wrapland::Server::text_input_v3_change_cause::input_method);
230     server_input_method->done();
231 
232     QVERIFY(done_spy.wait());
233     QCOMPARE(input_method->state().active, true);
234     QCOMPARE(input_method->state().surrounding_text.data, surrounding_text);
235     QCOMPARE(input_method->state().surrounding_text.cursor_position, 5);
236     QCOMPARE(input_method->state().surrounding_text.selection_anchor, 6);
237     QCOMPARE(input_method->state().surrounding_text.change_cause,
238              Wrapland::Client::text_input_v3_change_cause::input_method);
239 
240     // Now deactivate.
241     server_input_method->set_active(false);
242     server_input_method->done();
243 
244     // Not active anymore but surrounding text data still available.
245     QVERIFY(done_spy.wait());
246     QCOMPARE(input_method->state().active, false);
247     QCOMPARE(input_method->state().surrounding_text.data, surrounding_text);
248     QCOMPARE(input_method->state().surrounding_text.cursor_position, 5);
249     QCOMPARE(input_method->state().surrounding_text.selection_anchor, 6);
250     QCOMPARE(input_method->state().surrounding_text.change_cause,
251              Wrapland::Client::text_input_v3_change_cause::input_method);
252 
253     // Activate again but do not set surrounding text.
254     server_input_method->set_active(true);
255     server_input_method->done();
256 
257     // Active again and state reset now.
258     QVERIFY(done_spy.wait());
259     QVERIFY(input_method->state().surrounding_text.data.empty());
260     QCOMPARE(input_method->state().surrounding_text.cursor_position, 0);
261     QCOMPARE(input_method->state().surrounding_text.selection_anchor, 0);
262     QCOMPARE(input_method->state().surrounding_text.change_cause,
263              Wrapland::Client::text_input_v3_change_cause::other);
264 }
265 
266 using server_hint = Wrapland::Server::text_input_v3_content_hint;
267 using server_hints = Wrapland::Server::text_input_v3_content_hints;
268 using client_hint = Wrapland::Client::text_input_v3_content_hint;
269 using client_hints = Wrapland::Client::text_input_v3_content_hints;
270 
271 using server_purpose = Wrapland::Server::text_input_v3_content_purpose;
272 using client_purpose = Wrapland::Client::text_input_v3_content_purpose;
273 
test_content_hints_data()274 void input_method_v2_test::test_content_hints_data()
275 {
276     QTest::addColumn<client_hints>("clthints");
277     QTest::addColumn<server_hints>("srvhints");
278 
279     QTest::newRow("completion") << client_hints(client_hint::completion)
280                                 << server_hints(server_hint::completion);
281     QTest::newRow("Correction") << client_hints(client_hint::spellcheck)
282                                 << server_hints(server_hint::spellcheck);
283     QTest::newRow("Capitalization") << client_hints(client_hint::auto_capitalization)
284                                     << server_hints(server_hint::auto_capitalization);
285     QTest::newRow("lowercase") << client_hints(client_hint::lowercase)
286                                << server_hints(server_hint::lowercase);
287     QTest::newRow("uppercase") << client_hints(client_hint::uppercase)
288                                << server_hints(server_hint::uppercase);
289     QTest::newRow("titlecase") << client_hints(client_hint::titlecase)
290                                << server_hints(server_hint::titlecase);
291     QTest::newRow("hidden_text") << client_hints(client_hint::hidden_text)
292                                  << server_hints(server_hint::hidden_text);
293     QTest::newRow("sensitive_data")
294         << client_hints(client_hint::sensitive_data) << server_hints(server_hint::sensitive_data);
295     QTest::newRow("latin") << client_hints(client_hint::latin) << server_hints(server_hint::latin);
296     QTest::newRow("multiline") << client_hints(client_hint::multiline)
297                                << server_hints(server_hint::multiline);
298 
299     QTest::newRow("autos") << (client_hint::completion | client_hint::spellcheck
300                                | client_hint::auto_capitalization)
301                            << (server_hint::completion | server_hint::spellcheck
302                                | server_hint::auto_capitalization);
303 
304     // all has combinations which don't make sense - for example both lowercase and uppercase.
305     QTest::newRow("all") << (client_hint::completion | client_hint::spellcheck
306                              | client_hint::auto_capitalization | client_hint::lowercase
307                              | client_hint::uppercase | client_hint::titlecase
308                              | client_hint::hidden_text | client_hint::sensitive_data
309                              | client_hint::latin | client_hint::multiline)
310                          << (server_hint::completion | server_hint::spellcheck
311                              | server_hint::auto_capitalization | server_hint::lowercase
312                              | server_hint::uppercase | server_hint::titlecase
313                              | server_hint::hidden_text | server_hint::sensitive_data
314                              | server_hint::latin | server_hint::multiline);
315 }
316 
test_content_hints()317 void input_method_v2_test::test_content_hints()
318 {
319     // This test verifies that content hints are sent correctly.
320     QSignalSpy input_method_spy(server.seat, &Wrapland::Server::Seat::input_method_v2_changed);
321     QVERIFY(input_method_spy.isValid());
322 
323     auto input_method = std::unique_ptr<Wrapland::Client::input_method_v2>(get_input_method());
324     QVERIFY(input_method);
325     QVERIFY(input_method_spy.wait());
326 
327     auto server_input_method = server.seat->get_input_method_v2();
328     QVERIFY(server_input_method);
329 
330     QSignalSpy done_spy(input_method.get(), &Wrapland::Client::input_method_v2::done);
331     QVERIFY(done_spy.isValid());
332 
333     QCOMPARE(input_method->state().content.hints, client_hints());
334 
335     // Now activate and send content hints.
336     server_input_method->set_active(true);
337     QFETCH(server_hints, srvhints);
338     QFETCH(client_hints, clthints);
339     server_input_method->set_content_type(srvhints, server_purpose::normal);
340     server_input_method->done();
341 
342     QVERIFY(done_spy.wait());
343     QCOMPARE(input_method->state().active, true);
344     QCOMPARE(input_method->state().content.hints, clthints);
345 
346     // Now deactivate.
347     server_input_method->set_active(false);
348     server_input_method->done();
349 
350     // Not active anymore but content hints data still available.
351     QVERIFY(done_spy.wait());
352     QCOMPARE(input_method->state().active, false);
353     QCOMPARE(input_method->state().content.hints, clthints);
354 
355     // Activate again but do not set content hints.
356     server_input_method->set_active(true);
357     server_input_method->done();
358 
359     // Active again and state reset now.
360     QVERIFY(done_spy.wait());
361     QCOMPARE(input_method->state().content.hints, client_hints());
362 }
363 
test_content_purpose_data()364 void input_method_v2_test::test_content_purpose_data()
365 {
366     QTest::addColumn<client_purpose>("cltpurpose");
367     QTest::addColumn<server_purpose>("srvpurpose");
368 
369     QTest::newRow("alpha") << client_purpose::alpha << server_purpose::alpha;
370     QTest::newRow("digits") << client_purpose::digits << server_purpose::digits;
371     QTest::newRow("number") << client_purpose::number << server_purpose::number;
372     QTest::newRow("phone") << client_purpose::phone << server_purpose::phone;
373     QTest::newRow("url") << client_purpose::url << server_purpose::url;
374     QTest::newRow("email") << client_purpose::email << server_purpose::email;
375     QTest::newRow("name") << client_purpose::name << server_purpose::name;
376     QTest::newRow("password") << client_purpose::password << server_purpose::password;
377     QTest::newRow("date") << client_purpose::date << server_purpose::date;
378     QTest::newRow("time") << client_purpose::time << server_purpose::time;
379     QTest::newRow("datetime") << client_purpose::datetime << server_purpose::datetime;
380     QTest::newRow("terminal") << client_purpose::terminal << server_purpose::terminal;
381 }
382 
test_content_purpose()383 void input_method_v2_test::test_content_purpose()
384 {
385     // This test verifies that the content purpose is sent correctly.
386     QSignalSpy input_method_spy(server.seat, &Wrapland::Server::Seat::input_method_v2_changed);
387     QVERIFY(input_method_spy.isValid());
388 
389     auto input_method = std::unique_ptr<Wrapland::Client::input_method_v2>(get_input_method());
390     QVERIFY(input_method);
391     QVERIFY(input_method_spy.wait());
392 
393     auto server_input_method = server.seat->get_input_method_v2();
394     QVERIFY(server_input_method);
395 
396     QSignalSpy done_spy(input_method.get(), &Wrapland::Client::input_method_v2::done);
397     QVERIFY(done_spy.isValid());
398 
399     QCOMPARE(input_method->state().content.purpose, client_purpose::normal);
400 
401     // Now activate and send content hints.
402     server_input_method->set_active(true);
403     QFETCH(server_purpose, srvpurpose);
404     QFETCH(client_purpose, cltpurpose);
405     server_input_method->set_content_type(server_hints(), srvpurpose);
406     server_input_method->done();
407 
408     QVERIFY(done_spy.wait());
409     QCOMPARE(input_method->state().active, true);
410     QCOMPARE(input_method->state().content.purpose, cltpurpose);
411 
412     // Now deactivate.
413     server_input_method->set_active(false);
414     server_input_method->done();
415 
416     // Not active anymore but content hints data still available.
417     QVERIFY(done_spy.wait());
418     QCOMPARE(input_method->state().active, false);
419     QCOMPARE(input_method->state().content.purpose, cltpurpose);
420 
421     // Activate again but do not set content hints.
422     server_input_method->set_active(true);
423     server_input_method->done();
424 
425     // Active again and state reset now.
426     QVERIFY(done_spy.wait());
427     QCOMPARE(input_method->state().content.purpose, client_purpose::normal);
428 }
429 
test_commit()430 void input_method_v2_test::test_commit()
431 {
432     // This test verifies that commit string, preedit and delete surrounding text is sent correctly.
433     QSignalSpy input_method_spy(server.seat, &Wrapland::Server::Seat::input_method_v2_changed);
434     QVERIFY(input_method_spy.isValid());
435 
436     auto input_method = std::unique_ptr<Wrapland::Client::input_method_v2>(get_input_method());
437     QVERIFY(input_method);
438     QVERIFY(input_method_spy.wait());
439 
440     auto server_input_method = server.seat->get_input_method_v2();
441     QVERIFY(server_input_method);
442 
443     QSignalSpy done_spy(input_method.get(), &Wrapland::Client::input_method_v2::done);
444     QVERIFY(done_spy.isValid());
445 
446     QCOMPARE(input_method->state().content.purpose, client_purpose::normal);
447 
448     // Now activate and send surrounding text to indicate support for it.
449     server_input_method->set_active(true);
450     server_input_method->set_surrounding_text(
451         "", 0, 0, Wrapland::Server::text_input_v3_change_cause::other);
452     server_input_method->done();
453 
454     QVERIFY(done_spy.wait());
455     QCOMPARE(input_method->state().active, true);
456     QVERIFY(input_method->state().surrounding_text.data.empty());
457     QCOMPARE(input_method->state().surrounding_text.cursor_position, 0);
458     QCOMPARE(input_method->state().surrounding_text.selection_anchor, 0);
459     QCOMPARE(input_method->state().surrounding_text.change_cause,
460              Wrapland::Client::text_input_v3_change_cause::other);
461 
462     QSignalSpy commit_spy(server_input_method, &Wrapland::Server::input_method_v2::state_committed);
463     QVERIFY(commit_spy.isValid());
464 
465     // Now send data.
466     auto commit_text = "commit string text";
467     auto preedit_text = "preedit string text";
468     input_method->commit_string(commit_text);
469     input_method->set_preedit_string(preedit_text, 1, 2);
470     input_method->commit();
471 
472     QCOMPARE(server_input_method->state().preedit_string.update, false);
473     QCOMPARE(server_input_method->state().preedit_string.data, "");
474     QCOMPARE(server_input_method->state().preedit_string.cursor_begin, 0);
475     QCOMPARE(server_input_method->state().preedit_string.cursor_end, 0);
476     QCOMPARE(server_input_method->state().commit_string.update, false);
477     QCOMPARE(server_input_method->state().commit_string.data, "");
478     QCOMPARE(server_input_method->state().delete_surrounding_text.update, false);
479     QCOMPARE(server_input_method->state().delete_surrounding_text.before_length, 0);
480     QCOMPARE(server_input_method->state().delete_surrounding_text.after_length, 0);
481 
482     QVERIFY(commit_spy.wait());
483     QCOMPARE(server_input_method->state().preedit_string.update, true);
484     QCOMPARE(server_input_method->state().preedit_string.data, preedit_text);
485     QCOMPARE(server_input_method->state().preedit_string.cursor_begin, 1);
486     QCOMPARE(server_input_method->state().preedit_string.cursor_end, 2);
487     QCOMPARE(server_input_method->state().commit_string.update, true);
488     QCOMPARE(server_input_method->state().commit_string.data, commit_text);
489     QCOMPARE(server_input_method->state().delete_surrounding_text.update, false);
490     QCOMPARE(server_input_method->state().delete_surrounding_text.before_length, 0);
491     QCOMPARE(server_input_method->state().delete_surrounding_text.after_length, 0);
492 
493     // Now deactivate.
494     server_input_method->set_active(false);
495     server_input_method->done();
496 
497     // Not active anymore but data is still available.
498     QVERIFY(done_spy.wait());
499     QCOMPARE(server_input_method->state().preedit_string.update, true);
500     QCOMPARE(server_input_method->state().preedit_string.data, preedit_text);
501     QCOMPARE(server_input_method->state().preedit_string.cursor_begin, 1);
502     QCOMPARE(server_input_method->state().preedit_string.cursor_end, 2);
503     QCOMPARE(server_input_method->state().commit_string.update, true);
504     QCOMPARE(server_input_method->state().commit_string.data, commit_text);
505     QCOMPARE(server_input_method->state().delete_surrounding_text.update, false);
506     QCOMPARE(server_input_method->state().delete_surrounding_text.before_length, 0);
507     QCOMPARE(server_input_method->state().delete_surrounding_text.after_length, 0);
508 
509     // Activate again. Data is reset immediately.
510     server_input_method->set_active(true);
511     QCOMPARE(server_input_method->state().preedit_string.update, false);
512     QCOMPARE(server_input_method->state().preedit_string.data, "");
513     QCOMPARE(server_input_method->state().preedit_string.cursor_begin, 0);
514     QCOMPARE(server_input_method->state().preedit_string.cursor_end, 0);
515     QCOMPARE(server_input_method->state().commit_string.update, false);
516     QCOMPARE(server_input_method->state().commit_string.data, "");
517     QCOMPARE(server_input_method->state().delete_surrounding_text.update, false);
518     QCOMPARE(server_input_method->state().delete_surrounding_text.before_length, 0);
519     QCOMPARE(server_input_method->state().delete_surrounding_text.after_length, 0);
520 
521     // Set surrounding text for testing deletion of surrounding text.
522     auto const surrounding_text = "100 €, 100 $";
523 
524     server_input_method->set_active(true);
525     server_input_method->set_surrounding_text(
526         surrounding_text, 5, 6, Wrapland::Server::text_input_v3_change_cause::input_method);
527     server_input_method->done();
528 
529     QVERIFY(done_spy.wait());
530     QCOMPARE(input_method->state().active, true);
531     QCOMPARE(input_method->state().surrounding_text.data, surrounding_text);
532     QCOMPARE(input_method->state().surrounding_text.cursor_position, 5);
533     QCOMPARE(input_method->state().surrounding_text.selection_anchor, 6);
534     QCOMPARE(input_method->state().surrounding_text.change_cause,
535              Wrapland::Client::text_input_v3_change_cause::input_method);
536 
537     input_method->delete_surrounding_text(1, 2);
538     input_method->commit();
539 
540     QVERIFY(commit_spy.wait());
541     QCOMPARE(server_input_method->state().preedit_string.update, false);
542     QCOMPARE(server_input_method->state().preedit_string.data, "");
543     QCOMPARE(server_input_method->state().preedit_string.cursor_begin, 0);
544     QCOMPARE(server_input_method->state().preedit_string.cursor_end, 0);
545     QCOMPARE(server_input_method->state().commit_string.update, false);
546     QCOMPARE(server_input_method->state().commit_string.data, "");
547     QCOMPARE(server_input_method->state().delete_surrounding_text.update, true);
548     QCOMPARE(server_input_method->state().delete_surrounding_text.before_length, 1);
549     QCOMPARE(server_input_method->state().delete_surrounding_text.after_length, 2);
550 }
551 
test_popup_surface()552 void input_method_v2_test::test_popup_surface()
553 {
554     // This test verifies that the popup surface works as expected.
555     auto surface = std::unique_ptr<Wrapland::Client::Surface>(client1.compositor->createSurface());
556     QSignalSpy input_method_spy(server.seat, &Wrapland::Server::Seat::input_method_v2_changed);
557     QVERIFY(input_method_spy.isValid());
558 
559     auto input_method = std::unique_ptr<Wrapland::Client::input_method_v2>(get_input_method());
560     QVERIFY(input_method);
561     QVERIFY(input_method_spy.wait());
562 
563     auto server_input_method = server.seat->get_input_method_v2();
564     QVERIFY(server_input_method);
565 
566     QSignalSpy popup_spy(server_input_method,
567                          &Wrapland::Server::input_method_v2::popup_surface_created);
568     QVERIFY(popup_spy.isValid());
569 
570     auto popup = std::unique_ptr<Wrapland::Client::input_popup_surface_v2>(
571         input_method->get_input_popup_surface(surface.get()));
572 
573     QVERIFY(popup_spy.wait());
574     auto server_popup
575         = popup_spy.first().first().value<Wrapland::Server::input_method_popup_surface_v2*>();
576 
577     QSignalSpy rect_spy(popup.get(),
578                         &Wrapland::Client::input_popup_surface_v2::text_input_rectangle_changed);
579     QVERIFY(rect_spy.isValid());
580 
581     auto rect = QRect(1, 2, 3, 4);
582     server_popup->set_text_input_rectangle(rect);
583     QVERIFY(rect_spy.wait());
584 
585     QCOMPARE(popup->text_input_rectangle(), rect);
586 }
587 
test_keyboard_grab()588 void input_method_v2_test::test_keyboard_grab()
589 {
590     // This test verifies that a keyboard grab works as expected.
591     auto surface = std::unique_ptr<Wrapland::Client::Surface>(client1.compositor->createSurface());
592     QSignalSpy input_method_spy(server.seat, &Wrapland::Server::Seat::input_method_v2_changed);
593     QVERIFY(input_method_spy.isValid());
594 
595     auto input_method = std::unique_ptr<Wrapland::Client::input_method_v2>(get_input_method());
596     QVERIFY(input_method);
597     QVERIFY(input_method_spy.wait());
598 
599     auto server_input_method = server.seat->get_input_method_v2();
600     QVERIFY(server_input_method);
601 
602     QSignalSpy grab_spy(server_input_method, &Wrapland::Server::input_method_v2::keyboard_grabbed);
603     QVERIFY(grab_spy.isValid());
604 
605     auto grab = std::unique_ptr<Wrapland::Client::input_method_keyboard_grab_v2>(
606         input_method->grab_keyboard());
607 
608     QVERIFY(grab_spy.wait());
609     auto server_grab
610         = grab_spy.first().first().value<Wrapland::Server::input_method_keyboard_grab_v2*>();
611 
612     QSignalSpy keymap_spy(grab.get(),
613                           &Wrapland::Client::input_method_keyboard_grab_v2::keymap_changed);
614     QVERIFY(keymap_spy.isValid());
615 
616     server_grab->set_keymap("foo");
617     QVERIFY(keymap_spy.wait());
618 
619     auto fd = keymap_spy.first().first().toInt();
620     QVERIFY(fd != -1);
621     QCOMPARE(keymap_spy.first().last().value<quint32>(), 3u);
622 
623     QFile file;
624     QVERIFY(file.open(fd, QIODevice::ReadOnly));
625     auto address = reinterpret_cast<char*>(file.map(0, keymap_spy.first().last().value<quint32>()));
626     QVERIFY(address);
627     QCOMPARE(qstrcmp(address, "foo"), 0);
628     file.close();
629 
630     QSignalSpy key_spy(grab.get(), &Wrapland::Client::input_method_keyboard_grab_v2::key_changed);
631     QVERIFY(key_spy.isValid());
632 
633     server_grab->press_key(1, KEY_K);
634     QVERIFY(key_spy.wait());
635 
636     QCOMPARE(key_spy.first().at(0).value<uint32_t>(), KEY_K);
637     QCOMPARE(key_spy.first().at(1).value<Wrapland::Client::Keyboard::KeyState>(),
638              Wrapland::Client::Keyboard::KeyState::Pressed);
639     QCOMPARE(key_spy.first().at(2).value<uint32_t>(), 1);
640 
641     QSignalSpy modifiers_spy(grab.get(),
642                              &Wrapland::Client::input_method_keyboard_grab_v2::modifiers_changed);
643     QVERIFY(modifiers_spy.isValid());
644 
645     server_grab->update_modifiers(1, 2, 3, 4);
646     QVERIFY(modifiers_spy.wait());
647 
648     QCOMPARE(modifiers_spy.first().at(0).value<uint32_t>(), 1);
649     QCOMPARE(modifiers_spy.first().at(1).value<uint32_t>(), 2);
650     QCOMPARE(modifiers_spy.first().at(2).value<uint32_t>(), 3);
651     QCOMPARE(modifiers_spy.first().at(3).value<uint32_t>(), 4);
652 
653     QSignalSpy repeat_spy(grab.get(),
654                           &Wrapland::Client::input_method_keyboard_grab_v2::repeat_changed);
655     QVERIFY(repeat_spy.isValid());
656 
657     server_grab->set_repeat_info(1, 2);
658     QVERIFY(repeat_spy.wait());
659 
660     QCOMPARE(grab->repeat_rate(), 1);
661     QCOMPARE(grab->repeat_delay(), 2);
662 }
663 
664 QTEST_GUILESS_MAIN(input_method_v2_test)
665 #include "input_method_v2.moc"
666