today's work. obs crashes while screensharing.

This commit is contained in:
vaxerski 2023-08-27 20:30:04 +02:00
parent b32c560b31
commit daa9a2386b
10 changed files with 1076 additions and 36 deletions

View file

@ -22,7 +22,7 @@ include_directories(
) )
set(CMAKE_CXX_STANDARD 23) 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...") message(STATUS "Checking deps...")
add_subdirectory(subprojects/sdbus-cpp) 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("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-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("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)
## ##

View file

@ -5,8 +5,12 @@
#include <protocols/hyprland-toplevel-export-v1-protocol.h> #include <protocols/hyprland-toplevel-export-v1-protocol.h>
#include <protocols/wlr-foreign-toplevel-management-unstable-v1-protocol.h> #include <protocols/wlr-foreign-toplevel-management-unstable-v1-protocol.h>
#include <protocols/wlr-screencopy-unstable-v1-protocol.h> #include <protocols/wlr-screencopy-unstable-v1-protocol.h>
#include <protocols/linux-dmabuf-unstable-v1-protocol.h>
#include <pipewire/pipewire.h> #include <pipewire/pipewire.h>
#include <sys/poll.h>
#include <thread>
void handleGlobal(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { 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); g_pPortalManager->onGlobal(data, registry, name, interface, version);
@ -16,11 +20,64 @@ void handleGlobalRemove(void* data, struct wl_registry* registry, uint32_t name)
; // noop ; // noop
} }
inline const struct wl_registry_listener registryListener = { inline const wl_registry_listener registryListener = {
.global = handleGlobal, .global = handleGlobal,
.global_remove = handleGlobalRemove, .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) { 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) if (INTERFACE == zwlr_screencopy_manager_v1_interface.name && m_sPipewire.loop)
m_sPortals.screencopy = std::make_unique<CScreencopyPortal>((zwlr_screencopy_manager_v1*)wl_registry_bind(registry, name, &zwlr_screencopy_manager_v1_interface, version)); m_sPortals.screencopy = std::make_unique<CScreencopyPortal>((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<SOutput>()).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() { void CPortalManager::init() {
@ -72,23 +157,45 @@ void CPortalManager::init() {
if (!m_sPortals.screencopy) if (!m_sPortals.screencopy)
Debug::log(WARN, "Screencopy not started: compositor doesn't support zwlr_screencopy_v1 or pw refused a loop"); 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) { while (1) {
// dbus events // dbus events
m_mEventLock.lock();
while (m_pConnection->processPendingRequest()) { while (m_pConnection->processPendingRequest()) {
; ;
} }
// wayland events std::vector<CTimer*> toRemove;
while (1) { for (auto& t : m_vTimers) {
auto r = wl_display_dispatch_pending(m_sWaylandConnection.display); if (t->passed()) {
wl_display_flush(m_sWaylandConnection.display); t->m_fnCallback();
toRemove.emplace_back(t.get());
if (r <= 0) Debug::log(TRACE, "[core] calling timer {}", (void*)t.get());
break; }
} }
// 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)); std::this_thread::sleep_for(std::chrono::milliseconds(1));
} }
@ -97,3 +204,11 @@ void CPortalManager::init() {
sdbus::IConnection* CPortalManager::getConnection() { sdbus::IConnection* CPortalManager::getConnection() {
return m_pConnection.get(); 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;
}

View file

@ -5,31 +5,58 @@
#include <wayland-client.h> #include <wayland-client.h>
#include "../portals/Screencopy.hpp" #include "../portals/Screencopy.hpp"
#include "../helpers/Timer.hpp"
#include <mutex>
struct pw_loop; 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 { class CPortalManager {
public: public:
void init(); void init();
void onGlobal(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version); 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(); sdbus::IConnection* getConnection();
SOutput* getOutputFromName(const std::string& name);
struct { struct {
pw_loop* loop = nullptr; pw_loop* loop = nullptr;
} m_sPipewire; } m_sPipewire;
private:
std::unique_ptr<sdbus::IConnection> m_pConnection;
struct {
wl_display* display = nullptr;
} m_sWaylandConnection;
struct { struct {
std::unique_ptr<CScreencopyPortal> screencopy; std::unique_ptr<CScreencopyPortal> screencopy;
} m_sPortals; } m_sPortals;
struct {
wl_display* display = nullptr;
void* hyprlandToplevelMgr = nullptr;
void* linuxDmabuf = nullptr;
void* linuxDmabufFeedback = nullptr;
wl_shm* shm = nullptr;
} m_sWaylandConnection;
std::vector<SDMABUFModifier> m_vDMABUFMods;
std::vector<std::unique_ptr<CTimer>> m_vTimers;
private:
std::unique_ptr<sdbus::IConnection> m_pConnection;
std::vector<std::unique_ptr<SOutput>> m_vOutputs;
std::mutex m_mEventLock;
}; };
inline std::unique_ptr<CPortalManager> g_pPortalManager; inline std::unique_ptr<CPortalManager> g_pPortalManager;

View file

@ -5,7 +5,8 @@
enum eLogLevel enum eLogLevel
{ {
INFO = 0, TRACE = 0,
INFO,
LOG, LOG,
WARN, WARN,
ERR, ERR,
@ -18,6 +19,7 @@ namespace Debug {
std::cout << '['; std::cout << '[';
switch (level) { switch (level) {
case TRACE: std::cout << "TRACE"; break;
case INFO: std::cout << "INFO"; break; case INFO: std::cout << "INFO"; break;
case LOG: std::cout << "LOG"; break; case LOG: std::cout << "LOG"; break;
case WARN: std::cout << "WARN"; break; case WARN: std::cout << "WARN"; break;

11
src/helpers/Timer.cpp Normal file
View file

@ -0,0 +1,11 @@
#include "Timer.hpp"
CTimer::CTimer(float ms, std::function<void()> 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));
}

17
src/helpers/Timer.hpp Normal file
View file

@ -0,0 +1,17 @@
#pragma once
#include <functional>
#include <chrono>
class CTimer {
public:
CTimer(float ms, std::function<void()> callback);
bool passed() const;
std::function<void()> m_fnCallback;
private:
std::chrono::high_resolution_clock::time_point m_tStart;
float m_fDuration;
};

View file

@ -3,8 +3,123 @@
#include "../helpers/Log.hpp" #include "../helpers/Log.hpp"
#include "../helpers/MiscFunctions.hpp" #include "../helpers/MiscFunctions.hpp"
#include <libdrm/drm_fourcc.h>
#include <pipewire/pipewire.h> #include <pipewire/pipewire.h>
// --------------- 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<CTimer>(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) { void onCloseRequest(sdbus::MethodCall& call, CScreencopyPortal::SSession* sess) {
if (!sess || !sess->request) if (!sess || !sess->request)
return; 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); 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(); auto reply = call.createReply();
reply << (uint32_t)(SHAREDATA.type == TYPE_INVALID ? 1 : 0); reply << (uint32_t)(SHAREDATA.type == TYPE_INVALID ? 1 : 0);
reply << std::unordered_map<std::string, sdbus::Variant>{}; reply << std::unordered_map<std::string, sdbus::Variant>{};
@ -177,7 +299,7 @@ void CScreencopyPortal::onStart(sdbus::MethodCall& call) {
return; return;
} }
// TODO: start cast startSharing(PSESSION);
auto reply = call.createReply(); auto reply = call.createReply();
reply << (uint32_t)0; reply << (uint32_t)0;
@ -203,11 +325,86 @@ void CScreencopyPortal::onStart(sdbus::MethodCall& call) {
} }
options["source_type"] = type; options["source_type"] = type;
std::vector<sdbus::Struct<uint32_t, std::unordered_map<std::string, sdbus::Variant>>> streams;
std::unordered_map<std::string, sdbus::Variant> streamData;
streamData["position"] = sdbus::Variant{sdbus::Struct<int32_t, int32_t>{0, 0}};
streamData["size"] = sdbus::Variant{sdbus::Struct<int32_t, int32_t>{PSESSION->sharingData.frameInfoSHM.w, PSESSION->sharingData.frameInfoSHM.h}};
streamData["source_type"] = sdbus::Variant{uint32_t{type}};
streams.emplace_back(sdbus::Struct<uint32_t, std::unordered_map<std::string, sdbus::Variant>>{PSESSION->sharingData.nodeID, streamData});
options["streams"] = streams;
reply << options; reply << options;
reply.send(); 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<CTimer>(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) { CScreencopyPortal::SSession* CScreencopyPortal::getSession(sdbus::ObjectPath& path) {
for (auto& s : m_vSessions) { for (auto& s : m_vSessions) {
if (s->sessionHandle == path) if (s->sessionHandle == path)
@ -235,6 +432,12 @@ CScreencopyPortal::CScreencopyPortal(zwlr_screencopy_manager_v1* mgr) {
Debug::log(LOG, "[screencopy] init successful"); 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() { bool CPipewireConnection::good() {
return m_pContext && m_pCore; return m_pContext && m_pCore;
} }
@ -263,3 +466,334 @@ CPipewireConnection::~CPipewireConnection() {
if (m_pContext) if (m_pContext)
pw_context_destroy(m_pContext); pw_context_destroy(m_pContext);
} }
// --------------- 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<SBuffer>()).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<SPWStream>(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;
}

View file

@ -1,8 +1,12 @@
#pragma once #pragma once
#include <protocols/wlr-screencopy-unstable-v1-protocol.h> #include <protocols/wlr-screencopy-unstable-v1-protocol.h>
#include <protocols/hyprland-toplevel-export-v1-protocol.h>
#include <sdbus-c++/sdbus-c++.h> #include <sdbus-c++/sdbus-c++.h>
#include "../shared/ScreencopyShared.hpp" #include "../shared/ScreencopyShared.hpp"
#include <gbm.h>
#define FRAMERATE 60
enum cursorModes enum cursorModes
{ {
@ -18,25 +22,41 @@ enum sourceTypes
VIRTUAL = 4, VIRTUAL = 4,
}; };
enum frameStatus
{
FRAME_NONE = 0,
FRAME_QUEUED,
FRAME_READY,
FRAME_FAILED,
};
struct pw_context; struct pw_context;
struct pw_core; struct pw_core;
struct pw_stream;
struct pw_buffer;
class CPipewireConnection { struct SBuffer {
public: bool isDMABUF = false;
CPipewireConnection(); uint32_t w = 0, h = 0, fmt = 0;
~CPipewireConnection(); int planeCount = 0;
bool good(); int fd[4];
uint32_t size[4], stride[4], offset[4];
private: gbm_bo* bo = nullptr;
pw_context* m_pContext = nullptr;
pw_core* m_pCore = nullptr; wl_buffer* wlBuffer = nullptr;
pw_buffer* pwBuffer = nullptr;
}; };
class CPipewireConnection;
class CScreencopyPortal { class CScreencopyPortal {
public: public:
CScreencopyPortal(zwlr_screencopy_manager_v1*); CScreencopyPortal(zwlr_screencopy_manager_v1*);
void appendToplevelExport(void*);
void onCreateSession(sdbus::MethodCall& call); void onCreateSession(sdbus::MethodCall& call);
void onSelectSources(sdbus::MethodCall& call); void onSelectSources(sdbus::MethodCall& call);
void onStart(sdbus::MethodCall& call); void onStart(sdbus::MethodCall& call);
@ -50,23 +70,81 @@ class CScreencopyPortal {
std::unique_ptr<sdbus::IObject> request, session; std::unique_ptr<sdbus::IObject> request, session;
SSelectionData selection; SSelectionData selection;
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 onCloseRequest(sdbus::MethodCall&);
void onCloseSession(sdbus::MethodCall&); void onCloseSession(sdbus::MethodCall&);
}; };
void startFrameCopy(SSession* pSession);
std::unique_ptr<CPipewireConnection> m_pPipewire;
private: private:
std::unique_ptr<sdbus::IObject> m_pObject; std::unique_ptr<sdbus::IObject> m_pObject;
std::vector<std::unique_ptr<SSession>> m_vSessions; std::vector<std::unique_ptr<SSession>> m_vSessions;
SSession* getSession(sdbus::ObjectPath& path); SSession* getSession(sdbus::ObjectPath& path);
void startSharing(SSession* pSession);
std::unique_ptr<CPipewireConnection> m_pPipewire;
struct { struct {
zwlr_screencopy_manager_v1* screencopy = nullptr; zwlr_screencopy_manager_v1* screencopy = nullptr;
hyprland_toplevel_export_manager_v1* toplevel = nullptr;
} m_sState; } m_sState;
const std::string INTERFACE_NAME = "org.freedesktop.impl.portal.ScreenCast"; const std::string INTERFACE_NAME = "org.freedesktop.impl.portal.ScreenCast";
const std::string OBJECT_PATH = "/org/freedesktop/portal/desktop"; 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<std::unique_ptr<SBuffer>> buffers;
};
SPWStream* streamFromSession(CScreencopyPortal::SSession* pSession);
private:
std::vector<std::unique_ptr<SPWStream>> 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;
};

View file

@ -1,5 +1,13 @@
#include "ScreencopyShared.hpp" #include "ScreencopyShared.hpp"
#include "../helpers/MiscFunctions.hpp" #include "../helpers/MiscFunctions.hpp"
#include <wayland-client.h>
#include "../helpers/Log.hpp"
#include <libdrm/drm_fourcc.h>
#include <assert.h>
#include "../core/PortalManager.hpp"
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
SSelectionData promptForScreencopySelection() { SSelectionData promptForScreencopySelection() {
SSelectionData data; SSelectionData data;
@ -9,6 +17,8 @@ SSelectionData promptForScreencopySelection() {
if (RETVAL.find("screen:") == 0) { if (RETVAL.find("screen:") == 0) {
data.type = TYPE_OUTPUT; data.type = TYPE_OUTPUT;
data.output = RETVAL.substr(7); data.output = RETVAL.substr(7);
data.output.pop_back();
} else if (RETVAL.find("window:") == 0) { } else if (RETVAL.find("window:") == 0) {
// todo // todo
} else if (RETVAL.find("region:") == 0) { } else if (RETVAL.find("region:") == 0) {
@ -25,7 +35,225 @@ SSelectionData promptForScreencopySelection() {
data.w = std::stoi(running.substr(running.find_first_of(','))); data.w = std::stoi(running.substr(running.find_first_of(',')));
running = running.substr(running.find_first_of(',') + 1); running = running.substr(running.find_first_of(',') + 1);
data.h = std::stoi(running); data.h = std::stoi(running);
data.output.pop_back();
} }
return data; return data;
} }
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;
}

View file

@ -2,6 +2,22 @@
#include <string> #include <string>
#include <cstdint> #include <cstdint>
extern "C" {
#include <spa/pod/builder.h>
#define SPA_VERSION_POD_BUILDER_CALLBACKS .version = 0
#include <spa/buffer/meta.h>
#include <spa/utils/result.h>
#include <spa/param/props.h>
#include <spa/param/format-utils.h>
#include <spa/param/video/format-utils.h>
#include <spa/pod/dynamic.h>
#define SPA_VERSION_POD_BUILDER_CALLBACKS 0
}
#include <wayland-client.h>
#define XDPH_PWR_BUFFERS 2
#define XDPH_PWR_BUFFERS_MIN 2
#define XDPH_PWR_ALIGN 16
enum eSelectionType enum eSelectionType
{ {
@ -19,4 +35,15 @@ struct SSelectionData {
uint32_t x = 0, y = 0, w = 0, h = 0; // for TYPE_GEOMETRY uint32_t x = 0, y = 0, w = 0, h = 0; // for TYPE_GEOMETRY
}; };
struct wl_buffer;
SSelectionData promptForScreencopySelection(); 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);