File indexing completed on 2024-04-14 03:56:53

0001 /*
0002     SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-or-later
0005 */
0006 
0007 #include <QSignalSpy>
0008 #include <private/qtx11extras_p.h>
0009 
0010 #include <kselectionowner.h>
0011 #include <kwindoweffects.h>
0012 #include <kwindowsystem.h>
0013 #include <kx11extras.h>
0014 #include <netwm.h>
0015 #include <qtest_widgets.h>
0016 #include <xcb/xcb.h>
0017 
0018 #include "cptr_p.h"
0019 
0020 Q_DECLARE_METATYPE(KWindowEffects::SlideFromLocation)
0021 Q_DECLARE_METATYPE(KWindowEffects::Effect)
0022 
0023 class KWindowEffectsTest : public QObject
0024 {
0025     Q_OBJECT
0026 private Q_SLOTS:
0027     void initTestCase();
0028     void testSlideWindow_data();
0029     void testSlideWindow();
0030     void testSlideWindowRemove();
0031     void testBlur_data();
0032     void testBlur();
0033     void testBlurDisable();
0034     void testEffectAvailable_data();
0035     void testEffectAvailable();
0036 
0037 private:
0038     int32_t locationToValue(KWindowEffects::SlideFromLocation location) const;
0039     void performSlideWindowTest(xcb_window_t window, int offset, KWindowEffects::SlideFromLocation location) const;
0040     void performSlideWindowRemoveTest(xcb_window_t window);
0041     void performWindowsOnPropertyTest(xcb_atom_t atom, const QList<WId> &windows);
0042     void performAtomIsRemoveTest(xcb_window_t window, xcb_atom_t atom);
0043     void getHelperAtom(const QByteArray &name, xcb_atom_t *atom) const;
0044     xcb_atom_t m_slide;
0045     xcb_atom_t m_thumbnails;
0046     xcb_atom_t m_blur;
0047     std::unique_ptr<QWindow> m_window;
0048     std::unique_ptr<QWidget> m_widget;
0049 };
0050 
0051 void KWindowEffectsTest::initTestCase()
0052 {
0053     m_window.reset(new QWindow());
0054     QVERIFY(m_window->winId() != XCB_WINDOW_NONE);
0055     m_widget.reset(new QWidget());
0056     m_widget->show();
0057     QVERIFY(m_widget->effectiveWinId() != XCB_WINDOW_NONE);
0058 
0059     getHelperAtom(QByteArrayLiteral("_KDE_SLIDE"), &m_slide);
0060     getHelperAtom(QByteArrayLiteral("_KDE_WINDOW_PREVIEW"), &m_thumbnails);
0061     getHelperAtom(QByteArrayLiteral("_KDE_NET_WM_BLUR_BEHIND_REGION"), &m_blur);
0062 }
0063 
0064 void KWindowEffectsTest::getHelperAtom(const QByteArray &name, xcb_atom_t *atom) const
0065 {
0066     xcb_connection_t *c = QX11Info::connection();
0067     xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, false, name.length(), name.constData());
0068 
0069     UniqueCPointer<xcb_intern_atom_reply_t> reply(xcb_intern_atom_reply(c, atomCookie, nullptr));
0070     QVERIFY(reply);
0071     *atom = reply->atom;
0072 }
0073 
0074 void KWindowEffectsTest::testSlideWindow_data()
0075 {
0076     QTest::addColumn<int>("offset");
0077     QTest::addColumn<KWindowEffects::SlideFromLocation>("location");
0078 
0079     QTest::newRow("Left") << 10 << KWindowEffects::LeftEdge;
0080     QTest::newRow("Right") << 20 << KWindowEffects::RightEdge;
0081     QTest::newRow("Top") << 0 << KWindowEffects::TopEdge;
0082     QTest::newRow("Bottom") << -1 << KWindowEffects::BottomEdge;
0083 }
0084 
0085 void KWindowEffectsTest::testSlideWindow()
0086 {
0087     QFETCH(int, offset);
0088     QFETCH(KWindowEffects::SlideFromLocation, location);
0089 
0090     KWindowEffects::slideWindow(m_window.get(), location, offset);
0091     performSlideWindowTest(m_window->winId(), offset, location);
0092 }
0093 
0094 void KWindowEffectsTest::testSlideWindowRemove()
0095 {
0096     xcb_window_t window = m_window->winId();
0097     // first install the atom
0098     KWindowEffects::slideWindow(m_window.get(), KWindowEffects::TopEdge, 0);
0099     performSlideWindowTest(window, 0, KWindowEffects::TopEdge);
0100 
0101     // now delete it
0102     KWindowEffects::slideWindow(m_window.get(), KWindowEffects::NoEdge, 0);
0103     performSlideWindowRemoveTest(window);
0104 }
0105 
0106 void KWindowEffectsTest::performSlideWindowTest(xcb_window_t window, int offset, KWindowEffects::SlideFromLocation location) const
0107 {
0108     xcb_connection_t *c = QX11Info::connection();
0109     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(c, false, window, m_slide, m_slide, 0, 100);
0110     UniqueCPointer<xcb_get_property_reply_t> reply(xcb_get_property_reply(c, cookie, nullptr));
0111     QVERIFY(reply);
0112     QCOMPARE(reply->format, uint8_t(32));
0113     QCOMPARE(reply->value_len, uint32_t(2));
0114     QCOMPARE(reply->type, m_slide);
0115     int32_t *data = static_cast<int32_t *>(xcb_get_property_value(reply.get()));
0116     QCOMPARE(data[0], offset);
0117     QCOMPARE(data[1], locationToValue(location));
0118 }
0119 
0120 void KWindowEffectsTest::performSlideWindowRemoveTest(xcb_window_t window)
0121 {
0122     performAtomIsRemoveTest(window, m_slide);
0123 }
0124 
0125 void KWindowEffectsTest::performAtomIsRemoveTest(xcb_window_t window, xcb_atom_t atom)
0126 {
0127     xcb_connection_t *c = QX11Info::connection();
0128     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(c, false, window, atom, atom, 0, 100);
0129     UniqueCPointer<xcb_get_property_reply_t> reply(xcb_get_property_reply(c, cookie, nullptr));
0130     QVERIFY(reply);
0131     QCOMPARE(reply->type, xcb_atom_t(XCB_ATOM_NONE));
0132 }
0133 
0134 int32_t KWindowEffectsTest::locationToValue(KWindowEffects::SlideFromLocation location) const
0135 {
0136     switch (location) {
0137     case KWindowEffects::LeftEdge:
0138         return 0;
0139     case KWindowEffects::TopEdge:
0140         return 1;
0141     case KWindowEffects::RightEdge:
0142         return 2;
0143     case KWindowEffects::BottomEdge:
0144         return 3;
0145     default:
0146         return -1;
0147     }
0148 }
0149 
0150 void KWindowEffectsTest::performWindowsOnPropertyTest(xcb_atom_t atom, const QList<WId> &windows)
0151 {
0152     xcb_connection_t *c = QX11Info::connection();
0153     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(c, false, m_window->winId(), atom, atom, 0, 100);
0154     UniqueCPointer<xcb_get_property_reply_t> reply(xcb_get_property_reply(c, cookie, nullptr));
0155     QVERIFY(reply);
0156     QCOMPARE(reply->type, atom);
0157     QCOMPARE(reply->format, uint8_t(32));
0158     QCOMPARE(reply->value_len, uint32_t(windows.size()));
0159     int32_t *data = static_cast<int32_t *>(xcb_get_property_value(reply.get()));
0160     for (int i = 0; i < windows.size(); ++i) {
0161         QCOMPARE(data[i], int32_t(windows.at(i)));
0162     }
0163 }
0164 
0165 void KWindowEffectsTest::testBlur_data()
0166 {
0167     QTest::addColumn<QRegion>("blur");
0168 
0169     QRegion region(0, 0, 10, 10);
0170     QTest::newRow("one rect") << region;
0171     region = region.united(QRect(20, 20, 5, 5));
0172     QTest::newRow("two rects") << region;
0173     region = region.united(QRect(100, 100, 20, 20));
0174     QTest::newRow("three rects") << region;
0175     QTest::newRow("empty") << QRegion();
0176 }
0177 
0178 void KWindowEffectsTest::testBlur()
0179 {
0180     QFETCH(QRegion, blur);
0181 
0182     KWindowEffects::enableBlurBehind(m_window.get(), true, blur);
0183     xcb_connection_t *c = QX11Info::connection();
0184     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(c, false, m_window->winId(), m_blur, XCB_ATOM_CARDINAL, 0, 100);
0185     UniqueCPointer<xcb_get_property_reply_t> reply(xcb_get_property_reply(c, cookie, nullptr));
0186     QVERIFY(reply);
0187     QCOMPARE(reply->type, xcb_atom_t(XCB_ATOM_CARDINAL));
0188     QCOMPARE(reply->format, uint8_t(32));
0189     QCOMPARE(reply->value_len, uint32_t(blur.rectCount() * 4));
0190     uint32_t *data = static_cast<uint32_t *>(xcb_get_property_value(reply.get()));
0191     int dataOffset = 0;
0192     for (const QRect &rect : blur) {
0193         QCOMPARE(data[dataOffset++], uint32_t(rect.x()));
0194         QCOMPARE(data[dataOffset++], uint32_t(rect.y()));
0195         QCOMPARE(data[dataOffset++], uint32_t(rect.width()));
0196         QCOMPARE(data[dataOffset++], uint32_t(rect.height()));
0197     }
0198 }
0199 
0200 void KWindowEffectsTest::testBlurDisable()
0201 {
0202     KWindowEffects::enableBlurBehind(m_window.get(), false);
0203     performAtomIsRemoveTest(m_window->winId(), m_blur);
0204 
0205     KWindowEffects::enableBlurBehind(m_window.get(), true);
0206     // verify that it got added
0207     xcb_connection_t *c = QX11Info::connection();
0208     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(c, false, m_window->winId(), m_blur, XCB_ATOM_CARDINAL, 0, 100);
0209     UniqueCPointer<xcb_get_property_reply_t> reply(xcb_get_property_reply(c, cookie, nullptr));
0210     QVERIFY(reply);
0211     QCOMPARE(reply->type, xcb_atom_t(XCB_ATOM_CARDINAL));
0212 
0213     // and disable
0214     KWindowEffects::enableBlurBehind(m_window.get(), false);
0215     performAtomIsRemoveTest(m_window->winId(), m_blur);
0216 }
0217 
0218 void KWindowEffectsTest::testEffectAvailable_data()
0219 {
0220     QTest::addColumn<KWindowEffects::Effect>("effect");
0221     QTest::addColumn<QByteArray>("propertyName");
0222 
0223     QTest::newRow("slide") << KWindowEffects::Slide << QByteArrayLiteral("_KDE_SLIDE");
0224     QTest::newRow("BlurBehind") << KWindowEffects::BlurBehind << QByteArrayLiteral("_KDE_NET_WM_BLUR_BEHIND_REGION");
0225     QTest::newRow("BackgroundContrast") << KWindowEffects::BackgroundContrast << QByteArrayLiteral("_KDE_NET_WM_BACKGROUND_CONTRAST_REGION");
0226 }
0227 
0228 void KWindowEffectsTest::testEffectAvailable()
0229 {
0230     NETRootInfo rootInfo(QX11Info::connection(), NET::Supported | NET::SupportingWMCheck);
0231     if (qstrcmp(rootInfo.wmName(), "KWin") == 0) {
0232         QSKIP("KWin running, we don't want to interact with the running system");
0233     }
0234     // this test verifies whether an effect is available
0235     QFETCH(KWindowEffects::Effect, effect);
0236     // without a compositing manager it's not available
0237     // try-verify as there still might be the selection claimed from previous data run
0238     QTRY_VERIFY(!KX11Extras::compositingActive());
0239     QVERIFY(!KWindowEffects::isEffectAvailable(effect));
0240 
0241     // fake the compositor
0242     QSignalSpy compositingChangedSpy(KX11Extras::self(), &KX11Extras::compositingChanged);
0243     QVERIFY(compositingChangedSpy.isValid());
0244     KSelectionOwner compositorSelection("_NET_WM_CM_S0");
0245     QSignalSpy claimedSpy(&compositorSelection, &KSelectionOwner::claimedOwnership);
0246     QVERIFY(claimedSpy.isValid());
0247     compositorSelection.claim(true);
0248     QVERIFY(claimedSpy.wait());
0249     QCOMPARE(compositingChangedSpy.count(), 1);
0250     QCOMPARE(compositingChangedSpy.first().first().toBool(), true);
0251     QVERIFY(KX11Extras::compositingActive());
0252 
0253     // but not yet available
0254     QVERIFY(!KWindowEffects::isEffectAvailable(effect));
0255 
0256     // set the atom
0257     QFETCH(QByteArray, propertyName);
0258     xcb_connection_t *c = QX11Info::connection();
0259     xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, false, propertyName.length(), propertyName.constData());
0260     UniqueCPointer<xcb_intern_atom_reply_t> atom(xcb_intern_atom_reply(c, atomCookie, nullptr));
0261     QVERIFY(atom);
0262     unsigned char dummy = 0;
0263     xcb_change_property(c, XCB_PROP_MODE_REPLACE, QX11Info::appRootWindow(), atom->atom, atom->atom, 8, 1, &dummy);
0264     xcb_flush(c);
0265 
0266     // now the effect should be available
0267     QVERIFY(KWindowEffects::isEffectAvailable(effect));
0268 
0269     // delete the property again
0270     xcb_delete_property(c, QX11Info::appRootWindow(), atom->atom);
0271     xcb_flush(c);
0272     // which means it's no longer available
0273     QVERIFY(!KWindowEffects::isEffectAvailable(effect));
0274 
0275     // remove compositing selection
0276     compositorSelection.release();
0277     QVERIFY(compositingChangedSpy.wait());
0278     QCOMPARE(compositingChangedSpy.count(), 2);
0279     QCOMPARE(compositingChangedSpy.last().first().toBool(), false);
0280     QVERIFY(!KX11Extras::compositingActive());
0281 }
0282 
0283 QTEST_MAIN(KWindowEffectsTest)
0284 
0285 #include "kwindoweffectstest.moc"