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;