feat: new shake to find behaviour

This commit is contained in:
Virt 2024-08-25 20:00:16 +02:00
commit 553dc93cc0
6 changed files with 118 additions and 37 deletions

View file

@ -25,7 +25,7 @@ https://github.com/VirtCode/hypr-dynamic-cursors/assets/41426325/7b8289e7-9dd2-4
### shake to find ### 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`. 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 ## 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: 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 # enables shake to find
enabled = true 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 # use nearest-neighbour (pixelated) scaling when shaking
# may look weird when effects are enabled # may look weird when effects are enabled
nearest = true 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 # enable ipc events for shake
# see #3 # see the `ipc` section below
ipc = false 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 ## 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

@ -12,10 +12,14 @@
#define CONFIG_SHAKE "shake:enabled" #define CONFIG_SHAKE "shake:enabled"
#define CONFIG_SHAKE_NEAREST "shake:nearest" #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_EFFECTS "shake:effects"
#define CONFIG_SHAKE_IPC "shake:ipc" #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_LENGTH "rotate:length"
#define CONFIG_ROTATE_OFFSET "rotate:offset" #define CONFIG_ROTATE_OFFSET "rotate:offset"

View file

@ -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.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; 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(); auto texture = pointers->getCurrentCursorTexture();
if (!texture) if (!texture)
return; return;
@ -91,6 +88,9 @@ void CDynamicCursors::renderSoftware(CPointerManager* pointers, SP<CMonitor> pMo
box.w *= zoom; box.w *= zoom;
box.h *= zoom; box.h *= zoom;
if (box.intersection(CBox{{}, {pMonitor->vecSize}}).empty())
return;
// we rotate the cursor by our calculated amount // we rotate the cursor by our calculated amount
box.rot = resultShown.rotation; box.rot = resultShown.rotation;

View file

@ -109,8 +109,12 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
addConfig(CONFIG_SHAKE_NEAREST, 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, 4.0f); addConfig(CONFIG_SHAKE_THRESHOLD, 6.0f);
addConfig(CONFIG_SHAKE_FACTOR, 1.5f); 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_FUNCTION, "negative_quadratic");
addShapeConfig(CONFIG_TILT_LIMIT, 5000); addShapeConfig(CONFIG_TILT_LIMIT, 5000);

View file

@ -1,13 +1,49 @@
#include "../globals.hpp" #include "../globals.hpp"
#include "../config/config.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 "src/managers/EventManager.hpp"
#include "Shake.hpp" #include "Shake.hpp"
#include <algorithm>
#include <chrono>
#include <hyprland/src/Compositor.hpp> #include <hyprland/src/Compositor.hpp>
#include <hyprland/src/debug/Log.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) { double CShake::update(Vector2D pos) {
static auto* const* PTHRESHOLD = (Hyprlang::FLOAT* const*) getConfig(CONFIG_SHAKE_THRESHOLD); 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); static auto* const* PIPC = (Hyprlang::INT* const*) getConfig(CONFIG_SHAKE_IPC);
int max = g_pHyprRenderer->m_pMostHzMonitor->refreshRate; // 1s worth of history 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)); 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 sufficiently large and over threshold
if (diagonal < 100) return 1.0; 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 (**PIPC) {
if (zoom > 1) { if (started || this->zoom.value() > 1) {
if (!ipc) { if (!ipc) {
g_pEventManager->postEvent(SHyprIPCEvent { IPC_SHAKE_START }); g_pEventManager->postEvent(SHyprIPCEvent { IPC_SHAKE_START });
ipc = true; 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 { } else {
if (ipc) { if (ipc) {
g_pEventManager->postEvent(SHyprIPCEvent { IPC_SHAKE_END }); 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 return this->zoom.value();
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);
} }

View file

@ -1,3 +1,4 @@
#include "src/helpers/AnimatedVariable.hpp"
#include <hyprutils/math/Vector2D.hpp> #include <hyprutils/math/Vector2D.hpp>
#include <vector> #include <vector>
@ -6,9 +7,13 @@
#define IPC_SHAKE_END "shakeend" #define IPC_SHAKE_END "shakeend"
using namespace Hyprutils::Math; using namespace Hyprutils::Math;
using namespace std::chrono;
class CShake { class CShake {
public: public:
CShake();
~CShake();
/* calculates the new zoom factor for the current pos */ /* calculates the new zoom factor for the current pos */
double update(Vector2D pos); double update(Vector2D pos);
@ -16,8 +21,9 @@ class CShake {
/* tracks whether the current shake has already been announced in the ipc */ /* tracks whether the current shake has already been announced in the ipc */
bool ipc = false; bool ipc = false;
/* stores last measured diagonal */ bool started = false;
float diagonal = 0; CAnimatedVariable<float> zoom;
steady_clock::time_point end;
/* ringbuffer for last samples */ /* ringbuffer for last samples */
std::vector<Vector2D> samples; std::vector<Vector2D> samples;