diff options
Diffstat (limited to 'src/client/addons/addons.cpp')
-rw-r--r-- | src/client/addons/addons.cpp | 235 |
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); + } + } + } +} |