From 898154b2b6798c23ea06e47b3f535c3ef1b498ba Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 15 Oct 2026 00:45:22 +0200 Subject: [PATCH] Keep desktop video in a resizable window and retain decoder on audio clock updates --- tools/chatgui/src/call_video_engine.cpp | 4 +- tools/chatgui/src/callwindow.cpp | 54 ++++++++++++++++--------- tools/chatgui/src/callwindow.h | 3 +- 3 files changed, 37 insertions(+), 24 deletions(-) diff --git a/tools/chatgui/src/call_video_engine.cpp b/tools/chatgui/src/call_video_engine.cpp index 458c7d21..16485217 100644 --- a/tools/chatgui/src/call_video_engine.cpp +++ b/tools/chatgui/src/call_video_engine.cpp @@ -258,8 +258,8 @@ void CallVideoEngine::receive() { call_audio_get_clock(m->id, now, &clock); if (clock.valid) { if (sourceEpoch && sourceEpoch != clock.source_epoch) { - pictures.clear(); decoder.close(); call_video_port_request_key(m->id); lastShown = -1; - DEBUG_INFO(DEBUG_CATEGORY_CALL, "video playback audio source epoch changed"); + DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "video audio clock epoch %llu -> %llu; decoder retained", + (unsigned long long)sourceEpoch, (unsigned long long)clock.source_epoch); } sourceEpoch = clock.source_epoch; QImage selected; diff --git a/tools/chatgui/src/callwindow.cpp b/tools/chatgui/src/callwindow.cpp index 5006aed1..3f6247da 100644 --- a/tools/chatgui/src/callwindow.cpp +++ b/tools/chatgui/src/callwindow.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -34,6 +35,26 @@ enum class Glyph { Phone, PhoneDown, Mic, MicOff }; static QString callText(const char* text) { return QCoreApplication::translate("ChatGui", text); } +// Только изображение и миниатюра; подписи/настройки находятся в отдельной панели окна. +class CallVideoCanvas : public QWidget { +public: + explicit CallVideoCanvas(QWidget* parent) : QWidget(parent) { setMinimumSize(160, 120); } + QImage remote, local; +protected: + void paintEvent(QPaintEvent*) override { + QPainter painter(this); painter.fillRect(rect(), Qt::black); painter.setRenderHint(QPainter::SmoothPixmapTransform); + if (remote.isNull()) return; + QSize fitted = remote.size().scaled(size(), Qt::KeepAspectRatio); + QRect videoRect(QPoint((width() - fitted.width()) / 2, (height() - fitted.height()) / 2), fitted); + painter.drawImage(videoRect, remote); + if (!local.isNull()) { + QSize preview = local.size().scaled(qMin(200, fitted.width() / 4), qMin(150, fitted.height() / 4), Qt::KeepAspectRatio); + QRect target(QPoint(videoRect.right() - preview.width() - 12, videoRect.top() + 12), preview); + painter.setPen(QPen(Qt::white, 2)); painter.drawRect(target.adjusted(-1, -1, 1, 1)); painter.drawImage(target, local); + } + } +}; + static QColor colorForName(const QString& name) { static const QColor palette[] = { QColor("#5B9BD5"), QColor("#70AD47"), QColor("#ED7D31"), @@ -193,11 +214,11 @@ CallWindow::CallWindow(QWidget* parent) " padding: 6px 10px; font-size: 13px; background: palette(base); }"); auto* root = new QVBoxLayout(this); - root->setContentsMargins(12, 12, 12, 12); root->addStretch(1); + root->setContentsMargins(12, 12, 12, 12); + m_videoArea = new CallVideoCanvas(this); m_videoArea->hide(); root->addWidget(m_videoArea, 1); m_panel = new QWidget(this); m_panel->setObjectName("callPanel"); m_panel->setMaximumWidth(640); m_panel->setStyleSheet("QWidget#callPanel { background: palette(window); border-radius: 12px; }"); root->addWidget(m_panel, 0, Qt::AlignHCenter | Qt::AlignBottom); - root->addStretch(1); QVBoxLayout* lay = new QVBoxLayout(m_panel); lay->setContentsMargins(16, 20, 16, 16); lay->setSpacing(8); @@ -246,11 +267,13 @@ CallWindow::CallWindow(QWidget* parent) }); connect(m_video, &CallVideoEngine::imageChanged, this, [this](const QImage& image) { bool changed = m_remoteImage.isNull() != image.isNull(); m_remoteImage = image; + static_cast(m_videoArea)->remote = image; m_videoArea->update(); if (changed) updateVideoPresentation(); update(); }); connect(m_video, &CallVideoEngine::localImageChanged, this, [this](const QImage& image) { bool changed = m_localImage.isNull() != image.isNull(); m_localImage = image; + static_cast(m_videoArea)->local = image; m_videoArea->update(); if (!image.isNull()) { QSize size = image.size().scaled(200, 150, Qt::KeepAspectRatio); m_localPreview->setFixedSize(size); m_localPreview->setPixmap(QPixmap::fromImage(image).scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation)); @@ -397,11 +420,17 @@ void CallWindow::updateVideoPresentation() { m_receivingVideo = receiving; DEBUG_INFO(DEBUG_CATEGORY_CALL, "call video presentation remote=%d local=%d", receiving, !m_localImage.isNull()); if (receiving) { - m_settingsBtn->setChecked(false); m_windowGeometry = saveGeometry(); showFullScreen(); - } else leaveFullScreen(); + m_settingsBtn->setChecked(false); m_windowGeometry = saveGeometry(); + QSize available = screen() ? screen()->availableGeometry().size() : QSize(1280, 900); + resize(qMin(1100, available.width() * 9 / 10), qMin(800, available.height() * 9 / 10)); + } else { + leaveFullScreen(); + if (!m_windowGeometry.isEmpty()) restoreGeometry(m_windowGeometry); + } } bool active = m_state == Active, settings = active && m_settingsBtn->isChecked(); - static_cast(layout())->setStretch(2, receiving ? 0 : 1); + m_videoArea->setVisible(receiving); + layout()->setAlignment(m_panel, receiving ? Qt::AlignHCenter | Qt::AlignBottom : Qt::AlignCenter); m_localPreview->setVisible(active && !receiving && !m_localImage.isNull()); m_avatarLabel->setVisible(!receiving); m_statusLabel->setVisible(!receiving); m_connectionLabel->setVisible(settings); m_bgpPathLabel->setVisible(settings && !m_bgpPathLabel->text().isEmpty()); @@ -413,21 +442,6 @@ void CallWindow::updateVideoPresentation() { update(); } -void CallWindow::paintEvent(QPaintEvent* event) { - QWidget::paintEvent(event); - if (!m_receivingVideo) return; - QPainter painter(this); painter.fillRect(rect(), Qt::black); painter.setRenderHint(QPainter::SmoothPixmapTransform); - QSize remote = m_remoteImage.size().scaled(size(), Qt::KeepAspectRatio); - QRect videoRect(QPoint((width() - remote.width()) / 2, (height() - remote.height()) / 2), remote); - painter.drawImage(videoRect, m_remoteImage); - if (!m_localImage.isNull()) { - QSize preview = m_localImage.size().scaled(qMin(240, remote.width() / 4), qMin(180, remote.height() / 4), Qt::KeepAspectRatio); - QRect target(QPoint(videoRect.right() - preview.width() - 20, videoRect.top() + 20), preview); - painter.setPen(QPen(Qt::white, 2)); painter.drawRect(target.adjusted(-1, -1, 1, 1)); - painter.drawImage(target, m_localImage); - } -} - void CallWindow::keyPressEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Escape && isFullScreen()) { toggleFullScreen(); event->accept(); return; } if (event->key() == Qt::Key_F11 && m_receivingVideo) { toggleFullScreen(); event->accept(); return; } diff --git a/tools/chatgui/src/callwindow.h b/tools/chatgui/src/callwindow.h index 5c47f573..4270a5f8 100644 --- a/tools/chatgui/src/callwindow.h +++ b/tools/chatgui/src/callwindow.h @@ -37,7 +37,6 @@ signals: protected: void closeEvent(QCloseEvent* event) override; - void paintEvent(QPaintEvent* event) override; void keyPressEvent(QKeyEvent* event) override; void mouseDoubleClickEvent(QMouseEvent* event) override; @@ -62,7 +61,7 @@ private: QComboBox *m_camera, *m_resolution, *m_codec; QSpinBox *m_videoMin, *m_videoMax; QLabel *m_localPreview, *m_videoStatus; - QWidget *m_videoControls, *m_panel; + QWidget *m_videoControls, *m_panel, *m_videoArea; QPushButton *m_settingsBtn, *m_fullScreenBtn; QImage m_remoteImage, m_localImage; QByteArray m_windowGeometry;