diff --git a/README.md b/README.md index f5ac456..5310b28 100644 --- a/README.md +++ b/README.md @@ -98,10 +98,10 @@ plugin:dynamic-cursors { enabled = true # sets the cursor behaviour, supports these values: - # tilt - tilt the cursor based on x-velocity - # rotate - rotate the cursor based on movement direction + # tilt - tilt the cursor based on x-velocity + # rotate - rotate the cursor based on movement direction # stretch - stretch the cursor shape based on direction and velocity - # none - do not change the cursors behaviour + # none - do not change the cursors behaviour mode = tilt # minimum angle difference in degrees after which the shape is changed @@ -175,7 +175,7 @@ plugin:dynamic-cursors { base = 4.0 # magnification increase per second when continuing to shake speed = 4.0 - # factor the speed is influenced by the current shake intensitiy + # how much the speed is influenced by the current shake intensitiy influence = 0.0 # maximal magnification the cursor can reach @@ -209,7 +209,7 @@ shaperule = shape-name, mode (optional), property: value, property: value, ... - `property: value`: At the end of the rule follow zero or more property-value pairs. These are config values that will be overridden if this rule is active. Only config values from the sections `rotate`, `tilt`, `stretch` as seen above can be used. Here are a few example rules to get you started: -``` +```ini plugin:dynamic-cursors { # apply a 90° offset in rotate mode to the text shape shaperule = text, rotate:offset: 90 @@ -225,12 +225,12 @@ 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: +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. + - `trail` and `diagonal` are two floats, the first indicating the distance the mouse travelled, and second the diagonal this movement was within. Their quotient `trail / diagonal` indicates how intense the shaking is. + - `zoom` is the current cursor magnification level, as currently shown by this plugin, depending on the shake configuration. It is also interpolated smoothly. - `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. @@ -260,6 +260,8 @@ To work on this plugin, you can clone this repository and use the Makefile to bu make load ``` +In some cases when working in a nest, nothing will happen with the plugin loaded. This is because the mouse input is handled differently in a wayland nest. In these cases, set `plugin:dynamic-cursors:ignore_warps` to `false`, to disable warp ignoring, which should fix the issue. + If you want to debug hardware cursors, this plugin also has an additional configuration option, `plugin:dynamic-cursors:hw_debug` which when true will show where the whole cursor buffer is, and also shows when it is updated. Also make sure you disable the plugin on your host session, otherwise your cursor will be rotated twice. diff --git a/src/config/config.hpp b/src/config/config.hpp index b1722c4..95f07ad 100644 --- a/src/config/config.hpp +++ b/src/config/config.hpp @@ -9,6 +9,7 @@ #define CONFIG_MODE "mode" #define CONFIG_THRESHOLD "threshold" #define CONFIG_HW_DEBUG "hw_debug" +#define CONFIG_IGNORE_WARPS "ignore_warps" #define CONFIG_SHAKE "shake:enabled" #define CONFIG_SHAKE_NEAREST "shake:nearest" diff --git a/src/cursor.cpp b/src/cursor.cpp index 3e40c49..633c214 100644 --- a/src/cursor.cpp +++ b/src/cursor.cpp @@ -154,8 +154,10 @@ SP CDynamicCursors::renderHardware(CPointerManager* pointer Debug::log(TRACE, "hardware cursor too big! {} > {}", pointers->currentCursorImage.size, maxSize); return nullptr; } - } else + } else { maxSize = targetSize; + if (maxSize.x < 16 || maxSize.y < 16) maxSize = {16, 16}; // fix some annoying crashes in nest + } if (!state->monitor->cursorSwapchain || maxSize != state->monitor->cursorSwapchain->currentOptions().size || state->monitor->cursorSwapchain->currentOptions().length != 3) { @@ -317,6 +319,9 @@ bool CDynamicCursors::setHardware(CPointerManager* pointers, SPhasCursor()) return; @@ -332,7 +337,18 @@ void CDynamicCursors::onCursorMoved(CPointerManager* pointers) { m->output->moveCursor(CURSORPOS); } + // ignore warp + if (!isMove && **PIGNORE_WARPS) { + auto mode = this->currentMode(); + if (mode) mode->warp(lastPos, pointers->pointerPos); + + if (**PSHAKE) shake.warp(lastPos, pointers->pointerPos); + } + calculate(MOVE); + + isMove = false; + lastPos = pointers->pointerPos; } void CDynamicCursors::setShape(const std::string& shape) { @@ -426,3 +442,7 @@ void CDynamicCursors::calculate(EModeUpdate type) { } } } + +void CDynamicCursors::setMove() { + isMove = true; +} diff --git a/src/cursor.hpp b/src/cursor.hpp index 6bcca4d..b8cb7f9 100644 --- a/src/cursor.hpp +++ b/src/cursor.hpp @@ -35,12 +35,16 @@ class CDynamicCursors { /* hook on setCursorSoftware */ void unsetShape(); + /* hook on move, indicate that next onCursorMoved is actual move */ + void setMove(); + private: SP tick; // current state of the cursor SModeResult resultMode; double resultShake; + Vector2D lastPos; // used for warp compensation SModeResult resultShown; @@ -57,6 +61,9 @@ class CDynamicCursors { // shake CShake shake; + /* is set true if a genuine move is being performed, and will be reset to false after onCursorMoved */ + bool isMove = false; + // calculates the current angle of the cursor, and changes the cursor shape void calculate(EModeUpdate type); }; diff --git a/src/main.cpp b/src/main.cpp index 6c6c3b5..593f480 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -67,6 +67,13 @@ void hkSetCursorSurface(void* thisptr, SP surf, const Vector2D& hots (*(origSetCursorSurface)g_pSetCursorSurfaceHook->m_pOriginal)(thisptr, surf, hotspot); } +typedef void (*origMove)(void*, const Vector2D&); +inline CFunctionHook* g_pMoveHook = nullptr; +void hkMove(void* thisptr, const Vector2D& deltaLogical) { + if (isEnabled()) g_pDynamicCursors->setMove(); + (*(origMove)g_pMoveHook->m_pOriginal)(thisptr, deltaLogical); +} + /* hooks a function hook */ CFunctionHook* hook(std::string name, std::string object, void* function) { auto names = HyprlandAPI::findFunctionsByName(PHANDLE, name); @@ -126,6 +133,7 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { addShapeConfig(CONFIG_ROTATE_OFFSET, 0.0f); addConfig(CONFIG_HW_DEBUG, false); + addConfig(CONFIG_IGNORE_WARPS, true); addRulesConfig(); finishConfig(); @@ -140,6 +148,7 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { g_pRenderHWCursorBufferHook = hook("renderHWCursorBuffer", "CPointerManager", (void*) &hkRenderHWCursorBuffer); g_pSetHWCursorBufferHook = hook("setHWCursorBuffer", "CPointerManager", (void*) &hkSetHWCursorBuffer); g_pOnCursorMovedHook = hook("onCursorMoved", "CPointerManager", (void*) &hkOnCursorMoved); + g_pMoveHook = hook("moveER", "CPointerManager", (void*) &hkMove); // this `ER` makes it faster because `move` is very generic g_pSetCursorFromNameHook = hook("setCursorFromName", "CCursorManager", (void*) &hkSetCursorFromName); g_pSetCursorSurfaceHook = hook("setCursorSurface", "CCursorManager", (void*) &hkSetCursorSurface); diff --git a/src/mode/Mode.hpp b/src/mode/Mode.hpp index 6935f4c..9dccc8d 100644 --- a/src/mode/Mode.hpp +++ b/src/mode/Mode.hpp @@ -11,4 +11,6 @@ class IMode { virtual EModeUpdate strategy() = 0; /* updates the calculations and returns the new angle */ virtual SModeResult update(Vector2D pos) = 0; + /* called on warp, an update will be sent afterwards (probably) */ + virtual void warp(Vector2D old, Vector2D pos) = 0; }; diff --git a/src/mode/ModeRotate.cpp b/src/mode/ModeRotate.cpp index be25804..0369e24 100644 --- a/src/mode/ModeRotate.cpp +++ b/src/mode/ModeRotate.cpp @@ -45,3 +45,7 @@ SModeResult CModeRotate::update(Vector2D pos) { return result; } + +void CModeRotate::warp(Vector2D old, Vector2D pos) { + end += (pos - old); +} diff --git a/src/mode/ModeRotate.hpp b/src/mode/ModeRotate.hpp index dbb60e4..44b6ef9 100644 --- a/src/mode/ModeRotate.hpp +++ b/src/mode/ModeRotate.hpp @@ -9,6 +9,7 @@ class CModeRotate : public IMode { public: virtual EModeUpdate strategy(); virtual SModeResult update(Vector2D pos); + virtual void warp(Vector2D old, Vector2D pos); private: diff --git a/src/mode/ModeStretch.cpp b/src/mode/ModeStretch.cpp index d70f20b..ef4c930 100644 --- a/src/mode/ModeStretch.cpp +++ b/src/mode/ModeStretch.cpp @@ -40,3 +40,10 @@ SModeResult CModeStretch::update(Vector2D pos) { return result; } + +void CModeStretch::warp(Vector2D old, Vector2D pos) { + auto delta = pos - old; + + for (auto& sample : samples) + sample += delta; +} diff --git a/src/mode/ModeStretch.hpp b/src/mode/ModeStretch.hpp index a0bbc38..3c9fa76 100644 --- a/src/mode/ModeStretch.hpp +++ b/src/mode/ModeStretch.hpp @@ -6,6 +6,7 @@ class CModeStretch : public IMode { public: virtual EModeUpdate strategy(); virtual SModeResult update(Vector2D pos); + virtual void warp(Vector2D old, Vector2D pos); private: diff --git a/src/mode/ModeTilt.cpp b/src/mode/ModeTilt.cpp index 156314f..05a6475 100644 --- a/src/mode/ModeTilt.cpp +++ b/src/mode/ModeTilt.cpp @@ -30,3 +30,10 @@ SModeResult CModeTilt::update(Vector2D pos) { result.rotation = activation(function, limit, speed) * (PI / 3); // 120° in both directions return result; } + +void CModeTilt::warp(Vector2D old, Vector2D pos) { + auto delta = pos - old; + + for (auto& sample : samples) + sample += delta; +} diff --git a/src/mode/ModeTilt.hpp b/src/mode/ModeTilt.hpp index a83b9e8..fd57d01 100644 --- a/src/mode/ModeTilt.hpp +++ b/src/mode/ModeTilt.hpp @@ -6,6 +6,7 @@ class CModeTilt : public IMode { public: virtual EModeUpdate strategy(); virtual SModeResult update(Vector2D pos); + virtual void warp(Vector2D old, Vector2D pos); private: diff --git a/src/other/Shake.cpp b/src/other/Shake.cpp index 9245d70..ccd59cb 100644 --- a/src/other/Shake.cpp +++ b/src/other/Shake.cpp @@ -111,3 +111,10 @@ double CShake::update(Vector2D pos) { return this->zoom.value(); } + +void CShake::warp(Vector2D old, Vector2D pos) { + auto delta = pos - old; + + for (auto& sample : samples) + sample += delta; +} diff --git a/src/other/Shake.hpp b/src/other/Shake.hpp index 4fc26ab..f9df942 100644 --- a/src/other/Shake.hpp +++ b/src/other/Shake.hpp @@ -16,6 +16,8 @@ class CShake { /* calculates the new zoom factor for the current pos */ double update(Vector2D pos); + /* called when a cursor warp has happened (to avoid magnifying on warps) */ + void warp(Vector2D old, Vector2D pos); private: /* tracks whether the current shake has already been announced in the ipc */