diff options
Diffstat (limited to 'src/config')
| -rw-r--r-- | src/config/lua/ConfigManager.cpp | 2 | ||||
| -rw-r--r-- | src/config/lua/ConfigManager.hpp | 65 | ||||
| -rw-r--r-- | src/config/lua/bindings/LuaBindingsInternal.hpp | 1 | ||||
| -rw-r--r-- | src/config/lua/bindings/LuaBindingsRegistration.cpp | 1 | ||||
| -rw-r--r-- | src/config/lua/layout/LuaLayoutContext.cpp | 144 | ||||
| -rw-r--r-- | src/config/lua/layout/LuaLayoutContext.hpp | 20 | ||||
| -rw-r--r-- | src/config/lua/layout/LuaLayoutProvider.cpp | 323 | ||||
| -rw-r--r-- | src/config/lua/layout/LuaLayoutProvider.hpp | 59 | ||||
| -rw-r--r-- | src/config/lua/layout/LuaLayoutTarget.cpp | 98 | ||||
| -rw-r--r-- | src/config/lua/layout/LuaLayoutTarget.hpp | 18 | ||||
| -rw-r--r-- | src/config/lua/objects/LuaWindow.cpp | 2 | ||||
| -rw-r--r-- | src/config/lua/objects/LuaWorkspace.cpp | 2 |
12 files changed, 705 insertions, 30 deletions
diff --git a/src/config/lua/ConfigManager.cpp b/src/config/lua/ConfigManager.cpp index 3ad70b9b..0fe75b18 100644 --- a/src/config/lua/ConfigManager.cpp +++ b/src/config/lua/ConfigManager.cpp @@ -270,6 +270,7 @@ void CConfigManager::reinitLuaState() { m_eventHandler.reset(); cleanTimers(); + clearLuaLayoutProviders(); if (m_lua) { lua_close(m_lua); @@ -420,6 +421,7 @@ void CConfigManager::reload() { Desktop::Rule::ruleEngine()->clearAllRules(); g_pTrackpadGestures->clearGestures(); cleanTimers(); + clearLuaLayoutProviders(); m_luaWindowRules.clear(); m_luaLayerRules.clear(); m_errors.clear(); diff --git a/src/config/lua/ConfigManager.hpp b/src/config/lua/ConfigManager.hpp index 77387758..4e670b23 100644 --- a/src/config/lua/ConfigManager.hpp +++ b/src/config/lua/ConfigManager.hpp @@ -8,6 +8,7 @@ #include <chrono> #include <string_view> #include <unordered_map> +#include <expected> #include "../../helpers/memory/Memory.hpp" #include "../ConfigManager.hpp" @@ -38,6 +39,10 @@ namespace Config::Lua { class CConfigManagerPluginLuaTestAccessor; } +namespace Config::Lua::Layouts { + struct SLuaLayoutProvider; +} + namespace Config::Lua::Bindings { void registerBindings(lua_State* L, CConfigManager* mgr); } @@ -87,6 +92,7 @@ namespace Config::Lua { void registerLuaRef(int ref); void callLuaFn(int ref); + std::expected<void, std::string> registerLuaLayoutProvider(std::string name, lua_State* L, int providerTableIdx); // execute an arbitrary lua string on the current state. std::optional<std::string> eval(const std::string& code); @@ -100,6 +106,7 @@ namespace Config::Lua { static constexpr int LUA_TIMEOUT_EVENT_CALLBACK_MS = 50; static constexpr int LUA_TIMEOUT_KEYBIND_CALLBACK_MS = 100; static constexpr int LUA_TIMEOUT_TIMER_CALLBACK_MS = 50; + static constexpr int LUA_TIMEOUT_LAYOUT_CALLBACK_MS = 50; static constexpr int LUA_TIMEOUT_EVAL_MS = 250; static constexpr int LUA_TIMEOUT_DISPATCH_MS = 100; @@ -136,34 +143,36 @@ namespace Config::Lua { std::unordered_map<std::string, SP<Desktop::Rule::CLayerRule>> m_luaLayerRules; private: - void reinitLuaState(); - void postConfigReload(); - void registerValue(const char* name, ILuaConfigValue* val); - void cleanTimers(); - void clearHeldLuaRefs(); - std::string luaConfigValueName(const std::string& s); - std::expected<void, std::string> registerPluginLuaFunctionInState(uint64_t id, const std::string& namespace_, const std::string& name); - std::expected<void, std::string> unregisterPluginLuaFunctionInState(const std::string& namespace_, const std::string& name); - void erasePluginLuaFunction(uint64_t id); - void reregisterLuaPluginFns(); - - static void watchdogHook(lua_State* L, lua_Debug* ar); - - lua_State* m_lua = nullptr; - - bool m_lastConfigVerificationWasSuccessful = true; - bool m_isFirstLaunch = true; - bool m_manualCrashInitiated = false; - bool m_watchdogActive = false; - bool m_isParsingConfig = false; - bool m_isEvaluating = false; - - std::chrono::steady_clock::time_point m_watchdogDeadline; - std::string m_watchdogContext; - - std::string m_mainConfigPath; - - std::vector<int> m_heldLuaRefs; + void reinitLuaState(); + void postConfigReload(); + void registerValue(const char* name, ILuaConfigValue* val); + void cleanTimers(); + void clearLuaLayoutProviders(); + void clearHeldLuaRefs(); + std::string luaConfigValueName(const std::string& s); + std::expected<void, std::string> registerPluginLuaFunctionInState(uint64_t id, const std::string& namespace_, const std::string& name); + std::expected<void, std::string> unregisterPluginLuaFunctionInState(const std::string& namespace_, const std::string& name); + void erasePluginLuaFunction(uint64_t id); + void reregisterLuaPluginFns(); + + static void watchdogHook(lua_State* L, lua_Debug* ar); + + lua_State* m_lua = nullptr; + + bool m_lastConfigVerificationWasSuccessful = true; + bool m_isFirstLaunch = true; + bool m_manualCrashInitiated = false; + bool m_watchdogActive = false; + bool m_isParsingConfig = false; + bool m_isEvaluating = false; + + std::chrono::steady_clock::time_point m_watchdogDeadline; + std::string m_watchdogContext; + + std::string m_mainConfigPath; + + std::vector<int> m_heldLuaRefs; + std::vector<SP<Layouts::SLuaLayoutProvider>> m_luaLayoutProviders; // this is here for legacy reasons. std::unordered_map<std::string, const void*> m_configPtrMap; diff --git a/src/config/lua/bindings/LuaBindingsInternal.hpp b/src/config/lua/bindings/LuaBindingsInternal.hpp index 9b61360c..40fe804a 100644 --- a/src/config/lua/bindings/LuaBindingsInternal.hpp +++ b/src/config/lua/bindings/LuaBindingsInternal.hpp @@ -202,6 +202,7 @@ namespace Config::Lua::Bindings::Internal { bool hasTableField(lua_State* L, int tableIdx, const char* field); void registerToplevelBindings(lua_State* L, CConfigManager* mgr); + void registerLayoutBindings(lua_State* L, CConfigManager* mgr); void registerQueryBindings(lua_State* L); void registerNotificationBindings(lua_State* L); void registerConfigRuleBindings(lua_State* L, CConfigManager* mgr); diff --git a/src/config/lua/bindings/LuaBindingsRegistration.cpp b/src/config/lua/bindings/LuaBindingsRegistration.cpp index 0cb59364..f59e374b 100644 --- a/src/config/lua/bindings/LuaBindingsRegistration.cpp +++ b/src/config/lua/bindings/LuaBindingsRegistration.cpp @@ -86,6 +86,7 @@ void Internal::registerBindingsImpl(lua_State* L, CConfigManager* mgr) { Internal::registerConfigRuleBindings(L, mgr); Internal::registerToplevelBindings(L, mgr); + Internal::registerLayoutBindings(L, mgr); Internal::registerQueryBindings(L); Internal::registerDispatcherBindings(L); Internal::registerNotificationBindings(L); diff --git a/src/config/lua/layout/LuaLayoutContext.cpp b/src/config/lua/layout/LuaLayoutContext.cpp new file mode 100644 index 00000000..f7370e1b --- /dev/null +++ b/src/config/lua/layout/LuaLayoutContext.cpp @@ -0,0 +1,144 @@ +#include "LuaLayoutContext.hpp" + +#include "LuaLayoutTarget.hpp" +#include "../bindings/LuaBindingsInternal.hpp" + +#include <algorithm> +#include <cmath> +#include <string_view> + +using namespace Config::Lua::Layouts; + +void Config::Lua::Layouts::pushBox(lua_State* L, const CBox& box) { + lua_newtable(L); + lua_pushnumber(L, box.x); + lua_setfield(L, -2, "x"); + lua_pushnumber(L, box.y); + lua_setfield(L, -2, "y"); + lua_pushnumber(L, box.w); + lua_setfield(L, -2, "w"); + lua_pushnumber(L, box.h); + lua_setfield(L, -2, "h"); +} + +bool Config::Lua::Layouts::boxFromTable(lua_State* L, int idx, CBox& box) { + idx = lua_absindex(L, idx); + if (!lua_istable(L, idx)) + return false; + + auto getNum = [&](const char* key, double& out) -> bool { + lua_getfield(L, idx, key); + if (!lua_isnumber(L, -1)) { + lua_pop(L, 1); + return false; + } + out = lua_tonumber(L, -1); + lua_pop(L, 1); + return true; + }; + + return getNum("x", box.x) && getNum("y", box.y) && getNum("w", box.w) && getNum("h", box.h); +} + +static CBox areaFromContext(lua_State* L, int idx) { + idx = lua_absindex(L, idx); + CBox area; + lua_getfield(L, idx, "area"); + boxFromTable(L, -1, area); + lua_pop(L, 1); + return area; +} + +static size_t targetCountFromContext(lua_State* L, int idx) { + idx = lua_absindex(L, idx); + size_t count = 0; + lua_getfield(L, idx, "targets"); + if (lua_istable(L, -1)) + count = lua_rawlen(L, -1); + lua_pop(L, 1); + return count; +} + +static int ctxGridCell(lua_State* L) { + const auto AREA = areaFromContext(L, 1); + const int i = std::max(1, sc<int>(luaL_checkinteger(L, 2))); + const int cols = std::max(1, sc<int>(luaL_checkinteger(L, 3))); + int rows = 0; + + if (lua_gettop(L) >= 4 && lua_isnumber(L, 4)) + rows = std::max(1, sc<int>(lua_tointeger(L, 4))); + else { + const auto count = std::max<size_t>(1, targetCountFromContext(L, 1)); + rows = std::max(1, sc<int>(std::ceil(sc<double>(count) / sc<double>(cols)))); + } + + const int row = (i - 1) / cols; + const int col = (i - 1) % cols; + + pushBox(L, CBox{AREA.x + AREA.w * col / cols, AREA.y + AREA.h * row / rows, AREA.w / cols, AREA.h / rows}.noNegativeSize()); + return 1; +} + +static int ctxColumn(lua_State* L) { + const auto AREA = areaFromContext(L, 1); + const int i = std::max(1, sc<int>(luaL_checkinteger(L, 2))); + const int n = std::max(1, sc<int>(luaL_checkinteger(L, 3))); + + pushBox(L, CBox{AREA.x + AREA.w * (i - 1) / n, AREA.y, AREA.w / n, AREA.h}.noNegativeSize()); + return 1; +} + +static int ctxRow(lua_State* L) { + const auto AREA = areaFromContext(L, 1); + const int i = std::max(1, sc<int>(luaL_checkinteger(L, 2))); + const int n = std::max(1, sc<int>(luaL_checkinteger(L, 3))); + + pushBox(L, CBox{AREA.x, AREA.y + AREA.h * (i - 1) / n, AREA.w, AREA.h / n}.noNegativeSize()); + return 1; +} + +static int ctxSplit(lua_State* L) { + CBox area; + if (!boxFromTable(L, 2, area)) + return Config::Lua::Bindings::Internal::configError(L, "ctx:split expects a box table as first argument"); + + const std::string_view side = luaL_checkstring(L, 3); + const double ratio = std::clamp(luaL_checknumber(L, 4), 0.0, 1.0); + + if (side == "left") + pushBox(L, CBox{area.x, area.y, area.w * ratio, area.h}.noNegativeSize()); + else if (side == "right") + pushBox(L, CBox{area.x + area.w * (1.0 - ratio), area.y, area.w * ratio, area.h}.noNegativeSize()); + else if (side == "top" || side == "up") + pushBox(L, CBox{area.x, area.y, area.w, area.h * ratio}.noNegativeSize()); + else if (side == "bottom" || side == "down") + pushBox(L, CBox{area.x, area.y + area.h * (1.0 - ratio), area.w, area.h * ratio}.noNegativeSize()); + else + return Config::Lua::Bindings::Internal::configError(L, "ctx:split side must be left, right, top, or bottom"); + + return 1; +} + +void Config::Lua::Layouts::pushLayoutContext(lua_State* L, const std::vector<SP<Layout::ITarget>>& targets, const CBox& area) { + lua_newtable(L); + + pushBox(L, area); + lua_setfield(L, -2, "area"); + + lua_newtable(L); + int i = 1; + for (const auto& target : targets) { + pushLayoutTarget(L, target, i); + lua_rawseti(L, -2, i++); + } + lua_setfield(L, -2, "targets"); + + lua_pushcfunction(L, ctxGridCell); + lua_setfield(L, -2, "grid_cell"); + lua_pushcfunction(L, ctxColumn); + lua_setfield(L, -2, "column"); + lua_pushcfunction(L, ctxRow); + lua_setfield(L, -2, "row"); + lua_pushcfunction(L, ctxSplit); + lua_setfield(L, -2, "split"); +} diff --git a/src/config/lua/layout/LuaLayoutContext.hpp b/src/config/lua/layout/LuaLayoutContext.hpp new file mode 100644 index 00000000..2f997037 --- /dev/null +++ b/src/config/lua/layout/LuaLayoutContext.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "../../../helpers/math/Math.hpp" +#include "../../../helpers/memory/Memory.hpp" + +#include <vector> + +extern "C" { +#include <lua.h> +} + +namespace Layout { + class ITarget; +} + +namespace Config::Lua::Layouts { + void pushBox(lua_State* L, const CBox& box); + bool boxFromTable(lua_State* L, int idx, CBox& box); + void pushLayoutContext(lua_State* L, const std::vector<SP<Layout::ITarget>>& targets, const CBox& area); +} diff --git a/src/config/lua/layout/LuaLayoutProvider.cpp b/src/config/lua/layout/LuaLayoutProvider.cpp new file mode 100644 index 00000000..344362ff --- /dev/null +++ b/src/config/lua/layout/LuaLayoutProvider.cpp @@ -0,0 +1,323 @@ +#include "LuaLayoutProvider.hpp" + +#include "LuaLayoutContext.hpp" +#include "LuaLayoutTarget.hpp" +#include "../ConfigManager.hpp" +#include "../bindings/LuaBindingsInternal.hpp" + +#include "../../../debug/log/Logger.hpp" +#include "../../../layout/algorithm/Algorithm.hpp" +#include "../../../layout/space/Space.hpp" +#include "../../../layout/target/Target.hpp" +#include "../../../layout/supplementary/WorkspaceAlgoMatcher.hpp" + +#include <algorithm> +#include <cmath> +#include <format> + +using namespace Config::Lua; +using namespace Config::Lua::Layouts; + +static std::string normalizeLuaLayoutName(std::string name) { + if (!name.starts_with("lua:")) + name = "lua:" + name; + return name; +} + +CLuaTiledAlgorithm::CLuaTiledAlgorithm(SP<SLuaLayoutProvider> provider) : m_provider(std::move(provider)) { + ; +} + +void CLuaTiledAlgorithm::newTarget(SP<Layout::ITarget> target) { + m_targets.emplace_back(target); + recalculate(); +} + +void CLuaTiledAlgorithm::movedTarget(SP<Layout::ITarget> target, std::optional<Vector2D> focalPoint) { + newTarget(target); +} + +void CLuaTiledAlgorithm::removeTarget(SP<Layout::ITarget> target) { + std::erase_if(m_targets, [&target](const auto& t) { return !t || t.lock() == target; }); + recalculate(); +} + +void CLuaTiledAlgorithm::resizeTarget(const Vector2D& Δ, SP<Layout::ITarget> target, Layout::eRectCorner corner) { + recalculate(); +} + +void CLuaTiledAlgorithm::recalculate() { + auto targets = liveTargets(); + if (targets.empty()) + return; + + if (!callRecalculate(targets)) + applyDefaultGrid(targets); +} + +void CLuaTiledAlgorithm::swapTargets(SP<Layout::ITarget> a, SP<Layout::ITarget> b) { + auto ia = std::ranges::find_if(m_targets, [&a](const auto& t) { return t.lock() == a; }); + auto ib = std::ranges::find_if(m_targets, [&b](const auto& t) { return t.lock() == b; }); + + if (ia != m_targets.end() && ib != m_targets.end()) + std::iter_swap(ia, ib); + else { + if (ia != m_targets.end()) + *ia = b; + if (ib != m_targets.end()) + *ib = a; + } + + recalculate(); +} + +void CLuaTiledAlgorithm::moveTargetInDirection(SP<Layout::ITarget> t, Math::eDirection dir, bool silent) { + auto it = std::ranges::find_if(m_targets, [&t](const auto& target) { return target.lock() == t; }); + if (it == m_targets.end()) + return; + + if ((dir == Math::DIRECTION_LEFT || dir == Math::DIRECTION_UP) && it != m_targets.begin()) + std::iter_swap(it, std::prev(it)); + else if ((dir == Math::DIRECTION_RIGHT || dir == Math::DIRECTION_DOWN) && std::next(it) != m_targets.end()) + std::iter_swap(it, std::next(it)); + else + return; + + recalculate(); +} + +Config::ErrorResult CLuaTiledAlgorithm::layoutMsg(const std::string_view& sv) { + if (!m_provider || !m_provider->active || !m_provider->state) + return {}; + + auto targets = liveTargets(); + auto parent = m_parent.lock(); + auto space = parent ? parent->space() : nullptr; + if (!space) + return {}; + + lua_State* L = m_provider->state; + const int top = lua_gettop(L); + + lua_rawgeti(L, LUA_REGISTRYINDEX, m_provider->tableRef); + lua_getfield(L, -1, "layout_msg"); + lua_remove(L, -2); + + if (!lua_isfunction(L, -1)) { + lua_settop(L, top); + return {}; + } + + pushLayoutContext(L, targets, space->workArea()); + lua_pushlstring(L, sv.data(), sv.size()); + + const int status = m_provider->manager->guardedPCall(2, 1, 0, CConfigManager::LUA_TIMEOUT_LAYOUT_CALLBACK_MS, "lua layout_msg callback"); + if (status != LUA_OK) { + std::string err = lua_tostring(L, -1) ? lua_tostring(L, -1) : "unknown lua error"; + lua_settop(L, top); + reportError(err); + return Config::configError(std::format("lua layout {} layout_msg failed: {}", m_provider->name, err), Config::eConfigErrorLevel::ERROR, + Config::eConfigErrorCode::LUA_ERROR); + } + + Config::ErrorResult result = {}; + if (lua_isboolean(L, -1) && !lua_toboolean(L, -1)) + result = + Config::configError(std::format("lua layout {} rejected layoutmsg", m_provider->name), Config::eConfigErrorLevel::ERROR, Config::eConfigErrorCode::INVALID_ARGUMENT); + else if (lua_isstring(L, -1)) + result = Config::configError(lua_tostring(L, -1), Config::eConfigErrorLevel::ERROR, Config::eConfigErrorCode::INVALID_ARGUMENT); + + lua_settop(L, top); + recalculate(); + return result; +} + +std::optional<Vector2D> CLuaTiledAlgorithm::predictSizeForNewTarget() { + auto parent = m_parent.lock(); + auto space = parent ? parent->space() : nullptr; + if (!space) + return std::nullopt; + return space->workArea().size(); +} + +SP<Layout::ITarget> CLuaTiledAlgorithm::getNextCandidate(SP<Layout::ITarget> old) { + auto targets = liveTargets(); + if (targets.empty()) + return nullptr; + + auto it = std::ranges::find(targets, old); + if (it == targets.end()) + return targets.back(); + + if (targets.size() == 1) + return nullptr; + + auto next = std::next(it); + if (next == targets.end()) + next = targets.begin(); + + return *next; +} + +std::optional<std::string> CLuaTiledAlgorithm::layoutName() const { + if (!m_provider) + return std::nullopt; + return m_provider->name; +} + +std::vector<SP<Layout::ITarget>> CLuaTiledAlgorithm::liveTargets() { + std::erase_if(m_targets, [](const auto& target) { return !target || !target.lock(); }); + + std::vector<SP<Layout::ITarget>> result; + for (const auto& target : m_targets) { + if (const auto locked = target.lock(); locked) + result.emplace_back(locked); + } + return result; +} + +bool CLuaTiledAlgorithm::callRecalculate(const std::vector<SP<Layout::ITarget>>& targets) { + if (!m_provider || !m_provider->active || !m_provider->state || !m_provider->manager) + return false; + + auto parent = m_parent.lock(); + auto space = parent ? parent->space() : nullptr; + if (!space) + return false; + + lua_State* L = m_provider->state; + const int top = lua_gettop(L); + + lua_rawgeti(L, LUA_REGISTRYINDEX, m_provider->tableRef); + lua_getfield(L, -1, "recalculate"); + lua_remove(L, -2); + + if (!lua_isfunction(L, -1)) { + lua_settop(L, top); + return false; + } + + pushLayoutContext(L, targets, space->workArea()); + + const int status = m_provider->manager->guardedPCall(1, 0, 0, CConfigManager::LUA_TIMEOUT_LAYOUT_CALLBACK_MS, "lua layout recalculate callback"); + if (status != LUA_OK) { + std::string err = lua_tostring(L, -1) ? lua_tostring(L, -1) : "unknown lua error"; + lua_settop(L, top); + reportError(err); + return false; + } + + lua_settop(L, top); + return true; +} + +void CLuaTiledAlgorithm::applyDefaultGrid(const std::vector<SP<Layout::ITarget>>& targets) { + auto parent = m_parent.lock(); + auto space = parent ? parent->space() : nullptr; + if (!space || targets.empty()) + return; + + const auto AREA = space->workArea(); + const int cols = std::max(1, sc<int>(std::ceil(std::sqrt(sc<double>(targets.size()))))); + const int rows = std::max(1, sc<int>(std::ceil(sc<double>(targets.size()) / sc<double>(cols)))); + + for (size_t i = 0; i < targets.size(); ++i) { + const int col = sc<int>(i) % cols; + const int row = sc<int>(i) / cols; + targets[i]->setPositionGlobal(CBox{AREA.x + AREA.w * col / cols, AREA.y + AREA.h * row / rows, AREA.w / cols, AREA.h / rows}.noNegativeSize()); + } +} + +void CLuaTiledAlgorithm::reportError(const std::string& message) { + if (!m_provider) + return; + + Log::logger->log(Log::ERR, "[lua] layout {} error: {}", m_provider->name, message); + + if (m_provider->didError || !m_provider->manager) + return; + + m_provider->didError = true; + m_provider->manager->addError(std::format("lua layout {} error: {}", m_provider->name, message)); +} + +std::expected<void, std::string> CConfigManager::registerLuaLayoutProvider(std::string name, lua_State* L, int providerTableIdx) { + if (name.empty()) + return std::unexpected("layout name cannot be empty"); + + name = normalizeLuaLayoutName(std::move(name)); + if (name == "lua:") + return std::unexpected("layout name cannot be empty"); + + providerTableIdx = lua_absindex(L, providerTableIdx); + if (!lua_istable(L, providerTableIdx)) + return std::unexpected("provider must be a table"); + + lua_getfield(L, providerTableIdx, "recalculate"); + const bool hasRecalculate = lua_isfunction(L, -1); + lua_pop(L, 1); + + if (!hasRecalculate) + return std::unexpected("provider table must define recalculate(ctx)"); + + lua_pushvalue(L, providerTableIdx); + const int ref = luaL_ref(L, LUA_REGISTRYINDEX); + + auto provider = makeShared<SLuaLayoutProvider>(); + provider->manager = this; + provider->state = L; + provider->name = name; + provider->tableRef = ref; + + if (!Layout::Supplementary::algoMatcher()->registerTiledAlgo(name, &typeid(CLuaTiledAlgorithm), [provider] { return makeUnique<CLuaTiledAlgorithm>(provider); })) { + luaL_unref(L, LUA_REGISTRYINDEX, ref); + return std::unexpected(std::format("layout '{}' is already registered", name)); + } + + m_luaLayoutProviders.emplace_back(provider); + return {}; +} + +void CConfigManager::clearLuaLayoutProviders() { + if (m_luaLayoutProviders.empty()) + return; + + for (auto& provider : m_luaLayoutProviders) { + if (provider) + provider->active = false; + } + + for (auto& provider : m_luaLayoutProviders) { + if (provider) + Layout::Supplementary::algoMatcher()->unregisterAlgo(provider->name); + } + + for (auto& provider : m_luaLayoutProviders) { + if (provider && m_lua && provider->tableRef != LUA_NOREF) { + luaL_unref(m_lua, LUA_REGISTRYINDEX, provider->tableRef); + provider->tableRef = LUA_NOREF; + } + } + + m_luaLayoutProviders.clear(); +} + +static int hlLayoutRegister(lua_State* L) { + auto* mgr = sc<CConfigManager*>(lua_touserdata(L, lua_upvalueindex(1))); + const char* name = luaL_checkstring(L, 1); + luaL_checktype(L, 2, LUA_TTABLE); + + auto result = mgr->registerLuaLayoutProvider(name, L, 2); + if (!result) + return Config::Lua::Bindings::Internal::configError(L, "hl.layout.register: {}", result.error()); + + return 0; +} + +void Config::Lua::Bindings::Internal::registerLayoutBindings(lua_State* L, CConfigManager* mgr) { + setupLayoutTarget(L); + + lua_newtable(L); + setMgrFn(L, mgr, "register", hlLayoutRegister); + lua_setfield(L, -2, "layout"); +} diff --git a/src/config/lua/layout/LuaLayoutProvider.hpp b/src/config/lua/layout/LuaLayoutProvider.hpp new file mode 100644 index 00000000..4e223670 --- /dev/null +++ b/src/config/lua/layout/LuaLayoutProvider.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include "../../../helpers/memory/Memory.hpp" +#include "../../../helpers/math/Math.hpp" +#include "../../../layout/algorithm/TiledAlgorithm.hpp" + +#include <optional> +#include <string> +#include <string_view> +#include <vector> + +extern "C" { +#include <lua.h> +#include <lauxlib.h> +} + +namespace Config::Lua { + class CConfigManager; +} + +namespace Config::Lua::Layouts { + + struct SLuaLayoutProvider { + CConfigManager* manager = nullptr; + lua_State* state = nullptr; + std::string name; + int tableRef = LUA_NOREF; + bool active = true; + bool didError = false; + }; + + class CLuaTiledAlgorithm : public Layout::ITiledAlgorithm { + public: + explicit CLuaTiledAlgorithm(SP<SLuaLayoutProvider> provider); + virtual ~CLuaTiledAlgorithm() = default; + + virtual void newTarget(SP<Layout::ITarget> target); + virtual void movedTarget(SP<Layout::ITarget> target, std::optional<Vector2D> focalPoint = std::nullopt); + virtual void removeTarget(SP<Layout::ITarget> target); + virtual void resizeTarget(const Vector2D& Δ, SP<Layout::ITarget> target, Layout::eRectCorner corner = Layout::CORNER_NONE); + virtual void recalculate(); + virtual void swapTargets(SP<Layout::ITarget> a, SP<Layout::ITarget> b); + virtual void moveTargetInDirection(SP<Layout::ITarget> t, Math::eDirection dir, bool silent); + virtual Config::ErrorResult layoutMsg(const std::string_view& sv); + virtual std::optional<Vector2D> predictSizeForNewTarget(); + virtual SP<Layout::ITarget> getNextCandidate(SP<Layout::ITarget> old); + virtual std::optional<std::string> layoutName() const; + + private: + SP<SLuaLayoutProvider> m_provider; + std::vector<WP<Layout::ITarget>> m_targets; + + std::vector<SP<Layout::ITarget>> liveTargets(); + bool callRecalculate(const std::vector<SP<Layout::ITarget>>& targets); + void applyDefaultGrid(const std::vector<SP<Layout::ITarget>>& targets); + void reportError(const std::string& message); + }; + +} diff --git a/src/config/lua/layout/LuaLayoutTarget.cpp b/src/config/lua/layout/LuaLayoutTarget.cpp new file mode 100644 index 00000000..9113f0fd --- /dev/null +++ b/src/config/lua/layout/LuaLayoutTarget.cpp @@ -0,0 +1,98 @@ +#include "LuaLayoutTarget.hpp" + +#include "LuaLayoutContext.hpp" +#include "../bindings/LuaBindingsInternal.hpp" +#include "../objects/LuaObjectHelpers.hpp" +#include "../objects/LuaWindow.hpp" + +#include "../../../layout/target/Target.hpp" + +#include <string_view> + +using namespace Config::Lua; +using namespace Config::Lua::Layouts; + +static constexpr const char* TARGET_MT = "HL.LayoutTarget"; + +namespace { + struct SLuaLayoutTargetRef { + WP<Layout::ITarget> target; + size_t index = 0; + }; +} + +static int layoutTargetPlace(lua_State* L) { + auto* ref = sc<SLuaLayoutTargetRef*>(luaL_checkudata(L, 1, TARGET_MT)); + auto target = ref->target.lock(); + if (!target) + return 0; + + CBox box; + if (!boxFromTable(L, 2, box)) + return Bindings::Internal::configError(L, "HL.LayoutTarget: place expects a box table { x, y, w, h }"); + + target->setPositionGlobal(box.noNegativeSize()); + return 0; +} + +static int layoutTargetIndex(lua_State* L) { + auto* ref = sc<SLuaLayoutTargetRef*>(luaL_checkudata(L, 1, TARGET_MT)); + const std::string_view key = luaL_checkstring(L, 2); + + auto target = ref->target.lock(); + if (!target) { + lua_pushnil(L); + return 1; + } + + if (key == "index") + lua_pushinteger(L, sc<lua_Integer>(ref->index)); + else if (key == "window") { + const auto window = target->window(); + if (window) + Objects::CLuaWindow::push(L, window); + else + lua_pushnil(L); + } else if (key == "box") + pushBox(L, target->position()); + else if (key == "place" || key == "set_box") + lua_pushcfunction(L, layoutTargetPlace); + else + lua_pushnil(L); + + return 1; +} + +static int layoutTargetGc(lua_State* L) { + sc<SLuaLayoutTargetRef*>(lua_touserdata(L, 1))->~SLuaLayoutTargetRef(); + return 0; +} + +static int layoutTargetToString(lua_State* L) { + auto* ref = sc<SLuaLayoutTargetRef*>(luaL_checkudata(L, 1, TARGET_MT)); + auto target = ref->target.lock(); + if (!target) + lua_pushstring(L, "HL.LayoutTarget(expired)"); + else + lua_pushfstring(L, "HL.LayoutTarget(%d)", sc<int>(ref->index)); + return 1; +} + +void Config::Lua::Layouts::setupLayoutTarget(lua_State* L) { + luaL_newmetatable(L, TARGET_MT); + lua_pushcfunction(L, layoutTargetIndex); + lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, Objects::readOnlyNewIndex); + lua_setfield(L, -2, "__newindex"); + lua_pushcfunction(L, layoutTargetGc); + lua_setfield(L, -2, "__gc"); + lua_pushcfunction(L, layoutTargetToString); + lua_setfield(L, -2, "__tostring"); + lua_pop(L, 1); +} + +void Config::Lua::Layouts::pushLayoutTarget(lua_State* L, const SP<Layout::ITarget>& target, size_t index) { + new (lua_newuserdata(L, sizeof(SLuaLayoutTargetRef))) SLuaLayoutTargetRef{target, index}; + luaL_getmetatable(L, TARGET_MT); + lua_setmetatable(L, -2); +} diff --git a/src/config/lua/layout/LuaLayoutTarget.hpp b/src/config/lua/layout/LuaLayoutTarget.hpp new file mode 100644 index 00000000..5f23e74a --- /dev/null +++ b/src/config/lua/layout/LuaLayoutTarget.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "../../../helpers/memory/Memory.hpp" + +#include <cstddef> + +extern "C" { +#include <lua.h> +} + +namespace Layout { + class ITarget; +} + +namespace Config::Lua::Layouts { + void setupLayoutTarget(lua_State* L); + void pushLayoutTarget(lua_State* L, const SP<Layout::ITarget>& target, size_t index); +} diff --git a/src/config/lua/objects/LuaWindow.cpp b/src/config/lua/objects/LuaWindow.cpp index 7fe87334..41edcc78 100644 --- a/src/config/lua/objects/LuaWindow.cpp +++ b/src/config/lua/objects/LuaWindow.cpp @@ -176,7 +176,7 @@ static int windowIndex(lua_State* L) { } const auto& tiledAlgo = algo->tiledAlgo(); - const std::string name = Layout::Supplementary::algoMatcher()->getNameForTiledAlgo(&typeid(*tiledAlgo.get())); + const std::string name = Layout::Supplementary::algoMatcher()->getNameForTiledAlgo(tiledAlgo.get()); lua_newtable(L); lua_pushstring(L, name.c_str()); diff --git a/src/config/lua/objects/LuaWorkspace.cpp b/src/config/lua/objects/LuaWorkspace.cpp index 3f89b41a..8cfb32e7 100644 --- a/src/config/lua/objects/LuaWorkspace.cpp +++ b/src/config/lua/objects/LuaWorkspace.cpp @@ -136,7 +136,7 @@ static int workspaceIndex(lua_State* L) { std::string layoutName = "unknown"; if (ws->m_space && ws->m_space->algorithm() && ws->m_space->algorithm()->tiledAlgo()) { const auto& TILED_ALGO = ws->m_space->algorithm()->tiledAlgo(); - layoutName = Layout::Supplementary::algoMatcher()->getNameForTiledAlgo(&typeid(*TILED_ALGO.get())); + layoutName = Layout::Supplementary::algoMatcher()->getNameForTiledAlgo(TILED_ALGO.get()); } lua_pushstring(L, layoutName.c_str()); } else if (key == "last_window") { |
