mumble-voip_mumble/src/mumble/OverlayClient.cpp

656 lines
18 KiB
C++

// Copyright 2010-2023 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
#include "OverlayClient.h"
#include "Channel.h"
#include "Database.h"
#include "MainWindow.h"
#include "NetworkConfig.h"
#include "OverlayEditor.h"
#include "OverlayPositionableItem.h"
#include "OverlayText.h"
#include "ServerHandler.h"
#include "Themes.h"
#include "User.h"
#include "Utils.h"
#include "Global.h"
#include "GlobalShortcut.h"
#ifdef Q_OS_WIN
# include <QtGui/QBitmap>
#endif
#include <QtGui/QImageReader>
#include <QtWidgets/QGraphicsProxyWidget>
#ifdef Q_OS_WIN
# include <psapi.h>
#endif
OverlayClient::OverlayClient(QLocalSocket *socket, QObject *p)
: QObject(p), framesPerSecond(0), ougUsers(&Global::get().s.os), iMouseX(0), iMouseY(0) {
qlsSocket = socket;
qlsSocket->setParent(nullptr);
connect(qlsSocket, SIGNAL(readyRead()), this, SLOT(readyRead()));
omMsg.omh.iLength = -1;
smMem = nullptr;
iWidth = iHeight = 0;
uiPid = ~0ULL;
bWasVisible = false;
bDelete = false;
qgv.setScene(&qgs);
qgv.installEventFilter(this);
qgv.viewport()->installEventFilter(this);
// Make sure it has a native window id
qgv.winId();
qgpiCursor.reset(new OverlayMouse());
qgpiCursor->hide();
qgpiCursor->setZValue(10.0f);
ougUsers.setZValue(-1.0f);
qgs.addItem(&ougUsers);
ougUsers.show();
qgpiFPS.reset(new OverlayPositionableItem(&Global::get().s.os.qrfFps));
qgs.addItem(qgpiFPS.data());
qgpiFPS->setPos(Global::get().s.os.qrfFps.x(), Global::get().s.os.qrfFps.y());
qgpiFPS->show();
// Time
qgpiTime.reset(new OverlayPositionableItem(&Global::get().s.os.qrfTime));
qgs.addItem(qgpiTime.data());
qgpiTime->setPos(Global::get().s.os.qrfTime.x(), Global::get().s.os.qrfTime.y());
qgpiTime->show();
iOffsetX = iOffsetY = 0;
connect(&qgs, SIGNAL(changed(const QList< QRectF > &)), this, SLOT(changed(const QList< QRectF > &)));
}
OverlayClient::~OverlayClient() {
qlsSocket->disconnectFromServer();
if (!qlsSocket->waitForDisconnected(1000)) {
qDebug() << "OverlayClient: Failed to cleanly disconnect: " << qlsSocket->errorString();
qlsSocket->abort();
}
qlsSocket->deleteLater();
ougUsers.reset();
}
bool OverlayClient::eventFilter(QObject *o, QEvent *e) {
if (e->type() == QEvent::Paint) {
e->accept();
return true;
}
return QObject::eventFilter(o, e);
}
void OverlayClient::updateFPS() {
if (Global::get().s.os.bFps) {
const BasepointPixmap &pm =
OverlayTextLine(QString(QLatin1String("%1")).arg(static_cast< int >(framesPerSecond + 0.5f)),
Global::get().s.os.qfFps)
.createPixmap(Global::get().s.os.qcFps);
qgpiFPS->setVisible(true);
qgpiFPS->setPixmap(pm);
// offset to use basepoint
// TODO: settings are providing a top left anchor, so shift down by ascent
qgpiFPS->setOffset(-pm.qpBasePoint + QPoint(0, pm.iAscent));
qgpiFPS->updateRender();
} else {
qgpiFPS->setVisible(false);
}
}
void OverlayClient::updateTime() {
if (Global::get().s.os.bTime) {
const BasepointPixmap &pm =
OverlayTextLine(QString(QLatin1String("%1")).arg(QTime::currentTime().toString()), Global::get().s.os.qfFps)
.createPixmap(Global::get().s.os.qcFps);
qgpiTime->setVisible(true);
qgpiTime->setPixmap(pm);
qgpiTime->setOffset(-pm.qpBasePoint + QPoint(0, pm.iAscent));
qgpiTime->updateRender();
} else {
qgpiTime->setVisible(false);
}
}
#if !defined(Q_OS_MAC) || (defined(Q_OS_MAC) && defined(USE_MAC_UNIVERSAL))
void OverlayClient::updateMouse() {
# if defined(Q_OS_WIN)
QPixmap pm;
HICON c = ::GetCursor();
ICONINFO info;
ZeroMemory(&info, sizeof(info));
if (c && ::GetIconInfo(c, &info)) {
extern QPixmap qt_pixmapFromWinHBITMAP(HBITMAP bitmap, int format = 0);
if (info.hbmColor) {
pm = qt_pixmapFromWinHBITMAP(info.hbmColor);
pm.setMask(QBitmap(qt_pixmapFromWinHBITMAP(info.hbmMask)));
} else {
QBitmap orig(qt_pixmapFromWinHBITMAP(info.hbmMask));
QImage img = orig.toImage();
int h = img.height() / 2;
int w = img.bytesPerLine() / sizeof(quint32);
QImage out(img.width(), h, QImage::Format_MonoLSB);
QImage outmask(img.width(), h, QImage::Format_MonoLSB);
for (int i = 0; i < h; ++i) {
const quint32 *srcimg = reinterpret_cast< const quint32 * >(img.scanLine(i + h));
const quint32 *srcmask = reinterpret_cast< const quint32 * >(img.scanLine(i));
quint32 *dstimg = reinterpret_cast< quint32 * >(out.scanLine(i));
quint32 *dstmask = reinterpret_cast< quint32 * >(outmask.scanLine(i));
for (int j = 0; j < w; ++j) {
dstmask[j] = srcmask[j];
dstimg[j] = srcimg[j];
}
}
pm = QBitmap::fromImage(out);
}
if (info.hbmMask)
::DeleteObject(info.hbmMask);
if (info.hbmColor)
::DeleteObject(info.hbmColor);
iOffsetX = info.xHotspot;
iOffsetY = info.yHotspot;
}
qgpiCursor->setPixmap(pm);
# else
# endif
qgpiCursor->setPos(iMouseX - iOffsetX, iMouseY - iOffsetY);
}
#endif
// Qt gets very very unhappy if we embed or unmbed the widget that an event is called from.
// This means that if any modal dialog is open, we'll be in a event loop of an object
// that we're about to reparent.
void OverlayClient::showGui() {
int count = 0;
{
QWidgetList widgets = qApp->topLevelWidgets();
foreach (QWidget *w, widgets) {
if (w->isHidden() && (w != Global::get().mw))
continue;
count++;
}
}
// If there's more than one window up, we're likely deep in a message loop.
if (count > 1)
return;
Global::get().ocIntercept = this;
bWasVisible = !Global::get().mw->isHidden();
if (bWasVisible) {
if (Global::get().s.bMinimalView) {
Global::get().s.qbaMinimalViewGeometry = Global::get().mw->saveGeometry();
Global::get().s.qbaMinimalViewState = Global::get().mw->saveState();
} else {
Global::get().s.qbaMainWindowGeometry = Global::get().mw->saveGeometry();
Global::get().s.qbaMainWindowState = Global::get().mw->saveState();
Global::get().s.qbaHeaderState = Global::get().mw->qtvUsers->header()->saveState();
}
}
{
outer:
QWidgetList widgets = qApp->topLevelWidgets();
widgets.removeAll(Global::get().mw);
widgets.prepend(Global::get().mw);
foreach (QWidget *w, widgets) {
if (!w->graphicsProxyWidget()) {
if ((w == Global::get().mw) || (!w->isHidden())) {
QGraphicsProxyWidget *qgpw = new QGraphicsProxyWidget(nullptr, Qt::Window);
qgpw->setOpacity(0.90f);
qgpw->setWidget(w);
if (w == Global::get().mw) {
qgpw->setPos(static_cast< float >(iWidth) / 10, static_cast< float >(iHeight) / 10);
qgpw->resize(static_cast< float >(iWidth * 8) / 10, static_cast< float >(iHeight * 8) / 10);
}
qgs.addItem(qgpw);
qgpw->show();
qgpw->setActive(true);
goto outer;
}
}
}
}
QEvent activateEvent(QEvent::WindowActivate);
qApp->sendEvent(&qgs, &activateEvent);
QPoint p = QCursor::pos();
iMouseX = qBound< int >(0, p.x(), static_cast< int >(iWidth) - 1);
iMouseY = qBound< int >(0, p.y(), static_cast< int >(iHeight) - 1);
qgpiCursor->setPos(iMouseX, iMouseY);
qgs.setFocus();
#ifndef Q_OS_MAC
Global::get().mw->qteChat->activateWindow();
#endif
Global::get().mw->qteChat->setFocus();
qgv.setAttribute(Qt::WA_WState_Hidden, false);
qApp->setActiveWindow(&qgv);
qgv.setFocus();
ougUsers.bShowExamples = true;
#ifdef Q_OS_MAC
qApp->setAttribute(Qt::AA_DontUseNativeMenuBar);
Global::get().mw->setUnifiedTitleAndToolBarOnMac(false);
if (!Global::get().s.os.qsStyle.isEmpty())
qApp->setStyle(Global::get().s.os.qsStyle);
#endif
setupScene(true);
OverlayMsg om;
om.omh.uiMagic = OVERLAY_MAGIC_NUMBER;
om.omh.uiType = OVERLAY_MSGTYPE_INTERACTIVE;
om.omh.iLength = sizeof(struct OverlayMsgInteractive);
om.omin.state = true;
qlsSocket->write(om.headerbuffer, static_cast< int >(sizeof(OverlayMsgHeader)) + om.omh.iLength);
Global::get().o->updateOverlay();
}
void OverlayClient::hideGui() {
ougUsers.bShowExamples = false;
QList< QWidget * > widgetlist;
foreach (QGraphicsItem *qgi, qgs.items(Qt::DescendingOrder)) {
QGraphicsProxyWidget *qgpw = qgraphicsitem_cast< QGraphicsProxyWidget * >(qgi);
if (qgpw && qgpw->widget()) {
QWidget *w = qgpw->widget();
qgpw->setVisible(false);
widgetlist << w;
}
}
foreach (QWidget *w, widgetlist) {
QGraphicsProxyWidget *qgpw = w->graphicsProxyWidget();
if (qgpw) {
qgpw->setVisible(false);
qgpw->setWidget(nullptr);
delete qgpw;
}
}
if (Global::get().ocIntercept == this)
Global::get().ocIntercept = nullptr;
foreach (QWidget *w, widgetlist) {
if (bWasVisible)
w->show();
}
if (bWasVisible) {
if (Global::get().s.bMinimalView && !Global::get().s.qbaMinimalViewGeometry.isNull()) {
Global::get().mw->restoreGeometry(Global::get().s.qbaMinimalViewGeometry);
Global::get().mw->restoreState(Global::get().s.qbaMinimalViewState);
} else if (!Global::get().s.bMinimalView && !Global::get().s.qbaMainWindowGeometry.isNull()) {
Global::get().mw->restoreGeometry(Global::get().s.qbaMainWindowGeometry);
Global::get().mw->restoreState(Global::get().s.qbaMainWindowState);
}
}
#ifdef Q_OS_MAC
qApp->setAttribute(Qt::AA_DontUseNativeMenuBar, false);
Global::get().mw->setUnifiedTitleAndToolBarOnMac(true);
Themes::apply();
#endif
setupScene(false);
qgv.setAttribute(Qt::WA_WState_Hidden, true);
OverlayMsg om;
om.omh.uiMagic = OVERLAY_MAGIC_NUMBER;
om.omh.uiType = OVERLAY_MSGTYPE_INTERACTIVE;
om.omh.iLength = sizeof(struct OverlayMsgInteractive);
om.omin.state = false;
qlsSocket->write(om.headerbuffer, static_cast< int >(sizeof(OverlayMsgHeader)) + om.omh.iLength);
Global::get().o->updateOverlay();
if (bDelete)
deleteLater();
}
void OverlayClient::scheduleDelete() {
bDelete = true;
hideGui();
}
void OverlayClient::readyReadMsgInit(unsigned int length) {
if (length != sizeof(OverlayMsgInit)) {
return;
}
OverlayMsgInit *omi = &omMsg.omi;
iWidth = static_cast< int >(omi->uiWidth);
iHeight = static_cast< int >(omi->uiHeight);
qrLast = QRect();
delete smMem;
smMem = new SharedMemory2(this, static_cast< unsigned int >(iWidth * iHeight * 4));
if (!smMem->data()) {
qWarning() << "OverlayClient: Failed to create shared memory" << iWidth << iHeight;
delete smMem;
smMem = nullptr;
return;
}
QByteArray key = smMem->name().toUtf8();
key.append(static_cast< char >(0));
OverlayMsg om;
om.omh.uiMagic = OVERLAY_MAGIC_NUMBER;
om.omh.uiType = OVERLAY_MSGTYPE_SHMEM;
om.omh.iLength = key.length();
Q_ASSERT(sizeof(om.oms.a_cName) >= static_cast< size_t >(key.length())); // Name should be auto-generated and short
memcpy(om.oms.a_cName, key.constData(), static_cast< std::size_t >(key.length()));
qlsSocket->write(om.headerbuffer, static_cast< int >(sizeof(OverlayMsgHeader)) + om.omh.iLength);
setupRender();
Overlay *o = static_cast< Overlay * >(parent());
QTimer::singleShot(0, o, SLOT(updateOverlay()));
}
void OverlayClient::readyRead() {
while (true) {
quint64 ready = static_cast< quint64 >(qlsSocket->bytesAvailable());
if (omMsg.omh.iLength == -1) {
if (ready < sizeof(OverlayMsgHeader)) {
break;
} else {
qlsSocket->read(omMsg.headerbuffer, sizeof(OverlayMsgHeader));
if ((omMsg.omh.uiMagic != OVERLAY_MAGIC_NUMBER) || (omMsg.omh.iLength < 0)
|| (omMsg.omh.iLength > static_cast< int >(sizeof(OverlayMsgShmem)))) {
disconnect();
return;
}
ready -= sizeof(OverlayMsgHeader);
}
}
if (ready >= static_cast< unsigned int >(omMsg.omh.iLength)) {
qint64 length = qlsSocket->read(omMsg.msgbuffer, omMsg.omh.iLength);
if (length != omMsg.omh.iLength) {
disconnect();
return;
}
switch (omMsg.omh.uiType) {
case OVERLAY_MSGTYPE_INIT: {
readyReadMsgInit(static_cast< unsigned int >(length));
} break;
case OVERLAY_MSGTYPE_SHMEM: {
if (smMem)
smMem->systemRelease();
} break;
case OVERLAY_MSGTYPE_PID: {
if (length != static_cast< qint64 >(sizeof(OverlayMsgPid)))
break;
OverlayMsgPid *omp = &omMsg.omp;
uiPid = omp->pid;
#ifdef Q_OS_WIN
HANDLE h = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, (DWORD) uiPid);
if (h) {
wchar_t buf[MAX_PATH];
if (GetModuleFileNameEx(h, 0, buf, MAX_PATH) != 0) {
qsExecutablePath = QString::fromWCharArray(buf);
}
CloseHandle(h);
}
#else
qsExecutablePath = QLatin1String("Unknown");
#endif
} break;
case OVERLAY_MSGTYPE_FPS: {
if (length != sizeof(OverlayMsgFps))
break;
OverlayMsgFps *omf = &omMsg.omf;
framesPerSecond = omf->fps;
// qWarning() << "FPS: " << omf->fps;
Overlay *o = static_cast< Overlay * >(parent());
QTimer::singleShot(0, o, SLOT(updateOverlay()));
} break;
default:
break;
}
omMsg.omh.iLength = -1;
} else {
break;
}
}
}
void OverlayClient::reset() {
if (!iWidth || !iHeight || !smMem)
return;
qgpiLogo.reset();
ougUsers.reset();
setupScene(Global::get().ocIntercept == this);
}
void OverlayClient::setupScene(bool show) {
if (show) {
qgs.setBackgroundBrush(QColor(0, 0, 0, 64));
if (!qgpiLogo) {
qgpiLogo.reset(new OverlayMouse());
qgpiLogo->hide();
qgpiLogo->setOpacity(0.8f);
qgpiLogo->setZValue(-5.0f);
QImageReader qir(QLatin1String("skin:mumble.svg"));
QSize sz = qir.size();
sz.scale(static_cast< int >(iWidth), static_cast< int >(iHeight), Qt::KeepAspectRatio);
qir.setScaledSize(sz);
qgpiLogo->setPixmap(QPixmap::fromImage(qir.read()));
QRectF qrf = qgpiLogo->boundingRect();
qgpiLogo->setPos(static_cast< int >((iWidth - qrf.width()) / 2.0f + 0.5f),
static_cast< int >((iHeight - qrf.height()) / 2.0f + 0.5f));
}
qgpiCursor->show();
qgs.addItem(qgpiCursor.data());
qgpiLogo->show();
qgs.addItem(qgpiLogo.data());
} else {
qgs.setBackgroundBrush(Qt::NoBrush);
if (qgpiCursor->scene())
qgs.removeItem(qgpiCursor.data());
qgpiCursor->hide();
if (qgpiLogo) {
if (qgpiLogo->scene())
qgs.removeItem(qgpiLogo.data());
qgpiLogo->hide();
}
}
ougUsers.updateUsers();
updateFPS();
updateTime();
}
void OverlayClient::setupRender() {
qgs.setSceneRect(0, 0, iWidth, iHeight);
qgv.setScene(nullptr);
qgv.setGeometry(-2, -2, static_cast< int >(iWidth) + 2, static_cast< int >(iHeight) + 2);
qgv.viewport()->setGeometry(0, 0, static_cast< int >(iWidth), static_cast< int >(iHeight));
qgv.setScene(&qgs);
smMem->erase();
OverlayMsg om;
om.omh.uiMagic = OVERLAY_MAGIC_NUMBER;
om.omh.uiType = OVERLAY_MSGTYPE_BLIT;
om.omh.iLength = sizeof(OverlayMsgBlit);
om.omb.x = 0;
om.omb.y = 0;
om.omb.w = static_cast< unsigned int >(iWidth);
om.omb.h = static_cast< unsigned int >(iHeight);
qlsSocket->write(om.headerbuffer, sizeof(OverlayMsgHeader) + sizeof(OverlayMsgBlit));
reset();
}
bool OverlayClient::update() {
if (!iWidth || !iHeight || !smMem)
return true;
ougUsers.updateUsers();
updateFPS();
updateTime();
if (qlsSocket->bytesToWrite() > 1024) {
return (t.elapsed() <= 5000000ULL);
} else {
t.restart();
return true;
}
}
void OverlayClient::changed(const QList< QRectF > &region) {
if (region.isEmpty())
return;
qlDirty.append(region);
QMetaObject::invokeMethod(this, "render", Qt::QueuedConnection);
}
void OverlayClient::render() {
const QList< QRectF > region = qlDirty;
qlDirty.clear();
if (!iWidth || !iHeight || !smMem)
return;
QRect active;
QRectF dirtyf;
if (region.isEmpty())
return;
foreach (const QRectF &r, region) { dirtyf |= r; }
QRect dirty = dirtyf.toAlignedRect();
dirty = dirty.intersected(QRect(0, 0, iWidth, iHeight));
if ((dirty.width() <= 0) || (dirty.height() <= 0))
return;
QRect target = dirty;
target.moveTo(0, 0);
QImage img(reinterpret_cast< unsigned char * >(smMem->data()), iWidth, iHeight,
QImage::Format_ARGB32_Premultiplied);
QImage qi(target.size(), QImage::Format_ARGB32_Premultiplied);
qi.fill(0);
QPainter p;
p.begin(&qi);
p.setRenderHints(p.renderHints(), false);
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
qgs.render(&p, target, dirty, Qt::IgnoreAspectRatio);
p.end();
p.begin(&img);
p.setRenderHints(p.renderHints(), false);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.drawImage(dirty.x(), dirty.y(), qi);
p.end();
if (dirty.isValid()) {
OverlayMsg om;
om.omh.uiMagic = OVERLAY_MAGIC_NUMBER;
om.omh.uiType = OVERLAY_MSGTYPE_BLIT;
om.omh.iLength = sizeof(OverlayMsgBlit);
om.omb.x = static_cast< unsigned int >(dirty.x());
om.omb.y = static_cast< unsigned int >(dirty.y());
om.omb.w = static_cast< unsigned int >(dirty.width());
om.omb.h = static_cast< unsigned int >(dirty.height());
qlsSocket->write(om.headerbuffer, sizeof(OverlayMsgHeader) + sizeof(OverlayMsgBlit));
}
if (qgpiCursor->isVisible()) {
active = QRect(0, 0, iWidth, iHeight);
} else {
active = qgs.itemsBoundingRect().toAlignedRect();
if (active.isEmpty())
active = QRect(0, 0, 0, 0);
active = active.intersected(QRect(0, 0, iWidth, iHeight));
}
if (active != qrLast) {
qrLast = active;
OverlayMsg om;
om.omh.uiMagic = OVERLAY_MAGIC_NUMBER;
om.omh.uiType = OVERLAY_MSGTYPE_ACTIVE;
om.omh.iLength = sizeof(OverlayMsgActive);
om.oma.x = static_cast< unsigned int >(qrLast.x());
om.oma.y = static_cast< unsigned int >(qrLast.y());
om.oma.w = static_cast< unsigned int >(qrLast.width());
om.oma.h = static_cast< unsigned int >(qrLast.height());
qlsSocket->write(om.headerbuffer, sizeof(OverlayMsgHeader) + sizeof(OverlayMsgActive));
}
qlsSocket->flush();
}
void OverlayClient::openEditor() {
OverlayEditor oe(Global::get().mw, &ougUsers);
connect(&oe, SIGNAL(applySettings()), this, SLOT(updateLayout()));
oe.exec();
}