Improve plugin settings

This commit is contained in:
Mm2PL 2023-02-01 00:03:09 +01:00
parent 1616db56b1
commit 03ad993ab4
No known key found for this signature in database
GPG key ID: 94AC9B80EFA15ED9
5 changed files with 185 additions and 32 deletions

View file

@ -26,9 +26,37 @@ namespace chatterino {
void PluginController::initialize(Settings &settings, Paths &paths) 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; qCDebug(chatterinoLua) << "loading plugins from " << dir;
for (const auto &info : dir.entryInfoList()) for (const auto &info : dir.entryInfoList())
{ {
@ -110,6 +138,15 @@ void PluginController::load(QFileInfo index, QDir pluginDir, PluginMeta meta)
auto pluginName = pluginDir.dirName(); auto pluginName = pluginDir.dirName();
auto plugin = std::make_unique<Plugin>(pluginName, l, meta, pluginDir); auto plugin = std::make_unique<Plugin>(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)}); this->plugins_.insert({pluginName, std::move(plugin)});
int err = luaL_dofile(l, index.absoluteFilePath().toStdString().c_str()); int err = luaL_dofile(l, index.absoluteFilePath().toStdString().c_str());
@ -130,14 +167,22 @@ bool PluginController::reload(const QString &codename)
{ {
return false; 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) for (const auto &[cmd, _] : it->second->ownedCommands)
{ {
getApp()->commands->unregisterPluginCommand(cmd); getApp()->commands->unregisterPluginCommand(cmd);
} }
QDir loadDir = it->second->loadDirectory_; it->second->ownedCommands.clear();
this->plugins_.erase(codename); if (this->isEnabled(codename))
this->tryLoadFromDir(loadDir); {
QDir loadDir = it->second->loadDirectory_;
this->plugins_.erase(codename);
this->tryLoadFromDir(loadDir);
}
return true; return true;
} }
@ -291,4 +336,15 @@ void PluginController::loadChatterinoLib(lua_State *L)
luaL_setfuncs(L, C2LIB, 0); 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 }; // namespace chatterino

View file

@ -12,7 +12,9 @@
#include <QJsonArray> #include <QJsonArray>
#include <QJsonObject> #include <QJsonObject>
#include <QString> #include <QString>
#include <semver/semver.hpp>
#include <algorithm>
#include <map> #include <map>
#include <memory> #include <memory>
#include <utility> #include <utility>
@ -27,16 +29,33 @@ struct PluginMeta {
QString description; QString description;
QString authors; QString authors;
QString homepage; QString homepage;
QString license;
semver::version version;
std::vector<QString> tags; std::vector<QString> tags;
std::set<QString> libraryPermissions; std::set<QString> libraryPermissions;
explicit PluginMeta(const QJsonObject &obj) 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()) , description(obj.value("description").toString())
, authors(obj.value("authors").toString()) , authors(obj.value("authors").toString())
, homepage(obj.value("homepage").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()) for (const auto &t : obj.value("tags").toArray())
{ {
this->tags.push_back(t.toString()); this->tags.push_back(t.toString());
@ -53,6 +72,7 @@ class Plugin
public: public:
QString codename; QString codename;
PluginMeta meta; PluginMeta meta;
bool isDupeName{};
Plugin(QString codename, lua_State *state, PluginMeta meta, Plugin(QString codename, lua_State *state, PluginMeta meta,
const QDir &loadDirectory) const QDir &loadDirectory)
@ -133,8 +153,10 @@ public:
} }
bool reload(const QString &codename); bool reload(const QString &codename);
bool isEnabled(const QString &codename);
private: private:
void actuallyInitialize();
void load(QFileInfo index, QDir pluginDir, PluginMeta meta); void load(QFileInfo index, QDir pluginDir, PluginMeta meta);
void loadChatterinoLib(lua_State *l); void loadChatterinoLib(lua_State *l);

View file

@ -520,6 +520,10 @@ public:
{"d", 1}, {"d", 1},
{"w", 1}}}; {"w", 1}}};
BoolSetting enableAnyPlugins = {"/plugins/load", false};
ChatterinoSetting<std::vector<QString>> enabledPlugins = {
"/plugins/enabled", {}};
private: private:
void updateModerationActions(); void updateModerationActions();
}; };

View file

