feat: inverted hardware cursor support

This commit is contained in:
Virt 2024-06-30 17:32:53 +02:00
commit d172611292
8 changed files with 53 additions and 40 deletions

View file

@ -5,6 +5,10 @@ Why did I implement this again?
Inspired by KDE, it also supports shake to find, to enlarge the cursor when it is shaken so it is easier to find it. It can be enabled separately or together with one simulation mode. Inspired by KDE, it also supports shake to find, to enlarge the cursor when it is shaken so it is easier to find it. It can be enabled separately or together with one simulation mode.
> [!WARNING]
> This branch is experimental, poorly implemented, and not maintained. Use it at your own digression. Also, inverted hardware cursors are currently broken.
### simulation modes ### simulation modes
The plugin supports a few different modes. They can all be customized induvidually. The plugin supports a few different modes. They can all be customized induvidually.
@ -31,9 +35,9 @@ https://github.com/VirtCode/hypr-dynamic-cursors/assets/41426325/9ff64a9b-64e5-4
### inverted cursor (experimental) ### 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. 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. **Note:** Inverted cursors have about the same performance impact as a *basic* screen shader. They are also currently broken on nvidia.
INSERT VIDEO HERE https://github.com/VirtCode/hypr-dynamic-cursors/assets/41426325/b62698d8-d53f-45f2-b36f-2c9752b8f90d
## 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:
@ -191,15 +195,14 @@ plugin:dynamic-cursors {
ipc = false ipc = false
} }
# enables inverted cursor
# this replaces your cursor shape with the inverted colors of the background # this replaces your cursor shape with the inverted colors of the background
# by default, this replaces the non transparent parts of your cursor # by default, this replaces the non transparent parts of your cursor
# WARNING: inverted cursors are experimental and have a high performance impact # WARNING: inverted cursors are experimental and have a high performance impact
invert = true
# for when invert = true
invert { invert {
# enables inverted cursor
enabled = false
# shader function that is used on the background color, supports: # shader function that is used on the background color, supports:
# invert - take the negative of the color # invert - take the negative of the color
# invert_hue - take the negative of the color and shift hue by 180° # invert_hue - take the negative of the color and shift hue by 180°

View file

@ -10,10 +10,10 @@
#define CONFIG_THRESHOLD "threshold" #define CONFIG_THRESHOLD "threshold"
#define CONFIG_HW_DEBUG "hw_debug" #define CONFIG_HW_DEBUG "hw_debug"
#define CONFIG_INVERT "plugin:dynamic-cursors:invert" #define CONFIG_INVERT "invert:enabled"
#define CONFIG_INVERT_SHADER "plugin:dynamic-cursors:invert:shader" #define CONFIG_INVERT_SHADER "invert:shader"
#define CONFIG_INVERT_CHROMA "plugin:dynamic-cursors:invert:chroma" #define CONFIG_INVERT_CHROMA "invert:chroma"
#define CONFIG_INVERT_CHROMA_COLOR "plugin:dynamic-cursors:invert:chroma:color" #define CONFIG_INVERT_CHROMA_COLOR "invert:chroma:color"
#define CONFIG_SHAKE "shake:enabled" #define CONFIG_SHAKE "shake:enabled"
#define CONFIG_SHAKE_NEAREST "shake:nearest" #define CONFIG_SHAKE_NEAREST "shake:nearest"

View file

@ -49,24 +49,25 @@ CDynamicCursors::~CDynamicCursors() {
} }
} }
void renderCursorBox(SP<CTexture> texture, CBox box, CRegion& damage, Vector2D hotspot, float zoom) { void renderCursorBox(SP<CTexture> texture, CBox box, CRegion& damage, SP<CSyncTimeline> waitTimeline, uint64_t waitPoint, Vector2D hotspot, float zoom, float stretchAngle, Vector2D stretch, Vector2D offset) {
static auto* const* PNEAREST = (Hyprlang::INT* const*) getConfig(CONFIG_SHAKE_NEAREST); static auto* const* PNEAREST = (Hyprlang::INT* const*) getConfig(CONFIG_SHAKE_NEAREST);
static auto* const* PINVERT = (Hyprlang::INT* const*) getConfig(CONFIG_INVERT); static auto* const* PINVERT = (Hyprlang::INT* const*) getConfig(CONFIG_INVERT);
static auto const* PINVERT_MODE = (Hyprlang::INT* const*) getConfig(CONFIG_INVERT_SHADER); static auto const* PINVERT_MODE = (Hyprlang::STRING const*) getConfig(CONFIG_INVERT_SHADER);
static auto* const* PINVERT_CHROMA = (Hyprlang::STRING const*) getConfig(CONFIG_INVERT_CHROMA); static auto* const* PINVERT_CHROMA = (Hyprlang::INT* const*) getConfig(CONFIG_INVERT_CHROMA);
static auto* const* PINVERT_CHROMA_COLOR = (Hyprlang::INT* const*) getConfig(CONFIG_INVERT_CHROMA_COLOR); static auto* const* PINVERT_CHROMA_COLOR = (Hyprlang::INT* const*) getConfig(CONFIG_INVERT_CHROMA_COLOR);
bool nearest = zoom > 1 && **PNEAREST; bool nearest = zoom > 1 && **PNEAREST;
if (**PINVERT) { if (**PINVERT) {
int mode = 0; int mode = 0;
if (!strcmp(*PINVERT_MODE, "invert_hue")) mode = 1;
else if (!strcmp(*PINVERT_MODE, "hue")) mode = 2;
renderCursorTextureInternalWithDamageInverted(texture, &box, &damage, 1.f, hotspot, nearest, mode, **PINVERT_CHROMA, CColor(**PINVERT_CHROMA_COLOR)); if (*PINVERT_MODE == std::string("invert_hue")) mode = 1;
else if (*PINVERT_MODE == std::string("hue")) mode = 2;
renderCursorTextureInternalWithDamageInverted(texture, &box, &damage, 1.f, waitTimeline, waitPoint, hotspot, nearest, stretchAngle, stretch, mode, **PINVERT_CHROMA, CColor(**PINVERT_CHROMA_COLOR), offset);
} else } else
renderCursorTextureInternalWithDamage(texture, &box, &damage, 1.f, hotspot, nearest); renderCursorTextureInternalWithDamage(texture, &box, &damage, 1.f, waitTimeline, waitPoint, hotspot, nearest, stretchAngle, stretch);
} }
/* /*
@ -74,8 +75,6 @@ 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*) getConfig(CONFIG_SHAKE_NEAREST);
static auto* const* PINVERT = (Hyprlang::INT* const*) getConfig(CONFIG_INVERT);
if (!pointers->hasCursor()) if (!pointers->hasCursor())
return; return;
@ -115,7 +114,7 @@ void CDynamicCursors::renderSoftware(CPointerManager* pointers, SP<CMonitor> pMo
box.rot = resultShown.rotation; box.rot = resultShown.rotation;
// now pass the hotspot to rotate around // now pass the hotspot to rotate around
renderCursorBox(texture, box, damage, pointers->currentCursorImage.hotspot * state->monitor->scale * zoom, zoom); renderCursorBox(texture, box, damage, nullptr, 0, pointers->currentCursorImage.hotspot * state->monitor->scale * zoom, zoom, resultShown.stretch.angle, resultShown.stretch.magnitude, Vector2D());
if (pointers->currentCursorImage.surface) if (pointers->currentCursorImage.surface)
pointers->currentCursorImage.surface->resource()->frame(now); pointers->currentCursorImage.surface->resource()->frame(now);
@ -154,7 +153,7 @@ It is largely copied from hyprland, but adjusted to allow the cursor to be rotat
*/ */
SP<Aquamarine::IBuffer> CDynamicCursors::renderHardware(CPointerManager* pointers, SP<CPointerManager::SMonitorPointerState> state, SP<CTexture> texture) { SP<Aquamarine::IBuffer> CDynamicCursors::renderHardware(CPointerManager* pointers, SP<CPointerManager::SMonitorPointerState> state, SP<CTexture> texture) {
static auto* const* PHW_DEBUG = (Hyprlang::INT* const*) getConfig(CONFIG_HW_DEBUG); static auto* const* PHW_DEBUG = (Hyprlang::INT* const*) getConfig(CONFIG_HW_DEBUG);
static auto* const* PNEAREST = (Hyprlang::INT* const*) getConfig(CONFIG_SHAKE_NEAREST); static auto* const* PINVERT = (Hyprlang::INT* const*) getConfig(CONFIG_INVERT);
auto output = state->monitor->output; auto output = state->monitor->output;
@ -263,8 +262,21 @@ SP<Aquamarine::IBuffer> CDynamicCursors::renderHardware(CPointerManager* pointer
CBox xbox = {cursorPadding, Vector2D{pointers->currentCursorImage.size / pointers->currentCursorImage.scale * state->monitor->scale * zoom}.round()}; CBox xbox = {cursorPadding, Vector2D{pointers->currentCursorImage.size / pointers->currentCursorImage.scale * state->monitor->scale * zoom}.round()};
xbox.rot = resultShown.rotation; xbox.rot = resultShown.rotation;
// use our custom draw function // calculate correct offset of the offload buffer into the cursor buffer
renderCursorBox(texture, xbox, damage, pointers->currentCursorImage.hotspot * state->monitor->scale * zoom, zoom); auto offset = Vector2D();
if (**PINVERT) {
auto PMONITOR = state->monitor.lock();
const auto HOTSPOT = CBox{((pointers->currentCursorImage.hotspot * PMONITOR->scale) + cursorPadding) * resultShown.scale, {0, 0}}
.transform(wlTransformToHyprutils(invertTransform(PMONITOR->transform)), PMONITOR->cursorSwapchain->currentOptions().size.x, PMONITOR->cursorSwapchain->currentOptions().size.y)
.pos();
offset = pointers->getCursorPosForMonitor(state->monitor.lock()) - HOTSPOT;
offset.x /= xbox.x;
offset.y /= xbox.y;
}
renderCursorBox(texture, xbox, damage, nullptr, 0, pointers->currentCursorImage.hotspot * state->monitor->scale * zoom, zoom, resultShown.stretch.angle, resultShown.stretch.magnitude, offset);
g_pHyprOpenGL->end(); g_pHyprOpenGL->end();
glFlush(); glFlush();
@ -360,16 +372,6 @@ void CDynamicCursors::beforeRender(CPointerManager* pointers) {
if (**PINVERT) { if (**PINVERT) {
// we need introspection as we make use of the offloadFB // we need introspection as we make use of the offloadFB
g_pHyprOpenGL->m_RenderData.forceIntrospection = true; g_pHyprOpenGL->m_RenderData.forceIntrospection = true;
if (!invertSoftware) {
pointers->lockSoftwareAll();
invertSoftware = true;
}
} else {
if (invertSoftware) {
pointers->unlockSoftwareAll();
invertSoftware = false;
}
} }
} }
@ -377,6 +379,7 @@ void CDynamicCursors::calculate(EModeUpdate type) {
static auto* const* PTHRESHOLD = (Hyprlang::INT* const*) getConfig(CONFIG_THRESHOLD); static auto* const* PTHRESHOLD = (Hyprlang::INT* const*) getConfig(CONFIG_THRESHOLD);
static auto* const* PSHAKE = (Hyprlang::INT* const*) getConfig(CONFIG_SHAKE); static auto* const* PSHAKE = (Hyprlang::INT* const*) getConfig(CONFIG_SHAKE);
static auto* const* PSHAKE_EFFECTS = (Hyprlang::INT* const*) getConfig(CONFIG_SHAKE_EFFECTS); static auto* const* PSHAKE_EFFECTS = (Hyprlang::INT* const*) getConfig(CONFIG_SHAKE_EFFECTS);
static auto* const* PINVERT = (Hyprlang::INT* const*)getConfig(CONFIG_INVERT);
IMode* mode = currentMode(); IMode* mode = currentMode();
@ -395,7 +398,8 @@ void CDynamicCursors::calculate(EModeUpdate type) {
auto result = resultMode; auto result = resultMode;
result.scale *= resultShake; result.scale *= resultShake;
if (resultShown.hasDifference(&result, **PTHRESHOLD * (PI / 180.0), 0.01, 0.01)) { // always rerender inverted on move
if (resultShown.hasDifference(&result, **PTHRESHOLD * (PI / 180.0), 0.01, 0.01) || (**PINVERT && type == MOVE)) {
resultShown = result; resultShown = result;
resultShown.clamp(**PTHRESHOLD * (PI / 180.0), 0.01, 0.01); // clamp low values so it is rendered pixel-perfectly when no effect resultShown.clamp(**PTHRESHOLD * (PI / 180.0), 0.01, 0.01); // clamp low values so it is rendered pixel-perfectly when no effect

View file

@ -48,8 +48,6 @@ 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

@ -1,10 +1,11 @@
#include "src/debug/Log.hpp" #include "src/debug/Log.hpp"
#include "src/render/Renderer.hpp"
#define private public #define private public
#include <hyprland/src/render/OpenGL.hpp> #include <hyprland/src/render/OpenGL.hpp>
#undef private #undef private
#include "src/render/Renderer.hpp"
#include <hyprland/src/Compositor.hpp> #include <hyprland/src/Compositor.hpp>
#include <hyprland/src/render/Shaders.hpp> #include <hyprland/src/render/Shaders.hpp>
@ -18,6 +19,7 @@ void CInversionShader::compile(std::string vertex, std::string fragment) {
posAttrib = glGetAttribLocation(program, "pos"); posAttrib = glGetAttribLocation(program, "pos");
proj = glGetUniformLocation(program, "proj"); proj = glGetUniformLocation(program, "proj");
screenOffset = glGetUniformLocation(program, "screenOffset");
backgroundTex = glGetUniformLocation(program, "backgroundTex"); backgroundTex = glGetUniformLocation(program, "backgroundTex");
cursorTex = glGetUniformLocation(program, "cursorTex"); cursorTex = glGetUniformLocation(program, "cursorTex");
alpha = glGetUniformLocation(program, "alpha"); alpha = glGetUniformLocation(program, "alpha");

View file

@ -1,7 +1,7 @@
#include <string> #include <string>
#include <hyprland/src/render/Shader.hpp> #include <hyprland/src/render/Shader.hpp>
// we need our own shader class as we have two textures // we need our own shader class as we have two textures, and custom uniforms
class CInversionShader { class CInversionShader {
public: public:
GLuint program = 0; GLuint program = 0;
@ -10,6 +10,7 @@ class CInversionShader {
GLint texAttrib = -1; GLint texAttrib = -1;
GLint proj = -1; GLint proj = -1;
GLint screenOffset = -1;
GLint cursorTex = -1; GLint cursorTex = -1;
GLint backgroundTex = -1; GLint backgroundTex = -1;
GLint alpha = -1; GLint alpha = -1;
@ -44,10 +45,12 @@ inline const std::string VERTEX = R"#(
varying vec2 v_texcoord; varying vec2 v_texcoord;
varying vec2 v_screencord; varying vec2 v_screencord;
uniform vec2 screenOffset;
void main() { void main() {
gl_Position = vec4(proj * vec3(pos, 1.0), 1.0); gl_Position = vec4(proj * vec3(pos, 1.0), 1.0);
v_screencord = gl_Position.xy / 2.0 + vec2(0.5, 0.5); // transform to texture coords v_screencord = (proj * vec3(pos + screenOffset, 1.0)).xy / 2.0 + vec2(0.5, 0.5); // transform to texture coords
v_texcoord = texcoord; v_texcoord = texcoord;
} }
)#"; )#";

View file

@ -116,7 +116,7 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
addConfig(CONFIG_INVERT, 0); addConfig(CONFIG_INVERT, 0);
addConfig(CONFIG_INVERT_SHADER, "normal"); addConfig(CONFIG_INVERT_SHADER, "normal");
addConfig(CONFIG_INVERT_CHROMA, 0); addConfig(CONFIG_INVERT_CHROMA, 0);
addConfig(CONFIG_INVERT_CHROMA_COLOR, 0xFF000000); // opaque black addConfig(CONFIG_INVERT_CHROMA_COLOR, (int) 0xFF000000); // opaque black
addShapeConfig(CONFIG_TILT_FUNCTION, "negative_quadratic"); addShapeConfig(CONFIG_TILT_FUNCTION, "negative_quadratic");
addShapeConfig(CONFIG_TILT_LIMIT, 5000); addShapeConfig(CONFIG_TILT_LIMIT, 5000);

View file

@ -194,6 +194,9 @@ void renderCursorTextureInternalWithDamageInverted(SP<CTexture> tex, CBox* pBox,
} }
glActiveTexture(GL_TEXTURE0); glActiveTexture(GL_TEXTURE0);
glBindTexture(g_pHyprOpenGL->m_RenderData.pCurrentMonData->offloadFB.m_cTex->m_iTarget, g_pHyprOpenGL->m_RenderData.pCurrentMonData->offloadFB.m_cTex->m_iTexID);
glActiveTexture(GL_TEXTURE1);
glBindTexture(tex->m_iTarget, tex->m_iTexID); glBindTexture(tex->m_iTarget, tex->m_iTexID);
if (g_pHyprOpenGL->m_RenderData.useNearestNeighbor || nearest) { if (g_pHyprOpenGL->m_RenderData.useNearestNeighbor || nearest) {