#define WLR_USE_UNSTABLE #include #include #include #include #include #include #include #include #include using namespace Hyprutils::String; #include "globals.hpp" #include "overview.hpp" #include "ExpoGesture.hpp" // Methods inline CFunctionHook* g_pRenderWorkspaceHook = nullptr; inline CFunctionHook* g_pAddDamageHookA = nullptr; inline CFunctionHook* g_pAddDamageHookB = nullptr; typedef void (*origRenderWorkspace)(void*, PHLMONITOR, PHLWORKSPACE, timespec*, const CBox&); typedef void (*origAddDamageA)(void*, const CBox&); typedef void (*origAddDamageB)(void*, const pixman_region32_t*); static bool g_unloading = false; // Do NOT change this function. APICALL EXPORT std::string PLUGIN_API_VERSION() { return HYPRLAND_API_VERSION; } static bool renderingOverview = false; // static void hkRenderWorkspace(void* thisptr, PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, timespec* now, const CBox& geometry) { if (!g_pOverview || renderingOverview || g_pOverview->blockOverviewRendering || g_pOverview->pMonitor != pMonitor) ((origRenderWorkspace)(g_pRenderWorkspaceHook->m_original))(thisptr, pMonitor, pWorkspace, now, geometry); else g_pOverview->render(); } static void hkAddDamageA(void* thisptr, const CBox& box) { const auto PMONITOR = (CMonitor*)thisptr; if (!g_pOverview || g_pOverview->pMonitor != PMONITOR->m_self || g_pOverview->blockDamageReporting) { ((origAddDamageA)g_pAddDamageHookA->m_original)(thisptr, box); return; } g_pOverview->onDamageReported(); } static void hkAddDamageB(void* thisptr, const pixman_region32_t* rg) { const auto PMONITOR = (CMonitor*)thisptr; if (!g_pOverview || g_pOverview->pMonitor != PMONITOR->m_self || g_pOverview->blockDamageReporting) { ((origAddDamageB)g_pAddDamageHookB->m_original)(thisptr, rg); return; } g_pOverview->onDamageReported(); } static SDispatchResult onExpoDispatcher(std::string arg) { if (g_pOverview && g_pOverview->m_isSwiping) return {.success = false, .error = "already swiping"}; if (arg == "select") { if (g_pOverview) { g_pOverview->selectHoveredWorkspace(); g_pOverview->close(); } return {}; } if (arg == "toggle") { if (g_pOverview) g_pOverview->close(); else { renderingOverview = true; g_pOverview = std::make_unique(g_pCompositor->m_lastMonitor->m_activeWorkspace); renderingOverview = false; } return {}; } if (arg == "off" || arg == "close" || arg == "disable") { if (g_pOverview) g_pOverview->close(); return {}; } if (g_pOverview) return {}; renderingOverview = true; g_pOverview = std::make_unique(g_pCompositor->m_lastMonitor->m_activeWorkspace); renderingOverview = false; return {}; } static void failNotif(const std::string& reason) { HyprlandAPI::addNotification(PHANDLE, "[hyprexpo] Failure in initialization: " + reason, CHyprColor{1.0, 0.2, 0.2, 1.0}, 5000); } static Hyprlang::CParseResult expoGestureKeyword(const char* LHS, const char* RHS) { Hyprlang::CParseResult result; if (g_unloading) return result; CConstVarList data(RHS); size_t fingerCount = 0; eTrackpadGestureDirection direction = TRACKPAD_GESTURE_DIR_NONE; try { fingerCount = std::stoul(std::string{data[0]}); } catch (...) { result.setError(std::format("Invalid value {} for finger count", data[0]).c_str()); return result; } if (fingerCount <= 1 || fingerCount >= 10) { result.setError(std::format("Invalid value {} for finger count", data[0]).c_str()); return result; } direction = g_pTrackpadGestures->dirForString(data[1]); if (direction == TRACKPAD_GESTURE_DIR_NONE) { result.setError(std::format("Invalid direction: {}", data[1]).c_str()); return result; } int startDataIdx = 2; uint32_t modMask = 0; float deltaScale = 1.F; while (true) { if (data[startDataIdx].starts_with("mod:")) { modMask = g_pKeybindManager->stringToModMask(std::string{data[startDataIdx].substr(4)}); startDataIdx++; continue; } else if (data[startDataIdx].starts_with("scale:")) { try { deltaScale = std::clamp(std::stof(std::string{data[startDataIdx].substr(6)}), 0.1F, 10.F); startDataIdx++; continue; } catch (...) { result.setError(std::format("Invalid delta scale: {}", std::string{data[startDataIdx].substr(6)}).c_str()); return result; } } break; } std::expected resultFromGesture; if (data[startDataIdx] == "expo") resultFromGesture = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale); else if (data[startDataIdx] == "unset") resultFromGesture = g_pTrackpadGestures->removeGesture(fingerCount, direction, modMask, deltaScale); else { result.setError(std::format("Invalid gesture: {}", data[startDataIdx]).c_str()); return result; } if (!resultFromGesture) { result.setError(resultFromGesture.error().c_str()); return result; } return result; } APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { PHANDLE = handle; const std::string HASH = __hyprland_api_get_hash(); const std::string CLIENT_HASH = __hyprland_api_get_client_hash(); if (HASH != CLIENT_HASH) { failNotif("Version mismatch (headers ver is not equal to running hyprland ver)"); throw std::runtime_error("[he] Version mismatch"); } auto FNS = HyprlandAPI::findFunctionsByName(PHANDLE, "renderWorkspace"); if (FNS.empty()) { failNotif("no fns for hook renderWorkspace"); throw std::runtime_error("[he] No fns for hook renderWorkspace"); } g_pRenderWorkspaceHook = HyprlandAPI::createFunctionHook(PHANDLE, FNS[0].address, (void*)hkRenderWorkspace); FNS = HyprlandAPI::findFunctionsByName(PHANDLE, "addDamageEPK15pixman_region32"); if (FNS.empty()) { failNotif("no fns for hook addDamageEPK15pixman_region32"); throw std::runtime_error("[he] No fns for hook addDamageEPK15pixman_region32"); } g_pAddDamageHookB = HyprlandAPI::createFunctionHook(PHANDLE, FNS[0].address, (void*)hkAddDamageB); FNS = HyprlandAPI::findFunctionsByName(PHANDLE, "_ZN8CMonitor9addDamageERKN9Hyprutils4Math4CBoxE"); if (FNS.empty()) { failNotif("no fns for hook _ZN8CMonitor9addDamageERKN9Hyprutils4Math4CBoxE"); throw std::runtime_error("[he] No fns for hook _ZN8CMonitor9addDamageERKN9Hyprutils4Math4CBoxE"); } g_pAddDamageHookA = HyprlandAPI::createFunctionHook(PHANDLE, FNS[0].address, (void*)hkAddDamageA); bool success = g_pRenderWorkspaceHook->hook(); success = success && g_pAddDamageHookA->hook(); success = success && g_pAddDamageHookB->hook(); if (!success) { failNotif("Failed initializing hooks"); throw std::runtime_error("[he] Failed initializing hooks"); } static auto P = HyprlandAPI::registerCallbackDynamic(PHANDLE, "preRender", [](void* self, SCallbackInfo& info, std::any param) { if (!g_pOverview) return; g_pOverview->onPreRender(); }); HyprlandAPI::addDispatcherV2(PHANDLE, "hyprexpo:expo", ::onExpoDispatcher); HyprlandAPI::addConfigKeyword(PHANDLE, "hyprexpo-gesture", ::expoGestureKeyword, {}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:columns", Hyprlang::INT{3}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:gap_size", Hyprlang::INT{5}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:bg_col", Hyprlang::INT{0xFF111111}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:workspace_method", Hyprlang::STRING{"center current"}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:skip_empty", Hyprlang::INT{0}); HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:gesture_distance", Hyprlang::INT{200}); HyprlandAPI::reloadConfig(); return {"hyprexpo", "A plugin for an overview", "Vaxry", "1.0"}; } APICALL EXPORT void PLUGIN_EXIT() { g_pHyprRenderer->m_renderPass.removeAllOfType("COverviewPassElement"); g_unloading = true; g_pConfigManager->reload(); // we need to reload now to clear all the gestures }