mirror of
https://github.com/hyprwm/hyprland-wiki.git
synced 2024-11-22 20:55:59 +01:00
plugin system init pages (#136)
This commit is contained in:
parent
905e3ee8d9
commit
2c51ca3860
5 changed files with 379 additions and 8 deletions
|
@ -82,13 +82,8 @@ Hyprland can run nested in a window. For that, make sure you did the following:
|
||||||
- built in debug
|
- built in debug
|
||||||
- removed ALL `exec=` and `exec-once=` keywords from your debug config
|
- removed ALL `exec=` and `exec-once=` keywords from your debug config
|
||||||
(`hyprlandd.conf`)
|
(`hyprlandd.conf`)
|
||||||
- set a resolution and are not using `preferred`
|
- set a resolution for `WL-1` and are not using `preferred`
|
||||||
- made sure no keybinds overlap (use a different mod for your keybinds altogether)
|
- made sure no keybinds overlap (use a different mod for your keybinds altogether)
|
||||||
|
|
||||||
Once you launch, the display will probably be completely garbled. To fix that,
|
Once you launch, the display might be cropped. This can be fixed by setting the resolution for `WL-1` to
|
||||||
in the parent, do a `hyprctl clients` and note the size of the window. Make sure
|
the exact dimensions of the window as reported by `hyprctl clients`.
|
||||||
while opening the terminal to not resize the nested window. Note that resolution
|
|
||||||
and use it down to the pixel in your `hyprlandd.conf`.
|
|
||||||
|
|
||||||
If you segfault in `shadowKeybinds`, you probably either are using the same mod
|
|
||||||
as your parent or resized the window.
|
|
||||||
|
|
148
pages/Plugins/Development/Advanced.md
Normal file
148
pages/Plugins/Development/Advanced.md
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
This page documents a few advanced things about the Hyprland Plugin API.
|
||||||
|
|
||||||
|
{{< toc >}}
|
||||||
|
|
||||||
|
## Using Function Hooks
|
||||||
|
|
||||||
|
{{< hint type=important >}}
|
||||||
|
|
||||||
|
Function hooks are only available on `AMD64` (`x86_64`).
|
||||||
|
Attempting to hook on any other arch will make Hyprland simply ignore your hooking attempt.
|
||||||
|
|
||||||
|
{{</ hint >}}
|
||||||
|
|
||||||
|
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:
|
||||||
|
```cpp
|
||||||
|
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.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 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
|
||||||
|
g_pMonitorFrameHook = HyprlandAPI::createFunctionHook(handle, (void*)&Events::listener_monitorFrame, (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.
|
||||||
|
|
||||||
|
### The three horsemen of function hooking
|
||||||
|
|
||||||
|
The first type of functions we have hooked above. It's a public non-member.
|
||||||
|
|
||||||
|
For public members, e.g. `CCompositor::focusWindow(CWindow*, wlr_surface*)` you will also need to add the thisptr argument to your hook:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
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...
|
||||||
|
|
||||||
|
g_pFocusWindowHook = HyprlandAPI::createFunctionHook(handle, (void*)&CCompositor::focusWindow, (void*)&hkFocusWindow);
|
||||||
|
g_pFocusWindowHook->hook();
|
||||||
|
|
||||||
|
// further stuff...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For private functions or members, you will need to use the signature, for example for `CInputManager::processMouseDownNormal`:
|
||||||
|
```cpp
|
||||||
|
typedef void (*origMouseDownNormal)(void*, wlr_pointer_button_event*);
|
||||||
|
|
||||||
|
void hkProcessMouseDownNormal(void* thisptr, wlr_pointer_button_event* e) {
|
||||||
|
// stuff...
|
||||||
|
|
||||||
|
// and if you want to call the original...
|
||||||
|
(*(origMouseDownNormal)g_pMouseDownHook->m_pOriginal)(thisptr, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
|
||||||
|
// stuff...
|
||||||
|
|
||||||
|
g_pMouseDownHook = HyprlandAPI::createFunctionHook(
|
||||||
|
PHANDLE, HyprlandAPI::getFunctionAddressFromSignature(PHANDLE, "_ZN13CInputManager22processMouseDownNormalEP24wlr_pointer_button_event"), (void*)&hkProcessMouseDownNormal);
|
||||||
|
|
||||||
|
g_pMouseDownHook->hook();
|
||||||
|
|
||||||
|
// further stuff...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
To get the signature, compile Hyprland and run `objdump -D ./path/to/Hyprland | grep "functionName"`
|
||||||
|
|
||||||
|
When it finally spits out something, you can stop it with `ctrl+C`.
|
||||||
|
|
||||||
|
Example output:
|
||||||
|
```
|
||||||
|
9710b9: eb 01 jmp 9710bc <_ZN13CInputManager22processMouseDownNormalEP24wlr_pointer_button_event+0xed8>
|
||||||
|
9710c3: 74 37 je 9710fc <_ZN13CInputManager22processMouseDownNormalEP24wlr_pointer_button_event+0xf18>
|
||||||
|
9710fa: eb 1f jmp 97111b <_ZN13CInputManager22processMouseDownNormalEP24wlr_pointer_button_event+0xf37>
|
||||||
|
971128: 74 05 je 97112f <_ZN13CInputManager22processMouseDownNormalEP24wlr_pointer_button_event+0xf4b>
|
||||||
|
```
|
||||||
|
From this, we can see the signature is `_ZN13CInputManager22processMouseDownNormalEP24wlr_pointer_button_event`.
|
||||||
|
|
||||||
|
{{< hint type=warning >}}
|
||||||
|
Please note signatures may and most likely will differ between compilers. (gcc/clang)
|
||||||
|
{{</ hint >}}
|
||||||
|
|
||||||
|
## Using the config
|
||||||
|
You can register config values in the `PLUGIN_INIT` function:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
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:
|
||||||
|
```cpp
|
||||||
|
static auto* const MYVAR = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:myPlugin:variable1")->intValue;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Further
|
||||||
|
Read the API at `src/plugins/PluginAPI.hpp` and check out the examplePlugin in `examples/`.
|
||||||
|
|
||||||
|
And, most importantly, have fun!
|
128
pages/Plugins/Development/Getting-Started.md
Normal file
128
pages/Plugins/Development/Getting-Started.md
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
This page documents the basics of making your own Hyprland plugin from scratch.
|
||||||
|
|
||||||
|
{{< toc >}}
|
||||||
|
|
||||||
|
## How do plugins work?
|
||||||
|
|
||||||
|
Plugins are basically dynamic objects loaded by Hyprland. They have
|
||||||
|
(almost) full access to every part of Hyprland's internal process, and as such,
|
||||||
|
can modify and change way more than a script.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
In order to write a Hyprland plugin, you will need:
|
||||||
|
- Knowledge of C++
|
||||||
|
- The ability to read
|
||||||
|
- A rough understanding of the Hyprland internals (you _can_ learn this alongside your development work)
|
||||||
|
|
||||||
|
## Making your first plugin
|
||||||
|
|
||||||
|
Open your favorite code editor.
|
||||||
|
|
||||||
|
Make a new directory, in this example we will use `MyPlugin`.
|
||||||
|
|
||||||
|
***→ If you have the Hyprland source already cloned***
|
||||||
|
|
||||||
|
Make sure you have ran `make pluginenv` in the source.
|
||||||
|
If you use the source to build hyprland, `make install` and `make config` will
|
||||||
|
already do that for you, so there is no need.
|
||||||
|
|
||||||
|
***→ If you don't have the Hyprland source cloned***
|
||||||
|
|
||||||
|
Clone the Hyprland source code to a subdirectory, in our example `MyPlugin/Hyprland`.
|
||||||
|
Run `cd Hyprland && make pluginenv && cd ..`.
|
||||||
|
|
||||||
|
Now that you have the Hyprland sources set up, copy the contents of `example/examplePlugin/` to your working directory.
|
||||||
|
|
||||||
|
Your `MyPlugin` directory now should contain `main.cpp`, `globals.hpp`, `exampleLayout.cpp`, etc.
|
||||||
|
|
||||||
|
This plugin has quite a few examples of the things you can do, but we will focus on the basics for now.
|
||||||
|
|
||||||
|
### The basic parts of the plugin
|
||||||
|
|
||||||
|
Starting from the top, you will have to include the plugin API:
|
||||||
|
```cpp
|
||||||
|
#include <src/plugins/PluginAPI.hpp>
|
||||||
|
```
|
||||||
|
Feel free to take a look at the header. It contains a bunch of useful comments.
|
||||||
|
|
||||||
|
We also create a global pointer for our handle:
|
||||||
|
```cpp
|
||||||
|
inline HANDLE PHANDLE = nullptr;
|
||||||
|
```
|
||||||
|
we will initialize it in our plugin init function later. It serves as an internal "ID" of our plugin.
|
||||||
|
|
||||||
|
Then, there is the API version method:
|
||||||
|
```cpp
|
||||||
|
// Do NOT change this function.
|
||||||
|
APICALL EXPORT std::string PLUGIN_API_VERSION() {
|
||||||
|
return HYPRLAND_API_VERSION;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
This method will tell Hyprland what API version was used to compile this plugin. Do NOT change it.
|
||||||
|
It will be set automatically when compiling to the correct value.
|
||||||
|
|
||||||
|
Skipping over some example handlers, we have two important functions:
|
||||||
|
```cpp
|
||||||
|
APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
|
||||||
|
PHANDLE = handle;
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
return {"MyPlugin", "An amazing plugin that is going to change the world!", "Me", "1.0"};
|
||||||
|
}
|
||||||
|
|
||||||
|
APICALL EXPORT void PLUGIN_EXIT() {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
The first method will be called when your plugin gets initialized (loaded)
|
||||||
|
|
||||||
|
You can, and probably should, initialize everything you may want to use in there.
|
||||||
|
|
||||||
|
It's worth noting that adding config variables is _only_ allowed in this function.
|
||||||
|
|
||||||
|
The plugin init function is _required_.
|
||||||
|
|
||||||
|
The return value should be the `PLUGIN_DESCRIPTION_INFO` struct which lets Hyprland know about your
|
||||||
|
plugin's name, description, author and version.
|
||||||
|
|
||||||
|
Make sure to store your `HANDLE` as it's going to be required for API calls.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
The second method is not required, and will be called when your plugin is being unloaded by the user.
|
||||||
|
|
||||||
|
If your plugin is being unloaded because it committed a fault, this function will _not_ be called.
|
||||||
|
|
||||||
|
You do not have to unload layouts, remove config options, remove dispatchers, window decorations or unregister hooks
|
||||||
|
in the exit method. Hyprland will do that for you.
|
||||||
|
|
||||||
|
### Setting up a development environment
|
||||||
|
In order to make your life easier, it's a good idea to work on a nested debug Hyprland session.
|
||||||
|
|
||||||
|
Enter your Hyprland directory and run `sudo make config && make protocols && make debug`
|
||||||
|
|
||||||
|
Make a copy of your config in `~/.config/hypr` called `hyprlandd.conf`.
|
||||||
|
|
||||||
|
Remove _all_ `exec=` or `exec-once=` directives from your config.
|
||||||
|
|
||||||
|
*recommended*: Change the modifier for your keybinds (e.g. `SUPER` -> `ALT`)
|
||||||
|
|
||||||
|
Add this line:
|
||||||
|
```ini
|
||||||
|
monitor = WL-1, 1920x1080, 0x0, 1
|
||||||
|
```
|
||||||
|
|
||||||
|
Launch the output `Hyprland` binary in `./build/` _when logged into a Hyprland session_.
|
||||||
|
|
||||||
|
A new window should open with Hyprland running inside of it. You can now run your plugin in the nested session without worrying
|
||||||
|
about nuking your actual session, and also being able to debug it easily.
|
||||||
|
|
||||||
|
See more info in [the Contributing Section](https://wiki.hyprland.org/Contributing-and-Debugging/#nesting-hyprland)
|
||||||
|
|
||||||
|
### More advanced stuff
|
||||||
|
|
||||||
|
Take a look at the `src/plugins/PluginAPI.hpp` header. It has comments to every method to let you know what it is.
|
||||||
|
|
||||||
|
For more explanation on a few concepts, see [Advanced](../Advanced) and [Plugin Guidelines](../Plugin-Guidelines)
|
28
pages/Plugins/Development/Plugin-Guidelines.md
Normal file
28
pages/Plugins/Development/Plugin-Guidelines.md
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
This page documents the recommended guidelines for making a stable and neat plugin.
|
||||||
|
|
||||||
|
{{< toc >}}
|
||||||
|
|
||||||
|
## Formatting
|
||||||
|
Although Hyprland plugins obviously are not _required_ to follow Hyprland's formatting, naming conventions, etc.
|
||||||
|
it might be a good idea to keep your code consistent. See `.clang-format` in the Hyprland repo.
|
||||||
|
|
||||||
|
## Usage of the API
|
||||||
|
It's always advised to use the API entries whenever possible, as they are guaranteed stability as long as the version
|
||||||
|
matches.
|
||||||
|
|
||||||
|
It is, of course, possible to use the internal methods by just including the proper headers,
|
||||||
|
but it should not be treated as the default way of doing things.
|
||||||
|
|
||||||
|
Hyprland's internal methods may be changed, removed or added without any prior notice. It is worth nothing though
|
||||||
|
that methods that "seem" fundamental, like e.g. `focusWindow` or `mouseMoveUnified` probably are, and are
|
||||||
|
unlikely to change their general method of functioning.
|
||||||
|
|
||||||
|
## Function Hooks
|
||||||
|
Function hooks allow your plugin to intercept all calls to a function of your choice. They are to be treated as
|
||||||
|
a last resort, as they are the easiest thing to break between updates.
|
||||||
|
|
||||||
|
Always prefer using Event Hooks.
|
||||||
|
|
||||||
|
## Threads
|
||||||
|
The Wayland event loop is strictly single-threaded. It is not recommended to create threads in your code, unless
|
||||||
|
they are fully detached from the Hyprland process. (e.g. saving a file)
|
72
pages/Plugins/Using-Plugins.md
Normal file
72
pages/Plugins/Using-Plugins.md
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
|
||||||
|
{{< toc >}}
|
||||||
|
|
||||||
|
## Getting plugins
|
||||||
|
|
||||||
|
Plugins come as _shared objects_, aka. `.so` files.
|
||||||
|
|
||||||
|
Hyprland does not have any "default" plugins, so any plugin you may want
|
||||||
|
to use you will have to find yourself.
|
||||||
|
|
||||||
|
## Installing / Using plugins
|
||||||
|
|
||||||
|
Clone and compile plugin(s) of your choice.
|
||||||
|
|
||||||
|
{{< hint type=tip >}}
|
||||||
|
Due to the fact that plugins share C++ objects, your plugins must be
|
||||||
|
compiled with the same compiler as Hyprland, and on the same architecture.
|
||||||
|
|
||||||
|
In rare cases, they might even need to be compiled on the same machine.
|
||||||
|
|
||||||
|
Official releases are always compiled with `gcc`.
|
||||||
|
{{< /hint >}}
|
||||||
|
|
||||||
|
Place them somewhere on your system.
|
||||||
|
|
||||||
|
In hyprland, run in a terminal:
|
||||||
|
```sh
|
||||||
|
hyprctl plugin load /path/to/the/plugin.so
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also add this to an `exec-once`:
|
||||||
|
```ini
|
||||||
|
exec-once = hyprctl plugin load /my/epic/plugin.so
|
||||||
|
```
|
||||||
|
|
||||||
|
{{< hint type=warning >}}
|
||||||
|
Plugins are written in C++ and will run as a part of Hyprland.
|
||||||
|
|
||||||
|
Make sure to _always_ read the source code of the plugins you are going to use
|
||||||
|
and to trust the source.
|
||||||
|
|
||||||
|
Writing a plugin to wipe your computer is easy.
|
||||||
|
|
||||||
|
***Never*** trust random `.so` files you receive from other people.
|
||||||
|
{{< /hint >}}
|
||||||
|
|
||||||
|
## FAQ About Plugins
|
||||||
|
|
||||||
|
### My plugin crashes Hyprland!
|
||||||
|
Oops. Make sure your plugin is compiled on the same machine as Hyprland. If that doesn't help,
|
||||||
|
ask the plugin's maintainer to fix it.
|
||||||
|
|
||||||
|
### How do I list my loaded plugins?
|
||||||
|
`hyprctl plugin list`
|
||||||
|
|
||||||
|
### Can I unload a plugin?
|
||||||
|
Yes. `hyprctl plugin unload /path/to/plugin.so`
|
||||||
|
|
||||||
|
### How do I make my own plugin?
|
||||||
|
See [here](../Development/Getting-Started.md).
|
||||||
|
|
||||||
|
### Where do I find plugins?
|
||||||
|
Try looking around [here](https://duckduckgo.com).
|
||||||
|
|
||||||
|
### Are plugins safe?
|
||||||
|
As long as you read the source code of your plugin(s) and can see there's nothing bad going on,
|
||||||
|
they will be safe.
|
||||||
|
|
||||||
|
### Do plugins decrease Hyprland's stability?
|
||||||
|
Hyprland employs a few tactics to unload plugins that crash. However, those tactics may not
|
||||||
|
always work. In general, as long as the plugin is well-designed, it should not affect the
|
||||||
|
stability of Hyprland.
|
Loading…
Reference in a new issue