From e8b7c580afdb4d3954a255dbe23a0b12b2634fd6 Mon Sep 17 00:00:00 2001 From: Robin Carlier <57142648+robin-carlier@users.noreply.github.com> Date: Mon, 16 Dec 2024 10:10:47 +0100 Subject: [PATCH 1/6] widgets: Reload background and fade --- src/config/ConfigManager.cpp | 6 + src/renderer/Renderer.cpp | 69 +++++++- src/renderer/Renderer.hpp | 2 + src/renderer/Shader.hpp | 4 +- src/renderer/Shaders.hpp | 45 +++++- src/renderer/widgets/Background.cpp | 240 ++++++++++++++++++++++++++-- src/renderer/widgets/Background.hpp | 41 ++++- 7 files changed, 383 insertions(+), 24 deletions(-) diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 2ce8a102..5f5f555f 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -189,6 +189,9 @@ void CConfigManager::init() { m_config.addSpecialConfigValue("background", "vibrancy", Hyprlang::FLOAT{0.1686}); m_config.addSpecialConfigValue("background", "vibrancy_darkness", Hyprlang::FLOAT{0.05}); m_config.addSpecialConfigValue("background", "zindex", Hyprlang::INT{-1}); + m_config.addSpecialConfigValue("background", "reload_time", Hyprlang::INT{-1}); + m_config.addSpecialConfigValue("background", "reload_cmd", Hyprlang::STRING{""}); + m_config.addSpecialConfigValue("background", "crossfade_time", Hyprlang::FLOAT{-1.0}); m_config.addSpecialCategory("shape", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true}); m_config.addSpecialConfigValue("shape", "monitor", Hyprlang::STRING{""}); @@ -316,6 +319,9 @@ std::vector CConfigManager::getWidgetConfigs() { {"brightness", m_config.getSpecialConfigValue("background", "brightness", k.c_str())}, {"vibrancy_darkness", m_config.getSpecialConfigValue("background", "vibrancy_darkness", k.c_str())}, {"zindex", m_config.getSpecialConfigValue("background", "zindex", k.c_str())}, + {"reload_time", m_config.getSpecialConfigValue("background", "reload_time", k.c_str())}, + {"reload_cmd", m_config.getSpecialConfigValue("background", "reload_cmd", k.c_str())}, + {"crossfade_time", m_config.getSpecialConfigValue("background", "crossfade_time", k.c_str())}, } }); // clang-format on diff --git a/src/renderer/Renderer.cpp b/src/renderer/Renderer.cpp index cbbfdb8c..b637fae1 100644 --- a/src/renderer/Renderer.cpp +++ b/src/renderer/Renderer.cpp @@ -106,6 +106,27 @@ CRenderer::CRenderer() { texShader.tint = glGetUniformLocation(prog, "tint"); texShader.useAlphaMatte = glGetUniformLocation(prog, "useAlphaMatte"); + prog = createProgram(TEXVERTSRC, TEXMIXFRAGSRCRGBA); + texMixShader.program = prog; + texMixShader.proj = glGetUniformLocation(prog, "proj"); + texMixShader.tex = glGetUniformLocation(prog, "tex1"); + texMixShader.tex2 = glGetUniformLocation(prog, "tex2"); + texMixShader.alphaMatte = glGetUniformLocation(prog, "texMatte"); + texMixShader.alpha = glGetUniformLocation(prog, "alpha"); + texMixShader.mixFactor = glGetUniformLocation(prog, "mixFactor"); + texMixShader.texAttrib = glGetAttribLocation(prog, "texcoord"); + texMixShader.matteTexAttrib = glGetAttribLocation(prog, "texcoordMatte"); + texMixShader.posAttrib = glGetAttribLocation(prog, "pos"); + texMixShader.discardOpaque = glGetUniformLocation(prog, "discardOpaque"); + texMixShader.discardAlpha = glGetUniformLocation(prog, "discardAlpha"); + texMixShader.discardAlphaValue = glGetUniformLocation(prog, "discardAlphaValue"); + texMixShader.topLeft = glGetUniformLocation(prog, "topLeft"); + texMixShader.fullSize = glGetUniformLocation(prog, "fullSize"); + texMixShader.radius = glGetUniformLocation(prog, "radius"); + texMixShader.applyTint = glGetUniformLocation(prog, "applyTint"); + texMixShader.tint = glGetUniformLocation(prog, "tint"); + texMixShader.useAlphaMatte = glGetUniformLocation(prog, "useAlphaMatte"); + prog = createProgram(TEXVERTSRC, FRAGBLUR1); blurShader1.program = prog; blurShader1.tex = glGetUniformLocation(prog, "tex"); @@ -347,6 +368,52 @@ void CRenderer::renderTexture(const CBox& box, const CTexture& tex, float a, int glBindTexture(tex.m_iTarget, 0); } +void CRenderer::renderTextureMix(const CBox& box, const CTexture& tex, const CTexture& tex2, float a, float mixFactor, int rounding, std::optional tr) { + const auto ROUNDEDBOX = box.copy().round(); + Mat3x3 matrix = projMatrix.projectBox(ROUNDEDBOX, tr.value_or(HYPRUTILS_TRANSFORM_FLIPPED_180), box.rot); + Mat3x3 glMatrix = projection.copy().multiply(matrix); + + CShader* shader = &texMixShader; + + glActiveTexture(GL_TEXTURE0); + glBindTexture(tex.m_iTarget, tex.m_iTexID); + + glActiveTexture(GL_TEXTURE1); + glBindTexture(tex2.m_iTarget, tex2.m_iTexID); + + glUseProgram(shader->program); + + glUniformMatrix3fv(shader->proj, 1, GL_TRUE, glMatrix.getMatrix().data()); + glUniform1i(shader->tex, 0); + glUniform1i(shader->tex2, 1); + glUniform1f(shader->alpha, a); + glUniform1f(shader->mixFactor, mixFactor); + const auto TOPLEFT = Vector2D(ROUNDEDBOX.x, ROUNDEDBOX.y); + const auto FULLSIZE = Vector2D(ROUNDEDBOX.width, ROUNDEDBOX.height); + + // Rounded corners + glUniform2f(shader->topLeft, TOPLEFT.x, TOPLEFT.y); + glUniform2f(shader->fullSize, FULLSIZE.x, FULLSIZE.y); + glUniform1f(shader->radius, rounding); + + glUniform1i(shader->discardOpaque, 0); + glUniform1i(shader->discardAlpha, 0); + glUniform1i(shader->applyTint, 0); + + glVertexAttribPointer(shader->posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); + glVertexAttribPointer(shader->texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); + + glEnableVertexAttribArray(shader->posAttrib); + glEnableVertexAttribArray(shader->texAttrib); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glDisableVertexAttribArray(shader->posAttrib); + glDisableVertexAttribArray(shader->texAttrib); + + glBindTexture(tex.m_iTarget, 0); +} + std::vector>* CRenderer::getOrCreateWidgetsFor(const CSessionLockSurface* surf) { if (!widgets.contains(surf)) { @@ -570,4 +637,4 @@ void CRenderer::popFb() { void CRenderer::removeWidgetsFor(const CSessionLockSurface* surf) { widgets.erase(surf); -} \ No newline at end of file +} diff --git a/src/renderer/Renderer.hpp b/src/renderer/Renderer.hpp index 55501d7a..edc88157 100644 --- a/src/renderer/Renderer.hpp +++ b/src/renderer/Renderer.hpp @@ -33,6 +33,7 @@ class CRenderer { void renderRect(const CBox& box, const CColor& col, int rounding = 0); void renderBorder(const CBox& box, const CGradientValueData& gradient, int thickness, int rounding = 0, float alpha = 1.0); void renderTexture(const CBox& box, const CTexture& tex, float a = 1.0, int rounding = 0, std::optional tr = {}); + void renderTextureMix(const CBox& box, const CTexture& tex, const CTexture& tex2, float a = 1.0, float mixFactor = 0.0, int rounding = 0, std::optional tr = {}); void blurFB(const CFramebuffer& outfb, SBlurParams params); std::unique_ptr asyncResourceGatherer; @@ -50,6 +51,7 @@ class CRenderer { CShader rectShader; CShader texShader; + CShader texMixShader; CShader blurShader1; CShader blurShader2; CShader blurPrepareShader; diff --git a/src/renderer/Shader.hpp b/src/renderer/Shader.hpp index bb1d5e83..32171168 100644 --- a/src/renderer/Shader.hpp +++ b/src/renderer/Shader.hpp @@ -13,7 +13,9 @@ class CShader { GLint color = -1; GLint alphaMatte = -1; GLint tex = -1; + GLint tex2 = -1; GLint alpha = -1; + GLfloat mixFactor = -1; GLint posAttrib = -1; GLint texAttrib = -1; GLint matteTexAttrib = -1; @@ -70,4 +72,4 @@ class CShader { private: std::unordered_map m_muUniforms; -}; \ No newline at end of file +}; diff --git a/src/renderer/Shaders.hpp b/src/renderer/Shaders.hpp index 9a2f417c..b96bc1d7 100644 --- a/src/renderer/Shaders.hpp +++ b/src/renderer/Shaders.hpp @@ -129,6 +129,49 @@ void main() { gl_FragColor = pixColor * alpha; })#"; +inline const std::string TEXMIXFRAGSRCRGBA = R"#( +precision highp float; +varying vec2 v_texcoord; // is in 0-1 +uniform sampler2D tex1; +uniform sampler2D tex2; +uniform float mixFactor; +uniform float alpha; + +uniform vec2 topLeft; +uniform vec2 fullSize; +uniform float radius; + +uniform int discardOpaque; +uniform int discardAlpha; +uniform float discardAlphaValue; + +uniform int applyTint; +uniform vec3 tint; + +void main() { + + vec4 pixColor = mix(texture2D(tex1, v_texcoord), texture2D(tex2, v_texcoord), smoothstep(0.0, 1.0, mixFactor)); + + if (discardOpaque == 1 && pixColor[3] * alpha == 1.0) + discard; + + if (discardAlpha == 1 && pixColor[3] <= discardAlphaValue) + discard; + + if (applyTint == 1) { + pixColor[0] = pixColor[0] * tint[0]; + pixColor[1] = pixColor[1] * tint[1]; + pixColor[2] = pixColor[2] * tint[2]; + } + + if (radius > 0.0) { + )#" + + ROUNDED_SHADER_FUNC("pixColor") + R"#( + } + + gl_FragColor = pixColor * alpha; +})#"; + inline const std::string FRAGBLUR1 = R"#( #version 100 precision highp float; @@ -490,4 +533,4 @@ void main() { gl_FragColor = pixColor; } -)#"; \ No newline at end of file +)#"; diff --git a/src/renderer/widgets/Background.cpp b/src/renderer/widgets/Background.cpp index ac7ea0b0..0420398f 100644 --- a/src/renderer/widgets/Background.cpp +++ b/src/renderer/widgets/Background.cpp @@ -1,18 +1,58 @@ #include "Background.hpp" #include "../Renderer.hpp" +#include "../../core/hyprlock.hpp" +#include "src/helpers/Log.hpp" +#include #include +#include +#include +#include + +CBackground::~CBackground() { + + if (bgTimer) { + bgTimer->cancel(); + bgTimer.reset(); + } + + if (fade) { + if (fade->crossFadeTimer) { + fade->crossFadeTimer->cancel(); + fade->crossFadeTimer.reset(); + } + fade.reset(); + } +} CBackground::CBackground(const Vector2D& viewport_, COutput* output_, const std::string& resourceID_, const std::unordered_map& props, bool ss) : viewport(viewport_), resourceID(resourceID_), output(output_), isScreenshot(ss) { - color = std::any_cast(props.at("color")); - blurPasses = std::any_cast(props.at("blur_passes")); - blurSize = std::any_cast(props.at("blur_size")); - vibrancy = std::any_cast(props.at("vibrancy")); - vibrancy_darkness = std::any_cast(props.at("vibrancy_darkness")); - noise = std::any_cast(props.at("noise")); - brightness = std::any_cast(props.at("brightness")); - contrast = std::any_cast(props.at("contrast")); + try { + color = std::any_cast(props.at("color")); + blurPasses = std::any_cast(props.at("blur_passes")); + blurSize = std::any_cast(props.at("blur_size")); + vibrancy = std::any_cast(props.at("vibrancy")); + vibrancy_darkness = std::any_cast(props.at("vibrancy_darkness")); + noise = std::any_cast(props.at("noise")); + brightness = std::any_cast(props.at("brightness")); + contrast = std::any_cast(props.at("contrast")); + path = std::any_cast(props.at("path")); + reloadCommand = std::any_cast(props.at("reload_cmd")); + reloadTime = std::any_cast(props.at("reload_time")); + crossfade_time = std::any_cast(props.at("crossfade_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()); // + } + + try { + modificationTime = std::filesystem::last_write_time(path); + } catch (std::exception& e) { Debug::log(ERR, "{}", e.what()); } + + if (!isScreenshot) + plantTimer(); // No reloads for screenshots. } void CBackground::renderRect(CColor color) { @@ -20,6 +60,28 @@ void CBackground::renderRect(CColor color) { g_pRenderer->renderRect(monbox, color, 0); } + +static void onTimer(std::shared_ptr self, void* data) { + const auto PBG = (CBackground*)data; + + PBG->onTimerUpdate(); + PBG->plantTimer(); +} + +static void onFadeTimer(std::shared_ptr self, void* data) { + const auto PBG = (CBackground*)data; + PBG->onFadeTimerUpdate(); +} + +static void onAssetCallback(void* data) { + const auto PBG = (CBackground*)data; + PBG->startFadeOrUpdateRender(); +} + +static void onAssetCallbackTimer(std::shared_ptr self, void* data) { + onAssetCallback(data); +} + bool CBackground::draw(const SRenderData& data) { if (resourceID.empty()) { @@ -45,10 +107,14 @@ bool CBackground::draw(const SRenderData& data) { return true; } - if ((blurPasses > 0 || isScreenshot) && !blurredFB.isAllocated()) { + + if (fade || ((blurPasses > 0 || isScreenshot) && (!blurredFB.isAllocated() || firstRender))) { + + if (firstRender) + firstRender = false; + // make it brah Vector2D size = asset->texture.m_vSize; - if (output->transform % 2 == 1 && isScreenshot) { size.x = asset->texture.m_vSize.y; size.y = asset->texture.m_vSize.x; @@ -67,13 +133,23 @@ bool CBackground::draw(const SRenderData& data) { else texbox.x = -(texbox.w - viewport.x) / 2.f; texbox.round(); - blurredFB.alloc(viewport.x, viewport.y); // TODO 10 bit + + if (!blurredFB.isAllocated()) + blurredFB.alloc(viewport.x, viewport.y); // TODO 10 bit + blurredFB.bind(); - g_pRenderer->renderTexture(texbox, asset->texture, 1.0, 0, - isScreenshot ? - wlTransformToHyprutils(invertTransform(output->transform)) : - HYPRUTILS_TRANSFORM_NORMAL); // this could be omitted but whatever it's only once and makes code cleaner plus less blurring on large texs + if (fade) { + g_pRenderer->renderTextureMix(texbox, asset->texture, pendingAsset->texture, 1.0, + std::chrono::duration_cast(std::chrono::system_clock::now() - fade->start).count()/(1000*crossfade_time), + 0, HYPRUTILS_TRANSFORM_NORMAL); + } else { + g_pRenderer->renderTexture(texbox, asset->texture, 1.0, 0, + isScreenshot ? + wlTransformToHyprutils(invertTransform(output->transform)) : + HYPRUTILS_TRANSFORM_NORMAL); // this could be omitted but whatever it's only once and makes code cleaner plus less blurring on large texs + } + if (blurPasses > 0) g_pRenderer->blurFB(blurredFB, CRenderer::SBlurParams{blurSize, blurPasses, noise, contrast, brightness, vibrancy, vibrancy_darkness}); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); @@ -97,5 +173,135 @@ bool CBackground::draw(const SRenderData& data) { texbox.round(); g_pRenderer->renderTexture(texbox, *tex, data.opacity, 0, HYPRUTILS_TRANSFORM_FLIPPED_180); - return data.opacity < 1.0; -} \ No newline at end of file + if (fade) + return true; // actively redraw during fading + else + return data.opacity < 1.0; + +} + +void CBackground::plantTimer() { + + RASSERT(!(reloadTime >= 0 && isScreenshot), "Reloadable background can't be used with screenshot" ) + + if (reloadTime == 0) { + bgTimer = g_pHyprlock->addTimer(std::chrono::hours(1), onTimer, this, true); + } else if (reloadTime > 0) + bgTimer = g_pHyprlock->addTimer(std::chrono::seconds(reloadTime), onTimer, this, false); +}; + +void CBackground::onFadeTimerUpdate() { + + // Animation done: Unload previous asset, deinitialize the fade and pass the asset + + if (fade) { + fade->crossFadeTimer.reset(); + fade.reset(nullptr); + }; + + if (!(blurPasses > 0 || isScreenshot)) { + blurredFB.release(); + } + + asset = pendingAsset; + resourceID = pendingResourceID; + pendingResourceID = ""; + pendingAsset = nullptr; + firstRender = true; + + g_pHyprlock->renderOutput(output->stringPort); + +}; + +void CBackground::onTimerUpdate() { + const std::string OLDPATH = path; + + // Path parsing and early returns + + if (!reloadCommand.empty()) { + path = g_pHyprlock->spawnSync(reloadCommand); + + if (path.ends_with('\n')) + path.pop_back(); + + if (path.starts_with("file://")) + path = path.substr(7); + + if (path.empty()) { + return; + } + } + + try { + const auto MTIME = std::filesystem::last_write_time(path); + 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; + + request.callback = onAssetCallback; + request.callbackData = this; + + g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request); + +}; + +void CBackground::startFadeOrUpdateRender() { + 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!"); + } else if (resourceID != pendingResourceID) { + + pendingAsset = newAsset; + + if (crossfade_time > 0) { + // Start a fade + if (!fade) { + + fade = std::make_unique(std::chrono::system_clock::now(), 0, nullptr); + + } else { + + // Maybe we where already fading so reset it just in case, but should'nt be happening. + + if (fade->crossFadeTimer) { + fade->crossFadeTimer->cancel(); + fade->crossFadeTimer.reset(); + }; + + }; + + fade->start = std::chrono::system_clock::now(); + fade->a = 0; + fade->crossFadeTimer = g_pHyprlock->addTimer(std::chrono::milliseconds((int)(1000.0*crossfade_time)), onFadeTimer, this); + } else { + onFadeTimerUpdate(); + } + } + } else if (!pendingResourceID.empty()) { + Debug::log(WARN, "Asset {} not available after the asyncResourceGatherer's callback!", pendingResourceID); + + g_pHyprlock->addTimer(std::chrono::milliseconds(100), onAssetCallbackTimer, this); + } + + g_pHyprlock->renderOutput(output->stringPort); +} diff --git a/src/renderer/widgets/Background.hpp b/src/renderer/widgets/Background.hpp index bc3d9810..3f270137 100644 --- a/src/renderer/widgets/Background.hpp +++ b/src/renderer/widgets/Background.hpp @@ -3,21 +3,37 @@ #include "IWidget.hpp" #include "../../helpers/Color.hpp" #include "../../helpers/Math.hpp" +#include "../../core/Timer.hpp" #include "../Framebuffer.hpp" +#include "../AsyncResourceGatherer.hpp" #include #include #include +#include +#include struct SPreloadedAsset; class COutput; +struct SFade { + std::chrono::system_clock::time_point start; + float a = 0; + std::shared_ptr crossFadeTimer = nullptr; +}; + class CBackground : public IWidget { public: CBackground(const Vector2D& viewport, COutput* output_, const std::string& resourceID, const std::unordered_map& props, bool ss_); + ~CBackground(); virtual bool draw(const SRenderData& data); void renderRect(CColor color); + void onTimerUpdate(); + void onFadeTimerUpdate(); + void plantTimer(); + void startFadeOrUpdateRender(); + private: // if needed CFramebuffer blurredFB; @@ -30,9 +46,26 @@ class CBackground : public IWidget { float vibrancy = 0.1696; float vibrancy_darkness = 0.0; Vector2D viewport; + std::string path = ""; + + std::string resourceID; + std::string pendingResourceID; + + float crossfade_time = -1.0; + CColor color; - SPreloadedAsset* asset = nullptr; - COutput* output = nullptr; - bool isScreenshot = false; -}; \ No newline at end of file + SPreloadedAsset* asset = nullptr; + COutput* output = nullptr; + bool isScreenshot = false; + SPreloadedAsset* pendingAsset = nullptr; + bool firstRender = true; + + std::unique_ptr fade; + + int reloadTime; + std::string reloadCommand; + CAsyncResourceGatherer::SPreloadRequest request; + std::shared_ptr bgTimer; + std::filesystem::file_time_type modificationTime; +}; From b06a48d325574a59f24f1ce600d64237ccfbb77e Mon Sep 17 00:00:00 2001 From: Robin Carlier <57142648+robin-carlier@users.noreply.github.com> Date: Mon, 16 Dec 2024 11:37:08 +0100 Subject: [PATCH 2/6] clang-format also clang-format --- src/renderer/Renderer.hpp | 12 +++---- src/renderer/widgets/Background.cpp | 56 +++++++++++++---------------- src/renderer/widgets/Background.hpp | 45 ++++++++++++----------- 3 files changed, 53 insertions(+), 60 deletions(-) diff --git a/src/renderer/Renderer.hpp b/src/renderer/Renderer.hpp index edc88157..66bb844d 100644 --- a/src/renderer/Renderer.hpp +++ b/src/renderer/Renderer.hpp @@ -28,13 +28,13 @@ class CRenderer { float boostA = 1.0; }; - SRenderFeedback renderLock(const CSessionLockSurface& surface); + SRenderFeedback renderLock(const CSessionLockSurface& surface); - void renderRect(const CBox& box, const CColor& col, int rounding = 0); - void renderBorder(const CBox& box, const CGradientValueData& gradient, int thickness, int rounding = 0, float alpha = 1.0); - void renderTexture(const CBox& box, const CTexture& tex, float a = 1.0, int rounding = 0, std::optional tr = {}); - void renderTextureMix(const CBox& box, const CTexture& tex, const CTexture& tex2, float a = 1.0, float mixFactor = 0.0, int rounding = 0, std::optional tr = {}); - void blurFB(const CFramebuffer& outfb, SBlurParams params); + void renderRect(const CBox& box, const CColor& col, int rounding = 0); + void renderBorder(const CBox& box, const CGradientValueData& gradient, int thickness, int rounding = 0, float alpha = 1.0); + void renderTexture(const CBox& box, const CTexture& tex, float a = 1.0, int rounding = 0, std::optional tr = {}); + void renderTextureMix(const CBox& box, const CTexture& tex, const CTexture& tex2, float a = 1.0, float mixFactor = 0.0, int rounding = 0, std::optional tr = {}); + void blurFB(const CFramebuffer& outfb, SBlurParams params); std::unique_ptr asyncResourceGatherer; std::chrono::system_clock::time_point firstFullFrameTime; diff --git a/src/renderer/widgets/Background.cpp b/src/renderer/widgets/Background.cpp index 0420398f..98abfe44 100644 --- a/src/renderer/widgets/Background.cpp +++ b/src/renderer/widgets/Background.cpp @@ -16,11 +16,11 @@ CBackground::~CBackground() { } if (fade) { - if (fade->crossFadeTimer) { - fade->crossFadeTimer->cancel(); - fade->crossFadeTimer.reset(); - } - fade.reset(); + if (fade->crossFadeTimer) { + fade->crossFadeTimer->cancel(); + fade->crossFadeTimer.reset(); + } + fade.reset(); } } @@ -60,7 +60,6 @@ void CBackground::renderRect(CColor color) { g_pRenderer->renderRect(monbox, color, 0); } - static void onTimer(std::shared_ptr self, void* data) { const auto PBG = (CBackground*)data; @@ -107,7 +106,6 @@ bool CBackground::draw(const SRenderData& data) { return true; } - if (fade || ((blurPasses > 0 || isScreenshot) && (!blurredFB.isAllocated() || firstRender))) { if (firstRender) @@ -141,13 +139,13 @@ bool CBackground::draw(const SRenderData& data) { if (fade) { g_pRenderer->renderTextureMix(texbox, asset->texture, pendingAsset->texture, 1.0, - std::chrono::duration_cast(std::chrono::system_clock::now() - fade->start).count()/(1000*crossfade_time), - 0, HYPRUTILS_TRANSFORM_NORMAL); + std::chrono::duration_cast(std::chrono::system_clock::now() - fade->start).count() / (1000 * crossfade_time), + 0, HYPRUTILS_TRANSFORM_NORMAL); } else { g_pRenderer->renderTexture(texbox, asset->texture, 1.0, 0, - isScreenshot ? - wlTransformToHyprutils(invertTransform(output->transform)) : - HYPRUTILS_TRANSFORM_NORMAL); // this could be omitted but whatever it's only once and makes code cleaner plus less blurring on large texs + isScreenshot ? + wlTransformToHyprutils(invertTransform(output->transform)) : + HYPRUTILS_TRANSFORM_NORMAL); // this could be omitted but whatever it's only once and makes code cleaner plus less blurring on large texs } if (blurPasses > 0) @@ -177,12 +175,11 @@ bool CBackground::draw(const SRenderData& data) { return true; // actively redraw during fading else return data.opacity < 1.0; - } void CBackground::plantTimer() { - RASSERT(!(reloadTime >= 0 && isScreenshot), "Reloadable background can't be used with screenshot" ) + RASSERT(!(reloadTime >= 0 && isScreenshot), "Reloadable background can't be used with screenshot") if (reloadTime == 0) { bgTimer = g_pHyprlock->addTimer(std::chrono::hours(1), onTimer, this, true); @@ -203,14 +200,13 @@ void CBackground::onFadeTimerUpdate() { blurredFB.release(); } - asset = pendingAsset; - resourceID = pendingResourceID; + asset = pendingAsset; + resourceID = pendingResourceID; pendingResourceID = ""; - pendingAsset = nullptr; - firstRender = true; + pendingAsset = nullptr; + firstRender = true; g_pHyprlock->renderOutput(output->stringPort); - }; void CBackground::onTimerUpdate() { @@ -260,7 +256,6 @@ void CBackground::onTimerUpdate() { request.callbackData = this; g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request); - }; void CBackground::startFadeOrUpdateRender() { @@ -268,7 +263,7 @@ void CBackground::startFadeOrUpdateRender() { if (newAsset) { if (newAsset->texture.m_iType == TEXTURE_INVALID) { g_pRenderer->asyncResourceGatherer->unloadAsset(newAsset); - Debug::log( ERR, "New asset had an invalid texture!"); + Debug::log(ERR, "New asset had an invalid texture!"); } else if (resourceID != pendingResourceID) { pendingAsset = newAsset; @@ -277,22 +272,21 @@ void CBackground::startFadeOrUpdateRender() { // Start a fade if (!fade) { - fade = std::make_unique(std::chrono::system_clock::now(), 0, nullptr); + fade = std::make_unique(std::chrono::system_clock::now(), 0, nullptr); } else { - // Maybe we where already fading so reset it just in case, but should'nt be happening. - - if (fade->crossFadeTimer) { - fade->crossFadeTimer->cancel(); - fade->crossFadeTimer.reset(); - }; + // Maybe we where already fading so reset it just in case, but should'nt be happening. + if (fade->crossFadeTimer) { + fade->crossFadeTimer->cancel(); + fade->crossFadeTimer.reset(); + }; }; - fade->start = std::chrono::system_clock::now(); - fade->a = 0; - fade->crossFadeTimer = g_pHyprlock->addTimer(std::chrono::milliseconds((int)(1000.0*crossfade_time)), onFadeTimer, this); + fade->start = std::chrono::system_clock::now(); + fade->a = 0; + fade->crossFadeTimer = g_pHyprlock->addTimer(std::chrono::milliseconds((int)(1000.0 * crossfade_time)), onFadeTimer, this); } else { onFadeTimerUpdate(); } diff --git a/src/renderer/widgets/Background.hpp b/src/renderer/widgets/Background.hpp index 3f270137..5685be59 100644 --- a/src/renderer/widgets/Background.hpp +++ b/src/renderer/widgets/Background.hpp @@ -16,9 +16,9 @@ struct SPreloadedAsset; class COutput; struct SFade { - std::chrono::system_clock::time_point start; - float a = 0; - std::shared_ptr crossFadeTimer = nullptr; + std::chrono::system_clock::time_point start; + float a = 0; + std::shared_ptr crossFadeTimer = nullptr; }; class CBackground : public IWidget { @@ -36,30 +36,29 @@ class CBackground : public IWidget { private: // if needed - CFramebuffer blurredFB; + CFramebuffer blurredFB; - int blurSize = 10; - int blurPasses = 3; - float noise = 0.0117; - float contrast = 0.8916; - float brightness = 0.8172; - float vibrancy = 0.1696; - float vibrancy_darkness = 0.0; - Vector2D viewport; - std::string path = ""; + int blurSize = 10; + int blurPasses = 3; + float noise = 0.0117; + float contrast = 0.8916; + float brightness = 0.8172; + float vibrancy = 0.1696; + float vibrancy_darkness = 0.0; + Vector2D viewport; + std::string path = ""; + std::string resourceID; + std::string pendingResourceID; - std::string resourceID; - std::string pendingResourceID; + float crossfade_time = -1.0; - float crossfade_time = -1.0; - - CColor color; - SPreloadedAsset* asset = nullptr; - COutput* output = nullptr; - bool isScreenshot = false; - SPreloadedAsset* pendingAsset = nullptr; - bool firstRender = true; + CColor color; + SPreloadedAsset* asset = nullptr; + COutput* output = nullptr; + bool isScreenshot = false; + SPreloadedAsset* pendingAsset = nullptr; + bool firstRender = true; std::unique_ptr fade; From 3e952574d9f3d56471b634dfe3c928af2419f068 Mon Sep 17 00:00:00 2001 From: Robin Carlier <57142648+robin-carlier@users.noreply.github.com> Date: Mon, 16 Dec 2024 11:44:00 +0100 Subject: [PATCH 3/6] undo possibly unwanted format on Renderer.hpp --- src/renderer/Renderer.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/renderer/Renderer.hpp b/src/renderer/Renderer.hpp index 66bb844d..edc88157 100644 --- a/src/renderer/Renderer.hpp +++ b/src/renderer/Renderer.hpp @@ -28,13 +28,13 @@ class CRenderer { float boostA = 1.0; }; - SRenderFeedback renderLock(const CSessionLockSurface& surface); + SRenderFeedback renderLock(const CSessionLockSurface& surface); - void renderRect(const CBox& box, const CColor& col, int rounding = 0); - void renderBorder(const CBox& box, const CGradientValueData& gradient, int thickness, int rounding = 0, float alpha = 1.0); - void renderTexture(const CBox& box, const CTexture& tex, float a = 1.0, int rounding = 0, std::optional tr = {}); - void renderTextureMix(const CBox& box, const CTexture& tex, const CTexture& tex2, float a = 1.0, float mixFactor = 0.0, int rounding = 0, std::optional tr = {}); - void blurFB(const CFramebuffer& outfb, SBlurParams params); + void renderRect(const CBox& box, const CColor& col, int rounding = 0); + void renderBorder(const CBox& box, const CGradientValueData& gradient, int thickness, int rounding = 0, float alpha = 1.0); + void renderTexture(const CBox& box, const CTexture& tex, float a = 1.0, int rounding = 0, std::optional tr = {}); + void renderTextureMix(const CBox& box, const CTexture& tex, const CTexture& tex2, float a = 1.0, float mixFactor = 0.0, int rounding = 0, std::optional tr = {}); + void blurFB(const CFramebuffer& outfb, SBlurParams params); std::unique_ptr asyncResourceGatherer; std::chrono::system_clock::time_point firstFullFrameTime; From 9644282038db1065b84b11d5019de1ec5f0edeae Mon Sep 17 00:00:00 2001 From: Robin Carlier <57142648+robin-carlier@users.noreply.github.com> Date: Mon, 16 Dec 2024 14:52:59 +0100 Subject: [PATCH 4/6] rename stuff + style --- src/renderer/widgets/Background.cpp | 61 +++++++++++++---------------- src/renderer/widgets/Background.hpp | 12 +++--- 2 files changed, 33 insertions(+), 40 deletions(-) diff --git a/src/renderer/widgets/Background.cpp b/src/renderer/widgets/Background.cpp index 98abfe44..24a1a30f 100644 --- a/src/renderer/widgets/Background.cpp +++ b/src/renderer/widgets/Background.cpp @@ -10,9 +10,9 @@ CBackground::~CBackground() { - if (bgTimer) { - bgTimer->cancel(); - bgTimer.reset(); + if (reloadTimer) { + reloadTimer->cancel(); + reloadTimer.reset(); } if (fade) { @@ -39,7 +39,7 @@ CBackground::CBackground(const Vector2D& viewport_, COutput* output_, const std: path = std::any_cast(props.at("path")); reloadCommand = std::any_cast(props.at("reload_cmd")); reloadTime = std::any_cast(props.at("reload_time")); - crossfade_time = std::any_cast(props.at("crossfade_time")); + crossFadeTime = std::any_cast(props.at("crossfade_time")); } catch (const std::bad_any_cast& e) { RASSERT(false, "Failed to construct CBackground: {}", e.what()); // @@ -52,7 +52,7 @@ CBackground::CBackground(const Vector2D& viewport_, COutput* output_, const std: } catch (std::exception& e) { Debug::log(ERR, "{}", e.what()); } if (!isScreenshot) - plantTimer(); // No reloads for screenshots. + plantReloadTimer(); // No reloads for screenshots. } void CBackground::renderRect(CColor color) { @@ -60,21 +60,21 @@ void CBackground::renderRect(CColor color) { g_pRenderer->renderRect(monbox, color, 0); } -static void onTimer(std::shared_ptr self, void* data) { +static void onReloadTimer(std::shared_ptr self, void* data) { const auto PBG = (CBackground*)data; - PBG->onTimerUpdate(); - PBG->plantTimer(); + PBG->onReloadTimerUpdate(); + PBG->plantReloadTimer(); } -static void onFadeTimer(std::shared_ptr self, void* data) { +static void onCrossFadeTimer(std::shared_ptr self, void* data) { const auto PBG = (CBackground*)data; - PBG->onFadeTimerUpdate(); + PBG->onCrossFadeTimerUpdate(); } static void onAssetCallback(void* data) { const auto PBG = (CBackground*)data; - PBG->startFadeOrUpdateRender(); + PBG->startCrossFadeOrUpdateRender(); } static void onAssetCallbackTimer(std::shared_ptr self, void* data) { @@ -137,16 +137,16 @@ bool CBackground::draw(const SRenderData& data) { blurredFB.bind(); - if (fade) { + if (fade) g_pRenderer->renderTextureMix(texbox, asset->texture, pendingAsset->texture, 1.0, - std::chrono::duration_cast(std::chrono::system_clock::now() - fade->start).count() / (1000 * crossfade_time), + std::chrono::duration_cast(std::chrono::system_clock::now() - fade->start).count() / (1000 * crossFadeTime), 0, HYPRUTILS_TRANSFORM_NORMAL); - } else { + else g_pRenderer->renderTexture(texbox, asset->texture, 1.0, 0, isScreenshot ? wlTransformToHyprutils(invertTransform(output->transform)) : HYPRUTILS_TRANSFORM_NORMAL); // this could be omitted but whatever it's only once and makes code cleaner plus less blurring on large texs - } + if (blurPasses > 0) g_pRenderer->blurFB(blurredFB, CRenderer::SBlurParams{blurSize, blurPasses, noise, contrast, brightness, vibrancy, vibrancy_darkness}); @@ -171,23 +171,18 @@ bool CBackground::draw(const SRenderData& data) { texbox.round(); g_pRenderer->renderTexture(texbox, *tex, data.opacity, 0, HYPRUTILS_TRANSFORM_FLIPPED_180); - if (fade) - return true; // actively redraw during fading - else - return data.opacity < 1.0; + return fade || data.opacity < 1.0; // actively render during fading } -void CBackground::plantTimer() { - - RASSERT(!(reloadTime >= 0 && isScreenshot), "Reloadable background can't be used with screenshot") +void CBackground::plantReloadTimer() { if (reloadTime == 0) { - bgTimer = g_pHyprlock->addTimer(std::chrono::hours(1), onTimer, this, true); + reloadTimer = g_pHyprlock->addTimer(std::chrono::hours(1), onReloadTimer, this, true); } else if (reloadTime > 0) - bgTimer = g_pHyprlock->addTimer(std::chrono::seconds(reloadTime), onTimer, this, false); + reloadTimer = g_pHyprlock->addTimer(std::chrono::seconds(reloadTime), onReloadTimer, this, false); }; -void CBackground::onFadeTimerUpdate() { +void CBackground::onCrossFadeTimerUpdate() { // Animation done: Unload previous asset, deinitialize the fade and pass the asset @@ -209,7 +204,7 @@ void CBackground::onFadeTimerUpdate() { g_pHyprlock->renderOutput(output->stringPort); }; -void CBackground::onTimerUpdate() { +void CBackground::onReloadTimerUpdate() { const std::string OLDPATH = path; // Path parsing and early returns @@ -223,9 +218,8 @@ void CBackground::onTimerUpdate() { if (path.starts_with("file://")) path = path.substr(7); - if (path.empty()) { + if (path.empty()) return; - } } try { @@ -241,9 +235,8 @@ void CBackground::onTimerUpdate() { return; } - if (!pendingResourceID.empty()) { + if (!pendingResourceID.empty()) return; - } // Issue the next request @@ -258,7 +251,7 @@ void CBackground::onTimerUpdate() { g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request); }; -void CBackground::startFadeOrUpdateRender() { +void CBackground::startCrossFadeOrUpdateRender() { auto newAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(pendingResourceID); if (newAsset) { if (newAsset->texture.m_iType == TEXTURE_INVALID) { @@ -268,7 +261,7 @@ void CBackground::startFadeOrUpdateRender() { pendingAsset = newAsset; - if (crossfade_time > 0) { + if (crossFadeTime > 0) { // Start a fade if (!fade) { @@ -286,9 +279,9 @@ void CBackground::startFadeOrUpdateRender() { fade->start = std::chrono::system_clock::now(); fade->a = 0; - fade->crossFadeTimer = g_pHyprlock->addTimer(std::chrono::milliseconds((int)(1000.0 * crossfade_time)), onFadeTimer, this); + fade->crossFadeTimer = g_pHyprlock->addTimer(std::chrono::milliseconds((int)(1000.0 * crossFadeTime)), onCrossFadeTimer, this); } else { - onFadeTimerUpdate(); + onCrossFadeTimerUpdate(); } } } else if (!pendingResourceID.empty()) { diff --git a/src/renderer/widgets/Background.hpp b/src/renderer/widgets/Background.hpp index 5685be59..46db699f 100644 --- a/src/renderer/widgets/Background.hpp +++ b/src/renderer/widgets/Background.hpp @@ -29,10 +29,10 @@ class CBackground : public IWidget { virtual bool draw(const SRenderData& data); void renderRect(CColor color); - void onTimerUpdate(); - void onFadeTimerUpdate(); - void plantTimer(); - void startFadeOrUpdateRender(); + void onReloadTimerUpdate(); + void onCrossFadeTimerUpdate(); + void plantReloadTimer(); + void startCrossFadeOrUpdateRender(); private: // if needed @@ -51,7 +51,7 @@ class CBackground : public IWidget { std::string resourceID; std::string pendingResourceID; - float crossfade_time = -1.0; + float crossFadeTime = -1.0; CColor color; SPreloadedAsset* asset = nullptr; @@ -65,6 +65,6 @@ class CBackground : public IWidget { int reloadTime; std::string reloadCommand; CAsyncResourceGatherer::SPreloadRequest request; - std::shared_ptr bgTimer; + std::shared_ptr reloadTimer; std::filesystem::file_time_type modificationTime; }; From 37fe1bfbe6075ffc89acd63e7d75c6c8e6db4e3c Mon Sep 17 00:00:00 2001 From: Robin Carlier <57142648+robin-carlier@users.noreply.github.com> Date: Mon, 16 Dec 2024 20:26:33 +0100 Subject: [PATCH 5/6] codestyle + initialize reloadTime --- src/renderer/widgets/Background.cpp | 38 +++++++++++------------------ src/renderer/widgets/Background.hpp | 2 +- 2 files changed, 15 insertions(+), 25 deletions(-) diff --git a/src/renderer/widgets/Background.cpp b/src/renderer/widgets/Background.cpp index 24a1a30f..ea7a3c67 100644 --- a/src/renderer/widgets/Background.cpp +++ b/src/renderer/widgets/Background.cpp @@ -146,7 +146,7 @@ bool CBackground::draw(const SRenderData& data) { isScreenshot ? wlTransformToHyprutils(invertTransform(output->transform)) : HYPRUTILS_TRANSFORM_NORMAL); // this could be omitted but whatever it's only once and makes code cleaner plus less blurring on large texs - + if (blurPasses > 0) g_pRenderer->blurFB(blurredFB, CRenderer::SBlurParams{blurSize, blurPasses, noise, contrast, brightness, vibrancy, vibrancy_darkness}); @@ -176,11 +176,11 @@ bool CBackground::draw(const SRenderData& data) { void CBackground::plantReloadTimer() { - if (reloadTime == 0) { + if (reloadTime == 0) reloadTimer = g_pHyprlock->addTimer(std::chrono::hours(1), onReloadTimer, this, true); - } else if (reloadTime > 0) + else if (reloadTime > 0) reloadTimer = g_pHyprlock->addTimer(std::chrono::seconds(reloadTime), onReloadTimer, this, false); -}; +} void CBackground::onCrossFadeTimerUpdate() { @@ -189,11 +189,10 @@ void CBackground::onCrossFadeTimerUpdate() { if (fade) { fade->crossFadeTimer.reset(); fade.reset(nullptr); - }; + } - if (!(blurPasses > 0 || isScreenshot)) { + if (!(blurPasses > 0 || isScreenshot)) blurredFB.release(); - } asset = pendingAsset; resourceID = pendingResourceID; @@ -202,7 +201,7 @@ void CBackground::onCrossFadeTimerUpdate() { firstRender = true; g_pHyprlock->renderOutput(output->stringPort); -}; +} void CBackground::onReloadTimerUpdate() { const std::string OLDPATH = path; @@ -218,15 +217,14 @@ void CBackground::onReloadTimerUpdate() { if (path.starts_with("file://")) path = path.substr(7); - if (path.empty()) + if (path.empty()) return; } try { const auto MTIME = std::filesystem::last_write_time(path); - if (OLDPATH == path && MTIME == modificationTime) { + if (OLDPATH == path && MTIME == modificationTime) return; - } modificationTime = MTIME; } catch (std::exception& e) { @@ -249,7 +247,7 @@ void CBackground::onReloadTimerUpdate() { request.callbackData = this; g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request); -}; +} void CBackground::startCrossFadeOrUpdateRender() { auto newAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(pendingResourceID); @@ -258,25 +256,18 @@ void CBackground::startCrossFadeOrUpdateRender() { g_pRenderer->asyncResourceGatherer->unloadAsset(newAsset); Debug::log(ERR, "New asset had an invalid texture!"); } else if (resourceID != pendingResourceID) { - pendingAsset = newAsset; - if (crossFadeTime > 0) { // Start a fade - if (!fade) { - + if (!fade) fade = std::make_unique(std::chrono::system_clock::now(), 0, nullptr); - - } else { - + else { // Maybe we where already fading so reset it just in case, but should'nt be happening. - if (fade->crossFadeTimer) { fade->crossFadeTimer->cancel(); fade->crossFadeTimer.reset(); - }; - }; - + } + } fade->start = std::chrono::system_clock::now(); fade->a = 0; fade->crossFadeTimer = g_pHyprlock->addTimer(std::chrono::milliseconds((int)(1000.0 * crossFadeTime)), onCrossFadeTimer, this); @@ -286,7 +277,6 @@ void CBackground::startCrossFadeOrUpdateRender() { } } else if (!pendingResourceID.empty()) { Debug::log(WARN, "Asset {} not available after the asyncResourceGatherer's callback!", pendingResourceID); - g_pHyprlock->addTimer(std::chrono::milliseconds(100), onAssetCallbackTimer, this); } diff --git a/src/renderer/widgets/Background.hpp b/src/renderer/widgets/Background.hpp index 46db699f..0f3d2334 100644 --- a/src/renderer/widgets/Background.hpp +++ b/src/renderer/widgets/Background.hpp @@ -62,7 +62,7 @@ class CBackground : public IWidget { std::unique_ptr fade; - int reloadTime; + int reloadTime = -1; std::string reloadCommand; CAsyncResourceGatherer::SPreloadRequest request; std::shared_ptr reloadTimer; From 1da84552a64f9f0212e42da8e4b53f58af03b2b0 Mon Sep 17 00:00:00 2001 From: Robin Carlier <57142648+robin-carlier@users.noreply.github.com> Date: Mon, 16 Dec 2024 20:38:09 +0100 Subject: [PATCH 6/6] remove trailing eols --- src/renderer/Renderer.cpp | 2 +- src/renderer/Shader.hpp | 2 +- src/renderer/Shaders.hpp | 2 +- src/renderer/widgets/Background.cpp | 2 +- src/renderer/widgets/Background.hpp | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/renderer/Renderer.cpp b/src/renderer/Renderer.cpp index b637fae1..035982a3 100644 --- a/src/renderer/Renderer.cpp +++ b/src/renderer/Renderer.cpp @@ -637,4 +637,4 @@ void CRenderer::popFb() { void CRenderer::removeWidgetsFor(const CSessionLockSurface* surf) { widgets.erase(surf); -} +} \ No newline at end of file diff --git a/src/renderer/Shader.hpp b/src/renderer/Shader.hpp index 32171168..84251675 100644 --- a/src/renderer/Shader.hpp +++ b/src/renderer/Shader.hpp @@ -72,4 +72,4 @@ class CShader { private: std::unordered_map m_muUniforms; -}; +}; \ No newline at end of file diff --git a/src/renderer/Shaders.hpp b/src/renderer/Shaders.hpp index b96bc1d7..15b6c23a 100644 --- a/src/renderer/Shaders.hpp +++ b/src/renderer/Shaders.hpp @@ -533,4 +533,4 @@ void main() { gl_FragColor = pixColor; } -)#"; +)#"; \ No newline at end of file diff --git a/src/renderer/widgets/Background.cpp b/src/renderer/widgets/Background.cpp index ea7a3c67..f342474e 100644 --- a/src/renderer/widgets/Background.cpp +++ b/src/renderer/widgets/Background.cpp @@ -281,4 +281,4 @@ void CBackground::startCrossFadeOrUpdateRender() { } g_pHyprlock->renderOutput(output->stringPort); -} +} \ No newline at end of file diff --git a/src/renderer/widgets/Background.hpp b/src/renderer/widgets/Background.hpp index 0f3d2334..9e11aff0 100644 --- a/src/renderer/widgets/Background.hpp +++ b/src/renderer/widgets/Background.hpp @@ -67,4 +67,4 @@ class CBackground : public IWidget { CAsyncResourceGatherer::SPreloadRequest request; std::shared_ptr reloadTimer; std::filesystem::file_time_type modificationTime; -}; +}; \ No newline at end of file