Show audio output captions and validate the desktop call settings menus

This commit is contained in:
evgeny committed 2026-10-28 14:08:21 +01:00
1 parent 2540909fda
commit bb0fa13c8b
2 files changed
+68 -2

No files matched your search

+4 -2
View File
@@ -135,11 +135,13 @@ inline QIcon controlIcon(const QPalette& palette, const QString& shape, const QS
painter.drawRoundedRect(QRectF(15, 3, 10, 19), 5, 5); painter.drawArc(QRectF(10, 12, 20, 16), 180 * 16, 180 * 16);
painter.drawLine(20, 28, 20, 33); painter.drawLine(14, 33, 26, 33);
painter.restore(); labelRect = QRectF(1, 23, 38, 13); labelSize = 9;
} else if (shape == "sound" || shape == "silent") {
} else if (shape == "sound" || shape == "silent" || shape == "output") {
if (shape == "output") { painter.save(); painter.translate(7, 0); painter.scale(0.65, 0.65); }
QPolygon speaker; speaker << QPoint(6, 14) << QPoint(13, 14) << QPoint(22, 7) << QPoint(22, 29)
<< QPoint(13, 22) << QPoint(6, 22); painter.drawPolygon(speaker);
if (shape == "sound") painter.drawArc(QRectF(18, 6, 17, 24), -70 * 16, 140 * 16);
if (shape != "silent") painter.drawArc(QRectF(18, 6, 17, 24), -70 * 16, 140 * 16);
else { painter.drawLine(27, 13, 35, 23); painter.drawLine(27, 23, 35, 13); }
if (shape == "output") { painter.restore(); labelRect = QRectF(1, 23, 38, 13); labelSize = 9; }
} else {
painter.drawRoundedRect(QRectF(3, 5, 34, 26), 4, 4);
if (!detail.isEmpty()) labelRect = QRectF(3, 16, 34, 15);
@@ -2,12 +2,17 @@
* Linux hooks наблюдают границу native trampoline и затем вызывают его без подмены освобождения payload. */
#include "../src/callwindow.h"
#include "../src/call_camera_capture.h"
#include "../src/call_video_engine.h"
#include "../transport/gui_bridge.h"
#include <QtTest>
#include <QLabel>
#include <QPushButton>
#include <QPointer>
#include <QScopeGuard>
#include <QMenuBar>
#include <QMenu>
#include <QActionGroup>
#include <QSpinBox>
#include <atomic>
#include <thread>
#include <cstring>
@@ -154,6 +159,65 @@ private slots:
observed.clear(); bindingValid = true;
QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
}
void videoSettingsMenus() {
CallWindow window; window.setCallId(77); window.setPeerName(QStringLiteral("Video call")); window.setState(CallWindow::Active);
auto* engine = window.findChild<CallVideoEngine*>(); QVERIFY(engine);
auto* capture = engine->findChild<CallCameraCapture*>(); QVERIFY(capture);
QObject::disconnect(capture, nullptr, engine, nullptr); // Детерминированные режимы, без открытия камеры/звонка.
CallCameraDevice first{QStringLiteral("Main camera"), QByteArray("main"), {}};
stream_video_add_camera_modes(first.modes, QSize(1920, 1080), 29.97, 29.97);
stream_video_add_camera_modes(first.modes, QSize(1280, 720), 15, 60);
CallCameraDevice second{QStringLiteral("Secondary camera"), QByteArray("secondary"), {}};
stream_video_add_camera_modes(second.modes, QSize(640, 480), 7, 7);
emit engine->camerasChanged({first, second});
auto getMenu = [&window](const char* name) { return window.findChild<QMenu*>(name); };
auto chosen = [](QMenu* menu) { auto* action = menu->findChild<QActionGroup*>()->checkedAction(); return action ? action->data() : QVariant(); };
auto choose = [](QMenu* menu, const QVariant& value) {
for (auto* action : menu->actions()) if (action->isCheckable() && action->data() == value) { action->trigger(); return; }
QFAIL("Missing video setting");
};
auto* camera = getMenu("callVideoCamera"); auto* resolution = getMenu("callVideoResolution"); auto* fps = getMenu("callVideoFps");
QVERIFY(camera); QVERIFY(resolution); QVERIFY(fps); QCOMPARE(chosen(camera).toByteArray(), first.id);
choose(resolution, QSize(1280, 720)); choose(fps, 60); QCOMPARE(chosen(fps).toInt(), 60);
const QImage previousResolution = resolution->menuAction()->icon().pixmap(40, 36).toImage();
choose(resolution, QSize(1920, 1080)); QCOMPARE(chosen(fps).toInt(), 30);
QVERIFY(resolution->menuAction()->icon().pixmap(40, 36).toImage() != previousResolution);
for (auto* action : fps->actions()) if (action->isCheckable())
QCOMPARE(action->isEnabled(), action->data().toInt() <= 30);
choose(camera, second.id); QCOMPARE(chosen(resolution).toSize(), QSize(640, 480)); QCOMPARE(chosen(fps).toInt(), 7);
emit engine->camerasChanged({second, first}); QCOMPARE(chosen(camera).toByteArray(), second.id); QCOMPARE(chosen(fps).toInt(), 7);
choose(camera, first.id); choose(resolution, QSize(1920, 1080)); choose(getMenu("callVideoCodec"), 1);
auto* minimum = window.findChild<QSpinBox*>("callVideoMinimumBitrate"); QVERIFY(minimum);
auto* maximum = window.findChild<QSpinBox*>("callVideoMaximumBitrate"); QVERIFY(maximum);
minimum->setValue(2000); maximum->setValue(1000); QCOMPARE(minimum->value(), 1000);
minimum->setValue(300); maximum->setValue(4000);
auto* settings = window.findChild<QPushButton*>("callVideoSettings"); QVERIFY(settings);
settings->click(); window.show(); QTest::qWait(20);
auto* bar = window.findChild<QMenuBar*>("callVideoControls"); QVERIFY(bar); QVERIFY(bar->isVisible());
QRect previous;
for (auto* action : bar->actions()) {
QRect button = bar->actionGeometry(action); QCOMPARE(button.size(), QSize(48, 48));
if (!previous.isNull()) QCOMPARE(button.left() - previous.right() - 1, 4);
previous = button; QVERIFY(bar->rect().contains(button));
QVERIFY(!action->menu()->actions().first()->text().isEmpty()); QVERIFY(!action->toolTip().isEmpty());
}
const QString screenshot = qEnvironmentVariable("UTUN_TEST_CALL_CONTROLS_SCREENSHOT");
if (!screenshot.isEmpty()) QVERIFY(window.grab().save(screenshot));
auto* action = camera->menuAction(); const QIcon original = action->icon();
QPixmap marker(80, 72); marker.fill(Qt::magenta); action->setIcon(QIcon(marker));
const QImage rendered = bar->grab().toImage(); QRect iconBounds;
for (int y = 0; y < rendered.height(); y++) for (int x = 0; x < rendered.width(); x++) {
QColor color = rendered.pixelColor(x, y);
if (color.red() > 240 && color.blue() > 240 && color.green() < 15) iconBounds |= QRect(x, y, 1, 1);
}
action->setIcon(original); QCOMPARE(iconBounds.size(), QSize(40, 36) * rendered.devicePixelRatio());
QTest::mouseClick(bar, Qt::LeftButton, {}, bar->actionGeometry(resolution->menuAction()).center()); QTRY_VERIFY(resolution->isVisible());
QTest::mouseClick(bar, Qt::LeftButton, {}, bar->actionGeometry(fps->menuAction()).center());
QTRY_VERIFY(fps->isVisible()); QVERIFY(!resolution->isVisible());
QTest::mouseClick(bar, Qt::LeftButton, {}, bar->actionGeometry(fps->menuAction()).center()); QTRY_VERIFY(!fps->isVisible());
settings->click(); QVERIFY(!bar->isVisible()); window.setState(CallWindow::Ended);
DEBUG_INFO(DEBUG_CATEGORY_CALL, "call settings verified: rendered_icon=40x36 buttons=48x48 spacing=4 source/FPS/menu switching passed");
}
void windowAction_data() {
QTest::addColumn<QString>("scenario"); QTest::addColumn<QString>("action");
QStringList failures{"unavailable", "stopped", "cancelled", "executed"};