2024-02-18 23:08:03 +00:00
|
|
|
#include "Background.hpp"
|
|
|
|
|
#include "../Renderer.hpp"
|
2025-06-22 09:24:39 +02:00
|
|
|
#include "../Framebuffer.hpp"
|
|
|
|
|
#include "../Shared.hpp"
|
2024-12-18 16:28:05 +01:00
|
|
|
#include "../../core/hyprlock.hpp"
|
2024-12-26 15:43:11 +00:00
|
|
|
#include "../../helpers/Log.hpp"
|
|
|
|
|
#include "../../helpers/MiscFunctions.hpp"
|
2025-06-22 09:24:39 +02:00
|
|
|
#include "../../core/AnimationManager.hpp"
|
|
|
|
|
#include "../../config/ConfigManager.hpp"
|
2024-12-18 16:28:05 +01:00
|
|
|
#include <chrono>
|
2024-10-13 12:04:32 +00:00
|
|
|
#include <hyprlang.hpp>
|
2024-12-18 16:28:05 +01:00
|
|
|
#include <filesystem>
|
|
|
|
|
#include <memory>
|
|
|
|
|
#include <GLES3/gl32.h>
|
|
|
|
|
|
2025-06-22 09:24:39 +02:00
|
|
|
CBackground::CBackground() {
|
|
|
|
|
blurredFB = makeUnique<CFramebuffer>();
|
|
|
|
|
pendingBlurredFB = makeUnique<CFramebuffer>();
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-18 16:28:05 +01:00
|
|
|
CBackground::~CBackground() {
|
2025-03-05 08:35:43 +01:00
|
|
|
reset();
|
|
|
|
|
}
|
2024-12-18 16:28:05 +01:00
|
|
|
|
2025-03-05 08:35:43 +01:00
|
|
|
void CBackground::registerSelf(const SP<CBackground>& self) {
|
|
|
|
|
m_self = self;
|
2024-12-18 16:28:05 +01:00
|
|
|
}
|
2024-02-18 23:08:03 +00:00
|
|
|
|
2025-03-05 08:35:43 +01:00
|
|
|
void CBackground::configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput) {
|
|
|
|
|
reset();
|
2024-02-21 22:19:01 +00:00
|
|
|
|
2024-12-18 16:28:05 +01:00
|
|
|
try {
|
|
|
|
|
color = std::any_cast<Hyprlang::INT>(props.at("color"));
|
|
|
|
|
blurPasses = std::any_cast<Hyprlang::INT>(props.at("blur_passes"));
|
|
|
|
|
blurSize = std::any_cast<Hyprlang::INT>(props.at("blur_size"));
|
|
|
|
|
vibrancy = std::any_cast<Hyprlang::FLOAT>(props.at("vibrancy"));
|
|
|
|
|
vibrancy_darkness = std::any_cast<Hyprlang::FLOAT>(props.at("vibrancy_darkness"));
|
|
|
|
|
noise = std::any_cast<Hyprlang::FLOAT>(props.at("noise"));
|
|
|
|
|
brightness = std::any_cast<Hyprlang::FLOAT>(props.at("brightness"));
|
|
|
|
|
contrast = std::any_cast<Hyprlang::FLOAT>(props.at("contrast"));
|
|
|
|
|
path = std::any_cast<Hyprlang::STRING>(props.at("path"));
|
|
|
|
|
reloadCommand = std::any_cast<Hyprlang::STRING>(props.at("reload_cmd"));
|
|
|
|
|
reloadTime = std::any_cast<Hyprlang::INT>(props.at("reload_time"));
|
|
|
|
|
|
|
|
|
|
} catch (const std::bad_any_cast& e) {
|
|
|
|
|
RASSERT(false, "Failed to construct CBackground: {}", e.what()); //
|
|
|
|
|
} catch (const std::out_of_range& e) {
|
|
|
|
|
RASSERT(false, "Missing propperty for CBackground: {}", e.what()); //
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-05 08:35:43 +01:00
|
|
|
isScreenshot = path == "screenshot";
|
|
|
|
|
|
2025-06-22 09:24:39 +02:00
|
|
|
viewport = pOutput->getViewport();
|
|
|
|
|
outputPort = pOutput->stringPort;
|
|
|
|
|
transform = isScreenshot ? wlTransformToHyprutils(invertTransform(pOutput->transform)) : HYPRUTILS_TRANSFORM_NORMAL;
|
|
|
|
|
scResourceID = CScreencopyFrame::getResourceId(pOutput);
|
|
|
|
|
|
|
|
|
|
g_pAnimationManager->createAnimation(0.f, crossFadeProgress, g_pConfigManager->m_AnimationTree.getConfig("fadeIn"));
|
2025-03-05 08:35:43 +01:00
|
|
|
|
|
|
|
|
if (isScreenshot) {
|
2025-06-22 09:24:39 +02:00
|
|
|
resourceID = scResourceID;
|
2025-03-05 08:35:43 +01:00
|
|
|
// When the initial gather of the asyncResourceGatherer is completed (ready), all DMAFrames are available.
|
|
|
|
|
// Dynamic ones are tricky, because a screencopy would copy hyprlock itself.
|
|
|
|
|
if (g_pRenderer->asyncResourceGatherer->gathered) {
|
|
|
|
|
if (!g_pRenderer->asyncResourceGatherer->getAssetByID(resourceID))
|
|
|
|
|
resourceID = ""; // Fallback to solid color (background:color)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!g_pHyprlock->getScreencopy()) {
|
|
|
|
|
Debug::log(ERR, "No screencopy support! path=screenshot won't work. Falling back to background color.");
|
|
|
|
|
resourceID = "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} else if (!path.empty())
|
|
|
|
|
resourceID = "background:" + path;
|
|
|
|
|
|
2024-12-26 15:43:11 +00:00
|
|
|
if (!isScreenshot && reloadTime > -1) {
|
|
|
|
|
try {
|
|
|
|
|
modificationTime = std::filesystem::last_write_time(absolutePath(path, ""));
|
|
|
|
|
} catch (std::exception& e) { Debug::log(ERR, "{}", e.what()); }
|
2024-12-18 16:28:05 +01:00
|
|
|
|
|
|
|
|
plantReloadTimer(); // No reloads for screenshots.
|
2024-12-26 15:43:11 +00:00
|
|
|
}
|
2024-02-18 23:08:03 +00:00
|
|
|
}
|
|
|
|
|
|
2025-03-05 08:35:43 +01:00
|
|
|
void CBackground::reset() {
|
|
|
|
|
if (reloadTimer) {
|
|
|
|
|
reloadTimer->cancel();
|
|
|
|
|
reloadTimer.reset();
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-22 09:24:39 +02:00
|
|
|
blurredFB->destroyBuffer();
|
|
|
|
|
pendingBlurredFB->destroyBuffer();
|
2025-03-05 08:35:43 +01:00
|
|
|
}
|
|
|
|
|
|
2025-01-06 12:34:21 +00:00
|
|
|
void CBackground::renderRect(CHyprColor color) {
|
2024-07-07 18:43:17 +02:00
|
|
|
CBox monbox = {0, 0, viewport.x, viewport.y};
|
|
|
|
|
g_pRenderer->renderRect(monbox, color, 0);
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-05 08:35:43 +01:00
|
|
|
static void onReloadTimer(WP<CBackground> ref) {
|
|
|
|
|
if (auto PBG = ref.lock(); PBG) {
|
|
|
|
|
PBG->onReloadTimerUpdate();
|
|
|
|
|
PBG->plantReloadTimer();
|
|
|
|
|
}
|
2024-12-18 16:28:05 +01:00
|
|
|
}
|
|
|
|
|
|
2025-03-05 08:35:43 +01:00
|
|
|
static void onAssetCallback(WP<CBackground> ref) {
|
|
|
|
|
if (auto PBG = ref.lock(); PBG)
|
2025-06-22 09:24:39 +02:00
|
|
|
PBG->startCrossFade();
|
2024-12-18 16:28:05 +01:00
|
|
|
}
|
|
|
|
|
|
2025-06-22 09:24:39 +02:00
|
|
|
void CBackground::renderBlur(const CTexture& tex, CFramebuffer& fb) {
|
|
|
|
|
if (firstRender)
|
|
|
|
|
firstRender = false;
|
2024-02-19 20:50:58 +00:00
|
|
|
|
2025-06-22 09:24:39 +02:00
|
|
|
// make it brah
|
|
|
|
|
Vector2D size = asset->texture.m_vSize;
|
|
|
|
|
if (transform % 2 == 1 && isScreenshot) {
|
|
|
|
|
size.x = asset->texture.m_vSize.y;
|
|
|
|
|
size.y = asset->texture.m_vSize.x;
|
2024-02-19 20:50:58 +00:00
|
|
|
}
|
|
|
|
|
|
2025-06-22 09:24:39 +02:00
|
|
|
CBox texbox = {{}, size};
|
2024-02-18 23:08:03 +00:00
|
|
|
|
2025-06-22 09:24:39 +02:00
|
|
|
float scaleX = viewport.x / size.x;
|
|
|
|
|
float scaleY = viewport.y / size.y;
|
2024-03-09 17:45:44 +01:00
|
|
|
|
2025-06-22 09:24:39 +02:00
|
|
|
texbox.w *= std::max(scaleX, scaleY);
|
|
|
|
|
texbox.h *= std::max(scaleX, scaleY);
|
2024-02-18 23:08:03 +00:00
|
|
|
|
2025-06-22 09:24:39 +02:00
|
|
|
if (scaleX > scaleY)
|
|
|
|
|
texbox.y = -(texbox.h - viewport.y) / 2.f;
|
|
|
|
|
else
|
|
|
|
|
texbox.x = -(texbox.w - viewport.x) / 2.f;
|
|
|
|
|
texbox.round();
|
2024-12-18 16:28:05 +01:00
|
|
|
|
2025-06-22 09:24:39 +02:00
|
|
|
if (!fb.isAllocated())
|
|
|
|
|
fb.alloc(viewport.x, viewport.y); // TODO 10 bit
|
2024-12-18 16:28:05 +01:00
|
|
|
|
2025-06-22 09:24:39 +02:00
|
|
|
fb.bind();
|
2024-03-03 02:19:25 +00:00
|
|
|
|
2025-06-22 09:24:39 +02:00
|
|
|
g_pRenderer->renderTexture(texbox, tex, 1.0, 0, transform);
|
2024-02-21 22:19:01 +00:00
|
|
|
|
2025-06-22 09:24:39 +02:00
|
|
|
if (blurPasses > 0)
|
|
|
|
|
g_pRenderer->blurFB(fb,
|
|
|
|
|
CRenderer::SBlurParams{.size = blurSize,
|
|
|
|
|
.passes = blurPasses,
|
|
|
|
|
.noise = noise,
|
|
|
|
|
.contrast = contrast,
|
|
|
|
|
.brightness = brightness,
|
|
|
|
|
.vibrancy = vibrancy,
|
|
|
|
|
.vibrancy_darkness = vibrancy_darkness});
|
|
|
|
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
|
|
|
|
}
|
2024-02-21 22:19:01 +00:00
|
|
|
|
2025-06-22 09:24:39 +02:00
|
|
|
static CBox getScaledBoxForTexture(const CTexture& tex, const Vector2D& viewport) {
|
|
|
|
|
CBox texbox = {{}, tex.m_vSize};
|
2024-02-18 23:08:03 +00:00
|
|
|
|
2025-06-22 09:24:39 +02:00
|
|
|
Vector2D size = tex.m_vSize;
|
|
|
|
|
float scaleX = viewport.x / tex.m_vSize.x;
|
|
|
|
|
float scaleY = viewport.y / tex.m_vSize.y;
|
2024-02-20 00:26:13 +00:00
|
|
|
|
|
|
|
|
texbox.w *= std::max(scaleX, scaleY);
|
|
|
|
|
texbox.h *= std::max(scaleX, scaleY);
|
|
|
|
|
|
|
|
|
|
if (scaleX > scaleY)
|
|
|
|
|
texbox.y = -(texbox.h - viewport.y) / 2.f;
|
|
|
|
|
else
|
|
|
|
|
texbox.x = -(texbox.w - viewport.x) / 2.f;
|
2024-02-22 00:31:33 +00:00
|
|
|
texbox.round();
|
2024-02-18 23:08:03 +00:00
|
|
|
|
2025-06-22 09:24:39 +02:00
|
|
|
return texbox;
|
2024-12-18 16:28:05 +01:00
|
|
|
}
|
|
|
|
|
|
2025-06-22 09:24:39 +02:00
|
|
|
bool CBackground::draw(const SRenderData& data) {
|
|
|
|
|
if (!asset && !resourceID.empty())
|
|
|
|
|
asset = g_pRenderer->asyncResourceGatherer->getAssetByID(resourceID);
|
2024-12-18 16:28:05 +01:00
|
|
|
|
2025-06-22 09:24:39 +02:00
|
|
|
// path=screenshot -> scAsset = asset
|
|
|
|
|
if (!scAsset)
|
|
|
|
|
scAsset = (asset && isScreenshot) ? asset : g_pRenderer->asyncResourceGatherer->getAssetByID(scResourceID);
|
|
|
|
|
|
|
|
|
|
if (!asset || resourceID.empty()) {
|
|
|
|
|
// fade in/out with a solid color
|
|
|
|
|
if (data.opacity < 1.0 && scAsset) {
|
|
|
|
|
const auto SCTEXBOX = getScaledBoxForTexture(scAsset->texture, viewport);
|
|
|
|
|
g_pRenderer->renderTexture(SCTEXBOX, scAsset->texture, 1, 0, HYPRUTILS_TRANSFORM_FLIPPED_180);
|
|
|
|
|
CHyprColor col = color;
|
|
|
|
|
col.a *= data.opacity;
|
|
|
|
|
renderRect(col);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2024-12-18 16:28:05 +01:00
|
|
|
|
2025-06-22 09:24:39 +02:00
|
|
|
renderRect(color);
|
|
|
|
|
return !asset && !resourceID.empty(); // resource not ready
|
|
|
|
|
}
|
2024-12-18 16:28:05 +01:00
|
|
|
|
2025-06-22 09:24:39 +02:00
|
|
|
if (asset->texture.m_iType == TEXTURE_INVALID) {
|
|
|
|
|
g_pRenderer->asyncResourceGatherer->unloadAsset(asset);
|
|
|
|
|
resourceID = "";
|
|
|
|
|
renderRect(color);
|
|
|
|
|
return false;
|
2024-12-18 16:28:05 +01:00
|
|
|
}
|
|
|
|
|
|
2025-06-22 09:24:39 +02:00
|
|
|
if (asset && (blurPasses > 0 || isScreenshot) && (!blurredFB->isAllocated() || firstRender))
|
|
|
|
|
renderBlur(asset->texture, *blurredFB);
|
2024-12-18 16:28:05 +01:00
|
|
|
|
2025-06-22 09:24:39 +02:00
|
|
|
// For crossfading a new asset
|
|
|
|
|
if (pendingAsset && blurPasses > 0 && !pendingBlurredFB->isAllocated())
|
|
|
|
|
renderBlur(pendingAsset->texture, *pendingBlurredFB);
|
2024-12-18 16:28:05 +01:00
|
|
|
|
2025-06-22 09:24:39 +02:00
|
|
|
const auto& TEX = blurredFB->isAllocated() ? blurredFB->m_cTex : asset->texture;
|
|
|
|
|
const auto TEXBOX = getScaledBoxForTexture(TEX, viewport);
|
|
|
|
|
|
|
|
|
|
if (data.opacity < 1.0 && scAsset)
|
|
|
|
|
g_pRenderer->renderTextureMix(TEXBOX, scAsset->texture, TEX, 1.0, data.opacity, 0);
|
|
|
|
|
else if (crossFadeProgress->isBeingAnimated()) {
|
|
|
|
|
const auto& PENDINGTEX = pendingBlurredFB->isAllocated() ? pendingBlurredFB->m_cTex : pendingAsset->texture;
|
|
|
|
|
g_pRenderer->renderTextureMix(TEXBOX, TEX, PENDINGTEX, 1.0, crossFadeProgress->value(), 0);
|
|
|
|
|
} else
|
|
|
|
|
g_pRenderer->renderTexture(TEXBOX, TEX, 1, 0, HYPRUTILS_TRANSFORM_FLIPPED_180);
|
|
|
|
|
|
|
|
|
|
return crossFadeProgress->isBeingAnimated() || data.opacity < 1.0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CBackground::plantReloadTimer() {
|
|
|
|
|
|
|
|
|
|
if (reloadTime == 0)
|
|
|
|
|
reloadTimer = g_pHyprlock->addTimer(std::chrono::hours(1), [REF = m_self](auto, auto) { onReloadTimer(REF); }, nullptr, true);
|
|
|
|
|
else if (reloadTime > 0)
|
|
|
|
|
reloadTimer = g_pHyprlock->addTimer(std::chrono::seconds(reloadTime), [REF = m_self](auto, auto) { onReloadTimer(REF); }, nullptr, true);
|
2024-12-18 16:28:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CBackground::onReloadTimerUpdate() {
|
|
|
|
|
const std::string OLDPATH = path;
|
|
|
|
|
|
|
|
|
|
// Path parsing and early returns
|
|
|
|
|
|
|
|
|
|
if (!reloadCommand.empty()) {
|
2025-05-05 17:11:24 +02:00
|
|
|
path = spawnSync(reloadCommand);
|
2024-12-18 16:28:05 +01:00
|
|
|
|
|
|
|
|
if (path.ends_with('\n'))
|
|
|
|
|
path.pop_back();
|
|
|
|
|
|
|
|
|
|
if (path.starts_with("file://"))
|
|
|
|
|
path = path.substr(7);
|
|
|
|
|
|
|
|
|
|
if (path.empty())
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
2024-12-26 15:43:11 +00:00
|
|
|
const auto MTIME = std::filesystem::last_write_time(absolutePath(path, ""));
|
2024-12-18 16:28:05 +01:00
|
|
|
if (OLDPATH == path && MTIME == modificationTime)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
modificationTime = MTIME;
|
|
|
|
|
} catch (std::exception& e) {
|
|
|
|
|
path = OLDPATH;
|
|
|
|
|
Debug::log(ERR, "{}", e.what());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!pendingResourceID.empty())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Issue the next request
|
|
|
|
|
|
|
|
|
|
request.id = std::string{"background:"} + path + ",time:" + std::to_string((uint64_t)modificationTime.time_since_epoch().count());
|
|
|
|
|
pendingResourceID = request.id;
|
|
|
|
|
request.asset = path;
|
|
|
|
|
request.type = CAsyncResourceGatherer::eTargetType::TARGET_IMAGE;
|
|
|
|
|
|
2025-03-05 08:35:43 +01:00
|
|
|
request.callback = [REF = m_self]() { onAssetCallback(REF); };
|
2024-12-18 16:28:05 +01:00
|
|
|
|
|
|
|
|
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-22 09:24:39 +02:00
|
|
|
void CBackground::startCrossFade() {
|
2024-12-18 16:28:05 +01:00
|
|
|
auto newAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(pendingResourceID);
|
|
|
|
|
if (newAsset) {
|
|
|
|
|
if (newAsset->texture.m_iType == TEXTURE_INVALID) {
|
|
|
|
|
g_pRenderer->asyncResourceGatherer->unloadAsset(newAsset);
|
|
|
|
|
Debug::log(ERR, "New asset had an invalid texture!");
|
2025-06-22 09:24:39 +02:00
|
|
|
pendingResourceID = "";
|
2024-12-18 16:28:05 +01:00
|
|
|
} else if (resourceID != pendingResourceID) {
|
|
|
|
|
pendingAsset = newAsset;
|
2025-06-22 09:24:39 +02:00
|
|
|
crossFadeProgress->setValueAndWarp(0);
|
|
|
|
|
*crossFadeProgress = 1.0;
|
|
|
|
|
|
|
|
|
|
crossFadeProgress->setCallbackOnEnd(
|
|
|
|
|
[REF = m_self](auto) {
|
|
|
|
|
if (const auto PSELF = REF.lock()) {
|
|
|
|
|
PSELF->asset = PSELF->pendingAsset;
|
|
|
|
|
PSELF->pendingAsset = nullptr;
|
|
|
|
|
g_pRenderer->asyncResourceGatherer->unloadAsset(PSELF->pendingAsset);
|
|
|
|
|
PSELF->resourceID = PSELF->pendingResourceID;
|
|
|
|
|
PSELF->pendingResourceID = "";
|
|
|
|
|
|
|
|
|
|
PSELF->blurredFB->destroyBuffer();
|
|
|
|
|
PSELF->blurredFB = std::move(PSELF->pendingBlurredFB);
|
2024-12-18 16:28:05 +01:00
|
|
|
}
|
2025-06-22 09:24:39 +02:00
|
|
|
},
|
|
|
|
|
true);
|
|
|
|
|
|
|
|
|
|
g_pHyprlock->renderOutput(outputPort);
|
2024-12-18 16:28:05 +01:00
|
|
|
}
|
|
|
|
|
} else if (!pendingResourceID.empty()) {
|
|
|
|
|
Debug::log(WARN, "Asset {} not available after the asyncResourceGatherer's callback!", pendingResourceID);
|
2025-03-05 08:35:43 +01:00
|
|
|
g_pHyprlock->addTimer(std::chrono::milliseconds(100), [REF = m_self](auto, auto) { onAssetCallback(REF); }, nullptr);
|
2024-12-18 16:28:05 +01:00
|
|
|
}
|
2025-02-06 16:36:08 +05:00
|
|
|
}
|