Skip to content

Commit 8764591

Browse files
committed
implement image copy capture protocol
1 parent 61b772b commit 8764591

File tree

6 files changed

+328
-1
lines changed

6 files changed

+328
-1
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,7 @@ protocolnew("staging/pointer-warp" "pointer-warp-v1" false)
453453
protocolnew("staging/fifo" "fifo-v1" false)
454454
protocolnew("staging/commit-timing" "commit-timing-v1" false)
455455
protocolnew("staging/ext-image-capture-source" "ext-image-capture-source-v1" false)
456+
protocolnew("staging/ext-image-copy-capture" "ext-image-copy-capture-v1" false)
456457

457458
protocolwayland()
458459

protocols/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ protocols = [
8181
wayland_protocol_dir / 'staging/fifo/fifo-v1.xml',
8282
wayland_protocol_dir / 'staging/commit-timing/commit-timing-v1.xml',
8383
wayland_protocol_dir / 'staging/ext-image-capture-source/ext-image-capture-source-v1.xml',
84+
wayland_protocol_dir / 'staging/ext-image-copy-capture/ext-image-copy-capture-v1.xml',
8485
]
8586

8687
wl_protocols = []

src/managers/ProtocolManager.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
#include "../protocols/CTMControl.hpp"
5252
#include "../protocols/HyprlandSurface.hpp"
5353
#include "../protocols/ImageCaptureSource.hpp"
54+
#include "../protocols/ImageCopyCapture.hpp"
5455
#include "../protocols/core/Seat.hpp"
5556
#include "../protocols/core/DataDevice.hpp"
5657
#include "../protocols/core/Compositor.hpp"
@@ -201,7 +202,8 @@ CProtocolManager::CProtocolManager() {
201202
PROTO::fifo = makeUnique<CFifoProtocol>(&wp_fifo_manager_v1_interface, 1, "Fifo");
202203
PROTO::commitTiming = makeUnique<CCommitTimingProtocol>(&wp_commit_timing_manager_v1_interface, 1, "CommitTiming");
203204

204-
PROTO::imageCaptureSource = makeUnique<CImageCaptureSourceProtocol>();
205+
PROTO::imageCaptureSource = makeUnique<CImageCaptureSourceProtocol>(); // ctor inits actual protos, output and toplevel
206+
PROTO::imageCopyCapture = makeUnique<CImageCopyCaptureProtocol>(&ext_image_copy_capture_manager_v1_interface, 1, "ImageCopyCapture");
205207

206208
// ! please read the top of this file before adding another protocol
207209

src/protocols/ImageCaptureSource.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
#include "WaylandProtocol.hpp"
88
#include "ext-image-capture-source-v1.hpp"
99

10+
class CImageCopyCaptureSession;
11+
1012
class CImageCaptureSource {
1113
public:
1214
CImageCaptureSource(SP<CExtImageCaptureSourceV1> resource, PHLMONITOR pMonitor);

src/protocols/ImageCopyCapture.cpp

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
#include "ImageCopyCapture.hpp"
2+
#include "../managers/screenshare/ScreenshareManager.hpp"
3+
#include "LinuxDMABUF.hpp"
4+
#include "../render/OpenGL.hpp"
5+
#include <cstring>
6+
7+
CImageCopyCaptureSession::CImageCopyCaptureSession(SP<CExtImageCopyCaptureSessionV1> resource, SP<CImageCaptureSource> source, extImageCopyCaptureManagerV1Options options) :
8+
m_resource(resource), m_source(source), m_paintCursor(options & EXT_IMAGE_COPY_CAPTURE_MANAGER_V1_OPTIONS_PAINT_CURSORS) {
9+
10+
m_resource->setDestroy([this](CExtImageCopyCaptureSessionV1* pMgr) { PROTO::imageCopyCapture->destroyResource(this); });
11+
m_resource->setOnDestroy([this](CExtImageCopyCaptureSessionV1* pMgr) { PROTO::imageCopyCapture->destroyResource(this); });
12+
13+
m_resource->setCreateFrame([this](CExtImageCopyCaptureSessionV1* pMgr, uint32_t id) {
14+
if (!m_frame.expired()) {
15+
LOGM(LOG, "Duplicate frame in session for source: \"{}\"", m_source->getName());
16+
m_resource->error(EXT_IMAGE_COPY_CAPTURE_SESSION_V1_ERROR_DUPLICATE_FRAME, "duplicate frame");
17+
return;
18+
}
19+
20+
auto PFRAME = PROTO::imageCopyCapture->m_frames.emplace_back(
21+
makeShared<CImageCopyCaptureFrame>(makeShared<CExtImageCopyCaptureFrameV1>(pMgr->client(), pMgr->version(), id), m_self));
22+
23+
m_frame = PFRAME;
24+
});
25+
26+
if (m_source->m_monitor)
27+
m_session = g_pScreenshareManager->newSession(m_resource->client(), m_source->m_monitor.lock());
28+
else
29+
m_session = g_pScreenshareManager->newSession(m_resource->client(), m_source->m_window.lock());
30+
31+
if UNLIKELY (!m_session) {
32+
m_resource->sendStopped();
33+
m_resource->error(-1, "unable to share screen");
34+
return;
35+
}
36+
37+
sendConstraints();
38+
39+
m_listeners.constraintsChanged = m_session->m_events.constraintsChanged.listen([this]() { sendConstraints(); });
40+
m_listeners.stopped = m_session->m_events.stopped.listen([this]() { PROTO::imageCopyCapture->destroyResource(this); });
41+
}
42+
43+
CImageCopyCaptureSession::~CImageCopyCaptureSession() {
44+
if (m_session)
45+
m_session->stop();
46+
if (m_resource->resource())
47+
m_resource->sendStopped();
48+
}
49+
50+
void CImageCopyCaptureSession::sendConstraints() {
51+
auto formats = m_session->allowedFormats();
52+
53+
if UNLIKELY (formats.empty()) {
54+
m_session->stop();
55+
m_resource->error(-1, "no formats available");
56+
return;
57+
}
58+
59+
for (DRMFormat format : formats) {
60+
m_resource->sendShmFormat(NFormatUtils::drmToShm(format));
61+
62+
auto modifiers = g_pHyprOpenGL->getDRMFormatModifiers(format);
63+
64+
wl_array modsArr;
65+
wl_array_init(&modsArr);
66+
if (!modifiers.empty()) {
67+
wl_array_add(&modsArr, modifiers.size() * sizeof(uint64_t));
68+
memcpy(modsArr.data, modifiers.data(), modifiers.size() * sizeof(uint64_t));
69+
}
70+
m_resource->sendDmabufFormat(format, &modsArr);
71+
wl_array_release(&modsArr);
72+
}
73+
74+
dev_t device = PROTO::linuxDma->getMainDevice();
75+
struct wl_array deviceArr = {
76+
.size = sizeof(device),
77+
.data = sc<void*>(&device),
78+
};
79+
m_resource->sendDmabufDevice(&deviceArr);
80+
81+
m_bufferSize = m_session->bufferSize();
82+
m_resource->sendBufferSize(m_bufferSize.x, m_bufferSize.y);
83+
84+
m_resource->sendDone();
85+
}
86+
87+
CImageCopyCaptureFrame::CImageCopyCaptureFrame(SP<CExtImageCopyCaptureFrameV1> resource, WP<CImageCopyCaptureSession> session) : m_resource(resource), m_session(session) {
88+
if (m_session->m_bufferSize != m_session->m_session->bufferSize()) {
89+
m_session->sendConstraints();
90+
m_resource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN);
91+
return;
92+
}
93+
94+
m_frame = m_session->m_session->nextFrame(m_session->m_paintCursor);
95+
96+
m_resource->setDestroy([this](CExtImageCopyCaptureFrameV1* pMgr) { PROTO::imageCopyCapture->destroyResource(this); });
97+
m_resource->setOnDestroy([this](CExtImageCopyCaptureFrameV1* pMgr) { PROTO::imageCopyCapture->destroyResource(this); });
98+
99+
m_resource->setAttachBuffer([this](CExtImageCopyCaptureFrameV1* pMgr, wl_resource* buf) {
100+
if (m_captured) {
101+
LOGM(ERR, "Frame already captured in attach_buffer, {:x}", (uintptr_t)this);
102+
m_resource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, "already captured");
103+
return;
104+
}
105+
106+
auto PBUFFERRES = CWLBufferResource::fromResource(buf);
107+
if (!PBUFFERRES || !PBUFFERRES->m_buffer) {
108+
LOGM(ERR, "Invalid buffer in attach_buffer {:x}", (uintptr_t)this);
109+
m_resource->error(-1, "invalid buffer");
110+
return;
111+
}
112+
113+
m_buffer = PBUFFERRES->m_buffer.lock();
114+
});
115+
116+
m_resource->setDamageBuffer([this](CExtImageCopyCaptureFrameV1* pMgr, int32_t x, int32_t y, int32_t w, int32_t h) {
117+
if (m_captured) {
118+
LOGM(ERR, "Frame already captured in damage_buffer, {:x}", (uintptr_t)this);
119+
m_resource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, "already captured");
120+
return;
121+
}
122+
123+
if (x < 0 || y < 0 || w <= 0 || h <= 0) {
124+
m_resource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_INVALID_BUFFER_DAMAGE, "invalid buffer damage");
125+
return;
126+
}
127+
128+
m_clientDamage.add(x, y, w, h);
129+
});
130+
131+
m_resource->setCapture([this](CExtImageCopyCaptureFrameV1* pMgr) {
132+
if (m_captured) {
133+
LOGM(ERR, "Frame already captured in capture, {:x}", (uintptr_t)this);
134+
m_resource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, "already captured");
135+
return;
136+
}
137+
138+
auto error = m_frame->share(m_buffer, m_clientDamage, [this](eScreenshareResult result) {
139+
switch (result) {
140+
case RESULT_COPIED: m_resource->sendReady(); break;
141+
case RESULT_NOT_COPIED: m_resource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN); break;
142+
case RESULT_TIMESTAMP:
143+
auto [sec, nsec] = Time::secNsec(Time::steadyNow());
144+
uint32_t tvSecHi = (sizeof(sec) > 4) ? sec >> 32 : 0;
145+
uint32_t tvSecLo = sec & 0xFFFFFFFF;
146+
m_resource->sendPresentationTime(tvSecHi, tvSecLo, nsec);
147+
break;
148+
}
149+
});
150+
151+
switch (error) {
152+
case ERROR_NONE: m_captured = true; break;
153+
case ERROR_NO_BUFFER: m_resource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_NO_BUFFER, "no buffer attached"); break;
154+
case ERROR_BUFFER_SIZE:
155+
case ERROR_BUFFER_FORMAT: m_resource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_BUFFER_CONSTRAINTS); break;
156+
case ERROR_STOPPED: m_resource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_STOPPED); break;
157+
case ERROR_UNKNOWN: m_resource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN); break;
158+
}
159+
});
160+
161+
m_clientDamage.clear();
162+
163+
// TODO: see ScreenshareFrame::share() for "add a damage ring for output damage since last shared frame"
164+
m_resource->sendDamage(0, 0, m_session->m_bufferSize.x, m_session->m_bufferSize.y);
165+
166+
m_resource->sendTransform(m_frame->transform());
167+
}
168+
169+
CImageCopyCaptureFrame::~CImageCopyCaptureFrame() {
170+
if (m_session)
171+
m_session->m_frame.reset();
172+
}
173+
174+
CImageCopyCaptureProtocol::CImageCopyCaptureProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {
175+
;
176+
}
177+
178+
void CImageCopyCaptureProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {
179+
const auto RESOURCE = m_managers.emplace_back(makeShared<CExtImageCopyCaptureManagerV1>(client, ver, id));
180+
181+
if UNLIKELY (!RESOURCE->resource()) {
182+
wl_client_post_no_memory(client);
183+
m_managers.pop_back();
184+
return;
185+
}
186+
187+
RESOURCE->setDestroy([this](CExtImageCopyCaptureManagerV1* pMgr) { destroyResource(pMgr); });
188+
RESOURCE->setOnDestroy([this](CExtImageCopyCaptureManagerV1* pMgr) { destroyResource(pMgr); });
189+
190+
RESOURCE->setCreateSession([this](CExtImageCopyCaptureManagerV1* pMgr, uint32_t id, wl_resource* source_, extImageCopyCaptureManagerV1Options options) {
191+
auto source = PROTO::imageCaptureSource->sourceFromResource(source_);
192+
if (!source) {
193+
LOGM(LOG, "Client tried to create image copy capture session from invalid source");
194+
pMgr->error(-1, "invalid image capture source");
195+
return;
196+
}
197+
198+
if (options > 1) {
199+
LOGM(LOG, "Client tried to create image copy capture session with invalid options");
200+
pMgr->error(EXT_IMAGE_COPY_CAPTURE_MANAGER_V1_ERROR_INVALID_OPTION, "Options can't be above 1");
201+
return;
202+
}
203+
204+
auto& PSESSION =
205+
m_sessions.emplace_back(makeShared<CImageCopyCaptureSession>(makeShared<CExtImageCopyCaptureSessionV1>(pMgr->client(), pMgr->version(), id), source, options));
206+
PSESSION->m_self = PSESSION;
207+
LOGM(LOG, "New image copy capture session for source ({}): \"{}\"", source->getTypeName(), source->getName());
208+
});
209+
210+
RESOURCE->setCreatePointerCursorSession([this](CExtImageCopyCaptureManagerV1* pMgr, uint32_t id, wl_resource* source_, wl_resource* pointer_) {
211+
SP<CImageCaptureSource> source = PROTO::imageCaptureSource->sourceFromResource(source_);
212+
if (!source) {
213+
LOGM(LOG, "Client tried to create image copy capture session from invalid source");
214+
destroyResource(pMgr);
215+
return;
216+
}
217+
218+
// TODO: find out what this would be, then implement constructor aswell
219+
// auto pointer = CWLPointerResource::fromResource(pointer_);
220+
221+
// m_sessions.emplace_back(makeShared<CImageCopyCaptureSession>(makeShared<CExtImageCopyCaptureSessionV1>(pMgr->client(), pMgr->version(), id), source, pointer));
222+
// LOGM(LOG, "New image copy capture session for source ({}): {}", source->getTypeName(), source->getName());
223+
});
224+
}
225+
226+
void CImageCopyCaptureProtocol::destroyResource(CExtImageCopyCaptureManagerV1* resource) {
227+
std::erase_if(m_managers, [&](const auto& other) { return other.get() == resource; });
228+
}
229+
230+
void CImageCopyCaptureProtocol::destroyResource(CImageCopyCaptureSession* resource) {
231+
std::erase_if(m_sessions, [&](const auto& other) { return other.get() == resource; });
232+
}
233+
234+
void CImageCopyCaptureProtocol::destroyResource(CImageCopyCaptureFrame* resource) {
235+
std::erase_if(m_frames, [&](const auto& other) { return other.get() == resource; });
236+
}

