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