#include "addons.h" #include #include #include #include #include #include #include #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 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> 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 &buttons, QList &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); setting.setIniCodec("UTF-8"); 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); addon->button->setVisible(newValue); if (p != nullptr && wasVisible != newValue) { // Visibility changed -- adjust size of toolbar int size = (addon->button->width() + 2) * (newValue ? 1 : -1); p->setFixedWidth(p->width() + size); } } else { addon->menu->setVisible(newValue); } } static void handleAddonOutput(Addon* addon, QRegularExpressionMatchIterator &matches) { while (matches.hasNext()) { auto m = matches.next(); 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); } } } }