summaryrefslogblamecommitdiffstats
path: root/src/client/addons/addons.cpp
blob: 43813ee3f1f7792a40597b6f9b79fa6d7a64e57f (plain) (tree)





























































                                                                                                                
                                             






































































































































                                                                                                                                    



                                                                                      










                                                                                     



















                                                                    
#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);
		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);
			}
		}
	}
}