Plugin System (#1590)
--------- Co-authored-by: Mihai Fufezan <fufexan@protonmail.com>
This commit is contained in:
parent
74a10f26a4
commit
8b81f41e52
45 changed files with 1691 additions and 85 deletions
|
|
@ -18,6 +18,12 @@ int handleCritSignal(int signo, void* data) {
|
|||
}
|
||||
|
||||
void handleSegv(int sig) {
|
||||
|
||||
if (g_pHookSystem->m_bCurrentEventPlugin) {
|
||||
longjmp(g_pHookSystem->m_jbHookFaultJumpBuf, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
CrashReporter::createAndSaveCrash();
|
||||
abort();
|
||||
}
|
||||
|
|
@ -364,6 +370,12 @@ void CCompositor::startCompositor() {
|
|||
|
||||
Debug::log(LOG, "Creating the HyprDebugOverlay!");
|
||||
g_pDebugOverlay = std::make_unique<CHyprDebugOverlay>();
|
||||
|
||||
Debug::log(LOG, "Creating the HyprNotificationOverlay!");
|
||||
g_pHyprNotificationOverlay = std::make_unique<CHyprNotificationOverlay>();
|
||||
|
||||
Debug::log(LOG, "Creating the PluginSystem!");
|
||||
g_pPluginSystem = std::make_unique<CPluginSystem>();
|
||||
//
|
||||
//
|
||||
|
||||
|
|
@ -2012,6 +2024,9 @@ void CCompositor::scheduleFrameForMonitor(CMonitor* pMonitor) {
|
|||
if (!pMonitor->m_bEnabled)
|
||||
return;
|
||||
|
||||
if (pMonitor->renderingActive)
|
||||
pMonitor->pendingFrame = true;
|
||||
|
||||
wlr_output_schedule_frame(pMonitor->output);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,12 +19,14 @@
|
|||
#include "managers/SessionLockManager.hpp"
|
||||
#include "managers/HookSystemManager.hpp"
|
||||
#include "debug/HyprDebugOverlay.hpp"
|
||||
#include "debug/HyprNotificationOverlay.hpp"
|
||||
#include "helpers/Monitor.hpp"
|
||||
#include "helpers/Workspace.hpp"
|
||||
#include "Window.hpp"
|
||||
#include "render/Renderer.hpp"
|
||||
#include "render/OpenGL.hpp"
|
||||
#include "hyprerror/HyprError.hpp"
|
||||
#include "plugins/PluginSystem.hpp"
|
||||
|
||||
class CCompositor {
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -271,7 +271,7 @@ void CConfigManager::init() {
|
|||
|
||||
void CConfigManager::configSetValueSafe(const std::string& COMMAND, const std::string& VALUE) {
|
||||
if (configValues.find(COMMAND) == configValues.end()) {
|
||||
if (COMMAND.find("device:") != 0 /* devices parsed later */) {
|
||||
if (COMMAND.find("device:") != 0 /* devices parsed later */ && COMMAND.find("plugin:") != 0 /* plugins parsed later */) {
|
||||
if (COMMAND[0] == '$') {
|
||||
// register a dynamic var
|
||||
Debug::log(LOG, "Registered dynamic var \"%s\" -> %s", COMMAND.c_str(), VALUE.c_str());
|
||||
|
|
@ -306,6 +306,18 @@ void CConfigManager::configSetValueSafe(const std::string& COMMAND, const std::s
|
|||
}
|
||||
|
||||
CONFIGENTRY = &it->second.at(CONFIGVAR);
|
||||
} else if (COMMAND.find("plugin:") == 0) {
|
||||
for (auto& [handle, pMap] : pluginConfigs) {
|
||||
auto it = std::find_if(pMap->begin(), pMap->end(), [&](const auto& other) { return other.first == COMMAND; });
|
||||
if (it == pMap->end()) {
|
||||
return; // plugin vars do not err, so we silently ignore.
|
||||
}
|
||||
|
||||
CONFIGENTRY = &it->second;
|
||||
}
|
||||
|
||||
if (!CONFIGENTRY)
|
||||
return; // silent ignore
|
||||
} else {
|
||||
CONFIGENTRY = &configValues.at(COMMAND);
|
||||
}
|
||||
|
|
@ -1649,8 +1661,17 @@ SConfigValue* CConfigManager::getConfigValuePtr(const std::string& val) {
|
|||
SConfigValue* CConfigManager::getConfigValuePtrSafe(const std::string& val) {
|
||||
const auto IT = configValues.find(val);
|
||||
|
||||
if (IT == configValues.end())
|
||||
if (IT == configValues.end()) {
|
||||
// maybe plugin
|
||||
for (auto& [pl, pMap] : pluginConfigs) {
|
||||
const auto PLIT = pMap->find(val);
|
||||
|
||||
if (PLIT != pMap->end())
|
||||
return &PLIT->second;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &(IT->second);
|
||||
}
|
||||
|
|
@ -1800,3 +1821,19 @@ ICustomConfigValueData::~ICustomConfigValueData() {
|
|||
std::unordered_map<std::string, SAnimationPropertyConfig> CConfigManager::getAnimationConfig() {
|
||||
return animationConfig;
|
||||
}
|
||||
|
||||
void CConfigManager::addPluginConfigVar(HANDLE handle, const std::string& name, const SConfigValue& value) {
|
||||
auto CONFIGMAPIT = std::find_if(pluginConfigs.begin(), pluginConfigs.end(), [&](const auto& other) { return other.first == handle; });
|
||||
|
||||
if (CONFIGMAPIT == pluginConfigs.end()) {
|
||||
pluginConfigs.emplace(
|
||||
std::pair<HANDLE, std::unique_ptr<std::unordered_map<std::string, SConfigValue>>>(handle, std::make_unique<std::unordered_map<std::string, SConfigValue>>()));
|
||||
CONFIGMAPIT = std::find_if(pluginConfigs.begin(), pluginConfigs.end(), [&](const auto& other) { return other.first == handle; });
|
||||
}
|
||||
|
||||
(*CONFIGMAPIT->second)[name] = value;
|
||||
}
|
||||
|
||||
void CConfigManager::removePluginConfig(HANDLE handle) {
|
||||
std::erase_if(pluginConfigs, [&](const auto& other) { return other.first == handle; });
|
||||
}
|
||||
|
|
@ -21,6 +21,8 @@
|
|||
#define INITANIMCFG(name) animationConfig[name] = {}
|
||||
#define CREATEANIMCFG(name, parent) animationConfig[name] = {false, "", "", 0.f, -1, &animationConfig["global"], &animationConfig[parent]}
|
||||
|
||||
#define HANDLE void*
|
||||
|
||||
struct SConfigValue {
|
||||
int64_t intValue = -INT64_MAX;
|
||||
float floatValue = -__FLT_MAX__;
|
||||
|
|
@ -159,6 +161,9 @@ class CConfigManager {
|
|||
|
||||
std::unordered_map<std::string, SAnimationPropertyConfig> getAnimationConfig();
|
||||
|
||||
void addPluginConfigVar(HANDLE handle, const std::string& name, const SConfigValue& value);
|
||||
void removePluginConfig(HANDLE handle);
|
||||
|
||||
// no-op when done.
|
||||
void dispatchExecOnce();
|
||||
|
||||
|
|
@ -180,33 +185,35 @@ class CConfigManager {
|
|||
std::string configCurrentPath;
|
||||
|
||||
private:
|
||||
std::deque<std::string> configPaths; // stores all the config paths
|
||||
std::unordered_map<std::string, time_t> configModifyTimes; // stores modify times
|
||||
std::unordered_map<std::string, std::string> configDynamicVars; // stores dynamic vars declared by the user
|
||||
std::unordered_map<std::string, SConfigValue> configValues;
|
||||
std::unordered_map<std::string, std::unordered_map<std::string, SConfigValue>> deviceConfigs; // stores device configs
|
||||
std::deque<std::string> configPaths; // stores all the config paths
|
||||
std::unordered_map<std::string, time_t> configModifyTimes; // stores modify times
|
||||
std::unordered_map<std::string, std::string> configDynamicVars; // stores dynamic vars declared by the user
|
||||
std::unordered_map<std::string, SConfigValue> configValues;
|
||||
std::unordered_map<std::string, std::unordered_map<std::string, SConfigValue>> deviceConfigs; // stores device configs
|
||||
|
||||
std::unordered_map<std::string, SAnimationPropertyConfig> animationConfig; // stores all the animations with their set values
|
||||
std::unordered_map<std::string, SAnimationPropertyConfig> animationConfig; // stores all the animations with their set values
|
||||
|
||||
std::string currentCategory = ""; // For storing the category of the current item
|
||||
std::string currentCategory = ""; // For storing the category of the current item
|
||||
|
||||
std::string parseError = ""; // For storing a parse error to display later
|
||||
std::string parseError = ""; // For storing a parse error to display later
|
||||
|
||||
std::string m_szCurrentSubmap = ""; // For storing the current keybind submap
|
||||
std::string m_szCurrentSubmap = ""; // For storing the current keybind submap
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> boundWorkspaces;
|
||||
std::vector<std::pair<std::string, std::string>> boundWorkspaces;
|
||||
|
||||
std::vector<SExecRequestedRule> execRequestedRules; // rules requested with exec, e.g. [workspace 2] kitty
|
||||
std::vector<SExecRequestedRule> execRequestedRules; // rules requested with exec, e.g. [workspace 2] kitty
|
||||
|
||||
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;
|
||||
std::deque<SWindowRule> m_dWindowRules;
|
||||
std::deque<SLayerRule> m_dLayerRules;
|
||||
std::deque<std::string> m_dBlurLSNamespaces;
|
||||
bool isFirstLaunch = true; // For exec-once
|
||||
|
||||
bool firstExecDispatched = false;
|
||||
std::deque<std::string> firstExecRequests;
|
||||
std::deque<SMonitorRule> m_dMonitorRules;
|
||||
std::deque<SWindowRule> m_dWindowRules;
|
||||
std::deque<SLayerRule> m_dLayerRules;
|
||||
std::deque<std::string> m_dBlurLSNamespaces;
|
||||
|
||||
bool firstExecDispatched = false;
|
||||
std::deque<std::string> firstExecRequests;
|
||||
|
||||
// internal methods
|
||||
void setDefaultVars();
|
||||
|
|
|
|||
|
|
@ -4,10 +4,13 @@
|
|||
#include <execinfo.h>
|
||||
#include <fstream>
|
||||
|
||||
#include "../plugins/PluginSystem.hpp"
|
||||
|
||||
#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__)
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
|
||||
|
||||
std::string getRandomMessage() {
|
||||
|
||||
const std::vector<std::string> MESSAGES = {"Sorry, didn't mean to...",
|
||||
|
|
@ -43,6 +46,16 @@ void CrashReporter::createAndSaveCrash() {
|
|||
|
||||
finalCrashReport += "Hyprland received signal 11 (SIGSEGV): Segmentation Fault\n\n";
|
||||
|
||||
if (!g_pPluginSystem->getAllPlugins().empty()) {
|
||||
finalCrashReport += "Hyprland seems to be running with plugins. This crash might not be Hyprland's fault.\nPlugins:\n";
|
||||
|
||||
for (auto& p : g_pPluginSystem->getAllPlugins()) {
|
||||
finalCrashReport += getFormat("\t%s (%s) %s\n", p->name.c_str(), p->author.c_str(), p->version.c_str());
|
||||
}
|
||||
|
||||
finalCrashReport += "\n\n";
|
||||
}
|
||||
|
||||
finalCrashReport += "System info:\n";
|
||||
|
||||
struct utsname unameInfo;
|
||||
|
|
|
|||
|
|
@ -1087,6 +1087,50 @@ std::string dispatchOutput(std::string request) {
|
|||
return "ok";
|
||||
}
|
||||
|
||||
std::string dispatchPlugin(std::string request) {
|
||||
CVarList vars(request, 0, ' ');
|
||||
|
||||
if (vars.size() < 2)
|
||||
return "not enough args";
|
||||
|
||||
const auto OPERATION = vars[1];
|
||||
const auto PATH = vars[2];
|
||||
|
||||
if (OPERATION == "load") {
|
||||
if (vars.size() < 3)
|
||||
return "not enough args";
|
||||
|
||||
const auto PLUGIN = g_pPluginSystem->loadPlugin(PATH);
|
||||
|
||||
if (!PLUGIN)
|
||||
return "error in loading plugin";
|
||||
} else if (OPERATION == "unload") {
|
||||
if (vars.size() < 3)
|
||||
return "not enough args";
|
||||
|
||||
const auto PLUGIN = g_pPluginSystem->getPluginByPath(PATH);
|
||||
|
||||
if (!PLUGIN)
|
||||
return "plugin not loaded";
|
||||
|
||||
g_pPluginSystem->unloadPlugin(PLUGIN);
|
||||
} else if (OPERATION == "list") {
|
||||
const auto PLUGINS = g_pPluginSystem->getAllPlugins();
|
||||
|
||||
std::string list = "";
|
||||
for (auto& p : PLUGINS) {
|
||||
list += getFormat("\nPlugin %s by %s:\n\tHandle: %lx\n\tVersion: %s\n\tDescription: %s\n", p->name.c_str(), p->author.c_str(), p->m_pHandle, p->version.c_str(),
|
||||
p->description.c_str());
|
||||
}
|
||||
|
||||
return list;
|
||||
} else {
|
||||
return "unknown opt";
|
||||
}
|
||||
|
||||
return "ok";
|
||||
}
|
||||
|
||||
std::string getReply(std::string request) {
|
||||
auto format = HyprCtl::FORMAT_NORMAL;
|
||||
|
||||
|
|
@ -1134,6 +1178,8 @@ std::string getReply(std::string request) {
|
|||
return bindsRequest(format);
|
||||
else if (request == "animations")
|
||||
return animationsRequest(format);
|
||||
else if (request.find("plugin") == 0)
|
||||
return dispatchPlugin(request);
|
||||
else if (request.find("setprop") == 0)
|
||||
return dispatchSetProp(request);
|
||||
else if (request.find("seterror") == 0)
|
||||
|
|
@ -1156,6 +1202,10 @@ std::string getReply(std::string request) {
|
|||
return "unknown request";
|
||||
}
|
||||
|
||||
std::string HyprCtl::makeDynamicCall(const std::string& input) {
|
||||
return getReply(input);
|
||||
}
|
||||
|
||||
int hyprCtlFDTick(int fd, uint32_t mask, void* data) {
|
||||
if (mask & WL_EVENT_ERROR || mask & WL_EVENT_HANGUP)
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
#include "../helpers/MiscFunctions.hpp"
|
||||
|
||||
namespace HyprCtl {
|
||||
void startHyprCtlSocket();
|
||||
void startHyprCtlSocket();
|
||||
std::string makeDynamicCall(const std::string& input);
|
||||
|
||||
// very simple thread-safe request method
|
||||
inline bool requestMade = false;
|
||||
|
|
@ -18,8 +19,7 @@ namespace HyprCtl {
|
|||
|
||||
inline int iSocketFD = -1;
|
||||
|
||||
enum eHyprCtlOutputFormat
|
||||
{
|
||||
enum eHyprCtlOutputFormat {
|
||||
FORMAT_NORMAL = 0,
|
||||
FORMAT_JSON
|
||||
};
|
||||
|
|
|
|||
155
src/debug/HyprNotificationOverlay.cpp
Normal file
155
src/debug/HyprNotificationOverlay.cpp
Normal 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);
|
||||
}
|
||||
40
src/debug/HyprNotificationOverlay.hpp
Normal file
40
src/debug/HyprNotificationOverlay.hpp
Normal 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;
|
||||
|
|
@ -89,4 +89,4 @@
|
|||
|
||||
#define SPECIAL_WORKSPACE_START (-99)
|
||||
|
||||
#define PI 3.14159265358979
|
||||
#define PI 3.14159265358979
|
||||
|
|
@ -197,6 +197,8 @@ void Events::listener_monitorFrame(void* owner, void* data) {
|
|||
return;
|
||||
}
|
||||
|
||||
PMONITOR->renderingActive = true;
|
||||
|
||||
// we need to cleanup fading out when rendering the appropriate context
|
||||
g_pCompositor->cleanupFadingOut(PMONITOR->ID);
|
||||
|
||||
|
|
@ -207,6 +209,8 @@ void Events::listener_monitorFrame(void* owner, void* data) {
|
|||
if (*PDAMAGEBLINK || *PVFR == 0)
|
||||
g_pCompositor->scheduleFrameForMonitor(PMONITOR);
|
||||
|
||||
PMONITOR->renderingActive = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -260,6 +264,9 @@ void Events::listener_monitorFrame(void* owner, void* data) {
|
|||
if (PMONITOR == g_pCompositor->m_vMonitors.front().get())
|
||||
g_pHyprError->draw();
|
||||
|
||||
if (PMONITOR == g_pCompositor->m_pLastMonitor)
|
||||
g_pHyprNotificationOverlay->draw(PMONITOR);
|
||||
|
||||
// for drawing the debug overlay
|
||||
if (PMONITOR == g_pCompositor->m_vMonitors.front().get() && *PDEBUGOVERLAY == 1) {
|
||||
startRenderOverlay = std::chrono::high_resolution_clock::now();
|
||||
|
|
@ -306,12 +313,16 @@ void Events::listener_monitorFrame(void* owner, void* data) {
|
|||
pixman_region32_fini(&frameDamage);
|
||||
pixman_region32_fini(&damage);
|
||||
|
||||
PMONITOR->renderingActive = false;
|
||||
|
||||
if (!wlr_output_commit(PMONITOR->output))
|
||||
return;
|
||||
|
||||
if (*PDAMAGEBLINK || *PVFR == 0)
|
||||
if (*PDAMAGEBLINK || *PVFR == 0 || PMONITOR->pendingFrame)
|
||||
g_pCompositor->scheduleFrameForMonitor(PMONITOR);
|
||||
|
||||
PMONITOR->pendingFrame = false;
|
||||
|
||||
if (*PDEBUGOVERLAY == 1) {
|
||||
const float µs = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now() - startRender).count() / 1000.f;
|
||||
g_pDebugOverlay->renderData(PMONITOR, µs);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "../includes.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
class CColor {
|
||||
public:
|
||||
|
|
@ -13,7 +13,7 @@ class CColor {
|
|||
uint64_t getAsHex();
|
||||
|
||||
CColor operator-(const CColor& c2) const {
|
||||
return CColor(r - c2.r, g - c2.g, b - c2.b, a - c2.a);
|
||||
return CColor(r - c2.r, g - c2.g, b - c2.b, a - c2.a);
|
||||
}
|
||||
|
||||
CColor operator+(const CColor& c2) const {
|
||||
|
|
|
|||
|
|
@ -42,6 +42,9 @@ class CMonitor {
|
|||
bool enabled10bit = false; // as above, this can be TRUE even if 10 bit failed.
|
||||
bool createdByUser = false;
|
||||
|
||||
bool pendingFrame = false; // if we schedule a frame during rendering, reschedule it after
|
||||
bool renderingActive = false;
|
||||
|
||||
// mirroring
|
||||
CMonitor* pMirrorOf = nullptr;
|
||||
std::vector<CMonitor*> mirrors;
|
||||
|
|
|
|||
|
|
@ -1,44 +1,79 @@
|
|||
#include "HookSystemManager.hpp"
|
||||
|
||||
#include "../plugins/PluginSystem.hpp"
|
||||
|
||||
CHookSystemManager::CHookSystemManager() {
|
||||
; //
|
||||
}
|
||||
|
||||
// returns the pointer to the function
|
||||
HOOK_CALLBACK_FN* CHookSystemManager::hookDynamic(const std::string& event, HOOK_CALLBACK_FN fn) {
|
||||
HOOK_CALLBACK_FN* CHookSystemManager::hookDynamic(const std::string& event, HOOK_CALLBACK_FN fn, HANDLE handle) {
|
||||
const auto PVEC = getVecForEvent(event);
|
||||
const auto PFN = &m_lCallbackFunctions.emplace_back(fn);
|
||||
PVEC->emplace_back(PFN);
|
||||
PVEC->emplace_back(SCallbackFNPtr{PFN, handle});
|
||||
return PFN;
|
||||
}
|
||||
|
||||
void CHookSystemManager::hookStatic(const std::string& event, HOOK_CALLBACK_FN* fn) {
|
||||
void CHookSystemManager::hookStatic(const std::string& event, HOOK_CALLBACK_FN* fn, HANDLE handle) {
|
||||
const auto PVEC = getVecForEvent(event);
|
||||
PVEC->emplace_back(fn);
|
||||
PVEC->emplace_back(SCallbackFNPtr{fn, handle});
|
||||
}
|
||||
|
||||
void CHookSystemManager::unhook(HOOK_CALLBACK_FN* fn) {
|
||||
std::erase_if(m_lCallbackFunctions, [&](const auto& other) { return &other == fn; });
|
||||
for (auto& [k, v] : m_lpRegisteredHooks) {
|
||||
std::erase_if(v, [&](const auto& other) { return other == fn; });
|
||||
std::erase_if(v, [&](const auto& other) { return other.fn == fn; });
|
||||
}
|
||||
}
|
||||
|
||||
void CHookSystemManager::emit(const std::vector<HOOK_CALLBACK_FN*>* callbacks, std::any data) {
|
||||
void CHookSystemManager::emit(const std::vector<SCallbackFNPtr>* callbacks, std::any data) {
|
||||
if (callbacks->empty())
|
||||
return;
|
||||
|
||||
for (auto& cb : *callbacks)
|
||||
(*cb)(cb, data);
|
||||
std::vector<HANDLE> faultyHandles;
|
||||
|
||||
for (auto& cb : *callbacks) {
|
||||
|
||||
m_bCurrentEventPlugin = false;
|
||||
|
||||
if (!cb.handle) {
|
||||
// we don't guard hl hooks
|
||||
(*cb.fn)(cb.fn, data);
|
||||
continue;
|
||||
}
|
||||
|
||||
m_bCurrentEventPlugin = true;
|
||||
|
||||
if (std::find(faultyHandles.begin(), faultyHandles.end(), cb.handle) != faultyHandles.end())
|
||||
continue;
|
||||
|
||||
try {
|
||||
if (!setjmp(m_jbHookFaultJumpBuf))
|
||||
(*cb.fn)(cb.fn, data);
|
||||
else {
|
||||
// this module crashed.
|
||||
throw std::exception();
|
||||
}
|
||||
} catch (std::exception& e) {
|
||||
// TODO: this works only once...?
|
||||
faultyHandles.push_back(cb.handle);
|
||||
Debug::log(ERR, " [hookSystem] Hook from plugin %lx caused a SIGSEGV, queueing for unloading.", cb.handle);
|
||||
}
|
||||
}
|
||||
|
||||
if (!faultyHandles.empty()) {
|
||||
for (auto& h : faultyHandles)
|
||||
g_pPluginSystem->unloadPlugin(g_pPluginSystem->getPluginByHandle(h), true);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<HOOK_CALLBACK_FN*>* CHookSystemManager::getVecForEvent(const std::string& event) {
|
||||
std::vector<SCallbackFNPtr>* CHookSystemManager::getVecForEvent(const std::string& event) {
|
||||
auto IT = std::find_if(m_lpRegisteredHooks.begin(), m_lpRegisteredHooks.end(), [&](const auto& other) { return other.first == event; });
|
||||
|
||||
if (IT != m_lpRegisteredHooks.end())
|
||||
return &IT->second;
|
||||
|
||||
Debug::log(LOG, "[hookSystem] New hook event registered: %s", event.c_str());
|
||||
Debug::log(LOG, " [hookSystem] New hook event registered: %s", event.c_str());
|
||||
|
||||
return &m_lpRegisteredHooks.emplace_back(std::make_pair<>(event, std::vector<HOOK_CALLBACK_FN*>{})).second;
|
||||
return &m_lpRegisteredHooks.emplace_back(std::make_pair<>(event, std::vector<SCallbackFNPtr>{})).second;
|
||||
}
|
||||
|
|
@ -4,11 +4,21 @@
|
|||
|
||||
#include <unordered_map>
|
||||
#include <any>
|
||||
#include <array>
|
||||
#include <list>
|
||||
|
||||
#include <csetjmp>
|
||||
|
||||
#include "../plugins/PluginAPI.hpp"
|
||||
|
||||
// global typedef for hooked functions. Passes itself as a ptr when called, and `data` additionally.
|
||||
typedef std::function<void(void*, std::any)> HOOK_CALLBACK_FN;
|
||||
|
||||
struct SCallbackFNPtr {
|
||||
HOOK_CALLBACK_FN* fn = nullptr;
|
||||
HANDLE handle = nullptr;
|
||||
};
|
||||
|
||||
#define EMIT_HOOK_EVENT(name, param) \
|
||||
{ \
|
||||
static auto* const PEVENTVEC = g_pHookSystem->getVecForEvent(name); \
|
||||
|
|
@ -20,17 +30,20 @@ class CHookSystemManager {
|
|||
CHookSystemManager();
|
||||
|
||||
// returns the pointer to the function
|
||||
HOOK_CALLBACK_FN* hookDynamic(const std::string& event, HOOK_CALLBACK_FN fn);
|
||||
void hookStatic(const std::string& event, HOOK_CALLBACK_FN* fn);
|
||||
void unhook(HOOK_CALLBACK_FN* fn);
|
||||
HOOK_CALLBACK_FN* hookDynamic(const std::string& event, HOOK_CALLBACK_FN fn, HANDLE handle = nullptr);
|
||||
void hookStatic(const std::string& event, HOOK_CALLBACK_FN* fn, HANDLE handle = nullptr);
|
||||
void unhook(HOOK_CALLBACK_FN* fn);
|
||||
|
||||
void emit(const std::vector<HOOK_CALLBACK_FN*>* callbacks, std::any data = 0);
|
||||
std::vector<HOOK_CALLBACK_FN*>* getVecForEvent(const std::string& event);
|
||||
void emit(const std::vector<SCallbackFNPtr>* callbacks, std::any data = 0);
|
||||
std::vector<SCallbackFNPtr>* getVecForEvent(const std::string& event);
|
||||
|
||||
bool m_bCurrentEventPlugin = false;
|
||||
jmp_buf m_jbHookFaultJumpBuf;
|
||||
|
||||
private:
|
||||
// todo: this is slow. Maybe static ptrs should be somehow allowed. unique ptr for vec?
|
||||
std::list<std::pair<std::string, std::vector<HOOK_CALLBACK_FN*>>> m_lpRegisteredHooks;
|
||||
std::list<HOOK_CALLBACK_FN> m_lCallbackFunctions;
|
||||
std::list<std::pair<std::string, std::vector<SCallbackFNPtr>>> m_lpRegisteredHooks;
|
||||
std::list<HOOK_CALLBACK_FN> m_lCallbackFunctions;
|
||||
};
|
||||
|
||||
inline std::unique_ptr<CHookSystemManager> g_pHookSystem;
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
class CInputManager;
|
||||
class CConfigManager;
|
||||
class CPluginSystem;
|
||||
|
||||
struct SKeybind {
|
||||
std::string key = "";
|
||||
|
|
|
|||
|
|
@ -1,29 +1,51 @@
|
|||
#include "LayoutManager.hpp"
|
||||
|
||||
IHyprLayout* CLayoutManager::getCurrentLayout() {
|
||||
switch (m_iCurrentLayoutID) {
|
||||
case LAYOUT_DWINDLE: return &m_cDwindleLayout;
|
||||
case LAYOUT_MASTER: return &m_cMasterLayout;
|
||||
}
|
||||
CLayoutManager::CLayoutManager() {
|
||||
m_vLayouts.emplace_back(std::make_pair<>("dwindle", &m_cDwindleLayout));
|
||||
m_vLayouts.emplace_back(std::make_pair<>("master", &m_cMasterLayout));
|
||||
}
|
||||
|
||||
// fallback
|
||||
return &m_cDwindleLayout;
|
||||
IHyprLayout* CLayoutManager::getCurrentLayout() {
|
||||
return m_vLayouts[m_iCurrentLayoutID].second;
|
||||
}
|
||||
|
||||
void CLayoutManager::switchToLayout(std::string layout) {
|
||||
if (layout == "dwindle") {
|
||||
if (m_iCurrentLayoutID != LAYOUT_DWINDLE) {
|
||||
for (size_t i = 0; i < m_vLayouts.size(); ++i) {
|
||||
if (m_vLayouts[i].first == layout) {
|
||||
getCurrentLayout()->onDisable();
|
||||
m_iCurrentLayoutID = LAYOUT_DWINDLE;
|
||||
m_iCurrentLayoutID = i;
|
||||
getCurrentLayout()->onEnable();
|
||||
return;
|
||||
}
|
||||
} else if (layout == "master") {
|
||||
if (m_iCurrentLayoutID != LAYOUT_MASTER) {
|
||||
getCurrentLayout()->onDisable();
|
||||
m_iCurrentLayoutID = LAYOUT_MASTER;
|
||||
getCurrentLayout()->onEnable();
|
||||
}
|
||||
} else {
|
||||
Debug::log(ERR, "Unknown layout %s!", layout.c_str());
|
||||
}
|
||||
|
||||
Debug::log(ERR, "Unknown layout!");
|
||||
}
|
||||
|
||||
bool CLayoutManager::addLayout(const std::string& name, IHyprLayout* layout) {
|
||||
if (std::find_if(m_vLayouts.begin(), m_vLayouts.end(), [&](const auto& other) { return other.first == name || other.second == layout; }) != m_vLayouts.end())
|
||||
return false;
|
||||
|
||||
m_vLayouts.emplace_back(std::make_pair<>(name, layout));
|
||||
|
||||
Debug::log(LOG, "Added new layout %s at %lx", name.c_str(), layout);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CLayoutManager::removeLayout(IHyprLayout* layout) {
|
||||
const auto IT = std::find_if(m_vLayouts.begin(), m_vLayouts.end(), [&](const auto& other) { return other.second == layout; });
|
||||
|
||||
if (IT == m_vLayouts.end() || IT->first == "dwindle" || IT->first == "master")
|
||||
return false;
|
||||
|
||||
if (m_iCurrentLayoutID == IT - m_vLayouts.begin()) {
|
||||
switchToLayout("dwindle");
|
||||
}
|
||||
|
||||
Debug::log(LOG, "Removed a layout %s at %lx", IT->first.c_str(), layout);
|
||||
|
||||
std::erase(m_vLayouts, *IT);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,20 +5,28 @@
|
|||
|
||||
class CLayoutManager {
|
||||
public:
|
||||
CLayoutManager();
|
||||
|
||||
IHyprLayout* getCurrentLayout();
|
||||
|
||||
void switchToLayout(std::string);
|
||||
|
||||
bool addLayout(const std::string& name, IHyprLayout* layout);
|
||||
bool removeLayout(IHyprLayout* layout);
|
||||
|
||||
private:
|
||||
enum HYPRLAYOUTS {
|
||||
enum HYPRLAYOUTS
|
||||
{
|
||||
LAYOUT_DWINDLE = 0,
|
||||
LAYOUT_MASTER
|
||||
};
|
||||
|
||||
HYPRLAYOUTS m_iCurrentLayoutID = LAYOUT_DWINDLE;
|
||||
int m_iCurrentLayoutID = LAYOUT_DWINDLE;
|
||||
|
||||
CHyprDwindleLayout m_cDwindleLayout;
|
||||
CHyprMasterLayout m_cMasterLayout;
|
||||
CHyprDwindleLayout m_cDwindleLayout;
|
||||
CHyprMasterLayout m_cMasterLayout;
|
||||
|
||||
std::vector<std::pair<std::string, IHyprLayout*>> m_vLayouts;
|
||||
};
|
||||
|
||||
inline std::unique_ptr<CLayoutManager> g_pLayoutManager;
|
||||
|
|
@ -16,6 +16,7 @@ executable('Hyprland', src,
|
|||
xcb_dep,
|
||||
backtrace_dep,
|
||||
systemd_dep,
|
||||
udis86,
|
||||
|
||||
dependency('pixman-1'),
|
||||
dependency('gl', 'opengl'),
|
||||
|
|
|
|||
151
src/plugins/HookSystem.cpp
Normal file
151
src/plugins/HookSystem.cpp
Normal 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; });
|
||||
}
|
||||
48
src/plugins/HookSystem.hpp
Normal file
48
src/plugins/HookSystem.hpp
Normal 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
193
src/plugins/PluginAPI.cpp
Normal 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
217
src/plugins/PluginAPI.hpp
Normal 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);
|
||||
};
|
||||
139
src/plugins/PluginSystem.cpp
Normal file
139
src/plugins/PluginSystem.cpp
Normal 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;
|
||||
}
|
||||
44
src/plugins/PluginSystem.hpp
Normal file
44
src/plugins/PluginSystem.hpp
Normal 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;
|
||||
|
|
@ -6,7 +6,8 @@ enum eDecorationType
|
|||
{
|
||||
DECORATION_NONE = -1,
|
||||
DECORATION_GROUPBAR,
|
||||
DECORATION_SHADOW
|
||||
DECORATION_SHADOW,
|
||||
DECORATION_CUSTOM
|
||||
};
|
||||
|
||||
struct SWindowDecorationExtents {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue