You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
399 lines
11 KiB
399 lines
11 KiB
/* ============================================================ |
|
* Falkon - Qt web browser |
|
* Copyright (C) 2010-2018 David Rosca <nowrep@gmail.com> |
|
* |
|
* 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 3 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 <http://www.gnu.org/licenses/>. |
|
* ============================================================ */ |
|
#include "pluginproxy.h" |
|
#include "plugininterface.h" |
|
#include "mainapplication.h" |
|
#include "speeddial.h" |
|
#include "settings.h" |
|
#include "datapaths.h" |
|
#include "adblock/adblockplugin.h" |
|
#include "../config.h" |
|
#include "desktopfile.h" |
|
|
|
#include <iostream> |
|
#include <QPluginLoader> |
|
#include <QDir> |
|
|
|
Plugins::Plugins(QObject* parent) |
|
: QObject(parent) |
|
, m_pluginsLoaded(false) |
|
, m_speedDial(new SpeedDial(this)) |
|
{ |
|
loadSettings(); |
|
loadPythonSupport(); |
|
} |
|
|
|
QList<Plugins::Plugin> Plugins::getAvailablePlugins() |
|
{ |
|
loadAvailablePlugins(); |
|
|
|
return m_availablePlugins; |
|
} |
|
|
|
bool Plugins::loadPlugin(Plugins::Plugin* plugin) |
|
{ |
|
if (plugin->isLoaded()) { |
|
return true; |
|
} |
|
|
|
if (!initPlugin(PluginInterface::LateInitState, plugin)) { |
|
return false; |
|
} |
|
|
|
m_availablePlugins.removeOne(*plugin); |
|
m_availablePlugins.prepend(*plugin); |
|
|
|
refreshLoadedPlugins(); |
|
|
|
return plugin->isLoaded(); |
|
} |
|
|
|
void Plugins::unloadPlugin(Plugins::Plugin* plugin) |
|
{ |
|
if (!plugin->isLoaded()) { |
|
return; |
|
} |
|
|
|
plugin->instance->unload(); |
|
emit pluginUnloaded(plugin->instance); |
|
plugin->instance = nullptr; |
|
|
|
m_availablePlugins.removeOne(*plugin); |
|
m_availablePlugins.append(*plugin); |
|
|
|
refreshLoadedPlugins(); |
|
} |
|
|
|
void Plugins::loadSettings() |
|
{ |
|
Settings settings; |
|
settings.beginGroup("Plugin-Settings"); |
|
m_allowedPlugins = settings.value("AllowedPlugins", QStringList(QSL("internal:adblock"))).toStringList(); |
|
settings.endGroup(); |
|
} |
|
|
|
void Plugins::shutdown() |
|
{ |
|
foreach (PluginInterface* iPlugin, m_loadedPlugins) { |
|
iPlugin->unload(); |
|
} |
|
} |
|
|
|
PluginSpec Plugins::createSpec(const DesktopFile &metaData) |
|
{ |
|
PluginSpec spec; |
|
spec.name = metaData.name(); |
|
spec.description = metaData.comment(); |
|
spec.version = metaData.value(QSL("X-Falkon-Version")).toString(); |
|
spec.author = QSL("%1 <%2>").arg(metaData.value(QSL("X-Falkon-Author")).toString(), metaData.value(QSL("X-Falkon-Email")).toString()); |
|
spec.hasSettings = metaData.value(QSL("X-Falkon-Settings")).toBool(); |
|
|
|
const QString iconName = metaData.icon(); |
|
if (!iconName.isEmpty()) { |
|
if (QFileInfo::exists(iconName)) { |
|
spec.icon = QIcon(iconName).pixmap(32); |
|
} else { |
|
const QString relativeFile = QFileInfo(metaData.fileName()).dir().absoluteFilePath(iconName); |
|
if (QFileInfo::exists(relativeFile)) { |
|
spec.icon = QIcon(relativeFile).pixmap(32); |
|
} else { |
|
spec.icon = QIcon::fromTheme(iconName).pixmap(32); |
|
} |
|
} |
|
} |
|
|
|
return spec; |
|
} |
|
|
|
void Plugins::loadPlugins() |
|
{ |
|
QDir settingsDir(DataPaths::currentProfilePath() + "/extensions/"); |
|
if (!settingsDir.exists()) { |
|
settingsDir.mkdir(settingsDir.absolutePath()); |
|
} |
|
|
|
foreach (const QString &pluginId, m_allowedPlugins) { |
|
Plugin plugin = loadPlugin(pluginId); |
|
if (plugin.type == Plugin::Invalid) { |
|
continue; |
|
} |
|
if (plugin.pluginSpec.name.isEmpty()) { |
|
qWarning() << "Invalid plugin spec of" << pluginId << "plugin"; |
|
continue; |
|
} |
|
if (!initPlugin(PluginInterface::StartupInitState, &plugin)) { |
|
qWarning() << "Failed to init" << pluginId << "plugin"; |
|
continue; |
|
} |
|
registerAvailablePlugin(plugin); |
|
} |
|
|
|
refreshLoadedPlugins(); |
|
|
|
std::cout << "Falkon: " << m_loadedPlugins.count() << " extensions loaded" << std::endl; |
|
} |
|
|
|
void Plugins::loadAvailablePlugins() |
|
{ |
|
if (m_pluginsLoaded) { |
|
return; |
|
} |
|
|
|
m_pluginsLoaded = true; |
|
|
|
const QStringList dirs = DataPaths::allPaths(DataPaths::Plugins); |
|
|
|
// InternalPlugin |
|
registerAvailablePlugin(loadInternalPlugin(QSL("adblock"))); |
|
|
|
// SharedLibraryPlugin |
|
for (const QString &dir : dirs) { |
|
const auto files = QDir(dir).entryInfoList(QDir::Files); |
|
for (const QFileInfo &info : files) { |
|
if (info.baseName() == QL1S("PyFalkon")) { |
|
continue; |
|
} |
|
Plugin plugin = loadSharedLibraryPlugin(info.absoluteFilePath()); |
|
if (plugin.type == Plugin::Invalid) { |
|
continue; |
|
} |
|
if (plugin.pluginSpec.name.isEmpty()) { |
|
qWarning() << "Invalid plugin spec of" << info.absoluteFilePath() << "plugin"; |
|
continue; |
|
} |
|
registerAvailablePlugin(plugin); |
|
} |
|
} |
|
|
|
// PythonPlugin |
|
if (m_pythonPlugin) { |
|
auto f = (QVector<Plugin>(*)()) m_pythonPlugin->resolve("pyfalkon_load_available_plugins"); |
|
if (!f) { |
|
qWarning() << "Failed to resolve" << "pyfalkon_load_available_plugins"; |
|
} else { |
|
const auto plugins = f(); |
|
for (const auto &plugin : plugins) { |
|
registerAvailablePlugin(plugin); |
|
} |
|
} |
|
} |
|
} |
|
|
|
void Plugins::registerAvailablePlugin(const Plugin &plugin) |
|
{ |
|
if (!m_availablePlugins.contains(plugin)) { |
|
m_availablePlugins.append(plugin); |
|
} |
|
} |
|
|
|
void Plugins::refreshLoadedPlugins() |
|
{ |
|
m_loadedPlugins.clear(); |
|
|
|
foreach (const Plugin &plugin, m_availablePlugins) { |
|
if (plugin.isLoaded()) { |
|
m_loadedPlugins.append(plugin.instance); |
|
} |
|
} |
|
} |
|
|
|
void Plugins::loadPythonSupport() |
|
{ |
|
const QStringList dirs = DataPaths::allPaths(DataPaths::Plugins); |
|
for (const QString &dir : dirs) { |
|
const auto files = QDir(dir).entryInfoList({QSL("PyFalkon*")}, QDir::Files); |
|
for (const QFileInfo &info : files) { |
|
m_pythonPlugin = new QLibrary(info.absoluteFilePath(), this); |
|
m_pythonPlugin->setLoadHints(QLibrary::ExportExternalSymbolsHint); |
|
if (!m_pythonPlugin->load()) { |
|
qWarning() << "Failed to load python support plugin" << m_pythonPlugin->errorString(); |
|
delete m_pythonPlugin; |
|
m_pythonPlugin = nullptr; |
|
} else { |
|
std::cout << "Falkon: Python plugin support initialized" << std::endl; |
|
return; |
|
} |
|
} |
|
} |
|
} |
|
|
|
Plugins::Plugin Plugins::loadPlugin(const QString &id) |
|
{ |
|
QString name; |
|
Plugin::Type type = Plugin::Invalid; |
|
|
|
const int colon = id.indexOf(QL1C(':')); |
|
if (colon > -1) { |
|
const auto t = id.leftRef(colon); |
|
if (t == QL1S("internal")) { |
|
type = Plugin::InternalPlugin; |
|
} else if (t == QL1S("lib")) { |
|
type = Plugin::SharedLibraryPlugin; |
|
} else if (t == QL1S("python")) { |
|
type = Plugin::PythonPlugin; |
|
} |
|
name = id.mid(colon + 1); |
|
} else { |
|
name = id; |
|
type = Plugin::SharedLibraryPlugin; |
|
} |
|
|
|
switch (type) { |
|
case Plugin::InternalPlugin: |
|
return loadInternalPlugin(name); |
|
|
|
case Plugin::SharedLibraryPlugin: |
|
return loadSharedLibraryPlugin(name); |
|
|
|
case Plugin::PythonPlugin: |
|
return loadPythonPlugin(name); |
|
|
|
default: |
|
return Plugin(); |
|
} |
|
} |
|
|
|
Plugins::Plugin Plugins::loadInternalPlugin(const QString &name) |
|
{ |
|
if (name == QL1S("adblock")) { |
|
Plugin plugin; |
|
plugin.type = Plugin::InternalPlugin; |
|
plugin.pluginId = QSL("internal:adblock"); |
|
plugin.internalInstance = new AdBlockPlugin(); |
|
plugin.pluginSpec = createSpec(plugin.internalInstance->metaData()); |
|
return plugin; |
|
} else { |
|
return Plugin(); |
|
} |
|
} |
|
|
|
Plugins::Plugin Plugins::loadSharedLibraryPlugin(const QString &name) |
|
{ |
|
QString fullPath; |
|
if (QFileInfo(name).isAbsolute()) { |
|
fullPath = name; |
|
} else { |
|
fullPath = DataPaths::locate(DataPaths::Plugins, name); |
|
if (fullPath.isEmpty()) { |
|
qWarning() << "Plugin" << name << "not found"; |
|
return Plugin(); |
|
} |
|
} |
|
|
|
QPluginLoader *loader = new QPluginLoader(fullPath); |
|
PluginInterface *iPlugin = qobject_cast<PluginInterface*>(loader->instance()); |
|
|
|
if (!iPlugin) { |
|
qWarning() << "Loading" << fullPath << "failed:" << loader->errorString(); |
|
return Plugin(); |
|
} |
|
|
|
Plugin plugin; |
|
plugin.type = Plugin::SharedLibraryPlugin; |
|
plugin.pluginId = QSL("lib:%1").arg(QFileInfo(fullPath).fileName()); |
|
plugin.libraryPath = fullPath; |
|
plugin.pluginLoader = loader; |
|
plugin.pluginSpec = createSpec(iPlugin->metaData()); |
|
return plugin; |
|
} |
|
|
|
Plugins::Plugin Plugins::loadPythonPlugin(const QString &name) |
|
{ |
|
if (!m_pythonPlugin) { |
|
qWarning() << "Python support plugin is not loaded"; |
|
return Plugin(); |
|
} |
|
|
|
auto f = (Plugin(*)(const QString &)) m_pythonPlugin->resolve("pyfalkon_load_plugin"); |
|
if (!f) { |
|
qWarning() << "Failed to resolve" << "pyfalkon_load_plugin"; |
|
return Plugin(); |
|
} |
|
|
|
return f(name); |
|
} |
|
|
|
bool Plugins::initPlugin(PluginInterface::InitState state, Plugin *plugin) |
|
{ |
|
if (!plugin) { |
|
return false; |
|
} |
|
|
|
switch (plugin->type) { |
|
case Plugin::InternalPlugin: |
|
initInternalPlugin(plugin); |
|
break; |
|
|
|
case Plugin::SharedLibraryPlugin: |
|
initSharedLibraryPlugin(plugin); |
|
break; |
|
|
|
case Plugin::PythonPlugin: |
|
initPythonPlugin(plugin); |
|
break; |
|
|
|
default: |
|
return false; |
|
} |
|
|
|
if (!plugin->instance) { |
|
return false; |
|
} |
|
|
|
plugin->instance->init(state, DataPaths::currentProfilePath() + QL1S("/extensions")); |
|
|
|
if (!plugin->instance->testPlugin()) { |
|
emit pluginUnloaded(plugin->instance); |
|
plugin->instance = nullptr; |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
void Plugins::initInternalPlugin(Plugin *plugin) |
|
{ |
|
Q_ASSERT(plugin->type == Plugin::InternalPlugin); |
|
|
|
plugin->instance = plugin->internalInstance; |
|
} |
|
|
|
void Plugins::initSharedLibraryPlugin(Plugin *plugin) |
|
{ |
|
Q_ASSERT(plugin->type == Plugin::SharedLibraryPlugin); |
|
|
|
plugin->instance = qobject_cast<PluginInterface*>(plugin->pluginLoader->instance()); |
|
} |
|
|
|
void Plugins::initPythonPlugin(Plugin *plugin) |
|
{ |
|
Q_ASSERT(plugin->type == Plugin::PythonPlugin); |
|
|
|
if (!m_pythonPlugin) { |
|
qWarning() << "Python support plugin is not loaded"; |
|
return; |
|
} |
|
|
|
auto f = (void(*)(Plugin *)) m_pythonPlugin->resolve("pyfalkon_init_plugin"); |
|
if (!f) { |
|
qWarning() << "Failed to resolve" << "pyfalkon_init_plugin"; |
|
return; |
|
} |
|
|
|
f(plugin); |
|
}
|
|
|