From c85ae515319e7f537815dd467fcf5a0f4546cf3c Mon Sep 17 00:00:00 2001 From: vaxerski <43317083+vaxerski@users.noreply.github.com> Date: Mon, 28 Aug 2023 16:34:35 +0200 Subject: [PATCH] window sharing --- src/core/PortalManager.cpp | 4 + src/core/PortalManager.hpp | 5 + src/portals/Screencopy.cpp | 172 ++++++++++++++++++++++++++++++-- src/portals/Screencopy.hpp | 20 ++-- src/shared/ScreencopyShared.cpp | 49 ++++++++- src/shared/ScreencopyShared.hpp | 10 +- src/shared/ToplevelManager.cpp | 87 ++++++++++++++++ src/shared/ToplevelManager.hpp | 26 +++++ 8 files changed, 347 insertions(+), 26 deletions(-) create mode 100644 src/shared/ToplevelManager.cpp create mode 100644 src/shared/ToplevelManager.hpp diff --git a/src/core/PortalManager.cpp b/src/core/PortalManager.cpp index 5aac211..bc7d437 100644 --- a/src/core/PortalManager.cpp +++ b/src/core/PortalManager.cpp @@ -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((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) { diff --git a/src/core/PortalManager.hpp b/src/core/PortalManager.hpp index a11808b..e3908a8 100644 --- a/src/core/PortalManager.hpp +++ b/src/core/PortalManager.hpp @@ -6,6 +6,7 @@ #include "../portals/Screencopy.hpp" #include "../helpers/Timer.hpp" +#include "../shared/ToplevelManager.hpp" #include #include @@ -43,6 +44,10 @@ class CPortalManager { std::unique_ptr screencopy; } m_sPortals; + struct { + std::unique_ptr toplevel; + } m_sHelpers; + struct { wl_display* display = nullptr; void* hyprlandToplevelMgr = nullptr; diff --git a/src/portals/Screencopy.cpp b/src/portals/Screencopy.cpp index ad7c870..a02b79d 100644 --- a/src/portals/Screencopy.cpp +++ b/src/portals/Screencopy.cpp @@ -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); @@ -23,7 +23,7 @@ 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); @@ -31,7 +31,7 @@ static void wlrOnFlags(void* data, struct zwlr_screencopy_frame_v1* frame, uint3 // 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); @@ -52,7 +52,7 @@ 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); @@ -60,7 +60,7 @@ static void wlrOnFailed(void* data, struct zwlr_screencopy_frame_v1* frame) { 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); @@ -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); @@ -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); @@ -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) { @@ -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; } @@ -420,6 +556,14 @@ 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; @@ -427,7 +571,10 @@ void CScreencopyPortal::startFrameCopy(CScreencopyPortal::SSession* pSession) { 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"); } @@ -441,6 +588,9 @@ void CScreencopyPortal::queueNextShareFrame(CScreencopyPortal::SSession* pSessio g_pPortalManager->m_vTimers.emplace_back( std::make_unique(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) { diff --git a/src/portals/Screencopy.hpp b/src/portals/Screencopy.hpp index 2ec06db..e6d2651 100644 --- a/src/portals/Screencopy.hpp +++ b/src/portals/Screencopy.hpp @@ -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; @@ -100,6 +101,7 @@ class CScreencopyPortal { void startFrameCopy(SSession* pSession); void queueNextShareFrame(SSession* pSession); + bool hasToplevelCapabilities(); std::unique_ptr m_pPipewire; diff --git a/src/shared/ScreencopyShared.cpp b/src/shared/ScreencopyShared.cpp index c23b90e..6bba71e 100644 --- a/src/shared/ScreencopyShared.cpp +++ b/src/shared/ScreencopyShared.cpp @@ -9,10 +9,43 @@ #include #include +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); @@ -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); diff --git a/src/shared/ScreencopyShared.hpp b/src/shared/ScreencopyShared.hpp index 4145f58..15fe6de 100644 --- a/src/shared/ScreencopyShared.hpp +++ b/src/shared/ScreencopyShared.hpp @@ -28,11 +28,13 @@ enum eSelectionType TYPE_WORKSPACE, }; +struct zwlr_foreign_toplevel_handle_v1; + struct SSelectionData { - eSelectionType type = TYPE_INVALID; - std::string output; - uint64_t windowHandle = 0; - uint32_t x = 0, y = 0, w = 0, h = 0; // for TYPE_GEOMETRY + eSelectionType type = TYPE_INVALID; + std::string output; + zwlr_foreign_toplevel_handle_v1* windowHandle = nullptr; + uint32_t x = 0, y = 0, w = 0, h = 0; // for TYPE_GEOMETRY }; struct wl_buffer; diff --git a/src/shared/ToplevelManager.cpp b/src/shared/ToplevelManager.cpp new file mode 100644 index 0000000..ded80cf --- /dev/null +++ b/src/shared/ToplevelManager.cpp @@ -0,0 +1,87 @@ +#include "ToplevelManager.hpp" +#include "../helpers/Log.hpp" + +static void toplevelTitle(void* data, zwlr_foreign_toplevel_handle_v1* zwlr_foreign_toplevel_handle_v1, const char* title) { + const auto PTL = (SToplevelHandle*)data; + + if (title) + PTL->windowTitle = title; + + Debug::log(TRACE, "[toplevel] toplevel at {} set title to {}", data, PTL->windowTitle); +} + +static void toplevelAppid(void* data, zwlr_foreign_toplevel_handle_v1* zwlr_foreign_toplevel_handle_v1, const char* app_id) { + const auto PTL = (SToplevelHandle*)data; + + if (app_id) + PTL->windowClass = app_id; + + Debug::log(TRACE, "[toplevel] toplevel at {} set class to {}", data, PTL->windowClass); +} + +static void toplevelEnterOutput(void* data, zwlr_foreign_toplevel_handle_v1* zwlr_foreign_toplevel_handle_v1, wl_output* output) { + ; +} + +static void toplevelLeaveOutput(void* data, zwlr_foreign_toplevel_handle_v1* zwlr_foreign_toplevel_handle_v1, wl_output* output) { + ; +} + +static void toplevelState(void* data, zwlr_foreign_toplevel_handle_v1* zwlr_foreign_toplevel_handle_v1, wl_array* state) { + ; +} + +static void toplevelDone(void* data, zwlr_foreign_toplevel_handle_v1* zwlr_foreign_toplevel_handle_v1) { + ; +} + +static void toplevelClosed(void* data, zwlr_foreign_toplevel_handle_v1* zwlr_foreign_toplevel_handle_v1) { + const auto PTL = (SToplevelHandle*)data; + + std::erase_if(PTL->mgr->m_vToplevels, [&](const auto& e) { return e.get() == PTL; }); + + Debug::log(TRACE, "[toplevel] toplevel at {} closed", data); +} + +static void toplevelParent(void* data, zwlr_foreign_toplevel_handle_v1* zwlr_foreign_toplevel_handle_v1, struct zwlr_foreign_toplevel_handle_v1* parent) { + ; +} + +inline const zwlr_foreign_toplevel_handle_v1_listener toplevelListener = { + .title = toplevelTitle, + .app_id = toplevelAppid, + .output_enter = toplevelEnterOutput, + .output_leave = toplevelLeaveOutput, + .state = toplevelState, + .done = toplevelDone, + .closed = toplevelClosed, + .parent = toplevelParent, +}; + +static void managerToplevel(void* data, zwlr_foreign_toplevel_manager_v1* mgr, zwlr_foreign_toplevel_handle_v1* toplevel) { + const auto PMGR = (CToplevelManager*)data; + + Debug::log(TRACE, "[toplevel] New toplevel at {}", (void*)toplevel); + + const auto PTL = PMGR->m_vToplevels.emplace_back(std::make_unique("?", "?", toplevel, PMGR)).get(); + + zwlr_foreign_toplevel_handle_v1_add_listener(toplevel, &toplevelListener, PTL); +} + +static void managerFinished(void* data, zwlr_foreign_toplevel_manager_v1* mgr) { + const auto PMGR = (CToplevelManager*)data; + + Debug::log(ERR, "[toplevel] Compositor sent .finished???"); + + PMGR->m_vToplevels.clear(); +} + +inline const zwlr_foreign_toplevel_manager_v1_listener managerListener = { + .toplevel = managerToplevel, + .finished = managerFinished, +}; + +CToplevelManager::CToplevelManager(zwlr_foreign_toplevel_manager_v1* mgr) { + m_pManager = mgr; + zwlr_foreign_toplevel_manager_v1_add_listener(mgr, &managerListener, this); +} \ No newline at end of file diff --git a/src/shared/ToplevelManager.hpp b/src/shared/ToplevelManager.hpp new file mode 100644 index 0000000..8232a51 --- /dev/null +++ b/src/shared/ToplevelManager.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include +#include +#include + +class CToplevelManager; + +struct SToplevelHandle { + std::string windowClass; + std::string windowTitle; + zwlr_foreign_toplevel_handle_v1* handle = nullptr; + CToplevelManager* mgr = nullptr; +}; + +class CToplevelManager { + public: + CToplevelManager(zwlr_foreign_toplevel_manager_v1* mgr); + + std::vector> m_vToplevels; + + private: + zwlr_foreign_toplevel_manager_v1* m_pManager; +}; \ No newline at end of file