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
|
|
@ -96,6 +96,10 @@ Vector2D CPointerManager::position() {
|
|||
return m_pointerPos;
|
||||
}
|
||||
|
||||
Vector2D CPointerManager::hotspot() {
|
||||
return m_currentCursorImage.hotspot;
|
||||
}
|
||||
|
||||
bool CPointerManager::hasCursor() {
|
||||
return m_currentCursorImage.pBuffer || m_currentCursorImage.surface;
|
||||
}
|
||||
|
|
@ -115,6 +119,7 @@ void CPointerManager::setCursorBuffer(SP<Aquamarine::IBuffer> buf, const Vector2
|
|||
m_currentCursorImage.scale = scale;
|
||||
updateCursorBackend();
|
||||
damageIfSoftware();
|
||||
m_events.cursorChanged.emit();
|
||||
}
|
||||
|
||||
return;
|
||||
|
|
@ -132,6 +137,7 @@ void CPointerManager::setCursorBuffer(SP<Aquamarine::IBuffer> buf, const Vector2
|
|||
|
||||
updateCursorBackend();
|
||||
damageIfSoftware();
|
||||
m_events.cursorChanged.emit();
|
||||
}
|
||||
|
||||
void CPointerManager::setCursorSurface(SP<Desktop::View::CWLSurface> surf, const Vector2D& hotspot) {
|
||||
|
|
@ -143,6 +149,7 @@ void CPointerManager::setCursorSurface(SP<Desktop::View::CWLSurface> surf, const
|
|||
m_currentCursorImage.scale = surf && surf->resource() ? surf->resource()->m_current.scale : 1.F;
|
||||
updateCursorBackend();
|
||||
damageIfSoftware();
|
||||
m_events.cursorChanged.emit();
|
||||
}
|
||||
|
||||
return;
|
||||
|
|
@ -164,6 +171,7 @@ void CPointerManager::setCursorSurface(SP<Desktop::View::CWLSurface> surf, const
|
|||
recheckEnteredOutputs();
|
||||
updateCursorBackend();
|
||||
damageIfSoftware();
|
||||
m_events.cursorChanged.emit();
|
||||
});
|
||||
|
||||
if (surf->resource()->m_current.texture) {
|
||||
|
|
@ -177,6 +185,7 @@ void CPointerManager::setCursorSurface(SP<Desktop::View::CWLSurface> surf, const
|
|||
recheckEnteredOutputs();
|
||||
updateCursorBackend();
|
||||
damageIfSoftware();
|
||||
m_events.cursorChanged.emit();
|
||||
}
|
||||
|
||||
void CPointerManager::recheckEnteredOutputs() {
|
||||
|
|
@ -261,6 +270,8 @@ void CPointerManager::resetCursorImage(bool apply) {
|
|||
ms->cursorFrontBuffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
m_events.cursorChanged.emit();
|
||||
}
|
||||
|
||||
void CPointerManager::updateCursorBackend() {
|
||||
|
|
@ -888,6 +899,10 @@ void CPointerManager::onMonitorLayoutChange() {
|
|||
damageIfSoftware();
|
||||
}
|
||||
|
||||
const CPointerManager::SCursorImage& CPointerManager::currentCursorImage() {
|
||||
return m_currentCursorImage;
|
||||
}
|
||||
|
||||
SP<CTexture> CPointerManager::getCurrentCursorTexture() {
|
||||
if (!m_currentCursorImage.pBuffer && (!m_currentCursorImage.surface || !m_currentCursorImage.surface->resource()->m_current.texture))
|
||||
return nullptr;
|
||||
|
|
|
|||
|
|
@ -60,10 +60,34 @@ class CPointerManager {
|
|||
|
||||
//
|
||||
Vector2D position();
|
||||
Vector2D hotspot();
|
||||
Vector2D cursorSizeLogical();
|
||||
|
||||
void recheckEnteredOutputs();
|
||||
|
||||
// returns the thing in global coords
|
||||
CBox getCursorBoxGlobal();
|
||||
|
||||
struct SCursorImage {
|
||||
SP<Aquamarine::IBuffer> pBuffer;
|
||||
SP<CTexture> bufferTex;
|
||||
WP<Desktop::View::CWLSurface> surface;
|
||||
|
||||
Vector2D hotspot;
|
||||
Vector2D size;
|
||||
float scale = 1.F;
|
||||
|
||||
CHyprSignalListener destroySurface;
|
||||
CHyprSignalListener commitSurface;
|
||||
};
|
||||
|
||||
const SCursorImage& currentCursorImage();
|
||||
SP<CTexture> getCurrentCursorTexture();
|
||||
|
||||
struct {
|
||||
CSignalT<> cursorChanged;
|
||||
} m_events;
|
||||
|
||||
private:
|
||||
void recheckPointerPosition();
|
||||
void onMonitorLayoutChange();
|
||||
|
|
@ -79,13 +103,9 @@ class CPointerManager {
|
|||
// returns the thing in device coordinates. Is NOT offset by the hotspot, relies on set_cursor with hotspot.
|
||||
Vector2D getCursorPosForMonitor(PHLMONITOR pMonitor);
|
||||
// returns the thing in logical coordinates of the monitor
|
||||
CBox getCursorBoxLogicalForMonitor(PHLMONITOR pMonitor);
|
||||
// returns the thing in global coords
|
||||
CBox getCursorBoxGlobal();
|
||||
CBox getCursorBoxLogicalForMonitor(PHLMONITOR pMonitor);
|
||||
|
||||
Vector2D transformedHotspot(PHLMONITOR pMonitor);
|
||||
|
||||
SP<CTexture> getCurrentCursorTexture();
|
||||
Vector2D transformedHotspot(PHLMONITOR pMonitor);
|
||||
|
||||
struct SPointerListener {
|
||||
CHyprSignalListener destroy;
|
||||
|
|
@ -137,20 +157,9 @@ class CPointerManager {
|
|||
std::vector<CBox> monitorBoxes;
|
||||
} m_currentMonitorLayout;
|
||||
|
||||
struct {
|
||||
SP<Aquamarine::IBuffer> pBuffer;
|
||||
SP<CTexture> bufferTex;
|
||||
WP<Desktop::View::CWLSurface> surface;
|
||||
SCursorImage m_currentCursorImage; // TODO: support various sizes per-output so we can have pixel-perfect cursors
|
||||
|
||||
Vector2D hotspot;
|
||||
Vector2D size;
|
||||
float scale = 1.F;
|
||||
|
||||
CHyprSignalListener destroySurface;
|
||||
CHyprSignalListener commitSurface;
|
||||
} m_currentCursorImage; // TODO: support various sizes per-output so we can have pixel-perfect cursors
|
||||
|
||||
Vector2D m_pointerPos = {0, 0};
|
||||
Vector2D m_pointerPos = {0, 0};
|
||||
|
||||
struct SMonitorPointerState {
|
||||
SMonitorPointerState(const PHLMONITOR& m) : monitor(m) {}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,8 @@
|
|||
#include "../protocols/SecurityContext.hpp"
|
||||
#include "../protocols/CTMControl.hpp"
|
||||
#include "../protocols/HyprlandSurface.hpp"
|
||||
#include "../protocols/ImageCaptureSource.hpp"
|
||||
#include "../protocols/ImageCopyCapture.hpp"
|
||||
#include "../protocols/core/Seat.hpp"
|
||||
#include "../protocols/core/DataDevice.hpp"
|
||||
#include "../protocols/core/Compositor.hpp"
|
||||
|
|
@ -65,6 +67,7 @@
|
|||
#include "../protocols/PointerWarp.hpp"
|
||||
#include "../protocols/Fifo.hpp"
|
||||
#include "../protocols/CommitTiming.hpp"
|
||||
#include "HookSystemManager.hpp"
|
||||
|
||||
#include "../helpers/Monitor.hpp"
|
||||
#include "../render/Renderer.hpp"
|
||||
|
|
@ -180,8 +183,6 @@ CProtocolManager::CProtocolManager() {
|
|||
PROTO::dataWlr = makeUnique<CDataDeviceWLRProtocol>(&zwlr_data_control_manager_v1_interface, 2, "DataDeviceWlr");
|
||||
PROTO::primarySelection = makeUnique<CPrimarySelectionProtocol>(&zwp_primary_selection_device_manager_v1_interface, 1, "PrimarySelection");
|
||||
PROTO::xwaylandShell = makeUnique<CXWaylandShellProtocol>(&xwayland_shell_v1_interface, 1, "XWaylandShell");
|
||||
PROTO::screencopy = makeUnique<CScreencopyProtocol>(&zwlr_screencopy_manager_v1_interface, 3, "Screencopy");
|
||||
PROTO::toplevelExport = makeUnique<CToplevelExportProtocol>(&hyprland_toplevel_export_manager_v1_interface, 2, "ToplevelExport");
|
||||
PROTO::toplevelMapping = makeUnique<CToplevelMappingProtocol>(&hyprland_toplevel_mapping_manager_v1_interface, 1, "ToplevelMapping");
|
||||
PROTO::globalShortcuts = makeUnique<CGlobalShortcutsProtocol>(&hyprland_global_shortcuts_manager_v1_interface, 1, "GlobalShortcuts");
|
||||
PROTO::xdgDialog = makeUnique<CXDGDialogProtocol>(&xdg_wm_dialog_v1_interface, 1, "XDGDialog");
|
||||
|
|
@ -200,6 +201,12 @@ CProtocolManager::CProtocolManager() {
|
|||
if (*PENABLECT)
|
||||
PROTO::commitTiming = makeUnique<CCommitTimingProtocol>(&wp_commit_timing_manager_v1_interface, 1, "CommitTiming");
|
||||
|
||||
// Screensharing Protocols
|
||||
PROTO::screencopy = makeUnique<CScreencopyProtocol>(&zwlr_screencopy_manager_v1_interface, 3, "Screencopy");
|
||||
PROTO::toplevelExport = makeUnique<CToplevelExportProtocol>(&hyprland_toplevel_export_manager_v1_interface, 2, "ToplevelExport");
|
||||
PROTO::imageCaptureSource = makeUnique<CImageCaptureSourceProtocol>(); // ctor inits actual protos, output and toplevel
|
||||
PROTO::imageCopyCapture = makeUnique<CImageCopyCaptureProtocol>(&ext_image_copy_capture_manager_v1_interface, 1, "ImageCopyCapture");
|
||||
|
||||
if (*PENABLECM)
|
||||
PROTO::colorManagement = makeUnique<CColorManagementProtocol>(&wp_color_manager_v1_interface, 1, "ColorManagement", *PDEBUGCM);
|
||||
|
||||
|
|
@ -298,6 +305,7 @@ CProtocolManager::~CProtocolManager() {
|
|||
PROTO::pointerWarp.reset();
|
||||
PROTO::fifo.reset();
|
||||
PROTO::commitTiming.reset();
|
||||
PROTO::imageCaptureSource.reset();
|
||||
|
||||
for (auto& [_, lease] : PROTO::lease) {
|
||||
lease.reset();
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ static const char* permissionToString(eDynamicPermissionType type) {
|
|||
case PERMISSION_TYPE_SCREENCOPY: return "PERMISSION_TYPE_SCREENCOPY";
|
||||
case PERMISSION_TYPE_PLUGIN: return "PERMISSION_TYPE_PLUGIN";
|
||||
case PERMISSION_TYPE_KEYBOARD: return "PERMISSION_TYPE_KEYBOARD";
|
||||
case PERMISSION_TYPE_CURSOR_POS: return "PERMISSION_TYPE_CURSOR_POS";
|
||||
}
|
||||
|
||||
return "error";
|
||||
|
|
@ -251,6 +252,7 @@ void CDynamicPermissionManager::askForPermission(wl_client* client, const std::s
|
|||
std::string description = "";
|
||||
switch (rule->m_type) {
|
||||
case PERMISSION_TYPE_SCREENCOPY: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, {{"app", appName}}); break;
|
||||
case PERMISSION_TYPE_CURSOR_POS: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_CURSOR_POS, {{"app", appName}}); break;
|
||||
case PERMISSION_TYPE_PLUGIN: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_PLUGIN, {{"app", appName}, {"plugin", binaryPath}}); break;
|
||||
case PERMISSION_TYPE_KEYBOARD: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_KEYBOARD, {{"keyboard", binaryPath}}); break;
|
||||
case PERMISSION_TYPE_UNKNOWN: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_UNKNOWN, {{"app", appName}}); break;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ enum eDynamicPermissionType : uint8_t {
|
|||
PERMISSION_TYPE_SCREENCOPY,
|
||||
PERMISSION_TYPE_PLUGIN,
|
||||
PERMISSION_TYPE_KEYBOARD,
|
||||
PERMISSION_TYPE_CURSOR_POS,
|
||||
};
|
||||
|
||||
enum eDynamicPermissionRuleSource : uint8_t {
|
||||
|
|
@ -104,4 +105,4 @@ class CDynamicPermissionManager {
|
|||
std::vector<SP<CDynamicPermissionRule>> m_rules;
|
||||
};
|
||||
|
||||
inline UP<CDynamicPermissionManager> g_pDynamicPermissionManager;
|
||||
inline UP<CDynamicPermissionManager> g_pDynamicPermissionManager;
|
||||
|
|
|
|||
240
src/managers/screenshare/CursorshareSession.cpp
Normal file
240
src/managers/screenshare/CursorshareSession.cpp
Normal file
|
|
@ -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;
|
||||
}
|
||||
493
src/managers/screenshare/ScreenshareFrame.cpp
Normal file
493
src/managers/screenshare/ScreenshareFrame.cpp
Normal file
|
|
@ -0,0 +1,493 @@
|
|||
#include "ScreenshareManager.hpp"
|
||||
#include "../PointerManager.hpp"
|
||||
#include "../input/InputManager.hpp"
|
||||
#include "../permissions/DynamicPermissionManager.hpp"
|
||||
#include "../../protocols/ColorManagement.hpp"
|
||||
#include "../../protocols/XDGShell.hpp"
|
||||
#include "../../Compositor.hpp"
|
||||
#include "../../render/Renderer.hpp"
|
||||
#include "../../render/OpenGL.hpp"
|
||||
#include "../../helpers/Monitor.hpp"
|
||||
#include "../../desktop/view/Window.hpp"
|
||||
#include "../../desktop/state/FocusState.hpp"
|
||||
|
||||
using namespace Screenshare;
|
||||
|
||||
CScreenshareFrame::CScreenshareFrame(WP<CScreenshareSession> session, bool overlayCursor, bool isFirst) :
|
||||
m_session(session), m_bufferSize(m_session->bufferSize()), m_overlayCursor(overlayCursor), m_isFirst(isFirst) {
|
||||
;
|
||||
}
|
||||
|
||||
CScreenshareFrame::~CScreenshareFrame() {
|
||||
if (m_failed || !m_shared)
|
||||
return;
|
||||
|
||||
if (!m_copied && m_callback)
|
||||
m_callback(RESULT_NOT_COPIED);
|
||||
}
|
||||
|
||||
bool CScreenshareFrame::done() const {
|
||||
if (m_session.expired() || m_session->m_stopped)
|
||||
return true;
|
||||
|
||||
if (m_session->m_type == SHARE_NONE || m_bufferSize == Vector2D(0, 0))
|
||||
return true;
|
||||
|
||||
if (m_failed || m_copied)
|
||||
return true;
|
||||
|
||||
if (m_session->m_type == SHARE_MONITOR && !m_session->monitor())
|
||||
return true;
|
||||
|
||||
if (m_session->m_type == SHARE_REGION && !m_session->monitor())
|
||||
return true;
|
||||
|
||||
if (m_session->m_type == SHARE_WINDOW && (!m_session->monitor() || !validMapped(m_session->m_window)))
|
||||
return true;
|
||||
|
||||
if (!m_shared)
|
||||
return false;
|
||||
|
||||
if (!m_buffer || !m_buffer->m_resource || !m_buffer->m_resource->good())
|
||||
return true;
|
||||
|
||||
if (!m_callback)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
eScreenshareError CScreenshareFrame::share(SP<IHLBuffer> buffer, const CRegion& clientDamage, FScreenshareCallback callback) {
|
||||
if UNLIKELY (done())
|
||||
return ERROR_STOPPED;
|
||||
|
||||
if UNLIKELY (!m_session->monitor() || !g_pCompositor->monitorExists(m_session->monitor())) {
|
||||
LOGM(Log::ERR, "Client requested sharing of a monitor that is gone");
|
||||
m_failed = true;
|
||||
return ERROR_STOPPED;
|
||||
}
|
||||
|
||||
if UNLIKELY (m_session->m_type == SHARE_WINDOW && !validMapped(m_session->m_window)) {
|
||||
LOGM(Log::ERR, "Client requested sharing of window that is gone or not shareable!");
|
||||
m_failed = true;
|
||||
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 (std::ranges::count_if(m_session->allowedFormats(), [&](const DRMFormat& format) { return format == bufFormat; }) == 0) {
|
||||
LOGM(Log::ERR, "Invalid format {} in {:x}", bufFormat, (uintptr_t)this);
|
||||
return ERROR_BUFFER_FORMAT;
|
||||
}
|
||||
|
||||
m_buffer = buffer;
|
||||
m_callback = callback;
|
||||
m_shared = true;
|
||||
|
||||
// schedule a frame so that when a screenshare starts it isn't black until the output is updated
|
||||
if (m_isFirst) {
|
||||
g_pCompositor->scheduleFrameForMonitor(m_session->monitor(), Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME);
|
||||
g_pHyprRenderer->damageMonitor(m_session->monitor());
|
||||
}
|
||||
|
||||
// TODO: add a damage ring for output damage since last shared frame
|
||||
CRegion frameDamage = CRegion(0, 0, m_bufferSize.x, m_bufferSize.y);
|
||||
|
||||
// copy everything on the first frame
|
||||
if (m_isFirst)
|
||||
m_damage = CRegion(0, 0, m_bufferSize.x, m_bufferSize.y);
|
||||
else
|
||||
m_damage = frameDamage.add(clientDamage);
|
||||
|
||||
m_damage.intersect(0, 0, m_bufferSize.x, m_bufferSize.y);
|
||||
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
void CScreenshareFrame::copy() {
|
||||
if (done())
|
||||
return;
|
||||
|
||||
// tell client to send presented timestamp
|
||||
// TODO: is this right? this is right after we commit to aq, not when page flip happens..
|
||||
m_callback(RESULT_TIMESTAMP);
|
||||
|
||||
// store a snapshot before the permission popup so we don't break screenshots
|
||||
const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(m_session->m_client, PERMISSION_TYPE_SCREENCOPY);
|
||||
if (PERM == PERMISSION_RULE_ALLOW_MODE_PENDING) {
|
||||
if (!m_session->m_tempFB.isAllocated())
|
||||
storeTempFB();
|
||||
|
||||
// don't copy a frame while allow is pending because screenshot tools will only take the first frame we give, which is empty
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_buffer->shm().success)
|
||||
m_failed = !copyShm();
|
||||
else if (m_buffer->dmabuf().success)
|
||||
m_failed = !copyDmabuf();
|
||||
|
||||
if (!m_failed) {
|
||||
// screensharing has started again
|
||||
m_session->screenshareEvents(true);
|
||||
m_session->m_shareStopTimer->updateTimeout(std::chrono::milliseconds(500)); // check in half second
|
||||
} else
|
||||
m_callback(RESULT_NOT_COPIED);
|
||||
}
|
||||
|
||||
void CScreenshareFrame::renderMonitor() {
|
||||
if ((m_session->m_type != SHARE_MONITOR && m_session->m_type != SHARE_REGION) || done())
|
||||
return;
|
||||
|
||||
const auto PMONITOR = m_session->monitor();
|
||||
|
||||
auto TEXTURE = makeShared<CTexture>(PMONITOR->m_output->state->state().buffer);
|
||||
|
||||
const bool IS_CM_AWARE = PROTO::colorManagement && PROTO::colorManagement->isClientCMAware(m_session->m_client);
|
||||
g_pHyprOpenGL->m_renderData.transformDamage = false;
|
||||
g_pHyprOpenGL->m_renderData.noSimplify = true;
|
||||
|
||||
// render monitor texture
|
||||
CBox monbox = CBox{{}, PMONITOR->m_pixelSize}
|
||||
.transform(Math::wlTransformToHyprutils(Math::invertTransform(PMONITOR->m_transform)), PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y)
|
||||
.translate(-m_session->m_captureBox.pos()); // vvvv kinda ass-backwards but that's how I designed the renderer... sigh.
|
||||
g_pHyprOpenGL->pushMonitorTransformEnabled(true);
|
||||
g_pHyprOpenGL->setRenderModifEnabled(false);
|
||||
g_pHyprOpenGL->renderTexture(TEXTURE, monbox,
|
||||
{
|
||||
.cmBackToSRGB = !IS_CM_AWARE,
|
||||
.cmBackToSRGBSource = !IS_CM_AWARE ? PMONITOR : nullptr,
|
||||
});
|
||||
g_pHyprOpenGL->setRenderModifEnabled(true);
|
||||
g_pHyprOpenGL->popMonitorTransformEnabled();
|
||||
|
||||
// render black boxes for noscreenshare
|
||||
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(PMONITOR->m_position).scale(PMONITOR->m_scale).translate(-m_session->m_captureBox.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(-PMONITOR->m_position)
|
||||
.scale(PMONITOR->m_scale)
|
||||
.translate(-m_session->m_captureBox.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, PMONITOR))
|
||||
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(-PMONITOR->m_position)
|
||||
.scale(PMONITOR->m_scale)
|
||||
.translate(-m_session->m_captureBox.pos());
|
||||
|
||||
// seems like rounding doesn't play well with how we manipulate the box position to render regions causing the window to leak through
|
||||
const auto dontRound = m_session->m_captureBox.pos() != Vector2D() || w->isEffectiveInternalFSMode(FSMODE_FULLSCREEN);
|
||||
const auto rounding = dontRound ? 0 : w->rounding() * PMONITOR->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) {
|
||||
CRegion fakeDamage = {0, 0, INT16_MAX, INT16_MAX};
|
||||
Vector2D cursorPos = g_pInputManager->getMouseCoordsInternal() - PMONITOR->m_position - m_session->m_captureBox.pos() / PMONITOR->m_scale;
|
||||
g_pPointerManager->renderSoftwareCursorsFor(PMONITOR, Time::steadyNow(), fakeDamage, cursorPos, true);
|
||||
}
|
||||
}
|
||||
|
||||
void CScreenshareFrame::renderWindow() {
|
||||
if (m_session->m_type != SHARE_WINDOW || done())
|
||||
return;
|
||||
|
||||
const auto PWINDOW = m_session->m_window.lock();
|
||||
const auto PMONITOR = m_session->monitor();
|
||||
|
||||
const auto NOW = Time::steadyNow();
|
||||
|
||||
// TODO: implement a monitor independent render mode to buffer that does this in CHyprRenderer::begin() or something like that
|
||||
g_pHyprOpenGL->m_renderData.monitorProjection = Mat3x3::identity();
|
||||
g_pHyprOpenGL->m_renderData.projection = Mat3x3::outputProjection(m_bufferSize, HYPRUTILS_TRANSFORM_NORMAL);
|
||||
g_pHyprOpenGL->m_renderData.transformDamage = false;
|
||||
g_pHyprOpenGL->setViewport(0, 0, m_bufferSize.x, m_bufferSize.y);
|
||||
|
||||
g_pHyprRenderer->m_bBlockSurfaceFeedback = g_pHyprRenderer->shouldRenderWindow(PWINDOW); // block the feedback to avoid spamming the surface if it's visible
|
||||
g_pHyprRenderer->renderWindow(PWINDOW, PMONITOR, NOW, false, RENDER_PASS_ALL, true, true);
|
||||
g_pHyprRenderer->m_bBlockSurfaceFeedback = false;
|
||||
|
||||
if (!m_overlayCursor)
|
||||
return;
|
||||
|
||||
auto pointerSurfaceResource = g_pSeatManager->m_state.pointerFocus.lock();
|
||||
|
||||
if (!pointerSurfaceResource)
|
||||
return;
|
||||
|
||||
auto pointerSurface = Desktop::View::CWLSurface::fromResource(pointerSurfaceResource);
|
||||
|
||||
if (!pointerSurface || pointerSurface->getSurfaceBoxGlobal()->intersection(m_session->m_window->getFullWindowBoundingBox()).empty())
|
||||
return;
|
||||
|
||||
if (Desktop::focusState()->window() != m_session->m_window)
|
||||
return;
|
||||
|
||||
CRegion fakeDamage = {0, 0, INT16_MAX, INT16_MAX};
|
||||
g_pPointerManager->renderSoftwareCursorsFor(PMONITOR->m_self.lock(), NOW, fakeDamage, g_pInputManager->getMouseCoordsInternal() - PWINDOW->m_realPosition->value(), true);
|
||||
}
|
||||
|
||||
void CScreenshareFrame::render() {
|
||||
const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(m_session->m_client, PERMISSION_TYPE_SCREENCOPY);
|
||||
|
||||
if (PERM == PERMISSION_RULE_ALLOW_MODE_PENDING) {
|
||||
g_pHyprOpenGL->clear(CHyprColor(0, 0, 0, 0));
|
||||
return;
|
||||
}
|
||||
|
||||
bool windowShareDenied = m_session->m_type == SHARE_WINDOW && m_session->m_window->m_ruleApplicator && m_session->m_window->m_ruleApplicator->noScreenShare().valueOrDefault();
|
||||
if (PERM == PERMISSION_RULE_ALLOW_MODE_DENY || windowShareDenied) {
|
||||
g_pHyprOpenGL->clear(CHyprColor(0, 0, 0, 0));
|
||||
CBox texbox = CBox{m_bufferSize / 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, {});
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_session->m_tempFB.isAllocated()) {
|
||||
CBox texbox = {{}, m_bufferSize};
|
||||
g_pHyprOpenGL->renderTexture(m_session->m_tempFB.getTexture(), texbox, {});
|
||||
m_session->m_tempFB.release();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (m_session->m_type) {
|
||||
case SHARE_REGION: // TODO: could this be better? this is how screencopy works
|
||||
case SHARE_MONITOR: renderMonitor(); break;
|
||||
case SHARE_WINDOW: renderWindow(); break;
|
||||
case SHARE_NONE:
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
|
||||
bool CScreenshareFrame::copyDmabuf() {
|
||||
if (done())
|
||||
return false;
|
||||
|
||||
if (!g_pHyprRenderer->beginRender(m_session->monitor(), m_damage, RENDER_MODE_TO_BUFFER, m_buffer, nullptr, true)) {
|
||||
LOGM(Log::ERR, "Can't copy: failed to begin rendering to dma frame");
|
||||
return false;
|
||||
}
|
||||
|
||||
render();
|
||||
|
||||
g_pHyprOpenGL->m_renderData.blockScreenShader = true;
|
||||
|
||||
g_pHyprRenderer->endRender([self = m_self]() {
|
||||
if (!self || self.expired() || self->m_copied)
|
||||
return;
|
||||
|
||||
LOGM(Log::TRACE, "Copied frame via dma");
|
||||
self->m_callback(RESULT_COPIED);
|
||||
self->m_copied = true;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CScreenshareFrame::copyShm() {
|
||||
if (done())
|
||||
return false;
|
||||
|
||||
g_pHyprRenderer->makeEGLCurrent();
|
||||
|
||||
auto shm = m_buffer->shm();
|
||||
auto [pixelData, fmt, bufLen] = m_buffer->beginDataPtr(0); // no need for end, cuz it's shm
|
||||
|
||||
const auto PFORMAT = NFormatUtils::getPixelFormatFromDRM(shm.format);
|
||||
if (!PFORMAT) {
|
||||
LOGM(Log::ERR, "Can't copy: failed to find a pixel format");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto PMONITOR = m_session->monitor();
|
||||
|
||||
CFramebuffer outFB;
|
||||
outFB.alloc(m_bufferSize.x, m_bufferSize.y, shm.format);
|
||||
|
||||
if (!g_pHyprRenderer->beginRender(PMONITOR, m_damage, RENDER_MODE_FULL_FAKE, nullptr, &outFB, true)) {
|
||||
LOGM(Log::ERR, "Can't copy: failed to begin rendering");
|
||||
return false;
|
||||
}
|
||||
|
||||
render();
|
||||
|
||||
g_pHyprOpenGL->m_renderData.blockScreenShader = true;
|
||||
|
||||
g_pHyprRenderer->endRender();
|
||||
|
||||
g_pHyprOpenGL->m_renderData.pMonitor = PMONITOR;
|
||||
outFB.bind();
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, outFB.getFBID());
|
||||
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
||||
|
||||
uint32_t packStride = NFormatUtils::minStride(PFORMAT, m_bufferSize.x);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: use pixel buffer object to not block cpu
|
||||
if (packStride == sc<uint32_t>(shm.stride)) {
|
||||
m_damage.forEachRect([&](const auto& rect) {
|
||||
int width = rect.x2 - rect.x1;
|
||||
int height = rect.y2 - rect.y1;
|
||||
glReadPixels(rect.x1, rect.y1, width, height, glFormat, PFORMAT->glType, pixelData);
|
||||
});
|
||||
} else {
|
||||
m_damage.forEachRect([&](const auto& rect) {
|
||||
size_t width = rect.x2 - rect.x1;
|
||||
size_t height = rect.y2 - rect.y1;
|
||||
for (size_t i = rect.y1; i < height; ++i) {
|
||||
glReadPixels(rect.x1, i, width, 1, glFormat, PFORMAT->glType, pixelData + (rect.x1 * PFORMAT->bytesPerBlock) + (i * shm.stride));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
outFB.unbind();
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
||||
|
||||
g_pHyprOpenGL->m_renderData.pMonitor.reset();
|
||||
|
||||
if (!m_copied) {
|
||||
LOGM(Log::TRACE, "Copied frame via shm");
|
||||
m_callback(RESULT_COPIED);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CScreenshareFrame::storeTempFB() {
|
||||
g_pHyprRenderer->makeEGLCurrent();
|
||||
|
||||
m_session->m_tempFB.alloc(m_bufferSize.x, m_bufferSize.y);
|
||||
|
||||
CRegion fakeDamage = {0, 0, INT16_MAX, INT16_MAX};
|
||||
|
||||
if (!g_pHyprRenderer->beginRender(m_session->monitor(), fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, &m_session->m_tempFB, true)) {
|
||||
LOGM(Log::ERR, "Can't copy: failed to begin rendering to temp fb");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (m_session->m_type) {
|
||||
case SHARE_REGION: // TODO: could this be better? this is how screencopy works
|
||||
case SHARE_MONITOR: renderMonitor(); break;
|
||||
case SHARE_WINDOW: renderWindow(); break;
|
||||
case SHARE_NONE:
|
||||
default: return;
|
||||
}
|
||||
|
||||
g_pHyprRenderer->endRender();
|
||||
}
|
||||
|
||||
Vector2D CScreenshareFrame::bufferSize() const {
|
||||
return m_bufferSize;
|
||||
}
|
||||
|
||||
wl_output_transform CScreenshareFrame::transform() const {
|
||||
switch (m_session->m_type) {
|
||||
case SHARE_REGION:
|
||||
case SHARE_MONITOR: return m_session->monitor()->m_transform;
|
||||
default:
|
||||
case SHARE_WINDOW: return WL_OUTPUT_TRANSFORM_NORMAL;
|
||||
}
|
||||
}
|
||||
|
||||
const CRegion& CScreenshareFrame::damage() const {
|
||||
return m_damage;
|
||||
}
|
||||
161
src/managers/screenshare/ScreenshareManager.cpp
Normal file
161
src/managers/screenshare/ScreenshareManager.cpp
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
#include "ScreenshareManager.hpp"
|
||||
#include "../../render/Renderer.hpp"
|
||||
#include "../../Compositor.hpp"
|
||||
#include "../../desktop/view/Window.hpp"
|
||||
#include "../../protocols/core/Seat.hpp"
|
||||
|
||||
using namespace Screenshare;
|
||||
|
||||
CScreenshareManager::CScreenshareManager() {
|
||||
;
|
||||
}
|
||||
|
||||
void CScreenshareManager::onOutputCommit(PHLMONITOR monitor) {
|
||||
std::erase_if(m_sessions, [&](const WP<CScreenshareSession>& session) { return session.expired(); });
|
||||
|
||||
// if no pending frames, and no sessions are sharing, then unblock ds
|
||||
if (m_pendingFrames.empty()) {
|
||||
for (const auto& session : m_sessions) {
|
||||
if (!session->m_stopped && session->m_sharing)
|
||||
return;
|
||||
}
|
||||
|
||||
g_pHyprRenderer->m_directScanoutBlocked = false;
|
||||
return; // nothing to share
|
||||
}
|
||||
|
||||
std::ranges::for_each(m_pendingFrames, [&](WP<CScreenshareFrame>& frame) {
|
||||
if (frame.expired() || !frame->m_shared || frame->done())
|
||||
return;
|
||||
|
||||
if (frame->m_session->monitor() != monitor)
|
||||
return;
|
||||
|
||||
if (frame->m_session->m_type == SHARE_WINDOW) {
|
||||
CBox geometry = {frame->m_session->m_window->m_realPosition->value(), frame->m_session->m_window->m_realSize->value()};
|
||||
if (geometry.intersection({monitor->m_position, monitor->m_size}).empty())
|
||||
return;
|
||||
}
|
||||
|
||||
frame->copy();
|
||||
});
|
||||
|
||||
std::erase_if(m_pendingFrames, [&](const WP<CScreenshareFrame>& frame) { return frame.expired(); });
|
||||
}
|
||||
|
||||
UP<CScreenshareSession> CScreenshareManager::newSession(wl_client* client, PHLMONITOR monitor) {
|
||||
if UNLIKELY (!monitor || !g_pCompositor->monitorExists(monitor)) {
|
||||
LOGM(Log::ERR, "Client requested sharing of a monitor that is gone");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UP<CScreenshareSession> session = UP<CScreenshareSession>(new CScreenshareSession(monitor, client));
|
||||
|
||||
session->m_self = session;
|
||||
m_sessions.emplace_back(session);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
UP<CScreenshareSession> CScreenshareManager::newSession(wl_client* client, PHLMONITOR monitor, CBox captureRegion) {
|
||||
if UNLIKELY (!monitor || !g_pCompositor->monitorExists(monitor)) {
|
||||
LOGM(Log::ERR, "Client requested sharing of a monitor that is gone");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UP<CScreenshareSession> session = UP<CScreenshareSession>(new CScreenshareSession(monitor, captureRegion, client));
|
||||
|
||||
session->m_self = session;
|
||||
m_sessions.emplace_back(session);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
UP<CScreenshareSession> CScreenshareManager::newSession(wl_client* client, PHLWINDOW window) {
|
||||
if UNLIKELY (!window || !window->m_isMapped) {
|
||||
LOGM(Log::ERR, "Client requested sharing of window that is gone or not shareable!");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UP<CScreenshareSession> session = UP<CScreenshareSession>(new CScreenshareSession(window, client));
|
||||
|
||||
session->m_self = session;
|
||||
m_sessions.emplace_back(session);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
UP<CCursorshareSession> CScreenshareManager::newCursorSession(wl_client* client, WP<CWLPointerResource> pointer) {
|
||||
UP<CCursorshareSession> session = UP<CCursorshareSession>(new CCursorshareSession(client, pointer));
|
||||
|
||||
session->m_self = session;
|
||||
m_cursorSessions.emplace_back(session);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
WP<CScreenshareSession> CScreenshareManager::getManagedSession(wl_client* client, PHLMONITOR monitor) {
|
||||
return getManagedSession(SHARE_MONITOR, client, monitor, nullptr, {});
|
||||
}
|
||||
|
||||
WP<CScreenshareSession> CScreenshareManager::getManagedSession(wl_client* client, PHLMONITOR monitor, CBox captureBox) {
|
||||
|
||||
return getManagedSession(SHARE_REGION, client, monitor, nullptr, captureBox);
|
||||
}
|
||||
|
||||
WP<CScreenshareSession> CScreenshareManager::getManagedSession(wl_client* client, PHLWINDOW window) {
|
||||
return getManagedSession(SHARE_WINDOW, client, nullptr, window, {});
|
||||
}
|
||||
|
||||
WP<CScreenshareSession> CScreenshareManager::getManagedSession(eScreenshareType type, wl_client* client, PHLMONITOR monitor, PHLWINDOW window, CBox captureBox) {
|
||||
if (type == SHARE_NONE)
|
||||
return {};
|
||||
|
||||
auto it = std::ranges::find_if(m_managedSessions, [&](const auto& session) {
|
||||
if (session->m_session->m_client != client || session->m_session->m_type != type)
|
||||
return false;
|
||||
|
||||
switch (type) {
|
||||
case SHARE_MONITOR: return session->m_session->m_monitor == monitor;
|
||||
case SHARE_WINDOW: return session->m_session->m_window == window;
|
||||
case SHARE_REGION: return session->m_session->m_monitor == monitor && session->m_session->m_captureBox == captureBox;
|
||||
case SHARE_NONE:
|
||||
default: return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if (it == m_managedSessions.end()) {
|
||||
UP<CScreenshareSession> session;
|
||||
switch (type) {
|
||||
case SHARE_MONITOR: session = UP<CScreenshareSession>(new CScreenshareSession(monitor, client)); break;
|
||||
case SHARE_WINDOW: session = UP<CScreenshareSession>(new CScreenshareSession(window, client)); break;
|
||||
case SHARE_REGION: session = UP<CScreenshareSession>(new CScreenshareSession(monitor, captureBox, client)); break;
|
||||
case SHARE_NONE:
|
||||
default: return {};
|
||||
}
|
||||
|
||||
session->m_self = session;
|
||||
m_sessions.emplace_back(session);
|
||||
|
||||
it = m_managedSessions.emplace(m_managedSessions.end(), makeUnique<SManagedSession>(std::move(session)));
|
||||
}
|
||||
|
||||
auto& session = *it;
|
||||
|
||||
session->stoppedListener = session->m_session->m_events.stopped.listen([session = WP<SManagedSession>(session)]() {
|
||||
std::erase_if(Screenshare::mgr()->m_managedSessions, [&](const auto& s) { return !s || session.expired() || s->m_session == session->m_session; });
|
||||
});
|
||||
|
||||
return session->m_session;
|
||||
}
|
||||
|
||||
void CScreenshareManager::destroyClientSessions(wl_client* client) {
|
||||
LOGM(Log::TRACE, "Destroy client sessions for {:x}", (uintptr_t)client);
|
||||
std::erase_if(m_managedSessions, [&](const auto& session) { return !session || session->m_session->m_client == client; });
|
||||
}
|
||||
|
||||
CScreenshareManager::SManagedSession::SManagedSession(UP<CScreenshareSession>&& session) : m_session(std::move(session)) {
|
||||
;
|
||||
}
|
||||
252
src/managers/screenshare/ScreenshareManager.hpp
Normal file
252
src/managers/screenshare/ScreenshareManager.hpp
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "../../helpers/memory/Memory.hpp"
|
||||
#include "../../protocols/types/Buffer.hpp"
|
||||
#include "../../render/Framebuffer.hpp"
|
||||
#include "../eventLoop/EventLoopTimer.hpp"
|
||||
#include "../../render/Renderer.hpp"
|
||||
|
||||
// TODO: do screenshare damage
|
||||
|
||||
class CWLPointerResource;
|
||||
|
||||
namespace Screenshare {
|
||||
enum eScreenshareType : uint8_t {
|
||||
SHARE_MONITOR,
|
||||
SHARE_WINDOW,
|
||||
SHARE_REGION,
|
||||
SHARE_NONE
|
||||
};
|
||||
|
||||
enum eScreenshareError : uint8_t {
|
||||
ERROR_NONE,
|
||||
ERROR_UNKNOWN,
|
||||
ERROR_STOPPED,
|
||||
ERROR_NO_BUFFER,
|
||||
ERROR_BUFFER_SIZE,
|
||||
ERROR_BUFFER_FORMAT
|
||||
};
|
||||
|
||||
enum eScreenshareResult : uint8_t {
|
||||
RESULT_COPIED,
|
||||
RESULT_NOT_COPIED,
|
||||
RESULT_TIMESTAMP,
|
||||
};
|
||||
|
||||
using FScreenshareCallback = std::function<void(eScreenshareResult result)>;
|
||||
using FSourceBoxCallback = std::function<CBox(void)>;
|
||||
|
||||
class CScreenshareSession {
|
||||
public:
|
||||
CScreenshareSession(const CScreenshareSession&) = delete;
|
||||
CScreenshareSession(CScreenshareSession&&) = delete;
|
||||
~CScreenshareSession();
|
||||
|
||||
UP<CScreenshareFrame> nextFrame(bool overlayCursor);
|
||||
void stop();
|
||||
|
||||
// constraints
|
||||
const std::vector<DRMFormat>& allowedFormats() const;
|
||||
Vector2D bufferSize() const;
|
||||
PHLMONITOR monitor() const; // this will return the correct monitor based on type
|
||||
|
||||
struct {
|
||||
CSignalT<> stopped;
|
||||
CSignalT<> constraintsChanged;
|
||||
} m_events;
|
||||
|
||||
private:
|
||||
CScreenshareSession(PHLMONITOR monitor, wl_client* client);
|
||||
CScreenshareSession(PHLMONITOR monitor, CBox captureRegion, wl_client* client);
|
||||
CScreenshareSession(PHLWINDOW window, wl_client* client);
|
||||
|
||||
WP<CScreenshareSession> m_self;
|
||||
bool m_stopped = false;
|
||||
|
||||
eScreenshareType m_type = SHARE_NONE;
|
||||
PHLMONITORREF m_monitor;
|
||||
PHLWINDOWREF m_window;
|
||||
CBox m_captureBox = {}; // given capture area in logical coordinates (see xdg_output)
|
||||
|
||||
wl_client* m_client = nullptr;
|
||||
std::string m_name = "";
|
||||
|
||||
std::vector<DRMFormat> m_formats;
|
||||
Vector2D m_bufferSize = Vector2D(0, 0);
|
||||
|
||||
CFramebuffer m_tempFB;
|
||||
|
||||
SP<CEventLoopTimer> m_shareStopTimer;
|
||||
bool m_sharing = false;
|
||||
|
||||
struct {
|
||||
CHyprSignalListener monitorDestroyed;
|
||||
CHyprSignalListener monitorModeChanged;
|
||||
CHyprSignalListener windowDestroyed;
|
||||
CHyprSignalListener windowSizeChanged;
|
||||
CHyprSignalListener windowMonitorChanged;
|
||||
} m_listeners;
|
||||
|
||||
void screenshareEvents(bool started);
|
||||
void calculateConstraints();
|
||||
void init();
|
||||
|
||||
friend class CScreenshareFrame;
|
||||
friend class CScreenshareManager;
|
||||
};
|
||||
|
||||
class CCursorshareSession {
|
||||
public:
|
||||
CCursorshareSession(const CCursorshareSession&) = delete;
|
||||
CCursorshareSession(CCursorshareSession&&) = delete;
|
||||
~CCursorshareSession();
|
||||
|
||||
eScreenshareError share(PHLMONITOR monitor, SP<IHLBuffer> buffer, FSourceBoxCallback sourceBoxCallback, FScreenshareCallback callback);
|
||||
void stop();
|
||||
|
||||
// constraints
|
||||
DRMFormat format() const;
|
||||
Vector2D bufferSize() const;
|
||||
Vector2D hotspot() const;
|
||||
|
||||
struct {
|
||||
CSignalT<> stopped;
|
||||
CSignalT<> constraintsChanged;
|
||||
} m_events;
|
||||
|
||||
private:
|
||||
CCursorshareSession(wl_client* client, WP<CWLPointerResource> pointer);
|
||||
|
||||
WP<CCursorshareSession> m_self;
|
||||
bool m_stopped = false;
|
||||
bool m_constraintsChanged = true;
|
||||
|
||||
wl_client* m_client = nullptr;
|
||||
WP<CWLPointerResource> m_pointer;
|
||||
|
||||
// constraints
|
||||
DRMFormat m_format = 0 /* DRM_FORMAT_INVALID */;
|
||||
Vector2D m_hotspot = Vector2D(0, 0);
|
||||
Vector2D m_bufferSize = Vector2D(0, 0);
|
||||
|
||||
struct {
|
||||
bool pending = false;
|
||||
PHLMONITOR monitor;
|
||||
SP<IHLBuffer> buffer;
|
||||
FSourceBoxCallback sourceBoxCallback;
|
||||
FScreenshareCallback callback;
|
||||
} m_pendingFrame;
|
||||
|
||||
struct {
|
||||
CHyprSignalListener pointerDestroyed;
|
||||
CHyprSignalListener cursorChanged;
|
||||
} m_listeners;
|
||||
|
||||
bool copy();
|
||||
void render();
|
||||
void calculateConstraints();
|
||||
|
||||
friend class CScreenshareFrame;
|
||||
friend class CScreenshareManager;
|
||||
};
|
||||
|
||||
class CScreenshareFrame {
|
||||
public:
|
||||
CScreenshareFrame(const CScreenshareFrame&) = delete;
|
||||
CScreenshareFrame(CScreenshareFrame&&) = delete;
|
||||
CScreenshareFrame(WP<CScreenshareSession> session, bool overlayCursor, bool isFirst);
|
||||
~CScreenshareFrame();
|
||||
|
||||
bool done() const;
|
||||
eScreenshareError share(SP<IHLBuffer> buffer, const CRegion& damage, FScreenshareCallback callback);
|
||||
|
||||
Vector2D bufferSize() const;
|
||||
wl_output_transform transform() const; // returns the transform applied by compositor on the buffer
|
||||
const CRegion& damage() const;
|
||||
|
||||
private:
|
||||
WP<CScreenshareFrame> m_self;
|
||||
WP<CScreenshareSession> m_session;
|
||||
FScreenshareCallback m_callback;
|
||||
SP<IHLBuffer> m_buffer;
|
||||
Vector2D m_bufferSize = Vector2D(0, 0);
|
||||
CRegion m_damage; // damage in buffer coords
|
||||
bool m_shared = false, m_copied = false, m_failed = false;
|
||||
bool m_overlayCursor = true;
|
||||
bool m_isFirst = false;
|
||||
|
||||
//
|
||||
void copy();
|
||||
bool copyDmabuf();
|
||||
bool copyShm();
|
||||
|
||||
void render();
|
||||
void renderMonitor();
|
||||
void renderMonitorRegion();
|
||||
void renderWindow();
|
||||
|
||||
void storeTempFB();
|
||||
|
||||
friend class CScreenshareManager;
|
||||
friend class CScreenshareSession;
|
||||
};
|
||||
|
||||
class CScreenshareManager {
|
||||
public:
|
||||
CScreenshareManager();
|
||||
|
||||
UP<CScreenshareSession> newSession(wl_client* client, PHLMONITOR monitor);
|
||||
UP<CScreenshareSession> newSession(wl_client* client, PHLMONITOR monitor, CBox captureRegion);
|
||||
UP<CScreenshareSession> newSession(wl_client* client, PHLWINDOW window);
|
||||
|
||||
WP<CScreenshareSession> getManagedSession(wl_client* client, PHLMONITOR monitor);
|
||||
WP<CScreenshareSession> getManagedSession(wl_client* client, PHLMONITOR monitor, CBox captureBox);
|
||||
WP<CScreenshareSession> getManagedSession(wl_client* client, PHLWINDOW window);
|
||||
|
||||
UP<CCursorshareSession> newCursorSession(wl_client* client, WP<CWLPointerResource> pointer);
|
||||
|
||||
void destroyClientSessions(wl_client* client);
|
||||
|
||||
void onOutputCommit(PHLMONITOR monitor);
|
||||
|
||||
private:
|
||||
std::vector<WP<CScreenshareSession>> m_sessions;
|
||||
std::vector<WP<CCursorshareSession>> m_cursorSessions;
|
||||
std::vector<WP<CScreenshareFrame>> m_pendingFrames;
|
||||
|
||||
struct SManagedSession {
|
||||
SManagedSession(UP<CScreenshareSession>&& session);
|
||||
|
||||
UP<CScreenshareSession> m_session;
|
||||
CHyprSignalListener stoppedListener;
|
||||
};
|
||||
|
||||
std::vector<UP<SManagedSession>> m_managedSessions;
|
||||
WP<CScreenshareSession> getManagedSession(eScreenshareType type, wl_client* client, PHLMONITOR monitor, PHLWINDOW window, CBox captureBox);
|
||||
|
||||
friend class CScreenshareSession;
|
||||
};
|
||||
|
||||
inline UP<CScreenshareManager>& mgr() {
|
||||
static UP<CScreenshareManager> manager = nullptr;
|
||||
if (!manager && g_pHyprRenderer) {
|
||||
Log::logger->log(Log::DEBUG, "Starting ScreenshareManager");
|
||||
manager = makeUnique<CScreenshareManager>();
|
||||
}
|
||||
return manager;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
struct std::formatter<Screenshare::eScreenshareType> : std::formatter<std::string> {
|
||||
auto format(const Screenshare::eScreenshareType& res, std::format_context& ctx) const {
|
||||
switch (res) {
|
||||
case Screenshare::SHARE_MONITOR: return formatter<string>::format("monitor", ctx);
|
||||
case Screenshare::SHARE_WINDOW: return formatter<string>::format("window", ctx);
|
||||
case Screenshare::SHARE_REGION: return formatter<string>::format("region", ctx);
|
||||
case Screenshare::SHARE_NONE: return formatter<string>::format("ERR NONE", ctx);
|
||||
}
|
||||
return formatter<string>::format("error", ctx);
|
||||
}
|
||||
};
|
||||
162
src/managers/screenshare/ScreenshareSession.cpp
Normal file
162
src/managers/screenshare/ScreenshareSession.cpp
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
#include "ScreenshareManager.hpp"
|
||||
#include "../../render/OpenGL.hpp"
|
||||
#include "../../Compositor.hpp"
|
||||
#include "../../render/Renderer.hpp"
|
||||
#include "../HookSystemManager.hpp"
|
||||
#include "../EventManager.hpp"
|
||||
#include "../eventLoop/EventLoopManager.hpp"
|
||||
|
||||
using namespace Screenshare;
|
||||
|
||||
CScreenshareSession::CScreenshareSession(PHLMONITOR monitor, wl_client* client) : m_type(SHARE_MONITOR), m_monitor(monitor), m_client(client) {
|
||||
init();
|
||||
}
|
||||
|
||||
CScreenshareSession::CScreenshareSession(PHLWINDOW window, wl_client* client) : m_type(SHARE_WINDOW), m_window(window), m_client(client) {
|
||||
m_listeners.windowDestroyed = m_window->m_events.unmap.listen([this]() { stop(); });
|
||||
m_listeners.windowSizeChanged = m_window->m_events.resize.listen([this]() {
|
||||
calculateConstraints();
|
||||
m_events.constraintsChanged.emit();
|
||||
});
|
||||
m_listeners.windowMonitorChanged = m_window->m_events.monitorChanged.listen([this]() {
|
||||
m_listeners.monitorDestroyed = monitor()->m_events.disconnect.listen([this]() { stop(); });
|
||||
m_listeners.monitorModeChanged = monitor()->m_events.modeChanged.listen([this]() {
|
||||
calculateConstraints();
|
||||
m_events.constraintsChanged.emit();
|
||||
});
|
||||
|
||||
calculateConstraints();
|
||||
m_events.constraintsChanged.emit();
|
||||
});
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
CScreenshareSession::CScreenshareSession(PHLMONITOR monitor, CBox captureRegion, wl_client* client) :
|
||||
m_type(SHARE_REGION), m_monitor(monitor), m_captureBox(captureRegion), m_client(client) {
|
||||
init();
|
||||
}
|
||||
|
||||
CScreenshareSession::~CScreenshareSession() {
|
||||
stop();
|
||||
LOGM(Log::TRACE, "Destroyed screenshare session for ({}): {}", m_type, m_name);
|
||||
}
|
||||
|
||||
void CScreenshareSession::stop() {
|
||||
if (m_stopped)
|
||||
return;
|
||||
m_stopped = true;
|
||||
m_events.stopped.emit();
|
||||
|
||||
screenshareEvents(false);
|
||||
}
|
||||
|
||||
void CScreenshareSession::init() {
|
||||
m_shareStopTimer = makeShared<CEventLoopTimer>(
|
||||
std::chrono::milliseconds(500),
|
||||
[this](SP<CEventLoopTimer> self, void* data) {
|
||||
// if this fires, then it's been half a second since the last frame, so we aren't sharing
|
||||
screenshareEvents(false);
|
||||
},
|
||||
nullptr);
|
||||
|
||||
if (g_pEventLoopManager)
|
||||
g_pEventLoopManager->addTimer(m_shareStopTimer);
|
||||
|
||||
// scale capture box since it's in logical coords
|
||||
m_captureBox.scale(monitor()->m_scale);
|
||||
|
||||
m_listeners.monitorDestroyed = monitor()->m_events.disconnect.listen([this]() { stop(); });
|
||||
m_listeners.monitorModeChanged = monitor()->m_events.modeChanged.listen([this]() {
|
||||
calculateConstraints();
|
||||
m_events.constraintsChanged.emit();
|
||||
});
|
||||
|
||||
calculateConstraints();
|
||||
}
|
||||
|
||||
void CScreenshareSession::calculateConstraints() {
|
||||
const auto PMONITOR = monitor();
|
||||
if (!PMONITOR) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: maybe support more that just monitor format in the future?
|
||||
m_formats.clear();
|
||||
m_formats.push_back(NFormatUtils::alphaFormat(g_pHyprOpenGL->getPreferredReadFormat(PMONITOR)));
|
||||
m_formats.push_back(g_pHyprOpenGL->getPreferredReadFormat(PMONITOR)); // some clients don't like alpha formats
|
||||
|
||||
// TODO: hack, we can't bit flip so we'll format flip heh, GL_BGRA_EXT won't work here
|
||||
for (auto& format : m_formats) {
|
||||
if (format == DRM_FORMAT_XRGB2101010 || format == DRM_FORMAT_ARGB2101010)
|
||||
format = DRM_FORMAT_XBGR2101010;
|
||||
}
|
||||
|
||||
switch (m_type) {
|
||||
case SHARE_MONITOR:
|
||||
m_bufferSize = PMONITOR->m_pixelSize;
|
||||
m_name = PMONITOR->m_name;
|
||||
break;
|
||||
case SHARE_WINDOW:
|
||||
m_bufferSize = m_window->m_realSize->value().round();
|
||||
m_name = m_window->m_title;
|
||||
break;
|
||||
case SHARE_REGION:
|
||||
m_bufferSize = PMONITOR->m_transform % 2 == 0 ? m_captureBox.size() : Vector2D{m_captureBox.h, m_captureBox.w};
|
||||
m_name = PMONITOR->m_name;
|
||||
break;
|
||||
case SHARE_NONE:
|
||||
default:
|
||||
LOGM(Log::ERR, "Invalid share type?? This shouldn't happen");
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
LOGM(Log::TRACE, "constraints changed for {}", m_name);
|
||||
}
|
||||
|
||||
void CScreenshareSession::screenshareEvents(bool startSharing) {
|
||||
if (startSharing && !m_sharing) {
|
||||
m_sharing = true;
|
||||
EMIT_HOOK_EVENT("screencast", (std::vector<std::any>{1, m_type}));
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{.event = "screencast", .data = std::format("1,{}", m_type)});
|
||||
EMIT_HOOK_EVENT("screencastv2", (std::vector<std::any>{1, m_type, m_name}));
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{.event = "screencastv2", .data = std::format("1,{},{}", m_type, m_name)});
|
||||
LOGM(Log::INFO, "New screenshare session for ({}): {}", m_type, m_name);
|
||||
} else if (!startSharing && m_sharing) {
|
||||
m_sharing = false;
|
||||
EMIT_HOOK_EVENT("screencast", (std::vector<std::any>{0, m_type}));
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{.event = "screencast", .data = std::format("0,{}", m_type)});
|
||||
EMIT_HOOK_EVENT("screencastv2", (std::vector<std::any>{0, m_type, m_name}));
|
||||
g_pEventManager->postEvent(SHyprIPCEvent{.event = "screencastv2", .data = std::format("0,{},{}", m_type, m_name)});
|
||||
LOGM(Log::INFO, "Stopped screenshare session for ({}): {}", m_type, m_name);
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<DRMFormat>& CScreenshareSession::allowedFormats() const {
|
||||
return m_formats;
|
||||
}
|
||||
|
||||
Vector2D CScreenshareSession::bufferSize() const {
|
||||
return m_bufferSize;
|
||||
}
|
||||
|
||||
PHLMONITOR CScreenshareSession::monitor() const {
|
||||
if (m_type == SHARE_WINDOW && m_window.expired())
|
||||
return nullptr;
|
||||
PHLMONITORREF mon = m_type == SHARE_WINDOW ? m_window->m_monitor : m_monitor;
|
||||
return mon.expired() ? nullptr : mon.lock();
|
||||
}
|
||||
|
||||
UP<CScreenshareFrame> CScreenshareSession::nextFrame(bool overlayCursor) {
|
||||
UP<CScreenshareFrame> frame = makeUnique<CScreenshareFrame>(m_self, overlayCursor, !m_sharing);
|
||||
frame->m_self = frame;
|
||||
|
||||
Screenshare::mgr()->m_pendingFrames.emplace_back(frame);
|
||||
|
||||
// there is now a pending frame, so block ds
|
||||
g_pHyprRenderer->m_directScanoutBlocked = true;
|
||||
|
||||
return frame;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue