feat: inverted config, setup and docs

This commit is contained in:
Virt 2024-06-30 14:41:31 +02:00
commit c6a2b45f97
9 changed files with 120 additions and 40 deletions

View file

@ -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 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 ## 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: 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) - [ ] per-shape length and starting angle (if possible)
- [X] cursor shake to find - [X] cursor shake to find
- [ ] overdue refactoring (wait for aquamarine merge) - [ ] overdue refactoring (wait for aquamarine merge)
- [ ] inverted cursor? - [X] inverted cursor!
- [ ] hyprcursor magified shape - [ ] hyprcursor magified shape
If anything here sounds interesting to you, don't hesitate to contribute. If anything here sounds interesting to you, don't hesitate to contribute.
@ -157,6 +164,29 @@ plugin:dynamic-cursors {
# see #3 # see #3
ipc = false 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)
}
} }
``` ```

View file

@ -22,13 +22,33 @@
#include "cursor.hpp" #include "cursor.hpp"
#include "renderer.hpp" #include "renderer.hpp"
void renderCursorBox(SP<CTexture> 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. Reimplements rendering of the software cursor.
Is also largely identical to hyprlands impl, but uses our custom rendering to rotate the cursor. Is also largely identical to hyprlands impl, but uses our custom rendering to rotate the cursor.
*/ */
void CDynamicCursors::renderSoftware(CPointerManager* pointers, SP<CMonitor> pMonitor, timespec* now, CRegion& damage, std::optional<Vector2D> overridePos) { void CDynamicCursors::renderSoftware(CPointerManager* pointers, SP<CMonitor> pMonitor, timespec* now, CRegion& damage, std::optional<Vector2D> 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()) if (!pointers->hasCursor())
return; return;
@ -63,11 +83,7 @@ void CDynamicCursors::renderSoftware(CPointerManager* pointers, SP<CMonitor> pMo
// we rotate the cursor by our calculated amount // we rotate the cursor by our calculated amount
box.rot = this->angle; box.rot = this->angle;
// now pass the hotspot to rotate around renderCursorBox(texture, box, damage, pointers->currentCursorImage.hotspot * state->monitor->scale * zoom, zoom);
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);
} }
/* /*
@ -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<CPointerManager::SMonitorPointerState> state, SP<CTexture> texture) { wlr_buffer* CDynamicCursors::renderHardware(CPointerManager* pointers, SP<CPointerManager::SMonitorPointerState> state, SP<CTexture> texture) {
static auto* const* PHW_DEBUG= (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_HW_DEBUG)->getDataStaticPtr(); 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; auto output = state->monitor->output;
@ -168,11 +182,7 @@ wlr_buffer* CDynamicCursors::renderHardware(CPointerManager* pointers, SP<CPoint
CBox xbox = {size, Vector2D{pointers->currentCursorImage.size / pointers->currentCursorImage.scale * state->monitor->scale * zoom}.round()}; CBox xbox = {size, Vector2D{pointers->currentCursorImage.size / pointers->currentCursorImage.scale * state->monitor->scale * zoom}.round()};
xbox.rot = this->angle; xbox.rot = this->angle;
// use our custom draw function renderCursorBox(texture, xbox, damage, pointers->currentCursorImage.hotspot * state->monitor->scale * zoom, zoom);
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);
g_pHyprOpenGL->end(); g_pHyprOpenGL->end();
glFlush(); glFlush();
@ -252,6 +262,25 @@ IMode* CDynamicCursors::currentMode() {
else return nullptr; 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) { void CDynamicCursors::calculate(EModeUpdate type) {
static auto* const* PTHRESHOLD = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_THRESHOLD)->getDataStaticPtr(); 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(); static auto* const* PSHAKE = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_SHAKE)->getDataStaticPtr();

View file

@ -15,6 +15,8 @@ class CDynamicCursors {
void onCursorMoved(CPointerManager* pointers); void onCursorMoved(CPointerManager* pointers);
/* called on tick */ /* called on tick */
void onTick(CPointerManager* pointers); void onTick(CPointerManager* pointers);
/* called before render */
void beforeRender(CPointerManager* pointers);
/* hook on renderSoftwareCursorsFor */ /* hook on renderSoftwareCursorsFor */
void renderSoftware(CPointerManager* pointers, SP<CMonitor> pMonitor, timespec* now, CRegion& damage, std::optional<Vector2D> overridePos); void renderSoftware(CPointerManager* pointers, SP<CMonitor> pMonitor, timespec* now, CRegion& damage, std::optional<Vector2D> overridePos);
@ -33,6 +35,8 @@ class CDynamicCursors {
// whether we have already locked software for cursor zoom // whether we have already locked software for cursor zoom
bool zoomSoftware = false; bool zoomSoftware = false;
// whether we have already locked software for inverted cursors
bool invertSoftware = false;
// modes // modes
CModeRotate rotate; CModeRotate rotate;

View file

@ -5,6 +5,7 @@
#define CONFIG_ENABLED "plugin:dynamic-cursors:enabled" #define CONFIG_ENABLED "plugin:dynamic-cursors:enabled"
#define CONFIG_MODE "plugin:dynamic-cursors:mode" #define CONFIG_MODE "plugin:dynamic-cursors:mode"
#define CONFIG_THRESHOLD "plugin:dynamic-cursors:threshold" #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_HW_DEBUG "plugin:dynamic-cursors:hw_debug"
#define CONFIG_LENGTH "plugin:dynamic-cursors:rotate:length" #define CONFIG_LENGTH "plugin:dynamic-cursors:rotate:length"

View file

@ -20,6 +20,10 @@ void CInversionShader::compile(std::string vertex, std::string fragment) {
backgroundTex = glGetUniformLocation(program, "backgroundTex"); backgroundTex = glGetUniformLocation(program, "backgroundTex");
cursorTex = glGetUniformLocation(program, "cursorTex"); cursorTex = glGetUniformLocation(program, "cursorTex");
alpha = glGetUniformLocation(program, "alpha"); alpha = glGetUniformLocation(program, "alpha");
chroma = glGetUniformLocation(program, "chroma");
chromaColor = glGetUniformLocation(program, "chromaColor");
mode = glGetUniformLocation(program, "mode");
applyTint = glGetUniformLocation(program, "applyTint"); applyTint = glGetUniformLocation(program, "applyTint");
tint = glGetUniformLocation(program, "tint"); tint = glGetUniformLocation(program, "tint");
} }

