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..0bef0dca 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 .. @@ -165,6 +166,7 @@ install: 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) && cd ../.. make protocols make release cd hyprctl && make all && cd .. @@ -211,6 +213,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/README.md b/README.md index 0e9f6034..4fd92b1c 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ Although Hyprland is pretty stable, it may have some bugs. # Features - Easily expandable and readable codebase +- Plugin support - Config reloaded instantly upon saving - Custom bezier curve based animations - Dual Kawase blur 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= 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 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 + +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 +#include +#include "customLayout.hpp" +#include "customDecoration.hpp" + +#include +#include + +// Methods +inline std::unique_ptr g_pCustomLayout; +inline CFunctionHook* g_pFocusHook = nullptr; +inline CFunctionHook* g_pMotionHook = nullptr; +inline CFunctionHook* g_pMouseDownHook = nullptr; +typedef void (*origFocusWindow)(void*, CWindow*, wlr_surface*); +typedef void (*origMotion)(wlr_seat*, uint32_t, double, double); +typedef void (*origMouseDownNormal)(void*, wlr_pointer_button_event*); + +// Do NOT change this function. +APICALL EXPORT std::string PLUGIN_API_VERSION() { + return HYPRLAND_API_VERSION; +} + +static void onActiveWindowChange(void* self, std::any data) { + try { + auto* const PWINDOW = std::any_cast(data); + + HyprlandAPI::addNotification(PHANDLE, "[ExamplePlugin] Active window: " + (PWINDOW ? PWINDOW->m_szTitle : "None"), CColor{0.f, 0.5f, 1.f, 1.f}, 5000); + } catch (std::bad_any_cast& e) { HyprlandAPI::addNotification(PHANDLE, "[ExamplePlugin] Active window: None", CColor{0.f, 0.5f, 1.f, 1.f}, 5000); } +} + +static void onNewWindow(void* self, std::any data) { + auto* const PWINDOW = std::any_cast(data); + + HyprlandAPI::addWindowDecoration(PHANDLE, PWINDOW, new CCustomDecoration(PWINDOW)); +} + +void hkFocusWindow(void* thisptr, CWindow* pWindow, wlr_surface* pSurface) { + // HyprlandAPI::addNotification(PHANDLE, getFormat("FocusWindow with %lx %lx", pWindow, pSurface), CColor{0.f, 1.f, 1.f, 1.f}, 5000); + (*(origFocusWindow)g_pFocusHook->m_pOriginal)(thisptr, pWindow, pSurface); +} + +void hkNotifyMotion(wlr_seat* wlr_seat, uint32_t time_msec, double sx, double sy) { + // HyprlandAPI::addNotification(PHANDLE, getFormat("NotifyMotion with %lf %lf", sx, sy), CColor{0.f, 1.f, 1.f, 1.f}, 5000); + (*(origMotion)g_pMotionHook->m_pOriginal)(wlr_seat, time_msec, sx, sy); +} + +void hkProcessMouseDownNormal(void* thisptr, wlr_pointer_button_event* e) { + // HyprlandAPI::addNotification(PHANDLE, "Mouse down normal!", CColor{0.8f, 0.2f, 0.5f, 1.0f}, 5000); + (*(origMouseDownNormal)g_pMouseDownHook->m_pOriginal)(thisptr, e); +} + +APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { + PHANDLE = handle; + + HyprlandAPI::addNotification(PHANDLE, "Hello World from an example plugin!", CColor{0.f, 1.f, 1.f, 1.f}, 5000); + + HyprlandAPI::registerCallbackDynamic(PHANDLE, "activeWindow", [&](void* self, std::any data) { onActiveWindowChange(self, data); }); + HyprlandAPI::registerCallbackDynamic(PHANDLE, "openWindow", [&](void* self, std::any data) { onNewWindow(self, data); }); + + g_pCustomLayout = std::make_unique(); + + HyprlandAPI::addLayout(PHANDLE, "custom", g_pCustomLayout.get()); + + HyprlandAPI::addConfigValue(PHANDLE, "plugin:example:border_color", SConfigValue{.intValue = configStringToInt("rgb(44ee44)")}); + + HyprlandAPI::addDispatcher(PHANDLE, "example", [](std::string arg) { HyprlandAPI::addNotification(PHANDLE, "Arg passed: " + arg, CColor{0.5f, 0.5f, 0.7f, 1.0f}, 5000); }); + + // Hook a public member + g_pFocusHook = HyprlandAPI::createFunctionHook(PHANDLE, (void*)&CCompositor::focusWindow, (void*)&hkFocusWindow); + // Hook a public non-member + g_pMotionHook = HyprlandAPI::createFunctionHook(PHANDLE, (void*)&wlr_seat_pointer_notify_motion, (void*)&hkNotifyMotion); + // Hook a private member (!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 55eeaac7..9a8a3c73 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(); } @@ -279,6 +285,10 @@ void CCompositor::cleanup() { m_bIsShuttingDown = true; + // unload all remaining plugins while the compositor is + // still in a normal working state. + g_pPluginSystem->unloadAllPlugins(); + m_pLastFocus = nullptr; m_pLastWindow = nullptr; @@ -364,6 +374,12 @@ void CCompositor::startCompositor() { Debug::log(LOG, "Creating the HyprDebugOverlay!"); g_pDebugOverlay = std::make_unique(); + + Debug::log(LOG, "Creating the HyprNotificationOverlay!"); + g_pHyprNotificationOverlay = std::make_unique(); + + Debug::log(LOG, "Creating the PluginSystem!"); + g_pPluginSystem = std::make_unique(); // // @@ -486,10 +502,6 @@ CMonitor* CCompositor::getMonitorFromVector(const Vector2D& point) { void CCompositor::removeWindowFromVectorSafe(CWindow* pWindow) { if (windowExists(pWindow) && !pWindow->m_bFadingOut) { - if (pWindow->m_bIsX11 && pWindow->m_iX11Type == 2) { - std::erase_if(m_dUnmanagedX11Windows, [&](std::unique_ptr& el) { return el.get() == pWindow; }); - } - // if X11, also check its children // and delete any needed if (pWindow->m_bIsX11) { @@ -507,6 +519,10 @@ void CCompositor::removeWindowFromVectorSafe(CWindow* pWindow) { } } + if (pWindow->m_bIsX11 && pWindow->m_iX11Type == 2) { + std::erase_if(m_dUnmanagedX11Windows, [&](std::unique_ptr& el) { return el.get() == pWindow; }); + } + std::erase_if(m_vWindows, [&](std::unique_ptr& el) { return el.get() == pWindow; }); } } @@ -517,6 +533,11 @@ bool CCompositor::windowExists(CWindow* pWindow) { return true; } + for (auto& u : m_dUnmanagedX11Windows) { + if (u.get() == pWindow) + return true; + } + return false; } @@ -591,8 +612,8 @@ CWindow* CCompositor::vectorToWindowIdeal(const Vector2D& pos) { // special workspace if (PMONITOR->specialWorkspaceID) { for (auto w = m_vWindows.rbegin(); w != m_vWindows.rend(); w++) { - wlr_box box = {(*w)->m_vRealPosition.vec().x - BORDER_GRAB_AREA, (*w)->m_vRealPosition.vec().y - BORDER_GRAB_AREA, (*w)->m_vRealSize.vec().x + 2 * BORDER_GRAB_AREA, - (*w)->m_vRealSize.vec().y + 2 * BORDER_GRAB_AREA}; + const auto BB = w->get()->getWindowInputBox(); + wlr_box box = {BB.x - BORDER_GRAB_AREA, BB.y - BORDER_GRAB_AREA, BB.width + 2 * BORDER_GRAB_AREA, BB.height + 2 * BORDER_GRAB_AREA}; if ((*w)->m_bIsFloating && (*w)->m_iWorkspaceID == PMONITOR->specialWorkspaceID && (*w)->m_bIsMapped && wlr_box_contains_point(&box, pos.x, pos.y) && !(*w)->isHidden() && !(*w)->m_bX11ShouldntFocus) return (*w).get(); @@ -608,8 +629,8 @@ CWindow* CCompositor::vectorToWindowIdeal(const Vector2D& pos) { // pinned windows on top of floating regardless for (auto w = m_vWindows.rbegin(); w != m_vWindows.rend(); w++) { - wlr_box box = {(*w)->m_vRealPosition.vec().x - BORDER_GRAB_AREA, (*w)->m_vRealPosition.vec().y - BORDER_GRAB_AREA, (*w)->m_vRealSize.vec().x + 2 * BORDER_GRAB_AREA, - (*w)->m_vRealSize.vec().y + 2 * BORDER_GRAB_AREA}; + const auto BB = w->get()->getWindowInputBox(); + wlr_box box = {BB.x - BORDER_GRAB_AREA, BB.y - BORDER_GRAB_AREA, BB.width + 2 * BORDER_GRAB_AREA, BB.height + 2 * BORDER_GRAB_AREA}; if ((*w)->m_bIsFloating && (*w)->m_bIsMapped && !(*w)->isHidden() && !(*w)->m_bX11ShouldntFocus && (*w)->m_bPinned) { if (wlr_box_contains_point(&box, m_sWLRCursor->x, m_sWLRCursor->y)) return w->get(); @@ -623,8 +644,8 @@ CWindow* CCompositor::vectorToWindowIdeal(const Vector2D& pos) { // first loop over floating cuz they're above, m_lWindows should be sorted bottom->top, for tiled it doesn't matter. for (auto w = m_vWindows.rbegin(); w != m_vWindows.rend(); w++) { - wlr_box box = {(*w)->m_vRealPosition.vec().x - BORDER_GRAB_AREA, (*w)->m_vRealPosition.vec().y - BORDER_GRAB_AREA, (*w)->m_vRealSize.vec().x + 2 * BORDER_GRAB_AREA, - (*w)->m_vRealSize.vec().y + 2 * BORDER_GRAB_AREA}; + const auto BB = w->get()->getWindowInputBox(); + wlr_box box = {BB.x - BORDER_GRAB_AREA, BB.y - BORDER_GRAB_AREA, BB.width + 2 * BORDER_GRAB_AREA, BB.height + 2 * BORDER_GRAB_AREA}; if ((*w)->m_bIsFloating && (*w)->m_bIsMapped && isWorkspaceVisible((*w)->m_iWorkspaceID) && !(*w)->isHidden() && !(*w)->m_bPinned) { // OR windows should add focus to parent if ((*w)->m_bX11ShouldntFocus && (*w)->m_iX11Type != 2) @@ -1656,6 +1677,9 @@ void CCompositor::updateWindowAnimatedDecorationValues(CWindow* pWindow) { } else { pWindow->m_cRealShadowColor.setValueAndWarp(CColor(0, 0, 0, 0)); // no shadow } + + for (auto& d : pWindow->m_dWindowDecorations) + d->updateWindow(pWindow); } int CCompositor::getNextAvailableMonitorID() { @@ -2012,6 +2036,9 @@ void CCompositor::scheduleFrameForMonitor(CMonitor* pMonitor) { if (!pMonitor->m_bEnabled) return; + if (pMonitor->renderingActive) + pMonitor->pendingFrame = true; + wlr_output_schedule_frame(pMonitor->output); } @@ -2032,7 +2059,7 @@ CWindow* CCompositor::getWindowByRegex(const std::string& regexp) { } for (auto& w : g_pCompositor->m_vWindows) { - if (!w->m_bIsMapped || w->isHidden()) + if (!w->m_bIsMapped || (w->isHidden() && !w->m_sGroupData.pNextWindow)) continue; switch (mode) { 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/Window.cpp b/src/Window.cpp index 549133d5..1c3d0338 100644 --- a/src/Window.cpp +++ b/src/Window.cpp @@ -88,6 +88,59 @@ wlr_box CWindow::getWindowIdealBoundingBoxIgnoreReserved() { return wlr_box{(int)POS.x, (int)POS.y, (int)SIZE.x, (int)SIZE.y}; } +wlr_box CWindow::getWindowInputBox() { + static auto* const PBORDERSIZE = &g_pConfigManager->getConfigValuePtr("general:border_size")->intValue; + + if (m_sAdditionalConfigData.dimAround) { + const auto PMONITOR = g_pCompositor->getMonitorFromID(m_iMonitorID); + return {PMONITOR->vecPosition.x, PMONITOR->vecPosition.y, PMONITOR->vecSize.x, PMONITOR->vecSize.y}; + } + + SWindowDecorationExtents maxExtents = {{*PBORDERSIZE + 2, *PBORDERSIZE + 2}, {*PBORDERSIZE + 2, *PBORDERSIZE + 2}}; + + for (auto& wd : m_dWindowDecorations) { + + if (!wd->allowsInput()) + continue; + + const auto EXTENTS = wd->getWindowDecorationExtents(); + + if (EXTENTS.topLeft.x > maxExtents.topLeft.x) + maxExtents.topLeft.x = EXTENTS.topLeft.x; + + if (EXTENTS.topLeft.y > maxExtents.topLeft.y) + maxExtents.topLeft.y = EXTENTS.topLeft.y; + + if (EXTENTS.bottomRight.x > maxExtents.bottomRight.x) + maxExtents.bottomRight.x = EXTENTS.bottomRight.x; + + if (EXTENTS.bottomRight.y > maxExtents.bottomRight.y) + maxExtents.bottomRight.y = EXTENTS.bottomRight.y; + } + + // Add extents to the real base BB and return + wlr_box finalBox = {m_vRealPosition.vec().x - maxExtents.topLeft.x, m_vRealPosition.vec().y - maxExtents.topLeft.y, + m_vRealSize.vec().x + maxExtents.topLeft.x + maxExtents.bottomRight.x, m_vRealSize.vec().y + maxExtents.topLeft.y + maxExtents.bottomRight.y}; + + return finalBox; +} + +SWindowDecorationExtents CWindow::getFullWindowReservedArea() { + SWindowDecorationExtents extents; + + for (auto& wd : m_dWindowDecorations) { + const auto RESERVED = wd->getWindowDecorationReservedArea(); + + if (RESERVED.bottomRight == Vector2D{} && RESERVED.topLeft == Vector2D{}) + continue; + + extents.topLeft = extents.topLeft + RESERVED.topLeft; + extents.bottomRight = extents.bottomRight + RESERVED.bottomRight; + } + + return extents; +} + void CWindow::updateWindowDecos() { for (auto& wd : m_dWindowDecorations) wd->updateWindow(this); diff --git a/src/Window.hpp b/src/Window.hpp index 65ac78f5..85144e27 100644 --- a/src/Window.hpp +++ b/src/Window.hpp @@ -155,6 +155,7 @@ class CWindow { DYNLISTENER(toplevelClose); DYNLISTENER(toplevelActivate); DYNLISTENER(toplevelFullscreen); + DYNLISTENER(setOverrideRedirect); // DYNLISTENER(newSubsurfaceWindow); union { @@ -286,34 +287,36 @@ class CWindow { } // methods - wlr_box getFullWindowBoundingBox(); - wlr_box getWindowIdealBoundingBoxIgnoreReserved(); - void updateWindowDecos(); - pid_t getPID(); - IHyprWindowDecoration* getDecorationByType(eDecorationType); - void removeDecorationByType(eDecorationType); - void createToplevelHandle(); - void destroyToplevelHandle(); - void updateToplevel(); - void updateSurfaceOutputs(); - void moveToWorkspace(int); - CWindow* X11TransientFor(); - void onUnmap(); - void onMap(); - void setHidden(bool hidden); - bool isHidden(); - void applyDynamicRule(const SWindowRule& r); - void updateDynamicRules(); + wlr_box getFullWindowBoundingBox(); + wlr_box getWindowInputBox(); + wlr_box getWindowIdealBoundingBoxIgnoreReserved(); + void updateWindowDecos(); + pid_t getPID(); + IHyprWindowDecoration* getDecorationByType(eDecorationType); + void removeDecorationByType(eDecorationType); + void createToplevelHandle(); + void destroyToplevelHandle(); + void updateToplevel(); + void updateSurfaceOutputs(); + void moveToWorkspace(int); + CWindow* X11TransientFor(); + void onUnmap(); + void onMap(); + void setHidden(bool hidden); + bool isHidden(); + void applyDynamicRule(const SWindowRule& r); + void updateDynamicRules(); + SWindowDecorationExtents getFullWindowReservedArea(); - void onBorderAngleAnimEnd(void* ptr); - bool isInCurvedCorner(double x, double y); - bool hasPopupAt(const Vector2D& pos); + void onBorderAngleAnimEnd(void* ptr); + bool isInCurvedCorner(double x, double y); + bool hasPopupAt(const Vector2D& pos); - CWindow* getGroupHead(); - CWindow* getGroupTail(); - CWindow* getGroupCurrent(); - void setGroupCurrent(CWindow* pWindow); - void insertWindowToGroup(CWindow* pWindow); + CWindow* getGroupHead(); + CWindow* getGroupTail(); + CWindow* getGroupCurrent(); + void setGroupCurrent(CWindow* pWindow); + void insertWindowToGroup(CWindow* pWindow); private: // For hidden windows and stuff diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index c2d8a3ca..f8d2bbc9 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -52,21 +52,22 @@ void CConfigManager::setDefaultVars() { configValues["general:hover_icon_on_border"].intValue = 1; configValues["general:layout"].strValue = "dwindle"; - configValues["misc:disable_hyprland_logo"].intValue = 0; - configValues["misc:disable_splash_rendering"].intValue = 0; - configValues["misc:vfr"].intValue = 1; - configValues["misc:vrr"].intValue = 0; - configValues["misc:mouse_move_enables_dpms"].intValue = 0; - configValues["misc:always_follow_on_dnd"].intValue = 1; - configValues["misc:layers_hog_keyboard_focus"].intValue = 1; - configValues["misc:animate_manual_resizes"].intValue = 0; - configValues["misc:disable_autoreload"].intValue = 0; - configValues["misc:enable_swallow"].intValue = 0; - configValues["misc:swallow_regex"].strValue = STRVAL_EMPTY; - configValues["misc:focus_on_activate"].intValue = 0; - configValues["misc:no_direct_scanout"].intValue = 0; - configValues["misc:hide_cursor_on_touch"].intValue = 1; - configValues["misc:mouse_move_focuses_monitor"].intValue = 1; + configValues["misc:disable_hyprland_logo"].intValue = 0; + configValues["misc:disable_splash_rendering"].intValue = 0; + configValues["misc:vfr"].intValue = 1; + configValues["misc:vrr"].intValue = 0; + configValues["misc:mouse_move_enables_dpms"].intValue = 0; + configValues["misc:always_follow_on_dnd"].intValue = 1; + configValues["misc:layers_hog_keyboard_focus"].intValue = 1; + configValues["misc:animate_manual_resizes"].intValue = 1; + configValues["misc:animate_mouse_windowdragging"].intValue = 1; + configValues["misc:disable_autoreload"].intValue = 0; + configValues["misc:enable_swallow"].intValue = 0; + configValues["misc:swallow_regex"].strValue = STRVAL_EMPTY; + configValues["misc:focus_on_activate"].intValue = 0; + configValues["misc:no_direct_scanout"].intValue = 0; + configValues["misc:hide_cursor_on_touch"].intValue = 1; + configValues["misc:mouse_move_focuses_monitor"].intValue = 1; configValues["debug:int"].intValue = 0; configValues["debug:log_damage"].intValue = 0; @@ -271,7 +272,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 +307,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 +1662,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 +1822,19 @@ ICustomConfigValueData::~ICustomConfigValueData() { std::unordered_map 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::make_unique>())); + 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; }); +} 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 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 configPaths; // stores all the config paths - std::unordered_map configModifyTimes; // stores modify times - std::unordered_map configDynamicVars; // stores dynamic vars declared by the user - std::unordered_map configValues; - std::unordered_map> deviceConfigs; // stores device configs + std::deque configPaths; // stores all the config paths + std::unordered_map configModifyTimes; // stores modify times + std::unordered_map configDynamicVars; // stores dynamic vars declared by the user + std::unordered_map configValues; + std::unordered_map> deviceConfigs; // stores device configs - std::unordered_map animationConfig; // stores all the animations with their set values + std::unordered_map 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> boundWorkspaces; + std::vector> boundWorkspaces; - std::vector execRequestedRules; // rules requested with exec, e.g. [workspace 2] kitty + std::vector execRequestedRules; // rules requested with exec, e.g. [workspace 2] kitty - bool isFirstLaunch = true; // For exec-once + std::unordered_map>> pluginConfigs; // stores plugin configs - std::deque m_dMonitorRules; - std::deque m_dWindowRules; - std::deque m_dLayerRules; - std::deque m_dBlurLSNamespaces; + bool isFirstLaunch = true; // For exec-once - bool firstExecDispatched = false; - std::deque firstExecRequests; + std::deque m_dMonitorRules; + std::deque m_dWindowRules; + std::deque m_dLayerRules; + std::deque m_dBlurLSNamespaces; + + bool firstExecDispatched = false; + std::deque 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 #include +#include "../plugins/PluginSystem.hpp" + #if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) #include #endif + std::string getRandomMessage() { const std::vector 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..0adfb897 100644 --- a/src/debug/HyprCtl.cpp +++ b/src/debug/HyprCtl.cpp @@ -108,6 +108,8 @@ static std::string getWindowData(CWindow* w, HyprCtl::eHyprCtlOutputFormat forma return getFormat( R"#({ "address": "0x%x", + "mapped": %s, + "hidden": %s, "at": [%i, %i], "size": [%i, %i], "workspace": { @@ -127,7 +129,8 @@ static std::string getWindowData(CWindow* w, HyprCtl::eHyprCtlOutputFormat forma "grouped": [%s], "swallowing": %s },)#", - w, (int)w->m_vRealPosition.goalv().x, (int)w->m_vRealPosition.goalv().y, (int)w->m_vRealSize.goalv().x, (int)w->m_vRealSize.goalv().y, w->m_iWorkspaceID, + w, (w->m_bIsMapped ? "true" : "false"), (w->isHidden() ? "true" : "false"), (int)w->m_vRealPosition.goalv().x, (int)w->m_vRealPosition.goalv().y, + (int)w->m_vRealSize.goalv().x, (int)w->m_vRealSize.goalv().y, w->m_iWorkspaceID, escapeJSONStrings(w->m_iWorkspaceID == -1 ? "" : g_pCompositor->getWorkspaceByID(w->m_iWorkspaceID) ? g_pCompositor->getWorkspaceByID(w->m_iWorkspaceID)->m_szName : std::string("Invalid workspace " + std::to_string(w->m_iWorkspaceID))) @@ -139,10 +142,11 @@ static std::string getWindowData(CWindow* w, HyprCtl::eHyprCtlOutputFormat forma w->m_bFakeFullscreenState ? "true" : "false", getGroupedData(w, format).c_str(), (w->m_pSwallowed ? getFormat("\"0x%x\"", w->m_pSwallowed).c_str() : "null")); } else { return getFormat( - "Window %x -> %s:\n\tat: %i,%i\n\tsize: %i,%i\n\tworkspace: %i (%s)\n\tfloating: %i\n\tmonitor: %i\n\tclass: %s\n\ttitle: %s\n\tpid: %i\n\txwayland: %i\n\tpinned: " + "Window %x -> %s:\n\tmapped: %i\n\thidden: %i\n\tat: %i,%i\n\tsize: %i,%i\n\tworkspace: %i (%s)\n\tfloating: %i\n\tmonitor: %i\n\tclass: %s\n\ttitle: %s\n\tpid: " + "%i\n\txwayland: %i\n\tpinned: " "%i\n\tfullscreen: %i\n\tfullscreenmode: %i\n\tfakefullscreen: %i\n\tgrouped: %s\n\tswallowing: %x\n\n", - w, w->m_szTitle.c_str(), (int)w->m_vRealPosition.goalv().x, (int)w->m_vRealPosition.goalv().y, (int)w->m_vRealSize.goalv().x, (int)w->m_vRealSize.goalv().y, - w->m_iWorkspaceID, + w, w->m_szTitle.c_str(), (int)w->m_bIsMapped, (int)w->isHidden(), (int)w->m_vRealPosition.goalv().x, (int)w->m_vRealPosition.goalv().y, (int)w->m_vRealSize.goalv().x, + (int)w->m_vRealSize.goalv().y, w->m_iWorkspaceID, (w->m_iWorkspaceID == -1 ? "" : g_pCompositor->getWorkspaceByID(w->m_iWorkspaceID) ? g_pCompositor->getWorkspaceByID(w->m_iWorkspaceID)->m_szName.c_str() : std::string("Invalid workspace " + std::to_string(w->m_iWorkspaceID)).c_str()), @@ -159,9 +163,7 @@ std::string clientsRequest(HyprCtl::eHyprCtlOutputFormat format) { result += "["; for (auto& w : g_pCompositor->m_vWindows) { - if (w->m_bIsMapped) { - result += getWindowData(w.get(), format); - } + result += getWindowData(w.get(), format); } // remove trailing comma @@ -171,9 +173,7 @@ std::string clientsRequest(HyprCtl::eHyprCtlOutputFormat format) { result += "]"; } else { for (auto& w : g_pCompositor->m_vWindows) { - if (w->m_bIsMapped) { - result += getWindowData(w.get(), format); - } + result += getWindowData(w.get(), format); } } return result; @@ -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()).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 + +#include + +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> m_dNotifications; + + cairo_surface_t* m_pCairoSurface = nullptr; + cairo_t* m_pCairo = nullptr; + + CMonitor* m_pLastMonitor = nullptr; + + CTexture m_tTexture; +}; + +inline std::unique_ptr 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/Devices.cpp b/src/events/Devices.cpp index ddb9365a..5b1d6e4e 100644 --- a/src/events/Devices.cpp +++ b/src/events/Devices.cpp @@ -109,10 +109,8 @@ void Events::listener_newConstraint(wl_listener* listener, void* data) { if (g_pCompositor->m_pLastFocus == PCONSTRAINT->surface) { g_pInputManager->constrainMouse(CONSTRAINT->pMouse, PCONSTRAINT); - if (!CONSTRAINT->hintSet) { - const auto PWINDOW = g_pCompositor->getConstraintWindow(g_pCompositor->m_sSeat.mouse); + if (!CONSTRAINT->hintSet) CONSTRAINT->positionHint = Vector2D{-1, -1}; - } } } diff --git a/src/events/Events.hpp b/src/events/Events.hpp index 69b40d69..ae441fbb 100644 --- a/src/events/Events.hpp +++ b/src/events/Events.hpp @@ -58,6 +58,7 @@ namespace Events { DYNLISTENFUNC(requestResize); DYNLISTENFUNC(requestMinimize); DYNLISTENFUNC(requestMaximize); + DYNLISTENFUNC(setOverrideRedirect); // Window subsurfaces // LISTENER(newSubsurfaceWindow); diff --git a/src/events/Monitors.cpp b/src/events/Monitors.cpp index 799669a1..cafd77f8 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; } @@ -256,9 +260,10 @@ void Events::listener_monitorFrame(void* owner, void* data) { g_pHyprRenderer->renderAllClientsForMonitor(PMONITOR->ID, &now); - // if correct monitor draw hyprerror - if (PMONITOR == g_pCompositor->m_vMonitors.front().get()) + if (PMONITOR == g_pCompositor->m_pLastMonitor) { + g_pHyprNotificationOverlay->draw(PMONITOR); g_pHyprError->draw(); + } // for drawing the debug overlay if (PMONITOR == g_pCompositor->m_vMonitors.front().get() && *PDEBUGOVERLAY == 1) { @@ -306,12 +311,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::high_resolution_clock::now() - startRender).count() / 1000.f; g_pDebugOverlay->renderData(PMONITOR, µs); diff --git a/src/events/Windows.cpp b/src/events/Windows.cpp index 58273d96..52791d6c 100644 --- a/src/events/Windows.cpp +++ b/src/events/Windows.cpp @@ -446,7 +446,6 @@ void Events::listener_mapWindow(void* owner, void* data) { PWINDOW->hyprListener_fullscreenWindow.initCallback(&PWINDOW->m_uSurface.xwayland->events.request_fullscreen, &Events::listener_fullscreenWindow, PWINDOW, "XWayland Window Late"); PWINDOW->hyprListener_activateX11.initCallback(&PWINDOW->m_uSurface.xwayland->events.request_activate, &Events::listener_activateX11, PWINDOW, "XWayland Window Late"); - PWINDOW->hyprListener_configureX11.initCallback(&PWINDOW->m_uSurface.xwayland->events.request_configure, &Events::listener_configureX11, PWINDOW, "XWayland Window Late"); PWINDOW->hyprListener_setTitleWindow.initCallback(&PWINDOW->m_uSurface.xwayland->events.set_title, &Events::listener_setTitleWindow, PWINDOW, "XWayland Window Late"); PWINDOW->hyprListener_requestMinimize.initCallback(&PWINDOW->m_uSurface.xwayland->events.request_minimize, &Events::listener_requestMinimize, PWINDOW, "Xwayland Window Late"); @@ -596,7 +595,6 @@ void Events::listener_unmapWindow(void* owner, void* data) { Debug::log(LOG, "Unregistered late callbacks XWL"); PWINDOW->hyprListener_fullscreenWindow.removeCallback(); PWINDOW->hyprListener_activateX11.removeCallback(); - PWINDOW->hyprListener_configureX11.removeCallback(); PWINDOW->hyprListener_setTitleWindow.removeCallback(); PWINDOW->hyprListener_setGeometryX11U.removeCallback(); PWINDOW->hyprListener_requestMaximize.removeCallback(); @@ -889,26 +887,24 @@ void Events::listener_activateX11(void* owner, void* data) { } void Events::listener_configureX11(void* owner, void* data) { - CWindow* PWINDOW = (CWindow*)owner; - - if (!g_pCompositor->windowValidMapped(PWINDOW)) - return; + CWindow* PWINDOW = (CWindow*)owner; const auto E = (wlr_xwayland_surface_configure_event*)data; - g_pHyprRenderer->damageWindow(PWINDOW); - - if (!PWINDOW->m_bIsFloating || PWINDOW->m_bIsFullscreen) { - g_pXWaylandManager->setWindowSize(PWINDOW, PWINDOW->m_vRealSize.goalv(), true); - g_pInputManager->refocus(); - g_pHyprRenderer->damageWindow(PWINDOW); - return; - } if (!PWINDOW->m_uSurface.xwayland->mapped || !PWINDOW->m_bMappedX11) { wlr_xwayland_surface_configure(PWINDOW->m_uSurface.xwayland, E->x, E->y, E->width, E->height); return; } + g_pHyprRenderer->damageWindow(PWINDOW); + + if (!PWINDOW->m_bIsFloating || PWINDOW->m_bIsFullscreen || g_pInputManager->currentlyDraggedWindow == PWINDOW) { + g_pXWaylandManager->setWindowSize(PWINDOW, PWINDOW->m_vRealSize.goalv(), true); + g_pInputManager->refocus(); + g_pHyprRenderer->damageWindow(PWINDOW); + return; + } + if (E->width > 1 && E->height > 1) PWINDOW->setHidden(false); else @@ -974,6 +970,14 @@ void Events::listener_unmanagedSetGeometry(void* owner, void* data) { } } +void Events::listener_setOverrideRedirect(void* owner, void* data) { + const auto PWINDOW = (CWindow*)owner; + + if (!PWINDOW->m_bIsMapped && PWINDOW->m_uSurface.xwayland->mapped) { + Events::listener_mapWindow(PWINDOW, nullptr); + } +} + void Events::listener_surfaceXWayland(wl_listener* listener, void* data) { const auto XWSURFACE = (wlr_xwayland_surface*)data; @@ -993,6 +997,8 @@ void Events::listener_surfaceXWayland(wl_listener* listener, void* data) { PNEWWINDOW->hyprListener_mapWindow.initCallback(&XWSURFACE->events.map, &Events::listener_mapWindow, PNEWWINDOW, "XWayland Window"); PNEWWINDOW->hyprListener_unmapWindow.initCallback(&XWSURFACE->events.unmap, &Events::listener_unmapWindow, PNEWWINDOW, "XWayland Window"); PNEWWINDOW->hyprListener_destroyWindow.initCallback(&XWSURFACE->events.destroy, &Events::listener_destroyWindow, PNEWWINDOW, "XWayland Window"); + PNEWWINDOW->hyprListener_setOverrideRedirect.initCallback(&XWSURFACE->events.set_override_redirect, &Events::listener_setOverrideRedirect, PNEWWINDOW, "XWayland Window"); + PNEWWINDOW->hyprListener_configureX11.initCallback(&XWSURFACE->events.request_configure, &Events::listener_configureX11, PNEWWINDOW, "XWayland Window"); } void Events::listener_newXDGSurface(wl_listener* listener, void* data) { 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 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 mirrors; diff --git a/src/hyprerror/HyprError.cpp b/src/hyprerror/HyprError.cpp index 40f01aa3..ca0776ea 100644 --- a/src/hyprerror/HyprError.cpp +++ b/src/hyprerror/HyprError.cpp @@ -4,6 +4,22 @@ CHyprError::CHyprError() { m_fFadeOpacity.create(AVARTYPE_FLOAT, g_pConfigManager->getAnimationPropertyConfig("fadeIn"), nullptr, AVARDAMAGE_NONE); m_fFadeOpacity.registerVar(); + + g_pHookSystem->hookDynamic("focusedMon", [&](void* self, std::any param) { + if (!m_bIsCreated) + return; + + g_pHyprRenderer->damageMonitor(g_pCompositor->m_pLastMonitor); + m_bMonitorChanged = true; + }); + + g_pHookSystem->hookDynamic("preRender", [&](void* self, std::any param) { + if (!m_bIsCreated) + return; + + if (m_fFadeOpacity.isBeingAnimated() || m_bMonitorChanged) + g_pHyprRenderer->damageBox(&m_bDamageBox); + }); } CHyprError::~CHyprError() { @@ -54,7 +70,7 @@ void CHyprError::createQueued() { const double HEIGHT = (FONTSIZE + 2 * (FONTSIZE / 10.0)) * LINECOUNT + 3; const double RADIUS = PAD > HEIGHT / 2 ? HEIGHT / 2 - 1 : PAD; - m_bDamageBox = {(int)PMONITOR->vecPosition.x, (int)PMONITOR->vecPosition.y, (int)PMONITOR->vecPixelSize.x, (int)HEIGHT + (int)PAD * 2}; + m_bDamageBox = {0, 0, (int)PMONITOR->vecPixelSize.x, (int)HEIGHT + (int)PAD * 2}; cairo_new_sub_path(CAIRO); cairo_arc(CAIRO, X + WIDTH - RADIUS, Y + RADIUS, RADIUS, -90 * DEGREES, 0 * DEGREES); @@ -137,16 +153,18 @@ void CHyprError::draw() { } } - const auto PMONITOR = g_pCompositor->m_vMonitors.front().get(); + const auto PMONITOR = g_pHyprOpenGL->m_RenderData.pMonitor; - if (g_pHyprOpenGL->m_RenderData.pMonitor != PMONITOR) - return; // wrong mon + wlr_box monbox = {0, 0, PMONITOR->vecPixelSize.x, PMONITOR->vecPixelSize.y}; - wlr_box monbox = {0, 0, PMONITOR->vecPixelSize.x, PMONITOR->vecPixelSize.y}; + m_bDamageBox.x = (int)PMONITOR->vecPosition.x; + m_bDamageBox.y = (int)PMONITOR->vecPosition.y; - if (m_fFadeOpacity.isBeingAnimated()) + if (m_fFadeOpacity.isBeingAnimated() || m_bMonitorChanged) g_pHyprRenderer->damageBox(&m_bDamageBox); + m_bMonitorChanged = false; + g_pHyprOpenGL->renderTexture(m_tTexture, &monbox, m_fFadeOpacity.fl(), 0); } diff --git a/src/hyprerror/HyprError.hpp b/src/hyprerror/HyprError.hpp index 26f90e40..ff4e8577 100644 --- a/src/hyprerror/HyprError.hpp +++ b/src/hyprerror/HyprError.hpp @@ -24,6 +24,8 @@ class CHyprError { CTexture m_tTexture; CAnimatedVariable m_fFadeOpacity; wlr_box m_bDamageBox = {0, 0, 0, 0}; + + bool m_bMonitorChanged = false; }; inline std::unique_ptr g_pHyprError; // This is a full-screen error. Treat it with respect, and there can only be one at a time. \ No newline at end of file diff --git a/src/layout/DwindleLayout.cpp b/src/layout/DwindleLayout.cpp index 4777d31f..513969d5 100644 --- a/src/layout/DwindleLayout.cpp +++ b/src/layout/DwindleLayout.cpp @@ -179,6 +179,10 @@ void CHyprDwindleLayout::applyNodeDataToWindow(SDwindleNodeData* pNode, bool for } } + const auto RESERVED = PWINDOW->getFullWindowReservedArea(); + calcPos = calcPos + RESERVED.topLeft; + calcSize = calcSize - (RESERVED.topLeft + RESERVED.bottomRight); + if (g_pCompositor->isWorkspaceSpecial(PWINDOW->m_iWorkspaceID)) { // if special, we adjust the coords a bit static auto* const PSCALEFACTOR = &g_pConfigManager->getConfigValuePtr("dwindle:special_scale_factor")->floatValue; diff --git a/src/layout/IHyprLayout.cpp b/src/layout/IHyprLayout.cpp index 75f95682..6a1ff572 100644 --- a/src/layout/IHyprLayout.cpp +++ b/src/layout/IHyprLayout.cpp @@ -272,7 +272,8 @@ void IHyprLayout::onMouseMove(const Vector2D& mousePos) { const auto DELTA = Vector2D(mousePos.x - m_vBeginDragXY.x, mousePos.y - m_vBeginDragXY.y); const auto TICKDELTA = Vector2D(mousePos.x - m_vLastDragXY.x, mousePos.y - m_vLastDragXY.y); - const auto PANIMATE = &g_pConfigManager->getConfigValuePtr("misc:animate_manual_resizes")->intValue; + const auto PANIMATEMOUSE = &g_pConfigManager->getConfigValuePtr("misc:animate_mouse_windowdragging")->intValue; + const auto PANIMATE = &g_pConfigManager->getConfigValuePtr("misc:animate_manual_resizes")->intValue; if (abs(TICKDELTA.x) < 1.f && abs(TICKDELTA.y) < 1.f) return; @@ -283,7 +284,7 @@ void IHyprLayout::onMouseMove(const Vector2D& mousePos) { if (g_pInputManager->dragMode == MBIND_MOVE) { - if (*PANIMATE) { + if (*PANIMATEMOUSE) { DRAGGINGWINDOW->m_vRealPosition = m_vBeginDragPositionXY + DELTA; } else { DRAGGINGWINDOW->m_vRealPosition.setValueAndWarp(m_vBeginDragPositionXY + DELTA); diff --git a/src/layout/MasterLayout.cpp b/src/layout/MasterLayout.cpp index 2dcd40c1..b757c281 100644 --- a/src/layout/MasterLayout.cpp +++ b/src/layout/MasterLayout.cpp @@ -264,7 +264,7 @@ void CHyprMasterLayout::calculateWorkspace(const int& ws) { if (getNodesOnWorkspace(PWORKSPACE->m_iID) < 2 && !(ORIENTATION_CENTER == orientation)) { PMASTERNODE->position = PMONITOR->vecReservedTopLeft + PMONITOR->vecPosition; PMASTERNODE->size = Vector2D(PMONITOR->vecSize.x - PMONITOR->vecReservedTopLeft.x - PMONITOR->vecReservedBottomRight.x, - PMONITOR->vecSize.y - PMONITOR->vecReservedBottomRight.y - PMONITOR->vecReservedTopLeft.y); + PMONITOR->vecSize.y - PMONITOR->vecReservedBottomRight.y - PMONITOR->vecReservedTopLeft.y); applyNodeDataToWindow(PMASTERNODE); return; } else if (orientation == ORIENTATION_LEFT || orientation == ORIENTATION_RIGHT) { @@ -512,6 +512,10 @@ void CHyprMasterLayout::applyNodeDataToWindow(SMasterNodeData* pNode) { calcPos = calcPos + OFFSETTOPLEFT; calcSize = calcSize - OFFSETTOPLEFT - OFFSETBOTTOMRIGHT; + const auto RESERVED = PWINDOW->getFullWindowReservedArea(); + calcPos = calcPos + RESERVED.topLeft; + calcSize = calcSize - (RESERVED.topLeft + RESERVED.bottomRight); + if (g_pCompositor->isWorkspaceSpecial(PWINDOW->m_iWorkspaceID)) { static auto* const PSCALEFACTOR = &g_pConfigManager->getConfigValuePtr("master:special_scale_factor")->floatValue; 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* callbacks, std::any data) { +void CHookSystemManager::emit(const std::vector* callbacks, std::any data) { if (callbacks->empty()) return; - for (auto& cb : *callbacks) - (*cb)(cb, data); + std::vector 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* CHookSystemManager::getVecForEvent(const std::string& event) { +std::vector* 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{})).second; + return &m_lpRegisteredHooks.emplace_back(std::make_pair<>(event, std::vector{})).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 #include +#include #include +#include + +#include "../plugins/PluginAPI.hpp" + // global typedef for hooked functions. Passes itself as a ptr when called, and `data` additionally. typedef std::function 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* callbacks, std::any data = 0); - std::vector* getVecForEvent(const std::string& event); + void emit(const std::vector* callbacks, std::any data = 0); + std::vector* 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>> m_lpRegisteredHooks; - std::list m_lCallbackFunctions; + std::list>> m_lpRegisteredHooks; + std::list m_lCallbackFunctions; }; inline std::unique_ptr g_pHookSystem; \ No newline at end of file diff --git a/src/managers/KeybindManager.cpp b/src/managers/KeybindManager.cpp index 339a3016..f97347f6 100644 --- a/src/managers/KeybindManager.cpp +++ b/src/managers/KeybindManager.cpp @@ -1770,6 +1770,11 @@ void CKeybindManager::focusWindow(std::string regexp) { Debug::log(LOG, "Focusing to window name: %s", PWINDOW->m_szTitle.c_str()); + if (PWINDOW->isHidden() && PWINDOW->m_sGroupData.pNextWindow) { + // grouped, change the current to us + PWINDOW->setGroupCurrent(PWINDOW); + } + g_pCompositor->focusWindow(PWINDOW); const auto MIDPOINT = PWINDOW->m_vRealPosition.goalv() + PWINDOW->m_vRealSize.goalv() / 2.f; 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> m_vLayouts; }; inline std::unique_ptr g_pLayoutManager; \ No newline at end of file diff --git a/src/managers/XWaylandManager.cpp b/src/managers/XWaylandManager.cpp index d67441c7..60b339b3 100644 --- a/src/managers/XWaylandManager.cpp +++ b/src/managers/XWaylandManager.cpp @@ -136,7 +136,7 @@ void CHyprXWaylandManager::sendCloseWindow(CWindow* pWindow) { } } -void CHyprXWaylandManager::setWindowSize(CWindow* pWindow, const Vector2D& size, bool force) { +void CHyprXWaylandManager::setWindowSize(CWindow* pWindow, Vector2D size, bool force) { if (!force && ((pWindow->m_vReportedSize == size && pWindow->m_vRealPosition.vec() == pWindow->m_vReportedPosition) || (pWindow->m_vReportedSize == size && !pWindow->m_bIsX11))) diff --git a/src/managers/XWaylandManager.hpp b/src/managers/XWaylandManager.hpp index cb3daaf5..bb4fd640 100644 --- a/src/managers/XWaylandManager.hpp +++ b/src/managers/XWaylandManager.hpp @@ -17,7 +17,7 @@ class CHyprXWaylandManager { std::string getTitle(CWindow*); std::string getAppIDClass(CWindow*); void sendCloseWindow(CWindow*); - void setWindowSize(CWindow*, const Vector2D&, bool force = false); + void setWindowSize(CWindow*, Vector2D, bool force = false); void setWindowStyleTiled(CWindow*, uint32_t); void setWindowFullscreen(CWindow*, bool); wlr_surface* surfaceAt(CWindow*, const Vector2D&, Vector2D&); diff --git a/src/managers/input/InputManager.cpp b/src/managers/input/InputManager.cpp index e221651a..f20aed29 100644 --- a/src/managers/input/InputManager.cpp +++ b/src/managers/input/InputManager.cpp @@ -69,11 +69,6 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { if (!g_pCompositor->m_bReadyToProcess || g_pCompositor->m_bIsShuttingDown) return; - if (!g_pCompositor->m_sSeat.mouse) { - Debug::log(ERR, "BUG THIS: Mouse move on mouse nullptr!"); - return; - } - if (!g_pCompositor->m_bDPMSStateON && *PMOUSEDPMS) { // enable dpms g_pKeybindManager->dpms("on"); @@ -85,13 +80,15 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { if (MOUSECOORDSFLOORED == m_vLastCursorPosFloored && !refocus) return; + EMIT_HOOK_EVENT("mouseMove", MOUSECOORDSFLOORED); + m_vLastCursorPosFloored = MOUSECOORDSFLOORED; const auto PMONITOR = g_pCompositor->getMonitorFromCursor(); // constraints // All constraints TODO: multiple mice? - if (g_pCompositor->m_sSeat.mouse->currentConstraint && !g_pCompositor->m_sSeat.exclusiveClient && !g_pSessionLockManager->isSessionLocked()) { + if (g_pCompositor->m_sSeat.mouse && g_pCompositor->m_sSeat.mouse->currentConstraint && !g_pCompositor->m_sSeat.exclusiveClient && !g_pSessionLockManager->isSessionLocked()) { // XWayland windows sometimes issue constraints weirdly. // TODO: We probably should search their parent. wlr_xwayland_surface->parent const auto CONSTRAINTWINDOW = g_pCompositor->getConstraintWindow(g_pCompositor->m_sSeat.mouse); @@ -311,6 +308,18 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { setCursorIconOnBorder(pFoundWindow); } + // if we're on an input deco, reset cursor. Don't on overriden + if (!m_bCursorImageOverriden) { + if (!VECINRECT(m_vLastCursorPosFloored, pFoundWindow->m_vRealPosition.vec().x, pFoundWindow->m_vRealPosition.vec().y, + pFoundWindow->m_vRealPosition.vec().x + pFoundWindow->m_vRealSize.vec().x, pFoundWindow->m_vRealPosition.vec().y + pFoundWindow->m_vRealSize.vec().y)) { + wlr_xcursor_manager_set_cursor_image(g_pCompositor->m_sWLRXCursorMgr, "left_ptr", g_pCompositor->m_sWLRCursor); + cursorSurfaceInfo.bUsed = false; + } else if (!cursorSurfaceInfo.bUsed) { + cursorSurfaceInfo.bUsed = true; + wlr_cursor_set_surface(g_pCompositor->m_sWLRCursor, cursorSurfaceInfo.pSurface, cursorSurfaceInfo.vHotspot.x, cursorSurfaceInfo.vHotspot.y); + } + } + if (*PFOLLOWMOUSE != 1 && !refocus) { if (pFoundWindow != g_pCompositor->m_pLastWindow && g_pCompositor->m_pLastWindow && ((pFoundWindow->m_bIsFloating && *PFLOATBEHAVIOR == 2) || (g_pCompositor->m_pLastWindow->m_bIsFloating != pFoundWindow->m_bIsFloating && *PFLOATBEHAVIOR != 0))) { @@ -365,6 +374,8 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { void CInputManager::onMouseButton(wlr_pointer_button_event* e) { wlr_idle_notify_activity(g_pCompositor->m_sWLRIdle, g_pCompositor->m_sSeat.seat); + EMIT_HOOK_EVENT("mouseButton", e); + m_tmrLastCursorMovement.reset(); if (e->state == WLR_BUTTON_PRESSED) { @@ -401,6 +412,15 @@ void CInputManager::processMouseRequest(wlr_seat_pointer_request_set_cursor_even return; } + cursorSurfaceInfo.pSurface = e->surface; + + if (e->surface) { + hyprListener_CursorSurfaceDestroy.removeCallback(); + hyprListener_CursorSurfaceDestroy.initCallback( + &e->surface->events.destroy, [&](void* owner, void* data) { cursorSurfaceInfo.pSurface = nullptr; }, this, "InputManager"); + cursorSurfaceInfo.vHotspot = {e->hotspot_x, e->hotspot_y}; + } + if (e->seat_client == g_pCompositor->m_sSeat.seat->pointer_state.focused_client) wlr_cursor_set_surface(g_pCompositor->m_sWLRCursor, e->surface, e->hotspot_x, e->hotspot_y); } @@ -462,7 +482,7 @@ void CInputManager::processMouseDownNormal(wlr_pointer_button_event* e) { if (*PFOLLOWMOUSE == 3) // don't refocus on full loose break; - if (!g_pCompositor->m_sSeat.mouse->currentConstraint) + if (!g_pCompositor->m_sSeat.mouse || !g_pCompositor->m_sSeat.mouse->currentConstraint) refocus(); // if clicked on a floating window make it top @@ -1094,7 +1114,7 @@ void CInputManager::constrainMouse(SMouse* pMouse, wlr_pointer_constraint_v1* co } void CInputManager::unconstrainMouse() { - if (!g_pCompositor->m_sSeat.mouse->currentConstraint) + if (!g_pCompositor->m_sSeat.mouse || !g_pCompositor->m_sSeat.mouse->currentConstraint) return; const auto CONSTRAINTWINDOW = g_pCompositor->getConstraintWindow(g_pCompositor->m_sSeat.mouse); diff --git a/src/managers/input/InputManager.hpp b/src/managers/input/InputManager.hpp index af31118c..797dea92 100644 --- a/src/managers/input/InputManager.hpp +++ b/src/managers/input/InputManager.hpp @@ -7,18 +7,21 @@ #include "../../helpers/Timer.hpp" #include "InputMethodRelay.hpp" -enum eClickBehaviorMode { +enum eClickBehaviorMode +{ CLICKMODE_DEFAULT = 0, CLICKMODE_KILL }; -enum eMouseBindMode { +enum eMouseBindMode +{ MBIND_INVALID = -1, MBIND_MOVE = 0, MBIND_RESIZE }; -enum eBorderIconDirection { +enum eBorderIconDirection +{ BORDERICON_NONE, BORDERICON_UP, BORDERICON_DOWN, @@ -210,6 +213,14 @@ class CInputManager { void setBorderCursorIcon(eBorderIconDirection); void setCursorIconOnBorder(CWindow* w); + // cursor surface + struct cursorSI { + wlr_surface* pSurface = nullptr; + Vector2D vHotspot; + bool bUsed = false; + } cursorSurfaceInfo; + DYNLISTENER(CursorSurfaceDestroy); + friend class CKeybindManager; }; diff --git a/src/managers/input/Touch.cpp b/src/managers/input/Touch.cpp index b69b4f7b..70bae3b2 100644 --- a/src/managers/input/Touch.cpp +++ b/src/managers/input/Touch.cpp @@ -15,6 +15,13 @@ void CInputManager::onTouchDown(wlr_touch_down_event* e) { refocus(); + if (m_ecbClickBehavior == CLICKMODE_KILL) { + wlr_pointer_button_event e; + e.state = WLR_BUTTON_PRESSED; + g_pInputManager->processMouseDownKill(&e); + return; + } + m_bLastInputTouch = true; m_sTouchData.touchFocusWindow = m_pFoundWindowToFocus; 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..afae69a4 --- /dev/null +++ b/src/plugins/HookSystem.cpp @@ -0,0 +1,191 @@ +#include "HookSystem.hpp" + +#define register +#include +#undef register +#include +#include +#include + +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 CFunctionHook::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++; + } + + // check for RIP refs + std::string ins; + if (const auto CINS = ud_insn_asm(&udis); CINS) + ins = std::string(CINS); + + if (!ins.empty() && ins.find("rip") != std::string::npos) { + // todo: support something besides call qword ptr [rip + 0xdeadbeef] + // I don't have an assembler. I don't think udis provides one. Besides, variables might be tricky. + if (((uint8_t*)start)[0] == 0xFF && ((uint8_t*)start)[1] == 0x15) + m_vTrampolineRIPUses.emplace_back(std::make_pair<>((uint64_t)start - (uint64_t)m_pSource, ins)); + } + + return insSize; +} + +size_t CFunctionHook::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 + // offset for addr: 2 + static constexpr uint8_t ABSOLUTE_JMP_ADDRESS[] = {0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0}; + static constexpr size_t ABSOLUTE_JMP_ADDRESS_OFFSET = 2; + // 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; + /* + movabs $0,%rax + callq *%rax + + offset for addr: 3 + */ + static constexpr uint8_t CALL_WITH_RAX[] = {0x48, 0xB8, 0xEF, 0xBE, 0xAD, 0xDE, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x10}; + static constexpr size_t CALL_WITH_RAX_ADDRESS_OFFSET = 2; + + // get minimum size to overwrite + const auto HOOKSIZE = probeMinimumJumpSize(m_pSource, sizeof(ABSOLUTE_JMP_ADDRESS) + sizeof(PUSH_RAX) + sizeof(POP_RAX)); + + // alloc trampoline + const auto TRAMPOLINE_SIZE = sizeof(ABSOLUTE_JMP_ADDRESS) + HOOKSIZE + sizeof(PUSH_RAX) + m_vTrampolineRIPUses.size() * (sizeof(CALL_WITH_RAX) - 6); + m_pTrampolineAddr = mmap(NULL, TRAMPOLINE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + m_pOriginalBytes = malloc(HOOKSIZE); + memcpy(m_pOriginalBytes, m_pSource, HOOKSIZE); + + // 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 + + // fix trampoline %rip calls + for (size_t i = 0; i < m_vTrampolineRIPUses.size(); ++i) { + size_t callOffset = i * (sizeof(CALL_WITH_RAX) - 6 /* callq [rip + x] */) + m_vTrampolineRIPUses[i].first; + size_t realCallAddress = (uint64_t)m_pSource + callOffset + 6 + *((uint32_t*)(m_pSource + callOffset + 2)); + + memmove(m_pTrampolineAddr + callOffset + sizeof(CALL_WITH_RAX), m_pTrampolineAddr + callOffset + 6, TRAMPOLINE_SIZE - callOffset - 6); + memcpy(m_pTrampolineAddr + callOffset, CALL_WITH_RAX, sizeof(CALL_WITH_RAX)); + + *(uint64_t*)(m_pTrampolineAddr + callOffset + CALL_WITH_RAX_ADDRESS_OFFSET) = (uint64_t)realCallAddress; + } + + // fixup trampoline addr + *(uint64_t*)(m_pTrampolineAddr + TRAMPOLINE_SIZE - sizeof(ABSOLUTE_JMP_ADDRESS) + ABSOLUTE_JMP_ADDRESS_OFFSET) = (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 + ABSOLUTE_JMP_ADDRESS_OFFSET) = (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 = TRAMPOLINE_SIZE; + + 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_pOriginalBytes, 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; + m_pOriginalBytes = nullptr; + + free(m_pOriginalBytes); + + return true; +} + +CFunctionHook* CHookSystem::initHook(HANDLE owner, void* source, void* destination) { + return m_vHooks.emplace_back(std::make_unique(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..b871a379 --- /dev/null +++ b/src/plugins/HookSystem.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include + +#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; + + std::vector> m_vTrampolineRIPUses; + + void* m_pOriginalBytes = nullptr; + + size_t probeMinimumJumpSize(void* start, size_t min); + size_t getInstructionLenAt(void* start); + + 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> m_vHooks; +}; + +inline std::unique_ptr 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 + +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 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 +#include +#include + +typedef std::function 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 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..73de9bf5 --- /dev/null +++ b/src/plugins/PluginSystem.cpp @@ -0,0 +1,145 @@ +#include "PluginSystem.hpp" + +#include +#include +#include "../Compositor.hpp" + +CPluginSystem::CPluginSystem() { + g_pFunctionHookSystem = std::make_unique(); +} + +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()).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; }); +} + +void CPluginSystem::unloadAllPlugins() { + for (auto& p : m_vLoadedPlugins | std::views::reverse) + unloadPlugin(p.get(), false); // Unload remaining plugins gracefully +} + +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 CPluginSystem::getAllPlugins() { + std::vector results(m_vLoadedPlugins.size()); + for (size_t i = 0; i < m_vLoadedPlugins.size(); ++i) + results[i] = m_vLoadedPlugins[i].get(); + return results; +} diff --git a/src/plugins/PluginSystem.hpp b/src/plugins/PluginSystem.hpp new file mode 100644 index 00000000..5d1a1532 --- /dev/null +++ b/src/plugins/PluginSystem.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include "../defines.hpp" +#include "PluginAPI.hpp" +#include + +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 registeredLayouts; + std::vector registeredDecorations; + std::vector> registeredCallbacks; + std::vector registeredDispatchers; +}; + +class CPluginSystem { + public: + CPluginSystem(); + + CPlugin* loadPlugin(const std::string& path); + void unloadPlugin(const CPlugin* plugin, bool eject = false); + void unloadAllPlugins(); + CPlugin* getPluginByPath(const std::string& path); + CPlugin* getPluginByHandle(HANDLE handle); + std::vector getAllPlugins(); + + bool m_bAllowConfigVars = false; + + private: + std::vector> m_vLoadedPlugins; + + jmp_buf m_jbPluginFaultJumpBuf; +}; + +inline std::unique_ptr g_pPluginSystem; diff --git a/src/render/decorations/IHyprWindowDecoration.cpp b/src/render/decorations/IHyprWindowDecoration.cpp index 1271a712..3ad5591f 100644 --- a/src/render/decorations/IHyprWindowDecoration.cpp +++ b/src/render/decorations/IHyprWindowDecoration.cpp @@ -3,3 +3,11 @@ #include "../../Window.hpp" IHyprWindowDecoration::~IHyprWindowDecoration() {} + +SWindowDecorationExtents IHyprWindowDecoration::getWindowDecorationReservedArea() { + return SWindowDecorationExtents{}; +} + +bool IHyprWindowDecoration::allowsInput() { + return false; +} \ No newline at end of file diff --git a/src/render/decorations/IHyprWindowDecoration.hpp b/src/render/decorations/IHyprWindowDecoration.hpp index 093b99cf..a30d63b3 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 { @@ -30,4 +31,8 @@ interface IHyprWindowDecoration { virtual void updateWindow(CWindow*) = 0; virtual void damageEntire() = 0; + + virtual SWindowDecorationExtents getWindowDecorationReservedArea(); + + virtual bool allowsInput(); }; \ No newline at end of file diff --git a/subprojects/udis86 b/subprojects/udis86 new file mode 160000 index 00000000..5336633a --- /dev/null +++ b/subprojects/udis86 @@ -0,0 +1 @@ +Subproject commit 5336633af70f3917760a6d441ff02d93477b0c86