Plugin System (#1590)

---------

Co-authored-by: Mihai Fufezan <fufexan@protonmail.com>
This commit is contained in:
Vaxry 2023-02-27 12:32:38 +00:00 committed by GitHub
parent 74a10f26a4
commit 8b81f41e52
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 1691 additions and 85 deletions

View file

@ -12,7 +12,7 @@ jobs:
run: | run: |
sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf
pacman --noconfirm --noprogressbar -Syyu 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 - name: Set up user
run: | run: |
useradd -m githubuser useradd -m githubuser
@ -62,7 +62,7 @@ jobs:
run: | run: |
sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf
pacman --noconfirm --noprogressbar -Syyu 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 - name: Checkout Hyprland
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:

1
.gitignore vendored
View file

@ -20,6 +20,7 @@ result*
*-protocol.c *-protocol.c
*-protocol.h *-protocol.h
.ccls-cache .ccls-cache
*.so
hyprctl/hyprctl hyprctl/hyprctl

3
.gitmodules vendored
View file

@ -4,3 +4,6 @@
[submodule "subprojects/hyprland-protocols"] [submodule "subprojects/hyprland-protocols"]
path = subprojects/hyprland-protocols path = subprojects/hyprland-protocols
url = https://github.com/hyprwm/hyprland-protocols url = https://github.com/hyprwm/hyprland-protocols
[submodule "subprojects/udis86"]
path = subprojects/udis86
url = https://github.com/canihavesomecoffee/udis86

View file

@ -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/include/")
include_directories(. PRIVATE "subprojects/wlroots/build/include/") include_directories(. PRIVATE "subprojects/wlroots/build/include/")
include_directories(. PRIVATE "subprojects/udis86/")
set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD 23)
add_compile_options(-DWLR_USE_UNSTABLE) 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 ) ADD_LINK_OPTIONS( -rdynamic )
SET(CMAKE_ENABLE_EXPORTS TRUE) 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}/wlr-foreign-toplevel-management-unstable-v1-protocol.o
${CMAKE_SOURCE_DIR}/hyprland-toplevel-export-v1-protocol.o ${CMAKE_SOURCE_DIR}/hyprland-toplevel-export-v1-protocol.o
${CMAKE_SOURCE_DIR}/fractional-scale-v1-protocol.o ${CMAKE_SOURCE_DIR}/fractional-scale-v1-protocol.o
${CMAKE_SOURCE_DIR}/subprojects/udis86/build/libudis86/liblibudis86.a
) )

View file

@ -157,6 +157,7 @@ all:
make clear make clear
make fixwlr make fixwlr
cd ./subprojects/wlroots && meson build/ --buildtype=release && ninja -C build/ && cp ./build/libwlroots.so.12032 /usr/lib/ && cd ../.. 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 protocols
make release make release
cd hyprctl && make all && cd .. cd hyprctl && make all && cd ..
@ -211,6 +212,18 @@ config:
cd subprojects/wlroots && ninja -C build/ install 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: configdebug:
make protocols make protocols

View file

@ -0,0 +1,8 @@
# compile with HYPRLAND_HEADERS=<path_to_hl> make all
# make sure that the path above is to the root hl repo directory, NOT src/
# and that you have ran `make protocols` in the hl dir.
all:
g++ -shared -fPIC --no-gnu-unique main.cpp customLayout.cpp customDecoration.cpp -o examplePlugin.so -g -I "/usr/include/pixman-1" -I "/usr/include/libdrm" -I "${HYPRLAND_HEADERS}" -std=c++23
clean:
rm ./examplePlugin.so

View file

@ -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);
}

View file

@ -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;
};

View file

@ -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();
}

View file

@ -0,0 +1,32 @@
#pragma once
#define WLR_USE_UNSTABLE
#include "../../src/layout/IHyprLayout.hpp"
struct SWindowData {
CWindow* pWindow = nullptr;
};
class CHyprCustomLayout : public IHyprLayout {
public:
virtual void onWindowCreatedTiling(CWindow*);
virtual void onWindowRemovedTiling(CWindow*);
virtual bool isWindowTiled(CWindow*);
virtual void recalculateMonitor(const int&);
virtual void recalculateWindow(CWindow*);
virtual void resizeActiveWindow(const Vector2D&, CWindow* pWindow = nullptr);
virtual void fullscreenRequestForWindow(CWindow*, eFullscreenMode, bool);
virtual std::any layoutMessage(SLayoutMessageHeader, std::string);
virtual SWindowRenderLayoutHints requestRenderHints(CWindow*);
virtual void switchWindows(CWindow*, CWindow*);
virtual void alterSplitRatio(CWindow*, float, bool);
virtual std::string getLayoutName();
virtual void replaceWindowDataWith(CWindow* from, CWindow* to);
virtual void onEnable();
virtual void onDisable();
private:
std::vector<SWindowData> m_vWindowData;
};

View file

@ -0,0 +1,5 @@
#pragma once
#include <src/plugins/PluginAPI.hpp>
inline HANDLE PHANDLE = nullptr;

View file

@ -0,0 +1,92 @@
#define WLR_USE_UNSTABLE
#include "globals.hpp"
#include <src/Window.hpp>
#include <src/Compositor.hpp>
#include "customLayout.hpp"
#include "customDecoration.hpp"
#include <unistd.h>
#include <thread>
// Methods
inline std::unique_ptr<CHyprCustomLayout> g_pCustomLayout;
inline CFunctionHook* g_pFocusHook = nullptr;
inline CFunctionHook* g_pMotionHook = nullptr;
inline CFunctionHook* g_pMouseDownHook = nullptr;
typedef void (*origFocusWindow)(void*, CWindow*, wlr_surface*);
typedef void (*origMotion)(wlr_seat*, uint32_t, double, double);
typedef void (*origMouseDownNormal)(void*, wlr_pointer_button_event*);
// Do NOT change this function.
APICALL EXPORT std::string PLUGIN_API_VERSION() {
return HYPRLAND_API_VERSION;
}
static void onActiveWindowChange(void* self, std::any data) {
try {
auto* const PWINDOW = std::any_cast<CWindow*>(data);
HyprlandAPI::addNotification(PHANDLE, "[ExamplePlugin] Active window: " + (PWINDOW ? PWINDOW->m_szTitle : "None"), CColor{0.f, 0.5f, 1.f, 1.f}, 5000);
} catch (std::bad_any_cast& e) { HyprlandAPI::addNotification(PHANDLE, "[ExamplePlugin] Active window: None", CColor{0.f, 0.5f, 1.f, 1.f}, 5000); }
}
static void onNewWindow(void* self, std::any data) {
auto* const PWINDOW = std::any_cast<CWindow*>(data);
HyprlandAPI::addWindowDecoration(PHANDLE, PWINDOW, new CCustomDecoration(PWINDOW));
}
void hkFocusWindow(void* thisptr, CWindow* pWindow, wlr_surface* pSurface) {
// HyprlandAPI::addNotification(PHANDLE, getFormat("FocusWindow with %lx %lx", pWindow, pSurface), CColor{0.f, 1.f, 1.f, 1.f}, 5000);
(*(origFocusWindow)g_pFocusHook->m_pOriginal)(thisptr, pWindow, pSurface);
}
void hkNotifyMotion(wlr_seat* wlr_seat, uint32_t time_msec, double sx, double sy) {
// HyprlandAPI::addNotification(PHANDLE, getFormat("NotifyMotion with %lf %lf", sx, sy), CColor{0.f, 1.f, 1.f, 1.f}, 5000);
(*(origMotion)g_pMotionHook->m_pOriginal)(wlr_seat, time_msec, sx, sy);
}
void hkProcessMouseDownNormal(void* thisptr, wlr_pointer_button_event* e) {
// HyprlandAPI::addNotification(PHANDLE, "Mouse down normal!", CColor{0.8f, 0.2f, 0.5f, 1.0f}, 5000);
(*(origMouseDownNormal)g_pMouseDownHook->m_pOriginal)(thisptr, e);
}
APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
PHANDLE = handle;
HyprlandAPI::addNotification(PHANDLE, "Hello World from an example plugin!", CColor{0.f, 1.f, 1.f, 1.f}, 5000);
HyprlandAPI::registerCallbackDynamic(PHANDLE, "activeWindow", [&](void* self, std::any data) { onActiveWindowChange(self, data); });
HyprlandAPI::registerCallbackDynamic(PHANDLE, "openWindow", [&](void* self, std::any data) { onNewWindow(self, data); });
g_pCustomLayout = std::make_unique<CHyprCustomLayout>();
HyprlandAPI::addLayout(PHANDLE, "custom", g_pCustomLayout.get());
HyprlandAPI::addConfigValue(PHANDLE, "plugin:example:border_color", SConfigValue{.intValue = configStringToInt("rgb(44ee44)")});
HyprlandAPI::addDispatcher(PHANDLE, "example", [](std::string arg) { HyprlandAPI::addNotification(PHANDLE, "Arg passed: " + arg, CColor{0.5f, 0.5f, 0.7f, 1.0f}, 5000); });
// Hook a public member
g_pFocusHook = HyprlandAPI::createFunctionHook(PHANDLE, (void*)&CCompositor::focusWindow, (void*)&hkFocusWindow);
// Hook a public non-member
g_pMotionHook = HyprlandAPI::createFunctionHook(PHANDLE, (void*)&wlr_seat_pointer_notify_motion, (void*)&hkNotifyMotion);
// Hook a private member (!WARNING: the signature may differ in clang. This one is for gcc ONLY.)
g_pMouseDownHook = HyprlandAPI::createFunctionHook(
PHANDLE, HyprlandAPI::getFunctionAddressFromSignature(PHANDLE, "_ZN13CInputManager22processMouseDownNormalEP24wlr_pointer_button_event"), (void*)&hkProcessMouseDownNormal);
// Enable our hooks
g_pFocusHook->hook();
g_pMotionHook->hook();
g_pMouseDownHook->hook();
HyprlandAPI::reloadConfig();
return {"ExamplePlugin", "An example plugin", "Vaxry", "1.0"};
}
APICALL EXPORT void PLUGIN_EXIT() {
HyprlandAPI::invokeHyprctlCommand("seterror", "disable");
}

