[plasma/kwin] src: screencast: Resizable Virtual Monitors
David Edmundson <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit 452707eb5d948e69a9e506963fc49c75ff454f6c by David Edmundson.
Committed on 29/07/2026 at 15:06.
Pushed by davidedmundson into branch 'master'.
screencast: Resizable Virtual Monitors
When streaming a virtual monitor to a remote server, we want the client
to be able to be in charge of the virtual monitor size.
Pipewire has a path built in that allows the client and server to
negotiate a size that matches their constraints.
Gnome uses this for their resizing of virtual monitors, so clients will
have the expectation that kwin will match. It also allows the virtual
monitor path via the xdg-desktop-portal to not need additional
sidechannels.
BUG: 512620
----
See also:
https://invent.kde.org/plasma/kpipewire/-/merge_requests/205
https://invent.kde.org/plasma/krdp/-/merge_requests/113
M +15 -0 src/backends/drm/drm_virtual_output.cpp
M +3 -0 src/backends/drm/drm_virtual_output.h
M +15 -0 src/backends/virtual/virtual_output.cpp
M +2 -0 src/backends/virtual/virtual_output.h
M +10 -0 src/core/backendoutput.cpp
M +2 -0 src/core/backendoutput.h
M +10 -0 src/core/output.cpp
M +10 -0 src/core/output.h
M +13 -0 src/plugins/screencast/outputscreencastsource.cpp
M +2 -0 src/plugins/screencast/outputscreencastsource.h
M +10 -0 src/plugins/screencast/screencastsource.cpp
M +2 -0 src/plugins/screencast/screencastsource.h
M +27 -8 src/plugins/screencast/screencaststream.cpp
M +3 -2 src/plugins/screencast/screencaststream.h
https://invent.kde.org/plasma/kwin/-/commit/452707eb5d948e69a9e506963fc49c75ff454f6c
diff --git a/src/backends/drm/drm_virtual_output.cpp b/src/backends/drm/drm_virtual_output.cpp
index 0156447dc60..506e401aa94 100644
--- a/src/backends/drm/drm_virtual_output.cpp
+++ b/src/backends/drm/drm_virtual_output.cpp
@@ -138,6 +138,21 @@ void DrmVirtualOutput::applyChanges(const OutputConfiguration &config)
Q_EMIT changed();
}
+bool DrmVirtualOutput::canResize() const
+{
+ return true;
+}
+
+void DrmVirtualOutput::resize(const QSize &size)
+{
+ auto mode = std::make_shared<OutputMode>(OutputModeline(size, 60000, OutputModeline::Flag::Preferred));
+ auto next = m_state;
+ next.modes = {mode};
+ next.currentMode = mode;
+ setState(next);
+ Q_EMIT m_backend->outputsQueried();
+}
+
}
#include "moc_drm_virtual_output.cpp"
diff --git a/src/backends/drm/drm_virtual_output.h b/src/backends/drm/drm_virtual_output.h
index eae53e98522..c82a06592b0 100644
--- a/src/backends/drm/drm_virtual_output.h
+++ b/src/backends/drm/drm_virtual_output.h
@@ -36,6 +36,9 @@ public:
DrmOutputLayer *primaryLayer() const;
void recreateSurface();
+ bool canResize() const override;
+ void resize(const QSize &size) override;
+
private:
void vblank(std::chrono::nanoseconds timestamp);
diff --git a/src/backends/virtual/virtual_output.cpp b/src/backends/virtual/virtual_output.cpp
index 59aad898d24..36955a60ab9 100644
--- a/src/backends/virtual/virtual_output.cpp
+++ b/src/backends/virtual/virtual_output.cpp
@@ -137,6 +137,21 @@ void VirtualOutput::applyChanges(const OutputConfiguration &config)
Q_EMIT changed();
}
+bool VirtualOutput::canResize() const
+{
+ return true;
+}
+
+void VirtualOutput::resize(const QSize &size)
+{
+ auto mode = std::make_shared<OutputMode>(OutputModeline(size, 60000, OutputModeline::Flag::Preferred));
+ auto next = m_state;
+ next.modes = {mode};
+ next.currentMode = mode;
+ setState(next);
+ Q_EMIT m_backend->outputsQueried();
+}
+
void VirtualOutput::vblank(std::chrono::nanoseconds timestamp)
{
if (m_frame) {
diff --git a/src/backends/virtual/virtual_output.h b/src/backends/virtual/virtual_output.h
index b153af74a5f..105be88e315 100644
--- a/src/backends/virtual/virtual_output.h
+++ b/src/backends/virtual/virtual_output.h
@@ -33,6 +33,8 @@ public:
void init(const QSize &pixelSize, qreal scale, const QList<OutputModeline> &modes);
void applyChanges(const OutputConfiguration &config) override;
+ bool canResize() const override;
+ void resize(const QSize &size) override;
void setOutputLayer(std::unique_ptr<OutputLayer> &&layer);
OutputLayer *outputLayer() const;
diff --git a/src/core/backendoutput.cpp b/src/core/backendoutput.cpp
index b126d7dbee5..f74ecd1ff81 100644
--- a/src/core/backendoutput.cpp
+++ b/src/core/backendoutput.cpp
@@ -365,6 +365,16 @@ void BackendOutput::applyChanges(const OutputConfiguration &config)
Q_EMIT changed();
}
+bool BackendOutput::canResize() const
+{
+ return false;
+}
+
+void BackendOutput::resize(const QSize &size)
+{
+ Q_ASSERT(false);
+}
+
bool BackendOutput::isEnabled() const
{
return m_state.enabled;
diff --git a/src/core/backendoutput.h b/src/core/backendoutput.h
index 88a961066a9..58b806eb9bf 100644
--- a/src/core/backendoutput.h
+++ b/src/core/backendoutput.h
@@ -214,6 +214,8 @@ public:
QSize orientateSize(const QSize &size) const;
virtual void applyChanges(const OutputConfiguration &config);
+ virtual bool canResize() const;
+ virtual void resize(const QSize &size);
SubPixel subPixel() const;
QString description() const;
diff --git a/src/core/output.cpp b/src/core/output.cpp
index ffa599c8d8a..1eb91f97566 100644
--- a/src/core/output.cpp
+++ b/src/core/output.cpp
@@ -555,6 +555,16 @@ QSize LogicalOutput::orientateSize(const QSize &size) const
}
}
+bool LogicalOutput::canResize() const
+{
+ return m_backendOutput->canResize();
+}
+
+void LogicalOutput::resize(const QSize &size)
+{
+ m_backendOutput->resize(size);
+}
+
BackendOutput *LogicalOutput::backendOutput() const
{
return m_backendOutput;
diff --git a/src/core/output.h b/src/core/output.h
index 3c74b581909..18c3cffb2d0 100644
--- a/src/core/output.h
+++ b/src/core/output.h
@@ -434,6 +434,16 @@ public:
*/
QSize orientateSize(const QSize &size) const;
+ /*!
+ * Returns whether the modeline size can be changed from within kwin
+ */
+ bool canResize() const;
+
+ /*!
+ * Sets the pixel size of the output
+ */
+ void resize(const QSize &size);
+
/*!
* Returns \c true if this is a placeholder output; otherwise returns \c false.
*
diff --git a/src/plugins/screencast/outputscreencastsource.cpp b/src/plugins/screencast/outputscreencastsource.cpp
index 5a44c3f7f57..40e303aed41 100644
--- a/src/plugins/screencast/outputscreencastsource.cpp
+++ b/src/plugins/screencast/outputscreencastsource.cpp
@@ -167,6 +167,19 @@ RectF OutputScreenCastSource::mapFromGlobal(const RectF &rect) const
return m_output->mapFromGlobal(rect);
}
+bool OutputScreenCastSource::followsStreamSize()
+{
+ return m_output->canResize();
+}
+
+void OutputScreenCastSource::resize(const QSize &size)
+{
+ if (m_output->pixelSize() == size) {
+ return;
+ }
+ m_output->resize(size);
+}
+
} // namespace KWin
#include "moc_outputscreencastsource.cpp"
diff --git a/src/plugins/screencast/outputscreencastsource.h b/src/plugins/screencast/outputscreencastsource.h
index 7c3142b1796..ff206e8a662 100644
--- a/src/plugins/screencast/outputscreencastsource.h
+++ b/src/plugins/screencast/outputscreencastsource.h
@@ -43,6 +43,8 @@ public:
QPointF mapFromGlobal(const QPointF &point) const override;
RectF mapFromGlobal(const RectF &rect) const override;
+ bool followsStreamSize() override;
+ void resize(const QSize &size) override;
private:
QPointer<LogicalOutput> m_output;
diff --git a/src/plugins/screencast/screencastsource.cpp b/src/plugins/screencast/screencastsource.cpp
index df6bcb1f5d8..25d58f9cb61 100644
--- a/src/plugins/screencast/screencastsource.cpp
+++ b/src/plugins/screencast/screencastsource.cpp
@@ -13,6 +13,16 @@ ScreenCastSource::ScreenCastSource()
{
}
+bool ScreenCastSource::followsStreamSize()
+{
+ return false;
+}
+
+void ScreenCastSource::resize(const QSize &)
+{
+ Q_ASSERT(false);
+}
+
} // namespace KWin
#include "moc_screencastsource.cpp"
diff --git a/src/plugins/screencast/screencastsource.h b/src/plugins/screencast/screencastsource.h
index c15f81259bc..6e37cd4c3d7 100644
--- a/src/plugins/screencast/screencastsource.h
+++ b/src/plugins/screencast/screencastsource.h
@@ -43,6 +43,8 @@ public:
virtual QPointF mapFromGlobal(const QPointF &point) const = 0;
virtual RectF mapFromGlobal(const RectF &rect) const = 0;
+ virtual bool followsStreamSize();
+ virtual void resize(const QSize &size);
Q_SIGNALS:
void frame();
diff --git a/src/plugins/screencast/screencaststream.cpp b/src/plugins/screencast/screencaststream.cpp
index 204ad223c8e..919e9c0039a 100644
--- a/src/plugins/screencast/screencaststream.cpp
+++ b/src/plugins/screencast/screencaststream.cpp
@@ -235,6 +235,15 @@ void ScreenCastStream::onStreamParamChanged(uint32_t id, const struct spa_pod *f
}
spa_format_video_raw_parse(format, &m_videoFormat);
+ const QSize negotiatedSize(m_videoFormat.size.width, m_videoFormat.size.height);
+
+ qCDebug(KWIN_SCREENCAST) << objectName() << "negotiated stream size to" << negotiatedSize;
+ m_resolution = negotiatedSize;
+
+ if (m_source && m_source->followsStreamSize()) {
+ m_source->resize(negotiatedSize);
+ }
+
auto modifierProperty = spa_pod_find_prop(format, nullptr, SPA_FORMAT_VIDEO_modifier);
if (modifierProperty) {
const uint32_t valueCount = SPA_POD_CHOICE_N_VALUES(&modifierProperty->value);
@@ -671,7 +680,9 @@ void ScreenCastStream::record(Contents contents)
pw_stream_queue_buffer(m_pwStream, pwBuffer);
m_lastSent = std::chrono::steady_clock::now();
- resize(m_source->textureSize());
+ if (!m_source->followsStreamSize()) {
+ updateStreamSize(m_source->textureSize());
+ }
}
void ScreenCastStream::bumpBufferAge(ScreenCastBuffer *renderedBuffer)
@@ -685,7 +696,7 @@ void ScreenCastStream::bumpBufferAge(ScreenCastBuffer *renderedBuffer)
}
}
-void ScreenCastStream::resize(const QSize &resolution)
+void ScreenCastStream::updateStreamSize(const QSize &resolution)
{
if (m_resolution == resolution) {
return;
@@ -758,20 +769,26 @@ QList<const spa_pod *> ScreenCastStream::buildFormats(bool fixate, char buffer[2
spa_fraction minFramerate = SPA_FRACTION(0, 1);
spa_fraction maxFramerate = SPA_FRACTION(m_source->refreshRate() / 1000, 1);
- spa_rectangle resolution = SPA_RECTANGLE(uint32_t(m_resolution.width()), uint32_t(m_resolution.height()));
+ constexpr spa_rectangle streamMinSize = SPA_RECTANGLE(200, 200);
+ constexpr spa_rectangle streamMaxSize = SPA_RECTANGLE(10000, 10000);
+
+ spa_rectangle defaultSize = SPA_RECTANGLE(uint32_t(m_resolution.width()), uint32_t(m_resolution.height()));
+ spa_rectangle minSize = m_source->followsStreamSize() ? streamMinSize : defaultSize;
+ spa_rectangle maxSize = m_source->followsStreamSize() ? streamMaxSize : defaultSize;
QList<const spa_pod *> params;
if (m_hasDmaBuf) {
if (fixate) {
- params.append(buildFormat(&podBuilder, dmabufFormat, &resolution, &defFramerate, &minFramerate, &maxFramerate, {m_dmabufParams->modifier}, SPA_POD_PROP_FLAG_MANDATORY));
+ params.append(buildFormat(&podBuilder, dmabufFormat, defaultSize, minSize, maxSize, &defFramerate, &minFramerate, &maxFramerate, {m_dmabufParams->modifier}, SPA_POD_PROP_FLAG_MANDATORY));
}
- params.append(buildFormat(&podBuilder, dmabufFormat, &resolution, &defFramerate, &minFramerate, &maxFramerate, m_modifiers, SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE));
+ params.append(buildFormat(&podBuilder, dmabufFormat, defaultSize, minSize, maxSize, &defFramerate, &minFramerate, &maxFramerate, m_modifiers, SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE));
}
- params.append(buildFormat(&podBuilder, shmFormat, &resolution, &defFramerate, &minFramerate, &maxFramerate, {}, 0));
+ params.append(buildFormat(&podBuilder, shmFormat, defaultSize, minSize, maxSize, &defFramerate, &minFramerate, &maxFramerate, {}, 0));
return params;
}
-spa_pod *ScreenCastStream::buildFormat(struct spa_pod_builder *b, enum spa_video_format format, struct spa_rectangle *resolution,
+spa_pod *ScreenCastStream::buildFormat(struct spa_pod_builder *b, enum spa_video_format format,
+ struct spa_rectangle defaultSize, struct spa_rectangle minSize, struct spa_rectangle maxSize,
struct spa_fraction *defaultFramerate, struct spa_fraction *minFramerate, struct spa_fraction *maxFramerate,
const ModifierList &modifiers, quint32 modifiersFlags)
{
@@ -779,7 +796,9 @@ spa_pod *ScreenCastStream::buildFormat(struct spa_pod_builder *b, enum spa_video
spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat);
spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0);
spa_pod_builder_add(b, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0);
- spa_pod_builder_add(b, SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(resolution), 0);
+
+ spa_pod_builder_add(b, SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(SPA_POD_Rectangle(&defaultSize), SPA_POD_Rectangle(&minSize), SPA_POD_Rectangle(&maxSize)), 0);
+
spa_pod_builder_add(b, SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(defaultFramerate), 0);
spa_pod_builder_add(b, SPA_FORMAT_VIDEO_maxFramerate,
SPA_POD_CHOICE_RANGE_Fraction(
diff --git a/src/plugins/screencast/screencaststream.h b/src/plugins/screencast/screencaststream.h
index d0af11d6472..6129ab0cddc 100644
--- a/src/plugins/screencast/screencaststream.h
+++ b/src/plugins/screencast/screencaststream.h
@@ -90,14 +90,15 @@ private:
bool createStream();
QList<const spa_pod *> buildFormats(bool fixate, char buffer[2048]);
void updateParams();
- void resize(const QSize &resolution);
+ void updateStreamSize(const QSize &resolution);
void coreFailed(const QString &errorMessage);
void addCursorMetadata(spa_buffer *spaBuffer, Cursor *cursor);
void addHeader(spa_buffer *spaBuffer);
void corruptHeader(spa_buffer *spaBuffer);
void addDamage(spa_buffer *spaBuffer, const Region &damagedRegion);
void newStreamParams();
- spa_pod *buildFormat(struct spa_pod_builder *b, enum spa_video_format format, struct spa_rectangle *resolution,
+ spa_pod *buildFormat(struct spa_pod_builder *b, enum spa_video_format format,
+ struct spa_rectangle defaultSize, struct spa_rectangle minSize, struct spa_rectangle maxSize,
struct spa_fraction *defaultFramerate, struct spa_fraction *minFramerate, struct spa_fraction *maxFramerate,
const ModifierList &modifiers, quint32 modifiersFlags);
pw_buffer *dequeueBuffer();