summaryrefslogtreecommitdiffstats
path: root/src/client/addons/addons.cpp
diff options
context:
space:
mode:
authorSimon Rettberg2018-09-06 11:32:54 +0200
committerSimon Rettberg2018-09-06 11:32:54 +0200
commitcb93788148785758be72fcb2afe2039e7a882c53 (patch)
treec2095f4ac5995d53e62bd9e247345a1b60c3bf5a /src/client/addons/addons.cpp
parent[client] Fix memory leak (diff)
downloadpvs2-cb93788148785758be72fcb2afe2039e7a882c53.tar.gz
pvs2-cb93788148785758be72fcb2afe2039e7a882c53.tar.xz
pvs2-cb93788148785758be72fcb2afe2039e7a882c53.zip
[client] New addon system for buttons and menu
Instead of integrating workspace switching and screen locking directly into PVS2, introduce an addon system to insert new functionality into the toolbar, as buttons or menu entries.
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);
+ }
+ }
+ }
+}