From b4ee4674f9a74e3d602c7fb17bc09f79d221583c Mon Sep 17 00:00:00 2001 From: Ikalco <73481042+ikalco@users.noreply.github.com> Date: Sun, 22 Feb 2026 06:30:11 -0600 Subject: protocols: implement image-capture-source-v1 and image-copy-capture-v1 (#11709) Implements the new screencopy protocols --- src/protocols/ForeignToplevel.hpp | 2 +- src/protocols/ImageCaptureSource.cpp | 135 ++++++++ src/protocols/ImageCaptureSource.hpp | 73 +++++ src/protocols/ImageCopyCapture.cpp | 516 +++++++++++++++++++++++++++++ src/protocols/ImageCopyCapture.hpp | 133 ++++++++ src/protocols/LinuxDMABUF.cpp | 4 + src/protocols/LinuxDMABUF.hpp | 1 + src/protocols/Screencopy.cpp | 607 ++++++----------------------------- src/protocols/Screencopy.hpp | 86 ++--- src/protocols/ToplevelExport.cpp | 477 +++++---------------------- src/protocols/ToplevelExport.hpp | 71 ++-- src/protocols/core/Seat.cpp | 4 + src/protocols/core/Seat.hpp | 5 + 13 files changed, 1113 insertions(+), 1001 deletions(-) create mode 100644 src/protocols/ImageCaptureSource.cpp create mode 100644 src/protocols/ImageCaptureSource.hpp create mode 100644 src/protocols/ImageCopyCapture.cpp create mode 100644 src/protocols/ImageCopyCapture.hpp (limited to 'src/protocols') diff --git a/src/protocols/ForeignToplevel.hpp b/src/protocols/ForeignToplevel.hpp index f0188292..0ff74e75 100644 --- a/src/protocols/ForeignToplevel.hpp +++ b/src/protocols/ForeignToplevel.hpp @@ -45,9 +45,9 @@ class CForeignToplevelList { class CForeignToplevelProtocol : public IWaylandProtocol { public: CForeignToplevelProtocol(const wl_interface* iface, const int& ver, const std::string& name); - PHLWINDOW windowFromHandleResource(wl_resource* res); virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); + PHLWINDOW windowFromHandleResource(wl_resource* res); private: void onManagerResourceDestroy(CForeignToplevelList* mgr); diff --git a/src/protocols/ImageCaptureSource.cpp b/src/protocols/ImageCaptureSource.cpp new file mode 100644 index 00000000..9f54533e --- /dev/null +++ b/src/protocols/ImageCaptureSource.cpp @@ -0,0 +1,135 @@ +#include "ImageCaptureSource.hpp" +#include "core/Output.hpp" +#include "../helpers/Monitor.hpp" +#include "../desktop/view/Window.hpp" +#include "ForeignToplevel.hpp" + +CImageCaptureSource::CImageCaptureSource(SP resource, PHLMONITOR pMonitor) : m_resource(resource), m_monitor(pMonitor) { + if UNLIKELY (!good()) + return; + + m_resource->setData(this); + m_resource->setDestroy([this](CExtImageCaptureSourceV1* pMgr) { PROTO::imageCaptureSource->destroyResource(this); }); + m_resource->setOnDestroy([this](CExtImageCaptureSourceV1* pMgr) { PROTO::imageCaptureSource->destroyResource(this); }); +} + +CImageCaptureSource::CImageCaptureSource(SP resource, PHLWINDOW pWindow) : m_resource(resource), m_window(pWindow) { + if UNLIKELY (!good()) + return; + + m_resource->setData(this); + m_resource->setDestroy([this](CExtImageCaptureSourceV1* pMgr) { PROTO::imageCaptureSource->destroyResource(this); }); + m_resource->setOnDestroy([this](CExtImageCaptureSourceV1* pMgr) { PROTO::imageCaptureSource->destroyResource(this); }); +} + +bool CImageCaptureSource::good() { + return m_resource && m_resource->resource(); +} + +std::string CImageCaptureSource::getName() { + if (!m_monitor.expired()) + return m_monitor->m_name; + if (!m_window.expired()) + return m_window->m_title; + + return "error"; +} + +std::string CImageCaptureSource::getTypeName() { + if (!m_monitor.expired()) + return "monitor"; + if (!m_window.expired()) + return "window"; + + return "error"; +} + +CBox CImageCaptureSource::logicalBox() { + if (!m_monitor.expired()) + return m_monitor->logicalBox(); + if (!m_window.expired()) + return m_window->getFullWindowBoundingBox(); + return CBox(); +} + +COutputImageCaptureSourceProtocol::COutputImageCaptureSourceProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { + ; +} + +void COutputImageCaptureSourceProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { + const auto RESOURCE = PROTO::imageCaptureSource->m_outputManagers.emplace_back(makeShared(client, ver, id)); + + if UNLIKELY (!RESOURCE->resource()) { + wl_client_post_no_memory(client); + PROTO::imageCaptureSource->m_outputManagers.pop_back(); + return; + } + + RESOURCE->setDestroy([](CExtOutputImageCaptureSourceManagerV1* pMgr) { PROTO::imageCaptureSource->destroyResource(pMgr); }); + RESOURCE->setOnDestroy([](CExtOutputImageCaptureSourceManagerV1* pMgr) { PROTO::imageCaptureSource->destroyResource(pMgr); }); + RESOURCE->setCreateSource([](CExtOutputImageCaptureSourceManagerV1* pMgr, uint32_t id, wl_resource* output) { + PHLMONITOR pMonitor = CWLOutputResource::fromResource(output)->m_monitor.lock(); + if (!pMonitor) { + LOGM(Log::ERR, "Client tried to create source from invalid output resource"); + pMgr->error(-1, "invalid output resource"); + return; + } + + auto PSOURCE = + PROTO::imageCaptureSource->m_sources.emplace_back(makeShared(makeShared(pMgr->client(), pMgr->version(), id), pMonitor)); + PSOURCE->m_self = PSOURCE; + + LOGM(Log::INFO, "New capture source for monitor: {}", pMonitor->m_name); + }); +} + +CToplevelImageCaptureSourceProtocol::CToplevelImageCaptureSourceProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { + ; +} + +void CToplevelImageCaptureSourceProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { + const auto RESOURCE = PROTO::imageCaptureSource->m_toplevelManagers.emplace_back(makeShared(client, ver, id)); + + if UNLIKELY (!RESOURCE->resource()) { + RESOURCE->noMemory(); + PROTO::imageCaptureSource->m_toplevelManagers.pop_back(); + return; + } + + RESOURCE->setDestroy([](CExtForeignToplevelImageCaptureSourceManagerV1* pMgr) { PROTO::imageCaptureSource->destroyResource(pMgr); }); + RESOURCE->setOnDestroy([](CExtForeignToplevelImageCaptureSourceManagerV1* pMgr) { PROTO::imageCaptureSource->destroyResource(pMgr); }); + RESOURCE->setCreateSource([](CExtForeignToplevelImageCaptureSourceManagerV1* pMgr, uint32_t id, wl_resource* handle) { + PHLWINDOW pWindow = PROTO::foreignToplevel->windowFromHandleResource(handle); + if (!pWindow) { + LOGM(Log::ERR, "Client tried to create source from invalid foreign toplevel handle resource"); + pMgr->error(-1, "invalid foreign toplevel resource"); + return; + } + + auto PSOURCE = + PROTO::imageCaptureSource->m_sources.emplace_back(makeShared(makeShared(pMgr->client(), pMgr->version(), id), pWindow)); + PSOURCE->m_self = PSOURCE; + + LOGM(Log::INFO, "New capture source for foreign toplevel: {}", pWindow->m_title); + }); +} + +CImageCaptureSourceProtocol::CImageCaptureSourceProtocol() { + m_output = makeUnique(&ext_output_image_capture_source_manager_v1_interface, 1, "OutputImageCaptureSource"); + m_toplevel = makeUnique(&ext_foreign_toplevel_image_capture_source_manager_v1_interface, 1, "ForeignToplevelImageCaptureSource"); +} + +SP CImageCaptureSourceProtocol::sourceFromResource(wl_resource* res) { + auto data = sc(sc(wl_resource_get_user_data(res))->data()); + return data && data->m_self ? data->m_self.lock() : nullptr; +} + +void CImageCaptureSourceProtocol::destroyResource(CExtOutputImageCaptureSourceManagerV1* resource) { + std::erase_if(m_outputManagers, [&](const auto& other) { return other.get() == resource; }); +} +void CImageCaptureSourceProtocol::destroyResource(CExtForeignToplevelImageCaptureSourceManagerV1* resource) { + std::erase_if(m_toplevelManagers, [&](const auto& other) { return other.get() == resource; }); +} +void CImageCaptureSourceProtocol::destroyResource(CImageCaptureSource* resource) { + std::erase_if(m_sources, [&](const auto& other) { return other.get() == resource; }); +} diff --git a/src/protocols/ImageCaptureSource.hpp b/src/protocols/ImageCaptureSource.hpp new file mode 100644 index 00000000..47580dd2 --- /dev/null +++ b/src/protocols/ImageCaptureSource.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include + +#include "../defines.hpp" +#include "../helpers/signal/Signal.hpp" +#include "WaylandProtocol.hpp" +#include "ext-image-capture-source-v1.hpp" + +class CImageCopyCaptureSession; + +class CImageCaptureSource { + public: + CImageCaptureSource(SP resource, PHLMONITOR pMonitor); + CImageCaptureSource(SP resource, PHLWINDOW pWindow); + + bool good(); + std::string getName(); + std::string getTypeName(); + CBox logicalBox(); + + WP m_self; + + private: + SP m_resource; + + PHLMONITORREF m_monitor; + PHLWINDOWREF m_window; + + friend class CImageCopyCaptureSession; + friend class CImageCopyCaptureCursorSession; +}; + +class COutputImageCaptureSourceProtocol : public IWaylandProtocol { + public: + COutputImageCaptureSourceProtocol(const wl_interface* iface, const int& ver, const std::string& name); + + virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); +}; + +class CToplevelImageCaptureSourceProtocol : public IWaylandProtocol { + public: + CToplevelImageCaptureSourceProtocol(const wl_interface* iface, const int& ver, const std::string& name); + + virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); +}; + +class CImageCaptureSourceProtocol { + public: + CImageCaptureSourceProtocol(); + + SP sourceFromResource(wl_resource* resource); + + void destroyResource(CExtOutputImageCaptureSourceManagerV1* resource); + void destroyResource(CExtForeignToplevelImageCaptureSourceManagerV1* resource); + void destroyResource(CImageCaptureSource* resource); + + private: + UP m_output; + UP m_toplevel; + + std::vector> m_outputManagers; + std::vector> m_toplevelManagers; + + std::vector> m_sources; + + friend class COutputImageCaptureSourceProtocol; + friend class CToplevelImageCaptureSourceProtocol; +}; + +namespace PROTO { + inline UP imageCaptureSource; +}; diff --git a/src/protocols/ImageCopyCapture.cpp b/src/protocols/ImageCopyCapture.cpp new file mode 100644 index 00000000..eca3c939 --- /dev/null +++ b/src/protocols/ImageCopyCapture.cpp @@ -0,0 +1,516 @@ +#include "ImageCopyCapture.hpp" +#include "../managers/screenshare/ScreenshareManager.hpp" +#include "../managers/permissions/DynamicPermissionManager.hpp" +#include "../managers/PointerManager.hpp" +#include "./core/Seat.hpp" +#include "LinuxDMABUF.hpp" +#include "../desktop/view/Window.hpp" +#include "../render/OpenGL.hpp" +#include "../desktop/state/FocusState.hpp" +#include + +using namespace Screenshare; + +CImageCopyCaptureSession::CImageCopyCaptureSession(SP resource, SP source, extImageCopyCaptureManagerV1Options options) : + m_resource(resource), m_source(source), m_paintCursor(options & EXT_IMAGE_COPY_CAPTURE_MANAGER_V1_OPTIONS_PAINT_CURSORS) { + if UNLIKELY (!good()) + return; + + m_resource->setDestroy([this](CExtImageCopyCaptureSessionV1* pMgr) { PROTO::imageCopyCapture->destroyResource(this); }); + m_resource->setOnDestroy([this](CExtImageCopyCaptureSessionV1* pMgr) { PROTO::imageCopyCapture->destroyResource(this); }); + + m_resource->setCreateFrame([this](CExtImageCopyCaptureSessionV1* pMgr, uint32_t id) { + if (!m_frame.expired()) { + LOGM(Log::ERR, "Duplicate frame in session for source: \"{}\"", m_source->getName()); + m_resource->error(EXT_IMAGE_COPY_CAPTURE_SESSION_V1_ERROR_DUPLICATE_FRAME, "duplicate frame"); + return; + } + + auto PFRAME = PROTO::imageCopyCapture->m_frames.emplace_back( + makeShared(makeShared(pMgr->client(), pMgr->version(), id), m_self)); + + m_frame = PFRAME; + }); + + if (m_source->m_monitor) + m_session = Screenshare::mgr()->newSession(m_resource->client(), m_source->m_monitor.lock()); + else + m_session = Screenshare::mgr()->newSession(m_resource->client(), m_source->m_window.lock()); + + if UNLIKELY (!m_session) { + m_resource->sendStopped(); + m_resource->error(-1, "unable to share screen"); + return; + } + + sendConstraints(); + + m_listeners.constraintsChanged = m_session->m_events.constraintsChanged.listen([this]() { sendConstraints(); }); + m_listeners.stopped = m_session->m_events.stopped.listen([this]() { PROTO::imageCopyCapture->destroyResource(this); }); +} + +CImageCopyCaptureSession::~CImageCopyCaptureSession() { + if (m_session) + m_session->stop(); + if (m_resource->resource()) + m_resource->sendStopped(); +} + +bool CImageCopyCaptureSession::good() { + return m_resource && m_resource->resource(); +} + +void CImageCopyCaptureSession::sendConstraints() { + auto formats = m_session->allowedFormats(); + + if UNLIKELY (formats.empty()) { + m_session->stop(); + m_resource->error(-1, "no formats available"); + return; + } + + for (DRMFormat format : formats) { + m_resource->sendShmFormat(NFormatUtils::drmToShm(format)); + + auto modifiers = g_pHyprOpenGL->getDRMFormatModifiers(format); + + wl_array modsArr; + wl_array_init(&modsArr); + if (!modifiers.empty()) { + wl_array_add(&modsArr, modifiers.size() * sizeof(uint64_t)); + memcpy(modsArr.data, modifiers.data(), modifiers.size() * sizeof(uint64_t)); + } + m_resource->sendDmabufFormat(format, &modsArr); + wl_array_release(&modsArr); + } + + dev_t device = PROTO::linuxDma->getMainDevice(); + struct wl_array deviceArr = { + .size = sizeof(device), + .data = sc(&device), + }; + m_resource->sendDmabufDevice(&deviceArr); + + m_bufferSize = m_session->bufferSize(); + m_resource->sendBufferSize(m_bufferSize.x, m_bufferSize.y); + + m_resource->sendDone(); +} + +CImageCopyCaptureCursorSession::CImageCopyCaptureCursorSession(SP resource, SP source, SP pointer) : + m_resource(resource), m_source(source), m_pointer(pointer) { + if UNLIKELY (!good()) + return; + + if (!m_source || (!m_source->m_monitor && !m_source->m_window)) + return; + + const auto PMONITOR = m_source->m_monitor.expired() ? m_source->m_window->m_monitor.lock() : m_source->m_monitor.lock(); + + // TODO: add listeners for source being destroyed + + sendCursorEvents(); + m_listeners.commit = PMONITOR->m_events.commit.listen([this, PMONITOR]() { sendCursorEvents(); }); + + m_resource->setDestroy([this](CExtImageCopyCaptureCursorSessionV1* pMgr) { PROTO::imageCopyCapture->destroyResource(this); }); + m_resource->setOnDestroy([this](CExtImageCopyCaptureCursorSessionV1* pMgr) { PROTO::imageCopyCapture->destroyResource(this); }); + + m_resource->setGetCaptureSession([this](CExtImageCopyCaptureCursorSessionV1* pMgr, uint32_t id) { + if (m_session || m_sessionResource) { + LOGM(Log::ERR, "Duplicate cursor copy capture session for source: \"{}\"", m_source->getName()); + m_resource->error(EXT_IMAGE_COPY_CAPTURE_CURSOR_SESSION_V1_ERROR_DUPLICATE_SESSION, "duplicate session"); + return; + } + + m_sessionResource = makeShared(pMgr->client(), pMgr->version(), id); + + m_sessionResource->setDestroy([this](CExtImageCopyCaptureSessionV1* pMgr) { destroyCaptureSession(); }); + m_sessionResource->setOnDestroy([this](CExtImageCopyCaptureSessionV1* pMgr) { destroyCaptureSession(); }); + + m_sessionResource->setCreateFrame([this](CExtImageCopyCaptureSessionV1* pMgr, uint32_t id) { + if UNLIKELY (!m_session || !m_sessionResource) + return; + + if (m_frameResource) { + LOGM(Log::ERR, "Duplicate frame in session for source: \"{}\"", m_source->getName()); + m_resource->error(EXT_IMAGE_COPY_CAPTURE_SESSION_V1_ERROR_DUPLICATE_FRAME, "duplicate frame"); + return; + } + + createFrame(makeShared(pMgr->client(), pMgr->version(), id)); + }); + + m_session = Screenshare::mgr()->newCursorSession(pMgr->client(), m_pointer); + if UNLIKELY (!m_session) { + m_sessionResource->sendStopped(); + m_sessionResource->error(-1, "unable to share cursor"); + return; + } + + sendConstraints(); + + m_listeners.constraintsChanged = m_session->m_events.constraintsChanged.listen([this]() { sendConstraints(); }); + m_listeners.stopped = m_session->m_events.stopped.listen([this]() { destroyCaptureSession(); }); + }); +} + +CImageCopyCaptureCursorSession::~CImageCopyCaptureCursorSession() { + destroyCaptureSession(); +} + +bool CImageCopyCaptureCursorSession::good() { + return m_resource && m_resource->resource(); +} + +void CImageCopyCaptureCursorSession::destroyCaptureSession() { + m_listeners.constraintsChanged.reset(); + m_listeners.stopped.reset(); + + if (m_frameResource && m_frameResource->resource()) + m_frameResource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_STOPPED); + m_frameResource.reset(); + + m_sessionResource.reset(); + m_session.reset(); +} + +void CImageCopyCaptureCursorSession::createFrame(SP resource) { + m_frameResource = resource; + m_captured = false; + m_buffer.reset(); + + m_frameResource->setDestroy([this](CExtImageCopyCaptureFrameV1* pMgr) { m_frameResource.reset(); }); + m_frameResource->setOnDestroy([this](CExtImageCopyCaptureFrameV1* pMgr) { m_frameResource.reset(); }); + + m_frameResource->setAttachBuffer([this](CExtImageCopyCaptureFrameV1* pMgr, wl_resource* buf) { + if UNLIKELY (!m_frameResource || !m_frameResource->resource()) + return; + + if (m_captured) { + LOGM(Log::ERR, "Frame already captured in attach_buffer, {:x}", (uintptr_t)this); + m_frameResource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, "already captured"); + m_frameResource.reset(); + return; + } + + auto PBUFFERRES = CWLBufferResource::fromResource(buf); + if (!PBUFFERRES || !PBUFFERRES->m_buffer) { + LOGM(Log::ERR, "Invalid buffer in attach_buffer {:x}", (uintptr_t)this); + m_frameResource->error(-1, "invalid buffer"); + m_frameResource.reset(); + return; + } + + m_buffer = PBUFFERRES->m_buffer.lock(); + }); + + m_frameResource->setDamageBuffer([this](CExtImageCopyCaptureFrameV1* pMgr, int32_t x, int32_t y, int32_t w, int32_t h) { + if UNLIKELY (!m_frameResource || !m_frameResource->resource()) + return; + + if (m_captured) { + LOGM(Log::ERR, "Frame already captured in damage_buffer, {:x}", (uintptr_t)this); + m_frameResource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, "already captured"); + m_frameResource.reset(); + return; + } + + if (x < 0 || y < 0 || w <= 0 || h <= 0) { + m_frameResource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_INVALID_BUFFER_DAMAGE, "invalid buffer damage"); + m_frameResource.reset(); + return; + } + + // we don't really need to keep track of damage for cursor frames because we will just copy the whole thing + }); + + m_frameResource->setCapture([this](CExtImageCopyCaptureFrameV1* pMgr) { + if UNLIKELY (!m_frameResource || !m_frameResource->resource()) + return; + + if (m_captured) { + LOGM(Log::ERR, "Frame already captured in capture, {:x}", (uintptr_t)this); + m_frameResource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, "already captured"); + m_frameResource.reset(); + return; + } + + const auto PMONITOR = m_source->m_monitor.expired() ? m_source->m_window->m_monitor.lock() : m_source->m_monitor.lock(); + + auto sourceBoxCallback = [this]() { return m_source ? m_source->logicalBox() : CBox(); }; + auto error = m_session->share(PMONITOR, m_buffer, sourceBoxCallback, [this](eScreenshareResult result) { + switch (result) { + case RESULT_COPIED: m_frameResource->sendReady(); break; + case RESULT_NOT_COPIED: m_frameResource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN); break; + case RESULT_TIMESTAMP: + auto [sec, nsec] = Time::secNsec(Time::steadyNow()); + uint32_t tvSecHi = (sizeof(sec) > 4) ? sec >> 32 : 0; + uint32_t tvSecLo = sec & 0xFFFFFFFF; + m_frameResource->sendPresentationTime(tvSecHi, tvSecLo, nsec); + break; + } + }); + + if (!m_frameResource) + return; + + switch (error) { + case ERROR_NONE: m_captured = true; break; + case ERROR_NO_BUFFER: + m_frameResource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_NO_BUFFER, "no buffer attached"); + m_frameResource.reset(); + break; + case ERROR_BUFFER_SIZE: + case ERROR_BUFFER_FORMAT: m_frameResource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_BUFFER_CONSTRAINTS); break; + case ERROR_STOPPED: m_frameResource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_STOPPED); break; + case ERROR_UNKNOWN: m_frameResource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN); break; + } + }); + + // we should always copy over the entire cursor image, it doesn't cost much + m_frameResource->sendDamage(0, 0, m_bufferSize.x, m_bufferSize.y); + + // the cursor is never transformed... probably? + m_frameResource->sendTransform(WL_OUTPUT_TRANSFORM_NORMAL); +} + +void CImageCopyCaptureCursorSession::sendConstraints() { + if UNLIKELY (!m_session || !m_sessionResource) + return; + + auto format = m_session->format(); + if UNLIKELY (format == DRM_FORMAT_INVALID) { + m_session->stop(); + m_sessionResource->error(-1, "no formats available"); + return; + } + + m_sessionResource->sendShmFormat(NFormatUtils::drmToShm(format)); + + auto modifiers = g_pHyprOpenGL->getDRMFormatModifiers(format); + + wl_array modsArr; + wl_array_init(&modsArr); + if (!modifiers.empty()) { + wl_array_add(&modsArr, modifiers.size() * sizeof(uint64_t)); + memcpy(modsArr.data, modifiers.data(), modifiers.size() * sizeof(uint64_t)); + } + m_sessionResource->sendDmabufFormat(format, &modsArr); + wl_array_release(&modsArr); + + dev_t device = PROTO::linuxDma->getMainDevice(); + struct wl_array deviceArr = { + .size = sizeof(device), + .data = sc(&device), + }; + m_sessionResource->sendDmabufDevice(&deviceArr); + + m_bufferSize = m_session->bufferSize(); + m_sessionResource->sendBufferSize(m_bufferSize.x, m_bufferSize.y); + + m_sessionResource->sendDone(); +} + +void CImageCopyCaptureCursorSession::sendCursorEvents() { + const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(m_resource->client(), PERMISSION_TYPE_CURSOR_POS); + if (PERM != PERMISSION_RULE_ALLOW_MODE_ALLOW) { + if (PERM == PERMISSION_RULE_ALLOW_MODE_DENY) { + m_resource->error(-1, "client not allowed to capture cursor"); + PROTO::imageCopyCapture->destroyResource(this); + } + return; + } + + const auto PMONITOR = m_source->m_monitor.expired() ? m_source->m_window->m_monitor.lock() : m_source->m_monitor.lock(); + CBox sourceBox = m_source->logicalBox(); + bool overlaps = g_pPointerManager->getCursorBoxGlobal().overlaps(sourceBox); + + if (m_entered && !overlaps) { + m_entered = false; + m_resource->sendLeave(); + return; + } else if (!m_entered && overlaps) { + m_entered = true; + m_resource->sendEnter(); + } + + if (!overlaps) + return; + + Vector2D pos = g_pPointerManager->position() - sourceBox.pos(); + if (pos != m_pos) { + m_pos = pos; + m_resource->sendPosition(m_pos.x, m_pos.y); + } + + Vector2D hotspot = g_pPointerManager->hotspot(); + if (hotspot != m_hotspot) { + m_hotspot = hotspot; + m_resource->sendHotspot(m_hotspot.x, m_hotspot.y); + } +} + +CImageCopyCaptureFrame::CImageCopyCaptureFrame(SP resource, WP session) : m_resource(resource), m_session(session) { + if UNLIKELY (!good()) + return; + + if (m_session->m_bufferSize != m_session->m_session->bufferSize()) { + m_session->sendConstraints(); + m_resource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN); + return; + } + + m_frame = m_session->m_session->nextFrame(m_session->m_paintCursor); + + m_resource->setDestroy([this](CExtImageCopyCaptureFrameV1* pMgr) { PROTO::imageCopyCapture->destroyResource(this); }); + m_resource->setOnDestroy([this](CExtImageCopyCaptureFrameV1* pMgr) { PROTO::imageCopyCapture->destroyResource(this); }); + + m_resource->setAttachBuffer([this](CExtImageCopyCaptureFrameV1* pMgr, wl_resource* buf) { + if (m_captured) { + LOGM(Log::ERR, "Frame already captured in attach_buffer, {:x}", (uintptr_t)this); + m_resource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, "already captured"); + return; + } + + auto PBUFFERRES = CWLBufferResource::fromResource(buf); + if (!PBUFFERRES || !PBUFFERRES->m_buffer) { + LOGM(Log::ERR, "Invalid buffer in attach_buffer {:x}", (uintptr_t)this); + m_resource->error(-1, "invalid buffer"); + return; + } + + m_buffer = PBUFFERRES->m_buffer.lock(); + }); + + m_resource->setDamageBuffer([this](CExtImageCopyCaptureFrameV1* pMgr, int32_t x, int32_t y, int32_t w, int32_t h) { + if (m_captured) { + LOGM(Log::ERR, "Frame already captured in damage_buffer, {:x}", (uintptr_t)this); + m_resource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, "already captured"); + return; + } + + if (x < 0 || y < 0 || w <= 0 || h <= 0) { + m_resource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_INVALID_BUFFER_DAMAGE, "invalid buffer damage"); + return; + } + + m_clientDamage.add(x, y, w, h); + }); + + m_resource->setCapture([this](CExtImageCopyCaptureFrameV1* pMgr) { + if (m_captured) { + LOGM(Log::ERR, "Frame already captured in capture, {:x}", (uintptr_t)this); + m_resource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, "already captured"); + return; + } + + auto error = m_frame->share(m_buffer, m_clientDamage, [this](eScreenshareResult result) { + switch (result) { + case RESULT_COPIED: m_resource->sendReady(); break; + case RESULT_NOT_COPIED: m_resource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN); break; + case RESULT_TIMESTAMP: + auto [sec, nsec] = Time::secNsec(Time::steadyNow()); + uint32_t tvSecHi = (sizeof(sec) > 4) ? sec >> 32 : 0; + uint32_t tvSecLo = sec & 0xFFFFFFFF; + m_resource->sendPresentationTime(tvSecHi, tvSecLo, nsec); + break; + } + }); + + switch (error) { + case ERROR_NONE: m_captured = true; break; + case ERROR_NO_BUFFER: m_resource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_NO_BUFFER, "no buffer attached"); break; + case ERROR_BUFFER_SIZE: + case ERROR_BUFFER_FORMAT: m_resource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_BUFFER_CONSTRAINTS); break; + case ERROR_STOPPED: m_resource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_STOPPED); break; + case ERROR_UNKNOWN: m_resource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN); break; + } + }); + + m_clientDamage.clear(); + + // TODO: see ScreenshareFrame::share() for "add a damage ring for output damage since last shared frame" + m_resource->sendDamage(0, 0, m_session->m_bufferSize.x, m_session->m_bufferSize.y); + + m_resource->sendTransform(m_frame->transform()); +} + +CImageCopyCaptureFrame::~CImageCopyCaptureFrame() { + if (m_session) + m_session->m_frame.reset(); +} + +bool CImageCopyCaptureFrame::good() { + return m_resource && m_resource->resource(); +} + +CImageCopyCaptureProtocol::CImageCopyCaptureProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { + ; +} + +void CImageCopyCaptureProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { + const auto RESOURCE = m_managers.emplace_back(makeShared(client, ver, id)); + + if UNLIKELY (!RESOURCE->resource()) { + wl_client_post_no_memory(client); + m_managers.pop_back(); + return; + } + + RESOURCE->setDestroy([this](CExtImageCopyCaptureManagerV1* pMgr) { destroyResource(pMgr); }); + RESOURCE->setOnDestroy([this](CExtImageCopyCaptureManagerV1* pMgr) { destroyResource(pMgr); }); + + RESOURCE->setCreateSession([this](CExtImageCopyCaptureManagerV1* pMgr, uint32_t id, wl_resource* source_, extImageCopyCaptureManagerV1Options options) { + auto source = PROTO::imageCaptureSource->sourceFromResource(source_); + if (!source) { + LOGM(Log::ERR, "Client tried to create image copy capture session from invalid source"); + pMgr->error(-1, "invalid image capture source"); + return; + } + + if (options > 1) { + LOGM(Log::ERR, "Client tried to create image copy capture session with invalid options"); + pMgr->error(EXT_IMAGE_COPY_CAPTURE_MANAGER_V1_ERROR_INVALID_OPTION, "Options can't be above 1"); + return; + } + + auto& PSESSION = + m_sessions.emplace_back(makeShared(makeShared(pMgr->client(), pMgr->version(), id), source, options)); + PSESSION->m_self = PSESSION; + LOGM(Log::INFO, "New image copy capture session for source ({}): \"{}\"", source->getTypeName(), source->getName()); + }); + + RESOURCE->setCreatePointerCursorSession([this](CExtImageCopyCaptureManagerV1* pMgr, uint32_t id, wl_resource* source_, wl_resource* pointer_) { + SP source = PROTO::imageCaptureSource->sourceFromResource(source_); + if (!source) { + LOGM(Log::ERR, "Client tried to create image copy capture session from invalid source"); + destroyResource(pMgr); + return; + } + + const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(pMgr->client(), PERMISSION_TYPE_CURSOR_POS); + if (PERM == PERMISSION_RULE_ALLOW_MODE_DENY) + return; + + m_cursorSessions.emplace_back(makeShared(makeShared(pMgr->client(), pMgr->version(), id), source, + CWLPointerResource::fromResource(pointer_))); + + LOGM(Log::INFO, "New image copy capture cursor session for source ({}): \"{}\"", source->getTypeName(), source->getName()); + }); +} + +void CImageCopyCaptureProtocol::destroyResource(CExtImageCopyCaptureManagerV1* resource) { + std::erase_if(m_managers, [&](const auto& other) { return other.get() == resource; }); +} + +void CImageCopyCaptureProtocol::destroyResource(CImageCopyCaptureSession* resource) { + std::erase_if(m_sessions, [&](const auto& other) { return other.get() == resource; }); +} + +void CImageCopyCaptureProtocol::destroyResource(CImageCopyCaptureCursorSession* resource) { + std::erase_if(m_cursorSessions, [&](const auto& other) { return other.get() == resource; }); +} + +void CImageCopyCaptureProtocol::destroyResource(CImageCopyCaptureFrame* resource) { + std::erase_if(m_frames, [&](const auto& other) { return other.get() == resource; }); +} diff --git a/src/protocols/ImageCopyCapture.hpp b/src/protocols/ImageCopyCapture.hpp new file mode 100644 index 00000000..b8cfa1e8 --- /dev/null +++ b/src/protocols/ImageCopyCapture.hpp @@ -0,0 +1,133 @@ +#pragma once + +#include + +#include "../defines.hpp" +#include "../helpers/signal/Signal.hpp" +#include "../helpers/Format.hpp" +#include "WaylandProtocol.hpp" +#include "ImageCaptureSource.hpp" +#include "ext-image-copy-capture-v1.hpp" + +class IHLBuffer; +class CWLPointerResource; +namespace Screenshare { + class CCursorshareSession; + class CScreenshareSession; + class CScreenshareFrame; +}; + +class CImageCopyCaptureFrame { + public: + CImageCopyCaptureFrame(SP resource, WP session); + ~CImageCopyCaptureFrame(); + + bool good(); + + private: + SP m_resource; + WP m_session; + UP m_frame; + + bool m_captured = false; + SP m_buffer; + CRegion m_clientDamage; + + friend class CImageCopyCaptureSession; +}; + +class CImageCopyCaptureSession { + public: + CImageCopyCaptureSession(SP resource, SP source, extImageCopyCaptureManagerV1Options options); + ~CImageCopyCaptureSession(); + + bool good(); + + private: + SP m_resource; + + SP m_source; + UP m_session; + WP m_frame; + + Vector2D m_bufferSize = Vector2D(0, 0); + bool m_paintCursor = true; + + struct { + CHyprSignalListener constraintsChanged; + CHyprSignalListener stopped; + } m_listeners; + + WP m_self; + + // + void sendConstraints(); + + friend class CImageCopyCaptureProtocol; + friend class CImageCopyCaptureFrame; +}; + +class CImageCopyCaptureCursorSession { + public: + CImageCopyCaptureCursorSession(SP resource, SP source, SP pointer); + ~CImageCopyCaptureCursorSession(); + + bool good(); + + private: + SP m_resource; + SP m_source; + SP m_pointer; + + // cursor session stuff + bool m_entered = false; + Vector2D m_pos = Vector2D(0, 0); + Vector2D m_hotspot = Vector2D(0, 0); + + // capture session stuff + SP m_sessionResource; + UP m_session; + Vector2D m_bufferSize = Vector2D(0, 0); + + // frame stuff + SP m_frameResource; + bool m_captured = false; + SP m_buffer; + + struct { + CHyprSignalListener constraintsChanged; + CHyprSignalListener stopped; + CHyprSignalListener commit; + } m_listeners; + + void sendCursorEvents(); + + void createFrame(SP resource); + void destroyCaptureSession(); + void sendConstraints(); +}; + +class CImageCopyCaptureProtocol : public IWaylandProtocol { + public: + CImageCopyCaptureProtocol(const wl_interface* iface, const int& ver, const std::string& name); + + virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); + + void destroyResource(CExtImageCopyCaptureManagerV1* resource); + void destroyResource(CImageCopyCaptureSession* resource); + void destroyResource(CImageCopyCaptureCursorSession* resource); + void destroyResource(CImageCopyCaptureFrame* resource); + + private: + std::vector> m_managers; + std::vector> m_sessions; + std::vector> m_cursorSessions; + + std::vector> m_frames; + + friend class CImageCopyCaptureSession; +}; + +namespace PROTO { + inline UP imageCopyCapture; +}; diff --git a/src/protocols/LinuxDMABUF.cpp b/src/protocols/LinuxDMABUF.cpp index 296a27ed..e49e2b6e 100644 --- a/src/protocols/LinuxDMABUF.cpp +++ b/src/protocols/LinuxDMABUF.cpp @@ -643,3 +643,7 @@ void CLinuxDMABufV1Protocol::updateScanoutTranche(SP surface feedbackResource->m_lastFeedbackWasScanout = true; } + +dev_t CLinuxDMABufV1Protocol::getMainDevice() { + return m_mainDevice; +} diff --git a/src/protocols/LinuxDMABUF.hpp b/src/protocols/LinuxDMABUF.hpp index 296ef04d..b1d59155 100644 --- a/src/protocols/LinuxDMABUF.hpp +++ b/src/protocols/LinuxDMABUF.hpp @@ -113,6 +113,7 @@ class CLinuxDMABufV1Protocol : public IWaylandProtocol { virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); void updateScanoutTranche(SP surface, PHLMONITOR pMonitor); + dev_t getMainDevice(); private: void destroyResource(CLinuxDMABUFResource* resource); diff --git a/src/protocols/Screencopy.cpp b/src/protocols/Screencopy.cpp index ac7146b4..74b3b608 100644 --- a/src/protocols/Screencopy.cpp +++ b/src/protocols/Screencopy.cpp @@ -1,507 +1,164 @@ #include "Screencopy.hpp" -#include "../Compositor.hpp" -#include "../managers/eventLoop/EventLoopManager.hpp" -#include "../managers/PointerManager.hpp" -#include "../managers/input/InputManager.hpp" -#include "../managers/EventManager.hpp" -#include "../managers/permissions/DynamicPermissionManager.hpp" -#include "../render/Renderer.hpp" -#include "../render/OpenGL.hpp" -#include "../helpers/Monitor.hpp" +#include "../managers/screenshare/ScreenshareManager.hpp" #include "core/Output.hpp" -#include "types/WLBuffer.hpp" +#include "../render/Renderer.hpp" #include "types/Buffer.hpp" -#include "ColorManagement.hpp" #include "../helpers/Format.hpp" #include "../helpers/time/Time.hpp" -#include "XDGShell.hpp" -#include -#include +using namespace Screenshare; -CScreencopyFrame::CScreencopyFrame(SP resource_, int32_t overlay_cursor, wl_resource* output, CBox box_) : m_resource(resource_) { +CScreencopyClient::CScreencopyClient(SP resource_) : m_resource(resource_) { if UNLIKELY (!good()) return; - m_overlayCursor = !!overlay_cursor; - m_monitor = CWLOutputResource::fromResource(output)->m_monitor; - - if (!m_monitor) { - LOGM(Log::ERR, "Client requested sharing of a monitor that doesn't exist"); - m_resource->sendFailed(); - return; - } - - m_resource->setOnDestroy([this](CZwlrScreencopyFrameV1* pMgr) { PROTO::screencopy->destroyResource(this); }); - m_resource->setDestroy([this](CZwlrScreencopyFrameV1* pFrame) { PROTO::screencopy->destroyResource(this); }); - m_resource->setCopy([this](CZwlrScreencopyFrameV1* pFrame, wl_resource* res) { this->copy(pFrame, res); }); - m_resource->setCopyWithDamage([this](CZwlrScreencopyFrameV1* pFrame, wl_resource* res) { - m_withDamage = true; - this->copy(pFrame, res); + m_resource->setDestroy([this](CZwlrScreencopyManagerV1* pMgr) { PROTO::screencopy->destroyResource(this); }); + m_resource->setOnDestroy([this](CZwlrScreencopyManagerV1* pMgr) { + Screenshare::mgr()->destroyClientSessions(m_savedClient); + PROTO::screencopy->destroyResource(this); }); + m_resource->setCaptureOutput( + [this](CZwlrScreencopyManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, wl_resource* output) { captureOutput(frame, overlayCursor, output, {}); }); + m_resource->setCaptureOutputRegion([this](CZwlrScreencopyManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, wl_resource* output, int32_t x, int32_t y, int32_t w, + int32_t h) { captureOutput(frame, overlayCursor, output, {x, y, w, h}); }); - g_pHyprRenderer->makeEGLCurrent(); - - m_shmFormat = g_pHyprOpenGL->getPreferredReadFormat(m_monitor.lock()); - if (m_shmFormat == DRM_FORMAT_INVALID) { - LOGM(Log::ERR, "No format supported by renderer in capture output"); - m_resource->sendFailed(); - return; - } - - // TODO: hack, we can't bit flip so we'll format flip heh, GL_BGRA_EXT won't work here - if (m_shmFormat == DRM_FORMAT_XRGB2101010 || m_shmFormat == DRM_FORMAT_ARGB2101010) - m_shmFormat = DRM_FORMAT_XBGR2101010; - - const auto PSHMINFO = NFormatUtils::getPixelFormatFromDRM(m_shmFormat); - if (!PSHMINFO) { - LOGM(Log::ERR, "No pixel format supported by renderer in capture output"); - m_resource->sendFailed(); - return; - } - - m_dmabufFormat = g_pHyprOpenGL->getPreferredReadFormat(m_monitor.lock()); - - if (box_.width == 0 && box_.height == 0) - m_box = {0, 0, sc(m_monitor->m_size.x), sc(m_monitor->m_size.y)}; - else - m_box = box_; - - const auto POS = m_box.pos() * m_monitor->m_scale; - m_box.transform(Math::wlTransformToHyprutils(m_monitor->m_transform), m_monitor->m_pixelSize.x, m_monitor->m_pixelSize.y).scale(m_monitor->m_scale).round(); - m_box.x = POS.x; - m_box.y = POS.y; - - m_shmStride = NFormatUtils::minStride(PSHMINFO, m_box.w); - - m_resource->sendBuffer(NFormatUtils::drmToShm(m_shmFormat), m_box.width, m_box.height, m_shmStride); - - if (m_resource->version() >= 3) { - if LIKELY (m_dmabufFormat != DRM_FORMAT_INVALID) - m_resource->sendLinuxDmabuf(m_dmabufFormat, m_box.width, m_box.height); - - m_resource->sendBufferDone(); - } + m_savedClient = m_resource->client(); } -void CScreencopyFrame::copy(CZwlrScreencopyFrameV1* pFrame, wl_resource* buffer_) { - if UNLIKELY (!good()) { - LOGM(Log::ERR, "No frame in copyFrame??"); - return; - } - - if UNLIKELY (!g_pCompositor->monitorExists(m_monitor.lock())) { - LOGM(Log::ERR, "Client requested sharing of a monitor that is gone"); - m_resource->sendFailed(); - return; - } +CScreencopyClient::~CScreencopyClient() { + Screenshare::mgr()->destroyClientSessions(m_savedClient); +} - const auto PBUFFER = CWLBufferResource::fromResource(buffer_); - if UNLIKELY (!PBUFFER) { - LOGM(Log::ERR, "Invalid buffer in {:x}", (uintptr_t)this); - m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer"); - PROTO::screencopy->destroyResource(this); +void CScreencopyClient::captureOutput(uint32_t frame, int32_t overlayCursor_, wl_resource* output, CBox box) { + const auto PMONITORRES = CWLOutputResource::fromResource(output); + if (!PMONITORRES || !PMONITORRES->m_monitor) { + LOGM(Log::ERR, "Tried to capture invalid output/monitor in {:x}", (uintptr_t)this); + m_resource->error(-1, "invalid output"); return; } - if UNLIKELY (PBUFFER->m_buffer->size != m_box.size()) { - LOGM(Log::ERR, "Invalid dimensions in {:x}", (uintptr_t)this); - m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer dimensions"); - PROTO::screencopy->destroyResource(this); - return; - } + const auto PMONITOR = PMONITORRES->m_monitor.lock(); + auto session = box.w == 0 && box.h == 0 ? Screenshare::mgr()->getManagedSession(m_resource->client(), PMONITOR) : + Screenshare::mgr()->getManagedSession(m_resource->client(), PMONITOR, box); - if UNLIKELY (m_buffer) { - LOGM(Log::ERR, "Buffer used in {:x}", (uintptr_t)this); - m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_ALREADY_USED, "frame already used"); - PROTO::screencopy->destroyResource(this); - return; - } - - if (auto attrs = PBUFFER->m_buffer->dmabuf(); attrs.success) { - m_bufferDMA = true; + const auto FRAME = PROTO::screencopy->m_frames.emplace_back( + makeShared(makeShared(m_resource->client(), m_resource->version(), frame), session, !!overlayCursor_)); - if (attrs.format != m_dmabufFormat) { - LOGM(Log::ERR, "Invalid buffer dma format in {:x}", (uintptr_t)pFrame); - m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer format"); - PROTO::screencopy->destroyResource(this); - return; - } - } else if (auto attrs = PBUFFER->m_buffer->shm(); attrs.success) { - if (attrs.format != m_shmFormat) { - LOGM(Log::ERR, "Invalid buffer shm format in {:x}", (uintptr_t)pFrame); - m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer format"); - PROTO::screencopy->destroyResource(this); - return; - } else if (attrs.stride != m_shmStride) { - LOGM(Log::ERR, "Invalid buffer shm stride in {:x}", (uintptr_t)pFrame); - m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer stride"); - PROTO::screencopy->destroyResource(this); - return; - } - } else { - LOGM(Log::ERR, "Invalid buffer type in {:x}", (uintptr_t)pFrame); - m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer type"); - PROTO::screencopy->destroyResource(this); + if (!FRAME->good()) { + LOGM(Log::ERR, "Couldn't alloc frame for sharing! (no memory)"); + m_resource->noMemory(); + PROTO::screencopy->destroyResource(FRAME.get()); return; } - m_buffer = CHLBufferReference(PBUFFER->m_buffer.lock()); - - PROTO::screencopy->m_framesAwaitingWrite.emplace_back(m_self); - - g_pHyprRenderer->m_directScanoutBlocked = true; + FRAME->m_client = m_self; + FRAME->m_self = FRAME; +} - if (!m_withDamage) - g_pHyprRenderer->damageMonitor(m_monitor.lock()); +bool CScreencopyClient::good() { + return m_resource && m_resource->resource(); } -void CScreencopyFrame::share() { - if (!m_buffer || !m_monitor) +CScreencopyFrame::CScreencopyFrame(SP resource_, WP session, bool overlayCursor) : + m_resource(resource_), m_session(session), m_overlayCursor(overlayCursor) { + if UNLIKELY (!good()) return; - const auto NOW = Time::steadyNow(); - - auto callback = [this, NOW, weak = m_self](bool success) { - if (weak.expired()) - return; + m_resource->setOnDestroy([this](CZwlrScreencopyFrameV1* pMgr) { PROTO::screencopy->destroyResource(this); }); + m_resource->setDestroy([this](CZwlrScreencopyFrameV1* pFrame) { PROTO::screencopy->destroyResource(this); }); + m_resource->setCopy([this](CZwlrScreencopyFrameV1* pFrame, wl_resource* res) { shareFrame(pFrame, res, false); }); + m_resource->setCopyWithDamage([this](CZwlrScreencopyFrameV1* pFrame, wl_resource* res) { shareFrame(pFrame, res, true); }); - if (!success) { - LOGM(Log::ERR, "{} copy failed in {:x}", m_bufferDMA ? "Dmabuf" : "Shm", (uintptr_t)this); + m_listeners.stopped = m_session->m_events.stopped.listen([this]() { + if (good()) m_resource->sendFailed(); - return; - } - - m_resource->sendFlags(sc(0)); - if (m_withDamage) { - // TODO: add a damage ring for this. - m_resource->sendDamage(0, 0, m_buffer->size.x, m_buffer->size.y); - } - - const auto [sec, nsec] = Time::secNsec(NOW); - - uint32_t tvSecHi = (sizeof(sec) > 4) ? sec >> 32 : 0; - uint32_t tvSecLo = sec & 0xFFFFFFFF; - m_resource->sendReady(tvSecHi, tvSecLo, nsec); - }; + }); - if (m_bufferDMA) - copyDmabuf(callback); - else - callback(copyShm()); -} + m_frame = m_session->nextFrame(overlayCursor); -void CScreencopyFrame::renderMon() { - auto TEXTURE = makeShared(m_monitor->m_output->state->state().buffer); - CRegion fakeDamage = {0, 0, INT16_MAX, INT16_MAX}; - - const bool IS_CM_AWARE = PROTO::colorManagement && PROTO::colorManagement->isClientCMAware(m_client->client()); - - CBox monbox = CBox{0, 0, m_monitor->m_pixelSize.x, m_monitor->m_pixelSize.y} - .translate({-m_box.x, -m_box.y}) // vvvv kinda ass-backwards but that's how I designed the renderer... sigh. - .transform(Math::wlTransformToHyprutils(Math::invertTransform(m_monitor->m_transform)), m_monitor->m_pixelSize.x, m_monitor->m_pixelSize.y); - g_pHyprOpenGL->pushMonitorTransformEnabled(true); - g_pHyprOpenGL->setRenderModifEnabled(false); - g_pHyprOpenGL->renderTexture(TEXTURE, monbox, - { - .cmBackToSRGB = !IS_CM_AWARE, - .cmBackToSRGBSource = !IS_CM_AWARE ? m_monitor.lock() : nullptr, - }); - g_pHyprOpenGL->setRenderModifEnabled(true); - g_pHyprOpenGL->popMonitorTransformEnabled(); - - auto hidePopups = [&](Vector2D popupBaseOffset) { - return [&, popupBaseOffset](WP popup, void*) { - if (!popup->wlSurface() || !popup->wlSurface()->resource() || !popup->visible()) - return; - - const auto popRel = popup->coordsRelativeToParent(); - popup->wlSurface()->resource()->breadthfirst( - [&](SP surf, const Vector2D& localOff, void*) { - const auto size = surf->m_current.size; - const auto surfBox = CBox{popupBaseOffset + popRel + localOff, size}.translate(m_monitor->m_position).scale(m_monitor->m_scale).translate(-m_box.pos()); - - if LIKELY (surfBox.w > 0 && surfBox.h > 0) - g_pHyprOpenGL->renderRect(surfBox, Colors::BLACK, {}); - }, - nullptr); - }; - }; - - for (auto const& l : g_pCompositor->m_layers) { - if (!l->m_ruleApplicator->noScreenShare().valueOrDefault()) - continue; - - if UNLIKELY (!l->visible()) - continue; - - const auto REALPOS = l->m_realPosition->value(); - const auto REALSIZE = l->m_realSize->value(); - - const auto noScreenShareBox = - CBox{REALPOS.x, REALPOS.y, std::max(REALSIZE.x, 5.0), std::max(REALSIZE.y, 5.0)}.translate(-m_monitor->m_position).scale(m_monitor->m_scale).translate(-m_box.pos()); - - g_pHyprOpenGL->renderRect(noScreenShareBox, Colors::BLACK, {}); - - const auto geom = l->m_geometry; - const Vector2D popupBaseOffset = REALPOS - Vector2D{geom.pos().x, geom.pos().y}; - if (l->m_popupHead) - l->m_popupHead->breadthfirst(hidePopups(popupBaseOffset), nullptr); + auto formats = m_session->allowedFormats(); + if (formats.empty()) { + LOGM(Log::ERR, "No format supported by renderer in screencopy protocol"); + m_resource->sendFailed(); + return; } - for (auto const& w : g_pCompositor->m_windows) { - if (!w->m_ruleApplicator->noScreenShare().valueOrDefault()) - continue; - - if (!g_pHyprRenderer->shouldRenderWindow(w, m_monitor.lock())) - continue; + DRMFormat format = formats.at(0); + auto bufSize = m_frame->bufferSize(); - if (w->isHidden()) - continue; + const auto PSHMINFO = NFormatUtils::getPixelFormatFromDRM(format); + const auto stride = NFormatUtils::minStride(PSHMINFO, bufSize.x); + m_resource->sendBuffer(NFormatUtils::drmToShm(format), bufSize.x, bufSize.y, stride); - const auto PWORKSPACE = w->m_workspace; - - if UNLIKELY (!PWORKSPACE && !w->m_fadingOut && w->m_alpha->value() != 0.f) - continue; - - const auto renderOffset = PWORKSPACE && !w->m_pinned ? PWORKSPACE->m_renderOffset->value() : Vector2D{}; - const auto REALPOS = w->m_realPosition->value() + renderOffset; - const auto noScreenShareBox = CBox{REALPOS.x, REALPOS.y, std::max(w->m_realSize->value().x, 5.0), std::max(w->m_realSize->value().y, 5.0)} - .translate(-m_monitor->m_position) - .scale(m_monitor->m_scale) - .translate(-m_box.pos()); - - const auto dontRound = w->isEffectiveInternalFSMode(FSMODE_FULLSCREEN); - const auto rounding = dontRound ? 0 : w->rounding() * m_monitor->m_scale; - const auto roundingPower = dontRound ? 2.0f : w->roundingPower(); - - g_pHyprOpenGL->renderRect(noScreenShareBox, Colors::BLACK, {.round = rounding, .roundingPower = roundingPower}); - - if (w->m_isX11 || !w->m_popupHead) - continue; - - const auto geom = w->m_xdgSurface->m_current.geometry; - const Vector2D popupBaseOffset = REALPOS - Vector2D{geom.pos().x, geom.pos().y}; + if (m_resource->version() >= 3) { + if LIKELY (format != DRM_FORMAT_INVALID) + m_resource->sendLinuxDmabuf(format, bufSize.x, bufSize.y); - w->m_popupHead->breadthfirst(hidePopups(popupBaseOffset), nullptr); + m_resource->sendBufferDone(); } - - if (m_overlayCursor) - g_pPointerManager->renderSoftwareCursorsFor(m_monitor.lock(), Time::steadyNow(), fakeDamage, - g_pInputManager->getMouseCoordsInternal() - m_monitor->m_position - m_box.pos() / m_monitor->m_scale, true); } -void CScreencopyFrame::storeTempFB() { - g_pHyprRenderer->makeEGLCurrent(); - - m_tempFb.alloc(m_box.w, m_box.h); - - CRegion fakeDamage = {0, 0, INT16_MAX, INT16_MAX}; - - if (!g_pHyprRenderer->beginRender(m_monitor.lock(), fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, &m_tempFb, true)) { - LOGM(Log::ERR, "Can't copy: failed to begin rendering to temp fb"); +void CScreencopyFrame::shareFrame(CZwlrScreencopyFrameV1* pFrame, wl_resource* buffer, bool withDamage) { + if UNLIKELY (!good()) { + LOGM(Log::ERR, "No frame in shareFrame??"); return; } - renderMon(); - - g_pHyprRenderer->endRender(); -} - -void CScreencopyFrame::copyDmabuf(std::function callback) { - const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(m_resource->client(), PERMISSION_TYPE_SCREENCOPY); - - CRegion fakeDamage = {0, 0, INT16_MAX, INT16_MAX}; - - if (!g_pHyprRenderer->beginRender(m_monitor.lock(), fakeDamage, RENDER_MODE_TO_BUFFER, m_buffer.m_buffer, nullptr, true)) { - LOGM(Log::ERR, "Can't copy: failed to begin rendering to dma frame"); - callback(false); + if UNLIKELY (m_buffer) { + LOGM(Log::ERR, "Buffer used in {:x}", (uintptr_t)this); + m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_ALREADY_USED, "frame already used"); + m_resource->sendFailed(); return; } - if (PERM == PERMISSION_RULE_ALLOW_MODE_ALLOW) { - if (m_tempFb.isAllocated()) { - CBox texbox = {{}, m_box.size()}; - g_pHyprOpenGL->renderTexture(m_tempFb.getTexture(), texbox, {}); - m_tempFb.release(); - } else - renderMon(); - } else if (PERM == PERMISSION_RULE_ALLOW_MODE_PENDING) - g_pHyprOpenGL->clear(Colors::BLACK); - else { - g_pHyprOpenGL->clear(Colors::BLACK); - CBox texbox = CBox{m_monitor->m_transformedSize / 2.F, g_pHyprOpenGL->m_screencopyDeniedTexture->m_size}.translate(-g_pHyprOpenGL->m_screencopyDeniedTexture->m_size / 2.F); - g_pHyprOpenGL->renderTexture(g_pHyprOpenGL->m_screencopyDeniedTexture, texbox, {}); - } - - g_pHyprOpenGL->m_renderData.blockScreenShader = true; - - g_pHyprRenderer->endRender([callback]() { - LOGM(Log::TRACE, "Copied frame via dma"); - callback(true); - }); -} - -bool CScreencopyFrame::copyShm() { - const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(m_resource->client(), PERMISSION_TYPE_SCREENCOPY); - - auto shm = m_buffer->shm(); - auto [pixelData, fmt, bufLen] = m_buffer->beginDataPtr(0); // no need for end, cuz it's shm - - CRegion fakeDamage = {0, 0, INT16_MAX, INT16_MAX}; - - g_pHyprRenderer->makeEGLCurrent(); - - CFramebuffer fb; - fb.alloc(m_box.w, m_box.h, m_monitor->m_output->state->state().drmFormat); - - if (!g_pHyprRenderer->beginRender(m_monitor.lock(), fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, &fb, true)) { - LOGM(Log::ERR, "Can't copy: failed to begin rendering"); - return false; - } - - if (PERM == PERMISSION_RULE_ALLOW_MODE_ALLOW) { - if (m_tempFb.isAllocated()) { - CBox texbox = {{}, m_box.size()}; - g_pHyprOpenGL->renderTexture(m_tempFb.getTexture(), texbox, {}); - m_tempFb.release(); - } else - renderMon(); - } else if (PERM == PERMISSION_RULE_ALLOW_MODE_PENDING) - g_pHyprOpenGL->clear(Colors::BLACK); - else { - g_pHyprOpenGL->clear(Colors::BLACK); - CBox texbox = CBox{m_monitor->m_transformedSize / 2.F, g_pHyprOpenGL->m_screencopyDeniedTexture->m_size}.translate(-g_pHyprOpenGL->m_screencopyDeniedTexture->m_size / 2.F); - g_pHyprOpenGL->renderTexture(g_pHyprOpenGL->m_screencopyDeniedTexture, texbox, {}); - } - - glBindFramebuffer(GL_READ_FRAMEBUFFER, fb.getFBID()); - - const auto PFORMAT = NFormatUtils::getPixelFormatFromDRM(shm.format); - if (!PFORMAT) { - LOGM(Log::ERR, "Can't copy: failed to find a pixel format"); - g_pHyprRenderer->endRender(); - return false; + const auto PBUFFERRES = CWLBufferResource::fromResource(buffer); + if UNLIKELY (!PBUFFERRES || !PBUFFERRES->m_buffer) { + LOGM(Log::ERR, "Invalid buffer in {:x}", (uintptr_t)this); + m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer"); + m_resource->sendFailed(); + return; } - g_pHyprOpenGL->m_renderData.blockScreenShader = true; - g_pHyprRenderer->endRender(); - - g_pHyprRenderer->makeEGLCurrent(); - g_pHyprOpenGL->m_renderData.pMonitor = m_monitor; - fb.bind(); - - glPixelStorei(GL_PACK_ALIGNMENT, 1); + const auto& PBUFFER = PBUFFERRES->m_buffer.lock(); - uint32_t packStride = NFormatUtils::minStride(PFORMAT, m_box.w); - int glFormat = PFORMAT->glFormat; + if (!withDamage) + g_pHyprRenderer->damageMonitor(m_session->monitor()); - if (glFormat == GL_RGBA) - glFormat = GL_BGRA_EXT; - - if (glFormat != GL_BGRA_EXT && glFormat != GL_RGB) { - if (PFORMAT->swizzle.has_value()) { - std::array RGBA = SWIZZLE_RGBA; - std::array BGRA = SWIZZLE_BGRA; - if (PFORMAT->swizzle == RGBA) - glFormat = GL_RGBA; - else if (PFORMAT->swizzle == BGRA) - glFormat = GL_BGRA_EXT; - else { - LOGM(Log::ERR, "Copied frame via shm might be broken or color flipped"); - glFormat = GL_RGBA; + auto error = m_frame->share(PBUFFER, {}, [this, withDamage, self = m_self](eScreenshareResult result) { + if (self.expired() || !good()) + return; + switch (result) { + case RESULT_COPIED: { + m_resource->sendFlags(sc(0)); + if (withDamage) + m_frame->damage().forEachRect([&](const auto& rect) { m_resource->sendDamage(rect.x1, rect.y1, rect.x2 - rect.x1, rect.y2 - rect.y1); }); + + const auto [sec, nsec] = Time::secNsec(m_timestamp); + uint32_t tvSecHi = (sizeof(sec) > 4) ? sec >> 32 : 0; + uint32_t tvSecLo = sec & 0xFFFFFFFF; + m_resource->sendReady(tvSecHi, tvSecLo, nsec); + break; } + case RESULT_NOT_COPIED: + LOGM(Log::ERR, "Frame share failed in {:x}", (uintptr_t)this); + m_resource->sendFailed(); + break; + case RESULT_TIMESTAMP: m_timestamp = Time::steadyNow(); break; } - } + }); - // This could be optimized by using a pixel buffer object to make this async, - // but really clients should just use a dma buffer anyways. - if (packStride == sc(shm.stride)) { - glReadPixels(0, 0, m_box.w, m_box.h, glFormat, PFORMAT->glType, pixelData); - } else { - for (size_t i = 0; i < m_box.h; ++i) { - uint32_t y = i; - glReadPixels(0, y, m_box.w, 1, glFormat, PFORMAT->glType, pixelData + i * shm.stride); - } + switch (error) { + case ERROR_NONE: m_buffer = CHLBufferReference(PBUFFER); break; + case ERROR_NO_BUFFER: m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer"); break; + case ERROR_BUFFER_SIZE: + case ERROR_BUFFER_FORMAT: m_resource->sendFailed(); break; + case ERROR_UNKNOWN: + case ERROR_STOPPED: m_resource->sendFailed(); break; } - - glPixelStorei(GL_PACK_ALIGNMENT, 4); - g_pHyprOpenGL->m_renderData.pMonitor.reset(); - - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - - LOGM(Log::TRACE, "Copied frame via shm"); - - return true; } bool CScreencopyFrame::good() { - return m_resource->resource(); -} - -CScreencopyClient::~CScreencopyClient() { - g_pHookSystem->unhook(m_tickCallback); -} - -CScreencopyClient::CScreencopyClient(SP resource_) : m_resource(resource_) { - if UNLIKELY (!good()) - return; - - m_resource->setDestroy([this](CZwlrScreencopyManagerV1* pMgr) { PROTO::screencopy->destroyResource(this); }); - m_resource->setOnDestroy([this](CZwlrScreencopyManagerV1* pMgr) { PROTO::screencopy->destroyResource(this); }); - m_resource->setCaptureOutput( - [this](CZwlrScreencopyManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, wl_resource* output) { this->captureOutput(frame, overlayCursor, output, {}); }); - m_resource->setCaptureOutputRegion([this](CZwlrScreencopyManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, wl_resource* output, int32_t x, int32_t y, int32_t w, - int32_t h) { this->captureOutput(frame, overlayCursor, output, {x, y, w, h}); }); - - m_lastMeasure.reset(); - m_lastFrame.reset(); - m_tickCallback = g_pHookSystem->hookDynamic("tick", [&](void* self, SCallbackInfo& info, std::any data) { onTick(); }); -} - -void CScreencopyClient::captureOutput(uint32_t frame, int32_t overlayCursor_, wl_resource* output, CBox box) { - const auto FRAME = PROTO::screencopy->m_frames.emplace_back( - makeShared(makeShared(m_resource->client(), m_resource->version(), frame), overlayCursor_, output, box)); - - if (!FRAME->good()) { - LOGM(Log::ERR, "Couldn't alloc frame for sharing! (no memory)"); - m_resource->noMemory(); - PROTO::screencopy->destroyResource(FRAME.get()); - return; - } - - FRAME->m_self = FRAME; - FRAME->m_client = m_self; -} - -void CScreencopyClient::onTick() { - if (m_lastMeasure.getMillis() < 500) - return; - - m_framesInLastHalfSecond = m_frameCounter; - m_frameCounter = 0; - m_lastMeasure.reset(); - - const auto LASTFRAMEDELTA = m_lastFrame.getMillis() / 1000.0; - const bool FRAMEAWAITING = std::ranges::any_of(PROTO::screencopy->m_frames, [&](const auto& frame) { return frame->m_client.get() == this; }); - - if (m_framesInLastHalfSecond > 3 && !m_sentScreencast) { - EMIT_HOOK_EVENT("screencast", (std::vector{1, sc(m_framesInLastHalfSecond), sc(m_clientOwner)})); - g_pEventManager->postEvent(SHyprIPCEvent{"screencast", "1," + std::to_string(m_clientOwner)}); - m_sentScreencast = true; - } else if (m_framesInLastHalfSecond < 4 && m_sentScreencast && LASTFRAMEDELTA > 1.0 && !FRAMEAWAITING) { - EMIT_HOOK_EVENT("screencast", (std::vector{0, sc(m_framesInLastHalfSecond), sc(m_clientOwner)})); - g_pEventManager->postEvent(SHyprIPCEvent{"screencast", "0," + std::to_string(m_clientOwner)}); - m_sentScreencast = false; - } -} - -bool CScreencopyClient::good() { - return m_resource->resource(); -} - -wl_client* CScreencopyClient::client() { - return m_resource ? m_resource->client() : nullptr; + return m_resource && m_resource->resource(); } CScreencopyProtocol::CScreencopyProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { @@ -524,64 +181,10 @@ void CScreencopyProtocol::bindManager(wl_client* client, void* data, uint32_t ve } void CScreencopyProtocol::destroyResource(CScreencopyClient* client) { - std::erase_if(m_clients, [&](const auto& other) { return other.get() == client; }); std::erase_if(m_frames, [&](const auto& other) { return other->m_client.get() == client; }); - std::erase_if(m_framesAwaitingWrite, [&](const auto& other) { return !other || other->m_client.get() == client; }); + std::erase_if(m_clients, [&](const auto& other) { return other.get() == client; }); } void CScreencopyProtocol::destroyResource(CScreencopyFrame* frame) { std::erase_if(m_frames, [&](const auto& other) { return other.get() == frame; }); - std::erase_if(m_framesAwaitingWrite, [&](const auto& other) { return !other || other.get() == frame; }); -} - -void CScreencopyProtocol::onOutputCommit(PHLMONITOR pMonitor) { - if (m_framesAwaitingWrite.empty()) { - for (auto client : m_clients) { - if (client->m_framesInLastHalfSecond > 0) - return; - } - g_pHyprRenderer->m_directScanoutBlocked = false; - return; // nothing to share - } - - std::vector> framesToRemove; - // reserve number of elements to avoid reallocations - framesToRemove.reserve(m_framesAwaitingWrite.size()); - - // share frame if correct output - for (auto const& f : m_framesAwaitingWrite) { - if (!f) - continue; - - // check permissions - const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(f->m_resource->client(), PERMISSION_TYPE_SCREENCOPY); - - if (PERM == PERMISSION_RULE_ALLOW_MODE_PENDING) { - if (!f->m_tempFb.isAllocated()) - f->storeTempFB(); // make a snapshot before the popup - - continue; // pending an answer, don't do anything yet. - } - - // otherwise share. If it's denied, it will be black. - - if (!f->m_monitor || !f->m_buffer) { - framesToRemove.emplace_back(f); - continue; - } - - if (f->m_monitor != pMonitor) - continue; - - f->share(); - - f->m_client->m_lastFrame.reset(); - ++f->m_client->m_frameCounter; - - framesToRemove.emplace_back(f); - } - - for (auto const& f : framesToRemove) { - std::erase(m_framesAwaitingWrite, f); - } } diff --git a/src/protocols/Screencopy.hpp b/src/protocols/Screencopy.hpp index 54b0d28c..3659c753 100644 --- a/src/protocols/Screencopy.hpp +++ b/src/protocols/Screencopy.hpp @@ -1,25 +1,19 @@ #pragma once -#include "../defines.hpp" -#include "./types/Buffer.hpp" -#include "wlr-screencopy-unstable-v1.hpp" #include "WaylandProtocol.hpp" +#include "wlr-screencopy-unstable-v1.hpp" -#include -#include -#include "../managers/HookSystemManager.hpp" -#include "../helpers/time/Timer.hpp" #include "../helpers/time/Time.hpp" -#include "../render/Framebuffer.hpp" -#include "../managers/eventLoop/EventLoopTimer.hpp" +#include "./types/Buffer.hpp" #include +#include + class CMonitor; class IHLBuffer; - -enum eClientOwners { - CLIENT_SCREENCOPY = 0, - CLIENT_TOPLEVEL_EXPORT +namespace Screenshare { + class CScreenshareSession; + class CScreenshareFrame; }; class CScreencopyClient { @@ -27,24 +21,13 @@ class CScreencopyClient { CScreencopyClient(SP resource_); ~CScreencopyClient(); - bool good(); - wl_client* client(); - - WP m_self; - eClientOwners m_clientOwner = CLIENT_SCREENCOPY; - - CTimer m_lastFrame; - int m_frameCounter = 0; + bool good(); private: SP m_resource; + WP m_self; - int m_framesInLastHalfSecond = 0; - CTimer m_lastMeasure; - bool m_sentScreencast = false; - - SP m_tickCallback; - void onTick(); + wl_client* m_savedClient = nullptr; void captureOutput(uint32_t frame, int32_t overlayCursor, wl_resource* output, CBox box); @@ -53,38 +36,30 @@ class CScreencopyClient { class CScreencopyFrame { public: - CScreencopyFrame(SP resource, int32_t overlay_cursor, wl_resource* output, CBox box); - - bool good(); + CScreencopyFrame(SP resource, WP session, bool overlayCursor); - WP m_self; - WP m_client; + bool good(); private: - SP m_resource; + SP m_resource; + WP m_self; + WP m_client; - PHLMONITORREF m_monitor; - bool m_overlayCursor = false; - bool m_withDamage = false; + WP m_session; + UP m_frame; - CHLBufferReference m_buffer; - bool m_bufferDMA = false; - uint32_t m_shmFormat = 0; - uint32_t m_dmabufFormat = 0; - int m_shmStride = 0; - CBox m_box = {}; + CHLBufferReference m_buffer; + Time::steady_tp m_timestamp; + bool m_overlayCursor = true; - // if we have a pending perm, hold the buffer. - CFramebuffer m_tempFb; + struct { + CHyprSignalListener stopped; + } m_listeners; - void copy(CZwlrScreencopyFrameV1* pFrame, wl_resource* buffer); - void copyDmabuf(std::function callback); - bool copyShm(); - void renderMon(); - void storeTempFB(); - void share(); + void shareFrame(CZwlrScreencopyFrameV1* pFrame, wl_resource* buffer, bool withDamage); friend class CScreencopyProtocol; + friend class CScreencopyClient; }; class CScreencopyProtocol : public IWaylandProtocol { @@ -95,21 +70,10 @@ class CScreencopyProtocol : public IWaylandProtocol { void destroyResource(CScreencopyClient* resource); void destroyResource(CScreencopyFrame* resource); - void onOutputCommit(PHLMONITOR pMonitor); - private: std::vector> m_frames; - std::vector> m_framesAwaitingWrite; std::vector> m_clients; - void shareAllFrames(PHLMONITOR pMonitor); - void shareFrame(CScreencopyFrame* frame); - void sendFrameDamage(CScreencopyFrame* frame); - bool copyFrameDmabuf(CScreencopyFrame* frame); - bool copyFrameShm(CScreencopyFrame* frame, const Time::steady_tp& now); - - uint32_t drmFormatForMonitor(PHLMONITOR pMonitor); - friend class CScreencopyFrame; friend class CScreencopyClient; }; diff --git a/src/protocols/ToplevelExport.cpp b/src/protocols/ToplevelExport.cpp index b223f778..3b8973ab 100644 --- a/src/protocols/ToplevelExport.cpp +++ b/src/protocols/ToplevelExport.cpp @@ -1,41 +1,44 @@ #include "ToplevelExport.hpp" #include "../Compositor.hpp" #include "ForeignToplevelWlr.hpp" -#include "../managers/PointerManager.hpp" -#include "../managers/SeatManager.hpp" -#include "types/WLBuffer.hpp" -#include "types/Buffer.hpp" +#include "../managers/screenshare/ScreenshareManager.hpp" +#include "../managers/HookSystemManager.hpp" #include "../helpers/Format.hpp" -#include "../managers/EventManager.hpp" -#include "../managers/input/InputManager.hpp" -#include "../managers/permissions/DynamicPermissionManager.hpp" #include "../render/Renderer.hpp" -#include #include +using namespace Screenshare; + CToplevelExportClient::CToplevelExportClient(SP resource_) : m_resource(resource_) { if UNLIKELY (!good()) return; - m_resource->setOnDestroy([this](CHyprlandToplevelExportManagerV1* pMgr) { PROTO::toplevelExport->destroyResource(this); }); + m_resource->setOnDestroy([this](CHyprlandToplevelExportManagerV1* pMgr) { + Screenshare::mgr()->destroyClientSessions(m_savedClient); + PROTO::toplevelExport->destroyResource(this); + }); m_resource->setDestroy([this](CHyprlandToplevelExportManagerV1* pMgr) { PROTO::toplevelExport->destroyResource(this); }); m_resource->setCaptureToplevel([this](CHyprlandToplevelExportManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, uint32_t handle) { - this->captureToplevel(pMgr, frame, overlayCursor, g_pCompositor->getWindowFromHandle(handle)); + captureToplevel(frame, overlayCursor, g_pCompositor->getWindowFromHandle(handle)); }); m_resource->setCaptureToplevelWithWlrToplevelHandle([this](CHyprlandToplevelExportManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, wl_resource* handle) { - this->captureToplevel(pMgr, frame, overlayCursor, PROTO::foreignToplevelWlr->windowFromHandleResource(handle)); + captureToplevel(frame, overlayCursor, PROTO::foreignToplevelWlr->windowFromHandleResource(handle)); }); - m_lastMeasure.reset(); - m_lastFrame.reset(); - m_tickCallback = g_pHookSystem->hookDynamic("tick", [&](void* self, SCallbackInfo& info, std::any data) { onTick(); }); + m_savedClient = m_resource->client(); +} + +CToplevelExportClient::~CToplevelExportClient() { + Screenshare::mgr()->destroyClientSessions(m_savedClient); } -void CToplevelExportClient::captureToplevel(CHyprlandToplevelExportManagerV1* pMgr, uint32_t frame, int32_t overlayCursor_, PHLWINDOW handle) { +void CToplevelExportClient::captureToplevel(uint32_t frame, int32_t overlayCursor_, PHLWINDOW handle) { + auto session = Screenshare::mgr()->getManagedSession(m_resource->client(), handle); + // create a frame const auto FRAME = PROTO::toplevelExport->m_frames.emplace_back( - makeShared(makeShared(m_resource->client(), m_resource->version(), frame), overlayCursor_, handle)); + makeShared(makeShared(m_resource->client(), m_resource->version(), frame), session, !!overlayCursor_)); if UNLIKELY (!FRAME->good()) { LOGM(Log::ERR, "Couldn't alloc frame for sharing! (no memory)"); @@ -44,370 +47,115 @@ void CToplevelExportClient::captureToplevel(CHyprlandToplevelExportManagerV1* pM return; } - FRAME->m_self = FRAME; FRAME->m_client = m_self; -} - -void CToplevelExportClient::onTick() { - if (m_lastMeasure.getMillis() < 500) - return; - - m_framesInLastHalfSecond = m_frameCounter; - m_frameCounter = 0; - m_lastMeasure.reset(); - - const auto LASTFRAMEDELTA = m_lastFrame.getMillis() / 1000.0; - const bool FRAMEAWAITING = std::ranges::any_of(PROTO::toplevelExport->m_frames, [&](const auto& frame) { return frame->m_client.get() == this; }); - - if (m_framesInLastHalfSecond > 3 && !m_sentScreencast) { - EMIT_HOOK_EVENT("screencast", (std::vector{1, sc(m_framesInLastHalfSecond), sc(m_clientOwner)})); - g_pEventManager->postEvent(SHyprIPCEvent{"screencast", "1," + std::to_string(m_clientOwner)}); - m_sentScreencast = true; - } else if (m_framesInLastHalfSecond < 4 && m_sentScreencast && LASTFRAMEDELTA > 1.0 && !FRAMEAWAITING) { - EMIT_HOOK_EVENT("screencast", (std::vector{0, sc(m_framesInLastHalfSecond), sc(m_clientOwner)})); - g_pEventManager->postEvent(SHyprIPCEvent{"screencast", "0," + std::to_string(m_clientOwner)}); - m_sentScreencast = false; - } + FRAME->m_self = FRAME; } bool CToplevelExportClient::good() { - return m_resource->resource(); + return m_resource && m_resource->resource(); } -CToplevelExportFrame::CToplevelExportFrame(SP resource_, int32_t overlayCursor_, PHLWINDOW pWindow_) : m_resource(resource_), m_window(pWindow_) { +CToplevelExportFrame::CToplevelExportFrame(SP resource_, WP session, bool overlayCursor) : + m_resource(resource_), m_session(session) { if UNLIKELY (!good()) return; - m_cursorOverlayRequested = !!overlayCursor_; - - if UNLIKELY (!m_window) { - LOGM(Log::ERR, "Client requested sharing of window handle {:x} which does not exist!", m_window); - m_resource->sendFailed(); - return; - } - - if UNLIKELY (!m_window->m_isMapped) { - LOGM(Log::ERR, "Client requested sharing of window handle {:x} which is not shareable!", m_window); - m_resource->sendFailed(); - return; - } - m_resource->setOnDestroy([this](CHyprlandToplevelExportFrameV1* pFrame) { PROTO::toplevelExport->destroyResource(this); }); m_resource->setDestroy([this](CHyprlandToplevelExportFrameV1* pFrame) { PROTO::toplevelExport->destroyResource(this); }); - m_resource->setCopy([this](CHyprlandToplevelExportFrameV1* pFrame, wl_resource* res, int32_t ignoreDamage) { this->copy(pFrame, res, ignoreDamage); }); - - const auto PMONITOR = m_window->m_monitor.lock(); + m_resource->setCopy([this](CHyprlandToplevelExportFrameV1* pFrame, wl_resource* res, int32_t ignoreDamage) { shareFrame(res, !!ignoreDamage); }); - g_pHyprRenderer->makeEGLCurrent(); + m_listeners.stopped = m_session->m_events.stopped.listen([this]() { + if (good()) + m_resource->sendFailed(); + }); - m_shmFormat = NFormatUtils::alphaFormat(g_pHyprOpenGL->getPreferredReadFormat(PMONITOR)); - LOGM(Log::DEBUG, "Format {:x}", m_shmFormat); - //m_shmFormat = NFormatUtils::alphaFormat(m_shmFormat); - if UNLIKELY (m_shmFormat == DRM_FORMAT_INVALID) { - LOGM(Log::ERR, "No format supported by renderer in capture toplevel"); - m_resource->sendFailed(); - return; - } + m_frame = m_session->nextFrame(overlayCursor); - const auto PSHMINFO = NFormatUtils::getPixelFormatFromDRM(m_shmFormat); - if UNLIKELY (!PSHMINFO) { - LOGM(Log::ERR, "No pixel format supported by renderer in capture toplevel"); + auto formats = m_session->allowedFormats(); + if (formats.empty()) { + LOGM(Log::ERR, "No format supported by renderer in toplevel export protocol"); m_resource->sendFailed(); return; } - m_dmabufFormat = NFormatUtils::alphaFormat(g_pHyprOpenGL->getPreferredReadFormat(PMONITOR)); - - m_box = {0, 0, sc(m_window->m_realSize->value().x * PMONITOR->m_scale), sc(m_window->m_realSize->value().y * PMONITOR->m_scale)}; - - m_box.transform(Math::wlTransformToHyprutils(PMONITOR->m_transform), PMONITOR->m_transformedSize.x, PMONITOR->m_transformedSize.y).round(); + DRMFormat format = formats.at(0); + auto bufSize = m_frame->bufferSize(); - m_shmStride = NFormatUtils::minStride(PSHMINFO, m_box.w); + const auto PSHMINFO = NFormatUtils::getPixelFormatFromDRM(format); + const auto stride = NFormatUtils::minStride(PSHMINFO, bufSize.x); + m_resource->sendBuffer(NFormatUtils::drmToShm(format), bufSize.x, bufSize.y, stride); - m_resource->sendBuffer(NFormatUtils::drmToShm(m_shmFormat), m_box.width, m_box.height, m_shmStride); - - if LIKELY (m_dmabufFormat != DRM_FORMAT_INVALID) - m_resource->sendLinuxDmabuf(m_dmabufFormat, m_box.width, m_box.height); + if LIKELY (format != DRM_FORMAT_INVALID) + m_resource->sendLinuxDmabuf(format, bufSize.x, bufSize.y); m_resource->sendBufferDone(); } -void CToplevelExportFrame::copy(CHyprlandToplevelExportFrameV1* pFrame, wl_resource* buffer_, int32_t ignoreDamage) { - if UNLIKELY (!good()) { - LOGM(Log::ERR, "No frame in copyFrame??"); - return; - } - - if UNLIKELY (!validMapped(m_window)) { - LOGM(Log::ERR, "Client requested sharing of window handle {:x} which is gone!", m_window); - m_resource->sendFailed(); - return; - } - - if UNLIKELY (!m_window->m_isMapped) { - LOGM(Log::ERR, "Client requested sharing of window handle {:x} which is not shareable (2)!", m_window); - m_resource->sendFailed(); - return; - } - - const auto PBUFFER = CWLBufferResource::fromResource(buffer_); - if UNLIKELY (!PBUFFER) { - m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer"); - PROTO::toplevelExport->destroyResource(this); - return; - } +bool CToplevelExportFrame::good() { + return m_resource && m_resource->resource(); +} - if UNLIKELY (PBUFFER->m_buffer->size != m_box.size()) { - m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer dimensions"); - PROTO::toplevelExport->destroyResource(this); +void CToplevelExportFrame::shareFrame(wl_resource* buffer, bool ignoreDamage) { + if UNLIKELY (!good()) { + LOGM(Log::ERR, "No frame in shareFrame??"); return; } if UNLIKELY (m_buffer) { + LOGM(Log::ERR, "Buffer used in {:x}", (uintptr_t)this); m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_ALREADY_USED, "frame already used"); - PROTO::toplevelExport->destroyResource(this); + m_resource->sendFailed(); return; } - if (auto attrs = PBUFFER->m_buffer->dmabuf(); attrs.success) { - m_bufferDMA = true; - - if (attrs.format != m_dmabufFormat) { - m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer format"); - PROTO::toplevelExport->destroyResource(this); - return; - } - } else if (auto attrs = PBUFFER->m_buffer->shm(); attrs.success) { - if (attrs.format != m_shmFormat) { - m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer format"); - PROTO::toplevelExport->destroyResource(this); - return; - } else if (attrs.stride != m_shmStride) { - m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer stride"); - PROTO::toplevelExport->destroyResource(this); - return; - } - } else { - m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer type"); - PROTO::toplevelExport->destroyResource(this); + const auto PBUFFERRES = CWLBufferResource::fromResource(buffer); + if UNLIKELY (!PBUFFERRES || !PBUFFERRES->m_buffer) { + LOGM(Log::ERR, "Invalid buffer in {:x}", (uintptr_t)this); + m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer"); + m_resource->sendFailed(); return; } - m_buffer = CHLBufferReference(PBUFFER->m_buffer.lock()); - - m_ignoreDamage = ignoreDamage; - - if (ignoreDamage && validMapped(m_window)) - share(); - else - PROTO::toplevelExport->m_framesAwaitingWrite.emplace_back(m_self); -} + const auto& PBUFFER = PBUFFERRES->m_buffer.lock(); -void CToplevelExportFrame::share() { - if (!m_buffer || !validMapped(m_window)) - return; + if (ignoreDamage) + g_pHyprRenderer->damageMonitor(m_session->monitor()); - if (m_bufferDMA) { - if (!copyDmabuf(Time::steadyNow())) { - m_resource->sendFailed(); + auto error = m_frame->share(PBUFFER, {}, [this, ignoreDamage, self = m_self](eScreenshareResult result) { + if (self.expired() || !good()) return; - } - } else { - if (!copyShm(Time::steadyNow())) { - m_resource->sendFailed(); - return; - } - } - - m_resource->sendFlags(sc(0)); - - if (!m_ignoreDamage) - m_resource->sendDamage(0, 0, m_box.width, m_box.height); - - const auto [sec, nsec] = Time::secNsec(Time::steadyNow()); - - uint32_t tvSecHi = (sizeof(sec) > 4) ? sec >> 32 : 0; - uint32_t tvSecLo = sec & 0xFFFFFFFF; - m_resource->sendReady(tvSecHi, tvSecLo, nsec); -} - -bool CToplevelExportFrame::copyShm(const Time::steady_tp& now) { - const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(m_resource->client(), PERMISSION_TYPE_SCREENCOPY); - auto shm = m_buffer->shm(); - auto [pixelData, fmt, bufLen] = m_buffer->beginDataPtr(0); // no need for end, cuz it's shm - - // render the client - const auto PMONITOR = m_window->m_monitor.lock(); - CRegion fakeDamage{0, 0, PMONITOR->m_pixelSize.x * 10, PMONITOR->m_pixelSize.y * 10}; - - g_pHyprRenderer->makeEGLCurrent(); - - CFramebuffer outFB; - outFB.alloc(PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, PMONITOR->m_output->state->state().drmFormat); - - auto overlayCursor = shouldOverlayCursor(); - - if (overlayCursor) { - g_pPointerManager->lockSoftwareForMonitor(PMONITOR->m_self.lock()); - g_pPointerManager->damageCursor(PMONITOR->m_self.lock()); - } - - if (!g_pHyprRenderer->beginRender(PMONITOR, fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, &outFB)) - return false; - - g_pHyprOpenGL->clear(CHyprColor(0, 0, 0, 0)); - - // render client at 0,0 - if (PERM == PERMISSION_RULE_ALLOW_MODE_ALLOW) { - if (!m_window->m_ruleApplicator->noScreenShare().valueOrDefault()) { - g_pHyprRenderer->m_bBlockSurfaceFeedback = g_pHyprRenderer->shouldRenderWindow(m_window); // block the feedback to avoid spamming the surface if it's visible - g_pHyprRenderer->renderWindow(m_window, PMONITOR, now, false, RENDER_PASS_ALL, true, true); - g_pHyprRenderer->m_bBlockSurfaceFeedback = false; - } - if (overlayCursor) - g_pPointerManager->renderSoftwareCursorsFor(PMONITOR->m_self.lock(), now, fakeDamage, g_pInputManager->getMouseCoordsInternal() - m_window->m_realPosition->value()); - } else if (PERM == PERMISSION_RULE_ALLOW_MODE_DENY) { - CBox texbox = CBox{PMONITOR->m_transformedSize / 2.F, g_pHyprOpenGL->m_screencopyDeniedTexture->m_size}.translate(-g_pHyprOpenGL->m_screencopyDeniedTexture->m_size / 2.F); - g_pHyprOpenGL->renderTexture(g_pHyprOpenGL->m_screencopyDeniedTexture, texbox, {}); - } - - const auto PFORMAT = NFormatUtils::getPixelFormatFromDRM(shm.format); - if (!PFORMAT) { - g_pHyprRenderer->endRender(); - return false; - } - - g_pHyprOpenGL->m_renderData.blockScreenShader = true; - g_pHyprRenderer->endRender(); - - g_pHyprRenderer->makeEGLCurrent(); - g_pHyprOpenGL->m_renderData.pMonitor = PMONITOR; - outFB.bind(); - - glBindFramebuffer(GL_READ_FRAMEBUFFER, outFB.getFBID()); - glPixelStorei(GL_PACK_ALIGNMENT, 1); - - auto origin = Vector2D(0, 0); - switch (PMONITOR->m_transform) { - case WL_OUTPUT_TRANSFORM_FLIPPED_180: - case WL_OUTPUT_TRANSFORM_90: { - origin.y = PMONITOR->m_pixelSize.y - m_box.height; - break; - } - case WL_OUTPUT_TRANSFORM_FLIPPED_270: - case WL_OUTPUT_TRANSFORM_180: { - origin.x = PMONITOR->m_pixelSize.x - m_box.width; - origin.y = PMONITOR->m_pixelSize.y - m_box.height; - break; - } - case WL_OUTPUT_TRANSFORM_FLIPPED: - case WL_OUTPUT_TRANSFORM_270: { - origin.x = PMONITOR->m_pixelSize.x - m_box.width; - break; - } - default: break; - } - - int glFormat = PFORMAT->glFormat; - - if (glFormat == GL_RGBA) - glFormat = GL_BGRA_EXT; - - if (glFormat != GL_BGRA_EXT && glFormat != GL_RGB) { - if (PFORMAT->swizzle.has_value()) { - std::array RGBA = SWIZZLE_RGBA; - std::array BGRA = SWIZZLE_BGRA; - if (PFORMAT->swizzle == RGBA) - glFormat = GL_RGBA; - else if (PFORMAT->swizzle == BGRA) - glFormat = GL_BGRA_EXT; - else { - LOGM(Log::ERR, "Copied frame via shm might be broken or color flipped"); - glFormat = GL_RGBA; + switch (result) { + case RESULT_COPIED: { + m_resource->sendFlags(sc(0)); + if (!ignoreDamage) + m_frame->damage().forEachRect([&](const auto& rect) { m_resource->sendDamage(rect.x1, rect.y1, rect.x2 - rect.x1, rect.y2 - rect.y1); }); + + const auto [sec, nsec] = Time::secNsec(m_timestamp); + uint32_t tvSecHi = (sizeof(sec) > 4) ? sec >> 32 : 0; + uint32_t tvSecLo = sec & 0xFFFFFFFF; + m_resource->sendReady(tvSecHi, tvSecLo, nsec); + break; } + case RESULT_NOT_COPIED: + LOGM(Log::ERR, "Frame share failed in {:x}", (uintptr_t)this); + m_resource->sendFailed(); + break; + case RESULT_TIMESTAMP: m_timestamp = Time::steadyNow(); break; } - } - - glReadPixels(origin.x, origin.y, m_box.width, m_box.height, glFormat, PFORMAT->glType, pixelData); - - if (overlayCursor) { - g_pPointerManager->unlockSoftwareForMonitor(PMONITOR->m_self.lock()); - g_pPointerManager->damageCursor(PMONITOR->m_self.lock()); - } - - outFB.unbind(); - glPixelStorei(GL_PACK_ALIGNMENT, 4); - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - - return true; -} - -bool CToplevelExportFrame::copyDmabuf(const Time::steady_tp& now) { - const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(m_resource->client(), PERMISSION_TYPE_SCREENCOPY); - const auto PMONITOR = m_window->m_monitor.lock(); - - CRegion fakeDamage{0, 0, INT16_MAX, INT16_MAX}; - - auto overlayCursor = shouldOverlayCursor(); - - if (overlayCursor) { - g_pPointerManager->lockSoftwareForMonitor(PMONITOR->m_self.lock()); - g_pPointerManager->damageCursor(PMONITOR->m_self.lock()); - } - - if (!g_pHyprRenderer->beginRender(PMONITOR, fakeDamage, RENDER_MODE_TO_BUFFER, m_buffer.m_buffer)) - return false; - - g_pHyprOpenGL->clear(CHyprColor(0, 0, 0, 0)); - if (PERM == PERMISSION_RULE_ALLOW_MODE_ALLOW) { - if (!m_window->m_ruleApplicator->noScreenShare().valueOrDefault()) { - g_pHyprRenderer->m_bBlockSurfaceFeedback = g_pHyprRenderer->shouldRenderWindow(m_window); // block the feedback to avoid spamming the surface if it's visible - g_pHyprRenderer->renderWindow(m_window, PMONITOR, now, false, RENDER_PASS_ALL, true, true); - g_pHyprRenderer->m_bBlockSurfaceFeedback = false; - } - - if (overlayCursor) - g_pPointerManager->renderSoftwareCursorsFor(PMONITOR->m_self.lock(), now, fakeDamage, g_pInputManager->getMouseCoordsInternal() - m_window->m_realPosition->value()); - } else if (PERM == PERMISSION_RULE_ALLOW_MODE_DENY) { - CBox texbox = CBox{PMONITOR->m_transformedSize / 2.F, g_pHyprOpenGL->m_screencopyDeniedTexture->m_size}.translate(-g_pHyprOpenGL->m_screencopyDeniedTexture->m_size / 2.F); - g_pHyprOpenGL->renderTexture(g_pHyprOpenGL->m_screencopyDeniedTexture, texbox, {}); - } - - g_pHyprOpenGL->m_renderData.blockScreenShader = true; - g_pHyprRenderer->endRender(); + }); - if (overlayCursor) { - g_pPointerManager->unlockSoftwareForMonitor(PMONITOR->m_self.lock()); - g_pPointerManager->damageCursor(PMONITOR->m_self.lock()); + switch (error) { + case ERROR_NONE: m_buffer = CHLBufferReference(PBUFFER); break; + case ERROR_NO_BUFFER: m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer"); break; + case ERROR_BUFFER_SIZE: + case ERROR_BUFFER_FORMAT: m_resource->sendFailed(); break; + case ERROR_UNKNOWN: + case ERROR_STOPPED: m_resource->sendFailed(); break; } - - return true; -} - -bool CToplevelExportFrame::shouldOverlayCursor() const { - if (!m_cursorOverlayRequested) - return false; - - auto pointerSurfaceResource = g_pSeatManager->m_state.pointerFocus.lock(); - - if (!pointerSurfaceResource) - return false; - - auto pointerSurface = Desktop::View::CWLSurface::fromResource(pointerSurfaceResource); - - return pointerSurface && Desktop::View::CWindow::fromView(pointerSurface->view()) == m_window; -} - -bool CToplevelExportFrame::good() { - return m_resource->resource(); } CToplevelExportProtocol::CToplevelExportProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { - static auto P1 = g_pHookSystem->hookDynamic("closeWindow", [this](void* self, SCallbackInfo& info, std::any data) { - auto window = std::any_cast(data); - - onWindowUnmap(window); - }); + ; } void CToplevelExportProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { @@ -426,69 +174,10 @@ void CToplevelExportProtocol::bindManager(wl_client* client, void* data, uint32_ } void CToplevelExportProtocol::destroyResource(CToplevelExportClient* client) { - std::erase_if(m_clients, [&](const auto& other) { return other.get() == client; }); std::erase_if(m_frames, [&](const auto& other) { return other->m_client.get() == client; }); - std::erase_if(m_framesAwaitingWrite, [&](const auto& other) { return !other || other->m_client.get() == client; }); + std::erase_if(m_clients, [&](const auto& other) { return other.get() == client; }); } void CToplevelExportProtocol::destroyResource(CToplevelExportFrame* frame) { std::erase_if(m_frames, [&](const auto& other) { return other.get() == frame; }); - std::erase_if(m_framesAwaitingWrite, [&](const auto& other) { return !other || other.get() == frame; }); -} - -void CToplevelExportProtocol::onOutputCommit(PHLMONITOR pMonitor) { - if (m_framesAwaitingWrite.empty()) - return; // nothing to share - - std::vector> framesToRemove; - // reserve number of elements to avoid reallocations - framesToRemove.reserve(m_framesAwaitingWrite.size()); - - // share frame if correct output - for (auto const& f : m_framesAwaitingWrite) { - if (!f) - continue; - - // check permissions - const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(f->m_resource->client(), PERMISSION_TYPE_SCREENCOPY); - - if (PERM == PERMISSION_RULE_ALLOW_MODE_PENDING) - continue; // pending an answer, don't do anything yet. - - if (!validMapped(f->m_window)) { - framesToRemove.emplace_back(f); - continue; - } - - if (!f->m_window) - continue; - - const auto PWINDOW = f->m_window; - - if (pMonitor != PWINDOW->m_monitor.lock()) - continue; - - CBox geometry = {PWINDOW->m_realPosition->value().x, PWINDOW->m_realPosition->value().y, PWINDOW->m_realSize->value().x, PWINDOW->m_realSize->value().y}; - - if (geometry.intersection({pMonitor->m_position, pMonitor->m_size}).empty()) - continue; - - f->share(); - - f->m_client->m_lastFrame.reset(); - ++f->m_client->m_frameCounter; - - framesToRemove.push_back(f); - } - - for (auto const& f : framesToRemove) { - std::erase(m_framesAwaitingWrite, f); - } -} - -void CToplevelExportProtocol::onWindowUnmap(PHLWINDOW pWindow) { - for (auto const& f : m_frames) { - if (f->m_window == pWindow) - f->m_window.reset(); - } } diff --git a/src/protocols/ToplevelExport.hpp b/src/protocols/ToplevelExport.hpp index 44704d84..38dec784 100644 --- a/src/protocols/ToplevelExport.hpp +++ b/src/protocols/ToplevelExport.hpp @@ -1,72 +1,63 @@ #pragma once -#include "../defines.hpp" -#include "hyprland-toplevel-export-v1.hpp" #include "WaylandProtocol.hpp" -#include "Screencopy.hpp" +#include "hyprland-toplevel-export-v1.hpp" + #include "../helpers/time/Time.hpp" +#include "./types/Buffer.hpp" +#include #include class CMonitor; +namespace Screenshare { + class CScreenshareSession; + class CScreenshareFrame; +}; class CToplevelExportClient { public: CToplevelExportClient(SP resource_); + ~CToplevelExportClient(); - bool good(); - - WP m_self; - eClientOwners m_clientOwner = CLIENT_TOPLEVEL_EXPORT; - - CTimer m_lastFrame; - int m_frameCounter = 0; + bool good(); private: SP m_resource; + WP m_self; - int m_framesInLastHalfSecond = 0; - CTimer m_lastMeasure; - bool m_sentScreencast = false; + wl_client* m_savedClient = nullptr; - SP m_tickCallback; - void onTick(); - - void captureToplevel(CHyprlandToplevelExportManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, PHLWINDOW handle); + void captureToplevel(uint32_t frame, int32_t overlayCursor, PHLWINDOW handle); friend class CToplevelExportProtocol; }; class CToplevelExportFrame { public: - CToplevelExportFrame(SP resource_, int32_t overlayCursor, PHLWINDOW pWindow); - - bool good(); + CToplevelExportFrame(SP resource, WP session, bool overlayCursor); - WP m_self; - WP m_client; + bool good(); private: - SP m_resource; + SP m_resource; + WP m_self; + WP m_client; + + WP m_session; + UP m_frame; - PHLWINDOW m_window; - bool m_cursorOverlayRequested = false; - bool m_ignoreDamage = false; + CHLBufferReference m_buffer; + Time::steady_tp m_timestamp; - CHLBufferReference m_buffer; - bool m_bufferDMA = false; - uint32_t m_shmFormat = 0; - uint32_t m_dmabufFormat = 0; - int m_shmStride = 0; - CBox m_box = {}; + struct { + CHyprSignalListener stopped; + } m_listeners; - void copy(CHyprlandToplevelExportFrameV1* pFrame, wl_resource* buffer, int32_t ignoreDamage); - bool copyDmabuf(const Time::steady_tp& now); - bool copyShm(const Time::steady_tp& now); - void share(); - bool shouldOverlayCursor() const; + void shareFrame(wl_resource* buffer, bool ignoreDamage); friend class CToplevelExportProtocol; + friend class CToplevelExportClient; }; class CToplevelExportProtocol : IWaylandProtocol { @@ -82,15 +73,9 @@ class CToplevelExportProtocol : IWaylandProtocol { private: std::vector> m_clients; std::vector> m_frames; - std::vector> m_framesAwaitingWrite; void onWindowUnmap(PHLWINDOW pWindow); - void shareFrame(CToplevelExportFrame* frame); - bool copyFrameDmabuf(CToplevelExportFrame* frame, const Time::steady_tp& now); - bool copyFrameShm(CToplevelExportFrame* frame, const Time::steady_tp& now); - void sendDamage(CToplevelExportFrame* frame); - friend class CToplevelExportClient; friend class CToplevelExportFrame; }; diff --git a/src/protocols/core/Seat.cpp b/src/protocols/core/Seat.cpp index bfe70a75..52015fd8 100644 --- a/src/protocols/core/Seat.cpp +++ b/src/protocols/core/Seat.cpp @@ -141,6 +141,10 @@ CWLPointerResource::CWLPointerResource(SP resource_, SPm_state.pointerFocus.lock(), {-1, -1} /* Coords don't really matter that much, they will be updated next move */); } +CWLPointerResource::~CWLPointerResource() { + m_events.destroyed.emit(); +} + int CWLPointerResource::version() { return m_resource->version(); } diff --git a/src/protocols/core/Seat.hpp b/src/protocols/core/Seat.hpp index c30bbd71..85dc5c39 100644 --- a/src/protocols/core/Seat.hpp +++ b/src/protocols/core/Seat.hpp @@ -71,6 +71,7 @@ class CWLTouchResource { class CWLPointerResource { public: CWLPointerResource(SP resource_, SP owner_); + ~CWLPointerResource(); bool good(); int version(); @@ -88,6 +89,10 @@ class CWLPointerResource { WP m_owner; + struct { + CSignalT<> destroyed; + } m_events; + // static SP fromResource(wl_resource* res); -- cgit v1.2.3