mirror of
https://github.com/virtcode/hypr-dynamic-cursors
synced 2025-09-19 16:13:21 +02:00
feat: cursor shake to find
This commit is contained in:
parent
086c9a8c48
commit
cde5bf84fb
7 changed files with 134 additions and 50 deletions
|
@ -4,6 +4,7 @@
|
|||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <hyprlang.hpp>
|
||||
|
||||
#define private public
|
||||
#include <hyprland/src/managers/PointerManager.hpp>
|
||||
|
@ -26,6 +27,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*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_SHAKE_NEAREST)->getDataStaticPtr();
|
||||
|
||||
if (!pointers->hasCursor())
|
||||
return;
|
||||
|
@ -50,12 +52,14 @@ void CDynamicCursors::renderSoftware(CPointerManager* pointers, SP<CMonitor> pMo
|
|||
return;
|
||||
|
||||
box.scale(pMonitor->scale);
|
||||
box.w *= zoom;
|
||||
box.h *= zoom;
|
||||
|
||||
// we rotate the cursor by our calculated amount
|
||||
box.rot = this->angle;
|
||||
|
||||
// now pass the hotspot to rotate around
|
||||
renderCursorTextureInternalWithDamage(texture, &box, &damage, 1.F, pointers->currentCursorImage.hotspot);
|
||||
renderCursorTextureInternalWithDamage(texture, &box, &damage, 1.F, pointers->currentCursorImage.hotspot * zoom, zoom > 1 && **PNEAREST);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -65,8 +69,8 @@ It is largely identical to hyprlands implementation, but expands the damage reag
|
|||
void CDynamicCursors::damageSoftware(CPointerManager* pointers) {
|
||||
|
||||
// we damage a 3x3 area around the cursor, to accomodate for all possible hotspots and rotations
|
||||
Vector2D size = pointers->currentCursorImage.size / pointers->currentCursorImage.scale;
|
||||
CBox b = CBox{pointers->pointerPos, size * 3}.translate(-(pointers->currentCursorImage.hotspot + size));
|
||||
Vector2D size = pointers->currentCursorImage.size / pointers->currentCursorImage.scale * zoom;
|
||||
CBox b = CBox{pointers->pointerPos, size * 3}.translate(-(pointers->currentCursorImage.hotspot * zoom + size));
|
||||
|
||||
static auto PNOHW = CConfigValue<Hyprlang::INT>("cursor:no_hardware_cursors");
|
||||
|
||||
|
@ -87,10 +91,11 @@ It is largely copied from hyprland, but adjusted to allow the cursor to be rotat
|
|||
*/
|
||||
wlr_buffer* CDynamicCursors::renderHardware(CPointerManager* pointers, SP<CPointerManager::SMonitorPointerState> state, SP<CTexture> texture) {
|
||||
static auto* const* PHW_DEBUG= (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_HW_DEBUG)->getDataStaticPtr();
|
||||
static auto* const* PNEAREST = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_SHAKE_NEAREST)->getDataStaticPtr();
|
||||
|
||||
auto output = state->monitor->output;
|
||||
|
||||
auto size = pointers->currentCursorImage.size;
|
||||
auto size = pointers->currentCursorImage.size * zoom;
|
||||
// we try to allocate a buffer that is thrice as big, see software rendering
|
||||
auto target = size * 3;
|
||||
|
||||
|
@ -151,11 +156,11 @@ wlr_buffer* CDynamicCursors::renderHardware(CPointerManager* pointers, SP<CPoint
|
|||
g_pHyprOpenGL->clear(CColor{0.F, 0.F, 0.F, 0.F});
|
||||
|
||||
// the box should start in the middle portion, rotate by our calculated amount
|
||||
CBox xbox = {size, Vector2D{pointers->currentCursorImage.size / pointers->currentCursorImage.scale * state->monitor->scale}.round()};
|
||||
CBox xbox = {size, Vector2D{pointers->currentCursorImage.size / pointers->currentCursorImage.scale * state->monitor->scale * zoom}.round()};
|
||||
xbox.rot = this->angle;
|
||||
|
||||
// use our custom draw function
|
||||
renderCursorTextureInternalWithDamage(texture, &xbox, &damage, 1.F, pointers->currentCursorImage.hotspot);
|
||||
renderCursorTextureInternalWithDamage(texture, &xbox, &damage, 1.F, pointers->currentCursorImage.hotspot * zoom, zoom > 1 && **PNEAREST);
|
||||
|
||||
g_pHyprOpenGL->end();
|
||||
glFlush();
|
||||
|
@ -178,7 +183,7 @@ bool CDynamicCursors::setHardware(CPointerManager* pointers, SP<CPointerManager:
|
|||
if (!P_MONITOR->output->cursor_swapchain) return false;
|
||||
|
||||
// we need to transform the hotspot manually as we need to indent it by the size
|
||||
const auto HOTSPOT = CBox{(pointers->currentCursorImage.hotspot + pointers->currentCursorImage.size) * P_MONITOR->scale, {0, 0}}
|
||||
const auto HOTSPOT = CBox{(pointers->currentCursorImage.hotspot + pointers->currentCursorImage.size) * P_MONITOR->scale * zoom, {0, 0}}
|
||||
.transform(wlTransformToHyprutils(wlr_output_transform_invert(P_MONITOR->transform)), P_MONITOR->output->cursor_swapchain->width, P_MONITOR->output->cursor_swapchain->height)
|
||||
.pos();
|
||||
|
||||
|
@ -226,25 +231,36 @@ Handle cursor tick events.
|
|||
*/
|
||||
void CDynamicCursors::onTick(CPointerManager* pointers) {
|
||||
static auto const* PMODE = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_MODE)->getDataStaticPtr();
|
||||
static auto* const* PSHAKE = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_SHAKE)->getDataStaticPtr();
|
||||
|
||||
if (!strcmp(*PMODE, "tilt")) calculate();
|
||||
if (!strcmp(*PMODE, "tilt") || **PSHAKE) calculate();
|
||||
}
|
||||
|
||||
void CDynamicCursors::calculate() {
|
||||
static auto const* PMODE = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_MODE)->getDataStaticPtr();
|
||||
static auto* const* PTHRESHOLD = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_THRESHOLD)->getDataStaticPtr();
|
||||
static auto* const* PSHAKE = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_SHAKE)->getDataStaticPtr();
|
||||
static auto* const* PSHAKE_EFFECTS = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_SHAKE_EFFECTS)->getDataStaticPtr();
|
||||
|
||||
double zoom = 1;
|
||||
if (**PSHAKE)
|
||||
zoom = calculateShake();
|
||||
|
||||
double angle = 0;
|
||||
if (!strcmp(*PMODE, "rotate"))
|
||||
angle = calculateStick();
|
||||
else if (!strcmp(*PMODE, "tilt"))
|
||||
angle = calculateAir();
|
||||
else
|
||||
else if (strcmp(*PMODE, "none")) // if not none, print warning
|
||||
Debug::log(WARN, "[dynamic-cursors] unknown mode specified");
|
||||
|
||||
if (zoom > 1 && !**PSHAKE_EFFECTS)
|
||||
angle = 0;
|
||||
|
||||
// we only consider the angle changed if it is larger than 1 degree
|
||||
if (abs(this->angle - angle) > ((PI / 180) * **PTHRESHOLD)) {
|
||||
if (abs(this->angle - angle) > ((PI / 180) * **PTHRESHOLD) || abs(this->zoom - zoom) > 0.1) {
|
||||
this->angle = angle;
|
||||
this->zoom = zoom;
|
||||
|
||||
// damage software and change hardware cursor shape
|
||||
g_pPointerManager->damageIfSoftware();
|
||||
|
@ -301,32 +317,52 @@ double CDynamicCursors::calculateAir() {
|
|||
samples_index = (samples_index + 1) % max; // increase for next sample
|
||||
int first = samples_index;
|
||||
|
||||
/* turns out this is not relevant on my systems (should've checked before implementing lol):
|
||||
// motion smooting
|
||||
// fills samples in between with linear approximations
|
||||
// accomodates for mice with low polling rates and monitors with high fps
|
||||
int previous = current == 0 ? max - 1 : current - 1;
|
||||
if (samples[previous] != samples[current]) {
|
||||
int steps = std::abs(samples_last_change - previous);
|
||||
Vector2D amount = (samples[current] - samples[previous]) / steps;
|
||||
|
||||
int factor = 1;
|
||||
for (int i = (samples_last_change + 1) % max; i != current; i = (i + 1) % max) {
|
||||
samples[i] += amount * factor++;
|
||||
}
|
||||
|
||||
samples_last_change = current;
|
||||
} else if (samples_last_change == current) {
|
||||
samples_last_change = first; // next is the last then
|
||||
}
|
||||
*/
|
||||
|
||||
// calculate speed and tilt
|
||||
double speed = (samples[current].x - samples[first].x) / 0.1;
|
||||
|
||||
return airFunction(speed) * (PI / 3); // 120° in both directions
|
||||
}
|
||||
|
||||
double CDynamicCursors::calculateShake() {
|
||||
static auto* const* PTHRESHOLD = (Hyprlang::FLOAT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_SHAKE_THRESHOLD)->getDataStaticPtr();
|
||||
static auto* const* PFACTOR = (Hyprlang::FLOAT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_SHAKE_FACTOR)->getDataStaticPtr();
|
||||
|
||||
int max = g_pHyprRenderer->m_pMostHzMonitor->refreshRate; // 1s worth of history
|
||||
shake_samples.resize(max);
|
||||
shake_samples_distance.resize(max);
|
||||
|
||||
int previous = shake_samples_index == 0 ? max - 1 : shake_samples_index - 1;
|
||||
shake_samples[shake_samples_index] = Vector2D{g_pPointerManager->pointerPos};
|
||||
shake_samples_distance[shake_samples_index] = shake_samples[shake_samples_index].distance(shake_samples[previous]);
|
||||
shake_samples_index = (shake_samples_index + 1) % max; // increase for next sample
|
||||
|
||||
/*
|
||||
The idea for this algorith was largely inspired by KDE Plasma
|
||||
https://invent.kde.org/plasma/kwin/-/blob/master/src/plugins/shakecursor/shakedetector.cpp
|
||||
*/
|
||||
|
||||
// calculate total distance travelled
|
||||
double trail = 0;
|
||||
for (double distance : shake_samples_distance) trail += distance;
|
||||
|
||||
// calculate diagonal of bounding box travelled within
|
||||
double left = 1e100, right = 0, bottom = 0, top = 1e100;
|
||||
for (Vector2D position : shake_samples) {
|
||||
left = std::min(left, position.x);
|
||||
right = std::max(right, position.x);
|
||||
top = std::min(top, position.y);
|
||||
bottom = std::max(bottom, position.y);
|
||||
}
|
||||
double diagonal = Vector2D{left, top}.distance(Vector2D(right, bottom));
|
||||
|
||||
// discard when the diagonal is small, so we don't have issues with inaccuracies
|
||||
if (diagonal < 100) return 1.0;
|
||||
|
||||
std::cout << trail << " " << diagonal << " " << (trail / diagonal) << "\n";
|
||||
|
||||
return std::max(1.0, ((trail / diagonal) - **PTHRESHOLD) * **PFACTOR);
|
||||
}
|
||||
|
||||
double CDynamicCursors::calculateStick() {
|
||||
static auto* const* PLENGTH = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_LENGTH)->getDataStaticPtr();
|
||||
|
||||
|
|
|
@ -25,6 +25,8 @@ class CDynamicCursors {
|
|||
private:
|
||||
// current angle of the cursor in radiants
|
||||
double angle;
|
||||
// current zoom value of the cursor
|
||||
double zoom = 1;
|
||||
|
||||
// calculates the current angle of the cursor, and changes the cursor shape
|
||||
void calculate();
|
||||
|
@ -39,6 +41,11 @@ class CDynamicCursors {
|
|||
// ring buffer of last position samples
|
||||
std::vector<Vector2D> samples;
|
||||
int samples_index = 0;
|
||||
|
||||
double calculateShake();
|
||||
std::vector<Vector2D> shake_samples;
|
||||
std::vector<double> shake_samples_distance;
|
||||
int shake_samples_index = 0;
|
||||
};
|
||||
|
||||
inline std::unique_ptr<CDynamicCursors> g_pDynamicCursors;
|
||||
|
|
|
@ -2,14 +2,19 @@
|
|||
|
||||
#include <hyprland/src/plugins/PluginAPI.hpp>
|
||||
|
||||
#define CONFIG_ENABLED "plugin:dynamic-cursors:enabled"
|
||||
#define CONFIG_MODE "plugin:dynamic-cursors:mode"
|
||||
#define CONFIG_THRESHOLD "plugin:dynamic-cursors:threshold"
|
||||
#define CONFIG_LENGTH "plugin:dynamic-cursors:rotate:length"
|
||||
#define CONFIG_MASS "plugin:dynamic-cursors:tilt:limit"
|
||||
#define CONFIG_FUNCTION "plugin:dynamic-cursors:tilt:function"
|
||||
#define CONFIG_ENABLED "plugin:dynamic-cursors:enabled"
|
||||
#define CONFIG_MODE "plugin:dynamic-cursors:mode"
|
||||
#define CONFIG_SHAKE "plugin:dynamic-cursors:shake"
|
||||
#define CONFIG_THRESHOLD "plugin:dynamic-cursors:threshold"
|
||||
#define CONFIG_SHAKE_NEAREST "plugin:dynamic-cursors:shake:nearest"
|
||||
#define CONFIG_SHAKE_THRESHOLD "plugin:dynamic-cursors:shake:threshold"
|
||||
#define CONFIG_SHAKE_FACTOR "plugin:dynamic-cursors:shake:factor"
|
||||
#define CONFIG_SHAKE_EFFECTS "plugin:dynamic-cursors:shake:effects"
|
||||
#define CONFIG_LENGTH "plugin:dynamic-cursors:rotate:length"
|
||||
#define CONFIG_MASS "plugin:dynamic-cursors:tilt:limit"
|
||||
#define CONFIG_FUNCTION "plugin:dynamic-cursors:tilt:function"
|
||||
|
||||
#define CONFIG_HW_DEBUG "plugin:dynamic-cursors:hw_debug"
|
||||
#define CONFIG_HW_DEBUG "plugin:dynamic-cursors:hw_debug"
|
||||
|
||||
inline HANDLE PHANDLE = nullptr;
|
||||
|
||||
|
|
14
src/main.cpp
14
src/main.cpp
|
@ -55,12 +55,10 @@ void hkOnCursorMoved(void* thisptr) {
|
|||
else return (*(origOnCursorMoved)g_pOnCursorMovedHook->m_pOriginal)(thisptr);
|
||||
}
|
||||
|
||||
void onTick() {
|
||||
g_pDynamicCursors->onTick(g_pPointerManager.get());
|
||||
}
|
||||
|
||||
int onTick(void* data) {
|
||||
g_pDynamicCursors->onTick(g_pPointerManager.get());
|
||||
static auto* const* PENABLED = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_ENABLED)->getDataStaticPtr();
|
||||
|
||||
if (**PENABLED) g_pDynamicCursors->onTick(g_pPointerManager.get());
|
||||
|
||||
const int TIMEOUT = g_pHyprRenderer->m_pMostHzMonitor ? 1000.0 / g_pHyprRenderer->m_pMostHzMonitor->refreshRate : 16;
|
||||
wl_event_source_timer_update(tick, TIMEOUT);
|
||||
|
@ -111,6 +109,12 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
|
|||
HyprlandAPI::addConfigValue(PHANDLE, CONFIG_MODE, Hyprlang::STRING{"tilt"});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, CONFIG_THRESHOLD, Hyprlang::INT{2});
|
||||
|
||||
HyprlandAPI::addConfigValue(PHANDLE, CONFIG_SHAKE, Hyprlang::INT{1});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, CONFIG_SHAKE_NEAREST, Hyprlang::INT{1});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, CONFIG_SHAKE_EFFECTS, Hyprlang::INT{0});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, CONFIG_SHAKE_THRESHOLD, Hyprlang::FLOAT{4});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, CONFIG_SHAKE_FACTOR, Hyprlang::FLOAT{1.5});
|
||||
|
||||
HyprlandAPI::addConfigValue(PHANDLE, CONFIG_FUNCTION, Hyprlang::STRING{"negative_quadratic"});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, CONFIG_MASS, Hyprlang::INT{5000});
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ void projectCursorBox(float mat[9], CBox& box, eTransform transform, float rotat
|
|||
/*
|
||||
This renders a texture with damage but rotates the texture around a given hotspot.
|
||||
*/
|
||||
void renderCursorTextureInternalWithDamage(SP<CTexture> tex, CBox* pBox, CRegion* damage, float alpha, Vector2D hotspot) {
|
||||
void renderCursorTextureInternalWithDamage(SP<CTexture> tex, CBox* pBox, CRegion* damage, float alpha, Vector2D hotspot, bool nearest) {
|
||||
TRACY_GPU_ZONE("RenderDynamicCursor");
|
||||
|
||||
alpha = std::clamp(alpha, 0.f, 1.f);
|
||||
|
@ -75,7 +75,7 @@ void renderCursorTextureInternalWithDamage(SP<CTexture> tex, CBox* pBox, CRegion
|
|||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(tex->m_iTarget, tex->m_iTexID);
|
||||
|
||||
if (g_pHyprOpenGL->m_RenderData.useNearestNeighbor) {
|
||||
if (g_pHyprOpenGL->m_RenderData.useNearestNeighbor || nearest) {
|
||||
glTexParameteri(tex->m_iTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(tex->m_iTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
} else {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#include <hyprland/src/render/OpenGL.hpp>
|
||||
#include <hyprland/src/helpers/math/Math.hpp>
|
||||
|
||||
void renderCursorTextureInternalWithDamage(SP<CTexture> tex, CBox* pBox, CRegion* damage, float alpha, Vector2D hotspot);
|
||||
void renderCursorTextureInternalWithDamage(SP<CTexture> tex, CBox* pBox, CRegion* damage, float alpha, Vector2D hotspot, bool nearest);
|
||||
void projectCursorBox(float mat[9], CBox& box, eTransform transform, float rotation, const float projection[9], Vector2D hotspot);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue