diff --git a/kwin/src/CMakeLists.txt b/kwin/src/CMakeLists.txt index d99e237..e29b917 100644 --- a/kwin/src/CMakeLists.txt +++ b/kwin/src/CMakeLists.txt @@ -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) diff --git a/kwin/src/breezydesktopeffect.cpp b/kwin/src/breezydesktopeffect.cpp index 3ae15f9..59a32fb 100644 --- a/kwin/src/breezydesktopeffect.cpp +++ b/kwin/src/breezydesktopeffect.cpp @@ -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 +#include + #include #include #include @@ -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" \ No newline at end of file diff --git a/kwin/src/breezydesktopeffect.h b/kwin/src/breezydesktopeffect.h index ef038ad..df694dd 100644 --- a/kwin/src/breezydesktopeffect.h +++ b/kwin/src/breezydesktopeffect.h @@ -1,6 +1,7 @@ #pragma once #include "kcm/shortcuts.h" +#include "kwaylandclient.h" #include #include @@ -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 m_imuRotations; diff --git a/kwin/src/kwaylandclient.cpp b/kwin/src/kwaylandclient.cpp new file mode 100644 index 0000000..7f86a57 --- /dev/null +++ b/kwin/src/kwaylandclient.cpp @@ -0,0 +1,112 @@ +#include "kwaylandclient.h" +#include "wayland-zkde-screencast-unstable-v1-client-protocol.h" + +#include +#include +#include +#include + +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(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(); +} \ No newline at end of file diff --git a/kwin/src/kwaylandclient.h b/kwin/src/kwaylandclient.h new file mode 100644 index 0000000..1c8c6a2 --- /dev/null +++ b/kwin/src/kwaylandclient.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#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 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 m_streams; + Screencasting *m_screencasting = nullptr; + }; + + } // namespace Wayland +} // namespace KWin + +Q_DECLARE_METATYPE(KWin::Wayland::Stream) +Q_DECLARE_METATYPE(KWin::Wayland::Streams) \ No newline at end of file diff --git a/kwin/src/screencasting.cpp b/kwin/src/screencasting.cpp new file mode 100644 index 0000000..dd51a33 --- /dev/null +++ b/kwin/src/screencasting.cpp @@ -0,0 +1,127 @@ +/* + SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez + + 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 +#include +#include +#include +#include + +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 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); +} \ No newline at end of file diff --git a/kwin/src/screencasting.h b/kwin/src/screencasting.h new file mode 100644 index 0000000..3334210 --- /dev/null +++ b/kwin/src/screencasting.h @@ -0,0 +1,85 @@ +/* + SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#pragma once + +#include +#include +#include +#include +#include + +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 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 d; +}; \ No newline at end of file