feat: implemented shaperules

This commit is contained in:
Virt 2024-07-03 00:38:50 +02:00
commit 9fd1b6a1c2
13 changed files with 448 additions and 95 deletions

View file

@ -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 = <shape-name>, <mode> (optional), <property>: <value>, ...
shaperule = <shape-name>, <mode> (optional), <property>: <value>, ...
...
# 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.

118
src/config/ShapeRule.cpp Normal file
View file

@ -0,0 +1,118 @@
#include "ShapeRule.hpp"
#include <hyprutils/string/VarList.hpp>
#include <stdexcept>
#include <string>
#include <hyprutils/string/String.hpp>
#include <hyprutils/string/VarList.hpp>
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<std::string, float, int> 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<std::string> 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<std::string>(active->content[key]);
else return def;
}
int CShapeRuleHandler::getIntOr(std::string key, int def) {
if (active && active->content.contains(key)) return std::get<int>(active->content[key]);
else return def;
}
float CShapeRuleHandler::getFloatOr(std::string key, float def) {
if (active && active->content.contains(key)) return std::get<float>(active->content[key]);
else return def;
}

52
src/config/ShapeRule.hpp Normal file
View file

@ -0,0 +1,52 @@
#include <memory>
#include <optional>
#include <string>
#include <unordered_map>
#include <variant>
#include <hyprlang.hpp>
/* stores possible types in a shape rule */
enum EShapeRuleType {
STRING,
FLOAT,
INT
};
struct SShapeRule {
std::optional<std::string> mode;
std::unordered_map<std::string, std::variant<std::string, float, int>> content;
};
class CShapeRuleHandler {
/* induvidual rule content */
std::unordered_map<std::string, EShapeRuleType> content;
/* possible rules */
std::unordered_map<std::string, SShapeRule> 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<CShapeRuleHandler> g_pShapeRuleHandler;

65
src/config/config.cpp Normal file
View file

@ -0,0 +1,65 @@
#include "../globals.hpp"
#include "config.hpp"
#include <hyprland/src/plugins/PluginAPI.hpp>
#include <hyprlang.hpp>
#include <stdexcept>
#include <variant>
Hyprlang::CConfigValue toHyprlang(std::variant<std::string, float, int> value) {
if (std::holds_alternative<std::string>(value))
return Hyprlang::STRING { std::get<std::string>(value).c_str() };
if (std::holds_alternative<float>(value))
return Hyprlang::FLOAT { std::get<float>(value) };
if (std::holds_alternative<int>(value))
return Hyprlang::INT { std::get<int>(value) };
throw new std::logic_error("invalid type in variant?!");
}
EShapeRuleType toShapeRule(std::variant<std::string, float, int> value) {
if (std::holds_alternative<std::string>(value))
return EShapeRuleType::STRING;
if (std::holds_alternative<float>(value))
return EShapeRuleType::FLOAT;
if (std::holds_alternative<int>(value))
return EShapeRuleType::INT;
throw new std::logic_error("invalid type in variant?!");
}
void startConfig() {
g_pShapeRuleHandler = std::make_unique<CShapeRuleHandler>();
}
void addConfig(std::string name, std::variant<std::string, float, int> value) {
HyprlandAPI::addConfigValue(PHANDLE, NAMESPACE + name, toHyprlang(value));
}
void addShapeConfig(std::string name, std::variant<std::string, float, int> 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();
}

46
src/config/config.hpp Normal file
View file

@ -0,0 +1,46 @@
#pragma once
#include <hyprlang.hpp>
#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<std::string, float, int> value);
/* will add a config variable which is also a property for shape rules */
void addShapeConfig(std::string name, std::variant<std::string, float, int> value);
/* get static pointer to config value */
void* const* getConfig(std::string name);

View file

@ -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<CEventLoopTimer> 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<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* 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<CPointerManager::SMonitorPointerState> state, SP<CTexture> 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();
}
}
}

View file

@ -30,6 +30,11 @@ class CDynamicCursors {
/* hook on setHWCursorBuffer */
bool setHardware(CPointerManager* pointers, SP<CPointerManager::SMonitorPointerState> state, wlr_buffer* buf);
/* hook on setCursorFromName */
void setShape(const std::string& name);
/* hook on setCursorSoftware */
void unsetShape();
private:
SP<CEventLoopTimer> tick;

View file

@ -2,25 +2,4 @@
#include <hyprland/src/plugins/PluginAPI.hpp>
#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;

View file

@ -8,60 +8,73 @@
#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<CMonitor>, timespec*, CRegion&, std::optional<Vector2D>);
inline CFunctionHook* g_pRenderSoftwareCursorsForHook = nullptr;
void hkRenderSoftwareCursorsFor(void* thisptr, SP<CMonitor> pMonitor, timespec* now, CRegion& damage, std::optional<Vector2D> 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<CPointerManager::SMonitorPointerState>, SP<CTexture>);
inline CFunctionHook* g_pRenderHWCursorBufferHook = nullptr;
wlr_buffer* hkRenderHWCursorBuffer(void* thisptr, SP<CPointerManager::SMonitorPointerState> state, SP<CTexture> 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<CPointerManager::SMonitorPointerState>, wlr_buffer*);
inline CFunctionHook* g_pSetHWCursorBufferHook = nullptr;
bool hkSetHWCursorBuffer(void* thisptr, SP<CPointerManager::SMonitorPointerState> 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);
}
/* hooks a function hook */
CFunctionHook* hook(std::string name, void* function) {
auto names = HyprlandAPI::findFunctionsByName(PHANDLE, name);
auto match = names.at(0);
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);
}
Debug::log(LOG, "[dynamic-cursors] hooking on {} for {}", match.signature, name);
typedef void (*origSetCursorSurface)(void*, SP<CWLSurface>, const Vector2D&);
inline CFunctionHook* g_pSetCursorSurfaceHook = nullptr;
void hkSetCursorSurface(void* thisptr, SP<CWLSurface> 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, std::string object, void* function) {
auto names = HyprlandAPI::findFunctionsByName(PHANDLE, name);
// we hook on member functions, so search for them
for (auto match : names) {
if (!match.demangled.starts_with(object)) continue;
Debug::log(LOG, "[dynamic-cursors] hooking on {} for {}::{}", match.demangled, object, name);
auto hook = HyprlandAPI::createFunctionHook(PHANDLE, match.address, function);
hook->hook();
@ -69,6 +82,10 @@ CFunctionHook* hook(std::string name, void* function) {
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) {
PHANDLE = 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<CDynamicCursors>();
// 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");

View file

@ -1,4 +1,6 @@
#include "../globals.hpp"
#include "../config/config.hpp"
#include "src/macros.hpp"
#include <cmath>
#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;

View file

@ -1,6 +1,6 @@
#include "ModeStretch.hpp"
#include "utils.hpp"
#include "../globals.hpp"
#include "../config/config.hpp"
#include <hyprland/src/Compositor.hpp>
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;

View file

@ -1,6 +1,6 @@
#include "ModeTilt.hpp"
#include "utils.hpp"
#include "../globals.hpp"
#include "../config/config.hpp"
#include <hyprland/src/Compositor.hpp>
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;
}

View file

@ -1,12 +1,13 @@
#include "../globals.hpp"
#include "../config/config.hpp"
#include "src/managers/EventManager.hpp"
#include "Shake.hpp"
#include <hyprland/src/Compositor.hpp>
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);