feat: load hyprcursor shapes on a seperate thread

This commit is contained in:
Virt 2025-03-06 13:48:22 +01:00
commit 261bc1668f
3 changed files with 59 additions and 18 deletions

View file

@ -271,9 +271,10 @@ To use hyprcursor for magnified shapes, the following must be met:
As mentioned, there are some caveats to it. Here are the most common ones:
- **Still pixelated on GTK apps and xwayland** - These apps are using clientside cursors, so the program itself is specifying the cursor shape, hence we cannot load a higher resolution for it. You can set a specific shape to show in these cases with the `fallback` option (see config).
- **Hyprland lags when loading the plugin** - Loading a set of high resolution cursor shapes takes some time. This means your session will freeze while the theme is being loaded. You can try setting a custom / lower `resolution` option (see config).
- **Blurred at very large sizes** - The high resolution cursors are preloaded at a fixed size. If you magnify your cursor beyond this size, your cursors will look blurry. You can increase the preload size with the `resolution` option (see config), at the expense of some memory and higher loading times.
Loading a cursor theme at a high resolution is relatively resource intensive. This plugin thus loads the theme asynchronously on a seperate thread, meaning your session will stay interactive during this time. But this means that when loading the plugin or changing cursor theme, your CPU might spike momentarily and the high-resolution theme will only be available after a short time (usually just a couple of seconds).
### dispatchers
This plugin has a couple of dispatchers to trigger certain effects with a keybind. Here's a list:
- `plugin:dynamic-cursors:magnify` with arguments `duration?, size?` triggers cursor magnification like on a shake

View file

@ -1,9 +1,12 @@
#include "globals.hpp"
#include <chrono>
#include <cmath>
#include <future>
#include <hyprcursor/hyprcursor.hpp>
#include <hyprlang.hpp>
#include <hyprland/src/managers/eventLoop/EventLoopTimer.hpp> // required so we don't "unprivate" chrono
#include <hyprutils/memory/UniquePtr.hpp>
#define private public
#include <hyprland/src/managers/CursorManager.hpp>
#undef private
@ -53,36 +56,70 @@ void CHighresHandler::update() {
if (manager && loadedName == name && loadedSize == size)
return;
auto options = Hyprcursor::SManagerOptions();
options.logFn = hcLogger;
options.allowDefaultFallback = true;
// we are currently loading another theme
if (managerFuture) {
// in this case we don't do anything as proceeding would block until the future is done (thanks cpp apis)
// we just skip the update, but when retrieving the future we check again and then these changes will be loaded
manager = makeUnique<Hyprcursor::CHyprcursorManager>(name.empty() ? nullptr : name.c_str(), options);
if (!manager->valid()) {
Debug::log(ERR, "Hyprcursor for dynamic cursors failed loading theme \"{}\", falling back to pixelated trash.", name);
manager = nullptr;
texture = nullptr;
buffer = nullptr;
Debug::log(LOG, "Skipping hyprcursor theme reload for dynamic cursors because one is already being loaded");
return;
}
auto time = std::chrono::system_clock::now();
Debug::log(INFO, "Loading hyprcursor theme {} of size {} for dynamic cursors, this might take a while!", name, size);
style = Hyprcursor::SCursorStyleInfo { size };
manager->loadThemeStyle(style);
loadedSize = size;
loadedName = name;
float ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - time).count();
Debug::log(INFO, "Loading finished, took {}ms", ms);
Debug::log(LOG, "Creating future for loading hyprcursor theme for dynamic cursors");
auto fut = std::async(std::launch::async, [=, style = style] () -> UP<Hyprcursor::CHyprcursorManager> {
Debug::log(INFO, "Starting to load hyprcursor theme '{}' of size {} for dynamic cursors asynchronously ...", name, size);
auto time = std::chrono::system_clock::now();
auto options = Hyprcursor::SManagerOptions();
options.logFn = hcLogger;
options.allowDefaultFallback = true;
auto manager = makeUnique<Hyprcursor::CHyprcursorManager>(name.empty() ? nullptr : name.c_str(), options);
if (!manager->valid()) {
Debug::log(ERR, "... hyprcursor for dynamic cursors failed loading theme '{}', falling back to pixelated trash.", name);
return nullptr;
}
manager->loadThemeStyle(style);
float ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - time).count();
Debug::log(INFO, "... hyprcursor for dynamic cursors loading finished, took {}ms", ms);
return manager;
});
manager = nullptr; // free old manager
managerFuture = makeUnique<std::future<UP<Hyprcursor::CHyprcursorManager>>>(std::move(fut));
}
void CHighresHandler::loadShape(const std::string& name) {
static auto const* PFALLBACK = (Hyprlang::STRING const*) getConfig(CONFIG_HIGHRES_FALLBACK);
if (!manager) return;
if (!manager) {
// don't show old, potentially outdated shapes
texture = nullptr;
buffer = nullptr;
if (!managerFuture || managerFuture->wait_for(std::chrono::seconds(0)) != std::future_status::ready) return;
Debug::log(INFO, "Future for hyprcursor theme for dynamic cursors is ready, using new theme");
manager = managerFuture->get();
managerFuture = nullptr;
if (!manager) return; // could've failed
else {
// in case someone has updated the theme again in the meantime
update();
if (!manager) return; // new manager could be on the way
}
}
Hyprcursor::SCursorShapeData shape = manager->getShape(name.c_str(), style);

View file

@ -1,3 +1,4 @@
#include <future>
#include <hyprland/src/managers/CursorManager.hpp>
#include <hyprland/src/render/Texture.hpp>
#include <hyprland/src/helpers/memory/Memory.hpp>
@ -23,6 +24,8 @@ private:
bool enabled = true;
Hyprcursor::SCursorStyleInfo style;
UP<std::future<UP<Hyprcursor::CHyprcursorManager>>> managerFuture;
UP<Hyprcursor::CHyprcursorManager> manager;
/* keep track of loaded theme so we don't reload unnessecarily (<- i'm almost certain there's a typo in this word) */