commit 2d883200bc4263672af3846ca01cefc540841bf3 Author: ItsDrike Date: Fri Mar 31 13:48:22 2023 +0200 Initial commit diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..26b3ece --- /dev/null +++ b/.clang-format @@ -0,0 +1,14 @@ +BasedOnStyle: LLVM +BreakBeforeBraces: Stroustrup +TabWidth: 4 +IndentWidth: 4 +UseTab: Never +AllowShortIfStatementsOnASingleLine: false +IndentCaseLabels: true +ColumnLimit: 200 +PointerAlignment: Left +AllowShortBlocksOnASingleLine: Empty +AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..67a882e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.so +compile_flags.txt diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6d0f2c6 --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +# 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. + +PLUGIN_NAME=dwindle-autogroup + +SOURCE_FILES=$(wildcard src/*.cpp) + +.PHONY: clean clangd + +all: check_env $(PLUGIN_NAME).so + +install: all + cp $(PLUGIN_NAME).so ${HOME}/.local/share/hyprload/plugins/bin + +check_env: +ifndef HYPRLAND_HEADERS + $(error HYPRLAND_HEADERS is undefined! Please set it to the path to the root of the configured Hyprland repo) +endif + +$(PLUGIN_NAME).so: $(SOURCE_FILES) $(INCLUDE_FILES) + g++ -shared -fPIC --no-gnu-unique $(SOURCE_FILES) -o $(PLUGIN_NAME).so -g -I "/usr/include/pixman-1" -I "/usr/include/libdrm" -I "${HYPRLAND_HEADERS}" -Iinclude -std=c++23 + +clean: + rm ./${PLUGIN_NAME}.so + +clangd: + printf "%b" "-I/usr/include/pixman-1\n-I/usr/include/libdrm\n-I${HYPRLAND_HEADERS}\n-Iinclude\n-std=c++2b" > compile_flags.txt diff --git a/README.md b/README.md new file mode 100644 index 0000000..c072a7b --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# Dwindle-Autogroup + +This plugin changes the behavior of the `togglegroup` dispatcher for dwindle layout, to automatically group all of the child windows when a new group is created. + +Before Hyprland `v0.23.0beta`, this was actually the default behavior, however as that release introduced group support for other layouts, including floating windows, this dwindle specific feature was removed and `togglegroup` now only creates a group window, and requires you to move in all of the windows that should be a part of that group into it manually. + +## Installation + +Since Hyprland plugins don't have ABI guarantees, you should download the Hyprland source and compile it if you plan to use plugins. This ensures the compiler version is the same between the Hyprland build you're running, and the plugins you are using. + +The guide on compiling and installing Hyprland manually is on the [wiki](http://wiki.hyprland.org/Getting-Started/Installation/#manual-manual-build) + +## Using [hyprload](https://github.com/Duckonaut/hyprload) +1. Export the `HYPRLAND_HEADERS` variable to point to the root directory of the Hyprland repo + - `export HYPRLAND_HEADERS="$HOME/repos/Hyprland"` +2. Install + - `make install` + +## Manual installation +1. Export the `HYPRLAND_HEADERS` variable to point to the root directory of the Hyprland repo + - `export HYPRLAND_HEADERS="$HOME/repos/Hyprland"` +2. Compile + - `make all` +3. Add this line to the bottom of your hyprland config + - `exec-once=hyprctl plugin load ` + + +## Development + +When developing, it is useful to run `make clangd`, to generate `compile_flags.txt` file, allowing Clang language server to properly recognize the imports, and give you autocompletion. + +## Disclaimer + +I'm very new to C++ development, and I'm also not very familiar with Hyprland's codebase. So while I will do my best to follow best practices, with so little experience, you can be pretty much certain that there will be bugs, and that the code will not be pretty. But hey, if you know about something that I did wrong, feel free to PR/make an issue about it. diff --git a/hyprload.toml b/hyprload.toml new file mode 100644 index 0000000..81e7a1b --- /dev/null +++ b/hyprload.toml @@ -0,0 +1,10 @@ +[dwindle-autogroup] +description = "Dwindle Autogroup" +version = "1.0.0" +author = "ItsDrike" + +[diwndle-autogroup.build] +output = "dwindle-autogroup.so" +steps = [ + "make all", +] diff --git a/include/customDecoration.hpp b/include/customDecoration.hpp new file mode 100644 index 0000000..7817704 --- /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..d66bfb4 --- /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..3b61032 --- /dev/null +++ b/include/globals.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include + +inline HANDLE PHANDLE = nullptr; diff --git a/src/customDecoration.cpp b/src/customDecoration.cpp new file mode 100644 index 0000000..67e130a --- /dev/null +++ b/src/customDecoration.cpp @@ -0,0 +1,80 @@ +#include "customDecoration.hpp" +#include "globals.hpp" +#include +#include + +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..0105007 --- /dev/null +++ b/src/customLayout.cpp @@ -0,0 +1,96 @@ +#include "customLayout.hpp" +#include "globals.hpp" +#include + +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..c4102b3 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,112 @@ +#define WLR_USE_UNSTABLE + +#include "customDecoration.hpp" +#include "customLayout.hpp" +#include "globals.hpp" + +#include +#include + +#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 (!WARNING: the signature may differ in clang. This + // one is for gcc ONLY.) + g_pMouseDownHook = HyprlandAPI::createFunctionHook(PHANDLE, + HyprlandAPI::getFunctionAddressFromSignature(PHANDLE, "_ZN13CInputManager22processMouseDownNormalEP24wlr_pointer_" + "button_event"), + (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"); +}