File indexing completed on 2024-11-10 04:56:05
0001 /* 0002 KWin - the KDE window manager 0003 This file is part of the KDE project. 0004 0005 SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org> 0006 SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org> 0007 0008 SPDX-License-Identifier: GPL-2.0-or-later 0009 */ 0010 0011 #include "kwin_wayland_test.h" 0012 0013 #include "atoms.h" 0014 #include "cursor.h" 0015 #include "effect/effectloader.h" 0016 #include "main.h" 0017 #include "pointer_input.h" 0018 #include "screenedge.h" 0019 #include "wayland_server.h" 0020 #include "window.h" 0021 #include "workspace.h" 0022 0023 #include <KConfigGroup> 0024 #include <KWayland/Client/surface.h> 0025 0026 #include <QAbstractEventDispatcher> 0027 #include <QAction> 0028 #include <QSocketNotifier> 0029 0030 #include <xcb/xcb_icccm.h> 0031 0032 Q_DECLARE_METATYPE(KWin::ElectricBorder) 0033 0034 namespace KWin 0035 { 0036 0037 static const QString s_socketName = QStringLiteral("wayland_test_kwin_screen-edges-0"); 0038 0039 class TestObject : public QObject 0040 { 0041 Q_OBJECT 0042 0043 public Q_SLOTS: 0044 bool callback(ElectricBorder border) 0045 { 0046 Q_EMIT gotCallback(border); 0047 return true; 0048 } 0049 0050 Q_SIGNALS: 0051 void gotCallback(KWin::ElectricBorder); 0052 }; 0053 0054 class ScreenEdgesTest : public QObject 0055 { 0056 Q_OBJECT 0057 0058 private Q_SLOTS: 0059 void initTestCase(); 0060 void init(); 0061 void cleanup(); 0062 void testTouchCallback_data(); 0063 void testTouchCallback(); 0064 void testPushBack_data(); 0065 void testPushBack(); 0066 void testObjectEdge_data(); 0067 void testObjectEdge(); 0068 void testKdeNetWmScreenEdgeShow(); 0069 }; 0070 0071 void ScreenEdgesTest::initTestCase() 0072 { 0073 qRegisterMetaType<KWin::Window *>(); 0074 qRegisterMetaType<KWin::ElectricBorder>("ElectricBorder"); 0075 0076 QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); 0077 QVERIFY(waylandServer()->init(s_socketName)); 0078 Test::setOutputConfig({QRect(0, 0, 1280, 1024)}); 0079 0080 // Disable effects, in particular present windows, which reserves a screen edge. 0081 auto config = kwinApp()->config(); 0082 KConfigGroup plugins(config, QStringLiteral("Plugins")); 0083 const auto builtinNames = EffectLoader().listOfKnownEffects(); 0084 for (const QString &name : builtinNames) { 0085 plugins.writeEntry(name + QStringLiteral("Enabled"), false); 0086 } 0087 0088 config->sync(); 0089 kwinApp()->setConfig(config); 0090 0091 kwinApp()->start(); 0092 QVERIFY(applicationStartedSpy.wait()); 0093 } 0094 0095 void ScreenEdgesTest::init() 0096 { 0097 workspace()->screenEdges()->recreateEdges(); 0098 Workspace::self()->setActiveOutput(QPoint(640, 512)); 0099 KWin::input()->pointer()->warp(QPoint(640, 512)); 0100 0101 QVERIFY(Test::setupWaylandConnection()); 0102 } 0103 0104 void ScreenEdgesTest::cleanup() 0105 { 0106 Test::destroyWaylandConnection(); 0107 } 0108 0109 void ScreenEdgesTest::testTouchCallback_data() 0110 { 0111 QTest::addColumn<KWin::ElectricBorder>("border"); 0112 QTest::addColumn<QPointF>("startPos"); 0113 QTest::addColumn<QPointF>("delta"); 0114 0115 QTest::newRow("left") << ElectricLeft << QPointF(0, 50) << QPointF(256, 20); 0116 QTest::newRow("top") << ElectricTop << QPointF(50, 0) << QPointF(20, 250); 0117 QTest::newRow("right") << ElectricRight << QPointF(1279, 50) << QPointF(-256, 0); 0118 QTest::newRow("bottom") << ElectricBottom << QPointF(50, 1023) << QPointF(0, -205); 0119 } 0120 0121 void ScreenEdgesTest::testTouchCallback() 0122 { 0123 // This test verifies that touch screen edges trigger associated callbacks. 0124 0125 auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); 0126 auto group = config->group(QStringLiteral("TouchEdges")); 0127 group.writeEntry("Top", "none"); 0128 group.writeEntry("Left", "none"); 0129 group.writeEntry("Bottom", "none"); 0130 group.writeEntry("Right", "none"); 0131 config->sync(); 0132 0133 auto s = workspace()->screenEdges(); 0134 s->setConfig(config); 0135 s->reconfigure(); 0136 0137 // none of our actions should be reserved 0138 const auto &edges = s->edges(); 0139 QCOMPARE(edges.size(), 8); 0140 for (auto &edge : edges) { 0141 QCOMPARE(edge->isReserved(), false); 0142 QCOMPARE(edge->activatesForPointer(), false); 0143 QCOMPARE(edge->activatesForTouchGesture(), false); 0144 } 0145 0146 // let's reserve an action 0147 QAction action; 0148 QSignalSpy actionTriggeredSpy(&action, &QAction::triggered); 0149 0150 // reserve on edge 0151 QFETCH(KWin::ElectricBorder, border); 0152 s->reserveTouch(border, &action); 0153 for (auto &edge : edges) { 0154 QCOMPARE(edge->isReserved(), edge->border() == border); 0155 QCOMPARE(edge->activatesForPointer(), false); 0156 QCOMPARE(edge->activatesForTouchGesture(), edge->border() == border); 0157 } 0158 0159 quint32 timestamp = 0; 0160 0161 // press the finger 0162 QFETCH(QPointF, startPos); 0163 Test::touchDown(1, startPos, timestamp++); 0164 QVERIFY(actionTriggeredSpy.isEmpty()); 0165 0166 // move the finger 0167 QFETCH(QPointF, delta); 0168 Test::touchMotion(1, startPos + delta, timestamp++); 0169 QVERIFY(actionTriggeredSpy.isEmpty()); 0170 0171 // release the finger 0172 Test::touchUp(1, timestamp++); 0173 QVERIFY(actionTriggeredSpy.wait()); 0174 QCOMPARE(actionTriggeredSpy.count(), 1); 0175 0176 // unreserve again 0177 s->unreserveTouch(border, &action); 0178 for (auto &edge : edges) { 0179 QCOMPARE(edge->isReserved(), false); 0180 QCOMPARE(edge->activatesForPointer(), false); 0181 QCOMPARE(edge->activatesForTouchGesture(), false); 0182 } 0183 0184 // reserve another action 0185 std::unique_ptr<QAction> action2(new QAction); 0186 s->reserveTouch(border, action2.get()); 0187 for (auto &edge : edges) { 0188 QCOMPARE(edge->isReserved(), edge->border() == border); 0189 QCOMPARE(edge->activatesForPointer(), false); 0190 QCOMPARE(edge->activatesForTouchGesture(), edge->border() == border); 0191 } 0192 0193 // and unreserve by destroying 0194 action2.reset(); 0195 for (auto &edge : edges) { 0196 QCOMPARE(edge->isReserved(), false); 0197 QCOMPARE(edge->activatesForPointer(), false); 0198 QCOMPARE(edge->activatesForTouchGesture(), false); 0199 } 0200 } 0201 0202 void ScreenEdgesTest::testPushBack_data() 0203 { 0204 QTest::addColumn<KWin::ElectricBorder>("border"); 0205 QTest::addColumn<int>("pushback"); 0206 QTest::addColumn<QPointF>("trigger"); 0207 QTest::addColumn<QPointF>("expected"); 0208 0209 QTest::newRow("top-left-3") << ElectricTopLeft << 3 << QPointF(0, 0) << QPointF(3, 3); 0210 QTest::newRow("top-5") << ElectricTop << 5 << QPointF(50, 0) << QPointF(50, 5); 0211 QTest::newRow("top-right-2") << ElectricTopRight << 2 << QPointF(1279, 0) << QPointF(1277, 2); 0212 QTest::newRow("right-10") << ElectricRight << 10 << QPointF(1279, 50) << QPointF(1269, 50); 0213 QTest::newRow("bottom-right-5") << ElectricBottomRight << 5 << QPointF(1279, 1023) << QPointF(1274, 1018); 0214 QTest::newRow("bottom-10") << ElectricBottom << 10 << QPointF(50, 1023) << QPointF(50, 1013); 0215 QTest::newRow("bottom-left-3") << ElectricBottomLeft << 3 << QPointF(0, 1023) << QPointF(3, 1020); 0216 QTest::newRow("left-10") << ElectricLeft << 10 << QPointF(0, 50) << QPointF(10, 50); 0217 QTest::newRow("invalid") << ElectricLeft << 10 << QPointF(50, 0) << QPointF(50, 0); 0218 } 0219 0220 void ScreenEdgesTest::testPushBack() 0221 { 0222 // This test verifies that the pointer will be pushed back if it approached a screen edge. 0223 0224 QFETCH(int, pushback); 0225 auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); 0226 config->group(QStringLiteral("Windows")).writeEntry("ElectricBorderPushbackPixels", pushback); 0227 config->sync(); 0228 0229 auto s = workspace()->screenEdges(); 0230 s->setConfig(config); 0231 s->reconfigure(); 0232 0233 TestObject callback; 0234 QSignalSpy spy(&callback, &TestObject::gotCallback); 0235 0236 QFETCH(ElectricBorder, border); 0237 s->reserve(border, &callback, "callback"); 0238 0239 QFETCH(QPointF, trigger); 0240 Test::pointerMotion(trigger, 0); 0241 QVERIFY(spy.isEmpty()); 0242 QTEST(Cursors::self()->mouse()->pos(), "expected"); 0243 } 0244 0245 void ScreenEdgesTest::testObjectEdge_data() 0246 { 0247 QTest::addColumn<ElectricBorder>("border"); 0248 QTest::addColumn<QPointF>("triggerPoint"); 0249 QTest::addColumn<QPointF>("delta"); 0250 0251 QTest::newRow("top") << ElectricTop << QPointF(640, 0) << QPointF(0, 50); 0252 QTest::newRow("right") << ElectricRight << QPointF(1279, 512) << QPointF(-50, 0); 0253 QTest::newRow("bottom") << ElectricBottom << QPointF(640, 1023) << QPointF(0, -50); 0254 QTest::newRow("left") << ElectricLeft << QPointF(0, 512) << QPointF(50, 0); 0255 } 0256 0257 void ScreenEdgesTest::testObjectEdge() 0258 { 0259 // This test verifies that a screen edge reserved by a script or any QObject is activated. 0260 0261 TestObject callback; 0262 QSignalSpy spy(&callback, &TestObject::gotCallback); 0263 0264 // Reserve a screen edge border. 0265 QFETCH(ElectricBorder, border); 0266 workspace()->screenEdges()->reserve(border, &callback, "callback"); 0267 0268 QFETCH(QPointF, triggerPoint); 0269 QFETCH(QPointF, delta); 0270 0271 // doesn't trigger as the edge was not triggered yet 0272 qint64 timestamp = 0; 0273 Test::pointerMotion(triggerPoint + delta, timestamp); 0274 QVERIFY(spy.isEmpty()); 0275 0276 // test doesn't trigger due to too much offset 0277 timestamp += 160; 0278 Test::pointerMotion(triggerPoint, timestamp); 0279 QVERIFY(spy.isEmpty()); 0280 0281 // doesn't activate as we are waiting too short 0282 timestamp += 50; 0283 Test::pointerMotion(triggerPoint, timestamp); 0284 QVERIFY(spy.isEmpty()); 0285 0286 // and this one triggers 0287 timestamp += 110; 0288 Test::pointerMotion(triggerPoint, timestamp); 0289 QVERIFY(!spy.isEmpty()); 0290 0291 // now let's try to trigger again 0292 timestamp += 351; 0293 Test::pointerMotion(triggerPoint, timestamp); 0294 QCOMPARE(spy.count(), 1); 0295 0296 // it's still under the reactivation 0297 timestamp += 50; 0298 Test::pointerMotion(triggerPoint, timestamp); 0299 QCOMPARE(spy.count(), 1); 0300 0301 // now it should trigger again 0302 timestamp += 250; 0303 Test::pointerMotion(triggerPoint, timestamp); 0304 QCOMPARE(spy.count(), 2); 0305 } 0306 0307 static void enableAutoHide(xcb_connection_t *connection, xcb_window_t windowId, ElectricBorder border) 0308 { 0309 if (border == ElectricNone) { 0310 xcb_delete_property(connection, windowId, atoms->kde_screen_edge_show); 0311 } else { 0312 uint32_t value = 0; 0313 0314 switch (border) { 0315 case ElectricTop: 0316 value = 0; 0317 break; 0318 case ElectricRight: 0319 value = 1; 0320 break; 0321 case ElectricBottom: 0322 value = 2; 0323 break; 0324 case ElectricLeft: 0325 value = 3; 0326 break; 0327 default: 0328 Q_UNREACHABLE(); 0329 } 0330 0331 xcb_change_property(connection, XCB_PROP_MODE_REPLACE, windowId, atoms->kde_screen_edge_show, XCB_ATOM_CARDINAL, 32, 1, &value); 0332 } 0333 } 0334 0335 class ScreenEdgePropertyMonitor : public QObject 0336 { 0337 Q_OBJECT 0338 public: 0339 ScreenEdgePropertyMonitor(xcb_connection_t *c, xcb_window_t window) 0340 : QObject() 0341 , m_connection(c) 0342 , m_window(window) 0343 , m_notifier(new QSocketNotifier(xcb_get_file_descriptor(m_connection), QSocketNotifier::Read, this)) 0344 { 0345 connect(m_notifier, &QSocketNotifier::activated, this, &ScreenEdgePropertyMonitor::processXcbEvents); 0346 connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, this, &ScreenEdgePropertyMonitor::processXcbEvents); 0347 connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::awake, this, &ScreenEdgePropertyMonitor::processXcbEvents); 0348 } 0349 0350 Q_SIGNALS: 0351 void withdrawn(); 0352 0353 private: 0354 void processXcbEvents() 0355 { 0356 while (auto event = xcb_poll_for_event(m_connection)) { 0357 const uint8_t eventType = event->response_type & ~0x80; 0358 switch (eventType) { 0359 case XCB_PROPERTY_NOTIFY: { 0360 auto propertyNotifyEvent = reinterpret_cast<xcb_property_notify_event_t *>(event); 0361 if (propertyNotifyEvent->window == m_window && propertyNotifyEvent->atom == atoms->kde_screen_edge_show && propertyNotifyEvent->state == XCB_PROPERTY_DELETE) { 0362 Q_EMIT withdrawn(); 0363 } 0364 break; 0365 } 0366 } 0367 free(event); 0368 } 0369 } 0370 0371 xcb_connection_t *m_connection; 0372 xcb_window_t m_window; 0373 QSocketNotifier *m_notifier; 0374 }; 0375 0376 void ScreenEdgesTest::testKdeNetWmScreenEdgeShow() 0377 { 0378 // This test verifies that _KDE_NET_WM_SCREEN_EDGE_SHOW is handled properly. Note that 0379 // _KDE_NET_WM_SCREEN_EDGE_SHOW has oneshot effect. It's deleted when the window is shown. 0380 0381 auto config = kwinApp()->config(); 0382 config->group(QStringLiteral("Windows")).writeEntry("ElectricBorderDelay", 75); 0383 config->sync(); 0384 workspace()->slotReconfigure(); 0385 0386 Test::XcbConnectionPtr c = Test::createX11Connection(); 0387 QVERIFY(!xcb_connection_has_error(c.get())); 0388 0389 // Create a test window at the bottom of the screen. 0390 const QRect windowGeometry(0, 1024 - 30, 1280, 30); 0391 const uint32_t values[] = {XCB_EVENT_MASK_PROPERTY_CHANGE}; 0392 xcb_window_t windowId = xcb_generate_id(c.get()); 0393 xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), 0394 windowGeometry.x(), 0395 windowGeometry.y(), 0396 windowGeometry.width(), 0397 windowGeometry.height(), 0398 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, 0399 XCB_COPY_FROM_PARENT, 0400 XCB_CW_EVENT_MASK, values); 0401 xcb_size_hints_t hints; 0402 memset(&hints, 0, sizeof(hints)); 0403 xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); 0404 xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); 0405 xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); 0406 xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &windowId); 0407 xcb_map_window(c.get(), windowId); 0408 xcb_flush(c.get()); 0409 0410 QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); 0411 QVERIFY(windowCreatedSpy.wait()); 0412 Window *window = windowCreatedSpy.first().first().value<Window *>(); 0413 QVERIFY(window); 0414 0415 ScreenEdgePropertyMonitor screenEdgeMonitor(c.get(), windowId); 0416 QSignalSpy withdrawnSpy(&screenEdgeMonitor, &ScreenEdgePropertyMonitor::withdrawn); 0417 QSignalSpy windowShownSpy(window, &Window::windowShown); 0418 QSignalSpy windowHiddenSpy(window, &Window::windowHidden); 0419 quint32 timestamp = 0; 0420 0421 // The window will be shown when the pointer approaches its reserved screen edge. 0422 { 0423 enableAutoHide(c.get(), windowId, ElectricBottom); 0424 xcb_flush(c.get()); 0425 QVERIFY(windowHiddenSpy.wait()); 0426 QVERIFY(!window->isShown()); 0427 0428 Test::pointerMotion(QPointF(640, 1023), timestamp); 0429 timestamp += 160; 0430 Test::pointerMotion(QPointF(640, 1023), timestamp); 0431 QVERIFY(withdrawnSpy.wait()); 0432 QVERIFY(window->isShown()); 0433 timestamp += 160; 0434 Test::pointerMotion(QPointF(640, 512), timestamp); 0435 QVERIFY(window->isShown()); 0436 } 0437 0438 // The window will be shown when swiping on the touch screen. 0439 { 0440 enableAutoHide(c.get(), windowId, ElectricBottom); 0441 xcb_flush(c.get()); 0442 QVERIFY(windowHiddenSpy.wait()); 0443 QVERIFY(!window->isShown()); 0444 0445 Test::touchDown(0, QPointF(640, 1023), timestamp++); 0446 Test::touchMotion(0, QPointF(640, 512), timestamp++); 0447 Test::touchUp(0, timestamp++); 0448 QVERIFY(withdrawnSpy.wait()); 0449 QVERIFY(window->isShown()); 0450 } 0451 0452 // The screen edge reservation won't be affected when recreating screen edges (can happen when the screen layout changes). 0453 { 0454 enableAutoHide(c.get(), windowId, ElectricBottom); 0455 xcb_flush(c.get()); 0456 QVERIFY(windowHiddenSpy.wait()); 0457 QVERIFY(!window->isShown()); 0458 0459 workspace()->screenEdges()->recreateEdges(); 0460 QVERIFY(!withdrawnSpy.wait(50)); 0461 QVERIFY(!window->isShown()); 0462 0463 enableAutoHide(c.get(), windowId, ElectricNone); 0464 xcb_flush(c.get()); 0465 QVERIFY(windowShownSpy.wait()); 0466 QVERIFY(window->isShown()); 0467 } 0468 0469 // The window will be shown and hidden in response to changing _KDE_NET_WM_SCREEN_EDGE_SHOW. 0470 { 0471 enableAutoHide(c.get(), windowId, ElectricBottom); 0472 xcb_flush(c.get()); 0473 QVERIFY(windowHiddenSpy.wait()); 0474 QVERIFY(!window->isShown()); 0475 0476 enableAutoHide(c.get(), windowId, ElectricNone); 0477 xcb_flush(c.get()); 0478 QVERIFY(windowShownSpy.wait()); 0479 QVERIFY(window->isShown()); 0480 } 0481 0482 // The approaching state will be reset if the window is shown manually. 0483 { 0484 QSignalSpy approachingSpy(workspace()->screenEdges(), &ScreenEdges::approaching); 0485 enableAutoHide(c.get(), windowId, ElectricBottom); 0486 xcb_flush(c.get()); 0487 QVERIFY(windowHiddenSpy.wait()); 0488 QVERIFY(!window->isShown()); 0489 0490 Test::pointerMotion(QPointF(640, 1020), timestamp++); 0491 QVERIFY(approachingSpy.last().at(1).toReal() == 0.0); 0492 Test::pointerMotion(QPointF(640, 1021), timestamp++); 0493 QVERIFY(approachingSpy.last().at(1).toReal() != 0.0); 0494 0495 enableAutoHide(c.get(), windowId, ElectricNone); 0496 xcb_flush(c.get()); 0497 QVERIFY(windowShownSpy.wait()); 0498 QVERIFY(window->isShown()); 0499 QVERIFY(approachingSpy.last().at(1).toReal() == 0.0); 0500 0501 Test::pointerMotion(QPointF(640, 512), timestamp++); 0502 } 0503 } 0504 0505 } // namespace KWin 0506 0507 WAYLANDTEST_MAIN(KWin::ScreenEdgesTest) 0508 #include "screenedges_test.moc"