From 5753177f29547bd56d7a80661b15c08db45f765d Mon Sep 17 00:00:00 2001 From: evgeny Date: Fri, 16 Oct 2026 14:14:03 +0200 Subject: [PATCH] Include the cursor in X11 stream frames by default --- tools/chatgui/src/stream_publish_engine.cpp | 31 +++++++++++++++++++-- tools/chatgui/src/stream_publish_engine.h | 2 ++ tools/chatgui/src/stream_screen_cursor.h | 1 + 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/tools/chatgui/src/stream_publish_engine.cpp b/tools/chatgui/src/stream_publish_engine.cpp index da7c9853..183f6804 100644 --- a/tools/chatgui/src/stream_publish_engine.cpp +++ b/tools/chatgui/src/stream_publish_engine.cpp @@ -31,6 +31,9 @@ extern "C" { #include #include #endif +#ifdef UTUN_STREAM_X11_CURSOR +#include "stream_screen_cursor.h" +#endif namespace { int64_t monotonicUs() { return std::chrono::duration_cast( @@ -75,6 +78,9 @@ struct StreamPublishEngine::Impl { std::unique_ptr camera; std::unique_ptr screen; std::unique_ptr screenGrabTimer; +#ifdef UTUN_STREAM_X11_CURSOR + std::unique_ptr cursor; +#endif std::unique_ptr window; QVideoFrame frame; QVideoFrame previewFrame; @@ -141,6 +147,13 @@ QVariant StreamPublishEngine::previewFrame() const { #endif } QList StreamPublishEngine::codecs() const { std::lock_guard guard(m->lock); return m->codecs; } +bool StreamPublishEngine::cursorSupported(const StreamVideoSource& source) { +#ifdef UTUN_STREAM_X11_CURSOR + return source.kind == StreamVideoSource::Screen && source.screen && source.id != "portal" && QGuiApplication::platformName() == "xcb"; +#else + Q_UNUSED(source); return false; +#endif +} bool StreamPublishEngine::muted() const { return m->muted.load(); } void StreamPublishEngine::setMuted(bool muted) { if (!m->running || !config().audio) { @@ -256,6 +269,9 @@ bool StreamPublishEngine::openSource(const StreamPublishConfig& config) { } // Qt X11 screen capture ищет root по имени и путает мониторы; QScreen захватывает именно выбранный экран. if (selected && QGuiApplication::platformName() == "xcb") { +#ifdef UTUN_STREAM_X11_CURSOR + m->cursor = std::make_unique(selected); +#endif m->screenGrabTimer = std::make_unique(); m->screenGrabTimer->setTimerType(Qt::PreciseTimer); connect(m->screenGrabTimer.get(), &QTimer::timeout, this, [this, generation, screen = QPointer(selected), failed] { if (generation != m->sourceGeneration || !m->running) return; @@ -266,6 +282,12 @@ bool StreamPublishEngine::openSource(const StreamPublishConfig& config) { m->screenGrabTimer->stop(); QMetaObject::invokeMethod(this, [failed] { failed(text("Video capture stopped")); }); return; } +#ifdef UTUN_STREAM_X11_CURSOR + if (this->config().showCursor && !m->cursor->draw(image)) { + m->screenGrabTimer->stop(); + QMetaObject::invokeMethod(this, [failed] { failed(text("Could not capture the mouse cursor")); }); return; + } +#endif #if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) QVideoFrame frame(image); #else @@ -284,8 +306,8 @@ bool StreamPublishEngine::openSource(const StreamPublishConfig& config) { frame.setStartTime(captured); m->sink->setVideoFrame(frame); }); m->screenGrabTimer->start(std::max(1, 1000 / std::clamp(config.video.fps, 1, 60))); - DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "desktop X11 screen grab started source=%s interval_ms=%d", - qPrintable(config.source.name), m->screenGrabTimer->interval()); + DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "desktop X11 screen grab started source=%s interval_ms=%d cursor=%d supported=%d", + qPrintable(config.source.name), m->screenGrabTimer->interval(), config.showCursor, cursorSupported(config.source)); } else { m->screen = std::make_unique(); m->screen->setScreen(selected); @@ -318,6 +340,9 @@ bool StreamPublishEngine::openSource(const StreamPublishConfig& config) { void StreamPublishEngine::closeSource() { #ifdef HAVE_STREAM_CAPTURE m->screenGrabTimer.reset(); +#ifdef UTUN_STREAM_X11_CURSOR + m->cursor.reset(); +#endif emit previewVideoChanged(QVariant()); if (m->sink) disconnect(m->sink.get(), nullptr, this, nullptr); if (m->camera) { disconnect(m->camera.get(), nullptr, this, nullptr); m->camera->stop(); } @@ -367,6 +392,8 @@ bool StreamPublishEngine::reconfigure(const StreamPublishConfig& config) { DEBUG_WARN(DEBUG_CATEGORY_STREAM, "desktop microphone selection changed during live publication"); emit errorOccurred(text("Change the microphone before starting the stream")); return false; } + if (config.showCursor != previous.showCursor) + DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "desktop cursor selected enabled=%d supported=%d", config.showCursor, cursorSupported(config.source)); bool sourceChange = !config.source.sameSource(previous.source) || (config.source.kind == StreamVideoSource::Camera && (config.video.width != previous.video.width || config.video.height != previous.video.height || config.video.fps != previous.video.fps)); { std::lock_guard guard(m->lock); m->wanted = config; m->generation++; m->started = monotonicUs(); diff --git a/tools/chatgui/src/stream_publish_engine.h b/tools/chatgui/src/stream_publish_engine.h index e4342901..57bcbd52 100644 --- a/tools/chatgui/src/stream_publish_engine.h +++ b/tools/chatgui/src/stream_publish_engine.h @@ -34,6 +34,7 @@ struct StreamPublishConfig { LiveVideoConfig video; QString microphone; bool audio = true; + bool showCursor = true; bool nativeSize = false, automaticBitrate = false; }; class StreamPublishEngine : public QObject { @@ -52,6 +53,7 @@ public: QList codecs() const; static QList sources(); static bool supported(); + static bool cursorSupported(const StreamVideoSource& source); void setMuted(bool muted); bool muted() const; signals: diff --git a/tools/chatgui/src/stream_screen_cursor.h b/tools/chatgui/src/stream_screen_cursor.h index c5f5af6b..b531e5f3 100644 --- a/tools/chatgui/src/stream_screen_cursor.h +++ b/tools/chatgui/src/stream_screen_cursor.h @@ -6,6 +6,7 @@ #include #include +struct _XDisplay; class StreamScreenCursor { public: explicit StreamScreenCursor(QScreen* screen);