From 9fd1b6a1c2c84029dba7e948b7c76afacfa5559b Mon Sep 17 00:00:00 2001 From: Virt <41426325+VirtCode@users.noreply.github.com> Date: Wed, 3 Jul 2024 00:38:50 +0200 Subject: [PATCH] feat: implemented shaperules --- README.md | 39 ++++++++++++- src/config/ShapeRule.cpp | 118 +++++++++++++++++++++++++++++++++++++++ src/config/ShapeRule.hpp | 52 +++++++++++++++++ src/config/config.cpp | 65 +++++++++++++++++++++ src/config/config.hpp | 46 +++++++++++++++ src/cursor.cpp | 45 +++++++++++---- src/cursor.hpp | 5 ++ src/globals.hpp | 21 ------- src/main.cpp | 109 ++++++++++++++++++++++-------------- src/mode/ModeRotate.cpp | 16 ++++-- src/mode/ModeStretch.cpp | 10 ++-- src/mode/ModeTilt.cpp | 10 ++-- src/other/Shake.cpp | 7 ++- 13 files changed, 448 insertions(+), 95 deletions(-) create mode 100644 src/config/ShapeRule.cpp create mode 100644 src/config/ShapeRule.hpp create mode 100644 src/config/config.cpp create mode 100644 src/config/config.hpp diff --git a/README.md b/README.md index c39a719..d88d911 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ This plugin is still very early in its development. There are also multiple thin - [X] stick simulation - [X] air drag simulation - [ ] pendulum simulation -- [ ] per-shape length and starting angle (if possible) +- [X] per-shape length and starting angle (if possible) - [X] cursor shake to find - [ ] overdue refactoring (wait for aquamarine merge) - [ ] ~~inverted cursor?~~ (i think out of scope here, but see the [inverted branch](https://github.com/VirtCode/hypr-dynamic-cursors/tree/inverted)) @@ -112,6 +112,14 @@ plugin:dynamic-cursors { # smaller values are smoother, but more expensive for hw cursors threshold = 2 + # override the mode behaviour per shape + # this is a keyword and can be repeated many times + # by default, there are no rules added + # see the dedicated `shape rules` section below! + shaperule = , (optional), : , ... + shaperule = , (optional), : , ... + ... + # for mode = rotate rotate { @@ -153,10 +161,10 @@ plugin:dynamic-cursors { } # configure shake to find + # magnifies the cursor if its is being shaken shake { # enables shake to find - # magnifies the cursor if its is being shaken enabled = true # controls how soon a shake is detected @@ -180,6 +188,33 @@ plugin:dynamic-cursors { } ``` +### shape rules +Shape Rules can be used to override the mode or its behaviour on a per-shape basis. They can be defined with the keyword `shaperule` in the config file, perferrably in the `plugin:dynamic-cursors` section. + +**Note:** Shape rules only apply to server side cursor shapes. Sadly, not everyone supports server side cursors yet, which means shape rules won't work with apps using toolkits like e.g. GTK. + +A shape rule usually consists of three parts: +``` +shaperule = shape-name, mode (optional), property: value, property: value, ... +``` +- `shape-name`: This is the name of the shape, this rule will apply to. Should be one of [those specified in the protocol](https://wayland.app/protocols/cursor-shape-v1#wp_cursor_shape_device_v1:enum:shape). You can use the special shape `clientside` to apply your rule to **ALL** client side cursors. +- `mode` (optional): Can override the mode used by this shape, see `mode` in the config. This argument is optional and can be left out. +- `property: value`: At the end of the rule follow zero or more property-value pairs. These are config values that will be overridden if this rule is active. Only config values from the sections `rotate`, `tilt`, `stretch` as seen above can be used. + +Here are a few example rules to get you started: +``` +plugin:dynamic-cursors { + # apply a 90° offset in rotate mode to the text shape + shaperule = text, rotate:offset: 90 + + # use stretch mode when grabbing, and set the limit low + shaperule = grab, stretch, stretch:limit: 2000 + + # do not show any effects on clientside cursors + shaperule = clientside, none +} +``` + ## performance > **TL;DR:** Hardware cursor performance is about the same as if an animated cursor shape was shown whenever you move your mouse. Sofware cursor performance is not impacted. When the cursor is magnified during a shake, the compositor will temporarily switch to software cursors. diff --git a/src/config/ShapeRule.cpp b/src/config/ShapeRule.cpp new file mode 100644 index 0000000..f117ae5 --- /dev/null +++ b/src/config/ShapeRule.cpp @@ -0,0 +1,118 @@ +#include "ShapeRule.hpp" +#include +#include +#include +#include +#include + +using namespace Hyprutils::String; + +void CShapeRuleHandler::clear() { + rules.clear(); + active = nullptr; +} + +void CShapeRuleHandler::activate(std::string key) { + if (rules.contains(key)) + active = &rules[key]; + else + active = nullptr; +} + +void CShapeRuleHandler::addProperty(std::string key, EShapeRuleType type) { + content[key] = type; +} + +std::variant parse(std::string value, EShapeRuleType type) { + switch (type) { + case EShapeRuleType::STRING: + return value; + case EShapeRuleType::FLOAT: + return std::stof(value); + case EShapeRuleType::INT: + return std::stoi(value); + } + + throw std::logic_error("unknown type"); +} + +void CShapeRuleHandler::parseRule(std::string string) { + std::optional name; + SShapeRule rule; + + CVarList list = CVarList(string); + + for (auto arg : list) { + if (!name.has_value()) name = arg; + else { + auto pos = arg.rfind(':'); + + // mode value + if (pos == std::string::npos) { + if (rule.mode.has_value()) + throw std::logic_error("cannot specify mode twice"); + + rule.mode = arg; + + // settings value + } else { + auto key = arg.substr(0, pos); + auto value = arg.substr(pos + 1); + + if (rule.content.contains(key)) + throw std::logic_error("cannot specify property " + key + " twice"); + + if (!content.contains(key)) + throw std::logic_error("unkown property " + key); + + auto type = content[key]; + + try { + rule.content[key] = parse(value, type); + } catch (...) { + throw std::logic_error("invalid type for property " + key); + } + } + } + } + + if (!name.has_value()) + throw std::logic_error("need to specify at least shape name"); + + if (rules.contains(name.value())) + throw std::logic_error("cannot have two rules for shape " + name.value()); + + rules[name.value()] = rule; +} + +Hyprlang::CParseResult onShapeRuleKeyword(const char* COMMAND, const char* VALUE) { + Hyprlang::CParseResult res; + + try { + g_pShapeRuleHandler->parseRule(std::string{VALUE}); + } catch (const std::exception& ex) { + res.setError(ex.what()); + } + + return res; +} + +std::string CShapeRuleHandler::getModeOr(std::string def) { + if (active) return active->mode.value_or(def); + else return def; +} + +std::string CShapeRuleHandler::getStringOr(std::string key, std::string def) { + if (active && active->content.contains(key)) return std::get(active->content[key]); + else return def; +} + +int CShapeRuleHandler::getIntOr(std::string key, int def) { + if (active && active->content.contains(key)) return std::get(active->content[key]); + else return def; +} + +float CShapeRuleHandler::getFloatOr(std::string key, float def) { + if (active && active->content.contains(key)) return std::get(active->content[key]); + else return def; +} diff --git a/src/config/ShapeRule.hpp b/src/config/ShapeRule.hpp new file mode 100644 index 0000000..0d088f7 --- /dev/null +++ b/src/config/ShapeRule.hpp @@ -0,0 +1,52 @@ +#include +#include +#include +#include +#include + +#include + +/* stores possible types in a shape rule */ +enum EShapeRuleType { + STRING, + FLOAT, + INT +}; + +struct SShapeRule { + std::optional mode; + std::unordered_map> content; +}; + +class CShapeRuleHandler { + /* induvidual rule content */ + std::unordered_map content; + + /* possible rules */ + std::unordered_map rules; + + /* currently active rule, nullptr if none */ + SShapeRule* active = nullptr; + + public: + /* adds a valid shape rule property */ + void addProperty(std::string key, EShapeRuleType type); + + /* removes currently added shape rules */ + void clear(); + /* adds a new shape rule from string */ + void parseRule(std::string string); + + /* activates the shape rule for the given shape */ + void activate(std::string name); + + std::string getModeOr(std::string def); + std::string getStringOr(std::string key, std::string def); + int getIntOr(std::string key, int def); + float getFloatOr(std::string key, float def); +}; + +/* method called by hyprland api */ +Hyprlang::CParseResult onShapeRuleKeyword(const char* COMMAND, const char* VALUE); + +inline std::unique_ptr g_pShapeRuleHandler; diff --git a/src/config/config.cpp b/src/config/config.cpp new file mode 100644 index 0000000..664e128 --- /dev/null +++ b/src/config/config.cpp @@ -0,0 +1,65 @@ +#include "../globals.hpp" +#include "config.hpp" +#include +#include +#include +#include + +Hyprlang::CConfigValue toHyprlang(std::variant value) { + + if (std::holds_alternative(value)) + return Hyprlang::STRING { std::get(value).c_str() }; + + if (std::holds_alternative(value)) + return Hyprlang::FLOAT { std::get(value) }; + + if (std::holds_alternative(value)) + return Hyprlang::INT { std::get(value) }; + + throw new std::logic_error("invalid type in variant?!"); +} + +EShapeRuleType toShapeRule(std::variant value) { + + if (std::holds_alternative(value)) + return EShapeRuleType::STRING; + + if (std::holds_alternative(value)) + return EShapeRuleType::FLOAT; + + if (std::holds_alternative(value)) + return EShapeRuleType::INT; + + throw new std::logic_error("invalid type in variant?!"); +} + +void startConfig() { + g_pShapeRuleHandler = std::make_unique(); +} + +void addConfig(std::string name, std::variant value) { + HyprlandAPI::addConfigValue(PHANDLE, NAMESPACE + name, toHyprlang(value)); +} + +void addShapeConfig(std::string name, std::variant value) { + addConfig(name, value); + + g_pShapeRuleHandler->addProperty(name, toShapeRule(value)); +} + +void* const* getConfig(std::string name) { + return HyprlandAPI::getConfigValue(PHANDLE, NAMESPACE + name)->getDataStaticPtr(); +} + +void addRulesConfig() { + HyprlandAPI::addConfigKeyword(PHANDLE, CONFIG_SHAPERULE, onShapeRuleKeyword, Hyprlang::SHandlerOptions {}); + + // clear on reload + static const auto PCALLBACK = HyprlandAPI::registerCallbackDynamic( PHANDLE, "preConfigReload", [&](void* self, SCallbackInfo&, std::any data) { + g_pShapeRuleHandler->clear(); + }); +} + +void finishConfig() { + HyprlandAPI::reloadConfig(); +} diff --git a/src/config/config.hpp b/src/config/config.hpp new file mode 100644 index 0000000..d9acb94 --- /dev/null +++ b/src/config/config.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include "ShapeRule.hpp" + +#define NAMESPACE "plugin:dynamic-cursors:" + +#define CONFIG_ENABLED "enabled" +#define CONFIG_MODE "mode" +#define CONFIG_THRESHOLD "threshold" +#define CONFIG_HW_DEBUG "hw_debug" + +#define CONFIG_SHAKE "shake:enabled" +#define CONFIG_SHAKE_NEAREST "shake:nearest" +#define CONFIG_SHAKE_THRESHOLD "shake:threshold" +#define CONFIG_SHAKE_FACTOR "shake:factor" +#define CONFIG_SHAKE_EFFECTS "shake:effects" +#define CONFIG_SHAKE_IPC "shake:ipc" + +#define CONFIG_ROTATE_LENGTH "rotate:length" +#define CONFIG_ROTATE_OFFSET "rotate:offset" + +#define CONFIG_TILT_LIMIT "tilt:limit" +#define CONFIG_TILT_FUNCTION "tilt:function" + +#define CONFIG_STRETCH_LIMIT "stretch:limit" +#define CONFIG_STRETCH_FUNCTION "stretch:function" + +#define CONFIG_SHAPERULE "shaperule" + +/* initializes stuff so config can be set up */ +void startConfig(); +/* finishes config setup */ +void finishConfig(); + +/* add shaperule config entry */ +void addRulesConfig(); + +/* will add an ordinary config value */ +void addConfig(std::string name, std::variant value); + +/* will add a config variable which is also a property for shape rules */ +void addShapeConfig(std::string name, std::variant value); + +/* get static pointer to config value */ +void* const* getConfig(std::string name); diff --git a/src/cursor.cpp b/src/cursor.cpp index fa3f9f2..7818f31 100644 --- a/src/cursor.cpp +++ b/src/cursor.cpp @@ -1,4 +1,4 @@ -#include "globals.hpp" +#include "config/config.hpp" #include "mode/Mode.hpp" #include "src/debug/Log.hpp" #include "src/managers/eventLoop/EventLoopManager.hpp" @@ -25,7 +25,7 @@ #include "renderer.hpp" void tickRaw(SP self, void* data) { - static auto* const* PENABLED = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_ENABLED)->getDataStaticPtr(); + static auto* const* PENABLED = (Hyprlang::INT* const*) getConfig(CONFIG_ENABLED); if (**PENABLED && g_pDynamicCursors) g_pDynamicCursors->onTick(g_pPointerManager.get()); @@ -56,7 +56,7 @@ 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* PNEAREST = (Hyprlang::INT* const*) getConfig(CONFIG_SHAKE_NEAREST); if (!pointers->hasCursor()) return; @@ -125,8 +125,8 @@ This function reimplements the hardware cursor buffer drawing. It is largely copied from hyprland, but adjusted to allow the cursor to be rotated. */ 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* PHW_DEBUG= (Hyprlang::INT* const*) getConfig(CONFIG_HW_DEBUG); + static auto* const* PNEAREST = (Hyprlang::INT* const*) getConfig(CONFIG_SHAKE_NEAREST); auto output = state->monitor->output; auto zoom = resultShown.scale; @@ -261,6 +261,14 @@ void CDynamicCursors::onCursorMoved(CPointerManager* pointers) { calculate(MOVE); } +void CDynamicCursors::setShape(const std::string& shape) { + g_pShapeRuleHandler->activate(shape); +} + +void CDynamicCursors::unsetShape() { + g_pShapeRuleHandler->activate("clientside"); +} + /* Handle cursor tick events. */ @@ -269,18 +277,19 @@ void CDynamicCursors::onTick(CPointerManager* pointers) { } IMode* CDynamicCursors::currentMode() { - static auto const* PMODE = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_MODE)->getDataStaticPtr(); + static auto const* PMODE = (Hyprlang::STRING const*) getConfig(CONFIG_MODE); + auto mode = g_pShapeRuleHandler->getModeOr(*PMODE); - if (!strcmp(*PMODE, "rotate")) return &rotate; - else if (!strcmp(*PMODE, "tilt")) return &tilt; - else if (!strcmp(*PMODE, "stretch")) return &stretch; + if (mode == "rotate") return &rotate; + else if (mode == "tilt") return &tilt; + else if (mode == "stretch") return &stretch; else return nullptr; } 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(); - static auto* const* PSHAKE_EFFECTS = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_SHAKE_EFFECTS)->getDataStaticPtr(); + static auto* const* PTHRESHOLD = (Hyprlang::INT* const*) getConfig(CONFIG_THRESHOLD); + static auto* const* PSHAKE = (Hyprlang::INT* const*) getConfig(CONFIG_SHAKE); + static auto* const* PSHAKE_EFFECTS = (Hyprlang::INT* const*) getConfig(CONFIG_SHAKE_EFFECTS); IMode* mode = currentMode(); @@ -322,12 +331,24 @@ void CDynamicCursors::calculate(EModeUpdate type) { // damage software and change hardware cursor shape g_pPointerManager->damageIfSoftware(); + bool entered = false; + for (auto& m : g_pCompositor->m_vMonitors) { auto state = g_pPointerManager->stateFor(m); + + if (state->entered) entered = true; if (state->hardwareFailed || !state->entered) continue; g_pPointerManager->attemptHardwareCursor(state); } + + // there should always be one monitor entered + // this fixes an issue wheter the cursor shape would not properly update after change + if (!entered) { + Debug::log(LOG, "[dynamic-cursors] updating because none entered"); + g_pPointerManager->recheckEnteredOutputs(); + g_pPointerManager->updateCursorBackend(); + } } } diff --git a/src/cursor.hpp b/src/cursor.hpp index ad6a6bb..d421ce7 100644 --- a/src/cursor.hpp +++ b/src/cursor.hpp @@ -30,6 +30,11 @@ class CDynamicCursors { /* hook on setHWCursorBuffer */ bool setHardware(CPointerManager* pointers, SP state, wlr_buffer* buf); + /* hook on setCursorFromName */ + void setShape(const std::string& name); + /* hook on setCursorSoftware */ + void unsetShape(); + private: SP tick; diff --git a/src/globals.hpp b/src/globals.hpp index f86df57..66c4f04 100644 --- a/src/globals.hpp +++ b/src/globals.hpp @@ -2,25 +2,4 @@ #include -#define CONFIG_ENABLED "plugin:dynamic-cursors:enabled" -#define CONFIG_MODE "plugin:dynamic-cursors:mode" -#define CONFIG_THRESHOLD "plugin:dynamic-cursors:threshold" -#define CONFIG_HW_DEBUG "plugin:dynamic-cursors:hw_debug" - -#define CONFIG_SHAKE "plugin:dynamic-cursors:shake:enabled" -#define CONFIG_SHAKE_NEAREST "plugin:dynamic-cursors:shake:nearest" -#define CONFIG_SHAKE_THRESHOLD "plugin:dynamic-cursors:shake:threshold" -#define CONFIG_SHAKE_FACTOR "plugin:dynamic-cursors:shake:factor" -#define CONFIG_SHAKE_EFFECTS "plugin:dynamic-cursors:shake:effects" -#define CONFIG_SHAKE_IPC "plugin:dynamic-cursors:shake:ipc" - -#define CONFIG_ROTATE_LENGTH "plugin:dynamic-cursors:rotate:length" -#define CONFIG_ROTATE_OFFSET "plugin:dynamic-cursors:rotate:offset" - -#define CONFIG_TILT_LIMIT "plugin:dynamic-cursors:tilt:limit" -#define CONFIG_TILT_FUNCTION "plugin:dynamic-cursors:tilt:function" - -#define CONFIG_STRETCH_LIMIT "plugin:dynamic-cursors:stretch:limit" -#define CONFIG_STRETCH_FUNCTION "plugin:dynamic-cursors:stretch:function" - inline HANDLE PHANDLE = nullptr; diff --git a/src/main.cpp b/src/main.cpp index a7f03a1..9d9c43d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,65 +8,82 @@ #include "globals.hpp" #include "cursor.hpp" +#include "config/config.hpp" #include "src/debug/Log.hpp" #include "src/managers/PointerManager.hpp" +bool isEnabled() { + static auto* const* PENABLED = (Hyprlang::INT* const*) getConfig(CONFIG_ENABLED); + return **PENABLED; +} + 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) { - static auto* const* PENABLED = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_ENABLED)->getDataStaticPtr(); - - if (**PENABLED) g_pDynamicCursors->renderSoftware((CPointerManager*) thisptr, pMonitor, now, damage, overridePos); + if (isEnabled()) g_pDynamicCursors->renderSoftware((CPointerManager*) thisptr, pMonitor, now, damage, overridePos); else (*(origRenderSofwareCursorsFor)g_pRenderSoftwareCursorsForHook->m_pOriginal)(thisptr, pMonitor, now, damage, overridePos); } typedef void (*origDamageIfSoftware)(void*); inline CFunctionHook* g_pDamageIfSoftwareHook = nullptr; void hkDamageIfSoftware(void* thisptr) { - static auto* const* PENABLED = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_ENABLED)->getDataStaticPtr(); - - if (**PENABLED) g_pDynamicCursors->damageSoftware((CPointerManager*) thisptr); + if (isEnabled()) g_pDynamicCursors->damageSoftware((CPointerManager*) thisptr); else (*(origDamageIfSoftware)g_pDamageIfSoftwareHook->m_pOriginal)(thisptr); } typedef wlr_buffer* (*origRenderHWCursorBuffer)(void*, SP, SP); inline CFunctionHook* g_pRenderHWCursorBufferHook = nullptr; wlr_buffer* hkRenderHWCursorBuffer(void* thisptr, SP state, SP texture) { - static auto* const* PENABLED = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_ENABLED)->getDataStaticPtr(); - - if (**PENABLED) return g_pDynamicCursors->renderHardware((CPointerManager*) thisptr, state, texture); + if (isEnabled()) return g_pDynamicCursors->renderHardware((CPointerManager*) thisptr, state, texture); else return (*(origRenderHWCursorBuffer)g_pRenderHWCursorBufferHook->m_pOriginal)(thisptr, state, texture); } typedef bool (*origSetHWCursorBuffer)(void*, SP, wlr_buffer*); inline CFunctionHook* g_pSetHWCursorBufferHook = nullptr; bool hkSetHWCursorBuffer(void* thisptr, SP state, wlr_buffer* buffer) { - static auto* const* PENABLED = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_ENABLED)->getDataStaticPtr(); - - if (**PENABLED) return g_pDynamicCursors->setHardware((CPointerManager*) thisptr, state, buffer); + if (isEnabled()) return g_pDynamicCursors->setHardware((CPointerManager*) thisptr, state, buffer); else return (*(origSetHWCursorBuffer)g_pSetHWCursorBufferHook->m_pOriginal)(thisptr, state, buffer); } typedef void (*origOnCursorMoved)(void*); inline CFunctionHook* g_pOnCursorMovedHook = nullptr; void hkOnCursorMoved(void* thisptr) { - static auto* const* PENABLED = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_ENABLED)->getDataStaticPtr(); - - if (**PENABLED) return g_pDynamicCursors->onCursorMoved((CPointerManager*) thisptr); + if (isEnabled()) return g_pDynamicCursors->onCursorMoved((CPointerManager*) thisptr); else return (*(origOnCursorMoved)g_pOnCursorMovedHook->m_pOriginal)(thisptr); } +typedef void (*origSetCusorFromName)(void*, const std::string& name); +inline CFunctionHook* g_pSetCursorFromNameHook = nullptr; +void hkSetCursorFromName(void* thisptr, const std::string& name) { + if (isEnabled()) g_pDynamicCursors->setShape(name); + (*(origSetCusorFromName)g_pSetCursorFromNameHook->m_pOriginal)(thisptr, name); +} + +typedef void (*origSetCursorSurface)(void*, SP, const Vector2D&); +inline CFunctionHook* g_pSetCursorSurfaceHook = nullptr; +void hkSetCursorSurface(void* thisptr, SP surf, const Vector2D& hotspot) { + if (isEnabled()) g_pDynamicCursors->unsetShape(); + (*(origSetCursorSurface)g_pSetCursorSurfaceHook->m_pOriginal)(thisptr, surf, hotspot); +} + /* hooks a function hook */ -CFunctionHook* hook(std::string name, void* function) { +CFunctionHook* hook(std::string name, std::string object, void* function) { auto names = HyprlandAPI::findFunctionsByName(PHANDLE, name); - auto match = names.at(0); - Debug::log(LOG, "[dynamic-cursors] hooking on {} for {}", match.signature, name); + // we hook on member functions, so search for them + for (auto match : names) { + if (!match.demangled.starts_with(object)) continue; - auto hook = HyprlandAPI::createFunctionHook(PHANDLE, match.address, function); - hook->hook(); + Debug::log(LOG, "[dynamic-cursors] hooking on {} for {}::{}", match.demangled, object, name); - return hook; + auto hook = HyprlandAPI::createFunctionHook(PHANDLE, match.address, function); + hook->hook(); + + return hook; + } + + Debug::log(ERR, "Could not find hooking candidate for {}::{}", object, name); + throw std::runtime_error("no hook candidate found"); } APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { @@ -80,40 +97,46 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { } // setup config - HyprlandAPI::addConfigValue(PHANDLE, CONFIG_ENABLED, Hyprlang::INT{1}); - HyprlandAPI::addConfigValue(PHANDLE, CONFIG_MODE, Hyprlang::STRING{"tilt"}); - HyprlandAPI::addConfigValue(PHANDLE, CONFIG_THRESHOLD, Hyprlang::INT{2}); + startConfig(); - HyprlandAPI::addConfigValue(PHANDLE, CONFIG_SHAKE, Hyprlang::INT{1}); - HyprlandAPI::addConfigValue(PHANDLE, CONFIG_SHAKE_NEAREST, Hyprlang::INT{1}); - HyprlandAPI::addConfigValue(PHANDLE, CONFIG_SHAKE_EFFECTS, Hyprlang::INT{0}); - HyprlandAPI::addConfigValue(PHANDLE, CONFIG_SHAKE_IPC, Hyprlang::INT{0}); - HyprlandAPI::addConfigValue(PHANDLE, CONFIG_SHAKE_THRESHOLD, Hyprlang::FLOAT{4}); - HyprlandAPI::addConfigValue(PHANDLE, CONFIG_SHAKE_FACTOR, Hyprlang::FLOAT{1.5}); + addConfig(CONFIG_ENABLED, true); + addConfig(CONFIG_MODE, "tilt"); + addConfig(CONFIG_THRESHOLD, 2); - HyprlandAPI::addConfigValue(PHANDLE, CONFIG_TILT_FUNCTION, Hyprlang::STRING{"negative_quadratic"}); - HyprlandAPI::addConfigValue(PHANDLE, CONFIG_TILT_LIMIT, Hyprlang::INT{5000}); + addConfig(CONFIG_SHAKE, true); + addConfig(CONFIG_SHAKE_NEAREST, true); + addConfig(CONFIG_SHAKE_EFFECTS, false); + addConfig(CONFIG_SHAKE_IPC, false); + addConfig(CONFIG_SHAKE_THRESHOLD, 4.0f); + addConfig(CONFIG_SHAKE_FACTOR, 1.5f); - HyprlandAPI::addConfigValue(PHANDLE, CONFIG_STRETCH_FUNCTION, Hyprlang::STRING{"negative_quadratic"}); - HyprlandAPI::addConfigValue(PHANDLE, CONFIG_STRETCH_LIMIT, Hyprlang::INT{3000}); + addShapeConfig(CONFIG_TILT_FUNCTION, "negative_quadratic"); + addShapeConfig(CONFIG_TILT_LIMIT, 5000); - HyprlandAPI::addConfigValue(PHANDLE, CONFIG_ROTATE_LENGTH, Hyprlang::INT{20}); - HyprlandAPI::addConfigValue(PHANDLE, CONFIG_ROTATE_OFFSET, Hyprlang::FLOAT{0}); + addShapeConfig(CONFIG_STRETCH_FUNCTION, "negative_quadratic"); + addShapeConfig(CONFIG_STRETCH_LIMIT, 3000); - HyprlandAPI::addConfigValue(PHANDLE, CONFIG_HW_DEBUG, Hyprlang::INT{0}); + addShapeConfig(CONFIG_ROTATE_LENGTH, 20); + addShapeConfig(CONFIG_ROTATE_OFFSET, 0.0f); - HyprlandAPI::reloadConfig(); + addConfig(CONFIG_HW_DEBUG, false); + + addRulesConfig(); + finishConfig(); // init things g_pDynamicCursors = std::make_unique(); // try hooking try { - g_pRenderSoftwareCursorsForHook = hook("renderSoftwareCursorsFor", (void*) &hkRenderSoftwareCursorsFor); - g_pDamageIfSoftwareHook = hook("damageIfSoftware", (void*) &hkDamageIfSoftware); - g_pRenderHWCursorBufferHook = hook("renderHWCursorBuffer", (void*) &hkRenderHWCursorBuffer); - g_pSetHWCursorBufferHook = hook("setHWCursorBuffer", (void*) &hkSetHWCursorBuffer); - g_pOnCursorMovedHook = hook("onCursorMoved", (void*) &hkOnCursorMoved); + g_pRenderSoftwareCursorsForHook = hook("renderSoftwareCursorsFor", "CPointerManager", (void*) &hkRenderSoftwareCursorsFor); + g_pDamageIfSoftwareHook = hook("damageIfSoftware", "CPointerManager", (void*) &hkDamageIfSoftware); + g_pRenderHWCursorBufferHook = hook("renderHWCursorBuffer", "CPointerManager", (void*) &hkRenderHWCursorBuffer); + g_pSetHWCursorBufferHook = hook("setHWCursorBuffer", "CPointerManager", (void*) &hkSetHWCursorBuffer); + g_pOnCursorMovedHook = hook("onCursorMoved", "CPointerManager", (void*) &hkOnCursorMoved); + + g_pSetCursorFromNameHook = hook("setCursorFromName", "CCursorManager", (void*) &hkSetCursorFromName); + g_pSetCursorSurfaceHook = hook("setCursorSurface", "CCursorManager", (void*) &hkSetCursorSurface); } catch (...) { HyprlandAPI::addNotification(PHANDLE, "[dynamic-cursors] Failed to load, hooks could not be made!", CColor{1.0, 0.2, 0.2, 1.0}, 5000); throw std::runtime_error("hooks failed"); diff --git a/src/mode/ModeRotate.cpp b/src/mode/ModeRotate.cpp index da53e4a..90c2b31 100644 --- a/src/mode/ModeRotate.cpp +++ b/src/mode/ModeRotate.cpp @@ -1,4 +1,6 @@ -#include "../globals.hpp" +#include "../config/config.hpp" +#include "src/macros.hpp" +#include #include "ModeRotate.hpp" EModeUpdate CModeRotate::strategy() { @@ -6,8 +8,10 @@ EModeUpdate CModeRotate::strategy() { } SModeResult CModeRotate::update(Vector2D pos) { - static auto* const* PLENGTH = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_ROTATE_LENGTH)->getDataStaticPtr(); - static auto* const* POFFSET = (Hyprlang::FLOAT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_ROTATE_OFFSET)->getDataStaticPtr(); + static auto* const* PLENGTH = (Hyprlang::INT* const*) getConfig(CONFIG_ROTATE_LENGTH); + static auto* const* POFFSET = (Hyprlang::FLOAT* const*) getConfig(CONFIG_ROTATE_OFFSET); + auto length = g_pShapeRuleHandler->getIntOr(CONFIG_ROTATE_LENGTH, **PLENGTH); + auto offset = g_pShapeRuleHandler->getFloatOr(CONFIG_ROTATE_OFFSET, **POFFSET); // translate to origin end.x -= pos.x; @@ -19,14 +23,14 @@ SModeResult CModeRotate::update(Vector2D pos) { end.y /= size; // scale to length - end.x *= **PLENGTH; - end.y *= **PLENGTH; + end.x *= length; + end.y *= length; // calculate angle double angle = -std::atan(end.x / end.y); if (end.y > 0) angle += PI; angle += PI; - angle += **POFFSET * ((2 * PI) / 360); // convert to radiants + angle += offset * ((2 * PI) / 360); // convert to radiants // translate back end.x += pos.x; diff --git a/src/mode/ModeStretch.cpp b/src/mode/ModeStretch.cpp index a4a1067..d70f20b 100644 --- a/src/mode/ModeStretch.cpp +++ b/src/mode/ModeStretch.cpp @@ -1,6 +1,6 @@ #include "ModeStretch.hpp" #include "utils.hpp" -#include "../globals.hpp" +#include "../config/config.hpp" #include EModeUpdate CModeStretch::strategy() { @@ -8,8 +8,10 @@ EModeUpdate CModeStretch::strategy() { } SModeResult CModeStretch::update(Vector2D pos) { - static auto const* PFUNCTION = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_STRETCH_FUNCTION)->getDataStaticPtr(); - static auto* const* PLIMIT = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_STRETCH_LIMIT)->getDataStaticPtr(); + static auto const* PFUNCTION = (Hyprlang::STRING const*) getConfig(CONFIG_STRETCH_FUNCTION); + static auto* const* PLIMIT = (Hyprlang::INT* const*) getConfig(CONFIG_STRETCH_LIMIT); + auto function = g_pShapeRuleHandler->getStringOr(CONFIG_STRETCH_FUNCTION, *PFUNCTION); + auto limit = g_pShapeRuleHandler->getIntOr(CONFIG_STRETCH_LIMIT, **PLIMIT); // create samples array int max = g_pHyprRenderer->m_pMostHzMonitor->refreshRate / 10; // 100ms worth of history @@ -29,7 +31,7 @@ SModeResult CModeStretch::update(Vector2D pos) { if (speed.y > 0) angle += PI; if (mag == 0) angle = 0; - double scale = activation(*PFUNCTION, **PLIMIT, mag); + double scale = activation(function, limit, mag); auto result = SModeResult(); result.stretch.angle = angle; diff --git a/src/mode/ModeTilt.cpp b/src/mode/ModeTilt.cpp index c3bddeb..156314f 100644 --- a/src/mode/ModeTilt.cpp +++ b/src/mode/ModeTilt.cpp @@ -1,6 +1,6 @@ #include "ModeTilt.hpp" #include "utils.hpp" -#include "../globals.hpp" +#include "../config/config.hpp" #include EModeUpdate CModeTilt::strategy() { @@ -8,8 +8,10 @@ EModeUpdate CModeTilt::strategy() { } SModeResult CModeTilt::update(Vector2D pos) { - static auto const* PFUNCTION = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_TILT_FUNCTION)->getDataStaticPtr(); - static auto* const* PMASS = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_TILT_LIMIT)->getDataStaticPtr(); + static auto const* PFUNCTION = (Hyprlang::STRING const*) getConfig(CONFIG_TILT_FUNCTION); + static auto* const* PLIMIT = (Hyprlang::INT* const*) getConfig(CONFIG_TILT_LIMIT); + auto function = g_pShapeRuleHandler->getStringOr(CONFIG_TILT_FUNCTION, *PFUNCTION); + auto limit = g_pShapeRuleHandler->getIntOr(CONFIG_TILT_LIMIT, **PLIMIT); // create samples array int max = g_pHyprRenderer->m_pMostHzMonitor->refreshRate / 10; // 100ms worth of history @@ -25,6 +27,6 @@ SModeResult CModeTilt::update(Vector2D pos) { double speed = (samples[current].x - samples[first].x) / 0.1; auto result = SModeResult(); - result.rotation = activation(*PFUNCTION, **PMASS, speed) * (PI / 3); // 120° in both directions + result.rotation = activation(function, limit, speed) * (PI / 3); // 120° in both directions return result; } diff --git a/src/other/Shake.cpp b/src/other/Shake.cpp index 2e78dbb..3a62777 100644 --- a/src/other/Shake.cpp +++ b/src/other/Shake.cpp @@ -1,12 +1,13 @@ #include "../globals.hpp" +#include "../config/config.hpp" #include "src/managers/EventManager.hpp" #include "Shake.hpp" #include double CShake::update(Vector2D pos) { - static auto* const* PTHRESHOLD = (Hyprlang::FLOAT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_SHAKE_THRESHOLD)->getDataStaticPtr(); - static auto* const* PFACTOR = (Hyprlang::FLOAT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_SHAKE_FACTOR)->getDataStaticPtr(); - static auto* const* PIPC = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_SHAKE_IPC)->getDataStaticPtr(); + static auto* const* PTHRESHOLD = (Hyprlang::FLOAT* const*) getConfig(CONFIG_SHAKE_THRESHOLD); + static auto* const* PFACTOR = (Hyprlang::FLOAT* const*) getConfig(CONFIG_SHAKE_FACTOR); + static auto* const* PIPC = (Hyprlang::INT* const*) getConfig(CONFIG_SHAKE_IPC); int max = g_pHyprRenderer->m_pMostHzMonitor->refreshRate; // 1s worth of history samples.resize(max);