File indexing completed on 2024-04-21 09:18:03

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