1
mirror of https://invent.kde.org/network/falkon.git synced 2024-11-15 19:42:10 +01:00
falkonOfficial/src/plugins/PyFalkon/pythonplugin.cpp
David Rosca 2e377390b5
Add experimental support for Python extensions
Disabled by default until the build system is sorted out.

As it is now, Python extensions are loaded from "python"
subdirectory in standard plugin paths. Extensions can be loaded
and unloaded same way as C++ plugins.

Currently there are only wrappers needed to get PluginInterface
working from Python, other Falkon classes are inaccessible.
2018-02-25 18:51:31 +01:00

200 lines
5.5 KiB
C++

/* ============================================================
* Falkon - Qt web browser
* Copyright (C) 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 "pythonplugin.h"
#include "pythonpluginobject.h"
#include "plugins.h"
#include "datapaths.h"
#include "desktopfile.h"
#include <QDir>
#include <QCoreApplication>
#include <PyFalkon/pyfalkon_python.h>
extern "C" PyObject *PyInit_PyFalkon();
enum State
{
PythonUninitialized,
PythonInitialized,
PythonError
};
State state = PythonUninitialized;
PythonPluginObject *pluginObject = nullptr;
QHash<PyObject*, PluginInterface*> 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;
delete pluginObject;
}
}
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 void register_plugin_object()
{
pluginObject = new PythonPluginObject();
PyTypeObject *typeObject = Shiboken::SbkType<PythonPluginObject>();
PyObject *po = Shiboken::Conversions::pointerToPython(reinterpret_cast<const SbkObjectType *>(typeObject), pluginObject);
if (!po) {
qWarning() << "Failed to create wrapper for" << pluginObject;
return;
}
Py_INCREF(po);
PyObject *module = PyImport_AddModule("Falkon");
if (!module) {
Py_DECREF(po);
PyErr_Print();
qWarning() << "Failed to locate Falkon module!";
return;
}
if (PyModule_AddObject(module, "plugin", po) < 0) {
Py_DECREF(po);
PyErr_Print();
qWarning() << "Failed to add plugin object to Falkon module!";
return;
}
}
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);
state = PythonInitialized;
if (!PyImport_ImportModule("Falkon")) {
PyErr_Print();
qWarning() << "Failed to import Falkon module!";
return state = PythonError;
}
register_plugin_object();
return state;
}
Plugins::Plugin pyfalkon_load_plugin(const QString &name)
{
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();
}
}
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;
}
void pyfalkon_init_plugin(Plugins::Plugin *plugin)
{
if (init() != PythonInitialized) {
return;
}
PyObject *module = static_cast<PyObject*>(plugin->data.value<void*>());
if (module) {
plugin->instance = pluginInstances.value(module);
return;
}
const QString moduleName = plugin->pluginId.mid(7);
pluginObject->ptr = nullptr;
module = PyImport_ImportModule(qPrintable(moduleName));
if (!module) {
PyErr_Print();
qWarning() << "Failed to import module" << moduleName;
return;
}
if (!pluginObject->ptr) {
qWarning() << "No plugin registered! Falkon.plugin.registerPlugin() must be called from script.";
return;
}
pluginInstances[module] = pluginObject->ptr;
plugin->instance = pluginObject->ptr;
plugin->data = QVariant::fromValue(static_cast<void*>(module));
}
QVector<Plugins::Plugin> pyfalkon_load_available_plugins()
{
QVector<Plugins::Plugin> 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());
if (plugin.pluginSpec.name.isEmpty()) {
qWarning() << "Invalid plugin spec of" << info.absoluteFilePath() << "plugin";
continue;
}
plugins.append(plugin);
}
}
return plugins;
}