From daa9a2386bcb747b15880f08569b49df88d0c85e Mon Sep 17 00:00:00 2001 From: vaxerski <43317083+vaxerski@users.noreply.github.com> Date: Sun, 27 Aug 2023 20:30:04 +0200 Subject: [PATCH] today's work. obs crashes while screensharing. --- CMakeLists.txt | 3 +- src/core/PortalManager.cpp | 133 +++++++- src/core/PortalManager.hpp | 41 ++- src/helpers/Log.hpp | 4 +- src/helpers/Timer.cpp | 11 + src/helpers/Timer.hpp | 17 + src/portals/Screencopy.cpp | 540 +++++++++++++++++++++++++++++++- src/portals/Screencopy.hpp | 104 +++++- src/shared/ScreencopyShared.cpp | 230 +++++++++++++- src/shared/ScreencopyShared.hpp | 29 +- 10 files changed, 1076 insertions(+), 36 deletions(-) create mode 100644 src/helpers/Timer.cpp create mode 100644 src/helpers/Timer.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ba6048..1a06be0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,7 @@ include_directories( ) set(CMAKE_CXX_STANDARD 23) -add_compile_options(-Wall -Wextra -Wno-unused-parameter -Wno-unused-value -Wno-missing-field-initializers -Wno-narrowing -Wno-pointer-arith) +add_compile_options(-Wall -Wextra -Wno-unused-parameter -Wno-unused-value -Wno-missing-field-initializers -Wno-narrowing -Wno-pointer-arith -fpermissive) message(STATUS "Checking deps...") add_subdirectory(subprojects/sdbus-cpp) @@ -69,4 +69,5 @@ protocol("protocols/wlr-foreign-toplevel-management-unstable-v1.xml" "wlr-foreig protocol("protocols/wlr-screencopy-unstable-v1.xml" "wlr-screencopy-unstable-v1" true) protocol("subprojects/hyprland-protocols/protocols/hyprland-global-shortcuts-v1.xml" "hyprland-global-shortcuts-v1" true) protocol("subprojects/hyprland-protocols/protocols/hyprland-toplevel-export-v1.xml" "hyprland-toplevel-export-v1" true) +protocol("unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml" "linux-dmabuf-unstable-v1" false) ## diff --git a/src/core/PortalManager.cpp b/src/core/PortalManager.cpp index 622f23d..7a9e760 100644 --- a/src/core/PortalManager.cpp +++ b/src/core/PortalManager.cpp @@ -5,8 +5,12 @@ #include #include #include +#include #include +#include + +#include void handleGlobal(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { g_pPortalManager->onGlobal(data, registry, name, interface, version); @@ -16,11 +20,64 @@ void handleGlobalRemove(void* data, struct wl_registry* registry, uint32_t name) ; // noop } -inline const struct wl_registry_listener registryListener = { +inline const wl_registry_listener registryListener = { .global = handleGlobal, .global_remove = handleGlobalRemove, }; +static void handleOutputGeometry(void* data, struct wl_output* wl_output, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char* make, + const char* model, int32_t transform) { + ; +} + +static void handleOutputDone(void* data, struct wl_output* wl_output) { + ; +} + +static void handleOutputMode(void* data, struct wl_output* wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { + ; +} + +static void handleOutputScale(void* data, struct wl_output* wl_output, int32_t factor) { + ; +} + +static void handleOutputName(void* data, struct wl_output* wl_output, const char* name) { + if (!name) + return; + + const auto POUTPUT = (SOutput*)data; + POUTPUT->name = name; + + Debug::log(LOG, "Found output name {}", POUTPUT->name); +} + +static void handleOutputDescription(void* data, struct wl_output* wl_output, const char* description) { + ; +} + +inline const wl_output_listener outputListener = { + .geometry = handleOutputGeometry, + .mode = handleOutputMode, + .done = handleOutputDone, + .scale = handleOutputScale, + .name = handleOutputName, + .description = handleOutputDescription, +}; + +static void handleDMABUFFormat(void* data, struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1, uint32_t format) { + ; +} + +static void handleDMABUFModifier(void* data, struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1, uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo) { + g_pPortalManager->m_vDMABUFMods.push_back({format, (((uint64_t)modifier_hi) << 32) | modifier_lo}); +} + +inline const zwp_linux_dmabuf_v1_listener dmabufListener = { + .format = handleDMABUFFormat, + .modifier = handleDMABUFModifier, +}; + // void CPortalManager::onGlobal(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { @@ -30,6 +87,34 @@ void CPortalManager::onGlobal(void* data, struct wl_registry* registry, uint32_t if (INTERFACE == zwlr_screencopy_manager_v1_interface.name && m_sPipewire.loop) m_sPortals.screencopy = std::make_unique((zwlr_screencopy_manager_v1*)wl_registry_bind(registry, name, &zwlr_screencopy_manager_v1_interface, version)); + + else if (INTERFACE == hyprland_toplevel_export_manager_v1_interface.name) + m_sWaylandConnection.hyprlandToplevelMgr = wl_registry_bind(registry, name, &hyprland_toplevel_export_manager_v1_interface, version); + + else if (INTERFACE == wl_output_interface.name) { + const auto POUTPUT = m_vOutputs.emplace_back(std::make_unique()).get(); + POUTPUT->output = (wl_output*)wl_registry_bind(registry, name, &wl_output_interface, version); + wl_output_add_listener(POUTPUT->output, &outputListener, POUTPUT); + POUTPUT->id = name; + } + + else if (INTERFACE == zwp_linux_dmabuf_v1_interface.name) { + if (version < 4) { + Debug::log(ERR, "cannot use linux_dmabuf with ver < 4"); + return; + } + + m_sWaylandConnection.linuxDmabuf = wl_registry_bind(registry, name, &zwp_linux_dmabuf_v1_interface, version); + m_sWaylandConnection.linuxDmabufFeedback = zwp_linux_dmabuf_v1_get_default_feedback((zwp_linux_dmabuf_v1*)m_sWaylandConnection.linuxDmabuf); + // TODO: dmabuf + } + + else if (INTERFACE == wl_shm_interface.name) + m_sWaylandConnection.shm = (wl_shm*)wl_registry_bind(registry, name, &wl_shm_interface, version); +} + +void CPortalManager::onGlobalRemoved(void* data, struct wl_registry* registry, uint32_t name) { + std::erase_if(m_vOutputs, [&](const auto& other) { return other->id == name; }); } void CPortalManager::init() { @@ -72,23 +157,45 @@ void CPortalManager::init() { if (!m_sPortals.screencopy) Debug::log(WARN, "Screencopy not started: compositor doesn't support zwlr_screencopy_v1 or pw refused a loop"); + else if (m_sWaylandConnection.hyprlandToplevelMgr) + m_sPortals.screencopy->appendToplevelExport(m_sWaylandConnection.hyprlandToplevelMgr); + + wl_display_roundtrip(m_sWaylandConnection.display); while (1) { // dbus events + m_mEventLock.lock(); + while (m_pConnection->processPendingRequest()) { ; } - // wayland events - while (1) { - auto r = wl_display_dispatch_pending(m_sWaylandConnection.display); - wl_display_flush(m_sWaylandConnection.display); - - if (r <= 0) - break; + std::vector toRemove; + for (auto& t : m_vTimers) { + if (t->passed()) { + t->m_fnCallback(); + toRemove.emplace_back(t.get()); + Debug::log(TRACE, "[core] calling timer {}", (void*)t.get()); + } } - // TODO: pipewire loop + while (pw_loop_iterate(m_sPipewire.loop, 0) != 0) { + ; + } + + wl_display_flush(m_sWaylandConnection.display); + if (wl_display_prepare_read(m_sWaylandConnection.display) == 0) { + wl_display_read_events(m_sWaylandConnection.display); + wl_display_dispatch_pending(m_sWaylandConnection.display); + } else { + wl_display_dispatch(m_sWaylandConnection.display); + } + + if (!toRemove.empty()) + std::erase_if(m_vTimers, + [&](const auto& t) { return std::find_if(toRemove.begin(), toRemove.end(), [&](const auto& other) { return other == t.get(); }) != toRemove.end(); }); + + m_mEventLock.unlock(); std::this_thread::sleep_for(std::chrono::milliseconds(1)); } @@ -96,4 +203,12 @@ void CPortalManager::init() { sdbus::IConnection* CPortalManager::getConnection() { return m_pConnection.get(); +} + +SOutput* CPortalManager::getOutputFromName(const std::string& name) { + for (auto& o : m_vOutputs) { + if (o->name == name) + return o.get(); + } + return nullptr; } \ No newline at end of file diff --git a/src/core/PortalManager.hpp b/src/core/PortalManager.hpp index c74153c..14434c8 100644 --- a/src/core/PortalManager.hpp +++ b/src/core/PortalManager.hpp @@ -5,31 +5,58 @@ #include #include "../portals/Screencopy.hpp" +#include "../helpers/Timer.hpp" + +#include struct pw_loop; +struct SOutput { + std::string name; + wl_output* output = nullptr; + uint32_t id = 0; +}; + +struct SDMABUFModifier { + uint32_t fourcc = 0; + uint64_t mod = 0; +}; + class CPortalManager { public: void init(); void onGlobal(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version); + void onGlobalRemoved(void* data, struct wl_registry* registry, uint32_t name); sdbus::IConnection* getConnection(); + SOutput* getOutputFromName(const std::string& name); struct { pw_loop* loop = nullptr; } m_sPipewire; - private: - std::unique_ptr m_pConnection; - - struct { - wl_display* display = nullptr; - } m_sWaylandConnection; - struct { std::unique_ptr screencopy; } m_sPortals; + + struct { + wl_display* display = nullptr; + void* hyprlandToplevelMgr = nullptr; + void* linuxDmabuf = nullptr; + void* linuxDmabufFeedback = nullptr; + wl_shm* shm = nullptr; + } m_sWaylandConnection; + + std::vector m_vDMABUFMods; + + std::vector> m_vTimers; + + private: + std::unique_ptr m_pConnection; + std::vector> m_vOutputs; + + std::mutex m_mEventLock; }; inline std::unique_ptr g_pPortalManager; \ No newline at end of file diff --git a/src/helpers/Log.hpp b/src/helpers/Log.hpp index b5694a9..21ab904 100644 --- a/src/helpers/Log.hpp +++ b/src/helpers/Log.hpp @@ -5,7 +5,8 @@ enum eLogLevel { - INFO = 0, + TRACE = 0, + INFO, LOG, WARN, ERR, @@ -18,6 +19,7 @@ namespace Debug { std::cout << '['; switch (level) { + case TRACE: std::cout << "TRACE"; break; case INFO: std::cout << "INFO"; break; case LOG: std::cout << "LOG"; break; case WARN: std::cout << "WARN"; break; diff --git a/src/helpers/Timer.cpp b/src/helpers/Timer.cpp new file mode 100644 index 0000000..0857e66 --- /dev/null +++ b/src/helpers/Timer.cpp @@ -0,0 +1,11 @@ +#include "Timer.hpp" + +CTimer::CTimer(float ms, std::function callback) { + m_fDuration = ms; + m_tStart = std::chrono::high_resolution_clock::now(); + m_fnCallback = callback; +} + +bool CTimer::passed() const { + return std::chrono::high_resolution_clock::now() > (m_tStart + std::chrono::milliseconds((uint64_t)m_fDuration)); +} \ No newline at end of file diff --git a/src/helpers/Timer.hpp b/src/helpers/Timer.hpp new file mode 100644 index 0000000..b5832a4 --- /dev/null +++ b/src/helpers/Timer.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +class CTimer { + public: + CTimer(float ms, std::function callback); + + bool passed() const; + + std::function m_fnCallback; + + private: + std::chrono::high_resolution_clock::time_point m_tStart; + float m_fDuration; +}; \ No newline at end of file diff --git a/src/portals/Screencopy.cpp b/src/portals/Screencopy.cpp index a62f430..59e1167 100644 --- a/src/portals/Screencopy.cpp +++ b/src/portals/Screencopy.cpp @@ -3,8 +3,123 @@ #include "../helpers/Log.hpp" #include "../helpers/MiscFunctions.hpp" +#include #include +// --------------- Wayland Protocol Handlers --------------- // + +static void wlrOnBuffer(void* data, struct zwlr_screencopy_frame_v1* frame, uint32_t format, uint32_t width, uint32_t height, uint32_t stride) { + const auto PSESSION = (CScreencopyPortal::SSession*)data; + + Debug::log(TRACE, "[sc] wlrOnBuffer for {}", (void*)PSESSION); + + PSESSION->sharingData.frameInfoSHM.w = width; + PSESSION->sharingData.frameInfoSHM.h = height; + PSESSION->sharingData.frameInfoSHM.fmt = drmFourccFromSHM((wl_shm_format)format); + PSESSION->sharingData.frameInfoSHM.size = stride * height; + PSESSION->sharingData.frameInfoSHM.stride = stride; + + // todo: done if ver < 3 +} + +static void wlrOnFlags(void* data, struct zwlr_screencopy_frame_v1* frame, uint32_t flags) { + const auto PSESSION = (CScreencopyPortal::SSession*)data; + + Debug::log(TRACE, "[sc] wlrOnFlags for {}", (void*)PSESSION); + + // todo: maybe check for y invert? +} + +static void wlrOnReady(void* data, struct zwlr_screencopy_frame_v1* frame, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) { + const auto PSESSION = (CScreencopyPortal::SSession*)data; + + Debug::log(TRACE, "[sc] wlrOnReady for {}", (void*)PSESSION); + + PSESSION->sharingData.status = FRAME_READY; + + PSESSION->sharingData.tvSec = ((((uint64_t)tv_sec_hi) << 32) | tv_sec_lo); + PSESSION->sharingData.tvNsec = tv_nsec; + + g_pPortalManager->m_sPortals.screencopy->m_pPipewire->enqueue(PSESSION); + + g_pPortalManager->m_vTimers.emplace_back(std::make_unique(1000.0 / FRAMERATE, [PSESSION]() { g_pPortalManager->m_sPortals.screencopy->startFrameCopy(PSESSION); })); + + zwlr_screencopy_frame_v1_destroy(frame); + PSESSION->sharingData.frameCallback = nullptr; +} + +static void wlrOnFailed(void* data, struct zwlr_screencopy_frame_v1* frame) { + const auto PSESSION = (CScreencopyPortal::SSession*)data; + + Debug::log(TRACE, "[sc] wlrOnFailed for {}", (void*)PSESSION); + + PSESSION->sharingData.status = FRAME_FAILED; +} + +static void wlrOnDamage(void* data, struct zwlr_screencopy_frame_v1* frame, uint32_t x, uint32_t y, uint32_t width, uint32_t height) { + const auto PSESSION = (CScreencopyPortal::SSession*)data; + + Debug::log(TRACE, "[sc] wlrOnDamage for {}", (void*)PSESSION); + + // todo +} + +static void wlrOnDmabuf(void* data, struct zwlr_screencopy_frame_v1* frame, uint32_t format, uint32_t width, uint32_t height) { + const auto PSESSION = (CScreencopyPortal::SSession*)data; + + Debug::log(TRACE, "[sc] wlrOnDmabuf for {}", (void*)PSESSION); + + PSESSION->sharingData.frameInfoDMA.w = width; + PSESSION->sharingData.frameInfoDMA.h = height; + PSESSION->sharingData.frameInfoDMA.fmt = format; +} + +static void wlrOnBufferDone(void* data, struct zwlr_screencopy_frame_v1* frame) { + const auto PSESSION = (CScreencopyPortal::SSession*)data; + + Debug::log(TRACE, "[sc] wlrOnBufferDone for {}", (void*)PSESSION); + + const auto PSTREAM = g_pPortalManager->m_sPortals.screencopy->m_pPipewire->streamFromSession(PSESSION); + + if (!PSTREAM) { + Debug::log(TRACE, "[sc] wlrOnBufferDone: no stream"); + zwlr_screencopy_frame_v1_destroy(frame); + PSESSION->sharingData.frameCallback = nullptr; + return; + } + + Debug::log(TRACE, "[sc] pw format {} size {}x{}", (int)PSTREAM->pwVideoInfo.format, PSTREAM->pwVideoInfo.size.width, PSTREAM->pwVideoInfo.size.height); + Debug::log(TRACE, "[sc] wlr format {} size {}x{}", (int)PSESSION->sharingData.frameInfoSHM.fmt, PSESSION->sharingData.frameInfoSHM.w, PSESSION->sharingData.frameInfoSHM.h); + + if (!PSTREAM->currentPWBuffer) { + Debug::log(TRACE, "[sc] wlrOnBufferDone: dequeue, no current buffer"); + g_pPortalManager->m_sPortals.screencopy->m_pPipewire->dequeue(PSESSION); + } + + if (!PSTREAM->currentPWBuffer) { + zwlr_screencopy_frame_v1_destroy(frame); + PSESSION->sharingData.frameCallback = nullptr; + Debug::log(LOG, "[screencopy/pipewire] Out of buffers"); + return; + } + + zwlr_screencopy_frame_v1_copy_with_damage(frame, PSTREAM->currentPWBuffer->wlBuffer); + + Debug::log(TRACE, "[sc] wlr frame copied"); +} + +static const zwlr_screencopy_frame_v1_listener wlrFrameListener = { + .buffer = wlrOnBuffer, + .flags = wlrOnFlags, + .ready = wlrOnReady, + .failed = wlrOnFailed, + .damage = wlrOnDamage, + .linux_dmabuf = wlrOnDmabuf, + .buffer_done = wlrOnBufferDone, +}; + +// --------------------------------------------------------- // + void onCloseRequest(sdbus::MethodCall& call, CScreencopyPortal::SSession* sess) { if (!sess || !sess->request) return; @@ -141,10 +256,17 @@ void CScreencopyPortal::onSelectSources(sdbus::MethodCall& call) { } } - const auto SHAREDATA = promptForScreencopySelection(); + auto SHAREDATA = promptForScreencopySelection(); Debug::log(LOG, "[screencopy] SHAREDATA returned selection {}", (int)SHAREDATA.type); + if (SHAREDATA.type == TYPE_WINDOW && !m_sState.toplevel) { + Debug::log(ERR, "[screencopy] Requested type window for no toplevel export protocol!"); + SHAREDATA.type = TYPE_INVALID; + } + + PSESSION->selection = SHAREDATA; + auto reply = call.createReply(); reply << (uint32_t)(SHAREDATA.type == TYPE_INVALID ? 1 : 0); reply << std::unordered_map{}; @@ -177,7 +299,7 @@ void CScreencopyPortal::onStart(sdbus::MethodCall& call) { return; } - // TODO: start cast + startSharing(PSESSION); auto reply = call.createReply(); reply << (uint32_t)0; @@ -203,11 +325,86 @@ void CScreencopyPortal::onStart(sdbus::MethodCall& call) { } options["source_type"] = type; + std::vector>> streams; + + std::unordered_map streamData; + streamData["position"] = sdbus::Variant{sdbus::Struct{0, 0}}; + streamData["size"] = sdbus::Variant{sdbus::Struct{PSESSION->sharingData.frameInfoSHM.w, PSESSION->sharingData.frameInfoSHM.h}}; + streamData["source_type"] = sdbus::Variant{uint32_t{type}}; + streams.emplace_back(sdbus::Struct>{PSESSION->sharingData.nodeID, streamData}); + + options["streams"] = streams; + reply << options; reply.send(); } +void CScreencopyPortal::startSharing(CScreencopyPortal::SSession* pSession) { + pSession->sharingData.active = true; + + startFrameCopy(pSession); + + wl_display_dispatch(g_pPortalManager->m_sWaylandConnection.display); + wl_display_roundtrip(g_pPortalManager->m_sWaylandConnection.display); + + if (pSession->sharingData.frameInfoSHM.fmt == DRM_FORMAT_INVALID) { + Debug::log(ERR, "[screencopy] Couldn't obtain a format from shm"); + return; + } + + m_pPipewire->createStream(pSession); + + while (pSession->sharingData.nodeID == SPA_ID_INVALID) { + int ret = pw_loop_iterate(g_pPortalManager->m_sPipewire.loop, 0); + if (ret < 0) { + Debug::log(ERR, "[pipewire] pw_loop_iterate failed with {}", spa_strerror(ret)); + return; + } + } + + Debug::log(LOG, "[screencopy] Sharing initialized"); + + g_pPortalManager->m_vTimers.emplace_back(std::make_unique(1000.0 / FRAMERATE, [pSession]() { g_pPortalManager->m_sPortals.screencopy->startFrameCopy(pSession); })); + + Debug::log(TRACE, "[sc] queued frame in {}ms", 1000.0 / FRAMERATE); +} + +void CScreencopyPortal::startFrameCopy(CScreencopyPortal::SSession* pSession) { + const auto POUTPUT = g_pPortalManager->getOutputFromName(pSession->selection.output); + + if (!pSession->sharingData.active) { + Debug::log(TRACE, "[sc] startFrameCopy: not copying, inactive session"); + return; + } + + if (!POUTPUT) { + Debug::log(ERR, "[screencopy] Output {} not found??", pSession->selection.output); + return; + } + + if (pSession->sharingData.frameCallback) { + Debug::log(ERR, "[screencopy] tried scheduling on already scheduled cb"); + return; + } + + if (pSession->selection.type == TYPE_GEOMETRY) + pSession->sharingData.frameCallback = zwlr_screencopy_manager_v1_capture_output_region(m_sState.screencopy, pSession->cursorMode, POUTPUT->output, pSession->selection.x, + pSession->selection.y, pSession->selection.w, pSession->selection.h); + else if (pSession->selection.type == TYPE_OUTPUT) + pSession->sharingData.frameCallback = zwlr_screencopy_manager_v1_capture_output(m_sState.screencopy, pSession->cursorMode, POUTPUT->output); + else { + Debug::log(ERR, "[screencopy] Unsupported selection {}", (int)pSession->selection.type); + return; + } + + pSession->sharingData.status = FRAME_QUEUED; + + zwlr_screencopy_frame_v1_add_listener(pSession->sharingData.frameCallback, &wlrFrameListener, pSession); + + Debug::log(LOG, "[screencopy] frame callbacks initialized"); +} + CScreencopyPortal::SSession* CScreencopyPortal::getSession(sdbus::ObjectPath& path) { for (auto& s : m_vSessions) { if (s->sessionHandle == path) @@ -235,6 +432,12 @@ CScreencopyPortal::CScreencopyPortal(zwlr_screencopy_manager_v1* mgr) { Debug::log(LOG, "[screencopy] init successful"); } +void CScreencopyPortal::appendToplevelExport(void* proto) { + m_sState.toplevel = (hyprland_toplevel_export_manager_v1*)proto; + + Debug::log(LOG, "[screencopy] Registered for toplevel export"); +} + bool CPipewireConnection::good() { return m_pContext && m_pCore; } @@ -262,4 +465,335 @@ CPipewireConnection::~CPipewireConnection() { pw_core_disconnect(m_pCore); if (m_pContext) pw_context_destroy(m_pContext); -} \ No newline at end of file +} + +// --------------- Pipewire Stream Handlers --------------- // + +static void pwStreamStateChange(void* data, pw_stream_state old, pw_stream_state state, const char* error) { + const auto PSTREAM = (CPipewireConnection::SPWStream*)data; + + PSTREAM->pSession->sharingData.nodeID = pw_stream_get_node_id(PSTREAM->stream); + + Debug::log(TRACE, "[pw] pwStreamStateChange on {} from {} to {}, node id {}", (void*)PSTREAM, pw_stream_state_as_string(old), pw_stream_state_as_string(state), + PSTREAM->pSession->sharingData.nodeID); + + switch (state) { + case PW_STREAM_STATE_STREAMING: + PSTREAM->streamState = true; + if (PSTREAM->pSession->sharingData.status == FRAME_NONE) + g_pPortalManager->m_sPortals.screencopy->startFrameCopy(PSTREAM->pSession); + break; + case PW_STREAM_STATE_PAUSED: + if (old == PW_STREAM_STATE_STREAMING) + g_pPortalManager->m_sPortals.screencopy->m_pPipewire->enqueue(PSTREAM->pSession); + PSTREAM->streamState = false; + break; + default: PSTREAM->streamState = false; + } + + if (state == PW_STREAM_STATE_UNCONNECTED) { + g_pPortalManager->m_sPortals.screencopy->m_pPipewire->destroyStream(PSTREAM->pSession); + } +} + +static void pwStreamParamChanged(void* data, uint32_t id, const spa_pod* param) { + const auto PSTREAM = (CPipewireConnection::SPWStream*)data; + + Debug::log(TRACE, "[pw] pwStreamParamChanged on {}", (void*)PSTREAM); + + if (id != SPA_PARAM_Format || !param) { + Debug::log(TRACE, "[pw] invalid call in pwStreamParamChanged"); + return; + } + + spa_pod_dynamic_builder dynBuilder[3]; + const spa_pod* params[4]; + uint8_t params_buffer[3][1024]; + + spa_pod_dynamic_builder_init(&dynBuilder[0], params_buffer[0], sizeof(params_buffer[0]), 2048); + spa_pod_dynamic_builder_init(&dynBuilder[1], params_buffer[1], sizeof(params_buffer[1]), 2048); + spa_pod_dynamic_builder_init(&dynBuilder[2], params_buffer[2], sizeof(params_buffer[2]), 2048); + + spa_format_video_raw_parse(param, &PSTREAM->pwVideoInfo); + // todo: framerate + + const struct spa_pod_prop* prop_modifier; + if ((prop_modifier = spa_pod_find_prop(param, NULL, SPA_FORMAT_VIDEO_modifier)) != NULL) { + Debug::log(ERR, "[pipewire] pw requested dmabuf"); + return; + } + + Debug::log(TRACE, "[pw] Format renegotiated:"); + Debug::log(TRACE, "[pw] | buffer_type {}", "SHM"); + Debug::log(TRACE, "[pw] | format: {}", (int)PSTREAM->pwVideoInfo.format); + Debug::log(TRACE, "[pw] | modifier: {}", PSTREAM->pwVideoInfo.modifier); + Debug::log(TRACE, "[pw] | size: {}x{}", PSTREAM->pwVideoInfo.size.width, PSTREAM->pwVideoInfo.size.height); + Debug::log(TRACE, "[pw] | framerate {}", FRAMERATE); + + uint32_t blocks = 1; + uint32_t data_type = 1 << SPA_DATA_MemFd; + + params[0] = build_buffer(&dynBuilder[0].b, blocks, PSTREAM->pSession->sharingData.frameInfoSHM.size, PSTREAM->pSession->sharingData.frameInfoSHM.stride, data_type); + + params[1] = (const spa_pod*)spa_pod_builder_add_object(&dynBuilder[1].b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), + SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); + + params[2] = (const spa_pod*)spa_pod_builder_add_object(&dynBuilder[1].b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoTransform), + SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_videotransform))); + + params[3] = (const spa_pod*)spa_pod_builder_add_object( + &dynBuilder[2].b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoDamage), SPA_PARAM_META_size, + SPA_POD_CHOICE_RANGE_Int(sizeof(struct spa_meta_region) * 4, sizeof(struct spa_meta_region) * 1, sizeof(struct spa_meta_region) * 4)); + + pw_stream_update_params(PSTREAM->stream, params, 4); + spa_pod_dynamic_builder_clean(&dynBuilder[0]); + spa_pod_dynamic_builder_clean(&dynBuilder[1]); + spa_pod_dynamic_builder_clean(&dynBuilder[2]); +} + +static void pwStreamAddBuffer(void* data, pw_buffer* buffer) { + const auto PSTREAM = (CPipewireConnection::SPWStream*)data; + + Debug::log(TRACE, "[pw] pwStreamAddBuffer with {} on {}", (void*)buffer, (void*)PSTREAM); + + spa_data* spaData = buffer->buffer->datas; + spa_data_type type; + + if ((spaData[0].type & (1u << SPA_DATA_MemFd)) > 0) { + type = SPA_DATA_MemFd; + } else if ((spaData[0].type & (1u << SPA_DATA_DmaBuf)) > 0) { + type = SPA_DATA_DmaBuf; + } else { + Debug::log(ERR, "[pipewire] wrong format in addbuffer"); + return; + } + + const auto PBUFFER = PSTREAM->buffers.emplace_back(std::make_unique()).get(); + + buffer->user_data = PBUFFER; + + PBUFFER->fmt = PSTREAM->pSession->sharingData.frameInfoSHM.fmt; + PBUFFER->h = PSTREAM->pSession->sharingData.frameInfoSHM.h; + PBUFFER->w = PSTREAM->pSession->sharingData.frameInfoSHM.w; + PBUFFER->isDMABUF = type == SPA_DATA_DmaBuf; + PBUFFER->pwBuffer = buffer; + + Debug::log(TRACE, "[pw] Adding buffer of type {}", PBUFFER->isDMABUF ? "DMA" : "SHM"); + + // wl_shm only + PBUFFER->planeCount = 1; + PBUFFER->size[0] = PSTREAM->pSession->sharingData.frameInfoSHM.size; + PBUFFER->stride[0] = PSTREAM->pSession->sharingData.frameInfoSHM.stride; + PBUFFER->offset[0] = 0; + PBUFFER->fd[0] = anonymous_shm_open(); + if (PBUFFER->fd[0] == -1) { + Debug::log(ERR, "buffer fd failed"); + return; + } + + if (ftruncate(PBUFFER->fd[0], PBUFFER->size[0]) < 0) { + close(PBUFFER->fd[0]); + Debug::log(ERR, "buffer ftruncate failed"); + return; + } + + PBUFFER->wlBuffer = + import_wl_shm_buffer(PBUFFER->fd[0], (wl_shm_format)wlSHMFromDrmFourcc(PSTREAM->pSession->sharingData.frameInfoSHM.fmt), PSTREAM->pSession->sharingData.frameInfoSHM.w, + PSTREAM->pSession->sharingData.frameInfoSHM.h, PSTREAM->pSession->sharingData.frameInfoSHM.stride); + if (PBUFFER->wlBuffer == NULL) { + close(PBUFFER->fd[0]); + Debug::log(ERR, "buffer import failed"); + return; + } + // + + Debug::log(TRACE, "[pw] buffer datas {}", buffer->buffer->n_datas); + + for (uint32_t plane = 0; plane < buffer->buffer->n_datas; plane++) { + spaData[plane].type = type; + spaData[plane].maxsize = PBUFFER->size[plane]; + spaData[plane].mapoffset = 0; + spaData[plane].chunk->size = PBUFFER->size[plane]; + spaData[plane].chunk->stride = PBUFFER->stride[plane]; + spaData[plane].chunk->offset = PBUFFER->offset[plane]; + spaData[plane].flags = 0; + spaData[plane].fd = PBUFFER->fd[plane]; + spaData[plane].data = NULL; + // clients have implemented to check chunk->size if the buffer is valid instead + // of using the flags. Until they are patched we should use some arbitrary value. + if (PBUFFER->isDMABUF && spaData[plane].chunk->size == 0) { + spaData[plane].chunk->size = 9; // This was choosen by a fair d20. + } + } +} + +static void pwStreamRemoveBuffer(void* data, pw_buffer* buffer) { + const auto PSTREAM = (CPipewireConnection::SPWStream*)data; + const auto PBUFFER = (SBuffer*)buffer->user_data; + + Debug::log(TRACE, "[pw] pwStreamRemoveBuffer with {} on {}", (void*)buffer, (void*)PSTREAM); + + if (!PBUFFER) + return; + + wl_buffer_destroy(PBUFFER->wlBuffer); + for (int plane = 0; plane < PBUFFER->planeCount; plane++) { + close(PBUFFER->fd[plane]); + } + + for (uint32_t plane = 0; plane < buffer->buffer->n_datas; plane++) { + buffer->buffer->datas[plane].fd = -1; + } + + std::erase_if(PSTREAM->buffers, [&](const auto& other) { return other.get() == PBUFFER; }); + + buffer->user_data = nullptr; +} + +static const pw_stream_events pwStreamEvents = { + .version = PW_VERSION_STREAM_EVENTS, + .state_changed = pwStreamStateChange, + .param_changed = pwStreamParamChanged, + .add_buffer = pwStreamAddBuffer, + .remove_buffer = pwStreamRemoveBuffer, +}; + +// ------------------------------------------------------- // + +void CPipewireConnection::createStream(CScreencopyPortal::SSession* pSession) { + const auto PSTREAM = m_vStreams.emplace_back(std::make_unique(pSession)).get(); + + pw_loop_enter(g_pPortalManager->m_sPipewire.loop); + + uint8_t buffer[2][1024]; + spa_pod_dynamic_builder dynBuilder[2]; + spa_pod_dynamic_builder_init(&dynBuilder[0], buffer[0], sizeof(buffer[0]), 2048); + spa_pod_dynamic_builder_init(&dynBuilder[1], buffer[1], sizeof(buffer[1]), 2048); + + const std::string NAME = getRandName("xdph-streaming-"); + + PSTREAM->stream = pw_stream_new(m_pCore, NAME.c_str(), pw_properties_new(PW_KEY_MEDIA_CLASS, "Video/Source", nullptr)); + + Debug::log(TRACE, "[pw] New stream name {}", NAME); + + if (!PSTREAM->stream) { + Debug::log(ERR, "[pipewire] refused to create stream"); + exit(1); + } + + spa_pod_builder* builder[2] = {&dynBuilder[0].b, &dynBuilder[1].b}; + const spa_pod* params[2]; + const auto PARAMCOUNT = buildFormatsFor(builder, params, PSTREAM); + + spa_pod_dynamic_builder_clean(&dynBuilder[0]); + spa_pod_dynamic_builder_clean(&dynBuilder[1]); + + pw_stream_add_listener(PSTREAM->stream, &PSTREAM->streamListener, &pwStreamEvents, PSTREAM); + + pw_stream_connect(PSTREAM->stream, PW_DIRECTION_OUTPUT, PW_ID_ANY, (pw_stream_flags)(PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_ALLOC_BUFFERS), params, PARAMCOUNT); + + pSession->sharingData.nodeID = pw_stream_get_node_id(PSTREAM->stream); + + Debug::log(TRACE, "[pw] Stream got nodeid {}", pSession->sharingData.nodeID); +} + +void CPipewireConnection::destroyStream(CScreencopyPortal::SSession* pSession) { + const auto PSTREAM = streamFromSession(pSession); + + if (!PSTREAM || !PSTREAM->stream) + return; + + if (!PSTREAM->buffers.empty()) { + for (auto& b : PSTREAM->buffers) { + pwStreamRemoveBuffer(PSTREAM, b->pwBuffer); + } + } + + pw_stream_flush(PSTREAM->stream, false); + pw_stream_disconnect(PSTREAM->stream); + pw_stream_destroy(PSTREAM->stream); + + pSession->sharingData.active = false; + + std::erase_if(m_vStreams, [&](const auto& other) { return other.get() == PSTREAM; }); +} + +uint32_t CPipewireConnection::buildFormatsFor(spa_pod_builder* b[2], const spa_pod* params[2], CPipewireConnection::SPWStream* stream) { + uint32_t paramCount = 0; + uint32_t modCount = 0; + + if (/*TODO: dmabuf*/ false) { + + } else { + paramCount = 1; + params[0] = build_format(b[0], pwFromDrmFourcc(stream->pSession->sharingData.frameInfoSHM.fmt), stream->pSession->sharingData.frameInfoSHM.w, + stream->pSession->sharingData.frameInfoSHM.h, FRAMERATE /*TODO: FRAMERATE*/, NULL, 0); + } + + return paramCount; +} + +bool CPipewireConnection::buildModListFor(CPipewireConnection::SPWStream* stream, uint32_t drmFmt, uint64_t** mods, uint32_t* modCount) { + return true; +} + +CPipewireConnection::SPWStream* CPipewireConnection::streamFromSession(CScreencopyPortal::SSession* pSession) { + for (auto& s : m_vStreams) { + if (s->pSession == pSession) + return s.get(); + } + return nullptr; +} + +void CPipewireConnection::enqueue(CScreencopyPortal::SSession* pSession) { + const auto PSTREAM = streamFromSession(pSession); + + Debug::log(TRACE, "[pw] enqueue on {}", (void*)PSTREAM); + + if (!PSTREAM->currentPWBuffer) { + Debug::log(ERR, "[pipewire] no buffer in enqueue"); + return; + } + + spa_buffer* spaBuf = PSTREAM->currentPWBuffer->pwBuffer->buffer; + bool corrupt = PSTREAM->pSession->sharingData.status != FRAME_READY; + if (corrupt) + Debug::log(TRACE, "[pw] buffer corrupt"); + + spa_meta_header* header; + if ((header = (spa_meta_header*)spa_buffer_find_meta_data(spaBuf, SPA_META_Header, sizeof(*header)))) { + header->pts = PSTREAM->pSession->sharingData.tvSec + SPA_NSEC_PER_SEC * PSTREAM->pSession->sharingData.tvNsec; + header->flags = corrupt ? SPA_META_HEADER_FLAG_CORRUPTED : 0; + header->seq = PSTREAM->seq++; + header->dts_offset = 0; + Debug::log(TRACE, "[pw] enqueue: seq {} pts {}", header->seq, header->pts); + } + + spa_data* datas = spaBuf->datas; + + for (uint32_t plane = 0; plane < spaBuf->n_datas; plane++) { + datas[plane].chunk->flags = corrupt ? SPA_CHUNK_FLAG_CORRUPTED : SPA_CHUNK_FLAG_NONE; + } + + pw_stream_queue_buffer(PSTREAM->stream, PSTREAM->currentPWBuffer->pwBuffer); + + PSTREAM->currentPWBuffer = nullptr; +} + +void CPipewireConnection::dequeue(CScreencopyPortal::SSession* pSession) { + const auto PSTREAM = streamFromSession(pSession); + + Debug::log(TRACE, "[pw] dequeue on {}", (void*)PSTREAM); + + const auto PWBUF = pw_stream_dequeue_buffer(PSTREAM->stream); + + if (!PWBUF) { + Debug::log(TRACE, "[pw] dequeue failed"); + PSTREAM->currentPWBuffer = nullptr; + return; + } + + const auto PBUF = (SBuffer*)PWBUF->user_data; + + PSTREAM->currentPWBuffer = PBUF; +} diff --git a/src/portals/Screencopy.hpp b/src/portals/Screencopy.hpp index 0520e72..c2eb4e5 100644 --- a/src/portals/Screencopy.hpp +++ b/src/portals/Screencopy.hpp @@ -1,8 +1,12 @@ #pragma once #include +#include #include #include "../shared/ScreencopyShared.hpp" +#include + +#define FRAMERATE 60 enum cursorModes { @@ -18,25 +22,41 @@ enum sourceTypes VIRTUAL = 4, }; +enum frameStatus +{ + FRAME_NONE = 0, + FRAME_QUEUED, + FRAME_READY, + FRAME_FAILED, +}; + struct pw_context; struct pw_core; +struct pw_stream; +struct pw_buffer; -class CPipewireConnection { - public: - CPipewireConnection(); - ~CPipewireConnection(); +struct SBuffer { + bool isDMABUF = false; + uint32_t w = 0, h = 0, fmt = 0; + int planeCount = 0; - bool good(); + int fd[4]; + uint32_t size[4], stride[4], offset[4]; - private: - pw_context* m_pContext = nullptr; - pw_core* m_pCore = nullptr; + gbm_bo* bo = nullptr; + + wl_buffer* wlBuffer = nullptr; + pw_buffer* pwBuffer = nullptr; }; +class CPipewireConnection; + class CScreencopyPortal { public: CScreencopyPortal(zwlr_screencopy_manager_v1*); + void appendToplevelExport(void*); + void onCreateSession(sdbus::MethodCall& call); void onSelectSources(sdbus::MethodCall& call); void onStart(sdbus::MethodCall& call); @@ -50,23 +70,81 @@ class CScreencopyPortal { std::unique_ptr request, session; SSelectionData selection; - void onCloseRequest(sdbus::MethodCall&); - void onCloseSession(sdbus::MethodCall&); + struct { + bool active = false; + zwlr_screencopy_frame_v1* frameCallback = nullptr; + frameStatus status = FRAME_NONE; + uint64_t tvSec = 0; + uint32_t tvNsec = 0; + uint32_t nodeID = 0; + + struct { + uint32_t w = 0, h = 0, size = 0, stride = 0, fmt = 0; + } frameInfoSHM; + + struct { + uint32_t w = 0, h = 0, fmt = 0; + } frameInfoDMA; + } sharingData; + + void onCloseRequest(sdbus::MethodCall&); + void onCloseSession(sdbus::MethodCall&); }; + void startFrameCopy(SSession* pSession); + + std::unique_ptr m_pPipewire; + private: std::unique_ptr m_pObject; std::vector> m_vSessions; SSession* getSession(sdbus::ObjectPath& path); - - std::unique_ptr m_pPipewire; + void startSharing(SSession* pSession); struct { - zwlr_screencopy_manager_v1* screencopy = nullptr; + zwlr_screencopy_manager_v1* screencopy = nullptr; + hyprland_toplevel_export_manager_v1* toplevel = nullptr; } m_sState; const std::string INTERFACE_NAME = "org.freedesktop.impl.portal.ScreenCast"; const std::string OBJECT_PATH = "/org/freedesktop/portal/desktop"; +}; + +class CPipewireConnection { + public: + CPipewireConnection(); + ~CPipewireConnection(); + + bool good(); + + void createStream(CScreencopyPortal::SSession* pSession); + void destroyStream(CScreencopyPortal::SSession* pSession); + + void enqueue(CScreencopyPortal::SSession* pSession); + void dequeue(CScreencopyPortal::SSession* pSession); + + struct SPWStream { + CScreencopyPortal::SSession* pSession = nullptr; + pw_stream* stream = nullptr; + bool streamState = false; + spa_hook streamListener; + SBuffer* currentPWBuffer = nullptr; + spa_video_info_raw pwVideoInfo; + uint32_t seq = 0; + + std::vector> buffers; + }; + + SPWStream* streamFromSession(CScreencopyPortal::SSession* pSession); + + private: + std::vector> m_vStreams; + + uint32_t buildFormatsFor(spa_pod_builder* b[2], const spa_pod* params[2], SPWStream* stream); + bool buildModListFor(SPWStream* stream, uint32_t drmFmt, uint64_t** mods, uint32_t* modCount); + + pw_context* m_pContext = nullptr; + pw_core* m_pCore = nullptr; }; \ No newline at end of file diff --git a/src/shared/ScreencopyShared.cpp b/src/shared/ScreencopyShared.cpp index 063f7c1..e5fccb2 100644 --- a/src/shared/ScreencopyShared.cpp +++ b/src/shared/ScreencopyShared.cpp @@ -1,5 +1,13 @@ #include "ScreencopyShared.hpp" #include "../helpers/MiscFunctions.hpp" +#include +#include "../helpers/Log.hpp" +#include +#include +#include "../core/PortalManager.hpp" +#include +#include +#include SSelectionData promptForScreencopySelection() { SSelectionData data; @@ -9,6 +17,8 @@ SSelectionData promptForScreencopySelection() { if (RETVAL.find("screen:") == 0) { data.type = TYPE_OUTPUT; data.output = RETVAL.substr(7); + + data.output.pop_back(); } else if (RETVAL.find("window:") == 0) { // todo } else if (RETVAL.find("region:") == 0) { @@ -25,7 +35,225 @@ SSelectionData promptForScreencopySelection() { data.w = std::stoi(running.substr(running.find_first_of(','))); running = running.substr(running.find_first_of(',') + 1); data.h = std::stoi(running); + + data.output.pop_back(); } return data; -} \ No newline at end of file +} + +wl_shm_format wlSHMFromDrmFourcc(uint32_t format) { + switch (format) { + case DRM_FORMAT_ARGB8888: return WL_SHM_FORMAT_ARGB8888; + case DRM_FORMAT_XRGB8888: return WL_SHM_FORMAT_XRGB8888; + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_RGBX8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_BGRA8888: + case DRM_FORMAT_BGRX8888: + case DRM_FORMAT_NV12: + case DRM_FORMAT_XRGB2101010: + case DRM_FORMAT_XBGR2101010: + case DRM_FORMAT_RGBX1010102: + case DRM_FORMAT_BGRX1010102: + case DRM_FORMAT_ARGB2101010: + case DRM_FORMAT_ABGR2101010: + case DRM_FORMAT_RGBA1010102: + case DRM_FORMAT_BGRA1010102: return (wl_shm_format)format; + default: Debug::log(ERR, "[screencopy] Unknown format {}", format); exit(1); + } +} + +uint32_t drmFourccFromSHM(wl_shm_format format) { + switch (format) { + case WL_SHM_FORMAT_ARGB8888: return DRM_FORMAT_ARGB8888; + case WL_SHM_FORMAT_XRGB8888: return DRM_FORMAT_XRGB8888; + case WL_SHM_FORMAT_RGBA8888: + case WL_SHM_FORMAT_RGBX8888: + case WL_SHM_FORMAT_ABGR8888: + case WL_SHM_FORMAT_XBGR8888: + case WL_SHM_FORMAT_BGRA8888: + case WL_SHM_FORMAT_BGRX8888: + case WL_SHM_FORMAT_NV12: + case WL_SHM_FORMAT_XRGB2101010: + case WL_SHM_FORMAT_XBGR2101010: + case WL_SHM_FORMAT_RGBX1010102: + case WL_SHM_FORMAT_BGRX1010102: + case WL_SHM_FORMAT_ARGB2101010: + case WL_SHM_FORMAT_ABGR2101010: + case WL_SHM_FORMAT_RGBA1010102: + case WL_SHM_FORMAT_BGRA1010102: return (uint32_t)format; + default: Debug::log(ERR, "[screencopy] Unknown format {}", (int)format); exit(1); + } +} + +spa_video_format pwFromDrmFourcc(uint32_t format) { + switch (format) { + case DRM_FORMAT_ARGB8888: return SPA_VIDEO_FORMAT_BGRA; + case DRM_FORMAT_XRGB8888: return SPA_VIDEO_FORMAT_BGRx; + case DRM_FORMAT_RGBA8888: return SPA_VIDEO_FORMAT_ABGR; + case DRM_FORMAT_RGBX8888: return SPA_VIDEO_FORMAT_xBGR; + case DRM_FORMAT_ABGR8888: return SPA_VIDEO_FORMAT_RGBA; + case DRM_FORMAT_XBGR8888: return SPA_VIDEO_FORMAT_RGBx; + case DRM_FORMAT_BGRA8888: return SPA_VIDEO_FORMAT_ARGB; + case DRM_FORMAT_BGRX8888: return SPA_VIDEO_FORMAT_xRGB; + case DRM_FORMAT_NV12: return SPA_VIDEO_FORMAT_NV12; + case DRM_FORMAT_XRGB2101010: return SPA_VIDEO_FORMAT_xRGB_210LE; + case DRM_FORMAT_XBGR2101010: return SPA_VIDEO_FORMAT_xBGR_210LE; + case DRM_FORMAT_RGBX1010102: return SPA_VIDEO_FORMAT_RGBx_102LE; + case DRM_FORMAT_BGRX1010102: return SPA_VIDEO_FORMAT_BGRx_102LE; + case DRM_FORMAT_ARGB2101010: return SPA_VIDEO_FORMAT_ARGB_210LE; + case DRM_FORMAT_ABGR2101010: return SPA_VIDEO_FORMAT_ABGR_210LE; + case DRM_FORMAT_RGBA1010102: return SPA_VIDEO_FORMAT_RGBA_102LE; + case DRM_FORMAT_BGRA1010102: return SPA_VIDEO_FORMAT_BGRA_102LE; + default: Debug::log(ERR, "[screencopy] Unknown format {}", (int)format); exit(1); + } +} + +std::string getRandName(std::string prefix) { + std::srand(time(NULL)); + return prefix + + std::format("{}{}{}{}{}{}", (int)(std::rand() % 10), (int)(std::rand() % 10), (int)(std::rand() % 10), (int)(std::rand() % 10), (int)(std::rand() % 10), + (int)(std::rand() % 10)); +} + +spa_video_format xdph_format_pw_strip_alpha(spa_video_format format) { + switch (format) { + case SPA_VIDEO_FORMAT_BGRA: return SPA_VIDEO_FORMAT_BGRx; + case SPA_VIDEO_FORMAT_ABGR: return SPA_VIDEO_FORMAT_xBGR; + case SPA_VIDEO_FORMAT_RGBA: return SPA_VIDEO_FORMAT_RGBx; + case SPA_VIDEO_FORMAT_ARGB: return SPA_VIDEO_FORMAT_xRGB; + case SPA_VIDEO_FORMAT_ARGB_210LE: return SPA_VIDEO_FORMAT_xRGB_210LE; + case SPA_VIDEO_FORMAT_ABGR_210LE: return SPA_VIDEO_FORMAT_xBGR_210LE; + case SPA_VIDEO_FORMAT_RGBA_102LE: return SPA_VIDEO_FORMAT_RGBx_102LE; + case SPA_VIDEO_FORMAT_BGRA_102LE: return SPA_VIDEO_FORMAT_BGRx_102LE; + default: return SPA_VIDEO_FORMAT_UNKNOWN; + } +} + +spa_pod* build_buffer(spa_pod_builder* b, uint32_t blocks, uint32_t size, uint32_t stride, uint32_t datatype) { + assert(blocks > 0); + assert(datatype > 0); + spa_pod_frame f[1]; + + spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers); + spa_pod_builder_add(b, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(XDPH_PWR_BUFFERS, XDPH_PWR_BUFFERS_MIN, 32), 0); + spa_pod_builder_add(b, SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(blocks), 0); + if (size > 0) { + spa_pod_builder_add(b, SPA_PARAM_BUFFERS_size, SPA_POD_Int(size), 0); + } + if (stride > 0) { + spa_pod_builder_add(b, SPA_PARAM_BUFFERS_stride, SPA_POD_Int(stride), 0); + } + spa_pod_builder_add(b, SPA_PARAM_BUFFERS_align, SPA_POD_Int(XDPH_PWR_ALIGN), 0); + spa_pod_builder_add(b, SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(datatype), 0); + return (spa_pod*)spa_pod_builder_pop(b, &f[0]); +} + +spa_pod* fixate_format(spa_pod_builder* b, spa_video_format format, uint32_t width, uint32_t height, uint32_t framerate, uint64_t* modifier) { + spa_pod_frame f[1]; + + spa_video_format format_without_alpha = xdph_format_pw_strip_alpha(format); + + spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); + spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0); + spa_pod_builder_add(b, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); + /* format */ + if (modifier || format_without_alpha == SPA_VIDEO_FORMAT_UNKNOWN) { + spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0); + } else { + spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(3, format, format, format_without_alpha), 0); + } + /* modifiers */ + if (modifier) { + // implicit modifier + spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY); + spa_pod_builder_long(b, *modifier); + } + spa_pod_builder_add(b, SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&SPA_RECTANGLE(width, height)), 0); + // variable framerate + spa_pod_builder_add(b, SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&SPA_FRACTION(0, 1)), 0); + spa_pod_builder_add(b, SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_CHOICE_RANGE_Fraction(&SPA_FRACTION(framerate, 1), &SPA_FRACTION(1, 1), &SPA_FRACTION(framerate, 1)), 0); + return (spa_pod*)spa_pod_builder_pop(b, &f[0]); +} + +spa_pod* build_format(spa_pod_builder* b, spa_video_format format, uint32_t width, uint32_t height, uint32_t framerate, uint64_t* modifiers, int modifier_count) { + spa_pod_frame f[2]; + int i, c; + + spa_video_format format_without_alpha = xdph_format_pw_strip_alpha(format); + + spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); + spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0); + spa_pod_builder_add(b, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); + /* format */ + if (modifier_count > 0 || format_without_alpha == SPA_VIDEO_FORMAT_UNKNOWN) { + // modifiers are defined only in combinations with their format + // we should not announce the format without alpha + spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0); + } else { + spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(3, format, format, format_without_alpha), 0); + } + /* modifiers */ + if (modifier_count > 0) { + // build an enumeration of modifiers + spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE); + spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0); + // modifiers from the array + for (i = 0, c = 0; i < modifier_count; i++) { + spa_pod_builder_long(b, modifiers[i]); + if (c++ == 0) + spa_pod_builder_long(b, modifiers[i]); + } + spa_pod_builder_pop(b, &f[1]); + } + spa_pod_builder_add(b, SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&SPA_RECTANGLE(width, height)), 0); + // variable framerate + spa_pod_builder_add(b, SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&SPA_FRACTION(0, 1)), 0); + spa_pod_builder_add(b, SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_CHOICE_RANGE_Fraction(&SPA_FRACTION(framerate, 1), &SPA_FRACTION(1, 1), &SPA_FRACTION(framerate, 1)), 0); + return (spa_pod*)spa_pod_builder_pop(b, &f[0]); +} + +void randname(char* buf) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + long r = ts.tv_nsec; + for (int i = 0; i < 6; ++i) { + assert(buf[i] == 'X'); + buf[i] = 'A' + (r & 15) + (r & 16) * 2; + r >>= 5; + } +} + +int anonymous_shm_open() { + char name[] = "/xdph-shm-XXXXXX"; + int retries = 100; + + do { + randname(name + strlen(name) - 6); + + --retries; + // shm_open guarantees that O_CLOEXEC is set + int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); + if (fd >= 0) { + shm_unlink(name); + return fd; + } + } while (retries > 0 && errno == EEXIST); + + return -1; +} + +wl_buffer* import_wl_shm_buffer(int fd, wl_shm_format fmt, int width, int height, int stride) { + int size = stride * height; + + if (fd < 0) + return NULL; + + wl_shm_pool* pool = wl_shm_create_pool(g_pPortalManager->m_sWaylandConnection.shm, fd, size); + wl_buffer* buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, fmt); + wl_shm_pool_destroy(pool); + + return buffer; +} diff --git a/src/shared/ScreencopyShared.hpp b/src/shared/ScreencopyShared.hpp index c91e622..cac19c8 100644 --- a/src/shared/ScreencopyShared.hpp +++ b/src/shared/ScreencopyShared.hpp @@ -2,6 +2,22 @@ #include #include +extern "C" { +#include +#define SPA_VERSION_POD_BUILDER_CALLBACKS .version = 0 +#include +#include +#include +#include +#include +#include +#define SPA_VERSION_POD_BUILDER_CALLBACKS 0 +} +#include + +#define XDPH_PWR_BUFFERS 2 +#define XDPH_PWR_BUFFERS_MIN 2 +#define XDPH_PWR_ALIGN 16 enum eSelectionType { @@ -19,4 +35,15 @@ struct SSelectionData { uint32_t x = 0, y = 0, w = 0, h = 0; // for TYPE_GEOMETRY }; -SSelectionData promptForScreencopySelection(); \ No newline at end of file +struct wl_buffer; + +SSelectionData promptForScreencopySelection(); +uint32_t drmFourccFromSHM(wl_shm_format format); +spa_video_format pwFromDrmFourcc(uint32_t format); +wl_shm_format wlSHMFromDrmFourcc(uint32_t format); +std::string getRandName(std::string prefix); +spa_pod* build_format(spa_pod_builder* b, spa_video_format format, uint32_t width, uint32_t height, uint32_t framerate, uint64_t* modifiers, int modifier_count); +spa_pod* fixate_format(spa_pod_builder* b, spa_video_format format, uint32_t width, uint32_t height, uint32_t framerate, uint64_t* modifier); +spa_pod* build_buffer(spa_pod_builder* b, uint32_t blocks, uint32_t size, uint32_t stride, uint32_t datatype); +int anonymous_shm_open(); +wl_buffer* import_wl_shm_buffer(int fd, wl_shm_format fmt, int width, int height, int stride); \ No newline at end of file