diff options
| author | Vaxry <vaxry@vaxry.net> | 2024-05-10 23:28:33 +0100 |
|---|---|---|
| committer | Vaxry <43317083+vaxerski@users.noreply.github.com> | 2024-05-14 23:02:24 +0100 |
| commit | 0cfdde3d1acbfbf698af17f6986fbc4d644214da (patch) | |
| tree | 579ac53bc228db6d0feb373448c8e5557d3285d4 /src/protocols | |
| parent | 121d3a72137d4780602cf245704615f63357ea22 (diff) | |
xdg-shell: move to new impl
Diffstat (limited to 'src/protocols')
| -rw-r--r-- | src/protocols/ForeignToplevel.cpp | 2 | ||||
| -rw-r--r-- | src/protocols/ForeignToplevelWlr.cpp | 4 | ||||
| -rw-r--r-- | src/protocols/LayerShell.cpp | 7 | ||||
| -rw-r--r-- | src/protocols/XDGShell.cpp | 697 | ||||
| -rw-r--r-- | src/protocols/XDGShell.hpp | 256 |
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; +}; |
