From 416f6efc2f55fe4364606a4980ccf5c1a0d03fbf Mon Sep 17 00:00:00 2001 From: Virt <41426325+VirtCode@users.noreply.github.com> Date: Wed, 26 Jun 2024 17:14:16 +0200 Subject: [PATCH] feat: tilting simulation mode --- README.md | 2 +- src/cursor.cpp | 144 ++++++++++++++++++++++++++++++++++++++++++------ src/cursor.hpp | 15 ++++- src/globals.hpp | 5 ++ src/main.cpp | 24 ++++++++ 5 files changed, 169 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index c3aae41..f2305fb 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ This plugin is still very early in its development. **Currently, only the `-git` - [X] Stick simulation - [ ] Per-shape length and starting angle (at least with serverside cursors) - [ ] Pendulum simulation -- [ ] Air Drag simulation +- [X] Air Drag simulation If anything here sounds interesting to you, don't hesitate to contribute. diff --git a/src/cursor.cpp b/src/cursor.cpp index bc70dfb..48a6d1c 100644 --- a/src/cursor.cpp +++ b/src/cursor.cpp @@ -1,8 +1,9 @@ #include "globals.hpp" +#include "src/debug/Log.hpp" -#include #include #include +#include #define private public #include @@ -197,6 +198,22 @@ bool CDynamicCursors::setHardware(CPointerManager* pointers, SPgetDataStaticPtr(); + return !strcmp(*PMODE, "stick"); +} + +/* +Should the cursor be updated after tick? +*/ +bool shouldUpdate() { + static auto const* PMODE = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_MODE)->getDataStaticPtr(); + return !strcmp(*PMODE, "air"); +} + /* Handles cursor move events. */ @@ -204,8 +221,6 @@ void CDynamicCursors::onCursorMoved(CPointerManager* pointers) { if (!pointers->hasCursor()) return; - bool changed = calculate(&pointers->pointerPos); - for (auto& m : g_pCompositor->m_vMonitors) { auto state = pointers->stateFor(m); @@ -216,19 +231,118 @@ void CDynamicCursors::onCursorMoved(CPointerManager* pointers) { const auto CURSORPOS = pointers->getCursorPosForMonitor(m); m->output->impl->move_cursor(m->output, CURSORPOS.x, CURSORPOS.y); + } - // we set a new hardware cursor if the angle has changed significantly - if (changed) - pointers->attemptHardwareCursor(state); + if (shouldMove()) calculate(); +} + +void CDynamicCursors::onTick(CPointerManager* pointers) { + if (shouldUpdate()) calculate(); +} + +void CDynamicCursors::calculate() { + static auto const* PMODE = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_MODE)->getDataStaticPtr(); + + double angle = 0; + if (!strcmp(*PMODE, "stick")) + angle = calculateStick(); + else if (!strcmp(*PMODE, "air")) + angle = calculateAir(); + + // we only consider the angle changed if it is larger than 1 degree + if (abs(this->angle - angle) > (PI / 180)) { + this->angle = angle; + + // damage software and change hardware cursor shape + g_pPointerManager->damageIfSoftware(); + + for (auto& m : g_pCompositor->m_vMonitors) { + auto state = g_pPointerManager->stateFor(m); + if (state->hardwareFailed || !state->entered) + continue; + + g_pPointerManager->attemptHardwareCursor(state); + } } } -bool CDynamicCursors::calculate(Vector2D* pos) { +double airFunction(double speed) { + static auto const* PFUNCTION = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_FUNCTION)->getDataStaticPtr(); + static auto* const* PMASS = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_MASS)->getDataStaticPtr(); + double mass = **PMASS; + + double result = 0; + if (!strcmp(*PFUNCTION, "linear")) { + + result = speed / **PMASS; + + } else if (!strcmp(*PFUNCTION, "quadratic")) { + + + // (1 / m²) * x², is a quadratic function which will reach 1 at m + result = (1.0 / (mass * mass)) * (speed * speed); + result *= (speed > 0 ? 1 : -1); + + } else if (!strcmp(*PFUNCTION, "negative_quadratic")) { + + float x = std::abs(speed); + // (-1 / m²) * (x - m)² + 1, is a quadratic function with the inverse curvature which will reach 1 at m + result = (-1.0 / (mass * mass)) * ((x - mass) * (x - mass)) + 1; + if (x > mass) result = 1; // need to clamp manually, as the function would decrease again + + result *= (speed > 0 ? 1 : -1); + } else { + Debug::log(WARN, "[dynamic-cursors] unknown air function specified"); + } + + return std::clamp(result, -1.0, 1.0); +} + +double CDynamicCursors::calculateAir() { + // create samples array + int max = g_pHyprRenderer->m_pMostHzMonitor->refreshRate / 10; // 100ms worth of history + samples.resize(max); + + // capture current sample + samples[samples_index] = Vector2D{g_pPointerManager->pointerPos}; + int current = samples_index; + 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::calculateStick() { static auto* const* PLENGTH = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_LENGTH)->getDataStaticPtr(); + auto pos = g_pPointerManager->pointerPos; + // translate to origin - end.x -= pos->x; - end.y -= pos->y; + end.x -= pos.x; + end.y -= pos.y; // normalize double size = end.size(); @@ -245,14 +359,8 @@ bool CDynamicCursors::calculate(Vector2D* pos) { angle += PI; // translate back - end.x += pos->x; - end.y += pos->y; + end.x += pos.x; + end.y += pos.y; - // we only consider the angle changed if it is larger than 1 degree - if (abs(this->angle - angle) > (PI / 180)) { - this->angle = angle; - return true; - } - - return false; + return angle; } diff --git a/src/cursor.hpp b/src/cursor.hpp index 460a454..1a7497f 100644 --- a/src/cursor.hpp +++ b/src/cursor.hpp @@ -10,6 +10,8 @@ class CDynamicCursors { public: /* hook on onCursorMoved */ void onCursorMoved(CPointerManager* pointers); + /* called on tick */ + void onTick(CPointerManager* pointers); /* hook on renderSoftwareCursorsFor */ void renderSoftware(CPointerManager* pointers, SP pMonitor, timespec* now, CRegion& damage, std::optional overridePos); @@ -24,10 +26,19 @@ class CDynamicCursors { // current angle of the cursor in radiants double angle; - // calculates the current angle of the cursor, returns whether the angle has changed - bool calculate(Vector2D* pos); + // calculates the current angle of the cursor, and changes the cursor shape + void calculate(); + + // calculate the angle of the cursor if stick + double calculateStick(); // this is the end of the virtual stick Vector2D end; + + // calculate the angle of the cursor if air + double calculateAir(); + // ring buffer of last position samples + std::vector samples; + int samples_index = 0; }; inline std::unique_ptr g_pDynamicCursors; diff --git a/src/globals.hpp b/src/globals.hpp index 014184c..66846ee 100644 --- a/src/globals.hpp +++ b/src/globals.hpp @@ -4,6 +4,11 @@ #define CONFIG_ENABLED "plugin:dynamic-cursors:enabled" #define CONFIG_LENGTH "plugin:dynamic-cursors:length" +#define CONFIG_MODE "plugin:dynamic-cursors:mode" +#define CONFIG_MASS "plugin:dynamic-cursors:mass" +#define CONFIG_FUNCTION "plugin:dynamic-cursors:function" #define CONFIG_HW_DEBUG "plugin:dynamic-cursors:hw_debug" inline HANDLE PHANDLE = nullptr; + +inline wl_event_source* tick = nullptr; diff --git a/src/main.cpp b/src/main.cpp index 9d9229f..1b61852 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,11 +2,13 @@ #include #include #include +#include #include #include #include "globals.hpp" #include "cursor.hpp" +#include "src/managers/PointerManager.hpp" typedef void (*origRenderSofwareCursorsFor)(void*, SP, timespec*, CRegion&, std::optional); inline CFunctionHook* g_pRenderSoftwareCursorsForHook = nullptr; @@ -53,6 +55,19 @@ 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()); + + const int TIMEOUT = g_pHyprRenderer->m_pMostHzMonitor ? 1000.0 / g_pHyprRenderer->m_pMostHzMonitor->refreshRate : 16; + wl_event_source_timer_update(tick, TIMEOUT); + + return 0; +} + APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { PHANDLE = handle; @@ -86,9 +101,18 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { g_pOnCursorMovedHook = HyprlandAPI::createFunctionHook(PHANDLE, ON_CURSOR_MOVED_METHODS[0].address, (void*) &hkOnCursorMoved); g_pOnCursorMovedHook->hook(); + // for some damn reason this tick handler does not work + // static auto P_TICK = HyprlandAPI::registerCallbackDynamic(PHANDLE, "tick", [](void* self, SCallbackInfo& info, std::any data) { std::cout << "ticking?" << "\n"; }); + // so we have to use this (stolen from hyprtrails) + tick = wl_event_loop_add_timer(g_pCompositor->m_sWLEventLoop, &onTick, nullptr); + wl_event_source_timer_update(tick, 1); + HyprlandAPI::addConfigValue(PHANDLE, CONFIG_ENABLED, Hyprlang::INT{1}); + HyprlandAPI::addConfigValue(PHANDLE, CONFIG_MODE, Hyprlang::STRING{"air"}); + HyprlandAPI::addConfigValue(PHANDLE, CONFIG_FUNCTION, Hyprlang::STRING{"negative_quadratic"}); HyprlandAPI::addConfigValue(PHANDLE, CONFIG_LENGTH, Hyprlang::INT{20}); HyprlandAPI::addConfigValue(PHANDLE, CONFIG_HW_DEBUG, Hyprlang::INT{0}); + HyprlandAPI::addConfigValue(PHANDLE, CONFIG_MASS, Hyprlang::INT{5000}); HyprlandAPI::reloadConfig();