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"