diff --git a/autotests/x11lockertest.cpp b/autotests/x11lockertest.cpp index a155821..148b9b6 100644 --- a/autotests/x11lockertest.cpp +++ b/autotests/x11lockertest.cpp @@ -1,271 +1,271 @@ /******************************************************************** KSld - the KDE Screenlocker Daemon This file is part of the KDE project. Copyright (C) 2014 Martin Gräßlin Copyright (C) 2015 Bhushan Shah This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ // own #include "../x11locker.h" // Qt #include #include #include // xcb #include template using ScopedCPointer = QScopedPointer; class LockWindowTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testBlankScreen(); void testEmergencyShow(); }; xcb_screen_t *defaultScreen() { int screen = QX11Info::appScreen(); for (xcb_screen_iterator_t it = xcb_setup_roots_iterator(xcb_get_setup(QX11Info::connection())); it.rem; --screen, xcb_screen_next(&it)) { if (screen == 0) { return it.data; } } return nullptr; } bool isColored(const QColor color, const int x, const int y, const int width, const int height) { xcb_connection_t *c = QX11Info::connection(); const auto cookie = xcb_get_image(c, XCB_IMAGE_FORMAT_Z_PIXMAP, QX11Info::appRootWindow(), x, y, width, height, ~0); ScopedCPointer xImage(xcb_get_image_reply(c, cookie, nullptr)); if (xImage.isNull()) { return false; } // this operates on the assumption that X server default depth matches Qt's image format QImage image(xcb_get_image_data(xImage.data()), width, height, xcb_get_image_data_length(xImage.data()) / height, QImage::Format_ARGB32_Premultiplied); for (int i = 0; i < image.width(); i++) { for (int j = 0; j < image.height(); j++) { if (QColor(image.pixel(i, j)) != color) { return false; } } } return true; } bool isBlack() { xcb_screen_t *screen = defaultScreen(); const int width = screen->width_in_pixels; const int height = screen->height_in_pixels; return isColored(Qt::black, 0, 0, width, height); } xcb_atom_t screenLockerAtom() { const QByteArray atomName = QByteArrayLiteral("_KDE_SCREEN_LOCKER"); xcb_connection_t *c = QX11Info::connection(); const auto cookie = xcb_intern_atom(c, false, atomName.length(), atomName.constData()); ScopedCPointer atom(xcb_intern_atom_reply(c, cookie, nullptr)); if (atom.isNull()) { return XCB_ATOM_NONE; } return atom->atom; } void LockWindowTest::initTestCase() { QCoreApplication::setAttribute(Qt::AA_ForceRasterWidgets); } void LockWindowTest::testBlankScreen() { // create and show a dummy window to ensure the background doesn't start as black QWidget dummy; dummy.setWindowFlags(Qt::X11BypassWindowManagerHint); QPalette p; - p.setColor(QPalette::Background, Qt::red); + p.setColor(QPalette::Window, Qt::red); dummy.setAutoFillBackground(true); dummy.setPalette(p); dummy.setGeometry(0, 0, 100, 100); dummy.show(); xcb_flush(QX11Info::connection()); // Lets wait till it gets shown QTest::qWait(1000); // Verify that red window is shown QVERIFY(isColored(Qt::red, 0, 0, 100, 100)); ScreenLocker::X11Locker lockWindow; lockWindow.showLockWindow(); // the screen used to be blanked once the first lock window gets mapped, so let's create one QWindow fakeWindow; fakeWindow.setFlags(Qt::X11BypassWindowManagerHint); // it's on purpose outside the visual area fakeWindow.setGeometry(-1, -1, 1, 1); fakeWindow.create(); xcb_atom_t atom = screenLockerAtom(); QVERIFY(atom != XCB_ATOM_NONE); xcb_connection_t *c = QX11Info::connection(); xcb_change_property(c, XCB_PROP_MODE_REPLACE, fakeWindow.winId(), atom, atom, 32, 0, nullptr); xcb_flush(c); fakeWindow.show(); // give it time to be shown QTest::qWait(1000); // now lets try to get a screen grab and verify it's not yet black QVERIFY(!isBlack()); // nowadays we need to pass the window id to the lock window lockWindow.addAllowedWindow(fakeWindow.winId()); // give it time to be shown QTest::qWait(1000); // now lets try to get a screen grab and verify it's black QVERIFY(isBlack()); dummy.hide(); // destorying the fakeWindow should not remove the blanked screen fakeWindow.destroy(); QTest::qWait(1000); QVERIFY(isBlack()); // let's create another window and try to raise it above the lockWindow // using a QWidget to get proper content which won't be black QWidget widgetWindow; widgetWindow.setGeometry(10, 10, 100, 100); QPalette p1; - p1.setColor(QPalette::Background, Qt::blue); + p1.setColor(QPalette::Window, Qt::blue); widgetWindow.setAutoFillBackground(true); widgetWindow.setPalette(p1); widgetWindow.show(); const uint32_t values[] = { XCB_STACK_MODE_ABOVE }; xcb_configure_window(c, widgetWindow.winId(), XCB_CONFIG_WINDOW_STACK_MODE, values); xcb_flush(c); QTest::qWait(1000); QVERIFY(isBlack()); lockWindow.hideLockWindow(); } void LockWindowTest::testEmergencyShow() { QWidget dummy; dummy.setWindowFlags(Qt::X11BypassWindowManagerHint); QPalette p; - p.setColor(QPalette::Background, Qt::red); + p.setColor(QPalette::Window, Qt::red); dummy.setAutoFillBackground(true); dummy.setPalette(p); dummy.setGeometry(0, 0, 100, 100); dummy.show(); xcb_flush(QX11Info::connection()); // Lets wait till it gets shown QTest::qWait(1000); // Verify that red window is shown QVERIFY(isColored(Qt::red, 0, 0, 100, 100)); qputenv("KSLD_TESTMODE", QByteArrayLiteral("true")); ScreenLocker::X11Locker lockWindow; lockWindow.showLockWindow(); QTest::qWait(1000); // the screen used to be blanked once the first lock window gets mapped, so let's create one QWindow fakeWindow; fakeWindow.setFlags(Qt::X11BypassWindowManagerHint); // it's on purpose outside the visual area fakeWindow.setGeometry(-1, -1, 1, 1); fakeWindow.create(); xcb_atom_t atom = screenLockerAtom(); QVERIFY(atom != XCB_ATOM_NONE); xcb_connection_t *c = QX11Info::connection(); xcb_change_property(c, XCB_PROP_MODE_REPLACE, fakeWindow.winId(), atom, atom, 32, 0, nullptr); xcb_flush(c); fakeWindow.show(); // nowadays we need to pass the window id to the lock window lockWindow.addAllowedWindow(fakeWindow.winId()); QTest::qWait(1000); lockWindow.emergencyShow(); QTest::qWait(1000); xcb_flush(c); xcb_screen_t *screen = defaultScreen(); const int width = screen->width_in_pixels; const int height = screen->height_in_pixels; const auto cookie = xcb_get_image(c, XCB_IMAGE_FORMAT_Z_PIXMAP, QX11Info::appRootWindow(), 0, 0, width, height, ~0); ScopedCPointer xImage(xcb_get_image_reply(c, cookie, nullptr)); QVERIFY(!xImage.isNull()); // this operates on the assumption that X server default depth matches Qt's image format QImage image(xcb_get_image_data(xImage.data()), width, height, xcb_get_image_data_length(xImage.data()) / height, QImage::Format_ARGB32_Premultiplied); bool isColored = false; int blackPixelCount = 0; int whitePixelCount = 0; // This verifies that, // - there is at least one white and one black pixel // - there is no other colored pixel QColor color; for (int i = 0; i < image.width(); i++) { for (int j = 0; j < image.height(); j++) { color = QColor(image.pixel(i, j)); if (color == Qt::black) { blackPixelCount++; } else if (color == Qt::white) { whitePixelCount++; } else { isColored = true; break; } } } QVERIFY(!isColored); QVERIFY(blackPixelCount > 0); QVERIFY(whitePixelCount > 0); lockWindow.hideLockWindow(); } QTEST_MAIN(LockWindowTest) #include "x11lockertest.moc" diff --git a/kcm/kcm.cpp b/kcm/kcm.cpp index 25eb72e..bc592f5 100644 --- a/kcm/kcm.cpp +++ b/kcm/kcm.cpp @@ -1,317 +1,317 @@ /******************************************************************** KSld - the KDE Screenlocker Daemon This file is part of the KDE project. Copyright (C) 2014 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kcm.h" #include "kscreensaversettings.h" #include "ui_kcm.h" #include "screenlocker_interface.h" #include "../greeter/wallpaper_integration.h" #include "../greeter/lnf_integration.h" #include #include #include #include #include #include #include #include #include #include #include #include #include static const QString s_lockActionName = QStringLiteral("Lock Session"); static const QString s_defaultWallpaperPackage = QStringLiteral("org.kde.image"); class ScreenLockerKcmForm : public QWidget, public Ui::ScreenLockerKcmForm { Q_OBJECT public: explicit ScreenLockerKcmForm(QWidget *parent); }; ScreenLockerKcmForm::ScreenLockerKcmForm(QWidget *parent) : QWidget(parent) { setupUi(this); - layout()->setMargin(0); + layout()->setContentsMargins(0, 0, 0, 0); kcfg_Timeout->setSuffix(ki18ncp("Spinbox suffix. Short for minutes"," min"," mins")); kcfg_LockGrace->setSuffix(ki18ncp("Spinbox suffix. Short for seconds"," sec"," secs")); } ScreenLockerKcm::ScreenLockerKcm(QWidget *parent, const QVariantList &args) : KCModule(parent, args) , m_actionCollection(new KActionCollection(this, QStringLiteral("ksmserver"))) , m_ui(new ScreenLockerKcmForm(this)) { QVBoxLayout* layout = new QVBoxLayout(this); layout->addWidget(m_ui); addConfig(KScreenSaverSettings::self(), m_ui); m_actionCollection->setConfigGlobal(true); QAction *a = m_actionCollection->addAction(s_lockActionName); a->setProperty("isConfigurationAction", true); m_ui->lockscreenShortcut->setCheckForConflictsAgainst(KKeySequenceWidget::None); a->setText(i18n("Lock Session")); KGlobalAccel::self()->setShortcut(a, QList{Qt::ALT+Qt::CTRL+Qt::Key_L, Qt::Key_ScreenSaver}); connect(m_ui->lockscreenShortcut, &KKeySequenceWidget::keySequenceChanged, this, &ScreenLockerKcm::shortcutChanged); loadWallpapers(); auto wallpaperChangedSignal = static_cast(&QComboBox::currentIndexChanged); connect(m_ui->wallpaperCombo, wallpaperChangedSignal, this, static_cast(&ScreenLockerKcm::changed)); connect(m_ui->wallpaperCombo, wallpaperChangedSignal, this, &ScreenLockerKcm::loadWallpaperConfig); m_ui->wallpaperCombo->installEventFilter(this); auto proxy = new ScreenLockerProxy(this); m_ui->wallpaperConfigWidget->setClearColor(m_ui->palette().color(QPalette::Active, QPalette::Window)); m_ui->wallpaperConfigWidget->rootContext()->setContextProperty(QStringLiteral("configDialog"), proxy); m_ui->lnfConfigWidget->setClearColor(m_ui->palette().color(QPalette::Active, QPalette::Window)); m_ui->lnfConfigWidget->rootContext()->setContextProperty(QStringLiteral("configDialog"), proxy); connect(this, &ScreenLockerKcm::wallpaperConfigurationChanged, proxy, &ScreenLockerProxy::wallpaperConfigurationChanged); connect(this, &ScreenLockerKcm::currentWallpaperChanged, proxy, &ScreenLockerProxy::currentWallpaperChanged); m_ui->wallpaperConfigWidget->setSource(QUrl(QStringLiteral("qrc:/kscreenlocker-kcm-resources/wallpaperconfig.qml"))); connect(m_ui->wallpaperConfigWidget->rootObject(), SIGNAL(configurationChanged()), this, SLOT(changed())); m_ui->lnfConfigWidget->setSource(QUrl(QStringLiteral("qrc:/kscreenlocker-kcm-resources/lnfconfig.qml"))); connect(m_ui->lnfConfigWidget->rootObject(), SIGNAL(configurationChanged()), this, SLOT(changed())); m_ui->installEventFilter(this); } void ScreenLockerKcm::shortcutChanged(const QKeySequence &key) { if (QAction *a = m_actionCollection->action(s_lockActionName)) { auto shortcuts = KGlobalAccel::self()->shortcut(a); m_ui->lockscreenShortcut->setProperty("changed", !shortcuts.contains(key)); } changed(); } void ScreenLockerKcm::load() { KCModule::load(); m_package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel")); KConfigGroup cg(KSharedConfig::openConfig(QStringLiteral("kdeglobals")), "KDE"); const QString packageName = cg.readEntry("LookAndFeelPackage", QString()); if (!packageName.isEmpty()) { m_package.setPath(packageName); } if (QAction *a = m_actionCollection->action(s_lockActionName)) { auto shortcuts = KGlobalAccel::self()->shortcut(a); if (!shortcuts.isEmpty()) { m_ui->lockscreenShortcut->setKeySequence(shortcuts.first()); } } m_lnfIntegration = new ScreenLocker::LnFIntegration(this); m_lnfIntegration->setPackage(m_package); m_lnfIntegration->setConfig(KScreenSaverSettings::self()->sharedConfig()); m_lnfIntegration->init(); selectWallpaper(KScreenSaverSettings::self()->wallpaperPlugin()); loadWallpaperConfig(); loadLnfConfig(); } void ScreenLockerKcm::test(const QString &plugin) { if (plugin.isEmpty() || plugin == QLatin1String("none")) { return; } QProcess proc; QStringList arguments; arguments << plugin << QStringLiteral("--testing"); if (proc.execute(QString::fromLatin1(KSCREENLOCKER_GREET_BIN), arguments)) { QMessageBox::critical(this, i18n("Error"), i18n("Failed to successfully test the screen locker.")); } } void ScreenLockerKcm::save() { if (!shouldSaveShortcut()) { QMetaObject::invokeMethod(this, "changed", Qt::QueuedConnection); return; } KCModule::save(); QMetaObject::invokeMethod(m_ui->wallpaperConfigWidget->rootObject(), "saveConfig"); QMetaObject::invokeMethod(m_ui->lnfConfigWidget->rootObject(), "saveConfig"); // set the wallpaper config KScreenSaverSettings::self()->setWallpaperPlugin(m_ui->wallpaperCombo->currentData().toString()); KScreenSaverSettings::self()->save(); if (m_ui->lockscreenShortcut->property("changed").toBool()) { if (QAction *a = m_actionCollection->action(s_lockActionName)) { KGlobalAccel::self()->setShortcut(a, QList{m_ui->lockscreenShortcut->keySequence()}, KGlobalAccel::NoAutoloading); m_actionCollection->writeSettings(); } m_ui->lockscreenShortcut->setProperty("changed", false); } // reconfigure through DBus OrgKdeScreensaverInterface interface(QStringLiteral("org.kde.screensaver"), QStringLiteral("/ScreenSaver"), QDBusConnection::sessionBus()); if (interface.isValid()) { interface.configure(); } } bool ScreenLockerKcm::shouldSaveShortcut() { if (m_ui->lockscreenShortcut->property("changed").toBool()) { const QKeySequence &sequence = m_ui->lockscreenShortcut->keySequence(); auto conflicting = KGlobalAccel::getGlobalShortcutsByKey(sequence); if (!conflicting.isEmpty()) { // Inform and ask the user about the conflict and reassigning // the keys sequence if (!KGlobalAccel::promptStealShortcutSystemwide(this, conflicting, sequence)) { return false; } KGlobalAccel::stealShortcutSystemwide(sequence); } } return true; } void ScreenLockerKcm::defaults() { KCModule::defaults(); m_ui->lockscreenShortcut->setKeySequence(Qt::ALT+Qt::CTRL+Qt::Key_L); selectWallpaper(s_defaultWallpaperPackage); } void ScreenLockerKcm::loadWallpapers() { const auto wallpaperPackages = KPackage::PackageLoader::self()->listPackages(QStringLiteral("Plasma/Wallpaper")); for (auto &package : wallpaperPackages) { m_ui->wallpaperCombo->addItem(package.name(), package.pluginId()); } } void ScreenLockerKcm::selectWallpaper(const QString &pluginId) { const auto index = m_ui->wallpaperCombo->findData(pluginId); if (index != -1) { m_ui->wallpaperCombo->setCurrentIndex(index); } else if (pluginId != s_defaultWallpaperPackage) { // fall back to default plugin selectWallpaper(s_defaultWallpaperPackage); } } void ScreenLockerKcm::loadWallpaperConfig() { if (m_wallpaperIntegration) { if (m_wallpaperIntegration->pluginName() == m_ui->wallpaperCombo->currentData().toString()) { // nothing changed return; } delete m_wallpaperIntegration; } emit currentWallpaperChanged(); m_wallpaperIntegration = new ScreenLocker::WallpaperIntegration(this); m_wallpaperIntegration->setConfig(KScreenSaverSettings::self()->sharedConfig()); m_wallpaperIntegration->setPluginName(m_ui->wallpaperCombo->currentData().toString()); m_wallpaperIntegration->init(); m_ui->wallpaperConfigWidget->rootContext()->setContextProperty(QStringLiteral("wallpaper"), m_wallpaperIntegration); emit wallpaperConfigurationChanged(); m_ui->wallpaperConfigWidget->rootObject()->setProperty("sourceFile", m_wallpaperIntegration->package().filePath(QByteArrayLiteral("ui"), QStringLiteral("config.qml"))); } void ScreenLockerKcm::loadLnfConfig() { auto sourceFile = m_package.fileUrl(QByteArrayLiteral("lockscreen"), QStringLiteral("config.qml")); if (sourceFile.isEmpty()) { m_ui->lnfConfigWidget->hide(); return; } m_ui->lnfConfigWidget->rootObject()->setProperty("sourceFile", sourceFile); } KDeclarative::ConfigPropertyMap * ScreenLockerKcm::wallpaperConfiguration() const { if (!m_wallpaperIntegration) { return nullptr; } return m_wallpaperIntegration->configuration(); } KDeclarative::ConfigPropertyMap * ScreenLockerKcm::lnfConfiguration() const { if (!m_lnfIntegration) { return nullptr; } return m_lnfIntegration->configuration(); } QString ScreenLockerKcm::currentWallpaper() const { return m_ui->wallpaperCombo->currentData().toString(); } bool ScreenLockerKcm::eventFilter(QObject *watched, QEvent *event) { if (watched == m_ui) { if (event->type() == QEvent::PaletteChange) { m_ui->wallpaperConfigWidget->setClearColor(m_ui->palette().color(QPalette::Active, QPalette::Window)); } return false; } if (watched != m_ui->wallpaperCombo) { return false; } if (event->type() == QEvent::Move) { if (auto object = m_ui->wallpaperConfigWidget->rootObject()) { // QtQuick Layouts have a hardcoded 5 px spacing by default object->setProperty("formAlignment", m_ui->wallpaperCombo->x() + 5); } if (auto object = m_ui->lnfConfigWidget->rootObject()) { // QtQuick Layouts have a hardcoded 5 px spacing by default object->setProperty("formAlignment", m_ui->wallpaperCombo->x() + 5); } } return false; } K_PLUGIN_FACTORY_WITH_JSON(ScreenLockerKcmFactory, "screenlocker.json", registerPlugin();) #include "kcm.moc"