diff options
| author | Ikalco <73481042+ikalco@users.noreply.github.com> | 2026-02-22 06:30:11 -0600 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-02-22 12:30:11 +0000 |
| commit | b4ee4674f9a74e3d602c7fb17bc09f79d221583c (patch) | |
| tree | 294bbb207c44e778dd78afda65cbd79181f04124 /src/managers/screenshare/CursorshareSession.cpp | |
| parent | 93dbf884261efcb387e920b1cd0e4ed5b288bc3a (diff) | |
protocols: implement image-capture-source-v1 and image-copy-capture-v1 (#11709)
Implements the new screencopy protocols
Diffstat (limited to 'src/managers/screenshare/CursorshareSession.cpp')
| -rw-r--r-- | src/managers/screenshare/CursorshareSession.cpp | 240 |
1 files changed, 240 insertions, 0 deletions
diff --git a/src/managers/screenshare/CursorshareSession.cpp b/src/managers/screenshare/CursorshareSession.cpp new file mode 100644 index 00000000..2322625f --- /dev/null +++ b/src/managers/screenshare/CursorshareSession.cpp @@ -0,0 +1,240 @@ +#include "ScreenshareManager.hpp" +#include "../PointerManager.hpp" +#include "../../protocols/core/Seat.hpp" +#include "../permissions/DynamicPermissionManager.hpp" +#include "../../render/Renderer.hpp" + +using namespace Screenshare; + +CCursorshareSession::CCursorshareSession(wl_client* client, WP<CWLPointerResource> pointer) : m_client(client), m_pointer(pointer) { + m_listeners.pointerDestroyed = m_pointer->m_events.destroyed.listen([this] { stop(); }); + m_listeners.cursorChanged = g_pPointerManager->m_events.cursorChanged.listen([this] { + calculateConstraints(); + m_events.constraintsChanged.emit(); + + if (m_pendingFrame.pending) { + if (copy()) + return; + + LOGM(Log::ERR, "Failed to copy cursor image for cursor share"); + if (m_pendingFrame.callback) + m_pendingFrame.callback(RESULT_NOT_COPIED); + m_pendingFrame.pending = false; + return; + } + }); + + calculateConstraints(); +} + +CCursorshareSession::~CCursorshareSession() { + stop(); +} + +void CCursorshareSession::stop() { + if (m_stopped) + return; + m_stopped = true; + m_events.stopped.emit(); +} + +void CCursorshareSession::calculateConstraints() { + const auto& cursorImage = g_pPointerManager->currentCursorImage(); + m_constraintsChanged = true; + + // cursor is hidden, keep the previous constraints and render 0 alpha + if (!cursorImage.pBuffer) + return; + + // TODO: should cursor share have a format bit flip for RGBA? + if (auto attrs = cursorImage.pBuffer->shm(); attrs.success) { + m_format = attrs.format; + } else { + // we only have shm cursors + return; + } + + m_hotspot = cursorImage.hotspot; + m_bufferSize = cursorImage.size; +} + +// TODO: allow render to buffer without monitor and remove monitor param +eScreenshareError CCursorshareSession::share(PHLMONITOR monitor, SP<IHLBuffer> buffer, FSourceBoxCallback sourceBoxCallback, FScreenshareCallback callback) { + if (m_stopped || m_pointer.expired() || m_bufferSize == Vector2D(0, 0)) + return ERROR_STOPPED; + + if UNLIKELY (!buffer || !buffer->m_resource || !buffer->m_resource->good()) { + LOGM(Log::ERR, "Client requested sharing to an invalid buffer"); + return ERROR_NO_BUFFER; + } + + if UNLIKELY (buffer->size != m_bufferSize) { + LOGM(Log::ERR, "Client requested sharing to an invalid buffer size"); + return ERROR_BUFFER_SIZE; + } + + uint32_t bufFormat; + if (buffer->dmabuf().success) + bufFormat = buffer->dmabuf().format; + else if (buffer->shm().success) + bufFormat = buffer->shm().format; + else { + LOGM(Log::ERR, "Client requested sharing to an invalid buffer"); + return ERROR_NO_BUFFER; + } + + if (bufFormat != m_format) { + LOGM(Log::ERR, "Invalid format {} in {:x}", bufFormat, (uintptr_t)this); + return ERROR_BUFFER_FORMAT; + } + + m_pendingFrame.pending = true; + m_pendingFrame.monitor = monitor; + m_pendingFrame.buffer = buffer; + m_pendingFrame.sourceBoxCallback = sourceBoxCallback; + m_pendingFrame.callback = callback; + + // nothing changed, then delay copy until contraints changed + if (!m_constraintsChanged) + return ERROR_NONE; + + if (!copy()) { + LOGM(Log::ERR, "Failed to copy cursor image for cursor share"); + callback(RESULT_NOT_COPIED); + m_pendingFrame.pending = false; + return ERROR_UNKNOWN; + } + + return ERROR_NONE; +} + +void CCursorshareSession::render() { + const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(m_client, PERMISSION_TYPE_CURSOR_POS); + + const auto& cursorImage = g_pPointerManager->currentCursorImage(); + + // TODO: implement a monitor independent render mode to buffer that does this in CHyprRenderer::begin() or something like that + g_pHyprOpenGL->m_renderData.transformDamage = false; + g_pHyprOpenGL->setViewport(0, 0, m_bufferSize.x, m_bufferSize.y); + + bool overlaps = g_pPointerManager->getCursorBoxGlobal().overlaps(m_pendingFrame.sourceBoxCallback()); + if (PERM != PERMISSION_RULE_ALLOW_MODE_ALLOW || !overlaps) { + // render black when not allowed + g_pHyprOpenGL->clear(Colors::BLACK); + } else if (!cursorImage.pBuffer || !cursorImage.surface || !cursorImage.bufferTex) { + // render clear when cursor is probably hidden + g_pHyprOpenGL->clear(CHyprColor(0, 0, 0, 0)); + } else { + // render cursor + CBox texbox = {{}, cursorImage.bufferTex->m_size}; + g_pHyprOpenGL->renderTexture(cursorImage.bufferTex, texbox, {}); + } + + g_pHyprOpenGL->m_renderData.blockScreenShader = true; +} + +bool CCursorshareSession::copy() { + if (!m_pendingFrame.callback || !m_pendingFrame.monitor || !m_pendingFrame.callback || !m_pendingFrame.sourceBoxCallback) + return false; + + // FIXME: this doesn't really make sense but just to be safe + m_pendingFrame.callback(RESULT_TIMESTAMP); + + g_pHyprRenderer->makeEGLCurrent(); + + CRegion fakeDamage = {0, 0, INT16_MAX, INT16_MAX}; + if (auto attrs = m_pendingFrame.buffer->dmabuf(); attrs.success) { + if (attrs.format != m_format) { + LOGM(Log::ERR, "Can't copy: invalid format"); + return false; + } + + if (!g_pHyprRenderer->beginRender(m_pendingFrame.monitor, fakeDamage, RENDER_MODE_TO_BUFFER, m_pendingFrame.buffer, nullptr, true)) { + LOGM(Log::ERR, "Can't copy: failed to begin rendering to dmabuf"); + return false; + } + + render(); + + g_pHyprRenderer->endRender([callback = m_pendingFrame.callback]() { + if (callback) + callback(RESULT_COPIED); + }); + } else if (auto attrs = m_pendingFrame.buffer->shm(); attrs.success) { + auto [bufData, fmt, bufLen] = m_pendingFrame.buffer->beginDataPtr(0); + const auto PFORMAT = NFormatUtils::getPixelFormatFromDRM(m_format); + + if (attrs.format != m_format || !PFORMAT) { + LOGM(Log::ERR, "Can't copy: invalid format"); + return false; + } + + CFramebuffer outFB; + outFB.alloc(m_bufferSize.x, m_bufferSize.y, m_format); + + if (!g_pHyprRenderer->beginRender(m_pendingFrame.monitor, fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, &outFB, true)) { + LOGM(Log::ERR, "Can't copy: failed to begin rendering to shm"); + return false; + } + + render(); + + g_pHyprRenderer->endRender(); + + g_pHyprOpenGL->m_renderData.pMonitor = m_pendingFrame.monitor; + outFB.bind(); + glBindFramebuffer(GL_READ_FRAMEBUFFER, outFB.getFBID()); + + glPixelStorei(GL_PACK_ALIGNMENT, 1); + + 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; + } + } + } + + glReadPixels(0, 0, m_bufferSize.x, m_bufferSize.y, glFormat, PFORMAT->glType, bufData); + + g_pHyprOpenGL->m_renderData.pMonitor.reset(); + + m_pendingFrame.buffer->endDataPtr(); + outFB.unbind(); + glPixelStorei(GL_PACK_ALIGNMENT, 4); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + + m_pendingFrame.callback(RESULT_COPIED); + } else { + LOGM(Log::ERR, "Can't copy: invalid buffer type"); + return false; + } + + m_pendingFrame.pending = false; + m_constraintsChanged = false; + return true; +} + +DRMFormat CCursorshareSession::format() const { + return m_format; +} + +Vector2D CCursorshareSession::bufferSize() const { + return m_bufferSize; +} + +Vector2D CCursorshareSession::hotspot() const { + return m_hotspot; +} |
