feat: use hyprcursor for magnified shapes

closes #20
This commit is contained in:
Virt 2024-11-09 16:12:27 +01:00
commit 438daf1dfb
9 changed files with 268 additions and 13 deletions

View file

@ -5,7 +5,7 @@ all: $(PLUGIN_NAME).so
$(PLUGIN_NAME).so: $(SOURCE_FILES) $(PLUGIN_NAME).so: $(SOURCE_FILES)
mkdir -p out mkdir -p out
g++ -shared -Wall --no-gnu-unique -fPIC $(SOURCE_FILES) -g -DWLR_USE_UNSTABLE `pkg-config --cflags pixman-1 libdrm hyprland` -std=c++23 -o out/$(PLUGIN_NAME).so g++ -shared -Wall --no-gnu-unique -fPIC $(SOURCE_FILES) -g -I /usr/include/hyprland/src `pkg-config --cflags pixman-1 libdrm hyprland` -std=c++23 -o out/$(PLUGIN_NAME).so
clean: clean:
rm -f out/$(PLUGIN_NAME).so rm -f out/$(PLUGIN_NAME).so

View file

@ -38,7 +38,7 @@ This plugin is still very early in its development. There are also multiple thin
- [X] per-shape length and starting angle (if possible) - [X] per-shape length and starting angle (if possible)
- [X] cursor shake to find - [X] cursor shake to find
- [X] overdue refactoring (wait for aquamarine merge) - [X] overdue refactoring (wait for aquamarine merge)
- [ ] hyprcursor magnified shape - [X] hyprcursor magnified shape
If anything here sounds interesting to you, don't hesitate to contribute. If anything here sounds interesting to you, don't hesitate to contribute.
@ -192,6 +192,31 @@ plugin:dynamic-cursors {
# see the `ipc` section below # see the `ipc` section below
ipc = false ipc = false
} }
# use hyprcursor to get a higher resolution texture when the cursor is magnified
# see the `hyprcursor` section below
hyprcursor {
# use nearest-neighbour (pixelated) scaling when magnifing beyond texture size
# this will also have effect without hyprcursor support being enabled
# 0 / false - never use pixelated scaling
# 1 / true - use pixelated when no highres image
# 2 - always use pixleated scaling
nearest = true
# enable dedicated hyprcursor support
enabled = true
# resolution in pixels to load the magnified shapes at
# be warned that loading a very high-resolution image will take a long time and might impact memory consumption
# -1 means we use [normal cursor size] * [shake:base option]
resolution = -1
# shape to use when clientside cursors are being magnified
# see the shape-name property of shape rules for possible names
# specifying clientside will use the actual shape, but will be pixelated
fallback = clientside
}
} }
``` ```
@ -235,6 +260,20 @@ The following events with the described arguments are available, when IPC is ena
If you only want the IPC events and not the plugin actually changing the cursor size, you can set the properties `base` to `1`, `speed`, `influence` and `timeout` to `0` in the `plugin:dynamic-cursors:shake` section such that the cursor is not magified during the shake. If you only want the IPC events and not the plugin actually changing the cursor size, you can set the properties `base` to `1`, `speed`, `influence` and `timeout` to `0` in the `plugin:dynamic-cursors:shake` section such that the cursor is not magified during the shake.
### hyprcursor
This plugin supports using hyprcursor to get higher-resolution images for when the cursor is magnified, i.e. when using shake to find. Due to the nature of cursors on wayland, there are some caveats to it. All configuration for it is located in the `plugin:dynamic-cursors:hyprcursor` section.
To use hyprcursor for magnified shapes, the following must be met:
- `plugin:dynamic-cursors:hyprcursor:enabled` must be true (is by default)
- `cursor:enable_hyprcursor` must be true (is by default)
- you must be using a hyprcursor theme
- the hyprcursor theme should be **SVG-based**
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.
## performance ## performance
> **TL;DR:** Hardware cursor performance is about the same as if an animated cursor shape was shown whenever you move your mouse. Sofware cursor performance is not impacted. When the cursor is magnified during a shake, the compositor will temporarily switch to software cursors. > **TL;DR:** Hardware cursor performance is about the same as if an animated cursor shape was shown whenever you move your mouse. Sofware cursor performance is not impacted. When the cursor is magnified during a shake, the compositor will temporarily switch to software cursors.

