2022-12-05 17:05:15 +00:00
|
|
|
#include "ToplevelExport.hpp"
|
|
|
|
|
#include "../Compositor.hpp"
|
2024-04-25 00:58:40 +01:00
|
|
|
#include "ForeignToplevelWlr.hpp"
|
2026-02-22 06:30:11 -06:00
|
|
|
#include "../managers/screenshare/ScreenshareManager.hpp"
|
2024-06-08 10:07:59 +02:00
|
|
|
#include "../helpers/Format.hpp"
|
2025-01-17 15:21:35 +00:00
|
|
|
#include "../render/Renderer.hpp"
|
2022-12-05 17:05:15 +00:00
|
|
|
|
2025-02-06 13:42:20 +01:00
|
|
|
#include <hyprutils/math/Vector2D.hpp>
|
2022-12-05 17:05:15 +00:00
|
|
|
|
2026-02-22 06:30:11 -06:00
|
|
|
using namespace Screenshare;
|
|
|
|
|
|
2025-05-04 19:21:36 +02:00
|
|
|
CToplevelExportClient::CToplevelExportClient(SP<CHyprlandToplevelExportManagerV1> resource_) : m_resource(resource_) {
|
2025-01-17 18:21:34 +01:00
|
|
|
if UNLIKELY (!good())
|
2024-07-27 10:02:02 -05:00
|
|
|
return;
|
2022-12-05 17:05:15 +00:00
|
|
|
|
2026-02-22 06:30:11 -06:00
|
|
|
m_resource->setOnDestroy([this](CHyprlandToplevelExportManagerV1* pMgr) {
|
|
|
|
|
Screenshare::mgr()->destroyClientSessions(m_savedClient);
|
|
|
|
|
PROTO::toplevelExport->destroyResource(this);
|
|
|
|
|
});
|
2025-05-04 19:21:36 +02:00
|
|
|
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) {
|
2026-02-22 06:30:11 -06:00
|
|
|
captureToplevel(frame, overlayCursor, g_pCompositor->getWindowFromHandle(handle));
|
2024-07-27 10:02:02 -05:00
|
|
|
});
|
2025-05-04 19:21:36 +02:00
|
|
|
m_resource->setCaptureToplevelWithWlrToplevelHandle([this](CHyprlandToplevelExportManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, wl_resource* handle) {
|
2026-02-22 06:30:11 -06:00
|
|
|
captureToplevel(frame, overlayCursor, PROTO::foreignToplevelWlr->windowFromHandleResource(handle));
|
2024-07-27 10:02:02 -05:00
|
|
|
});
|
|
|
|
|
|
2026-02-22 06:30:11 -06:00
|
|
|
m_savedClient = m_resource->client();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CToplevelExportClient::~CToplevelExportClient() {
|
|
|
|
|
Screenshare::mgr()->destroyClientSessions(m_savedClient);
|
2024-07-24 12:07:36 -05:00
|
|
|
}
|
|
|
|
|
|
2026-02-22 06:30:11 -06:00
|
|
|
void CToplevelExportClient::captureToplevel(uint32_t frame, int32_t overlayCursor_, PHLWINDOW handle) {
|
|
|
|
|
auto session = Screenshare::mgr()->getManagedSession(m_resource->client(), handle);
|
|
|
|
|
|
2024-07-27 10:02:02 -05:00
|
|
|
// create a frame
|
2025-05-04 19:21:36 +02:00
|
|
|
const auto FRAME = PROTO::toplevelExport->m_frames.emplace_back(
|
2026-02-22 06:30:11 -06:00
|
|
|
makeShared<CToplevelExportFrame>(makeShared<CHyprlandToplevelExportFrameV1>(m_resource->client(), m_resource->version(), frame), session, !!overlayCursor_));
|
2022-12-05 17:05:15 +00:00
|
|
|
|
2025-01-17 18:21:34 +01:00
|
|
|
if UNLIKELY (!FRAME->good()) {
|
2025-12-18 17:23:24 +00:00
|
|
|
LOGM(Log::ERR, "Couldn't alloc frame for sharing! (no memory)");
|
2025-05-04 19:21:36 +02:00
|
|
|
m_resource->noMemory();
|
2024-07-27 10:02:02 -05:00
|
|
|
PROTO::toplevelExport->destroyResource(FRAME.get());
|
2022-12-05 17:05:15 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-04 19:21:36 +02:00
|
|
|
FRAME->m_client = m_self;
|
2026-02-22 06:30:11 -06:00
|
|
|
FRAME->m_self = FRAME;
|
2022-12-05 17:05:15 +00:00
|
|
|
}
|
|
|
|
|
|
2024-07-27 10:02:02 -05:00
|
|
|
bool CToplevelExportClient::good() {
|
2026-02-22 06:30:11 -06:00
|
|
|
return m_resource && m_resource->resource();
|
2022-12-05 17:05:15 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-22 06:30:11 -06:00
|
|
|
CToplevelExportFrame::CToplevelExportFrame(SP<CHyprlandToplevelExportFrameV1> resource_, WP<CScreenshareSession> session, bool overlayCursor) :
|
|
|
|
|
m_resource(resource_), m_session(session) {
|
2025-01-17 18:21:34 +01:00
|
|
|
if UNLIKELY (!good())
|
2022-12-05 17:05:15 +00:00
|
|
|
return;
|
|
|
|
|
|
2025-05-04 19:21:36 +02:00
|
|
|
m_resource->setOnDestroy([this](CHyprlandToplevelExportFrameV1* pFrame) { PROTO::toplevelExport->destroyResource(this); });
|
|
|
|
|
m_resource->setDestroy([this](CHyprlandToplevelExportFrameV1* pFrame) { PROTO::toplevelExport->destroyResource(this); });
|
2026-02-22 06:30:11 -06:00
|
|
|
m_resource->setCopy([this](CHyprlandToplevelExportFrameV1* pFrame, wl_resource* res, int32_t ignoreDamage) { shareFrame(res, !!ignoreDamage); });
|
2022-12-05 17:05:15 +00:00
|
|
|
|
2026-02-22 06:30:11 -06:00
|
|
|
m_listeners.stopped = m_session->m_events.stopped.listen([this]() {
|
|
|
|
|
if (good())
|
|
|
|
|
m_resource->sendFailed();
|
|
|
|
|
});
|
2024-02-15 00:58:58 +00:00
|
|
|
|
2026-02-22 06:30:11 -06:00
|
|
|
m_frame = m_session->nextFrame(overlayCursor);
|
2022-12-05 17:05:15 +00:00
|
|
|
|
2026-02-22 06:30:11 -06:00
|
|
|
auto formats = m_session->allowedFormats();
|
|
|
|
|
if (formats.empty()) {
|
|
|
|
|
LOGM(Log::ERR, "No format supported by renderer in toplevel export protocol");
|
2025-05-04 19:21:36 +02:00
|
|
|
m_resource->sendFailed();
|
2022-12-05 17:05:15 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-22 06:30:11 -06:00
|
|
|
DRMFormat format = formats.at(0);
|
|
|
|
|
auto bufSize = m_frame->bufferSize();
|
2022-12-05 17:05:15 +00:00
|
|
|
|
2026-02-22 06:30:11 -06:00
|
|
|
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);
|
2022-12-05 17:05:15 +00:00
|
|
|
|
2026-02-22 06:30:11 -06:00
|
|
|
if LIKELY (format != DRM_FORMAT_INVALID)
|
|
|
|
|
m_resource->sendLinuxDmabuf(format, bufSize.x, bufSize.y);
|
2023-07-20 12:42:25 +02:00
|
|
|
|
2025-05-04 19:21:36 +02:00
|
|
|
m_resource->sendBufferDone();
|
2022-12-05 17:05:15 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-22 06:30:11 -06:00
|
|
|
bool CToplevelExportFrame::good() {
|
|
|
|
|
return m_resource && m_resource->resource();
|
|
|
|
|
}
|
2022-12-05 17:05:15 +00:00
|
|
|
|
2026-02-22 06:30:11 -06:00
|
|
|
void CToplevelExportFrame::shareFrame(wl_resource* buffer, bool ignoreDamage) {
|
|
|
|
|
if UNLIKELY (!good()) {
|
|
|
|
|
LOGM(Log::ERR, "No frame in shareFrame??");
|
2022-12-05 17:05:15 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-04 19:21:36 +02:00
|
|
|
if UNLIKELY (m_buffer) {
|
2026-02-22 06:30:11 -06:00
|
|
|
LOGM(Log::ERR, "Buffer used in {:x}", (uintptr_t)this);
|
2025-05-04 19:21:36 +02:00
|
|
|
m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_ALREADY_USED, "frame already used");
|
2026-02-22 06:30:11 -06:00
|
|
|
m_resource->sendFailed();
|
2022-12-05 17:05:15 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-22 06:30:11 -06:00
|
|
|
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();
|
2022-12-05 17:05:15 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-22 06:30:11 -06:00
|
|
|
const auto& PBUFFER = PBUFFERRES->m_buffer.lock();
|
2022-12-05 17:05:15 +00:00
|
|
|
|
2026-02-22 06:30:11 -06:00
|
|
|
if (ignoreDamage)
|
|
|
|
|
g_pHyprRenderer->damageMonitor(m_session->monitor());
|
2022-12-05 17:05:15 +00:00
|
|
|
|
2026-02-22 06:30:11 -06:00
|
|
|
auto error = m_frame->share(PBUFFER, {}, [this, ignoreDamage, self = m_self](eScreenshareResult result) {
|
|
|
|
|
if (self.expired() || !good())
|
2022-12-05 17:05:15 +00:00
|
|
|
return;
|
2026-02-22 06:30:11 -06:00
|
|
|
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); });
|
|
|
|
|
|
|
|
|
|
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;
|
2026-01-07 19:53:42 +01:00
|
|
|
}
|
2026-02-22 06:30:11 -06:00
|
|
|
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;
|
2026-01-07 19:53:42 +01:00
|
|
|
}
|
2026-02-22 06:30:11 -06:00
|
|
|
});
|
2024-07-27 10:02:02 -05:00
|
|
|
|
2026-02-22 06:30:11 -06:00
|
|
|
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;
|
2024-07-27 10:02:02 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CToplevelExportProtocol::CToplevelExportProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {
|
2026-02-22 06:30:11 -06:00
|
|
|
;
|
2024-07-27 10:02:02 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CToplevelExportProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {
|
2025-05-04 19:21:36 +02:00
|
|
|
const auto CLIENT = m_clients.emplace_back(makeShared<CToplevelExportClient>(makeShared<CHyprlandToplevelExportManagerV1>(client, ver, id)));
|
2024-07-27 10:02:02 -05:00
|
|
|
|
|
|
|
|
if (!CLIENT->good()) {
|
2025-12-18 17:23:24 +00:00
|
|
|
LOGM(Log::DEBUG, "Failed to bind client! (out of memory)");
|
2024-07-27 10:02:02 -05:00
|
|
|
wl_client_post_no_memory(client);
|
2025-05-04 19:21:36 +02:00
|
|
|
m_clients.pop_back();
|
2024-07-27 10:02:02 -05:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-04 19:21:36 +02:00
|
|
|
CLIENT->m_self = CLIENT;
|
2024-07-27 10:02:02 -05:00
|
|
|
|
2025-12-18 17:23:24 +00:00
|
|
|
LOGM(Log::DEBUG, "Bound client successfully!");
|
2024-07-27 10:02:02 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CToplevelExportProtocol::destroyResource(CToplevelExportClient* client) {
|
2025-05-04 19:21:36 +02:00
|
|
|
std::erase_if(m_frames, [&](const auto& other) { return other->m_client.get() == client; });
|
2026-02-22 06:30:11 -06:00
|
|
|
std::erase_if(m_clients, [&](const auto& other) { return other.get() == client; });
|
2024-07-27 10:02:02 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CToplevelExportProtocol::destroyResource(CToplevelExportFrame* frame) {
|
2025-05-04 19:21:36 +02:00
|
|
|
std::erase_if(m_frames, [&](const auto& other) { return other.get() == frame; });
|
2023-02-16 22:51:34 +00:00
|
|
|
}
|