8#include "pipewiresourceitem.h"
11#include "pipewiresourcestream.h"
14#include <QGuiApplication>
15#include <QOpenGLContext>
16#include <QOpenGLTexture>
18#include <QQuickWindow>
20#include <QSGImageNode>
21#include <QSocketNotifier>
23#include <qpa/qplatformnativeinterface.h>
25#include <EGL/eglext.h>
27#include <libdrm/drm_fourcc.h>
32 pw_init(
nullptr,
nullptr);
34Q_COREAPP_STARTUP_FUNCTION(pwInit);
36class PipeWireSourceItemPrivate
40 std::optional<uint> m_fd;
41 std::function<
QSGTexture *()> m_createNextTexture;
42 std::unique_ptr<PipeWireSourceStream> m_stream;
43 std::unique_ptr<QOpenGLTexture> m_texture;
45 EGLImage m_image =
nullptr;
46 bool m_needsRecreateTexture =
false;
47 bool m_allowDmaBuf =
true;
52 std::optional<QPoint> position;
56 std::optional<QRegion> m_damage;
59class DiscardEglPixmapRunnable :
public QRunnable
62 DiscardEglPixmapRunnable(EGLImageKHR image,
QOpenGLTexture *texture)
70 if (m_image != EGL_NO_IMAGE_KHR) {
71 eglDestroyImageKHR(eglGetCurrentDisplay(), m_image);
78 const EGLImageKHR m_image;
82PipeWireSourceItem::PipeWireSourceItem(
QQuickItem *parent)
84 , d(new PipeWireSourceItemPrivate)
86 setFlag(ItemHasContents,
true);
89PipeWireSourceItem::~PipeWireSourceItem()
108 d->m_needsRecreateTexture =
true;
118void PipeWireSourceItem::releaseResources()
120 if (
window() && (d->m_image || d->m_texture)) {
122 d->m_image = EGL_NO_IMAGE_KHR;
126void PipeWireSourceItem::setFd(uint fd)
139void PipeWireSourceItem::resetFd()
141 if (!d->m_fd.has_value()) {
148 d->m_stream.reset(
nullptr);
149 d->m_createNextTexture = [] {
152 Q_EMIT streamSizeChanged();
155void PipeWireSourceItem::refresh()
163 if (d->m_nodeId == 0) {
165 d->m_stream.reset(
nullptr);
166 Q_EMIT streamSizeChanged();
168 d->m_createNextTexture = [] {
172 d->m_stream.reset(
new PipeWireSourceStream(
this));
173 d->m_stream->setAllowDmaBuf(d->m_allowDmaBuf);
174 Q_EMIT streamSizeChanged();
175 connect(d->m_stream.get(), &PipeWireSourceStream::streamParametersChanged,
this, &PipeWireSourceItem::streamSizeChanged);
176 connect(d->m_stream.get(), &PipeWireSourceStream::streamParametersChanged,
this, &PipeWireSourceItem::usingDmaBufChanged);
178 d->m_stream->createStream(d->m_nodeId, d->m_fd.value_or(0));
179 if (!d->m_stream->error().isEmpty()) {
180 d->m_stream.reset(
nullptr);
186 connect(d->m_stream.get(), &PipeWireSourceStream::frameReceived,
this, &PipeWireSourceItem::processFrame);
187 connect(d->m_stream.get(), &PipeWireSourceStream::stateChanged,
this, &PipeWireSourceItem::stateChanged);
192void PipeWireSourceItem::setNodeId(uint nodeId)
194 if (nodeId == d->m_nodeId)
197 d->m_nodeId = nodeId;
199 Q_EMIT nodeIdChanged(nodeId);
202class PipeWireRenderNode :
public QSGNode
208 m_screenNode =
window->createImageNode();
216 m_cursorNode =
window->createImageNode();
225 m_damageNode =
window->createImageNode();
236 m_cursorNode =
nullptr;
245 m_damageNode =
nullptr;
255QSGNode *PipeWireSourceItem::updatePaintNode(
QSGNode *node, QQuickItem::UpdatePaintNodeData *)
257 if (Q_UNLIKELY(!d->m_createNextTexture)) {
261 auto texture = d->m_createNextTexture();
267 auto pwNode =
static_cast<PipeWireRenderNode *
>(node);
269 pwNode =
new PipeWireRenderNode;
278 rect.moveCenter(br.center());
281 if (!d->m_cursor.position.has_value() || d->m_cursor.texture.isNull()) {
282 pwNode->discardCursor();
285 if (d->m_cursor.dirty || !cursorNode->
texture()) {
286 cursorNode->
setTexture(
window()->createTextureFromImage(d->m_cursor.texture));
288 d->m_cursor.dirty =
false;
290 const qreal
scale = qreal(rect.width()) / texture->textureSize().width();
291 cursorNode->
setRect(
QRectF{rect.topLeft() + (d->m_cursor.position.value() *
scale), d->m_cursor.texture.size() *
scale});
292 Q_ASSERT(cursorNode->
texture());
295 if (!d->m_damage || d->m_damage->isEmpty()) {
296 pwNode->discardDamage();
298 auto *damageNode = pwNode->damageNode(
window());
303 for (
auto rect : *d->m_damage) {
306 damageNode->setTexture(
window()->createTextureFromImage(damageImage));
307 damageNode->setOwnsTexture(
true);
308 damageNode->setRect(rect);
309 Q_ASSERT(damageNode->texture());
314QString PipeWireSourceItem::error()
const
316 return d->m_stream->error();
319void PipeWireSourceItem::processFrame(
const PipeWireFrame &frame)
321 d->m_damage = frame.damage;
324 d->m_cursor.position = frame.cursor->position;
325 d->m_cursor.hotspot = frame.cursor->hotspot;
326 if (!frame.cursor->texture.isNull()) {
327 d->m_cursor.dirty =
true;
328 d->m_cursor.texture = frame.cursor->texture;
331 d->m_cursor.position = std::nullopt;
332 d->m_cursor.hotspot = {};
336 updateTextureDmaBuf(*frame.dmabuf, frame.format);
337 }
else if (frame.dataFrame) {
338 updateTextureImage(frame.dataFrame);
346void PipeWireSourceItem::updateTextureDmaBuf(
const DmaBufAttributes &attribs, spa_video_format format)
349 qCWarning(PIPEWIRE_LOGGING) <<
"Window not available" <<
this;
354 if (!openglContext || !d->m_stream) {
355 qCWarning(PIPEWIRE_LOGGING) <<
"need a window and a context" <<
window();
359 d->m_createNextTexture = [
this, format, attribs]() ->
QSGTexture * {
360 const EGLDisplay display =
static_cast<EGLDisplay
>(QGuiApplication::platformNativeInterface()->nativeResourceForIntegration(
"egldisplay"));
362 eglDestroyImageKHR(display, d->m_image);
364 const auto size = d->m_stream->size();
365 d->m_image = GLHelpers::createImage(display, attribs, PipeWireSourceStream::spaVideoFormatToDrmFormat(format),
size,
nullptr);
366 if (d->m_image == EGL_NO_IMAGE_KHR) {
368 d->m_stream->renegotiateModifierFailed(format, attribs.modifier);
374 bool created = d->m_texture->create();
378 GLHelpers::initDebugOutput();
379 d->m_texture->bind();
381 glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)d->m_image);
385 d->m_texture->release();
388 int textureId = d->m_texture->textureId();
397void PipeWireSourceItem::updateTextureImage(
const std::shared_ptr<PipeWireFrameData> &data)
400 qCWarning(PIPEWIRE_LOGGING) <<
"pass";
404 d->m_createNextTexture = [
this, data] {
411void PipeWireSourceItem::componentComplete()
414 if (d->m_nodeId != 0) {
419PipeWireSourceItem::StreamState PipeWireSourceItem::state()
const
422 return StreamState::Unconnected;
424 switch (d->m_stream->state()) {
425 case PW_STREAM_STATE_ERROR:
426 return StreamState::Error;
427 case PW_STREAM_STATE_UNCONNECTED:
428 return StreamState::Unconnected;
429 case PW_STREAM_STATE_CONNECTING:
430 return StreamState::Connecting;
431 case PW_STREAM_STATE_PAUSED:
432 return StreamState::Paused;
433 case PW_STREAM_STATE_STREAMING:
434 return StreamState::Streaming;
436 return StreamState::Error;
440uint PipeWireSourceItem::fd()
const
442 return d->m_fd.value_or(0);
445uint PipeWireSourceItem::nodeId()
const
450QSize PipeWireSourceItem::streamSize()
const
455 return d->m_stream->size();
458bool PipeWireSourceItem::usingDmaBuf()
const
460 return d->m_stream && d->m_stream->usingDmaBuf();
463bool PipeWireSourceItem::allowDmaBuf()
const
465 return d->m_stream && d->m_stream->allowDmaBuf();
468void PipeWireSourceItem::setAllowDmaBuf(
bool allowed)
470 d->m_allowDmaBuf = allowed;
472 d->m_stream->setAllowDmaBuf(allowed);
476void PipeWireSourceItem::setReady(
bool ready)
478 if (d->m_ready != ready) {
484bool PipeWireSourceItem::isReady()
const
489#include "moc_pipewiresourceitem.cpp"
KGUIADDONS_EXPORT QWindow * window(QObject *job)
const QList< QKeySequence > & close()
QSGTexture * fromNative(GLuint textureId, QQuickWindow *window, const QSize &size, QQuickWindow::CreateTextureOptions options)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
virtual QRectF boundingRect() const const
virtual void componentComplete() override
bool isComponentComplete() const const
virtual void itemChange(ItemChange change, const ItemChangeData &value)
QSizeF size() const const
bool isVisible() const const
QQuickWindow * window() const const
QSGTexture * createTextureFromImage(const QImage &image) const const
QSGRendererInterface * rendererInterface() const const
void scheduleRenderJob(QRunnable *job, RenderStage stage)
QRect toRect() const const
virtual void setOwnsTexture(bool owns)=0
virtual void setRect(const QRectF &rect)=0
virtual void setTexture(QSGTexture *texture)=0
virtual QSGTexture * texture() const const=0
void appendChildNode(QSGNode *node)
void removeChildNode(QSGNode *node)
virtual void * getResource(QQuickWindow *window, Resource resource) const const
qreal height() const const
qreal width() const const