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:
parent
4395caa256
commit
1330376220
12 changed files with 181 additions and 79 deletions
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue