// Copyright 2005-2019 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 . #include "mumble_pch.hpp" #include "OverlayClient.h" #include "OverlayPositionableItem.h" #include "OverlayEditor.h" #include "OverlayText.h" #include "User.h" #include "Channel.h" #include "Message.h" #include "Database.h" #include "NetworkConfig.h" #include "ServerHandler.h" #include "MainWindow.h" #include "GlobalShortcut.h" #include "Themes.h" // 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. #include "Global.h" OverlayClient::OverlayClient(QLocalSocket *socket, QObject *p) : QObject(p) , framesPerSecond(0) , ougUsers(&g.s.os) , iMouseX(0) , iMouseY(0) { qlsSocket = socket; qlsSocket->setParent(NULL); connect(qlsSocket, SIGNAL(readyRead()), this, SLOT(readyRead())); omMsg.omh.iLength = -1; smMem = NULL; uiWidth = uiHeight = 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 = new OverlayMouse(); qgpiCursor->hide(); qgpiCursor->setZValue(10.0f); ougUsers.setZValue(-1.0f); qgs.addItem(&ougUsers); ougUsers.show(); qgpiFPS = new OverlayPositionableItem(&g.s.os.qrfFps); qgs.addItem(qgpiFPS); qgpiFPS->setPos(g.s.os.qrfFps.x(), g.s.os.qrfFps.y()); qgpiFPS->show(); // Time qgpiTime = new OverlayPositionableItem(&g.s.os.qrfTime); qgs.addItem(qgpiTime); qgpiTime->setPos(g.s.os.qrfTime.x(), g.s.os.qrfTime.y()); qgpiTime->show(); qgpiLogo = NULL; iOffsetX = iOffsetY = 0; connect(&qgs, SIGNAL(changed(const QList &)), this, SLOT(changed(const QList &))); } OverlayClient::~OverlayClient() { delete qgpiFPS; delete qgpiTime; delete qgpiCursor; delete qgpiLogo; 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 (g.s.os.bFps) { const BasepointPixmap &pm = OverlayTextLine( QString(QLatin1String("%1")).arg(iroundf(framesPerSecond + 0.5f)), g.s.os.qfFps).createPixmap(g.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 (g.s.os.bTime) { const BasepointPixmap &pm = OverlayTextLine(QString(QLatin1String("%1")).arg(QTime::currentTime().toString()), g.s.os.qfFps).createPixmap(g.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(); #if QT_VERSION < 0x050000 if (c == NULL) c = qgv.viewport()->cursor().handle(); #endif ICONINFO info; ZeroMemory(&info, sizeof(info)); if (c != NULL && ::GetIconInfo(c, &info)) { #if QT_VERSION >= 0x050000 extern QPixmap qt_pixmapFromWinHBITMAP(HBITMAP bitmap, int format = 0); #else # define qt_pixmapFromWinHBITMAP(bmp) QPixmap::fromWinHBITMAP(bmp) #endif 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(img.scanLine(i + h)); const quint32 *srcmask = reinterpret_cast(img.scanLine(i)); quint32 *dstimg = reinterpret_cast(out.scanLine(i)); quint32 *dstmask = reinterpret_cast(outmask.scanLine(i)); for (int j=0;jsetPixmap(pm); #else #endif qgpiCursor->setPos(iMouseX - iOffsetX, iMouseY - iOffsetY); } #endif #if QT_VERSION < 0x050000 && (defined(Q_OS_WIN) || defined(Q_OS_MAC)) extern bool Q_GUI_EXPORT qt_use_native_dialogs; #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() { #if defined(QT3_SUPPORT) || (defined(Q_OS_WIN) && QT_VERSION < 0x050000) if (QCoreApplication::loopLevel() > 1) return; #else int count = 0; { QWidgetList widgets = qApp->topLevelWidgets(); foreach(QWidget *w, widgets) { if (w->isHidden() && (w != g.mw)) continue; count++; } } // If there's more than one window up, we're likely deep in a message loop. if (count > 1) return; #endif g.ocIntercept = this; bWasVisible = ! g.mw->isHidden(); if (bWasVisible) { if (g.s.bMinimalView) { g.s.qbaMinimalViewGeometry = g.mw->saveGeometry(); g.s.qbaMinimalViewState = g.mw->saveState(); } else { g.s.qbaMainWindowGeometry = g.mw->saveGeometry(); g.s.qbaMainWindowState = g.mw->saveState(); g.s.qbaHeaderState = g.mw->qtvUsers->header()->saveState(); } } { outer: QWidgetList widgets = qApp->topLevelWidgets(); widgets.removeAll(g.mw); widgets.prepend(g.mw); foreach(QWidget *w, widgets) { if (w->graphicsProxyWidget() == NULL) { if ((w == g.mw) || (! w->isHidden())) { QGraphicsProxyWidget *qgpw = new QGraphicsProxyWidget(NULL, Qt::Window); qgpw->setOpacity(0.90f); qgpw->setWidget(w); if (w == g.mw) { qgpw->setPos(uiWidth / 10, uiHeight / 10); qgpw->resize((uiWidth * 8) / 10, (uiHeight * 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(0, p.x(), uiWidth-1); iMouseY = qBound(0, p.y(), uiHeight-1); qgpiCursor->setPos(iMouseX, iMouseY); qgs.setFocus(); #ifndef Q_OS_MAC g.mw->qteChat->activateWindow(); #endif g.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); g.mw->setUnifiedTitleAndToolBarOnMac(false); if (! g.s.os.qsStyle.isEmpty()) qApp->setStyle(g.s.os.qsStyle); #endif setupScene(true); #if QT_VERSION < 0x050000 && (defined(Q_OS_WIN) || defined(Q_OS_MAC)) qt_use_native_dialogs = false; #endif 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, sizeof(OverlayMsgHeader) + om.omh.iLength); g.o->updateOverlay(); } void OverlayClient::hideGui() { #if defined(QT3_SUPPORT) || (defined(Q_OS_WIN) && QT_VERSION < 0x050000) if (QCoreApplication::loopLevel() > 1) { QCoreApplication::exit_loop(); QMetaObject::invokeMethod(this, "hideGui", Qt::QueuedConnection); return; } #endif ougUsers.bShowExamples = false; QList widgetlist; foreach(QGraphicsItem *qgi, qgs.items(Qt::DescendingOrder)) { QGraphicsProxyWidget *qgpw = qgraphicsitem_cast(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(NULL); delete qgpw; } } if (g.ocIntercept == this) g.ocIntercept = NULL; foreach(QWidget *w, widgetlist) { if (bWasVisible) w->show(); } if (bWasVisible) { if (g.s.bMinimalView && ! g.s.qbaMinimalViewGeometry.isNull()) { g.mw->restoreGeometry(g.s.qbaMinimalViewGeometry); g.mw->restoreState(g.s.qbaMinimalViewState); } else if (! g.s.bMinimalView && ! g.s.qbaMainWindowGeometry.isNull()) { g.mw->restoreGeometry(g.s.qbaMainWindowGeometry); g.mw->restoreState(g.s.qbaMainWindowState); } } #ifdef Q_OS_MAC qApp->setAttribute(Qt::AA_DontUseNativeMenuBar, false); g.mw->setUnifiedTitleAndToolBarOnMac(true); Themes::apply(); #endif setupScene(false); qgv.setAttribute(Qt::WA_WState_Hidden, true); #if QT_VERSION < 0x050000 && (defined(Q_OS_WIN) || defined(Q_OS_MAC)) qt_use_native_dialogs = true; #endif 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, sizeof(OverlayMsgHeader) + om.omh.iLength); g.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; uiWidth = omi->uiWidth; uiHeight = omi->uiHeight; qrLast = QRect(); delete smMem; smMem = new SharedMemory2(this, uiWidth * uiHeight * 4); if (! smMem->data()) { qWarning() << "OverlayClient: Failed to create shared memory" << uiWidth << uiHeight; delete smMem; smMem = NULL; return; } QByteArray key = smMem->name().toUtf8(); key.append(static_cast(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(key.length())); // Name should be auto-generated and short memcpy(om.oms.a_cName, key.constData(), key.length()); qlsSocket->write(om.headerbuffer, sizeof(OverlayMsgHeader) + om.omh.iLength); setupRender(); Overlay *o = static_cast(parent()); QTimer::singleShot(0, o, SLOT(updateOverlay())); } void OverlayClient::readyRead() { while (true) { quint64 ready = static_cast(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(sizeof(OverlayMsgShmem)))) { disconnect(); return; } ready -= sizeof(OverlayMsgHeader); } } if (ready >= static_cast(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(length)); } break; case OVERLAY_MSGTYPE_SHMEM: { if (smMem) smMem->systemRelease(); } break; case OVERLAY_MSGTYPE_PID: { if (length != static_cast(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(parent()); QTimer::singleShot(0, o, SLOT(updateOverlay())); } break; default: break; } omMsg.omh.iLength = -1; } else { break; } } } void OverlayClient::reset() { if (! uiWidth || ! uiHeight || ! smMem) return; delete qgpiLogo; qgpiLogo = NULL; ougUsers.reset(); setupScene(g.ocIntercept == this); } void OverlayClient::setupScene(bool show) { if (show) { qgs.setBackgroundBrush(QColor(0,0,0,64)); if (! qgpiLogo) { qgpiLogo = new OverlayMouse(); qgpiLogo->hide(); qgpiLogo->setOpacity(0.8f); qgpiLogo->setZValue(-5.0f); QImageReader qir(QLatin1String("skin:mumble.svg")); QSize sz = qir.size(); sz.scale(uiWidth, uiHeight, Qt::KeepAspectRatio); qir.setScaledSize(sz); qgpiLogo->setPixmap(QPixmap::fromImage(qir.read())); QRectF qrf = qgpiLogo->boundingRect(); qgpiLogo->setPos(iroundf((uiWidth - qrf.width()) / 2.0f + 0.5f), iroundf((uiHeight - qrf.height()) / 2.0f + 0.5f)); } qgpiCursor->show(); qgs.addItem(qgpiCursor); qgpiLogo->show(); qgs.addItem(qgpiLogo); } else { qgs.setBackgroundBrush(Qt::NoBrush); if (qgpiCursor->scene()) qgs.removeItem(qgpiCursor); qgpiCursor->hide(); if (qgpiLogo) { if (qgpiLogo->scene()) qgs.removeItem(qgpiLogo); qgpiLogo->hide(); } } ougUsers.updateUsers(); updateFPS(); updateTime(); } void OverlayClient::setupRender() { qgs.setSceneRect(0, 0, uiWidth, uiHeight); qgv.setScene(NULL); qgv.setGeometry(-2, -2, uiWidth + 2, uiHeight + 2); qgv.viewport()->setGeometry(0, 0, uiWidth, uiHeight); 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 = uiWidth; om.omb.h = uiHeight; qlsSocket->write(om.headerbuffer, sizeof(OverlayMsgHeader) + sizeof(OverlayMsgBlit)); reset(); } bool OverlayClient::update() { if (! uiWidth || ! uiHeight || ! 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 ®ion) { if (region.isEmpty()) return; qlDirty.append(region); QMetaObject::invokeMethod(this, "render", Qt::QueuedConnection); } void OverlayClient::render() { const QList region = qlDirty; qlDirty.clear(); if (! uiWidth || ! uiHeight || ! 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,uiWidth, uiHeight)); if ((dirty.width() <= 0) || (dirty.height() <= 0)) return; QRect target = dirty; target.moveTo(0,0); QImage img(reinterpret_cast(smMem->data()), uiWidth, uiHeight, 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 = dirty.x(); om.omb.y = dirty.y(); om.omb.w = dirty.width(); om.omb.h = dirty.height(); qlsSocket->write(om.headerbuffer, sizeof(OverlayMsgHeader) + sizeof(OverlayMsgBlit)); } if (qgpiCursor->isVisible()) { active = QRect(0,0,uiWidth,uiHeight); } else { active = qgs.itemsBoundingRect().toAlignedRect(); if (active.isEmpty()) active = QRect(0,0,0,0); active = active.intersected(QRect(0,0,uiWidth,uiHeight)); } 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 = qrLast.x(); om.oma.y = qrLast.y(); om.oma.w = qrLast.width(); om.oma.h = qrLast.height(); qlsSocket->write(om.headerbuffer, sizeof(OverlayMsgHeader) + sizeof(OverlayMsgActive)); } qlsSocket->flush(); } void OverlayClient::openEditor() { OverlayEditor oe(g.mw, &ougUsers); connect(&oe, SIGNAL(applySettings()), this, SLOT(updateLayout())); oe.exec(); }