diff options
| author | Chris Naporlee <55722668+chrisn731@users.noreply.github.com> | 2026-03-23 19:08:58 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-03-23 23:08:58 +0000 |
| commit | 63c56bad6fcc55f99d1437da7cd1b0b68fd9cc88 (patch) | |
| tree | d319540e63de20229ca321ecd1aadc164bfdf501 /src/protocols | |
| parent | 64a2e4e26388f017fd9198a6d70424f1f251a5a0 (diff) | |
protocols: reimplement unstable/xdg-foreign-v2 (#13716)
Diffstat (limited to 'src/protocols')
| -rw-r--r-- | src/protocols/XDGForeignV2.cpp | 175 | ||||
| -rw-r--r-- | src/protocols/XDGForeignV2.hpp | 92 | ||||
| -rw-r--r-- | src/protocols/XDGShell.cpp | 69 | ||||
| -rw-r--r-- | src/protocols/XDGShell.hpp | 2 |
4 files changed, 301 insertions, 37 deletions
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 <hyprutils/memory/SharedPtr.hpp> +#include <hyprutils/memory/UniquePtr.hpp> +#include <string> +#include <string_view> +#include <unordered_map> +#include <wayland-server.h> +#include "protocols/core/Compositor.hpp" + +CXDGExportedResourceV2::CXDGExportedResourceV2(SP<CZxdgExportedV2> resource, SP<CXDGToplevelResource> 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<CXDGToplevelResource> 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<CZxdgExporterV2>(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<CXDGSurfaceRole*>(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<CXDGExportedResourceV2>(makeShared<CZxdgExportedV2>(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<CXDGExportedResourceV2> 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<CZxdgImportedV2> imported, SP<CXDGExportedResourceV2> 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<CXDGSurfaceRole*>(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<CZxdgImporterV2>(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<CXDGImportedResourceV2>(makeShared<CZxdgImportedV2>(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 <hyprutils/signal/Listener.hpp> +#include <string> +#include <string_view> +#include <unordered_map> +#include <vector> +#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<CZxdgExportedV2> resource_, SP<CXDGToplevelResource> surface_, const std::string& handle); + ~CXDGExportedResourceV2(); + + struct { + CSignalT<> destroy; + } m_events; + + bool good() const; + std::string_view handle() const; + WP<CXDGToplevelResource> xdgSurf() const; + + private: + bool m_topLevelDestroyed = false; + struct { + CHyprSignalListener topLevelDestroyed; + } m_listeners; + SP<CZxdgExportedV2> m_resource; + WP<CXDGToplevelResource> m_toplevel; + std::string m_handle; + + friend class CXDGForeignExporterProtocolV2; +}; + +// zxdg_imported_v2 +class CXDGImportedResourceV2 { + public: + CXDGImportedResourceV2(SP<CZxdgImportedV2> resource, SP<CXDGExportedResourceV2> exported, const std::string& handle); + ~CXDGImportedResourceV2(); + + private: + SP<CZxdgImportedV2> m_resource; + WP<CXDGExportedResourceV2> 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<CXDGExportedResourceV2> getExported(const std::string& handle) const; + + private: + void destroyExported(CXDGExportedResourceV2*); + void onExporterDestroyed(CZxdgExporterV2*); + std::vector<UP<CZxdgExporterV2>> m_exporters; + std::unordered_map<std::string, SP<CXDGExportedResourceV2>> 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<UP<CZxdgImporterV2>> m_importers; + std::vector<UP<CXDGImportedResourceV2>> m_imports; + + friend class CXDGImportedResourceV2; +}; + +namespace PROTO { + inline UP<CXDGForeignExporterProtocolV2> xdgForeignExporter; + inline UP<CXDGForeignImporterProtocolV2> 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<CXdgToplevel> resource_, SP<CXDGSu }); m_resource->setSetParent([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<bool(WP<CXDGToplevelResource>, WP<CXDGToplevelResource>)> exploreChildren = [](WP<CXDGToplevelResource> tl, - WP<CXDGToplevelResource> 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<CXDGToplevelResource> 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<bool(WP<CXDGToplevelResource>, WP<CXDGToplevelResource>)> exploreChildren = [](WP<CXDGToplevelResource> tl, WP<CXDGToplevelResource> 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<CXDGToplevelResource> newParent); void close(); |
