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"