diff --git a/src/lib/plugins/plugins.cpp b/src/lib/plugins/plugins.cpp index 9eb3e175..2c390d73 100644 --- a/src/lib/plugins/plugins.cpp +++ b/src/lib/plugins/plugins.cpp @@ -1,468 +1,471 @@ /* ============================================================ * Falkon - Qt web browser * Copyright (C) 2010-2018 David Rosca * * 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 . * ============================================================ */ #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 "qml/qmlplugins.h" #include "qml/qmlplugin.h" #include #include #include #include #include Plugins::Plugins(QObject* parent) : QObject(parent) , m_pluginsLoaded(false) , m_speedDial(new SpeedDial(this)) { loadSettings(); if (!MainApplication::isTestModeEnabled()) { loadPythonSupport(); } } QList 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::removePlugin(Plugins::Plugin *plugin) { if (plugin->type != Plugin::QmlPlugin) { return; } if (plugin->isLoaded()) { unloadPlugin(plugin); } // For QML plugins, pluginId is qml: const QString dirName = plugin->pluginId.mid(4); const QString dirPath = DataPaths::locate(DataPaths::Plugins, QSL("qml/") + dirName); bool result = QDir(dirPath).removeRecursively(); if (!result) { qWarning() << "Unable to remove" << plugin->pluginSpec.name; return; } m_availablePlugins.removeOne(*plugin); refreshedLoadedPlugins(); } void Plugins::loadSettings() { QStringList defaultAllowedPlugins = { QSL("internal:adblock") }; // Enable KDE Frameworks Integration when running inside KDE session if (qgetenv("KDE_FULL_SESSION") == QByteArray("true")) { defaultAllowedPlugins.append(QSL("lib:KDEFrameworksIntegration.so")); } Settings settings; settings.beginGroup("Plugin-Settings"); m_allowedPlugins = settings.value("AllowedPlugins", defaultAllowedPlugins).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(*)()) m_pythonPlugin->resolve("pyfalkon_load_available_plugins"); + auto f = (void(*)(QVector*)) m_pythonPlugin->resolve("pyfalkon_load_available_plugins"); if (!f) { qWarning() << "Failed to resolve" << "pyfalkon_load_available_plugins"; } else { - const auto plugins = f(); + QVector plugins; + f(&plugins); for (const auto &plugin : plugins) { registerAvailablePlugin(plugin); } } } // QmlPlugin for (QString dir : dirs) { // qml plugins will be loaded from subdirectory qml dir.append(QSL("/qml")); const auto qmlDirs = QDir(dir).entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); for (const QFileInfo &info : qmlDirs) { Plugin plugin = QmlPlugin::loadPlugin(info.absoluteFilePath()); if (plugin.type == Plugin::Invalid) { continue; } if (plugin.pluginSpec.name.isEmpty()) { qWarning() << "Invalid plugin spec of" << info.absoluteFilePath() << "plugin"; continue; } 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); } } emit refreshedLoadedPlugins(); } 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; } else if (t == QL1S("qml")) { type = Plugin::QmlPlugin; } 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); case Plugin::QmlPlugin: return QmlPlugin::loadPlugin(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(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"); + auto f = (bool(*)(const QString &,Plugin*)) m_pythonPlugin->resolve("pyfalkon_load_plugin"); if (!f) { qWarning() << "Failed to resolve" << "pyfalkon_load_plugin"; return Plugin(); } - return f(name); + Plugin plugin; + f(name, &plugin); + return plugin; } 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; case Plugin::QmlPlugin: QmlPlugin::initPlugin(plugin); break; default: return false; } if (!plugin->instance) { return false; } // DataPaths::currentProfilePath() + QL1S("/extensions") is duplicated in qmlsettings.cpp // If you change this, please change it there too. 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(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); } diff --git a/src/plugins/PyFalkon/pythonplugin.cpp b/src/plugins/PyFalkon/pythonplugin.cpp index 357c625d..4e69d4b3 100644 --- a/src/plugins/PyFalkon/pythonplugin.cpp +++ b/src/plugins/PyFalkon/pythonplugin.cpp @@ -1,178 +1,174 @@ /* ============================================================ * Falkon - Qt web browser * Copyright (C) 2018 David Rosca * * 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 . * ============================================================ */ #include "pythonplugin.h" #include "plugins.h" #include "datapaths.h" #include "desktopfile.h" #include #include #include extern "C" PyObject *PyInit_PyFalkon(); enum State { PythonUninitialized, PythonInitialized, PythonError }; State state = PythonUninitialized; PluginInterface *pluginInterface = nullptr; QHash pluginInstances; static QStringList script_paths() { QStringList dirs = DataPaths::allPaths(DataPaths::Plugins); for (int i = 0; i < dirs.count(); ++i) { dirs[i].append(QSL("/python")); } return dirs; } static void cleanup() { if (state > PythonUninitialized) { Py_Finalize(); state = PythonUninitialized; } } static void set_path(const QStringList &scriptPaths) { const QString originalPath = QString::fromLocal8Bit(qgetenv("PYTHONPATH")); QStringList paths = scriptPaths; paths.append(originalPath); qputenv("PYTHONPATH", paths.join(QL1C(':')).toLocal8Bit()); } static State init() { if (state > PythonUninitialized) { return state; } set_path(script_paths()); if (PyImport_AppendInittab("Falkon", PyInit_PyFalkon) != 0) { PyErr_Print(); qWarning() << "Failed to initialize Falkon module!"; return state = PythonError; } Py_Initialize(); qAddPostRoutine(cleanup); return state = PythonInitialized; } void pyfalkon_register_plugin(PluginInterface *plugin) { pluginInterface = plugin; } -Plugins::Plugin pyfalkon_load_plugin(const QString &name) +bool pyfalkon_load_plugin(const QString &name, Plugins::Plugin *out) { QString fullPath; if (QFileInfo(name).isAbsolute()) { fullPath = name; } else { fullPath = DataPaths::locate(DataPaths::Plugins, QSL("python/") + name); if (fullPath.isEmpty()) { qWarning() << "Plugin" << name << "not found"; - return Plugins::Plugin(); + return false; } } - Plugins::Plugin plugin; - plugin.type = Plugins::Plugin::PythonPlugin; - plugin.pluginId = QSL("python:%1").arg(QFileInfo(name).fileName()); - plugin.pluginSpec = Plugins::createSpec(DesktopFile(fullPath + QSL("/metadata.desktop"))); - return plugin; + out->type = Plugins::Plugin::PythonPlugin; + out->pluginId = QSL("python:%1").arg(QFileInfo(name).fileName()); + out->pluginSpec = Plugins::createSpec(DesktopFile(fullPath + QSL("/metadata.desktop"))); + return true; } void pyfalkon_init_plugin(Plugins::Plugin *plugin) { if (init() != PythonInitialized) { return; } PyObject *module = static_cast(plugin->data.value()); if (module) { plugin->instance = pluginInstances.value(module); return; } const QString moduleName = plugin->pluginId.mid(7); pluginInterface = nullptr; module = PyImport_ImportModule(qPrintable(moduleName)); if (!module) { PyErr_Print(); qWarning() << "Failed to import module" << moduleName; return; } if (!pluginInterface) { qWarning() << "No plugin registered! Falkon.registerPlugin() must be called from script."; return; } pluginInstances[module] = pluginInterface; plugin->instance = pluginInterface; plugin->data = QVariant::fromValue(static_cast(module)); } -QVector pyfalkon_load_available_plugins() +void pyfalkon_load_available_plugins(QVector *out) { - QVector plugins; - const QStringList dirs = script_paths(); for (const QString &dir : dirs) { const auto modules = QDir(dir).entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); for (const QFileInfo &info : modules) { - Plugins::Plugin plugin = pyfalkon_load_plugin(info.absoluteFilePath()); + Plugins::Plugin plugin; + pyfalkon_load_plugin(info.absoluteFilePath(), &plugin); if (plugin.pluginSpec.name.isEmpty()) { qWarning() << "Invalid plugin spec of" << info.absoluteFilePath() << "plugin"; continue; } - plugins.append(plugin); + out->append(plugin); } } - - return plugins; } bool pyfalkon_run_script(const QByteArray &script) { if (init() != PythonInitialized) { return false; } if (PyRun_SimpleString(script.constData()) != 0) { PyErr_Print(); return false; } return true; } diff --git a/src/plugins/PyFalkon/pythonplugin.h b/src/plugins/PyFalkon/pythonplugin.h index b52560aa..a00ac15d 100644 --- a/src/plugins/PyFalkon/pythonplugin.h +++ b/src/plugins/PyFalkon/pythonplugin.h @@ -1,28 +1,28 @@ /* ============================================================ * Falkon - Qt web browser * Copyright (C) 2018 David Rosca * * 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 . * ============================================================ */ #pragma once #include "plugins.h" void pyfalkon_register_plugin(PluginInterface *plugin); -extern "C" Q_DECL_EXPORT Plugins::Plugin pyfalkon_load_plugin(const QString &name); +extern "C" Q_DECL_EXPORT bool pyfalkon_load_plugin(const QString &name, Plugins::Plugin *out); extern "C" Q_DECL_EXPORT void pyfalkon_init_plugin(Plugins::Plugin *plugin); -extern "C" Q_DECL_EXPORT QVector pyfalkon_load_available_plugins(); +extern "C" Q_DECL_EXPORT void pyfalkon_load_available_plugins(QVector *out); extern "C" Q_DECL_EXPORT bool pyfalkon_run_script(const QByteArray &script);