Test X11 cursor placement and live visibility controls
This commit is contained in:
1 parent
ae1682bbe2
commit
cf3c8c7c34
1 file changed
+69
@@ -11,6 +11,8 @@
|
||||
#include <QJsonObject>
|
||||
#include <QScreen>
|
||||
#include <QLoggingCategory>
|
||||
#include <QScopeGuard>
|
||||
#include <QPainter>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <cmath>
|
||||
@@ -40,6 +42,13 @@ extern "C" {
|
||||
#include <QVideoSink>
|
||||
#include <QVideoFrame>
|
||||
#endif
|
||||
#ifdef UTUN_STREAM_X11_CURSOR
|
||||
#include "../src/stream_screen_cursor.h"
|
||||
#include <QtGui/qguiapplication_platform.h>
|
||||
#include <X11/Xlib.h>
|
||||
#undef None
|
||||
#undef Status
|
||||
#endif
|
||||
|
||||
#ifdef TEST_NULL_AUDIO
|
||||
static ma_device_data_proc captureCallback;
|
||||
@@ -137,6 +146,8 @@ private slots:
|
||||
for (int i = 0; i < sources.size(); i++) if (sources[i].kind == StreamVideoSource::Screen && !sources[i].size.isEmpty()) { screen = i; break; }
|
||||
if (screen < 0) QSKIP("No enumerated screen for capture settings");
|
||||
boxes[0]->setCurrentIndex(screen);
|
||||
auto* cursor = controls.findChild<QCheckBox*>("streamShowCursor"); QVERIFY(cursor); QVERIFY(cursor->isChecked());
|
||||
cursor->setChecked(false);
|
||||
QSize maximum = stream_video_screen_size(sources[screen].size);
|
||||
QCOMPARE(boxes[1]->currentData().toList(), (QVariantList{maximum.width(), maximum.height(), 30}));
|
||||
QVERIFY(boxes[1]->count() >= 6);
|
||||
@@ -153,6 +164,7 @@ private slots:
|
||||
QVERIFY(!stored.value("native_size").toBool());
|
||||
QCOMPARE(stored.value("bitrate").toInt(), 4000000); QCOMPARE(stored.value("rotation").toInt(), 270);
|
||||
QVERIFY(!stored.value("audio").toBool());
|
||||
QVERIFY(!stored.value("show_cursor").toBool(true));
|
||||
StreamPublishEngine reopenedEngine;
|
||||
StreamPublishWindow reopened(&reopenedEngine, group, nullptr, saved);
|
||||
QSignalSpy changes(&reopened, &StreamPublishWindow::settingsChanged);
|
||||
@@ -163,6 +175,7 @@ private slots:
|
||||
if (StreamPublishEngine::supported()) QTRY_COMPARE(boxes[2]->currentData().toInt(), stored.value("codec").toInt());
|
||||
QCOMPARE(reopened.findChild<QButtonGroup*>()->checkedId(), 270);
|
||||
QVERIFY(!reopened.findChildren<QCheckBox*>().first()->isChecked());
|
||||
QVERIFY(!reopened.findChild<QCheckBox*>("streamShowCursor")->isChecked());
|
||||
QVERIFY(changes.isEmpty()); // Асинхронный список кодеков не перезаписывает сохранённый выбор.
|
||||
boxes[3]->setCurrentIndex(0);
|
||||
QCOMPARE(changes.size(), 1);
|
||||
@@ -272,6 +285,62 @@ private slots:
|
||||
engine.stop(); QTRY_VERIFY_WITH_TIMEOUT(!engine.active(), 4000); gui_stream_stop(204);
|
||||
#else
|
||||
QSKIP("Screen capture unavailable");
|
||||
#endif
|
||||
}
|
||||
void screenCursorOverlay() {
|
||||
#ifdef UTUN_STREAM_X11_CURSOR
|
||||
if (!qEnvironmentVariableIsSet("UTUN_TEST_SCREEN_CAPTURE")) QSKIP("Run under isolated Xvfb");
|
||||
auto* native = qGuiApp->nativeInterface<QNativeInterface::QX11Application>(); QVERIFY(native);
|
||||
Display* display = native->display();
|
||||
for (auto* screen : QGuiApplication::screens()) {
|
||||
QWindow target(screen); target.setFlags(Qt::FramelessWindowHint | Qt::BypassWindowManagerHint);
|
||||
target.setGeometry(QRect(screen->geometry().topLeft() + QPoint(20, 20), QSize(200, 160))); target.show();
|
||||
QVERIFY(QTest::qWaitForWindowExposed(&target));
|
||||
char bits[32]; memset(bits, 0xff, sizeof(bits));
|
||||
Pixmap bitmap = XCreateBitmapFromData(display, target.winId(), bits, 16, 16); QVERIFY(bitmap);
|
||||
auto releaseBitmap = qScopeGuard([&] { XFreePixmap(display, bitmap); });
|
||||
XColor foreground{}, background{}; foreground.red = foreground.blue = 65535;
|
||||
Cursor cursor = XCreatePixmapCursor(display, bitmap, bitmap, &foreground, &background, 3, 4); QVERIFY(cursor);
|
||||
auto releaseCursor = qScopeGuard([&] { XFreeCursor(display, cursor); });
|
||||
XDefineCursor(display, target.winId(), cursor);
|
||||
XWarpPointer(display, 0, target.winId(), 0, 0, 0, 0, 40, 50); XSync(display, false);
|
||||
QPoint position(qRound(20 * screen->devicePixelRatio()) + 40, qRound(20 * screen->devicePixelRatio()) + 50);
|
||||
StreamScreenCursor overlay(screen);
|
||||
QImage frame(screen->size() * screen->devicePixelRatio(), QImage::Format_RGB32);
|
||||
frame.setDevicePixelRatio(screen->devicePixelRatio()); frame.fill(Qt::black);
|
||||
QVERIFY(overlay.draw(frame));
|
||||
QImage expected(frame.size(), QImage::Format_RGB32); expected.fill(Qt::black);
|
||||
{ QPainter painter(&expected); painter.fillRect(QRect(position - QPoint(3, 4), QSize(16, 16)), Qt::magenta); }
|
||||
QCOMPARE(frame, expected); // Включая hotspot, 64-битный Xlib и физические пиксели HiDPI.
|
||||
for (auto* other : QGuiApplication::screens()) {
|
||||
if (other == screen) continue;
|
||||
StreamScreenCursor otherOverlay(other);
|
||||
QImage otherFrame(other->size() * other->devicePixelRatio(), QImage::Format_RGB32); otherFrame.fill(Qt::black);
|
||||
QImage blank = otherFrame;
|
||||
QVERIFY(otherOverlay.draw(otherFrame)); QCOMPARE(otherFrame, blank);
|
||||
}
|
||||
StreamPublishEngine engine; QTRY_VERIFY_WITH_TIMEOUT(!engine.codecs().isEmpty(), 15000);
|
||||
StreamPublishConfig config; config.audio = false;
|
||||
for (const auto& source : engine.sources()) if (source.kind == StreamVideoSource::Screen && source.screen == screen) config.source = source;
|
||||
QVERIFY(config.source.screen); QVERIFY(config.showCursor);
|
||||
config.video.width = 320; config.video.height = 180;
|
||||
QSignalSpy errors(&engine, &StreamPublishEngine::errorOccurred);
|
||||
QVERIFY(engine.start(group, config));
|
||||
auto cursorShown = [&] {
|
||||
QImage preview = engine.previewFrame().value<QVideoFrame>().toImage();
|
||||
return !preview.isNull() && preview.pixelColor(position) == QColor(Qt::magenta);
|
||||
};
|
||||
QTRY_VERIFY_WITH_TIMEOUT(cursorShown(), 5000);
|
||||
StreamPublishWindow controls(&engine, group);
|
||||
auto* toggle = controls.findChild<QCheckBox*>("streamShowCursor"); QVERIFY(toggle); QVERIFY(toggle->isChecked());
|
||||
toggle->setChecked(false); QVERIFY(!engine.config().showCursor);
|
||||
QTRY_VERIFY_WITH_TIMEOUT(!cursorShown(), 5000);
|
||||
toggle->setChecked(true); QVERIFY(engine.config().showCursor);
|
||||
QTRY_VERIFY_WITH_TIMEOUT(cursorShown(), 5000);
|
||||
QVERIFY(errors.isEmpty()); engine.stop(); QTRY_VERIFY_WITH_TIMEOUT(!engine.active(), 4000);
|
||||
}
|
||||
#else
|
||||
QSKIP("X11 cursor unavailable");
|
||||
#endif
|
||||
}
|
||||
void broadcastAllowsRadioMicrophone() {
|
||||
|
||||
Reference in new issue
Block a user