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.
This commit is contained in:
Aleix Pol 2021-10-05 19:06:28 +02:00
parent 4395caa256
commit 1330376220
12 changed files with 181 additions and 79 deletions

View file

@ -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)

View file

@ -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);

View file

@ -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<DrmVirtualOutput *>(output);
if (!virtualOutput) {
return;
}
primaryGpu()->removeVirtualOutput(virtualOutput);
}
DmaBufTexture *DrmBackend::createDmaBufTexture(const QSize &size)
{
#if HAVE_GBM

View file

@ -62,6 +62,8 @@ public:
QVector<CompositingType> 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;

View file

@ -610,10 +610,11 @@ const QVector<DrmPipeline*> 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);

View file

@ -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:

View file

@ -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<Mode> 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<Mode> 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);
}

View file

@ -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<DrmBuffer> &buffer, QRegion damagedRegion) override;
@ -55,7 +56,6 @@ private:
bool m_pageFlipPending = true;
int m_modeIndex = 0;
int m_identifier;
SoftwareVsyncMonitor *m_vsyncMonitor;
};

View file

@ -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<WaylandOutput *>(output);
if (waylandOutput && m_outputs.removeAll(waylandOutput)) {
Q_EMIT outputDisabled(waylandOutput);
Q_EMIT outputRemoved(waylandOutput);
delete waylandOutput;
}
}
}
} // KWin

View file

@ -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<DpmsInputEventFilter> 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;

View file

@ -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<AbstractWaylandOutput *>(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;

View file

@ -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;