KWin
Loading...
Searching...
No Matches
pointer_input.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 "cursorsource.h"
15#include "options.h"
16#include "pointer_input.h"
17#include "utils/xcursortheme.h"
18#include "virtualdesktops.h"
19#include "wayland/seat.h"
20#include "wayland_server.h"
21#include "window.h"
22#include "workspace.h"
23#include "x11window.h"
24
25#include <KWayland/Client/buffer.h>
26#include <KWayland/Client/compositor.h>
27#include <KWayland/Client/connection_thread.h>
28#include <KWayland/Client/keyboard.h>
29#include <KWayland/Client/pointer.h>
30#include <KWayland/Client/region.h>
31#include <KWayland/Client/seat.h>
32#include <KWayland/Client/shm_pool.h>
33#include <KWayland/Client/surface.h>
34
35#include <linux/input.h>
36#include <xcb/xcb_icccm.h>
37
38namespace KWin
39{
40
41static PlatformCursorImage loadReferenceThemeCursor(const QByteArray &name)
42{
43 const Cursor *pointerCursor = Cursors::self()->mouse();
44
45 const KXcursorTheme theme(pointerCursor->themeName(), pointerCursor->themeSize(), kwinApp()->devicePixelRatio());
46 if (theme.isEmpty()) {
47 return PlatformCursorImage();
48 }
49
50 ShapeCursorSource source;
51 source.setShape(name);
52 source.setTheme(theme);
53
54 return PlatformCursorImage(source.image(), source.hotspot());
55}
56
57static PlatformCursorImage loadReferenceThemeCursor(const CursorShape &shape)
58{
59 return loadReferenceThemeCursor(shape.name());
60}
61
62static const QString s_socketName = QStringLiteral("wayland_test_kwin_pointer_input-0");
63
64class PointerInputTest : public QObject
65{
66 Q_OBJECT
67private Q_SLOTS:
68 void initTestCase();
69 void init();
70 void cleanup();
71 void testWarpingUpdatesFocus();
72 void testWarpingGeneratesPointerMotion();
73 void testWarpingBetweenWindows();
74 void testUpdateFocusAfterScreenChange();
75 void testUpdateFocusOnDecorationDestroy();
76 void testModifierClickUnrestrictedMove_data();
77 void testModifierClickUnrestrictedMove();
78 void testModifierClickUnrestrictedFullscreenMove();
79 void testModifierClickUnrestrictedMoveGlobalShortcutsDisabled();
80 void testModifierScrollOpacity_data();
81 void testModifierScrollOpacity();
82 void testModifierScrollOpacityGlobalShortcutsDisabled();
83 void testScrollAction();
84 void testFocusFollowsMouse();
85 void testMouseActionInactiveWindow_data();
86 void testMouseActionInactiveWindow();
87 void testMouseActionActiveWindow_data();
88 void testMouseActionActiveWindow();
89 void testCursorImage();
90 void testCursorShapeV1();
91 void testEffectOverrideCursorImage();
92 void testPopup();
93 void testDecoCancelsPopup();
94 void testWindowUnderCursorWhileButtonPressed();
95 void testConfineToScreenGeometry_data();
96 void testConfineToScreenGeometry();
97 void testResizeCursor_data();
98 void testResizeCursor();
99 void testMoveCursor();
100 void testHideShowCursor();
101 void testDefaultInputRegion();
102 void testEmptyInputRegion();
103 void testUnfocusedModifiers();
104
105private:
106 void render(KWayland::Client::Surface *surface, const QSize &size = QSize(100, 50));
107 KWayland::Client::Compositor *m_compositor = nullptr;
108 KWayland::Client::Seat *m_seat = nullptr;
109};
110
111void PointerInputTest::initTestCase()
112{
113 qRegisterMetaType<KWin::Window *>();
114 QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
115 QVERIFY(waylandServer()->init(s_socketName));
117 QRect(0, 0, 1280, 1024),
118 QRect(1280, 0, 1280, 1024),
119 });
120
121 kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig));
122
123 if (!QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("icons/DMZ-White/index.theme")).isEmpty()) {
124 qputenv("XCURSOR_THEME", QByteArrayLiteral("DMZ-White"));
125 } else {
126 // might be vanilla-dmz (e.g. Arch, FreeBSD)
127 qputenv("XCURSOR_THEME", QByteArrayLiteral("Vanilla-DMZ"));
128 }
129 qputenv("XCURSOR_SIZE", QByteArrayLiteral("24"));
130 qputenv("XKB_DEFAULT_RULES", "evdev");
131
132 kwinApp()->start();
133 QVERIFY(applicationStartedSpy.wait());
134 const auto outputs = workspace()->outputs();
135 QCOMPARE(outputs.count(), 2);
136 QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024));
137 QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024));
138 setenv("QT_QPA_PLATFORM", "wayland", true);
139}
140
141void PointerInputTest::init()
142{
145 m_compositor = Test::waylandCompositor();
146 m_seat = Test::waylandSeat();
147
148 workspace()->setActiveOutput(QPoint(640, 512));
149 input()->pointer()->warp(QPoint(640, 512));
150}
151
152void PointerInputTest::cleanup()
153{
155}
156
157void PointerInputTest::render(KWayland::Client::Surface *surface, const QSize &size)
158{
159 Test::render(surface, size, Qt::blue);
161}
162
163void PointerInputTest::testWarpingUpdatesFocus()
164{
165 // this test verifies that warping the pointer creates pointer enter and leave events
166
167 // create pointer and signal spy for enter and leave signals
168 auto pointer = m_seat->createPointer(m_seat);
169 QVERIFY(pointer);
170 QVERIFY(pointer->isValid());
171 QSignalSpy enteredSpy(pointer, &KWayland::Client::Pointer::entered);
172 QSignalSpy leftSpy(pointer, &KWayland::Client::Pointer::left);
173
174 // create a window
175 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
176 std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
177 QVERIFY(surface);
178 Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get());
179 QVERIFY(shellSurface);
180 render(surface.get());
181 QVERIFY(windowAddedSpy.wait());
182 Window *window = workspace()->activeWindow();
183 QVERIFY(window);
184
185 // currently there should not be a focused pointer surface
186 QVERIFY(!waylandServer()->seat()->focusedPointerSurface());
187 QVERIFY(!pointer->enteredSurface());
188
189 // enter
190 input()->pointer()->warp(QPoint(25, 25));
191 QVERIFY(enteredSpy.wait());
192 QCOMPARE(enteredSpy.count(), 1);
193 QCOMPARE(enteredSpy.first().at(1).toPointF(), QPointF(25, 25));
194 // window should have focus
195 QCOMPARE(pointer->enteredSurface(), surface.get());
196 // also on the server
197 QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), window->surface());
198
199 // and out again
200 input()->pointer()->warp(QPoint(250, 250));
201 QVERIFY(leftSpy.wait());
202 QCOMPARE(leftSpy.count(), 1);
203 // there should not be a focused pointer surface anymore
204 QVERIFY(!waylandServer()->seat()->focusedPointerSurface());
205 QVERIFY(!pointer->enteredSurface());
206}
207
208void PointerInputTest::testWarpingGeneratesPointerMotion()
209{
210 // this test verifies that warping the pointer creates pointer motion events
211
212 // create pointer and signal spy for enter and motion
213 auto pointer = m_seat->createPointer(m_seat);
214 QVERIFY(pointer);
215 QVERIFY(pointer->isValid());
216 QSignalSpy enteredSpy(pointer, &KWayland::Client::Pointer::entered);
217 QSignalSpy movedSpy(pointer, &KWayland::Client::Pointer::motion);
218
219 // create a window
220 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
221 std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
222 QVERIFY(surface);
223 Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get());
224 QVERIFY(shellSurface);
225 render(surface.get());
226 QVERIFY(windowAddedSpy.wait());
227 Window *window = workspace()->activeWindow();
228 QVERIFY(window);
229
230 // enter
231 Test::pointerMotion(QPointF(25, 25), 1);
232 QVERIFY(enteredSpy.wait());
233 QCOMPARE(enteredSpy.first().at(1).toPointF(), QPointF(25, 25));
234
235 // now warp
236 input()->pointer()->warp(QPoint(26, 26));
237 QVERIFY(movedSpy.wait());
238 QCOMPARE(movedSpy.count(), 1);
239 QCOMPARE(movedSpy.last().first().toPointF(), QPointF(26, 26));
240}
241
242void PointerInputTest::testWarpingBetweenWindows()
243{
244 // This test verifies that the compositor will send correct events when the pointer
245 // leaves one window and enters another window.
246
247 std::unique_ptr<KWayland::Client::Pointer> pointer(m_seat->createPointer(m_seat));
248 QSignalSpy enteredSpy(pointer.get(), &KWayland::Client::Pointer::entered);
249 QSignalSpy leftSpy(pointer.get(), &KWayland::Client::Pointer::left);
250 QSignalSpy motionSpy(pointer.get(), &KWayland::Client::Pointer::motion);
251
252 // create windows
253 std::unique_ptr<KWayland::Client::Surface> surface1(Test::createSurface());
254 std::unique_ptr<Test::XdgToplevel> shellSurface1(Test::createXdgToplevelSurface(surface1.get()));
255 auto window1 = Test::renderAndWaitForShown(surface1.get(), QSize(100, 50), Qt::cyan);
256 std::unique_ptr<KWayland::Client::Surface> surface2(Test::createSurface());
257 std::unique_ptr<Test::XdgToplevel> shellSurface2(Test::createXdgToplevelSurface(surface2.get()));
258 auto window2 = Test::renderAndWaitForShown(surface2.get(), QSize(200, 100), Qt::red);
259
260 // place windows side by side
261 window1->move(QPoint(0, 0));
262 window2->move(QPoint(100, 0));
263
264 quint32 timestamp = 0;
265
266 // put the pointer at the center of the first window
267 Test::pointerMotion(window1->frameGeometry().center(), timestamp++);
268 QVERIFY(enteredSpy.wait());
269 QCOMPARE(enteredSpy.count(), 1);
270 QCOMPARE(enteredSpy.last().at(1).toPointF(), QPointF(50, 25));
271 QCOMPARE(leftSpy.count(), 0);
272 QCOMPARE(motionSpy.count(), 0);
273 QCOMPARE(pointer->enteredSurface(), surface1.get());
274
275 // put the pointer at the center of the second window
276 Test::pointerMotion(window2->frameGeometry().center(), timestamp++);
277 QVERIFY(enteredSpy.wait());
278 QCOMPARE(enteredSpy.count(), 2);
279 QCOMPARE(enteredSpy.last().at(1).toPointF(), QPointF(100, 50));
280 QCOMPARE(leftSpy.count(), 1);
281 QCOMPARE(motionSpy.count(), 0);
282 QCOMPARE(pointer->enteredSurface(), surface2.get());
283}
284
285void PointerInputTest::testUpdateFocusAfterScreenChange()
286{
287 // this test verifies that a pointer enter event is generated when the cursor changes to another
288 // screen due to removal of screen
289
290 // create pointer and signal spy for enter and motion
291 auto pointer = m_seat->createPointer(m_seat);
292 QVERIFY(pointer);
293 QVERIFY(pointer->isValid());
294 QSignalSpy enteredSpy(pointer, &KWayland::Client::Pointer::entered);
295 QSignalSpy leftSpy(pointer, &KWayland::Client::Pointer::left);
296
297 // create a window
298 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
299 std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
300 QVERIFY(surface);
301 Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get());
302 QVERIFY(shellSurface);
303 render(surface.get(), QSize(1280, 1024));
304 QVERIFY(windowAddedSpy.wait());
305 Window *window = workspace()->activeWindow();
306 QVERIFY(window);
307 QVERIFY(exclusiveContains(window->frameGeometry(), Cursors::self()->mouse()->pos()));
308 QVERIFY(enteredSpy.wait());
309 QCOMPARE(enteredSpy.count(), 1);
310
311 // move the cursor to the second screen
312 input()->pointer()->warp(QPointF(1500, 300));
313 QVERIFY(!exclusiveContains(window->frameGeometry(), Cursors::self()->mouse()->pos()));
314 QVERIFY(leftSpy.wait());
315
316 // now let's remove the screen containing the cursor
317 Test::setOutputConfig({QRect(0, 0, 1280, 1024)});
318 QCOMPARE(workspace()->outputs().count(), 1);
319
320 // this should have warped the cursor
321 QCOMPARE(Cursors::self()->mouse()->pos(), QPoint(639, 511));
322 QVERIFY(exclusiveContains(window->frameGeometry(), Cursors::self()->mouse()->pos()));
323
324 // and we should get an enter event
325 QVERIFY(enteredSpy.wait());
326 QCOMPARE(enteredSpy.count(), 2);
327}
328
329void PointerInputTest::testUpdateFocusOnDecorationDestroy()
330{
331 // This test verifies that a maximized window gets it's pointer focus
332 // if decoration was focused and then destroyed on maximize with BorderlessMaximizedWindows option.
333
334 // create pointer for focus tracking
335 auto pointer = m_seat->createPointer(m_seat);
336 QVERIFY(pointer);
337 QVERIFY(pointer->isValid());
338 QSignalSpy buttonStateChangedSpy(pointer, &KWayland::Client::Pointer::buttonStateChanged);
339
340 // Enable the borderless maximized windows option.
341 auto group = kwinApp()->config()->group(QStringLiteral("Windows"));
342 group.writeEntry("BorderlessMaximizedWindows", true);
343 group.sync();
345 QCOMPARE(options->borderlessMaximizedWindows(), true);
346
347 // Create the test window.
348 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
349 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly));
350 std::unique_ptr<Test::XdgToplevelDecorationV1> decoration(Test::createXdgToplevelDecorationV1(shellSurface.get()));
351
352 QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
353 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
354 QSignalSpy decorationConfigureRequestedSpy(decoration.get(), &Test::XdgToplevelDecorationV1::configureRequested);
355 decoration->set_mode(Test::XdgToplevelDecorationV1::mode_server_side);
356 surface->commit(KWayland::Client::Surface::CommitFlag::None);
357
358 // Wait for the initial configure event.
359 Test::XdgToplevel::States states;
360 QVERIFY(surfaceConfigureRequestedSpy.wait());
361 QCOMPARE(surfaceConfigureRequestedSpy.count(), 1);
362 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(0, 0));
363 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
364 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated));
365 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized));
366
367 // Map the window.
368 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
369 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
370 QVERIFY(window);
371 QVERIFY(window->isActive());
372 QCOMPARE(window->maximizeMode(), MaximizeMode::MaximizeRestore);
373 QCOMPARE(window->requestedMaximizeMode(), MaximizeMode::MaximizeRestore);
374 QCOMPARE(window->isDecorated(), true);
375
376 // We should receive a configure event when the window becomes active.
377 QVERIFY(surfaceConfigureRequestedSpy.wait());
378 QCOMPARE(surfaceConfigureRequestedSpy.count(), 2);
379 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
380 QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated));
381 QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized));
382
383 // Simulate decoration hover
384 quint32 timestamp = 0;
385 Test::pointerMotion(window->frameGeometry().topLeft(), timestamp++);
386 QVERIFY(input()->pointer()->decoration());
387
388 // Maximize when on decoration
390 QVERIFY(surfaceConfigureRequestedSpy.wait());
391 QCOMPARE(surfaceConfigureRequestedSpy.count(), 3);
392 QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(1280, 1024));
393 states = toplevelConfigureRequestedSpy.last().at(1).value<Test::XdgToplevel::States>();
394 QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated));
395 QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized));
396
397 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
398 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
399 Test::render(surface.get(), QSize(1280, 1024), Qt::blue);
400 QVERIFY(frameGeometryChangedSpy.wait());
401 QCOMPARE(window->frameGeometry(), QRect(0, 0, 1280, 1024));
402 QCOMPARE(window->maximizeMode(), MaximizeFull);
403 QCOMPARE(window->requestedMaximizeMode(), MaximizeFull);
404 QCOMPARE(window->isDecorated(), false);
405
406 // Window should have focus, BUG 411884
407 QVERIFY(!input()->pointer()->decoration());
408 Test::pointerButtonPressed(BTN_LEFT, timestamp++);
409 Test::pointerButtonReleased(BTN_LEFT, timestamp++);
410 QVERIFY(buttonStateChangedSpy.wait());
411 QCOMPARE(pointer->enteredSurface(), surface.get());
412
413 // Destroy the window.
414 shellSurface.reset();
415 QVERIFY(Test::waitForWindowClosed(window));
416}
417
418void PointerInputTest::testModifierClickUnrestrictedMove_data()
419{
420 QTest::addColumn<int>("modifierKey");
421 QTest::addColumn<int>("mouseButton");
422 QTest::addColumn<QString>("modKey");
423 QTest::addColumn<bool>("capsLock");
424
425 const QString alt = QStringLiteral("Alt");
426 const QString meta = QStringLiteral("Meta");
427
428 QTest::newRow("Left Alt + Left Click") << KEY_LEFTALT << BTN_LEFT << alt << false;
429 QTest::newRow("Left Alt + Right Click") << KEY_LEFTALT << BTN_RIGHT << alt << false;
430 QTest::newRow("Left Alt + Middle Click") << KEY_LEFTALT << BTN_MIDDLE << alt << false;
431 QTest::newRow("Right Alt + Left Click") << KEY_RIGHTALT << BTN_LEFT << alt << false;
432 QTest::newRow("Right Alt + Right Click") << KEY_RIGHTALT << BTN_RIGHT << alt << false;
433 QTest::newRow("Right Alt + Middle Click") << KEY_RIGHTALT << BTN_MIDDLE << alt << false;
434 // now everything with meta
435 QTest::newRow("Left Meta + Left Click") << KEY_LEFTMETA << BTN_LEFT << meta << false;
436 QTest::newRow("Left Meta + Right Click") << KEY_LEFTMETA << BTN_RIGHT << meta << false;
437 QTest::newRow("Left Meta + Middle Click") << KEY_LEFTMETA << BTN_MIDDLE << meta << false;
438 QTest::newRow("Right Meta + Left Click") << KEY_RIGHTMETA << BTN_LEFT << meta << false;
439 QTest::newRow("Right Meta + Right Click") << KEY_RIGHTMETA << BTN_RIGHT << meta << false;
440 QTest::newRow("Right Meta + Middle Click") << KEY_RIGHTMETA << BTN_MIDDLE << meta << false;
441
442 // and with capslock
443 QTest::newRow("Left Alt + Left Click/CapsLock") << KEY_LEFTALT << BTN_LEFT << alt << true;
444 QTest::newRow("Left Alt + Right Click/CapsLock") << KEY_LEFTALT << BTN_RIGHT << alt << true;
445 QTest::newRow("Left Alt + Middle Click/CapsLock") << KEY_LEFTALT << BTN_MIDDLE << alt << true;
446 QTest::newRow("Right Alt + Left Click/CapsLock") << KEY_RIGHTALT << BTN_LEFT << alt << true;
447 QTest::newRow("Right Alt + Right Click/CapsLock") << KEY_RIGHTALT << BTN_RIGHT << alt << true;
448 QTest::newRow("Right Alt + Middle Click/CapsLock") << KEY_RIGHTALT << BTN_MIDDLE << alt << true;
449 // now everything with meta
450 QTest::newRow("Left Meta + Left Click/CapsLock") << KEY_LEFTMETA << BTN_LEFT << meta << true;
451 QTest::newRow("Left Meta + Right Click/CapsLock") << KEY_LEFTMETA << BTN_RIGHT << meta << true;
452 QTest::newRow("Left Meta + Middle Click/CapsLock") << KEY_LEFTMETA << BTN_MIDDLE << meta << true;
453 QTest::newRow("Right Meta + Left Click/CapsLock") << KEY_RIGHTMETA << BTN_LEFT << meta << true;
454 QTest::newRow("Right Meta + Right Click/CapsLock") << KEY_RIGHTMETA << BTN_RIGHT << meta << true;
455 QTest::newRow("Right Meta + Middle Click/CapsLock") << KEY_RIGHTMETA << BTN_MIDDLE << meta << true;
456}
457
458void PointerInputTest::testModifierClickUnrestrictedMove()
459{
460 // this test ensures that Alt+mouse button press triggers unrestricted move
461
462 // create pointer and signal spy for button events
463 auto pointer = m_seat->createPointer(m_seat);
464 QVERIFY(pointer);
465 QVERIFY(pointer->isValid());
466 QSignalSpy buttonSpy(pointer, &KWayland::Client::Pointer::buttonStateChanged);
467
468 // first modify the config for this run
469 QFETCH(QString, modKey);
470 KConfigGroup group = kwinApp()->config()->group(QStringLiteral("MouseBindings"));
471 group.writeEntry("CommandAllKey", modKey);
472 group.writeEntry("CommandAll1", "Move");
473 group.writeEntry("CommandAll2", "Move");
474 group.writeEntry("CommandAll3", "Move");
475 group.sync();
477 QCOMPARE(options->commandAllModifier(), modKey == QStringLiteral("Alt") ? Qt::AltModifier : Qt::MetaModifier);
481
482 // create a window
483 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
484 std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
485 QVERIFY(surface);
486 Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get());
487 QVERIFY(shellSurface);
488 render(surface.get());
489 QVERIFY(windowAddedSpy.wait());
490 Window *window = workspace()->activeWindow();
491 QVERIFY(window);
492
493 // move cursor on window
494 input()->pointer()->warp(window->frameGeometry().center());
495
496 // simulate modifier+click
497 quint32 timestamp = 1;
498 QFETCH(bool, capsLock);
499 if (capsLock) {
500 Test::keyboardKeyPressed(KEY_CAPSLOCK, timestamp++);
501 }
502 QFETCH(int, modifierKey);
503 QFETCH(int, mouseButton);
504 Test::keyboardKeyPressed(modifierKey, timestamp++);
505 QVERIFY(!window->isInteractiveMove());
506 Test::pointerButtonPressed(mouseButton, timestamp++);
507 QVERIFY(window->isInteractiveMove());
508 // release modifier should not change it
509 Test::keyboardKeyReleased(modifierKey, timestamp++);
510 QVERIFY(window->isInteractiveMove());
511 // but releasing the key should end move/resize
512 Test::pointerButtonReleased(mouseButton, timestamp++);
513 QVERIFY(!window->isInteractiveMove());
514 if (capsLock) {
515 Test::keyboardKeyReleased(KEY_CAPSLOCK, timestamp++);
516 }
517
518 // all of that should not have triggered button events on the surface
519 QCOMPARE(buttonSpy.count(), 0);
520 // also waiting shouldn't give us the event
521 QVERIFY(Test::waylandSync());
522 QCOMPARE(buttonSpy.count(), 0);
523}
524
525void PointerInputTest::testModifierClickUnrestrictedFullscreenMove()
526{
527 // this test ensures that Meta+mouse button press triggers unrestricted move for fullscreen windows
528 if (workspace()->outputs().size() < 2) {
530 QRect(0, 0, 1280, 1024),
531 QRect(1280, 0, 1280, 1024),
532 });
533 }
534
535 // first modify the config for this run
536 KConfigGroup group = kwinApp()->config()->group(QStringLiteral("MouseBindings"));
537 group.writeEntry("CommandAllKey", "Meta");
538 group.writeEntry("CommandAll1", "Move");
539 group.writeEntry("CommandAll2", "Move");
540 group.writeEntry("CommandAll3", "Move");
541 group.sync();
543 QCOMPARE(options->commandAllModifier(), Qt::MetaModifier);
547
548 // create a window
549 std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
550 QVERIFY(surface);
551 Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get());
552 QVERIFY(shellSurface);
553 shellSurface->set_fullscreen(nullptr);
554 QSignalSpy toplevelConfigureRequestedSpy(shellSurface, &Test::XdgToplevel::configureRequested);
555 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
556 QVERIFY(surfaceConfigureRequestedSpy.wait());
557 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
558 Window *window = Test::renderAndWaitForShown(surface.get(), toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), Qt::blue);
559 QVERIFY(window);
560 QVERIFY(window->isFullScreen());
561
562 // move cursor on window
563 input()->pointer()->warp(window->frameGeometry().center());
564
565 // simulate modifier+click
566 quint32 timestamp = 1;
567 Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++);
568 QVERIFY(!window->isInteractiveMove());
569 Test::pointerButtonPressed(BTN_LEFT, timestamp++);
570 QVERIFY(window->isInteractiveMove());
571 // release modifier should not change it
572 Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++);
573 QVERIFY(window->isInteractiveMove());
574 // but releasing the key should end move/resize
575 Test::pointerButtonReleased(BTN_LEFT, timestamp++);
576 QVERIFY(!window->isInteractiveMove());
577}
578
579void PointerInputTest::testModifierClickUnrestrictedMoveGlobalShortcutsDisabled()
580{
581 // this test ensures that Alt+mouse button press triggers unrestricted move
582
583 // create pointer and signal spy for button events
584 auto pointer = m_seat->createPointer(m_seat);
585 QVERIFY(pointer);
586 QVERIFY(pointer->isValid());
587 QSignalSpy buttonSpy(pointer, &KWayland::Client::Pointer::buttonStateChanged);
588
589 // first modify the config for this run
590 KConfigGroup group = kwinApp()->config()->group(QStringLiteral("MouseBindings"));
591 group.writeEntry("CommandAllKey", "Meta");
592 group.writeEntry("CommandAll1", "Move");
593 group.writeEntry("CommandAll2", "Move");
594 group.writeEntry("CommandAll3", "Move");
595 group.sync();
597 QCOMPARE(options->commandAllModifier(), Qt::MetaModifier);
601
602 // create a window
603 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
604 std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
605 QVERIFY(surface);
606 Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get());
607 QVERIFY(shellSurface);
608 render(surface.get());
609 QVERIFY(windowAddedSpy.wait());
610 Window *window = workspace()->activeWindow();
611 QVERIFY(window);
612
613 // disable global shortcuts
614 QVERIFY(!workspace()->globalShortcutsDisabled());
616 QVERIFY(workspace()->globalShortcutsDisabled());
617
618 // move cursor on window
619 input()->pointer()->warp(window->frameGeometry().center());
620
621 // simulate modifier+click
622 quint32 timestamp = 1;
623 Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++);
624 QVERIFY(!window->isInteractiveMove());
625 Test::pointerButtonPressed(BTN_LEFT, timestamp++);
626 QVERIFY(!window->isInteractiveMove());
627 // release modifier should not change it
628 Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++);
629 QVERIFY(!window->isInteractiveMove());
630 Test::pointerButtonReleased(BTN_LEFT, timestamp++);
631
633}
634
635void PointerInputTest::testModifierScrollOpacity_data()
636{
637 QTest::addColumn<int>("modifierKey");
638 QTest::addColumn<QString>("modKey");
639 QTest::addColumn<bool>("capsLock");
640
641 const QString alt = QStringLiteral("Alt");
642 const QString meta = QStringLiteral("Meta");
643
644 QTest::newRow("Left Alt") << KEY_LEFTALT << alt << false;
645 QTest::newRow("Right Alt") << KEY_RIGHTALT << alt << false;
646 QTest::newRow("Left Meta") << KEY_LEFTMETA << meta << false;
647 QTest::newRow("Right Meta") << KEY_RIGHTMETA << meta << false;
648 QTest::newRow("Left Alt/CapsLock") << KEY_LEFTALT << alt << true;
649 QTest::newRow("Right Alt/CapsLock") << KEY_RIGHTALT << alt << true;
650 QTest::newRow("Left Meta/CapsLock") << KEY_LEFTMETA << meta << true;
651 QTest::newRow("Right Meta/CapsLock") << KEY_RIGHTMETA << meta << true;
652}
653
654void PointerInputTest::testModifierScrollOpacity()
655{
656 // this test verifies that mod+wheel performs a window operation and does not
657 // pass the wheel to the window
658
659 // create pointer and signal spy for button events
660 auto pointer = m_seat->createPointer(m_seat);
661 QVERIFY(pointer);
662 QVERIFY(pointer->isValid());
663 QSignalSpy axisSpy(pointer, &KWayland::Client::Pointer::axisChanged);
664
665 // first modify the config for this run
666 QFETCH(QString, modKey);
667 KConfigGroup group = kwinApp()->config()->group(QStringLiteral("MouseBindings"));
668 group.writeEntry("CommandAllKey", modKey);
669 group.writeEntry("CommandAllWheel", "change opacity");
670 group.sync();
672
673 // create a window
674 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
675 std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
676 QVERIFY(surface);
677 Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get());
678 QVERIFY(shellSurface);
679 render(surface.get());
680 QVERIFY(windowAddedSpy.wait());
681 Window *window = workspace()->activeWindow();
682 QVERIFY(window);
683 // set the opacity to 0.5
684 window->setOpacity(0.5);
685 QCOMPARE(window->opacity(), 0.5);
686
687 // move cursor on window
688 input()->pointer()->warp(window->frameGeometry().center());
689
690 // simulate modifier+wheel
691 quint32 timestamp = 1;
692 QFETCH(bool, capsLock);
693 if (capsLock) {
694 Test::keyboardKeyPressed(KEY_CAPSLOCK, timestamp++);
695 }
696 QFETCH(int, modifierKey);
697 Test::keyboardKeyPressed(modifierKey, timestamp++);
698 Test::pointerAxisVertical(-5, timestamp++);
699 QCOMPARE(window->opacity(), 0.6);
700 Test::pointerAxisVertical(5, timestamp++);
701 QCOMPARE(window->opacity(), 0.5);
702 Test::keyboardKeyReleased(modifierKey, timestamp++);
703 if (capsLock) {
704 Test::keyboardKeyReleased(KEY_CAPSLOCK, timestamp++);
705 }
706
707 // axis should have been filtered out
708 QCOMPARE(axisSpy.count(), 0);
709 QVERIFY(Test::waylandSync());
710 QCOMPARE(axisSpy.count(), 0);
711}
712
713void PointerInputTest::testModifierScrollOpacityGlobalShortcutsDisabled()
714{
715 // this test verifies that mod+wheel performs a window operation and does not
716 // pass the wheel to the window
717
718 // create pointer and signal spy for button events
719 auto pointer = m_seat->createPointer(m_seat);
720 QVERIFY(pointer);
721 QVERIFY(pointer->isValid());
722 QSignalSpy axisSpy(pointer, &KWayland::Client::Pointer::axisChanged);
723
724 // first modify the config for this run
725 KConfigGroup group = kwinApp()->config()->group(QStringLiteral("MouseBindings"));
726 group.writeEntry("CommandAllKey", "Meta");
727 group.writeEntry("CommandAllWheel", "change opacity");
728 group.sync();
730
731 // create a window
732 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
733 std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
734 QVERIFY(surface);
735 Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get());
736 QVERIFY(shellSurface);
737 render(surface.get());
738 QVERIFY(windowAddedSpy.wait());
739 Window *window = workspace()->activeWindow();
740 QVERIFY(window);
741 // set the opacity to 0.5
742 window->setOpacity(0.5);
743 QCOMPARE(window->opacity(), 0.5);
744
745 // move cursor on window
746 input()->pointer()->warp(window->frameGeometry().center());
747
748 // disable global shortcuts
749 QVERIFY(!workspace()->globalShortcutsDisabled());
751 QVERIFY(workspace()->globalShortcutsDisabled());
752
753 // simulate modifier+wheel
754 quint32 timestamp = 1;
755 Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++);
756 Test::pointerAxisVertical(-5, timestamp++);
757 QCOMPARE(window->opacity(), 0.5);
758 Test::pointerAxisVertical(5, timestamp++);
759 QCOMPARE(window->opacity(), 0.5);
760 Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++);
761
763}
764
765void PointerInputTest::testScrollAction()
766{
767 // this test verifies that scroll on inactive window performs a mouse action
768 auto pointer = m_seat->createPointer(m_seat);
769 QVERIFY(pointer);
770 QVERIFY(pointer->isValid());
771 QSignalSpy axisSpy(pointer, &KWayland::Client::Pointer::axisChanged);
772
773 // first modify the config for this run
774 KConfigGroup group = kwinApp()->config()->group(QStringLiteral("MouseBindings"));
775 group.writeEntry("CommandWindowWheel", "activate and scroll");
776 group.sync();
778 // create two windows
779 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
780 std::unique_ptr<KWayland::Client::Surface> surface1 = Test::createSurface();
781 QVERIFY(surface1);
782 Test::XdgToplevel *shellSurface1 = Test::createXdgToplevelSurface(surface1.get(), surface1.get());
783 QVERIFY(shellSurface1);
784 render(surface1.get());
785 QVERIFY(windowAddedSpy.wait());
786 Window *window1 = workspace()->activeWindow();
787 QVERIFY(window1);
788 std::unique_ptr<KWayland::Client::Surface> surface2 = Test::createSurface();
789 QVERIFY(surface2);
790 Test::XdgToplevel *shellSurface2 = Test::createXdgToplevelSurface(surface2.get(), surface2.get());
791 QVERIFY(shellSurface2);
792 render(surface2.get());
793 QVERIFY(windowAddedSpy.wait());
794 Window *window2 = workspace()->activeWindow();
795 QVERIFY(window2);
796 QVERIFY(window1 != window2);
797
798 // move cursor to the inactive window
799 input()->pointer()->warp(window1->frameGeometry().center());
800
801 quint32 timestamp = 1;
802 QVERIFY(!window1->isActive());
803 Test::pointerAxisVertical(5, timestamp++);
804 QVERIFY(window1->isActive());
805
806 // but also the wheel event should be passed to the window
807 QVERIFY(axisSpy.wait());
808}
809
810void PointerInputTest::testFocusFollowsMouse()
811{
812 // need to create a pointer, otherwise it doesn't accept focus
813 auto pointer = m_seat->createPointer(m_seat);
814 QVERIFY(pointer);
815 QVERIFY(pointer->isValid());
816 // move cursor out of the way of first window to be created
817 input()->pointer()->warp(QPointF(900, 900));
818
819 // first modify the config for this run
820 KConfigGroup group = kwinApp()->config()->group(QStringLiteral("Windows"));
821 group.writeEntry("AutoRaise", true);
822 group.writeEntry("AutoRaiseInterval", 20);
823 group.writeEntry("DelayFocusInterval", 200);
824 group.writeEntry("FocusPolicy", "FocusFollowsMouse");
825 group.sync();
827 // verify the settings
829 QVERIFY(options->isAutoRaise());
830 QCOMPARE(options->autoRaiseInterval(), 20);
831 QCOMPARE(options->delayFocusInterval(), 200);
832
833 // create two windows
834 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
835 std::unique_ptr<KWayland::Client::Surface> surface1 = Test::createSurface();
836 QVERIFY(surface1);
837 Test::XdgToplevel *shellSurface1 = Test::createXdgToplevelSurface(surface1.get(), surface1.get());
838 QVERIFY(shellSurface1);
839 render(surface1.get(), QSize(800, 800));
840 QVERIFY(windowAddedSpy.wait());
841 Window *window1 = workspace()->activeWindow();
842 QVERIFY(window1);
843 std::unique_ptr<KWayland::Client::Surface> surface2 = Test::createSurface();
844 QVERIFY(surface2);
845 Test::XdgToplevel *shellSurface2 = Test::createXdgToplevelSurface(surface2.get(), surface2.get());
846 QVERIFY(shellSurface2);
847 render(surface2.get(), QSize(800, 800));
848 QVERIFY(windowAddedSpy.wait());
849 Window *window2 = workspace()->activeWindow();
850 QVERIFY(window2);
851 QVERIFY(window1 != window2);
852 QCOMPARE(workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop()), window2);
853 // geometry of the two windows should be overlapping
854 QVERIFY(window1->frameGeometry().intersects(window2->frameGeometry()));
855
856 // signal spies for active window changed and stacking order changed
857 QSignalSpy activeWindowChangedSpy(workspace(), &Workspace::windowActivated);
858 QSignalSpy stackingOrderChangedSpy(workspace(), &Workspace::stackingOrderChanged);
859
860 QVERIFY(!window1->isActive());
861 QVERIFY(window2->isActive());
862
863 // move on top of first window
864 QVERIFY(exclusiveContains(window1->frameGeometry(), QPointF(10, 10)));
865 QVERIFY(!exclusiveContains(window2->frameGeometry(), QPointF(10, 10)));
866 input()->pointer()->warp(QPointF(10, 10));
867 QVERIFY(stackingOrderChangedSpy.wait());
868 QCOMPARE(stackingOrderChangedSpy.count(), 1);
869 QCOMPARE(workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop()), window1);
870 QTRY_VERIFY(window1->isActive());
871
872 // move on second window, but move away before active window change delay hits
873 input()->pointer()->warp(QPointF(810, 810));
874 QVERIFY(stackingOrderChangedSpy.wait());
875 QCOMPARE(stackingOrderChangedSpy.count(), 2);
876 QCOMPARE(workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop()), window2);
877 input()->pointer()->warp(QPointF(10, 10));
878 QVERIFY(!activeWindowChangedSpy.wait(250));
879 QVERIFY(window1->isActive());
880 QCOMPARE(workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop()), window1);
881 // as we moved back on window 1 that should been raised in the mean time
882 QCOMPARE(stackingOrderChangedSpy.count(), 3);
883
884 // quickly move on window 2 and back on window 1 should not raise window 2
885 input()->pointer()->warp(QPointF(810, 810));
886 input()->pointer()->warp(QPointF(10, 10));
887 QVERIFY(!stackingOrderChangedSpy.wait(250));
888}
889
890void PointerInputTest::testMouseActionInactiveWindow_data()
891{
892 QTest::addColumn<quint32>("button");
893
894 QTest::newRow("Left") << quint32(BTN_LEFT);
895 QTest::newRow("Middle") << quint32(BTN_MIDDLE);
896 QTest::newRow("Right") << quint32(BTN_RIGHT);
897}
898
899void PointerInputTest::testMouseActionInactiveWindow()
900{
901 // this test performs the mouse button window action on an inactive window
902 // it should activate the window and raise it
903
904 // first modify the config for this run - disable FocusFollowsMouse
905 KConfigGroup group = kwinApp()->config()->group(QStringLiteral("Windows"));
906 group.writeEntry("FocusPolicy", "ClickToFocus");
907 group.sync();
908 group = kwinApp()->config()->group(QStringLiteral("MouseBindings"));
909 group.writeEntry("CommandWindow1", "Activate, raise and pass click");
910 group.writeEntry("CommandWindow2", "Activate, raise and pass click");
911 group.writeEntry("CommandWindow3", "Activate, raise and pass click");
912 group.sync();
914
915 // create two windows
916 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
917 std::unique_ptr<KWayland::Client::Surface> surface1 = Test::createSurface();
918 QVERIFY(surface1);
919 Test::XdgToplevel *shellSurface1 = Test::createXdgToplevelSurface(surface1.get(), surface1.get());
920 QVERIFY(shellSurface1);
921 render(surface1.get(), QSize(800, 800));
922 QVERIFY(windowAddedSpy.wait());
923 Window *window1 = workspace()->activeWindow();
924 QVERIFY(window1);
925 std::unique_ptr<KWayland::Client::Surface> surface2 = Test::createSurface();
926 QVERIFY(surface2);
927 Test::XdgToplevel *shellSurface2 = Test::createXdgToplevelSurface(surface2.get(), surface2.get());
928 QVERIFY(shellSurface2);
929 render(surface2.get(), QSize(800, 800));
930 QVERIFY(windowAddedSpy.wait());
931 Window *window2 = workspace()->activeWindow();
932 QVERIFY(window2);
933 QVERIFY(window1 != window2);
934 QCOMPARE(workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop()), window2);
935 // geometry of the two windows should be overlapping
936 QVERIFY(window1->frameGeometry().intersects(window2->frameGeometry()));
937
938 // signal spies for active window changed and stacking order changed
939 QSignalSpy activeWindowChangedSpy(workspace(), &Workspace::windowActivated);
940 QSignalSpy stackingOrderChangedSpy(workspace(), &Workspace::stackingOrderChanged);
941
942 QVERIFY(!window1->isActive());
943 QVERIFY(window2->isActive());
944
945 // move on top of first window
946 QVERIFY(exclusiveContains(window1->frameGeometry(), QPointF(10, 10)));
947 QVERIFY(!exclusiveContains(window2->frameGeometry(), QPointF(10, 10)));
948 input()->pointer()->warp(QPointF(10, 10));
949 // no focus follows mouse
950 QVERIFY(stackingOrderChangedSpy.isEmpty());
951 QVERIFY(activeWindowChangedSpy.isEmpty());
952 QVERIFY(window2->isActive());
953 // and click
954 quint32 timestamp = 1;
955 QFETCH(quint32, button);
956 Test::pointerButtonPressed(button, timestamp++);
957 // should raise window1 and activate it
958 QCOMPARE(stackingOrderChangedSpy.count(), 1);
959 QVERIFY(!activeWindowChangedSpy.isEmpty());
960 QCOMPARE(workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop()), window1);
961 QVERIFY(window1->isActive());
962 QVERIFY(!window2->isActive());
963
964 // release again
965 Test::pointerButtonReleased(button, timestamp++);
966}
967
968void PointerInputTest::testMouseActionActiveWindow_data()
969{
970 QTest::addColumn<bool>("clickRaise");
971 QTest::addColumn<quint32>("button");
972
973 for (quint32 i = BTN_LEFT; i < BTN_JOYSTICK; i++) {
974 QByteArray number = QByteArray::number(i, 16);
975 QTest::newRow(QByteArrayLiteral("click raise/").append(number).constData()) << true << i;
976 QTest::newRow(QByteArrayLiteral("no click raise/").append(number).constData()) << false << i;
977 }
978}
979
980void PointerInputTest::testMouseActionActiveWindow()
981{
982 // this test verifies the mouse action performed on an active window
983 // for all buttons it should trigger a window raise depending on the
984 // click raise option
985
986 // create a button spy - all clicks should be passed through
987 auto pointer = m_seat->createPointer(m_seat);
988 QVERIFY(pointer);
989 QVERIFY(pointer->isValid());
990 QSignalSpy buttonSpy(pointer, &KWayland::Client::Pointer::buttonStateChanged);
991
992 // adjust config for this run
993 QFETCH(bool, clickRaise);
994 KConfigGroup group = kwinApp()->config()->group(QStringLiteral("Windows"));
995 group.writeEntry("ClickRaise", clickRaise);
996 group.sync();
998 QCOMPARE(options->isClickRaise(), clickRaise);
999
1000 // create two windows
1001 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
1002 std::unique_ptr<KWayland::Client::Surface> surface1 = Test::createSurface();
1003 QVERIFY(surface1);
1004 Test::XdgToplevel *shellSurface1 = Test::createXdgToplevelSurface(surface1.get(), surface1.get());
1005 QVERIFY(shellSurface1);
1006 render(surface1.get(), QSize(800, 800));
1007 QVERIFY(windowAddedSpy.wait());
1008 Window *window1 = workspace()->activeWindow();
1009 QVERIFY(window1);
1010 QSignalSpy window1DestroyedSpy(window1, &QObject::destroyed);
1011 std::unique_ptr<KWayland::Client::Surface> surface2 = Test::createSurface();
1012 QVERIFY(surface2);
1013 Test::XdgToplevel *shellSurface2 = Test::createXdgToplevelSurface(surface2.get(), surface2.get());
1014 QVERIFY(shellSurface2);
1015 render(surface2.get(), QSize(800, 800));
1016 QVERIFY(windowAddedSpy.wait());
1017 Window *window2 = workspace()->activeWindow();
1018 QVERIFY(window2);
1019 QVERIFY(window1 != window2);
1020 QSignalSpy window2DestroyedSpy(window2, &QObject::destroyed);
1021 QCOMPARE(workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop()), window2);
1022 // geometry of the two windows should be overlapping
1023 QVERIFY(window1->frameGeometry().intersects(window2->frameGeometry()));
1024 // lower the currently active window
1025 workspace()->lowerWindow(window2);
1026 QCOMPARE(workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop()), window1);
1027
1028 // signal spy for stacking order spy
1029 QSignalSpy stackingOrderChangedSpy(workspace(), &Workspace::stackingOrderChanged);
1030
1031 // move on top of second window
1032 QVERIFY(!exclusiveContains(window1->frameGeometry(), QPointF(900, 900)));
1033 QVERIFY(exclusiveContains(window2->frameGeometry(), QPointF(900, 900)));
1034 input()->pointer()->warp(QPointF(900, 900));
1035
1036 // and click
1037 quint32 timestamp = 1;
1038 QFETCH(quint32, button);
1039 Test::pointerButtonPressed(button, timestamp++);
1040 QVERIFY(buttonSpy.wait());
1041 if (clickRaise) {
1042 QCOMPARE(stackingOrderChangedSpy.count(), 1);
1043 QCOMPARE(workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop()), window2);
1044 } else {
1045 QCOMPARE(stackingOrderChangedSpy.count(), 0);
1046 QCOMPARE(workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop()), window1);
1047 }
1048
1049 // release again
1050 Test::pointerButtonReleased(button, timestamp++);
1051
1052 surface1.reset();
1053 QVERIFY(window1DestroyedSpy.wait());
1054 surface2.reset();
1055 QVERIFY(window2DestroyedSpy.wait());
1056}
1057
1058void PointerInputTest::testCursorImage()
1059{
1060 // this test verifies that the pointer image gets updated correctly from the client provided data
1061
1062 // we need a pointer to get the enter event
1063 auto pointer = m_seat->createPointer(m_seat);
1064 QVERIFY(pointer);
1065 QVERIFY(pointer->isValid());
1066 QSignalSpy enteredSpy(pointer, &KWayland::Client::Pointer::entered);
1067
1068 // move cursor somewhere the new window won't open
1069 auto cursor = Cursors::self()->mouse();
1070 input()->pointer()->warp(QPointF(800, 800));
1071 auto p = input()->pointer();
1072 // at the moment it should be the fallback cursor
1073 const QImage fallbackCursor = kwinApp()->cursorImage().image();
1074 QVERIFY(!fallbackCursor.isNull());
1075
1076 // create a window
1077 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
1078 std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
1079 QVERIFY(surface);
1080 Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get());
1081 QVERIFY(shellSurface);
1082 render(surface.get());
1083 QVERIFY(windowAddedSpy.wait());
1084 Window *window = workspace()->activeWindow();
1085 QVERIFY(window);
1086
1087 // move cursor to center of window, this should first set a null pointer, so we still show old cursor
1088 input()->pointer()->warp(window->frameGeometry().center());
1089 QCOMPARE(p->focus(), window);
1090 QCOMPARE(kwinApp()->cursorImage().image(), fallbackCursor);
1091 QVERIFY(enteredSpy.wait());
1092
1093 // create a cursor on the pointer
1094 auto cursorSurface = Test::createSurface();
1095 QVERIFY(cursorSurface);
1096 QSignalSpy cursorRenderedSpy(cursorSurface.get(), &KWayland::Client::Surface::frameRendered);
1097 QImage red = QImage(QSize(10, 10), QImage::Format_ARGB32_Premultiplied);
1098 red.fill(Qt::red);
1099 cursorSurface->attachBuffer(Test::waylandShmPool()->createBuffer(red));
1100 cursorSurface->damage(QRect(0, 0, 10, 10));
1101 cursorSurface->commit();
1102 pointer->setCursor(cursorSurface.get(), QPoint(5, 5));
1103 QVERIFY(cursorRenderedSpy.wait());
1104 QCOMPARE(kwinApp()->cursorImage().image(), red);
1105 QCOMPARE(cursor->hotspot(), QPoint(5, 5));
1106 // change hotspot
1107 pointer->setCursor(cursorSurface.get(), QPoint(6, 6));
1109 QTRY_COMPARE(cursor->hotspot(), QPoint(6, 6));
1110 QCOMPARE(kwinApp()->cursorImage().image(), red);
1111
1112 // change the buffer
1113 QImage blue = QImage(QSize(10, 10), QImage::Format_ARGB32_Premultiplied);
1114 blue.fill(Qt::blue);
1115 auto b = Test::waylandShmPool()->createBuffer(blue);
1116 cursorSurface->attachBuffer(b);
1117 cursorSurface->damage(QRect(0, 0, 10, 10));
1118 cursorSurface->commit();
1119 QVERIFY(cursorRenderedSpy.wait());
1120 QTRY_COMPARE(kwinApp()->cursorImage().image(), blue);
1121 QCOMPARE(cursor->hotspot(), QPoint(6, 6));
1122
1123 // hide the cursor
1124 pointer->setCursor(nullptr);
1126 QTRY_VERIFY(kwinApp()->cursorImage().image().isNull());
1127
1128 // move cursor somewhere else, should reset to fallback cursor
1129 input()->pointer()->warp(window->frameGeometry().bottomLeft() + QPoint(20, 20));
1130 QVERIFY(!p->focus());
1131 QVERIFY(!kwinApp()->cursorImage().image().isNull());
1132 QCOMPARE(kwinApp()->cursorImage().image(), fallbackCursor);
1133}
1134
1135static QByteArray currentCursorShape()
1136{
1137 if (auto source = qobject_cast<ShapeCursorSource *>(Cursors::self()->currentCursor()->source())) {
1138 return source->shape();
1139 }
1140 return QByteArray();
1141}
1142
1143void PointerInputTest::testCursorShapeV1()
1144{
1145 // this test verifies the integration of the cursor-shape-v1 protocol
1146
1147 // get the pointer
1148 std::unique_ptr<KWayland::Client::Pointer> pointer(m_seat->createPointer());
1149 std::unique_ptr<Test::CursorShapeDeviceV1> cursorShapeDevice(Test::createCursorShapeDeviceV1(pointer.get()));
1150
1151 // move cursor somewhere the new window won't open
1152 input()->pointer()->warp(QPointF(800, 800));
1153 QCOMPARE(currentCursorShape(), QByteArrayLiteral("default"));
1154
1155 // create a window
1156 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
1157 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
1158 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 100), Qt::cyan);
1159 QVERIFY(window);
1160
1161 // move the pointer to the center of the window
1162 QSignalSpy enteredSpy(pointer.get(), &KWayland::Client::Pointer::entered);
1163 input()->pointer()->warp(window->frameGeometry().center());
1164 QVERIFY(enteredSpy.wait());
1165
1166 // set a custom cursor shape
1167 QSignalSpy cursorChanged(Cursors::self(), &Cursors::currentCursorChanged);
1168 cursorShapeDevice->set_shape(enteredSpy.last().at(0).value<quint32>(), Test::CursorShapeDeviceV1::shape_text);
1169 QVERIFY(cursorChanged.wait());
1170 QCOMPARE(currentCursorShape(), QByteArray("text"));
1171
1172 // cursor shape won't be changed if the window has no pointer focus
1173 input()->pointer()->warp(QPointF(800, 800));
1174 QCOMPARE(currentCursorShape(), QByteArrayLiteral("default"));
1175 cursorShapeDevice->set_shape(enteredSpy.last().at(0).value<quint32>(), Test::CursorShapeDeviceV1::shape_grab);
1176 QVERIFY(Test::waylandSync());
1177 QCOMPARE(currentCursorShape(), QByteArrayLiteral("default"));
1178}
1179
1180class HelperEffect : public Effect
1181{
1182 Q_OBJECT
1183public:
1185 {
1186 }
1187 ~HelperEffect() override
1188 {
1189 }
1190};
1191
1192void PointerInputTest::testEffectOverrideCursorImage()
1193{
1194 // this test verifies the effect cursor override handling
1195
1196 // we need a pointer to get the enter event and set a cursor
1197 std::unique_ptr<KWayland::Client::Pointer> pointer(m_seat->createPointer());
1198 std::unique_ptr<Test::CursorShapeDeviceV1> cursorShapeDevice(Test::createCursorShapeDeviceV1(pointer.get()));
1199 QSignalSpy enteredSpy(pointer.get(), &KWayland::Client::Pointer::entered);
1200 QSignalSpy leftSpy(pointer.get(), &KWayland::Client::Pointer::left);
1201 QSignalSpy cursorChanged(Cursors::self(), &Cursors::currentCursorChanged);
1202
1203 // move cursor somewhere the new window won't open
1204 input()->pointer()->warp(QPointF(800, 800));
1205
1206 // now let's create a window
1207 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
1208 std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
1209 QVERIFY(surface);
1210 Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get());
1211 QVERIFY(shellSurface);
1212 render(surface.get());
1213 QVERIFY(windowAddedSpy.wait());
1214 Window *window = workspace()->activeWindow();
1215 QVERIFY(window);
1216
1217 // and move cursor to the window
1218 QVERIFY(!exclusiveContains(window->frameGeometry(), QPoint(800, 800)));
1219 input()->pointer()->warp(window->frameGeometry().center());
1220 QVERIFY(enteredSpy.wait());
1221 cursorShapeDevice->set_shape(enteredSpy.last().at(0).value<quint32>(), Test::CursorShapeDeviceV1::shape_wait);
1222 QVERIFY(cursorChanged.wait());
1223 QCOMPARE(currentCursorShape(), QByteArray("wait"));
1224
1225 // now create an effect and set an override cursor
1226 std::unique_ptr<HelperEffect> effect(new HelperEffect);
1227 effects->startMouseInterception(effect.get(), Qt::SizeAllCursor);
1228 QCOMPARE(currentCursorShape(), QByteArrayLiteral("all-scroll"));
1229
1230 // let's change to arrow cursor, this should be our fallback
1231 effects->defineCursor(Qt::ArrowCursor);
1232 QCOMPARE(currentCursorShape(), QByteArrayLiteral("default"));
1233
1234 // back to size all
1235 effects->defineCursor(Qt::SizeAllCursor);
1236 QCOMPARE(currentCursorShape(), QByteArrayLiteral("all-scroll"));
1237
1238 // move cursor outside the window area
1239 input()->pointer()->warp(QPointF(800, 800));
1240 QCOMPARE(currentCursorShape(), QByteArrayLiteral("all-scroll"));
1241
1242 // move cursor to area of window
1243 input()->pointer()->warp(window->frameGeometry().center());
1244 // this should not result in an enter event
1245 QVERIFY(Test::waylandSync());
1246 QCOMPARE(enteredSpy.count(), 1);
1247
1248 // after ending the interception we should get an enter event
1249 effects->stopMouseInterception(effect.get());
1250 QVERIFY(enteredSpy.wait());
1251 cursorShapeDevice->set_shape(enteredSpy.last().at(0).value<quint32>(), Test::CursorShapeDeviceV1::shape_crosshair);
1252 QVERIFY(cursorChanged.wait());
1253 QCOMPARE(currentCursorShape(), QByteArrayLiteral("crosshair"));
1254}
1255
1256void PointerInputTest::testPopup()
1257{
1258 // this test validates the basic popup behavior
1259 // a button press outside the window should dismiss the popup
1260
1261 // first create a parent surface
1262 auto pointer = m_seat->createPointer(m_seat);
1263 QVERIFY(pointer);
1264 QVERIFY(pointer->isValid());
1265 QSignalSpy enteredSpy(pointer, &KWayland::Client::Pointer::entered);
1266 QSignalSpy leftSpy(pointer, &KWayland::Client::Pointer::left);
1267 QSignalSpy buttonStateChangedSpy(pointer, &KWayland::Client::Pointer::buttonStateChanged);
1268 QSignalSpy motionSpy(pointer, &KWayland::Client::Pointer::motion);
1269
1270 input()->pointer()->warp(QPointF(800, 800));
1271
1272 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
1273 std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
1274 QVERIFY(surface);
1275 Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get());
1276 QVERIFY(shellSurface);
1277 render(surface.get());
1278 QVERIFY(windowAddedSpy.wait());
1279 Window *window = workspace()->activeWindow();
1280 QVERIFY(window);
1281 QCOMPARE(window->hasPopupGrab(), false);
1282 // move pointer into window
1283 QVERIFY(!exclusiveContains(window->frameGeometry(), QPoint(800, 800)));
1284 input()->pointer()->warp(window->frameGeometry().center());
1285 QVERIFY(enteredSpy.wait());
1286 // click inside window to create serial
1287 quint32 timestamp = 0;
1288 Test::pointerButtonPressed(BTN_LEFT, timestamp++);
1289 Test::pointerButtonReleased(BTN_LEFT, timestamp++);
1290 QVERIFY(buttonStateChangedSpy.wait());
1291
1292 // now create the popup surface
1293 std::unique_ptr<Test::XdgPositioner> positioner(Test::createXdgPositioner());
1294 positioner->set_size(100, 50);
1295 positioner->set_anchor_rect(0, 0, 80, 20);
1296 positioner->set_anchor(Test::XdgPositioner::anchor_bottom_right);
1297 positioner->set_gravity(Test::XdgPositioner::gravity_bottom_right);
1298 std::unique_ptr<KWayland::Client::Surface> popupSurface = Test::createSurface();
1299 QVERIFY(popupSurface);
1300 Test::XdgPopup *popupShellSurface = Test::createXdgPopupSurface(popupSurface.get(), shellSurface->xdgSurface(), positioner.get());
1301 QVERIFY(popupShellSurface);
1302 QSignalSpy doneReceivedSpy(popupShellSurface, &Test::XdgPopup::doneReceived);
1303 popupShellSurface->grab(*Test::waylandSeat(), 0); // FIXME: Serial.
1304 render(popupSurface.get(), QSize(100, 50));
1305 QVERIFY(windowAddedSpy.wait());
1306 auto popupWindow = windowAddedSpy.last().first().value<Window *>();
1307 QVERIFY(popupWindow);
1308 QVERIFY(popupWindow != window);
1309 QCOMPARE(window, workspace()->activeWindow());
1310 QCOMPARE(popupWindow->transientFor(), window);
1311 QCOMPARE(popupWindow->pos(), window->pos() + QPoint(80, 20));
1312 QCOMPARE(popupWindow->hasPopupGrab(), true);
1313
1314 // let's move the pointer into the center of the window
1315 input()->pointer()->warp(popupWindow->frameGeometry().center());
1316 QVERIFY(enteredSpy.wait());
1317 QCOMPARE(enteredSpy.count(), 2);
1318 QCOMPARE(leftSpy.count(), 1);
1319 QCOMPARE(pointer->enteredSurface(), popupSurface.get());
1320
1321 // let's move the pointer outside of the popup window
1322 // this should not really change anything, it gets a leave event
1323 input()->pointer()->warp(popupWindow->frameGeometry().bottomRight() + QPoint(2, 2));
1324 QVERIFY(leftSpy.wait());
1325 QCOMPARE(leftSpy.count(), 2);
1326 QVERIFY(doneReceivedSpy.isEmpty());
1327 // now click, should trigger popupDone
1328 Test::pointerButtonPressed(BTN_LEFT, timestamp++);
1329 QVERIFY(doneReceivedSpy.wait());
1330 Test::pointerButtonReleased(BTN_LEFT, timestamp++);
1331}
1332
1333void PointerInputTest::testDecoCancelsPopup()
1334{
1335 // this test verifies that clicking the window decoration of parent window
1336 // cancels the popup
1337
1338 // first create a parent surface
1339 auto pointer = m_seat->createPointer(m_seat);
1340 QVERIFY(pointer);
1341 QVERIFY(pointer->isValid());
1342 QSignalSpy enteredSpy(pointer, &KWayland::Client::Pointer::entered);
1343 QSignalSpy leftSpy(pointer, &KWayland::Client::Pointer::left);
1344 QSignalSpy buttonStateChangedSpy(pointer, &KWayland::Client::Pointer::buttonStateChanged);
1345 QSignalSpy motionSpy(pointer, &KWayland::Client::Pointer::motion);
1346
1347 input()->pointer()->warp(QPointF(800, 800));
1348
1349 // create a decorated window
1350 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
1351 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly));
1352 std::unique_ptr<Test::XdgToplevelDecorationV1> decoration(Test::createXdgToplevelDecorationV1(shellSurface.get()));
1353 QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
1354 decoration->set_mode(Test::XdgToplevelDecorationV1::mode_server_side);
1355 surface->commit(KWayland::Client::Surface::CommitFlag::None);
1356 QVERIFY(surfaceConfigureRequestedSpy.wait());
1357 shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
1358 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
1359 QVERIFY(window);
1360 QCOMPARE(window->hasPopupGrab(), false);
1361 QVERIFY(window->isDecorated());
1362
1363 // move pointer into window
1364 QVERIFY(!exclusiveContains(window->frameGeometry(), QPoint(800, 800)));
1365 input()->pointer()->warp(window->frameGeometry().center());
1366 QVERIFY(enteredSpy.wait());
1367 // click inside window to create serial
1368 quint32 timestamp = 0;
1369 Test::pointerButtonPressed(BTN_LEFT, timestamp++);
1370 Test::pointerButtonReleased(BTN_LEFT, timestamp++);
1371 QVERIFY(buttonStateChangedSpy.wait());
1372
1373 // now create the popup surface
1374 std::unique_ptr<Test::XdgPositioner> positioner(Test::createXdgPositioner());
1375 positioner->set_size(100, 50);
1376 positioner->set_anchor_rect(0, 0, 80, 20);
1377 positioner->set_anchor(Test::XdgPositioner::anchor_bottom_right);
1378 positioner->set_gravity(Test::XdgPositioner::gravity_bottom_right);
1379 std::unique_ptr<KWayland::Client::Surface> popupSurface = Test::createSurface();
1380 QVERIFY(popupSurface);
1381 Test::XdgPopup *popupShellSurface = Test::createXdgPopupSurface(popupSurface.get(), shellSurface->xdgSurface(), positioner.get());
1382 QVERIFY(popupShellSurface);
1383 QSignalSpy doneReceivedSpy(popupShellSurface, &Test::XdgPopup::doneReceived);
1384 popupShellSurface->grab(*Test::waylandSeat(), 0); // FIXME: Serial.
1385 auto popupWindow = Test::renderAndWaitForShown(popupSurface.get(), QSize(100, 50), Qt::red);
1386 QVERIFY(popupWindow);
1387 QVERIFY(popupWindow != window);
1388 QCOMPARE(window, workspace()->activeWindow());
1389 QCOMPARE(popupWindow->transientFor(), window);
1390 QCOMPARE(popupWindow->pos(), window->mapFromLocal(QPoint(80, 20)));
1391 QCOMPARE(popupWindow->hasPopupGrab(), true);
1392
1393 // let's move the pointer into the center of the deco
1394 input()->pointer()->warp(QPointF(window->frameGeometry().center().x(), window->y() + (window->height() - window->clientSize().height()) / 2));
1395
1396 Test::pointerButtonPressed(BTN_RIGHT, timestamp++);
1397 QVERIFY(doneReceivedSpy.wait());
1398 Test::pointerButtonReleased(BTN_RIGHT, timestamp++);
1399}
1400
1401void PointerInputTest::testWindowUnderCursorWhileButtonPressed()
1402{
1403 // this test verifies that opening a window underneath the mouse cursor does not
1404 // trigger a leave event if a button is pressed
1405 // see BUG: 372876
1406
1407 // first create a parent surface
1408 auto pointer = m_seat->createPointer(m_seat);
1409 QVERIFY(pointer);
1410 QVERIFY(pointer->isValid());
1411 QSignalSpy enteredSpy(pointer, &KWayland::Client::Pointer::entered);
1412 QSignalSpy leftSpy(pointer, &KWayland::Client::Pointer::left);
1413
1414 input()->pointer()->warp(QPointF(800, 800));
1415 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
1416 std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
1417 QVERIFY(surface);
1418 Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get());
1419 QVERIFY(shellSurface);
1420 render(surface.get());
1421 QVERIFY(windowAddedSpy.wait());
1422 Window *window = workspace()->activeWindow();
1423 QVERIFY(window);
1424
1425 // move cursor over window
1426 QVERIFY(!exclusiveContains(window->frameGeometry(), QPoint(800, 800)));
1427 input()->pointer()->warp(window->frameGeometry().center());
1428 QVERIFY(enteredSpy.wait());
1429 // click inside window
1430 quint32 timestamp = 0;
1431 Test::pointerButtonPressed(BTN_LEFT, timestamp++);
1432
1433 // now create a second window as transient
1434 std::unique_ptr<Test::XdgPositioner> positioner(Test::createXdgPositioner());
1435 positioner->set_size(99, 49);
1436 positioner->set_anchor_rect(0, 0, 1, 1);
1437 positioner->set_anchor(Test::XdgPositioner::anchor_bottom_right);
1438 positioner->set_gravity(Test::XdgPositioner::gravity_bottom_right);
1439 std::unique_ptr<KWayland::Client::Surface> popupSurface = Test::createSurface();
1440 QVERIFY(popupSurface);
1441 Test::XdgPopup *popupShellSurface = Test::createXdgPopupSurface(popupSurface.get(), shellSurface->xdgSurface(), positioner.get());
1442 QVERIFY(popupShellSurface);
1443 render(popupSurface.get(), QSize(99, 49));
1444 QVERIFY(windowAddedSpy.wait());
1445 auto popupWindow = windowAddedSpy.last().first().value<Window *>();
1446 QVERIFY(popupWindow);
1447 QVERIFY(popupWindow != window);
1448 QVERIFY(exclusiveContains(window->frameGeometry(), Cursors::self()->mouse()->pos()));
1449 QVERIFY(exclusiveContains(popupWindow->frameGeometry(), Cursors::self()->mouse()->pos()));
1450 QVERIFY(Test::waylandSync());
1451 QCOMPARE(leftSpy.count(), 0);
1452
1453 Test::pointerButtonReleased(BTN_LEFT, timestamp++);
1454 // now that the button is no longer pressed we should get the leave event
1455 QVERIFY(leftSpy.wait());
1456 QCOMPARE(leftSpy.count(), 1);
1457 QCOMPARE(enteredSpy.count(), 2);
1458}
1459
1460void PointerInputTest::testConfineToScreenGeometry_data()
1461{
1462 QTest::addColumn<QPoint>("startPos");
1463 QTest::addColumn<QPoint>("targetPos");
1464 QTest::addColumn<QPoint>("expectedPos");
1465
1466 // screen layout:
1467 //
1468 // +----------+----------+---------+
1469 // | left | top | right |
1470 // +----------+----------+---------+
1471 // | bottom |
1472 // +----------+
1473 //
1474
1475 QTest::newRow("move top-left - left screen") << QPoint(640, 512) << QPoint(-100, -100) << QPoint(0, 0);
1476 QTest::newRow("move top - left screen") << QPoint(640, 512) << QPoint(640, -100) << QPoint(640, 0);
1477 QTest::newRow("move top-right - left screen") << QPoint(640, 512) << QPoint(1380, -100) << QPoint(1380, 0);
1478 QTest::newRow("move right - left screen") << QPoint(640, 512) << QPoint(1380, 512) << QPoint(1380, 512);
1479 QTest::newRow("move bottom-right - left screen") << QPoint(640, 512) << QPoint(1380, 1124) << QPoint(1380, 1124);
1480 QTest::newRow("move bottom - left screen") << QPoint(640, 512) << QPoint(640, 1124) << QPoint(640, 1023);
1481 QTest::newRow("move bottom-left - left screen") << QPoint(640, 512) << QPoint(-100, 1124) << QPoint(0, 1023);
1482 QTest::newRow("move left - left screen") << QPoint(640, 512) << QPoint(-100, 512) << QPoint(0, 512);
1483
1484 QTest::newRow("move top-left - top screen") << QPoint(1920, 512) << QPoint(1180, -100) << QPoint(1180, 0);
1485 QTest::newRow("move top - top screen") << QPoint(1920, 512) << QPoint(1920, -100) << QPoint(1920, 0);
1486 QTest::newRow("move top-right - top screen") << QPoint(1920, 512) << QPoint(2660, -100) << QPoint(2660, 0);
1487 QTest::newRow("move right - top screen") << QPoint(1920, 512) << QPoint(2660, 512) << QPoint(2660, 512);
1488 QTest::newRow("move bottom-right - top screen") << QPoint(1920, 512) << QPoint(2660, 1124) << QPoint(2660, 1023);
1489 QTest::newRow("move bottom - top screen") << QPoint(1920, 512) << QPoint(1920, 1124) << QPoint(1920, 1124);
1490 QTest::newRow("move bottom-left - top screen") << QPoint(1920, 512) << QPoint(1180, 1124) << QPoint(1280, 1124);
1491 QTest::newRow("move left - top screen") << QPoint(1920, 512) << QPoint(1180, 512) << QPoint(1180, 512);
1492
1493 QTest::newRow("move top-left - right screen") << QPoint(3200, 512) << QPoint(2460, -100) << QPoint(2460, 0);
1494 QTest::newRow("move top - right screen") << QPoint(3200, 512) << QPoint(3200, -100) << QPoint(3200, 0);
1495 QTest::newRow("move top-right - right screen") << QPoint(3200, 512) << QPoint(3940, -100) << QPoint(3839, 0);
1496 QTest::newRow("move right - right screen") << QPoint(3200, 512) << QPoint(3940, 512) << QPoint(3839, 512);
1497 QTest::newRow("move bottom-right - right screen") << QPoint(3200, 512) << QPoint(3940, 1124) << QPoint(3839, 1023);
1498 QTest::newRow("move bottom - right screen") << QPoint(3200, 512) << QPoint(3200, 1124) << QPoint(3200, 1023);
1499 QTest::newRow("move bottom-left - right screen") << QPoint(3200, 512) << QPoint(2460, 1124) << QPoint(2460, 1124);
1500 QTest::newRow("move left - right screen") << QPoint(3200, 512) << QPoint(2460, 512) << QPoint(2460, 512);
1501
1502 QTest::newRow("move top-left - bottom screen") << QPoint(1920, 1536) << QPoint(1180, 924) << QPoint(1180, 924);
1503 QTest::newRow("move top - bottom screen") << QPoint(1920, 1536) << QPoint(1920, 924) << QPoint(1920, 924);
1504 QTest::newRow("move top-right - bottom screen") << QPoint(1920, 1536) << QPoint(2660, 924) << QPoint(2660, 924);
1505 QTest::newRow("move right - bottom screen") << QPoint(1920, 1536) << QPoint(2660, 1536) << QPoint(2559, 1536);
1506 QTest::newRow("move bottom-right - bottom screen") << QPoint(1920, 1536) << QPoint(2660, 2148) << QPoint(2559, 2047);
1507 QTest::newRow("move bottom - bottom screen") << QPoint(1920, 1536) << QPoint(1920, 2148) << QPoint(1920, 2047);
1508 QTest::newRow("move bottom-left - bottom screen") << QPoint(1920, 1536) << QPoint(1180, 2148) << QPoint(1280, 2047);
1509 QTest::newRow("move left - bottom screen") << QPoint(1920, 1536) << QPoint(1180, 1536) << QPoint(1280, 1536);
1510}
1511
1512void PointerInputTest::testConfineToScreenGeometry()
1513{
1514 // this test verifies that pointer belongs to at least one screen
1515 // after moving it to off-screen area
1516
1517 // setup screen layout
1518 const QList<QRect> geometries{
1519 QRect(0, 0, 1280, 1024),
1520 QRect(1280, 0, 1280, 1024),
1521 QRect(2560, 0, 1280, 1024),
1522 QRect(1280, 1024, 1280, 1024)};
1523 Test::setOutputConfig(geometries);
1524
1525 const auto outputs = workspace()->outputs();
1526 QCOMPARE(outputs.count(), geometries.count());
1527 QCOMPARE(outputs[0]->geometry(), geometries.at(0));
1528 QCOMPARE(outputs[1]->geometry(), geometries.at(1));
1529 QCOMPARE(outputs[2]->geometry(), geometries.at(2));
1530 QCOMPARE(outputs[3]->geometry(), geometries.at(3));
1531
1532 // move pointer to initial position
1533 QFETCH(QPoint, startPos);
1534 input()->pointer()->warp(startPos);
1535 QCOMPARE(Cursors::self()->mouse()->pos(), startPos);
1536
1537 // perform movement
1538 QFETCH(QPoint, targetPos);
1539 Test::pointerMotion(targetPos, 1);
1540
1541 QFETCH(QPoint, expectedPos);
1542 QCOMPARE(Cursors::self()->mouse()->pos(), expectedPos);
1543}
1544
1545void PointerInputTest::testResizeCursor_data()
1546{
1547 QTest::addColumn<Qt::Edges>("edges");
1548 QTest::addColumn<KWin::CursorShape>("cursorShape");
1549
1550 QTest::newRow("top-left") << Qt::Edges(Qt::TopEdge | Qt::LeftEdge) << CursorShape(ExtendedCursor::SizeNorthWest);
1551 QTest::newRow("top") << Qt::Edges(Qt::TopEdge) << CursorShape(ExtendedCursor::SizeNorth);
1552 QTest::newRow("top-right") << Qt::Edges(Qt::TopEdge | Qt::RightEdge) << CursorShape(ExtendedCursor::SizeNorthEast);
1553 QTest::newRow("right") << Qt::Edges(Qt::RightEdge) << CursorShape(ExtendedCursor::SizeEast);
1554 QTest::newRow("bottom-right") << Qt::Edges(Qt::BottomEdge | Qt::RightEdge) << CursorShape(ExtendedCursor::SizeSouthEast);
1555 QTest::newRow("bottom") << Qt::Edges(Qt::BottomEdge) << CursorShape(ExtendedCursor::SizeSouth);
1556 QTest::newRow("bottom-left") << Qt::Edges(Qt::BottomEdge | Qt::LeftEdge) << CursorShape(ExtendedCursor::SizeSouthWest);
1557 QTest::newRow("left") << Qt::Edges(Qt::LeftEdge) << CursorShape(ExtendedCursor::SizeWest);
1558}
1559
1560void PointerInputTest::testResizeCursor()
1561{
1562 // this test verifies that the cursor has correct shape during resize operation
1563
1564 // first modify the config for this run
1565 KConfigGroup group = kwinApp()->config()->group(QStringLiteral("MouseBindings"));
1566 group.writeEntry("CommandAllKey", "Meta");
1567 group.writeEntry("CommandAll3", "Resize");
1568 group.sync();
1570 QCOMPARE(options->commandAllModifier(), Qt::MetaModifier);
1572
1573 // load the fallback cursor (arrow cursor)
1574 const PlatformCursorImage arrowCursor = loadReferenceThemeCursor(Qt::ArrowCursor);
1575 QVERIFY(!arrowCursor.isNull());
1576 QCOMPARE(kwinApp()->cursorImage().image(), arrowCursor.image());
1577 QCOMPARE(kwinApp()->cursorImage().hotSpot(), arrowCursor.hotSpot());
1578
1579 // we need a pointer to get the enter event
1580 auto pointer = m_seat->createPointer(m_seat);
1581 QVERIFY(pointer);
1582 QVERIFY(pointer->isValid());
1583 QSignalSpy enteredSpy(pointer, &KWayland::Client::Pointer::entered);
1584
1585 // create a test window
1586 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
1587 QVERIFY(surface != nullptr);
1588 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
1589 QVERIFY(shellSurface != nullptr);
1590 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
1591 QVERIFY(window);
1592
1593 // move the cursor to the test position
1594 QPoint cursorPos;
1595 QFETCH(Qt::Edges, edges);
1596
1597 if (edges & Qt::LeftEdge) {
1598 cursorPos.setX(window->frameGeometry().left());
1599 } else if (edges & Qt::RightEdge) {
1600 cursorPos.setX(window->frameGeometry().right() - 1);
1601 } else {
1602 cursorPos.setX(window->frameGeometry().center().x());
1603 }
1604
1605 if (edges & Qt::TopEdge) {
1606 cursorPos.setY(window->frameGeometry().top());
1607 } else if (edges & Qt::BottomEdge) {
1608 cursorPos.setY(window->frameGeometry().bottom() - 1);
1609 } else {
1610 cursorPos.setY(window->frameGeometry().center().y());
1611 }
1612
1613 input()->pointer()->warp(cursorPos);
1614
1615 // wait for the enter event and set the cursor
1616 QVERIFY(enteredSpy.wait());
1617 std::unique_ptr<KWayland::Client::Surface> cursorSurface(Test::createSurface());
1618 QVERIFY(cursorSurface);
1619 QSignalSpy cursorRenderedSpy(cursorSurface.get(), &KWayland::Client::Surface::frameRendered);
1620 cursorSurface->attachBuffer(Test::waylandShmPool()->createBuffer(arrowCursor.image()));
1621 cursorSurface->damage(arrowCursor.image().rect());
1622 cursorSurface->commit();
1623 pointer->setCursor(cursorSurface.get(), arrowCursor.hotSpot().toPoint());
1624 QVERIFY(cursorRenderedSpy.wait());
1625
1626 // start resizing the window
1627 int timestamp = 1;
1628 Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++);
1629 Test::pointerButtonPressed(BTN_RIGHT, timestamp++);
1630 QVERIFY(window->isInteractiveResize());
1631
1632 QFETCH(KWin::CursorShape, cursorShape);
1633 const PlatformCursorImage resizeCursor = loadReferenceThemeCursor(cursorShape);
1634 QVERIFY(!resizeCursor.isNull());
1635 QCOMPARE(kwinApp()->cursorImage().image(), resizeCursor.image());
1636 QCOMPARE(kwinApp()->cursorImage().hotSpot(), resizeCursor.hotSpot());
1637
1638 // finish resizing the window
1639 Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++);
1640 Test::pointerButtonReleased(BTN_RIGHT, timestamp++);
1641 QVERIFY(!window->isInteractiveResize());
1642
1643 QCOMPARE(kwinApp()->cursorImage().image(), arrowCursor.image());
1644 QCOMPARE(kwinApp()->cursorImage().hotSpot(), arrowCursor.hotSpot());
1645}
1646
1647void PointerInputTest::testMoveCursor()
1648{
1649 // this test verifies that the cursor has correct shape during move operation
1650
1651 // first modify the config for this run
1652 KConfigGroup group = kwinApp()->config()->group(QStringLiteral("MouseBindings"));
1653 group.writeEntry("CommandAllKey", "Meta");
1654 group.writeEntry("CommandAll1", "Move");
1655 group.sync();
1657 QCOMPARE(options->commandAllModifier(), Qt::MetaModifier);
1659
1660 // load the fallback cursor (arrow cursor)
1661 const PlatformCursorImage arrowCursor = loadReferenceThemeCursor(Qt::ArrowCursor);
1662 QVERIFY(!arrowCursor.isNull());
1663 QCOMPARE(kwinApp()->cursorImage().image(), arrowCursor.image());
1664 QCOMPARE(kwinApp()->cursorImage().hotSpot(), arrowCursor.hotSpot());
1665
1666 // we need a pointer to get the enter event
1667 auto pointer = m_seat->createPointer(m_seat);
1668 QVERIFY(pointer);
1669 QVERIFY(pointer->isValid());
1670 QSignalSpy enteredSpy(pointer, &KWayland::Client::Pointer::entered);
1671
1672 // create a test window
1673 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
1674 QVERIFY(surface != nullptr);
1675 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
1676 QVERIFY(shellSurface != nullptr);
1677 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
1678 QVERIFY(window);
1679
1680 // move cursor to the test position
1681 input()->pointer()->warp(window->frameGeometry().center());
1682
1683 // wait for the enter event and set the cursor
1684 QVERIFY(enteredSpy.wait());
1685 std::unique_ptr<KWayland::Client::Surface> cursorSurface = Test::createSurface();
1686 QVERIFY(cursorSurface);
1687 QSignalSpy cursorRenderedSpy(cursorSurface.get(), &KWayland::Client::Surface::frameRendered);
1688 cursorSurface->attachBuffer(Test::waylandShmPool()->createBuffer(arrowCursor.image()));
1689 cursorSurface->damage(arrowCursor.image().rect());
1690 cursorSurface->commit();
1691 pointer->setCursor(cursorSurface.get(), arrowCursor.hotSpot().toPoint());
1692 QVERIFY(cursorRenderedSpy.wait());
1693
1694 // start moving the window
1695 int timestamp = 1;
1696 Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++);
1697 Test::pointerButtonPressed(BTN_LEFT, timestamp++);
1698 QVERIFY(window->isInteractiveMove());
1699
1700 const PlatformCursorImage sizeAllCursor = loadReferenceThemeCursor(Qt::SizeAllCursor);
1701 QVERIFY(!sizeAllCursor.isNull());
1702 QCOMPARE(kwinApp()->cursorImage().image(), sizeAllCursor.image());
1703 QCOMPARE(kwinApp()->cursorImage().hotSpot(), sizeAllCursor.hotSpot());
1704
1705 // finish moving the window
1706 Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++);
1707 Test::pointerButtonReleased(BTN_LEFT, timestamp++);
1708 QVERIFY(!window->isInteractiveMove());
1709
1710 QCOMPARE(kwinApp()->cursorImage().image(), arrowCursor.image());
1711 QCOMPARE(kwinApp()->cursorImage().hotSpot(), arrowCursor.hotSpot());
1712}
1713
1714void PointerInputTest::testHideShowCursor()
1715{
1716 QCOMPARE(Cursors::self()->isCursorHidden(), false);
1718 QCOMPARE(Cursors::self()->isCursorHidden(), true);
1720 QCOMPARE(Cursors::self()->isCursorHidden(), false);
1721
1723 QCOMPARE(Cursors::self()->isCursorHidden(), true);
1727 QCOMPARE(Cursors::self()->isCursorHidden(), true);
1728
1730 QCOMPARE(Cursors::self()->isCursorHidden(), true);
1732 QCOMPARE(Cursors::self()->isCursorHidden(), true);
1734 QCOMPARE(Cursors::self()->isCursorHidden(), true);
1736 QCOMPARE(Cursors::self()->isCursorHidden(), false);
1737}
1738
1739void PointerInputTest::testDefaultInputRegion()
1740{
1741 // This test verifies that a surface that hasn't specified the input region can be focused.
1742
1743 // Create a test window.
1744 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
1745 QVERIFY(surface != nullptr);
1746 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
1747 QVERIFY(shellSurface != nullptr);
1748 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
1749 QVERIFY(window);
1750
1751 // Move the point to the center of the surface.
1752 input()->pointer()->warp(window->frameGeometry().center());
1753 QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), window->surface());
1754
1755 // Destroy the test window.
1756 shellSurface.reset();
1757 QVERIFY(Test::waitForWindowClosed(window));
1758}
1759
1760void PointerInputTest::testEmptyInputRegion()
1761{
1762 // This test verifies that a surface that has specified an empty input region can't be focused.
1763
1764 // Create a test window.
1765 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
1766 QVERIFY(surface != nullptr);
1767 std::unique_ptr<KWayland::Client::Region> inputRegion(m_compositor->createRegion(QRegion()));
1768 surface->setInputRegion(inputRegion.get());
1769 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
1770 QVERIFY(shellSurface != nullptr);
1771 Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
1772 QVERIFY(window);
1773
1774 // Move the point to the center of the surface.
1775 input()->pointer()->warp(window->frameGeometry().center());
1776 QVERIFY(!waylandServer()->seat()->focusedPointerSurface());
1777
1778 // Destroy the test window.
1779 shellSurface.reset();
1780 QVERIFY(Test::waitForWindowClosed(window));
1781}
1782
1783void PointerInputTest::testUnfocusedModifiers()
1784{
1785 // This test verifies that a window under the cursor gets modifier events,
1786 // even if it isn't focused
1787
1788 QVERIFY(Test::waylandSeat()->hasKeyboard());
1789 std::unique_ptr<KWayland::Client::Keyboard> keyboard(Test::waylandSeat()->createKeyboard());
1790
1791 // create a Wayland window
1792 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
1793 QVERIFY(surface != nullptr);
1794 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
1795 QVERIFY(shellSurface != nullptr);
1796 Window *waylandWindow = Test::renderAndWaitForShown(surface.get(), QSize(10, 10), Qt::blue);
1797 QVERIFY(waylandWindow);
1798 waylandWindow->move(QPoint(0, 0));
1799
1800 // Create an xcb window.
1802 QVERIFY(!xcb_connection_has_error(c.get()));
1803 const QRect windowGeometry(0, 0, 10, 10);
1804 xcb_window_t windowId = xcb_generate_id(c.get());
1805 xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(),
1806 windowGeometry.x(),
1807 windowGeometry.y(),
1808 windowGeometry.width(),
1809 windowGeometry.height(),
1810 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
1811 xcb_size_hints_t hints = {};
1812 xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
1813 xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
1814 xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints);
1815 xcb_map_window(c.get(), windowId);
1816 xcb_flush(c.get());
1817 QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
1818 QVERIFY(windowCreatedSpy.wait());
1819 X11Window *x11window = windowCreatedSpy.last().first().value<X11Window *>();
1820 QVERIFY(waylandWindow);
1821 x11window->move(QPoint(10, 10));
1822
1823 workspace()->activateWindow(x11window, true);
1824
1825 // Move the pointer over the now unfocused Wayland window
1826 input()->pointer()->warp(waylandWindow->frameGeometry().center());
1827 QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), waylandWindow->surface());
1828
1829 QSignalSpy spy(keyboard.get(), &KWayland::Client::Keyboard::modifiersChanged);
1830 Test::keyboardKeyPressed(KEY_LEFTCTRL, 1);
1831 QVERIFY(spy.wait());
1832 QCOMPARE(spy.last().at(0).toInt(), XCB_MOD_MASK_CONTROL);
1833
1834 Test::keyboardKeyReleased(KEY_LEFTCTRL, 2);
1835
1836 // Destroy the x11 window.
1837 QSignalSpy windowClosedSpy(waylandWindow, &X11Window::closed);
1838 xcb_unmap_window(c.get(), windowId);
1839 xcb_destroy_window(c.get(), windowId);
1840 xcb_flush(c.get());
1841 c.reset();
1842
1843 // Destroy the Wayland window.
1844 shellSurface.reset();
1845 QVERIFY(Test::waitForWindowClosed(waylandWindow));
1846}
1847}
1848
1850#include "pointer_input.moc"
QPointF pos()
Definition cursor.cpp:204
Wrapper round Qt::CursorShape with extensions enums into a single entity.
Definition cursor.h:50
void hideCursor()
Definition cursor.cpp:69
static Cursors * self()
Definition cursor.cpp:35
void currentCursorChanged(Cursor *cursor)
void showCursor()
Definition cursor.cpp:77
Cursor * mouse() const
Definition cursor.h:266
void stopMouseInterception(Effect *effect)
virtual void defineCursor(Qt::CursorShape shape)
void startMouseInterception(Effect *effect, Qt::CursorShape shape)
PointerInputRedirection * pointer() const
Definition input.h:220
bool borderlessMaximizedWindows
Definition options.h:172
@ MouseUnrestrictedResize
Definition options.h:460
@ MouseUnrestrictedMove
Definition options.h:456
Qt::KeyboardModifier commandAllModifier() const
Definition options.h:558
int delayFocusInterval
Definition options.h:97
MouseCommand commandAll2
Definition options.h:153
bool isAutoRaise() const
Definition options.h:278
bool isClickRaise() const
Definition options.h:270
MouseCommand commandAll3
Definition options.h:154
MouseCommand commandAll1
Definition options.h:152
FocusPolicy focusPolicy
Definition options.h:77
@ FocusFollowsMouse
Definition options.h:224
int autoRaiseInterval
Definition options.h:93
void warp(const QPointF &pos)
void configureRequested(quint32 serial)
void configureRequested(QtWayland::zxdg_toplevel_decoration_v1::mode mode)
void configureRequested(const QSize &size, KWin::Test::XdgToplevel::States states)
void frameGeometryChanged(const QRectF &oldGeometry)
Window * activeWindow() const
Definition workspace.h:767
void activateWindow(Window *window, bool force=false)
void stackingOrderChanged()
void windowActivated(KWin::Window *)
static Workspace * self()
Definition workspace.h:91
void windowAdded(KWin::Window *)
void lowerWindow(Window *window, bool nogroup=false)
Definition layers.cpp:296
QList< Output * > outputs() const
Definition workspace.h:762
void setActiveOutput(Output *output)
void disableGlobalShortcutsForClient(bool disable)
void slotReconfigure()
#define WAYLANDTEST_MAIN(TestObject)
XdgPositioner * createXdgPositioner()
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)
XdgPopup * createXdgPopupSurface(KWayland::Client::Surface *surface, XdgSurface *parentSurface, XdgPositioner *positioner, CreationSetup configureMode=CreationSetup::CreateAndConfigure, QObject *parent=nullptr)
void destroyWaylandConnection()
void setOutputConfig(const QList< QRect > &geometries)
KWayland::Client::Seat * seat
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())
KWayland::Client::Compositor * waylandCompositor()
void render(KWayland::Client::Surface *surface, const QSize &size, const QColor &color, const QImage::Format &format=QImage::Format_ARGB32_Premultiplied)
KWayland::Client::Seat * waylandSeat()
void pointerButtonPressed(quint32 button, quint32 time)
XcbConnectionPtr createX11Connection()
QList< KWayland::Client::Output * > outputs
void pointerMotion(const QPointF &position, quint32 time)
std::unique_ptr< KWayland::Client::Surface > createSurface()
KWayland::Client::ShmPool * waylandShmPool()
XdgToplevel * createXdgToplevelSurface(KWayland::Client::Surface *surface, QObject *parent=nullptr)
XdgToplevelDecorationV1 * createXdgToplevelDecorationV1(XdgToplevel *toplevel, QObject *parent=nullptr)
bool waylandSync()
bool waitForWaylandPointer()
void pointerButtonReleased(quint32 button, quint32 time)
CursorShapeDeviceV1 * createCursorShapeDeviceV1(KWayland::Client::Pointer *pointer)
void flushWaylandConnection()
std::unique_ptr< xcb_connection_t, XcbConnectionDeleter > XcbConnectionPtr
bool waitForWindowClosed(Window *window)
KWIN_EXPORT xcb_window_t rootWindow()
Definition xcb.h:24
@ MaximizeRestore
The window is not maximized in any direction.
Definition common.h:75
@ MaximizeFull
Equal to MaximizeVertical | MaximizeHorizontal.
Definition common.h:79
WaylandServer * waylandServer()
Workspace * workspace()
Definition workspace.h:830
Options * options
Definition main.cpp:73
InputRedirection * input()
Definition input.h:549
EffectsHandler * effects