KWin
Loading...
Searching...
No Matches
decoration_input_test.cpp
Go to the documentation of this file.
1/*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8*/
9#include "kwin_wayland_test.h"
10
11#include "core/output.h"
12#include "cursor.h"
13#include "internalwindow.h"
14#include "pointer_input.h"
15#include "touch_input.h"
16#include "wayland_server.h"
17#include "window.h"
18#include "workspace.h"
19
23
24#include <KWayland/Client/compositor.h>
25#include <KWayland/Client/connection_thread.h>
26#include <KWayland/Client/keyboard.h>
27#include <KWayland/Client/pointer.h>
28#include <KWayland/Client/seat.h>
29#include <KWayland/Client/shm_pool.h>
30#include <KWayland/Client/surface.h>
31
32#include <KDecoration2/Decoration>
33#include <KDecoration2/DecorationSettings>
34
35#include <QSignalSpy>
36
37#include <linux/input.h>
38
39Q_DECLARE_METATYPE(Qt::WindowFrameSection)
40
41namespace KWin
42{
43
44static const QString s_socketName = QStringLiteral("wayland_test_kwin_decoration_input-0");
45
46class DecorationInputTest : public QObject
47{
48 Q_OBJECT
49private Q_SLOTS:
50 void initTestCase();
51 void init();
52 void cleanup();
53 void testAxis_data();
54 void testAxis();
55 void testDoubleClickOnAllDesktops_data();
56 void testDoubleClickOnAllDesktops();
57 void testDoubleClickClose();
58 void testDoubleTap_data();
59 void testDoubleTap();
60 void testHover();
61 void testPressToMove_data();
62 void testPressToMove();
63 void testTapToMove_data();
64 void testTapToMove();
65 void testResizeOutsideWindow_data();
66 void testResizeOutsideWindow();
67 void testModifierClickUnrestrictedMove_data();
68 void testModifierClickUnrestrictedMove();
69 void testModifierScrollOpacity_data();
70 void testModifierScrollOpacity();
71 void testTouchEvents();
72 void testTooltipDoesntEatKeyEvents();
73
74private:
75 std::tuple<Window *, std::unique_ptr<KWayland::Client::Surface>, Test::XdgToplevel *> showWindow();
76};
77
78#define MOTION(target) Test::pointerMotion(target, timestamp++)
79
80#define PRESS Test::pointerButtonPressed(BTN_LEFT, timestamp++)
81
82#define RELEASE Test::pointerButtonReleased(BTN_LEFT, timestamp++)
83
84std::tuple<Window *, std::unique_ptr<KWayland::Client::Surface>, Test::XdgToplevel *> DecorationInputTest::showWindow()
85{
86#define VERIFY(statement) \
87 if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__)) \
88 return {nullptr, nullptr, nullptr};
89#define COMPARE(actual, expected) \
90 if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__)) \
91 return {nullptr, nullptr, nullptr};
92
93 std::unique_ptr<KWayland::Client::Surface> surface{Test::createSurface()};
94 VERIFY(surface.get());
95 Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly, surface.get());
96 VERIFY(shellSurface);
97 Test::XdgToplevelDecorationV1 *decoration = Test::createXdgToplevelDecorationV1(shellSurface, shellSurface);
98 VERIFY(decoration);
99
100 QSignalSpy decorationConfigureRequestedSpy(decoration, &Test::XdgToplevelDecorationV1::configureRequested);
101 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
102
103 decoration->set_mode(Test::XdgToplevelDecorationV1::mode_server_side);
104 surface->commit(KWayland::Client::Surface::CommitFlag::None);
105 VERIFY(surfaceConfigureRequestedSpy.wait());
106 COMPARE(decorationConfigureRequestedSpy.last().at(0).value<Test::XdgToplevelDecorationV1::mode>(), Test::XdgToplevelDecorationV1::mode_server_side);
107
108 // let's render
109 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
110 auto window = Test::renderAndWaitForShown(surface.get(), QSize(500, 50), Qt::blue);
111 VERIFY(window);
112 COMPARE(workspace()->activeWindow(), window);
113
114#undef VERIFY
115#undef COMPARE
116
117 return {window, std::move(surface), shellSurface};
118}
119
120void DecorationInputTest::initTestCase()
121{
122 qRegisterMetaType<KWin::Window *>();
123 qRegisterMetaType<KWin::InternalWindow *>();
124 QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
125 QVERIFY(waylandServer()->init(s_socketName));
127 QRect(0, 0, 1280, 1024),
128 QRect(1280, 0, 1280, 1024),
129 });
130
131 // change some options
132 KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig);
133 config->group(QStringLiteral("MouseBindings")).writeEntry("CommandTitlebarWheel", QStringLiteral("above/below"));
134 config->group(QStringLiteral("Windows")).writeEntry("TitlebarDoubleClickCommand", QStringLiteral("OnAllDesktops"));
135 config->group(QStringLiteral("Desktops")).writeEntry("Number", 2);
136 config->sync();
137
138 kwinApp()->setConfig(config);
139
140 kwinApp()->start();
141 QVERIFY(applicationStartedSpy.wait());
142 const auto outputs = workspace()->outputs();
143 QCOMPARE(outputs.count(), 2);
144 QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024));
145 QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024));
146 setenv("QT_QPA_PLATFORM", "wayland", true);
147}
148
149void DecorationInputTest::init()
150{
153
154 workspace()->setActiveOutput(QPoint(640, 512));
155 input()->pointer()->warp(QPoint(640, 512));
156}
157
158void DecorationInputTest::cleanup()
159{
161}
162
163void DecorationInputTest::testAxis_data()
164{
165 QTest::addColumn<QPoint>("decoPoint");
166 QTest::addColumn<Qt::WindowFrameSection>("expectedSection");
167
168 QTest::newRow("topLeft") << QPoint(0, 0) << Qt::TopLeftSection;
169 QTest::newRow("top") << QPoint(250, 0) << Qt::TopSection;
170 QTest::newRow("topRight") << QPoint(499, 0) << Qt::TopRightSection;
171}
172
173void DecorationInputTest::testAxis()
174{
175 static constexpr double oneTick = 15;
176
177 const auto [window, surface, shellSurface] = showWindow();
178 QVERIFY(window);
179 QVERIFY(window->isDecorated());
180 QVERIFY(!window->noBorder());
181 QCOMPARE(window->titlebarPosition(), Qt::TopEdge);
182 QVERIFY(!window->keepAbove());
183 QVERIFY(!window->keepBelow());
184
185 quint32 timestamp = 1;
186 MOTION(QPoint(window->frameGeometry().center().x(), window->frameMargins().top() / 2.0));
187 QVERIFY(input()->pointer()->decoration());
188 QCOMPARE(input()->pointer()->decoration()->decoration()->sectionUnderMouse(), Qt::TitleBarArea);
189
190 // TODO: mouse wheel direction looks wrong to me
191 // simulate wheel
192 Test::pointerAxisVertical(oneTick, timestamp++);
193 QVERIFY(window->keepBelow());
194 QVERIFY(!window->keepAbove());
195 Test::pointerAxisVertical(-oneTick, timestamp++);
196 QVERIFY(!window->keepBelow());
197 QVERIFY(!window->keepAbove());
198 Test::pointerAxisVertical(-oneTick, timestamp++);
199 QVERIFY(!window->keepBelow());
200 QVERIFY(window->keepAbove());
201
202 // test top most deco pixel, BUG: 362860
203 window->move(QPoint(0, 0));
204 QFETCH(QPoint, decoPoint);
205 MOTION(decoPoint);
206 QVERIFY(input()->pointer()->decoration());
207 QCOMPARE(input()->pointer()->decoration()->window(), window);
208 QTEST(input()->pointer()->decoration()->decoration()->sectionUnderMouse(), "expectedSection");
209 Test::pointerAxisVertical(oneTick, timestamp++);
210 QVERIFY(!window->keepBelow());
211 QVERIFY(!window->keepAbove());
212}
213
214void DecorationInputTest::testDoubleClickOnAllDesktops_data()
215{
216 QTest::addColumn<QPoint>("decoPoint");
217 QTest::addColumn<Qt::WindowFrameSection>("expectedSection");
218
219 QTest::newRow("topLeft") << QPoint(0, 0) << Qt::TopLeftSection;
220 QTest::newRow("top") << QPoint(250, 0) << Qt::TopSection;
221 QTest::newRow("topRight") << QPoint(499, 0) << Qt::TopRightSection;
222}
223
224void KWin::DecorationInputTest::testDoubleClickOnAllDesktops()
225{
226 KConfigGroup group = kwinApp()->config()->group(QStringLiteral("Windows"));
227 group.writeEntry("TitlebarDoubleClickCommand", QStringLiteral("OnAllDesktops"));
228 group.sync();
230
231 const auto [window, surface, shellSurface] = showWindow();
232 QVERIFY(window);
233 QVERIFY(window->isDecorated());
234 QVERIFY(!window->noBorder());
235 QVERIFY(!window->isOnAllDesktops());
236 quint32 timestamp = 1;
237 MOTION(QPoint(window->frameGeometry().center().x(), window->frameMargins().top() / 2.0));
238
239 // double click
240 PRESS;
241 RELEASE;
242 PRESS;
243 RELEASE;
244 QVERIFY(window->isOnAllDesktops());
245 // double click again
246 PRESS;
247 RELEASE;
248 QVERIFY(window->isOnAllDesktops());
249 PRESS;
250 RELEASE;
251 QVERIFY(!window->isOnAllDesktops());
252
253 // test top most deco pixel, BUG: 362860
254 window->move(QPoint(0, 0));
255 QFETCH(QPoint, decoPoint);
256 MOTION(decoPoint);
257 QVERIFY(input()->pointer()->decoration());
258 QCOMPARE(input()->pointer()->decoration()->window(), window);
259 QTEST(input()->pointer()->decoration()->decoration()->sectionUnderMouse(), "expectedSection");
260 // double click
261 PRESS;
262 RELEASE;
263 QVERIFY(!window->isOnAllDesktops());
264 PRESS;
265 RELEASE;
266 QVERIFY(window->isOnAllDesktops());
267}
268
269void DecorationInputTest::testDoubleClickClose()
270{
271 // this test verifies that no crash occurs when double click is configured to close action
272 KConfigGroup group = kwinApp()->config()->group(QStringLiteral("Windows"));
273 group.writeEntry("TitlebarDoubleClickCommand", QStringLiteral("Close"));
274 group.sync();
276
277 auto [window, surface, shellSurface] = showWindow();
278 QVERIFY(window);
279 QVERIFY(window->isDecorated());
280 quint32 timestamp = 1;
281 MOTION(QPoint(window->frameGeometry().center().x(), window->frameMargins().top() / 2.0));
282
283 connect(shellSurface, &Test::XdgToplevel::closeRequested, this, [&surface = surface]() {
284 surface.reset();
285 });
286
287 // double click
288 QSignalSpy closedSpy(window, &Window::closed);
289 window->ref();
290 PRESS;
291 RELEASE;
292 PRESS;
293 QVERIFY(closedSpy.wait());
294 RELEASE;
295
296 QVERIFY(window->isDeleted());
297 window->unref();
298}
299
300void DecorationInputTest::testDoubleTap_data()
301{
302 QTest::addColumn<QPoint>("decoPoint");
303 QTest::addColumn<Qt::WindowFrameSection>("expectedSection");
304
305 QTest::newRow("topLeft") << QPoint(10, 10) << Qt::TopLeftSection;
306 QTest::newRow("top") << QPoint(260, 10) << Qt::TopSection;
307 QTest::newRow("topRight") << QPoint(509, 10) << Qt::TopRightSection;
308}
309
310void KWin::DecorationInputTest::testDoubleTap()
311{
312 KConfigGroup group = kwinApp()->config()->group(QStringLiteral("Windows"));
313 group.writeEntry("TitlebarDoubleClickCommand", QStringLiteral("OnAllDesktops"));
314 group.sync();
316
317 const auto [window, surface, shellSurface] = showWindow();
318 QVERIFY(window);
319 QVERIFY(window->isDecorated());
320 QVERIFY(!window->noBorder());
321 QVERIFY(!window->isOnAllDesktops());
322 quint32 timestamp = 1;
323 const QPoint tapPoint(window->frameGeometry().center().x(), window->frameMargins().top() / 2.0);
324
325 // double tap
326 Test::touchDown(0, tapPoint, timestamp++);
327 Test::touchUp(0, timestamp++);
328 Test::touchDown(0, tapPoint, timestamp++);
329 Test::touchUp(0, timestamp++);
330 QVERIFY(window->isOnAllDesktops());
331 // double tap again
332 Test::touchDown(0, tapPoint, timestamp++);
333 Test::touchUp(0, timestamp++);
334 QVERIFY(window->isOnAllDesktops());
335 Test::touchDown(0, tapPoint, timestamp++);
336 Test::touchUp(0, timestamp++);
337 QVERIFY(!window->isOnAllDesktops());
338
339 // test top most deco pixel, BUG: 362860
340 //
341 // Not directly at (0, 0), otherwise ScreenEdgeInputFilter catches
342 // event before DecorationEventFilter.
343 window->move(QPoint(10, 10));
344 QFETCH(QPoint, decoPoint);
345 // double click
346 Test::touchDown(0, decoPoint, timestamp++);
347 QVERIFY(input()->touch()->decoration());
348 QCOMPARE(input()->touch()->decoration()->window(), window);
349 QTEST(input()->touch()->decoration()->decoration()->sectionUnderMouse(), "expectedSection");
350 Test::touchUp(0, timestamp++);
351 QVERIFY(!window->isOnAllDesktops());
352 Test::touchDown(0, decoPoint, timestamp++);
353 Test::touchUp(0, timestamp++);
354 QVERIFY(window->isOnAllDesktops());
355}
356
357void DecorationInputTest::testHover()
358{
359 const auto [window, surface, shellSurface] = showWindow();
360 QVERIFY(window);
361 QVERIFY(window->isDecorated());
362 QVERIFY(!window->noBorder());
363
364 // our left border is moved out of the visible area, so move the window to a better place
365 window->move(QPoint(20, 0));
366
367 quint32 timestamp = 1;
368 MOTION(QPoint(window->frameGeometry().center().x(), window->frameMargins().top() / 2.0));
369 QCOMPARE(window->cursor(), CursorShape(Qt::ArrowCursor));
370
371 // There is a mismatch of the cursor key positions between windows
372 // with and without borders (with borders one can move inside a bit and still
373 // be on an edge, without not). We should make this consistent in KWin's core.
374 //
375 // TODO: Test input position with different border sizes.
376 // TODO: We should test with the fake decoration to have a fixed test environment.
377 const bool hasBorders = Workspace::self()->decorationBridge()->settings()->borderSize() != KDecoration2::BorderSize::None;
378 auto deviation = [hasBorders] {
379 return hasBorders ? -1 : 0;
380 };
381
382 MOTION(QPoint(window->frameGeometry().x(), 0));
383 QCOMPARE(window->cursor(), CursorShape(KWin::ExtendedCursor::SizeNorthWest));
384 MOTION(QPoint(window->frameGeometry().x() + window->frameGeometry().width() / 2, 0));
385 QCOMPARE(window->cursor(), CursorShape(KWin::ExtendedCursor::SizeNorth));
386 MOTION(QPoint(window->frameGeometry().x() + window->frameGeometry().width() - 1, 0));
387 QCOMPARE(window->cursor(), CursorShape(KWin::ExtendedCursor::SizeNorthEast));
388 MOTION(QPoint(window->frameGeometry().x() + window->frameGeometry().width() + deviation(), window->height() / 2));
389 QCOMPARE(window->cursor(), CursorShape(KWin::ExtendedCursor::SizeEast));
390 MOTION(QPoint(window->frameGeometry().x() + window->frameGeometry().width() + deviation(), window->height() - 1));
391 QCOMPARE(window->cursor(), CursorShape(KWin::ExtendedCursor::SizeSouthEast));
392 MOTION(QPoint(window->frameGeometry().x() + window->frameGeometry().width() / 2, window->height() + deviation()));
393 QCOMPARE(window->cursor(), CursorShape(KWin::ExtendedCursor::SizeSouth));
394 MOTION(QPoint(window->frameGeometry().x(), window->height() + deviation()));
395 QCOMPARE(window->cursor(), CursorShape(KWin::ExtendedCursor::SizeSouthWest));
396 MOTION(QPoint(window->frameGeometry().x() - 1, window->height() / 2));
397 QCOMPARE(window->cursor(), CursorShape(KWin::ExtendedCursor::SizeWest));
398
399 MOTION(window->frameGeometry().center());
400 QEXPECT_FAIL("", "Cursor not set back on leave", Continue);
401 QCOMPARE(window->cursor(), CursorShape(Qt::ArrowCursor));
402}
403
404void DecorationInputTest::testPressToMove_data()
405{
406 QTest::addColumn<QPoint>("offset");
407 QTest::addColumn<QPoint>("offset2");
408 QTest::addColumn<QPoint>("offset3");
409
410 QTest::newRow("To right") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0);
411 QTest::newRow("To left") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0);
412 QTest::newRow("To bottom") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30);
413 QTest::newRow("To top") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30);
414}
415
416void DecorationInputTest::testPressToMove()
417{
418 const auto [window, surface, shellSurface] = showWindow();
419 QVERIFY(window);
420 QVERIFY(window->isDecorated());
421 QVERIFY(!window->noBorder());
422 window->move(workspace()->activeOutput()->geometry().center() - QPoint(window->width() / 2, window->height() / 2));
423 QSignalSpy interactiveMoveResizeStartedSpy(window, &Window::interactiveMoveResizeStarted);
424 QSignalSpy interactiveMoveResizeFinishedSpy(window, &Window::interactiveMoveResizeFinished);
425
426 quint32 timestamp = 1;
427 MOTION(QPoint(window->frameGeometry().center().x(), window->y() + window->frameMargins().top() / 2.0));
428 QCOMPARE(window->cursor(), CursorShape(Qt::ArrowCursor));
429
430 PRESS;
431 QVERIFY(!window->isInteractiveMove());
432 QFETCH(QPoint, offset);
433 MOTION(QPoint(window->frameGeometry().center().x(), window->y() + window->frameMargins().top() / 2.0) + offset);
434 const QPointF oldPos = window->pos();
435 QVERIFY(window->isInteractiveMove());
436 QCOMPARE(interactiveMoveResizeStartedSpy.count(), 1);
437
438 RELEASE;
439 QTRY_VERIFY(!window->isInteractiveMove());
440 QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 1);
441 QEXPECT_FAIL("", "Just trigger move doesn't move the window", Continue);
442 QCOMPARE(window->pos(), oldPos + offset);
443
444 // again
445 PRESS;
446 QVERIFY(!window->isInteractiveMove());
447 QFETCH(QPoint, offset2);
448 MOTION(QPoint(window->frameGeometry().center().x(), window->y() + window->frameMargins().top() / 2.0) + offset2);
449 QVERIFY(window->isInteractiveMove());
450 QCOMPARE(interactiveMoveResizeStartedSpy.count(), 2);
451 QFETCH(QPoint, offset3);
452 MOTION(QPoint(window->frameGeometry().center().x(), window->y() + window->frameMargins().top() / 2.0) + offset3);
453
454 RELEASE;
455 QTRY_VERIFY(!window->isInteractiveMove());
456 QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 2);
457 // TODO: the offset should also be included
458 QCOMPARE(window->pos(), oldPos + offset2 + offset3);
459}
460
461void DecorationInputTest::testTapToMove_data()
462{
463 QTest::addColumn<QPoint>("offset");
464 QTest::addColumn<QPoint>("offset2");
465 QTest::addColumn<QPoint>("offset3");
466
467 QTest::newRow("To right") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0);
468 QTest::newRow("To left") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0);
469 QTest::newRow("To bottom") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30);
470 QTest::newRow("To top") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30);
471}
472
473void DecorationInputTest::testTapToMove()
474{
475 const auto [window, surface, shellSurface] = showWindow();
476 QVERIFY(window);
477 QVERIFY(window->isDecorated());
478 QVERIFY(!window->noBorder());
479 window->move(workspace()->activeOutput()->geometry().center() - QPoint(window->width() / 2, window->height() / 2));
480 QSignalSpy interactiveMoveResizeStartedSpy(window, &Window::interactiveMoveResizeStarted);
481 QSignalSpy interactiveMoveResizeFinishedSpy(window, &Window::interactiveMoveResizeFinished);
482
483 quint32 timestamp = 1;
484 QPoint p = QPoint(window->frameGeometry().center().x(), window->y() + window->frameMargins().top() / 2.0);
485
486 Test::touchDown(0, p, timestamp++);
487 QVERIFY(!window->isInteractiveMove());
488 QFETCH(QPoint, offset);
489 QCOMPARE(input()->touch()->decorationPressId(), 0);
490 Test::touchMotion(0, p + offset, timestamp++);
491 const QPointF oldPos = window->pos();
492 QVERIFY(window->isInteractiveMove());
493 QCOMPARE(interactiveMoveResizeStartedSpy.count(), 1);
494
495 Test::touchUp(0, timestamp++);
496 QTRY_VERIFY(!window->isInteractiveMove());
497 QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 1);
498 QEXPECT_FAIL("", "Just trigger move doesn't move the window", Continue);
499 QCOMPARE(window->pos(), oldPos + offset);
500
501 // again
502 Test::touchDown(1, p + offset, timestamp++);
503 QCOMPARE(input()->touch()->decorationPressId(), 1);
504 QVERIFY(!window->isInteractiveMove());
505 QFETCH(QPoint, offset2);
506 Test::touchMotion(1, QPoint(window->frameGeometry().center().x(), window->y() + window->frameMargins().top() / 2.0) + offset2, timestamp++);
507 QVERIFY(window->isInteractiveMove());
508 QCOMPARE(interactiveMoveResizeStartedSpy.count(), 2);
509 QFETCH(QPoint, offset3);
510 Test::touchMotion(1, QPoint(window->frameGeometry().center().x(), window->y() + window->frameMargins().top() / 2.0) + offset3, timestamp++);
511
512 Test::touchUp(1, timestamp++);
513 QTRY_VERIFY(!window->isInteractiveMove());
514 QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 2);
515 // TODO: the offset should also be included
516 QCOMPARE(window->pos(), oldPos + offset2 + offset3);
517}
518
519void DecorationInputTest::testResizeOutsideWindow_data()
520{
521 QTest::addColumn<Qt::Edge>("edge");
522 QTest::addColumn<Qt::CursorShape>("expectedCursor");
523
524 QTest::newRow("left") << Qt::LeftEdge << Qt::SizeHorCursor;
525 QTest::newRow("right") << Qt::RightEdge << Qt::SizeHorCursor;
526 QTest::newRow("bottom") << Qt::BottomEdge << Qt::SizeVerCursor;
527}
528
529void DecorationInputTest::testResizeOutsideWindow()
530{
531 // this test verifies that one can resize the window outside the decoration with NoSideBorder
532
533 // first adjust config
534 kwinApp()->config()->group(QStringLiteral("org.kde.kdecoration2")).writeEntry("BorderSize", QStringLiteral("None"));
535 kwinApp()->config()->sync();
537
538 // now create window
539 const auto [window, surface, shellSurface] = showWindow();
540 QVERIFY(window);
541 QVERIFY(window->isDecorated());
542 QVERIFY(!window->noBorder());
543 window->move(workspace()->activeOutput()->geometry().center() - QPoint(window->width() / 2, window->height() / 2));
544 QSignalSpy interactiveMoveResizeStartedSpy(window, &Window::interactiveMoveResizeStarted);
545
546 // go to border
547 quint32 timestamp = 1;
548 QFETCH(Qt::Edge, edge);
549 switch (edge) {
550 case Qt::LeftEdge:
551 MOTION(QPoint(window->frameGeometry().x() - 1, window->frameGeometry().center().y()));
552 break;
553 case Qt::RightEdge:
554 MOTION(QPoint(window->frameGeometry().x() + window->frameGeometry().width() + 1, window->frameGeometry().center().y()));
555 break;
556 case Qt::BottomEdge:
557 MOTION(QPoint(window->frameGeometry().center().x(), window->frameGeometry().y() + window->frameGeometry().height() + 1));
558 break;
559 default:
560 break;
561 }
562 QVERIFY(!exclusiveContains(window->frameGeometry(), KWin::Cursors::self()->mouse()->pos()));
563
564 // pressing should trigger resize
565 PRESS;
566 QVERIFY(!window->isInteractiveResize());
567 QVERIFY(interactiveMoveResizeStartedSpy.wait());
568 QVERIFY(window->isInteractiveResize());
569
570 RELEASE;
571 QVERIFY(!window->isInteractiveResize());
572}
573
574void DecorationInputTest::testModifierClickUnrestrictedMove_data()
575{
576 QTest::addColumn<int>("modifierKey");
577 QTest::addColumn<int>("mouseButton");
578 QTest::addColumn<QString>("modKey");
579 QTest::addColumn<bool>("capsLock");
580
581 const QString alt = QStringLiteral("Alt");
582 const QString meta = QStringLiteral("Meta");
583
584 QTest::newRow("Left Alt + Left Click") << KEY_LEFTALT << BTN_LEFT << alt << false;
585 QTest::newRow("Left Alt + Right Click") << KEY_LEFTALT << BTN_RIGHT << alt << false;
586 QTest::newRow("Left Alt + Middle Click") << KEY_LEFTALT << BTN_MIDDLE << alt << false;
587 QTest::newRow("Right Alt + Left Click") << KEY_RIGHTALT << BTN_LEFT << alt << false;
588 QTest::newRow("Right Alt + Right Click") << KEY_RIGHTALT << BTN_RIGHT << alt << false;
589 QTest::newRow("Right Alt + Middle Click") << KEY_RIGHTALT << BTN_MIDDLE << alt << false;
590 // now everything with meta
591 QTest::newRow("Left Meta + Left Click") << KEY_LEFTMETA << BTN_LEFT << meta << false;
592 QTest::newRow("Left Meta + Right Click") << KEY_LEFTMETA << BTN_RIGHT << meta << false;
593 QTest::newRow("Left Meta + Middle Click") << KEY_LEFTMETA << BTN_MIDDLE << meta << false;
594 QTest::newRow("Right Meta + Left Click") << KEY_RIGHTMETA << BTN_LEFT << meta << false;
595 QTest::newRow("Right Meta + Right Click") << KEY_RIGHTMETA << BTN_RIGHT << meta << false;
596 QTest::newRow("Right Meta + Middle Click") << KEY_RIGHTMETA << BTN_MIDDLE << meta << false;
597
598 // and with capslock
599 QTest::newRow("Left Alt + Left Click/CapsLock") << KEY_LEFTALT << BTN_LEFT << alt << true;
600 QTest::newRow("Left Alt + Right Click/CapsLock") << KEY_LEFTALT << BTN_RIGHT << alt << true;
601 QTest::newRow("Left Alt + Middle Click/CapsLock") << KEY_LEFTALT << BTN_MIDDLE << alt << true;
602 QTest::newRow("Right Alt + Left Click/CapsLock") << KEY_RIGHTALT << BTN_LEFT << alt << true;
603 QTest::newRow("Right Alt + Right Click/CapsLock") << KEY_RIGHTALT << BTN_RIGHT << alt << true;
604 QTest::newRow("Right Alt + Middle Click/CapsLock") << KEY_RIGHTALT << BTN_MIDDLE << alt << true;
605 // now everything with meta
606 QTest::newRow("Left Meta + Left Click/CapsLock") << KEY_LEFTMETA << BTN_LEFT << meta << true;
607 QTest::newRow("Left Meta + Right Click/CapsLock") << KEY_LEFTMETA << BTN_RIGHT << meta << true;
608 QTest::newRow("Left Meta + Middle Click/CapsLock") << KEY_LEFTMETA << BTN_MIDDLE << meta << true;
609 QTest::newRow("Right Meta + Left Click/CapsLock") << KEY_RIGHTMETA << BTN_LEFT << meta << true;
610 QTest::newRow("Right Meta + Right Click/CapsLock") << KEY_RIGHTMETA << BTN_RIGHT << meta << true;
611 QTest::newRow("Right Meta + Middle Click/CapsLock") << KEY_RIGHTMETA << BTN_MIDDLE << meta << true;
612}
613
614void DecorationInputTest::testModifierClickUnrestrictedMove()
615{
616 // this test ensures that Alt+mouse button press triggers unrestricted move
617
618 // first modify the config for this run
619 QFETCH(QString, modKey);
620 KConfigGroup group = kwinApp()->config()->group(QStringLiteral("MouseBindings"));
621 group.writeEntry("CommandAllKey", modKey);
622 group.writeEntry("CommandAll1", "Move");
623 group.writeEntry("CommandAll2", "Move");
624 group.writeEntry("CommandAll3", "Move");
625 group.sync();
627 QCOMPARE(options->commandAllModifier(), modKey == QStringLiteral("Alt") ? Qt::AltModifier : Qt::MetaModifier);
631
632 // create a window
633 const auto [window, surface, shellSurface] = showWindow();
634 QVERIFY(window);
635 QVERIFY(window->isDecorated());
636 QVERIFY(!window->noBorder());
637 window->move(workspace()->activeOutput()->geometry().center() - QPoint(window->width() / 2, window->height() / 2));
638 // move cursor on window
639 input()->pointer()->warp(QPoint(window->frameGeometry().center().x(), window->y() + window->frameMargins().top() / 2.0));
640
641 // simulate modifier+click
642 quint32 timestamp = 1;
643 QFETCH(bool, capsLock);
644 if (capsLock) {
645 Test::keyboardKeyPressed(KEY_CAPSLOCK, timestamp++);
646 }
647 QFETCH(int, modifierKey);
648 QFETCH(int, mouseButton);
649 Test::keyboardKeyPressed(modifierKey, timestamp++);
650 QVERIFY(!window->isInteractiveMove());
651 Test::pointerButtonPressed(mouseButton, timestamp++);
652 QVERIFY(window->isInteractiveMove());
653 // release modifier should not change it
654 Test::keyboardKeyReleased(modifierKey, timestamp++);
655 QVERIFY(window->isInteractiveMove());
656 // but releasing the key should end move/resize
657 Test::pointerButtonReleased(mouseButton, timestamp++);
658 QVERIFY(!window->isInteractiveMove());
659 if (capsLock) {
660 Test::keyboardKeyReleased(KEY_CAPSLOCK, timestamp++);
661 }
662}
663
664void DecorationInputTest::testModifierScrollOpacity_data()
665{
666 QTest::addColumn<int>("modifierKey");
667 QTest::addColumn<QString>("modKey");
668 QTest::addColumn<bool>("capsLock");
669
670 const QString alt = QStringLiteral("Alt");
671 const QString meta = QStringLiteral("Meta");
672
673 QTest::newRow("Left Alt") << KEY_LEFTALT << alt << false;
674 QTest::newRow("Right Alt") << KEY_RIGHTALT << alt << false;
675 QTest::newRow("Left Meta") << KEY_LEFTMETA << meta << false;
676 QTest::newRow("Right Meta") << KEY_RIGHTMETA << meta << false;
677 QTest::newRow("Left Alt/CapsLock") << KEY_LEFTALT << alt << true;
678 QTest::newRow("Right Alt/CapsLock") << KEY_RIGHTALT << alt << true;
679 QTest::newRow("Left Meta/CapsLock") << KEY_LEFTMETA << meta << true;
680 QTest::newRow("Right Meta/CapsLock") << KEY_RIGHTMETA << meta << true;
681}
682
683void DecorationInputTest::testModifierScrollOpacity()
684{
685 // this test verifies that mod+wheel performs a window operation
686
687 // first modify the config for this run
688 QFETCH(QString, modKey);
689 KConfigGroup group = kwinApp()->config()->group(QStringLiteral("MouseBindings"));
690 group.writeEntry("CommandAllKey", modKey);
691 group.writeEntry("CommandAllWheel", "change opacity");
692 group.sync();
694
695 const auto [window, surface, shellSurface] = showWindow();
696 QVERIFY(window);
697 QVERIFY(window->isDecorated());
698 QVERIFY(!window->noBorder());
699 window->move(workspace()->activeOutput()->geometry().center() - QPoint(window->width() / 2, window->height() / 2));
700 // move cursor on window
701 input()->pointer()->warp(QPoint(window->frameGeometry().center().x(), window->y() + window->frameMargins().top() / 2.0));
702 // set the opacity to 0.5
703 window->setOpacity(0.5);
704 QCOMPARE(window->opacity(), 0.5);
705
706 // simulate modifier+wheel
707 quint32 timestamp = 1;
708 QFETCH(bool, capsLock);
709 if (capsLock) {
710 Test::keyboardKeyPressed(KEY_CAPSLOCK, timestamp++);
711 }
712 QFETCH(int, modifierKey);
713 Test::keyboardKeyPressed(modifierKey, timestamp++);
714 Test::pointerAxisVertical(-5, timestamp++);
715 QCOMPARE(window->opacity(), 0.6);
716 Test::pointerAxisVertical(5, timestamp++);
717 QCOMPARE(window->opacity(), 0.5);
718 Test::keyboardKeyReleased(modifierKey, timestamp++);
719 if (capsLock) {
720 Test::keyboardKeyReleased(KEY_CAPSLOCK, timestamp++);
721 }
722}
723
724class EventHelper : public QObject
725{
726 Q_OBJECT
727public:
729 : QObject()
730 {
731 }
732 ~EventHelper() override = default;
733
734 bool eventFilter(QObject *watched, QEvent *event) override
735 {
736 if (event->type() == QEvent::HoverMove) {
737 Q_EMIT hoverMove();
738 } else if (event->type() == QEvent::HoverLeave) {
739 Q_EMIT hoverLeave();
740 }
741 return false;
742 }
743
744Q_SIGNALS:
745 void hoverMove();
747};
748
749void DecorationInputTest::testTouchEvents()
750{
751 // this test verifies that the decoration gets a hover leave event on touch release
752 // see BUG 386231
753 const auto [window, surface, shellSurface] = showWindow();
754 QVERIFY(window);
755 QVERIFY(window->isDecorated());
756 QVERIFY(!window->noBorder());
757
758 EventHelper helper;
759 window->decoration()->installEventFilter(&helper);
760 QSignalSpy hoverMoveSpy(&helper, &EventHelper::hoverMove);
761 QSignalSpy hoverLeaveSpy(&helper, &EventHelper::hoverLeave);
762
763 quint32 timestamp = 1;
764 const QPoint tapPoint(window->frameGeometry().center().x(), window->frameMargins().top() / 2.0);
765
766 QVERIFY(!input()->touch()->decoration());
767 Test::touchDown(0, tapPoint, timestamp++);
768 QVERIFY(input()->touch()->decoration());
769 QCOMPARE(input()->touch()->decoration()->decoration(), window->decoration());
770 QCOMPARE(hoverMoveSpy.count(), 1);
771 QCOMPARE(hoverLeaveSpy.count(), 0);
772 Test::touchUp(0, timestamp++);
773 QCOMPARE(hoverMoveSpy.count(), 1);
774 QCOMPARE(hoverLeaveSpy.count(), 1);
775
776 QCOMPARE(window->isInteractiveMove(), false);
777
778 // let's check that a hover motion is sent if the pointer is on deco, when touch release
779 input()->pointer()->warp(tapPoint);
780 QCOMPARE(hoverMoveSpy.count(), 2);
781 Test::touchDown(0, tapPoint, timestamp++);
782 QCOMPARE(hoverMoveSpy.count(), 3);
783 QCOMPARE(hoverLeaveSpy.count(), 1);
784 Test::touchUp(0, timestamp++);
785 QCOMPARE(hoverMoveSpy.count(), 3);
786 QCOMPARE(hoverLeaveSpy.count(), 2);
787}
788
789void DecorationInputTest::testTooltipDoesntEatKeyEvents()
790{
791 // this test verifies that a tooltip on the decoration does not steal key events
792 // BUG: 393253
793
794 // first create a keyboard
795 auto keyboard = Test::waylandSeat()->createKeyboard(Test::waylandSeat());
796 QVERIFY(keyboard);
797 QSignalSpy enteredSpy(keyboard, &KWayland::Client::Keyboard::entered);
798
799 const auto [window, surface, shellSurface] = showWindow();
800 QVERIFY(window);
801 QVERIFY(window->isDecorated());
802 QVERIFY(!window->noBorder());
803 QVERIFY(enteredSpy.wait());
804
805 QSignalSpy keyEvent(keyboard, &KWayland::Client::Keyboard::keyChanged);
806 QVERIFY(keyEvent.isValid());
807
808 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
809 window->decoratedClient()->requestShowToolTip(QStringLiteral("test"));
810 // now we should get an internal window
811 QVERIFY(windowAddedSpy.wait());
812 InternalWindow *internal = windowAddedSpy.first().first().value<InternalWindow *>();
813 QVERIFY(internal->isInternal());
814 QVERIFY(internal->handle()->flags().testFlag(Qt::ToolTip));
815
816 // now send a key
817 quint32 timestamp = 0;
818 Test::keyboardKeyPressed(KEY_A, timestamp++);
819 QVERIFY(keyEvent.wait());
820 Test::keyboardKeyReleased(KEY_A, timestamp++);
821 QVERIFY(keyEvent.wait());
822
823 window->decoratedClient()->requestHideToolTip();
825}
826
827}
828
830#include "decoration_input_test.moc"
QPointF pos()
Definition cursor.cpp:204
static Cursors * self()
Definition cursor.cpp:35
Cursor * mouse() const
Definition cursor.h:266
std::unique_ptr< KDecoration2::DecorationSettingsPrivate > settings(KDecoration2::DecorationSettings *parent) override
bool eventFilter(QObject *watched, QEvent *event) override
~EventHelper() override=default
PointerInputRedirection * pointer() const
Definition input.h:220
@ MouseUnrestrictedMove
Definition options.h:456
Qt::KeyboardModifier commandAllModifier() const
Definition options.h:558
MouseCommand commandAll2
Definition options.h:153
MouseCommand commandAll3
Definition options.h:154
MouseCommand commandAll1
Definition options.h:152
void warp(const QPointF &pos)
void configureRequested(quint32 serial)
void configureRequested(QtWayland::zxdg_toplevel_decoration_v1::mode mode)
void interactiveMoveResizeStarted()
void interactiveMoveResizeFinished()
Decoration::DecorationBridge * decorationBridge() const
static Workspace * self()
Definition workspace.h:91
void windowAdded(KWin::Window *)
QList< Output * > outputs() const
Definition workspace.h:762
void setActiveOutput(Output *output)
void slotReconfigure()
#define VERIFY(statement)
#define PRESS
#define RELEASE
#define MOTION(target)
#define COMPARE(actual, expected)
Q_DECLARE_METATYPE(KWin::SwitchEvent::State)
#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)
void touchDown(qint32 id, const QPointF &pos, quint32 time)
void pointerAxisVertical(qreal delta, quint32 time, qint32 discreteDelta=0, InputRedirection::PointerAxisSource source=InputRedirection::PointerAxisSourceUnknown)
void keyboardKeyPressed(quint32 key, quint32 time)
bool setupWaylandConnection(AdditionalWaylandInterfaces flags=AdditionalWaylandInterfaces())
void touchMotion(qint32 id, const QPointF &pos, quint32 time)
KWayland::Client::Seat * waylandSeat()
void pointerButtonPressed(quint32 button, quint32 time)
QList< KWayland::Client::Output * > outputs
std::unique_ptr< KWayland::Client::Surface > createSurface()
XdgToplevel * createXdgToplevelSurface(KWayland::Client::Surface *surface, QObject *parent=nullptr)
XdgToplevelDecorationV1 * createXdgToplevelDecorationV1(XdgToplevel *toplevel, QObject *parent=nullptr)
bool waitForWaylandPointer()
void pointerButtonReleased(quint32 button, quint32 time)
void touchUp(qint32 id, quint32 time)
bool waitForWindowClosed(Window *window)
WaylandServer * waylandServer()
Workspace * workspace()
Definition workspace.h:830
Options * options
Definition main.cpp:73
InputRedirection * input()
Definition input.h:549