File indexing completed on 2025-03-23 13:48:04
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 "core/outputbackend.h" 0013 #include "cursor.h" 0014 #include "wayland_server.h" 0015 #include "workspace.h" 0016 0017 #include <KWayland/Client/output.h> 0018 #include <KWayland/Client/registry.h> 0019 #include <KWayland/Client/xdgoutput.h> 0020 0021 using namespace KWin; 0022 0023 static const QString s_socketName = QStringLiteral("wayland_test_kwin_screen_changes-0"); 0024 0025 class ScreenChangesTest : public QObject 0026 { 0027 Q_OBJECT 0028 private Q_SLOTS: 0029 void initTestCase(); 0030 void init(); 0031 void cleanup(); 0032 0033 void testScreenAddRemove(); 0034 }; 0035 0036 void ScreenChangesTest::initTestCase() 0037 { 0038 QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); 0039 QVERIFY(waylandServer()->init(s_socketName)); 0040 QMetaObject::invokeMethod(kwinApp()->outputBackend(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(QVector<QRect>, QVector<QRect>() << QRect(0, 0, 1280, 1024))); 0041 0042 kwinApp()->start(); 0043 QVERIFY(applicationStartedSpy.wait()); 0044 setenv("QT_QPA_PLATFORM", "wayland", true); 0045 } 0046 0047 void ScreenChangesTest::init() 0048 { 0049 QVERIFY(Test::setupWaylandConnection()); 0050 0051 workspace()->setActiveOutput(QPoint(640, 512)); 0052 KWin::Cursors::self()->mouse()->setPos(QPoint(640, 512)); 0053 } 0054 0055 void ScreenChangesTest::cleanup() 0056 { 0057 Test::destroyWaylandConnection(); 0058 } 0059 0060 void ScreenChangesTest::testScreenAddRemove() 0061 { 0062 // this test verifies that when a new screen is added it gets synced to Wayland 0063 0064 // first create a registry to get signals about Outputs announced/removed 0065 KWayland::Client::Registry registry; 0066 QSignalSpy allAnnounced(®istry, &KWayland::Client::Registry::interfacesAnnounced); 0067 QSignalSpy outputAnnouncedSpy(®istry, &KWayland::Client::Registry::outputAnnounced); 0068 QSignalSpy outputRemovedSpy(®istry, &KWayland::Client::Registry::outputRemoved); 0069 registry.create(Test::waylandConnection()); 0070 QVERIFY(registry.isValid()); 0071 registry.setup(); 0072 QVERIFY(allAnnounced.wait()); 0073 const auto xdgOMData = registry.interface(KWayland::Client::Registry::Interface::XdgOutputUnstableV1); 0074 auto xdgOutputManager = registry.createXdgOutputManager(xdgOMData.name, xdgOMData.version); 0075 0076 // should be one output 0077 QCOMPARE(workspace()->outputs().count(), 1); 0078 QCOMPARE(outputAnnouncedSpy.count(), 1); 0079 const quint32 firstOutputId = outputAnnouncedSpy.first().first().value<quint32>(); 0080 QVERIFY(firstOutputId != 0u); 0081 outputAnnouncedSpy.clear(); 0082 0083 // let's announce a new output 0084 const QVector<QRect> geometries{QRect(0, 0, 1280, 1024), QRect(1280, 0, 1280, 1024)}; 0085 QMetaObject::invokeMethod(kwinApp()->outputBackend(), "setVirtualOutputs", 0086 Qt::DirectConnection, 0087 Q_ARG(QVector<QRect>, geometries)); 0088 auto outputs = workspace()->outputs(); 0089 QCOMPARE(outputs.count(), 2); 0090 QCOMPARE(outputs[0]->geometry(), geometries[0]); 0091 QCOMPARE(outputs[1]->geometry(), geometries[1]); 0092 0093 // this should result in it getting announced, two new outputs are added... 0094 QVERIFY(outputAnnouncedSpy.wait()); 0095 if (outputAnnouncedSpy.count() < 2) { 0096 QVERIFY(outputAnnouncedSpy.wait()); 0097 } 0098 QCOMPARE(outputAnnouncedSpy.count(), 2); 0099 // ... and afterward the previous output gets removed 0100 if (outputRemovedSpy.isEmpty()) { 0101 QVERIFY(outputRemovedSpy.wait()); 0102 } 0103 QCOMPARE(outputRemovedSpy.count(), 1); 0104 QCOMPARE(outputRemovedSpy.first().first().value<quint32>(), firstOutputId); 0105 0106 // let's wait a little bit to ensure we don't get more events 0107 QTest::qWait(100); 0108 QCOMPARE(outputAnnouncedSpy.count(), 2); 0109 QCOMPARE(outputRemovedSpy.count(), 1); 0110 0111 // let's create the output objects to ensure they are correct 0112 std::unique_ptr<KWayland::Client::Output> o1(registry.createOutput(outputAnnouncedSpy.first().first().value<quint32>(), outputAnnouncedSpy.first().last().value<quint32>())); 0113 QVERIFY(o1->isValid()); 0114 QSignalSpy o1ChangedSpy(o1.get(), &KWayland::Client::Output::changed); 0115 QVERIFY(o1ChangedSpy.wait()); 0116 KWin::Output *serverOutput1 = kwinApp()->outputBackend()->findOutput(o1->name()); // use wl_output.name to find the compositor side output 0117 QCOMPARE(o1->globalPosition(), serverOutput1->geometry().topLeft()); 0118 QCOMPARE(o1->pixelSize(), serverOutput1->modeSize()); 0119 std::unique_ptr<KWayland::Client::Output> o2(registry.createOutput(outputAnnouncedSpy.last().first().value<quint32>(), outputAnnouncedSpy.last().last().value<quint32>())); 0120 QVERIFY(o2->isValid()); 0121 QSignalSpy o2ChangedSpy(o2.get(), &KWayland::Client::Output::changed); 0122 QVERIFY(o2ChangedSpy.wait()); 0123 KWin::Output *serverOutput2 = kwinApp()->outputBackend()->findOutput(o2->name()); // use wl_output.name to find the compositor side output 0124 QCOMPARE(o2->globalPosition(), serverOutput2->geometry().topLeft()); 0125 QCOMPARE(o2->pixelSize(), serverOutput2->modeSize()); 0126 0127 // and check XDGOutput is synced 0128 std::unique_ptr<KWayland::Client::XdgOutput> xdgO1(xdgOutputManager->getXdgOutput(o1.get())); 0129 QSignalSpy xdgO1ChangedSpy(xdgO1.get(), &KWayland::Client::XdgOutput::changed); 0130 QVERIFY(xdgO1ChangedSpy.wait()); 0131 QCOMPARE(xdgO1->logicalPosition(), serverOutput1->geometry().topLeft()); 0132 QCOMPARE(xdgO1->logicalSize(), serverOutput1->geometry().size()); 0133 std::unique_ptr<KWayland::Client::XdgOutput> xdgO2(xdgOutputManager->getXdgOutput(o2.get())); 0134 QSignalSpy xdgO2ChangedSpy(xdgO2.get(), &KWayland::Client::XdgOutput::changed); 0135 QVERIFY(xdgO2ChangedSpy.wait()); 0136 QCOMPARE(xdgO2->logicalPosition(), serverOutput2->geometry().topLeft()); 0137 QCOMPARE(xdgO2->logicalSize(), serverOutput2->geometry().size()); 0138 0139 QVERIFY(xdgO1->name().startsWith("Virtual-")); 0140 QVERIFY(xdgO1->name() != xdgO2->name()); 0141 QVERIFY(!xdgO1->description().isEmpty()); 0142 0143 // now let's try to remove one output again 0144 outputAnnouncedSpy.clear(); 0145 outputRemovedSpy.clear(); 0146 0147 QSignalSpy o1RemovedSpy(o1.get(), &KWayland::Client::Output::removed); 0148 QSignalSpy o2RemovedSpy(o2.get(), &KWayland::Client::Output::removed); 0149 0150 const QVector<QRect> geometries2{QRect(0, 0, 1280, 1024)}; 0151 QMetaObject::invokeMethod(kwinApp()->outputBackend(), "setVirtualOutputs", 0152 Qt::DirectConnection, 0153 Q_ARG(QVector<QRect>, geometries2)); 0154 outputs = workspace()->outputs(); 0155 QCOMPARE(outputs.count(), 1); 0156 QCOMPARE(outputs[0]->geometry(), geometries2.at(0)); 0157 0158 QVERIFY(outputAnnouncedSpy.wait()); 0159 QCOMPARE(outputAnnouncedSpy.count(), 1); 0160 if (o1RemovedSpy.isEmpty()) { 0161 QVERIFY(o1RemovedSpy.wait()); 0162 } 0163 if (o2RemovedSpy.isEmpty()) { 0164 QVERIFY(o2RemovedSpy.wait()); 0165 } 0166 // now wait a bit to ensure we don't get more events 0167 QTest::qWait(100); 0168 QCOMPARE(outputAnnouncedSpy.count(), 1); 0169 QCOMPARE(o1RemovedSpy.count(), 1); 0170 QCOMPARE(o2RemovedSpy.count(), 1); 0171 QCOMPARE(outputRemovedSpy.count(), 2); 0172 } 0173 0174 WAYLANDTEST_MAIN(ScreenChangesTest) 0175 #include "screen_changes_test.moc"