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

@ -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 {});

View file

@ -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);

View file

@ -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();
}
/*

View file

@ -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
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);
}
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");