treewide: replace hint with callout treewide: remove ToC header remove <toc>, since it's autogenerated add missing _index.md treewide: add frontmatter treewide: fix headings add weights Configuring,Getting Started: expand in sidebar Add version selector fix links
4.3 KiB
weight | title |
---|---|
4 | Advanced |
This page documents a few advanced things about the Hyprland Plugin API.
Using Function Hooks
{{< callout >}}
Function hooks are only available on AMD64
(x86_64
). Attempting to hook on
any other arch will make Hyprland simply ignore your hooking attempt.
{{</ callout >}}
Function hooks are intimidating at first, but when used properly can be extremely powerful.
Function hooks allow you to intercept any call to the function you hook.
Let's look at a simple example:
void Events::listener_monitorFrame(void* owner, void* data)
will be the function we want to hook. Events::
is a namespace, not a class, so
this is just a plain function.
// make a global instance of a hook class for this hook
inline CFunctionHook* g_pMonitorFrameHook = nullptr;
// create a pointer typedef for the function we are hooking.
typedef void (*origMonitorFrame)(void*, void*);
// our hook
void hkMonitorFrame(void* owner, void* data) {
(*(origMonitorFrame)g_pMonitorFrameHook->m_pOriginal)(owner, data);
}
APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
// stuff...
// create the hook
static const auto METHODS = HyprlandAPI::findFunctionsByName(PHANDLE, "listener_monitorFrame");
g_pMonitorFrameHook = HyprlandAPI::createFunctionHook(handle, METHODS[0].address, (void*)&hkMonitorFrame);
// init the hook
g_pMonitorFrameHook->hook();
// further stuff...
}
We have just made a hook. Now, whenever Hyprland calls
Events::listener_monitorFrame
, our hook will be called instead!
This way, you can run code before / after the function, modify the inputs or results, or even block the function from executing.
CFunctionHook
can also be unhooked whenever you please. Just run unhook()
.
It can be rehooked later by calling hook()
again.
Member functions
For members, e.g. CCompositor::focusWindow(CWindow*, wlr_surface*)
you will
also need to add the thisptr argument to your hook:
typedef void (*origFocusWindow)(void*, CWindow*, wlr_surface*);
void hkFocusWindow(void* thisptr, CWindow* pWindow, wlr_surface* pSurface) {
// stuff...
// and if you want to call the original...
(*(origFocusWindow)g_pFocusWindowHook->m_pOriginal)(thisptr, pWindow, pSurface);
}
APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
// stuff...
static const auto METHODS = HyprlandAPI::findFunctionsByName(PHANDLE, "focusWindow");
g_pFocusWindowHook = HyprlandAPI::createFunctionHook(handle, METHODS[0].address, (void*)&hkFocusWindow);
g_pFocusWindowHook->hook();
// further stuff...
}
{{< callout type=warning >}}
Please note method lookups are slow and should not be used often. The entries
will not change during runtime, so it's a good idea to make the lookups
static
.
{{</ callout >}}
Why use findFunctionsByName?
Why use that instead of e.g. &CCompositor::focusWindow
? Two reasons:
1 - less breakage. Whenever someone updates hyprland, that address might become invalid. findFunctionsByName is more resilient. As long as the function exists, it will be found.
2 - error handling. The method array contains, besides the address, the signatures. You can verify those to make 100% sure you got the right function, or throw an error if it was not found.
Using the config
You can register config values in the PLUGIN_INIT
function:
APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
// stuff...
HyprlandAPI::addConfigValue(PHANDLE, "plugin:example:exampleInt", SConfigValue{.intValue = 1});
// further stuff...
}
Plugin variables must be in the plugins:
category. Further categories
are up to you. It's generally a good idea to group all variables from your
plugin in a subcategory with the plugin name, e.g. plugins:myPlugin:variable1
.
For retrieving the values, call HyprlandAPI::getConfigValue
.
Please remember that the pointer to your config value will never change after
PLUGIN_INIT
, so to greatly optimize performance, make it static:
static auto* const MYVAR = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:myPlugin:variable1")->intValue;
Further
Read the API at src/plugins/PluginAPI.hpp
, check out the
official plugins.
And, most importantly, have fun!