File indexing completed on 2024-11-10 04:55:55
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 "effect/effecthandler.h" 0010 #include "effect/effectloader.h" 0011 #include "kwin_wayland_test.h" 0012 #include "wayland_server.h" 0013 #include "workspace.h" 0014 #include "x11window.h" 0015 0016 #include <KConfigGroup> 0017 0018 #include <KWayland/Client/connection_thread.h> 0019 #include <KWayland/Client/registry.h> 0020 #include <KWayland/Client/slide.h> 0021 #include <KWayland/Client/surface.h> 0022 0023 #include <netwm.h> 0024 #include <xcb/xcb_icccm.h> 0025 0026 using namespace KWin; 0027 static const QString s_socketName = QStringLiteral("wayland_test_effects_slidingpopups-0"); 0028 0029 class SlidingPopupsTest : public QObject 0030 { 0031 Q_OBJECT 0032 private Q_SLOTS: 0033 void initTestCase(); 0034 void init(); 0035 void cleanup(); 0036 0037 void testWithOtherEffect_data(); 0038 void testWithOtherEffect(); 0039 void testWithOtherEffectWayland_data(); 0040 void testWithOtherEffectWayland(); 0041 }; 0042 0043 void SlidingPopupsTest::initTestCase() 0044 { 0045 if (!Test::renderNodeAvailable()) { 0046 QSKIP("no render node available"); 0047 return; 0048 } 0049 qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8()); 0050 qRegisterMetaType<KWin::Window *>(); 0051 qRegisterMetaType<KWin::Effect *>(); 0052 QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); 0053 QVERIFY(waylandServer()->init(s_socketName)); 0054 Test::setOutputConfig({ 0055 QRect(0, 0, 1280, 1024), 0056 QRect(1280, 0, 1280, 1024), 0057 }); 0058 0059 // disable all effects - we don't want to have it interact with the rendering 0060 auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); 0061 KConfigGroup plugins(config, QStringLiteral("Plugins")); 0062 const auto builtinNames = EffectLoader().listOfKnownEffects(); 0063 for (QString name : builtinNames) { 0064 plugins.writeEntry(name + QStringLiteral("Enabled"), false); 0065 } 0066 KConfigGroup wobblyGroup = config->group(QStringLiteral("Effect-Wobbly")); 0067 wobblyGroup.writeEntry(QStringLiteral("Settings"), QStringLiteral("Custom")); 0068 wobblyGroup.writeEntry(QStringLiteral("OpenEffect"), true); 0069 wobblyGroup.writeEntry(QStringLiteral("CloseEffect"), true); 0070 0071 config->sync(); 0072 kwinApp()->setConfig(config); 0073 0074 qputenv("KWIN_COMPOSE", QByteArrayLiteral("O2")); 0075 qputenv("KWIN_EFFECTS_FORCE_ANIMATIONS", "1"); 0076 kwinApp()->start(); 0077 QVERIFY(applicationStartedSpy.wait()); 0078 } 0079 0080 void SlidingPopupsTest::init() 0081 { 0082 QVERIFY(Test::setupWaylandConnection()); 0083 } 0084 0085 void SlidingPopupsTest::cleanup() 0086 { 0087 Test::destroyWaylandConnection(); 0088 while (!effects->loadedEffects().isEmpty()) { 0089 const QString effect = effects->loadedEffects().first(); 0090 effects->unloadEffect(effect); 0091 QVERIFY(!effects->isEffectLoaded(effect)); 0092 } 0093 } 0094 0095 void SlidingPopupsTest::testWithOtherEffect_data() 0096 { 0097 QTest::addColumn<QStringList>("effectsToLoad"); 0098 0099 QTest::newRow("fade, slide") << QStringList{QStringLiteral("fade"), QStringLiteral("slidingpopups")}; 0100 QTest::newRow("slide, fade") << QStringList{QStringLiteral("slidingpopups"), QStringLiteral("fade")}; 0101 QTest::newRow("scale, slide") << QStringList{QStringLiteral("scale"), QStringLiteral("slidingpopups")}; 0102 QTest::newRow("slide, scale") << QStringList{QStringLiteral("slidingpopups"), QStringLiteral("scale")}; 0103 0104 if (effects->compositingType() & KWin::OpenGLCompositing) { 0105 QTest::newRow("glide, slide") << QStringList{QStringLiteral("glide"), QStringLiteral("slidingpopups")}; 0106 QTest::newRow("slide, glide") << QStringList{QStringLiteral("slidingpopups"), QStringLiteral("glide")}; 0107 QTest::newRow("wobblywindows, slide") << QStringList{QStringLiteral("wobblywindows"), QStringLiteral("slidingpopups")}; 0108 QTest::newRow("slide, wobblywindows") << QStringList{QStringLiteral("slidingpopups"), QStringLiteral("wobblywindows")}; 0109 QTest::newRow("fallapart, slide") << QStringList{QStringLiteral("fallapart"), QStringLiteral("slidingpopups")}; 0110 QTest::newRow("slide, fallapart") << QStringList{QStringLiteral("slidingpopups"), QStringLiteral("fallapart")}; 0111 } 0112 } 0113 0114 void SlidingPopupsTest::testWithOtherEffect() 0115 { 0116 // this test verifies that slidingpopups effect grabs the window added role 0117 // independently of the sequence how the effects are loaded. 0118 // see BUG 336866 0119 // find the effectsloader 0120 auto effectloader = effects->findChild<AbstractEffectLoader *>(); 0121 QVERIFY(effectloader); 0122 QSignalSpy effectLoadedSpy(effectloader, &AbstractEffectLoader::effectLoaded); 0123 0124 Effect *slidingPoupus = nullptr; 0125 Effect *otherEffect = nullptr; 0126 QFETCH(QStringList, effectsToLoad); 0127 for (const QString &effectName : effectsToLoad) { 0128 QVERIFY(!effects->isEffectLoaded(effectName)); 0129 QVERIFY(effects->loadEffect(effectName)); 0130 QVERIFY(effects->isEffectLoaded(effectName)); 0131 0132 QCOMPARE(effectLoadedSpy.count(), 1); 0133 Effect *effect = effectLoadedSpy.first().first().value<Effect *>(); 0134 if (effectName == QStringLiteral("slidingpopups")) { 0135 slidingPoupus = effect; 0136 } else { 0137 otherEffect = effect; 0138 } 0139 effectLoadedSpy.clear(); 0140 } 0141 QVERIFY(slidingPoupus); 0142 QVERIFY(otherEffect); 0143 0144 QVERIFY(!slidingPoupus->isActive()); 0145 QVERIFY(!otherEffect->isActive()); 0146 0147 QSignalSpy windowAddedSpy(effects, &EffectsHandler::windowAdded); 0148 0149 // create an xcb window 0150 Test::XcbConnectionPtr c = Test::createX11Connection(); 0151 QVERIFY(!xcb_connection_has_error(c.get())); 0152 const QRect windowGeometry(0, 0, 100, 200); 0153 xcb_window_t windowId = xcb_generate_id(c.get()); 0154 xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), 0155 windowGeometry.x(), 0156 windowGeometry.y(), 0157 windowGeometry.width(), 0158 windowGeometry.height(), 0159 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); 0160 xcb_size_hints_t hints; 0161 memset(&hints, 0, sizeof(hints)); 0162 xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); 0163 xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); 0164 xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); 0165 NETWinInfo winInfo(c.get(), windowId, rootWindow(), NET::Properties(), NET::Properties2()); 0166 winInfo.setWindowType(NET::Normal); 0167 0168 // and get the slide atom 0169 const QByteArray effectAtomName = QByteArrayLiteral("_KDE_SLIDE"); 0170 xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c.get(), false, effectAtomName.length(), effectAtomName.constData()); 0171 const int size = 2; 0172 int32_t data[size]; 0173 data[0] = 0; 0174 data[1] = 0; 0175 UniqueCPtr<xcb_intern_atom_reply_t> atom(xcb_intern_atom_reply(c.get(), atomCookie, nullptr)); 0176 QVERIFY(atom != nullptr); 0177 xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atom->atom, atom->atom, 32, size, data); 0178 0179 xcb_map_window(c.get(), windowId); 0180 xcb_flush(c.get()); 0181 0182 // we should get a window for it 0183 QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); 0184 QVERIFY(windowCreatedSpy.wait()); 0185 X11Window *window = windowCreatedSpy.first().first().value<X11Window *>(); 0186 QVERIFY(window); 0187 QCOMPARE(window->window(), windowId); 0188 QVERIFY(window->isNormalWindow()); 0189 0190 // sliding popups should be active 0191 QCOMPARE(windowAddedSpy.count(), 1); 0192 QTRY_VERIFY(slidingPoupus->isActive()); 0193 QVERIFY(!otherEffect->isActive()); 0194 0195 // wait till effect ends 0196 QTRY_VERIFY(!slidingPoupus->isActive()); 0197 QTRY_VERIFY(!otherEffect->isActive()); 0198 0199 // and destroy the window again 0200 xcb_unmap_window(c.get(), windowId); 0201 xcb_flush(c.get()); 0202 0203 QSignalSpy closedSpy(window, &X11Window::closed); 0204 0205 QSignalSpy windowDeletedSpy(effects, &EffectsHandler::windowDeleted); 0206 QVERIFY(closedSpy.wait()); 0207 0208 // again we should have the sliding popups active 0209 QVERIFY(slidingPoupus->isActive()); 0210 QVERIFY(!otherEffect->isActive()); 0211 0212 QVERIFY(windowDeletedSpy.wait()); 0213 0214 QCOMPARE(windowDeletedSpy.count(), 1); 0215 QVERIFY(!slidingPoupus->isActive()); 0216 QVERIFY(!otherEffect->isActive()); 0217 xcb_destroy_window(c.get(), windowId); 0218 c.reset(); 0219 } 0220 0221 void SlidingPopupsTest::testWithOtherEffectWayland_data() 0222 { 0223 QTest::addColumn<QStringList>("effectsToLoad"); 0224 0225 QTest::newRow("fade, slide") << QStringList{QStringLiteral("fade"), QStringLiteral("slidingpopups")}; 0226 QTest::newRow("slide, fade") << QStringList{QStringLiteral("slidingpopups"), QStringLiteral("fade")}; 0227 QTest::newRow("scale, slide") << QStringList{QStringLiteral("scale"), QStringLiteral("slidingpopups")}; 0228 QTest::newRow("slide, scale") << QStringList{QStringLiteral("slidingpopups"), QStringLiteral("scale")}; 0229 0230 if (effects->compositingType() & KWin::OpenGLCompositing) { 0231 QTest::newRow("glide, slide") << QStringList{QStringLiteral("glide"), QStringLiteral("slidingpopups")}; 0232 QTest::newRow("slide, glide") << QStringList{QStringLiteral("slidingpopups"), QStringLiteral("glide")}; 0233 QTest::newRow("wobblywindows, slide") << QStringList{QStringLiteral("wobblywindows"), QStringLiteral("slidingpopups")}; 0234 QTest::newRow("slide, wobblywindows") << QStringList{QStringLiteral("slidingpopups"), QStringLiteral("wobblywindows")}; 0235 QTest::newRow("fallapart, slide") << QStringList{QStringLiteral("fallapart"), QStringLiteral("slidingpopups")}; 0236 QTest::newRow("slide, fallapart") << QStringList{QStringLiteral("slidingpopups"), QStringLiteral("fallapart")}; 0237 } 0238 } 0239 0240 void SlidingPopupsTest::testWithOtherEffectWayland() 0241 { 0242 // this test verifies that slidingpopups effect grabs the window added role 0243 // independently of the sequence how the effects are loaded. 0244 // see BUG 336866 0245 // the test is like testWithOtherEffect, but simulates using a Wayland window 0246 // find the effectsloader 0247 auto effectloader = effects->findChild<AbstractEffectLoader *>(); 0248 QVERIFY(effectloader); 0249 QSignalSpy effectLoadedSpy(effectloader, &AbstractEffectLoader::effectLoaded); 0250 0251 Effect *slidingPoupus = nullptr; 0252 Effect *otherEffect = nullptr; 0253 QFETCH(QStringList, effectsToLoad); 0254 for (const QString &effectName : effectsToLoad) { 0255 QVERIFY(!effects->isEffectLoaded(effectName)); 0256 QVERIFY(effects->loadEffect(effectName)); 0257 QVERIFY(effects->isEffectLoaded(effectName)); 0258 0259 QCOMPARE(effectLoadedSpy.count(), 1); 0260 Effect *effect = effectLoadedSpy.first().first().value<Effect *>(); 0261 if (effectName == QStringLiteral("slidingpopups")) { 0262 slidingPoupus = effect; 0263 } else { 0264 otherEffect = effect; 0265 } 0266 effectLoadedSpy.clear(); 0267 } 0268 QVERIFY(slidingPoupus); 0269 QVERIFY(otherEffect); 0270 0271 QVERIFY(!slidingPoupus->isActive()); 0272 QVERIFY(!otherEffect->isActive()); 0273 QSignalSpy windowAddedSpy(effects, &EffectsHandler::windowAdded); 0274 0275 // the test created the slide protocol, let's create a Registry and listen for it 0276 std::unique_ptr<KWayland::Client::Registry> registry(new KWayland::Client::Registry); 0277 registry->create(Test::waylandConnection()); 0278 0279 QSignalSpy interfacesAnnouncedSpy(registry.get(), &KWayland::Client::Registry::interfacesAnnounced); 0280 registry->setup(); 0281 QVERIFY(interfacesAnnouncedSpy.wait()); 0282 auto slideInterface = registry->interface(KWayland::Client::Registry::Interface::Slide); 0283 QVERIFY(slideInterface.name != 0); 0284 std::unique_ptr<KWayland::Client::SlideManager> slideManager(registry->createSlideManager(slideInterface.name, slideInterface.version)); 0285 QVERIFY(slideManager); 0286 0287 // create Wayland window 0288 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 0289 QVERIFY(surface); 0290 std::unique_ptr<KWayland::Client::Slide> slide(slideManager->createSlide(surface.get())); 0291 slide->setLocation(KWayland::Client::Slide::Location::Left); 0292 slide->commit(); 0293 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get())); 0294 QVERIFY(shellSurface); 0295 QCOMPARE(windowAddedSpy.count(), 0); 0296 auto window = Test::renderAndWaitForShown(surface.get(), QSize(10, 20), Qt::blue); 0297 QVERIFY(window); 0298 QVERIFY(window->isNormalWindow()); 0299 0300 // sliding popups should be active 0301 QCOMPARE(windowAddedSpy.count(), 1); 0302 QTRY_VERIFY(slidingPoupus->isActive()); 0303 QVERIFY(!otherEffect->isActive()); 0304 0305 // wait till effect ends 0306 QTRY_VERIFY(!slidingPoupus->isActive()); 0307 QTRY_VERIFY(!otherEffect->isActive()); 0308 0309 // and destroy the window again 0310 shellSurface.reset(); 0311 surface.reset(); 0312 0313 QSignalSpy windowClosedSpy(window, &X11Window::closed); 0314 0315 QSignalSpy windowDeletedSpy(effects, &EffectsHandler::windowDeleted); 0316 QVERIFY(windowClosedSpy.wait()); 0317 0318 // again we should have the sliding popups active 0319 QVERIFY(slidingPoupus->isActive()); 0320 QVERIFY(!otherEffect->isActive()); 0321 0322 QVERIFY(windowDeletedSpy.wait()); 0323 0324 QCOMPARE(windowDeletedSpy.count(), 1); 0325 QVERIFY(!slidingPoupus->isActive()); 0326 QVERIFY(!otherEffect->isActive()); 0327 } 0328 0329 WAYLANDTEST_MAIN(SlidingPopupsTest) 0330 #include "slidingpopups_test.moc"