Add screencast wayland integration

This commit is contained in:
wheaney 2025-08-20 13:03:35 -07:00
parent 026f5825f3
commit 94289f1074
7 changed files with 437 additions and 1 deletions

View File

@ -1,8 +1,34 @@
add_subdirectory(kcm)
# Ensure C sources (wayland-*-protocol.c) are compiled
enable_language(C)
# ecm_add_qml_module ?
# ecm_target_qml_sources ?
kcoreaddons_add_plugin(breezy_desktop_effect INSTALL_NAMESPACE "kwin/effects/plugins/")
find_package(Wayland COMPONENTS Client)
find_package(KWayland REQUIRED)
find_package(Qt6 CONFIG REQUIRED COMPONENTS WaylandClient)
# Try to locate kde-screencast-unstable-v1.xml from PlasmaWaylandProtocols
find_package(PlasmaWaylandProtocols REQUIRED)
set(PWP_PROTOCOL_DIR "/usr/share/plasma-wayland-protocols")
set(PWP_PROTOCOL_XML "${PWP_PROTOCOL_DIR}/zkde-screencast-unstable-v1.xml")
if(EXISTS "${PWP_PROTOCOL_XML}")
set(PROTOCOL_XML "${PWP_PROTOCOL_XML}")
else()
set(PROTOCOL_XML "${CMAKE_CURRENT_SOURCE_DIR}/zkde-screencast-unstable-v1.xml")
endif()
qt6_generate_wayland_protocol_client_sources(breezy_desktop_effect
PRIVATE_CODE FILES "${PROTOCOL_XML}"
)
target_sources(breezy_desktop_effect PRIVATE
breezydesktopeffect.cpp
kwaylandclient.cpp
screencasting.cpp
main.cpp
)
kconfig_add_kcfg_files(breezy_desktop_effect breezydesktopconfig.kcfgc)
@ -12,6 +38,7 @@ target_link_libraries(breezy_desktop_effect
Qt6::Core
Qt6::Gui
Qt6::Quick
Qt6::WaylandClient
KF6::ConfigCore
KF6::ConfigGui
@ -21,6 +48,7 @@ target_link_libraries(breezy_desktop_effect
KF6::WindowSystem
KWin::kwin
Plasma::KWaylandClient
)
install(DIRECTORY qml DESTINATION ${KDE_INSTALL_DATADIR}/kwin/effects/breezy_desktop_effect)

View File

@ -3,10 +3,14 @@
#include "breezydesktopconfig.h"
#include "effect/effect.h"
#include "effect/effecthandler.h"
#include "kwaylandclient.h"
#include "opengl/glutils.h"
#include "core/rendertarget.h"
#include "core/renderviewport.h"
#include <kwin/main.h>
#include <core/outputbackend.h>
#include <functional>
#include <QAction>
#include <QFile>
@ -193,6 +197,10 @@ void BreezyDesktopEffect::deactivate()
disconnect(effects, &EffectsHandler::cursorShapeChanged, this, &BreezyDesktopEffect::updateCursorImage);
m_cursorUpdateTimer->stop();
showCursor();
if (m_waylandClient->isStreamingEnabled()) {
qCCritical(KWIN_XR) << "\t\t\tBreezy - deactivating - stopping streaming";
m_waylandClient->stopStreaming(m_stream.nodeId); // Stop any active streaming
}
// this triggers realDeactivate with a delay so if it's triggered from QML it gives the QML function time to
// exit, avoiding a crash
@ -207,7 +215,25 @@ void BreezyDesktopEffect::realDeactivate()
void BreezyDesktopEffect::recenter()
{
qCCritical(KWIN_XR) << "\t\t\tBreezy - recenter";
if (m_waylandClient == nullptr) {
m_waylandClient = new KWin::Wayland::Client();
m_waylandClient->init();
}
if (m_waylandClient->isConnectionReady() && m_waylandClient->isStreamingAvailable()) {
static int displayCounter = 0;
QString uniqueDisplayName = QStringLiteral("BreezyDesktopEffect_%1").arg(++displayCounter);
QString uniqueDisplayDesc = QStringLiteral("Breezy Desktop Effect %1").arg(displayCounter);
m_stream = m_waylandClient->startVirtualDisplay(
uniqueDisplayName,
uniqueDisplayDesc,
QSize(2560, 1440),
Screencasting::CursorMode::Hidden
);
} else {
qCCritical(KWIN_XR) << "\t\t\tBreezy - recenter - no streaming enabled";
}
QFile controlFile(QStringLiteral("/dev/shm/xr_driver_control"));
if (controlFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
controlFile.write("recenter_screen=true\n");
@ -465,6 +491,7 @@ void BreezyDesktopEffect::updateCursorPos()
Q_EMIT cursorPosChanged();
}
}
}
#include "moc_breezydesktopeffect.cpp"

View File

