diff --git a/CMakeLists.txt b/CMakeLists.txt index f9bd7d418..400b8e102 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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() diff --git a/src/desktop/view/WLSurface.hpp b/src/desktop/view/WLSurface.hpp index 3c5e3a382..d0442a794 100644 --- a/src/desktop/view/WLSurface.hpp +++ b/src/desktop/view/WLSurface.hpp @@ -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; diff --git a/src/managers/ProtocolManager.cpp b/src/managers/ProtocolManager.cpp index d77d1b0a6..84537b496 100644 --- a/src/managers/ProtocolManager.cpp +++ b/src/managers/ProtocolManager.cpp @@ -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(&wp_fifo_manager_v1_interface, 1, "Fifo"); PROTO::xdgForeignExporter = makeUnique(&zxdg_exporter_v2_interface, 1, "XDGForeignExporter"); PROTO::xdgForeignImporter = makeUnique(&zxdg_importer_v2_interface, 1, "XDGForeignImporter"); + PROTO::backgroundEffect = makeUnique(&ext_background_effect_manager_v1_interface, 1, "BackgroundEffect"); if (*PENABLECT) PROTO::commitTiming = makeUnique(&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, diff --git a/src/protocols/BackgroundEffect.cpp b/src/protocols/BackgroundEffect.cpp new file mode 100644 index 000000000..7eec991c2 --- /dev/null +++ b/src/protocols/BackgroundEffect.cpp @@ -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 +#include + +CBackgroundEffect::CBackgroundEffect(SP resource, SP surface) : m_surface(surface) { + setResource(std::move(resource)); +} + +bool CBackgroundEffect::good() const { + return m_resource->resource(); +} + +void CBackgroundEffect::setResource(SP 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(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 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(manager->client(), manager->version(), id)); + effect = iter->second.get(); + } + } else { + effect = m_effects.emplace(surface, makeUnique(makeShared(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; +} diff --git a/src/protocols/BackgroundEffect.hpp b/src/protocols/BackgroundEffect.hpp new file mode 100644 index 000000000..eb3f6fd7c --- /dev/null +++ b/src/protocols/BackgroundEffect.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include +#include "WaylandProtocol.hpp" +#include "ext-background-effect-v1.hpp" +#include "../helpers/signal/Signal.hpp" + +class CWLSurfaceResource; +class CBackgroundEffectProtocol; + +class CBackgroundEffect { + public: + CBackgroundEffect(SP resource, SP surface); + + bool good() const; + void setResource(SP resource); + + private: + SP m_resource; + WP 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 surface); + + std::vector> m_managers; + std::unordered_map, UP> m_effects; + + friend class CBackgroundEffect; +}; + +namespace PROTO { + inline UP backgroundEffect; +} diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp index 68e7e36d2..0fc47b065 100644 --- a/src/render/OpenGL.cpp +++ b/src/render/OpenGL.cpp @@ -1990,6 +1990,19 @@ void CHyprOpenGLImpl::renderTextureWithBlurInternal(SP tex, const CBox static auto PBLURIGNOREOPACITY = CConfigValue("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 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, diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index 315793423..43973c949 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -3322,16 +3322,33 @@ bool IHyprRenderer::shouldBlur(PHLLS ls) { return false; static auto PBLUR = CConfigValue("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("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("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 p) {