Pool Video Switch v2
Software video switch for distributed remote display in a lecture environment
addons.cpp
Go to the documentation of this file.
1 #include "addons.h"
2 
3 #include <QDir>
4 #include <QSettings>
5 #include <QPushButton>
6 #include <QAction>
7 #include <QProcess>
8 #include <QtDebug>
9 #include <QRegularExpression>
10 
11 #define MKSTR(x) static const QString s_ ## x(#x)
12 
13 MKSTR(EVENT);
14 MKSTR(ADDRESS);
15 MKSTR(ISLOCAL);
16 MKSTR(CHECKED);
17 MKSTR(ENABLED);
18 MKSTR(VISIBLE);
19 MKSTR(connected);
20 MKSTR(disconnected);
21 MKSTR(init);
22 MKSTR(clicked);
23 MKSTR(true);
24 MKSTR(false);
25 
26 static const QSize ICON_SIZE(20, 20);
27 
28 QList<Addon*> AddonManager::_addons;
29 
30 class Addon
31 {
32 public:
33  QPushButton *button{};
34  QAction *menu{};
35  QProcess process;
37  bool wantInit{};
38  bool runAsync{};
39  QList<QPair<QString, QString>> envir;
40 };
41 
42 static inline bool toBool(const QString &string)
43 {
44  return string.toLower() == s_true;
45 }
46 
47 static void executeAddon(Addon *addon);
48 
49 static void setAddonVisible(Addon *addon, bool visible);
50 
51 static void handleAddonOutput(Addon *addon, QRegularExpressionMatchIterator &matches);
52 
53 void AddonManager::loadFromPath(const QString &path, QList<QPushButton*> &buttons, QList<QAction*> &menuEntries)
54 {
55  QDir configDir(path);
56  QFileInfoList fileInfoList = configDir.entryInfoList(QDir::Files, QDir::Name);
57  QRegularExpression paramRegex("^([A-Z]+)=(.*)$", QRegularExpression::MultilineOption);
58 
59  for (const auto& fileInfo : fileInfoList) {
60  QString filePath = fileInfo.absoluteFilePath();
61  QSettings setting(filePath, QSettings::IniFormat);
62  setting.setIniCodec("UTF-8");
63  QString caption = setting.value("caption").toString(); // TODO: i18n
64  QString exec = setting.value("exec").toString();
65  QString type = setting.value("type").toString();
66  QString tooltip = setting.value("tooltip").toString();
67  QIcon icon(setting.value("icon").toString());
68  bool checkable = setting.value("checkable").toBool();
69  if (exec.isEmpty() || (caption.isEmpty() && icon.isNull())) {
70  qDebug() << "Ignoring" << filePath << "caption+icon or exec empty";
71  continue;
72  }
73  if (!QFileInfo(exec).isExecutable() || !QFileInfo(exec).isFile()) {
74  qDebug() << "Ignoring" << filePath << "since target" << exec << "doesn't exist or isn't an executable file";
75  continue;
76  }
77 
78  // Alloc addon
79  auto *addon = new Addon();
80  // Toggle/click callback
81  auto toggleFun = [=](bool value) {
82  addon->envir.append(qMakePair(s_EVENT, s_clicked));
83  addon->envir.append(qMakePair(s_CHECKED, (value ? s_true : s_false)));
84  executeAddon(addon);
85  };
86  if (type == "menu") {
87  addon->menu = new QAction(caption);
88  if (!icon.isNull()) {
89  addon->menu->setIcon(icon);
90  }
91  addon->menu->setCheckable(checkable);
92  addon->menu->setToolTip(tooltip);
93  menuEntries.append(addon->menu);
94  QObject::connect(addon->menu, &QAction::triggered, toggleFun);
95  } else if (type == "button") {
96  addon->button = new QPushButton(caption);
97  if (!icon.isNull()) {
98  addon->button->setIcon(icon);
99  addon->button->setIconSize(ICON_SIZE);
100  }
101  addon->button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding);
102  addon->button->setCheckable(checkable);
103  addon->button->setToolTip(tooltip);
104  buttons.append(addon->button);
105  QObject::connect(addon->button, &QPushButton::clicked, toggleFun);
106  } else {
107  qDebug() << "Ignoring unknown addon type" << type;
108  delete addon;
109  continue;
110  }
111  _addons.append(addon);
112 
113  addon->wantConnectInfo = setting.value("connection-events").toBool();
114  addon->wantInit = setting.value("init").toBool();
115  addon->runAsync = setting.value("async").toBool();
116  // Setup process
117  addon->process.setProgram(exec);
118  // Stdin for status updates
119  QObject::connect(&addon->process, &QProcess::readyReadStandardOutput, [=]() {
120  auto lines = addon->process.readAllStandardOutput();
121  auto matches = paramRegex.globalMatch(lines);
122  handleAddonOutput(addon, matches);
123  });
124  // Stderr just for debugging
125  QObject::connect(&addon->process, &QProcess::readyReadStandardError, [=]() {
126  qDebug() << exec << "stderr:" << QString::fromLocal8Bit(addon->process.readAllStandardError());
127  });
128  }
129 }
130 
132 {
133  // Call all init functions
134  for (auto addon : _addons) {
135  if (addon->wantInit) {
136  addon->envir.append(qMakePair(s_EVENT, s_init));
137  executeAddon(addon);
138  } else {
139  setAddonVisible(addon, true);
140  }
141  }
142 }
143 
144 void AddonManager::connectEvent(bool isLocal, const QString &address)
145 {
146  for (auto *addon : _addons) {
147  if (!addon->wantConnectInfo)
148  continue;
149  addon->envir.append(qMakePair(s_EVENT, s_connected));
150  addon->envir.append(qMakePair(s_ADDRESS, address));
151  addon->envir.append(qMakePair(s_ISLOCAL, isLocal ? s_true : s_false));
152  executeAddon(addon);
153  }
154 }
155 
157 {
158  for (auto addon : _addons) {
159  if (!addon->wantConnectInfo)
160  continue;
161  addon->envir.append(qMakePair(s_EVENT, s_disconnected));
162  executeAddon(addon);
163  }
164 }
165 
166 static void executeAddon(Addon *addon)
167 {
168  // Set up environment
169  auto env = QProcessEnvironment::systemEnvironment();
170  for (const auto& e : addon->envir) {
171  env.insert(e.first, e.second);
172  }
173  addon->envir.clear();
174  if (!addon->runAsync) {
175  // Kill remains
176  if (addon->process.state() != QProcess::NotRunning) {
177  addon->process.waitForFinished(500);
178  }
179  if (addon->process.state() != QProcess::NotRunning) {
180  addon->process.close();
181  }
182  }
183  addon->process.setProcessEnvironment(env);
184  // Run
185  if (addon->runAsync) {
186  QProcess::startDetached(addon->process.program(), QStringList());
187  } else {
188  addon->process.start();
189  addon->process.closeWriteChannel();
190  }
191 }
192 
193 static void setAddonVisible(Addon *addon, bool visible)
194 {
195  if (addon->button != nullptr) {
196  QWidget *p = addon->button->parentWidget();
197  bool wasVisible = p != nullptr && addon->button->isVisibleTo(p);
198  addon->button->setVisible(visible);
199  if (p != nullptr && wasVisible != visible) {
200  // Visibility changed -- adjust size of toolbar
201  int size = (addon->button->width() + 2) * (visible ? 1 : -1);
202  p->setFixedWidth(p->width() + size);
203  }
204  } else {
205  addon->menu->setVisible(visible);
206  }
207 }
208 
209 static void handleAddonOutput(Addon* addon, QRegularExpressionMatchIterator &matches)
210 {
211  while (matches.hasNext()) {
212  auto m = matches.next();
213  auto key = m.captured(1);
214  auto val = m.captured(2);
215  bool newValue = toBool(val);
216  if (key == s_VISIBLE) {
217  setAddonVisible(addon, newValue);
218  } else if (key == s_CHECKED) {
219  if (addon->button != nullptr) {
220  addon->button->setChecked(newValue);
221  } else {
222  addon->menu->setChecked(newValue);
223  }
224  } else if (key == s_ENABLED) {
225  if (addon->button != nullptr) {
226  addon->button->setEnabled(newValue);
227  } else {
228  addon->menu->setEnabled(newValue);
229  }
230  }
231  }
232 }
static void initControls()
Definition: addons.cpp:131
QList< QPair< QString, QString > > envir
Definition: addons.cpp:39
static const QSize ICON_SIZE(20, 20)
QPushButton * button
Definition: addons.cpp:33
static void connectEvent(bool isLocal, const QString &address)
Definition: addons.cpp:144
bool runAsync
Definition: addons.cpp:38
static void disconnectEvent()
Definition: addons.cpp:156
#define MKSTR(x)
Definition: addons.cpp:11
static void handleAddonOutput(Addon *addon, QRegularExpressionMatchIterator &matches)
Definition: addons.cpp:209
static bool toBool(const QString &string)
Definition: addons.cpp:42
bool wantInit
Definition: addons.cpp:37
QProcess process
Definition: addons.cpp:35
static void loadFromPath(const QString &path, QList< QPushButton * > &buttons, QList< QAction * > &menuEntries)
Definition: addons.cpp:53
Definition: addons.cpp:30
static void setAddonVisible(Addon *addon, bool visible)
Definition: addons.cpp:193
bool wantConnectInfo
Definition: addons.cpp:36
QAction * menu
Definition: addons.cpp:34
static void executeAddon(Addon *addon)
Definition: addons.cpp:166
static QList< Addon * > _addons
Definition: addons.h:22