From 1330376220f3440cc01f8f70161bec4b88f58533 Mon Sep 17 00:00:00 2001 From: Aleix Pol Date: Tue, 5 Oct 2021 19:06:28 +0200 Subject: [PATCH] screencast: Support the creation of virtual displays to cast In case the user has just the one display but they don't want to show it in their main workspace when sharing video, allow creating a virtual display. This also will allow using remote devices as support displays. --- src/platform.cpp | 13 ++ src/platform.h | 3 + src/plugins/platforms/drm/drm_backend.cpp | 19 ++- src/plugins/platforms/drm/drm_backend.h | 2 + src/plugins/platforms/drm/drm_gpu.cpp | 7 +- src/plugins/platforms/drm/drm_gpu.h | 3 + .../platforms/drm/drm_virtual_output.cpp | 25 ++-- .../platforms/drm/drm_virtual_output.h | 4 +- .../platforms/wayland/wayland_backend.cpp | 129 +++++++++++------- .../platforms/wayland/wayland_backend.h | 8 ++ src/plugins/screencast/screencastmanager.cpp | 28 +++- src/plugins/screencast/screencastmanager.h | 19 ++- 12 files changed, 181 insertions(+), 79 deletions(-) diff --git a/src/platform.cpp b/src/platform.cpp index b176c12290..48a6fc32da 100644 --- a/src/platform.cpp +++ b/src/platform.cpp @@ -478,6 +478,19 @@ RenderLoop *Platform::renderLoop() const return nullptr; } +AbstractOutput *Platform::createVirtualOutput(const QString &name, const QSize &size, double scale) +{ + Q_UNUSED(name); + Q_UNUSED(size); + Q_UNUSED(scale); + return nullptr; +} + +void Platform::removeVirtualOutput(AbstractOutput *output) +{ + Q_ASSERT(!output); +} + void Platform::warpPointer(const QPointF &globalPos) { Q_UNUSED(globalPos) diff --git a/src/platform.h b/src/platform.h index ecd60a39d1..df36f8e2c0 100644 --- a/src/platform.h +++ b/src/platform.h @@ -393,6 +393,9 @@ public: */ virtual RenderLoop *renderLoop() const; + virtual AbstractOutput *createVirtualOutput(const QString &name, const QSize &size, qreal scaling); + virtual void removeVirtualOutput(AbstractOutput *output); + public Q_SLOTS: void pointerMotion(const QPointF &position, quint32 time); void pointerButtonPressed(quint32 button, quint32 time); diff --git a/src/plugins/platforms/drm/drm_backend.cpp b/src/plugins/platforms/drm/drm_backend.cpp index eaf713d2f7..8fb3669268 100644 --- a/src/plugins/platforms/drm/drm_backend.cpp +++ b/src/plugins/platforms/drm/drm_backend.cpp @@ -502,7 +502,7 @@ void DrmBackend::enableOutput(DrmAbstractOutput *output, bool enable) } if (m_enabledOutputs.count() == 1 && !kwinApp()->isTerminating()) { qCDebug(KWIN_DRM) << "adding placeholder output"; - m_placeHolderOutput = primaryGpu()->createVirtualOutput(); + m_placeHolderOutput = primaryGpu()->createVirtualOutput({}, {1920, 1080}, 1, DrmGpu::Placeholder); // placeholder doesn't actually need to render anything m_placeHolderOutput->renderLoop()->inhibit(); } @@ -686,6 +686,23 @@ QString DrmBackend::supportInformation() const return supportInfo; } +AbstractOutput *DrmBackend::createVirtualOutput(const QString &name, const QSize &size, double scale) +{ + auto output = primaryGpu()->createVirtualOutput(name, size * scale, scale, DrmGpu::Full); + readOutputsConfiguration(); + Q_EMIT screensQueried(); + return output; +} + +void DrmBackend::removeVirtualOutput(AbstractOutput *output) +{ + auto virtualOutput = qobject_cast(output); + if (!virtualOutput) { + return; + } + primaryGpu()->removeVirtualOutput(virtualOutput); +} + DmaBufTexture *DrmBackend::createDmaBufTexture(const QSize &size) { #if HAVE_GBM diff --git a/src/plugins/platforms/drm/drm_backend.h b/src/plugins/platforms/drm/drm_backend.h index 2b844cbf53..f4578cabd1 100644 --- a/src/plugins/platforms/drm/drm_backend.h +++ b/src/plugins/platforms/drm/drm_backend.h @@ -62,6 +62,8 @@ public: QVector supportedCompositors() const override; QString supportInformation() const override; + AbstractOutput *createVirtualOutput(const QString &name, const QSize &size, double scale) override; + void removeVirtualOutput(AbstractOutput *output) override; DrmGpu *primaryGpu() const; DrmGpu *findGpu(dev_t deviceId) const; diff --git a/src/plugins/platforms/drm/drm_gpu.cpp b/src/plugins/platforms/drm/drm_gpu.cpp index 503ea8b54a..9349bfb706 100644 --- a/src/plugins/platforms/drm/drm_gpu.cpp +++ b/src/plugins/platforms/drm/drm_gpu.cpp @@ -610,10 +610,11 @@ const QVector DrmGpu::pipelines() const return m_pipelines; } -DrmVirtualOutput *DrmGpu::createVirtualOutput() +DrmVirtualOutput *DrmGpu::createVirtualOutput(const QString &name, const QSize &size, double scale, VirtualOutputMode mode) { - auto output = new DrmVirtualOutput(this); - output->setPlaceholder(true); + auto output = new DrmVirtualOutput(name, this, size); + output->setScale(scale); + output->setPlaceholder(mode == Placeholder); m_outputs << output; Q_EMIT outputEnabled(output); Q_EMIT outputAdded(output); diff --git a/src/plugins/platforms/drm/drm_gpu.h b/src/plugins/platforms/drm/drm_gpu.h index f5183ed9a3..d9bd858430 100644 --- a/src/plugins/platforms/drm/drm_gpu.h +++ b/src/plugins/platforms/drm/drm_gpu.h @@ -77,6 +77,9 @@ public: void waitIdle(); bool updateOutputs(); DrmVirtualOutput *createVirtualOutput(); + + enum VirtualOutputMode { Placeholder, Full }; + DrmVirtualOutput *createVirtualOutput(const QString &name, const QSize &size, double scale, VirtualOutputMode mode); void removeVirtualOutput(DrmVirtualOutput *output); Q_SIGNALS: diff --git a/src/plugins/platforms/drm/drm_virtual_output.cpp b/src/plugins/platforms/drm/drm_virtual_output.cpp index cb454d553e..fc5e3f0c82 100644 --- a/src/plugins/platforms/drm/drm_virtual_output.cpp +++ b/src/plugins/platforms/drm/drm_virtual_output.cpp @@ -17,23 +17,28 @@ namespace KWin { +static int s_serial = 0; +DrmVirtualOutput::DrmVirtualOutput(DrmGpu *gpu, const QSize &size) + : DrmVirtualOutput(QString::number(s_serial++), gpu, size) +{ +} -DrmVirtualOutput::DrmVirtualOutput(DrmGpu *gpu) +DrmVirtualOutput::DrmVirtualOutput(const QString &name, DrmGpu *gpu, const QSize &size) : DrmAbstractOutput(gpu) , m_vsyncMonitor(SoftwareVsyncMonitor::create(this)) { connect(m_vsyncMonitor, &VsyncMonitor::vblankOccurred, this, &DrmVirtualOutput::vblank); - static int s_serial = 0; - m_identifier = s_serial++; - setName("Virtual-" + QString::number(m_identifier)); + setName("Virtual-" + name); m_modeIndex = 0; - QVector modes = {{{1920, 1080}, 60000, AbstractWaylandOutput::ModeFlags(AbstractWaylandOutput::ModeFlag::Current) | AbstractWaylandOutput::ModeFlag::Preferred, 0}}; - initialize(QByteArray("model_").append(QByteArray::number(m_identifier)), - QByteArray("manufacturer_").append(QByteArray::number(m_identifier)), - QByteArray("eisa_").append(QByteArray::number(m_identifier)), - QByteArray("serial_").append(QByteArray::number(m_identifier)), - modes[m_modeIndex].size, modes, QByteArray("EDID_").append(QByteArray::number(m_identifier))); + QVector modes = {{size, 60000, AbstractWaylandOutput::ModeFlags(AbstractWaylandOutput::ModeFlag::Current) | AbstractWaylandOutput::ModeFlag::Preferred, 0}}; + initialize(QLatin1String("model_") + name, + QLatin1String("manufacturer_") + name, + QLatin1String("eisa_") + name, + QLatin1String("serial_") + name, + modes[m_modeIndex].size, + modes, + QByteArray("EDID_") + name.toUtf8()); m_renderLoop->setRefreshRate(modes[m_modeIndex].refreshRate); } diff --git a/src/plugins/platforms/drm/drm_virtual_output.h b/src/plugins/platforms/drm/drm_virtual_output.h index f12fe7c1d4..f208776460 100644 --- a/src/plugins/platforms/drm/drm_virtual_output.h +++ b/src/plugins/platforms/drm/drm_virtual_output.h @@ -24,7 +24,8 @@ class DrmVirtualOutput : public DrmAbstractOutput { Q_OBJECT public: - DrmVirtualOutput(DrmGpu *gpu); + DrmVirtualOutput(const QString &name, DrmGpu *gpu, const QSize &size); + DrmVirtualOutput(DrmGpu *gpu, const QSize &size); ~DrmVirtualOutput() override; bool present(const QSharedPointer &buffer, QRegion damagedRegion) override; @@ -55,7 +56,6 @@ private: bool m_pageFlipPending = true; int m_modeIndex = 0; - int m_identifier; SoftwareVsyncMonitor *m_vsyncMonitor; }; diff --git a/src/plugins/platforms/wayland/wayland_backend.cpp b/src/plugins/platforms/wayland/wayland_backend.cpp index 235761f6c7..be5db29a26 100644 --- a/src/plugins/platforms/wayland/wayland_backend.cpp +++ b/src/plugins/platforms/wayland/wayland_backend.cpp @@ -671,14 +671,20 @@ void WaylandBackend::updateScreenSize(WaylandOutput *output) } } +KWayland::Client::ServerSideDecorationManager *WaylandBackend::ssdManager() +{ + if (!m_ssdManager) { + using namespace KWayland::Client; + const auto ssdManagerIface = m_registry->interface(Registry::Interface::ServerSideDecorationManager); + m_ssdManager = ssdManagerIface.name == 0 ? nullptr : m_registry->createServerSideDecorationManager(ssdManagerIface.name, ssdManagerIface.version, this); + } + return m_ssdManager; +} + void WaylandBackend::createOutputs() { using namespace KWayland::Client; - const auto ssdManagerIface = m_registry->interface(Registry::Interface::ServerSideDecorationManager); - ServerSideDecorationManager *ssdManager = ssdManagerIface.name == 0 ? nullptr : - m_registry->createServerSideDecorationManager(ssdManagerIface.name, ssdManagerIface.version, this); - const auto xdgIface = m_registry->interface(Registry::Interface::XdgShellStable); if (xdgIface.name != 0) { @@ -693,59 +699,63 @@ void WaylandBackend::createOutputs() int logicalWidthSum = 0; for (int i = 0; i < initialOutputCount(); i++) { - auto surface = m_compositor->createSurface(this); - if (!surface || !surface->isValid()) { - qCCritical(KWIN_WAYLAND_BACKEND) << "Creating Wayland Surface failed"; - return; - } - - if (ssdManager) { - auto decoration = ssdManager->create(surface, this); - connect(decoration, &ServerSideDecoration::modeChanged, this, - [decoration] { - if (decoration->mode() != ServerSideDecoration::Mode::Server) { - decoration->requestMode(ServerSideDecoration::Mode::Server); - } - } - ); - } - - WaylandOutput *waylandOutput = nullptr; - - if (m_xdgShell && m_xdgShell->isValid()) { - waylandOutput = new XdgShellOutput(surface, m_xdgShell, this, i+1); - } - - if (!waylandOutput) { - qCCritical(KWIN_WAYLAND_BACKEND) << "Binding to all shell interfaces failed for output" << i; - return; - } - - waylandOutput->init(QPoint(logicalWidthSum, 0), QSize(pixelWidth, pixelHeight)); - - connect(waylandOutput, &WaylandOutput::sizeChanged, this, [this, waylandOutput](const QSize &size) { - Q_UNUSED(size) - updateScreenSize(waylandOutput); - Compositor::self()->addRepaintFull(); - }); - connect(waylandOutput, &WaylandOutput::frameRendered, this, [waylandOutput]() { - waylandOutput->resetRendered(); - - // The current time of the monotonic clock is a pretty good estimate when the frame - // has been presented, however it will be much better if we check whether the host - // compositor supports the wp_presentation protocol. - RenderLoopPrivate *renderLoopPrivate = RenderLoopPrivate::get(waylandOutput->renderLoop()); - renderLoopPrivate->notifyFrameCompleted(std::chrono::steady_clock::now().time_since_epoch()); - }); + createOutput(QPoint(logicalWidthSum, 0), QSize(pixelWidth, pixelHeight)); logicalWidthSum += logicalWidth; - - // The output will only actually be added when it receives its first - // configure event, and buffers can start being attached - m_pendingInitialOutputs++; } } +WaylandOutput *WaylandBackend::createOutput(const QPoint &position, const QSize &size) +{ + auto surface = m_compositor->createSurface(this); + if (!surface || !surface->isValid()) { + qCCritical(KWIN_WAYLAND_BACKEND) << "Creating Wayland Surface failed"; + return nullptr; + } + + if (ssdManager()) { + auto decoration = ssdManager()->create(surface, this); + connect(decoration, &ServerSideDecoration::modeChanged, this, [decoration] { + if (decoration->mode() != ServerSideDecoration::Mode::Server) { + decoration->requestMode(ServerSideDecoration::Mode::Server); + } + }); + } + + WaylandOutput *waylandOutput = nullptr; + + if (m_xdgShell && m_xdgShell->isValid()) { + waylandOutput = new XdgShellOutput(surface, m_xdgShell, this, m_nextId++); + } + + if (!waylandOutput) { + qCCritical(KWIN_WAYLAND_BACKEND) << "Binding to all shell interfaces failed for output"; + return nullptr; + } + + waylandOutput->init(position, size); + + connect(waylandOutput, &WaylandOutput::sizeChanged, this, [this, waylandOutput](const QSize &size) { + Q_UNUSED(size) + updateScreenSize(waylandOutput); + Compositor::self()->addRepaintFull(); + }); + connect(waylandOutput, &WaylandOutput::frameRendered, this, [waylandOutput]() { + waylandOutput->resetRendered(); + + // The current time of the monotonic clock is a pretty good estimate when the frame + // has been presented, however it will be much better if we check whether the host + // compositor supports the wp_presentation protocol. + RenderLoopPrivate *renderLoopPrivate = RenderLoopPrivate::get(waylandOutput->renderLoop()); + renderLoopPrivate->notifyFrameCompleted(std::chrono::steady_clock::now().time_since_epoch()); + }); + + // The output will only actually be added when it receives its first + // configure event, and buffers can start being attached + m_pendingInitialOutputs++; + return waylandOutput; +} + void WaylandBackend::destroyOutputs() { while (!m_outputs.isEmpty()) { @@ -891,6 +901,21 @@ void WaylandBackend::clearDpmsFilter() m_dpmsFilter.reset(); } +AbstractOutput *WaylandBackend::createVirtualOutput(const QString &name, const QSize &size, double scale) +{ + Q_UNUSED(name); + return createOutput(m_outputs.constLast()->geometry().topRight(), size * scale); +} + +void WaylandBackend::removeVirtualOutput(AbstractOutput *output) +{ + WaylandOutput *waylandOutput = dynamic_cast(output); + if (waylandOutput && m_outputs.removeAll(waylandOutput)) { + Q_EMIT outputDisabled(waylandOutput); + Q_EMIT outputRemoved(waylandOutput); + delete waylandOutput; + } +} } } // KWin diff --git a/src/plugins/platforms/wayland/wayland_backend.h b/src/plugins/platforms/wayland/wayland_backend.h index aecf68f9fe..ff10669ccb 100644 --- a/src/plugins/platforms/wayland/wayland_backend.h +++ b/src/plugins/platforms/wayland/wayland_backend.h @@ -46,6 +46,7 @@ class Registry; class RelativePointer; class RelativePointerManager; class Seat; +class ServerSideDecorationManager; class SubCompositor; class SubSurface; class Surface; @@ -204,6 +205,9 @@ public: void createDpmsFilter(); void clearDpmsFilter(); + AbstractOutput *createVirtualOutput(const QString &name, const QSize &size, double scale) override; + void removeVirtualOutput(AbstractOutput *output) override; + Q_SIGNALS: void systemCompositorDied(); void connectionFailed(); @@ -218,6 +222,7 @@ private: void updateScreenSize(WaylandOutput *output); void relativeMotionHandler(const QSizeF &delta, const QSizeF &deltaNonAccelerated, quint64 timestamp); + WaylandOutput *createOutput(const QPoint &position, const QSize &size); Session *m_session; wl_display *m_display; @@ -243,6 +248,9 @@ private: QScopedPointer m_dpmsFilter; bool m_pointerLockRequested = false; + KWayland::Client::ServerSideDecorationManager *m_ssdManager = nullptr; + KWayland::Client::ServerSideDecorationManager *ssdManager(); + int m_nextId = 0; #if HAVE_GBM && HAVE_WAYLAND_EGL int m_drmFileDescriptor = 0; gbm_device *m_gbmDevice; diff --git a/src/plugins/screencast/screencastmanager.cpp b/src/plugins/screencast/screencastmanager.cpp index d5a6032711..4a50c68b23 100644 --- a/src/plugins/screencast/screencastmanager.cpp +++ b/src/plugins/screencast/screencastmanager.cpp @@ -33,8 +33,8 @@ ScreencastManager::ScreencastManager(QObject *parent) { connect(m_screencast, &KWaylandServer::ScreencastV1Interface::windowScreencastRequested, this, &ScreencastManager::streamWindow); - connect(m_screencast, &KWaylandServer::ScreencastV1Interface::outputScreencastRequested, - this, &ScreencastManager::streamOutput); + connect(m_screencast, &KWaylandServer::ScreencastV1Interface::outputScreencastRequested, this, &ScreencastManager::streamWaylandOutput); + connect(m_screencast, &KWaylandServer::ScreencastV1Interface::virtualOutputScreencastRequested, this, &ScreencastManager::streamVirtualOutput); } class WindowStream : public PipeWireStream @@ -105,12 +105,30 @@ void ScreencastManager::streamWindow(KWaylandServer::ScreencastStreamV1Interface integrateStreams(waylandStream, stream); } +void ScreencastManager::streamVirtualOutput(KWaylandServer::ScreencastStreamV1Interface *stream, + const QString &name, + const QSize &size, + double scale, + KWaylandServer::ScreencastV1Interface::CursorMode mode) +{ + auto output = qobject_cast(kwinApp()->platform()->createVirtualOutput(name, size, scale)); + streamOutput(stream, output, mode); + connect(stream, &KWaylandServer::ScreencastStreamV1Interface::finished, output, [output] { + kwinApp()->platform()->removeVirtualOutput(output); + }); +} + +void ScreencastManager::streamWaylandOutput(KWaylandServer::ScreencastStreamV1Interface *waylandStream, + KWaylandServer::OutputInterface *output, + KWaylandServer::ScreencastV1Interface::CursorMode mode) +{ + streamOutput(waylandStream, waylandServer()->findOutput(output), mode); +} + void ScreencastManager::streamOutput(KWaylandServer::ScreencastStreamV1Interface *waylandStream, - KWaylandServer::OutputInterface *output, + AbstractWaylandOutput *streamOutput, KWaylandServer::ScreencastV1Interface::CursorMode mode) { - AbstractWaylandOutput *streamOutput = waylandServer()->findOutput(output); - if (!streamOutput) { waylandStream->sendFailed(i18n("Could not find output")); return; diff --git a/src/plugins/screencast/screencastmanager.h b/src/plugins/screencast/screencastmanager.h index 5d44f89dd8..b6b5489454 100644 --- a/src/plugins/screencast/screencastmanager.h +++ b/src/plugins/screencast/screencastmanager.h @@ -14,7 +14,7 @@ namespace KWin { - +class AbstractWaylandOutput; class PipeWireStream; class ScreencastManager : public Plugin @@ -24,12 +24,19 @@ class ScreencastManager : public Plugin public: explicit ScreencastManager(QObject *parent = nullptr); - void streamWindow(KWaylandServer::ScreencastStreamV1Interface *stream, const QString &winid); - void streamOutput(KWaylandServer::ScreencastStreamV1Interface *stream, - KWaylandServer::OutputInterface *output, - KWaylandServer::ScreencastV1Interface::CursorMode mode); - private: + void streamWindow(KWaylandServer::ScreencastStreamV1Interface *stream, const QString &winid); + void streamWaylandOutput(KWaylandServer::ScreencastStreamV1Interface *stream, + KWaylandServer::OutputInterface *output, + KWaylandServer::ScreencastV1Interface::CursorMode mode); + void + streamOutput(KWaylandServer::ScreencastStreamV1Interface *stream, AbstractWaylandOutput *output, KWaylandServer::ScreencastV1Interface::CursorMode mode); + void streamVirtualOutput(KWaylandServer::ScreencastStreamV1Interface *stream, + const QString &name, + const QSize &size, + double scale, + KWaylandServer::ScreencastV1Interface::CursorMode mode); + void integrateStreams(KWaylandServer::ScreencastStreamV1Interface *waylandStream, PipeWireStream *stream); KWaylandServer::ScreencastV1Interface *m_screencast;