File indexing completed on 2024-11-10 04:56:03
0001 /* 0002 KWin - the KDE window manager 0003 This file is part of the KDE project. 0004 0005 SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org> 0006 0007 SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 #include "kwin_wayland_test.h" 0010 0011 #include "core/output.h" 0012 #include "cursor.h" 0013 #include "keyboard_input.h" 0014 #include "pointer_input.h" 0015 #include "wayland/seat.h" 0016 #include "wayland/surface.h" 0017 #include "wayland_server.h" 0018 #include "window.h" 0019 #include "workspace.h" 0020 0021 #include <KWayland/Client/compositor.h> 0022 #include <KWayland/Client/keyboard.h> 0023 #include <KWayland/Client/pointer.h> 0024 #include <KWayland/Client/pointerconstraints.h> 0025 #include <KWayland/Client/region.h> 0026 #include <KWayland/Client/seat.h> 0027 #include <KWayland/Client/shm_pool.h> 0028 #include <KWayland/Client/surface.h> 0029 0030 #include <linux/input.h> 0031 0032 #include <functional> 0033 0034 using namespace KWin; 0035 0036 typedef std::function<QPointF(const QRectF &)> PointerFunc; 0037 Q_DECLARE_METATYPE(PointerFunc) 0038 0039 static const QString s_socketName = QStringLiteral("wayland_test_kwin_pointer_constraints-0"); 0040 0041 class TestPointerConstraints : public QObject 0042 { 0043 Q_OBJECT 0044 private Q_SLOTS: 0045 void initTestCase(); 0046 void init(); 0047 void cleanup(); 0048 0049 void testConfinedPointer_data(); 0050 void testConfinedPointer(); 0051 void testLockedPointer(); 0052 void testCloseWindowWithLockedPointer(); 0053 }; 0054 0055 void TestPointerConstraints::initTestCase() 0056 { 0057 qRegisterMetaType<PointerFunc>(); 0058 qRegisterMetaType<KWin::Window *>(); 0059 QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); 0060 QVERIFY(waylandServer()->init(s_socketName)); 0061 Test::setOutputConfig({ 0062 QRect(0, 0, 1280, 1024), 0063 QRect(1280, 0, 1280, 1024), 0064 }); 0065 0066 // set custom config which disables the OnScreenNotification 0067 KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); 0068 KConfigGroup group = config->group(QStringLiteral("OnScreenNotification")); 0069 group.writeEntry(QStringLiteral("QmlPath"), QString("/does/not/exist.qml")); 0070 group.sync(); 0071 0072 kwinApp()->setConfig(config); 0073 0074 kwinApp()->start(); 0075 QVERIFY(applicationStartedSpy.wait()); 0076 const auto outputs = workspace()->outputs(); 0077 QCOMPARE(outputs.count(), 2); 0078 QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); 0079 QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); 0080 } 0081 0082 void TestPointerConstraints::init() 0083 { 0084 QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::PointerConstraints)); 0085 QVERIFY(Test::waitForWaylandPointer()); 0086 0087 workspace()->setActiveOutput(QPoint(640, 512)); 0088 KWin::input()->pointer()->warp(QPoint(640, 512)); 0089 } 0090 0091 void TestPointerConstraints::cleanup() 0092 { 0093 Test::destroyWaylandConnection(); 0094 } 0095 0096 void TestPointerConstraints::testConfinedPointer_data() 0097 { 0098 QTest::addColumn<PointerFunc>("positionFunction"); 0099 QTest::addColumn<int>("xOffset"); 0100 QTest::addColumn<int>("yOffset"); 0101 PointerFunc bottomLeft = [](const QRectF &rect) { 0102 return rect.toRect().bottomLeft(); 0103 }; 0104 PointerFunc bottomRight = [](const QRectF &rect) { 0105 return rect.toRect().bottomRight(); 0106 }; 0107 PointerFunc topRight = [](const QRectF &rect) { 0108 return rect.toRect().topRight(); 0109 }; 0110 PointerFunc topLeft = [](const QRectF &rect) { 0111 return rect.toRect().topLeft(); 0112 }; 0113 0114 QTest::newRow("XdgWmBase - bottomLeft") << bottomLeft << -1 << 1; 0115 QTest::newRow("XdgWmBase - bottomRight") << bottomRight << 1 << 1; 0116 QTest::newRow("XdgWmBase - topLeft") << topLeft << -1 << -1; 0117 QTest::newRow("XdgWmBase - topRight") << topRight << 1 << -1; 0118 } 0119 0120 void TestPointerConstraints::testConfinedPointer() 0121 { 0122 // this test sets up a Surface with a confined pointer 0123 // simple interaction test to verify that the pointer gets confined 0124 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 0125 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get())); 0126 std::unique_ptr<KWayland::Client::Pointer> pointer(Test::waylandSeat()->createPointer()); 0127 std::unique_ptr<KWayland::Client::ConfinedPointer> confinedPointer(Test::waylandPointerConstraints()->confinePointer(surface.get(), pointer.get(), nullptr, KWayland::Client::PointerConstraints::LifeTime::OneShot)); 0128 QSignalSpy confinedSpy(confinedPointer.get(), &KWayland::Client::ConfinedPointer::confined); 0129 QSignalSpy unconfinedSpy(confinedPointer.get(), &KWayland::Client::ConfinedPointer::unconfined); 0130 0131 // now map the window 0132 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 100), Qt::blue); 0133 QVERIFY(window); 0134 if (window->pos() == QPoint(0, 0)) { 0135 window->move(QPoint(1, 1)); 0136 } 0137 QVERIFY(!exclusiveContains(window->frameGeometry(), KWin::Cursors::self()->mouse()->pos())); 0138 0139 // now let's confine 0140 QCOMPARE(input()->pointer()->isConstrained(), false); 0141 KWin::input()->pointer()->warp(window->frameGeometry().center()); 0142 QCOMPARE(input()->pointer()->isConstrained(), true); 0143 QVERIFY(confinedSpy.wait()); 0144 0145 // picking a position outside the window geometry should not move pointer 0146 QSignalSpy pointerPositionChangedSpy(input(), &InputRedirection::globalPointerChanged); 0147 KWin::input()->pointer()->warp(QPoint(512, 512)); 0148 QVERIFY(pointerPositionChangedSpy.isEmpty()); 0149 QCOMPARE(KWin::Cursors::self()->mouse()->pos(), window->frameGeometry().center()); 0150 0151 // TODO: test relative motion 0152 QFETCH(PointerFunc, positionFunction); 0153 const QPointF position = positionFunction(window->frameGeometry()); 0154 KWin::input()->pointer()->warp(position); 0155 QCOMPARE(pointerPositionChangedSpy.count(), 1); 0156 QCOMPARE(KWin::Cursors::self()->mouse()->pos(), position); 0157 // moving one to right should not be possible 0158 QFETCH(int, xOffset); 0159 KWin::input()->pointer()->warp(position + QPoint(xOffset, 0)); 0160 QCOMPARE(pointerPositionChangedSpy.count(), 1); 0161 QCOMPARE(KWin::Cursors::self()->mouse()->pos(), position); 0162 // moving one to bottom should not be possible 0163 QFETCH(int, yOffset); 0164 KWin::input()->pointer()->warp(position + QPoint(0, yOffset)); 0165 QCOMPARE(pointerPositionChangedSpy.count(), 1); 0166 QCOMPARE(KWin::Cursors::self()->mouse()->pos(), position); 0167 0168 // modifier + click should be ignored 0169 // first ensure the settings are ok 0170 KConfigGroup group = kwinApp()->config()->group(QStringLiteral("MouseBindings")); 0171 group.writeEntry("CommandAllKey", QStringLiteral("Meta")); 0172 group.writeEntry("CommandAll1", "Move"); 0173 group.writeEntry("CommandAll2", "Move"); 0174 group.writeEntry("CommandAll3", "Move"); 0175 group.writeEntry("CommandAllWheel", "change opacity"); 0176 group.sync(); 0177 workspace()->slotReconfigure(); 0178 QCOMPARE(options->commandAllModifier(), Qt::MetaModifier); 0179 QCOMPARE(options->commandAll1(), Options::MouseUnrestrictedMove); 0180 QCOMPARE(options->commandAll2(), Options::MouseUnrestrictedMove); 0181 QCOMPARE(options->commandAll3(), Options::MouseUnrestrictedMove); 0182 0183 quint32 timestamp = 1; 0184 Test::keyboardKeyPressed(KEY_LEFTALT, timestamp++); 0185 Test::pointerButtonPressed(BTN_LEFT, timestamp++); 0186 QVERIFY(!window->isInteractiveMove()); 0187 Test::pointerButtonReleased(BTN_LEFT, timestamp++); 0188 0189 // set the opacity to 0.5 0190 window->setOpacity(0.5); 0191 QCOMPARE(window->opacity(), 0.5); 0192 0193 // pointer is confined so shortcut should not work 0194 Test::pointerAxisVertical(-5, timestamp++); 0195 QCOMPARE(window->opacity(), 0.5); 0196 Test::pointerAxisVertical(5, timestamp++); 0197 QCOMPARE(window->opacity(), 0.5); 0198 0199 Test::keyboardKeyReleased(KEY_LEFTALT, timestamp++); 0200 0201 // deactivate the window, this should unconfine 0202 workspace()->activateWindow(nullptr); 0203 QVERIFY(unconfinedSpy.wait()); 0204 QCOMPARE(input()->pointer()->isConstrained(), false); 0205 0206 // reconfine pointer (this time with persistent life time) 0207 confinedPointer.reset(Test::waylandPointerConstraints()->confinePointer(surface.get(), pointer.get(), nullptr, KWayland::Client::PointerConstraints::LifeTime::Persistent)); 0208 QSignalSpy confinedSpy2(confinedPointer.get(), &KWayland::Client::ConfinedPointer::confined); 0209 QSignalSpy unconfinedSpy2(confinedPointer.get(), &KWayland::Client::ConfinedPointer::unconfined); 0210 0211 // activate it again, this confines again 0212 workspace()->activateWindow(static_cast<Window *>(input()->pointer()->focus())); 0213 QVERIFY(confinedSpy2.wait()); 0214 QCOMPARE(input()->pointer()->isConstrained(), true); 0215 0216 // deactivate the window one more time with the persistent life time constraint, this should unconfine 0217 workspace()->activateWindow(nullptr); 0218 QVERIFY(unconfinedSpy2.wait()); 0219 QCOMPARE(input()->pointer()->isConstrained(), false); 0220 // activate it again, this confines again 0221 workspace()->activateWindow(static_cast<Window *>(input()->pointer()->focus())); 0222 QVERIFY(confinedSpy2.wait()); 0223 QCOMPARE(input()->pointer()->isConstrained(), true); 0224 0225 // create a second window and move it above our constrained window 0226 std::unique_ptr<KWayland::Client::Surface> surface2(Test::createSurface()); 0227 std::unique_ptr<Test::XdgToplevel> shellSurface2(Test::createXdgToplevelSurface(surface2.get())); 0228 auto c2 = Test::renderAndWaitForShown(surface2.get(), QSize(1280, 1024), Qt::blue); 0229 QVERIFY(c2); 0230 QVERIFY(unconfinedSpy2.wait()); 0231 // and unmapping the second window should confine again 0232 shellSurface2.reset(); 0233 surface2.reset(); 0234 QVERIFY(confinedSpy2.wait()); 0235 0236 // let's set a region which results in unconfined 0237 auto r = Test::waylandCompositor()->createRegion(QRegion(2, 2, 3, 3)); 0238 confinedPointer->setRegion(r.get()); 0239 surface->commit(KWayland::Client::Surface::CommitFlag::None); 0240 QVERIFY(unconfinedSpy2.wait()); 0241 QCOMPARE(input()->pointer()->isConstrained(), false); 0242 // and set a full region again, that should confine 0243 confinedPointer->setRegion(nullptr); 0244 surface->commit(KWayland::Client::Surface::CommitFlag::None); 0245 QVERIFY(confinedSpy2.wait()); 0246 QCOMPARE(input()->pointer()->isConstrained(), true); 0247 0248 // delete pointer confine 0249 confinedPointer.reset(nullptr); 0250 Test::flushWaylandConnection(); 0251 0252 QSignalSpy constraintsChangedSpy(input()->pointer()->focus()->surface(), &SurfaceInterface::pointerConstraintsChanged); 0253 QVERIFY(constraintsChangedSpy.wait()); 0254 0255 // should be unconfined 0256 QCOMPARE(input()->pointer()->isConstrained(), false); 0257 0258 // confine again 0259 confinedPointer.reset(Test::waylandPointerConstraints()->confinePointer(surface.get(), pointer.get(), nullptr, KWayland::Client::PointerConstraints::LifeTime::Persistent)); 0260 QSignalSpy confinedSpy3(confinedPointer.get(), &KWayland::Client::ConfinedPointer::confined); 0261 QVERIFY(confinedSpy3.wait()); 0262 QCOMPARE(input()->pointer()->isConstrained(), true); 0263 0264 // and now unmap 0265 shellSurface.reset(); 0266 surface.reset(); 0267 QVERIFY(Test::waitForWindowClosed(window)); 0268 QCOMPARE(input()->pointer()->isConstrained(), false); 0269 } 0270 0271 void TestPointerConstraints::testLockedPointer() 0272 { 0273 // this test sets up a Surface with a locked pointer 0274 // simple interaction test to verify that the pointer gets locked 0275 // the various ways to unlock are not tested as that's already verified by testConfinedPointer 0276 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 0277 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get())); 0278 std::unique_ptr<KWayland::Client::Pointer> pointer(Test::waylandSeat()->createPointer()); 0279 std::unique_ptr<KWayland::Client::LockedPointer> lockedPointer(Test::waylandPointerConstraints()->lockPointer(surface.get(), pointer.get(), nullptr, KWayland::Client::PointerConstraints::LifeTime::OneShot)); 0280 QSignalSpy lockedSpy(lockedPointer.get(), &KWayland::Client::LockedPointer::locked); 0281 QSignalSpy unlockedSpy(lockedPointer.get(), &KWayland::Client::LockedPointer::unlocked); 0282 0283 // now map the window 0284 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 100), Qt::blue); 0285 QVERIFY(window); 0286 QVERIFY(!exclusiveContains(window->frameGeometry(), KWin::Cursors::self()->mouse()->pos())); 0287 0288 // now let's lock 0289 QCOMPARE(input()->pointer()->isConstrained(), false); 0290 KWin::input()->pointer()->warp(window->frameGeometry().center()); 0291 QCOMPARE(KWin::Cursors::self()->mouse()->pos(), window->frameGeometry().center()); 0292 QCOMPARE(input()->pointer()->isConstrained(), true); 0293 QVERIFY(lockedSpy.wait()); 0294 0295 // try to move the pointer 0296 // TODO: add relative pointer 0297 KWin::input()->pointer()->warp(window->frameGeometry().center() + QPoint(1, 1)); 0298 QCOMPARE(KWin::Cursors::self()->mouse()->pos(), window->frameGeometry().center()); 0299 0300 // deactivate the window, this should unlock 0301 workspace()->activateWindow(nullptr); 0302 QCOMPARE(input()->pointer()->isConstrained(), false); 0303 QVERIFY(unlockedSpy.wait()); 0304 0305 // moving cursor should be allowed again 0306 KWin::input()->pointer()->warp(window->frameGeometry().center() + QPoint(1, 1)); 0307 QCOMPARE(KWin::Cursors::self()->mouse()->pos(), window->frameGeometry().center() + QPoint(1, 1)); 0308 0309 lockedPointer.reset(Test::waylandPointerConstraints()->lockPointer(surface.get(), pointer.get(), nullptr, KWayland::Client::PointerConstraints::LifeTime::Persistent)); 0310 QSignalSpy lockedSpy2(lockedPointer.get(), &KWayland::Client::LockedPointer::locked); 0311 0312 // activate the window again, this should lock again 0313 workspace()->activateWindow(static_cast<Window *>(input()->pointer()->focus())); 0314 QVERIFY(lockedSpy2.wait()); 0315 QCOMPARE(input()->pointer()->isConstrained(), true); 0316 0317 // try to move the pointer 0318 QCOMPARE(input()->pointer()->isConstrained(), true); 0319 KWin::input()->pointer()->warp(window->frameGeometry().center()); 0320 QCOMPARE(KWin::Cursors::self()->mouse()->pos(), window->frameGeometry().center() + QPoint(1, 1)); 0321 0322 // delete pointer lock 0323 lockedPointer.reset(nullptr); 0324 Test::flushWaylandConnection(); 0325 0326 QSignalSpy constraintsChangedSpy(input()->pointer()->focus()->surface(), &SurfaceInterface::pointerConstraintsChanged); 0327 QVERIFY(constraintsChangedSpy.wait()); 0328 0329 // moving cursor should be allowed again 0330 QCOMPARE(input()->pointer()->isConstrained(), false); 0331 KWin::input()->pointer()->warp(window->frameGeometry().center()); 0332 QCOMPARE(KWin::Cursors::self()->mouse()->pos(), window->frameGeometry().center()); 0333 } 0334 0335 void TestPointerConstraints::testCloseWindowWithLockedPointer() 0336 { 0337 // test case which verifies that the pointer gets unlocked when the window for it gets closed 0338 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 0339 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get())); 0340 std::unique_ptr<KWayland::Client::Pointer> pointer(Test::waylandSeat()->createPointer()); 0341 std::unique_ptr<KWayland::Client::LockedPointer> lockedPointer(Test::waylandPointerConstraints()->lockPointer(surface.get(), pointer.get(), nullptr, KWayland::Client::PointerConstraints::LifeTime::OneShot)); 0342 QSignalSpy lockedSpy(lockedPointer.get(), &KWayland::Client::LockedPointer::locked); 0343 QSignalSpy unlockedSpy(lockedPointer.get(), &KWayland::Client::LockedPointer::unlocked); 0344 0345 // now map the window 0346 auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 100), Qt::blue); 0347 QVERIFY(window); 0348 QVERIFY(!exclusiveContains(window->frameGeometry(), KWin::Cursors::self()->mouse()->pos())); 0349 0350 // now let's lock 0351 QCOMPARE(input()->pointer()->isConstrained(), false); 0352 KWin::input()->pointer()->warp(window->frameGeometry().center()); 0353 QCOMPARE(KWin::Cursors::self()->mouse()->pos(), window->frameGeometry().center()); 0354 QCOMPARE(input()->pointer()->isConstrained(), true); 0355 QVERIFY(lockedSpy.wait()); 0356 0357 // close the window 0358 shellSurface.reset(); 0359 surface.reset(); 0360 // this should result in unlocked 0361 QVERIFY(unlockedSpy.wait()); 0362 QCOMPARE(input()->pointer()->isConstrained(), false); 0363 } 0364 0365 WAYLANDTEST_MAIN(TestPointerConstraints) 0366 #include "pointer_constraints_test.moc"