protocols: reimplement unstable/xdg-foreign-v2 (#13716)

This commit is contained in:
Chris Naporlee
2026-03-23 23:08:58 +00:00
committed by GitHub
parent 64a2e4e263
commit 63c56bad6f
6 changed files with 315 additions and 45 deletions
+1
View File
@@ -534,6 +534,7 @@ protocolnew("unstable/keyboard-shortcuts-inhibit"
protocolnew("unstable/text-input" "text-input-unstable-v3" false)
protocolnew("unstable/pointer-constraints" "pointer-constraints-unstable-v1"
false)
protocolnew("unstable/xdg-foreign" "xdg-foreign-unstable-v2" false)
protocolnew("staging/xdg-activation" "xdg-activation-v1" false)
protocolnew("staging/ext-idle-notify" "ext-idle-notify-v1" false)
protocolnew("staging/ext-session-lock" "ext-session-lock-v1" false)
+5
View File
@@ -67,6 +67,7 @@
#include "../protocols/PointerWarp.hpp"
#include "../protocols/Fifo.hpp"
#include "../protocols/CommitTiming.hpp"
#include "../protocols/XDGForeignV2.hpp"
#include "../helpers/Monitor.hpp"
#include "../event/EventBus.hpp"
@@ -194,6 +195,8 @@ CProtocolManager::CProtocolManager() {
PROTO::extDataDevice = makeUnique<CExtDataDeviceProtocol>(&ext_data_control_manager_v1_interface, 1, "ExtDataDevice");
PROTO::pointerWarp = makeUnique<CPointerWarpProtocol>(&wp_pointer_warp_v1_interface, 1, "PointerWarp");
PROTO::fifo = makeUnique<CFifoProtocol>(&wp_fifo_manager_v1_interface, 1, "Fifo");
PROTO::xdgForeignExporter = makeUnique<CXDGForeignExporterProtocolV2>(&zxdg_exporter_v2_interface, 1, "XDGForeignExporter");
PROTO::xdgForeignImporter = makeUnique<CXDGForeignImporterProtocolV2>(&zxdg_importer_v2_interface, 1, "XDGForeignImporter");
if (*PENABLECT)
PROTO::commitTiming = makeUnique<CCommitTimingProtocol>(&wp_commit_timing_manager_v1_interface, 1, "CommitTiming");
@@ -301,6 +304,8 @@ CProtocolManager::~CProtocolManager() {
PROTO::extDataDevice.reset();
PROTO::pointerWarp.reset();
PROTO::fifo.reset();
PROTO::xdgForeignExporter.reset();
PROTO::xdgForeignImporter.reset();
PROTO::commitTiming.reset();
PROTO::imageCaptureSource.reset();
+175
View File
@@ -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(); });
}
+92
View File
@@ -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;
};
+41 -44
View File
@@ -220,54 +220,51 @@ 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;
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);
if (any)
break;
}
return any;
};
if (exploreChildren(m_self, newp)) {
r->error(XDG_TOPLEVEL_ERROR_INVALID_PARENT, "Parent cannot be a descendant");
return;
}
}
m_parent = newp;
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)newp.get(), (oldParent ? std::format(" (was {:x})", (uintptr_t)oldParent.get()) : ""));
setNewParent(newp);
});
}
void CXDGToplevelResource::setNewParent(SP<CXDGToplevelResource> newParent) {
auto oldParent = m_parent;
if (m_parent)
std::erase(m_parent->m_children, m_self);
if (newParent) {
if (m_self == newParent) {
m_resource->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);
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;
}
}
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() {
m_events.destroy.emit();
if (m_parent)
+1 -1
View File
@@ -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();