summaryrefslogtreecommitdiffstats
path: root/src/client/addons/addons.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/addons/addons.cpp')
-rw-r--r--src/client/addons/addons.cpp235
1 files changed, 235 insertions, 0 deletions
diff --git a/src/client/addons/addons.cpp b/src/client/addons/addons.cpp
new file mode 100644
index 0000000..9f4a99f
--- /dev/null
+++ b/src/client/addons/addons.cpp
@@ -0,0 +1,235 @@
+#include "addons.h"
+
+#include <QDir>
+#include <QSettings>
+#include <QPushButton>
+#include <QAction>
+#include <QProcess>
+#include <QtDebug>
+#include <QRegularExpression>
+
+#define MKSTR(x) static const QString s_ ## x(#x)
+
+MKSTR(EVENT);
+MKSTR(ADDRESS);
+MKSTR(ISLOCAL);
+MKSTR(CHECKED);
+MKSTR(ENABLED);
+MKSTR(VISIBLE);
+MKSTR(connected);
+MKSTR(disconnected);
+MKSTR(init);
+MKSTR(clicked);
+MKSTR(true);
+MKSTR(false);
+
+static const QSize ICON_SIZE(20, 20);
+
+QList<Addon*> AddonManager::_addons;
+
+class Addon
+{
+public:
+ Addon() : button(nullptr), menu(nullptr), wantConnectInfo(false) {}
+ QPushButton *button;
+ QAction *menu;
+ QProcess process;
+ bool wantConnectInfo;
+ bool wantInit;
+ bool runAsync;
+ QList<QPair<QString, QString>> envir;
+};
+
+static inline bool toBool(const QString &string)
+{
+ return string.toLower() == s_true;
+}
+
+static void executeAddon(Addon *addon);
+
+static void setAddonVisible(Addon *addon, bool visible);
+
+static void handleAddonOutput(Addon *addon, QRegularExpressionMatchIterator &matches);
+
+void AddonManager::loadFromPath(const QString &path, QList<QPushButton*> &buttons, QList<QAction*> &menuEntries)
+{
+ QDir configDir(path);
+ QFileInfoList fileInfoList = configDir.entryInfoList(QDir::Files, QDir::Name);
+ QRegularExpression paramRegex("^([A-Z]+)=(.*)$", QRegularExpression::MultilineOption);
+
+ for (QFileInfo fileInfo : fileInfoList) {
+ QString filePath = fileInfo.absoluteFilePath();
+ QSettings setting(filePath, QSettings::IniFormat);
+ QString caption = setting.value("caption").toString(); // TODO: i18n
+ QString exec = setting.value("exec").toString();
+ QString type = setting.value("type").toString();
+ QString tooltip = setting.value("tooltip").toString();
+ QIcon icon(setting.value("icon").toString());
+ bool checkable = setting.value("checkable").toBool();
+ if (exec.isEmpty() || (caption.isEmpty() && icon.isNull())) {
+ qDebug() << "Ignoring" << filePath << "caption+icon or exec empty";
+ continue;
+ }
+ if (!QFileInfo(exec).isExecutable() || !QFileInfo(exec).isFile()) {
+ qDebug() << "Ignoring" << filePath << "since target" << exec << "doesn't exist or isn't an executable file";
+ continue;
+ }
+
+ // Alloc addon
+ Addon *addon = new Addon();
+ // Toggle/click callback
+ auto toggleFun = [=](bool value) {
+ addon->envir.append(qMakePair(s_EVENT, s_clicked));
+ addon->envir.append(qMakePair(s_CHECKED, (value ? s_true : s_false)));
+ executeAddon(addon);
+ };
+ if (type == "menu") {
+ addon->menu = new QAction(caption);
+ if (!icon.isNull()) {
+ addon->menu->setIcon(icon);
+ }
+ addon->menu->setCheckable(checkable);
+ addon->menu->setToolTip(tooltip);
+ menuEntries.append(addon->menu);
+ addon->menu->connect(addon->menu, &QAction::triggered, toggleFun);
+ } else if (type == "button") {
+ addon->button = new QPushButton(caption);
+ if (!icon.isNull()) {
+ addon->button->setIcon(icon);
+ addon->button->setIconSize(ICON_SIZE);
+ }
+ addon->button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding);
+ addon->button->setCheckable(checkable);
+ addon->button->setToolTip(tooltip);
+ buttons.append(addon->button);
+ addon->button->connect(addon->button, &QPushButton::clicked, toggleFun);
+ } else {
+ qDebug() << "Ignoring unknown addon type" << type;
+ delete addon;
+ continue;
+ }
+ _addons.append(addon);
+
+ addon->wantConnectInfo = setting.value("connection-events").toBool();
+ addon->wantInit = setting.value("init").toBool();
+ addon->runAsync = setting.value("async").toBool();
+ // Setup process
+ addon->process.setProgram(exec);
+ // Stdin for status updates
+ addon->process.connect(&addon->process, &QProcess::readyReadStandardOutput, [=]() {
+ auto lines = addon->process.readAllStandardOutput();
+ auto matches = paramRegex.globalMatch(lines);
+ handleAddonOutput(addon, matches);
+ });
+ // Stderr just for debugging
+ addon->process.connect(&addon->process, &QProcess::readyReadStandardError, [=]() {
+ qDebug() << exec << "stderr:" << QString::fromLocal8Bit(addon->process.readAllStandardError());
+ });
+ }
+}
+
+void AddonManager::initControls()
+{
+ // Call all init functions
+ for (auto addon : _addons) {
+ if (addon->wantInit) {
+ addon->envir.append(qMakePair(s_EVENT, s_init));
+ executeAddon(addon);
+ } else {
+ setAddonVisible(addon, true);
+ }
+ }
+}
+
+void AddonManager::connectEvent(bool isLocal, const QString &address)
+{
+ for (auto addon : _addons) {
+ if (!addon->wantConnectInfo)
+ continue;
+ addon->envir.append(qMakePair(s_EVENT, s_connected));
+ addon->envir.append(qMakePair(s_ADDRESS, address));
+ addon->envir.append(qMakePair(s_ISLOCAL, isLocal ? s_true : s_false));
+ executeAddon(addon);
+ }
+}
+
+void AddonManager::disconnectEvent()
+{
+ for (auto addon : _addons) {
+ if (!addon->wantConnectInfo)
+ continue;
+ addon->envir.append(qMakePair(s_EVENT, s_disconnected));
+ executeAddon(addon);
+ }
+}
+
+static void executeAddon(Addon *addon)
+{
+ // Set up environment
+ auto env = QProcessEnvironment::systemEnvironment();
+ for (auto e : addon->envir) {
+ env.insert(e.first, e.second);
+ }
+ addon->envir.clear();
+ if (!addon->runAsync) {
+ // Kill remains
+ if (addon->process.state() != QProcess::NotRunning) {
+ addon->process.waitForFinished(500);
+ }
+ if (addon->process.state() != QProcess::NotRunning) {
+ addon->process.close();
+ }
+ }
+ addon->process.setProcessEnvironment(env);
+ // Run
+ if (addon->runAsync) {
+ addon->process.startDetached(addon->process.program(), QStringList());
+ } else {
+ addon->process.start();
+ addon->process.closeWriteChannel();
+ }
+}
+
+static void setAddonVisible(Addon *addon, bool newValue)
+{
+ if (addon->button != nullptr) {
+ QWidget *p = addon->button->parentWidget();
+ bool wasVisible = p != nullptr && addon->button->isVisibleTo(p);
+ qDebug() << "Visibility" << wasVisible << "->" << newValue;
+ addon->button->setVisible(newValue);
+ if (p != nullptr && wasVisible != newValue) {
+ // Visibility changed -- adjust size of toolbar
+ int size = (addon->button->width() + 2) * (newValue ? 1 : -1);
+ qDebug() << "Adding" << size;
+ p->setFixedWidth(p->width() + size);
+ }
+ } else {
+ addon->menu->setVisible(newValue);
+ }
+}
+
+static void handleAddonOutput(Addon* addon, QRegularExpressionMatchIterator &matches)
+{
+ while (matches.hasNext()) {
+ auto m = matches.next();
+ qDebug() << "Match:" << m.captured(0);
+ auto key = m.captured(1);
+ auto val = m.captured(2);
+ bool newValue = toBool(val);
+ if (key == s_VISIBLE) {
+ setAddonVisible(addon, newValue);
+ } else if (key == s_CHECKED) {
+ if (addon->button != nullptr) {
+ addon->button->setChecked(newValue);
+ } else {
+ addon->menu->setChecked(newValue);
+ }
+ } else if (key == s_ENABLED) {
+ if (addon->button != nullptr) {
+ addon->button->setEnabled(newValue);
+ } else {
+ addon->menu->setEnabled(newValue);
+ }
+ }
+ }
+}