mirror of
https://github.com/hyprwm/xdg-desktop-portal-hyprland.git
synced 2024-11-23 22:55:58 +01:00
today's work. obs crashes while screensharing.
This commit is contained in:
parent
b32c560b31
commit
daa9a2386b
10 changed files with 1076 additions and 36 deletions
|
@ -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)
|
||||
##
|
||||
|
|
|
@ -5,8 +5,12 @@
|
|||
#include <protocols/hyprland-toplevel-export-v1-protocol.h>
|
||||
#include <protocols/wlr-foreign-toplevel-management-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 <sys/poll.h>
|
||||
|
||||
#include <thread>
|
||||
|
||||
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<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() {
|
||||
|
@ -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<CTimer*> 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));
|
||||
}
|
||||
|
@ -97,3 +204,11 @@ 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;
|
||||
}
|
|
@ -5,31 +5,58 @@
|
|||
#include <wayland-client.h>
|
||||
|
||||
#include "../portals/Screencopy.hpp"
|
||||
#include "../helpers/Timer.hpp"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
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<sdbus::IConnection> m_pConnection;
|
||||
|
||||
struct {
|
||||
wl_display* display = nullptr;
|
||||
} m_sWaylandConnection;
|
||||
|
||||
struct {
|
||||
std::unique_ptr<CScreencopyPortal> 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<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;
|
|
@ -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;
|
||||
|
|
11
src/helpers/Timer.cpp
Normal file
11
src/helpers/Timer.cpp
Normal 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
17
src/helpers/Timer.hpp
Normal 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;
|
||||
};
|
|
@ -3,8 +3,123 @@
|
|||
#include "../helpers/Log.hpp"
|
||||
#include "../helpers/MiscFunctions.hpp"
|
||||
|
||||
#include <libdrm/drm_fourcc.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) {
|
||||
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<std::string, sdbus::Variant>{};
|
||||
|
@ -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<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.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) {
|
||||
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;
|
||||
}
|
||||
|
@ -263,3 +466,334 @@ CPipewireConnection::~CPipewireConnection() {
|
|||
if (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;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <protocols/wlr-screencopy-unstable-v1-protocol.h>
|
||||
#include <protocols/hyprland-toplevel-export-v1-protocol.h>
|
||||
#include <sdbus-c++/sdbus-c++.h>
|
||||
#include "../shared/ScreencopyShared.hpp"
|
||||
#include <gbm.h>
|
||||
|
||||
#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<sdbus::IObject> 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<CPipewireConnection> m_pPipewire;
|
||||
|
||||
private:
|
||||
std::unique_ptr<sdbus::IObject> m_pObject;
|
||||
|
||||
std::vector<std::unique_ptr<SSession>> m_vSessions;
|
||||
|
||||
SSession* getSession(sdbus::ObjectPath& path);
|
||||
|
||||
std::unique_ptr<CPipewireConnection> 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<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;
|
||||
};
|
|
@ -1,5 +1,13 @@
|
|||
#include "ScreencopyShared.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 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,22 @@
|
|||
|
||||
#include <string>
|
||||
#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
|
||||
{
|
||||
|
@ -19,4 +35,15 @@ struct SSelectionData {
|
|||
uint32_t x = 0, y = 0, w = 0, h = 0; // for TYPE_GEOMETRY
|
||||
};
|
||||
|
||||
SSelectionData promptForScreencopySelection();
|
||||
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);
|
Loading…
Reference in a new issue