From 9a9830034c59a22164117f67c4cb94986b6f67f2 Mon Sep 17 00:00:00 2001 From: Virt <41426325+VirtCode@users.noreply.github.com> Date: Fri, 21 Jun 2024 16:06:55 +0200 Subject: [PATCH] feat: rotate cursor around hotspot --- Makefile | 2 +- src/{render.cpp => cursor.cpp} | 34 ++++---- src/{render.hpp => cursor.hpp} | 2 +- src/globals.hpp | 2 + src/hyprland/math.cpp | 148 +++++++++++++++++++++++++++++++++ src/hyprland/math.hpp | 13 +++ src/main.cpp | 13 +-- src/renderer.cpp | 137 ++++++++++++++++++++++++++++++ src/renderer.hpp | 5 ++ 9 files changed, 334 insertions(+), 22 deletions(-) rename src/{render.cpp => cursor.cpp} (63%) rename src/{render.hpp => cursor.hpp} (92%) create mode 100644 src/hyprland/math.cpp create mode 100644 src/hyprland/math.hpp create mode 100644 src/renderer.cpp create mode 100644 src/renderer.hpp diff --git a/Makefile b/Makefile index 1eb1391..65fe286 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ PLUGIN_NAME=dynamic-cursors -SOURCE_FILES=$(wildcard ./src/*.cpp) +SOURCE_FILES=$(wildcard ./src/*.cpp ./src/*/*.cpp) all: $(PLUGIN_NAME).so diff --git a/src/render.cpp b/src/cursor.cpp similarity index 63% rename from src/render.cpp rename to src/cursor.cpp index 963dac9..b86ef1c 100644 --- a/src/render.cpp +++ b/src/cursor.cpp @@ -1,13 +1,17 @@ #include "globals.hpp" #include -#include #define private public #include +#include #undef private -#include "render.hpp" +#include +#include + +#include "cursor.hpp" +#include "renderer.hpp" void CDynamicCursors::render(CPointerManager* pointers, SP pMonitor, timespec* now, CRegion& damage, std::optional overridePos) { @@ -36,31 +40,33 @@ void CDynamicCursors::render(CPointerManager* pointers, SP pMonitor, t box.scale(pMonitor->scale); box.rot = this->calculate(&pointers->pointerPos); - g_pHyprOpenGL->renderTextureWithDamage(texture, &box, &damage, 1.F); + renderCursorTextureInternalWithDamage(texture, &box, &damage, 1.F, pointers->currentCursorImage.hotspot); } double CDynamicCursors::calculate(Vector2D* pos) { + static auto* const* PLENGTH = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_LENGTH)->getDataStaticPtr(); + // translate to origin - this->end.x -= pos->x; - this->end.y -= pos->y; + end.x -= pos->x; + end.y -= pos->y; // normalize - double size = this->end.size(); - this->end.x /= size; - this->end.y /= size; + double size = end.size(); + end.x /= size; + end.y /= size; // scale to length - this->end.x *= this->size; - this->end.y *= this->size; + end.x *= **PLENGTH; + end.y *= **PLENGTH; // calculate angle - double angle = -atan(this->end.x / this->end.y); - if (this->end.y > 0) angle += PI; + double angle = -atan(end.x / end.y); + if (end.y > 0) angle += PI; angle += PI; // translate back - this->end.x += pos->x; - this->end.y += pos->y; + end.x += pos->x; + end.y += pos->y; return angle; } diff --git a/src/render.hpp b/src/cursor.hpp similarity index 92% rename from src/render.hpp rename to src/cursor.hpp index 989040a..6b17624 100644 --- a/src/render.hpp +++ b/src/cursor.hpp @@ -6,10 +6,10 @@ class CDynamicCursors; class CDynamicCursors { public: + /* hook on renderSoftwareCursorsFor */ void render(CPointerManager* pointers, SP pMonitor, timespec* now, CRegion& damage, std::optional overridePos); private: - double size = 10; // calculates the current angle of the cursor double calculate(Vector2D* pos); // this is the end of the virtual stick diff --git a/src/globals.hpp b/src/globals.hpp index 66c4f04..2178e12 100644 --- a/src/globals.hpp +++ b/src/globals.hpp @@ -2,4 +2,6 @@ #include +#define CONFIG_LENGTH "plugin:dynamic-cursors:length" + inline HANDLE PHANDLE = nullptr; diff --git a/src/hyprland/math.cpp b/src/hyprland/math.cpp new file mode 100644 index 0000000..33ad4cc --- /dev/null +++ b/src/hyprland/math.cpp @@ -0,0 +1,148 @@ +#include + +/* +The following functions are copied 1:1 from the hyprland codebase. +This is nessecary because we cannot use functions which are not declared in any header. +*/ + +void matrixIdentity(float mat[9]) { + static const float identity[9] = { + 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + }; + memcpy(mat, identity, sizeof(identity)); +} + +void matrixMultiply(float mat[9], const float a[9], const float b[9]) { + float product[9]; + + product[0] = a[0] * b[0] + a[1] * b[3] + a[2] * b[6]; + product[1] = a[0] * b[1] + a[1] * b[4] + a[2] * b[7]; + product[2] = a[0] * b[2] + a[1] * b[5] + a[2] * b[8]; + + product[3] = a[3] * b[0] + a[4] * b[3] + a[5] * b[6]; + product[4] = a[3] * b[1] + a[4] * b[4] + a[5] * b[7]; + product[5] = a[3] * b[2] + a[4] * b[5] + a[5] * b[8]; + + product[6] = a[6] * b[0] + a[7] * b[3] + a[8] * b[6]; + product[7] = a[6] * b[1] + a[7] * b[4] + a[8] * b[7]; + product[8] = a[6] * b[2] + a[7] * b[5] + a[8] * b[8]; + + memcpy(mat, product, sizeof(product)); +} + +void matrixTranslate(float mat[9], float x, float y) { + float translate[9] = { + 1.0f, 0.0f, x, 0.0f, 1.0f, y, 0.0f, 0.0f, 1.0f, + }; + wlr_matrix_multiply(mat, mat, translate); +} + +void matrixRotate(float mat[9], float rad) { + float rotate[9] = { + cos(rad), -sin(rad), 0.0f, sin(rad), cos(rad), 0.0f, 0.0f, 0.0f, 1.0f, + }; + wlr_matrix_multiply(mat, mat, rotate); +} + +static std::unordered_map> transforms = { + {HYPRUTILS_TRANSFORM_NORMAL, + { + 1.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + }}, + {HYPRUTILS_TRANSFORM_90, + { + 0.0f, + 1.0f, + 0.0f, + -1.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + }}, + {HYPRUTILS_TRANSFORM_180, + { + -1.0f, + 0.0f, + 0.0f, + 0.0f, + -1.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + }}, + {HYPRUTILS_TRANSFORM_270, + { + 0.0f, + -1.0f, + 0.0f, + 1.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + }}, + {HYPRUTILS_TRANSFORM_FLIPPED, + { + -1.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + }}, + {HYPRUTILS_TRANSFORM_FLIPPED_90, + { + 0.0f, + 1.0f, + 0.0f, + 1.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + }}, + {HYPRUTILS_TRANSFORM_FLIPPED_180, + { + 1.0f, + 0.0f, + 0.0f, + 0.0f, + -1.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + }}, + {HYPRUTILS_TRANSFORM_FLIPPED_270, + { + 0.0f, + -1.0f, + 0.0f, + -1.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f, + }}, +}; + +void matrixTransform(float mat[9], eTransform transform) { + matrixMultiply(mat, mat, transforms.at(transform).data()); +} diff --git a/src/hyprland/math.hpp b/src/hyprland/math.hpp new file mode 100644 index 0000000..5bf8c50 --- /dev/null +++ b/src/hyprland/math.hpp @@ -0,0 +1,13 @@ +#include +#include + +/* +The following functions are copied 1:1 from the hyprland codebase. +This is nessecary because we cannot use functions which are not declared in any header. +*/ + +void matrixTransform(float mat[9], eTransform transform); +void matrixTranslate(float mat[9], float x, float y); +void matrixMultiply(float mat[9], const float a[9], const float b[9]); +void matrixIdentity(float mat[9]); +void matrixRotate(float mat[9], float rad); diff --git a/src/main.cpp b/src/main.cpp index 84ee21a..9c820c4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,17 +2,16 @@ #include #include #include +#include #include #include "globals.hpp" -#include "render.hpp" +#include "cursor.hpp" typedef void (*origRenderSofwareCursorsFor)(void*, SP, timespec*, CRegion&, std::optional); inline CFunctionHook* g_pRenderSoftwareCursorsForHook = nullptr; - void hkRenderSoftwareCursorsFor(void* thisptr, SP pMonitor, timespec* now, CRegion& damage, std::optional overridePos) { g_pDynamicCursors->render((CPointerManager*) thisptr, pMonitor, now, damage, overridePos); - //(*(origRenderSofwareCursorsFor)g_pRenderSoftwareCursorsForHook->m_pOriginal)(thisptr, pMonitor, now, damage, overridePos); } APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { @@ -26,11 +25,13 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { throw std::runtime_error("[dynamic-cursors] Version mismatch"); } - static const auto METHODS = HyprlandAPI::findFunctionsByName(PHANDLE, "renderSoftwareCursorsFor"); - g_pRenderSoftwareCursorsForHook = HyprlandAPI::createFunctionHook(PHANDLE, METHODS[0].address, (void*) &hkRenderSoftwareCursorsFor); + g_pDynamicCursors = std::make_unique(); + + static const auto RENDER_SOFTWARE_CURSORS_FOR_METHODS = HyprlandAPI::findFunctionsByName(PHANDLE, "renderSoftwareCursorsFor"); + g_pRenderSoftwareCursorsForHook = HyprlandAPI::createFunctionHook(PHANDLE, RENDER_SOFTWARE_CURSORS_FOR_METHODS[0].address, (void*) &hkRenderSoftwareCursorsFor); g_pRenderSoftwareCursorsForHook->hook(); - g_pDynamicCursors = std::make_unique(); + HyprlandAPI::addConfigValue(PHANDLE, CONFIG_LENGTH, Hyprlang::INT{20}); return {"dynamic-cursors", "The most stupid cursor plugin.", "Virt", "1.1"}; } diff --git a/src/renderer.cpp b/src/renderer.cpp new file mode 100644 index 0000000..871a5b5 --- /dev/null +++ b/src/renderer.cpp @@ -0,0 +1,137 @@ +#include "globals.hpp" + +#define private public +#include +#include +#undef private + +#include +#include + +#include "renderer.hpp" +#include "hyprland/math.hpp" + +/* +This is the projectBox method from hyprland, but with support for rotation around a point, the hotspot. +*/ +void projectCursorBox(float mat[9], CBox& box, eTransform transform, float rotation, const float projection[9], Vector2D hotspot) { + double x = box.x; + double y = box.y; + double width = box.width; + double height = box.height; + + matrixIdentity(mat); + matrixTranslate(mat, x, y); + + if (rotation != 0) { + matrixTranslate(mat, hotspot.x, hotspot.y); + matrixRotate(mat, rotation); + matrixTranslate(mat, -hotspot.x, -hotspot.y); + } + + wlr_matrix_scale(mat, width, height); + + if (transform != HYPRUTILS_TRANSFORM_NORMAL) { + matrixTranslate(mat, 0.5, 0.5); + matrixTransform(mat, transform); + matrixTranslate(mat, -0.5, -0.5); + } + + matrixMultiply(mat, projection, mat); +} + +/* +This renders a texture with damage but rotates the texture around a given hotspot. +*/ +void renderCursorTextureInternalWithDamage(SP tex, CBox* pBox, CRegion* damage, float alpha, Vector2D hotspot) { + TRACY_GPU_ZONE("RenderDynamicCursor"); + + alpha = std::clamp(alpha, 0.f, 1.f); + + if (damage->empty()) + return; + + CBox newBox = *pBox; + g_pHyprOpenGL->m_RenderData.renderModif.applyToBox(newBox); + + // get transform + const auto TRANSFORM = wlTransformToHyprutils(wlr_output_transform_invert(!g_pHyprOpenGL->m_bEndFrame ? WL_OUTPUT_TRANSFORM_NORMAL : g_pHyprOpenGL->m_RenderData.pMonitor->transform)); + float matrix[9]; + projectCursorBox(matrix, newBox, TRANSFORM, newBox.rot, g_pHyprOpenGL->m_RenderData.monitorProjection.data(), hotspot); + + float glMatrix[9]; + wlr_matrix_multiply(glMatrix, g_pHyprOpenGL->m_RenderData.projection, matrix); + + CShader* shader = nullptr; + + switch (tex->m_iType) { + case TEXTURE_RGBA: shader = &g_pHyprOpenGL->m_RenderData.pCurrentMonData->m_shRGBA; break; + case TEXTURE_RGBX: shader = &g_pHyprOpenGL->m_RenderData.pCurrentMonData->m_shRGBX; break; + case TEXTURE_EXTERNAL: shader = &g_pHyprOpenGL->m_RenderData.pCurrentMonData->m_shEXT; break; + default: RASSERT(false, "tex->m_iTarget unsupported!"); + } + + glActiveTexture(GL_TEXTURE0); + glBindTexture(tex->m_iTarget, tex->m_iTexID); + + if (g_pHyprOpenGL->m_RenderData.useNearestNeighbor) { + glTexParameteri(tex->m_iTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(tex->m_iTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } else { + glTexParameteri(tex->m_iTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(tex->m_iTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } + + glUseProgram(shader->program); + +#ifndef GLES2 + glUniformMatrix3fv(shader->proj, 1, GL_TRUE, glMatrix); +#else + wlr_matrix_transpose(glMatrix, glMatrix); + glUniformMatrix3fv(shader->proj, 1, GL_FALSE, glMatrix); +#endif + glUniform1i(shader->tex, 0); + glUniform1f(shader->alpha, alpha); + glUniform1i(shader->discardOpaque, 0); + glUniform1i(shader->discardAlpha, 0); + + CBox transformedBox = newBox; + transformedBox.transform(wlTransformToHyprutils(wlr_output_transform_invert(g_pHyprOpenGL->m_RenderData.pMonitor->transform)), g_pHyprOpenGL->m_RenderData.pMonitor->vecTransformedSize.x, g_pHyprOpenGL->m_RenderData.pMonitor->vecTransformedSize.y); + + const auto TOPLEFT = Vector2D(transformedBox.x, transformedBox.y); + const auto FULLSIZE = Vector2D(transformedBox.width, transformedBox.height); + + glUniform2f(shader->topLeft, TOPLEFT.x, TOPLEFT.y); + glUniform2f(shader->fullSize, FULLSIZE.x, FULLSIZE.y); + glUniform1f(shader->radius, 0); + + glUniform1i(shader->applyTint, 0); + + glVertexAttribPointer(shader->posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); + glVertexAttribPointer(shader->texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); + + glEnableVertexAttribArray(shader->posAttrib); + glEnableVertexAttribArray(shader->texAttrib); + + if (g_pHyprOpenGL->m_RenderData.clipBox.width != 0 && g_pHyprOpenGL->m_RenderData.clipBox.height != 0) { + CRegion damageClip{g_pHyprOpenGL->m_RenderData.clipBox.x, g_pHyprOpenGL->m_RenderData.clipBox.y, g_pHyprOpenGL->m_RenderData.clipBox.width, g_pHyprOpenGL->m_RenderData.clipBox.height}; + damageClip.intersect(*damage); + + if (!damageClip.empty()) { + for (auto& RECT : damageClip.getRects()) { + g_pHyprOpenGL->scissor(&RECT); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + } + } + } else { + for (auto& RECT : damage->getRects()) { + g_pHyprOpenGL->scissor(&RECT); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + } + } + + glDisableVertexAttribArray(shader->posAttrib); + glDisableVertexAttribArray(shader->texAttrib); + + glBindTexture(tex->m_iTarget, 0); +} diff --git a/src/renderer.hpp b/src/renderer.hpp new file mode 100644 index 0000000..93730b4 --- /dev/null +++ b/src/renderer.hpp @@ -0,0 +1,5 @@ +#include +#include + +void renderCursorTextureInternalWithDamage(SP tex, CBox* pBox, CRegion* damage, float alpha, Vector2D hotspot); +void projectCursorBox(float mat[9], CBox& box, eTransform transform, float rotation, const float projection[9], Vector2D hotspot);