View file

@ -5,18 +5,17 @@
class CInversionShader { class CInversionShader {
public: public:
GLuint program = 0; GLuint program = 0;
GLint posAttrib = -1;
GLint texAttrib = -1;
GLint proj = -1; GLint proj = -1;
GLint color = -1;
GLint alphaMatte = -1;
GLint cursorTex = -1; GLint cursorTex = -1;
GLint backgroundTex = -1; GLint backgroundTex = -1;
GLint alpha = -1; GLint alpha = -1;
GLint posAttrib = -1; GLint mode = -1;
GLint texAttrib = -1; GLint chroma = -1;
GLint matteTexAttrib = -1; GLint chromaColor = -1;
GLint discardOpaque = -1;
GLint discardAlpha = -1;
GLfloat discardAlphaValue = -1;
GLint applyTint = -1; GLint applyTint = -1;
GLint tint = -1; GLint tint = -1;
@ -66,25 +65,28 @@ inline const std::string RGBA = R"#(
uniform int applyTint; uniform int applyTint;
uniform vec3 tint; uniform vec3 tint;
uniform int mode;
uniform int chroma;
uniform vec4 chromaColor;
void main() { void main() {
vec4 cursor = texture2D(cursorTex, v_texcoord); vec4 cursor = texture2D(cursorTex, v_texcoord);
// load and invert background
vec4 background = texture2D(backgroundTex, v_screencord); vec4 background = texture2D(backgroundTex, v_screencord);
// invert based on mode
if (mode == 0 || mode == 1) // invert
background.rgb = vec3(1.0, 1.0, 1.0) - background.rgb; 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 background *= cursor[3]; // premultiplied alpha
vec4 pixColor = background;
// invert hue // this is a very crude "chroma algorithm", feel free to contribute a better one
//background.rgb = -background.rgb + dot(vec3(0.26312, 0.5283, 0.10488), background.rgb) * 2.0; 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;
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; pixColor = background * (1.0 - diff) + cursor * diff;
}
if (applyTint == 1) { if (applyTint == 1) {
pixColor[0] = pixColor[0] * tint[0]; pixColor[0] = pixColor[0] * tint[0];

View file

@ -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); tick = wl_event_loop_add_timer(g_pCompositor->m_sWLEventLoop, &onTick, nullptr);
wl_event_source_timer_update(tick, 1); 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_ENABLED, Hyprlang::INT{1});
HyprlandAPI::addConfigValue(PHANDLE, CONFIG_MODE, Hyprlang::STRING{"tilt"}); HyprlandAPI::addConfigValue(PHANDLE, CONFIG_MODE, Hyprlang::STRING{"tilt"});
HyprlandAPI::addConfigValue(PHANDLE, CONFIG_THRESHOLD, Hyprlang::INT{2}); 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, Hyprlang::INT{1});
HyprlandAPI::addConfigValue(PHANDLE, CONFIG_SHAKE_NEAREST, 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_SHAKE_FACTOR, Hyprlang::FLOAT{1.5});
HyprlandAPI::addConfigValue(PHANDLE, CONFIG_INVERT, Hyprlang::INT{0}); 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, Hyprlang::INT{0});
HyprlandAPI::addConfigValue(PHANDLE, CONFIG_INVERT_CHROMA_COLOR, Hyprlang::INT{0xFF000000}); // opaque black HyprlandAPI::addConfigValue(PHANDLE, CONFIG_INVERT_CHROMA_COLOR, Hyprlang::INT{0xFF000000}); // opaque black

View file

@ -138,7 +138,7 @@ void renderCursorTextureInternalWithDamage(SP<CTexture> tex, CBox* pBox, CRegion
glBindTexture(tex->m_iTarget, 0); glBindTexture(tex->m_iTarget, 0);
} }
void renderCursorTextureInternalWithDamageInverted(SP<CTexture> tex, CBox* pBox, CRegion* damage, float alpha, Vector2D hotspot, bool nearest) { void renderCursorTextureInternalWithDamageInverted(SP<CTexture> tex, CBox* pBox, CRegion* damage, float alpha, Vector2D hotspot, bool nearest, int mode, bool chroma, CColor chromaColor) {
TRACY_GPU_ZONE("RenderDynamicCursor"); TRACY_GPU_ZONE("RenderDynamicCursor");
alpha = std::clamp(alpha, 0.f, 1.f); alpha = std::clamp(alpha, 0.f, 1.f);
@ -193,6 +193,10 @@ void renderCursorTextureInternalWithDamageInverted(SP<CTexture> tex, CBox* pBox,
glUniform1i(shader->cursorTex, 1); glUniform1i(shader->cursorTex, 1);
glUniform1f(shader->alpha, alpha); 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; 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); 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);

View file

@ -2,5 +2,5 @@
#include <hyprland/src/helpers/math/Math.hpp> #include <hyprland/src/helpers/math/Math.hpp>
void renderCursorTextureInternalWithDamage(SP<CTexture> tex, CBox* pBox, CRegion* damage, float alpha, Vector2D hotspot, bool nearest); void renderCursorTextureInternalWithDamage(SP<CTexture> tex, CBox* pBox, CRegion* damage, float alpha, Vector2D hotspot, bool nearest);
void renderCursorTextureInternalWithDamageInverted(SP<CTexture> tex, CBox* pBox, CRegion* damage, float alpha, Vector2D hotspot, bool nearest); void renderCursorTextureInternalWithDamageInverted(SP<CTexture> 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); void projectCursorBox(float mat[9], CBox& box, eTransform transform, float rotation, const float projection[9], Vector2D hotspot);