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 +++++++++++++++++++++++++++++++++++++++++ src/protocols/XDGForeignV2.hpp | 92 ++++++++++++++++++++++ src/protocols/XDGShell.cpp | 69 ++++++++-------- src/protocols/XDGShell.hpp | 2 +- 4 files changed, 301 insertions(+), 37 deletions(-) create mode 100644 src/protocols/XDGForeignV2.cpp create mode 100644 src/protocols/XDGForeignV2.hpp (limited to 'src/protocols') 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 diff --git a/src/protocols/XDGForeignV2.hpp b/src/protocols/XDGForeignV2.hpp new file mode 100644 index 00000000..78c8dbf7 --- /dev/null +++ b/src/protocols/XDGForeignV2.hpp @@ -0,0 +1,92 @@ +#include +#include +#include +#include +#include +#include "WaylandProtocol.hpp" +#include "xdg-foreign-unstable-v2.hpp" +#include "../helpers/signal/Signal.hpp" + +class CXDGImportedResourceV2; +class CWLSurface; +class CXDGToplevelResource; + +class CXDGExportedResourceV2 { + public: + CXDGExportedResourceV2(SP resource_, SP surface_, const std::string& handle); + ~CXDGExportedResourceV2(); + + struct { + CSignalT<> destroy; + } m_events; + + bool good() const; + std::string_view handle() const; + WP xdgSurf() const; + + private: + bool m_topLevelDestroyed = false; + struct { + CHyprSignalListener topLevelDestroyed; + } m_listeners; + SP m_resource; + WP m_toplevel; + std::string m_handle; + + friend class CXDGForeignExporterProtocolV2; +}; + +// zxdg_imported_v2 +class CXDGImportedResourceV2 { + public: + CXDGImportedResourceV2(SP resource, SP exported, const std::string& handle); + ~CXDGImportedResourceV2(); + + private: + SP m_resource; + WP m_exported; + std::string m_handle; + + struct { + CHyprSignalListener exportedDestroyed; + } m_listeners; + + friend class CXDGForeignImporterProtocolV2; +}; + +class CXDGForeignExporterProtocolV2 : public IWaylandProtocol { + public: + CXDGForeignExporterProtocolV2(const wl_interface* iface, const int& ver, const std::string& name); + + virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) override; + + SP getExported(const std::string& handle) const; + + private: + void destroyExported(CXDGExportedResourceV2*); + void onExporterDestroyed(CZxdgExporterV2*); + std::vector> m_exporters; + std::unordered_map> m_exported; + + friend class CXDGExportedResourceV2; +}; + +class CXDGForeignImporterProtocolV2 : public IWaylandProtocol { + public: + CXDGForeignImporterProtocolV2(const wl_interface* iface, const int& ver, const std::string& name); + + virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) override; + + private: + void destroyImported(CXDGImportedResourceV2*); + void onImporterDestroyed(CZxdgImporterV2*); + std::vector> m_importers; + std::vector> m_imports; + + friend class CXDGImportedResourceV2; +}; + +namespace PROTO { + inline UP xdgForeignExporter; + inline UP xdgForeignImporter; +}; \ No newline at end of file diff --git a/src/protocols/XDGShell.cpp b/src/protocols/XDGShell.cpp index 969556a3..774346d4 100644 --- a/src/protocols/XDGShell.cpp +++ b/src/protocols/XDGShell.cpp @@ -220,52 +220,49 @@ CXDGToplevelResource::CXDGToplevelResource(SP resource_, SPsetSetParent([this](CXdgToplevel* r, wl_resource* parentR) { - auto oldParent = m_parent; - - if (m_parent) - std::erase(m_parent->m_children, m_self); - auto newp = parentR ? CXDGToplevelResource::fromResource(parentR) : nullptr; + setNewParent(newp); + }); +} - if (newp) { - // check for protocol constraints - if (newp == m_self) { - r->error(XDG_TOPLEVEL_ERROR_INVALID_PARENT, "Parent cannot be self"); - return; - } - - static std::function, WP)> exploreChildren = [](WP tl, - WP target) -> bool { - bool any = false; - for (const auto& c : tl->m_children) { - if (c == target) - return true; - - any = any || exploreChildren(c, target); +void CXDGToplevelResource::setNewParent(SP newParent) { + auto oldParent = m_parent; - if (any) - break; - } - return any; - }; + if (m_parent) + std::erase(m_parent->m_children, m_self); - if (exploreChildren(m_self, newp)) { - r->error(XDG_TOPLEVEL_ERROR_INVALID_PARENT, "Parent cannot be a descendant"); - return; - } + if (newParent) { + if (m_self == newParent) { + m_resource->error(XDG_TOPLEVEL_ERROR_INVALID_PARENT, "Parent cannot be self"); + return; } - m_parent = newp; + static std::function, WP)> exploreChildren = [](WP tl, WP target) -> bool { + bool any = false; + for (const auto& c : tl->m_children) { + if (c == target) + return true; - if (m_parent) { - m_parent->m_children.emplace_back(m_self); + any = any || exploreChildren(c, target); - if (m_parent->m_window && m_parent->m_window->m_pinned) - m_self->m_window->m_pinned = true; + if (any) + break; + } + return any; + }; + if (exploreChildren(m_self, newParent)) { + m_resource->error(XDG_TOPLEVEL_ERROR_INVALID_PARENT, "Parent cannot be a descendant of itself"); + return; } + } - LOGM(Log::DEBUG, "Toplevel {:x} sets parent to {:x}{}", (uintptr_t)this, (uintptr_t)newp.get(), (oldParent ? std::format(" (was {:x})", (uintptr_t)oldParent.get()) : "")); - }); + m_parent = newParent; + if (m_parent) { + m_parent->m_children.emplace_back(m_self); + if (m_parent->m_window && m_parent->m_window->m_pinned) + m_self->m_window->m_pinned = true; + } + LOGM(Log::DEBUG, "Toplevel {:x} sets parent to {:x}{}", (uintptr_t)this, (uintptr_t)newParent.get(), (oldParent ? std::format(" (was {:x})", (uintptr_t)oldParent.get()) : "")); } CXDGToplevelResource::~CXDGToplevelResource() { diff --git a/src/protocols/XDGShell.hpp b/src/protocols/XDGShell.hpp index fbb6ba94..3d94d282 100644 --- a/src/protocols/XDGShell.hpp +++ b/src/protocols/XDGShell.hpp @@ -97,7 +97,6 @@ class CXDGToplevelResource { PHLWINDOWREF m_window; bool good(); - Vector2D layoutMinSize(); Vector2D layoutMaxSize(); @@ -107,6 +106,7 @@ class CXDGToplevelResource { uint32_t setFullscreen(bool fullscreen); uint32_t setActive(bool active); uint32_t setSuspeneded(bool sus); + void setNewParent(SP newParent); void close(); -- cgit v1.2.3