diff --git a/src/controllers/plugins/PluginController.cpp b/src/controllers/plugins/PluginController.cpp index 7cea33da6..509929cb4 100644 --- a/src/controllers/plugins/PluginController.cpp +++ b/src/controllers/plugins/PluginController.cpp @@ -26,9 +26,37 @@ namespace chatterino { void PluginController::initialize(Settings &settings, Paths &paths) { - (void)(settings); + (void)paths; - auto dir = QDir(paths.pluginsDirectory); + settings.enableAnyPlugins.connect([this](bool enabled) { + if (enabled) + { + this->actuallyInitialize(); + } + else + { + // uninitialize plugins + for (const auto &[codename, plugin] : this->plugins_) + { + this->reload(codename); + } + // can safely delete them now, after lua freed its stuff + this->plugins_.clear(); + } + }); + this->actuallyInitialize(); +} + +// this function exists to allow for connecting to enableAnyPlugins option +void PluginController::actuallyInitialize() +{ + if (!getSettings()->enableAnyPlugins) + { + qCDebug(chatterinoLua) + << "Loading plugins disabled via Setting, skipping"; + return; + } + auto dir = QDir(getPaths()->pluginsDirectory); qCDebug(chatterinoLua) << "loading plugins from " << dir; for (const auto &info : dir.entryInfoList()) { @@ -110,6 +138,15 @@ void PluginController::load(QFileInfo index, QDir pluginDir, PluginMeta meta) auto pluginName = pluginDir.dirName(); auto plugin = std::make_unique(pluginName, l, meta, pluginDir); + for (const auto &[codename, other] : this->plugins_) + { + if (other->meta.name == meta.name) + { + plugin->isDupeName = true; + other->isDupeName = true; + } + } + this->plugins_.insert({pluginName, std::move(plugin)}); int err = luaL_dofile(l, index.absoluteFilePath().toStdString().c_str()); @@ -130,14 +167,22 @@ bool PluginController::reload(const QString &codename) { return false; } - lua_close(it->second->state_); + if (it->second->state_ != nullptr) + { + lua_close(it->second->state_); + it->second->state_ = nullptr; + } for (const auto &[cmd, _] : it->second->ownedCommands) { getApp()->commands->unregisterPluginCommand(cmd); } - QDir loadDir = it->second->loadDirectory_; - this->plugins_.erase(codename); - this->tryLoadFromDir(loadDir); + it->second->ownedCommands.clear(); + if (this->isEnabled(codename)) + { + QDir loadDir = it->second->loadDirectory_; + this->plugins_.erase(codename); + this->tryLoadFromDir(loadDir); + } return true; } @@ -291,4 +336,15 @@ void PluginController::loadChatterinoLib(lua_State *L) luaL_setfuncs(L, C2LIB, 0); } +bool PluginController::isEnabled(const QString &codename) +{ + if (!getSettings()->enableAnyPlugins) + { + return false; + } + auto vec = getSettings()->enabledPlugins.getValue(); + auto it = std::find(vec.begin(), vec.end(), codename); + return it != vec.end(); +} + }; // namespace chatterino diff --git a/src/controllers/plugins/PluginController.hpp b/src/controllers/plugins/PluginController.hpp index c9e9757fe..fa546ded8 100644 --- a/src/controllers/plugins/PluginController.hpp +++ b/src/controllers/plugins/PluginController.hpp @@ -12,7 +12,9 @@ #include #include #include +#include +#include #include #include #include @@ -27,16 +29,33 @@ struct PluginMeta { QString description; QString authors; QString homepage; + + QString license; + semver::version version; + std::vector tags; std::set libraryPermissions; explicit PluginMeta(const QJsonObject &obj) - : name(obj.value("name").toString()) + : name(obj.value("name").toString("A Plugin with no name")) , description(obj.value("description").toString()) , authors(obj.value("authors").toString()) , homepage(obj.value("homepage").toString()) + , license(obj.value("license").toString("[unknown]")) + { + auto v = semver::from_string_noexcept( + obj.value("version").toString().toStdString()); + if (v.has_value()) + { + this->version = v.value(); + } + else + { + this->version = semver::version(0, 0, 0); + description.append("\nWarning: invalid version"); + } for (const auto &t : obj.value("tags").toArray()) { this->tags.push_back(t.toString()); @@ -53,6 +72,7 @@ class Plugin public: QString codename; PluginMeta meta; + bool isDupeName{}; Plugin(QString codename, lua_State *state, PluginMeta meta, const QDir &loadDirectory) @@ -133,8 +153,10 @@ public: } bool reload(const QString &codename); + bool isEnabled(const QString &codename); private: + void actuallyInitialize(); void load(QFileInfo index, QDir pluginDir, PluginMeta meta); void loadChatterinoLib(lua_State *l); diff --git a/src/singletons/Settings.hpp b/src/singletons/Settings.hpp index cd10ce0ee..289faeeba 100644 --- a/src/singletons/Settings.hpp +++ b/src/singletons/Settings.hpp @@ -520,6 +520,10 @@ public: {"d", 1}, {"w", 1}}}; + BoolSetting enableAnyPlugins = {"/plugins/load", false}; + ChatterinoSetting> enabledPlugins = { + "/plugins/enabled", {}}; + private: void updateModerationActions(); }; diff --git a/src/widgets/settingspages/PluginsPage.cpp b/src/widgets/settingspages/PluginsPage.cpp index 413bb2278..60aaebd68 100644 --- a/src/widgets/settingspages/PluginsPage.cpp +++ b/src/widgets/settingspages/PluginsPage.cpp @@ -8,50 +8,84 @@ #include "util/LayoutCreator.hpp" #include "util/RemoveScrollAreaBackground.hpp" +#include #include #include #include +#include +#include #include -#include +#include namespace chatterino { PluginsPage::PluginsPage() - : scrollArea_(nullptr) + : scrollAreaWidget_(nullptr) + , dataFrame_(nullptr) { + qDebug() << "plugins page created"; LayoutCreator layoutCreator(this); - this->scrollArea_ = layoutCreator.emplace(); + auto scrollArea = layoutCreator.emplace(); + + auto widget = scrollArea.emplaceScrollAreaWidget(); + this->scrollAreaWidget_ = widget; + removeScrollAreaBackground(scrollArea.getElement(), widget.getElement()); + + auto layout = widget.setLayoutType(); + + { + auto group = layout.emplace("General plugin settings"); + this->generalGroup = group.getElement(); + auto groupLayout = group.setLayoutType(); + auto *description = new QLabel( + "You can load plugins by putting them into " + + formatRichNamedLink("file:///" + getPaths()->pluginsDirectory, + "the Plugins directory") + + ". Each one is a new directory."); + description->setOpenExternalLinks(true); + description->setWordWrap(true); + description->setStyleSheet("color: #bbb"); + groupLayout->addRow(description); + + auto *box = this->createCheckBox("Enable plugins", + getSettings()->enableAnyPlugins); + QObject::connect(box, &QCheckBox::released, [this, box]() { + //using namespace std::chrono_literals; + //QTimer::singleShot(10000ms, [this]() { + this->rebuildContent(); + //}); + }); + groupLayout->addRow(box); + } this->rebuildContent(); } void PluginsPage::rebuildContent() { - auto widget = this->scrollArea_.emplaceScrollAreaWidget(); - removeScrollAreaBackground(this->scrollArea_.getElement(), - widget.getElement()); - - auto layout = widget.setLayoutType(); - auto group = layout.emplace("Plugins"); - auto groupLayout = group.setLayoutType(); - - auto *description = new QLabel( - "You can load plugins by putting them into " + - formatRichNamedLink("file:///" + getPaths()->pluginsDirectory, - "the Plugins directory") + - ". Each one is a " - "new directory."); - description->setOpenExternalLinks(true); - description->setWordWrap(true); - description->setStyleSheet("color: #bbb"); - groupLayout->addRow(description); - + if (this->dataFrame_ != nullptr) + { + this->dataFrame_->deleteLater(); + this->dataFrame_ = nullptr; + } + auto frame = LayoutCreator(new QFrame(this)); + this->dataFrame_ = frame.getElement(); + this->scrollAreaWidget_.append(this->dataFrame_); + auto layout = frame.setLayoutType(); for (const auto &[codename, plugin] : getApp()->plugins->plugins()) { - auto plgroup = groupLayout.emplace(plugin->meta.name); + auto headerText = QString("%1 (%2)").arg( + plugin->meta.name, + QString::fromStdString(plugin->meta.version.to_string())); + if (plugin->isDupeName) + { + // add ", from )" in place of ")" + headerText.chop(1); + headerText += ", from " + codename + ")"; + } + auto plgroup = layout.emplace(headerText); auto pl = plgroup.setLayoutType(); auto *descrText = new QLabel(plugin->meta.description); - //descrText->setTextFormat(Qt::TextFormat::MarkdownText); descrText->setWordWrap(true); descrText->setStyleSheet("color: #bbb"); pl->addRow(descrText); @@ -60,6 +94,7 @@ void PluginsPage::rebuildContent() homepage->setOpenExternalLinks(true); pl->addRow("Homepage", homepage); + pl->addRow("License", new QLabel(plugin->meta.license)); QString libString; bool hasDangerous = false; @@ -99,6 +134,32 @@ void PluginsPage::rebuildContent() } pl->addRow("Commands", new QLabel(cmds)); + QString enableOrDisableStr = "Enable"; + if (getApp()->plugins->isEnabled(codename)) + { + enableOrDisableStr = "Disable"; + } + + auto *enableDisable = new QPushButton(enableOrDisableStr); + QObject::connect( + enableDisable, &QPushButton::pressed, [name = codename, this]() { + std::vector val = + getSettings()->enabledPlugins.getValue(); + if (getApp()->plugins->isEnabled(name)) + { + val.erase(std::remove(val.begin(), val.end(), name), + val.end()); + } + else + { + val.push_back(name); + } + getSettings()->enabledPlugins.setValue(val); + getApp()->plugins->reload(name); + this->rebuildContent(); + }); + pl->addRow(enableDisable); + auto *reload = new QPushButton("Reload"); QObject::connect(reload, &QPushButton::pressed, [name = codename, this]() { @@ -108,4 +169,5 @@ void PluginsPage::rebuildContent() pl->addRow(reload); } } + } // namespace chatterino diff --git a/src/widgets/settingspages/PluginsPage.hpp b/src/widgets/settingspages/PluginsPage.hpp index 27d664dce..7c7f07e9d 100644 --- a/src/widgets/settingspages/PluginsPage.hpp +++ b/src/widgets/settingspages/PluginsPage.hpp @@ -2,8 +2,11 @@ #include "util/LayoutCreator.hpp" #include "widgets/settingspages/SettingsPage.hpp" +#include #include #include +#include + namespace chatterino { class Plugin; @@ -11,10 +14,16 @@ class PluginsPage : public SettingsPage { public: PluginsPage(); + ~PluginsPage() override + { + qDebug() << "plugins page deleted"; + } private: void rebuildContent(); - LayoutCreator scrollArea_; + LayoutCreator scrollAreaWidget_; + QGroupBox *generalGroup; + QFrame *dataFrame_; }; } // namespace chatterino