Browse Source

Distinguish monitors with identical Qt screen names

master
evgeny 4 days ago
parent
commit
3d2f90e8ae
  1. 20
      tools/chatgui/src/stream_publish_engine.cpp
  2. 40
      tools/chatgui/tests/test_stream_publish.cpp

20
tools/chatgui/src/stream_publish_engine.cpp

@ -7,6 +7,8 @@
#include <QScreen>
#include <QRandomGenerator>
#include <QTransform>
#include <QJsonArray>
#include <QJsonDocument>
#include <thread>
#include <mutex>
#include <condition_variable>
@ -33,6 +35,19 @@ namespace {
int64_t monotonicUs() { return std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count(); }
QString text(const char* value) { return QCoreApplication::translate("ChatGui", value); }
#ifdef HAVE_STREAM_CAPTURE
/* Имя QScreen не уникально (например, несколько X11 screens); номер различает одинаковые дескрипторы. */
QByteArray screenId(QScreen* screen) {
int duplicate = 0;
for (auto* other : QGuiApplication::screens()) {
if (other == screen) break;
if (other->name() == screen->name() && other->manufacturer() == screen->manufacturer()
&& other->model() == screen->model() && other->serialNumber() == screen->serialNumber()) duplicate++;
}
return QJsonDocument(QJsonArray{screen->name(), screen->manufacturer(), screen->model(), screen->serialNumber(), duplicate})
.toJson(QJsonDocument::Compact);
}
#endif
}
struct StreamPublishEngine::Impl {
struct Audio { int64_t pts = 0, created = 0; uint64_t sequence = 0; unsigned frames = 0; int16_t pcm[1920]{}; };
@ -148,9 +163,10 @@ QList<StreamVideoSource> StreamPublishEngine::sources() {
|| qEnvironmentVariableIsSet("WAYLAND_DISPLAY")) {
sources.push_back({StreamVideoSource::Screen, text("Screen (system selection)"), "portal", {}});
} else {
int number = 0;
for (auto* screen : QGuiApplication::screens()) {
QSize size = screen->size() * screen->devicePixelRatio();
sources.push_back({StreamVideoSource::Screen, screen->name(), screen->name().toUtf8(), {}, size});
sources.push_back({StreamVideoSource::Screen, QStringLiteral("%1: %2").arg(++number).arg(screen->name()), screenId(screen), {}, size});
DEBUG_DEBUG(DEBUG_CATEGORY_VIDEO, "desktop screen capability source=%s logical=%dx%d scale=%.3f pixels=%dx%d",
qPrintable(screen->name()), screen->size().width(), screen->size().height(), screen->devicePixelRatio(), size.width(), size.height());
}
@ -225,7 +241,7 @@ bool StreamPublishEngine::openSource(const StreamPublishConfig& config) {
m->screen = std::make_unique<QScreenCapture>();
if (config.source.id != "portal") {
QScreen* selected = nullptr;
for (auto* screen : QGuiApplication::screens()) if (screen->name().toUtf8() == config.source.id) selected = screen;
for (auto* screen : QGuiApplication::screens()) if (screenId(screen) == config.source.id) { selected = screen; break; }
if (!selected) { DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "desktop screen disappeared"); return false; }
m->screen->setScreen(selected);
}

40
tools/chatgui/tests/test_stream_publish.cpp

@ -223,9 +223,11 @@ private slots:
auto* modeBox = controls.findChild<QComboBox*>("streamVideoMode");
auto sources = StreamPublishEngine::sources();
QSignalSpy changes(&controls, &StreamPublishWindow::settingsChanged);
QList<QByteArray> screenIds;
unsigned screens = 0;
for (int i = 0; i < sources.size(); i++) {
if (sources[i].kind != StreamVideoSource::Screen || sources[i].size.isEmpty()) continue;
QVERIFY(!screenIds.contains(sources[i].id)); screenIds.append(sources[i].id);
screens++; sourceBox->setCurrentIndex(-1); changes.clear(); sourceBox->setCurrentIndex(i);
QSize size = stream_video_screen_size(sources[i].size);
auto mode = modeBox->currentData().toList();
@ -237,6 +239,41 @@ private slots:
}
if (!screens) QSKIP("No enumerated screens");
}
void nativeScreenCapture() {
#ifdef HAVE_STREAM_CAPTURE
if (!qEnvironmentVariableIsSet("UTUN_TEST_SCREEN_CAPTURE")) QSKIP("Run with X11 screen capture enabled");
StreamPublishEngine engine; QTRY_VERIFY_WITH_TIMEOUT(!engine.codecs().isEmpty(), 15000);
QSignalSpy errors(&engine, &StreamPublishEngine::errorOccurred);
StreamPublishConfig config; config.audio = false; config.nativeSize = true; config.automaticBitrate = true;
config.video.width = 640; config.video.height = 360; config.video.fps = 15;
uint64_t epoch = 0; unsigned captured = 0;
for (const auto& source : engine.sources()) {
if (source.kind != StreamVideoSource::Screen || source.size.isEmpty()) continue;
QSize expected = stream_video_screen_size(source.size);
config.source = source; config.source.size = {}; // Как portal: размер станет известен только из первого кадра.
if (!engine.active()) {
QVERIFY(engine.start(group, config));
stream_member_state member{};
QTRY_VERIFY_WITH_TIMEOUT(stream_member_load(inst, group, inst->node_id, &member, nullptr) == STREAM_OK && member.published, 5000);
epoch = member.source.source_epoch; QVERIFY(gui_stream_open(204, group, inst->node_id, epoch));
StreamRecord opened; QVERIFY(record(204, StreamRecordKind::Opened, opened));
} else QVERIFY(engine.reconfigure(config));
bool matched = false; StreamRecord frame; QElapsedTimer elapsed; elapsed.start();
while (!matched && elapsed.elapsed() < 8000) {
if (gui_stream_pull(204, frame, 0, true, 60000000) && frame.kind == StreamRecordKind::Video)
matched = frame.format.width == expected.width() && frame.format.height == expected.height();
QTest::qWait(1);
}
QVERIFY(matched); QCOMPARE(engine.config().source.id, source.id); QCOMPARE(engine.config().source.size, source.size);
QCOMPARE(engine.config().video.width, expected.width()); QCOMPARE(engine.config().video.height, expected.height());
QVERIFY(errors.isEmpty()); captured++;
}
if (!captured) QSKIP("No enumerated screens");
engine.stop(); QTRY_VERIFY_WITH_TIMEOUT(!engine.active(), 4000); gui_stream_stop(204);
#else
QSKIP("Screen capture unavailable");
#endif
}
void broadcastAllowsRadioMicrophone() {
AudioCaptureLease call, radio(AudioCaptureLease::Mode::Radio), recorder, publisher(AudioCaptureLease::Mode::Exclusive);
QVERIFY(call.acquire()); QVERIFY(radio.acquire()); QVERIFY(recorder.acquire()); QVERIFY(!publisher.acquire());
@ -368,7 +405,8 @@ private slots:
pattern.setStyleSheet("background: #c04020; color: white; font-size: 20px;"); pattern.show(); QTest::qWait(100);
StreamPublishEngine engine; QTRY_VERIFY_WITH_TIMEOUT(!engine.codecs().isEmpty(), 15000);
StreamPublishConfig config; config.video.width = 640; config.video.height = 360; config.video.fps = 15;
config.source = {StreamVideoSource::Screen, "Virtual screen", QGuiApplication::primaryScreen()->name().toUtf8(), {}};
for (const auto& source : engine.sources()) if (source.kind == StreamVideoSource::Screen) { config.source = source; break; }
QVERIFY(!config.source.id.isEmpty());
QSignalSpy errors(&engine, &StreamPublishEngine::errorOccurred);
QVERIFY(engine.start(group, config));
QTRY_VERIFY_WITH_TIMEOUT(engine.config().video.codec == STREAM_MEDIA_H264, 10000);

Loading…
Cancel
Save