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: 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). - **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. - **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 ### dispatchers
This plugin has a couple of dispatchers to trigger certain effects with a keybind. Here's a list: 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 - `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 "globals.hpp"
#include <chrono> #include <chrono>
#include <cmath> #include <cmath>
#include <future>
#include <hyprcursor/hyprcursor.hpp>
#include <hyprlang.hpp> #include <hyprlang.hpp>
#include <hyprland/src/managers/eventLoop/EventLoopTimer.hpp> // required so we don't "unprivate" chrono #include <hyprland/src/managers/eventLoop/EventLoopTimer.hpp> // required so we don't "unprivate" chrono
#include <hyprutils/memory/UniquePtr.hpp>
#define private public #define private public
#include <hyprland/src/managers/CursorManager.hpp> #include <hyprland/src/managers/CursorManager.hpp>
#undef private #undef private
@ -53,36 +56,70 @@ void CHighresHandler::update() {
if (manager && loadedName == name && loadedSize == size) if (manager && loadedName == name && loadedSize == size)
return; return;
auto options = Hyprcursor::SManagerOptions(); // we are currently loading another theme
options.logFn = hcLogger; if (managerFuture) {
options.allowDefaultFallback = true; // 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); Debug::log(LOG, "Skipping hyprcursor theme reload for dynamic cursors because one is already being loaded");
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;
return; 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 }; style = Hyprcursor::SCursorStyleInfo { size };
manager->loadThemeStyle(style);
loadedSize = size; loadedSize = size;
loadedName = name; loadedName = name;
float ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - time).count(); Debug::log(LOG, "Creating future for loading hyprcursor theme for dynamic cursors");
Debug::log(INFO, "Loading finished, took {}ms", ms);
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) { void CHighresHandler::loadShape(const std::string& name) {
static auto const* PFALLBACK = (Hyprlang::STRING const*) getConfig(CONFIG_HIGHRES_FALLBACK); 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); 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/managers/CursorManager.hpp>
#include <hyprland/src/render/Texture.hpp> #include <hyprland/src/render/Texture.hpp>
#include <hyprland/src/helpers/memory/Memory.hpp> #include <hyprland/src/helpers/memory/Memory.hpp>
@ -23,6 +24,8 @@ private:
bool enabled = true; bool enabled = true;
Hyprcursor::SCursorStyleInfo style; Hyprcursor::SCursorStyleInfo style;
UP<std::future<UP<Hyprcursor::CHyprcursorManager>>> managerFuture;
UP<Hyprcursor::CHyprcursorManager> manager; 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) */ /* keep track of loaded theme so we don't reload unnessecarily (<- i'm almost certain there's a typo in this word) */