From 673c9e995a4384da0af0abd2cef5dde69094258e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Zag=C3=B3rowski?= Date: Thu, 6 Apr 2023 14:08:42 +0200 Subject: [PATCH] Initial Commit --- .clang-format | 65 ++++++++++++++++++++++++ .gitignore | 2 + Makefile | 72 +++++++++++++++++++++++++++ README.md | 92 ++++++++++++++++++++++++++++++++++ include/customDecoration.hpp | 29 +++++++++++ include/customLayout.hpp | 32 ++++++++++++ include/globals.hpp | 5 ++ src/customDecoration.cpp | 74 ++++++++++++++++++++++++++++ src/customLayout.cpp | 80 ++++++++++++++++++++++++++++++ src/main.cpp | 95 ++++++++++++++++++++++++++++++++++++ 10 files changed, 546 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 include/customDecoration.hpp create mode 100644 include/customLayout.hpp create mode 100644 include/globals.hpp create mode 100644 src/customDecoration.cpp create mode 100644 src/customLayout.cpp create mode 100644 src/main.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..90314ef --- /dev/null +++ b/.clang-format @@ -0,0 +1,65 @@ +--- +Language: Cpp +BasedOnStyle: LLVM + +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: true +AlignConsecutiveAssignments: true +AlignEscapedNewlines: Right +AlignOperands: false +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: true +AllowShortCaseLabelsOnASingleLine: true +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +BreakBeforeBraces: Attach +BreakBeforeTernaryOperators: false +BreakConstructorInitializers: AfterColon +ColumnLimit: 180 +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false +IncludeBlocks: Preserve +IndentCaseLabels: true +IndentWidth: 4 +PointerAlignment: Left +ReflowComments: false +SortIncludes: false +SortUsingDeclarations: false +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +TabWidth: 4 +UseTab: Never + +AllowShortEnumsOnASingleLine: false + +BraceWrapping: + AfterEnum: false + +AlignConsecutiveDeclarations: AcrossEmptyLines + +NamespaceIndentation: All diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6c331cb --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +compile_flags.txt +obj/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..36fc0fb --- /dev/null +++ b/Makefile @@ -0,0 +1,72 @@ +# compile with HYPRLAND_HEADERS= make all +# make sure that the path above is to the root hl repo directory, NOT src/ +# and that you have ran `make protocols` in the hl dir. + +# This Makefile is not intended to be used directly, but rather as a template for your own plugin +# Change the PLUGIN_NAME variable to the name of your plugin +PLUGIN_NAME=example + +# Enable parallel builds +MAKEFLAGS := --jobs=$(shell nproc) +MAKEFLAGS += --output-sync=target + +# Source files +SOURCE_FILES=$(wildcard src/*.cpp) +# Add any new source file directories to the SOURCE_FILES variable +# SOURCE_FILES+=/*.cpp + +# Header files, used to check if they have changed +INCLUDE_FILES=$(wildcard include/*.hpp) +INCLUDE_FILES+=$(wildcard include/*.h) +# Add any new header file directories to the INCLUDE_FILES variable +# INCLUDE_FILES+=/*.hpp + +# Intermediate object files +OBJECT_DIR=obj + +# Compiler flags +COMPILE_FLAGS=-g -fPIC --no-gnu-unique -std=c++23 +COMPILE_FLAGS+=-I "/usr/include/pixman-1" +COMPILE_FLAGS+=-I "/usr/include/libdrm" +COMPILE_FLAGS+=-I "${HYPRLAND_HEADERS}" +COMPILE_FLAGS+=-I "${HYPRLAND_HEADERS}/subprojects/wlroots/include" +COMPILE_FLAGS+=-I "${HYPRLAND_HEADERS}/subprojects/wlroots/build/include" +COMPILE_FLAGS+=-Iinclude + +# Linker flags, set to shared library (plugin) +LINK_FLAGS=-shared + +# Phony targets (i.e. targets that don't actually build anything, and don't track dependencies) +# These will always be run when called +.PHONY: clean clangd + +# build +all: check_env $(PLUGIN_NAME).so + +# install to ~/.local/share/hyprload/plugins/bin, assuming that hyprload is installed +install: all + cp $(PLUGIN_NAME).so ${HOME}/.local/share/hyprload/plugins/bin + +# ensure that HYPRLAND_HEADERS is set, otherwise error. Gives a more helpful error message than just include errors +check_env: + mkdir -p $(OBJECT_DIR) +ifndef HYPRLAND_HEADERS + $(error HYPRLAND_HEADERS is undefined! Please set it to the path to the root of the configured Hyprland repo) +endif + +# build the plugin, using the headers from the configured Hyprland repo +$(OBJECT_DIR)/%.o: src/%.cpp $(INCLUDE_FILES) + g++ -c -o $@ $< $(COMPILE_FLAGS) + +$(PLUGIN_NAME).so: $(addprefix $(OBJECT_DIR)/, $(notdir $(SOURCE_FILES:.cpp=.o))) + g++ $(LINK_FLAGS) -o $@ $^ $(COMPILE_FLAGS) + +# clean up +clean: + rm -f $(OBJECT_DIR)/*.o + rm -f ./$(PLUGIN_NAME).so + +# generate compile_flags.txt for clangd, if you use it. This is not necessary for building the plugin +# the alternative is to use the compile_commands.json file generated by eg. bear +clangd: + echo "$(COMPILE_FLAGS)" | sed 's/ -/\n-/g' | sed 's/--no-gnu-unique//g' | sed 's/-I \//-I\//g' | sed 's/std=c++23/std=c++2b/g' > compile_flags.txt diff --git a/README.md b/README.md new file mode 100644 index 0000000..d0740ea --- /dev/null +++ b/README.md @@ -0,0 +1,92 @@ +# Hyprland Plugin Template +The goal of this repository is to create a robust `Hyprland` plugin template, with +- A working, extensible `Makefile` +- [`hyprload`](https://github.com/Duckonaut/hyprload) support out of the box +- Environment set up guide +- Clangd flags set up for autocomplete and error checking + +It is highly recommended to read the [Plugin development](https://wiki.hyprland.org/Plugins/Development/Getting-Started/) +section of the Hyprland Wiki first. Some stuff will be different in this template, but it gives +you a general idea about what's going on + +## Support +If you have any issues setting this up, open an issue in this repository. I will try to help. + +## Setup +This is a github template repository. To use it, use the green **Use this template** button +at the top of the repository file view. + +### Setting up a development environment +#### Text editor and autocompletion +For a code editor, I recommend VS Code or Neovim, but anything that can use Clangd will work +If you use Clangd, `make clangd` will generate a simple `compile_flags.txt` file with the proper +include paths and flags, which will make Clangd recognize the includes etc. + +> **Warning**: Compiling the plugin should still be done using GCC 12+. Clang does not properly +> build Hyprland, and is very fussy about the hook system. You will most likely encounter errors +> like `cannot cast from type 'void (CCompositor::*)(CWindow *, wlr_surface *)' to pointer type 'void *'` +> This won't happen when building with GCC and can be ignored. + +#### HYPRLAND_HEADERS +The most important part of setting up plugin builds is the `HYPRLAND_HEADERS` variable. +Plugins can hook directly into Hyprland's C++ code, which is what makes them so powerful. +Because of that, they need to be able to *see* the Hyprland source. `HYPRLAND_HEADERS` ensures +that. + +When building your own plugins for testing, you will need to manually define it using +`export HYPRLAND_HEADERS=(PATH_TO_HYPRLAND_SOURCE_ROOT)` before running `make` commands. You +can use a local path if you keep `Hyprland` source anyway, but I'd definitely recomment using +`hyprload`. If you use your local source different from the `hyprload` one, make sure to +run `make pluginenv` in the Hyprland folder. + +And here, excuse me, but I will go on a tangent about why `hyprload` is great: + +#### Hyprload, and why it's useful for plugin development +If you use `hyprload`, it will keep a copy of Hyprland source code up to date with the Hyprland +version you're running in `$HOME/.local/share/hyprload/hyprland`, and you can use that as your +`HYPRLAND_HEADERS` path. + +When installing your plugin on other people's computers, `hyprload` will automatically define +`HYPRLAND_HEADERS` to that path to ensure maximum compatibility. + +When developing plugins and frequently changing them, the `make install` command will +automatically place your plugin build in the directory `hyprload` automatically loads. You can +reload plugins when testing using the `hyprload,reload` dispatcher (bind it in your +`hyprland.conf` + +#### Making it Your Own +To change your plugin name, version, and author (that's you!) there are 3 variables that need +changing (I would like to streamline it somehow, but it's manageable for now) +- `main.cpp`: The `PLUGIN_INIT` function returns a struct with the plugin name, description, + author and version. Change those. +- `Makefile`: At the top of the file, the variable `PLUGIN_NAME` contains the name of the plugin + `.so` that will be built. This should generally match your plugin name. +- `hyprload.toml`: The `[examplePlugin]` and `[examplePlugin.build]` should be changed to match + the name of your plugin. `hyprload` will look at these dictionaries for info about the plugin. + For more info, see [hyprload docs](https://github.com/Duckonaut/hyprload#format) + +## Building and testing +After making sure you have defined `HYPRLAND_HEADERS` (you might need to do this *every time +you open a new terminal* if you don't put it in your `.bashrc` or `.zshrc` or whatever), the +steps to build are simple + +### Manual way of doing things +- `make`: This will build the `PLUGIN_NAME.so` file. +- `hyprctl plugin unload $PWD/PLUGIN_NAME.so`: If you have an old version loaded, unload it +- `hyprctl plugin load $PWD/PLUGIN_NAME.so`: Load the plugin + +### The `hyprload` way +This works rather well in nested Hyprland sessions, since `hyprload` keeps sessions separate. +- `make install`: This will build and copy the plugin to the `hyprload` plugin directory. +- Reload `hyprload` for the changes to take effect + +### Nested Hyprland +Developing a plugin may be tough. You might crash Hyprland a couple times. For this reason, +it's a good idea to develop them in a nested Hyprland session. If you run `Hyprland` from an +existing Hyprland session, it'll open in a window. If this window crashes, it's pretty much fine! +Refer to the [Hyprland wiki](http://wiki.hyprland.org/Plugins/Development/Getting-Started/#setting-up-a-development-environment) +for more info. + +## ""Publishing"" +If you haven't messed up your `hyprload.toml` manifest too badly, anyone should be able to use +your plugin by just adding `'YOUR_GITHUB_NAME/YOUR_PLUGIN'` to their own `hyprload.toml` config! diff --git a/include/customDecoration.hpp b/include/customDecoration.hpp new file mode 100644 index 0000000..dbd6341 --- /dev/null +++ b/include/customDecoration.hpp @@ -0,0 +1,29 @@ +#pragma once + +#define WLR_USE_UNSTABLE + +#include + +class CCustomDecoration : public IHyprWindowDecoration { + public: + CCustomDecoration(CWindow*); + virtual ~CCustomDecoration(); + + virtual SWindowDecorationExtents getWindowDecorationExtents(); + + virtual void draw(CMonitor*, float a, const Vector2D& offset); + + virtual eDecorationType getDecorationType(); + + virtual void updateWindow(CWindow*); + + virtual void damageEntire(); + + private: + SWindowDecorationExtents m_seExtents; + + CWindow* m_pWindow = nullptr; + + Vector2D m_vLastWindowPos; + Vector2D m_vLastWindowSize; +}; diff --git a/include/customLayout.hpp b/include/customLayout.hpp new file mode 100644 index 0000000..8128128 --- /dev/null +++ b/include/customLayout.hpp @@ -0,0 +1,32 @@ +#pragma once + +#define WLR_USE_UNSTABLE + +#include + +struct SWindowData { + CWindow* pWindow = nullptr; +}; + +class CHyprCustomLayout : public IHyprLayout { + public: + virtual void onWindowCreatedTiling(CWindow*); + virtual void onWindowRemovedTiling(CWindow*); + virtual bool isWindowTiled(CWindow*); + virtual void recalculateMonitor(const int&); + virtual void recalculateWindow(CWindow*); + virtual void resizeActiveWindow(const Vector2D&, CWindow* pWindow = nullptr); + virtual void fullscreenRequestForWindow(CWindow*, eFullscreenMode, bool); + virtual std::any layoutMessage(SLayoutMessageHeader, std::string); + virtual SWindowRenderLayoutHints requestRenderHints(CWindow*); + virtual void switchWindows(CWindow*, CWindow*); + virtual void alterSplitRatio(CWindow*, float, bool); + virtual std::string getLayoutName(); + virtual void replaceWindowDataWith(CWindow* from, CWindow* to); + + virtual void onEnable(); + virtual void onDisable(); + + private: + std::vector m_vWindowData; +}; diff --git a/include/globals.hpp b/include/globals.hpp new file mode 100644 index 0000000..37e8363 --- /dev/null +++ b/include/globals.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include + +inline HANDLE PHANDLE = nullptr; \ No newline at end of file diff --git a/src/customDecoration.cpp b/src/customDecoration.cpp new file mode 100644 index 0000000..fd4a76f --- /dev/null +++ b/src/customDecoration.cpp @@ -0,0 +1,74 @@ +#include "customDecoration.hpp" +#include +#include +#include "globals.hpp" + +CCustomDecoration::CCustomDecoration(CWindow* pWindow) { + m_pWindow = pWindow; + m_vLastWindowPos = pWindow->m_vRealPosition.vec(); + m_vLastWindowSize = pWindow->m_vRealSize.vec(); +} + +CCustomDecoration::~CCustomDecoration() { + damageEntire(); +} + +SWindowDecorationExtents CCustomDecoration::getWindowDecorationExtents() { + return m_seExtents; +} + +void CCustomDecoration::draw(CMonitor* pMonitor, float a, const Vector2D& offset) { + if (!g_pCompositor->windowValidMapped(m_pWindow)) + return; + + if (!m_pWindow->m_sSpecialRenderData.decorate) + return; + + static auto* const PCOLOR = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:example:border_color")->intValue; + static auto* const PROUNDING = &HyprlandAPI::getConfigValue(PHANDLE, "decoration:rounding")->intValue; + static auto* const PBORDERSIZE = &HyprlandAPI::getConfigValue(PHANDLE, "general:border_size")->intValue; + + const auto ROUNDING = !m_pWindow->m_sSpecialRenderData.rounding ? + 0 : + (m_pWindow->m_sAdditionalConfigData.rounding.toUnderlying() == -1 ? *PROUNDING : m_pWindow->m_sAdditionalConfigData.rounding.toUnderlying()); + + // draw the border + wlr_box fullBox = {(int)(m_vLastWindowPos.x - *PBORDERSIZE), (int)(m_vLastWindowPos.y - *PBORDERSIZE), (int)(m_vLastWindowSize.x + 2.0 * *PBORDERSIZE), + (int)(m_vLastWindowSize.y + 2.0 * *PBORDERSIZE)}; + + fullBox.x -= pMonitor->vecPosition.x; + fullBox.y -= pMonitor->vecPosition.y; + + m_seExtents = {{m_vLastWindowPos.x - fullBox.x - pMonitor->vecPosition.x + 2, m_vLastWindowPos.y - fullBox.y - pMonitor->vecPosition.y + 2}, + {fullBox.x + fullBox.width + pMonitor->vecPosition.x - m_vLastWindowPos.x - m_vLastWindowSize.x + 2, + fullBox.y + fullBox.height + pMonitor->vecPosition.y - m_vLastWindowPos.y - m_vLastWindowSize.y + 2}}; + + fullBox.x += offset.x; + fullBox.y += offset.y; + + if (fullBox.width < 1 || fullBox.height < 1) + return; // don't draw invisible shadows + + g_pHyprOpenGL->scissor((wlr_box*)nullptr); + + scaleBox(&fullBox, pMonitor->scale); + g_pHyprOpenGL->renderBorder(&fullBox, CColor(*PCOLOR), *PROUNDING * pMonitor->scale + *PBORDERSIZE * 2, a); +} + +eDecorationType CCustomDecoration::getDecorationType() { + return DECORATION_CUSTOM; +} + +void CCustomDecoration::updateWindow(CWindow* pWindow) { + + m_vLastWindowPos = pWindow->m_vRealPosition.vec(); + m_vLastWindowSize = pWindow->m_vRealSize.vec(); + + damageEntire(); +} + +void CCustomDecoration::damageEntire() { + wlr_box dm = {(int)(m_vLastWindowPos.x - m_seExtents.topLeft.x), (int)(m_vLastWindowPos.y - m_seExtents.topLeft.y), + (int)(m_vLastWindowSize.x + m_seExtents.topLeft.x + m_seExtents.bottomRight.x), (int)m_seExtents.topLeft.y}; + g_pHyprRenderer->damageBox(&dm); +} diff --git a/src/customLayout.cpp b/src/customLayout.cpp new file mode 100644 index 0000000..a3fbe78 --- /dev/null +++ b/src/customLayout.cpp @@ -0,0 +1,80 @@ +#include "customLayout.hpp" +#include +#include "globals.hpp" + +void CHyprCustomLayout::onWindowCreatedTiling(CWindow* pWindow) { + const auto PMONITOR = g_pCompositor->getMonitorFromID(pWindow->m_iMonitorID); + const auto SIZE = PMONITOR->vecSize; + + // these are used for focus and move calculations, and are *required* to touch for moving focus to work properly. + pWindow->m_vPosition = Vector2D{(SIZE.x / 2.0) * (m_vWindowData.size() % 2), (SIZE.y / 2.0) * (int)(m_vWindowData.size() > 1)}; + pWindow->m_vSize = SIZE / 2.0; + + // this is the actual pos and size of the window (where it's rendered) + pWindow->m_vRealPosition = pWindow->m_vPosition + Vector2D{10, 10}; + pWindow->m_vRealSize = pWindow->m_vSize - Vector2D{20, 20}; + + const auto PDATA = &m_vWindowData.emplace_back(); + PDATA->pWindow = pWindow; +} + +void CHyprCustomLayout::onWindowRemovedTiling(CWindow* pWindow) { + std::erase_if(m_vWindowData, [&](const auto& other) { return other.pWindow == pWindow; }); +} + +bool CHyprCustomLayout::isWindowTiled(CWindow* pWindow) { + return std::find_if(m_vWindowData.begin(), m_vWindowData.end(), [&](const auto& other) { return other.pWindow == pWindow; }) != m_vWindowData.end(); +} + +void CHyprCustomLayout::recalculateMonitor(const int& eIdleInhibitMode) { + ; // empty +} + +void CHyprCustomLayout::recalculateWindow(CWindow* pWindow) { + ; // empty +} + +void CHyprCustomLayout::resizeActiveWindow(const Vector2D& delta, CWindow* pWindow) { + ; // empty +} + +void CHyprCustomLayout::fullscreenRequestForWindow(CWindow* pWindow, eFullscreenMode mode, bool on) { + ; // empty +} + +std::any CHyprCustomLayout::layoutMessage(SLayoutMessageHeader header, std::string content) { + return ""; +} + +SWindowRenderLayoutHints CHyprCustomLayout::requestRenderHints(CWindow* pWindow) { + return {}; +} + +void CHyprCustomLayout::switchWindows(CWindow* pWindowA, CWindow* pWindowB) { + ; // empty +} + +void CHyprCustomLayout::alterSplitRatio(CWindow* pWindow, float delta, bool exact) { + ; // empty +} + +std::string CHyprCustomLayout::getLayoutName() { + return "custom"; +} + +void CHyprCustomLayout::replaceWindowDataWith(CWindow* from, CWindow* to) { + ; // empty +} + +void CHyprCustomLayout::onEnable() { + for (auto& w : g_pCompositor->m_vWindows) { + if (w->isHidden() || !w->m_bIsMapped || w->m_bFadingOut || w->m_bIsFloating) + continue; + + onWindowCreatedTiling(w.get()); + } +} + +void CHyprCustomLayout::onDisable() { + m_vWindowData.clear(); +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..ef829ae --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,95 @@ +#define WLR_USE_UNSTABLE + +#include "globals.hpp" + +#include +#include +#include "customLayout.hpp" +#include "customDecoration.hpp" + +#include +#include + +// Methods +inline std::unique_ptr g_pCustomLayout; +inline CFunctionHook* g_pFocusHook = nullptr; +inline CFunctionHook* g_pMotionHook = nullptr; +inline CFunctionHook* g_pMouseDownHook = nullptr; +typedef void (*origFocusWindow)(void*, CWindow*, wlr_surface*); +typedef void (*origMotion)(wlr_seat*, uint32_t, double, double); +typedef void (*origMouseDownNormal)(void*, wlr_pointer_button_event*); + +// Do NOT change this function. +APICALL EXPORT std::string PLUGIN_API_VERSION() { + return HYPRLAND_API_VERSION; +} + +static void onActiveWindowChange(void* self, std::any data) { + try { + auto* const PWINDOW = std::any_cast(data); + + HyprlandAPI::addNotification(PHANDLE, "[ExamplePlugin] Active window: " + (PWINDOW ? PWINDOW->m_szTitle : "None"), CColor{0.f, 0.5f, 1.f, 1.f}, 5000); + } catch (std::bad_any_cast& e) { HyprlandAPI::addNotification(PHANDLE, "[ExamplePlugin] Active window: None", CColor{0.f, 0.5f, 1.f, 1.f}, 5000); } +} + +static void onNewWindow(void* self, std::any data) { + auto* const PWINDOW = std::any_cast(data); + + HyprlandAPI::addWindowDecoration(PHANDLE, PWINDOW, new CCustomDecoration(PWINDOW)); +} + +void hkFocusWindow(void* thisptr, CWindow* pWindow, wlr_surface* pSurface) { + // HyprlandAPI::addNotification(PHANDLE, getFormat("FocusWindow with %lx %lx", pWindow, pSurface), CColor{0.f, 1.f, 1.f, 1.f}, 5000); + (*(origFocusWindow)g_pFocusHook->m_pOriginal)(thisptr, pWindow, pSurface); +} + +void hkNotifyMotion(wlr_seat* wlr_seat, uint32_t time_msec, double sx, double sy) { + // HyprlandAPI::addNotification(PHANDLE, getFormat("NotifyMotion with %lf %lf", sx, sy), CColor{0.f, 1.f, 1.f, 1.f}, 5000); + (*(origMotion)g_pMotionHook->m_pOriginal)(wlr_seat, time_msec, sx, sy); +} + +void hkProcessMouseDownNormal(void* thisptr, wlr_pointer_button_event* e) { + // HyprlandAPI::addNotification(PHANDLE, "Mouse down normal!", CColor{0.8f, 0.2f, 0.5f, 1.0f}, 5000); + (*(origMouseDownNormal)g_pMouseDownHook->m_pOriginal)(thisptr, e); +} + +APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { + PHANDLE = handle; + + HyprlandAPI::addNotification(PHANDLE, "Hello World from an example plugin!", CColor{0.f, 1.f, 1.f, 1.f}, 5000); + + HyprlandAPI::registerCallbackDynamic(PHANDLE, "activeWindow", [&](void* self, std::any data) { onActiveWindowChange(self, data); }); + HyprlandAPI::registerCallbackDynamic(PHANDLE, "openWindow", [&](void* self, std::any data) { onNewWindow(self, data); }); + + g_pCustomLayout = std::make_unique(); + + HyprlandAPI::addLayout(PHANDLE, "custom", g_pCustomLayout.get()); + + HyprlandAPI::addConfigValue(PHANDLE, "plugin:example:border_color", SConfigValue{.intValue = configStringToInt("rgb(44ee44)")}); + + HyprlandAPI::addDispatcher(PHANDLE, "example", [](std::string arg) { HyprlandAPI::addNotification(PHANDLE, "Arg passed: " + arg, CColor{0.5f, 0.5f, 0.7f, 1.0f}, 5000); }); + + // Hook a public member + g_pFocusHook = HyprlandAPI::createFunctionHook(PHANDLE, (void*)&CCompositor::focusWindow, (void*)&hkFocusWindow); + // Hook a public non-member + g_pMotionHook = HyprlandAPI::createFunctionHook(PHANDLE, (void*)&wlr_seat_pointer_notify_motion, (void*)&hkNotifyMotion); + // Hook a private member + static const auto METHODS = HyprlandAPI::findFunctionsByName(PHANDLE, "processMouseDownNormal"); + g_pMouseDownHook = HyprlandAPI::createFunctionHook(PHANDLE, METHODS[0].address, (void*)&hkProcessMouseDownNormal); + + // fancy notifications + HyprlandAPI::addNotificationV2(PHANDLE, {{"text", "Example hint"}, {"time", (uint64_t)10000}, {"color", CColor(0.2, 0.2, 0.9, 1.0)}, {"icon", ICON_HINT}}); + + // Enable our hooks + g_pFocusHook->hook(); + g_pMotionHook->hook(); + g_pMouseDownHook->hook(); + + HyprlandAPI::reloadConfig(); + + return {"ExamplePlugin", "An example plugin", "Vaxry", "1.0"}; +} + +APICALL EXPORT void PLUGIN_EXIT() { + HyprlandAPI::invokeHyprctlCommand("seterror", "disable"); +} \ No newline at end of file