diff --git a/src/lib/plugins/ocssupport.cpp b/src/lib/plugins/ocssupport.cpp index 138c15f1..fd4983e5 100644 --- a/src/lib/plugins/ocssupport.cpp +++ b/src/lib/plugins/ocssupport.cpp @@ -1,206 +1,223 @@ /* ============================================================ * Falkon - Qt web browser * Copyright (C) 2019 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 "ocssupport.h" #include "pluginproxy.h" #include "datapaths.h" #include "networkmanager.h" +#include "desktopfile.h" #include "desktopnotificationsfactory.h" #include "mainapplication.h" #include #include #include #include #include Q_GLOBAL_STATIC(OcsSupport, qz_ocs_support) +static DesktopFile readMetaData(const KArchiveDirectory *directory) +{ + const KArchiveEntry *entry = directory->entry(QSL("metadata.desktop")); + if (!entry || !entry->isFile()) { + qWarning() << "No metadata.desktop found"; + return DesktopFile(); + } + const QString tempDir = DataPaths::path(DataPaths::Temp); + static_cast(entry)->copyTo(tempDir); + return DesktopFile(tempDir + QL1S("/metadata.desktop")); +} + OcsSupport::OcsSupport(QObject *parent) : QObject(parent) { } bool OcsSupport::handleUrl(const QUrl &url) { if (url.host() != QL1S("install")) { return false; } QUrl fileUrl; QString fileType; QString fileName; const auto items = QUrlQuery(url).queryItems(QUrl::FullyDecoded); for (const auto &item : items) { if (item.first == QL1S("url")) { fileUrl = QUrl(item.second); } else if (item.first == QL1S("type")) { fileType = item.second; } else if (item.first == QL1S("filename")) { fileName = item.second; } } if (!fileType.startsWith(QL1S("falkon_"))) { return false; } if (fileType != QL1S("falkon_themes") && fileType != QL1S("falkon_extensions")) { qWarning() << "Unsupported type" << fileType; return false; } if (!fileUrl.isValid()) { qWarning() << "Invalid url" << fileUrl << url; return false; } qInfo() << "Downloading" << fileUrl; QNetworkReply *reply = mApp->networkManager()->get(QNetworkRequest(fileUrl)); connect(reply, &QNetworkReply::finished, this, [=]() { reply->deleteLater(); if (reply->error() != QNetworkReply::NoError) { qWarning() << "Error downloading" << fileUrl << reply->error() << reply->errorString(); return; } QBuffer buf; buf.setData(reply->readAll()); KZip zip(&buf); if (!zip.open(QIODevice::ReadOnly)) { qWarning() << "Failed to open archive"; return; } QString notifyMessage; if (fileType == QL1S("falkon_themes")) { installTheme(zip.directory()); } else if (fileType == QL1S("falkon_extensions")) { installExtension(zip.directory()); } }); return true; } // static OcsSupport *OcsSupport::instance() { return qz_ocs_support(); } void OcsSupport::installTheme(const KArchiveDirectory *directory) { auto showError = []() { mApp->desktopNotifications()->showNotification(tr("Installation failed"), tr("Failed to install theme")); }; if (directory->entries().size() != 1) { qWarning() << "Invalid archive format"; showError(); return; } const QString name = directory->entries().at(0); const KArchiveEntry *entry = directory->entry(name); if (!entry || !entry->isDirectory()) { qWarning() << "Invalid archive format"; showError(); return; } + const DesktopFile metaData = readMetaData(static_cast(entry)); + const QString targetDir = DataPaths::path(DataPaths::Config) + QL1S("/themes"); QDir().mkpath(targetDir); if (QFileInfo::exists(targetDir + QL1C('/') + name)) { qWarning() << "Theme" << name << "already exists"; mApp->desktopNotifications()->showNotification(tr("Installation failed"), tr("Theme is already installed")); return; } if (!directory->copyTo(targetDir)) { qWarning() << "Failed to copy theme to" << targetDir; showError(); return; } qInfo() << "Theme installed to" << targetDir; - mApp->desktopNotifications()->showNotification(tr("Theme installed"), tr("Theme was successfully installed")); + mApp->desktopNotifications()->showNotification(tr("Theme installed"), tr("'%1' was successfully installed").arg(metaData.name())); } void OcsSupport::installExtension(const KArchiveDirectory *directory) { auto showError = []() { mApp->desktopNotifications()->showNotification(tr("Installation failed"), tr("Failed to install extension")); }; if (directory->entries().size() != 1) { qWarning() << "Invalid archive format"; showError(); return; } const QString name = directory->entries().at(0); const KArchiveEntry *entry = directory->entry(name); if (!entry || !entry->isDirectory()) { qWarning() << "Invalid archive format"; showError(); return; } + const DesktopFile metaData = readMetaData(static_cast(entry)); + const QString extensionType = metaData.value(QSL("X-Falkon-Type")).toString(); + QString type; - const QStringList files = static_cast(entry)->entries(); - if (files.contains(QL1S("__init__.py"))) { + if (extensionType == QL1S("Extension/Python")) { type = QSL("python"); - } else if (files.contains(QL1S("main.qml"))) { + } else if (extensionType == QL1S("Extension/Qml")) { type = QSL("qml"); } if (type.isEmpty()) { - qWarning() << "Unsupported extension type"; + qWarning() << "Unsupported extension type" << extensionType; showError(); return; } - const QString targetDir = DataPaths::path(DataPaths::Config) + QL1S("/plugins/") + type; + const QString targetDir = DataPaths::path(DataPaths::Config) + QL1S("/plugins/"); QDir().mkpath(targetDir); if (QFileInfo::exists(targetDir + QL1S("/") + name)) { qWarning() << "Extension" << name << "already exists"; mApp->desktopNotifications()->showNotification(tr("Installation failed"), tr("Extension is already installed")); return; } if (!directory->copyTo(targetDir)) { qWarning() << "Failed to copy extension to" << targetDir; showError(); return; } qInfo() << "Extension installed to" << targetDir; const QString fullId = QSL("%1:%2/%3").arg(type, targetDir, name); if (!mApp->plugins()->addPlugin(fullId)) { qWarning() << "Failed to add plugin" << fullId; showError(); return; } - mApp->desktopNotifications()->showNotification(tr("Extension installed"), tr("Extension was successfully installed")); + mApp->desktopNotifications()->showNotification(tr("Extension installed"), tr("'%1' was successfully installed").arg(metaData.name())); } diff --git a/src/lib/plugins/plugins.cpp b/src/lib/plugins/plugins.cpp index 77aa86be..bdc8c3f5 100644 --- a/src/lib/plugins/plugins.cpp +++ b/src/lib/plugins/plugins.cpp @@ -1,505 +1,489 @@ /* ============================================================ * 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::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->isRemovable()) { return; } if (plugin->isLoaded()) { unloadPlugin(plugin); } 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() << "Failed to remove" << plugin->pluginSpec.name; return; } m_availablePlugins.removeOne(*plugin); emit availablePluginsChanged(); } bool Plugins::addPlugin(const QString &id) { Plugin plugin = loadPlugin(id); if (plugin.type == Plugin::Invalid) { return false; } if (plugin.pluginSpec.name.isEmpty()) { qWarning() << "Invalid plugin spec of" << id << "plugin"; return false; } registerAvailablePlugin(plugin); emit availablePluginsChanged(); return true; } 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); + const auto files = QDir(dir).entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); 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); + Plugin plugin; + const QString pluginPath = info.absoluteFilePath(); + if (info.isFile()) { + // SharedLibraryPlugin + if (info.baseName() != QL1S("PyFalkon")) { + plugin = loadSharedLibraryPlugin(pluginPath); + } + } else if (info.isDir()) { + const DesktopFile metaData(QDir(pluginPath).filePath(QSL("metadata.desktop"))); + const QString type = metaData.value(QSL("X-Falkon-Type")).toString(); + if (type == QL1S("Extension/Python")) { + // PythonPlugin + plugin = loadPythonPlugin(pluginPath); + } else if (type == QL1S("Extension/Qml")) { + // QmlPlugin + plugin = QmlPlugin::loadPlugin(pluginPath); + } else { + qWarning() << "Invalid type" << type << "of" << pluginPath << "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"; + qWarning() << "Invalid plugin spec of" << pluginPath << "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 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.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/plugins/PyFalkon/pythonplugin.cpp b/src/plugins/PyFalkon/pythonplugin.cpp index fcce66fc..88b03346 100644 --- a/src/plugins/PyFalkon/pythonplugin.cpp +++ b/src/plugins/PyFalkon/pythonplugin.cpp @@ -1,179 +1,136 @@ /* ============================================================ * 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()); + set_path(DataPaths::allPaths(DataPaths::Plugins)); 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; -} diff --git a/src/plugins/PyFalkon/pythonplugin.h b/src/plugins/PyFalkon/pythonplugin.h index b52560aa..db3bd830 100644 --- a/src/plugins/PyFalkon/pythonplugin.h +++ b/src/plugins/PyFalkon/pythonplugin.h @@ -1,28 +1,25 @@ /* ============================================================ * 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 void pyfalkon_init_plugin(Plugins::Plugin *plugin); -extern "C" Q_DECL_EXPORT QVector pyfalkon_load_available_plugins(); - -extern "C" Q_DECL_EXPORT bool pyfalkon_run_script(const QByteArray &script); diff --git a/src/scripts/CMakeLists.txt b/src/scripts/CMakeLists.txt index 6e18917f..70a3fa89 100644 --- a/src/scripts/CMakeLists.txt +++ b/src/scripts/CMakeLists.txt @@ -1,24 +1,24 @@ function(install_python_script name) if (ENABLE_PYTHON_PLUGINS) install( DIRECTORY ${name} - DESTINATION "${FALKON_INSTALL_PLUGINDIR}/python" + DESTINATION ${FALKON_INSTALL_PLUGINDIR} FILES_MATCHING PATTERN "*" PATTERN "Messages.sh" EXCLUDE ) - install(FILES i18n.py DESTINATION "${FALKON_INSTALL_PLUGINDIR}/python/${name}") + install(FILES i18n.py DESTINATION "${FALKON_INSTALL_PLUGINDIR}/${name}") endif() endfunction() function(install_qml_script name) install( DIRECTORY ${name} - DESTINATION "${FALKON_INSTALL_PLUGINDIR}/qml" + DESTINATION ${FALKON_INSTALL_PLUGINDIR} FILES_MATCHING PATTERN "*" PATTERN "Messages.sh" EXCLUDE ) endfunction() -install_python_script(hellopython) +# install_python_script(hellopython) install_python_script(runaction) install_python_script(middleclickloader) -install_qml_script(helloqml) +# install_qml_script(helloqml) diff --git a/src/scripts/hellopython/metadata.desktop b/src/scripts/hellopython/metadata.desktop index 30957aaa..ffe51f22 100644 --- a/src/scripts/hellopython/metadata.desktop +++ b/src/scripts/hellopython/metadata.desktop @@ -1,57 +1,58 @@ [Desktop Entry] Name=Hello Python Name[ca]=Hola Python Name[ca@valencia]=Hola Python Name[cs]=Hello Python Name[da]=Hallo Python Name[de]=Hallo-Python Name[en_GB]=Hello Python Name[es]=Hola Python Name[fi]=Hei Python Name[fr]=Hello Python Name[gl]=Ola, Python! Name[id]=Hello Python Name[it]=Hello Python Name[nl]=Hallo Python Name[nn]=Hei-Python Name[pl]=Witaj Python Name[pt]=Olá em Python Name[pt_BR]=Olá Python Name[sk]=Hello Python Name[sv]=Hej Python Name[uk]=Привіт, Python Name[x-test]=xxHello Pythonxx Name[zh_CN]=Hello Python Name[zh_TW]=嗨 Python Comment=Example Python extension Comment[ca]=Exemple d'una extensió en Python Comment[ca@valencia]=Exemple d'una extensió en Python Comment[cs]=Ukázkové rozšíření v Pythonu Comment[da]=Eksempel Python-udvidelse Comment[de]=Beispiel eines Python-Moduls Comment[en_GB]=Example Python extension Comment[es]=Ejemplo de extensión de Python Comment[fi]=Esimerkinomainen Python-laajennus Comment[fr]=Exemple d'extension Python Comment[gl]=Extensión de Python de exemplo Comment[id]=Contoh ekstensi Python Comment[it]=Estensione di esempio in Python Comment[nl]=Voorbeeld Python-extensie Comment[nn]=Eksempel på Python-tillegg Comment[pl]=Przykładowe rozszerzenie Python Comment[pt]=Extensão de exemplo em Python Comment[pt_BR]=Exemplo de extensão Python Comment[sk]=Príkladové rozšírenie v Python-e Comment[sv]=Exempel på Python-utökning Comment[uk]=Приклад розширення мовою Python Comment[x-test]=xxExample Python extensionxx Comment[zh_CN]=Python 扩展示例 Comment[zh_TW]=Python 擴充程式範例 Icon= Type=Service +X-Falkon-Type=Extension/Python X-Falkon-Author=David Rosca X-Falkon-Email=nowrep@gmail.com X-Falkon-Version=0.1.0 X-Falkon-Settings=true diff --git a/src/scripts/helloqml/metadata.desktop b/src/scripts/helloqml/metadata.desktop index c18ea3d4..dae7f2de 100644 --- a/src/scripts/helloqml/metadata.desktop +++ b/src/scripts/helloqml/metadata.desktop @@ -1,51 +1,54 @@ [Desktop Entry] Name=Hello Qml Name[ca]=Hola Qml Name[ca@valencia]=Hola Qml Name[cs]=Ahoj QML Name[de]=Hallo QML“ Name[en_GB]=Hello QML Name[es]=Hola Qml Name[fi]=Hei QML Name[fr]=Bonjour Qml Name[gl]=Ola Qml Name[id]=Hello Qml Name[it]=Hello Qml Name[nl]=Hello Qml Name[pl]=Hello Qml Name[pt]=Olá em QML Name[pt_BR]=Olá Qml Name[sk]=Hello Qml Name[sv]=Hallå QML Name[uk]=Привіт, Qml Name[x-test]=xxHello Qmlxx Name[zh_CN]=Hello Qml Name[zh_TW]=嗨 QML Comment=Sample Qml Plugin Comment[ca]=Connector Qml d'exemple Comment[ca@valencia]=Connector Qml d'exemple Comment[cs]=Ukázkový modul QML Comment[de]=Beispielmodul in Qml Comment[en_GB]=Sample QML Plugin Comment[es]=Complemento de ejemplo de Qml Comment[fi]=Esimerkki-QML-liitännäinen Comment[fr]=Exemple de module externe Qml Comment[gl]=Complemento de exemplo de Qml Comment[id]=Contoh Plugin Qml Comment[it]=Estensione di esempio in Qml Comment[nl]=Voorbeeld Qml-plug-in Comment[pl]=Przykładowa wtyczka Qml Comment[pt]='Plugin' de Exemplo em QML Comment[pt_BR]=Plug-in QML de exemplo Comment[sk]=Ukážkový QML plugin Comment[sv]=QML-exempelinsticksprogram Comment[uk]=Зразок додатка Qml Comment[x-test]=xxSample Qml Pluginxx Comment[zh_CN]=示例 Qml 插件 Comment[zh_TW]=簡易的 QML 外掛 + Icon= Type=Service +X-Falkon-Type=Extension/Qml + X-Falkon-Author=Anmol Gautam X-Falkon-Email=tarptaeya@gmail.com X-Falkon-Version=0.1.0 X-Falkon-Settings=true diff --git a/src/scripts/middleclickloader/metadata.desktop b/src/scripts/middleclickloader/metadata.desktop index cc83e40f..f4102b7a 100644 --- a/src/scripts/middleclickloader/metadata.desktop +++ b/src/scripts/middleclickloader/metadata.desktop @@ -1,53 +1,54 @@ [Desktop Entry] Name=MiddleClickLoader Name[ca]=MiddleClickLoader Name[ca@valencia]=MiddleClickLoader Name[cs]=Načtení prostředním tlačítkem myši Name[de]=Mittelklick-Lader Name[en_GB]=MiddleClickLoader Name[es]=Cargador de clic central Name[fi]=Keskipainikelatain Name[fr]=MiddleClickLoader Name[gl]=MiddleClickLoader Name[id]=MiddleClickLoader Name[it]=Caricatore al clic centrale Name[nl]=MiddleClickLoader Name[nn]=Lasting med midtklikk Name[pl]=MiddleClickLoader Name[pt]=CarregadorBotãoMeio Name[pt_BR]=CarregadorBotãoMeio Name[sk]=MiddleClickLoader Name[sv]=Mittenklicksinläsning Name[uk]=Завантажувач за клацанням середньою Name[x-test]=xxMiddleClickLoaderxx Name[zh_CN]=中键加载器 Name[zh_TW]=MiddleClickLoader Comment=Load text in selection clipboard as URL Comment[ca]=Carrega text com a URL en una selecció del porta-retalls Comment[ca@valencia]=Carrega text com a URL en una selecció del porta-retalls Comment[cs]=Načíst text ve schránce výběru jako URL Comment[de]=Text in der Auswahl der Zwischenablage als URL laden Comment[en_GB]=Load text in selection clipboard as URL Comment[es]=Cargar texto seleccionado en el portapapeles como URL Comment[fi]=Lataa leikepöytävalinnan tekstin verkko-osoitteeksi Comment[fr]=Charger l'URL sélectionnée dans le presse-papier Comment[gl]=Cargar texto no portapapeis de selección como URL Comment[id]=Muat teks dalam seleksi clipboard sebagai URL Comment[it]=Carica come URL il testo negli appunti della selezione Comment[nl]=Tekst laden in selectieklembord als URL Comment[pl]=Wczytaj tekst w schowku zaznaczenia jako adres URL Comment[pt]=Carrega o texto na área de transferência como um URL Comment[pt_BR]=Carrega o texto na área de transferência como uma URL Comment[sk]=Načítať text vo výbere schránky ako URL Comment[sv]=Läs in text i markeringen eller klippbordet som webbadress Comment[uk]=Завантажує текст у буфері обміну як адресу Comment[x-test]=xxLoad text in selection clipboard as URLxx Comment[zh_CN]=将选择剪贴板中的文本加载为 URL Icon= Type=Service +X-Falkon-Type=Extension/Python X-Falkon-Author=Juraj Oravec X-Falkon-Email=sgd.orava@gmail.com X-Falkon-Version=0.2.0 X-Falkon-Settings=true diff --git a/src/scripts/runaction/metadata.desktop b/src/scripts/runaction/metadata.desktop index be904445..70768719 100644 --- a/src/scripts/runaction/metadata.desktop +++ b/src/scripts/runaction/metadata.desktop @@ -1,55 +1,56 @@ [Desktop Entry] Name=Run Action Name[ca]=Executa una acció Name[ca@valencia]=Executa una acció Name[cs]=Spustit činnost Name[da]=Kør handling Name[en_GB]=Run Action Name[es]=Ejecutar acción Name[fi]=Suorita toiminto Name[fr]=Lancer une action Name[gl]=Executar a acción Name[id]=Run Action Name[it]=Esegui azione Name[nl]=Uitvoeractie Name[nn]=Køyr handling Name[pl]=Wykonaj działanie Name[pt]=Executar uma Acção Name[pt_BR]=Executar ação Name[sk]=Spustiť akciu Name[sv]=Utför åtgärd Name[uk]=Виконати дію Name[x-test]=xxRun Actionxx Name[zh_CN]=执行动作 Name[zh_TW]=執行動作 Comment=Run various actions on sites Comment[ca]=Executa diverses accions als llocs Comment[ca@valencia]=Executa diverses accions als llocs Comment[cs]=Spouštět různé činnosti na stránkách Comment[da]=Kør diverse handlinger på steder Comment[en_GB]=Run various actions on sites Comment[es]=Ejecutar varias acciones en sitios Comment[fi]=Suorita eri toimintoja sivustoilla Comment[fr]=Lancement d'actions diverses sur des sites Web Comment[gl]=Executar varias accións en sitios Comment[id]=Jalankan beberapa aksi pada situs Comment[it]=Esegue numerose azioni nei siti Comment[nl]=Verschillende acties op sites uitvoeren Comment[nn]=Køyr ymse handlingar på nettstadar Comment[pl]=Wykonuj różne działania na stronach Comment[pt]=Executar várias acções sobre as páginas Comment[pt_BR]=Executar várias ações nos sites Comment[sk]=Spustiť rôzne akcie na stránkach Comment[sv]=Utför diverse åtgärder på webbplatser Comment[uk]=Виконання різноманітних дій на сайтах Comment[x-test]=xxRun various actions on sitesxx Comment[zh_CN]=在站点上运行各种动作 Comment[zh_TW]=在網站上執行各種動作 Icon=icon.svg Type=Service +X-Falkon-Type=Extension/Python X-Falkon-Author=David Rosca X-Falkon-Email=nowrep@gmail.com X-Falkon-Version=0.1.0 X-Falkon-Settings=true diff --git a/themes/chrome/metadata.desktop b/themes/chrome/metadata.desktop index 41344559..cbe6dbd2 100644 --- a/themes/chrome/metadata.desktop +++ b/themes/chrome/metadata.desktop @@ -1,55 +1,56 @@ [Desktop Entry] Name=Chrome Name[ca]=Chrome Name[ca@valencia]=Chrome Name[cs]=Chrome Name[da]=Chrome Name[de]=Chrome Name[en_GB]=Chrome Name[es]=Chrome Name[fi]=Chrome Name[fr]=Chrome Name[gl]=Chrome Name[id]=Chrome Name[it]=Chrome Name[nl]=Chrome Name[nn]=Chrome Name[pl]=Chrome Name[pt]=Chrome Name[pt_BR]=Chrome Name[sk]=Chrome Name[sv]=Chrome Name[uk]=Хромування Name[x-test]=xxChromexx Name[zh_CN]=Chrome Name[zh_TW]=Chrome Comment=Chrome like theme for Falkon based on Firefox Chromifox theme Comment[ca]=Tema semblant al Chrome pel Falkon, basat en el tema Chromifox del Firefox Comment[ca@valencia]=Tema semblant al Chrome pel Falkon, basat en el tema Chromifox del Firefox Comment[cs]=Motiv pro Falkon podobný Chrome založený na motivu Firefox Chromifox Comment[da]=Chrome-lignende tema til Falkon baseret på Firefox Chromifox-tema Comment[de]=Chrome ähnliches Design für Falkon auf der Basis des Chromifox-Designs von Firefox Comment[en_GB]=Chrome like theme for Falkon based on Firefox Chromifox theme Comment[es]=Tema estilo Chrome para Falkon basado en el tema Firefox Chromifox Comment[fi]=Firefoxin Chromifox-teemaan pohjautuva Chromen kaltainen Falkon-teema Comment[fr]=Thème Chrome pour Falkon inspiré du thème Chromifox de Firefox Comment[gl]=Tema parecido a Chrome para Falkon baseado no tema Chromifox de Firefox Comment[id]=Tema seperti Chrome untuk Falkon berdasarkan pada tema Firefox Chromifox Comment[it]=Tema di tipo Chrome per Falkon, basato sul tema Chromifox di Firefox Comment[nl]=Op Chrome lijkend thema voor Falkon gebaseerd op Firefox Chromifox thema Comment[nn]=Chrome-liknande tema for Falkon, basert på Firefox Chromifox-temaet Comment[pl]=Wygląd przypominający Chrome dla Falkona oparty na wyglądzie Firefox Chromifox Comment[pt]=Tema semelhante ao Chrome para o Falkon, baseado no tema Chromifox do Firefox Comment[pt_BR]=Tema parecido com o Chrome para o Falkon, baseado no tema Chromifox do Firefox Comment[sk]=Chrome téma pre Falkon postavená na Firefox Chromifox téme Comment[sv]=Chrome-liknande tema för Falkon baserat på Firefox Chromifox-tema Comment[uk]=Chrome-подібна тема для Falkon, заснована на темі Chromifox для Firefox Comment[x-test]=xxChrome like theme for Falkon based on Firefox Chromifox themexx Comment[zh_CN]=基于火狐 Chromifox 主题的类似于 Chrome 的 Falkon 主题 Icon=theme.png Type=Service +X-Falkon-Type=Theme X-Falkon-Author=David Rosca X-Falkon-Email=nowrep@gmail.com X-Falkon-License=license.txt diff --git a/themes/linux/metadata.desktop b/themes/linux/metadata.desktop index 2ec5ac33..2cf30f3e 100644 --- a/themes/linux/metadata.desktop +++ b/themes/linux/metadata.desktop @@ -1,55 +1,56 @@ [Desktop Entry] Name=Linux Name[ca]=Linux Name[ca@valencia]=Linux Name[cs]=Linux Name[da]=Linux Name[de]=Linux Name[en_GB]=Linux Name[es]=Linux Name[fi]=Linux Name[fr]=Linux Name[gl]=Linux Name[id]=Linux Name[it]=Linux Name[nl]=Linux Name[nn]=Linux Name[pl]=Linux Name[pt]=Linux Name[pt_BR]=Linux Name[sk]=Linux Name[sv]=Linux Name[uk]=Linux Name[x-test]=xxLinuxxx Name[zh_CN]=Linux Name[zh_TW]=Linux Comment=Default simple theme for Linux using native widget style and some basic icons from desktop icon set Comment[ca]=Tema senzill predeterminat per a Linux, usant l'estil natiu dels estris i diverses icones bàsiques del conjunt d'icones de l'escriptori Comment[ca@valencia]=Tema senzill predeterminat per a Linux, usant l'estil natiu dels estris i diverses icones bàsiques del conjunt d'icones de l'escriptori Comment[cs]=Výchozí jednoduchý motiv pro Linux používající nativní styl widgetu style a nějaké základní ikony ze sady ikon pracovní plochy Comment[da]=Standard simpelt tema til Linux som bruger systemets widgetstil og nogle basisikoner fra skrivebordets ikonsæt Comment[de]=Als Vorgabe ein einfaches Design für Linux, das den nativen Stil der Bedienelemente und grundlegende Symbole der Arbeitsfläche verwendet Comment[en_GB]=Default simple theme for Linux using native widget style and some basic icons from desktop icon set Comment[es]=Tema sencillo predeterminado para Linux usando un estilo de controles nativo y algunos iconos básicos desde el conjunto de iconos de escritorio Comment[fi]=Yksinkertainen natiivia elementtityyliä käyttävä Linux-teema sekä joitakin kuvakkeita työpöytäkuvakejoukosta Comment[fr]=Thème simple par défaut pour Linux utilisant les styles de composants graphiques natifs et quelques icônes simples du jeu d'icônes du bureau Comment[gl]=Tema predeterminado simple para Linux que usa o estilo de trebellos nativo e algunhas iconas básicas do grupo de iconas do escritorio Comment[id]=Tema sederhana baku untuk Linux menggunakan gaya widget bawaan dan beberapa ikon dasar dari set ikon desktop Comment[it]=Semplice tema predefinito per Linux, che usa lo stile nativo degli oggetti ed alcune icone di base dall'insieme di icone del desktop Comment[nl]=Standaard eenvoudig thema voor Linux met inheemse widgetstijl en enige basis pictogrammen uit set pictogrammen voor bureaublad Comment[nn]=Standardtema for Linux, som brukar systemutsjånad på skjermelement og nokre ikon frå skrivebordsikontemaet Comment[pl]=Domyślny prosty wygląd dla Linuksa używający natywnego wyglądu elementów interfejsu i podstawowych ikon z zestawu ikon dla pulpitu Comment[pt]=Tema predefinido simples para o Linux, usando o estilo gráfico nativo e alguns ícones básicos do conjunto de ícones do ambiente de trabalho Comment[pt_BR]=Tema simples padrão para o Linux usando estilo de widgets nativos e alguns ícones básicos do conjunto de ícones da área de trabalho Comment[sk]=Predvolená jednoduchá téma pre Linux používajúca natívny štýl widgetov a jednoduché ikony z o štýlu ikon plochy Comment[sv]=Enkelt standardtema för Linux som använder inbyggd komponentstil och några grundläggande ikoner från skrivbordets ikonuppsättning Comment[uk]=Типова проста тема для Linux із використанням природного стилю віджетів і декількох базових піктограм із набору піктограм стільниці Comment[x-test]=xxDefault simple theme for Linux using native widget style and some basic icons from desktop icon setxx Comment[zh_CN]=给 Linux 设计的使用原生控件的默认简易主题,图标来自于桌面图标集 Icon=theme.png Type=Service +X-Falkon-Type=Theme X-Falkon-Author=David Rosca X-Falkon-Email=nowrep@gmail.com X-Falkon-License=GPLv3 diff --git a/themes/mac/metadata.desktop b/themes/mac/metadata.desktop index 1f61db92..cb9fc01c 100644 --- a/themes/mac/metadata.desktop +++ b/themes/mac/metadata.desktop @@ -1,56 +1,57 @@ [Desktop Entry] Name=Mac Name[ca]=Mac Name[ca@valencia]=Mac Name[cs]=Mac Name[da]=Mac Name[de]=Mac Name[en_GB]=Mac Name[es]=Mac Name[fi]=Mac Name[fr]=Mac Name[gl]=Mac Name[id]=Mac Name[it]=Mac Name[nl]=Mac Name[nn]=Mac Name[pl]=Mac Name[pt]=Mac Name[pt_BR]=Mac Name[sk]=Mac Name[sv]=Mac Name[uk]=Mac Name[x-test]=xxMacxx Name[zh_CN]=Mac Name[zh_TW]=Mac Comment=Mac like theme for Falkon based on Firefox Mac OS X theme Comment[ca]=Tema semblant al Mac pel Falkon, basat en el tema Mac OS X del Firefox Comment[ca@valencia]=Tema semblant al Mac pel Falkon, basat en el tema Mac OS X del Firefox Comment[cs]=Motiv pro Falkon podobný Macu založený na motivu Firefox Mac OS X Comment[da]=Mac-lignende tema til Falkon baseret på Firefox Mac OS X-tema Comment[de]=Mac ähnliches Design für Falkon auf der Basis des „Mac OS X“-Designs von Firefox Comment[en_GB]=Mac like theme for Falkon based on Firefox Mac OS X theme Comment[es]=Tema estilo Mac para Falkon basado en el tema Firefox Mac OS X Comment[fi]=Firefoxin Mac OS X -teemaan pohjautuva Macin kaltainen Falkon-teema Comment[fr]=Thème Mac pour Falkon inspiré du thème Mac OS X de Firefox Comment[gl]=Tema parecido a Mac para Falkon baseado no tema de Mac OS X de Firefox Comment[id]=Tema seperti Mac untuk Falkon berdasarkan pada tema Firefox Mac OS X Comment[it]=Tema di tipo Mac per Falkon, basato sul tema Mac OS X di Firefox Comment[nl]=Op Mac lijkend thema voor Falkon gebaseerd op Firefox Mac OS X thema Comment[nn]=Mac-liknande tema for Falkon, basert på Firefox Mac OS X-temaet Comment[pl]=Wygląd przypominający Maka dla Falkona oparty na wyglądzie Firefox Mac OS X Comment[pt]=Tema semelhante ao Mac para o Falkon, baseado no tema Mac OS X do Firefox Comment[pt_BR]=Tema estilo Mac para o Falkon baseado no tema Mac OS X do Firefox Comment[sk]=Mac téma pre Falkon postavená na Firefox Mac OS X téme Comment[sv]=Mac-liknande tema för Falkon baserat på Firefox Mac OS X-tema Comment[uk]=Mac-подібна тема для Falkon, заснована на темі Mac OS X для Firefox Comment[x-test]=xxMac like theme for Falkon based on Firefox Mac OS X themexx Comment[zh_CN]=基于 Firefox Mac OS X 主题的类 Mac 主题 Comment[zh_TW]=適用於 Falkon,基於 Firefox Mac OS X 主題的類 Mac 主題 Icon=theme.png Type=Service +X-Falkon-Type=Theme X-Falkon-Author=David Rosca X-Falkon-Email=nowrep@gmail.com X-Falkon-License=license.txt diff --git a/themes/windows/metadata.desktop b/themes/windows/metadata.desktop index 8f9e12bd..4ec0d08c 100644 --- a/themes/windows/metadata.desktop +++ b/themes/windows/metadata.desktop @@ -1,56 +1,57 @@ [Desktop Entry] Name=Windows Name[ca]=Windows Name[ca@valencia]=Windows Name[cs]=Windows Name[da]=Windows Name[de]=Windows Name[en_GB]=Windows Name[es]=Windows Name[fi]=Windows Name[fr]=Windows Name[gl]=Xanelas Name[id]=Windows Name[it]=Windows Name[nl]=Windows Name[nn]=Windows Name[pl]=Windows Name[pt]=Windows Name[pt_BR]=Windows Name[sk]=Windows Name[sv]=Windows Name[uk]=Windows Name[x-test]=xxWindowsxx Name[zh_CN]=Windows Name[zh_TW]=Windows Comment=Windows like theme based on Material design Comment[ca]=Tema semblant al Windows, basat en el «Material design» Comment[ca@valencia]=Tema semblant al Windows, basat en el «Material design» Comment[cs]=Motiv podobný Windows založený na designu Material Comment[da]=Windows-lignende tema baseret på Material design Comment[de]=Windows ähnliches Design auf der Basis des Designs Material Comment[en_GB]=Windows like theme based on Material design Comment[es]=Tema estilo Windows basado en el diseño Material Comment[fi]=Material-designiin pohjautuva Windowsin kaltainen teema Comment[fr]=Thème Windows inspiré par Material Comment[gl]=Tema parecido a Windows baseado no deseño material Comment[id]=Tema seperti Windows berdasarkan pada desain Material Comment[it]=Tema di tipo Windows, basato sul design Material Comment[nl]=Op Windows lijkend thema gebaseerd op Material ontwerp Comment[nn]=Windows-liknande tema, basert på Material-stilen Comment[pl]=Wygląd przypominający Windowsa dla Falkona oparty na wyglądzie Material Comment[pt]=Tema semelhante ao Windows, baseado no 'material design' Comment[pt_BR]=Tema estilo Windows baseado no Material design Comment[sk]=Téma ako Windows postavená na Material Dizajne Comment[sv]=Windows-liknande tema baserat på Material design Comment[uk]=Windows-подібна тема, заснована на дизайні Material Comment[x-test]=xxWindows like theme based on Material designxx Comment[zh_CN]=基于材料设置的类 Windows 主题 Comment[zh_TW]=基於質感設計的類 Windows 主題 Icon=theme.png Type=Service +X-Falkon-Type=Theme X-Falkon-Author=David Rosca X-Falkon-Email=nowrep@gmail.com X-Falkon-License=GPLv3