View file

@ -58,6 +58,10 @@ void* const* getConfig(std::string name) {
return HyprlandAPI::getConfigValue(PHANDLE, NAMESPACE + name)->getDataStaticPtr(); return HyprlandAPI::getConfigValue(PHANDLE, NAMESPACE + name)->getDataStaticPtr();
} }
void* const* getHyprlandConfig(std::string name) {
return HyprlandAPI::getConfigValue(PHANDLE, name)->getDataStaticPtr();
}
void addRulesConfig() { void addRulesConfig() {
HyprlandAPI::addConfigKeyword(PHANDLE, CONFIG_SHAPERULE, onShapeRuleKeyword, Hyprlang::SHandlerOptions {}); HyprlandAPI::addConfigKeyword(PHANDLE, CONFIG_SHAPERULE, onShapeRuleKeyword, Hyprlang::SHandlerOptions {});

View file

@ -12,7 +12,6 @@
#define CONFIG_IGNORE_WARPS "ignore_warps" #define CONFIG_IGNORE_WARPS "ignore_warps"
#define CONFIG_SHAKE "shake:enabled" #define CONFIG_SHAKE "shake:enabled"
#define CONFIG_SHAKE_NEAREST "shake:nearest"
#define CONFIG_SHAKE_EFFECTS "shake:effects" #define CONFIG_SHAKE_EFFECTS "shake:effects"
#define CONFIG_SHAKE_IPC "shake:ipc" #define CONFIG_SHAKE_IPC "shake:ipc"
#define CONFIG_SHAKE_THRESHOLD "shake:threshold" #define CONFIG_SHAKE_THRESHOLD "shake:threshold"
@ -31,6 +30,11 @@
#define CONFIG_STRETCH_LIMIT "stretch:limit" #define CONFIG_STRETCH_LIMIT "stretch:limit"
#define CONFIG_STRETCH_FUNCTION "stretch:function" #define CONFIG_STRETCH_FUNCTION "stretch:function"
#define CONFIG_HIGHRES_ENABLED "hyprcursor:enabled"
#define CONFIG_HIGHRES_NEAREST "hyprcursor:nearest"
#define CONFIG_HIGHRES_SIZE "hyprcursor:resolution"
#define CONFIG_HIGHRES_FALLBACK "hyprcursor:fallback"
#define CONFIG_SHAPERULE "shaperule" #define CONFIG_SHAPERULE "shaperule"
/* is the plugin enabled */ /* is the plugin enabled */
@ -52,3 +56,6 @@ void addShapeConfig(std::string name, std::variant<std::string, float, int> valu
/* get static pointer to config value */ /* get static pointer to config value */
void* const* getConfig(std::string name); void* const* getConfig(std::string name);
/* get static pointer a hyprland config value */
void* const* getHyprlandConfig(std::string name);

View file

@ -2,15 +2,16 @@
#include "mode/Mode.hpp" #include "mode/Mode.hpp"
#include "src/debug/Log.hpp" #include "src/debug/Log.hpp"
#include "src/helpers/math/Math.hpp" #include "src/helpers/math/Math.hpp"
#include "src/managers/eventLoop/EventLoopManager.hpp"
#include <cmath> #include <cmath>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <hyprcursor/hyprcursor.hpp>
#include <hyprlang.hpp> #include <hyprlang.hpp>
#include <gbm.h> #include <gbm.h>
#define private public #define private public
#include <hyprland/src/managers/CursorManager.hpp>
#include <hyprland/src/managers/PointerManager.hpp> #include <hyprland/src/managers/PointerManager.hpp>
#include <hyprland/src/render/OpenGL.hpp> #include <hyprland/src/render/OpenGL.hpp>
#include <hyprland/src/Compositor.hpp> #include <hyprland/src/Compositor.hpp>
@ -53,7 +54,7 @@ Reimplements rendering of the software cursor.
Is also largely identical to hyprlands impl, but uses our custom rendering to rotate the cursor. Is also largely identical to hyprlands impl, but uses our custom rendering to rotate the cursor.
*/ */
void CDynamicCursors::renderSoftware(CPointerManager* pointers, SP<CMonitor> pMonitor, timespec* now, CRegion& damage, std::optional<Vector2D> overridePos) { void CDynamicCursors::renderSoftware(CPointerManager* pointers, SP<CMonitor> pMonitor, timespec* now, CRegion& damage, std::optional<Vector2D> overridePos) {
static auto* const* PNEAREST = (Hyprlang::INT* const*) getConfig(CONFIG_SHAKE_NEAREST); static auto* const* PNEAREST = (Hyprlang::INT* const*) getConfig(CONFIG_HIGHRES_NEAREST);
if (!pointers->hasCursor()) if (!pointers->hasCursor())
return; return;
@ -74,11 +75,35 @@ void CDynamicCursors::renderSoftware(CPointerManager* pointers, SP<CMonitor> pMo
box.y = overridePos->y; box.y = overridePos->y;
} }
// poperly transform hotspot, this first has to undo the hotspot transform from getCursorBoxGlobal
box.x = box.x + pointers->currentCursorImage.hotspot.x - pointers->currentCursorImage.hotspot.x * zoom;
box.y = box.y + pointers->currentCursorImage.hotspot.y - pointers->currentCursorImage.hotspot.y * zoom;
auto texture = pointers->getCurrentCursorTexture(); auto texture = pointers->getCurrentCursorTexture();
bool nearest = false;
if (zoom > 1) {
// this first has to undo the hotspot transform from getCursorBoxGlobal
box.x += pointers->currentCursorImage.hotspot.x;
box.y += pointers->currentCursorImage.hotspot.y;
auto high = highres.getTexture();
if (high) {
texture = high;
auto buf = highres.getBuffer();
// we calculate a more accurate hotspot location if we have bigger shapes
box.x -= (buf->hotspot.x / buf->size.x) * pointers->currentCursorImage.size.x * zoom;
box.y -= (buf->hotspot.y / buf->size.y) * pointers->currentCursorImage.size.y * zoom;
// only use nearest-neighbour if magnifying over size
nearest = **PNEAREST == 2 && pointers->currentCursorImage.size.x * zoom > buf->size.x;
} else {
box.x -= pointers->currentCursorImage.hotspot.x * zoom;
box.y -= pointers->currentCursorImage.hotspot.y * zoom;
nearest = **PNEAREST;
}
}
if (!texture) if (!texture)
return; return;
@ -96,7 +121,7 @@ void CDynamicCursors::renderSoftware(CPointerManager* pointers, SP<CMonitor> pMo
box.rot = resultShown.rotation; box.rot = resultShown.rotation;
// now pass the hotspot to rotate around // now pass the hotspot to rotate around
renderCursorTextureInternalWithDamage(texture, &box, &damage, 1.F, nullptr, 0, pointers->currentCursorImage.hotspot * state->monitor->scale * zoom, zoom > 1 && **PNEAREST, resultShown.stretch.angle, resultShown.stretch.magnitude); renderCursorTextureInternalWithDamage(texture, &box, &damage, 1.F, nullptr, 0, pointers->currentCursorImage.hotspot * state->monitor->scale * zoom, nearest, resultShown.stretch.angle, resultShown.stretch.magnitude);
if (pointers->currentCursorImage.surface) if (pointers->currentCursorImage.surface)
pointers->currentCursorImage.surface->resource()->frame(now); pointers->currentCursorImage.surface->resource()->frame(now);
@ -135,7 +160,7 @@ It is largely copied from hyprland, but adjusted to allow the cursor to be rotat
*/ */
SP<Aquamarine::IBuffer> CDynamicCursors::renderHardware(CPointerManager* pointers, SP<CPointerManager::SMonitorPointerState> state, SP<CTexture> texture) { SP<Aquamarine::IBuffer> CDynamicCursors::renderHardware(CPointerManager* pointers, SP<CPointerManager::SMonitorPointerState> state, SP<CTexture> texture) {
static auto* const* PHW_DEBUG = (Hyprlang::INT* const*) getConfig(CONFIG_HW_DEBUG); static auto* const* PHW_DEBUG = (Hyprlang::INT* const*) getConfig(CONFIG_HW_DEBUG);
static auto* const* PNEAREST = (Hyprlang::INT* const*) getConfig(CONFIG_SHAKE_NEAREST); static auto* const* PNEAREST = (Hyprlang::INT* const*) getConfig(CONFIG_HIGHRES_NEAREST);
auto output = state->monitor->output; auto output = state->monitor->output;
@ -371,10 +396,16 @@ void CDynamicCursors::onCursorMoved(CPointerManager* pointers) {
void CDynamicCursors::setShape(const std::string& shape) { void CDynamicCursors::setShape(const std::string& shape) {
g_pShapeRuleHandler->activate(shape); g_pShapeRuleHandler->activate(shape);
highres.loadShape(shape);
} }
void CDynamicCursors::unsetShape() { void CDynamicCursors::unsetShape() {
g_pShapeRuleHandler->activate("clientside"); g_pShapeRuleHandler->activate("clientside");
highres.loadShape("clientside");
}
void CDynamicCursors::updateTheme() {
highres.update();
} }
/* /*

View file

@ -1,15 +1,18 @@
#include "globals.hpp" #include "globals.hpp"
#include <memory>
#define private public #define private public
#include <hyprland/src/managers/PointerManager.hpp> #include <hyprland/src/managers/PointerManager.hpp>
#undef private #undef private
#include <hyprutils/math/Vector2D.hpp> #include <hyprutils/math/Vector2D.hpp>
#include <hyprland/src/managers/eventLoop/EventLoopManager.hpp> #include <hyprland/src/managers/eventLoop/EventLoopManager.hpp>
#include <hyprcursor/hyprcursor.hpp>
#include "mode/ModeRotate.hpp" #include "mode/ModeRotate.hpp"
#include "mode/ModeTilt.hpp" #include "mode/ModeTilt.hpp"
#include "mode/ModeStretch.hpp" #include "mode/ModeStretch.hpp"
#include "other/Shake.hpp" #include "other/Shake.hpp"
#include "highres.hpp"
class CDynamicCursors { class CDynamicCursors {
public: public:
@ -34,6 +37,8 @@ class CDynamicCursors {
void setShape(const std::string& name); void setShape(const std::string& name);
/* hook on setCursorSoftware */ /* hook on setCursorSoftware */
void unsetShape(); void unsetShape();
/* hook on updateTheme */
void updateTheme();
/* hook on move, indicate that next onCursorMoved is actual move */ /* hook on move, indicate that next onCursorMoved is actual move */
void setMove(); void setMove();
@ -41,6 +46,9 @@ class CDynamicCursors {
private: private:
SP<CEventLoopTimer> tick; SP<CEventLoopTimer> tick;
/* hyprcursor handler for highres images */
CHighresHandler highres;
// current state of the cursor // current state of the cursor
SModeResult resultMode; SModeResult resultMode;
double resultShake; double resultShake;
@ -70,4 +78,4 @@ class CDynamicCursors {
void calculate(EModeUpdate type); void calculate(EModeUpdate type);
}; };
inline std::unique_ptr<CDynamicCursors> g_pDynamicCursors; inline UP<CDynamicCursors> g_pDynamicCursors;

118
src/highres.cpp Normal file
View file

@ -0,0 +1,118 @@
#include "globals.hpp"
#include "plugins/PluginAPI.hpp"
#include <chrono>
#include <cmath>
#include <hyprlang.hpp>
#include <hyprland/src/managers/eventLoop/EventLoopTimer.hpp> // required so we don't "unprivate" chrono
#define private public
#include "src/managers/CursorManager.hpp"
#undef private
#include "highres.hpp"
#include "config/config.hpp"
#include "src/debug/Log.hpp"
CHighresHandler::CHighresHandler() {
// load stuff on creation
update();
// and reload on config reload
static const auto PCALLBACK = HyprlandAPI::registerCallbackDynamic(PHANDLE, "configReloaded", [&](void* self, SCallbackInfo&, std::any data) {
update();
});
}
static void hcLogger(enum eHyprcursorLogLevel level, char* message) {
if (level == HC_LOG_TRACE) return;
Debug::log(NONE, "[hc (dynamic)] {}", message);
}
void CHighresHandler::update() {
static auto* const* PENABLED = (Hyprlang::INT* const*) getConfig(CONFIG_HIGHRES_ENABLED);
static auto* const* PUSEHYPRCURSOR = (Hyprlang::INT* const*) getHyprlandConfig("cursor:enable_hyprcursor");
static auto* const* PSIZE = (Hyprlang::INT* const*) getConfig(CONFIG_HIGHRES_SIZE);
static auto* const* PSHAKE_BASE= (Hyprlang::FLOAT* const*) getConfig(CONFIG_SHAKE_BASE);
static auto* const* PSHAKE = (Hyprlang::INT* const*) getConfig(CONFIG_SHAKE); // currently only needed for shake
if (!**PENABLED || !**PUSEHYPRCURSOR || !**PSHAKE) {
// free manager if no longer enabled
if (manager) {
manager = nullptr;
texture = nullptr;
buffer = nullptr;
}
return;
}
std::string name = g_pCursorManager->m_szTheme;
unsigned int size = **PSIZE != -1 ? **PSIZE : std::round(g_pCursorManager->m_sCurrentStyleInfo.size * **PSHAKE_BASE * 1.5f); // * 1.5f to accomodate for slight growth
// we already have loaded the same theme and size
if (manager && loadedName == name && loadedSize == size)
return;
auto options = Hyprcursor::SManagerOptions();
options.logFn = hcLogger;
options.allowDefaultFallback = true;
manager = std::make_unique<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;
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);
}
void CHighresHandler::loadShape(const std::string& name) {
static auto const* PFALLBACK = (Hyprlang::STRING const*) getConfig(CONFIG_HIGHRES_FALLBACK);
if (!manager) return;
Hyprcursor::SCursorShapeData shape = manager->getShape(name.c_str(), style);
// try load fallback image
if (shape.images.size() == 0) {
shape = manager->getShape(*PFALLBACK, style);
if (shape.images.size() == 0) {
Debug::log(WARN, "Failed to load fallback shape {}, for shape {}!", *PFALLBACK, name);
texture = nullptr;
buffer = nullptr;
return;
}
}
buffer = makeShared<CCursorBuffer>(
shape.images[0].surface,
Vector2D{shape.images[0].size, shape.images[0].size},
Vector2D{shape.images[0].hotspotX, shape.images[0].hotspotY}
);
texture = makeShared<CTexture>(buffer);
}
SP<CTexture> CHighresHandler::getTexture() {
return texture;
}
SP<CCursorBuffer> CHighresHandler::getBuffer() {
return buffer;
}

36
src/highres.hpp Normal file
View file

@ -0,0 +1,36 @@
#include "src/managers/CursorManager.hpp"
#include <hyprland/src/render/Texture.hpp>
#include <hyprland/src/helpers/memory/Memory.hpp>
#include <hyprcursor/hyprcursor.hpp>
#include <hyprutils/math/Vector2D.hpp>
#include <string>
class CHighresHandler {
public:
CHighresHandler();
/* refreshes the hyprcursor theme and stuff, should be called if config values change */
void update();
/* update the currently loaded shape */
void loadShape(const std::string& name);
SP<CTexture> getTexture();
SP<CCursorBuffer> getBuffer();
private:
bool enabled = true;
Hyprcursor::SCursorStyleInfo style;
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) */
unsigned int loadedSize = -1;
std::string loadedName = "";
/* current texture and hotspot */
std::string shape = "";
SP<CTexture> texture;
SP<CCursorBuffer> buffer;
};

View file

@ -69,6 +69,13 @@ void hkMove(void* thisptr, const Vector2D& deltaLogical) {
(*(origMove)g_pMoveHook->m_pOriginal)(thisptr, deltaLogical); (*(origMove)g_pMoveHook->m_pOriginal)(thisptr, deltaLogical);
} }
typedef void (*origUpdateTheme)(void*);
inline CFunctionHook* g_pUpdateThemeHook = nullptr;
void hkUpdateTheme(void* thisptr) {
(*(origUpdateTheme) g_pUpdateThemeHook->m_pOriginal)(thisptr);
if (isEnabled()) g_pDynamicCursors->updateTheme();
}
/* hooks a function hook */ /* hooks a function hook */
CFunctionHook* hook(std::string name, std::string object, void* function) { CFunctionHook* hook(std::string name, std::string object, void* function) {
auto names = HyprlandAPI::findFunctionsByName(PHANDLE, name); auto names = HyprlandAPI::findFunctionsByName(PHANDLE, name);
@ -108,7 +115,6 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
addConfig(CONFIG_THRESHOLD, 2); addConfig(CONFIG_THRESHOLD, 2);
addConfig(CONFIG_SHAKE, true); addConfig(CONFIG_SHAKE, true);
addConfig(CONFIG_SHAKE_NEAREST, true);
addConfig(CONFIG_SHAKE_EFFECTS, false); addConfig(CONFIG_SHAKE_EFFECTS, false);
addConfig(CONFIG_SHAKE_IPC, false); addConfig(CONFIG_SHAKE_IPC, false);
addConfig(CONFIG_SHAKE_THRESHOLD, 6.0f); addConfig(CONFIG_SHAKE_THRESHOLD, 6.0f);
@ -118,6 +124,11 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
addConfig(CONFIG_SHAKE_LIMIT, 0.0F); addConfig(CONFIG_SHAKE_LIMIT, 0.0F);
addConfig(CONFIG_SHAKE_TIMEOUT, 2000); addConfig(CONFIG_SHAKE_TIMEOUT, 2000);
addConfig(CONFIG_HIGHRES_ENABLED, true);
addConfig(CONFIG_HIGHRES_NEAREST, true);
addConfig(CONFIG_HIGHRES_FALLBACK, "clientside");
addConfig(CONFIG_HIGHRES_SIZE, -1);
addShapeConfig(CONFIG_TILT_FUNCTION, "negative_quadratic"); addShapeConfig(CONFIG_TILT_FUNCTION, "negative_quadratic");
addShapeConfig(CONFIG_TILT_LIMIT, 5000); addShapeConfig(CONFIG_TILT_LIMIT, 5000);
@ -147,6 +158,7 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
g_pSetCursorFromNameHook = hook("setCursorFromName", "CCursorManager", (void*) &hkSetCursorFromName); g_pSetCursorFromNameHook = hook("setCursorFromName", "CCursorManager", (void*) &hkSetCursorFromName);
g_pSetCursorSurfaceHook = hook("setCursorSurface", "CCursorManager", (void*) &hkSetCursorSurface); g_pSetCursorSurfaceHook = hook("setCursorSurface", "CCursorManager", (void*) &hkSetCursorSurface);
g_pUpdateThemeHook = hook("updateTheme", "CCursorManager", (void*) &hkUpdateTheme);
} catch (...) { } catch (...) {
HyprlandAPI::addNotification(PHANDLE, "[dynamic-cursors] Failed to load, hooks could not be made!", CColor{1.0, 0.2, 0.2, 1.0}, 5000); HyprlandAPI::addNotification(PHANDLE, "[dynamic-cursors] Failed to load, hooks could not be made!", CColor{1.0, 0.2, 0.2, 1.0}, 5000);
throw std::runtime_error("hooks failed"); throw std::runtime_error("hooks failed");