File indexing completed on 2024-04-21 15:05:27

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 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0009 #include <private/qtx11extras_p.h>
0010 #else
0011 #include <QX11Info>
0012 #endif
0013 
0014 #include <kmanagerselection.h>
0015 #include <kwindoweffects.h>
0016 #include <kwindowsystem.h>
0017 #include <kx11extras.h>
0018 #include <netwm.h>
0019 #include <qtest_widgets.h>
0020 #include <xcb/xcb.h>
0021 
0022 #include "cptr_p.h"
0023 
0024 Q_DECLARE_METATYPE(KWindowEffects::SlideFromLocation)
0025 Q_DECLARE_METATYPE(KWindowEffects::Effect)
0026 
0027 class KWindowEffectsTest : public QObject
0028 {
0029     Q_OBJECT
0030 private Q_SLOTS:
0031     void initTestCase();
0032     void testSlideWindow_data();
0033     void testSlideWindow();
0034     void testSlideWindowRemove();
0035 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 82)
0036     void testPresentWindows_data();
0037     void testPresentWindows();
0038     void testPresentWindowsEmptyGroup();
0039     void testPresentWindowsGroup_data();
0040     void testPresentWindowsGroup();
0041     void testHighlightWindows_data();
0042     void testHighlightWindows();
0043     void testHighlightWindowsEmpty();
0044 #endif
0045     void testBlur_data();
0046     void testBlur();
0047     void testBlurDisable();
0048 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 67)
0049     void testMarkAsDashboard();
0050 #endif
0051     void testEffectAvailable_data();
0052     void testEffectAvailable();
0053 
0054 private:
0055     int32_t locationToValue(KWindowEffects::SlideFromLocation location) const;
0056     void performSlideWindowTest(xcb_window_t window, int offset, KWindowEffects::SlideFromLocation location) const;
0057     void performSlideWindowRemoveTest(xcb_window_t window);
0058     void performWindowsOnPropertyTest(xcb_atom_t atom, const QList<WId> &windows);
0059     void performAtomIsRemoveTest(xcb_window_t window, xcb_atom_t atom);
0060     void getHelperAtom(const QByteArray &name, xcb_atom_t *atom) const;
0061     xcb_atom_t m_slide;
0062 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 82)
0063     xcb_atom_t m_presentWindows;
0064     xcb_atom_t m_presentWindowsGroup;
0065     xcb_atom_t m_highlightWindows;
0066 #endif
0067     xcb_atom_t m_thumbnails;
0068     xcb_atom_t m_blur;
0069     std::unique_ptr<QWindow> m_window;
0070     std::unique_ptr<QWidget> m_widget;
0071 };
0072 
0073 void KWindowEffectsTest::initTestCase()
0074 {
0075     m_window.reset(new QWindow());
0076     QVERIFY(m_window->winId() != XCB_WINDOW_NONE);
0077     m_widget.reset(new QWidget());
0078     m_widget->show();
0079     QVERIFY(m_widget->effectiveWinId() != XCB_WINDOW_NONE);
0080 
0081     getHelperAtom(QByteArrayLiteral("_KDE_SLIDE"), &m_slide);
0082 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 82)
0083     getHelperAtom(QByteArrayLiteral("_KDE_PRESENT_WINDOWS_DESKTOP"), &m_presentWindows);
0084     getHelperAtom(QByteArrayLiteral("_KDE_PRESENT_WINDOWS_GROUP"), &m_presentWindowsGroup);
0085     getHelperAtom(QByteArrayLiteral("_KDE_WINDOW_HIGHLIGHT"), &m_highlightWindows);
0086 #endif
0087     getHelperAtom(QByteArrayLiteral("_KDE_WINDOW_PREVIEW"), &m_thumbnails);
0088     getHelperAtom(QByteArrayLiteral("_KDE_NET_WM_BLUR_BEHIND_REGION"), &m_blur);
0089 }
0090 
0091 void KWindowEffectsTest::getHelperAtom(const QByteArray &name, xcb_atom_t *atom) const
0092 {
0093     xcb_connection_t *c = QX11Info::connection();
0094     xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, false, name.length(), name.constData());
0095 
0096     UniqueCPointer<xcb_intern_atom_reply_t> reply(xcb_intern_atom_reply(c, atomCookie, nullptr));
0097     QVERIFY(reply);
0098     *atom = reply->atom;
0099 }
0100 
0101 void KWindowEffectsTest::testSlideWindow_data()
0102 {
0103     QTest::addColumn<int>("offset");
0104     QTest::addColumn<KWindowEffects::SlideFromLocation>("location");
0105 
0106     QTest::newRow("Left") << 10 << KWindowEffects::LeftEdge;
0107     QTest::newRow("Right") << 20 << KWindowEffects::RightEdge;
0108     QTest::newRow("Top") << 0 << KWindowEffects::TopEdge;
0109     QTest::newRow("Bottom") << -1 << KWindowEffects::BottomEdge;
0110 }
0111 
0112 void KWindowEffectsTest::testSlideWindow()
0113 {
0114     QFETCH(int, offset);
0115     QFETCH(KWindowEffects::SlideFromLocation, location);
0116 
0117     KWindowEffects::slideWindow(m_window.get(), location, offset);
0118     performSlideWindowTest(m_window->winId(), offset, location);
0119 }
0120 
0121 void KWindowEffectsTest::testSlideWindowRemove()
0122 {
0123     xcb_window_t window = m_window->winId();
0124     // first install the atom
0125     KWindowEffects::slideWindow(m_window.get(), KWindowEffects::TopEdge, 0);
0126     performSlideWindowTest(window, 0, KWindowEffects::TopEdge);
0127 
0128     // now delete it
0129     KWindowEffects::slideWindow(m_window.get(), KWindowEffects::NoEdge, 0);
0130     performSlideWindowRemoveTest(window);
0131 }
0132 
0133 void KWindowEffectsTest::performSlideWindowTest(xcb_window_t window, int offset, KWindowEffects::SlideFromLocation location) const
0134 {
0135     xcb_connection_t *c = QX11Info::connection();
0136     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(c, false, window, m_slide, m_slide, 0, 100);
0137     UniqueCPointer<xcb_get_property_reply_t> reply(xcb_get_property_reply(c, cookie, nullptr));
0138     QVERIFY(reply);
0139     QCOMPARE(reply->format, uint8_t(32));
0140     QCOMPARE(reply->value_len, uint32_t(2));
0141     QCOMPARE(reply->type, m_slide);
0142     int32_t *data = static_cast<int32_t *>(xcb_get_property_value(reply.get()));
0143     QCOMPARE(data[0], offset);
0144     QCOMPARE(data[1], locationToValue(location));
0145 }
0146 
0147 void KWindowEffectsTest::performSlideWindowRemoveTest(xcb_window_t window)
0148 {
0149     performAtomIsRemoveTest(window, m_slide);
0150 }
0151 
0152 void KWindowEffectsTest::performAtomIsRemoveTest(xcb_window_t window, xcb_atom_t atom)
0153 {
0154     xcb_connection_t *c = QX11Info::connection();
0155     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(c, false, window, atom, atom, 0, 100);
0156     UniqueCPointer<xcb_get_property_reply_t> reply(xcb_get_property_reply(c, cookie, nullptr));
0157     QVERIFY(reply);
0158     QCOMPARE(reply->type, xcb_atom_t(XCB_ATOM_NONE));
0159 }
0160 
0161 int32_t KWindowEffectsTest::locationToValue(KWindowEffects::SlideFromLocation location) const
0162 {
0163     switch (location) {
0164     case KWindowEffects::LeftEdge:
0165         return 0;
0166     case KWindowEffects::TopEdge:
0167         return 1;
0168     case KWindowEffects::RightEdge:
0169         return 2;
0170     case KWindowEffects::BottomEdge:
0171         return 3;
0172     default:
0173         return -1;
0174     }
0175 }
0176 
0177 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 82)
0178 void KWindowEffectsTest::testPresentWindows_data()
0179 {
0180     QTest::addColumn<int>("desktop");
0181 
0182     QTest::newRow("all desktops") << -1;
0183     QTest::newRow("1") << 1;
0184     QTest::newRow("2") << 2;
0185     QTest::newRow("3") << 3;
0186     QTest::newRow("4") << 4;
0187 }
0188 #endif
0189 
0190 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 82)
0191 void KWindowEffectsTest::testPresentWindows()
0192 {
0193     QFETCH(int, desktop);
0194 
0195     KWindowEffects::presentWindows(m_window->winId(), desktop);
0196 
0197     xcb_connection_t *c = QX11Info::connection();
0198     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(c, false, m_window->winId(), m_presentWindows, m_presentWindows, 0, 100);
0199     UniqueCPointer<xcb_get_property_reply_t> reply(xcb_get_property_reply(c, cookie, nullptr));
0200     QVERIFY(reply);
0201     QCOMPARE(reply->format, uint8_t(32));
0202     QCOMPARE(reply->value_len, uint32_t(1));
0203     QCOMPARE(reply->type, m_presentWindows);
0204     int32_t *data = static_cast<int32_t *>(xcb_get_property_value(reply.get()));
0205     QCOMPARE(data[0], desktop);
0206 }
0207 #endif
0208 
0209 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 82)
0210 void KWindowEffectsTest::testPresentWindowsEmptyGroup()
0211 {
0212     KWindowEffects::presentWindows(m_window->winId(), QList<WId>());
0213 
0214     xcb_connection_t *c = QX11Info::connection();
0215     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(c, false, m_window->winId(), m_presentWindowsGroup, m_presentWindowsGroup, 0, 100);
0216     UniqueCPointer<xcb_get_property_reply_t> reply(xcb_get_property_reply(c, cookie, nullptr));
0217     QVERIFY(reply);
0218     QCOMPARE(reply->type, xcb_atom_t(XCB_ATOM_NONE));
0219 }
0220 #endif
0221 
0222 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 82)
0223 void KWindowEffectsTest::testPresentWindowsGroup_data()
0224 {
0225     QTest::addColumn<QList<WId>>("windows");
0226 
0227     QTest::newRow("one") << (QList<WId>() << m_window->winId());
0228     QTest::newRow("two") << (QList<WId>() << m_window->winId() << m_widget->effectiveWinId());
0229 }
0230 #endif
0231 
0232 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 82)
0233 void KWindowEffectsTest::testPresentWindowsGroup()
0234 {
0235     QFETCH(QList<WId>, windows);
0236     KWindowEffects::presentWindows(m_window->winId(), windows);
0237     performWindowsOnPropertyTest(m_presentWindowsGroup, windows);
0238 }
0239 #endif
0240 
0241 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 82)
0242 void KWindowEffectsTest::testHighlightWindows_data()
0243 {
0244     QTest::addColumn<QList<WId>>("windows");
0245 
0246     QTest::newRow("one") << (QList<WId>() << m_window->winId());
0247     QTest::newRow("two") << (QList<WId>() << m_window->winId() << m_widget->effectiveWinId());
0248 }
0249 #endif
0250 
0251 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 82)
0252 void KWindowEffectsTest::testHighlightWindows()
0253 {
0254     QFETCH(QList<WId>, windows);
0255     KWindowEffects::highlightWindows(m_window->winId(), windows);
0256     performWindowsOnPropertyTest(m_highlightWindows, windows);
0257 }
0258 #endif
0259 
0260 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 82)
0261 void KWindowEffectsTest::testHighlightWindowsEmpty()
0262 {
0263     // ensure it's empty
0264     KWindowEffects::highlightWindows(m_window->winId(), QList<WId>());
0265     performAtomIsRemoveTest(m_window->winId(), m_highlightWindows);
0266 
0267     // install some windows on the atom
0268     QList<WId> windows;
0269     windows.append(m_window->winId());
0270     windows.append(m_widget->effectiveWinId());
0271     KWindowEffects::highlightWindows(m_window->winId(), windows);
0272     performWindowsOnPropertyTest(m_highlightWindows, windows);
0273 
0274     // and remove it again
0275     KWindowEffects::highlightWindows(m_window->winId(), QList<WId>());
0276     performAtomIsRemoveTest(m_window->winId(), m_highlightWindows);
0277 }
0278 #endif
0279 
0280 void KWindowEffectsTest::performWindowsOnPropertyTest(xcb_atom_t atom, const QList<WId> &windows)
0281 {
0282     xcb_connection_t *c = QX11Info::connection();
0283     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(c, false, m_window->winId(), atom, atom, 0, 100);
0284     UniqueCPointer<xcb_get_property_reply_t> reply(xcb_get_property_reply(c, cookie, nullptr));
0285     QVERIFY(reply);
0286     QCOMPARE(reply->type, atom);
0287     QCOMPARE(reply->format, uint8_t(32));
0288     QCOMPARE(reply->value_len, uint32_t(windows.size()));
0289     int32_t *data = static_cast<int32_t *>(xcb_get_property_value(reply.get()));
0290     for (int i = 0; i < windows.size(); ++i) {
0291         QCOMPARE(data[i], int32_t(windows.at(i)));
0292     }
0293 }
0294 
0295 void KWindowEffectsTest::testBlur_data()
0296 {
0297     QTest::addColumn<QRegion>("blur");
0298 
0299     QRegion region(0, 0, 10, 10);
0300     QTest::newRow("one rect") << region;
0301     region = region.united(QRect(20, 20, 5, 5));
0302     QTest::newRow("two rects") << region;
0303     region = region.united(QRect(100, 100, 20, 20));
0304     QTest::newRow("three rects") << region;
0305     QTest::newRow("empty") << QRegion();
0306 }
0307 
0308 void KWindowEffectsTest::testBlur()
0309 {
0310     QFETCH(QRegion, blur);
0311 
0312     KWindowEffects::enableBlurBehind(m_window.get(), true, blur);
0313     xcb_connection_t *c = QX11Info::connection();
0314     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(c, false, m_window->winId(), m_blur, XCB_ATOM_CARDINAL, 0, 100);
0315     UniqueCPointer<xcb_get_property_reply_t> reply(xcb_get_property_reply(c, cookie, nullptr));
0316     QVERIFY(reply);
0317     QCOMPARE(reply->type, xcb_atom_t(XCB_ATOM_CARDINAL));
0318     QCOMPARE(reply->format, uint8_t(32));
0319     QCOMPARE(reply->value_len, uint32_t(blur.rectCount() * 4));
0320     uint32_t *data = static_cast<uint32_t *>(xcb_get_property_value(reply.get()));
0321     int dataOffset = 0;
0322     for (const QRect &rect : blur) {
0323         QCOMPARE(data[dataOffset++], uint32_t(rect.x()));
0324         QCOMPARE(data[dataOffset++], uint32_t(rect.y()));
0325         QCOMPARE(data[dataOffset++], uint32_t(rect.width()));
0326         QCOMPARE(data[dataOffset++], uint32_t(rect.height()));
0327     }
0328 }
0329 
0330 void KWindowEffectsTest::testBlurDisable()
0331 {
0332     KWindowEffects::enableBlurBehind(m_window.get(), false);
0333     performAtomIsRemoveTest(m_window->winId(), m_blur);
0334 
0335     KWindowEffects::enableBlurBehind(m_window.get(), true);
0336     // verify that it got added
0337     xcb_connection_t *c = QX11Info::connection();
0338     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(c, false, m_window->winId(), m_blur, XCB_ATOM_CARDINAL, 0, 100);
0339     UniqueCPointer<xcb_get_property_reply_t> reply(xcb_get_property_reply(c, cookie, nullptr));
0340     QVERIFY(reply);
0341     QCOMPARE(reply->type, xcb_atom_t(XCB_ATOM_CARDINAL));
0342 
0343     // and disable
0344     KWindowEffects::enableBlurBehind(m_window.get(), false);
0345     performAtomIsRemoveTest(m_window->winId(), m_blur);
0346 }
0347 
0348 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 67)
0349 void KWindowEffectsTest::testMarkAsDashboard()
0350 {
0351     const QByteArray className = QByteArrayLiteral("dashboard");
0352     // should not yet be set
0353     xcb_connection_t *c = QX11Info::connection();
0354     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(c, false, m_window->winId(), XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 0, 100);
0355     UniqueCPointer<xcb_get_property_reply_t> reply(xcb_get_property_reply(c, cookie, nullptr));
0356     QVERIFY(reply);
0357     QCOMPARE(reply->type, xcb_atom_t(XCB_ATOM_STRING));
0358     QCOMPARE(reply->format, uint8_t(8));
0359     char *data = static_cast<char *>(xcb_get_property_value(reply.get()));
0360     QVERIFY(QByteArray(data) != className);
0361 
0362     // now mark as dashboard
0363     KWindowEffects::markAsDashboard(m_window->winId());
0364     cookie = xcb_get_property_unchecked(c, false, m_window->winId(), XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 0, 100);
0365     reply.reset(xcb_get_property_reply(c, cookie, nullptr));
0366     QVERIFY(reply);
0367     QCOMPARE(reply->type, xcb_atom_t(XCB_ATOM_STRING));
0368     QCOMPARE(reply->format, uint8_t(8));
0369     QCOMPARE(reply->value_len, uint32_t(19));
0370     data = static_cast<char *>(xcb_get_property_value(reply.get()));
0371     QCOMPARE(QByteArray(data), className);
0372     data = data + 10;
0373     QCOMPARE(QByteArray(data), className);
0374 }
0375 #endif
0376 
0377 void KWindowEffectsTest::testEffectAvailable_data()
0378 {
0379     QTest::addColumn<KWindowEffects::Effect>("effect");
0380     QTest::addColumn<QByteArray>("propertyName");
0381 
0382     QTest::newRow("slide") << KWindowEffects::Slide << QByteArrayLiteral("_KDE_SLIDE");
0383 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 82)
0384     QTest::newRow("PresentWindows") << KWindowEffects::PresentWindows << QByteArrayLiteral("_KDE_PRESENT_WINDOWS_DESKTOP");
0385     QTest::newRow("PresentWindowsGroup") << KWindowEffects::PresentWindowsGroup << QByteArrayLiteral("_KDE_PRESENT_WINDOWS_GROUP");
0386     QTest::newRow("HighlightWindows") << KWindowEffects::HighlightWindows << QByteArrayLiteral("_KDE_WINDOW_HIGHLIGHT");
0387 #endif
0388     QTest::newRow("BlurBehind") << KWindowEffects::BlurBehind << QByteArrayLiteral("_KDE_NET_WM_BLUR_BEHIND_REGION");
0389 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 67)
0390     QTest::newRow("Dashboard") << KWindowEffects::Dashboard << QByteArrayLiteral("_WM_EFFECT_KDE_DASHBOARD");
0391 #endif
0392     QTest::newRow("BackgroundContrast") << KWindowEffects::BackgroundContrast << QByteArrayLiteral("_KDE_NET_WM_BACKGROUND_CONTRAST_REGION");
0393 }
0394 
0395 void KWindowEffectsTest::testEffectAvailable()
0396 {
0397     NETRootInfo rootInfo(QX11Info::connection(), NET::Supported | NET::SupportingWMCheck);
0398     if (qstrcmp(rootInfo.wmName(), "KWin") == 0) {
0399         QSKIP("KWin running, we don't want to interact with the running system");
0400     }
0401     // this test verifies whether an effect is available
0402     QFETCH(KWindowEffects::Effect, effect);
0403     // without a compositing manager it's not available
0404     // try-verify as there still might be the selection claimed from previous data run
0405     QTRY_VERIFY(!KX11Extras::compositingActive());
0406     QVERIFY(!KWindowEffects::isEffectAvailable(effect));
0407 
0408     // fake the compositor
0409     QSignalSpy compositingChangedSpy(KX11Extras::self(), &KX11Extras::compositingChanged);
0410     QVERIFY(compositingChangedSpy.isValid());
0411     KSelectionOwner compositorSelection("_NET_WM_CM_S0");
0412     QSignalSpy claimedSpy(&compositorSelection, &KSelectionOwner::claimedOwnership);
0413     QVERIFY(claimedSpy.isValid());
0414     compositorSelection.claim(true);
0415     QVERIFY(claimedSpy.wait());
0416     QCOMPARE(compositingChangedSpy.count(), 1);
0417     QCOMPARE(compositingChangedSpy.first().first().toBool(), true);
0418     QVERIFY(KX11Extras::compositingActive());
0419 
0420     // but not yet available
0421     QVERIFY(!KWindowEffects::isEffectAvailable(effect));
0422 
0423     // set the atom
0424     QFETCH(QByteArray, propertyName);
0425     xcb_connection_t *c = QX11Info::connection();
0426     xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, false, propertyName.length(), propertyName.constData());
0427     UniqueCPointer<xcb_intern_atom_reply_t> atom(xcb_intern_atom_reply(c, atomCookie, nullptr));
0428     QVERIFY(atom);
0429     unsigned char dummy = 0;
0430     xcb_change_property(c, XCB_PROP_MODE_REPLACE, QX11Info::appRootWindow(), atom->atom, atom->atom, 8, 1, &dummy);
0431     xcb_flush(c);
0432 
0433     // now the effect should be available
0434     QVERIFY(KWindowEffects::isEffectAvailable(effect));
0435 
0436     // delete the property again
0437     xcb_delete_property(c, QX11Info::appRootWindow(), atom->atom);
0438     xcb_flush(c);
0439     // which means it's no longer available
0440     QVERIFY(!KWindowEffects::isEffectAvailable(effect));
0441 
0442     // remove compositing selection
0443     compositorSelection.release();
0444     QVERIFY(compositingChangedSpy.wait());
0445     QCOMPARE(compositingChangedSpy.count(), 2);
0446     QCOMPARE(compositingChangedSpy.last().first().toBool(), false);
0447     QVERIFY(!KX11Extras::compositingActive());
0448 }
0449 
0450 QTEST_MAIN(KWindowEffectsTest)
0451 
0452 #include "kwindoweffectstest.moc"