File indexing completed on 2024-04-21 13:03:31

0001 /*
0002 SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
0003 SPDX-FileCopyrightText: 2015 Bhushan Shah <bhush94@gmail.com>
0004 
0005 SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 // own
0008 #include "../x11locker.h"
0009 // Qt
0010 #include <QWindow>
0011 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0012 #include <private/qtx11extras_p.h>
0013 #else
0014 #include <QX11Info>
0015 #endif
0016 #include <QtTest>
0017 // xcb
0018 #include <xcb/xcb.h>
0019 
0020 template<typename T>
0021 using ScopedCPointer = QScopedPointer<T, QScopedPointerPodDeleter>;
0022 
0023 class LockWindowTest : public QObject
0024 {
0025     Q_OBJECT
0026 private Q_SLOTS:
0027     void initTestCase();
0028     void testBlankScreen();
0029     void testEmergencyShow();
0030 };
0031 
0032 xcb_screen_t *defaultScreen()
0033 {
0034     int screen = QX11Info::appScreen();
0035     for (xcb_screen_iterator_t it = xcb_setup_roots_iterator(xcb_get_setup(QX11Info::connection())); it.rem; --screen, xcb_screen_next(&it)) {
0036         if (screen == 0) {
0037             return it.data;
0038         }
0039     }
0040     return nullptr;
0041 }
0042 
0043 bool isColored(const QColor color, const int x, const int y, const int width, const int height)
0044 {
0045     xcb_connection_t *c = QX11Info::connection();
0046     const auto cookie = xcb_get_image(c, XCB_IMAGE_FORMAT_Z_PIXMAP, QX11Info::appRootWindow(), x, y, width, height, ~0);
0047     ScopedCPointer<xcb_get_image_reply_t> xImage(xcb_get_image_reply(c, cookie, nullptr));
0048     if (xImage.isNull()) {
0049         return false;
0050     }
0051 
0052     // this operates on the assumption that X server default depth matches Qt's image format
0053     QImage image(xcb_get_image_data(xImage.data()), width, height, xcb_get_image_data_length(xImage.data()) / height, QImage::Format_ARGB32_Premultiplied);
0054 
0055     for (int i = 0; i < image.width(); i++) {
0056         for (int j = 0; j < image.height(); j++) {
0057             if (QColor(image.pixel(i, j)) != color) {
0058                 return false;
0059             }
0060         }
0061     }
0062     return true;
0063 }
0064 
0065 bool isBlack()
0066 {
0067     xcb_screen_t *screen = defaultScreen();
0068     const int width = screen->width_in_pixels;
0069     const int height = screen->height_in_pixels;
0070 
0071     return isColored(Qt::black, 0, 0, width, height);
0072 }
0073 
0074 xcb_atom_t screenLockerAtom()
0075 {
0076     const QByteArray atomName = QByteArrayLiteral("_KDE_SCREEN_LOCKER");
0077     xcb_connection_t *c = QX11Info::connection();
0078     const auto cookie = xcb_intern_atom(c, false, atomName.length(), atomName.constData());
0079     ScopedCPointer<xcb_intern_atom_reply_t> atom(xcb_intern_atom_reply(c, cookie, nullptr));
0080     if (atom.isNull()) {
0081         return XCB_ATOM_NONE;
0082     }
0083     return atom->atom;
0084 }
0085 
0086 void LockWindowTest::initTestCase()
0087 {
0088     QCoreApplication::setAttribute(Qt::AA_ForceRasterWidgets);
0089 }
0090 
0091 void LockWindowTest::testBlankScreen()
0092 {
0093     // create and show a dummy window to ensure the background doesn't start as black
0094     QWidget dummy;
0095     dummy.setWindowFlags(Qt::X11BypassWindowManagerHint);
0096     QPalette p;
0097     p.setColor(QPalette::Window, Qt::red);
0098     dummy.setAutoFillBackground(true);
0099     dummy.setPalette(p);
0100     dummy.setGeometry(0, 0, 100, 100);
0101     dummy.show();
0102     xcb_flush(QX11Info::connection());
0103 
0104     // Lets wait till it gets shown
0105     QTest::qWait(1000);
0106 
0107     // Verify that red window is shown
0108     QVERIFY(isColored(Qt::red, 0, 0, 100, 100));
0109 
0110     ScreenLocker::X11Locker lockWindow;
0111     lockWindow.showLockWindow();
0112 
0113     // the screen used to be blanked once the first lock window gets mapped, so let's create one
0114     QWindow fakeWindow;
0115     fakeWindow.setFlags(Qt::X11BypassWindowManagerHint);
0116     // it's on purpose outside the visual area
0117     fakeWindow.setGeometry(-1, -1, 1, 1);
0118     fakeWindow.create();
0119     xcb_atom_t atom = screenLockerAtom();
0120     QVERIFY(atom != XCB_ATOM_NONE);
0121     xcb_connection_t *c = QX11Info::connection();
0122     xcb_change_property(c, XCB_PROP_MODE_REPLACE, fakeWindow.winId(), atom, atom, 32, 0, nullptr);
0123     xcb_flush(c);
0124     fakeWindow.show();
0125 
0126     // give it time to be shown
0127     QTest::qWait(1000);
0128 
0129     // now lets try to get a screen grab and verify it's not yet black
0130     QVERIFY(!isBlack());
0131 
0132     // nowadays we need to pass the window id to the lock window
0133     lockWindow.addAllowedWindow(fakeWindow.winId());
0134 
0135     // give it time to be shown
0136     QTest::qWait(1000);
0137 
0138     // now lets try to get a screen grab and verify it's black
0139     QVERIFY(isBlack());
0140     dummy.hide();
0141 
0142     // destroying the fakeWindow should not remove the blanked screen
0143     fakeWindow.destroy();
0144     QTest::qWait(1000);
0145     QVERIFY(isBlack());
0146 
0147     // let's create another window and try to raise it above the lockWindow
0148     // using a QWidget to get proper content which won't be black
0149     QWidget widgetWindow;
0150     widgetWindow.setGeometry(10, 10, 100, 100);
0151     QPalette p1;
0152     p1.setColor(QPalette::Window, Qt::blue);
0153     widgetWindow.setAutoFillBackground(true);
0154     widgetWindow.setPalette(p1);
0155     widgetWindow.show();
0156     const uint32_t values[] = {XCB_STACK_MODE_ABOVE};
0157     xcb_configure_window(c, widgetWindow.winId(), XCB_CONFIG_WINDOW_STACK_MODE, values);
0158     xcb_flush(c);
0159     QTest::qWait(1000);
0160     QVERIFY(isBlack());
0161 
0162     lockWindow.hideLockWindow();
0163 }
0164 
0165 void LockWindowTest::testEmergencyShow()
0166 {
0167     QWidget dummy;
0168     dummy.setWindowFlags(Qt::X11BypassWindowManagerHint);
0169     QPalette p;
0170     p.setColor(QPalette::Window, Qt::red);
0171     dummy.setAutoFillBackground(true);
0172     dummy.setPalette(p);
0173     dummy.setGeometry(0, 0, 100, 100);
0174     dummy.show();
0175     xcb_flush(QX11Info::connection());
0176 
0177     // Lets wait till it gets shown
0178     QTest::qWait(1000);
0179 
0180     // Verify that red window is shown
0181     QVERIFY(isColored(Qt::red, 0, 0, 100, 100));
0182 
0183     qputenv("KSLD_TESTMODE", QByteArrayLiteral("true"));
0184 
0185     ScreenLocker::X11Locker lockWindow;
0186     lockWindow.showLockWindow();
0187 
0188     QTest::qWait(1000);
0189 
0190     // the screen used to be blanked once the first lock window gets mapped, so let's create one
0191     QWindow fakeWindow;
0192     fakeWindow.setFlags(Qt::X11BypassWindowManagerHint);
0193     // it's on purpose outside the visual area
0194     fakeWindow.setGeometry(-1, -1, 1, 1);
0195     fakeWindow.create();
0196     xcb_atom_t atom = screenLockerAtom();
0197     QVERIFY(atom != XCB_ATOM_NONE);
0198     xcb_connection_t *c = QX11Info::connection();
0199     xcb_change_property(c, XCB_PROP_MODE_REPLACE, fakeWindow.winId(), atom, atom, 32, 0, nullptr);
0200     xcb_flush(c);
0201     fakeWindow.show();
0202 
0203     // nowadays we need to pass the window id to the lock window
0204     lockWindow.addAllowedWindow(fakeWindow.winId());
0205 
0206     QTest::qWait(1000);
0207 
0208     lockWindow.emergencyShow();
0209 
0210     QTest::qWait(1000);
0211     xcb_flush(c);
0212 
0213     xcb_screen_t *screen = defaultScreen();
0214     const int width = screen->width_in_pixels;
0215     const int height = screen->height_in_pixels;
0216 
0217     const auto cookie = xcb_get_image(c, XCB_IMAGE_FORMAT_Z_PIXMAP, QX11Info::appRootWindow(), 0, 0, width, height, ~0);
0218     ScopedCPointer<xcb_get_image_reply_t> xImage(xcb_get_image_reply(c, cookie, nullptr));
0219 
0220     QVERIFY(!xImage.isNull());
0221 
0222     // this operates on the assumption that X server default depth matches Qt's image format
0223     QImage image(xcb_get_image_data(xImage.data()), width, height, xcb_get_image_data_length(xImage.data()) / height, QImage::Format_ARGB32_Premultiplied);
0224 
0225     bool isColored = false;
0226     int blackPixelCount = 0;
0227     int whitePixelCount = 0;
0228 
0229     // This verifies that,
0230     // - there is at least one white and one black pixel
0231     // - there is no other colored pixel
0232     QColor color;
0233     for (int i = 0; i < image.width(); i++) {
0234         for (int j = 0; j < image.height(); j++) {
0235             color = QColor(image.pixel(i, j));
0236             if (color == Qt::black) {
0237                 blackPixelCount++;
0238             } else if (color == Qt::white) {
0239                 whitePixelCount++;
0240             } else {
0241                 isColored = true;
0242                 break;
0243             }
0244         }
0245     }
0246 
0247     QVERIFY(!isColored);
0248     QVERIFY(blackPixelCount > 0);
0249     QVERIFY(whitePixelCount > 0);
0250 
0251     lockWindow.hideLockWindow();
0252 }
0253 
0254 QTEST_MAIN(LockWindowTest)
0255 #include "x11lockertest.moc"