Falkon Develop
Cross-platform Qt-based web browser
plugins.cpp
Go to the documentation of this file.
1/* ============================================================
2* Falkon - Qt web browser
3* Copyright (C) 2010-2018 David Rosca <nowrep@gmail.com>
4*
5* This program is free software: you can redistribute it and/or modify
6* it under the terms of the GNU General Public License as published by
7* the Free Software Foundation, either version 3 of the License, or
8* (at your option) any later version.
9*
10* This program is distributed in the hope that it will be useful,
11* but WITHOUT ANY WARRANTY; without even the implied warranty of
12* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13* GNU General Public License for more details.
14*
15* You should have received a copy of the GNU General Public License
16* along with this program. If not, see <http://www.gnu.org/licenses/>.
17* ============================================================ */
18#include "pluginproxy.h"
19#include "mainapplication.h"
20#include "speeddial.h"
21#include "settings.h"
22#include "datapaths.h"
24#include "../config.h"
25#include "desktopfile.h"
26#include "qml/qmlplugins.h"
27#include "qml/qmlplugin.h"
28
29#include <iostream>
30
31#include <QPluginLoader>
32#include <QDir>
33#include <QQmlEngine>
34#include <QQmlComponent>
35#include <QFileInfo>
36#include <QSettings>
37
39{
40 return instance;
41}
42
44{
45 return !pluginPath.isEmpty() && QFileInfo(pluginPath).isWritable();
46}
47
48bool Plugins::Plugin::operator==(const Plugin &other) const
49{
50 return type == other.type && pluginId == other.pluginId;
51}
52
53Plugins::Plugins(QObject* parent)
54 : QObject(parent)
55 , m_pluginsLoaded(false)
56 , m_speedDial(new SpeedDial(this))
57{
59
61 loadPythonSupport();
62 }
63}
64
65QList<Plugins::Plugin> Plugins::availablePlugins()
66{
67 loadAvailablePlugins();
68 return m_availablePlugins;
69}
70
72{
73 if (plugin->isLoaded()) {
74 return true;
75 }
76
77 if (!initPlugin(PluginInterface::LateInitState, plugin)) {
78 return false;
79 }
80
81 m_availablePlugins.removeOne(*plugin);
82 m_availablePlugins.prepend(*plugin);
83
84 refreshLoadedPlugins();
85
86 return plugin->isLoaded();
87}
88
90{
91 if (!plugin->isLoaded()) {
92 return;
93 }
94
95 plugin->instance->unload();
96 Q_EMIT pluginUnloaded(plugin->instance);
97 plugin->instance = nullptr;
98
99 m_availablePlugins.removeOne(*plugin);
100 m_availablePlugins.append(*plugin);
101
102 refreshLoadedPlugins();
103}
104
106{
107 if (!plugin->isRemovable()) {
108 return;
109 }
110
111 if (plugin->isLoaded()) {
112 unloadPlugin(plugin);
113 }
114
115 bool result = false;
116
117 QFileInfo info(plugin->pluginPath);
118 if (info.isDir()) {
119 result = QDir(plugin->pluginPath).removeRecursively();
120 } else if (info.isFile()) {
121 result = QFile::remove(plugin->pluginPath);
122 }
123
124 if (!result) {
125 qWarning() << "Failed to remove" << plugin->pluginSpec.name;
126 return;
127 }
128
129 m_availablePlugins.removeOne(*plugin);
131}
132
133bool Plugins::addPlugin(const QString &id)
134{
135 Plugin plugin = loadPlugin(id);
136 if (plugin.type == Plugin::Invalid) {
137 return false;
138 }
139 if (plugin.pluginSpec.name.isEmpty()) {
140 qWarning() << "Invalid plugin spec of" << id << "plugin";
141 return false;
142 }
143 registerAvailablePlugin(plugin);
145 return true;
146}
147
149{
150 QStringList defaultAllowedPlugins = {
151 QSL("internal:adblock")
152 };
153
154 // Enable KDE Frameworks Integration when running inside KDE session
155 if (qgetenv("KDE_FULL_SESSION") == QByteArray("true")) {
156 defaultAllowedPlugins.append(QSL("lib:KDEFrameworksIntegration.so"));
157 }
158
159 Settings settings;
160 settings.beginGroup(QSL("Plugin-Settings"));
161 m_allowedPlugins = settings.value(QSL("AllowedPlugins"), defaultAllowedPlugins).toStringList();
162 settings.endGroup();
163}
164
166{
167 for (PluginInterface* iPlugin : std::as_const(m_loadedPlugins)) {
168 iPlugin->unload();
169 }
170}
171
172PluginSpec Plugins::createSpec(const QJsonObject &metaData)
173{
174 const QString tempIcon = DataPaths::path(DataPaths::Temp) + QL1S("/icon");
175 const QString tempMetadata = DataPaths::path(DataPaths::Temp) + QL1S("/metadata.desktop");
176 QFile::remove(tempIcon);
177 QFile::remove(tempMetadata);
178 QSettings settings(tempMetadata, QSettings::IniFormat);
179 settings.beginGroup(QSL("Desktop Entry"));
180 for (auto it = metaData.begin(); it != metaData.end(); ++it) {
181 const QString value = it.value().toString();
182 if (it.key() == QL1S("Icon") && value.startsWith(QL1S("base64:"))) {
183 QFile file(tempIcon);
184 if (file.open(QFile::WriteOnly)) {
185 file.write(QByteArray::fromBase64(value.mid(7).toUtf8()));
186 settings.setValue(it.key(), tempIcon);
187 }
188 } else {
189 settings.setValue(it.key(), it.value().toString());
190 }
191 }
192 settings.sync();
193 return createSpec(DesktopFile(tempMetadata));
194}
195
197{
198 PluginSpec spec;
199 spec.name = metaData.name();
200 spec.description = metaData.comment();
201 spec.version = metaData.value(QSL("X-Falkon-Version")).toString();
202 spec.author = QSL("%1 <%2>").arg(metaData.value(QSL("X-Falkon-Author")).toString(), metaData.value(QSL("X-Falkon-Email")).toString());
203 spec.hasSettings = metaData.value(QSL("X-Falkon-Settings")).toBool();
204
205 const QString iconName = metaData.icon();
206 if (!iconName.isEmpty()) {
207 if (QFileInfo::exists(iconName)) {
208 spec.icon = QIcon(iconName).pixmap(32);
209 } else {
210 const QString relativeFile = QFileInfo(metaData.fileName()).dir().absoluteFilePath(iconName);
211 if (QFileInfo::exists(relativeFile)) {
212 spec.icon = QIcon(relativeFile).pixmap(32);
213 } else {
214 spec.icon = QIcon::fromTheme(iconName).pixmap(32);
215 }
216 }
217 }
218
219 return spec;
220}
221
223{
224 QDir settingsDir(DataPaths::currentProfilePath() + QStringLiteral("/extensions/"));
225 if (!settingsDir.exists()) {
226 settingsDir.mkdir(settingsDir.absolutePath());
227 }
228
229 for (const QString &pluginId : std::as_const(m_allowedPlugins)) {
230 Plugin plugin = loadPlugin(pluginId);
231 if (plugin.type == Plugin::Invalid) {
232 continue;
233 }
234 if (plugin.pluginSpec.name.isEmpty()) {
235 qWarning() << "Invalid plugin spec of" << pluginId << "plugin";
236 continue;
237 }
238 if (!initPlugin(PluginInterface::StartupInitState, &plugin)) {
239 qWarning() << "Failed to init" << pluginId << "plugin";
240 continue;
241 }
242 registerAvailablePlugin(plugin);
243 }
244
245 refreshLoadedPlugins();
246
247 std::cout << "Falkon: " << m_loadedPlugins.count() << " extensions loaded" << std::endl;
248}
249
250void Plugins::loadAvailablePlugins()
251{
252 if (m_pluginsLoaded) {
253 return;
254 }
255
256 m_pluginsLoaded = true;
257
258 const QStringList dirs = DataPaths::allPaths(DataPaths::Plugins);
259
260 // InternalPlugin
261 registerAvailablePlugin(loadInternalPlugin(QSL("adblock")));
262
263 for (const QString &dir : dirs) {
264 const auto files = QDir(dir).entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
265 for (const QFileInfo &info : files) {
266 Plugin plugin;
267 const QString pluginPath = info.absoluteFilePath();
268 if (info.isFile() && QLibrary::isLibrary(pluginPath)) {
269 // SharedLibraryPlugin
270 if (info.baseName() != QL1S("PyFalkon")) {
271 plugin = loadSharedLibraryPlugin(pluginPath);
272 }
273 } else if (info.isDir()) {
274 const DesktopFile metaData(QDir(pluginPath).filePath(QSL("metadata.desktop")));
275 const QString type = metaData.value(QSL("X-Falkon-Type")).toString();
276 if (type == QL1S("Extension/Python")) {
277 // PythonPlugin
278 plugin = loadPythonPlugin(pluginPath);
279 } else if (type == QL1S("Extension/Qml")) {
280 // QmlPlugin
281 plugin = QmlPlugin::loadPlugin(pluginPath);
282 } else {
283 qWarning() << "Invalid type" << type << "of" << pluginPath << "plugin";
284 }
285 }
286 if (plugin.type == Plugin::Invalid) {
287 continue;
288 }
289 if (plugin.pluginSpec.name.isEmpty()) {
290 qWarning() << "Invalid plugin spec of" << pluginPath << "plugin";
291 continue;
292 }
293 registerAvailablePlugin(plugin);
294 }
295 }
296}
297
298void Plugins::registerAvailablePlugin(const Plugin &plugin)
299{
300 if (!m_availablePlugins.contains(plugin)) {
301 m_availablePlugins.append(plugin);
302 }
303}
304
305void Plugins::refreshLoadedPlugins()
306{
307 m_loadedPlugins.clear();
308
309 for (const Plugin &plugin : std::as_const(m_availablePlugins)) {
310 if (plugin.isLoaded()) {
311 m_loadedPlugins.append(plugin.instance);
312 }
313 }
314
316}
317
318void Plugins::loadPythonSupport()
319{
320 const QStringList dirs = DataPaths::allPaths(DataPaths::Plugins);
321 for (const QString &dir : dirs) {
322 const auto files = QDir(dir).entryInfoList({QSL("PyFalkon*")}, QDir::Files);
323 for (const QFileInfo &info : files) {
324 m_pythonPlugin = new QLibrary(info.absoluteFilePath(), this);
325 m_pythonPlugin->setLoadHints(QLibrary::ExportExternalSymbolsHint);
326 if (!m_pythonPlugin->load()) {
327 qWarning() << "Failed to load python support plugin" << m_pythonPlugin->errorString();
328 delete m_pythonPlugin;
329 m_pythonPlugin = nullptr;
330 } else {
331 std::cout << "Falkon: Python plugin support initialized" << std::endl;
332 return;
333 }
334 }
335 }
336}
337
338Plugins::Plugin Plugins::loadPlugin(const QString &id)
339{
340 QString name;
342
343 const int colon = id.indexOf(QL1C(':'));
344 if (colon > -1) {
345 const auto t = QStringView{id}.left(colon);
346 if (t == QL1S("internal")) {
348 } else if (t == QL1S("lib")) {
350 } else if (t == QL1S("python")) {
352 } else if (t == QL1S("qml")) {
353 type = Plugin::QmlPlugin;
354 }
355 name = id.mid(colon + 1);
356 } else {
357 name = id;
359 }
360
361 switch (type) {
363 return loadInternalPlugin(name);
364
366 return loadSharedLibraryPlugin(name);
367
369 return loadPythonPlugin(name);
370
372 return QmlPlugin::loadPlugin(name);
373
374 default:
375 return {};
376 }
377}
378
379Plugins::Plugin Plugins::loadInternalPlugin(const QString &name)
380{
381 if (name == QL1S("adblock")) {
382 Plugin plugin;
384 plugin.pluginId = QSL("internal:adblock");
385 plugin.internalInstance = new AdBlockPlugin();
386 plugin.pluginSpec = createSpec(DesktopFile(QSL(":adblock/metadata.desktop")));
387 return plugin;
388 } else {
389 return {};
390 }
391}
392
393Plugins::Plugin Plugins::loadSharedLibraryPlugin(const QString &name)
394{
395 QString fullPath;
396 if (QFileInfo(name).isAbsolute()) {
397 fullPath = name;
398 } else {
399 fullPath = DataPaths::locate(DataPaths::Plugins, name);
400 if (fullPath.isEmpty()) {
401 qWarning() << "Library plugin" << name << "not found";
402 return {};
403 }
404 }
405
406 Plugin plugin;
407 plugin.type = Plugin::SharedLibraryPlugin;
408 plugin.pluginId = QSL("lib:%1").arg(QFileInfo(fullPath).fileName());
409 plugin.pluginPath = fullPath;
410 plugin.pluginLoader = new QPluginLoader(fullPath);
411 plugin.pluginSpec = createSpec(plugin.pluginLoader->metaData().value(QSL("MetaData")).toObject());
412 return plugin;
413}
414
415Plugins::Plugin Plugins::loadPythonPlugin(const QString &name)
416{
417 Plugin out;
418
419 if (!m_pythonPlugin) {
420 qWarning() << "Python support plugin is not loaded";
421 return out;
422 }
423
424 auto f = (Plugin*(*)(const QString &)) m_pythonPlugin->resolve("pyfalkon_load_plugin");
425 if (!f) {
426 qWarning() << "Failed to resolve" << "pyfalkon_load_plugin";
427 return out;
428 }
429
430 Plugin *p = f(name);
431 if (p) {
432 out = *p;
433 delete p;
434 }
435
436 return out;
437}
438
439bool Plugins::initPlugin(PluginInterface::InitState state, Plugin *plugin)
440{
441 if (!plugin) {
442 return false;
443 }
444
445 switch (plugin->type) {
447 initInternalPlugin(plugin);
448 break;
449
451 initSharedLibraryPlugin(plugin);
452 break;
453
455 initPythonPlugin(plugin);
456 break;
457
459 QmlPlugin::initPlugin(plugin);
460 break;
461
462 default:
463 return false;
464 }
465
466 if (!plugin->instance) {
467 return false;
468 }
469
470 // DataPaths::currentProfilePath() + QL1S("/extensions") is duplicated in qmlsettings.cpp
471 // If you change this, please change it there too.
472 plugin->instance->init(state, DataPaths::currentProfilePath() + QL1S("/extensions"));
473
474 if (!plugin->instance->testPlugin()) {
475 Q_EMIT pluginUnloaded(plugin->instance);
476 plugin->instance = nullptr;
477 return false;
478 }
479
480 return true;
481}
482
483void Plugins::initInternalPlugin(Plugin *plugin)
484{
485 Q_ASSERT(plugin->type == Plugin::InternalPlugin);
486
487 plugin->instance = plugin->internalInstance;
488}
489
490void Plugins::initSharedLibraryPlugin(Plugin *plugin)
491{
492 Q_ASSERT(plugin->type == Plugin::SharedLibraryPlugin);
493
494 plugin->instance = qobject_cast<PluginInterface*>(plugin->pluginLoader->instance());
495
496 if (!plugin->instance) {
497 qWarning() << "Loading" << plugin->pluginPath << "failed:" << plugin->pluginLoader->errorString();
498 }
499}
500
501void Plugins::initPythonPlugin(Plugin *plugin)
502{
503 Q_ASSERT(plugin->type == Plugin::PythonPlugin);
504
505 if (!m_pythonPlugin) {
506 qWarning() << "Python support plugin is not loaded";
507 return;
508 }
509
510 auto f = (void(*)(Plugin *)) m_pythonPlugin->resolve("pyfalkon_init_plugin");
511 if (!f) {
512 qWarning() << "Failed to resolve" << "pyfalkon_init_plugin";
513 return;
514 }
515
516 f(plugin);
517}
static QString path(Path type)
Definition: datapaths.cpp:66
static QStringList allPaths(Path type)
Definition: datapaths.cpp:74
static QString currentProfilePath()
Definition: datapaths.cpp:95
static QString locate(Path type, const QString &file)
Definition: datapaths.cpp:82
QString comment() const
Definition: desktopfile.cpp:43
QVariant value(const QString &key, bool localized=false) const
Definition: desktopfile.cpp:58
QString icon() const
Definition: desktopfile.cpp:53
QString name() const
Definition: desktopfile.cpp:38
QString fileName() const
Definition: desktopfile.cpp:33
static bool isTestModeEnabled()
virtual void unload()=0
void removePlugin(Plugin *plugin)
Definition: plugins.cpp:105
Plugins(QObject *parent=nullptr)
Definition: plugins.cpp:53
void shutdown()
Definition: plugins.cpp:165
QList< Plugin > availablePlugins()
Definition: plugins.cpp:65
void pluginUnloaded(PluginInterface *plugin)
void unloadPlugin(Plugin *plugin)
Definition: plugins.cpp:89
bool addPlugin(const QString &id)
Definition: plugins.cpp:133
void loadPlugins()
Definition: plugins.cpp:222
bool loadPlugin(Plugin *plugin)
Definition: plugins.cpp:71
void availablePluginsChanged()
static PluginSpec createSpec(const QJsonObject &metaData)
Definition: plugins.cpp:172
void loadSettings()
Definition: plugins.cpp:148
QList< PluginInterface * > m_loadedPlugins
Definition: plugins.h:106
static void initPlugin(Plugins::Plugin *plugin)
Definition: qmlplugin.cpp:59
static Plugins::Plugin loadPlugin(const QString &name)
Definition: qmlplugin.cpp:30
void beginGroup(const QString &prefix)
Definition: settings.cpp:79
void endGroup()
Definition: settings.cpp:84
QVariant value(const QString &key, const QVariant &defaultValue=QVariant())
Definition: settings.cpp:74
int value(const QColor &c)
Definition: colors.cpp:238
t
Definition: i18n.py:27
State state
#define QL1S(x)
Definition: qzcommon.h:44
#define QL1C(x)
Definition: qzcommon.h:48
#define QSL(x)
Definition: qzcommon.h:40
bool hasSettings
Definition: plugins.h:40
QString version
Definition: plugins.h:38
QString name
Definition: plugins.h:35
QPixmap icon
Definition: plugins.h:39
QString description
Definition: plugins.h:36
QString author
Definition: plugins.h:37
bool isLoaded() const
Definition: plugins.cpp:38
QString pluginId
Definition: plugins.h:63
PluginSpec pluginSpec
Definition: plugins.h:65
QString pluginPath
Definition: plugins.h:64
@ SharedLibraryPlugin
Definition: plugins.h:58
PluginInterface * instance
Definition: plugins.h:66
bool isRemovable() const
Definition: plugins.cpp:43
bool operator==(const Plugin &other) const
Definition: plugins.cpp:48