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: Plugin System (#1590) --------- Co-authored-by: Mihai Fufezan --- 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 +++++++++ 6 files changed, 792 insertions(+) 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 (limited to 'src/plugins') 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 -- cgit v1.2.3