@ -8,50 +8,84 @@
#include "util/LayoutCreator.hpp" #include "util/LayoutCreator.hpp"
#include "util/RemoveScrollAreaBackground.hpp" #include "util/RemoveScrollAreaBackground.hpp"
#include <QCheckBox>
#include <QFormLayout> #include <QFormLayout>
#include <QGroupBox> #include <QGroupBox>
#include <QLabel> #include <QLabel>
#include <qobject.h>
#include <QObject>
#include <QPushButton> #include <QPushButton>
#include <qwidget.h> #include <QWidget>
namespace chatterino { namespace chatterino {
PluginsPage::PluginsPage() PluginsPage::PluginsPage()
: scrollArea_(nullptr) : scrollAreaWidget_(nullptr)
, dataFrame_(nullptr)
{ {
qDebug() << "plugins page created";
LayoutCreator<PluginsPage> layoutCreator(this); LayoutCreator<PluginsPage> layoutCreator(this);
this->scrollArea_ = layoutCreator.emplace<QScrollArea>(); auto scrollArea = layoutCreator.emplace<QScrollArea>();
auto widget = scrollArea.emplaceScrollAreaWidget();
this->scrollAreaWidget_ = widget;
removeScrollAreaBackground(scrollArea.getElement(), widget.getElement());
auto layout = widget.setLayoutType<QVBoxLayout>();
{
auto group = layout.emplace<QGroupBox>("General plugin settings");
this->generalGroup = group.getElement();
auto groupLayout = group.setLayoutType<QFormLayout>();
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(); this->rebuildContent();
} }
void PluginsPage::rebuildContent() void PluginsPage::rebuildContent()
{ {
auto widget = this->scrollArea_.emplaceScrollAreaWidget(); if (this->dataFrame_ != nullptr)
removeScrollAreaBackground(this->scrollArea_.getElement(), {
widget.getElement()); this->dataFrame_->deleteLater();
this->dataFrame_ = nullptr;
auto layout = widget.setLayoutType<QVBoxLayout>(); }
auto group = layout.emplace<QGroupBox>("Plugins"); auto frame = LayoutCreator<QFrame>(new QFrame(this));
auto groupLayout = group.setLayoutType<QFormLayout>(); this->dataFrame_ = frame.getElement();
this->scrollAreaWidget_.append(this->dataFrame_);
auto *description = new QLabel( auto layout = frame.setLayoutType<QVBoxLayout>();
"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);
for (const auto &[codename, plugin] : getApp()->plugins->plugins()) for (const auto &[codename, plugin] : getApp()->plugins->plugins())
{ {
auto plgroup = groupLayout.emplace<QGroupBox>(plugin->meta.name); auto headerText = QString("%1 (%2)").arg(
plugin->meta.name,
QString::fromStdString(plugin->meta.version.to_string()));
if (plugin->isDupeName)
{
// add ", from <folder name>)" in place of ")"
headerText.chop(1);
headerText += ", from " + codename + ")";
}
auto plgroup = layout.emplace<QGroupBox>(headerText);
auto pl = plgroup.setLayoutType<QFormLayout>(); auto pl = plgroup.setLayoutType<QFormLayout>();
auto *descrText = new QLabel(plugin->meta.description); auto *descrText = new QLabel(plugin->meta.description);
//descrText->setTextFormat(Qt::TextFormat::MarkdownText);
descrText->setWordWrap(true); descrText->setWordWrap(true);
descrText->setStyleSheet("color: #bbb"); descrText->setStyleSheet("color: #bbb");
pl->addRow(descrText); pl->addRow(descrText);
@ -60,6 +94,7 @@ void PluginsPage::rebuildContent()
homepage->setOpenExternalLinks(true); homepage->setOpenExternalLinks(true);
pl->addRow("Homepage", homepage); pl->addRow("Homepage", homepage);
pl->addRow("License", new QLabel(plugin->meta.license));
QString libString; QString libString;
bool hasDangerous = false; bool hasDangerous = false;
@ -99,6 +134,32 @@ void PluginsPage::rebuildContent()
} }
pl->addRow("Commands", new QLabel(cmds)); 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<QString> 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"); auto *reload = new QPushButton("Reload");
QObject::connect(reload, &QPushButton::pressed, QObject::connect(reload, &QPushButton::pressed,
[name = codename, this]() { [name = codename, this]() {
@ -108,4 +169,5 @@ void PluginsPage::rebuildContent()
pl->addRow(reload); pl->addRow(reload);
} }
} }
} // namespace chatterino } // namespace chatterino

View file

@ -2,8 +2,11 @@
#include "util/LayoutCreator.hpp" #include "util/LayoutCreator.hpp"
#include "widgets/settingspages/SettingsPage.hpp" #include "widgets/settingspages/SettingsPage.hpp"
#include <QDebug>
#include <QFormLayout> #include <QFormLayout>
#include <QGroupBox> #include <QGroupBox>
#include <QWidget>
namespace chatterino { namespace chatterino {
class Plugin; class Plugin;
@ -11,10 +14,16 @@ class PluginsPage : public SettingsPage
{ {
public: public:
PluginsPage(); PluginsPage();
~PluginsPage() override
{
qDebug() << "plugins page deleted";
}
private: private:
void rebuildContent(); void rebuildContent();
LayoutCreator<QScrollArea> scrollArea_; LayoutCreator<QWidget> scrollAreaWidget_;
QGroupBox *generalGroup;
QFrame *dataFrame_;
}; };
} // namespace chatterino } // namespace chatterino