fix: ignore warps on modes and shake

This commit is contained in:
Virt 2024-08-25 22:58:55 +02:00
commit 55e8b316ce
14 changed files with 80 additions and 9 deletions

View file

@ -175,7 +175,7 @@ plugin:dynamic-cursors {
base = 4.0 base = 4.0
# magnification increase per second when continuing to shake # magnification increase per second when continuing to shake
speed = 4.0 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 influence = 0.0
# maximal magnification the cursor can reach # 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. - `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: Here are a few example rules to get you started:
``` ```ini
plugin:dynamic-cursors { plugin:dynamic-cursors {
# apply a 90° offset in rotate mode to the text shape # apply a 90° offset in rotate mode to the text shape
shaperule = text, rotate:offset: 90 shaperule = text, rotate:offset: 90
@ -225,12 +225,12 @@ plugin:dynamic-cursors {
### ipc ### 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). 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. - `shakestart`: fired when a shake is detected.
- `shakeupdate`: fired on frame during the shake, has arguments `x,y,trail,diagonal,zoom`: - `shakeupdate`: fired on frame during the shake, has arguments `x,y,trail,diagonal,zoom`:
- `x`, `y` are the current cursor position. - `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. - `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, as customized in the shake configuration. - `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`) - `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. 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 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. 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. Also make sure you disable the plugin on your host session, otherwise your cursor will be rotated twice.

View file

@ -9,6 +9,7 @@
#define CONFIG_MODE "mode" #define CONFIG_MODE "mode"
#define CONFIG_THRESHOLD "threshold" #define CONFIG_THRESHOLD "threshold"
#define CONFIG_HW_DEBUG "hw_debug" #define CONFIG_HW_DEBUG "hw_debug"
#define CONFIG_IGNORE_WARPS "ignore_warps"
#define CONFIG_SHAKE "shake:enabled" #define CONFIG_SHAKE "shake:enabled"
#define CONFIG_SHAKE_NEAREST "shake:nearest" #define CONFIG_SHAKE_NEAREST "shake:nearest"

View file

@ -154,8 +154,10 @@ SP<Aquamarine::IBuffer> CDynamicCursors::renderHardware(CPointerManager* pointer
Debug::log(TRACE, "hardware cursor too big! {} > {}", pointers->currentCursorImage.size, maxSize); Debug::log(TRACE, "hardware cursor too big! {} > {}", pointers->currentCursorImage.size, maxSize);
return nullptr; return nullptr;
} }
} else } else {
maxSize = targetSize; 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) { 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, SP<CPointerManager:
Handles cursor move events. Handles cursor move events.
*/ */
void CDynamicCursors::onCursorMoved(CPointerManager* pointers) { void CDynamicCursors::onCursorMoved(CPointerManager* pointers) {
static auto* const* PSHAKE = (Hyprlang::INT* const*) getConfig(CONFIG_SHAKE);
static auto* const* PIGNORE_WARPS = (Hyprlang::INT* const*) getConfig(CONFIG_IGNORE_WARPS);
if (!pointers->hasCursor()) if (!pointers->hasCursor())
return; return;
@ -332,7 +337,18 @@ void CDynamicCursors::onCursorMoved(CPointerManager* pointers) {
m->output->moveCursor(CURSORPOS); 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); calculate(MOVE);
isMove = false;
lastPos = pointers->pointerPos;
} }
void CDynamicCursors::setShape(const std::string& shape) { void CDynamicCursors::setShape(const std::string& shape) {
@ -426,3 +442,7 @@ void CDynamicCursors::calculate(EModeUpdate type) {
} }
} }
} }
void CDynamicCursors::setMove() {
isMove = true;
}

View file

@ -35,12 +35,16 @@ class CDynamicCursors {
/* hook on setCursorSoftware */ /* hook on setCursorSoftware */
void unsetShape(); void unsetShape();
/* hook on move, indicate that next onCursorMoved is actual move */
void setMove();
private: private:
SP<CEventLoopTimer> tick; SP<CEventLoopTimer> tick;
// current state of the cursor // current state of the cursor
SModeResult resultMode; SModeResult resultMode;
double resultShake; double resultShake;
Vector2D lastPos; // used for warp compensation
SModeResult resultShown; SModeResult resultShown;
@ -57,6 +61,9 @@ class CDynamicCursors {
// shake // shake
CShake 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 // calculates the current angle of the cursor, and changes the cursor shape
void calculate(EModeUpdate type); void calculate(EModeUpdate type);
}; };

View file

@ -67,6 +67,13 @@ void hkSetCursorSurface(void* thisptr, SP<CWLSurface> surf, const Vector2D& hots
(*(origSetCursorSurface)g_pSetCursorSurfaceHook->m_pOriginal)(thisptr, surf, hotspot); (*(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 */ /* hooks a function hook */
CFunctionHook* hook(std::string name, std::string object, void* function) { CFunctionHook* hook(std::string name, std::string object, void* function) {
auto names = HyprlandAPI::findFunctionsByName(PHANDLE, name); 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); addShapeConfig(CONFIG_ROTATE_OFFSET, 0.0f);
addConfig(CONFIG_HW_DEBUG, false); addConfig(CONFIG_HW_DEBUG, false);
addConfig(CONFIG_IGNORE_WARPS, true);
addRulesConfig(); addRulesConfig();
finishConfig(); finishConfig();
@ -140,6 +148,7 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
g_pRenderHWCursorBufferHook = hook("renderHWCursorBuffer", "CPointerManager", (void*) &hkRenderHWCursorBuffer); g_pRenderHWCursorBufferHook = hook("renderHWCursorBuffer", "CPointerManager", (void*) &hkRenderHWCursorBuffer);
g_pSetHWCursorBufferHook = hook("setHWCursorBuffer", "CPointerManager", (void*) &hkSetHWCursorBuffer); g_pSetHWCursorBufferHook = hook("setHWCursorBuffer", "CPointerManager", (void*) &hkSetHWCursorBuffer);
g_pOnCursorMovedHook = hook("onCursorMoved", "CPointerManager", (void*) &hkOnCursorMoved); 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_pSetCursorFromNameHook = hook("setCursorFromName", "CCursorManager", (void*) &hkSetCursorFromName);
g_pSetCursorSurfaceHook = hook("setCursorSurface", "CCursorManager", (void*) &hkSetCursorSurface); g_pSetCursorSurfaceHook = hook("setCursorSurface", "CCursorManager", (void*) &hkSetCursorSurface);

View file

@ -11,4 +11,6 @@ class IMode {
virtual EModeUpdate strategy() = 0; virtual EModeUpdate strategy() = 0;
/* updates the calculations and returns the new angle */ /* updates the calculations and returns the new angle */
virtual SModeResult update(Vector2D pos) = 0; virtual SModeResult update(Vector2D pos) = 0;
/* called on warp, an update will be sent afterwards (probably) */
virtual void warp(Vector2D old, Vector2D pos) = 0;
}; };

View file

@ -45,3 +45,7 @@ SModeResult CModeRotate::update(Vector2D pos) {
return result; return result;
} }
void CModeRotate::warp(Vector2D old, Vector2D pos) {
end += (pos - old);
}

View file

@ -9,6 +9,7 @@ class CModeRotate : public IMode {
public: public:
virtual EModeUpdate strategy(); virtual EModeUpdate strategy();
virtual SModeResult update(Vector2D pos); virtual SModeResult update(Vector2D pos);
virtual void warp(Vector2D old, Vector2D pos);
private: private:

View file

@ -40,3 +40,10 @@ SModeResult CModeStretch::update(Vector2D pos) {
return result; return result;
} }
void CModeStretch::warp(Vector2D old, Vector2D pos) {
auto delta = pos - old;
for (auto& sample : samples)
sample += delta;
}

View file

@ -6,6 +6,7 @@ class CModeStretch : public IMode {
public: public:
virtual EModeUpdate strategy(); virtual EModeUpdate strategy();
virtual SModeResult update(Vector2D pos); virtual SModeResult update(Vector2D pos);
virtual void warp(Vector2D old, Vector2D pos);
private: private:

View file

@ -30,3 +30,10 @@ SModeResult CModeTilt::update(Vector2D pos) {
result.rotation = activation(function, limit, speed) * (PI / 3); // 120° in both directions result.rotation = activation(function, limit, speed) * (PI / 3); // 120° in both directions
return result; return result;
} }
void CModeTilt::warp(Vector2D old, Vector2D pos) {
auto delta = pos - old;
for (auto& sample : samples)
sample += delta;
}

View file

@ -6,6 +6,7 @@ class CModeTilt : public IMode {
public: public:
virtual EModeUpdate strategy(); virtual EModeUpdate strategy();
virtual SModeResult update(Vector2D pos); virtual SModeResult update(Vector2D pos);
virtual void warp(Vector2D old, Vector2D pos);
private: private:

View file

@ -111,3 +111,10 @@ double CShake::update(Vector2D pos) {
return this->zoom.value(); return this->zoom.value();
} }
void CShake::warp(Vector2D old, Vector2D pos) {
auto delta = pos - old;
for (auto& sample : samples)
sample += delta;
}

View file

@ -16,6 +16,8 @@ class 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);
/* called when a cursor warp has happened (to avoid magnifying on warps) */
void warp(Vector2D old, Vector2D pos);
private: private:
/* tracks whether the current shake has already been announced in the ipc */ /* tracks whether the current shake has already been announced in the ipc */