View file

@ -52,11 +52,14 @@
version = props.version + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty"); version = props.version + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty");
wlroots = wlroots-hyprland; wlroots = wlroots-hyprland;
inherit (inputs.hyprland-protocols.packages.${prev.stdenv.hostPlatform.system}) hyprland-protocols; inherit (inputs.hyprland-protocols.packages.${prev.stdenv.hostPlatform.system}) hyprland-protocols;
inherit udis86;
}; };
hyprland-debug = hyprland.override {debug = true;}; hyprland-debug = hyprland.override {debug = true;};
hyprland-no-hidpi = hyprland.override {hidpiXWayland = false;}; hyprland-no-hidpi = hyprland.override {hidpiXWayland = false;};
hyprland-nvidia = hyprland.override {nvidiaPatches = true;}; hyprland-nvidia = hyprland.override {nvidiaPatches = true;};
udis86 = prev.callPackage ./nix/udis86.nix {};
waybar-hyprland = prev.waybar.overrideAttrs (oldAttrs: { waybar-hyprland = prev.waybar.overrideAttrs (oldAttrs: {
postPatch = '' postPatch = ''
# use hyprctl to switch workspaces # use hyprctl to switch workspaces

View file

@ -41,6 +41,7 @@ commands:
switchxkblayout switchxkblayout
seterror seterror
setprop setprop
plugin
flags: flags:
-j -> output in JSON -j -> output in JSON
@ -349,6 +350,8 @@ int main(int argc, char** argv) {
request(fullRequest, 1); request(fullRequest, 1);
else if (fullRequest.contains("/setprop")) else if (fullRequest.contains("/setprop"))
request(fullRequest, 3); request(fullRequest, 3);
else if (fullRequest.contains("/plugin"))
request(fullRequest, 1);
else if (fullRequest.contains("/output")) else if (fullRequest.contains("/output"))
exitStatus = outputRequest(argc, argv); exitStatus = outputRequest(argc, argv);
else if (fullRequest.contains("/setcursor")) else if (fullRequest.contains("/setcursor"))

View file

@ -43,6 +43,10 @@ wlroots = subproject('wlroots', default_options: ['examples=false'])
have_xwlr = wlroots.get_variable('features').get('xwayland') have_xwlr = wlroots.get_variable('features').get('xwayland')
xcb_dep = dependency('xcb', required: get_option('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 if get_option('xwayland').enabled() and not have_xwlr
error('Cannot enable Xwayland in Hyprland: wlroots has been built without Xwayland support') error('Cannot enable Xwayland in Hyprland: wlroots has been built without Xwayland support')
endif endif

View file

@ -18,6 +18,7 @@
mount, mount,
pciutils, pciutils,
systemd, systemd,
udis86,
wayland, wayland,
wayland-protocols, wayland-protocols,
wayland-scanner, wayland-scanner,
@ -72,6 +73,7 @@ in
libinput libinput
libxkbcommon libxkbcommon
mesa mesa
udis86
wayland wayland
wayland-protocols wayland-protocols
wayland-scanner wayland-scanner

View file

@ -1,34 +1,50 @@
diff --git a/meson.build b/meson.build diff --git a/meson.build b/meson.build
index 22ee4bf..5528613 100644 index f380255..abd7cd3 100644
--- a/meson.build --- a/meson.build
+++ b/meson.build +++ b/meson.build
@@ -2,16 +2,10 @@ project('Hyprland', 'cpp', 'c', @@ -39,23 +39,8 @@ add_project_arguments(
version : '0.1', ],
default_options : ['warning_level=3', 'cpp_std=c++20', 'default_library=static']) language: 'cpp')
-wlroots = subproject('wlroots', default_options: ['examples=false']) -wlroots = subproject('wlroots', default_options: ['examples=false'])
-have_xwlr = wlroots.get_variable('features').get('xwayland') -have_xwlr = wlroots.get_variable('features').get('xwayland')
+wlroots = dependency('wlroots', version: '>=0.16.0')
xcb_dep = dependency('xcb', required: get_option('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 -if get_option('xwayland').enabled() and not have_xwlr
- error('Cannot enable Xwayland in Hyprland: wlroots has been built without Xwayland support') - error('Cannot enable Xwayland in Hyprland: wlroots has been built without Xwayland support')
-endif -endif
-have_xwayland = xcb_dep.found() and have_xwlr -have_xwayland = xcb_dep.found() and have_xwlr
- -
-if not have_xwayland -if not have_xwayland
+if not xcb_dep.found() -add_project_arguments('-DNO_XWAYLAND', language: 'cpp')
add_project_arguments('-DNO_XWAYLAND', language: 'cpp') -endif
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 diff --git a/src/meson.build b/src/meson.build
index 5d64188..a676333 100644 index 7b658d3..da8baa5 100644
--- a/src/meson.build --- a/src/meson.build
+++ b/src/meson.build +++ b/src/meson.build
@@ -7,5 +7,5 @@ executable('Hyprland', src, @@ -7,7 +7,7 @@ executable('Hyprland', src,
server_protos, server_protos,
dependency('wayland-server'), dependency('wayland-server'),
dependency('wayland-client'), dependency('wayland-client'),
- wlroots.get_variable('wlroots'), - wlroots.get_variable('wlroots'),
+ wlroots, + dependency('wlroots'),
dependency('cairo'), 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'),

32
nix/udis86.nix Normal file
View file

@ -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;
};
}

View file

@ -18,6 +18,12 @@ int handleCritSignal(int signo, void* data) {
} }
void handleSegv(int sig) { void handleSegv(int sig) {
if (g_pHookSystem->m_bCurrentEventPlugin) {
longjmp(g_pHookSystem->m_jbHookFaultJumpBuf, 1);
return;
}
CrashReporter::createAndSaveCrash(); CrashReporter::createAndSaveCrash();
abort(); abort();
} }
@ -364,6 +370,12 @@ void CCompositor::startCompositor() {
Debug::log(LOG, "Creating the HyprDebugOverlay!"); Debug::log(LOG, "Creating the HyprDebugOverlay!");
g_pDebugOverlay = std::make_unique<CHyprDebugOverlay>(); g_pDebugOverlay = std::make_unique<CHyprDebugOverlay>();
Debug::log(LOG, "Creating the HyprNotificationOverlay!");
g_pHyprNotificationOverlay = std::make_unique<CHyprNotificationOverlay>();
Debug::log(LOG, "Creating the PluginSystem!");
g_pPluginSystem = std::make_unique<CPluginSystem>();
// //
// //
@ -2012,6 +2024,9 @@ void CCompositor::scheduleFrameForMonitor(CMonitor* pMonitor) {
if (!pMonitor->m_bEnabled) if (!pMonitor->m_bEnabled)
return; return;
if (pMonitor->renderingActive)
pMonitor->pendingFrame = true;
wlr_output_schedule_frame(pMonitor->output); wlr_output_schedule_frame(pMonitor->output);
} }

View file

@ -19,12 +19,14 @@
#include "managers/SessionLockManager.hpp" #include "managers/SessionLockManager.hpp"
#include "managers/HookSystemManager.hpp" #include "managers/HookSystemManager.hpp"
#include "debug/HyprDebugOverlay.hpp" #include "debug/HyprDebugOverlay.hpp"
#include "debug/HyprNotificationOverlay.hpp"
#include "helpers/Monitor.hpp" #include "helpers/Monitor.hpp"
#include "helpers/Workspace.hpp" #include "helpers/Workspace.hpp"
#include "Window.hpp" #include "Window.hpp"
#include "render/Renderer.hpp" #include "render/Renderer.hpp"
#include "render/OpenGL.hpp" #include "render/OpenGL.hpp"
#include "hyprerror/HyprError.hpp" #include "hyprerror/HyprError.hpp"
#include "plugins/PluginSystem.hpp"
class CCompositor { class CCompositor {
public: public:

View file

@ -271,7 +271,7 @@ void CConfigManager::init() {
void CConfigManager::configSetValueSafe(const std::string& COMMAND, const std::string& VALUE) { void CConfigManager::configSetValueSafe(const std::string& COMMAND, const std::string& VALUE) {
if (configValues.find(COMMAND) == configValues.end()) { 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] == '$') { if (COMMAND[0] == '$') {
// register a dynamic var // register a dynamic var
Debug::log(LOG, "Registered dynamic var \"%s\" -> %s", COMMAND.c_str(), VALUE.c_str()); 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); 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 { } else {
CONFIGENTRY = &configValues.at(COMMAND); CONFIGENTRY = &configValues.at(COMMAND);
} }
@ -1649,8 +1661,17 @@ SConfigValue* CConfigManager::getConfigValuePtr(const std::string& val) {
SConfigValue* CConfigManager::getConfigValuePtrSafe(const std::string& val) { SConfigValue* CConfigManager::getConfigValuePtrSafe(const std::string& val) {
const auto IT = configValues.find(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 nullptr;
}
return &(IT->second); return &(IT->second);
} }
@ -1800,3 +1821,19 @@ ICustomConfigValueData::~ICustomConfigValueData() {
std::unordered_map<std::string, SAnimationPropertyConfig> CConfigManager::getAnimationConfig() { std::unordered_map<std::string, SAnimationPropertyConfig> CConfigManager::getAnimationConfig() {
return animationConfig; return animationConfig;
} }
void CConfigManager::addPluginConfigVar(HANDLE handle, const std::string& name, const SConfigValue& value) {
auto CONFIGMAPIT = std::find_if(pluginConfigs.begin(), pluginConfigs.end(), [&](const auto& other) { return other.first == handle; });
if (CONFIGMAPIT == pluginConfigs.end()) {
pluginConfigs.emplace(
std::pair<HANDLE, std::unique_ptr<std::unordered_map<std::string, SConfigValue>>>(handle, std::make_unique<std::unordered_map<std::string, SConfigValue>>()));
CONFIGMAPIT = std::find_if(pluginConfigs.begin(), pluginConfigs.end(), [&](const auto& other) { return other.first == handle; });
}
(*CONFIGMAPIT->second)[name] = value;
}
void CConfigManager::removePluginConfig(HANDLE handle) {
std::erase_if(pluginConfigs, [&](const auto& other) { return other.first == handle; });
}

View file

@ -21,6 +21,8 @@
#define INITANIMCFG(name) animationConfig[name] = {} #define INITANIMCFG(name) animationConfig[name] = {}
#define CREATEANIMCFG(name, parent) animationConfig[name] = {false, "", "", 0.f, -1, &animationConfig["global"], &animationConfig[parent]} #define CREATEANIMCFG(name, parent) animationConfig[name] = {false, "", "", 0.f, -1, &animationConfig["global"], &animationConfig[parent]}
#define HANDLE void*
struct SConfigValue { struct SConfigValue {
int64_t intValue = -INT64_MAX; int64_t intValue = -INT64_MAX;
float floatValue = -__FLT_MAX__; float floatValue = -__FLT_MAX__;
@ -159,6 +161,9 @@ class CConfigManager {
std::unordered_map<std::string, SAnimationPropertyConfig> getAnimationConfig(); std::unordered_map<std::string, SAnimationPropertyConfig> getAnimationConfig();
void addPluginConfigVar(HANDLE handle, const std::string& name, const SConfigValue& value);
void removePluginConfig(HANDLE handle);
// no-op when done. // no-op when done.
void dispatchExecOnce(); void dispatchExecOnce();
@ -180,33 +185,35 @@ class CConfigManager {
std::string configCurrentPath; std::string configCurrentPath;
private: private:
std::deque<std::string> configPaths; // stores all the config paths std::deque<std::string> configPaths; // stores all the config paths
std::unordered_map<std::string, time_t> configModifyTimes; // stores modify times std::unordered_map<std::string, time_t> configModifyTimes; // stores modify times
std::unordered_map<std::string, std::string> configDynamicVars; // stores dynamic vars declared by the user std::unordered_map<std::string, std::string> configDynamicVars; // stores dynamic vars declared by the user
std::unordered_map<std::string, SConfigValue> configValues; std::unordered_map<std::string, SConfigValue> configValues;
std::unordered_map<std::string, std::unordered_map<std::string, SConfigValue>> deviceConfigs; // stores device configs std::unordered_map<std::string, std::unordered_map<std::string, SConfigValue>> deviceConfigs; // stores device configs
std::unordered_map<std::string, SAnimationPropertyConfig> animationConfig; // stores all the animations with their set values std::unordered_map<std::string, SAnimationPropertyConfig> animationConfig; // stores all the animations with their set values
std::string currentCategory = ""; // For storing the category of the current item std::string currentCategory = ""; // For storing the category of the current item
std::string parseError = ""; // For storing a parse error to display later std::string parseError = ""; // For storing a parse error to display later
std::string m_szCurrentSubmap = ""; // For storing the current keybind submap std::string m_szCurrentSubmap = ""; // For storing the current keybind submap
std::vector<std::pair<std::string, std::string>> boundWorkspaces; std::vector<std::pair<std::string, std::string>> boundWorkspaces;
std::vector<SExecRequestedRule> execRequestedRules; // rules requested with exec, e.g. [workspace 2] kitty std::vector<SExecRequestedRule> execRequestedRules; // rules requested with exec, e.g. [workspace 2] kitty
bool isFirstLaunch = true; // For exec-once std::unordered_map<HANDLE, std::unique_ptr<std::unordered_map<std::string, SConfigValue>>> pluginConfigs; // stores plugin configs
std::deque<SMonitorRule> m_dMonitorRules; bool isFirstLaunch = true; // For exec-once
std::deque<SWindowRule> m_dWindowRules;
std::deque<SLayerRule> m_dLayerRules;
std::deque<std::string> m_dBlurLSNamespaces;
bool firstExecDispatched = false; std::deque<SMonitorRule> m_dMonitorRules;
std::deque<std::string> firstExecRequests; std::deque<SWindowRule> m_dWindowRules;
std::deque<SLayerRule> m_dLayerRules;
std::deque<std::string> m_dBlurLSNamespaces;
bool firstExecDispatched = false;
std::deque<std::string> firstExecRequests;
// internal methods // internal methods
void setDefaultVars(); void setDefaultVars();

View file

@ -4,10 +4,13 @@
#include <execinfo.h> #include <execinfo.h>
#include <fstream> #include <fstream>
#include "../plugins/PluginSystem.hpp"
#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) #if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__)
#include <sys/sysctl.h> #include <sys/sysctl.h>
#endif #endif
std::string getRandomMessage() { std::string getRandomMessage() {
const std::vector<std::string> MESSAGES = {"Sorry, didn't mean to...", const std::vector<std::string> MESSAGES = {"Sorry, didn't mean to...",
@ -43,6 +46,16 @@ void CrashReporter::createAndSaveCrash() {
finalCrashReport += "Hyprland received signal 11 (SIGSEGV): Segmentation Fault\n\n"; 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"; finalCrashReport += "System info:\n";
struct utsname unameInfo; struct utsname unameInfo;

View file

@ -1087,6 +1087,50 @@ std::string dispatchOutput(std::string request) {
return "ok"; 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) { std::string getReply(std::string request) {
auto format = HyprCtl::FORMAT_NORMAL; auto format = HyprCtl::FORMAT_NORMAL;
@ -1134,6 +1178,8 @@ std::string getReply(std::string request) {
return bindsRequest(format); return bindsRequest(format);
else if (request == "animations") else if (request == "animations")
return animationsRequest(format); return animationsRequest(format);
else if (request.find("plugin") == 0)
return dispatchPlugin(request);
else if (request.find("setprop") == 0) else if (request.find("setprop") == 0)
return dispatchSetProp(request); return dispatchSetProp(request);
else if (request.find("seterror") == 0) else if (request.find("seterror") == 0)
@ -1156,6 +1202,10 @@ std::string getReply(std::string request) {
return "unknown request"; return "unknown request";
} }
std::string HyprCtl::makeDynamicCall(const std::string& input) {
return getReply(input);
}
int hyprCtlFDTick(int fd, uint32_t mask, void* data) { int hyprCtlFDTick(int fd, uint32_t mask, void* data) {
if (mask & WL_EVENT_ERROR || mask & WL_EVENT_HANGUP) if (mask & WL_EVENT_ERROR || mask & WL_EVENT_HANGUP)
return 0; return 0;

View file

@ -5,7 +5,8 @@
#include "../helpers/MiscFunctions.hpp" #include "../helpers/MiscFunctions.hpp"
namespace HyprCtl { namespace HyprCtl {
void startHyprCtlSocket(); void startHyprCtlSocket();
std::string makeDynamicCall(const std::string& input);
// very simple thread-safe request method // very simple thread-safe request method
inline bool requestMade = false; inline bool requestMade = false;
@ -18,8 +19,7 @@ namespace HyprCtl {
inline int iSocketFD = -1; inline int iSocketFD = -1;
enum eHyprCtlOutputFormat enum eHyprCtlOutputFormat {
{
FORMAT_NORMAL = 0, FORMAT_NORMAL = 0,
FORMAT_JSON FORMAT_JSON
}; };

View file

@ -0,0 +1,155 @@
#include "HyprNotificationOverlay.hpp"
#include "../Compositor.hpp"
CHyprNotificationOverlay::CHyprNotificationOverlay() {
g_pHookSystem->hookDynamic("focusedMon", [&](void* self, std::any param) {
if (m_dNotifications.size() == 0)
return;
g_pHyprRenderer->damageBox(&m_bLastDamage);
});
}
void CHyprNotificationOverlay::addNotification(const std::string& text, const CColor& color, const float timeMs) {
const auto PNOTIF = m_dNotifications.emplace_back(std::make_unique<SNotification>()).get();
PNOTIF->text = text;
PNOTIF->color = color;
PNOTIF->started.reset();
PNOTIF->timeMs = timeMs;
}
wlr_box CHyprNotificationOverlay::drawNotifications(CMonitor* pMonitor) {
static constexpr auto ANIM_DURATION_MS = 600.0;
static constexpr auto ANIM_LAG_MS = 100.0;
static constexpr auto NOTIF_LEFTBAR_SIZE = 5.0;
float offsetY = 10;
float maxWidth = 0;
const auto SCALE = pMonitor->scale;
const auto FONTSIZE = std::clamp((int)(10.f * ((pMonitor->vecPixelSize.x * SCALE) / 1920.f)), 8, 40);
const auto MONSIZE = pMonitor->vecPixelSize;
cairo_select_font_face(m_pCairo, "Noto Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
cairo_set_font_size(m_pCairo, FONTSIZE);
cairo_text_extents_t cairoExtents;
const auto PBEZIER = g_pAnimationManager->getBezier("default");
for (auto& notif : m_dNotifications) {
// first rect (bg, col)
const float FIRSTRECTANIMP =
(notif->started.getMillis() > (ANIM_DURATION_MS - ANIM_LAG_MS) ?
(notif->started.getMillis() > notif->timeMs - (ANIM_DURATION_MS - ANIM_LAG_MS) ? notif->timeMs - notif->started.getMillis() : (ANIM_DURATION_MS - ANIM_LAG_MS)) :
notif->started.getMillis()) /
(ANIM_DURATION_MS - ANIM_LAG_MS);
const float FIRSTRECTPERC = FIRSTRECTANIMP >= 0.99f ? 1.f : PBEZIER->getYForPoint(FIRSTRECTANIMP);
// second rect (fg, black)
const float SECONDRECTANIMP = (notif->started.getMillis() > ANIM_DURATION_MS ?
(notif->started.getMillis() > notif->timeMs - ANIM_DURATION_MS ? notif->timeMs - notif->started.getMillis() : ANIM_DURATION_MS) :
notif->started.getMillis()) /
ANIM_DURATION_MS;
const float SECONDRECTPERC = SECONDRECTANIMP >= 0.99f ? 1.f : PBEZIER->getYForPoint(SECONDRECTANIMP);
// third rect (horiz, col)
const float THIRDRECTPERC = notif->started.getMillis() / notif->timeMs;
// get text size
cairo_text_extents(m_pCairo, notif->text.c_str(), &cairoExtents);
cairo_set_source_rgba(m_pCairo, notif->color.r, notif->color.g, notif->color.b, notif->color.a);
const auto NOTIFSIZE = Vector2D{cairoExtents.width + 20, cairoExtents.height + 10};
// draw rects
cairo_rectangle(m_pCairo, MONSIZE.x - (NOTIFSIZE.x + NOTIF_LEFTBAR_SIZE) * FIRSTRECTPERC, offsetY, (NOTIFSIZE.x + NOTIF_LEFTBAR_SIZE) * FIRSTRECTPERC, NOTIFSIZE.y);
cairo_fill(m_pCairo);
cairo_set_source_rgb(m_pCairo, 0.f, 0.f, 0.f);
cairo_rectangle(m_pCairo, MONSIZE.x - NOTIFSIZE.x * SECONDRECTPERC, offsetY, NOTIFSIZE.x * SECONDRECTPERC, NOTIFSIZE.y);
cairo_fill(m_pCairo);
cairo_set_source_rgba(m_pCairo, notif->color.r, notif->color.g, notif->color.b, notif->color.a);
cairo_rectangle(m_pCairo, MONSIZE.x - NOTIFSIZE.x * SECONDRECTPERC + 3, offsetY + NOTIFSIZE.y - 4, THIRDRECTPERC * (NOTIFSIZE.x - 6), 2);
cairo_fill(m_pCairo);
// draw text
cairo_set_source_rgb(m_pCairo, 1.f, 1.f, 1.f);
cairo_move_to(m_pCairo, MONSIZE.x - NOTIFSIZE.x * SECONDRECTPERC + NOTIF_LEFTBAR_SIZE, offsetY + FONTSIZE + (FONTSIZE / 10.0));
cairo_show_text(m_pCairo, notif->text.c_str());
// adjust offset and move on
offsetY += NOTIFSIZE.y + 10;
if (maxWidth < NOTIFSIZE.x)
maxWidth = NOTIFSIZE.x;
}
// cleanup notifs
std::erase_if(m_dNotifications, [](const auto& notif) { return notif->started.getMillis() > notif->timeMs; });
return wlr_box{(int)(pMonitor->vecPosition.x + pMonitor->vecSize.x - maxWidth - 20), (int)pMonitor->vecPosition.y, (int)maxWidth + 20, (int)offsetY + 10};
}
void CHyprNotificationOverlay::draw(CMonitor* pMonitor) {
if (m_pLastMonitor != pMonitor || !m_pCairo || !m_pCairoSurface) {
if (m_pCairo && m_pCairoSurface) {
cairo_destroy(m_pCairo);
cairo_surface_destroy(m_pCairoSurface);
}
m_pCairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, pMonitor->vecPixelSize.x, pMonitor->vecPixelSize.y);
m_pCairo = cairo_create(m_pCairoSurface);
m_pLastMonitor = pMonitor;
}
// Draw the notifications
if (m_dNotifications.size() == 0)
return;
// Render to the monitor
// clear the pixmap
cairo_save(m_pCairo);
cairo_set_operator(m_pCairo, CAIRO_OPERATOR_CLEAR);
cairo_paint(m_pCairo);
cairo_restore(m_pCairo);
cairo_surface_flush(m_pCairoSurface);
wlr_box damage = drawNotifications(pMonitor);
g_pHyprRenderer->damageBox(&damage);
g_pHyprRenderer->damageBox(&m_bLastDamage);
g_pCompositor->scheduleFrameForMonitor(pMonitor);
m_bLastDamage = damage;
// copy the data to an OpenGL texture we have
const auto DATA = cairo_image_surface_get_data(m_pCairoSurface);
m_tTexture.allocate();
glBindTexture(GL_TEXTURE_2D, m_tTexture.m_iTexID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
#ifndef GLES2
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
#endif
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pMonitor->vecPixelSize.x, pMonitor->vecPixelSize.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA);
wlr_box pMonBox = {0, 0, pMonitor->vecPixelSize.x, pMonitor->vecPixelSize.y};
g_pHyprOpenGL->renderTexture(m_tTexture, &pMonBox, 1.f);
}

View file

@ -0,0 +1,40 @@
#pragma once
#include "../defines.hpp"
#include "../helpers/Timer.hpp"
#include "../helpers/Monitor.hpp"
#include "../render/Texture.hpp"
#include <deque>
#include <cairo/cairo.h>
struct SNotification {
std::string text = "";
CColor color;
CTimer started;
float timeMs = 0;
};
class CHyprNotificationOverlay {
public:
CHyprNotificationOverlay();
void draw(CMonitor* pMonitor);
void addNotification(const std::string& text, const CColor& color, const float timeMs);
private:
wlr_box drawNotifications(CMonitor* pMonitor);
wlr_box m_bLastDamage;
std::deque<std::unique_ptr<SNotification>> m_dNotifications;
cairo_surface_t* m_pCairoSurface = nullptr;
cairo_t* m_pCairo = nullptr;
CMonitor* m_pLastMonitor = nullptr;
CTexture m_tTexture;
};
inline std::unique_ptr<CHyprNotificationOverlay> g_pHyprNotificationOverlay;

View file

@ -89,4 +89,4 @@
#define SPECIAL_WORKSPACE_START (-99) #define SPECIAL_WORKSPACE_START (-99)
#define PI 3.14159265358979 #define PI 3.14159265358979

View file

@ -197,6 +197,8 @@ void Events::listener_monitorFrame(void* owner, void* data) {
return; return;
} }
PMONITOR->renderingActive = true;
// we need to cleanup fading out when rendering the appropriate context // we need to cleanup fading out when rendering the appropriate context
g_pCompositor->cleanupFadingOut(PMONITOR->ID); g_pCompositor->cleanupFadingOut(PMONITOR->ID);
@ -207,6 +209,8 @@ void Events::listener_monitorFrame(void* owner, void* data) {
if (*PDAMAGEBLINK || *PVFR == 0) if (*PDAMAGEBLINK || *PVFR == 0)
g_pCompositor->scheduleFrameForMonitor(PMONITOR); g_pCompositor->scheduleFrameForMonitor(PMONITOR);
PMONITOR->renderingActive = false;
return; return;
} }
@ -260,6 +264,9 @@ void Events::listener_monitorFrame(void* owner, void* data) {
if (PMONITOR == g_pCompositor->m_vMonitors.front().get()) if (PMONITOR == g_pCompositor->m_vMonitors.front().get())
g_pHyprError->draw(); g_pHyprError->draw();
if (PMONITOR == g_pCompositor->m_pLastMonitor)
g_pHyprNotificationOverlay->draw(PMONITOR);
// for drawing the debug overlay // for drawing the debug overlay
if (PMONITOR == g_pCompositor->m_vMonitors.front().get() && *PDEBUGOVERLAY == 1) { if (PMONITOR == g_pCompositor->m_vMonitors.front().get() && *PDEBUGOVERLAY == 1) {
startRenderOverlay = std::chrono::high_resolution_clock::now(); 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(&frameDamage);
pixman_region32_fini(&damage); pixman_region32_fini(&damage);
PMONITOR->renderingActive = false;
if (!wlr_output_commit(PMONITOR->output)) if (!wlr_output_commit(PMONITOR->output))
return; return;
if (*PDAMAGEBLINK || *PVFR == 0) if (*PDAMAGEBLINK || *PVFR == 0 || PMONITOR->pendingFrame)
g_pCompositor->scheduleFrameForMonitor(PMONITOR); g_pCompositor->scheduleFrameForMonitor(PMONITOR);
PMONITOR->pendingFrame = false;
if (*PDEBUGOVERLAY == 1) { if (*PDEBUGOVERLAY == 1) {
const float µs = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now() - startRender).count() / 1000.f; const float µs = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now() - startRender).count() / 1000.f;
g_pDebugOverlay->renderData(PMONITOR, µs); g_pDebugOverlay->renderData(PMONITOR, µs);

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
#include "../includes.hpp" #include <cstdint>
class CColor { class CColor {
public: public:
@ -13,7 +13,7 @@ class CColor {
uint64_t getAsHex(); uint64_t getAsHex();
CColor operator-(const CColor& c2) const { 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 { CColor operator+(const CColor& c2) const {

View file

@ -42,6 +42,9 @@ class CMonitor {
bool enabled10bit = false; // as above, this can be TRUE even if 10 bit failed. bool enabled10bit = false; // as above, this can be TRUE even if 10 bit failed.
bool createdByUser = false; bool createdByUser = false;
bool pendingFrame = false; // if we schedule a frame during rendering, reschedule it after
bool renderingActive = false;
// mirroring // mirroring
CMonitor* pMirrorOf = nullptr; CMonitor* pMirrorOf = nullptr;
std::vector<CMonitor*> mirrors; std::vector<CMonitor*> mirrors;

View file

@ -1,44 +1,79 @@
#include "HookSystemManager.hpp" #include "HookSystemManager.hpp"
#include "../plugins/PluginSystem.hpp"
CHookSystemManager::CHookSystemManager() { CHookSystemManager::CHookSystemManager() {
; // ; //
} }
// returns the pointer to the function // 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 PVEC = getVecForEvent(event);
const auto PFN = &m_lCallbackFunctions.emplace_back(fn); const auto PFN = &m_lCallbackFunctions.emplace_back(fn);
PVEC->emplace_back(PFN); PVEC->emplace_back(SCallbackFNPtr{PFN, handle});
return PFN; 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); const auto PVEC = getVecForEvent(event);
PVEC->emplace_back(fn); PVEC->emplace_back(SCallbackFNPtr{fn, handle});
} }
void CHookSystemManager::unhook(HOOK_CALLBACK_FN* fn) { void CHookSystemManager::unhook(HOOK_CALLBACK_FN* fn) {
std::erase_if(m_lCallbackFunctions, [&](const auto& other) { return &other == fn; }); std::erase_if(m_lCallbackFunctions, [&](const auto& other) { return &other == fn; });
for (auto& [k, v] : m_lpRegisteredHooks) { for (auto& [k, v] : m_lpRegisteredHooks) {
std::erase_if(v, [&](const auto& other) { return other == fn; }); std::erase_if(v, [&](const auto& other) { return other.fn == fn; });
} }
} }
void CHookSystemManager::emit(const std::vector<HOOK_CALLBACK_FN*>* callbacks, std::any data) { void CHookSystemManager::emit(const std::vector<SCallbackFNPtr>* callbacks, std::any data) {
if (callbacks->empty()) if (callbacks->empty())
return; return;
for (auto& cb : *callbacks) std::vector<HANDLE> faultyHandles;
(*cb)(cb, data);
for (auto& cb : *callbacks) {
m_bCurrentEventPlugin = false;
if (!cb.handle) {
// we don't guard hl hooks
(*cb.fn)(cb.fn, data);
continue;
}
m_bCurrentEventPlugin = true;
if (std::find(faultyHandles.begin(), faultyHandles.end(), cb.handle) != faultyHandles.end())
continue;
try {
if (!setjmp(m_jbHookFaultJumpBuf))
(*cb.fn)(cb.fn, data);
else {
// this module crashed.
throw std::exception();
}
} catch (std::exception& e) {
// TODO: this works only once...?
faultyHandles.push_back(cb.handle);
Debug::log(ERR, " [hookSystem] Hook from plugin %lx caused a SIGSEGV, queueing for unloading.", cb.handle);
}
}
if (!faultyHandles.empty()) {
for (auto& h : faultyHandles)
g_pPluginSystem->unloadPlugin(g_pPluginSystem->getPluginByHandle(h), true);
}
} }
std::vector<HOOK_CALLBACK_FN*>* CHookSystemManager::getVecForEvent(const std::string& event) { std::vector<SCallbackFNPtr>* CHookSystemManager::getVecForEvent(const std::string& event) {
auto IT = std::find_if(m_lpRegisteredHooks.begin(), m_lpRegisteredHooks.end(), [&](const auto& other) { return other.first == event; }); auto IT = std::find_if(m_lpRegisteredHooks.begin(), m_lpRegisteredHooks.end(), [&](const auto& other) { return other.first == event; });
if (IT != m_lpRegisteredHooks.end()) if (IT != m_lpRegisteredHooks.end())
return &IT->second; return &IT->second;
Debug::log(LOG, "[hookSystem] New hook event registered: %s", event.c_str()); Debug::log(LOG, " [hookSystem] New hook event registered: %s", event.c_str());
return &m_lpRegisteredHooks.emplace_back(std::make_pair<>(event, std::vector<HOOK_CALLBACK_FN*>{})).second; return &m_lpRegisteredHooks.emplace_back(std::make_pair<>(event, std::vector<SCallbackFNPtr>{})).second;
} }

View file

@ -4,11 +4,21 @@
#include <unordered_map> #include <unordered_map>
#include <any> #include <any>
#include <array>
#include <list> #include <list>
#include <csetjmp>
#include "../plugins/PluginAPI.hpp"
// global typedef for hooked functions. Passes itself as a ptr when called, and `data` additionally. // global typedef for hooked functions. Passes itself as a ptr when called, and `data` additionally.
typedef std::function<void(void*, std::any)> HOOK_CALLBACK_FN; typedef std::function<void(void*, std::any)> HOOK_CALLBACK_FN;
struct SCallbackFNPtr {
HOOK_CALLBACK_FN* fn = nullptr;
HANDLE handle = nullptr;
};
#define EMIT_HOOK_EVENT(name, param) \ #define EMIT_HOOK_EVENT(name, param) \
{ \ { \
static auto* const PEVENTVEC = g_pHookSystem->getVecForEvent(name); \ static auto* const PEVENTVEC = g_pHookSystem->getVecForEvent(name); \
@ -20,17 +30,20 @@ class CHookSystemManager {
CHookSystemManager(); CHookSystemManager();
// returns the pointer to the function // returns the pointer to the function
HOOK_CALLBACK_FN* hookDynamic(const std::string& event, 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); void hookStatic(const std::string& event, HOOK_CALLBACK_FN* fn, HANDLE handle = nullptr);
void unhook(HOOK_CALLBACK_FN* fn); void unhook(HOOK_CALLBACK_FN* fn);
void emit(const std::vector<HOOK_CALLBACK_FN*>* callbacks, std::any data = 0); void emit(const std::vector<SCallbackFNPtr>* callbacks, std::any data = 0);
std::vector<HOOK_CALLBACK_FN*>* getVecForEvent(const std::string& event); std::vector<SCallbackFNPtr>* getVecForEvent(const std::string& event);
bool m_bCurrentEventPlugin = false;
jmp_buf m_jbHookFaultJumpBuf;
private: private:
// todo: this is slow. Maybe static ptrs should be somehow allowed. unique ptr for vec? // todo: this is slow. Maybe static ptrs should be somehow allowed. unique ptr for vec?
std::list<std::pair<std::string, std::vector<HOOK_CALLBACK_FN*>>> m_lpRegisteredHooks; std::list<std::pair<std::string, std::vector<SCallbackFNPtr>>> m_lpRegisteredHooks;
std::list<HOOK_CALLBACK_FN> m_lCallbackFunctions; std::list<HOOK_CALLBACK_FN> m_lCallbackFunctions;
}; };
inline std::unique_ptr<CHookSystemManager> g_pHookSystem; inline std::unique_ptr<CHookSystemManager> g_pHookSystem;

View file

@ -8,6 +8,7 @@
class CInputManager; class CInputManager;
class CConfigManager; class CConfigManager;
class CPluginSystem;
struct SKeybind { struct SKeybind {
std::string key = ""; std::string key = "";

View file

@ -1,29 +1,51 @@
#include "LayoutManager.hpp" #include "LayoutManager.hpp"
IHyprLayout* CLayoutManager::getCurrentLayout() { CLayoutManager::CLayoutManager() {
switch (m_iCurrentLayoutID) { m_vLayouts.emplace_back(std::make_pair<>("dwindle", &m_cDwindleLayout));
case LAYOUT_DWINDLE: return &m_cDwindleLayout; m_vLayouts.emplace_back(std::make_pair<>("master", &m_cMasterLayout));
case LAYOUT_MASTER: return &m_cMasterLayout; }
}
// fallback IHyprLayout* CLayoutManager::getCurrentLayout() {
return &m_cDwindleLayout; return m_vLayouts[m_iCurrentLayoutID].second;
} }
void CLayoutManager::switchToLayout(std::string layout) { void CLayoutManager::switchToLayout(std::string layout) {
if (layout == "dwindle") { for (size_t i = 0; i < m_vLayouts.size(); ++i) {
if (m_iCurrentLayoutID != LAYOUT_DWINDLE) { if (m_vLayouts[i].first == layout) {
getCurrentLayout()->onDisable(); getCurrentLayout()->onDisable();
m_iCurrentLayoutID = LAYOUT_DWINDLE; m_iCurrentLayoutID = i;
getCurrentLayout()->onEnable(); 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;
} }

View file

@ -5,20 +5,28 @@
class CLayoutManager { class CLayoutManager {
public: public:
CLayoutManager();
IHyprLayout* getCurrentLayout(); IHyprLayout* getCurrentLayout();
void switchToLayout(std::string); void switchToLayout(std::string);
bool addLayout(const std::string& name, IHyprLayout* layout);
bool removeLayout(IHyprLayout* layout);
private: private:
enum HYPRLAYOUTS { enum HYPRLAYOUTS
{
LAYOUT_DWINDLE = 0, LAYOUT_DWINDLE = 0,
LAYOUT_MASTER LAYOUT_MASTER
}; };
HYPRLAYOUTS m_iCurrentLayoutID = LAYOUT_DWINDLE; int m_iCurrentLayoutID = LAYOUT_DWINDLE;
CHyprDwindleLayout m_cDwindleLayout; CHyprDwindleLayout m_cDwindleLayout;
CHyprMasterLayout m_cMasterLayout; CHyprMasterLayout m_cMasterLayout;
std::vector<std::pair<std::string, IHyprLayout*>> m_vLayouts;
}; };
inline std::unique_ptr<CLayoutManager> g_pLayoutManager; inline std::unique_ptr<CLayoutManager> g_pLayoutManager;

View file

@ -16,6 +16,7 @@ executable('Hyprland', src,
xcb_dep, xcb_dep,
backtrace_dep, backtrace_dep,
systemd_dep, systemd_dep,
udis86,
dependency('pixman-1'), dependency('pixman-1'),
dependency('gl', 'opengl'), dependency('gl', 'opengl'),

151
src/plugins/HookSystem.cpp Normal file
View file

@ -0,0 +1,151 @@
#include "HookSystem.hpp"
#define register
#include <udis86.h>
#undef register
#include <sys/mman.h>
#include <unistd.h>
#include <cstring>
CFunctionHook::CFunctionHook(HANDLE owner, void* source, void* destination) {
m_pSource = source;
m_pDestination = destination;
m_pOwner = owner;
}
CFunctionHook::~CFunctionHook() {
if (m_bActive) {
unhook();
}
}
size_t getInstructionLenAt(void* start) {
ud_t udis;
ud_init(&udis);
ud_set_mode(&udis, 64);
ud_set_syntax(&udis, UD_SYN_INTEL);
size_t curOffset = 1;
size_t insSize = 0;
while (true) {
ud_set_input_buffer(&udis, (uint8_t*)start, curOffset);
insSize = ud_disassemble(&udis);
if (insSize != curOffset)
break;
curOffset++;
}
return insSize;
}
size_t probeMinimumJumpSize(void* start, size_t min) {
size_t size = 0;
while (size <= min) {
// find info about this instruction
size_t insLen = getInstructionLenAt(start + size);
size += insLen;
}
return size;
}
bool CFunctionHook::hook() {
// check for unsupported platforms
#if !defined(__x86_64__)
return false;
#endif
// movabs $0,%rax | jmpq *%rax
static constexpr uint8_t ABSOLUTE_JMP_ADDRESS[] = {0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0};
// pushq %rax
static constexpr uint8_t PUSH_RAX[] = {0x50};
// popq %rax
static constexpr uint8_t POP_RAX[] = {0x58};
// nop
static constexpr uint8_t NOP = 0x90;
// get minimum size to overwrite
const auto HOOKSIZE = probeMinimumJumpSize(m_pSource, sizeof(ABSOLUTE_JMP_ADDRESS) + sizeof(PUSH_RAX) + sizeof(POP_RAX));
// alloc trampoline
m_pTrampolineAddr = mmap(NULL, sizeof(ABSOLUTE_JMP_ADDRESS) + HOOKSIZE + sizeof(PUSH_RAX), PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
// populate trampoline
memcpy(m_pTrampolineAddr, m_pSource, HOOKSIZE); // first, original func bytes
memcpy(m_pTrampolineAddr + HOOKSIZE, PUSH_RAX, sizeof(PUSH_RAX)); // then, pushq %rax
memcpy(m_pTrampolineAddr + HOOKSIZE + sizeof(PUSH_RAX), ABSOLUTE_JMP_ADDRESS, sizeof(ABSOLUTE_JMP_ADDRESS)); // then, jump to source
// fixup trampoline addr
*(uint64_t*)(m_pTrampolineAddr + HOOKSIZE + 2 + sizeof(PUSH_RAX)) = (uint64_t)(m_pSource + sizeof(ABSOLUTE_JMP_ADDRESS));
// make jump to hk
mprotect(m_pSource - ((uint64_t)m_pSource) % sysconf(_SC_PAGE_SIZE), sysconf(_SC_PAGE_SIZE), PROT_READ | PROT_WRITE | PROT_EXEC);
memcpy(m_pSource, ABSOLUTE_JMP_ADDRESS, sizeof(ABSOLUTE_JMP_ADDRESS));
// make popq %rax and NOP all remaining
memcpy(m_pSource + sizeof(ABSOLUTE_JMP_ADDRESS), POP_RAX, sizeof(POP_RAX));
size_t currentOp = sizeof(ABSOLUTE_JMP_ADDRESS) + sizeof(POP_RAX);
memset(m_pSource + currentOp, NOP, HOOKSIZE - currentOp);
// fixup jump addr
*(uint64_t*)(m_pSource + 2) = (uint64_t)(m_pDestination);
// revert mprot
mprotect(m_pSource - ((uint64_t)m_pSource) % sysconf(_SC_PAGE_SIZE), sysconf(_SC_PAGE_SIZE), PROT_READ | PROT_EXEC);
// set original addr to trampo addr
m_pOriginal = m_pTrampolineAddr;
m_bActive = true;
m_iHookLen = HOOKSIZE;
m_iTrampoLen = HOOKSIZE + sizeof(ABSOLUTE_JMP_ADDRESS);
return true;
}
bool CFunctionHook::unhook() {
// check for unsupported platforms
#if !defined(__x86_64__)
return false;
#endif
if (!m_bActive)
return false;
// allow write to src
mprotect(m_pSource - ((uint64_t)m_pSource) % sysconf(_SC_PAGE_SIZE), sysconf(_SC_PAGE_SIZE), PROT_READ | PROT_WRITE | PROT_EXEC);
// write back original bytes
memcpy(m_pSource, m_pTrampolineAddr, m_iHookLen);
// revert mprot
mprotect(m_pSource - ((uint64_t)m_pSource) % sysconf(_SC_PAGE_SIZE), sysconf(_SC_PAGE_SIZE), PROT_READ | PROT_EXEC);
// unmap
munmap(m_pTrampolineAddr, m_iTrampoLen);
// reset vars
m_bActive = false;
m_iHookLen = 0;
m_iTrampoLen = 0;
m_pTrampolineAddr = nullptr;
return true;
}
CFunctionHook* CHookSystem::initHook(HANDLE owner, void* source, void* destination) {
return m_vHooks.emplace_back(std::make_unique<CFunctionHook>(owner, source, destination)).get();
}
bool CHookSystem::removeHook(CFunctionHook* hook) {
std::erase_if(m_vHooks, [&](const auto& other) { return other.get() == hook; });
return true; // todo: make false if not found
}
void CHookSystem::removeAllHooksFrom(HANDLE handle) {
std::erase_if(m_vHooks, [&](const auto& other) { return other->m_pOwner == handle; });
}

View file

@ -0,0 +1,48 @@
#pragma once
#include <string>
#include <vector>
#include <memory>
#define HANDLE void*
class CFunctionHook {
public:
CFunctionHook(HANDLE owner, void* source, void* destination);
~CFunctionHook();
bool hook();
bool unhook();
CFunctionHook(const CFunctionHook&) = delete;
CFunctionHook(CFunctionHook&&) = delete;
CFunctionHook& operator=(const CFunctionHook&) = delete;
CFunctionHook& operator=(CFunctionHook&&) = delete;
void* m_pOriginal = nullptr;
private:
void* m_pSource = nullptr;
void* m_pFunctionAddr = nullptr;
void* m_pTrampolineAddr = nullptr;
void* m_pDestination = nullptr;
size_t m_iHookLen = 0;
size_t m_iTrampoLen = 0;
HANDLE m_pOwner = nullptr;
bool m_bActive = false;
friend class CHookSystem;
};
class CHookSystem {
public:
CFunctionHook* initHook(HANDLE handle, void* source, void* destination);
bool removeHook(CFunctionHook* hook);
void removeAllHooksFrom(HANDLE handle);
private:
std::vector<std::unique_ptr<CFunctionHook>> m_vHooks;
};
inline std::unique_ptr<CHookSystem> g_pFunctionHookSystem;

193
src/plugins/PluginAPI.cpp Normal file
View file

@ -0,0 +1,193 @@
#include "PluginAPI.hpp"
#include "../Compositor.hpp"
#include "../debug/HyprCtl.hpp"
#include <dlfcn.h>
APICALL bool HyprlandAPI::registerCallbackStatic(HANDLE handle, const std::string& event, HOOK_CALLBACK_FN* fn) {
auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
if (!PLUGIN)
return false;
g_pHookSystem->hookStatic(event, fn, handle);
PLUGIN->registeredCallbacks.emplace_back(std::make_pair<>(event, fn));
return true;
}
APICALL HOOK_CALLBACK_FN* HyprlandAPI::registerCallbackDynamic(HANDLE handle, const std::string& event, HOOK_CALLBACK_FN fn) {
auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
if (!PLUGIN)
return nullptr;
auto* const PFN = g_pHookSystem->hookDynamic(event, fn, handle);
PLUGIN->registeredCallbacks.emplace_back(std::make_pair<>(event, PFN));
return PFN;
}
APICALL bool HyprlandAPI::unregisterCallback(HANDLE handle, HOOK_CALLBACK_FN* fn) {
auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
if (!PLUGIN)
return false;
g_pHookSystem->unhook(fn);
std::erase_if(PLUGIN->registeredCallbacks, [&](const auto& other) { return other.second == fn; });
return true;
}
APICALL std::string HyprlandAPI::invokeHyprctlCommand(const std::string& call, const std::string& args, const std::string& format) {
std::string COMMAND = format + "/" + call + " " + args;
return HyprCtl::makeDynamicCall(COMMAND);
}
APICALL bool HyprlandAPI::addLayout(HANDLE handle, const std::string& name, IHyprLayout* layout) {
auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
if (!PLUGIN)
return false;
PLUGIN->registeredLayouts.push_back(layout);
return g_pLayoutManager->addLayout(name, layout);
}
APICALL bool HyprlandAPI::removeLayout(HANDLE handle, IHyprLayout* layout) {
auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
if (!PLUGIN)
return false;
std::erase(PLUGIN->registeredLayouts, layout);
return g_pLayoutManager->removeLayout(layout);
}
APICALL bool HyprlandAPI::reloadConfig() {
g_pConfigManager->m_bForceReload = true;
return true;
}
APICALL bool HyprlandAPI::addNotification(HANDLE handle, const std::string& text, const CColor& color, const float timeMs) {
auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
if (!PLUGIN)
return false;
g_pHyprNotificationOverlay->addNotification(text, color, timeMs);
return true;
}
APICALL CFunctionHook* HyprlandAPI::createFunctionHook(HANDLE handle, const void* source, const void* destination) {
auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
if (!PLUGIN)
return nullptr;
return g_pFunctionHookSystem->initHook(handle, (void*)source, (void*)destination);
}
APICALL bool HyprlandAPI::removeFunctionHook(HANDLE handle, CFunctionHook* hook) {
auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
if (!PLUGIN)
return false;
return g_pFunctionHookSystem->removeHook(hook);
}
APICALL bool HyprlandAPI::addWindowDecoration(HANDLE handle, CWindow* pWindow, IHyprWindowDecoration* pDecoration) {
auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
if (!PLUGIN)
return false;
if (!g_pCompositor->windowValidMapped(pWindow))
return false;
PLUGIN->registeredDecorations.push_back(pDecoration);
pWindow->m_dWindowDecorations.emplace_back(pDecoration);
return true;
}
APICALL bool HyprlandAPI::removeWindowDecoration(HANDLE handle, IHyprWindowDecoration* pDecoration) {
auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
if (!PLUGIN)
return false;
for (auto& w : g_pCompositor->m_vWindows) {
for (auto& d : w->m_dWindowDecorations) {
if (d.get() == pDecoration) {
std::erase(w->m_dWindowDecorations, d);
return true;
}
}
}
return false;
}
APICALL bool HyprlandAPI::addConfigValue(HANDLE handle, const std::string& name, const SConfigValue& value) {
auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
if (!g_pPluginSystem->m_bAllowConfigVars)
return false;
if (!PLUGIN)
return false;
if (name.find("plugin:") != 0)
return false;
g_pConfigManager->addPluginConfigVar(handle, name, value);
return true;
}
APICALL SConfigValue* HyprlandAPI::getConfigValue(HANDLE handle, const std::string& name) {
auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
if (!PLUGIN)
return nullptr;
return g_pConfigManager->getConfigValuePtrSafe(name);
}
APICALL void* HyprlandAPI::getFunctionAddressFromSignature(HANDLE handle, const std::string& sig) {
auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
if (!PLUGIN)
return nullptr;
return dlsym(nullptr, sig.c_str());
}
APICALL bool HyprlandAPI::addDispatcher(HANDLE handle, const std::string& name, std::function<void(std::string)> handler) {
auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
if (!PLUGIN)
return false;
PLUGIN->registeredDispatchers.push_back(name);
g_pKeybindManager->m_mDispatchers[name] = handler;
return true;
}
APICALL bool HyprlandAPI::removeDispatcher(HANDLE handle, const std::string& name) {
auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle);
if (!PLUGIN)
return false;
std::erase_if(g_pKeybindManager->m_mDispatchers, [&](const auto& other) { return other.first == name; });
std::erase_if(PLUGIN->registeredDispatchers, [&](const auto& other) { return other == name; });
return true;
}

217
src/plugins/PluginAPI.hpp Normal file
View file

@ -0,0 +1,217 @@
#pragma once
/*
Hyprland Plugin API.
Most documentation will be made with comments in this code, but more info can be also found on the wiki.
!WARNING!
The Hyprland API passes C++ objects over, so no ABI compatibility is guaranteed.
Make sure to compile your plugins with the same compiler as Hyprland, and ideally,
on the same machine.
See examples/examplePlugin for an example plugin
*/
#define HYPRLAND_API_VERSION "0.1"
#include "../helpers/Color.hpp"
#include "HookSystem.hpp"
#include <any>
#include <functional>
#include <string>
typedef std::function<void(void*, std::any)> HOOK_CALLBACK_FN;
typedef struct {
std::string name;
std::string description;
std::string author;
std::string version;
} PLUGIN_DESCRIPTION_INFO;
#define APICALL extern "C"
#define EXPORT __attribute__((visibility("default")))
#define REQUIRED
#define OPTIONAL
#define HANDLE void*
class IHyprLayout;
class CWindow;
class IHyprWindowDecoration;
struct SConfigValue;
/*
These methods are for the plugin to implement
Methods marked with REQUIRED are required.
*/
/*
called pre-plugin init.
In case of a version mismatch, will eject the .so.
This function should not be modified, see the example plugin.
*/
typedef REQUIRED std::string (*PPLUGIN_API_VERSION_FUNC)();
#define PLUGIN_API_VERSION pluginAPIVersion
#define PLUGIN_API_VERSION_FUNC_STR "pluginAPIVersion"
/*
called on plugin init. Passes a handle as the parameter, which the plugin should keep for identification later.
The plugin should return a PLUGIN_DESCRIPTION_INFO struct with information about itself.
Keep in mind this is executed synchronously, and as such any blocking calls to hyprland might hang. (e.g. system("hyprctl ..."))
*/
typedef REQUIRED PLUGIN_DESCRIPTION_INFO (*PPLUGIN_INIT_FUNC)(HANDLE);
#define PLUGIN_INIT pluginInit
#define PLUGIN_INIT_FUNC_STR "pluginInit"
/*
called on plugin unload, if that was a user action. If the plugin is being unloaded by an error,
this will not be called.
Hooks are unloaded after exit.
*/
typedef OPTIONAL void (*PPLUGIN_EXIT_FUNC)(void);
#define PLUGIN_EXIT pluginExit
#define PLUGIN_EXIT_FUNC_STR "pluginExit"
/*
End plugin methods
*/
namespace HyprlandAPI {
/*
Add a config value.
All config values MUST be in the plugin: namespace
This method may only be called in "pluginInit"
After you have registered ALL of your config values, you may call `getConfigValue`
returns: true on success, false on fail
*/
APICALL bool addConfigValue(HANDLE handle, const std::string& name, const SConfigValue& value);
/*
Get a config value.
returns: a pointer to the config value struct, which is guaranteed to be valid for the life of this plugin, unless another `addConfigValue` is called afterwards.
nullptr on error.
*/
APICALL SConfigValue* getConfigValue(HANDLE handle, const std::string& name);
/*
Register a static (pointer) callback to a selected event.
Pointer must be kept valid until unregisterCallback() is called.
returns: true on success, false on fail
*/
APICALL bool registerCallbackStatic(HANDLE handle, const std::string& event, HOOK_CALLBACK_FN* fn);
/*
Register a dynamic (function) callback to a selected event.
Pointer will be free'd by Hyprland on unregisterCallback().
returns: a pointer to the newly allocated function. nullptr on fail.
*/
APICALL HOOK_CALLBACK_FN* registerCallbackDynamic(HANDLE handle, const std::string& event, HOOK_CALLBACK_FN fn);
/*
Unregisters a callback. If the callback was dynamic, frees the memory.
returns: true on success, false on fail
*/
APICALL bool unregisterCallback(HANDLE handle, HOOK_CALLBACK_FN* fn);
/*
Calls a hyprctl command.
returns: the output (as in hyprctl)
*/
APICALL std::string invokeHyprctlCommand(const std::string& call, const std::string& args, const std::string& format = "");
/*
Adds a layout to Hyprland.
returns: true on success. False otherwise.
*/
APICALL bool addLayout(HANDLE handle, const std::string& name, IHyprLayout* layout);
/*
Removes an added layout from Hyprland.
returns: true on success. False otherwise.
*/
APICALL bool removeLayout(HANDLE handle, IHyprLayout* layout);
/*
Queues a config reload. Does not take effect immediately.
returns: true on success. False otherwise.
*/
APICALL bool reloadConfig();
/*
Adds a notification.
returns: true on success. False otherwise.
*/
APICALL bool addNotification(HANDLE handle, const std::string& text, const CColor& color, const float timeMs);
/*
Creates a trampoline function hook to an internal hl func.
returns: CFunctionHook*
!WARNING! Hooks are *not* guaranteed any API stability. Internal methods may be removed, added, or renamed. Consider preferring the API whenever possible.
*/
APICALL CFunctionHook* createFunctionHook(HANDLE handle, const void* source, const void* destination);
/*
Removes a trampoline function hook. Will unhook if still hooked.
returns: true on success. False otherwise.
!WARNING! Hooks are *not* guaranteed any API stability. Internal methods may be removed, added, or renamed. Consider preferring the API whenever possible.
*/
APICALL bool removeFunctionHook(HANDLE handle, CFunctionHook* hook);
/*
Gets a function address from a signature.
This is useful for hooking private functions.
returns: function address, or nullptr on fail.
*/
APICALL void* getFunctionAddressFromSignature(HANDLE handle, const std::string& sig);
/*
Adds a window decoration to a window
returns: true on success. False otherwise.
*/
APICALL bool addWindowDecoration(HANDLE handle, CWindow* pWindow, IHyprWindowDecoration* pDecoration);
/*
Removes a window decoration
returns: true on success. False otherwise.
*/
APICALL bool removeWindowDecoration(HANDLE handle, IHyprWindowDecoration* pDecoration);
/*
Adds a keybind dispatcher.
returns: true on success. False otherwise.
*/
APICALL bool addDispatcher(HANDLE handle, const std::string& name, std::function<void(std::string)> handler);
/*
Removes a keybind dispatcher.
returns: true on success. False otherwise.
*/
APICALL bool removeDispatcher(HANDLE handle, const std::string& name);
};

View file

@ -0,0 +1,139 @@
#include "PluginSystem.hpp"
#include <dlfcn.h>
#include "../Compositor.hpp"
CPluginSystem::CPluginSystem() {
g_pFunctionHookSystem = std::make_unique<CHookSystem>();
}
CPlugin* CPluginSystem::loadPlugin(const std::string& path) {
if (getPluginByPath(path)) {
Debug::log(ERR, " [PluginSystem] Cannot load a plugin twice!");
return nullptr;
}
auto* const PLUGIN = m_vLoadedPlugins.emplace_back(std::make_unique<CPlugin>()).get();
PLUGIN->path = path;
HANDLE MODULE = dlopen(path.c_str(), RTLD_LAZY);
if (!MODULE) {
Debug::log(ERR, " [PluginSystem] Plugin %s could not be loaded: %s", path.c_str(), dlerror());
m_vLoadedPlugins.pop_back();
return nullptr;
}
PLUGIN->m_pHandle = MODULE;
PPLUGIN_API_VERSION_FUNC apiVerFunc = (PPLUGIN_API_VERSION_FUNC)dlsym(MODULE, PLUGIN_API_VERSION_FUNC_STR);
PPLUGIN_INIT_FUNC initFunc = (PPLUGIN_INIT_FUNC)dlsym(MODULE, PLUGIN_INIT_FUNC_STR);
if (!apiVerFunc || !initFunc) {
Debug::log(ERR, " [PluginSystem] Plugin %s could not be loaded. (No apiver/init func)", path.c_str());
dlclose(MODULE);
m_vLoadedPlugins.pop_back();
return nullptr;
}
const std::string PLUGINAPIVER = apiVerFunc();
if (PLUGINAPIVER != HYPRLAND_API_VERSION) {
Debug::log(ERR, " [PluginSystem] Plugin %s could not be loaded. (API version mismatch)", path.c_str());
dlclose(MODULE);
m_vLoadedPlugins.pop_back();
return nullptr;
}
PLUGIN_DESCRIPTION_INFO PLUGINDATA;
try {
if (!setjmp(m_jbPluginFaultJumpBuf)) {
m_bAllowConfigVars = true;
PLUGINDATA = initFunc(MODULE);
} else {
// this module crashed.
throw std::exception();
}
} catch (std::exception& e) {
m_bAllowConfigVars = false;
Debug::log(ERR, " [PluginSystem] Plugin %s (Handle %lx) crashed in init. Unloading.", path.c_str(), MODULE);
unloadPlugin(PLUGIN, true); // Plugin could've already hooked/done something
return nullptr;
}
m_bAllowConfigVars = false;
PLUGIN->author = PLUGINDATA.author;
PLUGIN->description = PLUGINDATA.description;
PLUGIN->version = PLUGINDATA.version;
PLUGIN->name = PLUGINDATA.name;
Debug::log(LOG, " [PluginSystem] Plugin %s loaded. Handle: %lx, path: \"%s\", author: \"%s\", description: \"%s\", version: \"%s\"", PLUGINDATA.name.c_str(), MODULE,
path.c_str(), PLUGINDATA.author.c_str(), PLUGINDATA.description.c_str(), PLUGINDATA.version.c_str());
return PLUGIN;
}
void CPluginSystem::unloadPlugin(const CPlugin* plugin, bool eject) {
if (!plugin)
return;
if (!eject) {
PPLUGIN_EXIT_FUNC exitFunc = (PPLUGIN_EXIT_FUNC)dlsym(plugin->m_pHandle, PLUGIN_EXIT_FUNC_STR);
if (exitFunc)
exitFunc();
}
for (auto& [k, v] : plugin->registeredCallbacks)
g_pHookSystem->unhook(v);
const auto ls = plugin->registeredLayouts;
for (auto& l : ls)
g_pLayoutManager->removeLayout(l);
g_pFunctionHookSystem->removeAllHooksFrom(plugin->m_pHandle);
const auto rd = plugin->registeredDecorations;
for (auto& d : rd)
HyprlandAPI::removeWindowDecoration(plugin->m_pHandle, d);
const auto rdi = plugin->registeredDispatchers;
for (auto& d : rdi)
HyprlandAPI::removeDispatcher(plugin->m_pHandle, d);
g_pConfigManager->removePluginConfig(plugin->m_pHandle);
dlclose(plugin->m_pHandle);
Debug::log(LOG, " [PluginSystem] Plugin %s unloaded.", plugin->name.c_str());
std::erase_if(m_vLoadedPlugins, [&](const auto& other) { return other->m_pHandle == plugin->m_pHandle; });
}
CPlugin* CPluginSystem::getPluginByPath(const std::string& path) {
for (auto& p : m_vLoadedPlugins) {
if (p->path == path)
return p.get();
}
return nullptr;
}
CPlugin* CPluginSystem::getPluginByHandle(HANDLE handle) {
for (auto& p : m_vLoadedPlugins) {
if (p->m_pHandle == handle)
return p.get();
}
return nullptr;
}
std::vector<CPlugin*> CPluginSystem::getAllPlugins() {
std::vector<CPlugin*> results(m_vLoadedPlugins.size());
for (size_t i = 0; i < m_vLoadedPlugins.size(); ++i)
results[i] = m_vLoadedPlugins[i].get();
return results;
}

View file

@ -0,0 +1,44 @@
#pragma once
#include "../defines.hpp"
#include "PluginAPI.hpp"
#include <csetjmp>
class IHyprWindowDecoration;
class CPlugin {
public:
std::string name = "";
std::string description = "";
std::string author = "";
std::string version = "";
std::string path = "";
HANDLE m_pHandle = nullptr;
std::vector<IHyprLayout*> registeredLayouts;
std::vector<IHyprWindowDecoration*> registeredDecorations;
std::vector<std::pair<std::string, HOOK_CALLBACK_FN*>> registeredCallbacks;
std::vector<std::string> registeredDispatchers;
};
class CPluginSystem {
public:
CPluginSystem();
CPlugin* loadPlugin(const std::string& path);
void unloadPlugin(const CPlugin* plugin, bool eject = false);
CPlugin* getPluginByPath(const std::string& path);
CPlugin* getPluginByHandle(HANDLE handle);
std::vector<CPlugin*> getAllPlugins();
bool m_bAllowConfigVars = false;
private:
std::vector<std::unique_ptr<CPlugin>> m_vLoadedPlugins;
jmp_buf m_jbPluginFaultJumpBuf;
};
inline std::unique_ptr<CPluginSystem> g_pPluginSystem;

View file

@ -6,7 +6,8 @@ enum eDecorationType
{ {
DECORATION_NONE = -1, DECORATION_NONE = -1,
DECORATION_GROUPBAR, DECORATION_GROUPBAR,
DECORATION_SHADOW DECORATION_SHADOW,
DECORATION_CUSTOM
}; };
struct SWindowDecorationExtents { struct SWindowDecorationExtents {

1
subprojects/udis86 Submodule

@ -0,0 +1 @@
Subproject commit 5336633af70f3917760a6d441ff02d93477b0c86