Compare commits

..

19 commits
v0.9.1 ... main

Author SHA1 Message Date
William Escande
d099f87d36
pam: prevent nullpointer crash when user passwd isn't found (#928) 2025-12-19 15:53:53 +00:00
Maximilian Seidler
e2da7c6b1f
animation: migrate PHLANIMVAR from SP to UP (#920)
* animation: migrate PHLANIMVAR from SP to UP

* use create2

* bump hyprutils and flake update
2025-12-10 09:15:39 +00:00
Nathan.Woodburn/
98b86752fe
background: reload screenshot if reload_cmd specified (#903) 2025-10-28 18:20:34 +00:00
Maximilian Seidler
36ec73f166
cmake: add "libpam" as an alias for "pam" (#893) 2025-10-09 15:32:29 +00:00
Maximilian Seidler
bdc44ab5d6
flake.lock: update (#890) 2025-10-08 08:53:37 +00:00
Maximilian Seidler
de2cc5bd54
core: migrate to Hyprutils::CAsyncResourceGatherer and improve resource handling (#879)
* core: use Hyprgraphics::CAsyncResourceGatherer

* core: move screencopy frame generation to the new resource manager

* core: introduce a dedicated onAssetUpdate callback

* check for unloaded before finished and some cleanup

* also allow for dynamic label resource deduplication

use a simple counter instead of a timestamp to allow the same
widget on a different monitor to reuse a text cmd resource.

I didn't do this before, because I was worried about two labels that use
the same command with different reload times. I mitigated that by just
incrementing the revision by the time interval. This should be sufficent
to avoid clashes.

* don't render within onAssetUpdate to avoid duplicate renders

another much improvement for multi monitor setups.
allows updating within the same frame for most labels.

* remove nvidia workaround :)

I tested and it seems like the resource manager revision makes
the nvidia workaround obsolete.
2025-10-08 08:45:09 +00:00
Maximilian Seidler
c8a6768dca
misc: remove hyprland-protocols from README (#887)
hyprlock doesn't use it currently
2025-10-04 10:07:28 +00:00
Maximilian Seidler
3cb799b184
core: nvidia workaround destroy renderer before EGL (#884) 2025-10-03 11:58:38 +00:00
c48279d1e0
version: bump to 0.9.2 2025-10-02 11:03:05 +01:00
Maximilian Seidler
7f769fa993 Revert "background: unload screenshots when monitor is removed"
Pushed to the wrong remote :(
This reverts commit 1380ca04ae.
2025-09-17 17:54:49 +02:00
Maximilian Seidler
1380ca04ae background: unload screenshots when monitor is removed
This is just for testing as of now. No merging.
2025-09-17 17:51:34 +02:00
Maximilian Seidler
61b36c64a8
renderer: fix nvidia workaround (#878) 2025-09-17 15:51:17 +00:00
davc0n
a7f2634a9e
Refactor asset management to use shared_ptr (#870)
* Refactor asset management to use shared_ptr

* Replace shared_ptr with ASP for SPreloadedAsset usage
2025-09-15 18:14:34 +00:00
Maximilian Seidler
450ae1e5f0
lock-surface: remove redundant sendDestroy calls (#868)
They are already part of the generated protocol code.
2025-09-10 16:11:06 +00:00
Maximilian Seidler
04cfdc4e5b
core: recreate rendering context when monitors are empty on nvidia (#845)
This is a workaround for nvidia that can hopefully be removed at some point.
2025-09-04 07:04:54 +00:00
Maximilian Seidler
cedbb24472
renderer: move asyncResourceGatherer out of the renderer (#863)
* lockSurface: cleanup some resources

* renderer: move asyncResourceGatherer out of CRenderer

In preperation to recreate the rendering context.
2025-09-03 09:32:12 +00:00
Maximilian Seidler
8d0e56998e
core: remove dmabuf listeners after we are done with Screencopy (#858) 2025-08-27 14:40:51 +00:00
Maximilian Seidler
a356bf055b
background: monitor transforms fixups (#859) 2025-08-27 09:32:57 +00:00
Hiroki Tagato
347e05a40e
Make detection of pam library more portable (#840)
FreeBSD has the pam library installed in the base system. However, it does not provide pam.pc file. So pkg_check_modules() fails to detect pam library. With this change, cmake tries to find it using find_library() first and falls back to pkg_check_modules().
2025-08-05 17:00:48 +00:00
36 changed files with 974 additions and 961 deletions

View file

@ -86,14 +86,26 @@ pkg_check_modules(
pangocairo
libdrm
gbm
pam
hyprutils>=0.8.0
hyprutils>=0.11.0
sdbus-c++>=2.0.0
hyprgraphics)
hyprgraphics>=0.1.6)
find_library(PAM_FOUND NAMES pam libpam)
if(PAM_FOUND)
set(PAM_LIB ${PAM_FOUND})
else()
pkg_check_modules(PAM IMPORTED_TARGET pam libpam)
if(PAM_FOUND)
set(PAM_LIB PkgConfig::PAM)
else()
message(FATAL_ERROR "The required library libpam was not found.")
endif()
endif()
message(STATUS "Found pam at ${PAM_LIB}")
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp")
add_executable(hyprlock ${SRCFILES})
target_link_libraries(hyprlock PRIVATE pam rt Threads::Threads PkgConfig::deps
target_link_libraries(hyprlock PRIVATE ${PAM_LIB} rt Threads::Threads PkgConfig::deps
OpenGL::EGL OpenGL::GLES3)
# protocols

View file

@ -32,7 +32,6 @@ You need the following dependencies
- cairo
- hyprgraphics
- hyprland-protocols
- hyprlang
- hyprutils
- hyprwayland-scanner

View file

@ -1 +1 @@
0.9.1
0.9.2

30
flake.lock generated
View file

@ -13,11 +13,11 @@
]
},
"locked": {
"lastModified": 1750621377,
"narHash": "sha256-8u6b5oAdX0rCuoR8wFenajBRmI+mzbpNig6hSCuWUzE=",
"lastModified": 1763733840,
"narHash": "sha256-JnET78yl5RvpGuDQy3rCycOCkiKoLr5DN1fPhRNNMco=",
"owner": "hyprwm",
"repo": "hyprgraphics",
"rev": "b3d628d01693fb9bb0a6690cd4e7b80abda04310",
"rev": "8f1bec691b2d198c60cccabca7a94add2df4ed1a",
"type": "github"
},
"original": {
@ -39,11 +39,11 @@
]
},
"locked": {
"lastModified": 1750371198,
"narHash": "sha256-/iuJ1paQOBoSLqHflRNNGyroqfF/yvPNurxzcCT0cAE=",
"lastModified": 1764612430,
"narHash": "sha256-54ltTSbI6W+qYGMchAgCR6QnC1kOdKXN6X6pJhOWxFg=",
"owner": "hyprwm",
"repo": "hyprlang",
"rev": "cee01452bca58d6cadb3224e21e370de8bc20f0b",
"rev": "0d00dc118981531aa731150b6ea551ef037acddd",
"type": "github"
},
"original": {
@ -62,11 +62,11 @@
]
},
"locked": {
"lastModified": 1751061882,
"narHash": "sha256-g9n8Vrbx+2JYM170P9BbvGHN39Wlkr4U+V2WLHQsXL8=",
"lastModified": 1764962281,
"narHash": "sha256-rGbEMhTTyTzw4iyz45lch5kXseqnqcEpmrHdy+zHsfo=",
"owner": "hyprwm",
"repo": "hyprutils",
"rev": "4737241eaf8a1e51671a2a088518071f9a265cf4",
"rev": "fe686486ac867a1a24f99c753bb40ffed338e4b0",
"type": "github"
},
"original": {
@ -85,11 +85,11 @@
]
},
"locked": {
"lastModified": 1750371869,
"narHash": "sha256-lGk4gLjgZQ/rndUkzmPYcgbHr8gKU5u71vyrjnwfpB4=",
"lastModified": 1763640274,
"narHash": "sha256-Uan1Nl9i4TF/kyFoHnTq1bd/rsWh4GAK/9/jDqLbY5A=",
"owner": "hyprwm",
"repo": "hyprwayland-scanner",
"rev": "aa38edd6e3e277ae6a97ea83a69261a5c3aab9fd",
"rev": "f6cf414ca0e16a4d30198fd670ec86df3c89f671",
"type": "github"
},
"original": {
@ -100,11 +100,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1751011381,
"narHash": "sha256-krGXKxvkBhnrSC/kGBmg5MyupUUT5R6IBCLEzx9jhMM=",
"lastModified": 1765186076,
"narHash": "sha256-hM20uyap1a0M9d344I692r+ik4gTMyj60cQWO+hAYP8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "30e2e2857ba47844aa71991daa6ed1fc678bcbb7",
"rev": "addf7cf5f383a3101ecfba091b98d0a1263dc9b8",
"type": "github"
},
"original": {

View file

@ -78,8 +78,7 @@ void updateGradientVariable(CAnimatedVariable<CGradientValueData>& av, const flo
void CHyprlockAnimationManager::tick() {
static const auto ANIMATIONSENABLED = g_pConfigManager->getValue<Hyprlang::INT>("animations:enabled");
for (size_t i = 0; i < m_vActiveAnimatedVariables.size(); i++) {
const auto PAV = m_vActiveAnimatedVariables[i].lock();
for (const auto& PAV : m_vActiveAnimatedVariables) {
if (!PAV || !PAV->ok())
continue;

View file

@ -19,12 +19,10 @@ class CHyprlockAnimationManager : public Hyprutils::Animation::CAnimationManager
template <Animable VarType>
void createAnimation(const VarType& v, PHLANIMVAR<VarType>& pav, SP<SAnimationPropertyConfig> pConfig) {
constexpr const eAnimatedVarType EAVTYPE = typeToeAnimatedVarType<VarType>;
const auto PAV = makeShared<CAnimatedVariable<VarType>>();
pav = makeUnique<CAnimatedVariable<VarType>>();
PAV->create(EAVTYPE, static_cast<Hyprutils::Animation::CAnimationManager*>(this), PAV, v);
PAV->setConfig(pConfig);
pav = std::move(PAV);
pav->create2(EAVTYPE, static_cast<Hyprutils::Animation::CAnimationManager*>(this), pav, v);
pav->setConfig(pConfig);
}
bool m_bTickScheduled = false;

View file

@ -38,8 +38,9 @@ CEGL::CEGL(wl_display* display) {
if (eglCreatePlatformWindowSurfaceEXT == nullptr)
throw std::runtime_error("Failed to get eglCreatePlatformWindowSurfaceEXT");
eglDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_WAYLAND_EXT, display, nullptr);
EGLint matched = 0;
const char* vendorString = nullptr;
eglDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_WAYLAND_EXT, display, nullptr);
EGLint matched = 0;
if (eglDisplay == EGL_NO_DISPLAY) {
Debug::log(CRIT, "Failed to create EGL display");
goto error;
@ -65,6 +66,9 @@ CEGL::CEGL(wl_display* display) {
goto error;
}
vendorString = eglQueryString(eglDisplay, EGL_VENDOR);
m_isNvidia = (vendorString) ? std::string{vendorString}.contains("NVIDIA") : false;
return;
error:

View file

@ -17,6 +17,8 @@ class CEGL {
PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC eglCreatePlatformWindowSurfaceEXT;
void makeCurrent(EGLSurface surf);
bool m_isNvidia = false;
};
inline UP<CEGL> g_pEGL;

View file

@ -7,6 +7,12 @@
#include "../renderer/Renderer.hpp"
CSessionLockSurface::~CSessionLockSurface() {
if (frameCallback)
frameCallback.reset();
if (eglSurface)
eglDestroySurface(g_pEGL->eglDisplay, eglSurface);
if (eglWindow)
wl_egl_window_destroy(eglWindow);
}

View file

@ -1,11 +1,12 @@
#include "hyprlock.hpp"
#include "AnimationManager.hpp"
#include "../helpers/Log.hpp"
#include "../config/ConfigManager.hpp"
#include "../renderer/Renderer.hpp"
#include "../renderer/AsyncResourceManager.hpp"
#include "../auth/Auth.hpp"
#include "../auth/Fingerprint.hpp"
#include "Egl.hpp"
#include "./Egl.hpp"
#include "./Seat.hpp"
#include <chrono>
#include <hyprutils/memory/UniquePtr.hpp>
#include <sys/wait.h>
@ -17,8 +18,6 @@
#include <cassert>
#include <cstring>
#include <xf86drm.h>
#include <filesystem>
#include <fstream>
#include <algorithm>
#include <sdbus-c++/sdbus-c++.h>
#include <hyprutils/os/Process.hpp>
@ -238,6 +237,18 @@ void CHyprlock::addDmabufListener() {
});
}
void CHyprlock::removeDmabufListener() {
if (dma.linuxDmabufFeedback) {
dma.linuxDmabufFeedback->sendDestroy();
dma.linuxDmabufFeedback.reset();
}
if (dma.linuxDmabuf) {
dma.linuxDmabuf->sendDestroy();
dma.linuxDmabuf.reset();
}
}
void CHyprlock::run() {
m_sWaylandState.registry = makeShared<CCWlRegistry>((wl_proxy*)wl_display_get_registry(m_sWaylandState.display));
m_sWaylandState.registry->setGlobal([this](CCWlRegistry* r, uint32_t name, const char* interface, uint32_t version) {
@ -307,66 +318,26 @@ void CHyprlock::run() {
// gather info about monitors
wl_display_roundtrip(m_sWaylandState.display);
g_pRenderer = makeUnique<CRenderer>();
g_pAuth = makeUnique<CAuth>();
g_pRenderer = makeUnique<CRenderer>();
g_asyncResourceManager = makeUnique<CAsyncResourceManager>();
g_pAuth = makeUnique<CAuth>();
g_pAuth->start();
Debug::log(LOG, "Running on {}", m_sCurrentDesktop);
if (!g_pHyprlock->m_bImmediateRender) {
g_asyncResourceManager->enqueueStaticAssets();
g_asyncResourceManager->enqueueScreencopyFrames();
if (!g_pHyprlock->m_bImmediateRender)
// Gather background resources and screencopy frames before locking the screen.
// We need to do this because as soon as we lock the screen, workspaces frames can no longer be captured. It either won't work at all, or we will capture hyprlock itself.
// Bypass with --immediate-render (can cause the background first rendering a solid color and missing or inaccurate screencopy frames)
const auto MAXDELAYMS = 2000; // 2 Seconds
const auto STARTGATHERTP = std::chrono::system_clock::now();
int fdcount = 1;
pollfd pollfds[2];
pollfds[0] = {
.fd = wl_display_get_fd(m_sWaylandState.display),
.events = POLLIN,
};
if (g_pRenderer->asyncResourceGatherer->gatheredEventfd.isValid()) {
pollfds[1] = {
.fd = g_pRenderer->asyncResourceGatherer->gatheredEventfd.get(),
.events = POLLIN,
};
fdcount++;
}
while (!g_pRenderer->asyncResourceGatherer->gathered) {
wl_display_flush(m_sWaylandState.display);
if (wl_display_prepare_read(m_sWaylandState.display) == 0) {
if (poll(pollfds, fdcount, /* 100ms timeout */ 100) < 0) {
RASSERT(errno == EINTR, "[core] Polling fds failed with {}", errno);
wl_display_cancel_read(m_sWaylandState.display);
continue;
}
wl_display_read_events(m_sWaylandState.display);
wl_display_dispatch_pending(m_sWaylandState.display);
} else {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
wl_display_dispatch(m_sWaylandState.display);
}
if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - STARTGATHERTP).count() > MAXDELAYMS) {
Debug::log(WARN, "Gathering resources timed out after {} milliseconds. Backgrounds may be delayed and render `background:color` at first.", MAXDELAYMS);
break;
}
}
Debug::log(LOG, "Resources gathered after {} milliseconds",
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - STARTGATHERTP).count());
}
g_asyncResourceManager->gatherInitialResources(m_sWaylandState.display);
// Failed to lock the session
if (!acquireSessionLock()) {
m_sLoopState.timerEvent = true;
m_sLoopState.timerCV.notify_all();
g_pRenderer->asyncResourceGatherer->notify();
g_pRenderer->asyncResourceGatherer->await();
g_pAuth->terminate();
exit(1);
}
@ -476,43 +447,21 @@ void CHyprlock::run() {
}
}
// do timers
m_sLoopState.timersMutex.lock();
auto timerscpy = m_vTimers;
m_sLoopState.timersMutex.unlock();
std::vector<ASP<CTimer>> 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();
processTimers();
}
const auto DPY = m_sWaylandState.display;
m_sLoopState.timerEvent = true;
m_sLoopState.timerCV.notify_all();
g_pRenderer->asyncResourceGatherer->notify();
g_pRenderer->asyncResourceGatherer->await();
m_sWaylandState = {};
dma = {};
m_vOutputs.clear();
g_pEGL.reset();
g_pRenderer.reset();
g_pSeatManager.reset();
g_asyncResourceManager.reset();
g_pRenderer.reset();
g_pEGL.reset();
wl_display_disconnect(DPY);
@ -887,6 +836,31 @@ ASP<CTimer> CHyprlock::addTimer(const std::chrono::system_clock::duration& timeo
return T;
}
void CHyprlock::processTimers() {
// do timers
m_sLoopState.timersMutex.lock();
auto timerscpy = m_vTimers;
m_sLoopState.timersMutex.unlock();
std::vector<ASP<CTimer>> 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();
}
std::vector<ASP<CTimer>> CHyprlock::getTimers() {
return m_vTimers;
}

View file

@ -8,10 +8,7 @@
#include "linux-dmabuf-v1.hpp"
#include "viewporter.hpp"
#include "Output.hpp"
#include "Seat.hpp"
#include "CursorShape.hpp"
#include "Timer.hpp"
#include <memory>
#include <vector>
#include <condition_variable>
#include <optional>
@ -38,6 +35,7 @@ class CHyprlock {
bool isUnlocked();
ASP<CTimer> addTimer(const std::chrono::system_clock::duration& timeout, std::function<void(ASP<CTimer> self, void* data)> cb_, void* data, bool force = false);
void processTimers();
void enqueueForceUpdateTimers();
@ -118,6 +116,9 @@ class CHyprlock {
} dma;
gbm_device* createGBMDevice(drmDevice* dev);
void addDmabufListener();
void removeDmabufListener();
private:
struct {
wl_display* display = nullptr;
@ -130,8 +131,6 @@ class CHyprlock {
SP<CCWlShm> shm = nullptr;
} m_sWaylandState;
void addDmabufListener();
struct {
SP<CCExtSessionLockV1> lock = nullptr;
} m_sLockState;

View file

@ -7,6 +7,9 @@
using namespace Hyprutils::Memory;
using namespace Hyprgraphics;
using ResourceID = size_t;
#define SP CSharedPointer
#define WP CWeakPointer
#define UP CUniquePointer

View file

@ -61,7 +61,7 @@ template <Animable VarType>
using CAnimatedVariable = Hyprutils::Animation::CGenericAnimatedVariable<VarType, SAnimationContext>;
template <Animable VarType>
using PHLANIMVAR = SP<CAnimatedVariable<VarType>>;
using PHLANIMVAR = UP<CAnimatedVariable<VarType>>;
template <Animable VarType>
using PHLANIMVARREF = WP<CAnimatedVariable<VarType>>;

View file

@ -51,4 +51,4 @@ namespace Debug {
std::println("[{}] {}", logLevelString(level), std::vformat(fmt, std::make_format_args(args...)));
}
}
};
};

View file

@ -1,368 +0,0 @@
#include "AsyncResourceGatherer.hpp"
#include "../config/ConfigManager.hpp"
#include "../core/Egl.hpp"
#include "../core/hyprlock.hpp"
#include "../helpers/Color.hpp"
#include "../helpers/Log.hpp"
#include "../helpers/MiscFunctions.hpp"
#include <algorithm>
#include <cairo/cairo.h>
#include <filesystem>
#include <pango/pangocairo.h>
#include <sys/eventfd.h>
#include <hyprgraphics/image/Image.hpp>
#include <hyprutils/os/FileDescriptor.hpp>
using namespace Hyprgraphics;
using namespace Hyprutils::OS;
CAsyncResourceGatherer::CAsyncResourceGatherer() {
if (g_pHyprlock->getScreencopy())
enqueueScreencopyFrames();
initialGatherThread = std::thread([this]() { this->gather(); });
asyncLoopThread = std::thread([this]() { this->asyncAssetSpinLock(); });
gatheredEventfd = CFileDescriptor{eventfd(0, EFD_CLOEXEC)};
if (!gatheredEventfd.isValid())
Debug::log(ERR, "Failed to create eventfd: {}", strerror(errno));
}
void CAsyncResourceGatherer::enqueueScreencopyFrames() {
static const auto ANIMATIONSENABLED = g_pConfigManager->getValue<Hyprlang::INT>("animations:enabled");
const auto FADEINCFG = g_pConfigManager->m_AnimationTree.getConfig("fadeIn");
const auto FADEOUTCFG = g_pConfigManager->m_AnimationTree.getConfig("fadeOut");
const bool FADENEEDSSC = *ANIMATIONSENABLED &&
((FADEINCFG->pValues && FADEINCFG->pValues->internalEnabled) || // fadeIn or fadeOut enabled
(FADEOUTCFG->pValues && FADEOUTCFG->pValues->internalEnabled));
const auto BGSCREENSHOT = std::ranges::any_of(g_pConfigManager->getWidgetConfigs(), [](const auto& w) { //
return w.type == "background" && std::string{std::any_cast<Hyprlang::STRING>(w.values.at("path"))} == "screenshot";
});
if (!BGSCREENSHOT && !FADENEEDSSC) {
Debug::log(LOG, "Skipping screencopy");
return;
}
for (const auto& MON : g_pHyprlock->m_vOutputs) {
scframes.emplace_back(makeUnique<CScreencopyFrame>(MON));
}
}
SPreloadedAsset* CAsyncResourceGatherer::getAssetByID(const std::string& id) {
if (id.contains(CScreencopyFrame::RESOURCEIDPREFIX)) {
for (auto& frame : scframes) {
if (id == frame->m_resourceID)
return frame->m_asset.ready ? &frame->m_asset : nullptr;
}
return nullptr;
}
for (auto& a : assets) {
if (a.first == id)
return &a.second;
}
if (apply()) {
for (auto& a : assets) {
if (a.first == id)
return &a.second;
}
};
return nullptr;
}
static SP<CCairoSurface> getCairoSurfaceFromImageFile(const std::filesystem::path& path) {
auto image = CImage(path);
if (!image.success()) {
Debug::log(ERR, "Image {} could not be loaded: {}", path.string(), image.getError());
return nullptr;
}
return image.cairoSurface();
}
void CAsyncResourceGatherer::gather() {
const auto CWIDGETS = g_pConfigManager->getWidgetConfigs();
g_pEGL->makeCurrent(nullptr);
// gather resources to preload
// clang-format off
int preloads = std::count_if(CWIDGETS.begin(), CWIDGETS.end(), [](const auto& w) {
return w.type == "background" || w.type == "image";
});
// clang-format on
progress = 0;
for (auto& c : CWIDGETS) {
if (c.type == "background" || c.type == "image") {
#if defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 180100
progress = progress + 1.0 / (preloads + 1.0);
#else
progress += 1.0 / (preloads + 1.0);
#endif
std::string path = std::any_cast<Hyprlang::STRING>(c.values.at("path"));
if (path.empty() || path == "screenshot")
continue;
std::string id = (c.type == "background" ? std::string{"background:"} : std::string{"image:"}) + path;
// render the image directly, since we are in a seperate thread
CAsyncResourceGatherer::SPreloadRequest rq;
rq.type = CAsyncResourceGatherer::TARGET_IMAGE;
rq.asset = path;
rq.id = id;
renderImage(rq);
}
}
while (!g_pHyprlock->m_bTerminate && std::ranges::any_of(scframes, [](const auto& d) { return !d->m_asset.ready; })) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
gathered = true;
// wake hyprlock from poll
if (gatheredEventfd.isValid())
eventfd_write(gatheredEventfd.get(), 1);
}
bool CAsyncResourceGatherer::apply() {
preloadTargetsMutex.lock();
if (preloadTargets.empty()) {
preloadTargetsMutex.unlock();
return false;
}
auto currentPreloadTargets = preloadTargets;
preloadTargets.clear();
preloadTargetsMutex.unlock();
for (auto& t : currentPreloadTargets) {
if (t.type == TARGET_IMAGE) {
const auto ASSET = &assets[t.id];
const cairo_status_t SURFACESTATUS = (cairo_status_t)t.cairosurface->status();
const auto CAIROFORMAT = cairo_image_surface_get_format(t.cairosurface->cairo());
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;
if (SURFACESTATUS != CAIRO_STATUS_SUCCESS) {
Debug::log(ERR, "Resource {} invalid ({})", t.id, cairo_status_to_string(SURFACESTATUS));
ASSET->texture.m_iType = TEXTURE_INVALID;
}
ASSET->texture.m_vSize = t.size;
ASSET->texture.allocate();
glBindTexture(GL_TEXTURE_2D, ASSET->texture.m_iTexID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
if (CAIROFORMAT != CAIRO_FORMAT_RGB96F) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
}
glTexImage2D(GL_TEXTURE_2D, 0, glIFormat, ASSET->texture.m_vSize.x, ASSET->texture.m_vSize.y, 0, glFormat, glType, t.data);
cairo_destroy((cairo_t*)t.cairo);
t.cairosurface.reset();
} else
Debug::log(ERR, "Unsupported type in ::apply(): {}", (int)t.type);
}
return true;
}
void CAsyncResourceGatherer::renderImage(const SPreloadRequest& rq) {
SPreloadTarget target;
target.type = TARGET_IMAGE;
target.id = rq.id;
std::filesystem::path ABSOLUTEPATH(absolutePath(rq.asset, ""));
const auto CAIROISURFACE = getCairoSurfaceFromImageFile(ABSOLUTEPATH);
if (!CAIROISURFACE) {
Debug::log(ERR, "renderImage: No cairo surface!");
return;
}
const auto CAIRO = cairo_create(CAIROISURFACE->cairo());
cairo_scale(CAIRO, 1, 1);
target.cairo = CAIRO;
target.cairosurface = CAIROISURFACE;
target.data = CAIROISURFACE->data();
target.size = CAIROISURFACE->size();
std::lock_guard lg{preloadTargetsMutex};
preloadTargets.push_back(target);
}
void CAsyncResourceGatherer::renderText(const SPreloadRequest& rq) {
SPreloadTarget target;
target.type = TARGET_IMAGE; /* text is just an image lol */
target.id = rq.id;
const int FONTSIZE = rq.props.contains("font_size") ? std::any_cast<int>(rq.props.at("font_size")) : 16;
const CHyprColor FONTCOLOR = rq.props.contains("color") ? std::any_cast<CHyprColor>(rq.props.at("color")) : CHyprColor(1.0, 1.0, 1.0, 1.0);
const std::string FONTFAMILY = rq.props.contains("font_family") ? std::any_cast<std::string>(rq.props.at("font_family")) : "Sans";
const bool ISCMD = rq.props.contains("cmd") ? std::any_cast<bool>(rq.props.at("cmd")) : false;
static const auto TRIM = g_pConfigManager->getValue<Hyprlang::INT>("general:text_trim");
std::string text = ISCMD ? spawnSync(rq.asset) : rq.asset;
if (*TRIM) {
text.erase(0, text.find_first_not_of(" \n\r\t"));
text.erase(text.find_last_not_of(" \n\r\t") + 1);
}
auto CAIROSURFACE = makeShared<CCairoSurface>(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1920, 1080 /* dummy value */));
auto CAIRO = cairo_create(CAIROSURFACE->cairo());
// draw title using Pango
PangoLayout* layout = pango_cairo_create_layout(CAIRO);
PangoFontDescription* fontDesc = pango_font_description_from_string(FONTFAMILY.c_str());
pango_font_description_set_size(fontDesc, FONTSIZE * PANGO_SCALE);
pango_layout_set_font_description(layout, fontDesc);
pango_font_description_free(fontDesc);
if (rq.props.contains("text_align")) {
const std::string TEXTALIGN = std::any_cast<std::string>(rq.props.at("text_align"));
PangoAlignment align = PANGO_ALIGN_LEFT;
if (TEXTALIGN == "center")
align = PANGO_ALIGN_CENTER;
else if (TEXTALIGN == "right")
align = PANGO_ALIGN_RIGHT;
pango_layout_set_alignment(layout, align);
}
PangoAttrList* attrList = nullptr;
GError* gError = nullptr;
char* buf = nullptr;
if (pango_parse_markup(text.c_str(), -1, 0, &attrList, &buf, nullptr, &gError))
pango_layout_set_text(layout, buf, -1);
else {
Debug::log(ERR, "Pango markup parsing for {} failed: {}", text, gError->message);
g_error_free(gError);
pango_layout_set_text(layout, text.c_str(), -1);
}
if (!attrList)
attrList = pango_attr_list_new();
if (buf)
free(buf);
pango_attr_list_insert(attrList, pango_attr_scale_new(1));
pango_layout_set_attributes(layout, attrList);
pango_attr_list_unref(attrList);
int layoutWidth, layoutHeight;
pango_layout_get_size(layout, &layoutWidth, &layoutHeight);
// TODO: avoid this?
cairo_destroy(CAIRO);
CAIROSURFACE = makeShared<CCairoSurface>(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, layoutWidth / PANGO_SCALE, layoutHeight / PANGO_SCALE));
CAIRO = cairo_create(CAIROSURFACE->cairo());
// clear the pixmap
cairo_save(CAIRO);
cairo_set_operator(CAIRO, CAIRO_OPERATOR_CLEAR);
cairo_paint(CAIRO);
cairo_restore(CAIRO);
// render the thing
cairo_set_source_rgba(CAIRO, FONTCOLOR.r, FONTCOLOR.g, FONTCOLOR.b, FONTCOLOR.a);
cairo_move_to(CAIRO, 0, 0);
pango_cairo_show_layout(CAIRO, layout);
g_object_unref(layout);
cairo_surface_flush(CAIROSURFACE->cairo());
target.cairo = CAIRO;
target.cairosurface = CAIROSURFACE;
target.data = CAIROSURFACE->data();
target.size = {layoutWidth / (double)PANGO_SCALE, layoutHeight / (double)PANGO_SCALE};
std::lock_guard lg{preloadTargetsMutex};
preloadTargets.push_back(target);
}
void CAsyncResourceGatherer::asyncAssetSpinLock() {
while (!g_pHyprlock->m_bTerminate) {
std::unique_lock lk(asyncLoopState.requestsMutex);
if (!asyncLoopState.pending) // avoid a lock if a thread managed to request something already since we .unlock()ed
asyncLoopState.requestsCV.wait_for(lk, std::chrono::seconds(5), [this] { return asyncLoopState.pending; }); // wait for events
asyncLoopState.pending = false;
if (asyncLoopState.requests.empty()) {
lk.unlock();
continue;
}
auto requests = asyncLoopState.requests;
asyncLoopState.requests.clear();
lk.unlock();
// process requests
for (auto& r : requests) {
Debug::log(TRACE, "Processing requested resourceID {}", r.id);
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;
}
// plant timer for callback
if (r.callback)
g_pHyprlock->addTimer(std::chrono::milliseconds(0), [cb = r.callback](auto, auto) { cb(); }, nullptr);
}
}
}
void CAsyncResourceGatherer::requestAsyncAssetPreload(const SPreloadRequest& request) {
Debug::log(TRACE, "Requesting label resource {}", request.id);
std::lock_guard<std::mutex> lg(asyncLoopState.requestsMutex);
asyncLoopState.requests.push_back(request);
asyncLoopState.pending = true;
asyncLoopState.requestsCV.notify_all();
}
void CAsyncResourceGatherer::unloadAsset(SPreloadedAsset* asset) {
std::erase_if(assets, [asset](const auto& a) { return &a.second == asset; });
}
void CAsyncResourceGatherer::notify() {
std::lock_guard<std::mutex> lg(asyncLoopState.requestsMutex);
asyncLoopState.requests.clear();
asyncLoopState.pending = true;
asyncLoopState.requestsCV.notify_all();
}
void CAsyncResourceGatherer::await() {
if (initialGatherThread.joinable())
initialGatherThread.join();
if (asyncLoopThread.joinable())
asyncLoopThread.join();
}

View file

@ -1,88 +0,0 @@
#pragma once
#include "Screencopy.hpp"
#include <thread>
#include <atomic>
#include <vector>
#include <unordered_map>
#include <condition_variable>
#include <any>
#include "Shared.hpp"
#include <hyprgraphics/cairo/CairoSurface.hpp>
#include <hyprutils/os/FileDescriptor.hpp>
class CAsyncResourceGatherer {
public:
CAsyncResourceGatherer();
std::atomic<bool> gathered = false;
Hyprutils::OS::CFileDescriptor gatheredEventfd;
std::atomic<float> progress = 0;
/* only call from ogl thread */
SPreloadedAsset* getAssetByID(const std::string& id);
bool apply();
enum eTargetType {
TARGET_IMAGE = 0,
TARGET_TEXT
};
struct SPreloadRequest {
eTargetType type;
std::string asset;
std::string id;
std::unordered_map<std::string, std::any> props;
// optional. Callbacks will be dispatched from the main thread,
// so wayland/gl calls are OK.
// will fire once the resource is fully loaded and ready.
std::function<void()> callback = nullptr;
};
void requestAsyncAssetPreload(const SPreloadRequest& request);
void unloadAsset(SPreloadedAsset* asset);
void notify();
void await();
private:
std::thread asyncLoopThread;
std::thread initialGatherThread;
void asyncAssetSpinLock();
void renderText(const SPreloadRequest& rq);
void renderImage(const SPreloadRequest& rq);
struct {
std::condition_variable requestsCV;
std::mutex requestsMutex;
std::vector<SPreloadRequest> requests;
bool pending = false;
bool busy = false;
} asyncLoopState;
struct SPreloadTarget {
eTargetType type = TARGET_IMAGE;
std::string id = "";
void* data = nullptr;
void* cairo = nullptr;
SP<Hyprgraphics::CCairoSurface> cairosurface;
Vector2D size;
};
std::vector<UP<CScreencopyFrame>> scframes;
std::vector<SPreloadTarget> preloadTargets;
std::mutex preloadTargetsMutex;
std::unordered_map<std::string, SPreloadedAsset> assets;
void gather();
void enqueueScreencopyFrames();
};

View file

@ -0,0 +1,351 @@
#include "AsyncResourceManager.hpp"
#include "./resources/TextCmdResource.hpp"
#include "../helpers/Log.hpp"
#include "../helpers/MiscFunctions.hpp"
#include "../core/hyprlock.hpp"
#include "../config/ConfigManager.hpp"
#include <algorithm>
#include <cstdint>
#include <functional>
#include <sys/eventfd.h>
#include <sys/poll.h>
using namespace Hyprgraphics;
using namespace Hyprutils::OS;
static inline ResourceID scopeResourceID(uint8_t scope, size_t in) {
return (in & ~0x0f) | scope;
}
ResourceID CAsyncResourceManager::resourceIDForTextRequest(const CTextResource::STextResourceData& s) {
// TODO: Currently ignores the font string and resulting distribution is probably not perfect.
const auto H1 = std::hash<std::string>{}(s.text);
const auto H2 = std::hash<double>{}(s.color.asRgb().r);
const auto H3 = std::hash<double>{}(s.color.asRgb().g) + s.fontSize;
const auto H4 = std::hash<double>{}(s.color.asRgb().b) + s.align;
return scopeResourceID(1, H1 ^ (H2 << 1) ^ (H3 << 2) ^ (H4 << 3));
}
ResourceID CAsyncResourceManager::resourceIDForTextCmdRequest(const CTextResource::STextResourceData& s, size_t revision) {
return scopeResourceID(2, resourceIDForTextRequest(s) ^ (revision << 32));
}
ResourceID CAsyncResourceManager::resourceIDForImageRequest(const std::string& path, size_t revision) {
return scopeResourceID(3, std::hash<std::string>{}(path) ^ (revision << 32));
}
ResourceID CAsyncResourceManager::resourceIDForScreencopy(const std::string& port) {
return scopeResourceID(4, std::hash<std::string>{}(port));
}
ResourceID CAsyncResourceManager::requestText(const CTextResource::STextResourceData& params, const AWP<IWidget>& widget) {
const auto RESOURCEID = resourceIDForTextRequest(params);
if (request(RESOURCEID, widget)) {
Debug::log(TRACE, "Reusing text resource \"{}\" (resourceID: {})", params.text, RESOURCEID, (uintptr_t)widget.get());
return RESOURCEID;
}
auto resource = makeAtomicShared<CTextResource>(CTextResource::STextResourceData{params});
CAtomicSharedPointer<IAsyncResource> resourceGeneric{resource};
Debug::log(TRACE, "Requesting text resource \"{}\" (resourceID: {})", params.text, RESOURCEID, (uintptr_t)widget.get());
enqueue(RESOURCEID, resourceGeneric, widget);
return RESOURCEID;
}
ResourceID CAsyncResourceManager::requestTextCmd(const CTextResource::STextResourceData& params, size_t revision, const AWP<IWidget>& widget) {
const auto RESOURCEID = resourceIDForTextCmdRequest(params, revision);
if (request(RESOURCEID, widget)) {
Debug::log(TRACE, "Reusing text cmd resource \"{}\" revision {} (resourceID: {})", params.text, revision, RESOURCEID, (uintptr_t)widget.get());
return RESOURCEID;
}
auto resource = makeAtomicShared<CTextCmdResource>(CTextResource::STextResourceData{params});
CAtomicSharedPointer<IAsyncResource> resourceGeneric{resource};
Debug::log(TRACE, "Requesting text cmd resource \"{}\" revision {} (resourceID: {})", params.text, revision, RESOURCEID, (uintptr_t)widget.get());
enqueue(RESOURCEID, resourceGeneric, widget);
return RESOURCEID;
}
ResourceID CAsyncResourceManager::requestImage(const std::string& path, size_t revision, const AWP<IWidget>& widget) {
const auto RESOURCEID = resourceIDForImageRequest(path, revision);
if (request(RESOURCEID, widget)) {
Debug::log(TRACE, "Reusing image resource {} revision {} (resourceID: {})", path, revision, RESOURCEID, (uintptr_t)widget.get());
return RESOURCEID;
}
auto resource = makeAtomicShared<CImageResource>(absolutePath(path, ""));
CAtomicSharedPointer<IAsyncResource> resourceGeneric{resource};
Debug::log(TRACE, "Requesting image resource {} revision {} (resourceID: {})", path, revision, RESOURCEID, (uintptr_t)widget.get());
enqueue(RESOURCEID, resourceGeneric, widget);
return RESOURCEID;
}
ASP<CTexture> CAsyncResourceManager::getAssetByID(size_t id) {
if (!m_assets.contains(id))
return nullptr;
return m_assets[id].texture;
}
void CAsyncResourceManager::enqueueStaticAssets() {
const auto CWIDGETS = g_pConfigManager->getWidgetConfigs();
for (auto& c : CWIDGETS) {
if (c.type == "background" || c.type == "image") {
std::string path = std::any_cast<Hyprlang::STRING>(c.values.at("path"));
if (path.empty() || path == "screenshot")
continue;
requestImage(path, 0, nullptr);
}
}
}
void CAsyncResourceManager::enqueueScreencopyFrames() {
if (g_pHyprlock->m_vOutputs.empty())
return;
static const auto ANIMATIONSENABLED = g_pConfigManager->getValue<Hyprlang::INT>("animations:enabled");
const auto FADEINCFG = g_pConfigManager->m_AnimationTree.getConfig("fadeIn");
const auto FADEOUTCFG = g_pConfigManager->m_AnimationTree.getConfig("fadeOut");
const bool FADENEEDSSC = *ANIMATIONSENABLED &&
((FADEINCFG->pValues && FADEINCFG->pValues->internalEnabled) || // fadeIn or fadeOut enabled
(FADEOUTCFG->pValues && FADEOUTCFG->pValues->internalEnabled));
const auto BGSCREENSHOT = std::ranges::any_of(g_pConfigManager->getWidgetConfigs(), [](const auto& w) { //
return w.type == "background" && std::string{std::any_cast<Hyprlang::STRING>(w.values.at("path"))} == "screenshot";
});
if (!BGSCREENSHOT && !FADENEEDSSC) {
Debug::log(LOG, "Skipping screencopy");
return;
}
for (const auto& MON : g_pHyprlock->m_vOutputs) {
m_scFrames.emplace_back(makeUnique<CScreencopyFrame>());
auto* frame = m_scFrames.back().get();
frame->capture(MON);
m_assets.emplace(frame->m_resourceID, SPreloadedTexture{.texture = nullptr, .refs = 1});
}
}
void CAsyncResourceManager::screencopyToTexture(const CScreencopyFrame& scFrame) {
if (!scFrame.m_ready || !m_assets.contains(scFrame.m_resourceID)) {
Debug::log(ERR, "Bogus call to CAsyncResourceManager::screencopyToTexture. This is a bug!");
return;
}
m_assets[scFrame.m_resourceID].texture = scFrame.m_asset;
Debug::log(TRACE, "Done sc frame {}", scFrame.m_resourceID);
std::erase_if(m_scFrames, [&scFrame](const auto& f) { return f.get() == &scFrame; });
if (m_scFrames.empty()) {
Debug::log(LOG, "Gathered all screencopy frames - removing dmabuf listeners");
g_pHyprlock->removeDmabufListener();
}
}
void CAsyncResourceManager::gatherInitialResources(wl_display* display) {
const auto MAXDELAYMS = 2000; // 2 Seconds
const auto STARTGATHERTP = std::chrono::system_clock::now();
m_gatheredEventfd = CFileDescriptor{eventfd(0, EFD_CLOEXEC)};
int fdcount = 1;
pollfd pollfds[2];
pollfds[0] = {
.fd = wl_display_get_fd(display),
.events = POLLIN,
};
if (m_gatheredEventfd.isValid()) {
pollfds[1] = {
.fd = m_gatheredEventfd.get(),
.events = POLLIN,
};
fdcount++;
}
bool gathered = false;
while (!gathered) {
wl_display_flush(display);
if (wl_display_prepare_read(display) == 0) {
if (poll(pollfds, fdcount, /* 100ms timeout */ 100) < 0) {
RASSERT(errno == EINTR, "[core] Polling fds failed with {}", errno);
wl_display_cancel_read(display);
continue;
}
wl_display_read_events(display);
wl_display_dispatch_pending(display);
} else {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
wl_display_dispatch(display);
}
g_pHyprlock->processTimers();
if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - STARTGATHERTP).count() > MAXDELAYMS) {
Debug::log(WARN, "Gathering resources timed out after {} milliseconds. Backgrounds may be delayed and render `background:color` at first.", MAXDELAYMS);
break;
}
gathered = m_resources.empty() && m_scFrames.empty();
}
Debug::log(LOG, "Resources gathered after {} milliseconds", std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - STARTGATHERTP).count());
}
bool CAsyncResourceManager::checkIdPresent(ResourceID id) {
return m_assets.contains(id);
}
void CAsyncResourceManager::unload(ASP<CTexture> texture) {
auto preload = std::ranges::find_if(m_assets, [texture](const auto& a) { return a.second.texture == texture; });
if (preload == m_assets.end())
return;
preload->second.refs--;
if (preload->second.refs == 0) {
Debug::log(TRACE, "Releasing resourceID: {}!", preload->first);
m_assets.erase(preload->first);
}
}
void CAsyncResourceManager::unloadById(ResourceID id) {
if (!m_assets.contains(id))
return;
m_assets[id].refs--;
if (m_assets[id].refs == 0) {
Debug::log(TRACE, "Releasing resourceID: {}!", id);
m_assets.erase(id);
}
}
bool CAsyncResourceManager::request(ResourceID id, const AWP<IWidget>& widget) {
if (!m_assets.contains(id)) {
// New asset!!
m_assets.emplace(id, SPreloadedTexture{.texture = nullptr, .refs = 1});
return false;
}
m_assets[id].refs++;
if (m_assets[id].texture) {
// Asset already present. Dispatch the asset callback function.
const auto& TEXTURE = m_assets[id].texture;
if (widget)
widget->onAssetUpdate(id, TEXTURE);
// TODO: add a centalized mechanism to render in one place in the event loop to avoid duplicate render calls
g_pHyprlock->addTimer(std::chrono::milliseconds(0), [](auto, auto) { g_pHyprlock->renderAllOutputs(); }, nullptr);
} else if (widget) {
// Asset currently in-flight. Add the widget reference to in order for the callback to get dispatched later.
m_resourcesMutex.lock();
if (!m_resources.contains(id)) {
Debug::log(ERR, "In-flight resourceID: {} not found! This is a bug.", id);
m_resourcesMutex.unlock();
return true;
}
m_resources[id].second.emplace_back(widget);
m_resourcesMutex.unlock();
}
return true;
}
void CAsyncResourceManager::enqueue(ResourceID resourceID, const ASP<IAsyncResource>& resource, const AWP<IWidget>& widget) {
m_gatherer.enqueue(resource);
m_resourcesMutex.lock();
if (m_resources.contains(resourceID))
Debug::log(ERR, "Resource already enqueued! This is a bug.");
m_resources[resourceID] = {resource, {widget}};
m_resourcesMutex.unlock();
resource->m_events.finished.listenStatic([resourceID]() {
g_pHyprlock->addTimer(std::chrono::milliseconds(0), [](auto, void* resourceID) { g_asyncResourceManager->onResourceFinished((size_t)resourceID); }, (void*)resourceID);
});
}
void CAsyncResourceManager::onResourceFinished(ResourceID id) {
m_resourcesMutex.lock();
if (!m_resources.contains(id)) {
m_resourcesMutex.unlock();
return;
}
const auto RESOURCE = m_resources[id].first;
const auto WIDGETS = m_resources[id].second;
m_resources.erase(id);
m_resourcesMutex.unlock();
if (!m_assets.contains(id) || m_assets[id].refs == 0) // Not referenced? Drop it
return;
if (!RESOURCE || !RESOURCE->m_asset.cairoSurface)
return;
Debug::log(TRACE, "Resource to texture id:{}", id);
const auto texture = makeAtomicShared<CTexture>();
const cairo_status_t SURFACESTATUS = (cairo_status_t)RESOURCE->m_asset.cairoSurface->status();
const auto CAIROFORMAT = cairo_image_surface_get_format(RESOURCE->m_asset.cairoSurface->cairo());
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;
if (SURFACESTATUS != CAIRO_STATUS_SUCCESS) {
Debug::log(ERR, "resourceID: {} invalid ({})", id, cairo_status_to_string(SURFACESTATUS));
texture->m_iType = TEXTURE_INVALID;
}
texture->m_vSize = RESOURCE->m_asset.pixelSize;
texture->allocate();
glBindTexture(GL_TEXTURE_2D, texture->m_iTexID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
if (CAIROFORMAT != CAIRO_FORMAT_RGB96F) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
}
glTexImage2D(GL_TEXTURE_2D, 0, glIFormat, texture->m_vSize.x, texture->m_vSize.y, 0, glFormat, glType, RESOURCE->m_asset.cairoSurface->data());
m_assets[id].texture = texture;
for (const auto& widget : WIDGETS) {
if (widget)
widget->onAssetUpdate(id, texture);
}
g_pHyprlock->renderAllOutputs();
if (!m_gathered && !g_pHyprlock->m_bImmediateRender) {
m_resourcesMutex.lock();
if (m_resources.empty()) {
m_gathered = true;
if (m_gatheredEventfd.isValid())
eventfd_write(m_gatheredEventfd.get(), 1);
m_gatheredEventfd.reset();
}
m_resourcesMutex.unlock();
}
}

View file

@ -0,0 +1,91 @@
#pragma once
#include "../defines.hpp"
#include "./Texture.hpp"
#include "./Screencopy.hpp"
#include "./widgets/IWidget.hpp"
#include <hyprgraphics/resource/AsyncResourceGatherer.hpp>
#include <hyprgraphics/resource/resources/AsyncResource.hpp>
#include <hyprgraphics/resource/resources/TextResource.hpp>
#include <hyprgraphics/resource/resources/ImageResource.hpp>
#include <hyprutils/os/FileDescriptor.hpp>
class CAsyncResourceManager {
public:
// Notes on resource lifetimes:
// Resources id's are the result of hashing the requested resource parameters.
// When a new request is made, adding a new entry to the m_assets map is done immediatly.
// Subsequent calls through this section with the same resource id will increment the texture's references.
// The manager will release the resource when refs reaches 0, while the resource itelf may outlife it's reference in the manager.
// Why not use ASP/AWP for this?
// The problem is that we want to to increment the reference as soon as requesting the resource id.
// Not only when actually retrieving the asset with `getAssetById`.
//
// Improvement idea: Make a wrapper object that is returned when requesting and contains the resource id. Then we can unload with RAII.
// Those are hash functions that return the id for a requested resource.
static ResourceID resourceIDForTextRequest(const CTextResource::STextResourceData& s);
// Consumer needs to increment the revision parameter to get a new command evaluation.
static ResourceID resourceIDForTextCmdRequest(const CTextResource::STextResourceData& s, size_t revision);
// Image paths may be file system links, thus this function supports a revision parameter that gets factored into the resource id.
static ResourceID resourceIDForImageRequest(const std::string& path, size_t revision);
static ResourceID resourceIDForScreencopy(const std::string& port);
struct SPreloadedTexture {
ASP<CTexture> texture;
size_t refs = 0;
};
CAsyncResourceManager() = default;
~CAsyncResourceManager() = default;
ResourceID requestText(const CTextResource::STextResourceData& params, const AWP<IWidget>& widget);
// Same as requestText but substitute the text with what launching sh -c request.text returns.
ResourceID requestTextCmd(const CTextResource::STextResourceData& params, size_t revision, const AWP<IWidget>& widget);
ResourceID requestImage(const std::string& path, size_t revision, const AWP<IWidget>& widget);
ASP<CTexture> getAssetByID(ResourceID id);
void unload(ASP<CTexture> resource);
void unloadById(ResourceID id);
void enqueueStaticAssets();
void enqueueScreencopyFrames();
void screencopyToTexture(const CScreencopyFrame& scFrame);
void gatherInitialResources(wl_display* display);
bool checkIdPresent(ResourceID id);
private:
// Returns whether or not the id was already requested.
// Makes sure the widgets onAssetCallback function gets called.
bool request(ResourceID id, const AWP<IWidget>& widget);
// Adds a new resource to m_resources and passes it to m_gatherer.
void enqueue(ResourceID resourceID, const ASP<IAsyncResource>& resource, const AWP<IWidget>& widget);
// Callback for finished resources.
// Copies the resources cairo surface to a GL_TEXTURE_2D and sets it in the asset map.
// Removes the entry in m_resources.
// Call onAssetUpdate for all stored widget references.
void onResourceFinished(ResourceID id);
// For polling when using gatherInitialResources.
bool m_gathered = false;
Hyprutils::OS::CFileDescriptor m_gatheredEventfd;
bool m_exit = false;
int m_loadedAssets = 0;
// not shared between threads
std::unordered_map<ResourceID, SPreloadedTexture> m_assets;
std::vector<UP<CScreencopyFrame>> m_scFrames;
// shared between threads
std::mutex m_resourcesMutex;
std::unordered_map<ResourceID, std::pair<ASP<Hyprgraphics::IAsyncResource>, std::vector<AWP<IWidget>>>> m_resources;
Hyprgraphics::CAsyncResourceGatherer m_gatherer;
};
inline UP<CAsyncResourceManager> g_asyncResourceManager;

View file

@ -25,7 +25,7 @@ inline const float fullVerts[] = {
0, 1, // bottom left
};
GLuint compileShader(const GLuint& type, std::string src) {
static GLuint compileShader(const GLuint& type, std::string src) {
auto shader = glCreateShader(type);
auto shaderSource = src.c_str();
@ -41,7 +41,7 @@ GLuint compileShader(const GLuint& type, std::string src) {
return shader;
}
GLuint createProgram(const std::string& vert, const std::string& frag) {
static GLuint createProgram(const std::string& vert, const std::string& frag) {
auto vertCompiled = compileShader(GL_VERTEX_SHADER, vert);
RASSERT(vertCompiled, "Compiling shader failed. VERTEX NULL! Shader source:\n\n{}", vert);
@ -194,8 +194,6 @@ CRenderer::CRenderer() {
borderShader.gradientLerp = glGetUniformLocation(prog, "gradientLerp");
borderShader.alpha = glGetUniformLocation(prog, "alpha");
asyncResourceGatherer = makeUnique<CAsyncResourceGatherer>();
g_pAnimationManager->createAnimation(0.f, opacity, g_pConfigManager->m_AnimationTree.getConfig("fadeIn"));
}
@ -217,18 +215,12 @@ CRenderer::SRenderFeedback CRenderer::renderLock(const CSessionLockSurface& surf
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
SRenderFeedback feedback;
const bool WAITFORASSETS = !g_pHyprlock->m_bImmediateRender && !asyncResourceGatherer->gathered;
if (!WAITFORASSETS) {
// render widgets
const auto WIDGETS = getOrCreateWidgetsFor(surf);
for (auto& w : WIDGETS) {
feedback.needsFrame = w->draw({opacity->value()}) || feedback.needsFrame;
}
// render widgets
const auto WIDGETS = getOrCreateWidgetsFor(surf);
for (auto& w : WIDGETS) {
feedback.needsFrame = w->draw({opacity->value()}) || feedback.needsFrame;
}
feedback.needsFrame = feedback.needsFrame || !asyncResourceGatherer->gathered;
glDisable(GL_BLEND);
return feedback;
@ -619,3 +611,7 @@ void CRenderer::startFadeOut(bool unlock) {
if (unlock)
opacity->setCallbackOnEnd([](auto) { g_pHyprlock->releaseSessionLock(); }, true);
}
void CRenderer::warpOpacity(float newOpacity) {
opacity->setValueAndWarp(newOpacity);
}

View file

@ -7,7 +7,6 @@
#include "../core/LockSurface.hpp"
#include "../helpers/AnimatedVariable.hpp"
#include "../helpers/Color.hpp"
#include "AsyncResourceGatherer.hpp"
#include "../config/ConfigDataValues.hpp"
#include "widgets/IWidget.hpp"
#include "Framebuffer.hpp"
@ -37,7 +36,6 @@ class CRenderer {
void renderTextureMix(const CBox& box, const CTexture& tex, const CTexture& tex2, float a = 1.0, float mixFactor = 0.0, int rounding = 0, std::optional<eTransform> tr = {});
void blurFB(const CFramebuffer& outfb, SBlurParams params);
UP<CAsyncResourceGatherer> asyncResourceGatherer;
std::chrono::system_clock::time_point firstFullFrameTime;
void pushFb(GLint fb);
@ -48,6 +46,7 @@ class CRenderer {
void startFadeIn();
void startFadeOut(bool unlock = false);
void warpOpacity(float warpOpacity);
std::vector<ASP<IWidget>>& getOrCreateWidgetsFor(const CSessionLockSurface& surf);
private:

View file

@ -1,4 +1,5 @@
#include "Screencopy.hpp"
#include "./AsyncResourceManager.hpp"
#include "../helpers/Log.hpp"
#include "../helpers/MiscFunctions.hpp"
#include "../core/hyprlock.hpp"
@ -23,27 +24,20 @@ static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES = nullpt
static PFNEGLQUERYDMABUFMODIFIERSEXTPROC eglQueryDmaBufModifiersEXT = nullptr;
//
std::string CScreencopyFrame::getResourceId(SP<COutput> pOutput) {
return RESOURCEIDPREFIX + std::format(":{}-{}x{}", pOutput->stringPort, pOutput->size.x, pOutput->size.y);
}
CScreencopyFrame::CScreencopyFrame(SP<COutput> pOutput) : m_outputRef(pOutput) {
captureOutput();
void CScreencopyFrame::capture(SP<COutput> pOutput) {
RASSERT(pOutput, "Screencopy, but no valid output");
static const auto SCMODE = g_pConfigManager->getValue<Hyprlang::INT>("general:screencopy_mode");
m_asset = makeAtomicShared<CTexture>();
m_resourceID = CAsyncResourceManager::resourceIDForScreencopy(pOutput->stringPort);
m_sc = makeShared<CCZwlrScreencopyFrameV1>(g_pHyprlock->getScreencopy()->sendCaptureOutput(false, pOutput->m_wlOutput->resource()));
if (*SCMODE == 1)
m_frame = makeUnique<CSCSHMFrame>(m_sc);
else
m_frame = makeUnique<CSCDMAFrame>(m_sc);
}
void CScreencopyFrame::captureOutput() {
const auto POUTPUT = m_outputRef.lock();
RASSERT(POUTPUT, "Screencopy, but no valid output");
m_resourceID = getResourceId(POUTPUT);
m_sc = makeShared<CCZwlrScreencopyFrameV1>(g_pHyprlock->getScreencopy()->sendCaptureOutput(false, POUTPUT->m_wlOutput->resource()));
m_sc->setBufferDone([this](CCZwlrScreencopyFrameV1* r) {
Debug::log(TRACE, "[sc] wlrOnBufferDone for {}", (void*)this);
@ -73,6 +67,8 @@ void CScreencopyFrame::captureOutput() {
}
m_sc.reset();
m_ready = true;
g_asyncResourceManager->screencopyToTexture(*this);
});
}
@ -80,11 +76,21 @@ CSCDMAFrame::CSCDMAFrame(SP<CCZwlrScreencopyFrameV1> sc) : m_sc(sc) {
if (!glEGLImageTargetTexture2DOES) {
glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES");
if (!glEGLImageTargetTexture2DOES) {
Debug::log(ERR, "No glEGLImageTargetTexture2DOES??");
Debug::log(ERR, "[sc] No glEGLImageTargetTexture2DOES??");
return;
}
}
if (!g_pHyprlock->dma.linuxDmabuf) {
Debug::log(ERR, "[sc] No DMABUF support?");
return;
}
if (!g_pHyprlock->dma.gbmDevice) {
Debug::log(ERR, "[sc] No gbmDevice for DMABUF was created?");
return;
}
if (!eglQueryDmaBufModifiersEXT)
eglQueryDmaBufModifiersEXT = (PFNEGLQUERYDMABUFMODIFIERSEXTPROC)eglGetProcAddress("eglQueryDmaBufModifiersEXT");
@ -191,7 +197,7 @@ bool CSCDMAFrame::onBufferDone() {
return true;
}
bool CSCDMAFrame::onBufferReady(SPreloadedAsset& asset) {
bool CSCDMAFrame::onBufferReady(ASP<CTexture> texture) {
static constexpr struct {
EGLAttrib fd;
EGLAttrib offset;
@ -245,9 +251,9 @@ bool CSCDMAFrame::onBufferReady(SPreloadedAsset& asset) {
return false;
}
asset.texture.allocate();
asset.texture.m_vSize = {m_w, m_h};
glBindTexture(GL_TEXTURE_2D, asset.texture.m_iTexID);
texture->allocate();
texture->m_vSize = {m_w, m_h};
glBindTexture(GL_TEXTURE_2D, texture->m_iTexID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
@ -255,9 +261,7 @@ bool CSCDMAFrame::onBufferReady(SPreloadedAsset& asset) {
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, m_image);
glBindTexture(GL_TEXTURE_2D, 0);
Debug::log(LOG, "Got dma frame with size {}", asset.texture.m_vSize);
asset.ready = true;
Debug::log(LOG, "Got dma frame with size {}", texture->m_vSize);
return true;
}
@ -449,14 +453,14 @@ void CSCSHMFrame::convertBuffer() {
}
}
bool CSCSHMFrame::onBufferReady(SPreloadedAsset& asset) {
bool CSCSHMFrame::onBufferReady(ASP<CTexture> texture) {
convertBuffer();
asset.texture.allocate();
asset.texture.m_vSize.x = m_w;
asset.texture.m_vSize.y = m_h;
texture->allocate();
texture->m_vSize.x = m_w;
texture->m_vSize.y = m_h;
glBindTexture(GL_TEXTURE_2D, asset.texture.m_iTexID);
glBindTexture(GL_TEXTURE_2D, texture->m_iTexID);
void* buffer = m_convBuffer ? m_convBuffer : m_shmData;
@ -467,9 +471,7 @@ bool CSCSHMFrame::onBufferReady(SPreloadedAsset& asset) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_w, m_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
glBindTexture(GL_TEXTURE_2D, 0);
Debug::log(LOG, "[sc] [shm] Got screenshot with size {}", asset.texture.m_vSize);
asset.ready = true;
Debug::log(LOG, "[sc] [shm] Got screenshot with size {}", texture->m_vSize);
return true;
}

View file

@ -2,10 +2,9 @@
#include "../defines.hpp"
#include "../core/Output.hpp"
#include "../renderer/Texture.hpp"
#include <cstdint>
#include <gbm.h>
#include <memory>
#include "Shared.hpp"
#include "linux-dmabuf-v1.hpp"
#include "wlr-screencopy-unstable-v1.hpp"
@ -14,29 +13,27 @@ class ISCFrame {
ISCFrame() = default;
virtual ~ISCFrame() = default;
virtual bool onBufferDone() = 0;
virtual bool onBufferReady(SPreloadedAsset& asset) = 0;
virtual bool onBufferDone() = 0;
virtual bool onBufferReady(ASP<CTexture> asset) = 0;
SP<CCWlBuffer> m_wlBuffer = nullptr;
};
class CScreencopyFrame {
public:
static std::string getResourceId(SP<COutput> pOutput);
static constexpr const std::string RESOURCEIDPREFIX = "screencopy";
CScreencopyFrame(SP<COutput> pOutput);
CScreencopyFrame() = default;
~CScreencopyFrame() = default;
void captureOutput();
void capture(SP<COutput> pOutput);
SP<CCZwlrScreencopyFrameV1> m_sc = nullptr;
std::string m_resourceID;
SPreloadedAsset m_asset;
size_t m_resourceID;
ASP<CTexture> m_asset;
bool m_ready = false;
private:
WP<COutput> m_outputRef;
UP<ISCFrame> m_frame = nullptr;
bool m_dmaFailed = false;
@ -48,7 +45,7 @@ class CSCDMAFrame : public ISCFrame {
CSCDMAFrame(SP<CCZwlrScreencopyFrameV1> sc);
virtual ~CSCDMAFrame();
virtual bool onBufferReady(SPreloadedAsset& asset);
virtual bool onBufferReady(ASP<CTexture> asset);
virtual bool onBufferDone();
private:
@ -78,7 +75,7 @@ class CSCSHMFrame : public ISCFrame {
virtual bool onBufferDone() {
return m_ok;
}
virtual bool onBufferReady(SPreloadedAsset& asset);
virtual bool onBufferReady(ASP<CTexture> texture);
void convertBuffer();
private:

View file

@ -0,0 +1,31 @@
#include "TextCmdResource.hpp"
#include "../../config/ConfigManager.hpp"
#include "../../helpers/MiscFunctions.hpp"
#include <hyprgraphics/resource/resources/TextResource.hpp>
using namespace Hyprgraphics;
CTextCmdResource::CTextCmdResource(CTextResource::STextResourceData&& data) : m_data(std::move(data)) {
;
}
void CTextCmdResource::render() {
static const auto TRIM = g_pConfigManager->getValue<Hyprlang::INT>("general:text_trim");
CTextResource::STextResourceData textData = m_data;
textData.text = spawnSync(m_data.text);
if (*TRIM) {
textData.text.erase(0, textData.text.find_first_not_of(" \n\r\t"));
textData.text.erase(textData.text.find_last_not_of(" \n\r\t") + 1);
}
Hyprgraphics::CTextResource textResource(std::move(textData));
textResource.render();
std::swap(m_asset, textResource.m_asset);
}

View file

@ -0,0 +1,15 @@
#pragma once
#include <hyprgraphics/resource/resources/AsyncResource.hpp>
#include <hyprgraphics/resource/resources/TextResource.hpp>
class CTextCmdResource : public Hyprgraphics::IAsyncResource {
public:
CTextCmdResource(Hyprgraphics::CTextResource::STextResourceData&& data);
virtual ~CTextCmdResource() = default;
virtual void render();
private:
Hyprgraphics::CTextResource::STextResourceData m_data;
};

View file

@ -1,7 +1,7 @@
#include "Background.hpp"
#include "../Renderer.hpp"
#include "../AsyncResourceManager.hpp"
#include "../Framebuffer.hpp"
#include "../Shared.hpp"
#include "../../core/hyprlock.hpp"
#include "../../helpers/Log.hpp"
#include "../../helpers/MiscFunctions.hpp"
@ -10,7 +10,6 @@
#include <chrono>
#include <hyprlang.hpp>
#include <filesystem>
#include <memory>
#include <GLES3/gl32.h>
CBackground::CBackground() {
@ -54,33 +53,32 @@ void CBackground::configure(const std::unordered_map<std::string, std::any>& pro
viewport = pOutput->getViewport();
outputPort = pOutput->stringPort;
transform = wlTransformToHyprutils(invertTransform(pOutput->transform));
scResourceID = CScreencopyFrame::getResourceId(pOutput);
scResourceID = CAsyncResourceManager::resourceIDForScreencopy(pOutput->stringPort);
g_pAnimationManager->createAnimation(0.f, crossFadeProgress, g_pConfigManager->m_AnimationTree.getConfig("fadeIn"));
// 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 && !g_pRenderer->asyncResourceGatherer->getAssetByID(scResourceID)) {
if (!g_asyncResourceManager->checkIdPresent(scResourceID)) {
Debug::log(LOG, "Missing screenshot for output {}", outputPort);
scResourceID = "";
scResourceID = 0;
}
if (isScreenshot) {
resourceID = scResourceID; // Fallback to solid background:color when scResourceID==""
resourceID = scResourceID; // Fallback to solid background:color when scResourceID==0
if (!g_pHyprlock->getScreencopy()) {
Debug::log(ERR, "No screencopy support! path=screenshot won't work. Falling back to background color.");
resourceID = "";
resourceID = 0;
}
} else if (!path.empty())
resourceID = "background:" + path;
resourceID = g_asyncResourceManager->requestImage(path, m_imageRevision, nullptr);
if (!isScreenshot && reloadTime > -1) {
if (!reloadCommand.empty() && reloadTime > -1) {
try {
modificationTime = std::filesystem::last_write_time(absolutePath(path, ""));
if (!isScreenshot)
modificationTime = std::filesystem::last_write_time(absolutePath(path, ""));
} catch (std::exception& e) { Debug::log(ERR, "{}", e.what()); }
plantReloadTimer(); // No reloads for screenshots.
plantReloadTimer(); // No reloads if reloadCommand is empty
}
}
@ -95,16 +93,16 @@ void CBackground::reset() {
}
void CBackground::updatePrimaryAsset() {
if (asset || resourceID.empty())
if (asset || resourceID == 0)
return;
asset = g_pRenderer->asyncResourceGatherer->getAssetByID(resourceID);
asset = g_asyncResourceManager->getAssetByID(resourceID);
if (!asset)
return;
const bool NEEDFB = (isScreenshot || blurPasses > 0 || asset->texture.m_vSize != viewport) && (!blurredFB->isAllocated() || firstRender);
const bool NEEDFB = (isScreenshot || blurPasses > 0 || asset->m_vSize != viewport || transform != HYPRUTILS_TRANSFORM_NORMAL) && (!blurredFB->isAllocated() || firstRender);
if (NEEDFB)
renderToFB(asset->texture, *blurredFB, blurPasses);
renderToFB(*asset, *blurredFB, blurPasses, isScreenshot);
}
void CBackground::updatePendingAsset() {
@ -112,21 +110,21 @@ void CBackground::updatePendingAsset() {
if (!pendingAsset || blurPasses == 0 || pendingBlurredFB->isAllocated())
return;
renderToFB(pendingAsset->texture, *pendingBlurredFB, blurPasses);
renderToFB(*pendingAsset, *pendingBlurredFB, blurPasses);
}
void CBackground::updateScAsset() {
if (scAsset || scResourceID.empty())
if (scAsset || scResourceID == 0)
return;
// path=screenshot -> scAsset = asset
scAsset = (asset && isScreenshot) ? asset : g_pRenderer->asyncResourceGatherer->getAssetByID(scResourceID);
scAsset = (asset && isScreenshot) ? asset : g_asyncResourceManager->getAssetByID(scResourceID);
if (!scAsset)
return;
const bool NEEDSCTRANSFORM = transform != HYPRUTILS_TRANSFORM_NORMAL;
if (NEEDSCTRANSFORM)
renderToFB(scAsset->texture, *transformedScFB, 0, true);
renderToFB(*scAsset, *transformedScFB, 0, true);
}
const CTexture& CBackground::getPrimaryAssetTex() const {
@ -134,15 +132,15 @@ const CTexture& CBackground::getPrimaryAssetTex() const {
if (isScreenshot && blurPasses == 0 && transformedScFB->isAllocated())
return transformedScFB->m_cTex;
return (blurredFB->isAllocated()) ? blurredFB->m_cTex : asset->texture;
return (blurredFB->isAllocated()) ? blurredFB->m_cTex : *asset;
}
const CTexture& CBackground::getPendingAssetTex() const {
return (pendingBlurredFB->isAllocated()) ? pendingBlurredFB->m_cTex : pendingAsset->texture;
return (pendingBlurredFB->isAllocated()) ? pendingBlurredFB->m_cTex : *pendingAsset;
}
const CTexture& CBackground::getScAssetTex() const {
return (transformedScFB->isAllocated()) ? transformedScFB->m_cTex : scAsset->texture;
return (transformedScFB->isAllocated()) ? transformedScFB->m_cTex : *scAsset;
}
void CBackground::renderRect(CHyprColor color) {
@ -157,11 +155,6 @@ static void onReloadTimer(AWP<CBackground> ref) {
}
}
static void onAssetCallback(AWP<CBackground> ref) {
if (auto PBG = ref.lock(); PBG)
PBG->startCrossFade();
}
static CBox getScaledBoxForTextureSize(const Vector2D& size, const Vector2D& viewport) {
CBox texbox = {{}, size};
@ -219,14 +212,14 @@ bool CBackground::draw(const SRenderData& data) {
updatePendingAsset();
updateScAsset();
if (asset && asset->texture.m_iType == TEXTURE_INVALID) {
g_pRenderer->asyncResourceGatherer->unloadAsset(asset);
resourceID = "";
if (asset && asset->m_iType == TEXTURE_INVALID) {
g_asyncResourceManager->unload(asset);
resourceID = 0;
renderRect(color);
return false;
}
if (!asset || resourceID.empty()) {
if (!asset || resourceID == 0) {
// fade in/out with a solid color
if (data.opacity < 1.0 && scAsset) {
const auto& SCTEX = getScAssetTex();
@ -239,7 +232,7 @@ bool CBackground::draw(const SRenderData& data) {
}
renderRect(color);
return !asset && !resourceID.empty(); // resource not ready
return !asset && resourceID > 0; // resource not ready
}
const auto& TEX = getPrimaryAssetTex();
@ -251,11 +244,41 @@ bool CBackground::draw(const SRenderData& data) {
const auto& PENDINGTEX = getPendingAssetTex();
g_pRenderer->renderTextureMix(TEXBOX, TEX, PENDINGTEX, 1.0, crossFadeProgress->value(), 0);
} else
g_pRenderer->renderTexture(TEXBOX, TEX, 1, 0, HYPRUTILS_TRANSFORM_FLIPPED_180);
g_pRenderer->renderTexture(TEXBOX, TEX, 1, 0);
return crossFadeProgress->isBeingAnimated() || data.opacity < 1.0;
}
void CBackground::onAssetUpdate(ResourceID id, ASP<CTexture> newAsset) {
pendingResource = false;
if (!newAsset)
Debug::log(ERR, "Background asset update failed, resourceID: {} not available on update!", id);
else if (newAsset->m_iType == TEXTURE_INVALID) {
g_asyncResourceManager->unload(newAsset);
Debug::log(ERR, "New background asset has an invalid texture!");
} else {
pendingAsset = newAsset;
crossFadeProgress->setValueAndWarp(0);
*crossFadeProgress = 1.0;
crossFadeProgress->setCallbackOnEnd(
[REF = m_self, id](auto) {
if (const auto PSELF = REF.lock()) {
if (PSELF->asset)
g_asyncResourceManager->unload(PSELF->asset);
PSELF->asset = PSELF->pendingAsset;
PSELF->pendingAsset = nullptr;
PSELF->resourceID = id;
PSELF->blurredFB->destroyBuffer();
PSELF->blurredFB = std::move(PSELF->pendingBlurredFB);
}
},
true);
}
}
void CBackground::plantReloadTimer() {
if (reloadTime == 0)
@ -288,58 +311,22 @@ void CBackground::onReloadTimerUpdate() {
return;
modificationTime = MTIME;
if (OLDPATH == path)
m_imageRevision++;
else
m_imageRevision = 0;
} catch (std::exception& e) {
path = OLDPATH;
Debug::log(ERR, "{}", e.what());
return;
}
if (!pendingResourceID.empty())
if (pendingResource)
return;
pendingResource = true;
// 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 = [REF = m_self]() { onAssetCallback(REF); };
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
}
void CBackground::startCrossFade() {
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!");
pendingResourceID = "";
} else if (resourceID != pendingResourceID) {
pendingAsset = newAsset;
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);
}
},
true);
g_pHyprlock->renderOutput(outputPort);
}
} else if (!pendingResourceID.empty()) {
Debug::log(WARN, "Asset {} not available after the asyncResourceGatherer's callback!", pendingResourceID);
g_pHyprlock->addTimer(std::chrono::milliseconds(100), [REF = m_self](auto, auto) { onAssetCallback(REF); }, nullptr);
}
AWP<IWidget> widget(m_self);
g_asyncResourceManager->requestImage(path, m_imageRevision, widget);
}

View file

@ -1,18 +1,15 @@
#pragma once
#include "IWidget.hpp"
#include "../../defines.hpp"
#include "../../helpers/AnimatedVariable.hpp"
#include "../../helpers/Color.hpp"
#include "../../helpers/Math.hpp"
#include "../../core/Timer.hpp"
#include "../Framebuffer.hpp"
#include "../AsyncResourceGatherer.hpp"
#include <cstdint>
#include <hyprutils/math/Misc.hpp>
#include <string>
#include <unordered_map>
#include <any>
#include <chrono>
#include <filesystem>
struct SPreloadedAsset;
@ -27,6 +24,7 @@ class CBackground : public IWidget {
virtual void configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput);
virtual bool draw(const SRenderData& data);
virtual void onAssetUpdate(ResourceID id, ASP<CTexture> newAsset);
void reset(); // Unload assets, remove timers, etc.
@ -49,39 +47,39 @@ class CBackground : public IWidget {
AWP<CBackground> m_self;
// if needed
UP<CFramebuffer> blurredFB;
UP<CFramebuffer> pendingBlurredFB;
UP<CFramebuffer> transformedScFB;
UP<CFramebuffer> blurredFB;
UP<CFramebuffer> pendingBlurredFB;
UP<CFramebuffer> transformedScFB;
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 outputPort;
Hyprutils::Math::eTransform transform;
std::string outputPort;
Hyprutils::Math::eTransform transform;
std::string resourceID;
std::string scResourceID;
std::string pendingResourceID;
ResourceID resourceID = 0;
ResourceID scResourceID = 0;
bool pendingResource = false;
PHLANIMVAR<float> crossFadeProgress;
PHLANIMVAR<float> crossFadeProgress;
CHyprColor color;
SPreloadedAsset* asset = nullptr;
SPreloadedAsset* scAsset = nullptr;
SPreloadedAsset* pendingAsset = nullptr;
bool isScreenshot = false;
bool firstRender = true;
CHyprColor color;
ASP<CTexture> asset = nullptr;
ASP<CTexture> scAsset = nullptr;
ASP<CTexture> pendingAsset = nullptr;
bool isScreenshot = false;
bool firstRender = true;
int reloadTime = -1;
std::string reloadCommand;
CAsyncResourceGatherer::SPreloadRequest request;
ASP<CTimer> reloadTimer;
std::filesystem::file_time_type modificationTime;
int reloadTime = -1;
std::string reloadCommand;
ASP<CTimer> reloadTimer;
std::filesystem::file_time_type modificationTime;
size_t m_imageRevision = 0;
};

View file

@ -3,6 +3,7 @@
#include "../../core/hyprlock.hpp"
#include "../../auth/Auth.hpp"
#include <chrono>
#include <hyprgraphics/resource/resources/TextResource.hpp>
#include <unistd.h>
#include <pwd.h>
#include <hyprutils/string/String.hpp>
@ -76,6 +77,16 @@ int IWidget::roundingForBorderBox(const CBox& borderBox, int roundingConfig, int
return std::clamp(roundingConfig + thickness, 0, MINHALFBORDER);
}
Hyprgraphics::CTextResource::eTextAlignmentMode IWidget::parseTextAlignment(const std::string& alignment) {
Hyprgraphics::CTextResource::eTextAlignmentMode align = Hyprgraphics::CTextResource::TEXT_ALIGN_LEFT;
if (alignment == "center")
align = Hyprgraphics::CTextResource::TEXT_ALIGN_CENTER;
else if (alignment == "right")
align = Hyprgraphics::CTextResource::TEXT_ALIGN_RIGHT;
return align;
}
static void replaceAllAttempts(std::string& str) {
const size_t ATTEMPTS = g_pAuth->getFailedAttempts();
@ -175,8 +186,8 @@ static std::string getTime12h() {
IWidget::SFormatResult IWidget::formatString(std::string in) {
auto uidPassword = getpwuid(getuid());
char* username = uidPassword->pw_name;
char* user_gecos = uidPassword->pw_gecos;
char* username = uidPassword ? uidPassword->pw_name : nullptr;
char* user_gecos = uidPassword ? uidPassword->pw_gecos : nullptr;
if (!username)
Debug::log(ERR, "Error in formatString, username null. Errno: ", errno);

View file

@ -2,6 +2,10 @@
#include "../../defines.hpp"
#include "../../helpers/Math.hpp"
#include "../../core/Seat.hpp"
#include "../Texture.hpp"
#include <hyprgraphics/resource/resources/TextResource.hpp>
#include <string>
#include <unordered_map>
#include <any>
@ -16,15 +20,18 @@ class IWidget {
virtual ~IWidget() = default;
virtual void configure(const std::unordered_map<std::string, std::any>& prop, const SP<COutput>& pOutput) = 0;
virtual bool draw(const SRenderData& data) = 0;
virtual void configure(const std::unordered_map<std::string, std::any>& prop, const SP<COutput>& pOutput) = 0;
virtual bool draw(const SRenderData& data) = 0;
// Never render within onAssetUpdate!
virtual void onAssetUpdate(ResourceID id, ASP<CTexture> newAsset) = 0;
static Vector2D posFromHVAlign(const Vector2D& viewport, const Vector2D& size, const Vector2D& offset, const std::string& halign, const std::string& valign,
const double& ang = 0);
static int roundingForBox(const CBox& box, int roundingConfig);
static int roundingForBorderBox(const CBox& borderBox, int roundingConfig, int thickness);
static Hyprgraphics::CTextResource::eTextAlignmentMode parseTextAlignment(const std::string& alignment);
virtual CBox getBoundingBoxWl() const {
virtual CBox getBoundingBoxWl() const {
return CBox();
};
virtual void onClick(uint32_t button, bool down, const Vector2D& pos) {}

View file

@ -1,5 +1,6 @@
#include "Image.hpp"
#include "../Renderer.hpp"
#include "../AsyncResourceManager.hpp"
#include "../../core/hyprlock.hpp"
#include "../../helpers/Log.hpp"
#include "../../helpers/MiscFunctions.hpp"
@ -23,12 +24,12 @@ static void onTimer(AWP<CImage> ref) {
}
}
static void onAssetCallback(AWP<CImage> ref) {
if (auto PIMAGE = ref.lock(); PIMAGE)
PIMAGE->renderUpdate();
}
void CImage::onTimerUpdate() {
if (m_pendingResource) {
Debug::log(WARN, "Trying to update image, but a resource is still pending! Skipping update.");
return;
}
const std::string OLDPATH = path;
if (!reloadCommand.empty()) {
@ -50,22 +51,20 @@ void CImage::onTimerUpdate() {
return;
modificationTime = MTIME;
if (OLDPATH == path)
m_imageRevision++;
else
m_imageRevision = 0;
} catch (std::exception& e) {
path = OLDPATH;
Debug::log(ERR, "{}", e.what());
return;
}
if (!pendingResourceID.empty())
return;
m_pendingResource = true;
request.id = std::string{"image:"} + 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 = [REF = m_self]() { onAssetCallback(REF); };
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
AWP<IWidget> widget(m_self);
g_asyncResourceManager->requestImage(path, m_imageRevision, widget);
}
void CImage::plantTimer() {
@ -104,7 +103,7 @@ void CImage::configure(const std::unordered_map<std::string, std::any>& props, c
RASSERT(false, "Missing propperty for CImage: {}", e.what()); //
}
resourceID = "image:" + path;
resourceID = g_asyncResourceManager->requestImage(path, m_imageRevision, nullptr);
angle = angle * M_PI / 180.0;
if (reloadTime > -1) {
@ -128,27 +127,27 @@ void CImage::reset() {
imageFB.destroyBuffer();
if (asset && reloadTime > -1) // Don't unload asset if it's a static image
g_pRenderer->asyncResourceGatherer->unloadAsset(asset);
g_asyncResourceManager->unload(asset);
asset = nullptr;
pendingResourceID = "";
resourceID = "";
m_pendingResource = false;
resourceID = 0;
}
bool CImage::draw(const SRenderData& data) {
if (resourceID.empty())
if (resourceID == 0)
return false;
if (!asset)
asset = g_pRenderer->asyncResourceGatherer->getAssetByID(resourceID);
asset = g_asyncResourceManager->getAssetByID(resourceID);
if (!asset)
return true;
if (asset->texture.m_iType == TEXTURE_INVALID) {
g_pRenderer->asyncResourceGatherer->unloadAsset(asset);
resourceID = "";
if (asset->m_iType == TEXTURE_INVALID) {
g_asyncResourceManager->unload(asset);
resourceID = 0;
return false;
}
@ -156,7 +155,7 @@ bool CImage::draw(const SRenderData& data) {
const Vector2D IMAGEPOS = {border, border};
const Vector2D BORDERPOS = {0.0, 0.0};
const Vector2D TEXSIZE = asset->texture.m_vSize;
const Vector2D TEXSIZE = asset->m_vSize;
const float SCALEX = size / TEXSIZE.x;
const float SCALEY = size / TEXSIZE.y;
@ -184,7 +183,7 @@ bool CImage::draw(const SRenderData& data) {
g_pRenderer->renderBorder(borderBox, color, border, BORDERROUND, 1.0);
texbox.round();
g_pRenderer->renderTexture(texbox, asset->texture, 1.0, ROUND, HYPRUTILS_TRANSFORM_NORMAL);
g_pRenderer->renderTexture(texbox, *asset, 1.0, ROUND, HYPRUTILS_TRANSFORM_NORMAL);
g_pRenderer->popFb();
}
@ -210,30 +209,22 @@ bool CImage::draw(const SRenderData& data) {
return data.opacity < 1.0;
}
void CImage::renderUpdate() {
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.destroyBuffer();
void CImage::onAssetUpdate(ResourceID id, ASP<CTexture> newAsset) {
m_pendingResource = false;
asset = newAsset;
resourceID = pendingResourceID;
firstRender = true;
}
pendingResourceID = "";
} else if (!pendingResourceID.empty()) {
Debug::log(WARN, "Asset {} not available after the asyncResourceGatherer's callback!", pendingResourceID);
pendingResourceID = "";
} else if (!pendingResourceID.empty()) {
Debug::log(WARN, "Asset {} not available after the asyncResourceGatherer's callback!", pendingResourceID);
if (!newAsset)
Debug::log(ERR, "asset update failed, resourceID: {} not available on update!", id);
else if (newAsset->m_iType == TEXTURE_INVALID) {
g_asyncResourceManager->unload(newAsset);
Debug::log(ERR, "New image asset has an invalid texture!");
} else {
g_asyncResourceManager->unload(asset);
imageFB.destroyBuffer();
g_pHyprlock->addTimer(std::chrono::milliseconds(100), [REF = m_self](auto, auto) { onAssetCallback(REF); }, nullptr);
asset = newAsset;
resourceID = id;
firstRender = true;
}
g_pHyprlock->renderOutput(stringPort);
}
CBox CImage::getBoundingBoxWl() const {

View file

@ -1,11 +1,11 @@
#pragma once
#include "IWidget.hpp"
#include "../../defines.hpp"
#include "../../helpers/Color.hpp"
#include "../../helpers/Math.hpp"
#include "../../config/ConfigDataValues.hpp"
#include "../../core/Timer.hpp"
#include "../AsyncResourceGatherer.hpp"
#include "Shadowable.hpp"
#include <string>
#include <filesystem>
@ -24,6 +24,8 @@ class CImage : public IWidget {
virtual void configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput);
virtual bool draw(const SRenderData& data);
virtual void onAssetUpdate(ResourceID id, ASP<CTexture> newAsset);
virtual CBox getBoundingBoxWl() const;
virtual void onClick(uint32_t button, bool down, const Vector2D& pos);
virtual void onHover(const Vector2D& pos);
@ -35,35 +37,37 @@ class CImage : public IWidget {
void plantTimer();
private:
AWP<CImage> m_self;
AWP<CImage> m_self;
CFramebuffer imageFB;
CFramebuffer imageFB;
int size;
int rounding;
double border;
double angle;
CGradientValueData color;
Vector2D pos;
Vector2D configPos;
int size = 0;
int rounding = 0;
double border = 0;
double angle = 0;
CGradientValueData color;
Vector2D pos;
Vector2D configPos;
std::string halign, valign, path;
std::string halign, valign, path;
bool firstRender = true;
bool firstRender = true;
int reloadTime;
std::string reloadCommand;
std::string onclickCommand;
int reloadTime;
std::string reloadCommand;
std::string onclickCommand;
std::filesystem::file_time_type modificationTime;
ASP<CTimer> imageTimer;
CAsyncResourceGatherer::SPreloadRequest request;
std::filesystem::file_time_type modificationTime;
size_t m_imageRevision = 0;
Vector2D viewport;
std::string stringPort;
ASP<CTimer> imageTimer;
std::string resourceID;
std::string pendingResourceID; // if reloading image
SPreloadedAsset* asset = nullptr;
CShadowable shadow;
Vector2D viewport;
std::string stringPort;
ResourceID resourceID = 0;
bool m_pendingResource = false;
ASP<CTexture> asset = nullptr;
CShadowable shadow;
};

View file

@ -1,10 +1,12 @@
#include "Label.hpp"
#include "../Renderer.hpp"
#include "../AsyncResourceManager.hpp"
#include "../../helpers/Log.hpp"
#include "../../core/hyprlock.hpp"
#include "../../helpers/Color.hpp"
#include "../../helpers/MiscFunctions.hpp"
#include "../../config/ConfigDataValues.hpp"
#include "src/defines.hpp"
#include <hyprlang.hpp>
#include <stdexcept>
@ -25,16 +27,12 @@ static void onTimer(AWP<CLabel> ref) {
}
}
static void onAssetCallback(AWP<CLabel> ref) {
if (auto PLABEL = ref.lock(); PLABEL)
PLABEL->renderUpdate();
}
std::string CLabel::getUniqueResourceId() {
return std::string{"label:"} + std::to_string((uintptr_t)this) + ",time:" + std::to_string(std::chrono::system_clock::now().time_since_epoch().count());
}
void CLabel::onTimerUpdate() {
if (m_pendingResource) {
Debug::log(WARN, "Trying to update label, but a resource is still pending! Skipping update.");
return;
}
std::string oldFormatted = label.formatted;
label = formatString(labelPreFormat);
@ -42,19 +40,17 @@ void CLabel::onTimerUpdate() {
if (label.formatted == oldFormatted && !label.alwaysUpdate)
return;
if (!pendingResourceID.empty()) {
Debug::log(WARN, "Trying to update label, but resource {} is still pending! Skipping update.", pendingResourceID);
return;
}
// request new
request.id = getUniqueResourceId();
pendingResourceID = request.id;
request.asset = label.formatted;
request.text = label.formatted;
m_pendingResource = true;
request.callback = [REF = m_self]() { onAssetCallback(REF); };
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
AWP<IWidget> widget(m_self);
if (label.cmd) {
// Don't increment by one to avoid clashes with multiple widget using the same label command.
m_dynamicRevision += label.updateEveryMs;
g_asyncResourceManager->requestTextCmd(request, m_dynamicRevision, widget.lock());
} else
g_asyncResourceManager->requestText(request, widget.lock());
}
void CLabel::plantTimer() {
@ -89,17 +85,13 @@ void CLabel::configure(const std::unordered_map<std::string, std::any>& props, c
label = formatString(labelPreFormat);
request.id = getUniqueResourceId();
resourceID = request.id;
request.asset = label.formatted;
request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT;
request.props["font_family"] = fontFamily;
request.props["color"] = labelColor;
request.props["font_size"] = fontSize;
request.props["cmd"] = label.cmd;
request.text = label.formatted;
request.font = fontFamily;
request.fontSize = fontSize;
request.color = labelColor.asRGB();
if (!textAlign.empty())
request.props["text_align"] = textAlign;
request.align = parseTextAlignment(textAlign);
} catch (const std::bad_any_cast& e) {
RASSERT(false, "Failed to construct CLabel: {}", e.what()); //
@ -109,7 +101,10 @@ void CLabel::configure(const std::unordered_map<std::string, std::any>& props, c
pos = configPos; // Label size not known yet
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
if (label.cmd) {
resourceID = g_asyncResourceManager->requestTextCmd(request, m_dynamicRevision, nullptr);
} else
resourceID = g_asyncResourceManager->requestText(request, nullptr);
plantTimer();
}
@ -124,16 +119,16 @@ void CLabel::reset() {
return;
if (asset)
g_pRenderer->asyncResourceGatherer->unloadAsset(asset);
g_asyncResourceManager->unload(asset);
asset = nullptr;
pendingResourceID.clear();
resourceID.clear();
asset = nullptr;
m_pendingResource = false;
resourceID = 0;
}
bool CLabel::draw(const SRenderData& data) {
if (!asset) {
asset = g_pRenderer->asyncResourceGatherer->getAssetByID(resourceID);
asset = g_asyncResourceManager->getAssetByID(resourceID);
if (!asset)
return true;
@ -147,32 +142,31 @@ bool CLabel::draw(const SRenderData& data) {
shadow.draw(data);
// calc pos
pos = posFromHVAlign(viewport, asset->texture.m_vSize, configPos, halign, valign, angle);
pos = posFromHVAlign(viewport, asset->m_vSize, configPos, halign, valign, angle);
CBox box = {pos.x, pos.y, asset->texture.m_vSize.x, asset->texture.m_vSize.y};
CBox box = {pos.x, pos.y, asset->m_vSize.x, asset->m_vSize.y};
box.rot = angle;
g_pRenderer->renderTexture(box, asset->texture, data.opacity);
g_pRenderer->renderTexture(box, *asset, data.opacity);
return false;
}
void CLabel::renderUpdate() {
auto newAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(pendingResourceID);
if (newAsset) {
// new asset is ready :D
g_pRenderer->asyncResourceGatherer->unloadAsset(asset);
asset = newAsset;
resourceID = pendingResourceID;
pendingResourceID = "";
updateShadow = true;
void CLabel::onAssetUpdate(ResourceID id, ASP<CTexture> newAsset) {
Debug::log(TRACE, "Label update for resourceID {}", id);
m_pendingResource = false;
if (!newAsset)
Debug::log(ERR, "asset update failed, resourceID: {} not available on update!", id);
else if (newAsset->m_iType == TEXTURE_INVALID) {
g_asyncResourceManager->unload(newAsset);
Debug::log(ERR, "New image asset has an invalid texture!");
} else {
Debug::log(WARN, "Asset {} not available after the asyncResourceGatherer's callback!", pendingResourceID);
g_pHyprlock->addTimer(std::chrono::milliseconds(100), [REF = m_self](auto, auto) { onAssetCallback(REF); }, nullptr);
return;
// new asset is ready :D
g_asyncResourceManager->unload(asset);
asset = newAsset;
resourceID = id;
updateShadow = true;
}
g_pHyprlock->renderOutput(outputStringPort);
}
CBox CLabel::getBoundingBoxWl() const {
@ -180,8 +174,8 @@ CBox CLabel::getBoundingBoxWl() const {
return CBox{};
return {
Vector2D{pos.x, viewport.y - pos.y - asset->texture.m_vSize.y},
asset->texture.m_vSize,
Vector2D{pos.x, viewport.y - pos.y - asset->m_vSize.y},
asset->m_vSize,
};
}

View file

@ -1,10 +1,11 @@
#pragma once
#include "../../defines.hpp"
#include "IWidget.hpp"
#include "Shadowable.hpp"
#include "../../helpers/Math.hpp"
#include "../../core/Timer.hpp"
#include "../AsyncResourceGatherer.hpp"
#include <hyprgraphics/resource/resources/AsyncResource.hpp>
#include <hyprgraphics/resource/resources/TextResource.hpp>
#include <string>
#include <unordered_map>
#include <any>
@ -21,6 +22,8 @@ class CLabel : public IWidget {
virtual void configure(const std::unordered_map<std::string, std::any>& prop, const SP<COutput>& pOutput);
virtual bool draw(const SRenderData& data);
virtual void onAssetUpdate(ResourceID id, ASP<CTexture> newAsset);
virtual CBox getBoundingBoxWl() const;
virtual void onClick(uint32_t button, bool down, const Vector2D& pos);
virtual void onHover(const Vector2D& pos);
@ -32,29 +35,32 @@ class CLabel : public IWidget {
void plantTimer();
private:
AWP<CLabel> m_self;
AWP<CLabel> m_self;
std::string getUniqueResourceId();
std::string labelPreFormat;
IWidget::SFormatResult label;
std::string labelPreFormat;
IWidget::SFormatResult label;
std::string halign, valign;
std::string onclickCommand;
Vector2D viewport;
Vector2D pos;
Vector2D configPos;
double angle;
std::string resourceID;
std::string pendingResourceID; // if dynamic label
std::string halign, valign;
std::string onclickCommand;
SPreloadedAsset* asset = nullptr;
Vector2D viewport;
Vector2D pos;
Vector2D configPos;
double angle;
std::string outputStringPort;
ResourceID resourceID = 0;
bool m_pendingResource = false;
CAsyncResourceGatherer::SPreloadRequest request;
size_t m_dynamicRevision = 0;
ASP<CTimer> labelTimer = nullptr;
ASP<CTexture> asset = nullptr;
CShadowable shadow;
bool updateShadow = true;
std::string outputStringPort;
Hyprgraphics::CTextResource::STextResourceData request;
ASP<CTimer> labelTimer = nullptr;
CShadowable shadow;
bool updateShadow = true;
};

View file

@ -1,4 +1,5 @@
#include "PasswordInputField.hpp"
#include "../AsyncResourceManager.hpp"
#include "../Renderer.hpp"
#include "../../core/hyprlock.hpp"
#include "../../auth/Auth.hpp"
@ -8,6 +9,7 @@
#include "../../core/AnimationManager.hpp"
#include "../../helpers/Color.hpp"
#include <cmath>
#include <hyprgraphics/resource/resources/TextResource.hpp>
#include <hyprutils/math/Vector2D.hpp>
#include <hyprutils/string/String.hpp>
#include <algorithm>
@ -86,16 +88,12 @@ void CPasswordInputField::configure(const std::unordered_map<std::string, std::a
pos = posFromHVAlign(viewport, size->goal(), configPos, halign, valign);
if (!dots.textFormat.empty()) {
dots.textResourceID = std::format("input:{}-{}", (uintptr_t)this, dots.textFormat);
CAsyncResourceGatherer::SPreloadRequest request;
request.id = dots.textResourceID;
request.asset = dots.textFormat;
request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT;
request.props["font_family"] = fontFamily;
request.props["color"] = colorConfig.font;
request.props["font_size"] = (int)(std::nearbyint(configSize.y * dots.size * 0.5f) * 2.f);
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
Hyprgraphics::CTextResource::STextResourceData request;
request.text = dots.textFormat;
request.font = fontFamily;
request.color = colorConfig.font.asRGB();
request.fontSize = (int)(std::nearbyint(configSize.y * dots.size * 0.5f) * 2.f);
dots.textResourceID = g_asyncResourceManager->requestText(request, nullptr);
}
// request the inital placeholder asset
@ -103,7 +101,7 @@ void CPasswordInputField::configure(const std::unordered_map<std::string, std::a
}
void CPasswordInputField::reset() {
if (fade.fadeOutTimer.get()) {
if (fade.fadeOutTimer) {
fade.fadeOutTimer->cancel();
fade.fadeOutTimer.reset();
}
@ -112,10 +110,10 @@ void CPasswordInputField::reset() {
return;
if (placeholder.asset)
g_pRenderer->asyncResourceGatherer->unloadAsset(placeholder.asset);
g_asyncResourceManager->unload(placeholder.asset);
placeholder.asset = nullptr;
placeholder.resourceID.clear();
placeholder.asset = nullptr;
placeholder.resourceID = 0;
placeholder.currentText.clear();
}
@ -151,7 +149,7 @@ void CPasswordInputField::updateFade() {
if (fade.allowFadeOut || fadeTimeoutMs == 0) {
*fade.a = 0.0;
fade.allowFadeOut = false;
} else if (!fade.fadeOutTimer.get())
} else if (!fade.fadeOutTimer)
fade.fadeOutTimer = g_pHyprlock->addTimer(std::chrono::milliseconds(fadeTimeoutMs), [REF = m_self](auto, auto) { fadeOutCallback(REF); }, nullptr);
} else if (INPUTUSED && fade.a->goal() != 1.0)
@ -242,12 +240,12 @@ bool CPasswordInputField::draw(const SRenderData& data) {
if (!dots.textFormat.empty()) {
if (!dots.textAsset)
dots.textAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(dots.textResourceID);
dots.textAsset = g_asyncResourceManager->getAssetByID(dots.textResourceID);
if (!dots.textAsset)
forceReload = true;
else {
passSize = dots.textAsset->texture.m_vSize;
passSize = dots.textAsset->m_vSize;
passSpacing = std::floor(passSize.x * dots.spacing);
}
}
@ -293,7 +291,7 @@ bool CPasswordInputField::draw(const SRenderData& data) {
break;
}
g_pRenderer->renderTexture(box, dots.textAsset->texture, fontCol.a, dots.rounding);
g_pRenderer->renderTexture(box, *dots.textAsset, fontCol.a, dots.rounding);
} else
g_pRenderer->renderRect(box, fontCol, dots.rounding);
@ -301,22 +299,22 @@ bool CPasswordInputField::draw(const SRenderData& data) {
}
}
if (passwordLength == 0 && !checkWaiting && !placeholder.resourceID.empty()) {
SPreloadedAsset* currAsset = nullptr;
if (passwordLength == 0 && !checkWaiting && placeholder.resourceID > 0) {
ASP<CTexture> currAsset = nullptr;
if (!placeholder.asset)
placeholder.asset = g_pRenderer->asyncResourceGatherer->getAssetByID(placeholder.resourceID);
placeholder.asset = g_asyncResourceManager->getAssetByID(placeholder.resourceID);
currAsset = placeholder.asset;
if (currAsset) {
const Vector2D ASSETPOS = inputFieldBox.pos() + inputFieldBox.size() / 2.0 - currAsset->texture.m_vSize / 2.0;
const CBox ASSETBOX{ASSETPOS, currAsset->texture.m_vSize};
const Vector2D ASSETPOS = inputFieldBox.pos() + inputFieldBox.size() / 2.0 - currAsset->m_vSize / 2.0;
const CBox ASSETBOX{ASSETPOS, currAsset->m_vSize};
// Cut the texture to the width of the input field
glEnable(GL_SCISSOR_TEST);
glScissor(inputFieldBox.x, inputFieldBox.y, inputFieldBox.w, inputFieldBox.h);
g_pRenderer->renderTexture(ASSETBOX, currAsset->texture, data.opacity * fade.a->value(), 0);
g_pRenderer->renderTexture(ASSETBOX, *currAsset, data.opacity * fade.a->value(), 0);
glScissor(0, 0, viewport.x, viewport.y);
glDisable(GL_SCISSOR_TEST);
} else
@ -329,10 +327,9 @@ bool CPasswordInputField::draw(const SRenderData& data) {
void CPasswordInputField::updatePlaceholder() {
if (passwordLength != 0) {
if (placeholder.asset && /* keep prompt asset cause it is likely to be used again */ displayFail) {
std::erase(placeholder.registeredResourceIDs, placeholder.resourceID);
g_pRenderer->asyncResourceGatherer->unloadAsset(placeholder.asset);
g_asyncResourceManager->unload(placeholder.asset);
placeholder.asset = nullptr;
placeholder.resourceID = "";
placeholder.resourceID = 0;
redrawShadow = true;
}
return;
@ -351,42 +348,30 @@ void CPasswordInputField::updatePlaceholder() {
if (!ALLOWCOLORSWAP && newText == placeholder.currentText)
return;
const auto NEWRESOURCEID = std::format("placeholder:{}{}{}{}{}{}", newText, (uintptr_t)this, colorState.font.r, colorState.font.g, colorState.font.b, colorState.font.a);
if (placeholder.resourceID == NEWRESOURCEID)
return;
Debug::log(TRACE, "Updating placeholder text: {}", newText);
Debug::log(LOG, "Updating placeholder text: {}", newText);
placeholder.currentText = newText;
placeholder.asset = nullptr;
placeholder.resourceID = NEWRESOURCEID;
if (std::ranges::find(placeholder.registeredResourceIDs, placeholder.resourceID) != placeholder.registeredResourceIDs.end())
return;
Debug::log(TRACE, "Requesting new placeholder asset: {}", placeholder.resourceID);
placeholder.registeredResourceIDs.push_back(placeholder.resourceID);
// query
CAsyncResourceGatherer::SPreloadRequest request;
request.id = placeholder.resourceID;
request.asset = placeholder.currentText;
request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT;
request.props["font_family"] = fontFamily;
request.props["color"] = colorState.font;
request.props["font_size"] = (int)size->value().y / 4;
request.callback = [REF = m_self] {
if (const auto SELF = REF.lock(); SELF)
g_pHyprlock->renderOutput(SELF->outputStringPort);
};
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
Hyprgraphics::CTextResource::STextResourceData request;
request.text = placeholder.currentText;
request.font = fontFamily;
request.color = colorState.font.asRGB();
request.fontSize = (int)size->value().y / 4;
AWP<IWidget> widget(m_self);
placeholder.resourceID = g_asyncResourceManager->requestText(request, widget);
}
void CPasswordInputField::onAssetUpdate(ResourceID id, ASP<CTexture> newAsset) {
;
}
void CPasswordInputField::updateWidth() {
double targetSizeX = configSize.x;
if (passwordLength == 0 && placeholder.asset)
targetSizeX = placeholder.asset->texture.m_vSize.x + size->goal().y;
targetSizeX = placeholder.asset->m_vSize.x + size->goal().y;
targetSizeX = std::max(targetSizeX, configSize.x);

View file

@ -1,6 +1,7 @@
#pragma once
#include "IWidget.hpp"
#include "../../defines.hpp"
#include "../../helpers/Color.hpp"
#include "../../helpers/Math.hpp"
#include "../../core/Timer.hpp"
@ -23,6 +24,8 @@ class CPasswordInputField : public IWidget {
virtual void configure(const std::unordered_map<std::string, std::any>& prop, const SP<COutput>& pOutput);
virtual bool draw(const SRenderData& data);
virtual void onAssetUpdate(ResourceID id, ASP<CTexture> newAsset);
virtual void onHover(const Vector2D& pos);
virtual CBox getBoundingBoxWl() const;
@ -60,13 +63,13 @@ class CPasswordInputField : public IWidget {
struct {
PHLANIMVAR<float> currentAmount;
bool center = false;
float size = 0;
float spacing = 0;
int rounding = 0;
std::string textFormat = "";
std::string textResourceID;
SPreloadedAsset* textAsset = nullptr;
bool center = false;
float size = 0;
float spacing = 0;
int rounding = 0;
size_t textResourceID = 0;
std::string textFormat = "";
ASP<CTexture> textAsset = nullptr;
} dots;
struct {
@ -77,13 +80,11 @@ class CPasswordInputField : public IWidget {
} fade;
struct {
std::string resourceID = "";
SPreloadedAsset* asset = nullptr;
size_t resourceID = 0;
ASP<CTexture> asset = nullptr;
std::string currentText = "";
size_t failedAttempts = 0;
std::vector<std::string> registeredResourceIDs;
std::string currentText = "";
size_t failedAttempts = 0;
} placeholder;
struct {

View file

@ -104,6 +104,11 @@ bool CShape::draw(const SRenderData& data) {
return data.opacity < 1.0;
}
void CShape::onAssetUpdate(ResourceID id, ASP<CTexture> newAsset) {
;
}
CBox CShape::getBoundingBoxWl() const {
return {
Vector2D{pos.x, viewport.y - pos.y - size.y},

View file

@ -18,6 +18,8 @@ class CShape : public IWidget {
virtual void configure(const std::unordered_map<std::string, std::any>& prop, const SP<COutput>& pOutput);
virtual bool draw(const SRenderData& data);
virtual void onAssetUpdate(ResourceID id, ASP<CTexture> newAsset);
virtual CBox getBoundingBoxWl() const;
virtual void onClick(uint32_t button, bool down, const Vector2D& pos);
virtual void onHover(const Vector2D& pos);