@ -1,6 +1,7 @@
#pragma once
#include "kcm/shortcuts.h"
#include "kwaylandclient.h"
#include <effect/quickeffect.h>
#include <QAction>
@ -89,6 +90,8 @@ namespace KWin
QTimer *m_shutdownTimer;
QString m_cursorImageSource;
KWin::Wayland::Client *m_waylandClient = nullptr;
KWin::Wayland::Stream m_stream;
bool m_enabled = false;
bool m_imuResetState;
QList<QQuaternion> m_imuRotations;

112
kwin/src/kwaylandclient.cpp Normal file
View File

@ -0,0 +1,112 @@
#include "kwaylandclient.h"
#include "wayland-zkde-screencast-unstable-v1-client-protocol.h"
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/registry.h>
#include <QLoggingCategory>
#include <QTimer>
Q_LOGGING_CATEGORY(KWAYLAND, "kwayland.xr")
using namespace KWin::Wayland;
void Client::init()
{
auto connection = new KWayland::Client::ConnectionThread;
connection->initConnection();
connect(connection, &KWayland::Client::ConnectionThread::connected, this, [this, connection]() {
qCCritical(KWAYLAND) << "Connected to Wayland display";
m_registry = new KWayland::Client::Registry(this);
connect(m_registry, &KWayland::Client::Registry::interfaceAnnounced, this, [this](const QByteArray &interfaceName, quint32 name, quint32 version) {
if (interfaceName != "zkde_screencast_unstable_v1")
return;
qCCritical(KWAYLAND) << "Found screencasting interface" << interfaceName << name << version;
m_screencasting = new Screencasting(m_registry, name, version, this);
m_connectionReady = true;
});
connect(m_registry, &KWayland::Client::Registry::interfacesAnnounced, this, [this] {
m_registryInitialized = true;
qCCritical(KWAYLAND) << "Registry initialized";
});
m_registry->create(connection);
m_registry->setup();
});
}
bool Client::isConnectionReady()
{
return m_connectionReady;
}
Stream Client::startVirtualDisplay(const QString &name,
const QString &description,
const QSize &size,
Screencasting::CursorMode mode)
{
return startStreaming(m_screencasting->createVirtualOutputStream(name, description, size, 1, mode),
{
{QLatin1String("size"), size},
{QLatin1String("source_type"), static_cast<uint>(Screencasting::Virtual)},
});
}
Stream Client::startStreaming(ScreencastingStream *stream, const QVariantMap &streamOptions)
{
QEventLoop loop;
Stream ret;
connect(stream, &ScreencastingStream::failed, &loop, [&](const QString &error) {
qCCritical(KWAYLAND) << "failed to start streaming" << stream << error;
loop.quit();
});
connect(stream, &ScreencastingStream::created, &loop, [&](uint32_t nodeid) {
ret.stream = stream;
ret.nodeId = nodeid;
ret.map = streamOptions;
m_streams.append(ret);
connect(stream, &ScreencastingStream::closed, this, [this, nodeid] {
stopStreaming(nodeid);
});
Q_ASSERT(ret.isValid());
loop.quit();
});
QTimer::singleShot(3000, &loop, [&loop, stream] {
stream->deleteLater();
loop.quit();
});
loop.exec();
return ret;
}
void Client::stopStreaming(uint32_t nodeid)
{
for (auto it = m_streams.begin(), itEnd = m_streams.end(); it != itEnd; ++it) {
if (it->nodeId == nodeid) {
it->close();
m_streams.erase(it);
break;
}
}
}
bool Client::isStreamingEnabled()
{
return !m_streams.isEmpty();
}
bool Client::isStreamingAvailable()
{
return m_screencasting;
}
void Stream::close()
{
stream->deleteLater();
}

54
kwin/src/kwaylandclient.h Normal file
View File

@ -0,0 +1,54 @@
#pragma once
#include <QObject>
#include <KWayland/Client/registry.h>
#include "screencasting.h"
#include "wayland-zkde-screencast-unstable-v1-client-protocol.h"
namespace KWin
{
namespace Wayland
{
struct Stream {
ScreencastingStream *stream = nullptr;
uint nodeId;
QVariantMap map;
bool isValid() const
{
return stream != nullptr;
}
void close();
};
typedef QList<Stream> Streams;
class Client : public QObject
{
Q_OBJECT
public:
void init();
bool isConnectionReady();
bool isStreamingEnabled();
bool isStreamingAvailable();
Stream startVirtualDisplay(const QString &name, const QString &description, const QSize &size, Screencasting::CursorMode mode);
void stopStreaming(uint node);
Q_SIGNALS:
void connected();
void disconnected();
void errorOccurred(const QString &error);
private:
Stream startStreaming(ScreencastingStream *stream, const QVariantMap &streamOptions);
bool m_registryInitialized = false;
bool m_connectionReady = false;
KWayland::Client::Registry *m_registry = nullptr;
QList<Stream> m_streams;
Screencasting *m_screencasting = nullptr;
};
} // namespace Wayland
} // namespace KWin
Q_DECLARE_METATYPE(KWin::Wayland::Stream)
Q_DECLARE_METATYPE(KWin::Wayland::Streams)

127
kwin/src/screencasting.cpp Normal file
View File

@ -0,0 +1,127 @@
/*
SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "screencasting.h"
#include "qwayland-zkde-screencast-unstable-v1.h"
#include <KWayland/Client/output.h>
#include <KWayland/Client/plasmawindowmanagement.h>
#include <KWayland/Client/registry.h>
#include <QDebug>
#include <QRect>
using namespace KWayland::Client;
class ScreencastingStreamPrivate : public QtWayland::zkde_screencast_stream_unstable_v1
{
public:
ScreencastingStreamPrivate(ScreencastingStream *q)
: q(q)
{
}
~ScreencastingStreamPrivate() override
{
close();
q->deleteLater();
}
void zkde_screencast_stream_unstable_v1_created(uint32_t node) override
{
m_nodeid = node;
Q_EMIT q->created(node);
}
void zkde_screencast_stream_unstable_v1_closed() override
{
Q_EMIT q->closed();
}
void zkde_screencast_stream_unstable_v1_failed(const QString &error) override
{
Q_EMIT q->failed(error);
}
uint m_nodeid = 0;
QRect m_geometry;
QPointer<ScreencastingStream> q;
};
ScreencastingStream::ScreencastingStream(QObject *parent)
: QObject(parent)
, d(new ScreencastingStreamPrivate(this))
{
}
ScreencastingStream::~ScreencastingStream() = default;
quint32 ScreencastingStream::nodeid() const
{
return d->m_nodeid;
}
QRect ScreencastingStream::geometry() const
{
return d->m_geometry;
}
class ScreencastingPrivate : public QtWayland::zkde_screencast_unstable_v1
{
public:
ScreencastingPrivate(Registry *registry, int id, int version, Screencasting *q)
: QtWayland::zkde_screencast_unstable_v1(*registry, id, version)
, q(q)
{
}
ScreencastingPrivate(::zkde_screencast_unstable_v1 *screencasting, Screencasting *q)
: QtWayland::zkde_screencast_unstable_v1(screencasting)
, q(q)
{
}
~ScreencastingPrivate() override
{
destroy();
}
Screencasting *const q;
};
Screencasting::Screencasting(QObject *parent)
: QObject(parent)
{
}
Screencasting::Screencasting(Registry *registry, int id, int version, QObject *parent)
: QObject(parent)
, d(new ScreencastingPrivate(registry, id, version, this))
{
}
Screencasting::~Screencasting() = default;
ScreencastingStream *
Screencasting::createVirtualOutputStream(const QString &name, const QString &description, const QSize &s, qreal scale, Screencasting::CursorMode mode)
{
auto stream = new ScreencastingStream(this);
if (d->version() >= ZKDE_SCREENCAST_UNSTABLE_V1_STREAM_VIRTUAL_OUTPUT_WITH_DESCRIPTION_SINCE_VERSION) {
stream->d->init(d->stream_virtual_output_with_description(name, description, s.width(), s.height(), wl_fixed_from_double(scale), mode));
} else {
stream->d->init(d->stream_virtual_output(name, s.width(), s.height(), wl_fixed_from_double(scale), mode));
}
stream->d->m_geometry = QRect(QPoint(0, 0), s);
return stream;
}
void Screencasting::setup(::zkde_screencast_unstable_v1 *screencasting)
{
d.reset(new ScreencastingPrivate(screencasting, this));
}
void Screencasting::destroy()
{
d.reset(nullptr);
}

85
kwin/src/screencasting.h Normal file
View File

@ -0,0 +1,85 @@
/*
SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#pragma once
#include <QList>
#include <QObject>
#include <QScreen>
#include <QSharedPointer>
#include <optional>
struct zkde_screencast_unstable_v1;
namespace KWayland
{
namespace Client
{
class PlasmaWindow;
class Registry;
class Output;
}
}
class ScreencastingPrivate;
class ScreencastingSourcePrivate;
class ScreencastingStreamPrivate;
class ScreencastingStream : public QObject
{
Q_OBJECT
public:
ScreencastingStream(QObject *parent);
~ScreencastingStream() override;
quint32 nodeid() const;
QRect geometry() const;
Q_SIGNALS:
void created(quint32 nodeid);
void failed(const QString &error);
void closed();
private:
friend class Screencasting;
QScopedPointer<ScreencastingStreamPrivate> d;
};
class Screencasting : public QObject
{
Q_OBJECT
public:
explicit Screencasting(QObject *parent = nullptr);
explicit Screencasting(KWayland::Client::Registry *registry, int id, int version, QObject *parent = nullptr);
~Screencasting() override;
enum SourceType {
Any = 0,
Monitor = 1,
Window = 2,
Virtual = 4,
};
Q_ENUM(SourceType)
Q_DECLARE_FLAGS(SourceTypes, SourceType)
enum CursorMode {
Hidden = 1,
Embedded = 2,
Metadata = 4,
};
Q_ENUM(CursorMode)
ScreencastingStream *createVirtualOutputStream(const QString &name, const QString &description, const QSize &size, qreal scale, CursorMode mode);
void setup(zkde_screencast_unstable_v1 *screencasting);
void destroy();
Q_SIGNALS:
void removed();
void sourcesChanged();
private:
QScopedPointer<ScreencastingPrivate> d;
};