From 7370fc624fb774dfd580aefa1b308f7740862e2d Mon Sep 17 00:00:00 2001 From: Vaxry Date: Tue, 20 Feb 2024 00:11:19 +0000 Subject: [PATCH] label: add time and dynamic timers --- src/core/hyprlock.cpp | 124 ++++++++++++++++++++++++- src/core/hyprlock.hpp | 17 ++++ src/main.cpp | 4 +- src/renderer/AsyncResourceGatherer.cpp | 17 +++- src/renderer/AsyncResourceGatherer.hpp | 3 + src/renderer/Renderer.cpp | 2 +- src/renderer/widgets/IWidget.hpp | 7 ++ src/renderer/widgets/Label.cpp | 86 +++++++++++++---- src/renderer/widgets/Label.hpp | 33 +++++-- 9 files changed, 255 insertions(+), 38 deletions(-) diff --git a/src/core/hyprlock.cpp b/src/core/hyprlock.cpp index 64ffe49..672d63d 100644 --- a/src/core/hyprlock.cpp +++ b/src/core/hyprlock.cpp @@ -4,8 +4,12 @@ #include "../renderer/Renderer.hpp" #include "Password.hpp" #include "Egl.hpp" + +#include +#include #include -#include +#include +#include CHyprlock::CHyprlock(const std::string& wlDisplay) { m_sWaylandState.display = wl_display_connect(wlDisplay.empty() ? nullptr : wlDisplay.c_str()); @@ -110,9 +114,120 @@ void CHyprlock::run() { lockSession(); - while (wl_display_dispatch(m_sWaylandState.display) != -1) { + pollfd pollfds[] = { + { + .fd = wl_display_get_fd(m_sWaylandState.display), + .events = POLLIN, + }, + }; + + std::thread pollThr([this, &pollfds]() { + while (1) { + int ret = poll(pollfds, 1, 5000 /* 5 seconds, reasonable. It's because we might need to terminate */); + if (ret < 0) { + Debug::log(CRIT, "[core] Polling fds failed with {}", errno); + m_bTerminate = true; + exit(1); + } + + for (size_t i = 0; i < 3; ++i) { + if (pollfds[i].revents & POLLHUP) { + Debug::log(CRIT, "[core] Disconnected from pollfd id {}", i); + m_bTerminate = true; + exit(1); + } + } + + if (m_bTerminate) + break; + + if (ret != 0) { + Debug::log(TRACE, "[core] got poll event"); + std::lock_guard lg2(m_sLoopState.eventLoopMutex); + m_sLoopState.event = true; + m_sLoopState.loopCV.notify_all(); + } + } + }); + + std::thread timersThr([this]() { + while (1) { + // calc nearest thing + m_sLoopState.timersMutex.lock(); + + float least = 10000; + for (auto& t : m_vTimers) { + const auto TIME = t->leftMs(); + if (TIME < least) + least = TIME; + } + + m_sLoopState.timersMutex.unlock(); + + std::unique_lock lk(m_sLoopState.timerRequestMutex); + m_sLoopState.timerCV.wait_for(lk, std::chrono::milliseconds((int)least + 1), [this] { return m_sLoopState.event; }); + + // notify main + std::lock_guard lg2(m_sLoopState.eventLoopMutex); + Debug::log(TRACE, "timer thread firing"); + m_sLoopState.event = true; + m_sLoopState.loopCV.notify_all(); + } + }); + + m_sLoopState.event = true; // let it process once + + while (1) { + std::unique_lock lk(m_sLoopState.eventRequestMutex); + if (m_sLoopState.event == false) + m_sLoopState.loopCV.wait(lk, [this] { return m_sLoopState.event; }); + if (m_bTerminate) break; + + std::lock_guard lg(m_sLoopState.eventLoopMutex); + + m_sLoopState.event = false; + + if (pollfds[0].revents & POLLIN /* dbus */) { + Debug::log(TRACE, "got wl event"); + wl_display_flush(m_sWaylandState.display); + if (wl_display_prepare_read(m_sWaylandState.display) == 0) { + wl_display_read_events(m_sWaylandState.display); + wl_display_dispatch_pending(m_sWaylandState.display); + } else { + wl_display_dispatch(m_sWaylandState.display); + } + } + + m_sLoopState.timersMutex.lock(); + auto timerscpy = m_vTimers; + m_sLoopState.timersMutex.unlock(); + + std::vector> passed; + + for (auto& t : timerscpy) { + if (t->passed() && !t->cancelled()) { + t->call(t); + passed.push_back(t); + } + + if (t->cancelled()) + passed.push_back(t); + } + + m_sLoopState.timersMutex.lock(); + std::erase_if(m_vTimers, [passed](const auto& timer) { return std::find(passed.begin(), passed.end(), timer) != passed.end(); }); + m_sLoopState.timersMutex.unlock(); + + passed.clear(); + + // finalize wayland dispatching. Dispatch pending on the queue + int ret = 0; + do { + ret = wl_display_dispatch_pending(m_sWaylandState.display); + wl_display_flush(m_sWaylandState.display); + } while (ret > 0); } Debug::log(LOG, "Reached the end, exiting"); @@ -375,3 +490,8 @@ wp_viewporter* CHyprlock::getViewporter() { size_t CHyprlock::getPasswordBufferLen() { return m_sPasswordState.passBuffer.length(); } + +std::shared_ptr CHyprlock::addTimer(const std::chrono::system_clock::duration& timeout, std::function self, void* data)> cb_, void* data) { + std::lock_guard lg(m_sLoopState.timersMutex); + return m_vTimers.emplace_back(std::make_shared(timeout, cb_, data)); +} diff --git a/src/core/hyprlock.hpp b/src/core/hyprlock.hpp index 9b305af..82684bd 100644 --- a/src/core/hyprlock.hpp +++ b/src/core/hyprlock.hpp @@ -6,9 +6,11 @@ #include "viewporter-protocol.h" #include "Output.hpp" #include "CursorShape.hpp" +#include "Timer.hpp" #include #include +#include #include @@ -21,6 +23,8 @@ class CHyprlock { void onGlobal(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version); void onGlobalRemoved(void* data, struct wl_registry* registry, uint32_t name); + std::shared_ptr addTimer(const std::chrono::system_clock::duration& timeout, std::function self, void* data)> cb_, void* data); + void onLockLocked(); void onLockFinished(); @@ -68,7 +72,20 @@ class CHyprlock { std::string passBuffer = ""; } m_sPasswordState; + struct { + std::mutex timersMutex; + std::mutex eventRequestMutex; + std::mutex eventLoopMutex; + std::condition_variable loopCV; + bool event = false; + + std::condition_variable timerCV; + std::mutex timerRequestMutex; + } m_sLoopState; + std::vector> m_vOutputs; + + std::vector> m_vTimers; }; inline std::unique_ptr g_pHyprlock; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index b3a1f55..b6c6dc3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,8 +14,10 @@ int main(int argc, char** argv, char** envp) { else if (arg == "--quiet" || arg == "-q") Debug::quiet = true; - else if (arg == "--display" && i + 1 < argc) + else if (arg == "--display" && i + 1 < argc) { wlDisplay = argv[i + 1]; + i++; + } } try { diff --git a/src/renderer/AsyncResourceGatherer.cpp b/src/renderer/AsyncResourceGatherer.cpp index 905f50d..e02cabd 100644 --- a/src/renderer/AsyncResourceGatherer.cpp +++ b/src/renderer/AsyncResourceGatherer.cpp @@ -89,12 +89,13 @@ void CAsyncResourceGatherer::apply() { for (auto& t : preloadTargets) { if (t.type == TARGET_IMAGE) { - const auto ASSET = &assets[t.id]; + std::lock_guard lg(asyncLoopState.assetsMutex); + const auto ASSET = &assets[t.id]; - const auto CAIROFORMAT = cairo_image_surface_get_format((cairo_surface_t*)t.cairosurface); - const GLint glIFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB32F : GL_RGBA; - const GLint glFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB : GL_RGBA; - const GLint glType = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_FLOAT : GL_UNSIGNED_BYTE; + const auto CAIROFORMAT = cairo_image_surface_get_format((cairo_surface_t*)t.cairosurface); + const GLint glIFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB32F : GL_RGBA; + const GLint glFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB : GL_RGBA; + const GLint glType = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_FLOAT : GL_UNSIGNED_BYTE; ASSET->texture.m_vSize = t.size; ASSET->texture.allocate(); @@ -233,4 +234,10 @@ void CAsyncResourceGatherer::requestAsyncAssetPreload(const SPreloadRequest& req std::unique_lock lk(cvmtx); asyncLoopState.pending = true; asyncLoopState.loopGuard.notify_all(); +} + +void CAsyncResourceGatherer::unloadAsset(SPreloadedAsset* asset) { + std::lock_guard lg(asyncLoopState.assetsMutex); + + std::erase_if(assets, [asset](const auto& a) { return &a.second == asset; }); } \ No newline at end of file diff --git a/src/renderer/AsyncResourceGatherer.hpp b/src/renderer/AsyncResourceGatherer.hpp index 8a9e552..af8fd98 100644 --- a/src/renderer/AsyncResourceGatherer.hpp +++ b/src/renderer/AsyncResourceGatherer.hpp @@ -42,6 +42,7 @@ class CAsyncResourceGatherer { }; void requestAsyncAssetPreload(const SPreloadRequest& request); + void unloadAsset(SPreloadedAsset* asset); private: std::thread initThread; @@ -56,6 +57,8 @@ class CAsyncResourceGatherer { std::mutex requestMutex; + std::mutex assetsMutex; + std::vector requests; bool pending = false; diff --git a/src/renderer/Renderer.cpp b/src/renderer/Renderer.cpp index ae08d61..78bb03c 100644 --- a/src/renderer/Renderer.cpp +++ b/src/renderer/Renderer.cpp @@ -258,7 +258,7 @@ std::vector>* CRenderer::getOrCreateWidgetsFor(const CS } else if (c.type == "input-field") { widgets[surf].emplace_back(std::make_unique(surf->size, c.values)); } else if (c.type == "label") { - widgets[surf].emplace_back(std::make_unique(surf->size, c.values)); + widgets[surf].emplace_back(std::make_unique(surf->size, c.values, /* evil */ const_cast(surf))); } } } diff --git a/src/renderer/widgets/IWidget.hpp b/src/renderer/widgets/IWidget.hpp index 37810b3..71762e5 100644 --- a/src/renderer/widgets/IWidget.hpp +++ b/src/renderer/widgets/IWidget.hpp @@ -12,4 +12,11 @@ class IWidget { virtual bool draw(const SRenderData& data) = 0; virtual Vector2D posFromHVAlign(const Vector2D& viewport, const Vector2D& size, const Vector2D& offset, const std::string& halign, const std::string& valign); + + struct SFormatResult { + std::string formatted; + float updateEveryMs = 0; // 0 means don't (static) + }; + + virtual SFormatResult formatString(std::string in); }; \ No newline at end of file diff --git a/src/renderer/widgets/Label.cpp b/src/renderer/widgets/Label.cpp index e7d43d0..2dd71ec 100644 --- a/src/renderer/widgets/Label.cpp +++ b/src/renderer/widgets/Label.cpp @@ -3,32 +3,59 @@ #include #include "../Renderer.hpp" #include "../../helpers/Log.hpp" +#include "../../core/hyprlock.hpp" -void replaceAll(std::string& str, const std::string& from, const std::string& to) { - if (from.empty()) +CLabel::~CLabel() { + labelTimer->cancel(); + labelTimer.reset(); +} + +static void onTimer(std::shared_ptr self, void* data) { + const auto PLABEL = (CLabel*)data; + + // update label + PLABEL->onTimerUpdate(); + + // render and replant + PLABEL->renderSuper(); + PLABEL->plantTimer(); +} + +void CLabel::onTimerUpdate() { + std::string oldFormatted = label.formatted; + + label = formatString(labelPreFormat); + + if (label.formatted == oldFormatted) return; - size_t pos = 0; - while ((pos = str.find(from, pos)) != std::string::npos) { - str.replace(pos, from.length(), to); - pos += to.length(); - } + + if (!pendingResourceID.empty()) + return; // too many updates, we'll miss some. Shouldn't happen tbh + + // request new + request.id = std::string{"label:"} + std::to_string((uintptr_t)this) + ",time:" + std::to_string(time(nullptr)); + pendingResourceID = request.id; + request.asset = label.formatted; + + g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request); } -std::string CLabel::formatString(std::string in) { - replaceAll(in, "$USER", std::string{getlogin()}); - return in; +void CLabel::plantTimer() { + if (label.updateEveryMs != 0) + labelTimer = g_pHyprlock->addTimer(std::chrono::milliseconds((int)label.updateEveryMs), onTimer, this); } -CLabel::CLabel(const Vector2D& viewport_, const std::unordered_map& props) { - std::string labelPreFormat = std::any_cast(props.at("text")); - std::string fontFamily = std::any_cast(props.at("font_family")); - CColor labelColor = std::any_cast(props.at("color")); - int fontSize = std::any_cast(props.at("font_size")); +CLabel::CLabel(const Vector2D& viewport_, const std::unordered_map& props, CSessionLockSurface* surface_) : surface(surface_) { + labelPreFormat = std::any_cast(props.at("text")); + std::string fontFamily = std::any_cast(props.at("font_family")); + CColor labelColor = std::any_cast(props.at("color")); + int fontSize = std::any_cast(props.at("font_size")); - CAsyncResourceGatherer::SPreloadRequest request; - request.id = std::string{"label:"} + std::to_string((uintptr_t)this); + label = formatString(labelPreFormat); + + request.id = std::string{"label:"} + std::to_string((uintptr_t)this) + ",time:" + std::to_string(time(nullptr)); resourceID = request.id; - request.asset = formatString(labelPreFormat); + request.asset = label.formatted; request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT; request.props["font_family"] = fontFamily; request.props["color"] = labelColor; @@ -38,12 +65,14 @@ CLabel::CLabel(const Vector2D& viewport_, const std::unordered_map(props.at("position")); pos = {POS__.x, POS__.y}; + configPos = pos; viewport = viewport_; - label = request.asset; halign = std::any_cast(props.at("halign")); valign = std::any_cast(props.at("valign")); + + plantTimer(); } bool CLabel::draw(const SRenderData& data) { @@ -54,7 +83,20 @@ bool CLabel::draw(const SRenderData& data) { return true; // calc pos - pos = posFromHVAlign(viewport, asset->texture.m_vSize, pos, halign, valign); + pos = posFromHVAlign(viewport, asset->texture.m_vSize, configPos, halign, valign); + } + + if (!pendingResourceID.empty()) { + // new asset is pending + auto newAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(pendingResourceID); + if (newAsset) { + // new asset is ready :D + g_pRenderer->asyncResourceGatherer->unloadAsset(asset); + asset = newAsset; + resourceID = pendingResourceID; + pendingResourceID = ""; + pos = posFromHVAlign(viewport, asset->texture.m_vSize, configPos, halign, valign); + } } CBox box = {pos.x, pos.y, asset->texture.m_vSize.x, asset->texture.m_vSize.y}; @@ -62,4 +104,8 @@ bool CLabel::draw(const SRenderData& data) { g_pRenderer->renderTexture(box, asset->texture, data.opacity); return false; +} + +void CLabel::renderSuper() { + surface->render(); } \ No newline at end of file diff --git a/src/renderer/widgets/Label.hpp b/src/renderer/widgets/Label.hpp index 35a35c6..890390e 100644 --- a/src/renderer/widgets/Label.hpp +++ b/src/renderer/widgets/Label.hpp @@ -2,25 +2,40 @@ #include "IWidget.hpp" #include "../../helpers/Vector2D.hpp" +#include "../../core/Timer.hpp" +#include "../AsyncResourceGatherer.hpp" #include #include #include struct SPreloadedAsset; +class CSessionLockSurface; class CLabel : public IWidget { public: - CLabel(const Vector2D& viewport, const std::unordered_map& props); + CLabel(const Vector2D& viewport, const std::unordered_map& props, CSessionLockSurface* surface_); + ~CLabel(); virtual bool draw(const SRenderData& data); - private: - std::string formatString(std::string in); + void renderSuper(); + void onTimerUpdate(); + void plantTimer(); - Vector2D viewport; - Vector2D pos; - std::string resourceID; - std::string label; - std::string halign, valign; - SPreloadedAsset* asset = nullptr; + private: + std::string labelPreFormat; + IWidget::SFormatResult label; + + Vector2D viewport; + Vector2D pos; + Vector2D configPos; + std::string resourceID; + std::string pendingResourceID; // if dynamic label + std::string halign, valign; + SPreloadedAsset* asset = nullptr; + CSessionLockSurface* surface = nullptr; + + CAsyncResourceGatherer::SPreloadRequest request; + + std::shared_ptr labelTimer; }; \ No newline at end of file