src/protocols/ImageCopyCapture.hpp

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#pragma once
2+
3+
// TODO: implement rest of proto
4+
5+
#include <vector>
6+
7+
#include "../defines.hpp"
8+
#include "../helpers/signal/Signal.hpp"
9+
#include "../helpers/Format.hpp"
10+
#include "WaylandProtocol.hpp"
11+
#include "ImageCaptureSource.hpp"
12+
#include "ext-image-copy-capture-v1.hpp"
13+
14+
class IHLBuffer;
15+
class CScreenshareSession;
16+
class CScreenshareFrame;
17+
18+
class CImageCopyCaptureFrame {
19+
public:
20+
CImageCopyCaptureFrame(SP<CExtImageCopyCaptureFrameV1> resource, WP<CImageCopyCaptureSession> session);
21+
~CImageCopyCaptureFrame();
22+
23+
private:
24+
SP<CExtImageCopyCaptureFrameV1> m_resource;
25+
WP<CImageCopyCaptureSession> m_session;
26+
UP<CScreenshareFrame> m_frame;
27+
28+
bool m_captured = false;
29+
SP<IHLBuffer> m_buffer;
30+
CRegion m_clientDamage;
31+
32+
friend class CImageCopyCaptureSession;
33+
};
34+
35+
class CImageCopyCaptureSession {
36+
public:
37+
CImageCopyCaptureSession(SP<CExtImageCopyCaptureSessionV1> resource, SP<CImageCaptureSource> source, extImageCopyCaptureManagerV1Options options);
38+
~CImageCopyCaptureSession();
39+
40+
private:
41+
SP<CExtImageCopyCaptureSessionV1> m_resource;
42+
43+
SP<CImageCaptureSource> m_source;
44+
UP<CScreenshareSession> m_session;
45+
WP<CImageCopyCaptureFrame> m_frame;
46+
47+
Vector2D m_bufferSize;
48+
bool m_paintCursor;
49+
50+
struct {
51+
CHyprSignalListener constraintsChanged;
52+
CHyprSignalListener stopped;
53+
} m_listeners;
54+
55+
WP<CImageCopyCaptureSession> m_self;
56+
57+
//
58+
void sendConstraints();
59+
60+
friend class CImageCopyCaptureProtocol;
61+
friend class CImageCopyCaptureFrame;
62+
};
63+
64+
class CImageCopyCaptureProtocol : public IWaylandProtocol {
65+
public:
66+
CImageCopyCaptureProtocol(const wl_interface* iface, const int& ver, const std::string& name);
67+
68+
virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);
69+
70+
void destroyResource(CExtImageCopyCaptureManagerV1* resource);
71+
void destroyResource(CImageCopyCaptureSession* resource);
72+
void destroyResource(CImageCopyCaptureFrame* resource);
73+
74+
private:
75+
std::vector<SP<CExtImageCopyCaptureManagerV1>> m_managers;
76+
std::vector<SP<CImageCopyCaptureSession>> m_sessions;
77+
78+
std::vector<SP<CImageCopyCaptureFrame>> m_frames;
79+
80+
friend class CImageCopyCaptureSession;
81+
};
82+
83+
namespace PROTO {
84+
inline UP<CImageCopyCaptureProtocol> imageCopyCapture;
85+
};

0 commit comments

Comments
 (0)