screencast: Address minor issues

icc-effect-master
Vlad Zahorodnii 2020-07-28 16:37:04 +03:00 committed by Vlad Zahorodnii
parent 76b9fb15ea
commit 2103b999e7
9 changed files with 113 additions and 88 deletions

View File

@ -759,11 +759,11 @@ set(kwin_WAYLAND_SRCS
)
ecm_qt_declare_logging_category(kwin_WAYLAND_SRCS
HEADER
kwinpipewire_logging.h
kwinscreencast_logging.h
IDENTIFIER
KWIN_PIPEWIRE
KWIN_SCREENCAST
CATEGORY_NAME
kwin_pipewire
kwin_screencast
DEFAULT_SEVERITY
Warning
)

View File

@ -21,3 +21,4 @@ kwin_qpa_plugin KWin QtPlatformAbstraction plugin DEFAULT_SEVERITY [CRITICAL] ID
kwin_scene_xrender KWin XRender based compositor scene plugin DEFAULT_SEVERITY [CRITICAL] IDENTIFIER [KWIN_XRENDER]
kwin_scene_qpainter KWin QPainter based compositor scene plugin DEFAULT_SEVERITY [CRITICAL] IDENTIFIER [KWIN_QPAINTER]
kwin_scene_opengl KWin OpenGL based compositor scene plugins DEFAULT_SEVERITY [CRITICAL] IDENTIFIER [KWIN_OPENGL]
kwin_screencast KWin Screen Cast Service DEFAULT_SEVERITY [WARNING] IDENTIFIER [KWIN_SCREENCAST]

View File

@ -49,8 +49,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "decorations/decoratedclient.h"
#include <logging.h>
#include "abstract_wayland_output.h"
#include "abstract_egl_backend.h"
#include <KWaylandServer/buffer_interface.h>
#include <KWaylandServer/subcompositor_interface.h>
#include <KWaylandServer/surface_interface.h>

View File

@ -21,10 +21,15 @@
*/
#include "pipewirecore.h"
#include "kwinscreencast_logging.h"
#include <KLocalizedString>
#include <QDebug>
#include <QSocketNotifier>
#include <KLocalizedString>
#include "kwinpipewire_logging.h"
namespace KWin
{
PipeWireCore::PipeWireCore()
{
@ -56,7 +61,7 @@ void PipeWireCore::onCoreError(void* data, uint32_t id, int seq, int res, const
{
Q_UNUSED(seq)
qCWarning(KWIN_PIPEWIRE) << "PipeWire remote error: " << message;
qCWarning(KWIN_SCREENCAST) << "PipeWire remote error: " << message;
if (id == PW_ID_CORE && res == -EPIPE) {
PipeWireCore *pw = static_cast<PipeWireCore*>(data);
Q_EMIT pw->pipewireFailed(QString::fromUtf8(message));
@ -72,26 +77,26 @@ bool PipeWireCore::init()
connect(notifier, &QSocketNotifier::activated, this, [this] {
int result = pw_loop_iterate (pwMainLoop, 0);
if (result < 0)
qCWarning(KWIN_PIPEWIRE) << "pipewire_loop_iterate failed: " << result;
qCWarning(KWIN_SCREENCAST) << "pipewire_loop_iterate failed: " << result;
}
);
pwContext = pw_context_new(pwMainLoop, nullptr, 0);
if (!pwContext) {
qCWarning(KWIN_PIPEWIRE) << "Failed to create PipeWire context";
qCWarning(KWIN_SCREENCAST) << "Failed to create PipeWire context";
m_error = i18n("Failed to create PipeWire context");
return false;
}
pwCore = pw_context_connect(pwContext, nullptr, 0);
if (!pwCore) {
qCWarning(KWIN_PIPEWIRE) << "Failed to connect PipeWire context";
qCWarning(KWIN_SCREENCAST) << "Failed to connect PipeWire context";
m_error = i18n("Failed to connect PipeWire context");
return false;
}
if (pw_loop_iterate(pwMainLoop, 0) < 0) {
qCWarning(KWIN_PIPEWIRE) << "Failed to start main PipeWire loop";
qCWarning(KWIN_SCREENCAST) << "Failed to start main PipeWire loop";
m_error = i18n("Failed to start main PipeWire loop");
return false;
}
@ -113,3 +118,5 @@ QSharedPointer< PipeWireCore > PipeWireCore::self()
}
return ret;
}
} // namespace KWin

View File

@ -22,10 +22,14 @@
#pragma once
#include <QObject>
#include <QDebug>
#include <spa/utils/hook.h>
#include <QObject>
#include <pipewire/pipewire.h>
#include <spa/utils/hook.h>
namespace KWin
{
class PipeWireCore : public QObject
{
@ -52,3 +56,5 @@ public:
Q_SIGNALS:
void pipewireFailed(const QString &message);
};
} // namespace KWin

View File

@ -21,36 +21,39 @@
*/
#include "pipewirestream.h"
#include "cursor.h"
#include "dmabuftexture.h"
#include "kwineglimagetexture.h"
#include "kwinscreencast_logging.h"
#include "main.h"
#include "pipewirecore.h"
#include "platform.h"
#include "platformsupport/scenes/opengl/drm_fourcc.h"
#include "scenes/opengl/egl_dmabuf.h"
#include "utils.h"
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <gbm.h>
#include <spa/buffer/meta.h>
#include <KLocalizedString>
#include <QLoggingCategory>
#include <QPainter>
#include "utils.h"
#include "cursor.h"
#include "main.h"
#include "platform.h"
#include "scenes/opengl/egl_dmabuf.h"
#include "platformsupport/scenes/opengl/drm_fourcc.h"
#include "kwineglimagetexture.h"
#include "dmabuftexture.h"
#include "pipewirecore.h"
#include "kwinpipewire_logging.h"
#include <KLocalizedString>
#include <spa/buffer/meta.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
namespace KWin
{
void PipeWireStream::onStreamStateChanged(void *data, pw_stream_state old, pw_stream_state state, const char *error_message)
{
PipeWireStream *pw = static_cast<PipeWireStream*>(data);
qCDebug(KWIN_PIPEWIRE) << "state changed"<< pw_stream_state_as_string(old) << " -> " << pw_stream_state_as_string(state) << error_message;
qCDebug(KWIN_SCREENCAST) << "state changed"<< pw_stream_state_as_string(old) << " -> " << pw_stream_state_as_string(state) << error_message;
switch (state) {
case PW_STREAM_STATE_ERROR:
qCWarning(KWIN_PIPEWIRE) << "Stream error: " << error_message;
qCWarning(KWIN_SCREENCAST) << "Stream error: " << error_message;
break;
case PW_STREAM_STATE_PAUSED:
if (pw->nodeId() == 0 && pw->pwStream) {
@ -84,7 +87,7 @@ void PipeWireStream::newStreamParams()
spa_pod_builder pod_builder = SPA_POD_BUILDER_INIT (paramsBuffer, sizeof (paramsBuffer));
spa_rectangle resolution = SPA_RECTANGLE(uint32_t(m_resolution.width()), uint32_t(m_resolution.height()));
const auto cursorSize = KWin::Cursors::self()->currentCursor()->themeSize();
const auto cursorSize = Cursors::self()->currentCursor()->themeSize();
const spa_pod *params[] = {
(spa_pod*) spa_pod_builder_add_object(&pod_builder,
SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
@ -110,7 +113,7 @@ void PipeWireStream::onStreamParamChanged(void *data, uint32_t id, const struct
PipeWireStream *pw = static_cast<PipeWireStream *>(data);
spa_format_video_raw_parse (format, &pw->videoFormat);
qCDebug(KWIN_PIPEWIRE) << "Stream format changed" << pw << pw->videoFormat.format;
qCDebug(KWIN_SCREENCAST) << "Stream format changed" << pw << pw->videoFormat.format;
pw->newStreamParams();
}
@ -122,11 +125,11 @@ void PipeWireStream::onStreamAddBuffer(void *data, pw_buffer *buffer)
spa_data->mapoffset = 0;
spa_data->flags = SPA_DATA_FLAG_READWRITE;
QSharedPointer<KWin::DmaBufTexture> dmabuf (KWin::kwinApp()->platform()->createDmaBufTexture(stream->m_resolution));
QSharedPointer<DmaBufTexture> dmabuf(kwinApp()->platform()->createDmaBufTexture(stream->m_resolution));
if (dmabuf) {
spa_data->type = SPA_DATA_DmaBuf;
spa_data->fd = dmabuf->fd();
spa_data->data = NULL;
spa_data->data = nullptr;
spa_data->maxsize = dmabuf->stride() * stream->m_resolution.height();
stream->m_dmabufDataForPwBuffer.insert(buffer, dmabuf);
@ -138,30 +141,30 @@ void PipeWireStream::onStreamAddBuffer(void *data, pw_buffer *buffer)
spa_data->type = SPA_DATA_MemFd;
spa_data->fd = memfd_create("kwin-screencast-memfd", MFD_CLOEXEC | MFD_ALLOW_SEALING);
if (spa_data->fd == -1) {
qCCritical(KWIN_PIPEWIRE) << "memfd: Can't create memfd";
qCCritical(KWIN_SCREENCAST) << "memfd: Can't create memfd";
return;
}
spa_data->mapoffset = 0;
if (ftruncate (spa_data->fd, spa_data->maxsize) < 0) {
qCCritical(KWIN_PIPEWIRE) << "memfd: Can't truncate to" << spa_data->maxsize;
qCCritical(KWIN_SCREENCAST) << "memfd: Can't truncate to" << spa_data->maxsize;
return;
}
unsigned int seals = F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL;
if (fcntl(spa_data->fd, F_ADD_SEALS, seals) == -1)
qCWarning(KWIN_PIPEWIRE) << "memfd: Failed to add seals";
qCWarning(KWIN_SCREENCAST) << "memfd: Failed to add seals";
spa_data->data = mmap(NULL,
spa_data->data = mmap(nullptr,
spa_data->maxsize,
PROT_READ | PROT_WRITE,
MAP_SHARED,
spa_data->fd,
spa_data->mapoffset);
if (spa_data->data == MAP_FAILED)
qCCritical(KWIN_PIPEWIRE) << "memfd: Failed to mmap memory";
qCCritical(KWIN_SCREENCAST) << "memfd: Failed to mmap memory";
else
qCDebug(KWIN_PIPEWIRE) << "memfd: created successfully" << spa_data->data << spa_data->maxsize;
qCDebug(KWIN_SCREENCAST) << "memfd: created successfully" << spa_data->data << spa_data->maxsize;
#endif
}
}
@ -212,7 +215,7 @@ bool PipeWireStream::init()
connect(pwCore.data(), &PipeWireCore::pipewireFailed, this, &PipeWireStream::coreFailed);
if (!createStream()) {
qCWarning(KWIN_PIPEWIRE) << "Failed to create PipeWire stream";
qCWarning(KWIN_SCREENCAST) << "Failed to create PipeWire stream";
m_error = i18n("Failed to create PipeWire stream");
return false;
}
@ -247,7 +250,7 @@ bool PipeWireStream::createStream()
spa_rectangle resolution = SPA_RECTANGLE(uint32_t(m_resolution.width()), uint32_t(m_resolution.height()));
const auto format = m_hasAlpha || m_gbmDevice ? SPA_VIDEO_FORMAT_BGRA : SPA_VIDEO_FORMAT_BGR;
const auto format = m_hasAlpha ? SPA_VIDEO_FORMAT_BGRA : SPA_VIDEO_FORMAT_BGR;
const spa_pod *param = (spa_pod*)spa_pod_builder_add_object(&podBuilder,
SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
@ -262,16 +265,16 @@ bool PipeWireStream::createStream()
auto flags = pw_stream_flags(PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_ALLOC_BUFFERS);
if (pw_stream_connect(pwStream, PW_DIRECTION_OUTPUT, SPA_ID_INVALID, flags, &param, 1) != 0) {
qCWarning(KWIN_PIPEWIRE) << "Could not connect to stream";
qCWarning(KWIN_SCREENCAST) << "Could not connect to stream";
pw_stream_destroy(pwStream);
return false;
}
if (m_cursor.mode == KWaylandServer::ScreencastInterface::Embedded) {
connect(KWin::Cursors::self(), &KWin::Cursors::positionChanged, this, [this] {
connect(Cursors::self(), &Cursors::positionChanged, this, [this] {
if (m_cursor.lastFrameTexture) {
m_repainting = true;
recordFrame(m_cursor.lastFrameTexture.data(), QRegion{m_cursor.lastRect} | cursorGeometry(KWin::Cursors::self()->currentCursor()));
recordFrame(m_cursor.lastFrameTexture.data(), QRegion{m_cursor.lastRect} | cursorGeometry(Cursors::self()->currentCursor()));
m_repainting = false;
}
});
@ -291,9 +294,9 @@ void PipeWireStream::stop()
delete this;
}
static KWin::GLTexture *copyTexture(KWin::GLTexture *texture)
static GLTexture *copyTexture(GLTexture *texture)
{
KWin::GLTexture *copy = new KWin::GLTexture(texture->internalFormat(), texture->size());
GLTexture *copy = new GLTexture(texture->internalFormat(), texture->size());
copy->setFilter(GL_LINEAR);
copy->setWrapMode(GL_CLAMP_TO_EDGE);
@ -305,7 +308,7 @@ static KWin::GLTexture *copyTexture(KWin::GLTexture *texture)
return copy;
}
void PipeWireStream::recordFrame(KWin::GLTexture* frameTexture, const QRegion &damagedRegion)
void PipeWireStream::recordFrame(GLTexture *frameTexture, const QRegion &damagedRegion)
{
Q_ASSERT(!m_stopped);
Q_ASSERT(frameTexture);
@ -320,7 +323,7 @@ void PipeWireStream::recordFrame(KWin::GLTexture* frameTexture, const QRegion &d
auto state = pw_stream_get_state(pwStream, &error);
if (state != PW_STREAM_STATE_STREAMING) {
if (error) {
qCWarning(KWIN_PIPEWIRE) << "Failed to record frame: stream is not active" << error;
qCWarning(KWIN_SCREENCAST) << "Failed to record frame: stream is not active" << error;
}
return;
}
@ -336,7 +339,7 @@ void PipeWireStream::recordFrame(KWin::GLTexture* frameTexture, const QRegion &d
uint8_t *data = (uint8_t *) spa_data->data;
if (!data && spa_buffer->datas->type != SPA_DATA_DmaBuf) {
qCWarning(KWIN_PIPEWIRE) << "Failed to record frame: invalid buffer data";
qCWarning(KWIN_SCREENCAST) << "Failed to record frame: invalid buffer data";
pw_stream_queue_buffer(pwStream, buffer);
return;
}
@ -349,7 +352,7 @@ void PipeWireStream::recordFrame(KWin::GLTexture* frameTexture, const QRegion &d
const uint bufferSize = stride * size.height();
if (bufferSize > spa_data->maxsize) {
qCDebug(KWIN_PIPEWIRE) << "Failed to record frame: frame is too big";
qCDebug(KWIN_SCREENCAST) << "Failed to record frame: frame is too big";
pw_stream_queue_buffer(pwStream, buffer);
return;
}
@ -359,7 +362,7 @@ void PipeWireStream::recordFrame(KWin::GLTexture* frameTexture, const QRegion &d
frameTexture->bind();
glGetTextureImage(frameTexture->texture(), 0, m_hasAlpha ? GL_BGRA : GL_BGR, GL_UNSIGNED_BYTE, bufferSize, data);
auto cursor = KWin::Cursors::self()->currentCursor();
auto cursor = Cursors::self()->currentCursor();
if (m_cursor.mode == KWaylandServer::ScreencastInterface::Embedded && m_cursor.viewport.contains(cursor->pos())) {
QImage dest(data, size.width(), size.height(), QImage::Format_RGBA8888_Premultiplied);
QPainter painter(&dest);
@ -390,13 +393,13 @@ void PipeWireStream::recordFrame(KWin::GLTexture* frameTexture, const QRegion &d
frameTexture->render(damagedRegion, r, true);
auto cursor = KWin::Cursors::self()->currentCursor();
auto cursor = Cursors::self()->currentCursor();
if (m_cursor.mode == KWaylandServer::ScreencastInterface::Embedded && m_cursor.viewport.contains(cursor->pos())) {
if (!m_repainting) //We need to copy the last version of the stream to render the moved cursor on top
m_cursor.lastFrameTexture.reset(copyTexture(frameTexture));
if (!m_cursor.texture || m_cursor.lastKey != cursor->image().cacheKey())
m_cursor.texture.reset(new KWin::GLTexture(cursor->image()));
m_cursor.texture.reset(new GLTexture(cursor->image()));
m_cursor.texture->setYInverted(false);
m_cursor.texture->bind();
@ -418,20 +421,20 @@ void PipeWireStream::recordFrame(KWin::GLTexture* frameTexture, const QRegion &d
frameTexture->unbind();
if (m_cursor.mode == KWaylandServer::ScreencastInterface::Metadata) {
sendCursorData(KWin::Cursors::self()->currentCursor(),
sendCursorData(Cursors::self()->currentCursor(),
(spa_meta_cursor *) spa_buffer_find_meta_data (spa_buffer, SPA_META_Cursor, sizeof (spa_meta_cursor)));
}
pw_stream_queue_buffer(pwStream, buffer);
}
QRect PipeWireStream::cursorGeometry(KWin::Cursor *cursor) const
QRect PipeWireStream::cursorGeometry(Cursor *cursor) const
{
const auto position = (cursor->pos() - m_cursor.viewport.topLeft() - cursor->hotspot()) * m_cursor.scale;
return QRect{position, m_cursor.texture->size()};
}
void PipeWireStream::sendCursorData(KWin::Cursor* cursor, spa_meta_cursor *spa_meta_cursor)
void PipeWireStream::sendCursorData(Cursor *cursor, spa_meta_cursor *spa_meta_cursor)
{
if (!cursor || !spa_meta_cursor) {
return;
@ -476,3 +479,5 @@ void PipeWireStream::setCursorMode(KWaylandServer::ScreencastInterface::CursorMo
m_cursor.scale = scale;
m_cursor.viewport = viewport;
}
} // namespace KWin

View File

@ -22,27 +22,27 @@
#pragma once
#include <QObject>
#include <QSize>
#include <QHash>
#include <QSharedPointer>
#include "config-kwin.h"
#include "kwinglobals.h"
#include <KWaylandServer/screencast_interface.h>
#include "kwinglobals.h"
#include "config-kwin.h"
#include <QHash>
#include <QObject>
#include <QSharedPointer>
#include <QSize>
#include <pipewire/pipewire.h>
#include <spa/param/format-utils.h>
#include <spa/param/video/format-utils.h>
#include <spa/param/props.h>
#undef Status
#include <spa/param/video/format-utils.h>
namespace KWin
{
class Cursor;
class GLTexture;
class DmaBufTexture;
}
class Cursor;
class DmaBufTexture;
class GLTexture;
class PipeWireCore;
class KWIN_EXPORT PipeWireStream : public QObject
@ -62,7 +62,7 @@ public:
void stop();
/** Renders @p frame into the current framebuffer into the stream */
void recordFrame(KWin::GLTexture *frame, const QRegion &damagedRegion);
void recordFrame(GLTexture *frame, const QRegion &damagedRegion);
void setCursorMode(KWaylandServer::ScreencastInterface::CursorMode mode, qreal scale, const QRect &viewport);
@ -80,7 +80,7 @@ private:
bool createStream();
void updateParams();
void coreFailed(const QString &errorMessage);
void sendCursorData(KWin::Cursor* cursor, spa_meta_cursor *spa_cursor);
void sendCursorData(Cursor *cursor, spa_meta_cursor *spa_cursor);
void newStreamParams();
QSharedPointer<PipeWireCore> pwCore;
@ -96,7 +96,6 @@ private:
spa_video_info_raw videoFormat;
QString m_error;
const bool m_hasAlpha;
struct gbm_device *m_gbmDevice = nullptr;
struct {
KWaylandServer::ScreencastInterface::CursorMode mode = KWaylandServer::ScreencastInterface::Hidden;
@ -104,11 +103,13 @@ private:
QRect viewport;
qint64 lastKey = 0;
QRect lastRect;
QScopedPointer<KWin::GLTexture> texture;
QScopedPointer<KWin::GLTexture> lastFrameTexture;
QScopedPointer<GLTexture> texture;
QScopedPointer<GLTexture> lastFrameTexture;
} m_cursor;
bool m_repainting = false;
QRect cursorGeometry(KWin::Cursor *cursor) const;
QRect cursorGeometry(Cursor *cursor) const;
QHash<struct pw_buffer *, QSharedPointer<KWin::DmaBufTexture>> m_dmabufDataForPwBuffer;
QHash<struct pw_buffer *, QSharedPointer<DmaBufTexture>> m_dmabufDataForPwBuffer;
};
} // namespace KWin

View File

@ -36,7 +36,8 @@
#include <KWaylandServer/display.h>
#include <KWaylandServer/output_interface.h>
using namespace KWin;
namespace KWin
{
ScreencastManager::ScreencastManager(QObject *parent)
: QObject(parent)
@ -53,7 +54,7 @@ class EGLFence : public QObject
public:
EGLFence(EGLDisplay eglDisplay)
: m_eglDisplay(eglDisplay)
, m_sync(eglCreateSync(eglDisplay, EGL_SYNC_FENCE_KHR, NULL))
, m_sync(eglCreateSync(eglDisplay, EGL_SYNC_FENCE_KHR, nullptr))
{
Q_ASSERT(m_sync);
glFinish();
@ -80,7 +81,7 @@ private:
class WindowStream : public PipeWireStream
{
public:
WindowStream(KWin::Toplevel *toplevel, QObject *parent)
WindowStream(Toplevel *toplevel, QObject *parent)
: PipeWireStream(toplevel->hasAlpha(), toplevel->clientSize() * toplevel->bufferScale(), parent)
, m_toplevel(toplevel)
{
@ -93,14 +94,14 @@ public:
private:
void startFeeding() {
auto scene = KWin::Compositor::self()->scene();
auto scene = Compositor::self()->scene();
connect(scene, &Scene::frameRendered, this, &WindowStream::bufferToStream);
connect(m_toplevel, &Toplevel::damaged, this, &WindowStream::includeDamage);
m_toplevel->damaged(m_toplevel, m_toplevel->frameGeometry());
}
void includeDamage(KWin::Toplevel *toplevel, const QRect &damage) {
void includeDamage(Toplevel *toplevel, const QRect &damage) {
Q_ASSERT(m_toplevel == toplevel);
m_damagedRegion |= damage;
}
@ -122,12 +123,12 @@ private:
}
QRegion m_damagedRegion;
KWin::Toplevel *m_toplevel;
Toplevel *m_toplevel;
};
void ScreencastManager::streamWindow(KWaylandServer::ScreencastStreamInterface *waylandStream, const QString &winid)
{
auto *toplevel = KWin::Workspace::self()->findToplevel(winid);
auto *toplevel = Workspace::self()->findToplevel(winid);
if (!toplevel) {
waylandStream->sendFailed(i18n("Could not find window id %1", winid));
@ -166,7 +167,7 @@ void ScreencastManager::streamOutput(KWaylandServer::ScreencastStreamInterface *
stream->setCursorMode(mode, streamOutput->scale(), streamOutput->geometry());
connect(streamOutput, &QObject::destroyed, stream, &PipeWireStream::stopStreaming);
auto bufferToStream = [streamOutput, stream] (const QRegion &damagedRegion) {
auto scene = KWin::Compositor::self()->scene();
auto scene = Compositor::self()->scene();
auto texture = scene->textureForOutput(streamOutput);
const QRect frame({}, streamOutput->modeSize());
@ -174,7 +175,7 @@ void ScreencastManager::streamOutput(KWaylandServer::ScreencastStreamInterface *
stream->recordFrame(texture.data(), region);
};
connect(stream, &PipeWireStream::startStreaming, waylandStream, [streamOutput, stream, bufferToStream] {
KWin::Compositor::self()->addRepaint(streamOutput->geometry());
Compositor::self()->addRepaint(streamOutput->geometry());
connect(streamOutput, &AbstractWaylandOutput::outputChange, stream, bufferToStream);
});
integrateStreams(waylandStream, stream);
@ -195,3 +196,5 @@ void ScreencastManager::integrateStreams(KWaylandServer::ScreencastStreamInterfa
delete stream;
}
}
} // namespace KWin

View File

@ -23,6 +23,9 @@
#include <KWaylandServer/screencast_interface.h>
namespace KWin
{
class PipeWireStream;
class ScreencastManager : public QObject
@ -43,3 +46,4 @@ private:
KWaylandServer::ScreencastInterface *m_screencast;
};
} // namespace KWin