protocols: implement image-capture-source-v1 and image-copy-capture-v1 (#11709)
Implements the new screencopy protocols
This commit is contained in:
parent
93dbf88426
commit
b4ee4674f9
37 changed files with 2585 additions and 1078 deletions
|
|
@ -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);
|
||||
|
|
|
|||
135
src/protocols/ImageCaptureSource.cpp
Normal file
135
src/protocols/ImageCaptureSource.cpp
Normal file
|
|
@ -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<CExtImageCaptureSourceV1> 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<CExtImageCaptureSourceV1> 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<CExtOutputImageCaptureSourceManagerV1>(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<CImageCaptureSource>(makeShared<CExtImageCaptureSourceV1>(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<CExtForeignToplevelImageCaptureSourceManagerV1>(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<CImageCaptureSource>(makeShared<CExtImageCaptureSourceV1>(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<COutputImageCaptureSourceProtocol>(&ext_output_image_capture_source_manager_v1_interface, 1, "OutputImageCaptureSource");
|
||||
m_toplevel = makeUnique<CToplevelImageCaptureSourceProtocol>(&ext_foreign_toplevel_image_capture_source_manager_v1_interface, 1, "ForeignToplevelImageCaptureSource");
|
||||
}
|
||||
|
||||
SP<CImageCaptureSource> CImageCaptureSourceProtocol::sourceFromResource(wl_resource* res) {
|
||||
auto data = sc<CImageCaptureSource*>(sc<CExtImageCaptureSourceV1*>(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; });
|
||||
}
|
||||
73
src/protocols/ImageCaptureSource.hpp
Normal file
73
src/protocols/ImageCaptureSource.hpp
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#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<CExtImageCaptureSourceV1> resource, PHLMONITOR pMonitor);
|
||||
CImageCaptureSource(SP<CExtImageCaptureSourceV1> resource, PHLWINDOW pWindow);
|
||||
|
||||
bool good();
|
||||
std::string getName();
|
||||
std::string getTypeName();
|
||||
CBox logicalBox();
|
||||
|
||||
WP<CImageCaptureSource> m_self;
|
||||
|
||||
private:
|
||||
SP<CExtImageCaptureSourceV1> 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<CImageCaptureSource> sourceFromResource(wl_resource* resource);
|
||||
|
||||
void destroyResource(CExtOutputImageCaptureSourceManagerV1* resource);
|
||||
void destroyResource(CExtForeignToplevelImageCaptureSourceManagerV1* resource);
|
||||
void destroyResource(CImageCaptureSource* resource);
|
||||
|
||||
private:
|
||||
UP<COutputImageCaptureSourceProtocol> m_output;
|
||||
UP<CToplevelImageCaptureSourceProtocol> m_toplevel;
|
||||
|
||||
std::vector<SP<CExtOutputImageCaptureSourceManagerV1>> m_outputManagers;
|
||||
std::vector<SP<CExtForeignToplevelImageCaptureSourceManagerV1>> m_toplevelManagers;
|
||||
|
||||
std::vector<SP<CImageCaptureSource>> m_sources;
|
||||
|
||||
friend class COutputImageCaptureSourceProtocol;
|
||||
friend class CToplevelImageCaptureSourceProtocol;
|
||||
};
|
||||
|
||||
namespace PROTO {
|
||||
inline UP<CImageCaptureSourceProtocol> imageCaptureSource;
|
||||
};
|
||||
516
src/protocols/ImageCopyCapture.cpp
Normal file
516
src/protocols/ImageCopyCapture.cpp
Normal file
|
|
@ -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 <cstring>
|
||||
|
||||
using namespace Screenshare;
|
||||
|
||||
CImageCopyCaptureSession::CImageCopyCaptureSession(SP<CExtImageCopyCaptureSessionV1> resource, SP<CImageCaptureSource> 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<CImageCopyCaptureFrame>(makeShared<CExtImageCopyCaptureFrameV1>(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<void*>(&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<CExtImageCopyCaptureCursorSessionV1> resource, SP<CImageCaptureSource> source, SP<CWLPointerResource> 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<CExtImageCopyCaptureSessionV1>(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<CExtImageCopyCaptureFrameV1>(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<CExtImageCopyCaptureFrameV1> 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<void*>(&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<CExtImageCopyCaptureFrameV1> resource, WP<CImageCopyCaptureSession> 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<CExtImageCopyCaptureManagerV1>(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<CImageCopyCaptureSession>(makeShared<CExtImageCopyCaptureSessionV1>(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<CImageCaptureSource> 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<CImageCopyCaptureCursorSession>(makeShared<CExtImageCopyCaptureCursorSessionV1>(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; });
|
||||
}
|
||||
133
src/protocols/ImageCopyCapture.hpp
Normal file
133
src/protocols/ImageCopyCapture.hpp
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#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<CExtImageCopyCaptureFrameV1> resource, WP<CImageCopyCaptureSession> session);
|
||||
~CImageCopyCaptureFrame();
|
||||
|
||||
bool good();
|
||||
|
||||
private:
|
||||
SP<CExtImageCopyCaptureFrameV1> m_resource;
|
||||
WP<CImageCopyCaptureSession> m_session;
|
||||
UP<Screenshare::CScreenshareFrame> m_frame;
|
||||
|
||||
bool m_captured = false;
|
||||
SP<IHLBuffer> m_buffer;
|
||||
CRegion m_clientDamage;
|
||||
|
||||
friend class CImageCopyCaptureSession;
|
||||
};
|
||||
|
||||
class CImageCopyCaptureSession {
|
||||
public:
|
||||
CImageCopyCaptureSession(SP<CExtImageCopyCaptureSessionV1> resource, SP<CImageCaptureSource> source, extImageCopyCaptureManagerV1Options options);
|
||||
~CImageCopyCaptureSession();
|
||||
|
||||
bool good();
|
||||
|
||||
private:
|
||||
SP<CExtImageCopyCaptureSessionV1> m_resource;
|
||||
|
||||
SP<CImageCaptureSource> m_source;
|
||||
UP<Screenshare::CScreenshareSession> m_session;
|
||||
WP<CImageCopyCaptureFrame> m_frame;
|
||||
|
||||
Vector2D m_bufferSize = Vector2D(0, 0);
|
||||
bool m_paintCursor = true;
|
||||
|
||||
struct {
|
||||
CHyprSignalListener constraintsChanged;
|
||||
CHyprSignalListener stopped;
|
||||
} m_listeners;
|
||||
|
||||
WP<CImageCopyCaptureSession> m_self;
|
||||
|
||||
//
|
||||
void sendConstraints();
|
||||
|
||||
friend class CImageCopyCaptureProtocol;
|
||||
friend class CImageCopyCaptureFrame;
|
||||
};
|
||||
|
||||
class CImageCopyCaptureCursorSession {
|
||||
public:
|
||||
CImageCopyCaptureCursorSession(SP<CExtImageCopyCaptureCursorSessionV1> resource, SP<CImageCaptureSource> source, SP<CWLPointerResource> pointer);
|
||||
~CImageCopyCaptureCursorSession();
|
||||
|
||||
bool good();
|
||||
|
||||
private:
|
||||
SP<CExtImageCopyCaptureCursorSessionV1> m_resource;
|
||||
SP<CImageCaptureSource> m_source;
|
||||
SP<CWLPointerResource> 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<CExtImageCopyCaptureSessionV1> m_sessionResource;
|
||||
UP<Screenshare::CCursorshareSession> m_session;
|
||||
Vector2D m_bufferSize = Vector2D(0, 0);
|
||||
|
||||
// frame stuff
|
||||
SP<CExtImageCopyCaptureFrameV1> m_frameResource;
|
||||
bool m_captured = false;
|
||||
SP<IHLBuffer> m_buffer;
|
||||
|
||||
struct {
|
||||
CHyprSignalListener constraintsChanged;
|
||||
CHyprSignalListener stopped;
|
||||
CHyprSignalListener commit;
|
||||
} m_listeners;
|
||||
|
||||
void sendCursorEvents();
|
||||
|
||||
void createFrame(SP<CExtImageCopyCaptureFrameV1> 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<SP<CExtImageCopyCaptureManagerV1>> m_managers;
|
||||
std::vector<SP<CImageCopyCaptureSession>> m_sessions;
|
||||
std::vector<SP<CImageCopyCaptureCursorSession>> m_cursorSessions;
|
||||
|
||||
std::vector<SP<CImageCopyCaptureFrame>> m_frames;
|
||||
|
||||
friend class CImageCopyCaptureSession;
|
||||
};
|
||||
|
||||
namespace PROTO {
|
||||
inline UP<CImageCopyCaptureProtocol> imageCopyCapture;
|
||||
};
|
||||
|
|
@ -643,3 +643,7 @@ void CLinuxDMABufV1Protocol::updateScanoutTranche(SP<CWLSurfaceResource> surface
|
|||
|
||||
feedbackResource->m_lastFeedbackWasScanout = true;
|
||||
}
|
||||
|
||||
dev_t CLinuxDMABufV1Protocol::getMainDevice() {
|
||||
return m_mainDevice;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<CWLSurfaceResource> surface, PHLMONITOR pMonitor);
|
||||
dev_t getMainDevice();
|
||||
|
||||
private:
|
||||
void destroyResource(CLinuxDMABUFResource* resource);
|
||||
|
|
|
|||
|
|
@ -1,467 +1,48 @@
|
|||
#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 <algorithm>
|
||||
#include <functional>
|
||||
|
||||
CScreencopyFrame::CScreencopyFrame(SP<CZwlrScreencopyFrameV1> resource_, int32_t overlay_cursor, wl_resource* output, CBox box_) : 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);
|
||||
});
|
||||
|
||||
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<int>(m_monitor->m_size.x), sc<int>(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();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
m_buffer = CHLBufferReference(PBUFFER->m_buffer.lock());
|
||||
|
||||
PROTO::screencopy->m_framesAwaitingWrite.emplace_back(m_self);
|
||||
|
||||
g_pHyprRenderer->m_directScanoutBlocked = true;
|
||||
|
||||
if (!m_withDamage)
|
||||
g_pHyprRenderer->damageMonitor(m_monitor.lock());
|
||||
}
|
||||
|
||||
void CScreencopyFrame::share() {
|
||||
if (!m_buffer || !m_monitor)
|
||||
return;
|
||||
|
||||
const auto NOW = Time::steadyNow();
|
||||
|
||||
auto callback = [this, NOW, weak = m_self](bool success) {
|
||||
if (weak.expired())
|
||||
return;
|
||||
|
||||
if (!success) {
|
||||
LOGM(Log::ERR, "{} copy failed in {:x}", m_bufferDMA ? "Dmabuf" : "Shm", (uintptr_t)this);
|
||||
m_resource->sendFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
m_resource->sendFlags(sc<zwlrScreencopyFrameV1Flags>(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());
|
||||
}
|
||||
|
||||
void CScreencopyFrame::renderMon() {
|
||||
auto TEXTURE = makeShared<CTexture>(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<Desktop::View::CPopup> popup, void*) {
|
||||
if (!popup->wlSurface() || !popup->wlSurface()->resource() || !popup->visible())
|
||||
return;
|
||||
|
||||
const auto popRel = popup->coordsRelativeToParent();
|
||||
popup->wlSurface()->resource()->breadthfirst(
|
||||
[&](SP<CWLSurfaceResource> 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);
|
||||
}
|
||||
|
||||
for (auto const& w : g_pCompositor->m_windows) {
|
||||
if (!w->m_ruleApplicator->noScreenShare().valueOrDefault())
|
||||
continue;
|
||||
|
||||
if (!g_pHyprRenderer->shouldRenderWindow(w, m_monitor.lock()))
|
||||
continue;
|
||||
|
||||
if (w->isHidden())
|
||||
continue;
|
||||
|
||||
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};
|
||||
|
||||
w->m_popupHead->breadthfirst(hidePopups(popupBaseOffset), nullptr);
|
||||
}
|
||||
|
||||
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");
|
||||
return;
|
||||
}
|
||||
|
||||
renderMon();
|
||||
|
||||
g_pHyprRenderer->endRender();
|
||||
}
|
||||
|
||||
void CScreencopyFrame::copyDmabuf(std::function<void(bool)> 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);
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
uint32_t packStride = NFormatUtils::minStride(PFORMAT, m_box.w);
|
||||
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<GLint, 4> RGBA = SWIZZLE_RGBA;
|
||||
std::array<GLint, 4> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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<uint32_t>(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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
using namespace Screenshare;
|
||||
|
||||
CScreencopyClient::CScreencopyClient(SP<CZwlrScreencopyManagerV1> 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->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) { this->captureOutput(frame, overlayCursor, output, {}); });
|
||||
[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) { this->captureOutput(frame, overlayCursor, output, {x, y, w, h}); });
|
||||
int32_t h) { 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(); });
|
||||
m_savedClient = m_resource->client();
|
||||
}
|
||||
|
||||
CScreencopyClient::~CScreencopyClient() {
|
||||
Screenshare::mgr()->destroyClientSessions(m_savedClient);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
const auto FRAME = PROTO::screencopy->m_frames.emplace_back(
|
||||
makeShared<CScreencopyFrame>(makeShared<CZwlrScreencopyFrameV1>(m_resource->client(), m_resource->version(), frame), overlayCursor_, output, box));
|
||||
makeShared<CScreencopyFrame>(makeShared<CZwlrScreencopyFrameV1>(m_resource->client(), m_resource->version(), frame), session, !!overlayCursor_));
|
||||
|
||||
if (!FRAME->good()) {
|
||||
LOGM(Log::ERR, "Couldn't alloc frame for sharing! (no memory)");
|
||||
|
|
@ -470,38 +51,114 @@ void CScreencopyClient::captureOutput(uint32_t frame, int32_t overlayCursor_, wl
|
|||
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<uint64_t>{1, sc<uint64_t>(m_framesInLastHalfSecond), sc<uint64_t>(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<uint64_t>{0, sc<uint64_t>(m_framesInLastHalfSecond), sc<uint64_t>(m_clientOwner)}));
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{"screencast", "0," + std::to_string(m_clientOwner)});
|
||||
m_sentScreencast = false;
|
||||
}
|
||||
FRAME->m_self = FRAME;
|
||||
}
|
||||
|
||||
bool CScreencopyClient::good() {
|
||||
return m_resource->resource();
|
||||
return m_resource && m_resource->resource();
|
||||
}
|
||||
|
||||
wl_client* CScreencopyClient::client() {
|
||||
return m_resource ? m_resource->client() : nullptr;
|
||||
CScreencopyFrame::CScreencopyFrame(SP<CZwlrScreencopyFrameV1> resource_, WP<CScreenshareSession> session, bool overlayCursor) :
|
||||
m_resource(resource_), m_session(session), m_overlayCursor(overlayCursor) {
|
||||
if UNLIKELY (!good())
|
||||
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); });
|
||||
|
||||
m_listeners.stopped = m_session->m_events.stopped.listen([this]() {
|
||||
if (good())
|
||||
m_resource->sendFailed();
|
||||
});
|
||||
|
||||
m_frame = m_session->nextFrame(overlayCursor);
|
||||
|
||||
auto formats = m_session->allowedFormats();
|
||||
if (formats.empty()) {
|
||||
LOGM(Log::ERR, "No format supported by renderer in screencopy protocol");
|
||||
m_resource->sendFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
DRMFormat format = formats.at(0);
|
||||
auto bufSize = m_frame->bufferSize();
|
||||
|
||||
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);
|
||||
|
||||
if (m_resource->version() >= 3) {
|
||||
if LIKELY (format != DRM_FORMAT_INVALID)
|
||||
m_resource->sendLinuxDmabuf(format, bufSize.x, bufSize.y);
|
||||
|
||||
m_resource->sendBufferDone();
|
||||
}
|
||||
}
|
||||
|
||||
void CScreencopyFrame::shareFrame(CZwlrScreencopyFrameV1* pFrame, wl_resource* buffer, bool withDamage) {
|
||||
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(ZWLR_SCREENCOPY_FRAME_V1_ERROR_ALREADY_USED, "frame already used");
|
||||
m_resource->sendFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const auto& PBUFFER = PBUFFERRES->m_buffer.lock();
|
||||
|
||||
if (!withDamage)
|
||||
g_pHyprRenderer->damageMonitor(m_session->monitor());
|
||||
|
||||
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<zwlrScreencopyFrameV1Flags>(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;
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
bool CScreencopyFrame::good() {
|
||||
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<WP<CScreencopyFrame>> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <list>
|
||||
#include <vector>
|
||||
#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 <aquamarine/buffer/Buffer.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
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<CZwlrScreencopyManagerV1> resource_);
|
||||
~CScreencopyClient();
|
||||
|
||||
bool good();
|
||||
wl_client* client();
|
||||
|
||||
WP<CScreencopyClient> m_self;
|
||||
eClientOwners m_clientOwner = CLIENT_SCREENCOPY;
|
||||
|
||||
CTimer m_lastFrame;
|
||||
int m_frameCounter = 0;
|
||||
bool good();
|
||||
|
||||
private:
|
||||
SP<CZwlrScreencopyManagerV1> m_resource;
|
||||
WP<CScreencopyClient> m_self;
|
||||
|
||||
int m_framesInLastHalfSecond = 0;
|
||||
CTimer m_lastMeasure;
|
||||
bool m_sentScreencast = false;
|
||||
|
||||
SP<HOOK_CALLBACK_FN> 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<CZwlrScreencopyFrameV1> resource, int32_t overlay_cursor, wl_resource* output, CBox box);
|
||||
CScreencopyFrame(SP<CZwlrScreencopyFrameV1> resource, WP<Screenshare::CScreenshareSession> session, bool overlayCursor);
|
||||
|
||||
bool good();
|
||||
|
||||
WP<CScreencopyFrame> m_self;
|
||||
WP<CScreencopyClient> m_client;
|
||||
bool good();
|
||||
|
||||
private:
|
||||
SP<CZwlrScreencopyFrameV1> m_resource;
|
||||
SP<CZwlrScreencopyFrameV1> m_resource;
|
||||
WP<CScreencopyFrame> m_self;
|
||||
WP<CScreencopyClient> m_client;
|
||||
|
||||
PHLMONITORREF m_monitor;
|
||||
bool m_overlayCursor = false;
|
||||
bool m_withDamage = false;
|
||||
WP<Screenshare::CScreenshareSession> m_session;
|
||||
UP<Screenshare::CScreenshareFrame> 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<void(bool)> 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<SP<CScreencopyFrame>> m_frames;
|
||||
std::vector<WP<CScreencopyFrame>> m_framesAwaitingWrite;
|
||||
std::vector<SP<CScreencopyClient>> 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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 <algorithm>
|
||||
#include <hyprutils/math/Vector2D.hpp>
|
||||
|
||||
using namespace Screenshare;
|
||||
|
||||
CToplevelExportClient::CToplevelExportClient(SP<CHyprlandToplevelExportManagerV1> 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();
|
||||
}
|
||||
|
||||
void CToplevelExportClient::captureToplevel(CHyprlandToplevelExportManagerV1* pMgr, uint32_t frame, int32_t overlayCursor_, PHLWINDOW handle) {
|
||||
CToplevelExportClient::~CToplevelExportClient() {
|
||||
Screenshare::mgr()->destroyClientSessions(m_savedClient);
|
||||
}
|
||||
|
||||
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<CToplevelExportFrame>(makeShared<CHyprlandToplevelExportFrameV1>(m_resource->client(), m_resource->version(), frame), overlayCursor_, handle));
|
||||
makeShared<CToplevelExportFrame>(makeShared<CHyprlandToplevelExportFrameV1>(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<uint64_t>{1, sc<uint64_t>(m_framesInLastHalfSecond), sc<uint64_t>(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<uint64_t>{0, sc<uint64_t>(m_framesInLastHalfSecond), sc<uint64_t>(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<CHyprlandToplevelExportFrameV1> resource_, int32_t overlayCursor_, PHLWINDOW pWindow_) : m_resource(resource_), m_window(pWindow_) {
|
||||
CToplevelExportFrame::CToplevelExportFrame(SP<CHyprlandToplevelExportFrameV1> resource_, WP<CScreenshareSession> 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); });
|
||||
m_resource->setCopy([this](CHyprlandToplevelExportFrameV1* pFrame, wl_resource* res, int32_t ignoreDamage) { shareFrame(res, !!ignoreDamage); });
|
||||
|
||||
const auto PMONITOR = m_window->m_monitor.lock();
|
||||
m_listeners.stopped = m_session->m_events.stopped.listen([this]() {
|
||||
if (good())
|
||||
m_resource->sendFailed();
|
||||
});
|
||||
|
||||
g_pHyprRenderer->makeEGLCurrent();
|
||||
m_frame = m_session->nextFrame(overlayCursor);
|
||||
|
||||
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");
|
||||
auto formats = m_session->allowedFormats();
|
||||
if (formats.empty()) {
|
||||
LOGM(Log::ERR, "No format supported by renderer in toplevel export protocol");
|
||||
m_resource->sendFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto PSHMINFO = NFormatUtils::getPixelFormatFromDRM(m_shmFormat);
|
||||
if UNLIKELY (!PSHMINFO) {
|
||||
LOGM(Log::ERR, "No pixel format supported by renderer in capture toplevel");
|
||||
m_resource->sendFailed();
|
||||
return;
|
||||
}
|
||||
DRMFormat format = formats.at(0);
|
||||
auto bufSize = m_frame->bufferSize();
|
||||
|
||||
m_dmabufFormat = NFormatUtils::alphaFormat(g_pHyprOpenGL->getPreferredReadFormat(PMONITOR));
|
||||
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_box = {0, 0, sc<int>(m_window->m_realSize->value().x * PMONITOR->m_scale), sc<int>(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();
|
||||
|
||||
m_shmStride = NFormatUtils::minStride(PSHMINFO, m_box.w);
|
||||
|
||||
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) {
|
||||
bool CToplevelExportFrame::good() {
|
||||
return m_resource && m_resource->resource();
|
||||
}
|
||||
|
||||
void CToplevelExportFrame::shareFrame(wl_resource* buffer, bool 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;
|
||||
}
|
||||
|
||||
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);
|
||||
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());
|
||||
const auto& PBUFFER = PBUFFERRES->m_buffer.lock();
|
||||
|
||||
m_ignoreDamage = ignoreDamage;
|
||||
if (ignoreDamage)
|
||||
g_pHyprRenderer->damageMonitor(m_session->monitor());
|
||||
|
||||
if (ignoreDamage && validMapped(m_window))
|
||||
share();
|
||||
else
|
||||
PROTO::toplevelExport->m_framesAwaitingWrite.emplace_back(m_self);
|
||||
}
|
||||
|
||||
void CToplevelExportFrame::share() {
|
||||
if (!m_buffer || !validMapped(m_window))
|
||||
return;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
switch (result) {
|
||||
case RESULT_COPIED: {
|
||||
m_resource->sendFlags(sc<hyprlandToplevelExportFrameV1Flags>(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); });
|
||||
|
||||
m_resource->sendFlags(sc<hyprlandToplevelExportFrameV1Flags>(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<GLint, 4> RGBA = SWIZZLE_RGBA;
|
||||
std::array<GLint, 4> 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;
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
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<PHLWINDOW>(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<WP<CToplevelExportFrame>> 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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <aquamarine/buffer/Buffer.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
class CMonitor;
|
||||
namespace Screenshare {
|
||||
class CScreenshareSession;
|
||||
class CScreenshareFrame;
|
||||
};
|
||||
|
||||
class CToplevelExportClient {
|
||||
public:
|
||||
CToplevelExportClient(SP<CHyprlandToplevelExportManagerV1> resource_);
|
||||
~CToplevelExportClient();
|
||||
|
||||
bool good();
|
||||
|
||||
WP<CToplevelExportClient> m_self;
|
||||
eClientOwners m_clientOwner = CLIENT_TOPLEVEL_EXPORT;
|
||||
|
||||
CTimer m_lastFrame;
|
||||
int m_frameCounter = 0;
|
||||
bool good();
|
||||
|
||||
private:
|
||||
SP<CHyprlandToplevelExportManagerV1> m_resource;
|
||||
WP<CToplevelExportClient> m_self;
|
||||
|
||||
int m_framesInLastHalfSecond = 0;
|
||||
CTimer m_lastMeasure;
|
||||
bool m_sentScreencast = false;
|
||||
wl_client* m_savedClient = nullptr;
|
||||
|
||||
SP<HOOK_CALLBACK_FN> 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<CHyprlandToplevelExportFrameV1> resource_, int32_t overlayCursor, PHLWINDOW pWindow);
|
||||
CToplevelExportFrame(SP<CHyprlandToplevelExportFrameV1> resource, WP<Screenshare::CScreenshareSession> session, bool overlayCursor);
|
||||
|
||||
bool good();
|
||||
|
||||
WP<CToplevelExportFrame> m_self;
|
||||
WP<CToplevelExportClient> m_client;
|
||||
bool good();
|
||||
|
||||
private:
|
||||
SP<CHyprlandToplevelExportFrameV1> m_resource;
|
||||
SP<CHyprlandToplevelExportFrameV1> m_resource;
|
||||
WP<CToplevelExportFrame> m_self;
|
||||
WP<CToplevelExportClient> m_client;
|
||||
|
||||
PHLWINDOW m_window;
|
||||
bool m_cursorOverlayRequested = false;
|
||||
bool m_ignoreDamage = false;
|
||||
WP<Screenshare::CScreenshareSession> m_session;
|
||||
UP<Screenshare::CScreenshareFrame> 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;
|
||||
|
||||
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;
|
||||
struct {
|
||||
CHyprSignalListener stopped;
|
||||
} m_listeners;
|
||||
|
||||
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<SP<CToplevelExportClient>> m_clients;
|
||||
std::vector<SP<CToplevelExportFrame>> m_frames;
|
||||
std::vector<WP<CToplevelExportFrame>> 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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -141,6 +141,10 @@ CWLPointerResource::CWLPointerResource(SP<CWlPointer> resource_, SP<CWLSeatResou
|
|||
sendEnter(g_pSeatManager->m_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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ class CWLTouchResource {
|
|||
class CWLPointerResource {
|
||||
public:
|
||||
CWLPointerResource(SP<CWlPointer> resource_, SP<CWLSeatResource> owner_);
|
||||
~CWLPointerResource();
|
||||
|
||||
bool good();
|
||||
int version();
|
||||
|
|
@ -88,6 +89,10 @@ class CWLPointerResource {
|
|||
|
||||
WP<CWLSeatResource> m_owner;
|
||||
|
||||
struct {
|
||||
CSignalT<> destroyed;
|
||||
} m_events;
|
||||
|
||||
//
|
||||
static SP<CWLPointerResource> fromResource(wl_resource* res);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue