mirror of
https://github.com/virtcode/hypr-dynamic-cursors
synced 2025-09-19 16:13:21 +02:00
parent
1ae25556a1
commit
438daf1dfb
9 changed files with 268 additions and 13 deletions
|
@ -58,6 +58,10 @@ void* const* getConfig(std::string name) {
|
|||
return HyprlandAPI::getConfigValue(PHANDLE, NAMESPACE + name)->getDataStaticPtr();
|
||||
}
|
||||
|
||||
void* const* getHyprlandConfig(std::string name) {
|
||||
return HyprlandAPI::getConfigValue(PHANDLE, name)->getDataStaticPtr();
|
||||
}
|
||||
|
||||
void addRulesConfig() {
|
||||
HyprlandAPI::addConfigKeyword(PHANDLE, CONFIG_SHAPERULE, onShapeRuleKeyword, Hyprlang::SHandlerOptions {});
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
#define CONFIG_IGNORE_WARPS "ignore_warps"
|
||||
|
||||
#define CONFIG_SHAKE "shake:enabled"
|
||||
#define CONFIG_SHAKE_NEAREST "shake:nearest"
|
||||
#define CONFIG_SHAKE_EFFECTS "shake:effects"
|
||||
#define CONFIG_SHAKE_IPC "shake:ipc"
|
||||
#define CONFIG_SHAKE_THRESHOLD "shake:threshold"
|
||||
|
@ -31,6 +30,11 @@
|
|||
#define CONFIG_STRETCH_LIMIT "stretch:limit"
|
||||
#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"
|
||||
|
||||
/* 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 */
|
||||
void* const* getConfig(std::string name);
|
||||
|
||||
/* get static pointer a hyprland config value */
|
||||
void* const* getHyprlandConfig(std::string name);
|
||||
|
|
|
@ -2,15 +2,16 @@
|
|||
#include "mode/Mode.hpp"
|
||||
#include "src/debug/Log.hpp"
|
||||
#include "src/helpers/math/Math.hpp"
|
||||
#include "src/managers/eventLoop/EventLoopManager.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <hyprcursor/hyprcursor.hpp>
|
||||
#include <hyprlang.hpp>
|
||||
#include <gbm.h>
|
||||
|
||||
#define private public
|
||||
#include <hyprland/src/managers/CursorManager.hpp>
|
||||
#include <hyprland/src/managers/PointerManager.hpp>
|
||||
#include <hyprland/src/render/OpenGL.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.
|
||||
*/
|
||||
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())
|
||||
return;
|
||||
|
@ -74,11 +75,35 @@ void CDynamicCursors::renderSoftware(CPointerManager* pointers, SP<CMonitor> pMo
|
|||
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();
|
||||
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)
|
||||
return;
|
||||
|
||||
|
@ -96,7 +121,7 @@ void CDynamicCursors::renderSoftware(CPointerManager* pointers, SP<CMonitor> pMo
|
|||
box.rot = resultShown.rotation;
|
||||
|
||||
// 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)
|
||||
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) {
|
||||
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;
|
||||
|
||||
|
@ -371,10 +396,16 @@ void CDynamicCursors::onCursorMoved(CPointerManager* pointers) {
|
|||
|
||||
void CDynamicCursors::setShape(const std::string& shape) {
|
||||
g_pShapeRuleHandler->activate(shape);
|
||||
highres.loadShape(shape);
|
||||
}
|
||||
|
||||
void CDynamicCursors::unsetShape() {
|
||||
g_pShapeRuleHandler->activate("clientside");
|
||||
highres.loadShape("clientside");
|
||||
}
|
||||
|
||||
void CDynamicCursors::updateTheme() {
|
||||
highres.update();
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
#include "globals.hpp"
|
||||
#include <memory>
|
||||
|
||||
#define private public
|
||||
#include <hyprland/src/managers/PointerManager.hpp>
|
||||
#undef private
|
||||
#include <hyprutils/math/Vector2D.hpp>
|
||||
#include <hyprland/src/managers/eventLoop/EventLoopManager.hpp>
|
||||
#include <hyprcursor/hyprcursor.hpp>
|
||||
|
||||
#include "mode/ModeRotate.hpp"
|
||||
#include "mode/ModeTilt.hpp"
|
||||
#include "mode/ModeStretch.hpp"
|
||||
#include "other/Shake.hpp"
|
||||
#include "highres.hpp"
|
||||
|
||||
class CDynamicCursors {
|
||||
public:
|
||||
|
@ -34,6 +37,8 @@ class CDynamicCursors {
|
|||
void setShape(const std::string& name);
|
||||
/* hook on setCursorSoftware */
|
||||
void unsetShape();
|
||||
/* hook on updateTheme */
|
||||
void updateTheme();
|
||||
|
||||
/* hook on move, indicate that next onCursorMoved is actual move */
|
||||
void setMove();
|
||||
|
@ -41,6 +46,9 @@ class CDynamicCursors {
|
|||
private:
|
||||
SP<CEventLoopTimer> tick;
|
||||
|
||||
/* hyprcursor handler for highres images */
|
||||
CHighresHandler highres;
|
||||
|
||||
// current state of the cursor
|
||||
SModeResult resultMode;
|
||||
double resultShake;
|
||||
|
@ -70,4 +78,4 @@ class CDynamicCursors {
|
|||
void calculate(EModeUpdate type);
|
||||
};
|
||||
|
||||
inline std::unique_ptr<CDynamicCursors> g_pDynamicCursors;
|
||||
inline UP<CDynamicCursors> g_pDynamicCursors;
|
||||
|
|
118
src/highres.cpp
Normal file
118
src/highres.cpp
Normal 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
36
src/highres.hpp
Normal 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;
|
||||
};
|
14
src/main.cpp
14
src/main.cpp
|
@ -69,6 +69,13 @@ void hkMove(void* thisptr, const Vector2D& 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 */
|
||||
CFunctionHook* hook(std::string name, std::string object, void* function) {
|
||||
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_SHAKE, true);
|
||||
addConfig(CONFIG_SHAKE_NEAREST, true);
|
||||
addConfig(CONFIG_SHAKE_EFFECTS, false);
|
||||
addConfig(CONFIG_SHAKE_IPC, false);
|
||||
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_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_LIMIT, 5000);
|
||||
|
||||
|
@ -147,6 +158,7 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
|
|||
|
||||
g_pSetCursorFromNameHook = hook("setCursorFromName", "CCursorManager", (void*) &hkSetCursorFromName);
|
||||
g_pSetCursorSurfaceHook = hook("setCursorSurface", "CCursorManager", (void*) &hkSetCursorSurface);
|
||||
g_pUpdateThemeHook = hook("updateTheme", "CCursorManager", (void*) &hkUpdateTheme);
|
||||
} catch (...) {
|
||||
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");
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue