summaryrefslogtreecommitdiff
path: root/src/protocols
diff options
context:
space:
mode:
authorVaxry <vaxry@vaxry.net>2024-05-10 23:28:33 +0100
committerVaxry <43317083+vaxerski@users.noreply.github.com>2024-05-14 23:02:24 +0100
commit0cfdde3d1acbfbf698af17f6986fbc4d644214da (patch)
tree579ac53bc228db6d0feb373448c8e5557d3285d4 /src/protocols
parent121d3a72137d4780602cf245704615f63357ea22 (diff)
xdg-shell: move to new impl
Diffstat (limited to 'src/protocols')
-rw-r--r--src/protocols/ForeignToplevel.cpp2
-rw-r--r--src/protocols/ForeignToplevelWlr.cpp4
-rw-r--r--src/protocols/LayerShell.cpp7
-rw-r--r--src/protocols/XDGShell.cpp697
-rw-r--r--src/protocols/XDGShell.hpp256
5 files changed, 960 insertions, 6 deletions
diff --git a/src/protocols/ForeignToplevel.cpp b/src/protocols/ForeignToplevel.cpp
index 0d2a7e57..40420da7 100644
--- a/src/protocols/ForeignToplevel.cpp
+++ b/src/protocols/ForeignToplevel.cpp
@@ -91,7 +91,7 @@ void CForeignToplevelList::onClass(PHLWINDOW pWindow) {
if (!H || H->closed)
return;
- H->resource->sendAppId(g_pXWaylandManager->getAppIDClass(pWindow).c_str());
+ H->resource->sendAppId(pWindow->m_szClass.c_str());
}
void CForeignToplevelList::onUnmap(PHLWINDOW pWindow) {
diff --git a/src/protocols/ForeignToplevelWlr.cpp b/src/protocols/ForeignToplevelWlr.cpp
index 1762ad2a..782fbc0e 100644
--- a/src/protocols/ForeignToplevelWlr.cpp
+++ b/src/protocols/ForeignToplevelWlr.cpp
@@ -195,7 +195,7 @@ void CForeignToplevelWlrManager::onMap(PHLWINDOW pWindow) {
LOGM(LOG, "Newly mapped window {:016x}", (uintptr_t)pWindow.get());
resource->sendToplevel(NEWHANDLE->resource.get());
- NEWHANDLE->resource->sendAppId(g_pXWaylandManager->getAppIDClass(pWindow).c_str());
+ NEWHANDLE->resource->sendAppId(pWindow->m_szClass.c_str());
NEWHANDLE->resource->sendTitle(pWindow->m_szTitle.c_str());
if (const auto PMONITOR = g_pCompositor->getMonitorFromID(pWindow->m_iMonitorID); PMONITOR)
NEWHANDLE->sendMonitor(PMONITOR);
@@ -231,7 +231,7 @@ void CForeignToplevelWlrManager::onClass(PHLWINDOW pWindow) {
if (!H || H->closed)
return;
- H->resource->sendAppId(g_pXWaylandManager->getAppIDClass(pWindow).c_str());
+ H->resource->sendAppId(pWindow->m_szClass.c_str());
H->resource->sendDone();
}
diff --git a/src/protocols/LayerShell.cpp b/src/protocols/LayerShell.cpp
index ced765fd..962b89a3 100644
--- a/src/protocols/LayerShell.cpp
+++ b/src/protocols/LayerShell.cpp
@@ -1,5 +1,6 @@
#include "LayerShell.hpp"
#include "../Compositor.hpp"
+#include "XDGShell.hpp"
#define LOGM PROTO::layerShell->protoLog
@@ -118,14 +119,14 @@ CLayerShellResource::CLayerShellResource(SP<CZwlrLayerSurfaceV1> resource_, wlr_
});
resource->setGetPopup([this](CZwlrLayerSurfaceV1* r, wl_resource* popup_) {
- auto popup = wlr_xdg_popup_from_resource(popup_);
+ auto popup = CXDGPopupResource::fromResource(popup_);
- if (popup->parent) {
+ if (popup->taken) {
r->error(-1, "Parent already exists!");
return;
}
- popup->parent = surface;
+ popup->taken = true;
events.newPopup.emit(popup);
});
diff --git a/src/protocols/XDGShell.cpp b/src/protocols/XDGShell.cpp
new file mode 100644
index 00000000..fcb2d63b
--- /dev/null
+++ b/src/protocols/XDGShell.cpp
@@ -0,0 +1,697 @@
+#include "XDGShell.hpp"
+#include <algorithm>
+#include "../Compositor.hpp"
+
+#define LOGM PROTO::xdgShell->protoLog
+
+CXDGPopupResource::CXDGPopupResource(SP<CXdgPopup> resource_, SP<CXDGSurfaceResource> owner_, SP<CXDGSurfaceResource> surface_, SP<CXDGPositionerResource> positioner) :
+ surface(surface_), parent(owner_), resource(resource_), positionerRules(positioner) {
+ if (!good())
+ return;
+
+ resource->setData(this);
+
+ resource->setDestroy([this](CXdgPopup* r) {
+ if (surface && surface->mapped)
+ surface->events.unmap.emit();
+ events.destroy.emit();
+ PROTO::xdgShell->destroyResource(this);
+ });
+ resource->setOnDestroy([this](CXdgPopup* r) {
+ if (surface && surface->mapped)
+ surface->events.unmap.emit();
+ events.destroy.emit();
+ PROTO::xdgShell->destroyResource(this);
+ });
+
+ resource->setReposition([this](CXdgPopup* r, wl_resource* positionerRes, uint32_t token) {
+ LOGM(LOG, "Popup {:x} asks for reposition", (uintptr_t)this);
+ lastRepositionToken = token;
+ auto pos = CXDGPositionerResource::fromResource(positionerRes);
+ if (!pos)
+ return;
+ positionerRules = CXDGPositionerRules{pos};
+ events.reposition.emit();
+ });
+
+ if (parent)
+ taken = true;
+}
+
+CXDGPopupResource::~CXDGPopupResource() {
+ events.destroy.emit();
+}
+
+void CXDGPopupResource::applyPositioning(const CBox& box, const Vector2D& t1coord) {
+ CBox constraint = box.copy().translate(surface->pending.geometry.pos());
+
+ geometry = positionerRules.getPosition(constraint, accumulateParentOffset() + t1coord);
+
+ LOGM(LOG, "Popup {:x} gets unconstrained to {} {}", (uintptr_t)this, geometry.pos(), geometry.size());
+
+ configure(geometry);
+
+ if (lastRepositionToken)
+ repositioned();
+}
+
+Vector2D CXDGPopupResource::accumulateParentOffset() {
+ SP<CXDGSurfaceResource> current = parent.lock();
+ Vector2D off;
+ while (current) {
+ off += current->current.geometry.pos();
+ if (current->popup) {
+ off += current->popup->geometry.pos();
+ current = current->popup->parent.lock();
+ } else
+ break;
+ }
+ return off;
+}
+
+SP<CXDGPopupResource> CXDGPopupResource::fromResource(wl_resource* res) {
+ auto data = (CXDGPopupResource*)(((CXdgPopup*)wl_resource_get_user_data(res))->data());
+ return data ? data->self.lock() : nullptr;
+}
+
+bool CXDGPopupResource::good() {
+ return resource->resource();
+}
+
+void CXDGPopupResource::configure(const CBox& box) {
+ resource->sendConfigure(box.x, box.y, box.w, box.h);
+ if (surface)
+ surface->scheduleConfigure();
+}
+
+void CXDGPopupResource::done() {
+ resource->sendPopupDone();
+}
+
+void CXDGPopupResource::repositioned() {
+ if (!lastRepositionToken)
+ return;
+
+ LOGM(LOG, "repositioned: sending reposition token {}", lastRepositionToken);
+
+ resource->sendRepositioned(lastRepositionToken);
+ lastRepositionToken = 0;
+}
+
+CXDGToplevelResource::CXDGToplevelResource(SP<CXdgToplevel> resource_, SP<CXDGSurfaceResource> owner_) : owner(owner_), resource(resource_) {
+ if (!good())
+ return;
+
+ resource->setData(this);
+
+ resource->setDestroy([this](CXdgToplevel* r) {
+ events.destroy.emit();
+ PROTO::xdgShell->destroyResource(this);
+ });
+ resource->setOnDestroy([this](CXdgToplevel* r) {
+ events.destroy.emit();
+ PROTO::xdgShell->destroyResource(this);
+ });
+
+ if (resource->version() >= 5) {
+ wl_array arr;
+ wl_array_init(&arr);
+ auto p = (uint32_t*)wl_array_add(&arr, sizeof(uint32_t));
+ *p = XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN;
+ p = (uint32_t*)wl_array_add(&arr, sizeof(uint32_t));
+ *p = XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE;
+ resource->sendWmCapabilities(&arr);
+ wl_array_release(&arr);
+ }
+
+ pendingApply.states.push_back(XDG_TOPLEVEL_STATE_TILED_LEFT);
+ pendingApply.states.push_back(XDG_TOPLEVEL_STATE_TILED_RIGHT);
+ pendingApply.states.push_back(XDG_TOPLEVEL_STATE_TILED_TOP);
+ pendingApply.states.push_back(XDG_TOPLEVEL_STATE_TILED_BOTTOM);
+
+ resource->setSetTitle([this](CXdgToplevel* r, const char* t) {
+ state.title = t;
+ events.metadataChanged.emit();
+ });
+
+ resource->setSetAppId([this](CXdgToplevel* r, const char* id) {
+ state.appid = id;
+ events.metadataChanged.emit();
+ });
+
+ resource->setSetMaxSize([this](CXdgToplevel* r, int32_t x, int32_t y) {
+ pending.maxSize = {x, y};
+ events.sizeLimitsChanged.emit();
+ });
+
+ resource->setSetMinSize([this](CXdgToplevel* r, int32_t x, int32_t y) {
+ pending.minSize = {x, y};
+ events.sizeLimitsChanged.emit();
+ });
+
+ resource->setSetMaximized([this](CXdgToplevel* r) {
+ state.requestsMaximize = true;
+ events.stateChanged.emit();
+ state.requestsMaximize.reset();
+ });
+
+ resource->setUnsetMaximized([this](CXdgToplevel* r) {
+ state.requestsMaximize = false;
+ events.stateChanged.emit();
+ state.requestsMaximize.reset();
+ });
+
+ resource->setSetFullscreen([this](CXdgToplevel* r, wl_resource* output) {
+ state.requestsFullscreen = true;
+ events.stateChanged.emit();
+ state.requestsFullscreen.reset();
+ });
+
+ resource->setUnsetFullscreen([this](CXdgToplevel* r) {
+ state.requestsFullscreen = false;
+ events.stateChanged.emit();
+ state.requestsFullscreen.reset();
+ });
+
+ resource->setSetMinimized([this](CXdgToplevel* r) {
+ state.requestsMinimize = true;
+ events.stateChanged.emit();
+ state.requestsFullscreen.reset();
+ });
+
+ resource->setSetParent([this](CXdgToplevel* r, wl_resource* parentR) {
+ auto newp = parentR ? CXDGToplevelResource::fromResource(parentR) : nullptr;
+ parent = newp;
+
+ LOGM(LOG, "Toplevel {:x} sets parent to {:x}", (uintptr_t)this, (uintptr_t)newp.get());
+ });
+}
+
+CXDGToplevelResource::~CXDGToplevelResource() {
+ events.destroy.emit();
+}
+
+SP<CXDGToplevelResource> CXDGToplevelResource::fromResource(wl_resource* res) {
+ auto data = (CXDGToplevelResource*)(((CXdgToplevel*)wl_resource_get_user_data(res))->data());
+ return data ? data->self.lock() : nullptr;
+}
+
+bool CXDGToplevelResource::good() {
+ return resource->resource();
+}
+
+uint32_t CXDGToplevelResource::setSize(const Vector2D& size) {
+ pendingApply.size = size;
+ applyState();
+ return owner->scheduleConfigure();
+}
+
+uint32_t CXDGToplevelResource::setMaximized(bool maximized) {
+ bool set = std::find(pendingApply.states.begin(), pendingApply.states.end(), XDG_TOPLEVEL_STATE_MAXIMIZED) != pendingApply.states.end();
+
+ if (maximized == set)
+ return owner->scheduledSerial;
+
+ if (maximized && !set)
+ pendingApply.states.push_back(XDG_TOPLEVEL_STATE_MAXIMIZED);
+ else if (!maximized && set)
+ std::erase(pendingApply.states, XDG_TOPLEVEL_STATE_MAXIMIZED);
+ applyState();
+ return owner->scheduleConfigure();
+}
+
+uint32_t CXDGToplevelResource::setFullscreen(bool fullscreen) {
+ bool set = std::find(pendingApply.states.begin(), pendingApply.states.end(), XDG_TOPLEVEL_STATE_FULLSCREEN) != pendingApply.states.end();
+
+ if (fullscreen == set)
+ return owner->scheduledSerial;
+
+ if (fullscreen && !set)
+ pendingApply.states.push_back(XDG_TOPLEVEL_STATE_FULLSCREEN);
+ else if (!fullscreen && set)
+ std::erase(pendingApply.states, XDG_TOPLEVEL_STATE_FULLSCREEN);
+ applyState();
+ return owner->scheduleConfigure();
+}
+
+uint32_t CXDGToplevelResource::setActive(bool active) {
+ bool set = std::find(pendingApply.states.begin(), pendingApply.states.end(), XDG_TOPLEVEL_STATE_ACTIVATED) != pendingApply.states.end();
+
+ if (active == set)
+ return owner->scheduledSerial;
+
+ if (active && !set)
+ pendingApply.states.push_back(XDG_TOPLEVEL_STATE_ACTIVATED);
+ else if (!active && set)
+ std::erase(pendingApply.states, XDG_TOPLEVEL_STATE_ACTIVATED);
+ applyState();
+ return owner->scheduleConfigure();
+}
+
+uint32_t CXDGToplevelResource::setSuspeneded(bool sus) {
+ bool set = std::find(pendingApply.states.begin(), pendingApply.states.end(), XDG_TOPLEVEL_STATE_SUSPENDED) != pendingApply.states.end();
+
+ if (sus == set)
+ return owner->scheduledSerial;
+
+ if (sus && !set)
+ pendingApply.states.push_back(XDG_TOPLEVEL_STATE_SUSPENDED);
+ else if (!sus && set)
+ std::erase(pendingApply.states, XDG_TOPLEVEL_STATE_SUSPENDED);
+ applyState();
+ return owner->scheduleConfigure();
+}
+
+void CXDGToplevelResource::applyState() {
+ wl_array arr;
+ wl_array_init(&arr);
+ wl_array_add(&arr, pendingApply.states.size() * sizeof(int));
+ memcpy(arr.data, pendingApply.states.data(), pendingApply.states.size() * sizeof(int));
+
+ resource->sendConfigure(pendingApply.size.x, pendingApply.size.y, &arr);
+
+ wl_array_release(&arr);
+}
+
+void CXDGToplevelResource::close() {
+ resource->sendClose();
+}
+
+CXDGSurfaceResource::CXDGSurfaceResource(SP<CXdgSurface> resource_, SP<CXDGWMBase> owner_, wlr_surface* surface_) : owner(owner_), surface(surface_), resource(resource_) {
+ if (!good())
+ return;
+
+ resource->setData(this);
+
+ resource->setDestroy([this](CXdgSurface* r) {
+ if (mapped)
+ events.unmap.emit();
+ events.destroy.emit();
+ PROTO::xdgShell->destroyResource(this);
+ });
+ resource->setOnDestroy([this](CXdgSurface* r) {
+ if (mapped)
+ events.unmap.emit();
+ events.destroy.emit();
+ PROTO::xdgShell->destroyResource(this);
+ });
+
+ hyprListener_surfaceDestroy.initCallback(
+ &surface->events.destroy,
+ [this](void* owner, void* data) {
+ LOGM(WARN, "wl_surface destroyed before its xdg_surface role object");
+ hyprListener_surfaceDestroy.removeCallback();
+ hyprListener_surfaceCommit.removeCallback();
+
+ if (mapped)
+ events.unmap.emit();
+
+ mapped = false;
+ surface = nullptr;
+ events.destroy.emit();
+ },
+ nullptr, "CXDGSurfaceResource");
+
+ hyprListener_surfaceCommit.initCallback(
+ &surface->events.commit,
+ [this](void* owner, void* data) {
+ current = pending;
+ if (toplevel)
+ toplevel->current = toplevel->pending;
+
+ if (initialCommit && surface->pending.buffer_width > 0 && surface->pending.buffer_height > 0) {
+ resource->error(-1, "Buffer attached before initial commit");
+ return;
+ }
+
+ if (surface->pending.buffer_width > 0 && surface->pending.buffer_height > 0 && !mapped) {
+ // this forces apps to not draw CSD.
+ if (toplevel)
+ toplevel->setMaximized(true);
+
+ mapped = true;
+ wlr_surface_map(surface);
+ events.map.emit();
+ return;
+ }
+
+ if (surface->pending.buffer_width <= 0 && surface->pending.buffer_height <= 0 && mapped) {
+ mapped = false;
+ wlr_surface_unmap(surface);
+ events.unmap.emit();
+ return;
+ }
+
+ events.commit.emit();
+ initialCommit = false;
+ },
+ nullptr, "CXDGSurfaceResource");
+
+ resource->setGetToplevel([this](CXdgSurface* r, uint32_t id) {
+ const auto RESOURCE = PROTO::xdgShell->m_vToplevels.emplace_back(makeShared<CXDGToplevelResource>(makeShared<CXdgToplevel>(r->client(), r->version(), id), self.lock()));
+
+ if (!RESOURCE->good()) {
+ r->noMemory();
+ PROTO::xdgShell->m_vToplevels.pop_back();
+ return;
+ }
+
+ toplevel = RESOURCE;
+ toplevel->self = RESOURCE;
+
+ LOGM(LOG, "xdg_surface {:x} gets a toplevel {:x}", (uintptr_t)owner.get(), (uintptr_t)RESOURCE.get());
+
+ g_pCompositor->m_vWindows.emplace_back(CWindow::create(self.lock()));
+
+ for (auto& p : popups) {
+ if (!p)
+ continue;
+ events.newPopup.emit(p);
+ }
+ });
+
+ resource->setGetPopup([this](CXdgSurface* r, uint32_t id, wl_resource* parentXDG, wl_resource* positionerRes) {
+ auto parent = parentXDG ? CXDGSurfaceResource::fromResource(parentXDG) : nullptr;
+ auto positioner = CXDGPositionerResource::fromResource(positionerRes);
+ const auto RESOURCE =
+ PROTO::xdgShell->m_vPopups.emplace_back(makeShared<CXDGPopupResource>(makeShared<CXdgPopup>(r->client(), r->version(), id), parent, self.lock(), positioner));
+
+ if (!RESOURCE->good()) {
+ r->noMemory();
+ PROTO::xdgShell->m_vPopups.pop_back();
+ return;
+ }
+
+ popup = RESOURCE;
+ RESOURCE->self = RESOURCE;
+
+ LOGM(LOG, "xdg_surface {:x} gets a popup {:x} owner {:x}", (uintptr_t)self.get(), (uintptr_t)RESOURCE.get(), (uintptr_t)parent.get());
+
+ if (!parent)
+ return;
+
+ parent->popups.emplace_back(RESOURCE);
+ if (parent->mapped)
+ parent->events.newPopup.emit(RESOURCE);
+ });
+
+ resource->setAckConfigure([this](CXdgSurface* r, uint32_t serial) {
+ events.ack.emit(serial);
+ ; // TODO: verify it
+ });
+
+ resource->setSetWindowGeometry([this](CXdgSurface* r, int32_t x, int32_t y, int32_t w, int32_t h) {
+ LOGM(LOG, "xdg_surface {:x} requests geometry {}x{} {}x{}", (uintptr_t)this, x, y, w, h);
+ pending.geometry = {x, y, w, h};
+ });
+}
+
+CXDGSurfaceResource::~CXDGSurfaceResource() {
+ events.destroy.emit();
+ if (configureSource)
+ wl_event_source_remove(configureSource);
+}
+
+bool CXDGSurfaceResource::good() {
+ return resource->resource();
+}
+
+SP<CXDGSurfaceResource> CXDGSurfaceResource::fromResource(wl_resource* res) {
+ auto data = (CXDGSurfaceResource*)(((CXdgSurface*)wl_resource_get_user_data(res))->data());
+ return data ? data->self.lock() : nullptr;
+}
+
+static void onConfigure(void* data) {
+ ((CXDGSurfaceResource*)data)->configure();
+}
+
+uint32_t CXDGSurfaceResource::scheduleConfigure() {
+ if (configureSource)
+ return scheduledSerial;
+
+ configureSource = wl_event_loop_add_idle(g_pCompositor->m_sWLEventLoop, onConfigure, this);
+ scheduledSerial = wl_display_next_serial(g_pCompositor->m_sWLDisplay);
+
+ return scheduledSerial;
+}
+
+void CXDGSurfaceResource::configure() {
+ configureSource = nullptr;
+ resource->sendConfigure(scheduledSerial);
+}
+
+CXDGPositionerResource::CXDGPositionerResource(SP<CXdgPositioner> resource_, SP<CXDGWMBase> owner_) : owner(owner_), resource(resource_) {
+ if (!good())
+ return;
+
+ resource->setData(this);
+
+ resource->setDestroy([this](CXdgPositioner* r) { PROTO::xdgShell->destroyResource(this); });
+ resource->setOnDestroy([this](CXdgPositioner* r) { PROTO::xdgShell->destroyResource(this); });
+
+ resource->setSetSize([this](CXdgPositioner* r, int32_t x, int32_t y) {
+ if (x <= 0 || y <= 0) {
+ r->error(XDG_POSITIONER_ERROR_INVALID_INPUT, "Invalid size");
+ return;
+ }
+
+ state.requestedSize = {x, y};
+ });
+
+ resource->setSetAnchorRect([this](CXdgPositioner* r, int32_t x, int32_t y, int32_t w, int32_t h) {
+ if (w <= 0 || h <= 0) {
+ r->error(XDG_POSITIONER_ERROR_INVALID_INPUT, "Invalid box");
+ return;
+ }
+
+ state.anchorRect = {x, y, w, h};
+ });
+
+ resource->setSetOffset([this](CXdgPositioner* r, int32_t x, int32_t y) { state.offset = {x, y}; });
+
+ resource->setSetAnchor([this](CXdgPositioner* r, xdgPositionerAnchor a) { state.anchor = a; });
+
+ resource->setSetGravity([this](CXdgPositioner* r, xdgPositionerGravity g) { state.gravity = g; });
+
+ resource->setSetConstraintAdjustment([this](CXdgPositioner* r, xdgPositionerConstraintAdjustment a) { state.constraintAdjustment = (uint32_t)a; });
+
+ // TODO: support this shit better. The current impl _works_, but is lacking and could be wrong in a few cases.
+ // doesn't matter _that_ much for now, though.
+}
+
+SP<CXDGPositionerResource> CXDGPositionerResource::fromResource(wl_resource* res) {
+ auto data = (CXDGPositionerResource*)(((CXdgPositioner*)wl_resource_get_user_data(res))->data());
+ return data ? data->self.lock() : nullptr;
+}
+
+bool CXDGPositionerResource::good() {
+ return resource->resource();
+}
+
+CXDGPositionerRules::CXDGPositionerRules(SP<CXDGPositionerResource> positioner) {
+ state = positioner->state;
+}
+
+static Vector2D pointForAnchor(const CBox& box, xdgPositionerAnchor anchor) {
+ switch (anchor) {
+ case XDG_POSITIONER_ANCHOR_TOP: return box.pos() + Vector2D{box.size().x / 2.F, 0};
+ case XDG_POSITIONER_ANCHOR_BOTTOM: return box.pos() + Vector2D{box.size().x / 2.F, box.size().y};
+ case XDG_POSITIONER_ANCHOR_LEFT: return box.pos() + Vector2D{0, box.size().y / 2.F};
+ case XDG_POSITIONER_ANCHOR_RIGHT: return box.pos() + Vector2D{box.size().x, box.size().y / 2.F};
+ case XDG_POSITIONER_ANCHOR_TOP_LEFT: return box.pos();
+ case XDG_POSITIONER_ANCHOR_BOTTOM_LEFT: return box.pos() + Vector2D{0, box.size().y};
+ case XDG_POSITIONER_ANCHOR_TOP_RIGHT: return box.pos() + Vector2D{box.size().x, 0};
+ case XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT: return box.pos() + Vector2D{box.size().x, box.size().y};
+ default: return box.pos();
+ }
+
+ return {};
+}
+
+CBox CXDGPositionerRules::getPosition(const CBox& constraint, const Vector2D& parentCoord) {
+
+ Debug::log(LOG, "GetPosition with constraint {} {} and parent {}", constraint.pos(), constraint.size(), parentCoord);
+
+ CBox predictedBox = {parentCoord + constraint.pos() + pointForAnchor(state.anchorRect, state.anchor) + state.offset, state.requestedSize};
+
+ bool success = predictedBox.inside(constraint);
+
+ if (success)
+ return predictedBox.translate(-parentCoord - constraint.pos());
+
+ if (state.constraintAdjustment & (XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y)) {
+ // attempt to flip
+ const bool flipX = state.constraintAdjustment & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X;
+ const bool flipY = state.constraintAdjustment & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y;
+
+ CBox test = predictedBox;
+ success = true;
+ if (flipX && test.copy().translate(Vector2D{-predictedBox.w - state.anchorRect.w, 0}).expand(-1).inside(constraint))
+ test.translate(Vector2D{-predictedBox.w - state.anchorRect.w, 0});
+ else if (flipY && test.copy().translate(Vector2D{0, -predictedBox.h - state.anchorRect.h}).expand(-1).inside(constraint))
+ test.translate(Vector2D{0, -predictedBox.h - state.anchorRect.h});
+ else if (flipX && flipY && test.copy().translate(Vector2D{-predictedBox.w - state.anchorRect.w, -predictedBox.h - state.anchorRect.h}).expand(-1).inside(constraint))
+ test.translate(Vector2D{-predictedBox.w - state.anchorRect.w, -predictedBox.h - state.anchorRect.h});
+ else
+ success = false;
+
+ if (success)
+ return test.translate(-parentCoord - constraint.pos());
+ }
+
+ if (state.constraintAdjustment & (XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y)) {
+ // attempt to slide
+ const bool slideX = state.constraintAdjustment & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X;
+ const bool slideY = state.constraintAdjustment & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y;
+
+ //const bool gravityLeft = state.gravity == XDG_POSITIONER_GRAVITY_NONE || state.gravity == XDG_POSITIONER_GRAVITY_LEFT || state.gravity == XDG_POSITIONER_GRAVITY_TOP_LEFT || state.gravity == XDG_POSITIONER_GRAVITY_BOTTOM_LEFT;
+ //const bool gravityTop = state.gravity == XDG_POSITIONER_GRAVITY_NONE || state.gravity == XDG_POSITIONER_GRAVITY_TOP || state.gravity == XDG_POSITIONER_GRAVITY_TOP_LEFT || state.gravity == XDG_POSITIONER_GRAVITY_TOP_RIGHT;
+
+ const bool leftEdgeOut = predictedBox.x < constraint.x;
+ const bool topEdgeOut = predictedBox.y < constraint.y;
+ const bool rightEdgeOut = predictedBox.x + predictedBox.w > constraint.x + constraint.w;
+ const bool bottomEdgeOut = predictedBox.y + predictedBox.h > constraint.y + constraint.h;
+
+ CBox test = predictedBox;
+
+ // TODO: this isn't truly conformant.
+ if (leftEdgeOut && slideX)
+ test.x = constraint.x;
+ if (rightEdgeOut && slideX)
+ test.x = constraint.x + constraint.w - predictedBox.w;
+ if (topEdgeOut && slideY)
+ test.y = constraint.y;
+ if (bottomEdgeOut && slideY)
+ test.y = constraint.y + constraint.h - predictedBox.y;
+
+ success = test.copy().expand(-1).inside(constraint);
+
+ if (success)
+ return test.translate(-parentCoord - constraint.pos());
+ }
+
+ if (state.constraintAdjustment & (XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y)) {
+ const bool resizeX = state.constraintAdjustment & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X;
+ const bool resizeY = state.constraintAdjustment & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y;
+
+ const bool leftEdgeOut = predictedBox.x < constraint.x;
+ const bool topEdgeOut = predictedBox.y < constraint.y;
+ const bool rightEdgeOut = predictedBox.x + predictedBox.w > constraint.x + constraint.w;
+ const bool bottomEdgeOut = predictedBox.y + predictedBox.h > constraint.y + constraint.h;
+
+ CBox test = predictedBox;
+
+ // TODO: this isn't truly conformant.
+ if (leftEdgeOut && resizeX) {
+ test.w = test.x + test.w - constraint.x;
+ test.x = constraint.x;
+ }
+ if (rightEdgeOut && resizeX)
+ test.w = -(constraint.w + constraint.x - test.w - test.x);
+ if (topEdgeOut && resizeY) {
+ test.h = test.y + test.h - constraint.y;
+ test.y = constraint.y;
+ }
+ if (bottomEdgeOut && resizeY)
+ test.h = -(constraint.h + constraint.y - test.h - test.y);
+
+ success = test.copy().expand(-1).inside(constraint);
+
+ if (success)
+ return test.translate(parentCoord - constraint.pos());
+ }
+
+ LOGM(WARN, "Compositor/client bug: xdg_positioner couldn't find a place");
+
+ return predictedBox.translate(-parentCoord - constraint.pos());
+}
+
+CXDGWMBase::CXDGWMBase(SP<CXdgWmBase> resource_) : resource(resource_) {
+ if (!good())
+ return;
+
+ resource->setDestroy([this](CXdgWmBase* r) { PROTO::xdgShell->destroyResource(this); });
+ resource->setOnDestroy([this](CXdgWmBase* r) { PROTO::xdgShell->destroyResource(this); });
+
+ pClient = resource->client();
+
+ resource->setCreatePositioner([this](CXdgWmBase* r, uint32_t id) {
+ const auto RESOURCE =
+ PROTO::xdgShell->m_vPositioners.emplace_back(makeShared<CXDGPositionerResource>(makeShared<CXdgPositioner>(r->client(), r->version(), id), self.lock()));
+
+ if (!RESOURCE->good()) {
+ r->noMemory();
+ PROTO::xdgShell->m_vPositioners.pop_back();
+ return;
+ }
+
+ RESOURCE->self = RESOURCE;
+
+ positioners.emplace_back(RESOURCE);
+
+ LOGM(LOG, "New xdg_positioner at {:x}", (uintptr_t)RESOURCE.get());
+ });
+
+ resource->setGetXdgSurface([this](CXdgWmBase* r, uint32_t id, wl_resource* surf) {
+ const auto RESOURCE = PROTO::xdgShell->m_vSurfaces.emplace_back(
+ makeShared<CXDGSurfaceResource>(makeShared<CXdgSurface>(r->client(), r->version(), id), self.lock(), wlr_surface_from_resource(surf)));
+
+ if (!RESOURCE->good()) {
+ r->noMemory();
+ PROTO::xdgShell->m_vSurfaces.pop_back();
+ return;
+ }
+
+ RESOURCE->self = RESOURCE;
+
+ surfaces.emplace_back(RESOURCE);
+
+ LOGM(LOG, "New xdg_surface at {:x}", (uintptr_t)RESOURCE.get());
+ });
+}
+
+bool CXDGWMBase::good() {
+ return resource->resource();
+}
+
+wl_client* CXDGWMBase::client() {
+ return pClient;
+}
+
+CXDGShellProtocol::CXDGShellProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {
+ ;
+}
+
+void CXDGShellProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {
+ const auto RESOURCE = m_vWMBases.emplace_back(makeShared<CXDGWMBase>(makeShared<CXdgWmBase>(client, ver, id)));
+
+ if (!RESOURCE->good()) {
+ wl_client_post_no_memory(client);
+ m_vWMBases.pop_back();
+ return;
+ }
+
+ RESOURCE->self = RESOURCE;
+
+ LOGM(LOG, "New xdg_wm_base at {:x}", (uintptr_t)RESOURCE.get());
+}
+
+void CXDGShellProtocol::destroyResource(CXDGWMBase* resource) {
+ std::erase_if(m_vWMBases, [&](const auto& other) { return other.get() == resource; });
+}
+
+void CXDGShellProtocol::destroyResource(CXDGPositionerResource* resource) {
+ std::erase_if(m_vPositioners, [&](const auto& other) { return other.get() == resource; });
+}
+
+void CXDGShellProtocol::destroyResource(CXDGSurfaceResource* resource) {
+ std::erase_if(m_vSurfaces, [&](const auto& other) { return other.get() == resource; });
+}
+
+void CXDGShellProtocol::destroyResource(CXDGToplevelResource* resource) {
+ std::erase_if(m_vToplevels, [&](const auto& other) { return other.get() == resource; });
+}
+
+void CXDGShellProtocol::destroyResource(CXDGPopupResource* resource) {
+ std::erase_if(m_vPopups, [&](const auto& other) { return other.get() == resource; });
+}
diff --git a/src/protocols/XDGShell.hpp b/src/protocols/XDGShell.hpp
new file mode 100644
index 00000000..e214b6ac
--- /dev/null
+++ b/src/protocols/XDGShell.hpp
@@ -0,0 +1,256 @@
+#pragma once
+
+#include <memory>
+#include <vector>
+#include <cstdint>
+#include <optional>
+#include "WaylandProtocol.hpp"
+#include "xdg-shell.hpp"
+#include "../helpers/Vector2D.hpp"
+#include "../helpers/Box.hpp"
+#include "../helpers/signal/Signal.hpp"
+
+class CXDGWMBase;
+class CXDGPositionerResource;
+class CXDGSurfaceResource;
+class CXDGToplevelResource;
+class CXDGPopupResource;
+class CSeatGrab;
+
+struct SXDGPositionerState {
+ Vector2D requestedSize;
+ CBox anchorRect;
+ xdgPositionerAnchor anchor = XDG_POSITIONER_ANCHOR_NONE;
+ xdgPositionerGravity gravity = XDG_POSITIONER_GRAVITY_NONE;
+ uint32_t constraintAdjustment = 0;
+ Vector2D offset;
+ bool reactive = false;
+ Vector2D parentSize;
+};
+
+class CXDGPositionerRules {
+ public:
+ CXDGPositionerRules(SP<CXDGPositionerResource> positioner);
+
+ CBox getPosition(const CBox& constraint, const Vector2D& parentPos);
+
+ private:
+ SXDGPositionerState state;
+};
+
+class CXDGPopupResource {
+ public:
+ CXDGPopupResource(SP<CXdgPopup> resource_, SP<CXDGSurfaceResource> parent_, SP<CXDGSurfaceResource> surface_, SP<CXDGPositionerResource> positioner_);
+ ~CXDGPopupResource();
+
+ static SP<CXDGPopupResource> fromResource(wl_resource*);
+
+ bool good();
+
+ void applyPositioning(const CBox& availableBox, const Vector2D& t1coord /* relative to box */);
+
+ WP<CXDGSurfaceResource> surface;
+ WP<CXDGSurfaceResource> parent;
+ WP<CXDGPopupResource> self;
+
+ bool taken = false;
+
+ CBox geometry;
+
+ struct {
+ CSignal reposition;
+ CSignal destroy; // only the role
+ } events;
+
+ // schedules a configure event
+ void configure(const CBox& box);
+
+ void done();
+ void repositioned();
+
+ private:
+ SP<CXdgPopup> resource;
+
+ uint32_t lastRepositionToken = 0;
+
+ Vector2D accumulateParentOffset();
+
+ CXDGPositionerRules positionerRules;
+};
+
+class CXDGToplevelResource {
+ public:
+ CXDGToplevelResource(SP<CXdgToplevel> resource_, SP<CXDGSurfaceResource> owner_);
+ ~CXDGToplevelResource();
+
+ static SP<CXDGToplevelResource> fromResource(wl_resource*);
+
+ WP<CXDGSurfaceResource> owner;
+ WP<CXDGToplevelResource> self;
+
+ PHLWINDOWREF window;
+
+ bool good();
+
+ // schedule a configure event
+ uint32_t setSize(const Vector2D& size);
+ uint32_t setMaximized(bool maximized);
+ uint32_t setFullscreen(bool fullscreen);
+ uint32_t setActive(bool active);
+ uint32_t setSuspeneded(bool sus);
+
+ void close();
+
+ struct {
+ CSignal sizeLimitsChanged;
+ CSignal stateChanged; // maximized, fs, minimized, etc.
+ CSignal metadataChanged; // title, appid
+ CSignal destroy; // only the role
+ } events;
+
+ struct {
+ std::string title;
+ std::string appid;
+
+ // volatile state: is reset after the stateChanged signal fires
+ std::optional<bool> requestsMaximize;
+ std::optional<bool> requestsFullscreen;
+ std::optional<bool> requestsMinimize;
+ } state;
+
+ struct {
+ Vector2D size;
+ std::vector<xdgToplevelState> states;
+ } pendingApply;
+
+ struct {
+ Vector2D minSize = {1, 1};
+ Vector2D maxSize = {1337420, 694200};
+ } pending, current;
+
+ WP<CXDGToplevelResource> parent;
+
+ private:
+ SP<CXdgToplevel> resource;
+ void applyState();
+};
+
+class CXDGSurfaceResource {
+ public:
+ CXDGSurfaceResource(SP<CXdgSurface> resource_, SP<CXDGWMBase> owner_, wlr_surface* surface_);
+ ~CXDGSurfaceResource();
+
+ static SP<CXDGSurfaceResource> fromResource(wl_resource*);
+
+ bool good();
+
+ WP<CXDGWMBase> owner;
+ wlr_surface* surface = nullptr;
+
+ WP<CXDGToplevelResource> toplevel;
+ WP<CXDGPopupResource> popup;
+
+ WP<CXDGSurfaceResource> self;
+
+ struct {
+ CBox geometry;
+ } pending, current;
+
+ struct {
+ CSignal ack;
+ CSignal commit;
+ CSignal map;
+ CSignal unmap;
+ CSignal destroy;
+ CSignal newPopup; // SP<CXDGPopupResource>
+ } events;
+
+ bool initialCommit = true;
+ bool mapped = false;
+
+ uint32_t scheduleConfigure();
+ // do not call directly
+ void configure();
+
+ private:
+ SP<CXdgSurface> resource;
+
+ uint32_t lastConfigureSerial = 0;
+ uint32_t scheduledSerial = 0;
+
+ wl_event_source* configureSource = nullptr;
+
+ //
+ std::vector<WP<CXDGPopupResource>> popups;
+
+ DYNLISTENER(surfaceDestroy);
+ DYNLISTENER(surfaceCommit);
+
+ friend class CXDGPopupResource;
+ friend class CXDGToplevelResource;
+};
+
+class CXDGPositionerResource {
+ public:
+ CXDGPositionerResource(SP<CXdgPositioner> resource_, SP<CXDGWMBase> owner_);
+
+ static SP<CXDGPositionerResource> fromResource(wl_resource*);
+
+ bool good();
+
+ SXDGPositionerState state;
+
+ WP<CXDGWMBase> owner;
+ WP<CXDGPositionerResource> self;
+
+ private:
+ SP<CXdgPositioner> resource;
+};
+
+class CXDGWMBase {
+ public:
+ CXDGWMBase(SP<CXdgWmBase> resource_);
+
+ bool good();
+ wl_client* client();
+
+ std::vector<WP<CXDGPositionerResource>> positioners;
+ std::vector<WP<CXDGSurfaceResource>> surfaces;
+
+ WP<CXDGWMBase> self;
+
+ private:
+ SP<CXdgWmBase> resource;
+ wl_client* pClient = nullptr;
+};
+
+class CXDGShellProtocol : public IWaylandProtocol {
+ public:
+ CXDGShellProtocol(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);
+
+ private:
+ void destroyResource(CXDGWMBase* resource);
+ void destroyResource(CXDGPositionerResource* resource);
+ void destroyResource(CXDGSurfaceResource* resource);
+ void destroyResource(CXDGToplevelResource* resource);
+ void destroyResource(CXDGPopupResource* resource);
+
+ //
+ std::vector<SP<CXDGWMBase>> m_vWMBases;
+ std::vector<SP<CXDGPositionerResource>> m_vPositioners;
+ std::vector<SP<CXDGSurfaceResource>> m_vSurfaces;
+ std::vector<SP<CXDGToplevelResource>> m_vToplevels;
+ std::vector<SP<CXDGPopupResource>> m_vPopups;
+
+ friend class CXDGWMBase;
+ friend class CXDGPositionerResource;
+ friend class CXDGSurfaceResource;
+ friend class CXDGToplevelResource;
+ friend class CXDGPopupResource;
+};
+
+namespace PROTO {
+ inline UP<CXDGShellProtocol> xdgShell;
+};