protocols: implement ext-background-effect-v1 protocol (#13211)

* feat: implement ext-background-effect-v1 protocol

* chore: format

* build: unvendorize ext-background-effect protocol

* refactor: sync with upstream changes
This commit is contained in:
Aurelle
2026-05-16 19:09:56 +01:00
committed by GitHub
parent 8521487de8
commit 7d1e481bb8
7 changed files with 225 additions and 5 deletions
+1
View File
@@ -590,6 +590,7 @@ protocolnew("staging/fifo" "fifo-v1" false)
protocolnew("staging/commit-timing" "commit-timing-v1" false)
protocolnew("staging/ext-image-capture-source" "ext-image-capture-source-v1" false)
protocolnew("staging/ext-image-copy-capture" "ext-image-copy-capture-v1" false)
protocolnew("staging/ext-background-effect" "ext-background-effect-v1" false)
protocolwayland()
+4
View File
@@ -85,6 +85,10 @@ namespace Desktop::View {
float m_overallOpacity = 1.F;
CRegion m_visibleRegion;
// used by the ext-background-effect protocol
bool m_hasBackgroundEffect = false;
CRegion m_blurRegion;
struct {
CSignalT<> destroy;
} m_events;
+4
View File
@@ -52,6 +52,7 @@
#include "../protocols/HyprlandSurface.hpp"
#include "../protocols/ImageCaptureSource.hpp"
#include "../protocols/ImageCopyCapture.hpp"
#include "../protocols/BackgroundEffect.hpp"
#include "../protocols/core/Seat.hpp"
#include "../protocols/core/DataDevice.hpp"
#include "../protocols/core/Compositor.hpp"
@@ -199,6 +200,7 @@ CProtocolManager::CProtocolManager() {
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");
PROTO::backgroundEffect = makeUnique<CBackgroundEffectProtocol>(&ext_background_effect_manager_v1_interface, 1, "BackgroundEffect");
if (*PENABLECT)
PROTO::commitTiming = makeUnique<CCommitTimingProtocol>(&wp_commit_timing_manager_v1_interface, 1, "CommitTiming");
@@ -310,6 +312,7 @@ CProtocolManager::~CProtocolManager() {
PROTO::xdgForeignImporter.reset();
PROTO::commitTiming.reset();
PROTO::imageCaptureSource.reset();
PROTO::backgroundEffect.reset();
for (auto& [_, lease] : PROTO::lease) {
lease.reset();
@@ -366,6 +369,7 @@ bool CProtocolManager::isGlobalPrivileged(const wl_global* global) {
PROTO::commitTiming->getGlobal(),
PROTO::xdgForeignExporter->getGlobal(),
PROTO::xdgForeignImporter->getGlobal(),
PROTO::backgroundEffect->getGlobal(),
PROTO::sync ? PROTO::sync->getGlobal() : nullptr,
PROTO::mesaDRM ? PROTO::mesaDRM->getGlobal() : nullptr,
PROTO::linuxDma ? PROTO::linuxDma->getGlobal() : nullptr,
+127
View File
@@ -0,0 +1,127 @@
#include "BackgroundEffect.hpp"
#include "../desktop/view/WLSurface.hpp"
#include "../render/Renderer.hpp"
#include "core/Compositor.hpp"
#include "ext-background-effect-v1.hpp"
#include <hyprutils/math/Region.hpp>
#include <wayland-server.h>
CBackgroundEffect::CBackgroundEffect(SP<CExtBackgroundEffectSurfaceV1> resource, SP<CWLSurfaceResource> surface) : m_surface(surface) {
setResource(std::move(resource));
}
bool CBackgroundEffect::good() const {
return m_resource->resource();
}
void CBackgroundEffect::setResource(SP<CExtBackgroundEffectSurfaceV1> resource) {
m_resource = std::move(resource);
if UNLIKELY (!m_resource->resource())
return;
m_resource->setDestroy([this](CExtBackgroundEffectSurfaceV1* resource) { destroy(); });
m_resource->setOnDestroy([this](CExtBackgroundEffectSurfaceV1* resource) { destroy(); });
m_resource->setSetBlurRegion([this](CExtBackgroundEffectSurfaceV1* resource, wl_resource* region) {
if UNLIKELY (!m_surface) {
m_resource->error(EXT_BACKGROUND_EFFECT_SURFACE_V1_ERROR_SURFACE_DESTROYED, "set_blur_region called for destroyed wl_surface");
return;
}
if (!region) {
m_blurRegion.clear();
return;
}
m_blurRegion = CWLRegionResource::fromResource(region)->m_region;
});
m_listeners.surfaceCommitted = m_surface->m_events.commit.listen([this] {
auto hlSurface = Desktop::View::CWLSurface::fromResource(m_surface.lock());
if (!hlSurface)
return;
if (!m_resource) {
// effect was destroyed, clear state on commit per spec
hlSurface->m_hasBackgroundEffect = false;
hlSurface->m_blurRegion.clear();
auto box = hlSurface->getSurfaceBoxGlobal();
if (box.has_value())
g_pHyprRenderer->damageBox(*box);
PROTO::backgroundEffect->destroyEffect(this);
return;
}
hlSurface->m_hasBackgroundEffect = true;
hlSurface->m_blurRegion = m_blurRegion;
auto box = hlSurface->getSurfaceBoxGlobal();
if (box.has_value())
g_pHyprRenderer->damageBox(*box);
});
m_listeners.surfaceDestroyed = m_surface->m_events.destroy.listen([this] { PROTO::backgroundEffect->destroyEffect(this); });
}
void CBackgroundEffect::destroy() {
m_resource.reset();
m_blurRegion.clear();
// The spec requires effect removal to be double-buffered: state is cleared on next wl_surface commit.
// If the surface is already destroyed, clean up immediately.
if (!m_surface)
PROTO::backgroundEffect->destroyEffect(this);
}
CBackgroundEffectProtocol::CBackgroundEffectProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {
;
}
void CBackgroundEffectProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {
auto manager = m_managers.emplace_back(makeUnique<CExtBackgroundEffectManagerV1>(client, ver, id)).get();
manager->setOnDestroy([this](CExtBackgroundEffectManagerV1* manager) { destroyManager(manager); });
manager->setDestroy([this](CExtBackgroundEffectManagerV1* manager) { destroyManager(manager); });
manager->sendCapabilities(EXT_BACKGROUND_EFFECT_MANAGER_V1_CAPABILITY_BLUR);
manager->setGetBackgroundEffect(
[this](CExtBackgroundEffectManagerV1* manager, uint32_t id, wl_resource* surface) { getBackgroundEffect(manager, id, CWLSurfaceResource::fromResource(surface)); });
}
void CBackgroundEffectProtocol::destroyManager(CExtBackgroundEffectManagerV1* manager) {
std::erase_if(m_managers, [&](const auto& p) { return p.get() == manager; });
}
void CBackgroundEffectProtocol::destroyEffect(CBackgroundEffect* effect) {
std::erase_if(m_effects, [&](const auto& entry) { return entry.second.get() == effect; });
}
void CBackgroundEffectProtocol::getBackgroundEffect(CExtBackgroundEffectManagerV1* manager, uint32_t id, SP<CWLSurfaceResource> surface) {
CBackgroundEffect* effect = nullptr;
auto iter = std::ranges::find_if(m_effects, [&](const auto& entry) { return entry.second->m_surface == surface; });
if (iter != m_effects.end()) {
if (iter->second->m_resource) {
LOGM(Log::ERR, "BackgroundEffect already present for surface {:x}", (uintptr_t)surface.get());
manager->error(EXT_BACKGROUND_EFFECT_MANAGER_V1_ERROR_BACKGROUND_EFFECT_EXISTS, "BackgroundEffect already present");
return;
} else {
iter->second->setResource(makeShared<CExtBackgroundEffectSurfaceV1>(manager->client(), manager->version(), id));
effect = iter->second.get();
}
} else {
effect = m_effects.emplace(surface, makeUnique<CBackgroundEffect>(makeShared<CExtBackgroundEffectSurfaceV1>(manager->client(), manager->version(), id), surface))
.first->second.get();
}
if UNLIKELY (!effect->good()) {
manager->noMemory();
m_effects.erase(surface);
return;
}
auto hlSurface = Desktop::View::CWLSurface::fromResource(surface);
if (hlSurface)
hlSurface->m_hasBackgroundEffect = true;
}
+54
View File
@@ -0,0 +1,54 @@
#pragma once
#include <hyprutils/math/Region.hpp>
#include <vector>
#include <unordered_map>
#include "WaylandProtocol.hpp"
#include "ext-background-effect-v1.hpp"
#include "../helpers/signal/Signal.hpp"
class CWLSurfaceResource;
class CBackgroundEffectProtocol;
class CBackgroundEffect {
public:
CBackgroundEffect(SP<CExtBackgroundEffectSurfaceV1> resource, SP<CWLSurfaceResource> surface);
bool good() const;
void setResource(SP<CExtBackgroundEffectSurfaceV1> resource);
private:
SP<CExtBackgroundEffectSurfaceV1> m_resource;
WP<CWLSurfaceResource> m_surface;
CRegion m_blurRegion;
void destroy();
struct {
CHyprSignalListener surfaceCommitted;
CHyprSignalListener surfaceDestroyed;
} m_listeners;
friend class CBackgroundEffectProtocol;
};
class CBackgroundEffectProtocol : public IWaylandProtocol {
public:
CBackgroundEffectProtocol(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 destroyManager(CExtBackgroundEffectManagerV1* res);
void destroyEffect(CBackgroundEffect* effect);
void getBackgroundEffect(CExtBackgroundEffectManagerV1* manager, uint32_t id, SP<CWLSurfaceResource> surface);
std::vector<UP<CExtBackgroundEffectManagerV1>> m_managers;
std::unordered_map<WP<CWLSurfaceResource>, UP<CBackgroundEffect>> m_effects;
friend class CBackgroundEffect;
};
namespace PROTO {
inline UP<CBackgroundEffectProtocol> backgroundEffect;
}
+14 -1
View File
@@ -1990,6 +1990,19 @@ void CHyprOpenGLImpl::renderTextureWithBlurInternal(SP<ITexture> tex, const CBox
static auto PBLURIGNOREOPACITY = CConfigValue<Config::INTEGER>("decoration:blur:ignore_opacity");
// handle ext-background-effect-v1 blur region if specified
CRegion blurClipRegion = data.clipRegion;
auto PSURFACE = Desktop::View::CWLSurface::fromResource(data.surface);
if (PSURFACE && PSURFACE->m_hasBackgroundEffect && !PSURFACE->m_blurRegion.empty()) {
CRegion protocolBlur = PSURFACE->m_blurRegion.copy();
protocolBlur.scale(m_renderData.pMonitor->m_scale);
protocolBlur.translate(box.pos());
if (blurClipRegion.empty())
blurClipRegion = protocolBlur;
else
blurClipRegion.intersect(protocolBlur);
}
g_pHyprRenderer->pushMonitorTransformEnabled(true);
bool renderModif = g_pHyprRenderer->m_renderData.renderModif.enabled;
if (!data.blockBlurOptimization)
@@ -2008,7 +2021,7 @@ void CHyprOpenGLImpl::renderTextureWithBlurInternal(SP<ITexture> tex, const CBox
.wrapY = data.wrapY,
.discardMode = data.discardMode,
.discardOpacity = data.discardOpacity,
.clipRegion = data.clipRegion,
.clipRegion = blurClipRegion,
.currentLS = data.currentLS,
.primarySurfaceUVTopLeft = monitorSpaceBox.pos() / m_renderData.pMonitor->m_transformedSize,
+21 -4
View File
@@ -3322,16 +3322,33 @@ bool IHyprRenderer::shouldBlur(PHLLS ls) {
return false;
static auto PBLUR = CConfigValue<Config::INTEGER>("decoration:blur:enabled");
return *PBLUR && ls->m_ruleApplicator->blur().valueOrDefault();
if (!*PBLUR)
return false;
auto surface = ls->wlSurface();
if (surface && surface->m_hasBackgroundEffect)
return !surface->m_blurRegion.empty();
return ls->m_ruleApplicator->blur().valueOrDefault();
}
bool IHyprRenderer::shouldBlur(PHLWINDOW w) {
if (m_bRenderingSnapshot)
return false;
static auto PBLUR = CConfigValue<Config::INTEGER>("decoration:blur:enabled");
const bool DONT_BLUR = w->m_ruleApplicator->noBlur().valueOrDefault() || w->m_ruleApplicator->RGBX().valueOrDefault() || w->opaque();
return *PBLUR && !DONT_BLUR;
static auto PBLUR = CConfigValue<Config::INTEGER>("decoration:blur:enabled");
if (!*PBLUR)
return false;
const bool DONT_BLUR = w->m_ruleApplicator->noBlur().valueOrDefault() || w->m_ruleApplicator->RGBX().valueOrDefault() || w->opaque();
if (DONT_BLUR)
return false;
auto surface = w->wlSurface();
if (surface && surface->m_hasBackgroundEffect)
return !surface->m_blurRegion.empty();
return true;
}
bool IHyprRenderer::shouldBlur(WP<Desktop::View::CPopup> p) {