1 // Copyright 2005-2019 The Mumble Developers. All rights reserved.
2 // Use of this source code is governed by a BSD-style license
3 // that can be found in the LICENSE file at the root of the
4 // Mumble source tree or at <https://www.mumble.info/LICENSE>.
5 
6 #include "mumble_pch.hpp"
7 
8 #include "LCD.h"
9 
10 #include "ClientUser.h"
11 #include "Channel.h"
12 #include "Message.h"
13 #include "ServerHandler.h"
14 
15 // We define a global macro called 'g'. This can lead to issues when included code uses 'g' as a type or parameter name (like protobuf 3.7 does). As such, for now, we have to make this our last include.
16 #include "Global.h"
17 
18 QList<LCDEngineNew> *LCDEngineRegistrar::qlInitializers;
19 
LCDEngineRegistrar(LCDEngineNew cons)20 LCDEngineRegistrar::LCDEngineRegistrar(LCDEngineNew cons) {
21 	if (! qlInitializers)
22 		qlInitializers = new QList<LCDEngineNew>();
23 	n = cons;
24 	qlInitializers->append(n);
25 }
26 
~LCDEngineRegistrar()27 LCDEngineRegistrar::~LCDEngineRegistrar() {
28 	qlInitializers->removeAll(n);
29 	if (qlInitializers->isEmpty()) {
30 		delete qlInitializers;
31 		qlInitializers = NULL;
32 	}
33 }
34 
LCDConfigDialogNew(Settings & st)35 static ConfigWidget *LCDConfigDialogNew(Settings &st) {
36 	return new LCDConfig(st);
37 }
38 
39 class LCDDeviceManager : public DeferInit {
40 	protected:
41 		ConfigRegistrar *crLCD;
42 	public:
43 		QList<LCDEngine *> qlEngines;
44 		QList<LCDDevice *> qlDevices;
45 		void initialize();
46 		void destroy();
47 };
48 
initialize()49 void LCDDeviceManager::initialize() {
50 	if (LCDEngineRegistrar::qlInitializers) {
51 		foreach(LCDEngineNew engine, *LCDEngineRegistrar::qlInitializers) {
52 			LCDEngine *e = engine();
53 			qlEngines.append(e);
54 
55 			foreach(LCDDevice *d, e->devices()) {
56 				qlDevices << d;
57 			}
58 		}
59 	}
60 	if (qlDevices.count() > 0) {
61 		crLCD = new ConfigRegistrar(5900, LCDConfigDialogNew);
62 	} else {
63 		crLCD = NULL;
64 	}
65 }
66 
destroy()67 void LCDDeviceManager::destroy() {
68 	qlDevices.clear();
69 	foreach(LCDEngine *e, qlEngines) {
70 		delete e;
71 	}
72 	delete crLCD;
73 }
74 
75 static LCDDeviceManager devmgr;
76 
77 /* --- */
78 
79 
LCDConfig(Settings & st)80 LCDConfig::LCDConfig(Settings &st) : ConfigWidget(st) {
81 	setupUi(this);
82 
83 	QTreeWidgetItem *qtwi;
84 	foreach(LCDDevice *d, devmgr.qlDevices) {
85 		qtwi = new QTreeWidgetItem(qtwDevices);
86 
87 		qtwi->setFlags(Qt::ItemIsEnabled |Qt::ItemIsUserCheckable);
88 
89 		qtwi->setText(0, d->name());
90 		qtwi->setToolTip(0, Qt::escape(d->name()));
91 
92 		QSize lcdsize = d->size();
93 		QString qsSize = QString::fromLatin1("%1x%2").arg(lcdsize.width()).arg(lcdsize.height());
94 		qtwi->setText(1, qsSize);
95 		qtwi->setToolTip(1, qsSize);
96 
97 		qtwi->setCheckState(2, Qt::Unchecked);
98 		qtwi->setToolTip(2, tr("Enable this device"));
99 	}
100 }
101 
title() const102 QString LCDConfig::title() const {
103 	return tr("LCD");
104 }
105 
icon() const106 QIcon LCDConfig::icon() const {
107 	return QIcon(QLatin1String("skin:config_lcd.png"));
108 }
109 
load(const Settings & r)110 void LCDConfig::load(const Settings &r) {
111 	QList<QTreeWidgetItem *> qlItems = qtwDevices->findItems(QString(), Qt::MatchContains);
112 	foreach(QTreeWidgetItem *qtwi, qlItems) {
113 		QString qsName = qtwi->text(0);
114 		bool enabled = r.qmLCDDevices.contains(qsName) ? r.qmLCDDevices.value(qsName) : true;
115 		qtwi->setCheckState(2, enabled ? Qt::Checked : Qt::Unchecked);
116 	}
117 
118 	loadSlider(qsMinColWidth, r.iLCDUserViewMinColWidth);
119 	loadSlider(qsSplitterWidth, r.iLCDUserViewSplitterWidth);
120 }
121 
save() const122 void LCDConfig::save() const {
123 	QList<QTreeWidgetItem *> qlItems = qtwDevices->findItems(QString(), Qt::MatchContains);
124 
125 	foreach(QTreeWidgetItem *qtwi, qlItems) {
126 		QString qsName = qtwi->text(0);
127 		s.qmLCDDevices.insert(qsName, qtwi->checkState(2) == Qt::Checked);
128 	}
129 
130 	s.iLCDUserViewMinColWidth = qsMinColWidth->value();
131 	s.iLCDUserViewSplitterWidth = qsSplitterWidth->value();
132 }
133 
accept() const134 void LCDConfig::accept() const {
135 	foreach(LCDDevice *d, devmgr.qlDevices) {
136 		bool enabled = s.qmLCDDevices.value(d->name());
137 		d->setEnabled(enabled);
138 	}
139 	g.lcd->updateUserView();
140 }
141 
on_qsMinColWidth_valueChanged(int v)142 void LCDConfig::on_qsMinColWidth_valueChanged(int v) {
143 	qlMinColWidth->setText(QString::number(v));
144 }
145 
on_qsSplitterWidth_valueChanged(int v)146 void LCDConfig::on_qsSplitterWidth_valueChanged(int v) {
147 	qlSplitterWidth->setText(QString::number(v));
148 }
149 
150 /* --- */
151 
LCD()152 LCD::LCD() : QObject() {
153 
154 #ifdef Q_OS_MAC
155 	qfNormal.setStyleStrategy(QFont::NoAntialias);
156 	qfNormal.setKerning(false);
157 	qfNormal.setPointSize(10);
158 	qfNormal.setFixedPitch(true);
159 	qfNormal.setFamily(QString::fromLatin1("Andale Mono"));
160 #else
161 	qfNormal = QFont(QString::fromLatin1("Arial"), 7);
162 #endif
163 
164 	qfItalic = qfNormal;
165 	qfItalic.setItalic(true);
166 
167 	qfBold = qfNormal;
168 	qfBold.setWeight(QFont::Black);
169 
170 	qfItalicBold = qfBold;
171 	qfItalic.setItalic(true);
172 
173 	QFontMetrics qfm(qfNormal);
174 
175 	iFontHeight = 10;
176 
177 	initBuffers();
178 
179 	iFrameIndex = 0;
180 
181 	qtTimer = new QTimer(this);
182 	connect(qtTimer, SIGNAL(timeout()), this, SLOT(tick()));
183 
184 	foreach(LCDDevice *d, devmgr.qlDevices) {
185 		bool enabled = g.s.qmLCDDevices.contains(d->name()) ? g.s.qmLCDDevices.value(d->name()) : true;
186 		d->setEnabled(enabled);
187 	}
188 	qiLogo = QIcon(QLatin1String("skin:mumble.svg")).pixmap(48,48).toImage().convertToFormat(QImage::Format_MonoLSB);
189 
190 #if QT_VERSION >= 0x050600 && QT_VERSION <= 0x050601
191 	// Don't invert the logo image when using Qt 5.6.
192 	// See mumble-voip/mumble#2429
193 #else
194 	qiLogo.invertPixels();
195 #endif
196 
197 	updateUserView();
198 }
199 
tick()200 void LCD::tick() {
201 	iFrameIndex ++;
202 	updateUserView();
203 }
204 
initBuffers()205 void LCD::initBuffers() {
206 	foreach(LCDDevice *d, devmgr.qlDevices) {
207 		QSize size = d->size();
208 		if (! qhImageBuffers.contains(size)) {
209 			size_t buflen = (size.width() * size.height()) / 8;
210 			qhImageBuffers[size] = new unsigned char[buflen];
211 			qhImages[size] = new QImage(qhImageBuffers[size], size.width(), size.height(), QImage::Format_MonoLSB);
212 		}
213 	}
214 }
215 
destroyBuffers()216 void LCD::destroyBuffers() {
217 	foreach(QImage *img, qhImages)
218 		delete img;
219 	qhImages.clear();
220 
221 	foreach(unsigned char *buf, qhImageBuffers)
222 		delete [] buf;
223 	qhImageBuffers.clear();
224 }
225 
226 struct ListEntry {
227 	QString qsString;
228 	bool bBold;
229 	bool bItalic;
ListEntryListEntry230 	ListEntry(const QString &qs, bool bB, bool bI) : qsString(qs), bBold(bB), bItalic(bI) {};
231 };
232 
entriesSort(const ListEntry & a,const ListEntry & b)233 static bool entriesSort(const ListEntry &a, const ListEntry &b) {
234 	return a.qsString < b.qsString;
235 }
236 
updateUserView()237 void LCD::updateUserView() {
238 	if (qhImages.count() == 0)
239 		return;
240 
241 	QStringList qslTalking;
242 	User *me = g.uiSession ? ClientUser::get(g.uiSession) : NULL;
243 	Channel *home = me ? me->cChannel : NULL;
244 	bool alert = false;
245 
246 	foreach(const QSize &size, qhImages.keys()) {
247 		QImage *img = qhImages.value(size);
248 		QPainter painter(img);
249 		painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing, false);
250 
251 #if QT_VERSION >= 0x050600 && QT_VERSION <= 0x050601
252 		// Use Qt::white instead of Qt::color1 on Qt 5.6.
253 		// See mumble-voip/mumble#2429
254 		painter.setPen(Qt::white);
255 #else
256 		painter.setPen(Qt::color1);
257 #endif
258 
259 		painter.setFont(qfNormal);
260 
261 		img->fill(Qt::color0);
262 
263 		if (! me) {
264 			qmNew.clear();
265 			qmOld.clear();
266 			qmSpeaking.clear();
267 			qmNameCache.clear();
268 			painter.drawImage(0, 0, qiLogo);
269 			painter.drawText(60, 20, tr("Not connected"));
270 			continue;
271 		}
272 
273 		foreach(User *p, me->cChannel->qlUsers) {
274 			if (! qmNew.contains(p->uiSession)) {
275 				qmNew.insert(p->uiSession, Timer());
276 				qmNameCache.insert(p->uiSession, p->qsName);
277 				qmOld.remove(p->uiSession);
278 			}
279 		}
280 
281 		foreach(unsigned int session, qmNew.keys()) {
282 			User *p = ClientUser::get(session);
283 			if (!p || (p->cChannel != me->cChannel)) {
284 				qmNew.remove(session);
285 				qmOld.insert(session, Timer());
286 			}
287 		}
288 
289 		QMap<unsigned int, Timer> old;
290 
291 		foreach(unsigned int session, qmOld.keys()) {
292 			Timer t = qmOld.value(session);
293 			if (t.elapsed() > 3000000) {
294 				qmNameCache.remove(session);
295 			} else {
296 				old.insert(session, qmOld.value(session));
297 			}
298 		}
299 		qmOld = old;
300 
301 		QList<struct ListEntry> entries;
302 		entries << ListEntry(QString::fromLatin1("[%1:%2]").arg(me->cChannel->qsName).arg(me->cChannel->qlUsers.count()), false, false);
303 
304 		bool hasnew = false;
305 
306 		QMap<unsigned int, Timer> speaking;
307 
308 		foreach(Channel *c, home->allLinks()) {
309 			foreach(User *p, c->qlUsers) {
310 				ClientUser *u = static_cast<ClientUser *>(p);
311 				bool bTalk = (u->tsState != Settings::Passive);
312 				if (bTalk) {
313 					speaking.insert(p->uiSession, Timer());
314 				} else if (qmSpeaking.contains(p->uiSession)) {
315 					Timer t = qmSpeaking.value(p->uiSession);
316 					if (t.elapsed() > 1000000)
317 						qmSpeaking.remove(p->uiSession);
318 					else {
319 						speaking.insert(p->uiSession, t);
320 						bTalk = true;
321 					}
322 				}
323 				if (bTalk) {
324 					alert = true;
325 					entries << ListEntry(p->qsName, true, (p->cChannel != me->cChannel));
326 				} else if (c == me->cChannel) {
327 					if (qmNew.value(p->uiSession).elapsed() < 3000000) {
328 						entries << ListEntry(QLatin1String("+") + p->qsName, false, false);
329 						hasnew = true;
330 					}
331 				}
332 			}
333 		}
334 		qmSpeaking = speaking;
335 
336 		foreach(unsigned int session, qmOld.keys()) {
337 			entries << ListEntry(QLatin1String("-") + qmNameCache.value(session), false, false);
338 		}
339 
340 		if (! qmOld.isEmpty() || hasnew || ! qmSpeaking.isEmpty())
341 			qtTimer->start(500);
342 		else
343 			qtTimer->stop();
344 
345 		qSort(++ entries.begin(), entries.end(), entriesSort);
346 
347 		const int iWidth = size.width();
348 		const int iHeight = size.height();
349 		const int iUsersPerColumn = iHeight / iFontHeight;
350 		const int iSplitterWidth = g.s.iLCDUserViewSplitterWidth;
351 		const int iUserColumns = (entries.count() + iUsersPerColumn - 1) / iUsersPerColumn;
352 
353 		int iColumns = iUserColumns;
354 		int iColumnWidth = 1;
355 
356 		while (iColumns >= 1) {
357 			iColumnWidth = (iWidth - (iColumns-1)*iSplitterWidth) / iColumns;
358 			if (iColumnWidth >= g.s.iLCDUserViewMinColWidth)
359 				break;
360 			--iColumns;
361 		}
362 
363 		int row = 0, col = 0;
364 
365 
366 		foreach(const ListEntry &le, entries) {
367 			if (row >= iUsersPerColumn) {
368 				row = 0;
369 				++col;
370 			}
371 			if (col > iColumns)
372 				break;
373 
374 			if (! le.qsString.isEmpty()) {
375 				if (le.bBold && le.bItalic)
376 					painter.setFont(qfItalicBold);
377 				else if (le.bBold)
378 					painter.setFont(qfBold);
379 				else if (le.bItalic)
380 					painter.setFont(qfItalic);
381 				else
382 					painter.setFont(qfNormal);
383 				painter.drawText(QRect(col * (iColumnWidth  + iSplitterWidth),
384 				                       row * iFontHeight, iColumnWidth, iFontHeight+2), Qt::AlignLeft, le.qsString);
385 			}
386 			++row;
387 		}
388 	}
389 
390 	foreach(LCDDevice *d, devmgr.qlDevices) {
391 		QImage *img = qhImages[d->size()];
392 		if (! img)
393 			continue;
394 		d->blitImage(img, alert);
395 	}
396 }
397 
~LCD()398 LCD::~LCD() {
399 	destroyBuffers();
400 }
401 
hasDevices()402 bool LCD::hasDevices() {
403 	return (! devmgr.qlDevices.isEmpty());
404 }
405 
406 /* --- */
407 
LCDEngine()408 LCDEngine::LCDEngine() : QObject() {
409 }
410 
~LCDEngine()411 LCDEngine::~LCDEngine() {
412 	foreach(LCDDevice *lcd, qlDevices)
413 		delete lcd;
414 }
415 
LCDDevice()416 LCDDevice::LCDDevice() {
417 }
418 
~LCDDevice()419 LCDDevice::~LCDDevice() {
420 }
421 
422 /* --- */
423 
qHash(const QSize & size)424 uint qHash(const QSize &size) {
425 	return ((size.width() & 0xffff) << 16) | (size.height() & 0xffff);
426 }
427