From c6a2b45f97b7182bbd7b7e1effb22fe74f9433d7 Mon Sep 17 00:00:00 2001 From: Virt <41426325+VirtCode@users.noreply.github.com> Date: Sun, 30 Jun 2024 14:41:31 +0200 Subject: [PATCH] feat: inverted config, setup and docs --- README.md | 32 +++++++++++++++++++++++- src/cursor.cpp | 57 ++++++++++++++++++++++++++++++++----------- src/cursor.hpp | 4 +++ src/globals.hpp | 1 + src/invert/shader.cpp | 4 +++ src/invert/shader.hpp | 46 +++++++++++++++++----------------- src/main.cpp | 8 +++++- src/renderer.cpp | 6 ++++- src/renderer.hpp | 2 +- 9 files changed, 120 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index d4bdced..19cc01b 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,13 @@ The plugin supports shake to find, akin to how KDE Plasma, MacOS, etc. do it. It https://github.com/VirtCode/hypr-dynamic-cursors/assets/41426325/9ff64a9b-64e5-4595-b721-dcb4d62bee18 +### inverted cursor (experimental) +You can also finally have an inverted cursor with this plugin. This is similar to the inverted cursor theme found in MS Windows. + +**Note:** Inverted cursors have about the same performance impact as a *basic* screen shader. They are also only supported as software cursors. + +INSERT VIDEO HERE + ## 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: @@ -34,7 +41,7 @@ This plugin is still very early in its development. There are also multiple thin - [ ] per-shape length and starting angle (if possible) - [X] cursor shake to find - [ ] overdue refactoring (wait for aquamarine merge) -- [ ] inverted cursor? +- [X] inverted cursor! - [ ] hyprcursor magified shape If anything here sounds interesting to you, don't hesitate to contribute. @@ -157,6 +164,29 @@ plugin:dynamic-cursors { # see #3 ipc = false } + + # enables inverted cursor + # this replaces your cursor shape with the inverted colors of the background + # by default, this replaces the non transparent parts of your cursor + # WARNING: inverted cursors are experimental and have a high performance impact + invert = true + + # for when invert = true + invert { + + # shader function that is used on the background color, supports: + # invert - take the negative of the color + # invert_hue - take the negative of the color and shift hue by 180° + # hue - only shift hue by 180° + shader = invert + + # apply a chroma key algorithm to the cursor + # this allows for only replacing certain parts of the cursor texture + chroma = false + + # color to replace when chroma = true + chroma:color = rgba(000000ff) + } } ``` diff --git a/src/cursor.cpp b/src/cursor.cpp index 741bd57..82d172b 100644 --- a/src/cursor.cpp +++ b/src/cursor.cpp @@ -22,13 +22,33 @@ #include "cursor.hpp" #include "renderer.hpp" +void renderCursorBox(SP texture, CBox box, CRegion& damage, Vector2D hotspot, float zoom) { + static auto* const* PNEAREST = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_SHAKE_NEAREST)->getDataStaticPtr(); + static auto* const* POPACITY = (Hyprlang::FLOAT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_OPACITY)->getDataStaticPtr(); + + static auto* const* PINVERT = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_INVERT)->getDataStaticPtr(); + static auto const* PINVERT_MODE = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_INVERT_SHADER)->getDataStaticPtr(); + static auto* const* PINVERT_CHROMA = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_INVERT_CHROMA)->getDataStaticPtr(); + static auto* const* PINVERT_CHROMA_COLOR = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_INVERT_CHROMA_COLOR)->getDataStaticPtr(); + + bool nearest = zoom > 1 && **PNEAREST; + float alpha = std::clamp(**POPACITY, 0.f, 1.f); + + if (**PINVERT) { + int mode = 0; + if (!strcmp(*PINVERT_MODE, "invert_hue")) mode = 1; + else if (!strcmp(*PINVERT_MODE, "hue")) mode = 2; + + renderCursorTextureInternalWithDamageInverted(texture, &box, &damage, alpha, hotspot, nearest, mode, **PINVERT_CHROMA, CColor(**PINVERT_CHROMA_COLOR)); + } else + renderCursorTextureInternalWithDamage(texture, &box, &damage, alpha, hotspot, nearest); +} + /* Reimplements rendering of the software cursor. Is also largely identical to hyprlands impl, but uses our custom rendering to rotate the cursor. */ void CDynamicCursors::renderSoftware(CPointerManager* pointers, SP pMonitor, timespec* now, CRegion& damage, std::optional overridePos) { - static auto* const* PNEAREST = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_SHAKE_NEAREST)->getDataStaticPtr(); - static auto* const* PINVERT = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_INVERT)->getDataStaticPtr(); if (!pointers->hasCursor()) return; @@ -63,11 +83,7 @@ void CDynamicCursors::renderSoftware(CPointerManager* pointers, SP pMo // we rotate the cursor by our calculated amount box.rot = this->angle; - // now pass the hotspot to rotate around - if (**PINVERT) - renderCursorTextureInternalWithDamageInverted(texture, &box, &damage, 1.F, pointers->currentCursorImage.hotspot * state->monitor->scale * zoom, zoom > 1 && **PNEAREST); - else - renderCursorTextureInternalWithDamage(texture, &box, &damage, 1.F, pointers->currentCursorImage.hotspot * state->monitor->scale * zoom, zoom > 1 && **PNEAREST); + renderCursorBox(texture, box, damage, pointers->currentCursorImage.hotspot * state->monitor->scale * zoom, zoom); } /* @@ -99,8 +115,6 @@ It is largely copied from hyprland, but adjusted to allow the cursor to be rotat */ wlr_buffer* CDynamicCursors::renderHardware(CPointerManager* pointers, SP state, SP texture) { static auto* const* PHW_DEBUG= (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_HW_DEBUG)->getDataStaticPtr(); - static auto* const* PNEAREST = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_SHAKE_NEAREST)->getDataStaticPtr(); - static auto* const* PINVERT = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_INVERT)->getDataStaticPtr(); auto output = state->monitor->output; @@ -168,11 +182,7 @@ wlr_buffer* CDynamicCursors::renderHardware(CPointerManager* pointers, SPcurrentCursorImage.size / pointers->currentCursorImage.scale * state->monitor->scale * zoom}.round()}; xbox.rot = this->angle; - // use our custom draw function - if (**PINVERT) - renderCursorTextureInternalWithDamageInverted(texture, &xbox, &damage, 1.F, pointers->currentCursorImage.hotspot * state->monitor->scale * zoom, zoom > 1 && **PNEAREST); - else - renderCursorTextureInternalWithDamage(texture, &xbox, &damage, 1.F, pointers->currentCursorImage.hotspot * state->monitor->scale * zoom, zoom > 1 && **PNEAREST); + renderCursorBox(texture, xbox, damage, pointers->currentCursorImage.hotspot * state->monitor->scale * zoom, zoom); g_pHyprOpenGL->end(); glFlush(); @@ -252,6 +262,25 @@ IMode* CDynamicCursors::currentMode() { else return nullptr; } +void CDynamicCursors::beforeRender(CPointerManager* pointers) { + static auto* const* PINVERT = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_INVERT)->getDataStaticPtr(); + + if (**PINVERT) { + // we need introspection as we make use of the offloadFB + g_pHyprOpenGL->m_RenderData.forceIntrospection = true; + + if (!invertSoftware) { + pointers->lockSoftwareAll(); + invertSoftware = true; + } + } else { + if (invertSoftware) { + pointers->unlockSoftwareAll(); + invertSoftware = false; + } + } +} + void CDynamicCursors::calculate(EModeUpdate type) { static auto* const* PTHRESHOLD = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_THRESHOLD)->getDataStaticPtr(); static auto* const* PSHAKE = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_SHAKE)->getDataStaticPtr(); diff --git a/src/cursor.hpp b/src/cursor.hpp index 52dec73..f876c1e 100644 --- a/src/cursor.hpp +++ b/src/cursor.hpp @@ -15,6 +15,8 @@ class CDynamicCursors { void onCursorMoved(CPointerManager* pointers); /* called on tick */ void onTick(CPointerManager* pointers); + /* called before render */ + void beforeRender(CPointerManager* pointers); /* hook on renderSoftwareCursorsFor */ void renderSoftware(CPointerManager* pointers, SP pMonitor, timespec* now, CRegion& damage, std::optional overridePos); @@ -33,6 +35,8 @@ class CDynamicCursors { // whether we have already locked software for cursor zoom bool zoomSoftware = false; + // whether we have already locked software for inverted cursors + bool invertSoftware = false; // modes CModeRotate rotate; diff --git a/src/globals.hpp b/src/globals.hpp index 22350c5..ad4c9ab 100644 --- a/src/globals.hpp +++ b/src/globals.hpp @@ -5,6 +5,7 @@ #define CONFIG_ENABLED "plugin:dynamic-cursors:enabled" #define CONFIG_MODE "plugin:dynamic-cursors:mode" #define CONFIG_THRESHOLD "plugin:dynamic-cursors:threshold" +#define CONFIG_OPACITY "plugin:dynamic-cursors:opacity" #define CONFIG_HW_DEBUG "plugin:dynamic-cursors:hw_debug" #define CONFIG_LENGTH "plugin:dynamic-cursors:rotate:length" diff --git a/src/invert/shader.cpp b/src/invert/shader.cpp index 471dc70..1f79781 100644 --- a/src/invert/shader.cpp +++ b/src/invert/shader.cpp @@ -20,6 +20,10 @@ void CInversionShader::compile(std::string vertex, std::string fragment) { backgroundTex = glGetUniformLocation(program, "backgroundTex"); cursorTex = glGetUniformLocation(program, "cursorTex"); alpha = glGetUniformLocation(program, "alpha"); + chroma = glGetUniformLocation(program, "chroma"); + chromaColor = glGetUniformLocation(program, "chromaColor"); + mode = glGetUniformLocation(program, "mode"); + applyTint = glGetUniformLocation(program, "applyTint"); tint = glGetUniformLocation(program, "tint"); } diff --git a/src/invert/shader.hpp b/src/invert/shader.hpp index 23ae14d..793491b 100644 --- a/src/invert/shader.hpp +++ b/src/invert/shader.hpp @@ -5,18 +5,17 @@ class CInversionShader { public: GLuint program = 0; + + GLint posAttrib = -1; + GLint texAttrib = -1; + GLint proj = -1; - GLint color = -1; - GLint alphaMatte = -1; GLint cursorTex = -1; GLint backgroundTex = -1; GLint alpha = -1; - GLint posAttrib = -1; - GLint texAttrib = -1; - GLint matteTexAttrib = -1; - GLint discardOpaque = -1; - GLint discardAlpha = -1; - GLfloat discardAlphaValue = -1; + GLint mode = -1; + GLint chroma = -1; + GLint chromaColor = -1; GLint applyTint = -1; GLint tint = -1; @@ -66,25 +65,28 @@ inline const std::string RGBA = R"#( uniform int applyTint; uniform vec3 tint; + uniform int mode; + uniform int chroma; + uniform vec4 chromaColor; + void main() { - vec4 cursor = texture2D(cursorTex, v_texcoord); - - // load and invert background vec4 background = texture2D(backgroundTex, v_screencord); - background.rgb = vec3(1.0, 1.0, 1.0) - background.rgb; + + // invert based on mode + if (mode == 0 || mode == 1) // invert + background.rgb = vec3(1.0, 1.0, 1.0) - background.rgb; + if (mode == 1 || mode == 2) // hueshift + background.rgb = -background.rgb + dot(vec3(0.26312, 0.5283, 0.10488), background.rgb) * 2.0; + background *= cursor[3]; // premultiplied alpha + vec4 pixColor = background; - // invert hue - //background.rgb = -background.rgb + dot(vec3(0.26312, 0.5283, 0.10488), background.rgb) * 2.0; - - vec4 pixColor = cursor; - //if (cursor[3] != 1.0) pixColor = cursor; - //pixColor = vec4(v_screencord + vec2(1.0, 1.0) / 2.0, 0.0, 1.0); - - vec4 chroma = vec4(0.0, 0.0, 0.0, 1.0); - float diff = (abs(chroma.x - cursor.x) + abs(chroma.y - cursor.y) + abs(chroma.z - cursor.z) + abs(chroma.w - cursor.w)) / 4.0; - pixColor = background * (1.0 - diff) + cursor * diff; + // this is a very crude "chroma algorithm", feel free to contribute a better one + if (chroma == 1) { + float diff = (abs(chromaColor.x - cursor.x) + abs(chromaColor.y - cursor.y) + abs(chromaColor.z - cursor.z) + abs(chromaColor.w - cursor.w)) / 4.0; + pixColor = background * (1.0 - diff) + cursor * diff; + } if (applyTint == 1) { pixColor[0] = pixColor[0] * tint[0]; diff --git a/src/main.cpp b/src/main.cpp index 9e6dccf..d724a35 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -107,9 +107,15 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { tick = wl_event_loop_add_timer(g_pCompositor->m_sWLEventLoop, &onTick, nullptr); wl_event_source_timer_update(tick, 1); + static auto P_PRE_RENDER = HyprlandAPI::registerCallbackDynamic(PHANDLE, "preRender", [](void* self, SCallbackInfo& info, std::any data) { + static auto* const* PENABLED = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_ENABLED)->getDataStaticPtr(); + if (**PENABLED) g_pDynamicCursors->beforeRender(g_pPointerManager.get()); + }); + HyprlandAPI::addConfigValue(PHANDLE, CONFIG_ENABLED, Hyprlang::INT{1}); HyprlandAPI::addConfigValue(PHANDLE, CONFIG_MODE, Hyprlang::STRING{"tilt"}); HyprlandAPI::addConfigValue(PHANDLE, CONFIG_THRESHOLD, Hyprlang::INT{2}); + HyprlandAPI::addConfigValue(PHANDLE, CONFIG_OPACITY, Hyprlang::FLOAT{1}); HyprlandAPI::addConfigValue(PHANDLE, CONFIG_SHAKE, Hyprlang::INT{1}); HyprlandAPI::addConfigValue(PHANDLE, CONFIG_SHAKE_NEAREST, Hyprlang::INT{1}); @@ -119,7 +125,7 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { HyprlandAPI::addConfigValue(PHANDLE, CONFIG_SHAKE_FACTOR, Hyprlang::FLOAT{1.5}); HyprlandAPI::addConfigValue(PHANDLE, CONFIG_INVERT, Hyprlang::INT{0}); - HyprlandAPI::addConfigValue(PHANDLE, CONFIG_INVERT_SHADER, Hyprlang::STRING{"normal"}); + HyprlandAPI::addConfigValue(PHANDLE, CONFIG_INVERT_SHADER, Hyprlang::STRING{"invert"}); HyprlandAPI::addConfigValue(PHANDLE, CONFIG_INVERT_CHROMA, Hyprlang::INT{0}); HyprlandAPI::addConfigValue(PHANDLE, CONFIG_INVERT_CHROMA_COLOR, Hyprlang::INT{0xFF000000}); // opaque black diff --git a/src/renderer.cpp b/src/renderer.cpp index f58ed0b..e9e65a1 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -138,7 +138,7 @@ void renderCursorTextureInternalWithDamage(SP tex, CBox* pBox, CRegion glBindTexture(tex->m_iTarget, 0); } -void renderCursorTextureInternalWithDamageInverted(SP tex, CBox* pBox, CRegion* damage, float alpha, Vector2D hotspot, bool nearest) { +void renderCursorTextureInternalWithDamageInverted(SP tex, CBox* pBox, CRegion* damage, float alpha, Vector2D hotspot, bool nearest, int mode, bool chroma, CColor chromaColor) { TRACY_GPU_ZONE("RenderDynamicCursor"); alpha = std::clamp(alpha, 0.f, 1.f); @@ -193,6 +193,10 @@ void renderCursorTextureInternalWithDamageInverted(SP tex, CBox* pBox, glUniform1i(shader->cursorTex, 1); glUniform1f(shader->alpha, alpha); + glUniform1i(shader->mode, mode); + glUniform1i(shader->chroma, chroma); + glUniform4f(shader->chromaColor, chromaColor.r, chromaColor.g, chromaColor.b, chromaColor.a); + 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); diff --git a/src/renderer.hpp b/src/renderer.hpp index 62112e6..e512067 100644 --- a/src/renderer.hpp +++ b/src/renderer.hpp @@ -2,5 +2,5 @@ #include void renderCursorTextureInternalWithDamage(SP tex, CBox* pBox, CRegion* damage, float alpha, Vector2D hotspot, bool nearest); -void renderCursorTextureInternalWithDamageInverted(SP tex, CBox* pBox, CRegion* damage, float alpha, Vector2D hotspot, bool nearest); +void renderCursorTextureInternalWithDamageInverted(SP tex, CBox* pBox, CRegion* damage, float alpha, Vector2D hotspot, bool nearest, int mode, bool chroma, CColor chromaColor); void projectCursorBox(float mat[9], CBox& box, eTransform transform, float rotation, const float projection[9], Vector2D hotspot);