diff --git a/src/lib/network/schemehandlers/falkonschemehandler.cpp b/src/lib/network/schemehandlers/falkonschemehandler.cpp index 7fd13293..5b8c1330 100644 --- a/src/lib/network/schemehandlers/falkonschemehandler.cpp +++ b/src/lib/network/schemehandlers/falkonschemehandler.cpp @@ -1,416 +1,416 @@ /* ============================================================ * 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 "falkonschemehandler.h" #include "qztools.h" #include "browserwindow.h" #include "mainapplication.h" #include "speeddial.h" #include "pluginproxy.h" #include "plugininterface.h" #include "settings.h" #include "datapaths.h" #include "iconprovider.h" #include "sessionmanager.h" #include "restoremanager.h" #include "../config.h" #include #include #include #include #include static QString authorString(const char* name, const QString &mail) { return QSL("%1 <%2>").arg(QString::fromUtf8(name), mail); } FalkonSchemeHandler::FalkonSchemeHandler(QObject *parent) : QWebEngineUrlSchemeHandler(parent) { } void FalkonSchemeHandler::requestStarted(QWebEngineUrlRequestJob *job) { if (handleRequest(job)) { return; } QStringList knownPages; knownPages << "about" << "start" << "speeddial" << "config" << "restore"; if (knownPages.contains(job->requestUrl().path())) job->reply(QByteArrayLiteral("text/html"), new FalkonSchemeReply(job, job)); else job->fail(QWebEngineUrlRequestJob::UrlInvalid); } bool FalkonSchemeHandler::handleRequest(QWebEngineUrlRequestJob *job) { QUrlQuery query(job->requestUrl()); if (!query.isEmpty() && job->requestUrl().path() == QL1S("restore")) { if (mApp->restoreManager()) { if (query.hasQueryItem(QSL("new-session"))) { mApp->restoreManager()->clearRestoreData(); } else if (query.hasQueryItem(QSL("restore-session"))) { mApp->restoreSession(nullptr, mApp->restoreManager()->restoreData()); } } mApp->destroyRestoreManager(); job->redirect(QUrl(QSL("falkon:start"))); return true; } else if (job->requestUrl().path() == QL1S("reportbug")) { job->redirect(QUrl(Qz::BUGSADDRESS)); return true; } return false; } FalkonSchemeReply::FalkonSchemeReply(QWebEngineUrlRequestJob *job, QObject *parent) : QIODevice(parent) , m_loaded(false) , m_job(job) { m_pageName = m_job->requestUrl().path(); loadPage(); } void FalkonSchemeReply::loadPage() { if (m_loaded) return; QString contents; if (m_pageName == QLatin1String("about")) { contents = aboutPage(); } else if (m_pageName == QLatin1String("start")) { contents = startPage(); } else if (m_pageName == QLatin1String("speeddial")) { contents = speeddialPage(); } else if (m_pageName == QLatin1String("config")) { contents = configPage(); } else if (m_pageName == QLatin1String("restore")) { contents = restorePage(); } QMutexLocker lock(&m_mutex); m_buffer.setData(contents.toUtf8()); m_buffer.open(QIODevice::ReadOnly); lock.unlock(); emit readyRead(); m_loaded = true; } qint64 FalkonSchemeReply::bytesAvailable() const { QMutexLocker lock(&m_mutex); return m_buffer.bytesAvailable(); } qint64 FalkonSchemeReply::readData(char *data, qint64 maxSize) { QMutexLocker lock(&m_mutex); return m_buffer.read(data, maxSize); } qint64 FalkonSchemeReply::writeData(const char *data, qint64 len) { Q_UNUSED(data); Q_UNUSED(len); return 0; } QString FalkonSchemeReply::startPage() { static QString sPage; if (!sPage.isEmpty()) { return sPage; } sPage.append(QzTools::readAllFileContents(":html/start.html")); sPage.replace(QLatin1String("%ABOUT-IMG%"), QSL("qrc:icons/other/startpage.svg")); sPage.replace(QLatin1String("%TITLE%"), tr("Start Page")); sPage.replace(QLatin1String("%BUTTON-LABEL%"), tr("Search on Web")); sPage.replace(QLatin1String("%SEARCH-BY%"), tr("Search results provided by DuckDuckGo")); sPage.replace(QLatin1String("%WWW%"), Qz::WIKIADDRESS); sPage.replace(QLatin1String("%ABOUT-FALKON%"), tr("About Falkon")); sPage.replace(QLatin1String("%PRIVATE-BROWSING%"), mApp->isPrivate() ? tr("

Private Browsing

") : QString()); sPage = QzTools::applyDirectionToPage(sPage); return sPage; } QString FalkonSchemeReply::aboutPage() { static QString aPage; if (aPage.isEmpty()) { aPage.append(QzTools::readAllFileContents(":html/about.html")); aPage.replace(QLatin1String("%ABOUT-IMG%"), QSL("qrc:icons/other/about.svg")); aPage.replace(QLatin1String("%COPYRIGHT-INCLUDE%"), QzTools::readAllFileContents(":html/copyright").toHtmlEscaped()); aPage.replace(QLatin1String("%TITLE%"), tr("About Falkon")); aPage.replace(QLatin1String("%ABOUT-FALKON%"), tr("About Falkon")); aPage.replace(QLatin1String("%INFORMATIONS-ABOUT-VERSION%"), tr("Information about version")); aPage.replace(QLatin1String("%COPYRIGHT%"), tr("Copyright")); aPage.replace(QLatin1String("%VERSION-INFO%"), QString("
%1
%2
").arg(tr("Version"), #ifdef FALKON_GIT_REVISION QString("%1 (%2)").arg(Qz::VERSION, FALKON_GIT_REVISION))); #else Qz::VERSION)); #endif aPage.replace(QLatin1String("%MAIN-DEVELOPER%"), tr("Main developer")); aPage.replace(QLatin1String("%MAIN-DEVELOPER-TEXT%"), authorString(Qz::AUTHOR, "nowrep@gmail.com")); aPage = QzTools::applyDirectionToPage(aPage); } return aPage; } QString FalkonSchemeReply::speeddialPage() { static QString dPage; if (dPage.isEmpty()) { dPage.append(QzTools::readAllFileContents(":html/speeddial.html")); dPage.replace(QLatin1String("%IMG_PLUS%"), QLatin1String("qrc:html/plus.svg")); dPage.replace(QLatin1String("%IMG_CLOSE%"), QLatin1String("qrc:html/close.svg")); dPage.replace(QLatin1String("%IMG_EDIT%"), QLatin1String("qrc:html/edit.svg")); dPage.replace(QLatin1String("%IMG_RELOAD%"), QLatin1String("qrc:html/reload.svg")); dPage.replace(QLatin1String("%LOADING-IMG%"), QLatin1String("qrc:html/loading.gif")); dPage.replace(QLatin1String("%IMG_SETTINGS%"), QLatin1String("qrc:html/configure.svg")); dPage.replace(QLatin1String("%SITE-TITLE%"), tr("Speed Dial")); dPage.replace(QLatin1String("%ADD-TITLE%"), tr("Add New Page")); dPage.replace(QLatin1String("%TITLE-EDIT%"), tr("Edit")); dPage.replace(QLatin1String("%TITLE-REMOVE%"), tr("Remove")); dPage.replace(QLatin1String("%TITLE-RELOAD%"), tr("Reload")); dPage.replace(QLatin1String("%TITLE-WARN%"), tr("Are you sure you want to remove this speed dial?")); dPage.replace(QLatin1String("%TITLE-WARN-REL%"), tr("Are you sure you want to reload all speed dials?")); dPage.replace(QLatin1String("%TITLE-FETCHTITLE%"), tr("Load title from page")); dPage.replace(QLatin1String("%JAVASCRIPT-DISABLED%"), tr("SpeedDial requires enabled JavaScript.")); dPage.replace(QLatin1String("%URL%"), tr("Url")); dPage.replace(QLatin1String("%TITLE%"), tr("Title")); dPage.replace(QLatin1String("%APPLY%"), tr("Apply")); dPage.replace(QLatin1String("%CANCEL%"), tr("Cancel")); dPage.replace(QLatin1String("%NEW-PAGE%"), tr("New Page")); dPage.replace(QLatin1String("%SETTINGS-TITLE%"), tr("Speed Dial settings")); dPage.replace(QLatin1String("%TXT_PLACEMENT%"), tr("Placement: ")); dPage.replace(QLatin1String("%TXT_AUTO%"), tr("Auto")); dPage.replace(QLatin1String("%TXT_COVER%"), tr("Cover")); dPage.replace(QLatin1String("%TXT_FIT%"), tr("Fit")); dPage.replace(QLatin1String("%TXT_FWIDTH%"), tr("Fit Width")); dPage.replace(QLatin1String("%TXT_FHEIGHT%"), tr("Fit Height")); dPage.replace(QLatin1String("%TXT_NOTE%"), tr("Use custom wallpaper")); dPage.replace(QLatin1String("%TXT_SELECTIMAGE%"), tr("Click to select image")); dPage.replace(QLatin1String("%TXT_NRROWS%"), tr("Maximum pages in a row:")); dPage.replace(QLatin1String("%TXT_SDSIZE%"), tr("Change size of pages:")); dPage.replace(QLatin1String("%TXT_CNTRDLS%"), tr("Center speed dials")); dPage = QzTools::applyDirectionToPage(dPage); } QString page = dPage; SpeedDial* dial = mApp->plugins()->speedDial(); page.replace(QLatin1String("%INITIAL-SCRIPT%"), dial->initialScript().toUtf8().toBase64()); page.replace(QLatin1String("%IMG_BACKGROUND%"), dial->backgroundImage()); page.replace(QLatin1String("%URL_BACKGROUND%"), dial->backgroundImageUrl()); page.replace(QLatin1String("%B_SIZE%"), dial->backgroundImageSize()); page.replace(QLatin1String("%ROW-PAGES%"), QString::number(dial->pagesInRow())); page.replace(QLatin1String("%SD-SIZE%"), QString::number(dial->sdSize())); page.replace(QLatin1String("%SD-CENTER%"), dial->sdCenter() ? QSL("true") : QSL("false")); return page; } QString FalkonSchemeReply::restorePage() { static QString rPage; if (rPage.isEmpty()) { rPage.append(QzTools::readAllFileContents(":html/restore.html")); rPage.replace(QLatin1String("%IMAGE%"), QzTools::pixmapToDataUrl(IconProvider::standardIcon(QStyle::SP_MessageBoxWarning).pixmap(45)).toString()); rPage.replace(QLatin1String("%TITLE%"), tr("Restore Session")); rPage.replace(QLatin1String("%OOPS%"), tr("Oops, Falkon crashed.")); rPage.replace(QLatin1String("%APOLOGIZE%"), tr("We apologize for this. Would you like to restore the last saved state?")); rPage.replace(QLatin1String("%TRY-REMOVING%"), tr("Try removing one or more tabs that you think cause troubles")); rPage.replace(QLatin1String("%START-NEW%"), tr("Or you can start completely new session")); rPage.replace(QLatin1String("%WINDOW%"), tr("Window")); rPage.replace(QLatin1String("%WINDOWS-AND-TABS%"), tr("Windows and Tabs")); rPage.replace(QLatin1String("%BUTTON-START-NEW%"), tr("Start New Session")); rPage.replace(QLatin1String("%BUTTON-RESTORE%"), tr("Restore")); rPage.replace(QLatin1String("%JAVASCRIPT-DISABLED%"), tr("Requires enabled JavaScript.")); rPage = QzTools::applyDirectionToPage(rPage); } return rPage; } QString FalkonSchemeReply::configPage() { static QString cPage; if (cPage.isEmpty()) { cPage.append(QzTools::readAllFileContents(":html/config.html")); cPage.replace(QLatin1String("%ABOUT-IMG%"), QSL("qrc:icons/other/about.svg")); cPage.replace(QLatin1String("%TITLE%"), tr("Configuration Information")); cPage.replace(QLatin1String("%CONFIG%"), tr("Configuration Information")); cPage.replace(QLatin1String("%INFORMATIONS-ABOUT-VERSION%"), tr("Information about version")); cPage.replace(QLatin1String("%CONFIG-ABOUT%"), tr("This page contains information about Falkon's current configuration - relevant for troubleshooting. Please include this information when submitting bug reports.")); cPage.replace(QLatin1String("%BROWSER-IDENTIFICATION%"), tr("Browser Identification")); cPage.replace(QLatin1String("%PATHS%"), tr("Paths")); cPage.replace(QLatin1String("%BUILD-CONFIG%"), tr("Build Configuration")); cPage.replace(QLatin1String("%PREFS%"), tr("Preferences")); cPage.replace(QLatin1String("%OPTION%"), tr("Option")); cPage.replace(QLatin1String("%VALUE%"), tr("Value")); cPage.replace(QLatin1String("%PLUGINS%"), tr("Extensions")); cPage.replace(QLatin1String("%PL-NAME%"), tr("Name")); cPage.replace(QLatin1String("%PL-VER%"), tr("Version")); cPage.replace(QLatin1String("%PL-AUTH%"), tr("Author")); cPage.replace(QLatin1String("%PL-DESC%"), tr("Description")); auto allPaths = [](DataPaths::Path type) { QString out; const auto paths = DataPaths::allPaths(type); for (const QString &path : paths) { if (!out.isEmpty()) { out.append(QSL("
")); } out.append(path); } return out; }; cPage.replace(QLatin1String("%VERSION-INFO%"), QString("
%1
%2
").arg(tr("Application version"), #ifdef FALKON_GIT_REVISION QString("%1 (%2)").arg(Qz::VERSION, FALKON_GIT_REVISION) #else Qz::VERSION #endif ) + QString("
%1
%2
").arg(tr("Qt version"), qVersion()) + QString("
%1
%2
").arg(tr("Platform"), QzTools::operatingSystemLong())); cPage.replace(QLatin1String("%PATHS-TEXT%"), QString("
%1
%2
").arg(tr("Profile"), DataPaths::currentProfilePath()) + QString("
%1
%2
").arg(tr("Settings"), DataPaths::currentProfilePath() + "/settings.ini") + QString("
%1
%2
").arg(tr("Saved session"), SessionManager::defaultSessionPath()) + QString("
%1
%2
").arg(tr("Data"), allPaths(DataPaths::AppData)) + QString("
%1
%2
").arg(tr("Themes"), allPaths(DataPaths::Themes)) + QString("
%1
%2
").arg(tr("Extensions"), allPaths(DataPaths::Plugins))); #ifdef QT_DEBUG QString debugBuild = tr("Enabled"); #else QString debugBuild = tr("Disabled"); #endif #ifdef Q_OS_WIN #if defined(Q_OS_WIN) && defined(W7API) QString w7APIEnabled = tr("Enabled"); #else QString w7APIEnabled = tr("Disabled"); #endif #endif QString portableBuild = mApp->isPortable() ? tr("Enabled") : tr("Disabled"); cPage.replace(QLatin1String("%BUILD-CONFIG-TEXT%"), QString("
%1
%2
").arg(tr("Debug build"), debugBuild) + #ifdef Q_OS_WIN QString("
%1
%2
").arg(tr("Windows 7 API"), w7APIEnabled) + #endif QString("
%1
%2
").arg(tr("Portable build"), portableBuild)); cPage = QzTools::applyDirectionToPage(cPage); } QString page = cPage; page.replace(QLatin1String("%USER-AGENT%"), mApp->webProfile()->httpUserAgent()); QString pluginsString; - const QList &availablePlugins = mApp->plugins()->getAvailablePlugins(); + const QList &availablePlugins = mApp->plugins()->availablePlugins(); foreach (const Plugins::Plugin &plugin, availablePlugins) { PluginSpec spec = plugin.pluginSpec; pluginsString.append(QString("%1%2%3%4").arg( spec.name, spec.version, spec.author.toHtmlEscaped(), spec.description)); } if (pluginsString.isEmpty()) { pluginsString = QString("%1").arg(tr("No available extensions.")); } page.replace(QLatin1String("%PLUGINS-INFO%"), pluginsString); QString allGroupsString; QSettings* settings = Settings::globalSettings(); foreach (const QString &group, settings->childGroups()) { QString groupString = QString("[%1]").arg(group); settings->beginGroup(group); foreach (const QString &key, settings->childKeys()) { const QVariant keyValue = settings->value(key); QString keyString; switch (keyValue.type()) { case QVariant::ByteArray: keyString = QLatin1String("QByteArray"); break; case QVariant::Point: { const QPoint point = keyValue.toPoint(); keyString = QString("QPoint(%1, %2)").arg(point.x()).arg(point.y()); break; } case QVariant::StringList: keyString = keyValue.toStringList().join(","); break; default: keyString = keyValue.toString(); } if (keyString.isEmpty()) { keyString = QLatin1String("\"empty\""); } groupString.append(QString("%1%2").arg(key, keyString.toHtmlEscaped())); } settings->endGroup(); allGroupsString.append(groupString); } page.replace(QLatin1String("%PREFS-INFO%"), allGroupsString); return page; } diff --git a/src/lib/plugins/plugins.cpp b/src/lib/plugins/plugins.cpp index 9eb3e175..39be4a0d 100644 --- a/src/lib/plugins/plugins.cpp +++ b/src/lib/plugins/plugins.cpp @@ -1,468 +1,491 @@ /* ============================================================ * 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 +#include + +bool Plugins::Plugin::isLoaded() const +{ + return instance; +} + +bool Plugins::Plugin::isRemovable() const +{ + return !pluginPath.isEmpty() && QFileInfo(pluginPath).isWritable(); +} + +bool Plugins::Plugin::operator==(const Plugin &other) const +{ + return type == other.type && + pluginId == other.pluginId; +} Plugins::Plugins(QObject* parent) : QObject(parent) , m_pluginsLoaded(false) , m_speedDial(new SpeedDial(this)) { loadSettings(); if (!MainApplication::isTestModeEnabled()) { loadPythonSupport(); } } -QList Plugins::getAvailablePlugins() +QList Plugins::availablePlugins() { 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) { + if (!plugin->isRemovable()) { 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(); + bool result = false; + + QFileInfo info(plugin->pluginPath); + if (info.isDir()) { + result = QDir(plugin->pluginPath).removeRecursively(); + } else if (info.isFile()) { + result = QFile::remove(plugin->pluginPath); + } + if (!result) { - qWarning() << "Unable to remove" << plugin->pluginSpec.name; + qWarning() << "Failed to remove" << plugin->pluginSpec.name; return; } m_availablePlugins.removeOne(*plugin); - refreshedLoadedPlugins(); + emit availablePluginsChanged(); } 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"); if (!f) { qWarning() << "Failed to resolve" << "pyfalkon_load_available_plugins"; } else { const auto plugins = f(); for (const auto &plugin : plugins) { registerAvailablePlugin(plugin); } } } // QmlPlugin for (QString dir : dirs) { - // qml plugins will be loaded from subdirectory qml + // 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(); + emit availablePluginsChanged(); } 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.pluginPath = 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; 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/lib/plugins/plugins.h b/src/lib/plugins/plugins.h index 3f271bb3..43413975 100644 --- a/src/lib/plugins/plugins.h +++ b/src/lib/plugins/plugins.h @@ -1,142 +1,137 @@ /* ============================================================ * 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 . * ============================================================ */ #ifndef PLUGINLOADER_H #define PLUGINLOADER_H #include #include #include #include "qzcommon.h" #include "plugininterface.h" class QLibrary; class QPluginLoader; class SpeedDial; struct PluginSpec { QString name; QString description; QString author; QString version; QPixmap icon; bool hasSettings = false; bool operator==(const PluginSpec &other) const { return (this->name == other.name && this->description == other.description && this->author == other.author && this->version == other.version); } }; class FALKON_EXPORT Plugins : public QObject { Q_OBJECT public: struct Plugin { enum Type { Invalid = 0, InternalPlugin, SharedLibraryPlugin, PythonPlugin, QmlPlugin }; Type type = Invalid; QString pluginId; + QString pluginPath; PluginSpec pluginSpec; PluginInterface *instance = nullptr; // InternalPlugin PluginInterface *internalInstance = nullptr; // SharedLibraryPlugin - QString libraryPath; QPluginLoader *pluginLoader = nullptr; // Other QVariant data; - bool isLoaded() const { - return instance; - } - - bool operator==(const Plugin &other) const { - return this->type == other.type && - this->pluginId == other.pluginId; - } + bool isLoaded() const; + bool isRemovable() const; + bool operator==(const Plugin &other) const; }; explicit Plugins(QObject* parent = 0); - QList getAvailablePlugins(); + QList availablePlugins(); bool loadPlugin(Plugin* plugin); void unloadPlugin(Plugin* plugin); void removePlugin(Plugin *plugin); void shutdown(); // SpeedDial SpeedDial* speedDial() { return m_speedDial; } static PluginSpec createSpec(const DesktopFile &metaData); public Q_SLOTS: void loadSettings(); void loadPlugins(); protected: QList m_loadedPlugins; Q_SIGNALS: void pluginUnloaded(PluginInterface* plugin); - void refreshedLoadedPlugins(); + void availablePluginsChanged(); private: void loadPythonSupport(); Plugin loadPlugin(const QString &id); Plugin loadInternalPlugin(const QString &name); Plugin loadSharedLibraryPlugin(const QString &name); Plugin loadPythonPlugin(const QString &name); bool initPlugin(PluginInterface::InitState state, Plugin *plugin); void initInternalPlugin(Plugin *plugin); void initSharedLibraryPlugin(Plugin *plugin); void initPythonPlugin(Plugin *plugin); void registerAvailablePlugin(const Plugin &plugin); void refreshLoadedPlugins(); void loadAvailablePlugins(); QList m_availablePlugins; QStringList m_allowedPlugins; bool m_pluginsLoaded; SpeedDial* m_speedDial; QList m_internalPlugins; QLibrary *m_pythonPlugin = nullptr; }; Q_DECLARE_METATYPE(Plugins::Plugin) #endif // PLUGINLOADER_H diff --git a/src/lib/plugins/qml/qmlplugin.cpp b/src/lib/plugins/qml/qmlplugin.cpp index 3aeec86e..6a4eba65 100644 --- a/src/lib/plugins/qml/qmlplugin.cpp +++ b/src/lib/plugins/qml/qmlplugin.cpp @@ -1,78 +1,79 @@ /* ============================================================ * Falkon - Qt web browser * Copyright (C) 2018 Anmol Gautam * * 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 "qmlplugin.h" #include "qmlplugins.h" #include "qmlpluginloader.h" #include "datapaths.h" #include "desktopfile.h" #include #include QmlPlugin::QmlPlugin() { } Plugins::Plugin QmlPlugin::loadPlugin(const QString &name) { static bool qmlSupportLoaded = false; if (!qmlSupportLoaded) { QmlPlugins::registerQmlTypes(); qmlSupportLoaded = true; } QString fullPath; if (QFileInfo(name).isAbsolute()) { fullPath = name; } else { fullPath = DataPaths::locate(DataPaths::Plugins, QSL("qml/") + name); if (fullPath.isEmpty()) { qWarning() << "Plugin" << name << "not found"; return Plugins::Plugin(); } } Plugins::Plugin plugin; plugin.type = Plugins::Plugin::QmlPlugin; plugin.pluginId = QSL("qml:%1").arg(QFileInfo(name).fileName()); + plugin.pluginPath = fullPath; DesktopFile desktopFile(fullPath + QSL("/metadata.desktop")); plugin.pluginSpec = Plugins::createSpec(desktopFile); QString entryPoint = desktopFile.value(QSL("X-Falkon-EntryPoint")).toString(); plugin.data = QVariant::fromValue(new QmlPluginLoader(plugin.pluginSpec.name, fullPath, entryPoint)); return plugin; } void QmlPlugin::initPlugin(Plugins::Plugin *plugin) { Q_ASSERT(plugin->type == Plugins::Plugin::QmlPlugin); const QString name = plugin->pluginSpec.name; auto qmlPluginLoader = plugin->data.value(); if (!qmlPluginLoader) { qWarning() << "Failed to cast from data"; return; } qmlPluginLoader->createComponent(); if (!qmlPluginLoader->instance()) { qWarning().noquote() << "Falied to create component for" << name << "plugin:" << qmlPluginLoader->component()->errorString(); return; } plugin->instance = qobject_cast(qmlPluginLoader->instance()); } diff --git a/src/lib/preferences/pluginslist.ui b/src/lib/preferences/pluginslist.ui index b9e0e441..fbb3daf8 100644 --- a/src/lib/preferences/pluginslist.ui +++ b/src/lib/preferences/pluginslist.ui @@ -1,79 +1,89 @@ PluginsList 0 0 623 462 0 0 0 0 true QAbstractItemView::ScrollPerPixel true true false 0 0 Settings Qt::Horizontal 40 20 + + + + false + + + Remove + + + diff --git a/src/lib/preferences/pluginsmanager.cpp b/src/lib/preferences/pluginsmanager.cpp index a1e1fdae..96e5fba4 100644 --- a/src/lib/preferences/pluginsmanager.cpp +++ b/src/lib/preferences/pluginsmanager.cpp @@ -1,218 +1,244 @@ /* ============================================================ * 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 "pluginsmanager.h" #include "ui_pluginslist.h" #include "pluginproxy.h" #include "mainapplication.h" #include "plugininterface.h" #include "pluginlistdelegate.h" #include "qztools.h" #include "settings.h" #include "iconprovider.h" #include "../config.h" #include #include #include PluginsManager::PluginsManager(QWidget* parent) : QWidget(parent) , ui(new Ui::PluginsList) , m_loaded(false) { ui->setupUi(this); ui->list->setLayoutDirection(Qt::LeftToRight); ui->butSettings->setIcon(IconProvider::settingsIcon()); + ui->butRemove->setIcon(QIcon::fromTheme(QSL("edit-delete"))); //Application Extensions Settings settings; settings.beginGroup("Plugin-Settings"); bool appPluginsEnabled = settings.value("EnablePlugins", true).toBool(); settings.endGroup(); ui->list->setEnabled(appPluginsEnabled); connect(ui->butSettings, &QAbstractButton::clicked, this, &PluginsManager::settingsClicked); + connect(ui->butRemove, &QAbstractButton::clicked, this, &PluginsManager::removeClicked); connect(ui->list, &QListWidget::currentItemChanged, this, &PluginsManager::currentChanged); connect(ui->list, &QListWidget::itemChanged, this, &PluginsManager::itemChanged); + connect(mApp->plugins(), &Plugins::availablePluginsChanged, this, &PluginsManager::refresh); ui->list->setItemDelegate(new PluginListDelegate(ui->list)); } void PluginsManager::load() { if (!m_loaded) { refresh(); m_loaded = true; } } void PluginsManager::save() { if (!m_loaded) { return; } QStringList allowedPlugins; for (int i = 0; i < ui->list->count(); i++) { QListWidgetItem* item = ui->list->item(i); if (item->checkState() == Qt::Checked) { const Plugins::Plugin plugin = item->data(Qt::UserRole + 10).value(); allowedPlugins.append(plugin.pluginId); } } Settings settings; settings.beginGroup("Plugin-Settings"); settings.setValue("AllowedPlugins", allowedPlugins); settings.endGroup(); } void PluginsManager::refresh() { + if (m_blockRefresh) { + return; + } + + const int oldCurrentRow = ui->list->currentRow(); + ui->list->clear(); ui->butSettings->setEnabled(false); disconnect(ui->list, &QListWidget::itemChanged, this, &PluginsManager::itemChanged); - const QList &allPlugins = mApp->plugins()->getAvailablePlugins(); + const QList &allPlugins = mApp->plugins()->availablePlugins(); foreach (const Plugins::Plugin &plugin, allPlugins) { PluginSpec spec = plugin.pluginSpec; QListWidgetItem* item = new QListWidgetItem(ui->list); QIcon icon = QIcon(spec.icon); if (icon.isNull()) { icon = QIcon(QSL(":/icons/preferences/extensions.svg")); } item->setIcon(icon); QString pluginInfo = QString("%1 %2
%3
").arg(spec.name, spec.version, spec.author.toHtmlEscaped()); item->setToolTip(pluginInfo); item->setText(spec.name); item->setData(Qt::UserRole, spec.version); item->setData(Qt::UserRole + 1, spec.author); item->setData(Qt::UserRole + 2, spec.description); item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setCheckState(plugin.isLoaded() ? Qt::Checked : Qt::Unchecked); item->setData(Qt::UserRole + 10, QVariant::fromValue(plugin)); ui->list->addItem(item); } sortItems(); + if (oldCurrentRow >= 0) { + ui->list->setCurrentRow(qMax(0, oldCurrentRow - 1)); + ui->list->setFocus(); + } + connect(ui->list, &QListWidget::itemChanged, this, &PluginsManager::itemChanged); } void PluginsManager::sortItems() { ui->list->sortItems(); bool itemMoved; do { itemMoved = false; for (int i = 0; i < ui->list->count(); ++i) { QListWidgetItem* topItem = ui->list->item(i); QListWidgetItem* bottomItem = ui->list->item(i + 1); if (!topItem || !bottomItem) { continue; } if (topItem->checkState() == Qt::Unchecked && bottomItem->checkState() == Qt::Checked) { QListWidgetItem* item = ui->list->takeItem(i + 1); ui->list->insertItem(i, item); itemMoved = true; } } } while (itemMoved); } void PluginsManager::currentChanged(QListWidgetItem* item) { if (!item) { return; } const Plugins::Plugin plugin = item->data(Qt::UserRole + 10).value(); - bool showSettings = plugin.pluginSpec.hasSettings; - - if (!plugin.isLoaded()) { - showSettings = false; - } - - ui->butSettings->setEnabled(showSettings); + ui->butSettings->setEnabled(plugin.isLoaded() && plugin.pluginSpec.hasSettings); + ui->butRemove->setEnabled(plugin.isRemovable()); } void PluginsManager::itemChanged(QListWidgetItem* item) { if (!item) { return; } Plugins::Plugin plugin = item->data(Qt::UserRole + 10).value(); + m_blockRefresh = true; + if (item->checkState() == Qt::Checked) { mApp->plugins()->loadPlugin(&plugin); } else { mApp->plugins()->unloadPlugin(&plugin); } + m_blockRefresh = false; + disconnect(ui->list, &QListWidget::itemChanged, this, &PluginsManager::itemChanged); if (item->checkState() == Qt::Checked && !plugin.isLoaded()) { item->setCheckState(Qt::Unchecked); QMessageBox::critical(this, tr("Error!"), tr("Cannot load extension!")); } item->setData(Qt::UserRole + 10, QVariant::fromValue(plugin)); connect(ui->list, &QListWidget::itemChanged, this, &PluginsManager::itemChanged); - currentChanged(ui->list->currentItem()); } void PluginsManager::settingsClicked() { QListWidgetItem* item = ui->list->currentItem(); if (!item || item->checkState() == Qt::Unchecked) { return; } Plugins::Plugin plugin = item->data(Qt::UserRole + 10).value(); if (!plugin.isLoaded()) { mApp->plugins()->loadPlugin(&plugin); item->setData(Qt::UserRole + 10, QVariant::fromValue(plugin)); } if (plugin.isLoaded() && plugin.pluginSpec.hasSettings) { plugin.instance->showSettings(this); } } +void PluginsManager::removeClicked() +{ + QListWidgetItem* item = ui->list->currentItem(); + if (!item) { + return; + } + + Plugins::Plugin plugin = item->data(Qt::UserRole + 10).value(); + + if (plugin.isRemovable()) { + mApp->plugins()->removePlugin(&plugin); + } +} + PluginsManager::~PluginsManager() { delete ui; } diff --git a/src/lib/preferences/pluginsmanager.h b/src/lib/preferences/pluginsmanager.h index 789361d4..b19af012 100644 --- a/src/lib/preferences/pluginsmanager.h +++ b/src/lib/preferences/pluginsmanager.h @@ -1,57 +1,59 @@ /* ============================================================ * 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 . * ============================================================ */ #ifndef PLUGINSMANAGER_H #define PLUGINSMANAGER_H #include #include "qzcommon.h" namespace Ui { class PluginsList; } class QListWidgetItem; class FALKON_EXPORT PluginsManager : public QWidget { Q_OBJECT public: explicit PluginsManager(QWidget* parent = 0); ~PluginsManager(); void load(); void save(); private Q_SLOTS: void settingsClicked(); + void removeClicked(); void currentChanged(QListWidgetItem* item); void itemChanged(QListWidgetItem* item); void refresh(); private: void sortItems(); Ui::PluginsList* ui; bool m_loaded; + bool m_blockRefresh = false; }; #endif // PLUGINSMANAGER_H diff --git a/src/plugins/PyFalkon/pythonplugin.cpp b/src/plugins/PyFalkon/pythonplugin.cpp index 357c625d..fcce66fc 100644 --- a/src/plugins/PyFalkon/pythonplugin.cpp +++ b/src/plugins/PyFalkon/pythonplugin.cpp @@ -1,178 +1,179 @@ /* ============================================================ * 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) { 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.pluginPath = fullPath; 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(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() { 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()); if (plugin.pluginSpec.name.isEmpty()) { qWarning() << "Invalid plugin spec of" << info.absoluteFilePath() << "plugin"; continue; } plugins.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; }