mirror of
https://github.com/virtcode/hypr-dynamic-cursors
synced 2025-09-19 08:03:21 +02:00
feat: new shake to find behaviour
This commit is contained in:
parent
eabab33f00
commit
553dc93cc0
6 changed files with 118 additions and 37 deletions
48
README.md
48
README.md
|
@ -25,7 +25,7 @@ https://github.com/VirtCode/hypr-dynamic-cursors/assets/41426325/7b8289e7-9dd2-4
|
|||
### shake to find
|
||||
The plugin supports shake to find, akin to how KDE Plasma, MacOS, etc. do it. It can also be extensively configured and is enabled by default. If you only want shake to find, and no weird cursor behaviour, you can disable the above modes with the mode `none`.
|
||||
|
||||
https://github.com/VirtCode/hypr-dynamic-cursors/assets/41426325/9ff64a9b-64e5-4595-b721-dcb4d62bee18
|
||||
https://github.com/user-attachments/assets/f23669ac-b9c9-4667-993b-5133eb0a7f01
|
||||
|
||||
## state
|
||||
This plugin is still very early in its development. There are also multiple things which may or may not be implemented in the future:
|
||||
|
@ -163,22 +163,33 @@ plugin:dynamic-cursors {
|
|||
# enables shake to find
|
||||
enabled = true
|
||||
|
||||
# controls how soon a shake is detected
|
||||
# lower values mean sooner
|
||||
threshold = 4.0
|
||||
|
||||
# controls how fast the cursor gets larger
|
||||
factor = 1.5
|
||||
|
||||
# show cursor behaviour `tilt`, `rotate`, etc. while shaking
|
||||
effects = false
|
||||
|
||||
# use nearest-neighbour (pixelated) scaling when shaking
|
||||
# may look weird when effects are enabled
|
||||
nearest = true
|
||||
|
||||
# controls how soon a shake is detected
|
||||
# lower values mean sooner
|
||||
threshold = 6.0
|
||||
|
||||
# magnification level immediately after shake start
|
||||
base = 4.0
|
||||
# magnification increase per second when continuing to shake
|
||||
speed = 4.0
|
||||
# factor the speed is influenced by the current shake intensitiy
|
||||
influence = 0.0
|
||||
|
||||
# maximal magnification the cursor can reach
|
||||
# values below 1 disable the limit (e.g. 0)
|
||||
limit = 0.0
|
||||
|
||||
# time in millseconds the cursor will stay magnified after a shake has ended
|
||||
timeout = 2000
|
||||
|
||||
# show cursor behaviour `tilt`, `rotate`, etc. while shaking
|
||||
effects = false
|
||||
|
||||
# enable ipc events for shake
|
||||
# see #3
|
||||
# see the `ipc` section below
|
||||
ipc = false
|
||||
}
|
||||
}
|
||||
|
@ -211,6 +222,19 @@ plugin:dynamic-cursors {
|
|||
}
|
||||
```
|
||||
|
||||
### ipc
|
||||
This plugin can expose cursor shake events via IPC. This behaviour must be explicitly enabled via the `plugin:dynamic-cursors:shake:ipc` option, as it will spam the socket quite a bit during a shake. These events will appear on [Hyprland's event socket](https://wiki.hyprland.org/IPC/#xdg_runtime_dirhyprhissocket2sock).
|
||||
|
||||
The following events with the described arguments are available, when ipc is enabled:
|
||||
- `shakestart`: fired when a shake is detected.
|
||||
- `shakeupdate`: fired on frame during the shake, has arguments `x,y,trail,diagonal,zoom`:
|
||||
- `x`, `y` are the current cursor position.
|
||||
- `trail` and `diagonal` are two values indicating the distance the mouse travelled, and the diagonal this movement was within for the last second. Their quotient `trail / diagonal` indicates how intense the shaking is.
|
||||
- `zoom` is the current cursor magnification level, as currently shown by this plugin, as customized in the shake configuration.
|
||||
- `shakeend`: fired when a shake has ended (after the `timeout`)
|
||||
|
||||
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.
|
||||
|
||||
## 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.
|
||||
|
||||
|
|
|
@ -12,10 +12,14 @@
|
|||
|
||||
#define CONFIG_SHAKE "shake:enabled"
|
||||
#define CONFIG_SHAKE_NEAREST "shake:nearest"
|
||||
#define CONFIG_SHAKE_THRESHOLD "shake:threshold"
|
||||
#define CONFIG_SHAKE_FACTOR "shake:factor"
|
||||
#define CONFIG_SHAKE_EFFECTS "shake:effects"
|
||||
#define CONFIG_SHAKE_IPC "shake:ipc"
|
||||
#define CONFIG_SHAKE_THRESHOLD "shake:threshold"
|
||||
#define CONFIG_SHAKE_SPEED "shake:speed"
|
||||
#define CONFIG_SHAKE_INFLUENCE "shake:influence"
|
||||
#define CONFIG_SHAKE_BASE "shake:base"
|
||||
#define CONFIG_SHAKE_LIMIT "shake:limit"
|
||||
#define CONFIG_SHAKE_TIMEOUT "shake:timeout"
|
||||
|
||||
#define CONFIG_ROTATE_LENGTH "rotate:length"
|
||||
#define CONFIG_ROTATE_OFFSET "rotate:offset"
|
||||
|
|
|
@ -80,9 +80,6 @@ void CDynamicCursors::renderSoftware(CPointerManager* pointers, SP<CMonitor> pMo
|
|||
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;
|
||||
|
||||
if (box.intersection(CBox{{}, {pMonitor->vecSize}}).empty())
|
||||
return;
|
||||
|
||||
auto texture = pointers->getCurrentCursorTexture();
|
||||
if (!texture)
|
||||
return;
|
||||
|
@ -91,6 +88,9 @@ void CDynamicCursors::renderSoftware(CPointerManager* pointers, SP<CMonitor> pMo
|
|||
box.w *= zoom;
|
||||
box.h *= zoom;
|
||||
|
||||
if (box.intersection(CBox{{}, {pMonitor->vecSize}}).empty())
|
||||
return;
|
||||
|
||||
// we rotate the cursor by our calculated amount
|
||||
box.rot = resultShown.rotation;
|
||||
|
||||
|
|
|
@ -109,8 +109,12 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
|
|||
addConfig(CONFIG_SHAKE_NEAREST, true);
|
||||
addConfig(CONFIG_SHAKE_EFFECTS, false);
|
||||
addConfig(CONFIG_SHAKE_IPC, false);
|
||||
addConfig(CONFIG_SHAKE_THRESHOLD, 4.0f);
|
||||
addConfig(CONFIG_SHAKE_FACTOR, 1.5f);
|
||||
addConfig(CONFIG_SHAKE_THRESHOLD, 6.0f);
|
||||
addConfig(CONFIG_SHAKE_BASE, 4.0F);
|
||||
addConfig(CONFIG_SHAKE_SPEED, 4.0F);
|
||||
addConfig(CONFIG_SHAKE_INFLUENCE, 0.0F);
|
||||
addConfig(CONFIG_SHAKE_LIMIT, 0.0F);
|
||||
addConfig(CONFIG_SHAKE_TIMEOUT, 2000);
|
||||
|
||||
addShapeConfig(CONFIG_TILT_FUNCTION, "negative_quadratic");
|
||||
addShapeConfig(CONFIG_TILT_LIMIT, 5000);
|
||||
|
|
|
@ -1,13 +1,49 @@
|
|||
#include "../globals.hpp"
|
||||
#include "../config/config.hpp"
|
||||
#include "src/config/ConfigManager.hpp"
|
||||
#include "src/helpers/AnimatedVariable.hpp"
|
||||
#include "src/managers/AnimationManager.hpp"
|
||||
#include "src/managers/EventManager.hpp"
|
||||
#include "Shake.hpp"
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <hyprland/src/Compositor.hpp>
|
||||
#include <hyprland/src/debug/Log.hpp>
|
||||
|
||||
CShake::CShake() {
|
||||
// the timing and the bezier are quite crucial, as things will break down if they are just changed slighly
|
||||
// this is not ideal and should be fixed some time in the future, then it may be made configurable (if it has a substatntial enough effect on behaviour)
|
||||
|
||||
int time = 400;
|
||||
|
||||
// add custom bezier (and readd it after config reload)
|
||||
static auto bezier = "dynamic-cursors-magnification";
|
||||
g_pAnimationManager->addBezierWithName(bezier, {0.22, 1.0}, {0.36, 1.0});
|
||||
static const auto PCALLBACK = HyprlandAPI::registerCallbackDynamic( PHANDLE, "configReloaded", [&](void* self, SCallbackInfo&, std::any data) {
|
||||
g_pAnimationManager->addBezierWithName(bezier, {0.22, 1.0}, {0.36, 1.0});
|
||||
});
|
||||
|
||||
// wtf is this struct?
|
||||
static SAnimationPropertyConfig properties = {false, bezier, "", time / 100.F, 1, nullptr, nullptr };
|
||||
properties.pValues = &properties;
|
||||
|
||||
zoom.create(&properties, AVARDAMAGE_NONE);
|
||||
zoom.registerVar();
|
||||
zoom.setValueAndWarp(1);
|
||||
}
|
||||
|
||||
CShake::~CShake() {
|
||||
zoom.unregister();
|
||||
}
|
||||
|
||||
double CShake::update(Vector2D pos) {
|
||||
static auto* const* PTHRESHOLD = (Hyprlang::FLOAT* const*) getConfig(CONFIG_SHAKE_THRESHOLD);
|
||||
static auto* const* PFACTOR = (Hyprlang::FLOAT* const*) getConfig(CONFIG_SHAKE_FACTOR);
|
||||
static auto* const* PBASE = (Hyprlang::FLOAT* const*) getConfig(CONFIG_SHAKE_BASE);
|
||||
static auto* const* PSPEED = (Hyprlang::FLOAT* const*) getConfig(CONFIG_SHAKE_SPEED);
|
||||
static auto* const* PINFLUENCE = (Hyprlang::FLOAT* const*) getConfig(CONFIG_SHAKE_INFLUENCE);
|
||||
static auto* const* PLIMIT = (Hyprlang::FLOAT* const*) getConfig(CONFIG_SHAKE_LIMIT);
|
||||
static auto* const* PTIMEOUT = (Hyprlang::INT* const*) getConfig(CONFIG_SHAKE_TIMEOUT);
|
||||
|
||||
static auto* const* PIPC = (Hyprlang::INT* const*) getConfig(CONFIG_SHAKE_IPC);
|
||||
|
||||
int max = g_pHyprRenderer->m_pMostHzMonitor->refreshRate; // 1s worth of history
|
||||
|
@ -36,19 +72,35 @@ double CShake::update(Vector2D pos) {
|
|||
}
|
||||
double diagonal = Vector2D{left, top}.distance(Vector2D(right, bottom));
|
||||
|
||||
// discard when the diagonal is small, return so we don't have issues with inaccuracies
|
||||
if (diagonal < 100) return 1.0;
|
||||
// if diagonal sufficiently large and over threshold
|
||||
double amount = (trail / diagonal) - **PTHRESHOLD;
|
||||
if (diagonal > 100 && amount > 0) {
|
||||
float delta = 1.F / g_pHyprRenderer->m_pMostHzMonitor->refreshRate;
|
||||
|
||||
double zoom = ((trail / diagonal) - **PTHRESHOLD);
|
||||
float next = this->zoom.goal();
|
||||
|
||||
if (!started) next = **PBASE; // start on base zoom
|
||||
next += delta * (**PSPEED + (amount * amount) * **PINFLUENCE); // increase when moving
|
||||
if (**PLIMIT > 1) next = std::min(**PLIMIT, next); // limit overall zoom
|
||||
|
||||
if (next != this->zoom.goal()) this->zoom = next;
|
||||
this->end = steady_clock::now() + milliseconds(**PTIMEOUT);
|
||||
started = true;
|
||||
} else {
|
||||
if (started && end < std::chrono::steady_clock::now()) {
|
||||
this->zoom = 1;
|
||||
started = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (**PIPC) {
|
||||
if (zoom > 1) {
|
||||
if (started || this->zoom.value() > 1) {
|
||||
if (!ipc) {
|
||||
g_pEventManager->postEvent(SHyprIPCEvent { IPC_SHAKE_START });
|
||||
ipc = true;
|
||||
}
|
||||
|
||||
g_pEventManager->postEvent(SHyprIPCEvent { IPC_SHAKE_UPDATE, std::format("{},{},{},{}", (int) pos.x, (int) pos.y, trail, diagonal) });
|
||||
g_pEventManager->postEvent(SHyprIPCEvent { IPC_SHAKE_UPDATE, std::format("{},{},{},{},{}", (int) pos.x, (int) pos.y, trail, diagonal, this->zoom.value()) });
|
||||
} else {
|
||||
if (ipc) {
|
||||
g_pEventManager->postEvent(SHyprIPCEvent { IPC_SHAKE_END });
|
||||
|
@ -57,14 +109,5 @@ double CShake::update(Vector2D pos) {
|
|||
}
|
||||
}
|
||||
|
||||
// fix jitter by allowing the diagonal to only grow, until we are below the threshold again
|
||||
if (zoom > 0) { // larger than 0 because of factor
|
||||
if (diagonal > this->diagonal)
|
||||
this->diagonal = diagonal;
|
||||
|
||||
zoom = ((trail / this->diagonal) - **PTHRESHOLD);
|
||||
} else this->diagonal = 0;
|
||||
|
||||
// we want ipc to work with factor = 0, so we use it here
|
||||
return std::max(1.0, zoom * **PFACTOR);
|
||||
return this->zoom.value();
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#include "src/helpers/AnimatedVariable.hpp"
|
||||
#include <hyprutils/math/Vector2D.hpp>
|
||||
#include <vector>
|
||||
|
||||
|
@ -6,9 +7,13 @@
|
|||
#define IPC_SHAKE_END "shakeend"
|
||||
|
||||
using namespace Hyprutils::Math;
|
||||
using namespace std::chrono;
|
||||
|
||||
class CShake {
|
||||
public:
|
||||
CShake();
|
||||
~CShake();
|
||||
|
||||
/* calculates the new zoom factor for the current pos */
|
||||
double update(Vector2D pos);
|
||||
|
||||
|
@ -16,8 +21,9 @@ class CShake {
|
|||
/* tracks whether the current shake has already been announced in the ipc */
|
||||
bool ipc = false;
|
||||
|
||||
/* stores last measured diagonal */
|
||||
float diagonal = 0;
|
||||
bool started = false;
|
||||
CAnimatedVariable<float> zoom;
|
||||
steady_clock::time_point end;
|
||||
|
||||
/* ringbuffer for last samples */
|
||||
std::vector<Vector2D> samples;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue