1 /*
2     SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>
3 
4     SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "wallpaperinterface.h"
8 
9 #include "containmentinterface.h"
10 #include <kdeclarative/configpropertymap.h>
11 #include <kdeclarative/qmlobjectsharedengine.h>
12 
13 #include <KActionCollection>
14 #include <KConfigLoader>
15 #include <KDesktopFile>
16 
17 #include <QDebug>
18 #include <QFile>
19 #include <QQmlContext>
20 #include <QQmlExpression>
21 #include <QQmlProperty>
22 
23 #include <Plasma/PluginLoader>
24 #include <kpackage/packageloader.h>
25 
26 QHash<QObject *, WallpaperInterface *> WallpaperInterface::s_rootObjects = QHash<QObject *, WallpaperInterface *>();
27 
WallpaperInterface(ContainmentInterface * parent)28 WallpaperInterface::WallpaperInterface(ContainmentInterface *parent)
29     : QQuickItem(parent)
30     , m_containmentInterface(parent)
31     , m_qmlObject(nullptr)
32     , m_configuration(nullptr)
33     , m_configLoader(nullptr)
34 {
35     m_actions = new KActionCollection(this);
36 
37     // resize at the beginning to avoid as much resize events as possible
38     if (parent) {
39         setSize(QSizeF(parent->width(), parent->height()));
40     }
41 
42     if (!m_containmentInterface->containment()->wallpaper().isEmpty()) {
43         syncWallpaperPackage();
44     }
45     connect(m_containmentInterface->containment(), &Plasma::Containment::wallpaperChanged, this, &WallpaperInterface::syncWallpaperPackage);
46 }
47 
~WallpaperInterface()48 WallpaperInterface::~WallpaperInterface()
49 {
50     if (m_qmlObject) {
51         s_rootObjects.remove(m_qmlObject->engine());
52     }
53 }
54 
listWallpaperMetadataForMimetype(const QString & mimetype,const QString & formFactor)55 QList<KPluginMetaData> WallpaperInterface::listWallpaperMetadataForMimetype(const QString &mimetype, const QString &formFactor)
56 {
57     auto filter = [&mimetype, &formFactor](const KPluginMetaData &md) -> bool {
58         if (!formFactor.isEmpty() && !md.value(QStringLiteral("X-Plasma-FormFactors")).contains(formFactor)) {
59             return false;
60         }
61         return md.value(QStringLiteral("X-Plasma-DropMimeTypes"), QStringList()).contains(mimetype);
62     };
63     return KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/Wallpaper"), QString(), filter);
64 }
65 
kPackage() const66 KPackage::Package WallpaperInterface::kPackage() const
67 {
68     return m_pkg;
69 }
70 
pluginName() const71 QString WallpaperInterface::pluginName() const
72 {
73     return m_wallpaperPlugin;
74 }
75 
configuration() const76 KDeclarative::ConfigPropertyMap *WallpaperInterface::configuration() const
77 {
78     return m_configuration;
79 }
80 
configScheme()81 KConfigLoader *WallpaperInterface::configScheme()
82 {
83     if (!m_configLoader) {
84         // FIXME: do we need "mainconfigxml" in wallpaper packagestructures?
85         const QString xmlPath = m_pkg.filePath("config", QStringLiteral("main.xml"));
86 
87         KConfigGroup cfg = m_containmentInterface->containment()->config();
88         cfg = KConfigGroup(&cfg, "Wallpaper");
89         cfg = KConfigGroup(&cfg, m_wallpaperPlugin);
90 
91         if (xmlPath.isEmpty()) {
92             m_configLoader = new KConfigLoader(cfg, nullptr, this);
93         } else {
94             QFile file(xmlPath);
95             m_configLoader = new KConfigLoader(cfg, &file, this);
96         }
97     }
98 
99     return m_configLoader;
100 }
101 
syncWallpaperPackage()102 void WallpaperInterface::syncWallpaperPackage()
103 {
104     if (m_wallpaperPlugin == m_containmentInterface->containment()->wallpaper() && m_qmlObject->rootObject()) {
105         return;
106     }
107 
108     m_wallpaperPlugin = m_containmentInterface->containment()->wallpaper();
109 
110     if (!m_qmlObject) {
111         m_qmlObject = new KDeclarative::QmlObjectSharedEngine(this);
112         s_rootObjects[m_qmlObject->engine()] = this;
113         m_qmlObject->setInitializationDelayed(true);
114         connect(m_qmlObject, &KDeclarative::QmlObject::finished, this, &WallpaperInterface::loadFinished);
115     }
116 
117     m_actions->clear();
118     m_pkg = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Wallpaper"));
119     m_pkg.setPath(m_wallpaperPlugin);
120     if (!m_pkg.isValid()) {
121         qWarning() << "Error loading the wallpaper, no valid package loaded";
122         return;
123     }
124 
125     if (m_configLoader) {
126         m_configLoader->deleteLater();
127     }
128     if (m_configuration) {
129         m_configuration->deleteLater();
130     }
131     m_configLoader = nullptr;
132     m_configuration = nullptr;
133     if (configScheme()) {
134         m_configuration = new KDeclarative::ConfigPropertyMap(configScheme(), this);
135     }
136 
137     m_qmlObject->rootContext()->setContextProperty(QStringLiteral("wallpaper"), this);
138     m_qmlObject->setSource(m_pkg.fileUrl("mainscript"));
139 
140     const QString rootPath = m_pkg.metadata().value(QStringLiteral("X-Plasma-RootPath"));
141     if (!rootPath.isEmpty()) {
142         m_qmlObject->setTranslationDomain(QLatin1String("plasma_wallpaper_") + rootPath);
143     } else {
144         m_qmlObject->setTranslationDomain(QLatin1String("plasma_wallpaper_") + m_pkg.metadata().pluginId());
145     }
146 
147     // initialize with our size to avoid as much resize events as possible
148     QVariantHash props;
149     props[QStringLiteral("width")] = width();
150     props[QStringLiteral("height")] = height();
151     m_qmlObject->completeInitialization(props);
152 }
153 
loadFinished()154 void WallpaperInterface::loadFinished()
155 {
156     if (m_qmlObject->mainComponent() //
157         && m_qmlObject->rootObject() //
158         && !m_qmlObject->mainComponent()->isError()) {
159         m_qmlObject->rootObject()->setProperty("z", -1000);
160         m_qmlObject->rootObject()->setProperty("parent", QVariant::fromValue(this));
161 
162         // set anchors
163         QQmlExpression expr(m_qmlObject->engine()->rootContext(), m_qmlObject->rootObject(), QStringLiteral("parent"));
164         QQmlProperty prop(m_qmlObject->rootObject(), QStringLiteral("anchors.fill"));
165         prop.write(expr.evaluate());
166 
167     } else if (m_qmlObject->mainComponent()) {
168         qWarning() << "Error loading the wallpaper" << m_qmlObject->mainComponent()->errors();
169         s_rootObjects.remove(m_qmlObject->engine());
170         m_qmlObject->deleteLater();
171         m_qmlObject = nullptr;
172 
173     } else {
174         qWarning() << "Error loading the wallpaper, package not found";
175     }
176 
177     Q_EMIT packageChanged();
178     Q_EMIT configurationChanged();
179 }
180 
contextualActions() const181 QList<QAction *> WallpaperInterface::contextualActions() const
182 {
183     return m_actions->actions();
184 }
185 
supportsMimetype(const QString & mimetype) const186 bool WallpaperInterface::supportsMimetype(const QString &mimetype) const
187 {
188     return m_pkg.metadata().value(QStringLiteral("X-Plasma-DropMimeTypes"), QStringList()).contains(mimetype);
189 }
190 
setUrl(const QUrl & url)191 void WallpaperInterface::setUrl(const QUrl &url)
192 {
193     if (m_qmlObject->rootObject()) {
194         QMetaObject::invokeMethod(m_qmlObject->rootObject(), "setUrl", Qt::DirectConnection, Q_ARG(QVariant, QVariant::fromValue(url)));
195     }
196 }
197 
setAction(const QString & name,const QString & text,const QString & icon,const QString & shortcut)198 void WallpaperInterface::setAction(const QString &name, const QString &text, const QString &icon, const QString &shortcut)
199 {
200     QAction *action = m_actions->action(name);
201 
202     if (action) {
203         action->setText(text);
204     } else {
205         Q_ASSERT(!m_actions->action(name));
206         action = new QAction(text, this);
207         m_actions->addAction(name, action);
208 
209         connect(action, &QAction::triggered, this, [this, name] {
210             executeAction(name);
211         });
212     }
213 
214     if (!icon.isEmpty()) {
215         action->setIcon(QIcon::fromTheme(icon));
216     }
217 
218     if (!shortcut.isEmpty()) {
219         action->setShortcut(shortcut);
220     }
221 
222     action->setObjectName(name);
223     setProperty("contextualActions", QVariant::fromValue(contextualActions()));
224 }
225 
removeAction(const QString & name)226 void WallpaperInterface::removeAction(const QString &name)
227 {
228     QAction *action = m_actions->action(name);
229 
230     if (action) {
231         m_actions->removeAction(action);
232     }
233     setProperty("contextualActions", QVariant::fromValue(contextualActions()));
234 }
235 
action(QString name) const236 QAction *WallpaperInterface::action(QString name) const
237 {
238     return m_actions->action(name);
239 }
240 
executeAction(const QString & name)241 void WallpaperInterface::executeAction(const QString &name)
242 {
243     if (m_qmlObject->rootObject()) {
244         const QByteArray actionName("action_" + name.toUtf8());
245         QMetaObject::invokeMethod(m_qmlObject->rootObject(), actionName.constData(), Qt::DirectConnection);
246     }
247 }
248 
qmlAttachedProperties(QObject * object)249 WallpaperInterface *WallpaperInterface::qmlAttachedProperties(QObject *object)
250 {
251     // at the moment of the attached object creation, the root item is the only one that hasn't a parent
252     // only way to avoid creation of this attached for everybody but the root item
253     return object->parent() ? nullptr : s_rootObjects.value(QtQml::qmlEngine(object));
254 }
255 
isLoading() const256 bool WallpaperInterface::isLoading() const
257 {
258     return m_loading;
259 }
260 
261 #include "moc_wallpaperinterface.cpp"
262