diff --git a/nix/hm-module.nix b/nix/hm-module.nix index 34877bb..4029df6 100644 --- a/nix/hm-module.nix +++ b/nix/hm-module.nix @@ -225,6 +225,18 @@ in { default = 0.0; }; + reload_time = mkOption { + description = "Interval in seconds between reloading the image"; + type = int; + default = -1; + }; + + reload_cmd = mkOption { + description = "Command to obtain new path"; + type = str; + default = ""; + }; + position = { x = mkOption { description = "X position of the image"; @@ -558,6 +570,8 @@ in { border_size = ${toString image.border_size} border_color = ${image.border_color} rotate = ${toString image.rotate} + reload_time = ${toString image.reload_time} + reload_cmd = ${image.reload_cmd} position = ${toString image.position.x}, ${toString image.position.y} halign = ${image.halign} diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index a074d7d..4c8d8a8 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -73,6 +73,8 @@ void CConfigManager::init() { m_config.addSpecialConfigValue("image", "halign", Hyprlang::STRING{"center"}); m_config.addSpecialConfigValue("image", "valign", Hyprlang::STRING{"center"}); m_config.addSpecialConfigValue("image", "rotate", Hyprlang::FLOAT{0}); + m_config.addSpecialConfigValue("image", "reload_time", Hyprlang::INT{-1}); + m_config.addSpecialConfigValue("image", "reload_cmd", Hyprlang::STRING{""}); SHADOWABLE("image"); m_config.addSpecialCategory("input-field", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true}); @@ -184,6 +186,8 @@ std::vector CConfigManager::getWidgetConfigs() { {"halign", m_config.getSpecialConfigValue("image", "halign", k.c_str())}, {"valign", m_config.getSpecialConfigValue("image", "valign", k.c_str())}, {"rotate", m_config.getSpecialConfigValue("image", "rotate", k.c_str())}, + {"reload_time", m_config.getSpecialConfigValue("image", "reload_time", k.c_str())}, + {"reload_cmd", m_config.getSpecialConfigValue("image", "reload_cmd", k.c_str())}, SHADOWABLE("image"), } }); diff --git a/src/renderer/AsyncResourceGatherer.cpp b/src/renderer/AsyncResourceGatherer.cpp index df0b2b3..1233879 100644 --- a/src/renderer/AsyncResourceGatherer.cpp +++ b/src/renderer/AsyncResourceGatherer.cpp @@ -203,6 +203,25 @@ void CAsyncResourceGatherer::apply() { applied = true; } +void CAsyncResourceGatherer::renderImage(const SPreloadRequest& rq) { + SPreloadTarget target; + target.type = TARGET_IMAGE; + target.id = rq.id; + + const auto ABSOLUTEPATH = absolutePath(rq.asset, ""); + const auto CAIROISURFACE = cairo_image_surface_create_from_png(ABSOLUTEPATH.c_str()); + + const auto CAIRO = cairo_create(CAIROISURFACE); + cairo_scale(CAIRO, 1, 1); + + target.cairo = CAIRO; + target.cairosurface = CAIROISURFACE; + target.data = cairo_image_surface_get_data(CAIROISURFACE); + target.size = {(double)cairo_image_surface_get_width(CAIROISURFACE), (double)cairo_image_surface_get_height(CAIROISURFACE)}; + + preloadTargets.push_back(target); +} + void CAsyncResourceGatherer::renderText(const SPreloadRequest& rq) { SPreloadTarget target; target.type = TARGET_IMAGE; /* text is just an image lol */ @@ -314,6 +333,8 @@ void CAsyncResourceGatherer::asyncAssetSpinLock() { for (auto& r : requests) { if (r.type == TARGET_TEXT) { renderText(r); + } else if (r.type == TARGET_IMAGE) { + renderImage(r); } else { Debug::log(ERR, "Unsupported async preload type {}??", (int)r.type); continue; diff --git a/src/renderer/AsyncResourceGatherer.hpp b/src/renderer/AsyncResourceGatherer.hpp index b6dab11..2fa81a8 100644 --- a/src/renderer/AsyncResourceGatherer.hpp +++ b/src/renderer/AsyncResourceGatherer.hpp @@ -56,6 +56,7 @@ class CAsyncResourceGatherer { void asyncAssetSpinLock(); void renderText(const SPreloadRequest& rq); + void renderImage(const SPreloadRequest& rq); struct { std::condition_variable loopGuard; @@ -88,4 +89,4 @@ class CAsyncResourceGatherer { std::unordered_map assets; void gather(); -}; \ No newline at end of file +}; diff --git a/src/renderer/widgets/Image.cpp b/src/renderer/widgets/Image.cpp index e513461..5a9c230 100644 --- a/src/renderer/widgets/Image.cpp +++ b/src/renderer/widgets/Image.cpp @@ -1,7 +1,76 @@ #include "Image.hpp" #include "../Renderer.hpp" +#include "../../core/hyprlock.hpp" +#include "../../helpers/Log.hpp" #include +CImage::~CImage() { + imageTimer->cancel(); + imageTimer.reset(); +} + +static void onTimer(std::shared_ptr self, void* data) { + const auto PIMAGE = (CImage*)data; + + PIMAGE->onTimerUpdate(); + PIMAGE->plantTimer(); +} + +static void onAssetCallback(void* data) { + const auto PIMAGE = (CImage*)data; + PIMAGE->renderSuper(); +} + +void CImage::onTimerUpdate() { + const std::string OLDPATH = path; + + 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; + + request.id = std::string{"image:"} + path + ",time:" + std::to_string(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 CImage::plantTimer() { + + if (reloadTime == 0) { + imageTimer = g_pHyprlock->addTimer(std::chrono::hours(1), onTimer, this, true); + } else if (reloadTime > 0) + imageTimer = g_pHyprlock->addTimer(std::chrono::seconds(reloadTime), onTimer, this, false); +} + CImage::CImage(const Vector2D& viewport_, COutput* output_, const std::string& resourceID_, const std::unordered_map& props) : viewport(viewport_), resourceID(resourceID_), output(output_), shadow(this, props, viewport_) { @@ -14,11 +83,38 @@ CImage::CImage(const Vector2D& viewport_, COutput* output_, const std::string& r valign = std::any_cast(props.at("valign")); angle = std::any_cast(props.at("rotate")); + path = std::any_cast(props.at("path")); + reloadTime = std::any_cast(props.at("reload_time")); + reloadCommand = std::any_cast(props.at("reload_cmd")); + + try { + modificationTime = std::filesystem::last_write_time(path); + } catch (std::exception& e) { Debug::log(ERR, "{}", e.what()); } + angle = angle * M_PI / 180.0; + + plantTimer(); } bool CImage::draw(const SRenderData& data) { + if (!pendingResourceID.empty()) { + auto newAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(pendingResourceID); + if (newAsset) { + if (newAsset->texture.m_iType == TEXTURE_INVALID) { + g_pRenderer->asyncResourceGatherer->unloadAsset(newAsset); + } else if (resourceID != pendingResourceID) { + g_pRenderer->asyncResourceGatherer->unloadAsset(asset); + imageFB.release(); + + asset = newAsset; + resourceID = pendingResourceID; + firstRender = true; + } + pendingResourceID = ""; + } + } + if (resourceID.empty()) return false; @@ -54,6 +150,8 @@ bool CImage::draw(const SRenderData& data) { borderBox.round(); imageFB.alloc(borderBox.w, borderBox.h, true); g_pRenderer->pushFb(imageFB.m_iFb); + glClearColor(0.0, 0.0, 0.0, 0.0); + glClear(GL_COLOR_BUFFER_BIT); if (border > 0) g_pRenderer->renderRect(borderBox, color, ALLOWROUND ? rounding : std::min(borderBox.w, borderBox.h) / 2.0); @@ -84,3 +182,15 @@ bool CImage::draw(const SRenderData& data) { return data.opacity < 1.0; } + +static void onAssetCallbackTimer(std::shared_ptr self, void* data) { + const auto PIMAGE = (CImage*)data; + PIMAGE->renderSuper(); +} + +void CImage::renderSuper() { + g_pHyprlock->renderOutput(output->stringPort); + + if (!pendingResourceID.empty()) /* did not consume the pending resource */ + g_pHyprlock->addTimer(std::chrono::milliseconds(100), onAssetCallbackTimer, this); +} diff --git a/src/renderer/widgets/Image.hpp b/src/renderer/widgets/Image.hpp index 1747378..8a9906e 100644 --- a/src/renderer/widgets/Image.hpp +++ b/src/renderer/widgets/Image.hpp @@ -3,8 +3,11 @@ #include "IWidget.hpp" #include "../../helpers/Vector2D.hpp" #include "../../helpers/Color.hpp" +#include "../../core/Timer.hpp" +#include "../AsyncResourceGatherer.hpp" #include "Shadowable.hpp" #include +#include #include #include @@ -14,26 +17,38 @@ class COutput; class CImage : public IWidget { public: CImage(const Vector2D& viewport, COutput* output_, const std::string& resourceID, const std::unordered_map& props); + ~CImage(); virtual bool draw(const SRenderData& data); + void renderSuper(); + void onTimerUpdate(); + void plantTimer(); + private: - CFramebuffer imageFB; + CFramebuffer imageFB; - int size; - int rounding; - double border; - double angle; - CColor color; - Vector2D pos; + int size; + int rounding; + double border; + double angle; + CColor color; + Vector2D pos; - std::string halign, valign; + std::string halign, valign, path; - bool firstRender = true; + bool firstRender = true; - Vector2D viewport; - std::string resourceID; - SPreloadedAsset* asset = nullptr; - COutput* output = nullptr; - CShadowable shadow; + int reloadTime; + std::string reloadCommand; + std::filesystem::file_time_type modificationTime; + std::shared_ptr imageTimer; + CAsyncResourceGatherer::SPreloadRequest request; + + Vector2D viewport; + std::string resourceID; + std::string pendingResourceID; // if reloading image + SPreloadedAsset* asset = nullptr; + COutput* output = nullptr; + CShadowable shadow; };