Skip to content

Commit

Permalink
window sharing
Browse files Browse the repository at this point in the history
  • Loading branch information
vaxerski committed Aug 28, 2023
1 parent f0afc1a commit c85ae51
Show file tree
Hide file tree
Showing 8 changed files with 347 additions and 26 deletions.
4 changes: 4 additions & 0 deletions src/core/PortalManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,10 @@ void CPortalManager::onGlobal(void* data, struct wl_registry* registry, uint32_t

else if (INTERFACE == wl_shm_interface.name)
m_sWaylandConnection.shm = (wl_shm*)wl_registry_bind(registry, name, &wl_shm_interface, version);

else if (INTERFACE == zwlr_foreign_toplevel_manager_v1_interface.name)
m_sHelpers.toplevel =
std::make_unique<CToplevelManager>((zwlr_foreign_toplevel_manager_v1*)wl_registry_bind(registry, name, &zwlr_foreign_toplevel_manager_v1_interface, version));
}

void CPortalManager::onGlobalRemoved(void* data, struct wl_registry* registry, uint32_t name) {
Expand Down
5 changes: 5 additions & 0 deletions src/core/PortalManager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include "../portals/Screencopy.hpp"
#include "../helpers/Timer.hpp"
#include "../shared/ToplevelManager.hpp"
#include <gbm.h>
#include <xf86drm.h>

Expand Down Expand Up @@ -43,6 +44,10 @@ class CPortalManager {
std::unique_ptr<CScreencopyPortal> screencopy;
} m_sPortals;

struct {
std::unique_ptr<CToplevelManager> toplevel;
} m_sHelpers;

struct {
wl_display* display = nullptr;
void* hyprlandToplevelMgr = nullptr;
Expand Down
172 changes: 161 additions & 11 deletions src/portals/Screencopy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

// --------------- 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) {
static void wlrOnBuffer(void* data, 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);
Expand All @@ -23,15 +23,15 @@ static void wlrOnBuffer(void* data, struct zwlr_screencopy_frame_v1* frame, uint
// todo: done if ver < 3
}

static void wlrOnFlags(void* data, struct zwlr_screencopy_frame_v1* frame, uint32_t flags) {
static void wlrOnFlags(void* data, 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) {
static void wlrOnReady(void* data, 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);
Expand All @@ -52,15 +52,15 @@ static void wlrOnReady(void* data, struct zwlr_screencopy_frame_v1* frame, uint3
PSESSION->sharingData.frameCallback = nullptr;
}

static void wlrOnFailed(void* data, struct zwlr_screencopy_frame_v1* frame) {
static void wlrOnFailed(void* data, 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) {
static void wlrOnDamage(void* data, 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);
Expand All @@ -75,7 +75,7 @@ static void wlrOnDamage(void* data, struct zwlr_screencopy_frame_v1* frame, uint
Debug::log(TRACE, "[sc] wlr damage: {} {} {} {}", x, y, width, height);
}

static void wlrOnDmabuf(void* data, struct zwlr_screencopy_frame_v1* frame, uint32_t format, uint32_t width, uint32_t height) {
static void wlrOnDmabuf(void* data, 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);
Expand All @@ -85,7 +85,7 @@ static void wlrOnDmabuf(void* data, struct zwlr_screencopy_frame_v1* frame, uint
PSESSION->sharingData.frameInfoDMA.fmt = format;
}

static void wlrOnBufferDone(void* data, struct zwlr_screencopy_frame_v1* frame) {
static void wlrOnBufferDone(void* data, zwlr_screencopy_frame_v1* frame) {
const auto PSESSION = (CScreencopyPortal::SSession*)data;

Debug::log(TRACE, "[sc] wlrOnBufferDone for {}", (void*)PSESSION);
Expand Down Expand Up @@ -143,6 +143,141 @@ static const zwlr_screencopy_frame_v1_listener wlrFrameListener = {
.buffer_done = wlrOnBufferDone,
};

static void hlOnBuffer(void* data, hyprland_toplevel_export_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] hlOnBuffer 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 hlOnFlags(void* data, hyprland_toplevel_export_frame_v1* frame, uint32_t flags) {
const auto PSESSION = (CScreencopyPortal::SSession*)data;

Debug::log(TRACE, "[sc] hlOnFlags for {}", (void*)PSESSION);

// todo: maybe check for y invert?
}

static void hlOnReady(void* data, hyprland_toplevel_export_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] hlOnReady for {}", (void*)PSESSION);

PSESSION->sharingData.status = FRAME_READY;

PSESSION->sharingData.tvSec = ((((uint64_t)tv_sec_hi) << 32) + (uint64_t)tv_sec_lo);
PSESSION->sharingData.tvNsec = tv_nsec;
PSESSION->sharingData.tvTimestampNs = PSESSION->sharingData.tvSec * SPA_NSEC_PER_SEC + PSESSION->sharingData.tvNsec;

Debug::log(TRACE, "[sc] frame timestamp sec: {} nsec: {} combined: {}ns", PSESSION->sharingData.tvSec, PSESSION->sharingData.tvNsec, PSESSION->sharingData.tvTimestampNs);

g_pPortalManager->m_sPortals.screencopy->m_pPipewire->enqueue(PSESSION);

g_pPortalManager->m_sPortals.screencopy->queueNextShareFrame(PSESSION);

hyprland_toplevel_export_frame_v1_destroy(frame);
PSESSION->sharingData.windowFrameCallback = nullptr;
}

static void hlOnFailed(void* data, hyprland_toplevel_export_frame_v1* frame) {
const auto PSESSION = (CScreencopyPortal::SSession*)data;

Debug::log(TRACE, "[sc] hlOnFailed for {}", (void*)PSESSION);

PSESSION->sharingData.status = FRAME_FAILED;
}

static void hlOnDamage(void* data, hyprland_toplevel_export_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] hlOnDamage for {}", (void*)PSESSION);

if (PSESSION->sharingData.damageCount > 3) {
PSESSION->sharingData.damage[0] = {0, 0, PSESSION->sharingData.frameInfoDMA.w, PSESSION->sharingData.frameInfoDMA.h};
return;
}

PSESSION->sharingData.damage[PSESSION->sharingData.damageCount++] = {x, y, width, height};

Debug::log(TRACE, "[sc] hl damage: {} {} {} {}", x, y, width, height);
}

static void hlOnDmabuf(void* data, hyprland_toplevel_export_frame_v1* frame, uint32_t format, uint32_t width, uint32_t height) {
const auto PSESSION = (CScreencopyPortal::SSession*)data;

Debug::log(TRACE, "[sc] hlOnDmabuf for {}", (void*)PSESSION);

PSESSION->sharingData.frameInfoDMA.w = width;
PSESSION->sharingData.frameInfoDMA.h = height;
PSESSION->sharingData.frameInfoDMA.fmt = format;
}

static void hlOnBufferDone(void* data, hyprland_toplevel_export_frame_v1* frame) {
const auto PSESSION = (CScreencopyPortal::SSession*)data;

Debug::log(TRACE, "[sc] hlOnBufferDone for {}", (void*)PSESSION);

const auto PSTREAM = g_pPortalManager->m_sPortals.screencopy->m_pPipewire->streamFromSession(PSESSION);

if (!PSTREAM) {
Debug::log(TRACE, "[sc] hlOnBufferDone: no stream");
hyprland_toplevel_export_frame_v1_destroy(frame);
PSESSION->sharingData.windowFrameCallback = nullptr;
PSESSION->sharingData.status = FRAME_NONE;
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] hl format {} size {}x{}", (int)PSESSION->sharingData.frameInfoSHM.fmt, PSESSION->sharingData.frameInfoSHM.w, PSESSION->sharingData.frameInfoSHM.h);

const auto FMT = PSTREAM->isDMA ? PSESSION->sharingData.frameInfoDMA.fmt : PSESSION->sharingData.frameInfoSHM.fmt;
if ((PSTREAM->pwVideoInfo.format != pwFromDrmFourcc(FMT) && PSTREAM->pwVideoInfo.format != pwStripAlpha(pwFromDrmFourcc(FMT))) ||
(PSTREAM->pwVideoInfo.size.width != PSESSION->sharingData.frameInfoDMA.w || PSTREAM->pwVideoInfo.size.height != PSESSION->sharingData.frameInfoDMA.h)) {
Debug::log(LOG, "[sc] Incompatible formats, renegotiate stream");
PSESSION->sharingData.status = FRAME_RENEG;
hyprland_toplevel_export_frame_v1_destroy(frame);
PSESSION->sharingData.windowFrameCallback = nullptr;
g_pPortalManager->m_sPortals.screencopy->m_pPipewire->updateStreamParam(PSTREAM);
g_pPortalManager->m_sPortals.screencopy->queueNextShareFrame(PSESSION);
PSESSION->sharingData.status = FRAME_NONE;
return;
}

if (!PSTREAM->currentPWBuffer) {
Debug::log(TRACE, "[sc] wlrOnBufferDone: dequeue, no current buffer");
g_pPortalManager->m_sPortals.screencopy->m_pPipewire->dequeue(PSESSION);
}

if (!PSTREAM->currentPWBuffer) {
hyprland_toplevel_export_frame_v1_destroy(frame);
PSESSION->sharingData.windowFrameCallback = nullptr;
Debug::log(LOG, "[screencopy/pipewire] Out of buffers");
PSESSION->sharingData.status = FRAME_NONE;
return;
}

hyprland_toplevel_export_frame_v1_copy(frame, PSTREAM->currentPWBuffer->wlBuffer, false);

Debug::log(TRACE, "[sc] wlr frame copied");
}

static const hyprland_toplevel_export_frame_v1_listener hyprlandFrameListener = {
.buffer = hlOnBuffer,
.damage = hlOnDamage,
.flags = hlOnFlags,
.ready = hlOnReady,
.failed = hlOnFailed,
.linux_dmabuf = hlOnDmabuf,
.buffer_done = hlOnBufferDone,
};

// --------------------------------------------------------- //

void onCloseRequest(sdbus::MethodCall& call, CScreencopyPortal::SSession* sess) {
Expand Down Expand Up @@ -403,13 +538,14 @@ void CScreencopyPortal::startFrameCopy(CScreencopyPortal::SSession* pSession) {
return;
}

if (!POUTPUT) {
if (!POUTPUT && (pSession->selection.type == TYPE_GEOMETRY || pSession->selection.type == TYPE_OUTPUT)) {
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");
if ((pSession->sharingData.frameCallback && (pSession->selection.type == TYPE_GEOMETRY || pSession->selection.type == TYPE_OUTPUT)) ||
(pSession->sharingData.windowFrameCallback && pSession->selection.type == TYPE_WINDOW)) {
Debug::log(ERR, "[screencopy] tried scheduling on already scheduled cb (type {})", (int)pSession->selection.type);
return;
}

Expand All @@ -420,14 +556,25 @@ void CScreencopyPortal::startFrameCopy(CScreencopyPortal::SSession* pSession) {
} else if (pSession->selection.type == TYPE_OUTPUT) {
pSession->sharingData.frameCallback = zwlr_screencopy_manager_v1_capture_output(m_sState.screencopy, pSession->cursorMode, POUTPUT->output);
pSession->sharingData.transform = POUTPUT->transform;
} else if (pSession->selection.type == TYPE_WINDOW) {
if (!pSession->selection.windowHandle) {
Debug::log(ERR, "[screencopy] selected invalid window?");
return;
}
pSession->sharingData.windowFrameCallback =
hyprland_toplevel_export_manager_v1_capture_toplevel_with_wlr_toplevel_handle(m_sState.toplevel, pSession->cursorMode, pSession->selection.windowHandle);
pSession->sharingData.transform = WL_OUTPUT_TRANSFORM_NORMAL;
} 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);
if (pSession->sharingData.frameCallback)
zwlr_screencopy_frame_v1_add_listener(pSession->sharingData.frameCallback, &wlrFrameListener, pSession);
else if (pSession->sharingData.windowFrameCallback)
hyprland_toplevel_export_frame_v1_add_listener(pSession->sharingData.windowFrameCallback, &hyprlandFrameListener, pSession);

Debug::log(LOG, "[screencopy] frame callbacks initialized");
}
Expand All @@ -441,6 +588,9 @@ void CScreencopyPortal::queueNextShareFrame(CScreencopyPortal::SSession* pSessio
g_pPortalManager->m_vTimers.emplace_back(
std::make_unique<CTimer>(1000.0 / pSession->sharingData.framerate, [pSession]() { g_pPortalManager->m_sPortals.screencopy->startFrameCopy(pSession); }));
}
bool CScreencopyPortal::hasToplevelCapabilities() {
return m_sState.toplevel;
}

CScreencopyPortal::SSession* CScreencopyPortal::getSession(sdbus::ObjectPath& path) {
for (auto& s : m_vSessions) {
Expand Down
20 changes: 11 additions & 9 deletions src/portals/Screencopy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,16 @@ class CScreencopyPortal {
SSelectionData selection;

struct {
bool active = false;
zwlr_screencopy_frame_v1* frameCallback = nullptr;
frameStatus status = FRAME_NONE;
uint64_t tvSec = 0;
uint32_t tvNsec = 0;
uint64_t tvTimestampNs = 0;
uint32_t nodeID = 0;
uint32_t framerate = 60;
wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL;
bool active = false;
zwlr_screencopy_frame_v1* frameCallback = nullptr;
hyprland_toplevel_export_frame_v1* windowFrameCallback = nullptr;
frameStatus status = FRAME_NONE;
uint64_t tvSec = 0;
uint32_t tvNsec = 0;
uint64_t tvTimestampNs = 0;
uint32_t nodeID = 0;
uint32_t framerate = 60;
wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL;

struct {
uint32_t w = 0, h = 0, size = 0, stride = 0, fmt = 0;
Expand All @@ -100,6 +101,7 @@ class CScreencopyPortal {

void startFrameCopy(SSession* pSession);
void queueNextShareFrame(SSession* pSession);
bool hasToplevelCapabilities();

std::unique_ptr<CPipewireConnection> m_pPipewire;

Expand Down
49 changes: 47 additions & 2 deletions src/shared/ScreencopyShared.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,43 @@
#include <sys/stat.h>
#include <fcntl.h>

std::string sanitizeNameForWindowList(const std::string& name) {
std::string result = name;
for (size_t i = 1; i < result.size(); ++i) {
if (result[i - 1] == '>' && result[i] == ']')
result[i] = ' ';
if (result[i] == '\"')
result[i] = ' ';
}
return result;
}

std::string buildWindowList() {
std::string result = "";
if (!g_pPortalManager->m_sPortals.screencopy->hasToplevelCapabilities())
return result;

for (auto& e : g_pPortalManager->m_sHelpers.toplevel->m_vToplevels) {

result += std::format("{}[HC>]{}[HT>]{}[HE>]", (uint32_t)(((uint64_t)e->handle) & 0xFFFFFFFF), sanitizeNameForWindowList(e->windowClass),
sanitizeNameForWindowList(e->windowTitle));
}

return result;
}

SSelectionData promptForScreencopySelection() {
SSelectionData data;

const auto RETVAL = execAndGet("hyprland-share-picker");
const char* WAYLAND_DISPLAY = getenv("WAYLAND_DISPLAY");
const char* XCURSOR_SIZE = getenv("XCURSOR_SIZE");
const char* HYPRLAND_INSTANCE_SIGNATURE = getenv("HYPRLAND_INSTANCE_SIGNATURE");

std::string cmd =
std::format("WAYLAND_DISPLAY={} QT_QPA_PLATFORM=\"wayland\" XCURSOR_SIZE={} HYPRLAND_INSTANCE_SIGNATURE={} XDPH_WINDOW_SHARING_LIST=\"{}\" hyprland-share-picker",
WAYLAND_DISPLAY ? WAYLAND_DISPLAY : "", XCURSOR_SIZE ? XCURSOR_SIZE : "24", HYPRLAND_INSTANCE_SIGNATURE ? HYPRLAND_INSTANCE_SIGNATURE : "0", buildWindowList());

const auto RETVAL = execAndGet(cmd.c_str());

Debug::log(LOG, "[sc] Selection: {}", RETVAL);

Expand All @@ -22,7 +55,19 @@ SSelectionData promptForScreencopySelection() {

data.output.pop_back();
} else if (RETVAL.find("window:") == 0) {
// todo
data.type = TYPE_WINDOW;
uint32_t handleLo = std::stoull(RETVAL.substr(7));
data.windowHandle = nullptr;

for (auto& e : g_pPortalManager->m_sHelpers.toplevel->m_vToplevels) {
uint32_t handleLoE = (uint32_t)(((uint64_t)e->handle) & 0xFFFFFFFF);

if (handleLoE == handleLo) {
data.windowHandle = e->handle;
break;
}
}

} else if (RETVAL.find("region:") == 0) {
std::string running = RETVAL;
running = running.substr(7);
Expand Down
Loading

0 comments on commit c85ae51

Please sign in to comment.