From 31fb12e1f7b219f8cc27dea46ec42504584c8cca Mon Sep 17 00:00:00 2001 From: vaxerski <43317083+vaxerski@users.noreply.github.com> Date: Sat, 25 Feb 2023 22:17:01 +0000 Subject: [PATCH] plugin system init pages --- pages/Contributing and Debugging/_index.md | 11 +- pages/Plugins/Development/Advanced.md | 148 ++++++++++++++++++ pages/Plugins/Development/Getting-Started.md | 128 +++++++++++++++ .../Plugins/Development/Plugin-Guidelines.md | 28 ++++ pages/Plugins/Using-Plugins.md | 72 +++++++++ 5 files changed, 379 insertions(+), 8 deletions(-) create mode 100644 pages/Plugins/Development/Advanced.md create mode 100644 pages/Plugins/Development/Getting-Started.md create mode 100644 pages/Plugins/Development/Plugin-Guidelines.md create mode 100644 pages/Plugins/Using-Plugins.md diff --git a/pages/Contributing and Debugging/_index.md b/pages/Contributing and Debugging/_index.md index 1185868..671ad01 100644 --- a/pages/Contributing and Debugging/_index.md +++ b/pages/Contributing and Debugging/_index.md @@ -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`. diff --git a/pages/Plugins/Development/Advanced.md b/pages/Plugins/Development/Advanced.md new file mode 100644 index 0000000..3891f3e --- /dev/null +++ b/pages/Plugins/Development/Advanced.md @@ -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. + +{{}} + +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) +{{}} + +## 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! \ No newline at end of file diff --git a/pages/Plugins/Development/Getting-Started.md b/pages/Plugins/Development/Getting-Started.md new file mode 100644 index 0000000..cfa7430 --- /dev/null +++ b/pages/Plugins/Development/Getting-Started.md @@ -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 +``` +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) \ No newline at end of file diff --git a/pages/Plugins/Development/Plugin-Guidelines.md b/pages/Plugins/Development/Plugin-Guidelines.md new file mode 100644 index 0000000..9fd0e4d --- /dev/null +++ b/pages/Plugins/Development/Plugin-Guidelines.md @@ -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) \ No newline at end of file diff --git a/pages/Plugins/Using-Plugins.md b/pages/Plugins/Using-Plugins.md new file mode 100644 index 0000000..38a099d --- /dev/null +++ b/pages/Plugins/Using-Plugins.md @@ -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. \ No newline at end of file