aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorVaxry <[email protected]>2023-02-27 12:32:38 +0000
committerGitHub <[email protected]>2023-02-27 12:32:38 +0000
commit8b81f41e52b55835aaaa7e4962af23a00188cbdc (patch)
treebca65923843ea931c0e0222f29d4e2901cfa8637
parent74a10f26a469de54968e584525f6507928f615f0 (diff)
downloadHyprland-8b81f41e52b55835aaaa7e4962af23a00188cbdc.tar.gz
Hyprland-8b81f41e52b55835aaaa7e4962af23a00188cbdc.zip
Plugin System (#1590)
--------- Co-authored-by: Mihai Fufezan <[email protected]>
-rw-r--r--.github/workflows/ci.yaml4
-rw-r--r--.gitignore1
-rw-r--r--.gitmodules3
-rw-r--r--CMakeLists.txt4
-rw-r--r--Makefile13
-rw-r--r--example/examplePlugin/Makefile8
-rw-r--r--example/examplePlugin/customDecoration.cpp74
-rw-r--r--example/examplePlugin/customDecoration.hpp29
-rw-r--r--example/examplePlugin/customLayout.cpp80
-rw-r--r--example/examplePlugin/customLayout.hpp32
-rw-r--r--example/examplePlugin/globals.hpp5
-rw-r--r--example/examplePlugin/main.cpp92
-rw-r--r--flake.nix3
-rw-r--r--hyprctl/main.cpp3
-rw-r--r--meson.build4
-rw-r--r--nix/default.nix2
-rw-r--r--nix/meson-build.patch44
-rw-r--r--nix/udis86.nix32
-rw-r--r--src/Compositor.cpp15
-rw-r--r--src/Compositor.hpp2
-rw-r--r--src/config/ConfigManager.cpp41
-rw-r--r--src/config/ConfigManager.hpp43
-rw-r--r--src/debug/CrashReporter.cpp13
-rw-r--r--src/debug/HyprCtl.cpp50
-rw-r--r--src/debug/HyprCtl.hpp6
-rw-r--r--src/debug/HyprNotificationOverlay.cpp155
-rw-r--r--src/debug/HyprNotificationOverlay.hpp40
-rw-r--r--src/defines.hpp2
-rw-r--r--src/events/Monitors.cpp13
-rw-r--r--src/helpers/Color.hpp4
-rw-r--r--src/helpers/Monitor.hpp3
-rw-r--r--src/managers/HookSystemManager.cpp57
-rw-r--r--src/managers/HookSystemManager.hpp27
-rw-r--r--src/managers/KeybindManager.hpp1
-rw-r--r--src/managers/LayoutManager.cpp58
-rw-r--r--src/managers/LayoutManager.hpp16
-rw-r--r--src/meson.build1
-rw-r--r--src/plugins/HookSystem.cpp151
-rw-r--r--src/plugins/HookSystem.hpp48
-rw-r--r--src/plugins/PluginAPI.cpp193
-rw-r--r--src/plugins/PluginAPI.hpp217
-rw-r--r--src/plugins/PluginSystem.cpp139
-rw-r--r--src/plugins/PluginSystem.hpp44
-rw-r--r--src/render/decorations/IHyprWindowDecoration.hpp3
m---------subprojects/udis860
45 files changed, 1690 insertions, 85 deletions
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 68154929..d5b9d368 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -12,7 +12,7 @@ jobs:
run: |
sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf
pacman --noconfirm --noprogressbar -Syyu
- pacman --noconfirm --noprogressbar -Sy glslang libepoxy libfontenc libxcvt libxfont2 libxkbfile vulkan-headers vulkan-validation-layers xcb-util-errors xcb-util-renderutil xcb-util-wm xorg-fonts-encodings xorg-server-common xorg-setxkbmap xorg-xkbcomp xorg-xwayland git cmake go clang lld libc++ pkgconf meson ninja wayland wayland-protocols libinput libxkbcommon pixman glm libdrm libglvnd cairo pango systemd scdoc base-devel seatd
+ pacman --noconfirm --noprogressbar -Sy glslang libepoxy libfontenc libxcvt libxfont2 libxkbfile vulkan-headers vulkan-validation-layers xcb-util-errors xcb-util-renderutil xcb-util-wm xorg-fonts-encodings xorg-server-common xorg-setxkbmap xorg-xkbcomp xorg-xwayland git cmake go clang lld libc++ pkgconf meson ninja wayland wayland-protocols libinput libxkbcommon pixman glm libdrm libglvnd cairo pango systemd scdoc base-devel seatd python
- name: Set up user
run: |
useradd -m githubuser
@@ -62,7 +62,7 @@ jobs:
run: |
sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf
pacman --noconfirm --noprogressbar -Syyu
- pacman --noconfirm --noprogressbar -Sy glslang libepoxy libfontenc libxcvt libxfont2 libxkbfile vulkan-headers vulkan-validation-layers git go clang lld libc++ pkgconf meson ninja wayland wayland-protocols libinput libxkbcommon pixman glm libdrm libglvnd cairo pango systemd scdoc base-devel seatd cmake jq
+ pacman --noconfirm --noprogressbar -Sy glslang libepoxy libfontenc libxcvt libxfont2 libxkbfile vulkan-headers vulkan-validation-layers git go clang lld libc++ pkgconf meson ninja wayland wayland-protocols libinput libxkbcommon pixman glm libdrm libglvnd cairo pango systemd scdoc base-devel seatd cmake jq python
- name: Checkout Hyprland
uses: actions/checkout@v3
with:
diff --git a/.gitignore b/.gitignore
index 95a1b6f0..96fd6c87 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,6 +20,7 @@ result*
*-protocol.c
*-protocol.h
.ccls-cache
+*.so
hyprctl/hyprctl
diff --git a/.gitmodules b/.gitmodules
index ed443f60..26dbceeb 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,3 +4,6 @@
[submodule "subprojects/hyprland-protocols"]
path = subprojects/hyprland-protocols
url = https://github.com/hyprwm/hyprland-protocols
+[submodule "subprojects/udis86"]
+ path = subprojects/udis86
+ url = https://github.com/canihavesomecoffee/udis86
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6eec94b0..a5ea06a6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -52,9 +52,10 @@ ENDIF(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
include_directories(. PRIVATE "subprojects/wlroots/include/")
include_directories(. PRIVATE "subprojects/wlroots/build/include/")
+include_directories(. PRIVATE "subprojects/udis86/")
set(CMAKE_CXX_STANDARD 23)
add_compile_options(-DWLR_USE_UNSTABLE)
-add_compile_options(-Wall -Wextra -Wno-unused-parameter -Wno-unused-value -Wno-missing-field-initializers -Wno-narrowing)
+add_compile_options(-Wall -Wextra -Wno-unused-parameter -Wno-unused-value -Wno-missing-field-initializers -Wno-narrowing -Wno-pointer-arith)
ADD_LINK_OPTIONS( -rdynamic )
SET(CMAKE_ENABLE_EXPORTS TRUE)
@@ -139,4 +140,5 @@ target_link_libraries(Hyprland
${CMAKE_SOURCE_DIR}/wlr-foreign-toplevel-management-unstable-v1-protocol.o
${CMAKE_SOURCE_DIR}/hyprland-toplevel-export-v1-protocol.o
${CMAKE_SOURCE_DIR}/fractional-scale-v1-protocol.o
+ ${CMAKE_SOURCE_DIR}/subprojects/udis86/build/libudis86/liblibudis86.a
)
diff --git a/Makefile b/Makefile
index 47a27d00..6b375912 100644
--- a/Makefile
+++ b/Makefile
@@ -157,6 +157,7 @@ all:
make clear
make fixwlr
cd ./subprojects/wlroots && meson build/ --buildtype=release && ninja -C build/ && cp ./build/libwlroots.so.12032 /usr/lib/ && cd ../..
+ cd subprojects/udis86 && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -H./ -B./build -G Ninja && cmake --build ./build --config Release --target all -j$(shell nproc)
make protocols
make release
cd hyprctl && make all && cd ..
@@ -211,6 +212,18 @@ config:
cd subprojects/wlroots && ninja -C build/ install
+ cd subprojects/udis86 && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -H./ -B./build -G Ninja && cmake --build ./build --config Release --target all -j$(shell nproc)
+
+pluginenv:
+ make protocols
+
+ cd subprojects/udis86 && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -H./ -B./build -G Ninja && cmake --build ./build --config Release --target all -j$(shell nproc)
+
+ make fixwlr
+
+ cd subprojects/wlroots && meson ./build --prefix=/usr --buildtype=release -Dwerror=false -Dexamples=false
+ cd subprojects/wlroots && ninja -C build/
+
configdebug:
make protocols
diff --git a/example/examplePlugin/Makefile b/example/examplePlugin/Makefile
new file mode 100644
index 00000000..3ccc2930
--- /dev/null
+++ b/example/examplePlugin/Makefile
@@ -0,0 +1,8 @@
+# compile with HYPRLAND_HEADERS=<path_to_hl> 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.
+
+all:
+ g++ -shared -fPIC --no-gnu-unique main.cpp customLayout.cpp customDecoration.cpp -o examplePlugin.so -g -I "/usr/include/pixman-1" -I "/usr/include/libdrm" -I "${HYPRLAND_HEADERS}" -std=c++23
+clean:
+ rm ./examplePlugin.so
diff --git a/example/examplePlugin/customDecoration.cpp b/example/examplePlugin/customDecoration.cpp
new file mode 100644
index 00000000..d336d4e8
--- /dev/null
+++ b/example/examplePlugin/customDecoration.cpp
@@ -0,0 +1,74 @@
+#include "customDecoration.hpp"
+#include "../../src/Window.hpp"
+#include "../../src/Compositor.hpp"
+#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);
+} \ No newline at end of file
diff --git a/example/examplePlugin/customDecoration.hpp b/example/examplePlugin/customDecoration.hpp
new file mode 100644
index 00000000..a08b48bf
--- /dev/null
+++ b/example/examplePlugin/customDecoration.hpp
@@ -0,0 +1,29 @@
+#pragma once
+
+#define WLR_USE_UNSTABLE
+
+#include "../../src/render/decorations/IHyprWindowDecoration.hpp"
+
+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;
+}; \ No newline at end of file
diff --git a/example/examplePlugin/customLayout.cpp b/example/examplePlugin/customLayout.cpp
new file mode 100644
index 00000000..fcc51394
--- /dev/null
+++ b/example/examplePlugin/customLayout.cpp
@@ -0,0 +1,80 @@
+#include "customLayout.hpp"
+#include "../../src/Compositor.hpp"
+#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();
+} \ No newline at end of file
diff --git a/example/examplePlugin/customLayout.hpp b/example/examplePlugin/customLayout.hpp
new file mode 100644
index 00000000..974882c2
--- /dev/null
+++ b/example/examplePlugin/customLayout.hpp
@@ -0,0 +1,32 @@
+#pragma once
+
+#define WLR_USE_UNSTABLE
+
+#include "../../src/layout/IHyprLayout.hpp"
+
+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<SWindowData> m_vWindowData;
+}; \ No newline at end of file
diff --git a/example/examplePlugin/globals.hpp b/example/examplePlugin/globals.hpp
new file mode 100644
index 00000000..37e8363b
--- /dev/null
+++ b/example/examplePlugin/globals.hpp
@@ -0,0 +1,5 @@
+#pragma once
+
+#include <src/plugins/PluginAPI.hpp>
+
+inline HANDLE PHANDLE = nullptr; \ No newline at end of file
diff --git a/example/examplePlugin/main.cpp b/example/examplePlugin/main.cpp
new file mode 100644
index 00000000..49364f59
--- /dev/null
+++ b/example/examplePlugin/main.cpp
@@ -0,0 +1,92 @@
+#define WLR_USE_UNSTABLE
+
+#include "globals.hpp"
+
+#include <src/Window.hpp>
+#include <src/Compositor.hpp>
+#include "customLayout.hpp"
+#include "customDecoration.hpp"
+
+#include <unistd.h>
+#include <thread>
+
+// Methods
+inline std::unique_ptr<CHyprCustomLayout> 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<CWindow*>(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<CWindow*>(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<CHyprCustomLayout>();
+
+ 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);
+
+ // 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
diff --git a/flake.nix b/flake.nix
index 3f2b6940..973a6fb9 100644
--- a/flake.nix
+++ b/flake.nix
@@ -52,11 +52,14 @@
version = props.version + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty");
wlroots = wlroots-hyprland;
inherit (inputs.hyprland-protocols.packages.${prev.stdenv.hostPlatform.system}) hyprland-protocols;
+ inherit udis86;
};
hyprland-debug = hyprland.override {debug = true;};
hyprland-no-hidpi = hyprland.override {hidpiXWayland = false;};
hyprland-nvidia = hyprland.override {nvidiaPatches = true;};
+ udis86 = prev.callPackage ./nix/udis86.nix {};
+
waybar-hyprland = prev.waybar.overrideAttrs (oldAttrs: {
postPatch = ''
# use hyprctl to switch workspaces
diff --git a/hyprctl/main.cpp b/hyprctl/main.cpp
index 81343526..a5bfda65 100644
--- a/hyprctl/main.cpp
+++ b/hyprctl/main.cpp
@@ -41,6 +41,7 @@ commands:
switchxkblayout
seterror
setprop
+ plugin
flags:
-j -> output in JSON
@@ -349,6 +350,8 @@ int main(int argc, char** argv) {
request(fullRequest, 1);
else if (fullRequest.contains("/setprop"))
request(fullRequest, 3);
+ else if (fullRequest.contains("/plugin"))
+ request(fullRequest, 1);
else if (fullRequest.contains("/output"))
exitStatus = outputRequest(argc, argv);
else if (fullRequest.contains("/setcursor"))
diff --git a/meson.build b/meson.build
index 0ae3d518..f3802553 100644
--- a/meson.build
+++ b/meson.build
@@ -43,6 +43,10 @@ wlroots = subproject('wlroots', default_options: ['examples=false'])
have_xwlr = wlroots.get_variable('features').get('xwayland')
xcb_dep = dependency('xcb', required: get_option('xwayland'))
+cmake = import('cmake')
+udis = cmake.subproject('udis86')
+udis86 = udis.dependency('libudis86')
+
if get_option('xwayland').enabled() and not have_xwlr
error('Cannot enable Xwayland in Hyprland: wlroots has been built without Xwayland support')
endif
diff --git a/nix/default.nix b/nix/default.nix
index 89037960..78e103d4 100644
--- a/nix/default.nix
+++ b/nix/default.nix
@@ -18,6 +18,7 @@
mount,
pciutils,
systemd,
+ udis86,
wayland,
wayland-protocols,
wayland-scanner,
@@ -72,6 +73,7 @@ in
libinput
libxkbcommon
mesa
+ udis86
wayland
wayland-protocols
wayland-scanner
diff --git a/nix/meson-build.patch b/nix/meson-build.patch
index 53f11969..075cb6be 100644
--- a/nix/meson-build.patch
+++ b/nix/meson-build.patch
@@ -1,34 +1,50 @@
diff --git a/meson.build b/meson.build
-index 22ee4bf..5528613 100644
+index f380255..abd7cd3 100644
--- a/meson.build
+++ b/meson.build
-@@ -2,16 +2,10 @@ project('Hyprland', 'cpp', 'c',
- version : '0.1',
- default_options : ['warning_level=3', 'cpp_std=c++20', 'default_library=static'])
-
+@@ -39,23 +39,8 @@ add_project_arguments(
+ ],
+ language: 'cpp')
+
-wlroots = subproject('wlroots', default_options: ['examples=false'])
-have_xwlr = wlroots.get_variable('features').get('xwayland')
-+wlroots = dependency('wlroots', version: '>=0.16.0')
xcb_dep = dependency('xcb', required: get_option('xwayland'))
-
+
+-cmake = import('cmake')
+-udis = cmake.subproject('udis86')
+-udis86 = udis.dependency('libudis86')
+-
-if get_option('xwayland').enabled() and not have_xwlr
- error('Cannot enable Xwayland in Hyprland: wlroots has been built without Xwayland support')
-endif
-have_xwayland = xcb_dep.found() and have_xwlr
-
-if not have_xwayland
-+if not xcb_dep.found()
- add_project_arguments('-DNO_XWAYLAND', language: 'cpp')
- endif
-
+-add_project_arguments('-DNO_XWAYLAND', language: 'cpp')
+-endif
+-
+ backtrace_dep = cpp_compiler.find_library('execinfo', required: false)
+ systemd_dep = dependency('libsystemd', required: get_option('systemd'))
+
diff --git a/src/meson.build b/src/meson.build
-index 5d64188..a676333 100644
+index 7b658d3..da8baa5 100644
--- a/src/meson.build
+++ b/src/meson.build
-@@ -7,5 +7,5 @@ executable('Hyprland', src,
+@@ -7,7 +7,7 @@ executable('Hyprland', src,
server_protos,
dependency('wayland-server'),
dependency('wayland-client'),
- wlroots.get_variable('wlroots'),
-+ wlroots,
++ dependency('wlroots'),
dependency('cairo'),
+ dependency('libdrm'),
+ dependency('egl'),
+@@ -16,7 +16,7 @@ executable('Hyprland', src,
+ xcb_dep,
+ backtrace_dep,
+ systemd_dep,
+- udis86,
++ dependency('udis86'),
+
+ dependency('pixman-1'),
+ dependency('gl', 'opengl'),
diff --git a/nix/udis86.nix b/nix/udis86.nix
new file mode 100644
index 00000000..d5e92afc
--- /dev/null
+++ b/nix/udis86.nix
@@ -0,0 +1,32 @@
+{
+ lib,
+ stdenv,
+ fetchFromGitHub,
+ autoreconfHook,
+ python3,
+}:
+stdenv.mkDerivation {
+ pname = "udis86";
+ version = "unstable-2022-10-13";
+
+ src = fetchFromGitHub {
+ owner = "canihavesomecoffee";
+ repo = "udis86";
+ rev = "5336633af70f3917760a6d441ff02d93477b0c86";
+ hash = "sha256-HifdUQPGsKQKQprByeIznvRLONdOXeolOsU5nkwIv3g=";
+ };
+
+ nativeBuildInputs = [autoreconfHook python3];
+
+ configureFlags = ["--enable-shared"];
+
+ outputs = ["bin" "out" "dev" "lib"];
+
+ meta = with lib; {
+ homepage = "https://udis86.sourceforge.net";
+ license = licenses.bsd2;
+ mainProgram = "udcli";
+ description = "Easy-to-use, minimalistic x86 disassembler library (libudis86)";
+ platforms = platforms.all;
+ };
+}
diff --git a/src/Compositor.cpp b/src/Compositor.cpp
index 8ab741da..a7d48a15 100644
--- a/src/Compositor.cpp
+++ b/src/Compositor.cpp
@@ -18,6 +18,12 @@ int handleCritSignal(int signo, void* data) {
}
void handleSegv(int sig) {
+
+ if (g_pHookSystem->m_bCurrentEventPlugin) {
+ longjmp(g_pHookSystem->m_jbHookFaultJumpBuf, 1);
+ return;
+ }
+
CrashReporter::createAndSaveCrash();
abort();
}
@@ -364,6 +370,12 @@ void CCompositor::startCompositor() {
Debug::log(LOG, "Creating the HyprDebugOverlay!");
g_pDebugOverlay = std::make_unique<CHyprDebugOverlay>();
+
+ Debug::log(LOG, "Creating the HyprNotificationOverlay!");
+ g_pHyprNotificationOverlay = std::make_unique<CHyprNotificationOverlay>();
+
+ Debug::log(LOG, "Creating the PluginSystem!");
+ g_pPluginSystem = std::make_unique<CPluginSystem>();
//
//
@@ -2012,6 +2024,9 @@ void CCompositor::scheduleFrameForMonitor(CMonitor* pMonitor) {
if (!pMonitor->m_bEnabled)
return;
+ if (pMonitor->renderingActive)
+ pMonitor->pendingFrame = true;
+
wlr_output_schedule_frame(pMonitor->output);
}
diff --git a/src/Compositor.hpp b/src/Compositor.hpp
index 8008acc3..534f2487 100644
--- a/src/Compositor.hpp
+++ b/src/Compositor.hpp
@@ -19,12 +19,14 @@
#include "managers/SessionLockManager.hpp"
#include "managers/HookSystemManager.hpp"
#include "debug/HyprDebugOverlay.hpp"
+#include "debug/HyprNotificationOverlay.hpp"
#include "helpers/Monitor.hpp"
#include "helpers/Workspace.hpp"
#include "Window.hpp"
#include "render/Renderer.hpp"
#include "render/OpenGL.hpp"
#include "hyprerror/HyprError.hpp"
+#include "plugins/PluginSystem.hpp"
class CCompositor {
public:
diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp
index c2d8a3ca..0931a6f8 100644
--- a/src/config/ConfigManager.cpp
+++ b/src/config/ConfigManager.cpp
@@ -271,7 +271,7 @@ void CConfigManager::init() {
void CConfigManager::configSetValueSafe(const std::string& COMMAND, const std::string& VALUE) {
if (configValues.find(COMMAND) == configValues.end()) {
- if (COMMAND.find("device:") != 0 /* devices parsed later */) {
+ if (COMMAND.find("device:") != 0 /* devices parsed later */ && COMMAND.find("plugin:") != 0 /* plugins parsed later */) {
if (COMMAND[0] == '$') {
// register a dynamic var
Debug::log(LOG, "Registered dynamic var \"%s\" -> %s", COMMAND.c_str(), VALUE.c_str());
@@ -306,6 +306,18 @@ void CConfigManager::configSetValueSafe(const std::string& COMMAND, const std::s
}
CONFIGENTRY = &it->second.at(CONFIGVAR);
+ } else if (COMMAND.find("plugin:") == 0) {
+ for (auto& [handle, pMap] : pluginConfigs) {
+ auto it = std::find_if(pMap->begin(), pMap->end(), [&](const auto& other) { return other.first == COMMAND; });
+ if (it == pMap->end()) {
+ return; // plugin vars do not err, so we silently ignore.
+ }
+
+ CONFIGENTRY = &it->second;
+ }
+
+ if (!CONFIGENTRY)
+ return; // silent ignore
} else {
CONFIGENTRY = &configValues.at(COMMAND);
}
@@ -1649,8 +1661,17 @@ SConfigValue* CConfigManager::getConfigValuePtr(const std::string& val) {
SConfigValue* CConfigManager::getConfigValuePtrSafe(const std::string& val) {
const auto IT = configValues.find(val);
- if (IT == configValues.end())
+ if (IT == configValues.end()) {
+ // maybe plugin
+ for (auto& [pl, pMap] : pluginConfigs) {
+ const auto PLIT = pMap->find(val);
+
+ if (PLIT != pMap->end())
+ return &PLIT->second;
+ }
+
return nullptr;
+ }
return &(IT->second);
}
@@ -1800,3 +1821,19 @@ ICustomConfigValueData::~ICustomConfigValueData() {
std::unordered_map<std::string, SAnimationPropertyConfig> CConfigManager::getAnimationConfig() {
return animationConfig;
}
+
+void CConfigManager::addPluginConfigVar(HANDLE handle, const std::string& name, const SConfigValue& value) {
+ auto CONFIGMAPIT = std::find_if(pluginConfigs.begin(), pluginConfigs.end(), [&](const auto& other) { return other.first == handle; });
+
+ if (CONFIGMAPIT == pluginConfigs.end()) {
+ pluginConfigs.emplace(
+ std::pair<HANDLE, std::unique_ptr<std::unordered_map<std::string, SConfigValue>>>(handle, std::make_unique<std::unordered_map<std::string, SConfigValue>>()));
+ CONFIGMAPIT = std::find_if(pluginConfigs.begin(), pluginConfigs.end(), [&](const auto& other) { return other.first == handle; });
+ }
+
+ (*CONFIGMAPIT->second)[name] = value;
+}
+
+void CConfigManager::removePluginConfig(HANDLE handle) {
+ std::erase_if(pluginConfigs, [&](const auto& other) { return other.first == handle; });
+} \ No newline at end of file
diff --git a/src/config/ConfigManager.hpp b/src/config/ConfigManager.hpp
index e246d506..742f9fe8 100644
--- a/src/config/ConfigManager.hpp
+++ b/src/config/ConfigManager.hpp
@@ -21,6 +21,8 @@
#define INITANIMCFG(name) animationConfig[name] = {}
#define CREATEANIMCFG(name, parent) animationConfig[name] = {false, "", "", 0.f, -1, &animationConfig["global"], &animationConfig[parent]}
+#define HANDLE void*
+
struct SConfigValue {
int64_t intValue = -INT64_MAX;
float floatValue = -__FLT_MAX__;
@@ -159,6 +161,9 @@ class CConfigManager {
std::unordered_map<std::string, SAnimationPropertyConfig> getAnimationConfig();
+ void addPluginConfigVar(HANDLE handle, const std::string& name, const SConfigValue& value);
+ void removePluginConfig(HANDLE handle);
+
// no-op when done.
void dispatchExecOnce();
@@ -180,33 +185,35 @@ class CConfigManager {
std::string configCurrentPath;
private:
- std::deque<std::string> configPaths; // stores all the config paths
- std::unordered_map<std::string, time_t> configModifyTimes; // stores modify times
- std::unordered_map<std::string, std::string> configDynamicVars; // stores dynamic vars declared by the user
- std::unordered_map<std::string, SConfigValue> configValues;
- std::unordered_map<std::string, std::unordered_map<std::string, SConfigValue>> deviceConfigs; // stores device configs
+ std::deque<std::string> configPaths; // stores all the config paths
+ std::unordered_map<std::string, time_t> configModifyTimes; // stores modify times
+ std::unordered_map<std::string, std::string> configDynamicVars; // stores dynamic vars declared by the user
+ std::unordered_map<std::string, SConfigValue> configValues;
+ std::unordered_map<std::string, std::unordered_map<std::string, SConfigValue>> deviceConfigs; // stores device configs
+
+ std::unordered_map<std::string, SAnimationPropertyConfig> animationConfig; // stores all the animations with their set values
- std::unordered_map<std::string, SAnimationPropertyConfig> animationConfig; // stores all the animations with their set values
+ std::string currentCategory = ""; // For storing the category of the current item
- std::string currentCategory = ""; // For storing the category of the current item
+ std::string parseError = ""; // For storing a parse error to display later
- std::string parseError = ""; // For storing a parse error to display later
+ std::string m_szCurrentSubmap = ""; // For storing the current keybind submap
- std::string m_szCurrentSubmap = ""; // For storing the current keybind submap
+ std::vector<std::pair<std::string, std::string>> boundWorkspaces;
- std::vector<std::pair<std::string, std::string>> boundWorkspaces;
+ std::vector<SExecRequestedRule> execRequestedRules; // rules requested with exec, e.g. [workspace 2] kitty
- std::vector<SExecRequestedRule> execRequestedRules; // rules requested with exec, e.g. [workspace 2] kitty
+ std::unordered_map<HANDLE, std::unique_ptr<std::unordered_map<std::string, SConfigValue>>> pluginConfigs; // stores plugin configs
- bool isFirstLaunch = true; // For exec-once
+ bool isFirstLaunch = true; // For exec-once
- std::deque<SMonitorRule> m_dMonitorRules;
- std::deque<SWindowRule> m_dWindowRules;
- std::deque<SLayerRule> m_dLayerRules;
- std::deque<std::string> m_dBlurLSNamespaces;
+ std::deque<SMonitorRule> m_dMonitorRules;
+ std::deque<SWindowRule> m_dWindowRules;
+ std::deque<SLayerRule> m_dLayerRules;
+ std::deque<std::string> m_dBlurLSNamespaces;
- bool firstExecDispatched = false;
- std::deque<std::string> firstExecRequests;
+ bool firstExecDispatched = false;
+ std::deque<std::string> firstExecRequests;
// internal methods
void setDefaultVars();
diff --git a/src/debug/CrashReporter.cpp b/src/debug/CrashReporter.cpp
index 5738f180..084e44ad 100644
--- a/src/debug/CrashReporter.cpp
+++ b/src/debug/CrashReporter.cpp
@@ -4,10 +4,13 @@
#include <execinfo.h>
#include <fstream>
+#include "../plugins/PluginSystem.hpp"
+
#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__)
#include <sys/sysctl.h>
#endif
+
std::string getRandomMessage() {
const std::vector<std::string> MESSAGES = {"Sorry, didn't mean to...",
@@ -43,6 +46,16 @@ void CrashReporter::createAndSaveCrash() {
finalCrashReport += "Hyprland received signal 11 (SIGSEGV): Segmentation Fault\n\n";
+ if (!g_pPluginSystem->getAllPlugins().empty()) {
+ finalCrashReport += "Hyprland seems to be running with plugins. This crash might not be Hyprland's fault.\nPlugins:\n";
+
+ for (auto& p : g_pPluginSystem->getAllPlugins()) {
+ finalCrashReport += getFormat("\t%s (%s) %s\n", p->name.c_str(), p->author.c_str(), p->version.c_str());
+ }
+
+ finalCrashReport += "\n\n";
+ }
+
finalCrashReport += "System info:\n";
struct utsname unameInfo;
diff --git a/src/debug/HyprCtl.cpp b/src/debug/HyprCtl.cpp
index 91af3938..f6ed285b 100644
--- a/src/debug/HyprCtl.cpp
+++ b/src/debug/HyprCtl.cpp
@@ -1087,6 +1087,50 @@ std::string dispatchOutput(std::string request) {
return "ok";
}
+std::string dispatchPlugin(std::string request) {
+ CVarList vars(request, 0, ' ');
+
+ if (vars.size() < 2)
+ return "not enough args";
+
+ const auto OPERATION = vars[1];
+ const auto PATH = vars[2];
+
+ if (OPERATION == "load") {
+ if (vars.size() < 3)
+ return "not enough args";
+
+ const auto PLUGIN = g_pPluginSystem->loadPlugin(PATH);
+
+ if (!PLUGIN)
+ return "error in loading plugin";
+ } else if (OPERATION == "unload") {
+ if (vars.size() < 3)
+ return "not enough args";
+
+ const auto PLUGIN = g_pPluginSystem->getPluginByPath(PATH);
+
+ if (!PLUGIN)
+ return "plugin not loaded";
+
+ g_pPluginSystem->unloadPlugin(PLUGIN);
+ } else if (OPERATION == "list") {
+ const auto PLUGINS = g_pPluginSystem->getAllPlugins();
+
+ std::string list = "";
+ for (auto& p : PLUGINS) {
+ list += getFormat("\nPlugin %s by %s:\n\tHandle: %lx\n\tVersion: %s\n\tDescription: %s\n", p->name.c_str(), p->author.c_str(), p->m_pHandle, p->version.c_str(),
+ p->description.c_str());
+ }
+
+ return list;
+ } else {
+ return "unknown opt";
+ }
+
+ return "ok";
+}
+
std::string getReply(std::string request) {
auto format = HyprCtl::FORMAT_NORMAL;
@@ -1134,6 +1178,8 @@ std::string getReply(std::string request) {
return bindsRequest(format);
else if (request == "animations")
return animationsRequest(format);
+ else if (request.find("plugin") == 0)
+ return dispatchPlugin(request);
else if (request.find("setprop") == 0)
return dispatchSetProp(request);
else if (request.find("seterror") == 0)
@@ -1156,6 +1202,10 @@ std::string getReply(std::string request) {
return "unknown request";
}
+std::string HyprCtl::makeDynamicCall(const std::string& input) {
+ return getReply(input);
+}
+
int hyprCtlFDTick(int fd, uint32_t mask, void* data) {
if (mask & WL_EVENT_ERROR || mask & WL_EVENT_HANGUP)
return 0;
diff --git a/src/debug/HyprCtl.hpp b/src/debug/HyprCtl.hpp
index 26859692..00cf7c5b 100644
--- a/src/debug/HyprCtl.hpp
+++ b/src/debug/HyprCtl.hpp
@@ -5,7 +5,8 @@
#include "../helpers/MiscFunctions.hpp"
namespace HyprCtl {
- void startHyprCtlSocket();
+ void startHyprCtlSocket();
+ std::string makeDynamicCall(const std::string& input);
// very simple thread-safe request method
inline bool requestMade = false;
@@ -18,8 +19,7 @@ namespace HyprCtl {
inline int iSocketFD = -1;
- enum eHyprCtlOutputFormat
- {
+ enum eHyprCtlOutputFormat {
FORMAT_NORMAL = 0,
FORMAT_JSON
};
diff --git a/src/debug/HyprNotificationOverlay.cpp b/src/debug/HyprNotificationOverlay.cpp
new file mode 100644
index 00000000..94cb6a6c
--- /dev/null
+++ b/src/debug/HyprNotificationOverlay.cpp
@@ -0,0 +1,155 @@
+#include "HyprNotificationOverlay.hpp"
+#include "../Compositor.hpp"
+
+CHyprNotificationOverlay::CHyprNotificationOverlay() {
+ g_pHookSystem->hookDynamic("focusedMon", [&](void* self, std::any param) {
+ if (m_dNotifications.size() == 0)
+ return;
+
+ g_pHyprRenderer->damageBox(&m_bLastDamage);
+ });
+}
+
+void CHyprNotificationOverlay::addNotification(const std::string& text, const CColor& color, const float timeMs) {
+ const auto PNOTIF = m_dNotifications.emplace_back(std::make_unique<SNotification>()).get();
+
+ PNOTIF->text = text;
+ PNOTIF->color = color;
+ PNOTIF->started.reset();
+ PNOTIF->timeMs = timeMs;
+}
+
+wlr_box CHyprNotificationOverlay::drawNotifications(CMonitor* pMonitor) {
+ static constexpr auto ANIM_DURATION_MS = 600.0;
+ static constexpr auto ANIM_LAG_MS = 100.0;
+ static constexpr auto NOTIF_LEFTBAR_SIZE = 5.0;
+
+ float offsetY = 10;
+ float maxWidth = 0;
+
+ const auto SCALE = pMonitor->scale;
+ const auto FONTSIZE = std::clamp((int)(10.f * ((pMonitor->vecPixelSize.x * SCALE) / 1920.f)), 8, 40);
+
+ const auto MONSIZE = pMonitor->vecPixelSize;
+
+ cairo_select_font_face(m_pCairo, "Noto Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
+ cairo_set_font_size(m_pCairo, FONTSIZE);
+
+ cairo_text_extents_t cairoExtents;
+
+ const auto PBEZIER = g_pAnimationManager->getBezier("default");
+
+ for (auto& notif : m_dNotifications) {
+ // first rect (bg, col)
+ const float FIRSTRECTANIMP =
+ (notif->started.getMillis() > (ANIM_DURATION_MS - ANIM_LAG_MS) ?
+ (notif->started.getMillis() > notif->timeMs - (ANIM_DURATION_MS - ANIM_LAG_MS) ? notif->timeMs - notif->started.getMillis() : (ANIM_DURATION_MS - ANIM_LAG_MS)) :
+ notif->started.getMillis()) /
+ (ANIM_DURATION_MS - ANIM_LAG_MS);
+
+ const float FIRSTRECTPERC = FIRSTRECTANIMP >= 0.99f ? 1.f : PBEZIER->getYForPoint(FIRSTRECTANIMP);
+
+ // second rect (fg, black)
+ const float SECONDRECTANIMP = (notif->started.getMillis() > ANIM_DURATION_MS ?
+ (notif->started.getMillis() > notif->timeMs - ANIM_DURATION_MS ? notif->timeMs - notif->started.getMillis() : ANIM_DURATION_MS) :
+ notif->started.getMillis()) /
+ ANIM_DURATION_MS;
+
+ const float SECONDRECTPERC = SECONDRECTANIMP >= 0.99f ? 1.f : PBEZIER->getYForPoint(SECONDRECTANIMP);
+
+ // third rect (horiz, col)
+ const float THIRDRECTPERC = notif->started.getMillis() / notif->timeMs;
+
+ // get text size
+ cairo_text_extents(m_pCairo, notif->text.c_str(), &cairoExtents);
+
+ cairo_set_source_rgba(m_pCairo, notif->color.r, notif->color.g, notif->color.b, notif->color.a);
+
+ const auto NOTIFSIZE = Vector2D{cairoExtents.width + 20, cairoExtents.height + 10};
+
+ // draw rects
+ cairo_rectangle(m_pCairo, MONSIZE.x - (NOTIFSIZE.x + NOTIF_LEFTBAR_SIZE) * FIRSTRECTPERC, offsetY, (NOTIFSIZE.x + NOTIF_LEFTBAR_SIZE) * FIRSTRECTPERC, NOTIFSIZE.y);
+ cairo_fill(m_pCairo);
+
+ cairo_set_source_rgb(m_pCairo, 0.f, 0.f, 0.f);
+
+ cairo_rectangle(m_pCairo, MONSIZE.x - NOTIFSIZE.x * SECONDRECTPERC, offsetY, NOTIFSIZE.x * SECONDRECTPERC, NOTIFSIZE.y);
+ cairo_fill(m_pCairo);
+
+ cairo_set_source_rgba(m_pCairo, notif->color.r, notif->color.g, notif->color.b, notif->color.a);
+
+ cairo_rectangle(m_pCairo, MONSIZE.x - NOTIFSIZE.x * SECONDRECTPERC + 3, offsetY + NOTIFSIZE.y - 4, THIRDRECTPERC * (NOTIFSIZE.x - 6), 2);
+ cairo_fill(m_pCairo);
+
+ // draw text
+ cairo_set_source_rgb(m_pCairo, 1.f, 1.f, 1.f);
+ cairo_move_to(m_pCairo, MONSIZE.x - NOTIFSIZE.x * SECONDRECTPERC + NOTIF_LEFTBAR_SIZE, offsetY + FONTSIZE + (FONTSIZE / 10.0));
+ cairo_show_text(m_pCairo, notif->text.c_str());
+
+ // adjust offset and move on
+ offsetY += NOTIFSIZE.y + 10;
+
+ if (maxWidth < NOTIFSIZE.x)
+ maxWidth = NOTIFSIZE.x;
+ }
+
+ // cleanup notifs
+ std::erase_if(m_dNotifications, [](const auto& notif) { return notif->started.getMillis() > notif->timeMs; });
+
+ return wlr_box{(int)(pMonitor->vecPosition.x + pMonitor->vecSize.x - maxWidth - 20), (int)pMonitor->vecPosition.y, (int)maxWidth + 20, (int)offsetY + 10};
+}
+
+void CHyprNotificationOverlay::draw(CMonitor* pMonitor) {
+
+ if (m_pLastMonitor != pMonitor || !m_pCairo || !m_pCairoSurface) {
+
+ if (m_pCairo && m_pCairoSurface) {
+ cairo_destroy(m_pCairo);
+ cairo_surface_destroy(m_pCairoSurface);
+ }
+
+ m_pCairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, pMonitor->vecPixelSize.x, pMonitor->vecPixelSize.y);
+ m_pCairo = cairo_create(m_pCairoSurface);
+ m_pLastMonitor = pMonitor;
+ }
+
+ // Draw the notifications
+ if (m_dNotifications.size() == 0)
+ return;
+
+ // Render to the monitor
+
+ // clear the pixmap
+ cairo_save(m_pCairo);
+ cairo_set_operator(m_pCairo, CAIRO_OPERATOR_CLEAR);
+ cairo_paint(m_pCairo);
+ cairo_restore(m_pCairo);
+
+ cairo_surface_flush(m_pCairoSurface);
+
+ wlr_box damage = drawNotifications(pMonitor);
+
+ g_pHyprRenderer->damageBox(&damage);
+ g_pHyprRenderer->damageBox(&m_bLastDamage);
+
+ g_pCompositor->scheduleFrameForMonitor(pMonitor);
+
+ m_bLastDamage = damage;
+
+ // copy the data to an OpenGL texture we have
+ const auto DATA = cairo_image_surface_get_data(m_pCairoSurface);
+ m_tTexture.allocate();
+ glBindTexture(GL_TEXTURE_2D, m_tTexture.m_iTexID);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+
+#ifndef GLES2
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
+#endif
+
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pMonitor->vecPixelSize.x, pMonitor->vecPixelSize.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA);
+
+ wlr_box pMonBox = {0, 0, pMonitor->vecPixelSize.x, pMonitor->vecPixelSize.y};
+ g_pHyprOpenGL->renderTexture(m_tTexture, &pMonBox, 1.f);
+} \ No newline at end of file
diff --git a/src/debug/HyprNotificationOverlay.hpp b/src/debug/HyprNotificationOverlay.hpp
new file mode 100644
index 00000000..12ad8e38
--- /dev/null
+++ b/src/debug/HyprNotificationOverlay.hpp
@@ -0,0 +1,40 @@
+#pragma once
+
+#include "../defines.hpp"
+#include "../helpers/Timer.hpp"
+#include "../helpers/Monitor.hpp"
+#include "../render/Texture.hpp"
+
+#include <deque>
+
+#include <cairo/cairo.h>
+
+struct SNotification {
+ std::string text = "";
+ CColor color;
+ CTimer started;
+ float timeMs = 0;
+};
+
+class CHyprNotificationOverlay {
+ public:
+ CHyprNotificationOverlay();
+
+ void draw(CMonitor* pMonitor);
+ void addNotification(const std::string& text, const CColor& color, const float timeMs);
+
+ private:
+ wlr_box drawNotifications(CMonitor* pMonitor);
+ wlr_box m_bLastDamage;
+
+ std::deque<std::unique_ptr<SNotification>> m_dNotifications;
+
+ cairo_surface_t* m_pCairoSurface = nullptr;
+ cairo_t* m_pCairo = nullptr;
+
+ CMonitor* m_pLastMonitor = nullptr;
+
+ CTexture m_tTexture;
+};
+
+inline std::unique_ptr<CHyprNotificationOverlay> g_pHyprNotificationOverlay; \ No newline at end of file
diff --git a/src/defines.hpp b/src/defines.hpp
index cad434fd..b7047552 100644
--- a/src/defines.hpp
+++ b/src/defines.hpp
@@ -89,4 +89,4 @@
#define SPECIAL_WORKSPACE_START (-99)
-#define PI 3.14159265358979
+#define PI 3.14159265358979 \ No newline at end of file
diff --git a/src/events/Monitors.cpp b/src/events/Monitors.cpp
index 799669a1..271b7d81 100644
--- a/src/events/Monitors.cpp
+++ b/src/events/Monitors.cpp
@@ -197,6 +197,8 @@ void Events::listener_monitorFrame(void* owner, void* data) {
return;
}
+ PMONITOR->renderingActive = true;
+
// we need to cleanup fading out when rendering the appropriate context
g_pCompositor->cleanupFadingOut(PMONITOR->ID);
@@ -207,6 +209,8 @@ void Events::listener_monitorFrame(void* owner, void* data) {
if (*PDAMAGEBLINK || *PVFR == 0)
g_pCompositor->scheduleFrameForMonitor(PMONITOR);
+ PMONITOR->renderingActive = false;
+
return;
}
@@ -260,6 +264,9 @@ void Events::listener_monitorFrame(void* owner, void* data) {
if (PMONITOR == g_pCompositor->m_vMonitors.front().get())
g_pHyprError->draw();
+ if (PMONITOR == g_pCompositor->m_pLastMonitor)
+ g_pHyprNotificationOverlay->draw(PMONITOR);
+
// for drawing the debug overlay
if (PMONITOR == g_pCompositor->m_vMonitors.front().get() && *PDEBUGOVERLAY == 1) {
startRenderOverlay = std::chrono::high_resolution_clock::now();
@@ -306,12 +313,16 @@ void Events::listener_monitorFrame(void* owner, void* data) {
pixman_region32_fini(&frameDamage);
pixman_region32_fini(&damage);
+ PMONITOR->renderingActive = false;
+
if (!wlr_output_commit(PMONITOR->output))
return;
- if (*PDAMAGEBLINK || *PVFR == 0)
+ if (*PDAMAGEBLINK || *PVFR == 0 || PMONITOR->pendingFrame)
g_pCompositor->scheduleFrameForMonitor(PMONITOR);
+ PMONITOR->pendingFrame = false;
+
if (*PDEBUGOVERLAY == 1) {
const float µs = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now() - startRender).count() / 1000.f;
g_pDebugOverlay->renderData(PMONITOR, µs);
diff --git a/src/helpers/Color.hpp b/src/helpers/Color.hpp
index 184087ac..ffd6d2d8 100644
--- a/src/helpers/Color.hpp
+++ b/src/helpers/Color.hpp
@@ -1,6 +1,6 @@
#pragma once
-#include "../includes.hpp"
+#include <cstdint>
class CColor {
public:
@@ -13,7 +13,7 @@ class CColor {
uint64_t getAsHex();
CColor operator-(const CColor& c2) const {
- return CColor(r - c2.r, g - c2.g, b - c2.b, a - c2.a);
+ return CColor(r - c2.r, g - c2.g, b - c2.b, a - c2.a);
}
CColor operator+(const CColor& c2) const {
diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp
index 6582cbfc..9ad6d917 100644
--- a/src/helpers/Monitor.hpp
+++ b/src/helpers/Monitor.hpp
@@ -42,6 +42,9 @@ class CMonitor {
bool enabled10bit = false; // as above, this can be TRUE even if 10 bit failed.
bool createdByUser = false;
+ bool pendingFrame = false; // if we schedule a frame during rendering, reschedule it after
+ bool renderingActive = false;
+
// mirroring
CMonitor* pMirrorOf = nullptr;
std::vector<CMonitor*> mirrors;
diff --git a/src/managers/HookSystemManager.cpp b/src/managers/HookSystemManager.cpp
index 02775a24..c5e4cdd8 100644
--- a/src/managers/HookSystemManager.cpp
+++ b/src/managers/HookSystemManager.cpp
@@ -1,44 +1,79 @@
#include "HookSystemManager.hpp"
+#include "../plugins/PluginSystem.hpp"
+
CHookSystemManager::CHookSystemManager() {
; //
}
// returns the pointer to the function
-HOOK_CALLBACK_FN* CHookSystemManager::hookDynamic(const std::string& event, HOOK_CALLBACK_FN fn) {
+HOOK_CALLBACK_FN* CHookSystemManager::hookDynamic(const std::string& event, HOOK_CALLBACK_FN fn, HANDLE handle) {
const auto PVEC = getVecForEvent(event);
const auto PFN = &m_lCallbackFunctions.emplace_back(fn);
- PVEC->emplace_back(PFN);
+ PVEC->emplace_back(SCallbackFNPtr{PFN, handle});
return PFN;
}
-void CHookSystemManager::hookStatic(const std::string& event, HOOK_CALLBACK_FN* fn) {
+void CHookSystemManager::hookStatic(const std::string& event, HOOK_CALLBACK_FN* fn, HANDLE handle) {
const auto PVEC = getVecForEvent(event);
- PVEC->emplace_back(fn);
+ PVEC->emplace_back(SCallbackFNPtr{fn, handle});
}
void CHookSystemManager::unhook(HOOK_CALLBACK_FN* fn) {
std::erase_if(m_lCallbackFunctions, [&](const auto& other) { return &other == fn; });
for (auto& [k, v] : m_lpRegisteredHooks) {
- std::erase_if(v, [&](const auto& other) { return other == fn; });
+ std::erase_if(v, [&](const auto& other) { return other.fn == fn; });
}
}
-void CHookSystemManager::emit(const std::vector<HOOK_CALLBACK_FN*>* callbacks, std::any data) {
+void CHookSystemManager::emit(const std::vector<SCallbackFNPtr>* callbacks, std::any data) {
if (callbacks->empty())
return;
- for (auto& cb : *callbacks)
- (*cb)(cb, data);
+ std::vector<HANDLE> faultyHandles;
+
+ for (auto& cb : *callbacks) {
+
+ m_bCurrentEventPlugin = false;
+
+ if (!cb.handle) {
+ // we don't guard hl hooks
+ (*cb.fn)(cb.fn, data);
+ continue;
+ }
+
+ m_bCurrentEventPlugin = true;
+
+ if (std::find(faultyHandles.begin(), faultyHandles.end(), cb.handle) != faultyHandles.end())
+ continue;
+
+ try {
+ if (!setjmp(m_jbHookFaultJumpBuf))
+ (*cb.fn)(cb.fn, data);
+ else {
+ // this module crashed.
+ throw std::exception();
+ }
+ } catch (std::exception& e) {
+ // TODO: this works only once...?
+ faultyHandles.push_back(cb.handle);
+ Debug::log(ERR, " [hookSystem] Hook from plugin %lx caused a SIGSEGV, queueing for unloading.", cb.handle);
+ }
+ }
+
+ if (!faultyHandles.empty()) {
+ for (auto& h : faultyHandles)
+ g_pPluginSystem->unloadPlugin(g_pPluginSystem->getPluginByHandle(h), true);
+ }
}
-std::vector<HOOK_CALLBACK_FN*>* CHookSystemManager::getVecForEvent(const std::string& event) {
+std::vector<SCallbackFNPtr>* CHookSystemManager::getVecForEvent(const std::string& event) {
auto IT = std::find_if(m_lpRegisteredHooks.begin(), m_lpRegisteredHooks.end(), [&](const auto& other) { return other.first == event; });
if (IT != m_lpRegisteredHooks.end())
return &IT->second;
- Debug::log(LOG, "[hookSystem] New hook event registered: %s", event.c_str());
+ Debug::log(LOG, " [hookSystem] New hook event registered: %s", event.c_str());
- return &m_lpRegisteredHooks.emplace_back(std::make_pair<>(event, std::vector<HOOK_CALLBACK_FN*>{})).second;
+ return &m_lpRegisteredHooks.emplace_back(std::make_pair<>(event, std::vector<SCallbackFNPtr>{})).second;
} \ No newline at end of file
diff --git a/src/managers/HookSystemManager.hpp b/src/managers/HookSystemManager.hpp
index 57f1e58b..66edd1c0 100644
--- a/src/managers/HookSystemManager.hpp
+++ b/src/managers/HookSystemManager.hpp
@@ -4,11 +4,21 @@
#include <unordered_map>
#include <any>
+#include <array>
#include <list>
+#include <csetjmp>
+
+#include "../plugins/PluginAPI.hpp"
+
// global typedef for hooked functions. Passes itself as a ptr when called, and `data` additionally.
typedef std::function<void(void*, std::any)> HOOK_CALLBACK_FN;
+struct SCallbackFNPtr {
+ HOOK_CALLBACK_FN* fn = nullptr;
+ HANDLE handle = nullptr;
+};
+
#define EMIT_HOOK_EVENT(name, param) \
{ \
static auto* const PEVENTVEC = g_pHookSystem->getVecForEvent(name); \
@@ -20,17 +30,20 @@ class CHookSystemManager {
CHookSystemManager();
// returns the pointer to the function
- HOOK_CALLBACK_FN* hookDynamic(const std::string& event, HOOK_CALLBACK_FN fn);
- void hookStatic(const std::string& event, HOOK_CALLBACK_FN* fn);
- void unhook(HOOK_CALLBACK_FN* fn);
+ HOOK_CALLBACK_FN* hookDynamic(const std::string& event, HOOK_CALLBACK_FN fn, HANDLE handle = nullptr);
+ void hookStatic(const std::string& event, HOOK_CALLBACK_FN* fn, HANDLE handle = nullptr);
+ void unhook(HOOK_CALLBACK_FN* fn);
+
+ void emit(const std::vector<SCallbackFNPtr>* callbacks, std::any data = 0);
+ std::vector<SCallbackFNPtr>* getVecForEvent(const std::string& event);
- void emit(const std::vector<HOOK_CALLBACK_FN*>* callbacks, std::any data = 0);
- std::vector<HOOK_CALLBACK_FN*>* getVecForEvent(const std::string& event);
+ bool m_bCurrentEventPlugin = false;
+ jmp_buf m_jbHookFaultJumpBuf;
private:
// todo: this is slow. Maybe static ptrs should be somehow allowed. unique ptr for vec?
- std::list<std::pair<std::string, std::vector<HOOK_CALLBACK_FN*>>> m_lpRegisteredHooks;
- std::list<HOOK_CALLBACK_FN> m_lCallbackFunctions;
+ std::list<std::pair<std::string, std::vector<SCallbackFNPtr>>> m_lpRegisteredHooks;
+ std::list<HOOK_CALLBACK_FN> m_lCallbackFunctions;
};
inline std::unique_ptr<CHookSystemManager> g_pHookSystem; \ No newline at end of file
diff --git a/src/managers/KeybindManager.hpp b/src/managers/KeybindManager.hpp
index 0b8fa4e8..6c773a8c 100644
--- a/src/managers/KeybindManager.hpp
+++ b/src/managers/KeybindManager.hpp
@@ -8,6 +8,7 @@
class CInputManager;
class CConfigManager;
+class CPluginSystem;
struct SKeybind {
std::string key = "";
diff --git a/src/managers/LayoutManager.cpp b/src/managers/LayoutManager.cpp
index 86e45010..c219c25b 100644
--- a/src/managers/LayoutManager.cpp
+++ b/src/managers/LayoutManager.cpp
@@ -1,29 +1,51 @@
#include "LayoutManager.hpp"
-IHyprLayout* CLayoutManager::getCurrentLayout() {
- switch (m_iCurrentLayoutID) {
- case LAYOUT_DWINDLE: return &m_cDwindleLayout;
- case LAYOUT_MASTER: return &m_cMasterLayout;
- }
+CLayoutManager::CLayoutManager() {
+ m_vLayouts.emplace_back(std::make_pair<>("dwindle", &m_cDwindleLayout));
+ m_vLayouts.emplace_back(std::make_pair<>("master", &m_cMasterLayout));
+}
- // fallback
- return &m_cDwindleLayout;
+IHyprLayout* CLayoutManager::getCurrentLayout() {
+ return m_vLayouts[m_iCurrentLayoutID].second;
}
void CLayoutManager::switchToLayout(std::string layout) {
- if (layout == "dwindle") {
- if (m_iCurrentLayoutID != LAYOUT_DWINDLE) {
+ for (size_t i = 0; i < m_vLayouts.size(); ++i) {
+ if (m_vLayouts[i].first == layout) {
getCurrentLayout()->onDisable();
- m_iCurrentLayoutID = LAYOUT_DWINDLE;
+ m_iCurrentLayoutID = i;
getCurrentLayout()->onEnable();
+ return;
}
- } else if (layout == "master") {
- if (m_iCurrentLayoutID != LAYOUT_MASTER) {
- getCurrentLayout()->onDisable();
- m_iCurrentLayoutID = LAYOUT_MASTER;
- getCurrentLayout()->onEnable();
- }
- } else {
- Debug::log(ERR, "Unknown layout %s!", layout.c_str());
}
+
+ Debug::log(ERR, "Unknown layout!");
+}
+
+bool CLayoutManager::addLayout(const std::string& name, IHyprLayout* layout) {
+ if (std::find_if(m_vLayouts.begin(), m_vLayouts.end(), [&](const auto& other) { return other.first == name || other.second == layout; }) != m_vLayouts.end())
+ return false;
+
+ m_vLayouts.emplace_back(std::make_pair<>(name, layout));
+
+ Debug::log(LOG, "Added new layout %s at %lx", name.c_str(), layout);
+
+ return true;
+}
+
+bool CLayoutManager::removeLayout(IHyprLayout* layout) {
+ const auto IT = std::find_if(m_vLayouts.begin(), m_vLayouts.end(), [&](const auto& other) { return other.second == layout; });
+
+ if (IT == m_vLayouts.end() || IT->first == "dwindle" || IT->first == "master")
+ return false;
+
+ if (m_iCurrentLayoutID == IT - m_vLayouts.begin()) {
+ switchToLayout("dwindle");
+ }
+
+ Debug::log(LOG, "Removed a layout %s at %lx", IT->first.c_str(), layout);
+
+ std::erase(m_vLayouts, *IT);
+
+ return true;
}
diff --git a/src/managers/LayoutManager.hpp b/src/managers/LayoutManager.hpp
index b14b989f..1ebe5711 100644
--- a/src/managers/LayoutManager.hpp
+++ b/src/managers/LayoutManager.hpp
@@ -5,20 +5,28 @@
class CLayoutManager {
public:
+ CLayoutManager();
+
IHyprLayout* getCurrentLayout();
void switchToLayout(std::string);
+ bool addLayout(const std::string& name, IHyprLayout* layout);
+ bool removeLayout(IHyprLayout* layout);
+
private:
- enum HYPRLAYOUTS {
+ enum HYPRLAYOUTS
+ {
LAYOUT_DWINDLE = 0,
LAYOUT_MASTER
};
- HYPRLAYOUTS m_iCurrentLayoutID = LAYOUT_DWINDLE;
+ int m_iCurrentLayoutID = LAYOUT_DWINDLE;
+
+ CHyprDwindleLayout m_cDwindleLayout;
+ CHyprMasterLayout m_cMasterLayout;
- CHyprDwindleLayout m_cDwindleLayout;
- CHyprMasterLayout m_cMasterLayout;
+ std::vector<std::pair<std::string, IHyprLayout*>> m_vLayouts;
};
inline std::unique_ptr<CLayoutManager> g_pLayoutManager; \ No newline at end of file
diff --git a/src/meson.build b/src/meson.build
index 6c018e12..7b658d31 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -16,6 +16,7 @@ executable('Hyprland', src,
xcb_dep,
backtrace_dep,
systemd_dep,
+ udis86,
dependency('pixman-1'),
dependency('gl', 'opengl'),
diff --git a/src/plugins/HookSystem.cpp b/src/plugins/HookSystem.cpp
new file mode 100644
index 00000000..a8ddd90e
--- /dev/null
+++ b/src/plugins/HookSystem.cpp
@@ -0,0 +1,151 @@
+#include "HookSystem.hpp"
+
+#define register
+#include <udis86.h>
+#undef register
+#include <sys/mman.h>
+#include <unistd.h>
+#include <cstring>
+
+CFunctionHook::CFunctionHook(HANDLE owner, void* source, void* destination) {
+ m_pSource = source;
+ m_pDestination = destination;
+ m_pOwner = owner;
+}
+
+CFunctionHook::~CFunctionHook() {
+ if (m_bActive) {
+ unhook();
+ }
+}
+
+size_t getInstructionLenAt(void* start) {
+ ud_t udis;
+
+ ud_init(&udis);
+ ud_set_mode(&udis, 64);
+ ud_set_syntax(&udis, UD_SYN_INTEL);
+
+ size_t curOffset = 1;
+ size_t insSize = 0;
+ while (true) {
+ ud_set_input_buffer(&udis, (uint8_t*)start, curOffset);
+ insSize = ud_disassemble(&udis);
+ if (insSize != curOffset)
+ break;
+ curOffset++;
+ }
+
+ return insSize;
+}
+
+size_t probeMinimumJumpSize(void* start, size_t min) {
+
+ size_t size = 0;
+
+ while (size <= min) {
+ // find info about this instruction
+ size_t insLen = getInstructionLenAt(start + size);
+ size += insLen;
+ }
+
+ return size;
+}
+
+bool CFunctionHook::hook() {
+
+ // check for unsupported platforms
+#if !defined(__x86_64__)
+ return false;
+#endif
+
+ // movabs $0,%rax | jmpq *%rax
+ static constexpr uint8_t ABSOLUTE_JMP_ADDRESS[] = {0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0};
+ // pushq %rax
+ static constexpr uint8_t PUSH_RAX[] = {0x50};
+ // popq %rax
+ static constexpr uint8_t POP_RAX[] = {0x58};
+ // nop
+ static constexpr uint8_t NOP = 0x90;
+
+ // get minimum size to overwrite
+ const auto HOOKSIZE = probeMinimumJumpSize(m_pSource, sizeof(ABSOLUTE_JMP_ADDRESS) + sizeof(PUSH_RAX) + sizeof(POP_RAX));
+
+ // alloc trampoline
+ m_pTrampolineAddr = mmap(NULL, sizeof(ABSOLUTE_JMP_ADDRESS) + HOOKSIZE + sizeof(PUSH_RAX), PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
+ // populate trampoline
+ memcpy(m_pTrampolineAddr, m_pSource, HOOKSIZE); // first, original func bytes
+ memcpy(m_pTrampolineAddr + HOOKSIZE, PUSH_RAX, sizeof(PUSH_RAX)); // then, pushq %rax
+ memcpy(m_pTrampolineAddr + HOOKSIZE + sizeof(PUSH_RAX), ABSOLUTE_JMP_ADDRESS, sizeof(ABSOLUTE_JMP_ADDRESS)); // then, jump to source
+
+ // fixup trampoline addr
+ *(uint64_t*)(m_pTrampolineAddr + HOOKSIZE + 2 + sizeof(PUSH_RAX)) = (uint64_t)(m_pSource + sizeof(ABSOLUTE_JMP_ADDRESS));
+
+ // make jump to hk
+ mprotect(m_pSource - ((uint64_t)m_pSource) % sysconf(_SC_PAGE_SIZE), sysconf(_SC_PAGE_SIZE), PROT_READ | PROT_WRITE | PROT_EXEC);
+ memcpy(m_pSource, ABSOLUTE_JMP_ADDRESS, sizeof(ABSOLUTE_JMP_ADDRESS));
+
+ // make popq %rax and NOP all remaining
+ memcpy(m_pSource + sizeof(ABSOLUTE_JMP_ADDRESS), POP_RAX, sizeof(POP_RAX));
+ size_t currentOp = sizeof(ABSOLUTE_JMP_ADDRESS) + sizeof(POP_RAX);
+ memset(m_pSource + currentOp, NOP, HOOKSIZE - currentOp);
+
+ // fixup jump addr
+ *(uint64_t*)(m_pSource + 2) = (uint64_t)(m_pDestination);
+
+ // revert mprot
+ mprotect(m_pSource - ((uint64_t)m_pSource) % sysconf(_SC_PAGE_SIZE), sysconf(_SC_PAGE_SIZE), PROT_READ | PROT_EXEC);
+
+ // set original addr to trampo addr
+ m_pOriginal = m_pTrampolineAddr;
+
+ m_bActive = true;
+ m_iHookLen = HOOKSIZE;
+ m_iTrampoLen = HOOKSIZE + sizeof(ABSOLUTE_JMP_ADDRESS);
+
+ return true;
+}
+
+bool CFunctionHook::unhook() {
+ // check for unsupported platforms
+#if !defined(__x86_64__)
+ return false;
+#endif
+
+ if (!m_bActive)
+ return false;
+
+ // allow write to src
+ mprotect(m_pSource - ((uint64_t)m_pSource) % sysconf(_SC_PAGE_SIZE), sysconf(_SC_PAGE_SIZE), PROT_READ | PROT_WRITE | PROT_EXEC);
+
+ // write back original bytes
+ memcpy(m_pSource, m_pTrampolineAddr, m_iHookLen);
+
+ // revert mprot
+ mprotect(m_pSource - ((uint64_t)m_pSource) % sysconf(_SC_PAGE_SIZE), sysconf(_SC_PAGE_SIZE), PROT_READ | PROT_EXEC);
+
+ // unmap
+ munmap(m_pTrampolineAddr, m_iTrampoLen);
+
+ // reset vars
+ m_bActive = false;
+ m_iHookLen = 0;
+ m_iTrampoLen = 0;
+ m_pTrampolineAddr = nullptr;
+
+ return true;
+}
+
+CFunctionHook* CHookSystem::initHook(HANDLE owner, void* source, void* destination) {
+ return m_vHooks.emplace_back(std::make_unique<CFunctionHook>(owner, source, destination)).get();
+}
+
+bool CHookSystem::removeHook(CFunctionHook* hook) {
+ std::erase_if(m_vHooks, [&](const auto& other) { return other.get() == hook; });
+ return true; // todo: make false if not found
+}
+
+void CHookSystem::removeAllHooksFrom(HANDLE handle) {
+ std::erase_if(m_vHooks, [&](const auto& other) { return other->m_pOwner == handle; });
+} \ No newline at end of file
diff --git a/src/plugins/HookSystem.hpp b/src/plugins/HookSystem.hpp
new file mode 100644
index 00000000..56227449
--- /dev/null
+++ b/src/plugins/HookSystem.hpp
@@ -0,0 +1,48 @@
+#pragma once
+
+#include <string>
+#include <vector>
+#include <memory>
+
+#define HANDLE void*
+
+class CFunctionHook {
+ public:
+ CFunctionHook(HANDLE owner, void* source, void* destination);
+ ~CFunctionHook();
+
+ bool hook();
+ bool unhook();
+
+ CFunctionHook(const CFunctionHook&) = delete;
+ CFunctionHook(CFunctionHook&&) = delete;
+ CFunctionHook& operator=(const CFunctionHook&) = delete;
+ CFunctionHook& operator=(CFunctionHook&&) = delete;
+
+ void* m_pOriginal = nullptr;
+
+ private:
+ void* m_pSource = nullptr;
+ void* m_pFunctionAddr = nullptr;
+ void* m_pTrampolineAddr = nullptr;
+ void* m_pDestination = nullptr;
+ size_t m_iHookLen = 0;
+ size_t m_iTrampoLen = 0;
+ HANDLE m_pOwner = nullptr;
+ bool m_bActive = false;
+
+ friend class CHookSystem;
+};
+
+class CHookSystem {
+ public:
+ CFunctionHook* initHook(HANDLE handle, void* source, void* destination);
+ bool removeHook(CFunctionHook* hook);
+
+ void removeAllHooksFrom(HANDLE handle);
+
+ private:
+ std::vector<std::unique_ptr<CFunctionHook>> m_vHooks;
+};
+
+inline std::unique_ptr<CHookSystem> g_pFunctionHookSystem; \ No newline at end of file
diff --git a/src/plugins/PluginAPI.cpp b/src/plugins/PluginAPI.cpp
new file mode 100644
index 00000000..dc8c9707
--- /dev/null
+++ b/src/plugins/PluginAPI.cpp
@@ -0,0 +1,193 @@
+#include "PluginAPI.hpp"
+#include "../Compositor.hpp"
+#include "../debug/HyprCtl.hpp"
+#include <dlfcn.h>
+
+APICALL bool HyprlandAPI::registerCallbackStatic(HANDLE handle, const std::string& event, HOOK_CALLBACK_FN* fn) {
+ auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
+
+ if (!PLUGIN)
+ return false;
+
+ g_pHookSystem->hookStatic(event, fn, handle);
+ PLUGIN->registeredCallbacks.emplace_back(std::make_pair<>(event, fn));
+
+ return true;
+}
+
+APICALL HOOK_CALLBACK_FN* HyprlandAPI::registerCallbackDynamic(HANDLE handle, const std::string& event, HOOK_CALLBACK_FN fn) {
+ auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
+
+ if (!PLUGIN)
+ return nullptr;
+
+ auto* const PFN = g_pHookSystem->hookDynamic(event, fn, handle);
+ PLUGIN->registeredCallbacks.emplace_back(std::make_pair<>(event, PFN));
+ return PFN;
+}
+
+APICALL bool HyprlandAPI::unregisterCallback(HANDLE handle, HOOK_CALLBACK_FN* fn) {
+ auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
+
+ if (!PLUGIN)
+ return false;
+
+ g_pHookSystem->unhook(fn);
+ std::erase_if(PLUGIN->registeredCallbacks, [&](const auto& other) { return other.second == fn; });
+
+ return true;
+}
+
+APICALL std::string HyprlandAPI::invokeHyprctlCommand(const std::string& call, const std::string& args, const std::string& format) {
+ std::string COMMAND = format + "/" + call + " " + args;
+ return HyprCtl::makeDynamicCall(COMMAND);
+}
+
+APICALL bool HyprlandAPI::addLayout(HANDLE handle, const std::string& name, IHyprLayout* layout) {
+ auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
+
+ if (!PLUGIN)
+ return false;
+
+ PLUGIN->registeredLayouts.push_back(layout);
+
+ return g_pLayoutManager->addLayout(name, layout);
+}
+
+APICALL bool HyprlandAPI::removeLayout(HANDLE handle, IHyprLayout* layout) {
+ auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
+
+ if (!PLUGIN)
+ return false;
+
+ std::erase(PLUGIN->registeredLayouts, layout);
+
+ return g_pLayoutManager->removeLayout(layout);
+}
+
+APICALL bool HyprlandAPI::reloadConfig() {
+ g_pConfigManager->m_bForceReload = true;
+ return true;
+}
+
+APICALL bool HyprlandAPI::addNotification(HANDLE handle, const std::string& text, const CColor& color, const float timeMs) {
+ auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
+
+ if (!PLUGIN)
+ return false;
+
+ g_pHyprNotificationOverlay->addNotification(text, color, timeMs);
+
+ return true;
+}
+
+APICALL CFunctionHook* HyprlandAPI::createFunctionHook(HANDLE handle, const void* source, const void* destination) {
+ auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
+
+ if (!PLUGIN)
+ return nullptr;
+
+ return g_pFunctionHookSystem->initHook(handle, (void*)source, (void*)destination);
+}
+
+APICALL bool HyprlandAPI::removeFunctionHook(HANDLE handle, CFunctionHook* hook) {
+ auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
+
+ if (!PLUGIN)
+ return false;
+
+ return g_pFunctionHookSystem->removeHook(hook);
+}
+
+APICALL bool HyprlandAPI::addWindowDecoration(HANDLE handle, CWindow* pWindow, IHyprWindowDecoration* pDecoration) {
+ auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
+
+ if (!PLUGIN)
+ return false;
+
+ if (!g_pCompositor->windowValidMapped(pWindow))
+ return false;
+
+ PLUGIN->registeredDecorations.push_back(pDecoration);
+
+ pWindow->m_dWindowDecorations.emplace_back(pDecoration);
+
+ return true;
+}
+
+APICALL bool HyprlandAPI::removeWindowDecoration(HANDLE handle, IHyprWindowDecoration* pDecoration) {
+ auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
+
+ if (!PLUGIN)
+ return false;
+
+ for (auto& w : g_pCompositor->m_vWindows) {
+ for (auto& d : w->m_dWindowDecorations) {
+ if (d.get() == pDecoration) {
+ std::erase(w->m_dWindowDecorations, d);
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+APICALL bool HyprlandAPI::addConfigValue(HANDLE handle, const std::string& name, const SConfigValue& value) {
+ auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
+
+ if (!g_pPluginSystem->m_bAllowConfigVars)
+ return false;
+
+ if (!PLUGIN)
+ return false;
+
+ if (name.find("plugin:") != 0)
+ return false;
+
+ g_pConfigManager->addPluginConfigVar(handle, name, value);
+ return true;
+}
+
+APICALL SConfigValue* HyprlandAPI::getConfigValue(HANDLE handle, const std::string& name) {
+ auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
+
+ if (!PLUGIN)
+ return nullptr;
+
+ return g_pConfigManager->getConfigValuePtrSafe(name);
+}
+
+APICALL void* HyprlandAPI::getFunctionAddressFromSignature(HANDLE handle, const std::string& sig) {
+ auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
+
+ if (!PLUGIN)
+ return nullptr;
+
+ return dlsym(nullptr, sig.c_str());
+}
+
+APICALL bool HyprlandAPI::addDispatcher(HANDLE handle, const std::string& name, std::function<void(std::string)> handler) {
+ auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
+
+ if (!PLUGIN)
+ return false;
+
+ PLUGIN->registeredDispatchers.push_back(name);
+
+ g_pKeybindManager->m_mDispatchers[name] = handler;
+
+ return true;
+}
+
+APICALL bool HyprlandAPI::removeDispatcher(HANDLE handle, const std::string& name) {
+ auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
+
+ if (!PLUGIN)
+ return false;
+
+ std::erase_if(g_pKeybindManager->m_mDispatchers, [&](const auto& other) { return other.first == name; });
+ std::erase_if(PLUGIN->registeredDispatchers, [&](const auto& other) { return other == name; });
+
+ return true;
+} \ No newline at end of file
diff --git a/src/plugins/PluginAPI.hpp b/src/plugins/PluginAPI.hpp
new file mode 100644
index 00000000..756a5631
--- /dev/null
+++ b/src/plugins/PluginAPI.hpp
@@ -0,0 +1,217 @@
+#pragma once
+
+/*
+
+Hyprland Plugin API.
+
+Most documentation will be made with comments in this code, but more info can be also found on the wiki.
+
+!WARNING!
+The Hyprland API passes C++ objects over, so no ABI compatibility is guaranteed.
+Make sure to compile your plugins with the same compiler as Hyprland, and ideally,
+on the same machine.
+
+See examples/examplePlugin for an example plugin
+
+*/
+
+#define HYPRLAND_API_VERSION "0.1"
+
+#include "../helpers/Color.hpp"
+#include "HookSystem.hpp"
+
+#include <any>
+#include <functional>
+#include <string>
+
+typedef std::function<void(void*, std::any)> HOOK_CALLBACK_FN;
+typedef struct {
+ std::string name;
+ std::string description;
+ std::string author;
+ std::string version;
+} PLUGIN_DESCRIPTION_INFO;
+
+#define APICALL extern "C"
+#define EXPORT __attribute__((visibility("default")))
+#define REQUIRED
+#define OPTIONAL
+#define HANDLE void*
+
+class IHyprLayout;
+class CWindow;
+class IHyprWindowDecoration;
+struct SConfigValue;
+
+/*
+ These methods are for the plugin to implement
+ Methods marked with REQUIRED are required.
+*/
+
+/*
+ called pre-plugin init.
+ In case of a version mismatch, will eject the .so.
+
+ This function should not be modified, see the example plugin.
+*/
+typedef REQUIRED std::string (*PPLUGIN_API_VERSION_FUNC)();
+#define PLUGIN_API_VERSION pluginAPIVersion
+#define PLUGIN_API_VERSION_FUNC_STR "pluginAPIVersion"
+
+/*
+ called on plugin init. Passes a handle as the parameter, which the plugin should keep for identification later.
+ The plugin should return a PLUGIN_DESCRIPTION_INFO struct with information about itself.
+
+ Keep in mind this is executed synchronously, and as such any blocking calls to hyprland might hang. (e.g. system("hyprctl ..."))
+*/
+typedef REQUIRED PLUGIN_DESCRIPTION_INFO (*PPLUGIN_INIT_FUNC)(HANDLE);
+#define PLUGIN_INIT pluginInit
+#define PLUGIN_INIT_FUNC_STR "pluginInit"
+
+/*
+ called on plugin unload, if that was a user action. If the plugin is being unloaded by an error,
+ this will not be called.
+
+ Hooks are unloaded after exit.
+*/
+typedef OPTIONAL void (*PPLUGIN_EXIT_FUNC)(void);
+#define PLUGIN_EXIT pluginExit
+#define PLUGIN_EXIT_FUNC_STR "pluginExit"
+
+/*
+ End plugin methods
+*/
+
+namespace HyprlandAPI {
+
+ /*
+ Add a config value.
+ All config values MUST be in the plugin: namespace
+ This method may only be called in "pluginInit"
+
+ After you have registered ALL of your config values, you may call `getConfigValue`
+
+ returns: true on success, false on fail
+ */
+ APICALL bool addConfigValue(HANDLE handle, const std::string& name, const SConfigValue& value);
+
+ /*
+ Get a config value.
+
+ returns: a pointer to the config value struct, which is guaranteed to be valid for the life of this plugin, unless another `addConfigValue` is called afterwards.
+ nullptr on error.
+ */
+ APICALL SConfigValue* getConfigValue(HANDLE handle, const std::string& name);
+
+ /*
+ Register a static (pointer) callback to a selected event.
+ Pointer must be kept valid until unregisterCallback() is called.
+
+ returns: true on success, false on fail
+ */
+ APICALL bool registerCallbackStatic(HANDLE handle, const std::string& event, HOOK_CALLBACK_FN* fn);
+
+ /*
+ Register a dynamic (function) callback to a selected event.
+ Pointer will be free'd by Hyprland on unregisterCallback().
+
+ returns: a pointer to the newly allocated function. nullptr on fail.
+ */
+ APICALL HOOK_CALLBACK_FN* registerCallbackDynamic(HANDLE handle, const std::string& event, HOOK_CALLBACK_FN fn);
+
+ /*
+ Unregisters a callback. If the callback was dynamic, frees the memory.
+
+ returns: true on success, false on fail
+ */
+ APICALL bool unregisterCallback(HANDLE handle, HOOK_CALLBACK_FN* fn);
+
+ /*
+ Calls a hyprctl command.
+
+ returns: the output (as in hyprctl)
+ */
+ APICALL std::string invokeHyprctlCommand(const std::string& call, const std::string& args, const std::string& format = "");
+
+ /*
+ Adds a layout to Hyprland.
+
+ returns: true on success. False otherwise.
+ */
+ APICALL bool addLayout(HANDLE handle, const std::string& name, IHyprLayout* layout);
+
+ /*
+ Removes an added layout from Hyprland.
+
+ returns: true on success. False otherwise.
+ */
+ APICALL bool removeLayout(HANDLE handle, IHyprLayout* layout);
+
+ /*
+ Queues a config reload. Does not take effect immediately.
+
+ returns: true on success. False otherwise.
+ */
+ APICALL bool reloadConfig();
+
+ /*
+ Adds a notification.
+
+ returns: true on success. False otherwise.
+ */
+ APICALL bool addNotification(HANDLE handle, const std::string& text, const CColor& color, const float timeMs);
+
+ /*
+ Creates a trampoline function hook to an internal hl func.
+
+ returns: CFunctionHook*
+
+ !WARNING! Hooks are *not* guaranteed any API stability. Internal methods may be removed, added, or renamed. Consider preferring the API whenever possible.
+ */
+ APICALL CFunctionHook* createFunctionHook(HANDLE handle, const void* source, const void* destination);
+
+ /*
+ Removes a trampoline function hook. Will unhook if still hooked.
+
+ returns: true on success. False otherwise.
+
+ !WARNING! Hooks are *not* guaranteed any API stability. Internal methods may be removed, added, or renamed. Consider preferring the API whenever possible.
+ */
+ APICALL bool removeFunctionHook(HANDLE handle, CFunctionHook* hook);
+
+ /*
+ Gets a function address from a signature.
+ This is useful for hooking private functions.
+
+ returns: function address, or nullptr on fail.
+ */
+ APICALL void* getFunctionAddressFromSignature(HANDLE handle, const std::string& sig);
+
+ /*
+ Adds a window decoration to a window
+
+ returns: true on success. False otherwise.
+ */
+ APICALL bool addWindowDecoration(HANDLE handle, CWindow* pWindow, IHyprWindowDecoration* pDecoration);
+
+ /*
+ Removes a window decoration
+
+ returns: true on success. False otherwise.
+ */
+ APICALL bool removeWindowDecoration(HANDLE handle, IHyprWindowDecoration* pDecoration);
+
+ /*
+ Adds a keybind dispatcher.
+
+ returns: true on success. False otherwise.
+ */
+ APICALL bool addDispatcher(HANDLE handle, const std::string& name, std::function<void(std::string)> handler);
+
+ /*
+ Removes a keybind dispatcher.
+
+ returns: true on success. False otherwise.
+ */
+ APICALL bool removeDispatcher(HANDLE handle, const std::string& name);
+}; \ No newline at end of file
diff --git a/src/plugins/PluginSystem.cpp b/src/plugins/PluginSystem.cpp
new file mode 100644
index 00000000..5b15782d
--- /dev/null
+++ b/src/plugins/PluginSystem.cpp
@@ -0,0 +1,139 @@
+#include "PluginSystem.hpp"
+
+#include <dlfcn.h>
+#include "../Compositor.hpp"
+
+CPluginSystem::CPluginSystem() {
+ g_pFunctionHookSystem = std::make_unique<CHookSystem>();
+}
+
+CPlugin* CPluginSystem::loadPlugin(const std::string& path) {
+
+ if (getPluginByPath(path)) {
+ Debug::log(ERR, " [PluginSystem] Cannot load a plugin twice!");
+ return nullptr;
+ }
+
+ auto* const PLUGIN = m_vLoadedPlugins.emplace_back(std::make_unique<CPlugin>()).get();
+
+ PLUGIN->path = path;
+
+ HANDLE MODULE = dlopen(path.c_str(), RTLD_LAZY);
+
+ if (!MODULE) {
+ Debug::log(ERR, " [PluginSystem] Plugin %s could not be loaded: %s", path.c_str(), dlerror());
+ m_vLoadedPlugins.pop_back();
+ return nullptr;
+ }
+
+ PLUGIN->m_pHandle = MODULE;
+
+ PPLUGIN_API_VERSION_FUNC apiVerFunc = (PPLUGIN_API_VERSION_FUNC)dlsym(MODULE, PLUGIN_API_VERSION_FUNC_STR);
+ PPLUGIN_INIT_FUNC initFunc = (PPLUGIN_INIT_FUNC)dlsym(MODULE, PLUGIN_INIT_FUNC_STR);
+
+ if (!apiVerFunc || !initFunc) {
+ Debug::log(ERR, " [PluginSystem] Plugin %s could not be loaded. (No apiver/init func)", path.c_str());
+ dlclose(MODULE);
+ m_vLoadedPlugins.pop_back();
+ return nullptr;
+ }
+
+ const std::string PLUGINAPIVER = apiVerFunc();
+
+ if (PLUGINAPIVER != HYPRLAND_API_VERSION) {
+ Debug::log(ERR, " [PluginSystem] Plugin %s could not be loaded. (API version mismatch)", path.c_str());
+ dlclose(MODULE);
+ m_vLoadedPlugins.pop_back();
+ return nullptr;
+ }
+
+ PLUGIN_DESCRIPTION_INFO PLUGINDATA;
+
+ try {
+ if (!setjmp(m_jbPluginFaultJumpBuf)) {
+ m_bAllowConfigVars = true;
+ PLUGINDATA = initFunc(MODULE);
+ } else {
+ // this module crashed.
+ throw std::exception();
+ }
+ } catch (std::exception& e) {
+ m_bAllowConfigVars = false;
+ Debug::log(ERR, " [PluginSystem] Plugin %s (Handle %lx) crashed in init. Unloading.", path.c_str(), MODULE);
+ unloadPlugin(PLUGIN, true); // Plugin could've already hooked/done something
+ return nullptr;
+ }
+
+ m_bAllowConfigVars = false;
+
+ PLUGIN->author = PLUGINDATA.author;
+ PLUGIN->description = PLUGINDATA.description;
+ PLUGIN->version = PLUGINDATA.version;
+ PLUGIN->name = PLUGINDATA.name;
+
+ Debug::log(LOG, " [PluginSystem] Plugin %s loaded. Handle: %lx, path: \"%s\", author: \"%s\", description: \"%s\", version: \"%s\"", PLUGINDATA.name.c_str(), MODULE,
+ path.c_str(), PLUGINDATA.author.c_str(), PLUGINDATA.description.c_str(), PLUGINDATA.version.c_str());
+
+ return PLUGIN;
+}
+
+void CPluginSystem::unloadPlugin(const CPlugin* plugin, bool eject) {
+ if (!plugin)
+ return;
+
+ if (!eject) {
+ PPLUGIN_EXIT_FUNC exitFunc = (PPLUGIN_EXIT_FUNC)dlsym(plugin->m_pHandle, PLUGIN_EXIT_FUNC_STR);
+ if (exitFunc)
+ exitFunc();
+ }
+
+ for (auto& [k, v] : plugin->registeredCallbacks)
+ g_pHookSystem->unhook(v);
+
+ const auto ls = plugin->registeredLayouts;
+ for (auto& l : ls)
+ g_pLayoutManager->removeLayout(l);
+
+ g_pFunctionHookSystem->removeAllHooksFrom(plugin->m_pHandle);
+
+ const auto rd = plugin->registeredDecorations;
+ for (auto& d : rd)
+ HyprlandAPI::removeWindowDecoration(plugin->m_pHandle, d);
+
+ const auto rdi = plugin->registeredDispatchers;
+ for (auto& d : rdi)
+ HyprlandAPI::removeDispatcher(plugin->m_pHandle, d);
+
+ g_pConfigManager->removePluginConfig(plugin->m_pHandle);
+
+ dlclose(plugin->m_pHandle);
+
+ Debug::log(LOG, " [PluginSystem] Plugin %s unloaded.", plugin->name.c_str());
+
+ std::erase_if(m_vLoadedPlugins, [&](const auto& other) { return other->m_pHandle == plugin->m_pHandle; });
+}
+
+CPlugin* CPluginSystem::getPluginByPath(const std::string& path) {
+ for (auto& p : m_vLoadedPlugins) {
+ if (p->path == path)
+ return p.get();
+ }
+
+ return nullptr;
+}
+
+CPlugin* CPluginSystem::getPluginByHandle(HANDLE handle) {
+ for (auto& p : m_vLoadedPlugins) {
+ if (p->m_pHandle == handle)
+ return p.get();
+ }
+
+ return nullptr;
+}
+
+std::vector<CPlugin*> CPluginSystem::getAllPlugins() {
+ std::vector<CPlugin*> results(m_vLoadedPlugins.size());
+ for (size_t i = 0; i < m_vLoadedPlugins.size(); ++i)
+ results[i] = m_vLoadedPlugins[i].get();
+ return results;
+} \ No newline at end of file
diff --git a/src/plugins/PluginSystem.hpp b/src/plugins/PluginSystem.hpp
new file mode 100644
index 00000000..041ee47e
--- /dev/null
+++ b/src/plugins/PluginSystem.hpp
@@ -0,0 +1,44 @@
+#pragma once
+
+#include "../defines.hpp"
+#include "PluginAPI.hpp"
+#include <csetjmp>
+
+class IHyprWindowDecoration;
+
+class CPlugin {
+ public:
+ std::string name = "";
+ std::string description = "";
+ std::string author = "";
+ std::string version = "";
+
+ std::string path = "";
+
+ HANDLE m_pHandle = nullptr;
+
+ std::vector<IHyprLayout*> registeredLayouts;
+ std::vector<IHyprWindowDecoration*> registeredDecorations;
+ std::vector<std::pair<std::string, HOOK_CALLBACK_FN*>> registeredCallbacks;
+ std::vector<std::string> registeredDispatchers;
+};
+
+class CPluginSystem {
+ public:
+ CPluginSystem();
+
+ CPlugin* loadPlugin(const std::string& path);
+ void unloadPlugin(const CPlugin* plugin, bool eject = false);
+ CPlugin* getPluginByPath(const std::string& path);
+ CPlugin* getPluginByHandle(HANDLE handle);
+ std::vector<CPlugin*> getAllPlugins();
+
+ bool m_bAllowConfigVars = false;
+
+ private:
+ std::vector<std::unique_ptr<CPlugin>> m_vLoadedPlugins;
+
+ jmp_buf m_jbPluginFaultJumpBuf;
+};
+
+inline std::unique_ptr<CPluginSystem> g_pPluginSystem; \ No newline at end of file
diff --git a/src/render/decorations/IHyprWindowDecoration.hpp b/src/render/decorations/IHyprWindowDecoration.hpp
index 093b99cf..4cd52cba 100644
--- a/src/render/decorations/IHyprWindowDecoration.hpp
+++ b/src/render/decorations/IHyprWindowDecoration.hpp
@@ -6,7 +6,8 @@ enum eDecorationType
{
DECORATION_NONE = -1,
DECORATION_GROUPBAR,
- DECORATION_SHADOW
+ DECORATION_SHADOW,
+ DECORATION_CUSTOM
};
struct SWindowDecorationExtents {
diff --git a/subprojects/udis86 b/subprojects/udis86
new file mode 160000
+Subproject 5336633af70f3917760a6d441ff02d93477b0c8