From 63c56bad6fcc55f99d1437da7cd1b0b68fd9cc88 Mon Sep 17 00:00:00 2001 From: Chris Naporlee <55722668+chrisn731@users.noreply.github.com> Date: Mon, 23 Mar 2026 19:08:58 -0400 Subject: protocols: reimplement unstable/xdg-foreign-v2 (#13716) --- src/protocols/XDGForeignV2.cpp | 175 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 src/protocols/XDGForeignV2.cpp (limited to 'src/protocols/XDGForeignV2.cpp') diff --git a/src/protocols/XDGForeignV2.cpp b/src/protocols/XDGForeignV2.cpp new file mode 100644 index 00000000..f67cb86c --- /dev/null +++ b/src/protocols/XDGForeignV2.cpp @@ -0,0 +1,175 @@ +#include "protocols/XDGForeignV2.hpp" +#include "managers/TokenManager.hpp" +#include "protocols/XDGShell.hpp" +#include "xdg-foreign-unstable-v2.hpp" +#include +#include +#include +#include +#include +#include +#include "protocols/core/Compositor.hpp" + +CXDGExportedResourceV2::CXDGExportedResourceV2(SP resource, SP toplevel, const std::string& handle) : + m_resource(resource), m_toplevel(toplevel), m_handle(handle) { + + if UNLIKELY (!good()) + return; + + m_resource->setData(this); + m_resource->sendHandle(handle.c_str()); + m_listeners.topLevelDestroyed = toplevel->m_events.destroy.listen([this] { + m_topLevelDestroyed = true; + m_listeners.topLevelDestroyed.reset(); + m_events.destroy.emit(); + }); + m_resource->setOnDestroy([this](CZxdgExportedV2*) { PROTO::xdgForeignExporter->destroyExported(this); }); + m_resource->setDestroy([this](CZxdgExportedV2*) { PROTO::xdgForeignExporter->destroyExported(this); }); +} + +CXDGExportedResourceV2::~CXDGExportedResourceV2() { + if (!m_topLevelDestroyed) + m_events.destroy.emit(); +} + +bool CXDGExportedResourceV2::good() const { + return m_resource->resource(); +} + +WP CXDGExportedResourceV2::xdgSurf() const { + return m_toplevel; +} + +std::string_view CXDGExportedResourceV2::handle() const { + return m_handle; +} + +CXDGForeignExporterProtocolV2::CXDGForeignExporterProtocolV2(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {} + +void CXDGForeignExporterProtocolV2::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { + const auto RESOURCE = m_exporters.emplace_back(makeUnique(client, ver, id)).get(); + + if UNLIKELY (!RESOURCE->resource()) { + wl_client_post_no_memory(client); + m_exporters.pop_back(); + return; + } + + RESOURCE->setExportToplevel([this](CZxdgExporterV2* exporter, uint32_t id, wl_resource* surface) { + auto wlSurf = CWLSurfaceResource::fromResource(surface); + + if (wlSurf->m_role != SURFACE_ROLE_XDG_SHELL) { + exporter->error(zxdgExporterV2Error::ZXDG_EXPORTER_V2_ERROR_INVALID_SURFACE, "surface must be an xdg_toplevel"); + return; + } + + auto xdgSurfResource = sc(wlSurf->m_role.get())->m_xdgSurface.lock(); + if (xdgSurfResource->m_toplevel.expired()) + return; + + auto xdgSurf = xdgSurfResource->m_toplevel.lock(); + const std::string HANDLE = g_pTokenManager->getRandomUUID(); + const auto [ELM, EMPLACED] = + m_exported.emplace(HANDLE, makeShared(makeShared(exporter->client(), exporter->version(), id), xdgSurf, HANDLE)); + + // This should only happen if we have our generated handles collide. + if UNLIKELY (!EMPLACED) { + wl_client_post_no_memory(exporter->client()); + return; + } + + if UNLIKELY (!ELM->second->good()) { + wl_client_post_no_memory(exporter->client()); + destroyExported(ELM->second.get()); + } + }); + + RESOURCE->setDestroy([this](CZxdgExporterV2* e) { onExporterDestroyed(e); }); + RESOURCE->setOnDestroy([this](CZxdgExporterV2* e) { onExporterDestroyed(e); }); +} + +SP CXDGForeignExporterProtocolV2::getExported(const std::string& handle) const { + return m_exported.contains(handle) ? m_exported.at(handle) : nullptr; +} + +void CXDGForeignExporterProtocolV2::onExporterDestroyed(CZxdgExporterV2* exporter) { + std::erase_if(m_exporters, [exporter](const auto& other) { return exporter == other.get(); }); +} + +void CXDGForeignExporterProtocolV2::destroyExported(CXDGExportedResourceV2* r) { + PROTO::xdgForeignExporter->m_exported.erase(r->m_handle); +} + +CXDGImportedResourceV2::CXDGImportedResourceV2(SP imported, SP exported, const std::string& handle) : + m_resource(imported), m_exported(exported), m_handle(handle) { + if UNLIKELY (!m_resource->resource() || m_exported.expired()) + return; + + m_resource->setData(this); + + m_resource->setSetParentOf([this](CZxdgImportedV2* r, wl_resource* surf) { + const auto CHILDSURF = CWLSurfaceResource::fromResource(surf); + + if (CHILDSURF->m_role != SURFACE_ROLE_XDG_SHELL) { + m_resource->error(zxdgImportedV2Error::ZXDG_IMPORTED_V2_ERROR_INVALID_SURFACE, "surface must be an xdg_toplevel"); + return; + } + + const auto CHILDXDGSURF = sc(CHILDSURF->m_role.get())->m_xdgSurface.lock(); + if (CHILDXDGSURF->m_toplevel.expired()) + return; + + if LIKELY (auto exportedTopLevel = m_exported->xdgSurf(); !exportedTopLevel.expired()) + CHILDXDGSURF->m_toplevel->setNewParent(exportedTopLevel.lock()); + }); + + m_listeners.exportedDestroyed = m_exported->m_events.destroy.listen([this]() { PROTO::xdgForeignImporter->destroyImported(this); }); + m_resource->setDestroy([this](CZxdgImportedV2*) { PROTO::xdgForeignImporter->destroyImported(this); }); + m_resource->setOnDestroy([this](CZxdgImportedV2*) { PROTO::xdgForeignImporter->destroyImported(this); }); +} + +CXDGImportedResourceV2::~CXDGImportedResourceV2() { + m_resource->sendDestroyed(); +} + +CXDGForeignImporterProtocolV2::CXDGForeignImporterProtocolV2(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { + ; +} + +void CXDGForeignImporterProtocolV2::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { + const auto RESOURCE = m_importers.emplace_back(makeUnique(client, ver, id)).get(); + + if UNLIKELY (!RESOURCE->resource()) { + wl_client_post_no_memory(client); + m_importers.pop_back(); + return; + } + + RESOURCE->setImportToplevel([this](CZxdgImporterV2* importer, uint32_t id, const char* _handle) { + const std::string HANDLE = _handle; + auto exported = PROTO::xdgForeignExporter->getExported(HANDLE); + auto imported = + m_imports.emplace_back(makeUnique(makeShared(importer->client(), importer->version(), id), exported, HANDLE)).get(); + + if UNLIKELY (!imported->m_resource->resource()) { + wl_client_post_no_memory(importer->client()); + m_imports.pop_back(); + return; + } + + // Couldn't find the handle. + if UNLIKELY (imported->m_exported.expired()) + destroyImported(imported); + }); + + RESOURCE->setDestroy([this](CZxdgImporterV2* r) { onImporterDestroyed(r); }); + RESOURCE->setOnDestroy([this](CZxdgImporterV2* r) { onImporterDestroyed(r); }); +} + +void CXDGForeignImporterProtocolV2::onImporterDestroyed(CZxdgImporterV2* importer) { + std::erase_if(m_importers, [importer](const auto& other) { return importer == other.get(); }); +} + +void CXDGForeignImporterProtocolV2::destroyImported(CXDGImportedResourceV2* imported) { + std::erase_if(m_imports, [imported](const auto& other) { return imported == other.get(); }); +} \ No newline at end of file -- cgit v1.2.3