plugin system init pages (#136)

This commit is contained in:
Vaxry 2023-02-27 12:32:56 +00:00 committed by GitHub
parent 905e3ee8d9
commit 2c51ca3860
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 379 additions and 8 deletions

View file

@ -82,13 +82,8 @@ Hyprland can run nested in a window. For that, make sure you did the following:
- built in debug
- removed ALL `exec=` and `exec-once=` keywords from your debug config
(`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)
Once you launch, the display will probably be completely garbled. To fix that,
in the parent, do a `hyprctl clients` and note the size of the window. Make sure
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.
Once you launch, the display might be cropped. This can be fixed by setting the resolution for `WL-1` to
the exact dimensions of the window as reported by `hyprctl clients`.

View 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!

View 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)

View 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)

View 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.