Commit 6cb02405 authored by Martin Flöser's avatar Martin Flöser

Load a Wallpaper plugin in kscreenlocker_greet

Summary:
With this change the greeter loads a configured Wallpaper Plugin. The
idea behind this is to bring back the possibility of "animation" into
the lockscreen and to replace the rather hacky solution for a custom
background image.

As this is security relevant code we need to consider the security
implications of such a change. The existing code already allows to load
different QML files through the lookandfeel infrastructure. Given that
local data can be loaded anyway and network data is blocked.

For a proper integration there are further changes needed:
* default lnf package needs to "remove" the rendered image to be able
  to see the wallpaper
* blurred lockscreen background by default
* config transition for custom wallpaper images

To test manually edit kscreenlockerrc and add the following:
[Greeter]
WallpaperPlugin=org.kde.hunyango

And modify the lookandfeel package to be an Item instead of an Image.

Reviewers: #plasma

Subscribers: plasma-devel

Tags: #plasma

Differential Revision: https://phabricator.kde.org/D1800
parent be060a72
......@@ -12,6 +12,7 @@ set(kscreenlocker_greet_SRCS
greeterapp.cpp
main.cpp
noaccessnetworkaccessmanagerfactory.cpp
wallpaper_integration.cpp
)
qt5_add_resources(kscreenlocker_greet_SRCS fallbacktheme.qrc)
......
......@@ -22,12 +22,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "kscreensaversettings.h"
#include "authenticator.h"
#include "noaccessnetworkaccessmanagerfactory.h"
#include "wallpaper_integration.h"
// KDE
#include <KAuthorized>
#include <KCrash>
#include <kdeclarative/kdeclarative.h>
#include <KDeclarative/KQuickAddons/QuickViewSharedEngine>
#include <KDeclarative/QmlObjectSharedEngine>
#include <KUser>
//Plasma
#include <KPackage/Package>
......@@ -51,6 +53,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <QQmlContext>
#include <QQmlEngine>
#include <QQmlProperty>
#include <QQmlExpression>
#include <QX11Info>
// Wayland
......@@ -97,6 +100,7 @@ UnlockApp::UnlockApp(int &argc, char **argv)
, m_authenticator(new Authenticator(this))
, m_graceTime(0)
, m_noLock(false)
, m_wallpaperIntegration(new WallpaperIntegration(this))
{
connect(m_authenticator, &Authenticator::succeeded, this, &QCoreApplication::quit);
initialize();
......@@ -148,6 +152,10 @@ void UnlockApp::initialize()
m_mainQmlPath = QUrl::fromLocalFile(package.filePath("lockscreenmainscript"));
m_wallpaperIntegration->setConfig(KScreenSaverSettings::self()->sharedConfig());
m_wallpaperIntegration->setPluginName(KScreenSaverSettings::self()->wallpaperPlugin());
m_wallpaperIntegration->init();
installEventFilter(this);
}
......@@ -172,6 +180,37 @@ void UnlockApp::initializeWayland()
m_plasmaShell = r->createPlasmaShell(i.name, i.version, this);
}
void UnlockApp::loadWallpaperPlugin(KQuickAddons::QuickViewSharedEngine *view)
{
auto package = m_wallpaperIntegration->package();
if (!package.isValid()) {
qWarning() << "Error loading the wallpaper, no valid package loaded";
return;
}
auto qmlObject = new KDeclarative::QmlObjectSharedEngine(view);
qmlObject->setInitializationDelayed(true);
qmlObject->setPackage(package);
qmlObject->rootContext()->setContextProperty(QStringLiteral("wallpaper"), m_wallpaperIntegration);
view->setProperty("wallpaperGraphicsObject", QVariant::fromValue(qmlObject->rootObject()));
connect(qmlObject, &KDeclarative::QmlObject::finished, this,
[this, qmlObject, view] {
auto item = qobject_cast<QQuickItem*>(qmlObject->rootObject());
if (!item) {
qWarning() << "Wallpaper needs to be a QtQuick Item";
return;
}
item->setParentItem(view->rootObject());
item->setZ(-1000);
//set anchors
QQmlExpression expr(qmlObject->engine()->rootContext(), item, QStringLiteral("parent"));
QQmlProperty prop(item, QStringLiteral("anchors.fill"));
prop.write(expr.evaluate());
}
);
}
void UnlockApp::desktopResized()
{
const int nScreens = screens().count();
......@@ -191,11 +230,6 @@ void UnlockApp::desktopResized()
KDeclarative::KDeclarative declarative;
declarative.setDeclarativeEngine(view->engine());
declarative.setupBindings();
// overwrite the factory set by kdeclarative
auto oldFactory = view->engine()->networkAccessManagerFactory();
view->engine()->setNetworkAccessManagerFactory(nullptr);
delete oldFactory;
view->engine()->setNetworkAccessManagerFactory(new NoAccessNetworkAccessManagerFactory);
if (!m_testing) {
if (QX11Info::isPlatformX11()) {
......@@ -219,7 +253,6 @@ void UnlockApp::desktopResized()
}
}
// engine stuff
QQmlContext* context = view->engine()->rootContext();
const KUser user;
......@@ -228,7 +261,6 @@ void UnlockApp::desktopResized()
context->setContextProperty(QStringLiteral("kscreenlocker_userName"), fullName.isEmpty() ? user.loginName() : fullName);
context->setContextProperty(QStringLiteral("kscreenlocker_userImage"), user.faceIconPath());
context->setContextProperty(QStringLiteral("authenticator"), m_authenticator);
context->setContextProperty(QStringLiteral("backgroundPath"), KScreenSaverSettings::themeBackground());
context->setContextProperty(QStringLiteral("org_kde_plasma_screenlocker_greeter_interfaceVersion"), 2);
context->setContextProperty(QStringLiteral("org_kde_plasma_screenlocker_greeter_view"), view);
......@@ -243,6 +275,13 @@ void UnlockApp::desktopResized()
}
view->setResizeMode(KQuickAddons::QuickViewSharedEngine::SizeRootObjectToView);
loadWallpaperPlugin(view);
// overwrite the factory set by kdeclarative
auto oldFactory = view->engine()->networkAccessManagerFactory();
view->engine()->setNetworkAccessManagerFactory(nullptr);
delete oldFactory;
view->engine()->setNetworkAccessManagerFactory(new NoAccessNetworkAccessManagerFactory);
QQmlProperty lockProperty(view->rootObject(), QStringLiteral("locked"));
lockProperty.write(m_immediateLock || (!m_noLock && !m_delayedLockTimer));
......@@ -274,6 +313,13 @@ void UnlockApp::desktopResized()
if (plasmaSurface) {
plasmaSurface->setPosition(view->geometry().topLeft());
}
if (auto object = view->property("wallpaperGraphicsObject").value<KDeclarative::QmlObjectSharedEngine*>()) {
//initialize with our size to avoid as much resize events as possible
object->completeInitialization({
{QStringLiteral("width"), view->width()},
{QStringLiteral("height"), view->height()}
});
}
connect(screen,
&QScreen::geometryChanged,
......
......@@ -44,6 +44,7 @@ struct org_kde_ksld;
namespace ScreenLocker
{
class Unlocker;
class WallpaperIntegration;
class UnlockApp : public QGuiApplication
{
......@@ -83,6 +84,7 @@ private:
void initialize();
void initializeWayland();
void shareEvent(QEvent *e, KQuickAddons::QuickViewSharedEngine *from);
void loadWallpaperPlugin(KQuickAddons::QuickViewSharedEngine *view);
QString m_packageName;
QUrl m_mainQmlPath;
......@@ -107,6 +109,7 @@ private:
org_kde_ksld *m_ksldInterface = nullptr;
KWayland::Client::PlasmaShell *m_plasmaShell = nullptr;
WallpaperIntegration *m_wallpaperIntegration;
};
} // namespace
......
/********************************************************************
KSld - the KDE Screenlocker Daemon
This file is part of the KDE project.
Copyright (C) 2016 Martin Gräßlin <mgraesslin@kde.org>
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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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 <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "wallpaper_integration.h"
#include <KConfig>
#include <KConfigGroup>
#include <KConfigLoader>
#include <KPackage/PackageLoader>
#include <KDeclarative/ConfigPropertyMap>
#include <QFile>
namespace ScreenLocker
{
WallpaperIntegration::WallpaperIntegration(QObject *parent)
: QObject(parent)
, m_package(KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Wallpaper")))
{
qRegisterMetaType<KDeclarative::ConfigPropertyMap*>();
}
WallpaperIntegration::~WallpaperIntegration() = default;
void WallpaperIntegration::init()
{
if (!m_package.isValid()) {
return;
}
if (auto config = configScheme()) {
m_configuration = new KDeclarative::ConfigPropertyMap(config, this);
}
}
void WallpaperIntegration::setPluginName(const QString &name)
{
if (m_pluginName == name) {
return;
}
m_pluginName = name;
m_package.setPath(name);
emit packageChanged();
}
KConfigLoader *WallpaperIntegration::configScheme()
{
if (!m_configLoader) {
const QString xmlPath = m_package.filePath(QByteArrayLiteral("config"), QStringLiteral("main.xml"));
const KConfigGroup cfg = m_config->group("Greeter").group("Wallpaper").group(m_pluginName);
if (xmlPath.isEmpty()) {
m_configLoader = new KConfigLoader(cfg, 0, this);
} else {
QFile file(xmlPath);
m_configLoader = new KConfigLoader(cfg, &file, this);
}
}
return m_configLoader;
}
}
/********************************************************************
KSld - the KDE Screenlocker Daemon
This file is part of the KDE project.
Copyright (C) 2016 Martin Gräßlin <mgraesslin@kde.org>
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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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 <http://www.gnu.org/licenses/>.
*********************************************************************/
#ifndef KSCREENLOCKER_WALLPAPER_INTEGRATION_H
#define KSCREENLOCKER_WALLPAPER_INTEGRATION_H
#include <KPackage/Package>
#include <KSharedConfig>
class KConfigLoader;
namespace KDeclarative
{
class ConfigPropertyMap;
class QmlObject;
}
namespace ScreenLocker
{
class WallpaperIntegration : public QObject
{
Q_OBJECT
Q_PROPERTY(QString pluginName READ pluginName NOTIFY packageChanged)
Q_PROPERTY(KDeclarative::ConfigPropertyMap *configuration READ configuration NOTIFY configurationChanged)
public:
WallpaperIntegration(QObject *parent);
virtual ~WallpaperIntegration();
void init();
void setConfig(const KSharedConfig::Ptr &config) {
m_config = config;
}
QString pluginName() const {
return m_pluginName;
}
void setPluginName(const QString &name);
KPackage::Package package() const {
return m_package;
}
KDeclarative::ConfigPropertyMap *configuration() const {
return m_configuration;
}
Q_SIGNALS:
void packageChanged();
void configurationChanged();
private:
KConfigLoader *configScheme();
QString m_pluginName;
KPackage::Package m_package;
KSharedConfig::Ptr m_config;
KConfigLoader *m_configLoader = nullptr;
KDeclarative::ConfigPropertyMap *m_configuration = nullptr;
};
}
#endif
......@@ -38,8 +38,8 @@
<label></label>
<whatsthis></whatsthis>
</entry>
<entry key="ThemeBackground" type="String">
<default></default>
<entry key="WallpaperPlugin" type="String">
<default>org.kde.image</default>
<label></label>
<whatsthis></whatsthis>
</entry>
......
......@@ -4,23 +4,27 @@ add_definitions(-DTRANSLATION_DOMAIN=\"screenlocker_kcm\")
set(screenlocker_kcm_SRCS
kcm.cpp
selectimagebutton.cpp
../greeter/wallpaper_integration.cpp
)
include_directories(${CMAKE_CURRENT_BINARY_DIR}/../)
ki18n_wrap_ui(screenlocker_kcm_SRCS kcm.ui)
kconfig_add_kcfg_files(screenlocker_kcm_SRCS ../kcfg/kscreensaversettings.kcfgc)
qt5_add_dbus_interface(screenlocker_kcm_SRCS ../dbus/org.kde.screensaver.xml screenlocker_interface)
qt5_add_resources( screenlocker_kcm_SRCS resources.qrc )
add_library(screenlocker_kcm MODULE ${screenlocker_kcm_SRCS})
target_link_libraries(screenlocker_kcm
Qt5::DBus
Qt5::QuickWidgets
KF5::ConfigWidgets
KF5::I18n
KF5::TextWidgets
KF5::GlobalAccel
KF5::Package
KF5::XmlGui
KF5::Declarative
)
kcoreaddons_desktop_to_json(screenlocker_kcm screenlocker.desktop SERVICE_TYPES kcmodule.desktop)
......
/********************************************************************
KSld - the KDE Screenlocker Daemon
This file is part of the KDE project.
Copyright (C) 2016 Martin Gräßlin <mgraesslin@kde.org>
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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
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 <http://www.gnu.org/licenses/>.
*********************************************************************/
import QtQuick 2.0
import QtQuick.Controls 1.0 as QtControls
import QtQuick.Layouts 1.1
ColumnLayout {
id: root
property int formAlignment: 0
property alias sourceFile: main.sourceFile
signal configurationChanged
//BEGIN functions
function saveConfig() {
if (main.currentItem.saveConfig) {
main.currentItem.saveConfig()
}
for (var key in configDialog.wallpaperConfiguration) {
if (main.currentItem["cfg_"+key] !== undefined) {
configDialog.wallpaperConfiguration[key] = main.currentItem["cfg_"+key]
}
}
}
//END functions
Item {
id: emptyConfig
}
QtControls.StackView {
id: main
Layout.fillHeight: true
anchors {
left: parent.left;
right: parent.right;
}
property string sourceFile
onSourceFileChanged: {
if (sourceFile) {
var props = {}
var wallpaperConfig = configDialog.wallpaperConfiguration
for (var key in wallpaperConfig) {
props["cfg_" + key] = wallpaperConfig[key]
}
var newItem = push({
item: sourceFile,
replace: true,
properties: props
})
for (var key in wallpaperConfig) {
var changedSignal = newItem["cfg_" + key + "Changed"]
if (changedSignal) {
changedSignal.connect(root.configurationChanged)
}
}
} else {
replace(emptyConfig)
}
}
}
}
......@@ -21,6 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "kscreensaversettings.h"
#include "ui_kcm.h"
#include "screenlocker_interface.h"
#include "../greeter/wallpaper_integration.h"
#include <config-kscreenlocker.h>
#include <KActionCollection>
#include <KGlobalAccel>
......@@ -34,7 +35,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <KPackage/Package>
#include <KPackage/PackageLoader>
#include <QQuickItem>
#include <QQmlContext>
static const QString s_lockActionName = QStringLiteral("Lock Session");
static const QString s_defaultWallpaperPackage = QStringLiteral("org.kde.image");
class ScreenLockerKcmForm : public QWidget, public Ui::ScreenLockerKcmForm
{
......@@ -59,6 +64,9 @@ ScreenLockerKcm::ScreenLockerKcm(QWidget *parent, const QVariantList &args)
, m_actionCollection(new KActionCollection(this, QStringLiteral("ksmserver")))
, m_ui(new ScreenLockerKcmForm(this))
{
QVBoxLayout* layout = new QVBoxLayout(this);
layout->addWidget(m_ui);
KConfigDialogManager::changedMap()->insert(QStringLiteral("SelectImageButton"), SIGNAL(imagePathChanged(QString)));
addConfig(KScreenSaverSettings::self(), m_ui);
......@@ -70,6 +78,20 @@ ScreenLockerKcm::ScreenLockerKcm(QWidget *parent, const QVariantList &args)
a->setText(i18n("Lock Session"));
KGlobalAccel::self()->setShortcut(a, QList<QKeySequence>{Qt::ALT+Qt::CTRL+Qt::Key_L, Qt::Key_ScreenSaver});
connect(m_ui->lockscreenShortcut, &KKeySequenceWidget::keySequenceChanged, this, &ScreenLockerKcm::shortcutChanged);
loadWallpapers();
auto wallpaperChangedSignal = static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged);
connect(m_ui->wallpaperCombo, wallpaperChangedSignal, this, static_cast<void (KCModule::*)()>(&KCModule::changed));
connect(m_ui->wallpaperCombo, wallpaperChangedSignal, this, &ScreenLockerKcm::loadWallpaperConfig);
m_ui->wallpaperCombo->installEventFilter(this);
m_ui->wallpaperConfigWidget->setClearColor(m_ui->palette().color(QPalette::Active, QPalette::Window));
m_ui->wallpaperConfigWidget->rootContext()->setContextProperty("configDialog", this);
m_ui->wallpaperConfigWidget->setSource(QUrl(QStringLiteral("qrc:/kscreenlocker-kcm-resources/config.qml")));
connect(m_ui->wallpaperConfigWidget->rootObject(), SIGNAL(configurationChanged()), this, SLOT(changed()));
m_ui->installEventFilter(this);
}
void ScreenLockerKcm::shortcutChanged(const QKeySequence &key)
......@@ -98,6 +120,9 @@ void ScreenLockerKcm::load()
m_ui->lockscreenShortcut->setKeySequence(shortcuts.first());
}
}
selectWallpaper(KScreenSaverSettings::self()->wallpaperPlugin());
loadWallpaperConfig();
}
void ScreenLockerKcm::test(const QString &plugin)
......@@ -121,6 +146,10 @@ void ScreenLockerKcm::save()
return;
}
KCModule::save();
QMetaObject::invokeMethod(m_ui->wallpaperConfigWidget->rootObject(), "saveConfig");
// set the wallpaper config
KScreenSaverSettings::self()->setWallpaperPlugin(m_ui->wallpaperCombo->currentData().toString());
KScreenSaverSettings::self()->save();
if (m_ui->lockscreenShortcut->property("changed").toBool()) {
......@@ -160,6 +189,79 @@ 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")));
}
KDeclarative::ConfigPropertyMap * ScreenLockerKcm::wallpaperConfiguration() const
{
if (!m_wallpaperIntegration) {
return nullptr;
}
return m_wallpaperIntegration->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()) {
object->setProperty("formAlignment", m_ui->wallpaperCombo->x());
}
}
return false;
}
K_PLUGIN_FACTORY_WITH_JSON(ScreenLockerKcmFactory,
......
......@@ -27,9 +27,21 @@ class QStandardItemModel;
class KActionCollection;
class ScreenLockerKcmForm;
namespace ScreenLocker
{
class WallpaperIntegration;
}
namespace KDeclarative
{
class ConfigPropertyMap;
}
class ScreenLockerKcm : public KCModule
{
Q_OBJECT
Q_PROPERTY(KDeclarative::ConfigPropertyMap *wallpaperConfiguration READ wallpaperConfiguration NOTIFY wallpaperConfigurationChanged)
Q_PROPERTY(QString currentWallpaper READ currentWallpaper NOTIFY currentWallpaperChanged)
public:
enum Roles {
......@@ -38,16 +50,29 @@ public:
};
explicit ScreenLockerKcm(QWidget *parent = nullptr, const QVariantList& args = QVariantList());
KDeclarative::ConfigPropertyMap *wallpaperConfiguration() const;
QString currentWallpaper() const;
bool eventFilter(QObject *watched, QEvent *event) override;
public Q_SLOTS:
void load() Q_DECL_OVERRIDE;
void save() Q_DECL_OVERRIDE;
void defaults() Q_DECL_OVERRIDE;
void test(const QString &plugin);
Q_SIGNALS:
void wallpaperConfigurationChanged();
void currentWallpaperChanged();
private:
void shortcutChanged(const QKeySequence &key);
bool shouldSaveShortcut();
void loadWallpapers();
void selectWallpaper(const QString &pluginId);