From 8b81f41e52b55835aaaa7e4962af23a00188cbdc Mon Sep 17 00:00:00 2001 From: Vaxry <43317083+vaxerski@users.noreply.github.com> Date: Mon, 27 Feb 2023 12:32:38 +0000 Subject: [PATCH 01/26] Plugin System (#1590) --------- Co-authored-by: Mihai Fufezan --- .github/workflows/ci.yaml | 4 +- .gitignore | 1 + .gitmodules | 3 + CMakeLists.txt | 4 +- Makefile | 13 ++ example/examplePlugin/Makefile | 8 + example/examplePlugin/customDecoration.cpp | 74 ++++++ example/examplePlugin/customDecoration.hpp | 29 +++ example/examplePlugin/customLayout.cpp | 80 +++++++ example/examplePlugin/customLayout.hpp | 32 +++ example/examplePlugin/globals.hpp | 5 + example/examplePlugin/main.cpp | 92 ++++++++ flake.nix | 3 + hyprctl/main.cpp | 3 + meson.build | 4 + nix/default.nix | 2 + nix/meson-build.patch | 44 ++-- nix/udis86.nix | 32 +++ src/Compositor.cpp | 15 ++ src/Compositor.hpp | 2 + src/config/ConfigManager.cpp | 41 +++- src/config/ConfigManager.hpp | 43 ++-- src/debug/CrashReporter.cpp | 13 ++ src/debug/HyprCtl.cpp | 50 ++++ src/debug/HyprCtl.hpp | 6 +- src/debug/HyprNotificationOverlay.cpp | 155 +++++++++++++ src/debug/HyprNotificationOverlay.hpp | 40 ++++ src/defines.hpp | 2 +- src/events/Monitors.cpp | 13 +- src/helpers/Color.hpp | 4 +- src/helpers/Monitor.hpp | 3 + src/managers/HookSystemManager.cpp | 57 ++++- src/managers/HookSystemManager.hpp | 27 ++- src/managers/KeybindManager.hpp | 1 + src/managers/LayoutManager.cpp | 58 +++-- src/managers/LayoutManager.hpp | 16 +- src/meson.build | 1 + src/plugins/HookSystem.cpp | 151 ++++++++++++ src/plugins/HookSystem.hpp | 48 ++++ src/plugins/PluginAPI.cpp | 193 ++++++++++++++++ src/plugins/PluginAPI.hpp | 217 ++++++++++++++++++ src/plugins/PluginSystem.cpp | 139 +++++++++++ src/plugins/PluginSystem.hpp | 44 ++++ .../decorations/IHyprWindowDecoration.hpp | 3 +- subprojects/udis86 | 1 + 45 files changed, 1691 insertions(+), 85 deletions(-) create mode 100644 example/examplePlugin/Makefile create mode 100644 example/examplePlugin/customDecoration.cpp create mode 100644 example/examplePlugin/customDecoration.hpp create mode 100644 example/examplePlugin/customLayout.cpp create mode 100644 example/examplePlugin/customLayout.hpp create mode 100644 example/examplePlugin/globals.hpp create mode 100644 example/examplePlugin/main.cpp create mode 100644 nix/udis86.nix create mode 100644 src/debug/HyprNotificationOverlay.cpp create mode 100644 src/debug/HyprNotificationOverlay.hpp create mode 100644 src/plugins/HookSystem.cpp create mode 100644 src/plugins/HookSystem.hpp create mode 100644 src/plugins/PluginAPI.cpp create mode 100644 src/plugins/PluginAPI.hpp create mode 100644 src/plugins/PluginSystem.cpp create mode 100644 src/plugins/PluginSystem.hpp create mode 160000 subprojects/udis86 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 68154929..d5b9d368 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -12,7 +12,7 @@ jobs: run: | sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf pacman --noconfirm --noprogressbar -Syyu - pacman --noconfirm --noprogressbar -Sy glslang libepoxy libfontenc libxcvt libxfont2 libxkbfile vulkan-headers vulkan-validation-layers xcb-util-errors xcb-util-renderutil xcb-util-wm xorg-fonts-encodings xorg-server-common xorg-setxkbmap xorg-xkbcomp xorg-xwayland git cmake go clang lld libc++ pkgconf meson ninja wayland wayland-protocols libinput libxkbcommon pixman glm libdrm libglvnd cairo pango systemd scdoc base-devel seatd + pacman --noconfirm --noprogressbar -Sy glslang libepoxy libfontenc libxcvt libxfont2 libxkbfile vulkan-headers vulkan-validation-layers xcb-util-errors xcb-util-renderutil xcb-util-wm xorg-fonts-encodings xorg-server-common xorg-setxkbmap xorg-xkbcomp xorg-xwayland git cmake go clang lld libc++ pkgconf meson ninja wayland wayland-protocols libinput libxkbcommon pixman glm libdrm libglvnd cairo pango systemd scdoc base-devel seatd python - name: Set up user run: | useradd -m githubuser @@ -62,7 +62,7 @@ jobs: run: | sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf pacman --noconfirm --noprogressbar -Syyu - pacman --noconfirm --noprogressbar -Sy glslang libepoxy libfontenc libxcvt libxfont2 libxkbfile vulkan-headers vulkan-validation-layers git go clang lld libc++ pkgconf meson ninja wayland wayland-protocols libinput libxkbcommon pixman glm libdrm libglvnd cairo pango systemd scdoc base-devel seatd cmake jq + pacman --noconfirm --noprogressbar -Sy glslang libepoxy libfontenc libxcvt libxfont2 libxkbfile vulkan-headers vulkan-validation-layers git go clang lld libc++ pkgconf meson ninja wayland wayland-protocols libinput libxkbcommon pixman glm libdrm libglvnd cairo pango systemd scdoc base-devel seatd cmake jq python - name: Checkout Hyprland uses: actions/checkout@v3 with: diff --git a/.gitignore b/.gitignore index 95a1b6f0..96fd6c87 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ result* *-protocol.c *-protocol.h .ccls-cache +*.so hyprctl/hyprctl diff --git a/.gitmodules b/.gitmodules index ed443f60..26dbceeb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "subprojects/hyprland-protocols"] path = subprojects/hyprland-protocols url = https://github.com/hyprwm/hyprland-protocols +[submodule "subprojects/udis86"] + path = subprojects/udis86 + url = https://github.com/canihavesomecoffee/udis86 diff --git a/CMakeLists.txt b/CMakeLists.txt index 6eec94b0..a5ea06a6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,9 +52,10 @@ ENDIF(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) include_directories(. PRIVATE "subprojects/wlroots/include/") include_directories(. PRIVATE "subprojects/wlroots/build/include/") +include_directories(. PRIVATE "subprojects/udis86/") set(CMAKE_CXX_STANDARD 23) add_compile_options(-DWLR_USE_UNSTABLE) -add_compile_options(-Wall -Wextra -Wno-unused-parameter -Wno-unused-value -Wno-missing-field-initializers -Wno-narrowing) +add_compile_options(-Wall -Wextra -Wno-unused-parameter -Wno-unused-value -Wno-missing-field-initializers -Wno-narrowing -Wno-pointer-arith) ADD_LINK_OPTIONS( -rdynamic ) SET(CMAKE_ENABLE_EXPORTS TRUE) @@ -139,4 +140,5 @@ target_link_libraries(Hyprland ${CMAKE_SOURCE_DIR}/wlr-foreign-toplevel-management-unstable-v1-protocol.o ${CMAKE_SOURCE_DIR}/hyprland-toplevel-export-v1-protocol.o ${CMAKE_SOURCE_DIR}/fractional-scale-v1-protocol.o + ${CMAKE_SOURCE_DIR}/subprojects/udis86/build/libudis86/liblibudis86.a ) diff --git a/Makefile b/Makefile index 47a27d00..6b375912 100644 --- a/Makefile +++ b/Makefile @@ -157,6 +157,7 @@ all: make clear make fixwlr cd ./subprojects/wlroots && meson build/ --buildtype=release && ninja -C build/ && cp ./build/libwlroots.so.12032 /usr/lib/ && cd ../.. + cd subprojects/udis86 && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -H./ -B./build -G Ninja && cmake --build ./build --config Release --target all -j$(shell nproc) make protocols make release cd hyprctl && make all && cd .. @@ -211,6 +212,18 @@ config: cd subprojects/wlroots && ninja -C build/ install + cd subprojects/udis86 && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -H./ -B./build -G Ninja && cmake --build ./build --config Release --target all -j$(shell nproc) + +pluginenv: + make protocols + + cd subprojects/udis86 && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -H./ -B./build -G Ninja && cmake --build ./build --config Release --target all -j$(shell nproc) + + make fixwlr + + cd subprojects/wlroots && meson ./build --prefix=/usr --buildtype=release -Dwerror=false -Dexamples=false + cd subprojects/wlroots && ninja -C build/ + configdebug: make protocols diff --git a/example/examplePlugin/Makefile b/example/examplePlugin/Makefile new file mode 100644 index 00000000..3ccc2930 --- /dev/null +++ b/example/examplePlugin/Makefile @@ -0,0 +1,8 @@ +# compile with HYPRLAND_HEADERS= 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 8ab741da..a7d48a15 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -18,6 +18,12 @@ int handleCritSignal(int signo, void* data) { } void handleSegv(int sig) { + + if (g_pHookSystem->m_bCurrentEventPlugin) { + longjmp(g_pHookSystem->m_jbHookFaultJumpBuf, 1); + return; + } + CrashReporter::createAndSaveCrash(); abort(); } @@ -364,6 +370,12 @@ void CCompositor::startCompositor() { Debug::log(LOG, "Creating the HyprDebugOverlay!"); g_pDebugOverlay = std::make_unique(); + + Debug::log(LOG, "Creating the HyprNotificationOverlay!"); + g_pHyprNotificationOverlay = std::make_unique(); + + Debug::log(LOG, "Creating the PluginSystem!"); + g_pPluginSystem = std::make_unique(); // // @@ -2012,6 +2024,9 @@ void CCompositor::scheduleFrameForMonitor(CMonitor* pMonitor) { if (!pMonitor->m_bEnabled) return; + if (pMonitor->renderingActive) + pMonitor->pendingFrame = true; + wlr_output_schedule_frame(pMonitor->output); } diff --git a/src/Compositor.hpp b/src/Compositor.hpp index 8008acc3..534f2487 100644 --- a/src/Compositor.hpp +++ b/src/Compositor.hpp @@ -19,12 +19,14 @@ #include "managers/SessionLockManager.hpp" #include "managers/HookSystemManager.hpp" #include "debug/HyprDebugOverlay.hpp" +#include "debug/HyprNotificationOverlay.hpp" #include "helpers/Monitor.hpp" #include "helpers/Workspace.hpp" #include "Window.hpp" #include "render/Renderer.hpp" #include "render/OpenGL.hpp" #include "hyprerror/HyprError.hpp" +#include "plugins/PluginSystem.hpp" class CCompositor { public: diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index c2d8a3ca..0931a6f8 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -271,7 +271,7 @@ void CConfigManager::init() { void CConfigManager::configSetValueSafe(const std::string& COMMAND, const std::string& VALUE) { if (configValues.find(COMMAND) == configValues.end()) { - if (COMMAND.find("device:") != 0 /* devices parsed later */) { + if (COMMAND.find("device:") != 0 /* devices parsed later */ && COMMAND.find("plugin:") != 0 /* plugins parsed later */) { if (COMMAND[0] == '$') { // register a dynamic var Debug::log(LOG, "Registered dynamic var \"%s\" -> %s", COMMAND.c_str(), VALUE.c_str()); @@ -306,6 +306,18 @@ void CConfigManager::configSetValueSafe(const std::string& COMMAND, const std::s } CONFIGENTRY = &it->second.at(CONFIGVAR); + } else if (COMMAND.find("plugin:") == 0) { + for (auto& [handle, pMap] : pluginConfigs) { + auto it = std::find_if(pMap->begin(), pMap->end(), [&](const auto& other) { return other.first == COMMAND; }); + if (it == pMap->end()) { + return; // plugin vars do not err, so we silently ignore. + } + + CONFIGENTRY = &it->second; + } + + if (!CONFIGENTRY) + return; // silent ignore } else { CONFIGENTRY = &configValues.at(COMMAND); } @@ -1649,8 +1661,17 @@ SConfigValue* CConfigManager::getConfigValuePtr(const std::string& val) { SConfigValue* CConfigManager::getConfigValuePtrSafe(const std::string& val) { const auto IT = configValues.find(val); - if (IT == configValues.end()) + if (IT == configValues.end()) { + // maybe plugin + for (auto& [pl, pMap] : pluginConfigs) { + const auto PLIT = pMap->find(val); + + if (PLIT != pMap->end()) + return &PLIT->second; + } + return nullptr; + } return &(IT->second); } @@ -1800,3 +1821,19 @@ ICustomConfigValueData::~ICustomConfigValueData() { std::unordered_map 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; }); +} \ No newline at end of file diff --git a/src/config/ConfigManager.hpp b/src/config/ConfigManager.hpp index e246d506..742f9fe8 100644 --- a/src/config/ConfigManager.hpp +++ b/src/config/ConfigManager.hpp @@ -21,6 +21,8 @@ #define INITANIMCFG(name) animationConfig[name] = {} #define CREATEANIMCFG(name, parent) animationConfig[name] = {false, "", "", 0.f, -1, &animationConfig["global"], &animationConfig[parent]} +#define HANDLE void* + struct SConfigValue { int64_t intValue = -INT64_MAX; float floatValue = -__FLT_MAX__; @@ -159,6 +161,9 @@ class CConfigManager { std::unordered_map 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..f6ed285b 100644 --- a/src/debug/HyprCtl.cpp +++ b/src/debug/HyprCtl.cpp @@ -1087,6 +1087,50 @@ std::string dispatchOutput(std::string request) { return "ok"; } +std::string dispatchPlugin(std::string request) { + CVarList vars(request, 0, ' '); + + if (vars.size() < 2) + return "not enough args"; + + const auto OPERATION = vars[1]; + const auto PATH = vars[2]; + + if (OPERATION == "load") { + if (vars.size() < 3) + return "not enough args"; + + const auto PLUGIN = g_pPluginSystem->loadPlugin(PATH); + + if (!PLUGIN) + return "error in loading plugin"; + } else if (OPERATION == "unload") { + if (vars.size() < 3) + return "not enough args"; + + const auto PLUGIN = g_pPluginSystem->getPluginByPath(PATH); + + if (!PLUGIN) + return "plugin not loaded"; + + g_pPluginSystem->unloadPlugin(PLUGIN); + } else if (OPERATION == "list") { + const auto PLUGINS = g_pPluginSystem->getAllPlugins(); + + std::string list = ""; + for (auto& p : PLUGINS) { + list += getFormat("\nPlugin %s by %s:\n\tHandle: %lx\n\tVersion: %s\n\tDescription: %s\n", p->name.c_str(), p->author.c_str(), p->m_pHandle, p->version.c_str(), + p->description.c_str()); + } + + return list; + } else { + return "unknown opt"; + } + + return "ok"; +} + std::string getReply(std::string request) { auto format = HyprCtl::FORMAT_NORMAL; @@ -1134,6 +1178,8 @@ std::string getReply(std::string request) { return bindsRequest(format); else if (request == "animations") return animationsRequest(format); + else if (request.find("plugin") == 0) + return dispatchPlugin(request); else if (request.find("setprop") == 0) return dispatchSetProp(request); else if (request.find("seterror") == 0) @@ -1156,6 +1202,10 @@ std::string getReply(std::string request) { return "unknown request"; } +std::string HyprCtl::makeDynamicCall(const std::string& input) { + return getReply(input); +} + int hyprCtlFDTick(int fd, uint32_t mask, void* data) { if (mask & WL_EVENT_ERROR || mask & WL_EVENT_HANGUP) return 0; diff --git a/src/debug/HyprCtl.hpp b/src/debug/HyprCtl.hpp index 26859692..00cf7c5b 100644 --- a/src/debug/HyprCtl.hpp +++ b/src/debug/HyprCtl.hpp @@ -5,7 +5,8 @@ #include "../helpers/MiscFunctions.hpp" namespace HyprCtl { - void startHyprCtlSocket(); + void startHyprCtlSocket(); + std::string makeDynamicCall(const std::string& input); // very simple thread-safe request method inline bool requestMade = false; @@ -18,8 +19,7 @@ namespace HyprCtl { inline int iSocketFD = -1; - enum eHyprCtlOutputFormat - { + enum eHyprCtlOutputFormat { FORMAT_NORMAL = 0, FORMAT_JSON }; diff --git a/src/debug/HyprNotificationOverlay.cpp b/src/debug/HyprNotificationOverlay.cpp new file mode 100644 index 00000000..94cb6a6c --- /dev/null +++ b/src/debug/HyprNotificationOverlay.cpp @@ -0,0 +1,155 @@ +#include "HyprNotificationOverlay.hpp" +#include "../Compositor.hpp" + +CHyprNotificationOverlay::CHyprNotificationOverlay() { + g_pHookSystem->hookDynamic("focusedMon", [&](void* self, std::any param) { + if (m_dNotifications.size() == 0) + return; + + g_pHyprRenderer->damageBox(&m_bLastDamage); + }); +} + +void CHyprNotificationOverlay::addNotification(const std::string& text, const CColor& color, const float timeMs) { + const auto PNOTIF = m_dNotifications.emplace_back(std::make_unique()).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/Monitors.cpp b/src/events/Monitors.cpp index 799669a1..271b7d81 100644 --- a/src/events/Monitors.cpp +++ b/src/events/Monitors.cpp @@ -197,6 +197,8 @@ void Events::listener_monitorFrame(void* owner, void* data) { return; } + PMONITOR->renderingActive = true; + // we need to cleanup fading out when rendering the appropriate context g_pCompositor->cleanupFadingOut(PMONITOR->ID); @@ -207,6 +209,8 @@ void Events::listener_monitorFrame(void* owner, void* data) { if (*PDAMAGEBLINK || *PVFR == 0) g_pCompositor->scheduleFrameForMonitor(PMONITOR); + PMONITOR->renderingActive = false; + return; } @@ -260,6 +264,9 @@ void Events::listener_monitorFrame(void* owner, void* data) { if (PMONITOR == g_pCompositor->m_vMonitors.front().get()) g_pHyprError->draw(); + if (PMONITOR == g_pCompositor->m_pLastMonitor) + g_pHyprNotificationOverlay->draw(PMONITOR); + // for drawing the debug overlay if (PMONITOR == g_pCompositor->m_vMonitors.front().get() && *PDEBUGOVERLAY == 1) { startRenderOverlay = std::chrono::high_resolution_clock::now(); @@ -306,12 +313,16 @@ void Events::listener_monitorFrame(void* owner, void* data) { pixman_region32_fini(&frameDamage); pixman_region32_fini(&damage); + PMONITOR->renderingActive = false; + if (!wlr_output_commit(PMONITOR->output)) return; - if (*PDAMAGEBLINK || *PVFR == 0) + if (*PDAMAGEBLINK || *PVFR == 0 || PMONITOR->pendingFrame) g_pCompositor->scheduleFrameForMonitor(PMONITOR); + PMONITOR->pendingFrame = false; + if (*PDEBUGOVERLAY == 1) { const float µs = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - startRender).count() / 1000.f; g_pDebugOverlay->renderData(PMONITOR, µs); diff --git a/src/helpers/Color.hpp b/src/helpers/Color.hpp index 184087ac..ffd6d2d8 100644 --- a/src/helpers/Color.hpp +++ b/src/helpers/Color.hpp @@ -1,6 +1,6 @@ #pragma once -#include "../includes.hpp" +#include 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/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.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/meson.build b/src/meson.build index 6c018e12..7b658d31 100644 --- a/src/meson.build +++ b/src/meson.build @@ -16,6 +16,7 @@ executable('Hyprland', src, xcb_dep, backtrace_dep, systemd_dep, + udis86, dependency('pixman-1'), dependency('gl', 'opengl'), diff --git a/src/plugins/HookSystem.cpp b/src/plugins/HookSystem.cpp new file mode 100644 index 00000000..a8ddd90e --- /dev/null +++ b/src/plugins/HookSystem.cpp @@ -0,0 +1,151 @@ +#include "HookSystem.hpp" + +#define register +#include +#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 getInstructionLenAt(void* start) { + ud_t udis; + + ud_init(&udis); + ud_set_mode(&udis, 64); + ud_set_syntax(&udis, UD_SYN_INTEL); + + size_t curOffset = 1; + size_t insSize = 0; + while (true) { + ud_set_input_buffer(&udis, (uint8_t*)start, curOffset); + insSize = ud_disassemble(&udis); + if (insSize != curOffset) + break; + curOffset++; + } + + return insSize; +} + +size_t probeMinimumJumpSize(void* start, size_t min) { + + size_t size = 0; + + while (size <= min) { + // find info about this instruction + size_t insLen = getInstructionLenAt(start + size); + size += insLen; + } + + return size; +} + +bool CFunctionHook::hook() { + + // check for unsupported platforms +#if !defined(__x86_64__) + return false; +#endif + + // movabs $0,%rax | jmpq *%rax + static constexpr uint8_t ABSOLUTE_JMP_ADDRESS[] = {0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0}; + // pushq %rax + static constexpr uint8_t PUSH_RAX[] = {0x50}; + // popq %rax + static constexpr uint8_t POP_RAX[] = {0x58}; + // nop + static constexpr uint8_t NOP = 0x90; + + // get minimum size to overwrite + const auto HOOKSIZE = probeMinimumJumpSize(m_pSource, sizeof(ABSOLUTE_JMP_ADDRESS) + sizeof(PUSH_RAX) + sizeof(POP_RAX)); + + // alloc trampoline + m_pTrampolineAddr = mmap(NULL, sizeof(ABSOLUTE_JMP_ADDRESS) + HOOKSIZE + sizeof(PUSH_RAX), PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + // populate trampoline + memcpy(m_pTrampolineAddr, m_pSource, HOOKSIZE); // first, original func bytes + memcpy(m_pTrampolineAddr + HOOKSIZE, PUSH_RAX, sizeof(PUSH_RAX)); // then, pushq %rax + memcpy(m_pTrampolineAddr + HOOKSIZE + sizeof(PUSH_RAX), ABSOLUTE_JMP_ADDRESS, sizeof(ABSOLUTE_JMP_ADDRESS)); // then, jump to source + + // fixup trampoline addr + *(uint64_t*)(m_pTrampolineAddr + HOOKSIZE + 2 + sizeof(PUSH_RAX)) = (uint64_t)(m_pSource + sizeof(ABSOLUTE_JMP_ADDRESS)); + + // make jump to hk + mprotect(m_pSource - ((uint64_t)m_pSource) % sysconf(_SC_PAGE_SIZE), sysconf(_SC_PAGE_SIZE), PROT_READ | PROT_WRITE | PROT_EXEC); + memcpy(m_pSource, ABSOLUTE_JMP_ADDRESS, sizeof(ABSOLUTE_JMP_ADDRESS)); + + // make popq %rax and NOP all remaining + memcpy(m_pSource + sizeof(ABSOLUTE_JMP_ADDRESS), POP_RAX, sizeof(POP_RAX)); + size_t currentOp = sizeof(ABSOLUTE_JMP_ADDRESS) + sizeof(POP_RAX); + memset(m_pSource + currentOp, NOP, HOOKSIZE - currentOp); + + // fixup jump addr + *(uint64_t*)(m_pSource + 2) = (uint64_t)(m_pDestination); + + // revert mprot + mprotect(m_pSource - ((uint64_t)m_pSource) % sysconf(_SC_PAGE_SIZE), sysconf(_SC_PAGE_SIZE), PROT_READ | PROT_EXEC); + + // set original addr to trampo addr + m_pOriginal = m_pTrampolineAddr; + + m_bActive = true; + m_iHookLen = HOOKSIZE; + m_iTrampoLen = HOOKSIZE + sizeof(ABSOLUTE_JMP_ADDRESS); + + return true; +} + +bool CFunctionHook::unhook() { + // check for unsupported platforms +#if !defined(__x86_64__) + return false; +#endif + + if (!m_bActive) + return false; + + // allow write to src + mprotect(m_pSource - ((uint64_t)m_pSource) % sysconf(_SC_PAGE_SIZE), sysconf(_SC_PAGE_SIZE), PROT_READ | PROT_WRITE | PROT_EXEC); + + // write back original bytes + memcpy(m_pSource, m_pTrampolineAddr, m_iHookLen); + + // revert mprot + mprotect(m_pSource - ((uint64_t)m_pSource) % sysconf(_SC_PAGE_SIZE), sysconf(_SC_PAGE_SIZE), PROT_READ | PROT_EXEC); + + // unmap + munmap(m_pTrampolineAddr, m_iTrampoLen); + + // reset vars + m_bActive = false; + m_iHookLen = 0; + m_iTrampoLen = 0; + m_pTrampolineAddr = nullptr; + + return true; +} + +CFunctionHook* CHookSystem::initHook(HANDLE owner, void* source, void* destination) { + return m_vHooks.emplace_back(std::make_unique(owner, source, destination)).get(); +} + +bool CHookSystem::removeHook(CFunctionHook* hook) { + std::erase_if(m_vHooks, [&](const auto& other) { return other.get() == hook; }); + return true; // todo: make false if not found +} + +void CHookSystem::removeAllHooksFrom(HANDLE handle) { + std::erase_if(m_vHooks, [&](const auto& other) { return other->m_pOwner == handle; }); +} \ No newline at end of file diff --git a/src/plugins/HookSystem.hpp b/src/plugins/HookSystem.hpp new file mode 100644 index 00000000..56227449 --- /dev/null +++ b/src/plugins/HookSystem.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#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; + + 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..5b15782d --- /dev/null +++ b/src/plugins/PluginSystem.cpp @@ -0,0 +1,139 @@ +#include "PluginSystem.hpp" + +#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; }); +} + +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; +} \ No newline at end of file diff --git a/src/plugins/PluginSystem.hpp b/src/plugins/PluginSystem.hpp new file mode 100644 index 00000000..041ee47e --- /dev/null +++ b/src/plugins/PluginSystem.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include "../defines.hpp" +#include "PluginAPI.hpp" +#include + +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); + 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; \ No newline at end of file diff --git a/src/render/decorations/IHyprWindowDecoration.hpp b/src/render/decorations/IHyprWindowDecoration.hpp index 093b99cf..4cd52cba 100644 --- a/src/render/decorations/IHyprWindowDecoration.hpp +++ b/src/render/decorations/IHyprWindowDecoration.hpp @@ -6,7 +6,8 @@ enum eDecorationType { DECORATION_NONE = -1, DECORATION_GROUPBAR, - DECORATION_SHADOW + DECORATION_SHADOW, + DECORATION_CUSTOM }; struct SWindowDecorationExtents { diff --git a/subprojects/udis86 b/subprojects/udis86 new file mode 160000 index 00000000..5336633a --- /dev/null +++ b/subprojects/udis86 @@ -0,0 +1 @@ +Subproject commit 5336633af70f3917760a6d441ff02d93477b0c86 From 15d108fbc11f6e302eb42bc568e4a9d94066ed2f Mon Sep 17 00:00:00 2001 From: vaxerski <43317083+vaxerski@users.noreply.github.com> Date: Mon, 27 Feb 2023 14:08:29 +0000 Subject: [PATCH 02/26] fix make install --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 6b375912..93aae3a4 100644 --- a/Makefile +++ b/Makefile @@ -166,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) make protocols make release cd hyprctl && make all && cd .. From 5da96132b977d0d1a0bf016db7409d1546e466a8 Mon Sep 17 00:00:00 2001 From: vaxerski <43317083+vaxerski@users.noreply.github.com> Date: Mon, 27 Feb 2023 14:36:59 +0000 Subject: [PATCH 03/26] fix make install --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 93aae3a4..0bef0dca 100644 --- a/Makefile +++ b/Makefile @@ -166,7 +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 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 .. From 3bceabe29b8aa510c80d396fa97496cb7eec8ce5 Mon Sep 17 00:00:00 2001 From: vaxerski Date: Mon, 27 Feb 2023 15:26:44 +0000 Subject: [PATCH 04/26] add plugin support to readme --- README.md | 1 + 1 file changed, 1 insertion(+) 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 From c5d741fb39416f6e2fd35cabe1afbd87f510058b Mon Sep 17 00:00:00 2001 From: vaxerski <43317083+vaxerski@users.noreply.github.com> Date: Mon, 27 Feb 2023 18:34:44 +0000 Subject: [PATCH 05/26] Plugin Hooks: fix calls to %rip offsets --- src/plugins/HookSystem.cpp | 50 ++++++++++++++++++++++++++++++++------ src/plugins/HookSystem.hpp | 21 ++++++++++------ 2 files changed, 56 insertions(+), 15 deletions(-) diff --git a/src/plugins/HookSystem.cpp b/src/plugins/HookSystem.cpp index a8ddd90e..71789d46 100644 --- a/src/plugins/HookSystem.cpp +++ b/src/plugins/HookSystem.cpp @@ -19,7 +19,7 @@ CFunctionHook::~CFunctionHook() { } } -size_t getInstructionLenAt(void* start) { +size_t CFunctionHook::getInstructionLenAt(void* start) { ud_t udis; ud_init(&udis); @@ -36,10 +36,22 @@ size_t getInstructionLenAt(void* start) { 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 probeMinimumJumpSize(void* start, size_t min) { +size_t CFunctionHook::probeMinimumJumpSize(void* start, size_t min) { size_t size = 0; @@ -60,27 +72,51 @@ bool CFunctionHook::hook() { #endif // movabs $0,%rax | jmpq *%rax - static constexpr uint8_t ABSOLUTE_JMP_ADDRESS[] = {0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0}; + // 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; + /* + pushq %rax + movabs $0,%rax + callq *%rax + popq %rax + + offset for addr: 3 + */ + static constexpr uint8_t CALL_WITH_RAX[] = {0x50, 0x48, 0xB8, 0xEF, 0xBE, 0xAD, 0xDE, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x10, 0x58}; + static constexpr size_t CALL_WITH_RAX_ADDRESS_OFFSET = 3; // get minimum size to overwrite const auto HOOKSIZE = probeMinimumJumpSize(m_pSource, sizeof(ABSOLUTE_JMP_ADDRESS) + sizeof(PUSH_RAX) + sizeof(POP_RAX)); // alloc trampoline - m_pTrampolineAddr = mmap(NULL, sizeof(ABSOLUTE_JMP_ADDRESS) + HOOKSIZE + sizeof(PUSH_RAX), PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + 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); // 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 + HOOKSIZE + 2 + sizeof(PUSH_RAX)) = (uint64_t)(m_pSource + sizeof(ABSOLUTE_JMP_ADDRESS)); + *(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); @@ -92,7 +128,7 @@ bool CFunctionHook::hook() { memset(m_pSource + currentOp, NOP, HOOKSIZE - currentOp); // fixup jump addr - *(uint64_t*)(m_pSource + 2) = (uint64_t)(m_pDestination); + *(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); @@ -102,7 +138,7 @@ bool CFunctionHook::hook() { m_bActive = true; m_iHookLen = HOOKSIZE; - m_iTrampoLen = HOOKSIZE + sizeof(ABSOLUTE_JMP_ADDRESS); + m_iTrampoLen = TRAMPOLINE_SIZE; return true; } diff --git a/src/plugins/HookSystem.hpp b/src/plugins/HookSystem.hpp index 56227449..0ff7f888 100644 --- a/src/plugins/HookSystem.hpp +++ b/src/plugins/HookSystem.hpp @@ -22,14 +22,19 @@ class CFunctionHook { 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; + 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; + + size_t probeMinimumJumpSize(void* start, size_t min); + size_t getInstructionLenAt(void* start); friend class CHookSystem; }; From 03d7651916304e35a7f47027e7f1b58f618ed05d Mon Sep 17 00:00:00 2001 From: vaxerski <43317083+vaxerski@users.noreply.github.com> Date: Mon, 27 Feb 2023 19:17:58 +0000 Subject: [PATCH 06/26] Plugin Hooks: fix original bytes on %rip accesses --- src/plugins/HookSystem.cpp | 8 +++++++- src/plugins/HookSystem.hpp | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/plugins/HookSystem.cpp b/src/plugins/HookSystem.cpp index 71789d46..ce53d653 100644 --- a/src/plugins/HookSystem.cpp +++ b/src/plugins/HookSystem.cpp @@ -99,6 +99,9 @@ bool CFunctionHook::hook() { 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 @@ -156,7 +159,7 @@ bool CFunctionHook::unhook() { mprotect(m_pSource - ((uint64_t)m_pSource) % sysconf(_SC_PAGE_SIZE), sysconf(_SC_PAGE_SIZE), PROT_READ | PROT_WRITE | PROT_EXEC); // write back original bytes - memcpy(m_pSource, m_pTrampolineAddr, m_iHookLen); + 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); @@ -169,6 +172,9 @@ bool CFunctionHook::unhook() { m_iHookLen = 0; m_iTrampoLen = 0; m_pTrampolineAddr = nullptr; + m_pOriginalBytes = nullptr; + + free(m_pOriginalBytes); return true; } diff --git a/src/plugins/HookSystem.hpp b/src/plugins/HookSystem.hpp index 0ff7f888..b871a379 100644 --- a/src/plugins/HookSystem.hpp +++ b/src/plugins/HookSystem.hpp @@ -33,6 +33,8 @@ class CFunctionHook { std::vector> m_vTrampolineRIPUses; + void* m_pOriginalBytes = nullptr; + size_t probeMinimumJumpSize(void* start, size_t min); size_t getInstructionLenAt(void* start); From 18229043fa85bdf96a9d829c1e61d0f524a1966a Mon Sep 17 00:00:00 2001 From: vaxerski <43317083+vaxerski@users.noreply.github.com> Date: Mon, 27 Feb 2023 19:47:42 +0000 Subject: [PATCH 07/26] Remove useless rax preserve across callq --- src/plugins/HookSystem.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/plugins/HookSystem.cpp b/src/plugins/HookSystem.cpp index ce53d653..afae69a4 100644 --- a/src/plugins/HookSystem.cpp +++ b/src/plugins/HookSystem.cpp @@ -82,15 +82,13 @@ bool CFunctionHook::hook() { // nop static constexpr uint8_t NOP = 0x90; /* - pushq %rax movabs $0,%rax callq *%rax - popq %rax offset for addr: 3 */ - static constexpr uint8_t CALL_WITH_RAX[] = {0x50, 0x48, 0xB8, 0xEF, 0xBE, 0xAD, 0xDE, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x10, 0x58}; - static constexpr size_t CALL_WITH_RAX_ADDRESS_OFFSET = 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)); From 49f423aa8f9b6e063c7806098a9b6b43a7978c02 Mon Sep 17 00:00:00 2001 From: vaxerski <43317083+vaxerski@users.noreply.github.com> Date: Mon, 27 Feb 2023 23:34:41 +0000 Subject: [PATCH 08/26] update decos on anim values update --- src/Compositor.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Compositor.cpp b/src/Compositor.cpp index a7d48a15..fe81196c 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -1668,6 +1668,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() { From ac251d7a66ec3cbd18c15f29c4f54d33d54f1b13 Mon Sep 17 00:00:00 2001 From: vaxerski Date: Tue, 28 Feb 2023 12:34:59 +0000 Subject: [PATCH 09/26] allow focus to grouped windows --- src/Compositor.cpp | 2 +- src/managers/KeybindManager.cpp | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Compositor.cpp b/src/Compositor.cpp index fe81196c..9346dd9c 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -2050,7 +2050,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/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; From 89e7d812c80038083acdca85a5ba08ad97b2d0ea Mon Sep 17 00:00:00 2001 From: vaxerski Date: Tue, 28 Feb 2023 12:41:46 +0000 Subject: [PATCH 10/26] Include unmapped and hidden windows in hyprctl clients --- src/debug/HyprCtl.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/debug/HyprCtl.cpp b/src/debug/HyprCtl.cpp index f6ed285b..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; From 7e523e4d5e526bdf5ab86a8f4fe37efeef356707 Mon Sep 17 00:00:00 2001 From: vaxerski <43317083+vaxerski@users.noreply.github.com> Date: Tue, 28 Feb 2023 18:50:47 +0000 Subject: [PATCH 11/26] minor fixes to X11 configure/or handling --- src/Compositor.cpp | 13 +++++++++---- src/Window.hpp | 1 + src/events/Events.hpp | 1 + src/events/Windows.cpp | 28 +++++++++++++++++----------- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/Compositor.cpp b/src/Compositor.cpp index 9346dd9c..52e2f6ad 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -498,10 +498,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) { @@ -519,6 +515,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; }); } } @@ -529,6 +529,11 @@ bool CCompositor::windowExists(CWindow* pWindow) { return true; } + for (auto& u : m_dUnmanagedX11Windows) { + if (u.get() == pWindow) + return true; + } + return false; } diff --git a/src/Window.hpp b/src/Window.hpp index 65ac78f5..577b7432 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 { 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/Windows.cpp b/src/events/Windows.cpp index 58273d96..2405a309 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,12 +887,15 @@ 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; + + 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) { @@ -904,11 +905,6 @@ void Events::listener_configureX11(void* owner, void* data) { 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; - } - 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) { From 99b7d53817c111fb66063d60f4479ac6e06402e5 Mon Sep 17 00:00:00 2001 From: vaxerski <43317083+vaxerski@users.noreply.github.com> Date: Tue, 28 Feb 2023 18:52:03 +0000 Subject: [PATCH 12/26] deny configure requests on drag --- src/events/Windows.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/events/Windows.cpp b/src/events/Windows.cpp index 2405a309..52791d6c 100644 --- a/src/events/Windows.cpp +++ b/src/events/Windows.cpp @@ -898,7 +898,7 @@ void Events::listener_configureX11(void* owner, void* data) { g_pHyprRenderer->damageWindow(PWINDOW); - if (!PWINDOW->m_bIsFloating || PWINDOW->m_bIsFullscreen) { + 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); From 4d3f2ca96b006fe9fd27de7f1bd4d317ad732762 Mon Sep 17 00:00:00 2001 From: vaxerski <43317083+vaxerski@users.noreply.github.com> Date: Tue, 28 Feb 2023 19:02:30 +0000 Subject: [PATCH 13/26] minor fixes to touch handling --- src/managers/input/InputManager.cpp | 11 +++-------- src/managers/input/Touch.cpp | 7 +++++++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/managers/input/InputManager.cpp b/src/managers/input/InputManager.cpp index e221651a..042e19dd 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"); @@ -91,7 +86,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { // 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); @@ -462,7 +457,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 +1089,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/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; From 7beb9fd606106e82554c7050aebbc5ca23dafa79 Mon Sep 17 00:00:00 2001 From: vaxerski <43317083+vaxerski@users.noreply.github.com> Date: Tue, 28 Feb 2023 19:18:13 +0000 Subject: [PATCH 14/26] Draw HyprError on the last monitor --- src/events/Monitors.cpp | 8 +++----- src/hyprerror/HyprError.cpp | 30 ++++++++++++++++++++++++------ src/hyprerror/HyprError.hpp | 2 ++ 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/events/Monitors.cpp b/src/events/Monitors.cpp index 271b7d81..cafd77f8 100644 --- a/src/events/Monitors.cpp +++ b/src/events/Monitors.cpp @@ -260,12 +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()) - g_pHyprError->draw(); - - if (PMONITOR == g_pCompositor->m_pLastMonitor) + 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) { 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 From 4b20d4f1add40130653b9a3f2351eb44e0e0df4b Mon Sep 17 00:00:00 2001 From: vaxerski <43317083+vaxerski@users.noreply.github.com> Date: Tue, 28 Feb 2023 19:36:36 +0000 Subject: [PATCH 15/26] Added decoration reserved area --- src/Window.cpp | 16 ++++++ src/Window.hpp | 53 ++++++++++--------- src/managers/XWaylandManager.cpp | 6 ++- src/managers/XWaylandManager.hpp | 2 +- src/render/Renderer.cpp | 7 +-- .../decorations/IHyprWindowDecoration.cpp | 4 ++ .../decorations/IHyprWindowDecoration.hpp | 2 + 7 files changed, 59 insertions(+), 31 deletions(-) diff --git a/src/Window.cpp b/src/Window.cpp index 549133d5..e06c3d3a 100644 --- a/src/Window.cpp +++ b/src/Window.cpp @@ -88,6 +88,22 @@ wlr_box CWindow::getWindowIdealBoundingBoxIgnoreReserved() { return wlr_box{(int)POS.x, (int)POS.y, (int)SIZE.x, (int)SIZE.y}; } +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 577b7432..bc81e60a 100644 --- a/src/Window.hpp +++ b/src/Window.hpp @@ -287,34 +287,35 @@ 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 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/managers/XWaylandManager.cpp b/src/managers/XWaylandManager.cpp index d67441c7..3f8f51e8 100644 --- a/src/managers/XWaylandManager.cpp +++ b/src/managers/XWaylandManager.cpp @@ -136,7 +136,11 @@ void CHyprXWaylandManager::sendCloseWindow(CWindow* pWindow) { } } -void CHyprXWaylandManager::setWindowSize(CWindow* pWindow, const Vector2D& size, bool force) { +void CHyprXWaylandManager::setWindowSize(CWindow* pWindow, Vector2D size, bool force) { + + const auto RESERVED = pWindow->getFullWindowReservedArea(); + + size = size - RESERVED.topLeft - RESERVED.bottomRight; 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/render/Renderer.cpp b/src/render/Renderer.cpp index 07f043de..0914eb49 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -237,7 +237,8 @@ void CHyprRenderer::renderWindow(CWindow* pWindow, CMonitor* pMonitor, timespec* } const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(pWindow->m_iWorkspaceID); - const auto REALPOS = pWindow->m_vRealPosition.vec() + (pWindow->m_bPinned ? Vector2D{} : PWORKSPACE->m_vRenderOffset.vec()); + const auto RESERVED = pWindow->getFullWindowReservedArea(); + const auto REALPOS = pWindow->m_vRealPosition.vec() + (pWindow->m_bPinned ? Vector2D{} : PWORKSPACE->m_vRenderOffset.vec()) + RESERVED.topLeft; static auto* const PNOFLOATINGBORDERS = &g_pConfigManager->getConfigValuePtr("general:no_border_on_floating")->intValue; static auto* const PDIMAROUND = &g_pConfigManager->getConfigValuePtr("decoration:dim_around")->floatValue; @@ -251,8 +252,8 @@ void CHyprRenderer::renderWindow(CWindow* pWindow, CMonitor* pMonitor, timespec* decorate = false; renderdata.surface = g_pXWaylandManager->getWindowSurface(pWindow); - renderdata.w = std::max(pWindow->m_vRealSize.vec().x, 5.0); // clamp the size to min 5, - renderdata.h = std::max(pWindow->m_vRealSize.vec().y, 5.0); // otherwise we'll have issues later with invalid boxes + renderdata.w = std::max(pWindow->m_vRealSize.vec().x - RESERVED.topLeft.x - RESERVED.bottomRight.x, 5.0); // clamp the size to min 5, + renderdata.h = std::max(pWindow->m_vRealSize.vec().y - RESERVED.topLeft.y - RESERVED.bottomRight.y, 5.0); // otherwise we'll have issues later with invalid boxes renderdata.dontRound = (pWindow->m_bIsFullscreen && PWORKSPACE->m_efFullscreenMode == FULLSCREEN_FULL) || (!pWindow->m_sSpecialRenderData.rounding); renderdata.fadeAlpha = pWindow->m_fAlpha.fl() * (pWindow->m_bPinned ? 1.f : PWORKSPACE->m_fAlpha.fl()); renderdata.alpha = pWindow->m_fActiveInactiveAlpha.fl(); diff --git a/src/render/decorations/IHyprWindowDecoration.cpp b/src/render/decorations/IHyprWindowDecoration.cpp index 1271a712..1c1fe734 100644 --- a/src/render/decorations/IHyprWindowDecoration.cpp +++ b/src/render/decorations/IHyprWindowDecoration.cpp @@ -3,3 +3,7 @@ #include "../../Window.hpp" IHyprWindowDecoration::~IHyprWindowDecoration() {} + +SWindowDecorationExtents IHyprWindowDecoration::getWindowDecorationReservedArea() { + return SWindowDecorationExtents{}; +} diff --git a/src/render/decorations/IHyprWindowDecoration.hpp b/src/render/decorations/IHyprWindowDecoration.hpp index 4cd52cba..f07746ef 100644 --- a/src/render/decorations/IHyprWindowDecoration.hpp +++ b/src/render/decorations/IHyprWindowDecoration.hpp @@ -31,4 +31,6 @@ interface IHyprWindowDecoration { virtual void updateWindow(CWindow*) = 0; virtual void damageEntire() = 0; + + virtual SWindowDecorationExtents getWindowDecorationReservedArea(); }; \ No newline at end of file From 91fd854e3be1d798607d398596acce470bd9a8e2 Mon Sep 17 00:00:00 2001 From: vaxerski <43317083+vaxerski@users.noreply.github.com> Date: Tue, 28 Feb 2023 19:37:00 +0000 Subject: [PATCH 16/26] fix warn --- src/events/Devices.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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}; - } } } From 7187ea443e756899d32334cdb5e8a49fa49a3d79 Mon Sep 17 00:00:00 2001 From: Dashie Date: Tue, 28 Feb 2023 21:50:10 +0100 Subject: [PATCH 17/26] Add option to disable/enable mouse window dragging animations (#1658) --- src/config/ConfigManager.cpp | 34 ++++++++++++++++++---------------- src/layout/IHyprLayout.cpp | 5 +++-- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 0931a6f8..508980f6 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 = 0; + configValues["misc:animate_mouse_windowdragging"].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["debug:int"].intValue = 0; configValues["debug:log_damage"].intValue = 0; @@ -1836,4 +1837,5 @@ void CConfigManager::addPluginConfigVar(HANDLE handle, const std::string& name, void CConfigManager::removePluginConfig(HANDLE handle) { std::erase_if(pluginConfigs, [&](const auto& other) { return other.first == handle; }); -} \ No newline at end of file +} + diff --git a/src/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); From 72fc309fb14e0651448f35d08753dba772a5a409 Mon Sep 17 00:00:00 2001 From: vaxerski <43317083+vaxerski@users.noreply.github.com> Date: Tue, 28 Feb 2023 21:18:09 +0000 Subject: [PATCH 18/26] make window reserved area less stupid --- src/layout/DwindleLayout.cpp | 4 +++ src/layout/MasterLayout.cpp | 49 +++++++++++++++++--------------- src/managers/XWaylandManager.cpp | 4 --- src/render/Renderer.cpp | 7 ++--- 4 files changed, 33 insertions(+), 31 deletions(-) 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/MasterLayout.cpp b/src/layout/MasterLayout.cpp index c5367e05..aa119864 100644 --- a/src/layout/MasterLayout.cpp +++ b/src/layout/MasterLayout.cpp @@ -46,7 +46,7 @@ SMasterWorkspaceData* CHyprMasterLayout::getMasterWorkspaceData(const int& ws) { PWORKSPACEDATA->orientation = ORIENTATION_RIGHT; } else if (*orientation == "bottom") { PWORKSPACEDATA->orientation = ORIENTATION_BOTTOM; - } else if (*orientation == "left"){ + } else if (*orientation == "left") { PWORKSPACEDATA->orientation = ORIENTATION_LEFT; } else { PWORKSPACEDATA->orientation = ORIENTATION_CENTER; @@ -58,7 +58,6 @@ std::string CHyprMasterLayout::getLayoutName() { return "Master"; } - SMasterNodeData* CHyprMasterLayout::getMasterNodeOnWorkspace(const int& ws) { for (auto& n : m_lMasterNodesData) { if (n.isMaster && n.workspaceID == ws) @@ -239,11 +238,11 @@ void CHyprMasterLayout::calculateWorkspace(const int& ws) { if (!PMASTERNODE) return; - eOrientation orientation = PWORKSPACEDATA->orientation; - bool centerMasterWindow = false; - static auto* const ALWAYSCENTER = &g_pConfigManager->getConfigValuePtr("master:always_center_master")->intValue; + eOrientation orientation = PWORKSPACEDATA->orientation; + bool centerMasterWindow = false; + static auto* const ALWAYSCENTER = &g_pConfigManager->getConfigValuePtr("master:always_center_master")->intValue; if (orientation == ORIENTATION_CENTER) { - if (getNodesOnWorkspace(PWORKSPACE->m_iID) > 2 || (*ALWAYSCENTER==1)) { + if (getNodesOnWorkspace(PWORKSPACE->m_iID) > 2 || (*ALWAYSCENTER == 1)) { centerMasterWindow = true; } else { orientation = ORIENTATION_LEFT; @@ -256,7 +255,7 @@ void CHyprMasterLayout::calculateWorkspace(const int& ws) { if (getNodesOnWorkspace(PWORKSPACE->m_iID) < 2 && !centerMasterWindow) { 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) { @@ -320,8 +319,8 @@ void CHyprMasterLayout::calculateWorkspace(const int& ws) { for (auto& n : m_lMasterNodesData) { if (n.workspaceID == PWORKSPACE->m_iID && n.isMaster) { float CENTER_OFFSET = (PMONITOR->vecSize.x - WIDTH) / 2; - n.position = PMONITOR->vecReservedTopLeft + PMONITOR->vecPosition + Vector2D(CENTER_OFFSET, nextY); - float HEIGHT = nodesLeft > 1 ? heightLeft / nodesLeft * n.percSize : heightLeft; + n.position = PMONITOR->vecReservedTopLeft + PMONITOR->vecPosition + Vector2D(CENTER_OFFSET, nextY); + float HEIGHT = nodesLeft > 1 ? heightLeft / nodesLeft * n.percSize : heightLeft; if (HEIGHT > heightLeft * 0.9f && nodesLeft > 1) HEIGHT = heightLeft * 0.9f; n.size = Vector2D(WIDTH, HEIGHT); @@ -389,16 +388,16 @@ void CHyprMasterLayout::calculateWorkspace(const int& ws) { applyNodeDataToWindow(&nd); } } else if (orientation == ORIENTATION_CENTER) { - float heightLeftL = PMONITOR->vecSize.y - PMONITOR->vecReservedBottomRight.y - PMONITOR->vecReservedTopLeft.y; - float heightLeftR = heightLeftL; - float heightLeft = 0; - float nextYL = 0; - float nextYR = 0; - const float WIDTH = (PMONITOR->vecSize.x - PMONITOR->vecReservedBottomRight.x - PMONITOR->vecReservedTopLeft.x - PMASTERNODE->size.x) / 2.0; - bool on_left = true; + float heightLeftL = PMONITOR->vecSize.y - PMONITOR->vecReservedBottomRight.y - PMONITOR->vecReservedTopLeft.y; + float heightLeftR = heightLeftL; + float heightLeft = 0; + float nextYL = 0; + float nextYR = 0; + const float WIDTH = (PMONITOR->vecSize.x - PMONITOR->vecReservedBottomRight.x - PMONITOR->vecReservedTopLeft.x - PMASTERNODE->size.x) / 2.0; + bool on_left = true; - int slavesLeftL = 1 + (slavesLeft - 1) / 2; - int slavesLeftR = slavesLeft - slavesLeftL; + int slavesLeftL = 1 + (slavesLeft - 1) / 2; + int slavesLeftR = slavesLeft - slavesLeftL; for (auto& nd : m_lMasterNodesData) { if (nd.workspaceID != PWORKSPACE->m_iID || nd.isMaster) @@ -406,12 +405,12 @@ void CHyprMasterLayout::calculateWorkspace(const int& ws) { if (on_left) { nd.position = PMONITOR->vecReservedTopLeft + PMONITOR->vecPosition + Vector2D(0, nextYL); - heightLeft = heightLeftL; - slavesLeft = slavesLeftL; + heightLeft = heightLeftL; + slavesLeft = slavesLeftL; } else { - nd.position = PMONITOR->vecReservedTopLeft + PMONITOR->vecPosition + Vector2D(WIDTH+PMASTERNODE->size.x, nextYR); - heightLeft = heightLeftR; - slavesLeft = slavesLeftR; + nd.position = PMONITOR->vecReservedTopLeft + PMONITOR->vecPosition + Vector2D(WIDTH + PMASTERNODE->size.x, nextYR); + heightLeft = heightLeftR; + slavesLeft = slavesLeftR; } float HEIGHT = slavesLeft > 1 ? heightLeft / slavesLeft * nd.percSize : heightLeft; if (HEIGHT > heightLeft * 0.9f && slavesLeft > 1) @@ -504,6 +503,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/XWaylandManager.cpp b/src/managers/XWaylandManager.cpp index 3f8f51e8..60b339b3 100644 --- a/src/managers/XWaylandManager.cpp +++ b/src/managers/XWaylandManager.cpp @@ -138,10 +138,6 @@ void CHyprXWaylandManager::sendCloseWindow(CWindow* pWindow) { void CHyprXWaylandManager::setWindowSize(CWindow* pWindow, Vector2D size, bool force) { - const auto RESERVED = pWindow->getFullWindowReservedArea(); - - size = size - RESERVED.topLeft - RESERVED.bottomRight; - if (!force && ((pWindow->m_vReportedSize == size && pWindow->m_vRealPosition.vec() == pWindow->m_vReportedPosition) || (pWindow->m_vReportedSize == size && !pWindow->m_bIsX11))) return; diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index 0914eb49..07f043de 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -237,8 +237,7 @@ void CHyprRenderer::renderWindow(CWindow* pWindow, CMonitor* pMonitor, timespec* } const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(pWindow->m_iWorkspaceID); - const auto RESERVED = pWindow->getFullWindowReservedArea(); - const auto REALPOS = pWindow->m_vRealPosition.vec() + (pWindow->m_bPinned ? Vector2D{} : PWORKSPACE->m_vRenderOffset.vec()) + RESERVED.topLeft; + const auto REALPOS = pWindow->m_vRealPosition.vec() + (pWindow->m_bPinned ? Vector2D{} : PWORKSPACE->m_vRenderOffset.vec()); static auto* const PNOFLOATINGBORDERS = &g_pConfigManager->getConfigValuePtr("general:no_border_on_floating")->intValue; static auto* const PDIMAROUND = &g_pConfigManager->getConfigValuePtr("decoration:dim_around")->floatValue; @@ -252,8 +251,8 @@ void CHyprRenderer::renderWindow(CWindow* pWindow, CMonitor* pMonitor, timespec* decorate = false; renderdata.surface = g_pXWaylandManager->getWindowSurface(pWindow); - renderdata.w = std::max(pWindow->m_vRealSize.vec().x - RESERVED.topLeft.x - RESERVED.bottomRight.x, 5.0); // clamp the size to min 5, - renderdata.h = std::max(pWindow->m_vRealSize.vec().y - RESERVED.topLeft.y - RESERVED.bottomRight.y, 5.0); // otherwise we'll have issues later with invalid boxes + renderdata.w = std::max(pWindow->m_vRealSize.vec().x, 5.0); // clamp the size to min 5, + renderdata.h = std::max(pWindow->m_vRealSize.vec().y, 5.0); // otherwise we'll have issues later with invalid boxes renderdata.dontRound = (pWindow->m_bIsFullscreen && PWORKSPACE->m_efFullscreenMode == FULLSCREEN_FULL) || (!pWindow->m_sSpecialRenderData.rounding); renderdata.fadeAlpha = pWindow->m_fAlpha.fl() * (pWindow->m_bPinned ? 1.f : PWORKSPACE->m_fAlpha.fl()); renderdata.alpha = pWindow->m_fActiveInactiveAlpha.fl(); From 5e48e6b075cdf4cda28f1a39e01acf3b316097a2 Mon Sep 17 00:00:00 2001 From: vaxerski <43317083+vaxerski@users.noreply.github.com> Date: Tue, 28 Feb 2023 21:45:57 +0000 Subject: [PATCH 19/26] Added mouseButton event --- src/managers/input/InputManager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/managers/input/InputManager.cpp b/src/managers/input/InputManager.cpp index 042e19dd..2cd35326 100644 --- a/src/managers/input/InputManager.cpp +++ b/src/managers/input/InputManager.cpp @@ -360,6 +360,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) { From a224d366ca35c1731d73586b81bc17cd06c3539c Mon Sep 17 00:00:00 2001 From: vaxerski <43317083+vaxerski@users.noreply.github.com> Date: Tue, 28 Feb 2023 21:47:00 +0000 Subject: [PATCH 20/26] Added mouseMove event --- src/managers/input/InputManager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/managers/input/InputManager.cpp b/src/managers/input/InputManager.cpp index 2cd35326..70b42be3 100644 --- a/src/managers/input/InputManager.cpp +++ b/src/managers/input/InputManager.cpp @@ -80,6 +80,8 @@ 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(); From 984c2fdc6857de66697a35aede457464cf7adc76 Mon Sep 17 00:00:00 2001 From: vaxerski <43317083+vaxerski@users.noreply.github.com> Date: Tue, 28 Feb 2023 22:15:18 +0000 Subject: [PATCH 21/26] Enable manual anims by default --- src/config/ConfigManager.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 508980f6..f8d2bbc9 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -59,8 +59,8 @@ void CConfigManager::setDefaultVars() { 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:animate_mouse_windowdragging"].intValue = 0; + 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; @@ -1838,4 +1838,3 @@ void CConfigManager::addPluginConfigVar(HANDLE handle, const std::string& name, void CConfigManager::removePluginConfig(HANDLE handle) { std::erase_if(pluginConfigs, [&](const auto& other) { return other.first == handle; }); } - From 2187c6cf43681511c08c8c2c84cd5acff754e522 Mon Sep 17 00:00:00 2001 From: vaxerski <43317083+vaxerski@users.noreply.github.com> Date: Tue, 28 Feb 2023 22:32:42 +0000 Subject: [PATCH 22/26] Allow decos to request interactivity --- src/Compositor.cpp | 12 +++--- src/Window.cpp | 37 +++++++++++++++++++ src/Window.hpp | 1 + src/managers/input/InputManager.cpp | 6 +++ .../decorations/IHyprWindowDecoration.cpp | 4 ++ .../decorations/IHyprWindowDecoration.hpp | 2 + 6 files changed, 56 insertions(+), 6 deletions(-) diff --git a/src/Compositor.cpp b/src/Compositor.cpp index 52e2f6ad..18bdaaab 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -608,8 +608,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(); @@ -625,8 +625,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(); @@ -640,8 +640,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) diff --git a/src/Window.cpp b/src/Window.cpp index e06c3d3a..1c3d0338 100644 --- a/src/Window.cpp +++ b/src/Window.cpp @@ -88,6 +88,43 @@ 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; diff --git a/src/Window.hpp b/src/Window.hpp index bc81e60a..85144e27 100644 --- a/src/Window.hpp +++ b/src/Window.hpp @@ -288,6 +288,7 @@ class CWindow { // methods wlr_box getFullWindowBoundingBox(); + wlr_box getWindowInputBox(); wlr_box getWindowIdealBoundingBoxIgnoreReserved(); void updateWindowDecos(); pid_t getPID(); diff --git a/src/managers/input/InputManager.cpp b/src/managers/input/InputManager.cpp index 70b42be3..7fd186c0 100644 --- a/src/managers/input/InputManager.cpp +++ b/src/managers/input/InputManager.cpp @@ -302,6 +302,9 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { m_pFoundSurfaceToFocus = foundSurface; } + if (currentlyDraggedWindow && !refocus) + return; + if (pFoundWindow) { // change cursor icon if hovering over border if (*PRESIZEONBORDER && *PRESIZECURSORICON && !pFoundWindow->m_bIsFullscreen && !pFoundWindow->hasPopupAt(mouseCoords)) { @@ -374,6 +377,9 @@ void CInputManager::onMouseButton(wlr_pointer_button_event* e) { std::erase_if(m_lCurrentlyHeldButtons, [&](const auto& other) { return other == e->button; }); } + if (currentlyDraggedWindow) + return; + switch (m_ecbClickBehavior) { case CLICKMODE_DEFAULT: processMouseDownNormal(e); break; case CLICKMODE_KILL: processMouseDownKill(e); break; diff --git a/src/render/decorations/IHyprWindowDecoration.cpp b/src/render/decorations/IHyprWindowDecoration.cpp index 1c1fe734..3ad5591f 100644 --- a/src/render/decorations/IHyprWindowDecoration.cpp +++ b/src/render/decorations/IHyprWindowDecoration.cpp @@ -7,3 +7,7 @@ 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 f07746ef..a30d63b3 100644 --- a/src/render/decorations/IHyprWindowDecoration.hpp +++ b/src/render/decorations/IHyprWindowDecoration.hpp @@ -33,4 +33,6 @@ interface IHyprWindowDecoration { virtual void damageEntire() = 0; virtual SWindowDecorationExtents getWindowDecorationReservedArea(); + + virtual bool allowsInput(); }; \ No newline at end of file From cd2399715d84ac4bfa07acc2afe1ade7a2fbb3a2 Mon Sep 17 00:00:00 2001 From: vaxerski <43317083+vaxerski@users.noreply.github.com> Date: Tue, 28 Feb 2023 22:53:41 +0000 Subject: [PATCH 23/26] Revert small incorrect change to dragging --- src/managers/input/InputManager.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/managers/input/InputManager.cpp b/src/managers/input/InputManager.cpp index 7fd186c0..70b42be3 100644 --- a/src/managers/input/InputManager.cpp +++ b/src/managers/input/InputManager.cpp @@ -302,9 +302,6 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { m_pFoundSurfaceToFocus = foundSurface; } - if (currentlyDraggedWindow && !refocus) - return; - if (pFoundWindow) { // change cursor icon if hovering over border if (*PRESIZEONBORDER && *PRESIZECURSORICON && !pFoundWindow->m_bIsFullscreen && !pFoundWindow->hasPopupAt(mouseCoords)) { @@ -377,9 +374,6 @@ void CInputManager::onMouseButton(wlr_pointer_button_event* e) { std::erase_if(m_lCurrentlyHeldButtons, [&](const auto& other) { return other == e->button; }); } - if (currentlyDraggedWindow) - return; - switch (m_ecbClickBehavior) { case CLICKMODE_DEFAULT: processMouseDownNormal(e); break; case CLICKMODE_KILL: processMouseDownKill(e); break; From 07b98952bc09f61d85895c49504b681de8687975 Mon Sep 17 00:00:00 2001 From: vaxerski <43317083+vaxerski@users.noreply.github.com> Date: Tue, 28 Feb 2023 23:06:46 +0000 Subject: [PATCH 24/26] Reset cursor to pointer on focus on interactable deco --- src/managers/input/InputManager.cpp | 19 +++++++++++++++++++ src/managers/input/InputManager.hpp | 17 ++++++++++++++--- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/managers/input/InputManager.cpp b/src/managers/input/InputManager.cpp index 70b42be3..e712f4af 100644 --- a/src/managers/input/InputManager.cpp +++ b/src/managers/input/InputManager.cpp @@ -308,6 +308,16 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { setCursorIconOnBorder(pFoundWindow); } + // if we're on an input deco, reset cursor + 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))) { @@ -400,6 +410,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); } 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; }; From 5c93f6947a04163c6ea8299e8721b923bea2fb9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Zag=C3=B3rowski?= Date: Wed, 1 Mar 2023 00:11:49 +0100 Subject: [PATCH 25/26] Unload plugins on compositor cleanup (#1662) --- src/Compositor.cpp | 4 ++++ src/plugins/PluginSystem.cpp | 8 +++++++- src/plugins/PluginSystem.hpp | 3 ++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Compositor.cpp b/src/Compositor.cpp index 18bdaaab..26565b18 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -285,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; diff --git a/src/plugins/PluginSystem.cpp b/src/plugins/PluginSystem.cpp index 5b15782d..73de9bf5 100644 --- a/src/plugins/PluginSystem.cpp +++ b/src/plugins/PluginSystem.cpp @@ -1,6 +1,7 @@ #include "PluginSystem.hpp" #include +#include #include "../Compositor.hpp" CPluginSystem::CPluginSystem() { @@ -113,6 +114,11 @@ void CPluginSystem::unloadPlugin(const CPlugin* plugin, bool eject) { 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) @@ -136,4 +142,4 @@ std::vector CPluginSystem::getAllPlugins() { for (size_t i = 0; i < m_vLoadedPlugins.size(); ++i) results[i] = m_vLoadedPlugins[i].get(); return results; -} \ No newline at end of file +} diff --git a/src/plugins/PluginSystem.hpp b/src/plugins/PluginSystem.hpp index 041ee47e..5d1a1532 100644 --- a/src/plugins/PluginSystem.hpp +++ b/src/plugins/PluginSystem.hpp @@ -29,6 +29,7 @@ class 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(); @@ -41,4 +42,4 @@ class CPluginSystem { jmp_buf m_jbPluginFaultJumpBuf; }; -inline std::unique_ptr g_pPluginSystem; \ No newline at end of file +inline std::unique_ptr g_pPluginSystem; From 0e252d2c7770f712ed5395d75e31138abf0ea048 Mon Sep 17 00:00:00 2001 From: vaxerski <43317083+vaxerski@users.noreply.github.com> Date: Tue, 28 Feb 2023 23:17:04 +0000 Subject: [PATCH 26/26] Don't set surface cursors for overriden --- src/managers/input/InputManager.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/managers/input/InputManager.cpp b/src/managers/input/InputManager.cpp index e712f4af..f20aed29 100644 --- a/src/managers/input/InputManager.cpp +++ b/src/managers/input/InputManager.cpp @@ -308,14 +308,16 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { setCursorIconOnBorder(pFoundWindow); } - // if we're on an input deco, reset cursor - 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 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) {